diff --git a/.gitignore b/.gitignore index 657f2298ada..e9eb0cf2b99 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,7 @@ pkg Gemfile.lock Gemfile*.lock .rbx/ +*.gem +.rvmrc +/doc/ +.ruby-version diff --git a/.rubocop.yml b/.rubocop.yml new file mode 100644 index 00000000000..f1deca38196 --- /dev/null +++ b/.rubocop.yml @@ -0,0 +1,34 @@ +inherit_from: .rubocop_todo.yml + +# Please keep AllCops, Bundler, Layout, Style, Metrics groups and then order cops +# alphabetically +# +# References: +# * https://github.com/bbatsov/ruby-style-guide +# * https://rubocop.readthedocs.io/ +AllCops: + DisplayCopNames: true + DisplayStyleGuide: true + Exclude: + - "generators/**/*" + - "lib/active_merchant/billing/gateways/paypal/**/*" + - "lib/active_merchant/billing/gateways/paypal_express.rb" + - "vendor/**/*" + ExtraDetails: false + TargetRubyVersion: 2.3 + +# Active Merchant gateways are not amenable to length restrictions +Metrics/ClassLength: + Enabled: false + +Metrics/ModuleLength: + Enabled: false + +Layout/AlignParameters: + EnforcedStyle: with_fixed_indentation + +Layout/DotPosition: + EnforcedStyle: trailing + +Layout/CaseIndentation: + EnforcedStyle: end diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml new file mode 100644 index 00000000000..b8025e1f862 --- /dev/null +++ b/.rubocop_todo.yml @@ -0,0 +1,1095 @@ +# This configuration was generated by +# `rubocop --auto-gen-config` +# on 2018-11-20 16:45:49 -0500 using RuboCop version 0.60.0. +# The point is for the user to remove these configuration records +# one by one as the offenses are removed from the code base. +# Note that changes in the inspected code, or installation of new +# versions of RuboCop, may require this file to be generated again. + +# Offense count: 2 +# Cop supports --auto-correct. +# Configuration parameters: Include, TreatCommentsAsGroupSeparators. +# Include: **/*.gemspec +Gemspec/OrderedDependencies: + Exclude: + - 'activemerchant.gemspec' + +# Offense count: 1828 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedHashRocketStyle, EnforcedColonStyle, EnforcedLastArgumentHashStyle. +# SupportedHashRocketStyles: key, separator, table +# SupportedColonStyles: key, separator, table +# SupportedLastArgumentHashStyles: always_inspect, always_ignore, ignore_implicit, ignore_explicit +Layout/AlignHash: + Enabled: false + +# Offense count: 57 +# Cop supports --auto-correct. +Layout/ClosingHeredocIndentation: + Enabled: false + +# Offense count: 167 +# Cop supports --auto-correct. +Layout/EmptyLineAfterGuardClause: + Enabled: false + +# Offense count: 173 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle. +# SupportedStyles: empty_lines, empty_lines_except_namespace, empty_lines_special, no_empty_lines, beginning_only, ending_only +Layout/EmptyLinesAroundClassBody: + Enabled: false + +# Offense count: 39 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyleAlignWith, AutoCorrect, Severity. +# SupportedStylesAlignWith: keyword, variable, start_of_line +Layout/EndAlignment: + Enabled: false + +# Offense count: 174 +# Cop supports --auto-correct. +# Configuration parameters: AllowForAlignment, ForceEqualSignAlignment. +Layout/ExtraSpacing: + Enabled: false + +# Offense count: 105 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, IndentationWidth. +# SupportedStyles: consistent, consistent_relative_to_receiver, special_for_inner_method_call, special_for_inner_method_call_in_parentheses +Layout/FirstParameterIndentation: + Enabled: false + +# Offense count: 255 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, IndentationWidth. +# SupportedStyles: special_inside_parentheses, consistent, align_braces +Layout/IndentHash: + Enabled: false + +# Offense count: 392 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle. +# SupportedStyles: auto_detection, squiggly, active_support, powerpack, unindent +Layout/IndentHeredoc: + Enabled: false + +# Offense count: 1 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle. +# SupportedStyles: symmetrical, new_line, same_line +Layout/MultilineArrayBraceLayout: + Exclude: + - 'lib/active_merchant/billing/gateways/optimal_payment.rb' + +# Offense count: 36 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle. +# SupportedStyles: symmetrical, new_line, same_line +Layout/MultilineHashBraceLayout: + Enabled: false + +# Offense count: 232 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle. +# SupportedStyles: symmetrical, new_line, same_line +Layout/MultilineMethodCallBraceLayout: + Enabled: false + +# Offense count: 24 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, IndentationWidth. +# SupportedStyles: aligned, indented +Layout/MultilineOperationIndentation: + Exclude: + - 'lib/active_merchant/billing/credit_card_methods.rb' + - 'lib/active_merchant/billing/gateways/iridium.rb' + - 'lib/active_merchant/billing/gateways/moneris.rb' + - 'lib/active_merchant/billing/gateways/moneris_us.rb' + - 'lib/active_merchant/billing/gateways/orbital.rb' + - 'lib/active_merchant/billing/gateways/redsys.rb' + - 'test/unit/gateways/braintree_blue_test.rb' + - 'test/unit/gateways/skip_jack_test.rb' + +# Offense count: 15 +# Cop supports --auto-correct. +Layout/RescueEnsureAlignment: + Exclude: + - 'lib/active_merchant/billing/gateways/balanced.rb' + - 'lib/active_merchant/billing/gateways/clearhaus.rb' + - 'lib/active_merchant/billing/gateways/culqi.rb' + - 'lib/active_merchant/billing/gateways/eway_managed.rb' + - 'lib/active_merchant/billing/gateways/fat_zebra.rb' + - 'lib/active_merchant/billing/gateways/hps.rb' + - 'lib/active_merchant/billing/gateways/iveri.rb' + - 'lib/active_merchant/billing/gateways/kushki.rb' + - 'lib/active_merchant/billing/gateways/merchant_e_solutions.rb' + - 'lib/active_merchant/billing/gateways/netbanx.rb' + - 'lib/active_merchant/billing/gateways/opp.rb' + - 'lib/active_merchant/billing/gateways/orbital.rb' + - 'lib/active_merchant/billing/gateways/pay_junction_v2.rb' + - 'lib/active_merchant/billing/gateways/quickbooks.rb' + - 'lib/active_merchant/billing/gateways/trans_first_transaction_express.rb' + +# Offense count: 649 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle. +# SupportedStyles: space, no_space +Layout/SpaceAroundEqualsInParameterDefault: + Enabled: false + +# Offense count: 104 +# Cop supports --auto-correct. +Layout/SpaceAroundKeyword: + Enabled: false + +# Offense count: 782 +# Cop supports --auto-correct. +# Configuration parameters: AllowForAlignment. +Layout/SpaceAroundOperators: + Enabled: false + +# Offense count: 118 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, EnforcedStyleForEmptyBrackets. +# SupportedStyles: space, no_space, compact +# SupportedStylesForEmptyBrackets: space, no_space +Layout/SpaceInsideArrayLiteralBrackets: + Enabled: false + +# Offense count: 12 +# Cop supports --auto-correct. +Layout/SpaceInsideArrayPercentLiteral: + Exclude: + - 'lib/active_merchant/billing/gateways/migs/migs_codes.rb' + +# Offense count: 1186 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, EnforcedStyleForEmptyBraces. +# SupportedStyles: space, no_space, compact +# SupportedStylesForEmptyBraces: space, no_space +Layout/SpaceInsideHashLiteralBraces: + Enabled: false + +# Offense count: 115 +# Cop supports --auto-correct. +Layout/SpaceInsidePercentLiteralDelimiters: + Enabled: false + +# Offense count: 150 +# Configuration parameters: AllowSafeAssignment. +Lint/AssignmentInCondition: + Enabled: false + +# Offense count: 3 +Lint/FormatParameterMismatch: + Exclude: + - 'test/unit/credit_card_formatting_test.rb' + +# Offense count: 2 +Lint/HandleExceptions: + Exclude: + - 'lib/active_merchant/billing/gateways/mastercard.rb' + - 'lib/active_merchant/billing/gateways/trust_commerce.rb' + +# Offense count: 1 +Lint/RescueException: + Exclude: + - 'lib/active_merchant/billing/gateways/quantum.rb' + +# Offense count: 1502 +# Cop supports --auto-correct. +# Configuration parameters: IgnoreEmptyBlocks, AllowUnusedKeywordArguments. +Lint/UnusedBlockArgument: + Enabled: false + +# Offense count: 284 +# Cop supports --auto-correct. +# Configuration parameters: AllowUnusedKeywordArguments, IgnoreEmptyMethods. +Lint/UnusedMethodArgument: + Enabled: false + +# Offense count: 1418 +Metrics/AbcSize: + Max: 192 + +# Offense count: 26 +# Configuration parameters: CountComments, ExcludedMethods. +# ExcludedMethods: refine +Metrics/BlockLength: + Max: 54 + +# Offense count: 9 +# Configuration parameters: CountBlocks. +Metrics/BlockNesting: + Max: 6 + +# Offense count: 175 +Metrics/CyclomaticComplexity: + Max: 36 + +# Offense count: 1793 +# Configuration parameters: CountComments, ExcludedMethods. +Metrics/MethodLength: + Max: 163 + +# Offense count: 2 +# Configuration parameters: CountKeywordArgs. +Metrics/ParameterLists: + Max: 6 + +# Offense count: 129 +Metrics/PerceivedComplexity: + Max: 33 + +# Offense count: 6 +Naming/AccessorMethodName: + Exclude: + - 'lib/active_merchant/billing/gateways/usa_epay_advanced.rb' + - 'test/remote/gateways/remote_authorize_net_cim_test.rb' + - 'test/unit/gateways/authorize_net_cim_test.rb' + +# Offense count: 1 +Naming/ConstantName: + Exclude: + - 'test/test_helper.rb' + +# Offense count: 46 +# Configuration parameters: EnforcedStyle. +# SupportedStyles: lowercase, uppercase +Naming/HeredocDelimiterCase: + Exclude: + - 'test/unit/gateways/authorize_net_test.rb' + - 'test/unit/gateways/card_stream_test.rb' + - 'test/unit/gateways/hps_test.rb' + - 'test/unit/gateways/litle_test.rb' + - 'test/unit/gateways/moneris_test.rb' + +# Offense count: 85 +# Configuration parameters: Blacklist. +# Blacklist: (?-mix:(^|\s)(EO[A-Z]{1}|END)(\s|$)) +Naming/HeredocDelimiterNaming: + Enabled: false + +# Offense count: 1 +# Configuration parameters: EnforcedStyleForLeadingUnderscores. +# SupportedStylesForLeadingUnderscores: disallowed, required, optional +Naming/MemoizedInstanceVariableName: + Exclude: + - 'lib/active_merchant/billing/compatibility.rb' + +# Offense count: 15 +# Configuration parameters: EnforcedStyle. +# SupportedStyles: snake_case, camelCase +Naming/MethodName: + Exclude: + - 'lib/active_merchant/billing/gateways/card_connect.rb' + - 'lib/active_merchant/billing/gateways/latitude19.rb' + - 'lib/active_merchant/billing/gateways/qbms.rb' + - 'test/remote/gateways/remote_card_connect_test.rb' + - 'test/remote/gateways/remote_card_stream_test.rb' + - 'test/remote/gateways/remote_fat_zebra_test.rb' + - 'test/remote/gateways/remote_sage_pay_test.rb' + - 'test/unit/gateways/sage_pay_test.rb' + +# Offense count: 5 +# Configuration parameters: NamePrefix, NamePrefixBlacklist, NameWhitelist, MethodDefinitionMacros. +# NamePrefix: is_, has_, have_ +# NamePrefixBlacklist: is_, has_, have_ +# NameWhitelist: is_a? +# MethodDefinitionMacros: define_method, define_singleton_method +Naming/PredicateName: + Exclude: + - 'spec/**/*' + - 'lib/active_merchant/billing/gateways/authorize_net.rb' + - 'lib/active_merchant/billing/gateways/payeezy.rb' + - 'lib/active_merchant/billing/gateways/paymill.rb' + - 'lib/active_merchant/billing/gateways/redsys.rb' + - 'lib/active_merchant/billing/gateways/sage_pay.rb' + +# Offense count: 14 +# Configuration parameters: MinNameLength, AllowNamesEndingInNumbers, AllowedNames, ForbiddenNames. +# AllowedNames: io, id, to, by, on, in, at, ip, db +Naming/UncommunicativeMethodParamName: + Exclude: + - 'lib/active_merchant/billing/gateways/blue_snap.rb' + - 'lib/active_merchant/billing/gateways/cyber_source.rb' + - 'lib/active_merchant/billing/gateways/iridium.rb' + - 'lib/active_merchant/billing/gateways/latitude19.rb' + - 'lib/active_merchant/billing/gateways/merchant_ware.rb' + - 'lib/active_merchant/billing/gateways/quantum.rb' + - 'lib/active_merchant/billing/gateways/worldpay_online_payments.rb' + - 'test/unit/gateways/paypal/paypal_common_api_test.rb' + - 'test/unit/gateways/realex_test.rb' + +# Offense count: 49 +# Configuration parameters: EnforcedStyle. +# SupportedStyles: snake_case, camelCase +Naming/VariableName: + Exclude: + - 'lib/active_merchant/billing/gateways/cyber_source.rb' + - 'lib/active_merchant/billing/gateways/iridium.rb' + - 'lib/active_merchant/billing/gateways/latitude19.rb' + - 'lib/active_merchant/billing/gateways/optimal_payment.rb' + - 'lib/active_merchant/billing/gateways/quantum.rb' + - 'lib/active_merchant/billing/gateways/worldpay_online_payments.rb' + - 'test/remote/gateways/remote_authorize_net_test.rb' + - 'test/remote/gateways/remote_card_stream_test.rb' + - 'test/remote/gateways/remote_worldpay_test.rb' + - 'test/unit/gateways/card_stream_test.rb' + - 'test/unit/gateways/worldpay_online_payments_test.rb' + +# Offense count: 11 +# Configuration parameters: EnforcedStyle. +# SupportedStyles: snake_case, normalcase, non_integer +Naming/VariableNumber: + Exclude: + - 'lib/active_merchant/billing/gateways/merchant_partners.rb' + - 'lib/active_merchant/billing/gateways/mercury.rb' + - 'lib/active_merchant/billing/gateways/orbital.rb' + - 'test/remote/gateways/remote_paypal_test.rb' + - 'test/unit/gateways/merchant_ware_test.rb' + - 'test/unit/gateways/merchant_ware_version_four_test.rb' + - 'test/unit/gateways/orbital_test.rb' + - 'test/unit/gateways/paypal/paypal_common_api_test.rb' + +# Offense count: 4 +# Cop supports --auto-correct. +Performance/RedundantMatch: + Exclude: + - 'lib/active_merchant/billing/gateways/opp.rb' + - 'test/unit/gateways/payu_latam_test.rb' + +# Offense count: 11 +# Cop supports --auto-correct. +Performance/StringReplacement: + Exclude: + - 'lib/active_merchant/billing/compatibility.rb' + - 'lib/active_merchant/billing/gateways/card_connect.rb' + - 'lib/active_merchant/billing/gateways/firstdata_e4.rb' + - 'lib/active_merchant/billing/gateways/merchant_ware.rb' + - 'lib/active_merchant/billing/gateways/merchant_ware_version_four.rb' + - 'lib/active_merchant/billing/gateways/orbital.rb' + - 'lib/active_merchant/billing/gateways/quickbooks.rb' + - 'lib/active_merchant/billing/gateways/quickpay/quickpay_v10.rb' + - 'lib/active_merchant/billing/gateways/realex.rb' + - 'test/unit/gateways/nab_transact_test.rb' + +# Offense count: 2 +# Configuration parameters: EnforcedStyle. +# SupportedStyles: inline, group +Style/AccessModifierDeclarations: + Exclude: + - 'test/unit/gateways/metrics_global_test.rb' + - 'test/unit/gateways/optimal_payment_test.rb' + +# Offense count: 11 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle. +# SupportedStyles: prefer_alias, prefer_alias_method +Style/Alias: + Exclude: + - 'lib/active_merchant/billing/gateways/beanstream.rb' + - 'lib/active_merchant/billing/gateways/braintree_blue.rb' + - 'lib/active_merchant/billing/gateways/inspire.rb' + - 'lib/active_merchant/billing/gateways/migs.rb' + - 'lib/active_merchant/billing/gateways/smart_ps.rb' + - 'lib/active_merchant/billing/gateways/spreedly_core.rb' + - 'lib/active_merchant/post_data.rb' + - 'test/unit/gateways/bpoint_test.rb' + - 'test/unit/gateways/paymentez_test.rb' + +# Offense count: 12 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle. +# SupportedStyles: always, conditionals +Style/AndOr: + Exclude: + - 'lib/active_merchant/billing/gateways/beanstream/beanstream_core.rb' + - 'lib/active_merchant/billing/gateways/eway.rb' + - 'lib/active_merchant/billing/gateways/iridium.rb' + - 'lib/active_merchant/billing/gateways/pac_net_raven.rb' + - 'lib/active_merchant/billing/gateways/smart_ps.rb' + - 'lib/active_merchant/billing/gateways/stripe.rb' + - 'lib/active_merchant/billing/gateways/webpay.rb' + +# Offense count: 47 +# Configuration parameters: AllowedChars. +Style/AsciiComments: + Exclude: + - 'lib/active_merchant/billing/gateways/authorize_net_arb.rb' + - 'lib/active_merchant/billing/gateways/authorize_net_cim.rb' + - 'lib/active_merchant/billing/gateways/beanstream/beanstream_core.rb' + - 'lib/active_merchant/billing/gateways/eway_rapid.rb' + - 'lib/active_merchant/billing/gateways/netbanx.rb' + - 'lib/active_merchant/billing/gateways/ogone.rb' + - 'lib/active_merchant/billing/gateways/orbital.rb' + - 'lib/active_merchant/billing/gateways/payflow_express.rb' + - 'lib/active_merchant/billing/gateways/paystation.rb' + - 'lib/active_merchant/billing/gateways/psl_card.rb' + - 'lib/active_merchant/billing/gateways/sage.rb' + - 'test/remote/gateways/remote_data_cash_test.rb' + - 'test/remote/gateways/remote_nab_transact_test.rb' + +# Offense count: 1 +# Cop supports --auto-correct. +Style/Attr: + Exclude: + - 'test/unit/gateways/forte_test.rb' + +# Offense count: 2 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle. +# SupportedStyles: percent_q, bare_percent +Style/BarePercentLiterals: + Exclude: + - 'test/unit/gateways/eway_rapid_test.rb' + - 'test/unit/gateways/orbital_test.rb' + +# Offense count: 3 +# Cop supports --auto-correct. +Style/BlockComments: + Exclude: + - 'test/remote/gateways/remote_barclays_epdq_extra_plus_test.rb' + - 'test/remote/gateways/remote_netpay_test.rb' + - 'test/remote/gateways/remote_payu_in_test.rb' + +# Offense count: 77 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, ProceduralMethods, FunctionalMethods, IgnoredMethods. +# SupportedStyles: line_count_based, semantic, braces_for_chaining +# ProceduralMethods: benchmark, bm, bmbm, create, each_with_object, measure, new, realtime, tap, with_object +# FunctionalMethods: let, let!, subject, watch +# IgnoredMethods: lambda, proc, it +Style/BlockDelimiters: + Enabled: false + +# Offense count: 440 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle. +# SupportedStyles: braces, no_braces, context_dependent +Style/BracesAroundHashParameters: + Enabled: false + +# Offense count: 2 +Style/CaseEquality: + Exclude: + - 'lib/active_merchant/billing/gateways/braintree_blue.rb' + - 'test/test_helper.rb' + +# Offense count: 2 +# Cop supports --auto-correct. +# Configuration parameters: AutoCorrect, EnforcedStyle. +# SupportedStyles: nested, compact +Style/ClassAndModuleChildren: + Exclude: + - 'test/unit/gateways/optimal_payment_test.rb' + - 'test/unit/gateways/realex_test.rb' + +# Offense count: 30 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle. +# SupportedStyles: is_a?, kind_of? +Style/ClassCheck: + Enabled: false + +# Offense count: 8 +Style/ClassVars: + Exclude: + - 'lib/active_merchant/billing/base.rb' + - 'lib/active_merchant/billing/gateway.rb' + - 'lib/active_merchant/billing/gateways/balanced.rb' + - 'lib/active_merchant/billing/gateways/cyber_source.rb' + - 'test/remote/gateways/remote_cams_test.rb' + - 'test/test_helper.rb' + +# Offense count: 10 +# Cop supports --auto-correct. +Style/ColonMethodCall: + Exclude: + - 'lib/active_merchant/billing/gateways/blue_pay.rb' + - 'lib/active_merchant/billing/gateways/cashnet.rb' + - 'lib/active_merchant/billing/gateways/credorax.rb' + - 'lib/active_merchant/billing/gateways/epay.rb' + - 'lib/active_merchant/billing/gateways/evo_ca.rb' + - 'lib/active_merchant/billing/gateways/ezic.rb' + - 'lib/active_merchant/billing/gateways/migs.rb' + - 'lib/active_merchant/billing/gateways/nmi.rb' + - 'test/unit/gateways/quickpay_v4to7_test.rb' + +# Offense count: 5 +# Cop supports --auto-correct. +# Configuration parameters: Keywords. +# Keywords: TODO, FIXME, OPTIMIZE, HACK, REVIEW +Style/CommentAnnotation: + Exclude: + - 'test/remote/gateways/remote_usa_epay_advanced_test.rb' + - 'test/unit/gateways/authorize_net_cim_test.rb' + - 'test/unit/gateways/usa_epay_advanced_test.rb' + +# Offense count: 8 +Style/CommentedKeyword: + Exclude: + - 'lib/active_merchant/billing/gateways/blue_pay.rb' + - 'lib/active_merchant/billing/gateways/usa_epay_advanced.rb' + - 'lib/active_merchant/errors.rb' + - 'test/remote/gateways/remote_cardknox_test.rb' + - 'test/unit/gateways/cardknox_test.rb' + +# Offense count: 22 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, SingleLineConditionsOnly, IncludeTernaryExpressions. +# SupportedStyles: assign_to_condition, assign_inside_condition +Style/ConditionalAssignment: + Enabled: false + +# Offense count: 7 +# Configuration parameters: AllowCoercion. +Style/DateTime: + Exclude: + - 'test/remote/gateways/remote_first_pay_test.rb' + - 'test/remote/gateways/remote_pin_test.rb' + - 'test/remote/gateways/remote_trexle_test.rb' + - 'test/unit/gateways/orbital_test.rb' + - 'test/unit/gateways/paypal/paypal_common_api_test.rb' + +# Offense count: 211 +Style/Documentation: + Enabled: false + +# Offense count: 3 +Style/DoubleNegation: + Exclude: + - 'lib/active_merchant/billing/gateways/balanced.rb' + - 'lib/active_merchant/billing/gateways/netbilling.rb' + - 'lib/active_merchant/billing/gateways/pay_secure.rb' + +# Offense count: 18 +# Cop supports --auto-correct. +Style/EachWithObject: + Exclude: + - 'lib/active_merchant/billing/avs_result.rb' + - 'lib/active_merchant/billing/gateways/beanstream/beanstream_core.rb' + - 'lib/active_merchant/billing/gateways/cams.rb' + - 'lib/active_merchant/billing/gateways/ezic.rb' + - 'lib/active_merchant/billing/gateways/federated_canada.rb' + - 'lib/active_merchant/billing/gateways/merchant_one.rb' + - 'lib/active_merchant/billing/gateways/micropayment.rb' + - 'lib/active_merchant/billing/gateways/money_movers.rb' + - 'lib/active_merchant/billing/gateways/orbital.rb' + - 'lib/active_merchant/billing/gateways/payu_in.rb' + - 'lib/active_merchant/billing/gateways/usa_epay_advanced.rb' + - 'lib/active_merchant/billing/model.rb' + - 'test/test_helper.rb' + - 'test/unit/gateways/authorize_net_test.rb' + +# Offense count: 6 +# Cop supports --auto-correct. +Style/EmptyCaseCondition: + Exclude: + - 'lib/active_merchant/billing/gateways/inspire.rb' + - 'lib/active_merchant/billing/gateways/payflow/payflow_common_api.rb' + - 'lib/active_merchant/billing/gateways/smart_ps.rb' + - 'lib/active_merchant/billing/gateways/usa_epay_advanced.rb' + +# Offense count: 4 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle. +# SupportedStyles: empty, nil, both +Style/EmptyElse: + Exclude: + - 'lib/active_merchant/billing/gateways/fat_zebra.rb' + - 'lib/active_merchant/billing/gateways/mercado_pago.rb' + - 'lib/active_merchant/billing/gateways/payeezy.rb' + - 'lib/active_merchant/billing/gateways/payment_express.rb' + +# Offense count: 5 +# Cop supports --auto-correct. +Style/EmptyLiteral: + Exclude: + - 'lib/active_merchant/billing/gateways/element.rb' + - 'lib/active_merchant/billing/gateways/itransact.rb' + - 'lib/active_merchant/billing/gateways/kushki.rb' + - 'lib/active_merchant/billing/gateways/transact_pro.rb' + - 'test/unit/gateways/paypal_digital_goods_test.rb' + +# Offense count: 14 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle. +# SupportedStyles: compact, expanded +Style/EmptyMethod: + Exclude: + - 'lib/active_merchant/billing/gateways/qbms.rb' + - 'lib/active_merchant/billing/gateways/usa_epay_advanced.rb' + - 'test/remote/gateways/remote_ncr_secure_pay_test.rb' + - 'test/unit/gateways/card_connect_test.rb' + - 'test/unit/gateways/cardknox_test.rb' + - 'test/unit/gateways/latitude19_test.rb' + - 'test/unit/gateways/world_net_test.rb' + - 'test/unit/gateways/worldpay_online_payments_test.rb' + +# Offense count: 23 +# Cop supports --auto-correct. +Style/Encoding: + Enabled: false + +# Offense count: 2 +Style/EvalWithLocation: + Exclude: + - 'lib/active_merchant/billing/credit_card.rb' + - 'lib/active_merchant/billing/response.rb' + +# Offense count: 5 +# Cop supports --auto-correct. +Style/ExpandPathArguments: + Exclude: + - 'Rakefile' + - 'activemerchant.gemspec' + - 'script/generate' + - 'test/test_helper.rb' + +# Offense count: 11 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle. +# SupportedStyles: each, for +Style/For: + Exclude: + - 'lib/active_merchant/billing/gateways/cardknox.rb' + - 'lib/active_merchant/billing/gateways/linkpoint.rb' + - 'lib/active_merchant/billing/gateways/merchant_one.rb' + - 'lib/active_merchant/billing/gateways/psl_card.rb' + - 'lib/active_merchant/billing/gateways/usa_epay_transaction.rb' + - 'test/remote/gateways/remote_orbital_test.rb' + +# Offense count: 97 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle. +# SupportedStyles: format, sprintf, percent +Style/FormatString: + Enabled: false + +# Offense count: 22 +# Configuration parameters: EnforcedStyle. +# SupportedStyles: annotated, template, unannotated +Style/FormatStringToken: + Exclude: + - 'lib/active_merchant/billing/gateways/redsys.rb' + - 'lib/active_merchant/connection.rb' + - 'lib/active_merchant/network_connection_retries.rb' + - 'test/remote/gateways/remote_balanced_test.rb' + - 'test/remote/gateways/remote_openpay_test.rb' + - 'test/unit/gateways/balanced_test.rb' + - 'test/unit/gateways/elavon_test.rb' + - 'test/unit/gateways/exact_test.rb' + - 'test/unit/gateways/firstdata_e4_test.rb' + - 'test/unit/gateways/safe_charge_test.rb' + +# Offense count: 679 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle. +# SupportedStyles: when_needed, always, never +Style/FrozenStringLiteralComment: + Enabled: false + +# Offense count: 5 +# Configuration parameters: AllowedVariables. +Style/GlobalVars: + Exclude: + - 'test/remote/gateways/remote_finansbank_test.rb' + - 'test/remote/gateways/remote_garanti_test.rb' + - 'test/remote/gateways/remote_secure_pay_tech_test.rb' + - 'test/unit/gateways/finansbank_test.rb' + - 'test/unit/gateways/garanti_test.rb' + +# Offense count: 196 +# Configuration parameters: MinBodyLength. +Style/GuardClause: + Enabled: false + +# Offense count: 7482 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, UseHashRocketsWithSymbolValues, PreferHashRocketsForNonAlnumEndingSymbols. +# SupportedStyles: ruby19, hash_rockets, no_mixed_keys, ruby19_no_mixed_keys +Style/HashSyntax: + Enabled: false + +# Offense count: 6 +Style/IdenticalConditionalBranches: + Exclude: + - 'lib/active_merchant/billing/gateways/elavon.rb' + - 'lib/active_merchant/billing/gateways/litle.rb' + - 'lib/active_merchant/billing/gateways/payu_latam.rb' + +# Offense count: 14 +Style/IfInsideElse: + Exclude: + - 'lib/active_merchant/billing/credit_card.rb' + - 'lib/active_merchant/billing/gateways/authorize_net.rb' + - 'lib/active_merchant/billing/gateways/cecabank.rb' + - 'lib/active_merchant/billing/gateways/element.rb' + - 'lib/active_merchant/billing/gateways/eway_managed.rb' + - 'lib/active_merchant/billing/gateways/global_collect.rb' + - 'lib/active_merchant/billing/gateways/iridium.rb' + - 'lib/active_merchant/billing/gateways/quantum.rb' + - 'lib/active_merchant/billing/gateways/skip_jack.rb' + - 'lib/active_merchant/billing/gateways/worldpay_online_payments.rb' + +# Offense count: 128 +# Cop supports --auto-correct. +Style/IfUnlessModifier: + Enabled: false + +# Offense count: 1 +Style/IfUnlessModifierOfIfUnless: + Exclude: + - 'lib/active_merchant/billing/gateways/merchant_e_solutions.rb' + +# Offense count: 2 +# Cop supports --auto-correct. +# Configuration parameters: InverseMethods, InverseBlocks. +Style/InverseMethods: + Exclude: + - 'lib/active_merchant/billing/gateways/ogone.rb' + - 'lib/active_merchant/billing/gateways/worldpay.rb' + +# Offense count: 32 +# Cop supports --auto-correct. +# Configuration parameters: IgnoredMethods. +Style/MethodCallWithoutArgsParentheses: + Enabled: false + +# Offense count: 656 +Style/MultilineBlockChain: + Enabled: false + +# Offense count: 15 +# Cop supports --auto-correct. +Style/MultilineIfModifier: + Exclude: + - 'lib/active_merchant/billing/compatibility.rb' + - 'lib/active_merchant/billing/gateways/authorize_net_cim.rb' + - 'lib/active_merchant/billing/gateways/bank_frick.rb' + - 'lib/active_merchant/billing/gateways/cenpos.rb' + - 'lib/active_merchant/billing/gateways/efsnet.rb' + - 'lib/active_merchant/billing/gateways/eway.rb' + - 'lib/active_merchant/billing/gateways/flo2cash.rb' + - 'lib/active_merchant/billing/gateways/itransact.rb' + - 'lib/active_merchant/billing/gateways/monei.rb' + - 'lib/active_merchant/billing/gateways/optimal_payment.rb' + - 'lib/active_merchant/billing/gateways/payflow/payflow_common_api.rb' + - 'lib/active_merchant/billing/gateways/psigate.rb' + - 'lib/active_merchant/billing/gateways/realex.rb' + +# Offense count: 6 +# Cop supports --auto-correct. +Style/MultilineIfThen: + Exclude: + - 'lib/active_merchant/billing/gateways/eway_managed.rb' + +# Offense count: 4 +Style/MultilineTernaryOperator: + Exclude: + - 'lib/active_merchant/billing/gateways/merchant_partners.rb' + - 'lib/active_merchant/billing/gateways/ogone.rb' + - 'lib/active_merchant/billing/gateways/swipe_checkout.rb' + - 'lib/active_merchant/billing/gateways/transact_pro.rb' + +# Offense count: 1 +Style/MultipleComparison: + Exclude: + - 'lib/active_merchant/billing/gateways/worldpay_online_payments.rb' + +# Offense count: 530 +# Cop supports --auto-correct. +Style/MutableConstant: + Enabled: false + +# Offense count: 17 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle. +# SupportedStyles: both, prefix, postfix +Style/NegatedIf: + Exclude: + - 'lib/active_merchant/billing/credit_card.rb' + - 'lib/active_merchant/billing/gateways/adyen.rb' + - 'lib/active_merchant/billing/gateways/itransact.rb' + - 'lib/active_merchant/billing/gateways/iveri.rb' + - 'lib/active_merchant/billing/gateways/ogone.rb' + - 'lib/active_merchant/billing/gateways/orbital.rb' + - 'lib/active_merchant/billing/gateways/qbms.rb' + - 'lib/active_merchant/billing/gateways/worldpay.rb' + - 'lib/support/ssl_verify.rb' + - 'test/remote/gateways/remote_paypal_test.rb' + +# Offense count: 1 +# Cop supports --auto-correct. +Style/NestedModifier: + Exclude: + - 'lib/active_merchant/billing/gateways/merchant_e_solutions.rb' + +# Offense count: 2 +# Cop supports --auto-correct. +# Configuration parameters: Whitelist. +# Whitelist: be, be_a, be_an, be_between, be_falsey, be_kind_of, be_instance_of, be_truthy, be_within, eq, eql, end_with, include, match, raise_error, respond_to, start_with +Style/NestedParenthesizedCalls: + Exclude: + - 'lib/active_merchant/billing/gateways/quickpay/quickpay_v10.rb' + +# Offense count: 2 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, MinBodyLength. +# SupportedStyles: skip_modifier_ifs, always +Style/Next: + Exclude: + - 'lib/active_merchant/billing/gateways/authorize_net.rb' + - 'lib/support/outbound_hosts.rb' + +# Offense count: 5 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle. +# SupportedStyles: predicate, comparison +Style/NilComparison: + Exclude: + - 'lib/active_merchant/billing/gateways/card_stream.rb' + - 'lib/active_merchant/billing/gateways/litle.rb' + - 'lib/active_merchant/billing/gateways/telr.rb' + - 'test/unit/gateways/braintree_blue_test.rb' + - 'test/unit/gateways/stripe_test.rb' + +# Offense count: 1 +# Cop supports --auto-correct. +# Configuration parameters: IncludeSemanticChanges. +Style/NonNilCheck: + Exclude: + - 'lib/active_merchant/billing/gateways/usa_epay_advanced.rb' + +# Offense count: 4 +# Cop supports --auto-correct. +Style/Not: + Exclude: + - 'test/remote/gateways/remote_paypal_test.rb' + - 'test/test_helper.rb' + - 'test/unit/gateways/braintree_blue_test.rb' + +# Offense count: 19 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedOctalStyle. +# SupportedOctalStyles: zero_with_o, zero_only +Style/NumericLiteralPrefix: + Exclude: + - 'lib/active_merchant/billing/gateways/psl_card.rb' + - 'test/remote/gateways/remote_axcessms_test.rb' + - 'test/remote/gateways/remote_cecabank_test.rb' + - 'test/remote/gateways/remote_cenpos_test.rb' + - 'test/remote/gateways/remote_culqi_test.rb' + - 'test/remote/gateways/remote_litle_certification_test.rb' + - 'test/remote/gateways/remote_opp_test.rb' + - 'test/remote/gateways/remote_pay_junction_v2_test.rb' + - 'test/remote/gateways/remote_tns_test.rb' + - 'test/unit/credit_card_formatting_test.rb' + - 'test/unit/gateways/axcessms_test.rb' + - 'test/unit/gateways/opp_test.rb' + - 'test/unit/gateways/pay_junction_v2_test.rb' + +# Offense count: 466 +# Cop supports --auto-correct. +# Configuration parameters: Strict. +Style/NumericLiterals: + MinDigits: 17 + +# Offense count: 41 +# Cop supports --auto-correct. +# Configuration parameters: AutoCorrect, EnforcedStyle, IgnoredMethods. +# SupportedStyles: predicate, comparison +Style/NumericPredicate: + Enabled: false + +# Offense count: 1 +# Cop supports --auto-correct. +Style/OrAssignment: + Exclude: + - 'lib/active_merchant/billing/gateways/net_registry.rb' + +# Offense count: 24 +# Cop supports --auto-correct. +Style/ParallelAssignment: + Enabled: false + +# Offense count: 879 +# Cop supports --auto-correct. +# Configuration parameters: PreferredDelimiters. +Style/PercentLiteralDelimiters: + Enabled: false + +# Offense count: 6 +# Cop supports --auto-correct. +Style/PerlBackrefs: + Exclude: + - 'lib/active_merchant/billing/gateways/qvalent.rb' + - 'lib/active_merchant/billing/gateways/sage_pay.rb' + - 'lib/support/outbound_hosts.rb' + - 'test/unit/gateways/payu_in_test.rb' + +# Offense count: 96 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle. +# SupportedStyles: short, verbose +Style/PreferredHashMethods: + Enabled: false + +# Offense count: 2 +# Cop supports --auto-correct. +Style/Proc: + Exclude: + - 'test/unit/credit_card_methods_test.rb' + - 'test/unit/gateways/nab_transact_test.rb' + +# Offense count: 31 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle. +# SupportedStyles: compact, exploded +Style/RaiseArgs: + Enabled: false + +# Offense count: 86 +# Cop supports --auto-correct. +# Configuration parameters: AllowMultipleReturnValues. +Style/RedundantReturn: + Enabled: false + +# Offense count: 179 +# Cop supports --auto-correct. +Style/RedundantSelf: + Enabled: false + +# Offense count: 1209 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, AllowInnerSlashes. +# SupportedStyles: slashes, percent_r, mixed +Style/RegexpLiteral: + Enabled: false + +# Offense count: 15 +# Cop supports --auto-correct. +Style/RescueModifier: + Exclude: + - 'lib/active_merchant/billing/gateways/clearhaus.rb' + - 'lib/active_merchant/billing/gateways/cyber_source.rb' + - 'lib/active_merchant/billing/gateways/jetpay.rb' + - 'lib/active_merchant/billing/gateways/jetpay_v2.rb' + - 'lib/active_merchant/billing/gateways/optimal_payment.rb' + - 'lib/active_merchant/billing/gateways/payflow/payflow_response.rb' + - 'lib/active_merchant/billing/gateways/sage.rb' + - 'lib/active_merchant/billing/gateways/secure_pay.rb' + - 'lib/active_merchant/billing/gateways/telr.rb' + - 'test/remote/gateways/remote_secure_pay_au_test.rb' + +# Offense count: 1 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle. +# SupportedStyles: implicit, explicit +Style/RescueStandardError: + Exclude: + - 'lib/active_merchant/billing/base.rb' + +# Offense count: 2 +# Cop supports --auto-correct. +Style/SelfAssignment: + Exclude: + - 'lib/active_merchant/billing/credit_card.rb' + - 'lib/active_merchant/billing/gateways/barclaycard_smartpay.rb' + +# Offense count: 7 +# Cop supports --auto-correct. +# Configuration parameters: AllowAsExpressionSeparator. +Style/Semicolon: + Exclude: + - 'lib/active_merchant/billing/gateways/ezic.rb' + - 'lib/active_merchant/billing/gateways/merchant_one.rb' + - 'lib/active_merchant/billing/gateways/usa_epay_advanced.rb' + - 'test/remote/gateways/remote_orbital_test.rb' + - 'test/unit/gateways/cardknox_test.rb' + - 'test/unit/gateways/omise_test.rb' + +# Offense count: 2 +# Cop supports --auto-correct. +# Configuration parameters: AllowIfMethodIsEmpty. +Style/SingleLineMethods: + Exclude: + - 'test/unit/gateways/paypal/paypal_common_api_test.rb' + +# Offense count: 3 +# Cop supports --auto-correct. +# Configuration parameters: . +# SupportedStyles: use_perl_names, use_english_names +Style/SpecialGlobalVars: + EnforcedStyle: use_perl_names + +# Offense count: 27 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle. +# SupportedStyles: single_quotes, double_quotes +Style/StringLiteralsInInterpolation: + Exclude: + - 'lib/active_merchant/billing/gateways/banwire.rb' + - 'lib/active_merchant/billing/gateways/cams.rb' + - 'lib/active_merchant/billing/gateways/checkout_v2.rb' + - 'lib/active_merchant/billing/gateways/credorax.rb' + - 'lib/active_merchant/billing/gateways/digitzs.rb' + - 'lib/active_merchant/billing/gateways/ebanx.rb' + - 'lib/active_merchant/billing/gateways/merchant_one.rb' + - 'lib/active_merchant/billing/gateways/micropayment.rb' + - 'lib/active_merchant/billing/gateways/pagarme.rb' + - 'lib/active_merchant/billing/gateways/quickpay/quickpay_v10.rb' + - 'lib/active_merchant/billing/gateways/stripe.rb' + - 'lib/active_merchant/billing/gateways/usa_epay_advanced.rb' + - 'lib/active_merchant/billing/gateways/worldpay.rb' + - 'test/unit/gateways/eway_managed_test.rb' + +# Offense count: 309 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, MinSize. +# SupportedStyles: percent, brackets +Style/SymbolArray: + Enabled: false + +# Offense count: 7 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyleForMultiline. +# SupportedStylesForMultiline: comma, consistent_comma, no_comma +Style/TrailingCommaInArrayLiteral: + Exclude: + - 'lib/active_merchant/billing/credit_card_methods.rb' + - 'test/unit/gateways/netaxept_test.rb' + - 'test/unit/gateways/usa_epay_transaction_test.rb' + +# Offense count: 160 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyleForMultiline. +# SupportedStylesForMultiline: comma, consistent_comma, no_comma +Style/TrailingCommaInHashLiteral: + Enabled: false + +# Offense count: 38 +# Cop supports --auto-correct. +# Configuration parameters: AllowNamedUnderscoreVariables. +Style/TrailingUnderscoreVariable: + Enabled: false + +# Offense count: 119 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, MinSize, WordRegex. +# SupportedStyles: percent, brackets +Style/WordArray: + Enabled: false + +# Offense count: 34 +# Cop supports --auto-correct. +Style/ZeroLengthPredicate: + Enabled: false + +# Offense count: 9321 +# Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns. +# URISchemes: http, https +Metrics/LineLength: + Max: 2602 diff --git a/.travis.yml b/.travis.yml index e70c24ca93d..a43b6cfc204 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,24 +1,33 @@ +language: ruby +sudo: false +cache: bundler + rvm: - - 2.0.0 - - 1.9.3 - - 1.9.2 +- 2.5 +- 2.4 +- 2.3 gemfile: - - Gemfile - - Gemfile_rails31 - - Gemfile_rails30 - - Gemfile_rails23 +- gemfiles/Gemfile.rails52 +- gemfiles/Gemfile.rails51 +- gemfiles/Gemfile.rails50 +- gemfiles/Gemfile.rails42 +- gemfiles/Gemfile.rails_master + +jobs: + include: + rvm: 2.5 + gemfile: Gemfile + script: bundle exec rubocop --parallel -script: "bundle exec rake test:units" +matrix: + exclude: + - rvm: 2.3 + gemfile: 'gemfiles/Gemfile.rails_master' + - rvm: 2.4 + gemfile: 'gemfiles/Gemfile.rails_master' notifications: email: - - integrations-team@shopify.com - - nathaniel@talbott.ws - -matrix: - include: - - rvm: 2.0.0 - gemfile: Gemfile_rails40 - - rvm: 1.9.3 - gemfile: Gemfile_rails40 + on_success: never + on_failure: always diff --git a/.yardopts b/.yardopts index 8c3523426a1..7ebc3386a0d 100644 --- a/.yardopts +++ b/.yardopts @@ -1 +1,2 @@ - GettingStarted.md +- README.md diff --git a/CHANGELOG b/CHANGELOG index 1f5625b8e21..23ccdc0122e 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,1732 @@ = ActiveMerchant CHANGELOG +== HEAD +* Stripe Payment Intents: Add new gateway [britth] #3290 +* Stripe: Send cardholder name and address when creating sources for 3DS 1.0 [jknipp] #3300 +* Checkout_v2: Support for native 3DS2.0 [nfarve] #3303 +* eWAY Rapid: If no address is available, default to the name associated with the payment method when setting the Customer fields [jasonxp] #3306 +* eWAY Rapid: Fix a bug in which the email was not set in Customer fields if no address was provided [jasonxp] #3306 +* eWAY Rapid: Support both `phone` and `phone_number` fields under the `shipping_address` option [jasonxp] #3306 +* PayU Latam: Add support for the `merchant_buyer_id` field in the `options` and `buyer` hashes [jasonxp] #3308 +* Fat Zebra: Send metadata for purchase and authorize [montdidier] #3101 +* TrustCommerce: Add support for custom fields [jasonxp] #3313 +* Stripe Payment Intents: Support option fields `transfer_destination` and `transfer_amount` and remove `transfer_data` hash [britth] #3317 +* Barclaycard Smartpay: Add support for `shopperStatement` gateway-specific field [jasonxp] #3319 +* Stripe Payment Intents: Add support for billing_details on payment methods [britth] #3320 +* BlueSnap: add standardized 3DS 2 auth fields [bayprogrammer] #3318 +* Barclaycard Smartpay: Add app based 3DS requests for auth and purchase [britth] #3327 +* Stripe Payment Intents, Checkout V2: Add support for `MOTO` flagging [britth] #3323 +* Braintree Blue: Adding 3DS2 passthru support [molbrown] #3328 +* Global Collect: Add Cabal card [leila-alderman] #3310 +* WorldPay: Add Cabal card [leila-alderman] #3316 +* Decidir: Add Cabal card [leila-alderman] #3322 +* PayU Latam: Add Cabal card [leila-alderman] #3324 +* dLocal: Add Cabal card [leila-alderman] #3325 +* BlueSnap: Add Cabal card [leila-alderman] #3326 + +== Version 1.97.0 (Aug 15, 2019) +* CyberSource: Add issuer `additionalData` gateway-specific field [jasonxp] #3296 +* PayU Latam: Add Naranja card type [hdeters] #3299 +* Adyen: Add app based 3DS requests for auth and purchase [jeremywrowe] #3298 +* MercadoPago: Add Cabal card type [leila-alderman] #3295 +* MONEI: Add external MPI 3DS 1 support [jimmyn] #3292 +* Bambora formerly Beanstream: Pass card owner when storing tokenized cards [alexdunae] #3006 +* Realex: Prevent error calculating `refund_hash` or `credit_hash` when the secret is nil [jasonxp] #3291 +* Orbital: Add external MPI support for 3DS1 [pi3r] #3261 +* Paymill: Add currency and amount to store requests [jasonxp] #3289 +* Realex: Re-implement credit as general credit [leila-alderman] #3280 +* Braintree Blue: Support for stored credentials [hdeters] #3286 +* CardConnect: Move domain from gateway specific to gateway field [hdeters] #3283 +* Adds new Maestro BINs [tanyajajodia] #3305 + +== Version 1.96.0 (Jul 26, 2019) +* Bluesnap: Omit state codes for unsupported countries [therufs] #3229 +* Adyen: Pass updateShopperStatement, industryUsage [curiousepic] #3233 +* TransFirst Transaction Express: Fix blank address2 values [britth] #3231 +* WorldPay: Add support for store method [bayprogrammer] #3232 +* Adyen: Support for additional AVS code mapping [jknipp] #3236 +* Adyen: Update message for AVS result code 'A' to generically cover postal code mismatches [jknipp] #3237 +* CyberSource: Update CyberSource SOAP documentation link [vince-smith] #3204 +* USAePay: Handle additional error codes and add default error code [estelendur] #3167 +* Braintree: Add `skip_avs` and `skip_cvv` gateway specific fields [leila-alderman] #3241 +* NAB Transact: Update periodic test url [mengqing] #3177 +* NMI: Add level 3 gateway-specific fields tax, shipping, and ponumber [jasonxp] #3239 +* Checkout V2: Update stored card flag [curiousepic] #3247 +* NMI: Add support for stored credentials [bayprogrammer] #3243 +* Spreedly: Consolidate API requests and support bank accounts [lancecarlson] #3105 +* BPoint: Hook up merchant_reference and CRN fields [curiousepic] #3249 +* Checkout V2: Stop sending phone number to Checkout V2 integration [filipebarcos] #3248 +* Barclaycard Smartpay: Add support for 3DS2 [britth] #3251 +* Adyen: Add support for non-fractional currencies [molbrown] #3257 +* Decidir: Add new gateway [jknipp] #3254 +* Checkout V2: Reapply Update stored card flag [curiousepic] +* CyberSource: Update supported countries [molbrown] #3260 +* Credorax: Update supported countries [molbrown] #3260 +* Kushki: Update supported countries [molbrown] #3260 +* Paypal: Update supported countries [molbrown] #3260 +* BlueSnap: Send amount in capture requests [jknipp] #3262 +* Mundipagg: Add Alelo card support [jasonxp] #3255 +* Adyen: Remove temporary amount modification for non-fractional currencies [molbrown] #3263 +* Adyen: Set blank state to N/A [therufs] #3252 +* MiGS: Add tx_source gateway specific field [leila-alderman] #3264 +* NMI: Correct password scrubber to scrub symbols [hdeters] #3267 +* Global Collect: Only add name if present [curiousepic] #3268 +* HPS: Add Apple Pay raw cryptogram support [slogsdon] #3209 +* CardConnect: Fix parsing of level 3 fields [hdeters] #3273 +* TrustCommerce: Support void after purchase [jknipp] #3265 +* Payflow: Support arbitrary level 2 + level 3 fields [therufs] #3272 +* BlueSnap: Default to not send amount on capture [molbrown] #3270 +* Spreedly: extra fields, remove extraneous check [montdidier] #3102 #3281 +* Cecabank: Update encryption to SHA2 [leila-alderman] #3278 +* Checkout V2: Fix 3DS 1&2 integration [nicolas-maalouf-cko] #3240 +* Credorax: add 3DS2 MPI auth data support [bayprogrammer] #3274 +* Add Kosovo to the list of countries [AnotherJoSmith] #3226 +* Realex: Adds 3DS 1&2 support through external MPI [filipebarcos] #3284 +* PayPal: Adds 3DS 1 support through external MPI [nebdil] #3279 + +== Version 1.95.0 (May 23, 2019) +* Adyen: Constantize version to fix subdomains [curiousepic] #3228 +* Qvalent: Adds support for standard stored credential framework [molbrown] #3227 +* Cybersource: Send tokenization data when card is :master [pi3r] #3230 + +== Version 1.94.0 (May 21, 2019) +* Mundipagg: Fix number lengths for both VR and Sodexo [dtykocki] #3195 +* Stripe: Support show and list webhook endpoints [jknipp] #3196 +* CardConnect: Add frontendid parameter to requests [gcatlin] #3198 +* Adyen: Correct formatting of Billing Address [nfarve] #3200 +* Stripe: Stripe: Show payment source [jknipp] #3202 +* Checkout V2: Checkout V2: Correct success criteria [curiousepic] #3205 +* Adyen: Add normalized hash of 3DS 2.0 data fields from web browsers [davidsantoso] #3207 +* Stripe: Do not attempt application fee refund if refund was not successful [jasonwebster] #3206 +* Elavon: Send transaction_currency if currency is provided [gcatlin] #3201 +* Elavon: Multi-currency support [jknipp] #3210 +* Adyen: Support preAuths and Synchronous Adjusts [curiousepic] #3212 +* WorldPay: Support Unknown Card Type [tanyajajodia] #3213 +* Mundipagg: Make gateway_affiliation_id an option [curiousepic] #3219 +* CyberSource: Adds Elo Card Type [tanyajajodia] #3220 +* CyberSource: Support standalone credit for cards [curiousepic] #3225 + +== Version 1.93.0 (April 18, 2019) +* Stripe: Do not consider a refund unsuccessful if only refunding the fee failed [jasonwebster] #3188 +* Stripe: Fix webhook creation for connected account [jknipp] #3193 +* Adyen: Upgrade to v40 API version [davidsantoso] #3192 + +== Version 1.92.0 (April 8, 2019) +* BluePay: Send customer IP address when provided [jknipp] #3149 +* PaymentExpress: Use ip field for client_info field [jknipp] #3150 +* Bambora Asia-Pacific: Adds Store [molbrown] #3147 +* Orbital: Pass normalized stored credential fields [curiousepic] #3148 +* Adds Elo card type in general and specifically to Adyen [deedeelavinder] #3153 +* Mercado Pago: Adds Elo card type [deedeelavinder] #3156 +* Litle: Add support for stored credentials [bayprogrammer] #3155 +* Adyen: Correctly process risk_data option [bayprogrammer] #3161 +* Paymentez: Adds Elo card type [deedeelavinder] #3162 +* WorldPay: Adds Elo card type [deedeelavinder] #3163 +* Adyen: Idempotency for non-purchase requests [molbrown] #3164 +* FirstData e4 v27: Support v28 url and stored creds [curiousepic] #3165 +* WorldPay: Fix element order for 3DS + stored cred [bayprogrammer] #3172 +* Braintree: Add risk data to returned response [jknipp] #3169 +* Adyen: Support idempotency on purchase [molbrown] #3168 +* Adyen: Pass phone, statement, device_fingerprint [curiousepic] #3178 +* Adyen: Fix adding phone from billing address [curiousepic] #3179 +* Fix partial or missing address exceptions [molbrown] #3180 +* Adyen: Update to support normalized stored credential fields [molbrown] #3182 +* VisaNet Peru: Always include DSC_COD_ACCION [bayprogrammer] #3174 +* Adyen: Support adjust action [curiousepic] #3190 +* CyberSource: Add support for stored credentials [therufs] #3185 + +== Version 1.91.0 (February 22, 2019) +* WorldPay: Pull CVC and AVS Result from Response [nfarve] #3106 +* Worldpay: Add AVS and CVC Mapping [nfarve] #3107 +* Paymentez: Fixes extra_params field [molbrown] #3108 +* Improved support for account_type using Check class's account_type instead [lancecarlson] #3097 +* USA Epay: Allow quantity to be passed and check custom fields [lancecarlson] #3090 +* Fix usaepay transaction invoice [lancecarlson] #3093 +* Adyen: Handles blank state address field [molbrown] #3113 +* Braintree: Send all country fields [curiousepic] #3112 +* Braintree: Account for empty string countries [curiousepic] #3115 +* Orbital: Support for stored credentials framework [jknipp] #3117 +* Openpay: Fix for marking successful transaction(s) as failed [jknipp] #3121 +* Braintree: Adds support for transaction_source [molbrown] #3120 +* Moneris: Remove redundant card on file guard clause [davidsantoso] #3123 +* Switch order of Romania country codes [molbrown] #3125 +* Blue Snap: Supports Level 2/3 data [molbrown] #3126 +* Blue Snap: Support personal_identification_number [jknipp] #3128 +* ProPay: Send 9 digit zip code without dash [molbrown] #3129 +* Adyen: Extend AVS code mappings [therufs] #3119 +* NMI: Add customer id to authorization on store [curiousepic] #3130 +* Trans First Express: Don't pass blank name field [curiousepic] #3133 +* TrustCommerce: Send full name on ACH transactions [jknipp] #3132 +* Qvalent: Map CVV Result to responses [curiousepic] #3135 +* Card Connect: Handle 401s as responses [curiousepic] #3137 +* Worldpay: Introduce normalized stored credential options [davidsantoso] #3134 +* Worldpay: Adjust use of normalized stored credentials hash [davidsantoso] #3139 +* Adyen: Enable Dynamic 3DS [molbrown] #3138 +* Fat Zebra: Support voids [curiousepic] #3142 +* Blue Snap: Support ACH/ECP payments [jknipp] #3143 +* Blue Snap: Fix Card-on-File field typo [jknipp] #3143 +* Add Bambora gateway [InfraRuby] #3145 +* Bambora Asia-Pacific: Updates Gateway [molbrown] #3145 +* PaymentExpress: Support ClientInfo field [jknipp] #3131 +* Pin Payments: Concatenate card and customer tokens when storing card [therufs] #3144 +* Update Discover regex to allow card numbers longer than 16 digits [prashcr] #3146 +* Merrco partial refunds fix [payfirma1] #3141 + +== Version 1.90.0 (January 8, 2019) +* Mercado Pago: Support "gateway" processing mode [curiousepic] #3087 +* Braintree: Update gem to latest version [curiousepic] #3091 +* Adyen: Pass arbitrary riskData fields [curiousepic] #3089 +* Worldpay: Fix cookie header name [curiousepic] #3099 +* Paymentez: Adds support for extra_params optional field [molbrown] #3095 +* Braintree Blue: Support Level 2 and 3 data fields [curiousepic] #3094 +* Braintree Blue: Refactor line_items field [curiousepic] #3100 +* TrustCommerce: Use `application_id` [nfarve] #3103 +* Stripe: Add 3DS Support [nfarve] #3086 +* Cecabank: Append error text to message [therufs] #3127 + +== Version 1.89.0 (December 17, 2018) +* Worldpay: handle Visa and MasterCard payouts differently [bpollack] #3068 +* QuickPay: update supported countries [ta] #3049 +* WorldPay: set cardholder name to "3D" for 3DS transactions [bpollack] #3071 +* Authorize.Net: Support refunds for bank accounts [nfarve] #3063 +* Stripe: support specifying a reason for refunds [yosukehasumi] #3056 +* Paybox Direct: add support for XPF currency [adam-stead] #2938 +* TrustCommerce: Add ACH Ability [nfarve] #3073 +* Payeezy: Support $0 for verify transactions [molbrown] #3074 +* USA ePay: add support for recurring transactions, custom fields, and line items [lancecarlson] #3069 +* Add dLocal gateway [curiousepic] #3709 +* dLocal: Require secret_key [curiousepic] #3080 +* Adyen: Implement 3DS [nfarve] #3076 +* Adyen: Add 3DS Fix [nfarve] #3081 +* Payeezy: Add `stored_credentials` [nfarve] #3083 +* Fix CVC validation for 0 length CVC [filipebarcos] #3082 +* NMI: Supports vendor_id and processor_id fields [molbrown] #3085 +* Update Braintree Gem [curiousepic] #3311 + +== Version 1.88.0 (November 30, 2018) +* Added ActiveSupport/Rails master support [Edouard-chin] #3065 + +== Version 1.87.0 (November 29, 2018) +* Barclaycard Smartpay: Improves Error Handling [deedeelavinder] #3026 +* Braintree: Fix passing phone-only billing address [curiousepic] #3025 +* Litle: Capitalize check account type [curiousepic] #3028 +* Braintree: Account for nil billing address fields [curiousepic] #3029 +* Realex: Add verify [kheang] #3030 +* Braintree: Actually account for nil address fields [curiousepic] #3032 +* Mercado Pago: do not infer card type [bpollack] #3038 +* Credorax: allow sending submerchant ID (h3 parameter) [bpollack] #3040 +* Worldpay: Pass stored credential option fields [curiousepic] #3041 +* Make behavior of nil CC numbers more consistent [guaguasi] #3010 +* Moneris: Adds Credential on File logic [deedeelavinder] #3042 +* Adyen: Return AVS and CVC Result [nfarve] #3044 +* Paymentez: Supports phone field, does not send if empty [molbrown] #3043 +* Braintree: Account for nil address with existing customer [curiousepic] #3047 +* Optimal Payment: Add verify capabilities #3052 +* Moneris: Allows cof_enabled gateway to process non-cof transactions [deedeelavinder] #3051 +* Cenpos: update supported countries [bpollack] #3055 +* CyberSource: update supported countries [bpollack] #3055 +* MiGS: update supported countries [bpollack] #3055 +* Clearhaus: update submission data format [bpollack] #3053 +* Forte: Allow void on capture [nfarve] #3059 + +== Version 1.86.0 (October 26, 2018) +* UsaEpayTransaction: Support UMcheckformat option for echecks [dtykocki] #3002 +* Global Collect: handle internal server errors [molbrown] #3005 +* Barclaycard Smartpay: allow third-party payouts for credits [bpollack] #3009 +* RuboCop: AlignHash [nfarve] #3004 +* Beanstream: Switch `recurringPayment` flag from boolean to integer [dtykocki] #3011 +* Update Swipe HQ endpoint [bdewater] #3013 +* Braintree: Adds device_data [deedeelavinder] #3012 +* Payflow Express: Add phone to returned Response [filipebarcos] #3003 +* Authorize.Net: Pass some level 3 fields [curiousepic] #3022 +* Add state to the netbanx payload [Girardvjonathan] #3024 + +== Version 1.85.0 (September 28, 2018) +* Authorize.Net: Support custom delimiter for cim [curiousepic] #3001 + +== Version 1.84.0 (September 27, 2018) +* PayU Latam: support partial captures [bpollack] #2974 +* Braintree: Reflect correct test mode in Braintree responses [elfassy] #2980 +* FirstPay: Expose error code [curiousepic] #2979 +* Barclaycard Smartpay: Pass device_fingerprint when specified [dtykocki] #2981 +* Komoju: remove no-longer-relevant sandbox URL [miyazawadegica] #2987 +* [POSSIBLE BREAKAGE] Determine credit cards via functions [bpollack] #2983 +* Drop support for Laser cards [bpollack] #2983 +* Improve Maestro card detection [bpollack] #2983 +* Add ROU alpha3 code for Romania [dtykocki] #2989 +* [POSSIBLE BREAKAGE] Drop support for Solo and Switch cards [bpollack] #2991 +* Add support for Carnet cards [bpollack] #2992 +* Stripe: support a reason for voiding a transaction [whitby3001] #2378 +* Payeezy: Add reversal_id in support of timeout reversals [dtykocki] #2997 +* Stripe: support Level 3 transaction fields [bpollack] #2996 +* Conekta: support Carnet cards [bpollack] #2999 +* Openpay: support Carnet cards [bpollack] #2999 +* Adyen: Add support for GooglePay [dtykocki] #2971 + +== Version 1.83.0 (August 30, 2018) +* CT Payment: Update How Address is Passed [nfarve] #2960 +* Adyen: Add RecurringProcessingModel [nfarve] #2951 +* Optimal Payments: update country list [bpollack] #2961 +* Ebanx: update sandbox and production URLs [vnbrs] #2949 +* Ebanx: support additional countries [vnbrs] #2950 +* Gateway generator: fix a typo that would cause the script to crash [bpollack] #2962 +* Clearhaus: use $0 for verify transactions [bpollack] #2964 +* Global Collect: properly handle partial captures [bpollack] #2967 +* Braintree: Add support for GooglePay [dtykocki] #2966 +* Adyen: allow overriding card brands [bpollack] #2968 +* Adyen: allow custom routing [bpollack] #2969 +* First Pay: Adds scrubbing [deedeelavinder] #2972 + +== Version 1.82.0 (August 13, 2018) +* FirstData: add support for WalletProviderID in v27 gateway [bpollack] #2946 +* BlueSnap: Handle 403 responses [curiousepic] #2948 +* BlueSnap: Add StoreCard Field [nfarve] #2953 +* Worldpay: support installments [bpollack] #2957 +* Paymentez: support partial refunds [bpollack] #2959 +* Payflow: allow support for partial captures [pi3r] #2952 + +== Version 1.81.0 (July 30, 2018) +* GlobalCollect: Don't overwrite contactDetails [curiousepic] #2915 +* Pin Payments: Pass reference for statement desc [curiousepic] #2919 +* FirstData: introduce v27 gateway [shasum] #2912 +* Stripe: Fix contactless magstripe support [abhiin1947] #2917 +* ANET: Expose full response code [curiousepic] #2924 +* Global Collect: Fix customer data field structure [curiousepic] #2929 +* Adyen: Set Default Name for Apple Pay Transactions [nfarve] #2930 +* Beanstream: Update to use api key with login credentials [nfarve] #2934 +* CT Payments: Fix a typo in the live URL scheme [bpollack] #2936 +* CyberSource: Don't throw exceptions on HTML responses [bpollack] #2937 +* CyberSource: Remove extraneous parameter blocking echecks [chriscz] #2861 +* FirstPay: Update Fields For Recurring Payments [nfarve] #2940 +* Remove unused handle_response method [bl] #2309 +* Barclaycard Smartpay: bump API version to v30 [bpollack] #2941 +* Safecharge: Remove duplicate supported country [curiousepic] +* Payflow Express: Use SHIPTONAME instead of `full_name` for shipping address [filipebarcos] #2945 + +== Version 1.80.0 (July 4, 2018) +* Default SSL min_version to TLS 1.1 to comply with June 30 PCI DSS deadline [bdewater] #2909 +* Paymentez: return a Result object even when the upstream server 500s [bpollack] #2871 +* Drop support for Ruby versions older than 2.3 [bpollack] #2863 +* Bridge Pay: don't throw an exception when bank account type is omitted [bpollack] #2873 +* Avoid making actual connections in Blue Snap and Mundipagg unit tests [bpollack] #2875 +* Avoid making actual connections in the connection unit tests [bpollack] #2876 +* Openpay: support payment installments [bpollack] #2865 +* First Pay: support recurring charges [bpollack] #2877 +* Bridge Pay: pass full name of account type for echeck transactions [bpollack] #2878 +* Kushki: do not send 0 for tax values if tax values are not provided [bpollack] #2886 +* Payflow: Update ACH tests [curiousepic] #2887 +* Credorax: support passing billing description [bpollack] #2889 +* MiGS: scrub 3DS fields [abarrak] #2771 +* Forte: avoid crashing when location_id or account_id have spaces [bpollack] #2890 +* Adyen: Support Network Tokenization Cards via mpiData fields [curiousepic] #2891 +* Moneris US: Add ACH [nfarve] #2888 +* Realex: Pass amount for captures [curiousepic] #2895 +* Card Connect: support storing cards [bpollack] #2896 +* Avoid mutating headers passed in for Active Merchant connections [grantbdev] #2892 +* Forte: add support for refunds [bpollack] #2898 +* Forte: fix a bug in logic for selecting billing names [whitby3001] #2381 +* Paymentez: allow capture amount to exceed authorization amount [bpollack] #2900 +* JetPay: fix typo in error messages [reynhout] #2749 +* Braintree: add support for Maestro cards [matthewheath] #2571 +* Visanet Peru: Refund on unsettled transactions [nfarve] #2772 +* Remove iDeal offsite gateway references [bdewater] #2807 +* Conekta: Allow customer application in headers [curiousepic] #2908 +* Payment Express: use testing URLs when testing [oklas] #2231 +* Redsys: Fix payments with cc token [Leonardo Diez] #2586 +* Redsys: Missing cardnumber params in xml_signed_fields [nerburish] #2628 +* Bogus: allow authorizing with a tokenized card [Azdaroth] #2703 +* CT Payment: Add new gateway [nfarve] #2911 + +== Version 1.79.2 (June 2, 2018) +* Fix Gateway#max_version= overwriting min_version [bdewater] + +== Version 1.79.1 (May 31, 2018) +* Fix Net::HTTP connections defaulting to connection: keep-alive instead of close since #2862 [bpollack] #2868 +* Mundipagg: allow passing holder_document for credit card purchases [bpollack] #2864 +* Conekta: support monthly_installments [bpollack] #2866 +* Authorize.net: allow sending email_customer set to false [bpollack] #2867 + +== Version 1.79.0 (May 30, 2018) +* Allow setting min/max SSL version for a connection on Ruby 2.5 [bdewater] #2775 +* Add `gateways:ssl:min_version` rake task to test upcoming TLS 1.0 deprecation deadline [bdewater] #2775 +* Log negotiated SSL version and cipher [bdewater + methodmissing] #2862 +* Remove support for Rails < 4.2, add support for Rails 5.2 and Ruby 2.5 [bdewater] +* Spreedly: Support verify and find transactions [abarrak] #2798 +* Adyen: Support merchant-specific subdomains [curiousepic] #2799 +* Fix ENV based configuration of Net::Http for proxies [bbergstrom] #2800 +* ANET: Withhold cryptogram for credit [curiousepic] #2804 +* Borgun: Remove batch from request parameters [deedeelavinder] #2805 +* WorldPay: Remove Inquiry requests in verify transactions [nfarve] #2802 +* Credorax: Update tests [curiousepic] #2809 +* Braintree: Remove decimal for non-fractional currencies [nfarve] #2806 +* Realex: Add documented country support for US and CA [a-salty-strudel] #2810 +* Paymentez: Add `tax_percentage` optional parameter [deedeelavinder] #2814 +* Braintree: Add `skip_advanced_fraud_checking` optional parameter [deedeelavinder] #2811 +* SafeCharge: Additional gateway options [dtykocki] #2816 +* FirstPay: Handle missing billing addresses [dtykocki] #2822 +* Realex: Add ApplePay Support [nfarve] #2820 +* Checkout V2: Additional gateway options [dtykocki] #2821 +* CyberSource: Support 3ds validate request [curiousepic] #2823 +* Paymentez: Remove card tokenization step from authorize [dtykocki] #2825 +* WorldPay: Add 3DS [nfarve] #2819 +* EBANX: Interpolate authorization string [curiousepic] #2830 +* CyberSource: Support 3DS validation for authorize [curiousepic] #2832 +* Redsys: Fix ISO code for PLN [chopenhauer] #2831 +* Merchant E Solutions: Support transcript scrubbing [curiousepic] #2836 +* Paystation: Support transcript scrubbing [curiousepic] #2837 +* Psigate: Support transcript scrubbing [curiousepic] #2835 +* Braintree: Adding 3D Secure pass thru capabilities [filipebarcos] #2843 +* Authorize.net: Add flexibility for 3D Secure Parameters [filipebarcos] #2844 +* Elavon: Update Country List [nfarve] #2840 +* WorldPay: Update Country List [nfarve] #2841 +* Merchant Warrior: Support transcript scrubbing [curiousepic] #2845 +* NAB Transact: Pass nonfractional amounts correctly [curiousepic] #2843 +* Realex: Update Country List [nfarve] #2842 +* QBMS: Support transcript scrubbing [curiousepic] #2849 +* Adyen: Add support for installments [nfarve] #2839 +* Paymentez: Read messages on Failure with no error [nfarve] #2850 +* Paymentez: Fix response message conditional [curiousepic] #2851 +* Add ability to send email receipt [nfarve] #2852 +* Barclaycard Smartpay: Pass shopper_interaction [curiousepic] #2853 +* Stripe: Treat UGX as a zero-decimal currency [bpollack] #2857 +* Mundipagg: Remove Billing Address if no Address Sent [nfarve] #2855 +* Paypal: Support more robust scrubbing [curiousepic] #2858 +* Stripe: Report internal Stripe errors as failures [bpollack] #2859 +* Authorize.net: Add ability to pass `customer_payment_profile_id` [nfarve] #2854 + +== Version 1.78.0 (March 29, 2018) +* Litle: Add store for echecks [nfarve] #2779 +* Litle: Add Support for Echeck [nfarve] #2776 +* Orbital: Correct level 2 tax handling [deedeelavinder] #2729 +* Payeezy: Change determination method of endpoint for store request [deedeelavinder] #2731 +* Adyen: Return refusal_reason_raw when present [curiousepic] #2728 +* Payeezy: Update Store method [nfarve] #2733 +* CenPOS: Remove gzip encoding header [curiousepic] #2735 +* Mercado Pago: Allow binary_mode to be changed [nfarve] #2736 +* Stripe: Accept strings for refund_fee_amount [curiousepic] #2738 +* Orbital: Complete scrub test coverage [curiousepic] #2739 +* MIGS: Scrub sensitive data [curiousepic] #2740 +* Worldpay US: Scrub sensitive data [curiousepic] #2742 +* WorldPay: Remove Israel from supported country list [dtykocki] #2746 +* Optimal Payments: Scrub sensitive data [curiousepic] #2743 +* USA Epay Transaction: Scrub sensitive data [curiousepic] #2745 +* MIGS: Add unit test for scrub [curiousepic] #2747 +* Worldpay US: Fix bank account scrub [curiousepic] #2748 +* Litle: Add support for merchantData elements [dtykocki] #2751 +* Paymentez: Add support for purchasing and authorizatin with tokens [bpollack] #2753 +* Ingenico: Remove Trinidad and Tobego from supported country list [bpollack] #2754 +* Barclaycard Smartpay: Remove Georgia from supported country list [bpollack] #2755 +* Merchant Warrior: Remove requirement for state field [joshnuss] #2638 +* Wirecard: Adding missing DigiCert Global Root G2 Cert [filipebarcos] #2759 +* Redsys: Add support for CNY, IDR, INR, KRW and TWD [chopenhauer] #2761 +* Optimal Payments: Fix scrub for double escaping [curiousepic] #2763 +* Orbital: Scrub profile transactions [curiousepic] #2762 +* BlueSnap: Fix currency passing [curiousepic] #2765 +* Stripe: Support pickup_card decline code [dtykocki] #2764 +* Improve scrub testing for five gateways [curiousepic] #2767 +* Payflow: Support scrub [curiousepic] #2768 +* SecureNet: Support scrub [curiousepic] #2769 +* Payeezy: Update transaction method when using stored cards [dtykocki] #2770 +* Citrus Pay, DIBS, 1stPayGateway, Global Transport, NETbilling, Ogone, TNS: remove TLS 1.0 requirement [bdewater] #2774 +* CardStream: Default IP and customer country [dtykocki] #2773 +* Stripe: Support destination amount [dtykocki] #2777 +* GlobalCollect: Update supported country list [dtykocki] #2783 +* Adyen: Support store action [curiousepic] #2784 +* Psigate: Update Test URL and Card [nfarve] #2785 +* USA ePay Transaction: Support ACH/eChecks [curiousepic] #2786 +* PayU Latam: Support language parameter [dtykocki] #2787 +* Payflow: Pass OrderDesc field [curiousepic] #2789 +* Global Collect: Add arbitrary fraudField params [curiousepic] #2790 +* Paystation: Support verify action [curiousepic] #2793 +* Checkout V2: Return error codes in response [curiousepic] #2791 +* CardStream: Change refund to use REFUND_SALE [dtykocki] #2795 +* Spreedly: Scrub sensitive transaction data [abarrak] #2781 +* Stripe: Add `exchange_rate` parameter [WilsonChiang] #2796 +* Mundipagg: New Gateway Implementation [nfarve] #2791 + +== Version 1.77.0 (January 31, 2018) +* Authorize.net: Allow Transaction Id to be passed for refuds [nfarve] #2698 +* Forte: ensure unit tests are local-only [bpollack] #2696 +* Moneris US: ensure unit tests are local-only [bpollack] #2696 +* Payflow: Change Verify Method for Amex Cards [nfarve] #2693 +* Safe Charge: fix an issue with variable shadowing in the adapter [bpollack] #2697 +* Crashnet: add scrubbing support [bpollack] #2695 +* Barclays EPDQ: add scrubbing support [bpollack] #2695 +* Fat Zebra: add remote scrubbing test [bpollack] #2695 +* Clearhaus: add remote scrubbing test [bpollack] #2695 +* Borgun: add remote scrubbing test [bpollack] #2695 +* Stripe: Added support for the quickchip entry mode option [rbalsdon] +* Ogone: Add tests for scrubbing [bpollack] #2700 +* Global Transport: Add scrubbing support [bpollack] #2700 +* HPS: Add scrubbing support [bpollack] #2700 +* FirstData E4: Improve scrubbing and add remote scrubbing test [bpollack] #2700 +* Elavon: Add scrubbing support [bpollack] #2700 +* Data Cash: Add scrubbing support [bpollack] #2700 +* Litle: Fix testing URL [wsmoak] #2673 +* Barclays ePDQ Extra Plus: Add missing Entrust root certificates [pacso] #2614 +* Moneris US: Add scrubbing support [bpollack] #2702 +* Mercury: Add scrubbing support [bpollack] #2702 +* Fat Zebra: Tweak remote scrubbing test [bpollack] #2704 +* Card Connect: Add new gateway [nfarve] #2706 +* Payeezy: Ensure store calls are properly scrubbed [dtykocki] #2709 +* Payeezy: Add unit test for scrubbing store call [dtykocki] #2710 +* Element: Correct URL used by store transactions [dtykocki] #2711 +* Borgun: Add support for specifying TerminalID [bpollack] #2712 +* Barclaycard Smartpay: 3DS Implementation [nfarve] #2714 +* Payeezy: Surface gateway_message on failure [curiousepic] #2717 +* Payment Express: Scrub merchant password [curiousepic] #2723 +* Stripe: Fix Partial Application Fee Refunds [curiousepic] #2713 +* GooglePay: Support network tokenized cards [joshnuss] #2725 + +== Version 1.76.0 (January 3, 2018) +* PayU Latam: Change default text for description [nfarve] #2669 +* Checkout V2: Allows AVS and CVV result details to come through on authorizations [deedeelavinder] #2650 +* Global Collect: Adds boolean option for pre_authorization [deeedeelavinder] #2651 +* Credorax: Pass Transaction Type field [curiousepic] #2653 +* Add CardProcess Gateway [bpollack] #2659 +* Safe Charge: Provision 3DS option for approved merchants [deedeelavinder] #2661 +* PayU Latam: Require payment_country on initialize [curiousepic] #2663 +* Adyen: Remove CVV as Required Field and Determines shopperInteraction [nfarve] #2665 +* SafeCharge: add support for VendorID, WebsiteID, and IP logging [bpollack] #2667 +* Safe Charge: Adds 3DS flag [deedeelavinder] #2668 +* CardProcess: Fix success? to always return true or false [bpollack] #2674 +* SagePay: Correct CVV, AVS codes for Sagepay [singhai0] #2670 +* PayU Latam: Count pending Voids as successful [curiousepic] #2677 +* Mercado Pago: Ensure acess tokens are URL escaped [bpollack] #2675 +* MiGS: Update hash format to SHA256 and restore remote tests [bpollack] #2676 +* MiGS: Support verify calls [bpollack] #2664 +* iATS: Fix Messages with Failure on iATS Server [nfarve] #2680 +* Barclaycard Smartpay: Correct repsonse for fraud rejects #2683 +* Adyen: Allow incomplete addresses in some situations [bpollack] #2684 +* Paymentez: Add new gateway [bpollack] #2685 +* PayU Latam: Provide a mechanism to override the amount in verify [dtykocki] #2688 +* Mercado Pago: Support X-Device-Session-ID Header [bpollack] #2689 +* Mercado Pago: Support arbitrary additional_info JSON [bpollack] #2691 +* FirstData E4: Override ECI value for Apple Pay transactions with Discover [jasonwebster] #2671 +* Quickbooks: Add payment context to Quickbooks charges and refunds [bdewater] #2694 + +== Version 1.75.0 (November 9, 2017) +* Barclaycard Smartpay: Clean up test options hashes [bpollack] #2632 +* Barclaycard Smartpay: Extra data fields for credits [bpollack] #2631 +* Cyber Source: Correctly passes subscriptionID for store [deedeelavinder] #2633 +* Ebanx: Pass fields for business person responsible [curiousepic] #2635 +* Ebanx: Support Colombian transactions [bpollack] #2636 +* FirstData E4 (Payeezy): Ensure numeric ECI values are zero padded [jasonwebster] #2630 +* Netbanx: Only send currency and billing_details for auths and sales [anotherjosmith] #2643 +* Netbanx: Revert "Fixes basic auth for netbanx by sending the account_number and api_key" [anotherjosmith] #2644 +* PayU Latam: Adds `partnerID`, adjusts phone preferences, allows empty `ip_address`, and adjusts for no `cvv` [deedeelavinder] #2634 +* Sage Payment Solutions: Scrub check info [curiousepic] #2639 +* Worldbank US: Allow using the backup URL [bpollack] #2641 +* Worldbank US: Allow using the backup URL per-request [bpollack] #2645 + +== Version 1.74.0 (October 24, 2017) +* Adyen: Update list of supported countries [dtykocki] #2600 +* Authorize.net CIM: Handle multiple error messages [amandapuff] #2537 +* Barclaycard Smartpay: Pass street and house_number fields, in addition to standard address [deedeelavinder] #2603 +* Barclaycard Smartpay: Use authorization pspReference for refunds [davidsantoso] #2599 +* Beanstream: Pass email fields without address [curiousepic] #2615 +* Beanstream: Support recurringPayment for auth, capture, and purchase transactions [dtykocki] #2617 +* Borgun: Add support for USD transactions [dtykocki] #2602 +* Borgun: Include currency code from split authorization for voids [dtykocki] #2605 +* Checkout V2: Expose AVS and CVV results for purchases [dtykocki] #2619 +* Credorax: Update response codes [curiousepic] #2595 +* CyberSource: Support 3DSecure requests [curiousepic] #2624 +* Ebanx: Pass person_type and name for stored cards [curiousepic] #2621 +* Ebanx: Support Store and person_type option [curiousepic] #2604 +* Elavon: Update endpoint URLs [curiousepic] #2608 +* Netbanx: Fix basic auth by sending the account_number and api_key [anotherjosmith] #2616 +* Payeezy: Adds support for store [deedeelavinder] #2591 +* PayU Latam: Set payment_country gateway attribute [curiousepic] #2611 +* Redsys: Support the DKK currency type [bpollack] #2618 +* WePay: Only send ip and device for non-recurring transactions [dtykocki] #2597 + +== Version 1.73.0 (September 28, 2017) +* Adyen: Use original authorization pspReference on Refunds [lyverovski] #2589 +* Braintree Blue: Explicitly require braintree-ruby version 2.78 [anotherjosmith] +* FirstData E4: Scrub 3DS cryptogram [jasonwebster] #2596 +* PayHub: Replace single quotes with double quotes in error message [matthewheath] #2572 +* Wirecard: Format non-fractional currency amounts correctly [bdewater] #2594 + +== Version 1.72.0 (September 20, 2017) +* Adyen: Fix failing remote tests [dtykocki] #2584 +* Authorize.net: Remove numeric restriction on customer ID [dtykocki] #2579 +* Authorize.net: Restore default state value for non-US addresses [jasonwebster] #2563 +* Beanstream: Do not default state and zip with empty country [dtykocki] #2582 +* Braintree Blue: Add eci_indicator field for Apple Pay [davidsantoso] #2565 +* Conekta: Add guard clause for details fallbacks [curiousepic] #2573 +* Conekta: Pull required details from billing address [nfarve] #2568 +* DataCash: Enable refunding recurring transactions [davidsantoso] #2560 +* Ebanx: Adds Brazil Specific Parameters [nfarve] #2559 +* Kushki: Add support for refunds [dtykocki] #2575 +* MercadoPago: Additional tweaks for transaction requests [davidsantoso] +* MercadoPago: Default to alphanumeric order_id [davidsantoso] +* MercadoPago: Send diners_club cards as diners [davidsantoso] #2585 +* PayU Latam: Correctly condition buyer element fields [curiousepic] #2578 +* PayU Latam: Pass unique buyer fields and country requirements [curiousepic] #2570 +* Qvalent: Support general credit [curiousepic] #2558 +* SafeCharge: Update to Version 4.1.0 [nfarve] #2556 +* WePay: Don't default API version header [curiousepic] #2567 +* WePay: Don't require email for Store [curiousepic] #2588 + +== Version 1.71.0 (August 22, 2017) +* Bambora formerly Beanstream: Change casing on customerIp variable [aengusbates] #2551 +* Checkout V2: Add localized_amount support to add_invoice function [nicolas-maalouf-cko] #2452 +* Checkout V2: Add UAE to country list [shasum] #2548 +* Checkout V2: Fix success response code validation [nicolas-maalouf-cko] #2452 +* CreditCall: Only allow AVS when specified [curiousepic] #2549 +* CreditCall: Parse additional params from responses [nfarve] #2552 +* CreditCall: Parse more response params [nfavre] #2543 +* MercadoPago: Small tweaks to building requests [davidsantoso] #2555 +* Orbital: Support Network Tokenization Credit Cards [curiousepic] #2553 +* Orbital: Updgrade schema version to 7.1 [curiousepic] #2546 +* Remove HUF from default non-fractional currencies [curiousepic] #2538 +* Stripe: Add support for statement_address parameters for EMV transactions [malcolm-mergulhao] #2524 +* TransFirst Express: Don't send address2 without value [nfarve] #2545 +* TransFirst Express: Fix Optional Fields Being Passed Blank [nfarve] #2550 +* TransFirst: Fix partial refund [nfarve] #2541 +* Vantiv (Litle): Pass 3DS fields [curiousepic] #2536 +* Braintree Blue: Add phone to options [deedeelavinder] #2564 + +== Version 1.70.0 (August 4, 2017) +* Barclaycard Smartpay: Provider a default billing address house number [nfarve] #2520 +* FirstData E4: Fix duplicate XID and CAVV values in tokenized transactions [jasonwebster] #2529 +* FirstData E4: Loose XSD validation for Payeezy (FirstData E4) [jasonwebster] #2529 +* GlobalTransport: Support partial authorizations [dtykocki] #2511 +* Litle: Update schema and certification tests to v9.12 [curiousepic] #2522 +* Litle: Update urls and name to Vantiv [curiousepic] #2531 +* Mercado Pago: Add gateway support [davidsantoso] #2518 +* Orbital: Add support for level 2 data [dtykocki] #2515 +* PayU Latam: Pass DNI Number [curiousepic] #2517 +* Qvalent: Pass 3dSecure fields [curiousepic] #2508 +* SafeCharge: Correct UserID field name [curiousepic] +* SafeCharge: Pass UserID field [curiousepic] #2507 +* AuthorizeNet: Allow Response Code 4 to be returned as successful [nfarve] #2530 +* Forte: Remove order number from captures in Forte Gateway [nfarve] #2532 +* PayU Latam: Add additional mandatory fields [deedeelavinder] #2528 + +== Version 1.69.0 (July 12, 2017) +* WePay: Add payer_rbits and transaction_rbits optional fields [davidsantoso] +* Adyen: Use Active Merchant standard order_id option for reference [jasonwebster] #2483 +* Correct calculation for three-exponent currencies [curiousepic] #2486 +* SagePay: Use VPSTxId from authorization for refunds [dtykocki] #2489 +* Payflow: Move PAYPAL-NVP header option to a class attribute on the payment gateway [deuxpi] #2492 +* Optimal Payments: Pass CVD indicator accurately [curiousepic] #2491 +* SagePay: Make Repeat purchase if payment is a past authorization [curiousepic] #2495 +* Netbanx: map response errorCodes onto standard error code [iirving] #2456 +* Netbanx: Update supported countries and cardtypes [iirving] #2456 +* Barclaycard Smartpay: Support 0- and 3-exponent currencies [curiousepic] #2498 +* CyberSource: Fix XSD schema validation issues [jasonwebster] #2497 +* WorldPay: Support three-decimal currencies [curiousepic] #2501 +* NMI: Add first and lastname to echeck transactions [dtykocki] #2499 +* PayFlow: Add optional email field [davidsantoso] #2505 +* Worldpay: Support Credit on CFT-enabled merchant IDs [curiousepic] #2503 +* FirstPay: Add processor_id field [davidsantoso] #2506 +* Authorize.Net: Use two character default for billing state [dtykocki] #2496 + +== Version 1.68.0 (June 27, 2017) +* Authorize.Net: Return failed response if forced refund settlement fails [bizla] #2476 +* Authorize.net: Concatenate address1 and address2 [dtykocki] #2479 +* Braintree Blue: Braintree Blue: Add ECI indicator to Android Pay transactions [davidsantoso] #2474 +* Credorax: Support 0- and 3-exponent currencies [curiousepic] +* Cybersource: update supported card types [bdewater] #2477 +* FirstData: Add a default network tokenization strategy for FirstData E4 [krystosterone] #2473 +* FirstPay: FirstPay: Update hostname and force TLSv1 minimum [davidsantoso] #2478 +* JetPay V2: Support store transactions and token based payments [shasum] #2475 +* Moneris: Add 3DS fields for decrypted Apple and Android Pay data [davidsantoso] #2457 +* Openpay: Send customer name and email in authorize and purchase [dtykocki] #2468 +* Payflow: Moved to name value pair (NVP) with payflow [jusleg] #2462 +* Payflow: Set PAYPAL_NVP header as optional [davidsantoso] #2480 +* QuickPay V10: Return last response for purchase and authorize [curiousepic] #2461 +* SafeCharge: Map billing address fields [davidsantoso] #2464 +* SafeCharge: Track currency from original transaction [davidsantoso] #2470 +* Support three-decimal currencies [curiousepic] #2466 +* Trexle: Add gateway support [hossamhossny] #2351 + +== Version 1.67.0 (June 8, 2017) +* Acapture: Pass 3D Secure fields [davidsantoso] #2451 +* Authorize.net: Pass Level 2 Data Fields [curiousepic] #2444 +* Credorax: Add 3D Secure authentication fields [davidsantoso] #2446 +* Ebanx: Add gateway support [davidsantoso] #2447 +* Ebanx: Reduce supported countries to Brazil and Mexico [davidsantoso] +* FirstData Payeezy: Set default ECI value for auth/purchase transactions [jasonwebster] #2448 +* JetPay V2: Add new gateway [shasum] #2442 +* JetPay V2: Add optional tax data to capture calls [shasum] #2445 +* NMI: Add Network Tokenization support [shasum] #2431 +* Orbital: Pass soft descriptors from options hash [curiousepic] +* Orbital: Update test and production urls [jcowhigjr] #2436 +* Payeezy: Add client_email field for telecheck [davidsantoso] #2455 +* Payeezy: Add customer_id_type and customer_id_number fields [davidsantoso] #2454 +* Quickpay V10: Fix store and token use for recurring payments [wsmoak] #2180 +* Elavon: Support custom fields [curiousepic] #2416 +* WePay: Support risk headers [shasum] #2419 +* WePay: Add Canada as supported country [shasum] #2419 +* Fat Zebra: Fix xid 3D Secure field [curiousepic] +* SafeCharge: Mark support for European countries [curiousepic] +* Checkout V2: Pass customer ip option [curiousepic] +* Realex: Map AVS and CVV response codes [davidsantoso] #2424 +* Opp: Send disable3DSecure custom parameter if present [davidsantoso] #2432 +* SafeCharge: Map standard Active Merchant order_id field [davidsantoso] #2434 +* Payeezy: Default check number to 001 if not present [davidsantoso] #2439 +* Opp: Fix incorrect customParameter key to disable 3DS [davidsantoso] + +== Version 1.66.0 (May 4, 2017) +* Support Rails 5.1 [jhawthorn] #2407 +* ProPay: Add Canada as supported country [davidsantoso] +* ProPay: Add gateway support [davidsantoso] #2405 +* SafeCharge: Support credit transactions [shasum] #2404 +* WePay: Add scrub method [shasum] #2406 +* iVeri: Add gateway support [curiousepic] #2400 +* iVeri: Support 3DSecure data fields [davidsantoso] #2412 +* Opp: Fix transaction success criteria and clean up options [shasum] #2414 + +== Version 1.65.0 (April 26, 2017) +* Adyen: Add Adyen v18 gateway [adyenpayments] #2272 +* Authorize.Net: Force refund of unsettled payments via void [bizla] #2399 +* Barclays ePDQ: removed because it has been replaced by a new API [bdewater] #2331 +* Beanstream: Map ISO province codes for US and CA [shasum] #2396 +* Braintree Blue: Change :full_refund option to :force_full_refund_if_unsettled [bizla] #2403 +* Braintree Blue: Force refund of unsettled payments via void [bizla] #2398 +* Checkout V2: Fix sandbox URL [nicolas-maalouf-cko] #2391 +* Checkout V2: Fix success_from not properly checking two possible success codes [davidsantoso] +* Cybersource: Rescue XML parse exception [shasum] #2380 +* GlobalCollect: Make message and error reporting more robust [curiousepic] #2370 +* GlobalCollect: Set REJECTED refunds as unsuccessful transactions [davidsantoso] #2365 +* GlobalCollect: Truncate firstName field to 15 characters [davidsantoso] +* JetPay: Pass down authorization payment method token to refund a capture [davidsantoso] +* Openpay: Support card points [shasum] #2401 +* Orbital: Don't send CVV indicator if CVV is not present [curiousepic] #2368 +* PayU LATAM: Fix incorrect capture method definition [davidsantoso] +* Payeezy: Support dynamic soft descriptors [shasum] #2384 +* Pin: Add metadata optional field [shasum] #2363 +* Qvalent: Add soft descriptor fields. Add authorize, capture, and void [davidsantoso] +* SafeCharge: Add gateway [davidsantoso] +* SagePay: Support Repeat transactions [curiousepic] #2395 +* Stripe: Support custom application in X-Stripe-Client-User-Agent header [davidsantoso] +* TransFirst Transaction Express: Support ACH [curiousepic] #2389 +* WePay: Support unique_id for idempotent transactions [shasum] #2367 +* Worldpay: Force refund of unsettled payments via void [bizla] #2402 + +== Version 1.64.0 (March 6, 2017) +* Authorize.net: Allow settings to be passed for CIM purchases [fwilkins] #2300 +* Authorize.net: Use new `unsupported_feature` standard error code [jasonwebster] #2322 +* Base Gateway: Add new `unsupported_feature` standard error code [jasonwebster] #2322 +* Braintree Blue: Pass cardholder_name with card [curiousepic] #2324 +* Braintree: Add Android Pay meta data fields [jknipp] #2347 +* CardStream: Add additional of currencies [shasum] #2337 +* Credorax: Return failure response reason [shasum] #2341 +* Digitzs: Add gateway [davidsantoso] +* Digitzs: Remove merchant_id from gateway credentials [davidsantoso] +* GlobalCollect: Pass options to Refund [curiousepic] #2330 +* Kushki: Add new gateway [shasum] #2326 +* Kushki: Remove body from void call [shasum] #2348 +* Linkpoint: Raise ArgumentError when trying to instantiate without `:pem` [jasonwebster] #2329 +* Omise: Enable Japan, JPY and JCB support [zdk] #2284 +* PayU LATAM: Count pending refunds as succeeded [curiousepic] #2336 +* PayU LATAM: Let Refund take amount value [curiousepic] #2334 +* Paymill: Send new required fields on tokenization requests [tschelabaumann] #2279 +* Revert "Authorize.net: Allow settings to be passed for CIM purchases" [curiousepic] #2339 +* Sage: Default billing state when outside US [shasum] #2340 +* Stripe: Remove idempotency key from verify [shasum] #2335 +* TransFirst Transaction Express: Don't send order_id with refunds [curiousepic] #2350 +* TransFirst Transaction Express: Fix improper AVS and CVV response code mapping [shasum] #2342 +* WePay: Update API version [shasum] #2349 +* USA ePay Advanced: Add quick_update_customer action [joshreeves] #2229 + +== Version 1.63.0 (February 2, 2017) +* Authorize.net: Add #unstore support [jimryan] #2293 +* AuthorizeNet: Fix line items quirk [shasum] +* CardStream: Add dynamic descriptor option fields [curiousepic] +* CardStream: Support PEN currency [shasum] +* Culqi: Add new gateway [shasum] +* CyberSource: Add Lebanon to supported countries [shasum] +* Element: Add AVS and CVV codes to response [shasum] +* Firstdata E4 (Payeezy): Set correct ECI value for card present swipes [jasonwebster] #2318 +* GlobalCollect: On purchase skip capture if not required [davidsantoso] +* PaymentExpress: Update supported countries [shasum] +* Remove leading or trailing whitespace from credit card name [davidsantoso] +* Remove support for Ruby 2.0 [jasonwebster] +* Secure Pay AU: Add scrubbing support to Secure Pay AU [bruno] #2253 +* Stripe: Fix error in handling of track-only contactless EMV data [jasonwebster] +* Vanco: Update test URL [davidsantoso] +* WePay: Build fee structure correctly [curiousepic] +* WePay: Remove null address fields from request [davidsantoso] +* WePay: Update WePay to API version 2016-12-07 [davidsantoso] +* Wirecard: Send customer data in requests [davidsantoso] +* Worldpay: Add session id attribute [shasum] +* Worldpay: Do not default address when not provided [shasum] + +== Version 1.62.0 (December 5, 2016) +* AuthorizeNet: Map to standard AVSResult codes [shasum] +* CitrusPay: Add 3DSecureId field [davidsantoso] +* CyberSource: Only get alpha2 country code when it's a known country [bruno] #2238 +* Fat Zebra: Add scrubbing to Fat Zebra gateway [bruno] #2037 +* Monei: Add US and CA as new supported countries [davidgf] #2209 +* NAB Transact: Add scrubbing to NAB Transact [bruno] #2038 +* iATS: Add scrubbing support to iATS [bruno] #2228 +* Stripe: Ensure ECI values for tokenized cards are padded [jasonwebster] #2250 +* Forte: Fix incorrect authorization_code response mapping [davidsantoso] +* maxiPago: Send currency with request [curiousepic] +* Credorax: Map order_id to field H9 [curiousepic] +* Authorize.net: Remove duplicate country GB [shasum] +* PayU Latam: Add processWithoutCvv2 field [shasum] +* Fat Zebra: De-nest soft descriptor fields [curiousepic] +* Credorax: Only pass c5 field for billing address1 [davidsantoso] +* Orbital: Add support for CLP currency [curiousepic] +* Authorize.net: Add line item fields and additional transaction settings [shasum] +* Authorize.net: Pass through `header_email_receipt` [shasum] +* Stripe: Scrub additional network tokenization related sensitive data [jasonwebster] #2251 +* Applying: Worldpay: Format non-fractional currency amounts correctly [jasonwebster] #2267 + +== Version 1.61.0 (November 7, 2016) +* Add codes AQ, BQ, SX, and SS to list of countries and update SD numeric code [zxlin] +* AuthorizeNet: Update supported countries list [shasum] +* Barclay SmartPay: Add support for credit [shasum] +* Barclaycard SmartPay: Update supported countries [shasum] +* BluePay: Add Canada to supported countries list [shasum] +* BlueSnap: Update countries list [shasum] +* Braintree Blue: Add Android Pay support [mrezentes] +* Braintree Blue: Add remote test to verify card token [shasum] +* Braintree Blue: Get Android Pay tx id from payment method, not options [mrezentes] +* CardStream: Add MXN currency code [curiousepic] +* CardStream: Set captureDelay to zero on purchase [davidsantoso] +* CitrusPay: Add gateway [duff] +* CitrusPay: Update URL to current API version [davidsantoso] +* Clearhaus: Fix refund of captures [duff] +* Clearhaus: Update list of non fractal currencies [curiousepic] +* Clearhaus: Use localized amount [curiouspic] +* Conekta: Add void action [MauricioMurga] +* Credorax: Add gateway support [davidsantoso] +* CyberSource, Paymill, Payflow: Add verify_credentials [duff] +* CyberSource: Combine auth_reversal with Void [curiousepic] +* CyberSource: Increase merchant defined data fields [davidsantoso] +* CyberSource: Look up alpha2 country code [curiousepic] +* CyberSource: Use localized_amount [curiousepic] +* Element: Pass order_id and shipping address [curiousepic] +* Fat Zebra: Add cavv, xid, and sli fields [curiousepic] +* Fat Zebra: Fix improper descriptor nesting [curiousepic] +* Find countries if they are differently cased [curiousepic] +* GlobalCollect: Update credit card brand list [curiousepic] +* Jetpay: Support endpoint for Canada [shasum] +* Linkpoint: Clean whitespace from PEM [curiousepic] +* Litle: Retain amount to send in auth reversals [curiousepic] +* Litle: add scrubbing support [bruno] +* MONEI: Update supported countries list [davidgf] +* MiGS: Handle IDR currency [curiousepic] +* Migs: Add support for void [mohsenottello] +* Migs: Support some additional fields [duff] +* Moneris: Fix unit test stubs [shasum] +* Moneris: add scrubbing support [bruno] +* NMI, FirstData: Support verify_credentials [curiousepic] +* Openpay: Add support for verify [duff] +* PayJunctionV2: Add gateway support [shasum] +* PayU Latam: Add new gateway [shasum] +* PayU Latam: Update supported countries list [shasum] +* Payflow: Update supported countries list [shasum] +* PaypalExpress: Add SoftDescriptor field [talyssonoc] +* Redsys: Added DOP and CRC currency [davidsantoso] +* Sage: Add support for scrubbing [bruno] +* SagePay: Fix truncation [duff] +* SecurionPay: Update supported countries list [shasum] +* Stripe: Increase authorize amount during verify [davidsantoso] +* Stripe: Set minimum authorize amount depending on currency [davidsantoso] +* Stripe: Support new network tokenization API params [methodmissing] +* Stripe: Update supported countries list [shasum] +* TNS and CitrusPay: Support scrub and verify_credentials [duff] +* TNS and CitrusPay: Update to version 36 of the API [duff] +* TNS: Try TLS v1 [duff] +* Telr: Add gateway support [curiousepic] +* TransFirsTransactionExpress: Remove blank cvv element [davidsantoso] +* TransFirsTransactionExpress: Take into account blank string CVV [davidsantoso] +* Vanco: Improve handling of success determination [duff] +* Worldpay: Add hcgAdditionalData element [davidsantoso] +* Worldpay: Report error code [curiousepic] + +== Version 1.60.0 (July 4, 2016) +* Orbital: Fix CC num leak on profile calls [drewblas] +* VisaNetPeru: Add ability to refund [duff] +* AuthorizeNet: Fix store using new profile [duff] +* Clearhaus: Support private key for signature [curiousepic] +* Clearhaus: Copy private_key when stripping [curiousepic] +* CertoDirect: Remove gateway [shiroginne] +* Braintree: Extra error messaging [jordan-brough] +* AuthorizeNetCim: Set error code for AuthorizeNetCimGateway response [ka8725] +* Quickpay v10: Remove amount requirement for store [curiousepic] +* PSLCards: correct namespace in doc for Response object from ActiveRecord::Billing to ActiveMerchant::Billing [CJ Keeney] +* Pagar.me: Add pagar.me [chrisenytc] +* Stripe: Update Readme to show stripe support [rhlrjv] +* Orbital: Add support for the BRL currency [duff] +* GlobalTransport: Require TLSv1 [duff] +* Openpay: Allow currency to be specified [darkaz] +* DataCash: Use API version 2 [curiousepic] +* Stripe: Support verify_credentials [duff] +* AuthorizeNet: Support verify_credentials [duff] +* BraintreeBlue: Support verify_credentials [duff] +* Redsys: Added SAR currency [agseco] +* QuickPay: Adding customer_ip for authorize action in quickpay [dinesh] +* MaxiPago: add void and refund [shasum] +* MaxiPago: Allow processor_id override [duff] +* Stripe: Interpret string input to store method as token identifier [bizla] +* MaxiPago: Add verify and scrub [shasum] +* Stripe: Remove metadata restriction from EMV transactions [bizla] +* SagePay: Add optional fields to SagePay requests [cristianstanescu] +* CyberSource: Assign default with override for billing address and email [shasum] +* CyberSource: Assign default order_id [duff] +* TNS: Support asia_pacific endpoint [curiousepic] +* TransFirsTransactionExpress: Fix exception [duff] +* CyberSource: Add decision manager optional fields [shasum] +* CyberSource: Add decision manager optional fields [shasum] +* TNS: Add support for TLS v1.2 [curiousepic] +* QuickpayV7: Default description field for store operation [duff] +* Elavon: Support customer_number field [duff] +* Map test_mode_live_card code to new standard error code [berkcaputcu] +* Elavon: Pass customer_number correctly [duff] +* Stripe: add SG to supported_countries attribute [timbeiko] + +== Version 1.59.0 (May 18, 2016) +* Orbital: Allow AVS parts to be sent sans country [duff] +* SecureNet: Return the right error message for declines [duff] +* Moneris: Add verify [anellis] +* Moneris: Add verify [anellis] +* Jetpay: Add support for origin field[anellis] +* Jetpay: Don't default origin field [duff] +* GlobalCollect: New gateway support [curiousepic] +* Openpay: Use strict_encode64 [duff] +* Sage: Always pass along the billing state [duff] +* VisaNet Peru: New gateway support [shasum] +* Worldpay: Allow installationId to be specified at transaction time [duff] +* SecurionPay: Support store [shasum] +* Barclaycard Smartpay: Proper AVS return codes [curiousepic] +* VisaNetPeru: Pass through CVV [duff] +* Barclaycard Smartpay: Use strict_encode64 [duff] +* VisaNetPeru: Fix error when billing address empty [shasum] +* Vanco: Update live_url [duff] +* Cardstream: Reference purchase [curiousepic] +* Paymill: Fix error handling [methodmissing] +* Latitude19: New gateway support [shasum] +* BraintreeBlue: remove invalid test assertions [prburke] +* Merchant e-Solutions: Pass order_id with capture [curiousepic] +* CyberSource: Add rescue for ResponseErrors [curiousepic] +* AuthorizeNet: Always pass recurringBilling flag if present [curiousepic] +* S5: Pass order_id to TransactionID [curiousepic] +* NMI: Set ACH sec_code from options if present [curiousepic] +* VisaNet Peru: Refactor merchant_id and purchase_number handling [shasum] +* Braintree Blue: Pass descriptor_url field [curiousepic] +* VisaNet Peru: Add merchant_define_data option [duff] +* Merchant e-Solutions: pass optional 3Dsecure params [curiousepic] +* NMI: Fix refunds and voids of echecks [duff] +* VisaNet Peru: Pass dummy email when not present [curiousepic] +* PayU India: Add Maestro as supported card [curiousepic] +* Cashnet: Don't retry [duff] +* CardStream: Make Void call Cancel instead of Refund [curiousepic] +* Remove AN and KV country codes as they're not recognized by ISO-3166-1 [apdunston] +* Worldpay: Pass unchanged amount with correct currency exponent [curiousepic] +* Improve our handling of currencies sans fractions [duff] +* Stripe: Added support for the contactless magstripe entry mode option [rbalsdon] +* VisaNet Peru: Change money format to dollars [shasum] +* BlueSnap: Add gateway [duff] +* VisaNet Peru: Select the most meaningful gateway error message [shasum] +* SecurionPay: Update country list [duff] +* Support for BIN 2 MasterCard brand detection [rbalsdon] +* CardStream: Fix signature calculation [duff] +* CyberSource: Update test and live URL [marquisong] +* AuthorizeNet: Truncate nameOnAccount field [duff] +* Tns: Fix ipAddress field [duff] +* WorldNet: New gateway support [varyonic] +* BraintreeBlue: Allow channel override [duff] +* MerchantWarrior: Use Truncated Order Id [ThereExistsX] + + +== Version 1.58.0 (March 1, 2016) +* Move Electron check out of CreditCard into CreditCardMethods [ThereExistsX] +* CardStream: Add AED and NZD currencies [sdball] +* App55: Remove Gateway [ThereExistsX] +* Mercury: Stripping the start and end sentinels on card-present track data for max-length track1 requests [ryanbalsdon] +* SagePay: Update VISA Electron ranges [sdball] +* Clearhaus: Make request signing more transparent & robust [sdball] +* NCRSecurePay: Fix production URL [rwdaigle] +* Add ACH support to Stripe [sdball] +* PayPal Express: Fixing list of currencies without fractions [Krystosterone] +* Cashnet: Default custcode option and proper redirect handling [rwdaigle] +* TransFirst: Fix missing address and remove CC only fields for ACH [davidsantoso] +* More prominent links to contribution docs [rwdaigle] + + +== Version 1.57.0 (February 1, 2016) +* AuthorizeNetCim: Add unmaskExpirationDate option [RamilGilmanov] +* Element: Add gateway support [davidsantoso] +* Cardstream: 3D-secure capture fix [duff] +* Auth.net: Update store to create payment profiles [davidsantoso] +* CyberSource: Add support for mdd_fields [duff] +* Worldpay: Add support for verify [davidsantoso] +* Element: Add guard clause to handle undocumented errors [davidsantoso] +* Clearhaus: Add tests for signed requests [anellis] +* Stripe: Support adding cards to account [anellis] +* Clearhaus: Add text_on_statement option [anellis] +* Payeezy: Void and verify support [davidsantoso] +* Creditcall: Use ecommerce rather than cnp [duff] +* Payeezy: Add support for echecks [davidsantoso] +* Bridgepay: Add ability to store cards and pay with token [anellis] +* Initial support for Android Pay network tokenization cards [mrezentes] +* Transfirst: Fix exception when not all eCheck information is present [davidsantoso] +* Auth.net: Add tests for echeck refunds [davidsantoso] +* Transfirst: use default values for some eCheck data [davidsantoso] +* Element: Update the live URL endpoint [davidsantoso] +* Element: Parse responses from unexpected API errors [davidsantoso] +* Transfirst: Remove unused fields for echeck [davidsantoso] +* Sage: Internal refactoring into a single gateway class w/ common http conn [anellis] +* Cardstream: Adjust authorize and capture transactions [anellis] +* NCRSecurePay: New gateway support (Monetra white-label) [rwdaigle] +* Element: Map ReferenceNumber to order_id [duff] +* Element: Use a better MotoECICode default [duff] +* BraintreeBlue: Return transaction id for failed transactions when available [prburke] +* PayPal: Add InContextPaypalExpressGateway [xuorig] +* TransFirst: CVV is a required tag [duff] +* Checkout V2: Add Descriptor Name and City Options [anellis] +* Forte: Pass order_id [anellis] +* Merchant ESolutioins: Truncate order_id [anellis] +* Transfirst Transaction Express: New gateway support [sdball] +* Stripe: Add `stripe_account` header option [anellis] +* Cardstream: Add AVS code and message [anellis] +* Barclaycard Smartpay: New gateway support [curiousepic] +* Transfirst: Fix missing address and remove CC only fields for ACH [davidsantoso] +* Stripe: Support ACH payments [sdball] +* NCRSecurePay: Fix production URL [rwdaigle] +* Clearhaus: Make request signing more transparent & robust [sdball] +* SagePay: Properly detect Electron brand [sdball] +* Mercury: Fix for max-length track 1 [ryanbalsdon] + + +== Version 1.56.0 (December 1, 2015) +* Add Cardknox gateway [dlehren] +* Mercury: Add support for card present track 2 [ryanbalsdon] +* Cardstream: Improve default currency handling [duff] +* Mercury: Strip start and end sentinels on track 2 [ryanbalsdon] +* Redsys: Support new SHA256 authentication method [davidsantoso] +* Cashnet: Allow custcode override [duff] +* Add Rails 5 support [rafaelfranca] +* Set required Ruby version for install to 2 or greater [rafaelfranca] +* JetPay: Pass ud_fields in capture too [duff] +* Stripe: Correctly detect test mode refunds [aprofeit] +* Fix variables in remote gateways test template [sdball] +* Micropayment: Update fieldnames for new API [duff] +* Fix CreditCard#valid_number? erroring on non-digit characters [PatrickTulskie] +* Stripe: Correctly detect test mode voids [methodmissing] +* Garanti: Add test mode URL and update remote test credentials [cbilgili] +* Cashnet: Allow custcode override on refund [duff] +* Omise: Add a new optional api_version config [zdk] +* Elavon: Include IP address in purchase and authorize requests [aprofeit] +* TransFirst: Add support for ACH and more operations [davidsantoso] +* FirstData_e4: Fix void for even dollar transactions [duff] + +== Version 1.55.0 (November 9, 2015) +* CyberSource: send customer IP address when provided [fastjames] +* Braintree: Simplify Braintree scrubbing when no transcript [duff] +* AuthorizeNet: Allow market_type override [duff] +* FirstData_e4: Support level_2 data [duff] +* FirstData_e4: Fix level_2 and level_3 [duff] +* MerchantWareFour: Use Void not PreAuthorizationVoid [duff] +* JetPay: Allow partial captures [duff] +* Creditcall: Fix production url [duff] +* FirstData_e4: Fix float error in Void [duff] +* Micropayment: Upgrade to new API [mrezentes] +* Netbilling: Add order_id to user_info [mrezentes] +* Stripe: scrub swipe/track, EMV data out of gateway transcripts [girasquid] +* Remove integration_mode [mattfawcett] +* Allow setting CVV requirement at instance level [fabiokr] +* Add SecurionPay gateway [szajbus] +* AuthorizeNet: Don't send currency to void [duff] +* Add Komoju gateway [k2nr] +* Replace Connection magic numbers with constant references [larrylv] +* Add CAMS gateway [trevorgrayson] +* PayPal Express: Fix AllowedPaymentMethod [edclements] +* Litle: Store credit card from PayPage [dontmatta] +* Orbital: Deprecate profile management API [ntalbott] +* FirstData e4: Honor currency when supplied [tchill] +* Authorize.net: Add config_error standard error code [andrewpaliga] +* PayPal Express: Add support for TotalType in SetExpressCheckout [gingerhendrix] +* eWay Rapid: Add :invoice option [DylanFM] +* Braintree: Add nonce payment method [eric1234,cwoodcox] +* Payflow: Allow passing of 3D Secure details via options [marquisong] +* Elavon: Support capture via CCCOMPLETE without credit card [marquisong] +* Securenet: Allow setting test_mode independently [wedstar] +* Replace Base.integration_mode and Base.gateway_mode with just Base.mode [aprofeit] +* Micropayment: Allow specification of a project [duff] +* QuickpayV10: Truncate order_id [duff] +* FirstData_e4: Fix Level 2 data [duff] +* Remove some duplication around name handling [duff] +* FirstData_e4: Support Tax1Number [duff] +* Add Transact Pro gateway [varyonic] +* Add Payeezy gateway [huoxito] +* USAePay: Add test mode setting via options [marquisong] +* Add Clearhaus gateway [dinesh] +* WorldpayOnlinePayments: Fix logic to determine success [ao] +* Paymill: store order_id in description field [nikoloff] +* TWD isn't a zero decimal currency [duff] +* PaypalExpress: Use custom zero decimal currencies [duff] +* Stripe: Migrate from /refund to /refunds [matthelm] +* Bogus: Adding basic EMV support [ryanbalsdon] +* PayBox Direct: Refunds and working test credentials [ivanfer] +* Vanco: Handle case of no billing_address [duff] +* BluePay: Add support for CUSTOM_ID2 field [ajporterfield] +* Creditcall: Handle no verification_value [duff] + +== Version 1.54.0 (October 2, 2015) +* Beanstream: Add Network Tokenization support [girasquid] +* CenPOS: Allow order_id on void [duff] +* Provide better insight to CVV usage in requests [davidsantoso] +* Ogone: Add verify [duff] +* Beanstream: Add verify [mrezentes] +* PayPal: Map standard error codes [JakeCataford] +* Checkout.com: Fix an issue with empty phone numbers. [anotherjosmith] +* Quickpay: Edit store and add ability to purchase with stored card [anellis] +* Stripe: Set `receipt_email` to Stripe request if receipt delivery is requested [miccheng] +* Worldpay US: Add eCheck support [mrezentes] +* FirstData_e4: add level_3 data [mrezentes] +* Vanco: Support passing ip address [duff] +* Paybox Direct: Currency parsing fix [ivanfer] +* QuickpayV10: Remove currency requirement from store. [anellis] +* Raven: Use TLS 1.2 endpoint [bslobodin] + +== Version 1.53.0 (September 1, 2015) + +* Redsys: Add a number of currencies [agseco] +* Raven: update description, test url, and routing; fix tests [bslobodin] +* Raven: do not pass default (incorrect) PaymentType to #void [bslobodin] +* Add scrubbing to a number of gateways [anellis] +* BluePay: Add scrubbing [anellis] +* BraintreeBlue: Allow custom logger [duff] +* MerchantWareFour: Truncate invoiceNumber [duff] +* S5: Pass recurrence_mode in store [duff] +* QuickPay: Support 2-letter country codes in V10 API [girasquid] +* Stripe: Support validate:false field on store [anellis] +* CheckoutV2: Use correct live_url [duff] +* QuickPay: strip # from Order IDs before submission [girasquid] +* Litle: Use schema version 9.4 rather than 8.18 [anellis] +* Litle: Add decrypted apple_pay [anellis] +* QuickPay: fix method signature on #void [girasquid] +* Forte: Add gateway [davidsantoso] +* Stripe: return refund id for refund authorization [anellis] +* Paypal: Update api version [anellis] +* TNS: Translate countries to alpha3 codes [anellis] +* TNS: Handle non existent country [duff] +* TNS: Rescue Errors [anellis] +* CenPOS: Support avs_result and cvv_result [tjstankus] +* Stripe: Add application fee only on non-EMV transactions [bizla] +* Stripe: don't send blank, non-nil values [girasquid] +* Ogone: Send different auth type for mastercard [anellis] +* Cardstream: Add "type" field support [rwdaigle] +* Cardstream: 3dsecure transaction option [rwdaigle] +* Paystation: Map order_id to non-unique merchant reference field [anellis] +* Cardstream: Check for nil street address [anellis] +* Checkout.com and CheckoutV2.com: Update country list [duff] +* Cardstream: Handle nil addresses [rwdaigle] +* MiGS: Allow passing in currency [alovak] +* [POSSIBLE BREAKAGE] NMI: No longer use auth.net emulator [rwdaigle] +* SecureNet: Add DEVELOPERID if supplied [wedy] +* Braintree: Update country list [duff] +* NMI: Don't include dup_seconds if nil [rwdaigle] +* QuickPay: Make all operations to v10 platform synchronous [ta] +* QuickPay: Handle issue where no operations exists on payment [ta] +* NMI: Support merchant_defined_fields [duff] +* QuickpayV10: Add verify [anellis] +* BraintreeBlue: Use wiredump_device for logging only if present [braintreeps] +* QuickpayV10: Add scrubbing [anellis] +* QuickPayV10: Change tests to point to proper gateway [anellis] +* Monei: Add default options argument [davidgf] +* Ogone: Add additional 3d-secure parameters [ntalbott] +* Ogone: Refactor signature calculation [ntalbott] +* Add Creditcall gateway [davidsantoso] +* Redsys: Fix scrubbing for failed transactions [davidsantoso] +* Micropayment: Support Micropayment gateway [rwdaigle] +* USAePay: Use names from the given billing and shipping address [marquisong] +* Stripe: Add application fee on EMV authorize calls [bizla] + +== Version 1.52.0 (July 20, 2015) + +* Authorize.Net: Add device type to authorize.net retail requests [abecevello] +* Vanco: Change transaction type to WEB for echecks [duff] +* PayPal: Allow soft descriptor to be specified [davidsantoso] +* Authorize.net: Add disable_partial_auth field [anellis] +* SagePay: Add apply_avscv2 field [anellis] +* S5: Add Store [anellis] +* Merchant Ware v4: Add support for verify [davidsantoso] +* Mercury: No longer default to allow partial auth [duff] +* PayPal: Fix soft_descriptor and support soft_descriptor_city [duff] +* Merchant Ware: Add scrubbing [davidsantoso] +* Stripe: Make purchase via vaulted card consistent [duff] +* Moneris: Add network tokenization support [andrewpaliga] +* Ogone: Allow specifying a timeout value for requests [tomhipkin] +* PayU India: Increase allowed txnid to 30 characters [ntalbott] +* Authorize.Net: Allow passing device type through options, make wireless POS the default [abecevello] +* Authorize.Net: Update to new Akamai URL [taf2] +* Braintree: Add hold_in_escrow [anellis] +* Stripe: Allow purchases with tokens without customer specification [bizla] + +== Version 1.51.0 (July 2, 2015) + +* Garanti: Illegal character '&' parsing response [masaruhoshi] +* Stripe: Revert force USD for verify [duff] +* Litle: Surface XML validation errors in the response [jasonbosco] +* Litle: Pass the credit card verification value for tokenization (#store) requests, if one is set. [jasonbosco] +* S5: Make scrubbing regex less greedy [duff] +* CardStream: Add support for verify [anellis] +* Authorize.net: UTF-8 encode requests [duff] +* Banwire: Add default email [anellis] +* PayU India: Handle bad JSON [ntalbott] +* Dibs: Pass CVC param only if there's a value [bruno] +* Sage: Credit really is credit not refund [duff] +* Sage: Add ability to refund [duff] +* Cardstream: Add scrubbing [anellis] +* Litle: Add debt_repayment_flag [duff] +* iATS: Support ACH [rwdaigle] +* CheckoutV2: Add Gateway [anellis] +* CenPOS: Fix refund amount issue [duff] +* Add error_code mapping and error_code_from to gateway generator [jnormore] +* Stripe: Parse EMV ARC from error response [bizla] +* Redsys: Add MYR currency [agseco] +* Add "contactless" flag to credit card model [davidseal] +* Stripe: Add "contactless" flag support to gateway [davidseal] +* Add encrypted_pin data to credit card model [ryanbalsdon] +* Stripe: Add encrypted_pin support to gateway [ryanbalsdon] +* Stripe: Support mapping advanced decline codes to standard codes [abecevello] +* Epay: filter out invalid characters in returned URLs [dwradcliffe] +* Redsys: Strip leading zeroes from currency codes [agseco] +* Authorize.net: Add invoice information to refund [marquisong] +* Authorize.net: Add store ability [duff] +* Paystation: Add refund [mrezentes] +* Paystation: No longer require order_id everywhere [duff] +* Checkout: Support descriptor_name and descriptor_city [duff] +* Add supports_network_tokenization? to gateways [jnormore] +* Bpoint: Handle message for invalid login [anellis] +* TransFirst: Add scrubbing [davidsantoso] +* TransFirst: Add back a few request fields [davidsantoso] + + +== Version 1.50.0 (June 1, 2015) + +* Vanco: Add gateway [duff] +* Conekta: Move device fingerprint to root [MauricioMurga] +* Conekta: Change default language to Spanish [MauricioMurga] +* Vanco: Improve authentication handling [duff] +* Vanco: Allow specification of fund_id [duff] +* S5: Add gateway [davidsantoso] +* SecureNet: Truncate order_id [duff] +* [POSSIBLE BREAKAGE] Stripe: Be explicit about API version [duff] +* Dibs: Add gateway [mrezentes] +* Dibs: Rubyize merchant_id and secret_key [mrezentes] +* Stripe: Add support for reverse_transfer [duff] +* USA ePay: Add support for manual entry indicator [AnotherJoSmith] +* Authorize.Net: Add support for manual entry indicator [AnotherJoSmith] +* CenPOS: Change description to invoice_detail [mrezentes] +* BPoint: Add gateway [tjstankus] +* S5: Remove address requirement for purchase and authorize [davidsantoso] +* Vanco: Add support for eChecks [duff] +* Remove Adyen support [ntalbott] +* CenPOS: Use ProcessCreditCard action [duff] +* CASHnet: uri encode the merchant gateway name [mrezentes] +* S5: Include card brand in request body [davidsantoso] +* Vanco: Handle multiple error responses [duff] +* Merchant Partners gateway support [rwdaigle] +* BPoint: Update params to contain all response data [tjstankus] +* BPoint: Support biller_code in options [tjstankus] +* Sagepay: Add Verify [anellis] +* S5: Build XML with UTF-8 encoding [tjstankus] +* Cashnet: Handle unparsable response body [duff] +* CenPOS: Allow specification of customer_code [duff] +* Allied Wallet: Add gateway [anellis] +* S5: set Regex closure on scrubbing method [davidsantoso] +* Dibs: Require TLSv1 [duff] +* Optimal: Handle case of no billing address [duff] +* Omise: Add gateway [zdk] +* CenPOS: Simplify currency handling [duff] +* Beanstream: Don't treat redirect as success [aprofeit] +* Add PayU India gateway [ntalbott] +* NetBilling: Require TLSv1 [duff] +* S5: Handle recurring transactions without CVV [davidsantoso] +* Stripe: Force USD for verify [duff] +* PayU India: Prevent shadowing in response parsing [ntalbott] +* QuickPay: Add support for v10 API [ta] +* Fat Zebra: Fix refund and store signatures [duff] +* Fat Zebra: Allow transactions without a CVV [duff] + +== Version 1.49.0 (May 1, 2015) + +* Braintree: Add support for AVS error codes [ivanvfer] +* MerchantWarrior: Truncate description field [duff] +* Braintree: Add service_fee_amount option [duff] +* SecureNet: Allow shipping_address[:name] [duff] +* MonerisUS: Add verify [mrezentes] +* Ezic: Add gateway [duff] +* Stripe: Add destination field [cwise] +* SecureNet: Fix ordering of shipping field names [duff] +* SecurePayAu: Update API URL [girasquid] +* Stripe: Add EMV "chip & sign", "chip & offline PIN" and Maestro support [bizla] +* Add Errno::EHOSTUNREACH to NetworkConnectionRetries::DEFAULT_CONNECTION_ERRORS [randito78] +* Stripe: Add support for idempotency keys [michaelherold] +* WePay: Handle JSON::ParserError exceptions [duff] +* Borgun: Update country list and homepage url [mrezentes] +* AuthorizeNet: Add cvv to request only if it's valid [tjstankus] +* Stripe: Bug fix: add amounts only on non-EMV transactions, temporarily omit EMV testcases [bizla] +* Ezic: Add support for void [duff] +* iATS: Update supported countries [mrezentes] +* Ezic: Update supported countries [duff] +* AuthorizeNet: Truncate card number [tjstankus] + +== Version 1.48.0 (April 8, 2015) + +* Clean up `rake gateways:hosts` output [ntalbott] +* Add Axcess MS gateway [timtait] +* Add PayHub gateway [grepruby] +* Orbital: Improve data formatting [boone] +* [POSSIBLE BREAKAGE] USAePay Transaction: Make "void release" the default [dppcode] +* Redsys: Add rudimentary vaulting [varyonic] +* Exact: Handle 401 failures better [jefflaporte] +* SagePay: make `VPSProtocol` user-configurable [boxofrad] +* Netbilling: Add store support [cshepherd] +* Add Qvalent gateway [markabe] +* Expose proxy address and port to gateways [arkes] +* Remove Ruby 1.9 [j-mutter] +* Qvalent: Do not sent order.ipAddress when storing [markabe] +* Qvalent: Fix argument name [bruno] +* Qvalent: map card storage reference to authorization [markabe] +* Qvalent: Fix scrub replacement, it was too greedy [markabe] +* PayConex: Add gateway [duff] +* AuthorizeNet: Add credit support [duff] +* CenPOS: Add gateway [markabe] +* Stripe: Update country list [markabe] +* Add Monei.net gateway [leolara] +* MerchantWarrior: Fix refund and capture signatures [duff] +* CenPOS: Add support for capture and refund [markabe] +* Authorize.net: Add support for network tokenization credit cards [jnormore] +* Stripe: Add support for passing in metadata for auths and purchases [kitt] +* Pin: Pass amount param for captures [ab9] +* NAB Transact: Improve store functionality [duff] +* Add Worldpay Online Payments [ao] +* Fat Zebra: Add multi-currency support [nagash] +* Fat Zebra: Add auth/capture capability [nagash] +* Fat Zebra: Add soft descriptor support [nagash] + +== Version 1.47.0 (February 25, 2015) + +* Authorize.Net: Properly send name in shipping address line, when shipping address is provided [girasquid] +* Payflow: Add verify support [ntalbott] +* Capture ConnectionError#triggering_exception [ntalbott] +* Flo2Cash: Map Reference->:order_id (not :invoice) [ntalbott] +* Flo2Cash: Fix card brand handling [ntalbott] +* Flo2Cash: Improve error handling & simplify "Simple" gateway [ntalbott] +* Remove Vindicia gateway [mutemule] +* Firstdata E4: Support other mastercard string [jcbantuelle] +* Checkout: Disallow altering endpoint via options [markabe] + +== Version 1.46.0 (January 20, 2015) + +* CHANGE: drop `offsite_payments` and `active_utils` as dependencies. [wvanbergen] +* CHANGE: remove `OffsitePaymentShim`. You will have to add offsite_payments as a dependency, + and update any mentions of `ActiveSupport::Billing::Integration` to + `OffsitePayments::Integrations`. [wvanbergen] +* QuickBooks Payments: Add adapter [ivanfer, bizla] +* Quickbooks: Remove requirement of oauth gem. +* PayGate: Add support for refunds [StephanAtG2] +* PayPal: Add #scrub for scrubbing PCI information out of HTTP transcripts [girasquid] +* Stripe: Add #scrub for scrubbing PCI information out of HTTP transcripts [girasquid] +* Cybersource: Add ability to verify a card [duff] +* BraintreeBlue: Expose the error code in the response params [duff] +* eWay Rapid: Update supported countries and card types [incarnate] +* PayPal: Allow specifying ButtonSource at init [ntalbott] +* Payflow: Add fraud_review support [ntalbott] +* Add IPP gateway [InfraRuby] +* Redsys: Fix order_id truncation [duff] +* AuthorizeNet: Improve duplicate_window handling [duff] +* PayPal: Fix ButtonSource bug [ntalbott] +* Checkout: Prevent multiple trackids from being passed [markabe] +* Pin: Handle JSON parsing exception in response [duff] +* Improve test suite to test against multiple ActiveSupport versions [wvanbergen] +* Misc. code cleanup [wvanbergen] +* Add Flo2Cash gateway [markabe] +* Litle: Allow order_source override [duff] +* Quickpay: Add ability to finalize a capture [askehansen] +* AuthorizeNet: Prevent test mode using live gateway [duff] +* Add Flo2Cash Simple gateway [markabe] + +== Version 1.45.0 (December 1, 2014) + +* HPS: Always pass CardHolderData element [SecureSubmit, ntalbott] +* PayJunction: Include 'track' parameter if provided [hron] +* WebPay: Fix API calls [tomykaira] +* Moneris US: Add store, unstore, and update [AntoineInsa] +* Moneris US: Add CVV and AVS [AntoineInsa] +* Stripe: Add support for statement_description [markabe] +* CASHNet: Add support for fname and lname [markabe] +* Orbital: Fix customer profile auth/purchase [denis] +* Payex: Fix expiry month to allow 4 digit year [duff] +* Cashnet: Allow overriding custcode [hoenth] +* Cashnet: Fix overridding item_code per transaction [ntalbott] +* Add Checkout.com gateway [ravish-ramrakha-cko] +* HPS: Add verify support [SecureSubmit] +* Add Bank Frick gateway [varyonic] +* Add Global Transport gateway [duff] +* iATS: Add #store and #unstore [duff] +* Authorize.Net: Fix amount formatting [ntalbott] +* Authorize.Net: Truncate order_id to 20 characters [ntalbott] +* Authorize.Net: Truncate more fields [duff] +* Authorize.Net: Truncate invoiceNumber [ntalbott] +* Adyen: Add support for verify operation [duff] +* USAePay: Add track_data support [louiskearns] +* Payex: Use the right url for the purchase call [duff] +* Braintree: Allow dynamic descriptors [duff] +* Openpay: Add support for device session id [guillermo-delucio, ismaelem] +* Redsys: Add support for verify [duff] +* Redsys: Handle unknown currencies [duff] +* Stripe: Make #unstore signature consistent [duff] +* Remove defunct Samurai gateway [ntalbott] +* eWay Rapid: Tweak authorization support [duff] +* Litle: Add support for dynamic descriptors [duff] +* Add TNS gateway [markabe] +* TNS: Update countries and supported card types [markabe] +* Conekta: Add AMEX as a supported card type [MauricioMurga] +* Checkout: pass through currency type [markabe] +* Bogus: return standard error codes [jpcaissy] +* Add PaymentToken and ApplePayPaymentToken objects for token-based transactions [bizla] +* Authorize.Net: Add ApplePay in-app transaction support [bizla] +* Stripe: Add ApplePay in-app transaction support [bizla] +* eWAY Rapid: Add PartnerID param [j-mutter] +* GlobalTransport: Truncate order_id [duff] +* Redsys: Allow a description to be specified [duff] +* NetworkMerchants: Fix currency [j-mutter] +* Redsys: Improve handling of order_id [duff] +* Checkout: Add support for void, refund, and verify [markabe] + +== Version 1.44.1 (Aug 28, 2014) + +* Allow SSLv3 for PsiGate [mutemule] +* Set default :state to n/a for NetworkMerchants [cjoudrey] + +== Version 1.44.0 (Aug 21, 2014) + +* Moneris: Add :avs_enabled option [bslobodin] +* Stripe: Populate authorization in failed responses, when available [bslobodin] +* Moneris: Use the name on the card [duff] +* Balanced: More 1.1 API fixes and mappings [ntalbott] +* Balanced: Handle "pending" refunds [duff] +* Immediately convert credit card date fields to integers [ntalbott] +* Balanced: Handle outside card tokens [ntalbott] +* Balanced: Do not pass address if zip is missing [ntalbott] +* Float active_utils at the patch version instead of the minor version [nwjsmith] +* Wirecard: Fix CVV & AVS response handling [alevett] +* Consolidate deprecation handling [ntalbott] +* Authorize.Net CIM: Do not send x_test_request [danrabinowitz] +* Authorize.Net CIM: Pass delimiter through [jsoma] +* HPS: Add support for track data [SecureSubmit] +* HPS: Fix processing without an address [SecureSubmit] +* Balanced: Do not pass address if zip is blank [duff] +* Do CreditCard attribute cleanup at assignment [ntalbott] +* eWay Rapid: Add auth/capture/void support [ntalbott] +* [POSSIBLE BREAKAGE] Remove dependency on active_utils Validateable [ntalbott] +* Make all active_utils requires explicit [ntalbott] +* Balanced: Handle legacy card tokens [ntalbott] +* Braintree Blue: Default verification merchant id [speric] +* [POSSIBLE BREAKAGE] Extract integrations into an offsite_payments gem [ntalbott] +* [POSSIBLE BREAKAGE] Drop stated/tested compatibility with Rails < 3.2 [ntalbott] +* Stop requiring unused CurrencyCode class from active_utils [ntalbott] +* Move ActiveMerchant::Error into ActiveMerchant [ntalbott] +* Inline RequiresParameters in Gateway & move Country from active_utils [ntalbott] +* Quickpay v7: Fix passing acquirers field [moklett] +* Quickpay v7: Pass an amount when storing cards [ta & moklett] +* Quickpay: Expand list of supported countries [ta] +* Validate CreditCard verification value [mnoack] +* Authorize.Net CIM: Allow updating a payment profile without a full credit card number [speric] +* Wirecard: Add optional CommerceType element [timtait] +* Add Borgun gateway [markabe] +* [POSSIBLE BREAKAGE] NAB Transact: Allow timeout customization [duff] +* Wirecard: Add card store and purchase/authorize by reference [speric] +* Worldpay: Add support for Switch cards [dougal] +* FirstData e4: Send correct card type [npverni] +* Elavon: Add store/update support [npverni] +* Wirecard: Catch an empty ERROR Element [timtait] +* Finansbank (CC5): Add void/refund/credit support [muhammetdilek] +* Wirecard: Use authorization_check for Amex store [npverni] +* Elavon: Make void work for authorizations [duff] +* Add Commercegate gateway [vitaliyvasin] +* Wirecard: Fix "amex" references [mendable] +* HPS: Do not pass empty elements [SecureSubmit] +* Paymill: Add more supported card types [nikoloff] +* Paymill: Add source [nikoloff] +* Paymill: Add description for preauthorizations [nikoloff] +* Paymill: Add remote tests using tokens [nikoloff] +* HPS: Add developer, version number and site trace options [SecureSubmit] +* Update 1stPayGateway.Net gateway [rwdaigle] +* Payflow: Add verbosity option [doppler] +* Allow ignoring the result of any MultiResponse step [ntalbott] +* Stripe: Add ability to verify a card [duff] +* Paypal: Add ability to verify a card [duff] +* Authorize.net: Add ability to verify a card [duff] +* Braintree: Add ability to verify a card [duff] +* Enhance gateway generator to support verify [duff] +* PayPal Express: Add funding source support [baraabourghli] +* Optimal: Add IP address to requests [justinplouffe] +* Pin: Add authorize & capture support [keithpitt] +* Pin: Add update support [keithpitt] +* Vindicia: Stop using the vindicia-api gem [ntalbott] +* Clean up the warnings fog [ntalbott] +* Quickpay: Map options[:ip] for fraud analysis [ta] +* SagePay: Truncate fields [duff] +* First Data E4: Add ability to verify a card [duff] +* Elavon: Add ability to verify a card [duff] +* Worldpay: Pass email and IP address [duff] +* Worldpay: Use updated address format [duff] +* Moneris: Fix address splitting [ntalbott] +* FirstData E4: Allow passing CAVV through [Senjai] +* Braintree Blue: Remember the capture transaction id [duff] +* Eway Rapid: Truncate some fields [duff] +* Optimal Payment: Make account mandatory field [rwdaigle] +* Worldpay: Improve address defaulting [duff] +* Authorize.Net: Add maestro as a supported card type [vparihar01] +* Worldpay: Improve address defaults [duff] +* Braintree Blue: Pass cardholder_name when tokenizing [radar] +* Wirecard: Improve error handling [mendable] +* Litle: Add verify support [markabe] +* USAePay: Add verify support [markabe] +* BridgePay: Add verify support [duff] +* Braintree Blue: Add payment_method_token flag [JDutil] +* SagePay: Add optional FI fields [rob-anderson] +* Iridium: Add AVS and CVV results [X0Refraction] +* NAB Transact: Add credit support [nagash] +* Braintree Blue: Add application_id support [npverni] +* Realex: Add maestro mapping [uriklar] +* Add Worldpay US gateway [markabe] +* Cybersource: Add shipping address [pkoppula] + +== Version 1.43.2 (May 12, 2014) + +* Remove 2Checkout's #line_item due to conflict with Klarna/Shopify [edward] +* Auth.Net CIM: Fix ordering of order/trans_id [pdamer] +* Add PagoFacil gateway [bhserna, abisosa] +* [POSSIBLE BREAKAGE] Stripe: Allow for updating of stored card [speric] +* Authorize.Net: Deprecate ARB [ntalbott] +* Authorize.Net CIM: Add recurring billing flag [gabealmer] +* Spreedly: Add support for :ip [megamoose] +* PayPal Express: Improve received_at handling [jwarchol] +* PayPal Express: Add ReqBillingAddress flag [johnb-razoo] +* Beanstream: Add support for Legato single use tokens [tylerrooney] +* PayPal Digital Goods: Allow mobile [tomprats] +* Maxipago: Add installment support [alexandremcosta] +* Deprecate recurring API support [ntalbott] +* NMI: fix CreditCard check [bslobodin] +* SagePay: Add tokenization support [kernow] +* PayPal Express: Reference transaction details [lrostovsky] +* Balanced: Update to API 1.1 [steveklabnik] +* Add Cashnet gateway [hoenth] +* Conekta: Standardize address options [MauricioMurga] +* Wirecard: Add support for reference purchases [timtait] +* Worldpay: Add support for external references [matsubo] +* Balanced: Voiding a capture is not allowed [duff] +* Add HPS gateway (Heartland Payment Systems) [SecureSubmit] +* Balanced: Fix handling of 500 errors [ntalbott] +* Universal: Remove all billing address fields [bslobodin] +* Balanced: Stop creating a customer on each call [ntalbott] +* Balanced: Refactor and handle legacy authorizations [ntalbott] +* SagePay Form: Update to v3.00 [bslobodin] +* GestPay: Use a more specific error class when parsing [odorcicd] +* PagSeguro: Improve error handling [celsodantas] +* PxPay: Support for newer, query-less redirect URLs [odorcicd, bslobodin] +* eWAY Rapid: Update to 3.1 API [atomgiant] +* Misc refactorings [justinplouffe] +* Remove greedy rescue, and convert some errors to be user-facing [odorcicd] +* PayPal Express: pass logo image for setup request [dimko] +* Improve credit card validation [duff] + +== Version 1.43.1 (May 1, 2014) + +* Merchant Warrior: Scrub names [duff] +* Validate Gateway.supported_countries [rwdaigle] +* Stripe: Add recurring flag support [bslobodin] +* Stripe: Use localized amounts for currencies w/o minor units [bslobodin] +* WebPay: Leverage fixes to Stripe to remove duplicate code [bslobodin] +* Klarna: Miscellanenous fixes [edward] +* Mollie iDEAL: Use order's description [wvanbergen] + +== Version 1.43.0 (April 24, 2014) + +* PagSeguro: New offsite integration [celsodantas] +* Sage Pay: Fix amount parsing in notifications [berkcaputcu] +* Sage Pay: Use API v3.00 [bslobodin] +* BridgePay: Switch method of success detection [markabe] +* BridgePay: Use Return as TransType for refunds [markabe] +* IATS: Complete rewrite using first class API [rwdaigle] +* IATS: Fix invalid country code UK -> GB [rwdaigle] +* DataCash: Fix refund processing using original authorization [bslobodin] +* Transnational gateway renamed to Network Merchants [bslobodin] +* PayMill: Handle non-JSON server responses [bslobodin] + +== Version 1.42.9 (April 15, 2014) + +* Spreedly: Add ip, description and gateway_specific_fields [faizalzakaria] +* Sage (US): Support store/unstore of cards [rwdaigle] +* Pin: Add american express to supported cards [nagash] +* Raven: Update handling of CVV/AVS [bslobodin] +* Raven: Use UUID for RequestID [bslobodin] + +== Version 1.42.8 (April 4, 2014) + +* Cecabank: Handle invalid xml response body [duff] +* Wirecard: Capture error code in the response [duff] +* Litle: Remove gem dependency [duff] +* Litle: Fix case of missing address parts [duff] +* Universal: Add universal offsite API implementation [bslobodin] +* Iridium: Add more currencies [bslobodin] +* iDeal: Add Mollie iDeal offsite implementation [wvanbergen, maartenvg] + +== Version 1.42.7 (March 18, 2014) + +* SagePay: Add support for ReferrerID [markabe] +* Cecabank: Fix expiration date formatting [duff] +* Add WePay gateway [faizalzakaria] +* SmartPs: Add ECI option to SmartPs [odorcicd] +* Rescue and re-raise ActionViewHelperError when offsite helpers raise an error [odorcicd] +* Add FirstGiving gateway [faizalzakaria] +* FirstGiving: Fix refunds [ntalbott] +* Samurai: Handle server errors [ntalbott] +* WePay: Fix refund [duff] + +== Version 1.42.6 (February 24, 2014) + +* Litle: Truncate order_id [duff] +* Conekta: Fix #refund; respect :currency [leofischer] +* SagePay: Truncate description field [duff] +* Add Cecabank gateway [molpe] +* Add Openpay [darkaz] +* Openpay: Simplify test versus production mode [duff] +* Wirecard: Handle a utf-8 description [duff] +* Litle: Partial capture support [ttdonovan] +* Ogone: Allow D3D for alias purchases [pwoestelandt] +* USAePay Advanced: Fix verification_value mapping [dppcode] +* Orbital: Add additional success conditions [boone] +* Orbital: Handle special CVV responses [boone] +* Balanced: Allow working with balanced.js [michaelherold] +* Balanced: Allow passing customer name [michaelherold] +* Balanced: Add support for meta [michaelherold] +* Improve gateway generator [ntalbott] +* Add maxiPago gateway [alexandremcosta] +* Authorize.Net: Remove x_test_request support [ntalbott] +* Conekta: Add default description [bslobodin] +* Add PayDollar integration [bslobodin] + +== Version 1.42.5 (February 7th, 2014) + +* Add Doku Indonesia [bizla] +* Cardstream: Update gateway to use latest API [odorcicd] + +== Version 1.42.4 (January 8th, 2014) + +* DataCash: Set 'ecomm' as capturemethod [DavidGeukers] +* Cybersource: Fix subscriptions with a setup fee [ntalbott] +* Stripe: Do not pass customer details to the /cards endpoint [michellebu] +* Stripe: Allow Stripe API version to be initialized with the gateway [odorcicd] + +== Version 1.42.3 (December 18th, 2013) + +* Balanced: Add support for appears_on_statement_as [duff] +* Authorize.Net: Make already actioned responses failures [odorcicd] +* Add Payex gateway [atomgiant] +* Paymill: Fix authorizations [duff] +* Braintree Blue: Allow specifying the credit card token [ntalbott] +* Braintree Blue: Allow specifying the customer id [ntalbott] +* Braintree Blue: Scrub invalid emails and zips [ntalbott] +* Braintree Blue: Return :credit_card_token as a top level param [ntalbott] +* Braintree Blue: Allow unstoring just a credit card [ntalbott] +* Braintree Blue: #store adds cards to existing customers [ntalbott] +* USA ePay Advanced: Fix check handling [nearapogee] +* USA ePay Advanced: Fix credit card expiration handling [nearapogee] +* USA ePay Advanced: Fix handling of custom transaction responses for single items [nearapogee] +* USA ePay Advanced: Fix capture amount [nearapogee] +* NAB Transact: Fix merchant descriptor with capture/refund requests [nagash] +* Braintree Blue: Add custom_fields & device_data parameters [parallel588] +* Webpay: Add authorize & capture [keikubo] +* MerchantWarrior: Pass description [duff] +* Stripe: Separate email from description [duff] +* Add Payscout gateway [llopez] +* Merchant Warrior: Use billing_address [duff] +* Add SoEasyPay gateway [ir-soeasycorp] +* Bogus: Add check support [npverni] +* Payflow: Add Check support [crazyivan] +* Stripe: Allow expanding objects inline [odorcicd] + +== Version 1.42.2 (November 13th, 2013) + +* Renew public certificate + +== Version 1.42.1 (November 13th, 2013) + +* Signed version of 1.42.0 + +== Version 1.42.0 (November 13th, 2013) + +* Fix NoMethodError "tr" for params with dash [TimothyKlim] +* Authorize.Net: Add cardholder authentication options (CAVV) support [structure] +* CardStreamModern: Added better checks on inputs from the gateway [ExxKA] +* Stripe: Send :ip to the gateway instead of :browser_ip [f3ndot] +* Wirecard Page: new offsite gateway [mbretter] +* Mercury: Add support for requesting a token [kcdragon] +* Add App55 gateway [ianbutler55] +* UsaEpayTransaction: Support for split payments [GBH] +* Add Swipe Checkout gateway [matt-optimizerhq] +* Spreedly Core: Allow overriding the gateway token when running a transaction [hoenth] +* Spreedly Core: Add order_id [hoenth] +* Spreedly Core: Allow store without retain [hoenth] +* Stripe: Support multiple cards on account [pierre] +* Stripe: Add card_id parameter to unstore call [pierre] +* Remove usage of `uname -a` [ntalbott] +* Litle: Allow easier access to the response code [duff] +* Stripe: Add the option to pass a version header [odorcicd] +* Elavon: Update supported countries [duff] +* Add Raven PacNet gateway [llopez] +* BitPay: Fix BitPay issues and implement Notification#acknowledge [odorcicd] + +== Version 1.41.0 (October 24th, 2013) + +* Stripe: Payments won't fail when specifying a customer with a creditcard number [melari] +* Add Conekta gateway [leofischer] +* Wirecard: Add support for void and refund [duff] +* Orbital: Mandatory field fix [juicedM3, jduff] + +== Version 1.40.0 (October 18th, 2013) + +* Paymill: Revert Add support for specifying the :customer [melari] +* Quickpay: Make v7 of the API default [kvs] +* Bitpay: Add return [tahnok] + +== Version 1.39.2 (October 10th, 2013) + +* Eway Rapid: Fix a bug with access codes that have equal signs in them [odorcic] + +== Version 1.39.1 (October 9th, 2013) + +* Bitpay: Invoice Fix [orenmazor] + +== Version 1.39.0 (October 9th, 2013) + * Moneris: Add optional (off by default) verification_value support [duff] +* Citrus: New Integration [viatechs, melari] +* Payu Paisa: New Integration [melari] +* Spreedly: Pass country with other address fields [hoenth] +* SecureNet: Fix order of xml params [duff] +* Paymill: Add support for void [duff] +* Add MoneyMovers gateway [jeffutter] +* Ogone: Add a :store_amount option [rymai] +* Ogone: Require TLSv1 [ntalbott] +* Moneris: Add support for purchasecorrection [pgib] +* Spreedly: Add ability to retain on success [duff] +* Spreedly: Pass verification value [duff] +* Paymill: Add support for specifying the :customer [Sbastien] +* Realex: Correct AVS input format [ExxKA] +* USAEpay Transaction: Use sandbox when in test mode [radar] +* Braintree Blue: Do not use global config [rdj] +* eWay Rapid: Add response messages [BenZhang] +* Paysbuy: Add 'Pending' notification status [divineforest] +* Cybersource: Use standard :phone field [cade] +* Orbital: Fix/tweak AVS codes [boone] +* Quickpay: Add v7 support [larspind] +* Authorize.Net CIM: Add option to not mark transactions as test [alanandrade] == Version 1.38.1 (September 16, 2013) @@ -316,6 +2042,8 @@ * Braintree Blue gateway: Always pass CVV on update [shayfrendt] * eWAY gateway: Update docs. Require address [juggler] * Cybersource gateway: Add support for subscriptions [fabiokr] +* WePay: Use better endpoint for recurring with no CVV [davidsantoso] + == Version 1.24.0 (June 8, 2012) @@ -587,7 +2315,7 @@ value [jduff] * Support default Return class for all Integrations that don't use returns [Soleone] * Add support for passing additional options when creating a Notification to all Integrations [Soleone] * Update BraintreeBlue#refund to have consistent method signature [Jonathan Rudenberg] -* Add rails/init.rb for gem campatability in Rails [Rūdolfs Ošiņš] +* Add rails/init.rb for gem campatability in Rails [R?dolfs O?i??] * Fix Paypal Express response parser [Jonathan Rudenberg] * Braintree/Transax: Add tax field [wisq] @@ -903,7 +2631,7 @@ value [jduff] * Update Secure Pay Au to meet specs for MessageInfo elements [cody] * Add support for the Australian Secure Pay payment gateway [cody] * Allow LinkPoint cancellations for recurring billing. [yanagimoto.shin] -* Add support for Åland Islands to the country list [cody] +* Add support for ?land Islands to the country list [cody] == Version 1.3.1 (January 28, 2008) @@ -1218,4 +2946,3 @@ value [jduff] * Credit card validation methods as static methods of the credit card object == PlanetArgon fork for integrating Merchant eSolutions gateway - diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000000..c9ce8e449ea --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,42 @@ +# Contributing guidelines + +We gladly accept bugfixes, but are not actively looking to add new gateways. Please follow the guidelines here to ensure your work is accepted. + +## New Gateways + +We're not taking on many new gateways at the moment. The team maintaining ActiveMerchant is small and with the limited resources available, we generally prefer not to support a gateway than to support a gateway poorly. + +Please see the [ActiveMerchant Guide to Contributing a new Gateway](https://github.com/activemerchant/active_merchant/wiki/contributing) for information on creating a new gateway. You can place your gateway code in your application's `lib/active_merchant/billing` folder to use it. + +We would like to work with the community to figure out how gateways can release and maintain their integrations outside of the the ActiveMerchant repository. Please join [the discussion](https://github.com/activemerchant/active_merchant/issues/2923) if you're interested or have ideas. + +Gateway placement within Shopify is available by invitation only at this time. + +## Issues & Bugfixes + +### Reporting issues + +When filing a new Issue: + +- Please make clear in the subject what gateway the issue is about. +- Include the version of ActiveMerchant, Ruby, ActiveSupport, and Nokogiri you are using. + +### Pull request guidelines + +When submitting a pull request to resolve an issue: + +1. [Fork it](http://github.com/activemerchant/active_merchant/fork) and clone your new repo +2. Create a branch (`git checkout -b my_awesome_feature`) +3. Commit your changes (`git add my/awesome/file.rb; git commit -m "Added my awesome feature"`) +4. Push your changes to your fork (`git push origin my_awesome_feature`) +5. Open a [Pull Request](https://github.com/activemerchant/active_merchant/pulls) + +## Version/Release Management + +Contributors don't need to worry about versions, this is something Committers do at important milestones: + +1. Check the [semantic versioning page](http://semver.org) for info on how to version the new release. +2. Update the `ActiveMerchant::VERSION` constant in **lib/active_merchant/version.rb**. +3. Add a `CHANGELOG` entry for the new release with the date +4. Tag the release commit on GitHub: `bundle exec rake tag_release` +5. Release the gem to rubygems using ShipIt diff --git a/CONTRIBUTORS b/CONTRIBUTORS index dc9347beee2..a5fce1694e1 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -122,6 +122,7 @@ MerchantWARE (July 7, 2009) FirstPay (July 24, 2009) * Phil R +* Ryan Daigle (rwdaigle) Ogone (July 20, 2009) @@ -412,3 +413,156 @@ MerchantWare V4 (July 2013) Platron integration (July 2013) * Alexey Kiryushin (alexwl) + +MoneyMovers (September 2013) + +* Jeffery Utter (jeffutter) + +Be2Bill (September 2013) + +* Michaël Hoste (MichaelHoste) + +Conekta (October 2013) + +* Leo Fischer (leofischer) + +App55 (November 2013) + +* (ianbutler55) + +Swipe Checkout (November 2013) + +* (matt-optimizerhq) + +Raven PacNet (November 2013) + +* Luis Lopez (llopez) + +Payex (November 2013) + +* Tom Davies (atomgiant) + +Payscout (December 2013) + +* Luis Lopez (llopez) + +SoEasyPay (December 2013) + +* Ivan Radovanovic (ir-soeasycorp) + +Cecabank (February 2014) + +* Alberto Molpeceres (molpe) + +Openpay (February 2014) + +* (darkaz) + +maxiPago (February 2014) + +* (alexandremcosta) + +WePay (March 2014) + +* Faizal Zakaria (faizalzakaria) + +FirstGiving (March 2014) + +* Faizal Zakaria (faizalzakaria) + +BridgePay (April 2014) + +* Mark Bennett (markabe) + +PagoFacil (May 2014) + +* Benito Serna (bhserna) + +Cashnet (May 2014) + +* Tom Hoen (hoenth) + +Heartland Payment Systems (May 2014) + +* Mark Hagan (SecureSubmit) + +Borgun (June 2014) + +* Mark Bennett (markabe) + +Commercegate (June 2014) + +* Vitaliy (vitaliyvasin) + +Worldpay US (August 2014) + +* Mark Bennett (markabe) + +Checkout.com (September 2014) + +* (ravish-ramrakha-cko) + +Bank Frick (September 2014) + +* Piers Chambers (varyonic) + +Global Transport (September 2014) + +* Duff O'Melia (duff) + +IPP (December 2014) + +* (InfraRuby) + +QuickBooks Payments (January 2015) + +* Arbab Ahmed (bizla) +* Ivan Sanchez (ivanfer) + +Axcess MS (February 2015) + +* Tim Tait (timtait) + +PayHub (February 2015) + +* Grep Ruby (grepruby) + +Qvalent (March 2015) + +* Mark Bennett (markabe) + +Worldpay Online Payments (April 2015) + +* (ao) + +PayU India (May 2015) + +* Nathaniel Talbott (ntalbott) + +SecurionPay (October 2015) + +* Michał Szajbe (szajbus) + +Komoju (October 2015) + +* Kazunori Kajihiro (k2nr) + +CAMS: Central Account Management System (October 2015) + +* Trevor Grayson (trevorgrayson) + +Transact Pro (October 2015) + +* Piers C (varyonic) + +Payeezy (October 2015) + +* Washington L Braga Jr (huoxito) + +Clearhaus (October 2015) + +* Dinesh Yadav (dinesh) + +Cardknox (November 2015) + +* (dlehren) diff --git a/Gemfile b/Gemfile index 9cad2aca786..fc9791f2719 100644 --- a/Gemfile +++ b/Gemfile @@ -1,8 +1,11 @@ source 'https://rubygems.org' gemspec -gem 'builder', '~> 3.0' -gem 'activesupport', '~> 3.2' -gem 'rails', '~> 3.2' +gem 'jruby-openssl', :platforms => :jruby +gem 'rubocop', '~> 0.60.0', require: false -eval File.read(File.expand_path("../Gemfile_common", __FILE__)) +group :test, :remote_test do + # gateway-specific dependencies, keeping these gems out of the gemspec + gem 'braintree', '>= 2.98.0' + gem 'mechanize' +end diff --git a/Gemfile_common b/Gemfile_common deleted file mode 100644 index a6cee9cd1b8..00000000000 --- a/Gemfile_common +++ /dev/null @@ -1,20 +0,0 @@ -group :test do - gem 'json-jruby', :platforms => :jruby - gem 'jruby-openssl', :platforms => :jruby -end - -group :remote_test do - gem 'mechanize' - gem 'launchy' - gem 'mongrel', '1.2.0.pre2', :platforms => :ruby -end - -group :test, :remote_test do - # gateway-specific dependencies, keeping these gems out of the gemspec - gem 'samurai', '>= 0.2.25' - gem 'braintree', '>= 2.0.0' - gem 'vindicia-api', :git => 'git://github.com/agoragames/vindicia-api.git', :ref => "4e78744c79cb97448ff46c21301f53b346db4c91" - gem 'LitleOnline', '> 8.15.0' -end - -gem 'money', '5.0.0', :platforms => [:ruby_18] diff --git a/Gemfile_rails23 b/Gemfile_rails23 deleted file mode 100644 index bf06fd5734f..00000000000 --- a/Gemfile_rails23 +++ /dev/null @@ -1,14 +0,0 @@ -source 'https://rubygems.org' -gemspec - -gem 'builder', '~> 3.0' -gem 'activesupport', '~> 2.3' -gem 'rails', '~> 2.3' -platforms :mri_20 do - gem 'iconv' -end -if RUBY_VERSION < '1.9' - gem 'mocha', '~> 0.11.0', :require => false -end - -eval File.read(File.expand_path("../Gemfile_common", __FILE__)) diff --git a/Gemfile_rails30 b/Gemfile_rails30 deleted file mode 100644 index 96fdbe52b9c..00000000000 --- a/Gemfile_rails30 +++ /dev/null @@ -1,9 +0,0 @@ -source 'https://rubygems.org' -gemspec - -gem 'i18n', '~> 0.5.0' -gem 'builder', '~> 2.1.2' -gem 'activesupport', '~> 3.0.17' -gem 'rails', '~> 3.0.17' - -eval File.read(File.expand_path("../Gemfile_common", __FILE__)) diff --git a/Gemfile_rails31 b/Gemfile_rails31 deleted file mode 100644 index b3741159e62..00000000000 --- a/Gemfile_rails31 +++ /dev/null @@ -1,8 +0,0 @@ -source 'https://rubygems.org' -gemspec - -gem 'builder', '~> 3.0' -gem 'activesupport', '~> 3.1.8' -gem 'rails', '~> 3.1.8' - -eval File.read(File.expand_path("../Gemfile_common", __FILE__)) diff --git a/Gemfile_rails40 b/Gemfile_rails40 deleted file mode 100644 index 88778d2e269..00000000000 --- a/Gemfile_rails40 +++ /dev/null @@ -1,9 +0,0 @@ -source 'https://rubygems.org' -gemspec - -gem 'builder', '~> 3.0' -gem 'activesupport', '~> 4.0' -gem 'activeresource', '~> 4.0' -gem 'rails', '~> 4.0' - -eval File.read(File.expand_path("../Gemfile_common", __FILE__)) diff --git a/GettingStarted.md b/GettingStarted.md index 5ac90a839fc..d78458d7d40 100644 --- a/GettingStarted.md +++ b/GettingStarted.md @@ -3,7 +3,7 @@ Before getting started using Active Merchant, a bit of terminology is needed. In order to process credit card payments, your application needs to interface with a payment -gateway. In Active Merchant, these are represented as subclasses of {ActiveMerchant::Billing::Gateway}. +gateway. In Active Merchant, these are represented as subclasses of `ActiveMerchant::Billing::Gateway`. ## Gateway Operations @@ -18,46 +18,51 @@ When combined into a single operation, this is called a *purchase*. All of these operations are performed on an instance of a Gateway subclass: - gateway = SomeGateway.new +```ruby +gateway = SomeGateway.new - # Amounts are always specified in cents, so this is - # equal to $10. - response = gateway.purchase(1000, credit_card) +# Amounts are always specified in cents, so $10.00 is 1000 cents +response = gateway.purchase(1000, credit_card) +``` -Both `#authorize`, `#capture` and `#purchase` return a {ActiveMerchant::Billing::Response} instance. +All three `#authorize`, `#capture` and `#purchase` methods return a `ActiveMerchant::Billing::Response` instance. This object contains the details of the operation, most notably whether it was successful. - if response.success? - puts "Payment complete!" - else - puts "Payment failed: #{response.message}" - end - -As can be seen, {ActiveMerchant::Billing::Response#success?} returns whether the operation completed -successfully. If `false`, the error message is available in `#message`. +```ruby +if response.success? + puts "Payment complete!" +else + puts "Payment failed: #{response.message}" +end +``` ## Handling Credit Cards -In Active Merchant, credit cards are represented by instances of {ActiveMerchant::Billing::CreditCard}. +In Active Merchant, credit cards are represented by instances of `ActiveMerchant::Billing::CreditCard`. Instantiating such an object is simple: - credit_card = ActiveMerchant::Billing::CreditCard.new( - :first_name => 'Steve', - :last_name => 'Smith', - :month => '9', - :year => '2014', - :type => 'visa', - :number => '4242424242424242') +```ruby +credit_card = ActiveMerchant::Billing::CreditCard.new( + :first_name => 'Steve', + :last_name => 'Smith', + :month => '9', + :year => '2022', + :brand => 'visa', + :number => '4242424242424242', + :verification_value => '424') +``` Most often, though, you'll be using user-supplied data. In a typical Rails controller: - credit_card = ActiveMerchant::Billing::CreditCard.new(params[:credit_card]) +```ruby +credit_card = ActiveMerchant::Billing::CreditCard.new(params[:credit_card]) +``` ### Validation While the above attributes are always required for a `CreditCard` to be valid, some gateways also require a *verification value*, e.g. a CVV code, to be given. -Validating a credit card is as simple as calling `#valid?`, which +Validating a credit card is as simple as calling `CreditCard#valid?`, which returns `true` only if the credentials are syntactically valid. If there are any errors or omissions, -the `#errors` attribute will be non-empty. +the `CreditCard#errors` attribute will be non-empty. diff --git a/README.md b/README.md index f5755e5d416..0c478dcb687 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ # Active Merchant -[![Build Status](https://secure.travis-ci.org/Shopify/active_merchant.png)](http://travis-ci.org/Shopify/active_merchant) -[![Code Climate](https://codeclimate.com/badge.png)](https://codeclimate.com/github/Shopify/active_merchant) +[![Build Status](https://travis-ci.org/activemerchant/active_merchant.png?branch=master)](https://travis-ci.org/activemerchant/active_merchant) +[![Code Climate](https://codeclimate.com/github/activemerchant/active_merchant.png)](https://codeclimate.com/github/activemerchant/active_merchant) -Active Merchant is an extraction from the e-commerce system [Shopify](http://www.shopify.com). +Active Merchant is an extraction from the ecommerce system [Shopify](http://www.shopify.com). Shopify's requirements for a simple and unified API to access dozens of different payment gateways with very different internal APIs was the chief principle in designing the library. @@ -17,129 +17,172 @@ from an ever-growing set of contributors. See [GettingStarted.md](GettingStarted.md) if you want to learn more about using Active Merchant in your applications. +If you'd like to contribute to Active Merchant, please start with our [contribution guide](CONTRIBUTING.md). + ## Installation ### From Git You can check out the latest source from git: - git clone git://github.com/Shopify/active_merchant.git + git clone git://github.com/activemerchant/active_merchant.git ### From RubyGems Installation from RubyGems: - gem install activemerchant +```console +gem install activemerchant +``` Or, if you're using Bundler, just add the following to your Gemfile: - gem 'activemerchant' +```ruby +gem 'activemerchant' +``` ## Usage This simple example demonstrates how a purchase can be made using a person's credit card details. - require 'rubygems' - require 'active_merchant' - - # Use the TrustCommerce test servers - ActiveMerchant::Billing::Base.mode = :test - - gateway = ActiveMerchant::Billing::TrustCommerceGateway.new( - :login => 'TestMerchant', - :password => 'password') - - # ActiveMerchant accepts all amounts as Integer values in cents - amount = 1000 # $10.00 - - # The card verification value is also known as CVV2, CVC2, or CID - credit_card = ActiveMerchant::Billing::CreditCard.new( - :first_name => 'Bob', - :last_name => 'Bobsen', - :number => '4242424242424242', - :month => '8', - :year => Time.now.year+1, - :verification_value => '000') - - # Validating the card automatically detects the card type - if credit_card.valid? - # Capture $10 from the credit card - response = gateway.purchase(amount, credit_card) - - if response.success? - puts "Successfully charged $#{sprintf("%.2f", amount / 100)} to the credit card #{credit_card.display_number}" - else - raise StandardError, response.message - end - end +```ruby +require 'active_merchant' + +# Use the TrustCommerce test servers +ActiveMerchant::Billing::Base.mode = :test + +gateway = ActiveMerchant::Billing::TrustCommerceGateway.new( + :login => 'TestMerchant', + :password => 'password') + +# ActiveMerchant accepts all amounts as Integer values in cents +amount = 1000 # $10.00 + +# The card verification value is also known as CVV2, CVC2, or CID +credit_card = ActiveMerchant::Billing::CreditCard.new( + :first_name => 'Bob', + :last_name => 'Bobsen', + :number => '4242424242424242', + :month => '8', + :year => Time.now.year+1, + :verification_value => '000') + +# Validating the card automatically detects the card type +if credit_card.validate.empty? + # Capture $10 from the credit card + response = gateway.purchase(amount, credit_card) + + if response.success? + puts "Successfully charged $#{sprintf("%.2f", amount / 100)} to the credit card #{credit_card.display_number}" + else + raise StandardError, response.message + end +end +``` For more in-depth documentation and tutorials, see [GettingStarted.md](GettingStarted.md) and the -[API documentation](http://rubydoc.info/github/Shopify/active_merchant/master/file/README.md). +[API documentation](http://www.rubydoc.info/github/activemerchant/active_merchant/). -## Supported Direct Payment Gateways +Emerging ActiveMerchant 3DS conventions are documented in the [Contributing](https://github.com/activemerchant/active_merchant/wiki/Contributing#3ds-options) +guide and [Standardized 3DS Fields](https://github.com/activemerchant/active_merchant/wiki/Standardized-3DS-Fields) guide of the wiki. -The [ActiveMerchant Wiki](http://github.com/Shopify/active_merchant/wikis) contains a [table of features supported by each gateway](http://github.com/Shopify/active_merchant/wikis/gatewayfeaturematrix). +## Supported Payment Gateways + +The [ActiveMerchant Wiki](https://github.com/activemerchant/active_merchant/wikis) contains a [table of features supported by each gateway](https://github.com/activemerchant/active_merchant/wiki/Gateway-Feature-Matrix). * [Authorize.Net CIM](http://www.authorize.net/) - US -* [Authorize.Net](http://www.authorize.net/) - US, CA, GB +* [Authorize.Net](http://www.authorize.net/) - AD, AT, AU, BE, BG, CA, CH, CY, CZ, DE, DK, ES, FI, FR, GB, GB, GI, GR, HU, IE, IT, LI, LU, MC, MT, NL, NO, PL, PT, RO, SE, SI, SK, SM, TR, US, VA +* [Axcess MS](http://www.axcessms.com/) - AD, AT, BE, BG, BR, CA, CH, CY, CZ, DE, DK, EE, ES, FI, FO, FR, GB, GI, GR, HR, HU, IE, IL, IM, IS, IT, LI, LT, LU, LV, MC, MT, MX, NL, NO, PL, PT, RO, RU, SE, SI, SK, TR, US, VA * [Balanced](https://www.balancedpayments.com/) - US +* [Bambora Asia-Pacific](http://www.bambora.com/) - AU, NZ +* [Bank Frick](http://www.bankfrickacquiring.com/) - LI, US * [Banwire](http://www.banwire.com/) - MX * [Barclays ePDQ Extra Plus](http://www.barclaycard.co.uk/business/accepting-payments/epdq-ecomm/) - GB -* [Barclays ePDQ MPI](http://www.barclaycard.co.uk/business/accepting-payments/epdq-mpi/) - GB +* [Be2Bill](http://www.be2bill.com/) - FR * [Beanstream.com](http://www.beanstream.com/) - CA, US * [BluePay](http://www.bluepay.com/) - US -* [Braintree](http://www.braintreepaymentsolutions.com) - US, CA, AU, AD, AT, BE, BG, CY, CZ, DK, EE, FI, FR, GI, DE, GR, HU, IS, IM, IE, IT, LV, LI, LT, LU, MT, MC, NL, NO, PL, PT, RO, SM, SK, SI, ES, SE, CH, TR, GB +* [Borgun](https://www.borgun.is/) - IS +* [Braintree](https://www.braintreepayments.com) - US, CA, AU, AD, AT, BE, BG, CY, CZ, DK, EE, FI, FR, GI, DE, GR, HU, IS, IM, IE, IT, LV, LI, LT, LU, MT, MC, NL, NO, PL, PT, RO, SM, SK, SI, ES, SE, CH, TR, GB +* [BridgePay](http://www.bridgepaynetwork.com/) - CA, US +* [Cardknox](https://www.cardknox.com/) - US, CA, GB * [CardSave](http://www.cardsave.net/) - GB * [CardStream](http://www.cardstream.com/) - GB -* [CertoDirect](http://www.certodirect.com/) - BE, BG, CZ, DK, DE, EE, IE, EL, ES, FR, IT, CY, LV, LT, LU, HU, MT, NL, AT, PL, PT, RO, SI, SK, FI, SE, GB +* [Cashnet](http://www.higherone.com/) - US +* [Cecabank](http://www.ceca.es/es/) - ES +* [Cenpos](https://www.cenpos.com/) - AD, AI, AG, AR, AU, AT, BS, BB, BE, BZ, BM, BR, BN, BG, CA, HR, CY, CZ, DK, DM, EE, FI, FR, DE, GR, GD, GY, HK, HU, IS, IN, IL, IT, JP, LV, LI, LT, LU, MY, MT, MX, MC, MS, NL, PA, PL, PT, KN, LC, MF, VC, SM, SG, SK, SI, ZA, ES, SR, SE, CH, TR, GB, US, UY +* [CAMS: Central Account Management System](https://www.centralams.com/) - US +* [Checkout.com](https://www.checkout.com/) - AT, BE, BG, CY, CZ, DE, DK, EE, ES, FI, FR, GR, HR, HU, IE, IS, IT, LI, LT, LU, LV, MT, MU, NL, NO, PL, PT, RO, SE, SI, SK, US +* [Clearhaus](https://www.clearhaus.com) - AD, AT, BE, BG, CH, CY, CZ, DE, DK, EE, ES, FI, FO, FR, GB, GL, GR, HR, HU, IE, IS, IT, LI, LT, LU, LV, MT, NL, NO, PL, PT, RO, SE, SI, SK +* [Commercegate](http://www.commercegate.com/) - AD, AT, AX, BE, BG, CH, CY, CZ, DE, DK, ES, FI, FR, GB, GG, GI, GR, HR, HU, IE, IM, IS, IT, JE, LI, LT, LU, LV, MC, MT, NL, NO, PL, PT, RO, SE, SI, SK, VA +* [Conekta](https://conekta.io) - MX * [CyberSource](http://www.cybersource.com) - US, BR, CA, CN, DK, FI, FR, DE, JP, MX, NO, SE, GB, SG +* [DIBS](http://www.dibspayment.com/) - US, FI, NO, SE, GB * [DataCash](http://www.datacash.com/) - GB * [Efsnet](http://www.concordefsnet.com/) - US * [Elavon MyVirtualMerchant](http://www.elavon.com/) - US, CA * [ePay](http://epay.dk/) - DK, SE, NO * [EVO Canada](http://www.evocanada.com/) - CA * [eWAY](http://www.eway.com.au/) - AU, NZ, GB -* [eWAY Rapid 3.0](http://www.eway.com.au/) - AU +* [eWAY Rapid](http://www.eway.com.au/) - AU, NZ, GB, SG * [E-xact](http://www.e-xact.com) - CA, US +* [Ezic](http://www.ezic.com/) - AU, CA, CN, FR, DE, GI, IL, MT, MU, MX, NL, NZ, PA, PH, RU, SG, KR, ES, KN, GB * [Fat Zebra](https://www.fatzebra.com.au/) - AU * [Federated Canada](http://www.federatedcanada.com/) - CA * [Finansbank WebPOS](https://www.fbwebpos.com/) - US, TR -* [First Pay](http://www.first-pay.com) - US +* [Flo2Cash](http://www.flo2cash.co.nz/) - NZ +* [1stPayGateway.Net](http://1stpaygateway.net/) - US * [FirstData Global Gateway e4](http://www.firstdata.com) - CA, US +* [FirstGiving](http://www.firstgiving.com/) - US * [Garanti Sanal POS](https://sanalposweb.garanti.com.tr) - US, TR +* [Global Transport](https://www.globalpaymentsinc.com) - CA, PR, US * [HDFC](http://www.hdfcbank.com/sme/sme-details/merchant-services/guzh6m0i) - IN -* [IATSPayments](http://www.iatspayments.com/) - US, CA, GB +* [Heartland Payment Systems](http://developer.heartlandpaymentsystems.com/SecureSubmit/) - US +* [iATS Payments](http://home.iatspayments.com/) - AU, BR, CA, CH, DE, DK, ES, FI, FR, GR, HK, IE, IT, NL, NO, PT, SE, SG, TR, UK, US * [Inspire Commerce](http://www.inspiregateway.com) - US * [InstaPay](http://www.instapayllc.com) - US +* [IPP](http://www.ippayments.com.au/) - AU * [Iridium](http://www.iridiumcorp.co.uk/) - GB, ES * [iTransact](http://www.itransact.com/) - US * [JetPay](http://www.jetpay.com/) - US +* [Komoju](http://www.komoju.com/) - JP * [LinkPoint](http://www.linkpoint.com/) - US * [Litle & Co.](http://www.litle.com/) - US +* [maxiPago!](http://www.maxipago.com/) - BR * [Merchant e-Solutions](http://www.merchante-solutions.com/) - US * [Merchant One Gateway](http://merchantone.com/) - US * [MerchantWARE](http://merchantwarehouse.com/merchantware) - US * [MerchantWarrior](http://www.merchantwarrior.com/) - AU -* [Mercury](http://www.mercurypay.com) - US +* [Mercury](http://www.mercurypay.com) - US, CA * [Metrics Global](http://www.metricsglobal.com) - US * [MasterCard Internet Gateway Service (MiGS)](http://mastercard.com/mastercardsps) - AU, AE, BD, BN, EG, HK, ID, IN, JO, KW, LB, LK, MU, MV, MY, NZ, OM, PH, QA, SA, SG, TT, VN * [Modern Payments](http://www.modpay.com) - US +* [MONEI](http://www.monei.net/) - AD, AT, BE, BG, CA, CH, CY, CZ, DE, DK, EE, ES, FI, FO, FR, GB, GI, GR, HU, IE, IL, IS, IT, LI, LT, LU, LV, MT, NL, NO, PL, PT, RO, SE, SI, SK, TR, US, VA * [Moneris](http://www.moneris.com/) - CA * [Moneris (US)](http://www.monerisusa.com/) - US +* [MoneyMovers](http://mmoa.us/) - US * [NAB Transact](http://transact.nab.com.au) - AU +* [NELiX TransaX](https://www.nelixtransax.com/) - US * [NetRegistry](http://www.netregistry.com.au) - AU * [BBS Netaxept](http://www.betalingsterminal.no/Netthandel-forside/) - NO, DK, SE, FI * [NETbilling](http://www.netbilling.com) - US * [NETPAY Gateway](http://www.netpay.com.mx) - MX * [NMI](http://nmi.com/) - US * [Ogone](http://www.ogone.com/) - BE, DE, FR, NL, AT, CH +* [Omise](https://www.omise.co/) - TH, JP +* [Openpay](Openpay) - MX * [Optimal Payments](http://www.optimalpayments.com/) - CA, US, GB * [Orbital Paymentech](http://chasepaymentech.com/) - US, CA +* [Pagar.me](https://pagar.me/) - BR +* [PagoFacil](http://www.pagofacil.net/) - MX +* [PayConex](http://www.bluefincommerce.com/) - US, CA * [PayGate PayXML](http://paygate.co.za/) - US, ZA +* [PayHub](http://www.payhub.com/) - US * [PayJunction](http://www.payjunction.com/) - US * [PaySecure](http://www.commsecure.com.au/paysecure.shtml) - AU * [Paybox Direct](http://www.paybox.com/) - FR +* [Payeezy](https://developer.payeezy.com/) - CA, US +* [Payex](http://payex.com/) - DK, FI, NO, SE * [PaymentExpress](http://www.paymentexpress.com/) - AU, CA, DE, ES, FR, GB, HK, IE, MY, NL, NZ, SG, US, ZA * [PAYMILL](https://paymill.com) - AD, AT, BE, BG, CH, CY, CZ, DE, DK, EE, ES, FI, FO, FR, GB, GI, GR, HU, IE, IL, IS, IT, LI, LT, LU, LV, MT, NL, NO, PL, PT, RO, SE, SI, SK, TR, VA * [PayPal Express Checkout](https://www.paypal.com/webapps/mpp/express-checkout) - US, CA, SG, AU @@ -149,77 +192,55 @@ The [ActiveMerchant Wiki](http://github.com/Shopify/active_merchant/wikis) conta * [PayPal Payments Pro (UK)](https://www.paypal.com/uk/webapps/mpp/pro) - GB * [PayPal Website Payments Pro (CA)](https://www.paypal.com/cgi-bin/webscr?cmd=_wp-pro-overview-outside) - CA * [PayPal Express Checkout for Digital Goods](https://www.x.com/community/ppx/xspaces/digital_goods) - AU, CA, CN, FI, GB, ID, IN, IT, MY, NO, NZ, PH, PL, SE, SG, TH, VN +* [Payscout](http://www.payscout.com/) - US * [Paystation](http://paystation.co.nz) - NZ * [Pay Way](http://www.payway.com.au) - AU -* [Pin](http://www.pin.net.au/) - AU +* [PayU India](https://www.payu.in/) - IN +* [Pin Payments](http://www.pinpayments.com/) - AU * [Plug'n Pay](http://www.plugnpay.com/) - US * [Psigate](http://www.psigate.com/) - CA * [PSL Payment Solutions](http://www.paymentsolutionsltd.com/) - GB * [QuickBooks Merchant Services](http://payments.intuit.com/) - US +* [QuickBooks Payments](http://payments.intuit.com/) - US * [Quantum Gateway](http://www.quantumgateway.com) - US -* [Quickpay](http://quickpay.dk/) - DK, SE +* [QuickPay](http://quickpay.net/) - AT, BE, BG, CY, CZ, DE, DK, EE, ES, FI, FR, GB, GR, HR, HU, IE, IS, IT, LI, LT, LU, LV, MT, NL, NO, PL, PT, RO, SE SI, SK +* [Qvalent](https://www.qvalent.com/) - AU +* [Raven](http://www.deepcovelabs.com/raven) - AI, AN, AT, AU, BE, BG, BS, BZ, CA, CH, CR, CY, CZ, DE, DK, DM, DO, EE, EL, ES, FI, FR, GB, GG, GI, HK, HR, HU, IE, IL, IM, IN, IT, JE, KN, LI, LT, LU, LV, MH, MT, MY, NL, NO, NZ, PA, PE, PH, PL, PT, RO, RS, SC, SE, SG, SI, SK, UK, US, VG, ZA * [Realex](http://www.realexpayments.com/) - IE, GB, FR, BE, NL, LU, IT * [Redsys](http://www.redsys.es/) - ES +* [S5](http://www.s5.dk/) - DK * [SagePay](http://www.sagepay.com) - GB, IE * [Sage Payment Solutions](http://www.sagepayments.com) - US, CA * [Sallie Mae](http://www.salliemae.com/) - US -* [Samurai](https://samurai.feefighters.com) - US * [SecureNet](http://www.securenet.com/) - US * [SecurePay](http://www.securepay.com/) - US, CA, GB, AU * [SecurePayTech](http://www.securepaytech.com/) - NZ +* [SecurionPay](https://securionpay.com/) - AD, AE, AF, AG, AI, AL, AM, AO, AR, AS, AT, AU, AW, AX, AZ, BA, BB, BD, BE, BF, BG, BH, BI, BJ, BL, BM, BN, BO, BR, BS, BT, BV, BW, BY, BZ, CA, CC, CD, CF, CG, CH, CI, CK, CL, CM, CN, CO, CR, CU, CV, CW, CX, CY, CZ, DE, DJ, DK, DM, DO, DZ, EC, EE, EG, EH, ER, ES, ET, FI, FJ, FK, FM, FO, FR, GA, GB, GD, GE, GF, GG, GH, GI, GL, GM, GN, GP, GQ, GR, GS, GT, GU, GW, GY, HK, HM, HN, HR, HT, HU, ID, IE, IL, IM, IN, IO, IQ, IR, IS, IT, JE, JM, JO, JP, KE, KG, KH, KI, KM, KN, KP, KR, KW, KY, KZ, LA, LB, LC, LI, LK, LR, LS, LT, LU, LV, LY, MA, MC, MD, ME, MF, MG, MH, MK, ML, MM, MN, MO, MP, MQ, MR, MS, MT, MU, MV, MW, MX, MY, MZ, NA, NC, NE, NF, NG, NI, NL, NO, NP, NR, NU, NZ, OM, PA, PE, PF, PG, PH, PK, PL, PM, PN, PR, PS, PT, PW, PY, QA, RE, RO, RS, RU, RW, SA, SB, SC, SD, SE, SG, SH, SI, SJ, SK, SL, SM, SN, SO, SR, ST, SV, SY, SZ, TC, TD, TF, TG, TH, TJ, TK, TL, TM, TN, TO, TR, TT, TV, TW, TZ, UA, UG, UM, US, UY, UZ, VA, VC, VE, VG, VI, VN, VU, WF, WS, YE, YT, ZA, ZM, ZW * [SkipJack](http://www.skipjack.com/) - US, CA +* [SoEasyPay](http://www.soeasypay.com/) - US, CA, AT, BE, BG, HR, CY, CZ, DK, EE, FI, FR, DE, GR, HU, IE, IT, LV, LT, LU, MT, NL, PL, PT, RO, SK, SI, ES, SE, GB, IS, NO, CH * [Spreedly](https://spreedly.com) - AD, AE, AT, AU, BD, BE, BG, BN, CA, CH, CY, CZ, DE, DK, EE, EG, ES, FI, FR, GB, GI, GR, HK, HU, ID, IE, IL, IM, IN, IS, IT, JO, KW, LB, LI, LK, LT, LU, LV, MC, MT, MU, MV, MX, MY, NL, NO, NZ, OM, PH, PL, PT, QA, RO, SA, SE, SG, SI, SK, SM, TR, TT, UM, US, VA, VN, ZA -* [Stripe](https://stripe.com/) - US, CA, GB, AU, IE, FR, NL, BE, DE, ES +* [Stripe](https://stripe.com/) - AT, AU, BE, CA, CH, DE, DK, ES, FI, FR, GB, IE, IT, LU, NL, NO, SE, SG, US +* [Swipe](https://www.swipehq.com/checkout) - CA, NZ +* [TNS](http://www.tnsi.com/) - AR, AU, BR, FR, DE, HK, MX, NZ, SG, GB, US +* [Transact Pro](https://www.transactpro.lv/business/online-payments-acceptance) - US * [TransFirst](http://www.transfirst.com/) - US -* [NELiX TransaX](https://www.nelixtransax.com/) - US * [Transnational](http://www.tnbci.com/) - US * [TrustCommerce](http://www.trustcommerce.com/) - US * [USA ePay](http://www.usaepay.com/) - US +* [Vanco Payment Solutions](http://vancopayments.com/) - US * [Verifi](http://www.verifi.com/) - US * [ViaKLIX](http://viaklix.com) - US -* [Vindicia](http://www.vindicia.com/) - US, CA, GB, AU, MX, BR, DE, KR, CN, HK * [WebPay](https://webpay.jp/) - JP +* [WePay](https://www.wepay.com/) - US * [Wirecard](http://www.wirecard.com) - AD, CY, GI, IM, MT, RO, CH, AT, DK, GR, IT, MC, SM, TR, BE, EE, HU, LV, NL, SK, GB, BG, FI, IS, LI, NO, SI, VA, FR, IL, LT, PL, ES, CZ, DE, IE, LU, PT, SE -* [WorldPay](http://www.worldpay.com/) - HK, US, GB, AU, AD, BE, CH, CY, CZ, DE, DK, ES, FI, FR, GI, GR, HU, IE, IL, IT, LI, LU, MC, MT, NL, NO, NZ, PL, PT, SE, SG, SI, SM, TR, UM, VA - -## Supported Offsite Payment Gateways - -* [2 Checkout](http://www.2checkout.com) -* [A1Agregator](http://a1agregator.ru/) - RU -* [Authorize.Net SIM](http://developer.authorize.net/api/sim/) - US -* [Banca Sella GestPay](https://www.sella.it/banca/ecommerce/gestpay/gestpay.jsp) -* [Chronopay](http://www.chronopay.com) -* [DirecPay](http://www.timesofmoney.com/direcpay/jsp/home.jsp) -* [Direct-eBanking / sofortueberweisung.de by Payment-Networks AG](https://www.payment-network.com/deb_com_en/merchantarea/home) - DE, AT, CH, BE, UK, NL -* [Dotpay](http://dotpay.pl) -* [Dwolla](https://www.dwolla.com/default.aspx) -* [ePay](http://www.epay.dk/epay-payment-solutions/) -* [First Data](https://firstdata.zendesk.com/entries/407522-first-data-global-gateway-e4sm-payment-pages-integration-manual) -* [HiTRUST](http://www.hitrust.com.hk/) -* [Moneybookers](http://www.moneybookers.com) -* [Nochex](http://www.nochex.com) -* [Paxum](https://www.paxum.com/) -* [PayPal Website Payments Standard](https://www.paypal.com/cgi-bin/webscr?cmd#_wp-standard-overview-outside) -* [Paysbuy](https://www.paysbuy.com/) - TH -* [Platron](https://www.platron.ru/) - RU -* [RBK Money](https://rbkmoney.ru/) - RU -* [Robokassa](http://robokassa.ru/) - RU -* [SagePay Form](http://www.sagepay.com/products_services/sage_pay_go/integration/form) -* [Suomen Maksuturva](https://www.maksuturva.fi/services/vendor_services/integration_guidelines.html) -* [Valitor](http://www.valitor.is/) - IS -* [Verkkomaksut](http://www.verkkomaksut.fi) - FI -* [WebMoney](http://www.webmoney.ru) - RU -* [WebPay](http://webpay.by/) -* [WorldPay](http://www.worldpay.com) - -## Contributing - -The source code is hosted at [GitHub](http://github.com/Shopify/active_merchant), and can be fetched using: - - git clone git://github.com/Shopify/active_merchant.git - -Please see the [ActiveMerchant Guide to Contributing](http://github.com/Shopify/active_merchant/wikis/contributing) for -information on adding a new gateway to ActiveMerchant. - -Please don't touch the CHANGELOG in your pull requests, we'll add the appropriate CHANGELOG entries -at release time. +* [Worldpay Global](http://www.worldpay.com/) - HK, GB, AU, AD, BE, CH, CY, CZ, DE, DK, ES, FI, FR, GI, GR, HU, IE, IL, IT, LI, LU, MC, MT, NL, NO, NZ, PL, PT, SE, SG, SI, SM, TR, UM, VA +* [Worldpay Online](https://online.worldpay.com/) - HK, US, GB, AU, AD, BE, CH, CY, CZ, DE, DK, ES, FI, FR, GI, GR, HU, IE, IL, IT, LI, LU, MC, MT, NL, NO, NZ, PL, PT, SE, SG, SI, SM, TR, UM, VA +* [Worldpay US](http://www.worldpay.com/us) - US + +## API stability policy + +Functionality or APIs that are deprecated will be marked as such. Deprecated functionality is removed on major version changes - for example, deprecations from 2.x are removed in 3.x. + +## Ruby and Rails compatibility policies + +Because Active Merchant is a payment library, it needs to take security seriously. For this reason, Active Merchant guarantees compatibility only with actively supported versions of Ruby and Rails. At the time of this writing, that means that Ruby 2.3+ and Rails 4.2+ are supported. diff --git a/RELEASING b/RELEASING deleted file mode 100644 index a22f2819530..00000000000 --- a/RELEASING +++ /dev/null @@ -1,15 +0,0 @@ -releasing Active Merchant - -1. Get the gem-private_key.pem from Cody and store it chmod 660 somewhere safe -2. Check the Semantic Versioning page for info on how to version the new release: http://semver.org -3. Update the version of Active Merchant in lib/active_merchant/version.rb and in activemerchant.gemspec -4. Add a CHANGELOG entry for the new release with the date -5. Commit the changes with a commit message like "Packaging for release X.Y.Z" -6. Tag the release with the version (Leave REV blank for HEAD or provide a SHA) - $ git tag vX.Y.Z REV -7. Push out the changes - $ git push -8. Push out the tags - $ git push --tags -9. Publish the Gem to gemcutter - $ rake release GEM_PRIVATE_KEY=/path/to/private/key \ No newline at end of file diff --git a/Rakefile b/Rakefile index 5124db49d7f..5e92782a541 100644 --- a/Rakefile +++ b/Rakefile @@ -1,4 +1,5 @@ $:.unshift File.expand_path('../lib', __FILE__) +require 'active_merchant/version' begin require 'bundler' @@ -10,53 +11,39 @@ end require 'rake' require 'rake/testtask' -require 'rubygems/package_task' +require 'rubocop/rake_task' require 'support/gateway_support' require 'support/ssl_verify' +require 'support/ssl_version' require 'support/outbound_hosts' +require 'bundler/gem_tasks' -desc "Run the unit test suite" -task :default => 'test:units' +task :tag_release do + system "git tag 'v#{ActiveMerchant::VERSION}'" + system 'git push --tags' +end +desc 'Run the unit test suite' +task :default => 'test:units' task :test => 'test:units' -namespace :test do +RuboCop::RakeTask.new +namespace :test do Rake::TestTask.new(:units) do |t| t.pattern = 'test/unit/**/*_test.rb' - t.ruby_opts << '-rubygems' t.libs << 'test' t.verbose = true end + desc 'Run all tests that do not require network access' + task :local => ['test:units', 'rubocop'] + Rake::TestTask.new(:remote) do |t| t.pattern = 'test/remote/**/*_test.rb' - t.ruby_opts << '-rubygems' t.libs << 'test' t.verbose = true end - -end - -desc "Delete tar.gz / zip" -task :cleanup => [ :clobber_package ] - -spec = eval(File.read('activemerchant.gemspec')) - -Gem::PackageTask.new(spec) do |p| - p.gem_spec = spec - p.need_tar = true - p.need_zip = true -end - -desc "Release the gems and docs to RubyForge" -task :release => [ 'gemcutter:publish' ] - -namespace :gemcutter do - desc "Publish to gemcutter" - task :publish => :package do - sh "gem push pkg/activemerchant-#{ActiveMerchant::VERSION}.gem" - end end namespace :gateways do @@ -94,11 +81,30 @@ namespace :gateways do desc 'Print the list of destination hosts with port' task :hosts do - OutboundHosts.list + hosts, invalid_lines = OutboundHosts.list + + hosts.each do |host| + puts host + end + + unless invalid_lines.empty? + puts + puts 'Unable to parse:' + invalid_lines.each do |line| + puts line + end + end end - desc 'Test that gateways allow SSL verify_peer' - task :ssl_verify do - SSLVerify.new.test_gateways + namespace :ssl do + desc 'Test that gateways allow SSL verify_peer' + task :verify do + SSLVerify.new.test_gateways + end + + desc 'Test gateways minimal SSL version connection' + task :min_version do + SSLVersion.new.test_gateways + end end end diff --git a/activemerchant.gemspec b/activemerchant.gemspec index 953eea7edd7..505bcfecd42 100644 --- a/activemerchant.gemspec +++ b/activemerchant.gemspec @@ -1,4 +1,4 @@ -$:.push File.expand_path("../lib", __FILE__) +$:.push File.expand_path('../lib', __FILE__) require 'active_merchant/version' Gem::Specification.new do |s| @@ -7,29 +7,28 @@ Gem::Specification.new do |s| s.version = ActiveMerchant::VERSION s.summary = 'Framework and tools for dealing with credit card transactions.' s.description = 'Active Merchant is a simple payment abstraction library used in and sponsored by Shopify. It is written by Tobias Luetke, Cody Fauser, and contributors. The aim of the project is to feel natural to Ruby users and to abstract as many parts as possible away from the user to offer a consistent interface across all supported gateways.' + s.license = 'MIT' s.author = 'Tobias Luetke' s.email = 'tobi@leetsoft.com' s.homepage = 'http://activemerchant.org/' s.rubyforge_project = 'activemerchant' - s.files = Dir['CHANGELOG', 'README.md', 'MIT-LICENSE', 'CONTRIBUTORS', 'gem-public_cert.pem', 'lib/**/*', 'vendor/**/*'] + s.required_ruby_version = '>= 2.3' + + s.files = Dir['CHANGELOG', 'README.md', 'MIT-LICENSE', 'CONTRIBUTORS', 'lib/**/*', 'vendor/**/*'] s.require_path = 'lib' s.has_rdoc = true if Gem::VERSION < '1.7.0' - s.add_dependency('activesupport', '>= 2.3.14', '< 5.0.0') - s.add_dependency('i18n', '~> 0.5') - s.add_dependency('money', '< 6.0.0') + s.add_dependency('activesupport', '>= 4.2') + s.add_dependency('i18n', '>= 0.6.9') s.add_dependency('builder', '>= 2.1.2', '< 4.0.0') - s.add_dependency('json', '~> 1.7') - s.add_dependency('active_utils', '~> 2.0') - s.add_dependency('nokogiri', "~> 1.4") + s.add_dependency('nokogiri', '~> 1.4') s.add_development_dependency('rake') - s.add_development_dependency('mocha', '~> 0.13.0') - s.add_development_dependency('rails', '>= 2.3.14') + s.add_development_dependency('test-unit', '~> 3') + s.add_development_dependency('mocha', '~> 1') s.add_development_dependency('thor') - s.signing_key = ENV['GEM_PRIVATE_KEY'] - s.cert_chain = ['gem-public_cert.pem'] + s.add_development_dependency('pry') end diff --git a/circle.yml b/circle.yml new file mode 100644 index 00000000000..5d2a53e5abb --- /dev/null +++ b/circle.yml @@ -0,0 +1,39 @@ +machine: + ruby: + version: '2.3.0' + +dependencies: + cache_directories: + - 'vendor/bundle_32' + - 'vendor/bundle_40' + - 'vendor/bundle_41' + - 'vendor/bundle_42' + - 'vendor/bundle_5' + override: + - bundle check --path=vendor/bundle_32 --gemfile Gemfile.rails32 || bundle install --jobs=4 --retry=3 --gemfile Gemfile.rails32 --path=vendor/bundle_32 + - bundle check --path=vendor/bundle_40 --gemfile Gemfile.rails40 || bundle install --jobs=4 --retry=3 --gemfile Gemfile.rails40 --path=vendor/bundle_40 + - bundle check --path=vendor/bundle_41 --gemfile Gemfile.rails41 || bundle install --jobs=4 --retry=3 --gemfile Gemfile.rails41 --path=vendor/bundle_41 + - bundle check --path=vendor/bundle_42 --gemfile Gemfile.rails42 || bundle install --jobs=4 --retry=3 --gemfile Gemfile.rails42 --path=vendor/bundle_42 + - bundle check --path=vendor/bundle_5 --gemfile Gemfile.rails5 || bundle install --jobs=4 --retry=3 --gemfile Gemfile.rails5 --path=vendor/bundle_5 + +test: + override: + - bundle check --path=vendor/bundle_32 && bundle exec rake test: + environment: + BUNDLE_GEMFILE: Gemfile.rails32 + + - bundle check --path=vendor/bundle_40 && bundle exec rake test: + environment: + BUNDLE_GEMFILE: Gemfile.rails40 + + - bundle check --path=vendor/bundle_41 && bundle exec rake test: + environment: + BUNDLE_GEMFILE: Gemfile.rails41 + + - bundle check --path=vendor/bundle_42 && bundle exec rake test: + environment: + BUNDLE_GEMFILE: Gemfile.rails42 + + - bundle check --path=vendor/bundle_5 && bundle exec rake test: + environment: + BUNDLE_GEMFILE: Gemfile.rails5 diff --git a/gem-public_cert.pem b/gem-public_cert.pem deleted file mode 100644 index c2588d5c296..00000000000 --- a/gem-public_cert.pem +++ /dev/null @@ -1,20 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIDNjCCAh6gAwIBAgIBADANBgkqhkiG9w0BAQUFADBBMRMwEQYDVQQDDApjb2R5 -ZmF1c2VyMRUwEwYKCZImiZPyLGQBGRYFZ21haWwxEzARBgoJkiaJk/IsZAEZFgNj -b20wHhcNMDcwMjIyMTcyMTI3WhcNMDgwMjIyMTcyMTI3WjBBMRMwEQYDVQQDDApj -b2R5ZmF1c2VyMRUwEwYKCZImiZPyLGQBGRYFZ21haWwxEzARBgoJkiaJk/IsZAEZ -FgNjb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC6T4Iqt5iWvAlU -iXI6L8UO0URQhIC65X/gJ9hL/x4lwSl/ckVm/R/bPrJGmifT+YooFv824N3y/TIX -25o/lZtRj1TUZJK4OCb0aVzosQVxBHSe6rLmxO8cItNTMOM9wn3thaITFrTa1DOQ -O3wqEjvW2L6VMozVfK1MfjL9IGgy0rCnl+2g4Gh4jDDpkLfnMG5CWI6cTCf3C1ye -ytOpWgi0XpOEy8nQWcFmt/KCQ/kFfzBo4QxqJi54b80842EyvzWT9OB7Oew/CXZG -F2yIHtiYxonz6N09vvSzq4CvEuisoUFLKZnktndxMEBKwJU3XeSHAbuS7ix40OKO -WKuI54fHAgMBAAGjOTA3MAkGA1UdEwQCMAAwCwYDVR0PBAQDAgSwMB0GA1UdDgQW -BBR9QQpefI3oDCAxiqJW/3Gg6jI6qjANBgkqhkiG9w0BAQUFAAOCAQEAs0lX26O+ -HpyMp7WL+SgZuM8k76AjfOHuKajl2GEn3S8pWYGpsa0xu07HtehJhKLiavrfUYeE -qlFtyYMUyOh6/1S2vfkH6VqjX7mWjoi7XKHW/99fkMS40B5SbN+ypAUst+6c5R84 -w390mjtLHpdDE6WQYhS6bFvBN53vK6jG3DLyCJc0K9uMQ7gdHWoxq7RnG92ncQpT -ThpRA+fky5Xt2Q63YJDnJpkYAz79QIama1enSnd4jslKzSl89JS2luq/zioPe/Us -hbyalWR1+HrhgPoSPq7nk+s2FQUBJ9UZFK1lgMzho/4fZgzJwbu+cO8SNuaLS/bj -hPaSTyVU0yCSnw== ------END CERTIFICATE----- diff --git a/gemfiles/Gemfile.rails42 b/gemfiles/Gemfile.rails42 new file mode 100644 index 00000000000..8d11bec617c --- /dev/null +++ b/gemfiles/Gemfile.rails42 @@ -0,0 +1,3 @@ +eval_gemfile '../Gemfile' + +gem 'activesupport', '~> 4.2.0' diff --git a/gemfiles/Gemfile.rails50 b/gemfiles/Gemfile.rails50 new file mode 100644 index 00000000000..ce57bebccbe --- /dev/null +++ b/gemfiles/Gemfile.rails50 @@ -0,0 +1,3 @@ +eval_gemfile '../Gemfile' + +gem 'activesupport', '~> 5.0.0' diff --git a/gemfiles/Gemfile.rails51 b/gemfiles/Gemfile.rails51 new file mode 100644 index 00000000000..a352b24eaa8 --- /dev/null +++ b/gemfiles/Gemfile.rails51 @@ -0,0 +1,3 @@ +eval_gemfile '../Gemfile' + +gem 'activesupport', '~> 5.1.0' diff --git a/gemfiles/Gemfile.rails52 b/gemfiles/Gemfile.rails52 new file mode 100644 index 00000000000..c6c439fce53 --- /dev/null +++ b/gemfiles/Gemfile.rails52 @@ -0,0 +1,3 @@ +eval_gemfile '../Gemfile' + +gem 'activesupport', '~> 5.2.0.rc1' diff --git a/gemfiles/Gemfile.rails_master b/gemfiles/Gemfile.rails_master new file mode 100644 index 00000000000..04b88ec1b00 --- /dev/null +++ b/gemfiles/Gemfile.rails_master @@ -0,0 +1,3 @@ +eval_gemfile '../Gemfile' + +gem 'activesupport', github: 'rails/rails' diff --git a/generators/gateway/gateway_generator.rb b/generators/gateway/gateway_generator.rb index de8fe03c682..27dda8aa510 100644 --- a/generators/gateway/gateway_generator.rb +++ b/generators/gateway/gateway_generator.rb @@ -1,4 +1,5 @@ require "thor/group" +require "yaml" class GatewayGenerator < ActiveMerchantGenerator source_root File.expand_path("..", __FILE__) @@ -7,6 +8,15 @@ def generate template "templates/gateway.rb", gateway_file template "templates/gateway_test.rb", gateway_test_file template "templates/remote_gateway_test.rb", remote_gateway_test_file + + before = (next_identifier ? /(?:\n#[^\n]*)*\n#{next_identifier}:\s*\n/ : /\z/) + inject_into_file(fixtures_file, <Gateway < Gateway self.test_url = 'https://example.com/test' self.live_url = 'https://example.com/live' - # The countries the gateway supports merchants from as 2 digit ISO country codes self.supported_countries = ['US'] - - # The card types supported by the payment gateway + self.default_currency = 'USD' self.supported_cardtypes = [:visa, :master, :american_express, :discover] - # The homepage URL of the gateway self.homepage_url = 'http://www.example.net/' - - # The name of the gateway self.display_name = 'New Gateway' - def initialize(options = {}) - #requires!(options, :login, :password) + STANDARD_ERROR_CODE_MAPPING = {} + + def initialize(options={}) + requires!(options, :some_credential, :another_credential) super end - def authorize(money, creditcard, options = {}) + def purchase(money, payment, options={}) post = {} - add_invoice(post, options) - add_creditcard(post, creditcard) - add_address(post, creditcard, options) + add_invoice(post, money, options) + add_payment(post, payment) + add_address(post, payment, options) add_customer_data(post, options) - commit('authonly', money, post) + commit('sale', post) end - def purchase(money, creditcard, options = {}) + def authorize(money, payment, options={}) post = {} - add_invoice(post, options) - add_creditcard(post, creditcard) - add_address(post, creditcard, options) + add_invoice(post, money, options) + add_payment(post, payment) + add_address(post, payment, options) add_customer_data(post, options) - commit('sale', money, post) + commit('authonly', post) + end + + def capture(money, authorization, options={}) + commit('capture', post) + end + + def refund(money, authorization, options={}) + commit('refund', post) + end + + def void(authorization, options={}) + commit('void', post) + end + + def verify(credit_card, options={}) + MultiResponse.run(:use_first_response) do |r| + r.process { authorize(100, credit_card, options) } + r.process(:ignore_result) { void(r.authorization, options) } + end + end + + def supports_scrubbing? + true end - def capture(money, authorization, options = {}) - commit('capture', money, post) + def scrub(transcript) + transcript end private @@ -53,24 +73,51 @@ def add_customer_data(post, options) def add_address(post, creditcard, options) end - def add_invoice(post, options) + def add_invoice(post, money, options) + post[:amount] = amount(money) + post[:currency] = (options[:currency] || currency(money)) end - def add_creditcard(post, creditcard) + def add_payment(post, payment) end def parse(body) + {} + end + + def commit(action, parameters) + url = (test? ? test_url : live_url) + response = parse(ssl_post(url, post_data(action, parameters))) + + Response.new( + success_from(response), + message_from(response), + response, + authorization: authorization_from(response), + avs_result: AVSResult.new(code: response["some_avs_response_key"]), + cvv_result: CVVResult.new(response["some_cvv_response_key"]), + test: test?, + error_code: error_code_from(response) + ) end - def commit(action, money, parameters) + def success_from(response) end def message_from(response) end + def authorization_from(response) + end + def post_data(action, parameters = {}) end + + def error_code_from(response) + unless success_from(response) + # TODO: lookup error code for this response + end + end end end end - diff --git a/generators/gateway/templates/gateway_test.rb b/generators/gateway/templates/gateway_test.rb index 64018adb74f..03f699b9641 100644 --- a/generators/gateway/templates/gateway_test.rb +++ b/generators/gateway/templates/gateway_test.rb @@ -2,48 +2,126 @@ class <%= class_name %>Test < Test::Unit::TestCase def setup - @gateway = <%= class_name %>Gateway.new( - :login => 'login', - :password => 'password' - ) - + @gateway = <%= class_name %>Gateway.new(some_credential: 'login', another_credential: 'password') @credit_card = credit_card @amount = 100 @options = { - :order_id => '1', - :billing_address => address, - :description => 'Store Purchase' + order_id: '1', + billing_address: address, + description: 'Store Purchase' } end def test_successful_purchase @gateway.expects(:ssl_post).returns(successful_purchase_response) - assert response = @gateway.purchase(@amount, @credit_card, @options) - assert_instance_of Response, response + response = @gateway.purchase(@amount, @credit_card, @options) assert_success response - # Replace with authorization number from the successful response - assert_equal '', response.authorization + assert_equal 'REPLACE', response.authorization assert response.test? end - def test_unsuccessful_request + def test_failed_purchase @gateway.expects(:ssl_post).returns(failed_purchase_response) - assert response = @gateway.purchase(@amount, @credit_card, @options) + response = @gateway.purchase(@amount, @credit_card, @options) assert_failure response - assert response.test? + assert_equal Gateway::STANDARD_ERROR_CODE[:card_declined], response.error_code + end + + def test_successful_authorize + end + + def test_failed_authorize + end + + def test_successful_capture + end + + def test_failed_capture + end + + def test_successful_refund + end + + def test_failed_refund + end + + def test_successful_void + end + + def test_failed_void + end + + def test_successful_verify + end + + def test_successful_verify_with_failed_void + end + + def test_failed_verify + end + + def test_scrub + assert @gateway.supports_scrubbing? + assert_equal @gateway.scrub(pre_scrubbed), post_scrubbed end private - # Place raw successful response from gateway here + def pre_scrubbed + %q( + Run the remote tests for this gateway, and then put the contents of transcript.log here. + ) + end + + def post_scrubbed + %q( + Put the scrubbed contents of transcript.log here after implementing your scrubbing function. + Things to scrub: + - Credit card number + - CVV + - Sensitive authentication details + ) + end + def successful_purchase_response + %( + Easy to capture by setting the DEBUG_ACTIVE_MERCHANT environment variable + to "true" when running remote tests: + + $ DEBUG_ACTIVE_MERCHANT=true ruby -Itest \ + <%= remote_gateway_test_file %> \ + -n test_successful_purchase + ) end - # Place raw failed response from gateway here def failed_purchase_response end + + def successful_authorize_response + end + + def failed_authorize_response + end + + def successful_capture_response + end + + def failed_capture_response + end + + def successful_refund_response + end + + def failed_refund_response + end + + def successful_void_response + end + + def failed_void_response + end end diff --git a/generators/gateway/templates/remote_gateway_test.rb b/generators/gateway/templates/remote_gateway_test.rb index 95ce2be92b9..08262a97c93 100644 --- a/generators/gateway/templates/remote_gateway_test.rb +++ b/generators/gateway/templates/remote_gateway_test.rb @@ -1,57 +1,147 @@ require 'test_helper' class Remote<%= class_name %>Test < Test::Unit::TestCase - - def setup @gateway = <%= class_name %>Gateway.new(fixtures(:<%= identifier %>)) @amount = 100 @credit_card = credit_card('4000100011112224') @declined_card = credit_card('4000300011112220') - @options = { - :order_id => '1', - :billing_address => address, - :description => 'Store Purchase' + billing_address: address, + description: 'Store Purchase' } end def test_successful_purchase - assert response = @gateway.purchase(@amount, @credit_card, @options) + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal 'REPLACE WITH SUCCESS MESSAGE', response.message + end + + def test_successful_purchase_with_more_options + options = { + order_id: '1', + ip: "127.0.0.1", + email: "joe@example.com" + } + + response = @gateway.purchase(@amount, @credit_card, options) assert_success response assert_equal 'REPLACE WITH SUCCESS MESSAGE', response.message end - def test_unsuccessful_purchase - assert response = @gateway.purchase(@amount, @declined_card, @options) + def test_failed_purchase + response = @gateway.purchase(@amount, @declined_card, @options) assert_failure response assert_equal 'REPLACE WITH FAILED PURCHASE MESSAGE', response.message end - def test_authorize_and_capture - amount = @amount - assert auth = @gateway.authorize(amount, @credit_card, @options) + def test_successful_authorize_and_capture + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + assert capture = @gateway.capture(@amount, auth.authorization) + assert_success capture + assert_equal 'REPLACE WITH SUCCESS MESSAGE', capture.message + end + + def test_failed_authorize + response = @gateway.authorize(@amount, @declined_card, @options) + assert_failure response + assert_equal 'REPLACE WITH FAILED AUTHORIZE MESSAGE', response.message + end + + def test_partial_capture + auth = @gateway.authorize(@amount, @credit_card, @options) assert_success auth - assert_equal 'Success', auth.message - assert auth.authorization - assert capture = @gateway.capture(amount, auth.authorization) + + assert capture = @gateway.capture(@amount-1, auth.authorization) assert_success capture end def test_failed_capture - assert response = @gateway.capture(@amount, '') + response = @gateway.capture(@amount, '') assert_failure response - assert_equal 'REPLACE WITH GATEWAY FAILURE MESSAGE', response.message + assert_equal 'REPLACE WITH FAILED CAPTURE MESSAGE', response.message + end + + def test_successful_refund + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + + assert refund = @gateway.refund(@amount, purchase.authorization) + assert_success refund + assert_equal 'REPLACE WITH SUCCESSFUL REFUND MESSAGE', refund.message + end + + def test_partial_refund + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + + assert refund = @gateway.refund(@amount-1, purchase.authorization) + assert_success refund + end + + def test_failed_refund + response = @gateway.refund(@amount, '') + assert_failure response + assert_equal 'REPLACE WITH FAILED REFUND MESSAGE', response.message + end + + def test_successful_void + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + assert void = @gateway.void(auth.authorization) + assert_success void + assert_equal 'REPLACE WITH SUCCESSFUL VOID MESSAGE', void.message + end + + def test_failed_void + response = @gateway.void('') + assert_failure response + assert_equal 'REPLACE WITH FAILED VOID MESSAGE', response.message + end + + def test_successful_verify + response = @gateway.verify(@credit_card, @options) + assert_success response + assert_match %r{REPLACE WITH SUCCESS MESSAGE}, response.message + end + + def test_failed_verify + response = @gateway.verify(@declined_card, @options) + assert_failure response + assert_match %r{REPLACE WITH FAILED PURCHASE MESSAGE}, response.message end def test_invalid_login - gateway = <%= class_name %>Gateway.new( - :login => '', - :password => '' - ) - assert response = gateway.purchase(@amount, @credit_card, @options) + gateway = <%= class_name %>Gateway.new(login: '', password: '') + + response = gateway.purchase(@amount, @credit_card, @options) assert_failure response - assert_equal 'REPLACE WITH FAILURE MESSAGE', response.message + assert_match %r{REPLACE WITH FAILED LOGIN MESSAGE}, response.message end + + def test_dump_transcript + # This test will run a purchase transaction on your gateway + # and dump a transcript of the HTTP conversation so that + # you can use that transcript as a reference while + # implementing your scrubbing logic. You can delete + # this helper after completing your scrub implementation. + dump_transcript_and_fail(@gateway, @amount, @credit_card, @options) + end + + def test_transcript_scrubbing + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @credit_card, @options) + end + transcript = @gateway.scrub(transcript) + + assert_scrubbed(@credit_card.number, transcript) + assert_scrubbed(@credit_card.verification_value, transcript) + assert_scrubbed(@gateway.options[:password], transcript) + end + end diff --git a/generators/integration/integration_generator.rb b/generators/integration/integration_generator.rb deleted file mode 100644 index 12fa25ba9dd..00000000000 --- a/generators/integration/integration_generator.rb +++ /dev/null @@ -1,25 +0,0 @@ -require "thor/group" - -class IntegrationGenerator < ActiveMerchantGenerator - source_root File.expand_path("..", __FILE__) - - def generate - template "templates/integration.rb", "#{lib}.rb" - template "templates/helper.rb", "#{lib}/helper.rb" - template "templates/notification.rb", "#{lib}/notification.rb" - - template "templates/module_test.rb", "#{test_dir}/#{identifier}_module_test.rb" - template "templates/helper_test.rb", "#{test_dir}/helpers/#{identifier}_helper_test.rb" - template "templates/notification_test.rb", "#{test_dir}/notifications/#{identifier}_notification_test.rb" - end - - protected - - def lib - "lib/active_merchant/billing/integrations/#{identifier}" - end - - def test_dir - "test/unit/integrations" - end -end diff --git a/generators/integration/templates/helper.rb b/generators/integration/templates/helper.rb deleted file mode 100644 index d8fae310dba..00000000000 --- a/generators/integration/templates/helper.rb +++ /dev/null @@ -1,34 +0,0 @@ -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - module <%= class_name %> - class Helper < ActiveMerchant::Billing::Integrations::Helper - # Replace with the real mapping - mapping :account, '' - mapping :amount, '' - - mapping :order, '' - - mapping :customer, :first_name => '', - :last_name => '', - :email => '', - :phone => '' - - mapping :billing_address, :city => '', - :address1 => '', - :address2 => '', - :state => '', - :zip => '', - :country => '' - - mapping :notify_url, '' - mapping :return_url, '' - mapping :cancel_return_url, '' - mapping :description, '' - mapping :tax, '' - mapping :shipping, '' - end - end - end - end -end diff --git a/generators/integration/templates/helper_test.rb b/generators/integration/templates/helper_test.rb deleted file mode 100644 index 1c4d0f7a481..00000000000 --- a/generators/integration/templates/helper_test.rb +++ /dev/null @@ -1,54 +0,0 @@ -require 'test_helper' - -class <%= class_name %>HelperTest < Test::Unit::TestCase - include ActiveMerchant::Billing::Integrations - - def setup - @helper = <%= class_name %>::Helper.new('order-500','cody@example.com', :amount => 500, :currency => 'USD') - end - - def test_basic_helper_fields - assert_field '', 'cody@example.com' - - assert_field '', '5.00' - assert_field '', 'order-500' - end - - def test_customer_fields - @helper.customer :first_name => 'Cody', :last_name => 'Fauser', :email => 'cody@example.com' - assert_field '', 'Cody' - assert_field '', 'Fauser' - assert_field '', 'cody@example.com' - end - - def test_address_mapping - @helper.billing_address :address1 => '1 My Street', - :address2 => '', - :city => 'Leeds', - :state => 'Yorkshire', - :zip => 'LS2 7EE', - :country => 'CA' - - assert_field '', '1 My Street' - assert_field '', 'Leeds' - assert_field '', 'Yorkshire' - assert_field '', 'LS2 7EE' - end - - def test_unknown_address_mapping - @helper.billing_address :farm => 'CA' - assert_equal 3, @helper.fields.size - end - - def test_unknown_mapping - assert_nothing_raised do - @helper.company_address :address => '500 Dwemthy Fox Road' - end - end - - def test_setting_invalid_address_field - fields = @helper.fields.dup - @helper.billing_address :street => 'My Street' - assert_equal fields, @helper.fields - end -end diff --git a/generators/integration/templates/integration.rb b/generators/integration/templates/integration.rb deleted file mode 100644 index 1083c5d9ec3..00000000000 --- a/generators/integration/templates/integration.rb +++ /dev/null @@ -1,18 +0,0 @@ -require File.dirname(__FILE__) + '/<%= identifier %>/helper.rb' -require File.dirname(__FILE__) + '/<%= identifier %>/notification.rb' - -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - module <%= class_name %> - - mattr_accessor :service_url - self.service_url = 'https://www.example.com' - - def self.notification(post) - Notification.new(post) - end - end - end - end -end diff --git a/generators/integration/templates/module_test.rb b/generators/integration/templates/module_test.rb deleted file mode 100644 index 0794e5da149..00000000000 --- a/generators/integration/templates/module_test.rb +++ /dev/null @@ -1,9 +0,0 @@ -require 'test_helper' - -class <%= class_name %>ModuleTest < Test::Unit::TestCase - include ActiveMerchant::Billing::Integrations - - def test_notification_method - assert_instance_of <%= class_name %>::Notification, <%= class_name %>.notification('name=cody') - end -end diff --git a/generators/integration/templates/notification.rb b/generators/integration/templates/notification.rb deleted file mode 100644 index f7114796284..00000000000 --- a/generators/integration/templates/notification.rb +++ /dev/null @@ -1,101 +0,0 @@ -require 'net/http' - -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - module <%= class_name %> - class Notification < ActiveMerchant::Billing::Integrations::Notification - def complete? - params[''] - end - - def item_id - params[''] - end - - def transaction_id - params[''] - end - - # When was this payment received by the client. - def received_at - params[''] - end - - def payer_email - params[''] - end - - def receiver_email - params[''] - end - - def security_key - params[''] - end - - # the money amount we received in X.2 decimal. - def gross - params[''] - end - - # Was this a test transaction? - def test? - params[''] == 'test' - end - - def status - params[''] - end - - # Acknowledge the transaction to <%= class_name %>. This method has to be called after a new - # apc arrives. <%= class_name %> will verify that all the information we received are correct and will return a - # ok or a fail. - # - # Example: - # - # def ipn - # notify = <%= class_name %>Notification.new(request.raw_post) - # - # if notify.acknowledge - # ... process order ... if notify.complete? - # else - # ... log possible hacking attempt ... - # end - def acknowledge - payload = raw - - uri = URI.parse(<%= class_name %>.notification_confirmation_url) - - request = Net::HTTP::Post.new(uri.path) - - request['Content-Length'] = "#{payload.size}" - request['User-Agent'] = "Active Merchant -- http://home.leetsoft.com/am" - request['Content-Type'] = "application/x-www-form-urlencoded" - - http = Net::HTTP.new(uri.host, uri.port) - http.verify_mode = OpenSSL::SSL::VERIFY_NONE unless @ssl_strict - http.use_ssl = true - - response = http.request(request, payload) - - # Replace with the appropriate codes - raise StandardError.new("Faulty <%= class_name %> result: #{response.body}") unless ["AUTHORISED", "DECLINED"].include?(response.body) - response.body == "AUTHORISED" - end - - private - - # Take the posted data and move the relevant data into a hash - def parse(post) - @raw = post.to_s - for line in @raw.split('&') - key, value = *line.scan( %r{^([A-Za-z0-9_.]+)\=(.*)$} ).flatten - params[key] = CGI.unescape(value) - end - end - end - end - end - end -end diff --git a/generators/integration/templates/notification_test.rb b/generators/integration/templates/notification_test.rb deleted file mode 100644 index d9925317be6..00000000000 --- a/generators/integration/templates/notification_test.rb +++ /dev/null @@ -1,41 +0,0 @@ -require 'test_helper' - -class <%= class_name %>NotificationTest < Test::Unit::TestCase - include ActiveMerchant::Billing::Integrations - - def setup - @<%= identifier %> = <%= class_name %>::Notification.new(http_raw_data) - end - - def test_accessors - assert @<%= identifier %>.complete? - assert_equal "", @<%= identifier %>.status - assert_equal "", @<%= identifier %>.transaction_id - assert_equal "", @<%= identifier %>.item_id - assert_equal "", @<%= identifier %>.gross - assert_equal "", @<%= identifier %>.currency - assert_equal "", @<%= identifier %>.received_at - assert @<%= identifier %>.test? - end - - def test_compositions - assert_equal Money.new(3166, 'USD'), @<%= identifier %>.amount - end - - # Replace with real successful acknowledgement code - def test_acknowledgement - - end - - def test_send_acknowledgement - end - - def test_respond_to_acknowledge - assert @<%= identifier %>.respond_to?(:acknowledge) - end - - private - def http_raw_data - "" - end -end diff --git a/init.rb b/init.rb deleted file mode 100644 index bfa250711c5..00000000000 --- a/init.rb +++ /dev/null @@ -1,3 +0,0 @@ -require 'active_merchant' -require 'active_merchant/billing/integrations/action_view_helper' -ActionView::Base.send(:include, ActiveMerchant::Billing::Integrations::ActionViewHelper) diff --git a/lib/active_merchant.rb b/lib/active_merchant.rb index aff818bcb4e..06ab2f0d19d 100644 --- a/lib/active_merchant.rb +++ b/lib/active_merchant.rb @@ -27,37 +27,37 @@ require 'active_support/core_ext/hash/conversions' require 'active_support/core_ext/object/conversions' require 'active_support/core_ext/class/attribute' -require 'active_support/core_ext/class/attribute_accessors' -require 'active_support/core_ext/class/delegating_attributes' +require 'active_support/core_ext/enumerable' require 'active_support/core_ext/module/attribute_accessors' -begin - require 'active_support/base64' - - unless defined?(Base64) - Base64 = ActiveSupport::Base64 - end - - unless Base64.respond_to?(:strict_encode64) - def Base64.strict_encode64(v) - ActiveSupport::Base64.encode64s(v) - end - end -rescue LoadError - require 'base64' -end - +require 'base64' require 'securerandom' require 'builder' require 'cgi' require 'rexml/document' +require 'timeout' +require 'socket' +require 'openssl' + +require 'active_merchant/network_connection_retries' +require 'active_merchant/net_http_ssl_connection' +require 'active_merchant/connection' +require 'active_merchant/post_data' +require 'active_merchant/posts_data' -require 'active_utils' require 'active_merchant/billing' require 'active_merchant/version' +require 'active_merchant/country' -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - autoload :Integrations, 'active_merchant/billing/integrations' +module ActiveMerchant + def self.deprecated(message, caller=Kernel.caller[1]) + warning = caller + ': ' + message + if(respond_to?(:logger) && logger.present?) + logger.warn(warning) + else + warn(warning) + end end end + +I18n.enforce_available_locales = false diff --git a/lib/active_merchant/billing.rb b/lib/active_merchant/billing.rb index ee144b1f36a..ea3108597c8 100644 --- a/lib/active_merchant/billing.rb +++ b/lib/active_merchant/billing.rb @@ -1,9 +1,15 @@ +require 'active_merchant/errors' + require 'active_merchant/billing/avs_result' require 'active_merchant/billing/cvv_result' require 'active_merchant/billing/credit_card_methods' require 'active_merchant/billing/credit_card_formatting' require 'active_merchant/billing/credit_card' +require 'active_merchant/billing/network_tokenization_credit_card' require 'active_merchant/billing/base' require 'active_merchant/billing/check' +require 'active_merchant/billing/payment_token' +require 'active_merchant/billing/apple_pay_payment_token' require 'active_merchant/billing/response' -require 'active_merchant/billing/gateways' \ No newline at end of file +require 'active_merchant/billing/gateways' +require 'active_merchant/billing/gateway' diff --git a/lib/active_merchant/billing/apple_pay_payment_token.rb b/lib/active_merchant/billing/apple_pay_payment_token.rb new file mode 100644 index 00000000000..a3e7e98388a --- /dev/null +++ b/lib/active_merchant/billing/apple_pay_payment_token.rb @@ -0,0 +1,22 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class ApplePayPaymentToken < PaymentToken + # This is a representation of the token object specified here: + # https://developer.apple.com/library/ios/documentation/PassKit/Reference/PKPaymentToken_Ref/ + # https://developer.apple.com/library/IOs//documentation/PassKit/Reference/PaymentTokenJSON/PaymentTokenJSON.html + + attr_reader :payment_instrument_name, :payment_network + attr_accessor :transaction_identifier + + def initialize(payment_data, options = {}) + super + @payment_instrument_name = @metadata[:payment_instrument_name] + @payment_network = @metadata[:payment_network] + end + + def type + 'apple_pay' + end + end + end +end diff --git a/lib/active_merchant/billing/avs_result.rb b/lib/active_merchant/billing/avs_result.rb index 527c3efa119..77f9ebf3727 100644 --- a/lib/active_merchant/billing/avs_result.rb +++ b/lib/active_merchant/billing/avs_result.rb @@ -1,17 +1,15 @@ -#!ruby19 # encoding: utf-8 module ActiveMerchant - module Billing + module Billing # Implements the Address Verification System - # https://www.wellsfargo.com/downloads/pdf/biz/merchant/visa_avs.pdf + # https://www.cybersource.com/developers/other_resources/quick_references/avs_results/. # http://en.wikipedia.org/wiki/Address_Verification_System - # http://apps.cybersource.com/library/documentation/dev_guides/CC_Svcs_IG/html/app_avs_cvn_codes.htm#app_AVS_CVN_codes_7891_48375 - # http://imgserver.skipjack.com/imgServer/5293710/AVS%20and%20CVV2.pdf # http://www.emsecommerce.net/avs_cvv2_response_codes.htm + # https://www.cardfellow.com/blog/address-verification-service-avs/ class AVSResult MESSAGES = { - 'A' => 'Street address matches, but 5-digit and 9-digit postal code do not match.', + 'A' => 'Street address matches, but postal code does not match.', 'B' => 'Street address matches, but postal code not verified.', 'C' => 'Street address and postal code do not match.', 'D' => 'Street address and postal code match.', @@ -24,7 +22,7 @@ class AVSResult 'K' => 'Card member\'s name matches but billing address and billing postal code do not match.', 'L' => 'Card member\'s name and billing postal code match, but billing address does not match.', 'M' => 'Street address and postal code match.', - 'N' => 'Street address and postal code do not match.', + 'N' => 'Street address and postal code do not match. For American Express: Card member\'s name, street address and postal code do not match.', 'O' => 'Card member\'s name and billing address match, but billing postal code does not match.', 'P' => 'Postal code matches, but street address not verified.', 'Q' => 'Card member\'s name, billing address, and postal code match. Shipping information verified but chargeback protection not guaranteed.', @@ -38,7 +36,7 @@ class AVSResult 'Y' => 'Street address and 5-digit postal code match.', 'Z' => 'Street address does not match, but 5-digit postal code matches.' } - + # Map vendor's AVS result code to a postal match code POSTAL_MATCH_CODE = { 'Y' => %w( D H F H J L M P Q V W X Y Z ), @@ -49,7 +47,7 @@ class AVSResult codes.each { |code| map[code] = type } map end - + # Map vendor's AVS result code to a street match code STREET_MATCH_CODE = { 'Y' => %w( A B D H J M O Q T V X Y ), @@ -60,32 +58,32 @@ class AVSResult codes.each { |code| map[code] = type } map end - + attr_reader :code, :message, :street_match, :postal_match - + def self.messages MESSAGES end - + def initialize(attrs) attrs ||= {} - + @code = attrs[:code].upcase unless attrs[:code].blank? @message = self.class.messages[code] - + if attrs[:street_match].blank? @street_match = STREET_MATCH_CODE[code] - else + else @street_match = attrs[:street_match].upcase end - + if attrs[:postal_match].blank? @postal_match = POSTAL_MATCH_CODE[code] - else + else @postal_match = attrs[:postal_match].upcase end end - + def to_hash { 'code' => code, 'message' => message, diff --git a/lib/active_merchant/billing/base.rb b/lib/active_merchant/billing/base.rb index 719480c5f6c..5392189e46b 100644 --- a/lib/active_merchant/billing/base.rb +++ b/lib/active_merchant/billing/base.rb @@ -1,24 +1,21 @@ module ActiveMerchant #:nodoc: module Billing #:nodoc: module Base - # Set ActiveMerchant gateways in test mode. - # - # ActiveMerchant::Billing::Base.gateway_mode = :test - mattr_accessor :gateway_mode + GATEWAY_MODE_DEPRECATION_MESSAGE = 'Base#gateway_mode is deprecated in favor of Base#mode and will be removed in a future version' - # Set ActiveMerchant integrations in test mode. + # Set ActiveMerchant gateways in test mode. # - # ActiveMerchant::Billing::Base.integration_mode = :test - mattr_accessor :integration_mode - - # Set both the mode of both the gateways and integrations - # at once - mattr_reader :mode + # ActiveMerchant::Billing::Base.mode = :test + mattr_accessor :mode - def self.mode=(mode) + def self.gateway_mode=(mode) + ActiveMerchant.deprecated(GATEWAY_MODE_DEPRECATION_MESSAGE) @@mode = mode - self.gateway_mode = mode - self.integration_mode = mode + end + + def self.gateway_mode + ActiveMerchant.deprecated(GATEWAY_MODE_DEPRECATION_MESSAGE) + @@mode end self.mode = :production @@ -31,8 +28,15 @@ def self.mode=(mode) # # ActiveMerchant::Billing::Base.gateway('moneris').new def self.gateway(name) - raise NameError if (name_str = name.to_s.downcase).blank? - Billing.const_get("#{name_str}_gateway".camelize) + name_str = name.to_s.strip.downcase + + raise(ArgumentError, 'A gateway provider must be specified') if name_str.blank? + + begin + Billing.const_get("#{name_str}_gateway".camelize) + rescue + raise ArgumentError, "The specified gateway is not valid (#{name_str})" + end end # Return the matching integration module @@ -45,12 +49,12 @@ def self.gateway(name) # notification = chronopay.notification(raw_post) # def self.integration(name) - Billing::Integrations.const_get("#{name.to_s.downcase}".camelize) + Billing::Integrations.const_get(name.to_s.downcase.camelize) end # A check to see if we're in test mode def self.test? - self.gateway_mode == :test + mode == :test end end end diff --git a/lib/active_merchant/billing/check.rb b/lib/active_merchant/billing/check.rb index 79e99d2ee6a..6dcba4b0267 100644 --- a/lib/active_merchant/billing/check.rb +++ b/lib/active_merchant/billing/check.rb @@ -5,22 +5,20 @@ module Billing #:nodoc: # not backed by any database. # # You may use Check in place of CreditCard with any gateway that supports it. - class Check - include Validateable - + class Check < Model attr_accessor :first_name, :last_name, - :bank_name, :routing_number, :account_number, - :account_holder_type, :account_type, :number + :bank_name, :routing_number, :account_number, + :account_holder_type, :account_type, :number # Used for Canadian bank accounts attr_accessor :institution_number, :transit_number def name - @name ||= "#{@first_name} #{@last_name}".strip + @name ||= "#{first_name} #{last_name}".strip end def name=(value) - return if value.blank? + return if empty?(value) @name = value segments = value.split(' ') @@ -29,39 +27,52 @@ def name=(value) end def validate + errors = [] + [:name, :routing_number, :account_number].each do |attr| - errors.add(attr, "cannot be empty") if self.send(attr).blank? + errors << [attr, 'cannot be empty'] if empty?(self.send(attr)) end - errors.add(:routing_number, "is invalid") unless valid_routing_number? + errors << [:routing_number, 'is invalid'] unless valid_routing_number? - errors.add(:account_holder_type, "must be personal or business") if - !account_holder_type.blank? && !%w[business personal].include?(account_holder_type.to_s) + if(!empty?(account_holder_type) && !%w[business personal].include?(account_holder_type.to_s)) + errors << [:account_holder_type, 'must be personal or business'] + end - errors.add(:account_type, "must be checking or savings") if - !account_type.blank? && !%w[checking savings].include?(account_type.to_s) + if(!empty?(account_type) && !%w[checking savings].include?(account_type.to_s)) + errors << [:account_type, 'must be checking or savings'] + end + + errors_hash(errors) end def type 'check' end + def credit_card? + false + end + # Routing numbers may be validated by calculating a checksum and dividing it by 10. The # formula is: # (3(d1 + d4 + d7) + 7(d2 + d5 + d8) + 1(d3 + d6 + d9))mod 10 = 0 # See http://en.wikipedia.org/wiki/Routing_transit_number#Internal_checksums def valid_routing_number? - d = routing_number.to_s.split('').map(&:to_i).select { |d| (0..9).include?(d) } - case d.size - when 9 then - checksum = ((3 * (d[0] + d[3] + d[6])) + - (7 * (d[1] + d[4] + d[7])) + - (d[2] + d[5] + d[8])) % 10 - case checksum - when 0 then true - else false - end - else false + digits = routing_number.to_s.split('').map(&:to_i).select { |d| (0..9).cover?(d) } + case digits.size + when 9 + checksum = ((3 * (digits[0] + digits[3] + digits[6])) + + (7 * (digits[1] + digits[4] + digits[7])) + + (digits[2] + digits[5] + digits[8])) % 10 + case checksum + when 0 + true + else + false + end + else + false end end end diff --git a/lib/active_merchant/billing/compatibility.rb b/lib/active_merchant/billing/compatibility.rb new file mode 100644 index 00000000000..11103dcee68 --- /dev/null +++ b/lib/active_merchant/billing/compatibility.rb @@ -0,0 +1,117 @@ +module ActiveMerchant + module Billing + module Compatibility + module Model + def valid? + Compatibility.deprecated + super + end + + def errors + Compatibility.deprecated + internal_errors + end + end + + @rails_required = false + def self.rails_required! + @rails_required = true + end + + def self.deprecated + ActiveMerchant.deprecated( + %(Implicit inclusion of Rails-specific functionality is deprecated.) + + %( Explicitly require "active_merchant/billing/rails" if you need it.) + ) unless @rails_required + end + + def self.humanize(lower_case_and_underscored_word) + result = lower_case_and_underscored_word.to_s.dup + result.gsub!(/_id$/, '') + result.gsub!(/_/, ' ') + result.gsub(/([a-z\d]*)/i, &:downcase).gsub(/^\w/) { $&.upcase } + end + end + end +end + +# This lives in compatibility until we remove the deprecation for implicitly +# requiring Rails +module ActiveMerchant + module Billing + module Rails + module Model + def valid? + internal_errors.clear + + validate.each do |attribute, errors| + errors.each do |error| + internal_errors.add(attribute, error) + end + end + + internal_errors.empty? + end + + private + + def internal_errors + @errors ||= Errors.new + end + + class Errors < Hash + def initialize + super() { |h, k| h[k] = [] } + end + + alias count size + + def [](key) + super(key.to_s) + end + + def []=(key, value) + super(key.to_s, value) + end + + def empty? + all? { |k, v| v&.empty? } + end + + def on(field) + self[field].to_a.first + end + + def add(field, error) + self[field] << error + end + + def add_to_base(error) + add(:base, error) + end + + def each_full + full_messages.each { |msg| yield msg } + end + + def full_messages + result = [] + + self.each do |key, messages| + next unless(messages && !messages.empty?) + if key == 'base' + result << messages.first.to_s + else + result << "#{Compatibility.humanize(key)} #{messages.first}" + end + end + + result + end + end + end + end + + Compatibility::Model.send(:include, Rails::Model) + end +end diff --git a/lib/active_merchant/billing/credit_card.rb b/lib/active_merchant/billing/credit_card.rb index cb8d9ed491c..983e3edc384 100644 --- a/lib/active_merchant/billing/credit_card.rb +++ b/lib/active_merchant/billing/credit_card.rb @@ -1,6 +1,6 @@ require 'time' require 'date' -require 'active_merchant/billing/expiry_date' +require 'active_merchant/billing/model' module ActiveMerchant #:nodoc: module Billing #:nodoc: @@ -15,12 +15,13 @@ module Billing #:nodoc: # * American Express # * Diner's Club # * JCB - # * Switch - # * Solo # * Dankort # * Maestro # * Forbrugsforeningen - # * Laser + # * Elo + # * Alelo + # * Cabal + # * Naranja # # For testing purposes, use the 'bogus' credit card brand. This skips the vast majority of # validations, allowing you to focus on your core concerns until you're ready to be more concerned @@ -35,38 +36,48 @@ module Billing #:nodoc: # # == Example Usage # cc = CreditCard.new( - # :first_name => 'Steve', - # :last_name => 'Smith', - # :month => '9', - # :year => '2010', - # :brand => 'visa', - # :number => '4242424242424242' + # :first_name => 'Steve', + # :last_name => 'Smith', + # :month => '9', + # :year => '2017', + # :brand => 'visa', + # :number => '4242424242424242', + # :verification_value => '424' # ) # - # cc.valid? # => true + # cc.validate # => {} # cc.display_number # => XXXX-XXXX-XXXX-4242 # - class CreditCard + class CreditCard < Model include CreditCardMethods - include Validateable - cattr_accessor :require_verification_value + class << self + # Inherited, but can be overridden w/o changing parent's value + attr_accessor :require_verification_value + attr_accessor :require_name + end + + self.require_name = true self.require_verification_value = true # Returns or sets the credit card number. # # @return [String] - attr_accessor :number + attr_reader :number + + def number=(value) + @number = (empty?(value) ? value : value.to_s.gsub(/[^\d]/, '')) + end # Returns or sets the expiry month for the card. # # @return [Integer] - attr_accessor :month + attr_reader :month # Returns or sets the expiry year for the card. # # @return [Integer] - attr_accessor :year + attr_reader :year # Returns or sets the credit card brand. # @@ -78,17 +89,29 @@ class CreditCard # * +'american_express'+ # * +'diners_club'+ # * +'jcb'+ - # * +'switch'+ - # * +'solo'+ # * +'dankort'+ # * +'maestro'+ # * +'forbrugsforeningen'+ - # * +'laser'+ + # * +'elo'+ + # * +'alelo'+ + # * +'cabal'+ + # * +'naranja'+ # # Or, if you wish to test your implementation, +'bogus'+. # # @return (String) the credit card brand - attr_accessor :brand + def brand + if !defined?(@brand) || empty?(@brand) + self.class.brand?(number) + else + @brand + end + end + + def brand=(value) + value = value && value.to_s.dup + @brand = (value.respond_to?(:downcase) ? value.downcase : value) + end # Returns or sets the first name of the card holder. # @@ -100,9 +123,6 @@ class CreditCard # @return [String] attr_accessor :last_name - # Required for Switch / Solo cards - attr_accessor :start_month, :start_year, :issue_number - # Returns or sets the card verification value. # # This attribute is optional but recommended. The verification value is @@ -112,18 +132,80 @@ class CreditCard # @return [String] the verification value attr_accessor :verification_value + # Sets if the credit card requires a verification value. + # + # @return [Boolean] + def require_verification_value=(value) + @require_verification_value_set = true + @require_verification_value = value + end + + # Returns if this credit card needs a verification value. + # + # By default this returns the configured value from `CreditCard.require_verification_value`, + # but one can set a per instance requirement with `credit_card.require_verification_value = false`. + # + # @return [Boolean] + def requires_verification_value? + @require_verification_value_set ||= false + if @require_verification_value_set + @require_verification_value + else + self.class.requires_verification_value? + end + end + # Returns or sets the track data for the card # # @return [String] attr_accessor :track_data + # Returns or sets whether a card has been processed using manual entry. + # + # This attribute is optional and is only used by gateways who use this information in their transaction risk + # calculations. See {this page on 'card not present' transactions}[http://en.wikipedia.org/wiki/Card_not_present_transaction] + # for further explanation and examples of this kind of transaction. + # + # @return [true, false] + attr_accessor :manual_entry + + # Returns or sets the ICC/ASN1 credit card data for a EMV transaction, typically this is a BER-encoded TLV string. + # + # @return [String] + attr_accessor :icc_data + + # Returns or sets information about the source of the card data. + # + # @return [String] + attr_accessor :read_method + + READ_METHOD_DESCRIPTIONS = { + nil => 'A card reader was not used.', + 'fallback_no_chip' => 'Magstripe was read because the card has no chip.', + 'fallback_chip_error' => "Magstripe was read because the card's chip failed.", + 'contactless' => 'Data was read by a Contactless EMV kernel. Issuer script results are not available.', + 'contactless_magstripe' => 'Contactless data was read with a non-EMV protocol.', + 'contact' => 'Data was read using the EMV protocol. Issuer script results may follow.', + 'contact_quickchip' => 'Data was read by the Quickchip EMV kernel. Issuer script results are not available.', + } + + # Returns the ciphertext of the card's encrypted PIN. + # + # @return [String] + attr_accessor :encrypted_pin_cryptogram + + # Returns the Key Serial Number (KSN) of the card's encrypted PIN. + # + # @return [String] + attr_accessor :encrypted_pin_ksn + def type - self.class.deprecated "CreditCard#type is deprecated and will be removed from a future release of ActiveMerchant. Please use CreditCard#brand instead." + ActiveMerchant.deprecated 'CreditCard#type is deprecated and will be removed from a future release of ActiveMerchant. Please use CreditCard#brand instead.' brand end def type=(value) - self.class.deprecated "CreditCard#type is deprecated and will be removed from a future release of ActiveMerchant. Please use CreditCard#brand instead." + ActiveMerchant.deprecated 'CreditCard#type is deprecated and will be removed from a future release of ActiveMerchant. Please use CreditCard#brand instead.' self.brand = value end @@ -148,29 +230,42 @@ def name? # Returns whether the +first_name+ attribute has been set. def first_name? - @first_name.present? + first_name.present? end # Returns whether the +last_name+ attribute has been set. def last_name? - @last_name.present? + last_name.present? end # Returns the full name of the card holder. # # @return [String] the full name of the card holder def name - [@first_name, @last_name].compact.join(' ') + "#{first_name} #{last_name}".strip end def name=(full_name) names = full_name.split self.last_name = names.pop - self.first_name = names.join(" ") + self.first_name = names.join(' ') + end + + %w(month year start_month start_year).each do |m| + class_eval %( + def #{m}=(v) + @#{m} = case v + when "", nil, 0 + nil + else + v.to_i + end + end + ) end def verification_value? - !@verification_value.blank? + !verification_value.blank? end # Returns a display-friendly version of the card number. @@ -199,83 +294,111 @@ def last_digits # # Any validation errors are added to the {#errors} attribute. def validate - validate_essential_attributes + errors = validate_essential_attributes + validate_verification_value # Bogus card is pretty much for testing purposes. Lets just skip these extra tests if its used - return if brand == 'bogus' + return errors_hash(errors) if brand == 'bogus' - validate_card_brand - validate_card_number - validate_verification_value - validate_switch_or_solo_attributes + errors_hash( + errors + + validate_card_brand_and_number + ) end def self.requires_verification_value? require_verification_value end - private + def self.requires_name? + require_name + end - def before_validate #:nodoc: - self.month = month.to_i - self.year = year.to_i - self.start_month = start_month.to_i unless start_month.nil? - self.start_year = start_year.to_i unless start_year.nil? - self.number = number.to_s.gsub(/[^\d]/, "") - self.brand.downcase! if brand.respond_to?(:downcase) - self.brand = self.class.brand?(number) if brand.blank? + def emv? + icc_data.present? end - def validate_card_number #:nodoc: - if number.blank? - errors.add :number, "is required" - elsif !CreditCard.valid_number?(number) - errors.add :number, "is not a valid credit card number" + private + + def validate_essential_attributes #:nodoc: + errors = [] + + if self.class.requires_name? + errors << [:first_name, 'cannot be empty'] if first_name.blank? + errors << [:last_name, 'cannot be empty'] if last_name.blank? end - unless errors.on(:number) || errors.on(:brand) - errors.add :brand, "does not match the card number" unless CreditCard.matching_brand?(number, brand) + if(empty?(month) || empty?(year)) + errors << [:month, 'is required'] if empty?(month) + errors << [:year, 'is required'] if empty?(year) + else + errors << [:month, 'is not a valid month'] if !valid_month?(month) + + if expired? + errors << [:year, 'expired'] + else + errors << [:year, 'is not a valid year'] if !valid_expiry_year?(year) + end end - end - def validate_card_brand #:nodoc: - errors.add :brand, "is required" if brand.blank? && number.present? - errors.add :brand, "is invalid" unless brand.blank? || CreditCard.card_companies.keys.include?(brand) + errors end - alias_method :validate_card_type, :validate_card_brand + def validate_card_brand_and_number #:nodoc: + errors = [] - def validate_essential_attributes #:nodoc: - errors.add :first_name, "cannot be empty" if @first_name.blank? - errors.add :last_name, "cannot be empty" if @last_name.blank? + if !empty?(brand) + errors << [:brand, 'is invalid'] if !CreditCard.card_companies.include?(brand) + end - if @month.to_i.zero? || @year.to_i.zero? - errors.add :month, "is required" if @month.to_i.zero? - errors.add :year, "is required" if @year.to_i.zero? - else - errors.add :month, "is not a valid month" unless valid_month?(@month) - errors.add :year, "expired" if expired? - errors.add :year, "is not a valid year" unless expired? || valid_expiry_year?(@year) + if empty?(number) + errors << [:number, 'is required'] + elsif !CreditCard.valid_number?(number) + errors << [:number, 'is not a valid credit card number'] end + + if errors.empty? + errors << [:brand, 'does not match the card number'] if !CreditCard.matching_brand?(number, brand) + end + + errors end - def validate_switch_or_solo_attributes #:nodoc: - if %w[switch solo].include?(brand) - unless valid_month?(@start_month) && valid_start_year?(@start_year) || valid_issue_number?(@issue_number) - if @issue_number.blank? - errors.add :start_month, "is invalid" unless valid_month?(@start_month) - errors.add :start_year, "is invalid" unless valid_start_year?(@start_year) - errors.add :issue_number, "cannot be empty" - else - errors.add :issue_number, "is invalid" unless valid_issue_number?(@issue_number) - end + def validate_verification_value #:nodoc: + errors = [] + + if verification_value? + unless valid_card_verification_value?(verification_value, brand) + errors << [:verification_value, "should be #{card_verification_value_length(brand)} digits"] end + elsif requires_verification_value? && !valid_card_verification_value?(verification_value, brand) + errors << [:verification_value, 'is required'] end + errors end - def validate_verification_value #:nodoc: - if CreditCard.requires_verification_value? - errors.add :verification_value, "is required" unless verification_value? + class ExpiryDate #:nodoc: + attr_reader :month, :year + def initialize(month, year) + @month = month.to_i + @year = year.to_i + end + + def expired? #:nodoc: + Time.now.utc > expiration + end + + def expiration #:nodoc: + Time.utc(year, month, month_days, 23, 59, 59) + rescue ArgumentError + Time.at(0).utc + end + + private + + def month_days + mdays = [nil, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] + mdays[2] = 29 if Date.leap?(year) + mdays[month] end end end diff --git a/lib/active_merchant/billing/credit_card_formatting.rb b/lib/active_merchant/billing/credit_card_formatting.rb index 126207b147f..2a55bae60ad 100644 --- a/lib/active_merchant/billing/credit_card_formatting.rb +++ b/lib/active_merchant/billing/credit_card_formatting.rb @@ -1,6 +1,9 @@ module ActiveMerchant #:nodoc: module Billing #:nodoc: module CreditCardFormatting + def expdate(credit_card) + "#{format(credit_card.month, :two_digits)}#{format(credit_card.year, :two_digits)}" + end # This method is used to format numerical information pertaining to credit cards. # @@ -10,12 +13,11 @@ def format(number, option) return '' if number.blank? case option - when :two_digits ; sprintf("%.2i", number.to_i)[-2..-1] - when :four_digits ; sprintf("%.4i", number.to_i)[-4..-1] - else number + when :two_digits then sprintf('%.2i', number.to_i)[-2..-1] + when :four_digits then sprintf('%.4i', number.to_i)[-4..-1] + else number end end - end end end diff --git a/lib/active_merchant/billing/credit_card_methods.rb b/lib/active_merchant/billing/credit_card_methods.rb index fef4bf16c52..8cf91c5297c 100644 --- a/lib/active_merchant/billing/credit_card_methods.rb +++ b/lib/active_merchant/billing/credit_card_methods.rb @@ -2,140 +2,340 @@ module ActiveMerchant #:nodoc: module Billing #:nodoc: # Convenience methods that can be included into a custom Credit Card object, such as an ActiveRecord based Credit Card object. module CreditCardMethods - CARD_COMPANIES = { - 'visa' => /^4\d{12}(\d{3})?$/, - 'master' => /^(5[1-5]\d{4}|677189)\d{10}$/, - 'discover' => /^(6011|65\d{2}|64[4-9]\d)\d{12}|(62\d{14})$/, - 'american_express' => /^3[47]\d{13}$/, - 'diners_club' => /^3(0[0-5]|[68]\d)\d{11}$/, - 'jcb' => /^35(28|29|[3-8]\d)\d{12}$/, - 'switch' => /^6759\d{12}(\d{2,3})?$/, - 'solo' => /^6767\d{12}(\d{2,3})?$/, - 'dankort' => /^5019\d{12}$/, - 'maestro' => /^(5[06-8]|6\d)\d{10,17}$/, - 'forbrugsforeningen' => /^600722\d{10}$/, - 'laser' => /^(6304|6706|6709|6771(?!89))\d{8}(\d{4}|\d{6,7})?$/ + CARD_COMPANY_DETECTORS = { + 'visa' => ->(num) { num =~ /^4\d{12}(\d{3})?(\d{3})?$/ }, + 'master' => ->(num) { num&.size == 16 && in_bin_range?(num.slice(0, 6), MASTERCARD_RANGES) }, + 'elo' => ->(num) { num&.size == 16 && in_bin_range?(num.slice(0, 6), ELO_RANGES) }, + 'alelo' => ->(num) { num&.size == 16 && in_bin_range?(num.slice(0, 6), ALELO_RANGES) }, + 'discover' => ->(num) { num =~ /^(6011|65\d{2}|64[4-9]\d)\d{12,15}|(62\d{14,17})$/ }, + 'american_express' => ->(num) { num =~ /^3[47]\d{13}$/ }, + 'naranja' => ->(num) { num&.size == 16 && in_bin_range?(num.slice(0, 6), NARANJA_RANGES) }, + 'diners_club' => ->(num) { num =~ /^3(0[0-5]|[68]\d)\d{11}$/ }, + 'jcb' => ->(num) { num =~ /^35(28|29|[3-8]\d)\d{12}$/ }, + 'dankort' => ->(num) { num =~ /^5019\d{12}$/ }, + 'maestro' => ->(num) { (12..19).cover?(num&.size) && in_bin_range?(num.slice(0, 6), MAESTRO_RANGES) }, + 'forbrugsforeningen' => ->(num) { num =~ /^600722\d{10}$/ }, + 'sodexo' => ->(num) { num =~ /^(606071|603389|606070|606069|606068|600818)\d{10}$/ }, + 'vr' => ->(num) { num =~ /^(627416|637036)\d{10}$/ }, + 'cabal' => ->(num) { num&.size == 16 && in_bin_range?(num.slice(0, 8), CABAL_RANGES) }, + 'carnet' => lambda { |num| + num&.size == 16 && ( + in_bin_range?(num.slice(0, 6), CARNET_RANGES) || + CARNET_BINS.any? { |bin| num.slice(0, bin.size) == bin } + ) + } } - + + # http://www.barclaycard.co.uk/business/files/bin_rules.pdf + ELECTRON_RANGES = [ + [400115], + (400837..400839), + (412921..412923), + [417935], + (419740..419741), + (419773..419775), + [424519], + (424962..424963), + [437860], + [444000], + [459472], + (484406..484411), + (484413..484414), + (484418..484418), + (484428..484455), + (491730..491759), + ] + + CARNET_RANGES = [ + (506199..506499), + ] + + CARNET_BINS = Set.new( + [ + '286900', '502275', '606333', '627535', '636318', '636379', '639388', + '639484', '639559', '50633601', '50633606', '58877274', '62753500', + '60462203', '60462204', '588772' + ] + ) + + # https://www.mastercard.us/content/dam/mccom/global/documents/mastercard-rules.pdf, page 73 + MASTERCARD_RANGES = [ + (222100..272099), + (510000..559999), + ] + + # https://www.mastercard.us/content/dam/mccom/global/documents/mastercard-rules.pdf, page 73 + MAESTRO_RANGES = [ + (561200..561269), + (561271..561299), + (561320..561356), + (581700..581751), + (581753..581800), + (589998..591259), + (591261..596770), + (596772..598744), + (598746..599999), + (600297..600314), + (600316..600335), + (600337..600362), + (600364..600382), + (601232..601254), + (601256..601276), + (601640..601652), + (601689..601700), + (602011..602050), + (639000..639099), + (670000..679999), + ] + + # https://dev.elo.com.br/apis/tabela-de-bins, download csv from left sidebar + ELO_RANGES = [ + 506707..506708, 506715..506715, 506718..506722, 506724..506724, 506726..506736, 506739..506739, 506741..506743, + 506745..506747, 506753..506753, 506774..506776, 506778..506778, 509000..509001, 509003..509003, 509007..509007, + 509020..509022, 509035..509035, 509039..509042, 509045..509045, 509048..509048, 509051..509071, 509073..509074, + 509077..509080, 509084..509084, 509091..509094, 509098..509098, 509100..509100, 509104..509104, 509106..509109, + 627780..627780, 636368..636368, 650031..650033, 650035..650045, 650047..650047, 650406..650410, 650434..650436, + 650439..650439, 650485..650504, 650506..650530, 650577..650580, 650582..650591, 650721..650727, 650901..650922, + 650928..650928, 650938..650939, 650946..650948, 650954..650955, 650962..650963, 650967..650967, 650971..650971, + 651652..651667, 651675..651678, 655000..655010, 655012..655015, 655051..655052, 655056..655057 + ] + + # Alelo provides BIN ranges by e-mailing them out periodically. + # The BINs beginning with the digit 4 overlap with Visa's range of valid card numbers. + # By placing the 'alelo' entry in CARD_COMPANY_DETECTORS below the 'visa' entry, we + # identify these cards as Visa. This works because transactions with such cards will + # run on Visa rails. + ALELO_RANGES = [ + 402588..402588, 404347..404347, 405876..405876, 405882..405882, 405884..405884, + 405886..405886, 430471..430471, 438061..438061, 438064..438064, 470063..470066, + 496067..496067, 506699..506704, 506706..506706, 506713..506714, 506716..506716, + 506749..506750, 506752..506752, 506754..506756, 506758..506762, 506764..506767, + 506770..506771, 509015..509019, 509880..509882, 509884..509885, 509987..509988 + ] + + CABAL_RANGES = [ + 60420100..60440099, + 58965700..58965799, + 60352200..60352299 + ] + + NARANJA_RANGES = [ + 589562..589562 + ] + def self.included(base) base.extend(ClassMethods) end - + + def self.in_bin_range?(number, ranges) + bin = number.to_i + ranges.any? do |range| + range.cover?(bin) + end + end + def valid_month?(month) - (1..12).include?(month.to_i) + (1..12).cover?(month.to_i) end - + + def credit_card? + true + end + def valid_expiry_year?(year) - (Time.now.year..Time.now.year + 20).include?(year.to_i) + (Time.now.year..Time.now.year + 20).cover?(year.to_i) end - + def valid_start_year?(year) - year.to_s =~ /^\d{4}$/ && year.to_i > 1987 + ((year.to_s =~ /^\d{4}$/) && (year.to_i > 1987)) end - + + # Credit card providers have 3 digit verification values + # This isn't standardised, these are called various names such as + # CVC, CVV, CID, CSC and more + # See: http://en.wikipedia.org/wiki/Card_security_code + # American Express is the exception with 4 digits + # + # Below are links from the card providers with their requirements + # visa: http://usa.visa.com/personal/security/3-digit-security-code.jsp + # master: http://www.mastercard.com/ca/merchant/en/getstarted/Anatomy_MasterCard.html + # jcb: http://www.jcbcard.com/security/info.html + # diners_club: http://www.dinersclub.com/assets/DinersClub_card_ID_features.pdf + # discover: https://www.discover.com/credit-cards/help-center/glossary.html + # american_express: https://online.americanexpress.com/myca/fuidfyp/us/action?request_type=un_fuid&Face=en_US + def valid_card_verification_value?(cvv, brand) + cvv.to_s =~ /^\d{#{card_verification_value_length(brand)}}$/ + end + + def card_verification_value_length(brand) + case brand + when 'american_express' + 4 + when 'maestro' + 0 + else + 3 + end + end + def valid_issue_number?(number) - number.to_s =~ /^\d{1,2}$/ + (number.to_s =~ /^\d{1,2}$/) end - + + # Returns if the card matches known Electron BINs + def electron? + self.class.electron?(number) + end + module ClassMethods - # Returns true if it validates. Optionally, you can pass a card brand as an argument and + # Returns true if it validates. Optionally, you can pass a card brand as an argument and # make sure it is of the correct brand. # # References: # - http://perl.about.com/compute/perl/library/nosearch/P073000.htm # - http://www.beachnet.com/~hstiles/cardtype.html def valid_number?(number) - valid_test_mode_card_number?(number) || - valid_card_number_length?(number) && - valid_checksum?(number) - end - - # Regular expressions for the known card companies. - # - # References: - # - http://en.wikipedia.org/wiki/Credit_card_number - # - http://www.barclaycardbusiness.co.uk/information_zone/processing/bin_rules.html + valid_test_mode_card_number?(number) || + valid_card_number_length?(number) && + valid_card_number_characters?(number) && + valid_by_algorithm?(brand?(number), number) + end + def card_companies - CARD_COMPANIES + CARD_COMPANY_DETECTORS.keys end - + # Returns a string containing the brand of card from the list of known information below. - # Need to check the cards in a particular order, as there is some overlap of the allowable ranges - #-- - # TODO Refactor this method. We basically need to tighten up the Maestro Regexp. - # - # Right now the Maestro regexp overlaps with the MasterCard regexp (IIRC). If we can tighten - # things up, we can boil this whole thing down to something like... - # - # def brand?(number) - # return 'visa' if valid_test_mode_card_number?(number) - # card_companies.find([nil]) { |brand, regexp| number =~ regexp }.first.dup - # end - # def brand?(number) return 'bogus' if valid_test_mode_card_number?(number) - card_companies.reject { |c,p| c == 'maestro' }.each do |company, pattern| - return company.dup if number =~ pattern + CARD_COMPANY_DETECTORS.each do |company, func| + return company.dup if func.call(number) end - - return 'maestro' if number =~ card_companies['maestro'] return nil end + def electron?(number) + return false unless [16, 19].include?(number&.length) + + # don't recalculate for each range + bank_identification_number = first_digits(number).to_i + + ELECTRON_RANGES.any? do |range| + range.include?(bank_identification_number) + end + end + def type?(number) - deprecated "CreditCard#type? is deprecated and will be removed from a future release of ActiveMerchant. Please use CreditCard#brand? instead." + ActiveMerchant.deprecated 'CreditCard#type? is deprecated and will be removed from a future release of ActiveMerchant. Please use CreditCard#brand? instead.' brand?(number) end - + def first_digits(number) - number.to_s.slice(0,6) + number&.slice(0, 6) || '' end - - def last_digits(number) - number.to_s.length <= 4 ? number : number.to_s.slice(-4..-1) + + def last_digits(number) + return '' if number.nil? + number.length <= 4 ? number : number.slice(-4..-1) end - + def mask(number) "XXXX-XXXX-XXXX-#{last_digits(number)}" end - + # Checks to see if the calculated brand matches the specified brand def matching_brand?(number, brand) brand?(number) == brand end def matching_type?(number, brand) - deprecated "CreditCard#matching_type? is deprecated and will be removed from a future release of ActiveMerchant. Please use CreditCard#matching_brand? instead." + ActiveMerchant.deprecated 'CreditCard#matching_type? is deprecated and will be removed from a future release of ActiveMerchant. Please use CreditCard#matching_brand? instead.' matching_brand?(number, brand) end - def deprecated(message) - warn(Kernel.caller[1] + message) - end - private - + def valid_card_number_length?(number) #:nodoc: - number.to_s.length >= 12 + return false if number.nil? + number.length >= 12 end - + + def valid_card_number_characters?(number) #:nodoc: + return false if number.nil? + !number.match(/\D/) + end + def valid_test_mode_card_number?(number) #:nodoc: - ActiveMerchant::Billing::Base.test? && - %w[1 2 3 success failure error].include?(number.to_s) + ActiveMerchant::Billing::Base.test? && + %w[1 2 3 success failure error].include?(number) end - - # Checks the validity of a card number by use of the the Luhn Algorithm. + + def valid_by_algorithm?(brand, numbers) #:nodoc: + case brand + when 'naranja' + valid_naranja_algo?(numbers) + else + valid_luhn?(numbers) + end + end + + ODD_LUHN_VALUE = { + 48 => 0, + 49 => 1, + 50 => 2, + 51 => 3, + 52 => 4, + 53 => 5, + 54 => 6, + 55 => 7, + 56 => 8, + 57 => 9, + nil => 0 + }.freeze + + EVEN_LUHN_VALUE = { + 48 => 0, # 0 * 2 + 49 => 2, # 1 * 2 + 50 => 4, # 2 * 2 + 51 => 6, # 3 * 2 + 52 => 8, # 4 * 2 + 53 => 1, # 5 * 2 - 9 + 54 => 3, # 6 * 2 - 9 + 55 => 5, # etc ... + 56 => 7, + 57 => 9, + }.freeze + + # Checks the validity of a card number by use of the Luhn Algorithm. # Please see http://en.wikipedia.org/wiki/Luhn_algorithm for details. - def valid_checksum?(number) #:nodoc: + # This implementation is from the luhn_checksum gem, https://github.com/zendesk/luhn_checksum. + def valid_luhn?(numbers) #:nodoc: sum = 0 - for i in 0..number.length - weight = number[-1 * (i + 2), 1].to_i * (2 - (i % 2)) - sum += (weight < 10) ? weight : weight - 9 + + odd = true + numbers.reverse.bytes.each do |number| + if odd + odd = false + sum += ODD_LUHN_VALUE[number] + else + odd = true + sum += EVEN_LUHN_VALUE[number] + end end - - (number[-1,1].to_i == (10 - sum % 10) % 10) + + sum % 10 == 0 + end + + # Checks the validity of a card number by use of Naranja's specific algorithm. + def valid_naranja_algo?(numbers) #:nodoc: + num_array = numbers.to_s.chars.map(&:to_i) + multipliers = [4, 3, 2, 7, 6, 5, 4, 3, 2, 7, 6, 5, 4, 3, 2] + num_sum = num_array[0..14].zip(multipliers).map { |a, b| a*b }.reduce(:+) + intermediate = 11 - (num_sum % 11) + final_num = intermediate > 9 ? 0 : intermediate + final_num == num_array[15] end end end diff --git a/lib/active_merchant/billing/cvv_result.rb b/lib/active_merchant/billing/cvv_result.rb index edd2d5086ad..2fbbbb83c4b 100644 --- a/lib/active_merchant/billing/cvv_result.rb +++ b/lib/active_merchant/billing/cvv_result.rb @@ -4,29 +4,29 @@ module Billing # http://www.bbbonline.org/eExport/doc/MerchantGuide_cvv2.pdf # Check additional codes from cybersource website class CVVResult - + MESSAGES = { - 'D' => 'Suspicious transaction', - 'I' => 'Failed data validation check', - 'M' => 'Match', - 'N' => 'No Match', - 'P' => 'Not Processed', - 'S' => 'Should have been present', - 'U' => 'Issuer unable to process request', - 'X' => 'Card does not support verification' + 'D' => 'CVV check flagged transaction as suspicious', + 'I' => 'CVV failed data validation check', + 'M' => 'CVV matches', + 'N' => 'CVV does not match', + 'P' => 'CVV not processed', + 'S' => 'CVV should have been present', + 'U' => 'CVV request unable to be processed by issuer', + 'X' => 'CVV check not supported for card' } - + def self.messages MESSAGES end - + attr_reader :code, :message - + def initialize(code) - @code = code.upcase unless code.blank? + @code = (code.blank? ? nil : code.upcase) @message = MESSAGES[@code] end - + def to_hash { 'code' => code, @@ -35,4 +35,4 @@ def to_hash end end end -end \ No newline at end of file +end diff --git a/lib/active_merchant/billing/expiry_date.rb b/lib/active_merchant/billing/expiry_date.rb deleted file mode 100644 index 4ab1fb00a5c..00000000000 --- a/lib/active_merchant/billing/expiry_date.rb +++ /dev/null @@ -1,34 +0,0 @@ -require 'date' - -module ActiveMerchant - module Billing - class CreditCard - class ExpiryDate #:nodoc: - attr_reader :month, :year - def initialize(month, year) - @month = month.to_i - @year = year.to_i - end - - def expired? #:nodoc: - Time.now.utc > expiration - end - - def expiration #:nodoc: - begin - Time.utc(year, month, month_days, 23, 59, 59) - rescue ArgumentError - Time.at(0).utc - end - end - - private - def month_days - mdays = [nil,31,28,31,30,31,30,31,31,30,31,30,31] - mdays[2] = 29 if Date.leap?(year) - mdays[month] - end - end - end - end -end \ No newline at end of file diff --git a/lib/active_merchant/billing/gateway.rb b/lib/active_merchant/billing/gateway.rb index 97b31208db5..c90e56e28c7 100644 --- a/lib/active_merchant/billing/gateway.rb +++ b/lib/active_merchant/billing/gateway.rb @@ -10,19 +10,16 @@ module Billing #:nodoc: # # The standard list of gateway functions that most concrete gateway subclasses implement is: # - # * purchase(money, creditcard, options = {}) - # * authorize(money, creditcard, options = {}) + # * purchase(money, credit_card, options = {}) + # * authorize(money, credit_card, options = {}) # * capture(money, authorization, options = {}) # * void(identification, options = {}) - # * credit(money, identification, options = {}) - # - # Some gateways include features for recurring billing - # - # * recurring(money, creditcard, options = {}) + # * refund(money, identification, options = {}) + # * verify(credit_card, options = {}) # # Some gateways also support features for storing credit cards: # - # * store(creditcard, options = {}) + # * store(credit_card, options = {}) # * unstore(identification, options = {}) # # === Gateway Options @@ -51,19 +48,55 @@ module Billing #:nodoc: # * :zip - The zip or postal code of the customer. # * :phone - The phone number of the customer. # - # == Implmenting new gateways + # == Implementing new gateways # - # See the {ActiveMerchant Guide to Contributing}[https://github.com/Shopify/active_merchant/wiki/Contributing] + # See the {ActiveMerchant Guide to Contributing}[https://github.com/activemerchant/active_merchant/wiki/Contributing] # class Gateway include PostsData - include RequiresParameters include CreditCardFormatting - include Utils - DEBIT_CARDS = [ :switch, :solo ] - CURRENCIES_WITHOUT_FRACTIONS = [ 'JPY', 'HUF', 'TWD' ] - CREDIT_DEPRECATION_MESSAGE = "Support for using credit to refund existing transactions is deprecated and will be removed from a future release of ActiveMerchant. Please use the refund method instead." + CREDIT_DEPRECATION_MESSAGE = 'Support for using credit to refund existing transactions is deprecated and will be removed from a future release of ActiveMerchant. Please use the refund method instead.' + RECURRING_DEPRECATION_MESSAGE = 'Recurring functionality in ActiveMerchant is deprecated and will be removed in a future version. Please contact the ActiveMerchant maintainers if you have an interest in taking ownership of a separate gem that continues support for it.' + + # == Standardized Error Codes + # + # :incorrect_number - Card number does not comply with ISO/IEC 7812 numbering standard + # :invalid_number - Card number was not matched by processor + # :invalid_expiry_date - Expiry date does not match correct formatting + # :invalid_cvc - Security codes does not match correct format (3-4 digits) + # :expired_card - Card number is expired + # :incorrect_cvc - Security code was not matched by the processor + # :incorrect_zip - Zip code is not in correct format + # :incorrect_address - Billing address info was not matched by the processor + # :incorrect_pin - Card PIN is incorrect + # :card_declined - Card number declined by processor + # :processing_error - Processor error + # :call_issuer - Transaction requires voice authentication, call issuer + # :pickup_card - Issuer requests that you pickup the card from merchant + # :test_mode_live_card - Card was declined. Request was in test mode, but used a non test card. + # :unsupported_feature - Transaction failed due to gateway or merchant + # configuration not supporting a feature used, such + # as network tokenization. + + STANDARD_ERROR_CODE = { + :incorrect_number => 'incorrect_number', + :invalid_number => 'invalid_number', + :invalid_expiry_date => 'invalid_expiry_date', + :invalid_cvc => 'invalid_cvc', + :expired_card => 'expired_card', + :incorrect_cvc => 'incorrect_cvc', + :incorrect_zip => 'incorrect_zip', + :incorrect_address => 'incorrect_address', + :incorrect_pin => 'incorrect_pin', + :card_declined => 'card_declined', + :processing_error => 'processing_error', + :call_issuer => 'call_issuer', + :pickup_card => 'pick_up_card', + :config_error => 'config_error', + :test_mode_live_card => 'test_mode_live_card', + :unsupported_feature => 'unsupported_feature', + } cattr_reader :implementations @@implementations = [] @@ -73,6 +106,10 @@ def self.inherited(subclass) @@implementations << subclass end + def generate_unique_id + SecureRandom.hex(16) + end + # The format of the amounts used by the gateway # :dollars => '12.50' # :cents => '1250' @@ -82,14 +119,14 @@ def self.inherited(subclass) # The default currency for the transactions if no currency is provided class_attribute :default_currency - # The countries of merchants the gateway supports - class_attribute :supported_countries - self.supported_countries = [] - # The supported card types for the gateway class_attribute :supported_cardtypes self.supported_cardtypes = [] + class_attribute :currencies_without_fractions, :currencies_with_three_decimal_places + self.currencies_without_fractions = %w(BIF BYR CLP CVE DJF GNF ISK JPY KMF KRW PYG RWF UGX VND VUV XAF XOF XPF) + self.currencies_with_three_decimal_places = %w() + class_attribute :homepage_url class_attribute :display_name @@ -101,8 +138,8 @@ def self.inherited(subclass) # The application making the calls to the gateway # Useful for things like the PayPal build notation (BN) id fields - superclass_delegating_accessor :application_id - self.application_id = 'ActiveMerchant' + class_attribute :application_id, instance_writer: false + self.application_id = nil attr_reader :options @@ -116,6 +153,23 @@ def self.card_brand(source) result.to_s.downcase end + def self.supported_countries=(country_codes) + country_codes.each do |country_code| + unless ActiveMerchant::Country.find(country_code) + raise ActiveMerchant::InvalidCountryCodeError, "No country could be found for the country #{country_code}" + end + end + @supported_countries = country_codes.dup + end + + def self.supported_countries + @supported_countries ||= [] + end + + def supported_countries + self.class.supported_countries + end + def card_brand(source) self.class.card_brand(source) end @@ -133,6 +187,61 @@ def test? (@options.has_key?(:test) ? @options[:test] : Base.test?) end + # Does this gateway know how to scrub sensitive information out of HTTP transcripts? + def supports_scrubbing? + false + end + + def scrub(transcript) + raise 'This gateway does not support scrubbing.' + end + + def supports_network_tokenization? + false + end + + def add_fields_to_post_if_present(post, options, fields) + fields.each do |field| + add_field_to_post_if_present(post, options, field) + end + end + + def add_field_to_post_if_present(post, options, field) + post[field] = options[field] if options[field] + end + + protected # :nodoc: all + + def normalize(field) + case field + when 'true' then true + when 'false' then false + when '' then nil + when 'null' then nil + else field + end + end + + def user_agent + @@ua ||= JSON.dump({ + :bindings_version => ActiveMerchant::VERSION, + :lang => 'ruby', + :lang_version => "#{RUBY_VERSION} p#{RUBY_PATCHLEVEL} (#{RUBY_RELEASE_DATE})", + :platform => RUBY_PLATFORM, + :publisher => 'active_merchant' + }) + end + + def strip_invalid_xml_chars(xml) + begin + REXML::Document.new(xml) + rescue REXML::ParseException + xml = xml.gsub(/&(?!(?:[a-z]+|#[0-9]+|x[a-zA-Z0-9]+);)/, '&') + end + + xml + end + private # :nodoc: all def name @@ -142,10 +251,10 @@ def name def amount(money) return nil if money.nil? cents = if money.respond_to?(:cents) - deprecated "Support for Money objects is deprecated and will be removed from a future release of ActiveMerchant. Please use an Integer value in cents" - money.cents - else - money + ActiveMerchant.deprecated 'Support for Money objects is deprecated and will be removed from a future release of ActiveMerchant. Please use an Integer value in cents' + money.cents + else + money end if money.is_a?(String) @@ -155,26 +264,66 @@ def amount(money) if self.money_format == :cents cents.to_s else - sprintf("%.2f", cents.to_f / 100) + sprintf('%.2f', cents.to_f / 100) end end + def non_fractional_currency?(currency) + self.currencies_without_fractions.include?(currency.to_s) + end + + def three_decimal_currency?(currency) + self.currencies_with_three_decimal_places.include?(currency.to_s) + end + def localized_amount(money, currency) amount = amount(money) - non_fractional_currency?(currency) ? amount.split('.').first : amount - end - def non_fractional_currency?(currency) - CURRENCIES_WITHOUT_FRACTIONS.include?(currency.to_s) + return amount unless non_fractional_currency?(currency) || three_decimal_currency?(currency) + if non_fractional_currency?(currency) + if self.money_format == :cents + sprintf('%.0f', amount.to_f / 100) + else + amount.split('.').first + end + elsif three_decimal_currency?(currency) + if self.money_format == :cents + amount.to_s + else + sprintf('%.3f', (amount.to_f / 10)) + end + end end def currency(money) money.respond_to?(:currency) ? money.currency : self.default_currency end - def requires_start_date_or_issue_number?(credit_card) - return false if card_brand(credit_card).blank? - DEBIT_CARDS.include?(card_brand(credit_card).to_sym) + def truncate(value, max_size) + return nil unless value + value.to_s[0, max_size] + end + + def split_names(full_name) + names = (full_name || '').split + return [nil, nil] if names.size == 0 + + last_name = names.pop + first_name = names.join(' ') + [first_name, last_name] + end + + def requires!(hash, *params) + params.each do |param| + if param.is_a?(Array) + raise ArgumentError.new("Missing required parameter: #{param.first}") unless hash.has_key?(param.first) + + valid_options = param[1..-1] + raise ArgumentError.new("Parameter: #{param.first} must be one of #{valid_options.to_sentence(:words_connector => 'or')}") unless valid_options.include?(hash[param.first]) + else + raise ArgumentError.new("Missing required parameter: #{param}") unless hash.has_key?(param) + end + end end end end diff --git a/lib/active_merchant/billing/gateways.rb b/lib/active_merchant/billing/gateways.rb index a9bebd1b884..31f7a66c850 100644 --- a/lib/active_merchant/billing/gateways.rb +++ b/lib/active_merchant/billing/gateways.rb @@ -1,17 +1,14 @@ +require 'pathname' + module ActiveMerchant module Billing - autoload :Gateway, 'active_merchant/billing/gateway' - - Dir[File.dirname(__FILE__) + '/gateways/**/*.rb'].each do |f| - # Get camelized class name - filename = File.basename(f, '.rb') - # Add _gateway suffix - gateway_name = filename + '_gateway' - # Camelize the string to get the class name - gateway_class = gateway_name.camelize + load_path = Pathname.new(__FILE__ + '/../../..') + Dir[File.dirname(__FILE__) + '/gateways/**/*.rb'].each do |filename| + gateway_name = File.basename(filename, '.rb') + gateway_classname = "#{gateway_name}_gateway".camelize + gateway_filename = Pathname.new(filename).relative_path_from(load_path).sub_ext('') - # Register for autoloading - autoload gateway_class, f + autoload(gateway_classname, gateway_filename) end end end diff --git a/lib/active_merchant/billing/gateways/adyen.rb b/lib/active_merchant/billing/gateways/adyen.rb new file mode 100644 index 00000000000..56826eeda98 --- /dev/null +++ b/lib/active_merchant/billing/gateways/adyen.rb @@ -0,0 +1,503 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class AdyenGateway < Gateway + + # we recommend setting up merchant-specific endpoints. + # https://docs.adyen.com/developers/api-manual#apiendpoints + self.test_url = 'https://pal-test.adyen.com/pal/servlet/Payment/' + self.live_url = 'https://pal-live.adyen.com/pal/servlet/Payment/' + + self.supported_countries = ['AT', 'AU', 'BE', 'BG', 'BR', 'CH', 'CY', 'CZ', 'DE', 'DK', 'EE', 'ES', 'FI', 'FR', 'GB', 'GI', 'GR', 'HK', 'HU', 'IE', 'IS', 'IT', 'LI', 'LT', 'LU', 'LV', 'MC', 'MT', 'MX', 'NL', 'NO', 'PL', 'PT', 'RO', 'SE', 'SG', 'SK', 'SI', 'US'] + self.default_currency = 'USD' + self.currencies_without_fractions = %w(CVE DJF GNF IDR JPY KMF KRW PYG RWF UGX VND VUV XAF XOF XPF) + self.supported_cardtypes = [:visa, :master, :american_express, :diners_club, :jcb, :dankort, :maestro, :discover, :elo, :naranja] + + self.money_format = :cents + + self.homepage_url = 'https://www.adyen.com/' + self.display_name = 'Adyen' + + API_VERSION = 'v40' + + STANDARD_ERROR_CODE_MAPPING = { + '101' => STANDARD_ERROR_CODE[:incorrect_number], + '103' => STANDARD_ERROR_CODE[:invalid_cvc], + '131' => STANDARD_ERROR_CODE[:incorrect_address], + '132' => STANDARD_ERROR_CODE[:incorrect_address], + '133' => STANDARD_ERROR_CODE[:incorrect_address], + '134' => STANDARD_ERROR_CODE[:incorrect_address], + '135' => STANDARD_ERROR_CODE[:incorrect_address], + } + + def initialize(options={}) + requires!(options, :username, :password, :merchant_account) + @username, @password, @merchant_account = options.values_at(:username, :password, :merchant_account) + super + end + + def purchase(money, payment, options={}) + if options[:execute_threed] || options[:threed_dynamic] + authorize(money, payment, options) + else + MultiResponse.run do |r| + r.process { authorize(money, payment, options) } + r.process { capture(money, r.authorization, capture_options(options)) } + end + end + end + + def authorize(money, payment, options={}) + requires!(options, :order_id) + post = init_post(options) + add_invoice(post, money, options) + add_payment(post, payment) + add_extra_data(post, payment, options) + add_stored_credentials(post, payment, options) + add_address(post, options) + add_installments(post, options) if options[:installments] + add_3ds(post, options) + add_3ds_authenticated_data(post, options) + commit('authorise', post, options) + end + + def capture(money, authorization, options={}) + post = init_post(options) + add_invoice_for_modification(post, money, options) + add_reference(post, authorization, options) + commit('capture', post, options) + end + + def refund(money, authorization, options={}) + post = init_post(options) + add_invoice_for_modification(post, money, options) + add_original_reference(post, authorization, options) + commit('refund', post, options) + end + + def void(authorization, options={}) + post = init_post(options) + add_reference(post, authorization, options) + commit('cancel', post, options) + end + + def adjust(money, authorization, options={}) + post = init_post(options) + add_invoice_for_modification(post, money, options) + add_reference(post, authorization, options) + add_extra_data(post, nil, options) + commit('adjustAuthorisation', post, options) + end + + def store(credit_card, options={}) + requires!(options, :order_id) + post = init_post(options) + add_invoice(post, 0, options) + add_payment(post, credit_card) + add_extra_data(post, credit_card, options) + add_stored_credentials(post, credit_card, options) + add_recurring_contract(post, options) + add_address(post, options) + commit('authorise', post, options) + end + + def verify(credit_card, options={}) + MultiResponse.run(:use_first_response) do |r| + r.process { authorize(0, credit_card, options) } + options[:idempotency_key] = nil + r.process(:ignore_result) { void(r.authorization, options) } + end + end + + def supports_scrubbing? + true + end + + def scrub(transcript) + transcript. + gsub(%r((Authorization: Basic )\w+), '\1[FILTERED]'). + gsub(%r(("number\\?":\\?")[^"]*)i, '\1[FILTERED]'). + gsub(%r(("cvc\\?":\\?")[^"]*)i, '\1[FILTERED]'). + gsub(%r(("cavv\\?":\\?")[^"]*)i, '\1[FILTERED]') + end + + private + + AVS_MAPPING = { + '0' => 'R', # Unknown + '1' => 'A', # Address matches, postal code doesn't + '2' => 'N', # Neither postal code nor address match + '3' => 'R', # AVS unavailable + '4' => 'E', # AVS not supported for this card type + '5' => 'U', # No AVS data provided + '6' => 'Z', # Postal code matches, address doesn't match + '7' => 'D', # Both postal code and address match + '8' => 'U', # Address not checked, postal code unknown + '9' => 'B', # Address matches, postal code unknown + '10' => 'N', # Address doesn't match, postal code unknown + '11' => 'U', # Postal code not checked, address unknown + '12' => 'B', # Address matches, postal code not checked + '13' => 'U', # Address doesn't match, postal code not checked + '14' => 'P', # Postal code matches, address unknown + '15' => 'P', # Postal code matches, address not checked + '16' => 'N', # Postal code doesn't match, address unknown + '17' => 'U', # Postal code doesn't match, address not checked + '18' => 'I', # Neither postal code nor address were checked + '19' => 'L', # Name and postal code matches. + '20' => 'V', # Name, address and postal code matches. + '21' => 'O', # Name and address matches. + '22' => 'K', # Name matches. + '23' => 'F', # Postal code matches, name doesn't match. + '24' => 'H', # Both postal code and address matches, name doesn't match. + '25' => 'T', # Address matches, name doesn't match. + '26' => 'N' # Neither postal code, address nor name matches. + } + + CVC_MAPPING = { + '0' => 'P', # Unknown + '1' => 'M', # Matches + '2' => 'N', # Does not match + '3' => 'P', # Not checked + '4' => 'S', # No CVC/CVV provided, but was required + '5' => 'U', # Issuer not certifed by CVC/CVV + '6' => 'P' # No CVC/CVV provided + } + + NETWORK_TOKENIZATION_CARD_SOURCE = { + 'apple_pay' => 'applepay', + 'android_pay' => 'androidpay', + 'google_pay' => 'paywithgoogle' + } + + def add_extra_data(post, payment, options) + post[:telephoneNumber] = options[:billing_address][:phone] if options.dig(:billing_address, :phone) + post[:shopperEmail] = options[:shopper_email] if options[:shopper_email] + post[:shopperIP] = options[:shopper_ip] if options[:shopper_ip] + post[:shopperReference] = options[:shopper_reference] if options[:shopper_reference] + post[:shopperStatement] = options[:shopper_statement] if options[:shopper_statement] + post[:fraudOffset] = options[:fraud_offset] if options[:fraud_offset] + post[:selectedBrand] = options[:selected_brand] if options[:selected_brand] + post[:selectedBrand] ||= NETWORK_TOKENIZATION_CARD_SOURCE[payment.source.to_s] if payment.is_a?(NetworkTokenizationCreditCard) + post[:deliveryDate] = options[:delivery_date] if options[:delivery_date] + post[:merchantOrderReference] = options[:merchant_order_reference] if options[:merchant_order_reference] + post[:additionalData] ||= {} + post[:additionalData][:overwriteBrand] = normalize(options[:overwrite_brand]) if options[:overwrite_brand] + post[:additionalData][:customRoutingFlag] = options[:custom_routing_flag] if options[:custom_routing_flag] + post[:additionalData]['paymentdatasource.type'] = NETWORK_TOKENIZATION_CARD_SOURCE[payment.source.to_s] if payment.is_a?(NetworkTokenizationCreditCard) + post[:additionalData][:authorisationType] = options[:authorisation_type] if options[:authorisation_type] + post[:additionalData][:adjustAuthorisationData] = options[:adjust_authorisation_data] if options[:adjust_authorisation_data] + post[:additionalData][:industryUsage] = options[:industry_usage] if options[:industry_usage] + post[:additionalData][:updateShopperStatement] = options[:update_shopper_statement] if options[:update_shopper_statement] + post[:additionalData][:RequestedTestAcquirerResponseCode] = options[:requested_test_acquirer_response_code] if options[:requested_test_acquirer_response_code] && test? + post[:deviceFingerprint] = options[:device_fingerprint] if options[:device_fingerprint] + add_risk_data(post, options) + end + + def add_risk_data(post, options) + if (risk_data = options[:risk_data]) + risk_data = Hash[risk_data.map { |k, v| ["riskdata.#{k}", v] }] + post[:additionalData].merge!(risk_data) + end + end + + def add_stored_credentials(post, payment, options) + add_shopper_interaction(post, payment, options) + add_recurring_processing_model(post, options) + end + + def add_shopper_interaction(post, payment, options={}) + if options.dig(:stored_credential, :initial_transaction) || (payment.respond_to?(:verification_value) && payment.verification_value) || payment.is_a?(NetworkTokenizationCreditCard) + shopper_interaction = 'Ecommerce' + else + shopper_interaction = 'ContAuth' + end + + post[:shopperInteraction] = options[:shopper_interaction] || shopper_interaction + end + + def add_recurring_processing_model(post, options) + return unless options.dig(:stored_credential, :reason_type) || options[:recurring_processing_model] + if options.dig(:stored_credential, :reason_type) && options[:stored_credential][:reason_type] == 'unscheduled' + recurring_processing_model = 'CardOnFile' + else + recurring_processing_model = 'Subscription' + end + + post[:recurringProcessingModel] = options[:recurring_processing_model] || recurring_processing_model + end + + def add_address(post, options) + return unless post[:card]&.kind_of?(Hash) + if (address = options[:billing_address] || options[:address]) && address[:country] + post[:billingAddress] = {} + post[:billingAddress][:street] = address[:address1] || 'N/A' + post[:billingAddress][:houseNumberOrName] = address[:address2] || 'N/A' + post[:billingAddress][:postalCode] = address[:zip] if address[:zip] + post[:billingAddress][:city] = address[:city] || 'N/A' + post[:billingAddress][:stateOrProvince] = get_state(address) + post[:billingAddress][:country] = address[:country] if address[:country] + end + end + + def get_state(address) + address[:state] && !address[:state].blank? ? address[:state] : 'N/A' + end + + def add_invoice(post, money, options) + currency = options[:currency] || currency(money) + amount = { + value: localized_amount(money, currency), + currency: currency + } + post[:amount] = amount + end + + def add_invoice_for_modification(post, money, options) + currency = options[:currency] || currency(money) + amount = { + value: localized_amount(money, currency), + currency: currency + } + post[:modificationAmount] = amount + end + + def add_payment(post, payment) + if payment.is_a?(String) + _, _, recurring_detail_reference = payment.split('#') + post[:selectedRecurringDetailReference] = recurring_detail_reference + add_recurring_contract(post, options) + else + add_mpi_data_for_network_tokenization_card(post, payment) if payment.is_a?(NetworkTokenizationCreditCard) + add_card(post, payment) + end + end + + def add_card(post, credit_card) + card = { + expiryMonth: credit_card.month, + expiryYear: credit_card.year, + holderName: credit_card.name, + number: credit_card.number, + cvc: credit_card.verification_value + } + + card.delete_if { |k, v| v.blank? } + card[:holderName] ||= 'Not Provided' if credit_card.is_a?(NetworkTokenizationCreditCard) + requires!(card, :expiryMonth, :expiryYear, :holderName, :number) + post[:card] = card + end + + def capture_options(options) + return options.merge(idempotency_key: "#{options[:idempotency_key]}-cap") if options[:idempotency_key] + options + end + + def add_reference(post, authorization, options = {}) + _, psp_reference, _ = authorization.split('#') + post[:originalReference] = single_reference(authorization) || psp_reference + end + + def add_original_reference(post, authorization, options = {}) + original_psp_reference, _, _ = authorization.split('#') + post[:originalReference] = single_reference(authorization) || original_psp_reference + end + + def add_mpi_data_for_network_tokenization_card(post, payment) + post[:mpiData] = {} + post[:mpiData][:authenticationResponse] = 'Y' + post[:mpiData][:cavv] = payment.payment_cryptogram + post[:mpiData][:directoryResponse] = 'Y' + post[:mpiData][:eci] = payment.eci || '07' + end + + def single_reference(authorization) + authorization if !authorization.include?('#') + end + + def add_recurring_contract(post, options = {}) + recurring = { + contract: 'RECURRING' + } + + post[:recurring] = recurring + end + + def add_installments(post, options) + post[:installments] = { + value: options[:installments] + } + end + + def add_3ds(post, options) + if three_ds_2_options = options[:three_ds_2] + device_channel = three_ds_2_options[:channel] + if device_channel == 'app' + post[:threeDS2RequestData] = { deviceChannel: device_channel } + else + add_browser_info(three_ds_2_options[:browser_info], post) + post[:threeDS2RequestData] = { deviceChannel: device_channel, notificationURL: three_ds_2_options[:notification_url] } + end + else + return unless options[:execute_threed] || options[:threed_dynamic] + post[:browserInfo] = { userAgent: options[:user_agent], acceptHeader: options[:accept_header] } + post[:additionalData] = { executeThreeD: 'true' } if options[:execute_threed] + end + end + + def add_3ds_authenticated_data(post, options) + if options[:three_d_secure] && options[:three_d_secure][:eci] && options[:three_d_secure][:xid] + add_3ds1_authenticated_data(post, options) + elsif options[:three_d_secure] + add_3ds2_authenticated_data(post, options) + end + end + + def add_3ds1_authenticated_data(post, options) + three_d_secure_options = options[:three_d_secure] + post[:mpiData] = { + cavv: three_d_secure_options[:cavv], + cavvAlgorithm: three_d_secure_options[:cavv_algorithm], + eci: three_d_secure_options[:eci], + xid: three_d_secure_options[:xid], + directoryResponse: three_d_secure_options[:directory_response_status], + authenticationResponse: three_d_secure_options[:authentication_response_status] + } + end + + def add_3ds2_authenticated_data(post, options) + three_d_secure_options = options[:three_d_secure] + # If the transaction was authenticated in a frictionless flow, send the transStatus from the ARes. + if(three_d_secure_options[:authentication_response_status].nil?) + authentication_response = three_d_secure_options[:directory_response_status] + else + authentication_response = three_d_secure_options[:authentication_response_status] + end + post[:mpiData] = { + threeDSVersion: three_d_secure_options[:version], + eci: three_d_secure_options[:eci], + cavv: three_d_secure_options[:cavv], + dsTransID: three_d_secure_options[:ds_transaction_id], + directoryResponse: three_d_secure_options[:directory_response_status], + authenticationResponse: authentication_response + } + end + + def parse(body) + return {} if body.blank? + JSON.parse(body) + end + + def commit(action, parameters, options) + begin + raw_response = ssl_post("#{url}/#{action}", post_data(action, parameters), request_headers(options)) + response = parse(raw_response) + rescue ResponseError => e + raw_response = e.response.body + response = parse(raw_response) + end + success = success_from(action, response) + Response.new( + success, + message_from(action, response), + response, + authorization: authorization_from(action, parameters, response), + test: test?, + error_code: success ? nil : error_code_from(response), + avs_result: AVSResult.new(:code => avs_code_from(response)), + cvv_result: CVVResult.new(cvv_result_from(response)) + ) + end + + def avs_code_from(response) + AVS_MAPPING[response['additionalData']['avsResult'][0..1].strip] if response.dig('additionalData', 'avsResult') + end + + def cvv_result_from(response) + CVC_MAPPING[response['additionalData']['cvcResult'][0]] if response.dig('additionalData', 'cvcResult') + end + + def url + if test? + "#{test_url}#{API_VERSION}" + elsif @options[:subdomain] + "https://#{@options[:subdomain]}-pal-live.adyenpayments.com/pal/servlet/Payment/#{API_VERSION}" + else + "#{live_url}#{API_VERSION}" + end + end + + def basic_auth + Base64.strict_encode64("#{@username}:#{@password}") + end + + def request_headers(options) + headers = { + 'Content-Type' => 'application/json', + 'Authorization' => "Basic #{basic_auth}" + } + headers['Idempotency-Key'] = options[:idempotency_key] if options[:idempotency_key] + headers + end + + def success_from(action, response) + case action.to_s + when 'authorise', 'authorise3d' + ['Authorised', 'Received', 'RedirectShopper'].include?(response['resultCode']) + when 'capture', 'refund', 'cancel' + response['response'] == "[#{action}-received]" + when 'adjustAuthorisation' + response['response'] == 'Authorised' || response['response'] == '[adjustAuthorisation-received]' + else + false + end + end + + def message_from(action, response) + return authorize_message_from(response) if action.to_s == 'authorise' + response['response'] || response['message'] + end + + def authorize_message_from(response) + if response['refusalReason'] && response['additionalData'] && response['additionalData']['refusalReasonRaw'] + "#{response['refusalReason']} | #{response['additionalData']['refusalReasonRaw']}" + else + response['refusalReason'] || response['resultCode'] || response['message'] + end + end + + def authorization_from(action, parameters, response) + return nil if response['pspReference'].nil? + recurring = response['additionalData']['recurring.recurringDetailReference'] if response['additionalData'] + "#{parameters[:originalReference]}##{response['pspReference']}##{recurring}" + end + + def init_post(options = {}) + post = {} + post[:merchantAccount] = options[:merchant_account] || @merchant_account + post[:reference] = options[:order_id] if options[:order_id] + post + end + + def post_data(action, parameters = {}) + JSON.generate(parameters) + end + + def error_code_from(response) + STANDARD_ERROR_CODE_MAPPING[response['errorCode']] + end + + def add_browser_info(browser_info, post) + return unless browser_info + post[:browserInfo] = { + acceptHeader: browser_info[:accept_header], + colorDepth: browser_info[:depth], + javaEnabled: browser_info[:java], + language: browser_info[:language], + screenHeight: browser_info[:height], + screenWidth: browser_info[:width], + timeZoneOffset: browser_info[:timezone], + userAgent: browser_info[:user_agent] + } + end + end + end +end diff --git a/lib/active_merchant/billing/gateways/allied_wallet.rb b/lib/active_merchant/billing/gateways/allied_wallet.rb new file mode 100644 index 00000000000..8cdbd6f4eaa --- /dev/null +++ b/lib/active_merchant/billing/gateways/allied_wallet.rb @@ -0,0 +1,205 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class AlliedWalletGateway < Gateway + self.display_name = 'Allied Wallet' + self.homepage_url = 'https://www.alliedwallet.com' + + self.live_url = 'https://api.alliedwallet.com/merchants/' + + self.supported_countries = ['US'] + self.default_currency = 'USD' + self.money_format = :dollars + self.supported_cardtypes = [:visa, :master, :american_express, :discover, + :diners_club, :jcb, :maestro] + + def initialize(options={}) + requires!(options, :site_id, :merchant_id, :token) + super + end + + def purchase(amount, payment_method, options={}) + post = {} + add_invoice(post, amount, options) + add_payment_method(post, payment_method) + add_customer_data(post, options) + + commit(:purchase, post) + end + + def authorize(amount, payment_method, options={}) + post = {} + add_invoice(post, amount, options) + add_payment_method(post, payment_method) + add_customer_data(post, options) + + commit(:authorize, post) + end + + def capture(amount, authorization, options={}) + post = {} + add_invoice(post, amount, options) + add_reference(post, authorization, :capture) + add_customer_data(post, options) + + commit(:capture, post) + end + + def void(authorization, options={}) + post = {} + add_reference(post, authorization, :void) + + commit(:void, post) + end + + def refund(amount, authorization, options={}) + post = {} + add_invoice(post, amount, options) + add_reference(post, authorization, :refund) + add_amount(post, amount) + add_customer_data(post, options) + + commit(:refund, post) + end + + def verify(credit_card, options={}) + MultiResponse.run(:use_first_response) do |r| + r.process { authorize(100, credit_card, options) } + r.process(:ignore_result) { void(r.authorization, options) } + end + end + + def supports_scrubbing? + true + end + + def scrub(transcript) + transcript. + gsub(%r((Authorization: Bearer )[a-zA-Z0-9._-]+)i, '\1[FILTERED]'). + gsub(%r(("cardNumber\\?":\\?")[^"]*)i, '\1[FILTERED]'). + gsub(%r(("cVVCode\\?":\\?")\d+[^"]*)i, '\1[FILTERED]'). + gsub(%r(("cVVCode\\?":)null), '\1[BLANK]'). + gsub(%r(("cVVCode\\?":\\?")\\?"), '\1[BLANK]"'). + gsub(%r(("cVVCode\\?":\\?")\s+), '\1[BLANK]"') + end + + private + + def add_amount(post, amount) + post[:amount] = amount + end + + def add_invoice(post, money, options) + post[:siteId] = @options[:site_id] + post[:amount] = amount(money) + post[:trackingId] = options[:order_id] + post[:currency] = options[:currency] || currency(money) + end + + def add_payment_method(post, payment_method) + post[:nameOnCard] = payment_method.name + post[:cardNumber] = payment_method.number + post[:cVVCode] = payment_method.verification_value + post[:expirationYear] = format(payment_method.year, :four_digits) + post[:expirationMonth] = format(payment_method.month, :two_digits) + end + + def add_customer_data(post, options) + post[:email] = options[:email] || 'unspecified@example.com' + post[:iPAddress] = options[:ip] + if (billing_address = options[:billing_address]) + post[:firstName], post[:lastName] = split_names(billing_address[:name]) + post[:addressLine1] = billing_address[:address1] + post[:addressLine2] = billing_address[:address2] + post[:city] = billing_address[:city] + post[:state] = billing_address[:state] + post[:countryId] = billing_address[:country] + post[:postalCode] = billing_address[:zip] + post[:phone] = billing_address[:phone] + end + end + + def add_reference(post, authorization, action) + transactions = { + capture: :authorizetransactionid, + void: :authorizeTransactionid, + refund: :referencetransactionid, + recurring: :saleTransactionid + } + post[transactions[action]] = authorization + end + + ACTIONS = { + purchase: 'SALE', + authorize: 'AUTHORIZE', + capture: 'CAPTURE', + void: 'VOID', + refund: 'REFUND' + } + + def commit(action, post) + begin + raw_response = ssl_post(url(action), post.to_json, headers) + response = parse(raw_response) + rescue ResponseError => e + raise unless(e.response.code.to_s =~ /4\d\d/) + response = parse(e.response.body) + end + + succeeded = success_from(response['status']) + Response.new( + succeeded, + message_from(succeeded, response), + response, + authorization: response['id'], + :avs_result => AVSResult.new(code: response['avs_response']), + :cvv_result => CVVResult.new(response['cvv2_response']), + test: test? + ) + rescue JSON::ParserError + unparsable_response(raw_response) + end + + def unparsable_response(raw_response) + message = 'Unparsable response received from Allied Wallet. Please contact Allied Wallet if you continue to receive this message.' + message += " (The raw response returned by the API was #{raw_response.inspect})" + return Response.new(false, message) + end + + def headers + { + 'Content-type' => 'application/json', + 'Authorization' => 'Bearer ' + @options[:token] + } + end + + def url(action) + live_url + CGI.escape(@options[:merchant_id]) + '/' + ACTIONS[action] + 'transactions' + end + + def parse(body) + JSON.parse(body) + end + + def parse_element(response, node) + if node.has_elements? + node.elements.each { |element| parse_element(response, element) } + else + response[node.name.underscore.to_sym] = node.text + end + end + + def success_from(response) + response == 'Successful' + end + + def message_from(succeeded, response) + if succeeded + 'Succeeded' + else + response['message'] || 'Unable to read error message' + end + end + + end + end +end diff --git a/lib/active_merchant/billing/gateways/authorize_net.rb b/lib/active_merchant/billing/gateways/authorize_net.rb index fb2fada9f6f..ccc8794a05a 100644 --- a/lib/active_merchant/billing/gateways/authorize_net.rb +++ b/lib/active_merchant/billing/gateways/authorize_net.rb @@ -1,725 +1,1055 @@ -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - # For more information on the Authorize.Net Gateway please visit their {Integration Center}[http://developer.authorize.net/] - # - # The login and password are not the username and password you use to - # login to the Authorize.Net Merchant Interface. Instead, you will - # use the API Login ID as the login and Transaction Key as the - # password. - # - # ==== How to Get Your API Login ID and Transaction Key - # - # 1. Log into the Merchant Interface - # 2. Select Settings from the Main Menu - # 3. Click on API Login ID and Transaction Key in the Security section - # 4. Type in the answer to the secret question configured on setup - # 5. Click Submit - # - # ==== Automated Recurring Billing (ARB) - # - # Automated Recurring Billing (ARB) is an optional service for submitting and managing recurring, or subscription-based, transactions. - # - # To use recurring, update_recurring, cancel_recurring and status_recurring ARB must be enabled for your account. - # - # Information about ARB is available on the {Authorize.Net website}[http://www.authorize.net/solutions/merchantsolutions/merchantservices/automatedrecurringbilling/]. - # Information about the ARB API is available at the {Authorize.Net Integration Center}[http://developer.authorize.net/] +require 'nokogiri' + +module ActiveMerchant + module Billing class AuthorizeNetGateway < Gateway - API_VERSION = '3.1' + include Empty - class_attribute :arb_test_url, :arb_live_url + self.test_url = 'https://apitest.authorize.net/xml/v1/request.api' + self.live_url = 'https://api2.authorize.net/xml/v1/request.api' - self.test_url = "https://test.authorize.net/gateway/transact.dll" - self.live_url = "https://secure.authorize.net/gateway/transact.dll" + self.supported_countries = %w(AD AT AU BE BG CA CH CY CZ DE DK EE ES FI FR GB GI GR HU IE IL IS IT LI LT LU LV MC MT NL NO PL PT RO SE SI SK SM TR US VA) + self.default_currency = 'USD' + self.money_format = :dollars + self.supported_cardtypes = [:visa, :master, :american_express, :discover, :diners_club, :jcb, :maestro] - self.arb_test_url = 'https://apitest.authorize.net/xml/v1/request.api' - self.arb_live_url = 'https://api.authorize.net/xml/v1/request.api' + self.homepage_url = 'http://www.authorize.net/' + self.display_name = 'Authorize.Net' - class_attribute :duplicate_window + # Authorize.net has slightly different definitions for returned AVS codes + # that have been mapped to the closest equivalent AM standard AVSResult codes + # Authorize.net's descriptions noted below + STANDARD_AVS_CODE_MAPPING = { + 'A' => 'A', # Street Address: Match -- First 5 Digits of ZIP: No Match + 'B' => 'I', # Address not provided for AVS check or street address match, postal code could not be verified + 'E' => 'E', # AVS Error + 'G' => 'G', # Non U.S. Card Issuing Bank + 'N' => 'N', # Street Address: No Match -- First 5 Digits of ZIP: No Match + 'P' => 'I', # AVS not applicable for this transaction + 'R' => 'R', # Retry, System Is Unavailable + 'S' => 'S', # AVS Not Supported by Card Issuing Bank + 'U' => 'U', # Address Information For This Cardholder Is Unavailable + 'W' => 'W', # Street Address: No Match -- All 9 Digits of ZIP: Match + 'X' => 'X', # Street Address: Match -- All 9 Digits of ZIP: Match + 'Y' => 'Y', # Street Address: Match - First 5 Digits of ZIP: Match + 'Z' => 'Z' # Street Address: No Match - First 5 Digits of ZIP: Match + } - APPROVED, DECLINED, ERROR, FRAUD_REVIEW = 1, 2, 3, 4 + STANDARD_ERROR_CODE_MAPPING = { + '2127' => STANDARD_ERROR_CODE[:incorrect_address], + '22' => STANDARD_ERROR_CODE[:card_declined], + '227' => STANDARD_ERROR_CODE[:incorrect_address], + '23' => STANDARD_ERROR_CODE[:card_declined], + '2315' => STANDARD_ERROR_CODE[:invalid_number], + '2316' => STANDARD_ERROR_CODE[:invalid_expiry_date], + '2317' => STANDARD_ERROR_CODE[:expired_card], + '235' => STANDARD_ERROR_CODE[:processing_error], + '237' => STANDARD_ERROR_CODE[:invalid_number], + '24' => STANDARD_ERROR_CODE[:pickup_card], + '244' => STANDARD_ERROR_CODE[:incorrect_cvc], + '300' => STANDARD_ERROR_CODE[:config_error], + '3153' => STANDARD_ERROR_CODE[:processing_error], + '3155' => STANDARD_ERROR_CODE[:unsupported_feature], + '36' => STANDARD_ERROR_CODE[:incorrect_number], + '37' => STANDARD_ERROR_CODE[:invalid_expiry_date], + '378' => STANDARD_ERROR_CODE[:invalid_cvc], + '38' => STANDARD_ERROR_CODE[:expired_card], + '384' => STANDARD_ERROR_CODE[:config_error], + } - RESPONSE_CODE, RESPONSE_REASON_CODE, RESPONSE_REASON_TEXT, AUTHORIZATION_CODE = 0, 2, 3, 4 - AVS_RESULT_CODE, TRANSACTION_ID, CARD_CODE_RESPONSE_CODE = 5, 6, 38 + MARKET_TYPE = { + :moto => '1', + :retail => '2' + } - self.default_currency = 'USD' + DEVICE_TYPE = { + :unknown => '1', + :unattended_terminal => '2', + :self_service_terminal => '3', + :electronic_cash_register => '4', + :personal_computer_terminal => '5', + :airpay => '6', + :wireless_pos => '7', + :website => '8', + :dial_terminal => '9', + :virtual_terminal => '10' + } - self.supported_countries = ['US', 'CA', 'GB'] - self.supported_cardtypes = [:visa, :master, :american_express, :discover, :diners_club, :jcb] - self.homepage_url = 'http://www.authorize.net/' - self.display_name = 'Authorize.Net' + class_attribute :duplicate_window - CARD_CODE_ERRORS = %w( N S ) - AVS_ERRORS = %w( A E N R W Z ) + APPROVED, DECLINED, ERROR, FRAUD_REVIEW = 1, 2, 3, 4 + TRANSACTION_ALREADY_ACTIONED = %w(310 311) + + CARD_CODE_ERRORS = %w(N S) + AVS_ERRORS = %w(A E I N R W Z) AVS_REASON_CODES = %w(27 45) - AUTHORIZE_NET_ARB_NAMESPACE = 'AnetApi/xml/v1/schema/AnetApiSchema.xsd' + TRACKS = { + 1 => /^%(?.)(?[\d]{1,19}+)\^(?.{2,26})\^(?[\d]{0,4}|\^)(?[\d]{0,3}|\^)(?.*)\?\Z/, + 2 => /\A;(?[\d]{1,19}+)=(?[\d]{0,4}|=)(?[\d]{0,3}|=)(?.*)\?\Z/ + }.freeze - RECURRING_ACTIONS = { - :create => 'ARBCreateSubscription', - :update => 'ARBUpdateSubscription', - :cancel => 'ARBCancelSubscription', - :status => 'ARBGetSubscriptionStatus' - } + APPLE_PAY_DATA_DESCRIPTOR = 'COMMON.APPLE.INAPP.PAYMENT' - # Creates a new AuthorizeNetGateway - # - # The gateway requires that a valid login and password be passed - # in the +options+ hash. - # - # ==== Options - # - # * :login -- The Authorize.Net API Login ID (REQUIRED) - # * :password -- The Authorize.Net Transaction Key. (REQUIRED) - # * :test -- +true+ or +false+. If true, perform transactions against the test server. - # Otherwise, perform transactions against the production server. - def initialize(options = {}) + PAYMENT_METHOD_NOT_SUPPORTED_ERROR = '155' + INELIGIBLE_FOR_ISSUING_CREDIT_ERROR = '54' + + def initialize(options={}) requires!(options, :login, :password) super end - # Performs an authorization, which reserves the funds on the customer's credit card, but does not - # charge the card. - # - # ==== Parameters - # - # * money -- The amount to be authorized as an Integer value in cents. - # * paysource -- The CreditCard or Check details for the transaction. - # * options -- A hash of optional parameters. - def authorize(money, paysource, options = {}) - post = {} - add_currency_code(post, money, options) - add_invoice(post, options) - add_payment_source(post, paysource, options) - add_address(post, options) - add_customer_data(post, options) - add_duplicate_window(post) - - commit('AUTH_ONLY', money, post) - end - - # Perform a purchase, which is essentially an authorization and capture in a single operation. - # - # ==== Parameters - # - # * money -- The amount to be purchased as an Integer value in cents. - # * paysource -- The CreditCard or Check details for the transaction. - # * options -- A hash of optional parameters. - def purchase(money, paysource, options = {}) - post = {} - add_currency_code(post, money, options) - add_invoice(post, options) - add_payment_source(post, paysource, options) - add_address(post, options) - add_customer_data(post, options) - add_duplicate_window(post) - - commit('AUTH_CAPTURE', money, post) - end - - # Captures the funds from an authorized transaction. - # - # ==== Parameters - # - # * money -- The amount to be captured as an Integer value in cents. - # * authorization -- The authorization returned from the previous authorize request. - def capture(money, authorization, options = {}) - post = {:trans_id => authorization} - add_customer_data(post, options) - add_invoice(post, options) - commit('PRIOR_AUTH_CAPTURE', money, post) - end - - # Void a previous transaction - # - # ==== Parameters - # - # * authorization - The authorization returned from the previous authorize request. - def void(authorization, options = {}) - post = {:trans_id => authorization} - add_duplicate_window(post) - commit('VOID', nil, post) - end - - # Refund a transaction. - # - # This transaction indicates to the gateway that - # money should flow from the merchant to the customer. - # - # ==== Parameters - # - # * money -- The amount to be credited to the customer as an Integer value in cents. - # * identification -- The ID of the original transaction against which the refund is being issued. - # * options -- A hash of parameters. - # - # ==== Options - # - # * :card_number -- The credit card number the refund is being issued to. (REQUIRED) - # You can either pass the last four digits of the card number or the full card number. - # * :first_name -- The first name of the account being refunded. - # * :last_name -- The last name of the account being refunded. - # * :zip -- The postal code of the account being refunded. - def refund(money, identification, options = {}) - requires!(options, :card_number) - - post = { :trans_id => identification, - :card_num => options[:card_number] - } - - post[:first_name] = options[:first_name] if options[:first_name] - post[:last_name] = options[:last_name] if options[:last_name] - post[:zip] = options[:zip] if options[:zip] - - add_invoice(post, options) - add_duplicate_window(post) - - commit('CREDIT', money, post) - end - - def credit(money, identification, options = {}) - deprecated CREDIT_DEPRECATION_MESSAGE - refund(money, identification, options) - end - - # Create a recurring payment. - # - # This transaction creates a new Automated Recurring Billing (ARB) subscription. Your account must have ARB enabled. - # - # ==== Parameters - # - # * money -- The amount to be charged to the customer at each interval as an Integer value in cents. - # * creditcard -- The CreditCard details for the transaction. - # * options -- A hash of parameters. - # - # ==== Options - # - # * :interval -- A hash containing information about the interval of time between payments. Must - # contain the keys :length and :unit. :unit can be either :months or :days. - # If :unit is :months then :length must be an integer between 1 and 12 inclusive. - # If :unit is :days then :length must be an integer between 7 and 365 inclusive. - # For example, to charge the customer once every three months the hash would be - # +:interval => { :unit => :months, :length => 3 }+ (REQUIRED) - # * :duration -- A hash containing keys for the :start_date the subscription begins (also the date the - # initial billing occurs) and the total number of billing :occurences or payments for the subscription. (REQUIRED) - def recurring(money, creditcard, options={}) - requires!(options, :interval, :duration, :billing_address) - requires!(options[:interval], :length, [:unit, :days, :months]) - requires!(options[:duration], :start_date, :occurrences) - requires!(options[:billing_address], :first_name, :last_name) - - options[:credit_card] = creditcard - options[:amount] = money - - request = build_recurring_request(:create, options) - recurring_commit(:create, request) - end - - # Update a recurring payment's details. - # - # This transaction updates an existing Automated Recurring Billing (ARB) subscription. Your account must have ARB enabled - # and the subscription must have already been created previously by calling +recurring()+. The ability to change certain - # details about a recurring payment is dependent on transaction history and cannot be determined until after calling - # +update_recurring()+. See the ARB XML Guide for such conditions. - # - # ==== Parameters - # - # * options -- A hash of parameters. - # - # ==== Options - # - # * :subscription_id -- A string containing the :subscription_id of the recurring payment already in place - # for a given credit card. (REQUIRED) - def update_recurring(options={}) - requires!(options, :subscription_id) - request = build_recurring_request(:update, options) - recurring_commit(:update, request) - end - - # Cancel a recurring payment. - # - # This transaction cancels an existing Automated Recurring Billing (ARB) subscription. Your account must have ARB enabled - # and the subscription must have already been created previously by calling recurring() - # - # ==== Parameters - # - # * subscription_id -- A string containing the +subscription_id+ of the recurring payment already in place - # for a given credit card. (REQUIRED) - def cancel_recurring(subscription_id) - request = build_recurring_request(:cancel, :subscription_id => subscription_id) - recurring_commit(:cancel, request) - end - - # Get Subscription Status of a recurring payment. - # - # This transaction gets the status of an existing Automated Recurring Billing (ARB) subscription. Your account must have ARB enabled. - # - # ==== Parameters - # - # * subscription_id -- A string containing the +subscription_id+ of the recurring payment already in place - # for a given credit card. (REQUIRED) - def status_recurring(subscription_id) - request = build_recurring_request(:status, :subscription_id => subscription_id) - recurring_commit(:status, request) + def purchase(amount, payment, options = {}) + if payment.is_a?(String) + commit(:cim_purchase, options) do |xml| + add_cim_auth_purchase(xml, 'profileTransAuthCapture', amount, payment, options) + end + else + commit(:purchase) do |xml| + add_auth_purchase(xml, 'authCaptureTransaction', amount, payment, options) + end + end end - private + def authorize(amount, payment, options={}) + if payment.is_a?(String) + commit(:cim_authorize, options) do |xml| + add_cim_auth_purchase(xml, 'profileTransAuthOnly', amount, payment, options) + end + else + commit(:authorize) do |xml| + add_auth_purchase(xml, 'authOnlyTransaction', amount, payment, options) + end + end + end - def commit(action, money, parameters) - parameters[:amount] = amount(money) unless action == 'VOID' + def capture(amount, authorization, options={}) + if auth_was_for_cim?(authorization) + cim_capture(amount, authorization, options) + else + normal_capture(amount, authorization, options) + end + end - # Only activate the test_request when the :test option is passed in - parameters[:test_request] = @options[:test] ? 'TRUE' : 'FALSE' + def refund(amount, authorization, options={}) + response = if auth_was_for_cim?(authorization) + cim_refund(amount, authorization, options) + else + normal_refund(amount, authorization, options) + end - url = test? ? self.test_url : self.live_url - data = ssl_post url, post_data(action, parameters) + return response if response.success? + return response unless options[:force_full_refund_if_unsettled] - response = parse(data) - response[:action] = action + if response.params['response_reason_code'] == INELIGIBLE_FOR_ISSUING_CREDIT_ERROR + void(authorization, options) + else + response + end + end - message = message_from(response) + def void(authorization, options={}) + if auth_was_for_cim?(authorization) + cim_void(authorization, options) + else + normal_void(authorization, options) + end + end - # Return the response. The authorization can be taken out of the transaction_id - # Test Mode on/off is something we have to parse from the response text. - # It usually looks something like this - # - # (TESTMODE) Successful Sale - test_mode = test? || message =~ /TESTMODE/ + def credit(amount, payment, options={}) + if payment.is_a?(String) + raise ArgumentError, 'Reference credits are not supported. Please supply the original credit card or use the #refund method.' + end - Response.new(success?(response), message, response, - :test => test_mode, - :authorization => response[:transaction_id], - :fraud_review => fraud_review?(response), - :avs_result => { :code => response[:avs_result_code] }, - :cvv_result => response[:card_code] - ) + commit(:credit) do |xml| + add_order_id(xml, options) + xml.transactionRequest do + xml.transactionType('refundTransaction') + xml.amount(amount(amount)) + + add_payment_source(xml, payment, options, :credit) + xml.refTransId(transaction_id_from(options[:transaction_id])) if options[:transaction_id] + add_invoice(xml, 'refundTransaction', options) + add_customer_data(xml, payment, options) + add_settings(xml, payment, options) + add_user_fields(xml, amount, options) + end + end end - def success?(response) - response[:response_code] == APPROVED + def verify(credit_card, options = {}) + MultiResponse.run(:use_first_response) do |r| + r.process { authorize(100, credit_card, options) } + r.process(:ignore_result) { void(r.authorization, options) } + end end - def fraud_review?(response) - response[:response_code] == FRAUD_REVIEW + def store(credit_card, options = {}) + if options[:customer_profile_id] + create_customer_payment_profile(credit_card, options) + else + create_customer_profile(credit_card, options) + end end - def parse(body) - fields = split(body) + def unstore(authorization) + customer_profile_id, _, _ = split_authorization(authorization) - results = { - :response_code => fields[RESPONSE_CODE].to_i, - :response_reason_code => fields[RESPONSE_REASON_CODE], - :response_reason_text => fields[RESPONSE_REASON_TEXT], - :avs_result_code => fields[AVS_RESULT_CODE], - :transaction_id => fields[TRANSACTION_ID], - :card_code => fields[CARD_CODE_RESPONSE_CODE], - :authorization_code => fields[AUTHORIZATION_CODE] - } - results + delete_customer_profile(customer_profile_id) + end + + def verify_credentials + response = commit(:verify_credentials) {} + response.success? + end + + def supports_scrubbing? + true + end + + def scrub(transcript) + transcript. + gsub(%r((Authorization: Basic )\w+), '\1[FILTERED]'). + gsub(%r(().+()), '\1[FILTERED]\2'). + gsub(%r(().+()), '\1[FILTERED]\2'). + gsub(%r(().+()), '\1[FILTERED]\2'). + gsub(%r(().+()), '\1[FILTERED]\2'). + gsub(%r(().+()), '\1[FILTERED]\2'). + gsub(/().+(<\/routingNumber>)/, '\1[FILTERED]\2'). + gsub(/().+(<\/accountNumber>)/, '\1[FILTERED]\2'). + gsub(%r(().+()), '\1[FILTERED]\2') + end + + def supports_network_tokenization? + card = Billing::NetworkTokenizationCreditCard.new({ + :number => '4111111111111111', + :month => 12, + :year => 20, + :first_name => 'John', + :last_name => 'Smith', + :brand => 'visa', + :payment_cryptogram => 'EHuWW9PiBkWvqE5juRwDzAUFBAk=' + }) + + request = post_data(:authorize) do |xml| + add_auth_purchase(xml, 'authOnlyTransaction', 1, card, {}) + end + raw_response = ssl_post(url, request, headers) + response = parse(:authorize, raw_response) + response[:response_reason_code].to_s != PAYMENT_METHOD_NOT_SUPPORTED_ERROR + end + + private + + def add_auth_purchase(xml, transaction_type, amount, payment, options) + add_order_id(xml, options) + xml.transactionRequest do + xml.transactionType(transaction_type) + xml.amount(amount(amount)) + add_payment_source(xml, payment, options) + add_invoice(xml, transaction_type, options) + add_tax_fields(xml, options) + add_duty_fields(xml, options) + add_shipping_fields(xml, options) + add_tax_exempt_status(xml, options) + add_po_number(xml, options) + add_customer_data(xml, payment, options) + add_market_type_device_type(xml, payment, options) + add_settings(xml, payment, options) + add_user_fields(xml, amount, options) + add_ship_from_address(xml, options) + end end - def post_data(action, parameters = {}) - post = {} + def add_cim_auth_purchase(xml, transaction_type, amount, payment, options) + add_order_id(xml, options) + xml.transaction do + xml.send(transaction_type) do + xml.amount(amount(amount)) + add_tax_fields(xml, options) + add_shipping_fields(xml, options) + add_duty_fields(xml, options) + add_payment_source(xml, payment, options) + add_invoice(xml, transaction_type, options) + add_tax_exempt_status(xml, options) + end + end + add_extra_options_for_cim(xml, options) + end - post[:version] = API_VERSION - post[:login] = @options[:login] - post[:tran_key] = @options[:password] - post[:relay_response] = "FALSE" - post[:type] = action - post[:delim_data] = "TRUE" - post[:delim_char] = "," - post[:encap_char] = "$" - post[:solution_ID] = application_id if application_id.present? && application_id != "ActiveMerchant" + def cim_capture(amount, authorization, options) + commit(:cim_capture, options) do |xml| + add_order_id(xml, options) + xml.transaction do + xml.profileTransPriorAuthCapture do + xml.amount(amount(amount)) + add_tax_fields(xml, options) + add_shipping_fields(xml, options) + add_duty_fields(xml, options) + xml.transId(transaction_id_from(authorization)) + end + end + add_extra_options_for_cim(xml, options) + end + end - request = post.merge(parameters).collect { |key, value| "x_#{key}=#{CGI.escape(value.to_s)}" }.join("&") - request + def normal_capture(amount, authorization, options) + commit(:capture) do |xml| + add_order_id(xml, options) + xml.transactionRequest do + xml.transactionType('priorAuthCaptureTransaction') + xml.amount(amount(amount)) + add_tax_fields(xml, options) + add_duty_fields(xml, options) + add_shipping_fields(xml, options) + add_tax_exempt_status(xml, options) + add_po_number(xml, options) + xml.refTransId(transaction_id_from(authorization)) + add_invoice(xml, 'capture', options) + add_user_fields(xml, amount, options) + end + end end - def add_currency_code(post, money, options) - post[:currency_code] = options[:currency] || currency(money) + def cim_refund(amount, authorization, options) + transaction_id, card_number, _ = split_authorization(authorization) + + commit(:cim_refund, options) do |xml| + add_order_id(xml, options) + xml.transaction do + xml.profileTransRefund do + xml.amount(amount(amount)) + add_tax_fields(xml, options) + add_shipping_fields(xml, options) + add_duty_fields(xml, options) + xml.creditCardNumberMasked(card_number) + add_invoice(xml, 'profileTransRefund', options) + xml.transId(transaction_id) + end + end + add_extra_options_for_cim(xml, options) + end end - def add_invoice(post, options) - post[:invoice_num] = options[:order_id] - post[:description] = options[:description] + def normal_refund(amount, authorization, options) + transaction_id, card_number, _ = split_authorization(authorization) + + commit(:refund) do |xml| + xml.transactionRequest do + xml.transactionType('refundTransaction') + xml.amount(amount.nil? ? 0 : amount(amount)) + xml.payment do + if options[:routing_number] + xml.bankAccount do + xml.accountType(options[:account_type]) + xml.routingNumber(options[:routing_number]) + xml.accountNumber(options[:account_number]) + xml.nameOnAccount("#{options[:first_name]} #{options[:last_name]}") + end + else + xml.creditCard do + xml.cardNumber(card_number || options[:card_number]) + xml.expirationDate('XXXX') + end + end + end + xml.refTransId(transaction_id) + + add_invoice(xml, 'refundTransaction', options) + add_tax_fields(xml, options) + add_duty_fields(xml, options) + add_shipping_fields(xml, options) + add_tax_exempt_status(xml, options) + add_po_number(xml, options) + add_customer_data(xml, nil, options) + add_user_fields(xml, amount, options) + end + end end - def add_creditcard(post, creditcard, options={}) - post[:card_num] = creditcard.number - post[:card_code] = creditcard.verification_value if creditcard.verification_value? - post[:exp_date] = expdate(creditcard) - post[:first_name] = creditcard.first_name - post[:last_name] = creditcard.last_name + def cim_void(authorization, options) + commit(:cim_void, options) do |xml| + add_order_id(xml, options) + xml.transaction do + xml.profileTransVoid do + xml.transId(transaction_id_from(authorization)) + end + end + add_extra_options_for_cim(xml, options) + end end - def add_payment_source(params, source, options={}) - if card_brand(source) == "check" - add_check(params, source, options) + def normal_void(authorization, options) + commit(:void) do |xml| + add_order_id(xml, options) + xml.transactionRequest do + xml.transactionType('voidTransaction') + xml.refTransId(transaction_id_from(authorization)) + end + end + end + + def add_payment_source(xml, source, options, action = nil) + return unless source + if source.is_a?(String) + add_token_payment_method(xml, source, options) + elsif card_brand(source) == 'check' + add_check(xml, source) + elsif card_brand(source) == 'apple_pay' + add_apple_pay_payment_token(xml, source) else - add_creditcard(params, source, options) + add_credit_card(xml, source, action) end end - def add_check(post, check, options) - post[:method] = "ECHECK" - post[:bank_name] = check.bank_name - post[:bank_aba_code] = check.routing_number - post[:bank_acct_num] = check.account_number - post[:bank_acct_type] = check.account_type - post[:echeck_type] = "WEB" - post[:bank_acct_name] = check.name - post[:bank_check_number] = check.number if check.number.present? - post[:recurring_billing] = (options[:recurring] ? "TRUE" : "FALSE") + def camel_case_lower(key) + String(key).split('_').inject([]) { |buffer, e| buffer.push(buffer.empty? ? e : e.capitalize) }.join end - def add_customer_data(post, options) - if options.has_key? :email - post[:email] = options[:email] - post[:email_customer] = false + def add_settings(xml, source, options) + xml.transactionSettings do + if options[:recurring] + xml.setting do + xml.settingName('recurringBilling') + xml.settingValue('true') + end + end + if options[:disable_partial_auth] + xml.setting do + xml.settingName('allowPartialAuth') + xml.settingValue('false') + end + end + if options[:duplicate_window] + set_duplicate_window(xml, options[:duplicate_window]) + elsif self.class.duplicate_window + ActiveMerchant.deprecated 'Using the duplicate_window class_attribute is deprecated. Use the transaction options hash instead.' + set_duplicate_window(xml, self.class.duplicate_window) + end + if options.key?(:email_customer) + xml.setting do + xml.settingName('emailCustomer') + xml.settingValue(options[:email_customer] ? 'true' : 'false') + end + end + if options[:header_email_receipt] + xml.setting do + xml.settingName('headerEmailReceipt') + xml.settingValue(options[:header_email_receipt]) + end + end + if options[:test_request] + xml.setting do + xml.settingName('testRequest') + xml.settingValue('1') + end + end end + end - if options.has_key? :customer - post[:cust_id] = options[:customer] if Float(options[:customer]) rescue nil + def set_duplicate_window(xml, value) + xml.setting do + xml.settingName('duplicateWindow') + xml.settingValue(value) end + end - if options.has_key? :ip - post[:customer_ip] = options[:ip] + def add_user_fields(xml, amount, options) + xml.userFields do + if currency = (options[:currency] || currency(amount)) + xml.userField do + xml.name('x_currency_code') + xml.value(currency) + end + end + if application_id.present? + xml.userField do + xml.name('x_solution_id') + xml.value(application_id) + end + end end end - # x_duplicate_window won't be sent by default, because sending it changes the response. - # "If this field is present in the request with or without a value, an enhanced duplicate transaction response will be sent." - # (as of 2008-12-30) http://www.authorize.net/support/AIM_guide_SCC.pdf - def add_duplicate_window(post) - unless duplicate_window.nil? - post[:duplicate_window] = duplicate_window + def add_credit_card(xml, credit_card, action) + if credit_card.track_data + add_swipe_data(xml, credit_card) + else + xml.payment do + xml.creditCard do + xml.cardNumber(truncate(credit_card.number, 16)) + xml.expirationDate(format(credit_card.month, :two_digits) + '/' + format(credit_card.year, :four_digits)) + if credit_card.valid_card_verification_value?(credit_card.verification_value, credit_card.brand) + xml.cardCode(credit_card.verification_value) + end + if credit_card.is_a?(NetworkTokenizationCreditCard) && action != :credit + xml.cryptogram(credit_card.payment_cryptogram) + end + end + end end end - def add_address(post, options) - if address = options[:billing_address] || options[:address] - post[:address] = address[:address1].to_s - post[:company] = address[:company].to_s - post[:phone] = address[:phone].to_s - post[:zip] = address[:zip].to_s - post[:city] = address[:city].to_s - post[:country] = address[:country].to_s - post[:state] = address[:state].blank? ? 'n/a' : address[:state] + def add_swipe_data(xml, credit_card) + TRACKS.each do |key, regex| + if regex.match(credit_card.track_data) + @valid_track_data = true + xml.payment do + xml.trackData do + xml.public_send(:"track#{key}", credit_card.track_data) + end + end + end end + end + + def add_token_payment_method(xml, token, options) + customer_profile_id, customer_payment_profile_id, _ = split_authorization(token) + customer_profile_id = options[:customer_profile_id] if options[:customer_profile_id] + customer_payment_profile_id = options[:customer_payment_profile_id] if options[:customer_payment_profile_id] + xml.customerProfileId(customer_profile_id) + xml.customerPaymentProfileId(customer_payment_profile_id) + end - if address = options[:shipping_address] - post[:ship_to_first_name] = address[:first_name].to_s - post[:ship_to_last_name] = address[:last_name].to_s - post[:ship_to_address] = address[:address1].to_s - post[:ship_to_company] = address[:company].to_s - post[:ship_to_phone] = address[:phone].to_s - post[:ship_to_zip] = address[:zip].to_s - post[:ship_to_city] = address[:city].to_s - post[:ship_to_country] = address[:country].to_s - post[:ship_to_state] = address[:state].blank? ? 'n/a' : address[:state] + def add_apple_pay_payment_token(xml, apple_pay_payment_token) + xml.payment do + xml.opaqueData do + xml.dataDescriptor(APPLE_PAY_DATA_DESCRIPTOR) + xml.dataValue(Base64.strict_encode64(apple_pay_payment_token.payment_data.to_json)) + end end end - # Make a ruby type out of the response string - def normalize(field) - case field - when "true" then true - when "false" then false - when "" then nil - when "null" then nil - else field + def add_market_type_device_type(xml, payment, options) + return if payment.is_a?(String) || card_brand(payment) == 'check' || card_brand(payment) == 'apple_pay' + if valid_track_data + xml.retail do + xml.marketType(options[:market_type] || MARKET_TYPE[:retail]) + xml.deviceType(options[:device_type] || DEVICE_TYPE[:wireless_pos]) + end + elsif payment.manual_entry + xml.retail do + xml.marketType(options[:market_type] || MARKET_TYPE[:moto]) + end + else + if options[:market_type] + xml.retail do + xml.marketType(options[:market_type]) + end + end end end - def message_from(results) - if results[:response_code] == DECLINED - return CVVResult.messages[ results[:card_code] ] if CARD_CODE_ERRORS.include?(results[:card_code]) - if AVS_REASON_CODES.include?(results[:response_reason_code]) && AVS_ERRORS.include?(results[:avs_result_code]) - return AVSResult.messages[ results[:avs_result_code] ] + def valid_track_data + @valid_track_data ||= false + end + + def add_check(xml, check) + xml.payment do + xml.bankAccount do + xml.routingNumber(check.routing_number) + xml.accountNumber(check.account_number) + xml.nameOnAccount(truncate(check.name, 22)) + xml.bankName(check.bank_name) + xml.checkNumber(check.number) end end + end + + def add_customer_data(xml, payment_source, options) + xml.customer do + xml.id(options[:customer]) unless empty?(options[:customer]) || options[:customer] !~ /^\w+$/ + xml.email(options[:email]) unless empty?(options[:email]) + end + + add_billing_address(xml, payment_source, options) + add_shipping_address(xml, options) + + xml.customerIP(options[:ip]) unless empty?(options[:ip]) + + xml.cardholderAuthentication do + three_d_secure = options.fetch(:three_d_secure, {}) + xml.authenticationIndicator( + options[:authentication_indicator] || three_d_secure[:eci]) + xml.cardholderAuthenticationValue( + options[:cardholder_authentication_value] || three_d_secure[:cavv]) + end + end - (results[:response_reason_text] ? results[:response_reason_text].chomp('.') : '') + def add_billing_address(xml, payment_source, options) + address = options[:billing_address] || options[:address] || {} + + xml.billTo do + first_name, last_name = names_from(payment_source, address, options) + state = state_from(address, options) + full_address = "#{address[:address1]} #{address[:address2]}".strip + + xml.firstName(truncate(first_name, 50)) unless empty?(first_name) + xml.lastName(truncate(last_name, 50)) unless empty?(last_name) + xml.company(truncate(address[:company], 50)) unless empty?(address[:company]) + xml.address(truncate(full_address, 60)) + xml.city(truncate(address[:city], 40)) + xml.state(truncate(state, 40)) + xml.zip(truncate((address[:zip] || options[:zip]), 20)) + xml.country(truncate(address[:country], 60)) + xml.phoneNumber(truncate(address[:phone], 25)) unless empty?(address[:phone]) + xml.faxNumber(truncate(address[:fax], 25)) unless empty?(address[:fax]) + end end - def expdate(creditcard) - year = sprintf("%.4i", creditcard.year) - month = sprintf("%.2i", creditcard.month) + def add_shipping_address(xml, options, root_node='shipTo') + address = options[:shipping_address] || options[:address] + return unless address - "#{month}#{year[-2..-1]}" + xml.send(root_node) do + first_name, last_name = if address[:name] + split_names(address[:name]) + else + [address[:first_name], address[:last_name]] + end + full_address = "#{address[:address1]} #{address[:address2]}".strip + + xml.firstName(truncate(first_name, 50)) unless empty?(first_name) + xml.lastName(truncate(last_name, 50)) unless empty?(last_name) + xml.company(truncate(address[:company], 50)) unless empty?(address[:company]) + xml.address(truncate(full_address, 60)) + xml.city(truncate(address[:city], 40)) + xml.state(truncate(address[:state], 40)) + xml.zip(truncate(address[:zip], 20)) + xml.country(truncate(address[:country], 60)) + end end - def split(response) - response[1..-2].split(/\$,\$/) + def add_ship_from_address(xml, options, root_node='shipFrom') + address = options[:ship_from_address] + return unless address + + xml.send(root_node) do + xml.zip(truncate(address[:zip], 20)) unless empty?(address[:zip]) + xml.country(truncate(address[:country], 60)) unless empty?(address[:country]) + end end - # ARB + def add_order_id(xml, options) + xml.refId(truncate(options[:order_id], 20)) + end - # Builds recurring billing request - def build_recurring_request(action, options = {}) - unless RECURRING_ACTIONS.include?(action) - raise StandardError, "Invalid Automated Recurring Billing Action: #{action}" + def add_invoice(xml, transaction_type, options) + xml.order do + xml.invoiceNumber(truncate(options[:order_id], 20)) + xml.description(truncate(options[:description], 255)) + xml.purchaseOrderNumber(options[:po_number]) if options[:po_number] && transaction_type.start_with?('profileTrans') + xml.summaryCommodityCode(truncate(options[:summary_commodity_code], 4)) if options[:summary_commodity_code] && !transaction_type.start_with?('profileTrans') end - xml = Builder::XmlMarkup.new(:indent => 2) - xml.instruct!(:xml, :version => '1.0', :encoding => 'utf-8') - xml.tag!("#{RECURRING_ACTIONS[action]}Request", :xmlns => AUTHORIZE_NET_ARB_NAMESPACE) do - add_arb_merchant_authentication(xml) - # Merchant-assigned reference ID for the request - xml.tag!('refId', options[:ref_id]) if options[:ref_id] - send("build_arb_#{action}_subscription_request", xml, options) + # Authorize.net API requires lineItems to be placed directly after order tag + if options[:line_items] + xml.lineItems do + options[:line_items].each do |line_item| + xml.lineItem do + line_item.each do |key, value| + xml.send(camel_case_lower(key), value) + end + end + end + end end end - # Contains the merchant’s payment gateway account authentication information - def add_arb_merchant_authentication(xml) - xml.tag!('merchantAuthentication') do - xml.tag!('name', @options[:login]) - xml.tag!('transactionKey', @options[:password]) + def add_tax_fields(xml, options) + tax = options[:tax] + if tax.is_a?(Hash) + xml.tax do + xml.amount(amount(tax[:amount].to_i)) + xml.name(tax[:name]) + xml.description(tax[:description]) + end end end - # Builds body for ARBCreateSubscriptionRequest - def build_arb_create_subscription_request(xml, options) - # Subscription - add_arb_subscription(xml, options) + def add_duty_fields(xml, options) + duty = options[:duty] + if duty.is_a?(Hash) + xml.duty do + xml.amount(amount(duty[:amount].to_i)) + xml.name(duty[:name]) + xml.description(duty[:description]) + end + end + end - xml.target! + def add_shipping_fields(xml, options) + shipping = options[:shipping] + if shipping.is_a?(Hash) + xml.shipping do + xml.amount(amount(shipping[:amount].to_i)) + xml.name(shipping[:name]) + xml.description(shipping[:description]) + end + end end - # Builds body for ARBUpdateSubscriptionRequest - def build_arb_update_subscription_request(xml, options) - xml.tag!('subscriptionId', options[:subscription_id]) - # Adds Subscription - add_arb_subscription(xml, options) + def add_tax_exempt_status(xml, options) + xml.taxExempt(options[:tax_exempt]) if options[:tax_exempt] + end - xml.target! + def add_po_number(xml, options) + xml.poNumber(options[:po_number]) if options[:po_number] end - # Builds body for ARBCancelSubscriptionRequest - def build_arb_cancel_subscription_request(xml, options) - xml.tag!('subscriptionId', options[:subscription_id]) + def add_extra_options_for_cim(xml, options) + xml.extraOptions("x_delim_char=#{options[:delimiter]}") if options[:delimiter] + end - xml.target! + def create_customer_payment_profile(credit_card, options) + commit(:cim_store_update, options) do |xml| + xml.customerProfileId options[:customer_profile_id] + xml.paymentProfile do + add_billing_address(xml, credit_card, options) + xml.payment do + xml.creditCard do + xml.cardNumber(truncate(credit_card.number, 16)) + xml.expirationDate(format(credit_card.year, :four_digits) + '-' + format(credit_card.month, :two_digits)) + xml.cardCode(credit_card.verification_value) if credit_card.verification_value + end + end + end + end end - # Builds body for ARBGetSubscriptionStatusRequest - def build_arb_status_subscription_request(xml, options) - xml.tag!('subscriptionId', options[:subscription_id]) + def create_customer_profile(credit_card, options) + commit(:cim_store, options) do |xml| + xml.profile do + xml.merchantCustomerId(truncate(options[:merchant_customer_id], 20) || SecureRandom.hex(10)) + xml.description(truncate(options[:description], 255)) unless empty?(options[:description]) + xml.email(options[:email]) unless empty?(options[:email]) + + xml.paymentProfiles do + xml.customerType('individual') + add_billing_address(xml, credit_card, options) + add_shipping_address(xml, options, 'shipToList') + xml.payment do + xml.creditCard do + xml.cardNumber(truncate(credit_card.number, 16)) + xml.expirationDate(format(credit_card.year, :four_digits) + '-' + format(credit_card.month, :two_digits)) + xml.cardCode(credit_card.verification_value) if credit_card.verification_value + end + end + end + end + end + end - xml.target! + def delete_customer_profile(customer_profile_id) + commit(:cim_store_delete_customer, options) do |xml| + xml.customerProfileId(customer_profile_id) + end + end + + def names_from(payment_source, address, options) + if payment_source && !payment_source.is_a?(PaymentToken) && !payment_source.is_a?(String) + first_name, last_name = split_names(address[:name]) + [(payment_source.first_name || first_name), (payment_source.last_name || last_name)] + else + [options[:first_name], options[:last_name]] + end + end + + def state_from(address, options) + if ['US', 'CA'].include?(address[:country]) + address[:state] || 'NC' + else + address[:state] || 'n/a' + end + end + + def headers + { 'Content-Type' => 'text/xml' } end - # Adds subscription information - def add_arb_subscription(xml, options) - xml.tag!('subscription') do - # Merchant-assigned name for the subscription (optional) - xml.tag!('name', options[:subscription_name]) if options[:subscription_name] - # Contains information about the payment schedule - add_arb_payment_schedule(xml, options) - # The amount to be billed to the customer - # for each payment in the subscription - xml.tag!('amount', amount(options[:amount])) if options[:amount] - if trial = options[:trial] - # The amount to be charged for each payment during a trial period (conditional) - xml.tag!('trialAmount', amount(trial[:amount])) if trial[:amount] + def url + test? ? test_url : live_url + end + + def parse(action, raw_response, options = {}) + if is_cim_action?(action) || action == :verify_credentials + parse_cim(raw_response, options) + else + parse_normal(action, raw_response) + end + end + + def commit(action, options = {}, &payload) + raw_response = ssl_post(url, post_data(action, &payload), headers) + response = parse(action, raw_response, options) + + avs_result_code = response[:avs_result_code].upcase if response[:avs_result_code] + avs_result = AVSResult.new(code: STANDARD_AVS_CODE_MAPPING[avs_result_code]) + cvv_result = CVVResult.new(response[:card_code]) + if using_live_gateway_in_test_mode?(response) + Response.new(false, 'Using a live Authorize.net account in Test Mode is not permitted.') + else + Response.new( + success_from(action, response), + message_from(action, response, avs_result, cvv_result), + response, + authorization: authorization_from(action, response), + test: test?, + avs_result: avs_result, + cvv_result: cvv_result, + fraud_review: fraud_review?(response), + error_code: map_error_code(response[:response_code], response[:response_reason_code]) + ) + end + end + + def is_cim_action?(action) + action.to_s.start_with?('cim') + end + + def post_data(action) + Nokogiri::XML::Builder.new(encoding: 'UTF-8') do |xml| + xml.send(root_for(action), 'xmlns' => 'AnetApi/xml/v1/schema/AnetApiSchema.xsd') do + add_authentication(xml) + yield(xml) end - # Contains either the customer’s credit card - # or bank account payment information - add_arb_payment(xml, options) - # Contains order information (optional) - add_arb_order(xml, options) - # Contains information about the customer - add_arb_customer(xml, options) - # Contains the customer's billing address information - add_arb_address(xml, 'billTo', options[:billing_address]) - # Contains the customer's shipping address information (optional) - add_arb_address(xml, 'shipTo', options[:shipping_address]) + end.to_xml(indent: 0) + end + + def root_for(action) + if action == :cim_store + 'createCustomerProfileRequest' + elsif action == :cim_store_update + 'createCustomerPaymentProfileRequest' + elsif action == :cim_store_delete_customer + 'deleteCustomerProfileRequest' + elsif action == :verify_credentials + 'authenticateTestRequest' + elsif is_cim_action?(action) + 'createCustomerProfileTransactionRequest' + else + 'createTransactionRequest' end end - # Adds information about the interval of time between payments - def add_arb_interval(xml, options) - interval = options[:interval] - return unless interval - xml.tag!('interval') do - # The measurement of time, in association with the Interval Unit, - # that is used to define the frequency of the billing occurrences - xml.tag!('length', interval[:length]) - # The unit of time, in association with the Interval Length, - # between each billing occurrence - xml.tag!('unit', interval[:unit].to_s) - end + def add_authentication(xml) + xml.merchantAuthentication do + xml.name(@options[:login]) + xml.transactionKey(@options[:password]) + end end - # Adds information about the subscription duration - def add_arb_duration(xml, options) - duration = options[:duration] - return unless duration - # The date the subscription begins - # (also the date the initial billing occurs) - xml.tag!('startDate', duration[:start_date]) if duration[:start_date] - # Number of billing occurrences or payments for the subscription - xml.tag!('totalOccurrences', duration[:occurrences]) if duration[:occurrences] - end - - def add_arb_payment_schedule(xml, options) - return unless options[:interval] || options[:duration] - xml.tag!('paymentSchedule') do - # Contains information about the interval of time between payments - add_arb_interval(xml, options) - add_arb_duration(xml, options) - if trial = options[:trial] - # Number of billing occurrences or payments in the trial period (optional) - xml.tag!('trialOccurrences', trial[:occurrences]) if trial[:occurrences] - end - end - end - - # Adds customer's credit card or bank account payment information - def add_arb_payment(xml, options) - return unless options[:credit_card] || options[:bank_account] - xml.tag!('payment') do - # Contains the customer’s credit card information - add_arb_credit_card(xml, options) - # Contains the customer’s bank account information - add_arb_bank_account(xml, options) - end - end - - # Adds customer’s credit card information - # Note: This element should only be included - # when the payment method is credit card. - def add_arb_credit_card(xml, options) - credit_card = options[:credit_card] - return unless credit_card - xml.tag!('creditCard') do - # The credit card number used for payment of the subscription - xml.tag!('cardNumber', credit_card.number) - # The expiration date of the credit card used for the subscription - xml.tag!('expirationDate', arb_expdate(credit_card)) - end - end - - # Adds customer’s bank account information - # Note: This element should only be included - # when the payment method is bank account. - def add_arb_bank_account(xml, options) - bank_account = options[:bank_account] - return unless bank_account - xml.tag!('bankAccount') do - # The type of bank account used for payment of the subscription - xml.tag!('accountType', bank_account[:account_type]) - # The routing number of the customer’s bank - xml.tag!('routingNumber', bank_account[:routing_number]) - # The bank account number used for payment of the subscription - xml.tag!('accountNumber', bank_account[:account_number]) - # The full name of the individual associated - # with the bank account number - xml.tag!('nameOfAccount', bank_account[:name_of_account]) - # The full name of the individual associated - # with the bank account number (optional) - xml.tag!('bankName', bank_account[:bank_name]) if bank_account[:bank_name] - # The type of electronic check transaction used for the subscription - xml.tag!('echeckType', bank_account[:echeck_type]) - end - end - - # Adds order information (optional) - def add_arb_order(xml, options) - order = options[:order] - return unless order - xml.tag!('order') do - # Merchant-assigned invoice number for the subscription (optional) - xml.tag!('invoiceNumber', order[:invoice_number]) - # Description of the subscription (optional) - xml.tag!('description', order[:description]) - end - end - - # Adds information about the customer - def add_arb_customer(xml, options) - customer = options[:customer] - return unless customer - xml.tag!('customer') do - xml.tag!('type', customer[:type]) if customer[:type] - xml.tag!('id', customer[:id]) if customer[:id] - xml.tag!('email', customer[:email]) if customer[:email] - xml.tag!('phoneNumber', customer[:phone_number]) if customer[:phone_number] - xml.tag!('faxNumber', customer[:fax_number]) if customer[:fax_number] - add_arb_drivers_license(xml, options) - xml.tag!('taxId', customer[:tax_id]) if customer[:tax_id] - end - end - - # Adds the customer's driver's license information (conditional) - def add_arb_drivers_license(xml, options) - return unless customer = options[:customer] - return unless drivers_license = customer[:drivers_license] - xml.tag!('driversLicense') do - # The customer's driver's license number - xml.tag!('number', drivers_license[:number]) - # The customer's driver's license state - xml.tag!('state', drivers_license[:state]) - # The customer's driver's license date of birth - xml.tag!('dateOfBirth', drivers_license[:date_of_birth]) - end - end - - # Adds address information - def add_arb_address(xml, container_name, address) - return if address.blank? - xml.tag!(container_name) do - xml.tag!('firstName', address[:first_name]) - xml.tag!('lastName', address[:last_name]) - xml.tag!('company', address[:company]) - xml.tag!('address', address[:address1]) - xml.tag!('city', address[:city]) - xml.tag!('state', address[:state]) - xml.tag!('zip', address[:zip]) - xml.tag!('country', address[:country]) - end - end - - def arb_expdate(credit_card) - sprintf('%04d-%02d', credit_card.year, credit_card.month) - end - - def recurring_commit(action, request) - url = test? ? arb_test_url : arb_live_url - xml = ssl_post(url, request, "Content-Type" => "text/xml") - - response = recurring_parse(action, xml) - - message = response[:message] || response[:text] - test_mode = test? || message =~ /Test Mode/ - success = response[:result_code] == 'Ok' - - Response.new(success, message, response, - :test => test_mode, - :authorization => response[:subscription_id] - ) - end - - def recurring_parse(action, xml) + def parse_normal(action, body) + doc = Nokogiri::XML(body) + doc.remove_namespaces! + + response = {action: action} + + response[:response_code] = if(element = doc.at_xpath('//transactionResponse/responseCode')) + (empty?(element.content) ? nil : element.content.to_i) + end + + if(element = doc.at_xpath('//errors/error')) + response[:response_reason_code] = element.at_xpath('errorCode').content[/0*(\d+)$/, 1] + response[:response_reason_text] = element.at_xpath('errorText').content.chomp('.') + elsif(element = doc.at_xpath('//transactionResponse/messages/message')) + response[:response_reason_code] = element.at_xpath('code').content[/0*(\d+)$/, 1] + response[:response_reason_text] = element.at_xpath('description').content.chomp('.') + elsif(element = doc.at_xpath('//messages/message')) + response[:response_reason_code] = element.at_xpath('code').content[/0*(\d+)$/, 1] + response[:response_reason_text] = element.at_xpath('text').content.chomp('.') + else + response[:response_reason_code] = nil + response[:response_reason_text] = '' + end + + response[:avs_result_code] = if(element = doc.at_xpath('//avsResultCode')) + (empty?(element.content) ? nil : element.content) + end + + response[:transaction_id] = if(element = doc.at_xpath('//transId')) + (empty?(element.content) ? nil : element.content) + end + + response[:card_code] = if(element = doc.at_xpath('//cvvResultCode')) + (empty?(element.content) ? nil : element.content) + end + + response[:authorization_code] = if(element = doc.at_xpath('//authCode')) + (empty?(element.content) ? nil : element.content) + end + + response[:cardholder_authentication_code] = if(element = doc.at_xpath('//cavvResultCode')) + (empty?(element.content) ? nil : element.content) + end + + response[:account_number] = if(element = doc.at_xpath('//accountNumber')) + (empty?(element.content) ? nil : element.content[-4..-1]) + end + + response[:test_request] = if(element = doc.at_xpath('//testRequest')) + (empty?(element.content) ? nil : element.content) + end + + response[:full_response_code] = if(element = doc.at_xpath('//messages/message/code')) + (empty?(element.content) ? nil : element.content) + end + + response + end + + def parse_cim(body, options) response = {} - xml = REXML::Document.new(xml) - root = REXML::XPath.first(xml, "//#{RECURRING_ACTIONS[action]}Response") || - REXML::XPath.first(xml, "//ErrorResponse") - if root - root.elements.to_a.each do |node| - recurring_parse_element(response, node) - end + + doc = Nokogiri::XML(body).remove_namespaces! + + if (element = doc.at_xpath('//messages/message')) + response[:message_code] = element.at_xpath('code').content[/0*(\d+)$/, 1] + response[:message_text] = element.at_xpath('text').content.chomp('.') + end + + response[:result_code] = if(element = doc.at_xpath('//messages/resultCode')) + (empty?(element.content) ? nil : element.content) + end + + response[:test_request] = if(element = doc.at_xpath('//testRequest')) + (empty?(element.content) ? nil : element.content) + end + + response[:customer_profile_id] = if(element = doc.at_xpath('//customerProfileId')) + (empty?(element.content) ? nil : element.content) end + response[:customer_payment_profile_id] = if(element = doc.at_xpath('//customerPaymentProfileIdList/numericString')) + (empty?(element.content) ? nil : element.content) + end + + response[:customer_payment_profile_id] = if(element = doc.at_xpath('//customerPaymentProfileIdList/numericString') || + doc.at_xpath('//customerPaymentProfileId')) + (empty?(element.content) ? nil : element.content) + end + + response[:direct_response] = if(element = doc.at_xpath('//directResponse')) + (empty?(element.content) ? nil : element.content) + end + + response.merge!(parse_direct_response_elements(response, options)) + response end - def recurring_parse_element(response, node) - if node.has_elements? - node.elements.each{|e| recurring_parse_element(response, e) } + def success_from(action, response) + if cim?(action) || (action == :verify_credentials) + response[:result_code] == 'Ok' else - response[node.name.underscore.to_sym] = node.text + [APPROVED, FRAUD_REVIEW].include?(response[:response_code]) && TRANSACTION_ALREADY_ACTIONED.exclude?(response[:response_reason_code]) end end + + def message_from(action, response, avs_result, cvv_result) + if response[:response_code] == DECLINED + if CARD_CODE_ERRORS.include?(cvv_result.code) + return cvv_result.message + elsif(AVS_REASON_CODES.include?(response[:response_reason_code]) && AVS_ERRORS.include?(avs_result.code)) + return avs_result.message + end + end + + response[:response_reason_text] || response[:message_text] + end + + def authorization_from(action, response) + if cim?(action) + [response[:customer_profile_id], response[:customer_payment_profile_id], action].join('#') + else + [response[:transaction_id], response[:account_number], action].join('#') + end + end + + def split_authorization(authorization) + authorization.split('#') + end + + def cim?(action) + (action == :cim_store) || (action == :cim_store_update) || (action == :cim_store_delete_customer) + end + + def transaction_id_from(authorization) + transaction_id, _, _ = split_authorization(authorization) + transaction_id + end + + def fraud_review?(response) + (response[:response_code] == FRAUD_REVIEW) + end + + def using_live_gateway_in_test_mode?(response) + !test? && response[:test_request] == '1' + end + + def map_error_code(response_code, response_reason_code) + STANDARD_ERROR_CODE_MAPPING["#{response_code}#{response_reason_code}"] + end + + def auth_was_for_cim?(authorization) + _, _, action = split_authorization(authorization) + action && is_cim_action?(action) + end + + def parse_direct_response_elements(response, options) + params = response[:direct_response] + return {} unless params + + parts = params.split(options[:delimiter] || ',') + { + response_code: parts[0].to_i, + response_subcode: parts[1], + response_reason_code: parts[2], + response_reason_text: parts[3], + approval_code: parts[4], + avs_result_code: parts[5], + transaction_id: parts[6], + invoice_number: parts[7], + order_description: parts[8], + amount: parts[9], + method: parts[10], + transaction_type: parts[11], + customer_id: parts[12], + first_name: parts[13], + last_name: parts[14], + company: parts[15], + address: parts[16], + city: parts[17], + state: parts[18], + zip_code: parts[19], + country: parts[20], + phone: parts[21], + fax: parts[22], + email_address: parts[23], + ship_to_first_name: parts[24], + ship_to_last_name: parts[25], + ship_to_company: parts[26], + ship_to_address: parts[27], + ship_to_city: parts[28], + ship_to_state: parts[29], + ship_to_zip_code: parts[30], + ship_to_country: parts[31], + tax: parts[32], + duty: parts[33], + freight: parts[34], + tax_exempt: parts[35], + purchase_order_number: parts[36], + md5_hash: parts[37], + card_code: parts[38], + cardholder_authentication_verification_response: parts[39], + account_number: parts[50] || '', + card_type: parts[51] || '', + split_tender_id: parts[52] || '', + requested_amount: parts[53] || '', + balance_on_card: parts[54] || '', + } + end + end end end diff --git a/lib/active_merchant/billing/gateways/authorize_net_arb.rb b/lib/active_merchant/billing/gateways/authorize_net_arb.rb new file mode 100644 index 00000000000..406cc55e50e --- /dev/null +++ b/lib/active_merchant/billing/gateways/authorize_net_arb.rb @@ -0,0 +1,417 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + # For more information on the Authorize.Net Gateway please visit their {Integration Center}[http://developer.authorize.net/] + # + # The login and password are not the username and password you use to + # login to the Authorize.Net Merchant Interface. Instead, you will + # use the API Login ID as the login and Transaction Key as the + # password. + # + # ==== How to Get Your API Login ID and Transaction Key + # + # 1. Log into the Merchant Interface + # 2. Select Settings from the Main Menu + # 3. Click on API Login ID and Transaction Key in the Security section + # 4. Type in the answer to the secret question configured on setup + # 5. Click Submit + # + # ==== Automated Recurring Billing (ARB) + # + # Automated Recurring Billing (ARB) is an optional service for submitting and managing recurring, or subscription-based, transactions. + # + # To use recurring, update_recurring, cancel_recurring and status_recurring ARB must be enabled for your account. + # + # Information about ARB is available on the {Authorize.Net website}[http://www.authorize.net/solutions/merchantsolutions/merchantservices/automatedrecurringbilling/]. + # Information about the ARB API is available at the {Authorize.Net Integration Center}[http://developer.authorize.net/] + class AuthorizeNetArbGateway < Gateway + API_VERSION = '3.1' + + self.test_url = 'https://apitest.authorize.net/xml/v1/request.api' + self.live_url = 'https://api.authorize.net/xml/v1/request.api' + + self.default_currency = 'USD' + + self.supported_countries = ['US', 'CA', 'GB'] + self.supported_cardtypes = [:visa, :master, :american_express, :discover, :diners_club, :jcb] + self.homepage_url = 'http://www.authorize.net/' + self.display_name = 'Authorize.Net' + + AUTHORIZE_NET_ARB_NAMESPACE = 'AnetApi/xml/v1/schema/AnetApiSchema.xsd' + + RECURRING_ACTIONS = { + :create => 'ARBCreateSubscription', + :update => 'ARBUpdateSubscription', + :cancel => 'ARBCancelSubscription', + :status => 'ARBGetSubscriptionStatus' + } + + # Creates a new AuthorizeNetArbGateway + # + # The gateway requires that a valid login and password be passed + # in the +options+ hash. + # + # ==== Options + # + # * :login -- The Authorize.Net API Login ID (REQUIRED) + # * :password -- The Authorize.Net Transaction Key. (REQUIRED) + # * :test -- +true+ or +false+. If true, perform transactions against the test server. + # Otherwise, perform transactions against the production server. + def initialize(options = {}) + ActiveMerchant.deprecated 'ARB functionality in ActiveMerchant is deprecated and will be removed in a future version. Please contact the ActiveMerchant maintainers if you have an interest in taking ownership of a separate gem that continues support for it.' + requires!(options, :login, :password) + super + end + + # Create a recurring payment. + # + # This transaction creates a new Automated Recurring Billing (ARB) subscription. Your account must have ARB enabled. + # + # ==== Parameters + # + # * money -- The amount to be charged to the customer at each interval as an Integer value in cents. + # * creditcard -- The CreditCard details for the transaction. + # * options -- A hash of parameters. + # + # ==== Options + # + # * :interval -- A hash containing information about the interval of time between payments. Must + # contain the keys :length and :unit. :unit can be either :months or :days. + # If :unit is :months then :length must be an integer between 1 and 12 inclusive. + # If :unit is :days then :length must be an integer between 7 and 365 inclusive. + # For example, to charge the customer once every three months the hash would be + # +:interval => { :unit => :months, :length => 3 }+ (REQUIRED) + # * :duration -- A hash containing keys for the :start_date the subscription begins (also the date the + # initial billing occurs) and the total number of billing :occurrences or payments for the subscription. (REQUIRED) + def recurring(money, creditcard, options={}) + requires!(options, :interval, :duration, :billing_address) + requires!(options[:interval], :length, [:unit, :days, :months]) + requires!(options[:duration], :start_date, :occurrences) + requires!(options[:billing_address], :first_name, :last_name) + + options[:credit_card] = creditcard + options[:amount] = money + + request = build_recurring_request(:create, options) + recurring_commit(:create, request) + end + + # Update a recurring payment's details. + # + # This transaction updates an existing Automated Recurring Billing (ARB) subscription. Your account must have ARB enabled + # and the subscription must have already been created previously by calling +recurring()+. The ability to change certain + # details about a recurring payment is dependent on transaction history and cannot be determined until after calling + # +update_recurring()+. See the ARB XML Guide for such conditions. + # + # ==== Parameters + # + # * options -- A hash of parameters. + # + # ==== Options + # + # * :subscription_id -- A string containing the :subscription_id of the recurring payment already in place + # for a given credit card. (REQUIRED) + def update_recurring(options={}) + requires!(options, :subscription_id) + request = build_recurring_request(:update, options) + recurring_commit(:update, request) + end + + # Cancel a recurring payment. + # + # This transaction cancels an existing Automated Recurring Billing (ARB) subscription. Your account must have ARB enabled + # and the subscription must have already been created previously by calling recurring() + # + # ==== Parameters + # + # * subscription_id -- A string containing the +subscription_id+ of the recurring payment already in place + # for a given credit card. (REQUIRED) + def cancel_recurring(subscription_id) + request = build_recurring_request(:cancel, :subscription_id => subscription_id) + recurring_commit(:cancel, request) + end + + # Get Subscription Status of a recurring payment. + # + # This transaction gets the status of an existing Automated Recurring Billing (ARB) subscription. Your account must have ARB enabled. + # + # ==== Parameters + # + # * subscription_id -- A string containing the +subscription_id+ of the recurring payment already in place + # for a given credit card. (REQUIRED) + def status_recurring(subscription_id) + request = build_recurring_request(:status, :subscription_id => subscription_id) + recurring_commit(:status, request) + end + + private + + # Builds recurring billing request + def build_recurring_request(action, options = {}) + unless RECURRING_ACTIONS.include?(action) + raise StandardError, "Invalid Automated Recurring Billing Action: #{action}" + end + + xml = Builder::XmlMarkup.new(:indent => 2) + xml.instruct!(:xml, :version => '1.0', :encoding => 'utf-8') + xml.tag!("#{RECURRING_ACTIONS[action]}Request", :xmlns => AUTHORIZE_NET_ARB_NAMESPACE) do + add_merchant_authentication(xml) + # Merchant-assigned reference ID for the request + xml.tag!('refId', options[:ref_id]) if options[:ref_id] + send("build_#{action}_subscription_request", xml, options) + end + end + + # Contains the merchant’s payment gateway account authentication information + def add_merchant_authentication(xml) + xml.tag!('merchantAuthentication') do + xml.tag!('name', @options[:login]) + xml.tag!('transactionKey', @options[:password]) + end + end + + # Builds body for ARBCreateSubscriptionRequest + def build_create_subscription_request(xml, options) + # Subscription + add_subscription(xml, options) + + xml.target! + end + + # Builds body for ARBUpdateSubscriptionRequest + def build_update_subscription_request(xml, options) + xml.tag!('subscriptionId', options[:subscription_id]) + # Adds Subscription + add_subscription(xml, options) + + xml.target! + end + + # Builds body for ARBCancelSubscriptionRequest + def build_cancel_subscription_request(xml, options) + xml.tag!('subscriptionId', options[:subscription_id]) + + xml.target! + end + + # Builds body for ARBGetSubscriptionStatusRequest + def build_status_subscription_request(xml, options) + xml.tag!('subscriptionId', options[:subscription_id]) + + xml.target! + end + + # Adds subscription information + def add_subscription(xml, options) + xml.tag!('subscription') do + # Merchant-assigned name for the subscription (optional) + xml.tag!('name', options[:subscription_name]) if options[:subscription_name] + # Contains information about the payment schedule + add_payment_schedule(xml, options) + # The amount to be billed to the customer + # for each payment in the subscription + xml.tag!('amount', amount(options[:amount])) if options[:amount] + if trial = options[:trial] + # The amount to be charged for each payment during a trial period (conditional) + xml.tag!('trialAmount', amount(trial[:amount])) if trial[:amount] + end + # Contains either the customer’s credit card + # or bank account payment information + add_payment(xml, options) + # Contains order information (optional) + add_order(xml, options) + # Contains information about the customer + add_customer(xml, options) + # Contains the customer's billing address information + add_address(xml, 'billTo', options[:billing_address]) + # Contains the customer's shipping address information (optional) + add_address(xml, 'shipTo', options[:shipping_address]) + end + end + + # Adds information about the interval of time between payments + def add_interval(xml, options) + interval = options[:interval] + return unless interval + xml.tag!('interval') do + # The measurement of time, in association with the Interval Unit, + # that is used to define the frequency of the billing occurrences + xml.tag!('length', interval[:length]) + # The unit of time, in association with the Interval Length, + # between each billing occurrence + xml.tag!('unit', interval[:unit].to_s) + end + end + + # Adds information about the subscription duration + def add_duration(xml, options) + duration = options[:duration] + return unless duration + # The date the subscription begins + # (also the date the initial billing occurs) + xml.tag!('startDate', duration[:start_date]) if duration[:start_date] + # Number of billing occurrences or payments for the subscription + xml.tag!('totalOccurrences', duration[:occurrences]) if duration[:occurrences] + end + + def add_payment_schedule(xml, options) + return unless options[:interval] || options[:duration] + xml.tag!('paymentSchedule') do + # Contains information about the interval of time between payments + add_interval(xml, options) + add_duration(xml, options) + if trial = options[:trial] + # Number of billing occurrences or payments in the trial period (optional) + xml.tag!('trialOccurrences', trial[:occurrences]) if trial[:occurrences] + end + end + end + + # Adds customer's credit card or bank account payment information + def add_payment(xml, options) + return unless options[:credit_card] || options[:bank_account] + xml.tag!('payment') do + # Contains the customer’s credit card information + add_credit_card(xml, options) + # Contains the customer’s bank account information + add_bank_account(xml, options) + end + end + + # Adds customer’s credit card information + # Note: This element should only be included + # when the payment method is credit card. + def add_credit_card(xml, options) + credit_card = options[:credit_card] + return unless credit_card + xml.tag!('creditCard') do + # The credit card number used for payment of the subscription + xml.tag!('cardNumber', credit_card.number) + # The expiration date of the credit card used for the subscription + xml.tag!('expirationDate', expdate(credit_card)) + end + end + + # Adds customer’s bank account information + # Note: This element should only be included + # when the payment method is bank account. + def add_bank_account(xml, options) + bank_account = options[:bank_account] + return unless bank_account + xml.tag!('bankAccount') do + # The type of bank account used for payment of the subscription + xml.tag!('accountType', bank_account[:account_type]) + # The routing number of the customer’s bank + xml.tag!('routingNumber', bank_account[:routing_number]) + # The bank account number used for payment of the subscription + xml.tag!('accountNumber', bank_account[:account_number]) + # The full name of the individual associated + # with the bank account number + xml.tag!('nameOfAccount', bank_account[:name_of_account]) + # The full name of the individual associated + # with the bank account number (optional) + xml.tag!('bankName', bank_account[:bank_name]) if bank_account[:bank_name] + # The type of electronic check transaction used for the subscription + xml.tag!('echeckType', bank_account[:echeck_type]) + end + end + + # Adds order information (optional) + def add_order(xml, options) + order = options[:order] + return unless order + xml.tag!('order') do + # Merchant-assigned invoice number for the subscription (optional) + xml.tag!('invoiceNumber', order[:invoice_number]) + # Description of the subscription (optional) + xml.tag!('description', order[:description]) + end + end + + # Adds information about the customer + def add_customer(xml, options) + customer = options[:customer] + return unless customer + xml.tag!('customer') do + xml.tag!('type', customer[:type]) if customer[:type] + xml.tag!('id', customer[:id]) if customer[:id] + xml.tag!('email', customer[:email]) if customer[:email] + xml.tag!('phoneNumber', customer[:phone_number]) if customer[:phone_number] + xml.tag!('faxNumber', customer[:fax_number]) if customer[:fax_number] + add_drivers_license(xml, options) + xml.tag!('taxId', customer[:tax_id]) if customer[:tax_id] + end + end + + # Adds the customer's driver's license information (conditional) + def add_drivers_license(xml, options) + return unless customer = options[:customer] + return unless drivers_license = customer[:drivers_license] + xml.tag!('driversLicense') do + # The customer's driver's license number + xml.tag!('number', drivers_license[:number]) + # The customer's driver's license state + xml.tag!('state', drivers_license[:state]) + # The customer's driver's license date of birth + xml.tag!('dateOfBirth', drivers_license[:date_of_birth]) + end + end + + # Adds address information + def add_address(xml, container_name, address) + return if address.blank? + xml.tag!(container_name) do + xml.tag!('firstName', address[:first_name]) + xml.tag!('lastName', address[:last_name]) + xml.tag!('company', address[:company]) + xml.tag!('address', address[:address1]) + xml.tag!('city', address[:city]) + xml.tag!('state', address[:state]) + xml.tag!('zip', address[:zip]) + xml.tag!('country', address[:country]) + end + end + + def expdate(credit_card) + sprintf('%04d-%02d', credit_card.year, credit_card.month) + end + + def recurring_commit(action, request) + url = test? ? test_url : live_url + xml = ssl_post(url, request, 'Content-Type' => 'text/xml') + + response = recurring_parse(action, xml) + + message = response[:message] || response[:text] + test_mode = test? || message =~ /Test Mode/ + success = response[:result_code] == 'Ok' + + Response.new(success, message, response, + :test => test_mode, + :authorization => response[:subscription_id] + ) + end + + def recurring_parse(action, xml) + response = {} + xml = REXML::Document.new(xml) + root = REXML::XPath.first(xml, "//#{RECURRING_ACTIONS[action]}Response") || + REXML::XPath.first(xml, '//ErrorResponse') + if root + root.elements.to_a.each do |node| + recurring_parse_element(response, node) + end + end + + response + end + + def recurring_parse_element(response, node) + if node.has_elements? + node.elements.each { |e| recurring_parse_element(response, e) } + else + response[node.name.underscore.to_sym] = node.text + end + end + end + end +end diff --git a/lib/active_merchant/billing/gateways/authorize_net_cim.rb b/lib/active_merchant/billing/gateways/authorize_net_cim.rb index 284b001a705..fac2913ff37 100644 --- a/lib/active_merchant/billing/gateways/authorize_net_cim.rb +++ b/lib/active_merchant/billing/gateways/authorize_net_cim.rb @@ -28,7 +28,7 @@ module Billing #:nodoc: # 5. Click Submit class AuthorizeNetCimGateway < Gateway self.test_url = 'https://apitest.authorize.net/xml/v1/request.api' - self.live_url = 'https://api.authorize.net/xml/v1/request.api' + self.live_url = 'https://api2.authorize.net/xml/v1/request.api' AUTHORIZE_NET_CIM_NAMESPACE = 'AnetApi/xml/v1/schema/AnetApiSchema.xsd' @@ -94,10 +94,14 @@ class AuthorizeNetCimGateway < Gateway # * :password -- The Authorize.Net Transaction Key. (REQUIRED) # * :test -- +true+ or +false+. If true, perform transactions against the test server. # Otherwise, perform transactions against the production server. + # * :test_requests -- +true+ or +false+. If true, perform transactions without the + # test flag. This is useful when you need to generate card declines, AVS or CVV errors. + # Will hold the same value as :test by default. # * :delimiter -- The delimiter used in the direct response. Default is ',' (comma). def initialize(options = {}) requires!(options, :login, :password) super + @options[:test_requests] = test? if @options[:test_requests].nil? end # Creates a new customer profile along with any customer payment profiles and customer shipping addresses @@ -336,7 +340,7 @@ def update_customer_shipping_address(options) # ==== Transaction # # * :type -- The type of transaction. Can be either :auth_only, :capture_only, :auth_capture, :prior_auth_capture, :refund or :void. (REQUIRED) - # * :amount -- The amount for the tranaction. Formatted with a decimal. For example "4.95" (CONDITIONAL) + # * :amount -- The amount for the transaction. Formatted with a decimal. For example "4.95" (CONDITIONAL) # - :type == :void (NOT USED) # - :type == :refund (OPTIONAL) # - :type == (:auth_only, :capture_only, :auth_capture, :prior_auth_capture) (REQUIRED) @@ -359,31 +363,35 @@ def update_customer_shipping_address(options) # - :type = (:void, :refund, :prior_auth_capture) (NOT USED) # - :type = (:auth_only, :capture_only, :auth_capture) (OPTIONAL) # + # * :recurring_billing -- The recurring billing status (OPTIONAL) + # - :type = (:void, :refund, :prior_auth_capture) (NOT USED) + # - :type = (:auth_only, :capture_only, :auth_capture) (OPTIONAL) + # # * :customer_shipping_address_id -- Payment gateway assigned ID associated with the customer shipping address (CONDITIONAL) # - :type = (:void, :refund) (OPTIONAL) # - :type = (:auth_only, :capture_only, :auth_capture) (NOT USED) # - :type = (:prior_auth_capture) (OPTIONAL) # # ==== For :type == :refund only - # * :credit_card_number_masked -- (CONDITIONAL - requied for credit card refunds is :customer_profile_id AND :customer_payment_profile_id are missing) - # * :bank_routing_number_masked && :bank_account_number_masked -- (CONDITIONAL - requied for electronic check refunds is :customer_profile_id AND :customer_payment_profile_id are missing) (NOT ABLE TO TEST - I keep getting "ACH transactions are not accepted by this merchant." when trying to make a payment and, until that's possible I can't refund (wiseleyb@gmail.com)) + # * :credit_card_number_masked -- (CONDITIONAL - required for credit card refunds if :customer_profile_id AND :customer_payment_profile_id are missing) + # * :bank_routing_number_masked && :bank_account_number_masked -- (CONDITIONAL - required for electronic check refunds if :customer_profile_id AND :customer_payment_profile_id are missing) (NOT ABLE TO TEST - I keep getting "ACH transactions are not accepted by this merchant." when trying to make a payment and, until that's possible I can't refund (wiseleyb@gmail.com)) def create_customer_profile_transaction(options) requires!(options, :transaction) requires!(options[:transaction], :type) case options[:transaction][:type] - when :void - requires!(options[:transaction], :trans_id) - when :refund - requires!(options[:transaction], :trans_id) && - ( - (options[:transaction][:customer_profile_id] && options[:transaction][:customer_payment_profile_id]) || - options[:transaction][:credit_card_number_masked] || - (options[:transaction][:bank_routing_number_masked] && options[:transaction][:bank_account_number_masked]) - ) - when :prior_auth_capture - requires!(options[:transaction], :amount, :trans_id) - else - requires!(options[:transaction], :amount, :customer_profile_id, :customer_payment_profile_id) + when :void + requires!(options[:transaction], :trans_id) + when :refund + requires!(options[:transaction], :trans_id) && + ( + (options[:transaction][:customer_profile_id] && options[:transaction][:customer_payment_profile_id]) || + options[:transaction][:credit_card_number_masked] || + (options[:transaction][:bank_routing_number_masked] && options[:transaction][:bank_account_number_masked]) + ) + when :prior_auth_capture + requires!(options[:transaction], :amount, :trans_id) + else + requires!(options[:transaction], :amount, :customer_profile_id, :customer_payment_profile_id) end request = build_request(:create_customer_profile_transaction, options) commit(:create_customer_profile_transaction, request) @@ -406,13 +414,13 @@ def create_customer_profile_transaction(options) # * :customer_profile_id -- The Customer Profile ID of the customer to use in this transaction. (CONDITIONAL :customer_payment_profile_id must be included if used) # * :customer_payment_profile_id -- The Customer Payment Profile ID of the Customer Payment Profile to use in this transaction. (CONDITIONAL :customer_profile_id must be included if used) # - # * :credit_card_number_masked -- Four Xs follwed by the last four digits of the credit card (CONDITIONAL - used if customer_profile_id and customer_payment_profile_id aren't given) + # * :credit_card_number_masked -- Four Xs followed by the last four digits of the credit card (CONDITIONAL - used if customer_profile_id and customer_payment_profile_id aren't given) # - # * :bank_routing_number_masked -- The last four gidits of the routing number to be refunded (CONDITIONAL - must be used with :bank_account_number_masked) - # * :bank_account_number_masked -- The last four digis of the bank account number to be refunded, Ex. XXXX1234 (CONDITIONAL - must be used with :bank_routing_number_masked) + # * :bank_routing_number_masked -- The last four digits of the routing number to be refunded (CONDITIONAL - must be used with :bank_account_number_masked) + # * :bank_account_number_masked -- The last four digits of the bank account number to be refunded, Ex. XXXX1234 (CONDITIONAL - must be used with :bank_routing_number_masked) # # * :tax - A hash containing tax information for the refund (OPTIONAL - :amount, :name (31 characters), :description (255 characters)) - # * :duty - A hash containting duty information for the refund (OPTIONAL - :amount, :name (31 characters), :description (255 characters)) + # * :duty - A hash containing duty information for the refund (OPTIONAL - :amount, :name (31 characters), :description (255 characters)) # * :shipping - A hash containing shipping information for the refund (OPTIONAL - :amount, :name (31 characters), :description (255 characters)) def create_customer_profile_transaction_for_refund(options) requires!(options, :transaction) @@ -566,6 +574,7 @@ def build_get_customer_profile_ids_request(xml, options) def build_get_customer_payment_profile_request(xml, options) xml.tag!('customerProfileId', options[:customer_profile_id]) xml.tag!('customerPaymentProfileId', options[:customer_payment_profile_id]) + xml.tag!('unmaskExpirationDate', options[:unmask_expiration_date]) if options[:unmask_expiration_date] xml.target! end @@ -605,10 +614,12 @@ def build_update_customer_shipping_address_request(xml, options) def build_create_customer_profile_transaction_request(xml, options) options[:extra_options] ||= {} - options[:extra_options].merge!('x_test_request' => 'TRUE') if @options[:test] + options[:extra_options]['x_delim_char'] = @options[:delimiter] if @options[:delimiter] add_transaction(xml, options[:transaction]) - tag_unless_blank(xml, 'extraOptions', format_extra_options(options[:extra_options])) + xml.tag!('extraOptions') do + xml.cdata!(format_extra_options(options[:extra_options])) + end unless options[:extra_options].blank? xml.target! end @@ -654,35 +665,40 @@ def add_transaction(xml, transaction) xml.tag!(CIM_TRANSACTION_TYPES[transaction[:type]]) do # The amount to be billed to the customer case transaction[:type] - when :void - tag_unless_blank(xml,'customerProfileId', transaction[:customer_profile_id]) - tag_unless_blank(xml,'customerPaymentProfileId', transaction[:customer_payment_profile_id]) - tag_unless_blank(xml,'customerShippingAddressId', transaction[:customer_shipping_address_id]) - xml.tag!('transId', transaction[:trans_id]) - when :refund - #TODO - add lineItems field - xml.tag!('amount', transaction[:amount]) - tag_unless_blank(xml, 'customerProfileId', transaction[:customer_profile_id]) - tag_unless_blank(xml, 'customerPaymentProfileId', transaction[:customer_payment_profile_id]) - tag_unless_blank(xml, 'customerShippingAddressId', transaction[:customer_shipping_address_id]) - tag_unless_blank(xml, 'creditCardNumberMasked', transaction[:credit_card_number_masked]) - tag_unless_blank(xml, 'bankRoutingNumberMasked', transaction[:bank_routing_number_masked]) - tag_unless_blank(xml, 'bankAccountNumberMasked', transaction[:bank_account_number_masked]) - xml.tag!('transId', transaction[:trans_id]) - add_tax(xml, transaction[:tax]) if transaction[:tax] - add_duty(xml, transaction[:duty]) if transaction[:duty] - add_shipping(xml, transaction[:shipping]) if transaction[:shipping] - when :prior_auth_capture - xml.tag!('amount', transaction[:amount]) - xml.tag!('transId', transaction[:trans_id]) - else - xml.tag!('amount', transaction[:amount]) - xml.tag!('customerProfileId', transaction[:customer_profile_id]) - xml.tag!('customerPaymentProfileId', transaction[:customer_payment_profile_id]) - xml.tag!('approvalCode', transaction[:approval_code]) if transaction[:type] == :capture_only + when :void + tag_unless_blank(xml, 'customerProfileId', transaction[:customer_profile_id]) + tag_unless_blank(xml, 'customerPaymentProfileId', transaction[:customer_payment_profile_id]) + tag_unless_blank(xml, 'customerShippingAddressId', transaction[:customer_shipping_address_id]) + xml.tag!('transId', transaction[:trans_id]) + when :refund + xml.tag!('amount', transaction[:amount]) + tag_unless_blank(xml, 'customerProfileId', transaction[:customer_profile_id]) + tag_unless_blank(xml, 'customerPaymentProfileId', transaction[:customer_payment_profile_id]) + tag_unless_blank(xml, 'customerShippingAddressId', transaction[:customer_shipping_address_id]) + tag_unless_blank(xml, 'creditCardNumberMasked', transaction[:credit_card_number_masked]) + tag_unless_blank(xml, 'bankRoutingNumberMasked', transaction[:bank_routing_number_masked]) + tag_unless_blank(xml, 'bankAccountNumberMasked', transaction[:bank_account_number_masked]) + add_order(xml, transaction[:order]) if transaction[:order].present? + xml.tag!('transId', transaction[:trans_id]) + add_tax(xml, transaction[:tax]) if transaction[:tax] + add_duty(xml, transaction[:duty]) if transaction[:duty] + add_shipping(xml, transaction[:shipping]) if transaction[:shipping] + when :prior_auth_capture + xml.tag!('amount', transaction[:amount]) + add_order(xml, transaction[:order]) if transaction[:order].present? + xml.tag!('transId', transaction[:trans_id]) + else + xml.tag!('amount', transaction[:amount]) + xml.tag!('customerProfileId', transaction[:customer_profile_id]) + xml.tag!('customerPaymentProfileId', transaction[:customer_payment_profile_id]) + xml.tag!('approvalCode', transaction[:approval_code]) if transaction[:type] == :capture_only + add_order(xml, transaction[:order]) if transaction[:order].present? + + end + if [:auth_capture, :auth_only, :capture_only].include?(transaction[:type]) + xml.tag!('recurringBilling', transaction[:recurring_billing]) if transaction.has_key?(:recurring_billing) end - add_order(xml, transaction[:order]) if transaction[:order].present? - unless [:void,:refund,:prior_auth_capture].include?(transaction[:type]) + unless [:void, :refund, :prior_auth_capture].include?(transaction[:type]) tag_unless_blank(xml, 'cardCode', transaction[:card_code]) end end @@ -783,7 +799,7 @@ def add_credit_card(xml, credit_card) return unless credit_card xml.tag!('creditCard') do # The credit card number used for payment of the subscription - xml.tag!('cardNumber', credit_card.number) + xml.tag!('cardNumber', full_or_masked_card_number(credit_card.number)) # The expiration date of the credit card used for the subscription xml.tag!('expirationDate', expdate(credit_card)) # Note that Authorize.net does not save CVV codes as part of the @@ -837,20 +853,24 @@ def add_drivers_license(xml, drivers_license) def commit(action, request) url = test? ? test_url : live_url - xml = ssl_post(url, request, "Content-Type" => "text/xml") + xml = ssl_post(url, request, 'Content-Type' => 'text/xml') response_params = parse(action, xml) - message = response_params['messages']['message']['text'] - test_mode = test? || message =~ /Test Mode/ + message_element= response_params['messages']['message'] + first_error = message_element.is_a?(Array) ? message_element.first : message_element + message = first_error['text'] + test_mode = @options[:test_requests] || message =~ /Test Mode/ success = response_params['messages']['result_code'] == 'Ok' response_params['direct_response'] = parse_direct_response(response_params['direct_response']) if response_params['direct_response'] transaction_id = response_params['direct_response']['transaction_id'] if response_params['direct_response'] - Response.new(success, message, response_params, - :test => test_mode, - :authorization => transaction_id || response_params['customer_profile_id'] || (response_params['profile'] ? response_params['profile']['customer_profile_id'] : nil) - ) + response_options = {} + response_options[:test] = test_mode + response_options[:authorization] = transaction_id || response_params['customer_profile_id'] || (response_params['profile'] ? response_params['profile']['customer_profile_id'] : nil) + response_options[:error_code] = first_error['code'] unless success + + Response.new(success, message, response_params, response_options) end def tag_unless_blank(xml, tag_name, data) @@ -858,7 +878,7 @@ def tag_unless_blank(xml, tag_name, data) end def format_extra_options(options) - options.map{ |k, v| "#{k}=#{v}" }.join('&') unless options.nil? + options&.map { |k, v| "#{k}=#{v}" }&.join('&') end def parse_direct_response(params) @@ -921,7 +941,7 @@ def parse_direct_response(params) def parse(action, xml) xml = REXML::Document.new(xml) root = REXML::XPath.first(xml, "//#{CIM_ACTIONS[action]}Response") || - REXML::XPath.first(xml, "//ErrorResponse") + REXML::XPath.first(xml, '//ErrorResponse') if root response = parse_element(root) end @@ -932,7 +952,7 @@ def parse(action, xml) def parse_element(node) if node.has_elements? response = {} - node.elements.each{ |e| + node.elements.each { |e| key = e.name.underscore value = parse_element(e) if response.has_key?(key) @@ -951,6 +971,10 @@ def parse_element(node) response end + + def full_or_masked_card_number(card_number) + !card_number.nil? && card_number.length == 4 ? "XXXX#{card_number}" : card_number + end end end end diff --git a/lib/active_merchant/billing/gateways/axcessms.rb b/lib/active_merchant/billing/gateways/axcessms.rb new file mode 100644 index 00000000000..7f9207ff6a2 --- /dev/null +++ b/lib/active_merchant/billing/gateways/axcessms.rb @@ -0,0 +1,181 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class AxcessmsGateway < Gateway + self.test_url = 'https://test.ctpe.io/payment/ctpe' + self.live_url = 'https://ctpe.io/payment/ctpe' + + self.supported_countries = %w(AD AT BE BG BR CA CH CY CZ DE DK EE ES FI FO FR GB + GI GR HR HU IE IL IM IS IT LI LT LU LV MC MT MX NL + NO PL PT RO RU SE SI SK TR US VA) + + self.supported_cardtypes = [:visa, :master, :american_express, :discover, :jcb, :maestro] + + self.homepage_url = 'http://www.axcessms.com/' + self.display_name = 'Axcess MS' + self.money_format = :dollars + self.default_currency = 'GBP' + + API_VERSION = '1.0' + PAYMENT_CODE_PREAUTHORIZATION = 'CC.PA' + PAYMENT_CODE_DEBIT = 'CC.DB' + PAYMENT_CODE_CAPTURE = 'CC.CP' + PAYMENT_CODE_REVERSAL = 'CC.RV' + PAYMENT_CODE_REFUND = 'CC.RF' + PAYMENT_CODE_REBILL = 'CC.RB' + + def initialize(options={}) + requires!(options, :sender, :login, :password, :channel) + super + end + + def purchase(money, payment, options={}) + payment_code = payment.respond_to?(:number) ? PAYMENT_CODE_DEBIT : PAYMENT_CODE_REBILL + commit(payment_code, money, payment, options) + end + + def authorize(money, authorization, options={}) + commit(PAYMENT_CODE_PREAUTHORIZATION, money, authorization, options) + end + + def capture(money, authorization, options={}) + commit(PAYMENT_CODE_CAPTURE, money, authorization, options) + end + + def refund(money, authorization, options={}) + commit(PAYMENT_CODE_REFUND, money, authorization, options) + end + + def void(authorization, options={}) + commit(PAYMENT_CODE_REVERSAL, nil, authorization, options) + end + + def verify(credit_card, options={}) + MultiResponse.run(:use_first_response) do |r| + r.process { authorize(100, credit_card, options) } + r.process(:ignore_result) { void(r.authorization, options) } + end + end + + private + + def commit(paymentcode, money, payment, options) + options[:mode] ||= (test? ? 'INTEGRATOR_TEST' : 'LIVE') + request = build_request(paymentcode, money, payment, options) + + headers = { + 'Content-Type' => 'application/x-www-form-urlencoded;charset=UTF-8' + } + + response = parse(ssl_post((test? ? test_url : live_url), "load=#{CGI.escape(request)}", headers)) + success = (response[:result] == 'ACK') + message = "#{response[:reason]} - #{response[:return]}" + authorization = response[:unique_id] + + Response.new(success, message, response, + :authorization => authorization, + :test => (response[:mode] != 'LIVE') + ) + end + + def parse(body) + return {} if body.blank? + + xml = REXML::Document.new(body) + + response = {} + xml.root.elements.to_a.each do |node| + parse_element(response, node) + end + + response[:mode] = REXML::XPath.first(xml, '//Transaction').attributes['mode'] + + response + end + + def parse_element(response, node) + if node.has_attributes? + node.attributes.each { |name, value| response["#{node.name}_#{name}".underscore.to_sym] = value } + end + + if node.has_elements? + node.elements.each { |element| parse_element(response, element) } + else + response[node.name.underscore.to_sym] = node.text + end + end + + def build_request(payment_code, money, payment, options) + xml = Builder::XmlMarkup.new :indent => 2 + xml.instruct! + xml.tag! 'Request', 'version' => API_VERSION do + xml.tag! 'Header' do + xml.tag! 'Security', 'sender' => @options[:sender] + end + xml.tag! 'Transaction', 'mode' => options[:mode], 'channel' => @options[:channel], 'response' => 'SYNC' do + xml.tag! 'User', 'login' => @options[:login], 'pwd' => @options[:password] + xml.tag! 'Identification' do + xml.tag! 'TransactionID', options[:order_id] || generate_unique_id + xml.tag! 'ReferenceID', payment unless payment.respond_to?(:number) + end + + xml.tag! 'Payment', 'code' => payment_code do + xml.tag! 'Memo', options[:memo] unless options[:memo].blank? + xml.tag! 'Presentation' do + xml.tag! 'Amount', amount(money) + xml.tag! 'Currency', (options[:currency] || currency(money)) + xml.tag! 'Usage', options[:description] + end + end + + if payment.respond_to?(:number) + add_payment(xml, payment) + + xml.tag! 'Customer' do + add_customer_name(xml, payment) + add_address(xml, options[:billing_address] || options[:address]) + add_contact(xml, options) + end + end + end + end + + xml.target! + end + + def add_contact(xml, options) + xml.tag! 'Contact' do + xml.tag! 'Email', (options[:email] || 'unknown@example.com') + xml.tag! 'Ip', (options[:ip] || '127.0.0.1') + end + end + + def add_customer_name(xml, payment) + xml.tag! 'Name' do + xml.tag! 'Given', payment.first_name + xml.tag! 'Family', payment.last_name + end + end + + def add_payment(xml, payment) + xml.tag! 'Account' do + xml.tag! 'Number', payment.number + xml.tag! 'Holder', payment.name + xml.tag! 'Brand', payment.brand + xml.tag! 'Expiry', 'month' => payment.month, 'year' => payment.year + xml.tag! 'Verification', payment.verification_value + end + end + + def add_address(xml, address) + raise ArgumentError.new('Address is required') unless address + xml.tag! 'Address' do + xml.tag! 'Street', "#{address[:address1]} #{address[:address2]}".strip + xml.tag! 'City', address[:city] + xml.tag! 'State', address[:state] + xml.tag! 'Zip', address[:zip] + xml.tag! 'Country', address[:country] + end + end + end + end +end diff --git a/lib/active_merchant/billing/gateways/balanced.rb b/lib/active_merchant/billing/gateways/balanced.rb index 63350ebb677..f9b7accff02 100644 --- a/lib/active_merchant/billing/gateways/balanced.rb +++ b/lib/active_merchant/billing/gateways/balanced.rb @@ -2,7 +2,6 @@ module ActiveMerchant #:nodoc: module Billing #:nodoc: - # For more information on Balanced visit https://www.balancedpayments.com # or visit #balanced on irc.freenode.net # @@ -17,419 +16,207 @@ module Billing #:nodoc: # 4. When you're ready to generate a production API key click the "Go # live" button on the Balanced dashboard and fill in your marketplace # details. - # - # ==== Overview - # - # Balanced provides a RESTful API, all entities within Balanced are - # represented by their respective URIs, these are returned in the - # `authorization` parameter of the Active Merchant Response object. - # - # All Response objects will contain a hash property called `params` which - # holds the raw JSON dictionary returned by Balanced. You can find - # properties about the operation performed and the object that represents - # it within this hash. - # - # All operations within Balanced are tied to an account, as such, when you - # perform an `authorization` or a `capture` with a new credit card you - # must ensure you also pass the `:email` property within the `options` - # parameter. - # - # For more details about Balanced's API visit: - # https://www.balancedpayments.com/docs - # - # ==== Terminology & Transaction Flow - # - # * An `authorization` operation will return a Hold URI. An `authorization` - # within Balanced is valid until the `expires_at` property. You can see the - # exact date of the expiry on the Response object by inspecting the - # property `response.params['expires_at']`. The resulting Hold may be - # `capture`d or `void`ed at any time before the `expires_at` date for - # any amount up to the full amount of the original `authorization`. - # * A `capture` operation will return a Debit URI. You must pass the URI of - # the previously performed `authorization` - # * A `purchase` will create a Hold and Debit in a single operation and - # return the URI of the resulting Debit. - # * A `void` operation must be performed on an existing `authorization` - # and will result in releasing the funds reserved by the - # `authorization`. - # * The `refund` operation must be performed on a previously captured - # Debit URI. You may refund any fraction of the original amount of the - # debit up to the original total. - # class BalancedGateway < Gateway - VERSION = '1.0.0' + VERSION = '2.0.0' - TEST_URL = LIVE_URL = 'https://api.balancedpayments.com' + self.live_url = 'https://api.balancedpayments.com' - # The countries the gateway supports merchants from as 2 digit ISO - # country codes self.supported_countries = ['US'] self.supported_cardtypes = [:visa, :master, :american_express, :discover] self.homepage_url = 'https://www.balancedpayments.com/' self.display_name = 'Balanced' self.money_format = :cents - class Error < ActiveMerchant::ActiveMerchantError - attr_reader :response - - def initialize(response, msg=nil) - @response = response - super(msg || response['description']) - end - end - - class CardDeclined < Error - end - # Creates a new BalancedGateway # - # The gateway requires that a valid api_key be passed in the +options+ - # hash. - # # ==== Options # # * :login -- The Balanced API Secret (REQUIRED) def initialize(options = {}) requires!(options, :login) super - initialize_marketplace(options[:marketplace] || load_marketplace) end - # Performs an authorization (Hold in Balanced nonclementure), which - # reserves the funds on the customer's credit card, but does not charge - # the card. An authorization is valid until the `expires_at` field in - # the params Hash passes. See `response.params['expires_at']`. The exact - # amount of time until an authorization expires depends on the card - # issuer. - # - # If you pass a previously tokenized `credit_card` URI the only other - # parameter required is `money`. If you pass `credit_card` as a hash of - # credit card information you must also pass `options` with a `:email` - # entry. - # - # ==== Parameters - # - # * money -- The amount to be authorized as an Integer value in cents. - # * credit_card -- A hash of credit card details for this - # transaction or the URI of a card previously stored in Balanced. - # * options -- A hash of optional parameters. - # - # ==== Options - # - # If you are passing a new credit card you must pass one of these two - # parameters - # - # * email -- the email address of user associated with this - # purchase. - # * account_uri -- `account_uri` is the URI of an existing - # Balanced account. - def authorize(money, credit_card, options = {}) - if credit_card.respond_to?(:number) - requires!(options, :email) unless options[:account_uri] - end - + def purchase(money, payment_method, options = {}) post = {} - post[:amount] = money + add_amount(post, money) post[:description] = options[:description] - - create_or_find_account(post, options) - add_credit_card(post, credit_card, options) - add_address(credit_card, options) - - create_transaction(:post, @holds_uri, post) - rescue Error => ex - failed_response(ex.response) - end - - # Perform a purchase, which is an authorization and capture in a single - # operation. - # - # ==== Parameters - # - # * money -- The amount to be purchased as an Integer value in cents. - # * credit_card -- A hash of credit card details for this - # transaction or the URI of a card previously stored in Balanced. - # * options -- A hash of optional parameters. - # - # ==== Options - # - # If you are passing a new credit card you must pass one of these two - # parameters - # - # * email -- the email address of user associated with this - # purchase. - # * account_uri -- `account_uri` is the URI of an existing - # Balanced account. - def purchase(money, credit_card, options = {}) - if credit_card.respond_to?('number') - requires!(options, :email) unless options[:account_uri] + add_common_params(post, options) + + MultiResponse.run do |r| + identifier = if(payment_method.respond_to?(:number)) + r.process { store(payment_method, options) } + r.authorization + else + payment_method + end + r.process { commit('debits', "cards/#{card_identifier_from(identifier)}/debits", post) } end + end + def authorize(money, payment_method, options = {}) post = {} - post[:amount] = money + add_amount(post, money) post[:description] = options[:description] - - create_or_find_account(post, options) - add_credit_card(post, credit_card, options) - add_address(credit_card, options) - - create_transaction(:post, @debits_uri, post) - rescue Error => ex - failed_response(ex.response) + add_common_params(post, options) + + MultiResponse.run do |r| + identifier = if(payment_method.respond_to?(:number)) + r.process { store(payment_method, options) } + r.authorization + else + payment_method + end + r.process { commit('card_holds', "cards/#{card_identifier_from(identifier)}/card_holds", post) } + end end - # Captures the funds from an authorized transaction (Hold). - # - # ==== Parameters - # - # * money -- The amount to be captured as an Integer value in - # cents. If omitted the full amount of the original authorization - # transaction will be captured. - # * authorization -- The uri of an authorization returned from - # an authorize request. - # - # ==== Options - # - # * description -- A string that will be displayed on the - # Balanced dashboard - def capture(money, authorization, options = {}) + def capture(money, identifier, options = {}) post = {} - post[:hold_uri] = authorization - post[:amount] = money if money + add_amount(post, money) post[:description] = options[:description] if options[:description] - post[:on_behalf_of_uri] = options[:on_behalf_of_uri] if options[:on_behalf_of_uri] + add_common_params(post, options) - create_transaction(:post, @debits_uri, post) - rescue Error => ex - failed_response(ex.response) + commit('debits', "card_holds/#{reference_identifier_from(identifier)}/debits", post) end - # Void a previous authorization (Hold) - # - # ==== Parameters - # - # * authorization -- The uri of the authorization returned from - # an `authorize` request. - def void(authorization, options = {}) + def void(identifier, options = {}) post = {} post[:is_void] = true + add_common_params(post, options) - create_transaction(:put, authorization, post) - rescue Error => ex - failed_response(ex.response) + commit('card_holds', "card_holds/#{reference_identifier_from(identifier)}", post, :put) end - # Refund a transaction. - # - # Returns the money debited from a card to the card from the - # marketplace's escrow balance. - # - # ==== Parameters - # - # * debit_uri -- The uri of the original transaction against - # which the refund is being issued. - # * options -- A hash of parameters. - # - # ==== Options - # - # * `:amount` -- specify an amount if you want to perform a - # partial refund. This value will default to the total amount of the - # debit that has not been refunded so far. - def refund(amount, debit_uri = "deprecated", options = {}) - if(debit_uri == "deprecated" || debit_uri.kind_of?(Hash)) - deprecated "Calling the refund method without an amount parameter is deprecated and will be removed in a future version." - return refund(options[:amount], amount, options) - end - - requires!(debit_uri) + def refund(money, identifier, options = {}) post = {} - post[:debit_uri] = debit_uri - post[:amount] = amount + add_amount(post, money) post[:description] = options[:description] - create_transaction(:post, @refunds_uri, post) - rescue Error => ex - failed_response(ex.response) + add_common_params(post, options) + + commit('refunds', "debits/#{reference_identifier_from(identifier)}/refunds", post) end - # Stores a card and email address - # - # ==== Parameters - # - # * credit_card -- - def store(credit_card, options = {}) - requires!(options, :email) + def store(credit_card, options={}) post = {} - account_uri = create_or_find_account(post, options) - if credit_card.respond_to? :number - add_credit_card(post, credit_card, options) - else - associate_card_to_account(account_uri, credit_card) - credit_card - end - rescue Error => ex - failed_response(ex.response) - end - private + post[:number] = credit_card.number + post[:expiration_month] = credit_card.month + post[:expiration_year] = credit_card.year + post[:cvv] = credit_card.verification_value if credit_card.verification_value? + post[:name] = credit_card.name if credit_card.name - # Load URIs for this marketplace by inspecting the marketplace object - # returned from the uri. http://en.wikipedia.org/wiki/HATEOAS - def load_marketplace - response = http_request(:get, '/v1/marketplaces') - if error?(response) - raise Error.new(response, 'Invalid login credentials supplied') - end - response['items'][0] - end + add_address(post, options) - def initialize_marketplace(marketplace) - @marketplace_uri = marketplace['uri'] - @holds_uri = marketplace['holds_uri'] - @debits_uri = marketplace['debits_uri'] - @cards_uri = marketplace['cards_uri'] - @accounts_uri = marketplace['accounts_uri'] - @refunds_uri = marketplace['refunds_uri'] + commit('cards', 'cards', post) end - def create_or_find_account(post, options) - account_uri = nil - - if options.has_key? :account_uri - account_uri = options[:account_uri] - end - - if account_uri == nil - post[:email_address] = options[:email] - - # create an account - response = http_request(:post, @accounts_uri, post) + private - if response.has_key? 'uri' - account_uri = response['uri'] - elsif error?(response) - # lookup account from Balanced, account_uri should be in the - # exception in a dictionary called extras - account_uri = response['extras']['account_uri'] - raise Error.new(response) unless account_uri - end + def reference_identifier_from(identifier) + case identifier + when %r{\|} + uri = identifier. + split('|'). + detect { |part| part.size > 0 } + uri.split('/')[2] + when %r{\/} + identifier.split('/')[5] + else + identifier end - - post[:account_uri] = account_uri - - account_uri end - def add_address(credit_card, options) - return unless credit_card.kind_of?(Hash) - if address = options[:billing_address] || options[:address] - credit_card[:street_address] = address[:address1] if address[:address1] - credit_card[:street_address] += ' ' + address[:address2] if address[:address2] - credit_card[:postal_code] = address[:zip] if address[:zip] - credit_card[:country] = address[:country] if address[:country] - end + def card_identifier_from(identifier) + identifier.split('/').last end - def add_credit_card(post, credit_card, options) - if credit_card.respond_to? :number - card = {} - card[:card_number] = credit_card.number - card[:expiration_month] = credit_card.month - card[:expiration_year] = credit_card.year - card[:security_code] = credit_card.verification_value if credit_card.verification_value? - card[:name] = credit_card.name if credit_card.name - - add_address(card, options) - - response = http_request(:post, @cards_uri, card) - if error?(response) - raise CardDeclined, response - end - card_uri = response['uri'] - - associate_card_to_account(post[:account_uri], card_uri) + def add_amount(post, money) + post[:amount] = amount(money) if money + end - post[:card_uri] = card_uri - elsif credit_card.kind_of?(String) - post[:card_uri] = credit_card + def add_address(post, options) + address = (options[:billing_address] || options[:address]) + if(address && address[:zip].present?) + post[:address] = {} + post[:address][:line1] = address[:address1] if address[:address1] + post[:address][:line2] = address[:address2] if address[:address2] + post[:address][:city] = address[:city] if address[:city] + post[:address][:state] = address[:state] if address[:state] + post[:address][:postal_code] = address[:zip] if address[:zip] + post[:address][:country_code] = address[:country] if address[:country] end - - post[:card_uri] end - def associate_card_to_account(account_uri, card_uri) - http_request(:put, account_uri, :card_uri => card_uri) + def add_common_params(post, options) + post[:appears_on_statement_as] = options[:appears_on_statement_as] + post[:on_behalf_of_uri] = options[:on_behalf_of_uri] + post[:meta] = options[:meta] end - def http_request(method, url, parameters={}, meta={}) - begin - if method == :get - raw_response = ssl_get(LIVE_URL + url, headers(meta)) - else - raw_response = ssl_request(method, - LIVE_URL + url, - post_data(parameters), - headers(meta)) - end - parse(raw_response) + def commit(entity_name, path, post, method=:post) + raw_response = begin + parse(ssl_request( + method, + live_url + "/#{path}", + post_data(post), + headers + )) rescue ResponseError => e - raw_response = e.response.body - response_error(raw_response) - rescue JSON::ParserError - json_error(raw_response) + raise unless(e.response.code.to_s =~ /4\d\d/) + parse(e.response.body) end - end - - def create_transaction(method, url, parameters, meta={}) - response = http_request(method, url, parameters, meta) - success = !error?(response) - Response.new(success, - (success ? "Transaction approved" : response["description"]), - response, - :test => (@marketplace_uri.index("TEST") ? true : false), - :authorization => response["uri"] + Response.new( + success_from(entity_name, raw_response), + message_from(raw_response), + raw_response, + authorization: authorization_from(entity_name, raw_response), + test: test? ) end - def failed_response(response) - is_test = false - if @marketplace_uri - is_test = (@marketplace_uri.index("TEST") ? true : false) + def success_from(entity_name, raw_response) + entity = (raw_response[entity_name] || []).first + if(!entity) + false + elsif((entity_name == 'refunds') && entity.include?('status')) + %w(succeeded pending).include?(entity['status']) + elsif(entity.include?('status')) + (entity['status'] == 'succeeded') + elsif(entity_name == 'cards') + !!entity['id'] + else + false end - - Response.new(false, - response["description"], - response, - :test => is_test - ) end - def parse(body) - JSON.parse(body) + def message_from(raw_response) + if(raw_response['errors']) + error = raw_response['errors'].first + (error['additional'] || error['message'] || error['description']) + else + 'Success' + end end - def response_error(raw_response) - begin - parse(raw_response) - rescue JSON::ParserError - json_error(raw_response) - end + def authorization_from(entity_name, raw_response) + entity = (raw_response[entity_name] || []).first + (entity && entity['id']) end - def json_error(raw_response) - msg = 'Invalid response received from the Balanced API. Please contact support@balancedpayments.com if you continue to receive this message.' - msg += " (The raw response returned by the API was #{raw_response.inspect})" + def parse(body) + JSON.parse(body) + rescue JSON::ParserError + message = 'Invalid response received from the Balanced API. Please contact support@balancedpayments.com if you continue to receive this message.' + message += " (The raw response returned by the API was #{raw_response.inspect})" { - "error" => { - "message" => msg - } + 'errors' => [{ + 'message' => message + }] } end - def error?(response) - response.key?('status_code') - end - def post_data(params) return nil unless params @@ -444,23 +231,24 @@ def post_data(params) else "#{key}=#{CGI.escape(value.to_s)}" end - end.compact.join("&") + end.compact.join('&') end - def headers(meta={}) - @@ua ||= JSON.dump({ - :bindings_version => ActiveMerchant::VERSION, - :lang => 'ruby', - :lang_version => "#{RUBY_VERSION} p#{RUBY_PATCHLEVEL} (#{RUBY_RELEASE_DATE})", - :lib_version => BalancedGateway::VERSION, - :platform => RUBY_PLATFORM, - :publisher => 'active_merchant' - }) + def headers + @@ua ||= JSON.dump( + bindings_version: ActiveMerchant::VERSION, + lang: 'ruby', + lang_version: "#{RUBY_VERSION} p#{RUBY_PATCHLEVEL} (#{RUBY_RELEASE_DATE})", + lib_version: BalancedGateway::VERSION, + platform: RUBY_PLATFORM, + publisher: 'active_merchant' + ) { - "Authorization" => "Basic " + Base64.encode64(@options[:login].to_s + ":").strip, - "User-Agent" => "Balanced/v1 ActiveMerchantBindings/#{ActiveMerchant::VERSION}", - "X-Balanced-User-Agent" => @@ua, + 'Authorization' => 'Basic ' + Base64.encode64(@options[:login].to_s + ':').strip, + 'User-Agent' => "Balanced/v1.1 ActiveMerchantBindings/#{ActiveMerchant::VERSION}", + 'Accept' => 'application/vnd.api+json;revision=1.1', + 'X-Balanced-User-Agent' => @@ua, } end end diff --git a/lib/active_merchant/billing/gateways/bambora_apac.rb b/lib/active_merchant/billing/gateways/bambora_apac.rb new file mode 100644 index 00000000000..6cfe3a82bdc --- /dev/null +++ b/lib/active_merchant/billing/gateways/bambora_apac.rb @@ -0,0 +1,226 @@ +require 'nokogiri' + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class BamboraApacGateway < Gateway + self.live_url = 'https://www.bambora.co.nz/interface/api' + self.test_url = 'https://demo.bambora.co.nz/interface/api' + + self.supported_countries = ['AU', 'NZ'] + self.supported_cardtypes = [:visa, :master, :american_express, :diners_club, :jcb] + + self.homepage_url = 'http://www.bambora.com/' + self.display_name = 'Bambora Asia-Pacific' + + self.money_format = :cents + + STANDARD_ERROR_CODE_MAPPING = { + '05' => STANDARD_ERROR_CODE[:card_declined], + '06' => STANDARD_ERROR_CODE[:processing_error], + '14' => STANDARD_ERROR_CODE[:invalid_number], + '54' => STANDARD_ERROR_CODE[:expired_card], + } + + def initialize(options={}) + requires!(options, :username, :password) + super + end + + def purchase(money, payment, options={}) + commit('SubmitSinglePayment') do |xml| + xml.Transaction do + xml.CustRef options[:order_id] + add_amount(xml, money) + xml.TrnType '1' + add_payment(xml, payment) + add_credentials(xml, options) + xml.TrnSource options[:ip] + end + end + end + + def authorize(money, payment, options={}) + commit('SubmitSinglePayment') do |xml| + xml.Transaction do + xml.CustRef options[:order_id] + add_amount(xml, money) + xml.TrnType '2' + add_payment(xml, payment) + add_credentials(xml, options) + xml.TrnSource options[:ip] + end + end + end + + def capture(money, authorization, options={}) + commit('SubmitSingleCapture') do |xml| + xml.Capture do + xml.Receipt authorization + add_amount(xml, money) + add_credentials(xml, options) + end + end + end + + def refund(money, authorization, options={}) + commit('SubmitSingleRefund') do |xml| + xml.Refund do + xml.Receipt authorization + add_amount(xml, money) + add_credentials(xml, options) + end + end + end + + def void(money, authorization, options={}) + commit('SubmitSingleVoid') do |xml| + xml.Void do + xml.Receipt authorization + add_amount(xml, money) + add_credentials(xml, options) + end + end + end + + def store(payment, options={}) + commit('TokeniseCreditCard') do |xml| + xml.TokeniseCreditCard do + xml.CardNumber payment.number + xml.ExpM format(payment.month, :two_digits) + xml.ExpY format(payment.year, :four_digits) + xml.TokeniseAlgorithmID options[:tokenise_algorithm_id] || 2 + xml.UserName @options[:username] + xml.Password @options[:password] + end + end + end + + def supports_scrubbing? + true + end + + def scrub(transcript) + transcript. + gsub(%r(()[^<]+(<))i, '\1[FILTERED]\2'). + gsub(%r(()[^<]+(<))i, '\1[FILTERED]\2'). + gsub(%r(()[^<]+(<))i, '\1[FILTERED]\2') + end + + private + + def add_credentials(xml, options) + xml.AccountNumber options[:account_number] if options[:account_number] + xml.Security do + xml.UserName @options[:username] + xml.Password @options[:password] + end + end + + def add_amount(xml, money) + xml.Amount amount(money) + end + + def add_payment(xml, payment) + if payment.is_a?(String) + add_token(xml, payment) + else + add_credit_card(xml, payment) + end + end + + def add_token(xml, payment) + xml.CreditCard do + xml.TokeniseAlgorithmID options[:tokenise_algorithm_id] || 2 + xml.CardNumber payment + end + end + + def add_credit_card(xml, payment) + xml.CreditCard :Registered => 'False' do + xml.CardNumber payment.number + xml.ExpM format(payment.month, :two_digits) + xml.ExpY format(payment.year, :four_digits) + xml.CVN payment.verification_value + xml.CardHolderName payment.name + end + end + + def parse(body) + element = Nokogiri::XML(body).root.first_element_child.first_element_child + + response = {} + doc = Nokogiri::XML(element) + doc.root.elements.each do |e| + response[e.name.underscore.to_sym] = e.inner_text + end + response + end + + def commit(action, &block) + headers = { + 'Content-Type' => 'text/xml; charset=utf-8', + 'SOAPAction' => "http://www.ippayments.com.au/interface/api/#{endpoint(action)}/#{action}" + } + response = parse(ssl_post("#{commit_url}/#{endpoint(action)}.asmx", new_submit_xml(action, &block), headers)) + + Response.new( + success_from(response), + message_from(response), + response, + authorization: authorization_from(response), + error_code: error_code_from(response), + test: test? + ) + end + + def new_submit_xml(action) + xml = Builder::XmlMarkup.new(indent: 2) + xml.instruct! + xml.soap :Envelope, 'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance', 'xmlns:xsd' => 'http://www.w3.org/2001/XMLSchema', 'xmlns:soap' => 'http://schemas.xmlsoap.org/soap/envelope/' do + xml.soap :Body do + xml.__send__(action, 'xmlns' => "http://www.ippayments.com.au/interface/api/#{endpoint(action)}") do + if action == 'TokeniseCreditCard' + xml.tokeniseCreditCardXML do + inner_xml = Builder::XmlMarkup.new(indent: 2) + yield(inner_xml) + xml.cdata!(inner_xml.target!) + end + else + xml.trnXML do + inner_xml = Builder::XmlMarkup.new(indent: 2) + yield(inner_xml) + xml.cdata!(inner_xml.target!) + end + end + end + end + end + xml.target! + end + + def endpoint(action) + action == 'TokeniseCreditCard' ? 'sipp' : 'dts' + end + + def commit_url + test? ? test_url : live_url + end + + def success_from(response) + response[:response_code] == '0' || response[:return_value] == '0' + end + + def error_code_from(response) + STANDARD_ERROR_CODE_MAPPING[response[:declined_code]] + end + + def message_from(response) + success_from(response) ? 'Succeeded' : response[:declined_message] + end + + def authorization_from(response) + response[:receipt] || response[:token] + end + end + end +end diff --git a/lib/active_merchant/billing/gateways/bank_frick.rb b/lib/active_merchant/billing/gateways/bank_frick.rb new file mode 100644 index 00000000000..8a35089978c --- /dev/null +++ b/lib/active_merchant/billing/gateways/bank_frick.rb @@ -0,0 +1,225 @@ +require 'nokogiri' + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + # For more information visit {Bank Frick Acquiring Services}[http://www.bankfrickacquiring.com/merchantsolutions_en.html] + # + # Written by Piers Chambers (Varyonic.com) + class BankFrickGateway < Gateway + self.test_url = 'https://test.ctpe.io/payment/ctpe' + self.live_url = 'https://ctpe.io/payment/ctpe' + + self.supported_countries = ['LI', 'US'] + self.default_currency = 'EUR' + self.supported_cardtypes = [:visa, :master, :american_express, :discover] + + self.homepage_url = 'http://www.bankfrickacquiring.com/' + self.display_name = 'Bank Frick' + + # The set of supported transactions for this gateway. + # More operations are supported by the gateway itself, but + # are not supported in this library. + SUPPORTED_TRANSACTIONS = { + 'sale' => 'CC.DB', + 'authonly' => 'CC.PA', + 'capture' => 'CC.CP', + 'refund' => 'CC.RF', + 'void' => 'CC.RV', + } + + def initialize(options={}) + requires!(options, :sender, :channel, :userid, :userpwd) + super + end + + def purchase(money, payment, options={}) + post = {} + add_invoice(post, money, options) + add_payment(post, payment) + add_address(post, payment, options) + add_customer_data(post, options) + + commit('sale', post) + end + + def authorize(money, payment, options={}) + post = {} + add_invoice(post, money, options) + add_payment(post, payment) + add_address(post, payment, options) + add_customer_data(post, options) + + commit('authonly', post) + end + + def capture(money, authorization, options={}) + post = {} + post[:authorization] = authorization + add_invoice(post, money, options) + + commit('capture', post) + end + + def refund(money, authorization, options={}) + post = {} + post[:authorization] = authorization + add_invoice(post, money, options) + + commit('refund', post) + end + + def void(authorization, options={}) + post = {} + post[:authorization] = authorization + + commit('void', post) + end + + def verify(credit_card, options={}) + MultiResponse.run(:use_first_response) do |r| + r.process { authorize(100, credit_card, options) } + r.process(:ignore_result) { void(r.authorization, options) } + end + end + + private + + def add_customer_data(post, options) + post[:email] = options[:email] || 'noone@example.com' + post[:ip] = options[:ip] || '0.0.0.0' + end + + def add_address(post, creditcard, options) + if address = options[:billing_address] || options[:address] + post[:address] = address[:address1].to_s + post[:company] = address[:company].to_s + post[:phone] = address[:phone].to_s.gsub(/[^0-9]/, '') || '0000000' + post[:zip] = address[:zip].to_s + post[:city] = address[:city].to_s + post[:country] = address[:country].to_s + post[:state] = address[:state].blank? ? 'n/a' : address[:state] + end + end + + def add_invoice(post, money, options) + post[:order_id] = options[:order_id] if post.has_key? :order_id + post[:amount] = amount(money) + post[:currency] = (options[:currency] || currency(money)) + post[:description] = options[:description] + end + + def add_payment(post, payment) + post[:first_name] = payment.first_name + post[:last_name] = payment.last_name + post[:brand] = payment.brand + post[:card_num] = payment.number + post[:card_code] = payment.verification_value if payment.verification_value? + post[:exp_year] = payment.year + post[:exp_month] = payment.month + end + + def parse(body) + results = {} + xml = Nokogiri::XML(body) + resp = xml.xpath('//Response/Transaction/Identification') + resp.children.each do |element| + results[element.name.downcase.to_sym] = element.text + end + resp = xml.xpath('//Response/Transaction/Processing') + resp.children.each do |element| + results[element.name.downcase.to_sym] = element.text + end + results + end + + def commit(action, parameters) + url = (test? ? test_url : live_url) + headers = { + 'Content-Type' => 'application/x-www-form-urlencoded;charset=UTF-8' + } + response = parse(ssl_post(url, post_data(action, parameters), headers)) + + Response.new( + success_from(response), + message_from(response), + response, + authorization: authorization_from(response), + test: test? + ) + end + + def success_from(response) + response[:result] == 'ACK' + end + + def message_from(response) + response[:return] + end + + def authorization_from(response) + response[:uniqueid] + end + + def post_data(action, parameters = {}) + xml = build_xml_request(action, parameters) + "load=#{CGI.escape(xml)}" + end + + def build_xml_request(action, data) + xml = Builder::XmlMarkup.new :indent => 2 + xml.Request(version: '1.0') do + xml.Header do + xml.Security(sender: @options[:sender], type: 'MERCHANT') + end + xml.Transaction(response: 'SYNC', channel: @options[:channel], mode: 'LIVE') do + xml.User(pwd: @options[:userpwd], login: @options[:userid]) + xml.Identification do + xml.TransactionID data[:order_id] if data.has_key? :order_id + xml.ReferenceID data[:authorization] if data.has_key? :authorization + end + xml.Account do + xml.Holder "#{data[:first_name]} #{data[:last_name]}" + xml.Brand data[:brand] + xml.Number data[:card_num] + xml.Bank data[:bankname] + xml.Country data[:country] + xml.Authorization data[:authorization] + xml.Verification data[:card_code] + xml.Year data[:exp_year] + xml.Month data[:exp_month] + end if data.has_key? :card_num + xml.Payment(code: SUPPORTED_TRANSACTIONS[action]) do + xml.Presentation do + xml.Amount data[:amount] + xml.Currency data[:currency] + xml.Usage data[:description] + end + end + xml.Customer do + xml.Contact do + xml.Email data[:email] + xml.Mobile data[:mobile] + xml.Ip data[:ip] + xml.Phone data[:phone] + end + xml.Address do + xml.Street data[:address] + xml.Zip data[:zip] + xml.City data[:city] + xml.State data[:state] + xml.Country data[:country] + end + xml.Name do + xml.Salutation data[:salutation] + xml.Title data[:title] + xml.Given data[:first_name] + xml.Family data[:last_name] + xml.Company data[:company] + end + end if data.has_key? :last_name + end + end + end + end + end +end diff --git a/lib/active_merchant/billing/gateways/banwire.rb b/lib/active_merchant/billing/gateways/banwire.rb index b7b63adde52..99d3e683e34 100644 --- a/lib/active_merchant/billing/gateways/banwire.rb +++ b/lib/active_merchant/billing/gateways/banwire.rb @@ -26,20 +26,31 @@ def purchase(money, creditcard, options = {}) commit(money, post) end + def supports_scrubbing? + true + end + + def scrub(transcript) + transcript. + gsub(%r((Authorization: Basic )\w+), '\1[FILTERED]'). + gsub(%r((&?card_num=)[^&]*)i, '\1[FILTERED]'). + gsub(%r((&?card_ccv2=)[^&]*)i, '\1[FILTERED]') + end + private def add_response_type(post) - post[:response_format] = "JSON" + post[:response_format] = 'JSON' end def add_customer_data(post, options) post[:user] = @options[:login] post[:phone] = options[:billing_address][:phone] - post[:mail] = options[:email] + post[:mail] = options[:email] || 'unspecified@email.com' end def add_order_data(post, options) - post[:reference] = options[:order_id] + post[:reference] = options[:order_id] || generate_unique_id post[:concept] = options[:description] end @@ -52,7 +63,7 @@ def add_creditcard(post, creditcard) post[:card_num] = creditcard.number post[:card_name] = creditcard.name post[:card_type] = card_brand(creditcard) - post[:card_exp] = "#{sprintf("%02d", creditcard.month)}/#{"#{creditcard.year}"[-2, 2]}" + post[:card_exp] = "#{sprintf("%02d", creditcard.month)}/#{creditcard.year.to_s[-2, 2]}" post[:card_ccv2] = creditcard.verification_value end @@ -63,7 +74,7 @@ def add_amount(post, money, options) def card_brand(card) brand = super - ({"master" => "mastercard", "american_express" => "amex"}[brand] || brand) + ({'master' => 'mastercard', 'american_express' => 'amex'}[brand] || brand) end def parse(body) @@ -79,25 +90,25 @@ def commit(money, parameters) end Response.new(success?(response), - response["message"], - response, - :test => test?, - :authorization => response["code_auth"]) + response['message'], + response, + :test => test?, + :authorization => response['code_auth']) end def success?(response) - (response["response"] == "ok") + (response['response'] == 'ok') end def post_data(parameters = {}) - parameters.collect { |key, value| "#{key}=#{CGI.escape(value.to_s)}" }.join("&") + parameters.collect { |key, value| "#{key}=#{CGI.escape(value.to_s)}" }.join('&') end def json_error(raw_response) msg = 'Invalid response received from the Banwire API. Please contact Banwire support if you continue to receive this message.' msg += " (The raw response returned by the API was #{raw_response.inspect})" { - "message" => msg + 'message' => msg } end end diff --git a/lib/active_merchant/billing/gateways/barclaycard_smartpay.rb b/lib/active_merchant/billing/gateways/barclaycard_smartpay.rb new file mode 100644 index 00000000000..1788954da3b --- /dev/null +++ b/lib/active_merchant/billing/gateways/barclaycard_smartpay.rb @@ -0,0 +1,384 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class BarclaycardSmartpayGateway < Gateway + self.test_url = 'https://pal-test.barclaycardsmartpay.com/pal/servlet' + self.live_url = 'https://pal-live.barclaycardsmartpay.com/pal/servlet' + + self.supported_countries = ['AL', 'AD', 'AM', 'AT', 'AZ', 'BY', 'BE', 'BA', 'BG', 'HR', 'CY', 'CZ', 'DK', 'EE', 'FI', 'FR', 'DE', 'GR', 'HU', 'IS', 'IE', 'IT', 'KZ', 'LV', 'LI', 'LT', 'LU', 'MK', 'MT', 'MD', 'MC', 'ME', 'NL', 'NO', 'PL', 'PT', 'RO', 'RU', 'SM', 'RS', 'SK', 'SI', 'ES', 'SE', 'CH', 'TR', 'UA', 'GB', 'VA'] + self.default_currency = 'EUR' + self.currencies_with_three_decimal_places = %w(BHD KWD OMR RSD TND) + self.money_format = :cents + self.supported_cardtypes = [:visa, :master, :american_express, :discover, :diners_club, :jcb, :dankort, :maestro] + + self.homepage_url = 'https://www.barclaycardsmartpay.com/' + self.display_name = 'Barclaycard Smartpay' + + API_VERSION = 'v40' + + def initialize(options = {}) + requires!(options, :company, :merchant, :password) + super + end + + def purchase(money, creditcard, options = {}) + requires!(options, :order_id) + + MultiResponse.run do |r| + r.process { authorize(money, creditcard, options) } + r.process { capture(money, r.authorization, options) } + end + end + + def authorize(money, creditcard, options = {}) + requires!(options, :order_id) + + post = payment_request(money, options) + post[:amount] = amount_hash(money, options[:currency]) + post[:card] = credit_card_hash(creditcard) + post[:billingAddress] = billing_address_hash(options) if options[:billing_address] + post[:deliveryAddress] = shipping_address_hash(options) if options[:shipping_address] + post[:shopperStatement] = options[:shopper_statement] if options[:shopper_statement] + + add_3ds(post, options) + commit('authorise', post) + end + + def capture(money, authorization, options = {}) + requires!(options, :order_id) + + post = modification_request(authorization, options) + post[:modificationAmount] = amount_hash(money, options[:currency]) + + commit('capture', post) + end + + def refund(money, authorization, options = {}) + requires!(options, :order_id) + + post = modification_request(authorization, options) + post[:modificationAmount] = amount_hash(money, options[:currency]) + + commit('refund', post) + end + + def credit(money, creditcard, options = {}) + post = payment_request(money, options) + post[:amount] = amount_hash(money, options[:currency]) + post[:card] = credit_card_hash(creditcard) + post[:dateOfBirth] = options[:date_of_birth] if options[:date_of_birth] + post[:entityType] = options[:entity_type] if options[:entity_type] + post[:nationality] = options[:nationality] if options[:nationality] + post[:shopperName] = options[:shopper_name] if options[:shopper_name] + + if options[:third_party_payout] + post[:recurring] = options[:recurring_contract] || {contract: 'PAYOUT'} + MultiResponse.run do |r| + r.process { + commit( + 'storeDetailAndSubmitThirdParty', + post, + @options[:store_payout_account], + @options[:store_payout_password]) + } + r.process { + commit( + 'confirmThirdParty', + modification_request(r.authorization, @options), + @options[:review_payout_account], + @options[:review_payout_password]) + } + end + else + commit('refundWithData', post) + end + end + + def void(identification, options = {}) + requires!(options, :order_id) + + post = modification_request(identification, options) + + commit('cancel', post) + end + + def verify(creditcard, options = {}) + authorize(0, creditcard, options) + end + + def store(creditcard, options = {}) + post = store_request(options) + post[:card] = credit_card_hash(creditcard) + post[:recurring] = {:contract => 'RECURRING'} + + commit('store', post) + end + + def supports_scrubbing? + true + end + + def scrub(transcript) + transcript. + gsub(%r(((?:\r\n)?Authorization: Basic )[^\r\n]+(\r\n)?), '\1[FILTERED]'). + gsub(%r((card.number=)\d+), '\1[FILTERED]'). + gsub(%r((card.cvc=)\d+), '\1[FILTERED]') + end + + private + + # Smartpay may return AVS codes not covered by standard AVSResult codes. + # Smartpay's descriptions noted below. + AVS_MAPPING = { + '0' => 'R', # Unknown + '1' => 'A', # Address matches, postal code doesn't + '2' => 'N', # Neither postal code nor address match + '3' => 'R', # AVS unavailable + '4' => 'E', # AVS not supported for this card type + '5' => 'U', # No AVS data provided + '6' => 'Z', # Postal code matches, address doesn't match + '7' => 'D', # Both postal code and address match + '8' => 'U', # Address not checked, postal code unknown + '9' => 'B', # Address matches, postal code unknown + '10' => 'N', # Address doesn't match, postal code unknown + '11' => 'U', # Postal code not checked, address unknown + '12' => 'B', # Address matches, postal code not checked + '13' => 'U', # Address doesn't match, postal code not checked + '14' => 'P', # Postal code matches, address unknown + '15' => 'P', # Postal code matches, address not checked + '16' => 'N', # Postal code doesn't match, address unknown + '17' => 'U', # Postal code doesn't match, address not checked + '18' => 'I' # Neither postal code nor address were checked + } + + def commit(action, post, account = 'ws', password = @options[:password]) + request = post_data(flatten_hash(post)) + request_headers = headers(account, password) + raw_response = ssl_post(build_url(action), request, request_headers) + response = parse(raw_response) + + Response.new( + success_from(response), + message_from(response), + response, + test: test?, + avs_result: AVSResult.new(:code => parse_avs_code(response)), + authorization: response['recurringDetailReference'] || authorization_from(post, response) + ) + rescue ResponseError => e + case e.response.code + when '401' + return Response.new(false, 'Invalid credentials', {}, :test => test?) + when '403' + return Response.new(false, 'Not allowed', {}, :test => test?) + when '422', '500' + if e.response.body.split(/\W+/).any? { |word| %w(validation configuration security).include?(word) } + error_message = e.response.body[/#{Regexp.escape('message=')}(.*?)#{Regexp.escape('&')}/m, 1].tr('+', ' ') + error_code = e.response.body[/#{Regexp.escape('errorCode=')}(.*?)#{Regexp.escape('&')}/m, 1] + return Response.new(false, error_code + ': ' + error_message, {}, :test => test?) + end + end + raise + end + + def authorization_from(parameters, response) + authorization = [parameters[:originalReference], response['pspReference']].compact + + return nil if authorization.empty? + return authorization.join('#') + end + + def parse_avs_code(response) + AVS_MAPPING[response['additionalData']['avsResult'][0..1].strip] if response.dig('additionalData', 'avsResult') + end + + def flatten_hash(hash, prefix = nil) + flat_hash = {} + hash.each_pair do |key, val| + conc_key = prefix.nil? ? key : "#{prefix}.#{key}" + if val.is_a?(Hash) + flat_hash.merge!(flatten_hash(val, conc_key)) + else + flat_hash[conc_key] = val + end + end + flat_hash + end + + def headers(account, password) + { + 'Content-Type' => 'application/x-www-form-urlencoded; charset=utf-8', + 'Authorization' => 'Basic ' + Base64.strict_encode64("#{account}@Company.#{@options[:company]}:#{password}").strip + } + end + + def parse(response) + parsed_response = {} + params = CGI.parse(response) + params.each do |key, value| + parsed_key = key.split('.', 2) + if parsed_key.size > 1 + parsed_response[parsed_key[0]] ||= {} + parsed_response[parsed_key[0]][parsed_key[1]] = value[0] + else + parsed_response[parsed_key[0]] = value[0] + end + end + parsed_response + end + + def post_data(data) + data.map do |key, val| + "#{key}=#{CGI.escape(val.to_s)}" + end.reduce do |x, y| + "#{x}&#{y}" + end + end + + def message_from(response) + return response['resultCode'] if response.has_key?('resultCode') # Payment request + return response['response'] if response['response'] # Modification request + return response['result'] if response.has_key?('result') # Store/Recurring request + 'Failure' # Negative fallback in case of error + end + + def success_from(response) + return true if response['result'] == 'Success' + + successful_results = %w(Authorised Received [payout-submit-received]) + successful_responses = %w([capture-received] [cancel-received] [refund-received] [payout-confirm-received]) + successful_results.include?(response['resultCode']) || successful_responses.include?(response['response']) + end + + def build_url(action) + case action + when 'store' + "#{test? ? self.test_url : self.live_url}/Recurring/#{API_VERSION}/storeToken" + when 'finalize3ds' + "#{test? ? self.test_url : self.live_url}/Payment/#{API_VERSION}/authorise3d" + when 'storeDetailAndSubmitThirdParty', 'confirmThirdParty' + "#{test? ? self.test_url : self.live_url}/Payout/#{API_VERSION}/#{action}" + else + "#{test? ? self.test_url : self.live_url}/Payment/#{API_VERSION}/#{action}" + end + end + + def billing_address_hash(options) + address = options[:address] || options[:billing_address] if options[:address] || options[:billing_address] + street = options[:street] || parse_street(address) + house = options[:house_number] || parse_house_number(address) + + create_address_hash(address, house, street) + end + + def shipping_address_hash(options) + address = options[:shipping_address] + street = options[:shipping_street] || parse_street(address) + house = options[:shipping_house_number] || parse_house_number(address) + + create_address_hash(address, house, street) + end + + def parse_street(address) + address_to_parse = "#{address[:address1]} #{address[:address2]}" + street = address[:street] || address_to_parse.split(/\s+/).keep_if { |x| x !~ /\d/ }.join(' ') + street.empty? ? 'Not Provided' : street + end + + def parse_house_number(address) + address_to_parse = "#{address[:address1]} #{address[:address2]}" + house = address[:houseNumberOrName] || address_to_parse.split(/\s+/).keep_if { |x| x =~ /\d/ }.join(' ') + house.empty? ? 'Not Provided' : house + end + + def create_address_hash(address, house, street) + hash = {} + hash[:houseNumberOrName] = house + hash[:street] = street + hash[:city] = address[:city] + hash[:stateOrProvince] = address[:state] + hash[:postalCode] = address[:zip] + hash[:country] = address[:country] + hash.keep_if { |_, v| v } + end + + def amount_hash(money, currency) + currency = currency || currency(money) + hash = {} + hash[:currency] = currency + hash[:value] = localized_amount(money, currency) if money + hash + end + + def credit_card_hash(creditcard) + hash = {} + hash[:cvc] = creditcard.verification_value if creditcard.verification_value + hash[:expiryMonth] = format(creditcard.month, :two_digits) if creditcard.month + hash[:expiryYear] = format(creditcard.year, :four_digits) if creditcard.year + hash[:holderName] = creditcard.name if creditcard.name + hash[:number] = creditcard.number if creditcard.number + hash + end + + def modification_request(reference, options) + hash = {} + hash[:merchantAccount] = @options[:merchant] + hash[:originalReference] = psp_reference_from(reference) + hash.keep_if { |_, v| v } + end + + def psp_reference_from(authorization) + authorization.nil? ? nil : authorization.split('#').first + end + + def payment_request(money, options) + hash = {} + hash[:merchantAccount] = @options[:merchant] + hash[:reference] = options[:order_id] + hash[:shopperEmail] = options[:email] + hash[:shopperIP] = options[:ip] + hash[:shopperReference] = options[:customer] + hash[:shopperInteraction] = options[:shopper_interaction] + hash[:deviceFingerprint] = options[:device_fingerprint] + hash.keep_if { |_, v| v } + end + + def store_request(options) + hash = {} + hash[:merchantAccount] = @options[:merchant] + hash[:shopperEmail] = options[:email] + hash[:shopperReference] = options[:customer] if options[:customer] + hash.keep_if { |_, v| v } + end + + def add_3ds(post, options) + if three_ds_2_options = options[:three_ds_2] + device_channel = three_ds_2_options[:channel] + if device_channel == 'app' + post[:threeDS2RequestData] = { deviceChannel: device_channel } + else + add_browser_info(three_ds_2_options[:browser_info], post) + post[:threeDS2RequestData] = { deviceChannel: device_channel, notificationURL: three_ds_2_options[:notification_url] } + end + else + return unless options[:execute_threed] || options[:threed_dynamic] + post[:browserInfo] = { userAgent: options[:user_agent], acceptHeader: options[:accept_header] } + post[:additionalData] = { executeThreeD: 'true' } if options[:execute_threed] + end + end + + def add_browser_info(browser_info, post) + return unless browser_info + post[:browserInfo] = { + acceptHeader: browser_info[:accept_header], + colorDepth: browser_info[:depth], + javaEnabled: browser_info[:java], + language: browser_info[:language], + screenHeight: browser_info[:height], + screenWidth: browser_info[:width], + timeZoneOffset: browser_info[:timezone], + userAgent: browser_info[:user_agent] + } + end + end + end +end diff --git a/lib/active_merchant/billing/gateways/barclays_epdq.rb b/lib/active_merchant/billing/gateways/barclays_epdq.rb deleted file mode 100644 index 45375c728e2..00000000000 --- a/lib/active_merchant/billing/gateways/barclays_epdq.rb +++ /dev/null @@ -1,314 +0,0 @@ -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - class BarclaysEpdqGateway < Gateway - self.test_url = 'https://secure2.mde.epdq.co.uk:11500' - self.live_url = 'https://secure2.epdq.co.uk:11500' - - self.supported_countries = ['GB'] - self.default_currency = 'GBP' - self.supported_cardtypes = [:visa, :master, :american_express, :maestro, :switch ] - self.money_format = :cents - self.homepage_url = 'http://www.barclaycard.co.uk/business/accepting-payments/epdq-mpi/' - self.display_name = 'Barclays ePDQ MPI' - - def initialize(options = {}) - requires!(options, :login, :password, :client_id) - super - end - - def authorize(money, creditcard, options = {}) - document = Document.new(self, @options) do - add_order_form(options[:order_id]) do - add_consumer(options) do - add_creditcard(creditcard) - end - add_transaction(:PreAuth, money) - end - end - - commit(document) - end - - def purchase(money, creditcard, options = {}) - # disable fraud checks if this is a repeat order: - if options[:payment_number] && (options[:payment_number] > 1) - no_fraud = true - else - no_fraud = options[:no_fraud] - end - document = Document.new(self, @options, :no_fraud => no_fraud) do - add_order_form(options[:order_id], options[:group_id]) do - add_consumer(options) do - add_creditcard(creditcard) - end - add_transaction(:Auth, money, options) - end - end - commit(document) - end - - # authorization is your unique order ID, not the authorization - # code returned by ePDQ - def capture(money, authorization, options = {}) - document = Document.new(self, @options) do - add_order_form(authorization) do - add_transaction(:PostAuth, money) - end - end - - commit(document) - end - - # authorization is your unique order ID, not the authorization - # code returned by ePDQ - def credit(money, creditcard_or_authorization, options = {}) - if creditcard_or_authorization.is_a?(String) - deprecated CREDIT_DEPRECATION_MESSAGE - refund(money, creditcard_or_authorization, options) - else - credit_new_order(money, creditcard_or_authorization, options) - end - end - - def refund(money, authorization, options = {}) - credit_existing_order(money, authorization, options) - end - - def void(authorization, options = {}) - document = Document.new(self, @options) do - add_order_form(authorization) do - add_transaction(:Void) - end - end - - commit(document) - end - - private - def credit_new_order(money, creditcard, options) - document = Document.new(self, @options) do - add_order_form do - add_consumer(options) do - add_creditcard(creditcard) - end - add_transaction(:Credit, money) - end - end - - commit(document) - end - - def credit_existing_order(money, authorization, options) - order_id, _ = authorization.split(":") - document = Document.new(self, @options) do - add_order_form(order_id) do - add_transaction(:Credit, money) - end - end - - commit(document) - end - - def parse(body) - parser = Parser.new(body) - response = parser.parse - Response.new(response[:success], response[:message], response, - :test => test?, - :authorization => response[:authorization], - :avs_result => response[:avsresponse], - :cvv_result => response[:cvv_result], - :order_id => response[:order_id], - :raw_response => response[:raw_response] - ) - end - - def commit(document) - url = (test? ? self.test_url : self.live_url) - data = ssl_post(url, document.to_xml) - parse(data) - end - - class Parser - def initialize(response) - @response = response - end - - def parse - require 'iconv' unless String.method_defined?(:encode) - if String.method_defined?(:encode) - doc = REXML::Document.new(@response.encode("UTF-8", "ISO-8859-1")) - else - ic = Iconv.new('UTF-8', 'ISO-8859-1') - doc = REXML::Document.new(ic.iconv(@response)) - end - - auth_type = find(doc, "//Transaction/Type").to_s - - message = find(doc, "//Message/Text") - if message.blank? - message = find(doc, "//Transaction/CardProcResp/CcReturnMsg") - end - - case auth_type - when 'Credit', 'Void' - success = find(doc, "//CcReturnMsg") == "Approved." - else - success = find(doc, "//Transaction/AuthCode").present? - end - - { - :success => success, - :message => message, - :transaction_id => find(doc, "//Transaction/Id"), - :avs_result => find(doc, "//Transaction/AvsRespCode"), - :cvv_result => find(doc, "//Transaction/Cvv2Resp"), - :authorization => find(doc, "//OrderFormDoc/Id"), - :raw_response => @response - } - end - - def find(doc, xpath) - REXML::XPath.first(doc, xpath).try(:text) - end - end - - class Document - attr_reader :type, :xml - - PAYMENT_INTERVALS = { - :days => 'D', - :months => 'M' - } - - EPDQ_CARD_TYPES = { - :visa => 1, - :master => 2, - :switch => 9, - :maestro => 10, - } - - def initialize(gateway, options = {}, document_options = {}, &block) - @gateway = gateway - @options = options - @document_options = document_options - @xml = Builder::XmlMarkup.new(:indent => 2) - build(&block) - end - - def to_xml - @xml.target! - end - - def build(&block) - xml.instruct!(:xml, :version => '1.0') - xml.EngineDocList do - xml.DocVersion "1.0" - xml.EngineDoc do - xml.ContentType "OrderFormDoc" - xml.User do - xml.Name(@options[:login]) - xml.Password(@options[:password]) - xml.ClientId({ :DataType => "S32" }, @options[:client_id]) - end - xml.Instructions do - if @document_options[:no_fraud] - xml.Pipeline "PaymentNoFraud" - else - xml.Pipeline "Payment" - end - end - instance_eval(&block) - end - end - end - - def add_order_form(order_id=nil, group_id=nil, &block) - xml.OrderFormDoc do - xml.Mode 'P' - xml.Id(order_id) if order_id - xml.GroupId(group_id) if group_id - instance_eval(&block) - end - end - - def add_consumer(options=nil, &block) - xml.Consumer do - if options - xml.Email(options[:email]) if options[:email] - billing_address = options[:billing_address] || options[:address] - if billing_address - xml.BillTo do - xml.Location do - xml.Address do - xml.Street1 billing_address[:address1] - xml.Street2 billing_address[:address2] - xml.City billing_address[:city] - xml.StateProv billing_address[:state] - xml.PostalCode billing_address[:zip] - xml.Country billing_address[:country_code] - end - end - end - end - end - instance_eval(&block) - end - end - - def add_creditcard(creditcard) - xml.PaymentMech do - xml.CreditCard do - xml.Type({ :DataType => 'S32' }, EPDQ_CARD_TYPES[creditcard.brand.to_sym]) - xml.Number creditcard.number - xml.Expires({ :DataType => 'ExpirationDate', :Locale => 826 }, format_expiry_date(creditcard)) - if creditcard.verification_value.present? - xml.Cvv2Indicator 1 - xml.Cvv2Val creditcard.verification_value - else - xml.Cvv2Indicator 5 - end - xml.IssueNum(creditcard.issue_number) if creditcard.issue_number.present? - end - end - end - - def add_transaction(auth_type, amount = nil, options = {}) - @auth_type = auth_type - xml.Transaction do - xml.Type @auth_type.to_s - if options[:payment_number] && options[:payment_number] > 1 - xml.CardholderPresentCode({ :DataType => 'S32' }, 8) - else - xml.CardholderPresentCode({ :DataType => 'S32' }, 7) - end - if options[:payment_number] - xml.PaymentNumber({ :DataType => 'S32' }, options[:payment_number]) - end - if options[:total_payments] - xml.TotalNumberPayments({ :DataType => 'S32' }, options[:total_payments]) - end - if amount - xml.CurrentTotals do - xml.Totals do - xml.Total({ :DataType => 'Money', :Currency => 826 }, amount) - end - end - end - end - end - - # date must be formatted MM/YY - def format_expiry_date(creditcard) - month_str = "%02d" % creditcard.month - if match = creditcard.year.to_s.match(/^\d{2}(\d{2})$/) - year_str = "%02d" % match[1].to_i - else - year_str = "%02d" % creditcard.year - end - "#{month_str}/#{year_str}" - end - end - end - end -end - diff --git a/lib/active_merchant/billing/gateways/barclays_epdq_extra_plus.rb b/lib/active_merchant/billing/gateways/barclays_epdq_extra_plus.rb index 933d27f7f63..6f020860b2a 100644 --- a/lib/active_merchant/billing/gateways/barclays_epdq_extra_plus.rb +++ b/lib/active_merchant/billing/gateways/barclays_epdq_extra_plus.rb @@ -1,15 +1,15 @@ module ActiveMerchant #:nodoc: module Billing #:nodoc: class BarclaysEpdqExtraPlusGateway < OgoneGateway - self.test_url = "https://mdepayments.epdq.co.uk/ncol/test/" - self.live_url = "https://payments.epdq.co.uk/ncol/prod/" + self.test_url = 'https://mdepayments.epdq.co.uk/ncol/test/' + self.live_url = 'https://payments.epdq.co.uk/ncol/prod/' - self.display_name = "Barclays ePDQ Extra Plus" - self.homepage_url = "http://www.barclaycard.co.uk/business/accepting-payments/epdq-ecomm/" + self.display_name = 'Barclays ePDQ Extra Plus' + self.homepage_url = 'http://www.barclaycard.co.uk/business/accepting-payments/epdq-ecomm/' - self.supported_countries = ["GB"] + self.supported_countries = ['GB'] self.supported_cardtypes = [:visa, :master, :american_express, :diners_club, :discover, :jcb, :maestro] - self.default_currency = "GBP" + self.default_currency = 'GBP' end end end diff --git a/lib/active_merchant/billing/gateways/be2bill.rb b/lib/active_merchant/billing/gateways/be2bill.rb new file mode 100644 index 00000000000..f309272466f --- /dev/null +++ b/lib/active_merchant/billing/gateways/be2bill.rb @@ -0,0 +1,131 @@ +require 'digest/sha2' + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class Be2billGateway < Gateway + self.test_url = 'https://secure-test.be2bill.com/front/service/rest/process.php' + self.live_url = 'https://secure-magenta1.be2bill.com/front/service/rest/process.php' + + self.display_name = 'Be2Bill' + self.homepage_url = 'http://www.be2bill.com/' + self.supported_countries = ['FR'] + self.supported_cardtypes = [:visa, :master, :american_express] + self.default_currency = 'EUR' + self.money_format = :cents + + # These options are mandatory on be2bill (cf. tests) : + # + # options = { :order_id => order.id, + # :customer_id => user.id, + # :description => "Some description", + # :referrer => request.env['HTTP_REFERER'], + # :user_agent => request.env['HTTP_USER_AGENT'], + # :ip => request.remote_ip, + # :email => user.email } + + def initialize(options = {}) + requires!(options, :login, :password) + super + end + + def authorize(money, creditcard, options = {}) + post = {} + add_invoice(post, options) + add_creditcard(post, creditcard) + add_customer_data(post, options) + + commit('authorization', money, post) + end + + def purchase(money, creditcard, options = {}) + post = {} + add_invoice(post, options) + add_creditcard(post, creditcard) + add_customer_data(post, options) + + commit('payment', money, post) + end + + def capture(money, authorization, options = {}) + post = {} + add_invoice(post, options) + post[:TRANSACTIONID] = authorization + + commit('capture', money, post) + end + + private + + def add_customer_data(post, options) + post[:CLIENTREFERRER] = options[:referrer] + post[:CLIENTUSERAGENT] = options[:user_agent] + post[:CLIENTIP] = options[:ip] + post[:CLIENTEMAIL] = options[:email] + post[:CLIENTIDENT] = options[:customer_id] + end + + def add_invoice(post, options) + post[:ORDERID] = options[:order_id] + post[:DESCRIPTION] = options[:description] + end + + def add_creditcard(post, creditcard) + post[:CARDFULLNAME] = creditcard ? creditcard.name : '' + post[:CARDCODE] = creditcard ? creditcard.number : '' + post[:CARDVALIDITYDATE] = creditcard ? '%02d-%02s' % [creditcard.month, creditcard.year.to_s[-2..-1]] : '' + post[:CARDCVV] = creditcard ? creditcard.verification_value : '' + end + + def parse(response) + ActiveSupport::JSON.decode(response) + end + + def commit(action, money, parameters) + parameters[:IDENTIFIER] = @options[:login] + parameters[:AMOUNT] = amount(money) + parameters[:VERSION] = '2.0' + + url = (test? ? self.test_url : self.live_url) + response = parse(ssl_post(url, post_data(action, parameters))) + + Response.new( + successful?(response), + message_from(response), + response, + :authorization => response['TRANSACTIONID'], + :test => test? + ) + end + + def successful?(response) + %w(0000 0001).include?(response['EXECCODE']) + end + + def message_from(response) + if successful?(response) + "Approved : #{response['MESSAGE']}" + else + "Declined (#{response['EXECCODE']} - #{response['MESSAGE']}" + end + end + + def post_data(action, parameters = {}) + { + :method => action, + :params => parameters.merge(HASH: signature(parameters, action)) + }.to_query + end + + def signature(parameters, action) + parameters[:OPERATIONTYPE] = action unless parameters[:OPERATIONTYPE] + + signature = @options[:password] + parameters.sort.each do |key, value| + signature += ("#{key.upcase}=#{value}" + @options[:password]) + end + + Digest::SHA256.hexdigest(signature) + end + end + end +end diff --git a/lib/active_merchant/billing/gateways/beanstream.rb b/lib/active_merchant/billing/gateways/beanstream.rb index edc3bb11f02..de03c2a1afa 100644 --- a/lib/active_merchant/billing/gateways/beanstream.rb +++ b/lib/active_merchant/billing/gateways/beanstream.rb @@ -1,4 +1,4 @@ -require File.dirname(__FILE__) + '/beanstream/beanstream_core' +require 'active_merchant/billing/gateways/beanstream/beanstream_core' module ActiveMerchant #:nodoc: module Billing #:nodoc: @@ -16,7 +16,10 @@ module Billing #:nodoc: # BeanStream supports payment profiles (vaults). This allows you to store cc information with BeanStream and process subsequent transactions with a customer id. # Secure Payment Profiles must be enabled on your account (must be done over the phone). # Your API Access Passcode must be set in Administration => account settings => order settings. - # To learn more about storing credit cards with the Beanstream gateway, please read the BEAN_Payment_Profiles.pdf (I had to phone BeanStream to request it.) + # To learn more about storing credit cards with the Beanstream gateway, documentation can be found at http://developer.beanstream.com/documentation/classic-apis + # + # To store a credit card using Beanstream's Legato Javascript Library (http://developer.beanstream.com/documentation/legato) you must pass the singleUseToken in + # the store method's option parameter. Example: @gateway.store("gt6-0c78c25b-3637-4ba0-90e2-26105287f198") # # == Notes # * Adding of order products information is not implemented. @@ -72,6 +75,7 @@ def authorize(money, source, options = {}) add_address(post, options) add_transaction_type(post, :authorization) add_customer_ip(post, options) + add_recurring_payment(post, options) commit(post) end @@ -83,6 +87,7 @@ def purchase(money, source, options = {}) add_address(post, options) add_transaction_type(post, purchase_action(source)) add_customer_ip(post, options) + add_recurring_payment(post, options) commit(post) end @@ -96,7 +101,20 @@ def void(authorization, options = {}) commit(post) end + def verify(source, options={}) + MultiResponse.run(:use_first_response) do |r| + r.process { authorize(100, source, options) } + r.process(:ignore_result) { void(r.authorization, options) } + end + end + + def success?(response) + response[:trnApproved] == '1' || response[:responseCode] == '1' + end + def recurring(money, source, options = {}) + ActiveMerchant.deprecated RECURRING_DEPRECATION_MESSAGE + post = {} add_amount(post, money) add_invoice(post, options) @@ -108,6 +126,8 @@ def recurring(money, source, options = {}) end def update_recurring(amount, source, options = {}) + ActiveMerchant.deprecated RECURRING_DEPRECATION_MESSAGE + post = {} add_recurring_amount(post, amount) add_recurring_invoice(post, options) @@ -119,6 +139,8 @@ def update_recurring(amount, source, options = {}) end def cancel_recurring(options = {}) + ActiveMerchant.deprecated RECURRING_DEPRECATION_MESSAGE + post = {} add_recurring_operation_type(post, :cancel) add_recurring_service(post, options) @@ -131,18 +153,26 @@ def interac # To match the other stored-value gateways, like TrustCommerce, # store and unstore need to be defined - def store(credit_card, options = {}) + # + # When passing a single-use token the :name option is required + def store(payment_method, options = {}) post = {} add_address(post, options) - add_credit_card(post, credit_card) - add_secure_profile_variables(post,options) + + if payment_method.respond_to?(:number) + add_credit_card(post, payment_method) + else + post[:singleUseToken] = payment_method + end + add_secure_profile_variables(post, options) + commit(post, true) end - #can't actually delete a secure profile with the supplicaed API. This function sets the status of the profile to closed (C). - #Closed profiles will have to removed manually. + # can't actually delete a secure profile with the supplicated API. This function sets the status of the profile to closed (C). + # Closed profiles will have to removed manually. def delete(vault_id) - update(vault_id, false, {:status => "C"}) + update(vault_id, false, {:status => 'C'}) end alias_method :unstore, :delete @@ -150,20 +180,38 @@ def delete(vault_id) # Update the values (such as CC expiration) stored at # the gateway. The CC number must be supplied in the # CreditCard object. - def update(vault_id, credit_card, options = {}) + def update(vault_id, payment_method, options = {}) post = {} add_address(post, options) - add_credit_card(post, credit_card) - options.merge!({:vault_id => vault_id, :operation => secure_profile_action(:modify)}) - add_secure_profile_variables(post,options) + if payment_method.respond_to?(:number) + add_credit_card(post, payment_method) + else + post[:singleUseToken] = payment_method + end + options[:vault_id] = vault_id + options[:operation] = secure_profile_action(:modify) + add_secure_profile_variables(post, options) commit(post, true) end + def supports_scrubbing? + true + end + + def scrub(transcript) + transcript. + gsub(%r((Authorization: Basic )\w+), '\1[FILTERED]'). + gsub(/(&?password=)[^&\s]*(&?)/, '\1[FILTERED]\2'). + gsub(/(&?passcode=)[^&\s]*(&?)/, '\1[FILTERED]\2'). + gsub(/(&?trnCardCvd=)\d*(&?)/, '\1[FILTERED]\2'). + gsub(/(&?trnCardNumber=)\d*(&?)/, '\1[FILTERED]\2') + end + private + def build_response(*args) Response.new(*args) end end end end - diff --git a/lib/active_merchant/billing/gateways/beanstream/beanstream_core.rb b/lib/active_merchant/billing/gateways/beanstream/beanstream_core.rb index 634db69483d..e34e6daed12 100644 --- a/lib/active_merchant/billing/gateways/beanstream/beanstream_core.rb +++ b/lib/active_merchant/billing/gateways/beanstream/beanstream_core.rb @@ -1,6 +1,8 @@ module ActiveMerchant #:nodoc: module Billing #:nodoc: module BeanstreamCore + include Empty + RECURRING_URL = 'https://www.beanstream.com/scripts/recurring_billing.asp' SECURE_PROFILE_URL = 'https://www.beanstream.com/scripts/payment_profile.asp' @@ -59,6 +61,72 @@ module BeanstreamCore :cancel => 'C' } + STATES = { + 'ALBERTA' => 'AB', + 'BRITISH COLUMBIA' => 'BC', + 'MANITOBA' => 'MB', + 'NEW BRUNSWICK' => 'NB', + 'NEWFOUNDLAND AND LABRADOR' => 'NL', + 'NOVA SCOTIA' => 'NS', + 'ONTARIO' => 'ON', + 'PRINCE EDWARD ISLAND' => 'PE', + 'QUEBEC' => 'QC', + 'SASKATCHEWAN' => 'SK', + 'NORTHWEST TERRITORIES' => 'NT', + 'NUNAVUT' => 'NU', + 'YUKON' => 'YT', + 'ALABAMA' => 'AL', + 'ALASKA' => 'AK', + 'ARIZONA' => 'AZ', + 'ARKANSAS' => 'AR', + 'CALIFORNIA' => 'CA', + 'COLORADO' => 'CO', + 'CONNECTICUT' => 'CT', + 'DELAWARE' => 'DE', + 'FLORIDA' => 'FL', + 'GEORGIA' => 'GA', + 'HAWAII' => 'HI', + 'IDAHO' => 'ID', + 'ILLINOIS' => 'IL', + 'INDIANA' => 'IN', + 'IOWA' => 'IA', + 'KANSAS' => 'KS', + 'KENTUCKY' => 'KY', + 'LOUISIANA' => 'LA', + 'MAINE' => 'ME', + 'MARYLAND' => 'MD', + 'MASSACHUSETTS' => 'MA', + 'MICHIGAN' => 'MI', + 'MINNESOTA' => 'MN', + 'MISSISSIPPI' => 'MS', + 'MISSOURI' => 'MO', + 'MONTANA' => 'MT', + 'NEBRASKA' => 'NE', + 'NEVADA' => 'NV', + 'NEW HAMPSHIRE' => 'NH', + 'NEW JERSEY' => 'NJ', + 'NEW MEXICO' => 'NM', + 'NEW YORK' => 'NY', + 'NORTH CAROLINA' => 'NC', + 'NORTH DAKOTA' => 'ND', + 'OHIO' => 'OH', + 'OKLAHOMA' => 'OK', + 'OREGON' => 'OR', + 'PENNSYLVANIA' => 'PA', + 'RHODE ISLAND' => 'RI', + 'SOUTH CAROLINA' => 'SC', + 'SOUTH DAKOTA' => 'SD', + 'TENNESSEE' => 'TN', + 'TEXAS' => 'TX', + 'UTAH' => 'UT', + 'VERMONT' => 'VT', + 'VIRGINIA' => 'VA', + 'WASHINGTON' => 'WA', + 'WEST VIRGINIA' => 'WV', + 'WISCONSIN' => 'WI', + 'WYOMING' => 'WY' + } + def self.included(base) base.default_currency = 'CAD' @@ -70,7 +138,7 @@ def self.included(base) # The homepage URL of the gateway base.homepage_url = 'http://www.beanstream.com/' - base.live_url = 'https://www.beanstream.com/scripts/process_transaction.asp' + base.live_url = 'https://api.na.bambora.com/scripts/process_transaction.asp' # The name of the gateway base.display_name = 'Beanstream.com' @@ -87,18 +155,18 @@ def initialize(options = {}) end def capture(money, authorization, options = {}) - reference, amount, type = split_auth(authorization) - + reference, _, _ = split_auth(authorization) post = {} add_amount(post, money) add_reference(post, reference) add_transaction_type(post, :capture) + add_recurring_payment(post, options) commit(post) end def refund(money, source, options = {}) post = {} - reference, amount, type = split_auth(source) + reference, _, type = split_auth(source) add_reference(post, reference) add_transaction_type(post, refund_action(type)) add_amount(post, money) @@ -106,11 +174,12 @@ def refund(money, source, options = {}) end def credit(money, source, options = {}) - deprecated Gateway::CREDIT_DEPRECATION_MESSAGE + ActiveMerchant.deprecated Gateway::CREDIT_DEPRECATION_MESSAGE refund(money, source, options) end private + def purchase_action(source) if source.is_a?(Check) :check_purchase @@ -120,15 +189,15 @@ def purchase_action(source) end def add_customer_ip(post, options) - post[:customerIP] = options[:ip] if options[:ip] + post[:customerIp] = options[:ip] if options[:ip] end def void_action(original_transaction_type) - (original_transaction_type == TRANSACTIONS[:refund]) ? :void_refund : :void_purchase + original_transaction_type == TRANSACTIONS[:refund] ? :void_refund : :void_purchase end def refund_action(type) - (type == TRANSACTIONS[:check_purchase]) ? :check_refund : :refund + type == TRANSACTIONS[:check_purchase] ? :check_refund : :refund end def secure_profile_action(type) @@ -136,7 +205,7 @@ def secure_profile_action(type) end def split_auth(string) - string.split(";") + string.split(';') end def add_amount(post, money) @@ -152,27 +221,29 @@ def add_reference(post, reference) end def add_address(post, options) + post[:ordEmailAddress] = options[:email] if options[:email] + post[:shipEmailAddress] = options[:shipping_email] || options[:email] if options[:email] + prepare_address_for_non_american_countries(options) if billing_address = options[:billing_address] || options[:address] post[:ordName] = billing_address[:name] - post[:ordEmailAddress] = options[:email] post[:ordPhoneNumber] = billing_address[:phone] post[:ordAddress1] = billing_address[:address1] post[:ordAddress2] = billing_address[:address2] post[:ordCity] = billing_address[:city] - post[:ordProvince] = billing_address[:state] + post[:ordProvince] = state_for(billing_address) post[:ordPostalCode] = billing_address[:zip] post[:ordCountry] = billing_address[:country] end + if shipping_address = options[:shipping_address] post[:shipName] = shipping_address[:name] - post[:shipEmailAddress] = options[:email] post[:shipPhoneNumber] = shipping_address[:phone] post[:shipAddress1] = shipping_address[:address1] post[:shipAddress2] = shipping_address[:address2] post[:shipCity] = shipping_address[:city] - post[:shipProvince] = shipping_address[:state] + post[:shipProvince] = state_for(shipping_address) post[:shipPostalCode] = shipping_address[:zip] post[:shipCountry] = shipping_address[:country] post[:shippingMethod] = shipping_address[:shipping_method] @@ -180,8 +251,13 @@ def add_address(post, options) end end + def state_for(address) + STATES[address[:state].upcase] || address[:state] if address[:state] + end + def prepare_address_for_non_american_countries(options) [ options[:billing_address], options[:shipping_address] ].compact.each do |address| + next if empty?(address[:country]) unless ['US', 'CA'].include?(address[:country]) address[:state] = '--' address[:zip] = '000000' unless address[:zip] @@ -189,6 +265,10 @@ def prepare_address_for_non_american_countries(options) end end + def add_recurring_payment(post, options) + post[:recurringPayment] = 1 if options[:recurring].to_s == 'true' + end + def add_invoice(post, options) post[:trnOrderNumber] = options[:order_id] post[:trnComments] = options[:description] @@ -206,6 +286,11 @@ def add_credit_card(post, credit_card) post[:trnExpMonth] = format(credit_card.month, :two_digits) post[:trnExpYear] = format(credit_card.year, :two_digits) post[:trnCardCvd] = credit_card.verification_value + if credit_card.is_a?(NetworkTokenizationCreditCard) + post[:"3DSecureXID"] = credit_card.transaction_id + post[:"3DSecureECI"] = credit_card.eci + post[:"3DSecureCAVV"] = credit_card.payment_cryptogram + end end end @@ -227,10 +312,12 @@ def add_secure_profile_variables(post, options = {}) post[:serviceVersion] = SP_SERVICE_VERSION post[:responseFormat] = 'QS' post[:cardValidation] = (options[:cardValidation].to_i == 1) || '0' - post[:operationType] = options[:operationType] || options[:operation] || secure_profile_action(:new) post[:customerCode] = options[:billing_id] || options[:vault_id] || false post[:status] = options[:status] + + billing_address = options[:billing_address] || options[:address] + post[:trnCardOwner] = billing_address[:name] end def add_recurring_amount(post, money) @@ -291,17 +378,15 @@ def interval(options) def parse(body) results = {} - if !body.nil? - body.split(/&/).each do |pair| - key, val = pair.split(/\=/) - results[key.to_sym] = val.nil? ? nil : CGI.unescape(val) - end + body&.split(/&/)&.each do |pair| + key, val = pair.split(/\=/) + results[key.to_sym] = val.nil? ? nil : CGI.unescape(val) end # Clean up the message text if there is any if results[:messageText] - results[:messageText].gsub!(/
  • /, "") - results[:messageText].gsub!(/(\.)?
    /, ". ") + results[:messageText].gsub!(/
  • /, '') + results[:messageText].gsub!(/(\.)?
    /, '. ') results[:messageText].strip! end @@ -316,7 +401,7 @@ def recurring_parse(data) end def commit(params, use_profile_api = false) - post(post_data(params,use_profile_api),use_profile_api) + post(post_data(params, use_profile_api), use_profile_api) end def recurring_commit(params) @@ -327,10 +412,10 @@ def post(data, use_profile_api=nil) response = parse(ssl_post((use_profile_api ? SECURE_PROFILE_URL : self.live_url), data)) response[:customer_vault_id] = response[:customerCode] if response[:customerCode] build_response(success?(response), message_from(response), response, - :test => test? || response[:authCode] == "TEST", + :test => test? || response[:authCode] == 'TEST', :authorization => authorization_from(response), :cvv_result => CVD_CODES[response[:cvdId]], - :avs_result => { :code => (AVS_CODES.include? response[:avsId]) ? AVS_CODES[response[:avsId]] : response[:avsId] } + :avs_result => { :code => AVS_CODES.include?(response[:avsId]) ? AVS_CODES[response[:avsId]] : response[:avsId] } ) end @@ -351,10 +436,6 @@ def recurring_message_from(response) response[:message] end - def success?(response) - response[:responseType] == 'R' || response[:trnApproved] == '1' || response[:responseCode] == '1' - end - def recurring_success?(response) response[:code] == '1' end @@ -363,7 +444,7 @@ def add_source(post, source) if source.is_a?(String) or source.is_a?(Integer) post[:customerCode] = source else - card_brand(source) == "check" ? add_check(post, source) : add_credit_card(post, source) + card_brand(source) == 'check' ? add_check(post, source) : add_credit_card(post, source) end end @@ -380,14 +461,13 @@ def post_data(params, use_profile_api) params[:username] = @options[:user] if @options[:user] params[:password] = @options[:password] if @options[:password] params[:merchant_id] = @options[:login] + params[:passcode] = @options[:api_key] end params[:vbvEnabled] = '0' params[:scEnabled] = '0' - params.reject{|k, v| v.blank?}.collect { |key, value| "#{key}=#{CGI.escape(value.to_s)}" }.join("&") + params.reject { |k, v| v.blank? }.collect { |key, value| "#{key}=#{CGI.escape(value.to_s)}" }.join('&') end - end end end - diff --git a/lib/active_merchant/billing/gateways/beanstream_interac.rb b/lib/active_merchant/billing/gateways/beanstream_interac.rb index 2caa3b7a761..37ca7595a31 100644 --- a/lib/active_merchant/billing/gateways/beanstream_interac.rb +++ b/lib/active_merchant/billing/gateways/beanstream_interac.rb @@ -1,4 +1,4 @@ -require File.dirname(__FILE__) + '/beanstream/beanstream_core' +require 'active_merchant/billing/gateways/beanstream/beanstream_core' module ActiveMerchant #:nodoc: module Billing #:nodoc: @@ -7,21 +7,21 @@ def redirect params['pageContents'] end end - + class BeanstreamInteracGateway < Gateway include BeanstreamCore # Confirm a transaction posted back from the bank to Beanstream. # Confirming a transaction does not require any credentials, # and in an application with many merchants sharing a funded - # URL the application may not yet know which merchant the + # URL the application may not yet know which merchant the # post back is for until the response of the confirmation is # received, which contains the order number. def self.confirm(transaction) gateway = new(:login => '') gateway.confirm(transaction) end - + def purchase(money, options = {}) post = {} add_amount(post, money) @@ -31,24 +31,27 @@ def purchase(money, options = {}) add_transaction_type(post, :purchase) commit(post) end - + + def success?(response) + response[:responseType] == 'R' || response[:trnApproved] == '1' || response[:responseCode] == '1' + end + # Confirm a transaction posted back from the bank to Beanstream. def confirm(transaction) post(transaction) end - + private - + def add_interac_details(post, options) address = options[:billing_address] || options[:address] || {} post[:trnCardOwner] = address[:name] post[:paymentMethod] = 'IO' end - + def build_response(*args) BeanstreamInteracResponse.new(*args) end end end end - diff --git a/lib/active_merchant/billing/gateways/blue_pay.rb b/lib/active_merchant/billing/gateways/blue_pay.rb index 1a0b999c7f5..e275b649dfe 100644 --- a/lib/active_merchant/billing/gateways/blue_pay.rb +++ b/lib/active_merchant/billing/gateways/blue_pay.rb @@ -1,501 +1,522 @@ -require 'digest/md5' - -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - class BluePayGateway < Gateway - class_attribute :rebilling_url, :ignore_http_status - - self.live_url = 'https://secure.bluepay.com/interfaces/bp20post' - self.rebilling_url = 'https://secure.bluepay.com/interfaces/bp20rebadmin' - - self.ignore_http_status = true - - CARD_CODE_ERRORS = %w( N S ) - AVS_ERRORS = %w( A E N R W Z ) - AVS_REASON_CODES = %w(27 45) - - FRAUD_REVIEW_STATUSES = %w( E 0 ) - - FIELD_MAP = { - 'TRANS_ID' => :transaction_id, - 'STATUS' => :response_code, - 'AVS' => :avs_result_code, - 'CVV2'=> :card_code, - 'AUTH_CODE' => :authorization, - 'MESSAGE' => :message, - 'REBID' => :rebid, - 'TRANS_TYPE' => :trans_type, - 'PAYMENT_ACCOUNT_MASK' => :acct_mask, - 'CARD_TYPE' => :card_type, - } - - REBILL_FIELD_MAP = { - 'REBILL_ID' => :rebill_id, - 'ACCOUNT_ID'=> :account_id, - 'USER_ID' => :user_id, - 'TEMPLATE_ID' => :template_id, - 'STATUS' => :status, - 'CREATION_DATE' => :creation_date, - 'NEXT_DATE' => :next_date, - 'LAST_DATE' => :last_date, - 'SCHED_EXPR' => :schedule, - 'CYCLES_REMAIN' => :cycles_remain, - 'REB_AMOUNT' => :rebill_amount, - 'NEXT_AMOUNT' => :next_amount, - 'USUAL_DATE' => :undoc_usual_date, # Not found in the bp20rebadmin API doc. - } - - self.supported_countries = ['US'] - self.supported_cardtypes = [:visa, :master, :american_express, :discover, :diners_club, :jcb] - self.homepage_url = 'http://www.bluepay.com/' - self.display_name = 'BluePay' - self.money_format = :dollars - - # Creates a new BluepayGateway - # - # The gateway requires that a valid Account ID and Secret Key be passed - # in the +options+ hash. - # - # ==== Options - # - # * :account_id -- The BluePay gateway Account ID (REQUIRED) - # * :secret_key -- The BluePay gateway Secret Key (REQUIRED) - # * :test -- set to true for TEST mode or false for LIVE mode - def initialize(options = {}) - requires!(options, :login, :password) - super - end - - # Performs an authorization, which reserves the funds on the customer's credit card. This does not actually take funds from the customer - # This is referred to an AUTH transaction in BluePay - # - # ==== Parameters - # - # * money -- The amount to be authorized as an Integer value in cents. - # * payment_object -- This can either be one of three things: - # A CreditCard object, - # A Check object, - # or a token. The token is called the Master ID. This is a unique transaction ID returned from a previous transaction. This token associates all the stored information for a previous transaction. - # * options -- A hash of optional parameters. - def authorize(money, payment_object, options = {}) - post = {} - add_payment_method(post, payment_object) - add_invoice(post, options) - add_address(post, options) - add_customer_data(post, options) - add_rebill(post, options) if options[:rebill] - add_duplicate_override(post, options) - post[:TRANS_TYPE] = 'AUTH' - commit('AUTH_ONLY', money, post) - end - - # Perform a purchase, which is essentially an authorization and capture in a single operation. - # This is referred to a SALE transaction in BluePay - # - # ==== Parameters - # - # * money -- The amount to be purchased as an Integer value in cents. - # * payment_object -- This can either be one of three things: - # A CreditCard object, - # A Check object, - # or a token. The token is called the Master ID. This is a unique transaction ID returned from a previous transaction. This token associates all the stored information for a previous transaction. - # * options -- A hash of optional parameters., - def purchase(money, payment_object, options = {}) - post = {} - add_payment_method(post, payment_object) - add_invoice(post, options) - add_address(post, options) - add_customer_data(post, options) - add_rebill(post, options) if options[:rebill] - add_duplicate_override(post, options) - post[:TRANS_TYPE] = 'SALE' - commit('AUTH_CAPTURE', money, post) - end - - # Captures the funds from an authorize transaction. - # This is referred to a CAPTURE transaction in BluePay - # - # ==== Parameters - # - # * money -- The amount to be captured as an Integer value in cents. - # * identification -- The Master ID, or token, returned from the previous authorize transaction. - def capture(money, identification, options = {}) - post = {} - add_address(post, options) - add_customer_data(post, options) - post[:MASTER_ID] = identification - post[:TRANS_TYPE] = 'CAPTURE' - commit('PRIOR_AUTH_CAPTURE', money, post) - end - - # Void a previous transaction - # This is referred to a VOID transaction in BluePay - # - # ==== Parameters - # - # * identification - The Master ID, or token, returned from a previous authorize transaction. - def void(identification, options = {}) - post = {} - post[:MASTER_ID] = identification - post[:TRANS_TYPE] = 'VOID' - commit('VOID', nil, post) - end - - # Performs a credit. - # - # This transaction indicates that money should flow from the merchant to the customer. - # - # ==== Parameters - # - # * money -- The amount to be credited to the customer as an Integer value in cents. - # * payment_object -- This can either be one of three things: - # A CreditCard object, - # A Check object, - # or a token. The token is called the Master ID. This is a unique transaction ID returned from a previous transaction. This token associates all the stored information for a previous transaction. - # If the payment_object is a token, then the transaction type will reverse a previous capture or purchase transaction, returning the funds to the customer. If the amount is nil, a full credit will be processed. This is referred to a REFUND transaction in BluePay. - # If the payment_object is either a CreditCard or Check object, then the transaction type will be an unmatched credit placing funds in the specified account. This is referred to a CREDIT transaction in BluePay. - # * options -- A hash of parameters. - def refund(money, identification, options = {}) - if(identification && !identification.kind_of?(String)) - deprecated "refund should only be used to refund a referenced transaction" - return credit(money, identification, options) - end - - post = {} - post[:PAYMENT_ACCOUNT] = '' - post[:MASTER_ID] = identification - post[:TRANS_TYPE] = 'REFUND' - post[:NAME1] = (options[:first_name] ? options[:first_name] : "") - post[:NAME2] = options[:last_name] if options[:last_name] - post[:ZIP] = options[:zip] if options[:zip] - add_invoice(post, options) - add_address(post, options) - add_customer_data(post, options) - commit('CREDIT', money, post) - end - - def credit(money, payment_object, options = {}) - if(payment_object && payment_object.kind_of?(String)) - deprecated "credit should only be used to credit a payment method" - return refund(money, payment_object, options) - end - - post = {} - post[:PAYMENT_ACCOUNT] = '' - add_payment_method(post, payment_object) - post[:TRANS_TYPE] = 'CREDIT' - - post[:NAME1] = (options[:first_name] ? options[:first_name] : "") - post[:NAME2] = options[:last_name] if options[:last_name] - post[:ZIP] = options[:zip] if options[:zip] - add_invoice(post, options) - add_address(post, options) - add_customer_data(post, options) - commit('CREDIT', money, post) - end - - # Create a new recurring payment. - # - # ==== Parameters - # - # * money -- The amount to charge the customer at the time of the recurring payment setup, in cents. Set to zero if you do not want the customer to be charged at this time. - # * payment_object -- This can either be one of three things: - # A CreditCard object, - # A Check object, - # or a token. The token is called the Master ID. This is a unique transaction ID returned from a previous transaction. This token associates all the stored information for a previous transaction. - # * options -- A hash of optional parameters., - - # ==== Options - # - # * :rebill_start_date is a string that tells the gateway when to start the rebill. (REQUIRED) - # Has two valid formats: - # "YYYY-MM-DD HH:MM:SS" Hours, minutes, and seconds are optional. - # "XX UNITS" Relative date as explained below. Marked from the time of the - # transaction (i.e.: 10 DAYS, 1 MONTH, 1 YEAR) - # * :rebill_expression is the period of time in-between rebillings. (REQUIRED) - # It uses the same "XX UNITS" format as rebill_start_date, explained above. - # Optional parameters include: - # * rebill_cycles: Number of times to rebill. Don't send or set to nil for infinite rebillings (or - # until canceled). - # * rebill_amount: Amount to rebill. Defaults to amount of transaction for rebillings. - # - # For example, to charge the customer $19.95 now and then charge $39.95 in 60 days every 3 months for 5 times, the options hash would be as follows: - # :rebill_start_date => '60 DAYS', - # :rebill_expression => '3 MONTHS', - # :rebill_cycles => '5', - # :rebill_amount => '39.95' - # A money object of 1995 cents would be passed into the 'money' parameter. - def recurring(money, payment_object, options = {}) - requires!(options, :rebill_start_date, :rebill_expression) - options[:rebill] = true - if money - purchase(money, payment_object, options) - else - authorize(money, payment_object, options) - end - end - - # View a recurring payment - # - # This will pull data associated with a current recurring billing - # - # ==== Parameters - # - # * rebill_id -- A string containing the rebill_id of the recurring billing that is already active (REQUIRED) - def status_recurring(rebill_id) - post = {} - requires!(rebill_id) - post[:REBILL_ID] = rebill_id - post[:TRANS_TYPE] = 'GET' - commit('rebill', 'nil', post) - end - - # Update a recurring payment's details. - # - # This transaction updates an existing recurring billing - # - # ==== Options - # - # * :rebill_id -- The 12 digit rebill ID used to update a particular rebilling cycle. (REQUIRED) - # * :rebill_amount -- A string containing the new rebilling amount. - # * :rebill_next_date -- A string containing the new rebilling next date. - # * :rebill_expression -- A string containing the new rebilling expression. - # * :rebill_cycles -- A string containing the new rebilling cycles. - # * :rebill_next_amount -- A string containing the next rebilling amount to charge the customer. This ONLY affects the next scheduled charge; all other rebillings will continue at the regular (rebill_amount) amount. - # Take a look above at the recurring_payment method for similar examples on how to use. - def update_recurring(options = {}) - post = {} - requires!(options, :rebill_id) - post[:REBILL_ID] = options[:rebill_id] - post[:TRANS_TYPE] = 'SET' - post[:REB_AMOUNT] = amount(options[:rebill_amount]) if options[:rebill_amount] - post[:NEXT_DATE] = options[:rebill_next_date] - post[:REB_EXPR] = options[:rebill_expression] - post[:REB_CYCLES] = options[:rebill_cycles] - post[:NEXT_AMOUNT] = options[:rebill_next_amount] - commit('rebill', 'nil', post) - end - - # Cancel a recurring payment. - # - # This transaction cancels an existing recurring billing. - # - # ==== Parameters - # - # * rebill_id -- A string containing the rebill_id of the recurring billing that you wish to cancel/stop (REQUIRED) - def cancel_recurring(rebill_id) - post = {} - requires!(rebill_id) - post[:REBILL_ID] = rebill_id - post[:TRANS_TYPE] = 'SET' - post[:STATUS] = 'stopped' - commit('rebill', 'nil', post) - end - - private - - def commit(action, money, fields) - fields[:AMOUNT] = amount(money) unless(fields[:TRANS_TYPE] == 'VOID' || action == 'rebill') - fields[:MODE] = (test? ? 'TEST' : 'LIVE') - fields[:ACCOUNT_ID] = @options[:login] - - if action == 'rebill' - url = rebilling_url - fields[:TAMPER_PROOF_SEAL] = calc_rebill_tps(fields) - else - url = live_url - fields[:TAMPER_PROOF_SEAL] = calc_tps(amount(money), fields) - end - parse(ssl_post(url, post_data(action, fields))) - end - - def parse_recurring(response_fields, opts={}) # expected status? - parsed = {} - response_fields.each do |k,v| - mapped_key = REBILL_FIELD_MAP.include?(k) ? REBILL_FIELD_MAP[k] : k - parsed[mapped_key] = v - end - - success = parsed[:status] != 'error' - message = parsed[:status] - - Response.new(success, message, parsed, - :test => test?, - :authorization => parsed[:rebill_id]) - end - - def parse(body) - # The bp20api has max one value per form field. - response_fields = Hash[CGI::parse(body).map{|k,v| [k.upcase,v.first]}] - - if response_fields.include? "REBILL_ID" - return parse_recurring(response_fields) - end - - parsed = {} - response_fields.each do |k,v| - mapped_key = FIELD_MAP.include?(k) ? FIELD_MAP[k] : k - parsed[mapped_key] = v - end - - # normalize message - message = message_from(parsed) - success = parsed[:response_code] == '1' - Response.new(success, message, parsed, - :test => test?, - :authorization => (parsed[:rebid] && parsed[:rebid] != '' ? parsed[:rebid] : parsed[:transaction_id]), - :fraud_review => FRAUD_REVIEW_STATUSES.include?(parsed[:response_code]), - :avs_result => { :code => parsed[:avs_result_code] }, - :cvv_result => parsed[:card_code] - ) - end - - def message_from(parsed) - message = parsed[:message] - if(parsed[:response_code].to_i == 2) - if CARD_CODE_ERRORS.include?(parsed[:card_code]) - message = CVVResult.messages[parsed[:card_code]] - elsif AVS_ERRORS.include?(parsed[:avs_result_code]) - message = AVSResult.messages[ parsed[:avs_result_code] ] - else - message = message.chomp('.') - end - elsif message == "Missing ACCOUNT_ID" - message = "The merchant login ID or password is invalid" - elsif message =~ /Approved/ - message = "This transaction has been approved" - elsif message =~ /Expired/ - message = "The credit card has expired" - end - message - end - - def add_invoice(post, options) - post[:ORDER_ID] = options[:order_id] - post[:INVOICE_ID] = options[:invoice] - post[:invoice_num] = options[:order_id] - post[:MEMO] = options[:description] - post[:description] = options[:description] - end - - def add_payment_method(post, payment_object) - post[:MASTER_ID] = '' - case payment_object - when String - post[:MASTER_ID] = payment_object - when Check - add_check(post, payment_object) - else - add_creditcard(post, payment_object) - end - end - - def add_creditcard(post, creditcard) - post[:PAYMENT_TYPE] = 'CREDIT' - post[:PAYMENT_ACCOUNT] = creditcard.number - post[:CARD_CVV2] = creditcard.verification_value - post[:CARD_EXPIRE] = expdate(creditcard) - post[:NAME1] = creditcard.first_name - post[:NAME2] = creditcard.last_name - end - - CHECK_ACCOUNT_TYPES = { - "checking" => "C", - "savings" => "S" - } - - def add_check(post, check) - post[:PAYMENT_TYPE] = 'ACH' - post[:PAYMENT_ACCOUNT] = [CHECK_ACCOUNT_TYPES[check.account_type], check.routing_number, check.account_number].join(":") - post[:NAME1] = check.first_name - post[:NAME2] = check.last_name - end - - def add_customer_data(post, options) - post[:EMAIL] = options[:email] - post[:CUSTOM_ID] = options[:customer] - end - - def add_duplicate_override(post, options) - post[:DUPLICATE_OVERRIDE] = options[:duplicate_override] - end - - def add_address(post, options) - if address = (options[:shipping_address] || options[:billing_address] || options[:address]) - post[:ADDR1] = address[:address1] - post[:ADDR2] = address[:address2] - post[:COMPANY_NAME] = address[:company] - post[:PHONE] = address[:phone] - post[:CITY] = address[:city] - post[:STATE] = (address[:state].blank? ? 'n/a' : address[:state]) - post[:ZIP] = address[:zip] - post[:COUNTRY] = address[:country] - end - end - - def add_rebill(post, options) - post[:DO_REBILL] = '1' - post[:REB_AMOUNT] = amount(options[:rebill_amount]) - post[:REB_FIRST_DATE] = options[:rebill_start_date] - post[:REB_EXPR] = options[:rebill_expression] - post[:REB_CYCLES] = options[:rebill_cycles] - end - - def post_data(action, parameters = {}) - post = {} - post[:version] = '1' - post[:login] = '' - post[:tran_key] = '' - post[:relay_response] = "FALSE" - post[:type] = action - post[:delim_data] = "TRUE" - post[:delim_char] = "," - post[:encap_char] = "$" - post[:card_num] = '4111111111111111' - post[:exp_date] = '1212' - post[:solution_ID] = application_id if(application_id && application_id != "ActiveMerchant") - post.merge(parameters).collect { |key, value| "#{key}=#{CGI.escape(value.to_s)}" }.join("&") - end - - def expdate(creditcard) - year = format(creditcard.year, :two_digits) - month = format(creditcard.month, :two_digits) - - "#{month}#{year}" - end - - def calc_tps(amount, post) - post[:NAME1] ||= '' - Digest::MD5.hexdigest( - [ - @options[:password], - @options[:login], - post[:TRANS_TYPE], - amount, - post[:MASTER_ID], - post[:NAME1], - post[:PAYMENT_ACCOUNT] - ].join("") - ) - end - - def calc_rebill_tps(post) - Digest::MD5.hexdigest( - [ - @options[:password], - @options[:login], - post[:TRANS_TYPE], - post[:REBILL_ID] - ].join("") - ) - end - - def handle_response(response) - if ignore_http_status || (200...300).include?(response.code.to_i) - return response.body - end - raise ResponseError.new(response) - end - end - end -end +require 'digest/md5' + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class BluePayGateway < Gateway + class_attribute :rebilling_url, :ignore_http_status + + self.live_url = 'https://secure.bluepay.com/interfaces/bp20post' + self.rebilling_url = 'https://secure.bluepay.com/interfaces/bp20rebadmin' + + self.ignore_http_status = true + + CARD_CODE_ERRORS = %w( N S ) + AVS_ERRORS = %w( A E N R W Z ) + AVS_REASON_CODES = %w(27 45) + + FIELD_MAP = { + 'TRANS_ID' => :transaction_id, + 'STATUS' => :response_code, + 'AVS' => :avs_result_code, + 'CVV2'=> :card_code, + 'AUTH_CODE' => :authorization, + 'MESSAGE' => :message, + 'REBID' => :rebid, + 'TRANS_TYPE' => :trans_type, + 'PAYMENT_ACCOUNT_MASK' => :acct_mask, + 'CARD_TYPE' => :card_type + } + + REBILL_FIELD_MAP = { + 'REBILL_ID' => :rebill_id, + 'ACCOUNT_ID'=> :account_id, + 'USER_ID' => :user_id, + 'TEMPLATE_ID' => :template_id, + 'STATUS' => :status, + 'CREATION_DATE' => :creation_date, + 'NEXT_DATE' => :next_date, + 'LAST_DATE' => :last_date, + 'SCHED_EXPR' => :schedule, + 'CYCLES_REMAIN' => :cycles_remain, + 'REB_AMOUNT' => :rebill_amount, + 'NEXT_AMOUNT' => :next_amount, + 'USUAL_DATE' => :undoc_usual_date, # Not found in the bp20rebadmin API doc. + 'CUST_TOKEN' => :cust_token + } + + self.supported_countries = ['US', 'CA'] + self.supported_cardtypes = [:visa, :master, :american_express, :discover, :diners_club, :jcb] + self.homepage_url = 'http://www.bluepay.com/' + self.display_name = 'BluePay' + self.money_format = :dollars + + # Creates a new BluepayGateway + # + # The gateway requires that a valid Account ID and Secret Key be passed + # in the +options+ hash. + # + # ==== Options + # + # * :account_id -- The BluePay gateway Account ID (REQUIRED) + # * :secret_key -- The BluePay gateway Secret Key (REQUIRED) + # * :test -- set to true for TEST mode or false for LIVE mode + def initialize(options = {}) + requires!(options, :login, :password) + super + end + + # Performs an authorization, which reserves the funds on the customer's credit card. This does not actually take funds from the customer + # This is referred to an AUTH transaction in BluePay + # + # ==== Parameters + # + # * money -- The amount to be authorized as an Integer value in cents. + # * payment_object -- This can either be one of three things: + # A CreditCard object, + # A Check object, + # or a token. The token is called the Master ID. This is a unique transaction ID returned from a previous transaction. This token associates all the stored information for a previous transaction. + # * options -- A hash of optional parameters. + def authorize(money, payment_object, options = {}) + post = {} + add_payment_method(post, payment_object) + add_invoice(post, options) + add_address(post, options) + add_customer_data(post, options) + add_rebill(post, options) if options[:rebill] + add_duplicate_override(post, options) + post[:TRANS_TYPE] = 'AUTH' + commit('AUTH_ONLY', money, post, options) + end + + # Perform a purchase, which is essentially an authorization and capture in a single operation. + # This is referred to a SALE transaction in BluePay + # + # ==== Parameters + # + # * money -- The amount to be purchased as an Integer value in cents. + # * payment_object -- This can either be one of three things: + # A CreditCard object, + # A Check object, + # or a token. The token is called the Master ID. This is a unique transaction ID returned from a previous transaction. This token associates all the stored information for a previous transaction. + # * options -- A hash of optional parameters., + def purchase(money, payment_object, options = {}) + post = {} + add_payment_method(post, payment_object) + add_invoice(post, options) + add_address(post, options) + add_customer_data(post, options) + add_rebill(post, options) if options[:rebill] + add_duplicate_override(post, options) + post[:TRANS_TYPE] = 'SALE' + commit('AUTH_CAPTURE', money, post, options) + end + + # Captures the funds from an authorize transaction. + # This is referred to a CAPTURE transaction in BluePay + # + # ==== Parameters + # + # * money -- The amount to be captured as an Integer value in cents. + # * identification -- The Master ID, or token, returned from the previous authorize transaction. + def capture(money, identification, options = {}) + post = {} + add_address(post, options) + add_customer_data(post, options) + post[:MASTER_ID] = identification + post[:TRANS_TYPE] = 'CAPTURE' + commit('PRIOR_AUTH_CAPTURE', money, post, options) + end + + # Void a previous transaction + # This is referred to a VOID transaction in BluePay + # + # ==== Parameters + # + # * identification - The Master ID, or token, returned from a previous authorize transaction. + def void(identification, options = {}) + post = {} + post[:MASTER_ID] = identification + post[:TRANS_TYPE] = 'VOID' + commit('VOID', nil, post, options) + end + + # Performs a credit. + # + # This transaction indicates that money should flow from the merchant to the customer. + # + # ==== Parameters + # + # * money -- The amount to be credited to the customer as an Integer value in cents. + # * payment_object -- This can either be one of three things: + # A CreditCard object, + # A Check object, + # or a token. The token is called the Master ID. This is a unique transaction ID returned from a previous transaction. This token associates all the stored information for a previous transaction. + # If the payment_object is a token, then the transaction type will reverse a previous capture or purchase transaction, returning the funds to the customer. If the amount is nil, a full credit will be processed. This is referred to a REFUND transaction in BluePay. + # If the payment_object is either a CreditCard or Check object, then the transaction type will be an unmatched credit placing funds in the specified account. This is referred to a CREDIT transaction in BluePay. + # * options -- A hash of parameters. + def refund(money, identification, options = {}) + if(identification && !identification.kind_of?(String)) + ActiveMerchant.deprecated 'refund should only be used to refund a referenced transaction' + return credit(money, identification, options) + end + + post = {} + post[:PAYMENT_ACCOUNT] = '' + post[:MASTER_ID] = identification + post[:TRANS_TYPE] = 'REFUND' + post[:NAME1] = options[:first_name] || '' + post[:NAME2] = options[:last_name] if options[:last_name] + post[:ZIP] = options[:zip] if options[:zip] + add_invoice(post, options) + add_address(post, options) + add_customer_data(post, options) + commit('CREDIT', money, post, options) + end + + def credit(money, payment_object, options = {}) + if payment_object&.kind_of?(String) + ActiveMerchant.deprecated 'credit should only be used to credit a payment method' + return refund(money, payment_object, options) + end + + post = {} + post[:PAYMENT_ACCOUNT] = '' + add_payment_method(post, payment_object) + post[:TRANS_TYPE] = 'CREDIT' + + post[:NAME1] = options[:first_name] || '' + post[:NAME2] = options[:last_name] if options[:last_name] + post[:ZIP] = options[:zip] if options[:zip] + add_invoice(post, options) + add_address(post, options) + add_customer_data(post, options) + commit('CREDIT', money, post, options) + end + + # Create a new recurring payment. + # + # ==== Parameters + # + # * money -- The amount to charge the customer at the time of the recurring payment setup, in cents. Set to zero if you do not want the customer to be charged at this time. + # * payment_object -- This can either be one of three things: + # A CreditCard object, + # A Check object, + # or a token. The token is called the Master ID. This is a unique transaction ID returned from a previous transaction. This token associates all the stored information for a previous transaction. + # * options -- A hash of optional parameters., + + # ==== Options + # + # * :rebill_start_date is a string that tells the gateway when to start the rebill. (REQUIRED) + # Has two valid formats: + # "YYYY-MM-DD HH:MM:SS" Hours, minutes, and seconds are optional. + # "XX UNITS" Relative date as explained below. Marked from the time of the + # transaction (i.e.: 10 DAYS, 1 MONTH, 1 YEAR) + # * :rebill_expression is the period of time in-between rebillings. (REQUIRED) + # It uses the same "XX UNITS" format as rebill_start_date, explained above. + # Optional parameters include: + # * rebill_cycles: Number of times to rebill. Don't send or set to nil for infinite rebillings (or + # until canceled). + # * rebill_amount: Amount to rebill. Defaults to amount of transaction for rebillings. + # + # For example, to charge the customer $19.95 now and then charge $39.95 in 60 days every 3 months for 5 times, the options hash would be as follows: + # :rebill_start_date => '60 DAYS', + # :rebill_expression => '3 MONTHS', + # :rebill_cycles => '5', + # :rebill_amount => '39.95' + # A money object of 1995 cents would be passed into the 'money' parameter. + def recurring(money, payment_object, options = {}) + ActiveMerchant.deprecated RECURRING_DEPRECATION_MESSAGE + + requires!(options, :rebill_start_date, :rebill_expression) + options[:rebill] = true + if money + purchase(money, payment_object, options) + else + authorize(money, payment_object, options) + end + end + + # View a recurring payment + # + # This will pull data associated with a current recurring billing + # + # ==== Parameters + # + # * rebill_id -- A string containing the rebill_id of the recurring billing that is already active (REQUIRED) + def status_recurring(rebill_id) + ActiveMerchant.deprecated RECURRING_DEPRECATION_MESSAGE + + post = {} + requires!(rebill_id) + post[:REBILL_ID] = rebill_id + post[:TRANS_TYPE] = 'GET' + commit('rebill', 'nil', post) + end + + # Update a recurring payment's details. + # + # This transaction updates an existing recurring billing + # + # ==== Options + # + # * :rebill_id -- The 12 digit rebill ID used to update a particular rebilling cycle. (REQUIRED) + # * :rebill_amount -- A string containing the new rebilling amount. + # * :rebill_next_date -- A string containing the new rebilling next date. + # * :rebill_expression -- A string containing the new rebilling expression. + # * :rebill_cycles -- A string containing the new rebilling cycles. + # * :rebill_next_amount -- A string containing the next rebilling amount to charge the customer. This ONLY affects the next scheduled charge; all other rebillings will continue at the regular (rebill_amount) amount. + # Take a look above at the recurring_payment method for similar examples on how to use. + def update_recurring(options = {}) + ActiveMerchant.deprecated RECURRING_DEPRECATION_MESSAGE + + post = {} + requires!(options, :rebill_id) + post[:REBILL_ID] = options[:rebill_id] + post[:TRANS_TYPE] = 'SET' + post[:REB_AMOUNT] = amount(options[:rebill_amount]) if options[:rebill_amount] + post[:NEXT_DATE] = options[:rebill_next_date] + post[:REB_EXPR] = options[:rebill_expression] + post[:REB_CYCLES] = options[:rebill_cycles] + post[:NEXT_AMOUNT] = options[:rebill_next_amount] + commit('rebill', 'nil', post) + end + + # Cancel a recurring payment. + # + # This transaction cancels an existing recurring billing. + # + # ==== Parameters + # + # * rebill_id -- A string containing the rebill_id of the recurring billing that you wish to cancel/stop (REQUIRED) + def cancel_recurring(rebill_id) + ActiveMerchant.deprecated RECURRING_DEPRECATION_MESSAGE + + post = {} + requires!(rebill_id) + post[:REBILL_ID] = rebill_id + post[:TRANS_TYPE] = 'SET' + post[:STATUS] = 'stopped' + commit('rebill', 'nil', post) + end + + def supports_scrubbing + true + end + + def scrub(transcript) + transcript. + gsub(%r((Authorization: Basic )\w+), '\1[FILTERED]'). + gsub(%r((&?card_num=)[^&]*)i, '\1[FILTERED]'). + gsub(%r((&?CARD_CVV2=)[^&]*)i, '\1[FILTERED]'). + gsub(%r((&?PAYMENT_ACCOUNT=)[^&]*)i, '\1[FILTERED]'). + gsub(%r((&?TAMPER_PROOF_SEAL=)[^&"]*)i, '\1[FILTERED]') + end + + private + + def commit(action, money, fields, options = {}) + fields[:AMOUNT] = amount(money) unless(fields[:TRANS_TYPE] == 'VOID' || action == 'rebill') + fields[:MODE] = (test? ? 'TEST' : 'LIVE') + fields[:ACCOUNT_ID] = @options[:login] + fields[:CUSTOMER_IP] = options[:ip] if options[:ip] + + if action == 'rebill' + url = rebilling_url + fields[:TAMPER_PROOF_SEAL] = calc_rebill_tps(fields) + else + url = live_url + fields[:TAMPER_PROOF_SEAL] = calc_tps(amount(money), fields) + end + parse(ssl_post(url, post_data(action, fields))) + end + + def parse_recurring(response_fields, opts={}) # expected status? + parsed = {} + response_fields.each do |k, v| + mapped_key = REBILL_FIELD_MAP.include?(k) ? REBILL_FIELD_MAP[k] : k + parsed[mapped_key] = v + end + + success = parsed[:status] != 'error' + message = parsed[:status] + + Response.new(success, message, parsed, + :test => test?, + :authorization => parsed[:rebill_id]) + end + + def parse(body) + # The bp20api has max one value per form field. + response_fields = Hash[CGI::parse(body).map { |k, v| [k.upcase, v.first] }] + + if response_fields.include? 'REBILL_ID' + return parse_recurring(response_fields) + end + + parsed = {} + response_fields.each do |k, v| + mapped_key = FIELD_MAP.include?(k) ? FIELD_MAP[k] : k + parsed[mapped_key] = v + end + + # normalize message + message = message_from(parsed) + success = parsed[:response_code] == '1' + Response.new(success, message, parsed, + :test => test?, + :authorization => (parsed[:rebid] && parsed[:rebid] != '' ? parsed[:rebid] : parsed[:transaction_id]), + :avs_result => { :code => parsed[:avs_result_code] }, + :cvv_result => parsed[:card_code] + ) + end + + def message_from(parsed) + message = parsed[:message] + if(parsed[:response_code].to_i == 2) + if CARD_CODE_ERRORS.include?(parsed[:card_code]) + message = CVVResult.messages[parsed[:card_code]] + elsif AVS_ERRORS.include?(parsed[:avs_result_code]) + message = AVSResult.messages[parsed[:avs_result_code]] + else + message = message.chomp('.') + end + elsif message == 'Missing ACCOUNT_ID' + message = 'The merchant login ID or password is invalid' + elsif message =~ /Approved/ + message = 'This transaction has been approved' + elsif message =~ /Expired/ + message = 'The credit card has expired' + end + message + end + + def add_invoice(post, options) + post[:ORDER_ID] = options[:order_id] + post[:INVOICE_ID] = options[:invoice] + post[:invoice_num] = options[:order_id] + post[:MEMO] = options[:description] + post[:description] = options[:description] + end + + def add_payment_method(post, payment_object) + post[:MASTER_ID] = '' + case payment_object + when String + post[:MASTER_ID] = payment_object + when Check + add_check(post, payment_object) + else + add_creditcard(post, payment_object) + end + end + + def add_creditcard(post, creditcard) + post[:PAYMENT_TYPE] = 'CREDIT' + post[:PAYMENT_ACCOUNT] = creditcard.number + post[:CARD_CVV2] = creditcard.verification_value + post[:CARD_EXPIRE] = expdate(creditcard) + post[:NAME1] = creditcard.first_name + post[:NAME2] = creditcard.last_name + end + + CHECK_ACCOUNT_TYPES = { + 'checking' => 'C', + 'savings' => 'S' + } + + def add_check(post, check) + post[:PAYMENT_TYPE] = 'ACH' + post[:PAYMENT_ACCOUNT] = [CHECK_ACCOUNT_TYPES[check.account_type], check.routing_number, check.account_number].join(':') + post[:NAME1] = check.first_name + post[:NAME2] = check.last_name + end + + def add_customer_data(post, options) + post[:EMAIL] = options[:email] + post[:CUSTOM_ID] = options[:customer] + post[:CUSTOM_ID2] = options[:custom_id2] + end + + def add_duplicate_override(post, options) + post[:DUPLICATE_OVERRIDE] = options[:duplicate_override] + end + + def add_address(post, options) + if address = (options[:shipping_address] || options[:billing_address] || options[:address]) + post[:ADDR1] = address[:address1] + post[:ADDR2] = address[:address2] + post[:COMPANY_NAME] = address[:company] + post[:PHONE] = address[:phone] + post[:CITY] = address[:city] + post[:STATE] = (address[:state].blank? ? 'n/a' : address[:state]) + post[:ZIP] = address[:zip] + post[:COUNTRY] = address[:country] + end + end + + def add_rebill(post, options) + post[:DO_REBILL] = '1' + post[:REB_AMOUNT] = amount(options[:rebill_amount]) + post[:REB_FIRST_DATE] = options[:rebill_start_date] + post[:REB_EXPR] = options[:rebill_expression] + post[:REB_CYCLES] = options[:rebill_cycles] + end + + def post_data(action, parameters = {}) + post = {} + post[:version] = '1' + post[:login] = '' + post[:tran_key] = '' + post[:relay_response] = 'FALSE' + post[:type] = action + post[:delim_data] = 'TRUE' + post[:delim_char] = ',' + post[:encap_char] = '$' + post[:card_num] = '4111111111111111' + post[:exp_date] = '1212' + post[:solution_ID] = application_id if application_id + post.merge(parameters).collect { |key, value| "#{key}=#{CGI.escape(value.to_s)}" }.join('&') + end + + def expdate(creditcard) + year = format(creditcard.year, :two_digits) + month = format(creditcard.month, :two_digits) + + "#{month}#{year}" + end + + def calc_tps(amount, post) + post[:NAME1] ||= '' + Digest::MD5.hexdigest( + [ + @options[:password], + @options[:login], + post[:TRANS_TYPE], + amount, + post[:MASTER_ID], + post[:NAME1], + post[:PAYMENT_ACCOUNT] + ].join('') + ) + end + + def calc_rebill_tps(post) + Digest::MD5.hexdigest( + [ + @options[:password], + @options[:login], + post[:TRANS_TYPE], + post[:REBILL_ID] + ].join('') + ) + end + + def handle_response(response) + if ignore_http_status || (200...300).cover?(response.code.to_i) + return response.body + end + raise ResponseError.new(response) + end + end + end +end diff --git a/lib/active_merchant/billing/gateways/blue_snap.rb b/lib/active_merchant/billing/gateways/blue_snap.rb new file mode 100644 index 00000000000..b344749b2d4 --- /dev/null +++ b/lib/active_merchant/billing/gateways/blue_snap.rb @@ -0,0 +1,522 @@ +require 'nokogiri' + +module ActiveMerchant + module Billing + class BlueSnapGateway < Gateway + self.test_url = 'https://sandbox.bluesnap.com/services/2' + self.live_url = 'https://ws.bluesnap.com/services/2' + self.supported_countries = %w(US CA GB AT BE BG HR CY CZ DK EE FI FR DE GR HU IE IT LV LT LU MT NL PL PT RO SK SI ES SE AR BO BR BZ CL CO CR DO EC GF GP GT HN HT MF MQ MX NI PA PE PR PY SV UY VE) + + self.default_currency = 'USD' + self.supported_cardtypes = [:visa, :master, :american_express, :discover, :jcb, :diners_club, :maestro, :naranja, :cabal] + + self.homepage_url = 'https://home.bluesnap.com/' + self.display_name = 'BlueSnap' + + TRANSACTIONS = { + purchase: 'AUTH_CAPTURE', + authorize: 'AUTH_ONLY', + capture: 'CAPTURE', + void: 'AUTH_REVERSAL', + refund: 'REFUND' + } + + CVC_CODE_TRANSLATOR = { + 'MA' => 'M', + 'NC' => 'U', + 'ND' => 'P', + 'NM' => 'N', + 'NP' => 'S' + } + + AVS_CODE_TRANSLATOR = { + 'line1: U, zip: U, name: U' => 'I', + 'line1: U, zip: U, name: M' => 'I', + 'line1: U, zip: U, name: N' => 'I', + 'line1: U, zip: M, name: U' => 'P', + 'line1: U, zip: M, name: M' => 'P', + 'line1: U, zip: M, name: N' => 'F', + 'line1: U, zip: N, name: U' => 'O', + 'line1: U, zip: N, name: M' => 'O', + 'line1: U, zip: N, name: N' => 'O', + 'line1: M, zip: U, name: U' => 'B', + 'line1: M, zip: U, name: M' => 'B', + 'line1: M, zip: U, name: N' => 'T', + 'line1: M, zip: M, name: U' => 'M', + 'line1: M, zip: M, name: M' => 'V', + 'line1: M, zip: M, name: N' => 'H', + 'line1: M, zip: N, name: U' => 'A', + 'line1: M, zip: N, name: M' => 'O', + 'line1: M, zip: N, name: N' => 'A', + 'line1: N, zip: U, name: U' => 'C', + 'line1: N, zip: U, name: M' => 'C', + 'line1: N, zip: U, name: N' => 'C', + 'line1: N, zip: M, name: U' => 'W', + 'line1: N, zip: M, name: M' => 'L', + 'line1: N, zip: M, name: N' => 'W', + 'line1: N, zip: N, name: U' => 'N', + 'line1: N, zip: N, name: M' => 'K', + 'line1: N, zip: N, name: N' => 'N', + } + + BANK_ACCOUNT_TYPE_MAPPING = { + 'personal_checking' => 'CONSUMER_CHECKING', + 'personal_savings' => 'CONSUMER_SAVINGS', + 'business_checking' => 'CORPORATE_CHECKING', + 'business_savings' => 'CORPORATE_SAVINGS' + } + + STATE_CODE_COUNTRIES = %w(US CA) + + def initialize(options={}) + requires!(options, :api_username, :api_password) + super + end + + def purchase(money, payment_method, options={}) + payment_method_details = PaymentMethodDetails.new(payment_method) + + commit(:purchase, :post, payment_method_details) do |doc| + if payment_method_details.alt_transaction? + add_alt_transaction_purchase(doc, money, payment_method_details, options) + else + add_auth_purchase(doc, money, payment_method, options) + end + end + end + + def authorize(money, payment_method, options={}) + commit(:authorize) do |doc| + add_auth_purchase(doc, money, payment_method, options) + end + end + + def capture(money, authorization, options={}) + commit(:capture, :put) do |doc| + add_authorization(doc, authorization) + add_order(doc, options) + add_amount(doc, money, options) if options[:include_capture_amount] == true + end + end + + def refund(money, authorization, options={}) + commit(:refund, :put) do |doc| + add_authorization(doc, authorization) + add_amount(doc, money, options) + add_order(doc, options) + end + end + + def void(authorization, options={}) + commit(:void, :put) do |doc| + add_authorization(doc, authorization) + add_order(doc, options) + end + end + + def verify(payment_method, options={}) + authorize(0, payment_method, options) + end + + def store(payment_method, options = {}) + payment_method_details = PaymentMethodDetails.new(payment_method) + + commit(:store, :post, payment_method_details) do |doc| + add_personal_info(doc, payment_method, options) + add_echeck_company(doc, payment_method) if payment_method_details.check? + doc.send('payment-sources') do + payment_method_details.check? ? store_echeck(doc, payment_method) : store_credit_card(doc, payment_method) + end + add_order(doc, options) + end + end + + def store_credit_card(doc, payment_method) + doc.send('credit-card-info') do + add_credit_card(doc, payment_method) + end + end + + def store_echeck(doc, payment_method) + doc.send('ecp-info') do + doc.send('ecp') do + add_echeck(doc, payment_method) + end + end + end + + def verify_credentials + begin + ssl_get(url.to_s, headers) + rescue ResponseError => e + return false if e.response.code.to_i == 401 + end + + true + end + + def supports_scrubbing? + true + end + + def scrub(transcript) + transcript. + gsub(%r((Authorization: Basic )\w+), '\1[FILTERED]'). + gsub(%r(().+()), '\1[FILTERED]\2'). + gsub(%r(().+()), '\1[FILTERED]\2'). + gsub(%r((<(?:public-)?account-number>).+()), '\1[FILTERED]\2'). + gsub(%r((<(?:public-)?routing-number>).+()), '\1[FILTERED]\2') + end + + private + + def add_auth_purchase(doc, money, payment_method, options) + doc.send('recurring-transaction', options[:recurring] ? 'RECURRING' : 'ECOMMERCE') + add_order(doc, options) + doc.send('store-card', options[:store_card] || false) + add_amount(doc, money, options) + add_fraud_info(doc, options) + + if payment_method.is_a?(String) + doc.send('vaulted-shopper-id', payment_method) + else + doc.send('card-holder-info') do + add_personal_info(doc, payment_method, options) + end + add_credit_card(doc, payment_method) + end + end + + def add_amount(doc, money, options) + doc.amount(amount(money)) + doc.currency(options[:currency] || currency(money)) + end + + def add_personal_info(doc, payment_method, options) + doc.send('first-name', payment_method.first_name) + doc.send('last-name', payment_method.last_name) + doc.send('personal-identification-number', options[:personal_identification_number]) if options[:personal_identification_number] + doc.email(options[:email]) if options[:email] + add_address(doc, options) + end + + def add_credit_card(doc, card) + doc.send('credit-card') do + doc.send('card-number', card.number) + doc.send('security-code', card.verification_value) + doc.send('expiration-month', card.month) + doc.send('expiration-year', card.year) + end + end + + def add_description(doc, description) + doc.send('transaction-meta-data') do + doc.send('meta-data') do + doc.send('meta-key', 'description') + doc.send('meta-value', truncate(description, 500)) + doc.send('meta-description', 'Description') + end + end + end + + def add_order(doc, options) + doc.send('merchant-transaction-id', truncate(options[:order_id], 50)) if options[:order_id] + doc.send('soft-descriptor', options[:soft_descriptor]) if options[:soft_descriptor] + add_description(doc, options[:description]) if options[:description] + add_3ds(doc, options[:three_d_secure]) if options[:three_d_secure] + add_level_3_data(doc, options) + end + + def add_address(doc, options) + address = options[:billing_address] + return unless address + + doc.country(address[:country]) if address[:country] + doc.state(address[:state]) if address[:state] && STATE_CODE_COUNTRIES.include?(address[:country]) + doc.address(address[:address]) if address[:address] + doc.city(address[:city]) if address[:city] + doc.zip(address[:zip]) if address[:zip] + end + + def add_3ds(doc, three_d_secure_options) + eci = three_d_secure_options[:eci] + cavv = three_d_secure_options[:cavv] + xid = three_d_secure_options[:xid] + ds_transaction_id = three_d_secure_options[:ds_transaction_id] + version = three_d_secure_options[:version] + + doc.send('three-d-secure') do + doc.eci(eci) if eci + doc.cavv(cavv) if cavv + doc.xid(xid) if xid + doc.send('three-d-secure-version', version) if version + doc.send('ds-transaction-id', ds_transaction_id) if ds_transaction_id + end + end + + def add_level_3_data(doc, options) + return unless options[:customer_reference_number] + doc.send('level-3-data') do + send_when_present(doc, :customer_reference_number, options) + send_when_present(doc, :sales_tax_amount, options) + send_when_present(doc, :freight_amount, options) + send_when_present(doc, :duty_amount, options) + send_when_present(doc, :destination_zip_code, options) + send_when_present(doc, :destination_country_code, options) + send_when_present(doc, :ship_from_zip_code, options) + send_when_present(doc, :discount_amount, options) + send_when_present(doc, :tax_amount, options) + send_when_present(doc, :tax_rate, options) + add_level_3_data_items(doc, options[:level_3_data_items]) if options[:level_3_data_items] + end + end + + def send_when_present(doc, options_key, options, xml_element_name = nil) + return unless options[options_key] + xml_element_name ||= options_key.to_s + + doc.send(xml_element_name.dasherize, options[options_key]) + end + + def add_level_3_data_items(doc, items) + items.each do |item| + doc.send('level-3-data-item') do + item.each do |key, value| + key = key.to_s.dasherize + doc.send(key, value) + end + end + end + end + + def add_authorization(doc, authorization) + doc.send('transaction-id', authorization) + end + + def add_fraud_info(doc, options) + doc.send('transaction-fraud-info') do + doc.send('shopper-ip-address', options[:ip]) if options[:ip] + end + end + + def add_alt_transaction_purchase(doc, money, payment_method_details, options) + doc.send('merchant-transaction-id', truncate(options[:order_id], 50)) if options[:order_id] + doc.send('soft-descriptor', options[:soft_descriptor]) if options[:soft_descriptor] + add_amount(doc, money, options) + + vaulted_shopper_id = payment_method_details.vaulted_shopper_id + doc.send('vaulted-shopper-id', vaulted_shopper_id) if vaulted_shopper_id + + if payment_method_details.check? + add_echeck_transaction(doc, payment_method_details.payment_method, options, vaulted_shopper_id.present?) + end + + add_fraud_info(doc, options) + add_description(doc, options) + end + + def add_echeck_transaction(doc, check, options, vaulted_shopper) + unless vaulted_shopper + doc.send('payer-info') do + add_personal_info(doc, check, options) + add_echeck_company(doc, check) + end + end + + doc.send('ecp-transaction') do + add_echeck(doc, check) unless vaulted_shopper + end + + doc.send('authorized-by-shopper', options[:authorized_by_shopper]) + end + + def add_echeck_company(doc, check) + doc.send('company-name', truncate(check.name, 50)) if check.account_holder_type = 'business' + end + + def add_echeck(doc, check) + doc.send('account-number', check.account_number) + doc.send('routing-number', check.routing_number) + doc.send('account-type', BANK_ACCOUNT_TYPE_MAPPING["#{check.account_holder_type}_#{check.account_type}"]) + end + + def parse(response) + return bad_authentication_response if response.code.to_i == 401 + return forbidden_response(response.body) if response.code.to_i == 403 + + parsed = {} + doc = Nokogiri::XML(response.body) + doc.root.xpath('*').each do |node| + if node.elements.empty? + parsed[node.name.downcase] = node.text + else + node.elements.each do |childnode| + parse_element(parsed, childnode) + end + end + end + + parsed['content-location-header'] = response['content-location'] + parsed + end + + def parse_element(parsed, node) + if !node.elements.empty? + node.elements.each { |e| parse_element(parsed, e) } + else + parsed[node.name.downcase] = node.text + end + end + + def api_request(action, request, verb, payment_method_details) + ssl_request(verb, url(action, payment_method_details), request, headers) + rescue ResponseError => e + e.response + end + + def commit(action, verb = :post, payment_method_details = PaymentMethodDetails.new()) + request = build_xml_request(action, payment_method_details) { |doc| yield(doc) } + response = api_request(action, request, verb, payment_method_details) + parsed = parse(response) + + succeeded = success_from(action, response) + Response.new( + succeeded, + message_from(succeeded, parsed), + parsed, + authorization: authorization_from(action, parsed, payment_method_details), + avs_result: avs_result(parsed), + cvv_result: cvv_result(parsed), + error_code: error_code_from(parsed), + test: test? + ) + end + + def url(action = nil, payment_method_details = PaymentMethodDetails.new()) + base = test? ? test_url : live_url + resource = action == :store ? 'vaulted-shoppers' : payment_method_details.resource_url + "#{base}/#{resource}" + end + + def cvv_result(parsed) + CVVResult.new(CVC_CODE_TRANSLATOR[parsed['cvv-response-code']]) + end + + def avs_result(parsed) + AVSResult.new(code: AVS_CODE_TRANSLATOR[avs_lookup_key(parsed)]) + end + + def avs_lookup_key(p) + "line1: #{p['avs-response-code-address']}, zip: #{p['avs-response-code-zip']}, name: #{p['avs-response-code-name']}" + end + + def success_from(action, response) + (200...300).cover?(response.code.to_i) + end + + def message_from(succeeded, parsed_response) + return 'Success' if succeeded + parsed_response['description'] + end + + def authorization_from(action, parsed_response, payment_method_details) + action == :store ? vaulted_shopper_id(parsed_response, payment_method_details) : parsed_response['transaction-id'] + end + + def vaulted_shopper_id(parsed_response, payment_method_details) + return nil unless parsed_response['content-location-header'] + vaulted_shopper_id = parsed_response['content-location-header'].split('/').last + vaulted_shopper_id += "|#{payment_method_details.payment_method_type}" if payment_method_details.alt_transaction? + vaulted_shopper_id + end + + def error_code_from(parsed_response) + parsed_response['code'] + end + + def root_attributes + { + xmlns: 'http://ws.plimus.com' + } + end + + def root_element(action, payment_method_details) + action == :store ? 'vaulted-shopper' : payment_method_details.root_element + end + + def headers + { + 'Content-Type' => 'application/xml', + 'Authorization' => ('Basic ' + Base64.strict_encode64("#{@options[:api_username]}:#{@options[:api_password]}").strip), + } + end + + def build_xml_request(action, payment_method_details) + builder = Nokogiri::XML::Builder.new + builder.__send__(root_element(action, payment_method_details), root_attributes) do |doc| + doc.send('card-transaction-type', TRANSACTIONS[action]) if TRANSACTIONS[action] && !payment_method_details.alt_transaction? + yield(doc) + end + builder.doc.root.to_xml + end + + def handle_response(response) + case response.code.to_i + when 200...300 + response + else + raise ResponseError.new(response) + end + end + + def bad_authentication_response + { 'description' => 'Unable to authenticate. Please check your credentials.' } + end + + def forbidden_response(body) + { 'description' => body } + end + end + + class PaymentMethodDetails + attr_reader :payment_method, :vaulted_shopper_id, :payment_method_type + + def initialize(payment_method = nil) + @payment_method = payment_method + @payment_method_type = nil + parse(payment_method) + end + + def check? + @payment_method.is_a?(Check) || @payment_method_type == 'check' + end + + def alt_transaction? + check? + end + + def root_element + alt_transaction? ? 'alt-transaction' : 'card-transaction' + end + + def resource_url + alt_transaction? ? 'alt-transactions' : 'transactions' + end + + private + + def parse(payment_method) + return unless payment_method + + if payment_method.is_a?(String) + @vaulted_shopper_id, payment_method_type = payment_method.split('|') + @payment_method_type = payment_method_type if payment_method_type.present? + elsif payment_method.is_a?(Check) + @payment_method_type = payment_method.type + else + @payment_method_type = 'credit_card' + end + end + end + end +end diff --git a/lib/active_merchant/billing/gateways/bogus.rb b/lib/active_merchant/billing/gateways/bogus.rb index 3a23de6e8b0..8cafd0eeba5 100644 --- a/lib/active_merchant/billing/gateways/bogus.rb +++ b/lib/active_merchant/billing/gateways/bogus.rb @@ -4,70 +4,54 @@ module Billing #:nodoc: class BogusGateway < Gateway AUTHORIZATION = '53433' - SUCCESS_MESSAGE = "Bogus Gateway: Forced success" - FAILURE_MESSAGE = "Bogus Gateway: Forced failure" - ERROR_MESSAGE = "Bogus Gateway: Use CreditCard number ending in 1 for success, 2 for exception and anything else for error" - CREDIT_ERROR_MESSAGE = "Bogus Gateway: Use CreditCard number ending in 1 for success, 2 for exception and anything else for error" - UNSTORE_ERROR_MESSAGE = "Bogus Gateway: Use trans_id ending in 1 for success, 2 for exception and anything else for error" - CAPTURE_ERROR_MESSAGE = "Bogus Gateway: Use authorization number ending in 1 for exception, 2 for error and anything else for success" - VOID_ERROR_MESSAGE = "Bogus Gateway: Use authorization number ending in 1 for exception, 2 for error and anything else for success" - REFUND_ERROR_MESSAGE = "Bogus Gateway: Use trans_id number ending in 1 for exception, 2 for error and anything else for success" - - self.supported_countries = ['US'] + AUTHORIZATION_EMV_SUCCESS = '8A023030' + AUTHORIZATION_EMV_DECLINE = '8A023035' + + SUCCESS_MESSAGE = 'Bogus Gateway: Forced success' + FAILURE_MESSAGE = 'Bogus Gateway: Forced failure' + NUMBER_ERROR_MESSAGE = 'Bogus Gateway: Use CreditCard number ending in 1 for success, 2 for exception and anything else for error' + AMOUNT_ERROR_MESSAGE = 'Bogus Gateway: Use amount ending in 00 for success, 05 for failure and anything else for exception' + UNSTORE_ERROR_MESSAGE = 'Bogus Gateway: Use trans_id ending in 1 for success, 2 for exception and anything else for error' + CAPTURE_ERROR_MESSAGE = 'Bogus Gateway: Use authorization number ending in 1 for exception, 2 for error and anything else for success' + VOID_ERROR_MESSAGE = 'Bogus Gateway: Use authorization number ending in 1 for exception, 2 for error and anything else for success' + REFUND_ERROR_MESSAGE = 'Bogus Gateway: Use trans_id number ending in 1 for exception, 2 for error and anything else for success' + CHECK_ERROR_MESSAGE = 'Bogus Gateway: Use bank account number ending in 1 for success, 2 for exception and anything else for error' + + self.supported_countries = [] self.supported_cardtypes = [:bogus] self.homepage_url = 'http://example.com' self.display_name = 'Bogus' - def authorize(money, credit_card_or_reference, options = {}) - money = amount(money) - case normalize(credit_card_or_reference) - when /1$/ - Response.new(true, SUCCESS_MESSAGE, {:authorized_amount => money}, :test => true, :authorization => AUTHORIZATION ) - when /2$/ - Response.new(false, FAILURE_MESSAGE, {:authorized_amount => money, :error => FAILURE_MESSAGE }, :test => true) + def authorize(money, paysource, options = {}) + if paysource.respond_to?(:emv?) && paysource.emv? + authorize_emv(money, paysource, options) else - raise Error, ERROR_MESSAGE + authorize_swipe(money, paysource, options) end end - def purchase(money, credit_card_or_reference, options = {}) - money = amount(money) - case normalize(credit_card_or_reference) - when /1$/, AUTHORIZATION - Response.new(true, SUCCESS_MESSAGE, {:paid_amount => money}, :test => true, :authorization => AUTHORIZATION) - when /2$/ - Response.new(false, FAILURE_MESSAGE, {:paid_amount => money, :error => FAILURE_MESSAGE },:test => true) + def purchase(money, paysource, options = {}) + if paysource.respond_to?(:emv?) && paysource.emv? + purchase_emv(money, paysource, options) else - raise Error, ERROR_MESSAGE + purchase_swipe(money, paysource, options) end end - def recurring(money, credit_card_or_reference, options = {}) - money = amount(money) - case normalize(credit_card_or_reference) - when /1$/ - Response.new(true, SUCCESS_MESSAGE, {:paid_amount => money}, :test => true) - when /2$/ - Response.new(false, FAILURE_MESSAGE, {:paid_amount => money, :error => FAILURE_MESSAGE },:test => true) - else - raise Error, ERROR_MESSAGE - end - end - - def credit(money, credit_card_or_reference, options = {}) - if credit_card_or_reference.is_a?(String) - deprecated CREDIT_DEPRECATION_MESSAGE - return refund(money, credit_card_or_reference, options) + def credit(money, paysource, options = {}) + if paysource.is_a?(String) + ActiveMerchant.deprecated CREDIT_DEPRECATION_MESSAGE + return refund(money, paysource, options) end money = amount(money) - case normalize(credit_card_or_reference) + case normalize(paysource) when /1$/ - Response.new(true, SUCCESS_MESSAGE, {:paid_amount => money}, :test => true ) + Response.new(true, SUCCESS_MESSAGE, {:paid_amount => money}, :test => true) when /2$/ - Response.new(false, FAILURE_MESSAGE, {:paid_amount => money, :error => FAILURE_MESSAGE }, :test => true) + Response.new(false, FAILURE_MESSAGE, {:paid_amount => money, :error => FAILURE_MESSAGE }, :test => true, :error_code => STANDARD_ERROR_CODE[:processing_error]) else - raise Error, CREDIT_ERROR_MESSAGE + raise Error, error_message(paysource) end end @@ -77,7 +61,7 @@ def refund(money, reference, options = {}) when /1$/ raise Error, REFUND_ERROR_MESSAGE when /2$/ - Response.new(false, FAILURE_MESSAGE, {:paid_amount => money, :error => FAILURE_MESSAGE }, :test => true) + Response.new(false, FAILURE_MESSAGE, {:paid_amount => money, :error => FAILURE_MESSAGE }, :test => true, :error_code => STANDARD_ERROR_CODE[:processing_error]) else Response.new(true, SUCCESS_MESSAGE, {:paid_amount => money}, :test => true) end @@ -89,7 +73,7 @@ def capture(money, reference, options = {}) when /1$/ raise Error, CAPTURE_ERROR_MESSAGE when /2$/ - Response.new(false, FAILURE_MESSAGE, {:paid_amount => money, :error => FAILURE_MESSAGE }, :test => true) + Response.new(false, FAILURE_MESSAGE, {:paid_amount => money, :error => FAILURE_MESSAGE }, :test => true, :error_code => STANDARD_ERROR_CODE[:processing_error]) else Response.new(true, SUCCESS_MESSAGE, {:paid_amount => money}, :test => true) end @@ -100,20 +84,20 @@ def void(reference, options = {}) when /1$/ raise Error, VOID_ERROR_MESSAGE when /2$/ - Response.new(false, FAILURE_MESSAGE, {:authorization => reference, :error => FAILURE_MESSAGE }, :test => true) + Response.new(false, FAILURE_MESSAGE, {:authorization => reference, :error => FAILURE_MESSAGE }, :test => true, :error_code => STANDARD_ERROR_CODE[:processing_error]) else Response.new(true, SUCCESS_MESSAGE, {:authorization => reference}, :test => true) end end - def store(credit_card_or_reference, options = {}) - case normalize(credit_card_or_reference) + def store(paysource, options = {}) + case normalize(paysource) when /1$/ Response.new(true, SUCCESS_MESSAGE, {:billingid => '1'}, :test => true, :authorization => AUTHORIZATION) when /2$/ - Response.new(false, FAILURE_MESSAGE, {:billingid => nil, :error => FAILURE_MESSAGE }, :test => true) + Response.new(false, FAILURE_MESSAGE, {:billingid => nil, :error => FAILURE_MESSAGE }, :test => true, :error_code => STANDARD_ERROR_CODE[:processing_error]) else - raise Error, ERROR_MESSAGE + raise Error, error_message(paysource) end end @@ -122,7 +106,7 @@ def unstore(reference, options = {}) when /1$/ Response.new(true, SUCCESS_MESSAGE, {}, :test => true) when /2$/ - Response.new(false, FAILURE_MESSAGE, {:error => FAILURE_MESSAGE },:test => true) + Response.new(false, FAILURE_MESSAGE, {:error => FAILURE_MESSAGE }, :test => true, :error_code => STANDARD_ERROR_CODE[:processing_error]) else raise Error, UNSTORE_ERROR_MESSAGE end @@ -130,11 +114,71 @@ def unstore(reference, options = {}) private - def normalize(credit_card_or_reference) - if credit_card_or_reference.respond_to?(:number) - credit_card_or_reference.number + def authorize_emv(money, paysource, options = {}) + money = amount(money) + case money + when /00$/ + Response.new(true, SUCCESS_MESSAGE, {:authorized_amount => money}, :test => true, :authorization => AUTHORIZATION, :emv_authorization => AUTHORIZATION_EMV_SUCCESS) + when /05$/ + Response.new(false, FAILURE_MESSAGE, {:authorized_amount => money, :error => FAILURE_MESSAGE }, :test => true, :error_code => STANDARD_ERROR_CODE[:processing_error], :emv_authorization => AUTHORIZATION_EMV_DECLINE) + else + raise Error, error_message(paysource) + end + end + + def authorize_swipe(money, paysource, options = {}) + money = amount(money) + case normalize(paysource) + when /1$/, AUTHORIZATION + Response.new(true, SUCCESS_MESSAGE, {:authorized_amount => money}, :test => true, :authorization => AUTHORIZATION) + when /2$/ + Response.new(false, FAILURE_MESSAGE, {:authorized_amount => money, :error => FAILURE_MESSAGE }, :test => true, :error_code => STANDARD_ERROR_CODE[:processing_error]) + else + raise Error, error_message(paysource) + end + end + + def purchase_emv(money, paysource, options = {}) + money = amount(money) + case money + when /00$/ + Response.new(true, SUCCESS_MESSAGE, {:paid_amount => money}, :test => true, :authorization => AUTHORIZATION, :emv_authorization => AUTHORIZATION_EMV_SUCCESS) + when /05$/ + Response.new(false, FAILURE_MESSAGE, {:paid_amount => money, :error => FAILURE_MESSAGE }, :test => true, :error_code => STANDARD_ERROR_CODE[:processing_error], :emv_authorization => AUTHORIZATION_EMV_DECLINE) else - credit_card_or_reference.to_s + raise Error, error_message(paysource) + end + end + + def purchase_swipe(money, paysource, options = {}) + money = amount(money) + case normalize(paysource) + when /1$/, AUTHORIZATION + Response.new(true, SUCCESS_MESSAGE, {:paid_amount => money}, :test => true, :authorization => AUTHORIZATION) + when /2$/ + Response.new(false, FAILURE_MESSAGE, {:paid_amount => money, :error => FAILURE_MESSAGE }, :test => true, :error_code => STANDARD_ERROR_CODE[:processing_error]) + else + raise Error, error_message(paysource) + end + end + + def normalize(paysource) + if paysource.respond_to?(:account_number) && (paysource.try(:number).blank? || paysource.number.blank?) + paysource.account_number + elsif paysource.respond_to?(:number) + paysource.number + else + paysource.to_s + end + end + + def error_message(paysource) + if paysource.respond_to?(:emv?) && paysource.emv? + AMOUNT_ERROR_MESSAGE + elsif paysource.respond_to?(:account_number) + CHECK_ERROR_MESSAGE + elsif paysource.respond_to?(:number) + NUMBER_ERROR_MESSAGE end end end diff --git a/lib/active_merchant/billing/gateways/borgun.rb b/lib/active_merchant/billing/gateways/borgun.rb new file mode 100644 index 00000000000..b949144fcfb --- /dev/null +++ b/lib/active_merchant/billing/gateways/borgun.rb @@ -0,0 +1,220 @@ +require 'nokogiri' + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class BorgunGateway < Gateway + self.display_name = 'Borgun' + self.homepage_url = 'http://www.borgun.com' + + self.test_url = 'https://gatewaytest.borgun.is/ws/Heimir.pub.ws:Authorization' + self.live_url = 'https://gateway01.borgun.is/ws/Heimir.pub.ws:Authorization' + + self.supported_countries = ['IS', 'GB', 'HU', 'CZ', 'DE', 'DK', 'SE' ] + self.default_currency = 'ISK' + self.money_format = :cents + self.supported_cardtypes = [:visa, :master, :american_express, :diners_club, :discover, :jcb] + + self.homepage_url = 'https://www.borgun.is/' + + def initialize(options={}) + requires!(options, :processor, :merchant_id, :username, :password) + super + end + + def purchase(money, payment, options={}) + post = {} + post[:TransType] = '1' + add_invoice(post, money, options) + add_payment_method(post, payment) + commit('sale', post) + end + + def authorize(money, payment, options={}) + post = {} + post[:TransType] = '5' + add_invoice(post, money, options) + add_payment_method(post, payment) + commit('authonly', post) + end + + def capture(money, authorization, options={}) + post = {} + post[:TransType] = '1' + add_invoice(post, money, options) + add_reference(post, authorization) + commit('capture', post) + end + + def refund(money, authorization, options={}) + post = {} + post[:TransType] = '3' + add_invoice(post, money, options) + add_reference(post, authorization) + commit('refund', post) + end + + def void(authorization, options={}) + post = {} + # TransType, TrAmount, and currency must match original values from auth or purchase. + _, _, _, _, _, transtype, tramount, currency = split_authorization(authorization) + post[:TransType] = transtype + options[:currency] = options[:currency] || CURRENCY_CODES.key(currency) + add_invoice(post, tramount.to_i, options) + add_reference(post, authorization) + commit('void', post) + end + + def supports_scrubbing + true + end + + def scrub(transcript) + transcript.gsub(%r((<PAN>)[^&]*(</PAN>))i, '\1[FILTERED]\2'). + gsub(%r((<CVC2>)[^&]*(</CVC2>))i, '\1[FILTERED]\2'). + gsub(%r(((?:\r\n)?Authorization: Basic )[^\r\n]+(\r\n)?), '\1[FILTERED]\2') + end + + private + + CURRENCY_CODES = Hash.new { |h, k| raise ArgumentError.new("Unsupported currency for HDFC: #{k}") } + CURRENCY_CODES['ISK'] = '352' + CURRENCY_CODES['EUR'] = '978' + CURRENCY_CODES['USD'] = '840' + + def add_invoice(post, money, options) + post[:TrAmount] = amount(money) + post[:TrCurrency] = CURRENCY_CODES[options[:currency] || currency(money)] + post[:TerminalID] = options[:terminal_id] || '1' + end + + def add_payment_method(post, payment_method) + post[:PAN] = payment_method.number + post[:ExpDate] = format(payment_method.year, :two_digits) + format(payment_method.month, :two_digits) + post[:CVC2] = payment_method.verification_value + post[:DateAndTime] = Time.now.strftime('%y%m%d%H%M%S') + post[:RRN] = 'AMRCNT' + six_random_digits + end + + def add_reference(post, authorization) + dateandtime, _batch, transaction, rrn, authcode, _, _, _ = split_authorization(authorization) + post[:DateAndTime] = dateandtime + post[:Transaction] = transaction + post[:RRN] = rrn + post[:AuthCode] = authcode + end + + def parse(xml) + response = {} + + doc = Nokogiri::XML(CGI.unescapeHTML(xml)) + body = doc.xpath('//getAuthorizationReply') + body = doc.xpath('//cancelAuthorizationReply') if body.length == 0 + body.children.each do |node| + if node.text? + next + elsif node.elements.size == 0 + response[node.name.downcase.to_sym] = node.text + else + node.elements.each do |childnode| + name = "#{node.name.downcase}_#{childnode.name.downcase}" + response[name.to_sym] = childnode.text + end + end + end + + response + end + + def commit(action, post) + post[:Version] = '1000' + post[:Processor] = @options[:processor] + post[:MerchantID] = @options[:merchant_id] + + request = build_request(action, post) + raw = ssl_post(url(action), request, headers) + pairs = parse(raw) + success = success_from(pairs) + + Response.new( + success, + message_from(success, pairs), + pairs, + authorization: authorization_from(pairs), + test: test? + ) + end + + def success_from(response) + (response[:actioncode] == '000') + end + + def message_from(succeeded, response) + if succeeded + 'Succeeded' + else + response[:message] || "Error with ActionCode=#{response[:actioncode]}" + end + end + + def authorization_from(response) + [ + response[:dateandtime], + response[:batch], + response[:transaction], + response[:rrn], + response[:authcode], + response[:transtype], + response[:tramount], + response[:trcurrency] + ].join('|') + end + + def split_authorization(authorization) + dateandtime, batch, transaction, rrn, authcode, transtype, tramount, currency = authorization.split('|') + [dateandtime, batch, transaction, rrn, authcode, transtype, tramount, currency] + end + + def headers + { + 'Authorization' => 'Basic ' + Base64.strict_encode64(@options[:username].to_s + ':' + @options[:password].to_s), + } + end + + def build_request(action, post) + mode = action == 'void' ? 'cancel' : 'get' + xml = Builder::XmlMarkup.new :indent => 18 + xml.instruct!(:xml, :version => '1.0', :encoding => 'utf-8') + xml.tag!("#{mode}Authorization") do + post.each do |field, value| + xml.tag!(field, value) + end + end + inner = CGI.escapeHTML(xml.target!) + envelope(mode).sub(/{{ :body }}/, inner) + end + + def envelope(mode) + <<-EOS + + + + + <#{mode}AuthReqXml> + {{ :body }} + + + + + EOS + end + + def url(action) + (test? ? test_url : live_url) + end + + def six_random_digits + (0...6).map { rand(48..57).chr }.join + end + end + end +end diff --git a/lib/active_merchant/billing/gateways/bpoint.rb b/lib/active_merchant/billing/gateways/bpoint.rb new file mode 100644 index 00000000000..320e024a03b --- /dev/null +++ b/lib/active_merchant/billing/gateways/bpoint.rb @@ -0,0 +1,277 @@ +require 'nokogiri' + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class BpointGateway < Gateway + self.live_url = 'https://www.bpoint.com.au/evolve/service_1_4_4.asmx' + + self.supported_countries = ['AU'] + self.default_currency = 'AUD' + self.supported_cardtypes = [:visa, :master, :american_express, :diners_club] + + self.homepage_url = 'https://www.bpoint.com.au/bpoint' + self.display_name = 'BPoint' + + def initialize(options={}) + requires!(options, :username, :password, :merchant_number) + super + end + + def store(credit_card, options={}) + options[:crn1] ||= 'DEFAULT' + request_body = soap_request do |xml| + add_token(xml, credit_card, options) + end + commit(request_body) + end + + def purchase(amount, credit_card, options={}) + request_body = soap_request do |xml| + process_payment(xml) do |payment_xml| + add_purchase(payment_xml, amount, credit_card, options) + end + end + commit(request_body) + end + + def authorize(amount, credit_card, options={}) + request_body = soap_request do |xml| + process_payment(xml) do |payment_xml| + add_authorize(payment_xml, amount, credit_card, options) + end + end + commit(request_body) + end + + def capture(amount, authorization, options={}) + request_body = soap_request do |xml| + process_payment(xml) do |payment_xml| + add_capture(payment_xml, amount, authorization, options) + end + end + commit(request_body) + end + + def refund(amount, authorization, options={}) + request_body = soap_request do |xml| + process_payment(xml) do |payment_xml| + add_refund(payment_xml, amount, authorization, options) + end + end + commit(request_body) + end + + def void(amount, authorization, options={}) + request_body = soap_request do |xml| + process_payment(xml) do |payment_xml| + add_void(payment_xml, amount, authorization, options) + end + end + commit(request_body) + end + + def verify(credit_card, options={}) + MultiResponse.run(:use_first_response) do |r| + r.process { authorize(100, credit_card, options) } + r.process(:ignore_result) { void(100, r.authorization, options) } + end + end + + def supports_scrubbing? + true + end + + def scrub(transcript) + transcript. + gsub(%r(().+()), '\1[FILTERED]\2'). + gsub(%r(().+()), '\1[FILTERED]\2'). + gsub(%r(().+()), '\1[FILTERED]\2') + end + + private + + def soap_request + Nokogiri::XML::Builder.new(:encoding => 'utf-8') do |xml| + xml.send('soap12:Envelope', soap_envelope_attributes) { + xml.send('soap12:Body') { + yield(xml) if block_given? + } + } + end.to_xml + end + + def soap_envelope_attributes + { 'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance', + 'xmlns:xsd' => 'http://www.w3.org/2001/XMLSchema', + 'xmlns:soap12' => 'http://www.w3.org/2003/05/soap-envelope' } + end + + def process_payment(xml) + xml.send('ProcessPayment', { 'xmlns' => 'urn:Eve_1_4_4' }) { + credentials_xml(xml) + xml.send('txnReq') { + yield(xml) if block_given? + } + } + end + + def add_token(xml, credit_card, options) + xml.send('AddToken', { 'xmlns' => 'urn:Eve_1_4_4' }) { + credentials_xml(xml) + xml.send('tokenRequest') { + xml.send('CRN1', options[:crn1]) + xml.send('CRN2', '') + xml.send('CRN3', '') + xml.send('CardNumber', credit_card.number) + xml.send('ExpiryDate', expdate(credit_card)) + } + } + end + + def credentials_xml(xml) + xml.send('username', @options[:username]) + xml.send('password', @options[:password]) + xml.send('merchantNumber', @options[:merchant_number]) + end + + def add_purchase(xml, amount, credit_card, options) + payment_xml(xml, 'PAYMENT', amount, options) + credit_card_xml(xml, credit_card) + end + + def add_authorize(xml, amount, credit_card, options) + payment_xml(xml, 'PREAUTH', amount, options) + credit_card_xml(xml, credit_card) + end + + def add_capture(xml, amount, transaction_number, options) + payment_xml(xml, 'CAPTURE', amount, options) + transaction_number_xml(xml, transaction_number) + end + + def add_refund(xml, amount, transaction_number, options) + payment_xml(xml, 'REFUND', amount, options) + transaction_number_xml(xml, transaction_number) + end + + def add_void(xml, amount, transaction_number, options) + payment_xml(xml, 'REVERSAL', amount, options) + transaction_number_xml(xml, transaction_number) + end + + def payment_xml(xml, payment_type, amount, options) + xml.send('PaymentType', payment_type) + xml.send('TxnType', 'WEB_SHOP') + xml.send('BillerCode', options.fetch(:biller_code, '')) + xml.send('MerchantReference', options[:order_id]) if options[:order_id] + xml.send('CRN1', options[:crn1]) if options[:crn1] + xml.send('CRN2', options[:crn2]) if options[:crn2] + xml.send('CRN3', options[:crn3]) if options[:crn3] + xml.send('Amount', amount) + end + + def credit_card_xml(xml, credit_card) + xml.send('CardNumber', credit_card.number) + xml.send('ExpiryDate', expdate(credit_card)) + xml.send('CVC', credit_card.verification_value) + end + + def transaction_number_xml(xml, transaction_number) + xml.send('OriginalTransactionNumber', transaction_number) + end + + def commit(request_body) + parse(ssl_post(live_url, request_body, request_headers)) + end + + def request_headers + { 'Content-Type' => 'application/soap+xml; charset=utf-8' } + end + + def parse(body) + response_for(Nokogiri::XML(body).remove_namespaces!) + end + + def response_for(xml_doc) + if xml_doc.xpath('//ProcessPaymentResponse').any? + ProcessPaymentResponse.new(xml_doc, self).to_response + elsif xml_doc.xpath('//AddTokenResponse').any? + AddTokenResponse.new(xml_doc, self).to_response + end + end + + class BPointResponse + attr_reader :xml_doc, :gateway, :params + + def initialize(xml_doc, gateway) + @xml_doc = xml_doc + @gateway = gateway + @params = init_params + end + + def to_response + Response.new(success?, message, params, options) + end + + private + + def init_params + {}.tap do |h| + xml_doc.xpath(response_node).each do |node| + if node.elements.empty? + h[node.name.to_sym] = node.text + else + node.elements.each do |childnode| + name = "#{node.name}_#{childnode.name}" + h[name.to_sym] = childnode.text + end + end + end + end + end + + def response_node + "//#{self.class.name.split('::').last}/*" + end + + def options + { authorization: params[authorization_key], test: gateway.test? } + end + end + + class ProcessPaymentResponse < BPointResponse + + private + + def authorization_key + :ProcessPaymentResult_TransactionNumber + end + + def success? + params[:ProcessPaymentResult_ResponseCode] == '0' + end + + def message + params[:ProcessPaymentResult_AuthorisationResult] || params[:response_ResponseMessage] + end + end + + class AddTokenResponse < BPointResponse + + private + + def authorization_key + :AddTokenResult_Token + end + + def success? + params[:response_ResponseCode] == 'SUCCESS' + end + + def message + params[:response_ResponseCode].capitalize + end + end + end + end +end diff --git a/lib/active_merchant/billing/gateways/braintree.rb b/lib/active_merchant/billing/gateways/braintree.rb index 6167d6be6de..bfe682c31db 100644 --- a/lib/active_merchant/billing/gateways/braintree.rb +++ b/lib/active_merchant/billing/gateways/braintree.rb @@ -1,10 +1,10 @@ -require File.dirname(__FILE__) + '/braintree/braintree_common' +require 'active_merchant/billing/gateways/braintree/braintree_common' module ActiveMerchant #:nodoc: module Billing #:nodoc: class BraintreeGateway < Gateway include BraintreeCommon - + self.abstract_class = true def self.new(options={}) diff --git a/lib/active_merchant/billing/gateways/braintree/braintree_common.rb b/lib/active_merchant/billing/gateways/braintree/braintree_common.rb index 87e980f71a2..7343584f7aa 100644 --- a/lib/active_merchant/billing/gateways/braintree/braintree_common.rb +++ b/lib/active_merchant/billing/gateways/braintree/braintree_common.rb @@ -1,9 +1,22 @@ module BraintreeCommon def self.included(base) - base.supported_countries = %w(US CA AU AD AT BE BG CY CZ DK EE FI FR GI DE GR HU IS IM IE IT LV LI LT LU MT MC NL NO PL PT RO SM SK SI ES SE CH TR GB) - base.supported_cardtypes = [:visa, :master, :american_express, :discover, :jcb, :diners_club] + base.supported_countries = %w(US CA AD AT BE BG HR CY CZ DK EE FI FR GI DE GR GG HU IS IM IE IT JE LV LI LT LU MT MC NL NO PL PT RO SM SK SI ES SE CH TR GB SG HK MY AU NZ) + base.supported_cardtypes = [:visa, :master, :american_express, :discover, :jcb, :diners_club, :maestro] base.homepage_url = 'http://www.braintreepaymentsolutions.com' base.display_name = 'Braintree' base.default_currency = 'USD' + base.currencies_without_fractions = %w(BIF CLP DJF GNF JPY KMF KRW LAK PYG RWF UGX VND VUV XAF XOF XPF) + end + + def supports_scrubbing + true + end + + def scrub(transcript) + return '' if transcript.blank? + transcript. + gsub(%r((Authorization: Basic )\w+), '\1[FILTERED]'). + gsub(%r((&?ccnumber=)\d*(&?)), '\1[FILTERED]\2'). + gsub(%r((&?cvv=)\d*(&?)), '\1[FILTERED]\2') end end diff --git a/lib/active_merchant/billing/gateways/braintree_blue.rb b/lib/active_merchant/billing/gateways/braintree_blue.rb index a400a02dcbd..463c7ac2b5f 100644 --- a/lib/active_merchant/billing/gateways/braintree_blue.rb +++ b/lib/active_merchant/billing/gateways/braintree_blue.rb @@ -1,12 +1,14 @@ -require File.dirname(__FILE__) + '/braintree/braintree_common' +require 'active_merchant/billing/gateways/braintree/braintree_common' begin - require "braintree" + require 'braintree' rescue LoadError - raise "Could not load the braintree gem. Use `gem install braintree` to install it." + raise 'Could not load the braintree gem. Use `gem install braintree` to install it.' end -raise "Need braintree gem 2.x.y. Run `gem install braintree --version '~>2.0'` to get the correct version." unless Braintree::Version::Major == 2 +unless Braintree::Version::Major == 2 && Braintree::Version::Minor >= 78 + raise "Need braintree gem >= 2.78.0. Run `gem install braintree --version '~>2.78'` to get the correct version." +end module ActiveMerchant #:nodoc: module Billing #:nodoc: @@ -23,56 +25,52 @@ module Billing #:nodoc: # Setting an ActiveMerchant +wiredump_device+ will automatically # configure the Braintree logger (via the Braintree gem's # configuration) when the BraintreeBlueGateway is instantiated. - # Additionally, the log level will be set to +DEBUG+. Therefore, - # all you have to do is set the +wiredump_device+ and you'll - # get your debug output from your HTTP interactions with the - # remote gateway. (Don't enable this in production.) - # - # For example: - # - # ActiveMerchant::Billing::BraintreeBlueGateway.wiredump_device = Logger.new(STDOUT) - # # => # - # - # Braintree::Configuration.logger - # # => (some other logger, created by default by the gem) - # - # Braintree::Configuration.logger.level - # # => 1 (INFO) - # - # ActiveMerchant::Billing::BraintreeBlueGateway.new(:merchant_id => 'x', :public_key => 'x', :private_key => 'x') + # Additionally, the log level will be set to +DEBUG+. Therefore, + # all you have to do is set the +wiredump_device+ and you'll get + # your debug output from your HTTP interactions with the remote + # gateway. (Don't enable this in production.) The ActiveMerchant + # implementation doesn't mess with the Braintree::Configuration + # globals at all, so there won't be any side effects outside + # Active Merchant. # - # Braintree::Configuration.logger - # # => # + # If no +wiredump_device+ is set, the logger in + # +Braintree::Configuration.logger+ will be cloned and the log + # level set to +WARN+. # - # Braintree::Configuration.logger.level - # # => 0 (DEBUG) - # - # Alternatively, you can avoid setting the +wiredump_device+ - # and set +Braintree::Configuration.logger+ and/or - # +Braintree::Configuration.logger.level+ directly. class BraintreeBlueGateway < Gateway include BraintreeCommon + include Empty self.display_name = 'Braintree (Blue Platform)' + ERROR_CODES = { + cannot_refund_if_unsettled: 91506 + } + def initialize(options = {}) requires!(options, :merchant_id, :public_key, :private_key) @merchant_account_id = options[:merchant_account_id] super - Braintree::Configuration.merchant_id = options[:merchant_id] - Braintree::Configuration.public_key = options[:public_key] - Braintree::Configuration.private_key = options[:private_key] - Braintree::Configuration.environment = (options[:environment] || (test? ? :sandbox : :production)).to_sym - Braintree::Configuration.custom_user_agent = "ActiveMerchant #{ActiveMerchant::VERSION}" - - if wiredump_device - Braintree::Configuration.logger = ((Logger === wiredump_device) ? wiredump_device : Logger.new(wiredump_device)) - Braintree::Configuration.logger.level = Logger::DEBUG + if wiredump_device.present? + logger = (Logger === wiredump_device ? wiredump_device : Logger.new(wiredump_device)) + logger.level = Logger::DEBUG else - Braintree::Configuration.logger.level = Logger::WARN + logger = Braintree::Configuration.logger.clone + logger.level = Logger::WARN end + + @configuration = Braintree::Configuration.new( + :merchant_id => options[:merchant_id], + :public_key => options[:public_key], + :private_key => options[:private_key], + :environment => (options[:environment] || (test? ? :sandbox : :production)).to_sym, + :custom_user_agent => "ActiveMerchant #{ActiveMerchant::VERSION}", + :logger => options[:logger] || logger + ) + + @braintree_gateway = Braintree::Gateway.new(@configuration) end def authorize(money, credit_card_or_vault_id, options = {}) @@ -81,8 +79,8 @@ def authorize(money, credit_card_or_vault_id, options = {}) def capture(money, authorization, options = {}) commit do - result = Braintree::Transaction.submit_for_settlement(authorization, amount(money).to_s) - Response.new(result.success?, message_from_result(result)) + result = @braintree_gateway.transaction.submit_for_settlement(authorization, localized_amount(money, options[:currency] || default_currency).to_s) + response_from_result(result) end end @@ -97,76 +95,76 @@ def credit(money, credit_card_or_vault_id, options = {}) def refund(*args) # legacy signature: #refund(transaction_id, options = {}) # new signature: #refund(money, transaction_id, options = {}) - money, transaction_id, _ = extract_refund_args(args) - money = amount(money).to_s if money + money, transaction_id, options = extract_refund_args(args) + money = localized_amount(money, options[:currency] || default_currency).to_s if money commit do - result = Braintree::Transaction.refund(transaction_id, money) - Response.new(result.success?, message_from_result(result), - {:braintree_transaction => (transaction_hash(result.transaction) if result.success?)}, - {:authorization => (result.transaction.id if result.success?)} - ) + response = response_from_result(@braintree_gateway.transaction.refund(transaction_id, money)) + return response if response.success? + return response unless options[:force_full_refund_if_unsettled] + + void(transaction_id) if response.message =~ /#{ERROR_CODES[:cannot_refund_if_unsettled]}/ end end def void(authorization, options = {}) commit do - result = Braintree::Transaction.void(authorization) - Response.new(result.success?, message_from_result(result), - {:braintree_transaction => (transaction_hash(result.transaction) if result.success?)}, - {:authorization => (result.transaction.id if result.success?)} - ) + response_from_result(@braintree_gateway.transaction.void(authorization)) + end + end + + def verify(credit_card, options = {}) + MultiResponse.run(:use_first_response) do |r| + r.process { authorize(100, credit_card, options) } + r.process(:ignore_result) { void(r.authorization, options) } end end def store(creditcard, options = {}) - commit do - parameters = { - :first_name => creditcard.first_name, - :last_name => creditcard.last_name, - :email => options[:email], - :credit_card => { - :number => creditcard.number, - :cvv => creditcard.verification_value, - :expiration_month => creditcard.month.to_s.rjust(2, "0"), - :expiration_year => creditcard.year.to_s - } - } - result = Braintree::Customer.create(merge_credit_card_options(parameters, options)) - Response.new(result.success?, message_from_result(result), - { - :braintree_customer => (customer_hash(result.customer) if result.success?), - :customer_vault_id => (result.customer.id if result.success?) - }, - :authorization => (result.customer.id if result.success?) - ) + if options[:customer].present? + MultiResponse.new.tap do |r| + customer_exists_response = nil + r.process { customer_exists_response = check_customer_exists(options[:customer]) } + r.process do + if customer_exists_response.params['exists'] + add_credit_card_to_customer(creditcard, options) + else + add_customer_with_credit_card(creditcard, options) + end + end + end + else + add_customer_with_credit_card(creditcard, options) end end def update(vault_id, creditcard, options = {}) braintree_credit_card = nil commit do - braintree_credit_card = Braintree::Customer.find(vault_id).credit_cards.detect { |cc| cc.default? } + braintree_credit_card = @braintree_gateway.customer.find(vault_id).credit_cards.detect(&:default?) return Response.new(false, 'Braintree::NotFoundError') if braintree_credit_card.nil? - options.merge!(:update_existing_token => braintree_credit_card.token) + options[:update_existing_token] = braintree_credit_card.token credit_card_params = merge_credit_card_options({ :credit_card => { + :cardholder_name => creditcard.name, :number => creditcard.number, :cvv => creditcard.verification_value, - :expiration_month => creditcard.month.to_s.rjust(2, "0"), + :expiration_month => creditcard.month.to_s.rjust(2, '0'), :expiration_year => creditcard.year.to_s } }, options)[:credit_card] - result = Braintree::Customer.update(vault_id, + result = @braintree_gateway.customer.update(vault_id, :first_name => creditcard.first_name, :last_name => creditcard.last_name, - :email => options[:email], + :email => scrub_email(options[:email]), + :phone => options[:phone] || (options[:billing_address][:phone] if options[:billing_address] && + options[:billing_address][:phone]), :credit_card => credit_card_params ) Response.new(result.success?, message_from_result(result), - :braintree_customer => (customer_hash(Braintree::Customer.find(vault_id)) if result.success?), + :braintree_customer => (customer_hash(@braintree_gateway.customer.find(vault_id), :include_credit_cards) if result.success?), :customer_vault_id => (result.customer.id if result.success?) ) end @@ -174,45 +172,167 @@ def update(vault_id, creditcard, options = {}) def unstore(customer_vault_id, options = {}) commit do - Braintree::Customer.delete(customer_vault_id) - Response.new(true, "OK") + if(!customer_vault_id && options[:credit_card_token]) + @braintree_gateway.credit_card.delete(options[:credit_card_token]) + else + @braintree_gateway.customer.delete(customer_vault_id) + end + Response.new(true, 'OK') end end alias_method :delete, :unstore + def supports_network_tokenization? + true + end + + def verify_credentials + begin + @braintree_gateway.transaction.find('non_existent_token') + rescue Braintree::AuthenticationError + return false + rescue Braintree::NotFoundError + return true + end + + true + end + private + def check_customer_exists(customer_vault_id) + commit do + begin + @braintree_gateway.customer.find(customer_vault_id) + ActiveMerchant::Billing::Response.new(true, 'Customer found', {exists: true}, authorization: customer_vault_id) + rescue Braintree::NotFoundError + ActiveMerchant::Billing::Response.new(true, 'Customer not found', {exists: false}) + end + end + end + + def add_customer_with_credit_card(creditcard, options) + commit do + if options[:payment_method_nonce] + credit_card_params = { payment_method_nonce: options[:payment_method_nonce] } + else + credit_card_params = { + :credit_card => { + :cardholder_name => creditcard.name, + :number => creditcard.number, + :cvv => creditcard.verification_value, + :expiration_month => creditcard.month.to_s.rjust(2, '0'), + :expiration_year => creditcard.year.to_s, + :token => options[:credit_card_token] + } + } + end + parameters = { + :first_name => creditcard.first_name, + :last_name => creditcard.last_name, + :email => scrub_email(options[:email]), + :phone => options[:phone] || (options[:billing_address][:phone] if options[:billing_address] && + options[:billing_address][:phone]), + :id => options[:customer], + :device_data => options[:device_data], + }.merge credit_card_params + result = @braintree_gateway.customer.create(merge_credit_card_options(parameters, options)) + Response.new(result.success?, message_from_result(result), + { + :braintree_customer => (customer_hash(result.customer, :include_credit_cards) if result.success?), + :customer_vault_id => (result.customer.id if result.success?), + :credit_card_token => (result.customer.credit_cards[0].token if result.success?) + }, + :authorization => (result.customer.id if result.success?) + ) + end + end + + def add_credit_card_to_customer(credit_card, options) + commit do + parameters = { + customer_id: options[:customer], + token: options[:credit_card_token], + cardholder_name: credit_card.name, + number: credit_card.number, + cvv: credit_card.verification_value, + expiration_month: credit_card.month.to_s.rjust(2, '0'), + expiration_year: credit_card.year.to_s, + device_data: options[:device_data], + } + if options[:billing_address] + address = map_address(options[:billing_address]) + parameters[:credit_card][:billing_address] = address unless address.all? { |_k, v| empty?(v) } + end + + result = @braintree_gateway.credit_card.create(parameters) + ActiveMerchant::Billing::Response.new( + result.success?, + message_from_result(result), + { + customer_vault_id: (result.credit_card.customer_id if result.success?), + credit_card_token: (result.credit_card.token if result.success?) + }, + authorization: (result.credit_card.customer_id if result.success?) + ) + end + end + + def scrub_email(email) + return nil unless email.present? + return nil if + email !~ /^.+@[^\.]+(\.[^\.]+)+[a-z]$/i || + email =~ /\.(con|met)$/i + + email + end + + def scrub_zip(zip) + return nil unless zip.present? + return nil if( + zip.gsub(/[^a-z0-9]/i, '').length > 9 || + zip =~ /[^a-z0-9\- ]/i + ) + zip + end + def merge_credit_card_options(parameters, options) valid_options = {} options.each do |key, value| valid_options[key] = value if [:update_existing_token, :verify_card, :verification_merchant_account_id].include?(key) end + if valid_options.include?(:verify_card) && @merchant_account_id + valid_options[:verification_merchant_account_id] ||= @merchant_account_id + end + parameters[:credit_card] ||= {} - parameters[:credit_card].merge!(:options => valid_options) - parameters[:credit_card][:billing_address] = map_address(options[:billing_address]) if options[:billing_address] + parameters[:credit_card][:options] = valid_options + if options[:billing_address] + address = map_address(options[:billing_address]) + parameters[:credit_card][:billing_address] = address unless address.all? { |_k, v| empty?(v) } + end parameters end def map_address(address) - return {} if address.nil? mapped = { :street_address => address[:address1], :extended_address => address[:address2], :company => address[:company], :locality => address[:city], :region => address[:state], - :postal_code => address[:zip], + :postal_code => scrub_zip(address[:zip]), } - if(address[:country] || address[:country_code_alpha2]) - mapped[:country_code_alpha2] = (address[:country] || address[:country_code_alpha2]) - elsif address[:country_name] - mapped[:country_name] = address[:country_name] - elsif address[:country_code_alpha3] - mapped[:country_code_alpha3] = address[:country_code_alpha3] - elsif address[:country_code_numeric] - mapped[:country_code_numeric] = address[:country_code_numeric] + + mapped[:country_code_alpha2] = (address[:country] || address[:country_code_alpha2]) if address[:country] || address[:country_code_alpha2] + mapped[:country_name] = address[:country_name] if address[:country_name] + mapped[:country_code_alpha3] = address[:country_code_alpha3] if address[:country_code_alpha3] + unless address[:country].blank? + mapped[:country_code_alpha3] ||= Country.find(address[:country]).code(:alpha3).value end + mapped[:country_code_numeric] = address[:country_code_numeric] if address[:country_code_numeric] + mapped end @@ -224,41 +344,112 @@ def commit(&block) def message_from_result(result) if result.success? - "OK" - elsif result.errors.size == 0 && result.credit_card_verification + 'OK' + elsif result.errors.any? + result.errors.map { |e| "#{e.message} (#{e.code})" }.join(' ') + elsif result.credit_card_verification "Processor declined: #{result.credit_card_verification.processor_response_text} (#{result.credit_card_verification.processor_response_code})" else - result.errors.map { |e| "#{e.message} (#{e.code})" }.join(" ") + result.message.to_s + end + end + + def response_from_result(result) + response_hash = { braintree_transaction: transaction_hash(result) } + + Response.new( + result.success?, + message_from_result(result), + response_hash, + authorization: result.transaction&.id, + test: test? + ) + end + + def response_params(result) + params = {} + params[:customer_vault_id] = result.transaction.customer_details.id if result.success? + params[:braintree_transaction] = transaction_hash(result) + params + end + + def response_options(result) + options = {} + if result.transaction + options[:authorization] = result.transaction.id + options[:avs_result] = { code: avs_code_from(result.transaction) } + options[:cvv_result] = result.transaction.cvv_response_code + end + options[:test] = test? + options + end + + def avs_code_from(transaction) + transaction.avs_error_response_code || + avs_mapping["street: #{transaction.avs_street_address_response_code}, zip: #{transaction.avs_postal_code_response_code}"] + end + + def avs_mapping + { + 'street: M, zip: M' => 'M', + 'street: M, zip: N' => 'A', + 'street: M, zip: U' => 'B', + 'street: M, zip: I' => 'B', + 'street: M, zip: A' => 'B', + + 'street: N, zip: M' => 'Z', + 'street: N, zip: N' => 'C', + 'street: N, zip: U' => 'C', + 'street: N, zip: I' => 'C', + 'street: N, zip: A' => 'C', + + 'street: U, zip: M' => 'P', + 'street: U, zip: N' => 'N', + 'street: U, zip: U' => 'I', + 'street: U, zip: I' => 'I', + 'street: U, zip: A' => 'I', + + 'street: I, zip: M' => 'P', + 'street: I, zip: N' => 'C', + 'street: I, zip: U' => 'I', + 'street: I, zip: I' => 'I', + 'street: I, zip: A' => 'I', + + 'street: A, zip: M' => 'P', + 'street: A, zip: N' => 'C', + 'street: A, zip: U' => 'I', + 'street: A, zip: I' => 'I', + 'street: A, zip: A' => 'I', + + 'street: B, zip: B' => 'B' + } + end + + def message_from_transaction_result(result) + if result.transaction && result.transaction.status == 'gateway_rejected' + 'Transaction declined - gateway rejected' + elsif result.transaction + "#{result.transaction.processor_response_code} #{result.transaction.processor_response_text}" + else + message_from_result(result) + end + end + + def response_code_from_result(result) + if result.transaction + result.transaction.processor_response_code + elsif result.errors.size == 0 && result.credit_card_verification + result.credit_card_verification.processor_response_code + elsif result.errors.size > 0 + result.errors.first.code end end def create_transaction(transaction_type, money, credit_card_or_vault_id, options) transaction_params = create_transaction_parameters(money, credit_card_or_vault_id, options) - commit do - result = Braintree::Transaction.send(transaction_type, transaction_params) - response_params, response_options, avs_result, cvv_result = {}, {}, {}, {} - if result.success? - response_params[:braintree_transaction] = transaction_hash(result.transaction) - response_params[:customer_vault_id] = result.transaction.customer_details.id - response_options[:authorization] = result.transaction.id - end - if result.transaction - response_options[:avs_result] = { - :code => nil, :message => nil, - :street_match => result.transaction.avs_street_address_response_code, - :postal_match => result.transaction.avs_postal_code_response_code - } - response_options[:cvv_result] = result.transaction.cvv_response_code - if result.transaction.status == "gateway_rejected" - message = "Transaction declined - gateway rejected" - else - message = "#{result.transaction.processor_response_code} #{result.transaction.processor_response_text}" - end - else - message = message_from_result(result) - end - response = Response.new(result.success?, message, response_params, response_options) + result = @braintree_gateway.transaction.send(transaction_type, transaction_params) + response = Response.new(result.success?, message_from_transaction_result(result), response_params(result), response_options(result)) response.cvv_result['message'] = '' response end @@ -277,34 +468,43 @@ def extract_refund_args(args) end end - def customer_hash(customer) - credit_cards = customer.credit_cards.map do |cc| - { - "bin" => cc.bin, - "expiration_date" => cc.expiration_date, - "token" => cc.token, - "last_4" => cc.last_4, - "card_type" => cc.card_type, - "masked_number" => cc.masked_number - } + def customer_hash(customer, include_credit_cards=false) + hash = { + 'email' => customer.email, + 'phone' => customer.phone, + 'first_name' => customer.first_name, + 'last_name' => customer.last_name, + 'id' => customer.id + } + + if include_credit_cards + hash['credit_cards'] = customer.credit_cards.map do |cc| + { + 'bin' => cc.bin, + 'expiration_date' => cc.expiration_date, + 'token' => cc.token, + 'last_4' => cc.last_4, + 'card_type' => cc.card_type, + 'masked_number' => cc.masked_number + } + end end - { - "email" => customer.email, - "first_name" => customer.first_name, - "last_name" => customer.last_name, - "credit_cards" => credit_cards, - "id" => customer.id - } + hash end - def transaction_hash(transaction) + def transaction_hash(result) + unless result.success? + return { 'processor_response_code' => response_code_from_result(result) } + end + + transaction = result.transaction if transaction.vault_customer vault_customer = { } - vault_customer["credit_cards"] = transaction.vault_customer.credit_cards.map do |cc| + vault_customer['credit_cards'] = transaction.vault_customer.credit_cards.map do |cc| { - "bin" => cc.bin + 'bin' => cc.bin } end else @@ -312,90 +512,229 @@ def transaction_hash(transaction) end customer_details = { - "id" => transaction.customer_details.id, - "email" => transaction.customer_details.email + 'id' => transaction.customer_details.id, + 'email' => transaction.customer_details.email, + 'phone' => transaction.customer_details.phone, } billing_details = { - "street_address" => transaction.billing_details.street_address, - "extended_address" => transaction.billing_details.extended_address, - "company" => transaction.billing_details.company, - "locality" => transaction.billing_details.locality, - "region" => transaction.billing_details.region, - "postal_code" => transaction.billing_details.postal_code, - "country_name" => transaction.billing_details.country_name, + 'street_address' => transaction.billing_details.street_address, + 'extended_address' => transaction.billing_details.extended_address, + 'company' => transaction.billing_details.company, + 'locality' => transaction.billing_details.locality, + 'region' => transaction.billing_details.region, + 'postal_code' => transaction.billing_details.postal_code, + 'country_name' => transaction.billing_details.country_name, } shipping_details = { - "street_address" => transaction.shipping_details.street_address, - "extended_address" => transaction.shipping_details.extended_address, - "company" => transaction.shipping_details.company, - "locality" => transaction.shipping_details.locality, - "region" => transaction.shipping_details.region, - "postal_code" => transaction.shipping_details.postal_code, - "country_name" => transaction.shipping_details.country_name, + 'street_address' => transaction.shipping_details.street_address, + 'extended_address' => transaction.shipping_details.extended_address, + 'company' => transaction.shipping_details.company, + 'locality' => transaction.shipping_details.locality, + 'region' => transaction.shipping_details.region, + 'postal_code' => transaction.shipping_details.postal_code, + 'country_name' => transaction.shipping_details.country_name, } credit_card_details = { - "masked_number" => transaction.credit_card_details.masked_number, - "bin" => transaction.credit_card_details.bin, - "last_4" => transaction.credit_card_details.last_4, - "card_type" => transaction.credit_card_details.card_type, - "token" => transaction.credit_card_details.token + 'masked_number' => transaction.credit_card_details.masked_number, + 'bin' => transaction.credit_card_details.bin, + 'last_4' => transaction.credit_card_details.last_4, + 'card_type' => transaction.credit_card_details.card_type, + 'token' => transaction.credit_card_details.token } + if transaction.risk_data + risk_data = { + 'id' => transaction.risk_data.id, + 'decision' => transaction.risk_data.decision, + 'device_data_captured' => transaction.risk_data.device_data_captured, + 'fraud_service_provider' => transaction.risk_data.fraud_service_provider + } + else + risk_data = nil + end + { - "order_id" => transaction.order_id, - "status" => transaction.status, - "credit_card_details" => credit_card_details, - "customer_details" => customer_details, - "billing_details" => billing_details, - "shipping_details" => shipping_details, - "vault_customer" => vault_customer, - "merchant_account_id" => transaction.merchant_account_id + 'order_id' => transaction.order_id, + 'amount' => transaction.amount.to_s, + 'status' => transaction.status, + 'credit_card_details' => credit_card_details, + 'customer_details' => customer_details, + 'billing_details' => billing_details, + 'shipping_details' => shipping_details, + 'vault_customer' => vault_customer, + 'merchant_account_id' => transaction.merchant_account_id, + 'risk_data' => risk_data, + 'network_transaction_id' => transaction.network_transaction_id || nil, + 'processor_response_code' => response_code_from_result(result) } end def create_transaction_parameters(money, credit_card_or_vault_id, options) parameters = { - :amount => amount(money).to_s, + :amount => localized_amount(money, options[:currency] || default_currency).to_s, :order_id => options[:order_id], :customer => { - :id => options[:store] == true ? "" : options[:store], - :email => options[:email] + :id => options[:store] == true ? '' : options[:store], + :email => scrub_email(options[:email]), + :phone => options[:phone] || (options[:billing_address][:phone] if options[:billing_address] && + options[:billing_address][:phone]) }, :options => { :store_in_vault => options[:store] ? true : false, - :submit_for_settlement => options[:submit_for_settlement] + :submit_for_settlement => options[:submit_for_settlement], + :hold_in_escrow => options[:hold_in_escrow], } } + if options[:skip_advanced_fraud_checking] + parameters[:options][:skip_advanced_fraud_checking] = options[:skip_advanced_fraud_checking] + end + + if options[:skip_avs] + parameters[:options][:skip_avs] = options[:skip_avs] + end + + if options[:skip_cvv] + parameters[:options][:skip_cvv] = options[:skip_cvv] + end + + parameters[:custom_fields] = options[:custom_fields] + parameters[:device_data] = options[:device_data] if options[:device_data] + parameters[:service_fee_amount] = options[:service_fee_amount] if options[:service_fee_amount] if merchant_account_id = (options[:merchant_account_id] || @merchant_account_id) parameters[:merchant_account_id] = merchant_account_id end - if options[:recurring] + if options[:transaction_source] + parameters[:transaction_source] = options[:transaction_source] + elsif options[:recurring] parameters[:recurring] = true end + add_payment_method(parameters, credit_card_or_vault_id, options) + add_stored_credential_data(parameters, credit_card_or_vault_id, options) + + parameters[:billing] = map_address(options[:billing_address]) if options[:billing_address] + parameters[:shipping] = map_address(options[:shipping_address]) if options[:shipping_address] + + channel = @options[:channel] || application_id + parameters[:channel] = channel if channel + + if options[:descriptor_name] || options[:descriptor_phone] || options[:descriptor_url] + parameters[:descriptor] = { + name: options[:descriptor_name], + phone: options[:descriptor_phone], + url: options[:descriptor_url] + } + end + + add_3ds_info(parameters, options[:three_d_secure]) + + parameters[:tax_amount] = options[:tax_amount] if options[:tax_amount] + parameters[:tax_exempt] = options[:tax_exempt] if options[:tax_exempt] + parameters[:purchase_order_number] = options[:purchase_order_number] if options[:purchase_order_number] + + parameters[:shipping_amount] = options[:shipping_amount] if options[:shipping_amount] + parameters[:discount_amount] = options[:discount_amount] if options[:discount_amount] + parameters[:ships_from_postal_code] = options[:ships_from_postal_code] if options[:ships_from_postal_code] + + parameters[:line_items] = options[:line_items] if options[:line_items] + + parameters + end + + def add_3ds_info(parameters, three_d_secure_opts) + return if empty?(three_d_secure_opts) + pass_thru = {} + + pass_thru[:three_d_secure_version] = three_d_secure_opts[:version] if three_d_secure_opts[:version] + pass_thru[:eci_flag] = three_d_secure_opts[:eci] if three_d_secure_opts[:eci] + pass_thru[:cavv_algorithm] = three_d_secure_opts[:cavv_algorithm] if three_d_secure_opts[:cavv_algorithm] + pass_thru[:cavv] = three_d_secure_opts[:cavv] if three_d_secure_opts[:cavv] + pass_thru[:directory_response] = three_d_secure_opts[:directory_response_status] if three_d_secure_opts[:directory_response_status] + pass_thru[:authentication_response] = three_d_secure_opts[:authentication_response_status] if three_d_secure_opts[:authentication_response_status] + + parameters[:three_d_secure_pass_thru] = pass_thru.merge(xid_or_ds_trans_id(three_d_secure_opts)) + end + + def xid_or_ds_trans_id(three_d_secure_opts) + if three_d_secure_opts[:version].to_f >= 2 + { ds_transaction_id: three_d_secure_opts[:ds_transaction_id] } + else + { xid: three_d_secure_opts[:xid] } + end + end + + def add_stored_credential_data(parameters, credit_card_or_vault_id, options) + return unless (stored_credential = options[:stored_credential]) + parameters[:external_vault] = {} + if stored_credential[:initial_transaction] + parameters[:external_vault][:status] = 'will_vault' + else + parameters[:external_vault][:status] = 'vaulted' + parameters[:external_vault][:previous_network_transaction_id] = stored_credential[:network_transaction_id] + end + if stored_credential[:initiator] == 'merchant' + if stored_credential[:reason_type] == 'installment' + parameters[:transaction_source] = 'recurring' + else + parameters[:transaction_source] = stored_credential[:reason_type] + end + else + parameters[:transaction_source] = '' + end + end + + def add_payment_method(parameters, credit_card_or_vault_id, options) if credit_card_or_vault_id.is_a?(String) || credit_card_or_vault_id.is_a?(Integer) - parameters[:customer_id] = credit_card_or_vault_id + if options[:payment_method_token] + parameters[:payment_method_token] = credit_card_or_vault_id + options.delete(:billing_address) + elsif options[:payment_method_nonce] + parameters[:payment_method_nonce] = credit_card_or_vault_id + else + parameters[:customer_id] = credit_card_or_vault_id + end else parameters[:customer].merge!( :first_name => credit_card_or_vault_id.first_name, :last_name => credit_card_or_vault_id.last_name ) - parameters[:credit_card] = { - :number => credit_card_or_vault_id.number, - :cvv => credit_card_or_vault_id.verification_value, - :expiration_month => credit_card_or_vault_id.month.to_s.rjust(2, "0"), - :expiration_year => credit_card_or_vault_id.year.to_s - } + if credit_card_or_vault_id.is_a?(NetworkTokenizationCreditCard) + if credit_card_or_vault_id.source == :apple_pay + parameters[:apple_pay_card] = { + :number => credit_card_or_vault_id.number, + :expiration_month => credit_card_or_vault_id.month.to_s.rjust(2, '0'), + :expiration_year => credit_card_or_vault_id.year.to_s, + :cardholder_name => credit_card_or_vault_id.name, + :cryptogram => credit_card_or_vault_id.payment_cryptogram, + :eci_indicator => credit_card_or_vault_id.eci + } + elsif credit_card_or_vault_id.source == :android_pay || credit_card_or_vault_id.source == :google_pay + parameters[:android_pay_card] = { + :number => credit_card_or_vault_id.number, + :cryptogram => credit_card_or_vault_id.payment_cryptogram, + :expiration_month => credit_card_or_vault_id.month.to_s.rjust(2, '0'), + :expiration_year => credit_card_or_vault_id.year.to_s, + :google_transaction_id => credit_card_or_vault_id.transaction_id, + :source_card_type => credit_card_or_vault_id.brand, + :source_card_last_four => credit_card_or_vault_id.last_digits, + :eci_indicator => credit_card_or_vault_id.eci + } + end + else + parameters[:credit_card] = { + :number => credit_card_or_vault_id.number, + :cvv => credit_card_or_vault_id.verification_value, + :expiration_month => credit_card_or_vault_id.month.to_s.rjust(2, '0'), + :expiration_year => credit_card_or_vault_id.year.to_s, + :cardholder_name => credit_card_or_vault_id.name + } + end end - parameters[:billing] = map_address(options[:billing_address]) if options[:billing_address] - parameters[:shipping] = map_address(options[:shipping_address]) if options[:shipping_address] - parameters end end end end - diff --git a/lib/active_merchant/billing/gateways/braintree_orange.rb b/lib/active_merchant/billing/gateways/braintree_orange.rb index 16e24beea42..f56502eb7a0 100644 --- a/lib/active_merchant/billing/gateways/braintree_orange.rb +++ b/lib/active_merchant/billing/gateways/braintree_orange.rb @@ -1,5 +1,5 @@ -require File.dirname(__FILE__) + '/smart_ps.rb' -require File.dirname(__FILE__) + '/braintree/braintree_common' +require 'active_merchant/billing/gateways/smart_ps.rb' +require 'active_merchant/billing/gateways/braintree/braintree_common' module ActiveMerchant #:nodoc: module Billing #:nodoc: @@ -7,7 +7,8 @@ class BraintreeOrangeGateway < SmartPs include BraintreeCommon self.display_name = 'Braintree (Orange Platform)' - + self.supported_countries = ['US'] + self.live_url = self.test_url = 'https://secure.braintreepaymentgateway.com/api/transact.php' def add_processor(post, options) @@ -16,4 +17,3 @@ def add_processor(post, options) end end end - diff --git a/lib/active_merchant/billing/gateways/bridge_pay.rb b/lib/active_merchant/billing/gateways/bridge_pay.rb new file mode 100644 index 00000000000..d8cf6218265 --- /dev/null +++ b/lib/active_merchant/billing/gateways/bridge_pay.rb @@ -0,0 +1,244 @@ +require 'nokogiri' + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class BridgePayGateway < Gateway + self.display_name = 'BridgePay' + self.homepage_url = 'http://www.bridgepaynetwork.com/' + + self.test_url = 'https://gatewaystage.itstgate.com/SmartPayments/transact3.asmx' + self.live_url = 'https://gateway.itstgate.com/SmartPayments/transact3.asmx' + + self.supported_countries = ['CA', 'US'] + self.default_currency = 'USD' + self.supported_cardtypes = [:visa, :master, :american_express, :discover, :diners_club, :jcb] + + def initialize(options={}) + requires!(options, :user_name, :password) + super + end + + def purchase(amount, payment_method, options={}) + post = initialize_required_fields('Sale') + + # Allow the same amount in multiple transactions. + post[:ExtData] = 'T' + add_invoice(post, amount, options) + add_payment_method(post, payment_method) + add_customer_data(post, options) + + commit(post) + end + + def authorize(amount, payment_method, options={}) + post = initialize_required_fields('Auth') + + add_invoice(post, amount, options) + add_payment_method(post, payment_method) + add_customer_data(post, options) + + commit(post) + end + + def capture(amount, authorization, options={}) + post = initialize_required_fields('Force') + + add_invoice(post, amount, options) + add_reference(post, authorization) + add_customer_data(post, options) + + commit(post) + end + + def refund(amount, authorization, options={}) + post = initialize_required_fields('Return') + + add_invoice(post, amount, options) + add_reference(post, authorization) + + commit(post) + end + + def void(authorization, options={}) + post = initialize_required_fields('Void') + + add_reference(post, authorization) + + commit(post) + end + + def verify(creditcard, options = {}) + MultiResponse.run(:use_first_response) do |r| + r.process { authorize(100, creditcard, options) } + r.process(:ignore_result) { void(r.authorization, options) } + end + end + + def store(creditcard, options={}) + post = initialize_required_fields('') + post[:transaction] = 'Create' + post[:CardNumber] = creditcard.number + post[:CustomerPaymentInfoKey] = '' + post[:token] = '' + add_payment_method(post, creditcard) + + commit(post) + end + + def supports_scrubbing? + true + end + + def scrub(transcript) + transcript. + gsub(%r((&?CardNum=)[^&]*)i, '\1[FILTERED]'). + gsub(%r((&?CVNum=)[^&]*)i, '\1[FILTERED]'). + gsub(%r((&?Password=)[^&]*)i, '\1[FILTERED]'). + gsub(%r((&?TransitNum=)[^&]*)i, '\1[FILTERED]'). + gsub(%r((&?AccountNum=)[^&]*)i, '\1[FILTERED]') + end + + private + + def add_payment_method(post, payment_method) + if payment_method.respond_to? :brand + post[:NameOnCard] = payment_method.name if payment_method.name + post[:ExpDate] = expdate(payment_method) + post[:CardNum] = payment_method.number + post[:CVNum] = payment_method.verification_value + elsif payment_method.is_a?(String) + add_token(post, payment_method) + else + post[:CheckNum] = payment_method.number + post[:TransitNum] = payment_method.routing_number + post[:AccountNum] = payment_method.account_number + post[:NameOnCheck] = payment_method.name + post[:ExtData] = "#{payment_method.account_type.capitalize}" if payment_method.account_type + end + end + + def add_token(post, payment_method) + payment_method = payment_method.split('|') + post[:ExtData] = "TRead#{payment_method[1]}#{payment_method[0]}#{payment_method[2]}" + end + + def initialize_required_fields(transaction_type) + post = {} + post[:TransType] = transaction_type + post[:Amount] = '' + post[:PNRef] = '' + post[:InvNum] = '' + post[:CardNum] = '' + post[:ExpDate] = '' + post[:MagData] = '' + post[:NameOnCard] = '' + post[:Zip] = '' + post[:Street] = '' + post[:CVNum] = '' + post[:MagData] = '' + post[:ExtData] = '' + post[:MICR] = '' + post[:DL] = '' + post[:SS] = '' + post[:DOB] = '' + post[:StateCode] = '' + post[:CheckType] = '' + post + end + + def add_customer_data(post, options) + if(billing_address = (options[:billing_address] || options[:address])) + post[:Street] = billing_address[:address1] + post[:Zip] = billing_address[:zip] + end + end + + def add_invoice(post, amount, options) + post[:Amount] = amount(amount) + post[:InvNum] = options[:order_id] + end + + def expdate(creditcard) + "#{format(creditcard.month, :two_digits)}#{format(creditcard.year, :two_digits)}" + end + + def parse(xml) + response = {} + + doc = Nokogiri::XML(xml) + doc.root&.xpath('*')&.each do |node| + if node.elements.size == 0 + response[node.name.downcase.to_sym] = node.text + else + node.elements.each do |childnode| + name = "#{node.name.downcase}_#{childnode.name.downcase}" + response[name.to_sym] = childnode.text + end + end + end + + response + end + + def commit(parameters) + data = post_data(parameters) + raw = parse(ssl_post(url(parameters), data)) + + Response.new( + success_from(raw), + message_from(raw), + raw, + authorization: authorization_from(raw), + test: test? + ) + end + + def url(params) + if params[:transaction] + "#{base_url}/ManageCardVault" + else + action = params[:TransitNum] ? 'ProcessCheck' : 'ProcessCreditCard' + "#{base_url}/#{action}" + end + end + + def base_url + test? ? test_url : live_url + end + + def success_from(response) + response[:result] == '0' + end + + def message_from(response) + response[:respmsg] || response[:message] + end + + def authorization_from(response) + if response[:token] + [response[:token], response[:customerpaymentinfokey], response[:expdate]].join('|') + else + [response[:authcode], response[:pnref]].join('|') + end + end + + def split_authorization(authorization) + authcode, pnref = authorization.split('|') + [authcode, pnref] + end + + def add_reference(post, authorization) + authcode, pnref = split_authorization(authorization) + post[:AuthCode] = authcode + post[:PNRef] = pnref + end + + def post_data(post) + { + :UserName => @options[:user_name], + :Password => @options[:password] + }.merge(post).collect { |k, v| "#{k}=#{CGI.escape(v.to_s)}" }.join('&') + end + end + end +end diff --git a/lib/active_merchant/billing/gateways/cams.rb b/lib/active_merchant/billing/gateways/cams.rb new file mode 100644 index 00000000000..4fd30dd0489 --- /dev/null +++ b/lib/active_merchant/billing/gateways/cams.rb @@ -0,0 +1,230 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class CamsGateway < Gateway + self.live_url = 'https://secure.centralams.com/gw/api/transact.php' + + self.supported_countries = ['US'] + self.default_currency = 'USD' + self.supported_cardtypes = [:visa, :master, :american_express, :discover] + + self.homepage_url = 'https://www.centralams.com/' + self.display_name = 'CAMS: Central Account Management System' + + STANDARD_ERROR_CODE_MAPPING = { + '200' => STANDARD_ERROR_CODE[:card_declined], + '201' => STANDARD_ERROR_CODE[:card_declined], + '202' => STANDARD_ERROR_CODE[:card_declined], + '203' => STANDARD_ERROR_CODE[:card_declined], + '204' => STANDARD_ERROR_CODE[:card_declined], + '220' => STANDARD_ERROR_CODE[:card_declined], + '221' => STANDARD_ERROR_CODE[:card_declined], + '222' => STANDARD_ERROR_CODE[:incorrect_number], + '223' => STANDARD_ERROR_CODE[:expired_card], + '224' => STANDARD_ERROR_CODE[:invalid_expiry_date], + '225' => STANDARD_ERROR_CODE[:invalid_cvc], + '240' => STANDARD_ERROR_CODE[:call_issuer], + '250' => STANDARD_ERROR_CODE[:pickup_card], + '251' => STANDARD_ERROR_CODE[:pickup_card], + '252' => STANDARD_ERROR_CODE[:pickup_card], + '253' => STANDARD_ERROR_CODE[:pickup_card], + '260' => STANDARD_ERROR_CODE[:card_declined], + '261' => STANDARD_ERROR_CODE[:card_declined], + '262' => STANDARD_ERROR_CODE[:card_declined], + '263' => STANDARD_ERROR_CODE[:processing_error], + '264' => STANDARD_ERROR_CODE[:card_declined], + '300' => STANDARD_ERROR_CODE[:card_declined], + '400' => STANDARD_ERROR_CODE[:processing_error], + '410' => STANDARD_ERROR_CODE[:processing_error], + '411' => STANDARD_ERROR_CODE[:processing_error], + '420' => STANDARD_ERROR_CODE[:processing_error], + '421' => STANDARD_ERROR_CODE[:processing_error], + '430' => STANDARD_ERROR_CODE[:processing_error], + '440' => STANDARD_ERROR_CODE[:processing_error], + '441' => STANDARD_ERROR_CODE[:processing_error], + '460' => STANDARD_ERROR_CODE[:invalid_number], + '461' => STANDARD_ERROR_CODE[:processing_error], + '801' => STANDARD_ERROR_CODE[:processing_error], + '811' => STANDARD_ERROR_CODE[:processing_error], + '812' => STANDARD_ERROR_CODE[:processing_error], + '813' => STANDARD_ERROR_CODE[:processing_error], + '814' => STANDARD_ERROR_CODE[:processing_error], + '815' => STANDARD_ERROR_CODE[:processing_error], + '823' => STANDARD_ERROR_CODE[:processing_error], + '824' => STANDARD_ERROR_CODE[:processing_error], + '881' => STANDARD_ERROR_CODE[:processing_error], + '882' => STANDARD_ERROR_CODE[:processing_error], + '883' => STANDARD_ERROR_CODE[:processing_error], + '884' => STANDARD_ERROR_CODE[:card_declined], + '885' => STANDARD_ERROR_CODE[:card_declined], + '886' => STANDARD_ERROR_CODE[:card_declined], + '887' => STANDARD_ERROR_CODE[:processing_error], + '888' => STANDARD_ERROR_CODE[:processing_error], + '889' => STANDARD_ERROR_CODE[:processing_error], + '890' => STANDARD_ERROR_CODE[:processing_error], + '891' => STANDARD_ERROR_CODE[:incorrect_cvc], + '892' => STANDARD_ERROR_CODE[:incorrect_cvc], + '893' => STANDARD_ERROR_CODE[:processing_error], + '894' => STANDARD_ERROR_CODE[:processing_error] + } + + def initialize(options={}) + requires!(options, :username, :password) + super + end + + def purchase(money, payment, options={}) + post = {} + add_invoice(post, money, options) + + if payment.respond_to?(:number) + add_payment(post, payment) + add_address(post, payment, options) + elsif payment.kind_of?(String) + post[:transactionid] = split_authorization(payment)[0] + end + + commit('sale', post) + end + + def authorize(money, payment, options={}) + post = {} + add_invoice(post, money, options) + add_payment(post, payment) + add_address(post, payment, options) + + commit('auth', post) + end + + def capture(money, authorization, options={}) + post = {} + add_reference(post, authorization) + add_invoice(post, money, options) + + commit('capture', post) + end + + def refund(money, authorization, options={}) + post = {} + add_reference(post, authorization) + add_invoice(post, money, options) + commit('refund', post) + end + + def void(authorization, options={}) + post = {} + add_reference(post, authorization) + commit('void', post) + end + + def verify(credit_card, options={}) + post = {} + add_invoice(post, 0, options) + add_payment(post, credit_card) + add_address(post, credit_card, options) + commit('verify', post) + end + + def supports_scrubbing? + true + end + + def scrub(transcript) + %w(ccnumber cvv password).each do |field| + transcript = transcript.gsub(%r((#{field}=)[^&]+), '\1[FILTERED]\2') + end + + transcript + end + + private + + def add_address(post, creditcard, options={}) + post[:firstname] = creditcard.first_name + post[:lastname] = creditcard.last_name + + return unless options[:billing_address] + + address = options[:billing_address] + post[:address1] = address[:address1] + post[:address2] = address[:address2] + post[:city] = address[:city] + post[:state] = address[:state] + post[:zip] = address[:zip] + post[:country] = address[:country] + post[:phone] = address[:phone] + end + + def add_reference(post, authorization) + transaction_id, authcode = split_authorization(authorization) + post['transactionid'] = transaction_id + post['authcode'] = authcode + end + + def add_invoice(post, money, options) + post[:amount] = amount(money) + post[:currency] = (options[:currency] || default_currency) + end + + def add_payment(post, payment) + post[:ccnumber] = payment.number + post[:ccexp] = "#{payment.month.to_s.rjust(2, "0")}#{payment.year.to_s[-2..-1]}" + post[:cvv] = payment.verification_value + end + + def parse(body) + kvs = body.split('&') + + kvs.inject({}) { |h, kv| + k, v = kv.split('=') + h[k] = v + h + } + end + + def commit(action, parameters) + url = live_url + parameters[:type] = action + + response_body = ssl_post(url, post_data(parameters)) + response = parse(response_body) + + Response.new( + success_from(response), + message_from(response), + response, + authorization: authorization_from(response), + test: test?, + error_code: error_code_from(response) + ) + end + + def success_from(response) + response['response_code'] == '100' + end + + def message_from(response) + response['responsetext'] + end + + def authorization_from(response) + [response['transactionid'], response['authcode']].join('#') + end + + def split_authorization(authorization) + transaction_id, authcode = authorization.split('#') + [transaction_id, authcode] + end + + def post_data(parameters = {}) + parameters[:password] = @options[:password] + parameters[:username] = @options[:username] + + parameters.collect { |k, v| "#{k}=#{v}" }.join('&') + end + + def error_code_from(response) + STANDARD_ERROR_CODE_MAPPING[response['response_code']] + end + end + end +end diff --git a/lib/active_merchant/billing/gateways/card_connect.rb b/lib/active_merchant/billing/gateways/card_connect.rb new file mode 100644 index 00000000000..4d953f0a6dd --- /dev/null +++ b/lib/active_merchant/billing/gateways/card_connect.rb @@ -0,0 +1,316 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class CardConnectGateway < Gateway + self.test_url = 'https://fts.cardconnect.com:6443/cardconnect/rest/' + self.live_url = 'https://fts.cardconnect.com:8443/cardconnect/rest/' + + self.supported_countries = ['US'] + self.default_currency = 'USD' + self.supported_cardtypes = [:visa, :master, :american_express, :discover] + + self.homepage_url = 'https://cardconnect.com/' + self.display_name = 'Card Connect' + + STANDARD_ERROR_CODE_MAPPING = { + '11' => STANDARD_ERROR_CODE[:card_declined], + '12' => STANDARD_ERROR_CODE[:incorrect_number], + '13' => STANDARD_ERROR_CODE[:incorrect_cvc], + '14' => STANDARD_ERROR_CODE[:incorrect_cvc], + '15' => STANDARD_ERROR_CODE[:invalid_expiry_date], + '16' => STANDARD_ERROR_CODE[:expired_card], + '17' => STANDARD_ERROR_CODE[:incorrect_zip], + '21' => STANDARD_ERROR_CODE[:config_error], + '22' => STANDARD_ERROR_CODE[:config_error], + '23' => STANDARD_ERROR_CODE[:config_error], + '24' => STANDARD_ERROR_CODE[:processing_error], + '25' => STANDARD_ERROR_CODE[:processing_error], + '27' => STANDARD_ERROR_CODE[:processing_error], + '28' => STANDARD_ERROR_CODE[:processing_error], + '29' => STANDARD_ERROR_CODE[:processing_error], + '31' => STANDARD_ERROR_CODE[:processing_error], + '32' => STANDARD_ERROR_CODE[:processing_error], + '33' => STANDARD_ERROR_CODE[:card_declined], + '34' => STANDARD_ERROR_CODE[:card_declined], + '35' => STANDARD_ERROR_CODE[:incorrect_zip], + '36' => STANDARD_ERROR_CODE[:processing_error], + '37' => STANDARD_ERROR_CODE[:incorrect_cvc], + '41' => STANDARD_ERROR_CODE[:processing_error], + '42' => STANDARD_ERROR_CODE[:processing_error], + '43' => STANDARD_ERROR_CODE[:processing_error], + '44' => STANDARD_ERROR_CODE[:config_error], + '61' => STANDARD_ERROR_CODE[:processing_error], + '62' => STANDARD_ERROR_CODE[:processing_error], + '63' => STANDARD_ERROR_CODE[:processing_error], + '64' => STANDARD_ERROR_CODE[:config_error], + '65' => STANDARD_ERROR_CODE[:processing_error], + '66' => STANDARD_ERROR_CODE[:processing_error], + '91' => STANDARD_ERROR_CODE[:processing_error], + '92' => STANDARD_ERROR_CODE[:processing_error], + '93' => STANDARD_ERROR_CODE[:processing_error], + '94' => STANDARD_ERROR_CODE[:processing_error], + '95' => STANDARD_ERROR_CODE[:config_error], + '96' => STANDARD_ERROR_CODE[:processing_error], + 'NU' => STANDARD_ERROR_CODE[:card_declined], + 'N3' => STANDARD_ERROR_CODE[:card_declined], + 'NJ' => STANDARD_ERROR_CODE[:card_declined], + '51' => STANDARD_ERROR_CODE[:card_declined], + 'C2' => STANDARD_ERROR_CODE[:incorrect_cvc], + '54' => STANDARD_ERROR_CODE[:expired_card], + '05' => STANDARD_ERROR_CODE[:card_declined], + '03' => STANDARD_ERROR_CODE[:config_error], + '60' => STANDARD_ERROR_CODE[:pickup_card] + } + + def initialize(options = {}) + requires!(options, :merchant_id, :username, :password) + require_valid_domain!(options, :domain) + super + end + + def require_valid_domain!(options, param) + if options[param] + raise ArgumentError.new('not a valid cardconnect domain') unless /\Dcardconnect.com:\d{1,}\D/ =~ options[param] + end + end + + def purchase(money, payment, options = {}) + if options[:po_number] + MultiResponse.run do |r| + r.process { authorize(money, payment, options) } + r.process { capture(money, r.authorization, options) } + end + else + post = {} + add_invoice(post, options) + add_money(post, money) + add_payment(post, payment) + add_currency(post, money, options) + add_address(post, options) + add_customer_data(post, options) + add_3DS(post, options) + post[:capture] = 'Y' + commit('auth', post) + end + end + + def authorize(money, payment, options = {}) + post = {} + add_money(post, money) + add_currency(post, money, options) + add_invoice(post, options) + add_payment(post, payment) + add_address(post, options) + add_customer_data(post, options) + add_3DS(post, options) + commit('auth', post) + end + + def capture(money, authorization, options = {}) + post = {} + add_money(post, money) + add_reference(post, authorization) + add_additional_data(post, options) + commit('capture', post) + end + + def refund(money, authorization, options = {}) + post = {} + add_money(post, money) + add_reference(post, authorization) + commit('refund', post) + end + + def void(authorization, options = {}) + post = {} + add_reference(post, authorization) + commit('void', post) + end + + def verify(credit_card, options = {}) + authorize(0, credit_card, options) + end + + def store(payment, options = {}) + post = {} + add_payment(post, payment) + add_address(post, options) + add_customer_data(post, options) + commit('profile', post) + end + + def unstore(authorization, options = {}) + account_id, profile_id = authorization.split('|') + commit('profile', {}, + verb: :delete, + path: "/#{profile_id}/#{account_id}/#{@options[:merchant_id]}") + end + + def supports_scrubbing? + true + end + + def scrub(transcript) + transcript. + gsub(%r((Authorization: Basic )\w+), '\1[FILTERED]'). + gsub(%r(("cvv2\\":\\")\d*), '\1[FILTERED]'). + gsub(%r(("merchid\\":\\")\d*), '\1[FILTERED]'). + gsub(%r((&?"account\\":\\")\d*), '\1[FILTERED]'). + gsub(%r((&?"token\\":\\")\d*), '\1[FILTERED]') + end + + private + + def add_customer_data(post, options) + post[:email] = options[:email] if options[:email] + end + + def add_address(post, options) + if address = options[:billing_address] || options[:address] + post[:address] = address[:address1] if address[:address1] + post[:address].concat(" #{address[:address2]}") if address[:address2] + post[:city] = address[:city] if address[:city] + post[:region] = address[:state] if address[:state] + post[:country] = address[:country] if address[:country] + post[:postal] = address[:zip] if address[:zip] + post[:phone] = address[:phone] if address[:phone] + end + end + + def add_money(post, money) + post[:amount] = amount(money) + end + + def add_currency(post, money, options) + post[:currency] = (options[:currency] || currency(money)) + end + + def add_invoice(post, options) + post[:orderid] = options[:order_id] + post[:ecomind] = (options[:recurring] ? 'R' : 'E') + end + + def add_payment(post, payment) + if payment.is_a?(String) + account_id, profile_id = payment.split('|') + post[:profile] = profile_id + post[:acctid] = account_id + else + post[:name] = payment.name + if card_brand(payment) == 'check' + add_echeck(post, payment) + else + post[:account] = payment.number + post[:expiry] = expdate(payment) + post[:cvv2] = payment.verification_value + end + end + end + + def add_echeck(post, payment) + post[:accttype] = 'ECHK' + post[:account] = payment.account_number + post[:bankaba] = payment.routing_number + end + + def add_reference(post, authorization) + post[:retref] = authorization + end + + def add_additional_data(post, options) + post[:ponumber] = options[:po_number] + post[:taxamnt] = options[:tax_amount] if options[:tax_amount] + post[:frtamnt] = options[:freight_amount] if options[:freight_amount] + post[:dutyamnt] = options[:duty_amount] if options[:duty_amount] + post[:orderdate] = options[:order_date] if options[:order_date] + post[:shipfromzip] = options[:ship_from_zip] if options[:ship_from_zip] + if (shipping_address = options[:shipping_address]) + post[:shiptozip] = shipping_address[:zip] + post[:shiptocountry] = shipping_address[:country] + end + if options[:items] + post[:items] = options[:items].map do |item| + updated = {} + item.each_pair do |k, v| + updated.merge!(k.to_s.gsub(/_/, '') => v) + end + updated + end + end + end + + def add_3DS(post, options) + post[:secureflag] = options[:secure_flag] if options[:secure_flag] + post[:securevalue] = options[:secure_value] if options[:secure_value] + post[:securexid] = options[:secure_xid] if options[:secure_xid] + end + + def headers + { + 'Authorization' => 'Basic ' + Base64.strict_encode64("#{@options[:username]}:#{@options[:password]}"), + 'Content-Type' => 'application/json' + } + end + + def expdate(credit_card) + "#{format(credit_card.month, :two_digits)}#{format(credit_card.year, :two_digits)}" + end + + def parse(body) + JSON.parse(body) + end + + def url(action, path) + if test? + test_url + action + path + else + (@options[:domain] || live_url) + action + path + end + end + + def commit(action, parameters, verb: :put, path: '') + parameters[:frontendid] = application_id + parameters[:merchid] = @options[:merchant_id] + url = url(action, path) + response = parse(ssl_request(verb, url, post_data(parameters), headers)) + + Response.new( + success_from(response), + message_from(response), + response, + authorization: authorization_from(response), + avs_result: AVSResult.new(code: response['avsresp']), + cvv_result: CVVResult.new(response['cvvresp']), + test: test?, + error_code: error_code_from(response) + ) + rescue ResponseError => e + return Response.new(false, 'Unable to authenticate. Please check your credentials.', {}, :test => test?) if e.response.code == '401' + raise + end + + def success_from(response) + response['respstat'] == 'A' + end + + def message_from(response) + response['setlstat'] ? "#{response['resptext']} #{response['setlstat']}" : response['resptext'] + end + + def authorization_from(response) + if response['profileid'] + "#{response['acctid']}|#{response['profileid']}" + else + response['retref'] + end + end + + def post_data(parameters = {}) + parameters.to_json + end + + def error_code_from(response) + STANDARD_ERROR_CODE_MAPPING[response['respcode']] unless success_from(response) + end + end + end +end diff --git a/lib/active_merchant/billing/gateways/card_save.rb b/lib/active_merchant/billing/gateways/card_save.rb index 7bd9ee8e4d2..7d5920be05b 100644 --- a/lib/active_merchant/billing/gateways/card_save.rb +++ b/lib/active_merchant/billing/gateways/card_save.rb @@ -1,23 +1,22 @@ module ActiveMerchant #:nodoc: module Billing #:nodoc: class CardSaveGateway < IridiumGateway - #CardSave lets you handle failovers on payments by providing 3 gateways in case one happens to be down - #URLS = ['https://gw1.cardsaveonlinepayments.com:4430/','https://gw2.cardsaveonlinepayments.com:4430/','https://gw3.cardsaveonlinepayments.com:4430/'] - + # CardSave lets you handle failovers on payments by providing 3 gateways in case one happens to be down + # URLS = ['https://gw1.cardsaveonlinepayments.com:4430/','https://gw2.cardsaveonlinepayments.com:4430/','https://gw3.cardsaveonlinepayments.com:4430/'] + self.money_format = :cents self.default_currency = 'GBP' - self.supported_cardtypes = [ :visa, :switch, :maestro, :master, :solo, :american_express, :jcb ] + self.supported_cardtypes = [ :visa, :maestro, :master, :american_express, :jcb ] self.supported_countries = [ 'GB' ] self.homepage_url = 'http://www.cardsave.net/' self.display_name = 'CardSave' - + def initialize(options={}) super @test_url = 'https://gw1.cardsaveonlinepayments.com:4430/' @live_url = 'https://gw1.cardsaveonlinepayments.com:4430/' end - + end end end - diff --git a/lib/active_merchant/billing/gateways/card_stream.rb b/lib/active_merchant/billing/gateways/card_stream.rb index 30dc8897f38..f5ac54fdb83 100644 --- a/lib/active_merchant/billing/gateways/card_stream.rb +++ b/lib/active_merchant/billing/gateways/card_stream.rb @@ -1,55 +1,108 @@ module ActiveMerchant #:nodoc: module Billing #:nodoc: - # - # CardStream supports the following credit cards, which are auto-detected by - # the gateway based on the card number used: - # * AM American Express - # * Diners Club - # * Electron - # * JCB - # * UK Maestro - # * Maestro International - # * Mastercard - # * Solo - # * Style - # * Switch - # * Visa Credit - # * Visa Debit - # * Visa Purchasing - # class CardStreamGateway < Gateway - self.live_url = self.test_url = 'https://gateway.cardstream.com/process.ashx' + THREEDSECURE_REQUIRED_DEPRECATION_MESSAGE = 'Specifying the :threeDSRequired initialization option is deprecated. Please use the `:threeds_required => true` *transaction* option instead.' + + self.test_url = self.live_url = 'https://gateway.cardstream.com/direct/' self.money_format = :cents self.default_currency = 'GBP' - self.supported_countries = ['GB'] - self.supported_cardtypes = [:visa, :master, :american_express, :diners_club, :discover, :jcb, :maestro, :solo, :switch] + self.supported_countries = ['GB', 'US', 'CH', 'SE', 'SG', 'NO', 'JP', 'IS', 'HK', 'NL', 'CZ', 'CA', 'AU'] + self.supported_cardtypes = [:visa, :master, :american_express, :diners_club, :discover, :jcb, :maestro] self.homepage_url = 'http://www.cardstream.com/' self.display_name = 'CardStream' - APPROVED = '00' - CURRENCY_CODES = { - "AUD"=> '036', - "CAD"=> '124', - "CZK"=> '203', - "DKK"=> '208', - "HKD"=> '344', - "ICK"=> '352', - "JPY"=> '392', - "NOK"=> '578', - "SGD"=> '702', - "SEK"=> '752', - "CHF"=> '756', - "GBP"=> '826', - "USD"=> '840', - "EUR"=> '978' - } - - TRANSACTIONS = { - :purchase => 'ESALE_KEYED', - :refund => 'EREFUND_KEYED', - :authorization => 'ESALE_KEYED' + 'AED' => '784', + 'ALL' => '008', + 'AMD' => '051', + 'ANG' => '532', + 'ARS' => '032', + 'AUD' => '036', + 'AWG' => '533', + 'BAM' => '977', + 'BBD' => '052', + 'BGN' => '975', + 'BMD' => '060', + 'BOB' => '068', + 'BRL' => '986', + 'BSD' => '044', + 'BWP' => '072', + 'BZD' => '084', + 'CAD' => '124', + 'CHF' => '756', + 'CLP' => '152', + 'CNY' => '156', + 'COP' => '170', + 'CRC' => '188', + 'CZK' => '203', + 'DKK' => '208', + 'DOP' => '214', + 'EGP' => '818', + 'EUR' => '978', + 'GBP' => '826', + 'GEL' => '981', + 'GIP' => '292', + 'GTQ' => '320', + 'GYD' => '328', + 'HKD' => '344', + 'HNL' => '340', + 'HRK' => '191', + 'HUF' => '348', + 'ISK' => '352', + 'IDR' => '360', + 'ILS' => '376', + 'INR' => '356', + 'JPY' => '392', + 'JMD' => '388', + 'KES' => '404', + 'KRW' => '410', + 'KYD' => '136', + 'LBP' => '422', + 'LKR' => '144', + 'MAD' => '504', + 'MVR' => '462', + 'MWK' => '454', + 'MXN' => '484', + 'MYR' => '458', + 'NAD' => '516', + 'NGN' => '566', + 'NIO' => '558', + 'NOK' => '578', + 'NPR' => '524', + 'NZD' => '554', + 'PAB' => '590', + 'PEN' => '604', + 'PGK' => '598', + 'PHP' => '608', + 'PKR' => '586', + 'PLN' => '985', + 'PYG' => '600', + 'QAR' => '634', + 'RON' => '946', + 'RSD' => '941', + 'RUB' => '643', + 'RWF' => '646', + 'SAR' => '682', + 'SEK' => '752', + 'SGD' => '702', + 'SRD' => '968', + 'THB' => '764', + 'TND' => '788', + 'TRY' => '949', + 'TTD' => '780', + 'TWD' => '901', + 'TZS' => '834', + 'UAH' => '980', + 'UGX' => '800', + 'USD' => '840', + 'UYU' => '858', + 'VND' => '704', + 'WST' => '882', + 'XAF' => '950', + 'XCD' => '951', + 'XOF' => '952', + 'ZAR' => '710' } CVV_CODE = { @@ -65,11 +118,11 @@ class CardStreamGateway < Gateway # 4 - Postcode not matched. # 8 - Postcode partially matched. AVS_POSTAL_MATCH = { - "0" => nil, - "1" => nil, - "2" => "Y", - "4" => "N", - "8" => "N" + '0' => nil, + '1' => nil, + '2' => 'Y', + '4' => 'N', + '8' => 'N' } # 0 - No additional information available. @@ -78,148 +131,237 @@ class CardStreamGateway < Gateway # 4 - Address numeric not matched. # 8 - Address numeric partially matched. AVS_STREET_MATCH = { - "0" => nil, - "1" => nil, - "2" => "Y", - "4" => "N", - "8" => "N" + '0' => nil, + '1' => nil, + '2' => 'Y', + '4' => 'N', + '8' => 'N' } def initialize(options = {}) - requires!(options, :login, :password) + requires!(options, :login, :shared_secret) + @threeds_required = false + if options[:threeDSRequired] + ActiveMerchant.deprecated(THREEDSECURE_REQUIRED_DEPRECATION_MESSAGE) + @threeds_required = options[:threeDSRequired] + end super end - def purchase(money, credit_card, options = {}) - requires!(options, :order_id) - + def authorize(money, credit_card_or_reference, options = {}) post = {} + add_pair(post, :captureDelay, -1) + add_amount(post, money, options) + add_invoice(post, credit_card_or_reference, money, options) + add_credit_card_or_reference(post, credit_card_or_reference) + add_customer_data(post, options) + add_remote_address(post, options) + commit('SALE', post) + end + def purchase(money, credit_card_or_reference, options = {}) + post = {} + add_pair(post, :captureDelay, 0) add_amount(post, money, options) - add_invoice(post, money, credit_card, options) - add_credit_card(post, credit_card) - add_address(post, options) + add_invoice(post, credit_card_or_reference, money, options) + add_credit_card_or_reference(post, credit_card_or_reference) add_customer_data(post, options) + add_remote_address(post, options) + commit('SALE', post) + end + + def capture(money, authorization, options = {}) + post = {} + add_pair(post, :xref, authorization) + add_pair(post, :amount, amount(money), :required => true) + add_remote_address(post, options) - commit(:purchase, post) + commit('CAPTURE', post) end - private + def refund(money, authorization, options = {}) + post = {} + add_pair(post, :xref, authorization) + add_amount(post, money, options) + add_remote_address(post, options) + response = commit('REFUND_SALE', post) - def add_amount(post, money, options) - add_pair(post, :Amount, amount(money), :required => true) - add_pair(post, :CurrencyCode, currency_code(options[:currency] || currency(money)), :required => true) + return response if response.success? + return response unless options[:force_full_refund_if_unsettled] + + if response.params['responseCode'] == '65541' + void(authorization, options) + else + response + end end - def add_customer_data(post, options) - add_pair(post, :BillingEmail, options[:email]) - add_pair(post, :BillingPhoneNumber, options[:phone]) + def void(authorization, options = {}) + post = {} + add_pair(post, :xref, authorization) + add_remote_address(post, options) + commit('CANCEL', post) end - def add_address(post, options) - address = options[:billing_address] || options[:address] + def verify(creditcard, options={}) + MultiResponse.run(:use_first_response) do |r| + r.process { authorize(100, creditcard, options) } + r.process(:ignore_result) { void(r.authorization, options) } + end + end - return if address.nil? + def supports_scrubbing? + true + end - add_pair(post, :BillingStreet, address[:address1]) - add_pair(post, :BillingHouseNumber, address[:address2]) - add_pair(post, :BillingCity, address[:city]) - add_pair(post, :BillingState, address[:state]) - add_pair(post, :BillingPostCode, address[:zip]) + def scrub(transcript) + transcript. + gsub(%r((Authorization: Basic )\w+), '\1[FILTERED]'). + gsub(%r((cardNumber=)\d+), '\1[FILTERED]'). + gsub(%r((CVV=)\d+), '\1[FILTERED]') end - def add_invoice(post, money, credit_card, options) - add_pair(post, :TransactionUnique, options[:order_id], :required => true) - add_pair(post, :OrderDesc, options[:description] || options[:order_id], :required => true) + private - if [ 'american_express', 'diners_club' ].include?(card_brand(credit_card).to_s) - add_pair(post, :AEIT1Quantity, 1) - add_pair(post, :AEIT1Description, (options[:description] || options[:order_id]).slice(0, 15)) - add_pair(post, :AEIT1GrossValue, amount(money)) - end + def add_amount(post, money, options) + add_pair(post, :amount, amount(money), :required => true) + add_pair(post, :currencyCode, currency_code(options[:currency] || currency(money))) end - def add_credit_card(post, credit_card) - add_pair(post, :CardName, credit_card.name, :required => true) - add_pair(post, :CardNumber, credit_card.number, :required => true) + def add_customer_data(post, options) + add_pair(post, :customerEmail, options[:email]) + if (address = options[:billing_address] || options[:address]) + add_pair(post, :customerAddress, "#{address[:address1]} #{address[:address2]}".strip) + add_pair(post, :customerPostCode, address[:zip]) + add_pair(post, :customerPhone, options[:phone]) + add_pair(post, :customerCountryCode, address[:country] || 'GB') + else + add_pair(post, :customerCountryCode, 'GB') + end + end - add_pair(post, :ExpiryDateMM, format(credit_card.month, :two_digits), :required => true) - add_pair(post, :ExpiryDateYY, format(credit_card.year, :two_digits), :required => true) + def add_invoice(post, credit_card_or_reference, money, options) + add_pair(post, :transactionUnique, options[:order_id], :required => true) + add_pair(post, :orderRef, options[:description] || options[:order_id], :required => true) + add_pair(post, :statementNarrative1, options[:merchant_name]) if options[:merchant_name] + add_pair(post, :statementNarrative2, options[:dynamic_descriptor]) if options[:dynamic_descriptor] + if credit_card_or_reference.respond_to?(:number) + if ['american_express', 'diners_club'].include?(card_brand(credit_card_or_reference).to_s) + add_pair(post, :item1Quantity, 1) + add_pair(post, :item1Description, (options[:description] || options[:order_id]).slice(0, 15)) + add_pair(post, :item1GrossValue, amount(money)) + end + end - if requires_start_date_or_issue_number?(credit_card) - add_pair(post, :StartDateMM, format(credit_card.start_month, :two_digits)) - add_pair(post, :StartDateYY, format(credit_card.start_year, :two_digits)) + add_pair(post, :type, options[:type] || '1') + add_threeds_required(post, options) + end - add_pair(post, :IssueNumber, credit_card.issue_number) + def add_credit_card_or_reference(post, credit_card_or_reference) + if credit_card_or_reference.respond_to?(:number) + add_credit_card(post, credit_card_or_reference) + else + add_reference(post, credit_card_or_reference.to_s) end + end - add_pair(post, :CV2, credit_card.verification_value) + def add_reference(post, reference) + add_pair(post, :xref, reference, :required => true) end - def commit(action, parameters) - response = parse( ssl_post(self.live_url, post_data(action, parameters)) ) + def add_credit_card(post, credit_card) + add_pair(post, :customerName, credit_card.name, :required => true) + add_pair(post, :cardNumber, credit_card.number, :required => true) + add_pair(post, :cardExpiryMonth, format(credit_card.month, :two_digits), :required => true) + add_pair(post, :cardExpiryYear, format(credit_card.year, :two_digits), :required => true) + add_pair(post, :cardCVV, credit_card.verification_value) + end - Response.new(response[:response_code] == APPROVED, message_from(response), response, - :test => test?, - :authorization => response[:cross_reference], - :cvv_result => CVV_CODE[ response[:avscv2_response_code].to_s[0, 1] ], - :avs_result => { - :street_match => AVS_STREET_MATCH[ response[:avscv2_response_code].to_s[2, 1] ], - :postal_match => AVS_POSTAL_MATCH[ response[:avscv2_response_code].to_s[1, 1] ] - } - ) + def add_threeds_required(post, options) + add_pair(post, :threeDSRequired, options[:threeds_required] || @threeds_required ? 'Y' : 'N') end - def message_from(results) - results[:response_code] == APPROVED ? "APPROVED" : results[:message] + def add_remote_address(post, options={}) + add_pair(post, :remoteAddress, options[:ip] || '1.1.1.1') end - def post_data(action, parameters = {}) - parameters.update( - :MerchantPassword => @options[:password], - :MerchantID => @options[:login], - :MessageType => TRANSACTIONS[action], - :CallBack => "disable", - :DuplicateDelay => "0", - :EchoCardType => "YES", - :EchoAmount => "YES", - :EchoAVSCV2ResponseCode => "YES", - :ReturnAVSCV2Message => "YES", - :CountryCode => '826' # 826 for UK based merchant - ) + def normalize_line_endings(str) + str.gsub(/%0D%0A|%0A%0D|%0D/, '%0A') + end - add_pair(parameters, :Dispatch, action == :authorization ? "LATER" : "NOW") + def add_hmac(post) + result = post.sort.collect { |key, value| "#{key}=#{normalize_line_endings(CGI.escape(value.to_s))}" }.join('&') + result = Digest::SHA512.hexdigest("#{result}#{@options[:shared_secret]}") - parameters.collect { |key, value| "VP#{key}=#{CGI.escape(value.to_s)}" }.join("&") + add_pair(post, :signature, result) end - # VPCrossReference - # The value in VPCrossReference on a success transaction will contain - # a unique reference that you may use to run future transactions. - # Please note that cross reference transactions must come a static IP - # addressed that has been pre-registered with Cardstream. To - # register an IP address please send it to support@cardstream.com - # with your Cardstream issued merchant ID and it will be added to - # your account. def parse(body) result = {} - pairs = body.split("&") + pairs = body.split('&') pairs.each do |pair| - a = pair.split("=") - result[a[0].gsub(/^VP/,'').underscore.to_sym] = a[1] + a = pair.split('=') + # because some value pairs don't have a value + result[a[0].to_sym] = a[1] == nil ? '' : CGI.unescape(a[1]) end - result end + def commit(action, parameters) + parameters.update(:countryCode => self.supported_countries[0]) unless ['CAPTURE', 'CANCEL'].include?(action) + parameters.update( + :merchantID => @options[:login], + :action => action + ) + # adds a signature to the post hash/array + add_hmac(parameters) + + response = parse(ssl_post(self.live_url, post_data(action, parameters))) + + Response.new( + response[:responseCode] == '0', + response[:responseCode] == '0' ? 'APPROVED' : response[:responseMessage], + response, + :test => test?, + :authorization => response[:xref], + :cvv_result => CVV_CODE[response[:avscv2ResponseCode].to_s[0, 1]], + :avs_result => avs_from(response) + ) + end + + def avs_from(response) + postal_match = AVS_POSTAL_MATCH[response[:avscv2ResponseCode].to_s[1, 1]] + street_match = AVS_STREET_MATCH[response[:avscv2ResponseCode].to_s[2, 1]] + + code = if postal_match == 'Y' && street_match == 'Y' + 'M' + elsif postal_match == 'Y' + 'P' + elsif street_match == 'Y' + 'A' + else + 'I' + end + + AVSResult.new({ + :code => code, + :postal_match => postal_match, + :street_match => street_match + }) + end + def currency_code(currency) CURRENCY_CODES[currency] end + def post_data(action, parameters = {}) + parameters.collect { |key, value| "#{key}=#{CGI.escape(value.to_s)}" }.join('&') + end + def add_pair(post, key, value, options = {}) post[key] = value if !value.blank? || options[:required] end + end end end - diff --git a/lib/active_merchant/billing/gateways/card_stream_modern.rb b/lib/active_merchant/billing/gateways/card_stream_modern.rb deleted file mode 100644 index 1d3393fa22d..00000000000 --- a/lib/active_merchant/billing/gateways/card_stream_modern.rb +++ /dev/null @@ -1,155 +0,0 @@ -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - class CardStreamModernGateway < Gateway - self.test_url = self.live_url = 'https://gateway.cardstream.com/direct/' - self.money_format = :cents - self.default_currency = 'GBP' - self.supported_countries = ['GB'] - self.supported_cardtypes = [:visa, :master, :american_express, :diners_club, :discover, :jcb, :maestro, :solo, :switch] - self.homepage_url = 'http://www.cardstream.com/' - self.display_name = 'CardStream' - - def initialize(options = {}) - requires!(options, :login) - if(options[:threeDSRequired]) - @threeDSRequired = options[:threeDSRequired] - else - @threeDSRequired = 'N' - end - super - end - - def authorize(money, creditcard, options = {}) - post = {} - add_amount(post, money, options) - add_invoice(post, creditcard, money, options) - add_creditcard(post, creditcard) - add_address(post, creditcard, options) - add_customer_data(post, options) - commit('PREAUTH', post) - end - - def purchase(money, creditcard, options = {}) - post = {} - add_amount(post, money, options) - add_invoice(post, creditcard, money, options) - add_creditcard(post, creditcard) - add_address(post, creditcard, options) - add_customer_data(post, options) - commit('SALE', post) - end - - def capture(money, authorization, options = {}) - post = {} - add_pair(post, :xref, authorization) - add_amount(post, money, options) - commit('SALE', post) - end - - def refund(money, authorization, options = {}) - post = {} - add_pair(post, :xref, authorization) - add_amount(post, money, options) - commit('REFUND', post) - end - - def void(authorization, options = {}) - post = {} - add_pair(post, :xref, authorization) - commit('REFUND', post) - end - - private - - def add_amount(post, money, options) - add_pair(post, :amount, amount(money), :required => true) - add_pair(post, :currencyCode, options[:currency] || self.default_currency) - end - - def add_customer_data(post, options) - address = options[:billing_address] || options[:address] - add_pair(post, :customerPostCode, address[:zip]) - add_pair(post, :customerEmail, options[:email]) - add_pair(post, :customerPhone, options[:phone]) - end - - def add_address(post, creditcard, options) - address = options[:billing_address] || options[:address] - - return if address.nil? - - add_pair(post, :customerAddress, address[:address1] + " " + (address[:address2].nil? ? "" : address[:address2]) ) - add_pair(post, :customerPostCode, address[:zip]) - end - - def add_invoice(post, credit_card, money, options) - add_pair(post, :transactionUnique, options[:order_id], :required => true) - add_pair(post, :orderRef, options[:description] || options[:order_id], :required => true) - if [ 'american_express', 'diners_club' ].include?(card_brand(credit_card).to_s) - add_pair(post, :item1Quantity, 1) - add_pair(post, :item1Description, (options[:description] || options[:order_id]).slice(0, 15)) - add_pair(post, :item1GrossValue, amount(money)) - end - end - - def add_creditcard(post, credit_card) - add_pair(post, :customerName, credit_card.name, :required => true) - add_pair(post, :cardNumber, credit_card.number, :required => true) - - add_pair(post, :cardExpiryMonth, format(credit_card.month, :two_digits), :required => true) - add_pair(post, :cardExpiryYear, format(credit_card.year, :two_digits), :required => true) - - if requires_start_date_or_issue_number?(credit_card) - add_pair(post, :cardStartMonth, format(credit_card.start_month, :two_digits)) - add_pair(post, :cardStartYear, format(credit_card.start_year, :two_digits)) - - add_pair(post, :cardIssueNumber, credit_card.issue_number) - end - - add_pair(post, :cardCVV, credit_card.verification_value) - end - - def parse(body) - result = {} - pairs = body.split("&") - pairs.each do |pair| - a = pair.split("=") - result[a[0].to_sym] = CGI.unescape(a[1]) - end - result - end - - def commit(action, parameters) - response = parse( ssl_post(self.live_url, post_data(action, parameters)) ) - - Response.new(response[:responseCode] == "0", - response[:responseCode] == "0" ? "APPROVED" : response[:responseMessage], - response, - :test => test?, - :authorization => response[:xref], - :avs_result => { - :street_match => response[:addressCheck], - :postal_match => response[:postcodeCheck], - }, - :cvv_result => response[:cv2Check] - ) - end - - def post_data(action, parameters = {}) - parameters.update( - :merchantID => @options[:login], - :action => action, - :type => '1', #Ecommerce - :countryCode => self.supported_countries[0], - :threeDSRequired => @threeDSRequired #Disable 3d secure by default - ) - parameters.collect { |key, value| "#{key}=#{CGI.escape(value.to_s)}" }.join("&") - end - - def add_pair(post, key, value, options = {}) - post[key] = value if !value.blank? || options[:required] - end - end - end -end - diff --git a/lib/active_merchant/billing/gateways/cardknox.rb b/lib/active_merchant/billing/gateways/cardknox.rb new file mode 100644 index 00000000000..938e6ade478 --- /dev/null +++ b/lib/active_merchant/billing/gateways/cardknox.rb @@ -0,0 +1,327 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class CardknoxGateway < Gateway + self.live_url = 'https://x1.cardknox.com/gateway' + + self.supported_countries = ['US', 'CA', 'GB'] + self.default_currency = 'USD' + self.supported_cardtypes = [:visa, :master, :american_express, :discover, :diners_club, :jcb] + + self.homepage_url = 'https://www.cardknox.com/' + self.display_name = 'Cardknox' + + COMMANDS = { + credit_card: { + purchase: 'cc:sale', + authorization: 'cc:authonly', + capture: 'cc:capture', + refund: 'cc:refund', + void: 'cc:void', + save: 'cc:save' + }, + check: { + purchase: 'check:sale', + refund: 'check:refund', + void: 'check:void', + save: 'check:save' + } + } + + def initialize(options={}) + requires!(options, :api_key) + super + end + + # There are three sources for doing a purchase transation: + # - credit card + # - check + # - cardknox token, which is returned in the the authorization string "ref_num;token;command" + + def purchase(amount, source, options={}) + post = {} + add_amount(post, amount, options) + add_invoice(post, options) + add_source(post, source) + add_address(post, source, options) + add_customer_data(post, options) + add_custom_fields(post, options) + commit(:purchase, source_type(source), post) + end + + def authorize(amount, source, options={}) + post = {} + add_amount(post, amount) + add_invoice(post, options) + add_source(post, source) + add_address(post, source, options) + add_customer_data(post, options) + add_custom_fields(post, options) + commit(:authorization, source_type(source), post) + end + + def capture(amount, authorization, options = {}) + post = {} + add_reference(post, authorization) + add_amount(post, amount) + commit(:capture, source_type(authorization), post) + end + + def refund(amount, authorization, options={}) + post = {} + add_reference(post, authorization) + add_amount(post, amount) + commit(:refund, source_type(authorization), post) + end + + def void(authorization, options = {}) + post = {} + add_reference(post, authorization) + commit(:void, source_type(authorization), post) + end + + def verify(credit_card, options={}) + MultiResponse.run(:use_first_response) do |r| + r.process { authorize(100, credit_card, options) } + r.process(:ignore_result) { void(r.authorization, options) } + end + end + + def store(source, options = {}) + post = {} + add_source(post, source) + add_address(post, source, options) + add_invoice(post, options) + add_customer_data(post, options) + add_custom_fields(post, options) + commit(:save, source_type(source), post) + end + + def supports_scrubbing? + true + end + + def scrub(transcript) + transcript. + gsub(%r((xCardNum=)\d+), '\1[FILTERED]'). + gsub(%r((xCVV=)\d+), '\1[FILTERED]'). + gsub(%r((xAccount=)\d+), '\1[FILTERED]'). + gsub(%r((xRouting=)\d+), '\1[FILTERED]'). + gsub(%r((xKey=)\w+), '\1[FILTERED]') + end + + private + + def split_authorization(authorization) + authorization.split(';') + end + + def add_reference(post, reference) + reference, _, _ = split_authorization(reference) + post[:Refnum] = reference + end + + def source_type(source) + if source.respond_to?(:brand) + :credit_card + elsif source.respond_to?(:routing_number) + :check + elsif source.kind_of?(String) + source_type_from(source) + else + raise ArgumentError, "Unknown source #{source.inspect}" + end + end + + def source_type_from(authorization) + _, _, source_type = split_authorization(authorization) + (source_type || 'credit_card').to_sym + end + + def add_source(post, source) + if source.respond_to?(:brand) + add_credit_card(post, source) + elsif source.respond_to?(:routing_number) + add_check(post, source) + elsif source.kind_of?(String) + add_cardknox_token(post, source) + else + raise ArgumentError, "Invalid payment source #{source.inspect}" + end + end + + # Subtotal + Tax + Tip = Amount. + + def add_amount(post, money, options = {}) + post[:Tip] = amount(options[:tip]) + post[:Amount] = amount(money) + end + + def expdate(credit_card) + year = format(credit_card.year, :two_digits) + month = format(credit_card.month, :two_digits) + "#{month}#{year}" + end + + def add_customer_data(post, options) + address = options[:billing_address] || {} + post[:Street] = address[:address1] + post[:Zip] = address[:zip] + post[:PONum] = options[:po_number] + post[:Fax] = options[:fax] + post[:Email] = options[:email] + post[:IP] = options[:ip] + end + + def add_address(post, source, options) + add_address_for_type(:billing, post, source, options[:billing_address]) if options[:billing_address] + add_address_for_type(:shipping, post, source, options[:shipping_address]) if options[:shipping_address] + end + + def add_address_for_type(type, post, source, address) + prefix = address_key_prefix(type) + if source.respond_to?(:first_name) + post[address_key(prefix, 'FirstName')] = source.first_name + post[address_key(prefix, 'LastName')] = source.last_name + else + post[address_key(prefix, 'FirstName')] = address[:first_name] + post[address_key(prefix, 'LastName')] = address[:last_name] + end + post[address_key(prefix, 'MiddleName')] = address[:middle_name] + + post[address_key(prefix, 'Company')] = address[:company] + post[address_key(prefix, 'Street')] = address[:address1] + post[address_key(prefix, 'Street2')] = address[:address2] + post[address_key(prefix, 'City')] = address[:city] + post[address_key(prefix, 'State')] = address[:state] + post[address_key(prefix, 'Zip')] = address[:zip] + post[address_key(prefix, 'Country')] = address[:country] + post[address_key(prefix, 'Phone')] = address[:phone] + post[address_key(prefix, 'Mobile')] = address[:mobile] + end + + def address_key_prefix(type) + case type + when :shipping then 'Ship' + when :billing then 'Bill' + else + raise ArgumentError, "Unknown address key prefix: #{type}" + end + end + + def address_key(prefix, key) + "#{prefix}#{key}".to_sym + end + + def add_invoice(post, options) + post[:Invoice] = options[:invoice] + post[:OrderID] = options[:order_id] + post[:Comments] = options[:comments] + post[:Description] = options[:description] + post[:Tax] = amount(options[:tax]) + end + + def add_custom_fields(post, options) + options.keys.grep(/^custom(?:[01]\d|20)$/) do |key| + post[key.to_s.capitalize] = options[key] + end + end + + def add_credit_card(post, credit_card) + if credit_card.track_data.present? + post[:Magstripe] = credit_card.track_data + post[:Cardpresent] = true + else + post[:CardNum] = credit_card.number + post[:CVV] = credit_card.verification_value + post[:Exp] = expdate(credit_card) + post[:Name] = credit_card.name + post[:CardPresent] = true if credit_card.manual_entry + end + end + + def add_check(post, check) + post[:Routing] = check.routing_number + post[:Account] = check.account_number + post[:Name] = check.name + post[:CheckNum] = check.number + end + + def add_cardknox_token(post, authorization) + _, token, _ = split_authorization(authorization) + + post[:Token] = token + end + + def parse(body) + fields = {} + for line in body.split('&') + key, value = *line.scan(%r{^(\w+)\=(.*)$}).flatten + fields[key] = CGI.unescape(value.to_s) + end + + { + result: fields['xResult'], + status: fields['xStatus'], + error: fields['xError'], + auth_code: fields['xAuthCode'], + ref_num: fields['xRefNum'], + current_ref_num: fields['xRefNumCurrent'], + token: fields['xToken'], + batch: fields['xBatch'], + avs_result: fields['xAvsResult'], + avs_result_code: fields['xAvsResultCode'], + cvv_result: fields['xCvvResult'], + cvv_result_code: fields['xCvvResultCode'], + remaining_balance: fields['xRemainingBalance'], + amount: fields['xAuthAmount'], + masked_card_num: fields['xMaskedCardNumber'], + masked_account_number: fields['MaskedAccountNumber'] + }.delete_if { |k, v| v.nil? } + end + + def commit(action, source_type, parameters) + response = parse(ssl_post(live_url, post_data(COMMANDS[source_type][action], parameters))) + + Response.new( + (response[:status] == 'Approved'), + message_from(response), + response, + authorization: authorization_from(response, source_type), + avs_result: { code: response[:avs_result_code] }, + cvv_result: response[:cvv_result_code] + ) + end + + def message_from(response) + if response[:status] == 'Approved' + 'Success' + elsif response[:error].blank? + 'Unspecified error' + else + response[:error] + end + end + + def authorization_from(response, source_type) + "#{response[:ref_num]};#{response[:token]};#{source_type}" + end + + def post_data(command, parameters = {}) + initial_parameters = { + Key: @options[:api_key], + Version: '4.5.4', + SoftwareName: 'Active Merchant', + SoftwareVersion: ActiveMerchant::VERSION.to_s, + Command: command, + } + + seed = SecureRandom.hex(32).upcase + hash = Digest::SHA1.hexdigest("#{initial_parameters[:command]}:#{@options[:pin]}:#{parameters[:amount]}:#{parameters[:invoice]}:#{seed}") + initial_parameters[:Hash] = "s/#{seed}/#{hash}/n" unless @options[:pin].blank? + parameters = initial_parameters.merge(parameters) + + parameters.reject { |k, v| v.blank? }.collect { |key, value| "x#{key}=#{CGI.escape(value.to_s)}" }.join('&') + end + end + end +end diff --git a/lib/active_merchant/billing/gateways/cardprocess.rb b/lib/active_merchant/billing/gateways/cardprocess.rb new file mode 100644 index 00000000000..c91121c0641 --- /dev/null +++ b/lib/active_merchant/billing/gateways/cardprocess.rb @@ -0,0 +1,254 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class CardprocessGateway < Gateway + self.test_url = 'https://test.vr-pay-ecommerce.de/v1/payments' + self.live_url = 'https://vr-pay-ecommerce.de/v1/payments' + + self.supported_countries = %w[ BE BG CZ DK DE EE IE ES FR HR IT CY LV LT LU + MT HU NL AT PL PT RO SI SK FI SE GB IS LI NO + CH ME MK AL RS TR BA ] + self.default_currency = 'EUR' + self.supported_cardtypes = [:visa, :master, :american_express, :diners_club, :jcb] + + self.homepage_url = 'https://vr-pay-ecommerce.docs.oppwa.com/' + self.display_name = 'CardProcess VR-Pay' + self.money_format = :dollars + + STANDARD_ERROR_CODE_MAPPING = {} + + # Creates a new CardProcess Gateway + # + # The gateway requires a valid login, password, and entity ID + # to be passed in the +options+ hash. + # + # === Options + # + # * :user_id -- The CardProcess user ID + # * :password -- The CardProcess password + # * :entity_id -- The CardProcess channel or entity ID for any transactions + def initialize(options={}) + requires!(options, :user_id, :password, :entity_id) + super + # This variable exists purely to allow remote tests to force error codes; + # the lack of a setter despite its usage is intentional. + @test_options = {} + end + + def purchase(money, payment, options = {}) + post = {} + add_invoice(post, money, options) + add_payment(post, payment) + add_address(post, payment, options) + add_customer_data(post, options) + + commit('DB', post) + end + + def authorize(money, payment, options = {}) + post = {} + add_invoice(post, money, options) + add_payment(post, payment) + add_address(post, payment, options) + add_customer_data(post, options) + + commit('PA', post) + end + + def capture(money, authorization, options = {}) + post = { + id: authorization + } + add_invoice(post, money, options) + commit('CP', post) + end + + def refund(money, authorization, options = {}) + post = { + id: authorization + } + add_invoice(post, money, options) + commit('RF', post) + end + + def credit(money, payment, options = {}) + post = {} + add_invoice(post, money, options) + add_payment(post, payment) + add_address(post, payment, options) + add_customer_data(post, options) + + commit('CD', post) + end + + def void(authorization, _options = {}) + post = { + id: authorization + } + commit('RV', post) + end + + def verify(credit_card, options = {}) + MultiResponse.run do |r| + r.process { authorize(100, credit_card, options) } + r.process { void(r.authorization, options) } + end + end + + def supports_scrubbing? + true + end + + def scrub(transcript) + transcript. + gsub(%r{(authentication\.[^=]+=)[^&]+}, '\1[FILTERED]'). + gsub(%r{(card\.number=)\d+}, '\1[FILTERED]'). + gsub(%r{(cvv=)\d{3,4}}, '\1[FILTERED]\2') + end + + private + + def add_customer_data(post, options) + post['customer.ip'] = options[:ip] if options[:ip] + end + + def add_address(post, _card, options) + if (address = options[:billing_address] || options[:address]) + post[:billing] = hashify_address(address) + end + + if (shipping = options[:shipping_address]) + post[:shipping] = hashify_address(shipping) + end + end + + def add_invoice(post, money, options) + return if money.nil? + post[:amount] = amount(money) + post[:currency] = (options[:currency] || currency(money)) + post[:merchantInvoiceId] = options[:merchant_invoice_id] if options[:merchant_invoice_id] + post[:merchantTransactionId] = options[:merchant_transaction_id] if options[:merchant_transaction_id] + post[:transactionCategory] = options[:transaction_category] if options[:transaction_category] + end + + def add_payment(post, payment) + return if payment.is_a?(String) + post[:paymentBrand] = payment.brand.upcase if payment.brand + post[:card] ||= {} + post[:card][:number] = payment.number + post[:card][:holder] = payment.name + post[:card][:expiryMonth] = sprintf('%02d', payment.month) + post[:card][:expiryYear] = sprintf('%02d', payment.year) + post[:card][:cvv] = payment.verification_value + end + + def parse(body) + JSON.parse(body) + end + + def commit(action, parameters) + url = (test? ? test_url : live_url) + if (id = parameters.delete(:id)) + url += "/#{id}" + end + + begin + raw_response = ssl_post(url, post_data(action, parameters.merge(@test_options))) + rescue ResponseError => e + raw_response = e.response.body + end + response = parse(raw_response) + + Response.new( + success_from(response), + message_from(response), + response, + authorization: authorization_from(response), + avs_result: AVSResult.new(code: response['result']['avsResponse']), + cvv_result: CVVResult.new(response['result']['cvvResponse']), + test: test?, + error_code: error_code_from(response) + ) + end + + def success_from(response) + !(response['result']['code'] =~ /^(000\.000\.|000\.100\.1|000\.[36])/).nil? + end + + def message_from(response) + response['result']['description'] + end + + def authorization_from(response) + response['id'] + end + + def post_data(action, parameters = {}) + post = parameters.clone + post[:authentication] ||= {} + post[:authentication][:userId] = @options[:user_id] + post[:authentication][:password] = @options[:password] + post[:authentication][:entityId] = @options[:entity_id] + post[:paymentType] = action + dot_flatten_hash(post).map { |key, value| "#{key}=#{CGI.escape(value.to_s)}" }.join('&') + end + + def error_code_from(response) + unless success_from(response) + case response['result']['code'] + when '100.100.101' + STANDARD_ERROR_CODE[:incorrect_number] + when '100.100.303' + STANDARD_ERROR_CODE[:expired_card] + when /100\.100\.(201|301|305)/ + STANDARD_ERROR_CODE[:invalid_expiry_date] + when /100.100.60[01]/ + STANDARD_ERROR_CODE[:invalid_cvc] + when '800.100.151' + STANDARD_ERROR_CODE[:invalid_number] + when '800.100.153' + STANDARD_ERROR_CODE[:incorrect_cvc] + when /800.800.(102|302)/ + STANDARD_ERROR_CODE[:incorrect_address] + when '800.800.202' + STANDARD_ERROR_CODE[:invalid_zip] + when '800.100.166' + STANDARD_ERROR_CODE[:incorrect_pin] + when '800.100.171' + STANDARD_ERROR_CODE[:pickup_card] + when /^(200|700)\./ + STANDARD_ERROR_CODE[:config_error] + when /^(800\.[17]00|800\.800\.[123])/ + STANDARD_ERROR_CODE[:card_declined] + when /^(900\.[1234]00)/ + STANDARD_ERROR_CODE[:processing_error] + else + STANDARD_ERROR_CODE[:processing_error] + end + end + end + + def hashify_address(address) + hash = {} + hash[:street1] = address[:address1] if address[:address1] + hash[:street2] = address[:address2] if address[:address2] + hash[:city] = address[:city] if address[:city] + hash[:state] = address[:state] if address[:state] + hash[:postcode] = address[:zip] if address[:zip] + hash[:country] = address[:country] if address[:country] + hash + end + + def dot_flatten_hash(hash, prefix = '') + h = {} + hash.each_pair do |k, v| + if v.is_a?(Hash) + h.merge!(dot_flatten_hash(v, prefix + k.to_s + '.')) + else + h[prefix + k.to_s] = v + end + end + h + end + end + end +end diff --git a/lib/active_merchant/billing/gateways/cashnet.rb b/lib/active_merchant/billing/gateways/cashnet.rb new file mode 100644 index 00000000000..48a7d1308c5 --- /dev/null +++ b/lib/active_merchant/billing/gateways/cashnet.rb @@ -0,0 +1,219 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class CashnetGateway < Gateway + include Empty + + self.live_url = 'https://commerce.cashnet.com/' + self.test_url = 'https://train.cashnet.com/' + + self.supported_countries = ['US'] + self.supported_cardtypes = [:visa, :master, :american_express, :discover, :diners_club, :jcb] + self.homepage_url = 'http://www.higherone.com/' + self.display_name = 'Cashnet' + self.money_format = :dollars + self.max_retries = 0 + + # Creates a new CashnetGateway + # + # ==== Options + # + # * :merchant -- Gateway Merchant (REQUIRED) + # * :operator -- Operator (REQUIRED) + # * :password -- Password (REQUIRED) + # * :merchant_gateway_name -- Site name (REQUIRED) + # * :station -- Station (defaults to "WEB") + # * :custcode -- Customer code (defaults to + # "ActiveMerchant/#{ActiveMerchant::VERSION}") + # * :default_item_code -- Default item code (defaults to "FEE", + # can be overridden on a per-transaction basis with options[:item_code]) + def initialize(options = {}) + requires!( + options, + :merchant, + :operator, + :password, + :merchant_gateway_name + ) + options[:default_item_code] ||= 'FEE' + super + end + + def purchase(money, payment_object, options = {}) + post = {} + add_creditcard(post, payment_object) + add_invoice(post, options) + add_address(post, options) + add_customer_data(post, options) + commit('SALE', money, post) + end + + def refund(money, identification, options = {}) + post = {} + post[:origtx] = identification + add_invoice(post, options) + add_customer_data(post, options) + commit('REFUND', money, post) + end + + def supports_scrubbing? + true + end + + def scrub(transcript) + transcript. + gsub(%r{(password=)[^&]+}, '\1[FILTERED]'). + gsub(%r{(cardno=)[^&]+}, '\1[FILTERED]'). + gsub(%r{(cid=)[^&]+}, '\1[FILTERED]') + end + + private + + def commit(action, money, fields) + fields[:amount] = amount(money) + url = (test? ? test_url : live_url) + CGI.escape(@options[:merchant_gateway_name]) + raw_response = ssl_post(url, post_data(action, fields)) + parsed_response = parse(raw_response) + + return unparsable_response(raw_response) unless parsed_response + + success = (parsed_response[:result] == '0') + Response.new( + success, + CASHNET_CODES[parsed_response[:result]], + parsed_response, + test: test?, + authorization: (success ? parsed_response[:tx] : '') + ) + end + + def post_data(action, parameters = {}) + post = {} + post[:command] = action + post[:merchant] = @options[:merchant] + post[:operator] = @options[:operator] + post[:password] = @options[:password] + post[:station] = (@options[:station] || 'WEB') + post[:custcode] = (@options[:custcode] || "ActiveMerchant/#{ActiveMerchant::VERSION}") + post.merge(parameters).collect { |key, value| "#{key}=#{CGI.escape(value.to_s)}" }.join('&') + end + + def add_creditcard(post, creditcard) + post[:cardno] = creditcard.number + post[:cid] = creditcard.verification_value + post[:expdate] = expdate(creditcard) + post[:card_name_g] = creditcard.name + post[:fname] = creditcard.first_name + post[:lname] = creditcard.last_name + end + + def add_invoice(post, options) + post[:order_number] = options[:order_id] if options[:order_id].present? + post[:itemcode] = (options[:item_code] || @options[:default_item_code]) + end + + def add_address(post, options) + if address = (options[:shipping_address] || options[:billing_address] || options[:address]) + post[:addr_g] = String(address[:address1]) + ',' + String(address[:address2]) + post[:city_g] = address[:city] + post[:state_g] = address[:state] + post[:zip_g] = address[:zip] + end + end + + def add_customer_data(post, options) + post[:email_g] = options[:email] + post[:custcode] = options[:custcode] unless empty?(options[:custcode]) + end + + def expdate(creditcard) + year = format(creditcard.year, :two_digits) + month = format(creditcard.month, :two_digits) + + "#{month}#{year}" + end + + def parse(body) + match = body.match(/(.*)<\/cngateway>/) + return nil unless match + + Hash[CGI::parse(match[1]).map { |k, v| [k.to_sym, v.first] }] + end + + def handle_response(response) + if (200...300).cover?(response.code.to_i) + return response.body + elsif response.code.to_i == 302 + return ssl_get(URI.parse(response['location'])) + end + raise ResponseError.new(response) + end + + def unparsable_response(raw_response) + message = 'Unparsable response received from Cashnet. Please contact Cashnet if you continue to receive this message.' + message += " (The raw response returned by the API was #{raw_response.inspect})" + return Response.new(false, message) + end + + CASHNET_CODES = { + '0' => 'Success', + '1' => 'Invalid customer code, no customer code specified', + '2' => 'Invalid operator code, no operator specified', + '3' => 'Invalid workstation code, no station specified', + '4' => 'Invalid item code, no code specified', + '5' => 'Negative amount is not allowed', + '6' => 'Invalid credit card number, no credit card number provided', + '7' => 'Invalid expiration date, no expiration date provided', + '8' => 'Please only provide either ACH or credit card information', + '9' => 'Invalid ACH account number, no account number provided', + '10' => 'Invalid routing/transit number, no routing/transit number provided', + '11' => 'Invalid account type, no account type provided', + '12' => 'Invalid check digit for routing/transit number', + '13' => 'No ACH merchant account set up for the location of the station being used', + '21' => 'Invalid merchant code, no merchant code provided', + '22' => 'Invalid client code, no client code provided', + '23' => 'Invalid password, no password provided', + '24' => 'Invalid transaction type, no transaction type provided', + '25' => 'Invalid amount, amount not provided', + '26' => 'Invalid payment code provided', + '27' => 'Invalid version number, version not found', + '31' => 'Application amount exceeds account balance', + '150' => 'Invalid payment information, no payment information provided', + '200' => 'Invalid command', + '201' => 'Customer not on file', + '205' => 'Invalid operator or password', + '206' => 'Operator is not authorized for this function', + '208' => 'Customer/PIN authentication unsuccessful', + '209' => 'Credit card error', + '211' => 'Credit card error', + '212' => 'Customer/PIN not on file', + '213' => 'Customer information not on file', + '215' => 'Old PIN does not validate ', + '221' => 'Invalid credit card processor type specified in location or payment code', + '222' => 'Credit card processor error', + '280' => 'SmartPay transaction not posted', + '301' => 'Original transaction not found for this customer', + '302' => 'Amount to refund exceeds original payment amount or is missing', + '304' => 'Original credit card payment not found or corrupted', + '305' => 'Refund amounts should be expressed as positive amounts', + '306' => 'Original ACH payment not found', + '307' => 'Original electronic payment not found', + '308' => 'Invalid original transaction number, original transaction number not found', + '310' => 'Refund amount exceeds amount still available for a refund', + '321' => 'Store has not been implemented', + '501' => 'Unable to roll over batch', + '502' => 'Batch not found', + '503' => 'Batch information not available', + '650' => 'Invalid quick code', + '651' => 'Transaction amount does not match amount specified in quick code', + '652' => 'Invalid item code in the detail of the quick code', + '701' => 'This website has been disabled. Please contact the system administrator.', + '702' => 'Improper merchant code. Please contact the system administrator.', + '703' => 'This site is temporarily down for maintenance. We regret the inconvenience. Please try again later.', + '704' => 'Duplicate item violation. Please contact the system administrator.', + '705' => 'An invalid reference type has been passed into the system. Please contact the system administrator', + '706' => 'Items violating unique selection have been passed in. Please contact the system administrator.', + '999' => 'An unexpected error has occurred. Please consult the event log.' + } + end + end +end diff --git a/lib/active_merchant/billing/gateways/cc5.rb b/lib/active_merchant/billing/gateways/cc5.rb index 4d315243c4b..3d25ec7d3f1 100644 --- a/lib/active_merchant/billing/gateways/cc5.rb +++ b/lib/active_merchant/billing/gateways/cc5.rb @@ -33,6 +33,18 @@ def capture(money, authorization, options = {}) commit(build_capture_request(money, authorization, options)) end + def void(authorization, options = {}) + commit(build_void_request(authorization, options)) + end + + def refund(money, authorization, options = {}) + commit(build_authorization_credit_request(money, authorization, options)) + end + + def credit(money, creditcard, options = {}) + commit(build_creditcard_credit_request(money, creditcard, options)) + end + protected def build_sale_request(type, money, creditcard, options = {}) @@ -58,7 +70,6 @@ def build_sale_request(type, money, creditcard, options = {}) add_address(xml, address) end end - end xml.target! @@ -75,6 +86,39 @@ def build_capture_request(money, authorization, options = {}) end end + def build_void_request(authorization, options = {}) + xml = Builder::XmlMarkup.new :indent => 2 + + xml.tag! 'CC5Request' do + add_login_tags(xml) + xml.tag! 'OrderId', authorization + xml.tag! 'Type', 'Void' + end + end + + def build_authorization_credit_request(money, authorization, options = {}) + xml = Builder::XmlMarkup.new :indent => 2 + + xml.tag! 'CC5Request' do + add_login_tags(xml) + xml.tag! 'OrderId', authorization + xml.tag! 'Type', 'Credit' + add_amount_tags(money, options, xml) + end + end + + def build_creditcard_credit_request(money, creditcard, options = {}) + xml = Builder::XmlMarkup.new :indent => 2 + + xml.tag! 'CC5Request' do + add_login_tags(xml) + xml.tag! 'Type', 'Credit' + xml.tag! 'Number', creditcard.number + + add_amount_tags(money, options, xml) + end + end + def add_address(xml, address) xml.tag! 'Name', normalize(address[:name]) xml.tag! 'Street1', normalize(address[:address1]) @@ -103,7 +147,7 @@ def currency_code(currency) end def commit(request) - raw_response = ssl_post((test? ? self.test_url : self.live_url), "DATA=" + request) + raw_response = ssl_post((test? ? self.test_url : self.live_url), 'DATA=' + request) response = parse(raw_response) @@ -130,25 +174,23 @@ def parse(body) def parse_element(response, node) if node.has_elements? - node.elements.each{|element| parse_element(response, element) } + node.elements.each { |element| parse_element(response, element) } else response[node.name.underscore.to_sym] = node.text end end def success?(response) - (response[:response] == "Approved") + (response[:response] == 'Approved') end def normalize(text) return unless text if ActiveSupport::Inflector.method(:transliterate).arity == -2 - ActiveSupport::Inflector.transliterate(text,'') - elsif RUBY_VERSION >= '1.9' - text.gsub(/[^\x00-\x7F]+/, '') + ActiveSupport::Inflector.transliterate(text, '') else - ActiveSupport::Inflector.transliterate(text).to_s + text.gsub(/[^\x00-\x7F]+/, '') end end end diff --git a/lib/active_merchant/billing/gateways/cecabank.rb b/lib/active_merchant/billing/gateways/cecabank.rb new file mode 100644 index 00000000000..b09ed92c37a --- /dev/null +++ b/lib/active_merchant/billing/gateways/cecabank.rb @@ -0,0 +1,249 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class CecabankGateway < Gateway + self.test_url = 'https://tpv.ceca.es' + self.live_url = 'https://pgw.ceca.es' + + self.supported_countries = ['ES'] + self.supported_cardtypes = [:visa, :master, :american_express] + self.homepage_url = 'http://www.ceca.es/es/' + self.display_name = 'Cecabank' + self.default_currency = 'EUR' + self.money_format = :cents + + #### CECA's MAGIC NUMBERS + CECA_NOTIFICATIONS_URL = 'NONE' + CECA_ENCRIPTION = 'SHA2' + CECA_DECIMALS = '2' + CECA_MODE = 'SSL' + CECA_UI_LESS_LANGUAGE = 'XML' + CECA_UI_LESS_LANGUAGE_REFUND = '1' + CECA_UI_LESS_REFUND_PAGE = 'anulacion_xml' + CECA_ACTION_REFUND = 'anulaciones/anularParcial' # use partial refund's URL to avoid time frame limitations and decision logic on client side + CECA_ACTION_PURCHASE = 'tpv/compra' + CECA_CURRENCIES_DICTIONARY = {'EUR' => 978, 'USD' => 840, 'GBP' => 826} + + # Creates a new CecabankGateway + # + # The gateway requires four values for connection to be passed + # in the +options+ hash. + # + # ==== Options + # + # * :merchant_id -- Cecabank's merchant_id (REQUIRED) + # * :acquirer_bin -- Cecabank's acquirer_bin (REQUIRED) + # * :terminal_id -- Cecabank's terminal_id (REQUIRED) + # * :key -- Cecabank's cypher key (REQUIRED) + # * :test -- +true+ or +false+. If true, perform transactions against the test server. + # Otherwise, perform transactions against the production server. + def initialize(options = {}) + requires!(options, :merchant_id, :acquirer_bin, :terminal_id, :key) + super + end + + # Perform a purchase, which is essentially an authorization and capture in a single operation. + # + # ==== Parameters + # + # * money -- The amount to be purchased as an Integer value in cents. + # * creditcard -- The CreditCard details for the transaction. + # * options -- A hash of optional parameters. + # + # ==== Options + # + # * :order_id -- order_id passed used purchase. (REQUIRED) + # * :currency -- currency. Supported: EUR, USD, GBP. + # * :description -- description to be pased to the gateway. + def purchase(money, creditcard, options = {}) + requires!(options, :order_id) + + post = {'Descripcion' => options[:description], + 'Num_operacion' => options[:order_id], + 'Idioma' => CECA_UI_LESS_LANGUAGE, + 'Pago_soportado' => CECA_MODE, + 'URL_OK' => CECA_NOTIFICATIONS_URL, + 'URL_NOK' => CECA_NOTIFICATIONS_URL, + 'Importe' => amount(money), + 'TipoMoneda' => CECA_CURRENCIES_DICTIONARY[options[:currency] || currency(money)]} + + add_creditcard(post, creditcard) + + commit(CECA_ACTION_PURCHASE, post) + end + + # Refund a transaction. + # + # This transaction indicates to the gateway that + # money should flow from the merchant to the customer. + # + # ==== Parameters + # + # * money -- The amount to be credited to the customer as an Integer value in cents. + # * identification -- The reference given from the gateway on purchase (reference, not operation). + # * options -- A hash of parameters. + def refund(money, identification, options = {}) + reference, order_id = split_authorization(identification) + + post = {'Referencia' => reference, + 'Num_operacion' => order_id, + 'Idioma' => CECA_UI_LESS_LANGUAGE_REFUND, + 'Pagina' => CECA_UI_LESS_REFUND_PAGE, + 'Importe' => amount(money), + 'TipoMoneda' => CECA_CURRENCIES_DICTIONARY[options[:currency] || currency(money)]} + + commit(CECA_ACTION_REFUND, post) + end + + def supports_scrubbing + true + end + + def scrub(transcript) + transcript. + gsub(%r((Authorization: Basic )\w+), '\1[FILTERED]'). + gsub(%r((&?pan=)[^&]*)i, '\1[FILTERED]'). + gsub(%r((&?cvv2=)[^&]*)i, '\1[FILTERED]') + end + + private + + def add_creditcard(post, creditcard) + post['PAN'] = creditcard.number + post['Caducidad'] = expdate(creditcard) + post['CVV2'] = creditcard.verification_value + post['Pago_elegido'] = CECA_MODE + end + + def expdate(creditcard) + "#{format(creditcard.year, :four_digits)}#{format(creditcard.month, :two_digits)}" + end + + def parse(body) + response = {} + + root = REXML::Document.new(body).root + + response[:success] = (root.attributes['valor'] == 'OK') + response[:date] = root.attributes['fecha'] + response[:operation_number] = root.attributes['numeroOperacion'] + response[:message] = root.attributes['valor'] + + if root.elements['OPERACION'] + response[:operation_type] = root.elements['OPERACION'].attributes['tipo'] + response[:amount] = root.elements['OPERACION/importe'].text.strip + end + + response[:description] = root.elements['OPERACION/descripcion'].text if root.elements['OPERACION/descripcion'] + response[:authorization_number] = root.elements['OPERACION/numeroAutorizacion'].text if root.elements['OPERACION/numeroAutorizacion'] + response[:reference] = root.elements['OPERACION/referencia'].text if root.elements['OPERACION/referencia'] + response[:pan] = root.elements['OPERACION/pan'].text if root.elements['OPERACION/pan'] + + if root.elements['ERROR'] + response[:error_code] = root.elements['ERROR/codigo'].text + response[:error_message] = root.elements['ERROR/descripcion'].text + else + if root.elements['OPERACION'].attributes['numeroOperacion'] == '000' + if(root.elements['OPERACION/numeroAutorizacion']) + response[:authorization] = root.elements['OPERACION/numeroAutorizacion'].text + end + else + response[:authorization] = root.attributes['numeroOperacion'] + end + end + + return response + rescue REXML::ParseException => e + response[:success] = false + response[:message] = 'Unable to parse the response.' + response[:error_message] = e.message + response + end + + def commit(action, parameters) + parameters.merge!( + 'Cifrado' => CECA_ENCRIPTION, + 'Firma' => generate_signature(action, parameters), + 'Exponente' => CECA_DECIMALS, + 'MerchantID' => options[:merchant_id], + 'AcquirerBIN' => options[:acquirer_bin], + 'TerminalID' => options[:terminal_id] + ) + url = (test? ? self.test_url : self.live_url) + "/tpvweb/#{action}.action" + xml = ssl_post("#{url}?", post_data(parameters)) + response = parse(xml) + Response.new( + response[:success], + message_from(response), + response, + :test => test?, + :authorization => build_authorization(response), + :error_code => response[:error_code] + ) + end + + def message_from(response) + if response[:message] == 'ERROR' && response[:error_message] + response[:error_message] + elsif response[:error_message] + "#{response[:message]} #{response[:error_message]}" + else + response[:message] + end + end + + def post_data(params) + return nil unless params + + params.map do |key, value| + next if value.blank? + if value.is_a?(Hash) + h = {} + value.each do |k, v| + h["#{key}.#{k}"] = v unless v.blank? + end + post_data(h) + else + "#{key}=#{CGI.escape(value.to_s)}" + end + end.compact.join('&') + end + + def build_authorization(response) + [response[:reference], response[:authorization]].join('|') + end + + def split_authorization(authorization) + authorization.split('|') + end + + def generate_signature(action, parameters) + signature_fields = case action + when CECA_ACTION_REFUND + options[:key].to_s + + options[:merchant_id].to_s + + options[:acquirer_bin].to_s + + options[:terminal_id].to_s + + parameters['Num_operacion'].to_s + + parameters['Importe'].to_s + + parameters['TipoMoneda'].to_s + + CECA_DECIMALS + + parameters['Referencia'].to_s + + CECA_ENCRIPTION + else + options[:key].to_s + + options[:merchant_id].to_s + + options[:acquirer_bin].to_s + + options[:terminal_id].to_s + + parameters['Num_operacion'].to_s + + parameters['Importe'].to_s + + parameters['TipoMoneda'].to_s + + CECA_DECIMALS + + CECA_ENCRIPTION + + CECA_NOTIFICATIONS_URL + + CECA_NOTIFICATIONS_URL + end + Digest::SHA2.hexdigest(signature_fields) + end + end + end +end diff --git a/lib/active_merchant/billing/gateways/cenpos.rb b/lib/active_merchant/billing/gateways/cenpos.rb new file mode 100644 index 00000000000..9ba37a7c00b --- /dev/null +++ b/lib/active_merchant/billing/gateways/cenpos.rb @@ -0,0 +1,327 @@ +require 'nokogiri' + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class CenposGateway < Gateway + self.display_name = 'CenPOS' + self.homepage_url = 'https://www.cenpos.com/' + + self.live_url = 'https://ww3.cenpos.net/6/transact.asmx' + + self.supported_countries = %w(AD AI AG AR AU AT BS BB BE BZ BM BR BN BG CA HR CY CZ DK DM EE FI FR DE GR GD GY HK HU IS IL IT JP LV LI LT LU MY MT MX MC MS NL PA PL PT KN LC MF VC SM SG SK SI ZA ES SR SE CH TR GB US UY) + self.default_currency = 'USD' + self.money_format = :dollars + self.supported_cardtypes = [:visa, :master, :american_express, :discover] + + def initialize(options={}) + requires!(options, :merchant_id, :password, :user_id) + super + end + + def purchase(amount, payment_method, options={}) + post = {} + add_invoice(post, amount, options) + add_payment_method(post, payment_method) + add_customer_data(post, options) + + commit('Sale', post) + end + + def authorize(amount, payment_method, options={}) + post = {} + add_invoice(post, amount, options) + add_payment_method(post, payment_method) + add_customer_data(post, options) + + commit('Auth', post) + end + + def capture(amount, authorization, options={}) + post = {} + add_invoice(post, amount, options) + add_reference(post, authorization) + add_customer_data(post, options) + + commit('SpecialForce', post) + end + + def void(authorization, options={}) + post = {} + add_void_required_elements(post) + add_reference(post, authorization) + add_remembered_amount(post, authorization) + add_tax(post, options) + add_order_id(post, options) + + commit('Void', post) + end + + def refund(amount, authorization, options={}) + post = {} + add_invoice(post, amount, options) + add_reference(post, authorization) + add_customer_data(post, options) + + commit('SpecialReturn', post) + end + + def credit(amount, payment_method, options={}) + post = {} + add_invoice(post, amount, options) + add_payment_method(post, payment_method) + + commit('Credit', post) + end + + def verify(credit_card, options={}) + MultiResponse.run(:use_first_response) do |r| + r.process { authorize(100, credit_card, options) } + r.process(:ignore_result) { void(r.authorization, options) } + end + end + + def supports_scrubbing? + true + end + + def scrub(transcript) + transcript. + gsub(%r(()[^<]+(<))i, '\1[FILTERED]\2'). + gsub(%r(()[^<]+(<))i, '\1[FILTERED]\2'). + gsub(%r(()[^<]+(<))i, '\1[FILTERED]\2') + end + + private + + def add_invoice(post, money, options) + post[:Amount] = amount(money) + post[:CurrencyCode] = options[:currency] || currency(money) + post[:InvoiceDetail] = options[:invoice_detail] if options[:invoice_detail] + post[:CustomerCode] = options[:customer_code] if options[:customer_code] + add_order_id(post, options) + add_tax(post, options) + end + + def add_payment_method(post, payment_method) + post[:NameOnCard] = payment_method.name + post[:CardNumber] = payment_method.number + post[:CardVerificationNumber] = payment_method.verification_value + post[:CardExpirationDate] = format(payment_method.month, :two_digits) + format(payment_method.year, :two_digits) + post[:CardLastFourDigits] = payment_method.last_digits + post[:MagneticData] = payment_method.track_data if payment_method.track_data + end + + def add_customer_data(post, options) + if(billing_address = (options[:billing_address] || options[:address])) + post[:CustomerEmailAddress] = billing_address[:email] + post[:CustomerPhone] = billing_address[:phone] + post[:CustomerBillingAddress] = billing_address[:address1] + post[:CustomerCity] = billing_address[:city] + post[:CustomerState] = billing_address[:state] + post[:CustomerZipCode] = billing_address[:zip] + end + end + + def add_void_required_elements(post) + post[:GeoLocationInformation] = nil + post[:IMEI] = nil + end + + def add_order_id(post, options) + post[:InvoiceNumber] = options[:order_id] + end + + def add_tax(post, options) + post[:TaxAmount] = amount(options[:tax] || 0) + end + + def add_reference(post, authorization) + reference_number, last_four_digits = split_authorization(authorization) + post[:ReferenceNumber] = reference_number + post[:CardLastFourDigits] = last_four_digits + end + + def add_remembered_amount(post, authorization) + post[:Amount] = split_authorization(authorization).last + end + + def commit(action, post) + post[:MerchantId] = @options[:merchant_id] + post[:Password] = @options[:password] + post[:UserId] = @options[:user_id] + post[:TransactionType] = action + + data = build_request(post) + begin + xml = ssl_post(self.live_url, data, headers) + raw = parse(xml) + rescue ActiveMerchant::ResponseError => e + if(e.response.code == '500' && e.response.body.start_with?(' 'identity', + 'Content-Type' => 'text/xml;charset=UTF-8', + 'SOAPAction' => 'http://tempuri.org/Transactional/ProcessCreditCard' + } + end + + def build_request(post) + xml = Builder::XmlMarkup.new :indent => 8 + xml.tag!('acr:MerchantId', post.delete(:MerchantId)) + xml.tag!('acr:Password', post.delete(:Password)) + xml.tag!('acr:UserId', post.delete(:UserId)) + post.sort.each do |field, value| + xml.tag!("acr1:#{field}", value) + end + envelope(xml.target!) + end + + def envelope(body) + <<-EOS + + + + + + #{body} + + + + + EOS + end + + def parse(xml) + response = {} + + doc = Nokogiri::XML(xml) + doc.remove_namespaces! + body = doc.xpath('//ProcessCreditCardResult') + body.children.each do |node| + if node.elements.size == 0 + response[node.name.underscore.to_sym] = node.text + else + node.elements.each do |childnode| + name = "#{node.name.underscore}_#{childnode.name.underscore}" + response[name.to_sym] = childnode.text + end + end + end unless doc.root.nil? + + response + end + + def success_from(response) + response == '0' + end + + def message_from(succeeded, response) + if succeeded + 'Succeeded' + else + response[:message] || 'Unable to read error message' + end + end + + STANDARD_ERROR_CODE_MAPPING = { + '211' => STANDARD_ERROR_CODE[:invalid_number], + '252' => STANDARD_ERROR_CODE[:invalid_expiry_date], + '257' => STANDARD_ERROR_CODE[:invalid_cvc], + '333' => STANDARD_ERROR_CODE[:expired_card], + '1' => STANDARD_ERROR_CODE[:card_declined], + '99' => STANDARD_ERROR_CODE[:processing_error], + } + + def authorization_from(request, response) + [ response[:reference_number], request[:CardLastFourDigits], request[:Amount] ].join('|') + end + + def split_authorization(authorization) + reference_number, last_four_digits, original_amount = authorization.split('|') + [reference_number, last_four_digits, original_amount] + end + + def error_code_from(succeeded, response) + succeeded ? nil : STANDARD_ERROR_CODE_MAPPING[response[:result]] + end + + def cvv_result_from_xml(xml) + ActiveMerchant::Billing::CVVResult.new(cvv_result_code(xml)) + end + + def avs_result_from_xml(xml) + ActiveMerchant::Billing::AVSResult.new(code: avs_result_code(xml)) + end + + def cvv_result_code(xml) + cvv = validation_result_element(xml, 'CVV') + return nil unless cvv + validation_result_matches?(*validation_result_element_text(cvv.parent)) ? 'M' : 'N' + end + + def avs_result_code(xml) + billing_address_elem = validation_result_element(xml, 'Billing Address') + zip_code_elem = validation_result_element(xml, 'Zip Code') + + return nil unless billing_address_elem && zip_code_elem + + billing_matches = avs_result_matches(billing_address_elem) + zip_matches = avs_result_matches(zip_code_elem) + + if billing_matches && zip_matches + 'D' + elsif !billing_matches && zip_matches + 'P' + elsif billing_matches && !zip_matches + 'B' + else + 'C' + end + end + + def avs_result_matches(elem) + validation_result_matches?(*validation_result_element_text(elem.parent)) + end + + def validation_result_element(xml, name) + doc = Nokogiri::XML(xml) + doc.remove_namespaces! + doc.at_xpath("//ParameterValidationResultList//ParameterValidationResult//Name[text() = '#{name}']") + end + + def validation_result_element_text(element) + result_text = element.elements.detect { |elem| + elem.name == 'Result' + }.children.detect(&:text).text + + result_text.split(';').collect(&:strip) + end + + def validation_result_matches?(present, match) + present.downcase.start_with?('present') && + match.downcase.start_with?('match') + end + end + end +end diff --git a/lib/active_merchant/billing/gateways/certo_direct.rb b/lib/active_merchant/billing/gateways/certo_direct.rb deleted file mode 100644 index a77fc780ab0..00000000000 --- a/lib/active_merchant/billing/gateways/certo_direct.rb +++ /dev/null @@ -1,277 +0,0 @@ -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - class CertoDirectGateway < Gateway - self.live_url = self.test_url = "https://secure.certodirect.com/gateway/process/v2" - - self.supported_countries = [ - "BE", "BG", "CZ", "DK", "DE", "EE", "IE", "EL", "ES", "FR", - "IT", "CY", "LV", "LT", "LU", "HU", "MT", "NL", "AT", "PL", - "PT", "RO", "SI", "SK", "FI", "SE", "GB" - ] - - self.supported_cardtypes = [:visa, :master, :american_express, :discover] - self.homepage_url = 'http://www.certodirect.com/' - self.display_name = 'CertoDirect' - - # Creates a new CertoDirectGateway - # - # The gateway requires that a valid login and password be passed - # in the +options+ hash. - # - # ==== Options - # - # * :login -- The CertoDirect Shop ID (REQUIRED) - # * :password -- The CertoDirect Shop Password. (REQUIRED) - # * :test -- +true+ or +false+. If true, perform transactions against the test server. - # Otherwise, perform transactions against the production server. - def initialize(options = {}) - requires!(options, :login, :password) - super - end - - # Perform a purchase, which is essentially an authorization and capture in a single operation. - # - # ==== Parameters - # - # * money -- The amount to be purchased as an Integer value in cents. - # * credit_card -- The CreditCard details for the transaction. - # * options -- A hash of optional parameters. - def purchase(money, credit_card, options = {}) - requires!(options, :email, :currency, :ip, :description) - - commit(build_sale_request(money, credit_card, options)) - end - - # Refund a transaction. - # - # This transaction indicates to the gateway that - # money should flow from the merchant to the customer. - # - # ==== Parameters - # - # * money -- The amount to be credited to the customer as an Integer value in cents. - # * identification -- The ID of the original order against which the refund is being issued. - # * options -- A hash of parameters. - def refund(money, identification, options = {}) - requires!(options, :reason) - - commit(build_refund_request(money, identification, options)) - end - - # Performs an authorization, which reserves the funds on the customer's credit card, but does not - # charge the card. - # - # ==== Parameters - # - # * money -- The amount to be authorized as an Integer value in cents. - # * credit_card -- The CreditCard details for the transaction. - # * options -- A hash of optional parameters. - def authorize(money, credit_card, options = {}) - requires!(options, :email, :currency, :ip, :description) - - commit(build_authorize_request(money, credit_card, options)) - end - - # Captures the funds from an authorized transaction. - # - # ==== Parameters - # - # * money -- The amount to be captured as an Integer value in cents. - # * identification -- The authorization returned from the previous authorize request. - def capture(money, identification, options = {}) - commit(build_capture_request(money, identification)) - end - - # Void a previous transaction - # - # ==== Parameters - # - # * money -- The amount to be captured as an Integer value in cents. - # * identification - The authorization returned from the previous authorize request. - def void(money, identification, options = {}) - commit(build_void_request(money, identification)) - end - - # Create a recurring payment. - # - # ==== Parameters - # - # * options -- A hash of parameters. - # - # ==== Options - # - def recurring(identification, options={}) - commit(build_recurring_request(identification, options)) - end - - - private - - def commit(request_xml) - begin - response = Hash.from_xml(ssl_post(self.live_url, request_xml, headers)) - Response.new(success?(response), - message(response), - response, - :test => test?, - :authorization => authorization(response)) - rescue ResponseError => e - raise e unless e.response.code == '403' - response = Hash.from_xml(e.response.body)['response'] - Response.new(false, message(response), {}, :test => test?) - end - end - - def build_sale_request(money, credit_card, options) - build_request_xml('Sale') do |xml| - add_order(xml, money, credit_card, options) - end - end - - def build_authorize_request(money, credit_card, options) - build_request_xml('Authorize') do |xml| - add_order(xml, money, credit_card, options) - end - end - - def build_refund_request(money, identification, options) - build_request_xml('Refund') do |xml| - add_reference_info(xml, money, identification, options) - xml.tag! 'reason', options[:reason] - end - end - - def build_capture_request(money, identification) - build_request_xml('Capture') do |xml| - add_reference_info(xml, money, identification, options) - end - end - - def build_void_request(money, identification) - build_request_xml('Void') do |xml| - add_reference_info(xml, money, identification, options) - end - end - - def build_recurring_request(identification, options) - build_request_xml('Sale') do |xml| - xml.tag! 'order' do |xml| - xml.tag!('test', 'true') if test? - xml.tag! 'initial_order_id', identification, :type => 'integer' - - add_order_details(xml, options[:amount], options) if has_any_order_details_key?(options) - add_address(xml, 'billing_address', options[:billing_address]) if options[:billing_address] - add_address(xml, 'shipping_address', options[:shipping_address]) if options[:shipping_address] - end - end - end - - def build_request_xml(type, &block) - xml = Builder::XmlMarkup.new(:indent => 2) - xml.tag! 'transaction' do - xml.tag! 'type', type - yield(xml) - end - xml.target! - end - - def add_order(xml, money, credit_card, options) - xml.tag! 'order' do - xml.tag!('test', 'true') if test? - - xml.tag!('return_url', options[:return_url]) if options[:return_url] - xml.tag!('cancel_url', options[:cancel_url]) if options[:cancel_url] - - xml.tag! 'payment_method_type', 'CreditCard' - xml.tag! 'payment_method' do - xml.tag! 'number', credit_card.number - xml.tag! 'exp_month', "%02i" % credit_card.month - xml.tag! 'exp_year', credit_card.year - xml.tag! 'holder', credit_card.name - xml.tag! 'verification_value', credit_card.verification_value - end - - add_order_details(xml, money, options) - add_address(xml, 'billing_address', options[:billing_address]) if options[:billing_address] - add_address(xml, 'shipping_address', options[:shipping_address]) if options[:shipping_address] - end - end - - def add_order_details(xml, money, options) - xml.tag! 'details' do - xml.tag!('amount', localized_amount(money, options[:currency]), :type => 'decimal') if money - xml.tag!('currency', options[:currency]) if options[:currency] - xml.tag!('email', options[:email]) if options[:email] - xml.tag!('ip', options[:ip]) if options[:ip] - xml.tag!('shipping', options[:shipping], :type => 'decimal') if options[:shipping] - xml.tag!('description', options[:description]) if options[:description] - end - end - - def add_reference_info(xml, money, identification, options) - xml.tag! 'order_id', identification, :type => 'integer' - xml.tag! 'amount', localized_amount(money, options[:currency]), :type => 'decimal' - end - - def add_address(xml, address_type, address) - xml.tag! address_type do - xml.tag! 'address', address[:address1] - xml.tag! 'city', address[:city] - xml.tag! 'country', address[:country] - xml.tag! 'first_name', address[:first_name] - xml.tag! 'last_name', address[:last_name] - xml.tag! 'state', address[:state] - xml.tag! 'phone', address[:phone] - xml.tag! 'zip', address[:zip] - end - end - - def has_any_order_details_key?(options) - [ :currency, :amount, :email, :ip, :shipping, :description ].any? do |key| - options.has_key?(key) - end - end - - def success?(response) - %w(completed forwarding).include?(state(response)) and - status(response) == 'success' - end - - def error?(response) - response['errors'] - end - - def state(response) - response["transaction"].try(:[], "state") - end - - def status(response) - response['transaction'].try(:[], 'response').try(:[], 'status') - end - - def authorization(response) - error?(response) ? nil : response["transaction"]["order"]['id'].to_s - end - - def message(response) - return response['errors'].join('; ') if error?(response) - - if state(response) == 'completed' - response["transaction"]["response"]["message"] - else - response['transaction']['message'] - end - end - - def headers - { 'authorization' => basic_auth, - 'Accept' => 'application/xml', - 'Content-Type' => 'application/xml' } - end - - def basic_auth - 'Basic ' + ["#{@options[:login]}:#{@options[:password]}"].pack('m').delete("\r\n") - end - end - end -end diff --git a/lib/active_merchant/billing/gateways/checkout.rb b/lib/active_merchant/billing/gateways/checkout.rb new file mode 100644 index 00000000000..bd6149b246d --- /dev/null +++ b/lib/active_merchant/billing/gateways/checkout.rb @@ -0,0 +1,214 @@ +require 'rubygems' +require 'nokogiri' + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class CheckoutGateway < Gateway + self.default_currency = 'USD' + self.money_format = :cents + + self.supported_countries = ['AD', 'AT', 'BE', 'BG', 'CH', 'CY', 'CZ', 'DE', 'DK', 'EE', 'ES', 'FO', 'FI', 'FR', 'GB', 'GI', 'GL', 'GR', 'HR', 'HU', 'IE', 'IS', 'IL', 'IT', 'LI', 'LT', 'LU', 'LV', 'MC', 'MT', 'NL', 'NO', 'PL', 'PT', 'RO', 'SE', 'SI', 'SM', 'SK', 'SJ', 'TR', 'VA'] + self.supported_cardtypes = [:visa, :master, :american_express, :diners_club] + + self.homepage_url = 'https://www.checkout.com/' + self.display_name = 'Checkout.com' + + self.live_url = 'https://api.checkout.com/Process/gateway.aspx' + + ACTIONS = { + 'purchase' => '1', + 'authorize' => '4', + 'capture' => '5', + 'refund' => '2', + 'void_purchase' => '3', + 'void_authorize' => '9', + 'void_capture' => '7' + } + + def initialize(options = {}) + requires!(options, :merchant_id, :password) + super + end + + def purchase(amount, payment_method, options) + commit('purchase', amount, options) do |xml| + add_credentials(xml, options) + add_invoice(xml, amount, options) + add_track_id(xml, options[:order_id] || generate_unique_id) + add_payment_method(xml, payment_method) + add_billing_info(xml, options) + add_shipping_info(xml, options) + add_user_defined_fields(xml, options) + add_other_fields(xml, options) + end + end + + def authorize(amount, payment_method, options) + commit('authorize', amount, options) do |xml| + add_credentials(xml, options) + add_invoice(xml, amount, options) + add_track_id(xml, options[:order_id] || generate_unique_id) + add_payment_method(xml, payment_method) + add_billing_info(xml, options) + add_shipping_info(xml, options) + add_user_defined_fields(xml, options) + add_other_fields(xml, options) + end + end + + def capture(amount, authorization, options = {}) + commit('capture', amount, options) do |xml| + add_credentials(xml, options) + add_reference(xml, authorization) + add_invoice(xml, amount, options) + add_user_defined_fields(xml, options) + add_other_fields(xml, options) + end + end + + def void(authorization, options = {}) + _, _, orig_action, amount, currency = split_authorization(authorization) + commit("void_#{orig_action}") do |xml| + add_credentials(xml, options) + add_invoice(xml, amount.to_i, options.merge(currency: currency)) + add_reference(xml, authorization) + end + end + + def refund(amount, authorization, options = {}) + commit('refund') do |xml| + add_credentials(xml, options) + add_invoice(xml, amount.to_i, options) + add_reference(xml, authorization) + end + end + + def verify(credit_card, options={}) + MultiResponse.run(:use_first_response) do |r| + r.process { authorize(100, credit_card, options) } + r.process(:ignore_result) { void(r.authorization, options) } + end + end + + private + + def add_credentials(xml, options) + xml.merchantid_ @options[:merchant_id] + xml.password_ @options[:password] + end + + def add_invoice(xml, amount, options) + xml.bill_amount_ amount(amount) + xml.bill_currencycode_ options[:currency] || currency(amount) + end + + def add_payment_method(xml, payment_method) + xml.bill_cardholder_ payment_method.name + xml.bill_cc_ payment_method.number + xml.bill_expmonth_ format(payment_method.month, :two_digits) + xml.bill_expyear_ format(payment_method.year, :four_digits) + if payment_method.verification_value? + xml.bill_cvv2_ payment_method.verification_value + end + end + + def add_billing_info(xml, options) + if options[:billing_address] + xml.bill_address_ options[:billing_address][:address1] + xml.bill_city_ options[:billing_address][:city] + xml.bill_state_ options[:billing_address][:state] + xml.bill_postal_ options[:billing_address][:zip] + xml.bill_country_ options[:billing_address][:country] + xml.bill_phone_ options[:billing_address][:phone] + end + end + + def add_shipping_info(xml, options) + if options[:shipping_address] + xml.ship_address_ options[:shipping_address][:address1] + xml.ship_address2_ options[:shipping_address][:address2] + xml.ship_city_ options[:shipping_address][:city] + xml.ship_state_ options[:shipping_address][:state] + xml.ship_postal_ options[:shipping_address][:zip] + xml.ship_country_ options[:shipping_address][:country] + xml.ship_phone_ options[:shipping_address][:phone] + end + end + + def add_user_defined_fields(xml, options) + xml.udf1_ options[:udf1] + xml.udf2_ options[:udf2] + xml.udf3_ options[:udf3] + xml.udf4_ options[:udf4] + xml.udf5_ options[:udf5] + end + + def add_other_fields(xml, options) + xml.bill_email_ options[:email] + xml.bill_customerip_ options[:ip] + xml.merchantcustomerid_ options[:customer] + xml.descriptor_name options[:descriptor_name] + xml.descriptor_city options[:descriptor_city] + end + + def add_reference(xml, authorization) + transid, trackid, _, _, _ = split_authorization(authorization) + xml.transid transid + add_track_id(xml, trackid) + end + + def add_track_id(xml, trackid) + xml.trackid(trackid) if trackid + end + + def commit(action, amount=nil, options={}, &builder) + response = parse_xml(ssl_post(live_url, build_xml(action, &builder))) + Response.new( + (response[:responsecode] == '0'), + (response[:result] || response[:error_text] || 'Unknown Response'), + response, + authorization: authorization_from(response, action, amount, options), + test: test? + ) + end + + def build_xml(action) + Nokogiri::XML::Builder.new do |xml| + xml.request do + xml.action_ ACTIONS[action] + yield xml + end + end.to_xml + end + + def parse_xml(xml) + response = {} + + Nokogiri::XML(CGI.unescapeHTML(xml)).xpath('//response').children.each do |node| + if node.text? + next + elsif node.elements.size == 0 + response[node.name.downcase.to_sym] = node.text + else + node.elements.each do |childnode| + name = "#{node.name.downcase}_#{childnode.name.downcase}" + response[name.to_sym] = childnode.text + end + end + end + + response + end + + def authorization_from(response, action, amount, options) + currency = options[:currency] || currency(amount) + [response[:tranid], response[:trackid], action, amount, currency].join('|') + end + + def split_authorization(authorization) + transid, trackid, action, amount, currency = authorization.split('|') + [transid, trackid, action, amount, currency] + end + end + end +end diff --git a/lib/active_merchant/billing/gateways/checkout_v2.rb b/lib/active_merchant/billing/gateways/checkout_v2.rb new file mode 100644 index 00000000000..0f916158a22 --- /dev/null +++ b/lib/active_merchant/billing/gateways/checkout_v2.rb @@ -0,0 +1,270 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class CheckoutV2Gateway < Gateway + self.display_name = 'Checkout.com Unified Payments' + self.homepage_url = 'https://www.checkout.com/' + self.live_url = 'https://api.checkout.com' + self.test_url = 'https://api.sandbox.checkout.com' + + self.supported_countries = ['AD', 'AE', 'AT', 'BE', 'BG', 'CH', 'CY', 'CZ', 'DE', 'DK', 'EE', 'ES', 'FO', 'FI', 'FR', 'GB', 'GI', 'GL', 'GR', 'HR', 'HU', 'IE', 'IS', 'IL', 'IT', 'LI', 'LT', 'LU', 'LV', 'MC', 'MT', 'NL', 'NO', 'PL', 'PT', 'RO', 'SE', 'SI', 'SM', 'SK', 'SJ', 'TR', 'VA'] + self.default_currency = 'USD' + self.money_format = :cents + self.supported_cardtypes = [:visa, :master, :american_express, :diners_club, :maestro, :discover] + + def initialize(options = {}) + requires!(options, :secret_key) + super + end + + def purchase(amount, payment_method, options = {}) + multi = MultiResponse.run do |r| + r.process { authorize(amount, payment_method, options) } + r.process { capture(amount, r.authorization, options) } + end + + merged_params = multi.responses.map(&:params).reduce({}, :merge) + succeeded = success_from(merged_params) + + response(:purchase, succeeded, merged_params) + end + + def authorize(amount, payment_method, options = {}) + post = {} + post[:capture] = false + add_invoice(post, amount, options) + add_payment_method(post, payment_method, options) + add_customer_data(post, options) + add_transaction_data(post, options) + add_3ds(post, options) + + commit(:authorize, post) + end + + def capture(amount, authorization, options = {}) + post = {} + add_invoice(post, amount, options) + add_customer_data(post, options) + + commit(:capture, post, authorization) + end + + def void(authorization, _options = {}) + post = {} + commit(:void, post, authorization) + end + + def refund(amount, authorization, options = {}) + post = {} + add_invoice(post, amount, options) + add_customer_data(post, options) + + commit(:refund, post, authorization) + end + + def verify(credit_card, options = {}) + MultiResponse.run(:use_first_response) do |r| + r.process { authorize(100, credit_card, options) } + r.process(:ignore_result) { void(r.authorization, options) } + end + end + + def verify_payment(authorization, option={}) + commit(:verify_payment, authorization) + end + + def supports_scrubbing? + true + end + + def scrub(transcript) + transcript. + gsub(/(Authorization: )[^\\]*/i, '\1[FILTERED]'). + gsub(/("number\\":\\")\d+/, '\1[FILTERED]'). + gsub(/("cvv\\":\\")\d+/, '\1[FILTERED]') + end + + private + + def add_invoice(post, money, options) + post[:amount] = localized_amount(money, options[:currency]) + post[:reference] = options[:order_id] + post[:currency] = options[:currency] || currency(money) + if options[:descriptor_name] || options[:descriptor_city] + post[:billing_descriptor] = {} + post[:billing_descriptor][:name] = options[:descriptor_name] if options[:descriptor_name] + post[:billing_descriptor][:city] = options[:descriptor_city] if options[:descriptor_city] + end + post[:metadata] = {} + post[:metadata][:udf5] = application_id || 'ActiveMerchant' + end + + def add_payment_method(post, payment_method, options) + post[:source] = {} + post[:source][:type] = 'card' + post[:source][:name] = payment_method.name + post[:source][:number] = payment_method.number + post[:source][:cvv] = payment_method.verification_value + post[:source][:expiry_year] = format(payment_method.year, :four_digits) + post[:source][:expiry_month] = format(payment_method.month, :two_digits) + post[:source][:stored] = 'true' if options[:card_on_file] == true + end + + def add_customer_data(post, options) + post[:customer] = {} + post[:customer][:email] = options[:email] || nil + post[:payment_ip] = options[:ip] if options[:ip] + address = options[:billing_address] + if address && post[:source] + post[:source][:billing_address] = {} + post[:source][:billing_address][:address_line1] = address[:address1] unless address[:address1].blank? + post[:source][:billing_address][:address_line2] = address[:address2] unless address[:address2].blank? + post[:source][:billing_address][:city] = address[:city] unless address[:city].blank? + post[:source][:billing_address][:state] = address[:state] unless address[:state].blank? + post[:source][:billing_address][:country] = address[:country] unless address[:country].blank? + post[:source][:billing_address][:zip] = address[:zip] unless address[:zip].blank? + end + end + + def add_transaction_data(post, options = {}) + post[:payment_type] = 'Regular' if options[:transaction_indicator] == 1 + post[:payment_type] = 'Recurring' if options[:transaction_indicator] == 2 + post[:payment_type] = 'MOTO' if options[:transaction_indicator] == 3 + post[:previous_payment_id] = options[:previous_charge_id] if options[:previous_charge_id] + end + + def add_3ds(post, options) + if options[:three_d_secure] || options[:execute_threed] + post[:'3ds'] = {} + post[:'3ds'][:enabled] = true + post[:success_url] = options[:callback_url] if options[:callback_url] + post[:failure_url] = options[:callback_url] if options[:callback_url] + end + + if options[:three_d_secure] + post[:'3ds'][:eci] = options[:three_d_secure][:eci] if options[:three_d_secure][:eci] + post[:'3ds'][:cryptogram] = options[:three_d_secure][:cavv] if options[:three_d_secure][:cavv] + post[:'3ds'][:version] = options[:three_d_secure][:version] if options[:three_d_secure][:version] + post[:'3ds'][:xid] = options[:three_d_secure][:ds_transaction_id] || options[:three_d_secure][:xid] + end + end + + def commit(action, post, authorization = nil) + begin + raw_response = (action == :verify_payment ? ssl_get("#{base_url}/payments/#{post}", headers) : ssl_post(url(post, action, authorization), post.to_json, headers)) + response = parse(raw_response) + if action == :capture && response.key?('_links') + response['id'] = response['_links']['payment']['href'].split('/')[-1] + end + rescue ResponseError => e + raise unless e.response.code.to_s =~ /4\d\d/ + response = parse(e.response.body) + end + + succeeded = success_from(response) + + response(action, succeeded, response) + end + + def response(action, succeeded, response) + successful_response = succeeded && action == :purchase || action == :authorize + avs_result = successful_response ? avs_result(response) : nil + cvv_result = successful_response ? cvv_result(response) : nil + + Response.new( + succeeded, + message_from(succeeded, response), + response, + authorization: authorization_from(response), + error_code: error_code_from(succeeded, response), + test: test?, + avs_result: avs_result, + cvv_result: cvv_result + ) + end + + def headers + { + 'Authorization' => @options[:secret_key], + 'Content-Type' => 'application/json;charset=UTF-8', + } + end + + def url(_post, action, authorization) + if action == :authorize + "#{base_url}/payments" + elsif action == :capture + "#{base_url}/payments/#{authorization}/captures" + elsif action == :refund + "#{base_url}/payments/#{authorization}/refunds" + elsif action == :void + "#{base_url}/payments/#{authorization}/voids" + else + "#{base_url}/payments/#{authorization}/#{action}" + end + end + + def base_url + test? ? test_url : live_url + end + + def avs_result(response) + response['source'] && response['source']['avs_check'] ? AVSResult.new(code: response['source']['avs_check']) : nil + end + + def cvv_result(response) + response['source'] && response['source']['cvv_check'] ? CVVResult.new(response['source']['cvv_check']) : nil + end + + def parse(body) + JSON.parse(body) + rescue JSON::ParserError + { + 'message' => 'Invalid JSON response received from Checkout.com Unified Payments Gateway. Please contact Checkout.com if you continue to receive this message.', + 'raw_response' => scrub(body) + } + end + + def success_from(response) + response['response_summary'] == 'Approved' || response['approved'] == true || !response.key?('response_summary') && response.key?('action_id') + end + + def message_from(succeeded, response) + if succeeded + 'Succeeded' + elsif response['error_type'] + response['error_type'] + ': ' + response['error_codes'].first + else + response['response_summary'] || response['response_code'] || 'Unable to read error message' + end + end + + STANDARD_ERROR_CODE_MAPPING = { + '20014' => STANDARD_ERROR_CODE[:invalid_number], + '20100' => STANDARD_ERROR_CODE[:invalid_expiry_date], + '20054' => STANDARD_ERROR_CODE[:expired_card], + '40104' => STANDARD_ERROR_CODE[:incorrect_cvc], + '40108' => STANDARD_ERROR_CODE[:incorrect_zip], + '40111' => STANDARD_ERROR_CODE[:incorrect_address], + '20005' => STANDARD_ERROR_CODE[:card_declined], + '20088' => STANDARD_ERROR_CODE[:processing_error], + '20001' => STANDARD_ERROR_CODE[:call_issuer], + '30004' => STANDARD_ERROR_CODE[:pickup_card] + } + + def authorization_from(raw) + raw['id'] + end + + def error_code_from(succeeded, response) + return if succeeded + if response['error_type'] && response['error_codes'] + "#{response['error_type']}: #{response['error_codes'].join(', ')}" + elsif response['error_type'] + response['error_type'] + else + STANDARD_ERROR_CODE_MAPPING[response['response_code']] + end + end + end + end +end diff --git a/lib/active_merchant/billing/gateways/citrus_pay.rb b/lib/active_merchant/billing/gateways/citrus_pay.rb new file mode 100644 index 00000000000..00ab762d196 --- /dev/null +++ b/lib/active_merchant/billing/gateways/citrus_pay.rb @@ -0,0 +1,22 @@ +module ActiveMerchant + module Billing + class CitrusPayGateway < Gateway + include MastercardGateway + + class_attribute :live_na_url, :live_ap_url, :test_na_url, :test_ap_url + + self.test_na_url = 'https://test-gateway.mastercard.com/api/rest/version/39/' + self.test_ap_url = 'https://test-gateway.mastercard.com/api/rest/version/39/' + + self.live_na_url = 'https://na-gateway.mastercard.com/api/rest/version/39/' + self.live_ap_url = 'https://ap-gateway.mastercard.com/api/rest/version/39/' + + self.display_name = 'Citrus Pay' + self.homepage_url = 'http://www.citruspay.com/' + self.supported_countries = %w(AR AU BR FR DE HK MX NZ SG GB US) + self.default_currency = 'USD' + self.supported_cardtypes = [:visa, :master, :american_express, :discover, :diners_club, :jcb, :maestro] + + end + end +end diff --git a/lib/active_merchant/billing/gateways/clearhaus.rb b/lib/active_merchant/billing/gateways/clearhaus.rb new file mode 100644 index 00000000000..b53d792a4f4 --- /dev/null +++ b/lib/active_merchant/billing/gateways/clearhaus.rb @@ -0,0 +1,220 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class ClearhausGateway < Gateway + self.test_url = 'https://gateway.test.clearhaus.com' + self.live_url = 'https://gateway.clearhaus.com' + + self.supported_countries = ['DK', 'NO', 'SE', 'FI', 'DE', 'CH', 'NL', 'AD', 'AT', 'BE', 'BG', 'HR', 'CY', 'CZ', 'FO', 'GL', 'EE', 'FR', 'GR', + 'HU', 'IS', 'IE', 'IT', 'LV', 'LI', 'LT', 'LU', 'MT', 'PL', 'PT', 'RO', 'SK', 'SI', 'ES', 'GB'] + + self.default_currency = 'EUR' + self.currencies_without_fractions = %w(BIF BYR DJF GNF JPY KMF KRW PYG RWF VND VUV XAF XOF XPF) + self.supported_cardtypes = [:visa, :master] + + self.homepage_url = 'https://www.clearhaus.com' + self.display_name = 'Clearhaus' + self.money_format = :cents + + ACTION_CODE_MESSAGES = { + 20000 => 'Approved', + 40000 => 'General input error', + 40110 => 'Invalid card number', + 40120 => 'Invalid CSC', + 40130 => 'Invalid expire date', + 40135 => 'Card expired', + 40140 => 'Invalid currency', + 40200 => 'Clearhaus rule violation', + 40300 => '3-D Secure problem', + 40310 => '3-D Secure authentication failure', + 40400 => 'Backend problem', + 40410 => 'Declined by issuer or card scheme', + 40411 => 'Card restricted', + 40412 => 'Card lost or stolen', + 40413 => 'Insufficient funds', + 40414 => 'Suspected fraud', + 40415 => 'Amount limit exceeded', + 50000 => 'Clearhaus error' + } + + def initialize(options={}) + requires!(options, :api_key) + options[:private_key] = options[:private_key].strip if options[:private_key] + super + end + + def purchase(amount, payment, options={}) + MultiResponse.run(:use_first_response) do |r| + r.process { authorize(amount, payment, options) } + r.process { capture(amount, r.authorization, options) } + end + end + + def authorize(amount, payment, options={}) + post = {} + add_invoice(post, amount, options) + + action = if payment.respond_to?(:number) + add_payment(post, payment) + '/authorizations' + elsif payment.kind_of?(String) + "/cards/#{payment}/authorizations" + else + raise ArgumentError.new("Unknown payment type #{payment.inspect}") + end + + post[:recurring] = options[:recurring] if options[:recurring] + post[:card][:pares] = options[:pares] if options[:pares] + + commit(action, post) + end + + def capture(amount, authorization, options={}) + post = {} + add_invoice(post, amount, options) + + commit("/authorizations/#{authorization}/captures", post) + end + + def refund(amount, authorization, options={}) + post = {} + add_amount(post, amount, options) + + commit("/authorizations/#{authorization}/refunds", post) + end + + def void(authorization, options = {}) + commit("/authorizations/#{authorization}/voids", options) + end + + def verify(credit_card, options={}) + MultiResponse.run(:use_first_response) do |r| + r.process { authorize(0, credit_card, options) } + r.process(:ignore_result) { void(r.authorization, options) } + end + end + + def store(credit_card, options={}) + post = {} + add_payment(post, credit_card) + + commit('/cards', post) + end + + def supports_scrubbing? + true + end + + def scrub(transcript) + transcript. + gsub(%r((Authorization: Basic )[\w=]+), '\1[FILTERED]'). + gsub(%r((&?card(?:\[|%5B)csc(?:\]|%5D)=)[^&]*)i, '\1[FILTERED]'). + gsub(%r((&?card(?:\[|%5B)pan(?:\]|%5D)=)[^&]*)i, '\1[FILTERED]') + end + + private + + def add_invoice(post, money, options) + add_amount(post, money, options) + post[:reference] = options[:order_id] if options[:order_id] + post[:text_on_statement] = options[:text_on_statement] if options[:text_on_statement] + end + + def add_amount(post, amount, options) + post[:amount] = localized_amount(amount, options[:currency] || default_currency) + post[:currency] = (options[:currency] || default_currency) + end + + def add_payment(post, payment) + card = {} + card[:pan] = payment.number + card[:expire_month] = '%02d'% payment.month + card[:expire_year] = payment.year + + if payment.verification_value? + card[:csc] = payment.verification_value + end + + post[:card] = card if card.any? + end + + def headers(api_key) + { + 'Authorization' => 'Basic ' + Base64.strict_encode64("#{api_key}:"), + 'User-Agent' => "Clearhaus ActiveMerchantBindings/#{ActiveMerchant::VERSION}" + } + end + + def parse(body) + JSON.parse(body) rescue body + end + + def commit(action, parameters) + url = (test? ? test_url : live_url) + action + headers = headers(@options[:api_key]) + body = parameters.to_query + + if @options[:signing_key] && @options[:private_key] + begin + headers['Signature'] = generate_signature(body) + rescue OpenSSL::PKey::RSAError => e + return Response.new(false, e.message) + end + end + + response = begin + parse(ssl_post(url, body, headers)) + rescue ResponseError => e + raise unless(e.response.code.to_s =~ /400/) + parse(e.response.body) + end + + Response.new( + success_from(response), + message_from(response), + response, + authorization: authorization_from(action, response), + test: test?, + error_code: error_code_from(response) + ) + end + + def success_from(response) + (response && (response['status']['code'] == 20000)) + end + + def message_from(response) + default_message = ACTION_CODE_MESSAGES[response['status']['code']] + + if success_from(response) + default_message + else + (response['status']['message'] || default_message) + end + end + + def authorization_from(action, response) + id_of_auth_for_capture(action) || response['id'] + end + + def id_of_auth_for_capture(action) + match = action.match(/authorizations\/(.+)\/captures/) + return nil unless match + + match.captures.first + end + + def generate_signature(body) + key = OpenSSL::PKey::RSA.new(@options[:private_key]) + hex = key.sign(OpenSSL::Digest.new('sha256'), body).unpack('H*').first + + "#{@options[:signing_key]} RS256-hex #{hex}" + end + + def error_code_from(response) + unless success_from(response) + response['status']['code'] + end + end + end + end +end diff --git a/lib/active_merchant/billing/gateways/commercegate.rb b/lib/active_merchant/billing/gateways/commercegate.rb new file mode 100644 index 00000000000..c1d6d5bc1f4 --- /dev/null +++ b/lib/active_merchant/billing/gateways/commercegate.rb @@ -0,0 +1,142 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class CommercegateGateway < Gateway + self.test_url = self.live_url = 'https://secure.commercegate.com/gateway/nvp' + + self.supported_countries = %w( + AD AT AX BE BG CH CY CZ DE DK ES FI FR GB GG + GI GR HR HU IE IM IS IT JE LI LT LU LV MC MT + NL NO PL PT RO SE SI SK VA + ) + + self.money_format = :dollars + self.default_currency = 'EUR' + self.supported_cardtypes = [:visa, :master, :american_express, :discover] + self.homepage_url = 'http://www.commercegate.com/' + self.display_name = 'CommerceGate' + + def initialize(options = {}) + requires!(options, :login, :password, :site_id, :offer_id) + super + end + + def authorize(money, creditcard, options = {}) + post = {} + add_creditcard(post, creditcard) + add_auth_purchase_options(post, money, options) + commit('AUTH', post) + end + + def capture(money, authorization, options = {}) + post = {} + post[:currencyCode] = (options[:currency] || currency(money)) + post[:amount] = amount(money) + post[:transID] = authorization + commit('CAPTURE', post) + end + + def purchase(money, creditcard, options = {}) + post = {} + add_creditcard(post, creditcard) + add_auth_purchase_options(post, money, options) + commit('SALE', post) + end + + def refund(money, identification, options = {}) + post = {} + post[:currencyCode] = options[:currency] || currency(money) + post[:amount] = amount(money) + post[:transID] = identification + commit('REFUND', post) + end + + def void(identification, options = {}) + post = {} + post[:transID] = identification + commit('VOID_AUTH', post) + end + + private + + def add_address(post, address) + if address + post[:address] = address[:address1] + post[:city] = address[:city] + post[:state] = address[:state] + post[:postalCode] = address[:zip] + end + post[:countryCode] = ((address && address[:country]) || 'US') + end + + def add_auth_purchase_options(post, money, options) + add_address(post, options[:address]) + + post[:customerIP] = options[:ip] || '127.0.0.1' + post[:amount] = amount(money) + post[:email] = options[:email] || 'unknown@example.com' + post[:currencyCode]= options[:currency] || currency(money) + post[:merchAcct] = options[:merchant] + end + + def add_creditcard(params, creditcard) + params[:firstName] = creditcard.first_name + params[:lastName] = creditcard.last_name + params[:cardNumber] = creditcard.number + params[:expiryMonth] = creditcard.month + params[:expiryYear] = creditcard.year + params[:cvv] = creditcard.verification_value if creditcard.verification_value? + end + + def commit(action, parameters) + parameters[:apiUsername] = @options[:login] + parameters[:apiPassword] = @options[:password] + parameters[:siteID] = @options[:site_id] + parameters[:offerID] = @options[:offer_id] + parameters[:action] = action + + response = parse(ssl_post(self.live_url, post_data(parameters))) + + Response.new( + successful?(response), + message_from(response), + response, + authorization: response['transID'], + test: test?, + avs_result: {code: response['avsCode']}, + cvv_result: response['cvvCode'] + ) + end + + def parse(body) + results = {} + + body.split(/\&/).each do |pair| + key, val = pair.split(%r{=}) + results[key] = CGI.unescape(val) + end + + results + end + + def successful?(response) + response['returnCode'] == '0' + end + + def message_from(response) + if response['returnText'].present? + response['returnText'] + else + 'Invalid response received from the CommerceGate API. ' \ + 'Please contact CommerceGate support if you continue to receive this message. ' \ + "(The raw response returned by the API was #{response.inspect})" + end + end + + def post_data(parameters) + parameters.collect do |key, value| + "#{key}=#{CGI.escape(value.to_s)}" + end.join('&') + end + end + end +end diff --git a/lib/active_merchant/billing/gateways/conekta.rb b/lib/active_merchant/billing/gateways/conekta.rb new file mode 100644 index 00000000000..06aad777b09 --- /dev/null +++ b/lib/active_merchant/billing/gateways/conekta.rb @@ -0,0 +1,228 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class ConektaGateway < Gateway + self.live_url = 'https://api.conekta.io/' + + self.supported_countries = ['MX'] + self.supported_cardtypes = [:visa, :master, :american_express, :carnet] + self.homepage_url = 'https://conekta.io/' + self.display_name = 'Conekta Gateway' + self.money_format = :cents + self.default_currency = 'MXN' + + def initialize(options = {}) + requires!(options, :key) + options[:version] ||= '1.0.0' + super + end + + def purchase(money, payment_source, options = {}) + post = {} + + add_order(post, money, options) + add_payment_source(post, payment_source, options) + add_details_data(post, options) + + commit(:post, 'charges', post, options) + end + + def authorize(money, payment_source, options = {}) + post = {} + + add_order(post, money, options) + add_payment_source(post, payment_source, options) + add_details_data(post, options) + + post[:capture] = false + commit(:post, 'charges', post, options) + end + + def capture(money, identifier, options = {}) + post = {} + + post[:order_id] = identifier + add_order(post, money, options) + + commit(:post, "charges/#{identifier}/capture", post, options) + end + + def refund(money, identifier, options) + post = {} + + post[:order_id] = identifier + add_order(post, money, options) + + commit(:post, "charges/#{identifier}/refund", post, options) + end + + def void(identifier, options = {}) + post = {} + commit(:post, "charges/#{identifier}/void", post, options) + end + + def supports_scrubbing + true + end + + def scrub(transcript) + transcript. + gsub(%r((Authorization: Basic )\w+), '\1[FILTERED]'). + gsub(%r((&?card%5Bnumber%5D=)[^&]*)i, '\1[FILTERED]'). + gsub(%r((&?card%5Bcvc%5D=)[^&]*)i, '\1[FILTERED]') + end + + private + + def add_order(post, money, options) + post[:description] = options[:description] || 'Active Merchant Purchase' + post[:reference_id] = options[:order_id] if options[:order_id] + post[:currency] = (options[:currency] || currency(money)).downcase + post[:monthly_installments] = options[:monthly_installments] if options[:monthly_installments] + post[:amount] = amount(money) + end + + def add_details_data(post, options) + details = {} + details[:name] = options[:customer] || (options[:billing_address][:name] if options[:billing_address]) + details[:phone] = options[:phone] || (options[:billing_address][:phone] if options[:billing_address]) + details[:email] = options[:email] if options[:email] + details[:ip] = options[:ip] if options[:ip] + add_billing_address(details, options) + add_line_items(details, options) + add_shipment(details, options) + post[:details] = details + post[:device_fingerprint] = options[:device_fingerprint] if options[:device_fingerprint] + end + + def add_shipment(post, options) + shipment = {} + shipment[:carrier] = options[:carrier] if options[:carrier] + shipment[:service] = options[:service] if options[:service] + shipment[:tracking_number] = options[:tracking_number] if options[:tracking_number] + shipment[:price] = options[:price] if options[:price] + add_shipment_address(shipment, options) + post[:shipment] = shipment + end + + def add_shipment_address(post, options) + if(address = options[:shipping_address]) + post[:address] = {} + post[:address][:street1] = address[:address1] if address[:address1] + post[:address][:street2] = address[:address2] if address[:address2] + post[:address][:street3] = address[:address3] if address[:address3] + post[:address][:city] = address[:city] if address[:city] + post[:address][:state] = address[:state] if address[:state] + post[:address][:country] = address[:country] if address[:country] + post[:address][:zip] = address[:zip] if address[:zip] + end + end + + def add_line_items(post, options) + post[:line_items] = (options[:line_items] || []).collect do |line_item| + line_item + end + end + + def add_billing_address(post, options) + if(address = (options[:billing_address] || options[:address])) + post[:billing_address] = {} + post[:billing_address][:street1] = address[:address1] if address[:address1] + post[:billing_address][:street2] = address[:address2] if address[:address2] + post[:billing_address][:street3] = address[:address3] if address[:address3] + post[:billing_address][:city] = address[:city] if address[:city] + post[:billing_address][:state] = address[:state] if address[:state] + post[:billing_address][:country] = address[:country] if address[:country] + post[:billing_address][:zip] = address[:zip] if address[:zip] + post[:billing_address][:company_name] = address[:company_name] if address[:company_name] + post[:billing_address][:tax_id] = address[:tax_id] if address[:tax_id] + post[:billing_address][:name] = address[:name] if address[:name] + post[:billing_address][:phone] = address[:phone] if address[:phone] + post[:billing_address][:email] = address[:email] if address[:email] + end + end + + def add_address(post, options) + if(address = (options[:billing_address] || options[:address])) + post[:address] = {} + post[:address][:street1] = address[:address1] if address[:address1] + post[:address][:street2] = address[:address2] if address[:address2] + post[:address][:street3] = address[:address3] if address[:address3] + post[:address][:city] = address[:city] if address[:city] + post[:address][:state] = address[:state] if address[:state] + post[:address][:country] = address[:country] if address[:country] + post[:address][:zip] = address[:zip] if address[:zip] + end + end + + def add_payment_source(post, payment_source, options) + if payment_source.kind_of?(String) + post[:card] = payment_source + elsif payment_source.respond_to?(:number) + post[:card] = {} + post[:card][:name] = payment_source.name + post[:card][:cvc] = payment_source.verification_value + post[:card][:number] = payment_source.number + post[:card][:exp_month] = sprintf('%02d', payment_source.month) + post[:card][:exp_year] = payment_source.year.to_s[-2, 2] + add_address(post[:card], options) + end + end + + def parse(body) + return {} unless body + JSON.parse(body) + end + + def headers(options) + { + 'Accept' => "application/vnd.conekta-v#{@options[:version]}+json", + 'Accept-Language' => 'es', + 'Authorization' => 'Basic ' + Base64.encode64("#{@options[:key]}:"), + 'RaiseHtmlError' => 'false', + 'Conekta-Client-User-Agent' => {'agent'=>"Conekta ActiveMerchantBindings/#{ActiveMerchant::VERSION}"}.to_json, + 'X-Conekta-Client-User-Agent' => conekta_client_user_agent(options), + 'X-Conekta-Client-User-Metadata' => options[:meta].to_json + } + end + + def conekta_client_user_agent(options) + return user_agent unless options[:application] + JSON.dump(JSON.parse(user_agent).merge!({application: options[:application]})) + end + + def commit(method, url, parameters, options = {}) + success = false + begin + raw_response = parse(ssl_request(method, live_url + url, (parameters ? parameters.to_query : nil), headers(options))) + success = (raw_response.key?('object') && (raw_response['object'] != 'error')) + rescue ResponseError => e + raw_response = response_error(e.response.body) + rescue JSON::ParserError + raw_response = json_error(raw_response) + end + + Response.new( + success, + raw_response['message_to_purchaser'], + raw_response, + test: test?, + authorization: raw_response['id'] + ) + end + + def response_error(raw_response) + parse(raw_response) + rescue JSON::ParserError + json_error(raw_response) + end + + def json_error(raw_response) + msg = 'Invalid response received from the Conekta API.' + msg += " (The raw response returned by the API was #{raw_response.inspect})" + { + 'message' => msg + } + end + end + end +end diff --git a/lib/active_merchant/billing/gateways/creditcall.rb b/lib/active_merchant/billing/gateways/creditcall.rb new file mode 100644 index 00000000000..45f84569f5d --- /dev/null +++ b/lib/active_merchant/billing/gateways/creditcall.rb @@ -0,0 +1,271 @@ +require 'nokogiri' + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class CreditcallGateway < Gateway + include Empty + + self.test_url = 'https://test.cardeasexml.com/generic.cex' + self.live_url = 'https://live.cardeasexml.com/generic.cex' + + self.supported_countries = ['US'] + self.default_currency = 'USD' + self.supported_cardtypes = [:visa, :master, :american_express, :discover] + + self.homepage_url = 'https://www.creditcall.com' + self.display_name = 'Creditcall' + + CVV_CODE = { + 'matched' => 'M', + 'notmatched' => 'N', + 'notchecked' => 'P', + 'partialmatch' => 'N' + } + + AVS_CODE = { + 'matched;matched' => 'D', + 'matched;notchecked' =>'B', + 'matched;notmatched' => 'A', + 'matched;partialmatch' => 'A', + 'notchecked;matched' => 'P', + 'notchecked;notchecked' =>'I', + 'notchecked;notmatched' => 'I', + 'notchecked;partialmatch' => 'I', + 'notmatched;matched' => 'W', + 'notmatched;notchecked' =>'C', + 'notmatched;notmatched' => 'C', + 'notmatched;partialmatch' => 'C', + 'partialmatched;matched' => 'W', + 'partialmatched;notchecked' =>'C', + 'partialmatched;notmatched' => 'C', + 'partialmatched;partialmatch' => 'C' + } + + def initialize(options={}) + requires!(options, :terminal_id, :transaction_key) + super + end + + def purchase(money, payment_method, options={}) + multi_response = MultiResponse.run do |r| + r.process { authorize(money, payment_method, options) } + r.process { capture(money, r.authorization, options) } + end + + merged_params = multi_response.responses.map(&:params).reduce({}, :merge) + + Response.new( + multi_response.primary_response.success?, + multi_response.primary_response.message, + merged_params, + authorization: multi_response.responses.first.authorization, + avs_result: AVSResult.new(code: avs_result_code_from(merged_params)), + cvv_result: CVVResult.new(cvv_result_code_from(merged_params)), + error_code: error_result_code_from(merged_params), + test: test? + ) + end + + def authorize(money, payment_method, options={}) + request = build_xml_request do |xml| + add_transaction_details(xml, money, nil, 'Auth', options) + add_terminal_details(xml, options) + add_card_details(xml, payment_method, options) + end + + commit(request) + end + + def capture(money, authorization, options={}) + request = build_xml_request do |xml| + add_transaction_details(xml, money, authorization, 'Conf', options) + add_terminal_details(xml, options) + end + + commit(request) + end + + def refund(money, authorization, options={}) + request = build_xml_request do |xml| + add_transaction_details(xml, money, authorization, 'Refund', options) + add_terminal_details(xml, options) + end + + commit(request) + end + + def void(authorization, options={}) + request = build_xml_request do |xml| + add_transaction_details(xml, nil, authorization, 'Void', options) + add_terminal_details(xml, options) + end + + commit(request) + end + + def verify(credit_card, options={}) + MultiResponse.run(:use_first_response) do |r| + r.process { authorize(100, credit_card, options) } + r.process(:ignore_result) { void(r.authorization, options) } + end + end + + def supports_scrubbing? + true + end + + def scrub(transcript) + transcript. + gsub(%r(().+?())i, '\1[FILTERED]\2'). + gsub(%r(().+?())i, '\1[FILTERED]\2'). + gsub(%r(().+?())i, '\1[FILTERED]\2') + end + + private + + def avs_result_code_from(params) + AVS_CODE["#{params['Address']};#{params['Zip']}"] + end + + def cvv_result_code_from(params) + CVV_CODE[params['CSC']] + end + + def error_result_code_from(params) + params['ErrorCode'] + end + + def build_xml_request + builder = Nokogiri::XML::Builder.new do |xml| + xml.Request(type: 'CardEaseXML', version: '1.0.0') do + yield(xml) + end + end + builder.to_xml + end + + def add_transaction_details(xml, amount, authorization, type, options={}) + xml.TransactionDetails do + xml.MessageType type + xml.Amount(unit: 'Minor') { xml.text(amount) } if amount + xml.CardEaseReference authorization if authorization + xml.VoidReason '01' if type == 'Void' + end + end + + def add_terminal_details(xml, options={}) + xml.TerminalDetails do + xml.TerminalID @options[:terminal_id] + xml.TransactionKey @options[:transaction_key] + xml.Software(version: 'SoftwareVersion') { xml.text('SoftwareName') } + end + end + + def add_card_details(xml, payment_method, options={}) + xml.CardDetails do + xml.Manual(type: manual_type(options)) do + xml.PAN payment_method.number + xml.ExpiryDate exp_date(payment_method) + xml.CSC payment_method.verification_value unless empty?(payment_method.verification_value) + end + + add_additional_verification(xml, options) + end + end + + def add_additional_verification(xml, options) + return unless (options[:verify_zip].to_s == 'true') || (options[:verify_address].to_s == 'true') + if address = options[:billing_address] + xml.AdditionalVerification do + xml.Zip address[:zip] if options[:verify_zip].to_s == 'true' + xml.Address address[:address1] if options[:verify_address].to_s == 'true' + end + end + end + + def exp_date(payment_method) + "#{format(payment_method.year, :two_digits)}#{format(payment_method.month, :two_digits)}" + end + + def parse(body) + response = {} + xml = Nokogiri::XML(body) + + node = xml.xpath('//Response/TransactionDetails') + node.children.each do |childnode| + response[childnode.name] = childnode.text + end + + node = xml.xpath('//Response/Result') + node.children.each do |childnode| + if childnode.elements.empty? + response[childnode.name] = childnode.text + else + childnode_to_response(response, childnode) + end + end + + node = xml.xpath('//Response/CardDetails') + node.children.each do |childnode| + if childnode.elements.empty? + response[childnode.name] = childnode.text + else + childnode_to_response(response, childnode) + end + end + + response + end + + def childnode_to_response(response, childnode) + childnode.elements.each do |element| + if element.name == 'Error' + response['ErrorCode'] = element.attr('code') + response['ErrorMessage'] = element.text + else + response[element.name] = element.text + end + end + end + + def commit(parameters) + response = parse(ssl_post(url, parameters)) + + Response.new( + success_from(response), + message_from(response), + response, + authorization: authorization_from(response), + avs_result: AVSResult.new(code: avs_result_code_from(response)), + cvv_result: CVVResult.new(cvv_result_code_from(response)), + error_code: error_result_code_from(response), + test: test? + ) + end + + def url + test? ? test_url : live_url + end + + def success_from(response) + response['LocalResult'] == '0' || response['LocalResult'] == '00' + end + + def message_from(response) + if success_from(response) + 'Succeeded' + else + response['ErrorMessage'] + end + end + + def authorization_from(response) + response['CardEaseReference'] + end + + def manual_type(options) + options[:manual_type] || 'ecommerce' + end + end + end +end diff --git a/lib/active_merchant/billing/gateways/credorax.rb b/lib/active_merchant/billing/gateways/credorax.rb new file mode 100644 index 00000000000..6f28d82f259 --- /dev/null +++ b/lib/active_merchant/billing/gateways/credorax.rb @@ -0,0 +1,382 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class CredoraxGateway < Gateway + class_attribute :test_url, :live_na_url, :live_eu_url + + self.display_name = 'Credorax Gateway' + self.homepage_url = 'https://www.credorax.com/' + + # NOTE: the IP address you run the remote tests from will need to be + # whitelisted by Credorax; contact support@credorax.com as necessary to + # request your IP address be added to the whitelist for your test + # account. + self.test_url = 'https://intconsole.credorax.com/intenv/service/gateway' + + # The live URL is assigned on a per merchant basis once certification has passed + # See the Credorax remote tests for the full certification test suite + # + # Once you have your assigned subdomain, you can override the live URL in your application via: + # ActiveMerchant::Billing::CredoraxGateway.live_url = "https://assigned-subdomain.credorax.net/crax_gate/service/gateway" + self.live_url = 'https://assigned-subdomain.credorax.net/crax_gate/service/gateway' + + self.supported_countries = %w(AD AT BE BG HR CY CZ DK EE FR DE GI GR GG HU IS IE IM IT JE LV LI LT LU MT MC NO PL PT RO SM SK ES SE CH GB) + self.default_currency = 'EUR' + self.currencies_without_fractions = %w(CLP JPY KRW PYG VND) + self.currencies_with_three_decimal_places = %w(BHD JOD KWD OMR RSD TND) + + self.money_format = :cents + self.supported_cardtypes = [:visa, :master, :maestro] + + RESPONSE_MESSAGES = { + '00' => 'Approved or completed successfully', + '01' => 'Refer to card issuer', + '02' => 'Refer to card issuer special condition', + '03' => 'Invalid merchant', + '04' => 'Pick up card', + '05' => 'Do not Honour', + '06' => 'Error', + '07' => 'Pick up card special condition', + '08' => 'Honour with identification', + '09' => 'Request in progress', + '10' => 'Approved for partial amount', + '11' => 'Approved (VIP)', + '12' => 'Invalid transaction', + '13' => 'Invalid amount', + '14' => 'Invalid card number', + '15' => 'No such issuer', + '16' => 'Approved, update track 3', + '17' => 'Customer cancellation', + '18' => 'Customer dispute', + '19' => 'Re-enter transaction', + '20' => 'Invalid response', + '21' => 'No action taken', + '22' => 'Suspected malfunction', + '23' => 'Unacceptable transaction fee', + '24' => 'File update not supported by receiver', + '25' => 'No such record', + '26' => 'Duplicate record update, old record replaced', + '27' => 'File update field edit error', + '28' => 'File locked out while update', + '29' => 'File update error, contact acquirer', + '30' => 'Format error', + '31' => 'Issuer signed-off', + '32' => 'Completed partially', + '33' => 'Pick-up, expired card', + '34' => 'Suspect Fraud', + '35' => 'Pick-up, card acceptor contact acquirer', + '36' => 'Pick up, card restricted', + '37' => 'Pick up, call acquirer security', + '38' => 'Pick up, Allowable PIN tries exceeded', + '39' => 'Transaction Not Allowed', + '40' => 'Requested function not supported', + '41' => 'Lost Card, Pickup', + '42' => 'No universal account', + '43' => 'Pick up, stolen card', + '44' => 'No investment account', + '50' => 'Do not renew', + '51' => 'Not sufficient funds', + '52' => 'No checking Account', + '53' => 'No savings account', + '54' => 'Expired card', + '55' => 'Pin incorrect', + '56' => 'No card record', + '57' => 'Transaction not allowed for cardholder', + '58' => 'Transaction not allowed for merchant', + '59' => 'Suspected Fraud', + '60' => 'Card acceptor contact acquirer', + '61' => 'Exceeds withdrawal amount limit', + '62' => 'Restricted card', + '63' => 'Security violation', + '64' => 'Wrong original amount', + '65' => 'Activity count limit exceeded', + '66' => 'Call acquirers security department', + '67' => 'Card to be picked up at ATM', + '68' => 'Response received too late.', + '70' => 'Invalid transaction; contact card issuer', + '71' => 'Decline PIN not changed', + '75' => 'Pin tries exceeded', + '76' => 'Wrong PIN, number of PIN tries exceeded', + '77' => 'Wrong Reference No.', + '78' => 'Record Not Found', + '79' => 'Already reversed', + '80' => 'Network error', + '81' => 'Foreign network error / PIN cryptographic error', + '82' => 'Time out at issuer system', + '83' => 'Transaction failed', + '84' => 'Pre-authorization timed out', + '85' => 'No reason to decline', + '86' => 'Cannot verify pin', + '87' => 'Purchase amount only, no cashback allowed', + '88' => 'MAC sync Error', + '89' => 'Authentication failure', + '91' => 'Issuer not available', + '92' => 'Unable to route at acquirer Module', + '93' => 'Cannot be completed, violation of law', + '94' => 'Duplicate Transmission', + '95' => 'Reconcile error / Auth Not found', + '96' => 'System malfunction', + 'R0' => 'Stop Payment Order', + 'R1' => 'Revocation of Authorisation Order', + 'R3' => 'Revocation of all Authorisations Order' + } + + def initialize(options={}) + requires!(options, :merchant_id, :cipher_key) + super + end + + def purchase(amount, payment_method, options={}) + post = {} + add_invoice(post, amount, options) + add_payment_method(post, payment_method) + add_customer_data(post, options) + add_email(post, options) + add_3d_secure(post, options) + add_echo(post, options) + add_submerchant_id(post, options) + add_transaction_type(post, options) + + commit(:purchase, post) + end + + def authorize(amount, payment_method, options={}) + post = {} + add_invoice(post, amount, options) + add_payment_method(post, payment_method) + add_customer_data(post, options) + add_email(post, options) + add_3d_secure(post, options) + add_echo(post, options) + add_submerchant_id(post, options) + add_transaction_type(post, options) + + commit(:authorize, post) + end + + def capture(amount, authorization, options={}) + post = {} + add_invoice(post, amount, options) + add_reference(post, authorization) + add_customer_data(post, options) + add_echo(post, options) + add_submerchant_id(post, options) + + commit(:capture, post) + end + + def void(authorization, options={}) + post = {} + add_customer_data(post, options) + reference_action = add_reference(post, authorization) + add_echo(post, options) + add_submerchant_id(post, options) + post[:a1] = generate_unique_id + + commit(:void, post, reference_action) + end + + def refund(amount, authorization, options={}) + post = {} + add_invoice(post, amount, options) + add_reference(post, authorization) + add_customer_data(post, options) + add_echo(post, options) + add_submerchant_id(post, options) + + commit(:refund, post) + end + + def credit(amount, payment_method, options={}) + post = {} + add_invoice(post, amount, options) + add_payment_method(post, payment_method) + add_customer_data(post, options) + add_email(post, options) + add_echo(post, options) + add_submerchant_id(post, options) + add_transaction_type(post, options) + + commit(:credit, post) + end + + def verify(credit_card, options={}) + MultiResponse.run(:use_first_response) do |r| + r.process { authorize(100, credit_card, options) } + r.process(:ignore_result) { void(r.authorization, options) } + end + end + + def supports_scrubbing? + true + end + + def scrub(transcript) + transcript. + gsub(%r((b1=)\d+), '\1[FILTERED]'). + gsub(%r((b5=)\d+), '\1[FILTERED]') + end + + private + + def add_invoice(post, money, options) + currency = options[:currency] || currency(money) + + post[:a4] = localized_amount(money, currency) + post[:a1] = generate_unique_id + post[:a5] = currency + post[:h9] = options[:order_id] + post[:i2] = options[:billing_descriptor] if options[:billing_descriptor] + end + + CARD_TYPES = { + 'visa' => '1', + 'mastercard' => '2', + 'maestro' => '9' + } + + def add_payment_method(post, payment_method) + post[:c1] = payment_method.name + post[:b2] = CARD_TYPES[payment_method.brand] || '' + post[:b1] = payment_method.number + post[:b5] = payment_method.verification_value + post[:b4] = format(payment_method.year, :two_digits) + post[:b3] = format(payment_method.month, :two_digits) + end + + def add_customer_data(post, options) + post[:d1] = options[:ip] || '127.0.0.1' + if (billing_address = options[:billing_address]) + post[:c5] = billing_address[:address1] + post[:c7] = billing_address[:city] + post[:c10] = billing_address[:zip] + post[:c8] = billing_address[:state] + post[:c9] = billing_address[:country] + post[:c2] = billing_address[:phone] + end + end + + def add_reference(post, authorization) + response_id, authorization_code, request_id, action = authorization.split(';') + post[:g2] = response_id + post[:g3] = authorization_code + post[:g4] = request_id + action || :authorize + end + + def add_email(post, options) + post[:c3] = options[:email] || 'unspecified@example.com' + end + + def add_3d_secure(post, options) + if options[:eci] && options[:xid] + add_3d_secure_1_data(post, options) + elsif options[:three_d_secure] + add_normalized_3d_secure_2_data(post, options) + end + end + + def add_3d_secure_1_data(post, options) + post[:i8] = build_i8(options[:eci], options[:cavv], options[:xid]) + end + + def add_normalized_3d_secure_2_data(post, options) + three_d_secure_options = options[:three_d_secure] + + post[:i8] = build_i8( + three_d_secure_options[:eci], + three_d_secure_options[:cavv] + ) + post[:'3ds_version'] = three_d_secure_options[:version] + post[:'3ds_dstrxid'] = three_d_secure_options[:ds_transaction_id] + end + + def build_i8(eci, cavv=nil, xid=nil) + "#{eci}:#{cavv || 'none'}:#{xid || 'none'}" + end + + def add_echo(post, options) + # The d2 parameter is used during the certification process + # See remote tests for full certification test suite + post[:d2] = options[:echo] unless options[:echo].blank? + end + + def add_submerchant_id(post, options) + post[:h3] = options[:submerchant_id] if options[:submerchant_id] + end + + def add_transaction_type(post, options) + post[:a9] = options[:transaction_type] if options[:transaction_type] + end + + ACTIONS = { + purchase: '1', + authorize: '2', + capture: '3', + authorize_void: '4', + refund: '5', + credit: '6', + purchase_void: '7', + refund_void: '8', + capture_void: '9' + } + + def commit(action, params, reference_action = nil) + raw_response = ssl_post(url, post_data(action, params, reference_action)) + response = parse(raw_response) + + Response.new( + success_from(response), + message_from(response), + response, + authorization: "#{response["Z1"]};#{response["Z4"]};#{response["A1"]};#{action}", + avs_result: AVSResult.new(code: response['Z9']), + cvv_result: CVVResult.new(response['Z14']), + test: test? + ) + end + + def sign_request(params) + params = params.sort + params.each { |param| param[1].gsub!(/[<>()\\]/, ' ') } + values = params.map { |param| param[1].strip } + Digest::MD5.hexdigest(values.join + @options[:cipher_key]) + end + + def post_data(action, params, reference_action) + params.keys.each { |key| params[key] = params[key].to_s } + params[:M] = @options[:merchant_id] + params[:O] = request_action(action, reference_action) + params[:K] = sign_request(params) + params.map { |k, v| "#{k}=#{CGI.escape(v.to_s)}" }.join('&') + end + + def request_action(action, reference_action) + if reference_action + ACTIONS["#{reference_action}_#{action}".to_sym] + else + ACTIONS[action] + end + end + + def url + test? ? test_url : live_url + end + + def parse(body) + Hash[CGI::parse(body).map { |k, v| [k.upcase, v.first] }] + end + + def success_from(response) + response['Z2'] == '0' + end + + def message_from(response) + if success_from(response) + 'Succeeded' + else + RESPONSE_MESSAGES[response['Z6']] || response['Z3'] || 'Unable to read error message' + end + end + end + end +end diff --git a/lib/active_merchant/billing/gateways/ct_payment.rb b/lib/active_merchant/billing/gateways/ct_payment.rb new file mode 100644 index 00000000000..315f16375d8 --- /dev/null +++ b/lib/active_merchant/billing/gateways/ct_payment.rb @@ -0,0 +1,268 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class CtPaymentGateway < Gateway + self.test_url = 'https://test.ctpaiement.ca/v1/' + self.live_url = 'https://www.ctpaiement.com/v1/' + + self.supported_countries = ['US', 'CA'] + self.default_currency = 'CAD' + self.supported_cardtypes = [:visa, :master, :american_express, :discover, :diners_club] + + self.homepage_url = 'http://www.ct-payment.com/' + self.display_name = 'CT Payment' + + STANDARD_ERROR_CODE_MAPPING = { + '14' => STANDARD_ERROR_CODE[:invalid_number], + '05' => STANDARD_ERROR_CODE[:card_declined], + 'M6' => STANDARD_ERROR_CODE[:card_declined], + '9068' => STANDARD_ERROR_CODE[:incorrect_number], + '9067' => STANDARD_ERROR_CODE[:incorrect_number] + } + CARD_BRAND = { + 'american_express' => 'A', + 'master' => 'M', + 'diners_club' => 'I', + 'visa' => 'V', + 'discover' => 'O' + } + + def initialize(options={}) + requires!(options, :api_key, :company_number, :merchant_number) + super + end + + def purchase(money, payment, options={}) + requires!(options, :order_id) + post = {} + add_terminal_number(post, options) + add_money(post, money) + add_operator_id(post, options) + add_invoice(post, money, options) + add_payment(post, payment) + add_address(post, payment, options) + add_customer_data(post, options) + + payment.is_a?(String) ? commit('purchaseWithToken', post) : commit('purchase', post) + end + + def authorize(money, payment, options={}) + requires!(options, :order_id) + post = {} + add_money(post, money) + add_terminal_number(post, options) + add_operator_id(post, options) + add_invoice(post, money, options) + add_payment(post, payment) + add_address(post, payment, options) + add_customer_data(post, options) + + payment.is_a?(String) ? commit('preAuthorizationWithToken', post) : commit('preAuthorization', post) + end + + def capture(money, authorization, options={}) + requires!(options, :order_id) + post = {} + add_invoice(post, money, options) + add_money(post, money) + add_customer_data(post, options) + transaction_number, authorization_number, invoice_number = split_authorization(authorization) + post[:OriginalTransactionNumber] = transaction_number + post[:OriginalAuthorizationNumber] = authorization_number + post[:OriginalInvoiceNumber] = invoice_number + + commit('completion', post) + end + + def refund(money, authorization, options={}) + requires!(options, :order_id) + post = {} + add_invoice(post, money, options) + add_money(post, money) + add_customer_data(post, options) + transaction_number, _, invoice_number = split_authorization(authorization) + post[:OriginalTransactionNumber] = transaction_number + post[:OriginalInvoiceNumber] = invoice_number + + commit('refundWithoutCard', post) + end + + def credit(money, payment, options={}) + requires!(options, :order_id) + post = {} + add_terminal_number(post, options) + add_money(post, money) + add_operator_id(post, options) + add_invoice(post, money, options) + add_payment(post, payment) + add_address(post, payment, options) + add_customer_data(post, options) + + payment.is_a?(String) ? commit('refundWithToken', post) : commit('refund', post) + end + + def void(authorization, options={}) + post = {} + post[:InputType] = 'I' + post[:LanguageCode] = 'E' + transaction_number, _, invoice_number = split_authorization(authorization) + post[:OriginalTransactionNumber] = transaction_number + post[:OriginalInvoiceNumber] = invoice_number + add_operator_id(post, options) + add_customer_data(post, options) + + commit('void', post) + end + + def verify(credit_card, options={}) + requires!(options, :order_id) + post = {} + add_terminal_number(post, options) + add_operator_id(post, options) + add_invoice(post, 0, options) + add_payment(post, credit_card) + add_address(post, credit_card, options) + add_customer_data(post, options) + + commit('verifyAccount', post) + end + + def store(credit_card, options={}) + requires!(options, :email) + post = { + LanguageCode: 'E', + Name: credit_card.name.rjust(50, ' '), + Email: options[:email].rjust(240, ' ') + } + add_operator_id(post, options) + add_payment(post, credit_card) + add_customer_data(post, options) + + commit('recur/AddUser', post) + end + + def supports_scrubbing? + true + end + + def scrub(transcript) + transcript. + gsub(%r((&?auth-api-key=)[^&]*)i, '\1[FILTERED]'). + gsub(%r((&?payload=)[a-zA-Z%0-9=]+)i, '\1[FILTERED]'). + gsub(%r((&?token:)[^&]*)i, '\1[FILTERED]'). + gsub(%r((&?cardNumber:)[^&]*)i, '\1[FILTERED]') + end + + private + + def add_terminal_number(post, options) + post[:MerchantTerminalNumber] = options[:merchant_terminal_number] || ' ' * 5 + end + + def add_money(post, money) + post[:Amount] = money.to_s.rjust(11, '0') + end + + def add_operator_id(post, options) + post[:OperatorID] = options[:operator_id] || '0' * 8 + end + + def add_customer_data(post, options) + post[:CustomerNumber] = options[:customer_number] || '0' * 8 + end + + def add_address(post, creditcard, options) + if address = options[:billing_address] || options[:address] + post[:CardHolderAddress] = "#{address[:address1]} #{address[:address2]} #{address[:city]} #{address[:state]}".rjust(20, ' ') + post[:CardHolderPostalCode] = address[:zip].gsub(/\s+/, '').rjust(9, ' ') + end + end + + def add_invoice(post, money, options) + post[:CurrencyCode] = options[:currency] || (currency(money) if money) + post[:InvoiceNumber] = options[:order_id].rjust(12, '0') + post[:InputType] = 'I' + post[:LanguageCode] = 'E' + end + + def add_payment(post, payment) + if payment.is_a?(String) + post[:Token] = split_authorization(payment)[3].strip + else + post[:CardType] = CARD_BRAND[payment.brand] || ' ' + post[:CardNumber] = payment.number.rjust(40, ' ') + post[:ExpirationDate] = expdate(payment) + post[:Cvv2Cvc2Number] = payment.verification_value + end + end + + def parse(body) + JSON.parse(body) + end + + def split_authorization(authorization) + authorization.split(';') + end + + def commit_raw(action, parameters) + url = (test? ? test_url : live_url) + action + response = parse(ssl_post(url, post_data(action, parameters))) + + Response.new( + success_from(response), + message_from(response), + response, + authorization: authorization_from(response), + avs_result: AVSResult.new(code: response['avsStatus']), + cvv_result: CVVResult.new(response['cvv2Cvc2Status']), + test: test?, + error_code: error_code_from(response) + ) + end + + def commit(action, parameters) + if action == 'void' + commit_raw(action, parameters) + else + MultiResponse.run(true) do |r| + r.process { commit_raw(action, parameters) } + r.process { + split_auth = split_authorization(r.authorization) + auth = (action.include?('recur')? split_auth[4] : split_auth[0]) + action.include?('recur') ? commit_raw('recur/ack', {ID: auth}) : commit_raw('ack', {TransactionNumber: auth}) + } + end + end + end + + def success_from(response) + return true if response['returnCode'] == ' 00' + return true if response['returnCode'] == 'true' + return true if response['recurReturnCode'] == ' 00' + return false + end + + def message_from(response) + response['errorDescription'] || response['terminalDisp']&.strip + end + + def authorization_from(response) + "#{response['transactionNumber']};#{response['authorizationNumber']};"\ + "#{response['invoiceNumber']};#{response['token']};#{response['id']}" + end + + def post_data(action, parameters = {}) + parameters[:CompanyNumber] = @options[:company_number] + parameters[:MerchantNumber] = @options[:merchant_number] + parameters = parameters.collect do |key, value| + "#{key}=#{value}" unless value.nil? || value.empty? + end.join('&') + payload = Base64.strict_encode64(parameters) + "auth-api-key=#{@options[:api_key]}&payload=#{payload}".strip + end + + def error_code_from(response) + STANDARD_ERROR_CODE_MAPPING[response['returnCode'].strip || response['recurReturnCode'.strip]] unless success_from(response) + end + end + end +end diff --git a/lib/active_merchant/billing/gateways/culqi.rb b/lib/active_merchant/billing/gateways/culqi.rb new file mode 100644 index 00000000000..80b4d030198 --- /dev/null +++ b/lib/active_merchant/billing/gateways/culqi.rb @@ -0,0 +1,277 @@ +require 'digest/md5' + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + # Important note: + # === + # Culqi merchant accounts are configured for either purchase or auth/capture + # modes. This is configured by Culqi when setting up a merchant account and + # largely depends on the transaction acquiring bank. Be sure to understand how + # your account was configured prior to using this gateway. + class CulqiGateway < Gateway + self.display_name = 'Culqi' + self.homepage_url = 'https://www.culqi.com' + + self.test_url = 'https://staging.paymentz.com/transaction/' + self.live_url = 'https://secure.culqi.com/transaction/' + + self.supported_countries = ['PE'] + self.default_currency = 'PEN' + self.money_format = :dollars + self.supported_cardtypes = [:visa, :master, :diners_club, :american_express] + + def initialize(options={}) + requires!(options, :merchant_id, :terminal_id, :secret_key) + super + end + + def purchase(amount, payment_method, options={}) + authorize(amount, payment_method, options) + end + + def authorize(amount, payment_method, options={}) + if payment_method.is_a?(String) + action = :tokenpay + else + action = :authorize + end + post = {} + add_credentials(post) + add_invoice(action, post, amount, options) + add_payment_method(post, payment_method, action, options) + add_customer_data(post, options) + add_checksum(action, post) + + commit(action, post) + end + + def capture(amount, authorization, options={}) + action = :capture + post = {} + add_credentials(post) + add_invoice(action, post, amount, options) + add_reference(post, authorization) + add_checksum(action, post) + + commit(action, post) + end + + def void(authorization, options={}) + action = :void + post = {} + add_credentials(post) + add_invoice(action, post, nil, options) + add_reference(post, authorization) + add_checksum(action, post) + + commit(action, post) + end + + def refund(amount, authorization, options={}) + action = :refund + post = {} + add_credentials(post) + add_invoice(action, post, amount, options) + add_reference(post, authorization) + add_checksum(action, post) + + commit(action, post) + end + + def verify(credit_card, options={}) + MultiResponse.run(:use_first_response) do |r| + r.process { authorize(1000, credit_card, options) } + r.process(:ignore_result) { void(r.authorization, options) } + end + end + + def verify_credentials + response = void('0', order_id: '0') + response.message.include? 'Transaction not found' + end + + def store(credit_card, options={}) + action = :tokenize + post = {} + post[:partnerid] = options[:partner_id] if options[:partner_id] + post[:cardholderid] = options[:cardholder_id] if options[:cardholder_id] + add_credentials(post) + add_payment_method(post, credit_card, action, options) + add_customer_data(post, options) + add_checksum(action, post) + + commit(action, post) + end + + def invalidate(authorization, options={}) + action = :invalidate + post = {} + post[:partnerid] = options[:partner_id] if options[:partner_id] + add_credentials(post) + post[:token] = authorization + add_checksum(action, post) + + commit(action, post) + end + + def supports_scrubbing? + true + end + + def scrub(transcript) + transcript. + gsub(%r((cardnumber=)\d+), '\1[FILTERED]'). + gsub(%r((cvv=)\d+), '\1[FILTERED]') + end + + private + + def add_credentials(post) + post[:toid] = @options[:merchant_id] + post[:totype] = 'Culqi' + post[:terminalid] = @options[:terminal_id] + post[:language] = 'ENG' + end + + def add_invoice(action, post, money, options) + case action + when :capture + post[:captureamount] = amount(money) + when :refund + post[:refundamount] = amount(money) + post[:reason] = 'none' + else + post[:amount] = amount(money) + end + + post[:description] = options[:order_id] + post[:redirecturl] = options[:redirect_url] || 'http://www.example.com' + end + + def add_payment_method(post, payment_method, action, options) + if payment_method.is_a?(String) + post[:token] = payment_method + post[:cvv] = options[:cvv] if options[:cvv] + else + post[:cardnumber] = payment_method.number + post[:cvv] = payment_method.verification_value + post[:firstname], post[:lastname] = payment_method.name.split(' ') + if action == :tokenize + post[:expirymonth] = format(payment_method.month, :two_digits) + post[:expiryyear] = format(payment_method.year, :four_digits) + else + post[:expiry_month] = format(payment_method.month, :two_digits) + post[:expiry_year] = format(payment_method.year, :four_digits) + end + end + end + + def add_customer_data(post, options) + post[:emailaddr] = options[:email] || 'unspecified@example.com' + if (billing_address = options[:billing_address] || options[:address]) + post[:street] = [billing_address[:address1], billing_address[:address2]].join(' ') + post[:city] = billing_address[:city] + post[:state] = billing_address[:state] + post[:countrycode] = billing_address[:country] + post[:zip] = billing_address[:zip] + post[:telno] = billing_address[:phone] + post[:telnocc] = options[:telephone_country_code] || '051' + end + end + + def add_checksum(action, post) + checksum_elements = case action + when :capture then [post[:toid], post[:trackingid], post[:captureamount], @options[:secret_key]] + when :void then [post[:toid], post[:description], post[:trackingid], @options[:secret_key]] + when :refund then [post[:toid], post[:trackingid], post[:refundamount], @options[:secret_key]] + when :tokenize then [post[:partnerid], post[:cardnumber], post[:cvv], @options[:secret_key]] + when :invalidate then [post[:partnerid], post[:token], @options[:secret_key]] + else [post[:toid], post[:totype], post[:amount], post[:description], post[:redirecturl], + post[:cardnumber] || post[:token], @options[:secret_key]] + end + + post[:checksum] = Digest::MD5.hexdigest(checksum_elements.compact.join('|')) + end + + def add_reference(post, authorization) + post[:trackingid] = authorization + end + + ACTIONS = { + authorize: 'SingleCallGenericServlet', + capture: 'SingleCallGenericCaptureServlet', + void: 'SingleCallGenericVoid', + refund: 'SingleCallGenericReverse', + tokenize: 'SingleCallTokenServlet', + invalidate: 'SingleCallInvalidateToken', + tokenpay: 'SingleCallTokenTransaction', + } + + def commit(action, params) + response = begin + parse(ssl_post(url + ACTIONS[action], post_data(action, params), headers)) + rescue ResponseError => e + parse(e.response.body) + end + + success = success_from(response) + + Response.new( + success, + message_from(response), + response, + authorization: success ? authorization_from(response) : nil, + cvv_result: success ? cvvresult_from(response) : nil, + error_code: success ? nil : error_from(response), + test: test? + ) + end + + def headers + { + 'Accept' => 'application/json', + 'Content-Type' => 'application/x-www-form-urlencoded;charset=UTF-8' + } + end + + def post_data(action, params) + params.map { |k, v| "#{k}=#{CGI.escape(v.to_s)}" }.join('&') + end + + def url + test? ? test_url : live_url + end + + def parse(body) + JSON.parse(body) + rescue JSON::ParserError + message = 'Invalid JSON response received from CulqiGateway. Please contact CulqiGateway if you continue to receive this message.' + message += "(The raw response returned by the API was #{body.inspect})" + { + 'status' => 'N', + 'statusdescription' => message + } + end + + def success_from(response) + response['status'] == 'Y' + end + + def message_from(response) + response['statusdescription'] || response['statusDescription'] + end + + def authorization_from(response) + response['trackingid'] || response['token'] + end + + def cvvresult_from(response) + CVVResult.new(response['cvvresult']) + end + + def error_from(response) + response['resultcode'] + end + end + end +end diff --git a/lib/active_merchant/billing/gateways/cyber_source.rb b/lib/active_merchant/billing/gateways/cyber_source.rb index 3213dd0f4b4..ba4cb8438f9 100644 --- a/lib/active_merchant/billing/gateways/cyber_source.rb +++ b/lib/active_merchant/billing/gateways/cyber_source.rb @@ -1,16 +1,7 @@ module ActiveMerchant #:nodoc: module Billing #:nodoc: - # See the remote and mocked unit test files for example usage. Pay special - # attention to the contents of the options hash. - # # Initial setup instructions can be found in - # http://cybersource.com/support_center/implementation/downloads/soap_api/SOAP_toolkits.pdf - # - # Debugging - # If you experience an issue with this gateway be sure to examine the - # transaction information from a general transaction search inside the - # CyberSource Business Center for the full error messages including field - # names. + # http://apps.cybersource.com/library/documentation/dev_guides/SOAP_Toolkits/SOAP_toolkits.pdf # # Important Notes # * For checks you can purchase and store. @@ -24,70 +15,79 @@ module Billing #:nodoc: # CyberSource what kind of item you are selling. It is used when # calculating tax/VAT. # * All transactions use dollar values. - # * To process pinless debit cards throught the pinless debit card + # * To process pinless debit cards through the pinless debit card # network, your Cybersource merchant account must accept pinless # debit card payments. + # * The order of the XML elements does matter, make sure to follow the order in + # the documentation exactly. class CyberSourceGateway < Gateway - self.test_url = 'https://ics2wstest.ic3.com/commerce/1.x/transactionProcessor' - self.live_url = 'https://ics2ws.ic3.com/commerce/1.x/transactionProcessor' + self.test_url = 'https://ics2wstesta.ic3.com/commerce/1.x/transactionProcessor' + self.live_url = 'https://ics2wsa.ic3.com/commerce/1.x/transactionProcessor' - XSD_VERSION = "1.69" + # Schema files can be found here: https://ics2ws.ic3.com/commerce/1.x/transactionProcessor/ + TEST_XSD_VERSION = '1.156' + PRODUCTION_XSD_VERSION = '1.155' + + self.supported_cardtypes = [:visa, :master, :american_express, :discover, :diners_club, :jcb, :dankort, :maestro, :elo] + self.supported_countries = %w(US BR CA CN DK FI FR DE IN JP MX NO SE GB SG LB PK) - # visa, master, american_express, discover - self.supported_cardtypes = [:visa, :master, :american_express, :discover] - self.supported_countries = %w(US BR CA CN DK FI FR DE JP MX NO SE GB SG) self.default_currency = 'USD' + self.currencies_without_fractions = %w(JPY) + self.homepage_url = 'http://www.cybersource.com' self.display_name = 'CyberSource' - # map credit card to the CyberSource expected representation @@credit_card_codes = { :visa => '001', :master => '002', :american_express => '003', - :discover => '004' + :discover => '004', + :diners_club => '005', + :jcb => '007', + :dankort => '034', + :maestro => '042', + :elo => '054' } - # map response codes to something humans can read @@response_codes = { - :r100 => "Successful transaction", - :r101 => "Request is missing one or more required fields" , - :r102 => "One or more fields contains invalid data", - :r150 => "General failure", - :r151 => "The request was received but a server time-out occurred", - :r152 => "The request was received, but a service timed out", - :r200 => "The authorization request was approved by the issuing bank but declined by CyberSource because it did not pass the AVS check", - :r201 => "The issuing bank has questions about the request", - :r202 => "Expired card", - :r203 => "General decline of the card", - :r204 => "Insufficient funds in the account", - :r205 => "Stolen or lost card", - :r207 => "Issuing bank unavailable", - :r208 => "Inactive card or card not authorized for card-not-present transactions", - :r209 => "American Express Card Identifiction Digits (CID) did not match", - :r210 => "The card has reached the credit limit", - :r211 => "Invalid card verification number", + :r100 => 'Successful transaction', + :r101 => 'Request is missing one or more required fields', + :r102 => 'One or more fields contains invalid data', + :r150 => 'General failure', + :r151 => 'The request was received but a server time-out occurred', + :r152 => 'The request was received, but a service timed out', + :r200 => 'The authorization request was approved by the issuing bank but declined by CyberSource because it did not pass the AVS check', + :r201 => 'The issuing bank has questions about the request', + :r202 => 'Expired card', + :r203 => 'General decline of the card', + :r204 => 'Insufficient funds in the account', + :r205 => 'Stolen or lost card', + :r207 => 'Issuing bank unavailable', + :r208 => 'Inactive card or card not authorized for card-not-present transactions', + :r209 => 'American Express Card Identifiction Digits (CID) did not match', + :r210 => 'The card has reached the credit limit', + :r211 => 'Invalid card verification number', :r221 => "The customer matched an entry on the processor's negative file", - :r230 => "The authorization request was approved by the issuing bank but declined by CyberSource because it did not pass the card verification check", - :r231 => "Invalid account number", - :r232 => "The card type is not accepted by the payment processor", - :r233 => "General decline by the processor", - :r234 => "A problem exists with your CyberSource merchant configuration", - :r235 => "The requested amount exceeds the originally authorized amount", - :r236 => "Processor failure", - :r237 => "The authorization has already been reversed", - :r238 => "The authorization has already been captured", - :r239 => "The requested transaction amount must match the previous transaction amount", - :r240 => "The card type sent is invalid or does not correlate with the credit card number", - :r241 => "The request ID is invalid", - :r242 => "You requested a capture, but there is no corresponding, unused authorization record.", - :r243 => "The transaction has already been settled or reversed", - :r244 => "The bank account number failed the validation check", - :r246 => "The capture or credit is not voidable because the capture or credit information has already been submitted to your processor", - :r247 => "You requested a credit for a capture that was previously voided", - :r250 => "The request was received, but a time-out occurred with the payment processor", - :r254 => "Your CyberSource account is prohibited from processing stand-alone refunds", - :r255 => "Your CyberSource account is not configured to process the service in the country you specified" + :r230 => 'The authorization request was approved by the issuing bank but declined by CyberSource because it did not pass the card verification check', + :r231 => 'Invalid account number', + :r232 => 'The card type is not accepted by the payment processor', + :r233 => 'General decline by the processor', + :r234 => 'A problem exists with your CyberSource merchant configuration', + :r235 => 'The requested amount exceeds the originally authorized amount', + :r236 => 'Processor failure', + :r237 => 'The authorization has already been reversed', + :r238 => 'The authorization has already been captured', + :r239 => 'The requested transaction amount must match the previous transaction amount', + :r240 => 'The card type sent is invalid or does not correlate with the credit card number', + :r241 => 'The request ID is invalid', + :r242 => 'You requested a capture, but there is no corresponding, unused authorization record.', + :r243 => 'The transaction has already been settled or reversed', + :r244 => 'The bank account number failed the validation check', + :r246 => 'The capture or credit is not voidable because the capture or credit information has already been submitted to your processor', + :r247 => 'You requested a credit for a capture that was previously voided', + :r250 => 'The request was received, but a time-out occurred with the payment processor', + :r254 => 'Your CyberSource account is prohibited from processing stand-alone refunds', + :r255 => 'Your CyberSource account is not configured to process the service in the country you specified' } # These are the options that can be used when creating a new CyberSource @@ -102,7 +102,7 @@ class CyberSourceGateway < Gateway # :vat_reg_number => your VAT registration number # # :nexus => "WI CA QC" sets the states/provinces where you have a physical - # presense for tax purposes + # presence for tax purposes # # :ignore_avs => true don't want to use AVS so continue processing even # if AVS would have failed @@ -114,74 +114,68 @@ def initialize(options = {}) super end - # Request an authorization for an amount from CyberSource - # - # You must supply an :order_id in the options hash def authorize(money, creditcard_or_reference, options = {}) - requires!(options, :order_id) setup_address_hash(options) - commit(build_auth_request(money, creditcard_or_reference, options), options ) - end - - def auth_reversal(money, identification, options = {}) - commit(build_auth_reversal_request(money, identification, options), options) + commit(build_auth_request(money, creditcard_or_reference, options), :authorize, money, options) end - # Capture an authorization that has previously been requested def capture(money, authorization, options = {}) setup_address_hash(options) - commit(build_capture_request(money, authorization, options), options) + commit(build_capture_request(money, authorization, options), :capture, money, options) end - # Purchase is an auth followed by a capture - # You must supply an order_id in the options hash # options[:pinless_debit_card] => true # attempts to process as pinless debit card def purchase(money, payment_method_or_reference, options = {}) - requires!(options, :order_id) setup_address_hash(options) - commit(build_purchase_request(money, payment_method_or_reference, options), options) + commit(build_purchase_request(money, payment_method_or_reference, options), :purchase, money, options) end def void(identification, options = {}) - commit(build_void_request(identification, options), options) + commit(build_void_request(identification, options), :void, nil, options) end def refund(money, identification, options = {}) - commit(build_refund_request(money, identification, options), options) + commit(build_refund_request(money, identification, options), :refund, money, options) end - # Adds credit to a subscription (stand alone credit). - def credit(money, reference, options = {}) - requires!(options, :order_id) - commit(build_credit_request(money, reference, options), options) + def verify(payment, options = {}) + MultiResponse.run(:use_first_response) do |r| + r.process { authorize(100, payment, options) } + r.process(:ignore_result) { void(r.authorization, options) } + end + end + + # Adds credit to a card or subscription (stand alone credit). + def credit(money, creditcard_or_reference, options = {}) + setup_address_hash(options) + commit(build_credit_request(money, creditcard_or_reference, options), :credit, money, options) end # Stores a customer subscription/profile with type "on-demand". # To charge the card while creating a profile, pass # options[:setup_fee] => money def store(payment_method, options = {}) - requires!(options, :order_id) setup_address_hash(options) - commit(build_create_subscription_request(payment_method, options), options) + commit(build_create_subscription_request(payment_method, options), :store, nil, options) end # Updates a customer subscription/profile def update(reference, creditcard, options = {}) requires!(options, :order_id) setup_address_hash(options) - commit(build_update_subscription_request(reference, creditcard, options), options) + commit(build_update_subscription_request(reference, creditcard, options), :update, nil, options) end # Removes a customer subscription/profile def unstore(reference, options = {}) requires!(options, :order_id) - commit(build_delete_subscription_request(reference, options), options) + commit(build_delete_subscription_request(reference, options), :unstore, nil, options) end # Retrieves a customer subscription/profile def retrieve(reference, options = {}) requires!(options, :order_id) - commit(build_retrieve_subscription_request(reference, options), options) + commit(build_retrieve_subscription_request(reference, options), :retrieve, nil, options) end # CyberSource requires that you provide line item information for tax @@ -213,29 +207,66 @@ def retrieve(reference, options = {}) def calculate_tax(creditcard, options) requires!(options, :line_items) setup_address_hash(options) - commit(build_tax_calculation_request(creditcard, options), options) + commit(build_tax_calculation_request(creditcard, options), :calculate_tax, nil, options) end # Determines if a card can be used for Pinless Debit Card transactions def validate_pinless_debit_card(creditcard, options = {}) requires!(options, :order_id) - commit(build_validate_pinless_debit_request(creditcard,options), options) + commit(build_validate_pinless_debit_request(creditcard, options), :validate_pinless_debit_card, nil, options) + end + + def supports_scrubbing? + true + end + + def scrub(transcript) + transcript. + gsub(%r((]*>)[^<]*())i, '\1[FILTERED]\2'). + gsub(%r(()[^<]*())i, '\1[FILTERED]\2'). + gsub(%r(()[^<]*())i, '\1[FILTERED]\2'). + gsub(%r(()[^<]*())i, '\1[FILTERED]\2'). + gsub(%r(()[^<]*())i, '\1[FILTERED]\2'). + gsub(%r(()[^<]*())i, '\1[FILTERED]\2') + end + + def supports_network_tokenization? + true + end + + def verify_credentials + response = void('0') + response.params['reasonCode'] == '102' end private # Create all address hash key value pairs so that we still function if we - # were only provided with one or two of them + # were only provided with one or two of them or even none def setup_address_hash(options) - options[:billing_address] = options[:billing_address] || options[:address] || {} + default_address = { + :address1 => 'Unspecified', + :city => 'Unspecified', + :state => 'NC', + :zip => '00000', + :country => 'US' + } + options[:billing_address] = options[:billing_address] || options[:address] || default_address options[:shipping_address] = options[:shipping_address] || {} end def build_auth_request(money, creditcard_or_reference, options) xml = Builder::XmlMarkup.new :indent => 2 add_payment_method_or_subscription(xml, money, creditcard_or_reference, options) - add_auth_service(xml) - add_business_rules_data(xml) + add_decision_manager_fields(xml, options) + add_mdd_fields(xml, options) + add_auth_service(xml, creditcard_or_reference, options) + add_threeds_services(xml, options) + add_payment_network_token(xml) if network_tokenization?(creditcard_or_reference) + add_business_rules_data(xml, creditcard_or_reference, options) + add_stored_credential_options(xml, options) + add_issuer_additional_data(xml, options) + xml.target! end @@ -246,53 +277,55 @@ def build_tax_calculation_request(creditcard, options) add_line_item_data(xml, options) add_purchase_data(xml, 0, false, options) add_tax_service(xml) - add_business_rules_data(xml) + add_business_rules_data(xml, creditcard, options) xml.target! end def build_capture_request(money, authorization, options) - order_id, request_id, request_token = authorization.split(";") + order_id, request_id, request_token = authorization.split(';') options[:order_id] = order_id xml = Builder::XmlMarkup.new :indent => 2 add_purchase_data(xml, money, true, options) add_capture_service(xml, request_id, request_token) - add_business_rules_data(xml) + add_business_rules_data(xml, authorization, options) xml.target! end def build_purchase_request(money, payment_method_or_reference, options) xml = Builder::XmlMarkup.new :indent => 2 add_payment_method_or_subscription(xml, money, payment_method_or_reference, options) + add_decision_manager_fields(xml, options) + add_mdd_fields(xml, options) if !payment_method_or_reference.is_a?(String) && card_brand(payment_method_or_reference) == 'check' add_check_service(xml) else - add_purchase_service(xml, options) - add_business_rules_data(xml) unless options[:pinless_debit_card] + add_purchase_service(xml, payment_method_or_reference, options) + add_threeds_services(xml, options) + add_payment_network_token(xml) if network_tokenization?(payment_method_or_reference) + add_business_rules_data(xml, payment_method_or_reference, options) unless options[:pinless_debit_card] end + add_issuer_additional_data(xml, options) + xml.target! end def build_void_request(identification, options) - order_id, request_id, request_token = identification.split(";") + order_id, request_id, request_token, action, money, currency = identification.split(';') options[:order_id] = order_id xml = Builder::XmlMarkup.new :indent => 2 - add_void_service(xml, request_id, request_token) - xml.target! - end - - def build_auth_reversal_request(money, identification, options) - order_id, request_id, request_token = identification.split(";") - options[:order_id] = order_id - xml = Builder::XmlMarkup.new :indent => 2 - add_purchase_data(xml, money, true, options) - add_auth_reversal_service(xml, request_id, request_token) + if action == 'capture' + add_void_service(xml, request_id, request_token) + else + add_purchase_data(xml, money, true, options.merge(:currency => currency || default_currency)) + add_auth_reversal_service(xml, request_id, request_token) + end xml.target! end def build_refund_request(money, identification, options) - order_id, request_id, request_token = identification.split(";") + order_id, request_id, request_token = identification.split(';') options[:order_id] = order_id xml = Builder::XmlMarkup.new :indent => 2 @@ -302,18 +335,20 @@ def build_refund_request(money, identification, options) xml.target! end - def build_credit_request(money, reference, options) + def build_credit_request(money, creditcard_or_reference, options) xml = Builder::XmlMarkup.new :indent => 2 - add_purchase_data(xml, money, true, options) - add_subscription(xml, options, reference) + add_payment_method_or_subscription(xml, money, creditcard_or_reference, options) add_credit_service(xml) xml.target! end def build_create_subscription_request(payment_method, options) - options[:subscription] = (options[:subscription] || {}).merge(:frequency => "on-demand", :amount => 0, :automatic_renew => false) + default_subscription_params = {:frequency => 'on-demand', :amount => 0, :automatic_renew => false} + options[:subscription] = default_subscription_params.update( + options[:subscription] || {} + ) xml = Builder::XmlMarkup.new :indent => 2 add_address(xml, payment_method, options[:billing_address], options) @@ -321,15 +356,21 @@ def build_create_subscription_request(payment_method, options) if card_brand(payment_method) == 'check' add_check(xml, payment_method) add_check_payment_method(xml) - add_check_service(xml, options) if options[:setup_fee] else add_creditcard(xml, payment_method) add_creditcard_payment_method(xml) - add_purchase_service(xml, options) if options[:setup_fee] end add_subscription(xml, options) + if options[:setup_fee] + if card_brand(payment_method) == 'check' + add_check_service(xml) + else + add_purchase_service(xml, payment_method, options) + add_payment_network_token(xml) if network_tokenization?(payment_method) + end + end add_subscription_create_service(xml, options) - add_business_rules_data(xml) + add_business_rules_data(xml, payment_method, options) xml.target! end @@ -341,7 +382,7 @@ def build_update_subscription_request(reference, creditcard, options) add_creditcard_payment_method(xml) if creditcard add_subscription(xml, options, reference) add_subscription_update_service(xml, options) - add_business_rules_data(xml) + add_business_rules_data(xml, creditcard, options) xml.target! end @@ -359,24 +400,35 @@ def build_retrieve_subscription_request(reference, options) xml.target! end - def build_validate_pinless_debit_request(creditcard,options) + def build_validate_pinless_debit_request(creditcard, options) xml = Builder::XmlMarkup.new :indent => 2 add_creditcard(xml, creditcard) add_validate_pinless_debit_service(xml) xml.target! end - def add_business_rules_data(xml) - xml.tag! 'businessRules' do - xml.tag!('ignoreAVSResult', 'true') if @options[:ignore_avs] - xml.tag!('ignoreCVResult', 'true') if @options[:ignore_cvv] + def add_business_rules_data(xml, payment_method, options) + prioritized_options = [options, @options] + + unless network_tokenization?(payment_method) + xml.tag! 'businessRules' do + xml.tag!('ignoreAVSResult', 'true') if extract_option(prioritized_options, :ignore_avs) + xml.tag!('ignoreCVResult', 'true') if extract_option(prioritized_options, :ignore_cvv) + end + end + end + + def extract_option(prioritized_options, option_name) + options_matching_key = prioritized_options.detect do |options| + options.has_key? option_name end + options_matching_key[option_name] if options_matching_key end def add_line_item_data(xml, options) options[:line_items].each_with_index do |value, index| xml.tag! 'item', {'id' => index} do - xml.tag! 'unitPrice', amount(value[:declared_value]) + xml.tag! 'unitPrice', localized_amount(value[:declared_value].to_i, options[:currency] || default_currency) xml.tag! 'quantity', value[:quantity] xml.tag! 'productCode', value[:code] || 'shipping_only' xml.tag! 'productName', value[:description] @@ -387,16 +439,16 @@ def add_line_item_data(xml, options) def add_merchant_data(xml, options) xml.tag! 'merchantID', @options[:login] - xml.tag! 'merchantReferenceCode', options[:order_id] - xml.tag! 'clientLibrary' ,'Ruby Active Merchant' + xml.tag! 'merchantReferenceCode', options[:order_id] || generate_unique_id + xml.tag! 'clientLibrary', 'Ruby Active Merchant' xml.tag! 'clientLibraryVersion', VERSION - xml.tag! 'clientEnvironment' , RUBY_PLATFORM + xml.tag! 'clientEnvironment', RUBY_PLATFORM end def add_purchase_data(xml, money = 0, include_grand_total = false, options={}) xml.tag! 'purchaseTotals' do xml.tag! 'currency', options[:currency] || currency(money) - xml.tag!('grandTotalAmount', amount(money)) if include_grand_total + xml.tag!('grandTotalAmount', localized_amount(money.to_i, options[:currency] || default_currency)) if include_grand_total end end @@ -409,11 +461,12 @@ def add_address(xml, payment_method, address, options, shipTo = false) xml.tag! 'city', address[:city] xml.tag! 'state', address[:state] xml.tag! 'postalCode', address[:zip] - xml.tag! 'country', address[:country] + xml.tag! 'country', lookup_country_code(address[:country]) unless address[:country].blank? xml.tag! 'company', address[:company] unless address[:company].blank? xml.tag! 'companyTaxID', address[:companyTaxID] unless address[:company_tax_id].blank? - xml.tag! 'phoneNumber', address[:phone_number] unless address[:phone_number].blank? - xml.tag! 'email', options[:email] + xml.tag! 'phoneNumber', address[:phone] unless address[:phone].blank? + xml.tag! 'email', options[:email] || 'null@cybersource.com' + xml.tag! 'ipAddress', options[:ip] unless options[:ip].blank? || shipTo xml.tag! 'driversLicenseNumber', options[:drivers_license_number] unless options[:drivers_license_number].blank? xml.tag! 'driversLicenseState', options[:drivers_license_state] unless options[:drivers_license_state].blank? end @@ -424,11 +477,39 @@ def add_creditcard(xml, creditcard) xml.tag! 'accountNumber', creditcard.number xml.tag! 'expirationMonth', format(creditcard.month, :two_digits) xml.tag! 'expirationYear', format(creditcard.year, :four_digits) - xml.tag!('cvNumber', creditcard.verification_value) unless (@options[:ignore_cvv] || creditcard.verification_value.blank? ) + xml.tag!('cvNumber', creditcard.verification_value) unless @options[:ignore_cvv] || creditcard.verification_value.blank? xml.tag! 'cardType', @@credit_card_codes[card_brand(creditcard).to_sym] end end + def add_decision_manager_fields(xml, options) + return unless options[:decision_manager_enabled] + + xml.tag! 'decisionManager' do + xml.tag! 'enabled', options[:decision_manager_enabled] if options[:decision_manager_enabled] + xml.tag! 'profile', options[:decision_manager_profile] if options[:decision_manager_profile] + end + end + + def add_issuer_additional_data(xml, options) + return unless options[:issuer_additional_data] + + xml.tag! 'issuer' do + xml.tag! 'additionalData', options[:issuer_additional_data] + end + end + + def add_mdd_fields(xml, options) + return unless options.keys.any? { |key| key.to_s.start_with?('mdd_field') } + + xml.tag! 'merchantDefinedData' do + (1..100).each do |each| + key = "mdd_field_#{each}".to_sym + xml.tag!("field#{each}", options[key]) if options[key] + end + end + end + def add_check(xml, check) xml.tag! 'check' do xml.tag! 'accountNumber', check.account_number @@ -444,8 +525,67 @@ def add_tax_service(xml) end end - def add_auth_service(xml) - xml.tag! 'ccAuthService', {'run' => 'true'} + def add_auth_service(xml, payment_method, options) + if network_tokenization?(payment_method) + add_auth_network_tokenization(xml, payment_method, options) + else + xml.tag! 'ccAuthService', {'run' => 'true'} do + check_for_stored_cred_commerce_indicator(xml, options) + end + end + end + + def check_for_stored_cred_commerce_indicator(xml, options) + return unless options[:stored_credential] + if commerce_indicator(options) + xml.tag!('commerceIndicator', commerce_indicator(options)) + end + end + + def commerce_indicator(options) + return if options[:stored_credential][:initial_transaction] + case options[:stored_credential][:reason_type] + when 'installment' then 'install' + when 'recurring' then 'recurring' + end + end + + def network_tokenization?(payment_method) + payment_method.is_a?(NetworkTokenizationCreditCard) + end + + def add_auth_network_tokenization(xml, payment_method, options) + return unless network_tokenization?(payment_method) + + case card_brand(payment_method).to_sym + when :visa + xml.tag! 'ccAuthService', {'run' => 'true'} do + xml.tag!('cavv', payment_method.payment_cryptogram) + xml.tag!('commerceIndicator', 'vbv') + xml.tag!('xid', payment_method.payment_cryptogram) + end + when :master + xml.tag! 'ucaf' do + xml.tag!('authenticationData', payment_method.payment_cryptogram) + xml.tag!('collectionIndicator', '2') + end + xml.tag! 'ccAuthService', {'run' => 'true'} do + xml.tag!('commerceIndicator', 'spa') + end + when :american_express + cryptogram = Base64.decode64(payment_method.payment_cryptogram) + xml.tag! 'ccAuthService', {'run' => 'true'} do + xml.tag!('cavv', Base64.encode64(cryptogram[0...20])) + xml.tag!('commerceIndicator', 'aesk') + xml.tag!('xid', Base64.encode64(cryptogram[20...40])) + end + end + end + + def add_payment_network_token(xml) + xml.tag! 'paymentNetworkToken' do + xml.tag!('transactionType', '1') + end end def add_capture_service(xml, request_id, request_token) @@ -455,11 +595,11 @@ def add_capture_service(xml, request_id, request_token) end end - def add_purchase_service(xml, options) + def add_purchase_service(xml, payment_method, options) if options[:pinless_debit_card] xml.tag! 'pinlessDebitService', {'run' => 'true'} else - xml.tag! 'ccAuthService', {'run' => 'true'} + add_auth_service(xml, payment_method, options) xml.tag! 'ccCaptureService', {'run' => 'true'} end end @@ -510,17 +650,17 @@ def add_subscription(xml, options, reference = nil) xml.tag! 'recurringSubscriptionInfo' do if reference - _, subscription_id, _ = reference.split(";") + subscription_id = reference.split(';')[6] xml.tag! 'subscriptionID', subscription_id end xml.tag! 'status', options[:subscription][:status] if options[:subscription][:status] - xml.tag! 'amount', options[:subscription][:amount] if options[:subscription][:amount] + xml.tag! 'amount', localized_amount(options[:subscription][:amount].to_i, options[:currency] || default_currency) if options[:subscription][:amount] xml.tag! 'numberOfPayments', options[:subscription][:occurrences] if options[:subscription][:occurrences] xml.tag! 'automaticRenew', options[:subscription][:automatic_renew] if options[:subscription][:automatic_renew] xml.tag! 'frequency', options[:subscription][:frequency] if options[:subscription][:frequency] - xml.tag! 'startDate', options[:subscription][:start_date].strftime("%Y%m%d") if options[:subscription][:start_date] - xml.tag! 'endDate', options[:subscription][:end_date].strftime("%Y%m%d") if options[:subscription][:end_date] + xml.tag! 'startDate', options[:subscription][:start_date].strftime('%Y%m%d') if options[:subscription][:start_date] + xml.tag! 'endDate', options[:subscription][:end_date].strftime('%Y%m%d') if options[:subscription][:end_date] xml.tag! 'approvalRequired', options[:subscription][:approval_required] || false xml.tag! 'event', options[:subscription][:event] if options[:subscription][:event] xml.tag! 'billPayment', options[:subscription][:bill_payment] if options[:subscription][:bill_payment] @@ -529,13 +669,13 @@ def add_subscription(xml, options, reference = nil) def add_creditcard_payment_method(xml) xml.tag! 'subscription' do - xml.tag! 'paymentMethod', "credit card" + xml.tag! 'paymentMethod', 'credit card' end end def add_check_payment_method(xml) xml.tag! 'subscription' do - xml.tag! 'paymentMethod', "check" + xml.tag! 'paymentMethod', 'check' end end @@ -549,46 +689,86 @@ def add_payment_method_or_subscription(xml, money, payment_method_or_reference, add_check(xml, payment_method_or_reference) else add_address(xml, payment_method_or_reference, options[:billing_address], options) + add_address(xml, payment_method_or_reference, options[:shipping_address], options, true) add_purchase_data(xml, money, true, options) add_creditcard(xml, payment_method_or_reference) end end def add_validate_pinless_debit_service(xml) - xml.tag!'pinlessDebitValidateService', {'run' => 'true'} + xml.tag! 'pinlessDebitValidateService', {'run' => 'true'} + end + + def add_threeds_services(xml, options) + xml.tag! 'payerAuthEnrollService', {'run' => 'true'} if options[:payer_auth_enroll_service] + if options[:payer_auth_validate_service] + xml.tag! 'payerAuthValidateService', {'run' => 'true'} do + xml.tag! 'signedPARes', options[:pares] + end + end + end + + def lookup_country_code(country_field) + country_code = Country.find(country_field) rescue nil + country_code&.code(:alpha2) + end + + def add_stored_credential_options(xml, options={}) + return unless options[:stored_credential] + if options[:stored_credential][:initial_transaction] + xml.tag! 'subsequentAuthFirst', 'true' + elsif options[:stored_credential][:reason_type] == 'unscheduled' + xml.tag! 'subsequentAuth', 'true' + xml.tag! 'subsequentAuthTransactionID', options[:stored_credential][:network_transaction_id] + else + xml.tag! 'subsequentAuthTransactionID', options[:stored_credential][:network_transaction_id] + end end # Where we actually build the full SOAP request using builder def build_request(body, options) + xsd_version = test? ? TEST_XSD_VERSION : PRODUCTION_XSD_VERSION + xml = Builder::XmlMarkup.new :indent => 2 - xml.instruct! - xml.tag! 's:Envelope', {'xmlns:s' => 'http://schemas.xmlsoap.org/soap/envelope/'} do - xml.tag! 's:Header' do - xml.tag! 'wsse:Security', {'s:mustUnderstand' => '1', 'xmlns:wsse' => 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd'} do - xml.tag! 'wsse:UsernameToken' do - xml.tag! 'wsse:Username', @options[:login] - xml.tag! 'wsse:Password', @options[:password], 'Type' => 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText' - end + xml.instruct! + xml.tag! 's:Envelope', {'xmlns:s' => 'http://schemas.xmlsoap.org/soap/envelope/'} do + xml.tag! 's:Header' do + xml.tag! 'wsse:Security', {'s:mustUnderstand' => '1', 'xmlns:wsse' => 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd'} do + xml.tag! 'wsse:UsernameToken' do + xml.tag! 'wsse:Username', @options[:login] + xml.tag! 'wsse:Password', @options[:password], 'Type' => 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText' end end - xml.tag! 's:Body', {'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance', 'xmlns:xsd' => 'http://www.w3.org/2001/XMLSchema'} do - xml.tag! 'requestMessage', {'xmlns' => "urn:schemas-cybersource-com:transaction-data-#{XSD_VERSION}"} do - add_merchant_data(xml, options) - xml << body - end + end + xml.tag! 's:Body', {'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance', 'xmlns:xsd' => 'http://www.w3.org/2001/XMLSchema'} do + xml.tag! 'requestMessage', {'xmlns' => "urn:schemas-cybersource-com:transaction-data-#{xsd_version}"} do + add_merchant_data(xml, options) + xml << body end end + end xml.target! end # Contact CyberSource, make the SOAP request, and parse the reply into a # Response object - def commit(request, options) - response = parse(ssl_post(test? ? self.test_url : self.live_url, build_request(request, options))) + def commit(request, action, amount, options) + begin + raw_response = ssl_post(test? ? self.test_url : self.live_url, build_request(request, options)) + rescue ResponseError => e + raw_response = e.response.body + end + + begin + response = parse(raw_response) + rescue REXML::ParseException => e + response = { message: e.to_s } + end - success = response[:decision] == "ACCEPT" - message = @@response_codes[('r' + response[:reasonCode]).to_sym] rescue response[:message] - authorization = success ? [ options[:order_id], response[:requestID], response[:requestToken] ].compact.join(";") : nil + success = response[:decision] == 'ACCEPT' + message = response[:message] + + authorization = success ? authorization_from(response, action, amount, options) : nil Response.new(success, message, response, :test => test?, @@ -603,16 +783,17 @@ def commit(request, options) def parse(xml) reply = {} xml = REXML::Document.new(xml) - if root = REXML::XPath.first(xml, "//c:replyMessage") + if root = REXML::XPath.first(xml, '//c:replyMessage') root.elements.to_a.each do |node| - case node.name + case node.expanded_name when 'c:reasonCode' - reply[:message] = reply(node.text) + reply[:reasonCode] = node.text + reply[:message] = reason_message(node.text) else parse_element(reply, node) end end - elsif root = REXML::XPath.first(xml, "//soap:Fault") + elsif root = REXML::XPath.first(xml, '//soap:Fault') parse_element(reply, root) reply[:message] = "#{reply[:faultcode]}: #{reply[:faultstring]}" end @@ -621,17 +802,27 @@ def parse(xml) def parse_element(reply, node) if node.has_elements? - node.elements.each{|e| parse_element(reply, e) } + node.elements.each { |e| parse_element(reply, e) } else if node.parent.name =~ /item/ - parent = node.parent.name + (node.parent.attributes["id"] ? "_" + node.parent.attributes["id"] : '') - reply[(parent + '_' + node.name).to_sym] = node.text - else - reply[node.name.to_sym] = node.text + parent = node.parent.name + parent += '_' + node.parent.attributes['id'] if node.parent.attributes['id'] + parent += '_' end + reply["#{parent}#{node.name}".to_sym] ||= node.text end return reply end + + def reason_message(reason_code) + return if reason_code.blank? + @@response_codes[:"r#{reason_code}"] + end + + def authorization_from(response, action, amount, options) + [options[:order_id], response[:requestID], response[:requestToken], action, amount, + options[:currency], response[:subscriptionID]].join(';') + end end end end diff --git a/lib/active_merchant/billing/gateways/d_local.rb b/lib/active_merchant/billing/gateways/d_local.rb new file mode 100644 index 00000000000..19739582f00 --- /dev/null +++ b/lib/active_merchant/billing/gateways/d_local.rb @@ -0,0 +1,226 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class DLocalGateway < Gateway + self.test_url = 'https://sandbox.dlocal.com' + self.live_url = 'https://api.dlocal.com' + + self.supported_countries = ['AR', 'BR', 'CL', 'CO', 'MX', 'PE', 'UY', 'TR'] + self.default_currency = 'USD' + self.supported_cardtypes = [:visa, :master, :american_express, :discover, :jcb, :diners_club, :maestro, :naranja, :cabal] + + self.homepage_url = 'https://dlocal.com/' + self.display_name = 'dLocal' + + def initialize(options={}) + requires!(options, :login, :trans_key, :secret_key) + super + end + + def purchase(money, payment, options={}) + post = {} + add_auth_purchase_params(post, money, payment, 'purchase', options) + + commit('purchase', post, options) + end + + def authorize(money, payment, options={}) + post = {} + add_auth_purchase_params(post, money, payment, 'authorize', options) + + commit('authorize', post, options) + end + + def capture(money, authorization, options={}) + post = {} + post[:payment_id] = authorization + add_invoice(post, money, options) if money + commit('capture', post, options) + end + + def refund(money, authorization, options={}) + post = {} + post[:payment_id] = authorization + post[:notification_url] = options[:notification_url] + add_invoice(post, money, options) if money + commit('refund', post, options) + end + + def void(authorization, options={}) + post = {} + post[:payment_id] = authorization + commit('void', post, options) + end + + def verify(credit_card, options={}) + MultiResponse.run(:use_first_response) do |r| + r.process { authorize(100, credit_card, options) } + r.process(:ignore_result) { void(r.authorization, options) } + end + end + + def supports_scrubbing? + true + end + + def scrub(transcript) + transcript. + gsub(%r((X-Trans-Key: )\w+), '\1[FILTERED]'). + gsub(%r((\"number\\\":\\\")\d+), '\1[FILTERED]'). + gsub(%r((\"cvv\\\":\\\")\d+), '\1[FILTERED]') + end + + private + + def add_auth_purchase_params(post, money, card, action, options) + add_invoice(post, money, options) + post[:payment_method_id] = 'CARD' + post[:payment_method_flow] = 'DIRECT' + add_country(post, card, options) + add_payer(post, card, options) + add_card(post, card, action, options) + post[:order_id] = options[:order_id] || generate_unique_id + post[:description] = options[:description] if options[:description] + end + + def add_invoice(post, money, options) + post[:amount] = amount(money) + post[:currency] = (options[:currency] || currency(money)) + end + + def add_country(post, card, options) + return unless address = options[:billing_address] || options[:address] + post[:country] = lookup_country_code(address[:country]) + end + + def lookup_country_code(country) + Country.find(country).code(:alpha2).value + end + + def add_payer(post, card, options) + address = options[:billing_address] || options[:address] + post[:payer] = {} + post[:payer][:name] = card.name + post[:payer][:email] = options[:email] if options[:email] + post[:payer][:birth_date] = options[:birth_date] if options[:birth_date] + post[:payer][:phone] = address[:phone] if address && address[:phone] + post[:payer][:document] = options[:document] if options[:document] + post[:payer][:document2] = options[:document2] if options[:document2] + post[:payer][:user_reference] = options[:user_reference] if options[:user_reference] + post[:payer][:address] = add_address(post, card, options) + end + + def add_address(post, card, options) + return unless address = options[:billing_address] || options[:address] + address_object = {} + address_object[:state] = address[:state] if address[:state] + address_object[:city] = address[:city] if address[:city] + address_object[:zip_code] = address[:zip_code] if address[:zip_code] + address_object[:street] = address[:street] if address[:street] + address_object[:number] = address[:number] if address[:number] + address_object + end + + def add_card(post, card, action, options={}) + post[:card] = {} + post[:card][:holder_name] = card.name + post[:card][:expiration_month] = card.month + post[:card][:expiration_year] = card.year + post[:card][:number] = card.number + post[:card][:cvv] = card.verification_value + post[:card][:descriptor] = options[:dynamic_descriptor] if options[:dynamic_descriptor] + post[:card][:capture] = (action == 'purchase') + end + + def parse(body) + JSON.parse(body) + end + + def commit(action, parameters, options={}) + url = url(action, parameters, options) + post = post_data(action, parameters) + begin + raw = ssl_post(url, post, headers(post, options)) + response = parse(raw) + rescue ResponseError => e + raw = e.response.body + response = parse(raw) + end + + Response.new( + success_from(action, response), + message_from(action, response), + response, + authorization: authorization_from(response), + avs_result: AVSResult.new(code: response['some_avs_response_key']), + cvv_result: CVVResult.new(response['some_cvv_response_key']), + test: test?, + error_code: error_code_from(action, response) + ) + end + + # A refund may not be immediate, and return a status_code of 100, "Pending". + # Since we aren't handling async notifications of eventual success, + # we count 100 as a success. + def success_from(action, response) + return false unless response['status_code'] + ['100', '200', '400', '600'].include? response['status_code'].to_s + end + + def message_from(action, response) + response['status_detail'] || response['message'] + end + + def authorization_from(response) + response['id'] + end + + def error_code_from(action, response) + return if success_from(action, response) + code = response['status_code'] || response['code'] + code&.to_s + end + + def url(action, parameters, options={}) + "#{(test? ? test_url : live_url)}/#{endpoint(action, parameters, options)}/" + end + + def endpoint(action, parameters, options) + case action + when 'purchase' + 'secure_payments' + when 'authorize' + 'secure_payments' + when 'refund' + 'refunds' + when 'capture' + "payments/#{parameters[:payment_id]}/capture" + when 'void' + "payments/#{parameters[:payment_id]}/cancel" + end + end + + def headers(post, options={}) + timestamp = Time.now.utc.iso8601 + headers = { + 'Content-Type' => 'application/json', + 'X-Date' => timestamp, + 'X-Login' => @options[:login], + 'X-Trans-Key' => @options[:trans_key], + 'Authorization' => signature(post, timestamp) + } + headers.merge('X-Idempotency-Key' => options[:idempotency_key]) if options[:idempotency_key] + headers + end + + def signature(post, timestamp) + content = "#{@options[:login]}#{timestamp}#{post}" + digest = OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new('sha256'), @options[:secret_key], content) + "V2-HMAC-SHA256, Signature: #{digest}" + end + + def post_data(action, parameters = {}) + parameters.to_json + end + end + end +end diff --git a/lib/active_merchant/billing/gateways/data_cash.rb b/lib/active_merchant/billing/gateways/data_cash.rb index 2c62302dd86..def3288babf 100644 --- a/lib/active_merchant/billing/gateways/data_cash.rb +++ b/lib/active_merchant/billing/gateways/data_cash.rb @@ -1,26 +1,19 @@ +require 'active_support/core_ext/string/access' + module ActiveMerchant module Billing class DataCashGateway < Gateway self.default_currency = 'GBP' self.supported_countries = ['GB'] - # From the DataCash docs; Page 13, the following cards are - # usable: - # American Express, ATM, Carte Blanche, Diners Club, Discover, - # EnRoute, GE Capital, JCB, Laser, Maestro, Mastercard, Solo, - # Switch, Visa, Visa Delta, VISA Electron, Visa Purchasing - # - # Note continuous authority is only supported for :visa, :master and :american_express card types - self.supported_cardtypes = [ :visa, :master, :american_express, :discover, :diners_club, :jcb, :maestro, :switch, :solo, :laser ] + self.supported_cardtypes = [ :visa, :master, :american_express, :discover, :diners_club, :jcb, :maestro ] self.homepage_url = 'http://www.datacash.com/' self.display_name = 'DataCash' - # Datacash server URLs self.test_url = 'https://testserver.datacash.com/Transaction' self.live_url = 'https://mars.transaction.datacash.com/Transaction' - # Different Card Transaction Types AUTH_TYPE = 'auth' CANCEL_TYPE = 'cancel' FULFILL_TYPE = 'fulfill' @@ -28,44 +21,14 @@ class DataCashGateway < Gateway REFUND_TYPE = 'refund' TRANSACTION_REFUND_TYPE = 'txn_refund' - # Constant strings for use in the ExtendedPolicy complex element for - # CV2 checks POLICY_ACCEPT = 'accept' POLICY_REJECT = 'reject' - # Datacash success code - DATACASH_SUCCESS = '1' - - # Creates a new DataCashGateway - # - # The gateway requires that a valid login and password be passed - # in the +options+ hash. - # - # ==== Options - # - # * :login -- The Datacash account login. - # * :password -- The Datacash account password. - # * :test => +true+ or +false+ -- Use the test or live Datacash url. - # def initialize(options = {}) requires!(options, :login, :password) super end - # Perform a purchase, which is essentially an authorization and capture in a single operation. - # - # ==== Parameters - # * money The amount to be authorized as an Integer value in cents. - # * authorization_or_credit_card:: The continuous authority reference or CreditCard details for the transaction. - # * options A hash of optional parameters. - # * :order_id A unique reference for this order (corresponds to merchantreference in datacash documentation) - # * :set_up_continuous_authority - # Set to true to set up a recurring historic transaction account be set up. - # Only supported for :visa, :master and :american_express card types - # See http://www.datacash.com/services/recurring/historic.php for more details of historic transactions. - # * :address:: billing address for card - # - # The continuous authority reference will be available in response#params['ca_reference'] if you have requested one def purchase(money, authorization_or_credit_card, options = {}) requires!(options, :order_id) @@ -78,22 +41,6 @@ def purchase(money, authorization_or_credit_card, options = {}) commit(request) end - # Performs an authorization, which reserves the funds on the customer's credit card, but does not - # charge the card. - # - # ==== Parameters - # - # * money The amount to be authorized as an Integer value in cents. - # * authorization_or_credit_card:: The continuous authority reference or CreditCard details for the transaction. - # * options A hash of optional parameters. - # * :order_id A unique reference for this order (corresponds to merchantreference in datacash documentation) - # * :set_up_continuous_authority:: - # Set to true to set up a recurring historic transaction account be set up. - # Only supported for :visa, :master and :american_express card types - # See http://www.datacash.com/services/recurring/historic.php for more details of historic transactions. - # * :address:: billing address for card - # - # The continuous authority reference will be available in response#params['ca_reference'] if you have requested one def authorize(money, authorization_or_credit_card, options = {}) requires!(options, :order_id) @@ -106,42 +53,22 @@ def authorize(money, authorization_or_credit_card, options = {}) commit(request) end - # Captures the funds from an authorized transaction. - # - # ==== Parameters - # - # * money -- The amount to be captured as anInteger value in cents. - # * authorization -- The authorization returned from the previous authorize request. def capture(money, authorization, options = {}) commit(build_void_or_capture_request(FULFILL_TYPE, money, authorization, options)) end - # Void a previous transaction - # - # ==== Parameters - # - # * authorization - The authorization returned from the previous authorize request. def void(authorization, options = {}) request = build_void_or_capture_request(CANCEL_TYPE, nil, authorization, options) commit(request) end - # Refund to a card - # - # ==== Parameters - # - # * money The amount to be refunded as an Integer value in cents. Set to nil for a full refund on existing transaction. - # * reference_or_credit_card The credit card you want to refund OR the datacash_reference for the existing transaction you are refunding - # * options Are ignored when refunding via reference to an existing transaction, otherwise - # * :order_id A unique reference for this order (corresponds to merchantreference in datacash documentation) - # * :address:: billing address for card def credit(money, reference_or_credit_card, options = {}) if reference_or_credit_card.is_a?(String) - deprecated CREDIT_DEPRECATION_MESSAGE + ActiveMerchant.deprecated CREDIT_DEPRECATION_MESSAGE refund(money, reference_or_credit_card) else - request = build_refund_request(money, reference_or_credit_card, options) + request = build_credit_request(money, reference_or_credit_card, options) commit(request) end end @@ -150,49 +77,30 @@ def refund(money, reference, options = {}) commit(build_transaction_refund_request(money, reference)) end + def supports_scrubbing? + true + end + + def scrub(transcript) + transcript. + gsub(/()\d+(<\/pan>)/i, '\1[FILTERED]\2'). + gsub(/()\d+(<\/cv2>)/i, '\1[FILTERED]\2'). + gsub(/().+(<\/password>)/i, '\1[FILTERED]\2') + end + private - # Create the xml document for a 'cancel' or 'fulfill' transaction. - # - # Final XML should look like: - # - # - # 99000001 - # ****** - # - # - # - # 25.00 - # - # - # 4900200000000001 - # A6 - # fulfill - # - # - # - # - # Parameters: - # * type must be FULFILL_TYPE or CANCEL_TYPE - # * money - optional - Integer value in cents - # * authorization - the Datacash authorization from a previous succesful authorize transaction - # * options - # * order_id - A unique reference for the transaction - # - # Returns: - # -Builder xml document - # - def build_void_or_capture_request(type, money, authorization, options) - reference, auth_code, ca_reference = authorization.to_s.split(';') + def build_void_or_capture_request(type, money, authorization, options) + parsed_authorization = parse_authorization_string(authorization) xml = Builder::XmlMarkup.new :indent => 2 xml.instruct! - xml.tag! :Request do + xml.tag! :Request, :version => '2' do add_authentication(xml) xml.tag! :Transaction do xml.tag! :HistoricTxn do - xml.tag! :reference, reference - xml.tag! :authcode, auth_code + xml.tag! :reference, parsed_authorization[:reference] + xml.tag! :authcode, parsed_authorization[:auth_code] xml.tag! :method, type end @@ -200,6 +108,7 @@ def build_void_or_capture_request(type, money, authorization, options) xml.tag! :TxnDetails do xml.tag! :merchantreference, format_reference_number(options[:order_id]) xml.tag! :amount, amount(money), :currency => options[:currency] || currency(money) + xml.tag! :capturemethod, 'ecomm' end end end @@ -207,71 +116,10 @@ def build_void_or_capture_request(type, money, authorization, options) xml.target! end - # Create the xml document for an 'auth' or 'pre' transaction with a credit card - # - # Final XML should look like: - # - # - # - # 99000000 - # ******* - # - # - # - # 123456 - # 10.00 - # - # - # - # 4444********1111 - # 03/04 - # - # Flat 7 - # 89 Jumble - # Street - # Mytown - # AV12FR - # 123 - # - # - # - # - # - # - # - # auth - # - # - # - # - # Parameters: - # -type must be 'auth' or 'pre' - # -money - A money object with the price and currency - # -credit_card - The credit_card details to use - # -options: - # :order_id is the merchant reference number - # :billing_address is the billing address for the cc - # :address is the delivery address - # - # Returns: - # -xml: Builder document containing the markup - # def build_purchase_or_authorization_request_with_credit_card_request(type, money, credit_card, options) xml = Builder::XmlMarkup.new :indent => 2 xml.instruct! - xml.tag! :Request do + xml.tag! :Request, :version => '2' do add_authentication(xml) xml.tag! :Transaction do @@ -285,58 +133,25 @@ def build_purchase_or_authorization_request_with_credit_card_request(type, money xml.tag! :TxnDetails do xml.tag! :merchantreference, format_reference_number(options[:order_id]) xml.tag! :amount, amount(money), :currency => options[:currency] || currency(money) + xml.tag! :capturemethod, 'ecomm' end end end xml.target! end - # Create the xml document for an 'auth' or 'pre' transaction with - # continuous authorization - # - # Final XML should look like: - # - # - # - # - # - # 3851231 - # cont_auth - # 18.50 - # - # - # 4500200040925092 - # auth - # - # - # - # 99000001 - # mypasswd - # - # - # - # Parameters: - # -type must be 'auth' or 'pre' - # -money - A money object with the price and currency - # -authorization - The authorization containing a continuous authority reference previously set up on a credit card - # -options: - # :order_id is the merchant reference number - # - # Returns: - # -xml: Builder document containing the markup - # def build_purchase_or_authorization_request_with_continuous_authority_reference_request(type, money, authorization, options) - reference, auth_code, ca_reference = authorization.to_s.split(';') - raise ArgumentError, "The continuous authority reference is required for continuous authority transactions" if ca_reference.blank? + parsed_authorization = parse_authorization_string(authorization) + raise ArgumentError, 'The continuous authority reference is required for continuous authority transactions' if parsed_authorization[:ca_reference].blank? xml = Builder::XmlMarkup.new :indent => 2 xml.instruct! - xml.tag! :Request do + xml.tag! :Request, :version => '2' do add_authentication(xml) xml.tag! :Transaction do xml.tag! :ContAuthTxn, :type => 'historic' xml.tag! :HistoricTxn do - xml.tag! :reference, ca_reference + xml.tag! :reference, parsed_authorization[:ca_reference] xml.tag! :method, type end xml.tag! :TxnDetails do @@ -349,39 +164,21 @@ def build_purchase_or_authorization_request_with_continuous_authority_reference_ xml.target! end - # Create the xml document for a full or partial refund transaction with - # - # Final XML should look like: - # - # - # - # 99000001 - # ******* - # - # - # - # txn_refund - # 12345678 - # - # - # 10.00 - # - # - # - # - def build_transaction_refund_request(money, reference) + def build_transaction_refund_request(money, authorization) + parsed_authorization = parse_authorization_string(authorization) xml = Builder::XmlMarkup.new :indent => 2 xml.instruct! - xml.tag! :Request do + xml.tag! :Request, :version => '2' do add_authentication(xml) xml.tag! :Transaction do xml.tag! :HistoricTxn do - xml.tag! :reference, reference + xml.tag! :reference, parsed_authorization[:reference] xml.tag! :method, TRANSACTION_REFUND_TYPE end unless money.nil? xml.tag! :TxnDetails do xml.tag! :amount, amount(money) + xml.tag! :capturemethod, 'ecomm' end end end @@ -389,34 +186,10 @@ def build_transaction_refund_request(money, reference) xml.target! end - # Create the xml document for a full or partial refund with - # - # Final XML should look like: - # - # - # - # 99000001 - # ***** - # - # - # - # - # 633300*********1 - # 04/06 - # 01/04 - # - # refund - # - # - # 1000001 - # 95.99 - # - # - # - def build_refund_request(money, credit_card, options) + def build_credit_request(money, credit_card, options) xml = Builder::XmlMarkup.new :indent => 2 xml.instruct! - xml.tag! :Request do + xml.tag! :Request, :version => '2' do add_authentication(xml) xml.tag! :Transaction do xml.tag! :CardTxn do @@ -426,21 +199,13 @@ def build_refund_request(money, credit_card, options) xml.tag! :TxnDetails do xml.tag! :merchantreference, format_reference_number(options[:order_id]) xml.tag! :amount, amount(money) + xml.tag! :capturemethod, 'ecomm' end end end xml.target! end - - # Adds the authentication element to the passed builder xml doc - # - # Parameters: - # -xml: Builder document that is being built up - # - # Returns: - # -none: The results is stored in the passed xml document - # def add_authentication(xml) xml.tag! :Authentication do xml.tag! :client, @options[:login] @@ -448,34 +213,12 @@ def add_authentication(xml) end end - # Add credit_card details to the passed XML Builder doc - # - # Parameters: - # -xml: Builder document that is being built up - # -credit_card: ActiveMerchant::Billing::CreditCard object - # -billing_address: Hash containing all of the billing address details - # - # Returns: - # -none: The results is stored in the passed xml document - # def add_credit_card(xml, credit_card, address) - xml.tag! :Card do - # DataCash calls the CC number 'pan' xml.tag! :pan, credit_card.number xml.tag! :expirydate, format_date(credit_card.month, credit_card.year) - # optional values - for Solo etc - if [ 'switch', 'solo' ].include?(card_brand(credit_card).to_s) - - xml.tag! :issuenumber, credit_card.issue_number unless credit_card.issue_number.blank? - - if !credit_card.start_month.blank? && !credit_card.start_year.blank? - xml.tag! :startdate, format_date(credit_card.start_month, credit_card.start_year) - end - end - xml.tag! :Cv2Avs do xml.tag! :cv2, credit_card.verification_value if credit_card.verification_value? if address @@ -494,71 +237,45 @@ def add_credit_card(xml, credit_card, address) # a predefined one xml.tag! :ExtendedPolicy do xml.tag! :cv2_policy, - :notprovided => POLICY_REJECT, - :notchecked => POLICY_REJECT, - :matched => POLICY_ACCEPT, - :notmatched => POLICY_REJECT, - :partialmatch => POLICY_REJECT + :notprovided => POLICY_REJECT, + :notchecked => POLICY_REJECT, + :matched => POLICY_ACCEPT, + :notmatched => POLICY_REJECT, + :partialmatch => POLICY_REJECT xml.tag! :postcode_policy, - :notprovided => POLICY_ACCEPT, - :notchecked => POLICY_ACCEPT, - :matched => POLICY_ACCEPT, - :notmatched => POLICY_REJECT, - :partialmatch => POLICY_ACCEPT + :notprovided => POLICY_ACCEPT, + :notchecked => POLICY_ACCEPT, + :matched => POLICY_ACCEPT, + :notmatched => POLICY_REJECT, + :partialmatch => POLICY_ACCEPT xml.tag! :address_policy, - :notprovided => POLICY_ACCEPT, - :notchecked => POLICY_ACCEPT, - :matched => POLICY_ACCEPT, - :notmatched => POLICY_REJECT, - :partialmatch => POLICY_ACCEPT + :notprovided => POLICY_ACCEPT, + :notchecked => POLICY_ACCEPT, + :matched => POLICY_ACCEPT, + :notmatched => POLICY_REJECT, + :partialmatch => POLICY_ACCEPT end end end end - # Send the passed data to DataCash for processing - # - # Parameters: - # -request: The XML data that is to be sent to Datacash - # - # Returns: - # - ActiveMerchant::Billing::Response object - # def commit(request) response = parse(ssl_post(test? ? self.test_url : self.live_url, request)) - Response.new(response[:status] == DATACASH_SUCCESS, response[:reason], response, + Response.new(response[:status] == '1', response[:reason], response, :test => test?, :authorization => "#{response[:datacash_reference]};#{response[:authcode]};#{response[:ca_reference]}" ) end - # Returns a date string in the format Datacash expects - # - # Parameters: - # -month: integer, the month - # -year: integer, the year - # - # Returns: - # -String: date in MM/YY format - # def format_date(month, year) - "#{format(month,:two_digits)}/#{format(year, :two_digits)}" + "#{format(month, :two_digits)}/#{format(year, :two_digits)}" end - # Parse the datacash response and create a Response object - # - # Parameters: - # -body: The XML returned from Datacash - # - # Returns: - # -a hash with all of the values returned in the Datacash XML response - # def parse(body) - response = {} xml = REXML::Document.new(body) - root = REXML::XPath.first(xml, "//Response") + root = REXML::XPath.first(xml, '//Response') root.elements.to_a.each do |node| parse_element(response, node) @@ -567,24 +284,21 @@ def parse(body) response end - # Parse an xml element - # - # Parameters: - # -response: The hash that the values are being returned in - # -node: The node that is currently being read - # - # Returns: - # - none (results are stored in the passed hash) def parse_element(response, node) if node.has_elements? - node.elements.each{|e| parse_element(response, e) } + node.elements.each { |e| parse_element(response, e) } else response[node.name.underscore.to_sym] = node.text end end def format_reference_number(number) - number.to_s.gsub(/[^A-Za-z0-9]/, '').rjust(6, "0").first(30) + number.to_s.gsub(/[^A-Za-z0-9]/, '').rjust(6, '0').first(30) + end + + def parse_authorization_string(authorization) + reference, auth_code, ca_reference = authorization.to_s.split(';') + {:reference => reference, :auth_code => auth_code, :ca_reference => ca_reference} end end end diff --git a/lib/active_merchant/billing/gateways/decidir.rb b/lib/active_merchant/billing/gateways/decidir.rb new file mode 100644 index 00000000000..550d6bdb49e --- /dev/null +++ b/lib/active_merchant/billing/gateways/decidir.rb @@ -0,0 +1,233 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class DecidirGateway < Gateway + self.test_url = 'https://developers.decidir.com/api/v2' + self.live_url = 'https://live.decidir.com/api/v2' + + self.supported_countries = ['AR'] + self.money_format = :cents + self.default_currency = 'ARS' + self.supported_cardtypes = [:visa, :master, :american_express, :diners_club, :naranja, :cabal] + + self.homepage_url = 'http://www.decidir.com' + self.display_name = 'Decidir' + + STANDARD_ERROR_CODE_MAPPING = { + 1 => STANDARD_ERROR_CODE[:call_issuer], + 2 => STANDARD_ERROR_CODE[:call_issuer], + 3 => STANDARD_ERROR_CODE[:config_error], + 4 => STANDARD_ERROR_CODE[:pickup_card], + 5 => STANDARD_ERROR_CODE[:card_declined], + 7 => STANDARD_ERROR_CODE[:pickup_card], + 12 => STANDARD_ERROR_CODE[:processing_error], + 14 => STANDARD_ERROR_CODE[:invalid_number], + 28 => STANDARD_ERROR_CODE[:processing_error], + 38 => STANDARD_ERROR_CODE[:incorrect_pin], + 39 => STANDARD_ERROR_CODE[:invalid_number], + 43 => STANDARD_ERROR_CODE[:pickup_card], + 45 => STANDARD_ERROR_CODE[:card_declined], + 46 => STANDARD_ERROR_CODE[:invalid_number], + 47 => STANDARD_ERROR_CODE[:card_declined], + 48 => STANDARD_ERROR_CODE[:card_declined], + 49 => STANDARD_ERROR_CODE[:invalid_expiry_date], + 51 => STANDARD_ERROR_CODE[:card_declined], + 53 => STANDARD_ERROR_CODE[:card_declined], + 54 => STANDARD_ERROR_CODE[:expired_card], + 55 => STANDARD_ERROR_CODE[:incorrect_pin], + 56 => STANDARD_ERROR_CODE[:card_declined], + 57 => STANDARD_ERROR_CODE[:card_declined], + 76 => STANDARD_ERROR_CODE[:call_issuer], + 96 => STANDARD_ERROR_CODE[:processing_error], + 97 => STANDARD_ERROR_CODE[:processing_error], + } + + def initialize(options={}) + requires!(options, :api_key) + super + @options[:preauth_mode] ||= false + end + + def purchase(money, payment, options={}) + raise ArgumentError, 'Purchase is not supported on Decidir gateways configured with the preauth_mode option' if @options[:preauth_mode] + + post = {} + add_auth_purchase_params(post, money, payment, options) + commit(:post, 'payments', post) + end + + def authorize(money, payment, options={}) + raise ArgumentError, 'Authorize is not supported on Decidir gateways unless the preauth_mode option is enabled' unless @options[:preauth_mode] + + post = {} + add_auth_purchase_params(post, money, payment, options) + commit(:post, 'payments', post) + end + + def capture(money, authorization, options={}) + raise ArgumentError, 'Capture is not supported on Decidir gateways unless the preauth_mode option is enabled' unless @options[:preauth_mode] + + post = {} + add_amount(post, money, options) + commit(:put, "payments/#{authorization}", post) + end + + def refund(money, authorization, options={}) + post = {} + add_amount(post, money, options) + commit(:post, "payments/#{authorization}/refunds", post) + end + + def void(authorization, options={}) + post = {} + commit(:post, "payments/#{authorization}/refunds", post) + end + + def verify(credit_card, options={}) + raise ArgumentError, 'Verify is not supported on Decidir gateways unless the preauth_mode option is enabled' unless @options[:preauth_mode] + + MultiResponse.run(:use_first_response) do |r| + r.process { authorize(100, credit_card, options) } + r.process(:ignore_result) { void(r.authorization, options) } + end + end + + def supports_scrubbing? + true + end + + def scrub(transcript) + transcript. + gsub(%r((apikey: )\w+)i, '\1[FILTERED]'). + gsub(%r((\"card_number\\\":\\\")\d+), '\1[FILTERED]'). + gsub(%r((\"security_code\\\":\\\")\d+), '\1[FILTERED]'). + gsub(%r((\"emv_issuer_data\\\":\\\")\d+), '\1[FILTERED]') + end + + private + + def add_auth_purchase_params(post, money, credit_card, options) + post[:payment_method_id] = options[:payment_method_id] ? options[:payment_method_id].to_i : 1 + post[:site_transaction_id] = options[:order_id] + post[:bin] = credit_card.number[0..5] + post[:payment_type] = options[:payment_type] || 'single' + post[:installments] = options[:installments] ? options[:installments].to_i : 1 + post[:description] = options[:description] if options[:description] + post[:email] = options[:email] if options[:email] + post[:sub_payments] = [] + + add_invoice(post, money, options) + add_payment(post, credit_card, options) + end + + def add_invoice(post, money, options) + add_amount(post, money, options) + post[:currency] = (options[:currency] || currency(money)) + end + + def add_amount(post, money, options) + currency = (options[:currency] || currency(money)) + post[:amount] = localized_amount(money, currency).to_i + end + + def add_payment(post, credit_card, options) + card_data = {} + card_data[:card_number] = credit_card.number + card_data[:card_expiration_month] = format(credit_card.month, :two_digits) + card_data[:card_expiration_year] = format(credit_card.year, :two_digits) + card_data[:security_code] = credit_card.verification_value if credit_card.verification_value? + card_data[:card_holder_name] = credit_card.name if credit_card.name + + # additional data used for Visa transactions + card_data[:card_holder_door_number] = options[:card_holder_door_number].to_i if options[:card_holder_door_number] + card_data[:card_holder_birthday] = options[:card_holder_birthday] if options[:card_holder_birthday] + + card_data[:card_holder_identification] = {} + card_data[:card_holder_identification][:type] = options[:card_holder_identification_type] if options[:card_holder_identification_type] + card_data[:card_holder_identification][:number] = options[:card_holder_identification_number] if options[:card_holder_identification_number] + + post[:card_data] = card_data + end + + def headers(options = {}) + { + 'apikey' => @options[:api_key], + 'Content-type' => 'application/json', + 'Cache-Control' => 'no-cache' + } + end + + def commit(method, endpoint, parameters, options={}) + url = "#{(test? ? test_url : live_url)}/#{endpoint}" + + begin + raw_response = ssl_request(method, url, post_data(parameters), headers(options)) + response = parse(raw_response) + rescue ResponseError => e + raw_response = e.response.body + response = parse(raw_response) + end + + success = success_from(response) + Response.new( + success, + message_from(success, response), + response, + authorization: authorization_from(response), + test: test?, + error_code: success ? nil : error_code_from(response) + ) + end + + def post_data(parameters = {}) + parameters.to_json + end + + def parse(body) + JSON.parse(body) + rescue JSON::ParserError + { + 'message' => "A non-JSON response was received from Decidir where one was expected. The raw response was:\n\n#{body}" + } + end + + def message_from(success, response) + return response['status'] if success + return response['message'] if response['message'] + + message = nil + + if error = response.dig('status_details', 'error') + message = error.dig('reason', 'description') + elsif response['error_type'] + if response['validation_errors'] + message = response['validation_errors'].map { |errors| "#{errors['code']}: #{errors['param']}" }.join(', ') + end + message ||= response['error_type'] + end + + message + end + + def success_from(response) + response['status'] == 'approved' || response['status'] == 'pre_approved' + end + + def authorization_from(response) + response['id'] + end + + def error_code_from(response) + error_code = nil + if error = response.dig('status_details', 'error') + code = error.dig('reason', 'id') + error_code = STANDARD_ERROR_CODE_MAPPING[code] + error_code ||= error['type'] + elsif response['error_type'] + error_code = response['error_type'] if response['validation_errors'] + end + + error_code || STANDARD_ERROR_CODE[:processing_error] + end + end + end +end diff --git a/lib/active_merchant/billing/gateways/dibs.rb b/lib/active_merchant/billing/gateways/dibs.rb new file mode 100644 index 00000000000..e3936bc383f --- /dev/null +++ b/lib/active_merchant/billing/gateways/dibs.rb @@ -0,0 +1,199 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class DibsGateway < Gateway + self.display_name = 'DIBS' + self.homepage_url = 'http://www.dibspayment.com/' + + self.live_url = 'https://api.dibspayment.com/merchant/v1/JSON/Transaction/' + + self.supported_countries = ['US', 'FI', 'NO', 'SE', 'GB'] + self.default_currency = 'USD' + self.money_format = :cents + self.supported_cardtypes = [:visa, :master, :american_express, :discover] + + def initialize(options={}) + requires!(options, :merchant_id, :secret_key) + super + end + + def purchase(amount, payment_method, options={}) + MultiResponse.run(false) do |r| + r.process { authorize(amount, payment_method, options) } + r.process { capture(amount, r.authorization, options) } + end + end + + def authorize(amount, payment_method, options={}) + post = {} + add_amount(post, amount) + add_invoice(post, amount, options) + if payment_method.respond_to?(:number) + add_payment_method(post, payment_method, options) + commit(:authorize, post) + else + add_ticket_id(post, payment_method) + commit(:authorize_ticket, post) + end + end + + def capture(amount, authorization, options={}) + post = {} + add_amount(post, amount) + add_reference(post, authorization) + + commit(:capture, post) + end + + def void(authorization, options={}) + post = {} + add_reference(post, authorization) + + commit(:void, post) + end + + def refund(amount, authorization, options={}) + post = {} + add_amount(post, amount) + add_reference(post, authorization) + + commit(:refund, post) + end + + def verify(credit_card, options={}) + MultiResponse.run(:use_first_response) do |r| + r.process { authorize(100, credit_card, options) } + r.process(:ignore_result) { void(r.authorization, options) } + end + end + + def store(payment_method, options = {}) + post = {} + + add_invoice(post, 0, options) + add_payment_method(post, payment_method, options) + + commit(:store, post) + end + + def supports_scrubbing? + true + end + + def scrub(transcript) + transcript. + gsub(%r(("cardNumber\\?":\\?")[^"]*)i, '\1[FILTERED]'). + gsub(%r(("cvc\\?":\\?")[^"]*)i, '\1[FILTERED]') + end + + private + + CURRENCY_CODES = Hash.new { |h, k| raise ArgumentError.new("Unsupported currency: #{k}") } + CURRENCY_CODES['USD'] = '840' + CURRENCY_CODES['DKK'] = '208' + CURRENCY_CODES['NOK'] = '578' + CURRENCY_CODES['SEK'] = '752' + CURRENCY_CODES['GBP'] = '826' + CURRENCY_CODES['EUR'] = '978' + + def add_invoice(post, money, options) + post[:orderId] = options[:order_id] || generate_unique_id + post[:currency] = CURRENCY_CODES[options[:currency] || currency(money)] + end + + def add_ticket_id(post, payment_method) + post[:ticketId] = payment_method + end + + def add_payment_method(post, payment_method, options) + post[:cardNumber] = payment_method.number + post[:cvc] = payment_method.verification_value if payment_method.verification_value + post[:expYear] = format(payment_method.year, :two_digits) + post[:expMonth] = payment_method.month + post[:clientIp] = options[:ip] || '127.0.0.1' + post[:test] = true if test? + end + + def add_reference(post, authorization) + post[:transactionId] = authorization + end + + def add_amount(post, amount) + post[:amount] = amount + end + + ACTIONS = { + authorize: 'AuthorizeCard', + authorize_ticket: 'AuthorizeTicket', + capture: 'CaptureTransaction', + void: 'CancelTransaction', + refund: 'RefundTransaction', + store: 'CreateTicket' + } + + def commit(action, post) + post[:merchantId] = @options[:merchant_id] + + data = build_request(post) + raw = parse(ssl_post(url(action), "request=#{data}", headers)) + succeeded = success_from(raw) + Response.new( + succeeded, + message_from(succeeded, raw), + raw, + authorization: authorization_from(post, raw), + test: test? + ) + rescue JSON::ParserError + unparsable_response(raw) + end + + def headers + { + 'Content-Type' => 'application/x-www-form-urlencoded' + } + end + + def build_request(post) + add_hmac(post) + post.to_json + end + + def add_hmac(post) + data = post.sort.collect { |key, value| "#{key}=#{value}" }.join('&') + digest = OpenSSL::Digest.new('sha256') + key = [@options[:secret_key]].pack('H*') + post[:MAC] = OpenSSL::HMAC.hexdigest(digest, key, data) + end + + def url(action) + live_url + ACTIONS[action] + end + + def parse(body) + JSON.parse(body) + end + + def success_from(raw_response) + raw_response['status'] == 'ACCEPT' + end + + def message_from(succeeded, response) + if succeeded + 'Succeeded' + else + response['status'] + ': ' + response['declineReason'] || 'Unable to read error message' + end + end + + def authorization_from(request, response) + response['transactionId'] || response['ticketId'] || request[:transactionId] + end + + def unparsable_response(raw_response) + message = 'Invalid JSON response received from Dibs. Please contact Dibs if you continue to receive this message.' + message += " (The raw response returned by the API was #{raw_response.inspect})" + return Response.new(false, message) + end + end + end +end diff --git a/lib/active_merchant/billing/gateways/digitzs.rb b/lib/active_merchant/billing/gateways/digitzs.rb new file mode 100644 index 00000000000..bbc82d4a2b8 --- /dev/null +++ b/lib/active_merchant/billing/gateways/digitzs.rb @@ -0,0 +1,292 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class DigitzsGateway < Gateway + include Empty + + self.test_url = 'https://beta.digitzsapi.com/sandbox' + self.live_url = 'https://beta.digitzsapi.com/v3' + + self.supported_countries = ['US'] + self.default_currency = 'USD' + self.supported_cardtypes = [:visa, :master, :american_express, :discover] + self.money_format = :cents + + self.homepage_url = 'https://digitzs.com' + self.display_name = 'Digitzs' + + def initialize(options={}) + requires!(options, :app_key, :api_key) + super + end + + def purchase(money, payment, options={}) + MultiResponse.run do |r| + r.process { commit('auth/token', app_token_request(options)) } + r.process { commit('payments', purchase_request(money, payment, options), options.merge({ app_token: app_token_from(r) })) } + end + end + + def refund(money, authorization, options={}) + MultiResponse.run do |r| + r.process { commit('auth/token', app_token_request(options)) } + r.process { commit('payments', refund_request(money, authorization, options), options.merge({ app_token: app_token_from(r) })) } + end + end + + def store(payment, options = {}) + MultiResponse.run do |r| + r.process { commit('auth/token', app_token_request(options)) } + options[:app_token] = app_token_from(r) + + if options[:customer_id].present? + customer_id = check_customer_exists(options) + + if customer_id + r.process { add_credit_card_to_customer(payment, options) } + else + r.process { add_customer_with_credit_card(payment, options) } + end + else + r.process { add_customer_with_credit_card(payment, options) } + end + end + end + + def supports_scrubbing? + true + end + + def scrub(transcript) + transcript. + gsub(%r((Authorization: Bearer ).+), '\1[FILTERED]'). + gsub(%r((X-Api-Key: )\w+), '\1[FILTERED]'). + gsub(%r((\"id\\\":\\\").+), '\1[FILTERED]'). + gsub(%r((\"appKey\\\":\\\").+), '\1[FILTERED]'). + gsub(%r((\"appToken\\\":\\\").+), '\1[FILTERED]'). + gsub(%r((\"code\\\":\\\")\d+), '\1[FILTERED]'). + gsub(%r((\"number\\\":\\\")\d+), '\1[FILTERED]') + end + + private + + def new_post + { + data: { + attributes: {} + } + } + end + + def add_split(post, options) + return unless options[:payment_type] == 'card_split' || options[:payment_type] == 'token_split' + + post[:data][:attributes][:split] = { + merchantId: options[:split_merchant_id], + amount: amount(options[:split_amount]) + } + end + + def add_payment(post, payment, options) + if payment.is_a? String + customer_id, token = split_authorization(payment) + post[:data][:attributes][:token] = { + customerId: customer_id, + tokenId: token + } + else + post[:data][:attributes][:card] = { + type: payment.brand, + holder: payment.name, + number: payment.number, + expiry: expdate(payment), + code: payment.verification_value + } + end + end + + def add_transaction(post, money, options) + post[:data][:attributes][:transaction] = { + amount: amount(money), + currency: (options[:currency] || currency(money)), + invoice: options[:order_id] || generate_unique_id + } + end + + def add_address(post, options) + if address = options[:billing_address] || options[:address] + post[:data][:attributes][:billingAddress] = { + line1: address[:address1] || '', + line2: address[:address2] || '', + city: address[:city] || '', + state: address[:state] || '', + zip: address[:zip] || '', + country: address['country'] || 'USA' + } + end + end + + def app_token_request(options) + post = new_post + post[:data][:type] = 'auth' + post[:data][:attributes] = { appKey: @options[:app_key] } + + post + end + + def purchase_request(money, payment, options) + post = new_post + post[:data][:type] = 'payments' + post[:data][:attributes][:merchantId] = options[:merchant_id] + post[:data][:attributes][:paymentType] = determine_payment_type(payment, options) + add_split(post, options) + add_payment(post, payment, options) + add_transaction(post, money, options) + add_address(post, options) + + post + end + + def refund_request(money, authorization, options) + post = new_post + post[:data][:type] = 'payments' + post[:data][:attributes][:merchantId] = options[:merchant_id] + post[:data][:attributes][:paymentType] = 'cardRefund' + post[:data][:attributes][:originalTransaction] = {id: authorization} + add_transaction(post, money, options) + + post + end + + def create_customer_request(payment, options) + post = new_post + post[:data][:type] = 'customers' + post[:data][:attributes] = { + merchantId: options[:merchant_id], + name: payment.name, + externalId: SecureRandom.hex(16) + } + + post + end + + def create_token_request(payment, options) + post = new_post + post[:data][:type] = 'tokens' + post[:data][:attributes] = { + tokenType: 'card', + customerId: options[:customer_id], + label: 'Credit Card', + } + add_payment(post, payment, options) + add_address(post, options) + + post + end + + def check_customer_exists(options = {}) + url = (test? ? test_url : live_url) + response = parse(ssl_get(url + "/customers/#{options[:customer_id]}", headers(options))) + + return response.try(:[], 'data').try(:[], 'customerId') if success_from(response) + return nil + end + + def add_credit_card_to_customer(payment, options = {}) + commit('tokens', create_token_request(payment, options), options) + end + + def add_customer_with_credit_card(payment, options = {}) + customer_response = commit('customers', create_customer_request(payment, options), options) + options[:customer_id] = customer_response.authorization + commit('tokens', create_token_request(payment, options), options) + end + + def parse(body) + JSON.parse(body) + end + + def commit(action, parameters, options={}) + url = (test? ? test_url : live_url) + response = parse(ssl_post(url + "/#{action}", parameters.to_json, headers(options))) + + Response.new( + success_from(response), + message_from(response), + response, + authorization: authorization_from(response), + avs_result: AVSResult.new(code: avs_result_from(response)), + cvv_result: CVVResult.new(cvv_result_from(response)), + test: test?, + error_code: error_code_from(response) + ) + end + + def success_from(response) + response['errors'].nil? && response['message'].nil? + end + + def message_from(response) + return response['message'] if response['message'] + return 'Success' if success_from(response) + response['errors'].map { |error_hash| error_hash['detail'] }.join(', ') + end + + def authorization_from(response) + if customer_id = response.try(:[], 'data').try(:[], 'attributes').try(:[], 'customerId') + "#{customer_id}|#{response.try(:[], "data").try(:[], "id")}" + else + response.try(:[], 'data').try(:[], 'id') + end + end + + def avs_result_from(response) + response.try(:[], 'data').try(:[], 'attributes').try(:[], 'transaction').try(:[], 'avsResult') + end + + def cvv_result_from(response) + response.try(:[], 'data').try(:[], 'attributes').try(:[], 'transaction').try(:[], 'codeResult') + end + + def app_token_from(response) + response.params.try(:[], 'data').try(:[], 'attributes').try(:[], 'appToken') + end + + def headers(options) + headers = { + 'Content-Type' => 'application/json', + 'x-api-key' => @options[:api_key] + } + + headers['Authorization'] = "Bearer #{options[:app_token]}" if options[:app_token] + headers + end + + def error_code_from(response) + unless success_from(response) + response['errors'].nil? ? response['message'] : response['errors'].map { |error_hash| error_hash['code'] }.join(', ') + end + end + + def split_authorization(authorization) + customer_id, token = authorization.split('|') + [customer_id, token] + end + + def determine_payment_type(payment, options) + return 'cardSplit' if options[:payment_type] == 'card_split' + return 'tokenSplit' if options[:payment_type] == 'token_split' + return 'token' if payment.is_a? String + 'card' + end + + def handle_response(response) + case response.code.to_i + when 200..499 + response.body + else + raise ResponseError.new(response) + end + end + end + end +end diff --git a/lib/active_merchant/billing/gateways/ebanx.rb b/lib/active_merchant/billing/gateways/ebanx.rb new file mode 100644 index 00000000000..b7d1dc15171 --- /dev/null +++ b/lib/active_merchant/billing/gateways/ebanx.rb @@ -0,0 +1,296 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class EbanxGateway < Gateway + self.test_url = 'https://sandbox.ebanxpay.com/ws/' + self.live_url = 'https://api.ebanxpay.com/ws/' + + self.supported_countries = ['BR', 'MX', 'CO', 'CL', 'AR'] + self.default_currency = 'USD' + self.supported_cardtypes = [:visa, :master, :american_express, :discover, :diners_club] + + self.homepage_url = 'http://www.ebanx.com/' + self.display_name = 'EBANX' + + CARD_BRAND = { + visa: 'visa', + master: 'master_card', + american_express: 'amex', + discover: 'discover', + diners_club: 'diners' + } + + URL_MAP = { + purchase: 'direct', + authorize: 'direct', + capture: 'capture', + refund: 'refund', + void: 'cancel', + store: 'token' + } + + HTTP_METHOD = { + purchase: :post, + authorize: :post, + capture: :get, + refund: :post, + void: :get, + store: :post + } + + def initialize(options={}) + requires!(options, :integration_key) + super + end + + def purchase(money, payment, options={}) + post = { payment: {} } + add_integration_key(post) + add_operation(post) + add_invoice(post, money, options) + add_customer_data(post, payment, options) + add_card_or_token(post, payment) + add_address(post, options) + add_customer_responsible_person(post, payment, options) + + commit(:purchase, post) + end + + def authorize(money, payment, options={}) + post = { payment: {} } + add_integration_key(post) + add_operation(post) + add_invoice(post, money, options) + add_customer_data(post, payment, options) + add_card_or_token(post, payment) + add_address(post, options) + add_customer_responsible_person(post, payment, options) + post[:payment][:creditcard][:auto_capture] = false + + commit(:authorize, post) + end + + def capture(money, authorization, options={}) + post = {} + add_integration_key(post) + post[:hash] = authorization + post[:amount] = amount(money) + + commit(:capture, post) + end + + def refund(money, authorization, options={}) + post = {} + add_integration_key(post) + add_operation(post) + add_authorization(post, authorization) + post[:amount] = amount(money) + post[:description] = options[:description] + + commit(:refund, post) + end + + def void(authorization, options={}) + post = {} + add_integration_key(post) + add_authorization(post, authorization) + + commit(:void, post) + end + + def store(credit_card, options={}) + post = {} + add_integration_key(post) + add_payment_details(post, credit_card) + post[:country] = customer_country(options) + + commit(:store, post) + end + + def verify(credit_card, options={}) + MultiResponse.run(:use_first_response) do |r| + r.process { authorize(100, credit_card, options) } + r.process(:ignore_result) { void(r.authorization, options) } + end + end + + def supports_scrubbing? + true + end + + def scrub(transcript) + transcript. + gsub(/(integration_key\\?":\\?")(\d*)/, '\1[FILTERED]'). + gsub(/(card_number\\?":\\?")(\d*)/, '\1[FILTERED]'). + gsub(/(card_cvv\\?":\\?")(\d*)/, '\1[FILTERED]') + end + + private + + def add_integration_key(post) + post[:integration_key] = @options[:integration_key].to_s + end + + def add_operation(post) + post[:operation] = 'request' + end + + def add_authorization(post, authorization) + post[:hash] = authorization + end + + def add_customer_data(post, payment, options) + post[:payment][:name] = customer_name(payment, options) + post[:payment][:email] = options[:email] || 'unspecified@example.com' + post[:payment][:document] = options[:document] + post[:payment][:birth_date] = options[:birth_date] if options[:birth_date] + end + + def add_customer_responsible_person(post, payment, options) + post[:payment][:person_type] = options[:person_type] if options[:person_type] + if options[:person_type]&.casecmp('business')&.zero? + post[:payment][:responsible] = {} + post[:payment][:responsible][:name] = options[:responsible_name] if options[:responsible_name] + post[:payment][:responsible][:document] = options[:responsible_document] if options[:responsible_document] + post[:payment][:responsible][:birth_date] = options[:responsible_birth_date] if options[:responsible_birth_date] + end + end + + def add_address(post, options) + if address = options[:billing_address] || options[:address] + post[:payment][:address] = address[:address1].split[1..-1].join(' ') if address[:address1] + post[:payment][:street_number] = address[:address1].split.first if address[:address1] + post[:payment][:city] = address[:city] + post[:payment][:state] = address[:state] + post[:payment][:zipcode] = address[:zip] + post[:payment][:country] = address[:country].downcase + post[:payment][:phone_number] = address[:phone] + end + end + + def add_invoice(post, money, options) + post[:payment][:amount_total] = amount(money) + post[:payment][:currency_code] = (options[:currency] || currency(money)) + post[:payment][:merchant_payment_code] = options[:order_id] + post[:payment][:instalments] = options[:instalments] || 1 + end + + def add_card_or_token(post, payment) + if payment.is_a?(String) + payment, brand = payment.split('|') + end + post[:payment][:payment_type_code] = payment.is_a?(String) ? brand : CARD_BRAND[payment.brand.to_sym] + post[:payment][:creditcard] = payment_details(payment) + end + + def add_payment_details(post, payment) + post[:payment_type_code] = CARD_BRAND[payment.brand.to_sym] + post[:creditcard] = payment_details(payment) + end + + def payment_details(payment) + if payment.is_a?(String) + { token: payment } + else + { + card_number: payment.number, + card_name: payment.name, + card_due_date: "#{payment.month}/#{payment.year}", + card_cvv: payment.verification_value + } + end + end + + def parse(body) + JSON.parse(body) + end + + def commit(action, parameters) + url = url_for((test? ? test_url : live_url), action, parameters) + response = parse(ssl_request(HTTP_METHOD[action], url, post_data(action, parameters), {})) + + success = success_from(action, response) + + Response.new( + success, + message_from(response), + response, + authorization: authorization_from(action, parameters, response), + test: test?, + error_code: error_code_from(response, success) + ) + end + + def success_from(action, response) + if [:purchase, :capture, :refund].include?(action) + response.try(:[], 'payment').try(:[], 'status') == 'CO' + elsif action == :authorize + response.try(:[], 'payment').try(:[], 'status') == 'PE' + elsif action == :void + response.try(:[], 'payment').try(:[], 'status') == 'CA' + elsif action == :store + response.try(:[], 'status') == 'SUCCESS' + else + false + end + end + + def message_from(response) + return response['status_message'] if response['status'] == 'ERROR' + response.try(:[], 'payment').try(:[], 'transaction_status').try(:[], 'description') + end + + def authorization_from(action, parameters, response) + if action == :store + "#{response.try(:[], "token")}|#{CARD_BRAND[parameters[:payment_type_code].to_sym]}" + else + response.try(:[], 'payment').try(:[], 'hash') + end + end + + def post_data(action, parameters = {}) + return nil if requires_http_get(action) + return convert_to_url_form_encoded(parameters) if action == :refund + "request_body=#{parameters.to_json}" + end + + def url_for(hostname, action, parameters) + return "#{hostname}#{URL_MAP[action]}?#{convert_to_url_form_encoded(parameters)}" if requires_http_get(action) + "#{hostname}#{URL_MAP[action]}" + end + + def requires_http_get(action) + return true if [:capture, :void].include?(action) + false + end + + def convert_to_url_form_encoded(parameters) + parameters.map do |key, value| + next if value != false && value.blank? + "#{key}=#{value}" + end.compact.join('&') + end + + def error_code_from(response, success) + unless success + return response['status_code'] if response['status'] == 'ERROR' + response.try(:[], 'payment').try(:[], 'transaction_status').try(:[], 'code') + end + end + + def customer_country(options) + if country = options[:country] || (options[:billing_address][:country] if options[:billing_address]) + country.downcase + end + end + + def customer_name(payment, options) + address_name = options[:billing_address][:name] if options[:billing_address] && options[:billing_address][:name] + if payment.is_a?(String) + address_name || 'Not Provided' + else + payment.name + end + end + end + end +end diff --git a/lib/active_merchant/billing/gateways/efsnet.rb b/lib/active_merchant/billing/gateways/efsnet.rb index fcf28d8e076..ad16cfbf349 100644 --- a/lib/active_merchant/billing/gateways/efsnet.rb +++ b/lib/active_merchant/billing/gateways/efsnet.rb @@ -2,7 +2,6 @@ module ActiveMerchant #:nodoc: module Billing #:nodoc: - class EfsnetGateway < Gateway self.supported_countries = ['US'] self.supported_cardtypes = [:visa, :master, :american_express, :discover] @@ -36,7 +35,7 @@ def capture(money, identification, options = {}) def credit(money, identification_or_credit_card, options = {}) if identification_or_credit_card.is_a?(String) - deprecated CREDIT_DEPRECATION_MESSAGE + ActiveMerchant.deprecated CREDIT_DEPRECATION_MESSAGE # Perform authorization reversal refund(money, identification_or_credit_card, options) else @@ -54,7 +53,7 @@ def refund(money, reference, options = {}) def void(identification, options = {}) requires!(options, :order_id) - original_transaction_id, original_transaction_amount = identification.split(";") + original_transaction_id, _ = identification.split(';') commit(:void_transaction, {:reference_number => format_reference_number(options[:order_id]), :transaction_id => original_transaction_id}) end @@ -77,11 +76,11 @@ def system_check private def build_refund_or_settle_request(money, identification, options = {}) - original_transaction_id, original_transaction_amount = identification.split(";") + original_transaction_id, original_transaction_amount = identification.split(';') requires!(options, :order_id) - post = { + { :reference_number => format_reference_number(options[:order_id]), :transaction_amount => amount(money), :original_transaction_amount => original_transaction_amount, @@ -100,16 +99,16 @@ def build_credit_card_request(money, creditcard, options = {}) :client_ip_address => options[:ip] } - add_creditcard(post,creditcard) - add_address(post,options) + add_creditcard(post, creditcard) + add_address(post, options) post end def format_reference_number(number) - number.to_s.slice(0,12) + number.to_s.slice(0, 12) end - def add_address(post,options) + def add_address(post, options) if address = options[:billing_address] || options[:address] if address[:address2] post[:billing_address] = address[:address1].to_s << ' ' << address[:address2].to_s @@ -139,11 +138,10 @@ def add_creditcard(post, creditcard) post[:billing_name] = creditcard.name if creditcard.name post[:account_number] = creditcard.number post[:card_verification_value] = creditcard.verification_value if creditcard.verification_value? - post[:expiration_month] = sprintf("%.2i", creditcard.month) - post[:expiration_year] = sprintf("%.4i", creditcard.year)[-2..-1] + post[:expiration_month] = sprintf('%.2i', creditcard.month) + post[:expiration_year] = sprintf('%.4i', creditcard.year)[-2..-1] end - def commit(action, parameters) response = parse(ssl_post(test? ? self.test_url : self.live_url, post_data(action, parameters), 'Content-Type' => 'text/xml')) @@ -169,9 +167,7 @@ def parse(xml) xml = REXML::Document.new(xml) xml.elements.each('//Reply//TransactionReply/*') do |node| - response[node.name.underscore.to_sym] = normalize(node.text) - end unless xml.root.nil? response @@ -179,10 +175,10 @@ def parse(xml) def post_data(action, parameters = {}) xml = REXML::Document.new("") - root = xml.add_element("Request") - root.attributes["StoreID"] = options[:login] - root.attributes["StoreKey"] = options[:password] - root.attributes["ApplicationID"] = 'ot 1.0' + root = xml.add_element('Request') + root.attributes['StoreID'] = options[:login] + root.attributes['StoreKey'] = options[:password] + root.attributes['ApplicationID'] = 'ot 1.0' transaction = root.add_element(action.to_s.camelize) actions[action].each do |key| @@ -194,18 +190,7 @@ def post_data(action, parameters = {}) def message_from(message) return 'Unspecified error' if message.blank? - message.gsub(/[^\w]/, ' ').split.join(" ").capitalize - end - - # Make a ruby type out of the response string - def normalize(field) - case field - when "true" then true - when "false" then false - when "" then nil - when "null" then nil - else field - end + message.gsub(/[^\w]/, ' ').split.join(' ').capitalize end def actions @@ -215,15 +200,15 @@ def actions CREDIT_CARD_FIELDS = %w(AuthorizationNumber ClientIpAddress BillingAddress BillingCity BillingState BillingPostalCode BillingCountry BillingName CardVerificationValue ExpirationMonth ExpirationYear ReferenceNumber TransactionAmount AccountNumber ) ACTIONS = { - :credit_card_authorize => CREDIT_CARD_FIELDS, - :credit_card_charge => CREDIT_CARD_FIELDS, - :credit_card_voice_authorize => CREDIT_CARD_FIELDS, - :credit_card_capture => CREDIT_CARD_FIELDS, - :credit_card_credit => CREDIT_CARD_FIELDS + ["OriginalTransactionAmount"], - :credit_card_refund => %w(ReferenceNumber TransactionAmount OriginalTransactionAmount OriginalTransactionID ClientIpAddress), - :void_transaction => %w(ReferenceNumber TransactionID), - :credit_card_settle => %w(ReferenceNumber TransactionAmount OriginalTransactionAmount OriginalTransactionID ClientIpAddress), - :system_check => %w(SystemCheck), + :credit_card_authorize => CREDIT_CARD_FIELDS, + :credit_card_charge => CREDIT_CARD_FIELDS, + :credit_card_voice_authorize => CREDIT_CARD_FIELDS, + :credit_card_capture => CREDIT_CARD_FIELDS, + :credit_card_credit => CREDIT_CARD_FIELDS + ['OriginalTransactionAmount'], + :credit_card_refund => %w(ReferenceNumber TransactionAmount OriginalTransactionAmount OriginalTransactionID ClientIpAddress), + :void_transaction => %w(ReferenceNumber TransactionID), + :credit_card_settle => %w(ReferenceNumber TransactionAmount OriginalTransactionAmount OriginalTransactionID ClientIpAddress), + :system_check => %w(SystemCheck), } end end diff --git a/lib/active_merchant/billing/gateways/elavon.rb b/lib/active_merchant/billing/gateways/elavon.rb index ddc4559b937..7abaa0abfd7 100644 --- a/lib/active_merchant/billing/gateways/elavon.rb +++ b/lib/active_merchant/billing/gateways/elavon.rb @@ -1,42 +1,17 @@ -require File.dirname(__FILE__) + '/viaklix' +require 'active_merchant/billing/gateways/viaklix' module ActiveMerchant #:nodoc: module Billing #:nodoc: - # = Elavon Virtual Merchant Gateway - # - # == Example use: - # - # gateway = ActiveMerchant::Billing::ElavonGateway.new( - # :login => "my_virtual_merchant_id", - # :password => "my_virtual_merchant_pin", - # :user => "my_virtual_merchant_user_id" # optional - # ) - # - # # set up credit card obj as in main ActiveMerchant example - # creditcard = ActiveMerchant::Billing::CreditCard.new( - # :type => 'visa', - # :number => '41111111111111111', - # :month => 10, - # :year => 2011, - # :first_name => 'Bob', - # :last_name => 'Bobsen' - # ) - # - # # run request - # response = gateway.purchase(1000, creditcard) # authorize and capture 10 USD - # - # puts response.success? # Check whether the transaction was successful - # puts response.message # Retrieve the message returned by Elavon - # puts response.authorization # Retrieve the unique transaction ID returned by Elavon - # class ElavonGateway < Gateway + include Empty + class_attribute :test_url, :live_url, :delimiter, :actions - self.test_url = 'https://demo.myvirtualmerchant.com/VirtualMerchantDemo/process.do' - self.live_url = 'https://www.myvirtualmerchant.com/VirtualMerchant/process.do' + self.test_url = 'https://api.demo.convergepay.com/VirtualMerchantDemo/process.do' + self.live_url = 'https://api.convergepay.com/VirtualMerchant/process.do' self.display_name = 'Elavon MyVirtualMerchant' - self.supported_countries = ['US', 'CA'] + self.supported_countries = %w(US CA PR DE IE NO PL LU BE NL MX) self.supported_cardtypes = [:visa, :master, :american_express, :discover] self.homepage_url = 'http://www.elavon.com/' @@ -47,130 +22,140 @@ class ElavonGateway < Gateway :refund => 'CCRETURN', :authorize => 'CCAUTHONLY', :capture => 'CCFORCE', - :void => 'CCVOID' + :capture_complete => 'CCCOMPLETE', + :void => 'CCDELETE', + :store => 'CCGETTOKEN', + :update => 'CCUPDATETOKEN', } - # Initialize the Gateway - # - # The gateway requires that a valid login and password be passed - # in the +options+ hash. - # - # ==== Options - # - # * :login -- Merchant ID - # * :password -- PIN - # * :user -- Specify a subuser of the account (optional) - # * :test => +true+ or +false+ -- Force test transactions def initialize(options = {}) requires!(options, :login, :password) super end - # Make a purchase - def purchase(money, creditcard, options = {}) + def purchase(money, payment_method, options = {}) form = {} add_salestax(form, options) add_invoice(form, options) - add_creditcard(form, creditcard) + if payment_method.is_a?(String) + add_token(form, payment_method) + else + add_creditcard(form, payment_method) + end + add_currency(form, money, options) add_address(form, options) add_customer_data(form, options) add_test_mode(form, options) - commit(:purchase, money, form) + add_ip(form, options) + commit(:purchase, money, form, options) end - # Authorize a credit card for a given amount. - # - # ==== Parameters - # * money - The amount to be authorized as an Integer value in cents. - # * credit_card - The CreditCard details for the transaction. - # * options - # * :billing_address - The billing address for the cardholder. def authorize(money, creditcard, options = {}) form = {} add_salestax(form, options) add_invoice(form, options) add_creditcard(form, creditcard) + add_currency(form, money, options) add_address(form, options) add_customer_data(form, options) add_test_mode(form, options) - commit(:authorize, money, form) + add_ip(form, options) + commit(:authorize, money, form, options) end - # Capture authorized funds from a credit card. - # - # ==== Parameters - # * money - The amount to be captured as an Integer value in cents. - # * authorization - The approval code returned from the initial authorization. - # * options - # * :credit_card - The CreditCard details from the initial transaction (required). def capture(money, authorization, options = {}) - requires!(options, :credit_card) - form = {} - add_salestax(form, options) - add_approval_code(form, authorization) - add_invoice(form, options) - add_creditcard(form, options[:credit_card]) - add_customer_data(form, options) - add_test_mode(form, options) - commit(:capture, money, form) - end - - # Refund a transaction. - # - # This transaction indicates to the gateway that - # money should flow from the merchant to the customer. - # - # ==== Parameters - # - # * money -- The amount to be credited to the customer as an Integer value in cents. - # * identification -- The ID of the original transaction against which the refund is being issued. - # * options -- A hash of parameters. + if options[:credit_card] + action = :capture + add_salestax(form, options) + add_approval_code(form, authorization) + add_invoice(form, options) + add_creditcard(form, options[:credit_card]) + add_currency(form, money, options) + add_customer_data(form, options) + add_test_mode(form, options) + else + action = :capture_complete + add_txn_id(form, authorization) + add_partial_shipment_flag(form, options) + add_test_mode(form, options) + end + commit(action, money, form, options) + end + def refund(money, identification, options = {}) form = {} add_txn_id(form, identification) add_test_mode(form, options) - commit(:refund, money, form) + commit(:refund, money, form, options) end - # Void a previous transaction - # - # ==== Parameters - # - # * authorization - The authorization returned from the previous request. def void(identification, options = {}) form = {} add_txn_id(form, identification) add_test_mode(form, options) - commit(:void, nil, form) + commit(:void, nil, form, options) end - # Make a credit to a card. Use the refund method if you'd like to credit using - # previous transaction - # - # ==== Parameters - # * money - The amount to be credited as an Integer value in cents. - # * creditcard - The credit card to be credited. - # * options def credit(money, creditcard, options = {}) if creditcard.is_a?(String) - raise ArgumentError, "Reference credits are not supported. Please supply the original credit card or use the #refund method." + raise ArgumentError, 'Reference credits are not supported. Please supply the original credit card or use the #refund method.' end form = {} add_invoice(form, options) add_creditcard(form, creditcard) + add_currency(form, money, options) add_address(form, options) add_customer_data(form, options) add_test_mode(form, options) - commit(:credit, money, form) + commit(:credit, money, form, options) + end + + def verify(credit_card, options = {}) + MultiResponse.run(:use_first_response) do |r| + r.process { authorize(100, credit_card, options) } + r.process(:ignore_result) { void(r.authorization, options) } + end end + def store(creditcard, options = {}) + form = {} + add_creditcard(form, creditcard) + add_address(form, options) + add_customer_data(form, options) + add_test_mode(form, options) + add_verification(form, options) + form[:add_token] = 'Y' + commit(:store, nil, form, options) + end + + def update(token, creditcard, options = {}) + form = {} + add_token(form, token) + add_creditcard(form, creditcard) + add_address(form, options) + add_customer_data(form, options) + add_test_mode(form, options) + commit(:update, nil, form, options) + end + + def supports_scrubbing? + true + end + + def scrub(transcript) + transcript. + gsub(%r((&?ssl_pin=)[^&]*)i, '\1[FILTERED]'). + gsub(%r((&?ssl_card_number=)[^&\\n\r\n]*)i, '\1[FILTERED]'). + gsub(%r((&?ssl_cvv2cvc2=)[^&]*)i, '\1[FILTERED]') + end private - def add_invoice(form,options) - form[:invoice_number] = (options[:order_id] || options[:invoice]).to_s.slice(0, 10) - form[:description] = options[:description].to_s.slice(0, 255) + + def add_invoice(form, options) + form[:invoice_number] = truncate((options[:order_id] || options[:invoice]), 10) + form[:description] = truncate(options[:description], 255) end def add_approval_code(form, authorization) @@ -193,8 +178,17 @@ def add_creditcard(form, creditcard) add_verification_value(form, creditcard) end - form[:first_name] = creditcard.first_name.to_s.slice(0, 20) - form[:last_name] = creditcard.last_name.to_s.slice(0, 30) + form[:first_name] = truncate(creditcard.first_name, 20) + form[:last_name] = truncate(creditcard.last_name, 30) + end + + def add_currency(form, money, options) + currency = options[:currency] || currency(money) + form[:transaction_currency] = currency if currency && (@options[:multi_currency] || options[:multi_currency]) + end + + def add_token(form, token) + form[:token] = token end def add_verification_value(form, creditcard) @@ -203,60 +197,62 @@ def add_verification_value(form, creditcard) end def add_customer_data(form, options) - form[:email] = options[:email].to_s.slice(0, 100) unless options[:email].blank? - form[:customer_code] = options[:customer].to_s.slice(0, 10) unless options[:customer].blank? + form[:email] = truncate(options[:email], 100) unless empty?(options[:email]) + form[:customer_code] = truncate(options[:customer], 10) unless empty?(options[:customer]) + form[:customer_number] = options[:customer_number] unless empty?(options[:customer_number]) + options[:custom_fields]&.each do |key, value| + form[key.to_s] = value + end end def add_salestax(form, options) form[:salestax] = options[:tax] if options[:tax].present? end - def expdate(creditcard) - year = sprintf("%.4i", creditcard.year) - month = sprintf("%.2i", creditcard.month) - "#{month}#{year[2..3]}" - end - - def add_address(form,options) + def add_address(form, options) billing_address = options[:billing_address] || options[:address] if billing_address - form[:avs_address] = billing_address[:address1].to_s.slice(0, 30) - form[:address2] = billing_address[:address2].to_s.slice(0, 30) - form[:avs_zip] = billing_address[:zip].to_s.slice(0, 10) - form[:city] = billing_address[:city].to_s.slice(0, 30) - form[:state] = billing_address[:state].to_s.slice(0, 10) - form[:company] = billing_address[:company].to_s.slice(0, 50) - form[:phone] = billing_address[:phone].to_s.slice(0, 20) - form[:country] = billing_address[:country].to_s.slice(0, 50) + form[:avs_address] = truncate(billing_address[:address1], 30) + form[:address2] = truncate(billing_address[:address2], 30) + form[:avs_zip] = truncate(billing_address[:zip].to_s.gsub(/[^a-zA-Z0-9]/, ''), 9) + form[:city] = truncate(billing_address[:city], 30) + form[:state] = truncate(billing_address[:state], 10) + form[:company] = truncate(billing_address[:company], 50) + form[:phone] = truncate(billing_address[:phone], 20) + form[:country] = truncate(billing_address[:country], 50) end if shipping_address = options[:shipping_address] - first_name, last_name = parse_first_and_last_name(shipping_address[:name]) - form[:ship_to_first_name] = first_name.to_s.slice(0, 20) - form[:ship_to_last_name] = last_name.to_s.slice(0, 30) - form[:ship_to_address1] = shipping_address[:address1].to_s.slice(0, 30) - form[:ship_to_address2] = shipping_address[:address2].to_s.slice(0, 30) - form[:ship_to_city] = shipping_address[:city].to_s.slice(0, 30) - form[:ship_to_state] = shipping_address[:state].to_s.slice(0, 10) - form[:ship_to_company] = shipping_address[:company].to_s.slice(0, 50) - form[:ship_to_country] = shipping_address[:country].to_s.slice(0, 50) - form[:ship_to_zip] = shipping_address[:zip].to_s.slice(0, 10) + first_name, last_name = split_names(shipping_address[:name]) + form[:ship_to_first_name] = truncate(first_name, 20) + form[:ship_to_last_name] = truncate(last_name, 30) + form[:ship_to_address1] = truncate(shipping_address[:address1], 30) + form[:ship_to_address2] = truncate(shipping_address[:address2], 30) + form[:ship_to_city] = truncate(shipping_address[:city], 30) + form[:ship_to_state] = truncate(shipping_address[:state], 10) + form[:ship_to_company] = truncate(shipping_address[:company], 50) + form[:ship_to_country] = truncate(shipping_address[:country], 50) + form[:ship_to_zip] = truncate(shipping_address[:zip], 10) end end - def parse_first_and_last_name(value) - name = value.to_s.split(' ') - - last_name = name.pop || '' - first_name = name.join(' ') - [ first_name, last_name ] + def add_verification(form, options) + form[:verify] = 'Y' if options[:verify] end def add_test_mode(form, options) form[:test_mode] = 'TRUE' if options[:test_mode] end + def add_partial_shipment_flag(form, options) + form[:partial_shipment_flag] = 'Y' if options[:partial_shipment_flag] + end + + def add_ip(form, options) + form[:cardholder_ip] = options[:ip] if options.has_key?(:ip) + end + def message_from(response) success?(response) ? response['result_message'] : response['errorMessage'] end @@ -265,11 +261,11 @@ def success?(response) !response.has_key?('errorMessage') end - def commit(action, money, parameters) + def commit(action, money, parameters, options) parameters[:amount] = amount(money) parameters[:transaction_type] = self.actions[action] - response = parse( ssl_post(test? ? self.test_url : self.live_url, post_data(parameters)) ) + response = parse(ssl_post(test? ? self.test_url : self.live_url, post_data(parameters, options))) Response.new(response['result'] == '0', message_from(response), response, :test => @options[:test] || test?, @@ -279,10 +275,23 @@ def commit(action, money, parameters) ) end - def post_data(parameters) + def post_data(parameters, options) result = preamble result.merge!(parameters) - result.collect { |key, value| "ssl_#{key}=#{CGI.escape(value.to_s)}" }.join("&") + result.collect { |key, value| post_data_string(key, value, options) }.join('&') + end + + def post_data_string(key, value, options) + if custom_field?(key, options) + "#{key}=#{CGI.escape(value.to_s)}" + else + "ssl_#{key}=#{CGI.escape(value.to_s)}" + end + end + + def custom_field?(field_name, options) + return true if options[:custom_fields]&.include?(field_name.to_sym) + field_name == :customer_number end def preamble @@ -293,20 +302,19 @@ def preamble 'result_format' => 'ASCII' } - result['user_id'] = @options[:user] unless @options[:user].blank? + result['user_id'] = @options[:user] unless empty?(@options[:user]) result end def parse(msg) resp = {} - msg.split(self.delimiter).collect{|li| - key, value = li.split("=") - resp[key.strip.gsub(/^ssl_/, '')] = value.to_s.strip - } + msg.split(self.delimiter).collect { |li| + key, value = li.split('=') + resp[key.to_s.strip.gsub(/^ssl_/, '')] = value.to_s.strip + } resp end end end end - diff --git a/lib/active_merchant/billing/gateways/element.rb b/lib/active_merchant/billing/gateways/element.rb new file mode 100644 index 00000000000..a82803884ba --- /dev/null +++ b/lib/active_merchant/billing/gateways/element.rb @@ -0,0 +1,356 @@ +require 'nokogiri' +require 'securerandom' + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class ElementGateway < Gateway + self.test_url = 'https://certtransaction.elementexpress.com/express.asmx' + self.live_url = 'https://transaction.elementexpress.com/express.asmx' + + self.supported_countries = ['US'] + self.default_currency = 'USD' + self.supported_cardtypes = [:visa, :master, :american_express, :discover, :diners_club, :jcb] + + self.homepage_url = 'http://www.elementps.com' + self.display_name = 'Element' + + SERVICE_TEST_URL = 'https://certservices.elementexpress.com/express.asmx' + SERVICE_LIVE_URL = 'https://services.elementexpress.com/express.asmx' + + def initialize(options={}) + requires!(options, :account_id, :account_token, :application_id, :acceptor_id, :application_name, :application_version) + super + end + + def purchase(money, payment, options={}) + action = payment.is_a?(Check) ? 'CheckSale' : 'CreditCardSale' + + request = build_soap_request do |xml| + xml.send(action, xmlns: 'https://transaction.elementexpress.com') do + add_credentials(xml) + add_payment_method(xml, payment) + add_transaction(xml, money, options) + add_terminal(xml, options) + add_address(xml, options) + end + end + + commit(action, request, money) + end + + def authorize(money, payment, options={}) + request = build_soap_request do |xml| + xml.CreditCardAuthorization(xmlns: 'https://transaction.elementexpress.com') do + add_credentials(xml) + add_payment_method(xml, payment) + add_transaction(xml, money, options) + add_terminal(xml, options) + add_address(xml, options) + end + end + + commit('CreditCardAuthorization', request, money) + end + + def capture(money, authorization, options={}) + trans_id, _ = split_authorization(authorization) + options[:trans_id] = trans_id + + request = build_soap_request do |xml| + xml.CreditCardAuthorizationCompletion(xmlns: 'https://transaction.elementexpress.com') do + add_credentials(xml) + add_transaction(xml, money, options) + add_terminal(xml, options) + end + end + + commit('CreditCardAuthorizationCompletion', request, money) + end + + def refund(money, authorization, options={}) + trans_id, _ = split_authorization(authorization) + options[:trans_id] = trans_id + + request = build_soap_request do |xml| + xml.CreditCardReturn(xmlns: 'https://transaction.elementexpress.com') do + add_credentials(xml) + add_transaction(xml, money, options) + add_terminal(xml, options) + end + end + + commit('CreditCardReturn', request, money) + end + + def void(authorization, options={}) + trans_id, trans_amount = split_authorization(authorization) + options.merge!({trans_id: trans_id, trans_amount: trans_amount, reversal_type: 'Full'}) + + request = build_soap_request do |xml| + xml.CreditCardReversal(xmlns: 'https://transaction.elementexpress.com') do + add_credentials(xml) + add_transaction(xml, trans_amount, options) + add_terminal(xml, options) + end + end + + commit('CreditCardReversal', request, trans_amount) + end + + def store(payment, options = {}) + request = build_soap_request do |xml| + xml.PaymentAccountCreate(xmlns: 'https://services.elementexpress.com') do + add_credentials(xml) + add_payment_method(xml, payment) + add_payment_account(xml, payment, options[:payment_account_reference_number] || SecureRandom.hex(20)) + add_address(xml, options) + end + end + + commit('PaymentAccountCreate', request, nil) + end + + def verify(credit_card, options={}) + MultiResponse.run(:use_first_response) do |r| + r.process { authorize(100, credit_card, options) } + r.process(:ignore_result) { void(r.authorization, options) } + end + end + + def supports_scrubbing? + true + end + + def scrub(transcript) + transcript. + gsub(%r(().+?())i, '\1[FILTERED]\2'). + gsub(%r(().+?())i, '\1[FILTERED]\2'). + gsub(%r(().+?())i, '\1[FILTERED]\2'). + gsub(%r(().+?())i, '\1[FILTERED]\2'). + gsub(%r(().+?())i, '\1[FILTERED]\2') + end + + private + + def add_credentials(xml) + xml.credentials do + xml.AccountID @options[:account_id] + xml.AccountToken @options[:account_token] + xml.AcceptorID @options[:acceptor_id] + end + xml.application do + xml.ApplicationID @options[:application_id] + xml.ApplicationName @options[:application_name] + xml.ApplicationVersion @options[:application_version] + end + end + + def add_payment_method(xml, payment) + if payment.is_a?(String) + add_payment_account_id(xml, payment) + elsif payment.is_a?(Check) + add_echeck(xml, payment) + else + add_credit_card(xml, payment) + end + end + + def add_payment_account(xml, payment, payment_account_reference_number) + xml.paymentAccount do + xml.PaymentAccountType payment_account_type(payment) + xml.PaymentAccountReferenceNumber payment_account_reference_number + end + end + + def add_payment_account_id(xml, payment) + xml.extendedParameters do + xml.ExtendedParameters do + xml.Key 'PaymentAccount' + xml.Value('xsi:type' => 'PaymentAccount') do + xml.PaymentAccountID payment + end + end + end + end + + def add_transaction(xml, money, options = {}) + xml.transaction do + xml.ReversalType options[:reversal_type] if options[:reversal_type] + xml.TransactionID options[:trans_id] if options[:trans_id] + xml.TransactionAmount amount(money.to_i) if money + xml.MarketCode 'Default' if money + xml.ReferenceNumber options[:order_id] || SecureRandom.hex(20) + end + end + + def add_terminal(xml, options) + xml.terminal do + xml.TerminalID '01' + xml.CardPresentCode 'UseDefault' + xml.CardholderPresentCode 'UseDefault' + xml.CardInputCode 'UseDefault' + xml.CVVPresenceCode 'UseDefault' + xml.TerminalCapabilityCode 'UseDefault' + xml.TerminalEnvironmentCode 'UseDefault' + xml.MotoECICode 'NonAuthenticatedSecureECommerceTransaction' + end + end + + def add_credit_card(xml, payment) + xml.card do + xml.CardNumber payment.number + xml.ExpirationMonth format(payment.month, :two_digits) + xml.ExpirationYear format(payment.year, :two_digits) + xml.CardholderName payment.first_name + ' ' + payment.last_name + xml.CVV payment.verification_value + end + end + + def add_echeck(xml, payment) + xml.demandDepositAccount do + xml.AccountNumber payment.account_number + xml.RoutingNumber payment.routing_number + xml.DDAAccountType payment.account_type.capitalize + end + end + + def add_address(xml, options) + if address = options[:billing_address] || options[:address] + xml.address do + xml.BillingAddress1 address[:address1] if address[:address1] + xml.BillingAddress2 address[:address2] if address[:address2] + xml.BillingCity address[:city] if address[:city] + xml.BillingState address[:state] if address[:state] + xml.BillingZipcode address[:zip] if address[:zip] + xml.BillingEmail address[:email] if address[:email] + xml.BillingPhone address[:phone_number] if address[:phone_number] + end + end + if shipping_address = options[:shipping_address] + xml.address do + xml.ShippingAddress1 shipping_address[:address1] if shipping_address[:address1] + xml.ShippingAddress2 shipping_address[:address2] if shipping_address[:address2] + xml.ShippingCity shipping_address[:city] if shipping_address[:city] + xml.ShippingState shipping_address[:state] if shipping_address[:state] + xml.ShippingZipcode shipping_address[:zip] if shipping_address[:zip] + xml.ShippingEmail shipping_address[:email] if shipping_address[:email] + xml.ShippingPhone shipping_address[:phone_number] if shipping_address[:phone_number] + end + end + end + + def parse(xml) + response = {} + + doc = Nokogiri::XML(xml) + doc.remove_namespaces! + root = doc.root.xpath('//response/*') + + if root.empty? + root = doc.root.xpath('//Response/*') + end + + root.each do |node| + if node.elements.empty? + response[node.name.downcase] = node.text + else + node_name = node.name.downcase + response[node_name] = Hash.new + + node.elements.each do |childnode| + response[node_name][childnode.name.downcase] = childnode.text + end + end + end + + response + end + + def commit(action, xml, amount) + response = parse(ssl_post(url(action), xml, headers(action))) + + Response.new( + success_from(response), + message_from(response), + response, + authorization: authorization_from(action, response, amount), + avs_result: success_from(response) ? avs_from(response) : nil, + cvv_result: success_from(response) ? cvv_from(response) : nil, + test: test? + ) + end + + def authorization_from(action, response, amount) + if action == 'PaymentAccountCreate' + response['paymentaccount']['paymentaccountid'] + else + "#{response['transaction']['transactionid']}|#{amount}" if response['transaction'] + end + end + + def success_from(response) + response['expressresponsecode'] == '0' + end + + def message_from(response) + response['expressresponsemessage'] + end + + def avs_from(response) + AVSResult.new(code: response['card']['avsresponsecode']) if response['card'] + end + + def cvv_from(response) + CVVResult.new(response['card']['cvvresponsecode']) if response['card'] + end + + def split_authorization(authorization) + authorization.split('|') + end + + def build_soap_request + builder = Nokogiri::XML::Builder.new(encoding: 'UTF-8') do |xml| + xml['soap'].Envelope('xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance', + 'xmlns:xsd' => 'http://www.w3.org/2001/XMLSchema', + 'xmlns:soap' => 'http://schemas.xmlsoap.org/soap/envelope/') do + + xml['soap'].Body do + yield(xml) + end + end + end + + builder.to_xml + end + + def payment_account_type(payment) + if payment.is_a?(Check) + payment_account_type = payment.account_type + else + payment_account_type = 'CreditCard' + end + payment_account_type + end + + def url(action) + if action == 'PaymentAccountCreate' + test? ? SERVICE_TEST_URL : SERVICE_LIVE_URL + else + test? ? test_url : live_url + end + end + + def interface(action) + return 'transaction' if action != 'PaymentAccountCreate' + return 'services' if action == 'PaymentAccountCreate' + end + + def headers(action) + { + 'Content-Type' => 'text/xml; charset=utf-8', + 'SOAPAction' => "https://#{interface(action)}.elementexpress.com/#{action}" + } + end + end + end +end diff --git a/lib/active_merchant/billing/gateways/epay.rb b/lib/active_merchant/billing/gateways/epay.rb index ff242f9d93b..4f33ae1afe8 100644 --- a/lib/active_merchant/billing/gateways/epay.rb +++ b/lib/active_merchant/billing/gateways/epay.rb @@ -1,8 +1,7 @@ module ActiveMerchant #:nodoc: module Billing #:nodoc: class EpayGateway < Gateway - API_HOST = 'ssl.ditonlinebetalingssystem.dk' - self.live_url = 'https://' + API_HOST + '/remote/payment' + self.live_url = 'https://ssl.ditonlinebetalingssystem.dk/' self.default_currency = 'DKK' self.money_format = :cents @@ -106,10 +105,21 @@ def refund(money, identification, options = {}) end def credit(money, identification, options = {}) - deprecated CREDIT_DEPRECATION_MESSAGE + ActiveMerchant.deprecated CREDIT_DEPRECATION_MESSAGE refund(money, identification, options) end + def supports_scrubbing + true + end + + def scrub(transcript) + transcript. + gsub(%r((Authorization: Basic )\w+), '\1[FILTERED]'). + gsub(%r(((?:\?|&)cardno=)\d*(&?)), '\1[FILTERED]\2'). + gsub(%r((&?cvc=)\d*(&?)), '\1[FILTERED]\2') + end + private def add_amount(post, money, options) @@ -153,16 +163,16 @@ def commit(action, params) if action == :authorize Response.new response['accept'].to_i == 1, - response['errortext'], - response, - :test => test?, - :authorization => response['tid'] + response['errortext'], + response, + :test => test?, + :authorization => response['tid'] else Response.new response['result'] == 'true', - messages(response['epay'], response['pbs']), - response, - :test => test?, - :authorization => params[:transaction] + messages(response['epay'], response['pbs']), + response, + :test => test?, + :authorization => params[:transaction] end end @@ -175,19 +185,19 @@ def messages(epay, pbs = nil) def soap_post(method, params) data = xml_builder(params, method) headers = make_headers(data, method) - REXML::Document.new(ssl_post('https://' + API_HOST + '/remote/payment.asmx', data, headers)) + REXML::Document.new(ssl_post(live_url + 'remote/payment.asmx', data, headers)) end def do_authorize(params) headers = {} - headers['Referer'] = (options[:password] || "activemerchant.org") + headers['Referer'] = (options[:password] || 'activemerchant.org') - response = raw_ssl_request(:post, 'https://' + API_HOST + '/auth/default.aspx', authorize_post_data(params), headers) + response = raw_ssl_request(:post, live_url + 'auth/default.aspx', authorize_post_data(params), headers) # Authorize gives the response back by redirecting with the values in # the URL query if location = response['Location'] - query = CGI::parse(URI.parse(location.gsub(' ', '%20')).query) + query = CGI::parse(URI.parse(location.gsub(' ', '%20').gsub('<', '%3C').gsub('>', '%3E')).query) else return { 'accept' => '0', @@ -198,7 +208,7 @@ def do_authorize(params) end result = {} - query.each_pair do |k,v| + query.each_pair do |k, v| result[k] = v.is_a?(Array) && v.size == 1 ? v[0] : v # make values like ['v'] into 'v' end result @@ -233,42 +243,42 @@ def do_void(params) def make_headers(data, soap_call) { 'Content-Type' => 'text/xml; charset=utf-8', - 'Host' => API_HOST, + 'Host' => 'ssl.ditonlinebetalingssystem.dk', 'Content-Length' => data.size.to_s, - 'SOAPAction' => self.live_url + '/' + soap_call + 'SOAPAction' => self.live_url + 'remote/payment/' + soap_call } end def xml_builder(params, soap_call) xml = Builder::XmlMarkup.new(:indent => 2) xml.instruct! - xml.tag! 'soap:Envelope', { 'xmlns:xsi' => 'http://schemas.xmlsoap.org/soap/envelope/', - 'xmlns:xsd' => 'http://www.w3.org/2001/XMLSchema', - 'xmlns:soap' => 'http://schemas.xmlsoap.org/soap/envelope/' } do - xml.tag! 'soap:Body' do - xml.tag! soap_call, { 'xmlns' => self.live_url } do - xml.tag! 'merchantnumber', @options[:login] - xml.tag! 'transactionid', params[:transaction] - xml.tag! 'amount', params[:amount].to_s if soap_call != 'delete' - end + xml.tag! 'soap:Envelope', { 'xmlns:xsi' => 'http://schemas.xmlsoap.org/soap/envelope/', + 'xmlns:xsd' => 'http://www.w3.org/2001/XMLSchema', + 'xmlns:soap' => 'http://schemas.xmlsoap.org/soap/envelope/' } do + xml.tag! 'soap:Body' do + xml.tag! soap_call, { 'xmlns' => "#{self.live_url}remote/payment" } do + xml.tag! 'merchantnumber', @options[:login] + xml.tag! 'transactionid', params[:transaction] + xml.tag! 'amount', params[:amount].to_s if soap_call != 'delete' end end + end xml.target! end def authorize_post_data(params = {}) params[:language] = '2' params[:cms] = 'activemerchant' - params[:accepturl] = 'https://ssl.ditonlinebetalingssystem.dk/auth/default.aspx?accept=1' - params[:declineurl] = 'https://ssl.ditonlinebetalingssystem.dk/auth/default.aspx?decline=1' + params[:accepturl] = live_url + 'auth/default.aspx?accept=1' + params[:declineurl] = live_url + 'auth/default.aspx?decline=1' params[:merchantnumber] = @options[:login] - params.collect { |key, value| "#{key}=#{CGI.escape(value.to_s)}" }.join("&") + params.collect { |key, value| "#{key}=#{CGI.escape(value.to_s)}" }.join('&') end # Limited to 20 digits max def format_order_number(number) - number.to_s.gsub(/[^\w_]/, '').rjust(4, "0")[0...20] + number.to_s.gsub(/[^\w]/, '').rjust(4, '0')[0...20] end end end diff --git a/lib/active_merchant/billing/gateways/evo_ca.rb b/lib/active_merchant/billing/gateways/evo_ca.rb index c2f76883ebf..b5f976b7cee 100644 --- a/lib/active_merchant/billing/gateways/evo_ca.rb +++ b/lib/active_merchant/billing/gateways/evo_ca.rb @@ -301,7 +301,7 @@ def post_data(action, parameters = {}) post[:username] = options[:username] post[:password] = options[:password] end - post.merge(parameters).collect { |key, value| "#{key}=#{CGI.escape(value.to_s)}" unless value.nil? }.compact.join("&") + post.merge(parameters).collect { |key, value| "#{key}=#{CGI.escape(value.to_s)}" unless value.nil? }.compact.join('&') end end end diff --git a/lib/active_merchant/billing/gateways/eway.rb b/lib/active_merchant/billing/gateways/eway.rb index 6c60255fdf9..04874aac7ba 100644 --- a/lib/active_merchant/billing/gateways/eway.rb +++ b/lib/active_merchant/billing/gateways/eway.rb @@ -8,7 +8,7 @@ class EwayGateway < Gateway self.live_url = 'https://www.eway.com.au' self.money_format = :cents - self.supported_countries = ['AU', 'NZ', 'GB'] + self.supported_countries = ['AU'] self.supported_cardtypes = [:visa, :master, :american_express, :diners_club] self.homepage_url = 'http://www.eway.com.au/' self.display_name = 'eWAY' @@ -52,15 +52,27 @@ def refund(money, authorization, options={}) commit(refund_url, money, post) end + def supports_scrubbing + true + end + + def scrub(transcript) + transcript. + gsub(%r((Authorization: Basic )\w+), '\1[FILTERED]'). + gsub(%r(()\d+())i, '\1[FILTERED]\2'). + gsub(%r(()\d+())i, '\1[FILTERED]\2') + end + private + def requires_address!(options) - raise ArgumentError.new("Missing eWay required parameters: address or billing_address") unless (options.has_key?(:address) or options.has_key?(:billing_address)) + raise ArgumentError.new('Missing eWay required parameters: address or billing_address') unless options.has_key?(:address) or options.has_key?(:billing_address) end def add_creditcard(post, creditcard) post[:CardNumber] = creditcard.number - post[:CardExpiryMonth] = sprintf("%.2i", creditcard.month) - post[:CardExpiryYear] = sprintf("%.4i", creditcard.year)[-2..-1] + post[:CardExpiryMonth] = sprintf('%.2i', creditcard.month) + post[:CardExpiryYear] = sprintf('%.4i', creditcard.year)[-2..-1] post[:CustomerFirstName] = creditcard.first_name post[:CustomerLastName] = creditcard.last_name post[:CardHoldersName] = creditcard.name @@ -108,7 +120,7 @@ def commit(url, money, parameters) end def success?(response) - response[:ewaytrxnstatus] == "True" + response[:ewaytrxnstatus] == 'True' end def parse(xml) @@ -123,7 +135,7 @@ def parse(xml) def post_data(parameters = {}) xml = REXML::Document.new - root = xml.add_element("ewaygateway") + root = xml.add_element('ewaygateway') parameters.each do |key, value| root.add_element("eway#{key}").text = value @@ -133,18 +145,7 @@ def post_data(parameters = {}) def message_from(message) return '' if message.blank? - MESSAGES[message[0,2]] || message - end - - # Make a ruby type out of the response string - def normalize(field) - case field - when "true" then true - when "false" then false - when "" then nil - when "null" then nil - else field - end + MESSAGES[message[0, 2]] || message end def purchase_url(cvn) @@ -159,66 +160,66 @@ def refund_url end MESSAGES = { - "00" => "Transaction Approved", - "01" => "Refer to Issuer", - "02" => "Refer to Issuer, special", - "03" => "No Merchant", - "04" => "Pick Up Card", - "05" => "Do Not Honour", - "06" => "Error", - "07" => "Pick Up Card, Special", - "08" => "Honour With Identification", - "09" => "Request In Progress", - "10" => "Approved For Partial Amount", - "11" => "Approved, VIP", - "12" => "Invalid Transaction", - "13" => "Invalid Amount", - "14" => "Invalid Card Number", - "15" => "No Issuer", - "16" => "Approved, Update Track 3", - "19" => "Re-enter Last Transaction", - "21" => "No Action Taken", - "22" => "Suspected Malfunction", - "23" => "Unacceptable Transaction Fee", - "25" => "Unable to Locate Record On File", - "30" => "Format Error", - "31" => "Bank Not Supported By Switch", - "33" => "Expired Card, Capture", - "34" => "Suspected Fraud, Retain Card", - "35" => "Card Acceptor, Contact Acquirer, Retain Card", - "36" => "Restricted Card, Retain Card", - "37" => "Contact Acquirer Security Department, Retain Card", - "38" => "PIN Tries Exceeded, Capture", - "39" => "No Credit Account", - "40" => "Function Not Supported", - "41" => "Lost Card", - "42" => "No Universal Account", - "43" => "Stolen Card", - "44" => "No Investment Account", - "51" => "Insufficient Funds", - "52" => "No Cheque Account", - "53" => "No Savings Account", - "54" => "Expired Card", - "55" => "Incorrect PIN", - "56" => "No Card Record", - "57" => "Function Not Permitted to Cardholder", - "58" => "Function Not Permitted to Terminal", - "59" => "Suspected Fraud", - "60" => "Acceptor Contact Acquirer", - "61" => "Exceeds Withdrawal Limit", - "62" => "Restricted Card", - "63" => "Security Violation", - "64" => "Original Amount Incorrect", - "66" => "Acceptor Contact Acquirer, Security", - "67" => "Capture Card", - "75" => "PIN Tries Exceeded", - "82" => "CVV Validation Error", - "90" => "Cutoff In Progress", - "91" => "Card Issuer Unavailable", - "92" => "Unable To Route Transaction", - "93" => "Cannot Complete, Violation Of The Law", - "94" => "Duplicate Transaction", - "96" => "System Error" + '00' => 'Transaction Approved', + '01' => 'Refer to Issuer', + '02' => 'Refer to Issuer, special', + '03' => 'No Merchant', + '04' => 'Pick Up Card', + '05' => 'Do Not Honour', + '06' => 'Error', + '07' => 'Pick Up Card, Special', + '08' => 'Honour With Identification', + '09' => 'Request In Progress', + '10' => 'Approved For Partial Amount', + '11' => 'Approved, VIP', + '12' => 'Invalid Transaction', + '13' => 'Invalid Amount', + '14' => 'Invalid Card Number', + '15' => 'No Issuer', + '16' => 'Approved, Update Track 3', + '19' => 'Re-enter Last Transaction', + '21' => 'No Action Taken', + '22' => 'Suspected Malfunction', + '23' => 'Unacceptable Transaction Fee', + '25' => 'Unable to Locate Record On File', + '30' => 'Format Error', + '31' => 'Bank Not Supported By Switch', + '33' => 'Expired Card, Capture', + '34' => 'Suspected Fraud, Retain Card', + '35' => 'Card Acceptor, Contact Acquirer, Retain Card', + '36' => 'Restricted Card, Retain Card', + '37' => 'Contact Acquirer Security Department, Retain Card', + '38' => 'PIN Tries Exceeded, Capture', + '39' => 'No Credit Account', + '40' => 'Function Not Supported', + '41' => 'Lost Card', + '42' => 'No Universal Account', + '43' => 'Stolen Card', + '44' => 'No Investment Account', + '51' => 'Insufficient Funds', + '52' => 'No Cheque Account', + '53' => 'No Savings Account', + '54' => 'Expired Card', + '55' => 'Incorrect PIN', + '56' => 'No Card Record', + '57' => 'Function Not Permitted to Cardholder', + '58' => 'Function Not Permitted to Terminal', + '59' => 'Suspected Fraud', + '60' => 'Acceptor Contact Acquirer', + '61' => 'Exceeds Withdrawal Limit', + '62' => 'Restricted Card', + '63' => 'Security Violation', + '64' => 'Original Amount Incorrect', + '66' => 'Acceptor Contact Acquirer, Security', + '67' => 'Capture Card', + '75' => 'PIN Tries Exceeded', + '82' => 'CVV Validation Error', + '90' => 'Cutoff In Progress', + '91' => 'Card Issuer Unavailable', + '92' => 'Unable To Route Transaction', + '93' => 'Cannot Complete, Violation Of The Law', + '94' => 'Duplicate Transaction', + '96' => 'System Error' } end end diff --git a/lib/active_merchant/billing/gateways/eway_managed.rb b/lib/active_merchant/billing/gateways/eway_managed.rb index 2dcae353a1e..fcdd34afbd4 100644 --- a/lib/active_merchant/billing/gateways/eway_managed.rb +++ b/lib/active_merchant/billing/gateways/eway_managed.rb @@ -12,7 +12,7 @@ class EwayManagedGateway < Gateway self.default_currency = 'AUD' - #accepted money format + # accepted money format self.money_format = :cents # The homepage URL of the gateway @@ -46,7 +46,7 @@ def store(creditcard, options = {}) add_address(post, billing_address) add_misc_fields(post, options) - commit("CreateCustomer", post) + commit('CreateCustomer', post) end def update(billing_id, creditcard, options={}) @@ -64,7 +64,7 @@ def update(billing_id, creditcard, options={}) add_address(post, billing_address) add_misc_fields(post, options) - commit("UpdateCustomer", post) + commit('UpdateCustomer', post) end # Process a payment in the given amount against the stored credit card given by billing_id @@ -86,7 +86,7 @@ def purchase(money, billing_id, options={}) post[:amount]=money add_invoice(post, options) - commit("ProcessPayment", post) + commit('ProcessPayment', post) end # Get customer's stored credit card details given by billing_id @@ -98,7 +98,7 @@ def retrieve(billing_id) post = {} post[:managedCustomerID] = billing_id.to_s - commit("QueryCustomer", post) + commit('QueryCustomer', post) end # TODO: eWay API also provides QueryPayment @@ -106,8 +106,8 @@ def retrieve(billing_id) private def eway_requires!(hash) - raise ArgumentError.new("Missing eWay required parameter in `billing_address`: title") unless hash.has_key?(:title) - raise ArgumentError.new("Missing eWay required parameter in `billing_address`: country") unless hash.has_key?(:country) + raise ArgumentError.new('Missing eWay required parameter in `billing_address`: title') unless hash.has_key?(:title) + raise ArgumentError.new('Missing eWay required parameter in `billing_address`: country') unless hash.has_key?(:country) end def add_address(post, address) @@ -136,12 +136,11 @@ def add_invoice(post, options) post[:invoiceDescription] = options[:description] end - # add credit card details to be stored by eway. NOTE eway requires "title" field def add_creditcard(post, creditcard) post[:CCNumber] = creditcard.number - post[:CCExpiryMonth] = sprintf("%.2i", creditcard.month) - post[:CCExpiryYear] = sprintf("%.4i", creditcard.year)[-2..-1] + post[:CCExpiryMonth] = sprintf('%.2i', creditcard.month) + post[:CCExpiryYear] = sprintf('%.4i', creditcard.year)[-2..-1] post[:CCNameOnCard] = creditcard.name post[:FirstName] = creditcard.first_name post[:LastName] = creditcard.last_name @@ -150,8 +149,8 @@ def add_creditcard(post, creditcard) def parse(body) reply = {} xml = REXML::Document.new(body) - if root = REXML::XPath.first(xml, "//soap:Fault") then - reply=parse_fault(root) + if root = REXML::XPath.first(xml, '//soap:Fault') then + reply=parse_fault(root) else if root = REXML::XPath.first(xml, '//ProcessPaymentResponse/ewayResponse') then # Successful payment @@ -166,19 +165,19 @@ def parse(body) reply[:success]=true else if root = REXML::XPath.first(xml, '//UpdateCustomerResult') then - if root.text.downcase == 'true' then + if root.text.casecmp('true').zero? then reply[:message]='OK' reply[:success]=true else # ERROR: This state should never occur. If there is a problem, # a soap:Fault will be returned. The presence of this # element always means a success. - raise StandardError, "Unexpected \"false\" in UpdateCustomerResult" + raise StandardError, 'Unexpected "false" in UpdateCustomerResult' end else # ERROR: This state should never occur currently. We have handled # responses for all the methods which we support. - raise StandardError, "Unexpected response" + raise StandardError, 'Unexpected response' end end end @@ -232,34 +231,34 @@ def commit(action, post) def soap_request(arguments, action) # eWay demands all fields be sent, but contain an empty string if blank post = case action - when 'QueryCustomer' - arguments - when 'ProcessPayment' - default_payment_fields.merge(arguments) - when 'CreateCustomer' - default_customer_fields.merge(arguments) - when 'UpdateCustomer' - default_customer_fields.merge(arguments) + when 'QueryCustomer' + arguments + when 'ProcessPayment' + default_payment_fields.merge(arguments) + when 'CreateCustomer' + default_customer_fields.merge(arguments) + when 'UpdateCustomer' + default_customer_fields.merge(arguments) end xml = Builder::XmlMarkup.new :indent => 2 - xml.instruct! - xml.tag! 'soap12:Envelope', {'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance', 'xmlns:xsd' => 'http://www.w3.org/2001/XMLSchema', 'xmlns:soap12' => 'http://www.w3.org/2003/05/soap-envelope'} do - xml.tag! 'soap12:Header' do - xml.tag! 'eWAYHeader', {'xmlns' => 'https://www.eway.com.au/gateway/managedpayment'} do - xml.tag! 'eWAYCustomerID', @options[:login] - xml.tag! 'Username', @options[:username] - xml.tag! 'Password', @options[:password] - end + xml.instruct! + xml.tag! 'soap12:Envelope', {'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance', 'xmlns:xsd' => 'http://www.w3.org/2001/XMLSchema', 'xmlns:soap12' => 'http://www.w3.org/2003/05/soap-envelope'} do + xml.tag! 'soap12:Header' do + xml.tag! 'eWAYHeader', {'xmlns' => 'https://www.eway.com.au/gateway/managedpayment'} do + xml.tag! 'eWAYCustomerID', @options[:login] + xml.tag! 'Username', @options[:username] + xml.tag! 'Password', @options[:password] end - xml.tag! 'soap12:Body' do |x| - x.tag! "#{action}", {'xmlns' => 'https://www.eway.com.au/gateway/managedpayment'} do |y| - post.each do |key, value| - y.tag! "#{key}", "#{value}" - end + end + xml.tag! 'soap12:Body' do |x| + x.tag! action, {'xmlns' => 'https://www.eway.com.au/gateway/managedpayment'} do |y| + post.each do |key, value| + y.tag! key, value end end end + end xml.target! end diff --git a/lib/active_merchant/billing/gateways/eway_rapid.rb b/lib/active_merchant/billing/gateways/eway_rapid.rb index ae2caf64d89..34470c07183 100644 --- a/lib/active_merchant/billing/gateways/eway_rapid.rb +++ b/lib/active_merchant/billing/gateways/eway_rapid.rb @@ -1,221 +1,305 @@ -require "nokogiri" -require "cgi" +require 'json' module ActiveMerchant #:nodoc: module Billing #:nodoc: class EwayRapidGateway < Gateway - self.test_url = "https://api.sandbox.ewaypayments.com/" - self.live_url = "https://api.ewaypayments.com/" + self.test_url = 'https://api.sandbox.ewaypayments.com/' + self.live_url = 'https://api.ewaypayments.com/' self.money_format = :cents - self.supported_countries = ["AU"] - self.supported_cardtypes = [:visa, :master, :american_express, :diners_club] - self.homepage_url = "http://www.eway.com.au/" - self.display_name = "eWAY Rapid 3.0" - self.default_currency = "AUD" + self.supported_countries = ['AU', 'NZ', 'GB', 'SG', 'MY', 'HK'] + self.supported_cardtypes = [:visa, :master, :american_express, :diners_club, :jcb] + self.homepage_url = 'http://www.eway.com.au/' + self.display_name = 'eWAY Rapid 3.1' + self.default_currency = 'AUD' + + class_attribute :partner_id def initialize(options = {}) requires!(options, :login, :password) super end - # Public: Run a purchase transaction. Treats the Rapid 3.0 transparent - # redirect as an API endpoint in order to conform to the standard - # ActiveMerchant #purchase API. + # Public: Run a purchase transaction. # - # amount - The monetary amount of the transaction in cents. - # options - A standard ActiveMerchant options hash: - # :order_id - A merchant-supplied identifier for the - # transaction (optional). - # :description - A merchant-supplied description of the - # transaction (optional). - # :currency - Three letter currency code for the - # transaction (default: "AUD") - # :billing_address - Standard ActiveMerchant address hash - # (optional). - # :shipping_address - Standard ActiveMerchant address hash - # (optional). - # :ip - The ip of the consumer initiating the - # transaction (optional). - # :application_id - A string identifying the application - # submitting the transaction - # (default: "https://github.com/Shopify/active_merchant") + # amount - The monetary amount of the transaction in cents. + # payment_method - The payment method or authorization token returned from store. + # options - A standard ActiveMerchant options hash: + # :transaction_type - One of: Purchase (default), MOTO + # or Recurring. For stored card payments (aka - TokenPayments), + # this must be either MOTO or Recurring. + # :invoice - The merchant’s invoice number for this + # transaction (optional). + # :order_id - A merchant-supplied identifier for the + # transaction (optional). + # :description - A merchant-supplied description of the + # transaction (optional). + # :currency - Three letter currency code for the + # transaction (default: "AUD") + # :billing_address - Standard ActiveMerchant address hash + # (optional). + # :shipping_address - Standard ActiveMerchant address hash + # (optional). + # :ip - The ip of the consumer initiating the + # transaction (optional). + # :application_id - A string identifying the application + # submitting the transaction + # (default: "https://github.com/activemerchant/active_merchant") # - # Returns an ActiveMerchant::Billing::Response object + # Returns an ActiveMerchant::Billing::Response object where authorization is the Transaction ID on success def purchase(amount, payment_method, options={}) - MultiResponse.new.tap do |r| - # Rather than follow the redirect, we detect the 302 and capture the - # token out of the Location header in the run_purchase step. But we - # still need a placeholder url to pass to eWay, and that is what - # example.com is used for here. - r.process{setup_purchase(amount, options.merge(:redirect_url => "http://example.com/"))} - r.process{run_purchase(r.authorization, payment_method, r.params["formactionurl"])} - r.process{status(r.authorization)} - end + params = {} + add_metadata(params, options) + add_invoice(params, amount, options) + add_customer_data(params, options, payment_method) + add_credit_card(params, payment_method, options) + params['Method'] = payment_method.respond_to?(:number) ? 'ProcessPayment' : 'TokenPayment' + commit(url_for('Transaction'), params) end - # Public: Acquire the token necessary to run a transparent redirect. - # - # amount - The monetary amount of the transaction in cents. - # options - A supplemented ActiveMerchant options hash: - # :redirect_url - The url to return the customer to after - # the transparent redirect is completed - # (required). - # :order_id - A merchant-supplied identifier for the - # transaction (optional). - # :description - A merchant-supplied description of the - # transaction (optional). - # :currency - Three letter currency code for the - # transaction (default: "AUD") - # :billing_address - Standard ActiveMerchant address hash - # (optional). - # :shipping_address - Standard ActiveMerchant address hash - # (optional). - # :ip - The ip of the consumer initiating the - # transaction (optional). - # :application_id - A string identifying the application - # submitting the transaction - # (default: "https://github.com/Shopify/active_merchant") - # - # Returns an EwayRapidResponse object, which conforms to the - # ActiveMerchant::Billing::Response API, but also exposes #form_url. - def setup_purchase(amount, options={}) - requires!(options, :redirect_url) - request = build_xml_request("CreateAccessCodeRequest") do |doc| - add_metadata(doc, options) - add_invoice(doc, amount, options) - add_customer_data(doc, options) - end + def authorize(amount, payment_method, options={}) + params = {} + add_metadata(params, options) + add_invoice(params, amount, options) + add_customer_data(params, options, payment_method) + add_credit_card(params, payment_method, options) + params['Method'] = 'Authorise' + commit(url_for('Authorisation'), params) + end - commit(url_for("CreateAccessCode"), request) + def capture(amount, identification, options = {}) + params = {} + add_metadata(params, options) + add_invoice(params, amount, options) + add_reference(params, identification) + commit(url_for('CapturePayment'), params) end - # Public: Retrieve the status of a transaction. + def void(identification, options = {}) + params = {} + add_reference(params, identification) + commit(url_for('CancelAuthorisation'), params) + end + + # Public: Refund a transaction. # - # identification - The Eway Rapid 3.0 access code for the transaction - # (returned as the response.authorization by - # #setup_purchase). + # amount - The monetary amount of the transaction in cents + # identification - The transaction id which is returned in the + # authorization of the successful purchase transaction + # options - A standard ActiveMerchant options hash: + # :invoice - The merchant’s invoice number for this + # transaction (optional). + # :order_id - A merchant-supplied identifier for the + # transaction (optional). + # :description - A merchant-supplied description of the + # transaction (optional). + # :currency - Three letter currency code for the + # transaction (default: "AUD") + # :billing_address - Standard ActiveMerchant address hash + # (optional). + # :shipping_address - Standard ActiveMerchant address hash + # (optional). + # :ip - The ip of the consumer initiating the + # transaction (optional). + # :application_id - A string identifying the application + # submitting the transaction + # (default: "https://github.com/activemerchant/active_merchant") # - # Returns an EwayRapidResponse object. - def status(identification) - request = build_xml_request("GetAccessCodeResultRequest") do |doc| - doc.AccessCode identification - end - commit(url_for("GetAccessCodeResult"), request) + # Returns an ActiveMerchant::Billing::Response object + def refund(amount, identification, options = {}) + params = {} + add_metadata(params, options) + add_invoice(params, amount, options, 'Refund') + add_reference(params['Refund'], identification) + add_customer_data(params, options) + commit(url_for("Transaction/#{identification}/Refund"), params) end # Public: Store card details and return a valid token # - # options - A supplemented ActiveMerchant options hash: - # :order_id - A merchant-supplied identifier for the - # transaction (optional). - # :billing_address - Standard ActiveMerchant address hash - # (required). - # :ip - The ip of the consumer initiating the - # transaction (optional). - # :application_id - A string identifying the application - # submitting the transaction - # (default: "https://github.com/Shopify/active_merchant") + # payment_method - The payment method or nil if :customer_token is provided + # options - A supplemented ActiveMerchant options hash: + # :order_id - A merchant-supplied identifier for the + # transaction (optional). + # :description - A merchant-supplied description of the + # transaction (optional). + # :billing_address - Standard ActiveMerchant address hash + # (required). + # :ip - The ip of the consumer initiating the + # transaction (optional). + # :application_id - A string identifying the application + # submitting the transaction + # (default: "https://github.com/activemerchant/active_merchant") + # + # Returns an ActiveMerchant::Billing::Response object where the authorization is the customer_token on success def store(payment_method, options = {}) requires!(options, :billing_address) - purchase(0, payment_method, options.merge(:request_method => "CreateTokenCustomer")) + params = {} + add_metadata(params, options) + add_invoice(params, 0, options) + add_customer_data(params, options, payment_method) + add_credit_card(params, payment_method, options) + params['Method'] = 'CreateTokenCustomer' + commit(url_for('Transaction'), params) + end + + # Public: Update a customer's data + # + # customer_token - The customer token returned in the authorization of + # a successful store transaction. + # payment_method - The payment method or nil if :customer_token is provided + # options - A supplemented ActiveMerchant options hash: + # :order_id - A merchant-supplied identifier for the + # transaction (optional). + # :description - A merchant-supplied description of the + # transaction (optional). + # :billing_address - Standard ActiveMerchant address hash + # (optional). + # :ip - The ip of the consumer initiating the + # transaction (optional). + # :application_id - A string identifying the application + # submitting the transaction + # (default: "https://github.com/activemerchant/active_merchant") + # + # Returns an ActiveMerchant::Billing::Response object where the authorization is the customer_token on success + def update(customer_token, payment_method, options = {}) + params = {} + add_metadata(params, options) + add_invoice(params, 0, options) + add_customer_data(params, options, payment_method) + add_credit_card(params, payment_method, options) + add_customer_token(params, customer_token) + params['Method'] = 'UpdateTokenCustomer' + commit(url_for('Transaction'), params) + end + + def supports_scrubbing + true + end + + def scrub(transcript) + transcript. + gsub(%r((Authorization: Basic )\w+), '\1[FILTERED]'). + gsub(%r(("Number\\?":\\?")[^"]*)i, '\1[FILTERED]'). + gsub(%r(("CVN\\?":\\?"?)[^",]*)i, '\1[FILTERED]') end private - def run_purchase(identification, payment_method, endpoint) - post = { - "accesscode" => identification + def add_metadata(params, options) + params['RedirectUrl'] = options[:redirect_url] || 'http://example.com' + params['CustomerIP'] = options[:ip] if options[:ip] + params['TransactionType'] = options[:transaction_type] || 'Purchase' + params['DeviceID'] = options[:application_id] || application_id + if partner = options[:partner_id] || partner_id + params['PartnerID'] = truncate(partner, 50) + end + params + end + + def add_invoice(params, money, options, key = 'Payment') + currency_code = options[:currency] || currency(money) + params[key] = { + 'TotalAmount' => localized_amount(money, currency_code), + 'InvoiceReference' => truncate(options[:order_id], 50), + 'InvoiceNumber' => truncate(options[:invoice] || options[:order_id], 12), + 'InvoiceDescription' => truncate(options[:description], 64), + 'CurrencyCode' => currency_code, } - add_credit_card(post, payment_method) + end - commit_form(endpoint, build_form_request(post)) + def add_reference(params, reference) + params['TransactionID'] = reference end - def add_metadata(doc, options) - doc.RedirectUrl(options[:redirect_url]) - doc.CustomerIP options[:ip] if options[:ip] - doc.Method options[:request_method] || "ProcessPayment" - doc.DeviceID(options[:application_id] || application_id) + def add_customer_data(params, options, payment_method = nil) + add_customer_fields(params, options, payment_method) + add_shipping_fields(params, options) end - def add_invoice(doc, money, options) - doc.Payment do - doc.TotalAmount amount(money) - doc.InvoiceReference options[:order_id] - doc.InvoiceDescription options[:description] - currency_code = (options[:currency] || currency(money) || default_currency) - doc.CurrencyCode currency_code - end + def add_customer_fields(params, options, payment_method) + key = 'Customer' + params[key] ||= {} + + customer_address = options[:billing_address] || options[:address] + + add_name_and_email(params[key], customer_address, options[:email], payment_method) + add_address(params[key], customer_address) end - def add_customer_data(doc, options) - doc.Customer do - add_address(doc, (options[:billing_address] || options[:address]), {:email => options[:email]}) - end - doc.ShippingAddress do - add_address(doc, options[:shipping_address], {:skip_company => true}) - end + def add_shipping_fields(params, options) + key = 'ShippingAddress' + params[key] = {} + + add_name_and_email(params[key], options[:shipping_address], options[:email]) + add_address(params[key], options[:shipping_address], {:skip_company => true}) end - def add_address(doc, address, options={}) - return unless address - if name = address[:name] - parts = name.split(/\s+/) - doc.FirstName parts.shift if parts.size > 1 - doc.LastName parts.join(" ") + def add_name_and_email(params, address, email, payment_method = nil) + if address.present? + params['FirstName'], params['LastName'] = split_names(address[:name]) + elsif payment_method_name_available?(payment_method) + params['FirstName'] = payment_method.first_name + params['LastName'] = payment_method.last_name end - doc.Title address[:title] - doc.CompanyName address[:company] unless options[:skip_company] - doc.Street1 address[:address1] - doc.Street2 address[:address2] - doc.City address[:city] - doc.State address[:state] - doc.PostalCode address[:zip] - doc.Country address[:country].to_s.downcase - doc.Phone address[:phone] - doc.Fax address[:fax] - doc.Email options[:email] + + params['Email'] = email end - def add_credit_card(post, credit_card) - post["cardname"] = credit_card.name - post["cardnumber"] = credit_card.number - post["cardexpirymonth"] = credit_card.month - post["cardexpiryyear"] = credit_card.year - post["cardcvn"] = credit_card.verification_value + def payment_method_name_available?(payment_method) + payment_method.respond_to?(:first_name) && payment_method.respond_to?(:last_name) && + payment_method.first_name.present? && payment_method.last_name.present? end - def build_xml_request(root) - builder = Nokogiri::XML::Builder.new - builder.__send__(root) do |doc| - yield(doc) - end - builder.to_xml + def add_address(params, address, options={}) + return unless address + + params['Title'] = address[:title] + params['CompanyName'] = address[:company] unless options[:skip_company] + params['Street1'] = truncate(address[:address1], 50) + params['Street2'] = truncate(address[:address2], 50) + params['City'] = truncate(address[:city], 50) + params['State'] = address[:state] + params['PostalCode'] = address[:zip] + params['Country'] = address[:country].to_s.downcase + params['Phone'] = address[:phone] || address[:phone_number] + params['Fax'] = address[:fax] end - def build_form_request(post) - request = [] - post.each do |key, value| - request << "EWAY_#{key.upcase}=#{CGI.escape(value.to_s)}" + def add_credit_card(params, credit_card, options) + return unless credit_card + params['Customer'] ||= {} + if credit_card.respond_to? :number + card_details = params['Customer']['CardDetails'] = {} + card_details['Name'] = truncate(credit_card.name, 50) + card_details['Number'] = credit_card.number + card_details['ExpiryMonth'] = '%02d' % (credit_card.month || 0) + card_details['ExpiryYear'] = '%02d' % (credit_card.year || 0) + card_details['CVN'] = credit_card.verification_value + else + add_customer_token(params, credit_card) end - request.join("&") + end + + def add_customer_token(params, token) + params['Customer'] ||= {} + params['Customer']['TokenCustomerID'] = token end def url_for(action) - (test? ? test_url : live_url) + action + ".xml" + (test? ? test_url : live_url) + action end - def commit(url, request, form_post=false) + def commit(url, params) headers = { - "Authorization" => ("Basic " + Base64.strict_encode64(@options[:login].to_s + ":" + @options[:password].to_s).chomp), - "Content-Type" => "text/xml" + 'Authorization' => ('Basic ' + Base64.strict_encode64(@options[:login].to_s + ':' + @options[:password].to_s).chomp), + 'Content-Type' => 'application/json' } - + request = params.to_json raw = parse(ssl_post(url, request, headers)) succeeded = success?(raw) - EwayRapidResponse.new( + ActiveMerchant::Billing::Response.new( succeeded, message_from(succeeded, raw), raw, @@ -225,96 +309,255 @@ def commit(url, request, form_post=false) :cvv_result => cvv_result_from(raw) ) rescue ActiveMerchant::ResponseError => e - return EwayRapidResponse.new(false, e.response.message, {:status_code => e.response.code}, :test => test?) - end - - def commit_form(url, request) - http_response = raw_ssl_request(:post, url, request) - - success = (http_response.code.to_s == "302") - message = (success ? "Succeeded" : http_response.body) - if success - authorization = CGI.unescape(http_response["Location"].split("=").last) - end - Response.new(success, message, {:location => http_response["Location"]}, :authorization => authorization, :test => test?) + return ActiveMerchant::Billing::Response.new(false, e.response.message, {:status_code => e.response.code}, :test => test?) end - def parse(xml) - response = {} - - doc = Nokogiri::XML(xml) - doc.root.xpath("*").each do |node| - if (node.elements.size == 0) - response[node.name.downcase.to_sym] = node.text - else - node.elements.each do |childnode| - name = "#{node.name.downcase}_#{childnode.name.downcase}" - response[name.to_sym] = childnode.text - end - end - end unless doc.root.nil? - - response + def parse(data) + JSON.parse(data) end def success?(response) - if response[:errors] - false - elsif response[:responsecode] == "00" + if response['ResponseCode'] == '00' true - elsif response[:transactionstatus] - (response[:transactionstatus] == "true") + elsif response['TransactionStatus'] + (response['TransactionStatus'] == true) + elsif response['Succeeded'] + (response['Succeeded'] == true) else - true + false end end + def parse_errors(message) + errors = message.split(',').collect { |code| MESSAGES[code.strip] }.flatten.join(',') + errors.presence || message + end + def message_from(succeeded, response) - if response[:errors] - response[:errors] - elsif response[:responsecode] - ActiveMerchant::Billing::EwayGateway::MESSAGES[response[:responsecode]] - elsif response[:responsemessage] - response[:responsemessage] + if response['Errors'] + parse_errors(response['Errors']) + elsif response['ResponseMessage'] + parse_errors(response['ResponseMessage']) + elsif response['ResponseCode'] + ActiveMerchant::Billing::EwayGateway::MESSAGES[response['ResponseCode']] elsif succeeded - "Succeeded" + 'Succeeded' else - "Failed" + 'Failed' end end def authorization_from(response) - response[:accesscode] + # Note: TransactionID is always null for store requests, but TokenCustomerID is also sent back for purchase from + # stored card transactions so we give precedence to TransactionID + response['TransactionID'] || response['Customer']['TokenCustomerID'] end def avs_result_from(response) - code = case response[:verification_address] - when "Valid" - "M" - when "Invalid" - "N" + verification = response['Verification'] || {} + code = case verification['Address'] + when 'Valid' + 'M' + when 'Invalid' + 'N' else - "I" + 'I' end {:code => code} end def cvv_result_from(response) - case response[:verification_cvn] - when "Valid" - "M" - when "Invalid" - "N" + verification = response['Verification'] || {} + case verification['CVN'] + when 'Valid' + 'M' + when 'Invalid' + 'N' else - "P" + 'P' end end - class EwayRapidResponse < ActiveMerchant::Billing::Response - def form_url - params["formactionurl"] - end - end + MESSAGES = { + 'A2000' => 'Transaction Approved Successful', + 'A2008' => 'Honour With Identification Successful', + 'A2010' => 'Approved For Partial Amount Successful', + 'A2011' => 'Approved, VIP Successful', + 'A2016' => 'Approved, Update Track 3 Successful', + 'D4401' => 'Refer to Issuer Failed', + 'D4402' => 'Refer to Issuer, special Failed', + 'D4403' => 'No Merchant Failed', + 'D4404' => 'Pick Up Card Failed', + 'D4405' => 'Do Not Honour Failed', + 'D4406' => 'Error Failed', + 'D4407' => 'Pick Up Card, Special Failed', + 'D4409' => 'Request In Progress Failed', + 'D4412' => 'Invalid Transaction Failed', + 'D4413' => 'Invalid Amount Failed', + 'D4414' => 'Invalid Card Number Failed', + 'D4415' => 'No Issuer Failed', + 'D4419' => 'Re-enter Last Transaction Failed', + 'D4421' => 'No Action Taken Failed', + 'D4422' => 'Suspected Malfunction Failed', + 'D4423' => 'Unacceptable Transaction Fee Failed', + 'D4425' => 'Unable to Locate Record On File Failed', + 'D4430' => 'Format Error Failed ', + 'D4431' => 'Bank Not Supported By Switch Failed', + 'D4433' => 'Expired Card, Capture Failed ', + 'D4434' => 'Suspected Fraud, Retain Card Failed', + 'D4435' => 'Card Acceptor, Contact Acquirer, Retain Card Failed', + 'D4436' => 'Restricted Card, Retain Card Failed', + 'D4437' => 'Contact Acquirer Security Department, Retain Card Failed', + 'D4438' => 'PIN Tries Exceeded, Capture Failed', + 'D4439' => 'No Credit Account Failed', + 'D4440' => 'Function Not Supported Failed', + 'D4441' => 'Lost Card Failed', + 'D4442' => 'No Universal Account Failed', + 'D4443' => 'Stolen Card Failed', + 'D4444' => 'No Investment Account Failed', + 'D4451' => 'Insufficient Funds Failed', + 'D4452' => 'No Cheque Account Failed', + 'D4453' => 'No Savings Account Failed', + 'D4454' => 'Expired Card Failed', + 'D4455' => 'Incorrect PIN Failed', + 'D4456' => 'No Card Record Failed', + 'D4457' => 'Function Not Permitted to Cardholder Failed', + 'D4458' => 'Function Not Permitted to Terminal Failed', + 'D4459' => 'Suspected Fraud Failed', + 'D4460' => 'Acceptor Contact Acquirer Failed', + 'D4461' => 'Exceeds Withdrawal Limit Failed', + 'D4462' => 'Restricted Card Failed', + 'D4463' => 'Security Violation Failed', + 'D4464' => 'Original Amount Incorrect Failed', + 'D4466' => 'Acceptor Contact Acquirer, Security Failed', + 'D4467' => 'Capture Card Failed', + 'D4475' => 'PIN Tries Exceeded Failed', + 'D4482' => 'CVV Validation Error Failed', + 'D4490' => 'Cut off In Progress Failed', + 'D4491' => 'Card Issuer Unavailable Failed', + 'D4492' => 'Unable To Route Transaction Failed', + 'D4493' => 'Cannot Complete, Violation Of The Law Failed', + 'D4494' => 'Duplicate Transaction Failed', + 'D4496' => 'System Error Failed', + 'D4497' => 'MasterPass Error Failed', + 'D4498' => 'PayPal Create Transaction Error Failed', + 'D4499' => 'Invalid Transaction for Auth/Void Failed', + 'S5000' => 'System Error', + 'S5011' => 'PayPal Connection Error', + 'S5012' => 'PayPal Settings Error', + 'S5085' => 'Started 3dSecure', + 'S5086' => 'Routed 3dSecure', + 'S5087' => 'Completed 3dSecure', + 'S5088' => 'PayPal Transaction Created', + 'S5099' => 'Incomplete (Access Code in progress/incomplete)', + 'S5010' => 'Unknown error returned by gateway', + 'V6000' => 'Validation error', + 'V6001' => 'Invalid CustomerIP', + 'V6002' => 'Invalid DeviceID', + 'V6003' => 'Invalid Request PartnerID', + 'V6004' => 'Invalid Request Method', + 'V6010' => 'Invalid TransactionType, account not certified for eCome only MOTO or Recurring available', + 'V6011' => 'Invalid Payment TotalAmount', + 'V6012' => 'Invalid Payment InvoiceDescription', + 'V6013' => 'Invalid Payment InvoiceNumber', + 'V6014' => 'Invalid Payment InvoiceReference', + 'V6015' => 'Invalid Payment CurrencyCode', + 'V6016' => 'Payment Required', + 'V6017' => 'Payment CurrencyCode Required', + 'V6018' => 'Unknown Payment CurrencyCode', + 'V6021' => 'EWAY_CARDHOLDERNAME Required', + 'V6022' => 'EWAY_CARDNUMBER Required', + 'V6023' => 'EWAY_CARDCVN Required', + 'V6033' => 'Invalid Expiry Date', + 'V6034' => 'Invalid Issue Number', + 'V6035' => 'Invalid Valid From Date', + 'V6040' => 'Invalid TokenCustomerID', + 'V6041' => 'Customer Required', + 'V6042' => 'Customer FirstName Required', + 'V6043' => 'Customer LastName Required', + 'V6044' => 'Customer CountryCode Required', + 'V6045' => 'Customer Title Required ', + 'V6046' => 'TokenCustomerID Required', + 'V6047' => 'RedirectURL Required', + 'V6048' => 'Invalid Checkout URL', + 'V6051' => 'Invalid Customer FirstName', + 'V6052' => 'Invalid Customer LastName', + 'V6053' => 'Invalid Customer CountryCode', + 'V6058' => 'Invalid Customer Title ', + 'V6059' => 'Invalid RedirectURL', + 'V6060' => 'Invalid TokenCustomerID', + 'V6061' => 'Invalid Customer Reference', + 'V6062' => 'Invalid Customer CompanyName', + 'V6063' => 'Invalid Customer JobDescription', + 'V6064' => 'Invalid Customer Street1', + 'V6065' => 'Invalid Customer Street2', + 'V6066' => 'Invalid Customer City', + 'V6067' => 'Invalid Customer State', + 'V6068' => 'Invalid Customer PostalCode', + 'V6069' => 'Invalid Customer Email ', + 'V6070' => 'Invalid Customer Phone', + 'V6071' => 'Invalid Customer Mobile', + 'V6072' => 'Invalid Customer Comments', + 'V6073' => 'Invalid Customer Fax', + 'V6074' => 'Invalid Customer URL', + 'V6075' => 'Invalid ShippingAddress FirstName', + 'V6076' => 'Invalid ShippingAddress LastName', + 'V6077' => 'Invalid ShippingAddress Street1', + 'V6078' => 'Invalid ShippingAddress Street2', + 'V6079' => 'Invalid ShippingAddress City', + 'V6080' => 'Invalid ShippingAddress State', + 'V6081' => 'Invalid ShippingAddress PostalCode', + 'V6082' => 'Invalid ShippingAddress Email', + 'V6083' => 'Invalid ShippingAddress Phone', + 'V6084' => 'Invalid ShippingAddress Country', + 'V6085' => 'Invalid ShippingAddress ShippingMethod', + 'V6086' => 'Invalid ShippingAddress Fax', + 'V6091' => 'Unknown Customer CountryCode', + 'V6092' => 'Unknown ShippingAddress CountryCode', + 'V6100' => 'Invalid EWAY_CARDNAME', + 'V6101' => 'Invalid EWAY_CARDEXPIRYMONTH', + 'V6102' => 'Invalid EWAY_CARDEXPIRYYEAR ', + 'V6103' => 'Invalid EWAY_CARDSTARTMONTH', + 'V6104' => 'Invalid EWAY_CARDSTARTYEAR', + 'V6105' => 'Invalid EWAY_CARDISSUENUMBER ', + 'V6106' => 'Invalid EWAY_CARDCVN', + 'V6107' => 'Invalid EWAY_ACCESSCODE', + 'V6108' => 'Invalid CustomerHostAddress', + 'V6109' => 'Invalid UserAgent', + 'V6110' => 'Invalid EWAY_CARDNUMBER', + 'V6111' => 'Unauthorised API Access, Account Not PCI Certified', + 'V6112' => 'Redundant card details other than expiry year and month', + 'V6113' => 'Invalid transaction for refund', + 'V6114' => 'Gateway validation error', + 'V6115' => 'Invalid DirectRefundRequest, Transaction ID', + 'V6116' => 'Invalid card data on original TransactionID', + 'V6117' => 'Invalid CreateAccessCodeSharedRequest, FooterText', + 'V6118' => 'Invalid CreateAccessCodeSharedRequest, HeaderText', + 'V6119' => 'Invalid CreateAccessCodeSharedRequest, Language', + 'V6120' => 'Invalid CreateAccessCodeSharedRequest, LogoUrl ', + 'V6121' => 'Invalid TransactionSearch, Filter Match Type', + 'V6122' => 'Invalid TransactionSearch, Non numeric Transaction ID', + 'V6123' => 'Invalid TransactionSearch,no TransactionID or AccessCode specified', + 'V6124' => 'Invalid Line Items. The line items have been provided however the totals do not match the TotalAmount field', + 'V6125' => 'Selected Payment Type not enabled', + 'V6126' => 'Invalid encrypted card number, decryption failed', + 'V6127' => 'Invalid encrypted cvn, decryption failed', + 'V6128' => 'Invalid Method for Payment Type', + 'V6129' => 'Transaction has not been authorised for Capture/Cancellation', + 'V6130' => 'Generic customer information error ', + 'V6131' => 'Generic shipping information error', + 'V6132' => 'Transaction has already been completed or voided, operation not permitted', + 'V6133' => 'Checkout not available for Payment Type', + 'V6134' => 'Invalid Auth Transaction ID for Capture/Void', + 'V6135' => 'PayPal Error Processing Refund', + 'V6140' => 'Merchant account is suspended', + 'V6141' => 'Invalid PayPal account details or API signature', + 'V6142' => 'Authorise not available for Bank/Branch', + 'V6150' => 'Invalid Refund Amount', + 'V6151' => 'Refund amount greater than original transaction', + 'V6152' => 'Original transaction already refunded for total amount', + 'V6153' => 'Card type not support by merchant', + } end end end diff --git a/lib/active_merchant/billing/gateways/exact.rb b/lib/active_merchant/billing/gateways/exact.rb index 8b6f04ec282..d9649b84e21 100644 --- a/lib/active_merchant/billing/gateways/exact.rb +++ b/lib/active_merchant/billing/gateways/exact.rb @@ -3,23 +3,22 @@ module Billing #:nodoc: class ExactGateway < Gateway self.live_url = self.test_url = 'https://secure2.e-xact.com/vplug-in/transaction/rpc-enc/service.asmx' - API_VERSION = "8.5" + API_VERSION = '8.5' - TEST_LOGINS = [ {:login => "A00049-01", :password => "test1"}, - {:login => "A00427-01", :password => "testus"} ] - - TRANSACTIONS = { :sale => "00", - :authorization => "01", - :capture => "32", - :credit => "34" } + TEST_LOGINS = [ {:login => 'A00049-01', :password => 'test1'}, + {:login => 'A00427-01', :password => 'testus'} ] + TRANSACTIONS = { :sale => '00', + :authorization => '01', + :capture => '32', + :credit => '34' } ENVELOPE_NAMESPACES = { 'xmlns:xsd' => 'http://www.w3.org/2001/XMLSchema', 'xmlns:env' => 'http://schemas.xmlsoap.org/soap/envelope/', 'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance' } - SEND_AND_COMMIT_ATTRIBUTES = { 'xmlns:n1' => "http://secure2.e-xact.com/vplug-in/transaction/rpc-enc/Request", + SEND_AND_COMMIT_ATTRIBUTES = { 'xmlns:n1' => 'http://secure2.e-xact.com/vplug-in/transaction/rpc-enc/Request', 'env:encodingStyle' => 'http://schemas.xmlsoap.org/soap/encoding/' } @@ -27,11 +26,11 @@ class ExactGateway < Gateway 'xsi:type' => 'n2:Transaction' } - POST_HEADERS = { 'soapAction' => "http://secure2.e-xact.com/vplug-in/transaction/rpc-enc/SendAndCommit", + POST_HEADERS = { 'soapAction' => 'http://secure2.e-xact.com/vplug-in/transaction/rpc-enc/SendAndCommit', 'Content-Type' => 'text/xml' } - SUCCESS = "true" + SUCCESS = 'true' SENSITIVE_FIELDS = [ :verification_str2, :expiry_date, :card_number ] @@ -59,7 +58,7 @@ def capture(money, authorization, options = {}) end def credit(money, authorization, options = {}) - deprecated CREDIT_DEPRECATION_MESSAGE + ActiveMerchant.deprecated CREDIT_DEPRECATION_MESSAGE refund(money, authorization, options) end @@ -68,6 +67,7 @@ def refund(money, authorization, options = {}) end private + def build_request(action, body) xml = Builder::XmlMarkup.new @@ -160,14 +160,21 @@ def expdate(credit_card) end def commit(action, request) - response = parse(ssl_post(self.live_url, build_request(action, request), POST_HEADERS)) - - Response.new(successful?(response), message_from(response), response, - :test => test?, - :authorization => authorization_from(response), - :avs_result => { :code => response[:avs] }, - :cvv_result => response[:cvv2] - ) + response = parse(ssl_post(self.live_url, build_request(action, request), POST_HEADERS)) + + Response.new(successful?(response), message_from(response), response, + :test => test?, + :authorization => authorization_from(response), + :avs_result => { :code => response[:avs] }, + :cvv_result => response[:cvv2] + ) + rescue ResponseError => e + case e.response.code + when '401' + return Response.new(false, "Invalid Login: #{e.response.body}", {}, :test => test?) + else + raise + end end def successful?(response) @@ -176,9 +183,9 @@ def successful?(response) def authorization_from(response) if response[:authorization_num] && response[:transaction_tag] - "#{response[:authorization_num]};#{response[:transaction_tag]}" + "#{response[:authorization_num]};#{response[:transaction_tag]}" else - '' + '' end end @@ -198,13 +205,13 @@ def parse(xml) response = {} xml = REXML::Document.new(xml) - if root = REXML::XPath.first(xml, "//types:TransactionResult") + if root = REXML::XPath.first(xml, '//types:TransactionResult') parse_elements(response, root) - elsif root = REXML::XPath.first(xml, "//soap:Fault") + elsif root = REXML::XPath.first(xml, '//soap:Fault') parse_elements(response, root) end - response.delete_if{ |k,v| SENSITIVE_FIELDS.include?(k) } + response.delete_if { |k, v| SENSITIVE_FIELDS.include?(k) } end def parse_elements(response, root) @@ -215,4 +222,3 @@ def parse_elements(response, root) end end end - diff --git a/lib/active_merchant/billing/gateways/ezic.rb b/lib/active_merchant/billing/gateways/ezic.rb new file mode 100644 index 00000000000..3bfe469c857 --- /dev/null +++ b/lib/active_merchant/billing/gateways/ezic.rb @@ -0,0 +1,195 @@ +module ActiveMerchant + module Billing + class EzicGateway < Gateway + self.live_url = 'https://secure-dm3.ezic.com/gw/sas/direct3.2' + + self.supported_countries = %w(AU CA CN FR DE GI IL MT MU MX NL NZ PA PH RU SG KR ES KN GB US) + self.default_currency = 'USD' + self.supported_cardtypes = [:visa, :master, :american_express, :discover, :jcb, :diners_club] + + self.homepage_url = 'http://www.ezic.com/' + self.display_name = 'Ezic' + + def initialize(options={}) + requires!(options, :account_id) + super + end + + def purchase(money, payment, options={}) + post = {} + + add_account_id(post) + add_invoice(post, money, options) + add_payment(post, payment) + add_customer_data(post, options) + + commit('S', post) + end + + def authorize(money, payment, options={}) + post = {} + + add_account_id(post) + add_invoice(post, money, options) + add_payment(post, payment) + add_customer_data(post, options) + + commit('A', post) + end + + def capture(money, authorization, options={}) + post = {} + + add_account_id(post) + add_invoice(post, money, options) + add_authorization(post, authorization) + add_pay_type(post) + + commit('D', post) + end + + def refund(money, authorization, options={}) + post = {} + + add_account_id(post) + add_invoice(post, money, options) + add_authorization(post, authorization) + add_pay_type(post) + + commit('R', post) + end + + def void(authorization, options={}) + post = {} + + add_account_id(post) + add_authorization(post, authorization) + add_pay_type(post) + + commit('U', post) + end + + def verify(credit_card, options={}) + MultiResponse.run(:use_first_response) do |r| + r.process { authorize(100, credit_card, options) } + r.process(:ignore_result) { void(r.authorization, options) } + end + end + + def supports_scrubbing? + true + end + + def scrub(transcript) + transcript. + gsub(%r((card_number=)\w+), '\1[FILTERED]'). + gsub(%r((card_cvv2=)\w+), '\1[FILTERED]') + end + + private + + def add_account_id(post) + post[:account_id] = @options[:account_id] + end + + def add_addresses(post, options) + add_billing_address(post, options) + add_shipping_address(post, options) + end + + def add_billing_address(post, options) + address = options[:billing_address] || {} + + post[:bill_name1], post[:bill_name2] = split_names(address[:name]) + post[:bill_street] = address[:address1] if address[:address1] + post[:bill_city] = address[:city] if address[:city] + post[:bill_state] = address[:state] if address[:state] + post[:bill_zip] = address[:zip] if address[:zip] + post[:bill_country] = address[:country] if address[:country] + post[:cust_phone] = address[:phone] if address[:phone] + end + + def add_shipping_address(post, options) + address = options[:shipping_address] || {} + + post[:ship_name1], post[:ship_name2] = split_names(address[:name]) + post[:ship_street] = address[:address1] if address[:address1] + post[:ship_city] = address[:city] if address[:city] + post[:ship_state] = address[:state] if address[:state] + post[:ship_zip] = address[:zip] if address[:zip] + post[:ship_country] = address[:country] if address[:country] + end + + def add_customer_data(post, options) + post[:cust_ip] = options[:ip] if options[:ip] + post[:cust_email] = options[:email] if options[:email] + add_addresses(post, options) + end + + def add_invoice(post, money, options) + post[:amount] = amount(money) + post[:description] = options[:description] if options[:description] + end + + def add_payment(post, payment) + add_pay_type(post) + post[:card_number] = payment.number + post[:card_cvv2] = payment.verification_value + post[:card_expire] = expdate(payment) + end + + def add_authorization(post, authorization) + post[:orig_id] = authorization + end + + def add_pay_type(post) + post[:pay_type] = 'C' + end + + def parse(body) + CGI::parse(body).inject({}) { |hash, (key, value)| hash[key] = value.first; hash } + end + + def commit(transaction_type, parameters) + parameters[:tran_type] = transaction_type + + begin + response = parse(ssl_post(live_url, post_data(parameters), headers)) + Response.new( + success_from(response), + message_from(response), + response, + authorization: authorization_from(response), + avs_result: AVSResult.new(code: response['avs_code']), + cvv_result: CVVResult.new(response['cvv2_code']), + test: test? + ) + rescue ResponseError => e + Response.new(false, e.response.message) + end + end + + def success_from(response) + response['status_code'] == '1' || response['status_code'] == 'T' + end + + def message_from(response) + response['auth_msg'] + end + + def authorization_from(response) + response['trans_id'] + end + + def post_data(parameters = {}) + parameters.collect { |key, value| "#{key}=#{CGI.escape(value.to_s)}" }.join('&') + end + + def headers + { + 'User-Agent' => "ActiveMerchantBindings/#{ActiveMerchant::VERSION}", + } + end + end + end +end diff --git a/lib/active_merchant/billing/gateways/fat_zebra.rb b/lib/active_merchant/billing/gateways/fat_zebra.rb index e9d05365ebf..9903e010a99 100644 --- a/lib/active_merchant/billing/gateways/fat_zebra.rb +++ b/lib/active_merchant/billing/gateways/fat_zebra.rb @@ -3,8 +3,8 @@ module ActiveMerchant #:nodoc: module Billing #:nodoc: class FatZebraGateway < Gateway - self.live_url = "https://gateway.fatzebra.com.au/v1.0" - self.test_url = "https://gateway.sandbox.fatzebra.com.au/v1.0" + self.live_url = 'https://gateway.fatzebra.com.au/v1.0' + self.test_url = 'https://gateway.sandbox.fatzebra.com.au/v1.0' self.supported_countries = ['AU'] self.default_currency = 'AUD' @@ -14,69 +14,94 @@ class FatZebraGateway < Gateway self.homepage_url = 'https://www.fatzebra.com.au/' self.display_name = 'Fat Zebra' - # Setup a new instance of the gateway. - # - # The options hash should include :username and :token - # You can find your username and token at https://dashboard.fatzebra.com.au - # Under the Your Account section def initialize(options = {}) requires!(options, :username, :token) - @username = options[:username] - @token = options[:token] super end - # To create a purchase on a credit card use: - # - # purchase(money, creditcard) - # - # To charge a tokenized card - # - # purchase(money, "abzy87u", :cvv => "123") def purchase(money, creditcard, options = {}) post = {} add_amount(post, money, options) add_creditcard(post, creditcard, options) - post[:reference] = options[:order_id] - post[:customer_ip] = options[:ip] + add_extra_options(post, options) + add_order_id(post, options) + add_ip(post, options) + add_metadata(post, options) commit(:post, 'purchases', post) end - # Refund a transaction - # - # amount - Integer - the amount to refund - # txn_id - String - the original transaction to be refunded - # reference - String - your transaction reference - def refund(money, txn_id, reference) + def authorize(money, creditcard, options = {}) post = {} - post[:amount] = money + add_amount(post, money, options) + add_creditcard(post, creditcard, options) + add_extra_options(post, options) + add_order_id(post, options) + add_ip(post, options) + add_metadata(post, options) + + post[:capture] = false + + commit(:post, 'purchases', post) + end + + def capture(money, authorization, options = {}) + txn_id, _ = authorization.to_s.split('|') + post = {} + + add_amount(post, money, options) + add_extra_options(post, options) + + commit(:post, "purchases/#{CGI.escape(txn_id)}/capture", post) + end + + def refund(money, authorization, options={}) + txn_id, _ = authorization.to_s.split('|') + post = {} + + add_extra_options(post, options) + add_amount(post, money, options) post[:transaction_id] = txn_id - post[:reference] = reference + add_order_id(post, options) + + commit(:post, 'refunds', post) + end + + def void(authorization, options={}) + txn_id, endpoint = authorization.to_s.split('|') - commit(:post, "refunds", post) + commit(:post, "#{endpoint}/void?id=#{txn_id}", {}) end - # Tokenize a credit card - # - # The token is returned in the Response#authorization - def store(creditcard) + def store(creditcard, options={}) post = {} + add_creditcard(post, creditcard) - commit(:post, "credit_cards", post) + commit(:post, 'credit_cards', post) + end + + def supports_scrubbing? + true + end + + def scrub(transcript) + transcript. + gsub(%r((Authorization: Basic )\w+), '\1[FILTERED]'). + gsub(%r(("card_number\\?":\\?")[^"\\]*)i, '\1[FILTERED]'). + gsub(%r(("cvv\\?":\\?")\d+), '\1[FILTERED]') end private - # Add the money details to the request def add_amount(post, money, options) + post[:currency] = (options[:currency] || currency(money)) + post[:currency] = post[:currency].upcase if post[:currency] post[:amount] = money end - # Add the credit card details to the request def add_creditcard(post, creditcard, options = {}) if creditcard.respond_to?(:number) post[:card_number] = creditcard.number @@ -84,10 +109,11 @@ def add_creditcard(post, creditcard, options = {}) post[:cvv] = creditcard.verification_value if creditcard.verification_value? post[:card_holder] = creditcard.name if creditcard.name elsif creditcard.is_a?(String) - post[:card_token] = creditcard + id, _ = creditcard.to_s.split('|') + post[:card_token] = id post[:cvv] = options[:cvv] elsif creditcard.is_a?(Hash) - deprecated "Passing the credit card as a Hash is deprecated. Use a String and put the (optional) CVV in the options hash instead." + ActiveMerchant.deprecated 'Passing the credit card as a Hash is deprecated. Use a String and put the (optional) CVV in the options hash instead.' post[:card_token] = creditcard[:token] post[:cvv] = creditcard[:cvv] else @@ -95,12 +121,34 @@ def add_creditcard(post, creditcard, options = {}) end end - # Post the data to the gateway + def add_extra_options(post, options) + extra = {} + extra[:ecm] = '32' if options[:recurring] + extra[:cavv] = options[:cavv] if options[:cavv] + extra[:xid] = options[:xid] if options[:xid] + extra[:sli] = options[:sli] if options[:sli] + extra[:name] = options[:merchant] if options[:merchant] + extra[:location] = options[:merchant_location] if options[:merchant_location] + post[:extra] = extra if extra.any? + end + + def add_order_id(post, options) + post[:reference] = options[:order_id] || SecureRandom.hex(15) + end + + def add_ip(post, options) + post[:customer_ip] = options[:ip] || '127.0.0.1' + end + + def add_metadata(post, options) + post[:metadata] = options.fetch(:metadata, {}) + end + def commit(method, uri, parameters=nil) response = begin parse(ssl_request(method, get_url(uri), parameters.to_json, headers)) rescue ResponseError => e - return Response.new(false, "Invalid Login") if(e.response.code == "401") + return Response.new(false, 'Invalid Login') if(e.response.code == '401') parse(e.response.body) end @@ -109,63 +157,60 @@ def commit(method, uri, parameters=nil) success, message_from(response), response, - :test => response["test"], - :authorization => authorization_from(response, success) + :test => response['test'], + :authorization => authorization_from(response, success, uri) ) end def success_from(response) ( - response["successful"] && - response["response"] && - (response["response"]["successful"] || response["response"]["token"]) + response['successful'] && + response['response'] && + (response['response']['successful'] || response['response']['token'] || response['response']['response_code'] == '00') ) end - def authorization_from(response, success) + def authorization_from(response, success, uri) + endpoint = uri.split('/')[0] if success - (response["response"]["id"] || response["response"]["token"]) + id = response['response']['id'] || response['response']['token'] + "#{id}|#{endpoint}" else nil end end def message_from(response) - if !response["errors"].empty? - response["errors"].join(", ") - elsif response["response"]["message"] - response["response"]["message"] + if !response['errors'].empty? + response['errors'].join(', ') + elsif response['response']['message'] + response['response']['message'] else - "Unknown Error" + 'Unknown Error' end end - # Parse the returned JSON, if parse errors are raised then return a detailed error. def parse(response) - begin - JSON.parse(response) - rescue JSON::ParserError - msg = 'Invalid JSON response received from Fat Zebra. Please contact support@fatzebra.com.au if you continue to receive this message.' - msg += " (The raw response returned by the API was #{response.inspect})" - { - "successful" => false, - "response" => {}, - "errors" => [msg] - } - end + JSON.parse(response) + rescue JSON::ParserError + msg = 'Invalid JSON response received from Fat Zebra. Please contact support@fatzebra.com.au if you continue to receive this message.' + msg += " (The raw response returned by the API was #{response.inspect})" + { + 'successful' => false, + 'response' => {}, + 'errors' => [msg] + } end - # Build the URL based on the AM mode and the URI def get_url(uri) base = test? ? self.test_url : self.live_url - base + "/" + uri + base + '/' + uri end - # Builds the auth and U-A headers for the request def headers { - "Authorization" => "Basic " + Base64.strict_encode64(@username.to_s + ":" + @token.to_s).strip, - "User-Agent" => "Fat Zebra v1.0/ActiveMerchant #{ActiveMerchant::VERSION}" + 'Authorization' => 'Basic ' + Base64.strict_encode64(@options[:username].to_s + ':' + @options[:token].to_s).strip, + 'User-Agent' => "Fat Zebra v1.0/ActiveMerchant #{ActiveMerchant::VERSION}" } end end diff --git a/lib/active_merchant/billing/gateways/federated_canada.rb b/lib/active_merchant/billing/gateways/federated_canada.rb index d08e87a44e4..b6666a9fa44 100644 --- a/lib/active_merchant/billing/gateways/federated_canada.rb +++ b/lib/active_merchant/billing/gateways/federated_canada.rb @@ -58,7 +58,7 @@ def refund(money, authorization, options = {}) end def credit(money, authorization, options = {}) - deprecated CREDIT_DEPRECATION_MESSAGE + ActiveMerchant.deprecated CREDIT_DEPRECATION_MESSAGE refund(money, authorization, options) end @@ -107,12 +107,6 @@ def add_creditcard(post, creditcard) post[:cvv] = creditcard.verification_value end - def expdate(creditcard) - year = sprintf("%.4i", creditcard.year) - month = sprintf("%.2i", creditcard.month) - "#{month}#{year[-2..-1]}" - end - def parse(body) body.split('&').inject({}) do |memo, x| k, v = x.split('=') @@ -126,7 +120,6 @@ def commit(action, money, parameters) data = ssl_post(self.live_url, post_data(action, parameters)) response = parse(data) message = message_from(response) - test_mode = test? Response.new(success?(response), message, response, :test => test?, @@ -141,17 +134,17 @@ def success?(response) end def test? - (@options[:login].eql?('demo')) && (@options[:password].eql?('password')) + @options[:login].eql?('demo') && @options[:password].eql?('password') end def message_from(response) case response['response'].to_i when APPROVED - "Transaction Approved" + 'Transaction Approved' when DECLINED - "Transaction Declined" + 'Transaction Declined' else - "Error in transaction data or system error" + 'Error in transaction data or system error' end end @@ -159,9 +152,8 @@ def post_data(action, parameters = {}) parameters[:type] = action parameters[:username] = @options[:login] parameters[:password] = @options[:password] - parameters.map{|k, v| "#{k}=#{CGI.escape(v.to_s)}"}.join('&') + parameters.map { |k, v| "#{k}=#{CGI.escape(v.to_s)}" }.join('&') end end end end - diff --git a/lib/active_merchant/billing/gateways/finansbank.rb b/lib/active_merchant/billing/gateways/finansbank.rb index a81cecd5448..5f496570853 100644 --- a/lib/active_merchant/billing/gateways/finansbank.rb +++ b/lib/active_merchant/billing/gateways/finansbank.rb @@ -1,9 +1,10 @@ -require File.dirname(__FILE__) + '/cc5' +require 'active_merchant/billing/gateways/cc5' module ActiveMerchant #:nodoc: module Billing #:nodoc: class FinansbankGateway < CC5Gateway - self.live_url = self.test_url = 'https://www.fbwebpos.com/servlet/cc5ApiServer' + self.live_url = 'https://www.fbwebpos.com/servlet/cc5ApiServer' + self.test_url = 'https://entegrasyon.asseco-see.com.tr/fim/api' # The countries the gateway supports merchants from as 2 digit ISO country codes self.supported_countries = ['US', 'TR'] @@ -19,4 +20,3 @@ class FinansbankGateway < CC5Gateway end end end - diff --git a/lib/active_merchant/billing/gateways/first_giving.rb b/lib/active_merchant/billing/gateways/first_giving.rb new file mode 100644 index 00000000000..09dea7f8e5a --- /dev/null +++ b/lib/active_merchant/billing/gateways/first_giving.rb @@ -0,0 +1,142 @@ +require 'nokogiri' + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class FirstGivingGateway < Gateway + self.test_url = 'http://usapisandbox.fgdev.net' + self.live_url = 'https://api.firstgiving.com' + + self.supported_countries = ['US'] + self.supported_cardtypes = [:visa, :master, :american_express, :discover] + self.homepage_url = 'http://www.firstgiving.com/' + self.default_currency = 'USD' + self.display_name = 'FirstGiving' + + def initialize(options = {}) + requires!(options, :application_key, :security_token, :charity_id) + super + end + + def purchase(money, creditcard, options = {}) + post = {} + add_invoice(post, options) + add_creditcard(post, creditcard) + add_address(post, options) + add_customer_data(post, options) + add_donation_data(post, money, options) + commit('/donation/creditcard', post) + end + + def refund(money, identifier, options = {}) + get = {} + get[:transactionId] = identifier + get[:tranType] = 'REFUNDREQUEST' + commit('/transaction/refundrequest?' + encode(get)) + end + + private + + def add_donation_data(post, money, options) + post[:amount] = amount(money) + post[:charityId] = @options[:charity_id] + post[:description] = (options[:description] || 'Purchase') + post[:currencyCode] = (options[:currency] || currency(money)) + end + + def add_customer_data(post, options) + post[:billToEmail] = (options[:email] || 'activemerchant@example.com') + post[:remoteAddr] = (options[:ip] || '127.0.0.1') + end + + def add_address(post, options) + if(billing_address = (options[:billing_address] || options[:address])) + post[:billToAddressLine1] = billing_address[:address1] + post[:billToCity] = billing_address[:city] + post[:billToState] = billing_address[:state] + post[:billToZip] = billing_address[:zip] + post[:billToCountry] = billing_address[:country] + end + end + + def add_invoice(post, options) + post[:orderId] = options[:order_id] + end + + def add_creditcard(post, creditcard) + post[:billToFirstName] = creditcard.first_name + post[:billToLastName] = creditcard.last_name + post[:ccNumber] = creditcard.number + post[:ccType] = creditcard_brand(creditcard.brand) + post[:ccExpDateMonth] = creditcard.month + post[:ccExpDateYear] = creditcard.year + post[:ccCardValidationNum] = creditcard.verification_value + end + + def parse(body) + response = {} + + xml = Nokogiri::XML(body) + element = xml.xpath('//firstGivingDonationApi/firstGivingResponse').first + + element.attributes.each do |name, attribute| + response[name] = attribute.content + end + element.children.each do |child| + next if child.text? + response[child.name] = child.text + end + + response + end + + def commit(action, post=nil) + url = (test? ? self.test_url : self.live_url) + action + + begin + if post + response = parse(ssl_post(url, post_data(post), headers)) + else + response = parse(ssl_get(url, headers)) + end + rescue ResponseError => e + response = parse(e.response.body) + end + + Response.new( + (response['acknowledgement'] == 'Success'), + (response['friendlyErrorMessage'] || response['verboseErrorMessage'] || response['acknowledgement']), + response, + authorization: response['transactionId'], + test: test? + ) + end + + def post_data(post) + post.collect { |key, value| "#{key}=#{CGI.escape(value.to_s)}" }.join('&') + end + + def encode(hash) + hash.collect { |(k, v)| "#{CGI.escape(k.to_s)}=#{CGI.escape(v.to_s)}" }.join('&') + end + + def creditcard_brand(brand) + case brand + when 'visa' then 'VI' + when 'master' then 'MC' + when 'discover' then 'DI' + when 'american_express' then 'AX' + else + raise "Unhandled credit card brand #{brand}" + end + end + + def headers + { + 'User-Agent' => "ActiveMerchantBindings/#{ActiveMerchant::VERSION}", + 'JG_APPLICATIONKEY' => @options[:application_key].to_s, + 'JG_SECURITYTOKEN' => @options[:security_token].to_s + } + end + end + end +end diff --git a/lib/active_merchant/billing/gateways/first_pay.rb b/lib/active_merchant/billing/gateways/first_pay.rb index b356dddb7cf..3c197f0d79e 100644 --- a/lib/active_merchant/billing/gateways/first_pay.rb +++ b/lib/active_merchant/billing/gateways/first_pay.rb @@ -1,176 +1,182 @@ +require 'nokogiri' + module ActiveMerchant #:nodoc: module Billing #:nodoc: class FirstPayGateway < Gateway - class FirstPayPostData < PostData - # Fields that will be sent even if they are blank - self.required_fields = [ :action, :amount, :trackid ] - end + self.live_url = 'https://secure.goemerchant.com/secure/gateway/xmlgateway.aspx' - # both URLs are IP restricted - self.test_url = 'https://apgcert.first-pay.com/AcqENGIN/SecureCapture' - self.live_url = 'https://acqengin.first-pay.com/AcqENGIN/SecureCapture' - - # The countries the gateway supports merchants from as 2 digit ISO country codes self.supported_countries = ['US'] + self.default_currency = 'USD' + self.money_format = :dollars + self.supported_cardtypes = [:visa, :master, :american_express, :discover] - # The card types supported by the payment gateway - self.supported_cardtypes = [:visa, :master] - - # The homepage URL of the gateway - self.homepage_url = 'http://www.first-pay.com' + self.homepage_url = 'http://1stpaygateway.net/' + self.display_name = '1stPayGateway.Net' - # The name of the gateway - self.display_name = 'First Pay' - - # all transactions are in cents - self.money_format = :cents - - ACTIONS = { - 'sale' => 1, - 'credit' => 2, - 'void' => 3 - } - - def initialize(options = {}) - requires!(options, :login, :password) + def initialize(options={}) + requires!(options, :transaction_center_id, :gateway_id) super end - def purchase(money, creditcard, options = {}) - post = FirstPayPostData.new - add_invoice(post, options) - add_creditcard(post, creditcard) - add_address(post, options) + def purchase(money, payment, options={}) + post = {} + add_invoice(post, money, options) + add_payment(post, payment, options) + add_address(post, payment, options) add_customer_data(post, options) - commit('sale', money, post) + commit('sale', post) end - def refund(money, reference, options = {}) - requires!(options, :credit_card) - - post = FirstPayPostData.new - add_invoice(post, options) - add_creditcard(post, options[:credit_card]) - add_address(post, options) + def authorize(money, payment, options={}) + post = {} + add_invoice(post, money, options) + add_payment(post, payment, options) + add_address(post, payment, options) add_customer_data(post, options) - add_credit_data(post, reference) - commit('credit', money, post) + commit('auth', post) end - def credit(money, reference, options = {}) - deprecated CREDIT_DEPRECATION_MESSAGE - refund(money, reference, options) + def capture(money, authorization, options={}) + post = {} + add_reference(post, 'settle', money, authorization) + commit('settle', post) end - def void(money, creditcard, options = {}) - post = FirstPayPostData.new - add_creditcard(post, creditcard) - add_void_data(post, options) - add_invoice(post, options) - add_customer_data(post, options) + def refund(money, authorization, options={}) + post = {} + add_reference(post, 'credit', money, authorization) + commit('credit', post) + end - commit('void', money, post) + def void(authorization, options={}) + post = {} + add_reference(post, 'void', nil, authorization) + commit('void', post) end + def supports_scrubbing? + true + end + + def scrub(transcript) + transcript. + gsub(%r((gateway_id)[^<]*())i, '\1[FILTERED]\2'). + gsub(%r((card_number)[^<]*())i, '\1[FILTERED]\2'). + gsub(%r((cvv2)[^<]*())i, '\1[FILTERED]\2') + end private - def add_customer_data(post, options) - post[:cardip] = options[:ip] - post[:email] = options[:email] + def add_authentication(post, options) + post[:transaction_center_id] = options[:transaction_center_id] + post[:gateway_id] = options[:gateway_id] end - def add_address(post, options) - if billing_address = options[:billing_address] || options[:address] - post[:addr] = billing_address[:address1].to_s + ' ' + billing_address[:address2].to_s - post[:city] = billing_address[:city] - post[:state] = billing_address[:state] - post[:zip] = billing_address[:zip] - post[:country] = billing_address[:country] + def add_customer_data(post, options) + post[:owner_email] = options[:email] if options[:email] + post[:remote_ip_address] = options[:ip] if options[:ip] + post[:processor_id] = options[:processor_id] if options[:processor_id] + end + + def add_address(post, creditcard, options) + if address = options[:billing_address] || options[:address] + post[:owner_name] = address[:name] + post[:owner_street] = address[:address1] + post[:owner_street2] = address[:address2] if address[:address2] + post[:owner_city] = address[:city] + post[:owner_state] = address[:state] + post[:owner_zip] = address[:zip] + post[:owner_country] = address[:country] + post[:owner_phone] = address[:phone] if address[:phone] end end - def add_invoice(post, options) - post[:trackid] = rand(Time.now.to_i) + def add_invoice(post, money, options) + post[:order_id] = options[:order_id] + post[:total] = amount(money) end - def add_creditcard(post, creditcard) - post[:member] = creditcard.first_name.to_s + " " + creditcard.last_name.to_s - post[:card] = creditcard.number - post[:exp] = expdate(creditcard) + def add_payment(post, payment, options) + post[:card_name] = payment.brand # Unclear if need to map to known names or open text field?? + post[:card_number] = payment.number + post[:card_exp] = expdate(payment) + post[:cvv2] = payment.verification_value + post[:recurring] = options[:recurring] if options[:recurring] + post[:recurring_start_date] = options[:recurring_start_date] if options[:recurring_start_date] + post[:recurring_end_date] = options[:recurring_end_date] if options[:recurring_end_date] + post[:recurring_type] = options[:recurring_type] if options[:recurring_type] end - def expdate(credit_card) - year = sprintf("%.4i", credit_card.year) - month = sprintf("%.2i", credit_card.month) - - "#{month}#{year[-2..-1]}" + def add_reference(post, action, money, authorization) + post[:"#{action}_amount1"] = amount(money) if money + post[:total_number_transactions] = 1 + post[:reference_number1] = authorization end - def add_credit_data(post, transaction_id) - post[:transid] = transaction_id - end + def parse(xml) + response = {} - def add_void_data(post, options) - post[:transid] = options[:transactionid] + doc = Nokogiri::XML(xml) + doc.root&.xpath('//RESPONSE/FIELDS/FIELD')&.each do |field| + response[field['KEY']] = field.text + end + + response end - def commit(action, money, post) - response = parse( ssl_post(test? ? self.test_url : self.live_url, post_data(action, post, money)) ) + def commit(action, parameters) + response = parse(ssl_post(live_url, post_data(action, parameters))) - Response.new(response[:response] == 'CAPTURED', response[:message], response, - :test => test?, - :authorization => response[:authorization], - :avs_result => { :code => response[:avsresponse] }, - :cvv_result => response[:cvvresponse]) + Response.new( + success_from(response), + message_from(response), + response, + authorization: authorization_from(response), + error_code: error_code_from(response), + test: test? + ) end - def parse(body) - response = {} + def success_from(response) + ( + (response['status'] == '1') || + (response['status1'] == '1') + ) + end - # check for an error first - if body.include?('!ERROR!') - response[:response] = 'ERROR' - response[:message] = error_message_from(body) - else - # a capture / not captured response will be : delimited - split = body.split(':') - response[:response] = split[0] - - # FirstPay docs are worthless. turns out the transactionid is required for credits - # so we need to store that in authorization, not the actual auth. - if response[:response] == 'CAPTURED' - response[:message] = 'CAPTURED' - response[:authorization] = split[9] # actually the transactionid - response[:auth] = split[1] - response[:avsresponse] = split[3] - response[:cvvresponse] = split[17] - else - # NOT CAPTURED response - response[:message] = split[1] - response[:transactionid] = split[9] - end - end + def message_from(response) + # Silly inconsistent gateway. Always make capitalized (but not all caps) + msg = (response['auth_response'] || response['response1']) + msg&.downcase&.capitalize + end - return response + def error_code_from(response) + response['error'] end - def error_message_from(response) - # error messages use this format - '!ERROR! 704-MISSING BASIC DATA TYPE:card, exp, zip, addr, member, amount\n' - response.split("! ")[1].chomp + def authorization_from(response) + response['reference_number'] || response['reference_number1'] end - def post_data(action, post, money) - post[:vid] = @options[:login] - post[:password] = @options[:password] - post[:action] = ACTIONS[action] - post[:amount] = amount(money) + def post_data(action, parameters = {}) + parameters[:transaction_center_id] = @options[:transaction_center_id] + parameters[:gateway_id] = @options[:gateway_id] + + parameters[:operation_type] = action - return post.to_post_data + xml = Builder::XmlMarkup.new + xml.instruct! + xml.tag! 'TRANSACTION' do + xml.tag! 'FIELDS' do + parameters.each do |key, value| + xml.tag! 'FIELD', value, { 'KEY' => key } + end + end + end + xml.target! end end end end - diff --git a/lib/active_merchant/billing/gateways/firstdata_e4.rb b/lib/active_merchant/billing/gateways/firstdata_e4.rb old mode 100644 new mode 100755 index 1915ca758cc..12290ad51c6 --- a/lib/active_merchant/billing/gateways/firstdata_e4.rb +++ b/lib/active_merchant/billing/gateways/firstdata_e4.rb @@ -2,32 +2,66 @@ module ActiveMerchant #:nodoc: module Billing #:nodoc: class FirstdataE4Gateway < Gateway # TransArmor support requires v11 or lower - self.test_url = "https://api.demo.globalgatewaye4.firstdata.com/transaction/v11" - self.live_url = "https://api.globalgatewaye4.firstdata.com/transaction/v11" + self.test_url = 'https://api.demo.globalgatewaye4.firstdata.com/transaction/v11' + self.live_url = 'https://api.globalgatewaye4.firstdata.com/transaction/v11' TRANSACTIONS = { - :sale => "00", - :authorization => "01", - :capture => "32", - :void => "33", - :credit => "34", - :store => "05" + sale: '00', + authorization: '01', + verify: '05', + capture: '32', + void: '33', + credit: '34', + store: '05' } POST_HEADERS = { - "Accepts" => "application/xml", - "Content-Type" => "application/xml" + 'Accepts' => 'application/xml', + 'Content-Type' => 'application/xml' } - SUCCESS = "true" + SUCCESS = 'true' SENSITIVE_FIELDS = [:verification_str2, :expiry_date, :card_number] - self.supported_cardtypes = [:visa, :master, :american_express, :jcb, :discover] - self.supported_countries = ["CA", "US"] - self.default_currency = "USD" - self.homepage_url = "http://www.firstdata.com" - self.display_name = "FirstData Global Gateway e4" + BRANDS = { + :visa => 'Visa', + :master => 'Mastercard', + :american_express => 'American Express', + :jcb => 'JCB', + :discover => 'Discover' + } + + E4_BRANDS = BRANDS.merge({:mastercard => 'Mastercard'}) + + DEFAULT_ECI = '07' + + self.supported_cardtypes = BRANDS.keys + self.supported_countries = ['CA', 'US'] + self.default_currency = 'USD' + self.homepage_url = 'http://www.firstdata.com' + self.display_name = 'FirstData Global Gateway e4' + + STANDARD_ERROR_CODE_MAPPING = { + # Bank error codes: https://firstdata.zendesk.com/entries/471297-First-Data-Global-Gateway-e4-Bank-Response-Codes + '201' => STANDARD_ERROR_CODE[:incorrect_number], + '531' => STANDARD_ERROR_CODE[:invalid_cvc], + '503' => STANDARD_ERROR_CODE[:invalid_cvc], + '811' => STANDARD_ERROR_CODE[:invalid_cvc], + '605' => STANDARD_ERROR_CODE[:invalid_expiry_date], + '522' => STANDARD_ERROR_CODE[:expired_card], + '303' => STANDARD_ERROR_CODE[:card_declined], + '530' => STANDARD_ERROR_CODE[:card_declined], + '401' => STANDARD_ERROR_CODE[:call_issuer], + '402' => STANDARD_ERROR_CODE[:call_issuer], + '501' => STANDARD_ERROR_CODE[:pickup_card], + # Ecommerce error codes -- https://firstdata.zendesk.com/entries/451980-ecommerce-response-codes-etg-codes + '22' => STANDARD_ERROR_CODE[:invalid_number], + '25' => STANDARD_ERROR_CODE[:invalid_expiry_date], + '31' => STANDARD_ERROR_CODE[:incorrect_cvc], + '44' => STANDARD_ERROR_CODE[:incorrect_zip], + '42' => STANDARD_ERROR_CODE[:processing_error] + } # Create a new FirstdataE4Gateway # @@ -66,6 +100,10 @@ def refund(money, authorization, options = {}) commit(:credit, build_capture_or_credit_request(money, authorization, options)) end + def verify(credit_card, options = {}) + commit(:verify, build_sale_or_authorization_request(0, credit_card, options)) + end + # Tokenize a credit card with TransArmor # # The TransArmor token and other card data necessary for subsequent @@ -93,13 +131,35 @@ def store(credit_card, options = {}) commit(:store, build_store_request(credit_card, options), credit_card) end + def verify_credentials + response = void('0') + response.message != 'Unauthorized Request. Bad or missing credentials.' + end + + def supports_scrubbing? + true + end + + def scrub(transcript) + transcript. + gsub(%r(().+()), '\1[FILTERED]\2'). + gsub(%r(().+()), '\1[FILTERED]\2'). + gsub(%r(().+())i, '\1[FILTERED]\2'). + gsub(%r(().+()), '\1[FILTERED]\2'). + gsub(%r((Card Number : ).*\d)i, '\1[FILTERED]') + end + + def supports_network_tokenization? + true + end + private def build_request(action, body) xml = Builder::XmlMarkup.new xml.instruct! - xml.tag! "Transaction" do + xml.tag! 'Transaction', xmlns: 'http://secure2.e-xact.com/vplug-in/transaction/rpc-enc/encodedTypes' do add_credentials(xml) add_transaction_type(xml, action) xml << body @@ -111,16 +171,18 @@ def build_request(action, body) def build_sale_or_authorization_request(money, credit_card_or_store_authorization, options) xml = Builder::XmlMarkup.new - add_amount(xml, money) + add_amount(xml, money, options) if credit_card_or_store_authorization.is_a? String - add_credit_card_token(xml, credit_card_or_store_authorization) + add_credit_card_token(xml, credit_card_or_store_authorization, options) else add_credit_card(xml, credit_card_or_store_authorization, options) end add_customer_data(xml, options) add_invoice(xml, options) + add_tax_fields(xml, options) + add_level_3(xml, options) xml.target! end @@ -129,8 +191,9 @@ def build_capture_or_credit_request(money, identification, options) xml = Builder::XmlMarkup.new add_identification(xml, identification) - add_amount(xml, money) + add_amount(xml, money, options) add_customer_data(xml, options) + add_card_authentication_data(xml, options) xml.target! end @@ -145,32 +208,53 @@ def build_store_request(credit_card, options) end def add_credentials(xml) - xml.tag! "ExactID", @options[:login] - xml.tag! "Password", @options[:password] + xml.tag! 'ExactID', @options[:login] + xml.tag! 'Password', @options[:password] end def add_transaction_type(xml, action) - xml.tag! "Transaction_Type", TRANSACTIONS[action] + xml.tag! 'Transaction_Type', TRANSACTIONS[action] end def add_identification(xml, identification) - authorization_num, transaction_tag, _ = identification.split(";") + authorization_num, transaction_tag, _ = identification.split(';') - xml.tag! "Authorization_Num", authorization_num - xml.tag! "Transaction_Tag", transaction_tag + xml.tag! 'Authorization_Num', authorization_num + xml.tag! 'Transaction_Tag', transaction_tag end - def add_amount(xml, money) - xml.tag! "DollarAmount", amount(money) + def add_amount(xml, money, options) + currency_code = options[:currency] || default_currency + xml.tag! 'DollarAmount', localized_amount(money, currency_code) + xml.tag! 'Currency', currency_code end def add_credit_card(xml, credit_card, options) - xml.tag! "Card_Number", credit_card.number - xml.tag! "Expiry_Date", expdate(credit_card) - xml.tag! "CardHoldersName", credit_card.name - xml.tag! "CardType", credit_card.brand + if credit_card.respond_to?(:track_data) && credit_card.track_data.present? + xml.tag! 'Track1', credit_card.track_data + xml.tag! 'Ecommerce_Flag', 'R' + else + xml.tag! 'Card_Number', credit_card.number + xml.tag! 'Expiry_Date', expdate(credit_card) + xml.tag! 'CardHoldersName', credit_card.name + xml.tag! 'CardType', card_type(credit_card.brand) + + add_credit_card_eci(xml, credit_card, options) + add_credit_card_verification_strings(xml, credit_card, options) + end + end + + def add_credit_card_eci(xml, credit_card, options) + eci = if credit_card.is_a?(NetworkTokenizationCreditCard) && credit_card.source == :apple_pay && card_brand(credit_card) == 'discover' + # Discover requires any Apple Pay transaction, regardless of in-app + # or web, and regardless of the ECI contained in the PKPaymentToken, + # to have an ECI value explicitly of 04. + '04' + else + (credit_card.respond_to?(:eci) ? credit_card.eci : nil) || options[:eci] || DEFAULT_ECI + end - add_credit_card_verification_strings(xml, credit_card, options) + xml.tag! 'Ecommerce_Flag', eci.to_s =~ /^[0-9]+$/ ? eci.to_s.rjust(2, '0') : eci end def add_credit_card_verification_strings(xml, credit_card, options) @@ -178,17 +262,40 @@ def add_credit_card_verification_strings(xml, credit_card, options) if address address_values = [] [:address1, :zip, :city, :state, :country].each { |part| address_values << address[part].to_s } - xml.tag! "VerificationStr1", address_values.join("|") + xml.tag! 'VerificationStr1', address_values.join('|') end - if credit_card.verification_value? - xml.tag! "CVD_Presence_Ind", "1" - xml.tag! "VerificationStr2", credit_card.verification_value + if credit_card.is_a?(NetworkTokenizationCreditCard) + add_network_tokenization_credit_card(xml, credit_card) + else + if credit_card.verification_value? + xml.tag! 'CVD_Presence_Ind', '1' + xml.tag! 'VerificationStr2', credit_card.verification_value + end + + add_card_authentication_data(xml, options) end end - def add_credit_card_token(xml, store_authorization) - params = store_authorization.split(";") + def add_network_tokenization_credit_card(xml, credit_card) + case card_brand(credit_card).to_sym + when :american_express + cryptogram = Base64.decode64(credit_card.payment_cryptogram) + xml.tag!('XID', Base64.encode64(cryptogram[20...40])) + xml.tag!('CAVV', Base64.encode64(cryptogram[0...20])) + else + xml.tag!('XID', credit_card.transaction_id) if credit_card.transaction_id + xml.tag!('CAVV', credit_card.payment_cryptogram) + end + end + + def add_card_authentication_data(xml, options) + xml.tag! 'CAVV', options[:cavv] + xml.tag! 'XID', options[:xid] + end + + def add_credit_card_token(xml, store_authorization, options) + params = store_authorization.split(';') credit_card = CreditCard.new( :brand => params[1], :first_name => params[2], @@ -196,33 +303,47 @@ def add_credit_card_token(xml, store_authorization) :month => params[4], :year => params[5]) - xml.tag! "TransarmorToken", params[0] - xml.tag! "Expiry_Date", expdate(credit_card) - xml.tag! "CardHoldersName", credit_card.name - xml.tag! "CardType", credit_card.brand + xml.tag! 'TransarmorToken', params[0] + xml.tag! 'Expiry_Date', expdate(credit_card) + xml.tag! 'CardHoldersName', credit_card.name + xml.tag! 'CardType', card_type(credit_card.brand) + add_card_authentication_data(xml, options) end def add_customer_data(xml, options) - xml.tag! "Customer_Ref", options[:customer] if options[:customer] - xml.tag! "Client_IP", options[:ip] if options[:ip] - xml.tag! "Client_Email", options[:email] if options[:email] + xml.tag! 'Customer_Ref', options[:customer] if options[:customer] + xml.tag! 'Client_IP', options[:ip] if options[:ip] + xml.tag! 'Client_Email', options[:email] if options[:email] end def add_address(xml, options) if address = (options[:billing_address] || options[:address]) - xml.tag! "ZipCode", address[:zip] + xml.tag! 'ZipCode', address[:zip] end end def add_invoice(xml, options) - xml.tag! "Reference_No", options[:order_id] - xml.tag! "Reference_3", options[:description] if options[:description] + xml.tag! 'Reference_No', options[:order_id] + xml.tag! 'Reference_3', options[:description] if options[:description] + end + + def add_tax_fields(xml, options) + xml.tag! 'Tax1Amount', options[:tax1_amount] if options[:tax1_amount] + xml.tag! 'Tax1Number', options[:tax1_number] if options[:tax1_number] + end + + def add_level_3(xml, options) + xml.tag!('Level3') { |x| x << options[:level_3] } if options[:level_3] end def expdate(credit_card) "#{format(credit_card.month, :two_digits)}#{format(credit_card.year, :two_digits)}" end + def card_type(credit_card_brand) + E4_BRANDS[credit_card_brand.to_sym] if credit_card_brand + end + def commit(action, request, credit_card = nil) url = (test? ? self.test_url : self.live_url) begin @@ -233,9 +354,10 @@ def commit(action, request, credit_card = nil) Response.new(successful?(response), message_from(response), response, :test => test?, - :authorization => response_authorization(action, response, credit_card), + :authorization => successful?(response) ? response_authorization(action, response, credit_card) : '', :avs_result => {:code => response[:avs]}, - :cvv_result => response[:cvv2] + :cvv_result => response[:cvv2], + :error_code => standard_error_code(response) ) end @@ -256,10 +378,10 @@ def authorization_from(response) [ response[:authorization_num], response[:transaction_tag], - (response[:dollar_amount].to_f * 100).to_i - ].join(";") + (response[:dollar_amount].to_f * 100).round + ].join(';') else - "" + '' end end @@ -272,7 +394,7 @@ def store_authorization_from(response, credit_card) credit_card.last_name, credit_card.month, credit_card.year - ].map { |value| value.to_s.gsub(/;/, "") }.join(";") + ].map { |value| value.to_s.gsub(/;/, '') }.join(';') else raise StandardError, "TransArmor support is not enabled on your #{display_name} account" end @@ -280,16 +402,16 @@ def store_authorization_from(response, credit_card) def money_from_authorization(auth) _, _, amount = auth.split(/;/, 3) - amount.to_i # return the # of cents, no need to divide + amount.to_i end def message_from(response) if(response[:faultcode] && response[:faultstring]) response[:faultstring] - elsif(response[:error_number] && response[:error_number] != "0") + elsif(response[:error_number] && response[:error_number] != '0') response[:error_description] else - result = (response[:exact_message] || "") + result = (response[:exact_message] || '') result << " - #{response[:bank_message]}" if response[:bank_message].present? result end @@ -297,29 +419,33 @@ def message_from(response) def parse_error(error) { - :transaction_approved => "false", + :transaction_approved => 'false', :error_number => error.code, - :error_description => error.body + :error_description => error.body, + :ecommerce_error_code => error.body.gsub(/[^\d]/, '') } end + def standard_error_code(response) + STANDARD_ERROR_CODE_MAPPING[response[:bank_resp_code] || response[:ecommerce_error_code]] + end + def parse(xml) response = {} xml = REXML::Document.new(xml) - if root = REXML::XPath.first(xml, "//TransactionResult") + if root = REXML::XPath.first(xml, '//TransactionResult') parse_elements(response, root) end - response.delete_if{ |k,v| SENSITIVE_FIELDS.include?(k) } + response.delete_if { |k, v| SENSITIVE_FIELDS.include?(k) } end def parse_elements(response, root) root.elements.to_a.each do |node| - response[node.name.gsub(/EXact/, "Exact").underscore.to_sym] = (node.text || "").strip + response[node.name.gsub(/EXact/, 'Exact').underscore.to_sym] = (node.text || '').strip end end end end end - diff --git a/lib/active_merchant/billing/gateways/firstdata_e4_v27.rb b/lib/active_merchant/billing/gateways/firstdata_e4_v27.rb new file mode 100644 index 00000000000..4124139a7ca --- /dev/null +++ b/lib/active_merchant/billing/gateways/firstdata_e4_v27.rb @@ -0,0 +1,485 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class FirstdataE4V27Gateway < Gateway + self.test_url = 'https://api.demo.globalgatewaye4.firstdata.com/transaction/v28' + self.live_url = 'https://api.globalgatewaye4.firstdata.com/transaction/v28' + + TRANSACTIONS = { + sale: '00', + authorization: '01', + verify: '05', + capture: '32', + void: '33', + credit: '34', + store: '05' + } + + SUCCESS = 'true' + + SENSITIVE_FIELDS = [:cvdcode, :expiry_date, :card_number] + + BRANDS = { + :visa => 'Visa', + :master => 'Mastercard', + :american_express => 'American Express', + :jcb => 'JCB', + :discover => 'Discover' + } + + DEFAULT_ECI = '07' + + self.supported_cardtypes = BRANDS.keys + self.supported_countries = ['CA', 'US'] + self.default_currency = 'USD' + self.homepage_url = 'http://www.firstdata.com' + self.display_name = 'FirstData Global Gateway e4 v27' + + STANDARD_ERROR_CODE_MAPPING = { + # Bank error codes: https://support.payeezy.com/hc/en-us/articles/203730509-First-Data-Global-Gateway-e4-Bank-Response-Codes + '201' => STANDARD_ERROR_CODE[:incorrect_number], + '531' => STANDARD_ERROR_CODE[:invalid_cvc], + '503' => STANDARD_ERROR_CODE[:invalid_cvc], + '811' => STANDARD_ERROR_CODE[:invalid_cvc], + '605' => STANDARD_ERROR_CODE[:invalid_expiry_date], + '522' => STANDARD_ERROR_CODE[:expired_card], + '303' => STANDARD_ERROR_CODE[:card_declined], + '530' => STANDARD_ERROR_CODE[:card_declined], + '401' => STANDARD_ERROR_CODE[:call_issuer], + '402' => STANDARD_ERROR_CODE[:call_issuer], + '501' => STANDARD_ERROR_CODE[:pickup_card], + # Ecommerce error codes: https://support.payeezy.com/hc/en-us/articles/203730499-eCommerce-Response-Codes-ETG-e4-Transaction-Gateway-Codes + '22' => STANDARD_ERROR_CODE[:invalid_number], + '25' => STANDARD_ERROR_CODE[:invalid_expiry_date], + '31' => STANDARD_ERROR_CODE[:incorrect_cvc], + '44' => STANDARD_ERROR_CODE[:incorrect_zip], + '42' => STANDARD_ERROR_CODE[:processing_error] + } + + def initialize(options = {}) + requires!(options, :login, :password, :key_id, :hmac_key) + @options = options + + super + end + + def authorize(money, credit_card_or_store_authorization, options = {}) + commit(:authorization, build_sale_or_authorization_request(money, credit_card_or_store_authorization, options)) + end + + def purchase(money, credit_card_or_store_authorization, options = {}) + commit(:sale, build_sale_or_authorization_request(money, credit_card_or_store_authorization, options)) + end + + def capture(money, authorization, options = {}) + commit(:capture, build_capture_or_credit_request(money, authorization, options)) + end + + def void(authorization, options = {}) + commit(:void, build_capture_or_credit_request(money_from_authorization(authorization), authorization, options)) + end + + def refund(money, authorization, options = {}) + commit(:credit, build_capture_or_credit_request(money, authorization, options)) + end + + def verify(credit_card, options = {}) + commit(:verify, build_sale_or_authorization_request(0, credit_card, options)) + end + + # Tokenize a credit card with TransArmor + # + # The TransArmor token and other card data necessary for subsequent + # transactions is stored in the response's +authorization+ attribute. + # The authorization string may be passed to +authorize+ and +purchase+ + # instead of a +ActiveMerchant::Billing::CreditCard+ instance. + # + # TransArmor support must be explicitly activated on your gateway + # account by FirstData. If your authorization string is empty, contact + # FirstData support for account setup assistance. + # + # https://support.payeezy.com/hc/en-us/articles/203731189-TransArmor-Tokenization + def store(credit_card, options = {}) + commit(:store, build_store_request(credit_card, options), credit_card) + end + + def verify_credentials + response = void('0') + response.message != 'Unauthorized Request. Bad or missing credentials.' + end + + def supports_scrubbing? + true + end + + def scrub(transcript) + transcript. + gsub(%r(().+()), '\1[FILTERED]\2'). + gsub(%r(().+()), '\1[FILTERED]\2'). + gsub(%r(().+())i, '\1[FILTERED]\2'). + gsub(%r(().+()), '\1[FILTERED]\2'). + gsub(%r((CARD NUMBER\s+: )#+\d+), '\1[FILTERED]') + end + + def supports_network_tokenization? + true + end + + private + + def build_request(action, body) + xml = Builder::XmlMarkup.new + + xml.instruct! + xml.tag! 'Transaction', xmlns: 'http://secure2.e-xact.com/vplug-in/transaction/rpc-enc/encodedTypes' do + add_credentials(xml) + add_transaction_type(xml, action) + xml << body + end + + xml.target! + end + + def build_sale_or_authorization_request(money, credit_card_or_store_authorization, options) + xml = Builder::XmlMarkup.new + + add_amount(xml, money, options) + + if credit_card_or_store_authorization.is_a? String + add_credit_card_token(xml, credit_card_or_store_authorization, options) + else + add_credit_card(xml, credit_card_or_store_authorization, options) + add_stored_credentials(xml, credit_card_or_store_authorization, options) + end + + add_address(xml, options) + add_customer_data(xml, options) + add_invoice(xml, options) + add_tax_fields(xml, options) + add_level_3(xml, options) + + xml.target! + end + + def build_capture_or_credit_request(money, identification, options) + xml = Builder::XmlMarkup.new + + add_identification(xml, identification) + add_amount(xml, money, options) + add_customer_data(xml, options) + add_card_authentication_data(xml, options) + + xml.target! + end + + def build_store_request(credit_card, options) + xml = Builder::XmlMarkup.new + + add_credit_card(xml, credit_card, options) + add_address(xml, options) + add_customer_data(xml, options) + + xml.target! + end + + def add_credentials(xml) + xml.tag! 'ExactID', @options[:login] + xml.tag! 'Password', @options[:password] + end + + def add_transaction_type(xml, action) + xml.tag! 'Transaction_Type', TRANSACTIONS[action] + end + + def add_identification(xml, identification) + authorization_num, transaction_tag, _ = identification.split(';') + + xml.tag! 'Authorization_Num', authorization_num + xml.tag! 'Transaction_Tag', transaction_tag + end + + def add_amount(xml, money, options) + currency_code = options[:currency] || default_currency + xml.tag! 'DollarAmount', localized_amount(money, currency_code) + xml.tag! 'Currency', currency_code + end + + def add_credit_card(xml, credit_card, options) + if credit_card.respond_to?(:track_data) && credit_card.track_data.present? + xml.tag! 'Track1', credit_card.track_data + xml.tag! 'Ecommerce_Flag', 'R' + else + xml.tag! 'Card_Number', credit_card.number + xml.tag! 'Expiry_Date', expdate(credit_card) + xml.tag! 'CardHoldersName', credit_card.name + xml.tag! 'CardType', card_type(credit_card.brand) + xml.tag! 'WalletProviderID', options[:wallet_provider_id] if options[:wallet_provider_id] + + add_credit_card_eci(xml, credit_card, options) + add_credit_card_verification_strings(xml, credit_card, options) + end + end + + def add_credit_card_eci(xml, credit_card, options) + eci = if credit_card.is_a?(NetworkTokenizationCreditCard) && credit_card.source == :apple_pay && card_brand(credit_card) == 'discover' + # Discover requires any Apple Pay transaction, regardless of in-app + # or web, and regardless of the ECI contained in the PKPaymentToken, + # to have an ECI value explicitly of 04. + '04' + else + (credit_card.respond_to?(:eci) ? credit_card.eci : nil) || options[:eci] || DEFAULT_ECI + end + + xml.tag! 'Ecommerce_Flag', eci.to_s =~ /^[0-9]+$/ ? eci.to_s.rjust(2, '0') : eci + end + + def add_credit_card_verification_strings(xml, credit_card, options) + if credit_card.is_a?(NetworkTokenizationCreditCard) + add_network_tokenization_credit_card(xml, credit_card) + else + if credit_card.verification_value? + xml.tag! 'CVD_Presence_Ind', '1' + xml.tag! 'CVDCode', credit_card.verification_value + end + + add_card_authentication_data(xml, options) + end + end + + def add_network_tokenization_credit_card(xml, credit_card) + case card_brand(credit_card).to_sym + when :american_express + cryptogram = Base64.decode64(credit_card.payment_cryptogram) + xml.tag!('XID', Base64.encode64(cryptogram[20...40])) + xml.tag!('CAVV', Base64.encode64(cryptogram[0...20])) + else + xml.tag!('XID', credit_card.transaction_id) if credit_card.transaction_id + xml.tag!('CAVV', credit_card.payment_cryptogram) + end + end + + def add_card_authentication_data(xml, options) + xml.tag! 'CAVV', options[:cavv] + xml.tag! 'XID', options[:xid] + end + + def add_credit_card_token(xml, store_authorization, options) + params = store_authorization.split(';') + credit_card = CreditCard.new( + :brand => params[1], + :first_name => params[2], + :last_name => params[3], + :month => params[4], + :year => params[5]) + + xml.tag! 'TransarmorToken', params[0] + xml.tag! 'Expiry_Date', expdate(credit_card) + xml.tag! 'CardHoldersName', credit_card.name + xml.tag! 'CardType', card_type(credit_card.brand) + xml.tag! 'WalletProviderID', options[:wallet_provider_id] if options[:wallet_provider_id] + add_card_authentication_data(xml, options) + end + + def add_customer_data(xml, options) + xml.tag! 'Customer_Ref', options[:customer] if options[:customer] + xml.tag! 'Client_IP', options[:ip] if options[:ip] + xml.tag! 'Client_Email', options[:email] if options[:email] + end + + def add_address(xml, options) + if (address = options[:billing_address] || options[:address]) + xml.tag! 'Address' do + xml.tag! 'Address1', address[:address1] + xml.tag! 'Address2', address[:address2] if address[:address2] + xml.tag! 'City', address[:city] + xml.tag! 'State', address[:state] + xml.tag! 'Zip', address[:zip] + xml.tag! 'CountryCode', address[:country] + end + xml.tag! 'ZipCode', address[:zip] + end + end + + def add_invoice(xml, options) + xml.tag! 'Reference_No', options[:order_id] + xml.tag! 'Reference_3', options[:description] if options[:description] + end + + def add_tax_fields(xml, options) + xml.tag! 'Tax1Amount', options[:tax1_amount] if options[:tax1_amount] + xml.tag! 'Tax1Number', options[:tax1_number] if options[:tax1_number] + end + + def add_level_3(xml, options) + xml.tag!('Level3') { |x| x << options[:level_3] } if options[:level_3] + end + + def add_stored_credentials(xml, card, options) + return unless options[:stored_credential] + xml.tag! 'StoredCredentials' do + xml.tag! 'Indicator', stored_credential_indicator(xml, card, options) + if initiator = options.dig(:stored_credential, :initiator) + xml.tag! initiator == 'merchant' ? 'M' : 'C' + end + if reason_type = options.dig(:stored_credential, :reason_type) + xml.tag! 'Schedule', reason_type == 'unscheduled' ? 'U' : 'S' + end + xml.tag! 'AuthorizationTypeOverride', options[:authorization_type_override] if options[:authorization_type_override] + if network_transaction_id = options[:stored_credential][:network_transaction_id] + xml.tag! 'TransactionId', network_transaction_id + else + xml.tag! 'TransactionId', 'new' + end + xml.tag! 'OriginalAmount', options[:original_amount] if options[:original_amount] + xml.tag! 'ProtectbuyIndicator', options[:protectbuy_indicator] if options[:protectbuy_indicator] + end + end + + def stored_credential_indicator(xml, card, options) + if card.brand == 'master' || options.dig(:stored_credential, :initial_transaction) == false + 'S' + else + '1' + end + end + + def expdate(credit_card) + "#{format(credit_card.month, :two_digits)}#{format(credit_card.year, :two_digits)}" + end + + def card_type(credit_card_brand) + BRANDS[credit_card_brand.to_sym] if credit_card_brand + end + + def commit(action, data, credit_card = nil) + url = (test? ? self.test_url : self.live_url) + request = build_request(action, data) + begin + response = parse(ssl_post(url, request, headers('POST', url, request))) + rescue ResponseError => e + response = parse_error(e.response) + end + + Response.new(successful?(response), message_from(response), response, + :test => test?, + :authorization => successful?(response) ? response_authorization(action, response, credit_card) : '', + :avs_result => {:code => response[:avs]}, + :cvv_result => response[:cvv2], + :error_code => standard_error_code(response) + ) + end + + def headers(method, url, request) + content_type = 'application/xml' + content_digest = Digest::SHA1.hexdigest(request) + sending_time = Time.now.utc.iso8601 + payload = [method, content_type, content_digest, sending_time, url.split('.com')[1]].join("\n") + hmac = OpenSSL::HMAC.digest('sha1', @options[:hmac_key], payload) + encoded = Base64.strict_encode64(hmac) + + { + 'x-gge4-date' => sending_time, + 'x-gge4-content-sha1' => content_digest, + 'Authorization' => 'GGE4_API ' + @options[:key_id].to_s + ':' + encoded, + 'Accepts' => content_type, + 'Content-Type' => content_type + } + end + + def successful?(response) + response[:transaction_approved] == SUCCESS + end + + def response_authorization(action, response, credit_card) + if action == :store + store_authorization_from(response, credit_card) + else + authorization_from(response) + end + end + + def authorization_from(response) + if response[:authorization_num] && response[:transaction_tag] + [ + response[:authorization_num], + response[:transaction_tag], + (response[:dollar_amount].to_f * 100).round + ].join(';') + else + '' + end + end + + def store_authorization_from(response, credit_card) + if response[:transarmor_token].present? + [ + response[:transarmor_token], + credit_card.brand, + credit_card.first_name, + credit_card.last_name, + credit_card.month, + credit_card.year + ].map { |value| value.to_s.tr(';', '') }.join(';') + else + raise StandardError, "TransArmor support is not enabled on your #{display_name} account" + end + end + + def money_from_authorization(auth) + _, _, amount = auth.split(/;/, 3) + amount.to_i + end + + def message_from(response) + if response[:faultcode] && response[:faultstring] + response[:faultstring] + elsif response[:error_number] && response[:error_number] != '0' + response[:error_description] + else + result = (response[:exact_message] || '') + result << " - #{response[:bank_message]}" if response[:bank_message].present? + result + end + end + + def parse_error(error) + { + :transaction_approved => 'false', + :error_number => error.code, + :error_description => error.body, + :ecommerce_error_code => error.body.gsub(/[^\d]/, '') + } + end + + def standard_error_code(response) + STANDARD_ERROR_CODE_MAPPING[response[:bank_resp_code] || response[:ecommerce_error_code]] + end + + def parse(xml) + response = {} + xml = REXML::Document.new(xml) + + if (root = REXML::XPath.first(xml, '//TransactionResult')) + parse_elements(response, root) + end + + SENSITIVE_FIELDS.each { |key| response.delete(key) } + response + end + + def parse_elements(response, root) + root.elements.to_a.each do |node| + if node.has_elements? + parse_elements(response, node) + else + response[name_node(root, node)] = (node.text || '').strip + end + end + end + + def name_node(root, node) + parent = root.name unless root.name == 'TransactionResult' + "#{parent}#{node.name}".gsub(/EXact/, 'Exact').underscore.to_sym + end + end + end +end diff --git a/lib/active_merchant/billing/gateways/flo2cash.rb b/lib/active_merchant/billing/gateways/flo2cash.rb new file mode 100644 index 00000000000..1f5c9d8076b --- /dev/null +++ b/lib/active_merchant/billing/gateways/flo2cash.rb @@ -0,0 +1,215 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class Flo2cashGateway < Gateway + self.display_name = 'Flo2Cash' + self.homepage_url = 'http://www.flo2cash.co.nz/' + + self.test_url = 'https://demo.flo2cash.co.nz/ws/paymentws.asmx' + self.live_url = 'https://secure.flo2cash.co.nz/ws/paymentws.asmx' + + self.supported_countries = ['NZ'] + self.default_currency = 'NZD' + self.money_format = :dollars + self.supported_cardtypes = [:visa, :master, :american_express, :diners_club] + + BRAND_MAP = { + 'visa' => 'VISA', + 'master' => 'MC', + 'american_express' => 'AMEX', + 'diners_club' => 'DINERS' + } + + def initialize(options={}) + requires!(options, :username, :password, :account_id) + super + end + + def purchase(amount, payment_method, options={}) + MultiResponse.run do |r| + r.process { authorize(amount, payment_method, options) } + r.process { capture(amount, r.authorization, options) } + end + end + + def authorize(amount, payment_method, options={}) + post = {} + add_invoice(post, amount, options) + add_payment_method(post, payment_method) + add_customer_data(post, options) + + commit('ProcessAuthorise', post) + end + + def capture(amount, authorization, options={}) + post = {} + add_invoice(post, amount, options) + add_reference(post, authorization) + add_customer_data(post, options) + + commit('ProcessCapture', post) + end + + def refund(amount, authorization, options={}) + post = {} + add_invoice(post, amount, options) + add_reference(post, authorization) + add_customer_data(post, options) + + commit('ProcessRefund', post) + end + + def supports_scrubbing? + true + end + + def scrub(transcript) + transcript. + gsub(%r(()[^<]+(<))i, '\1[FILTERED]\2'). + gsub(%r(()[^<]+(<))i, '\1[FILTERED]\2'). + gsub(%r(()[^<]+(<))i, '\1[FILTERED]\2') + end + + private + + CURRENCY_CODES = Hash.new { |h, k| raise ArgumentError.new("Unsupported currency: #{k}") } + CURRENCY_CODES['NZD'] = '554' + + def add_invoice(post, money, options) + post[:Amount] = amount(money) + post[:Reference] = options[:order_id] + post[:Particular] = options[:description] + end + + def add_payment_method(post, payment_method) + post[:CardNumber] = payment_method.number + post[:CardType] = BRAND_MAP[payment_method.brand.to_s] + post[:CardExpiry] = format(payment_method.month, :two_digits) + format(payment_method.year, :two_digits) + post[:CardHolderName] = payment_method.name + post[:CardCSC] = payment_method.verification_value + end + + def add_customer_data(post, options) + if(billing_address = (options[:billing_address] || options[:address])) + post[:Email] = billing_address[:email] + end + end + + def add_reference(post, authorization) + post[:OriginalTransactionId] = authorization + end + + def commit(action, post) + post[:Username] = @options[:username] + post[:Password] = @options[:password] + post[:AccountId] = @options[:account_id] + + data = build_request(action, post) + begin + raw = parse(ssl_post(url, data, headers(action)), action) + rescue ActiveMerchant::ResponseError => e + if(e.response.code == '500' && e.response.body.start_with?(' authorization_from(action, raw[:transaction_id], post[:OriginalTransactionId]), + :error_code => error_code_from(succeeded, raw), + :test => test? + ) + end + + def headers(action) + { + 'Content-Type' => 'application/soap+xml; charset=utf-8', + 'SOAPAction' => %{"http://www.flo2cash.co.nz/webservices/paymentwebservice/#{action}"} + } + end + + def build_request(action, post) + xml = Builder::XmlMarkup.new :indent => 2 + post.each do |field, value| + xml.tag!(field, value) + end + body = xml.target! + envelope_wrap(action, body) + end + + def envelope_wrap(action, body) + <<-EOS + + + + <#{action} xmlns="http://www.flo2cash.co.nz/webservices/paymentwebservice"> + #{body} + + + + EOS + end + + def url + (test? ? test_url : live_url) + end + + def parse(body, action) + response = {} + xml = REXML::Document.new(body) + root = (REXML::XPath.first(xml, "//#{action}Response") || REXML::XPath.first(xml, '//detail')) + + root.elements.to_a.each do |node| + parse_element(response, node) + end if root + + response + end + + def parse_element(response, node) + if node.has_elements? + node.elements.each { |element| parse_element(response, element) } + else + response[node.name.underscore.to_sym] = node.text + end + end + + def success_from(response) + response == 'SUCCESSFUL' + end + + def message_from(succeeded, response) + if succeeded + 'Succeeded' + else + response[:message] || response[:errormessage] || 'Unable to read error message' + end + end + + def authorization_from(action, current, original) + # Refunds require the authorization from the authorize() of the MultiResponse. + if action == 'ProcessCapture' + original + else + current + end + end + + STANDARD_ERROR_CODE_MAPPING = { + 'Transaction Declined - Expired Card' => STANDARD_ERROR_CODE[:expired_card], + 'Bank Declined Transaction' => STANDARD_ERROR_CODE[:card_declined], + 'Insufficient Funds' => STANDARD_ERROR_CODE[:card_declined], + 'Transaction Declined - Bank Error' => STANDARD_ERROR_CODE[:processing_error], + 'No Reply from Bank' => STANDARD_ERROR_CODE[:processing_error], + } + + def error_code_from(succeeded, response) + succeeded ? nil : STANDARD_ERROR_CODE_MAPPING[response[:message]] + end + end + end +end diff --git a/lib/active_merchant/billing/gateways/flo2cash_simple.rb b/lib/active_merchant/billing/gateways/flo2cash_simple.rb new file mode 100644 index 00000000000..f0662ff463c --- /dev/null +++ b/lib/active_merchant/billing/gateways/flo2cash_simple.rb @@ -0,0 +1,20 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class Flo2cashSimpleGateway < Flo2cashGateway + self.display_name = 'Flo2Cash Simple' + + def purchase(amount, payment_method, options={}) + post = {} + add_invoice(post, amount, options) + add_payment_method(post, payment_method) + add_customer_data(post, options) + + commit('ProcessPurchase', post) + end + + # Flo2Cash's "simple mode" does not support auth/capture + undef_method :authorize + undef_method :capture + end + end +end diff --git a/lib/active_merchant/billing/gateways/forte.rb b/lib/active_merchant/billing/gateways/forte.rb new file mode 100644 index 00000000000..bdc06c7a3f5 --- /dev/null +++ b/lib/active_merchant/billing/gateways/forte.rb @@ -0,0 +1,270 @@ +require 'json' + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class ForteGateway < Gateway + include Empty + + self.test_url = 'https://sandbox.forte.net/api/v2' + self.live_url = 'https://api.forte.net/v2' + + self.supported_countries = ['US'] + self.default_currency = 'USD' + self.supported_cardtypes = [:visa, :master, :american_express, :discover] + + self.homepage_url = 'https://www.forte.net' + self.display_name = 'Forte' + + def initialize(options={}) + requires!(options, :api_key, :secret, :location_id, :account_id) + super + end + + def purchase(money, payment_method, options={}) + post = {} + add_amount(post, money, options) + add_invoice(post, options) + add_payment_method(post, payment_method) + add_billing_address(post, payment_method, options) + add_shipping_address(post, options) + post[:action] = 'sale' + + commit(:post, post) + end + + def authorize(money, payment_method, options={}) + post = {} + add_amount(post, money, options) + add_invoice(post, options) + add_payment_method(post, payment_method) + add_billing_address(post, payment_method, options) + add_shipping_address(post, options) + post[:action] = 'authorize' + + commit(:post, post) + end + + def capture(money, authorization, options={}) + post = {} + post[:transaction_id] = transaction_id_from(authorization) + post[:authorization_code] = authorization_code_from(authorization) || '' + post[:action] = 'capture' + + commit(:put, post) + end + + def credit(money, payment_method, options={}) + post = {} + add_amount(post, money, options) + add_invoice(post, options) + add_payment_method(post, payment_method) + add_billing_address(post, payment_method, options) + post[:action] = 'disburse' + + commit(:post, post) + end + + def refund(money, authorization, options={}) + post = {} + add_amount(post, money, options) + post[:original_transaction_id] = transaction_id_from(authorization) + post[:authorization_code] = authorization_code_from(authorization) + post[:action] = 'reverse' + + commit(:post, post) + end + + def void(authorization, options={}) + post = {} + post[:transaction_id] = transaction_id_from(authorization) + post[:authorization_code] = authorization_code_from(authorization) + post[:action] = 'void' + + commit(:put, post) + end + + def verify(credit_card, options={}) + MultiResponse.run(:use_first_response) do |r| + r.process { authorize(100, credit_card, options) } + r.process(:ignore_result) { void(r.authorization, options) } + end + end + + def supports_scrubbing? + true + end + + def scrub(transcript) + transcript. + gsub(%r((Authorization: Basic )\w+), '\1[FILTERED]'). + gsub(%r((account_number)\W+\d+), '\1[FILTERED]'). + gsub(%r((card_verification_value)\W+\d+), '\1[FILTERED]') + end + + private + + def add_auth(post) + post[:account_id] = "act_#{@options[:account_id]}" + post[:location_id] = "loc_#{@options[:location_id]}" + end + + def add_invoice(post, options) + post[:order_number] = options[:order_id] + end + + def add_amount(post, money, options) + post[:authorization_amount] = amount(money) + end + + def add_billing_address(post, payment, options) + post[:billing_address] = {} + if address = options[:billing_address] || options[:address] + first_name, last_name = split_names(address[:name]) + post[:billing_address][:first_name] = first_name if first_name + post[:billing_address][:last_name] = last_name if last_name + post[:billing_address][:physical_address] = {} + post[:billing_address][:physical_address][:street_line1] = address[:address1] if address[:address1] + post[:billing_address][:physical_address][:street_line2] = address[:address2] if address[:address2] + post[:billing_address][:physical_address][:postal_code] = address[:zip] if address[:zip] + post[:billing_address][:physical_address][:region] = address[:state] if address[:state] + post[:billing_address][:physical_address][:locality] = address[:city] if address[:city] + end + + if empty?(post[:billing_address][:first_name]) && payment.first_name + post[:billing_address][:first_name] = payment.first_name + end + + if empty?(post[:billing_address][:last_name]) && payment.last_name + post[:billing_address][:last_name] = payment.last_name + end + end + + def add_shipping_address(post, options) + return unless options[:shipping_address] + address = options[:shipping_address] + + post[:shipping_address] = {} + first_name, last_name = split_names(address[:name]) + post[:shipping_address][:first_name] = first_name if first_name + post[:shipping_address][:last_name] = last_name if last_name + post[:shipping_address][:physical_address][:street_line1] = address[:address1] if address[:address1] + post[:shipping_address][:physical_address][:street_line2] = address[:address2] if address[:address2] + post[:shipping_address][:physical_address][:postal_code] = address[:zip] if address[:zip] + post[:shipping_address][:physical_address][:region] = address[:state] if address[:state] + post[:shipping_address][:physical_address][:locality] = address[:city] if address[:city] + end + + def add_payment_method(post, payment_method) + if payment_method.respond_to?(:brand) + add_credit_card(post, payment_method) + else + add_echeck(post, payment_method) + end + end + + def add_echeck(post, payment) + post[:echeck] = {} + post[:echeck][:account_holder] = payment.name + post[:echeck][:account_number] = payment.account_number + post[:echeck][:routing_number] = payment.routing_number + post[:echeck][:account_type] = payment.account_type + post[:echeck][:check_number] = payment.number + end + + def add_credit_card(post, payment) + post[:card] = {} + post[:card][:card_type] = format_card_brand(payment.brand) + post[:card][:name_on_card] = payment.name + post[:card][:account_number] = payment.number + post[:card][:expire_month] = payment.month + post[:card][:expire_year] = payment.year + post[:card][:card_verification_value] = payment.verification_value + end + + def commit(type, parameters) + add_auth(parameters) + + url = (test? ? test_url : live_url) + response = parse(handle_resp(raw_ssl_request(type, url + endpoint, parameters.to_json, headers))) + + Response.new( + success_from(response), + message_from(response), + response, + authorization: authorization_from(response, parameters), + avs_result: AVSResult.new(code: response['response']['avs_result']), + cvv_result: CVVResult.new(response['response']['cvv_code']), + test: test? + ) + end + + def handle_resp(response) + case response.code.to_i + when 200..499 + response.body + else + raise ResponseError.new(response) + end + end + + def parse(response_body) + JSON.parse(response_body) + end + + def success_from(response) + response['response']['response_code'] == 'A01' + end + + def message_from(response) + response['response']['response_desc'] + end + + def authorization_from(response, parameters) + if parameters[:action] == 'capture' + [response['transaction_id'], response.dig('response', 'authorization_code'), parameters[:transaction_id], parameters[:authorization_code]].join('#') + else + [response['transaction_id'], response.dig('response', 'authorization_code')].join('#') + end + end + + def endpoint + "/accounts/act_#{@options[:account_id].strip}/locations/loc_#{@options[:location_id].strip}/transactions/" + end + + def headers + { + 'Authorization' => ('Basic ' + Base64.strict_encode64("#{@options[:api_key]}:#{@options[:secret]}")), + 'X-Forte-Auth-Account-Id' => "act_#{@options[:account_id]}", + 'Content-Type' => 'application/json' + } + end + + def format_card_brand(card_brand) + case card_brand + when 'visa' + return 'visa' + when 'master' + return 'mast' + when 'american_express' + return 'amex' + when 'discover' + return 'disc' + end + end + + def split_authorization(authorization) + authorization.split('#') + end + + def authorization_code_from(authorization) + _, authorization_code, _, original_auth_authorization_code = split_authorization(authorization) + original_auth_authorization_code.present? ? original_auth_authorization_code : authorization_code + end + + def transaction_id_from(authorization) + transaction_id, _, original_auth_transaction_id, _= split_authorization(authorization) + original_auth_transaction_id.present? ? original_auth_transaction_id : transaction_id + end + end + end +end diff --git a/lib/active_merchant/billing/gateways/garanti.rb b/lib/active_merchant/billing/gateways/garanti.rb index c6de668a19c..3c7f19efc5d 100644 --- a/lib/active_merchant/billing/gateways/garanti.rb +++ b/lib/active_merchant/billing/gateways/garanti.rb @@ -1,10 +1,11 @@ module ActiveMerchant #:nodoc: module Billing #:nodoc: class GarantiGateway < Gateway - self.live_url = self.test_url = 'https://sanalposprov.garanti.com.tr/VPServlet' + self.live_url = 'https://sanalposprov.garanti.com.tr/VPServlet' + self.test_url = 'https://sanalposprovtest.garanti.com.tr/VPServlet' # The countries the gateway supports merchants from as 2 digit ISO country codes - self.supported_countries = ['US','TR'] + self.supported_countries = ['US', 'TR'] # The card types supported by the payment gateway self.supported_cardtypes = [:visa, :master, :american_express, :discover] @@ -29,31 +30,30 @@ class GarantiGateway < Gateway 'JPY' => 392 } - def initialize(options = {}) requires!(options, :login, :password, :terminal_id, :merchant_id) super end def purchase(money, credit_card, options = {}) - options = options.merge(:gvp_order_type => "sales") + options = options.merge(:gvp_order_type => 'sales') commit(money, build_sale_request(money, credit_card, options)) end def authorize(money, credit_card, options = {}) - options = options.merge(:gvp_order_type => "preauth") + options = options.merge(:gvp_order_type => 'preauth') commit(money, build_authorize_request(money, credit_card, options)) end def capture(money, ref_id, options = {}) - options = options.merge(:gvp_order_type => "postauth") + options = options.merge(:gvp_order_type => 'postauth') commit(money, build_capture_request(money, ref_id, options)) end private def security_data - rjusted_terminal_id = @options[:terminal_id].to_s.rjust(9, "0") + rjusted_terminal_id = @options[:terminal_id].to_s.rjust(9, '0') Digest::SHA1.hexdigest(@options[:password].to_s + rjusted_terminal_id).upcase end @@ -67,7 +67,7 @@ def build_xml_request(money, credit_card, options, &block) hash_data = generate_hash_data(format_order_id(options[:order_id]), @options[:terminal_id], card_number, amount(money), security_data) xml = Builder::XmlMarkup.new(:indent => 2) - xml.instruct! :xml, :version => "1.0", :encoding => "UTF-8" + xml.instruct! :xml, :version => '1.0', :encoding => 'UTF-8' xml.tag! 'GVPSRequest' do xml.tag! 'Mode', test? ? 'TEST' : 'PROD' @@ -91,7 +91,7 @@ def build_xml_request(money, credit_card, options, &block) def build_sale_request(money, credit_card, options) build_xml_request(money, credit_card, options) do |xml| add_customer_data(xml, options) - add_order_data(xml, options) do |xml| + add_order_data(xml, options) do add_addresses(xml, options) end add_credit_card(xml, credit_card) @@ -102,9 +102,9 @@ def build_sale_request(money, credit_card, options) end def build_authorize_request(money, credit_card, options) - build_xml_request(money, credit_card, options) do |xml| + build_xml_request(money, credit_card, options) do |xml| add_customer_data(xml, options) - add_order_data(xml, options) do |xml| + add_order_data(xml, options) do add_addresses(xml, options) end add_credit_card(xml, credit_card) @@ -116,7 +116,7 @@ def build_authorize_request(money, credit_card, options) def build_capture_request(money, ref_id, options) options = options.merge(:order_id => ref_id) - build_xml_request(money, ref_id, options) do |xml| + build_xml_request(money, ref_id, options) do |xml| add_customer_data(xml, options) add_order_data(xml, options) add_transaction_data(xml, money, options) @@ -181,7 +181,7 @@ def add_addresses(xml, options) def add_address(xml, address) xml.tag! 'Name', normalize(address[:name]) address_text = address[:address1] - address_text << " #{ address[:address2]}" if address[:address2] + address_text << " #{address[:address2]}" if address[:address2] xml.tag! 'Text', normalize(address_text) xml.tag! 'City', normalize(address[:city]) xml.tag! 'District', normalize(address[:state]) @@ -195,11 +195,9 @@ def normalize(text) return unless text if ActiveSupport::Inflector.method(:transliterate).arity == -2 - ActiveSupport::Inflector.transliterate(text,'') - elsif RUBY_VERSION >= '1.9' - text.gsub(/[^\x00-\x7F]+/, '') + ActiveSupport::Inflector.transliterate(text, '') else - ActiveSupport::Inflector.transliterate(text).to_s + text.gsub(/[^\x00-\x7F]+/, '') end end @@ -216,21 +214,22 @@ def currency_code(currency) CURRENCY_CODES[currency] || CURRENCY_CODES[default_currency] end - def commit(money,request) - raw_response = ssl_post(self.live_url, "data=" + request) + def commit(money, request) + url = test? ? self.test_url : self.live_url + raw_response = ssl_post(url, 'data=' + request) response = parse(raw_response) success = success?(response) Response.new(success, - success ? 'Approved' : "Declined (Reason: #{response[:reason_code]} - #{response[:error_msg]} - #{response[:sys_err_msg]})", - response, - :test => test?, - :authorization => response[:order_id]) + success ? 'Approved' : "Declined (Reason: #{response[:reason_code]} - #{response[:error_msg]} - #{response[:sys_err_msg]})", + response, + :test => test?, + :authorization => response[:order_id]) end def parse(body) - xml = REXML::Document.new(body) + xml = REXML::Document.new(strip_invalid_xml_chars(body)) response = {} xml.root.elements.to_a.each do |node| @@ -241,17 +240,20 @@ def parse(body) def parse_element(response, node) if node.has_elements? - node.elements.each{|element| parse_element(response, element) } + node.elements.each { |element| parse_element(response, element) } else response[node.name.underscore.to_sym] = node.text end end def success?(response) - response[:message] == "Approved" + response[:message] == 'Approved' + end + + def strip_invalid_xml_chars(xml) + xml.gsub(/&(?!(?:[a-z]+|#[0-9]+|x[a-zA-Z0-9]+);)/, '&') end end end end - diff --git a/lib/active_merchant/billing/gateways/global_collect.rb b/lib/active_merchant/billing/gateways/global_collect.rb new file mode 100644 index 00000000000..879ff3acc6f --- /dev/null +++ b/lib/active_merchant/billing/gateways/global_collect.rb @@ -0,0 +1,336 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class GlobalCollectGateway < Gateway + self.display_name = 'GlobalCollect' + self.homepage_url = 'http://www.globalcollect.com/' + + self.test_url = 'https://eu.sandbox.api-ingenico.com' + self.live_url = 'https://api.globalcollect.com' + + self.supported_countries = ['AD', 'AE', 'AG', 'AI', 'AL', 'AM', 'AO', 'AR', 'AS', 'AT', 'AU', 'AW', 'AX', 'AZ', 'BA', 'BB', 'BD', 'BE', 'BF', 'BG', 'BH', 'BI', 'BJ', 'BL', 'BM', 'BN', 'BO', 'BQ', 'BR', 'BS', 'BT', 'BW', 'BY', 'BZ', 'CA', 'CC', 'CD', 'CF', 'CH', 'CI', 'CK', 'CL', 'CM', 'CN', 'CO', 'CR', 'CU', 'CV', 'CW', 'CX', 'CY', 'CZ', 'DE', 'DJ', 'DK', 'DM', 'DO', 'DZ', 'EC', 'EE', 'EG', 'ER', 'ES', 'ET', 'FI', 'FJ', 'FK', 'FM', 'FO', 'FR', 'GA', 'GB', 'GD', 'GE', 'GF', 'GH', 'GI', 'GL', 'GM', 'GN', 'GP', 'GQ', 'GR', 'GS', 'GT', 'GU', 'GW', 'GY', 'HK', 'HN', 'HR', 'HT', 'HU', 'ID', 'IE', 'IL', 'IM', 'IN', 'IS', 'IT', 'JM', 'JO', 'JP', 'KE', 'KG', 'KH', 'KI', 'KM', 'KN', 'KR', 'KW', 'KY', 'KZ', 'LA', 'LB', 'LC', 'LI', 'LK', 'LR', 'LS', 'LT', 'LU', 'LV', 'MA', 'MC', 'MD', 'ME', 'MF', 'MG', 'MH', 'MK', 'MM', 'MN', 'MO', 'MP', 'MQ', 'MR', 'MS', 'MT', 'MU', 'MV', 'MW', 'MX', 'MY', 'MZ', 'NA', 'NC', 'NE', 'NG', 'NI', 'NL', 'NO', 'NP', 'NR', 'NU', 'NZ', 'OM', 'PA', 'PE', 'PF', 'PG', 'PH', 'PL', 'PN', 'PS', 'PT', 'PW', 'QA', 'RE', 'RO', 'RS', 'RU', 'RW', 'SA', 'SB', 'SC', 'SE', 'SG', 'SH', 'SI', 'SJ', 'SK', 'SL', 'SM', 'SN', 'SR', 'ST', 'SV', 'SZ', 'TC', 'TD', 'TG', 'TH', 'TJ', 'TL', 'TM', 'TN', 'TO', 'TR', 'TT', 'TV', 'TW', 'TZ', 'UA', 'UG', 'US', 'UY', 'UZ', 'VC', 'VE', 'VG', 'VI', 'VN', 'WF', 'WS', 'ZA', 'ZM', 'ZW'] + self.default_currency = 'USD' + self.money_format = :cents + self.supported_cardtypes = [:visa, :master, :american_express, :discover, :naranja, :cabal] + + def initialize(options={}) + requires!(options, :merchant_id, :api_key_id, :secret_api_key) + super + end + + def purchase(money, payment, options={}) + MultiResponse.run do |r| + r.process { authorize(money, payment, options) } + r.process { capture(money, r.authorization, options) } unless capture_requested?(r) + end + end + + def authorize(money, payment, options={}) + post = nestable_hash + add_order(post, money, options) + add_payment(post, payment, options) + add_customer_data(post, options, payment) + add_address(post, payment, options) + add_creator_info(post, options) + add_fraud_fields(post, options) + + commit(:authorize, post) + end + + def capture(money, authorization, options={}) + post = nestable_hash + add_order(post, money, options, capture: true) + add_customer_data(post, options) + add_creator_info(post, options) + commit(:capture, post, authorization) + end + + def refund(money, authorization, options={}) + post = nestable_hash + add_amount(post, money, options) + add_refund_customer_data(post, options) + add_creator_info(post, options) + commit(:refund, post, authorization) + end + + def void(authorization, options={}) + post = nestable_hash + add_creator_info(post, options) + commit(:void, post, authorization) + end + + def verify(payment, options={}) + MultiResponse.run(:use_first_response) do |r| + r.process { authorize(100, payment, options) } + r.process { void(r.authorization, options) } + end + end + + def supports_scrubbing? + true + end + + def scrub(transcript) + transcript. + gsub(%r((Authorization: )[^\\]*)i, '\1[FILTERED]'). + gsub(%r(("cardNumber\\+":\\+")\d+), '\1[FILTERED]'). + gsub(%r(("cvv\\+":\\+")\d+), '\1[FILTERED]') + end + + private + + BRAND_MAP = { + 'visa' => '1', + 'american_express' => '2', + 'master' => '3', + 'discover' => '128', + 'jcb' => '125', + 'diners_club' => '132' + } + + def add_order(post, money, options, capture: false) + if capture + post['amount'] = amount(money) + else + add_amount(post['order'], money, options) + end + post['order']['references'] = { + 'merchantReference' => options[:order_id], + 'descriptor' => options[:description] # Max 256 chars + } + post['order']['references']['invoiceData'] = { + 'invoiceNumber' => options[:invoice] + } + end + + def add_creator_info(post, options) + post['sdkIdentifier'] = options[:sdk_identifier] if options[:sdk_identifier] + post['sdkCreator'] = options[:sdk_creator] if options[:sdk_creator] + post['integrator'] = options[:integrator] if options[:integrator] + post['shoppingCartExtension'] = {} + post['shoppingCartExtension']['creator'] = options[:creator] if options[:creator] + post['shoppingCartExtension']['name'] = options[:name] if options[:name] + post['shoppingCartExtension']['version'] = options[:version] if options[:version] + post['shoppingCartExtension']['extensionID'] = options[:extension_ID] if options[:extension_ID] + end + + def add_amount(post, money, options={}) + post['amountOfMoney'] = { + 'amount' => amount(money), + 'currencyCode' => options[:currency] || currency(money) + } + end + + def add_payment(post, payment, options) + year = format(payment.year, :two_digits) + month = format(payment.month, :two_digits) + expirydate = "#{month}#{year}" + pre_authorization = options[:pre_authorization] ? 'PRE_AUTHORIZATION' : 'FINAL_AUTHORIZATION' + + post['cardPaymentMethodSpecificInput'] = { + 'paymentProductId' => BRAND_MAP[payment.brand], + 'skipAuthentication' => 'true', # refers to 3DSecure + 'skipFraudService' => 'true', + 'authorizationMode' => pre_authorization + } + post['cardPaymentMethodSpecificInput']['card'] = { + 'cvv' => payment.verification_value, + 'cardNumber' => payment.number, + 'expiryDate' => expirydate, + 'cardholderName' => payment.name + } + end + + def add_customer_data(post, options, payment = nil) + if payment + post['order']['customer']['personalInformation']['name']['firstName'] = payment.first_name[0..14] if payment.first_name + post['order']['customer']['personalInformation']['name']['surname'] = payment.last_name[0..69] if payment.last_name + end + post['order']['customer']['merchantCustomerId'] = options[:customer] if options[:customer] + post['order']['customer']['companyInformation']['name'] = options[:company] if options[:company] + post['order']['customer']['contactDetails']['emailAddress'] = options[:email] if options[:email] + if address = options[:billing_address] || options[:address] + post['order']['customer']['contactDetails']['phoneNumber'] = address[:phone] if address[:phone] + end + end + + def add_refund_customer_data(post, options) + if address = options[:billing_address] || options[:address] + post['customer']['address'] = { + 'countryCode' => address[:country] + } + post['customer']['contactDetails']['emailAddress'] = options[:email] if options[:email] + if address = options[:billing_address] || options[:address] + post['customer']['contactDetails']['phoneNumber'] = address[:phone] if address[:phone] + end + end + end + + def add_address(post, creditcard, options) + shipping_address = options[:shipping_address] + if billing_address = options[:billing_address] || options[:address] + post['order']['customer']['billingAddress'] = { + 'street' => billing_address[:address1], + 'additionalInfo' => billing_address[:address2], + 'zip' => billing_address[:zip], + 'city' => billing_address[:city], + 'state' => billing_address[:state], + 'countryCode' => billing_address[:country] + } + end + if shipping_address + post['order']['customer']['shippingAddress'] = { + 'street' => shipping_address[:address1], + 'additionalInfo' => shipping_address[:address2], + 'zip' => shipping_address[:zip], + 'city' => shipping_address[:city], + 'state' => shipping_address[:state], + 'countryCode' => shipping_address[:country] + } + post['order']['customer']['shippingAddress']['name'] = { + 'firstName' => shipping_address[:firstname], + 'surname' => shipping_address[:lastname] + } + end + end + + def add_fraud_fields(post, options) + fraud_fields = {} + fraud_fields.merge!(options[:fraud_fields]) if options[:fraud_fields] + fraud_fields[:customerIpAddress] = options[:ip] if options[:ip] + + post['fraudFields'] = fraud_fields unless fraud_fields.empty? + end + + def parse(body) + JSON.parse(body) + end + + def url(action, authorization) + (test? ? test_url : live_url) + uri(action, authorization) + end + + def uri(action, authorization) + uri = "/v1/#{@options[:merchant_id]}/" + case action + when :authorize + uri + 'payments' + when :capture + uri + "payments/#{authorization}/approve" + when :refund + uri + "payments/#{authorization}/refund" + when :void + uri + "payments/#{authorization}/cancel" + end + end + + def commit(action, post, authorization = nil) + begin + raw_response = ssl_post(url(action, authorization), post.to_json, headers(action, post, authorization)) + response = parse(raw_response) + rescue ResponseError => e + if e.response.code.to_i >= 400 + response = parse(e.response.body) + end + rescue JSON::ParserError + response = json_error(raw_response) + end + + succeeded = success_from(response) + Response.new( + succeeded, + message_from(succeeded, response), + response, + authorization: authorization_from(succeeded, response), + error_code: error_code_from(succeeded, response), + test: test? + ) + end + + def json_error(raw_response) + { + 'error_message' => 'Invalid response received from the Ingenico ePayments (formerly GlobalCollect) API. Please contact Ingenico ePayments if you continue to receive this message.' \ + " (The raw response returned by the API was #{raw_response.inspect})", + 'status' => 'REJECTED' + } + end + + def headers(action, post, authorization = nil) + { + 'Content-Type' => content_type, + 'Authorization' => auth_digest(action, post, authorization), + 'Date' => date + } + end + + def auth_digest(action, post, authorization = nil) + data = <<-EOS +POST +#{content_type} +#{date} +#{uri(action, authorization)} +EOS + digest = OpenSSL::Digest.new('sha256') + key = @options[:secret_api_key] + "GCS v1HMAC:#{@options[:api_key_id]}:#{Base64.strict_encode64(OpenSSL::HMAC.digest(digest, key, data))}" + end + + def date + @date ||= Time.now.strftime('%a, %d %b %Y %H:%M:%S %Z') # Must be same in digest and HTTP header + end + + def content_type + 'application/json' + end + + def success_from(response) + !response['errorId'] && response['status'] != 'REJECTED' + end + + def message_from(succeeded, response) + if succeeded + 'Succeeded' + else + if errors = response['errors'] + errors.first.try(:[], 'message') + elsif response['error_message'] + response['error_message'] + elsif response['status'] + 'Status: ' + response['status'] + else + 'No message available' + end + end + end + + def authorization_from(succeeded, response) + if succeeded + response['id'] || response['payment']['id'] || response['paymentResult']['payment']['id'] + elsif response['errorId'] + response['errorId'] + else + 'GATEWAY ERROR' + end + end + + def error_code_from(succeeded, response) + unless succeeded + if errors = response['errors'] + errors.first.try(:[], 'code') + elsif status = response.try(:[], 'statusOutput').try(:[], 'statusCode') + status.to_s + else + 'No error code available' + end + end + end + + def nestable_hash + Hash.new { |h, k| h[k] = Hash.new(&h.default_proc) } + end + + def capture_requested?(response) + response.params.try(:[], 'payment').try(:[], 'status') == 'CAPTURE_REQUESTED' + end + end + end +end diff --git a/lib/active_merchant/billing/gateways/global_transport.rb b/lib/active_merchant/billing/gateways/global_transport.rb new file mode 100644 index 00000000000..94087b23e17 --- /dev/null +++ b/lib/active_merchant/billing/gateways/global_transport.rb @@ -0,0 +1,194 @@ +require 'nokogiri' + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class GlobalTransportGateway < Gateway + self.test_url = 'https://certapia.globalpay.com/GlobalPay/transact.asmx/ProcessCreditCard' + self.live_url = 'https://api.globalpay.com/GlobalPay/transact.asmx/ProcessCreditCard' + + self.supported_countries = %w(CA PR US) + self.default_currency = 'USD' + self.supported_cardtypes = [:visa, :master, :american_express, :discover, :diners_club, :jcb] + + self.homepage_url = 'https://www.globalpaymentsinc.com' + self.display_name = 'Global Transport' + + # Public: Create a new Global Transport gateway. + # + # options - A hash of options: + # :global_user_name - Your Global user name + # :global_password - Your Global password + # :term_type - 3 character field assigned by Global Transport after + # - your application is certified. + def initialize(options={}) + requires!(options, :global_user_name, :global_password, :term_type) + super + end + + def purchase(money, payment_method, options={}) + post = {} + add_invoice(post, money, options) + add_payment_method(post, payment_method) + add_address(post, options) + + commit('Sale', post, options) + end + + def authorize(money, payment_method, options={}) + post = {} + add_invoice(post, money, options) + add_payment_method(post, payment_method) + add_address(post, options) + + commit('Auth', post, options) + end + + def capture(money, authorization, options={}) + post = {} + add_invoice(post, money, options) + add_auth(post, authorization) + + commit('Force', post, options) + end + + def refund(money, authorization, options={}) + post = {} + add_invoice(post, money, options) + add_auth(post, authorization) + + commit('Return', post, options) + end + + def void(authorization, options={}) + post = {} + add_auth(post, authorization) + + commit('Void', post, options) + end + + def verify(payment_method, options={}) + post = {} + add_payment_method(post, payment_method) + add_address(post, options) + + commit('CardVerify', post, options) + end + + def supports_scrubbing? + true + end + + def scrub(transcript) + transcript. + gsub(%r((&?CardNum=)[^&]*)i, '\1[FILTERED]'). + gsub(%r((&?CVNum=)[^&]*)i, '\1[FILTERED]'). + gsub(%r((&?GlobalPassword=)[^&]*)i, '\1[FILTERED]') + end + + private + + def add_address(post, options) + if address = (options[:billing_address] || options[:address]) + post[:Street] = address[:address1] + post[:Zip] = address[:zip] + end + end + + def add_auth(post, authorization) + post[:PNRef] = authorization + end + + def add_invoice(post, money, options) + currency = (options[:currency] || currency(money)) + + post[:Amount] = localized_amount(money, currency) + post[:InvNum] = truncate(options[:order_id], 16) + end + + def add_payment_method(post, payment_method) + post[:CardNum] = payment_method.number + post[:ExpDate] = expdate(payment_method) + post[:NameOnCard] = payment_method.name + post[:CVNum] = payment_method.verification_value + end + + def parse(body) + response = {} + + Nokogiri::XML(body).root.xpath('*').each do |node| + response[node.name.downcase.to_sym] = node.text + end + + ext_data = Nokogiri::HTML.parse(response[:extdata]) + response[:approved_amount] = ext_data.xpath('//approvedamount').text + response[:balance_due] = ext_data.xpath('//balancedue').text + + response + end + + def commit(action, parameters, options) + raw = parse(ssl_post(url, post_data(action, parameters, options))) + Response.new( + success_from(raw), + message_from(raw), + raw, + authorization: authorization_from(raw), + test: test?, + avs_result: avs_from(raw), + cvv_result: cvv_from(raw) + ) + end + + def post_data(action, params, options) + post = default_params + post[:GlobalUserName] = @options[:global_user_name] + post[:GlobalPassword] = @options[:global_password] + post[:TransType] = action + post[:ExtData] = "#{@options[:term_type]}" + + post.merge(params).map { |key, value| "#{key}=#{CGI.escape(value.to_s)}" }.join('&') + end + + def url + (test? ? test_url : live_url) + end + + def success_from(response) + response[:result] == '0' || response[:result] == '200' + end + + def message_from(response) + response[:respmsg] + end + + def authorization_from(response) + response[:pnref] + end + + def avs_from(response) + { code: response[:getavsresult] } + end + + def cvv_from(response) + response[:getcvresult] + end + + def default_params + { + CardNum: '', + ExpDate: '', + NameOnCard: '', + Amount: '', + PNRef: '', + Zip: '', + Street: '', + CVNum: '', + MagData: '', + InvNum: '', + ExtData: '' + } + end + + end + end +end diff --git a/lib/active_merchant/billing/gateways/hdfc.rb b/lib/active_merchant/billing/gateways/hdfc.rb index be973857764..142d7c83b82 100644 --- a/lib/active_merchant/billing/gateways/hdfc.rb +++ b/lib/active_merchant/billing/gateways/hdfc.rb @@ -1,16 +1,16 @@ -require "nokogiri" +require 'nokogiri' module ActiveMerchant #:nodoc: module Billing #:nodoc: class HdfcGateway < Gateway - self.display_name = "HDFC" - self.homepage_url = "http://www.hdfcbank.com/sme/sme-details/merchant-services/guzh6m0i" + self.display_name = 'HDFC' + self.homepage_url = 'http://www.hdfcbank.com/sme/sme-details/merchant-services/guzh6m0i' - self.test_url = "https://securepgtest.fssnet.co.in/pgway/servlet/" - self.live_url = "https://securepg.fssnet.co.in/pgway/servlet/" + self.test_url = 'https://securepgtest.fssnet.co.in/pgway/servlet/' + self.live_url = 'https://securepg.fssnet.co.in/pgway/servlet/' - self.supported_countries = ["IN"] - self.default_currency = "INR" + self.supported_countries = ['IN'] + self.default_currency = 'INR' self.money_format = :dollars self.supported_cardtypes = [:visa, :master, :discover, :diners_club] @@ -25,7 +25,7 @@ def purchase(amount, payment_method, options={}) add_payment_method(post, payment_method) add_customer_data(post, options) - commit("purchase", post) + commit('purchase', post) end def authorize(amount, payment_method, options={}) @@ -34,7 +34,7 @@ def authorize(amount, payment_method, options={}) add_payment_method(post, payment_method) add_customer_data(post, options) - commit("authorize", post) + commit('authorize', post) end def capture(amount, authorization, options={}) @@ -43,7 +43,7 @@ def capture(amount, authorization, options={}) add_reference(post, authorization) add_customer_data(post, options) - commit("capture", post) + commit('capture', post) end def refund(amount, authorization, options={}) @@ -52,22 +52,22 @@ def refund(amount, authorization, options={}) add_reference(post, authorization) add_customer_data(post, options) - commit("refund", post) + commit('refund', post) end private - CURRENCY_CODES = Hash.new{|h,k| raise ArgumentError.new("Unsupported currency for HDFC: #{k}")} - CURRENCY_CODES["AED"] = "784" - CURRENCY_CODES["AUD"] = "036" - CURRENCY_CODES["CAD"] = "124" - CURRENCY_CODES["EUR"] = "978" - CURRENCY_CODES["GBP"] = "826" - CURRENCY_CODES["INR"] = "356" - CURRENCY_CODES["OMR"] = "512" - CURRENCY_CODES["QAR"] = "634" - CURRENCY_CODES["SGD"] = "702" - CURRENCY_CODES["USD"] = "840" + CURRENCY_CODES = Hash.new { |h, k| raise ArgumentError.new("Unsupported currency for HDFC: #{k}") } + CURRENCY_CODES['AED'] = '784' + CURRENCY_CODES['AUD'] = '036' + CURRENCY_CODES['CAD'] = '124' + CURRENCY_CODES['EUR'] = '978' + CURRENCY_CODES['GBP'] = '826' + CURRENCY_CODES['INR'] = '356' + CURRENCY_CODES['OMR'] = '512' + CURRENCY_CODES['QAR'] = '634' + CURRENCY_CODES['SGD'] = '702' + CURRENCY_CODES['USD'] = '840' def add_invoice(post, amount, options) post[:amt] = amount(amount) @@ -113,7 +113,7 @@ def parse(xml) doc.children.each do |node| if node.text? next - elsif (node.elements.size == 0) + elsif node.elements.size == 0 response[node.name.downcase.to_sym] = node.text else node.elements.each do |childnode| @@ -127,14 +127,14 @@ def parse(xml) end def fix_xml(xml) - xml.gsub(/&(?!(?:amp|quot|apos|lt|gt);)/, "&") + xml.gsub(/&(?!(?:amp|quot|apos|lt|gt);)/, '&') end ACTIONS = { - "purchase" => "1", - "refund" => "2", - "authorize" => "4", - "capture" => "5", + 'purchase' => '1', + 'refund' => '2', + 'authorize' => '4', + 'capture' => '5', } def commit(action, post) @@ -164,13 +164,13 @@ def build_request(post) end def url(action) - endpoint = "TranPortalXMLServlet" + endpoint = 'TranPortalXMLServlet' (test? ? test_url : live_url) + endpoint end def success_from(result) case result - when "CAPTURED", "APPROVED", "NOT ENROLLED", "ENROLLED" + when 'CAPTURED', 'APPROVED', 'NOT ENROLLED', 'ENROLLED' true else false @@ -179,23 +179,23 @@ def success_from(result) def message_from(succeeded, response) if succeeded - "Succeeded" + 'Succeeded' else - (response[:error_text] || response[:result] || "Unable to read error message").split("-").last + (response[:error_text] || response[:result] || 'Unable to read error message').split('-').last end end def authorization_from(request, response) - [response[:tranid], request[:member]].join("|") + [response[:tranid], request[:member]].join('|') end def split_authorization(authorization) - tranid, member = authorization.split("|") + tranid, member = authorization.split('|') [tranid, member] end def escape(string, max_length=250) - return "" unless string + return '' unless string if max_length string = string[0...max_length] end @@ -204,4 +204,3 @@ def escape(string, max_length=250) end end end - diff --git a/lib/active_merchant/billing/gateways/hps.rb b/lib/active_merchant/billing/gateways/hps.rb new file mode 100644 index 00000000000..6069040003c --- /dev/null +++ b/lib/active_merchant/billing/gateways/hps.rb @@ -0,0 +1,350 @@ +require 'nokogiri' + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class HpsGateway < Gateway + self.live_url = 'https://posgateway.secureexchange.net/Hps.Exchange.PosGateway/PosGatewayService.asmx?wsdl' + self.test_url = 'https://posgateway.cert.secureexchange.net/Hps.Exchange.PosGateway/PosGatewayService.asmx?wsdl' + + self.supported_countries = ['US'] + self.default_currency = 'USD' + self.supported_cardtypes = [:visa, :master, :american_express, :discover, :jbc, :diners_club] + + self.homepage_url = 'http://developer.heartlandpaymentsystems.com/SecureSubmit/' + self.display_name = 'Heartland Payment Systems' + + self.money_format = :dollars + + PAYMENT_DATA_SOURCE_MAPPING = { + apple_pay: 'ApplePay', + master: 'MasterCard 3DSecure', + visa: 'Visa 3DSecure', + american_express: 'AMEX 3DSecure', + discover: 'Discover 3DSecure', + } + + def initialize(options={}) + requires!(options, :secret_api_key) + super + end + + def authorize(money, card_or_token, options={}) + commit('CreditAuth') do |xml| + add_amount(xml, money) + add_allow_dup(xml) + add_customer_data(xml, card_or_token, options) + add_details(xml, options) + add_descriptor_name(xml, options) + add_payment(xml, card_or_token, options) + add_three_d_secure(xml, card_or_token, options) + end + end + + def capture(money, transaction_id, options={}) + commit('CreditAddToBatch') do |xml| + add_amount(xml, money) + add_reference(xml, transaction_id) + end + end + + def purchase(money, card_or_token, options={}) + commit('CreditSale') do |xml| + add_amount(xml, money) + add_allow_dup(xml) + add_customer_data(xml, card_or_token, options) + add_details(xml, options) + add_descriptor_name(xml, options) + add_payment(xml, card_or_token, options) + add_three_d_secure(xml, card_or_token, options) + end + end + + def refund(money, transaction_id, options={}) + commit('CreditReturn') do |xml| + add_amount(xml, money) + add_allow_dup(xml) + add_reference(xml, transaction_id) + add_customer_data(xml, transaction_id, options) + add_details(xml, options) + end + end + + def verify(card_or_token, options={}) + commit('CreditAccountVerify') do |xml| + add_customer_data(xml, card_or_token, options) + add_descriptor_name(xml, options) + add_payment(xml, card_or_token, options) + end + end + + def void(transaction_id, options={}) + commit('CreditVoid') do |xml| + add_reference(xml, transaction_id) + end + end + + def supports_scrubbing? + true + end + + def scrub(transcript) + transcript. + gsub(%r(()[^<]*(<\/hps:CardNbr>))i, '\1[FILTERED]\2'). + gsub(%r(()[^<]*(<\/hps:CVV2>))i, '\1[FILTERED]\2'). + gsub(%r(()[^<]*(<\/hps:SecretAPIKey>))i, '\1[FILTERED]\2'). + gsub(%r(()[^<]*(<\/hps:PaymentData>))i, '\1[FILTERED]\2') + end + + private + + def add_reference(xml, transaction_id) + xml.hps :GatewayTxnId, transaction_id + end + + def add_amount(xml, money) + xml.hps :Amt, amount(money) if money + end + + def add_customer_data(xml, credit_card, options) + xml.hps :CardHolderData do + if credit_card.respond_to?(:number) + xml.hps :CardHolderFirstName, credit_card.first_name if credit_card.first_name + xml.hps :CardHolderLastName, credit_card.last_name if credit_card.last_name + end + + xml.hps :CardHolderEmail, options[:email] if options[:email] + xml.hps :CardHolderPhone, options[:phone] if options[:phone] + + if(billing_address = (options[:billing_address] || options[:address])) + xml.hps :CardHolderAddr, billing_address[:address1] if billing_address[:address1] + xml.hps :CardHolderCity, billing_address[:city] if billing_address[:city] + xml.hps :CardHolderState, billing_address[:state] if billing_address[:state] + xml.hps :CardHolderZip, billing_address[:zip] if billing_address[:zip] + end + end + end + + def add_payment(xml, card_or_token, options) + xml.hps :CardData do + if card_or_token.respond_to?(:number) + if card_or_token.track_data + xml.tag!('hps:TrackData', 'method'=>'swipe') do + xml.text! card_or_token.track_data + end + if options[:encryption_type] + xml.hps :EncryptionData do + xml.hps :Version, options[:encryption_type] + if options[:encryption_type] == '02' + xml.hps :EncryptedTrackNumber, options[:encrypted_track_number] + xml.hps :KTB, options[:ktb] + end + end + end + else + xml.hps :ManualEntry do + xml.hps :CardNbr, card_or_token.number + xml.hps :ExpMonth, card_or_token.month + xml.hps :ExpYear, card_or_token.year + xml.hps :CVV2, card_or_token.verification_value if card_or_token.verification_value + xml.hps :CardPresent, 'N' + xml.hps :ReaderPresent, 'N' + end + end + else + xml.hps :TokenData do + xml.hps :TokenValue, card_or_token + end + end + xml.hps :TokenRequest, (options[:store] ? 'Y' : 'N') + end + end + + def add_details(xml, options) + xml.hps :AdditionalTxnFields do + xml.hps :Description, options[:description] if options[:description] + xml.hps :InvoiceNbr, options[:order_id] if options[:order_id] + xml.hps :CustomerID, options[:customer_id] if options[:customer_id] + end + end + + def add_allow_dup(xml) + xml.hps :AllowDup, 'Y' + end + + def add_descriptor_name(xml, options) + xml.hps :TxnDescriptor, options[:descriptor_name] if options[:descriptor_name] + end + + def add_three_d_secure(xml, card_or_token, options) + if card_or_token.is_a?(NetworkTokenizationCreditCard) + build_three_d_secure(xml, { + source: card_or_token.source, + cavv: card_or_token.payment_cryptogram, + eci: card_or_token.eci, + xid: card_or_token.transaction_id, + }) + elsif options[:three_d_secure] + options[:three_d_secure][:source] ||= card_brand(card_or_token) + build_three_d_secure(xml, options[:three_d_secure]) + end + end + + def build_three_d_secure(xml, three_d_secure) + # PaymentDataSource is required when supplying the SecureECommerce data group, + # and the gateway currently only allows the values within the mapping + return unless PAYMENT_DATA_SOURCE_MAPPING[three_d_secure[:source].to_sym] + + xml.hps :SecureECommerce do + xml.hps :PaymentDataSource, PAYMENT_DATA_SOURCE_MAPPING[three_d_secure[:source].to_sym] + xml.hps :TypeOfPaymentData, '3DSecure' # Only type currently supported + xml.hps :PaymentData, three_d_secure[:cavv] if three_d_secure[:cavv] + # the gateway only allows a single character for the ECI + xml.hps :ECommerceIndicator, strip_leading_zero(three_d_secure[:eci]) if three_d_secure[:eci] + xml.hps :XID, three_d_secure[:xid] if three_d_secure[:xid] + end + end + + def strip_leading_zero(value) + return value unless value[0] == '0' + value[1, 1] + end + + def build_request(action) + xml = Builder::XmlMarkup.new(encoding: 'UTF-8') + xml.instruct!(:xml, encoding: 'UTF-8') + xml.SOAP :Envelope, { + 'xmlns:SOAP' => 'http://schemas.xmlsoap.org/soap/envelope/', + 'xmlns:hps' => 'http://Hps.Exchange.PosGateway' } do + xml.SOAP :Body do + xml.hps :PosRequest do + xml.hps 'Ver1.0'.to_sym do + xml.hps :Header do + xml.hps :SecretAPIKey, @options[:secret_api_key] + xml.hps :DeveloperID, @options[:developer_id] if @options[:developer_id] + xml.hps :VersionNbr, @options[:version_number] if @options[:version_number] + xml.hps :SiteTrace, @options[:site_trace] if @options[:site_trace] + end + xml.hps :Transaction do + xml.hps action.to_sym do + if %w(CreditVoid CreditAddToBatch).include?(action) + yield(xml) + else + xml.hps :Block1 do + yield(xml) + end + end + end + end + end + end + end + end + xml.target! + end + + def parse(raw) + response = {} + + doc = Nokogiri::XML(raw) + doc.remove_namespaces! + if(header = doc.xpath('//Header').first) + header.elements.each do |node| + if node.elements.size == 0 + response[node.name] = node.text + else + node.elements.each do |childnode| + response[childnode.name] = childnode.text + end + end + end + end + if(transaction = doc.xpath('//Transaction/*[1]').first) + transaction.elements.each do |node| + response[node.name] = node.text + end + end + if(fault = doc.xpath('//Fault/Reason/Text').first) + response['Fault'] = fault.text + end + + response + end + + def commit(action, &request) + data = build_request(action, &request) + + response = begin + parse(ssl_post((test? ? test_url : live_url), data, 'Content-Type' => 'text/xml')) + rescue ResponseError => e + parse(e.response.body) + end + + ActiveMerchant::Billing::Response.new( + successful?(response), + message_from(response), + response, + test: test?, + authorization: authorization_from(response), + avs_result: { + code: response['AVSRsltCode'], + message: response['AVSRsltText'] + }, + cvv_result: response['CVVRsltCode'] + ) + end + + def successful?(response) + ( + (response['GatewayRspCode'] == '0') && + ((response['RspCode'] || '00') == '00' || response['RspCode'] == '85') + ) + end + + def message_from(response) + if(response['Fault']) + response['Fault'] + elsif(response['GatewayRspCode'] == '0') + if(response['RspCode'] != '00' && response['RspCode'] != '85') + issuer_message(response['RspCode']) + else + response['GatewayRspMsg'] + end + else + (GATEWAY_MESSAGES[response['GatewayRspCode']] || response['GatewayRspMsg']) + end + end + + def authorization_from(response) + response['GatewayTxnId'] + end + + def test? + @options[:secret_api_key]&.include?('_cert_') + end + + ISSUER_MESSAGES = { + '13' => 'Must be greater than or equal 0.', + '14' => 'The card number is incorrect.', + '54' => 'The card has expired.', + '55' => 'The 4-digit pin is invalid.', + '75' => 'Maximum number of pin retries exceeded.', + '80' => 'Card expiration date is invalid.', + '86' => "Can't verify card pin number." + } + def issuer_message(code) + return 'The card was declined.' if %w(02 03 04 05 41 43 44 51 56 61 62 63 65 78).include?(code) + return 'An error occurred while processing the card.' if %w(06 07 12 15 19 12 52 53 57 58 76 77 91 96 EC).include?(code) + return "The card's security code is incorrect." if %w(EB N7).include?(code) + ISSUER_MESSAGES[code] + end + + GATEWAY_MESSAGES = { + '-2' => 'Authentication error. Please double check your service configuration.', + '12' => 'Invalid CPC data.', + '13' => 'Invalid card data.', + '14' => 'The card number is not a valid credit card number.', + '30' => 'Gateway timed out.' + } + end + end +end diff --git a/lib/active_merchant/billing/gateways/iats_payments.rb b/lib/active_merchant/billing/gateways/iats_payments.rb index 8db8cfb54e9..c2d4505dfb9 100644 --- a/lib/active_merchant/billing/gateways/iats_payments.rb +++ b/lib/active_merchant/billing/gateways/iats_payments.rb @@ -1,34 +1,289 @@ module ActiveMerchant #:nodoc: module Billing #:nodoc: - class IatsPaymentsGateway < AuthorizeNetGateway - self.live_url = self.test_url = 'https://www.iatspayments.com/netgate/AEGateway.aspx' + class IatsPaymentsGateway < Gateway + class_attribute :live_na_url, :live_uk_url - self.homepage_url = 'http://www.iatspayments.com/' - self.display_name = 'IATSPayments' + self.live_na_url = 'https://www.iatspayments.com/NetGate' + self.live_uk_url = 'https://www.uk.iatspayments.com/NetGate' - def authorize(money, paysource, options = {}) - raise NotImplementedError + self.supported_countries = %w(AU BR CA CH DE DK ES FI FR GR HK IE IT NL NO PT SE SG TR GB US TH ID PH BE) + self.default_currency = 'USD' + self.supported_cardtypes = [:visa, :master, :american_express, :discover] + + self.homepage_url = 'http://home.iatspayments.com/' + self.display_name = 'iATS Payments' + + ACTIONS = { + purchase: 'ProcessCreditCardV1', + purchase_check: 'ProcessACHEFTV1', + refund: 'ProcessCreditCardRefundWithTransactionIdV1', + refund_check: 'ProcessACHEFTRefundWithTransactionIdV1', + store: 'CreateCreditCardCustomerCodeV1', + unstore: 'DeleteCustomerCodeV1' + } + + def initialize(options={}) + if(options[:login]) + ActiveMerchant.deprecated("The 'login' option is deprecated in favor of 'agent_code' and will be removed in a future version.") + options[:agent_code] = options[:login] + end + + options[:region] = 'na' unless options[:region] + + requires!(options, :agent_code, :password, :region) + super end - def capture(money, authorization, options = {}) - raise NotImplementedError + def purchase(money, payment, options={}) + post = {} + add_invoice(post, money, options) + add_payment(post, payment) + add_address(post, options) + add_ip(post, options) + add_description(post, options) + + commit((payment.is_a?(Check) ? :purchase_check : :purchase), post) end - def void(authorization, options = {}) - raise NotImplementedError + def refund(money, authorization, options={}) + post = {} + transaction_id, payment_type = split_authorization(authorization) + post[:transaction_id] = transaction_id + add_invoice(post, -money, options) + add_ip(post, options) + add_description(post, options) + + commit((payment_type == 'check' ? :refund_check : :refund), post) end - def refund(money, identification, options = {}) - raise NotImplementedError + def store(credit_card, options = {}) + post = {} + add_payment(post, credit_card) + add_address(post, options) + add_ip(post, options) + add_description(post, options) + add_store_defaults(post) + + commit(:store, post) end - def credit(money, identification, options = {}) - raise NotImplementedError + def unstore(authorization, options = {}) + post = {} + post[:customer_code] = authorization + add_ip(post, options) + + commit(:unstore, post) + end + + def supports_scrubbing? + true + end + + def scrub(transcript) + transcript. + gsub(%r(().+()), '\1[FILTERED]\2'). + gsub(%r(().+()), '\1[FILTERED]\2'). + gsub(%r(().+()), '\1[FILTERED]\2'). + gsub(%r(().+()), '\1[FILTERED]\2'). + gsub(%r(().+()), '\1[FILTERED]\2') end private - def split(response) - response.split(',') + + def add_ip(post, options) + post[:customer_ip_address] = options[:ip] if options.has_key?(:ip) + end + + def add_address(post, options) + billing_address = options[:billing_address] || options[:address] + if(billing_address) + post[:address] = billing_address[:address1] + post[:city] = billing_address[:city] + post[:state] = billing_address[:state] + post[:zip_code] = billing_address[:zip] + end + end + + def add_invoice(post, money, options) + post[:invoice_num] = options[:order_id] if options[:order_id] + post[:total] = amount(money) + end + + def add_description(post, options) + post[:comment] = options[:description] if options[:description] + end + + def add_payment(post, payment) + if payment.is_a?(Check) + add_check(post, payment) + else + add_credit_card(post, payment) + end + end + + def add_credit_card(post, payment) + post[:first_name] = payment.first_name + post[:last_name] = payment.last_name + post[:credit_card_num] = payment.number + post[:credit_card_expiry] = expdate(payment) + post[:cvv2] = payment.verification_value if payment.verification_value? + post[:mop] = creditcard_brand(payment.brand) + end + + def add_check(post, payment) + post[:first_name] = payment.first_name + post[:last_name] = payment.last_name + post[:account_num] = "#{payment.routing_number}#{payment.account_number}" + post[:account_type] = payment.account_type.upcase + end + + def add_store_defaults(post) + post[:recurring] = false + post[:begin_date] = Time.now.xmlschema + post[:end_date] = Time.now.xmlschema + post[:amount] = 0 + end + + def expdate(creditcard) + year = sprintf('%.4i', creditcard.year) + month = sprintf('%.2i', creditcard.month) + + "#{month}/#{year[-2..-1]}" + end + + def creditcard_brand(brand) + case brand + when 'visa' then 'VISA' + when 'master' then 'MC' + when 'discover' then 'DSC' + when 'american_express' then 'AMX' + when 'maestro' then 'MAESTR' + else + raise "Unhandled credit card brand #{brand}" + end + end + + def commit(action, parameters) + response = parse(ssl_post(url(action), post_data(action, parameters), + { 'Content-Type' => 'application/soap+xml; charset=utf-8'})) + + Response.new( + success_from(response), + message_from(response), + response, + authorization: authorization_from(action, response), + test: test? + ) + end + + def endpoints + { + purchase: 'ProcessLink.asmx', + purchase_check: 'ProcessLink.asmx', + refund: 'ProcessLink.asmx', + refund_check: 'ProcessLink.asmx', + store: 'CustomerLink.asmx', + unstore: 'CustomerLink.asmx' + } + end + + def url(action) + base_url = @options[:region] == 'uk' ? live_uk_url : live_na_url + "#{base_url}/#{endpoints[action]}?op=#{ACTIONS[action]}" + end + + def parse(body) + response = {} + hashify_xml!(body, response) + response + end + + def dexmlize_param_name(name) + names = { + 'AUTHORIZATIONRESULT' => :authorization_result, + 'SETTLEMENTBATCHDATE' => :settlement_batch_date, + 'SETTLEMENTDATE' => :settlement_date, + 'TRANSACTIONID' => :transaction_id + } + names[name] || name.to_s.downcase.intern + end + + def hashify_xml!(xml, response) + xml = REXML::Document.new(xml) + + xml.elements.each('//IATSRESPONSE/*') do |node| + recursively_parse_element(node, response) + end + end + + def recursively_parse_element(node, response) + if(node.has_elements?) + node.elements.each { |n| recursively_parse_element(n, response) } + else + response[dexmlize_param_name(node.name)] = (node.text ? node.text.strip : nil) + end + end + + def successful_result_message?(response) + response[:authorization_result] ? response[:authorization_result].start_with?('OK') : false + end + + def success_from(response) + response[:status] == 'Success' && successful_result_message?(response) + end + + def message_from(response) + if !successful_result_message?(response) && response[:authorization_result] + return response[:authorization_result].strip + elsif(response[:status] == 'Failure') + return response[:errors] + else + response[:status] + end + end + + def authorization_from(action, response) + if [:store, :unstore].include?(action) + response[:customercode] + elsif [:purchase_check].include?(action) + response[:transaction_id] ? "#{response[:transaction_id]}|check" : nil + else + response[:transaction_id] + end + end + + def split_authorization(authorization) + authorization.split('|') + end + + def envelope_namespaces + { + 'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance', + 'xmlns:xsd' => 'http://www.w3.org/2001/XMLSchema', + 'xmlns:soap12' => 'http://www.w3.org/2003/05/soap-envelope' + } + end + + def post_data(action, parameters = {}) + xml = Builder::XmlMarkup.new + xml.instruct!(:xml, :version => '1.0', :encoding => 'utf-8') + xml.tag! 'soap12:Envelope', envelope_namespaces do + xml.tag! 'soap12:Body' do + xml.tag! ACTIONS[action], { 'xmlns' => 'https://www.iatspayments.com/NetGate/' } do + xml.tag!('agentCode', @options[:agent_code]) + xml.tag!('password', @options[:password]) + parameters.each do |name, value| + xml.tag!(xmlize_param_name(name), value) + end + end + end + end + xml.target! + end + + def xmlize_param_name(name) + names = { customer_ip_address: 'customerIPAddress' } + names[name] || name.to_s.camelcase(:lower) end end end diff --git a/lib/active_merchant/billing/gateways/ideal/ideal_base.rb b/lib/active_merchant/billing/gateways/ideal/ideal_base.rb deleted file mode 100644 index 360f0ad5e7f..00000000000 --- a/lib/active_merchant/billing/gateways/ideal/ideal_base.rb +++ /dev/null @@ -1,249 +0,0 @@ -require File.dirname(__FILE__) + '/ideal_response' - -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - # Implementation contains some simplifications - # - does not support multiple subID per merchant - # - language is fixed to 'nl' - class IdealBaseGateway < Gateway - class_attribute :server_pem, :pem_password, :default_expiration_period - self.default_expiration_period = 'PT10M' - self.default_currency = 'EUR' - self.pem_password = true - - self.abstract_class = true - - # These constants will never change for most users - AUTHENTICATION_TYPE = 'SHA1_RSA' - LANGUAGE = 'nl' - SUB_ID = '0' - API_VERSION = '1.1.0' - - def initialize(options = {}) - requires!(options, :login, :password, :pem) - - options[:pem_password] = options[:password] - super - end - - # Setup transaction. Get redirect_url from response.service_url - def setup_purchase(money, options = {}) - requires!(options, :issuer_id, :return_url, :order_id, :currency, :description, :entrance_code) - - commit(build_transaction_request(money, options)) - end - - # Check status of transaction and confirm payment - # transaction_id must be a valid transaction_id from a prior setup. - def capture(transaction, options = {}) - options[:transaction_id] = transaction - commit(build_status_request(options)) - end - - # Get list of issuers from response.issuer_list - def issuers - commit(build_directory_request) - end - - private - - def url - (test? ? test_url : live_url) - end - - def token - if @token.nil? - @token = create_fingerprint(@options[:pem]) - end - @token - end - - # - # - # 2001-12-17T09:30:47.0Z - # - # 1003 - # - # - # 000123456 - # 0 - # passkey - # 1 - # 3823ad872eff23 - # https://www.mijnwinkel.nl/betaalafhandeling - # - # - # - # iDEAL-aankoop 21 - # 5999 - # EUR - # PT3M30S - # nl - # Documentensuite - # D67tyx6rw9IhY71 - # - # - def build_transaction_request(money, options) - date_time_stamp = create_time_stamp - message = date_time_stamp + - options[:issuer_id] + - @options[:login] + - SUB_ID + - options[:return_url] + - options[:order_id] + - money.to_s + - (options[:currency] || currency(money)) + - LANGUAGE + - options[:description] + - options[:entrance_code] - token_code = sign_message(@options[:pem], @options[:password], message) - - xml = Builder::XmlMarkup.new(:indent => 2) - xml.instruct! - xml.tag! 'AcquirerTrxReq', 'xmlns' => 'http://www.idealdesk.com/Message', 'version' => API_VERSION do - xml.tag! 'createDateTimeStamp', date_time_stamp - xml.tag! 'Issuer' do - xml.tag! 'issuerID', options[:issuer_id] - end - xml.tag! 'Merchant' do - xml.tag! 'merchantID', @options[:login] - xml.tag! 'subID', SUB_ID - xml.tag! 'authentication', AUTHENTICATION_TYPE - xml.tag! 'token', token - xml.tag! 'tokenCode', token_code - xml.tag! 'merchantReturnURL', options[:return_url] - end - xml.tag! 'Transaction' do - xml.tag! 'purchaseID', options[:order_id] - xml.tag! 'amount', money - xml.tag! 'currency', options[:currency] - xml.tag! 'expirationPeriod', options[:expiration_period] || default_expiration_period - xml.tag! 'language', LANGUAGE - xml.tag! 'description', options[:description] - xml.tag! 'entranceCode', options[:entrance_code] - end - xml.target! - end - end - - # - # - # 2001-12-17T09:30:47.0Z - # - # 000123456 - # 0 - # keyed hash - # 1 - # 3823ad872eff23 - # - # - # 0001023456789112 - # - # - def build_status_request(options) - datetimestamp = create_time_stamp - message = datetimestamp + @options[:login] + SUB_ID + options[:transaction_id] - tokenCode = sign_message(@options[:pem], @options[:password], message) - - xml = Builder::XmlMarkup.new(:indent => 2) - xml.instruct! - xml.tag! 'AcquirerStatusReq', 'xmlns' => 'http://www.idealdesk.com/Message', 'version' => API_VERSION do - xml.tag! 'createDateTimeStamp', datetimestamp - xml.tag! 'Merchant' do - xml.tag! 'merchantID', @options[:login] - xml.tag! 'subID', SUB_ID - xml.tag! 'authentication' , AUTHENTICATION_TYPE - xml.tag! 'token', token - xml.tag! 'tokenCode', tokenCode - end - xml.tag! 'Transaction' do - xml.tag! 'transactionID', options[:transaction_id] - end - end - xml.target! - end - - # - # - # 2001-12-17T09:30:47.0Z - # - # 000000001 - # 0 - # 1 - # hashkey - # WajqV1a3nDen0be2r196g9FGFF= - # - # - def build_directory_request - datetimestamp = create_time_stamp - message = datetimestamp + @options[:login] + SUB_ID - tokenCode = sign_message(@options[:pem], @options[:password], message) - - xml = Builder::XmlMarkup.new(:indent => 2) - xml.instruct! - xml.tag! 'DirectoryReq', 'xmlns' => 'http://www.idealdesk.com/Message', 'version' => API_VERSION do - xml.tag! 'createDateTimeStamp', datetimestamp - xml.tag! 'Merchant' do - xml.tag! 'merchantID', @options[:login] - xml.tag! 'subID', SUB_ID - xml.tag! 'authentication', AUTHENTICATION_TYPE - xml.tag! 'token', token - xml.tag! 'tokenCode', tokenCode - end - end - xml.target! - end - - def commit(request) - raw_response = ssl_post(url, request) - response = Hash.from_xml(raw_response.to_s) - response_type = response.keys[0] - - case response_type - when 'AcquirerTrxRes', 'DirectoryRes' - success = true - when 'ErrorRes' - success = false - when 'AcquirerStatusRes' - raise SecurityError, "Message verification failed.", caller unless status_response_verified?(response) - success = (response['AcquirerStatusRes']['Transaction']['status'] == 'Success') - else - raise ArgumentError, "Unknown response type.", caller - end - - return IdealResponse.new(success, response.keys[0], response, :test => test?) - end - - def create_fingerprint(cert_file) - cert_data = OpenSSL::X509::Certificate.new(cert_file).to_s - cert_data = cert_data.sub(/-----BEGIN CERTIFICATE-----/, '') - cert_data = cert_data.sub(/-----END CERTIFICATE-----/, '') - fingerprint = Base64.decode64(cert_data) - fingerprint = Digest::SHA1.hexdigest(fingerprint) - return fingerprint.upcase - end - - def sign_message(private_key_data, password, data) - private_key = OpenSSL::PKey::RSA.new(private_key_data, password) - signature = private_key.sign(OpenSSL::Digest::SHA1.new, data.gsub('\s', '')) - return Base64.encode64(signature).gsub(/\n/, '') - end - - def verify_message(cert_file, data, signature) - public_key = OpenSSL::X509::Certificate.new(cert_file).public_key - return public_key.verify(OpenSSL::Digest::SHA1.new, Base64.decode64(signature), data) - end - - def status_response_verified?(response) - transaction = response['AcquirerStatusRes']['Transaction'] - message = response['AcquirerStatusRes']['createDateTimeStamp'] + transaction['transactionID' ] + transaction['status'] - message << transaction['consumerAccountNumber'].to_s - verify_message(server_pem, message, response['AcquirerStatusRes']['Signature']['signatureValue']) - end - - def create_time_stamp - Time.now.gmtime.strftime('%Y-%m-%dT%H:%M:%S.000Z') - end - end - end -end diff --git a/lib/active_merchant/billing/gateways/ideal/ideal_rabobank.pem b/lib/active_merchant/billing/gateways/ideal/ideal_rabobank.pem deleted file mode 100755 index 3259cfa330a..00000000000 --- a/lib/active_merchant/billing/gateways/ideal/ideal_rabobank.pem +++ /dev/null @@ -1,13 +0,0 @@ ------BEGIN CERTIFICATE----- -MIICQDCCAakCBELvbPYwDQYJKoZIhvcNAQEEBQAwZzELMAkGA1UEBhMCREUxDzANBgNVBAgTBkhl -c3NlbjESMBAGA1UEBxMJRnJhbmtmdXJ0MQ4wDAYDVQQKEwVpREVBTDEOMAwGA1UECxMFaURFQUwx -EzARBgNVBAMTCmlERUFMIFJhYm8wHhcNMDUwODAyMTI1NDE0WhcNMTUwNzMxMTI1NDE0WjBnMQsw -CQYDVQQGEwJERTEPMA0GA1UECBMGSGVzc2VuMRIwEAYDVQQHEwlGcmFua2Z1cnQxDjAMBgNVBAoT -BWlERUFMMQ4wDAYDVQQLEwVpREVBTDETMBEGA1UEAxMKaURFQUwgUmFibzCBnzANBgkqhkiG9w0B -AQEFAAOBjQAwgYkCgYEA486iIKVhr8RNjxH+PZ3yTWx/8k2fqDFm8XU8I1Z5esRmPFnXmlgA8cG7 -e9AaBPaLoP7Dc8dRQoUO66KMakzGI/WAVdHIJiiKrz8xOcioIgrzPSqec7aqse3J28UraEHkGESJ -7dAW7Pw/shrmpmkzKsunLt6AkXss4W3JUndZUN0CAwEAATANBgkqhkiG9w0BAQQFAAOBgQCGy/FK -Lotp2ZOTtuLMgvDy74eicq/Znv4bLfpglzAPHycRHcHsXuer/lNHyvpKf2gdYe+IFalUW3OJZWIM -jpm4UniJ16RPdgwWVRJEdPr/P7JXMIqD2IEOgujuuTQ7x0VgCf9XrsPsP9ZR5DIPcDDhbrpSE0yF -Do77nwG61xMaGA== ------END CERTIFICATE----- diff --git a/lib/active_merchant/billing/gateways/ideal/ideal_response.rb b/lib/active_merchant/billing/gateways/ideal/ideal_response.rb deleted file mode 100644 index e050964ae40..00000000000 --- a/lib/active_merchant/billing/gateways/ideal/ideal_response.rb +++ /dev/null @@ -1,29 +0,0 @@ -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - class IdealResponse < Response - - def issuer_list - list = @params.values[0]['Directory']['Issuer'] - case list - when Hash - return [list] - when Array - return list - end - end - - def service_url - @params.values[0]['Issuer']['issuerAuthenticationURL'] - end - - def transaction - @params.values[0]['Transaction'] - end - - def error - @params.values[0]['Error'] - end - - end - end -end \ No newline at end of file diff --git a/lib/active_merchant/billing/gateways/ideal_rabobank.rb b/lib/active_merchant/billing/gateways/ideal_rabobank.rb deleted file mode 100644 index 1edf7bfbcf8..00000000000 --- a/lib/active_merchant/billing/gateways/ideal_rabobank.rb +++ /dev/null @@ -1,66 +0,0 @@ -require File.dirname(__FILE__) + '/ideal/ideal_base' - -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - # First, make sure you have everything setup correctly and all of your dependencies in place with: - # - # require 'rubygems' - # require 'active_merchant' - # - # ActiveMerchant expects the amounts to be given as an Integer in cents. In this case, 10 EUR becomes 1000. - # - # Create certificates for authentication: - # - # The PEM file expected should contain both the certificate and the generated PEM file. - # Some sample shell commands to generate the certificates: - # - # openssl genrsa -aes128 -out priv.pem -passout pass:[YOUR PASSWORD] 1024 - # openssl req -x509 -new -key priv.pem -passin pass:[YOUR PASSWORD] -days 3000 -out cert.cer - # cat cert.cer priv.pem > ideal.pem - # - # Following the steps above, upload cert.cer to the ideal web interface and pass the path of ideal.pem to the :pem option. - # - # Configure the gateway using your iDEAL bank account info and security settings: - # - # Create gateway: - # gateway = ActiveMerchant::Billing::IdealRabobankGateway.new( - # :login => '123456789', # 9 digit merchant number - # :pem => File.read(Rails.root + 'config/ideal.pem'), - # :password => 'password' # password for the PEM key - # ) - # - # Get list of issuers to fill selection list on your payment form: - # response = gateway.issuers - # list = response.issuer_list - # - # Request transaction: - # - # options = { - # :issuer_id => '0001', - # :expiration_period => 'PT10M', - # :return_url => 'http://www.return.url', - # :order_id => '1234567890123456', - # :currency => 'EUR', - # :description => 'Een omschrijving', - # :entrance_code => '1234' - # } - # - # response = gateway.setup_purchase(amount, options) - # transaction_id = response.transaction['transactionID'] - # redirect_url = response.service_url - # - # Mandatory status request will confirm transaction: - # response = gateway.capture(transaction_id) - # - # Implementation contains some simplifications - # - does not support multiple subID per merchant - # - language is fixed to 'nl' - class IdealRabobankGateway < IdealBaseGateway - class_attribute :test_url, :live_url - - self.test_url = 'https://idealtest.rabobank.nl/ideal/iDeal' - self.live_url = 'https://ideal.rabobank.nl/ideal/iDeal' - self.server_pem = File.read(File.dirname(__FILE__) + '/ideal/ideal_rabobank.pem') - end - end -end diff --git a/lib/active_merchant/billing/gateways/in_context_paypal_express.rb b/lib/active_merchant/billing/gateways/in_context_paypal_express.rb new file mode 100644 index 00000000000..e746d978ca0 --- /dev/null +++ b/lib/active_merchant/billing/gateways/in_context_paypal_express.rb @@ -0,0 +1,15 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class InContextPaypalExpressGateway < PaypalExpressGateway + self.test_redirect_url = 'https://www.sandbox.paypal.com/checkoutnow' + self.live_redirect_url = 'https://www.paypal.com/checkoutnow' + + def redirect_url_for(token, options = {}) + options = {review: true}.update(options) + url = "#{redirect_url}?token=#{token}" + url += '&useraction=commit' unless options[:review] + url + end + end + end +end diff --git a/lib/active_merchant/billing/gateways/inspire.rb b/lib/active_merchant/billing/gateways/inspire.rb index a3646b7ff02..e7771e9ec81 100644 --- a/lib/active_merchant/billing/gateways/inspire.rb +++ b/lib/active_merchant/billing/gateways/inspire.rb @@ -17,7 +17,7 @@ class InspireGateway < Gateway # ==== Options # # * :login -- The Inspire Username. - # * :password -- The Inspire Passowrd. + # * :password -- The Inspire Password. # See the Inspire Integration Guide for details. (default: +false+) def initialize(options = {}) requires!(options, :login, :password) @@ -33,7 +33,7 @@ def initialize(options = {}) def authorize(money, creditcard, options = {}) post = {} add_invoice(post, options) - add_payment_source(post, creditcard,options) + add_payment_source(post, creditcard, options) add_address(post, creditcard, options) add_customer_data(post, options) @@ -62,12 +62,18 @@ def void(authorization, options = {}) commit('void', nil, post) end + def refund(money, authorization, options = {}) + post = {} + post[:transactionid] = authorization + commit('refund', money, post) + end + # Update the values (such as CC expiration) stored at # InspireGateway. The CC number must be supplied in the # CreditCard object. def update(vault_id, creditcard, options = {}) post = {} - post[:customer_vault] = "update_customer" + post[:customer_vault] = 'update_customer' add_customer_vault_id(post, vault_id) add_creditcard(post, creditcard, options) add_address(post, creditcard, options) @@ -78,7 +84,7 @@ def update(vault_id, creditcard, options = {}) def delete(vault_id) post = {} - post[:customer_vault] = "delete_customer" + post[:customer_vault] = 'delete_customer' add_customer_vault_id(post, vault_id) commit(nil, nil, post) end @@ -93,6 +99,7 @@ def store(creditcard, options = {}) alias_method :unstore, :delete private + def add_customer_data(post, options) if options.has_key? :email post[:email] = options[:email] @@ -129,13 +136,13 @@ def add_payment_source(params, source, options={}) end end - def add_customer_vault_id(params,vault_id) + def add_customer_vault_id(params, vault_id) params[:customer_vault_id] = vault_id end - def add_creditcard(post, creditcard,options) + def add_creditcard(post, creditcard, options) if options[:store] - post[:customer_vault] = "add_customer" + post[:customer_vault] = 'add_customer' post[:customer_vault_id] = options[:store] unless options[:store] == true end post[:ccnumber] = creditcard.number @@ -157,7 +164,7 @@ def add_check(post, check) def parse(body) results = {} body.split(/&/).each do |pair| - key,val = pair.split(/=/) + key, val = pair.split(%r{=}) results[key] = val end @@ -167,33 +174,24 @@ def parse(body) def commit(action, money, parameters) parameters[:amount] = amount(money) if money - response = parse( ssl_post(self.live_url, post_data(action,parameters)) ) + response = parse(ssl_post(self.live_url, post_data(action, parameters))) - Response.new(response["response"] == "1", message_from(response), response, - :authorization => response["transactionid"], + Response.new(response['response'] == '1', message_from(response), response, + :authorization => response['transactionid'], :test => test?, - :cvv_result => response["cvvresponse"], - :avs_result => { :code => response["avsresponse"] } + :cvv_result => response['cvvresponse'], + :avs_result => { :code => response['avsresponse'] } ) - end - def expdate(creditcard) - year = sprintf("%.4i", creditcard.year) - month = sprintf("%.2i", creditcard.month) - - "#{month}#{year[-2..-1]}" - end - - def message_from(response) - case response["responsetext"] - when "SUCCESS","Approved" - "This transaction has been approved" - when "DECLINE" - "This transaction has been declined" + case response['responsetext'] + when 'SUCCESS', 'Approved' + 'This transaction has been approved' + when 'DECLINE' + 'This transaction has been declined' else - response["responsetext"] + response['responsetext'] end end @@ -203,19 +201,18 @@ def post_data(action, parameters = {}) post[:password] = @options[:password] post[:type] = action if action - request = post.merge(parameters).map {|key,value| "#{key}=#{CGI.escape(value.to_s)}"}.join("&") + request = post.merge(parameters).map { |key, value| "#{key}=#{CGI.escape(value.to_s)}" }.join('&') request end def determine_funding_source(source) case when source.is_a?(String) then :vault - when CreditCard.card_companies.keys.include?(card_brand(source)) then :credit_card + when CreditCard.card_companies.include?(card_brand(source)) then :credit_card when card_brand(source) == 'check' then :check - else raise ArgumentError, "Unsupported funding source provided" + else raise ArgumentError, 'Unsupported funding source provided' end end end end end - diff --git a/lib/active_merchant/billing/gateways/instapay.rb b/lib/active_merchant/billing/gateways/instapay.rb old mode 100755 new mode 100644 index ee4ddc55007..7d18c8da05b --- a/lib/active_merchant/billing/gateways/instapay.rb +++ b/lib/active_merchant/billing/gateways/instapay.rb @@ -16,8 +16,8 @@ class InstapayGateway < Gateway # The name of the gateway self.display_name = 'InstaPay' - SUCCESS = "Accepted" - SUCCESS_MESSAGE = "The transaction has been approved" + SUCCESS = 'Accepted' + SUCCESS_MESSAGE = 'The transaction has been approved' def initialize(options = {}) requires!(options, :login) @@ -66,7 +66,7 @@ def add_reference(post, reference) def add_customer_data(post, options) post[:ci_email] = options[:email] - post["ci_IP Address"] = options[:ip] + post['ci_IP Address'] = options[:ip] end def add_address(post, options) @@ -140,7 +140,7 @@ def commit(action, parameters) data = ssl_post self.live_url, post_data(action, parameters) response = parse(data) - Response.new(response[:success] , response[:message], response, + Response.new(response[:success], response[:message], response, :authorization => response[:transaction_id], :avs_result => { :code => response[:avs_result] }, :cvv_result => response[:cvv_result] @@ -154,10 +154,9 @@ def post_data(action, parameters = {}) post[:merchantpin] = @options[:password] end post[:action] = action - request = post.merge(parameters).collect { |key, value| "#{key}=#{CGI.escape(value.to_s)}" }.join("&") + request = post.merge(parameters).collect { |key, value| "#{key}=#{CGI.escape(value.to_s)}" }.join('&') request end end end end - diff --git a/lib/active_merchant/billing/gateways/ipp.rb b/lib/active_merchant/billing/gateways/ipp.rb new file mode 100644 index 00000000000..fa672636787 --- /dev/null +++ b/lib/active_merchant/billing/gateways/ipp.rb @@ -0,0 +1,176 @@ +require 'nokogiri' + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class IppGateway < Gateway + self.live_url = 'https://www.ippayments.com.au/interface/api/dts.asmx' + self.test_url = 'https://demo.ippayments.com.au/interface/api/dts.asmx' + + self.supported_countries = ['AU'] + self.supported_cardtypes = [:visa, :master, :american_express, :diners_club, :jcb] + + self.homepage_url = 'http://www.ippayments.com.au/' + self.display_name = 'IPP' + + self.money_format = :cents + + STANDARD_ERROR_CODE_MAPPING = { + '05' => STANDARD_ERROR_CODE[:card_declined], + '06' => STANDARD_ERROR_CODE[:processing_error], + '14' => STANDARD_ERROR_CODE[:invalid_number], + '54' => STANDARD_ERROR_CODE[:expired_card], + } + + def initialize(options={}) + ActiveMerchant.deprecated('IPP gateway is now named Bambora Asia-Pacific') + requires!(options, :username, :password) + super + end + + def purchase(money, payment, options={}) + commit('SubmitSinglePayment') do |xml| + xml.Transaction do + xml.CustRef options[:order_id] + add_amount(xml, money) + xml.TrnType '1' + add_credit_card(xml, payment) + add_credentials(xml) + xml.TrnSource options[:ip] + end + end + end + + def authorize(money, payment, options={}) + commit('SubmitSinglePayment') do |xml| + xml.Transaction do + xml.CustRef options[:order_id] + add_amount(xml, money) + xml.TrnType '2' + add_credit_card(xml, payment) + add_credentials(xml) + xml.TrnSource options[:ip] + end + end + end + + def capture(money, authorization, options={}) + commit('SubmitSingleCapture') do |xml| + xml.Capture do + xml.Receipt authorization + add_amount(xml, money) + add_credentials(xml) + end + end + end + + def refund(money, authorization, options={}) + commit('SubmitSingleRefund') do |xml| + xml.Refund do + xml.Receipt authorization + add_amount(xml, money) + add_credentials(xml) + end + end + end + + def supports_scrubbing? + true + end + + def scrub(transcript) + transcript. + gsub(%r(()[^<]+(<))i, '\1[FILTERED]\2'). + gsub(%r(()[^<]+(<))i, '\1[FILTERED]\2'). + gsub(%r(()[^<]+(<))i, '\1[FILTERED]\2') + end + + private + + def add_credentials(xml) + xml.Security do + xml.UserName @options[:username] + xml.Password @options[:password] + end + end + + def add_amount(xml, money) + xml.Amount amount(money) + end + + def add_credit_card(xml, payment) + xml.CreditCard :Registered => 'False' do + xml.CardNumber payment.number + xml.ExpM format(payment.month, :two_digits) + xml.ExpY format(payment.year, :four_digits) + xml.CVN payment.verification_value + xml.CardHolderName payment.name + end + end + + def parse(body) + element = Nokogiri::XML(body).root.first_element_child.first_element_child + + response = {} + doc = Nokogiri::XML(element) + doc.root.elements.each do |e| + response[e.name.underscore.to_sym] = e.inner_text + end + response + end + + def commit(action, &block) + headers = { + 'Content-Type' => 'text/xml; charset=utf-8', + 'SOAPAction' => "http://www.ippayments.com.au/interface/api/dts/#{action}", + } + response = parse(ssl_post(commit_url, new_submit_xml(action, &block), headers)) + + Response.new( + success_from(response), + message_from(response), + response, + authorization: authorization_from(response), + error_code: error_code_from(response), + test: test? + ) + end + + def new_submit_xml(action) + xml = Builder::XmlMarkup.new(indent: 2) + xml.instruct! + xml.soap :Envelope, 'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance', 'xmlns:xsd' => 'http://www.w3.org/2001/XMLSchema', 'xmlns:soap' => 'http://schemas.xmlsoap.org/soap/envelope/' do + xml.soap :Body do + xml.__send__(action, 'xmlns' => 'http://www.ippayments.com.au/interface/api/dts') do + xml.trnXML do + inner_xml = Builder::XmlMarkup.new(indent: 2) + yield(inner_xml) + xml.cdata!(inner_xml.target!) + end + end + end + end + xml.target! + end + + def commit_url + (test? ? test_url : live_url) + end + + def success_from(response) + (response[:response_code] == '0') + end + + def error_code_from(response) + STANDARD_ERROR_CODE_MAPPING[response[:declined_code]] + end + + def message_from(response) + response[:declined_message] + end + + def authorization_from(response) + response[:receipt] + end + end + end +end diff --git a/lib/active_merchant/billing/gateways/iridium.rb b/lib/active_merchant/billing/gateways/iridium.rb index f0156b3d543..6b6eda3805b 100644 --- a/lib/active_merchant/billing/gateways/iridium.rb +++ b/lib/active_merchant/billing/gateways/iridium.rb @@ -15,7 +15,7 @@ class IridiumGateway < Gateway self.money_format = :cents # The card types supported by the payment gateway - self.supported_cardtypes = [:visa, :master, :american_express, :discover, :maestro, :jcb, :solo, :diners_club] + self.supported_cardtypes = [:visa, :master, :american_express, :discover, :maestro, :jcb, :diners_club] # The homepage URL of the gateway self.homepage_url = 'http://www.iridiumcorp.co.uk/' @@ -24,13 +24,202 @@ class IridiumGateway < Gateway self.display_name = 'Iridium' CURRENCY_CODES = { - "AUD" => '036', - "CAD" => '124', - "EUR" => '978', - "GBP" => '826', - "MXN" => '484', - "NZD" => '554', - "USD" => '840', + 'AED' => '784', + 'AFN' => '971', + 'ALL' => '008', + 'AMD' => '051', + 'ANG' => '532', + 'AOA' => '973', + 'ARS' => '032', + 'AUD' => '036', + 'AWG' => '533', + 'AZN' => '944', + 'BAM' => '977', + 'BBD' => '052', + 'BDT' => '050', + 'BGN' => '975', + 'BHD' => '048', + 'BIF' => '108', + 'BMD' => '060', + 'BND' => '096', + 'BOB' => '068', + 'BOV' => '984', + 'BRL' => '986', + 'BSD' => '044', + 'BTN' => '064', + 'BWP' => '072', + 'BYR' => '974', + 'BZD' => '084', + 'CAD' => '124', + 'CDF' => '976', + 'CHE' => '947', + 'CHF' => '756', + 'CHW' => '948', + 'CLF' => '990', + 'CLP' => '152', + 'CNY' => '156', + 'COP' => '170', + 'COU' => '970', + 'CRC' => '188', + 'CUP' => '192', + 'CVE' => '132', + 'CYP' => '196', + 'CZK' => '203', + 'DJF' => '262', + 'DKK' => '208', + 'DOP' => '214', + 'DZD' => '012', + 'EEK' => '233', + 'EGP' => '818', + 'ERN' => '232', + 'ETB' => '230', + 'EUR' => '978', + 'FJD' => '242', + 'FKP' => '238', + 'GBP' => '826', + 'GEL' => '981', + 'GHS' => '288', + 'GIP' => '292', + 'GMD' => '270', + 'GNF' => '324', + 'GTQ' => '320', + 'GYD' => '328', + 'HKD' => '344', + 'HNL' => '340', + 'HRK' => '191', + 'HTG' => '332', + 'HUF' => '348', + 'IDR' => '360', + 'ILS' => '376', + 'INR' => '356', + 'IQD' => '368', + 'IRR' => '364', + 'ISK' => '352', + 'JMD' => '388', + 'JOD' => '400', + 'JPY' => '392', + 'KES' => '404', + 'KGS' => '417', + 'KHR' => '116', + 'KMF' => '174', + 'KPW' => '408', + 'KRW' => '410', + 'KWD' => '414', + 'KYD' => '136', + 'KZT' => '398', + 'LAK' => '418', + 'LBP' => '422', + 'LKR' => '144', + 'LRD' => '430', + 'LSL' => '426', + 'LTL' => '440', + 'LVL' => '428', + 'LYD' => '434', + 'MAD' => '504', + 'MDL' => '498', + 'MGA' => '969', + 'MKD' => '807', + 'MMK' => '104', + 'MNT' => '496', + 'MOP' => '446', + 'MRO' => '478', + 'MTL' => '470', + 'MUR' => '480', + 'MVR' => '462', + 'MWK' => '454', + 'MXN' => '484', + 'MXV' => '979', + 'MYR' => '458', + 'MZN' => '943', + 'NAD' => '516', + 'NGN' => '566', + 'NIO' => '558', + 'NOK' => '578', + 'NPR' => '524', + 'NZD' => '554', + 'OMR' => '512', + 'PAB' => '590', + 'PEN' => '604', + 'PGK' => '598', + 'PHP' => '608', + 'PKR' => '586', + 'PLN' => '985', + 'PYG' => '600', + 'QAR' => '634', + 'ROL' => '642', + 'RON' => '946', + 'RSD' => '941', + 'RUB' => '643', + 'RWF' => '646', + 'SAR' => '682', + 'SBD' => '090', + 'SCR' => '690', + 'SDG' => '938', + 'SEK' => '752', + 'SGD' => '702', + 'SHP' => '654', + 'SKK' => '703', + 'SLL' => '694', + 'SOS' => '706', + 'SRD' => '968', + 'STD' => '678', + 'SYP' => '760', + 'SZL' => '748', + 'THB' => '764', + 'TJS' => '972', + 'TMM' => '795', + 'TND' => '788', + 'TOP' => '776', + 'TRY' => '949', + 'TTD' => '780', + 'TWD' => '901', + 'TZS' => '834', + 'UAH' => '980', + 'UGX' => '800', + 'USD' => '840', + 'USN' => '997', + 'USS' => '998', + 'UYU' => '858', + 'UZS' => '860', + 'VEB' => '862', + 'VND' => '704', + 'VUV' => '548', + 'WST' => '882', + 'XAF' => '950', + 'XAG' => '961', + 'XAU' => '959', + 'XBA' => '955', + 'XBB' => '956', + 'XBC' => '957', + 'XBD' => '958', + 'XCD' => '951', + 'XDR' => '960', + 'XOF' => '952', + 'XPD' => '964', + 'XPF' => '953', + 'XPT' => '962', + 'XTS' => '963', + 'XXX' => '999', + 'YER' => '886', + 'ZAR' => '710', + 'ZMK' => '894', + 'ZWD' => '716', + } + + AVS_CODE = { + 'PASSED' => 'Y', + 'FAILED' => 'N', + 'PARTIAL' => 'X', + 'NOT_CHECKED' => 'X', + 'UNKNOWN' => 'X' + } + + CVV_CODE = { + 'PASSED' => 'M', + 'FAILED' => 'N', + 'PARTIAL' => 'I', + 'NOT_CHECKED' => 'P', + 'UNKNOWN' => 'U' } def initialize(options = {}) @@ -63,7 +252,7 @@ def capture(money, authorization, options = {}) end def credit(money, authorization, options={}) - deprecated CREDIT_DEPRECATION_MESSAGE + ActiveMerchant.deprecated CREDIT_DEPRECATION_MESSAGE refund(money, authorization, options) end @@ -75,10 +264,21 @@ def void(authorization, options={}) commit(build_reference_request('VOID', nil, authorization, options), options) end + def supports_scrubbing + true + end + + def scrub(transcript) + transcript. + gsub(%r((Authorization: Basic )\w+), '\1[FILTERED]'). + gsub(%r(()\d+()), '\1[FILTERED]\2'). + gsub(%r(()\d+()), '\1[FILTERED]\2') + end + private def build_purchase_request(type, money, creditcard, options) - options.merge!(:action => 'CardDetailsTransaction') + options[:action] = 'CardDetailsTransaction' build_request(options) do |xml| add_purchase_data(xml, type, money, options) add_creditcard(xml, creditcard) @@ -87,8 +287,8 @@ def build_purchase_request(type, money, creditcard, options) end def build_reference_request(type, money, authorization, options) - options.merge!(:action => 'CrossReferenceTransaction') - order_id, cross_reference, auth_id = authorization.split(";") + options[:action] = 'CrossReferenceTransaction' + order_id, cross_reference, _ = authorization.split(';') build_request(options) do |xml| if money details = {'CurrencyCode' => currency_code(options[:currency] || default_currency), 'Amount' => amount(money)} @@ -110,7 +310,7 @@ def build_request(options) 'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance', 'xmlns:xsd' => 'http://www.w3.org/2001/XMLSchema'} do xml.tag! 'soap:Body' do - xml.tag! options[:action], {'xmlns' => "https://www.thepaymentgateway.net/"} do + xml.tag! options[:action], {'xmlns' => 'https://www.thepaymentgateway.net/'} do xml.tag! 'PaymentMessage' do add_merchant_data(xml, options) yield(xml) @@ -157,7 +357,7 @@ def add_customerdetails(xml, creditcard, address, options, shipTo = false) end xml.tag! 'EmailAddress', options[:email] - xml.tag! 'CustomerIPAddress', options[:ip] || "127.0.0.1" + xml.tag! 'CustomerIPAddress', options[:ip] || '127.0.0.1' end end @@ -166,34 +366,40 @@ def add_creditcard(xml, creditcard) xml.tag! 'CardName', creditcard.name xml.tag! 'CV2', creditcard.verification_value if creditcard.verification_value xml.tag! 'CardNumber', creditcard.number - xml.tag! 'ExpiryDate', { 'Month' => creditcard.month.to_s.rjust(2, "0"), 'Year' => creditcard.year.to_s[/\d\d$/] } + xml.tag! 'ExpiryDate', { 'Month' => creditcard.month.to_s.rjust(2, '0'), 'Year' => creditcard.year.to_s[/\d\d$/] } end end def add_merchant_data(xml, options) - xml.tag! 'MerchantAuthentication', {"MerchantID" => @options[:login], "Password" => @options[:password]} + xml.tag! 'MerchantAuthentication', {'MerchantID' => @options[:login], 'Password' => @options[:password]} end def commit(request, options) requires!(options, :action) response = parse(ssl_post(test? ? self.test_url : self.live_url, request, - {"SOAPAction" => "https://www.thepaymentgateway.net/#{options[:action]}", - "Content-Type" => "text/xml; charset=utf-8" })) + {'SOAPAction' => 'https://www.thepaymentgateway.net/' + options[:action], + 'Content-Type' => 'text/xml; charset=utf-8' })) - success = response[:transaction_result][:status_code] == "0" + success = response[:transaction_result][:status_code] == '0' message = response[:transaction_result][:message] - authorization = success ? [ options[:order_id], response[:transaction_output_data][:cross_reference], response[:transaction_output_data][:auth_code] ].compact.join(";") : nil + authorization = success ? [ options[:order_id], response[:transaction_output_data][:cross_reference], response[:transaction_output_data][:auth_code] ].compact.join(';') : nil Response.new(success, message, response, :test => test?, - :authorization => authorization) + :authorization => authorization, + :avs_result => { + :street_match => AVS_CODE[ response[:transaction_output_data][:address_numeric_check_result] ], + :postal_match => AVS_CODE[ response[:transaction_output_data][:post_code_check_result] ], + }, + :cvv_result => CVV_CODE[ response[:transaction_output_data][:cv2_check_result] ] + ) end def parse(xml) reply = {} xml = REXML::Document.new(xml) - if (root = REXML::XPath.first(xml, "//CardDetailsTransactionResponse")) or - (root = REXML::XPath.first(xml, "//CrossReferenceTransactionResponse")) + if (root = REXML::XPath.first(xml, '//CardDetailsTransactionResponse')) or + (root = REXML::XPath.first(xml, '//CrossReferenceTransactionResponse')) root.elements.to_a.each do |node| case node.name when 'Message' @@ -202,7 +408,7 @@ def parse(xml) parse_element(reply, node) end end - elsif root = REXML::XPath.first(xml, "//soap:Fault") + elsif root = REXML::XPath.first(xml, '//soap:Fault') parse_element(reply, root) reply[:message] = "#{reply[:faultcode]}: #{reply[:faultstring]}" end @@ -211,41 +417,41 @@ def parse(xml) def parse_element(reply, node) case node.name - when "CrossReferenceTransactionResult" + when 'CrossReferenceTransactionResult' reply[:transaction_result] = {} - node.attributes.each do |a,b| + node.attributes.each do |a, b| reply[:transaction_result][a.underscore.to_sym] = b end - node.elements.each{|e| parse_element(reply[:transaction_result], e) } if node.has_elements? + node.elements.each { |e| parse_element(reply[:transaction_result], e) } if node.has_elements? - when "CardDetailsTransactionResult" + when 'CardDetailsTransactionResult' reply[:transaction_result] = {} - node.attributes.each do |a,b| + node.attributes.each do |a, b| reply[:transaction_result][a.underscore.to_sym] = b end - node.elements.each{|e| parse_element(reply[:transaction_result], e) } if node.has_elements? + node.elements.each { |e| parse_element(reply[:transaction_result], e) } if node.has_elements? - when "TransactionOutputData" + when 'TransactionOutputData' reply[:transaction_output_data] = {} - node.attributes.each{|a,b| reply[:transaction_output_data][a.underscore.to_sym] = b } - node.elements.each{|e| parse_element(reply[:transaction_output_data], e) } if node.has_elements? - when "CustomVariables" + node.attributes.each { |a, b| reply[:transaction_output_data][a.underscore.to_sym] = b } + node.elements.each { |e| parse_element(reply[:transaction_output_data], e) } if node.has_elements? + when 'CustomVariables' reply[:custom_variables] = {} - node.attributes.each{|a,b| reply[:custom_variables][a.underscore.to_sym] = b } - node.elements.each{|e| parse_element(reply[:custom_variables], e) } if node.has_elements? - when "GatewayEntryPoints" + node.attributes.each { |a, b| reply[:custom_variables][a.underscore.to_sym] = b } + node.elements.each { |e| parse_element(reply[:custom_variables], e) } if node.has_elements? + when 'GatewayEntryPoints' reply[:gateway_entry_points] = {} - node.attributes.each{|a,b| reply[:gateway_entry_points][a.underscore.to_sym] = b } - node.elements.each{|e| parse_element(reply[:gateway_entry_points], e) } if node.has_elements? + node.attributes.each { |a, b| reply[:gateway_entry_points][a.underscore.to_sym] = b } + node.elements.each { |e| parse_element(reply[:gateway_entry_points], e) } if node.has_elements? else k = node.name.underscore.to_sym if node.has_elements? reply[k] = {} - node.elements.each{|e| parse_element(reply[k], e) } + node.elements.each { |e| parse_element(reply[k], e) } else if node.has_attributes? reply[k] = {} - node.attributes.each{|a,b| reply[k][a.underscore.to_sym] = b } + node.attributes.each { |a, b| reply[k][a.underscore.to_sym] = b } else reply[k] = node.text end diff --git a/lib/active_merchant/billing/gateways/itransact.rb b/lib/active_merchant/billing/gateways/itransact.rb index 0d96f7556e6..8bac0734e0a 100644 --- a/lib/active_merchant/billing/gateways/itransact.rb +++ b/lib/active_merchant/billing/gateways/itransact.rb @@ -302,7 +302,7 @@ def add_invoice(xml, money, options) xml.AuthCode options[:force] if options[:force] if options[:order_items].blank? xml.Total(amount(money)) unless(money.nil? || money < 0.01) - xml.Description(options[:description]) unless( options[:description].blank?) + xml.Description(options[:description]) unless(options[:description].blank?) else xml.OrderItems { options[:order_items].each do |item| @@ -336,7 +336,7 @@ def add_creditcard(xml, creditcard) xml.AccountInfo { xml.CardAccount { xml.AccountNumber(creditcard.number.to_s) - xml.ExpirationMonth(creditcard.month.to_s.rjust(2,'0')) + xml.ExpirationMonth(creditcard.month.to_s.rjust(2, '0')) xml.ExpirationYear(creditcard.year.to_s) xml.CVVNumber(creditcard.verification_value.to_s) unless creditcard.verification_value.blank? } @@ -372,7 +372,7 @@ def add_transaction_control(xml, options) def add_vendor_data(xml, options) return if options[:vendor_data].blank? xml.VendorData { - options[:vendor_data].each do |k,v| + options[:vendor_data].each do |k, v| xml.Element { xml.Name(k) xml.Key(v) @@ -424,7 +424,7 @@ def parse(raw_xml) def successful?(response) # Turns out the PaymentClearing gateway is not consistent... - response[:status].downcase =='ok' + response[:status].casecmp('ok').zero? end def test_mode?(response) @@ -445,4 +445,3 @@ def sign_payload(payload) end end end - diff --git a/lib/active_merchant/billing/gateways/iveri.rb b/lib/active_merchant/billing/gateways/iveri.rb new file mode 100644 index 00000000000..4a8d96e4752 --- /dev/null +++ b/lib/active_merchant/billing/gateways/iveri.rb @@ -0,0 +1,251 @@ +require 'nokogiri' + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class IveriGateway < Gateway + self.live_url = self.test_url = 'https://portal.nedsecure.co.za/iVeriWebService/Service.asmx' + + self.supported_countries = ['US', 'ZA', 'GB'] + self.default_currency = 'ZAR' + self.money_format = :cents + self.supported_cardtypes = [:visa, :master, :american_express] + + self.homepage_url = 'http://www.iveri.com' + self.display_name = 'iVeri' + + def initialize(options={}) + requires!(options, :app_id, :cert_id) + super + end + + def purchase(money, payment_method, options={}) + post = build_vxml_request('Debit', options) do |xml| + add_auth_purchase_params(xml, money, payment_method, options) + end + + commit(post) + end + + def authorize(money, payment_method, options={}) + post = build_vxml_request('Authorisation', options) do |xml| + add_auth_purchase_params(xml, money, payment_method, options) + end + + commit(post) + end + + def capture(money, authorization, options={}) + post = build_vxml_request('Debit', options) do |xml| + add_authorization(xml, authorization, options) + end + + commit(post) + end + + def refund(money, authorization, options={}) + post = build_vxml_request('Credit', options) do |xml| + add_amount(xml, money, options) + add_authorization(xml, authorization, options) + end + + commit(post) + end + + def void(authorization, options={}) + post = build_vxml_request('Void', options) do |xml| + add_authorization(xml, authorization, options) + end + + commit(post) + end + + def verify(credit_card, options={}) + authorize(0, credit_card, options) + end + + def verify_credentials + void = void('', options) + return true if void.message == 'Missing OriginalMerchantTrace' + false + end + + def supports_scrubbing? + true + end + + def scrub(transcript) + transcript. + gsub(%r((CertificateID=\\\")[^\\]*), '\1[FILTERED]'). + gsub(%r((<PAN>)[^&]*), '\1[FILTERED]'). + gsub(%r((<CardSecurityCode>)[^&]*), '\1[FILTERED]') + end + + private + + def build_xml_envelope(vxml) + builder = Nokogiri::XML::Builder.new(:encoding => 'UTF-8') do |xml| + xml[:soap].Envelope 'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance', 'xmlns:xsd' => 'http://www.w3.org/2001/XMLSchema', 'xmlns:soap' => 'http://schemas.xmlsoap.org/soap/envelope/' do + xml[:soap].Body do + xml.Execute 'xmlns' => 'http://iveri.com/' do + xml.validateRequest 'true' + xml.protocol 'V_XML' + xml.protocolVersion '2.0' + xml.request vxml + end + end + end + end + + builder.to_xml + end + + def build_vxml_request(action, options) + builder = Nokogiri::XML::Builder.new do |xml| + xml.V_XML('Version' => '2.0', 'CertificateID' => @options[:cert_id], 'Direction' => 'Request') do + xml.Transaction('ApplicationID' => @options[:app_id], 'Command' => action, 'Mode' => mode) do + yield(xml) + end + end + end + + builder.doc.root.to_xml + end + + def add_auth_purchase_params(post, money, payment_method, options) + add_card_holder_authentication(post, options) + add_amount(post, money, options) + add_electronic_commerce_indicator(post, options) + add_payment_method(post, payment_method, options) + end + + def add_amount(post, money, options) + post.Amount(amount(money)) + post.Currency(options[:currency] || default_currency) + end + + def add_electronic_commerce_indicator(post, options) + post.ElectronicCommerceIndicator(options[:eci]) if options[:eci] + end + + def add_authorization(post, authorization, options) + post.MerchantReference(split_auth(authorization)[2]) + post.TransactionIndex(split_auth(authorization)[1]) + post.OriginalRequestID(split_auth(authorization)[0]) + end + + def add_payment_method(post, payment_method, options) + post.ExpiryDate("#{format(payment_method.month, :two_digits)}#{payment_method.year}") + add_new_reference(post, options) + post.CardSecurityCode(payment_method.verification_value) + post.PAN(payment_method.number) + end + + def add_new_reference(post, options) + post.MerchantReference(options[:order_id] || generate_unique_id) + end + + def add_card_holder_authentication(post, options) + post.CardHolderAuthenticationID(options[:xid]) if options[:xid] + post.CardHolderAuthenticationData(options[:cavv]) if options[:cavv] + end + + def commit(post) + raw_response = begin + ssl_post(live_url, build_xml_envelope(post), headers(post)) + rescue ActiveMerchant::ResponseError => e + e.response.body + end + + parsed = parse(raw_response) + succeeded = success_from(parsed) + + Response.new( + succeeded, + message_from(parsed, succeeded), + parsed, + authorization: authorization_from(parsed), + error_code: error_code_from(parsed, succeeded), + test: test? + ) + end + + def mode + test? ? 'Test' : 'Live' + end + + def headers(post) + { + 'Content-Type' => 'text/xml; charset=utf-8', + 'Content-Length' => post.size.to_s, + 'SOAPAction' => 'http://iveri.com/Execute' + } + end + + def parse(body) + parsed = {} + + vxml = Nokogiri::XML(body).remove_namespaces!.xpath('//Envelope/Body/ExecuteResponse/ExecuteResult').inner_text + doc = Nokogiri::XML(vxml) + doc.xpath('*').each do |node| + if node.elements.empty? + parsed[underscore(node.name)] = node.text + else + node.elements.each do |childnode| + parse_element(parsed, childnode) + end + end + end + parsed + end + + def parse_element(parsed, node) + if !node.attributes.empty? + node.attributes.each do |a| + parsed[underscore(node.name)+ '_' + underscore(a[1].name)] = a[1].value + end + end + + if !node.elements.empty? + node.elements.each { |e| parse_element(parsed, e) } + else + parsed[underscore(node.name)] = node.text + end + end + + def success_from(response) + response['result_status'] == '0' + end + + def message_from(response, succeeded) + if succeeded + 'Succeeded' + else + response['result_description'] || response['result_acquirer_description'] + end + end + + def authorization_from(response) + "#{response['transaction_request_id']}|#{response['transaction_index']}|#{response['merchant_reference']}" + end + + def split_auth(authorization) + request_id, transaction_index, merchant_reference = authorization.to_s.split('|') + [request_id, transaction_index, merchant_reference] + end + + def error_code_from(response, succeeded) + unless succeeded + response['result_code'] + end + end + + def underscore(camel_cased_word) + camel_cased_word.to_s.gsub(/::/, '/'). + gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2'). + gsub(/([a-z\d])([A-Z])/, '\1_\2'). + tr('-', '_'). + downcase + end + end + end +end diff --git a/lib/active_merchant/billing/gateways/jetpay.rb b/lib/active_merchant/billing/gateways/jetpay.rb index 7a7002d0b04..aaa955dd24a 100644 --- a/lib/active_merchant/billing/gateways/jetpay.rb +++ b/lib/active_merchant/billing/gateways/jetpay.rb @@ -1,11 +1,14 @@ module ActiveMerchant #:nodoc: module Billing #:nodoc: class JetpayGateway < Gateway + class_attribute :live_us_url, :live_ca_url + self.test_url = 'https://test1.jetpay.com/jetpay' - self.live_url = 'https://gateway17.jetpay.com/jetpay' + self.live_us_url = 'https://gateway17.jetpay.com/jetpay' + self.live_ca_url = 'https://gateway17.jetpay.com/canada-bb' # The countries the gateway supports merchants from as 2 digit ISO country codes - self.supported_countries = ['US'] + self.supported_countries = ['US', 'CA'] # The card types supported by the payment gateway self.supported_cardtypes = [:visa, :master, :american_express, :discover] @@ -20,46 +23,132 @@ class JetpayGateway < Gateway self.money_format = :cents ACTION_CODE_MESSAGES = { - "001" => "Refer to card issuer.", - "002" => "Refer to card issuer, special condition.", - "003" => "Pick up card.", - "200" => "Deny - Pick up card.", - "005" => "Do not honor.", - "100" => "Deny.", - "006" => "Error.", - "181" => "Format error.", - "007" => "Pickup card, special condition.", - "104" => "Deny - New card issued.", - "110" => "Invalid amount.", - "014" => "Invalid account number (no such number).", - "111" => "Invalid account.", - "015" => "No such issuer.", - "103" => "Deny - Invalid manual Entry 4DBC.", - "182" => "Please wait.", - "109" => "Invalid merchant.", - "041" => "Pick up card (lost card).", - "043" => "Pick up card (stolen card).", - "051" => "Insufficient funds.", - "052" => "No checking account.", - "105" => "Deny - Account Cancelled.", - "054" => "Expired Card.", - "101" => "Expired Card.", - "183" => "Invalid currency code.", - "057" => "Transaction not permitted to cardholder.", - "115" => "Service not permitted.", - "062" => "Restricted card.", - "189" => "Deny - Cancelled or Closed Merchant/SE.", - "188" => "Deny - Expiration date required.", - "125" => "Invalid effective date.", - "122" => "Invalid card (CID) security code.", - "400" => "Reversal accepted.", - "992" => "DECLINE/TIMEOUT.", - "107" => "Please Call Issuer.", - "025" => "Transaction Not Found.", - "981" => "AVS Error.", - "913" => "Invalid Card Type.", - "996" => "Terminal ID Not Found.", - nil => "No response returned (missing credentials?)." + '000' => 'Approved.', + '001' => 'Refer to card issuer.', + '002' => 'Refer to card issuer, special condition.', + '003' => 'Invalid merchant or service provider.', + '004' => 'Pick up card.', + '005' => 'Do not honor.', + '006' => 'Error.', + '007' => 'Pick up card, special condition.', + '008' => 'Honor with ID (Show ID).', + '010' => 'Partial approval.', + '011' => 'VIP approval.', + '012' => 'Invalid transaction.', + '013' => 'Invalid amount or exceeds maximum for card program.', + '014' => 'Invalid account number (no such number).', + '015' => 'No such issuer.', + '019' => 'Re-enter Transaction.', + '021' => 'No action taken (unable to back out prior transaction).', + '025' => 'Transaction Not Found.', + '027' => 'File update field edit error.', + '028' => 'File is temporarily unavailable.', + '030' => 'Format error.', + '039' => 'No credit account.', + '041' => 'Pick up card (lost card).', + '043' => 'Pick up card (stolen card).', + '051' => 'Insufficient funds.', + '052' => 'No checking account.', + '053' => 'No savings account.', + '054' => 'Expired Card.', + '055' => 'Incorrect PIN.', + '057' => 'Transaction not permitted to cardholder.', + '058' => 'Transaction not allowed at terminal.', + '061' => 'Exceeds withdrawal limit.', + '062' => 'Restricted card (eg, Country Exclusion).', + '063' => 'Security violation.', + '065' => 'Activity count limit exceeded.', + '068' => 'Response late.', + '070' => 'Contact card issuer.', + '071' => 'PIN not changed.', + '075' => 'Allowable number of PIN-entry tries exceeded.', + '076' => 'Unable to locate previous message (no matching retrieval reference number).', + '077' => 'Repeat or reversal data are inconsistent with original message.', + '078' => 'Blocked (first use), or non-existent account.', + '079' => 'Key exchange validation failed.', + '080' => 'Credit issuer unavailable or invalid date.', + '081' => 'PIN cryptographic error found.', + '082' => 'Negative online CVV results.', + '084' => 'Invalid auth life cycle.', + '085' => 'No reason to decline - CVV or AVS approved.', + '086' => 'Cannot verify PIN.', + '087' => 'Cashback not allowed.', + '089' => 'Issuer Down.', + '091' => 'Issuer Down.', + '092' => 'Unable to route transaction.', + '093' => 'Transaction cannot be completed - violation of law.', + '094' => 'Duplicate transmission.', + '096' => 'System error.', + '100' => 'Deny.', + '101' => 'Expired Card.', + '103' => 'Deny - Invalid manual Entry 4DBC.', + '104' => 'Deny - New card issued.', + '105' => 'Deny - Account Cancelled.', + '106' => 'Exceeded PIN Attempts.', + '107' => 'Please Call Issuer.', + '109' => 'Invalid merchant.', + '110' => 'Invalid amount.', + '111' => 'Invalid account.', + '115' => 'Service not permitted.', + '122' => 'Invalid card (CID) security code.', + '125' => 'Invalid effective date.', + '181' => 'Format error.', + '182' => 'Please wait.', + '183' => 'Invalid currency code.', + '187' => 'Deny - new card issued.', + '188' => 'Deny - Expiration date required.', + '189' => 'Deny - Cancelled or Closed Merchant/SE.', + '200' => 'Deny - Pick up card.', + '400' => 'Reversal accepted.', + '601' => 'Reject - EMV Chip Declined Transaction.', + '602' => 'Reject - Suspected Fraud.', + '603' => 'Reject - Communications Error.', + '604' => 'Reject - Insufficient Approval.', + '750' => 'Velocity Check Fail.', + '899' => 'Misc Decline.', + '900' => 'Invalid Message Type.', + '901' => 'Invalid Merchant ID.', + '903' => 'Debit not supported.', + '904' => 'Private label not supported.', + '905' => 'Invalid card type.', + '906' => 'Unit not active.', + '908' => 'Manual card entry invalid.', + '909' => 'Invalid track information.', + '911' => 'Master merchant not found.', + '912' => 'Invalid card format.', + '913' => 'Invalid card type.', + '914' => 'Invalid card length.', + '917' => 'Expired card.', + '919' => 'Invalid entry type.', + '920' => 'Invalid amount.', + '921' => 'Invalid messge format.', + '923' => 'Invalid ABA.', + '924' => 'Invalid DDA.', + '925' => 'Invalid TID.', + '926' => 'Invalid Password.', + '930' => 'Invalid zipcode.', + '931' => 'Invalid Address.', + '932' => 'Invalid ZIP and Address.', + '933' => 'Invalid CVV2.', + '934' => 'Program Not Allowed.', + '940' => 'Record Not Found.', + '941' => 'Merchant ID error.', + '942' => 'Refund Not Allowed.', + '943' => 'Refund denied.', + '955' => 'Invalid PIN block.', + '956' => 'Invalid KSN.', + '958' => 'Bad Status.', + '959' => 'Seek Record limit exceeded.', + '962' => 'Invalid PIN key (Unknown KSN).', + '981' => 'Invalid AVS.', + '987' => 'Issuer Unavailable.', + '988' => 'System error SD.', + '989' => 'Database Error.', + '992' => 'Transaction Timeout.', + '996' => 'Bad Terminal ID.', + '997' => 'Message rejected by association.', + '999' => 'Communication failure', + nil => 'No response returned (missing credentials?).' } def initialize(options = {}) @@ -76,39 +165,57 @@ def authorize(money, credit_card, options = {}) end def capture(money, reference, options = {}) - commit(money, build_capture_request('CAPT', reference.split(";").first)) + split_authorization = reference.split(';') + transaction_id = split_authorization[0] + token = split_authorization[3] + commit(money, build_capture_request(transaction_id, money, options), token) end def void(reference, options = {}) - transaction_id, approval, amount = reference.split(";") - commit(amount.to_i, build_void_request(amount.to_i, transaction_id, approval)) + transaction_id, approval, amount, token = reference.split(';') + commit(amount.to_i, build_void_request(amount.to_i, transaction_id, approval, token, options), token) end def credit(money, transaction_id_or_card, options = {}) if transaction_id_or_card.is_a?(String) - deprecated CREDIT_DEPRECATION_MESSAGE + ActiveMerchant.deprecated CREDIT_DEPRECATION_MESSAGE refund(money, transaction_id_or_card, options) else - commit(money, build_credit_request('CREDIT', money, nil, transaction_id_or_card)) + commit(money, build_credit_request('CREDIT', money, nil, transaction_id_or_card, nil, options)) end end def refund(money, reference, options = {}) - transaction_id = reference.split(";").first + split_authorization = reference.split(';') + transaction_id = split_authorization[0] + token = split_authorization[3] credit_card = options[:credit_card] - commit(money, build_credit_request('CREDIT', money, transaction_id, credit_card)) + commit(money, build_credit_request('CREDIT', money, transaction_id, credit_card, token, options)) + end + + def supports_scrubbing + true end + def scrub(transcript) + transcript. + gsub(%r((Authorization: Basic )\w+), '\1[FILTERED]'). + gsub(%r((>)\d+()), '\1[FILTERED]\2'). + gsub(%r(()\d+()), '\1[FILTERED]\2') + end private - def build_xml_request(transaction_type, transaction_id = nil, &block) + def build_xml_request(transaction_type, options = {}, transaction_id = nil, &block) xml = Builder::XmlMarkup.new xml.tag! 'JetPay' do # The basic values needed for any request xml.tag! 'TerminalID', @options[:login] xml.tag! 'TransactionType', transaction_type xml.tag! 'TransactionID', transaction_id.nil? ? generate_unique_id.slice(0, 18) : transaction_id + if options && options[:origin] + xml.tag! 'Origin', options[:origin] + end if block_given? yield xml @@ -119,11 +226,12 @@ def build_xml_request(transaction_type, transaction_id = nil, &block) end def build_sale_request(money, credit_card, options) - build_xml_request('SALE') do |xml| + build_xml_request('SALE', options) do |xml| add_credit_card(xml, credit_card) add_addresses(xml, options) add_customer_data(xml, options) add_invoice_data(xml, options) + add_user_defined_fields(xml, options) xml.tag! 'TotalAmount', amount(money) xml.target! @@ -136,49 +244,63 @@ def build_authonly_request(money, credit_card, options) add_addresses(xml, options) add_customer_data(xml, options) add_invoice_data(xml, options) + add_user_defined_fields(xml, options) xml.tag! 'TotalAmount', amount(money) xml.target! end end - def build_capture_request(transaction_type, transaction_id) - build_xml_request(transaction_type, transaction_id) + def build_capture_request(transaction_id, money, options) + build_xml_request('CAPT', options, transaction_id) do |xml| + xml.tag! 'TotalAmount', amount(money) + add_user_defined_fields(xml, options) + end end - def build_void_request(money, transaction_id, approval) - build_xml_request('VOID', transaction_id) do |xml| + def build_void_request(money, transaction_id, approval, token, options) + build_xml_request('VOID', options, transaction_id) do |xml| xml.tag! 'Approval', approval xml.tag! 'TotalAmount', amount(money) - + xml.tag! 'Token', token if token xml.target! end end # `transaction_id` may be nil for unlinked credit transactions. - def build_credit_request(transaction_type, money, transaction_id, card) - build_xml_request(transaction_type, transaction_id) do |xml| + def build_credit_request(transaction_type, money, transaction_id, card, token, options) + build_xml_request(transaction_type, options, transaction_id) do |xml| add_credit_card(xml, card) if card + add_invoice_data(xml, options) + add_addresses(xml, options) + add_customer_data(xml, options) + add_user_defined_fields(xml, options) xml.tag! 'TotalAmount', amount(money) + xml.tag! 'Token', token if token xml.target! end end - def commit(money, request) - response = parse(ssl_post(test? ? self.test_url : self.live_url, request)) + def commit(money, request, token = nil) + response = parse(ssl_post(url, request)) success = success?(response) Response.new(success, success ? 'APPROVED' : message_from(response), response, :test => test?, - :authorization => authorization_from(response, money), + :authorization => authorization_from(response, money, token), :avs_result => { :code => response[:avs] }, :cvv_result => response[:cvv2] ) end + def url + live_url = @options[:region] == 'CA' ? live_ca_url : live_us_url + test? ? test_url : live_url + end + def parse(body) return {} if body.blank? @@ -193,7 +315,7 @@ def parse(body) def parse_element(response, node) if node.has_elements? - node.elements.each{|element| parse_element(response, element) } + node.elements.each { |element| parse_element(response, element) } else response[node.name.underscore.to_sym] = node.text end @@ -204,25 +326,25 @@ def format_exp(value) end def success?(response) - response[:action_code] == "000" + response[:action_code] == '000' end def message_from(response) ACTION_CODE_MESSAGES[response[:action_code]] end - def authorization_from(response, money) + def authorization_from(response, money, previous_token) original_amount = amount(money) if money - [ response[:transaction_id], response[:approval], original_amount ].join(";") + [ response[:transaction_id], response[:approval], original_amount, (response[:token] || previous_token)].join(';') end def add_credit_card(xml, credit_card) - xml.tag! 'CardNum', credit_card.number + xml.tag! 'CardNum', credit_card.number, 'Tokenize' => true xml.tag! 'CardExpMonth', format_exp(credit_card.month) xml.tag! 'CardExpYear', format_exp(credit_card.year) if credit_card.first_name || credit_card.last_name - xml.tag! 'CardName', [credit_card.first_name,credit_card.last_name].compact.join(' ') + xml.tag! 'CardName', [credit_card.first_name, credit_card.last_name].compact.join(' ') end unless credit_card.verification_value.nil? || (credit_card.verification_value.length == 0) @@ -232,7 +354,7 @@ def add_credit_card(xml, credit_card) def add_addresses(xml, options) if billing_address = options[:billing_address] || options[:address] - xml.tag! 'BillingAddress', [billing_address[:address1], billing_address[:address2]].compact.join(" ") + xml.tag! 'BillingAddress', [billing_address[:address1], billing_address[:address2]].compact.join(' ') xml.tag! 'BillingCity', billing_address[:city] xml.tag! 'BillingStateProv', billing_address[:state] xml.tag! 'BillingPostalCode', billing_address[:zip] @@ -245,7 +367,7 @@ def add_addresses(xml, options) xml.tag! 'ShippingName', shipping_address[:name] xml.tag! 'ShippingAddr' do - xml.tag! 'Address', [shipping_address[:address1], shipping_address[:address2]].compact.join(" ") + xml.tag! 'Address', [shipping_address[:address1], shipping_address[:address2]].compact.join(' ') xml.tag! 'City', shipping_address[:city] xml.tag! 'StateProv', shipping_address[:state] xml.tag! 'PostalCode', shipping_address[:zip] @@ -265,11 +387,16 @@ def add_invoice_data(xml, options) xml.tag! 'TaxAmount', amount(options[:tax]) if options[:tax] end + def add_user_defined_fields(xml, options) + xml.tag! 'UDField1', options[:ud_field_1] if options[:ud_field_1] + xml.tag! 'UDField2', options[:ud_field_2] if options[:ud_field_2] + xml.tag! 'UDField3', options[:ud_field_3] if options[:ud_field_3] + end + def lookup_country_code(code) country = Country.find(code) rescue nil - country && country.code(:alpha3) + country&.code(:alpha3) end end end end - diff --git a/lib/active_merchant/billing/gateways/jetpay_v2.rb b/lib/active_merchant/billing/gateways/jetpay_v2.rb new file mode 100644 index 00000000000..515bba8fc84 --- /dev/null +++ b/lib/active_merchant/billing/gateways/jetpay_v2.rb @@ -0,0 +1,437 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class JetpayV2Gateway < Gateway + self.test_url = 'https://test1.jetpay.com/jetpay' + self.live_url = 'https://gateway20.jetpay.com/jetpay' + + self.money_format = :cents + self.default_currency = 'USD' + self.supported_countries = ['US', 'CA'] + self.supported_cardtypes = [:visa, :master, :american_express, :discover] + + self.homepage_url = 'http://www.jetpay.com' + self.display_name = 'JetPay' + + API_VERSION = '2.2' + + ACTION_CODE_MESSAGES = { + '000' => 'Approved.', + '001' => 'Refer to card issuer.', + '002' => 'Refer to card issuer, special condition.', + '003' => 'Invalid merchant or service provider.', + '004' => 'Pick up card.', + '005' => 'Do not honor.', + '006' => 'Error.', + '007' => 'Pick up card, special condition.', + '008' => 'Honor with ID (Show ID).', + '010' => 'Partial approval.', + '011' => 'VIP approval.', + '012' => 'Invalid transaction.', + '013' => 'Invalid amount or exceeds maximum for card program.', + '014' => 'Invalid account number (no such number).', + '015' => 'No such issuer.', + '019' => 'Re-enter Transaction.', + '021' => 'No action taken (unable to back out prior transaction).', + '025' => 'Transaction Not Found.', + '027' => 'File update field edit error.', + '028' => 'File is temporarily unavailable.', + '030' => 'Format error.', + '039' => 'No credit account.', + '041' => 'Pick up card (lost card).', + '043' => 'Pick up card (stolen card).', + '051' => 'Insufficient funds.', + '052' => 'No checking account.', + '053' => 'No savings account.', + '054' => 'Expired Card.', + '055' => 'Incorrect PIN.', + '057' => 'Transaction not permitted to cardholder.', + '058' => 'Transaction not allowed at terminal.', + '061' => 'Exceeds withdrawal limit.', + '062' => 'Restricted card (eg, Country Exclusion).', + '063' => 'Security violation.', + '065' => 'Activity count limit exceeded.', + '068' => 'Response late.', + '070' => 'Contact card issuer.', + '071' => 'PIN not changed.', + '075' => 'Allowable number of PIN-entry tries exceeded.', + '076' => 'Unable to locate previous message (no matching retrieval reference number).', + '077' => 'Repeat or reversal data are inconsistent with original message.', + '078' => 'Blocked (first use), or non-existent account.', + '079' => 'Key exchange validation failed.', + '080' => 'Credit issuer unavailable or invalid date.', + '081' => 'PIN cryptographic error found.', + '082' => 'Negative online CVV results.', + '084' => 'Invalid auth life cycle.', + '085' => 'No reason to decline - CVV or AVS approved.', + '086' => 'Cannot verify PIN.', + '087' => 'Cashback not allowed.', + '089' => 'Issuer Down.', + '091' => 'Issuer Down.', + '092' => 'Unable to route transaction.', + '093' => 'Transaction cannot be completed - violation of law.', + '094' => 'Duplicate transmission.', + '096' => 'System error.', + '100' => 'Deny.', + '101' => 'Expired Card.', + '103' => 'Deny - Invalid manual Entry 4DBC.', + '104' => 'Deny - New card issued.', + '105' => 'Deny - Account Cancelled.', + '106' => 'Exceeded PIN Attempts.', + '107' => 'Please Call Issuer.', + '109' => 'Invalid merchant.', + '110' => 'Invalid amount.', + '111' => 'Invalid account.', + '115' => 'Service not permitted.', + '117' => 'Invalid PIN.', + '119' => 'Card member not enrolled.', + '122' => 'Invalid card (CID) security code.', + '125' => 'Invalid effective date.', + '181' => 'Format error.', + '182' => 'Please wait.', + '183' => 'Invalid currency code.', + '187' => 'Deny - new card issued.', + '188' => 'Deny - Expiration date required.', + '189' => 'Deny - Cancelled or Closed Merchant/SE.', + '200' => 'Deny - Pick up card.', + '400' => 'Reversal accepted.', + '601' => 'Reject - EMV Chip Declined Transaction.', + '602' => 'Reject - Suspected Fraud.', + '603' => 'Reject - Communications Error.', + '604' => 'Reject - Insufficient Approval.', + '750' => 'Velocity Check Fail.', + '899' => 'Misc Decline.', + '900' => 'Invalid Message Type.', + '901' => 'Invalid Merchant ID.', + '903' => 'Debit not supported.', + '904' => 'Private label not supported.', + '905' => 'Invalid card type.', + '906' => 'Unit not active.', + '908' => 'Manual card entry invalid.', + '909' => 'Invalid track information.', + '911' => 'Master merchant not found.', + '912' => 'Invalid card format.', + '913' => 'Invalid card type.', + '914' => 'Invalid card length.', + '917' => 'Expired card.', + '919' => 'Invalid entry type.', + '920' => 'Invalid amount.', + '921' => 'Invalid messge format.', + '923' => 'Invalid ABA.', + '924' => 'Invalid DDA.', + '925' => 'Invalid TID.', + '926' => 'Invalid Password.', + '930' => 'Invalid zipcode.', + '931' => 'Invalid Address.', + '932' => 'Invalid ZIP and Address.', + '933' => 'Invalid CVV2.', + '934' => 'Program Not Allowed.', + '935' => 'Invalid Device/App.', + '940' => 'Record Not Found.', + '941' => 'Merchant ID error.', + '942' => 'Refund Not Allowed.', + '943' => 'Refund denied.', + '955' => 'Invalid PIN block.', + '956' => 'Invalid KSN.', + '958' => 'Bad Status.', + '959' => 'Seek Record limit exceeded.', + '960' => 'Internal Key Database Error.', + '961' => 'TRANS not Supported. Cash Disbursement required a specific MCC.', + '962' => 'Invalid PIN key (Unknown KSN).', + '981' => 'Invalid AVS.', + '987' => 'Issuer Unavailable.', + '988' => 'System error SD.', + '989' => 'Database Error.', + '992' => 'Transaction Timeout.', + '996' => 'Bad Terminal ID.', + '997' => 'Message rejected by association.', + '999' => 'Communication failure', + nil => 'No response returned (missing credentials?).' + } + + def initialize(options = {}) + requires!(options, :login) + super + end + + def purchase(money, payment, options = {}) + commit(money, build_sale_request(money, payment, options)) + end + + def authorize(money, payment, options = {}) + commit(money, build_authonly_request(money, payment, options)) + end + + def capture(money, reference, options = {}) + transaction_id, _, _, token = reference.split(';') + commit(money, build_capture_request(money, transaction_id, options), token) + end + + def void(reference, options = {}) + transaction_id, _, amount, token = reference.split(';') + commit(amount.to_i, build_void_request(amount.to_i, transaction_id, options), token) + end + + def credit(money, payment, options = {}) + commit(money, build_credit_request(money, nil, payment, options)) + end + + def refund(money, reference, options = {}) + transaction_id, _, _, token = reference.split(';') + commit(money, build_credit_request(money, transaction_id, token, options), token) + end + + def verify(credit_card, options = {}) + authorize(0, credit_card, options) + end + + def store(credit_card, options = {}) + commit(nil, build_store_request(credit_card, options)) + end + + def supports_scrubbing + true + end + + def scrub(transcript) + transcript. + gsub(%r((Authorization: Basic )\w+), '\1[FILTERED]'). + gsub(%r((>)\d+()), '\1[FILTERED]\2'). + gsub(%r(()\d+()), '\1[FILTERED]\2') + end + + private + + def build_xml_request(transaction_type, options = {}, transaction_id = nil, &block) + xml = Builder::XmlMarkup.new + xml.tag! 'JetPay', 'Version' => API_VERSION do + # Basic values needed for any request + xml.tag! 'TerminalID', @options[:login] + xml.tag! 'TransactionType', transaction_type + xml.tag! 'TransactionID', transaction_id.nil? ? generate_unique_id.slice(0, 18) : transaction_id + xml.tag! 'Origin', options[:origin] || 'INTERNET' + xml.tag! 'IndustryInfo', 'Type' => options[:industry_info] || 'ECOMMERCE' + xml.tag! 'Application', (options[:application] || 'n/a'), {'Version' => options[:application_version] || '1.0'} + xml.tag! 'Device', (options[:device] || 'n/a'), {'Version' => options[:device_version] || '1.0'} + xml.tag! 'Library', 'VirtPOS SDK', 'Version' => '1.5' + xml.tag! 'Gateway', 'JetPay' + xml.tag! 'DeveloperID', options[:developer_id] || 'n/a' + + if block_given? + yield xml + else + xml.target! + end + end + end + + def build_sale_request(money, payment, options) + build_xml_request('SALE', options) do |xml| + add_payment(xml, payment) + add_addresses(xml, options) + add_customer_data(xml, options) + add_invoice_data(xml, options) + add_user_defined_fields(xml, options) + xml.tag! 'TotalAmount', amount(money) + + xml.target! + end + end + + def build_authonly_request(money, payment, options) + build_xml_request('AUTHONLY', options) do |xml| + add_payment(xml, payment) + add_addresses(xml, options) + add_customer_data(xml, options) + add_invoice_data(xml, options) + add_user_defined_fields(xml, options) + xml.tag! 'TotalAmount', amount(money) + + xml.target! + end + end + + def build_capture_request(money, transaction_id, options) + build_xml_request('CAPT', options, transaction_id) do |xml| + add_invoice_data(xml, options) + add_purchase_order(xml, options) + add_user_defined_fields(xml, options) + xml.tag! 'TotalAmount', amount(money) + + xml.target! + end + end + + def build_void_request(money, transaction_id, options) + build_xml_request('VOID', options, transaction_id) do |xml| + xml.tag! 'TotalAmount', amount(money) + xml.target! + end + end + + def build_credit_request(money, transaction_id, payment, options) + build_xml_request('CREDIT', options, transaction_id) do |xml| + add_payment(xml, payment) + add_invoice_data(xml, options) + add_addresses(xml, options) + add_customer_data(xml, options) + add_user_defined_fields(xml, options) + xml.tag! 'TotalAmount', amount(money) + + xml.target! + end + end + + def build_store_request(credit_card, options) + build_xml_request('TOKENIZE', options) do |xml| + add_payment(xml, credit_card) + add_addresses(xml, options) + add_customer_data(xml, options) + + xml.target! + end + end + + def commit(money, request, token = nil) + response = parse(ssl_post(url, request)) + + success = success?(response) + Response.new(success, + success ? 'APPROVED' : message_from(response), + response, + :test => test?, + :authorization => authorization_from(response, money, token), + :avs_result => AVSResult.new(:code => response[:avs]), + :cvv_result => CVVResult.new(response[:cvv2]), + :error_code => success ? nil : error_code_from(response) + ) + end + + def url + test? ? test_url : live_url + end + + def parse(body) + return {} if body.blank? + + xml = REXML::Document.new(body) + + response = {} + xml.root.elements.to_a.each do |node| + parse_element(response, node) + end + response + end + + def parse_element(response, node) + if node.has_elements? + node.elements.each { |element| parse_element(response, element) } + else + response[node.name.underscore.to_sym] = node.text + end + end + + def format_exp(value) + format(value, :two_digits) + end + + def success?(response) + response[:action_code] == '000' + end + + def message_from(response) + ACTION_CODE_MESSAGES[response[:action_code]] + end + + def authorization_from(response, money, previous_token) + original_amount = amount(money) if money + [ response[:transaction_id], response[:approval], original_amount, (response[:token] || previous_token)].join(';') + end + + def error_code_from(response) + response[:action_code] + end + + def add_payment(xml, payment) + return unless payment + + if payment.is_a? String + token = payment + _, _, _, token = payment.split(';') if payment.include? ';' + xml.tag! 'Token', token if token + else + add_credit_card(xml, payment) + end + end + + def add_credit_card(xml, credit_card) + xml.tag! 'CardNum', credit_card.number, 'CardPresent' => false, 'Tokenize' => true + xml.tag! 'CardExpMonth', format_exp(credit_card.month) + xml.tag! 'CardExpYear', format_exp(credit_card.year) + + if credit_card.first_name || credit_card.last_name + xml.tag! 'CardName', [credit_card.first_name, credit_card.last_name].compact.join(' ') + end + + unless credit_card.verification_value.nil? || (credit_card.verification_value.length == 0) + xml.tag! 'CVV2', credit_card.verification_value + end + end + + def add_addresses(xml, options) + if billing_address = options[:billing_address] || options[:address] + xml.tag! 'Billing' do + xml.tag! 'Address', [billing_address[:address1], billing_address[:address2]].compact.join(' ') + xml.tag! 'City', billing_address[:city] + xml.tag! 'StateProv', billing_address[:state] + xml.tag! 'PostalCode', billing_address[:zip] + xml.tag! 'Country', lookup_country_code(billing_address[:country]) + xml.tag! 'Phone', billing_address[:phone] + xml.tag! 'Email', options[:email] if options[:email] + end + end + + if shipping_address = options[:shipping_address] + xml.tag! 'Shipping' do + xml.tag! 'Name', shipping_address[:name] + xml.tag! 'Address', [shipping_address[:address1], shipping_address[:address2]].compact.join(' ') + xml.tag! 'City', shipping_address[:city] + xml.tag! 'StateProv', shipping_address[:state] + xml.tag! 'PostalCode', shipping_address[:zip] + xml.tag! 'Country', lookup_country_code(shipping_address[:country]) + xml.tag! 'Phone', shipping_address[:phone] + end + end + end + + def add_customer_data(xml, options) + xml.tag! 'UserIPAddress', options[:ip] if options[:ip] + end + + def add_invoice_data(xml, options) + xml.tag! 'OrderNumber', options[:order_id] if options[:order_id] + if tax_amount = options[:tax_amount] + xml.tag! 'TaxAmount', tax_amount, {'ExemptInd' => options[:tax_exempt] || 'false'} + end + end + + def add_purchase_order(xml, options) + if purchase_order = options[:purchase_order] + xml.tag! 'Billing' do + xml.tag! 'CustomerPO', purchase_order + end + end + end + + def add_user_defined_fields(xml, options) + xml.tag! 'UDField1', options[:ud_field_1] if options[:ud_field_1] + xml.tag! 'UDField2', options[:ud_field_2] if options[:ud_field_2] + xml.tag! 'UDField3', options[:ud_field_3] if options[:ud_field_3] + end + + def lookup_country_code(code) + country = Country.find(code) rescue nil + country&.code(:alpha3) + end + end + end +end diff --git a/lib/active_merchant/billing/gateways/komoju.rb b/lib/active_merchant/billing/gateways/komoju.rb new file mode 100644 index 00000000000..d53ab5f5165 --- /dev/null +++ b/lib/active_merchant/billing/gateways/komoju.rb @@ -0,0 +1,115 @@ +require 'json' + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class KomojuGateway < Gateway + self.test_url = 'https://komoju.com/api/v1' + self.live_url = 'https://komoju.com/api/v1' + self.supported_countries = ['JP'] + self.default_currency = 'JPY' + self.money_format = :cents + self.homepage_url = 'https://www.komoju.com/' + self.display_name = 'Komoju' + self.supported_cardtypes = [:visa, :master, :american_express, :jcb] + + STANDARD_ERROR_CODE_MAPPING = { + 'bad_verification_value' => 'incorrect_cvc', + 'card_expired' => 'expired_card', + 'card_declined' => 'card_declined', + 'invalid_number' => 'invalid_number' + } + + def initialize(options = {}) + requires!(options, :login) + super + end + + def purchase(money, payment, options = {}) + post = {} + post[:amount] = amount(money) + post[:description] = options[:description] + add_payment_details(post, payment, options) + post[:currency] = options[:currency] || default_currency + post[:external_order_num] = options[:order_id] if options[:order_id] + post[:tax] = options[:tax] if options[:tax] + add_fraud_details(post, options) + + commit('/payments', post) + end + + def refund(money, identification, options = {}) + commit("/payments/#{identification}/refund", {}) + end + + private + + def add_payment_details(post, payment, options) + details = {} + + details[:type] = 'credit_card' + details[:number] = payment.number + details[:month] = payment.month + details[:year] = payment.year + details[:verification_value] = payment.verification_value + details[:given_name] = payment.first_name + details[:family_name] = payment.last_name + details[:email] = options[:email] if options[:email] + + post[:payment_details] = details + end + + def add_fraud_details(post, options) + details = {} + + details[:customer_ip] = options[:ip] if options[:ip] + details[:customer_email] = options[:email] if options[:email] + details[:browser_language] = options[:browser_language] if options[:browser_language] + details[:browser_user_agent] = options[:browser_user_agent] if options[:browser_user_agent] + + post[:fraud_details] = details unless details.empty? + end + + def api_request(path, data) + raw_response = nil + begin + raw_response = ssl_post("#{url}#{path}", data, headers) + rescue ResponseError => e + raw_response = e.response.body + end + + JSON.parse(raw_response) + end + + def commit(path, params) + response = api_request(path, params.to_json) + success = !response.key?('error') + message = (success ? 'Transaction succeeded' : response['error']['message']) + Response.new( + success, + message, + response, + test: test?, + error_code: (success ? nil : error_code(response['error']['code'])), + authorization: (success ? response['id'] : nil) + ) + end + + def error_code(code) + STANDARD_ERROR_CODE_MAPPING[code] || code + end + + def url + test? ? self.test_url : self.live_url + end + + def headers + { + 'Authorization' => 'Basic ' + Base64.encode64(@options[:login].to_s + ':').strip, + 'Accept' => 'application/json', + 'Content-Type' => 'application/json', + 'User-Agent' => "Komoju/v1 ActiveMerchantBindings/#{ActiveMerchant::VERSION}" + } + end + end + end +end diff --git a/lib/active_merchant/billing/gateways/kushki.rb b/lib/active_merchant/billing/gateways/kushki.rb new file mode 100644 index 00000000000..646df52166a --- /dev/null +++ b/lib/active_merchant/billing/gateways/kushki.rb @@ -0,0 +1,219 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class KushkiGateway < Gateway + self.display_name = 'Kushki' + self.homepage_url = 'https://www.kushkipagos.com' + + self.test_url = 'https://api-uat.kushkipagos.com/v1/' + self.live_url = 'https://api.kushkipagos.com/v1/' + + self.supported_countries = ['CL', 'CO', 'EC', 'MX', 'PE'] + self.default_currency = 'USD' + self.money_format = :dollars + self.supported_cardtypes = [:visa, :master, :american_express, :discover, :diners_club] + + def initialize(options={}) + requires!(options, :public_merchant_id, :private_merchant_id) + super + end + + def purchase(amount, payment_method, options={}) + MultiResponse.run() do |r| + r.process { tokenize(amount, payment_method, options) } + r.process { charge(amount, r.authorization, options) } + end + end + + def refund(amount, authorization, options={}) + action = 'refund' + + post = {} + post[:ticketNumber] = authorization + + commit(action, post) + end + + def void(authorization, options={}) + action = 'void' + + post = {} + post[:ticketNumber] = authorization + + commit(action, post) + end + + def supports_scrubbing? + true + end + + def scrub(transcript) + transcript. + gsub(%r((Private-Merchant-Id: )\d+), '\1[FILTERED]'). + gsub(%r((\"card\\\":{\\\"number\\\":\\\")\d+), '\1[FILTERED]'). + gsub(%r((\"cvv\\\":\\\")\d+), '\1[FILTERED]') + end + + private + + def tokenize(amount, payment_method, options) + action = 'tokenize' + + post = {} + add_invoice(action, post, amount, options) + add_payment_method(post, payment_method, options) + + commit(action, post) + end + + def charge(amount, authorization, options) + action = 'charge' + + post = {} + add_reference(post, authorization, options) + add_invoice(action, post, amount, options) + + commit(action, post) + end + + def add_invoice(action, post, money, options) + if action == 'tokenize' + post[:totalAmount] = amount(money).to_f + post[:currency] = options[:currency] || currency(money) + post[:isDeferred] = false + else + sum = {} + sum[:currency] = options[:currency] || currency(money) + add_amount_defaults(sum, money, options) + add_amount_by_country(sum, options) + post[:amount] = sum + end + end + + def add_amount_defaults(sum, money, options) + sum[:subtotalIva] = amount(money).to_f + sum[:iva] = 0 + sum[:subtotalIva0] = 0 + + if sum[:currency] != 'COP' + sum[:ice] = 0 + end + end + + def add_amount_by_country(sum, options) + if amount = options[:amount] + sum[:subtotalIva] = amount[:subtotal_iva].to_f if amount[:subtotal_iva] + sum[:iva] = amount[:iva].to_f if amount[:iva] + sum[:subtotalIva0] = amount[:subtotal_iva_0].to_f if amount[:subtotal_iva_0] + sum[:ice] = amount[:ice].to_f if amount[:ice] + if (extra_taxes = amount[:extra_taxes]) && sum[:currency] == 'COP' + sum[:extraTaxes] ||= Hash.new + sum[:extraTaxes][:propina] = extra_taxes[:propina].to_f if extra_taxes[:propina] + sum[:extraTaxes][:tasaAeroportuaria] = extra_taxes[:tasa_aeroportuaria].to_f if extra_taxes[:tasa_aeroportuaria] + sum[:extraTaxes][:agenciaDeViaje] = extra_taxes[:agencia_de_viaje].to_f if extra_taxes[:agencia_de_viaje] + sum[:extraTaxes][:iac] = extra_taxes[:iac].to_f if extra_taxes[:iac] + end + end + end + + def add_payment_method(post, payment_method, options) + card = {} + card[:number] = payment_method.number + card[:cvv] = payment_method.verification_value + card[:expiryMonth] = format(payment_method.month, :two_digits) + card[:expiryYear] = format(payment_method.year, :two_digits) + card[:name] = payment_method.name + post[:card] = card + end + + def add_reference(post, authorization, options) + post[:token] = authorization + end + + ENDPOINT = { + 'tokenize' => 'tokens', + 'charge' => 'charges', + 'void' => 'charges', + 'refund' => 'refund' + } + + def commit(action, params) + response = begin + parse(ssl_invoke(action, params)) + rescue ResponseError => e + parse(e.response.body) + end + + success = success_from(response) + + Response.new( + success, + message_from(success, response), + response, + authorization: success ? authorization_from(response) : nil, + error_code: success ? nil : error_from(response), + test: test? + ) + end + + def ssl_invoke(action, params) + if ['void', 'refund'].include?(action) + ssl_request(:delete, url(action, params), nil, headers(action)) + else + ssl_post(url(action, params), post_data(params), headers(action)) + end + end + + def headers(action) + hfields = {} + hfields['Public-Merchant-Id'] = @options[:public_merchant_id] if action == 'tokenize' + hfields['Private-Merchant-Id'] = @options[:private_merchant_id] unless action == 'tokenize' + hfields['Content-Type'] = 'application/json' + hfields + end + + def post_data(params) + params.to_json + end + + def url(action, params) + base_url = test? ? test_url : live_url + + if ['void', 'refund'].include?(action) + base_url + ENDPOINT[action] + '/' + params[:ticketNumber].to_s + else + base_url + ENDPOINT[action] + end + end + + def parse(body) + JSON.parse(body) + rescue JSON::ParserError + message = 'Invalid JSON response received from KushkiGateway. Please contact KushkiGateway if you continue to receive this message.' + message += " (The raw response returned by the API was #{body.inspect})" + { + 'message' => message + } + end + + def success_from(response) + return true if response['token'] || response['ticketNumber'] || response['code'] == 'K000' + end + + def message_from(succeeded, response) + if succeeded + 'Succeeded' + else + response['message'] + end + end + + def authorization_from(response) + response['token'] || response['ticketNumber'] + end + + def error_from(response) + response['code'] + end + end + end +end diff --git a/lib/active_merchant/billing/gateways/latitude19.rb b/lib/active_merchant/billing/gateways/latitude19.rb new file mode 100644 index 00000000000..d30b5e14a22 --- /dev/null +++ b/lib/active_merchant/billing/gateways/latitude19.rb @@ -0,0 +1,411 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class Latitude19Gateway < Gateway + self.display_name = 'Latitude19 Gateway' + self.homepage_url = 'http://www.l19tech.com' + + self.live_url = 'https://gateway.l19tech.com/payments/' + self.test_url = 'https://gateway-sb.l19tech.com/payments/' + + self.supported_countries = ['US', 'CA'] + self.default_currency = 'USD' + self.money_format = :cents + self.supported_cardtypes = [:visa, :master, :american_express, :discover, :diners_club, :jcb] + + RESPONSE_CODE_MAPPING = { + '100' => 'Approved', + '101' => 'Local duplicate detected', + '102' => 'Accepted local capture with no match', + '103' => 'Auth succeeded but capture failed', + '104' => 'Auth succeeded but failed to save info', + '200' => STANDARD_ERROR_CODE[:card_declined], + '300' => 'Processor reject', + '301' => 'Local reject on user/password', + '302' => 'Local reject', + '303' => 'Processor unknown response', + '304' => 'Error parsing processor response', + '305' => 'Processor auth succeeded but settle failed', + '306' => 'Processor auth succeeded settle status unknown', + '307' => 'Processor settle status unknown', + '308' => 'Processor duplicate', + '400' => 'Not submitted', + '401' => 'Terminated before request submitted', + '402' => 'Local server busy', + '500' => 'Submitted not returned', + '501' => 'Terminated before response returned', + '502' => 'Processor returned timeout status', + '600' => 'Failed local capture with no match', + '601' => 'Failed local capture', + '700' => 'Failed local void (not in capture file)', + '701' => 'Failed local void', + '800' => 'Failed local refund (not authorized)', + '801' => 'Failed local refund' + } + + BRAND_MAP = { + 'master' => 'MC', + 'visa' => 'VI', + 'american_express' => 'AX', + 'discover' => 'DS', + 'diners_club' => 'DC', + 'jcb' => 'JC' + } + + def initialize(options={}) + requires!(options, :account_number, :configuration_id, :secret) + super + end + + def purchase(amount, payment_method, options={}) + if payment_method.is_a?(String) + auth_or_sale('sale', payment_method, amount, nil, options) + else + MultiResponse.run() do |r| + r.process { get_session(options) } + r.process { get_token(r.authorization, payment_method, options) } + r.process { auth_or_sale('sale', r.authorization, amount, payment_method, options) } + end + end + end + + def authorize(amount, payment_method, options={}) + if payment_method.is_a?(String) + auth_or_sale('auth', payment_method, amount, nil, options) + else + MultiResponse.run() do |r| + r.process { get_session(options) } + r.process { get_token(r.authorization, payment_method, options) } + r.process { auth_or_sale('auth', r.authorization, amount, payment_method, options) } + end + end + end + + def capture(amount, authorization, options={}) + post = {} + post[:method] = 'deposit' + add_request_id(post) + + params = {} + + _, params[:pgwTID] = split_authorization(authorization) + + add_invoice(params, amount, options) + add_credentials(params, post[:method]) + + post[:params] = [params] + commit('v1/', post) + end + + def void(authorization, options={}) + method, pgwTID = split_authorization(authorization) + case method + when 'auth' + reverse_or_void('reversal', pgwTID, options) + when 'deposit', 'sale' + reverse_or_void('void', pgwTID, options) + else + message = 'Unsupported operation: successful Purchase, Authorize and unsettled Capture transactions can only be voided.' + return Response.new(false, message) + end + end + + def credit(amount, payment_method, options={}) + if payment_method.is_a?(String) + refundWithCard(payment_method, amount, nil, options) + else + MultiResponse.run() do |r| + r.process { get_session(options) } + r.process { get_token(r.authorization, payment_method, options) } + r.process { refundWithCard(r.authorization, amount, payment_method, options) } + end + end + end + + def verify(payment_method, options={}, action=nil) + if payment_method.is_a?(String) + verifyOnly(action, payment_method, nil, options) + else + MultiResponse.run() do |r| + r.process { get_session(options) } + r.process { get_token(r.authorization, payment_method, options) } + r.process { verifyOnly(action, r.authorization, payment_method, options) } + end + end + end + + def store(payment_method, options={}) + verify(payment_method, options, 'store') + end + + def supports_scrubbing? + true + end + + def scrub(transcript) + transcript. + gsub(%r((\"cardNumber\\\":\\\")\d+), '\1[FILTERED]'). + gsub(%r((\"cvv\\\":\\\")\d+), '\1[FILTERED]') + end + + private + + def add_request_id(post) + post[:id] = SecureRandom.hex(16) + end + + def add_timestamp + Time.now.getutc.strftime('%Y%m%d%H%M%S') + end + + def add_hmac(params, method) + if method == 'getSession' + hmac_message = params[:pgwAccountNumber] + '|' + params[:pgwConfigurationId] + '|' + params[:requestTimeStamp] + '|' + method + else + hmac_message = params[:pgwAccountNumber] + '|' + params[:pgwConfigurationId] + '|' + (params[:orderNumber] || '') + '|' + method + '|' + (params[:amount] || '') + '|' + (params[:sessionToken] || '') + '|' + (params[:accountToken] || '') + end + + OpenSSL::HMAC.hexdigest('sha512', @options[:secret], hmac_message) + end + + def add_credentials(params, method) + params[:pgwAccountNumber] = @options[:account_number] + params[:pgwConfigurationId] = @options[:configuration_id] + + params[:requestTimeStamp] = add_timestamp() if method == 'getSession' + + params[:pgwHMAC] = add_hmac(params, method) + end + + def add_invoice(params, money, options) + params[:amount] = amount(money) + params[:orderNumber] = options[:order_id] + params[:transactionClass] = options[:transaction_class] || 'eCommerce' + end + + def add_payment_method(params, credit_card) + params[:cardExp] = format(credit_card.month, :two_digits).to_s + '/' + format(credit_card.year, :two_digits).to_s + params[:cardType] = BRAND_MAP[credit_card.brand.to_s] + params[:cvv] = credit_card.verification_value + params[:firstName] = credit_card.first_name + params[:lastName] = credit_card.last_name + end + + def add_customer_data(params, options) + if (billing_address = options[:billing_address] || options[:address]) + params[:address1] = billing_address[:address1] + params[:address2] = billing_address[:address2] + params[:city] = billing_address[:city] + params[:stateProvince] = billing_address[:state] + params[:zipPostalCode] = billing_address[:zip] + params[:countryCode] = billing_address[:country] + end + end + + def get_session(options={}) + post = {} + post[:method] = 'getSession' + add_request_id(post) + + params = {} + add_credentials(params, post[:method]) + + post[:params] = [params] + commit('session', post) + end + + def get_token(authorization, payment_method, options={}) + post = {} + post[:method] = 'tokenize' + add_request_id(post) + + params = {} + _, params[:sessionId] = split_authorization(authorization) + params[:cardNumber] = payment_method.number + + post[:params] = [params] + commit('token', post) + end + + def auth_or_sale(method, authorization, amount, credit_card, options={}) + post = {} + post[:method] = method + add_request_id(post) + + params = {} + if credit_card + _, params[:sessionToken] = split_authorization(authorization) + add_payment_method(params, credit_card) + add_customer_data(params, options) + else + _, params[:accountToken] = split_authorization(authorization) + end + add_invoice(params, amount, options) + add_credentials(params, post[:method]) + + post[:params] = [params] + commit('v1/', post) + end + + def verifyOnly(action, authorization, credit_card, options={}) + post = {} + post[:method] = 'verifyOnly' + add_request_id(post) + + params = {} + if credit_card + _, params[:sessionToken] = split_authorization(authorization) + add_payment_method(params, credit_card) + add_customer_data(params, options) + else + _, params[:accountToken] = split_authorization(authorization) + end + params[:requestAccountToken] = '1' if action == 'store' + add_invoice(params, 0, options) + add_credentials(params, post[:method]) + + post[:params] = [params] + commit('v1/', post) + end + + def refundWithCard(authorization, amount, credit_card, options={}) + post = {} + post[:method] = 'refundWithCard' + add_request_id(post) + + params = {} + if credit_card + _, params[:sessionToken] = split_authorization(authorization) + add_payment_method(params, credit_card) + else + _, params[:accountToken] = split_authorization(authorization) + end + add_invoice(params, amount, options) + add_credentials(params, post[:method]) + + post[:params] = [params] + commit('v1/', post) + end + + def reverse_or_void(method, pgwTID, options={}) + post = {} + post[:method] = method + add_request_id(post) + + params = {} + params[:orderNumber] = options[:order_id] + params[:pgwTID] = pgwTID + add_credentials(params, post[:method]) + + post[:params] = [params] + commit('v1/', post) + end + + def commit(endpoint, post) + raw_response = ssl_post(url() + endpoint, post_data(post), headers) + response = parse(raw_response) + rescue ResponseError => e + raw_response = e.response.body + response_error(raw_response) + rescue JSON::ParserError + unparsable_response(raw_response) + else + success = success_from(response) + Response.new( + success, + message_from(response), + response, + authorization: success ? authorization_from(response, post[:method]) : nil, + avs_result: success ? avs_from(response) : nil, + cvv_result: success ? cvv_from(response) : nil, + error_code: success ? nil : error_from(response), + test: test? + ) + end + + def headers + { + 'Content-Type' => 'application/json' + } + end + + def post_data(params) + params.to_json + end + + def url + test? ? test_url : live_url + end + + def parse(body) + JSON.parse(body) + end + + def success_from(response) + return false if response['result'].nil? || response['error'] + + if response['result'].key?('pgwResponseCode') + response['error'].nil? && response['result']['lastActionSucceeded'] == 1 && response['result']['pgwResponseCode'] == '100' + else + response['error'].nil? && response['result']['lastActionSucceeded'] == 1 + end + end + + def message_from(response) + return response['error'] if response['error'] + return 'Failed' unless response.key?('result') + + if response['result'].key?('pgwResponseCode') + RESPONSE_CODE_MAPPING[response['result']['pgwResponseCode']] || response['result']['responseText'] + else + response['result']['lastActionSucceeded'] == 1 ? 'Succeeded' : 'Failed' + end + end + + def error_from(response) + return response['error'] if response['error'] + return 'Failed' unless response.key?('result') + return response['result']['pgwResponseCode'] || response['result']['processor']['responseCode'] || 'Failed' + end + + def authorization_from(response, method) + method + '|' + ( + response['result']['sessionId'] || + response['result']['sessionToken'] || + response['result']['pgwTID'] || + response['result']['accountToken'] + ) + end + + def split_authorization(authorization) + authorization.split('|') + end + + def avs_from(response) + response['result'].key?('avsResponse') ? AVSResult.new(code: response['result']['avsResponse']) : nil + end + + def cvv_from(response) + response['result'].key?('cvvResponse') ? CVVResult.new(response['result']['cvvResponse']) : nil + end + + def response_error(raw_response) + response = parse(raw_response) + rescue JSON::ParserError + unparsable_response(raw_response) + else + return Response.new( + false, + message_from(response), + response, + :test => test? + ) + end + + def unparsable_response(raw_response) + message = 'Invalid JSON response received from Latitude19Gateway. Please contact Latitude19Gateway if you continue to receive this message.' + message += " (The raw response returned by the API was #{raw_response.inspect})" + return Response.new(false, message) + end + end + end +end diff --git a/lib/active_merchant/billing/gateways/linkpoint.rb b/lib/active_merchant/billing/gateways/linkpoint.rb index 10917c87532..4dd09c89800 100644 --- a/lib/active_merchant/billing/gateways/linkpoint.rb +++ b/lib/active_merchant/billing/gateways/linkpoint.rb @@ -2,7 +2,6 @@ module ActiveMerchant #:nodoc: module Billing #:nodoc: - # Initialization Options # :login Your store number # :pem The text of your linkpoint PEM file. Note @@ -144,6 +143,8 @@ def initialize(options = {}) }.update(options) raise ArgumentError, "You need to pass in your pem file using the :pem parameter or set it globally using ActiveMerchant::Billing::LinkpointGateway.pem_file = File.read( File.dirname(__FILE__) + '/../mycert.pem' ) or similar" if @options[:pem].blank? + + @options[:pem].strip! end # Send a purchase request with periodic options @@ -166,14 +167,16 @@ def initialize(options = {}) # :comments Uh... comments # def recurring(money, creditcard, options={}) - requires!(options, [:periodicity, :bimonthly, :monthly, :biweekly, :weekly, :yearly, :daily], :installments, :order_id ) + ActiveMerchant.deprecated RECURRING_DEPRECATION_MESSAGE + + requires!(options, [:periodicity, :bimonthly, :monthly, :biweekly, :weekly, :yearly, :daily], :installments, :order_id) options.update( - :ordertype => "SALE", - :action => options[:action] || "SUBMIT", + :ordertype => 'SALE', + :action => options[:action] || 'SUBMIT', :installments => options[:installments] || 12, - :startdate => options[:startdate] || "immediate", - :periodicity => options[:periodicity].to_s || "monthly", + :startdate => options[:startdate] || 'immediate', + :periodicity => options[:periodicity].to_s || 'monthly', :comments => options[:comments] || nil, :threshold => options[:threshold] || 3 ) @@ -184,7 +187,7 @@ def recurring(money, creditcard, options={}) def purchase(money, creditcard, options={}) requires!(options, :order_id) options.update( - :ordertype => "SALE" + :ordertype => 'SALE' ) commit(money, creditcard, options) end @@ -197,7 +200,7 @@ def purchase(money, creditcard, options={}) def authorize(money, creditcard, options = {}) requires!(options, :order_id) options.update( - :ordertype => "PREAUTH" + :ordertype => 'PREAUTH' ) commit(money, creditcard, options) end @@ -211,7 +214,7 @@ def authorize(money, creditcard, options = {}) def capture(money, authorization, options = {}) options.update( :order_id => authorization, - :ordertype => "POSTAUTH" + :ordertype => 'POSTAUTH' ) commit(money, nil, options) end @@ -220,7 +223,7 @@ def capture(money, authorization, options = {}) def void(identification, options = {}) options.update( :order_id => identification, - :ordertype => "VOID" + :ordertype => 'VOID' ) commit(nil, nil, options) end @@ -232,18 +235,30 @@ def void(identification, options = {}) # def refund(money, identification, options = {}) options.update( - :ordertype => "CREDIT", + :ordertype => 'CREDIT', :order_id => identification ) commit(money, nil, options) end def credit(money, identification, options = {}) - deprecated CREDIT_DEPRECATION_MESSAGE + ActiveMerchant.deprecated CREDIT_DEPRECATION_MESSAGE refund(money, identification, options) end + def supports_scrubbing + true + end + + def scrub(transcript) + transcript. + gsub(%r((Authorization: Basic )\w+), '\1[FILTERED]'). + gsub(%r(()\d+())i, '\1[FILTERED]\2'). + gsub(%r(()\d+())i, '\1[FILTERED]\2') + end + private + # Commit the transaction by posting the XML file to the LinkPoint server def commit(money, creditcard, options = {}) response = parse(ssl_post(test? ? self.test_url : self.live_url, post_data(money, creditcard, options))) @@ -251,13 +266,13 @@ def commit(money, creditcard, options = {}) Response.new(successful?(response), response[:message], response, :test => test?, :authorization => response[:ordernum], - :avs_result => { :code => response[:avs].to_s[2,1] }, - :cvv_result => response[:avs].to_s[3,1] + :avs_result => { :code => response[:avs].to_s[2, 1] }, + :cvv_result => response[:avs].to_s[3, 1] ) end def successful?(response) - response[:approved] == "APPROVED" + response[:approved] == 'APPROVED' end # Build the XML file @@ -265,11 +280,11 @@ def post_data(money, creditcard, options) params = parameters(money, creditcard, options) xml = REXML::Document.new - order = xml.add_element("order") + order = xml.add_element('order') # Merchant Info - merchantinfo = order.add_element("merchantinfo") - merchantinfo.add_element("configfile").text = @options[:login] + merchantinfo = order.add_element('merchantinfo') + merchantinfo.add_element('configfile').text = @options[:login] # Loop over the params hash to construct the XML string for key, value in params @@ -277,7 +292,7 @@ def post_data(money, creditcard, options) if key == :items build_items(elem, value) else - for k, v in params[key] + for k, _ in params[key] elem.add_element(k.to_s).text = params[key][k].to_s if params[key][k] end end @@ -290,14 +305,14 @@ def post_data(money, creditcard, options) # adds LinkPoint's Items entity to the XML. Called from post_data def build_items(element, items) for item in items - item_element = element.add_element("item") + item_element = element.add_element('item') for key, value in item if key == :options - options_element = item_element.add_element("options") + options_element = item_element.add_element('options') for option in value - opt_element = options_element.add_element("option") - opt_element.add_element("name").text = option[:name] unless option[:name].blank? - opt_element.add_element("value").text = option[:value] unless option[:value].blank? + opt_element = options_element.add_element('option') + opt_element.add_element('name').text = option[:name] unless option[:name].blank? + opt_element.add_element('value').text = option[:value] unless option[:value].blank? end else item_element.add_element(key.to_s).text = item[key].to_s unless item[key].blank? @@ -309,7 +324,6 @@ def build_items(element, items) # Set up the parameters hash just once so we don't have to do it # for every action. def parameters(money, creditcard, options = {}) - params = { :payment => { :subtotal => amount(options[:subtotal]), @@ -319,14 +333,14 @@ def parameters(money, creditcard, options = {}) :chargetotal => amount(money) }, :transactiondetails => { - :transactionorigin => options[:transactionorigin] || "ECI", + :transactionorigin => options[:transactionorigin] || 'ECI', :oid => options[:order_id], :ponumber => options[:ponumber], :taxexempt => options[:taxexempt], :terminaltype => options[:terminaltype], :ip => options[:ip], :reference_number => options[:reference_number], - :recurring => options[:recurring] || "NO", #DO NOT USE if you are using the periodic billing option. + :recurring => options[:recurring] || 'NO', # DO NOT USE if you are using the periodic billing option. :tdate => options[:tdate] }, :orderoptions => { @@ -403,7 +417,6 @@ def parameters(money, creditcard, options = {}) end def parse(xml) - # For reference, a typical response... # # @@ -418,29 +431,18 @@ def parse(xml) # APPROVED # - response = {:message => "Global Error Receipt", :complete => false} + response = {:message => 'Global Error Receipt', :complete => false} xml = REXML::Document.new("#{xml}") - xml.root.elements.each do |node| + xml.root&.elements&.each do |node| response[node.name.downcase.sub(/^r_/, '').to_sym] = normalize(node.text) - end unless xml.root.nil? + end response end - # Make a ruby type out of the response string - def normalize(field) - case field - when "true" then true - when "false" then false - when "" then nil - when "null" then nil - else field - end - end - def format_creditcard_expiry_year(year) - sprintf("%.4i", year)[-2..-1] + sprintf('%.4i', year)[-2..-1] end end end diff --git a/lib/active_merchant/billing/gateways/litle.rb b/lib/active_merchant/billing/gateways/litle.rb old mode 100755 new mode 100644 index 83ce2bc28dc..0cdfe62f9ef --- a/lib/active_merchant/billing/gateways/litle.rb +++ b/lib/active_merchant/billing/gateways/litle.rb @@ -1,141 +1,167 @@ +require 'nokogiri' +require 'active_merchant/billing/gateways/litle/paypage_registration' + module ActiveMerchant #:nodoc: module Billing #:nodoc: class LitleGateway < Gateway - # Specific to Litle options: - # * :merchant_id - Merchant Id assigned by Litle - # * :user - Username assigned by Litle - # * :password - Password assigned by Litle - # * :version - The version of the api you are using (eg, '8.10') - # * :proxy_addr - Proxy address - nil if not needed - # * :proxy_port - Proxy port - nil if not needed - # * :url - URL assigned by Litle (for testing, use the sandbox) - # - # Standard Active Merchant options - # * :order_id - The order number - # * :ip - The IP address of the customer making the purchase - # * :customer - The name, customer number, or other information that identifies the customer - # * :invoice - The invoice number - # * :merchant - The name or description of the merchant offering the product - # * :description - A description of the transaction - # * :email - The email address of the customer - # * :currency - The currency of the transaction. Only important when you are using a currency that is not the default with a gateway that supports multiple currencies. - # * :billing_address - A hash containing the billing address of the customer. - # * :shipping_address - A hash containing the shipping address of the customer. - # - # The :billing_address, and :shipping_address hashes can have the following keys: - # - # * :name - The full name of the customer. - # * :company - The company name of the customer. - # * :address1 - The primary street address of the customer. - # * :address2 - Additional line of address information. - # * :city - The city of the customer. - # * :state - The state of the customer. The 2 digit code for US and Canadian addresses. The full name of the state or province for foreign addresses. - # * :country - The [ISO 3166-1-alpha-2 code](http://www.iso.org/iso/country_codes/iso_3166_code_lists/english_country_names_and_code_elements.htm) for the customer. - # * :zip - The zip or postal code of the customer. - # * :phone - The phone number of the customer. - - self.test_url = 'https://www.testlitle.com/sandbox/communicator/online' - self.live_url = 'https://payments.litle.com/vap/communicator/online' - - LITLE_SCHEMA_VERSION = '8.13' - - # The countries the gateway supports merchants from as 2 digit ISO country codes - self.supported_countries = ['US'] + SCHEMA_VERSION = '9.14' - # The card types supported by the payment gateway - self.supported_cardtypes = [:visa, :master, :american_express, :discover, :diners_club, :jcb] + self.test_url = 'https://www.testvantivcnp.com/sandbox/communicator/online' + self.live_url = 'https://payments.vantivcnp.com/vap/communicator/online' - # The homepage URL of the gateway - self.homepage_url = 'http://www.litle.com/' + self.supported_countries = ['US'] + self.default_currency = 'USD' + self.supported_cardtypes = [:visa, :master, :american_express, :discover, :diners_club, :jcb] - # The name of the gateway - self.display_name = 'Litle & Co.' + self.homepage_url = 'http://www.vantiv.com/' + self.display_name = 'Vantiv eCommerce' - self.default_currency = 'USD' + def initialize(options={}) + requires!(options, :login, :password, :merchant_id) + super + end - def initialize(options = {}) - begin - require 'LitleOnline' - rescue LoadError - raise "Could not load the LitleOnline gem (> 08.15.0). Use `gem install LitleOnline` to install it." + def purchase(money, payment_method, options={}) + request = build_xml_request do |doc| + add_authentication(doc) + if check?(payment_method) + doc.echeckSale(transaction_attributes(options)) do + add_echeck_purchase_params(doc, money, payment_method, options) + end + else + doc.sale(transaction_attributes(options)) do + add_auth_purchase_params(doc, money, payment_method, options) + end + end end + check?(payment_method) ? commit(:echeckSales, request, money) : commit(:sale, request, money) + end - if wiredump_device - LitleOnline::Configuration.logger = ((Logger === wiredump_device) ? wiredump_device : Logger.new(wiredump_device)) - LitleOnline::Configuration.logger.level = Logger::DEBUG - else - LitleOnline::Configuration.logger = Logger.new(STDOUT) - LitleOnline::Configuration.logger.level = Logger::WARN + def authorize(money, payment_method, options={}) + request = build_xml_request do |doc| + add_authentication(doc) + if check?(payment_method) + doc.echeckVerification(transaction_attributes(options)) do + add_echeck_purchase_params(doc, money, payment_method, options) + end + else + doc.authorization(transaction_attributes(options)) do + add_auth_purchase_params(doc, money, payment_method, options) + end + end end + check?(payment_method) ? commit(:echeckVerification, request, money) : commit(:authorization, request, money) + end - @litle = LitleOnline::LitleOnlineRequest.new - - options[:version] ||= LITLE_SCHEMA_VERSION - options[:merchant] ||= 'Default Report Group' - options[:user] ||= options[:login] + def capture(money, authorization, options={}) + transaction_id, _, _ = split_authorization(authorization) - requires!(options, :merchant_id, :user, :password, :merchant, :version) + request = build_xml_request do |doc| + add_authentication(doc) + add_descriptor(doc, options) + doc.capture_(transaction_attributes(options)) do + doc.litleTxnId(transaction_id) + doc.amount(money) if money + end + end - super + commit(:capture, request, money) end - def authorize(money, creditcard_or_token, options = {}) - to_pass = build_authorize_request(money, creditcard_or_token, options) - build_response(:authorization, @litle.authorization(to_pass)) + def credit(money, authorization, options = {}) + ActiveMerchant.deprecated CREDIT_DEPRECATION_MESSAGE + refund(money, authorization, options) end - def purchase(money, creditcard_or_token, options = {}) - to_pass = build_purchase_request(money, creditcard_or_token, options) - build_response(:sale, @litle.sale(to_pass)) + def refund(money, payment, options={}) + request = build_xml_request do |doc| + add_authentication(doc) + add_descriptor(doc, options) + doc.send(refund_type(payment), transaction_attributes(options)) do + if payment.is_a?(String) + transaction_id, _, _ = split_authorization(payment) + doc.litleTxnId(transaction_id) + doc.amount(money) if money + elsif check?(payment) + add_echeck_purchase_params(doc, money, payment, options) + else + add_auth_purchase_params(doc, money, payment, options) + end + end + end + + commit(refund_type(payment), request) end - def capture(money, authorization, options = {}) - transaction_id, kind = split_authorization(authorization) - to_pass = create_capture_hash(money, transaction_id, options) - build_response(:capture, @litle.capture(to_pass)) + def verify(creditcard, options = {}) + MultiResponse.run(:use_first_response) do |r| + r.process { authorize(0, creditcard, options) } + r.process(:ignore_result) { void(r.authorization, options) } + end end - # Note: Litle requires that authorization requests be voided via auth_reversal - # and other requests via void. To maintain the same interface as the other - # gateways the transaction_id and the kind of transaction are concatenated - # together with a ; separator (e.g. 1234;authorization) - # - # A partial auth_reversal can be accomplished by passing :amount as an option - def void(identification, options = {}) - transaction_id, kind = split_authorization(identification) - if(kind == 'authorization') - to_pass = create_auth_reversal_hash(transaction_id, options[:amount], options) - build_response(:authReversal, @litle.auth_reversal(to_pass)) - else - to_pass = create_void_hash(transaction_id, options) - build_response(:void, @litle.void(to_pass)) + def void(authorization, options={}) + transaction_id, kind, money = split_authorization(authorization) + + request = build_xml_request do |doc| + add_authentication(doc) + doc.send(void_type(kind), transaction_attributes(options)) do + doc.litleTxnId(transaction_id) + doc.amount(money) if void_type(kind) == :authReversal + end end + + commit(void_type(kind), request) end - def refund(money, authorization, options = {}) - to_pass = build_credit_request(money, authorization, options) - build_response(:credit, @litle.credit(to_pass)) + def store(payment_method, options = {}) + request = build_xml_request do |doc| + add_authentication(doc) + doc.registerTokenRequest(transaction_attributes(options)) do + doc.orderId(truncate(options[:order_id], 24)) + if payment_method.is_a?(String) + doc.paypageRegistrationId(payment_method) + elsif check?(payment_method) + doc.echeckForToken do + doc.accNum(payment_method.account_number) + doc.routingNum(payment_method.routing_number) + end + else + doc.accountNumber(payment_method.number) + doc.cardValidationNum(payment_method.verification_value) if payment_method.verification_value + end + end + end + + commit(:registerToken, request) end - def credit(money, authorization, options = {}) - deprecated CREDIT_DEPRECATION_MESSAGE - refund(money, authorization, options) + def supports_scrubbing? + true end - def store(creditcard_or_paypage_registration_id, options = {}) - to_pass = create_token_hash(creditcard_or_paypage_registration_id, options) - build_response(:registerToken, @litle.register_token_request(to_pass), %w(000 801 802)) + def scrub(transcript) + transcript. + gsub(%r(().+()), '\1[FILTERED]\2'). + gsub(%r(().+()), '\1[FILTERED]\2'). + gsub(%r(().+()), '\1[FILTERED]\2'). + gsub(%r(().+()), '\1[FILTERED]\2'). + gsub(%r(().+()), '\1[FILTERED]\2'). + gsub(%r(().+()), '\1[FILTERED]\2'). + gsub(%r(().+()), '\1[FILTERED]\2'). + gsub(%r(().+()), '\1[FILTERED]\2'). + gsub(%r(().+()), '\1[FILTERED]\2') end private CARD_TYPE = { - 'visa' => 'VI', - 'master' => 'MC', - 'american_express' => 'AX', - 'discover' => 'DI', - 'jcb' => 'DI', - 'diners_club' => 'DI' + 'visa' => 'VI', + 'master' => 'MC', + 'american_express' => 'AX', + 'discover' => 'DI', + 'jcb' => 'JC', + 'diners_club' => 'DC' } AVS_RESPONSE_CODE = { @@ -156,403 +182,358 @@ def store(creditcard_or_paypage_registration_id, options = {}) '40' => 'E' } - def url - return @options[:url] if @options[:url].present? - - test? ? self.test_url : self.live_url - end - - def build_response(kind, litle_response, valid_responses=%w(000)) - response = Hash.from_xml(litle_response.raw_xml.to_s)['litleOnlineResponse'] - - if response['response'] == "0" - detail = response["#{kind}Response"] - fraud = fraud_result(detail) - Response.new( - valid_responses.include?(detail['response']), - detail['message'], - { :litleOnlineResponse => response }, - :authorization => authorization_from(detail, kind), - :avs_result => { :code => fraud['avs'] }, - :cvv_result => fraud['cvv'], - :test => test? - ) + def void_type(kind) + if kind == 'authorization' + :authReversal + elsif kind == 'echeckSales' + :echeckVoid else - Response.new(false, response['message'], :litleOnlineResponse => response, :test => test?) + :void end end - # Generates an authorization string of the appropriate id and the kind of transaction - # See #void for how the kind is used - def authorization_from(litle_response, kind) - case kind - when :registerToken - authorization = litle_response['litleToken'] + def refund_type(payment) + _, kind, _ = split_authorization(payment) + if check?(payment) || kind == 'echeckSales' + :echeckCredit else - authorization = [litle_response['litleTxnId'], kind.to_s].join(";") + :credit end end - def split_authorization(authorization) - transaction_id, kind = authorization.to_s.split(';') - [transaction_id, kind] + def check?(payment_method) + return false if payment_method.is_a?(String) + card_brand(payment_method) == 'check' end - def build_authorize_request(money, creditcard_or_token, options) - payment_method = build_payment_method(creditcard_or_token, options) - - hash = create_hash(money, options) - - add_creditcard_or_cardtoken_hash(hash, payment_method) - - hash + def add_authentication(doc) + doc.authentication do + doc.user(@options[:login]) + doc.password(@options[:password]) + end end - def build_purchase_request(money, creditcard_or_token, options) - payment_method = build_payment_method(creditcard_or_token, options) - - hash = create_hash(money, options) - - add_creditcard_or_cardtoken_hash(hash, payment_method) - - hash + def add_auth_purchase_params(doc, money, payment_method, options) + doc.orderId(truncate(options[:order_id], 24)) + doc.amount(money) + add_order_source(doc, payment_method, options) + add_billing_address(doc, payment_method, options) + add_shipping_address(doc, payment_method, options) + add_payment_method(doc, payment_method, options) + add_pos(doc, payment_method) + add_descriptor(doc, options) + add_processing_type(doc, options) + add_original_network_transaction(doc, options) + add_merchant_data(doc, options) + add_debt_repayment(doc, options) + add_stored_credential_params(doc, options) end - def build_credit_request(money, identification_or_token, options) - payment_method = build_payment_method(identification_or_token, options) - - hash = create_hash(money, options) - - add_identification_or_cardtoken_hash(hash, payment_method) - - unless payment_method.is_a?(LitleCardToken) - hash['orderSource'] = nil - hash['orderId'] = nil + def add_merchant_data(doc, options={}) + if options[:affiliate] || options[:campaign] || options[:merchant_grouping_id] + doc.merchantData do + doc.affiliate(options[:affiliate]) if options[:affiliate] + doc.campaign(options[:campaign]) if options[:campaign] + doc.merchantGroupingId(options[:merchant_grouping_id]) if options[:merchant_grouping_id] + end end - - hash end - def build_payment_method(payment_method, options) - result = payment_method + def add_echeck_purchase_params(doc, money, payment_method, options) + doc.orderId(truncate(options[:order_id], 24)) + doc.amount(money) + add_order_source(doc, payment_method, options) + add_billing_address(doc, payment_method, options) + add_payment_method(doc, payment_method, options) + add_descriptor(doc, options) + add_processing_type(doc, options) + add_original_network_transaction(doc, options) + end - # Build instance of the LitleCardToken class for internal use if this is a token request. - if payment_method.is_a?(String) && options.has_key?(:token) - result = LitleCardToken.new(:token => payment_method) - result.month = options[:token][:month] - result.year = options[:token][:year] - result.verification_value = options[:token][:verification_value] - result.brand = options[:token][:brand] + def add_descriptor(doc, options) + if options[:descriptor_name] || options[:descriptor_phone] + doc.customBilling do + doc.phone(options[:descriptor_phone]) if options[:descriptor_phone] + doc.descriptor(options[:descriptor_name]) if options[:descriptor_name] + end end + end - result + def add_debt_repayment(doc, options) + doc.debtRepayment(true) if options[:debt_repayment] == true end - def add_creditcard_or_cardtoken_hash(hash, creditcard_or_cardtoken) - if creditcard_or_cardtoken.is_a?(LitleCardToken) - add_cardtoken_hash(hash, creditcard_or_cardtoken) + def add_payment_method(doc, payment_method, options) + if payment_method.is_a?(String) + doc.token do + doc.litleToken(payment_method) + if options[:month].present? && options[:year].present? + doc.expDate("#{options[:month]}#{options[:year]}") + end + end + elsif payment_method.respond_to?(:litle_token) + doc.token do + doc.litleToken(payment_method.litle_token) + if payment_method.try(:month) && payment_method.try(:year) + doc.expDate("#{payment_method.month}#{payment_method.year}") + end + end + elsif payment_method.respond_to?(:paypage_registration_id) + doc.paypage do + doc.paypageRegistrationId(payment_method.paypage_registration_id) + if payment_method.try(:month) && payment_method.try(:year) + doc.expDate("#{payment_method.month}#{payment_method.year}") + end + if payment_method.try(:verification_value) + doc.cardValidationNum(payment_method.verification_value) + end + end + elsif payment_method.respond_to?(:track_data) && payment_method.track_data.present? + doc.card do + doc.track(payment_method.track_data) + end + elsif check?(payment_method) + doc.echeck do + doc.accType(payment_method.account_type.capitalize) + doc.accNum(payment_method.account_number) + doc.routingNum(payment_method.routing_number) + doc.checkNum(payment_method.number) + end else - add_creditcard_hash(hash, creditcard_or_cardtoken) + doc.card do + doc.type_(CARD_TYPE[payment_method.brand]) + doc.number(payment_method.number) + doc.expDate(exp_date(payment_method)) + doc.cardValidationNum(payment_method.verification_value) + end + if payment_method.is_a?(NetworkTokenizationCreditCard) + doc.cardholderAuthentication do + doc.authenticationValue(payment_method.payment_cryptogram) + end + elsif options[:order_source]&.start_with?('3ds') + doc.cardholderAuthentication do + doc.authenticationValue(options[:cavv]) if options[:cavv] + doc.authenticationTransactionId(options[:xid]) if options[:xid] + end + end end end - def add_identification_or_cardtoken_hash(hash, identification_or_cardtoken) - if identification_or_cardtoken.is_a?(LitleCardToken) - add_cardtoken_hash(hash, identification_or_cardtoken) + def add_stored_credential_params(doc, options={}) + return unless options[:stored_credential] + + if options[:stored_credential][:initial_transaction] + add_stored_credential_params_initial(doc, options) else - transaction_id, kind = split_authorization(identification_or_cardtoken) - hash['litleTxnId'] = transaction_id + add_stored_credential_params_used(doc, options) end end - def add_cardtoken_hash(hash, cardtoken) - token_info = {} - token_info['litleToken'] = cardtoken.token - token_info['expDate'] = cardtoken.exp_date if cardtoken.exp_date? - token_info['cardValidationNum'] = cardtoken.verification_value unless cardtoken.verification_value.blank? - token_info['type'] = cardtoken.type unless cardtoken.type.blank? - - hash['token'] = token_info - hash + def add_stored_credential_params_initial(doc, options) + case options[:stored_credential][:reason_type] + when 'unscheduled' + doc.processingType('initialCOF') + when 'installment' + doc.processingType('initialInstallment') + when 'recurring' + doc.processingType('initialRecurring') + end end - def add_creditcard_hash(hash, creditcard) - cc_type = CARD_TYPE[creditcard.brand] - exp_date_yr = creditcard.year.to_s[2..3] - exp_date_mo = '%02d' % creditcard.month.to_i - exp_date = exp_date_mo + exp_date_yr + def add_stored_credential_params_used(doc, options) + if options[:stored_credential][:reason_type] == 'unscheduled' + if options[:stored_credential][:initiator] == 'merchant' + doc.processingType('merchantInitiatedCOF') + else + doc.processingType('cardholderInitiatedCOF') + end + end + doc.originalNetworkTransactionId(options[:stored_credential][:network_transaction_id]) + end - card_info = { - 'type' => cc_type, - 'number' => creditcard.number, - 'expDate' => exp_date, - 'cardValidationNum' => creditcard.verification_value - } + def add_billing_address(doc, payment_method, options) + return if payment_method.is_a?(String) - hash['card'] = card_info - hash - end + doc.billToAddress do + if check?(payment_method) + doc.name(payment_method.name) + doc.firstName(payment_method.first_name) + doc.lastName(payment_method.last_name) + else + doc.name(payment_method.name) + end + doc.email(options[:email]) if options[:email] - def create_capture_hash(money, authorization, options) - hash = create_hash(money, options) - hash['litleTxnId'] = authorization - hash + add_address(doc, options[:billing_address]) + end end - def create_token_hash(creditcard_or_paypage_registration_id, options) - hash = create_hash(0, options) + def add_shipping_address(doc, payment_method, options) + return if payment_method.is_a?(String) - if creditcard_or_paypage_registration_id.is_a?(String) - hash['paypageRegistrationId'] = creditcard_or_paypage_registration_id - else - hash['accountNumber'] = creditcard_or_paypage_registration_id.number + doc.shipToAddress do + add_address(doc, options[:shipping_address]) end - - hash end - def create_void_hash(identification, options) - hash = create_hash(nil, options) - hash['litleTxnId'] = identification - hash + def add_address(doc, address) + return unless address + + doc.companyName(address[:company]) unless address[:company].blank? + doc.addressLine1(address[:address1]) unless address[:address1].blank? + doc.addressLine2(address[:address2]) unless address[:address2].blank? + doc.city(address[:city]) unless address[:city].blank? + doc.state(address[:state]) unless address[:state].blank? + doc.zip(address[:zip]) unless address[:zip].blank? + doc.country(address[:country]) unless address[:country].blank? + doc.phone(address[:phone]) unless address[:phone].blank? end - def create_auth_reversal_hash(identification, money, options) - hash = create_hash(money, options) - hash['litleTxnId'] = identification - hash + def add_order_source(doc, payment_method, options) + order_source = order_source(options) + if order_source + doc.orderSource(order_source) + elsif payment_method.is_a?(NetworkTokenizationCreditCard) && payment_method.source == :apple_pay + doc.orderSource('applepay') + elsif payment_method.is_a?(NetworkTokenizationCreditCard) && payment_method.source == :android_pay + doc.orderSource('androidpay') + elsif payment_method.respond_to?(:track_data) && payment_method.track_data.present? + doc.orderSource('retail') + else + doc.orderSource('ecommerce') + end end - def create_hash(money, options) - fraud_check_type = {} - if options[:ip] - fraud_check_type['customerIpAddress'] = options[:ip] + def order_source(options={}) + return options[:order_source] unless options[:stored_credential] + order_source = nil + + case options[:stored_credential][:reason_type] + when 'unscheduled' + if options[:stored_credential][:initiator] == 'merchant' + # For merchant-initiated, we should always set order source to + # 'ecommerce' + order_source = 'ecommerce' + else + # For cardholder-initiated, we rely on #add_order_source's + # default logic to set orderSource appropriately + order_source = options[:order_source] + end + when 'installment' + order_source = 'installment' + when 'recurring' + order_source = 'recurring' end - enhanced_data = {} - if options[:invoice] - enhanced_data['invoiceReferenceNumber'] = options[:invoice] - end + order_source + end - if options[:description] - enhanced_data['customerReference'] = options[:description] - end + def add_pos(doc, payment_method) + return unless payment_method.respond_to?(:track_data) && payment_method.track_data.present? - if options[:billing_address] - bill_to_address = { - 'name' => options[:billing_address][:name], - 'companyName' => options[:billing_address][:company], - 'addressLine1' => options[:billing_address][:address1], - 'addressLine2' => options[:billing_address][:address2], - 'city' => options[:billing_address][:city], - 'state' => options[:billing_address][:state], - 'zip' => options[:billing_address][:zip], - 'country' => options[:billing_address][:country], - 'email' => options[:email], - 'phone' => options[:billing_address][:phone] - } - end - if options[:shipping_address] - ship_to_address = { - 'name' => options[:shipping_address][:name], - 'companyName' => options[:shipping_address][:company], - 'addressLine1' => options[:shipping_address][:address1], - 'addressLine2' => options[:shipping_address][:address2], - 'city' => options[:shipping_address][:city], - 'state' => options[:shipping_address][:state], - 'zip' => options[:shipping_address][:zip], - 'country' => options[:shipping_address][:country], - 'email' => options[:email], - 'phone' => options[:shipping_address][:phone] - } + doc.pos do + doc.capability('magstripe') + doc.entryMode('completeread') + doc.cardholderId('signature') end + end - hash = { - 'billToAddress' => bill_to_address, - 'shipToAddress' => ship_to_address, - 'orderId' => (options[:order_id] || @options[:order_id]), - 'customerId' => options[:customer], - 'reportGroup' => (options[:merchant] || @options[:merchant]), - 'merchantId' => (options[:merchant_id] || @options[:merchant_id]), - 'orderSource' => (options[:order_source] || 'ecommerce'), - 'enhancedData' => enhanced_data, - 'fraudCheckType' => fraud_check_type, - 'user' => (options[:user] || @options[:user]), - 'password' => (options[:password] || @options[:password]), - 'version' => (options[:version] || @options[:version]), - 'url' => (options[:url] || url), - 'proxy_addr' => (options[:proxy_addr] || @options[:proxy_addr]), - 'proxy_port' => (options[:proxy_port] || @options[:proxy_port]), - 'id' => (options[:id] || options[:order_id] || @options[:order_id]) - } + def add_processing_type(doc, options) + doc.processingType(options[:processing_type]) unless options[:processing_type].blank? + end - if (!money.nil? && money.to_s.length > 0) - hash.merge!({ 'amount' => money }) - end - hash + def add_original_network_transaction(doc, options) + doc.originalNetworkTransactionId(options[:original_network_transaction_id]) unless options[:original_network_transaction_id].blank? end - def fraud_result(authorization_response) - if result = authorization_response['fraudResult'] - if result.key?('cardValidationResult') - cvv_to_pass = result['cardValidationResult'].blank? ? "P" : result['cardValidationResult'] - end + def exp_date(payment_method) + "#{format(payment_method.month, :two_digits)}#{format(payment_method.year, :two_digits)}" + end - avs_to_pass = AVS_RESPONSE_CODE[result['avsResult']] unless result['avsResult'].blank? - end - { 'cvv' => cvv_to_pass, 'avs' => avs_to_pass } - end - - # A +LitleCardToken+ object represents a tokenized credit card, and is capable of validating the various - # data associated with these. - # - # == Example Usage - # token = LitleCardToken.new( - # :token => '1234567890123456', - # :month => '9', - # :year => '2010', - # :brand => 'visa', - # :verification_value => '123' - # ) - # - # token.valid? # => true - # cc.exp_date # => 0910 - # - class LitleCardToken - include Validateable - - # Returns or sets the token. (required) - # - # @return [String] - attr_accessor :token - - # Returns or sets the expiry month for the card associated with token. (optional) - # - # @return [Integer] - attr_accessor :month - - # Returns or sets the expiry year for the card associated with token. (optional) - # - # @return [Integer] - attr_accessor :year - - # Returns or sets the card verification value. (optional) - # - # @return [String] the verification value - attr_accessor :verification_value - - # Returns or sets the credit card brand. (optional) - # - # Valid card types are - # - # * +'visa'+ - # * +'master'+ - # * +'discover'+ - # * +'american_express'+ - # * +'diners_club'+ - # * +'jcb'+ - # * +'switch'+ - # * +'solo'+ - # * +'dankort'+ - # * +'maestro'+ - # * +'forbrugsforeningen'+ - # * +'laser'+ - # - # @return (String) the credit card brand - attr_accessor :brand - - # Returns the Litle credit card type identifier. - # - # @return (String) the credit card type identifier - def type - CARD_TYPE[brand] unless brand.blank? + def parse(kind, xml) + parsed = {} + + doc = Nokogiri::XML(xml).remove_namespaces! + doc.xpath("//litleOnlineResponse/#{kind}Response/*").each do |node| + if node.elements.empty? + parsed[node.name.to_sym] = node.text + else + node.elements.each do |childnode| + name = "#{node.name}_#{childnode.name}" + parsed[name.to_sym] = childnode.text + end + end end - # Returns true if the expiration date is set. - # - # @return (Boolean) - def exp_date? - !month.to_i.zero? && !year.to_i.zero? + if parsed.empty? + %w(response message).each do |attribute| + parsed[attribute.to_sym] = doc.xpath('//litleOnlineResponse').attribute(attribute).value + end end - # Returns the card token expiration date in MMYY format. - # - # @return (String) the expiration date in MMYY format - def exp_date - result = '' - if exp_date? - exp_date_yr = year.to_s[2..3] - exp_date_mo = '%02d' % month.to_i + parsed + end - result = exp_date_mo + exp_date_yr - end - result - end + def commit(kind, request, money=nil) + parsed = parse(kind, ssl_post(url, request, headers)) - # Validates the card token details. - # - # Any validation errors are added to the {#errors} attribute. - def validate - validate_card_token - validate_expiration_date - validate_card_brand - end + options = { + authorization: authorization_from(kind, parsed, money), + test: test?, + :avs_result => { :code => AVS_RESPONSE_CODE[parsed[:fraudResult_avsResult]] }, + :cvv_result => parsed[:fraudResult_cardValidationResult] + } - def check? - false - end + Response.new(success_from(kind, parsed), parsed[:message], parsed, options) + end - private + def success_from(kind, parsed) + return (parsed[:response] == '000') unless kind == :registerToken + %w(000 801 802).include?(parsed[:response]) + end - CARD_TYPE = { - 'visa' => 'VI', - 'master' => 'MC', - 'american_express' => 'AX', - 'discover' => 'DI', - 'jcb' => 'DI', - 'diners_club' => 'DI' - } + def authorization_from(kind, parsed, money) + kind == :registerToken ? parsed[:litleToken] : "#{parsed[:litleTxnId]};#{kind};#{money}" + end - def before_validate #:nodoc: - self.month = month.to_i - self.year = year.to_i - end + def split_authorization(authorization) + transaction_id, kind, money = authorization.to_s.split(';') + [transaction_id, kind, money] + end - # Litle XML Reference Guide 1.8.2 - # - # The length of the original card number is reflected in the token, so a - # submitted 16-digit number results in a 16-digit token. Also, all tokens - # use only numeric characters, so you do not have to change your - # systems to accept alpha-numeric characters. - # - # The credit card token numbers themselves have two parts. - # The last four digits match the last four digits of the card number. - # The remaining digits (length can vary based upon original card number - # length) are a randomly generated. - def validate_card_token #:nodoc: - if token.to_s.length < 12 || token.to_s.match(/\A\d+\Z/).nil? - errors.add :token, "is not a valid card token" - end - end + def transaction_attributes(options) + attributes = {} + attributes[:id] = truncate(options[:id] || options[:order_id], 24) + attributes[:reportGroup] = options[:merchant] || 'Default Report Group' + attributes[:customerId] = options[:customer] + attributes.delete_if { |key, value| value == nil } + attributes + end - def validate_expiration_date #:nodoc: - if !month.to_i.zero? || !year.to_i.zero? - errors.add :month, "is not a valid month" unless valid_month?(month) - errors.add :year, "is not a valid year" unless valid_expiry_year?(year) - end - end + def root_attributes + { + merchantId: @options[:merchant_id], + version: SCHEMA_VERSION, + xmlns: 'http://www.litle.com/schema' + } + end - def validate_card_brand #:nodoc: - errors.add :brand, "is invalid" unless brand.blank? || CreditCard.card_companies.keys.include?(brand) + def build_xml_request + builder = Nokogiri::XML::Builder.new + builder.__send__('litleOnlineRequest', root_attributes) do |doc| + yield(doc) end + builder.doc.root.to_xml + end - def valid_month?(month) - (1..12).include?(month.to_i) - end + def url + test? ? test_url : live_url + end - def valid_expiry_year?(year) - year.to_s =~ /\A\d{4}\Z/ && year.to_i > 1987 - end + def headers + { + 'Content-Type' => 'text/xml' + } end end end diff --git a/lib/active_merchant/billing/gateways/litle/paypage_registration.rb b/lib/active_merchant/billing/gateways/litle/paypage_registration.rb new file mode 100644 index 00000000000..f8f4613295a --- /dev/null +++ b/lib/active_merchant/billing/gateways/litle/paypage_registration.rb @@ -0,0 +1,28 @@ +module ActiveMerchant + module Billing + # Litle provides functionality to make authorization and sale calls + # with a Paypage Registration Id instead of a Litle Token. + # This class wraps the required data for such a request. + # + # The first parameter is the paypage_registration_id and is required. + # + # The second parameter optionally takes a month, year, verification_value, and name. + # These parameters are allowed by the Litle endpoints but not necessary for + # a successful request. + # + # The name parameter is allowed by Vantiv as a member of the billToAddress element. + # It is passed in here to be consistent with the rest of the Litle gateway and Activemerchant. + class LitlePaypageRegistration + attr_reader :paypage_registration_id, :month, :year, :verification_value, :name, :type + + def initialize(paypage_registration_id, options = {}) + @paypage_registration_id = paypage_registration_id + @month = options[:month] + @year = options[:year] + @verification_value = options[:verification_value] + @name = options[:name] + @type = options[:type] + end + end + end +end diff --git a/lib/active_merchant/billing/gateways/mastercard.rb b/lib/active_merchant/billing/gateways/mastercard.rb new file mode 100644 index 00000000000..609039cb9e3 --- /dev/null +++ b/lib/active_merchant/billing/gateways/mastercard.rb @@ -0,0 +1,267 @@ +module ActiveMerchant + module Billing + module MastercardGateway + def initialize(options={}) + requires!(options, :userid, :password) + super + end + + def purchase(amount, payment_method, options={}) + MultiResponse.run do |r| + r.process { authorize(amount, payment_method, options) } + r.process { capture(amount, r.authorization, options) } + end + end + + def authorize(amount, payment_method, options={}) + post = new_post + add_invoice(post, amount, options) + add_reference(post, *new_authorization) + add_payment_method(post, payment_method) + add_customer_data(post, payment_method, options) + add_3dsecure_id(post, options) + + commit('authorize', post) + end + + def capture(amount, authorization, options={}) + post = new_post + add_invoice(post, amount, options, :transaction) + add_reference(post, *next_authorization(authorization)) + add_customer_data(post, nil, options) + add_3dsecure_id(post, options) + + commit('capture', post) + end + + def refund(amount, authorization, options={}) + post = new_post + add_invoice(post, amount, options, :transaction) + add_reference(post, *next_authorization(authorization)) + add_customer_data(post, nil, options) + + commit('refund', post) + end + + def void(authorization, options={}) + post = new_post + add_reference(post, *next_authorization(authorization), :targetTransactionId) + + commit('void', post) + end + + def verify(credit_card, options={}) + MultiResponse.run(:use_first_response) do |r| + r.process { authorize(100, credit_card, options) } + r.process(:ignore_result) { void(r.authorization, options) } + end + end + + def verify_credentials + url = build_url(SecureRandom.uuid, 'nonexistent') + begin + ssl_get(url, headers) + rescue ResponseError => e + return false if e.response.code.to_i == 401 + end + + true + end + + def supports_scrubbing? + true + end + + def scrub(transcript) + transcript. + gsub(%r((Authorization: Basic ).*\\r\\n), '\1[FILTERED]'). + gsub(%r(("number"?\\?":"?\\?")\d*), '\1[FILTERED]'). + gsub(%r(("securityCode"?\\?":"?\\?")\d*), '\1[FILTERED]') + end + + private + + def new_post + { + order: {}, + sourceOfFunds: { + provided: { + card: { + } + } + }, + customer: {}, + billing: {}, + device: {}, + shipping: {}, + transaction: {}, + } + end + + def add_invoice(post, amount, options, node=:order) + post[node][:amount] = amount(amount) + post[node][:currency] = (options[:currency] || currency(amount)) + end + + def add_reference(post, orderid, transactionid, transaction_reference, reference_key=:reference) + post[:orderid] = orderid + post[:transactionid] = transactionid + post[:transaction][reference_key] = transaction_reference if transaction_reference + end + + def add_payment_method(post, payment_method) + card = {} + card[:expiry] = {} + card[:number] = payment_method.number + card[:securityCode] = payment_method.verification_value + card[:expiry][:year] = format(payment_method.year, :two_digits) + card[:expiry][:month] = format(payment_method.month, :two_digits) + + post[:sourceOfFunds][:type] = 'CARD' + post[:sourceOfFunds][:provided][:card].merge!(card) + end + + def add_customer_data(post, payment_method, options) + billing = {} + shipping = {} + customer = {} + device = {} + + customer[:firstName] = payment_method.first_name if payment_method + customer[:lastName] = payment_method.last_name if payment_method + customer[:email] = options[:email] if options[:email] + device[:ipAddress] = options[:ip] if options[:ip] + + if (billing_address = options[:billing_address]) + billing[:address] = {} + billing[:address][:street] = billing_address[:address1] + billing[:address][:street2] = billing_address[:address2] + billing[:address][:city] = billing_address[:city] + billing[:address][:stateProvince] = billing_address[:state] + billing[:address][:postcodeZip] = billing_address[:zip] + billing[:address][:country] = country_code(billing_address[:country]) + customer[:phone] = billing_address[:phone] + end + + if (shipping_address = options[:shipping_address]) + shipping[:address] = {} + shipping[:address][:street] = shipping_address[:address1] + shipping[:address][:street2] = shipping_address[:address2] + shipping[:address][:city] = shipping_address[:city] + shipping[:address][:stateProvince] = shipping_address[:state] + shipping[:address][:postcodeZip] = shipping_address[:zip] + shipping[:address][:shipcountry] = country_code(shipping_address[:country]) + + first_name, last_name = split_names(shipping_address[:name]) + shipping[:firstName] = first_name if first_name + shipping[:lastName] = last_name if last_name + end + post[:billing].merge!(billing) + post[:shipping].merge!(shipping) + post[:device].merge!(device) + post[:customer].merge!(customer) + end + + def add_3dsecure_id(post, options) + return unless options[:threed_secure_id] + post.merge!({'3DSecureId' => options[:threed_secure_id]}) + end + + def country_code(country) + if country + country = ActiveMerchant::Country.find(country) + country.code(:alpha3).value + end + rescue InvalidCountryCodeError + end + + def headers + { + 'Authorization' => 'Basic ' + Base64.encode64("merchant.#{@options[:userid]}:#{@options[:password]}").strip.delete("\r\n"), + 'Content-Type' => 'application/json', + } + end + + def commit(action, post) + url = build_url(post.delete(:orderid), post.delete(:transactionid)) + post[:apiOperation] = action.upcase + begin + raw = parse(ssl_request(:put, url, build_request(post), headers)) + rescue ResponseError => e + raw = parse(e.response.body) + end + succeeded = success_from(raw) + Response.new( + succeeded, + message_from(succeeded, raw), + raw, + :authorization => authorization_from(post, raw), + :test => test? + ) + end + + def build_url(orderid, transactionid) + "#{base_url}merchant/#{@options[:userid]}/order/#{orderid}/transaction/#{transactionid}" + end + + def base_url + if test? + @options[:region] == 'asia_pacific' ? test_ap_url : test_na_url + else + @options[:region] == 'asia_pacific' ? live_ap_url : live_na_url + end + end + + def build_request(post = {}) + post.to_json + end + + def parse(body) + JSON.parse(body) + end + + def success_from(response) + response['result'] == 'SUCCESS' + end + + def message_from(succeeded, response) + if succeeded + 'Succeeded' + else + [ + response['result'], + response['response'] && response['response']['gatewayCode'], + response['error'] && response['error']['cause'], + response['error'] && response['error']['explanation'] + ].compact.join(' - ') + end + end + + def authorization_from(request, response) + [response['order']['id'], response['transaction']['id']].join('|') if response['order'] + end + + def split_authorization(authorization) + authorization.split('|') + end + + def new_authorization + # Must be unique within a merchant id. + orderid = SecureRandom.uuid + + # Must be unique within an order id. + transactionid = '1' + + # New transactions have no previous reference. + transaction_reference = nil + [orderid, transactionid, transaction_reference] + end + + def next_authorization(authorization) + orderid, prev_transactionid = split_authorization(authorization) + next_transactionid = SecureRandom.uuid + [orderid, next_transactionid, prev_transactionid] + end + end + end +end diff --git a/lib/active_merchant/billing/gateways/maxipago.rb b/lib/active_merchant/billing/gateways/maxipago.rb new file mode 100644 index 00000000000..b4ca346327b --- /dev/null +++ b/lib/active_merchant/billing/gateways/maxipago.rb @@ -0,0 +1,220 @@ +require 'nokogiri' + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class MaxipagoGateway < Gateway + API_VERSION = '3.1.1.15' + + self.live_url = 'https://api.maxipago.net/UniversalAPI/postXML' + self.test_url = 'https://testapi.maxipago.net/UniversalAPI/postXML' + + self.supported_countries = ['BR'] + self.default_currency = 'BRL' + self.money_format = :dollars + self.supported_cardtypes = [:visa, :master, :discover, :american_express, :diners_club] + self.homepage_url = 'http://www.maxipago.com/' + self.display_name = 'maxiPago!' + + def initialize(options = {}) + requires!(options, :login, :password) + super + end + + def purchase(money, creditcard, options = {}) + commit(:sale) do |xml| + add_auth_purchase(xml, money, creditcard, options) + end + end + + def authorize(money, creditcard, options = {}) + commit(:auth) do |xml| + add_auth_purchase(xml, money, creditcard, options) + end + end + + def capture(money, authorization, options = {}) + commit(:capture) do |xml| + add_order_id(xml, authorization) + add_reference_num(xml, options) + xml.payment do + add_amount(xml, money, options) + end + end + end + + def void(authorization, options = {}) + _, transaction_id = split_authorization(authorization) + commit(:void) do |xml| + xml.transactionID transaction_id + end + end + + def refund(money, authorization, options = {}) + commit(:return) do |xml| + add_order_id(xml, authorization) + add_reference_num(xml, options) + xml.payment do + add_amount(xml, money, options) + end + end + end + + def verify(creditcard, options = {}) + MultiResponse.run(:use_first_response) do |r| + r.process { authorize(100, creditcard, options) } + r.process(:ignore_result) { void(r.authorization, options) } + end + end + + def supports_scrubbing? + true + end + + def scrub(transcript) + transcript. + gsub(%r(()[^<]*())i, '\1[FILTERED]\2'). + gsub(%r(()[^<]*())i, '\1[FILTERED]\2'). + gsub(%r(()[^<]*())i, '\1[FILTERED]\2') + end + + private + + def commit(action) + request = build_xml_request(action) { |doc| yield(doc) } + response = parse(ssl_post(url, request, 'Content-Type' => 'text/xml')) + + Response.new( + success?(response), + message_from(response), + response, + test: test?, + authorization: authorization_from(response) + ) + end + + def url + test? ? self.test_url : self.live_url + end + + def build_xml_request(action) + builder = Nokogiri::XML::Builder.new(encoding: 'UTF-8') + builder.send('transaction-request') do |xml| + xml.version '3.1.1.15' + xml.verification do + xml.merchantId @options[:login] + xml.merchantKey @options[:password] + end + xml.order do + xml.send("#{action}!") do + yield(xml) + end + end + end + + builder.to_xml(indent: 2) + end + + def success?(response) + response[:response_code] == '0' + end + + def message_from(response) + response[:error_message] || response[:response_message] || response[:processor_message] || response[:error_msg] + end + + def authorization_from(response) + "#{response[:order_id]}|#{response[:transaction_id]}" + end + + def split_authorization(authorization) + authorization.split('|') + end + + def parse(body) + xml = REXML::Document.new(body) + + response = {} + xml.root.elements.to_a.each do |node| + parse_element(response, node) + end + response + end + + def parse_element(response, node) + if node.has_elements? + node.elements.each { |element| parse_element(response, element) } + else + response[node.name.underscore.to_sym] = node.text + end + end + + def add_auth_purchase(xml, money, creditcard, options) + add_processor_id(xml) + xml.fraudCheck('N') + add_reference_num(xml, options) + xml.transactionDetail do + xml.payType do + xml.creditCard do + xml.number(creditcard.number) + xml.expMonth(creditcard.month) + xml.expYear(creditcard.year) + xml.cvvNumber(creditcard.verification_value) + end + end + end + xml.payment do + add_amount(xml, money, options) + add_installments(xml, options) + end + add_billing_address(xml, creditcard, options) + end + + def add_reference_num(xml, options) + xml.referenceNum(options[:order_id] || generate_unique_id) + end + + def add_amount(xml, money, options) + xml.chargeTotal(amount(money)) + xml.currencyCode(options[:currency] || currency(money) || default_currency) + end + + def add_processor_id(xml) + if test? + xml.processorID(1) + else + xml.processorID(@options[:processor_id] || 4) + end + end + + def add_installments(xml, options) + if options.has_key?(:installments) && options[:installments] > 1 + xml.creditInstallment do + xml.numberOfInstallments options[:installments] + xml.chargeInterest 'N' + end + end + end + + def add_billing_address(xml, creditcard, options) + address = options[:billing_address] + return unless address + + xml.billing do + xml.name creditcard.name + xml.address address[:address1] if address[:address1] + xml.address2 address[:address2] if address[:address2] + xml.city address[:city] if address[:city] + xml.state address[:state] if address[:state] + xml.postalcode address[:zip] if address[:zip] + xml.country address[:country] if address[:country] + xml.phone address[:phone] if address[:phone] + end + end + + def add_order_id(xml, authorization) + order_id, _ = split_authorization(authorization) + xml.orderID order_id + end + end + end +end diff --git a/lib/active_merchant/billing/gateways/mercado_pago.rb b/lib/active_merchant/billing/gateways/mercado_pago.rb new file mode 100644 index 00000000000..98cc40bb374 --- /dev/null +++ b/lib/active_merchant/billing/gateways/mercado_pago.rb @@ -0,0 +1,277 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class MercadoPagoGateway < Gateway + self.live_url = self.test_url = 'https://api.mercadopago.com/v1' + + self.supported_countries = ['AR', 'BR', 'CL', 'CO', 'MX', 'PE', 'UY'] + self.supported_cardtypes = [:visa, :master, :american_express, :elo, :cabal, :naranja] + + self.homepage_url = 'https://www.mercadopago.com/' + self.display_name = 'Mercado Pago' + self.money_format = :dollars + + def initialize(options={}) + requires!(options, :access_token) + super + end + + def purchase(money, payment, options={}) + MultiResponse.run do |r| + r.process { commit('tokenize', 'card_tokens', card_token_request(money, payment, options)) } + options[:card_token] = r.authorization.split('|').first + r.process { commit('purchase', 'payments', purchase_request(money, payment, options)) } + end + end + + def authorize(money, payment, options={}) + MultiResponse.run do |r| + r.process { commit('tokenize', 'card_tokens', card_token_request(money, payment, options)) } + options[:card_token] = r.authorization.split('|').first + r.process { commit('authorize', 'payments', authorize_request(money, payment, options)) } + end + end + + def capture(money, authorization, options={}) + post = {} + authorization, _ = authorization.split('|') + post[:capture] = true + post[:transaction_amount] = amount(money).to_f + commit('capture', "payments/#{authorization}", post) + end + + def refund(money, authorization, options={}) + post = {} + authorization, original_amount = authorization.split('|') + post[:amount] = amount(money).to_f if original_amount && original_amount.to_f > amount(money).to_f + commit('refund', "payments/#{authorization}/refunds", post) + end + + def void(authorization, options={}) + authorization, _ = authorization.split('|') + post = { status: 'cancelled' } + commit('void', "payments/#{authorization}", post) + end + + def verify(credit_card, options={}) + MultiResponse.run(:use_first_response) do |r| + r.process { authorize(100, credit_card, options) } + r.process(:ignore_result) { void(r.authorization, options) } + end + end + + def supports_scrubbing? + true + end + + def scrub(transcript) + transcript. + gsub(%r((access_token=).*?([^\s]+)), '\1[FILTERED]'). + gsub(%r((\"card_number\\\":\\\")\d+), '\1[FILTERED]'). + gsub(%r((\"security_code\\\":\\\")\d+), '\1[FILTERED]') + end + + private + + def card_token_request(money, payment, options = {}) + post = {} + post[:card_number] = payment.number + post[:security_code] = payment.verification_value + post[:expiration_month] = payment.month + post[:expiration_year] = payment.year + post[:cardholder] = { + name: payment.name, + identification: { + type: options[:cardholder_identification_type], + number: options[:cardholder_identification_number] + } + } + post + end + + def purchase_request(money, payment, options = {}) + post = {} + add_invoice(post, money, options) + add_payment(post, options) + add_additional_data(post, options) + add_customer_data(post, payment, options) + add_address(post, options) + add_processing_mode(post, options) + post[:binary_mode] = (options[:binary_mode].nil? ? true : options[:binary_mode]) + post + end + + def authorize_request(money, payment, options = {}) + post = purchase_request(money, payment, options) + post[:capture] = false + post + end + + def add_processing_mode(post, options) + return unless options[:processing_mode] + post[:processing_mode] = options[:processing_mode] + post[:merchant_account_id] = options[:merchant_account_id] if options[:merchant_account_id] + add_merchant_services(post, options) + end + + def add_merchant_services(post, options) + return unless options[:fraud_scoring] || options[:fraud_manual_review] + merchant_services = {} + merchant_services[:fraud_scoring] = options[:fraud_scoring] if options[:fraud_scoring] + merchant_services[:fraud_manual_review] = options[:fraud_manual_review] if options[:fraud_manual_review] + post[:merchant_services] = merchant_services + end + + def add_additional_data(post, options) + post[:sponsor_id] = options[:sponsor_id] + post[:device_id] = options[:device_id] if options[:device_id] + post[:additional_info] = { + ip_address: options[:ip_address] + }.merge(options[:additional_info] || {}) + + add_address(post, options) + add_shipping_address(post, options) + end + + def add_customer_data(post, payment, options) + post[:payer] = { + email: options[:email], + first_name: payment.first_name, + last_name: payment.last_name + } + end + + def add_address(post, options) + if address = (options[:billing_address] || options[:address]) + + post[:additional_info].merge!({ + payer: { + address: { + zip_code: address[:zip], + street_name: "#{address[:address1]} #{address[:address2]}" + } + } + }) + end + end + + def add_shipping_address(post, options) + if address = options[:shipping_address] + + post[:additional_info].merge!({ + shipments: { + receiver_address: { + zip_code: address[:zip], + street_name: "#{address[:address1]} #{address[:address2]}" + } + } + }) + end + end + + def split_street_address(address1) + street_number = address1.split(' ').first + + if street_name = address1.split(' ')[1..-1] + street_name = street_name.join(' ') + else + nil + end + + [street_number, street_name] + end + + def add_invoice(post, money, options) + post[:transaction_amount] = amount(money).to_f + post[:description] = options[:description] + post[:installments] = options[:installments] ? options[:installments].to_i : 1 + post[:statement_descriptor] = options[:statement_descriptor] if options[:statement_descriptor] + post[:external_reference] = options[:order_id] || SecureRandom.hex(16) + end + + def add_payment(post, options) + post[:token] = options[:card_token] + post[:issuer_id] = options[:issuer_id] if options[:issuer_id] + post[:payment_method_id] = options[:payment_method_id] if options[:payment_method_id] + end + + def parse(body) + JSON.parse(body) + rescue JSON::ParserError + { + 'status' => 'error', + 'status_detail' => 'json_parse_error', + 'message' => "A non-JSON response was received from Mercado Pago where one was expected. The raw response was:\n\n#{body}" + } + end + + def commit(action, path, parameters) + if ['capture', 'void'].include?(action) + response = parse(ssl_request(:put, url(path), post_data(parameters), headers)) + else + response = parse(ssl_post(url(path), post_data(parameters), headers(parameters))) + end + + Response.new( + success_from(action, response), + message_from(response), + response, + authorization: authorization_from(response, parameters), + test: test?, + error_code: error_code_from(action, response) + ) + end + + def success_from(action, response) + if action == 'refund' + response['status'] != 404 && response['error'].nil? + else + ['active', 'approved', 'authorized', 'cancelled', 'in_process'].include?(response['status']) + end + end + + def message_from(response) + (response['status_detail']) || (response['message']) + end + + def authorization_from(response, params) + [response['id'], params[:transaction_amount]].join('|') + end + + def post_data(parameters = {}) + parameters.clone.tap { |p| p.delete(:device_id) }.to_json + end + + def error_code_from(action, response) + unless success_from(action, response) + if cause = response['cause'] + cause.empty? ? nil : cause.first['code'] + else + response['status'] + end + end + end + + def url(action) + full_url = (test? ? test_url : live_url) + full_url + "/#{action}?access_token=#{CGI.escape(@options[:access_token])}" + end + + def headers(options = {}) + headers = { + 'Content-Type' => 'application/json' + } + headers['X-Device-Session-ID'] = options[:device_id] if options[:device_id] + headers + end + + def handle_response(response) + case response.code.to_i + when 200..499 + response.body + else + raise ResponseError.new(response) + end + end + end + end +end diff --git a/lib/active_merchant/billing/gateways/merchant_e_solutions.rb b/lib/active_merchant/billing/gateways/merchant_e_solutions.rb index 7e5d8db0e4a..bad8070e2d1 100644 --- a/lib/active_merchant/billing/gateways/merchant_e_solutions.rb +++ b/lib/active_merchant/billing/gateways/merchant_e_solutions.rb @@ -1,6 +1,8 @@ module ActiveMerchant #:nodoc: module Billing #:nodoc: class MerchantESolutionsGateway < Gateway + include Empty + self.test_url = 'https://cert.merchante-solutions.com/mes-api/tridentApi' self.live_url = 'https://api.merchante-solutions.com/mes-api/tridentApi' @@ -28,6 +30,7 @@ def authorize(money, creditcard_or_card_id, options = {}) add_invoice(post, options) add_payment_source(post, creditcard_or_card_id, options) add_address(post, options) + add_3dsecure_params(post, options) commit('P', money, post) end @@ -38,6 +41,7 @@ def purchase(money, creditcard_or_card_id, options = {}) add_invoice(post, options) add_payment_source(post, creditcard_or_card_id, options) add_address(post, options) + add_3dsecure_params(post, options) commit('D', money, post) end @@ -45,6 +49,8 @@ def capture(money, transaction_id, options = {}) post ={} post[:transaction_id] = transaction_id post[:client_reference_number] = options[:customer] if options.has_key?(:customer) + add_invoice(post, options) + add_3dsecure_params(post, options) commit('S', money, post) end @@ -88,6 +94,17 @@ def void(transaction_id, options = {}) commit('V', nil, options.merge(post)) end + def supports_scrubbing? + true + end + + def scrub(transcript) + transcript. + gsub(%r((&?profile_key=)\w*(&?)), '\1[FILTERED]\2'). + gsub(%r((&?card_number=)\d*(&?)), '\1[FILTERED]\2'). + gsub(%r((&?cvv2=)\d*(&?)), '\1[FILTERED]\2') + end + private def add_address(post, options) @@ -99,7 +116,8 @@ def add_address(post, options) def add_invoice(post, options) if options.has_key? :order_id - post[:invoice_number] = options[:order_id].to_s.gsub(/[^\w.]/, '') + order_id = options[:order_id].to_s.gsub(/[^\w.]/, '') + post[:invoice_number] = truncate(order_id, 17) end end @@ -120,10 +138,17 @@ def add_creditcard(post, creditcard, options) post[:card_exp_date] = expdate(creditcard) end + def add_3dsecure_params(post, options) + post[:xid] = options[:xid] unless empty?(options[:xid]) + post[:cavv] = options[:cavv] unless empty?(options[:cavv]) + post[:ucaf_collection_ind] = options[:ucaf_collection_ind] unless empty?(options[:ucaf_collection_ind]) + post[:ucaf_auth_data] = options[:ucaf_auth_data] unless empty?(options[:ucaf_auth_data]) + end + def parse(body) results = {} body.split(/&/).each do |pair| - key,val = pair.split(/=/) + key, val = pair.split(/=/) results[key] = val end results @@ -133,32 +158,25 @@ def commit(action, money, parameters) url = test? ? self.test_url : self.live_url parameters[:transaction_amount] = amount(money) if money unless action == 'V' - response = begin - parse( ssl_post(url, post_data(action,parameters)) ) + parse(ssl_post(url, post_data(action, parameters))) rescue ActiveMerchant::ResponseError => e - { "error_code" => "404", "auth_response_text" => e.to_s } + { 'error_code' => '404', 'auth_response_text' => e.to_s } end - Response.new(response["error_code"] == "000", message_from(response), response, - :authorization => response["transaction_id"], + Response.new(response['error_code'] == '000', message_from(response), response, + :authorization => response['transaction_id'], :test => test?, - :cvv_result => response["cvv2_result"], - :avs_result => { :code => response["avs_result"] } + :cvv_result => response['cvv2_result'], + :avs_result => { :code => response['avs_result'] } ) end - def expdate(creditcard) - year = sprintf("%.4i", creditcard.year) - month = sprintf("%.2i", creditcard.month) - "#{month}#{year[-2..-1]}" - end - def message_from(response) - if response["error_code"] == "000" - "This transaction has been approved" + if response['error_code'] == '000' + 'This transaction has been approved' else - response["auth_response_text"] + response['auth_response_text'] end end @@ -168,7 +186,7 @@ def post_data(action, parameters = {}) post[:profile_key] = @options[:password] post[:transaction_type] = action if action - request = post.merge(parameters).map {|key,value| "#{key}=#{CGI.escape(value.to_s)}"}.join("&") + request = post.merge(parameters).map { |key, value| "#{key}=#{CGI.escape(value.to_s)}" }.join('&') request end end diff --git a/lib/active_merchant/billing/gateways/merchant_one.rb b/lib/active_merchant/billing/gateways/merchant_one.rb index b85844c7bdd..20f60dd0cd2 100644 --- a/lib/active_merchant/billing/gateways/merchant_one.rb +++ b/lib/active_merchant/billing/gateways/merchant_one.rb @@ -1,4 +1,4 @@ -require "cgi" +require 'cgi' module ActiveMerchant #:nodoc: module Billing #:nodoc: @@ -8,7 +8,6 @@ class MerchantOneSslConnection < ActiveMerchant::Connection def configure_ssl(http) super(http) http.use_ssl = true - http.ssl_version = :SSLv3 end end @@ -47,7 +46,7 @@ def purchase(money, creditcard, options = {}) def capture(money, authorization, options = {}) post = {} - post.merge!(:transactionid => authorization) + post[:transactionid] = authorization add_amount(post, money, options) commit('capture', money, post) end @@ -76,40 +75,39 @@ def add_address(post, creditcard, options) end def add_creditcard(post, creditcard) - post['cvv'] = creditcard.verification_value - post['ccnumber'] = creditcard.number - post['ccexp'] = "#{sprintf("%02d", creditcard.month)}#{"#{creditcard.year}"[-2, 2]}" + post['cvv'] = creditcard.verification_value + post['ccnumber'] = creditcard.number + post['ccexp'] = "#{sprintf("%02d", creditcard.month)}#{creditcard.year.to_s[-2, 2]}" end def commit(action, money, parameters={}) parameters['username'] = @options[:username] parameters['password'] = @options[:password] - parse(ssl_post(BASE_URL,post_data(action, parameters))) + parse(ssl_post(BASE_URL, post_data(action, parameters))) end def post_data(action, parameters = {}) - parameters.merge!({:type => action}) - ret = "" + parameters[:type] = action + ret = '' for key in parameters.keys ret += "#{key}=#{CGI.escape(parameters[key].to_s)}" if key != parameters.keys.last - ret += "&" + ret += '&' end end ret.to_s end def parse(data) - responses = CGI.parse(data).inject({}){|h,(k, v)| h[k] = v.first; h} + responses = CGI.parse(data).inject({}) { |h, (k, v)| h[k] = v.first; h } Response.new( - (responses["response"].to_i == 1), - responses["responsetext"], + (responses['response'].to_i == 1), + responses['responsetext'], responses, :test => test?, - :authorization => responses["transactionid"] + :authorization => responses['transactionid'] ) end end end end - diff --git a/lib/active_merchant/billing/gateways/merchant_partners.rb b/lib/active_merchant/billing/gateways/merchant_partners.rb new file mode 100644 index 00000000000..e4630211a5d --- /dev/null +++ b/lib/active_merchant/billing/gateways/merchant_partners.rb @@ -0,0 +1,245 @@ +require 'nokogiri' + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class MerchantPartnersGateway < Gateway + self.display_name = 'Merchant Partners' + self.homepage_url = 'http://www.merchantpartners.com/' + + self.live_url = 'https://trans.merchantpartners.com/cgi-bin/ProcessXML.cgi' + + self.supported_countries = ['US'] + self.default_currency = 'USD' + self.money_format = :dollars + self.supported_cardtypes = [:visa, :master, :american_express, :discover, :diners_club, :jcb] + + def initialize(options={}) + requires!(options, :account_id, :merchant_pin) + super + end + + def purchase(amount, payment_method, options={}) + post = {} + add_invoice(post, amount, options) + add_payment_method(post, payment_method) + add_customer_data(post, options) + + commit(payment_method.is_a?(String) ? :stored_purchase : :purchase, post) + end + + def authorize(amount, payment_method, options={}) + post = {} + add_invoice(post, amount, options) + add_payment_method(post, payment_method) + add_customer_data(post, options) + + commit(:authorize, post) + end + + def capture(amount, authorization, options={}) + post = {} + add_invoice(post, amount, options) + add_reference(post, authorization) + add_customer_data(post, options) + + commit(:capture, post) + end + + def void(authorization, options={}) + post = {} + add_reference(post, authorization) + + commit(:void, post) + end + + def refund(amount, authorization, options={}) + post = {} + add_invoice(post, amount, options) + add_reference(post, authorization) + add_customer_data(post, options) + + commit(:refund, post) + end + + def credit(amount, payment_method, options={}) + post = {} + add_invoice(post, amount, options) + add_payment_method(post, payment_method) + + commit(payment_method.is_a?(String) ? :stored_credit : :credit, post) + end + + def verify(credit_card, options={}) + MultiResponse.run(:use_first_response) do |r| + r.process { authorize(100, credit_card, options) } + r.process(:ignore_result) { void(r.authorization, options) } + end + end + + def store(payment_method, options = {}) + post = {} + add_payment_method(post, payment_method) + add_customer_data(post, options) + + post[:profileactiontype] = options[:profileactiontype] || STORE_TX_TYPES[:store_only] + + commit(:store, post) + end + + def supports_scrubbing? + true + end + + def scrub(transcript) + transcript. + gsub(%r(()[^<]+(<))i, '\1[FILTERED]\2'). + gsub(%r(()[^<]+(<))i, '\1[FILTERED]\2'). + gsub(%r(()[^<]+(<))i, '\1[FILTERED]\2') + end + + def test? + @options[:account_id].eql?('TEST0') + end + + private + + def add_invoice(post, money, options) + post[:amount] = amount(money) + post[:merchantordernumber] = options[:order_id] + post[:currency] = options[:currency] || currency(money) + end + + def add_payment_method(post, payment_method) + if(payment_method.is_a?(String)) + user_profile_id, last_4 = split_authorization(payment_method) + post[:userprofileid] = user_profile_id + post[:last4digits] = last_4 + else + post[:ccname] = payment_method.name + post[:ccnum] = payment_method.number + post[:cvv2] = payment_method.verification_value + post[:expmon] = format(payment_method.month, :two_digits) + post[:expyear] = format(payment_method.year, :four_digits) + post[:swipedata] = payment_method.track_data if payment_method.track_data + end + end + + def add_customer_data(post, options) + post[:email] = options[:email] if options[:email] + post[:ipaddress] = options[:ip] if options[:ip] + if(billing_address = options[:billing_address]) + post[:billaddr1] = billing_address[:address1] + post[:billaddr2] = billing_address[:address2] + post[:billcity] = billing_address[:city] + post[:billstate] = billing_address[:state] + post[:billcountry] = billing_address[:country] + post[:bilzip] = billing_address[:zip] + post[:phone] = billing_address[:phone] + end + end + + def add_reference(post, authorization) + post[:historykeyid] = authorization + end + + ACTIONS = { + purchase: '2', + authorize: '1', + capture: '3', + void: '5', + refund: '4', + credit: '6', + store: '7', + stored_purchase: '8', + stored_credit: '13' + } + + STORE_TX_TYPES = { + store_only: '3' + } + + def commit(action, post) + post[:acctid] = @options[:account_id] + post[:merchantpin] = @options[:merchant_pin] + post[:service] = ACTIONS[action] if ACTIONS[action] + + data = build_request(post) + response_data = parse(ssl_post(live_url, data, headers)) + succeeded = success_from(response_data) + + Response.new( + succeeded, + message_from(succeeded, response_data), + response_data, + authorization: authorization_from(post, response_data), + :avs_result => AVSResult.new(code: response_data['avs_response']), + :cvv_result => CVVResult.new(response_data['cvv2_response']), + test: test? + ) + end + + def headers + { + 'Content-Type' => 'application/xml' + } + end + + def build_request(post) + Nokogiri::XML::Builder.new(:encoding => 'utf-8') do |xml| + xml.interface_driver { + xml.trans_catalog { + xml.transaction(name: 'creditcard') { + xml.inputs { + post.each do |field, value| + xml.send(field, value) + end + } + } + } + } + end.to_xml + end + + def parse(body) + response = {} + Nokogiri::XML(CGI.unescapeHTML(body)).xpath('//trans_catalog/transaction/outputs').children.each do |node| + parse_element(response, node) + end + response + end + + def parse_element(response, node) + if node.elements.size == 0 + response[node.name.downcase.underscore.to_sym] = node.text + else + node.elements.each { |element| parse_element(response, element) } + end + end + + def success_from(response) + response[:status] == 'Approved' + end + + def message_from(succeeded, response) + succeeded ? 'Succeeded' : error_message_from(response) + end + + def authorization_from(request, response) + request[:service] == ACTIONS[:store] ? + "#{response[:userprofileid]}|#{response[:last4digits]}" : + response[:historyid] + end + + def split_authorization(authorization) + authorization.split('|') + end + + def error_message_from(response) + if(response[:status] == 'Declined') + match = response[:result].match(/DECLINED:\d{10}:(.+):/) + match[1] if match + end + end + end + end +end diff --git a/lib/active_merchant/billing/gateways/merchant_ware.rb b/lib/active_merchant/billing/gateways/merchant_ware.rb index a7609f458b8..d028024ccb0 100644 --- a/lib/active_merchant/billing/gateways/merchant_ware.rb +++ b/lib/active_merchant/billing/gateways/merchant_ware.rb @@ -11,25 +11,25 @@ class MerchantWareGateway < Gateway self.homepage_url = 'http://merchantwarehouse.com/merchantware' self.display_name = 'MerchantWARE' - ENV_NAMESPACES = { "xmlns:xsi" => "http://www.w3.org/2001/XMLSchema-instance", - "xmlns:xsd" => "http://www.w3.org/2001/XMLSchema", - "xmlns:env" => "http://schemas.xmlsoap.org/soap/envelope/" + ENV_NAMESPACES = { 'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance', + 'xmlns:xsd' => 'http://www.w3.org/2001/XMLSchema', + 'xmlns:env' => 'http://schemas.xmlsoap.org/soap/envelope/' } - ENV_NAMESPACES_V4 = { "xmlns:xsi" => "http://www.w3.org/2001/XMLSchema-instance", - "xmlns:xsd" => "http://www.w3.org/2001/XMLSchema", - "xmlns:soap" => "http://schemas.xmlsoap.org/soap/envelope/" + ENV_NAMESPACES_V4 = { 'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance', + 'xmlns:xsd' => 'http://www.w3.org/2001/XMLSchema', + 'xmlns:soap' => 'http://schemas.xmlsoap.org/soap/envelope/' } - TX_NAMESPACE = "http://merchantwarehouse.com/MerchantWARE/Client/TransactionRetail" - TX_NAMESPACE_V4 = "http://schemas.merchantwarehouse.com/merchantware/40/Credit/" + TX_NAMESPACE = 'http://merchantwarehouse.com/MerchantWARE/Client/TransactionRetail' + TX_NAMESPACE_V4 = 'http://schemas.merchantwarehouse.com/merchantware/40/Credit/' ACTIONS = { - :purchase => "IssueKeyedSale", - :authorize => "IssueKeyedPreAuth", - :capture => "IssuePostAuth", - :void => "VoidPreAuthorization", - :credit => "IssueKeyedRefund", - :reference_credit => "IssueRefundByReference" + :purchase => 'IssueKeyedSale', + :authorize => 'IssueKeyedPreAuth', + :capture => 'IssuePostAuth', + :void => 'VoidPreAuthorization', + :credit => 'IssueKeyedRefund', + :reference_credit => 'IssueRefundByReference' } # Creates a new MerchantWareGateway @@ -105,7 +105,7 @@ def void(authorization, options = {}) # * :order_id - A unique reference for this order (required when performing a non-referenced credit) def credit(money, identification, options = {}) if identification.is_a?(String) - deprecated CREDIT_DEPRECATION_MESSAGE + ActiveMerchant.deprecated CREDIT_DEPRECATION_MESSAGE refund(money, identification, options) else perform_credit(money, identification, options) @@ -121,9 +121,9 @@ def refund(money, reference, options = {}) def soap_request(action) xml = Builder::XmlMarkup.new :indent => 2 xml.instruct! - xml.tag! "env:Envelope", ENV_NAMESPACES do - xml.tag! "env:Body" do - xml.tag! ACTIONS[action], "xmlns" => TX_NAMESPACE do + xml.tag! 'env:Envelope', ENV_NAMESPACES do + xml.tag! 'env:Body' do + xml.tag! ACTIONS[action], 'xmlns' => TX_NAMESPACE do add_credentials(xml) yield xml end @@ -135,12 +135,12 @@ def soap_request(action) def v4_soap_request(action) xml = Builder::XmlMarkup.new :indent => 2 xml.instruct! - xml.tag! "soap:Envelope", ENV_NAMESPACES_V4 do - xml.tag! "soap:Body" do - xml.tag! ACTIONS[:void], "xmlns" => TX_NAMESPACE_V4 do - xml.tag! "merchantName", @options[:name] - xml.tag! "merchantSiteId", @options[:login] - xml.tag! "merchantKey", @options[:password] + xml.tag! 'soap:Envelope', ENV_NAMESPACES_V4 do + xml.tag! 'soap:Body' do + xml.tag! ACTIONS[:void], 'xmlns' => TX_NAMESPACE_V4 do + xml.tag! 'merchantName', @options[:name] + xml.tag! 'merchantSiteId', @options[:login] + xml.tag! 'merchantKey', @options[:password] yield xml end end @@ -151,7 +151,7 @@ def v4_soap_request(action) def build_purchase_request(action, money, credit_card, options) requires!(options, :order_id) - request = soap_request(action) do |xml| + soap_request(action) do |xml| add_invoice(xml, options) add_amount(xml, money) add_credit_card(xml, credit_card) @@ -162,7 +162,7 @@ def build_purchase_request(action, money, credit_card, options) def build_capture_request(action, money, identification, options) reference, options[:order_id] = split_reference(identification) - request = soap_request(action) do |xml| + soap_request(action) do |xml| add_reference(xml, reference) add_invoice(xml, options) add_amount(xml, money) @@ -175,7 +175,7 @@ def perform_reference_credit(money, identification, options) request = soap_request(:reference_credit) do |xml| add_reference(xml, reference) add_invoice(xml, options) - add_amount(xml, money, "strOverrideAmount") + add_amount(xml, money, 'strOverrideAmount') end commit(:reference_credit, request) @@ -194,50 +194,47 @@ def perform_credit(money, credit_card, options) end def add_credentials(xml) - xml.tag! "strSiteId", @options[:login] - xml.tag! "strKey", @options[:password] - xml.tag! "strName", @options[:name] - end - - def expdate(credit_card) - year = sprintf("%.4i", credit_card.year) - month = sprintf("%.2i", credit_card.month) - - "#{month}#{year[-2..-1]}" + xml.tag! 'strSiteId', @options[:login] + xml.tag! 'strKey', @options[:password] + xml.tag! 'strName', @options[:name] end def add_invoice(xml, options) - xml.tag! "strOrderNumber", options[:order_id].to_s.gsub(/[^\w]/, '').slice(0, 25) + xml.tag! 'strOrderNumber', options[:order_id].to_s.gsub(/[^\w]/, '').slice(0, 25) end - def add_amount(xml, money, tag = "strAmount") + def add_amount(xml, money, tag = 'strAmount') xml.tag! tag, amount(money) end def add_reference(xml, reference) - xml.tag! "strReferenceCode", reference + xml.tag! 'strReferenceCode', reference end def add_reference_token(xml, reference) - xml.tag! "token", reference + xml.tag! 'token', reference end def add_address(xml, options) if address = options[:billing_address] || options[:address] - xml.tag! "strAVSStreetAddress", address[:address1] - xml.tag! "strAVSZipCode", address[:zip] + xml.tag! 'strAVSStreetAddress', address[:address1] + xml.tag! 'strAVSZipCode', address[:zip] end end def add_credit_card(xml, credit_card) - xml.tag! "strPAN", credit_card.number - xml.tag! "strExpDate", expdate(credit_card) - xml.tag! "strCardHolder", credit_card.name - xml.tag! "strCVCode", credit_card.verification_value if credit_card.verification_value? + if credit_card.respond_to?(:track_data) && credit_card.track_data.present? + xml.tag! 'trackData', credit_card.track_data + else + xml.tag! 'strPAN', credit_card.number + xml.tag! 'strExpDate', expdate(credit_card) + xml.tag! 'strCardHolder', credit_card.name + xml.tag! 'strCVCode', credit_card.verification_value if credit_card.verification_value? + end end def split_reference(reference) - reference.to_s.split(";") + reference.to_s.split(';') end def parse(action, data) @@ -250,10 +247,10 @@ def parse(action, data) response[element.name] = element.text end - status, code, message = response["ApprovalStatus"].split(";") + status, code, message = response['ApprovalStatus'].split(';') response[:status] = status - if response[:success] = status == "APPROVED" + if response[:success] = status == 'APPROVED' response[:message] = status else response[:message] = message @@ -271,17 +268,17 @@ def parse_error(http_response) document = REXML::Document.new(http_response.body) - node = REXML::XPath.first(document, "//soap:Fault") + node = REXML::XPath.first(document, '//soap:Fault') node.elements.each do |element| response[element.name] = element.text end - response[:message] = response["faultstring"].to_s.gsub("\n", " ") + response[:message] = response['faultstring'].to_s.gsub("\n", ' ') response - rescue REXML::ParseException => e + rescue REXML::ParseException response[:http_body] = http_response.body - response[:message] = "Failed to parse the failed response" + response[:message] = 'Failed to parse the failed response' response end @@ -296,9 +293,9 @@ def url(v4 = false) def commit(action, request, v4 = false) begin data = ssl_post(url(v4), request, - "Content-Type" => 'text/xml; charset=utf-8', - "SOAPAction" => soap_action(action, v4) - ) + 'Content-Type' => 'text/xml; charset=utf-8', + 'SOAPAction' => soap_action(action, v4) + ) response = parse(action, data) rescue ActiveMerchant::ResponseError => e response = parse_error(e.response) @@ -307,14 +304,14 @@ def commit(action, request, v4 = false) Response.new(response[:success], response[:message], response, :test => test?, :authorization => authorization_from(response), - :avs_result => { :code => response["AVSResponse"] }, - :cvv_result => response["CVResponse"] + :avs_result => { :code => response['AVSResponse'] }, + :cvv_result => response['CVResponse'] ) end def authorization_from(response) if response[:success] - [ response["ReferenceID"], response["OrderNumber"] ].join(";") + [ response['ReferenceID'], response['OrderNumber'] ].join(';') end end end diff --git a/lib/active_merchant/billing/gateways/merchant_ware_version_four.rb b/lib/active_merchant/billing/gateways/merchant_ware_version_four.rb index 88421ca91c9..4b1667334a4 100644 --- a/lib/active_merchant/billing/gateways/merchant_ware_version_four.rb +++ b/lib/active_merchant/billing/gateways/merchant_ware_version_four.rb @@ -2,26 +2,26 @@ module ActiveMerchant #:nodoc: module Billing #:nodoc: class MerchantWareVersionFourGateway < Gateway self.live_url = 'https://ps1.merchantware.net/Merchantware/ws/RetailTransaction/v4/Credit.asmx' - self.test_url = 'https://staging.merchantware.net/Merchantware/ws/RetailTransaction/v4/Credit.asmx' + self.test_url = 'https://ps1.merchantware.net/Merchantware/ws/RetailTransaction/v4/Credit.asmx' self.supported_countries = ['US'] self.supported_cardtypes = [:visa, :master, :american_express, :discover] self.homepage_url = 'http://merchantwarehouse.com/merchantware' self.display_name = 'MerchantWARE' - ENV_NAMESPACES = { "xmlns:xsi" => "http://www.w3.org/2001/XMLSchema-instance", - "xmlns:xsd" => "http://www.w3.org/2001/XMLSchema", - "xmlns:soap" => "http://schemas.xmlsoap.org/soap/envelope/" } + ENV_NAMESPACES = { 'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance', + 'xmlns:xsd' => 'http://www.w3.org/2001/XMLSchema', + 'xmlns:soap' => 'http://schemas.xmlsoap.org/soap/envelope/' } - TX_NAMESPACE = "http://schemas.merchantwarehouse.com/merchantware/40/Credit/" + TX_NAMESPACE = 'http://schemas.merchantwarehouse.com/merchantware/40/Credit/' ACTIONS = { - :purchase => "SaleKeyed", + :purchase => 'SaleKeyed', :reference_purchase => 'RepeatSale', - :authorize => "PreAuthorizationKeyed", - :capture => "PostAuthorization", - :void => "VoidPreAuthorization", - :refund => "Refund" + :authorize => 'PreAuthorizationKeyed', + :capture => 'PostAuthorization', + :void => 'Void', + :refund => 'Refund' } # Creates a new MerchantWareVersionFourGateway @@ -102,23 +102,41 @@ def refund(money, identification, options = {}) request = soap_request(:refund) do |xml| add_reference_token(xml, reference) add_invoice(xml, options) - add_amount(xml, money, "overrideAmount") + add_amount(xml, money, 'overrideAmount') end commit(:refund, request) end + def verify(credit_card, options={}) + MultiResponse.run(:use_first_response) do |r| + r.process { authorize(100, credit_card, options) } + r.process(:ignore_result) { void(r.authorization, options) } + end + end + + def supports_scrubbing? + true + end + + def scrub(transcript) + transcript. + gsub(%r(().+?())i, '\1[FILTERED]\2'). + gsub(%r(().+?())i, '\1[FILTERED]\2'). + gsub(%r(().+?())i, '\1[FILTERED]\2') + end + private def soap_request(action) xml = Builder::XmlMarkup.new :indent => 2 xml.instruct! - xml.tag! "soap:Envelope", ENV_NAMESPACES do - xml.tag! "soap:Body" do - xml.tag! ACTIONS[action], "xmlns" => TX_NAMESPACE do - xml.tag! "merchantName", @options[:name] - xml.tag! "merchantSiteId", @options[:login] - xml.tag! "merchantKey", @options[:password] + xml.tag! 'soap:Envelope', ENV_NAMESPACES do + xml.tag! 'soap:Body' do + xml.tag! ACTIONS[action], 'xmlns' => TX_NAMESPACE do + xml.tag! 'merchantName', @options[:name] + xml.tag! 'merchantSiteId', @options[:login] + xml.tag! 'merchantKey', @options[:password] yield xml end end @@ -129,7 +147,7 @@ def soap_request(action) def build_purchase_request(action, money, payment_source, options) requires!(options, :order_id) - request = soap_request(action) do |xml| + soap_request(action) do |xml| add_invoice(xml, options) add_amount(xml, money) add_payment_source(xml, payment_source) @@ -140,36 +158,29 @@ def build_purchase_request(action, money, payment_source, options) def build_capture_request(action, money, identification, options) reference, options[:order_id] = split_reference(identification) - request = soap_request(action) do |xml| + soap_request(action) do |xml| add_reference_token(xml, reference) add_invoice(xml, options) add_amount(xml, money) end end - def expdate(credit_card) - year = sprintf("%.4i", credit_card.year) - month = sprintf("%.2i", credit_card.month) - - "#{month}#{year[-2..-1]}" - end - def add_invoice(xml, options) - xml.tag! "invoiceNumber", options[:order_id].to_s.gsub(/[^\w]/, '').slice(0, 25) + xml.tag! 'invoiceNumber', truncate(options[:order_id].to_s.gsub(/[^\w]/, ''), 8) end - def add_amount(xml, money, tag = "amount") + def add_amount(xml, money, tag = 'amount') xml.tag! tag, amount(money) end def add_reference_token(xml, reference) - xml.tag! "token", reference + xml.tag! 'token', reference end def add_address(xml, options) address = options[:billing_address] || options[:address] || {} - xml.tag! "avsStreetAddress", address[:address1] - xml.tag! "avsStreetZipCode", address[:zip] + xml.tag! 'avsStreetAddress', address[:address1] + xml.tag! 'avsStreetZipCode', address[:zip] end def add_payment_source(xml, source) @@ -181,14 +192,14 @@ def add_payment_source(xml, source) end def add_credit_card(xml, credit_card) - xml.tag! "cardNumber", credit_card.number - xml.tag! "expirationDate", expdate(credit_card) - xml.tag! "cardholder", credit_card.name - xml.tag! "cardSecurityCode", credit_card.verification_value if credit_card.verification_value? + xml.tag! 'cardNumber', credit_card.number + xml.tag! 'expirationDate', expdate(credit_card) + xml.tag! 'cardholder', credit_card.name + xml.tag! 'cardSecurityCode', credit_card.verification_value if credit_card.verification_value? end def split_reference(reference) - reference.to_s.split(";") + reference.to_s.split(';') end def parse(action, data) @@ -201,14 +212,14 @@ def parse(action, data) response[element.name] = element.text end - if response["ErrorMessage"].present? - response[:message] = response["ErrorMessage"] + if response['ErrorMessage'].present? + response[:message] = response['ErrorMessage'] response[:success] = false else - status, code, message = response["ApprovalStatus"].split(";") + status, code, message = response['ApprovalStatus'].split(';') response[:status] = status - if response[:success] = status == "APPROVED" + if response[:success] = status == 'APPROVED' response[:message] = status else response[:message] = message @@ -232,11 +243,11 @@ def parse_error(http_response, action) response[element.name] = element.text end - response[:message] = response["ErrorMessage"].to_s.gsub("\n", " ") + response[:message] = response['ErrorMessage'].to_s.gsub("\n", ' ') response - rescue REXML::ParseException => e + rescue REXML::ParseException response[:http_body] = http_response.body - response[:message] = "Failed to parse the failed response" + response[:message] = 'Failed to parse the failed response' response end @@ -251,9 +262,9 @@ def url def commit(action, request) begin data = ssl_post(url, request, - "Content-Type" => 'text/xml; charset=utf-8', - "SOAPAction" => soap_action(action) - ) + 'Content-Type' => 'text/xml; charset=utf-8', + 'SOAPAction' => soap_action(action) + ) response = parse(action, data) rescue ActiveMerchant::ResponseError => e response = parse_error(e.response, action) @@ -262,8 +273,8 @@ def commit(action, request) Response.new(response[:success], response[:message], response, :test => test?, :authorization => authorization_from(response), - :avs_result => { :code => response["AvsResponse"] }, - :cvv_result => response["CvResponse"] + :avs_result => { :code => response['AvsResponse'] }, + :cvv_result => response['CvResponse'] ) end diff --git a/lib/active_merchant/billing/gateways/merchant_warrior.rb b/lib/active_merchant/billing/gateways/merchant_warrior.rb index 9ee22a3c3b1..8e92e21811e 100644 --- a/lib/active_merchant/billing/gateways/merchant_warrior.rb +++ b/lib/active_merchant/billing/gateways/merchant_warrior.rb @@ -12,9 +12,9 @@ class MerchantWarriorGateway < Gateway self.supported_countries = ['AU'] self.supported_cardtypes = [:visa, :master, :american_express, - :diners_club, :discover] - self.homepage_url = 'http://www.merchantwarrior.com/' - self.display_name = 'MerchantWarrior' + :diners_club, :discover, :jcb] + self.homepage_url = 'https://www.merchantwarrior.com/' + self.display_name = 'Merchant Warrior' self.money_format = :dollars self.default_currency = 'AUD' @@ -27,7 +27,7 @@ def initialize(options = {}) def authorize(money, payment_method, options = {}) post = {} add_amount(post, money, options) - add_product(post, options) + add_order_id(post, options) add_address(post, options) add_payment_method(post, payment_method) commit('processAuth', post) @@ -36,21 +36,21 @@ def authorize(money, payment_method, options = {}) def purchase(money, payment_method, options = {}) post = {} add_amount(post, money, options) - add_product(post, options) + add_order_id(post, options) add_address(post, options) add_payment_method(post, payment_method) commit('processCard', post) end - def capture(money, identification) + def capture(money, identification, options = {}) post = {} add_amount(post, money, options) add_transaction(post, identification) - post.merge!('captureAmount' => amount(money)) + post['captureAmount'] = amount(money) commit('processCapture', post) end - def refund(money, identification) + def refund(money, identification, options = {}) post = {} add_amount(post, money, options) add_transaction(post, identification) @@ -60,7 +60,7 @@ def refund(money, identification) def store(creditcard, options = {}) post = { - 'cardName' => creditcard.name, + 'cardName' => scrub_name(creditcard.name), 'cardNumber' => creditcard.number, 'cardExpiryMonth' => format(creditcard.month, :two_digits), 'cardExpiryYear' => format(creditcard.year, :two_digits) @@ -68,6 +68,18 @@ def store(creditcard, options = {}) commit('addCard', post) end + def supports_scrubbing? + true + end + + def scrub(transcript) + transcript. + gsub(%r((&?paymentCardNumber=)[^&]*)i, '\1[FILTERED]'). + gsub(%r((CardNumber=)[^&]*)i, '\1[FILTERED]'). + gsub(%r((&?paymentCardCSC=)[^&]*)i, '\1[FILTERED]'). + gsub(%r((&?apiKey=)[^&]*)i, '\1[FILTERED]') + end + private def add_transaction(post, identification) @@ -75,18 +87,21 @@ def add_transaction(post, identification) end def add_address(post, options) - return unless(address = options[:address]) + return unless(address = (options[:billing_address] || options[:address])) - post['customerName'] = address[:name] + post['customerName'] = scrub_name(address[:name]) post['customerCountry'] = address[:country] - post['customerState'] = address[:state] + post['customerState'] = address[:state] || 'N/A' post['customerCity'] = address[:city] post['customerAddress'] = address[:address1] post['customerPostCode'] = address[:zip] + post['customerIP'] = address[:ip] + post['customerPhone'] = address[:phone] + post['customerEmail'] = address[:email] end - def add_product(post, options) - post['transactionProduct'] = options[:transaction_product] + def add_order_id(post, options) + post['transactionProduct'] = truncate(options[:order_id], 34) || SecureRandom.hex(15) end def add_payment_method(post, payment_method) @@ -103,11 +118,15 @@ def add_token(post, token) def add_creditcard(post, creditcard) post['paymentCardNumber'] = creditcard.number - post['paymentCardName'] = creditcard.name - post['paymentCardExpiry'] = creditcard.expiry_date.expiration.strftime("%m%y") + post['paymentCardName'] = scrub_name(creditcard.name) + post['paymentCardExpiry'] = creditcard.expiry_date.expiration.strftime('%m%y') post['paymentCardCSC'] = creditcard.verification_value if creditcard.verification_value? end + def scrub_name(name) + name.gsub(/[^a-zA-Z\. -]/, '') + end + def add_amount(post, money, options) currency = (options[:currency] || currency(money)) @@ -139,7 +158,7 @@ def parse(body) def parse_element(response, node) if node.has_elements? - node.elements.each{|element| parse_element(response, element)} + node.elements.each { |element| parse_element(response, element) } else response[node.name.underscore.to_sym] = node.text end @@ -169,14 +188,14 @@ def add_auth(action, post) def url_for(action, post) if token?(post) - [(test? ? TOKEN_TEST_URL : TOKEN_LIVE_URL), action].join("/") + [(test? ? TOKEN_TEST_URL : TOKEN_LIVE_URL), action].join('/') else (test? ? POST_TEST_URL : POST_LIVE_URL) end end def token?(post) - (post["cardID"] || post["cardName"]) + (post['cardID'] || post['cardName']) end def success?(response) @@ -184,7 +203,7 @@ def success?(response) end def post_data(post) - post.collect{|k,v| "#{k}=#{CGI.escape(v.to_s)}" }.join("&") + post.collect { |k, v| "#{k}=#{CGI.escape(v.to_s)}" }.join('&') end end end diff --git a/lib/active_merchant/billing/gateways/mercury.rb b/lib/active_merchant/billing/gateways/mercury.rb index 4f969015904..6fea6dd9197 100644 --- a/lib/active_merchant/billing/gateways/mercury.rb +++ b/lib/active_merchant/billing/gateways/mercury.rb @@ -12,16 +12,22 @@ module Billing #:nodoc: # and +refund+ will become mandatory. class MercuryGateway < Gateway URLS = { - :test => 'https://w1.mercurydev.net/ws/ws.asmx', + :test => 'https://w1.mercurycert.net/ws/ws.asmx', :live => 'https://w1.mercurypay.com/ws/ws.asmx' } self.homepage_url = 'http://www.mercurypay.com' self.display_name = 'Mercury' - self.supported_countries = ['US'] + self.supported_countries = ['US', 'CA'] self.supported_cardtypes = [:visa, :master, :american_express, :discover, :diners_club, :jcb] self.default_currency = 'USD' + STANDARD_ERROR_CODE_MAPPING = { + '100204' => STANDARD_ERROR_CODE[:invalid_number], + '100205' => STANDARD_ERROR_CODE[:invalid_expiry_date], + '000000' => STANDARD_ERROR_CODE[:card_declined] + } + def initialize(options = {}) requires!(options, :login, :password) @use_tokenization = (!options.has_key?(:tokenization) || options[:tokenization]) @@ -66,35 +72,46 @@ def refund(money, authorization, options = {}) def void(authorization, options={}) requires!(options, :credit_card) unless @use_tokenization - if options[:try_reversal] - request = build_authorized_request('VoidSale', nil, authorization, options[:credit_card], options.merge(:reversal => true)) - response = commit('VoidSale', request) - - return response if response.success? - end - request = build_authorized_request('VoidSale', nil, authorization, options[:credit_card], options) commit('VoidSale', request) end + def store(credit_card, options={}) + request = build_card_lookup_request(credit_card, options) + commit('CardLookup', request) + end + + def supports_scrubbing? + true + end + + def scrub(transcript) + transcript. + gsub(%r(<), '<'). + gsub(%r(>), '>'). + gsub(%r(().*())i, '\1[FILTERED]\2'). + gsub(%r(()(\d|x)*())i, '\1[FILTERED]\3'). + gsub(%r(()\d*())i, '\1[FILTERED]\2') + end + private def build_non_authorized_request(action, money, credit_card, options) xml = Builder::XmlMarkup.new - xml.tag! "TStream" do - xml.tag! "Transaction" do + xml.tag! 'TStream' do + xml.tag! 'Transaction' do xml.tag! 'TranType', 'Credit' xml.tag! 'TranCode', action - if action == 'PreAuth' || action == 'Sale' - xml.tag! "PartialAuth", "Allow" + if options[:allow_partial_auth] && ['PreAuth', 'Sale'].include?(action) + xml.tag! 'PartialAuth', 'Allow' end add_invoice(xml, options[:order_id], nil, options) - add_reference(xml, "RecordNumberRequested") + add_reference(xml, 'RecordNumberRequested') add_customer_data(xml, options) add_amount(xml, money, options) add_credit_card(xml, credit_card, action) - add_address(xml, options) + add_address(xml, options) unless credit_card.track_data.present? end end xml = xml.target! @@ -104,15 +121,15 @@ def build_authorized_request(action, money, authorization, credit_card, options) xml = Builder::XmlMarkup.new invoice_no, ref_no, auth_code, acq_ref_data, process_data, record_no, amount = split_authorization(authorization) - ref_no = invoice_no if options[:reversal] + ref_no = '1' if ref_no.blank? - xml.tag! "TStream" do - xml.tag! "Transaction" do + xml.tag! 'TStream' do + xml.tag! 'Transaction' do xml.tag! 'TranType', 'Credit' - if action == 'PreAuthCapture' - xml.tag! "PartialAuth", "Allow" + if options[:allow_partial_auth] && (action == 'PreAuthCapture') + xml.tag! 'PartialAuth', 'Allow' end - xml.tag! 'TranCode', (@use_tokenization ? (action + "ByRecordNo") : action) + xml.tag! 'TranCode', (@use_tokenization ? (action + 'ByRecordNo') : action) add_invoice(xml, invoice_no, ref_no, options) add_reference(xml, record_no) add_customer_data(xml, options) @@ -120,20 +137,33 @@ def build_authorized_request(action, money, authorization, credit_card, options) add_credit_card(xml, credit_card, action) if credit_card add_address(xml, options) xml.tag! 'TranInfo' do - xml.tag! "AuthCode", auth_code - xml.tag! "AcqRefData", acq_ref_data if options[:reversal] - xml.tag! "ProcessData", process_data if options[:reversal] + xml.tag! 'AuthCode', auth_code + xml.tag! 'AcqRefData', acq_ref_data + xml.tag! 'ProcessData', process_data end end end xml = xml.target! end - def add_invoice(xml, invoice_no, ref_no, options) - if /^\d+$/ !~ invoice_no.to_s - raise ArgumentError.new("order_id '#{invoice_no}' is not numeric as required by Mercury") + def build_card_lookup_request(credit_card, options) + xml = Builder::XmlMarkup.new + + xml.tag! 'TStream' do + xml.tag! 'Transaction' do + xml.tag! 'TranType', 'CardLookup' + xml.tag! 'RecordNo', 'RecordNumberRequested' + xml.tag! 'Frequency', 'OneTime' + + xml.tag! 'Memo', options[:description] + add_customer_data(xml, options) + add_credit_card(xml, credit_card, options) + end end + xml.target! + end + def add_invoice(xml, invoice_no, ref_no, options) xml.tag! 'InvoiceNo', invoice_no xml.tag! 'RefNo', (ref_no || invoice_no) xml.tag! 'OperatorID', options[:merchant] if options[:merchant] @@ -142,15 +172,15 @@ def add_invoice(xml, invoice_no, ref_no, options) def add_reference(xml, record_no) if @use_tokenization - xml.tag! "Frequency", "OneTime" - xml.tag! "RecordNo", record_no + xml.tag! 'Frequency', 'OneTime' + xml.tag! 'RecordNo', record_no end end def add_customer_data(xml, options) xml.tag! 'IpAddress', options[:ip] if options[:ip] if options[:customer] - xml.tag! "TranInfo" do + xml.tag! 'TranInfo' do xml.tag! 'CustomerCode', options[:customer] end end @@ -177,22 +207,36 @@ def add_amount(xml, money, options = {}) def add_credit_card(xml, credit_card, action) xml.tag! 'Account' do - xml.tag! 'AcctNo', credit_card.number - xml.tag! 'ExpDate', expdate(credit_card) + if credit_card.track_data.present? + # Track 1 has a start sentinel (STX) of '%' and track 2 is ';' + # Track 1 and 2 have identical end sentinels (ETX) of '?' + # Tracks may or may not have checksum (LRC) after the ETX + # If the track has no STX or is corrupt, we send it as track 1, to let Mercury + # handle with the validation error as it sees fit. + # Track 2 requires having the STX and ETX stripped. Track 1 does not. + # Max-length track 1s require having the STX and ETX stripped. Max is 79 bytes including LRC. + is_track_2 = credit_card.track_data[0] == ';' + etx_index = credit_card.track_data.rindex('?') || credit_card.track_data.length + is_max_track1 = etx_index >= 77 + + if is_track_2 + xml.tag! 'Track2', credit_card.track_data[1...etx_index] + elsif is_max_track1 + xml.tag! 'Track1', credit_card.track_data[1...etx_index] + else + xml.tag! 'Track1', credit_card.track_data + end + else + xml.tag! 'AcctNo', credit_card.number + xml.tag! 'ExpDate', expdate(credit_card) + end end xml.tag! 'CardType', CARD_CODES[credit_card.brand] if credit_card.brand - include_cvv = !%w(Return PreAuthCapture).include?(action) + include_cvv = !%w(Return PreAuthCapture).include?(action) && !credit_card.track_data.present? xml.tag! 'CVVData', credit_card.verification_value if(include_cvv && credit_card.verification_value) end - def expdate(credit_card) - year = sprintf("%.4i", credit_card.year) - month = sprintf("%.2i", credit_card.month) - - "#{month}#{year[-2..-1]}" - end - def add_address(xml, options) if billing_address = options[:billing_address] || options[:address] xml.tag! 'AVS' do @@ -211,12 +255,12 @@ def parse(action, body) def hashify_xml!(xml, response) xml = REXML::Document.new(xml) - xml.elements.each("//CmdResponse/*") do |node| + xml.elements.each('//CmdResponse/*') do |node| response[node.name.underscore.to_sym] = node.text end - xml.elements.each("//TranResponse/*") do |node| - if node.name.to_s == "Amount" + xml.elements.each('//TranResponse/*') do |node| + if node.name.to_s == 'Amount' node.elements.each do |amt| response[amt.name.underscore.to_sym] = amt.text end @@ -249,8 +293,8 @@ def build_soap_request(body) def build_header { - "SOAPAction" => "http://www.mercurypay.com/CreditTransaction", - "Content-Type" => "text/xml; charset=utf-8" + 'SOAPAction' => 'http://www.mercurypay.com/CreditTransaction', + 'Content-Type' => 'text/xml; charset=utf-8' } end @@ -266,7 +310,8 @@ def commit(action, request) :test => test?, :authorization => authorization_from(response), :avs_result => { :code => response[:avs_result] }, - :cvv_result => response[:cvv_result]) + :cvv_result => response[:cvv_result], + :error_code => success ? nil : STANDARD_ERROR_CODE_MAPPING[response[:dsix_return_code]]) end def message_from(response) @@ -274,7 +319,7 @@ def message_from(response) end def authorization_from(response) - dollars, cents = (response[:purchase] || "").split(".").collect{|e| e.to_i} + dollars, cents = (response[:purchase] || '').split('.').collect(&:to_i) dollars ||= 0 cents ||= 0 [ @@ -285,17 +330,17 @@ def authorization_from(response) response[:process_data], response[:record_no], ((dollars * 100) + cents).to_s - ].join(";") + ].join(';') end def split_authorization(authorization) - invoice_no, ref_no, auth_code, acq_ref_data, process_data, record_no, amount = authorization.split(";") + invoice_no, ref_no, auth_code, acq_ref_data, process_data, record_no, amount = authorization.split(';') [invoice_no, ref_no, auth_code, acq_ref_data, process_data, record_no, amount] end ENVELOPE_NAMESPACES = { 'xmlns:xsd' => 'http://www.w3.org/2001/XMLSchema', - 'xmlns:soap' => "http://schemas.xmlsoap.org/soap/envelope/", + 'xmlns:soap' => 'http://schemas.xmlsoap.org/soap/envelope/', 'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance' } @@ -304,7 +349,7 @@ def escape_xml(xml) end def unescape_xml(escaped_xml) - escaped_xml.gsub(/\>/,'>').gsub(/\</,'<') + escaped_xml.gsub(/\>/, '>').gsub(/\</, '<') end end end diff --git a/lib/active_merchant/billing/gateways/metrics_global.rb b/lib/active_merchant/billing/gateways/metrics_global.rb index fd7bf764352..314f403c83c 100644 --- a/lib/active_merchant/billing/gateways/metrics_global.rb +++ b/lib/active_merchant/billing/gateways/metrics_global.rb @@ -20,8 +20,8 @@ class MetricsGlobalGateway < Gateway class_attribute :test_url, :live_url - self.test_url = "https://secure.metricsglobalgateway.com/gateway/transact.dll?testing=true" - self.live_url = "https://secure.metricsglobalgateway.com/gateway/transact.dll" + self.test_url = 'https://secure.metricsglobalgateway.com/gateway/transact.dll?testing=true' + self.live_url = 'https://secure.metricsglobalgateway.com/gateway/transact.dll' class_attribute :duplicate_window @@ -150,7 +150,7 @@ def refund(money, identification, options = {}) end def credit(money, identification, options = {}) - deprecated CREDIT_DEPRECATION_MESSAGE + ActiveMerchant.deprecated CREDIT_DEPRECATION_MESSAGE refund(money, identification, options) end @@ -213,14 +213,14 @@ def post_data(action, parameters = {}) post[:version] = API_VERSION post[:login] = @options[:login] post[:tran_key] = @options[:password] - post[:relay_response] = "FALSE" + post[:relay_response] = 'FALSE' post[:type] = action - post[:delim_data] = "TRUE" - post[:delim_char] = "," - post[:encap_char] = "$" - post[:solution_ID] = application_id if application_id.present? && application_id != "ActiveMerchant" + post[:delim_data] = 'TRUE' + post[:delim_char] = ',' + post[:encap_char] = '$' + post[:solution_ID] = application_id if application_id - request = post.merge(parameters).collect { |key, value| "x_#{key}=#{CGI.escape(value.to_s)}" }.join("&") + request = post.merge(parameters).collect { |key, value| "x_#{key}=#{CGI.escape(value.to_s)}" }.join('&') request end @@ -284,39 +284,20 @@ def add_address(post, options) end end - # Make a ruby type out of the response string - def normalize(field) - case field - when "true" then true - when "false" then false - when "" then nil - when "null" then nil - else field - end - end - def message_from(results) if results[:response_code] == DECLINED - return CVVResult.messages[ results[:card_code] ] if CARD_CODE_ERRORS.include?(results[:card_code]) + return CVVResult.messages[results[:card_code]] if CARD_CODE_ERRORS.include?(results[:card_code]) if AVS_REASON_CODES.include?(results[:response_reason_code]) && AVS_ERRORS.include?(results[:avs_result_code]) - return AVSResult.messages[ results[:avs_result_code] ] + return AVSResult.messages[results[:avs_result_code]] end end (results[:response_reason_text] ? results[:response_reason_text].chomp('.') : '') end - def expdate(creditcard) - year = sprintf("%.4i", creditcard.year) - month = sprintf("%.2i", creditcard.month) - - "#{month}#{year[-2..-1]}" - end - def split(response) response[1..-2].split(/\$,\$/) end - end end end diff --git a/lib/active_merchant/billing/gateways/micropayment.rb b/lib/active_merchant/billing/gateways/micropayment.rb new file mode 100644 index 00000000000..fc416311865 --- /dev/null +++ b/lib/active_merchant/billing/gateways/micropayment.rb @@ -0,0 +1,183 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class MicropaymentGateway < Gateway + + self.display_name = 'micropayment' + self.homepage_url = 'https://www.micropayment.de/' + + self.test_url = self.live_url = 'https://sipg.micropayment.de/public/creditcardpsp/v1/nvp/' + + self.supported_countries = %w(DE) + self.default_currency = 'EUR' + self.money_format = :cents + self.supported_cardtypes = [:visa, :master, :american_express] + + def initialize(options={}) + requires!(options, :access_key) + super + end + + def purchase(amount, payment_method, options={}) + post = {} + add_invoice(post, amount, options) + add_payment_method(post, payment_method, options) + add_customer_data(post, options) + add_address(post, options) + commit('purchase', post) + end + + def authorize(amount, payment_method, options={}) + post = {} + add_invoice(post, amount, options) + add_payment_method(post, payment_method, options) + add_customer_data(post, options) + add_address(post, options) + commit('authorize', post) + end + + def capture(amount, authorization, options={}) + post = {} + add_reference(post, authorization) + add_invoice(post, amount, options) + commit('capture', post) + end + + def void(authorization, options={}) + post = {} + add_reference(post, authorization) + commit('void', post) + end + + def refund(amount, authorization, options={}) + post = {} + add_reference(post, authorization) + add_invoice(post, amount, options) + commit('refund', post) + end + + def verify(credit_card, options={}) + MultiResponse.run(:use_first_response) do |r| + r.process { authorize(250, credit_card, options) } + r.process(:ignore_result) { void(r.authorization, options) } + end + end + + def supports_scrubbing? + true + end + + def scrub(transcript) + transcript. + gsub(%r((accessKey=)\w+), '\1[FILTERED]'). + gsub(%r((number=)\d+), '\1[FILTERED]'). + gsub(%r((cvc2=)\d+), '\1[FILTERED]') + end + + private + + def add_invoice(post, money, options) + if money + post[:amount] = amount(money) + post[:currency] = options[:currency] || currency(money) + end + post[:project] = options[:project] if options[:project] + post['params[title]'] = options[:description] if options[:description] + end + + def add_payment_method(post, payment_method, options={}) + post[:number] = payment_method.number + post[:recurring] = 1 if options[:recurring] == true + post[:cvc2] = payment_method.verification_value + post[:expiryYear] = format(payment_method.year, :four_digits) + post[:expiryMonth] = format(payment_method.month, :two_digits) + + post['params[firstname]'] = payment_method.first_name + post['params[surname]'] = payment_method.last_name + end + + def add_customer_data(post, options) + post['params[email]'] = options[:email] if options[:email] + post['params[ip]'] = options[:ip] || '1.1.1.1' + post['params[sendMail]'] = options[:send_mail] || 'false' + end + + def add_address(post, options) + address = options[:billing_address] + return unless address + + post['params[address]'] = address[:address1] if address[:address1] + post['params[zipcode]'] = address[:zip] if address[:zip] + post['params[town]'] = address[:city] if address[:city] + post['params[country]'] = address[:country] if address[:country] + end + + def add_reference(post, authorization) + session_id, transaction_id = split_authorization(authorization) + post[:sessionId] = session_id + post[:transactionId] = transaction_id + end + + def commit(action, params) + params[:testMode] = 1 if test? + params[:accessKey] = @options[:access_key] + params[:apiKey] = @options[:api_key] || 'af1fd841af792f4c50131414ff76e004' + + response = parse(ssl_post(url(action), post_data(action, params), headers)) + + Response.new( + succeeded = success_from(response), + message_from(succeeded, response), + response, + authorization: authorization_from(response, params), + avs_result: AVSResult.new(code: response['some_avs_result_key']), + cvv_result: CVVResult.new(response['some_cvv_result_key']), + test: test? + ) + end + + def headers + { 'Content-Type' => 'application/x-www-form-urlencoded;charset=UTF-8' } + end + + def post_data(action, params) + params.map { |k, v| "#{CGI.escape(k.to_s)}=#{CGI.escape(v.to_s)}" }.join('&') + end + + def url(action) + action_url = test? ? test_url : live_url + "#{action_url}?action=#{action}" + end + + def parse(body) + body.split(/\r?\n/).inject({}) do |acc, pair| + key, value = pair.split('=') + acc[key] = CGI.unescape(value) + acc + end + end + + def success_from(response) + response['error'] == '0' && + response['transactionResultCode'] == '00' && + response['transactionStatus'] == 'SUCCESS' + end + + def message_from(succeeded, response) + if succeeded + 'Succeeded' + else + response['errorMessage'] || response['transactionResultMessage'] + end + end + + def split_authorization(authorization) + authorization.split('|') + end + + def authorization_from(response, request_params) + session_id = response['sessionId'] || request_params[:sessionId] + "#{session_id}|#{response["transactionId"]}" + end + end + end +end diff --git a/lib/active_merchant/billing/gateways/migs.rb b/lib/active_merchant/billing/gateways/migs.rb index 7eabdfdfa75..e1ae2922682 100644 --- a/lib/active_merchant/billing/gateways/migs.rb +++ b/lib/active_merchant/billing/gateways/migs.rb @@ -1,6 +1,4 @@ -require File.dirname(__FILE__) + '/migs/migs_codes' - -require 'digest/md5' # Used in add_secure_hash +require 'active_merchant/billing/gateways/migs/migs_codes' module ActiveMerchant #:nodoc: module Billing #:nodoc: @@ -19,12 +17,13 @@ class MigsGateway < Gateway # MiGS is supported throughout Asia Pacific, Middle East and Africa # MiGS is used in Australia (AU) by ANZ (eGate), CBA (CommWeb) and more # Source of Country List: http://www.scribd.com/doc/17811923 - self.supported_countries = %w(AU AE BD BN EG HK ID IN JO KW LB LK MU MV MY NZ OM PH QA SA SG TT VN) + self.supported_countries = %w(AU AE BD BN EG HK ID JO KW LB LK MU MV MY NZ OM PH QA SA SG TT VN) # The card types supported by the payment gateway self.supported_cardtypes = [:visa, :master, :american_express, :diners_club, :jcb] self.money_format = :cents + self.currencies_without_fractions = %w(IDR) # The homepage URL of the gateway self.homepage_url = 'http://mastercard.com/mastercardsps' @@ -58,10 +57,13 @@ def purchase(money, creditcard, options = {}) requires!(options, :order_id) post = {} - post[:Amount] = amount(money) + + add_amount(post, money, options) add_invoice(post, options) add_creditcard(post, creditcard) add_standard_parameters('pay', post, options[:unique_id]) + add_3ds(post, options) + add_tx_source(post, options) commit(post) end @@ -78,9 +80,11 @@ def capture(money, authorization, options = {}) requires!(@options, :advanced_login, :advanced_password) post = options.merge(:TransNo => authorization) - post[:Amount] = amount(money) + + add_amount(post, money, options) add_advanced_user(post) add_standard_parameters('capture', post, options[:unique_id]) + add_tx_source(post, options) commit(post) end @@ -93,18 +97,39 @@ def refund(money, authorization, options = {}) requires!(@options, :advanced_login, :advanced_password) post = options.merge(:TransNo => authorization) - post[:Amount] = amount(money) + + add_amount(post, money, options) add_advanced_user(post) add_standard_parameters('refund', post, options[:unique_id]) + add_tx_source(post, options) + + commit(post) + end + + def void(authorization, options = {}) + requires!(@options, :advanced_login, :advanced_password) + + post = options.merge(:TransNo => authorization) + + add_advanced_user(post) + add_standard_parameters('voidAuthorisation', post, options[:unique_id]) + add_tx_source(post, options) commit(post) end def credit(money, authorization, options = {}) - deprecated CREDIT_DEPRECATION_MESSAGE + ActiveMerchant.deprecated CREDIT_DEPRECATION_MESSAGE refund(money, authorization, options) end + def verify(credit_card, options={}) + MultiResponse.run do |r| + r.process { authorize(100, credit_card, options) } + r.process(:ignore_result) { void(r.authorization, options) } + end + end + # Checks the status of a previous transaction # This can be useful when a response is not received due to network issues # @@ -143,14 +168,13 @@ def purchase_offsite_url(money, options = {}) requires!(@options, :secure_hash) post = {} - post[:Amount] = amount(money) + + add_amount(post, money, options) add_invoice(post, options) add_creditcard_type(post, options[:card_type]) if options[:card_type] - post.merge!( - :Locale => options[:locale] || 'en', - :ReturnURL => options[:return_url] - ) + post[:Locale] = options[:locale] || 'en' + post[:ReturnURL] = options[:return_url] add_standard_parameters('pay', post, options[:unique_id]) @@ -170,9 +194,9 @@ def purchase_offsite_response(data) response_hash = parse(data) - expected_secure_hash = calculate_secure_hash(response_hash.reject{|k, v| k == :SecureHash}, @options[:secure_hash]) + expected_secure_hash = calculate_secure_hash(response_hash, @options[:secure_hash]) unless response_hash[:SecureHash] == expected_secure_hash - raise SecurityError, "Secure Hash mismatch, response may be tampered with" + raise SecurityError, 'Secure Hash mismatch, response may be tampered with' end response_object(response_hash) @@ -182,8 +206,27 @@ def test? @options[:login].start_with?('TEST') end + def supports_scrubbing? + true + end + + def scrub(transcript) + transcript. + gsub(%r((&?CardNum=)\d*(&?)), '\1[FILTERED]\2'). + gsub(%r((&?CardSecurityCode=)\d*(&?)), '\1[FILTERED]\2'). + gsub(%r((&?AccessCode=)[^&]*(&?)), '\1[FILTERED]\2'). + gsub(%r((&?Password=)[^&]*(&?)), '\1[FILTERED]\2'). + gsub(%r((&?3DSXID=)[^&]*(&?)), '\1[FILTERED]\2'). + gsub(%r((&?VerToken=)[^&]*(&?)), '\1[FILTERED]\2') + end + private + def add_amount(post, money, options) + post[:Amount] = localized_amount(money, options[:currency]) + post[:Currency] = options[:currency] if options[:currency] + end + def add_advanced_user(post) post[:User] = @options[:advanced_login] post[:Password] = @options[:advanced_password] @@ -193,6 +236,19 @@ def add_invoice(post, options) post[:OrderInfo] = options[:order_id] end + def add_3ds(post, options) + post[:VerType] = options[:ver_type] if options[:ver_type] + post[:VerToken] = options[:ver_token] if options[:ver_token] + post['3DSXID'] = options[:three_ds_xid] if options[:three_ds_xid] + post['3DSECI'] = options[:three_ds_eci] if options[:three_ds_eci] + post['3DSenrolled'] = options[:three_ds_enrolled] if options[:three_ds_enrolled] + post['3DSstatus'] = options[:three_ds_status] if options[:three_ds_status] + end + + def add_tx_source(post, options) + post[:TxSource] = options[:tx_source] if options[:tx_source] + end + def add_creditcard(post, creditcard) post[:CardNum] = creditcard.number post[:CardSecurityCode] = creditcard.verification_value if creditcard.verification_value? @@ -201,7 +257,7 @@ def add_creditcard(post, creditcard) def add_creditcard_type(post, card_type) post[:Gateway] = 'ssl' - post[:card] = CARD_TYPES.detect{|ct| ct.am_code == card_type}.migs_long_code + post[:card] = CARD_TYPES.detect { |ct| ct.am_code == card_type }.migs_long_code end def parse(body) @@ -214,18 +270,25 @@ def parse(body) end def commit(post) + add_secure_hash(post) if @options[:secure_hash] data = ssl_post self.merchant_hosted_url, post_data(post) response_hash = parse(data) response_object(response_hash) end def response_object(response) + avs_response_code = response[:AVSResultCode] + avs_response_code = 'S' if avs_response_code == 'Unsupported' + + cvv_result_code = response[:CSCResultCode] + cvv_result_code = 'P' if cvv_result_code == 'Unsupported' + Response.new(success?(response), response[:Message], response, :test => test?, :authorization => response[:TransactionNo], :fraud_review => fraud_review?(response), - :avs_result => { :code => response[:AVSResultCode] }, - :cvv_result => response[:CSCResultCode] + :avs_result => { :code => avs_response_code }, + :cvv_result => cvv_result_code ) end @@ -248,17 +311,21 @@ def add_standard_parameters(action, post, unique_id = nil) end def post_data(post) - post.collect { |key, value| "vpc_#{key}=#{CGI.escape(value.to_s)}" }.join("&") + post.collect { |key, value| "vpc_#{key}=#{CGI.escape(value.to_s)}" }.join('&') end def add_secure_hash(post) post[:SecureHash] = calculate_secure_hash(post, @options[:secure_hash]) + post[:SecureHashType] = 'SHA256' end def calculate_secure_hash(post, secure_hash) - sorted_values = post.sort_by(&:to_s).map(&:last) - input = secure_hash + sorted_values.join - Digest::MD5.hexdigest(input).upcase + input = post. + reject { |k| %i[SecureHash SecureHashType].include?(k) }. + sort. + map { |(k, v)| "vpc_#{k}=#{v}" }. + join('&') + OpenSSL::HMAC.hexdigest('SHA256', [secure_hash].pack('H*'), input).upcase end end end diff --git a/lib/active_merchant/billing/gateways/modern_payments.rb b/lib/active_merchant/billing/gateways/modern_payments.rb index 91f059017a9..c5846f7a079 100644 --- a/lib/active_merchant/billing/gateways/modern_payments.rb +++ b/lib/active_merchant/billing/gateways/modern_payments.rb @@ -1,4 +1,4 @@ -require File.dirname(__FILE__) + '/modern_payments_cim' +require 'active_merchant/billing/gateways/modern_payments_cim' module ActiveMerchant #:nodoc: module Billing #:nodoc: @@ -19,7 +19,7 @@ def purchase(money, credit_card, options = {}) customer_response = cim.create_customer(options) return customer_response unless customer_response.success? - customer_id = customer_response.params["create_customer_result"] + customer_id = customer_response.params['create_customer_result'] card_response = cim.modify_customer_credit_card(customer_id, credit_card) return card_response unless card_response.success? @@ -28,10 +28,10 @@ def purchase(money, credit_card, options = {}) end private + def cim @cim ||= ModernPaymentsCimGateway.new(@options) end end end end - diff --git a/lib/active_merchant/billing/gateways/modern_payments_cim.rb b/lib/active_merchant/billing/gateways/modern_payments_cim.rb index 756055ad791..b1626434d38 100644 --- a/lib/active_merchant/billing/gateways/modern_payments_cim.rb +++ b/lib/active_merchant/billing/gateways/modern_payments_cim.rb @@ -1,20 +1,20 @@ module ActiveMerchant #:nodoc: module Billing #:nodoc: class ModernPaymentsCimGateway < Gateway #:nodoc: - self.test_url = "https://secure.modpay.com/netservices/test/ModpayTest.asmx" + self.test_url = 'https://secure.modpay.com/netservices/test/ModpayTest.asmx' self.live_url = 'https://secure.modpay.com/ws/modpay.asmx' - LIVE_XMLNS = "https://secure.modpay.com/ws/" - TEST_XMLNS = "https://secure.modpay.com/netservices/test/" + LIVE_XMLNS = 'https://secure.modpay.com/ws/' + TEST_XMLNS = 'https://secure.modpay.com/netservices/test/' self.supported_countries = ['US'] self.supported_cardtypes = [:visa, :master, :american_express, :discover] self.homepage_url = 'http://www.modpay.com' self.display_name = 'Modern Payments' - SUCCESS_MESSAGE = "Transaction accepted" - FAILURE_MESSAGE = "Transaction failed" - ERROR_MESSAGE = "Transaction error" + SUCCESS_MESSAGE = 'Transaction accepted' + FAILURE_MESSAGE = 'Transaction failed' + ERROR_MESSAGE = 'Transaction error' PAYMENT_METHOD = { :check => 1, @@ -35,7 +35,7 @@ def create_customer(options = {}) end def modify_customer_credit_card(customer_id, credit_card) - raise ArgumentError, "The customer_id cannot be blank" if customer_id.blank? + raise ArgumentError, 'The customer_id cannot be blank' if customer_id.blank? post = {} add_customer_id(post, customer_id) @@ -45,7 +45,7 @@ def modify_customer_credit_card(customer_id, credit_card) end def authorize_credit_card_payment(customer_id, amount) - raise ArgumentError, "The customer_id cannot be blank" if customer_id.blank? + raise ArgumentError, 'The customer_id cannot be blank' if customer_id.blank? post = {} add_customer_id(post, customer_id) @@ -55,7 +55,7 @@ def authorize_credit_card_payment(customer_id, amount) end def create_payment(customer_id, amount, options = {}) - raise ArgumentError, "The customer_id cannot be blank" if customer_id.blank? + raise ArgumentError, 'The customer_id cannot be blank' if customer_id.blank? post = {} add_customer_id(post, customer_id) @@ -66,8 +66,9 @@ def create_payment(customer_id, amount, options = {}) end private + def add_payment_details(post, options) - post[:pmtDate] = (options[:payment_date] || Time.now.utc).strftime("%Y-%m-%dT%H:%M:%SZ") + post[:pmtDate] = (options[:payment_date] || Time.now.utc).strftime('%Y-%m-%dT%H:%M:%SZ') post[:pmtType] = PAYMENT_METHOD[options[:payment_method] || :credit_card] end @@ -87,9 +88,7 @@ def add_address(post, options) address = options[:billing_address] || options[:address] || {} if name = address[:name] - segments = name.split(' ') - post[:lastName] = segments.pop - post[:firstName] = segments.join(' ') + post[:firstName], post[:lastName] = split_names(name) else post[:firstName] = address[:first_name] post[:lastName] = address[:last_name] @@ -120,10 +119,10 @@ def build_request(action, params) 'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance' } do xml.tag! 'env:Body' do - xml.tag! action, { "xmlns" => xmlns(action) } do - xml.tag! "clientId", @options[:login] - xml.tag! "clientCode", @options[:password] - params.each {|key, value| xml.tag! key, value } + xml.tag! action, { 'xmlns' => xmlns(action) } do + xml.tag! 'clientId', @options[:login] + xml.tag! 'clientCode', @options[:password] + params.each { |key, value| xml.tag! key, value } end end end @@ -148,9 +147,9 @@ def url(action) def commit(action, params) data = ssl_post(url(action), build_request(action, params), - { 'Content-Type' =>'text/xml; charset=utf-8', - 'SOAPAction' => "#{xmlns(action)}#{action}" } - ) + { 'Content-Type' =>'text/xml; charset=utf-8', + 'SOAPAction' => "#{xmlns(action)}#{action}" } + ) response = parse(action, data) Response.new(successful?(action, response), message_from(action, response), response, @@ -165,14 +164,14 @@ def authorization_from(action, response) end def authorization_key(action) - action == "AuthorizeCreditCardPayment" ? :trans_id : "#{action.underscore}_result".to_sym + action == 'AuthorizeCreditCardPayment' ? :trans_id : "#{action.underscore}_result".to_sym end def successful?(action, response) key = authorization_key(action) if key == :trans_id - response[:approved] == "true" + response[:approved] == 'true' else response[key].to_i > 0 end @@ -197,7 +196,7 @@ def parse(action, xml) root.elements.to_a.each do |node| parse_element(response, node) end - elsif root = REXML::XPath.first(xml, "//soap:Fault") + elsif root = REXML::XPath.first(xml, '//soap:Fault') root.elements.to_a.each do |node| response[node.name.underscore.to_sym] = node.text end @@ -208,7 +207,7 @@ def parse(action, xml) def parse_element(response, node) if node.has_elements? - node.elements.each{|e| parse_element(response, e) } + node.elements.each { |e| parse_element(response, e) } else response[node.name.underscore.to_sym] = node.text.to_s.strip end @@ -216,4 +215,3 @@ def parse_element(response, node) end end end - diff --git a/lib/active_merchant/billing/gateways/monei.rb b/lib/active_merchant/billing/gateways/monei.rb new file mode 100755 index 00000000000..37247721a6a --- /dev/null +++ b/lib/active_merchant/billing/gateways/monei.rb @@ -0,0 +1,338 @@ +require 'nokogiri' + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + # + # == Monei gateway + # This class implements Monei gateway for Active Merchant. For more information about Monei + # gateway please go to http://www.monei.net + # + # === Setup + # In order to set-up the gateway you need four paramaters: sender_id, channel_id, login and pwd. + # Request that data to Monei. + class MoneiGateway < Gateway + self.test_url = 'https://test.monei-api.net/payment/ctpe' + self.live_url = 'https://monei-api.net/payment/ctpe' + + self.supported_countries = ['AD', 'AT', 'BE', 'BG', 'CA', 'CH', 'CY', 'CZ', 'DE', 'DK', 'EE', 'ES', 'FI', 'FO', 'FR', 'GB', 'GI', 'GR', 'HU', 'IE', 'IL', 'IS', 'IT', 'LI', 'LT', 'LU', 'LV', 'MT', 'NL', 'NO', 'PL', 'PT', 'RO', 'SE', 'SI', 'SK', 'TR', 'US', 'VA'] + self.default_currency = 'EUR' + self.supported_cardtypes = [:visa, :master, :maestro, :jcb, :american_express] + + self.homepage_url = 'http://www.monei.net/' + self.display_name = 'Monei' + + # Constructor + # + # options - Hash containing the gateway credentials, ALL MANDATORY + # :sender_id Sender ID + # :channel_id Channel ID + # :login User login + # :pwd User password + # + def initialize(options={}) + requires!(options, :sender_id, :channel_id, :login, :pwd) + super + end + + # Public: Performs purchase operation + # + # money - Amount of purchase + # credit_card - Credit card + # options - Hash containing purchase options + # :order_id Merchant created id for the purchase + # :billing_address Hash with billing address information + # :description Merchant created purchase description (optional) + # :currency Sale currency to override money object or default (optional) + # + # Returns Active Merchant response object + def purchase(money, credit_card, options={}) + execute_new_order(:purchase, money, credit_card, options) + end + + # Public: Performs authorization operation + # + # money - Amount to authorize + # credit_card - Credit card + # options - Hash containing authorization options + # :order_id Merchant created id for the authorization + # :billing_address Hash with billing address information + # :description Merchant created authorization description (optional) + # :currency Sale currency to override money object or default (optional) + # + # Returns Active Merchant response object + def authorize(money, credit_card, options={}) + execute_new_order(:authorize, money, credit_card, options) + end + + # Public: Performs capture operation on previous authorization + # + # money - Amount to capture + # authorization - Reference to previous authorization, obtained from response object returned by authorize + # options - Hash containing capture options + # :order_id Merchant created id for the authorization (optional) + # :description Merchant created authorization description (optional) + # :currency Sale currency to override money object or default (optional) + # + # Note: you should pass either order_id or description + # + # Returns Active Merchant response object + def capture(money, authorization, options={}) + execute_dependant(:capture, money, authorization, options) + end + + # Public: Refunds from previous purchase + # + # money - Amount to refund + # authorization - Reference to previous purchase, obtained from response object returned by purchase + # options - Hash containing refund options + # :order_id Merchant created id for the authorization (optional) + # :description Merchant created authorization description (optional) + # :currency Sale currency to override money object or default (optional) + # + # Note: you should pass either order_id or description + # + # Returns Active Merchant response object + def refund(money, authorization, options={}) + execute_dependant(:refund, money, authorization, options) + end + + # Public: Voids previous authorization + # + # authorization - Reference to previous authorization, obtained from response object returned by authorize + # options - Hash containing capture options + # :order_id Merchant created id for the authorization (optional) + # + # Returns Active Merchant response object + def void(authorization, options={}) + execute_dependant(:void, nil, authorization, options) + end + + # Public: Verifies credit card. Does this by doing a authorization of 1.00 Euro and then voiding it. + # + # credit_card - Credit card + # options - Hash containing authorization options + # :order_id Merchant created id for the authorization + # :billing_address Hash with billing address information + # :description Merchant created authorization description (optional) + # :currency Sale currency to override money object or default (optional) + # + # Returns Active Merchant response object of Authorization operation + def verify(credit_card, options={}) + MultiResponse.run(:use_first_response) do |r| + r.process { authorize(100, credit_card, options) } + r.process(:ignore_result) { void(r.authorization, options) } + end + end + + private + + # Private: Execute purchase or authorize operation + def execute_new_order(action, money, credit_card, options) + request = build_request do |xml| + add_identification_new_order(xml, options) + add_payment(xml, action, money, options) + add_account(xml, credit_card) + add_customer(xml, credit_card, options) + add_three_d_secure(xml, options) + end + + commit(request) + end + + # Private: Execute operation that depends on authorization code from previous purchase or authorize operation + def execute_dependant(action, money, authorization, options) + request = build_request do |xml| + add_identification_authorization(xml, authorization, options) + add_payment(xml, action, money, options) + end + + commit(request) + end + + # Private: Build XML wrapping code yielding to code to fill the transaction information + def build_request + builder = Nokogiri::XML::Builder.new(:encoding => 'UTF-8') do |xml| + xml.Request(:version => '1.0') do + xml.Header { xml.Security(:sender => @options[:sender_id]) } + xml.Transaction(:mode => test? ? 'CONNECTOR_TEST' : 'LIVE', :response => 'SYNC', :channel => @options[:channel_id]) do + xml.User(:login => @options[:login], :pwd => @options[:pwd]) + yield xml + end + end + end + builder.to_xml + end + + # Private: Add identification part to XML for new orders + def add_identification_new_order(xml, options) + requires!(options, :order_id) + xml.Identification do + xml.TransactionID options[:order_id] + end + end + + # Private: Add identification part to XML for orders that depend on authorization from previous operation + def add_identification_authorization(xml, authorization, options) + xml.Identification do + xml.ReferenceID authorization + xml.TransactionID options[:order_id] + end + end + + # Private: Add payment part to XML + def add_payment(xml, action, money, options) + code = tanslate_payment_code(action) + + xml.Payment(:code => code) do + xml.Presentation do + xml.Amount amount(money) + xml.Currency options[:currency] || currency(money) + xml.Usage options[:description] || options[:order_id] + end unless money.nil? + end + end + + # Private: Add account part to XML + def add_account(xml, credit_card) + xml.Account do + xml.Holder credit_card.name + xml.Number credit_card.number + xml.Brand credit_card.brand.upcase + xml.Expiry(:month => credit_card.month, :year => credit_card.year) + xml.Verification credit_card.verification_value + end + end + + # Private: Add customer part to XML + def add_customer(xml, credit_card, options) + requires!(options, :billing_address) + address = options[:billing_address] + xml.Customer do + xml.Name do + xml.Given credit_card.first_name + xml.Family credit_card.last_name + end + xml.Address do + xml.Street address[:address1].to_s + xml.Zip address[:zip].to_s + xml.City address[:city].to_s + xml.State address[:state].to_s if address.has_key? :state + xml.Country address[:country].to_s + end + xml.Contact do + xml.Email options[:email] || 'noemail@monei.net' + xml.Ip options[:ip] || '0.0.0.0' + end + end + end + + # Private : Convert ECI to ResultIndicator + # Possible ECI values: + # 02 or 05 - Fully Authenticated Transaction + # 00 or 07 - Non 3D Secure Transaction + # Possible ResultIndicator values: + # 01 = MASTER_3D_ATTEMPT + # 02 = MASTER_3D_SUCCESS + # 05 = VISA_3D_SUCCESS + # 06 = VISA_3D_ATTEMPT + # 07 = DEFAULT_E_COMMERCE + def eci_to_result_indicator(eci) + case eci + when '02', '05' + return eci + else + return '07' + end + end + + # Private : Add the 3DSecure infos to XML + def add_three_d_secure(xml, options) + if options[:three_d_secure] + xml.Authentication(:type => '3DSecure') do + xml.ResultIndicator eci_to_result_indicator options[:three_d_secure][:eci] + xml.Parameter(:name => 'VERIFICATION_ID') { xml.text options[:three_d_secure][:cavv] } + xml.Parameter(:name => 'XID') { xml.text options[:three_d_secure][:xid] } + end + end + end + + # Private: Parse XML response from Monei servers + def parse(body) + xml = Nokogiri::XML(body) + { + :unique_id => xml.xpath('//Response/Transaction/Identification/UniqueID').text, + :status => translate_status_code(xml.xpath('//Response/Transaction/Processing/Status/@code').text), + :reason => translate_status_code(xml.xpath('//Response/Transaction/Processing/Reason/@code').text), + :message => xml.xpath('//Response/Transaction/Processing/Return').text + } + end + + # Private: Send XML transaction to Monei servers and create AM response + def commit(xml) + url = (test? ? test_url : live_url) + + response = parse(ssl_post(url, post_data(xml), 'Content-Type' => 'application/x-www-form-urlencoded;charset=UTF-8')) + + Response.new( + success_from(response), + message_from(response), + response, + authorization: authorization_from(response), + test: test?, + error_code: error_code_from(response) + ) + end + + # Private: Decide success from servers response + def success_from(response) + response[:status] == :success || response[:status] == :new + end + + # Private: Get message from servers response + def message_from(response) + response[:message] + end + + # Private: Get error code from servers response + def error_code_from(response) + success_from(response) ? nil : STANDARD_ERROR_CODE[:card_declined] + end + + # Private: Get authorization code from servers response + def authorization_from(response) + response[:unique_id] + end + + # Private: Encode POST parameters + def post_data(xml) + "load=#{CGI.escape(xml)}" + end + + # Private: Translate Monei status code to native ruby symbols + def translate_status_code(code) + { + '00' => :success, + '40' => :neutral, + '59' => :waiting_bank, + '60' => :rejected_bank, + '64' => :waiting_risk, + '65' => :rejected_risk, + '70' => :rejected_validation, + '80' => :waiting, + '90' => :new + }[code] + end + + # Private: Translate AM operations to Monei operations codes + def tanslate_payment_code(action) + { + :purchase => 'CC.DB', + :authorize => 'CC.PA', + :capture => 'CC.CP', + :refund => 'CC.RF', + :void => 'CC.RV' + }[action] + end + end + end +end diff --git a/lib/active_merchant/billing/gateways/moneris.rb b/lib/active_merchant/billing/gateways/moneris.rb index c501f0911d1..f17f0e28acd 100644 --- a/lib/active_merchant/billing/gateways/moneris.rb +++ b/lib/active_merchant/billing/gateways/moneris.rb @@ -2,7 +2,6 @@ module ActiveMerchant #:nodoc: module Billing #:nodoc: - # To learn more about the Moneris gateway, please contact # eselectplus@moneris.com for a copy of their integration guide. For # information on remote testing, please see "Test Environment Penny Value @@ -33,7 +32,8 @@ class MonerisGateway < Gateway def initialize(options = {}) requires!(options, :login, :password) @cvv_enabled = options[:cvv_enabled] - options = { :crypt_type => 7 }.merge(options) + @avs_enabled = options[:avs_enabled] + options[:crypt_type] = 7 unless options.has_key?(:crypt_type) super end @@ -45,12 +45,19 @@ def initialize(options = {}) def authorize(money, creditcard_or_datakey, options = {}) requires!(options, :order_id) post = {} - add_payment_source(post, creditcard_or_datakey) - post[:amount] = amount(money) - post[:order_id] = options[:order_id] - post[:cust_id] = options[:customer] + add_payment_source(post, creditcard_or_datakey, options) + post[:amount] = amount(money) + post[:order_id] = options[:order_id] + post[:address] = options[:billing_address] || options[:address] post[:crypt_type] = options[:crypt_type] || @options[:crypt_type] - action = (post[:data_key].blank?) ? 'preauth' : 'res_preauth_cc' + add_cof(post, options) + action = if post[:cavv] + 'cavv_preauth' + elsif post[:data_key].blank? + 'preauth' + else + 'res_preauth_cc' + end commit(action, post) end @@ -61,12 +68,19 @@ def authorize(money, creditcard_or_datakey, options = {}) def purchase(money, creditcard_or_datakey, options = {}) requires!(options, :order_id) post = {} - add_payment_source(post, creditcard_or_datakey) - post[:amount] = amount(money) - post[:order_id] = options[:order_id] - post[:cust_id] = options[:customer] + add_payment_source(post, creditcard_or_datakey, options) + post[:amount] = amount(money) + post[:order_id] = options[:order_id] + post[:address] = options[:billing_address] || options[:address] post[:crypt_type] = options[:crypt_type] || @options[:crypt_type] - action = (post[:data_key].blank?) ? 'purchase' : 'res_purchase_cc' + add_cof(post, options) + action = if post[:cavv] + 'cavv_purchase' + elsif post[:data_key].blank? + 'purchase' + else + 'res_purchase_cc' + end commit(action, post) end @@ -81,13 +95,28 @@ def capture(money, authorization, options = {}) commit 'completion', crediting_params(authorization, :comp_amount => amount(money)) end - # Voiding cancels an open authorization. + # Voiding requires the original transaction ID and order ID of some open + # transaction. Closed transactions must be refunded. + # + # Moneris supports the voiding of an unsettled capture or purchase via + # its purchasecorrection command. This action can only occur + # on the same day as the capture/purchase prior to 22:00-23:00 EST. If + # you want to do this, pass :purchasecorrection => true as + # an option. + # + # Fun, Historical Trivia: + # Voiding an authorization in Moneris is a relatively new feature + # (September, 2011). It is actually done by doing a $0 capture. # # Concatenate your transaction number and order_id by using a semicolon # (';'). This is to keep the Moneris interface consistent with other # gateways. (See +capture+ for details.) def void(authorization, options = {}) - capture(0, authorization, options) + if options[:purchasecorrection] + commit 'purchasecorrection', crediting_params(authorization) + else + capture(0, authorization, options) + end end # Performs a refund. This method requires that the original transaction @@ -96,7 +125,7 @@ def void(authorization, options = {}) # Moneris interface consistent with other gateways. (See +capture+ for # details.) def credit(money, authorization, options = {}) - deprecated CREDIT_DEPRECATION_MESSAGE + ActiveMerchant.deprecated CREDIT_DEPRECATION_MESSAGE refund(money, authorization, options) end @@ -104,6 +133,13 @@ def refund(money, authorization, options = {}) commit 'refund', crediting_params(authorization, :amount => amount(money)) end + def verify(credit_card, options={}) + MultiResponse.run(:use_first_response) do |r| + r.process { authorize(100, credit_card, options) } + r.process(:ignore_result) { void(r.authorization, options) } + end + end + def store(credit_card, options = {}) post = {} post[:pan] = credit_card.number @@ -127,22 +163,51 @@ def update(data_key, credit_card, options = {}) commit('res_update_cc', post) end + def supports_scrubbing? + true + end + + def scrub(transcript) + transcript. + gsub(%r(().+()), '\1[FILTERED]\2'). + gsub(%r(().+()), '\1[FILTERED]\2'). + gsub(%r(().+()), '\1[FILTERED]\2'). + gsub(%r(().+()), '\1[FILTERED]\2'). + gsub(%r(().+()), '\1[FILTERED]\2') + end + private # :nodoc: all def expdate(creditcard) - sprintf("%.4i", creditcard.year)[-2..-1] + sprintf("%.2i", creditcard.month) + sprintf('%.4i', creditcard.year)[-2..-1] + sprintf('%.2i', creditcard.month) end - def add_payment_source(post, source) - if source.is_a?(String) - post[:data_key] = source + def add_payment_source(post, payment_method, options) + if payment_method.is_a?(String) + post[:data_key] = payment_method + post[:cust_id] = options[:customer] else - post[:pan] = source.number - post[:expdate] = expdate(source) - post[:cvd_value] = source.verification_value if source.verification_value? + if payment_method.respond_to?(:track_data) && payment_method.track_data.present? + post[:pos_code] = '00' + post[:track2] = payment_method.track_data + else + post[:pan] = payment_method.number + post[:expdate] = expdate(payment_method) + post[:cvd_value] = payment_method.verification_value if payment_method.verification_value? + post[:cavv] = payment_method.payment_cryptogram if payment_method.is_a?(NetworkTokenizationCreditCard) + post[:wallet_indicator] = wallet_indicator(payment_method.source.to_s) if payment_method.is_a?(NetworkTokenizationCreditCard) + post[:crypt_type] = (payment_method.eci || 7) if payment_method.is_a?(NetworkTokenizationCreditCard) + end + post[:cust_id] = options[:customer] || payment_method.name end end + def add_cof(post, options) + post[:issuer_id] = options[:issuer_id] if options[:issuer_id] + post[:payment_indicator] = options[:payment_indicator] if options[:payment_indicator] + post[:payment_information] = options[:payment_information] if options[:payment_information] + end + # Common params used amongst the +credit+, +void+ and +capture+ methods def crediting_params(authorization, options = {}) { @@ -152,7 +217,7 @@ def crediting_params(authorization, options = {}) }.merge(options) end - # Splits an +authorization+ param and retrives the order id and + # Splits an +authorization+ param and retrieves the order id and # transaction number in that order. def split_authorization(authorization) if authorization.nil? || authorization.empty? || authorization !~ /;/ @@ -163,13 +228,19 @@ def split_authorization(authorization) end def commit(action, parameters = {}) - response = parse(ssl_post(test? ? self.test_url : self.live_url, post_data(action, parameters))) - - Response.new(successful?(response), message_from(response[:message]), response, - :test => test?, - :cvv_result => response[:cvd_result_code].try(:last), - :authorization => authorization_from(response) - ) + data = post_data(action, parameters) + url = test? ? self.test_url : self.live_url + raw = ssl_post(url, data) + response = parse(raw) + + Response.new( + successful?(response), + message_from(response[:message]), + response, + :test => test?, + :avs_result => {:code => response[:avs_result_code]}, + :cvv_result => response[:cvd_result_code] && response[:cvd_result_code][-1, 1], + :authorization => authorization_from(response)) end # Generates a Moneris authorization string of the form 'trans_id;receipt_id'. @@ -183,11 +254,11 @@ def authorization_from(response = {}) def successful?(response) response[:response_code] && response[:complete] && - (0..49).include?(response[:response_code].to_i) + (0..49).cover?(response[:response_code].to_i) end def parse(xml) - response = { :message => "Global Error Receipt", :complete => false } + response = { :message => 'Global Error Receipt', :complete => false } hashify_xml!(xml, response) response end @@ -201,10 +272,10 @@ def hashify_xml!(xml, response) end def post_data(action, parameters = {}) - xml = REXML::Document.new - root = xml.add_element("request") - root.add_element("store_id").text = options[:login] - root.add_element("api_token").text = options[:password] + xml = REXML::Document.new + root = xml.add_element('request') + root.add_element('store_id').text = options[:login] + root.add_element('api_token').text = options[:password] root.add_element(transaction_element(action, parameters)) xml.to_s @@ -215,8 +286,13 @@ def transaction_element(action, parameters) # Must add the elements in the correct order actions[action].each do |key| - if((key == :cvd_info) && @cvv_enabled) - transaction.add_element(cvd_element(parameters[:cvd_value])) + case key + when :avs_info + transaction.add_element(avs_element(parameters[:address])) if @avs_enabled && parameters[:address] + when :cvd_info + transaction.add_element(cvd_element(parameters[:cvd_value])) if @cvv_enabled + when :cof_info + transaction.add_element(credential_on_file(parameters)) if cof_details_present?(parameters) else transaction.add_element(key.to_s).text = parameters[key] unless parameters[key].blank? end @@ -225,52 +301,75 @@ def transaction_element(action, parameters) transaction end + def avs_element(address) + full_address = "#{address[:address1]} #{address[:address2]}" + tokens = full_address.split(/\s+/) + + element = REXML::Element.new('avs_info') + element.add_element('avs_street_number').text = tokens.select { |x| x =~ /\d/ }.join(' ') + element.add_element('avs_street_name').text = tokens.reject { |x| x =~ /\d/ }.join(' ') + element.add_element('avs_zipcode').text = address[:zip] + element + end + def cvd_element(cvd_value) element = REXML::Element.new('cvd_info') if cvd_value - element.add_element('cvd_indicator').text = "1" + element.add_element('cvd_indicator').text = '1' element.add_element('cvd_value').text = cvd_value else - element.add_element('cvd_indicator').text = "0" + element.add_element('cvd_indicator').text = '0' end element end - def message_from(message) - return 'Unspecified error' if message.blank? - message.gsub(/[^\w]/, ' ').split.join(" ").capitalize + def cof_details_present?(parameters) + parameters[:issuer_id] && parameters[:payment_indicator] && parameters[:payment_information] end - # Make a Ruby type out of the response string - def normalize(field) - case field - when "true" then true - when "false" then false - when '', "null" then nil - else field - end + def credential_on_file(parameters) + issuer_id = parameters[:issuer_id] + payment_indicator = parameters[:payment_indicator] + payment_information = parameters[:payment_information] + + cof_info = REXML::Element.new('cof_info') + cof_info.add_element('issuer_id').text = issuer_id + cof_info.add_element('payment_indicator').text = payment_indicator + cof_info.add_element('payment_information').text = payment_information + cof_info + end + + def wallet_indicator(token_source) + return 'APP' if token_source == 'apple_pay' + return 'ANP' if token_source == 'android_pay' + nil + end + + def message_from(message) + return 'Unspecified error' if message.blank? + message.gsub(/[^\w]/, ' ').split.join(' ').capitalize end def actions { - "purchase" => [:order_id, :cust_id, :amount, :pan, :expdate, :crypt_type, :cvd_info], - "preauth" => [:order_id, :cust_id, :amount, :pan, :expdate, :crypt_type, :cvd_info], - "command" => [:order_id], - "refund" => [:order_id, :amount, :txn_number, :crypt_type], - "indrefund" => [:order_id, :cust_id, :amount, :pan, :expdate, :crypt_type], - "completion" => [:order_id, :comp_amount, :txn_number, :crypt_type], - "purchasecorrection" => [:order_id, :txn_number, :crypt_type], - "cavvpurcha" => [:order_id, :cust_id, :amount, :pan, :expdate, :cav], - "cavvpreaut" => [:order_id, :cust_id, :amount, :pan, :expdate, :cavv], - "transact" => [:order_id, :cust_id, :amount, :pan, :expdate, :crypt_type], - "Batchcloseall" => [], - "opentotals" => [:ecr_number], - "batchclose" => [:ecr_number], - "res_add_cc" => [:pan, :expdate, :crypt_type], - "res_delete" => [:data_key], - "res_update_cc" => [:data_key, :pan, :expdate, :crypt_type], - "res_purchase_cc" => [:data_key, :order_id, :cust_id, :amount, :crypt_type], - "res_preauth_cc" => [:data_key, :order_id, :cust_id, :amount, :crypt_type] + 'purchase' => [:order_id, :cust_id, :amount, :pan, :expdate, :crypt_type, :avs_info, :cvd_info, :track2, :pos_code, :cof_info], + 'preauth' => [:order_id, :cust_id, :amount, :pan, :expdate, :crypt_type, :avs_info, :cvd_info, :track2, :pos_code, :cof_info], + 'command' => [:order_id], + 'refund' => [:order_id, :amount, :txn_number, :crypt_type], + 'indrefund' => [:order_id, :cust_id, :amount, :pan, :expdate, :crypt_type], + 'completion' => [:order_id, :comp_amount, :txn_number, :crypt_type], + 'purchasecorrection' => [:order_id, :txn_number, :crypt_type], + 'cavv_preauth' => [:order_id, :cust_id, :amount, :pan, :expdate, :cavv, :crypt_type, :wallet_indicator], + 'cavv_purchase' => [:order_id, :cust_id, :amount, :pan, :expdate, :cavv, :crypt_type, :wallet_indicator], + 'transact' => [:order_id, :cust_id, :amount, :pan, :expdate, :crypt_type], + 'Batchcloseall' => [], + 'opentotals' => [:ecr_number], + 'batchclose' => [:ecr_number], + 'res_add_cc' => [:pan, :expdate, :crypt_type, :cof_info], + 'res_delete' => [:data_key], + 'res_update_cc' => [:data_key, :pan, :expdate, :crypt_type], + 'res_purchase_cc' => [:data_key, :order_id, :cust_id, :amount, :crypt_type, :cof_info], + 'res_preauth_cc' => [:data_key, :order_id, :cust_id, :amount, :crypt_type, :cof_info] } end end diff --git a/lib/active_merchant/billing/gateways/moneris_us.rb b/lib/active_merchant/billing/gateways/moneris_us.rb index 92feec27397..28c06ea91d0 100644 --- a/lib/active_merchant/billing/gateways/moneris_us.rb +++ b/lib/active_merchant/billing/gateways/moneris_us.rb @@ -2,7 +2,6 @@ module ActiveMerchant #:nodoc: module Billing #:nodoc: - # To learn more about the Moneris (US) gateway, please contact # ussales@moneris.com for a copy of their integration guide. For # information on remote testing, please see "Test Environment Penny Value @@ -18,29 +17,70 @@ class MonerisUsGateway < Gateway self.homepage_url = 'http://www.monerisusa.com/' self.display_name = 'Moneris (US)' - # login is your Store ID - # password is your API Token + # Initialize the Gateway + # + # The gateway requires that a valid login and password be passed + # in the +options+ hash. + # + # ==== Options + # + # * :login -- Your Store ID + # * :password -- Your API Token + # * :cvv_enabled -- Specify that you would like the CVV passed to the gateway. + # Only particular account types at Moneris will allow this. + # Defaults to false. (optional) def initialize(options = {}) requires!(options, :login, :password) + @cvv_enabled = options[:cvv_enabled] + @avs_enabled = options[:avs_enabled] options = { :crypt_type => 7 }.merge(options) super end + def verify(creditcard_or_datakey, options = {}) + MultiResponse.run(:use_first_response) do |r| + r.process { authorize(100, creditcard_or_datakey, options) } + r.process(:ignore_result) { capture(0, r.authorization) } + end + end + # Referred to as "PreAuth" in the Moneris integration guide, this action # verifies and locks funds on a customer's card, which then must be # captured at a later date. # # Pass in +order_id+ and optionally a +customer+ parameter. - def authorize(money, creditcard, options = {}) - debit_commit 'us_preauth', money, creditcard, options + def authorize(money, creditcard_or_datakey, options = {}) + requires!(options, :order_id) + post = {} + add_payment_source(post, creditcard_or_datakey, options) + post[:amount] = amount(money) + post[:order_id] = options[:order_id] + post[:address] = options[:billing_address] || options[:address] + post[:crypt_type] = options[:crypt_type] || @options[:crypt_type] + action = post[:data_key].blank? ? 'us_preauth' : 'us_res_preauth_cc' + commit(action, post) end - # This action verifies funding on a customer's card, and readies them for + # This action verifies funding on a customer's card and readies them for # deposit in a merchant's account. # # Pass in order_id and optionally a customer parameter - def purchase(money, creditcard, options = {}) - debit_commit 'us_purchase', money, creditcard, options + def purchase(money, creditcard_or_datakey, options = {}) + requires!(options, :order_id) + post = {} + add_payment_source(post, creditcard_or_datakey, options) + post[:amount] = amount(money) + post[:order_id] = options[:order_id] + add_address(post, creditcard_or_datakey, options) + post[:crypt_type] = options[:crypt_type] || @options[:crypt_type] + action = if creditcard_or_datakey.is_a?(String) + 'us_res_purchase_cc' + elsif card_brand(creditcard_or_datakey) == 'check' + 'us_ach_debit' + elsif post[:data_key].blank? + 'us_purchase' + end + commit(action, post) end # This method retrieves locked funds from a customer's account (from a @@ -71,7 +111,7 @@ def void(authorization, options = {}) # Moneris interface consistent with other gateways. (See +capture+ for # details.) def credit(money, authorization, options = {}) - deprecated CREDIT_DEPRECATION_MESSAGE + ActiveMerchant.deprecated CREDIT_DEPRECATION_MESSAGE refund(money, authorization, options) end @@ -79,27 +119,80 @@ def refund(money, authorization, options = {}) commit 'us_refund', crediting_params(authorization, :amount => amount(money)) end + def store(payment_source, options = {}) + post = {} + add_payment_source(post, payment_source, options) + post[:crypt_type] = options[:crypt_type] || @options[:crypt_type] + card_brand(payment_source) == 'check' ? commit('us_res_add_ach', post) : commit('us_res_add_cc', post) + end + + def unstore(data_key, options = {}) + post = {} + post[:data_key] = data_key + commit('us_res_delete', post) + end + + def update(data_key, payment_source, options = {}) + post = {} + add_payment_source(post, payment_source, options) + post[:data_key] = data_key + card_brand(payment_source) == 'check' ? commit('us_res_update_ach', post) : commit('us_res_update_cc', post) + end + + def supports_scrubbing? + true + end + + def scrub(transcript) + transcript. + gsub(%r(()[^<]*())i, '\1[FILTERED]\2'). + gsub(%r(()[^<]*())i, '\1[FILTERED]\2'). + gsub(%r(()[^<]*())i, '\1[FILTERED]\2') + end + private # :nodoc: all def expdate(creditcard) - sprintf("%.4i", creditcard.year)[-2..-1] + sprintf("%.2i", creditcard.month) + sprintf('%.4i', creditcard.year)[-2..-1] + sprintf('%.2i', creditcard.month) end - def debit_commit(commit_type, money, creditcard, options) - requires!(options, :order_id) - commit(commit_type, debit_params(money, creditcard, options)) + def add_address(post, payment_method, options) + if !payment_method.is_a?(String) && card_brand(payment_method) == 'check' + post[:ach_info][:cust_first_name] = payment_method.first_name if payment_method.first_name + post[:ach_info][:cust_last_name] = payment_method.last_name if payment_method.last_name + if address = options[:billing_address] || options[:address] + post[:ach_info][:cust_address1] = address[:address1] if address[:address1] + post[:ach_info][:cust_address2] = address[:address2] if address[:address2] + post[:ach_info][:city] = address[:city] if address[:city] + post[:ach_info][:state] = address[:state] if address[:state] + post[:ach_info][:zip] = address[:zip] if address[:zip] + end + else + post[:address] = options[:billing_address] || options[:address] + end end - # Common params used amongst the +purchase+ and +authorization+ methods - def debit_params(money, creditcard, options = {}) - { - :order_id => options[:order_id], - :cust_id => options[:customer], - :amount => amount(money), - :pan => creditcard.number, - :expdate => expdate(creditcard), - :crypt_type => options[:crypt_type] || @options[:crypt_type] - } + def add_payment_source(post, source, options) + if source.is_a?(String) + post[:data_key] = source + post[:cust_id] = options[:customer] + elsif card_brand(source) == 'check' + ach_info = {} + ach_info[:sec] = 'web' + ach_info[:routing_num] = source.routing_number + ach_info[:account_num] = source.account_number + ach_info[:account_type] = source.account_type + ach_info[:check_num] = source.number if source.number + post[:ach_info] = ach_info + else + post[:pan] = source.number + post[:expdate] = expdate(source) + post[:cvd_value] = source.verification_value if source.verification_value? + if crypt_type = options[:crypt_type] || @options[:crypt_type] + post[:crypt_type] = crypt_type + end + post[:cust_id] = options[:customer] || source.name + end end # Common params used amongst the +credit+, +void+ and +capture+ methods @@ -111,7 +204,7 @@ def crediting_params(authorization, options = {}) }.merge(options) end - # Splits an +authorization+ param and retrives the order id and + # Splits an +authorization+ param and retrieves the order id and # transaction number in that order. def split_authorization(authorization) if authorization.nil? || authorization.empty? || authorization !~ /;/ @@ -122,10 +215,15 @@ def split_authorization(authorization) end def commit(action, parameters = {}) - response = parse(ssl_post(test? ? self.test_url : self.live_url, post_data(action, parameters))) + data = post_data(action, parameters) + url = test? ? self.test_url : self.live_url + raw = ssl_post(url, data) + response = parse(raw) Response.new(successful?(response), message_from(response[:message]), response, :test => test?, + :avs_result => { :code => response[:avs_result_code] }, + :cvv_result => response[:cvd_result_code] && response[:cvd_result_code][-1, 1], :authorization => authorization_from(response) ) end @@ -141,11 +239,11 @@ def authorization_from(response = {}) def successful?(response) response[:response_code] && response[:complete] && - (0..49).include?(response[:response_code].to_i) + (0..49).cover?(response[:response_code].to_i) end def parse(xml) - response = { :message => "Global Error Receipt", :complete => false } + response = { :message => 'Global Error Receipt', :complete => false } hashify_xml!(xml, response) response end @@ -160,47 +258,93 @@ def hashify_xml!(xml, response) def post_data(action, parameters = {}) xml = REXML::Document.new - root = xml.add_element("request") - root.add_element("store_id").text = options[:login] - root.add_element("api_token").text = options[:password] - transaction = root.add_element(action) + root = xml.add_element('request') + root.add_element('store_id').text = options[:login] + root.add_element('api_token').text = options[:password] + root.add_element(transaction_element(action, parameters)) + + xml.to_s + end + + def transaction_element(action, parameters) + transaction = REXML::Element.new(action) # Must add the elements in the correct order actions[action].each do |key| - transaction.add_element(key.to_s).text = parameters[key] unless parameters[key].blank? + case key + when :avs_info + transaction.add_element(avs_element(parameters[:address])) if @avs_enabled && parameters[:address] + when :cvd_info + transaction.add_element(cvd_element(parameters[:cvd_value])) if @cvv_enabled + when :ach_info + transaction.add_element(ach_element(parameters[:ach_info])) + else + transaction.add_element(key.to_s).text = parameters[key] unless parameters[key].blank? + end end - xml.to_s + transaction end - def message_from(message) - return 'Unspecified error' if message.blank? - message.gsub(/[^\w]/, ' ').split.join(" ").capitalize + def avs_element(address) + full_address = "#{address[:address1]} #{address[:address2]}" + tokens = full_address.split(/\s+/) + + element = REXML::Element.new('avs_info') + element.add_element('avs_street_number').text = tokens.select { |x| x =~ /\d/ }.join(' ') + element.add_element('avs_street_name').text = tokens.reject { |x| x =~ /\d/ }.join(' ') + element.add_element('avs_zipcode').text = address[:zip] + element end - # Make a Ruby type out of the response string - def normalize(field) - case field - when "true" then true - when "false" then false - when '', "null" then nil - else field + def cvd_element(cvd_value) + element = REXML::Element.new('cvd_info') + if cvd_value + element.add_element('cvd_indicator').text = '1' + element.add_element('cvd_value').text = cvd_value + else + element.add_element('cvd_indicator').text = '0' end + element + end + + def ach_element(ach_info) + element = REXML::Element.new('ach_info') + actions['ach_info'].each do |key| + element.add_element(key.to_s).text = ach_info[key] unless ach_info[key].blank? + end + element + end + + def message_from(message) + return 'Unspecified error' if message.blank? + message.gsub(/[^\w]/, ' ').split.join(' ').capitalize end def actions { - "us_purchase" => [:order_id, :cust_id, :amount, :pan, :expdate, :crypt_type], - "us_preauth" => [:order_id, :cust_id, :amount, :pan, :expdate, :crypt_type], - "us_refund" => [:order_id, :amount, :txn_number, :crypt_type], - "us_ind_refund" => [:order_id, :cust_id, :amount, :pan, :expdate, :crypt_type], - "us_completion" => [:order_id, :comp_amount, :txn_number, :crypt_type], - "us_purchasecorrection" => [:order_id, :txn_number, :crypt_type], - "us_cavv_purchase" => [:order_id, :cust_id, :amount, :pan, :expdate, :cavv], - "us_cavv_preauth" => [:order_id, :cust_id, :amount, :pan, :expdate, :cavv], - "us_batchcloseall" => [], - "us_opentotals" => [:ecr_number], - "us_batchclose" => [:ecr_number] + 'us_purchase' => [:order_id, :cust_id, :amount, :pan, :expdate, :crypt_type, :avs_info, :cvd_info], + 'us_preauth' => [:order_id, :cust_id, :amount, :pan, :expdate, :crypt_type, :avs_info, :cvd_info], + 'us_command' => [:order_id], + 'us_refund' => [:order_id, :amount, :txn_number, :crypt_type], + 'us_indrefund' => [:order_id, :cust_id, :amount, :pan, :expdate, :crypt_type], + 'us_completion' => [:order_id, :comp_amount, :txn_number, :crypt_type], + 'us_purchasecorrection' => [:order_id, :txn_number, :crypt_type], + 'us_cavvpurcha' => [:order_id, :cust_id, :amount, :pan, :expdate, :cav], + 'us_cavvpreaut' => [:order_id, :cust_id, :amount, :pan, :expdate, :cavv], + 'us_transact' => [:order_id, :cust_id, :amount, :pan, :expdate, :crypt_type], + 'us_Batchcloseall' => [], + 'us_opentotals' => [:ecr_number], + 'us_batchclose' => [:ecr_number], + 'us_res_add_cc' => [:pan, :expdate, :crypt_type], + 'us_res_delete' => [:data_key], + 'us_res_update_cc' => [:data_key, :pan, :expdate, :crypt_type], + 'us_res_purchase_cc' => [:data_key, :order_id, :cust_id, :amount, :crypt_type], + 'us_res_preauth_cc' => [:data_key, :order_id, :cust_id, :amount, :crypt_type], + 'us_ach_debit' => [:order_id, :cust_id, :amount, :ach_info], + 'us_res_add_ach' => [:order_id, :cust_id, :amount, :ach_info], + 'us_res_update_ach' => [:order_id, :data_key, :cust_id, :amount, :ach_info], + 'ach_info' => [:sec, :cust_first_name, :cust_last_name, :cust_address1, :cust_address2, :cust_city, :cust_state, :cust_zip, :routing_num, :account_num, :check_num, :account_type] } end end diff --git a/lib/active_merchant/billing/gateways/money_movers.rb b/lib/active_merchant/billing/gateways/money_movers.rb new file mode 100644 index 00000000000..8a7a1e9e9a4 --- /dev/null +++ b/lib/active_merchant/billing/gateways/money_movers.rb @@ -0,0 +1,151 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class MoneyMoversGateway < Gateway + self.live_url = self.test_url = 'https://secure.mmoagateway.com/api/transact.php' + + APPROVED, DECLINED, ERROR = 1, 2, 3 + + self.homepage_url = 'http://mmoa.us/' + self.display_name = 'MoneyMovers' + self.supported_countries = ['US'] + self.supported_cardtypes = [:visa, :master, :american_express, :discover] + + def initialize(options = {}) + requires!(options, :login, :password) + @options = options + super + end + + def purchase(money, creditcard, options = {}) + post = {} + add_invoice(post, options) + add_creditcard(post, creditcard) + add_address(post, options) + add_customer_data(post, options) + commit('sale', money, post) + end + + def authorize(money, creditcard, options = {}) + post = {} + add_invoice(post, options) + add_creditcard(post, creditcard) + add_address(post, options) + add_customer_data(post, options) + commit('auth', money, post) + end + + def capture(money, authorization, options = {}) + options[:transactionid] = authorization + commit('capture', money, options) + end + + def void(authorization, options = {}) + options[:transactionid] = authorization + commit('void', nil, options) + end + + def refund(money, authorization, options = {}) + commit('refund', money, options.merge(:transactionid => authorization)) + end + + def credit(money, authorization, options = {}) + ActiveMerchant.deprecated CREDIT_DEPRECATION_MESSAGE + refund(money, authorization, options) + end + + private + + def add_customer_data(post, options) + post[:firstname] = options[:first_name] + post[:lastname] = options[:last_name] + + post[:email] = options[:email] + end + + def add_address(post, options) + if address = (options[:billing_address] || options[:address]) + post[:company] = address[:company] + post[:address1] = address[:address1] + post[:address2] = address[:address2] + post[:city] = address[:city] + post[:state] = address[:state] + post[:zip] = address[:zip] + post[:country] = address[:country] + post[:phone] = address[:phone] + end + if address = options[:shipping_address] + post[:shipping_firstname] = address[:first_name] + post[:shipping_lastname] = address[:last_name] + post[:shipping_company] = address[:company] + post[:shipping_address1] = address[:address1] + post[:shipping_address2] = address[:address2] + post[:shipping_city] = address[:city] + post[:shipping_state] = address[:state] + post[:shipping_zip] = address[:zip] + post[:shipping_country] = address[:country] + post[:shipping_email] = address[:email] + end + end + + def add_invoice(post, options) + post[:orderid] = options[:order_id] + post[:orderdescription] = options[:description] + end + + def add_creditcard(post, creditcard) + post[:ccnumber] = creditcard.number + post[:ccexp] = expdate(creditcard) + post[:cvv] = creditcard.verification_value + end + + def parse(body) + body.split('&').inject({}) do |memo, x| + k, v = x.split('=') + memo[k] = v + memo + end + end + + def commit(action, money, parameters) + parameters[:amount] = amount(money) + + data = ssl_post(self.live_url, post_data(action, parameters)) + response = parse(data) + message = message_from(response) + + Response.new(success?(response), message, response, + :test => test?, + :authorization => response['transactionid'], + :avs_result => {:code => response['avsresponse']}, + :cvv_result => response['cvvresponse'] + ) + end + + def success?(response) + response['response'] == '1' + end + + def test? + @options[:login].eql?('demo') && @options[:password].eql?('password') + end + + def message_from(response) + case response['response'].to_i + when APPROVED + 'Transaction Approved' + when DECLINED + 'Transaction Declined' + else + 'Error in transaction data or system error' + end + end + + def post_data(action, parameters = {}) + parameters[:type] = action + parameters[:username] = @options[:login] + parameters[:password] = @options[:password] + parameters.map { |k, v| "#{k}=#{CGI.escape(v.to_s)}" }.join('&') + end + end + end +end diff --git a/lib/active_merchant/billing/gateways/mundipagg.rb b/lib/active_merchant/billing/gateways/mundipagg.rb new file mode 100644 index 00000000000..e66a6b94680 --- /dev/null +++ b/lib/active_merchant/billing/gateways/mundipagg.rb @@ -0,0 +1,293 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class MundipaggGateway < Gateway + self.live_url = 'https://api.mundipagg.com/core/v1' + + self.supported_countries = ['US'] + self.default_currency = 'USD' + self.supported_cardtypes = [:visa, :master, :american_express, :discover, :alelo] + + self.homepage_url = 'https://www.mundipagg.com/' + self.display_name = 'Mundipagg' + + STANDARD_ERROR_CODE_MAPPING = { + '400' => STANDARD_ERROR_CODE[:processing_error], + '401' => STANDARD_ERROR_CODE[:config_error], + '404' => STANDARD_ERROR_CODE[:processing_error], + '412' => STANDARD_ERROR_CODE[:processing_error], + '422' => STANDARD_ERROR_CODE[:processing_error], + '500' => STANDARD_ERROR_CODE[:processing_error] + } + + STANDARD_ERROR_MESSAGE_MAPPING = { + '400' => 'Invalid request;', + '401' => 'Invalid API key;', + '404' => 'The requested resource does not exist;', + '412' => 'Valid parameters but request failed;', + '422' => 'Invalid parameters;', + '500' => 'An internal error occurred;' + } + + def initialize(options={}) + requires!(options, :api_key) + super + end + + def purchase(money, payment, options={}) + post = {} + add_invoice(post, money, options) + add_customer_data(post, options) unless payment.is_a?(String) + add_shipping_address(post, options) + add_payment(post, payment, options) + + commit('sale', post) + end + + def authorize(money, payment, options={}) + post = {} + add_invoice(post, money, options) + add_customer_data(post, options) unless payment.is_a?(String) + add_shipping_address(post, options) + add_payment(post, payment, options) + add_capture_flag(post, payment) + commit('authonly', post) + end + + def capture(money, authorization, options={}) + post = {} + post[:code] = authorization + add_invoice(post, money, options) + commit('capture', post, authorization) + end + + def refund(money, authorization, options={}) + add_invoice(post={}, money, options) + commit('refund', post, authorization) + end + + def void(authorization, options={}) + commit('void', nil, authorization) + end + + def store(payment, options={}) + post = {} + options.update(name: payment.name) + options = add_customer(options) unless options[:customer_id] + add_payment(post, payment, options) + commit('store', post, options[:customer_id]) + end + + def verify(credit_card, options={}) + MultiResponse.run(:use_first_response) do |r| + r.process { authorize(100, credit_card, options) } + r.process(:ignore_result) { void(r.authorization, options) } + end + end + + def supports_scrubbing? + true + end + + def scrub(transcript) + transcript. + gsub(%r((Authorization: Basic )\w+), '\1[FILTERED]'). + gsub(%r(("cvv\\":\\")\d*), '\1[FILTERED]'). + gsub(%r((card\\":{\\"number\\":\\")\d*), '\1[FILTERED]') + end + + private + + def add_customer(options) + post = {} + post[:name] = options[:name] + customer = commit('customer', post) + options.update(customer_id: customer.authorization) + end + + def add_customer_data(post, options) + post[:customer] = {} + post[:customer][:email] = options[:email] + end + + def add_billing_address(post, type, options) + if address = (options[:billing_address] || options[:address]) + billing = {} + address = options[:billing_address] || options[:address] + billing[:street] = address[:address1].match(/\D+/)[0].strip if address[:address1] + billing[:number] = address[:address1].match(/\d+/)[0] if address[:address1] + billing[:compliment] = address[:address2] if address[:address2] + billing[:city] = address[:city] if address[:city] + billing[:state] = address[:state] if address[:state] + billing[:country] = address[:country] if address[:country] + billing[:zip_code] = address[:zip] if address[:zip] + billing[:neighborhood] = address[:neighborhood] + post[:payment][type.to_sym][:card][:billing_address] = billing + end + end + + def add_shipping_address(post, options) + if address = options[:shipping_address] + post[:address] = {} + post[:address][:street] = address[:address1].match(/\D+/)[0].strip if address[:address1]&.match(/\D+/) + post[:address][:number] = address[:address1].match(/\d+/)[0] if address[:address1]&.match(/\d+/) + post[:address][:compliment] = address[:address2] if address[:address2] + post[:address][:city] = address[:city] if address[:city] + post[:address][:state] = address[:state] if address[:state] + post[:address][:country] = address[:country] if address[:country] + post[:address][:zip_code] = address[:zip] if address[:zip] + end + end + + def add_invoice(post, money, options) + post[:amount] = money + post[:currency] = (options[:currency] || currency(money)) + end + + def add_capture_flag(post, payment) + if voucher?(payment) + post[:payment][:voucher][:capture] = false + else + post[:payment][:credit_card][:capture] = false + end + end + + def add_payment(post, payment, options) + post[:customer][:name] = payment.name if post[:customer] + post[:customer_id] = parse_auth(payment)[0] if payment.is_a?(String) + post[:payment] = {} + affiliation = options[:gateway_affiliation_id] || @options[:gateway_id] + post[:payment][:gateway_affiliation_id] = affiliation if affiliation + post[:payment][:metadata] = { mundipagg_payment_method_code: '1' } if test? + if voucher?(payment) + add_voucher(post, payment, options) + else + add_credit_card(post, payment, options) + end + end + + def add_credit_card(post, payment, options) + post[:payment][:payment_method] = 'credit_card' + post[:payment][:credit_card] = {} + if payment.is_a?(String) + post[:payment][:credit_card][:card_id] = parse_auth(payment)[1] + else + post[:payment][:credit_card][:card] = {} + post[:payment][:credit_card][:card][:number] = payment.number + post[:payment][:credit_card][:card][:holder_name] = payment.name + post[:payment][:credit_card][:card][:exp_month] = payment.month + post[:payment][:credit_card][:card][:exp_year] = payment.year + post[:payment][:credit_card][:card][:cvv] = payment.verification_value + post[:payment][:credit_card][:card][:holder_document] = options[:holder_document] if options[:holder_document] + add_billing_address(post, 'credit_card', options) + end + end + + def add_voucher(post, payment, options) + post[:currency] = 'BRL' + post[:payment][:payment_method] = 'voucher' + post[:payment][:voucher] = {} + post[:payment][:voucher][:card] = {} + post[:payment][:voucher][:card][:number] = payment.number + post[:payment][:voucher][:card][:holder_name] = payment.name + post[:payment][:voucher][:card][:holder_document] = options[:holder_document] + post[:payment][:voucher][:card][:exp_month] = payment.month + post[:payment][:voucher][:card][:exp_year] = payment.year + post[:payment][:voucher][:card][:cvv] = payment.verification_value + add_billing_address(post, 'voucher', options) + end + + def voucher?(payment) + return false if payment.is_a?(String) + %w[sodexo vr].include? card_brand(payment) + end + + def headers + { + 'Authorization' => 'Basic ' + Base64.strict_encode64("#{@options[:api_key]}:"), + 'Content-Type' => 'application/json', + 'Accept' => 'application/json' + } + end + + def parse(body) + JSON.parse(body) + end + + def url_for(action, auth = nil) + url = live_url + case action + when 'store' + "#{url}/customers/#{auth}/cards/" + when 'customer' + "#{url}/customers/" + when 'refund', 'void' + "#{url}/charges/#{auth}/" + when 'capture' + "#{url}/charges/#{auth}/capture/" + else + "#{url}/charges/" + end + end + + def commit(action, parameters, auth = nil) + url = url_for(action, auth) + parameters.merge!(parameters[:payment][:credit_card].delete(:card)).delete(:payment) if action == 'store' + response = if %w[refund void].include? action + parse(ssl_request(:delete, url, post_data(parameters), headers)) + else + parse(ssl_post(url, post_data(parameters), headers)) + end + + Response.new( + success_from(response), + message_from(response), + response, + authorization: authorization_from(response, action), + avs_result: AVSResult.new(code: response['some_avs_response_key']), + cvv_result: CVVResult.new(response['some_cvv_response_key']), + test: test?, + error_code: error_code_from(response) + ) + rescue ResponseError => e + message = get_error_message(e) + return Response.new( + false, + "#{STANDARD_ERROR_MESSAGE_MAPPING[e.response.code]} #{message}", + parse(e.response.body), + test: test?, + error_code: STANDARD_ERROR_CODE_MAPPING[e.response.code] + ) + end + + def success_from(response) + %w[pending paid processing canceled active].include? response['status'] + end + + def get_error_message(error) + JSON.parse(error.response.body)['message'] + end + + def message_from(response) + return response['message'] if response['message'] + return response['last_transaction']['acquirer_message'] if response['last_transaction'] + end + + def authorization_from(response, action) + return "#{response['customer']['id']}|#{response['id']}" if action == 'store' + response['id'] + end + + def parse_auth(auth) + auth.split('|') + end + + def post_data(parameters = {}) + parameters.to_json + end + + def error_code_from(response) + STANDARD_ERROR_CODE[:processing_error] unless success_from(response) + end + end + end +end diff --git a/lib/active_merchant/billing/gateways/nab_transact.rb b/lib/active_merchant/billing/gateways/nab_transact.rb index 456d582b343..6715c0e1afb 100644 --- a/lib/active_merchant/billing/gateways/nab_transact.rb +++ b/lib/active_merchant/billing/gateways/nab_transact.rb @@ -6,50 +6,41 @@ module Billing #:nodoc: # be a rebadged Securepay Australia service, though some differences exist. class NabTransactGateway < Gateway API_VERSION = 'xml-4.2' - PERIODIC_API_VERSION = "spxml-4.2" + PERIODIC_API_VERSION = 'spxml-4.2' class_attribute :test_periodic_url, :live_periodic_url - self.test_url = 'https://transact.nab.com.au/test/xmlapi/payment' + self.test_url = 'https://demo.transact.nab.com.au/xmlapi/payment' self.live_url = 'https://transact.nab.com.au/live/xmlapi/payment' - self.test_periodic_url = 'https://transact.nab.com.au/xmlapidemo/periodic' + self.test_periodic_url = 'https://demo.transact.nab.com.au/xmlapi/periodic' self.live_periodic_url = 'https://transact.nab.com.au/xmlapi/periodic' self.supported_countries = ['AU'] - - # The card types supported by the payment gateway - # Note that support for Diners, Amex, and JCB require extra - # steps in setting up your account, as detailed in the NAB Transact API self.supported_cardtypes = [:visa, :master, :american_express, :diners_club, :jcb] self.homepage_url = 'http://transact.nab.com.au' self.display_name = 'NAB Transact' - - cattr_accessor :request_timeout - self.request_timeout = 60 - self.money_format = :cents self.default_currency = 'AUD' - #Transactions currently accepted by NAB Transact XML API + # Transactions currently accepted by NAB Transact XML API TRANSACTIONS = { - :purchase => 0, #Standard Payment - :refund => 4, #Refund - :void => 6, #Client Reversal (Void) - :authorization => 10, #Preauthorise - :capture => 11 #Preauthorise Complete (Advice) + :purchase => 0, # Standard Payment + :refund => 4, # Refund + :void => 6, # Client Reversal (Void) + :unmatched_refund => 666, # Unmatched Refund + :authorization => 10, # Preauthorise + :capture => 11 # Preauthorise Complete (Advice) } PERIODIC_TYPES = { :addcrn => 5, - :editcrn => 5, :deletecrn => 5, :trigger => 8 } SUCCESS_CODES = [ '00', '08', '11', '16', '77' ] - def initialize(options = {}) requires!(options, :login, :password) super @@ -57,15 +48,16 @@ def initialize(options = {}) def purchase(money, credit_card_or_stored_id, options = {}) if credit_card_or_stored_id.respond_to?(:number) - #Credit card for instant payment commit :purchase, build_purchase_request(money, credit_card_or_stored_id, options) else - #Triggered payment for an existing stored credit card - options[:billing_id] = credit_card_or_stored_id.to_s - commit_periodic build_periodic_item(:trigger, money, nil, options) + commit_periodic(:trigger, build_purchase_using_stored_card_request(money, credit_card_or_stored_id, options)) end end + def credit(money, credit_card, options = {}) + commit :unmatched_refund, build_purchase_request(money, credit_card, options) + end + def refund(money, authorization, options = {}) commit :refund, build_reference_request(money, authorization, options) end @@ -78,14 +70,24 @@ def capture(money, authorization, options = {}) commit :capture, build_reference_request(money, authorization, options) end - def store(creditcard, options = {}) - requires!(options, :billing_id, :amount) - commit_periodic(build_periodic_item(:addcrn, options[:amount], creditcard, options)) + def store(credit_card, options = {}) + commit_periodic(:addcrn, build_store_request(credit_card, options)) end def unstore(identification, options = {}) - options[:billing_id] = identification - commit_periodic(build_periodic_item(:deletecrn, options[:amount], nil, options)) + commit_periodic(:deletecrn, build_unstore_request(identification, options)) + end + + def supports_scrubbing? + true + end + + def scrub(transcript) + return '' if transcript.blank? + transcript. + gsub(%r(()[^<]+(<))i, '\1[FILTERED]\2'). + gsub(%r(()[^<]+(<))i, '\1[FILTERED]\2'). + gsub(%r(()[^<]+(<))i, '\1[FILTERED]\2') end private @@ -101,7 +103,7 @@ def add_metadata(xml, options) def build_purchase_request(money, credit_card, options) xml = Builder::XmlMarkup.new - xml.tag! 'amount', amount(money) + xml.tag! 'amount', localized_amount(money, options[:currency] || currency(money)) xml.tag! 'currency', options[:currency] || currency(money) xml.tag! 'purchaseOrderNo', options[:order_id].to_s.gsub(/[ ']/, '') @@ -121,24 +123,26 @@ def build_reference_request(money, reference, options) transaction_id, order_id, preauth_id, original_amount = reference.split('*') - xml.tag! 'amount', (money ? amount(money) : original_amount) + xml.tag! 'amount', (money ? localized_amount(money, options[:currency] || currency(money)) : original_amount) xml.tag! 'currency', options[:currency] || currency(money) xml.tag! 'txnID', transaction_id xml.tag! 'purchaseOrderNo', order_id xml.tag! 'preauthID', preauth_id + add_metadata(xml, options) + xml.target! end - #Generate payment request XML - # - API is set to allow multiple Txn's but currentlu only allows one + # Generate payment request XML + # - API is set to allow multiple Txn's but currently only allows one # - txnSource = 23 - (XML) def build_request(action, body) xml = Builder::XmlMarkup.new xml.instruct! xml.tag! 'NABTransactMessage' do xml.tag! 'MessageInfo' do - xml.tag! 'messageID', Utils.generate_unique_id.slice(0, 30) + xml.tag! 'messageID', SecureRandom.hex(15) xml.tag! 'messageTimestamp', generate_timestamp xml.tag! 'timeoutValue', request_timeout xml.tag! 'apiVersion', API_VERSION @@ -151,8 +155,8 @@ def build_request(action, body) xml.tag! 'RequestType', 'Payment' xml.tag! 'Payment' do - xml.tag! 'TxnList', "count" => 1 do - xml.tag! 'Txn', "ID" => 1 do + xml.tag! 'TxnList', 'count' => 1 do + xml.tag! 'Txn', 'ID' => 1 do xml.tag! 'txnType', TRANSACTIONS[action] xml.tag! 'txnSource', 23 xml << body @@ -164,32 +168,12 @@ def build_request(action, body) xml.target! end - def build_periodic_item(action, money, credit_card, options) - xml = Builder::XmlMarkup.new - - xml.tag! 'actionType', action.to_s - xml.tag! 'periodicType', PERIODIC_TYPES[action] if PERIODIC_TYPES[action] - xml.tag! 'currency', options[:currency] || currency(money) - xml.tag! 'crn', options[:billing_id] - - if credit_card - xml.tag! 'CreditCardInfo' do - xml.tag! 'cardNumber', credit_card.number - xml.tag! 'expiryDate', expdate(credit_card) - xml.tag! 'cvv', credit_card.verification_value if credit_card.verification_value? - end - end - xml.tag! 'amount', amount(money) - - xml.target! - end - - def build_periodic_request(body) + def build_periodic_request(action, body) xml = Builder::XmlMarkup.new xml.instruct! xml.tag! 'NABTransactMessage' do xml.tag! 'MessageInfo' do - xml.tag! 'messageID', ActiveMerchant::Utils.generate_unique_id.slice(0, 30) + xml.tag! 'messageID', SecureRandom.hex(15) xml.tag! 'messageTimestamp', generate_timestamp xml.tag! 'timeoutValue', request_timeout xml.tag! 'apiVersion', PERIODIC_API_VERSION @@ -202,8 +186,10 @@ def build_periodic_request(body) xml.tag! 'RequestType', 'Periodic' xml.tag! 'Periodic' do - xml.tag! 'PeriodicList', "count" => 1 do - xml.tag! 'PeriodicItem', "ID" => 1 do + xml.tag! 'PeriodicList', 'count' => 1 do + xml.tag! 'PeriodicItem', 'ID' => 1 do + xml.tag! 'actionType', action.to_s + xml.tag! 'periodicType', PERIODIC_TYPES[action] if PERIODIC_TYPES[action] xml << body end end @@ -213,20 +199,50 @@ def build_periodic_request(body) xml.target! end + def build_purchase_using_stored_card_request(money, identification, options) + xml = Builder::XmlMarkup.new + + xml.tag! 'crn', identification + xml.tag! 'currency', options[:currency] || currency(money) + xml.tag! 'amount', localized_amount(money, options[:currency] || currency(money)) + + xml.target! + end + + def build_store_request(credit_card, options) + xml = Builder::XmlMarkup.new + + xml.tag! 'crn', options[:billing_id] || SecureRandom.hex(10) + xml.tag! 'CreditCardInfo' do + xml.tag! 'cardNumber', credit_card.number + xml.tag! 'expiryDate', expdate(credit_card) + xml.tag! 'cvv', credit_card.verification_value if credit_card.verification_value? + end + + xml.target! + end + + def build_unstore_request(identification, options) + xml = Builder::XmlMarkup.new + + xml.tag! 'crn', identification + xml.target! + end + def commit(action, request) response = parse(ssl_post(test? ? self.test_url : self.live_url, build_request(action, request))) Response.new(success?(response), message_from(response), response, :test => test?, - :authorization => authorization_from(response) + :authorization => authorization_from(action, response) ) end - def commit_periodic(request) - response = parse(ssl_post(test? ? self.test_periodic_url : self.live_periodic_url, build_periodic_request(request))) + def commit_periodic(action, request) + response = parse(ssl_post(test? ? self.test_periodic_url : self.live_periodic_url, build_periodic_request(action, request))) Response.new(success?(response), message_from(response), response, :test => test?, - :authorization => authorization_from(response) + :authorization => authorization_from(action, response) ) end @@ -234,8 +250,12 @@ def success?(response) SUCCESS_CODES.include?(response[:response_code]) end - def authorization_from(response) - [response[:txn_id], response[:purchase_order_no], response[:preauth_id], response[:amount]].join('*') + def authorization_from(action, response) + if action == :addcrn + response[:crn] + else + [response[:txn_id], response[:purchase_order_no], response[:preauth_id], response[:amount]].join('*') + end end def message_from(response) @@ -260,7 +280,7 @@ def parse(body) def parse_element(response, node) if node.has_elements? - node.elements.each{|element| parse_element(response, element) } + node.elements.each { |element| parse_element(response, element) } else response[node.name.underscore.to_sym] = node.text end @@ -272,6 +292,10 @@ def generate_timestamp time.strftime("%Y%d%m%H%M%S#{time.usec}+000") end + def request_timeout + @options[:request_timeout] || 60 + end + end end end diff --git a/lib/active_merchant/billing/gateways/ncr_secure_pay.rb b/lib/active_merchant/billing/gateways/ncr_secure_pay.rb new file mode 100644 index 00000000000..1a595dd196d --- /dev/null +++ b/lib/active_merchant/billing/gateways/ncr_secure_pay.rb @@ -0,0 +1,165 @@ +require 'nokogiri' + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class NcrSecurePayGateway < Gateway + self.test_url = 'https://testbox.monetra.com:8665/' + self.live_url = 'https://portal.ncrsecurepay.com:8444/' + + self.supported_countries = ['US'] + self.default_currency = 'USD' + self.supported_cardtypes = [:visa, :master, :american_express, :discover] + + self.homepage_url = 'http://www.ncrretailonline.com' + self.display_name = 'NCR Secure Pay' + + def initialize(options={}) + requires!(options, :username, :password) + super + end + + def purchase(money, payment, options={}) + post = {} + add_invoice(post, money, options) + add_payment(post, payment) + add_address(post, payment, options) + + commit('sale', post) + end + + def authorize(money, payment, options={}) + post = {} + add_invoice(post, money, options) + add_payment(post, payment) + add_address(post, payment, options) + + commit('preauth', post) + end + + def capture(money, authorization, options={}) + post = {} + add_reference(post, authorization) + add_invoice(post, money, options) + + commit('preauthcomplete', post) + end + + def refund(money, authorization, options={}) + post = {} + add_reference(post, authorization) + add_invoice(post, money, options) + + commit('credit', post) + end + + def void(authorization, options={}) + post = {} + add_reference(post, authorization) + commit('void', post) + end + + def verify(credit_card, options={}) + MultiResponse.run(:use_first_response) do |r| + r.process { authorize(100, credit_card, options) } + r.process(:ignore_result) { void(r.authorization, options) } + end + end + + def supports_scrubbing? + true + end + + def scrub(transcript) + transcript.gsub(%r(()[^<]*())i, '\1[FILTERED]\2'). + gsub(%r(()[^<]*())i, '\1[FILTERED]\2'). + gsub(%r(()[^<]*())i, '\1[FILTERED]\2') + end + + private + + def add_reference(post, reference) + post[:ttid] = reference + end + + def add_address(post, payment, options) + address = options[:billing_address] || options[:address] + post[:zip] = address[:zip] + post[:street] = address[:address1] + end + + def add_invoice(post, money, options) + post[:amount] = amount(money) + post[:currency] = (options[:currency] || currency(money)) + post[:descmerch] = options[:merchant] if options[:merchant] + post[:ordernum] = options[:order_id] if options[:order_id] + post[:comments] = options[:description] if options[:description] + end + + def add_payment(post, payment) + post[:cardholdername] = payment.name + post[:account] = payment.number + post[:cv] = payment.verification_value + post[:expdate] = expdate(payment) + end + + def parse(body) + doc = Nokogiri::XML(body) + doc.remove_namespaces! + response = doc.xpath('/MonetraResp/Resp')[0] + resp_params = {} + + response.elements.each do |node| + resp_params[node.name.downcase.to_sym] = node.text + end + resp_params + end + + def commit(action, parameters) + url = (test? ? test_url : live_url) + response = parse(ssl_post(url, request_body(action, parameters))) + + Response.new( + success_from(response), + message_from(response), + response, + authorization: authorization_from(response), + test: test?, + error_code: error_code_from(response) + ) + end + + def success_from(response) + response[:code] == 'AUTH' + end + + def message_from(response) + response[:verbiage] + end + + def authorization_from(response) + response[:ttid] + end + + def request_body(action, parameters = {}) + Nokogiri::XML::Builder.new(:encoding => 'utf-8') do |xml| + xml.MonetraTrans do + xml.Trans(identifier: parameters.delete(:identifier) || '1') do + xml.username(options[:username]) + xml.password(options[:password]) + xml.action(action) + parameters.each do |name, value| + xml.send(name, value) + end + end + end + end.to_xml + end + + def error_code_from(response) + unless success_from(response) + response[:msoft_code] || response[:phard_code] + end + end + end + end +end diff --git a/lib/active_merchant/billing/gateways/net_registry.rb b/lib/active_merchant/billing/gateways/net_registry.rb index fddad0d1629..8f199a88ca8 100644 --- a/lib/active_merchant/billing/gateways/net_registry.rb +++ b/lib/active_merchant/billing/gateways/net_registry.rb @@ -22,7 +22,7 @@ module Billing # response will contain a 'receipt' parameter # (response.params['receipt']) if a receipt was issued by the gateway. class NetRegistryGateway < Gateway - self.live_url = self.test_url = 'https://4tknox.au.com/cgi-bin/themerchant.au.com/ecom/external2.pl' + self.live_url = self.test_url = 'https://paygate.ssllock.net/external2.pl' FILTERED_PARAMS = [ 'card_no', 'card_expiry', 'receipt_array' ] @@ -104,7 +104,7 @@ def refund(money, identification, options = {}) end def credit(money, identification, options = {}) - deprecated CREDIT_DEPRECATION_MESSAGE + ActiveMerchant.deprecated CREDIT_DEPRECATION_MESSAGE refund(money, identification, options) end @@ -122,6 +122,7 @@ def status(identification) end private + def add_request_details(params, options) params['COMMENT'] = options[:description] unless options[:description].blank? end @@ -130,7 +131,7 @@ def add_request_details(params, options) # format for a command. def expiry(credit_card) month = format(credit_card.month, :two_digits) - year = format(credit_card.year , :two_digits) + year = format(credit_card.year, :two_digits) "#{month}/#{year}" end @@ -141,7 +142,7 @@ def expiry(credit_card) # omitted if nil. def commit(action, params) # get gateway response - response = parse( ssl_post(self.live_url, post_data(action, params)) ) + response = parse(ssl_post(self.live_url, post_data(action, params))) Response.new(response['status'] == 'approved', message_from(response), response, :authorization => authorization_from(response, action) @@ -151,7 +152,12 @@ def commit(action, params) def post_data(action, params) params['COMMAND'] = TRANSACTIONS[action] params['LOGIN'] = "#{@options[:login]}/#{@options[:password]}" - URI.encode(params.map{|k,v| "#{k}=#{v}"}.join('&')) + escape_uri(params.map { |k, v| "#{k}=#{v}" }.join('&')) + end + + # The upstream is picky and so we can't use CGI.escape like we want to + def escape_uri(uri) + URI::DEFAULT_PARSER.escape(uri) end def parse(response) diff --git a/lib/active_merchant/billing/gateways/netaxept.rb b/lib/active_merchant/billing/gateways/netaxept.rb index 3ee6443f5a9..6681259e657 100644 --- a/lib/active_merchant/billing/gateways/netaxept.rb +++ b/lib/active_merchant/billing/gateways/netaxept.rb @@ -31,8 +31,8 @@ def purchase(money, creditcard, options = {}) requires!(options, :order_id) MultiResponse.run do |r| - r.process{authorize(money, creditcard, options)} - r.process{capture(money, r.authorization, options)} + r.process { authorize(money, creditcard, options) } + r.process { capture(money, r.authorization, options) } end end @@ -40,9 +40,9 @@ def authorize(money, creditcard, options = {}) requires!(options, :order_id) MultiResponse.run do |r| - r.process{setup_transaction(money, options)} - r.process{add_and_auth_credit_card(r.authorization, creditcard, options)} - r.process{query_transaction(r.authorization, options)} + r.process { setup_transaction(money, options) } + r.process { add_and_auth_credit_card(r.authorization, creditcard, options) } + r.process { query_transaction(r.authorization, options) } end end @@ -50,24 +50,24 @@ def capture(money, authorization, options = {}) post = {} add_credentials(post, options) add_authorization(post, authorization, money) - post[:operation] = "Capture" - commit("Netaxept/process.aspx", post) + post[:operation] = 'Capture' + commit('Netaxept/process.aspx', post) end def refund(money, authorization, options = {}) post = {} add_credentials(post, options) add_authorization(post, authorization, money) - post[:operation] = "Credit" - commit("Netaxept/process.aspx", post) + post[:operation] = 'Credit' + commit('Netaxept/process.aspx', post) end def void(authorization, options = {}) post = {} add_credentials(post, options) add_authorization(post, authorization) - post[:operation] = "Annul" - commit("Netaxept/process.aspx", post) + post[:operation] = 'Annul' + commit('Netaxept/process.aspx', post) end private @@ -76,7 +76,7 @@ def setup_transaction(money, options) post = {} add_credentials(post, options) add_order(post, money, options) - commit("Netaxept/Register.aspx", post) + commit('Netaxept/Register.aspx', post) end def add_and_auth_credit_card(authorization, creditcard, options) @@ -84,14 +84,14 @@ def add_and_auth_credit_card(authorization, creditcard, options) add_credentials(post, options, false) add_authorization(post, authorization) add_creditcard(post, creditcard) - commit("terminal/default.aspx", post, false) + commit('terminal/default.aspx', post, false) end def query_transaction(authorization, options) post = {} add_credentials(post, options) add_authorization(post, authorization) - commit("Netaxept/query.aspx", post) + commit('Netaxept/query.aspx', post) end def add_credentials(post, options, secure=true) @@ -109,7 +109,7 @@ def add_order(post, money, options) post[:orderNumber] = options[:order_id] post[:amount] = amount(money) post[:currencyCode] = (options[:currency] || currency(money)) - post[:autoAuth] = "true" + post[:autoAuth] = 'true' end def add_creditcard(post, options) @@ -122,13 +122,13 @@ def commit(path, parameters, xml=true) raw = parse(ssl_get(build_url(path, parameters)), xml) success = false - authorization = (raw["TransactionId"] || parameters[:transactionId]) + authorization = (raw['TransactionId'] || parameters[:transactionId]) if raw[:container] =~ /Exception|Error/ - message = (raw["Message"] || raw["Error"]["Message"]) - elsif raw["Error"] && !raw["Error"].empty? - message = (raw["Error"]["ResponseText"] || raw["Error"]["ResponseCode"]) + message = (raw['Message'] || raw['Error']['Message']) + elsif raw['Error'] && !raw['Error'].empty? + message = (raw['Error']['ResponseText'] || raw['Error']['ResponseCode']) else - message = (raw["ResponseText"] || raw["ResponseCode"] || "OK") + message = (raw['ResponseText'] || raw['ResponseCode'] || 'OK') success = true end @@ -173,9 +173,8 @@ def build_url(base, parameters=nil) end def encode(hash) - hash.collect{|(k,v)| "#{CGI.escape(k.to_s)}=#{CGI.escape(v.to_s)}"}.join('&') + hash.collect { |(k, v)| "#{CGI.escape(k.to_s)}=#{CGI.escape(v.to_s)}" }.join('&') end end end end - diff --git a/lib/active_merchant/billing/gateways/netbanx.rb b/lib/active_merchant/billing/gateways/netbanx.rb new file mode 100644 index 00000000000..88e53fe1298 --- /dev/null +++ b/lib/active_merchant/billing/gateways/netbanx.rb @@ -0,0 +1,294 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class NetbanxGateway < Gateway + # Netbanx is the new REST based API for Optimal Payments / Paysafe + self.test_url = 'https://api.test.netbanx.com/' + self.live_url = 'https://api.netbanx.com/' + + self.supported_countries = %w(AF AX AL DZ AS AD AO AI AQ AG AR AM AW AU AT AZ BS BH BD BB BY BE BZ BJ BM BT BO BQ BA BW BV BR IO BN BG BF BI KH CM CA CV KY CF TD CL CN CX CC CO KM CG CD CK CR CI HR CU CW CY CZ DK DJ DM DO EC EG SV GQ ER EE ET FK FO FJ FI FR GF PF TF GA GM GE DE GH GI GR GL GD GP GU GT GG GN GW GY HT HM HN HK HU IS IN ID IR IQ IE IM IL IT JM JP JE JO KZ KE KI KP KR KW KG LA LV LB LS LR LY LI LT LU MO MK MG MW MY MV ML MT MH MQ MR MU YT MX FM MD MC MN ME MS MA MZ MM NA NR NP NC NZ NI NE NG NU NF MP NO OM PK PW PS PA PG PY PE PH PN PL PT PR QA RE RO RU RW BL SH KN LC MF VC WS SM ST SA SN RS SC SL SG SX SK SI SB SO ZA GS SS ES LK PM SD SR SJ SZ SE CH SY TW TJ TZ TH NL TL TG TK TO TT TN TR TM TC TV UG UA AE GB US UM UY UZ VU VA VE VN VG VI WF EH YE ZM ZW) + self.default_currency = 'CAD' + self.supported_cardtypes = [ + :american_express, + :diners_club, + :discover, + :jcb, + :master, + :maestro, + :visa + ] + + self.money_format = :cents + + self.homepage_url = 'https://processing.paysafe.com/' + self.display_name = 'Netbanx by PaySafe' + + def initialize(options={}) + requires!(options, :account_number, :api_key) + super + end + + def purchase(money, payment, options={}) + post = {} + add_invoice(post, money, options) + add_settle_with_auth(post) + add_payment(post, payment, options) + + commit(:post, 'auths', post) + end + + def authorize(money, payment, options={}) + post = {} + add_invoice(post, money, options) + add_payment(post, payment, options) + + commit(:post, 'auths', post) + end + + def capture(money, authorization, options={}) + post = {} + add_invoice(post, money, options) + + commit(:post, "auths/#{authorization}/settlements", post) + end + + def refund(money, authorization, options={}) + post = {} + add_invoice(post, money, options) + + # Setting merchantRefNumber to a unique id for each refund + # This is to support multiple partial refunds for the same order + post[:merchantRefNum] = SecureRandom.uuid + + commit(:post, "settlements/#{authorization}/refunds", post) + end + + def void(authorization, options={}) + post = {} + add_order_id(post, options) + + commit(:post, "auths/#{authorization}/voidauths", post) + end + + def verify(credit_card, options={}) + post = {} + add_payment(post, credit_card) + add_order_id(post, options) + + commit(:post, 'verifications', post) + end + + # note: when passing options[:customer] we only attempt to add the + # card to the profile_id passed as the options[:customer] + def store(credit_card, options={}) + # locale can only be one of en_US, fr_CA, en_GB + requires!(options, :locale) + post = {} + add_credit_card(post, credit_card, options) + add_customer_data(post, options) + + commit(:post, 'customervault/v1/profiles', post) + end + + def unstore(identification, options = {}) + customer_id, card_id = identification.split('|') + + if card_id.nil? + # deleting the profile + commit(:delete, "customervault/v1/profiles/#{CGI.escape(customer_id)}", nil) + else + # deleting the card from the profile + commit(:delete, "customervault/v1/profiles/#{CGI.escape(customer_id)}/cards/#{CGI.escape(card_id)}", nil) + end + end + + def supports_scrubbing? + true + end + + def scrub(transcript) + transcript. + gsub(%r((Authorization: Basic )\w+), '\1[FILTERED]'). + gsub(%r(("card\\?":{\\?"cardNum\\?":\\?")\d+), '\1[FILTERED]'). + gsub(%r(("cvv\\?":\\?")\d+), '\1[FILTERED]') + end + + private + + def add_settle_with_auth(post) + post[:settleWithAuth] = true + end + + def add_customer_data(post, options) + post[:merchantCustomerId] = (options[:merchant_customer_id] || SecureRandom.uuid) + post[:locale] = options[:locale] + end + + def add_credit_card(post, credit_card, options = {}) + post[:card] ||= {} + post[:card][:cardNum] = credit_card.number + post[:card][:holderName] = credit_card.name + post[:card][:cvv] = credit_card.verification_value + post[:card][:cardExpiry] = expdate(credit_card) + if options[:billing_address] + post[:card][:billingAddress] = map_address(options[:billing_address]) + end + end + + def add_invoice(post, money, options) + post[:amount] = amount(money) + add_order_id(post, options) + end + + def add_payment(post, credit_card_or_reference, options = {}) + post[:card] ||= {} + if credit_card_or_reference.is_a?(String) + post[:card][:paymentToken] = credit_card_or_reference + else + post[:card][:cardNum] = credit_card_or_reference.number + post[:card][:cvv] = credit_card_or_reference.verification_value + post[:card][:cardExpiry] = expdate(credit_card_or_reference) + end + + post[:currencyCode] = options[:currency] if options[:currency] + post[:billingDetails] = map_address(options[:billing_address]) if options[:billing_address] + end + + def expdate(credit_card) + year = format(credit_card.year, :four_digits) + month = format(credit_card.month, :two_digits) + + # returns a hash (necessary in the card JSON object) + { :month => month, :year => year } + end + + def add_order_id(post, options) + post[:merchantRefNum] = (options[:order_id] || SecureRandom.uuid) + end + + def map_address(address) + return {} if address.nil? + country = Country.find(address[:country]) if address[:country] + mapped = { + :street => address[:address1], + :city => address[:city], + :zip => address[:zip], + :state => address[:state], + } + mapped[:country] = country.code(:alpha2).value unless country.blank? + + mapped + end + + def parse(body) + body.blank? ? {} : JSON.parse(body) + end + + def commit(method, uri, parameters) + params = parameters.to_json unless parameters.nil? + response = begin + parse(ssl_request(method, get_url(uri), params, headers)) + rescue ResponseError => e + return Response.new(false, 'Invalid Login') if(e.response.code == '401') + parse(e.response.body) + end + + success = success_from(response) + Response.new( + success, + message_from(success, response), + response, + :test => test?, + :error_code => error_code_from(response), + :authorization => authorization_from(success, get_url(uri), method, response) + ) + end + + def get_url(uri) + url = (test? ? test_url : live_url) + if uri =~ /^customervault/ + "#{url}#{uri}" + else + "#{url}cardpayments/v1/accounts/#{@options[:account_number]}/#{uri}" + end + end + + def success_from(response) + response.blank? || !response.key?('error') + end + + def message_from(success, response) + success ? 'OK' : (response['error']['message'] || 'Unknown error - please contact Netbanx-Paysafe') + end + + def authorization_from(success, url, method, response) + if success && response.present? && url.match(/cardpayments\/v1\/accounts\/.*\//) + response['id'] + elsif method == :post && url.match(/customervault\/.*\//) + # auth for tokenised customer vault is returned as + # customer_profile_id|card_id|payment_method_token + # + # customer_profile_id is the uuid that identifies the customer + # card_id is the uuid that identifies the card + # payment_method_token is the token that needs to be used when + # calling purchase with a token + # + # both id's are used to unstore, the payment token is only used for + # purchase transactions + [response['id'], response['cards'].first['id'], response['cards'].first['paymentToken']].join('|') + end + end + + # Builds the auth and U-A headers for the request + def headers + { + 'Accept' => 'application/json', + 'Content-type' => 'application/json', + 'Authorization' => "Basic #{Base64.strict_encode64(@options[:api_key].to_s)}", + 'User-Agent' => "Netbanx-Paysafe v1.0/ActiveMerchant #{ActiveMerchant::VERSION}" + } + end + + def error_code_from(response) + unless success_from(response) + case response['errorCode'] + when '3002' then STANDARD_ERROR_CODE[:invalid_number] # You submitted an invalid card number or brand or combination of card number and brand with your request. + when '3004' then STANDARD_ERROR_CODE[:incorrect_zip] # The zip/postal code must be provided for an AVS check request. + when '3005' then STANDARD_ERROR_CODE[:incorrect_cvc] # You submitted an incorrect CVC value with your request. + when '3006' then STANDARD_ERROR_CODE[:expired_card] # You submitted an expired credit card number with your request. + when '3009' then STANDARD_ERROR_CODE[:card_declined] # Your request has been declined by the issuing bank. + when '3011' then STANDARD_ERROR_CODE[:card_declined] # Your request has been declined by the issuing bank because the card used is a restricted card. Contact the cardholder's credit card company for further investigation. + when '3012' then STANDARD_ERROR_CODE[:card_declined] # Your request has been declined by the issuing bank because the credit card expiry date submitted is invalid. + when '3013' then STANDARD_ERROR_CODE[:card_declined] # Your request has been declined by the issuing bank due to problems with the credit card account. + when '3014' then STANDARD_ERROR_CODE[:card_declined] # Your request has been declined - the issuing bank has returned an unknown response. Contact the card holder's credit card company for further investigation. + when '3015' then STANDARD_ERROR_CODE[:card_declined] # The bank has requested that you process the transaction manually by calling the cardholder's credit card company. + when '3016' then STANDARD_ERROR_CODE[:card_declined] # The bank has requested that you retrieve the card from the cardholder – it may be a lost or stolen card. + when '3017' then STANDARD_ERROR_CODE[:invalid_number] # You submitted an invalid credit card number with your request. + when '3022' then STANDARD_ERROR_CODE[:card_declined] # The card has been declined due to insufficient funds. + when '3023' then STANDARD_ERROR_CODE[:card_declined] # Your request has been declined by the issuing bank due to its proprietary card activity regulations. + when '3024' then STANDARD_ERROR_CODE[:card_declined] # Your request has been declined because the issuing bank does not permit the transaction for this card. + when '3032' then STANDARD_ERROR_CODE[:card_declined] # Your request has been declined by the issuing bank or external gateway because the card is probably in one of their negative databases. + when '3035' then STANDARD_ERROR_CODE[:card_declined] # Your request has been declined due to exceeded PIN attempts. + when '3036' then STANDARD_ERROR_CODE[:card_declined] # Your request has been declined due to an invalid issuer. + when '3037' then STANDARD_ERROR_CODE[:card_declined] # Your request has been declined because it is invalid. + when '3038' then STANDARD_ERROR_CODE[:card_declined] # Your request has been declined due to customer cancellation. + when '3039' then STANDARD_ERROR_CODE[:card_declined] # Your request has been declined due to an invalid authentication value. + when '3040' then STANDARD_ERROR_CODE[:card_declined] # Your request has been declined because the request type is not permitted on the card. + when '3041' then STANDARD_ERROR_CODE[:card_declined] # Your request has been declined due to a timeout. + when '3042' then STANDARD_ERROR_CODE[:card_declined] # Your request has been declined due to a cryptographic error. + when '3045' then STANDARD_ERROR_CODE[:invalid_expiry_date] # You submitted an invalid date format for this request. + when '3046' then STANDARD_ERROR_CODE[:card_declined] # The transaction was declined because the amount was set to zero. + when '3047' then STANDARD_ERROR_CODE[:card_declined] # The transaction was declined because the amount exceeds the floor limit. + when '3048' then STANDARD_ERROR_CODE[:card_declined] # The transaction was declined because the amount is less than the floor limit. + when '3049' then STANDARD_ERROR_CODE[:card_declined] # The bank has requested that you retrieve the card from the cardholder – the credit card has expired. + when '3050' then STANDARD_ERROR_CODE[:card_declined] # The bank has requested that you retrieve the card from the cardholder – fraudulent activity is suspected. + when '3051' then STANDARD_ERROR_CODE[:card_declined] # The bank has requested that you retrieve the card from the cardholder – contact the acquirer for more information. + when '3052' then STANDARD_ERROR_CODE[:card_declined] # The bank has requested that you retrieve the card from the cardholder – the credit card is restricted. + when '3053' then STANDARD_ERROR_CODE[:card_declined] # The bank has requested that you retrieve the card from the cardholder – please call the acquirer. + when '3054' then STANDARD_ERROR_CODE[:card_declined] # The transaction was declined due to suspected fraud. + else STANDARD_ERROR_CODE[:processing_error] + end + end + end + end + end +end diff --git a/lib/active_merchant/billing/gateways/netbilling.rb b/lib/active_merchant/billing/gateways/netbilling.rb index 6488d54d14f..296d9e198ad 100644 --- a/lib/active_merchant/billing/gateways/netbilling.rb +++ b/lib/active_merchant/billing/gateways/netbilling.rb @@ -1,5 +1,15 @@ module ActiveMerchant #:nodoc: module Billing #:nodoc: + # To perform PCI Compliant Repeat Billing + # + # Ensure that PCI Compliant Repeat Billing is enabled on your merchant account: + # "Enable PCI Compliant Repeat Billing, Up-selling and Cross-selling" in Step 6 of the Credit Cards setup page + # + # Instead of passing a credit_card to authorize or purchase, pass the transaction id (res.authorization) + # of a past Netbilling transaction + # + # To store billing information without performing an operation, use the 'store' method + # which invokes the tran_type 'Q' (Quasi) operation and returns a transaction id to use in future Repeat Billing operations class NetbillingGateway < Gateway self.live_url = self.test_url = 'https://secure.netbilling.com:1402/gw/sas/direct3.1' @@ -9,7 +19,8 @@ class NetbillingGateway < Gateway :refund => 'R', :credit => 'C', :capture => 'D', - :void => 'U' + :void => 'U', + :quasi => 'Q' } SUCCESS_CODES = [ '1', 'T' ] @@ -27,24 +38,26 @@ def initialize(options = {}) super end - def authorize(money, credit_card, options = {}) + def authorize(money, payment_source, options = {}) post = {} add_amount(post, money) add_invoice(post, options) - add_credit_card(post, credit_card) - add_address(post, credit_card, options) + add_payment_source(post, payment_source) + add_address(post, payment_source, options) add_customer_data(post, options) + add_user_data(post, options) commit(:authorization, post) end - def purchase(money, credit_card, options = {}) + def purchase(money, payment_source, options = {}) post = {} add_amount(post, money) add_invoice(post, options) - add_credit_card(post, credit_card) - add_address(post, credit_card, options) + add_payment_source(post, payment_source) + add_address(post, payment_source, options) add_customer_data(post, options) + add_user_data(post, options) commit(:purchase, post) end @@ -69,6 +82,7 @@ def credit(money, credit_card, options = {}) add_credit_card(post, credit_card) add_address(post, credit_card, options) add_customer_data(post, options) + add_user_data(post, options) commit(:credit, post) end @@ -79,10 +93,31 @@ def void(source, options = {}) commit(:void, post) end + def store(credit_card, options = {}) + post = {} + add_amount(post, 0) + add_payment_source(post, credit_card) + add_address(post, credit_card, options) + add_customer_data(post, options) + + commit(:quasi, post) + end + def test? (@options[:login] == TEST_LOGIN || super) end + def supports_scrubbing + true + end + + def scrub(transcript) + transcript. + gsub(%r((Authorization: Basic )\w+), '\1[FILTERED]'). + gsub(%r((&?card_number=)[^&]*), '\1[FILTERED]'). + gsub(%r((&?card_cvv2=)[^&]*), '\1[FILTERED]') + end + private def add_amount(post, money) @@ -108,23 +143,38 @@ def add_address(post, credit_card, options) post[:bill_state] = billing_address[:state] end - if shipping_address = options[:shipping_address] - first_name, last_name = parse_first_and_last_name(shipping_address[:name]) - - post[:ship_name1] = first_name - post[:ship_name2] = last_name - post[:ship_street] = shipping_address[:address1] - post[:ship_zip] = shipping_address[:zip] - post[:ship_city] = shipping_address[:city] - post[:ship_country] = shipping_address[:country] - post[:ship_state] = shipping_address[:state] - end + if shipping_address = options[:shipping_address] + post[:ship_name1], post[:ship_name2] = split_names(shipping_address[:name]) + post[:ship_street] = shipping_address[:address1] + post[:ship_zip] = shipping_address[:zip] + post[:ship_city] = shipping_address[:city] + post[:ship_country] = shipping_address[:country] + post[:ship_state] = shipping_address[:state] + end end def add_invoice(post, options) post[:description] = options[:description] end + def add_payment_source(params, source) + if source.is_a?(String) + add_transaction_id(params, source) + else + add_credit_card(params, source) + end + end + + def add_user_data(post, options) + if options[:order_id] + post[:user_data] = "order_id:#{options[:order_id]}" + end + end + + def add_transaction_id(post, transaction_id) + post[:card_number] = 'CS:' + transaction_id + end + def add_credit_card(post, credit_card) post[:bill_name1] = credit_card.first_name post[:bill_name2] = credit_card.last_name @@ -136,7 +186,7 @@ def add_credit_card(post, credit_card) def parse(body) results = {} body.split(/&/).each do |pair| - key,val = pair.split(/\=/) + key, val = pair.split(/\=/) results[key.to_sym] = CGI.unescape(val) end results @@ -168,30 +218,15 @@ def message_from(response) success?(response) ? SUCCESS_MESSAGE : (response[:auth_msg] || FAILURE_MESSAGE) end - def expdate(credit_card) - year = sprintf("%.4i", credit_card.year) - month = sprintf("%.2i", credit_card.month) - - "#{month}#{year[-2..-1]}" - end - def post_data(action, parameters = {}) parameters[:account_id] = @options[:login] parameters[:site_tag] = @options[:site_tag] if @options[:site_tag].present? parameters[:pay_type] = 'C' parameters[:tran_type] = TRANSACTIONS[action] - parameters.reject{|k,v| v.blank?}.collect { |key, value| "#{key}=#{CGI.escape(value.to_s)}" }.join("&") + parameters.reject { |k, v| v.blank? }.collect { |key, value| "#{key}=#{CGI.escape(value.to_s)}" }.join('&') end - def parse_first_and_last_name(value) - name = value.to_s.split(' ') - - last_name = name.pop || '' - first_name = name.join(' ') - [ first_name, last_name ] - end end end end - diff --git a/lib/active_merchant/billing/gateways/netpay.rb b/lib/active_merchant/billing/gateways/netpay.rb index babf9542502..48e2a0a14d5 100644 --- a/lib/active_merchant/billing/gateways/netpay.rb +++ b/lib/active_merchant/billing/gateways/netpay.rb @@ -18,7 +18,7 @@ module Billing #:nodoc: # transaction. After this, a refund should be performed instead. # # In addition to the regular ActiveMerchant transaction options, NETPAY - # also supports a `:mode` parameter. This allows testing to be peformed + # also supports a `:mode` parameter. This allows testing to be performed # in production and force specific results. # # * 'P' - Production @@ -51,7 +51,7 @@ class NetpayGateway < Gateway self.display_name = 'NETPAY Gateway' CURRENCY_CODES = { - "MXN" => '484' + 'MXN' => '484' } # The header keys that we will provide in the response params hash @@ -110,7 +110,6 @@ def refund(money, authorization, options = {}) add_order_id(post, order_id_from(authorization)) add_amount(post, money, options) - #commit('Refund', post, options) commit('Credit', post, options) end @@ -157,7 +156,7 @@ def build_authorization(request_params, response_params) end def split_authorization(authorization) - order_id, amount, currency = authorization.split("|") + order_id, amount, currency = authorization.split('|') [order_id, amount, currency] end @@ -166,8 +165,8 @@ def order_id_from(authorization) end def expdate(credit_card) - year = sprintf("%.4i", credit_card.year) - month = sprintf("%.2i", credit_card.month) + year = sprintf('%.4i', credit_card.year) + month = sprintf('%.2i', credit_card.month) "#{month}/#{year[-2..-1]}" end @@ -191,7 +190,7 @@ def commit(action, parameters, options) add_login_data(parameters) add_action(parameters, action, options) - post = parameters.collect{|key, value| "#{key}=#{CGI.escape(value.to_s)}" }.join("&") + post = parameters.collect { |key, value| "#{key}=#{CGI.escape(value.to_s)}" }.join('&') parse(ssl_post(url, post), parameters) end diff --git a/lib/active_merchant/billing/gateways/network_merchants.rb b/lib/active_merchant/billing/gateways/network_merchants.rb new file mode 100644 index 00000000000..aadc09d425f --- /dev/null +++ b/lib/active_merchant/billing/gateways/network_merchants.rb @@ -0,0 +1,241 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class NetworkMerchantsGateway < Gateway + self.live_url = self.test_url = 'https://secure.networkmerchants.com/api/transact.php' + + self.supported_countries = ['US'] + self.supported_cardtypes = [:visa, :master, :american_express, :discover] + + self.homepage_url = 'http://www.nmi.com/' + self.display_name = 'Network Merchants (NMI)' + + self.money_format = :dollars + self.default_currency = 'USD' + + def initialize(options = {}) + requires!(options, :login, :password) + super + end + + def authorize(money, creditcard_or_vault_id, options = {}) + post = build_auth_post(money, creditcard_or_vault_id, options) + commit('auth', post) + end + + def purchase(money, creditcard_or_vault_id, options = {}) + post = build_purchase_post(money, creditcard_or_vault_id, options) + commit('sale', post) + end + + def capture(money, authorization, options = {}) + post = build_capture_post(money, authorization, options) + commit('capture', post) + end + + def void(authorization, options = {}) + post = build_void_post(authorization, options) + commit('void', post) + end + + def refund(money, authorization, options = {}) + post = build_refund_post(money, authorization, options) + commit('refund', post) + end + + def store(creditcard, options = {}) + post = build_store_post(creditcard, options) + commit_vault('add_customer', post) + end + + def unstore(customer_vault_id, options = {}) + post = build_unstore_post(customer_vault_id, options) + commit_vault('delete_customer', post) + end + + private + + def build_auth_post(money, creditcard_or_vault_id, options) + post = {} + add_order(post, options) + add_address(post, options) + add_shipping_address(post, options) + add_payment_method(post, creditcard_or_vault_id, options) + add_amount(post, money, options) + post + end + + def build_purchase_post(money, creditcard, options) + build_auth_post(money, creditcard, options) + end + + def build_capture_post(money, authorization, options) + post = {} + post[:transactionid] = authorization + add_amount(post, money, options) + post + end + + def build_void_post(authorization, options) + post = {} + post[:transactionid] = authorization + post + end + + def build_refund_post(money, authorization, options) + post = {} + post[:transactionid] = authorization + add_amount(post, money, options) + post + end + + def build_store_post(creditcard_or_check, options) + post = {} + add_address(post, options) + add_shipping_address(post, options) + add_payment_method(post, creditcard_or_check, options) + post + end + + def build_unstore_post(customer_vault_id, options) + post = {} + post['customer_vault_id'] = customer_vault_id + post + end + + def add_order(post, options) + post[:orderid] = options[:order_id] + post[:orderdescription] = options[:description] + end + + def add_address(post, options) + post[:email] = options[:email] + post[:ipaddress] = options[:ip] + + address = options[:billing_address] || options[:address] || {} + post[:address1] = address[:address1] + post[:address2] = address[:address2] + post[:city] = address[:city] + post[:state] = address[:state].blank? ? 'n/a' : address[:state] + post[:zip] = address[:zip] + post[:country] = address[:country] + post[:phone] = address[:phone] + end + + def add_shipping_address(post, options) + shipping_address = options[:shipping_address] || {} + post[:shipping_address1] = shipping_address[:address1] + post[:shipping_address2] = shipping_address[:address2] + post[:shipping_city] = shipping_address[:city] + post[:shipping_state] = shipping_address[:state] + post[:shipping_zip] = shipping_address[:zip] + post[:shipping_country] = shipping_address[:country] + end + + def add_swipe_data(post, creditcard, options) + # unencrypted tracks + if creditcard.respond_to?(:track_data) && creditcard.track_data.present? + post[:track_1] = creditcard.track_data + else + post[:track_1] = options[:track_1] + post[:track_2] = options[:track_2] + post[:track_3] = options[:track_3] + end + + # encrypted tracks + post[:magnesafe_track_1] = options[:magnesafe_track_1] + post[:magnesafe_track_2] = options[:magnesafe_track_2] + post[:magnesafe_track_3] = options[:magnesafe_track_3] + post[:magnesafe_magneprint] = options[:magnesafe_magneprint] + post[:magnesafe_ksn] = options[:magnesafe_ksn] + post[:magnesafe_magneprint_status] = options[:magnesafe_magneprint_status] + end + + def add_payment_method(post, payment_source, options) + post[:processor_id] = options[:processor_id] + post[:customer_vault] = 'add_customer' if options[:store] + + add_swipe_data(post, payment_source, options) + + if payment_source.is_a?(Check) + check = payment_source + post[:firstname] = check.first_name + post[:lastname] = check.last_name + post[:checkname] = check.name + post[:checkaba] = check.routing_number + post[:checkaccount] = check.account_number + post[:account_type] = check.account_type + post[:account_holder_type] = check.account_holder_type + post[:payment] = 'check' + elsif payment_source.respond_to?(:number) + creditcard = payment_source + post[:firstname] = creditcard.first_name + post[:lastname] = creditcard.last_name + post[:ccnumber] = creditcard.number + post[:ccexp] = format(creditcard.month, :two_digits) + format(creditcard.year, :two_digits) + post[:cvv] = creditcard.verification_value + post[:payment] = 'creditcard' + else + post[:customer_vault_id] = payment_source + end + end + + def add_login(post) + post[:username] = @options[:login] + post[:password] = @options[:password] + end + + def add_amount(post, money, options) + post[:currency] = options[:currency] || currency(money) + post[:amount] = amount(money) + end + + def commit_vault(action, parameters) + commit(nil, parameters.merge(:customer_vault => action)) + end + + def commit(action, parameters) + raw = parse(ssl_post(self.live_url, build_request(action, parameters))) + + success = (raw['response'] == ResponseCodes::APPROVED) + + authorization = authorization_from(success, parameters, raw) + + Response.new(success, raw['responsetext'], raw, + :test => test?, + :authorization => authorization, + :avs_result => { :code => raw['avsresponse']}, + :cvv_result => raw['cvvresponse'] + ) + end + + def build_request(action, parameters) + parameters[:type] = action if action + add_login(parameters) + parameters.to_query + end + + def authorization_from(success, parameters, response) + return nil unless success + + authorization = response['transactionid'] + if(parameters[:customer_vault] && (authorization.nil? || authorization.empty?)) + authorization = response['customer_vault_id'] + end + + authorization + end + + class ResponseCodes + APPROVED = '1' + DENIED = '2' + ERROR = '3' + end + + def parse(raw_response) + rsp = CGI.parse(raw_response) + rsp.keys.each { |k| rsp[k] = rsp[k].first } # flatten out the values + rsp + end + end + end +end diff --git a/lib/active_merchant/billing/gateways/nmi.rb b/lib/active_merchant/billing/gateways/nmi.rb index 39fc1e8af34..7d5339d6d70 100644 --- a/lib/active_merchant/billing/gateways/nmi.rb +++ b/lib/active_merchant/billing/gateways/nmi.rb @@ -1,20 +1,324 @@ module ActiveMerchant #:nodoc: module Billing #:nodoc: - class NmiGateway < AuthorizeNetGateway - self.test_url = 'https://secure.networkmerchants.com/gateway/transact.dll' - self.live_url = 'https://secure.networkmerchants.com/gateway/transact.dll' - self.homepage_url = 'http://nmi.com/' - self.display_name = 'NMI' + class NmiGateway < Gateway + include Empty + + DUP_WINDOW_DEPRECATION_MESSAGE = 'The class-level duplicate_window variable is deprecated. Please use the :dup_seconds transaction option instead.' + + self.test_url = self.live_url = 'https://secure.nmi.com/api/transact.php' + self.default_currency = 'USD' + self.money_format = :dollars self.supported_countries = ['US'] self.supported_cardtypes = [:visa, :master, :american_express, :discover] + self.homepage_url = 'http://nmi.com/' + self.display_name = 'NMI' - private - def add_creditcard(post, creditcard, options={}) + def self.duplicate_window=(seconds) + ActiveMerchant.deprecated(DUP_WINDOW_DEPRECATION_MESSAGE) + @dup_seconds = seconds + end + + def self.duplicate_window + instance_variable_defined?(:@dup_seconds) ? @dup_seconds : nil + end + + def initialize(options = {}) + requires!(options, :login, :password) super - post[:recurring_billing] = "TRUE" if options[:recurring] end - end + def purchase(amount, payment_method, options={}) + post = {} + add_invoice(post, amount, options) + add_payment_method(post, payment_method, options) + add_stored_credential(post, options) + add_customer_data(post, options) + add_vendor_data(post, options) + add_merchant_defined_fields(post, options) + add_level3_fields(post, options) + + commit('sale', post) + end + + def authorize(amount, payment_method, options={}) + post = {} + add_invoice(post, amount, options) + add_payment_method(post, payment_method, options) + add_stored_credential(post, options) + add_customer_data(post, options) + add_vendor_data(post, options) + add_merchant_defined_fields(post, options) + add_level3_fields(post, options) + + commit('auth', post) + end + + def capture(amount, authorization, options={}) + post = {} + add_invoice(post, amount, options) + add_reference(post, authorization) + add_merchant_defined_fields(post, options) + + commit('capture', post) + end + + def void(authorization, options={}) + post = {} + add_reference(post, authorization) + add_payment_type(post, authorization) + + commit('void', post) + end + + def refund(amount, authorization, options={}) + post = {} + add_invoice(post, amount, options) + add_reference(post, authorization) + add_payment_type(post, authorization) + + commit('refund', post) + end + + def credit(amount, payment_method, options={}) + post = {} + add_invoice(post, amount, options) + add_payment_method(post, payment_method, options) + add_customer_data(post, options) + add_vendor_data(post, options) + add_level3_fields(post, options) + + commit('credit', post) + end + + def verify(payment_method, options={}) + post = {} + add_payment_method(post, payment_method, options) + add_customer_data(post, options) + add_vendor_data(post, options) + add_merchant_defined_fields(post, options) + add_level3_fields(post, options) + + commit('validate', post) + end + + def store(payment_method, options = {}) + post = {} + add_invoice(post, nil, options) + add_payment_method(post, payment_method, options) + add_customer_data(post, options) + add_vendor_data(post, options) + add_merchant_defined_fields(post, options) + + commit('add_customer', post) + end + + def verify_credentials + response = void('0') + response.message != 'Authentication Failed' + end + + def supports_scrubbing? + true + end + + def scrub(transcript) + transcript. + gsub(%r((password=)[^&\n]*), '\1[FILTERED]'). + gsub(%r((ccnumber=)\d+), '\1[FILTERED]'). + gsub(%r((cvv=)\d+), '\1[FILTERED]'). + gsub(%r((checkaba=)\d+), '\1[FILTERED]'). + gsub(%r((checkaccount=)\d+), '\1[FILTERED]'). + gsub(%r((cryptogram=)[^&]+(&?)), '\1[FILTERED]\2') + end + + def supports_network_tokenization? + true + end + + private + + def add_level3_fields(post, options) + add_fields_to_post_if_present(post, options, [:tax, :shipping, :ponumber]) + end + + def add_invoice(post, money, options) + post[:amount] = amount(money) + post[:orderid] = options[:order_id] + post[:orderdescription] = options[:description] + post[:currency] = options[:currency] || currency(money) + post[:billing_method] = 'recurring' if options[:recurring] + if (dup_seconds = (options[:dup_seconds] || self.class.duplicate_window)) + post[:dup_seconds] = dup_seconds + end + end + + def add_payment_method(post, payment_method, options) + if(payment_method.is_a?(String)) + customer_vault_id, _ = split_authorization(payment_method) + post[:customer_vault_id] = customer_vault_id + elsif payment_method.is_a?(NetworkTokenizationCreditCard) + post[:ccnumber] = payment_method.number + post[:ccexp] = exp_date(payment_method) + post[:token_cryptogram] = payment_method.payment_cryptogram + elsif(card_brand(payment_method) == 'check') + post[:payment] = 'check' + post[:firstname] = payment_method.first_name + post[:lastname] = payment_method.last_name + post[:checkname] = payment_method.name + post[:checkaba] = payment_method.routing_number + post[:checkaccount] = payment_method.account_number + post[:account_holder_type] = payment_method.account_holder_type + post[:account_type] = payment_method.account_type + post[:sec_code] = options[:sec_code] || 'WEB' + else + post[:payment] = 'creditcard' + post[:firstname] = payment_method.first_name + post[:lastname] = payment_method.last_name + post[:ccnumber] = payment_method.number + post[:cvv] = payment_method.verification_value unless empty?(payment_method.verification_value) + post[:ccexp] = exp_date(payment_method) + end + end + + def add_stored_credential(post, options) + return unless (stored_credential = options[:stored_credential]) + + if stored_credential[:initiator] == 'cardholder' + post[:initiated_by] = 'customer' + else + post[:initiated_by] = 'merchant' + end + + # :reason_type, when provided, overrides anything previously set in + # post[:billing_method] (see `add_invoice` and the :recurring) option + case stored_credential[:reason_type] + when 'recurring' + post[:billing_method] = 'recurring' + when 'installment' + post[:billing_method] = 'installment' + when 'unscheduled' + post.delete(:billing_method) + end + + if stored_credential[:initial_transaction] + post[:stored_credential_indicator] = 'stored' + else + post[:stored_credential_indicator] = 'used' + post[:initial_transaction_id] = stored_credential[:network_transaction_id] + end + end + + def add_customer_data(post, options) + post[:email] = options[:email] + post[:ipaddress] = options[:ip] + post[:customer_id] = options[:customer_id] || options[:customer] + + if(billing_address = options[:billing_address] || options[:address]) + post[:company] = billing_address[:company] + post[:address1] = billing_address[:address1] + post[:address2] = billing_address[:address2] + post[:city] = billing_address[:city] + post[:state] = billing_address[:state] + post[:country] = billing_address[:country] + post[:zip] = billing_address[:zip] + post[:phone] = billing_address[:phone] + end + + if(shipping_address = options[:shipping_address]) + post[:shipping_company] = shipping_address[:company] + post[:shipping_address1] = shipping_address[:address1] + post[:shipping_address2] = shipping_address[:address2] + post[:shipping_city] = shipping_address[:city] + post[:shipping_state] = shipping_address[:state] + post[:shipping_country] = shipping_address[:country] + post[:shipping_zip] = shipping_address[:zip] + post[:shipping_phone] = shipping_address[:phone] + end + end + + def add_vendor_data(post, options) + post[:vendor_id] = options[:vendor_id] if options[:vendor_id] + post[:processor_id] = options[:processor_id] if options[:processor_id] + end + + def add_merchant_defined_fields(post, options) + (1..20).each do |each| + key = "merchant_defined_field_#{each}".to_sym + post[key] = options[key] if options[key] + end + end + + def add_reference(post, authorization) + transaction_id, _ = split_authorization(authorization) + post[:transactionid] = transaction_id + end + + def add_payment_type(post, authorization) + _, payment_type = split_authorization(authorization) + post[:payment] = payment_type if payment_type + end + + def exp_date(payment_method) + "#{format(payment_method.month, :two_digits)}#{format(payment_method.year, :two_digits)}" + end + + def commit(action, params) + params[action == 'add_customer' ? :customer_vault : :type] = action + params[:username] = @options[:login] + params[:password] = @options[:password] + + raw_response = ssl_post(url, post_data(action, params), headers) + response = parse(raw_response) + succeeded = success_from(response) + + Response.new( + succeeded, + message_from(succeeded, response), + response, + authorization: authorization_from(response, params[:payment], action), + avs_result: AVSResult.new(code: response[:avsresponse]), + cvv_result: CVVResult.new(response[:cvvresponse]), + test: test? + ) + end + + def authorization_from(response, payment_type, action) + authorization = (action == 'add_customer' ? response[:customer_vault_id] : response[:transactionid]) + [ authorization, payment_type ].join('#') + end + + def split_authorization(authorization) + authorization.split('#') + end + + def headers + { 'Content-Type' => 'application/x-www-form-urlencoded;charset=UTF-8' } + end + + def post_data(action, params) + params.map { |k, v| "#{k}=#{CGI.escape(v.to_s)}" }.join('&') + end + + def url + test? ? test_url : live_url + end + + def parse(body) + Hash[CGI::parse(body).map { |k, v| [k.intern, v.first] }] + end + + def success_from(response) + response[:response] == '1' + end + + def message_from(succeeded, response) + if succeeded + 'Succeeded' + else + response[:responsetext] + end + end + + end end end - diff --git a/lib/active_merchant/billing/gateways/ogone.rb b/lib/active_merchant/billing/gateways/ogone.rb index 973faa5c4ec..883ac94a05a 100644 --- a/lib/active_merchant/billing/gateways/ogone.rb +++ b/lib/active_merchant/billing/gateways/ogone.rb @@ -1,4 +1,5 @@ # coding: utf-8 + require 'rexml/document' module ActiveMerchant #:nodoc: @@ -32,13 +33,13 @@ module Billing #:nodoc: # == Usage # # gateway = ActiveMerchant::Billing::OgoneGateway.new( - # :login => "my_ogone_psp_id", - # :user => "my_ogone_user_id", - # :password => "my_ogone_pswd", - # :signature => "my_ogone_sha_signature", # Only if you configured your Ogone environment so. - # :signature_encryptor => "sha512", # Can be "none" (default), "sha1", "sha256" or "sha512". + # :login => "my_ogone_psp_id", + # :user => "my_ogone_user_id", + # :password => "my_ogone_pswd", + # :signature => "my_ogone_sha_signature", # Only if you configured your Ogone environment so. + # :signature_encryptor => "sha512" # Can be "none" (default), "sha1", "sha256" or "sha512". # # Must be the same as the one configured in your Ogone account. - # ) + # ) # # # set up credit card object as in main ActiveMerchant example # creditcard = ActiveMerchant::Billing::CreditCard.new( @@ -75,7 +76,19 @@ module Billing #:nodoc: # # # When using store, you can also let Ogone generate the alias for you # response = gateway.store(creditcard) - # puts response.billing_id # Retrieve the generated alias + # puts response.billing_id # Retrieve the generated alias + # + # # By default, Ogone tries to authorize 0.01 EUR but you can change this + # # amount using the :store_amount option when creating the gateway object: + # gateway = ActiveMerchant::Billing::OgoneGateway.new( + # :login => "my_ogone_psp_id", + # :user => "my_ogone_user_id", + # :password => "my_ogone_pswd", + # :signature => "my_ogone_sha_signature", + # :signature_encryptor => "sha512", + # :store_amount => 100 # The store method will try to authorize 1 EUR instead of 0.01 EUR + # ) + # response = gateway.store(creditcard) # authorize 1 EUR and void the authorization right away # # == 3-D Secure feature # @@ -107,20 +120,18 @@ class OgoneGateway < Gateway 'KO' => 'N', 'NO' => 'R' } - SUCCESS_MESSAGE = "The transaction was successful" + SUCCESS_MESSAGE = 'The transaction was successful' - THREE_D_SECURE_DISPLAY_WAYS = { :main_window => 'MAINW', # display the identification page in the main window - # (default value). - :pop_up => 'POPUP', # display the identification page in a pop-up window - # and return to the main window at the end. - :pop_ix => 'POPIX' } # display the identification page in a pop-up window - # and remain in the pop-up window. + THREE_D_SECURE_DISPLAY_WAYS = { :main_window => 'MAINW', # display the identification page in the main window (default value). - OGONE_NO_SIGNATURE_DEPRECATION_MESSAGE = "Signature usage will be the default for a future release of ActiveMerchant. You should either begin using it, or update your configuration to explicitly disable it (signature_encryptor: none)" + :pop_up => 'POPUP', # display the identification page in a pop-up window and return to the main window at the end. + :pop_ix => 'POPIX' } # display the identification page in a pop-up window and remain in the pop-up window. + + OGONE_NO_SIGNATURE_DEPRECATION_MESSAGE = 'Signature usage will be the default for a future release of ActiveMerchant. You should either begin using it, or update your configuration to explicitly disable it (signature_encryptor: none)' OGONE_STORE_OPTION_DEPRECATION_MESSAGE = "The 'store' option has been renamed to 'billing_id', and its usage is deprecated." - self.test_url = "https://secure.ogone.com/ncol/test/" - self.live_url = "https://secure.ogone.com/ncol/prod/" + self.test_url = 'https://secure.ogone.com/ncol/test/' + self.live_url = 'https://secure.ogone.com/ncol/prod/' self.supported_countries = ['BE', 'DE', 'FR', 'NL', 'AT', 'CH'] # also supports Airplus and UATP @@ -138,12 +149,13 @@ def initialize(options = {}) # Verify and reserve the specified amount on the account, without actually doing the transaction. def authorize(money, payment_source, options = {}) post = {} + action = payment_source.brand == 'mastercard' ? 'PAU' : 'RES' add_invoice(post, options) add_payment_source(post, payment_source, options) add_address(post, payment_source, options) add_customer_data(post, options) add_money(post, money, options) - commit('RES', post) + commit(action, post) end # Verify and transfer the specified amount. @@ -179,7 +191,7 @@ def void(identification, options = {}) # Credit the specified account by a specific amount. def credit(money, identification_or_credit_card, options = {}) if reference_transaction?(identification_or_credit_card) - deprecated CREDIT_DEPRECATION_MESSAGE + ActiveMerchant.deprecated CREDIT_DEPRECATION_MESSAGE # Referenced credit: refund of a settled transaction refund(money, identification_or_credit_card, options) else # must be a credit card or card reference @@ -192,23 +204,42 @@ def refund(money, reference, options = {}) perform_reference_credit(money, reference, options) end + def verify(credit_card, options={}) + MultiResponse.run(:use_first_response) do |r| + r.process { authorize(100, credit_card, options) } + r.process(:ignore_result) { void(r.authorization, options) } + end + end + # Store a credit card by creating an Ogone Alias def store(payment_source, options = {}) - options.merge!(:alias_operation => 'BYPSP') unless(options.has_key?(:billing_id) || options.has_key?(:store)) - response = authorize(1, payment_source, options) + options[:alias_operation] = 'BYPSP' unless(options.has_key?(:billing_id) || options.has_key?(:store)) + response = authorize(@options[:store_amount] || 1, payment_source, options) void(response.authorization) if response.success? response end + def supports_scrubbing? + true + end + + def scrub(transcript) + transcript. + gsub(%r((Authorization: Basic )\w+), '\1[FILTERED]'). + gsub(%r((&?cardno=)[^&]*)i, '\1[FILTERED]'). + gsub(%r((&?cvc=)[^&]*)i, '\1[FILTERED]'). + gsub(%r((&?pswd=)[^&]*)i, '\1[FILTERED]') + end + private def reference_from(authorization) - authorization.split(";").first + authorization.split(';').first end def reference_transaction?(identifier) return false unless identifier.is_a?(String) - _, action = identifier.split(";") + _, action = identifier.split(';') !action.nil? end @@ -231,17 +262,18 @@ def perform_non_referenced_credit(money, payment_target, options = {}) end def add_payment_source(post, payment_source, options) + add_d3d(post, options) if options[:d3d] + if payment_source.is_a?(String) add_alias(post, payment_source, options[:alias_operation]) add_eci(post, options[:eci] || '9') else if options.has_key?(:store) - deprecated OGONE_STORE_OPTION_DEPRECATION_MESSAGE + ActiveMerchant.deprecated OGONE_STORE_OPTION_DEPRECATION_MESSAGE options[:billing_id] ||= options[:store] end add_alias(post, options[:billing_id], options[:alias_operation]) add_eci(post, options[:eci] || '7') - add_d3d(post, options) if options[:d3d] add_creditcard(post, payment_source) end end @@ -253,11 +285,13 @@ def add_d3d(post, options) THREE_D_SECURE_DISPLAY_WAYS[:main_window] add_pair post, 'WIN3DS', win_3ds - add_pair post, 'HTTP_ACCEPT', options[:http_accept] || "*/*" + add_pair post, 'HTTP_ACCEPT', options[:http_accept] || '*/*' add_pair post, 'HTTP_USER_AGENT', options[:http_user_agent] if options[:http_user_agent] add_pair post, 'ACCEPTURL', options[:accept_url] if options[:accept_url] add_pair post, 'DECLINEURL', options[:decline_url] if options[:decline_url] add_pair post, 'EXCEPTIONURL', options[:exception_url] if options[:exception_url] + add_pair post, 'CANCELURL', options[:cancel_url] if options[:cancel_url] + add_pair post, 'PARAMVAR', options[:paramvar] if options[:paramvar] add_pair post, 'PARAMPLUS', options[:paramplus] if options[:paramplus] add_pair post, 'COMPLUS', options[:complus] if options[:complus] add_pair post, 'LANGUAGE', options[:language] if options[:language] @@ -267,8 +301,8 @@ def add_eci(post, eci) add_pair post, 'ECI', eci.to_s end - def add_alias(post, _alias, alias_operation = nil) - add_pair post, 'ALIAS', _alias + def add_alias(post, alias_name, alias_operation = nil) + add_pair post, 'ALIAS', alias_name add_pair post, 'ALIASOPERATION', alias_operation unless alias_operation.nil? end @@ -298,12 +332,13 @@ def add_address(post, creditcard, options) def add_invoice(post, options) add_pair post, 'orderID', options[:order_id] || generate_unique_id[0...30] add_pair post, 'COM', options[:description] + add_pair post, 'ORIG', options[:origin] if options[:origin] end def add_creditcard(post, creditcard) add_pair post, 'CN', creditcard.name add_pair post, 'CARDNO', creditcard.number - add_pair post, 'ED', "%02d%02s" % [creditcard.month, creditcard.year.to_s[-2..-1]] + add_pair post, 'ED', '%02d%02s' % [creditcard.month, creditcard.year.to_s[-2..-1]] add_pair post, 'CVC', creditcard.verification_value end @@ -313,14 +348,15 @@ def parse(body) # Add HTML_ANSWER element (3-D Secure specific to the response's params) # Note: HTML_ANSWER is not an attribute so we add it "by hand" to the response - if html_answer = REXML::XPath.first(xml_root, "//HTML_ANSWER") - response["HTML_ANSWER"] = html_answer.text + if html_answer = REXML::XPath.first(xml_root, '//HTML_ANSWER') + response['HTML_ANSWER'] = html_answer.text end response end def commit(action, parameters) + add_pair parameters, 'RTIMEOUT', @options[:timeout] if @options[:timeout] add_pair parameters, 'PSPID', @options[:login] add_pair parameters, 'USERID', @options[:user] add_pair parameters, 'PSWD', @options[:password] @@ -328,27 +364,27 @@ def commit(action, parameters) response = parse(ssl_post(url(parameters['PAYID']), post_data(action, parameters))) options = { - :authorization => [response["PAYID"], action].join(";"), + :authorization => [response['PAYID'], action].join(';'), :test => test?, - :avs_result => { :code => AVS_MAPPING[response["AAVCheck"]] }, - :cvv_result => CVV_MAPPING[response["CVCCheck"]] + :avs_result => { :code => AVS_MAPPING[response['AAVCheck']] }, + :cvv_result => CVV_MAPPING[response['CVCCheck']] } OgoneResponse.new(successful?(response), message_from(response), response, options) end def url(payid) - (test? ? test_url : live_url) + (payid ? "maintenancedirect.asp" : "orderdirect.asp") + (test? ? test_url : live_url) + (payid ? 'maintenancedirect.asp' : 'orderdirect.asp') end def successful?(response) - response["NCERROR"] == "0" + response['NCERROR'] == '0' end def message_from(response) if successful?(response) SUCCESS_MESSAGE else - format_error_message(response["NCERRORPLUS"]) + format_error_message(response['NCERRORPLUS']) end end @@ -356,9 +392,9 @@ def format_error_message(message) raw_message = message.to_s.strip case raw_message when /\|/ - raw_message.split("|").join(", ").capitalize + raw_message.split('|').join(', ').capitalize when /\// - raw_message.split("/").first.to_s.capitalize + raw_message.split('/').first.to_s.capitalize else raw_message.to_s.capitalize end @@ -372,27 +408,48 @@ def post_data(action, parameters = {}) def add_signature(parameters) if @options[:signature].blank? - deprecated(OGONE_NO_SIGNATURE_DEPRECATION_MESSAGE) unless(@options[:signature_encryptor] == "none") - return + ActiveMerchant.deprecated(OGONE_NO_SIGNATURE_DEPRECATION_MESSAGE) unless(@options[:signature_encryptor] == 'none') + return end - sha_encryptor = case @options[:signature_encryptor] - when 'sha256' - Digest::SHA256 - when 'sha512' - Digest::SHA512 - else - Digest::SHA1 - end - - string_to_digest = if @options[:signature_encryptor] - parameters.sort { |a, b| a[0].upcase <=> b[0].upcase }.map { |k, v| "#{k.upcase}=#{v}" }.join(@options[:signature]) + add_pair parameters, 'SHASign', calculate_signature(parameters, @options[:signature_encryptor], @options[:signature]) + end + + def calculate_signature(signed_parameters, algorithm, secret) + return legacy_calculate_signature(signed_parameters, secret) unless algorithm + + sha_encryptor = case algorithm + when 'sha256' + Digest::SHA256 + when 'sha512' + Digest::SHA512 + when 'sha1' + Digest::SHA1 else - %w[orderID amount currency CARDNO PSPID Operation ALIAS].map { |key| parameters[key] }.join + raise "Unknown signature algorithm #{algorithm}" end - string_to_digest << @options[:signature] - add_pair parameters, 'SHASign', sha_encryptor.hexdigest(string_to_digest).upcase + filtered_params = signed_parameters.select { |k, v| !v.blank? } + sha_encryptor.hexdigest( + filtered_params.sort_by { |k, v| k.upcase }.map { |k, v| "#{k.upcase}=#{v}#{secret}" }.join('') + ).upcase + end + + def legacy_calculate_signature(parameters, secret) + Digest::SHA1.hexdigest( + ( + %w( + orderID + amount + currency + CARDNO + PSPID + Operation + ALIAS + ).map { |key| parameters[key] } + + [secret] + ).join('') + ).upcase end def add_pair(post, key, value) diff --git a/lib/active_merchant/billing/gateways/omise.rb b/lib/active_merchant/billing/gateways/omise.rb new file mode 100644 index 00000000000..218b099590f --- /dev/null +++ b/lib/active_merchant/billing/gateways/omise.rb @@ -0,0 +1,324 @@ +require 'active_merchant/billing/rails' + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class OmiseGateway < Gateway + API_URL = 'https://api.omise.co/' + VAULT_URL = 'https://vault.omise.co/' + + STANDARD_ERROR_CODE_MAPPING = { + 'invalid_security_code' => STANDARD_ERROR_CODE[:invalid_cvc], + 'failed_capture' => STANDARD_ERROR_CODE[:card_declined] + } + + self.live_url = self.test_url = API_URL + + # Currency supported by Omise + # * Thai Baht with Satang, 50000 (THB500.00) + # * Japanese Yen, 500 (JPY500) + self.default_currency = 'THB' + self.money_format = :cents + + # Country supported by Omise + # * Thailand + self.supported_countries = %w( TH JP ) + + # Credit cards supported by Omise + # * VISA + # * MasterCard + # * JCB + self.supported_cardtypes = [:visa, :master, :jcb] + + # Omise main page + self.homepage_url = 'https://www.omise.co/' + self.display_name = 'Omise' + + # Creates a new OmiseGateway. + # + # Omise requires public_key for token creation. + # And it requires secret_key for other transactions. + # These keys can be found in https://dashboard.omise.co/test/api-keys + # + # ==== Options + # + # * :public_key -- Omise's public key (REQUIRED). + # * :secret_key -- Omise's secret key (REQUIRED). + # * :api_version -- Omise's API Version (OPTIONAL), default version is '2014-07-27' + # See version at page https://dashboard.omise.co/api-version/edit + + def initialize(options={}) + requires!(options, :public_key, :secret_key) + @public_key = options[:public_key] + @secret_key = options[:secret_key] + @api_version = options[:api_version] + super + end + + # Perform a purchase (with auto capture) + # + # ==== Parameters + # + # * money -- The purchasing amount in Thai Baht Satang + # * payment_method -- The CreditCard object + # * options -- An optional parameters, such as token from Omise.js + # + # ==== Options + # * token_id -- token id, use Omise.js library to retrieve a token id + # if this is passed as an option, it will ignore tokenizing via Omisevaultgateway object + # + # === Example + # To create a charge on a card + # + # purchase(money, Creditcard_object) + # + # To create a charge on a token + # + # purchase(money, nil, { :token_id => token_id, ... }) + # + # To create a charge on a customer + # + # purchase(money, nil, { :customer_id => customer_id }) + + def purchase(money, payment_method, options={}) + create_charge(money, payment_method, options) + end + + # Authorize a charge. + # + # ==== Parameters + # + # * money -- The purchasing amount in Thai Baht Satang + # * payment_method -- The CreditCard object + # * options -- An optional parameters, such as token or capture + + def authorize(money, payment_method, options={}) + options[:capture] = 'false' + create_charge(money, payment_method, options) + end + + # Capture an authorized charge. + # + # ==== Parameters + # + # * money -- An amount in Thai Baht Satang + # * charge_id -- The CreditCard object + # * options -- An optional parameters, such as token or capture + + def capture(money, charge_id, options={}) + post = {} + add_amount(post, money, options) + commit(:post, "charges/#{CGI.escape(charge_id)}/capture", post, options) + end + + # Refund a charge. + # + # ==== Parameters + # + # * money -- An amount of money to charge in Satang. + # * charge_id -- The CreditCard object + # * options -- An optional parameters, such as token or capture + + def refund(money, charge_id, options={}) + options[:amount] = money if money + commit(:post, "charges/#{CGI.escape(charge_id)}/refunds", options) + end + + # Store a card details as customer + # + # ==== Parameters + # + # * payment_method -- The CreditCard. + # * options -- Optional Customer information: + # 'email' (A customer email) + # 'description' (A customer description) + + def store(payment_method, options={}) + post, card_params = {}, {} + add_customer_data(post, options) + add_token(card_params, payment_method, options) + commit(:post, 'customers', post.merge(card_params), options) + end + + # Delete a customer and all associated credit cards. + # + # ==== Parameters + # + # * customer_id -- The Customer identifier (REQUIRED). + + def unstore(customer_id, options={}) + commit(:delete, "customers/#{CGI.escape(customer_id)}") + end + + # Enable scrubbing sensitive information + def supports_scrubbing? + true + end + + # Scrub sensitive information out of HTTP transcripts + # + # ==== Parameters + # + # * transcript -- The HTTP transcripts + + def scrub(transcript) + transcript. + gsub(/(Authorization: Basic )\w+/i, '\1[FILTERED]'). + gsub(/(\\"number\\":)\\"\d+\\"/, '\1[FILTERED]'). + gsub(/(\\"security_code\\":)\\"\d+\\"/, '\1[FILTERED]') + end + + private + + def create_charge(money, payment_method, options) + post = {} + add_token(post, payment_method, options) + add_amount(post, money, options) + add_customer(post, options) + post[:capture] = options[:capture] if options[:capture] + commit(:post, 'charges', post, options) + end + + def headers(options={}) + key = options[:key] || @secret_key + { + 'Content-Type' => 'application/json;utf-8', + 'Omise-Version' => @api_version || '2014-07-27', + 'User-Agent' => "ActiveMerchantBindings/#{ActiveMerchant::VERSION} Ruby/#{RUBY_VERSION}", + 'Authorization' => 'Basic ' + Base64.encode64(key.to_s + ':').strip, + 'Accept-Encoding' => 'utf-8' + } + end + + def url_for(endpoint) + (endpoint == 'tokens' ? VAULT_URL : API_URL) + endpoint + end + + def post_data(parameters) + parameters.present? ? parameters.to_json : nil + end + + def https_request(method, endpoint, parameters=nil, options={}) + raw_response = response = nil + begin + raw_response = ssl_request(method, url_for(endpoint), post_data(parameters), headers(options)) + response = parse(raw_response) + rescue ResponseError => e + raw_response = e.response.body + response = parse(raw_response) + rescue JSON::ParserError + response = json_error(raw_response) + end + response + end + + def parse(body) + JSON.parse(body) + end + + def json_error(raw_response) + msg = 'Invalid response received from Omise API. Please contact support@omise.co if you continue to receive this message.' + msg += "The raw response returned by the API was #{raw_response.inspect})" + { message: msg } + end + + def commit(method, endpoint, params=nil, options={}) + response = https_request(method, endpoint, params, options) + Response.new( + successful?(response), + message_from(response), + response, + { + authorization: authorization_from(response), + test: test?, + error_code: successful?(response) ? nil : standard_error_code_mapping(response) + } + ) + end + + def standard_error_code_mapping(response) + STANDARD_ERROR_CODE_MAPPING[error_code_from(response)] || message_to_standard_error_code_from(response) + end + + def error_code_from(response) + error?(response) ? response['code'] : response['failure_code'] + end + + def message_to_standard_error_code_from(response) + message = response['message'] if response['code'] == 'invalid_card' + case message + when /brand not supported/ + STANDARD_ERROR_CODE[:invalid_number] + when /number is invalid/ + STANDARD_ERROR_CODE[:incorrect_number] + when /expiration date cannot be in the past/ + STANDARD_ERROR_CODE[:expired_card] + when /expiration \w+ is invalid/ + STANDARD_ERROR_CODE[:invalid_expiry_date] + else + STANDARD_ERROR_CODE[:processing_error] + end + end + + def message_from(response) + if successful?(response) + 'Success' + else + response['message'] || response['failure_message'] + end + end + + def authorization_from(response) + response['id'] if successful?(response) + end + + def successful?(response) + !error?(response) && response['failure_code'].nil? + end + + def error?(response) + response.key?('object') && (response['object'] == 'error') + end + + def get_token(post, credit_card) + add_creditcard(post, credit_card) if credit_card + commit(:post, 'tokens', post, { key: @public_key }) + end + + def add_token(post, credit_card, options={}) + if options[:token_id].present? + post[:card] = options[:token_id] + else + response = get_token(post, credit_card) + response.authorization ? (post[:card] = response.authorization) : response + end + end + + def add_creditcard(post, payment_method) + card = { + number: payment_method.number, + name: payment_method.name, + security_code: payment_method.verification_value, + expiration_month: payment_method.month, + expiration_year: payment_method.year + } + post[:card] = card + end + + def add_customer(post, options={}) + post[:customer] = options[:customer_id] if options[:customer_id] + end + + def add_customer_data(post, options={}) + post[:description] = options[:description] if options[:description] + post[:email] = options[:email] if options[:email] + end + + def add_amount(post, money, options) + post[:amount] = amount(money) + post[:currency] = (options[:currency] || currency(money)) + post[:description] = options[:description] if options.key?(:description) + end + + end + end +end diff --git a/lib/active_merchant/billing/gateways/openpay.rb b/lib/active_merchant/billing/gateways/openpay.rb new file mode 100644 index 00000000000..166f1f44b43 --- /dev/null +++ b/lib/active_merchant/billing/gateways/openpay.rb @@ -0,0 +1,228 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class OpenpayGateway < Gateway + self.live_url = 'https://api.openpay.mx/v1/' + self.test_url = 'https://sandbox-api.openpay.mx/v1/' + + self.supported_countries = ['MX'] + self.supported_cardtypes = [:visa, :master, :american_express, :carnet] + self.homepage_url = 'http://www.openpay.mx/' + self.display_name = 'Openpay' + self.default_currency = 'MXN' + + # Instantiate a instance of OpenpayGateway by passing through your + # merchant id and private api key. + # + # === To obtain your own credentials + # 1. Visit http://openpay.mx + # 2. Sign up + # 3. Activate your account clicking on the email confirmation + def initialize(options = {}) + requires!(options, :key, :merchant_id) + @api_key = options[:key] + @merchant_id = options[:merchant_id] + super + end + + def purchase(money, creditcard, options = {}) + post = create_post_for_auth_or_purchase(money, creditcard, options) + commit(:post, 'charges', post, options) + end + + def authorize(money, creditcard, options = {}) + post = create_post_for_auth_or_purchase(money, creditcard, options) + post[:capture] = false + commit(:post, 'charges', post, options) + end + + def capture(money, authorization, options = {}) + post = {} + post[:amount] = amount(money) if money + post[:payments] = options[:payments] if options[:payments] + commit(:post, "charges/#{CGI.escape(authorization)}/capture", post, options) + end + + def void(identification, options = {}) + commit(:post, "charges/#{CGI.escape(identification)}/refund", nil, options) + end + + def refund(money, identification, options = {}) + post = {} + post[:description] = options[:description] + post[:amount] = amount(money) + commit(:post, "charges/#{CGI.escape(identification)}/refund", post, options) + end + + def verify(credit_card, options = {}) + MultiResponse.run(:use_first_response) do |r| + r.process { authorize(100, credit_card, options) } + r.process(:ignore_result) { void(r.authorization, options) } + end + end + + def store(creditcard, options = {}) + card_params = {} + add_creditcard(card_params, creditcard, options) + card = card_params[:card] + + if options[:customer].present? + commit(:post, "customers/#{CGI.escape(options[:customer])}/cards", card, options) + else + requires!(options, :email, :name) + post = {} + post[:name] = options[:name] + post[:email] = options[:email] + MultiResponse.run(:first) do |r| + r.process { commit(:post, 'customers', post, options) } + + if(r.success? && !r.params['id'].blank?) + customer_id = r.params['id'] + r.process { commit(:post, "customers/#{customer_id}/cards", card, options) } + end + end + end + end + + def unstore(customer_id, card_id = nil, options = {}) + if card_id.nil? + commit(:delete, "customers/#{CGI.escape(customer_id)}", nil, options) + else + commit(:delete, "customers/#{CGI.escape(customer_id)}/cards/#{CGI.escape(card_id)}", nil, options) + end + end + + def supports_scrubbing + true + end + + def scrub(transcript) + transcript. + gsub(%r((Authorization: Basic )\w+), '\1[FILTERED]'). + gsub(%r((card_number\\?":\\?")[^"\\]*)i, '\1[FILTERED]'). + gsub(%r((cvv2\\?":\\?")\d+[^"\\]*)i, '\1[FILTERED]'). + gsub(%r((cvv2\\?":)null), '\1[BLANK]'). + gsub(%r((cvv2\\?":\\?")\\?"), '\1[BLANK]"'). + gsub(%r((cvv2\\?":\\?")\s+), '\1[BLANK]') + end + + private + + def create_post_for_auth_or_purchase(money, creditcard, options) + post = {} + post[:amount] = amount(money) + post[:method] = 'card' + post[:description] = options[:description] + post[:order_id] = options[:order_id] + post[:device_session_id] = options[:device_session_id] + post[:currency] = (options[:currency] || currency(money)).upcase + post[:use_card_points] = options[:use_card_points] if options[:use_card_points] + post[:payment_plan] = {payments: options[:payments]} if options[:payments] + add_creditcard(post, creditcard, options) + post + end + + def add_creditcard(post, creditcard, options) + if creditcard.kind_of?(String) + post[:source_id] = creditcard + elsif creditcard.respond_to?(:number) + card = { + card_number: creditcard.number, + expiration_month: sprintf('%02d', creditcard.month), + expiration_year: creditcard.year.to_s[-2, 2], + cvv2: creditcard.verification_value, + holder_name: creditcard.name + } + add_address(card, options) + add_customer_data(post, creditcard, options) + post[:card] = card + end + end + + def add_customer_data(post, creditcard, options) + if options[:email] + customer = { + name: creditcard.name || options[:name], + email: options[:email] + } + post[:customer] = customer + end + post + end + + def add_address(card, options) + return unless card.kind_of?(Hash) + if address = (options[:billing_address] || options[:address]) + card[:address] = { + line1: address[:address1], + line2: address[:address2], + line3: address[:company], + city: address[:city], + postal_code: address[:zip], + state: address[:state], + country_code: address[:country] + } + end + end + + def headers(options = {}) + { + 'Content-Type' => 'application/json', + 'Authorization' => 'Basic ' + Base64.strict_encode64(@api_key.to_s + ':').strip, + 'User-Agent' => "Openpay/v1 ActiveMerchantBindings/#{ActiveMerchant::VERSION}", + 'X-Openpay-Client-User-Agent' => user_agent + } + end + + def parse(body) + return {} unless body + JSON.parse(body) + end + + def commit(method, resource, parameters, options = {}) + response = http_request(method, resource, parameters, options) + success = !error?(response) + + Response.new(success, + (success ? response['error_code'] : response['description']), + response, + :test => test?, + :authorization => response['id'] + ) + end + + def http_request(method, resource, parameters={}, options={}) + url = (test? ? self.test_url : self.live_url) + @merchant_id + '/' + resource + raw_response = nil + begin + raw_response = ssl_request(method, url, (parameters ? parameters.to_json : nil), headers(options)) + parse(raw_response) + rescue ResponseError => e + raw_response = e.response.body + response_error(raw_response) + rescue JSON::ParserError + json_error(raw_response) + end + end + + def error?(response) + response['error_code'] && !response['error_code'].blank? + end + + def response_error(raw_response) + parse(raw_response) + rescue JSON::ParserError + json_error(raw_response) + end + + def json_error(raw_response) + msg = 'Invalid response received from the Openpay API. Please contact soporte@openpay.mx if you continue to receive this message.' + msg += " (The raw response returned by the API was #{raw_response.inspect})" + { + 'category' => 'request', + 'error_code' => '9999', + 'description' => msg + } + end + end + end +end diff --git a/lib/active_merchant/billing/gateways/opp.rb b/lib/active_merchant/billing/gateways/opp.rb new file mode 100644 index 00000000000..cdd4900e729 --- /dev/null +++ b/lib/active_merchant/billing/gateways/opp.rb @@ -0,0 +1,388 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class OppGateway < Gateway + # = Open Payment Platform + # + # The Open Payment Platform includes a powerful omni-channel transaction processing API, + # enabling you to quickly and flexibly build new applications and services on the platform. + # + # This plugin enables connectivity to the Open Payment Platform for activemerchant. + # + # For any questions or comments please contact support@payon.com + # + # == Usage + # + # gateway = ActiveMerchant::Billing::OppGateway.new( + # user_id: 'merchant user id', + # password: 'password', + # entity_id: 'entity id', + # ) + # + # # set up credit card object as in main ActiveMerchant example + # creditcard = ActiveMerchant::Billing::CreditCard.new( + # :type => 'visa', + # :number => '4242424242424242', + # :month => 8, + # :year => 2009, + # :first_name => 'Bob', + # :last_name => 'Bobsen' + # :verification_value: '123') + # + # # Request: complete example, including address, billing address, shipping address + # complete_request_options = { + # order_id: "your merchant/shop order id", # alternative is to set merchantInvoiceId + # merchant_transaction_id: "your merchant/shop transaction id", + # address: address, + # description: 'Store Purchase - Books', + # risk_workflow: false, + # test_mode: 'EXTERNAL' # or 'INTERNAL', valid only for test system + # create_registration: false, # payment details will be stored on the server an latter can be referenced + # + # billing_address: { + # address1: '123 Test Street', + # city: 'Test', + # state: 'TE', + # zip: 'AB12CD', + # country: 'GB', + # }, + # shipping_address: { + # name: 'Muton DeMicelis', + # address1: 'My Street On Upiter, Apt 3.14/2.78', + # city: 'Munich', + # state: 'Bov', + # zip: '81675', + # country: 'DE', + # }, + # customer: { + # merchant_customer_id: "your merchant/customer id", + # givenname: 'Jane', + # surname: 'Jones', + # birth_date: '1965-05-01', + # phone: '(?!?)555-5555', + # mobile: '(?!?)234-23423', + # email: 'jane@jones.com', + # company_name: 'JJ Ltd.', + # identification_doctype: 'PASSPORT', + # identification_docid: 'FakeID2342431234123', + # ip: 101.102.103.104, + # }, + # } + # + # # Request: minimal example + # minimal_request_options = { + # order_id: "your merchant/shop order id", # alternative is to set merchantInvoiceId + # description: 'Store Purchase - Books', + # } + # + # options = + # # run request + # response = gateway.purchase(754, creditcard, options) # charge 7,54 EUR + # + # response.success? # Check whether the transaction was successful + # response.error_code # Retrieve the error message - it's mapped to Gateway::STANDARD_ERROR_CODE + # response.message # Retrieve the message returned by opp + # response.authorization # Retrieve the unique transaction ID returned by opp + # response.params['result']['code'] # Retrieve original return code returned by opp server + # + # == Errors + # If transaction is not successful, response.error_code contains mapped to Gateway::STANDARD_ERROR_CODE error message. + # Complete list of opp error codes can be viewed on https://docs.oppwa.com/ + # Because this list is much bigger than Gateway::STANDARD_ERROR_CODE, only fraction is mapped to Gateway::STANDARD_ERROR_CODE. + # All other codes are mapped as Gateway::STANDARD_ERROR_CODE[:processing_error], so if this is the case, + # you may check the original result code from OPP that can be found in response.params['result']['code'] + # + # == Special features + # For purchase method risk check can be forced when options[:risk_workflow] = true + # This will split (on OPP server side) the transaction into two separate transactions: authorize and capture, + # but capture will be executed only if risk checks are successful. + # + # For testing you may use the test account details listed fixtures.yml under opp. It is important to note that there are two test modes available: + # options[:test_mode]='EXTERNAL' causes test transactions to be forwarded to the processor's test system for 'end-to-end' testing + # options[:test_mode]='INTERNAL' causes transactions to be sent to opp simulators, which is useful when switching to the live endpoint for connectivity testing. + # If no test_mode parameter is sent, test_mode=INTERNAL is the default behaviour. + # + # Billing Address, Shipping Address, Custom Parameters are supported as described under https://docs.oppwa.com/parameters + # See complete example above for details. + # + # == Tokenization + # When create_registration is set to true, the payment details will be stored and a token will be returned in registrationId response field, + # which can subsequently be used to reference the stored payment. + + self.test_url = 'https://test.oppwa.com/v1/payments' + self.live_url = 'https://oppwa.com/v1/payments' + + self.supported_countries = %w(AD AI AG AR AU AT BS BB BE BZ BM BR BN BG CA HR CY CZ DK DM EE FI FR DE GR GD GY HK HU IS IN IL IT JP LV LI LT LU MY MT MX MC MS NL PA PL PT KN LC MF VC SM SG SK SI ZA ES SR SE CH TR GB US UY) + self.default_currency = 'EUR' + self.supported_cardtypes = [:visa, :master, :american_express, :diners_club, :discover, :jcb, :maestro, :dankort] + + self.homepage_url = 'https://docs.oppwa.com' + self.display_name = 'Open Payment Platform' + + def initialize(options={}) + requires!(options, :user_id, :password, :entity_id) + super + end + + def purchase(money, payment, options={}) + # debit + if payment.is_a?(String) + options[:registrationId] = payment + end + execute_dbpa(options[:risk_workflow] ? 'PA.CP': 'DB', + money, payment, options) + end + + def authorize(money, payment, options={}) + # preauthorization PA + execute_dbpa('PA', money, payment, options) + end + + def capture(money, authorization, options={}) + # capture CP + execute_referencing('CP', money, authorization, options) + end + + def refund(money, authorization, options={}) + # refund RF + execute_referencing('RF', money, authorization, options) + end + + def void(authorization, options={}) + # reversal RV + execute_referencing('RV', nil, authorization, options) + end + + def verify(credit_card, options={}) + MultiResponse.run(:use_first_response) do |r| + r.process { authorize(100, credit_card, options) } + r.process(:ignore_result) { void(r.authorization, options) } + end + end + + def store(credit_card, options = {}) + execute_store(credit_card, options.merge(store: true)) + end + + def supports_scrubbing? + true + end + + def scrub(transcript) + transcript. + gsub(%r((authentication\.password=)\w+), '\1[FILTERED]'). + gsub(%r((card\.number=)\d+), '\1[FILTERED]'). + gsub(%r((card\.cvv=)\d+), '\1[FILTERED]') + end + + private + + def execute_store(payment, options) + post = {} + add_payment_method(post, payment, options) + add_address(post, options) + add_options(post, options) + add_3d_secure(post, options) + commit(post, nil, options) + end + + def execute_dbpa(txtype, money, payment, options) + post = {} + post[:paymentType] = txtype + add_invoice(post, money, options) + add_payment_method(post, payment, options) + add_address(post, options) + add_customer_data(post, payment, options) + add_options(post, options) + add_3d_secure(post, options) + commit(post, nil, options) + end + + def execute_referencing(txtype, money, authorization, options) + post = {} + post[:paymentType] = txtype + add_invoice(post, money, options) + commit(post, authorization, options) + end + + def add_authentication(post) + post[:authentication] = { entityId: @options[:entity_id], password: @options[:password], userId: @options[:user_id]} + end + + def add_customer_data(post, payment, options) + if options[:customer] + post[:customer] = { + merchantCustomerId: options[:customer][:merchant_customer_id], + givenName: options[:customer][:givenname] || payment.first_name, + surname: options[:customer][:surname] || payment.last_name, + birthDate: options[:customer][:birth_date], + phone: options[:customer][:phone], + mobile: options[:customer][:mobile], + email: options[:customer][:email] || options[:email], + companyName: options[:customer][:company_name], + identificationDocType: options[:customer][:identification_doctype], + identificationDocId: options[:customer][:identification_docid], + ip: options[:customer][:ip] || options[:ip] + } + end + end + + def add_address(post, options) + if billing_address = options[:billing_address] || options[:address] + address(post, billing_address, 'billing') + end + if shipping_address = options[:shipping_address] + address(post, shipping_address, 'shipping') + if shipping_address[:name] + firstname, lastname = shipping_address[:name].split(' ') + post[:shipping] = { givenName: firstname, surname: lastname } + end + end + end + + def address(post, address, prefix) + post[prefix] = { + street1: address[:address1], + street2: address[:address2], + city: address[:city], + state: address[:state], + postcode: address[:zip], + country: address[:country], + } + end + + def add_invoice(post, money, options) + post[:amount] = amount(money) + post[:currency] = options[:currency] || currency(money) unless post[:paymentType] == 'RV' + post[:descriptor] = options[:description] || options[:descriptor] + post[:merchantInvoiceId] = options[:merchantInvoiceId] || options[:order_id] + post[:merchantTransactionId] = options[:merchant_transaction_id] || generate_unique_id + end + + def add_payment_method(post, payment, options) + return if payment.is_a?(String) + if options[:registrationId] + post[:card] = { + cvv: payment.verification_value, + } + else + post[:paymentBrand] = payment.brand.upcase + post[:card] = { + holder: payment.name, + number: payment.number, + expiryMonth: '%02d' % payment.month, + expiryYear: payment.year, + cvv: payment.verification_value, + } + end + end + + def add_3d_secure(post, options) + return unless options[:eci] && options[:cavv] && options[:xid] + + post[:threeDSecure] = { + eci: options[:eci], + verificationId: options[:cavv], + xid: options[:xid] + } + end + + def add_options(post, options) + post[:createRegistration] = options[:create_registration] if options[:create_registration] && !options[:registrationId] + post[:testMode] = options[:test_mode] if test? && options[:test_mode] + options.each { |key, value| post[key] = value if key.to_s.match('customParameters\[[a-zA-Z0-9\._]{3,64}\]') } + post['customParameters[SHOPPER_pluginId]'] = 'activemerchant' + post['customParameters[custom_disable3DSecure]'] = options[:disable_3d_secure] if options[:disable_3d_secure] + end + + def build_url(url, authorization, options) + if options[:store] + url.gsub(/payments/, 'registrations') + elsif options[:registrationId] + "#{url.gsub(/payments/, 'registrations')}/#{options[:registrationId]}/payments" + elsif authorization + "#{url}/#{authorization}" + else + url + end + end + + def commit(post, authorization, options) + url = build_url(test? ? test_url : live_url, authorization, options) + add_authentication(post) + post = flatten_hash(post) + + response = begin + parse( + ssl_post( + url, + post.collect { |key, value| "#{key}=#{CGI.escape(value.to_s)}" }.join('&'), + 'Content-Type' => 'application/x-www-form-urlencoded;charset=UTF-8' + ) + ) + rescue ResponseError => e + parse(e.response.body) + end + + success = success_from(response) + + Response.new( + success, + message_from(response), + response, + authorization: authorization_from(response), + test: test?, + error_code: success ? nil : error_code_from(response) + ) + end + + def parse(body) + JSON.parse(body) + rescue JSON::ParserError + json_error(body) + end + + def json_error(body) + message = "Invalid response received #{body.inspect}" + { 'result' => {'description' => message, 'code' => 'unknown' } } + end + + def success_from(response) + return false unless response['result'] + + success_regex = /^(000\.000\.|000\.100\.1|000\.[36])/ + + if success_regex =~ response['result']['code'] + true + else + false + end + end + + def message_from(response) + return 'Failed' unless response['result'] + + response['result']['description'] + end + + def authorization_from(response) + response['id'] + end + + def error_code_from(response) + response['result']['code'] + end + + def flatten_hash(hash) + hash.each_with_object({}) do |(k, v), h| + if v.is_a? Hash + flatten_hash(v).map do |h_k, h_v| + h["#{k}.#{h_k}".to_sym] = h_v + end + else + h[k] = v + end + end + end + end + end +end diff --git a/lib/active_merchant/billing/gateways/optimal_payment.rb b/lib/active_merchant/billing/gateways/optimal_payment.rb index 604eefa18f5..30f7bf57e02 100644 --- a/lib/active_merchant/billing/gateways/optimal_payment.rb +++ b/lib/active_merchant/billing/gateways/optimal_payment.rb @@ -5,10 +5,12 @@ class OptimalPaymentGateway < Gateway self.live_url = 'https://webservices.optimalpayments.com/creditcardWS/CreditCardServlet/v1' # The countries the gateway supports merchants from as 2 digit ISO country codes - self.supported_countries = ['CA', 'US', 'GB'] + self.supported_countries = ['CA', 'US', 'GB', 'AU', 'AT', 'BE', 'BG', 'HR', 'CY', 'CZ', 'DK', + 'EE', 'FI', 'DE', 'GR', 'HU', 'IE', 'IT', 'LV', 'LT', 'LU', 'MT', + 'NL', 'NO', 'PL', 'PT', 'RO', 'SK', 'SI', 'ES', 'SE', 'CH'] # The card types supported by the payment gateway - self.supported_cardtypes = [:visa, :master, :american_express, :discover, :diners_club, :solo] # :switch? + self.supported_cardtypes = [:visa, :master, :american_express, :discover, :diners_club] # The homepage URL of the gateway self.homepage_url = 'http://www.optimalpayments.com/' @@ -17,7 +19,17 @@ class OptimalPaymentGateway < Gateway self.display_name = 'Optimal Payments' def initialize(options = {}) - #requires!(options, :login, :password) + if(options[:login]) + ActiveMerchant.deprecated("The 'login' option is deprecated in favor of 'store_id' and will be removed in a future version.") + options[:store_id] = options[:login] + end + + if(options[:account]) + ActiveMerchant.deprecated("The 'account' option is deprecated in favor of 'account_number' and will be removed in a future version.") + options[:account_number] = options[:account] + end + + requires!(options, :account_number, :store_id, :password) super end @@ -48,15 +60,31 @@ def capture(money, authorization, options = {}) commit('ccSettlement', money, options) end + def verify(credit_card, options = {}) + parse_card_or_auth(credit_card, options) + commit('ccVerification', 0, options) + end + + def supports_scrubbing? + true + end + + def scrub(transcript) + transcript. + gsub(%r((%3CstorePwd%3E).*(%3C(%2F|/)storePwd%3E))i, '\1[FILTERED]\2'). + gsub(%r((%3CcardNum%3E)\d*(%3C(%2F|/)cardNum%3E))i, '\1[FILTERED]\2'). + gsub(%r((%3Ccvd%3E)\d*(%3C(%2F|/)cvd%3E))i, '\1[FILTERED]\2') + end + private def parse_card_or_auth(card_or_auth, options) if card_or_auth.respond_to?(:number) @credit_card = card_or_auth - @stored_data = "" + @stored_data = '' else options[:confirmationNumber] = card_or_auth - @stored_data = "StoredData" + @stored_data = 'StoredData' end end @@ -76,16 +104,16 @@ def commit(action, money, post) cc_stored_data_request(money, post) when 'ccAuthorizeReversal' cc_auth_reversal_request(post) - #when 'ccCancelSettle', 'ccCancelCredit', 'ccCancelPayment' + # when 'ccCancelSettle', 'ccCancelCredit', 'ccCancelPayment' # cc_cancel_request(money, post) - #when 'ccPayment' + # when 'ccPayment' # cc_payment_request(money, post) - #when 'ccAuthenticate' + # when 'ccAuthenticate' # cc_authenticate_request(money, post) else raise 'Unknown Action' end - txnRequest = URI.encode(xml) + txnRequest = escape_uri(xml) response = parse(ssl_post(test? ? self.test_url : self.live_url, "txnMode=#{action}&txnRequest=#{txnRequest}")) Response.new(successful?(response), message_from(response), hash_from_xml(response), @@ -96,6 +124,11 @@ def commit(action, money, post) ) end + # The upstream is picky and so we can't use CGI.escape like we want to + def escape_uri(uri) + URI::DEFAULT_PARSER.escape(uri) + end + def successful?(response) REXML::XPath.first(response, '//decision').text == 'ACCEPTED' rescue false end @@ -150,23 +183,24 @@ def xml_document(root_tag) def get_text_from_document(document, node) node = REXML::XPath.first(document, node) - node && node.text + node&.text end def cc_auth_request(money, opts) xml_document('ccAuthRequestV1') do |xml| - build_merchant_account(xml, @options) + build_merchant_account(xml) xml.merchantRefNum opts[:order_id] xml.amount(money/100.0) build_card(xml, opts) build_billing_details(xml, opts) build_shipping_details(xml, opts) + xml.customerIP opts[:ip] if opts[:ip] end end def cc_auth_reversal_request(opts) xml_document('ccAuthReversalRequestV1') do |xml| - build_merchant_account(xml, @options) + build_merchant_account(xml) xml.confirmationNumber opts[:confirmationNumber] xml.merchantRefNum opts[:order_id] end @@ -174,7 +208,7 @@ def cc_auth_reversal_request(opts) def cc_post_auth_request(money, opts) xml_document('ccPostAuthRequestV1') do |xml| - build_merchant_account(xml, @options) + build_merchant_account(xml) xml.confirmationNumber opts[:confirmationNumber] xml.merchantRefNum opts[:order_id] xml.amount(money/100.0) @@ -183,7 +217,7 @@ def cc_post_auth_request(money, opts) def cc_stored_data_request(money, opts) xml_document('ccStoredDataRequestV1') do |xml| - build_merchant_account(xml, @options) + build_merchant_account(xml) xml.merchantRefNum opts[:order_id] xml.confirmationNumber opts[:confirmationNumber] xml.amount(money/100.0) @@ -194,14 +228,14 @@ def cc_stored_data_request(money, opts) # # def cc_cancel_request(opts) # xml_document('ccCancelRequestV1') do |xml| - # build_merchant_account(xml, @options) + # build_merchant_account(xml) # xml.confirmationNumber opts[:confirmationNumber] # end # end # # def cc_payment_request(money, opts) # xml_document('ccPaymentRequestV1') do |xml| - # build_merchant_account(xml, @options) + # build_merchant_account(xml) # xml.merchantRefNum opts[:order_id] # xml.amount(money/100.0) # build_card(xml, opts) @@ -211,7 +245,7 @@ def cc_stored_data_request(money, opts) # # def cc_authenticate_request(opts) # xml_document('ccAuthenticateRequestV1') do |xml| - # build_merchant_account(xml, @options) + # build_merchant_account(xml) # xml.confirmationNumber opts[:confirmationNumber] # xml.paymentResponse 'myPaymentResponse' # end @@ -224,27 +258,29 @@ def schema } end - def build_merchant_account(xml, opts) + def build_merchant_account(xml) xml.tag! 'merchantAccount' do - xml.tag! 'accountNum' , opts[:account] - xml.tag! 'storeID' , opts[:login] - xml.tag! 'storePwd' , opts[:password] + xml.tag! 'accountNum', @options[:account_number] + xml.tag! 'storeID', @options[:store_id] + xml.tag! 'storePwd', @options[:password] end end def build_card(xml, opts) xml.tag! 'card' do - xml.tag! 'cardNum' , @credit_card.number + xml.tag! 'cardNum', @credit_card.number xml.tag! 'cardExpiry' do - xml.tag! 'month' , @credit_card.month - xml.tag! 'year' , @credit_card.year + xml.tag! 'month', @credit_card.month + xml.tag! 'year', @credit_card.year end if brand = card_type(@credit_card.brand) - xml.tag! 'cardType' , brand + xml.tag! 'cardType', brand end - if @credit_card.verification_value - xml.tag! 'cvdIndicator' , '1' # Value Provided - xml.tag! 'cvd' , @credit_card.verification_value + if @credit_card.verification_value? + xml.tag! 'cvdIndicator', '1' # Value Provided + xml.tag! 'cvd', @credit_card.verification_value + else + xml.tag! 'cvdIndicator', '0' end end end @@ -252,32 +288,34 @@ def build_card(xml, opts) def build_billing_details(xml, opts) xml.tag! 'billingDetails' do xml.tag! 'cardPayMethod', 'WEB' - build_address(xml, opts[:billing_address], opts[:email]) + build_address(xml, opts[:billing_address]) if opts[:billing_address] + xml.tag! 'email', opts[:email] if opts[:email] end end def build_shipping_details(xml, opts) xml.tag! 'shippingDetails' do - build_address(xml, opts[:shipping_address], opts[:email]) + build_address(xml, opts[:shipping_address]) + xml.tag! 'email', opts[:email] if opts[:email] end if opts[:shipping_address].present? end - def build_address(xml, addr, email=nil) + def build_address(xml, addr) if addr[:name] - xml.tag! 'firstName', CGI.escape(addr[:name].split(' ').first) # TODO: parse properly - xml.tag! 'lastName' , CGI.escape(addr[:name].split(' ').last ) + first_name, last_name = split_names(addr[:name]) + xml.tag! 'firstName', first_name + xml.tag! 'lastName', last_name end - xml.tag! 'street' , CGI.escape(addr[:address1]) if addr[:address1].present? - xml.tag! 'street2', CGI.escape(addr[:address2]) if addr[:address2].present? - xml.tag! 'city' , CGI.escape(addr[:city] ) if addr[:city].present? + xml.tag! 'street', addr[:address1] if addr[:address1].present? + xml.tag! 'street2', addr[:address2] if addr[:address2].present? + xml.tag! 'city', addr[:city] if addr[:city].present? if addr[:state].present? state_tag = %w(US CA).include?(addr[:country]) ? 'state' : 'region' - xml.tag! state_tag, CGI.escape(addr[:state]) + xml.tag! state_tag, addr[:state] end - xml.tag! 'country', CGI.escape(addr[:country] ) if addr[:country].present? - xml.tag! 'zip' , CGI.escape(addr[:zip] ) if addr[:zip].present? - xml.tag! 'phone' , CGI.escape(addr[:phone] ) if addr[:phone].present? - xml.tag! 'email', CGI.escape(email) if email + xml.tag! 'country', addr[:country] if addr[:country].present? + xml.tag! 'zip', addr[:zip] if addr[:zip].present? + xml.tag! 'phone', addr[:phone] if addr[:phone].present? end def card_type(key) @@ -286,12 +324,9 @@ def card_type(key) 'american_express'=> 'AM', 'discover' => 'DI', 'diners_club' => 'DC', - #'switch' => '', - 'solo' => 'SO' }[key] end end end end - diff --git a/lib/active_merchant/billing/gateways/orbital.rb b/lib/active_merchant/billing/gateways/orbital.rb index c26ebbac4a9..aad16855ea2 100644 --- a/lib/active_merchant/billing/gateways/orbital.rb +++ b/lib/active_merchant/billing/gateways/orbital.rb @@ -1,6 +1,5 @@ -require File.dirname(__FILE__) + '/orbital/orbital_soft_descriptors' -require File.dirname(__FILE__) + '/orbital/avs_result' -require "rexml/document" +require 'active_merchant/billing/gateways/orbital/orbital_soft_descriptors' +require 'rexml/document' module ActiveMerchant #:nodoc: module Billing #:nodoc: @@ -29,29 +28,51 @@ module Billing #:nodoc: # Company will automatically be affiliated. class OrbitalGateway < Gateway - API_VERSION = "5.6" + include Empty + + API_VERSION = '7.7' POST_HEADERS = { - "MIME-Version" => "1.1", - "Content-Type" => "application/PTI56", - "Content-transfer-encoding" => "text", - "Request-number" => '1', - "Document-type" => "Request", - "Interface-Version" => "Ruby|ActiveMerchant|Proprietary Gateway" + 'MIME-Version' => '1.1', + 'Content-Type' => "application/PTI#{API_VERSION.gsub(/\./, '')}", + 'Content-transfer-encoding' => 'text', + 'Request-number' => '1', + 'Document-type' => 'Request', + 'Interface-Version' => 'Ruby|ActiveMerchant|Proprietary Gateway' } - SUCCESS, APPROVED = '0', '00' + SUCCESS = '0' + + APPROVED = [ + '00', # Approved + '08', # Approved authorization, honor with ID + '11', # Approved authorization, VIP approval + '24', # Validated + '26', # Pre-noted + '27', # No reason to decline + '28', # Received and stored + '29', # Provided authorization + '31', # Request received + '32', # BIN alert + '34', # Approved for partial + '91', # Approved low fraud + '92', # Approved medium fraud + '93', # Approved high fraud + '94', # Approved fraud service unavailable + 'E7', # Stored + 'PA' # Partial approval + ] class_attribute :secondary_test_url, :secondary_live_url - self.test_url = "https://orbitalvar1.paymentech.net/authorize" - self.secondary_test_url = "https://orbitalvar2.paymentech.net/authorize" + self.test_url = 'https://orbitalvar1.chasepaymentech.com/authorize' + self.secondary_test_url = 'https://orbitalvar2.chasepaymentech.com/authorize' - self.live_url = "https://orbital1.paymentech.net/authorize" - self.secondary_live_url = "https://orbital2.paymentech.net/authorize" + self.live_url = 'https://orbital1.chasepaymentech.com/authorize' + self.secondary_live_url = 'https://orbital2.chasepaymentech.com/authorize' - self.supported_countries = ["US", "CA"] - self.default_currency = "CAD" + self.supported_countries = ['US', 'CA'] + self.default_currency = 'CAD' self.supported_cardtypes = [:visa, :master, :american_express, :discover, :diners_club, :jcb] self.display_name = 'Orbital Paymentech' @@ -62,22 +83,45 @@ class OrbitalGateway < Gateway AVS_SUPPORTED_COUNTRIES = ['US', 'CA', 'UK', 'GB'] CURRENCY_CODES = { - "AUD" => '036', - "CAD" => '124', - "CZK" => '203', - "DKK" => '208', - "HKD" => '344', - "ICK" => '352', - "JPY" => '392', - "MXN" => '484', - "NZD" => '554', - "NOK" => '578', - "SGD" => '702', - "SEK" => '752', - "CHF" => '756', - "GBP" => '826', - "USD" => '840', - "EUR" => '978' + 'AUD' => '036', + 'BRL' => '986', + 'CAD' => '124', + 'CLP' => '152', + 'CZK' => '203', + 'DKK' => '208', + 'HKD' => '344', + 'ICK' => '352', + 'JPY' => '392', + 'MXN' => '484', + 'NZD' => '554', + 'NOK' => '578', + 'SGD' => '702', + 'SEK' => '752', + 'CHF' => '756', + 'GBP' => '826', + 'USD' => '840', + 'EUR' => '978' + } + + CURRENCY_EXPONENTS = { + 'AUD' => '2', + 'BRL' => '2', + 'CAD' => '2', + 'CLP' => '2', + 'CZK' => '2', + 'DKK' => '2', + 'HKD' => '2', + 'ICK' => '2', + 'JPY' => '0', + 'MXN' => '2', + 'NZD' => '2', + 'NOK' => '2', + 'SGD' => '2', + 'SEK' => '2', + 'CHF' => '2', + 'GBP' => '2', + 'USD' => '2', + 'EUR' => '2' } # INDUSTRY TYPES @@ -137,32 +181,42 @@ class OrbitalGateway < Gateway USE_ORDER_ID = 'O' # Use OrderID field USE_COMMENTS = 'D' # Use Comments field + SENSITIVE_FIELDS = [:account_num, :cc_account_num] + def initialize(options = {}) requires!(options, :merchant_id) requires!(options, :login, :password) unless options[:ip_authentication] super + @options[:merchant_id] = @options[:merchant_id].to_s end # A – Authorization request def authorize(money, creditcard, options = {}) - order = build_new_order_xml(AUTH_ONLY, money, options) do |xml| - add_creditcard(xml, creditcard, options[:currency]) unless creditcard.nil? && options[:profile_txn] + order = build_new_order_xml(AUTH_ONLY, money, creditcard, options) do |xml| + add_creditcard(xml, creditcard, options[:currency]) add_address(xml, creditcard, options) if @options[:customer_profiles] - add_customer_data(xml, options) + add_customer_data(xml, creditcard, options) add_managed_billing(xml, options) end end commit(order, :authorize, options[:trace_number]) end + def verify(creditcard, options = {}) + MultiResponse.run(:use_first_response) do |r| + r.process { authorize(100, creditcard, options) } + r.process(:ignore_result) { void(r.authorization) } + end + end + # AC – Authorization and Capture def purchase(money, creditcard, options = {}) - order = build_new_order_xml(AUTH_AND_CAPTURE, money, options) do |xml| - add_creditcard(xml, creditcard, options[:currency]) unless creditcard.nil? && options[:profile_txn] + order = build_new_order_xml(AUTH_AND_CAPTURE, money, creditcard, options) do |xml| + add_creditcard(xml, creditcard, options[:currency]) add_address(xml, creditcard, options) if @options[:customer_profiles] - add_customer_data(xml, options) + add_customer_data(xml, creditcard, options) add_managed_billing(xml, options) end end @@ -176,7 +230,7 @@ def capture(money, authorization, options = {}) # R – Refund request def refund(money, authorization, options = {}) - order = build_new_order_xml(REFUND, money, options.merge(:authorization => authorization)) do |xml| + order = build_new_order_xml(REFUND, money, nil, options.merge(:authorization => authorization)) do |xml| add_refund(xml, options[:currency]) xml.tag! :CustomerRefNum, options[:customer_ref_num] if @options[:customer_profiles] && options[:profile_txn] end @@ -184,13 +238,13 @@ def refund(money, authorization, options = {}) end def credit(money, authorization, options= {}) - deprecated CREDIT_DEPRECATION_MESSAGE + ActiveMerchant.deprecated CREDIT_DEPRECATION_MESSAGE refund(money, authorization, options) end def void(authorization, options = {}, deprecated = {}) if(!options.kind_of?(Hash)) - deprecated("Calling the void method with an amount parameter is deprecated and will be removed in a future version.") + ActiveMerchant.deprecated('Calling the void method with an amount parameter is deprecated and will be removed in a future version.') return void(options, deprecated.merge(:amount => authorization)) end @@ -198,9 +252,8 @@ def void(authorization, options = {}, deprecated = {}) commit(order, :void, options[:trace_number]) end - # ==== Customer Profiles - # :customer_ref_num should be set unless your happy with Orbital providing one + # :customer_ref_num should be set unless you're happy with Orbital providing one # # :customer_profile_order_override_ind can be set to map # the CustomerRefNum to OrderID or Comments. Defaults to 'NO' - no mapping @@ -221,13 +274,13 @@ def void(authorization, options = {}, deprecated = {}) # 'MS' - Manual Suspend def add_customer_profile(creditcard, options = {}) - options.merge!(:customer_profile_action => CREATE) + options[:customer_profile_action] = CREATE order = build_customer_request_xml(creditcard, options) commit(order, :add_customer_profile) end def update_customer_profile(creditcard, options = {}) - options.merge!(:customer_profile_action => UPDATE) + options[:customer_profile_action] = UPDATE order = build_customer_request_xml(creditcard, options) commit(order, :update_customer_profile) end @@ -244,22 +297,39 @@ def delete_customer_profile(customer_ref_num) commit(order, :delete_customer_profile) end + def supports_scrubbing? + true + end + + def scrub(transcript) + transcript. + gsub(%r(().+()), '\1[FILTERED]\2'). + gsub(%r(().+()), '\1[FILTERED]\2'). + gsub(%r(().+()), '\1[FILTERED]\2'). + gsub(%r(().+()), '\1[FILTERED]\2'). + gsub(%r(().+()), '\1[FILTERED]\2'). + gsub(%r(().+()), '\1[FILTERED]\2'). + gsub(%r(().+()), '\1[FILTERED]\2') + end + private def authorization_string(*args) - args.compact.join(";") + args.compact.join(';') end def split_authorization(authorization) authorization.split(';') end - def add_customer_data(xml, options) + def add_customer_data(xml, creditcard, options) if options[:profile_txn] xml.tag! :CustomerRefNum, options[:customer_ref_num] else if options[:customer_ref_num] - xml.tag! :CustomerProfileFromOrderInd, USE_CUSTOMER_REF_NUM + if creditcard + xml.tag! :CustomerProfileFromOrderInd, USE_CUSTOMER_REF_NUM + end xml.tag! :CustomerRefNum, options[:customer_ref_num] else xml.tag! :CustomerProfileFromOrderInd, AUTO_GENERATE @@ -277,21 +347,58 @@ def add_soft_descriptors(xml, soft_desc) xml.tag! :SDMerchantEmail, soft_desc.merchant_email if soft_desc.merchant_email end + def add_soft_descriptors_from_hash(xml, soft_desc) + xml.tag! :SDMerchantName, soft_desc[:merchant_name] || nil + xml.tag! :SDProductDescription, soft_desc[:product_description] || nil + xml.tag! :SDMerchantCity, soft_desc[:merchant_city] || nil + xml.tag! :SDMerchantPhone, soft_desc[:merchant_phone] || nil + xml.tag! :SDMerchantURL, soft_desc[:merchant_url] || nil + xml.tag! :SDMerchantEmail, soft_desc[:merchant_email] || nil + end + + def add_level_2_tax(xml, options={}) + if (level_2 = options[:level_2_data]) + xml.tag! :TaxInd, level_2[:tax_indicator] if [TAX_NOT_PROVIDED, TAX_INCLUDED, NON_TAXABLE_TRANSACTION].include?(level_2[:tax_indicator].to_i) + xml.tag! :Tax, level_2[:tax].to_i if level_2[:tax] + end + end + + def add_level_2_advice_addendum(xml, options={}) + if (level_2 = options[:level_2_data]) + xml.tag! :AMEXTranAdvAddn1, byte_limit(level_2[:advice_addendum_1], 40) if level_2[:advice_addendum_1] + xml.tag! :AMEXTranAdvAddn2, byte_limit(level_2[:advice_addendum_2], 40) if level_2[:advice_addendum_2] + xml.tag! :AMEXTranAdvAddn3, byte_limit(level_2[:advice_addendum_3], 40) if level_2[:advice_addendum_3] + xml.tag! :AMEXTranAdvAddn4, byte_limit(level_2[:advice_addendum_4], 40) if level_2[:advice_addendum_4] + end + end + + def add_level_2_purchase(xml, options={}) + if (level_2 = options[:level_2_data]) + xml.tag! :PCOrderNum, byte_limit(level_2[:purchase_order], 17) if level_2[:purchase_order] + xml.tag! :PCDestZip, byte_limit(format_address_field(level_2[:zip]), 10) if level_2[:zip] + xml.tag! :PCDestName, byte_limit(format_address_field(level_2[:name]), 30) if level_2[:name] + xml.tag! :PCDestAddress1, byte_limit(format_address_field(level_2[:address1]), 30) if level_2[:address1] + xml.tag! :PCDestAddress2, byte_limit(format_address_field(level_2[:address2]), 30) if level_2[:address2] + xml.tag! :PCDestCity, byte_limit(format_address_field(level_2[:city]), 20) if level_2[:city] + xml.tag! :PCDestState, byte_limit(format_address_field(level_2[:state]), 2) if level_2[:state] + end + end + def add_address(xml, creditcard, options) if(address = (options[:billing_address] || options[:address])) - avs_supported = AVS_SUPPORTED_COUNTRIES.include?(address[:country].to_s) + avs_supported = AVS_SUPPORTED_COUNTRIES.include?(address[:country].to_s) || empty?(address[:country]) if avs_supported - xml.tag! :AVSzip, (address[:zip] ? address[:zip].to_s[0..9] : nil) - xml.tag! :AVSaddress1, (address[:address1] ? address[:address1][0..29] : nil) - xml.tag! :AVSaddress2, (address[:address2] ? address[:address2][0..29] : nil) - xml.tag! :AVScity, (address[:city] ? address[:city][0..19] : nil) - xml.tag! :AVSstate, address[:state] - xml.tag! :AVSphoneNum, (address[:phone] ? address[:phone].scan(/\d/).join.to_s[0..13] : nil) + xml.tag! :AVSzip, byte_limit(format_address_field(address[:zip]), 10) + xml.tag! :AVSaddress1, byte_limit(format_address_field(address[:address1]), 30) + xml.tag! :AVSaddress2, byte_limit(format_address_field(address[:address2]), 30) + xml.tag! :AVScity, byte_limit(format_address_field(address[:city]), 20) + xml.tag! :AVSstate, byte_limit(format_address_field(address[:state]), 2) + xml.tag! :AVSphoneNum, (address[:phone] ? address[:phone].scan(/\d/).join.to_s[0..13] : nil) end - # can't look in billing address? - xml.tag! :AVSname, ((creditcard && creditcard.name) ? creditcard.name[0..29] : nil) - xml.tag! :AVScountryCode, (avs_supported ? address[:country] : '') + + xml.tag! :AVSname, (creditcard&.name ? creditcard.name[0..29] : nil) + xml.tag! :AVScountryCode, (avs_supported ? byte_limit(format_address_field(address[:country]), 2) : '') # Needs to come after AVScountryCode add_destination_address(xml, address) if avs_supported @@ -300,38 +407,44 @@ def add_address(xml, creditcard, options) def add_destination_address(xml, address) if address[:dest_zip] - xml.tag! :AVSDestzip, (address[:dest_zip] ? address[:dest_zip].to_s[0..9] : nil) - xml.tag! :AVSDestaddress1, (address[:dest_address1] ? address[:dest_address1][0..29] : nil) - xml.tag! :AVSDestaddress2, (address[:dest_address2] ? address[:dest_address2][0..29] : nil) - xml.tag! :AVSDestcity, (address[:dest_city] ? address[:dest_city][0..19] : nil) - xml.tag! :AVSDeststate, address[:dest_state] + avs_supported = AVS_SUPPORTED_COUNTRIES.include?(address[:dest_country].to_s) + + xml.tag! :AVSDestzip, byte_limit(format_address_field(address[:dest_zip]), 10) + xml.tag! :AVSDestaddress1, byte_limit(format_address_field(address[:dest_address1]), 30) + xml.tag! :AVSDestaddress2, byte_limit(format_address_field(address[:dest_address2]), 30) + xml.tag! :AVSDestcity, byte_limit(format_address_field(address[:dest_city]), 20) + xml.tag! :AVSDeststate, byte_limit(format_address_field(address[:dest_state]), 2) xml.tag! :AVSDestphoneNum, (address[:dest_phone] ? address[:dest_phone].scan(/\d/).join.to_s[0..13] : nil) - xml.tag! :AVSDestname, (address[:dest_name] ? address[:dest_name][0..29] : nil) - xml.tag! :AVSDestcountryCode, address[:dest_country] + xml.tag! :AVSDestname, byte_limit(address[:dest_name], 30) + xml.tag! :AVSDestcountryCode, (avs_supported ? address[:dest_country] : '') end end # For Profile requests def add_customer_address(xml, options) if(address = (options[:billing_address] || options[:address])) - xml.tag! :CustomerAddress1, (address[:address1] ? address[:address1][0..29] : nil) - xml.tag! :CustomerAddress2, (address[:address2] ? address[:address2][0..29] : nil) - xml.tag! :CustomerCity, (address[:city] ? address[:city][0..19] : nil) - xml.tag! :CustomerState, address[:state] - xml.tag! :CustomerZIP, (address[:zip] ? address[:zip].to_s[0..9] : nil) - xml.tag! :CustomerEmail, address[:email].to_s[0..49] if address[:email] + avs_supported = AVS_SUPPORTED_COUNTRIES.include?(address[:country].to_s) + + xml.tag! :CustomerAddress1, byte_limit(format_address_field(address[:address1]), 30) + xml.tag! :CustomerAddress2, byte_limit(format_address_field(address[:address2]), 30) + xml.tag! :CustomerCity, byte_limit(format_address_field(address[:city]), 20) + xml.tag! :CustomerState, byte_limit(format_address_field(address[:state]), 2) + xml.tag! :CustomerZIP, byte_limit(format_address_field(address[:zip]), 10) + xml.tag! :CustomerEmail, byte_limit(address[:email], 50) if address[:email] xml.tag! :CustomerPhone, (address[:phone] ? address[:phone].scan(/\d/).join.to_s : nil) - xml.tag! :CustomerCountryCode, (address[:country] ? address[:country][0..1] : nil) + xml.tag! :CustomerCountryCode, (avs_supported ? address[:country] : '') end end def add_creditcard(xml, creditcard, currency=nil) - xml.tag! :AccountNum, creditcard.number - xml.tag! :Exp, expiry_date(creditcard) + unless creditcard.nil? + xml.tag! :AccountNum, creditcard.number + xml.tag! :Exp, expiry_date(creditcard) + end xml.tag! :CurrencyCode, currency_code(currency) - xml.tag! :CurrencyExponent, '2' # Will need updating to support currencies such as the Yen. + xml.tag! :CurrencyExponent, currency_exponents(currency) # If you are trying to collect a Card Verification Number # (CardSecVal) for a Visa or Discover transaction, pass one of these values: @@ -342,21 +455,83 @@ def add_creditcard(xml, creditcard, currency=nil) # Null-fill this attribute OR # Do not submit the attribute at all. # - http://download.chasepaymentech.com/docs/orbital/orbital_gateway_xml_specification.pdf - if %w( visa discover ).include?(creditcard.brand) - xml.tag! :CardSecValInd, (creditcard.verification_value? ? '1' : '9') + unless creditcard.nil? + if creditcard.verification_value? + if %w( visa discover ).include?(creditcard.brand) + xml.tag! :CardSecValInd, '1' + end + xml.tag! :CardSecVal, creditcard.verification_value + end end - xml.tag! :CardSecVal, creditcard.verification_value if creditcard.verification_value? + end + + def add_eci(xml, creditcard, three_d_secure) + eci = if three_d_secure + three_d_secure[:eci] + elsif creditcard.is_a?(NetworkTokenizationCreditCard) + creditcard.eci + end + + xml.tag!(:AuthenticationECIInd, eci) if eci + end + + def add_xid(xml, creditcard, three_d_secure) + xid = if three_d_secure && creditcard.brand == 'visa' + three_d_secure[:xid] + elsif creditcard.is_a?(NetworkTokenizationCreditCard) + creditcard.transaction_id + end + + xml.tag!(:XID, xid) if xid + end + + def add_cavv(xml, creditcard, three_d_secure) + return unless three_d_secure && creditcard.brand == 'visa' + + xml.tag!(:CAVV, three_d_secure[:cavv]) + end + + def add_aav(xml, creditcard, three_d_secure) + return unless three_d_secure && creditcard.brand == 'master' + + xml.tag!(:AAV, three_d_secure[:cavv]) + end + + def add_dpanind(xml, creditcard) + return unless creditcard.is_a?(NetworkTokenizationCreditCard) + + xml.tag! :DPANInd, 'Y' + end + + def add_digital_token_cryptogram(xml, creditcard) + return unless creditcard.is_a?(NetworkTokenizationCreditCard) + + xml.tag! :DigitalTokenCryptogram, creditcard.payment_cryptogram + end + + def add_aevv(xml, creditcard, three_d_secure) + return unless three_d_secure && creditcard.brand == 'american_express' + + xml.tag!(:AEVV, three_d_secure[:cavv]) + end + + def add_pymt_brand_program_code(xml, creditcard, three_d_secure) + return unless three_d_secure && creditcard.brand == 'american_express' + + xml.tag!(:PymtBrandProgramCode, 'ASK') end def add_refund(xml, currency=nil) xml.tag! :AccountNum, nil xml.tag! :CurrencyCode, currency_code(currency) - xml.tag! :CurrencyExponent, '2' # Will need updating to support currencies such as the Yen. + xml.tag! :CurrencyExponent, currency_exponents(currency) end def add_managed_billing(xml, options) if mb = options[:managed_billing] + ActiveMerchant.deprecated RECURRING_DEPRECATION_MESSAGE + # default to recurring (R). Other option is deferred (D). xml.tag! :MBType, mb[:type] || RECURRING # default to Customer Reference Number @@ -377,32 +552,65 @@ def add_managed_billing(xml, options) end end + def add_stored_credentials(xml, parameters) + return unless parameters[:mit_stored_credential_ind] == 'Y' || parameters[:stored_credential] && !parameters[:stored_credential].values.all?(&:nil?) + if msg_type = get_msg_type(parameters) + xml.tag! :MITMsgType, msg_type + end + xml.tag! :MITStoredCredentialInd, 'Y' + if parameters[:mit_submitted_transaction_id] + xml.tag! :MITSubmittedTransactionID, parameters[:mit_submitted_transaction_id] + elsif parameters.dig(:stored_credential, :network_transaction_id) && parameters.dig(:stored_credential, :initiator) == 'merchant' + xml.tag! :MITSubmittedTransactionID, parameters[:stored_credential][:network_transaction_id] + end + end + + def get_msg_type(parameters) + return parameters[:mit_msg_type] if parameters[:mit_msg_type] + return 'CSTO' if parameters[:stored_credential][:initial_transaction] + return unless parameters[:stored_credential][:initiator] && parameters[:stored_credential][:reason_type] + initiator = case parameters[:stored_credential][:initiator] + when 'customer' then 'C' + when 'merchant' then 'M' + end + reason = case parameters[:stored_credential][:reason_type] + when 'recurring' then 'REC' + when 'installment' then 'INS' + when 'unscheduled' then 'USE' + end + + "#{initiator}#{reason}" + end + def parse(body) response = {} xml = REXML::Document.new(body) - root = REXML::XPath.first(xml, "//Response") || - REXML::XPath.first(xml, "//ErrorResponse") + root = REXML::XPath.first(xml, '//Response') || + REXML::XPath.first(xml, '//ErrorResponse') if root root.elements.to_a.each do |node| recurring_parse_element(response, node) end end - response + + response.delete_if { |k, _| SENSITIVE_FIELDS.include?(k) } end def recurring_parse_element(response, node) if node.has_elements? - node.elements.each{|e| recurring_parse_element(response, e) } + node.elements.each { |e| recurring_parse_element(response, e) } else response[node.name.underscore.to_sym] = node.text end end def commit(order, message_type, trace_number=nil) - headers = POST_HEADERS.merge("Content-length" => order.size.to_s) - headers.merge!( "Trace-number" => trace_number.to_s, - "Merchant-Id" => @options[:merchant_id] ) if @options[:retry_logic] && trace_number - request = lambda{|url| parse(ssl_post(url, order, headers))} + headers = POST_HEADERS.merge('Content-length' => order.size.to_s) + if @options[:retry_logic] && trace_number + headers['Trace-number'] = trace_number.to_s + headers['Merchant-Id'] = @options[:merchant_id] + end + request = ->(url) { parse(ssl_post(url, order, headers)) } # Failover URL will be attempted in the event of a connection error response = begin @@ -416,7 +624,7 @@ def commit(order, message_type, trace_number=nil) :authorization => authorization_string(response[:tx_ref_num], response[:order_id]), :test => self.test?, :avs_result => OrbitalGateway::AVSResult.new(response[:avs_resp_code]), - :cvv_result => response[:cvv2_resp_code] + :cvv_result => OrbitalGateway::CVVResult.new(response[:cvv2_resp_code]) } ) end @@ -436,7 +644,7 @@ def success?(response, message_type) response[:profile_proc_status] == SUCCESS else response[:proc_status] == SUCCESS && - response[:resp_code] == APPROVED + APPROVED.include?(response[:resp_code]) end end @@ -448,7 +656,7 @@ def ip_authentication? @options[:ip_authentication] == true end - def build_new_order_xml(action, money, parameters = {}) + def build_new_order_xml(action, money, creditcard, parameters = {}) requires!(parameters, :order_id) xml = xml_envelope xml.tag! :Request do @@ -471,14 +679,30 @@ def build_new_order_xml(action, money, parameters = {}) yield xml if block_given? + three_d_secure = parameters[:three_d_secure] + + add_eci(xml, creditcard, three_d_secure) + add_cavv(xml, creditcard, three_d_secure) + add_xid(xml, creditcard, three_d_secure) + xml.tag! :OrderID, format_order_id(parameters[:order_id]) xml.tag! :Amount, amount(money) xml.tag! :Comments, parameters[:comments] if parameters[:comments] + add_level_2_tax(xml, parameters) + add_level_2_advice_addendum(xml, parameters) + + add_aav(xml, creditcard, three_d_secure) # CustomerAni, AVSPhoneType and AVSDestPhoneType could be added here. + add_dpanind(xml, creditcard) + add_aevv(xml, creditcard, three_d_secure) + add_digital_token_cryptogram(xml, creditcard) + if parameters[:soft_descriptors].is_a?(OrbitalSoftDescriptors) add_soft_descriptors(xml, parameters[:soft_descriptors]) + elsif parameters[:soft_descriptors].is_a?(Hash) + add_soft_descriptors_from_hash(xml, parameters[:soft_descriptors]) end set_recurring_ind(xml, parameters) @@ -488,6 +712,10 @@ def build_new_order_xml(action, money, parameters = {}) tx_ref_num, _ = split_authorization(parameters[:authorization]) xml.tag! :TxRefNum, tx_ref_num end + + add_level_2_purchase(xml, parameters) + add_stored_credentials(xml, parameters) + add_pymt_brand_program_code(xml, creditcard, three_d_secure) end end xml.target! @@ -498,7 +726,7 @@ def build_new_order_xml(action, money, parameters = {}) # RS - Subsequent Recurring Transactions def set_recurring_ind(xml, parameters) if parameters[:recurring_ind] - raise "RecurringInd must be set to either \"RF\" or \"RS\"" unless %w(RF RS).include?(parameters[:recurring_ind]) + raise 'RecurringInd must be set to either "RF" or "RS"' unless %w(RF RS).include?(parameters[:recurring_ind]) xml.tag! :RecurringInd, parameters[:recurring_ind] end end @@ -511,8 +739,11 @@ def build_mark_for_capture_xml(money, authorization, parameters = {}) add_xml_credentials(xml) xml.tag! :OrderID, format_order_id(order_id) xml.tag! :Amount, amount(money) + add_level_2_tax(xml, parameters) add_bin_merchant_and_terminal(xml, parameters) xml.tag! :TxRefNum, tx_ref_num + add_level_2_purchase(xml, parameters) + add_level_2_advice_addendum(xml, parameters) end end xml.target! @@ -540,6 +771,10 @@ def currency_code(currency) CURRENCY_CODES[(currency || self.default_currency)].to_s end + def currency_exponents(currency) + CURRENCY_EXPONENTS[(currency || self.default_currency)].to_s + end + def expiry_date(credit_card) "#{format(credit_card.month, :two_digits)}#{format(credit_card.year, :two_digits)}" end @@ -577,13 +812,34 @@ def salem_mid? # 2. - , $ @ & and a space character, though the space character cannot be the leading character # 3. PINless Debit transactions can only use uppercase and lowercase alpha (A-Z, a-z) and numeric (0-9) def format_order_id(order_id) - illegal_characters = /[^,$@\- \w]/ + illegal_characters = /[^,$@&\- \w]/ order_id = order_id.to_s.gsub(/\./, '-') order_id.gsub!(illegal_characters, '') + order_id.lstrip! order_id[0...22] end + # Address-related fields cannot contain % | ^ \ / + # Returns the value with these characters removed, or nil + def format_address_field(value) + value.gsub(/[%\|\^\\\/]/, '') if value.respond_to?(:gsub) + end + + # Field lengths should be limited by byte count instead of character count + # Returns the truncated value or nil + def byte_limit(value, byte_length) + limited_value = '' + + value.to_s.each_char do |c| + break if((limited_value.bytesize + c.bytesize) > byte_length) + limited_value << c + end + + limited_value + end + def build_customer_request_xml(creditcard, options = {}) + ActiveMerchant.deprecated 'Customer Profile support in Orbital is non-conformant to the ActiveMerchant API and will be removed in its current form in a future version. Please contact the ActiveMerchant maintainers if you have an interest in modifying it to conform to the store/unstore/update API.' xml = xml_envelope xml.tag! :Request do xml.tag! :Profile do @@ -620,7 +876,7 @@ def build_customer_request_xml(creditcard, options = {}) end xml.tag! :CCAccountNum, creditcard.number if creditcard - xml.tag! :CCExpireDate, creditcard.expiry_date.expiration.strftime("%m%y") if creditcard + xml.tag! :CCExpireDate, creditcard.expiry_date.expiration.strftime('%m%y') if creditcard # This has to come after CCExpireDate. add_managed_billing(xml, options) @@ -628,6 +884,123 @@ def build_customer_request_xml(creditcard, options = {}) end xml.target! end + + # Unfortunately, Orbital uses their own special codes for AVS responses + # that are different than the standard codes defined in + # ActiveMerchant::Billing::AVSResult. + # + # This class encapsulates the response codes shown on page 240 of their spec: + # http://download.chasepaymentech.com/docs/orbital/orbital_gateway_xml_specification.pdf + # + class AVSResult < ActiveMerchant::Billing::AVSResult + CODES = { + '1' => 'No address supplied', + '2' => 'Bill-to address did not pass Auth Host edit checks', + '3' => 'AVS not performed', + '4' => 'Issuer does not participate in AVS', + '5' => 'Edit-error - AVS data is invalid', + '6' => 'System unavailable or time-out', + '7' => 'Address information unavailable', + '8' => 'Transaction Ineligible for AVS', + '9' => 'Zip Match/Zip 4 Match/Locale match', + 'A' => 'Zip Match/Zip 4 Match/Locale no match', + 'B' => 'Zip Match/Zip 4 no Match/Locale match', + 'C' => 'Zip Match/Zip 4 no Match/Locale no match', + 'D' => 'Zip No Match/Zip 4 Match/Locale match', + 'E' => 'Zip No Match/Zip 4 Match/Locale no match', + 'F' => 'Zip No Match/Zip 4 No Match/Locale match', + 'G' => 'No match at all', + 'H' => 'Zip Match/Locale match', + 'J' => 'Issuer does not participate in Global AVS', + 'JA' => 'International street address and postal match', + 'JB' => 'International street address match. Postal code not verified', + 'JC' => 'International street address and postal code not verified', + 'JD' => 'International postal code match. Street address not verified', + 'M1' => 'Cardholder name matches', + 'M2' => 'Cardholder name, billing address, and postal code matches', + 'M3' => 'Cardholder name and billing code matches', + 'M4' => 'Cardholder name and billing address match', + 'M5' => 'Cardholder name incorrect, billing address and postal code match', + 'M6' => 'Cardholder name incorrect, billing postal code matches', + 'M7' => 'Cardholder name incorrect, billing address matches', + 'M8' => 'Cardholder name, billing address and postal code are all incorrect', + 'N3' => 'Address matches, ZIP not verified', + 'N4' => 'Address and ZIP code not verified due to incompatible formats', + 'N5' => 'Address and ZIP code match (International only)', + 'N6' => 'Address not verified (International only)', + 'N7' => 'ZIP matches, address not verified', + 'N8' => 'Address and ZIP code match (International only)', + 'N9' => 'Address and ZIP code match (UK only)', + 'R' => 'Issuer does not participate in AVS', + 'UK' => 'Unknown', + 'X' => 'Zip Match/Zip 4 Match/Address Match', + 'Z' => 'Zip Match/Locale no match', + } + + # Map vendor's AVS result code to a postal match code + ORBITAL_POSTAL_MATCH_CODE = { + 'Y' => %w( 9 A B C H JA JD M2 M3 M5 N5 N8 N9 X Z ), + 'N' => %w( D E F G M8 ), + 'X' => %w( 4 J R ), + nil => %w( 1 2 3 5 6 7 8 JB JC M1 M4 M6 M7 N3 N4 N6 N7 UK ) + }.inject({}) do |map, (type, codes)| + codes.each { |code| map[code] = type } + map + end + + # Map vendor's AVS result code to a street match code + ORBITAL_STREET_MATCH_CODE = { + 'Y' => %w( 9 B D F H JA JB M2 M4 M5 M6 M7 N3 N5 N7 N8 N9 X ), + 'N' => %w( A C E G M8 Z ), + 'X' => %w( 4 J R ), + nil => %w( 1 2 3 5 6 7 8 JC JD M1 M3 N4 N6 UK ) + }.inject({}) do |map, (type, codes)| + codes.each { |code| map[code] = type } + map + end + + def self.messages + CODES + end + + def initialize(code) + @code = (code.blank? ? nil : code.to_s.strip.upcase) + if @code + @message = CODES[@code] + @postal_match = ORBITAL_POSTAL_MATCH_CODE[@code] + @street_match = ORBITAL_STREET_MATCH_CODE[@code] + end + end + end + + # Unfortunately, Orbital uses their own special codes for CVV responses + # that are different than the standard codes defined in + # ActiveMerchant::Billing::CVVResult. + # + # This class encapsulates the response codes shown on page 255 of their spec: + # http://download.chasepaymentech.com/docs/orbital/orbital_gateway_xml_specification.pdf + # + class CVVResult < ActiveMerchant::Billing::CVVResult + MESSAGES = { + 'M' => 'Match', + 'N' => 'No match', + 'P' => 'Not processed', + 'S' => 'Should have been present', + 'U' => 'Unsupported by issuer/Issuer unable to process request', + 'I' => 'Invalid', + 'Y' => 'Invalid', + '' => 'Not applicable' + } + + def self.messages + MESSAGES + end + + def initialize(code) + @code = code.blank? ? '' : code.upcase + @message = MESSAGES[@code] + end + end end end end diff --git a/lib/active_merchant/billing/gateways/orbital/avs_result.rb b/lib/active_merchant/billing/gateways/orbital/avs_result.rb deleted file mode 100644 index 264030a2347..00000000000 --- a/lib/active_merchant/billing/gateways/orbital/avs_result.rb +++ /dev/null @@ -1,93 +0,0 @@ -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - class OrbitalGateway < Gateway - # Unfortunately, Orbital uses their own special codes for AVS responses - # that are different than the standard codes defined in - # ActiveMerchant::Billing::AVSResult. - # - # This class encapsulates the response codes shown on page 240 of their spec: - # http://download.chasepaymentech.com/docs/orbital/orbital_gateway_xml_specification.pdf - # - class AVSResult < ActiveMerchant::Billing::AVSResult - CODES = { - '1' => 'No address supplied', - '2' => 'Bill-to address did not pass Auth Host edit checks', - '3' => 'AVS not performed', - '4' => 'Issuer does not participate in AVS', - '5' => 'Edit-error - AVS data is invalid', - '6' => 'System unavailable or time-out', - '7' => 'Address information unavailable', - '8' => 'Transaction Ineligible for AVS', - '9' => 'Zip Match/Zip 4 Match/Locale match', - 'A' => 'Zip Match/Zip 4 Match/Locale no match', - 'B' => 'Zip Match/Zip 4 no Match/Locale match', - 'C' => 'Zip Match/Zip 4 no Match/Locale no match', - 'D' => 'Zip No Match/Zip 4 Match/Locale match', - 'E' => 'Zip No Match/Zip 4 Match/Locale no match', - 'F' => 'Zip No Match/Zip 4 No Match/Locale match', - 'G' => 'No match at all', - 'H' => 'Zip Match/Locale match', - 'J' => 'Issuer does not participate in Global AVS', - 'JA' => 'International street address and postal match', - 'JB' => 'International street address match. Postal code not verified', - 'JC' => 'International street address and postal code not verified', - 'JD' => 'International postal code match. Street address not verified', - 'M1' => 'Merchant Override Decline', - 'M2' => 'Cardholder name, billing address, and postal code matches', - 'M3' => 'Cardholder name and billing code matches', - 'M4' => 'Cardholder name and billing address matches', - 'M5' => 'Cardholder name incorrect, billing address and postal code match', - 'M6' => 'Cardholder name incorrect, billing address matches', - 'M7' => 'Cardholder name incorrect, billing address matches', - 'M8' => 'Cardholder name, billing address and postal code are all incorrect', - 'N3' => 'Address matches, ZIP not verified', - 'N4' => 'Address and ZIP code not verified due to incompatible formats', - 'N5' => 'Address and ZIP code match (International only)', - 'N6' => 'Address not verified (International only)', - 'N7' => 'ZIP matches, address not verified', - 'N8' => 'Address and ZIP code match (International only)', - 'N9' => 'Address and ZIP code match (UK only)', - 'R' => 'Issuer does not participate in AVS', - 'UX' => 'Unknown', - 'X' => 'Zip Match/Zip 4 Match/Address Match', - 'Z' => 'Zip Match/Locale no match', - } - - # Map vendor's AVS result code to a postal match code - ORBITAL_POSTAL_MATCH_CODE = { - 'Y' => %w( 9 A B C H JA JD M2 M3 M5 N5 N8 N9 X Z ), - 'N' => %w( D E F G M8 ), - 'X' => %w( 4 J R ), - nil => %w( 1 2 3 5 6 7 8 JB JC M1 M4 M6 M7 N3 N4 N6 N7 UX ) - }.inject({}) do |map, (type, codes)| - codes.each { |code| map[code] = type } - map - end - - # Map vendor's AVS result code to a street match code - ORBITAL_STREET_MATCH_CODE = { - 'Y' => %w( 9 B D F H JA JB M2 M4 M5 M6 M7 N3 N5 N7 N8 N9 X ), - 'N' => %w( A C E G M8 Z ), - 'X' => %w( 4 J R ), - nil => %w( 1 2 3 5 6 7 8 JC JD M1 M3 N4 N6 UX ) - }.inject({}) do |map, (type, codes)| - codes.each { |code| map[code] = type } - map - end - - def self.messages - CODES - end - - def initialize(code) - @code = code.to_s.strip.upcase unless code.blank? - if @code - @message = CODES[@code] - @postal_match = ORBITAL_POSTAL_MATCH_CODE[@code] - @street_match = ORBITAL_STREET_MATCH_CODE[@code] - end - end - end - end - end -end diff --git a/lib/active_merchant/billing/gateways/orbital/orbital_soft_descriptors.rb b/lib/active_merchant/billing/gateways/orbital/orbital_soft_descriptors.rb index 010af391187..da1d82d0ca0 100644 --- a/lib/active_merchant/billing/gateways/orbital/orbital_soft_descriptors.rb +++ b/lib/active_merchant/billing/gateways/orbital/orbital_soft_descriptors.rb @@ -1,23 +1,21 @@ module ActiveMerchant #:nodoc: module Billing #:nodoc: - class OrbitalSoftDescriptors - include Validateable - + class OrbitalSoftDescriptors < Model PHONE_FORMAT_1 = /\A\d{3}-\d{3}-\d{4}\z/ PHONE_FORMAT_2 = /\A\d{3}-\w{7}\z/ - + # ==== Tampa PNS Soft Descriptors - # The support for Soft Descriptors via the PNS Host is only for customers processing through Chase - # Paymentech Canada. + # The support for Soft Descriptors via the PNS Host is only for customers processing through Chase + # Paymentech Canada. - # Unlike Salem, the only value that gets passed on the cardholder statement is the Merchant Name field. - # And for these customers, it is a maximum of 25 bytes of data. - # - # All other Soft Descriptor fields can optionally be sent, but will not be submitted to the settlement host + # Unlike Salem, the only value that gets passed on the cardholder statement is the Merchant Name field. + # And for these customers, it is a maximum of 25 bytes of data. + # + # All other Soft Descriptor fields can optionally be sent, but will not be submitted to the settlement host # and will not display on the cardholder statement. - + attr_accessor :merchant_name, :product_description, :merchant_city, :merchant_phone, :merchant_url, :merchant_email - + def initialize(options = {}) self.merchant_name = options[:merchant_name] self.merchant_city = options[:merchant_city] @@ -25,22 +23,25 @@ def initialize(options = {}) self.merchant_url = options[:merchant_url] self.merchant_email = options[:merchant_email] end - + def validate - errors.add(:merchant_name, "is required") if self.merchant_name.blank? - errors.add(:merchant_name, "is required to be 25 bytes or less") if self.merchant_name.bytesize > 25 - - unless self.merchant_phone.blank? || self.merchant_phone.match(PHONE_FORMAT_1) || self.merchant_phone.match(PHONE_FORMAT_2) - errors.add(:merchant_phone, "is required to follow \"NNN-NNN-NNNN\" or \"NNN-AAAAAAA\" format") + errors = [] + + errors << [:merchant_name, 'is required'] if self.merchant_name.blank? + errors << [:merchant_name, 'is required to be 25 bytes or less'] if self.merchant_name.bytesize > 25 + + if(!empty?(self.merchant_phone) && !self.merchant_phone.match(PHONE_FORMAT_1) && !self.merchant_phone.match(PHONE_FORMAT_2)) + errors << [:merchant_phone, 'is required to follow "NNN-NNN-NNNN" or "NNN-AAAAAAA" format'] end - + [:merchant_email, :merchant_url].each do |attr| unless self.send(attr).blank? - errors.add(attr, "is required to be 13 bytes or less") if self.send(attr).bytesize > 13 + errors << [attr, 'is required to be 13 bytes or less'] if(self.send(attr).bytesize > 13) end end + + errors_hash(errors) end - end end end diff --git a/lib/active_merchant/billing/gateways/pac_net_raven.rb b/lib/active_merchant/billing/gateways/pac_net_raven.rb new file mode 100644 index 00000000000..952684b1943 --- /dev/null +++ b/lib/active_merchant/billing/gateways/pac_net_raven.rb @@ -0,0 +1,206 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class PacNetRavenGateway < Gateway + + AVS_ADDRESS_CODES = { + 'avs_address_unavailable' => 'X', + 'avs_address_not_checked' => 'X', + 'avs_address_matched' => 'Y', + 'avs_address_not_matched' => 'N', + 'avs_address_partial_match' => 'N' + } + + AVS_POSTAL_CODES = { + 'avs_postal_unavailable' => 'X', + 'avs_postal_not_checked' => 'X', + 'avs_postal_matched' => 'Y', + 'avs_postal_not_matched' => 'N', + 'avs_postal_partial_match' => 'N' + } + + CVV2_CODES = { + 'cvv2_matched' => 'Y', + 'cvv2_not_matched' => 'N', + 'cvv2_unavailable' => 'X', + 'cvv2_not_checked' => 'X' + } + + self.live_url = 'https://raven.deepcovelabs.net/realtime/' + self.test_url = self.live_url + + self.supported_countries = ['US'] + self.supported_cardtypes = [:visa, :master] + self.money_format = :cents + self.default_currency = 'USD' + self.homepage_url = 'https://www.deepcovelabs.com/raven' + self.display_name = 'Raven' + + def initialize(options = {}) + requires!(options, :user, :secret, :prn) + super + end + + def authorize(money, creditcard, options = {}) + post = {} + add_creditcard(post, creditcard) + add_currency_code(post, money, options) + add_address(post, options) + post['PRN'] = @options[:prn] + + commit('cc_preauth', money, post) + end + + def purchase(money, creditcard, options = {}) + post = {} + add_currency_code(post, money, options) + add_creditcard(post, creditcard) + add_address(post, options) + post['PRN'] = @options[:prn] + + commit('cc_debit', money, post) + end + + def void(authorization, options = {}) + post = {} + post['TrackingNumber'] = authorization + post['PymtType'] = options[:pymt_type] + + commit('void', nil, post) + end + + def capture(money, authorization, options = {}) + post = {} + post['PreauthNumber'] = authorization + post['PRN'] = @options[:prn] + add_currency_code(post, money, options) + + commit('cc_settle', money, post) + end + + def refund(money, template_number, options = {}) + post = {} + post['PRN'] = @options[:prn] + post['TemplateNumber'] = template_number + add_currency_code(post, money, options) + + commit('cc_refund', money, post) + end + + private + + def add_creditcard(post, creditcard) + post['CardNumber'] = creditcard.number + post['Expiry'] = expdate(creditcard) + post['CVV2'] = creditcard.verification_value if creditcard.verification_value + end + + def add_currency_code(post, money, options) + post['Currency'] = options[:currency] || currency(money) + end + + def add_address(post, options) + if address = options[:billing_address] || options[:address] + post['BillingStreetAddressLineOne'] = address[:address1].to_s + post['BillingStreetAddressLineFour'] = address[:address2].to_s + post['BillingPostalCode'] = address[:zip].to_s + end + end + + def parse(body) + Hash[body.split('&').map { |x| x.split('=').map { |y| CGI.unescape(y) } }] + end + + def commit(action, money, parameters) + parameters['Amount'] = amount(money) unless action == 'void' + + data = ssl_post url(action), post_data(action, parameters) + + response = parse(data) + response[:action] = action + + message = message_from(response) + + test_mode = test? || message =~ /TESTMODE/ + + Response.new(success?(response), message, response, + :test => test_mode, + :authorization => response['TrackingNumber'], + :fraud_review => fraud_review?(response), + :avs_result => { + :postal_match => AVS_POSTAL_CODES[response['AVSPostalResponseCode']], + :street_match => AVS_ADDRESS_CODES[response['AVSAddressResponseCode']] + }, + :cvv_result => CVV2_CODES[response['CVV2ResponseCode']] + ) + end + + def url(action) + (test? ? self.test_url : self.live_url) + endpoint(action) + end + + def endpoint(action) + return 'void' if action == 'void' + 'submit' + end + + def fraud_review?(response) + false + end + + def success?(response) + if %w(cc_settle cc_debit cc_preauth cc_refund).include?(response[:action]) + !response['ApprovalCode'].nil? and response['ErrorCode'].nil? and response['Status'] == 'Approved' + elsif response[:action] = 'void' + !response['ApprovalCode'].nil? and response['ErrorCode'].nil? and response['Status'] == 'Voided' + end + end + + def message_from(response) + return response['Message'] if response['Message'] + + if response['Status'] == 'Approved' + 'This transaction has been approved' + elsif response['Status'] == 'Declined' + 'This transaction has been declined' + elsif response['Status'] == 'Voided' + 'This transaction has been voided' + else + response['Status'] + end + end + + def post_data(action, parameters = {}) + post = {} + + post['PymtType'] = action + post['RAPIVersion'] = '2' + post['UserName'] = @options[:user] + post['Timestamp'] = timestamp + post['RequestID'] = request_id + post['Signature'] = signature(action, post, parameters) + + request = post.merge(parameters).collect { |key, value| "#{key}=#{CGI.escape(value.to_s)}" }.join('&') + request + end + + def timestamp + Time.now.strftime('%Y-%m-%dT%H:%M:%S.Z') + end + + def request_id + SecureRandom.uuid + end + + def signature(action, post, parameters = {}) + string = if %w(cc_settle cc_debit cc_preauth cc_refund).include?(action) + post['UserName'] + post['Timestamp'] + post['RequestID'] + post['PymtType'] + parameters['Amount'].to_s + parameters['Currency'] + elsif action == 'void' + post['UserName'] + post['Timestamp'] + post['RequestID'] + parameters['TrackingNumber'] + else + post['UserName'] + end + OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA1.new(@options[:secret]), @options[:secret], string) + end + end + end +end diff --git a/lib/active_merchant/billing/gateways/pagarme.rb b/lib/active_merchant/billing/gateways/pagarme.rb new file mode 100644 index 00000000000..26545c7c2d3 --- /dev/null +++ b/lib/active_merchant/billing/gateways/pagarme.rb @@ -0,0 +1,246 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class PagarmeGateway < Gateway + self.live_url = 'https://api.pagar.me/1/' + + self.supported_countries = ['BR'] + self.default_currency = 'BRL' + self.money_format = :cents + self.supported_cardtypes = [:visa, :master, :american_express, :discover, :diners_club] + + self.homepage_url = 'https://pagar.me/' + self.display_name = 'Pagar.me' + + STANDARD_ERROR_CODE_MAPPING = { + 'refused' => STANDARD_ERROR_CODE[:card_declined], + 'processing_error' => STANDARD_ERROR_CODE[:processing_error], + } + + def initialize(options={}) + requires!(options, :api_key) + @api_key = options[:api_key] + + super + end + + def purchase(money, payment_method, options={}) + post = {} + add_amount(post, money) + add_payment_method(post, payment_method) + add_metadata(post, options) + + commit(:post, 'transactions', post) + end + + def authorize(money, payment_method, options={}) + post = {} + add_amount(post, money) + add_payment_method(post, payment_method) + add_metadata(post, options) + + post[:capture] = false + + commit(:post, 'transactions', post) + end + + def capture(money, authorization, options={}) + if authorization.nil? + return Response.new(false, 'Não é possível capturar uma transação sem uma prévia autorização.') + end + + post = {} + commit(:post, "transactions/#{authorization}/capture", post) + end + + def refund(money, authorization, options={}) + if authorization.nil? + return Response.new(false, 'Não é possível estornar uma transação sem uma prévia captura.') + end + + void(authorization, options) + end + + def void(authorization, options={}) + if authorization.nil? + return Response.new(false, 'Não é possível estornar uma transação autorizada sem uma prévia autorização.') + end + + post = {} + commit(:post, "transactions/#{authorization}/refund", post) + end + + def verify(payment_method, options={}) + MultiResponse.run(:use_first_response) do |r| + r.process { authorize(127, payment_method, options) } + r.process(:ignore_result) { void(r.authorization, options) } + end + end + + def supports_scrubbing? + true + end + + def scrub(transcript) + transcript. + gsub(%r((Authorization: Basic )\w+), '\1[FILTERED]'). + gsub(%r((card_number=)\d+), '\1[FILTERED]'). + gsub(%r((card_cvv=)\d+), '\1[FILTERED]') + end + + private + + def add_amount(post, money) + post[:amount] = amount(money) + end + + def add_payment_method(post, payment_method) + post[:payment_method] = 'credit_card' + add_credit_card(post, payment_method) + end + + def add_credit_card(post, credit_card) + post[:card_number] = credit_card.number + post[:card_holder_name] = credit_card.name + post[:card_expiration_date] = "#{credit_card.month}/#{credit_card.year}" + post[:card_cvv] = credit_card.verification_value + end + + def add_metadata(post, options={}) + post[:metadata] = {} + post[:metadata][:order_id] = options[:order_id] + post[:metadata][:ip] = options[:ip] + post[:metadata][:customer] = options[:customer] + post[:metadata][:invoice] = options[:invoice] + post[:metadata][:merchant] = options[:merchant] + post[:metadata][:description] = options[:description] + post[:metadata][:email] = options[:email] + end + + def parse(body) + JSON.parse(body) + end + + def post_data(params) + return nil unless params + + params.map do |key, value| + next if value != false && value.blank? + if value.is_a?(Hash) + h = {} + value.each do |k, v| + h["#{key}[#{k}]"] = v unless v.blank? + end + post_data(h) + elsif value.is_a?(Array) + value.map { |v| "#{key}[]=#{CGI.escape(v.to_s)}" }.join('&') + else + "#{key}=#{CGI.escape(value.to_s)}" + end + end.compact.join('&') + end + + def headers(options = {}) + { + 'Authorization' => 'Basic ' + Base64.encode64(@api_key.to_s + ':x').strip, + 'User-Agent' => "Pagar.me/1 ActiveMerchant/#{ActiveMerchant::VERSION}", + 'Accept-Encoding' => 'deflate' + } + end + + def api_request(method, endpoint, parameters = nil, options = {}) + raw_response = response = nil + begin + raw_response = ssl_request(method, self.live_url + endpoint, post_data(parameters), headers(options)) + response = parse(raw_response) + rescue ResponseError => e + raw_response = e.response.body + response = response_error(raw_response) + rescue JSON::ParserError + response = json_error(raw_response) + end + response + end + + def commit(method, url, parameters, options = {}) + response = api_request(method, url, parameters, options) + + Response.new( + success_from(response), + message_from(response), + response, + authorization: authorization_from(response), + test: test?, + error_code: error_code_from(response) + ) + end + + def response_error(raw_response) + parse(raw_response) + rescue JSON::ParserError + json_error(raw_response) + end + + def json_error(raw_response) + msg = 'Resposta inválida retornada pela API do Pagar.me. Por favor entre em contato com suporte@pagar.me se você continuar recebendo essa mensagem.' + msg += " (A resposta retornada pela API foi #{raw_response.inspect})" + { + 'errors' => [{ + 'message' => msg + }] + } + end + + def success_from(response) + success_purchase = response.key?('status') && response['status'] == 'paid' + success_authorize = response.key?('status') && response['status'] == 'authorized' + success_refund = response.key?('status') && response['status'] == 'refunded' + + success_purchase || success_authorize || success_refund + end + + def failure_from(response) + response.key?('status') && response['status'] == 'refused' + end + + def message_from(response) + if success_from(response) + case response['status'] + when 'paid' + 'Transação aprovada' + when 'authorized' + 'Transação autorizada' + when 'refunded' + 'Transação estornada' + else + "Transação com status '#{response["status"]}'" + end + elsif failure_from(response) + 'Transação recusada' + elsif response.key?('errors') + response['errors'][0]['message'] + else + msg = json_error(response) + msg['errors'][0]['message'] + end + end + + def authorization_from(response) + if success_from(response) + response['id'] + end + end + + def test? + @api_key.start_with?('ak_test') + end + + def error_code_from(response) + if failure_from(response) + STANDARD_ERROR_CODE_MAPPING['refused'] + elsif response.key?('errors') + STANDARD_ERROR_CODE_MAPPING['processing_error'] + end + end + end + end +end diff --git a/lib/active_merchant/billing/gateways/pago_facil.rb b/lib/active_merchant/billing/gateways/pago_facil.rb new file mode 100644 index 00000000000..85793fbb369 --- /dev/null +++ b/lib/active_merchant/billing/gateways/pago_facil.rb @@ -0,0 +1,122 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class PagoFacilGateway < Gateway + self.test_url = 'https://www.pagofacil.net/st/public/Wsrtransaccion/index/format/json?' + self.live_url = 'https://www.pagofacil.net/ws/public/Wsrtransaccion/index/format/json?' + + self.supported_countries = ['MX'] + self.default_currency = 'MXN' + self.supported_cardtypes = [:visa, :master, :american_express, :jcb] + + self.homepage_url = 'http://www.pagofacil.net/' + self.display_name = 'PagoFacil' + + def initialize(options={}) + requires!(options, :branch_id, :merchant_id, :service_id) + super + end + + def purchase(money, credit_card, options={}) + post = {} + add_invoice(post, money, options) + add_payment(post, credit_card) + add_address(post, options) + add_customer_data(post, options) + add_merchant_data(post) + + commit(post) + end + + private + + def add_customer_data(post, options) + post[:email] = options[:email] + post[:celular] = options[:cellphone] + end + + def add_address(post, options) + address = options.fetch(:billing_address, {}) + post[:calleyNumero] = address[:address1] + post[:colonia] = address[:address2] + post[:municipio] = address[:city] + post[:estado] = address[:state] + post[:pais] = address[:country] + post[:telefono] = address[:phone] + post[:cp] = address[:zip] + end + + def add_invoice(post, money, options) + post[:monto] = amount(money) + post[:idPedido] = options[:order_id] + add_currency(post, money, options) + end + + def add_currency(post, money, options) + currency = options.fetch(:currency, currency(money)) + unless currency == self.class.default_currency + post[:divisa] = currency + end + end + + def add_payment(post, credit_card) + post[:nombre] = credit_card.first_name + post[:apellidos] = credit_card.last_name + post[:numeroTarjeta] = credit_card.number + post[:cvt] = credit_card.verification_value + post[:mesExpiracion] = sprintf('%02d', credit_card.month) + post[:anyoExpiracion] = credit_card.year.to_s.slice(-2, 2) + end + + def add_merchant_data(post) + post[:idSucursal] = options.fetch(:branch_id) + post[:idUsuario] = options.fetch(:merchant_id) + post[:idServicio] = options.fetch(:service_id) + end + + def parse(body) + JSON.parse(body)['WebServices_Transacciones']['transaccion'] + rescue JSON::ParserError + json_error(body) + end + + def commit(parameters) + url = (test? ? test_url : live_url) + response = parse(ssl_post(url, post_data(parameters))) + Response.new( + success_from(response), + message_from(response), + response, + authorization: authorization_from(response), + test: test? + ) + end + + def success_from(response) + response['autorizado'] == '1' || + response['autorizado'] == true + end + + def message_from(response) + response['texto'] + end + + def authorization_from(response) + response['autorizacion'] + end + + def post_data(parameters = {}) + { + method: 'transaccion', + data: parameters + }.to_query + end + + def json_error(response) + { + 'texto' => 'Invalid response received from the PagoFacil API.', + 'raw_response' => response + } + end + end + end +end diff --git a/lib/active_merchant/billing/gateways/pay_conex.rb b/lib/active_merchant/billing/gateways/pay_conex.rb new file mode 100644 index 00000000000..d0ae08146e8 --- /dev/null +++ b/lib/active_merchant/billing/gateways/pay_conex.rb @@ -0,0 +1,245 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class PayConexGateway < Gateway + include Empty + + self.test_url = 'https://cert.payconex.net/api/qsapi/3.8/' + self.live_url = 'https://secure.payconex.net/api/qsapi/3.8/' + + self.supported_countries = %w(US CA) + self.default_currency = 'USD' + self.supported_cardtypes = [:visa, :master, :american_express, :discover, :jcb, :diners_club] + + self.homepage_url = 'http://www.bluefincommerce.com/' + self.display_name = 'PayConex' + + def initialize(options={}) + requires!(options, :account_id, :api_accesskey) + super + end + + def purchase(money, payment_method, options={}) + post = {} + add_auth_purchase_params(post, money, payment_method, options) + commit('SALE', post) + end + + def authorize(money, payment_method, options={}) + post = {} + add_auth_purchase_params(post, money, payment_method, options) + commit('AUTHORIZATION', post) + end + + def capture(money, authorization, options={}) + post = {} + add_reference_params(post, authorization, options) + add_amount(post, money, options) + commit('CAPTURE', post) + end + + def refund(money, authorization, options={}) + post = {} + add_reference_params(post, authorization, options) + add_amount(post, money, options) + commit('REFUND', post) + end + + def void(authorization, options = {}) + post = {} + add_reference_params(post, authorization, options) + commit('REVERSAL', post) + end + + def credit(money, payment_method, options={}) + if payment_method.is_a?(String) + raise ArgumentError, 'Reference credits are not supported. Please supply the original credit card or use the #refund method.' + end + + post = {} + add_auth_purchase_params(post, money, payment_method, options) + commit('CREDIT', post) + end + + def verify(payment_method, options={}) + authorize(0, payment_method, options) + end + + def store(payment_method, options={}) + post = {} + add_credentials(post) + add_payment_method(post, payment_method) + add_address(post, options) + add_common_options(post, options) + commit('STORE', post) + end + + def supports_scrubbing? + true + end + + def scrub(transcript) + force_utf8(transcript). + gsub(%r((api_accesskey=)\w+), '\1[FILTERED]'). + gsub(%r((card_number=)\w+), '\1[FILTERED]'). + gsub(%r((card_verification=)\w+), '\1[FILTERED]') + end + + private + + def force_utf8(string) + return nil unless string + binary = string.encode('BINARY', invalid: :replace, undef: :replace, replace: '?') # Needed for Ruby 2.0 since #encode is a no-op if the string is already UTF-8. It's not needed for Ruby 2.1 and up since it's not a no-op there. + binary.encode('UTF-8', invalid: :replace, undef: :replace, replace: '?') + end + + def add_credentials(post) + post[:account_id] = @options[:account_id] + post[:api_accesskey] = @options[:api_accesskey] + end + + def add_auth_purchase_params(post, money, payment_method, options) + add_credentials(post) + add_payment_method(post, payment_method) + add_address(post, options) + add_common_options(post, options) + add_amount(post, money, options) + add_if_present(post, :email, options[:email]) + end + + def add_reference_params(post, authorization, options) + add_credentials(post) + add_common_options(post, options) + add_token_id(post, authorization) + end + + def add_amount(post, money, options) + post[:transaction_amount] = amount(money) + currency_code = (options[:currency] || currency(money)) + add_if_present(post, :currency, currency_code) + end + + def add_payment_method(post, payment_method) + case payment_method + when String + add_token_payment_method(post, payment_method) + when Check + add_check(post, payment_method) + else + if payment_method.respond_to?(:track_data) && payment_method.track_data.present? + add_card_present_payment_method(post, payment_method) + else + add_credit_card(post, payment_method) + end + end + end + + def add_credit_card(post, payment_method) + post[:tender_type] = 'CARD' + post[:card_number] = payment_method.number + post[:card_expiration] = expdate(payment_method) + post[:card_verification] = payment_method.verification_value + post[:first_name] = payment_method.first_name + post[:last_name] = payment_method.last_name + end + + def add_token_payment_method(post, payment_method) + post[:tender_type] = 'CARD' + post[:token_id] = payment_method + post[:reissue] = true + end + + def add_card_present_payment_method(post, payment_method) + post[:tender_type] = 'CARD' + post[:card_tracks] = payment_method.track_data + end + + def add_check(post, payment_method) + post[:tender_type] = 'ACH' + post[:first_name] = payment_method.first_name + post[:last_name] = payment_method.last_name + post[:bank_account_number] = payment_method.account_number + post[:bank_routing_number] = payment_method.routing_number + post[:check_number] = payment_method.number + add_if_present(post, :ach_account_type, payment_method.account_type) + end + + def add_address(post, options) + address = options[:billing_address] + return unless address + + add_if_present(post, :street_address1, address[:address1]) + add_if_present(post, :street_address2, address[:address2]) + add_if_present(post, :city, address[:city]) + add_if_present(post, :state, address[:state]) + add_if_present(post, :zip, address[:zip]) + add_if_present(post, :country, address[:country]) + add_if_present(post, :phone, address[:phone]) + end + + def add_common_options(post, options) + add_if_present(post, :transaction_description, options[:description]) + add_if_present(post, :custom_id, options[:custom_id]) + add_if_present(post, :custom_data, options[:custom_data]) + add_if_present(post, :ip_address, options[:ip]) + add_if_present(post, :payment_type, options[:payment_type]) + add_if_present(post, :cashier, options[:cashier]) + + post[:disable_cvv] = options[:disable_cvv] unless options[:disable_cvv].nil? + post[:response_format] = 'JSON' + end + + def add_if_present(post, key, value) + post[key] = value unless empty?(value) + end + + def add_token_id(post, authorization) + post[:token_id] = authorization + end + + def parse(body) + JSON.parse(body) + end + + def commit(action, params) + raw_response = ssl_post(url, post_data(action, params)) + response = parse(raw_response) + + Response.new( + success_from(response), + message_from(response), + response, + authorization: response['transaction_id'], + :avs_result => AVSResult.new(code: response['avs_response']), + :cvv_result => CVVResult.new(response['cvv2_response']), + test: test? + ) + rescue JSON::ParserError + unparsable_response(raw_response) + end + + def url + test? ? test_url : live_url + end + + def success_from(response) + response['transaction_approved'] || !response['error'] + end + + def message_from(response) + success_from(response) ? response['authorization_message'] : response['error_message'] + end + + def post_data(action, params) + params[:transaction_type] = action + params.map { |k, v| "#{k}=#{CGI.escape(v.to_s)}" }.join('&') + end + + def unparsable_response(raw_response) + message = 'Invalid JSON response received from PayConex. Please contact PayConex if you continue to receive this message.' + message += " (The raw response returned by the API was #{raw_response.inspect})" + return Response.new(false, message) + end + + end + end +end diff --git a/lib/active_merchant/billing/gateways/pay_gate_xml.rb b/lib/active_merchant/billing/gateways/pay_gate_xml.rb index 5f84e9f6d3f..35261aaf564 100644 --- a/lib/active_merchant/billing/gateways/pay_gate_xml.rb +++ b/lib/active_merchant/billing/gateways/pay_gate_xml.rb @@ -52,7 +52,7 @@ module Billing #:nodoc: # # PayGateXML Field ActiveMerchant Use # - # pgid use :login value to gateway instantation + # pgid use :login value to gateway instantiation # pwd use :password value to gateway instantiation # # cname credit_card.name @@ -102,45 +102,45 @@ class PayGateXmlGateway < Gateway DECLINE_CODES = { # Credit Card Errors - These RESULT_CODEs are returned if the transaction cannot be authorized due to a problem with the card. The TRANSACTION_STATUS will be 2 - 900001 => "Call for Approval", - 900002 => "Card Expired", - 900003 => "Insufficient Funds", - 900004 => "Invalid Card Number", - 900005 => "Bank Interface Timeout", # indicates a communications failure between the banks systems - 900006 => "Invalid Card", - 900007 => "Declined", - 900009 => "Lost Card", - 900010 => "Invalid Card Length", - 900011 => "Suspected Fraud", - 900012 => "Card Reported As Stolen", - 900013 => "Restricted Card", - 900014 => "Excessive Card Usage", - 900015 => "Card Blacklisted", - - 900207 => "Declined; authentication failed", # indicates the cardholder did not enter their MasterCard SecureCode / Verified by Visa password correctly - - 990020 => "Auth Declined", - - 991001 => "Invalid expiry date", - 991002 => "Invalid amount", + 900001 => 'Call for Approval', + 900002 => 'Card Expired', + 900003 => 'Insufficient Funds', + 900004 => 'Invalid Card Number', + 900005 => 'Bank Interface Timeout', # indicates a communications failure between the banks systems + 900006 => 'Invalid Card', + 900007 => 'Declined', + 900009 => 'Lost Card', + 900010 => 'Invalid Card Length', + 900011 => 'Suspected Fraud', + 900012 => 'Card Reported As Stolen', + 900013 => 'Restricted Card', + 900014 => 'Excessive Card Usage', + 900015 => 'Card Blacklisted', + + 900207 => 'Declined; authentication failed', # indicates the cardholder did not enter their MasterCard SecureCode / Verified by Visa password correctly + + 990020 => 'Auth Declined', + + 991001 => 'Invalid expiry date', + 991002 => 'Invalid amount', # Communication Errors - These RESULT_CODEs are returned if the transaction cannot be completed due to an unexpected error. TRANSACTION_STATUS will be 0. - 900205 => "Unexpected authentication result (phase 1)", - 900206 => "Unexpected authentication result (phase 1)", + 900205 => 'Unexpected authentication result (phase 1)', + 900206 => 'Unexpected authentication result (phase 1)', - 990001 => "Could not insert into Database", + 990001 => 'Could not insert into Database', - 990022 => "Bank not available", + 990022 => 'Bank not available', - 990053 => "Error processing transaction", + 990053 => 'Error processing transaction', # Miscellaneous - Unless otherwise noted, the TRANSACTION_STATUS will be 0. - 900209 => "Transaction verification failed (phase 2)", # Indicates the verification data returned from MasterCard SecureCode / Verified by Visa has been altered - 900210 => "Authentication complete; transaction must be restarted", # Indicates that the MasterCard SecuerCode / Verified by Visa transaction has already been completed. Most likely caused by the customer clicking the refresh button + 900209 => 'Transaction verification failed (phase 2)', # Indicates the verification data returned from MasterCard SecureCode / Verified by Visa has been altered + 900210 => 'Authentication complete; transaction must be restarted', # Indicates that the MasterCard SecuerCode / Verified by Visa transaction has already been completed. Most likely caused by the customer clicking the refresh button - 990024 => "Duplicate Transaction Detected. Please check before submitting", + 990024 => 'Duplicate Transaction Detected. Please check before submitting', - 990028 => "Transaction cancelled" # Customer clicks the 'Cancel' button on the payment page + 990028 => 'Transaction cancelled' # Customer clicks the 'Cancel' button on the payment page } SUCCESS_CODES = %w( 990004 990005 990017 990012 990018 990031 ) @@ -162,22 +162,32 @@ def initialize(options = {}) def purchase(money, creditcard, options = {}) MultiResponse.run do |r| - r.process{authorize(money, creditcard, options)} - r.process{capture(money, r.authorization, options)} + r.process { authorize(money, creditcard, options) } + r.process { capture(money, r.authorization, options) } end end def authorize(money, creditcard, options = {}) action = 'authtx' - options.merge!(:money => money, :creditcard => creditcard) + options[:money] = money + options[:creditcard] = creditcard commit(action, build_request(action, options)) end def capture(money, authorization, options = {}) action = 'settletx' - options.merge!(:money => money, :authorization => authorization) + options[:money] = money + options[:authorization] = authorization + commit(action, build_request(action, options), authorization) + end + + def refund(money, authorization, options={}) + action = 'refundtx' + + options[:money] = money + options[:authorization] = authorization commit(action, build_request(action, options)) end @@ -192,17 +202,18 @@ def build_request(action, options={}) xml.instruct! xml.tag! 'protocol', :ver => API_VERSION, :pgid => (test? ? TEST_ID : @options[:login]), :pwd => @options[:password] do |protocol| + money = options.delete(:money) + authorization = options.delete(:authorization) + creditcard = options.delete(:creditcard) case action - when 'authtx' - money = options.delete(:money) - creditcard = options.delete(:creditcard) - build_authorization(protocol, money, creditcard, options) - when 'settletx' - money = options.delete(:money) - authorization = options.delete(:authorization) - build_capture(protocol, money, authorization, options) + when 'authtx' + build_authorization(protocol, money, creditcard, options) + when 'settletx' + build_capture(protocol, money, authorization, options) + when 'refundtx' + build_refund(protocol, money, authorization, options) else - raise "no action specified for build_request" + raise 'no action specified for build_request' end end @@ -218,7 +229,9 @@ def build_authorization(xml, money, creditcard, options={}) :budp => 0, :amt => amount(money), :cur => (options[:currency] || currency(money)), - :cvv => creditcard.verification_value + :cvv => creditcard.verification_value, + :email => options[:email], + :ip => options[:ip] } end @@ -228,6 +241,13 @@ def build_capture(xml, money, authorization, options={}) } end + def build_refund(xml, money, authorization, options={}) + xml.tag! 'refundtx', { + :tid => authorization, + :amt => amount(money) + } + end + def parse(action, body) hash = {} xml = REXML::Document.new(body) @@ -244,11 +264,11 @@ def parse(action, body) hash end - def commit(action, request) + def commit(action, request, authorization = nil) response = parse(action, ssl_post(self.live_url, request)) Response.new(successful?(response), message_from(response), response, :test => test?, - :authorization => response[:tid] + :authorization => authorization || response[:tid] ) end @@ -258,4 +278,3 @@ def message_from(response) end end end - diff --git a/lib/active_merchant/billing/gateways/pay_hub.rb b/lib/active_merchant/billing/gateways/pay_hub.rb new file mode 100644 index 00000000000..fdcc6c5d600 --- /dev/null +++ b/lib/active_merchant/billing/gateways/pay_hub.rb @@ -0,0 +1,213 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class PayHubGateway < Gateway + self.live_url = 'https://checkout.payhub.com/transaction/api' + + self.supported_countries = ['US'] + self.default_currency = 'USD' + self.supported_cardtypes = [:visa, :master, :american_express, :discover] + + self.homepage_url = 'http://www.payhub.com/' + self.display_name = 'PayHub' + + CVV_CODE_TRANSLATOR = { + 'M' => 'CVV matches', + 'N' => 'CVV does not match', + 'P' => 'CVV not processed', + 'S' => 'CVV should have been present', + 'U' => 'CVV request unable to be processed by issuer' + } + + AVS_CODE_TRANSLATOR = { + '0' => 'Approved, Address verification was not requested.', + 'A' => 'Approved, Address matches only.', + 'B' => 'Address Match. Street Address math for international transaction Postal Code not verified because of incompatible formats (Acquirer sent both street address and Postal Code)', + 'C' => 'Serv Unavailable. Street address and Postal Code not verified for international transaction because of incompatible formats (Acquirer sent both street and Postal Code).', + 'D' => 'Exact Match, Street Address and Postal Code match for international transaction.', + 'F' => 'Exact Match, Street Address and Postal Code match. Applies to UK only.', + 'G' => 'Ver Unavailable, Non-U.S. Issuer does not participate.', + 'I' => 'Ver Unavailable, Address information not verified for international transaction', + 'M' => 'Exact Match, Street Address and Postal Code match for international transaction', + 'N' => 'No - Address and ZIP Code does not match', + 'P' => 'Zip Match, Postal Codes match for international transaction Street address not verified because of incompatible formats (Acquirer sent both street address and Postal Code).', + 'R' => 'Retry - Issuer system unavailable', + 'S' => 'Serv Unavailable, Service not supported', + 'U' => 'Ver Unavailable, Address unavailable.', + 'W' => 'ZIP match - Nine character numeric ZIP match only.', + 'X' => 'Exact match, Address and nine-character ZIP match.', + 'Y' => 'Exact Match, Address and five character ZIP match.', + 'Z' => 'Zip Match, Five character numeric ZIP match only.', + '1' => 'Cardholder name and ZIP match AMEX only.', + '2' => 'Cardholder name, address, and ZIP match AMEX only.', + '3' => 'Cardholder name and address match AMEX only.', + '4' => 'Cardholder name match AMEX only.', + '5' => 'Cardholder name incorrect, ZIP match AMEX only.', + '6' => 'Cardholder name incorrect, address and ZIP match AMEX only.', + '7' => 'Cardholder name incorrect, address match AMEX only.', + '8' => 'Cardholder, all do not match AMEX only.' + } + + STANDARD_ERROR_CODE_MAPPING = { + '14' => STANDARD_ERROR_CODE[:invalid_number], + '80' => STANDARD_ERROR_CODE[:invalid_expiry_date], + '82' => STANDARD_ERROR_CODE[:invalid_cvc], + '54' => STANDARD_ERROR_CODE[:expired_card], + '51' => STANDARD_ERROR_CODE[:card_declined], + '05' => STANDARD_ERROR_CODE[:card_declined], + '61' => STANDARD_ERROR_CODE[:card_declined], + '62' => STANDARD_ERROR_CODE[:card_declined], + '65' => STANDARD_ERROR_CODE[:card_declined], + '93' => STANDARD_ERROR_CODE[:card_declined], + '01' => STANDARD_ERROR_CODE[:call_issuer], + '02' => STANDARD_ERROR_CODE[:call_issuer], + '04' => STANDARD_ERROR_CODE[:pickup_card], + '07' => STANDARD_ERROR_CODE[:pickup_card], + '41' => STANDARD_ERROR_CODE[:pickup_card], + '43' => STANDARD_ERROR_CODE[:pickup_card] + } + + def initialize(options={}) + requires!(options, :orgid, :username, :password, :tid) + + super + end + + def authorize(amount, creditcard, options = {}) + post = setup_post('auth') + add_creditcard(post, creditcard) + add_amount(post, amount) + add_address(post, (options[:address] || options[:billing_address])) + add_customer_data(post, options) + + commit(post) + end + + def purchase(amount, creditcard, options={}) + post = setup_post('sale') + add_creditcard(post, creditcard) + add_amount(post, amount) + add_address(post, (options[:address] || options[:billing_address])) + add_customer_data(post, options) + + commit(post) + end + + def refund(amount, trans_id, options={}) + # Attempt a void in case the transaction is unsettled + post = setup_post('void') + add_reference(post, trans_id) + response = commit(post) + return response if response.success? + + post = setup_post('refund') + add_reference(post, trans_id) + commit(post) + end + + def capture(amount, trans_id, options = {}) + post = setup_post('capture') + + add_reference(post, trans_id) + add_amount(post, amount) + + commit(post) + end + + # No void, as PayHub's void does not work on authorizations + + def verify(creditcard, options={}) + authorize(100, creditcard, options) + end + + private + + def setup_post(action) + post = {} + post[:orgid] = @options[:orgid] + post[:tid] = @options[:tid] + post[:username] = @options[:username] + post[:password] = @options[:password] + post[:mode] = (test? ? 'demo' : 'live') + post[:trans_type] = action + post + end + + def add_reference(post, trans_id) + post[:trans_id] = trans_id + end + + def add_customer_data(post, options = {}) + post[:first_name] = options[:first_name] + post[:last_name] = options[:last_name] + post[:phone] = options[:phone] + post[:email] = options[:email] + end + + def add_address(post, address) + return unless address + post[:address1] = address[:address1] + post[:address2] = address[:address2] + post[:zip] = address[:zip] + post[:state] = address[:state] + post[:city] = address[:city] + end + + def add_amount(post, amount) + post[:amount] = amount(amount) + end + + def add_creditcard(post, creditcard) + post[:cc] = creditcard.number + post[:month] = creditcard.month.to_s + post[:year] = creditcard.year.to_s + post[:cvv] = creditcard.verification_value + end + + def parse(body) + JSON.parse(body) + end + + def commit(post) + success = false + + begin + raw_response = ssl_post(live_url, post.to_json, {'Content-Type' => 'application/json'}) + response = parse(raw_response) + success = (response['RESPONSE_CODE'] == '00') + rescue ResponseError => e + raw_response = e.response.body + response = response_error(raw_response) + rescue JSON::ParserError + response = json_error(raw_response) + end + + Response.new(success, + response_message(response), + response, + test: test?, + avs_result: {code: response['AVS_RESULT_CODE']}, + cvv_result: response['VERIFICATION_RESULT_CODE'], + error_code: (success ? nil : STANDARD_ERROR_CODE_MAPPING[response['RESPONSE_CODE']]), + authorization: response['TRANSACTION_ID'] + ) + end + + def response_error(raw_response) + parse(raw_response) + rescue JSON::ParserError + json_error(raw_response) + end + + def json_error(raw_response) + { + error_message: 'Invalid response received from the Payhub API. Please contact wecare@payhub.com if you continue to receive this message.' \ + " (The raw response returned by the API was #{raw_response.inspect})" + } + end + + def response_message(response) + (response['RESPONSE_TEXT'] || response['RESPONSE_CODE'] || response[:error_message]) + end + end + end +end diff --git a/lib/active_merchant/billing/gateways/pay_junction.rb b/lib/active_merchant/billing/gateways/pay_junction.rb index ceb2e184d93..68a24d6f8fc 100644 --- a/lib/active_merchant/billing/gateways/pay_junction.rb +++ b/lib/active_merchant/billing/gateways/pay_junction.rb @@ -73,16 +73,16 @@ module Billing #:nodoc: # # PayJunction Field ActiveMerchant Use # - # dc_logon provide as :login value to gateway instantation + # dc_logon provide as :login value to gateway instantiation # dc_password provide as :password value to gateway instantiation # # dc_name will be retrieved from credit_card.name - # dc_first_name :first_name on CreditCard object instantation - # dc_last_name :last_name on CreditCard object instantation - # dc_number :number on CreditCard object instantation - # dc_expiration_month :month on CreditCard object instantation - # dc_expiration_year :year on CreditCard object instantation - # dc_verification_number :verification_value on CC object instantation + # dc_first_name :first_name on CreditCard object instantiation + # dc_last_name :last_name on CreditCard object instantiation + # dc_number :number on CreditCard object instantiation + # dc_expiration_month :month on CreditCard object instantiation + # dc_expiration_year :year on CreditCard object instantiation + # dc_verification_number :verification_value on CC object instantiation # # dc_transaction_amount include as argument to method for your transaction type # dc_transaction_type do nothing, set by your transaction type @@ -101,19 +101,19 @@ class PayJunctionGateway < Gateway class_attribute :test_url, :live_url - self.test_url = "https://www.payjunctionlabs.com/quick_link" - self.live_url = "https://payjunction.com/quick_link" + self.test_url = 'https://www.payjunctionlabs.com/quick_link' + self.live_url = 'https://payjunction.com/quick_link' TEST_LOGIN = 'pj-ql-01' TEST_PASSWORD = 'pj-ql-01p' - SUCCESS_CODES = ["00", "85"] + SUCCESS_CODES = ['00', '85'] SUCCESS_MESSAGE = 'The transaction was approved.' FAILURE_MESSAGE = 'The transaction was declined.' DECLINE_CODES = { - "AE" => 'Address verification failed because address did not match.', + 'AE' => 'Address verification failed because address did not match.', 'ZE' => 'Address verification failed because zip did not match.', 'XE' => 'Address verification failed because zip and address did not match.', 'YE' => 'Address verification failed because zip and address did not match.', @@ -144,8 +144,8 @@ class PayJunctionGateway < Gateway '96' => 'Declined because of a system error.', 'N7' => 'Declined because of a CVV2/CVC2 mismatch.', 'M4' => 'Declined.', - "FE" => "There was a format error with your Trinity Gateway Service (API) request.", - "LE" => "Could not log you in (problem with dc_logon and/or dc_password).", + 'FE' => 'There was a format error with your Trinity Gateway Service (API) request.', + 'LE' => 'Could not log you in (problem with dc_logon and/or dc_password).', 'NL' => 'Aborted because of a system error, please try again later. ', 'AB' => 'Aborted because of an upstream system error, please try again later.' } @@ -211,7 +211,7 @@ def refund(money, authorization, options = {}) end def credit(money, authorization, options = {}) - deprecated CREDIT_DEPRECATION_MESSAGE + ActiveMerchant.deprecated CREDIT_DEPRECATION_MESSAGE refund(money, authorization, options) end @@ -239,6 +239,8 @@ def void(authorization, options = {}) # YYYYMMDD format and can be used to specify when the first charge will be made. # If omitted the first charge will be immediate. def recurring(money, payment_source, options = {}) + ActiveMerchant.deprecated RECURRING_DEPRECATION_MESSAGE + requires!(options, [:periodicity, :monthly, :weekly, :daily], :payments) periodic_type = case options[:periodicity] @@ -295,11 +297,15 @@ def add_payment_source(params, source) # add fields for credit card def add_creditcard(params, creditcard) - params[:name] = creditcard.name - params[:number] = creditcard.number - params[:expiration_month] = creditcard.month - params[:expiration_year] = creditcard.year - params[:verification_number] = creditcard.verification_value if creditcard.verification_value? + if creditcard.respond_to?(:track_data) && creditcard.track_data.present? + params[:track] = creditcard.track_data + else + params[:name] = creditcard.name + params[:number] = creditcard.number + params[:expiration_month] = creditcard.month + params[:expiration_year] = creditcard.year + params[:verification_number] = creditcard.verification_value if creditcard.verification_value? + end end # add field for "instant" transaction, using previous transaction id @@ -328,7 +334,7 @@ def add_optional_fields(params, options) def commit(action, parameters) url = test? ? self.test_url : self.live_url - response = parse( ssl_post(url, post_data(action, parameters)) ) + response = parse(ssl_post(url, post_data(action, parameters))) Response.new(successful?(response), message_from(response), response, :test => test?, @@ -360,7 +366,7 @@ def post_data(action, params) params[:version] = API_VERSION params[:transaction_type] = action - params.reject{|k,v| v.blank?}.collect{ |k, v| "dc_#{k.to_s}=#{CGI.escape(v.to_s)}" }.join("&") + params.reject { |k, v| v.blank? }.collect { |k, v| "dc_#{k}=#{CGI.escape(v.to_s)}" }.join('&') end def parse(body) @@ -379,18 +385,6 @@ def parse(body) end response end - - # Make a ruby type out of the response string - def normalize(field) - case field - when "true" then true - when "false" then false - when "" then nil - when "null" then nil - else field - end - end - end end end diff --git a/lib/active_merchant/billing/gateways/pay_junction_v2.rb b/lib/active_merchant/billing/gateways/pay_junction_v2.rb new file mode 100644 index 00000000000..aed266facd6 --- /dev/null +++ b/lib/active_merchant/billing/gateways/pay_junction_v2.rb @@ -0,0 +1,188 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class PayJunctionV2Gateway < Gateway + self.display_name = 'PayJunction' + self.homepage_url = 'https://www.payjunction.com/' + + self.test_url = 'https://api.payjunctionlabs.com/transactions' + self.live_url = 'https://api.payjunction.com/transactions' + + self.supported_countries = ['US'] + self.default_currency = 'USD' + self.money_format = :dollars + self.supported_cardtypes = [:visa, :master, :american_express, :discover] + + def initialize(options={}) + requires!(options, :api_login, :api_password, :api_key) + super + end + + def purchase(amount, payment_method, options={}) + post = {} + add_invoice(post, amount, options) + add_payment_method(post, payment_method) + + commit('purchase', post) + end + + def authorize(amount, payment_method, options={}) + post = {} + post[:status] = 'HOLD' + add_invoice(post, amount, options) + add_payment_method(post, payment_method) + + commit('authorize', post) + end + + def capture(amount, authorization, options={}) + post = {} + post[:status] = 'CAPTURE' + post[:transactionId] = authorization + add_invoice(post, amount, options) + + commit('capture', post) + end + + def void(authorization, options={}) + post = {} + post[:status] = 'VOID' + post[:transactionId] = authorization + + commit('void', post) + end + + def refund(amount, authorization, options={}) + post = {} + post[:action] = 'REFUND' + post[:transactionId] = authorization + add_invoice(post, amount, options) + + commit('refund', post) + end + + def credit(amount, payment_method, options={}) + post = {} + post[:action] = 'REFUND' + add_invoice(post, amount, options) + add_payment_method(post, payment_method) + + commit('credit', post) + end + + def verify(credit_card, options={}) + MultiResponse.run(:use_first_response) do |r| + r.process { authorize(100, credit_card, options) } + r.process(:ignore_result) { void(r.authorization, options) } + end + end + + def store(payment_method, options = {}) + verify(payment_method, options) + end + + def supports_scrubbing? + true + end + + def scrub(transcript) + transcript. + gsub(%r((Authorization: Basic )\w+), '\1[FILTERED]'). + gsub(%r((X-Pj-Application-Key: )[\w-]+), '\1[FILTERED]'). + gsub(%r((cardNumber=)\d+), '\1[FILTERED]'). + gsub(%r((cardCvv=)\d+), '\1[FILTERED]') + end + + private + + def add_invoice(post, money, options) + post[:amountBase] = amount(money) if money + post[:invoiceNumber] = options[:order_id] if options[:order_id] + end + + def add_payment_method(post, payment_method) + if payment_method.is_a? Integer + post[:transactionId] = payment_method + else + post[:cardNumber] = payment_method.number + post[:cardExpMonth] = format(payment_method.month, :two_digits) + post[:cardExpYear] = format(payment_method.year, :four_digits) + post[:cardCvv] = payment_method.verification_value + end + end + + def commit(action, params) + response = begin + parse(ssl_invoke(action, params)) + rescue ResponseError => e + parse(e.response.body) + end + + success = success_from(response) + Response.new( + success, + message_from(response), + response, + authorization: success ? authorization_from(response) : nil, + error_code: success ? nil : error_from(response), + test: test? + ) + end + + def ssl_invoke(action, params) + if ['purchase', 'authorize', 'refund', 'credit'].include?(action) + ssl_post(url(), post_data(params), headers) + else + ssl_request(:put, url(params), post_data(params), headers) + end + end + + def headers + { + 'Authorization' => 'Basic ' + Base64.encode64("#{@options[:api_login]}:#{@options[:api_password]}").strip, + 'Content-Type' => 'application/x-www-form-urlencoded;charset=UTF-8', + 'Accept' => 'application/json', + 'X-PJ-Application-Key' => @options[:api_key].to_s + } + end + + def post_data(params) + params.map { |k, v| "#{k}=#{CGI.escape(v.to_s)}" }.join('&') + end + + def url(params={}) + test? ? "#{test_url}/#{params[:transactionId]}" : "#{live_url}/#{params[:transactionId]}" + end + + def parse(body) + JSON.parse(body) + rescue JSON::ParserError + message = 'Invalid JSON response received from PayJunctionV2Gateway. Please contact PayJunctionV2Gateway if you continue to receive this message.' + message += " (The raw response returned by the API was #{body.inspect})" + { + 'errors' => [{ + 'message' => message + }] + } + end + + def success_from(response) + return response['response']['approved'] if response['response'] + false + end + + def message_from(response) + return response['response']['message'] if response['response'] + + response['errors']&.inject('') { |message, error| error['message'] + '|' + message } + end + + def authorization_from(response) + response['transactionId'] + end + + def error_from(response) + response['response']['code'] if response['response'] + end + end + end +end diff --git a/lib/active_merchant/billing/gateways/pay_secure.rb b/lib/active_merchant/billing/gateways/pay_secure.rb index 16df1f9fa84..76c13578379 100644 --- a/lib/active_merchant/billing/gateways/pay_secure.rb +++ b/lib/active_merchant/billing/gateways/pay_secure.rb @@ -39,9 +39,10 @@ def purchase(money, credit_card, options = {}) end private + # Used for capturing, which is currently not supported. def add_reference(post, identification) - auth, trans_id = identification.split(";") + auth, trans_id = identification.split(';') post[:authnum] = auth post[:transid] = trans_id end @@ -51,7 +52,7 @@ def add_amount(post, money) end def add_invoice(post, options) - post[:merchant_transid] = options[:order_id].to_s.slice(0,21) + post[:merchant_transid] = options[:order_id].to_s.slice(0, 21) post[:memnum] = options[:invoice] post[:custnum] = options[:customer] post[:clientdata] = options[:description] @@ -64,21 +65,13 @@ def add_credit_card(post, credit_card) post[:cvv2] = credit_card.verification_value end - def expdate(credit_card) - year = sprintf("%.4i", credit_card.year) - month = sprintf("%.2i", credit_card.month) - - "#{month}#{year[-2..-1]}" - end - def commit(action, money, parameters) - response = parse( ssl_post(self.live_url, post_data(action, parameters)) ) + response = parse(ssl_post(self.live_url, post_data(action, parameters))) Response.new(successful?(response), message_from(response), response, :test => test_response?(response), :authorization => authorization_from(response) ) - end def successful?(response) @@ -86,7 +79,7 @@ def successful?(response) end def authorization_from(response) - [ response[:authnum], response[:transid] ].compact.join(";") + [ response[:authnum], response[:transid] ].compact.join(';') end def test_response?(response) @@ -100,7 +93,7 @@ def message_from(response) def parse(body) response = {} body.to_s.each_line do |l| - key, value = l.split(":", 2) + key, value = l.split(':', 2) response[key.to_s.downcase.to_sym] = value.strip end response @@ -111,9 +104,8 @@ def post_data(action, parameters = {}) parameters[:merchant_id] = @options[:login] parameters[:password] = @options[:password] - parameters.reject{|k,v| v.blank?}.collect { |key, value| "#{key.to_s.upcase}=#{CGI.escape(value.to_s)}" }.join("&") + parameters.reject { |k, v| v.blank? }.collect { |key, value| "#{key.to_s.upcase}=#{CGI.escape(value.to_s)}" }.join('&') end end end end - diff --git a/lib/active_merchant/billing/gateways/paybox_direct.rb b/lib/active_merchant/billing/gateways/paybox_direct.rb index a3e3d70ae21..de236330928 100644 --- a/lib/active_merchant/billing/gateways/paybox_direct.rb +++ b/lib/active_merchant/billing/gateways/paybox_direct.rb @@ -21,20 +21,21 @@ class PayboxDirectGateway < Gateway } CURRENCY_CODES = { - "AUD"=> '036', - "CAD"=> '124', - "CZK"=> '203', - "DKK"=> '208', - "HKD"=> '344', - "ICK"=> '352', - "JPY"=> '392', - "NOK"=> '578', - "SGD"=> '702', - "SEK"=> '752', - "CHF"=> '756', - "GBP"=> '826', - "USD"=> '840', - "EUR"=> '978' + 'AUD'=> '036', + 'CAD'=> '124', + 'CZK'=> '203', + 'DKK'=> '208', + 'HKD'=> '344', + 'ICK'=> '352', + 'JPY'=> '392', + 'NOK'=> '578', + 'SGD'=> '702', + 'SEK'=> '752', + 'CHF'=> '756', + 'GBP'=> '826', + 'USD'=> '840', + 'EUR'=> '978', + 'XPF'=> '953' } SUCCESS_CODES = ['00000'] @@ -67,6 +68,8 @@ def authorize(money, creditcard, options = {}) post = {} add_invoice(post, options) add_creditcard(post, creditcard) + add_amount(post, money, options) + commit('authorization', money, post) end @@ -74,6 +77,8 @@ def purchase(money, creditcard, options = {}) post = {} add_invoice(post, options) add_creditcard(post, creditcard) + add_amount(post, money, options) + commit('purchase', money, post) end @@ -81,8 +86,10 @@ def capture(money, authorization, options = {}) requires!(options, :order_id) post = {} add_invoice(post, options) - post[:numappel] = authorization[0,10] - post[:numtrans] = authorization[10,10] + add_amount(post, money, options) + post[:numappel] = authorization[0, 10] + post[:numtrans] = authorization[10, 10] + commit('capture', money, post) end @@ -91,13 +98,15 @@ def void(identification, options = {}) post ={} add_invoice(post, options) add_reference(post, identification) + add_amount(post, options[:amount], options) post[:porteur] = '000000000000000' post[:dateval] = '0000' + commit('void', options[:amount], post) end def credit(money, identification, options = {}) - deprecated CREDIT_DEPRECATION_MESSAGE + ActiveMerchant.deprecated CREDIT_DEPRECATION_MESSAGE refund(money, identification, options) end @@ -105,6 +114,7 @@ def refund(money, identification, options = {}) post = {} add_invoice(post, options) add_reference(post, identification) + add_amount(post, money, options) commit('refund', money, post) end @@ -121,23 +131,26 @@ def add_creditcard(post, creditcard) end def add_reference(post, identification) - post[:numappel] = identification[0,10] - post[:numtrans] = identification[10,10] + post[:numappel] = identification[0, 10] + post[:numtrans] = identification[10, 10] + end + + def add_amount(post, money, options) + post[:montant] = ('0000000000' + (money ? amount(money) : ''))[-10..-1] + post[:devise] = CURRENCY_CODES[options[:currency] || currency(money)] end def parse(body) results = {} body.split(/&/).each do |pair| - key,val = pair.split(/\=/) + key, val = pair.split(/\=/) results[key.downcase.to_sym] = CGI.unescape(val) if val end results end def commit(action, money = nil, parameters = nil) - parameters[:montant] = ('0000000000' + (money ? amount(money) : ''))[-10..-1] - parameters[:devise] = CURRENCY_CODES[options[:currency] || currency(money)] - request_data = post_data(action,parameters) + request_data = post_data(action, parameters) response = parse(ssl_post(test? ? self.test_url : self.live_url, request_data)) response = parse(ssl_post(self.live_url_backup, request_data)) if service_unavailable?(response) && !test? Response.new(success?(response), message_from(response), response.merge( @@ -145,7 +158,7 @@ def commit(action, money = nil, parameters = nil) :test => test?, :authorization => response[:numappel].to_s + response[:numtrans].to_s, :fraud_review => false, - :sent_params => parameters.delete_if{|key,value| ['porteur','dateval','cvv'].include?(key.to_s)} + :sent_params => parameters.delete_if { |key, value| ['porteur', 'dateval', 'cvv'].include?(key.to_s) } ) end @@ -162,20 +175,19 @@ def message_from(response) end def post_data(action, parameters = {}) - parameters.update( :version => API_VERSION, :type => TRANSACTIONS[action.to_sym], :dateq => Time.now.strftime('%d%m%Y%H%M%S'), :numquestion => unique_id(parameters[:order_id]), - :site => @options[:login].to_s[0,7], - :rang => @options[:login].to_s[7..-1], + :site => @options[:login].to_s[0, 7], + :rang => @options[:rang] || @options[:login].to_s[7..-1], :cle => @options[:password], :pays => '', :archivage => parameters[:order_id] ) - parameters.collect { |key, value| "#{key.to_s.upcase}=#{CGI.escape(value.to_s)}" }.join("&") + parameters.collect { |key, value| "#{key.to_s.upcase}=#{CGI.escape(value.to_s)}" }.join('&') end def unique_id(seed = 0) @@ -183,14 +195,6 @@ def unique_id(seed = 0) "0000000000#{randkey}"[-10..-1] end - - def expdate(credit_card) - year = sprintf("%.4i", credit_card.year) - month = sprintf("%.2i", credit_card.month) - - "#{month}#{year[-2..-1]}" - end - end end end diff --git a/lib/active_merchant/billing/gateways/payeezy.rb b/lib/active_merchant/billing/gateways/payeezy.rb new file mode 100644 index 00000000000..b1ac0632f4c --- /dev/null +++ b/lib/active_merchant/billing/gateways/payeezy.rb @@ -0,0 +1,410 @@ +module ActiveMerchant + module Billing + class PayeezyGateway < Gateway + class_attribute :integration_url + + self.test_url = 'https://api-cert.payeezy.com/v1' + self.integration_url = 'https://api-cat.payeezy.com/v1' + self.live_url = 'https://api.payeezy.com/v1' + + self.default_currency = 'USD' + self.money_format = :cents + self.supported_countries = %w(US CA) + + self.supported_cardtypes = [:visa, :master, :american_express, :discover, :jcb, :diners_club] + + self.homepage_url = 'https://developer.payeezy.com/' + self.display_name = 'Payeezy' + + CREDIT_CARD_BRAND = { + 'visa' => 'Visa', + 'master' => 'Mastercard', + 'american_express' => 'American Express', + 'discover' => 'Discover', + 'jcb' => 'JCB', + 'diners_club' => 'Diners Club' + } + + def initialize(options = {}) + requires!(options, :apikey, :apisecret, :token) + super + end + + def purchase(amount, payment_method, options = {}) + params = payment_method.is_a?(String) ? { transaction_type: 'recurring' } : { transaction_type: 'purchase' } + + add_invoice(params, options) + add_reversal_id(params, options) + add_payment_method(params, payment_method, options) + add_address(params, options) + add_amount(params, amount, options) + add_soft_descriptors(params, options) + add_stored_credentials(params, options) + + commit(params, options) + end + + def authorize(amount, payment_method, options = {}) + params = {transaction_type: 'authorize'} + + add_invoice(params, options) + add_reversal_id(params, options) + add_payment_method(params, payment_method, options) + add_address(params, options) + add_amount(params, amount, options) + add_soft_descriptors(params, options) + add_stored_credentials(params, options) + + commit(params, options) + end + + def capture(amount, authorization, options = {}) + params = {transaction_type: 'capture'} + + add_authorization_info(params, authorization) + add_amount(params, amount, options) + add_soft_descriptors(params, options) + + commit(params, options) + end + + def refund(amount, authorization, options = {}) + params = {transaction_type: 'refund'} + + add_authorization_info(params, authorization) + add_amount(params, (amount || amount_from_authorization(authorization)), options) + + commit(params, options) + end + + def store(payment_method, options = {}) + params = {transaction_type: 'store'} + + add_creditcard_for_tokenization(params, payment_method, options) + + commit(params, options) + end + + def void(authorization, options = {}) + params = {transaction_type: 'void'} + + add_authorization_info(params, authorization, options) + add_amount(params, amount_from_authorization(authorization), options) + + commit(params, options) + end + + def verify(credit_card, options={}) + MultiResponse.run(:use_first_response) do |r| + r.process { authorize(0, credit_card, options) } + r.process(:ignore_result) { void(r.authorization, options) } + end + end + + def supports_scrubbing? + true + end + + def scrub(transcript) + transcript. + gsub(%r((Token: )(\w|-)+), '\1[FILTERED]'). + gsub(%r((Apikey: )(\w|-)+), '\1[FILTERED]'). + gsub(%r((\\?"card_number\\?":\\?")\d+), '\1[FILTERED]'). + gsub(%r((\\?"cvv\\?":\\?")\d+), '\1[FILTERED]'). + gsub(%r((\\?"account_number\\?":\\?")\d+), '\1[FILTERED]'). + gsub(%r((\\?"routing_number\\?":\\?")\d+), '\1[FILTERED]'). + gsub(%r((\\?card_number=)\d+(&?)), '\1[FILTERED]'). + gsub(%r((\\?cvv=)\d+(&?)), '\1[FILTERED]'). + gsub(%r((\\?apikey=)\w+(&?)), '\1[FILTERED]'). + gsub(%r{(\\?"credit_card\.card_number\\?":)(\\?"[^"]+\\?")}, '\1[FILTERED]'). + gsub(%r{(\\?"credit_card\.cvv\\?":)(\\?"[^"]+\\?")}, '\1[FILTERED]'). + gsub(%r{(\\?"apikey\\?":)(\\?"[^"]+\\?")}, '\1[FILTERED]') + end + + private + + def add_invoice(params, options) + params[:merchant_ref] = options[:order_id] + end + + def add_reversal_id(params, options) + params[:reversal_id] = options[:reversal_id] if options[:reversal_id] + end + + def amount_from_authorization(authorization) + authorization.split('|').last.to_i + end + + def add_authorization_info(params, authorization, options = {}) + transaction_id, transaction_tag, method, _ = authorization.split('|') + params[:method] = method == 'token' ? 'credit_card' : method + + if options[:reversal_id] + params[:reversal_id] = options[:reversal_id] + else + params[:transaction_id] = transaction_id + params[:transaction_tag] = transaction_tag + end + end + + def add_creditcard_for_tokenization(params, payment_method, options) + params[:apikey] = @options[:apikey] + params[:ta_token] = options[:ta_token] + params[:type] = 'FDToken' + params[:credit_card] = add_card_data(payment_method) + params[:auth] = 'false' + end + + def is_store_action?(params) + params[:transaction_type] == 'store' + end + + def add_payment_method(params, payment_method, options) + if payment_method.is_a? Check + add_echeck(params, payment_method, options) + elsif payment_method.is_a? String + add_token(params, payment_method, options) + else + add_creditcard(params, payment_method) + end + end + + def add_echeck(params, echeck, options) + tele_check = {} + + tele_check[:check_number] = echeck.number || '001' + tele_check[:check_type] = 'P' + tele_check[:routing_number] = echeck.routing_number + tele_check[:account_number] = echeck.account_number + tele_check[:accountholder_name] = "#{echeck.first_name} #{echeck.last_name}" + tele_check[:customer_id_type] = options[:customer_id_type] if options[:customer_id_type] + tele_check[:customer_id_number] = options[:customer_id_number] if options[:customer_id_number] + tele_check[:client_email] = options[:client_email] if options[:client_email] + + params[:method] = 'tele_check' + params[:tele_check] = tele_check + end + + def add_token(params, payment_method, options) + token = {} + token[:token_type] = 'FDToken' + + type, cardholder_name, exp_date, card_number = payment_method.split('|') + + token[:token_data] = {} + token[:token_data][:type] = type + token[:token_data][:cardholder_name] = cardholder_name + token[:token_data][:value] = card_number + token[:token_data][:exp_date] = exp_date + token[:token_data][:cvv] = options[:cvv] if options[:cvv] + + params[:method] = 'token' + params[:token] = token + end + + def add_creditcard(params, creditcard) + credit_card = add_card_data(creditcard) + + params[:method] = 'credit_card' + params[:credit_card] = credit_card + end + + def add_card_data(payment_method) + card = {} + card[:type] = CREDIT_CARD_BRAND[payment_method.brand] + card[:cardholder_name] = payment_method.name + card[:card_number] = payment_method.number + card[:exp_date] = format_exp_date(payment_method.month, payment_method.year) + card[:cvv] = payment_method.verification_value if payment_method.verification_value? + card + end + + def format_exp_date(month, year) + "#{format(month, :two_digits)}#{format(year, :two_digits)}" + end + + def add_address(params, options) + address = options[:billing_address] + return unless address + + billing_address = {} + billing_address[:street] = address[:address1] if address[:address1] + billing_address[:city] = address[:city] if address[:city] + billing_address[:state_province] = address[:state] if address[:state] + billing_address[:zip_postal_code] = address[:zip] if address[:zip] + billing_address[:country] = address[:country] if address[:country] + + params[:billing_address] = billing_address + end + + def add_amount(params, money, options) + params[:currency_code] = (options[:currency] || default_currency).upcase + params[:amount] = amount(money) + end + + def add_soft_descriptors(params, options) + params[:soft_descriptors] = options[:soft_descriptors] if options[:soft_descriptors] + end + + def add_stored_credentials(params, options) + if options[:sequence] + params[:stored_credentials] = {} + params[:stored_credentials][:cardbrand_original_transaction_id] = options[:cardbrand_original_transaction_id] if options[:cardbrand_original_transaction_id] + params[:stored_credentials][:sequence] = options[:sequence] + params[:stored_credentials][:initiator] = options[:initiator] if options[:initiator] + params[:stored_credentials][:is_scheduled] = options[:is_scheduled] + params[:stored_credentials][:auth_type_override] = options[:auth_type_override] if options[:auth_type_override] + end + end + + def commit(params, options) + url = base_url(options) + endpoint(params) + + if transaction_id = params.delete(:transaction_id) + url = "#{url}/#{transaction_id}" + end + + begin + response = api_request(url, params) + rescue ResponseError => e + response = response_error(e.response.body) + rescue JSON::ParserError + response = json_error(e.response.body) + end + + Response.new( + success_from(response), + handle_message(response, success_from(response)), + response, + test: test?, + authorization: authorization_from(params, response), + avs_result: {code: response['avs']}, + cvv_result: response['cvv2'], + error_code: error_code(response, success_from(response)) + ) + end + + def base_url(options) + if options[:integration] + integration_url + elsif test? + test_url + else + live_url + end + end + + def endpoint(params) + is_store_action?(params) ? '/transactions/tokens' : '/transactions' + end + + def api_request(url, params) + body = params.to_json + parse(ssl_post(url, body, headers(body))) + end + + def post_data(params) + return nil unless params + params.reject { |k, v| v.blank? }.collect { |k, v| "#{k}=#{CGI.escape(v.to_s)}" }.join('&') + end + + def generate_hmac(nonce, current_timestamp, payload) + message = [ + @options[:apikey], + nonce.to_s, + current_timestamp.to_s, + @options[:token], + payload + ].join('') + hash = Base64.strict_encode64(OpenSSL::HMAC.hexdigest('sha256', @options[:apisecret], message)) + hash + end + + def headers(payload) + nonce = (SecureRandom.random_number * 10_000_000_000) + current_timestamp = (Time.now.to_f * 1000).to_i + { + 'Content-Type' => 'application/json', + 'apikey' => options[:apikey], + 'token' => options[:token], + 'nonce' => nonce.to_s, + 'timestamp' => current_timestamp.to_s, + 'Authorization' => generate_hmac(nonce, current_timestamp, payload) + } + end + + def error_code(response, success) + return if success + response['Error'].to_h['messages'].to_a.map { |e| e['code'] }.join(', ') + end + + def success_from(response) + if response['transaction_status'] + response['transaction_status'] == 'approved' + elsif response['results'] + response['results']['status'] == 'success' + elsif response['status'] + response['status'] == 'success' + else + false + end + end + + def handle_message(response, success) + if success && response['status'].present? + 'Token successfully created.' + elsif success + "#{response['gateway_message']} - #{response['bank_message']}" + elsif %w(401 403).include?(response['code']) + response['message'] + elsif response.key?('Error') + response['Error']['messages'].first['description'] + elsif response.key?('results') + response['results']['Error']['messages'].first['description'] + elsif response.key?('error') + response['error'] + elsif response.key?('fault') + response['fault'].to_h['faultstring'] + else + response['bank_message'] || response['gateway_message'] || 'Failure to successfully create token.' + end + end + + def authorization_from(params, response) + if is_store_action?(params) + if success_from(response) + [ + response['token']['type'], + response['token']['cardholder_name'], + response['token']['exp_date'], + response['token']['value'] + ].join('|') + else + nil + end + else + [ + response['transaction_id'], + response['transaction_tag'], + params[:method], + response['amount']&.to_i + ].join('|') + end + end + + def parse(body) + JSON.parse(body) + end + + def response_error(raw_response) + parse(raw_response) + rescue JSON::ParserError + json_error(raw_response) + end + + def json_error(raw_response) + {'error' => "Unable to parse response: #{raw_response.inspect}"} + end + end + end +end diff --git a/lib/active_merchant/billing/gateways/payex.rb b/lib/active_merchant/billing/gateways/payex.rb new file mode 100644 index 00000000000..c43e36bb13f --- /dev/null +++ b/lib/active_merchant/billing/gateways/payex.rb @@ -0,0 +1,410 @@ +require 'nokogiri' + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class PayexGateway < Gateway + class_attribute :live_external_url, :test_external_url, :live_confined_url, :test_confined_url + + self.live_external_url = 'https://external.payex.com/' + self.test_external_url = 'https://test-external.payex.com/' + + self.live_confined_url = 'https://confined.payex.com/' + self.test_confined_url = 'https://test-confined.payex.com/' + + self.money_format = :cents + self.supported_countries = ['DK', 'FI', 'NO', 'SE'] + self.supported_cardtypes = [:visa, :master, :american_express, :discover] + self.homepage_url = 'http://payex.com/' + self.display_name = 'Payex' + self.default_currency = 'EUR' + + TRANSACTION_STATUS = { + sale: '0', + initialize: '1', + credit: '2', + authorize: '3', + cancel: '4', + failure: '5', + capture: '6', + } + + SOAP_ACTIONS = { + initialize: { name: 'Initialize8', url: 'pxorder/pxorder.asmx', xmlns: 'http://external.payex.com/PxOrder/' }, + purchasecc: { name: 'PurchaseCC', url: 'pxconfined/pxorder.asmx', xmlns: 'http://confined.payex.com/PxOrder/', confined: true}, + cancel: { name: 'Cancel2', url: 'pxorder/pxorder.asmx', xmlns: 'http://external.payex.com/PxOrder/' }, + capture: { name: 'Capture5', url: 'pxorder/pxorder.asmx', xmlns: 'http://external.payex.com/PxOrder/' }, + credit: { name: 'Credit5', url: 'pxorder/pxorder.asmx', xmlns: 'http://external.payex.com/PxOrder/' }, + create_agreement: { name: 'CreateAgreement3', url: 'pxagreement/pxagreement.asmx', xmlns: 'http://external.payex.com/PxAgreement/' }, + delete_agreement: { name: 'DeleteAgreement', url: 'pxagreement/pxagreement.asmx', xmlns: 'http://external.payex.com/PxAgreement/' }, + autopay: { name: 'AutoPay3', url: 'pxagreement/pxagreement.asmx', xmlns: 'http://external.payex.com/PxAgreement/' }, + } + + def initialize(options = {}) + requires!(options, :account, :encryption_key) + super + end + + # Public: Send an authorize Payex request + # + # amount - The monetary amount of the transaction in cents. + # payment_method - The Active Merchant payment method or the +store+ authorization for stored transactions. + # options - A standard ActiveMerchant options hash: + # :currency - Three letter currency code for the transaction (default: "EUR") + # :order_id - The unique order ID for this transaction (required). + # :product_number - The merchant product number (default: '1'). + # :description - The merchant description for this product (default: The :order_id). + # :ip - The client IP address (default: '127.0.0.1'). + # :vat - The vat amount (optional). + # + # Returns an ActiveMerchant::Billing::Response object + def authorize(amount, payment_method, options = {}) + requires!(options, :order_id) + amount = amount(amount) + if payment_method.respond_to?(:number) + # credit card authorization + MultiResponse.new.tap do |r| + r.process { send_initialize(amount, true, options) } + r.process { send_purchasecc(payment_method, r.params['orderref']) } + end + else + # stored authorization + send_autopay(amount, payment_method, true, options) + end + end + + # Public: Send a purchase Payex request + # + # amount - The monetary amount of the transaction in cents. + # payment_method - The Active Merchant payment method or the +store+ authorization for stored transactions. + # options - A standard ActiveMerchant options hash: + # :currency - Three letter currency code for the transaction (default: "EUR") + # :order_id - The unique order ID for this transaction (required). + # :product_number - The merchant product number (default: '1'). + # :description - The merchant description for this product (default: The :order_id). + # :ip - The client IP address (default: '127.0.0.1'). + # :vat - The vat amount (optional). + # + # Returns an ActiveMerchant::Billing::Response object + def purchase(amount, payment_method, options = {}) + requires!(options, :order_id) + amount = amount(amount) + if payment_method.respond_to?(:number) + # credit card purchase + MultiResponse.new.tap do |r| + r.process { send_initialize(amount, false, options) } + r.process { send_purchasecc(payment_method, r.params['orderref']) } + end + else + # stored purchase + send_autopay(amount, payment_method, false, options) + end + end + + # Public: Capture money from a previously authorized transaction + # + # money - The amount to capture + # authorization - The authorization token from the authorization request + # + # Returns an ActiveMerchant::Billing::Response object + def capture(money, authorization, options = {}) + amount = amount(money) + send_capture(amount, authorization) + end + + # Public: Voids an authorize transaction + # + # authorization - The authorization returned from the successful authorize transaction. + # options - A standard ActiveMerchant options hash + # + # Returns an ActiveMerchant::Billing::Response object + def void(authorization, options={}) + send_cancel(authorization) + end + + # Public: Refunds a purchase transaction + # + # money - The amount to refund + # authorization - The authorization token from the purchase request. + # options - A standard ActiveMerchant options hash: + # :order_id - The unique order ID for this transaction (required). + # :vat_amount - The vat amount (optional). + # + # Returns an ActiveMerchant::Billing::Response object + def refund(money, authorization, options = {}) + requires!(options, :order_id) + amount = amount(money) + send_credit(authorization, amount, options) + end + + # Public: Stores a credit card and creates a Payex agreement with a customer + # + # creditcard - The credit card to store. + # options - A standard ActiveMerchant options hash: + # :order_id - The unique order ID for this transaction (required). + # :merchant_ref - A reference that links this agreement to something the merchant takes money for (default: '1') + # :currency - Three letter currency code for the transaction (default: "EUR") + # :product_number - The merchant product number (default: '1'). + # :description - The merchant description for this product (default: The :order_id). + # :ip - The client IP address (default: '127.0.0.1'). + # :max_amount - The maximum amount to allow to be charged (default: 100000). + # :vat - The vat amount (optional). + # + # Returns an ActiveMerchant::Billing::Response object where the authorization is set to the agreement_ref which is used for stored payments. + def store(creditcard, options = {}) + requires!(options, :order_id) + amount = amount(1) # 1 cent for authorization + MultiResponse.run(:first) do |r| + r.process { send_create_agreement(options) } + r.process { send_initialize(amount, true, options.merge({agreement_ref: r.authorization})) } + order_ref = r.params['orderref'] + r.process { send_purchasecc(creditcard, order_ref) } + end + end + + # Public: Unstores a customer's credit card and deletes their Payex agreement. + # + # authorization - The authorization token from the store request. + # + # Returns an ActiveMerchant::Billing::Response object + def unstore(authorization, options = {}) + send_delete_agreement(authorization) + end + + private + + def send_initialize(amount, is_auth, options = {}) + properties = { + accountNumber: @options[:account], + purchaseOperation: is_auth ? 'AUTHORIZATION' : 'SALE', + price: amount, + priceArgList: nil, + currency: (options[:currency] || default_currency), + vat: options[:vat] || 0, + orderID: options[:order_id], + productNumber: options[:product_number] || '1', + description: options[:description] || options[:order_id], + clientIPAddress: options[:client_ip_address] || '127.0.0.1', + clientIdentifier: nil, + additionalValues: nil, + externalID: nil, + returnUrl: 'http://example.net', # set to dummy value since this is not used but is required + view: 'CREDITCARD', + agreementRef: options[:agreement_ref], # this is used to attach a stored agreement to a transaction as part of the store card + cancelUrl: nil, + clientLanguage: nil + } + hash_fields = [:accountNumber, :purchaseOperation, :price, :priceArgList, :currency, :vat, :orderID, + :productNumber, :description, :clientIPAddress, :clientIdentifier, :additionalValues, + :externalID, :returnUrl, :view, :agreementRef, :cancelUrl, :clientLanguage] + add_request_hash(properties, hash_fields) + soap_action = SOAP_ACTIONS[:initialize] + request = build_xml_request(soap_action, properties) + commit(soap_action, request) + end + + def send_purchasecc(payment_method, order_ref) + properties = { + accountNumber: @options[:account], + orderRef: order_ref, + transactionType: 1, # online payment + cardNumber: payment_method.number, + cardNumberExpireMonth: format(payment_method.month, :two_digits), + cardNumberExpireYear: format(payment_method.year, :two_digits), + cardHolderName: payment_method.name, + cardNumberCVC: payment_method.verification_value + } + hash_fields = [:accountNumber, :orderRef, :transactionType, :cardNumber, :cardNumberExpireMonth, + :cardNumberExpireYear, :cardNumberCVC, :cardHolderName] + add_request_hash(properties, hash_fields) + + soap_action = SOAP_ACTIONS[:purchasecc] + request = build_xml_request(soap_action, properties) + commit(soap_action, request) + end + + def send_autopay(amount, authorization, is_auth, options = {}) + properties = { + accountNumber: @options[:account], + agreementRef: authorization, + price: amount, + productNumber: options[:product_number] || '1', + description: options[:description] || options[:order_id], + orderId: options[:order_id], + purchaseOperation: is_auth ? 'AUTHORIZATION' : 'SALE', + currency: (options[:currency] || default_currency), + } + hash_fields = [:accountNumber, :agreementRef, :price, :productNumber, :description, :orderId, :purchaseOperation, :currency] + add_request_hash(properties, hash_fields) + + soap_action = SOAP_ACTIONS[:autopay] + request = build_xml_request(soap_action, properties) + commit(soap_action, request) + end + + def send_capture(amount, transaction_number, options = {}) + properties = { + accountNumber: @options[:account], + transactionNumber: transaction_number, + amount: amount, + orderId: options[:order_id] || '', + vatAmount: options[:vat_amount] || 0, + additionalValues: '' + } + hash_fields = [:accountNumber, :transactionNumber, :amount, :orderId, :vatAmount, :additionalValues] + add_request_hash(properties, hash_fields) + + soap_action = SOAP_ACTIONS[:capture] + request = build_xml_request(soap_action, properties) + commit(soap_action, request) + end + + def send_credit(transaction_number, amount, options = {}) + properties = { + accountNumber: @options[:account], + transactionNumber: transaction_number, + amount: amount, + orderId: options[:order_id], + vatAmount: options[:vat_amount] || 0, + additionalValues: '' + } + hash_fields = [:accountNumber, :transactionNumber, :amount, :orderId, :vatAmount, :additionalValues] + add_request_hash(properties, hash_fields) + + soap_action = SOAP_ACTIONS[:credit] + request = build_xml_request(soap_action, properties) + commit(soap_action, request) + end + + def send_cancel(transaction_number) + properties = { + accountNumber: @options[:account], + transactionNumber: transaction_number, + } + hash_fields = [:accountNumber, :transactionNumber] + add_request_hash(properties, hash_fields) + + soap_action = SOAP_ACTIONS[:cancel] + request = build_xml_request(soap_action, properties) + commit(soap_action, request) + end + + def send_create_agreement(options) + properties = { + accountNumber: @options[:account], + merchantRef: options[:merchant_ref] || '1', + description: options[:description] || options[:order_id], + purchaseOperation: 'SALE', + maxAmount: options[:max_amount] || 100000, # default to 1,000 + notifyUrl: '', + startDate: options[:startDate] || '', + stopDate: options[:stopDate] || '' + } + hash_fields = [:accountNumber, :merchantRef, :description, :purchaseOperation, :maxAmount, :notifyUrl, :startDate, :stopDate] + add_request_hash(properties, hash_fields) + + soap_action = SOAP_ACTIONS[:create_agreement] + request = build_xml_request(soap_action, properties) + commit(soap_action, request) + end + + def send_delete_agreement(authorization) + properties = { + accountNumber: @options[:account], + agreementRef: authorization, + } + hash_fields = [:accountNumber, :agreementRef] + add_request_hash(properties, hash_fields) + + soap_action = SOAP_ACTIONS[:delete_agreement] + request = build_xml_request(soap_action, properties) + commit(soap_action, request) + end + + def url_for(soap_action) + File.join(base_url(soap_action), soap_action[:url]) + end + + def base_url(soap_action) + if soap_action[:confined] + test? ? test_confined_url : live_confined_url + else + test? ? test_external_url : live_external_url + end + end + + # this will add a hash to the passed in properties as required by Payex requests + def add_request_hash(properties, fields) + data = fields.map { |e| properties[e] } + data << @options[:encryption_key] + properties['hash_'] = Digest::MD5.hexdigest(data.join('')) + end + + def build_xml_request(soap_action, properties) + builder = Nokogiri::XML::Builder.new + builder.__send__('soap12:Envelope', {'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance', + 'xmlns:xsd' => 'http://www.w3.org/2001/XMLSchema', + 'xmlns:soap12' => 'http://www.w3.org/2003/05/soap-envelope'}) do |root| + root.__send__('soap12:Body') do |body| + body.__send__(soap_action[:name], xmlns: soap_action[:xmlns]) do |doc| + properties.each do |key, val| + doc.send(key, val) + end + end + end + end + builder.to_xml + end + + def parse(xml) + response = {} + + xmldoc = Nokogiri::XML(xml) + body = xmldoc.xpath('//soap:Body/*[1]')[0].inner_text + + doc = Nokogiri::XML(body) + + doc.root&.xpath('*')&.each do |node| + if node.elements.size == 0 + response[node.name.downcase.to_sym] = node.text + else + node.elements.each do |childnode| + name = "#{node.name.downcase}_#{childnode.name.downcase}" + response[name.to_sym] = childnode.text + end + end + end + + response + end + + # Commits all requests to the Payex soap endpoint + def commit(soap_action, request) + url = url_for(soap_action) + headers = { + 'Content-Type' => 'application/soap+xml; charset=utf-8', + 'Content-Length' => request.size.to_s + } + response = parse(ssl_post(url, request, headers)) + Response.new(success?(response), + message_from(response), + response, + test: test?, + authorization: build_authorization(response) + ) + end + + def build_authorization(response) + # agreementref is for the store transaction, everything else gets transactionnumber + response[:transactionnumber] || response[:agreementref] + end + + def success?(response) + response[:status_errorcode] == 'OK' && response[:transactionstatus] != TRANSACTION_STATUS[:failure] + end + + def message_from(response) + response[:status_description] + end + end + end +end diff --git a/lib/active_merchant/billing/gateways/payflow.rb b/lib/active_merchant/billing/gateways/payflow.rb index 770c426d8fe..709cd19ef45 100644 --- a/lib/active_merchant/billing/gateways/payflow.rb +++ b/lib/active_merchant/billing/gateways/payflow.rb @@ -1,6 +1,7 @@ -require File.dirname(__FILE__) + '/payflow/payflow_common_api' -require File.dirname(__FILE__) + '/payflow/payflow_response' -require File.dirname(__FILE__) + '/payflow_express' +require 'nokogiri' +require 'active_merchant/billing/gateways/payflow/payflow_common_api' +require 'active_merchant/billing/gateways/payflow/payflow_response' +require 'active_merchant/billing/gateways/payflow_express' module ActiveMerchant #:nodoc: module Billing #:nodoc: @@ -19,20 +20,23 @@ def authorize(money, credit_card_or_reference, options = {}) commit(request, options) end - def purchase(money, credit_card_or_reference, options = {}) - request = build_sale_or_authorization_request(:purchase, money, credit_card_or_reference, options) + def purchase(money, funding_source, options = {}) + request = build_sale_or_authorization_request(:purchase, money, funding_source, options) commit(request, options) end - def credit(money, identification_or_credit_card, options = {}) - if identification_or_credit_card.is_a?(String) - deprecated CREDIT_DEPRECATION_MESSAGE + def credit(money, funding_source, options = {}) + if funding_source.is_a?(String) + ActiveMerchant.deprecated CREDIT_DEPRECATION_MESSAGE # Perform referenced credit - refund(money, identification_or_credit_card, options) - else + refund(money, funding_source, options) + elsif card_brand(funding_source) == 'check' # Perform non-referenced credit - request = build_credit_card_request(:credit, money, identification_or_credit_card, options) + request = build_check_request(:credit, money, funding_source, options) + commit(request, options) + else + request = build_credit_card_request(:credit, money, funding_source, options) commit(request, options) end end @@ -41,6 +45,22 @@ def refund(money, reference, options = {}) commit(build_reference_request(:credit, money, reference, options), options) end + def verify(payment, options={}) + if credit_card_type(payment) == 'Amex' + MultiResponse.run(:use_first_response) do |r| + r.process { authorize(100, payment, options) } + r.process(:ignore_result) { void(r.authorization, options) } + end + else + authorize(0, payment, options) + end + end + + def verify_credentials + response = void('0') + response.params['result'] != '26' + end + # Adds or modifies a recurring Payflow profile. See the Payflow Pro Recurring Billing Guide for more details: # https://www.paypal.com/en_US/pdf/PayflowPro_RecurringBilling_Guide.pdf # @@ -54,20 +74,26 @@ def refund(money, reference, options = {}) # * payments - The term, or number of payments that will be made # * comment - A comment associated with the profile def recurring(money, credit_card, options = {}) + ActiveMerchant.deprecated RECURRING_DEPRECATION_MESSAGE + options[:name] = credit_card.name if options[:name].blank? && credit_card request = build_recurring_request(options[:profile_id] ? :modify : :add, money, options) do |xml| - add_credit_card(xml, credit_card) if credit_card + add_credit_card(xml, credit_card, options) if credit_card end commit(request, options.merge(:request_type => :recurring)) end def cancel_recurring(profile_id) + ActiveMerchant.deprecated RECURRING_DEPRECATION_MESSAGE + request = build_recurring_request(:cancel, 0, :profile_id => profile_id) commit(request, options.merge(:request_type => :recurring)) end def recurring_inquiry(profile_id, options = {}) - request = build_recurring_request(:inquiry, nil, options.update( :profile_id => profile_id )) + ActiveMerchant.deprecated RECURRING_DEPRECATION_MESSAGE + + request = build_recurring_request(:inquiry, nil, options.update(:profile_id => profile_id)) commit(request, options.merge(:request_type => :recurring)) end @@ -75,12 +101,27 @@ def express @express ||= PayflowExpressGateway.new(@options) end + def supports_scrubbing? + true + end + + def scrub(transcript) + transcript. + gsub(%r(()[^<]*()), '\1[FILTERED]\2'). + gsub(%r(()[^<]*()), '\1[FILTERED]\2'). + gsub(%r(()[^<]*()), '\1[FILTERED]\2'). + gsub(%r(()[^<]*()), '\1[FILTERED]\2') + end + private - def build_sale_or_authorization_request(action, money, credit_card_or_reference, options) - if credit_card_or_reference.is_a?(String) - build_reference_sale_or_authorization_request(action, money, credit_card_or_reference, options) + + def build_sale_or_authorization_request(action, money, funding_source, options) + if funding_source.is_a?(String) + build_reference_sale_or_authorization_request(action, money, funding_source, options) + elsif card_brand(funding_source) == 'check' + build_check_request(action, money, funding_source, options) else - build_credit_card_request(action, money, credit_card_or_reference, options) + build_credit_card_request(action, money, funding_source, options) end end @@ -93,6 +134,7 @@ def build_reference_sale_or_authorization_request(action, money, reference, opti xml.tag! 'CustIP', options[:ip] unless options[:ip].blank? xml.tag! 'InvNum', options[:order_id].to_s.gsub(/[^\w.]/, '') unless options[:order_id].blank? xml.tag! 'Description', options[:description] unless options[:description].blank? + xml.tag! 'OrderDesc', options[:order_desc] unless options[:order_desc].blank? xml.tag! 'Comment', options[:comment] unless options[:comment].blank? xml.tag!('ExtData', 'Name'=> 'COMMENT2', 'Value'=> options[:comment2]) unless options[:comment2].blank? xml.tag! 'TaxAmt', options[:taxamt] unless options[:taxamt].blank? @@ -102,7 +144,7 @@ def build_reference_sale_or_authorization_request(action, money, reference, opti billing_address = options[:billing_address] || options[:address] add_address(xml, 'BillTo', billing_address, options) if billing_address - add_address(xml, 'ShipTo', options[:shipping_address],options) if options[:shipping_address] + add_address(xml, 'ShipTo', options[:shipping_address], options) if options[:shipping_address] xml.tag! 'TotalAmt', amount(money), 'Currency' => options[:currency] || currency(money) end @@ -124,6 +166,7 @@ def build_credit_card_request(action, money, credit_card, options) xml.tag! 'CustIP', options[:ip] unless options[:ip].blank? xml.tag! 'InvNum', options[:order_id].to_s.gsub(/[^\w.]/, '') unless options[:order_id].blank? xml.tag! 'Description', options[:description] unless options[:description].blank? + xml.tag! 'OrderDesc', options[:order_desc] unless options[:order_desc].blank? # Comment and Comment2 will show up in manager.paypal.com as Comment1 and Comment2 xml.tag! 'Comment', options[:comment] unless options[:comment].blank? xml.tag!('ExtData', 'Name'=> 'COMMENT2', 'Value'=> options[:comment2]) unless options[:comment2].blank? @@ -131,6 +174,7 @@ def build_credit_card_request(action, money, credit_card, options) xml.tag! 'FreightAmt', options[:freightamt] unless options[:freightamt].blank? xml.tag! 'DutyAmt', options[:dutyamt] unless options[:dutyamt].blank? xml.tag! 'DiscountAmt', options[:discountamt] unless options[:discountamt].blank? + xml.tag! 'EMail', options[:email] unless options[:email].nil? billing_address = options[:billing_address] || options[:address] add_address(xml, 'BillTo', billing_address, options) if billing_address @@ -140,14 +184,77 @@ def build_credit_card_request(action, money, credit_card, options) end xml.tag! 'Tender' do - add_credit_card(xml, credit_card) + add_credit_card(xml, credit_card, options) end end end - xml.target! + add_level_two_three_fields(xml.target!, options) end - def add_credit_card(xml, credit_card) + def add_level_two_three_fields(xml_string, options) + if options[:level_two_fields] || options[:level_three_fields] + xml_doc = Nokogiri::XML.parse(xml_string) + %i[level_two_fields level_three_fields].each do |fields| + xml_string = add_fields(xml_doc, options[fields]) if options[fields] + end + end + xml_string + end + + def check_fields(parent, fields, xml_doc) + fields.each do |k, v| + if v.is_a? String + new_node = Nokogiri::XML::Node.new(k, xml_doc) + new_node.add_child(v) + xml_doc.at_css(parent).add_child(new_node) + else + check_subparent_before_continuing(parent, k, xml_doc) + check_fields(k, v, xml_doc) + end + end + xml_doc + end + + def check_subparent_before_continuing(parent, subparent, xml_doc) + unless xml_doc.at_css(subparent) + subparent_node = Nokogiri::XML::Node.new(subparent, xml_doc) + xml_doc.at_css(parent).add_child(subparent_node) + end + end + + def add_fields(xml_doc, options_fields) + fields_to_add = JSON.parse(options_fields) + check_fields('Invoice', fields_to_add, xml_doc) + xml_doc.root.to_s + end + + def build_check_request(action, money, check, options) + xml = Builder::XmlMarkup.new + xml.tag! TRANSACTIONS[action] do + xml.tag! 'PayData' do + xml.tag! 'Invoice' do + xml.tag! 'CustIP', options[:ip] unless options[:ip].blank? + xml.tag! 'InvNum', options[:order_id].to_s.gsub(/[^\w.]/, '') unless options[:order_id].blank? + xml.tag! 'Description', options[:description] unless options[:description].blank? + xml.tag! 'OrderDesc', options[:order_desc] unless options[:order_desc].blank? + xml.tag! 'BillTo' do + xml.tag! 'Name', check.name + end + xml.tag! 'TotalAmt', amount(money), 'Currency' => options[:currency] || currency(money) + end + xml.tag! 'Tender' do + xml.tag! 'ACH' do + xml.tag! 'AcctType', check.account_type == 'checking' ? 'C' : 'S' + xml.tag! 'AcctNum', check.account_number + xml.tag! 'ABA', check.routing_number + end + end + end + end + add_level_two_three_fields(xml.target!, options) + end + + def add_credit_card(xml, credit_card, options = {}) xml.tag! 'Card' do xml.tag! 'CardType', credit_card_type(credit_card) xml.tag! 'CardNum', credit_card.number @@ -155,10 +262,19 @@ def add_credit_card(xml, credit_card) xml.tag! 'NameOnCard', credit_card.first_name xml.tag! 'CVNum', credit_card.verification_value if credit_card.verification_value? - if requires_start_date_or_issue_number?(credit_card) - xml.tag!('ExtData', 'Name' => 'CardStart', 'Value' => startdate(credit_card)) unless credit_card.start_month.blank? || credit_card.start_year.blank? - xml.tag!('ExtData', 'Name' => 'CardIssue', 'Value' => format(credit_card.issue_number, :two_digits)) unless credit_card.issue_number.blank? + if options[:three_d_secure] + three_d_secure = options[:three_d_secure] + xml.tag! 'BuyerAuthResult' do + xml.tag! 'Status', three_d_secure[:status] unless three_d_secure[:status].blank? + xml.tag! 'AuthenticationId', three_d_secure[:authentication_id] unless three_d_secure[:authentication_id].blank? + xml.tag! 'PAReq', three_d_secure[:pareq] unless three_d_secure[:pareq].blank? + xml.tag! 'ACSUrl', three_d_secure[:acs_url] unless three_d_secure[:acs_url].blank? + xml.tag! 'ECI', three_d_secure[:eci] unless three_d_secure[:eci].blank? + xml.tag! 'CAVV', three_d_secure[:cavv] unless three_d_secure[:cavv].blank? + xml.tag! 'XID', three_d_secure[:xid] unless three_d_secure[:xid].blank? + end end + xml.tag! 'ExtData', 'Name' => 'LASTNAME', 'Value' => credit_card.last_name end end @@ -170,8 +286,8 @@ def credit_card_type(credit_card) end def expdate(creditcard) - year = sprintf("%.4i", creditcard.year.to_s.sub(/^0+/, '')) - month = sprintf("%.2i", creditcard.month.to_s.sub(/^0+/, '')) + year = sprintf('%.4i', creditcard.year.to_s.sub(/^0+/, '')) + month = sprintf('%.2i', creditcard.month.to_s.sub(/^0+/, '')) "#{year}#{month}" end @@ -211,7 +327,7 @@ def build_recurring_request(action, money, options) end if action == :add - xml.tag! 'Start', format_rp_date(options[:starting_at] || Date.today + 1 ) + xml.tag! 'Start', format_rp_date(options[:starting_at] || Date.today + 1) else xml.tag! 'Start', format_rp_date(options[:starting_at]) unless options[:starting_at].nil? end @@ -227,10 +343,10 @@ def build_recurring_request(action, money, options) end end if action != :add - xml.tag! "ProfileID", options[:profile_id] + xml.tag! 'ProfileID', options[:profile_id] end if action == :inquiry - xml.tag! "PaymentHistory", ( options[:history] ? 'Y' : 'N' ) + xml.tag! 'PaymentHistory', (options[:history] ? 'Y' : 'N') end end end @@ -240,20 +356,20 @@ def build_recurring_request(action, money, options) def get_pay_period(options) requires!(options, [:periodicity, :bimonthly, :monthly, :biweekly, :weekly, :yearly, :daily, :semimonthly, :quadweekly, :quarterly, :semiyearly]) case options[:periodicity] - when :weekly then 'Weekly' - when :biweekly then 'Bi-weekly' - when :semimonthly then 'Semi-monthly' - when :quadweekly then 'Every four weeks' - when :monthly then 'Monthly' - when :quarterly then 'Quarterly' - when :semiyearly then 'Semi-yearly' - when :yearly then 'Yearly' + when :weekly then 'Weekly' + when :biweekly then 'Bi-weekly' + when :semimonthly then 'Semi-monthly' + when :quadweekly then 'Every four weeks' + when :monthly then 'Monthly' + when :quarterly then 'Quarterly' + when :semiyearly then 'Semi-yearly' + when :yearly then 'Yearly' end end def format_rp_date(time) case time - when Time, Date then time.strftime("%m%d%Y") + when Time, Date then time.strftime('%m%d%Y') else time.to_s end @@ -265,4 +381,3 @@ def build_response(success, message, response, options = {}) end end end - diff --git a/lib/active_merchant/billing/gateways/payflow/payflow_common_api.rb b/lib/active_merchant/billing/gateways/payflow/payflow_common_api.rb index f1aa2baa263..4ad1bd00739 100644 --- a/lib/active_merchant/billing/gateways/payflow/payflow_common_api.rb +++ b/lib/active_merchant/billing/gateways/payflow/payflow_common_api.rb @@ -10,7 +10,7 @@ def self.included(base) # Set the default partner to PayPal base.partner = 'PayPal' - base.supported_countries = ['US', 'CA', 'SG', 'AU'] + base.supported_countries = ['US', 'CA', 'NZ', 'AU'] base.class_attribute :timeout base.timeout = 60 @@ -25,6 +25,13 @@ def self.included(base) # subsequent Responses will have a :duplicate parameter set in the params # hash. base.retry_safe = true + + # Send Payflow requests to PayPal directly by activating the NVP protocol. + # Valid XMLPay documents may have issues being parsed correctly by + # Payflow but will be accepted by PayPal if a PAYPAL-NVP request header + # is declared. + base.class_attribute :use_paypal_nvp + base.use_paypal_nvp = false end XMLNS = 'http://www.paypal.com/XMLPay' @@ -36,16 +43,14 @@ def self.included(base) :american_express => 'Amex', :jcb => 'JCB', :diners_club => 'DinersClub', - :switch => 'Switch', - :solo => 'Solo' } TRANSACTIONS = { - :purchase => "Sale", - :authorization => "Authorization", - :capture => "Capture", - :void => "Void", - :credit => "Credit" + :purchase => 'Sale', + :authorization => 'Authorization', + :capture => 'Capture', + :void => 'Void', + :credit => 'Credit' } CVV_CODE = { @@ -73,10 +78,11 @@ def void(authorization, options = {}) end private + def build_request(body, options = {}) xml = Builder::XmlMarkup.new xml.instruct! - xml.tag! 'XMLPayRequest', 'Timeout' => timeout.to_s, 'version' => "2.1", "xmlns" => XMLNS do + xml.tag! 'XMLPayRequest', 'Timeout' => timeout.to_s, 'version' => '2.1', 'xmlns' => XMLNS do xml.tag! 'RequestData' do xml.tag! 'Vendor', @options[:login] xml.tag! 'Partner', @options[:partner] @@ -85,7 +91,7 @@ def build_request(body, options = {}) else xml.tag! 'Transactions' do xml.tag! 'Transaction', 'CustRef' => options[:customer] do - xml.tag! 'Verbosity', 'MEDIUM' + xml.tag! 'Verbosity', @options[:verbosity] || 'MEDIUM' xml << body end end @@ -112,6 +118,11 @@ def build_reference_request(action, money, authorization, options) xml.tag!('Description', options[:description]) unless options[:description].blank? xml.tag!('Comment', options[:comment]) unless options[:comment].blank? xml.tag!('ExtData', 'Name'=> 'COMMENT2', 'Value'=> options[:comment2]) unless options[:comment2].blank? + xml.tag!( + 'ExtData', + 'Name' => 'CAPTURECOMPLETE', + 'Value' => options[:capture_complete] + ) unless options[:capture_complete].blank? end end end @@ -130,8 +141,9 @@ def add_address(xml, tag, address, options) xml.tag! 'Address' do xml.tag! 'Street', address[:address1] unless address[:address1].blank? + xml.tag! 'Street2', address[:address2] unless address[:address2].blank? xml.tag! 'City', address[:city] unless address[:city].blank? - xml.tag! 'State', address[:state].blank? ? "N/A" : address[:state] + xml.tag! 'State', address[:state].blank? ? 'N/A' : address[:state] xml.tag! 'Country', address[:country] unless address[:country].blank? xml.tag! 'Zip', address[:zip] unless address[:zip].blank? end @@ -142,16 +154,16 @@ def parse(data) response = {} xml = Nokogiri::XML(data) xml.remove_namespaces! - root = xml.xpath("//ResponseData") + root = xml.xpath('//ResponseData') # REXML::XPath in Ruby 1.8.6 is now unable to match nodes based on their attributes - tx_result = root.xpath(".//TransactionResult").first + tx_result = root.xpath('.//TransactionResult').first - if tx_result && tx_result.attributes['Duplicate'].to_s == "true" + if tx_result && tx_result.attributes['Duplicate'].to_s == 'true' response[:duplicate] = true end - root.xpath(".//*").each do |node| + root.xpath('.//*').each do |node| parse_element(response, node) end @@ -166,10 +178,10 @@ def parse_element(response, node) # down as we do everywhere else. RPPaymentResult elements are not contained # in an RPPaymentResults element so we'll come here multiple times response[node_name] ||= [] - response[node_name] << ( payment_result_response = {} ) - node.xpath(".//*").each{ |e| parse_element(payment_result_response, e) } - when node.xpath(".//*").to_a.any? - node.xpath(".//*").each{|e| parse_element(response, e) } + response[node_name] << (payment_result_response = {}) + node.xpath('.//*').each { |e| parse_element(payment_result_response, e) } + when node.xpath('.//*').to_a.any? + node.xpath('.//*').each { |e| parse_element(response, e) } when node_name.to_s =~ /amt$/ # *Amt elements don't put the value in the #text - instead they use a Currency attribute response[node_name] = node.attributes['Currency'].to_s @@ -181,29 +193,43 @@ def parse_element(response, node) end def build_headers(content_length) - { - "Content-Type" => "text/xml", - "Content-Length" => content_length.to_s, - "X-VPS-Client-Timeout" => timeout.to_s, - "X-VPS-VIT-Integration-Product" => "ActiveMerchant", - "X-VPS-VIT-Runtime-Version" => RUBY_VERSION, - "X-VPS-Request-ID" => Utils.generate_unique_id - } - end - - def commit(request_body, options = {}) + headers = { + 'Content-Type' => 'text/xml', + 'Content-Length' => content_length.to_s, + 'X-VPS-Client-Timeout' => timeout.to_s, + 'X-VPS-VIT-Integration-Product' => 'ActiveMerchant', + 'X-VPS-VIT-Runtime-Version' => RUBY_VERSION, + 'X-VPS-Request-ID' => SecureRandom.hex(16) + } + + headers['PAYPAL-NVP'] = 'Y' if self.use_paypal_nvp + headers + end + + def commit(request_body, options = {}) request = build_request(request_body, options) headers = build_headers(request.size) - response = parse(ssl_post(test? ? self.test_url : self.live_url, request, headers)) + response = parse(ssl_post(test? ? self.test_url : self.live_url, request, headers)) - build_response(response[:result] == "0", response[:message], response, - :test => test?, - :authorization => response[:pn_ref] || response[:rp_ref], - :cvv_result => CVV_CODE[response[:cv_result]], - :avs_result => { :code => response[:avs_result] } + build_response( + success_for(response), + response[:message], response, + test: test?, + authorization: response[:pn_ref] || response[:rp_ref], + cvv_result: CVV_CODE[response[:cv_result]], + avs_result: { code: response[:avs_result] }, + fraud_review: under_fraud_review?(response) ) end + + def success_for(response) + %w(0 126).include?(response[:result]) + end + + def under_fraud_review?(response) + (response[:result] == '126') + end end end end diff --git a/lib/active_merchant/billing/gateways/payflow/payflow_express_response.rb b/lib/active_merchant/billing/gateways/payflow/payflow_express_response.rb index 7b4068dea05..3c43642265f 100644 --- a/lib/active_merchant/billing/gateways/payflow/payflow_express_response.rb +++ b/lib/active_merchant/billing/gateways/payflow/payflow_express_response.rb @@ -4,26 +4,30 @@ class PayflowExpressResponse < Response def email @params['e_mail'] end - + def full_name "#{@params['name']} #{@params['lastname']}" end - + def token @params['token'] end - + def payer_id @params['payer_id'] end - + # Really the shipping country, but it is all the information provided def payer_country address['country'] end - + + def phone + @params['phone'] + end + def address - { 'name' => full_name, + { 'name' => @params['shiptoname'] || full_name, 'company' => nil, 'address1' => @params['street'], 'address2' => @params['shiptostreet2'] || @params['street2'], @@ -31,7 +35,7 @@ def address 'state' => @params['state'], 'country' => @params['country'], 'zip' => @params['zip'], - 'phone' => nil + 'phone' => phone, } end end diff --git a/lib/active_merchant/billing/gateways/payflow/payflow_response.rb b/lib/active_merchant/billing/gateways/payflow/payflow_response.rb index d2b6e670009..83caaff5800 100644 --- a/lib/active_merchant/billing/gateways/payflow/payflow_response.rb +++ b/lib/active_merchant/billing/gateways/payflow/payflow_response.rb @@ -4,10 +4,10 @@ class PayflowResponse < Response def profile_id @params['profile_id'] end - + def payment_history - @payment_history ||= @params['rp_payment_result'].collect{ |result| result.stringify_keys } rescue [] + @payment_history ||= @params['rp_payment_result'].collect(&:stringify_keys) rescue [] end end end -end \ No newline at end of file +end diff --git a/lib/active_merchant/billing/gateways/payflow_express.rb b/lib/active_merchant/billing/gateways/payflow_express.rb index f6183218d28..9676d2b1cf2 100644 --- a/lib/active_merchant/billing/gateways/payflow_express.rb +++ b/lib/active_merchant/billing/gateways/payflow_express.rb @@ -1,70 +1,67 @@ -require File.dirname(__FILE__) + '/payflow/payflow_common_api' -require File.dirname(__FILE__) + '/payflow/payflow_express_response' -require File.dirname(__FILE__) + '/paypal_express_common' +require 'active_merchant/billing/gateways/payflow/payflow_common_api' +require 'active_merchant/billing/gateways/payflow/payflow_express_response' +require 'active_merchant/billing/gateways/paypal_express_common' module ActiveMerchant #:nodoc: module Billing #:nodoc: - # ==General Parameters - # The following parameters are supported for #setup_authorization, #setup_purchase, #authorize and #purchase transactions. I've read - # in the docs that they recommend you pass the exact same parameters to both setup and authorize/purchase. - # - # This information was gleaned from a mix of: - # * PayFlow documentation - # * for key value pairs: {Express Checkout for Payflow Pro (PDF)}[https://cms.paypal.com/cms_content/US/en_US/files/developer/PFP_ExpressCheckout_PP.pdf] - # * XMLPay: {Payflow Pro XMLPay Developer's Guide (PDF)}[https://cms.paypal.com/cms_content/US/en_US/files/developer/PP_PayflowPro_XMLPay_Guide.pdf] - # * previous ActiveMerchant code - # * trial & error - # - # The following parameters are currently supported. - # [:ip] (opt) Customer IP Address - # [:order_id] (opt) An order or invoice number. This will be passed through to the Payflow backend at manager.paypal.com, and show up as "Supplier Reference #" - # [:description] (opt) Order description, shown to buyer (after redirected to PayPal). If Order Line Items are used (see below), then the description is suppressed. This will not be passed through to the Payflow backend. - # [:billing_address] (opt) See ActiveMerchant::Billing::Gateway for details - # [:shipping_address] (opt) See ActiveMerchant::Billing::Gateway for details - # [:currency] (req) Currency of transaction, will be set to USD by default for PayFlow Express if not specified - # [:email] (opt) Email of buyer; used to pre-fill PayPal login screen - # [:payer_id] (opt) Unique PayPal buyer account identification number, as returned by details_for request - # [:token] (req for #authorize & #purchase) Token returned by setup transaction - # [:no_shipping] (opt) Boolean for whether or not to display shipping address to buyer - # [:address_override] (opt) Boolean. If true, display shipping address passed by parameters, rather than shipping address on file with PayPal - # [:allow_note] (opt) Boolean for permitting buyer to add note during checkout. Note contents can be retrieved with details_for transaction - # [:return_url] (req) URL to which the buyer’s browser is returned after choosing to pay. - # [:cancel_return_url] (req) URL to which the buyer is returned if the buyer cancels the order. - # [:notify_url] (opt) Your URL for receiving Instant Payment Notification (IPN) about this transaction. - # [:comment] (opt) Comment field which will be reported to Payflow backend (at manager.paypal.com) as Comment1 - # [:comment2] (opt) Comment field which will be reported to Payflow backend (at manager.paypal.com) as Comment2 - # [:discount] (opt) Total discounts in cents - # - # ==Line Items - # Support for order line items is available, but has to be enabled on the PayFlow backend. This is what I was told by Todd Sieber at Technical Support: - # - # You will need to call Payflow Support at 1-888-883-9770, choose option #2. Request that they update your account in "Pandora" under Product Settings >> PayPal Mark and update the Features Bitmap to 1111111111111112. This is 15 ones and a two. - # - # See here[https://www.x.com/message/206214#206214] for the forum discussion (requires login to {x.com}[https://x.com] - # - # [:items] (opt) Array of Order Line Items hashes. These are shown to the buyer after redirect to PayPal. - # - # - # - # The following keys are supported for line items: - # [:name] Name of line item - # [:description] Description of line item - # [:amount] Line Item Amount in Cents (as Integer) - # [:quantity] Line Item Quantity (default to 1 if left blank) - # - # ====Customization of Payment Page - # [:page_style] (opt) Your URL for receiving Instant Payment Notification (IPN) about this transaction. - # [:header_image] (opt) Your URL for receiving Instant Payment Notification (IPN) about this transaction. - # [:background_color] (opt) Your URL for receiving Instant Payment Notification (IPN) about this transaction. - # ====Additional options for old Checkout Experience, being phased out in 2010 and 2011 - # [:header_background_color] (opt) Your URL for receiving Instant Payment Notification (IPN) about this transaction. - # [:header_border_color] (opt) Your URL for receiving Instant Payment Notification (IPN) about this transaction. - + # ==General Parameters + # The following parameters are supported for #setup_authorization, #setup_purchase, #authorize and #purchase transactions. I've read + # in the docs that they recommend you pass the exact same parameters to both setup and authorize/purchase. + # + # This information was gleaned from a mix of: + # * {PayFlow documentation}[https://developer.paypal.com/docs/classic/payflow/integration-guide/] + # * previous ActiveMerchant code + # * trial & error + # + # The following parameters are currently supported. + # [:ip] (opt) Customer IP Address + # [:order_id] (opt) An order or invoice number. This will be passed through to the Payflow backend at manager.paypal.com, and show up as "Supplier Reference #" + # [:description] (opt) Order description, shown to buyer (after redirected to PayPal). If Order Line Items are used (see below), then the description is suppressed. This will not be passed through to the Payflow backend. + # [:billing_address] (opt) See ActiveMerchant::Billing::Gateway for details + # [:shipping_address] (opt) See ActiveMerchant::Billing::Gateway for details + # [:currency] (req) Currency of transaction, will be set to USD by default for PayFlow Express if not specified + # [:email] (opt) Email of buyer; used to pre-fill PayPal login screen + # [:payer_id] (opt) Unique PayPal buyer account identification number, as returned by details_for request + # [:token] (req for #authorize & #purchase) Token returned by setup transaction + # [:no_shipping] (opt) Boolean for whether or not to display shipping address to buyer + # [:address_override] (opt) Boolean. If true, display shipping address passed by parameters, rather than shipping address on file with PayPal + # [:allow_note] (opt) Boolean for permitting buyer to add note during checkout. Note contents can be retrieved with details_for transaction + # [:return_url] (req) URL to which the buyer’s browser is returned after choosing to pay. + # [:cancel_return_url] (req) URL to which the buyer is returned if the buyer cancels the order. + # [:notify_url] (opt) Your URL for receiving Instant Payment Notification (IPN) about this transaction. + # [:comment] (opt) Comment field which will be reported to Payflow backend (at manager.paypal.com) as Comment1 + # [:comment2] (opt) Comment field which will be reported to Payflow backend (at manager.paypal.com) as Comment2 + # [:discount] (opt) Total discounts in cents + # + # ==Line Items + # Support for order line items is available, but has to be enabled on the PayFlow backend. This is what I was told by Todd Sieber at Technical Support: + # + # You will need to call Payflow Support at 1-888-883-9770, choose option #2. Request that they update your account in "Pandora" under Product Settings >> PayPal Mark and update the Features Bitmap to 1111111111111112. This is 15 ones and a two. + # + # See here[https://www.x.com/message/206214#206214] for the forum discussion (requires login to {x.com}[https://x.com] + # + # [:items] (opt) Array of Order Line Items hashes. These are shown to the buyer after redirect to PayPal. + # + # + # + # The following keys are supported for line items: + # [:name] Name of line item + # [:description] Description of line item + # [:amount] Line Item Amount in Cents (as Integer) + # [:quantity] Line Item Quantity (default to 1 if left blank) + # + # ====Customization of Payment Page + # [:page_style] (opt) Your URL for receiving Instant Payment Notification (IPN) about this transaction. + # [:header_image] (opt) Your URL for receiving Instant Payment Notification (IPN) about this transaction. + # [:background_color] (opt) Your URL for receiving Instant Payment Notification (IPN) about this transaction. + # ====Additional options for old Checkout Experience, being phased out in 2010 and 2011 + # [:header_background_color] (opt) Your URL for receiving Instant Payment Notification (IPN) about this transaction. + # [:header_border_color] (opt) Your URL for receiving Instant Payment Notification (IPN) about this transaction. class PayflowExpressGateway < Gateway include PayflowCommonAPI include PaypalExpressCommon - + self.test_redirect_url = 'https://www.sandbox.paypal.com/cgi-bin/webscr' self.homepage_url = 'https://www.paypal.com/cgi-bin/webscr?cmd=xpt/merchant/ExpressCheckoutIntro-outside' self.display_name = 'PayPal Express Checkout' @@ -74,48 +71,49 @@ def authorize(money, options = {}) request = build_sale_or_authorization_request('Authorization', money, options) commit(request, options) end - - def purchase(money, options = {}) + + def purchase(money, options = {}) requires!(options, :token, :payer_id) request = build_sale_or_authorization_request('Sale', money, options) commit(request, options) end - + def refund(money, identification, options = {}) request = build_reference_request(:credit, money, identification, options) commit(request, options) - end + end def credit(money, identification, options = {}) - deprecated CREDIT_DEPRECATION_MESSAGE + ActiveMerchant.deprecated CREDIT_DEPRECATION_MESSAGE refund(money, identification, options) end def setup_authorization(money, options = {}) requires!(options, :return_url, :cancel_return_url) - + request = build_setup_express_sale_or_authorization_request('Authorization', money, options) commit(request, options) end - + def setup_purchase(money, options = {}) requires!(options, :return_url, :cancel_return_url) - + request = build_setup_express_sale_or_authorization_request('Sale', money, options) commit(request, options) end - + def details_for(token) request = build_get_express_details_request(token) commit(request, options) end - + private + def build_get_express_details_request(token) - xml = Builder::XmlMarkup.new :indent => 2 + xml = Builder::XmlMarkup.new :indent => 2 xml.tag! 'GetExpressCheckout' do xml.tag! 'Authorization' do - xml.tag! 'PayData' do + xml.tag! 'PayData' do xml.tag! 'Tender' do xml.tag! 'PayPal' do xml.tag! 'Token', token @@ -136,8 +134,8 @@ def build_setup_express_sale_or_authorization_request(action, money, options = { end xml.target! end - - def build_sale_or_authorization_request(action, money, options) + + def build_sale_or_authorization_request(action, money, options) xml = Builder::XmlMarkup.new :indent => 2 xml.tag! 'DoExpressCheckout' do xml.tag! action do @@ -178,11 +176,10 @@ def add_pay_data(xml, money, options) end if items.any? xml.tag! 'ExtData', 'Name' => 'CURRENCY', 'Value' => options[:currency] || currency(money) - xml.tag! 'ExtData', 'Name' => "ITEMAMT", 'Value' => amount(options[:subtotal] || money) + xml.tag! 'ExtData', 'Name' => 'ITEMAMT', 'Value' => amount(options[:subtotal] || money) end xml.tag! 'DiscountAmt', amount(options[:discount]) if options[:discount] xml.tag! 'TotalAmt', amount(money), 'Currency' => options[:currency] || currency(money) - end xml.tag! 'Tender' do @@ -192,7 +189,7 @@ def add_pay_data(xml, money, options) end def add_paypal_details(xml, options) - xml.tag! 'PayPal' do + xml.tag! 'PayPal' do xml.tag! 'EMail', options[:email] unless options[:email].blank? xml.tag! 'ReturnURL', options[:return_url] unless options[:return_url].blank? xml.tag! 'CancelURL', options[:cancel_return_url] unless options[:cancel_return_url].blank? @@ -201,8 +198,8 @@ def add_paypal_details(xml, options) xml.tag! 'Token', options[:token] unless options[:token].blank? xml.tag! 'NoShipping', options[:no_shipping] ? '1' : '0' xml.tag! 'AddressOverride', options[:address_override] ? '1' : '0' - xml.tag! 'ButtonSource', application_id.to_s.slice(0,32) unless application_id.blank? - + xml.tag! 'ButtonSource', application_id.to_s.slice(0, 32) unless application_id.blank? + # Customization of the payment page xml.tag! 'PageStyle', options[:page_style] unless options[:page_style].blank? xml.tag! 'HeaderImage', options[:header_image] unless options[:header_image].blank? @@ -214,11 +211,10 @@ def add_paypal_details(xml, options) xml.tag! 'ExtData', 'Name' => 'ALLOWNOTE', 'Value' => options[:allow_note] end end - + def build_response(success, message, response, options = {}) PayflowExpressResponse.new(success, message, response, options) end end end end - diff --git a/lib/active_merchant/billing/gateways/payflow_express_uk.rb b/lib/active_merchant/billing/gateways/payflow_express_uk.rb index 0469e65e66a..a314bad48c4 100644 --- a/lib/active_merchant/billing/gateways/payflow_express_uk.rb +++ b/lib/active_merchant/billing/gateways/payflow_express_uk.rb @@ -1,15 +1,14 @@ -require File.dirname(__FILE__) + '/payflow_express' +require 'active_merchant/billing/gateways/payflow_express' module ActiveMerchant #:nodoc: module Billing #:nodoc: class PayflowExpressUkGateway < PayflowExpressGateway self.default_currency = 'GBP' self.partner = 'PayPalUk' - + self.supported_countries = ['GB'] self.homepage_url = 'https://www.paypal.com/uk/cgi-bin/webscr?cmd=_additional-payment-overview-outside' self.display_name = 'PayPal Express Checkout (UK)' end end end - diff --git a/lib/active_merchant/billing/gateways/payflow_uk.rb b/lib/active_merchant/billing/gateways/payflow_uk.rb index 1bb1bf51730..e963c152ef0 100644 --- a/lib/active_merchant/billing/gateways/payflow_uk.rb +++ b/lib/active_merchant/billing/gateways/payflow_uk.rb @@ -1,21 +1,20 @@ -require File.dirname(__FILE__) + '/payflow' -require File.dirname(__FILE__) + '/payflow_express_uk' +require 'active_merchant/billing/gateways/payflow' +require 'active_merchant/billing/gateways/payflow_express_uk' module ActiveMerchant #:nodoc: module Billing #:nodoc: class PayflowUkGateway < PayflowGateway self.default_currency = 'GBP' self.partner = 'PayPalUk' - + def express @express ||= PayflowExpressUkGateway.new(@options) end - - self.supported_cardtypes = [:visa, :master, :american_express, :discover, :solo, :switch] + + self.supported_cardtypes = [:visa, :master, :american_express, :discover] self.supported_countries = ['GB'] self.homepage_url = 'https://www.paypal.com/uk/webapps/mpp/pro' self.display_name = 'PayPal Payments Pro (UK)' end end end - diff --git a/lib/active_merchant/billing/gateways/payment_express.rb b/lib/active_merchant/billing/gateways/payment_express.rb index 450b26e2661..ff89eb8cb08 100644 --- a/lib/active_merchant/billing/gateways/payment_express.rb +++ b/lib/active_merchant/billing/gateways/payment_express.rb @@ -2,7 +2,6 @@ module ActiveMerchant #:nodoc: module Billing #:nodoc: - # In NZ DPS supports ANZ, Westpac, National Bank, ASB and BNZ. # In Australia DPS supports ANZ, NAB, Westpac, CBA, St George and Bank of South Australia. # The Maybank in Malaysia is supported and the Citibank for Singapore. @@ -16,12 +15,13 @@ class PaymentExpressGateway < Gateway # However, regular accounts with DPS only support VISA and Mastercard self.supported_cardtypes = [ :visa, :master, :american_express, :diners_club, :jcb ] - self.supported_countries = %w[ AU CA DE ES FR GB HK IE MY NL NZ SG US ZA ] + self.supported_countries = %w[ AU FJ GB HK IE MY NZ PG SG US ] self.homepage_url = 'http://www.paymentexpress.com/' self.display_name = 'PaymentExpress' - self.live_url = self.test_url = 'https://sec.paymentexpress.com/pxpost.aspx' + self.live_url = 'https://sec.paymentexpress.com/pxpost.aspx' + self.test_url = 'https://uat.paymentexpress.com/pxpost.aspx' APPROVED = '1' @@ -82,7 +82,7 @@ def refund(money, identification, options = {}) end def credit(money, identification, options = {}) - deprecated CREDIT_DEPRECATION_MESSAGE + ActiveMerchant.deprecated CREDIT_DEPRECATION_MESSAGE refund(money, identification, options) end @@ -122,6 +122,18 @@ def store(credit_card, options = {}) commit(:validate, request) end + def supports_scrubbing + true + end + + def scrub(transcript) + transcript. + gsub(%r(().+()), '\1[FILTERED]\2'). + gsub(%r((Authorization: Basic )\w+), '\1[FILTERED]\2'). + gsub(%r(()\d+()), '\1[FILTERED]\2'). + gsub(%r(()\d+()), '\1[FILTERED]\2') + end + private def use_custom_payment_token? @@ -141,6 +153,7 @@ def build_purchase_or_authorization_request(money, payment_source, options) add_invoice(result, options) add_address_verification_data(result, options) add_optional_elements(result, options) + add_ip(result, options) result end @@ -151,83 +164,84 @@ def build_capture_or_credit_request(money, identification, options) add_invoice(result, options) add_reference(result, identification) add_optional_elements(result, options) + add_ip(result, options) result end def build_token_request(credit_card, options) result = new_transaction add_credit_card(result, credit_card) - add_amount(result, 100, options) #need to make an auth request for $1 + add_amount(result, 100, options) # need to make an auth request for $1 add_token_request(result, options) add_optional_elements(result, options) + add_ip(result, options) result end def add_credentials(xml) - xml.add_element("PostUsername").text = @options[:login] - xml.add_element("PostPassword").text = @options[:password] + xml.add_element('PostUsername').text = @options[:login] + xml.add_element('PostPassword').text = @options[:password] end def add_reference(xml, identification) - xml.add_element("DpsTxnRef").text = identification + xml.add_element('DpsTxnRef').text = identification end def add_credit_card(xml, credit_card) - xml.add_element("CardHolderName").text = credit_card.name - xml.add_element("CardNumber").text = credit_card.number - xml.add_element("DateExpiry").text = format_date(credit_card.month, credit_card.year) + xml.add_element('CardHolderName').text = credit_card.name + xml.add_element('CardNumber').text = credit_card.number + xml.add_element('DateExpiry').text = format_date(credit_card.month, credit_card.year) if credit_card.verification_value? - xml.add_element("Cvc2").text = credit_card.verification_value - xml.add_element("Cvc2Presence").text = "1" - end - - if requires_start_date_or_issue_number?(credit_card) - xml.add_element("DateStart").text = format_date(credit_card.start_month, credit_card.start_year) unless credit_card.start_month.blank? || credit_card.start_year.blank? - xml.add_element("IssueNumber").text = credit_card.issue_number unless credit_card.issue_number.blank? + xml.add_element('Cvc2').text = credit_card.verification_value + xml.add_element('Cvc2Presence').text = '1' end end def add_billing_token(xml, token) if use_custom_payment_token? - xml.add_element("BillingId").text = token + xml.add_element('BillingId').text = token else - xml.add_element("DpsBillingId").text = token + xml.add_element('DpsBillingId').text = token end end def add_token_request(xml, options) - xml.add_element("BillingId").text = options[:billing_id] if options[:billing_id] - xml.add_element("EnableAddBillCard").text = 1 + xml.add_element('BillingId').text = options[:billing_id] if options[:billing_id] + xml.add_element('EnableAddBillCard').text = 1 end def add_amount(xml, money, options) - xml.add_element("Amount").text = amount(money) - xml.add_element("InputCurrency").text = options[:currency] || currency(money) + xml.add_element('Amount').text = amount(money) + xml.add_element('InputCurrency').text = options[:currency] || currency(money) end def add_transaction_type(xml, action) - xml.add_element("TxnType").text = TRANSACTIONS[action] + xml.add_element('TxnType').text = TRANSACTIONS[action] end def add_invoice(xml, options) - xml.add_element("TxnId").text = options[:order_id].to_s.slice(0, 16) unless options[:order_id].blank? - xml.add_element("MerchantReference").text = options[:description].to_s.slice(0, 50) unless options[:description].blank? + xml.add_element('TxnId').text = options[:order_id].to_s.slice(0, 16) unless options[:order_id].blank? + xml.add_element('MerchantReference').text = options[:description].to_s.slice(0, 50) unless options[:description].blank? end def add_address_verification_data(xml, options) address = options[:billing_address] || options[:address] return if address.nil? - xml.add_element("EnableAvsData").text = 1 - xml.add_element("AvsAction").text = 1 + xml.add_element('EnableAvsData').text = 1 + xml.add_element('AvsAction').text = 1 - xml.add_element("AvsStreetAddress").text = address[:address1] - xml.add_element("AvsPostCode").text = address[:zip] + xml.add_element('AvsStreetAddress').text = address[:address1] + xml.add_element('AvsPostCode').text = address[:zip] + end + + def add_ip(xml, options) + xml.add_element('ClientInfo').text = options[:ip] if options[:ip] end # The options hash may contain optional data which will be passed - # through the the specialized optional fields at PaymentExpress + # through the specialized optional fields at PaymentExpress # as follows: # # { @@ -264,16 +278,16 @@ def add_address_verification_data(xml, options) # +purchase+, +authorize+, +capture+, +refund+, +store+ def add_optional_elements(xml, options) if client_type = normalized_client_type(options[:client_type]) - xml.add_element("ClientType").text = client_type + xml.add_element('ClientType').text = client_type end - xml.add_element("TxnData1").text = options[:txn_data1].to_s.slice(0,255) unless options[:txn_data1].blank? - xml.add_element("TxnData2").text = options[:txn_data2].to_s.slice(0,255) unless options[:txn_data2].blank? - xml.add_element("TxnData3").text = options[:txn_data3].to_s.slice(0,255) unless options[:txn_data3].blank? + xml.add_element('TxnData1').text = options[:txn_data1].to_s.slice(0, 255) unless options[:txn_data1].blank? + xml.add_element('TxnData2').text = options[:txn_data2].to_s.slice(0, 255) unless options[:txn_data2].blank? + xml.add_element('TxnData3').text = options[:txn_data3].to_s.slice(0, 255) unless options[:txn_data3].blank? end def new_transaction - REXML::Document.new.add_element("Txn") + REXML::Document.new.add_element('Txn') end # Take in the request and post it to DPS @@ -281,8 +295,10 @@ def commit(action, request) add_credentials(request) add_transaction_type(request, action) + url = test? ? self.test_url : self.live_url + # Parse the XML response - response = parse( ssl_post(self.live_url, request.to_s) ) + response = parse(ssl_post(url, request.to_s)) # Return a response PaymentExpressResponse.new(response[:success] == APPROVED, message_from(response), response, @@ -331,13 +347,13 @@ def format_date(month, year) def normalized_client_type(client_type_from_options) case client_type_from_options.to_s.downcase - when 'web' then "Web" - when 'ivr' then "IVR" - when 'moto' then "MOTO" - when 'unattended' then "Unattended" - when 'internet' then "Internet" - when 'recurring' then "Recurring" - else nil + when 'web' then 'Web' + when 'ivr' then 'IVR' + when 'moto' then 'MOTO' + when 'unattended' then 'Unattended' + when 'internet' then 'Internet' + when 'recurring' then 'Recurring' + else nil end end end @@ -346,7 +362,7 @@ class PaymentExpressResponse < Response # add a method to response so we can easily get the token # for Validate transactions def token - @params["billing_id"] || @params["dps_billing_id"] + @params['billing_id'] || @params['dps_billing_id'] end end end diff --git a/lib/active_merchant/billing/gateways/paymentez.rb b/lib/active_merchant/billing/gateways/paymentez.rb new file mode 100644 index 00000000000..50463010ea2 --- /dev/null +++ b/lib/active_merchant/billing/gateways/paymentez.rb @@ -0,0 +1,300 @@ +require 'base64' +require 'digest' + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class PaymentezGateway < Gateway #:nodoc: + self.test_url = 'https://ccapi-stg.paymentez.com/v2/' + self.live_url = 'https://ccapi.paymentez.com/v2/' + + self.supported_countries = %w[MX EC VE CO BR CL] + self.default_currency = 'USD' + self.supported_cardtypes = %i[visa master american_express diners_club elo] + + self.homepage_url = 'https://secure.paymentez.com/' + self.display_name = 'Paymentez' + + STANDARD_ERROR_CODE_MAPPING = { + 1 => :processing_error, + 6 => :card_declined, + 9 => :card_declined, + 10 => :processing_error, + 11 => :card_declined, + 12 => :config_error, + 13 => :config_error, + 19 => :invalid_cvc, + 20 => :config_error, + 21 => :card_declined, + 22 => :card_declined, + 23 => :card_declined, + 24 => :card_declined, + 25 => :card_declined, + 26 => :card_declined, + 27 => :card_declined, + 28 => :card_declined + }.freeze + + CARD_MAPPING = { + 'visa' => 'vi', + 'master' => 'mc', + 'american_express' => 'ax', + 'diners_club' => 'di', + 'elo' => 'el' + }.freeze + + def initialize(options = {}) + requires!(options, :application_code, :app_key) + super + end + + def purchase(money, payment, options = {}) + post = {} + + add_invoice(post, money, options) + add_payment(post, payment) + add_customer_data(post, options) + add_extra_params(post, options) + action = payment.is_a?(String) ? 'debit' : 'debit_cc' + + commit_transaction(action, post) + end + + def authorize(money, payment, options = {}) + post = {} + + add_invoice(post, money, options) + add_payment(post, payment) + add_customer_data(post, options) + add_extra_params(post, options) + + commit_transaction('authorize', post) + end + + def capture(money, authorization, _options = {}) + post = { + transaction: { id: authorization } + } + post[:order] = {amount: amount(money).to_f} if money + + commit_transaction('capture', post) + end + + def refund(money, authorization, options = {}) + post = {transaction: {id: authorization}} + post[:order] = {amount: amount(money).to_f} if money + + commit_transaction('refund', post) + end + + def void(authorization, _options = {}) + post = { transaction: { id: authorization } } + commit_transaction('refund', post) + end + + def verify(credit_card, options = {}) + MultiResponse.run do |r| + r.process { authorize(100, credit_card, options) } + r.process { void(r.authorization, options) } + end + end + + def store(credit_card, options = {}) + post = {} + + add_customer_data(post, options) + add_payment(post, credit_card) + + response = commit_card('add', post) + if !response.success? && !(token = extract_previous_card_token(response)).nil? + unstore(token, options) + response = commit_card('add', post) + end + response + end + + def unstore(identification, options = {}) + post = { card: { token: identification }, user: { id: options[:user_id] }} + commit_card('delete', post) + end + + def supports_scrubbing? + true + end + + def scrub(transcript) + transcript. + gsub(%r{(\\?"number\\?":)(\\?"[^"]+\\?")}, '\1[FILTERED]'). + gsub(%r{(\\?"cvc\\?":)(\\?"[^"]+\\?")}, '\1[FILTERED]'). + gsub(%r{(Auth-Token: )([A-Za-z0-9=]+)}, '\1[FILTERED]') + end + + private + + def add_customer_data(post, options) + requires!(options, :user_id, :email) + post[:user] ||= {} + post[:user][:id] = options[:user_id] + post[:user][:email] = options[:email] + post[:user][:ip_address] = options[:ip] if options[:ip] + post[:user][:fiscal_number] = options[:fiscal_number] if options[:fiscal_number] + if phone = options[:phone] || options.dig(:billing_address, :phone) + post[:user][:phone] = phone + end + end + + def add_invoice(post, money, options) + post[:session_id] = options[:session_id] if options[:session_id] + + post[:order] ||= {} + post[:order][:amount] = amount(money).to_f + post[:order][:vat] = options[:vat] if options[:vat] + post[:order][:dev_reference] = options[:dev_reference] if options[:dev_reference] + post[:order][:description] = options[:description] if options[:description] + post[:order][:discount] = options[:discount] if options[:discount] + post[:order][:installments] = options[:installments] if options[:installments] + post[:order][:installments_type] = options[:installments_type] if options[:installments_type] + post[:order][:taxable_amount] = options[:taxable_amount] if options[:taxable_amount] + post[:order][:tax_percentage] = options[:tax_percentage] if options[:tax_percentage] + end + + def add_payment(post, payment) + post[:card] ||= {} + if payment.is_a?(String) + post[:card][:token] = payment + else + post[:card][:number] = payment.number + post[:card][:holder_name] = payment.name + post[:card][:expiry_month] = payment.month + post[:card][:expiry_year] = payment.year + post[:card][:cvc] = payment.verification_value + post[:card][:type] = CARD_MAPPING[payment.brand] + end + end + + def add_extra_params(post, options) + extra_params = {} + extra_params.merge!(options[:extra_params]) if options[:extra_params] + + post['extra_params'] = extra_params unless extra_params.empty? + end + + def parse(body) + JSON.parse(body) + end + + def commit_raw(object, action, parameters) + url = "#{(test? ? test_url : live_url)}#{object}/#{action}" + + begin + raw_response = ssl_post(url, post_data(parameters), headers) + rescue ResponseError => e + raw_response = e.response.body + end + + begin + parse(raw_response) + rescue JSON::ParserError + {'status' => 'Internal server error'} + end + end + + def commit_transaction(action, parameters) + response = commit_raw('transaction', action, parameters) + Response.new( + success_from(response), + message_from(response), + response, + authorization: authorization_from(response), + test: test?, + error_code: error_code_from(response) + ) + end + + def commit_card(action, parameters) + response = commit_raw('card', action, parameters) + Response.new( + card_success_from(response), + card_message_from(response), + response, + authorization: card_authorization_from(response), + test: test?, + error_code: card_error_code_from(response) + ) + end + + def headers + { + 'Auth-Token' => authentication_code, + 'Content-Type' => 'application/json' + } + end + + def success_from(response) + !response.include?('error') && (response['status'] || response['transaction']['status']) == 'success' + end + + def card_success_from(response) + return false if response.include?('error') + return true if response['message'] == 'card deleted' + response['card']['status'] == 'valid' + end + + def message_from(response) + if !success_from(response) && response['error'] + response['error'] && response['error']['type'] + else + response['transaction'] && response['transaction']['message'] + end + end + + def card_message_from(response) + if !response.include?('error') + response['message'] || response['card']['message'] + else + response['error']['type'] + end + end + + def authorization_from(response) + response['transaction'] && response['transaction']['id'] + end + + def card_authorization_from(response) + response['card'] && response['card']['token'] + end + + def extract_previous_card_token(response) + match = /Card already added: (\d+)/.match(response.message) + match && match[1] + end + + def post_data(parameters = {}) + JSON.dump(parameters) + end + + def error_code_from(response) + return if success_from(response) + if response['transaction'] + detail = response['transaction']['status_detail'] + if STANDARD_ERROR_CODE_MAPPING.include?(detail) + return STANDARD_ERROR_CODE[STANDARD_ERROR_CODE_MAPPING[detail]] + end + elsif response['error'] + return STANDARD_ERROR_CODE[:config_error] + end + STANDARD_ERROR_CODE[:processing_error] + end + + def card_error_code_from(response) + STANDARD_ERROR_CODE[:processing_error] unless card_success_from(response) + end + + def authentication_code + timestamp = Time.new.to_i + unique_token = Digest::SHA256.hexdigest("#{@options[:app_key]}#{timestamp}") + authentication_string = "#{@options[:application_code]};#{timestamp};#{unique_token}" + Base64.encode64(authentication_string).delete("\n") + end + end + end +end diff --git a/lib/active_merchant/billing/gateways/paymill.rb b/lib/active_merchant/billing/gateways/paymill.rb index a426e573248..ba4dbeb8f9d 100644 --- a/lib/active_merchant/billing/gateways/paymill.rb +++ b/lib/active_merchant/billing/gateways/paymill.rb @@ -2,42 +2,27 @@ module ActiveMerchant #:nodoc: module Billing #:nodoc: class PaymillGateway < Gateway self.supported_countries = %w(AD AT BE BG CH CY CZ DE DK EE ES FI FO FR GB - GI GR HU IE IL IS IT LI LT LU LV MT NL NO PL - PT RO SE SI SK TR VA) + GI GR HR HU IE IL IM IS IT LI LT LU LV MC MT + NL NO PL PT RO SE SI SK TR VA) - self.supported_cardtypes = [:visa, :master] + self.supported_cardtypes = [:visa, :master, :american_express, :diners_club, :discover, :union_pay, :jcb] self.homepage_url = 'https://paymill.com' self.display_name = 'PAYMILL' self.money_format = :cents self.default_currency = 'EUR' + self.live_url = 'https://api.paymill.com/v2/' def initialize(options = {}) requires!(options, :public_key, :private_key) super end - def purchase(money, payment_method, options = {}) - case payment_method - when String - purchase_with_token(money, payment_method, options) - else - MultiResponse.run do |r| - r.process { save_card(payment_method) } - r.process { purchase_with_token(money, r.authorization, options) } - end - end + def purchase(money, payment_method, options={}) + action_with_token(:purchase, money, payment_method, options) end def authorize(money, payment_method, options = {}) - case payment_method - when String - authorize_with_token(money, payment_method, options) - else - MultiResponse.run do |r| - r.process { save_card(payment_method) } - r.process { authorize_with_token(money, r.authorization, options) } - end - end + action_with_token(:authorize, money, payment_method, options) end def capture(money, authorization, options = {}) @@ -45,7 +30,8 @@ def capture(money, authorization, options = {}) add_amount(post, money, options) post[:preauthorization] = preauth(authorization) - post[:description] = options[:description] + post[:description] = options[:order_id] + post[:source] = 'active_merchant' commit(:post, 'transactions', post) end @@ -53,33 +39,71 @@ def refund(money, authorization, options={}) post = {} post[:amount] = amount(money) - post[:description] = options[:description] + post[:description] = options[:order_id] commit(:post, "refunds/#{transaction_id(authorization)}", post) end + def void(authorization, options={}) + commit(:delete, "preauthorizations/#{preauth(authorization)}") + end + def store(credit_card, options={}) - save_card(credit_card) + # The store request requires a currency and amount of at least $1 USD. + # This is used for an authorization that is handled internally by Paymill. + options[:currency] = 'USD' + options[:money] = 100 + + save_card(credit_card, options) + end + + def supports_scrubbing + true + end + + def scrub(transcript) + transcript. + gsub(%r((Authorization: Basic )\w+), '\1[FILTERED]'). + gsub(/(account.number=)(\d*)/, '\1[FILTERED]'). + gsub(/(account.verification=)(\d*)/, '\1[FILTERED]') + end + + def verify_credentials + begin + ssl_get(live_url + 'transactions/nonexistent', headers) + rescue ResponseError => e + return false if e.response.code.to_i == 401 + end + + true end private - def add_credit_card(post, credit_card) + def add_credit_card(post, credit_card, options) + post['account.holder'] = (credit_card.try(:name) || '') post['account.number'] = credit_card.number - post['account.expiry.month'] = sprintf("%.2i", credit_card.month) - post['account.expiry.year'] = sprintf("%.4i", credit_card.year) + post['account.expiry.month'] = sprintf('%.2i', credit_card.month) + post['account.expiry.year'] = sprintf('%.4i', credit_card.year) post['account.verification'] = credit_card.verification_value + post['account.email'] = (options[:email] || nil) + post['presentation.amount3D'] = (options[:money] || nil) + post['presentation.currency3D'] = (options[:currency] || currency(options[:money])) end def headers { 'Authorization' => ('Basic ' + Base64.strict_encode64("#{@options[:private_key]}:X").chomp) } end - def commit(method, url, parameters=nil) + def commit(method, action, parameters=nil) begin - raw_response = ssl_request(method, "https://api.paymill.com/v2/#{url}", post_data(parameters), headers) + raw_response = ssl_request(method, live_url + action, post_data(parameters), headers) rescue ResponseError => e - parsed = JSON.parse(e.response.body) - return Response.new(false, parsed['error'], parsed, {}) + begin + parsed = JSON.parse(e.response.body) + rescue JSON::ParserError + return Response.new(false, "Unable to parse error response: '#{e.response.body}'") + end + return Response.new(false, response_message(parsed), parsed, {}) end response_from(raw_response) @@ -87,20 +111,36 @@ def commit(method, url, parameters=nil) def response_from(raw_response) parsed = JSON.parse(raw_response) - options = { :authorization => authorization_from(parsed), :test => (parsed['mode'] == 'test'), } - Response.new(true, 'Transaction approved', parsed, options) + succeeded = (parsed['data'] == []) || (parsed['data']['response_code'].to_i == 20000) + Response.new(succeeded, response_message(parsed), parsed, options) end def authorization_from(parsed_response) + parsed_data = parsed_response['data'] + return '' unless parsed_data.kind_of?(Hash) + [ - parsed_response['data']['id'], - parsed_response['data']['preauthorization'].try(:[], 'id') - ].join(";") + parsed_data['id'], + parsed_data['preauthorization'].try(:[], 'id') + ].join(';') + end + + def action_with_token(action, money, payment_method, options) + options[:money] = money + case payment_method + when String + self.send("#{action}_with_token", money, payment_method, options) + else + MultiResponse.run do |r| + r.process { save_card(payment_method, options) } + r.process { self.send("#{action}_with_token", money, r.authorization, options) } + end + end end def purchase_with_token(money, card_token, options) @@ -108,7 +148,8 @@ def purchase_with_token(money, card_token, options) add_amount(post, money, options) post[:token] = card_token - post[:description] = options[:description] + post[:description] = options[:order_id] + post[:source] = 'active_merchant' commit(:post, 'transactions', post) end @@ -117,13 +158,15 @@ def authorize_with_token(money, card_token, options) add_amount(post, money, options) post[:token] = card_token + post[:description] = options[:order_id] + post[:source] = 'active_merchant' commit(:post, 'preauthorizations', post) end - def save_card(credit_card) + def save_card(credit_card, options) post = {} - add_credit_card(post, credit_card) + add_credit_card(post, credit_card, options) post['channel.id'] = @options[:public_key] post['jsonPFunction'] = 'jsonPFunction' post['transaction.mode'] = (test? ? 'CONNECTOR_TEST' : 'LIVE') @@ -131,7 +174,7 @@ def save_card(credit_card) begin raw_response = ssl_request(:get, "#{save_card_url}?#{post_data(post)}", nil, {}) rescue ResponseError => e - return Response.new(false, e.response.body, e.response.body, {}) + return Response.new(false, e.response.body) end response_for_save_from(raw_response) @@ -140,17 +183,12 @@ def save_card(credit_card) def response_for_save_from(raw_response) options = { :test => test? } - parsed = JSON.parse(raw_response.sub(/jsonPFunction\(/, '').sub(/\)\z/, '')) - if parsed['error'] - succeeded = false - message = parsed['error']['message'] - else - succeeded = parsed['transaction']['processing']['result'] == 'ACK' - message = parsed['transaction']['processing']['return']['message'] - options[:authorization] = parsed['transaction']['identification']['uniqueId'] if succeeded - end + parser = ResponseParser.new(raw_response, options) + parser.generate_response + end - Response.new(succeeded, message, parsed, options) + def parse_reponse(response) + JSON.parse(response.sub(/jsonPFunction\(/, '').sub(/\)\z/, '')) end def save_card_url @@ -158,8 +196,10 @@ def save_card_url end def post_data(params) + return nil unless params + no_blanks = params.reject { |key, value| value.blank? } - no_blanks.map { |key, value| "#{key}=#{CGI.escape(value.to_s)}" }.join("&") + no_blanks.map { |key, value| "#{key}=#{CGI.escape(value.to_s)}" }.join('&') end def add_amount(post, money, options) @@ -168,12 +208,164 @@ def add_amount(post, money, options) end def preauth(authorization) - authorization.split(";").last + authorization.split(';').last end def transaction_id(authorization) authorization.split(';').first end + + RESPONSE_CODES = { + 10001 => 'Undefined response', + 10002 => 'Waiting for something', + 11000 => 'Retry request at a later time', + + 20000 => 'Operation successful', + 20100 => 'Funds held by acquirer', + 20101 => 'Funds held by acquirer because merchant is new', + 20200 => 'Transaction reversed', + 20201 => 'Reversed due to chargeback', + 20202 => 'Reversed due to money-back guarantee', + 20203 => 'Reversed due to complaint by buyer', + 20204 => 'Payment has been refunded', + 20300 => 'Reversal has been canceled', + 22000 => 'Initiation of transaction successful', + + 30000 => 'Transaction still in progress', + 30100 => 'Transaction has been accepted', + 31000 => 'Transaction pending', + 31100 => 'Pending due to address', + 31101 => 'Pending due to uncleared eCheck', + 31102 => 'Pending due to risk review', + 31103 => 'Pending due regulatory review', + 31104 => 'Pending due to unregistered/unconfirmed receiver', + 31200 => 'Pending due to unverified account', + 31201 => 'Pending due to non-captured funds', + 31202 => 'Pending due to international account (accept manually)', + 31203 => 'Pending due to currency conflict (accept manually)', + 31204 => 'Pending due to fraud filters (accept manually)', + + 40000 => 'Problem with transaction data', + 40001 => 'Problem with payment data', + 40002 => 'Invalid checksum', + 40100 => 'Problem with credit card data', + 40101 => 'Problem with CVV', + 40102 => 'Card expired or not yet valid', + 40103 => 'Card limit exceeded', + 40104 => 'Card is not valid', + 40105 => 'Expiry date not valid', + 40106 => 'Credit card brand required', + 40200 => 'Problem with bank account data', + 40201 => 'Bank account data combination mismatch', + 40202 => 'User authentication failed', + 40300 => 'Problem with 3-D Secure data', + 40301 => 'Currency/amount mismatch', + 40400 => 'Problem with input data', + 40401 => 'Amount too low or zero', + 40402 => 'Usage field too long', + 40403 => 'Currency not allowed', + 40410 => 'Problem with shopping cart data', + 40420 => 'Problem with address data', + 40500 => 'Permission error with acquirer API', + 40510 => 'Rate limit reached for acquirer API', + 42000 => 'Initiation of transaction failed', + 42410 => 'Initiation of transaction expired', + + 50000 => 'Problem with back end', + 50001 => 'Country blacklisted', + 50002 => 'IP address blacklisted', + 50004 => 'Live mode not allowed', + 50005 => 'Insufficient permissions (API key)', + 50100 => 'Technical error with credit card', + 50101 => 'Error limit exceeded', + 50102 => 'Card declined', + 50103 => 'Manipulation or stolen card', + 50104 => 'Card restricted', + 50105 => 'Invalid configuration data', + 50200 => 'Technical error with bank account', + 50201 => 'Account blacklisted', + 50300 => 'Technical error with 3-D Secure', + 50400 => 'Declined because of risk issues', + 50401 => 'Checksum was wrong', + 50402 => 'Bank account number was invalid (formal check)', + 50403 => 'Technical error with risk check', + 50404 => 'Unknown error with risk check', + 50405 => 'Unknown bank code', + 50406 => 'Open chargeback', + 50407 => 'Historical chargeback', + 50408 => 'Institution / public bank account (NCA)', + 50409 => 'KUNO/Fraud', + 50410 => 'Personal Account Protection (PAP)', + 50420 => 'Rejected due to acquirer fraud settings', + 50430 => 'Rejected due to acquirer risk settings', + 50440 => 'Failed due to restrictions with acquirer account', + 50450 => 'Failed due to restrictions with user account', + 50500 => 'General timeout', + 50501 => 'Timeout on side of the acquirer', + 50502 => 'Risk management transaction timeout', + 50600 => 'Duplicate operation', + 50700 => 'Cancelled by user', + 50710 => 'Failed due to funding source', + 50711 => 'Payment method not usable, use other payment method', + 50712 => 'Limit of funding source was exceeded', + 50713 => 'Means of payment not reusable (canceled by user)', + 50714 => 'Means of payment not reusable (expired)', + 50720 => 'Rejected by acquirer', + 50730 => 'Transaction denied by merchant', + 50800 => 'Preauthorisation failed', + 50810 => 'Authorisation has been voided', + 50820 => 'Authorisation period expired' + } + + def response_message(parsed_response) + return parsed_response['error'] if parsed_response['error'] + return 'Transaction approved.' if parsed_response['data'] == [] + + code = parsed_response['data']['response_code'].to_i + RESPONSE_CODES[code] || code.to_s + end + + class ResponseParser + attr_reader :raw_response, :parsed, :succeeded, :message, :options + + def initialize(raw_response='', options={}) + @raw_response = raw_response + @options = options + end + + def generate_response + parse_response + if parsed['error'] + handle_response_parse_error + else + handle_response_correct_parsing + end + + Response.new(succeeded, message, parsed, options) + end + + private + + def parse_response + @parsed = JSON.parse(raw_response.sub(/jsonPFunction\(/, '').sub(/\)\z/, '')) + end + + def handle_response_parse_error + @succeeded = false + @message = parsed['error']['message'] + end + + def handle_response_correct_parsing + @message = parsed['transaction']['processing']['return']['message'] + if @succeeded = is_ack? + @options[:authorization] = parsed['transaction']['identification']['uniqueId'] + end + end + + def is_ack? + parsed['transaction']['processing']['result'] == 'ACK' + end + end end end end diff --git a/lib/active_merchant/billing/gateways/paypal.rb b/lib/active_merchant/billing/gateways/paypal.rb index 4faf808a5af..3563a40410d 100644 --- a/lib/active_merchant/billing/gateways/paypal.rb +++ b/lib/active_merchant/billing/gateways/paypal.rb @@ -1,18 +1,18 @@ -require File.dirname(__FILE__) + '/paypal/paypal_common_api' -require File.dirname(__FILE__) + '/paypal/paypal_recurring_api' -require File.dirname(__FILE__) + '/paypal_express' +require 'active_merchant/billing/gateways/paypal/paypal_common_api' +require 'active_merchant/billing/gateways/paypal/paypal_recurring_api' +require 'active_merchant/billing/gateways/paypal_express' module ActiveMerchant #:nodoc: module Billing #:nodoc: class PaypalGateway < Gateway include PaypalCommonAPI include PaypalRecurringApi - + self.supported_cardtypes = [:visa, :master, :american_express, :discover] - self.supported_countries = ['US'] + self.supported_countries = ['CA', 'NZ', 'GB', 'US'] self.homepage_url = 'https://www.paypal.com/us/webapps/mpp/paypal-payments-pro' self.display_name = 'PayPal Payments Pro (US)' - + def authorize(money, credit_card_or_referenced_id, options = {}) requires!(options, :ip) commit define_transaction_type(credit_card_or_referenced_id), build_sale_or_authorization_request('Authorization', money, credit_card_or_referenced_id, options) @@ -22,13 +22,24 @@ def purchase(money, credit_card_or_referenced_id, options = {}) requires!(options, :ip) commit define_transaction_type(credit_card_or_referenced_id), build_sale_or_authorization_request('Sale', money, credit_card_or_referenced_id, options) end - + + def verify(credit_card, options = {}) + if %w(visa master).include?(credit_card.brand) + authorize(0, credit_card, options) + else + MultiResponse.run(:use_first_response) do |r| + r.process { authorize(100, credit_card, options) } + r.process(:ignore_result) { void(r.authorization, options) } + end + end + end + def express @express ||= PaypalExpressGateway.new(@options) end - + private - + def define_transaction_type(transaction_arg) if transaction_arg.is_a?(String) return 'DoReferenceTransaction' @@ -36,14 +47,14 @@ def define_transaction_type(transaction_arg) return 'DoDirectPayment' end end - + def build_sale_or_authorization_request(action, money, credit_card_or_referenced_id, options) transaction_type = define_transaction_type(credit_card_or_referenced_id) - reference_id = credit_card_or_referenced_id if transaction_type == "DoReferenceTransaction" - + reference_id = credit_card_or_referenced_id if transaction_type == 'DoReferenceTransaction' + billing_address = options[:billing_address] || options[:address] currency_code = options[:currency] || currency(money) - + xml = Builder::XmlMarkup.new :indent => 2 xml.tag! transaction_type + 'Req', 'xmlns' => PAYPAL_NAMESPACE do xml.tag! transaction_type + 'Request', 'xmlns:n2' => EBAY_NAMESPACE do @@ -51,6 +62,7 @@ def build_sale_or_authorization_request(action, money, credit_card_or_referenced xml.tag! 'n2:' + transaction_type + 'RequestDetails' do xml.tag! 'n2:ReferenceID', reference_id if transaction_type == 'DoReferenceTransaction' xml.tag! 'n2:PaymentAction', action + add_descriptors(xml, options) add_payment_details(xml, money, currency_code, options) add_credit_card(xml, credit_card_or_referenced_id, billing_address, options) unless transaction_type == 'DoReferenceTransaction' xml.tag! 'n2:IPAddress', options[:ip] @@ -58,32 +70,44 @@ def build_sale_or_authorization_request(action, money, credit_card_or_referenced end end - xml.target! + xml.target! end - + def add_credit_card(xml, credit_card, address, options) xml.tag! 'n2:CreditCard' do xml.tag! 'n2:CreditCardType', credit_card_type(card_brand(credit_card)) xml.tag! 'n2:CreditCardNumber', credit_card.number xml.tag! 'n2:ExpMonth', format(credit_card.month, :two_digits) xml.tag! 'n2:ExpYear', format(credit_card.year, :four_digits) - xml.tag! 'n2:CVV2', credit_card.verification_value - - if [ 'switch', 'solo' ].include?(card_brand(credit_card).to_s) - xml.tag! 'n2:StartMonth', format(credit_card.start_month, :two_digits) unless credit_card.start_month.blank? - xml.tag! 'n2:StartYear', format(credit_card.start_year, :four_digits) unless credit_card.start_year.blank? - xml.tag! 'n2:IssueNumber', format(credit_card.issue_number, :two_digits) unless credit_card.issue_number.blank? - end - + xml.tag! 'n2:CVV2', credit_card.verification_value unless credit_card.verification_value.blank? + xml.tag! 'n2:CardOwner' do xml.tag! 'n2:PayerName' do xml.tag! 'n2:FirstName', credit_card.first_name xml.tag! 'n2:LastName', credit_card.last_name end - + xml.tag! 'n2:Payer', options[:email] add_address(xml, 'n2:Address', address) end + + add_three_d_secure(xml, options) if options[:three_d_secure] + end + end + + def add_descriptors(xml, options) + xml.tag! 'n2:SoftDescriptor', options[:soft_descriptor] unless options[:soft_descriptor].blank? + xml.tag! 'n2:SoftDescriptorCity', options[:soft_descriptor_city] unless options[:soft_descriptor_city].blank? + end + + def add_three_d_secure(xml, options) + three_d_secure = options[:three_d_secure] + xml.tag! 'ThreeDSecureRequest' do + xml.tag! 'MpiVendor3ds', 'Y' + xml.tag! 'AuthStatus3ds', three_d_secure[:trans_status] unless three_d_secure[:trans_status].blank? + xml.tag! 'Cavv', three_d_secure[:cavv] unless three_d_secure[:cavv].blank? + xml.tag! 'Eci3ds', three_d_secure[:eci] unless three_d_secure[:eci].blank? + xml.tag! 'Xid', three_d_secure[:xid] unless three_d_secure[:xid].blank? end end @@ -93,13 +117,11 @@ def credit_card_type(type) when 'master' then 'MasterCard' when 'discover' then 'Discover' when 'american_express' then 'Amex' - when 'switch' then 'Switch' - when 'solo' then 'Solo' end end - + def build_response(success, message, response, options = {}) - Response.new(success, message, response, options) + Response.new(success, message, response, options) end end end diff --git a/lib/active_merchant/billing/gateways/paypal/paypal_common_api.rb b/lib/active_merchant/billing/gateways/paypal/paypal_common_api.rb index 74fb4d4c91b..fe3146eac55 100644 --- a/lib/active_merchant/billing/gateways/paypal/paypal_common_api.rb +++ b/lib/active_merchant/billing/gateways/paypal/paypal_common_api.rb @@ -2,12 +2,14 @@ module ActiveMerchant #:nodoc: module Billing #:nodoc: # This module is included in both PaypalGateway and PaypalExpressGateway module PaypalCommonAPI - API_VERSION = '72' + include Empty + + API_VERSION = '124' URLS = { :test => { :certificate => 'https://api.sandbox.paypal.com/2.0/', :signature => 'https://api-3t.sandbox.paypal.com/2.0/' }, - :live => { :certificate => 'https://api-aa.paypal.com/2.0/', + :live => { :certificate => 'https://api.paypal.com/2.0/', :signature => 'https://api-3t.paypal.com/2.0/' } } @@ -38,6 +40,20 @@ module PaypalCommonAPI FRAUD_REVIEW_CODE = "11610" + STANDARD_ERROR_CODE_MAPPING = { + '15005' => :card_declined, + '10754' => :card_declined, + '10752' => :card_declined, + '10759' => :card_declined, + '10761' => :card_declined, + '15002' => :card_declined, + '11084' => :card_declined, + '15004' => :incorrect_cvc, + '10762' => :invalid_cvc, + } + + STANDARD_ERROR_CODE_MAPPING.default = :processing_error + def self.included(base) base.default_currency = 'USD' base.cattr_accessor :pem_file @@ -122,7 +138,7 @@ def refund(money, identification, options = {}) end def credit(money, identification, options = {}) - deprecated Gateway::CREDIT_DEPRECATION_MESSAGE + ActiveMerchant.deprecated Gateway::CREDIT_DEPRECATION_MESSAGE refund(money, identification, options) end @@ -242,7 +258,7 @@ def authorize_transaction(transaction_id, money, options = {}) commit 'DoAuthorization', build_do_authorize(transaction_id, money, options) end - # The ManagePendingTransactionStatus API operation accepts or denys a + # The ManagePendingTransactionStatus API operation accepts or denies a # pending transaction held by Fraud Management Filters. # # ==== Parameters: @@ -253,7 +269,21 @@ def manage_pending_transaction(transaction_id, action) commit 'ManagePendingTransactionStatus', build_manage_pending_transaction_status(transaction_id, action) end + def supports_scrubbing? + true + end + + def scrub(transcript) + transcript. + gsub(%r(().+()), '\1[FILTERED]\2'). + gsub(%r(().+()), '\1[FILTERED]\2'). + gsub(%r(().+()), '\1[FILTERED]\2'). + gsub(%r(().+()\d+( 2 xml.tag! action + 'Req', 'xmlns' => PAYPAL_NAMESPACE do @@ -573,7 +603,7 @@ def add_payment_details(xml, money, currency_code, options = {}) xml.tag! 'n2:Custom', options[:custom] unless options[:custom].blank? xml.tag! 'n2:InvoiceID', (options[:order_id] || options[:invoice_id]) unless (options[:order_id] || options[:invoice_id]).blank? - xml.tag! 'n2:ButtonSource', application_id.to_s.slice(0,32) unless application_id.blank? + add_button_source(xml) # The notify URL applies only to DoExpressCheckoutPayment. # This value is ignored when set in SetExpressCheckout or GetExpressCheckoutDetails @@ -593,11 +623,18 @@ def add_payment_details(xml, money, currency_code, options = {}) end end + def add_button_source(xml) + button_source = (@options[:button_source] || application_id) + if !empty?(button_source) + xml.tag! 'n2:ButtonSource', button_source.to_s.slice(0, 32) + end + end + def add_express_only_payment_details(xml, options = {}) add_optional_fields(xml, - %w{n2:NoteText n2:SoftDescriptor - n2:TransactionId n2:AllowedPaymentMethodType - n2:PaymentRequestID n2:PaymentAction}, + %w{n2:NoteText n2:PaymentAction + n2:TransactionId n2:AllowedPaymentMethod + n2:PaymentRequestID n2:SoftDescriptor }, options) end @@ -628,11 +665,21 @@ def commit(action, request) :test => test?, :authorization => authorization_from(response), :fraud_review => fraud_review?(response), + :error_code => standardized_error_code(response), :avs_result => { :code => response[:avs_code] }, :cvv_result => response[:cvv2_code] ) end + def standardized_error_code(response) + STANDARD_ERROR_CODE_MAPPING[error_codes(response).first] + end + + def error_codes(response) + return [] unless response.has_key?(:error_codes) + response[:error_codes].split(',') + end + def fraud_review?(response) response[:error_codes] == FRAUD_REVIEW_CODE end diff --git a/lib/active_merchant/billing/gateways/paypal/paypal_express_response.rb b/lib/active_merchant/billing/gateways/paypal/paypal_express_response.rb index 7cd7aadbb32..eeb38da4117 100644 --- a/lib/active_merchant/billing/gateways/paypal/paypal_express_response.rb +++ b/lib/active_merchant/billing/gateways/paypal/paypal_express_response.rb @@ -56,6 +56,10 @@ def shipping 'name' => shipping['ShippingOptionName'] } end + + def note + @params['note_text'] + end end end end diff --git a/lib/active_merchant/billing/gateways/paypal/paypal_recurring_api.rb b/lib/active_merchant/billing/gateways/paypal/paypal_recurring_api.rb index 157037cb176..764e7b0a2eb 100644 --- a/lib/active_merchant/billing/gateways/paypal/paypal_recurring_api.rb +++ b/lib/active_merchant/billing/gateways/paypal/paypal_recurring_api.rb @@ -1,4 +1,4 @@ -require File.dirname(__FILE__) + '/paypal_common_api' +require 'active_merchant/billing/gateways/paypal/paypal_common_api' module ActiveMerchant #:nodoc: module Billing #:nodoc: @@ -12,7 +12,7 @@ module PaypalRecurringApi # This transaction creates a recurring payment profile # ==== Parameters # - # * money -- The amount to be charged to the customer at each interval as an Integer value in cents. + # * amount -- The amount to be charged to the customer at each interval as an Integer value in cents. # * credit_card -- The CreditCard details for the transaction. # * options -- A hash of parameters. # @@ -23,8 +23,10 @@ module PaypalRecurringApi # * :cycles -- Limit to certain # of cycles (OPTIONAL) # * :start_date -- When does the charging starts (REQUIRED) # * :description -- The description to appear in the profile (REQUIRED) - + def recurring(amount, credit_card, options = {}) + ActiveMerchant.deprecated Gateway::RECURRING_DEPRECATION_MESSAGE + options[:credit_card] = credit_card options[:amount] = amount requires!(options, :description, :start_date, :period, :frequency, :amount) @@ -34,7 +36,7 @@ def recurring(amount, credit_card, options = {}) # Update a recurring payment's details. # # This transaction updates an existing Recurring Billing Profile - # and the subscription must have already been created previously + # and the subscription must have already been created previously # by calling +recurring()+. The ability to change certain # details about a recurring payment is dependent on transaction history # and the type of plan you're subscribed with paypal. Web Payment Pro @@ -49,6 +51,8 @@ def recurring(amount, credit_card, options = {}) # * :profile_id -- A string containing the :profile_id # of the recurring payment already in place for a given credit card. (REQUIRED) def update_recurring(options={}) + ActiveMerchant.deprecated Gateway::RECURRING_DEPRECATION_MESSAGE + requires!(options, :profile_id) opts = options.dup commit 'UpdateRecurringPaymentsProfile', build_change_profile_request(opts.delete(:profile_id), opts) @@ -65,6 +69,8 @@ def update_recurring(options={}) # recurring payment already in place for a given credit card. (REQUIRED) # * options -- A hash with extra info ('note' for ex.) def cancel_recurring(profile_id, options = {}) + ActiveMerchant.deprecated Gateway::RECURRING_DEPRECATION_MESSAGE + raise_error_if_blank('profile_id', profile_id) commit 'ManageRecurringPaymentsProfileStatus', build_manage_profile_request(profile_id, 'Cancel', options) end @@ -76,6 +82,8 @@ def cancel_recurring(profile_id, options = {}) # * profile_id -- A string containing the +profile_id+ of the # recurring payment already in place for a given credit card. (REQUIRED) def status_recurring(profile_id) + ActiveMerchant.deprecated Gateway::RECURRING_DEPRECATION_MESSAGE + raise_error_if_blank('profile_id', profile_id) commit 'GetRecurringPaymentsProfileDetails', build_get_profile_details_request(profile_id) end @@ -87,6 +95,8 @@ def status_recurring(profile_id) # * profile_id -- A string containing the +profile_id+ of the # recurring payment already in place for a given credit card. (REQUIRED) def suspend_recurring(profile_id, options = {}) + ActiveMerchant.deprecated Gateway::RECURRING_DEPRECATION_MESSAGE + raise_error_if_blank('profile_id', profile_id) commit 'ManageRecurringPaymentsProfileStatus', build_manage_profile_request(profile_id, 'Suspend', options) end @@ -98,6 +108,8 @@ def suspend_recurring(profile_id, options = {}) # * profile_id -- A string containing the +profile_id+ of the # recurring payment already in place for a given credit card. (REQUIRED) def reactivate_recurring(profile_id, options = {}) + ActiveMerchant.deprecated Gateway::RECURRING_DEPRECATION_MESSAGE + raise_error_if_blank('profile_id', profile_id) commit 'ManageRecurringPaymentsProfileStatus', build_manage_profile_request(profile_id, 'Reactivate', options) end @@ -109,6 +121,8 @@ def reactivate_recurring(profile_id, options = {}) # * profile_id -- A string containing the +profile_id+ of the # recurring payment already in place for a given credit card. (REQUIRED) def bill_outstanding_amount(profile_id, options = {}) + ActiveMerchant.deprecated Gateway::RECURRING_DEPRECATION_MESSAGE + raise_error_if_blank('profile_id', profile_id) commit 'BillOutstandingAmount', build_bill_outstanding_amount(profile_id, options) end diff --git a/lib/active_merchant/billing/gateways/paypal_ca.rb b/lib/active_merchant/billing/gateways/paypal_ca.rb index 69c94ec3c4d..91fb551d342 100644 --- a/lib/active_merchant/billing/gateways/paypal_ca.rb +++ b/lib/active_merchant/billing/gateways/paypal_ca.rb @@ -1,4 +1,4 @@ -require File.dirname(__FILE__) + '/paypal' +require 'active_merchant/billing/gateways/paypal' module ActiveMerchant #:nodoc: module Billing #:nodoc: diff --git a/lib/active_merchant/billing/gateways/paypal_digital_goods.rb b/lib/active_merchant/billing/gateways/paypal_digital_goods.rb index ce5824ceada..653a8c59015 100644 --- a/lib/active_merchant/billing/gateways/paypal_digital_goods.rb +++ b/lib/active_merchant/billing/gateways/paypal_digital_goods.rb @@ -1,6 +1,6 @@ -require File.dirname(__FILE__) + '/paypal/paypal_common_api' -require File.dirname(__FILE__) + '/paypal/paypal_express_response' -require File.dirname(__FILE__) + '/paypal_express_common' +require 'active_merchant/billing/gateways/paypal/paypal_common_api' +require 'active_merchant/billing/gateways/paypal/paypal_express_response' +require 'active_merchant/billing/gateways/paypal_express_common' module ActiveMerchant #:nodoc: module Billing #:nodoc: @@ -11,9 +11,10 @@ class PaypalDigitalGoodsGateway < PaypalExpressGateway self.supported_countries = %w(AU CA CN FI GB ID IN IT MY NO NZ PH PL SE SG TH VN) self.homepage_url = 'https://www.x.com/community/ppx/xspaces/digital_goods' self.display_name = 'PayPal Express Checkout for Digital Goods' - + def redirect_url_for(token, options = {}) - "#{redirect_url}?token=#{token}&useraction=commit" + options[:review] ||= false + super end # GATEWAY.setup_purchase(100, @@ -29,7 +30,7 @@ def redirect_url_for(token, options = {}) # :category => "Digital" } ] ) def build_setup_request(action, money, options) requires!(options, :items) - raise ArgumentError, "Must include at least 1 Item" unless options[:items].length > 0 + raise ArgumentError, 'Must include at least 1 Item' unless options[:items].length > 0 options[:items].each do |item| requires!(item, :name, :number, :quantity, :amount, :description, :category) raise ArgumentError, "Each of the items must have the category 'Digital'" unless item[:category] == 'Digital' diff --git a/lib/active_merchant/billing/gateways/paypal_express.rb b/lib/active_merchant/billing/gateways/paypal_express.rb index dd87384cc48..60d659437f9 100644 --- a/lib/active_merchant/billing/gateways/paypal_express.rb +++ b/lib/active_merchant/billing/gateways/paypal_express.rb @@ -1,7 +1,7 @@ -require File.dirname(__FILE__) + '/paypal/paypal_common_api' -require File.dirname(__FILE__) + '/paypal/paypal_express_response' -require File.dirname(__FILE__) + '/paypal/paypal_recurring_api' -require File.dirname(__FILE__) + '/paypal_express_common' +require 'active_merchant/billing/gateways/paypal/paypal_common_api' +require 'active_merchant/billing/gateways/paypal/paypal_express_response' +require 'active_merchant/billing/gateways/paypal/paypal_recurring_api' +require 'active_merchant/billing/gateways/paypal_express_common' module ActiveMerchant #:nodoc: module Billing #:nodoc: @@ -30,6 +30,7 @@ class PaypalExpressGateway < Gateway self.supported_countries = ['US'] self.homepage_url = 'https://www.paypal.com/cgi-bin/webscr?cmd=xpt/merchant/ExpressCheckoutIntro-outside' self.display_name = 'PayPal Express Checkout' + self.currencies_without_fractions = %w(HUF JPY TWD) def setup_authorization(money, options = {}) requires!(options, :return_url, :cancel_return_url) @@ -67,6 +68,10 @@ def unstore(token, options = {}) commit 'BAUpdate', build_cancel_billing_agreement_request(token) end + def agreement_details(reference_id, options = {}) + commit 'BAUpdate', build_details_billing_agreement_request(reference_id) + end + def authorize_reference_transaction(money, options = {}) requires!(options, :reference_id, :payment_type, :invoice_id, :description, :ip) @@ -127,19 +132,23 @@ def build_setup_request(action, money, options) if options[:max_amount] xml.tag! 'n2:MaxAmount', localized_amount(options[:max_amount], currency_code), 'currencyID' => currency_code end + xml.tag! 'n2:ReqBillingAddress', options[:req_billing_address] ? '1' : '0' xml.tag! 'n2:NoShipping', options[:no_shipping] ? '1' : '0' xml.tag! 'n2:AddressOverride', options[:address_override] ? '1' : '0' xml.tag! 'n2:LocaleCode', locale_code(options[:locale]) unless options[:locale].blank? xml.tag! 'n2:BrandName', options[:brand_name] unless options[:brand_name].blank? # Customization of the payment page xml.tag! 'n2:PageStyle', options[:page_style] unless options[:page_style].blank? + xml.tag! 'n2:cpp-logo-image', options[:logo_image] unless options[:logo_image].blank? xml.tag! 'n2:cpp-header-image', options[:header_image] unless options[:header_image].blank? xml.tag! 'n2:cpp-header-border-color', options[:header_border_color] unless options[:header_border_color].blank? xml.tag! 'n2:cpp-header-back-color', options[:header_background_color] unless options[:header_background_color].blank? xml.tag! 'n2:cpp-payflow-color', options[:background_color] unless options[:background_color].blank? if options[:allow_guest_checkout] xml.tag! 'n2:SolutionType', 'Sole' - xml.tag! 'n2:LandingPage', options[:landing_page] || 'Billing' + unless options[:paypal_chooses_landing_page] + xml.tag! 'n2:LandingPage', options[:landing_page] || 'Billing' + end end xml.tag! 'n2:BuyerEmail', options[:email] unless options[:email].blank? @@ -154,6 +163,13 @@ def build_setup_request(action, money, options) if !options[:allow_note].nil? xml.tag! 'n2:AllowNote', options[:allow_note] ? '1' : '0' end + + if options[:funding_sources] + xml.tag! 'n2:FundingSourceDetails' do + xml.tag! 'n2:UserSelectedFundingSource', options[:funding_sources][:source] + end + end + xml.tag! 'n2:CallbackURL', options[:callback_url] unless options[:callback_url].blank? add_payment_details(xml, money, currency_code, options) @@ -173,6 +189,8 @@ def build_setup_request(action, money, options) if options.has_key?(:allow_buyer_optin) xml.tag! 'n2:BuyerEmailOptInEnable', (options[:allow_buyer_optin] ? '1' : '0') end + + xml.tag! 'n2:TotalType', options[:total_type] unless options[:total_type].blank? end end end @@ -205,6 +223,18 @@ def build_cancel_billing_agreement_request(token) xml.target! end + def build_details_billing_agreement_request(reference_id) + xml = Builder::XmlMarkup.new :indent => 2 + xml.tag! 'BillAgreementUpdateReq', 'xmlns' => PAYPAL_NAMESPACE do + xml.tag! 'BAUpdateRequest', 'xmlns:n2' => EBAY_NAMESPACE do + xml.tag! 'n2:Version', API_VERSION + xml.tag! 'ReferenceID', reference_id + end + end + + xml.target! + end + def build_reference_transaction_request(action, money, options) currency_code = options[:currency] || currency(money) diff --git a/lib/active_merchant/billing/gateways/paypal_express_common.rb b/lib/active_merchant/billing/gateways/paypal_express_common.rb index 47cf830d347..7cb72b82745 100644 --- a/lib/active_merchant/billing/gateways/paypal_express_common.rb +++ b/lib/active_merchant/billing/gateways/paypal_express_common.rb @@ -9,13 +9,13 @@ def self.included(base) base.class_inheritable_accessor :test_redirect_url base.class_inheritable_accessor :live_redirect_url end - base.live_redirect_url = 'https://www.paypal.com/cgibin/webscr' + base.live_redirect_url = 'https://www.paypal.com/cgi-bin/webscr' end - + def redirect_url test? ? test_redirect_url : live_redirect_url end - + def redirect_url_for(token, options = {}) options = {:review => true, :mobile => false}.update(options) diff --git a/lib/active_merchant/billing/gateways/payscout.rb b/lib/active_merchant/billing/gateways/payscout.rb new file mode 100644 index 00000000000..d2ad18b5a16 --- /dev/null +++ b/lib/active_merchant/billing/gateways/payscout.rb @@ -0,0 +1,160 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class PayscoutGateway < Gateway + self.live_url = self.test_url = 'https://secure.payscout.com/api/transact.php' + + self.supported_countries = ['US'] + self.supported_cardtypes = [:visa, :master, :american_express, :discover] + self.default_currency = 'USD' + self.homepage_url = 'http://www.payscout.com/' + self.display_name = 'Payscout' + + def initialize(options = {}) + requires!(options, :username, :password) + super + end + + def authorize(money, creditcard, options = {}) + post = {} + add_invoice(post, options) + add_creditcard(post, creditcard) + add_currency(post, money, options) + add_address(post, options) + + commit('auth', money, post) + end + + def purchase(money, creditcard, options = {}) + post = {} + add_invoice(post, options) + add_creditcard(post, creditcard) + add_currency(post, money, options) + add_address(post, options) + + commit('sale', money, post) + end + + def capture(money, authorization, options = {}) + post = {} + post[:transactionid] = authorization + + commit('capture', money, post) + end + + def refund(money, authorization, options = {}) + post = {} + post[:transactionid] = authorization + + commit('refund', money, post) + end + + def void(authorization, options = {}) + post = {} + post[:transactionid] = authorization + + commit('void', nil, post) + end + + private + + def add_address(post, options) + if address = options[:billing_address] || options[:address] + post[:address1] = address[:address1].to_s + post[:address2] = address[:address2].to_s + post[:city] = address[:city].to_s + post[:state] = (address[:state].blank? ? 'n/a' : address[:state]) + post[:zip] = address[:zip].to_s + post[:country] = address[:country].to_s + post[:phone] = address[:phone].to_s + post[:fax] = address[:fax].to_s + post[:email] = address[:email].to_s + end + + if address = options[:shipping_address] + post[:shipping_firstname] = address[:first_name].to_s + post[:shipping_lastname] = address[:last_name].to_s + post[:shipping_company] = address[:company].to_s + post[:shipping_address1] = address[:address1].to_s + post[:shipping_address2] = address[:address2].to_s + post[:shipping_city] = address[:city].to_s + post[:shipping_country] = address[:country].to_s + post[:shipping_state] = (address[:state].blank? ? 'n/a' : address[:state]) + post[:shipping_zip] = address[:zip].to_s + post[:shipping_email] = address[:email].to_s + end + end + + def add_currency(post, money, options) + post[:currency] = options[:currency] || currency(money) + end + + def add_invoice(post, options) + post[:orderdescription] = options[:description] + post[:orderid] = options[:order_id] + end + + def add_creditcard(post, creditcard) + post[:ccnumber] = creditcard.number + post[:cvv] = creditcard.verification_value if creditcard.verification_value? + post[:ccexp] = expdate(creditcard) + post[:firstname] = creditcard.first_name + post[:lastname] = creditcard.last_name + end + + def parse(body) + Hash[body.split('&').map { |x| x.split('=') }] + end + + def commit(action, money, parameters) + parameters[:amount] = amount(money) unless action == 'void' + url = (test? ? self.test_url : self.live_url) + data = ssl_post(url, post_data(action, parameters)) + + response = parse(data) + response[:action] = action + + message = message_from(response) + test_mode = (test? || message =~ /TESTMODE/) + Response.new(success?(response), message, response, + :test => test_mode, + :authorization => response['transactionid'], + :fraud_review => fraud_review?(response), + :avs_result => { :code => response['avsresponse'] }, + :cvv_result => response['cvvresponse'] + ) + end + + def message_from(response) + case response['response'] + when '1' + 'The transaction has been approved' + when '2' + 'The transaction has been declined' + when '3' + response['responsetext'] + else + 'There was an error processing the transaction' + end + end + + def fraud_review?(response) + false + end + + def success?(response) + (response['response'] == '1') + end + + def post_data(action, parameters = {}) + post = {} + + post[:username] = @options[:username] + post[:password] = @options[:password] + post[:type] = action + + request = post.merge(parameters).collect { |key, value| "#{key}=#{CGI.escape(value.to_s)}" }.join('&') + request + end + end + end +end diff --git a/lib/active_merchant/billing/gateways/paystation.rb b/lib/active_merchant/billing/gateways/paystation.rb index af851810171..0c2afac4358 100644 --- a/lib/active_merchant/billing/gateways/paystation.rb +++ b/lib/active_merchant/billing/gateways/paystation.rb @@ -2,7 +2,7 @@ module ActiveMerchant #:nodoc: module Billing #:nodoc: class PaystationGateway < Gateway - self.live_url = self.test_url = "https://www.paystation.co.nz/direct/paystation.dll" + self.live_url = self.test_url = 'https://www.paystation.co.nz/direct/paystation.dll' # an "error code" of "0" means "No error - transaction successful" SUCCESSFUL_RESPONSE_CODE = '0' @@ -32,9 +32,7 @@ def authorize(money, credit_card, options = {}) add_invoice(post, options) add_amount(post, money, options) - add_credit_card(post, credit_card) - add_authorize_flag(post, options) commit(post) @@ -45,7 +43,6 @@ def capture(money, authorization_token, options = {}) add_invoice(post, options) add_amount(post, money, options) - add_authorization_token(post, authorization_token, options[:credit_card_verification]) commit(post) @@ -78,122 +75,133 @@ def store(credit_card, options = {}) commit(post) end - private - - def new_request - { - :pi => @options[:paystation_id], # paystation account id - :gi => @options[:gateway_id], # paystation gateway id - "2p" => "t", # two-party transaction type - :nr => "t", # -- redirect?? - :df => "yymm" # date format: optional sometimes, required others - } - end - - def add_customer_data(post, options) - post[:mc] = options[:customer] - end + def refund(money, authorization, options={}) + post = new_request + add_amount(post, money, options) + add_invoice(post, options) + add_refund_specific_fields(post, authorization) - def add_invoice(post, options) - requires!(options, :order_id) + commit(post) + end - post[:ms] = options[:order_id] # "Merchant Session", must be unique per request - post[:mo] = options[:invoice] # "Order Details", displayed in Paystation Admin - post[:mr] = options[:description] # "Merchant Reference Code", seen from Paystation Admin - end + def verify(credit_card, options={}) + authorize(0, credit_card, options) + end - def add_credit_card(post, credit_card) + def supports_scrubbing? + true + end - post[:cn] = credit_card.number - post[:ct] = credit_card.brand - post[:ex] = format_date(credit_card.month, credit_card.year) - post[:cc] = credit_card.verification_value if credit_card.verification_value? + def scrub(transcript) + transcript. + gsub(%r((pstn_cn=)\d*), '\1[FILTERED]'). + gsub(%r((pstn_cc=)\d*), '\1[FILTERED]') + end - end + private - # bill a token (stored via "store") rather than a Credit Card - def add_token(post, token) - post[:fp] = "t" # turn on "future payments" - what paystation calls Token Billing - post[:ft] = token - end + def new_request + { + :pi => @options[:paystation_id], # paystation account id + :gi => @options[:gateway_id], # paystation gateway id + '2p' => 't', # two-party transaction type + :nr => 't', # -- redirect?? + :df => 'yymm' # date format: optional sometimes, required others + } + end - def store_credit_card(post, options) + def add_customer_data(post, options) + post[:mc] = options[:customer] + end - post[:fp] = "t" # turn on "future payments" - what paystation calls Token Billing - post[:fs] = "t" # tells paystation to store right now, not bill - post[:ft] = options[:token] if options[:token] # specify a token to use that, or let Paystation generate one + def add_invoice(post, options) + post[:ms] = generate_unique_id + post[:mo] = options[:description] + post[:mr] = options[:order_id] + end - end + def add_credit_card(post, credit_card) + post[:cn] = credit_card.number + post[:ct] = credit_card.brand + post[:ex] = format_date(credit_card.month, credit_card.year) + post[:cc] = credit_card.verification_value if credit_card.verification_value? + end - def add_authorize_flag(post, options) - post[:pa] = "t" # tells Paystation that this is a pre-auth authorisation payment (account must be in pre-auth mode) - end + def add_token(post, token) + post[:fp] = 't' # turn on "future payments" - what paystation calls Token Billing + post[:ft] = token + end - def add_authorization_token(post, auth_token, verification_value = nil) - post[:cp] = "t" # Capture Payment flag – tells Paystation this transaction should be treated as a capture payment - post[:cx] = auth_token - post[:cc] = verification_value - end + def store_credit_card(post, options) + post[:fp] = 't' # turn on "future payments" - what paystation calls Token Billing + post[:fs] = 't' # tells paystation to store right now, not bill + post[:ft] = options[:token] if options[:token] # specify a token to use that, or let Paystation generate one + end - def add_amount(post, money, options) + def add_authorize_flag(post, options) + post[:pa] = 't' # tells Paystation that this is a pre-auth authorisation payment (account must be in pre-auth mode) + end - post[:am] = amount(money) - post[:cu] = options[:currency] || currency(money) + def add_refund_specific_fields(post, authorization) + post[:rc] = 't' + post[:rt] = authorization + end - end + def add_authorization_token(post, auth_token, verification_value = nil) + post[:cp] = 't' # Capture Payment flag – tells Paystation this transaction should be treated as a capture payment + post[:cx] = auth_token + post[:cc] = verification_value + end - def parse(xml_response) - response = {} + def add_amount(post, money, options) + post[:am] = amount(money) + post[:cu] = options[:currency] || currency(money) + end - xml = REXML::Document.new(xml_response) + def parse(xml_response) + response = {} - # for normal payments, the root node is - # for "future payments", it's - xml.elements.each("#{xml.root.name}/*") do |element| - response[element.name.underscore.to_sym] = element.text - end + xml = REXML::Document.new(xml_response) - response + xml.elements.each("#{xml.root.name}/*") do |element| + response[element.name.underscore.to_sym] = element.text end - def commit(post) - - post[:tm] = "T" if test? # test mode + response + end - pstn_prefix_params = post.collect { |key, value| "pstn_#{key}=#{CGI.escape(value.to_s)}" }.join("&") + def commit(post) + post[:tm] = 'T' if test? + pstn_prefix_params = post.collect { |key, value| "pstn_#{key}=#{CGI.escape(value.to_s)}" }.join('&') - # need include paystation param as "initiator flag for payment engine" - data = ssl_post(self.live_url, "#{pstn_prefix_params}&paystation=_empty") - response = parse(data) - message = message_from(response) + data = ssl_post(self.live_url, "#{pstn_prefix_params}&paystation=_empty") + response = parse(data) + message = message_from(response) - PaystationResponse.new(success?(response), message, response, - :test => (response[:tm] && response[:tm].downcase == "t"), - :authorization => response[:paystation_transaction_id] - ) - end + PaystationResponse.new(success?(response), message, response, + :test => (response[:tm]&.casecmp('t')&.zero?), + :authorization => response[:paystation_transaction_id] + ) + end - def success?(response) - (response[:ec] == SUCCESSFUL_RESPONSE_CODE) || (response[:ec] == SUCCESSFUL_FUTURE_PAYMENT) - end + def success?(response) + (response[:ec] == SUCCESSFUL_RESPONSE_CODE) || (response[:ec] == SUCCESSFUL_FUTURE_PAYMENT) + end - def message_from(response) - response[:em] - end + def message_from(response) + response[:em] + end - def format_date(month, year) - "#{format(year, :two_digits)}#{format(month, :two_digits)}" - end + def format_date(month, year) + "#{format(year, :two_digits)}#{format(month, :two_digits)}" + end end class PaystationResponse < Response - # add a method to response so we can easily get the token - # for Validate transactions def token - @params["future_payment_token"] + @params['future_payment_token'] end end end end - diff --git a/lib/active_merchant/billing/gateways/payu_in.rb b/lib/active_merchant/billing/gateways/payu_in.rb new file mode 100644 index 00000000000..eabc32c1cd6 --- /dev/null +++ b/lib/active_merchant/billing/gateways/payu_in.rb @@ -0,0 +1,248 @@ +# encoding: utf-8 + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class PayuInGateway < Gateway + self.test_url = 'https://test.payu.in/_payment' + self.live_url = 'https://secure.payu.in/_payment' + + TEST_INFO_URL = 'https://test.payu.in/merchant/postservice.php?form=2' + LIVE_INFO_URL = 'https://info.payu.in/merchant/postservice.php?form=2' + + self.supported_countries = ['IN'] + self.default_currency = 'INR' + self.supported_cardtypes = [:visa, :master, :american_express, :diners_club, :maestro] + + self.homepage_url = 'https://www.payu.in/' + self.display_name = 'PayU India' + + def initialize(options={}) + requires!(options, :key, :salt) + super + end + + def purchase(money, payment, options={}) + requires!(options, :order_id) + + post = {} + add_invoice(post, money, options) + add_payment(post, payment) + add_addresses(post, options) + add_customer_data(post, options) + add_auth(post) + + MultiResponse.run do |r| + r.process { commit(url('purchase'), post) } + if(r.params['enrolled'].to_s == '0') + r.process { commit(r.params['post_uri'], r.params['form_post_vars']) } + else + r.process { handle_3dsecure(r) } + end + end + end + + def refund(money, authorization, options={}) + raise ArgumentError, 'Amount is required' unless money + + post = {} + + post[:command] = 'cancel_refund_transaction' + post[:var1] = authorization + post[:var2] = generate_unique_id + post[:var3] = amount(money) + + add_auth(post, :command, :var1) + + commit(url('refund'), post) + end + + def supports_scrubbing? + true + end + + def scrub(transcript) + transcript. + gsub(/(ccnum=)[^&\n"]*(&|\n|"|$)/, '\1[FILTERED]\2'). + gsub(/(ccvv=)[^&\n"]*(&|\n|"|$)/, '\1[FILTERED]\2'). + gsub(/(card_hash=)[^&\n"]*(&|\n|"|$)/, '\1[FILTERED]\2'). + gsub(/(ccnum":")[^"]*(")/, '\1[FILTERED]\2'). + gsub(/(ccvv":")[^"]*(")/, '\1[FILTERED]\2') + end + + private + + PAYMENT_DIGEST_KEYS = %w( + txnid amount productinfo firstname email + udf1 udf2 udf3 udf4 udf5 + bogus bogus bogus bogus bogus + ) + def add_auth(post, *digest_keys) + post[:key] = @options[:key] + post[:txn_s2s_flow] = 1 + + digest_keys = PAYMENT_DIGEST_KEYS if digest_keys.empty? + digest = Digest::SHA2.new(512) + digest << @options[:key] << '|' + digest_keys.each do |key| + digest << (post[key.to_sym] || '') << '|' + end + digest << @options[:salt] + post[:hash] = digest.hexdigest + end + + def add_customer_data(post, options) + post[:email] = clean(options[:email] || 'unknown@example.com', nil, 50) + post[:phone] = clean((options[:billing_address] && options[:billing_address][:phone]) || '11111111111', :numeric, 50) + end + + def add_addresses(post, options) + if options[:billing_address] + post[:address1] = clean(options[:billing_address][:address1], :text, 100) + post[:address2] = clean(options[:billing_address][:address2], :text, 100) + post[:city] = clean(options[:billing_address][:city], :text, 50) + post[:state] = clean(options[:billing_address][:state], :text, 50) + post[:country] = clean(options[:billing_address][:country], :text, 50) + post[:zipcode] = clean(options[:billing_address][:zip], :numeric, 20) + end + + if options[:shipping_address] + if options[:shipping_address][:name] + first, *rest = options[:shipping_address][:name].split(/\s+/) + post[:shipping_firstname] = clean(first, :name, 60) + post[:shipping_lastname] = clean(rest.join(' '), :name, 20) + end + post[:shipping_address1] = clean(options[:shipping_address][:address1], :text, 100) + post[:shipping_address2] = clean(options[:shipping_address][:address2], :text, 100) + post[:shipping_city] = clean(options[:shipping_address][:city], :text, 50) + post[:shipping_state] = clean(options[:shipping_address][:state], :text, 50) + post[:shipping_country] = clean(options[:shipping_address][:country], :text, 50) + post[:shipping_zipcode] = clean(options[:shipping_address][:zip], :numeric, 20) + post[:shipping_phone] = clean(options[:shipping_address][:phone], :numeric, 50) + end + end + + def add_invoice(post, money, options) + post[:amount] = amount(money) + + post[:txnid] = clean(options[:order_id], :alphanumeric, 30) + post[:productinfo] = clean(options[:description] || 'Purchase', nil, 100) + + post[:surl] = 'http://example.com' + post[:furl] = 'http://example.com' + end + + BRAND_MAP = { + visa: 'VISA', + master: 'MAST', + american_express: 'AMEX', + diners_club: 'DINR', + maestro: 'MAES' + } + + def add_payment(post, payment) + post[:pg] = 'CC' + post[:firstname] = clean(payment.first_name, :name, 60) + post[:lastname] = clean(payment.last_name, :name, 20) + + post[:bankcode] = BRAND_MAP[payment.brand.to_sym] + post[:ccnum] = payment.number + post[:ccvv] = payment.verification_value + post[:ccname] = payment.name + post[:ccexpmon] = format(payment.month, :two_digits) + post[:ccexpyr] = format(payment.year, :four_digits) + end + + def clean(value, format, maxlength) + value ||= '' + value = case format + when :alphanumeric + value.gsub(/[^A-Za-z0-9]/, '') + when :name + value.gsub(/[^A-Za-z ]/, '') + when :numeric + value.gsub(/[^0-9]/, '') + when :text + value.gsub(/[^A-Za-z0-9@\-_\/\. ]/, '') + when nil + value + else + raise "Unknown format #{format} for #{value}" + end + value[0...maxlength] + end + + def parse(body) + top = JSON.parse(body) + + if result = top.delete('result') + result.split('&').inject({}) do |hash, string| + key, value = string.split('=') + hash[CGI.unescape(key).downcase] = CGI.unescape(value || '') + hash + end.each do |key, value| + if top[key] + top["result_#{key}"] = value + else + top[key] = value + end + end + end + + if response = top.delete('response') + top.merge!(response) + end + + top + rescue JSON::ParserError + { + 'error' => "Invalid response received from the PayU API. (The raw response was `#{body}`)." + } + end + + def commit(url, parameters) + response = parse(ssl_post(url, post_data(parameters), 'Accept-Encoding' => 'identity')) + + Response.new( + success_from(response), + message_from(response), + response, + authorization: authorization_from(response), + test: test? + ) + end + + def url(action) + case action + when 'purchase' + (test? ? test_url : live_url) + else + (test? ? TEST_INFO_URL : LIVE_INFO_URL) + end + end + + def success_from(response) + if response['result_status'] + (response['status'] == 'success' && response['result_status'] == 'success') + else + (response['status'] == 'success' || response['status'].to_s == '1') + end + end + + def message_from(response) + (response['error_message'] || response['error'] || response['msg']) + end + + def authorization_from(response) + response['mihpayid'] + end + + def post_data(parameters = {}) + PostData.new.merge!(parameters).to_post_data + end + + def handle_3dsecure(response) + Response.new(false, '3D-secure enrolled cards are not supported.') + end + end + end +end diff --git a/lib/active_merchant/billing/gateways/payu_latam.rb b/lib/active_merchant/billing/gateways/payu_latam.rb new file mode 100644 index 00000000000..aef7ea42e89 --- /dev/null +++ b/lib/active_merchant/billing/gateways/payu_latam.rb @@ -0,0 +1,449 @@ +require 'digest/md5' + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class PayuLatamGateway < Gateway + self.display_name = 'PayU Latam' + self.homepage_url = 'http://www.payulatam.com' + + self.test_url = 'https://sandbox.api.payulatam.com/payments-api/4.0/service.cgi' + self.live_url = 'https://api.payulatam.com/payments-api/4.0/service.cgi' + + self.supported_countries = ['AR', 'BR', 'CL', 'CO', 'MX', 'PA', 'PE'] + self.default_currency = 'USD' + self.money_format = :dollars + self.supported_cardtypes = [:visa, :master, :american_express, :diners_club, :naranja, :cabal] + + BRAND_MAP = { + 'visa' => 'VISA', + 'master' => 'MASTERCARD', + 'american_express' => 'AMEX', + 'diners_club' => 'DINERS', + 'naranja' => 'NARANJA', + 'cabal' => 'CABAL' + } + + MINIMUMS = { + 'ARS' => 1700, + 'BRL' => 600, + 'MXN' => 3900, + 'PEN' => 500 + } + + def initialize(options={}) + requires!(options, :merchant_id, :account_id, :api_login, :api_key, :payment_country) + super + end + + def purchase(amount, payment_method, options={}) + post = {} + auth_or_sale(post, 'AUTHORIZATION_AND_CAPTURE', amount, payment_method, options) + commit('purchase', post) + end + + def authorize(amount, payment_method, options={}) + post = {} + auth_or_sale(post, 'AUTHORIZATION', amount, payment_method, options) + commit('auth', post) + end + + def capture(amount, authorization, options={}) + post = {} + + add_credentials(post, 'SUBMIT_TRANSACTION', options) + add_transaction_elements(post, 'CAPTURE', options) + add_reference(post, authorization) + + if !amount.nil? && amount.to_f != 0.0 + post[:transaction][:additionalValues] ||= {} + post[:transaction][:additionalValues][:TX_VALUE] = invoice_for(amount, options)[:TX_VALUE] + end + + commit('capture', post) + end + + def void(authorization, options={}) + post = {} + + add_credentials(post, 'SUBMIT_TRANSACTION', options) + add_transaction_elements(post, 'VOID', options) + add_reference(post, authorization) + + commit('void', post) + end + + def refund(amount, authorization, options={}) + post = {} + + add_credentials(post, 'SUBMIT_TRANSACTION', options) + add_transaction_elements(post, 'REFUND', options) + add_reference(post, authorization) + + commit('refund', post) + end + + def verify(credit_card, options={}) + minimum = MINIMUMS[options[:currency].upcase] if options[:currency] + amount = options[:verify_amount] || minimum || 100 + + MultiResponse.run(:use_first_response) do |r| + r.process { authorize(amount, credit_card, options) } + r.process(:ignore_result) { void(r.authorization, options) } + end + end + + def store(payment_method, options = {}) + post = {} + + add_credentials(post, 'CREATE_TOKEN') + add_payment_method_to_be_tokenized(post, payment_method) + + commit('store', post) + end + + def verify_credentials + post = {} + add_credentials(post, 'GET_PAYMENT_METHODS') + response = commit('verify_credentials', post) + response.success? + end + + def supports_scrubbing? + true + end + + def scrub(transcript) + transcript. + gsub(%r((\"creditCard\\\":{\\\"number\\\":\\\")\d+), '\1[FILTERED]'). + gsub(%r((\"securityCode\\\":\\\")\d+), '\1[FILTERED]'). + gsub(%r((\"apiKey\\\":\\\")\w+), '\1[FILTERED]') + end + + private + + def auth_or_sale(post, transaction_type, amount, payment_method, options) + add_credentials(post, 'SUBMIT_TRANSACTION', options) + add_transaction_elements(post, transaction_type, options) + add_order(post, options) + add_buyer(post, payment_method, options) + add_invoice(post, amount, options) + add_signature(post) + add_payment_method(post, payment_method, options) + add_payer(post, payment_method, options) + add_extra_parameters(post, options) + end + + def add_credentials(post, command, options={}) + post[:test] = test? unless command == 'CREATE_TOKEN' + post[:language] = options[:language] || 'en' + post[:command] = command + merchant = {} + merchant[:apiLogin] = @options[:api_login] + merchant[:apiKey] = @options[:api_key] + post[:merchant] = merchant + end + + def add_transaction_elements(post, type, options) + transaction = {} + transaction[:paymentCountry] = @options[:payment_country] + transaction[:type] = type + transaction[:ipAddress] = options[:ip] || '' + transaction[:userAgent] = options[:user_agent] if options[:user_agent] + transaction[:cookie] = options[:cookie] if options[:cookie] + transaction[:deviceSessionId] = options[:device_session_id] if options[:device_session_id] + post[:transaction] = transaction + end + + def add_order(post, options) + order = {} + order[:accountId] = @options[:account_id] + order[:partnerId] = options[:partner_id] if options[:partner_id] + order[:referenceCode] = options[:order_id] || generate_unique_id + order[:description] = options[:description] || 'Compra en ' + @options[:merchant_id] + order[:language] = options[:language] || 'en' + order[:shippingAddress] = shipping_address_fields(options) if options[:shipping_address] + post[:transaction][:order] = order + end + + def add_payer(post, payment_method, options) + address = options[:billing_address] + payer = {} + payer[:fullName] = payment_method.name.strip + payer[:contactPhone] = address[:phone] if address && address[:phone] + payer[:dniNumber] = options[:dni_number] if options[:dni_number] + payer[:dniType] = options[:dni_type] if options[:dni_type] + payer[:emailAddress] = options[:email] if options[:email] + payer[:birthdate] = options[:birth_date] if options[:birth_date] && @options[:payment_country] == 'MX' + payer[:billingAddress] = billing_address_fields(options) + post[:transaction][:payer] = payer + end + + def billing_address_fields(options) + return unless address = options[:billing_address] + billing_address = {} + billing_address[:street1] = address[:address1] + billing_address[:street2] = address[:address2] + billing_address[:city] = address[:city] + billing_address[:state] = address[:state] + billing_address[:country] = address[:country] + billing_address[:postalCode] = address[:zip] if @options[:payment_country] == 'MX' + billing_address[:phone] = address[:phone] + billing_address + end + + def add_buyer(post, payment_method, options) + buyer = {} + if buyer_hash = options[:buyer] + buyer[:fullName] = buyer_hash[:name] + buyer[:dniNumber] = buyer_hash[:dni_number] + buyer[:dniType] = buyer_hash[:dni_type] + buyer[:merchantBuyerId] = buyer_hash[:merchant_buyer_id] + buyer[:cnpj] = buyer_hash[:cnpj] if @options[:payment_country] == 'BR' + buyer[:emailAddress] = buyer_hash[:email] + buyer[:contactPhone] = (options[:billing_address][:phone] if options[:billing_address]) || (options[:shipping_address][:phone] if options[:shipping_address]) || '' + buyer[:shippingAddress] = shipping_address_fields(options) if options[:shipping_address] + else + buyer[:fullName] = payment_method.name.strip + buyer[:dniNumber] = options[:dni_number] + buyer[:dniType] = options[:dni_type] + buyer[:merchantBuyerId] = options[:merchant_buyer_id] + buyer[:cnpj] = options[:cnpj] if @options[:payment_country] == 'BR' + buyer[:emailAddress] = options[:email] + buyer[:contactPhone] = (options[:billing_address][:phone] if options[:billing_address]) || (options[:shipping_address][:phone] if options[:shipping_address]) || '' + buyer[:shippingAddress] = shipping_address_fields(options) if options[:shipping_address] + end + post[:transaction][:order][:buyer] = buyer + end + + def shipping_address_fields(options) + return unless address = options[:shipping_address] + shipping_address = {} + shipping_address[:street1] = address[:address1] + shipping_address[:street2] = address[:address2] + shipping_address[:city] = address[:city] + shipping_address[:state] = address[:state] + shipping_address[:country] = address[:country] + shipping_address[:postalCode] = address[:zip] + shipping_address[:phone] = address[:phone] + shipping_address + end + + def add_invoice(post, money, options) + post[:transaction][:order][:additionalValues] = invoice_for(money, options) + end + + def invoice_for(money, options) + tx_value = {} + tx_value[:value] = amount(money) + tx_value[:currency] = options[:currency] || currency(money) + + tx_tax = {} + tx_tax[:value] = options[:tax] || '0' + tx_tax[:currency] = options[:currency] || currency(money) + + tx_tax_return_base = {} + tx_tax_return_base[:value] = options[:tax_return_base] || '0' + tx_tax_return_base[:currency] = options[:currency] || currency(money) + + additional_values = {} + additional_values[:TX_VALUE] = tx_value + additional_values[:TX_TAX] = tx_tax if @options[:payment_country] == 'CO' + additional_values[:TX_TAX_RETURN_BASE] = tx_tax_return_base if @options[:payment_country] == 'CO' + + additional_values + end + + def add_signature(post) + post[:transaction][:order][:signature] = signature_from(post) + end + + def signature_from(post) + signature_string = [ + @options[:api_key], + @options[:merchant_id], + post[:transaction][:order][:referenceCode], + post[:transaction][:order][:additionalValues][:TX_VALUE][:value], + post[:transaction][:order][:additionalValues][:TX_VALUE][:currency] + ].compact.join('~') + + Digest::MD5.hexdigest(signature_string) + end + + def add_payment_method(post, payment_method, options) + if payment_method.is_a?(String) + brand, token = split_authorization(payment_method) + credit_card = {} + credit_card[:securityCode] = options[:cvv] if options[:cvv] + credit_card[:processWithoutCvv2] = true if options[:cvv].blank? + post[:transaction][:creditCard] = credit_card + post[:transaction][:creditCardTokenId] = token + post[:transaction][:paymentMethod] = brand.upcase + else + credit_card = {} + credit_card[:number] = payment_method.number + credit_card[:securityCode] = payment_method.verification_value || options[:cvv] + credit_card[:expirationDate] = format(payment_method.year, :four_digits).to_s + '/' + format(payment_method.month, :two_digits).to_s + credit_card[:name] = payment_method.name.strip + credit_card[:processWithoutCvv2] = true if add_process_without_cvv2(payment_method, options) + post[:transaction][:creditCard] = credit_card + post[:transaction][:paymentMethod] = BRAND_MAP[payment_method.brand.to_s] + end + end + + def add_process_without_cvv2(payment_method, options) + return true if payment_method.verification_value.blank? && options[:cvv].blank? + false + end + + def add_extra_parameters(post, options) + extra_parameters = {} + extra_parameters[:INSTALLMENTS_NUMBER] = options[:installments_number] || 1 + post[:transaction][:extraParameters] = extra_parameters + end + + def add_reference(post, authorization) + order_id, transaction_id = split_authorization(authorization) + order = {} + order[:id] = order_id + post[:transaction][:order] = order + post[:transaction][:parentTransactionId] = transaction_id + post[:transaction][:reason] = 'n/a' + end + + def add_payment_method_to_be_tokenized(post, payment_method) + credit_card_token = {} + credit_card_token[:payerId] = generate_unique_id + credit_card_token[:name] = payment_method.name.strip + credit_card_token[:identificationNumber] = generate_unique_id + credit_card_token[:paymentMethod] = BRAND_MAP[payment_method.brand.to_s] + credit_card_token[:number] = payment_method.number + credit_card_token[:expirationDate] = format(payment_method.year, :four_digits).to_s + '/' + format(payment_method.month, :two_digits).to_s + credit_card_token[:securityCode] = payment_method.verification_value + post[:creditCardToken] = credit_card_token + end + + def commit(action, params) + raw_response = ssl_post(url, post_data(params), headers) + response = parse(raw_response) + rescue ResponseError => e + raw_response = e.response.body + response_error(raw_response) + rescue JSON::ParserError + unparsable_response(raw_response) + else + success = success_from(action, response) + Response.new( + success, + message_from(action, success, response), + response, + authorization: success ? authorization_from(action, response) : nil, + error_code: success ? nil : error_from(action, response), + test: test? + ) + end + + def headers + { + 'Content-Type' => 'application/json', + 'Accept' => 'application/json' + } + end + + def post_data(params) + params.merge(test: test?) + params.to_json + end + + def url + test? ? test_url : live_url + end + + def parse(body) + JSON.parse(body) + end + + def success_from(action, response) + case action + when 'store' + response['code'] == 'SUCCESS' && response['creditCardToken'] && response['creditCardToken']['creditCardTokenId'].present? + when 'verify_credentials' + response['code'] == 'SUCCESS' + when 'refund', 'void' + response['code'] == 'SUCCESS' && response['transactionResponse'] && (response['transactionResponse']['state'] == 'PENDING' || response['transactionResponse']['state'] == 'APPROVED') + else + response['code'] == 'SUCCESS' && response['transactionResponse'] && (response['transactionResponse']['state'] == 'APPROVED') + end + end + + def message_from(action, success, response) + case action + when 'store' + return response['code'] if success + error_description = response['creditCardToken']['errorDescription'] if response['creditCardToken'] + response['error'] || error_description || 'FAILED' + when 'verify_credentials' + return 'VERIFIED' if success + 'FAILED' + else + if response['transactionResponse'] + response_message = response['transactionResponse']['responseMessage'] + response_code = response['transactionResponse']['responseCode'] || response['transactionResponse']['pendingReason'] + end + return response_code if success + response['error'] || response_message || response_code || 'FAILED' + end + end + + def authorization_from(action, response) + case action + when 'store' + [ + response['creditCardToken']['paymentMethod'], + response['creditCardToken']['creditCardTokenId'] + ].compact.join('|') + when 'verify_credentials' + nil + else + [ + response['transactionResponse']['orderId'], + response['transactionResponse']['transactionId'] + ].compact.join('|') + end + end + + def split_authorization(authorization) + authorization.split('|') + end + + def error_from(action, response) + case action + when 'store' + response['creditCardToken']['errorDescription'] if response['creditCardToken'] + when 'verify_credentials' + response['error'] || 'FAILED' + else + response['transactionResponse']['errorCode'] || response['transactionResponse']['responseCode'] if response['transactionResponse'] + end + end + + def response_error(raw_response) + response = parse(raw_response) + rescue JSON::ParserError + unparsable_response(raw_response) + else + return Response.new( + false, + message_from('', false, response), + response, + :test => test? + ) + end + + def unparsable_response(raw_response) + message = 'Invalid JSON response received from PayuLatamGateway. Please contact PayuLatamGateway if you continue to receive this message.' + message += " (The raw response returned by the API was #{raw_response.inspect})" + return Response.new(false, message) + end + end + end +end diff --git a/lib/active_merchant/billing/gateways/payway.rb b/lib/active_merchant/billing/gateways/payway.rb index 10777f7b00a..b80e5f937a1 100644 --- a/lib/active_merchant/billing/gateways/payway.rb +++ b/lib/active_merchant/billing/gateways/payway.rb @@ -150,7 +150,7 @@ def add_payment_method(post, payment_method) post['card.cardHolderName'] = "#{payment_method.first_name} #{payment_method.last_name}" post['card.PAN'] = payment_method.number post['card.CVN'] = payment_method.verification_value - post['card.expiryYear'] = payment_method.year.to_s[-2,2] + post['card.expiryYear'] = payment_method.year.to_s[-2, 2] post['card.expiryMonth'] = sprintf('%02d', payment_method.month) else post['customer.customerReferenceNumber'] = payment_method @@ -177,30 +177,30 @@ def add_auth(post) # Creates the request and returns the summarized result def commit(action, post) add_auth(post) - post.merge!('order.type' => TRANSACTIONS[action]) + post['order.type'] = TRANSACTIONS[action] - request = post.map { |k, v| "#{k}=#{CGI.escape(v.to_s)}" }.join("&") + request = post.map { |k, v| "#{k}=#{CGI.escape(v.to_s)}" }.join('&') response = ssl_post(self.live_url, request) params = {} CGI.parse(response).each_pair do |key, value| - actual_key = key.split(".").last + actual_key = key.split('.').last params[actual_key.underscore.to_sym] = value[0] end message = "#{SUMMARY_CODES[params[:summary_code]]} - #{RESPONSE_CODES[params[:response_code]]}" - success = (params[:summary_code] ? (params[:summary_code] == "0") : (params[:response_code] == "00")) + success = (params[:summary_code] ? (params[:summary_code] == '0') : (params[:response_code] == '00')) Response.new(success, message, params, - :test => (@options[:merchant].to_s == "TEST"), + :test => (@options[:merchant].to_s == 'TEST'), :authorization => post[:order_number] ) rescue ActiveMerchant::ResponseError => e raise unless e.response.code == '403' - return Response.new(false, "Invalid credentials", {}, :test => test?) + return Response.new(false, 'Invalid credentials', {}, :test => test?) rescue ActiveMerchant::ClientCertificateError - return Response.new(false, "Invalid certificate", {}, :test => test?) + return Response.new(false, 'Invalid certificate', {}, :test => test?) end end end diff --git a/lib/active_merchant/billing/gateways/pin.rb b/lib/active_merchant/billing/gateways/pin.rb index 8e1c2ffd0be..89e92895980 100644 --- a/lib/active_merchant/billing/gateways/pin.rb +++ b/lib/active_merchant/billing/gateways/pin.rb @@ -1,15 +1,15 @@ module ActiveMerchant #:nodoc: module Billing #:nodoc: class PinGateway < Gateway - self.test_url = 'https://test-api.pin.net.au/1' - self.live_url = 'https://api.pin.net.au/1' + self.test_url = 'https://test-api.pinpayments.com/1' + self.live_url = 'https://api.pinpayments.com/1' self.default_currency = 'AUD' self.money_format = :cents self.supported_countries = ['AU'] - self.supported_cardtypes = [:visa, :master] - self.homepage_url = 'http://www.pin.net.au/' - self.display_name = 'Pin' + self.supported_cardtypes = [:visa, :master, :american_express] + self.homepage_url = 'http://www.pinpayments.com/' + self.display_name = 'Pin Payments' def initialize(options = {}) requires!(options, :api_key) @@ -28,8 +28,10 @@ def purchase(money, creditcard, options = {}) add_invoice(post, options) add_creditcard(post, creditcard) add_address(post, creditcard, options) + add_capture(post, options) + add_metadata(post, options) - commit('charges', post, options) + commit(:post, 'charges', post, options) end # Create a customer and associated credit card. The token that is returned @@ -40,17 +42,52 @@ def store(creditcard, options = {}) add_creditcard(post, creditcard) add_customer_data(post, options) add_address(post, creditcard, options) - commit('customers', post, options) + commit(:post, 'customers', post, options) end - # Refund a transaction, note that the money attribute is ignored at the - # moment as the API does not support partial refunds. The parameter is - # kept for compatibility reasons + # Refund a transaction def refund(money, token, options = {}) - commit("charges/#{CGI.escape(token)}/refunds", { :amount => amount(money) }, options) + commit(:post, "charges/#{CGI.escape(token)}/refunds", { :amount => amount(money) }, options) + end + + # Authorize an amount on a credit card. Once authorized, you can later + # capture this charge using the charge token that is returned. + def authorize(money, creditcard, options = {}) + options[:capture] = false + + purchase(money, creditcard, options) + end + + # Captures a previously authorized charge. Capturing only part of the original + # authorization is currently not supported. + def capture(money, token, options = {}) + commit(:put, "charges/#{CGI.escape(token)}/capture", { :amount => amount(money) }, options) + end + + # Updates the credit card for the customer. + def update(token, creditcard, options = {}) + post = {} + token = get_customer_token(token) + + add_creditcard(post, creditcard) + add_customer_data(post, options) + add_address(post, creditcard, options) + commit(:put, "customers/#{CGI.escape(token)}", post, options) + end + + def supports_scrubbing + true + end + + def scrub(transcript) + transcript. + gsub(%r((Authorization: Basic )\w+), '\1[FILTERED]'). + gsub(/(number\\?":\\?")(\d*)/, '\1[FILTERED]'). + gsub(/(cvc\\?":\\?")(\d*)/, '\1[FILTERED]') end private + def add_amount(post, money, options) post[:amount] = amount(money) post[:currency] = (options[:currency] || currency(money)) @@ -58,8 +95,8 @@ def add_amount(post, money, options) end def add_customer_data(post, options) - post[:email] = options[:email] - post[:ip_address] = options[:ip] + post[:email] = options[:email] if options[:email] + post[:ip_address] = options[:ip] if options[:ip] end def add_address(post, creditcard, options) @@ -78,7 +115,14 @@ def add_address(post, creditcard, options) end def add_invoice(post, options) - post[:description] = options[:description] || "Active Merchant Purchase" + post[:description] = options[:description] || 'Active Merchant Purchase' + post[:reference] = options[:reference] if options[:reference] + end + + def add_capture(post, options) + capture = options[:capture] + + post[:capture] = capture != false end def add_creditcard(post, creditcard) @@ -90,21 +134,33 @@ def add_creditcard(post, creditcard) :expiry_month => creditcard.month, :expiry_year => creditcard.year, :cvc => creditcard.verification_value, - :name => "#{creditcard.first_name} #{creditcard.last_name}" + :name => creditcard.name ) elsif creditcard.kind_of?(String) if creditcard =~ /^card_/ - post[:card_token] = creditcard + post[:card_token] = get_card_token(creditcard) else post[:customer_token] = creditcard end end end + def get_customer_token(token) + token.split(/;(?=cus)/).last + end + + def get_card_token(token) + token.split(/;(?=cus)/).first + end + + def add_metadata(post, options) + post[:metadata] = options[:metadata] if options[:metadata] + end + def headers(params = {}) result = { - "Content-Type" => "application/json", - "Authorization" => "Basic #{Base64.strict_encode64(options[:api_key] + ':').strip}" + 'Content-Type' => 'application/json', + 'Authorization' => "Basic #{Base64.strict_encode64(options[:api_key] + ':').strip}" } result['X-Partner-Key'] = params[:partner_key] if params[:partner_key] @@ -112,24 +168,27 @@ def headers(params = {}) result end - def commit(action, params, options) + def commit(method, action, params, options) url = "#{test? ? test_url : live_url}/#{action}" begin - body = parse(ssl_post(url, post_data(params), headers(options))) + raw_response = ssl_request(method, url, post_data(params), headers(options)) + body = parse(raw_response) rescue ResponseError => e body = parse(e.response.body) end - if body["response"] + if body['response'] success_response(body) - elsif body["error"] + elsif body['error'] error_response(body) end + rescue JSON::ParserError + return unparsable_response(raw_response) end def success_response(body) - response = body["response"] + response = body['response'] Response.new( true, response['status_message'], @@ -149,8 +208,18 @@ def error_response(body) ) end + def unparsable_response(raw_response) + message = 'Invalid JSON response received from Pin Payments. Please contact support@pinpayments.com if you continue to receive this message.' + message += " (The raw response returned by the API was #{raw_response.inspect})" + return Response.new(false, message) + end + def token(response) - response['token'] + if response['token'].start_with?('cus') + "#{response.dig('card', 'token')};#{response['token']}" + else + response['token'] + end end def parse(body) diff --git a/lib/active_merchant/billing/gateways/plugnpay.rb b/lib/active_merchant/billing/gateways/plugnpay.rb index 8f743cebd7c..36becf69ff8 100644 --- a/lib/active_merchant/billing/gateways/plugnpay.rb +++ b/lib/active_merchant/billing/gateways/plugnpay.rb @@ -3,80 +3,80 @@ module Billing class PlugnpayGateway < Gateway class PlugnpayPostData < PostData # Fields that will be sent even if they are blank - self.required_fields = [ :publisher_name, :publisher_password, - :card_amount, :card_name, :card_number, :card_exp, :orderID ] + self.required_fields = [:publisher_name, :publisher_password, + :card_amount, :card_name, :card_number, :card_exp, :orderID] end self.live_url = self.test_url = 'https://pay1.plugnpay.com/payment/pnpremote.cgi' CARD_CODE_MESSAGES = { - "M" => "Card verification number matched", - "N" => "Card verification number didn't match", - "P" => "Card verification number was not processed", - "S" => "Card verification number should be on card but was not indicated", - "U" => "Issuer was not certified for card verification" + 'M' => 'Card verification number matched', + 'N' => "Card verification number didn't match", + 'P' => 'Card verification number was not processed', + 'S' => 'Card verification number should be on card but was not indicated', + 'U' => 'Issuer was not certified for card verification' } CARD_CODE_ERRORS = %w( N S ) AVS_MESSAGES = { - "A" => "Street address matches billing information, zip/postal code does not", - "B" => "Address information not provided for address verification check", - "E" => "Address verification service error", - "G" => "Non-U.S. card-issuing bank", - "N" => "Neither street address nor zip/postal match billing information", - "P" => "Address verification not applicable for this transaction", - "R" => "Payment gateway was unavailable or timed out", - "S" => "Address verification service not supported by issuer", - "U" => "Address information is unavailable", - "W" => "9-digit zip/postal code matches billing information, street address does not", - "X" => "Street address and 9-digit zip/postal code matches billing information", - "Y" => "Street address and 5-digit zip/postal code matches billing information", - "Z" => "5-digit zip/postal code matches billing information, street address does not", + 'A' => 'Street address matches billing information, zip/postal code does not', + 'B' => 'Address information not provided for address verification check', + 'E' => 'Address verification service error', + 'G' => 'Non-U.S. card-issuing bank', + 'N' => 'Neither street address nor zip/postal match billing information', + 'P' => 'Address verification not applicable for this transaction', + 'R' => 'Payment gateway was unavailable or timed out', + 'S' => 'Address verification service not supported by issuer', + 'U' => 'Address information is unavailable', + 'W' => '9-digit zip/postal code matches billing information, street address does not', + 'X' => 'Street address and 9-digit zip/postal code matches billing information', + 'Y' => 'Street address and 5-digit zip/postal code matches billing information', + 'Z' => '5-digit zip/postal code matches billing information, street address does not', } AVS_ERRORS = %w( A E N R W Z ) PAYMENT_GATEWAY_RESPONSES = { - "P01" => "AVS Mismatch Failure", - "P02" => "CVV2 Mismatch Failure", - "P21" => "Transaction may not be marked", - "P30" => "Test Tran. Bad Card", - "P35" => "Test Tran. Problem", - "P40" => "Username already exists", - "P41" => "Username is blank", - "P50" => "Fraud Screen Failure", - "P51" => "Missing PIN Code", - "P52" => "Invalid Bank Acct. No.", - "P53" => "Invalid Bank Routing No.", - "P54" => "Invalid/Missing Check No.", - "P55" => "Invalid Credit Card No.", - "P56" => "Invalid CVV2/CVC2 No.", - "P57" => "Expired. CC Exp. Date", - "P58" => "Missing Data", - "P59" => "Missing Email Address", - "P60" => "Zip Code does not match Billing State.", - "P61" => "Invalid Billing Zip Code", - "P62" => "Zip Code does not match Shipping State.", - "P63" => "Invalid Shipping Zip Code", - "P64" => "Invalid Credit Card CVV2/CVC2 Format.", - "P65" => "Maximum number of attempts has been exceeded.", - "P66" => "Credit Card number has been flagged and can not be used to access this service.", - "P67" => "IP Address is on Blocked List.", - "P68" => "Billing country does not match ipaddress country.", - "P69" => "US based ipaddresses are currently blocked.", - "P70" => "Credit Cards issued from this bank are currently not being accepted.", - "P71" => "Credit Cards issued from this bank are currently not being accepted.", - "P72" => "Daily volume exceeded.", - "P73" => "Too many transactions within allotted time.", - "P91" => "Missing/incorrect password", - "P92" => "Account not configured for mobil administration", - "P93" => "IP Not registered to username.", - "P94" => "Mode not permitted for this account.", - "P95" => "Currently Blank", - "P96" => "Currently Blank", - "P97" => "Processor not responding", - "P98" => "Missing merchant/publisher name", - "P99" => "Currently Blank" + 'P01' => 'AVS Mismatch Failure', + 'P02' => 'CVV2 Mismatch Failure', + 'P21' => 'Transaction may not be marked', + 'P30' => 'Test Tran. Bad Card', + 'P35' => 'Test Tran. Problem', + 'P40' => 'Username already exists', + 'P41' => 'Username is blank', + 'P50' => 'Fraud Screen Failure', + 'P51' => 'Missing PIN Code', + 'P52' => 'Invalid Bank Acct. No.', + 'P53' => 'Invalid Bank Routing No.', + 'P54' => 'Invalid/Missing Check No.', + 'P55' => 'Invalid Credit Card No.', + 'P56' => 'Invalid CVV2/CVC2 No.', + 'P57' => 'Expired. CC Exp. Date', + 'P58' => 'Missing Data', + 'P59' => 'Missing Email Address', + 'P60' => 'Zip Code does not match Billing State.', + 'P61' => 'Invalid Billing Zip Code', + 'P62' => 'Zip Code does not match Shipping State.', + 'P63' => 'Invalid Shipping Zip Code', + 'P64' => 'Invalid Credit Card CVV2/CVC2 Format.', + 'P65' => 'Maximum number of attempts has been exceeded.', + 'P66' => 'Credit Card number has been flagged and can not be used to access this service.', + 'P67' => 'IP Address is on Blocked List.', + 'P68' => 'Billing country does not match ipaddress country.', + 'P69' => 'US based ipaddresses are currently blocked.', + 'P70' => 'Credit Cards issued from this bank are currently not being accepted.', + 'P71' => 'Credit Cards issued from this bank are currently not being accepted.', + 'P72' => 'Daily volume exceeded.', + 'P73' => 'Too many transactions within allotted time.', + 'P91' => 'Missing/incorrect password', + 'P92' => 'Account not configured for mobil administration', + 'P93' => 'IP Not registered to username.', + 'P94' => 'Mode not permitted for this account.', + 'P95' => 'Currently Blank', + 'P96' => 'Currently Blank', + 'P97' => 'Processor not responding', + 'P98' => 'Missing merchant/publisher name', + 'P99' => 'Currently Blank' } TRANSACTIONS = { @@ -153,7 +153,7 @@ def credit(money, identification_or_creditcard, options = {}) add_amount(post, money, options) if identification_or_creditcard.is_a?(String) - deprecated CREDIT_DEPRECATION_MESSAGE + ActiveMerchant.deprecated CREDIT_DEPRECATION_MESSAGE refund(money, identification_or_creditcard, options) else add_creditcard(post, identification_or_creditcard) @@ -172,8 +172,9 @@ def refund(money, reference, options = {}) end private + def commit(action, post) - response = parse( ssl_post(self.live_url, post_data(action, post)) ) + response = parse(ssl_post(self.live_url, post_data(action, post))) success = SUCCESS_CODES.include?(response[:finalstatus]) message = success ? 'Success' : message_from(response) @@ -188,7 +189,7 @@ def commit(action, post) def parse(body) body = CGI.unescape(body) results = {} - body.split('&').collect { |e| e.split('=') }.each do |key,value| + body.split('&').collect { |e| e.split('=') }.each do |key, value| results[key.downcase.to_sym] = normalize(value.to_s.strip) end @@ -268,24 +269,13 @@ def add_amount(post, money, options) post[:currency] = options[:currency] || currency(money) end - # Make a ruby type out of the response string - def normalize(field) - case field - when "true" then true - when "false" then false - when "" then nil - when "null" then nil - else field - end - end - def message_from(results) PAYMENT_GATEWAY_RESPONSES[results[:resp_code]] end def expdate(creditcard) - year = sprintf("%.4i", creditcard.year) - month = sprintf("%.2i", creditcard.month) + year = sprintf('%.4i', creditcard.year) + month = sprintf('%.2i', creditcard.month) "#{month}/#{year[-2..-1]}" end diff --git a/lib/active_merchant/billing/gateways/pro_pay.rb b/lib/active_merchant/billing/gateways/pro_pay.rb new file mode 100644 index 00000000000..dd286ad6fcb --- /dev/null +++ b/lib/active_merchant/billing/gateways/pro_pay.rb @@ -0,0 +1,326 @@ +require 'nokogiri' + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class ProPayGateway < Gateway + self.test_url = 'https://xmltest.propay.com/API/PropayAPI.aspx' + self.live_url = 'https://epay.propay.com/api/propayapi.aspx' + + self.supported_countries = ['US', 'CA'] + self.default_currency = 'USD' + self.money_format = :cents + self.supported_cardtypes = [:visa, :master, :american_express, :discover] + + self.homepage_url = 'https://www.propay.com/' + self.display_name = 'ProPay' + + STATUS_RESPONSE_CODES = { + '00' => 'Success', + '20' => 'Invalid username', + '21' => 'Invalid transType', + '22' => 'Invalid Currency Code', + '23' => 'Invalid accountType', + '24' => 'Invalid sourceEmail', + '25' => 'Invalid firstName', + '26' => 'Invalid mInitial', + '27' => 'Invalid lastName', + '28' => 'Invalid billAddr', + '29' => 'Invalid aptNum', + '30' => 'Invalid city', + '31' => 'Invalid state', + '32' => 'Invalid billZip', + '33' => 'Invalid mailAddr', + '34' => 'Invalid mailApt', + '35' => 'Invalid mailCity', + '36' => 'Invalid mailState', + '37' => 'Invalid mailZip', + '38' => 'Invalid dayPhone', + '39' => 'Invalid evenPhone', + '40' => 'Invalid ssn', + '41' => 'Invalid dob', + '42' => 'Invalid recEmail', + '43' => 'Invalid knownAccount', + '44' => 'Invalid amount', + '45' => 'Invalid invNum', + '46' => 'Invalid rtNum', + '47' => 'Invalid accntNum', + '48' => 'Invalid ccNum', + '49' => 'Invalid expDate', + '50' => 'Invalid cvv2', + '51' => 'Invalid transNum and/or Unable to act perform actions on transNum due to funding', + '52' => 'Invalid splitNum', + '53' => 'A ProPay account with this email address already exists AND/OR User has no account number', + '54' => 'A ProPay account with this social security number already exists', + '55' => 'The email address provided does not correspond to a ProPay account.', + '56' => 'Recipient’s email address shouldn’t have a ProPay account and does', + '57' => 'Cannot settle transaction because it already expired', + '58' => 'Credit card declined', + '59' => 'Invalid Credential or IP address not allowed', + '60' => 'Credit card authorization timed out; retry at a later time', + '61' => 'Amount exceeds single transaction limit', + '62' => 'Amount exceeds monthly volume limit', + '63' => 'Insufficient funds in account', + '64' => 'Over credit card use limit', + '65' => 'Miscellaneous error', + '66' => 'Denied a ProPay account', + '67' => 'Unauthorized service requested', + '68' => 'Account not affiliated', + '69' => 'Duplicate invoice number (The same card was charged for the same amount with the same invoice number (including blank invoices) in a 1 minute period. Details about the original transaction are included whenever a 69 response is returned. These details include a repeat of the auth code, the original AVS response, and the original CVV response.)', + '70' => 'Duplicate external ID', + '71' => 'Account previously set up, but problem affiliating it with partner', + '72' => 'The ProPay Account has already been upgraded to a Premium Account', + '73' => 'Invalid Destination Account', + '74' => 'Account or Trans Error', + '75' => 'Money already pulled', + '76' => 'Not Premium (used only for push/pull transactions)', + '77' => 'Empty results', + '78' => 'Invalid Authentication', + '79' => 'Generic account status error', + '80' => 'Invalid Password', + '81' => 'Account Expired', + '82' => 'InvalidUserID', + '83' => 'BatchTransCountError', + '84' => 'InvalidBeginDate', + '85' => 'InvalidEndDate', + '86' => 'InvalidExternalID', + '87' => 'DuplicateUserID', + '88' => 'Invalid track 1', + '89' => 'Invalid track 2', + '90' => 'Transaction already refunded', + '91' => 'Duplicate Batch ID' + } + + TRANSACTION_RESPONSE_CODES = { + '00' => 'Success', + '1' => 'Transaction blocked by issuer', + '4' => 'Pick up card and deny transaction', + '5' => 'Problem with the account', + '6' => 'Customer requested stop to recurring payment', + '7' => 'Customer requested stop to all recurring payments', + '8' => 'Honor with ID only', + '9' => 'Unpaid items on customer account', + '12' => 'Invalid transaction', + '13' => 'Amount Error', + '14' => 'Invalid card number', + '15' => 'No such issuer. Could not route transaction', + '16' => 'Refund error', + '17' => 'Over limit', + '19' => 'Reenter transaction or the merchant account may be boarded incorrectly', + '25' => 'Invalid terminal 41 Lost card', + '43' => 'Stolen card', + '51' => 'Insufficient funds', + '52' => 'No such account', + '54' => 'Expired card', + '55' => 'Incorrect PIN', + '57' => 'Bank does not allow this type of purchase', + '58' => 'Credit card network does not allow this type of purchase for your merchant account.', + '61' => 'Exceeds issuer withdrawal limit', + '62' => 'Issuer does not allow this card to be charged for your business.', + '63' => 'Security Violation', + '65' => 'Activity limit exceeded', + '75' => 'PIN tries exceeded', + '76' => 'Unable to locate account', + '78' => 'Account not recognized', + '80' => 'Invalid Date', + '82' => 'Invalid CVV2', + '83' => 'Cannot verify the PIN', + '85' => 'Service not supported for this card', + '93' => 'Cannot complete transaction. Customer should call 800 number.', + '95' => 'Misc Error Transaction failure', + '96' => 'Issuer system malfunction or timeout.', + '97' => 'Approved for a lesser amount. ProPay will not settle and consider this a decline.', + '98' => 'Failure HV', + '99' => 'Generic decline or unable to parse issuer response code' + } + + def initialize(options={}) + requires!(options, :cert_str) + super + end + + def purchase(money, payment, options={}) + request = build_xml_request do |xml| + add_invoice(xml, money, options) + add_payment(xml, payment, options) + add_address(xml, options) + add_account(xml, options) + add_recurring(xml, options) + xml.transType '04' + end + + commit(request) + end + + def authorize(money, payment, options={}) + request = build_xml_request do |xml| + add_invoice(xml, money, options) + add_payment(xml, payment, options) + add_address(xml, options) + add_account(xml, options) + add_recurring(xml, options) + xml.transType '05' + end + + commit(request) + end + + def capture(money, authorization, options={}) + request = build_xml_request do |xml| + add_invoice(xml, money, options) + add_account(xml, options) + xml.transNum authorization + xml.transType '06' + end + + commit(request) + end + + def refund(money, authorization, options={}) + request = build_xml_request do |xml| + add_invoice(xml, money, options) + add_account(xml, options) + xml.transNum authorization + xml.transType '07' + end + + commit(request) + end + + def void(authorization, options={}) + refund(nil, authorization, options) + end + + def credit(money, payment, options={}) + request = build_xml_request do |xml| + add_invoice(xml, money, options) + add_payment(xml, payment, options) + add_account(xml, options) + xml.transType '35' + end + + commit(request) + end + + def verify(credit_card, options={}) + MultiResponse.run(:use_first_response) do |r| + r.process { authorize(100, credit_card, options) } + r.process(:ignore_result) { void(r.authorization, options) } + end + end + + def supports_scrubbing? + true + end + + def scrub(transcript) + transcript. + gsub(%r(().+()), '\1[FILTERED]\2'). + gsub(%r(().+()), '\1[FILTERED]\2'). + gsub(%r(().+()), '\1[FILTERED]\2') + end + + private + + def add_payment(xml, payment, options) + xml.ccNum payment.number + xml.expDate "#{format(payment.month, :two_digits)}#{format(payment.year, :two_digits)}" + xml.CVV2 payment.verification_value + xml.cardholderName payment.name + end + + def add_address(xml, options) + if address = options[:billing_address] || options[:address] + xml.addr address[:address1] + xml.aptNum address[:address2] + xml.city address[:city] + xml.state address[:state] + xml.zip address[:zip].to_s.delete('-') + end + end + + def add_account(xml, options) + xml.accountNum options[:account_num] + end + + def add_invoice(xml, money, options) + xml.amount amount(money) + xml.currencyCode options[:currency] || currency(money) + xml.invNum options[:order_id] || SecureRandom.hex(25) + end + + def add_recurring(xml, options) + xml.recurringPayment options[:recurring_payment] + end + + def parse(body) + results = {} + xml = Nokogiri::XML(body) + resp = xml.xpath('//XMLResponse/XMLTrans') + resp.children.each do |element| + results[element.name.underscore.downcase.to_sym] = element.text + end + results + end + + def commit(parameters) + url = (test? ? test_url : live_url) + response = parse(ssl_post(url, parameters)) + + Response.new( + success_from(response), + message_from(response), + response, + authorization: authorization_from(response), + avs_result: AVSResult.new(code: response[:avs]), + cvv_result: CVVResult.new(response[:cvv2_resp]), + test: test?, + error_code: error_code_from(response) + ) + end + + def success_from(response) + response[:status] == '00' + end + + def message_from(response) + return 'Success' if success_from(response) + message = STATUS_RESPONSE_CODES[response[:status]] + message += " - #{TRANSACTION_RESPONSE_CODES[response[:response_code]]}" if response[:response_code] + + message + end + + def authorization_from(response) + response[:trans_num] + end + + def error_code_from(response) + unless success_from(response) + response[:status] + end + end + + def build_xml_request + builder = Nokogiri::XML::Builder.new do |xml| + xml.XMLRequest do + xml.certStr @options[:cert_str] + xml.class_ 'partner' + xml.XMLTrans do + yield(xml) + end + end + end + + builder.to_xml + end + end + + def underscore(camel_cased_word) + camel_cased_word.to_s.gsub(/::/, '/'). + gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2'). + gsub(/([a-z\d])([A-Z])/, '\1_\2'). + tr('-', '_'). + downcase + end + end +end diff --git a/lib/active_merchant/billing/gateways/psigate.rb b/lib/active_merchant/billing/gateways/psigate.rb index c30a1f20f77..b987dd1e74b 100644 --- a/lib/active_merchant/billing/gateways/psigate.rb +++ b/lib/active_merchant/billing/gateways/psigate.rb @@ -35,8 +35,8 @@ module Billing #:nodoc: # :email => 'jack@yahoo.com' # ) class PsigateGateway < Gateway - self.test_url = 'https://dev.psigate.com:7989/Messenger/XMLMessenger' - self.live_url = 'https://secure.psigate.com:7934/Messenger/XMLMessenger' + self.test_url = 'https://realtimestaging.psigate.com/xml' + self.live_url = 'https://realtime.psigate.com/xml' self.supported_cardtypes = [:visa, :master, :american_express] self.supported_countries = ['CA'] @@ -53,39 +53,50 @@ def initialize(options = {}) def authorize(money, creditcard, options = {}) requires!(options, :order_id) - options[:CardAction] = "1" + options[:CardAction] = '1' commit(money, creditcard, options) end def purchase(money, creditcard, options = {}) requires!(options, :order_id) - options[:CardAction] = "0" + options[:CardAction] = '0' commit(money, creditcard, options) end def capture(money, authorization, options = {}) - options[:CardAction] = "2" + options[:CardAction] = '2' options[:order_id], options[:trans_ref_number] = split_authorization(authorization) commit(money, nil, options) end def credit(money, authorization, options = {}) - deprecated CREDIT_DEPRECATION_MESSAGE + ActiveMerchant.deprecated CREDIT_DEPRECATION_MESSAGE refund(money, authorization, options) end def refund(money, authorization, options = {}) - options[:CardAction] = "3" + options[:CardAction] = '3' options[:order_id], options[:trans_ref_number] = split_authorization(authorization) commit(money, nil, options) end def void(authorization, options = {}) - options[:CardAction] = "9" + options[:CardAction] = '9' options[:order_id], options[:trans_ref_number] = split_authorization(authorization) commit(nil, nil, options) end + def supports_scrubbing? + true + end + + def scrub(transcript) + transcript. + gsub(%r(()[^<]*())i, '\1[FILTERED]\2'). + gsub(%r(()[^<]*())i, '\1[FILTERED]\2'). + gsub(%r(()[^<]*())i, '\1[FILTERED]\2') + end + private def commit(money, creditcard, options = {}) @@ -93,7 +104,7 @@ def commit(money, creditcard, options = {}) Response.new(successful?(response), message_from(response), response, :test => test?, - :authorization => build_authorization(response) , + :authorization => build_authorization(response), :avs_result => { :code => response[:avsresult] }, :cvv_result => response[:cardidresult] ) @@ -104,11 +115,11 @@ def url end def successful?(response) - response[:approved] == "APPROVED" + response[:approved] == 'APPROVED' end def parse(xml) - response = {:message => "Global Error Receipt", :complete => false} + response = {:message => 'Global Error Receipt', :complete => false} xml = REXML::Document.new(xml) xml.elements.each('//Result/*') do |node| @@ -121,7 +132,7 @@ def parse(xml) def post_data(money, creditcard, options) xml = REXML::Document.new xml << REXML::XMLDecl.new - root = xml.add_element("Order") + root = xml.add_element('Order') parameters(money, creditcard, options).each do |key, value| root.add_element(key.to_s).text = value if value @@ -144,7 +155,7 @@ def parameters(money, creditcard, options = {}) :TransRefNumber => options[:trans_ref_number], # Credit Card parameters - :PaymentType => "CC", + :PaymentType => 'CC', :CardAction => options[:CardAction], # Financial parameters @@ -156,9 +167,9 @@ def parameters(money, creditcard, options = {}) } if creditcard - exp_month = sprintf("%.2i", creditcard.month) unless creditcard.month.blank? - exp_year = creditcard.year.to_s[2,2] unless creditcard.year.blank? - card_id_code = (creditcard.verification_value.blank? ? nil : "1") + exp_month = sprintf('%.2i', creditcard.month) unless creditcard.month.blank? + exp_year = creditcard.year.to_s[2, 2] unless creditcard.year.blank? + card_id_code = (creditcard.verification_value.blank? ? nil : '1') params.update( :CardNumber => creditcard.number, @@ -195,22 +206,11 @@ def parameters(money, creditcard, options = {}) end def message_from(response) - if response[:approved] == "APPROVED" + if response[:approved] == 'APPROVED' return SUCCESS_MESSAGE else return FAILURE_MESSAGE if response[:errmsg].blank? - return response[:errmsg].gsub(/[^\w]/, ' ').split.join(" ").capitalize - end - end - - # Make a ruby type out of the response string - def normalize(field) - case field - when "true" then true - when "false" then false - when "" then nil - when "null" then nil - else field + return response[:errmsg].gsub(/[^\w]/, ' ').split.join(' ').capitalize end end @@ -220,7 +220,7 @@ def split_authorization(authorization) end def build_authorization(response) - [response[:orderid], response[:transrefnumber]].join(";") + [response[:orderid], response[:transrefnumber]].join(';') end end end diff --git a/lib/active_merchant/billing/gateways/psl_card.rb b/lib/active_merchant/billing/gateways/psl_card.rb index 758aac6fefb..77da4a123db 100644 --- a/lib/active_merchant/billing/gateways/psl_card.rb +++ b/lib/active_merchant/billing/gateways/psl_card.rb @@ -17,11 +17,11 @@ class PslCardGateway < Gateway self.default_currency = 'GBP' self.supported_countries = ['GB'] - # Visa Credit, Visa Debit, Mastercard, Maestro, Solo, Electron, + # Visa Credit, Visa Debit, Mastercard, Maestro, Electron, # American Express, Diners Club, JCB, International Maestro, # Style, Clydesdale Financial Services, Other - self.supported_cardtypes = [ :visa, :master, :american_express, :diners_club, :jcb, :switch, :solo, :maestro ] + self.supported_cardtypes = [ :visa, :master, :american_express, :diners_club, :jcb, :maestro ] self.homepage_url = 'http://www.paymentsolutionsltd.com/' self.display_name = 'PSL Payment Solutions' @@ -46,38 +46,38 @@ class PslCardGateway < Gateway 'USD' => 840 } - #The terminal used - only for swipe transactions, so hard coded to 32 for online + # The terminal used - only for swipe transactions, so hard coded to 32 for online EMV_TERMINAL_TYPE = 32 - #Different Dispatch types + # Different Dispatch types DISPATCH_LATER = 'LATER' DISPATCH_NOW = 'NOW' # Return codes APPROVED = '00' - #Nominal amount to authorize for a 'dispatch later' type - #The nominal amount is held straight away, when the goods are ready - #to be dispatched, PSL is informed and the full amount is the - #taken. + # Nominal amount to authorize for a 'dispatch later' type + # The nominal amount is held straight away, when the goods are ready + # to be dispatched, PSL is informed and the full amount is the + # taken. NOMINAL_AMOUNT = 101 AVS_CODE = { - "ALL MATCH" => 'Y', - "SECURITY CODE MATCH ONLY" => 'N', - "ADDRESS MATCH ONLY" => 'Y', - "NO DATA MATCHES" => 'N', - "DATA NOT CHECKED" => 'R', - "SECURITY CHECKS NOT SUPPORTED" => 'X' + 'ALL MATCH' => 'Y', + 'SECURITY CODE MATCH ONLY' => 'N', + 'ADDRESS MATCH ONLY' => 'Y', + 'NO DATA MATCHES' => 'N', + 'DATA NOT CHECKED' => 'R', + 'SECURITY CHECKS NOT SUPPORTED' => 'X' } CVV_CODE = { - "ALL MATCH" => 'M', - "SECURITY CODE MATCH ONLY" => 'M', - "ADDRESS MATCH ONLY" => 'N', - "NO DATA MATCHES" => 'N', - "DATA NOT CHECKED" => 'P', - "SECURITY CHECKS NOT SUPPORTED" => 'X' + 'ALL MATCH' => 'M', + 'SECURITY CODE MATCH ONLY' => 'M', + 'ADDRESS MATCH ONLY' => 'N', + 'NO DATA MATCHES' => 'N', + 'DATA NOT CHECKED' => 'P', + 'SECURITY CHECKS NOT SUPPORTED' => 'X' } # Create a new PslCardGateway @@ -101,7 +101,7 @@ def initialize(options = {}) # -options: # # Returns: - # -ActiveRecord::Billing::Response object + # -ActiveMerchant::Billing::Response object # def purchase(money, credit_card, options = {}) post = {} @@ -129,7 +129,7 @@ def purchase(money, credit_card, options = {}) # -options: # # Returns: - # -ActiveRecord::Billing::Response object + # -ActiveMerchant::Billing::Response object # def authorize(money, credit_card, options = {}) post = {} @@ -153,7 +153,7 @@ def authorize(money, credit_card, options = {}) # -options: # # Returns: - # -ActiveRecord::Billing::Response object + # -ActiveMerchant::Billing::Response object # def capture(money, authorization, options = {}) post = {} @@ -174,12 +174,6 @@ def add_credit_card(post, credit_card) post[:ExpMonth] = credit_card.month post[:ExpYear] = credit_card.year - if requires_start_date_or_issue_number?(credit_card) - post[:IssueNumber] = credit_card.issue_number unless credit_card.issue_number.blank? - post[:StartMonth] = credit_card.start_month unless credit_card.start_month.blank? - post[:StartYear] = credit_card.start_year unless credit_card.start_year.blank? - end - # CV2 check post[:AVSCV2Check] = credit_card.verification_value? ? 'YES' : 'NO' post[:CV2] = credit_card.verification_value if credit_card.verification_value? @@ -189,7 +183,7 @@ def add_address(post, options) address = options[:billing_address] || options[:address] return if address.nil? - post[:QAAddress] = [:address1, :address2, :city, :state].collect{|a| address[a]}.reject{|a| a.blank?}.join(' ') + post[:QAAddress] = [:address1, :address2, :city, :state].collect { |a| address[a] }.reject(&:blank?).join(' ') post[:QAPostcode] = address[:zip] end @@ -246,10 +240,9 @@ def currency_code(currency) # -a hash with all of the values returned in the PSL response # def parse(body) - fields = {} for line in body.split('&') - key, value = *line.scan( %r{^(\w+)\=(.*)$} ).flatten + key, value = *line.scan(%r{^(\w+)\=(.*)$}).flatten fields[key] = CGI.unescape(value) end fields.symbolize_keys @@ -264,7 +257,7 @@ def parse(body) # - ActiveMerchant::Billing::Response object # def commit(request) - response = parse( ssl_post(self.live_url, post_data(request)) ) + response = parse(ssl_post(self.live_url, post_data(request))) Response.new(response[:ResponseCode] == APPROVED, response[:Message], response, :test => test?, @@ -296,7 +289,7 @@ def post_data(post) post.collect { |key, value| "#{key}=#{CGI.escape(value.to_s.tr('&=', ' '))}" - }.join("&") + }.join('&') end end end diff --git a/lib/active_merchant/billing/gateways/qbms.rb b/lib/active_merchant/billing/gateways/qbms.rb index a1ca4bccca4..5d37f900bf4 100644 --- a/lib/active_merchant/billing/gateways/qbms.rb +++ b/lib/active_merchant/billing/gateways/qbms.rb @@ -5,8 +5,8 @@ class QbmsGateway < Gateway class_attribute :test_url, :live_url - self.test_url = "https://webmerchantaccount.ptc.quickbooks.com/j/AppGateway" - self.live_url = "https://webmerchantaccount.quickbooks.com/j/AppGateway" + self.test_url = 'https://webmerchantaccount.ptc.quickbooks.com/j/AppGateway' + self.live_url = 'https://webmerchantaccount.quickbooks.com/j/AppGateway' self.homepage_url = 'http://payments.intuit.com/' self.display_name = 'QuickBooks Merchant Services' @@ -100,8 +100,8 @@ def void(authorization, options = {}) # # def credit(money, identification, options = {}) - deprecated CREDIT_DEPRECATION_MESSAGE - refund(money, identification, options = {}) + ActiveMerchant.deprecated CREDIT_DEPRECATION_MESSAGE + refund(money, identification, {}) end def refund(money, identification, options = {}) @@ -113,6 +113,17 @@ def query commit(:query, nil, {}) end + def supports_scrubbing? + true + end + + def scrub(transcript) + transcript. + gsub(%r(()[^<]*())i, '\1[FILTERED]\2'). + gsub(%r(()[^<]*())i, '\1[FILTERED]\2'). + gsub(%r(()[^<]*())i, '\1[FILTERED]\2') + end + private def hosted? @@ -127,7 +138,7 @@ def commit(action, money, parameters) req = build_request(type, money, parameters) - data = ssl_post(url, req, "Content-Type" => "application/x-qbmsxml") + data = ssl_post(url, req, 'Content-Type' => 'application/x-qbmsxml') response = parse(type, data) message = (response[:status_message] || '').strip @@ -152,20 +163,20 @@ def parse(type, body) xml = REXML::Document.new(body) signon = REXML::XPath.first(xml, "//SignonMsgsRs/#{hosted? ? 'SignonAppCertRs' : 'SignonDesktopRs'}") - status_code = signon.attributes["statusCode"].to_i + status_code = signon.attributes['statusCode'].to_i if status_code != 0 return { :status_code => status_code, - :status_message => signon.attributes["statusMessage"], + :status_message => signon.attributes['statusMessage'], } end response = REXML::XPath.first(xml, "//QBMSXMLMsgsRs/#{type}Rs") results = { - :status_code => response.attributes["statusCode"].to_i, - :status_message => response.attributes["statusMessage"], + :status_code => response.attributes['statusCode'].to_i, + :status_message => response.attributes['statusMessage'], } response.elements.each do |e| @@ -189,16 +200,16 @@ def build_request(type, money, parameters = {}) xml.instruct!(:xml, :version => '1.0', :encoding => 'utf-8') xml.instruct!(:qbmsxml, :version => API_VERSION) - xml.tag!("QBMSXML") do - xml.tag!("SignonMsgsRq") do - xml.tag!(hosted? ? "SignonAppCertRq" : "SignonDesktopRq") do - xml.tag!("ClientDateTime", Time.now.xmlschema) - xml.tag!("ApplicationLogin", @options[:login]) - xml.tag!("ConnectionTicket", @options[:ticket]) + xml.tag!('QBMSXML') do + xml.tag!('SignonMsgsRq') do + xml.tag!(hosted? ? 'SignonAppCertRq' : 'SignonDesktopRq') do + xml.tag!('ClientDateTime', Time.now.xmlschema) + xml.tag!('ApplicationLogin', @options[:login]) + xml.tag!('ConnectionTicket', @options[:ticket]) end end - xml.tag!("QBMSXMLMsgsRq") do + xml.tag!('QBMSXMLMsgsRq') do xml.tag!("#{type}Rq") do method("build_#{type}").call(xml, money, parameters) end @@ -212,47 +223,47 @@ def build_CustomerCreditCardAuth(xml, money, parameters) cc = parameters[:credit_card] name = "#{cc.first_name} #{cc.last_name}"[0...30] - xml.tag!("TransRequestID", parameters[:trans_request_id]) - xml.tag!("CreditCardNumber", cc.number) - xml.tag!("ExpirationMonth", cc.month) - xml.tag!("ExpirationYear", cc.year) - xml.tag!("IsECommerce", "true") - xml.tag!("Amount", amount(money)) - xml.tag!("NameOnCard", name) + xml.tag!('TransRequestID', parameters[:trans_request_id]) + xml.tag!('CreditCardNumber', cc.number) + xml.tag!('ExpirationMonth', cc.month) + xml.tag!('ExpirationYear', cc.year) + xml.tag!('IsECommerce', 'true') + xml.tag!('Amount', amount(money)) + xml.tag!('NameOnCard', name) add_address(xml, parameters) - xml.tag!("CardSecurityCode", cc.verification_value) if cc.verification_value? + xml.tag!('CardSecurityCode', cc.verification_value) if cc.verification_value? end def build_CustomerCreditCardCapture(xml, money, parameters) - xml.tag!("TransRequestID", parameters[:trans_request_id]) - xml.tag!("CreditCardTransID", parameters[:transaction_id]) - xml.tag!("Amount", amount(money)) + xml.tag!('TransRequestID', parameters[:trans_request_id]) + xml.tag!('CreditCardTransID', parameters[:transaction_id]) + xml.tag!('Amount', amount(money)) end def build_CustomerCreditCardCharge(xml, money, parameters) cc = parameters[:credit_card] name = "#{cc.first_name} #{cc.last_name}"[0...30] - xml.tag!("TransRequestID", parameters[:trans_request_id]) - xml.tag!("CreditCardNumber", cc.number) - xml.tag!("ExpirationMonth", cc.month) - xml.tag!("ExpirationYear", cc.year) - xml.tag!("IsECommerce", "true") - xml.tag!("Amount", amount(money)) - xml.tag!("NameOnCard", name) + xml.tag!('TransRequestID', parameters[:trans_request_id]) + xml.tag!('CreditCardNumber', cc.number) + xml.tag!('ExpirationMonth', cc.month) + xml.tag!('ExpirationYear', cc.year) + xml.tag!('IsECommerce', 'true') + xml.tag!('Amount', amount(money)) + xml.tag!('NameOnCard', name) add_address(xml, parameters) - xml.tag!("CardSecurityCode", cc.verification_value) if cc.verification_value? + xml.tag!('CardSecurityCode', cc.verification_value) if cc.verification_value? end def build_CustomerCreditCardTxnVoidOrRefund(xml, money, parameters) - xml.tag!("TransRequestID", parameters[:trans_request_id]) - xml.tag!("CreditCardTransID", parameters[:transaction_id]) - xml.tag!("Amount", amount(money)) + xml.tag!('TransRequestID', parameters[:trans_request_id]) + xml.tag!('CreditCardTransID', parameters[:transaction_id]) + xml.tag!('Amount', amount(money)) end def build_CustomerCreditCardTxnVoid(xml, money, parameters) - xml.tag!("TransRequestID", parameters[:trans_request_id]) - xml.tag!("CreditCardTransID", parameters[:transaction_id]) + xml.tag!('TransRequestID', parameters[:trans_request_id]) + xml.tag!('CreditCardTransID', parameters[:transaction_id]) end # Called reflectively by build_request @@ -261,30 +272,30 @@ def build_MerchantAccountQuery(xml, money, parameters) def add_address(xml, parameters) if address = parameters[:billing_address] || parameters[:address] - xml.tag!("CreditCardAddress", (address[:address1] || "")[0...30]) - xml.tag!("CreditCardPostalCode", (address[:zip] || "")[0...9]) + xml.tag!('CreditCardAddress', (address[:address1] || '')[0...30]) + xml.tag!('CreditCardPostalCode', (address[:zip] || '')[0...9]) end end def cvv_result(response) case response[:card_security_code_match] - when "Pass" then 'M' - when "Fail" then 'N' - when "NotAvailable" then 'P' + when 'Pass' then 'M' + when 'Fail' then 'N' + when 'NotAvailable' then 'P' end end def avs_result(response) case "#{response[:avs_street]}|#{response[:avs_zip]}" - when "Pass|Pass" then "D" - when "Pass|Fail" then "A" - when "Pass|NotAvailable" then "B" - when "Fail|Pass" then "Z" - when "Fail|Fail" then "C" - when "Fail|NotAvailable" then "N" - when "NotAvailable|Pass" then "P" - when "NotAvailable|Fail" then "N" - when "NotAvailable|NotAvailable" then "U" + when 'Pass|Pass' then 'D' + when 'Pass|Fail' then 'A' + when 'Pass|NotAvailable' then 'B' + when 'Fail|Pass' then 'Z' + when 'Fail|Fail' then 'C' + when 'Fail|NotAvailable' then 'N' + when 'NotAvailable|Pass' then 'P' + when 'NotAvailable|Fail' then 'N' + when 'NotAvailable|NotAvailable' then 'U' end end end diff --git a/lib/active_merchant/billing/gateways/quantum.rb b/lib/active_merchant/billing/gateways/quantum.rb index 7d8b51be13d..4fd43ad07ca 100644 --- a/lib/active_merchant/billing/gateways/quantum.rb +++ b/lib/active_merchant/billing/gateways/quantum.rb @@ -44,7 +44,7 @@ def initialize(options = {}) # def authorize(money, creditcard, options = {}) setup_address_hash(options) - commit(build_auth_request(money, creditcard, options), options ) + commit(build_auth_request(money, creditcard, options), options) end # Capture an authorization that has previously been requested @@ -69,7 +69,7 @@ def refund(money, identification, options = {}) end def credit(money, identification, options = {}) - deprecated CREDIT_DEPRECATION_MESSAGE + ActiveMerchant.deprecated CREDIT_DEPRECATION_MESSAGE refund(money, identification, options) end @@ -81,7 +81,7 @@ def setup_address_hash(options) def build_auth_request(money, creditcard, options) xml = Builder::XmlMarkup.new - add_common_credit_card_info(xml,'AUTH_ONLY') + add_common_credit_card_info(xml, 'AUTH_ONLY') add_purchase_data(xml, money) add_creditcard(xml, creditcard) add_address(xml, creditcard, options[:billing_address], options) @@ -94,7 +94,7 @@ def build_auth_request(money, creditcard, options) def build_capture_request(money, authorization, options) xml = Builder::XmlMarkup.new - add_common_credit_card_info(xml,'PREVIOUS_SALE') + add_common_credit_card_info(xml, 'PREVIOUS_SALE') transaction_id, _ = authorization_parts_from(authorization) add_transaction_id(xml, transaction_id) xml.target! @@ -115,7 +115,7 @@ def build_purchase_request(money, creditcard, options) def build_void_request(authorization, options) xml = Builder::XmlMarkup.new - add_common_credit_card_info(xml,'VOID') + add_common_credit_card_info(xml, 'VOID') transaction_id, _ = authorization_parts_from(authorization) add_transaction_id(xml, transaction_id) xml.target! @@ -123,7 +123,7 @@ def build_void_request(authorization, options) def build_credit_request(money, authorization, options) xml = Builder::XmlMarkup.new - add_common_credit_card_info(xml,'RETURN') + add_common_credit_card_info(xml, 'RETURN') add_purchase_data(xml, money) transaction_id, cc = authorization_parts_from(authorization) add_transaction_id(xml, transaction_id) @@ -182,7 +182,7 @@ def add_creditcard(xml, creditcard) xml.tag! 'CreditCardNumber', creditcard.number xml.tag! 'ExpireMonth', format(creditcard.month, :two_digits) xml.tag! 'ExpireYear', format(creditcard.year, :four_digits) - xml.tag!('CVV2', creditcard.verification_value) unless (@options[:ignore_cvv] || creditcard.verification_value.blank? ) + xml.tag!('CVV2', creditcard.verification_value) unless @options[:ignore_cvv] || creditcard.verification_value.blank? end # Where we actually build the full SOAP request using builder @@ -206,7 +206,7 @@ def commit(request, options) headers = { 'Content-Type' => 'text/xml' } response = parse(ssl_post(self.live_url, build_request(request, options), headers)) - success = response[:request_status] == "Success" + success = response[:request_status] == 'Success' message = response[:request_message] if success # => checking for connectivity success first @@ -216,10 +216,10 @@ def commit(request, options) end Response.new(success, message, response, - :test => test?, - :authorization => authorization, - :avs_result => { :code => response[:AVSResponseCode] }, - :cvv_result => response[:CVV2ResponseCode] + :test => test?, + :authorization => authorization, + :avs_result => { :code => response[:AVSResponseCode] }, + :cvv_result => response[:CVV2ResponseCode] ) end @@ -231,19 +231,19 @@ def parse(xml) begin xml = REXML::Document.new(xml) - root = REXML::XPath.first(xml, "//QGWRequest/ResponseSummary") + root = REXML::XPath.first(xml, '//QGWRequest/ResponseSummary') parse_element(reply, root) reply[:request_status] = reply[:Status] reply[:request_message] = "#{reply[:Status]}: #{reply[:StatusDescription]}" - if root = REXML::XPath.first(xml, "//QGWRequest/Result") + if root = REXML::XPath.first(xml, '//QGWRequest/Result') root.elements.to_a.each do |node| parse_element(reply, node) end end - rescue Exception => e + rescue Exception reply[:request_status] = 'Failure' - reply[:request_message] = "Failure: There was a problem parsing the response XML" + reply[:request_message] = 'Failure: There was a problem parsing the response XML' end return reply @@ -251,10 +251,10 @@ def parse(xml) def parse_element(reply, node) if node.has_elements? - node.elements.each{|e| parse_element(reply, e) } + node.elements.each { |e| parse_element(reply, e) } else if node.parent.name =~ /item/ - parent = node.parent.name + (node.parent.attributes["id"] ? "_" + node.parent.attributes["id"] : '') + parent = node.parent.name + (node.parent.attributes['id'] ? '_' + node.parent.attributes['id'] : '') reply[(parent + '_' + node.name).to_sym] = node.text else reply[node.name.to_sym] = node.text diff --git a/lib/active_merchant/billing/gateways/quickbooks.rb b/lib/active_merchant/billing/gateways/quickbooks.rb new file mode 100644 index 00000000000..6759a57aee6 --- /dev/null +++ b/lib/active_merchant/billing/gateways/quickbooks.rb @@ -0,0 +1,290 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class QuickbooksGateway < Gateway + self.test_url = 'https://sandbox.api.intuit.com' + self.live_url = 'https://api.intuit.com' + + self.supported_countries = ['US'] + self.default_currency = 'USD' + self.supported_cardtypes = [:visa, :master, :american_express, :discover, :diners] + + self.homepage_url = 'http://payments.intuit.com' + self.display_name = 'QuickBooks Payments' + ENDPOINT = '/quickbooks/v4/payments/charges' + OAUTH_ENDPOINTS = { + site: 'https://oauth.intuit.com', + request_token_path: '/oauth/v1/get_request_token', + authorize_url: 'https://appcenter.intuit.com/Connect/Begin', + access_token_path: '/oauth/v1/get_access_token' + } + + # https://developer.intuit.com/docs/0150_payments/0300_developer_guides/error_handling + + STANDARD_ERROR_CODE_MAPPING = { + # Fraud Warnings + 'PMT-1000' => STANDARD_ERROR_CODE[:processing_error], # payment was accepted, but refund was unsuccessful + 'PMT-1001' => STANDARD_ERROR_CODE[:invalid_cvc], # payment processed, but cvc was invalid + 'PMT-1002' => STANDARD_ERROR_CODE[:incorrect_address], # payment processed, incorrect address info + 'PMT-1003' => STANDARD_ERROR_CODE[:processing_error], # payment processed, address info couldn't be validated + + # Fraud Errors + 'PMT-2000' => STANDARD_ERROR_CODE[:incorrect_cvc], # Incorrect CVC + 'PMT-2001' => STANDARD_ERROR_CODE[:invalid_cvc], # CVC check unavaliable + 'PMT-2002' => STANDARD_ERROR_CODE[:incorrect_address], # Incorrect address + 'PMT-2003' => STANDARD_ERROR_CODE[:incorrect_address], # Address info unavailable + + 'PMT-3000' => STANDARD_ERROR_CODE[:processing_error], # Merchant account could not be validated + + # Invalid Request + 'PMT-4000' => STANDARD_ERROR_CODE[:processing_error], # Object is invalid + 'PMT-4001' => STANDARD_ERROR_CODE[:processing_error], # Object not found + 'PMT-4002' => STANDARD_ERROR_CODE[:processing_error], # Object is required + + # Transaction Declined + 'PMT-5000' => STANDARD_ERROR_CODE[:card_declined], # Request was declined + 'PMT-5001' => STANDARD_ERROR_CODE[:card_declined], # Merchant does not support given payment method + + # System Error + 'PMT-6000' => STANDARD_ERROR_CODE[:processing_error], # A temporary Issue prevented this request from being processed. + } + + FRAUD_WARNING_CODES = ['PMT-1000', 'PMT-1001', 'PMT-1002', 'PMT-1003'] + + def initialize(options = {}) + requires!(options, :consumer_key, :consumer_secret, :access_token, :token_secret, :realm) + @options = options + super + end + + def purchase(money, payment, options = {}) + post = {} + add_amount(post, money, options) + add_charge_data(post, payment, options) + post[:capture] = 'true' + + commit(ENDPOINT, post) + end + + def authorize(money, payment, options = {}) + post = {} + add_amount(post, money, options) + add_charge_data(post, payment, options) + post[:capture] = 'false' + + commit(ENDPOINT, post) + end + + def capture(money, authorization, options = {}) + post = {} + capture_uri = "#{ENDPOINT}/#{CGI.escape(authorization)}/capture" + post[:amount] = localized_amount(money, currency(money)) + add_context(post, options) + + commit(capture_uri, post) + end + + def refund(money, authorization, options = {}) + post = {} + post[:amount] = localized_amount(money, currency(money)) + add_context(post, options) + + commit(refund_uri(authorization), post) + end + + def verify(credit_card, options = {}) + authorize(1.00, credit_card, options) + end + + def supports_scrubbing? + true + end + + def scrub(transcript) + transcript. + gsub(%r((realm=\")\w+), '\1[FILTERED]'). + gsub(%r((oauth_consumer_key=\")\w+), '\1[FILTERED]'). + gsub(%r((oauth_nonce=\")\w+), '\1[FILTERED]'). + gsub(%r((oauth_signature=\")[a-zA-Z%0-9]+), '\1[FILTERED]'). + gsub(%r((oauth_token=\")\w+), '\1[FILTERED]'). + gsub(%r((number\D+)\d{16}), '\1[FILTERED]'). + gsub(%r((cvc\D+)\d{3}), '\1[FILTERED]') + end + + private + + def add_charge_data(post, payment, options = {}) + add_payment(post, payment, options) + add_address(post, options) + end + + def add_address(post, options) + return unless post[:card]&.kind_of?(Hash) + + card_address = {} + if address = options[:billing_address] || options[:address] + card_address[:streetAddress] = address[:address1] + card_address[:city] = address[:city] + card_address[:region] = address[:state] || address[:region] + card_address[:country] = address[:country] + card_address[:postalCode] = address[:zip] if address[:zip] + end + post[:card][:address] = card_address + end + + def add_amount(post, money, options = {}) + currency = options[:currency] || currency(money) + post[:amount] = localized_amount(money, currency) + post[:currency] = currency.upcase + end + + def add_payment(post, payment, options = {}) + add_creditcard(post, payment, options) + add_context(post, options) + end + + def add_creditcard(post, creditcard, options = {}) + card = {} + card[:number] = creditcard.number + card[:expMonth] = '%02d' % creditcard.month + card[:expYear] = creditcard.year + card[:cvc] = creditcard.verification_value if creditcard.verification_value? + card[:name] = creditcard.name if creditcard.name + card[:commercialCardCode] = options[:card_code] if options[:card_code] + + post[:card] = card + end + + def add_context(post, options = {}) + post[:context] = { + mobile: options.fetch(:mobile, false), + isEcommerce: options.fetch(:ecommerce, true) + } + end + + def parse(body) + JSON.parse(body) + end + + def commit(uri, body = {}, method = :post) + endpoint = gateway_url + uri + # The QuickBooks API returns HTTP 4xx on failed transactions, which causes a + # ResponseError raise, so we have to inspect the response and discern between + # a legitimate HTTP error and an actual gateway transactional error. + response = begin + case method + when :post + ssl_post(endpoint, post_data(body), headers(:post, endpoint)) + when :get + ssl_request(:get, endpoint, nil, headers(:get, endpoint)) + else + raise ArgumentError, "Invalid HTTP method: #{method}. Valid methods are :post and :get" + end + rescue ResponseError => e + extract_response_body_or_raise(e) + end + + response_object(response) + end + + def response_object(raw_response) + parsed_response = parse(raw_response) + + Response.new( + success?(parsed_response), + message_from(parsed_response), + parsed_response, + authorization: authorization_from(parsed_response), + test: test?, + cvv_result: cvv_code_from(parsed_response), + error_code: errors_from(parsed_response), + fraud_review: fraud_review_status_from(parsed_response) + ) + end + + def gateway_url + test? ? test_url : live_url + end + + def post_data(data = {}) + data.to_json + end + + def headers(method, uri) + raise ArgumentError, "Invalid HTTP method: #{method}. Valid methods are :post and :get" unless [:post, :get].include?(method) + request_uri = URI.parse(uri) + + # Following the guidelines from http://nouncer.com/oauth/authentication.html + oauth_parameters = { + oauth_nonce: generate_unique_id, + oauth_timestamp: Time.now.to_i.to_s, + oauth_signature_method: 'HMAC-SHA1', + oauth_version: '1.0', + oauth_consumer_key: @options[:consumer_key], + oauth_token: @options[:access_token] + } + + # prepare components for signature + oauth_signature_base_string = [method.to_s.upcase, request_uri.to_s, oauth_parameters.to_param].map { |v| CGI.escape(v) }.join('&') + oauth_signing_key = [@options[:consumer_secret], @options[:token_secret]].map { |v| CGI.escape(v) }.join('&') + hmac_signature = OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha1'), oauth_signing_key, oauth_signature_base_string) + + # append signature to required OAuth parameters + oauth_parameters[:oauth_signature] = CGI.escape(Base64.encode64(hmac_signature).chomp.gsub(/\n/, '')) + + # prepare Authorization header string + oauth_parameters = Hash[oauth_parameters.sort_by { |k, _| k }] + oauth_headers = ["OAuth realm=\"#{@options[:realm]}\""] + oauth_headers += oauth_parameters.map { |k, v| "#{k}=\"#{v}\"" } + + { + 'Content-type' => 'application/json', + 'Request-Id' => generate_unique_id, + 'Authorization' => oauth_headers.join(', ') + } + end + + def cvv_code_from(response) + if response['errors'].present? + FRAUD_WARNING_CODES.include?(response['errors'].first['code']) ? 'I' : '' + else + success?(response) ? 'M' : '' + end + end + + def success?(response) + return FRAUD_WARNING_CODES.concat(['0']).include?(response['errors'].first['code']) if response['errors'] + + !['DECLINED', 'CANCELLED'].include?(response['status']) + end + + def message_from(response) + response['errors'].present? ? response['errors'].map { |error_hash| error_hash['message'] }.join(' ') : response['status'] + end + + def errors_from(response) + response['errors'].present? ? STANDARD_ERROR_CODE_MAPPING[response['errors'].first['code']] : '' + end + + def authorization_from(response) + response['id'] + end + + def fraud_review_status_from(response) + response['errors'] && FRAUD_WARNING_CODES.include?(response['errors'].first['code']) + end + + def extract_response_body_or_raise(response_error) + begin + parse(response_error.response.body) + rescue JSON::ParserError + raise response_error + end + response_error.response.body + end + + def refund_uri(authorization) + "#{ENDPOINT}/#{CGI.escape(authorization)}/refunds" + end + end + end +end diff --git a/lib/active_merchant/billing/gateways/quickpay.rb b/lib/active_merchant/billing/gateways/quickpay.rb index 141d1ff03a0..5c1f6fb33bb 100644 --- a/lib/active_merchant/billing/gateways/quickpay.rb +++ b/lib/active_merchant/billing/gateways/quickpay.rb @@ -1,335 +1,25 @@ require 'rexml/document' require 'digest/md5' +require 'active_merchant/billing/gateways/quickpay/quickpay_v10' +require 'active_merchant/billing/gateways/quickpay/quickpay_v4to7' + module ActiveMerchant #:nodoc: module Billing #:nodoc: class QuickpayGateway < Gateway - self.live_url = self.test_url = 'https://secure.quickpay.dk/api' - - self.default_currency = 'DKK' - self.money_format = :cents - self.supported_cardtypes = [:dankort, :forbrugsforeningen, :visa, :master, :american_express, :diners_club, :jcb, :maestro] - self.supported_countries = ['DK', 'SE'] - self.homepage_url = 'http://quickpay.dk/' - self.display_name = 'Quickpay' - - MD5_CHECK_FIELDS = { - 3 => { - :authorize => %w(protocol msgtype merchant ordernumber amount - currency autocapture cardnumber expirationdate - cvd cardtypelock testmode), - - :capture => %w(protocol msgtype merchant amount transaction), - - :cancel => %w(protocol msgtype merchant transaction), - - :refund => %w(protocol msgtype merchant amount transaction), - - :subscribe => %w(protocol msgtype merchant ordernumber cardnumber - expirationdate cvd cardtypelock description testmode), - - :recurring => %w(protocol msgtype merchant ordernumber amount - currency autocapture transaction), - - :status => %w(protocol msgtype merchant transaction), - - :chstatus => %w(protocol msgtype merchant) - }, - - 4 => { - :authorize => %w(protocol msgtype merchant ordernumber amount - currency autocapture cardnumber expirationdate cvd - cardtypelock testmode fraud_remote_addr - fraud_http_accept fraud_http_accept_language - fraud_http_accept_encoding fraud_http_accept_charset - fraud_http_referer fraud_http_user_agent apikey), - - :capture => %w(protocol msgtype merchant amount transaction apikey), - - :cancel => %w(protocol msgtype merchant transaction apikey), - - :refund => %w(protocol msgtype merchant amount transaction apikey), - - :subscribe => %w(protocol msgtype merchant ordernumber cardnumber - expirationdate cvd cardtypelock description testmode - fraud_remote_addr fraud_http_accept fraud_http_accept_language - fraud_http_accept_encoding fraud_http_accept_charset - fraud_http_referer fraud_http_user_agent apikey), - - :recurring => %w(protocol msgtype merchant ordernumber amount currency - autocapture transaction apikey), - - :status => %w(protocol msgtype merchant transaction apikey), - - :chstatus => %w(protocol msgtype merchant apikey) - }, - - 5 => { - :authorize => %w(protocol msgtype merchant ordernumber amount - currency autocapture cardnumber expirationdate cvd - cardtypelock testmode fraud_remote_addr - fraud_http_accept fraud_http_accept_language - fraud_http_accept_encoding fraud_http_accept_charset - fraud_http_referer fraud_http_user_agent apikey), - - :capture => %w(protocol msgtype merchant amount transaction apikey), - - :cancel => %w(protocol msgtype merchant transaction apikey), - - :refund => %w(protocol msgtype merchant amount transaction apikey), - - :subscribe => %w(protocol msgtype merchant ordernumber cardnumber - expirationdate cvd cardtypelock description testmode - fraud_remote_addr fraud_http_accept fraud_http_accept_language - fraud_http_accept_encoding fraud_http_accept_charset - fraud_http_referer fraud_http_user_agent apikey), - - :recurring => %w(protocol msgtype merchant ordernumber amount currency - autocapture transaction apikey), - - :status => %w(protocol msgtype merchant transaction apikey), - - :chstatus => %w(protocol msgtype merchant apikey) - }, - - 6 => { - :authorize => %w(protocol msgtype merchant ordernumber amount - currency autocapture cardnumber expirationdate cvd - cardtypelock testmode fraud_remote_addr - fraud_http_accept fraud_http_accept_language - fraud_http_accept_encoding fraud_http_accept_charset - fraud_http_referer fraud_http_user_agent apikey), - - :capture => %w(protocol msgtype merchant amount transaction - apikey), - - :cancel => %w(protocol msgtype merchant transaction apikey), - - :refund => %w(protocol msgtype merchant amount transaction apikey), - - :subscribe => %w(protocol msgtype merchant ordernumber cardnumber - expirationdate cvd cardtypelock description testmode - fraud_remote_addr fraud_http_accept fraud_http_accept_language - fraud_http_accept_encoding fraud_http_accept_charset - fraud_http_referer fraud_http_user_agent apikey), - - :recurring => %w(protocol msgtype merchant ordernumber amount currency - autocapture transaction apikey), - - :status => %w(protocol msgtype merchant transaction apikey), - - :chstatus => %w(protocol msgtype merchant apikey) - } - } - - APPROVED = '000' - - # The login is the QuickpayId - # The password is the md5checkword from the Quickpay manager - # To use the API-key from the Quickpay manager, specify :api-key - # Using the API-key, requires that you use version 4+. Specify :version => 4/5/6 in options. - def initialize(options = {}) - requires!(options, :login, :password) - @protocol = options.delete(:version) || 3 # default to protocol version 3 - super - end - - def authorize(money, credit_card_or_reference, options = {}) - post = {} - - action = recurring_or_authorize(credit_card_or_reference) - - add_amount(post, money, options) - add_invoice(post, options) - add_creditcard_or_reference(post, credit_card_or_reference, options) - add_autocapture(post, false) - add_fraud_parameters(post, options) if action.eql?(:authorize) - add_testmode(post) - - commit(action, post) - end - - def purchase(money, credit_card_or_reference, options = {}) - post = {} - - action = recurring_or_authorize(credit_card_or_reference) - - add_amount(post, money, options) - add_creditcard_or_reference(post, credit_card_or_reference, options) - add_invoice(post, options) - add_fraud_parameters(post, options) if action.eql?(:authorize) - add_autocapture(post, true) - - commit(action, post) - end - - def capture(money, authorization, options = {}) - post = {} - - add_reference(post, authorization) - add_amount_without_currency(post, money) - commit(:capture, post) - end - - def void(identification, options = {}) - post = {} - - add_reference(post, identification) - - commit(:cancel, post) - end - - def refund(money, identification, options = {}) - post = {} + self.abstract_class = true - add_amount_without_currency(post, money) - add_reference(post, identification) + def self.new(options = {}) + options.fetch(:login) { raise ArgumentError.new('Missing required parameter: login') } - commit(:refund, post) - end - - def credit(money, identification, options = {}) - deprecated CREDIT_DEPRECATION_MESSAGE - refund(money, identification, options) - end - - def store(creditcard, options = {}) - post = {} - - add_creditcard(post, creditcard, options) - add_invoice(post, options) - add_description(post, options) - add_fraud_parameters(post, options) - add_testmode(post) - - commit(:subscribe, post) - end - - private - - def add_amount(post, money, options = {}) - post[:amount] = amount(money) - post[:currency] = options[:currency] || currency(money) - end - - def add_amount_without_currency(post, money, options = {}) - post[:amount] = amount(money) - end - - def add_invoice(post, options) - post[:ordernumber] = format_order_number(options[:order_id]) - end - - def add_creditcard(post, credit_card, options) - post[:cardnumber] = credit_card.number - post[:cvd] = credit_card.verification_value - post[:expirationdate] = expdate(credit_card) - post[:cardtypelock] = options[:cardtypelock] unless options[:cardtypelock].blank? - end - - def add_reference(post, identification) - post[:transaction] = identification - end - - def add_creditcard_or_reference(post, credit_card_or_reference, options) - if credit_card_or_reference.is_a?(String) - add_reference(post, credit_card_or_reference) + version = options[:login].to_i < 10000000 ? 10 : 7 + if version <= 7 + QuickpayV4to7Gateway.new(options) else - add_creditcard(post, credit_card_or_reference, options) + QuickpayV10Gateway.new(options) end end - def add_autocapture(post, autocapture) - post[:autocapture] = autocapture ? 1 : 0 - end - - def recurring_or_authorize(credit_card_or_reference) - credit_card_or_reference.is_a?(String) ? :recurring : :authorize - end - - def add_description(post, options) - post[:description] = options[:description] - end - - def add_testmode(post) - return if post[:transaction].present? - post[:testmode] = test? ? '1' : '0' - end - - def add_fraud_parameters(post, options) - if @protocol >= 4 - post[:fraud_remote_addr] = options[:fraud_remote_addr] if options[:fraud_remote_addr] - post[:fraud_http_accept] = options[:fraud_http_accept] if options[:fraud_http_accept] - post[:fraud_http_accept_language] = options[:fraud_http_accept_language] if options[:fraud_http_accept_language] - post[:fraud_http_accept_encoding] = options[:fraud_http_accept_encoding] if options[:fraud_http_accept_encoding] - post[:fraud_http_accept_charset] = options[:fraud_http_accept_charset] if options[:fraud_http_accept_charset] - post[:fraud_http_referer] = options[:fraud_http_referer] if options[:fraud_http_referer] - post[:fraud_http_user_agent] = options[:fraud_http_user_agent] if options[:fraud_http_user_agent] - end - end - - def commit(action, params) - response = parse(ssl_post(self.live_url, post_data(action, params))) - - Response.new(successful?(response), message_from(response), response, - :test => test?, - :authorization => response[:transaction] - ) - end - - def successful?(response) - response[:qpstat] == APPROVED - end - - def parse(data) - response = {} - - doc = REXML::Document.new(data) - - doc.root.elements.each do |element| - response[element.name.to_sym] = element.text - end - - response - end - - def message_from(response) - response[:qpstatmsg].to_s - end - - def post_data(action, params = {}) - params[:protocol] = @protocol - params[:msgtype] = action.to_s - params[:merchant] = @options[:login] - params[:apikey] = @options[:apikey] if @options[:apikey] - params[:md5check] = generate_check_hash(action, params) - - params.collect { |key, value| "#{key}=#{CGI.escape(value.to_s)}" }.join("&") - end - - def generate_check_hash(action, params) - string = MD5_CHECK_FIELDS[@protocol][action].collect do |key| - params[key.to_sym] - end.join('') - - # Add the md5checkword - string << @options[:password].to_s - - Digest::MD5.hexdigest(string) - end - - def expdate(credit_card) - year = format(credit_card.year, :two_digits) - month = format(credit_card.month, :two_digits) - - "#{year}#{month}" - end - - # Limited to 20 digits max - def format_order_number(number) - number.to_s.gsub(/[^\w_]/, '').rjust(4, "0")[0...20] - end end end end - diff --git a/lib/active_merchant/billing/gateways/quickpay/quickpay_common.rb b/lib/active_merchant/billing/gateways/quickpay/quickpay_common.rb new file mode 100644 index 00000000000..930958e0cbb --- /dev/null +++ b/lib/active_merchant/billing/gateways/quickpay/quickpay_common.rb @@ -0,0 +1,184 @@ +module QuickpayCommon + MD5_CHECK_FIELDS = { + 3 => { + :authorize => %w(protocol msgtype merchant ordernumber amount + currency autocapture cardnumber expirationdate + cvd cardtypelock testmode), + + :capture => %w(protocol msgtype merchant amount finalize transaction), + + :cancel => %w(protocol msgtype merchant transaction), + + :refund => %w(protocol msgtype merchant amount transaction), + + :subscribe => %w(protocol msgtype merchant ordernumber cardnumber + expirationdate cvd cardtypelock description testmode), + + :recurring => %w(protocol msgtype merchant ordernumber amount + currency autocapture transaction), + + :status => %w(protocol msgtype merchant transaction), + + :chstatus => %w(protocol msgtype merchant) + }, + + 4 => { + :authorize => %w(protocol msgtype merchant ordernumber amount + currency autocapture cardnumber expirationdate cvd + cardtypelock testmode fraud_remote_addr + fraud_http_accept fraud_http_accept_language + fraud_http_accept_encoding fraud_http_accept_charset + fraud_http_referer fraud_http_user_agent apikey), + + :capture => %w(protocol msgtype merchant amount finalize transaction apikey), + + :cancel => %w(protocol msgtype merchant transaction apikey), + + :refund => %w(protocol msgtype merchant amount transaction apikey), + + :subscribe => %w(protocol msgtype merchant ordernumber cardnumber + expirationdate cvd cardtypelock description testmode + fraud_remote_addr fraud_http_accept fraud_http_accept_language + fraud_http_accept_encoding fraud_http_accept_charset + fraud_http_referer fraud_http_user_agent apikey), + + :recurring => %w(protocol msgtype merchant ordernumber amount currency + autocapture transaction apikey), + + :status => %w(protocol msgtype merchant transaction apikey), + + :chstatus => %w(protocol msgtype merchant apikey) + }, + + 5 => { + :authorize => %w(protocol msgtype merchant ordernumber amount + currency autocapture cardnumber expirationdate cvd + cardtypelock testmode fraud_remote_addr + fraud_http_accept fraud_http_accept_language + fraud_http_accept_encoding fraud_http_accept_charset + fraud_http_referer fraud_http_user_agent apikey), + + :capture => %w(protocol msgtype merchant amount finalize transaction apikey), + + :cancel => %w(protocol msgtype merchant transaction apikey), + + :refund => %w(protocol msgtype merchant amount transaction apikey), + + :subscribe => %w(protocol msgtype merchant ordernumber cardnumber + expirationdate cvd cardtypelock description testmode + fraud_remote_addr fraud_http_accept fraud_http_accept_language + fraud_http_accept_encoding fraud_http_accept_charset + fraud_http_referer fraud_http_user_agent apikey), + + :recurring => %w(protocol msgtype merchant ordernumber amount currency + autocapture transaction apikey), + + :status => %w(protocol msgtype merchant transaction apikey), + + :chstatus => %w(protocol msgtype merchant apikey) + }, + + 6 => { + :authorize => %w(protocol msgtype merchant ordernumber amount + currency autocapture cardnumber expirationdate cvd + cardtypelock testmode fraud_remote_addr + fraud_http_accept fraud_http_accept_language + fraud_http_accept_encoding fraud_http_accept_charset + fraud_http_referer fraud_http_user_agent apikey), + + :capture => %w(protocol msgtype merchant amount finalize transaction + apikey), + + :cancel => %w(protocol msgtype merchant transaction apikey), + + :refund => %w(protocol msgtype merchant amount transaction apikey), + + :subscribe => %w(protocol msgtype merchant ordernumber cardnumber + expirationdate cvd cardtypelock description testmode + fraud_remote_addr fraud_http_accept fraud_http_accept_language + fraud_http_accept_encoding fraud_http_accept_charset + fraud_http_referer fraud_http_user_agent apikey), + + :recurring => %w(protocol msgtype merchant ordernumber amount currency + autocapture transaction apikey), + + :status => %w(protocol msgtype merchant transaction apikey), + + :chstatus => %w(protocol msgtype merchant apikey) + }, + + 7 => { + :authorize => %w(protocol msgtype merchant ordernumber amount + currency autocapture cardnumber expirationdate cvd + acquirers cardtypelock testmode fraud_remote_addr + fraud_http_accept fraud_http_accept_language + fraud_http_accept_encoding fraud_http_accept_charset + fraud_http_referer fraud_http_user_agent apikey), + + :capture => %w(protocol msgtype merchant amount finalize transaction + apikey), + + :cancel => %w(protocol msgtype merchant transaction apikey), + + :refund => %w(protocol msgtype merchant amount transaction apikey), + + :subscribe => %w(protocol msgtype merchant ordernumber amount currency + cardnumber expirationdate cvd acquirers cardtypelock + description testmode fraud_remote_addr fraud_http_accept + fraud_http_accept_language fraud_http_accept_encoding + fraud_http_accept_charset fraud_http_referer + fraud_http_user_agent apikey), + + :recurring => %w(protocol msgtype merchant ordernumber amount currency + autocapture transaction apikey), + + :status => %w(protocol msgtype merchant transaction apikey), + + :chstatus => %w(protocol msgtype merchant apikey) + }, + + 10 => { + :authorize => %w(mobile_number acquirer autofee customer_id extras + zero_auth customer_ip), + :capture => %w( extras ), + :cancel => %w( extras ), + :refund => %w( extras ), + :subscribe => %w( variables branding_id), + :authorize_subscription => %w( mobile_number acquirer customer_ip), + :recurring => %w(auto_capture autofee zero_auth) + } + } + + RESPONSE_CODES = { + 200 => 'OK', + 201 => 'Created', + 202 => 'Accepted', + 400 => 'Bad Request', + 401 => 'UnAuthorized', + 402 => 'Payment Required', + 403 => 'Forbidden', + 404 => 'Not Found', + 405 => 'Method Not Allowed', + 406 => 'Not Acceptable', + 409 => 'Conflict', + 500 => 'Internal Server Error' + } + + def self.included(base) + base.default_currency = 'DKK' + base.money_format = :cents + + base.supported_cardtypes = [:dankort, :forbrugsforeningen, :visa, :master, + :american_express, :diners_club, :jcb, :maestro] + base.supported_countries = ['DE', 'DK', 'ES', 'FI', 'FR', 'FO', 'GB', 'IS', 'NO', 'SE'] + base.homepage_url = 'http://quickpay.net/' + base.display_name = 'QuickPay' + end + + def expdate(credit_card) + year = format(credit_card.year, :two_digits) + month = format(credit_card.month, :two_digits) + + "#{year}#{month}" + end +end diff --git a/lib/active_merchant/billing/gateways/quickpay/quickpay_v10.rb b/lib/active_merchant/billing/gateways/quickpay/quickpay_v10.rb new file mode 100644 index 00000000000..565890a150e --- /dev/null +++ b/lib/active_merchant/billing/gateways/quickpay/quickpay_v10.rb @@ -0,0 +1,301 @@ +require 'json' +require 'active_merchant/billing/gateways/quickpay/quickpay_common' + +module ActiveMerchant + module Billing + class QuickpayV10Gateway < Gateway + include QuickpayCommon + API_VERSION = 10 + + self.live_url = self.test_url = 'https://api.quickpay.net' + + def initialize(options = {}) + requires!(options, :api_key) + super + end + + def purchase(money, credit_card_or_reference, options = {}) + MultiResponse.run do |r| + if credit_card_or_reference.is_a?(String) + r.process { create_token(credit_card_or_reference, options) } + credit_card_or_reference = r.authorization + end + r.process { create_payment(money, options) } + r.process { + post = authorization_params(money, credit_card_or_reference, options) + add_autocapture(post, false) + commit(synchronized_path("/payments/#{r.responses.last.params["id"]}/authorize"), post) + } + r.process { + post = capture_params(money, credit_card_or_reference, options) + commit(synchronized_path("/payments/#{r.responses.last.params["id"]}/capture"), post) + } + end + end + + def authorize(money, credit_card_or_reference, options = {}) + MultiResponse.run do |r| + if credit_card_or_reference.is_a?(String) + r.process { create_token(credit_card_or_reference, options) } + credit_card_or_reference = r.authorization + end + r.process { create_payment(money, options) } + r.process { + post = authorization_params(money, credit_card_or_reference, options) + commit(synchronized_path("/payments/#{r.responses.last.params["id"]}/authorize"), post) + } + end + end + + def void(identification, _options = {}) + commit(synchronized_path "/payments/#{identification}/cancel") + end + + def credit(money, identification, options = {}) + ActiveMerchant.deprecated CREDIT_DEPRECATION_MESSAGE + refund(money, identification, options) + end + + def capture(money, identification, options = {}) + post = capture_params(money, identification, options) + commit(synchronized_path("/payments/#{identification}/capture"), post) + end + + def refund(money, identification, options = {}) + post = {} + add_amount(post, money, options) + add_additional_params(:refund, post, options) + commit(synchronized_path("/payments/#{identification}/refund"), post) + end + + def verify(credit_card, options={}) + MultiResponse.run(:use_first_response) do |r| + r.process { authorize(100, credit_card, options) } + r.process(:ignore_result) { void(r.authorization, options) } + end + end + + def store(credit_card, options = {}) + MultiResponse.run do |r| + r.process { create_store(options) } + r.process { authorize_store(r.authorization, credit_card, options) } + end + end + + def unstore(identification) + commit(synchronized_path "/cards/#{identification}/cancel") + end + + def supports_scrubbing? + true + end + + def scrub(transcript) + transcript. + gsub(%r((Authorization: Basic )\w+), '\1[FILTERED]'). + gsub(%r(("card\\?":{\\?"number\\?":\\?")\d+), '\1[FILTERED]'). + gsub(%r(("cvd\\?":\\?")\d+), '\1[FILTERED]') + end + + private + + def authorization_params(money, credit_card_or_reference, options = {}) + post = {} + + add_amount(post, money, options) + add_credit_card_or_reference(post, credit_card_or_reference, options) + add_additional_params(:authorize, post, options) + + post + end + + def capture_params(money, credit_card, options = {}) + post = {} + + add_amount(post, money, options) + add_additional_params(:capture, post, options) + + post + end + + def create_store(options = {}) + post = {} + commit('/cards', post) + end + + def authorize_store(identification, credit_card, options = {}) + post = {} + + add_credit_card_or_reference(post, credit_card, options) + commit(synchronized_path("/cards/#{identification}/authorize"), post) + end + + def create_token(identification, options) + post = {} + commit(synchronized_path("/cards/#{identification}/tokens"), post) + end + + def create_payment(money, options = {}) + post = {} + add_currency(post, money, options) + add_invoice(post, options) + commit('/payments', post) + end + + def commit(action, params = {}) + success = false + begin + response = parse(ssl_post(self.live_url + action, params.to_json, headers)) + success = successful?(response) + rescue ResponseError => e + response = response_error(e.response.body) + rescue JSON::ParserError + response = json_error(response) + end + + Response.new(success, message_from(success, response), response, + :test => test?, + :authorization => authorization_from(response) + ) + end + + def authorization_from(response) + if response['token'] + response['token'].to_s + else + response['id'].to_s + end + end + + def add_currency(post, money, options) + post[:currency] = options[:currency] || currency(money) + end + + def add_amount(post, money, options) + post[:amount] = options[:amount] || amount(money) + end + + def add_autocapture(post, value) + post[:auto_capture] = value + end + + def add_order_id(post, options) + requires!(options, :order_id) + post[:order_id] = format_order_id(options[:order_id]) + end + + def add_invoice(post, options) + add_order_id(post, options) + + if options[:billing_address] + post[:invoice_address] = map_address(options[:billing_address]) + end + + if options[:shipping_address] + post[:shipping_address] = map_address(options[:shipping_address]) + end + + [:metadata, :branding_id, :variables].each do |field| + post[field] = options[field] if options[field] + end + end + + def add_additional_params(action, post, options = {}) + MD5_CHECK_FIELDS[API_VERSION][action].each do |key| + key = key.to_sym + post[key] = options[key] if options[key] + end + end + + def add_credit_card_or_reference(post, credit_card_or_reference, options = {}) + post[:card] ||= {} + if credit_card_or_reference.is_a?(String) + post[:card][:token] = credit_card_or_reference + else + post[:card][:number] = credit_card_or_reference.number + post[:card][:cvd] = credit_card_or_reference.verification_value + post[:card][:expiration] = expdate(credit_card_or_reference) + post[:card][:issued_to] = credit_card_or_reference.name + end + + if options[:three_d_secure] + post[:card][:cavv]= options.dig(:three_d_secure, :cavv) + post[:card][:eci] = options.dig(:three_d_secure, :eci) + post[:card][:xav] = options.dig(:three_d_secure, :xid) + end + end + + def parse(body) + JSON.parse(body) + end + + def successful?(response) + has_error = response['errors'] + invalid_code = invalid_operation_code?(response) + + !(has_error || invalid_code) + end + + def message_from(success, response) + success ? 'OK' : (response['message'] || invalid_operation_message(response) || 'Unknown error - please contact QuickPay') + end + + def invalid_operation_code?(response) + if response['operations'] + operation = response['operations'].last + operation && operation['qp_status_code'] != '20000' + end + end + + def invalid_operation_message(response) + response['operations'] && response['operations'].last['qp_status_msg'] + end + + def map_address(address) + return {} if address.nil? + requires!(address, :name, :address1, :city, :zip, :country) + country = Country.find(address[:country]) + mapped = { + :name => address[:name], + :street => address[:address1], + :city => address[:city], + :region => address[:address2], + :zip_code => address[:zip], + :country_code => country.code(:alpha3).value + } + mapped + end + + def format_order_id(order_id) + truncate(order_id.to_s.gsub(/#/, ''), 20) + end + + def headers + auth = Base64.strict_encode64(":#{@options[:api_key]}") + { + 'Authorization' => 'Basic ' + auth, + 'User-Agent' => "Quickpay-v#{API_VERSION} ActiveMerchantBindings/#{ActiveMerchant::VERSION}", + 'Accept' => 'application/json', + 'Accept-Version' => "v#{API_VERSION}", + 'Content-Type' => 'application/json' + } + end + + def response_error(raw_response) + parse(raw_response) + rescue JSON::ParserError + json_error(raw_response) + end + + def json_error(raw_response) + msg = 'Invalid response received from the Quickpay API.' + msg += " (The raw response returned by the API was #{raw_response.inspect})" + { 'message' => msg } + end + + def synchronized_path(path) + "#{path}?synchronized" + end + end + end +end diff --git a/lib/active_merchant/billing/gateways/quickpay/quickpay_v4to7.rb b/lib/active_merchant/billing/gateways/quickpay/quickpay_v4to7.rb new file mode 100644 index 00000000000..810d5cdefaa --- /dev/null +++ b/lib/active_merchant/billing/gateways/quickpay/quickpay_v4to7.rb @@ -0,0 +1,226 @@ +require 'rexml/document' +require 'digest/md5' +require 'active_merchant/billing/gateways/quickpay/quickpay_common' + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class QuickpayV4to7Gateway < Gateway + include QuickpayCommon + self.live_url = self.test_url = 'https://secure.quickpay.dk/api' + APPROVED = '000' + + # The login is the QuickpayId + # The password is the md5checkword from the Quickpay manager + # To use the API-key from the Quickpay manager, specify :api-key + # Using the API-key, requires that you use version 4+. Specify :version => 4/5/6/7 in options. + def initialize(options = {}) + requires!(options, :login, :password) + @protocol = options.delete(:version) || 7 # default to protocol version 7 + super + end + + def authorize(money, credit_card_or_reference, options = {}) + post = {} + + action = recurring_or_authorize(credit_card_or_reference) + + add_amount(post, money, options) + add_invoice(post, options) + add_creditcard_or_reference(post, credit_card_or_reference, options) + add_autocapture(post, false) + add_fraud_parameters(post, options) if action.eql?(:authorize) + add_testmode(post) + + commit(action, post) + end + + def purchase(money, credit_card_or_reference, options = {}) + post = {} + + action = recurring_or_authorize(credit_card_or_reference) + + add_amount(post, money, options) + add_creditcard_or_reference(post, credit_card_or_reference, options) + add_invoice(post, options) + add_fraud_parameters(post, options) if action.eql?(:authorize) + add_autocapture(post, true) + + commit(action, post) + end + + def capture(money, authorization, options = {}) + post = {} + + add_finalize(post, options) + add_reference(post, authorization) + add_amount_without_currency(post, money) + commit(:capture, post) + end + + def void(identification, options = {}) + post = {} + + add_reference(post, identification) + + commit(:cancel, post) + end + + def refund(money, identification, options = {}) + post = {} + + add_amount_without_currency(post, money) + add_reference(post, identification) + + commit(:refund, post) + end + + def credit(money, identification, options = {}) + ActiveMerchant.deprecated CREDIT_DEPRECATION_MESSAGE + refund(money, identification, options) + end + + def store(creditcard, options = {}) + post = {} + + add_creditcard(post, creditcard, options) + add_amount(post, 0, options) if @protocol >= 7 + add_invoice(post, options) + add_description(post, options) + add_fraud_parameters(post, options) + add_testmode(post) + + commit(:subscribe, post) + end + + private + + def add_amount(post, money, options = {}) + post[:amount] = amount(money) + post[:currency] = options[:currency] || currency(money) + end + + def add_amount_without_currency(post, money, options = {}) + post[:amount] = amount(money) + end + + def add_invoice(post, options) + post[:ordernumber] = format_order_number(options[:order_id]) + end + + def add_creditcard(post, credit_card, options) + post[:cardnumber] = credit_card.number + post[:cvd] = credit_card.verification_value + post[:expirationdate] = expdate(credit_card) + post[:cardtypelock] = options[:cardtypelock] unless options[:cardtypelock].blank? + post[:acquirers] = options[:acquirers] unless options[:acquirers].blank? + end + + def add_reference(post, identification) + post[:transaction] = identification + end + + def add_creditcard_or_reference(post, credit_card_or_reference, options) + if credit_card_or_reference.is_a?(String) + add_reference(post, credit_card_or_reference) + else + add_creditcard(post, credit_card_or_reference, options) + end + end + + def add_autocapture(post, autocapture) + post[:autocapture] = autocapture ? 1 : 0 + end + + def recurring_or_authorize(credit_card_or_reference) + credit_card_or_reference.is_a?(String) ? :recurring : :authorize + end + + def add_description(post, options) + post[:description] = options[:description] || 'Description' + end + + def add_testmode(post) + return if post[:transaction].present? + post[:testmode] = test? ? '1' : '0' + end + + def add_fraud_parameters(post, options) + if @protocol >= 4 + post[:fraud_remote_addr] = options[:ip] if options[:ip] + post[:fraud_http_accept] = options[:fraud_http_accept] if options[:fraud_http_accept] + post[:fraud_http_accept_language] = options[:fraud_http_accept_language] if options[:fraud_http_accept_language] + post[:fraud_http_accept_encoding] = options[:fraud_http_accept_encoding] if options[:fraud_http_accept_encoding] + post[:fraud_http_accept_charset] = options[:fraud_http_accept_charset] if options[:fraud_http_accept_charset] + post[:fraud_http_referer] = options[:fraud_http_referer] if options[:fraud_http_referer] + post[:fraud_http_user_agent] = options[:fraud_http_user_agent] if options[:fraud_http_user_agent] + end + end + + def add_finalize(post, options) + post[:finalize] = options[:finalize] ? '1' : '0' + end + + def commit(action, params) + response = parse(ssl_post(self.live_url, post_data(action, params))) + + Response.new(successful?(response), message_from(response), response, + :test => test?, + :authorization => response[:transaction] + ) + end + + def successful?(response) + response[:qpstat] == APPROVED + end + + def parse(data) + response = {} + + doc = REXML::Document.new(data) + + doc.root.elements.each do |element| + response[element.name.to_sym] = element.text + end + + response + end + + def message_from(response) + response[:qpstatmsg].to_s + end + + def post_data(action, params = {}) + params[:protocol] = @protocol + params[:msgtype] = action.to_s + params[:merchant] = @options[:login] + params[:apikey] = @options[:apikey] if @options[:apikey] + params[:md5check] = generate_check_hash(action, params) + + params.collect { |key, value| "#{key}=#{CGI.escape(value.to_s)}" }.join('&') + end + + def generate_check_hash(action, params) + string = MD5_CHECK_FIELDS[@protocol][action].collect do |key| + params[key.to_sym] + end.join('') + + # Add the md5checkword + string << @options[:password].to_s + + Digest::MD5.hexdigest(string) + end + + def expdate(credit_card) + year = format(credit_card.year, :two_digits) + month = format(credit_card.month, :two_digits) + + "#{year}#{month}" + end + + # Limited to 20 digits max + def format_order_number(number) + number.to_s.gsub(/[^\w]/, '').rjust(4, '0')[0...20] + end + end + end +end diff --git a/lib/active_merchant/billing/gateways/qvalent.rb b/lib/active_merchant/billing/gateways/qvalent.rb new file mode 100644 index 00000000000..420b8cccc96 --- /dev/null +++ b/lib/active_merchant/billing/gateways/qvalent.rb @@ -0,0 +1,289 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class QvalentGateway < Gateway + self.display_name = 'Qvalent' + self.homepage_url = 'https://www.qvalent.com/' + + self.test_url = 'https://ccapi.client.support.qvalent.com/post/CreditCardAPIReceiver' + self.live_url = 'https://ccapi.client.qvalent.com/post/CreditCardAPIReceiver' + + self.supported_countries = ['AU'] + self.default_currency = 'AUD' + self.money_format = :cents + self.supported_cardtypes = [:visa, :master, :american_express, :discover, :jcb, :diners] + + CVV_CODE_MAPPING = { + 'S' => 'D' + } + + def initialize(options={}) + requires!(options, :username, :password, :merchant, :pem, :pem_password) + super + end + + def purchase(amount, payment_method, options={}) + post = {} + add_invoice(post, amount, options) + add_order_number(post, options) + add_payment_method(post, payment_method) + add_verification_value(post, payment_method) + add_stored_credential_data(post, payment_method, options) + add_customer_data(post, options) + add_soft_descriptors(post, options) + + commit('capture', post) + end + + def authorize(amount, payment_method, options={}) + post = {} + add_invoice(post, amount, options) + add_order_number(post, options) + add_payment_method(post, payment_method) + add_verification_value(post, payment_method) + add_stored_credential_data(post, payment_method, options) + add_customer_data(post, options) + add_soft_descriptors(post, options) + + commit('preauth', post) + end + + def capture(amount, authorization, options={}) + post = {} + add_invoice(post, amount, options) + add_reference(post, authorization, options) + add_customer_data(post, options) + add_soft_descriptors(post, options) + + commit('captureWithoutAuth', post) + end + + def refund(amount, authorization, options={}) + post = {} + add_invoice(post, amount, options) + add_reference(post, authorization, options) + add_customer_data(post, options) + add_soft_descriptors(post, options) + post['order.ECI'] = options[:eci] || 'SSL' + + commit('refund', post) + end + + # Credit requires the merchant account to be enabled for "Adhoc Refunds" + def credit(amount, payment_method, options={}) + post = {} + add_invoice(post, amount, options) + add_order_number(post, options) + add_payment_method(post, payment_method) + add_customer_data(post, options) + add_soft_descriptors(post, options) + + commit('refund', post) + end + + def void(authorization, options={}) + post = {} + add_reference(post, authorization, options) + add_customer_data(post, options) + add_soft_descriptors(post, options) + + commit('reversal', post) + end + + def store(payment_method, options = {}) + post = {} + add_payment_method(post, payment_method) + add_card_reference(post) + + commit('registerAccount', post) + end + + def supports_scrubbing? + true + end + + def scrub(transcript) + transcript. + gsub(%r((&?customer.password=)[^&]*), '\1[FILTERED]'). + gsub(%r((&?card.PAN=)[^&]*), '\1[FILTERED]'). + gsub(%r((&?card.CVN=)[^&]*), '\1[FILTERED]') + end + + private + + CURRENCY_CODES = Hash.new { |h, k| raise ArgumentError.new("Unsupported currency: #{k}") } + CURRENCY_CODES['AUD'] = 'AUD' + CURRENCY_CODES['INR'] = 'INR' + + def add_soft_descriptors(post, options) + post['customer.merchantName'] = options[:customer_merchant_name] if options[:customer_merchant_name] + post['customer.merchantStreetAddress'] = options[:customer_merchant_street_address] if options[:customer_merchant_street_address] + post['customer.merchantLocation'] = options[:customer_merchant_location] if options[:customer_merchant_location] + post['customer.merchantState'] = options[:customer_merchant_state] if options[:customer_merchant_state] + post['customer.merchantCountry'] = options[:customer_merchant_country] if options[:customer_merchant_country] + post['customer.merchantPostCode'] = options[:customer_merchant_post_code] if options[:customer_merchant_post_code] + post['customer.subMerchantId'] = options[:customer_sub_merchant_id] if options[:customer_sub_merchant_id] + end + + def add_invoice(post, money, options) + post['order.amount'] = amount(money) + post['card.currency'] = CURRENCY_CODES[options[:currency] || currency(money)] + end + + def add_payment_method(post, payment_method) + post['card.cardHolderName'] = payment_method.name + post['card.PAN'] = payment_method.number + post['card.expiryYear'] = format(payment_method.year, :two_digits) + post['card.expiryMonth'] = format(payment_method.month, :two_digits) + end + + def add_stored_credential_data(post, payment_method, options) + post['order.ECI'] = options[:eci] || eci(options) + if (stored_credential = options[:stored_credential]) && %w(visa master).include?(payment_method.brand) + post['card.posEntryMode'] = stored_credential[:initial_transaction] ? 'MANUAL' : 'STORED_CREDENTIAL' + stored_credential_usage(post, payment_method, options) unless stored_credential[:initiator] && stored_credential[:initiator] == 'cardholder' + post['order.authTraceId'] = stored_credential[:network_transaction_id] if stored_credential[:network_transaction_id] + end + end + + def stored_credential_usage(post, payment_method, options) + return unless payment_method.brand == 'visa' + stored_credential = options[:stored_credential] + if stored_credential[:initial_transaction] + post['card.storedCredentialUsage'] = 'INITIAL_STORAGE' + elsif stored_credential[:reason_type] == ('recurring' || 'installment') + post['card.storedCredentialUsage'] = 'RECURRING' + elsif stored_credential[:reason_type] == 'unscheduled' + post['card.storedCredentialUsage'] = 'UNSCHEDULED' + end + end + + def eci(options) + if options.dig(:stored_credential, :initial_transaction) + 'SSL' + elsif options.dig(:stored_credential, :initiator) && options[:stored_credential][:initiator] == 'cardholder' + 'MTO' + elsif options.dig(:stored_credential, :reason_type) + case options[:stored_credential][:reason_type] + when 'recurring' + 'REC' + when 'installment' + 'INS' + when 'unscheduled' + 'MTO' + end + else + 'SSL' + end + end + + def add_verification_value(post, payment_method) + post['card.CVN'] = payment_method.verification_value + end + + def add_card_reference(post) + post['customer.customerReferenceNumber'] = options[:order_id] + end + + def add_reference(post, authorization, options) + post['customer.originalOrderNumber'] = authorization + add_order_number(post, options) + end + + def add_order_number(post, options) + post['customer.orderNumber'] = options[:order_id] || SecureRandom.uuid + end + + def add_customer_data(post, options) + post['order.ipAddress'] = options[:ip] || '127.0.0.1' + post['order.xid'] = options[:xid] if options[:xid] + post['order.cavv'] = options[:cavv] if options[:cavv] + end + + def commit(action, post) + post['customer.username'] = @options[:username] + post['customer.password'] = @options[:password] + post['customer.merchant'] = @options[:merchant] + post['order.type'] = action + + data = build_request(post) + raw = parse(ssl_post(url(action), data, headers)) + + succeeded = success_from(raw['response.responseCode']) + Response.new( + succeeded, + message_from(succeeded, raw), + raw, + authorization: raw['response.orderNumber'] || raw['response.customerReferenceNumber'], + cvv_result: cvv_result(succeeded, raw), + error_code: error_code_from(succeeded, raw), + test: test? + ) + end + + def cvv_result(succeeded, raw) + return unless succeeded + code = CVV_CODE_MAPPING[raw['response.cvnResponse']] || raw['response.cvnResponse'] + CVVResult.new(code) + end + + def headers + { + 'Content-Type' => 'application/x-www-form-urlencoded' + } + end + + def build_request(post) + post.to_query + '&message.end' + end + + def url(action) + (test? ? test_url : live_url) + end + + def parse(body) + result = {} + body.to_s.each_line do |pair| + result[$1] = $2 if pair.strip =~ /\A([^=]+)=(.+)\Z/im + end + result + end + + def parse_element(response, node) + if node.has_elements? + node.elements.each { |element| parse_element(response, element) } + else + response[node.name.underscore.to_sym] = node.text + end + end + + SUCCESS_CODES = %w(00 08 10 11 16 QS QZ) + + def success_from(response) + SUCCESS_CODES.include?(response) + end + + def message_from(succeeded, response) + if succeeded + 'Succeeded' + else + response['response.text'] || 'Unable to read error message' + end + end + + STANDARD_ERROR_CODE_MAPPING = { + '14' => STANDARD_ERROR_CODE[:invalid_number], + 'QQ' => STANDARD_ERROR_CODE[:invalid_cvc], + '33' => STANDARD_ERROR_CODE[:expired_card], + 'NT' => STANDARD_ERROR_CODE[:incorrect_address], + '12' => STANDARD_ERROR_CODE[:card_declined], + '06' => STANDARD_ERROR_CODE[:processing_error], + '01' => STANDARD_ERROR_CODE[:call_issuer], + '04' => STANDARD_ERROR_CODE[:pickup_card], + } + + def error_code_from(succeeded, response) + succeeded ? nil : STANDARD_ERROR_CODE_MAPPING[response['response.responseCode']] + end + end + end +end diff --git a/lib/active_merchant/billing/gateways/realex.rb b/lib/active_merchant/billing/gateways/realex.rb index 1cbbe5b0481..ca402a5ca97 100644 --- a/lib/active_merchant/billing/gateways/realex.rb +++ b/lib/active_merchant/billing/gateways/realex.rb @@ -12,7 +12,7 @@ module Billing # login - The unique id of the merchant # password - The secret is used to digitally sign the request # account - This is an optional third part of the authentication process - # and is used if the merchant wishes do distuinguish cc traffic from the different sources + # and is used if the merchant wishes do distinguish cc traffic from the different sources # by using a different account. This must be created in advance # # the Realex team decided to make the orderid unique per request, @@ -26,25 +26,24 @@ class RealexGateway < Gateway 'visa' => 'VISA', 'american_express' => 'AMEX', 'diners_club' => 'DINERS', - 'switch' => 'SWITCH', - 'solo' => 'SWITCH', - 'laser' => 'LASER' + 'maestro' => 'MC' } self.money_format = :cents self.default_currency = 'EUR' - self.supported_cardtypes = [ :visa, :master, :american_express, :diners_club, :switch, :solo, :laser ] - self.supported_countries = %w(IE GB FR BE NL LU IT) + self.supported_cardtypes = [ :visa, :master, :american_express, :diners_club ] + self.supported_countries = %w(IE GB FR BE NL LU IT US CA ES) self.homepage_url = 'http://www.realexpayments.com/' self.display_name = 'Realex' - SUCCESS, DECLINED = "Successful", "Declined" - BANK_ERROR = REALEX_ERROR = "Gateway is in maintenance. Please try again later." - ERROR = CLIENT_DEACTIVATED = "Gateway Error" + SUCCESS, DECLINED = 'Successful', 'Declined' + BANK_ERROR = REALEX_ERROR = 'Gateway is in maintenance. Please try again later.' + ERROR = CLIENT_DEACTIVATED = 'Gateway Error' def initialize(options = {}) requires!(options, :login, :password) - options[:refund_hash] = Digest::SHA1.hexdigest(options[:rebate_secret]) if options.has_key?(:rebate_secret) + options[:refund_hash] = Digest::SHA1.hexdigest(options[:rebate_secret]) if options[:rebate_secret].present? + options[:credit_hash] = Digest::SHA1.hexdigest(options[:refund_secret]) if options[:refund_secret].present? super end @@ -63,7 +62,7 @@ def authorize(money, creditcard, options = {}) end def capture(money, authorization, options = {}) - request = build_capture_request(authorization, options) + request = build_capture_request(money, authorization, options) commit(request) end @@ -72,9 +71,9 @@ def refund(money, authorization, options = {}) commit(request) end - def credit(money, authorization, options = {}) - deprecated CREDIT_DEPRECATION_MESSAGE - refund(money, authorization, options) + def credit(money, creditcard, options = {}) + request = build_credit_request(money, creditcard, options) + commit(request) end def void(authorization, options = {}) @@ -82,18 +81,36 @@ def void(authorization, options = {}) commit(request) end + def verify(credit_card, options = {}) + requires!(options, :order_id) + + request = build_verify_request(credit_card, options) + commit(request) + end + + def supports_scrubbing + true + end + + def scrub(transcript) + transcript. + gsub(%r((Authorization: Basic )\w+), '\1[FILTERED]'). + gsub(%r(()\d+())i, '\1[FILTERED]\2') + end + private + def commit(request) response = parse(ssl_post(self.live_url, request)) - Response.new(response[:result] == "00", message_from(response), response, - :test => response[:message] =~ /\[ test system \]/, + Response.new( + (response[:result] == '00'), + message_from(response), + response, + :test => (response[:message] =~ %r{\[ test system \]}), :authorization => authorization_from(response), - :cvv_result => response[:cvnresult], - :avs_result => { - :street_match => response[:avspostcoderesponse], - :postal_match => response[:avspostcoderesponse] - } + avs_result: AVSResult.new(code: response[:avspostcoderesponse]), + cvv_result: CVVResult.new(response[:cvnresult]) ) end @@ -102,7 +119,7 @@ def parse(xml) doc = Nokogiri::XML(xml) doc.xpath('//response/*').each do |node| - if (node.elements.size == 0) + if node.elements.size == 0 response[node.name.downcase.to_sym] = normalize(node.text) else node.elements.each do |childnode| @@ -129,20 +146,26 @@ def build_purchase_or_authorization_request(action, money, credit_card, options) add_card(xml, credit_card) xml.tag! 'autosettle', 'flag' => auto_settle_flag(action) add_signed_digest(xml, timestamp, @options[:login], sanitize_order_id(options[:order_id]), amount(money), (options[:currency] || currency(money)), credit_card.number) + if credit_card.is_a?(NetworkTokenizationCreditCard) + add_network_tokenization_card(xml, credit_card) + else + add_three_d_secure(xml, options) + end add_comments(xml, options) add_address_and_customer_info(xml, options) end xml.target! end - def build_capture_request(authorization, options) + def build_capture_request(money, authorization, options) timestamp = new_timestamp xml = Builder::XmlMarkup.new :indent => 2 xml.tag! 'request', 'timestamp' => timestamp, 'type' => 'settle' do add_merchant_details(xml, options) + add_amount(xml, money, options) add_transaction_identifiers(xml, authorization, options) add_comments(xml, options) - add_signed_digest(xml, timestamp, @options[:login], sanitize_order_id(options[:order_id]), nil, nil, nil) + add_signed_digest(xml, timestamp, @options[:login], sanitize_order_id(options[:order_id]), amount(money), (options[:currency] || currency(money)), nil) end xml.target! end @@ -162,6 +185,22 @@ def build_refund_request(money, authorization, options) xml.target! end + def build_credit_request(money, credit_card, options) + timestamp = new_timestamp + xml = Builder::XmlMarkup.new :indent => 2 + xml.tag! 'request', 'timestamp' => timestamp, 'type' => 'credit' do + add_merchant_details(xml, options) + xml.tag! 'orderid', sanitize_order_id(options[:order_id]) + add_amount(xml, money, options) + add_card(xml, credit_card) + xml.tag! 'refundhash', @options[:credit_hash] if @options[:credit_hash] + xml.tag! 'autosettle', 'flag' => 1 + add_comments(xml, options) + add_signed_digest(xml, timestamp, @options[:login], sanitize_order_id(options[:order_id]), amount(money), (options[:currency] || currency(money)), credit_card.number) + end + xml.target! + end + def build_void_request(authorization, options) timestamp = new_timestamp xml = Builder::XmlMarkup.new :indent => 2 @@ -174,6 +213,20 @@ def build_void_request(authorization, options) xml.target! end + # Verify initiates an OTB (Open To Buy) request + def build_verify_request(credit_card, options) + timestamp = new_timestamp + xml = Builder::XmlMarkup.new :indent => 2 + xml.tag! 'request', 'timestamp' => timestamp, 'type' => 'otb' do + add_merchant_details(xml, options) + xml.tag! 'orderid', sanitize_order_id(options[:order_id]) + add_card(xml, credit_card) + add_comments(xml, options) + add_signed_digest(xml, timestamp, @options[:login], sanitize_order_id(options[:order_id]), credit_card.number) + end + xml.target! + end + def add_address_and_customer_info(xml, options) billing_address = options[:billing_address] || options[:address] shipping_address = options[:shipping_address] @@ -187,14 +240,14 @@ def add_address_and_customer_info(xml, options) if billing_address xml.tag! 'address', 'type' => 'billing' do - xml.tag! 'code', format_shipping_zip_code(billing_address[:zip]) + xml.tag! 'code', format_address_code(billing_address) xml.tag! 'country', billing_address[:country] end end if shipping_address xml.tag! 'address', 'type' => 'shipping' do - xml.tag! 'code', format_shipping_zip_code(shipping_address[:zip]) + xml.tag! 'code', format_address_code(shipping_address) xml.tag! 'country', shipping_address[:country] end end @@ -232,7 +285,7 @@ def add_card(xml, credit_card) xml.tag! 'expdate', expiry_date(credit_card) xml.tag! 'chname', credit_card.name xml.tag! 'type', CARD_MAPPING[card_brand(credit_card).to_s] - xml.tag! 'issueno', credit_card.issue_number + xml.tag! 'issueno', '' xml.tag! 'cvn' do xml.tag! 'number', credit_card.verification_value xml.tag! 'presind', (options['presind'] || (credit_card.verification_value? ? 1 : nil)) @@ -240,8 +293,37 @@ def add_card(xml, credit_card) end end - def format_shipping_zip_code(zip) - zip.to_s.gsub(/\W/, '') + def add_network_tokenization_card(xml, payment) + xml.tag! 'mpi' do + xml.tag! 'cavv', payment.payment_cryptogram + xml.tag! 'eci', payment.eci + end + xml.tag! 'supplementarydata' do + xml.tag! 'item', 'type' => 'mobile' do + xml.tag! 'field01', payment.source.to_s.gsub('_', '-') + end + end + end + + def add_three_d_secure(xml, options) + return unless three_d_secure = options[:three_d_secure] + version = three_d_secure.fetch(:version, '') + xml.tag! 'mpi' do + if version =~ /^2/ + xml.tag! 'authentication_value', three_d_secure[:cavv] + xml.tag! 'ds_trans_id', three_d_secure[:ds_transaction_id] + else + xml.tag! 'cavv', three_d_secure[:cavv] + xml.tag! 'xid', three_d_secure[:xid] + end + xml.tag! 'eci', three_d_secure[:eci] + xml.tag! 'message_version', version + end + end + + def format_address_code(address) + code = [address[:zip].to_s, address[:address1].to_s + address[:address2].to_s] + code.collect { |e| e.gsub(/\D/, '') }.reject(&:empty?).join('|') end def new_timestamp @@ -249,8 +331,8 @@ def new_timestamp end def add_signed_digest(xml, *values) - string = Digest::SHA1.hexdigest(values.join(".")) - xml.tag! 'sha1hash', Digest::SHA1.hexdigest([string, @options[:password]].join(".")) + string = Digest::SHA1.hexdigest(values.join('.')) + xml.tag! 'sha1hash', Digest::SHA1.hexdigest([string, @options[:password]].join('.')) end def auto_settle_flag(action) @@ -261,37 +343,26 @@ def expiry_date(credit_card) "#{format(credit_card.month, :two_digits)}#{format(credit_card.year, :two_digits)}" end - def normalize(field) - case field - when "true" then true - when "false" then false - when "" then nil - when "null" then nil - else field - end - end - def message_from(response) - message = nil case response[:result] - when "00" - message = SUCCESS - when "101" - message = response[:message] - when "102", "103" - message = DECLINED + when '00' + SUCCESS + when '101' + response[:message] + when '102', '103' + DECLINED when /^2[0-9][0-9]/ - message = BANK_ERROR + BANK_ERROR when /^3[0-9][0-9]/ - message = REALEX_ERROR + REALEX_ERROR when /^5[0-9][0-9]/ - message = response[:message] - when "600", "601", "603" - message = ERROR - when "666" - message = CLIENT_DEACTIVATED + response[:message] + when '600', '601', '603' + ERROR + when '666' + CLIENT_DEACTIVATED else - message = DECLINED + DECLINED end end diff --git a/lib/active_merchant/billing/gateways/redsys.rb b/lib/active_merchant/billing/gateways/redsys.rb index 583e025393c..792b935d557 100644 --- a/lib/active_merchant/billing/gateways/redsys.rb +++ b/lib/active_merchant/billing/gateways/redsys.rb @@ -1,4 +1,5 @@ # coding: utf-8 + require 'nokogiri' module ActiveMerchant #:nodoc: @@ -9,30 +10,14 @@ module Billing #:nodoc: # used by many banks in Spain and is particularly well supported by # Catalunya Caixa's ecommerce department. # - # Standard ActiveMerchant methods are supported, with one notable exception: - # :order_id must be provided and must conform to a very specific format. - # - # == Example use: - # - # gateway = ActiveMerchant::Billing::RedsysGateway.new( - # :login => "091358382", - # :secret_key => "qwertyasdf0123456789" - # ) - # - # # Run a purchase for 10 euros - # response = gateway.purchase(1000, creditcard, :order_id => "123456") - # puts reponse.success? # => true - # - # # Partially refund the purchase - # response = gateway.refund(500, response.authorization) + # Redsys requires an order_id be provided with each transaction and it must + # follow a specific format. The rules are as follows: # - # Redsys requires an order_id be provided with each transaction of a - # specific format. The rules are as follows: - # - # * Minimum length: 4 - # * Maximum length: 12 # * First 4 digits must be numerical # * Remaining 8 digits may be alphanumeric + # * Max length: 12 + # + # If an invalid order_id is provided, we do our best to clean it up. # # Much of the code for this library is based on the active_merchant_sermepa # integration gateway which uses essentially the same API but with the @@ -40,43 +25,66 @@ module Billing #:nodoc: # # Written by Samuel Lown for Cabify. For implementation questions, or # test access details please get in touch: sam@cabify.com. + # + # *** SHA256 Authentication Update *** + # + # Redsys is dropping support for the SHA1 authentication method. This + # adapter has been updated to work with the new SHA256 authentication + # method, however in your initialization options hash you will need to + # specify the key/value :signature_algorithm => "sha256" to use the + # SHA256 method. Otherwise it will default to using the SHA1. + # + # class RedsysGateway < Gateway - self.live_url = "https://sis.sermepa.es/sis/operaciones" - self.test_url = "https://sis-t.sermepa.es:25443/sis/operaciones" + self.live_url = 'https://sis.sermepa.es/sis/operaciones' + self.test_url = 'https://sis-t.redsys.es:25443/sis/operaciones' - # Sensible region specific defaults. self.supported_countries = ['ES'] self.default_currency = 'EUR' self.money_format = :cents - # Not all card types may be actived by the bank! + # Not all card types may be activated by the bank! self.supported_cardtypes = [:visa, :master, :american_express, :jcb, :diners_club] - - # Homepage URL of the gateway for reference - self.homepage_url = "http://www.redsys.es/" - - # What to call this gateway - self.display_name = "Redsys" + self.homepage_url = 'http://www.redsys.es/' + self.display_name = 'Redsys' CURRENCY_CODES = { - "ARS" => '032', - "AUD" => '036', - "BRL" => '986', - "BOB" => '068', - "CAD" => '124', - "CHF" => '756', - "CLP" => '152', - "COP" => '170', - "EUR" => '978', - "GBP" => '826', - "GTQ" => '320', - "JPY" => '392', - "MXN" => '484', - "NZD" => '554', - "PEN" => '604', - "RUB" => '643', - "USD" => '840', - "UYU" => '858' + 'AED' => '784', + 'ARS' => '32', + 'AUD' => '36', + 'BRL' => '986', + 'BOB' => '68', + 'CAD' => '124', + 'CHF' => '756', + 'CLP' => '152', + 'CNY' => '156', + 'COP' => '170', + 'CRC' => '188', + 'CZK' => '203', + 'DKK' => '208', + 'DOP' => '214', + 'EUR' => '978', + 'GBP' => '826', + 'GTQ' => '320', + 'HUF' => '348', + 'IDR' => '360', + 'INR' => '356', + 'JPY' => '392', + 'KRW' => '410', + 'MYR' => '458', + 'MXN' => '484', + 'NOK' => '578', + 'NZD' => '554', + 'PEN' => '604', + 'PLN' => '985', + 'RUB' => '643', + 'SAR' => '682', + 'SEK' => '752', + 'SGD' => '702', + 'THB' => '764', + 'TWD' => '901', + 'USD' => '840', + 'UYU' => '858' } # The set of supported transactions for this gateway. @@ -94,77 +102,72 @@ class RedsysGateway < Gateway # a card has been rejected. Syntax or general request errors # are not covered here. RESPONSE_TEXTS = { - # Accepted Codes - 0 => "Transaction Approved", - 400 => "Cancellation Accepted", - 481 => "Cancellation Accepted", - 500 => "Reconciliation Accepted", - 900 => "Refund / Confirmation approved", - - # Declined error codes - 101 => "Card expired", - 102 => "Card blocked temporarily or under susciption of fraud", - 104 => "Transaction not permitted", - 107 => "Contact the card issuer", - 109 => "Invalid identification by merchant or POS terminal", - 110 => "Invalid amount", - 114 => "Card cannot be used to the requested transaction", - 116 => "Insufficient credit", - 118 => "Non-registered card", - 125 => "Card not effective", - 129 => "CVV2/CVC2 Error", - 167 => "Contact the card issuer: suspected fraud", - 180 => "Card out of service", - 181 => "Card with credit or debit restrictions", - 182 => "Card with credit or debit restrictions", - 184 => "Authentication error", - 190 => "Refusal with no specific reason", - 191 => "Expiry date incorrect", - - # Declined, and suspected of fraud - 201 => "Card expired", - 202 => "Card blocked temporarily or under suscipition of fraud", - 204 => "Transaction not permitted", - 207 => "Contact the card issuer", - 208 => "Lost or stolen card", - 209 => "Lost or stolen card", - 280 => "CVV2/CVC2 Error", - 290 => "Declined with no specific reason", - - # More general codes for specific types of transaction - 480 => "Original transaction not located, or time-out exceeded", - 501 => "Original transaction not located, or time-out exceeded", - 502 => "Original transaction not located, or time-out exceeded", - 503 => "Original transaction not located, or time-out exceeded", - - # Declined transactions by the bank - 904 => "Merchant not registered at FUC", - 909 => "System error", - 912 => "Issuer not available", - 913 => "Duplicate transmission", - 916 => "Amount too low", - 928 => "Time-out exceeded", - 940 => "Transaction cancelled previously", - 941 => "Authorization operation already cancelled", - 942 => "Original authorization declined", - 943 => "Different details from origin transaction", - 944 => "Session error", - 945 => "Duplicate transmission", - 946 => "Cancellation of transaction while in progress", - 947 => "Duplicate tranmission while in progress", - 949 => "POS Inoperative", - 950 => "Refund not possible", - 9064 => "Card number incorrect", - 9078 => "No payment method available", - 9093 => "Non-existent card", - 9218 => "Recursive transaction in bad gateway", - 9253 => "Check-digit incorrect", - 9256 => "Preauth not allowed for merchant", - 9257 => "Preauth not allowed for card", - 9261 => "Operating limit exceeded", - 9912 => "Issuer not available", - 9913 => "Confirmation error", - 9914 => "KO Confirmation" + 0 => 'Transaction Approved', + 400 => 'Cancellation Accepted', + 481 => 'Cancellation Accepted', + 500 => 'Reconciliation Accepted', + 900 => 'Refund / Confirmation approved', + + 101 => 'Card expired', + 102 => 'Card blocked temporarily or under susciption of fraud', + 104 => 'Transaction not permitted', + 107 => 'Contact the card issuer', + 109 => 'Invalid identification by merchant or POS terminal', + 110 => 'Invalid amount', + 114 => 'Card cannot be used to the requested transaction', + 116 => 'Insufficient credit', + 118 => 'Non-registered card', + 125 => 'Card not effective', + 129 => 'CVV2/CVC2 Error', + 167 => 'Contact the card issuer: suspected fraud', + 180 => 'Card out of service', + 181 => 'Card with credit or debit restrictions', + 182 => 'Card with credit or debit restrictions', + 184 => 'Authentication error', + 190 => 'Refusal with no specific reason', + 191 => 'Expiry date incorrect', + + 201 => 'Card expired', + 202 => 'Card blocked temporarily or under suspicion of fraud', + 204 => 'Transaction not permitted', + 207 => 'Contact the card issuer', + 208 => 'Lost or stolen card', + 209 => 'Lost or stolen card', + 280 => 'CVV2/CVC2 Error', + 290 => 'Declined with no specific reason', + + 480 => 'Original transaction not located, or time-out exceeded', + 501 => 'Original transaction not located, or time-out exceeded', + 502 => 'Original transaction not located, or time-out exceeded', + 503 => 'Original transaction not located, or time-out exceeded', + + 904 => 'Merchant not registered at FUC', + 909 => 'System error', + 912 => 'Issuer not available', + 913 => 'Duplicate transmission', + 916 => 'Amount too low', + 928 => 'Time-out exceeded', + 940 => 'Transaction cancelled previously', + 941 => 'Authorization operation already cancelled', + 942 => 'Original authorization declined', + 943 => 'Different details from origin transaction', + 944 => 'Session error', + 945 => 'Duplicate transmission', + 946 => 'Cancellation of transaction while in progress', + 947 => 'Duplicate tranmission while in progress', + 949 => 'POS Inoperative', + 950 => 'Refund not possible', + 9064 => 'Card number incorrect', + 9078 => 'No payment method available', + 9093 => 'Non-existent card', + 9218 => 'Recursive transaction in bad gateway', + 9253 => 'Check-digit incorrect', + 9256 => 'Preauth not allowed for merchant', + 9257 => 'Preauth not allowed for card', + 9261 => 'Operating limit exceeded', + 9912 => 'Issuer not available', + 9913 => 'Confirmation error', + 9914 => 'KO Confirmation' } # Creates a new instance @@ -178,32 +181,38 @@ class RedsysGateway < Gateway # * :secret_key -- The Redsys Secret Key. (REQUIRED) # * :terminal -- The Redsys Terminal. Defaults to 1. (OPTIONAL) # * :test -- +true+ or +false+. Defaults to +false+. (OPTIONAL) + # * :signature_algorithm -- +"sha256"+ Defaults to +"sha1"+. (OPTIONAL) def initialize(options = {}) requires!(options, :login, :secret_key) options[:terminal] ||= 1 + options[:signature_algorithm] ||= 'sha1' super end - def purchase(money, creditcard, options = {}) + def purchase(money, payment, options = {}) requires!(options, :order_id) data = {} add_action(data, :purchase) add_amount(data, money, options) add_order(data, options[:order_id]) - add_creditcard(data, creditcard) + add_payment(data, payment) + data[:description] = options[:description] + data[:store_in_vault] = options[:store] commit data end - def authorize(money, creditcard, options = {}) + def authorize(money, payment, options = {}) requires!(options, :order_id) data = {} add_action(data, :authorize) add_amount(data, money, options) add_order(data, options[:order_id]) - add_creditcard(data, creditcard) + add_payment(data, payment) + data[:description] = options[:description] + data[:store_in_vault] = options[:store] commit data end @@ -214,6 +223,7 @@ def capture(money, authorization, options = {}) add_amount(data, money, options) order_id, _, _ = split_authorization(authorization) add_order(data, order_id) + data[:description] = options[:description] commit data end @@ -224,6 +234,7 @@ def void(authorization, options = {}) order_id, amount, currency = split_authorization(authorization) add_amount(data, amount, :currency => currency) add_order(data, order_id) + data[:description] = options[:description] commit data end @@ -234,10 +245,37 @@ def refund(money, authorization, options = {}) add_amount(data, money, options) order_id, _, _ = split_authorization(authorization) add_order(data, order_id) + data[:description] = options[:description] commit data end + def verify(creditcard, options = {}) + MultiResponse.run(:use_first_response) do |r| + r.process { authorize(100, creditcard, options) } + r.process(:ignore_result) { void(r.authorization, options) } + end + end + + def supports_scrubbing + true + end + + def scrub(transcript) + transcript. + gsub(%r((Authorization: Basic )\w+), '\1[FILTERED]'). + gsub(%r((%3CDS_MERCHANT_PAN%3E)\d+(%3C%2FDS_MERCHANT_PAN%3E))i, '\1[FILTERED]\2'). + gsub(%r((%3CDS_MERCHANT_CVV2%3E)\d+(%3C%2FDS_MERCHANT_CVV2%3E))i, '\1[FILTERED]\2'). + gsub(%r(()\d+())i, '\1[FILTERED]\2'). + gsub(%r(()\d+())i, '\1[FILTERED]\2'). + gsub(%r((DS_MERCHANT_CVV2)%2F%3E%0A%3C%2F)i, '\1[BLANK]'). + gsub(%r((DS_MERCHANT_CVV2)%2F%3E%3C)i, '\1[BLANK]'). + gsub(%r((DS_MERCHANT_CVV2%3E)(%3C%2FDS_MERCHANT_CVV2))i, '\1[BLANK]\2'). + gsub(%r(()())i, '\1[BLANK]\2'). + gsub(%r((DS_MERCHANT_CVV2%3E)\++(%3C%2FDS_MERCHANT_CVV2))i, '\1[BLANK]\2'). + gsub(%r(()\s+())i, '\1[BLANK]\2') + end + private def add_action(data, action) @@ -250,39 +288,52 @@ def add_amount(data, money, options) end def add_order(data, order_id) - raise ArgumentError.new("Invalid order_id format") unless(/^\d{4}[\da-zA-Z]{0,8}$/ =~ order_id) - data[:order_id] = order_id + data[:order_id] = clean_order_id(order_id) end def url test? ? test_url : live_url end - def add_creditcard(data, card) - name = [card.first_name, card.last_name].join(' ').slice(0, 60) - year = sprintf("%.4i", card.year) - month = sprintf("%.2i", card.month) - data[:card] = { - :name => name, - :pan => card.number, - :date => "#{year[2..3]}#{month}", - :cvv => card.verification_value - } + def add_payment(data, card) + if card.is_a?(String) + data[:credit_card_token] = card + else + name = [card.first_name, card.last_name].join(' ').slice(0, 60) + year = sprintf('%.4i', card.year) + month = sprintf('%.2i', card.month) + data[:card] = { + :name => name, + :pan => card.number, + :date => "#{year[2..3]}#{month}", + :cvv => card.verification_value + } + end end def commit(data) - headers = { + parse(ssl_post(url, "entrada=#{CGI.escape(xml_request_from(data))}", headers)) + end + + def headers + { 'Content-Type' => 'application/x-www-form-urlencoded' } - xml = build_xml_request(data) - parse(ssl_post(url, "entrada=#{CGI.escape(xml)}", headers)) + end + + def xml_request_from(data) + if sha256_authentication? + build_sha256_xml_request(data) + else + build_sha1_xml_request(data) + end end def build_signature(data) str = data[:amount] + - data[:order_id].to_s + - @options[:login].to_s + - data[:currency] + data[:order_id].to_s + + @options[:login].to_s + + data[:currency] if card = data[:card] str << card[:pan] @@ -290,23 +341,51 @@ def build_signature(data) end str << data[:action] + if data[:store_in_vault] + str << 'REQUIRED' + elsif data[:credit_card_token] + str << data[:credit_card_token] + end str << @options[:secret_key] Digest::SHA1.hexdigest(str) end - def build_xml_request(data) + def build_sha256_xml_request(data) + xml = Builder::XmlMarkup.new + xml.instruct! + xml.REQUEST do + build_merchant_data(xml, data) + xml.DS_SIGNATUREVERSION 'HMAC_SHA256_V1' + xml.DS_SIGNATURE sign_request(merchant_data_xml(data), data[:order_id]) + end + xml.target! + end + + def build_sha1_xml_request(data) xml = Builder::XmlMarkup.new :indent => 2 + build_merchant_data(xml, data) + xml.target! + end + + def merchant_data_xml(data) + xml = Builder::XmlMarkup.new + build_merchant_data(xml, data) + xml.target! + end + + def build_merchant_data(xml, data) xml.DATOSENTRADA do # Basic elements xml.DS_Version 0.1 - xml.DS_MERCHANT_CURRENCY data[:currency] - xml.DS_MERCHANT_AMOUNT data[:amount] - xml.DS_MERCHANT_ORDER data[:order_id] - xml.DS_MERCHANT_TRANSACTIONTYPE data[:action] - xml.DS_MERCHANT_TERMINAL @options[:terminal] - xml.DS_MERCHANT_MERCHANTCODE @options[:login] - xml.DS_MERCHANT_MERCHANTSIGNATURE build_signature(data) + xml.DS_MERCHANT_CURRENCY data[:currency] + xml.DS_MERCHANT_AMOUNT data[:amount] + xml.DS_MERCHANT_ORDER data[:order_id] + xml.DS_MERCHANT_TRANSACTIONTYPE data[:action] + xml.DS_MERCHANT_PRODUCTDESCRIPTION data[:description] + xml.DS_MERCHANT_TERMINAL @options[:terminal] + xml.DS_MERCHANT_MERCHANTCODE @options[:login] + xml.DS_MERCHANT_MERCHANTSIGNATURE build_signature(data) unless sha256_authentication? # Only when card is present if data[:card] @@ -314,20 +393,23 @@ def build_xml_request(data) xml.DS_MERCHANT_PAN data[:card][:pan] xml.DS_MERCHANT_EXPIRYDATE data[:card][:date] xml.DS_MERCHANT_CVV2 data[:card][:cvv] + xml.DS_MERCHANT_IDENTIFIER 'REQUIRED' if data[:store_in_vault] + elsif data[:credit_card_token] + xml.DS_MERCHANT_IDENTIFIER data[:credit_card_token] + xml.DS_MERCHANT_DIRECTPAYMENT 'true' end end - xml.target! end def parse(data) params = {} success = false - message = "" + message = '' options = @options.merge(:test => test?) xml = Nokogiri::XML(data) - code = xml.xpath("//RETORNOXML/CODIGO").text - if code == "0" - op = xml.xpath("//RETORNOXML/OPERACION") + code = xml.xpath('//RETORNOXML/CODIGO').text + if code == '0' + op = xml.xpath('//RETORNOXML/OPERACION') op.children.each do |element| params[element.name.downcase.to_sym] = element.text end @@ -337,7 +419,7 @@ def parse(data) options[:authorization] = build_authorization(params) success = is_success_response?(params[:ds_response]) else - message = "Response failed validation check" + message = 'Response failed validation check' end else # Some kind of programmer error with the request! @@ -348,31 +430,37 @@ def parse(data) end def validate_signature(data) - str = data[:ds_amount] + - data[:ds_order].to_s + - data[:ds_merchantcode] + - data[:ds_currency] + - data[:ds_response] + - data[:ds_cardnumber].to_s + - data[:ds_transactiontype].to_s + - data[:ds_securepayment].to_s + - @options[:secret_key] - - sig = Digest::SHA1.hexdigest(str) - data[:ds_signature].to_s.downcase == sig + if sha256_authentication? + sig = Base64.strict_encode64(mac256(get_key(data[:ds_order].to_s), xml_signed_fields(data))) + sig.casecmp(data[:ds_signature].to_s).zero? + else + str = data[:ds_amount] + + data[:ds_order].to_s + + data[:ds_merchantcode] + + data[:ds_currency] + + data[:ds_response] + + data[:ds_cardnumber].to_s + + data[:ds_transactiontype].to_s + + data[:ds_securepayment].to_s + + @options[:secret_key] + + sig = Digest::SHA1.hexdigest(str) + data[:ds_signature].to_s.downcase == sig + end end def build_authorization(params) - [params[:ds_order], params[:ds_amount], params[:ds_currency]].join("|") + [params[:ds_order], params[:ds_amount], params[:ds_currency]].join('|') end def split_authorization(authorization) - order_id, amount, currency = authorization.split("|") + order_id, amount, currency = authorization.split('|') [order_id, amount.to_i, currency] end def currency_code(currency) return currency if currency =~ /^\d+$/ + raise ArgumentError, "Unknown currency #{currency}" unless CURRENCY_CODES[currency] CURRENCY_CODES[currency] end @@ -383,12 +471,64 @@ def transaction_code(type) def response_text(code) code = code.to_i code = 0 if code < 100 - RESPONSE_TEXTS[code] || "Unkown code, please check in manual" + RESPONSE_TEXTS[code] || 'Unkown code, please check in manual' end def is_success_response?(code) (code.to_i < 100) || [400, 481, 500, 900].include?(code.to_i) end + + def clean_order_id(order_id) + cleansed = order_id.gsub(/[^\da-zA-Z]/, '') + if cleansed =~ /^\d{4}/ + cleansed[0..11] + else + '%04d%s' % [rand(0..9999), cleansed[0...8]] + end + end + + def sha256_authentication? + @options[:signature_algorithm] == 'sha256' + end + + def sign_request(xml_request_string, order_id) + key = encrypt(@options[:secret_key], order_id) + Base64.strict_encode64(mac256(key, xml_request_string)) + end + + def encrypt(key, order_id) + block_length = 8 + cipher = OpenSSL::Cipher.new('DES3') + cipher.encrypt + + cipher.key = Base64.strict_decode64(key) + # The OpenSSL default of an all-zeroes ("\\0") IV is used. + cipher.padding = 0 + + order_id += "\0" until order_id.bytesize % block_length == 0 # Pad with zeros + + output = cipher.update(order_id) + cipher.final + output + end + + def mac256(key, data) + OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha256'), key, data) + end + + def xml_signed_fields(data) + xml_signed_fields = data[:ds_amount] + data[:ds_order] + data[:ds_merchantcode] + + data[:ds_currency] + data[:ds_response] + + if data[:ds_cardnumber] + xml_signed_fields += data[:ds_cardnumber] + end + + xml_signed_fields + data[:ds_transactiontype] + data[:ds_securepayment] + end + + def get_key(order_id) + encrypt(@options[:secret_key], order_id) + end end end end diff --git a/lib/active_merchant/billing/gateways/s5.rb b/lib/active_merchant/billing/gateways/s5.rb new file mode 100644 index 00000000000..9f36a91e54e --- /dev/null +++ b/lib/active_merchant/billing/gateways/s5.rb @@ -0,0 +1,246 @@ +require 'nokogiri' + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class S5Gateway < Gateway + self.test_url = 'https://test.ctpe.io/payment/ctpe' + self.live_url = 'https://ctpe.io/payment/ctpe' + + self.supported_countries = ['DK'] + self.default_currency = 'EUR' + self.supported_cardtypes = [:visa, :master, :maestro] + + self.homepage_url = 'http://www.s5.dk/' + self.display_name = 'S5' + + SUPPORTED_TRANSACTIONS = { + 'sale' => 'CC.DB', + 'authonly' => 'CC.PA', + 'capture' => 'CC.CP', + 'refund' => 'CC.RF', + 'void' => 'CC.RV', + 'store' => 'CC.RG' + } + + def initialize(options={}) + requires!(options, :sender, :channel, :login, :password) + super + end + + def purchase(money, payment, options={}) + request = build_xml_request do |xml| + add_identification(xml, options) + add_payment(xml, money, 'sale', options) + add_account(xml, payment) + add_customer(xml, payment, options) + add_recurrence_mode(xml, options) + end + + commit(request) + end + + def refund(money, authorization, options={}) + request = build_xml_request do |xml| + add_identification(xml, options, authorization) + add_payment(xml, money, 'refund', options) + end + + commit(request) + end + + def authorize(money, payment, options={}) + request = build_xml_request do |xml| + add_identification(xml, options) + add_payment(xml, money, 'authonly', options) + add_account(xml, payment) + add_customer(xml, payment, options) + add_recurrence_mode(xml, options) + end + + commit(request) + end + + def capture(money, authorization, options={}) + request = build_xml_request do |xml| + add_identification(xml, options, authorization) + add_payment(xml, money, 'capture', options) + end + + commit(request) + end + + def void(authorization, options={}) + request = build_xml_request do |xml| + add_identification(xml, options, authorization) + add_payment(xml, nil, 'void', options) + end + + commit(request) + end + + def store(payment, options = {}) + request = build_xml_request do |xml| + xml.Payment(code: SUPPORTED_TRANSACTIONS['store']) + add_account(xml, payment) + add_customer(xml, payment, options) + add_recurrence_mode(xml, options) + end + + commit(request) + end + + def verify(credit_card, options={}) + MultiResponse.run(:use_first_response) do |r| + r.process { authorize(100, credit_card, options) } + r.process(:ignore_result) { void(r.authorization, options) } + end + end + + def supports_scrubbing? + true + end + + def scrub(transcript) + transcript. + gsub(%r((pwd=).+?(/>))i, '\1[FILTERED]\2'). + gsub(%r(().+?())i, '\1[FILTERED]\2'). + gsub(%r(().+?())i, '\1[FILTERED]\2') + end + + private + + def add_identification(xml, options, authorization = nil) + xml.Identification do + xml.TransactionID options[:order_id] if options[:order_id] + xml.ReferenceID authorization if authorization + end + end + + def add_payment(xml, money, action, options) + xml.Payment(code: SUPPORTED_TRANSACTIONS[action]) do + xml.Memo "return_code=#{options[:memo]}" if options[:memo] + xml.Presentation do + xml.Amount amount(money) + xml.Currency options[:currency] || currency(money) + xml.Usage options[:description] + end + end + end + + def add_account(xml, payment_method) + if !payment_method.respond_to?(:number) + xml.Account(registration: payment_method) + else + xml.Account do + xml.Number payment_method.number + xml.Holder "#{payment_method.first_name} #{payment_method.last_name}" + xml.Brand payment_method.brand + xml.Expiry(year: payment_method.year, month: payment_method.month) + xml.Verification payment_method.verification_value + end + end + end + + def add_customer(xml, creditcard, options) + return unless creditcard.respond_to?(:number) + address = options[:billing_address] + xml.Customer do + xml.Contact do + xml.Email options[:email] + xml.Ip options[:ip] + xml.Phone address[:phone] if address + end + add_address(xml, address) + xml.Name do + xml.Given creditcard.first_name + xml.Family creditcard.last_name + xml.Company options[:company] + end + end + end + + def add_address(xml, address) + return unless address + + xml.Address do + xml.Street "#{address[:address1]} #{address[:address2]}" + xml.Zip address[:zip] + xml.City address[:city] + xml.State address[:state] + xml.Country address[:country] + end + end + + def add_recurrence_mode(xml, options) + if options[:recurring] == true + xml.Recurrence(mode: 'REPEATED') + else + xml.Recurrence(mode: 'INITIAL') + end + end + + def parse(body) + results = {} + xml = Nokogiri::XML(body) + resp = xml.xpath('//Response/Transaction/Identification') + resp.children.each do |element| + results[element.name.downcase.to_sym] = element.text + end + resp = xml.xpath('//Response/Transaction/Processing') + resp.children.each do |element| + results[element.name.downcase.to_sym] = element.text + end + results + end + + def commit(xml) + url = (test? ? test_url : live_url) + headers = { + 'Content-Type' => 'application/x-www-form-urlencoded;charset=UTF-8' + } + + response = parse(ssl_post(url, post_data(xml), headers)) + + Response.new( + success_from(response), + message_from(response), + response, + authorization: authorization_from(response), + test: test? + ) + end + + def success_from(response) + response[:result] == 'ACK' + end + + def message_from(response) + response[:return] + end + + def authorization_from(response) + response[:uniqueid] + end + + def post_data(xml) + "load=#{xml}" + end + + def build_xml_request + builder = Nokogiri::XML::Builder.new(encoding: 'UTF-8') do |xml| + xml.Request(version: '1.0') do + xml.Header do + xml.Security(sender: @options[:sender]) + end + xml.Transaction(mode: @options[:mode] || 'LIVE', channel: @options[:channel]) do + xml.User(login: @options[:login], pwd: @options[:password]) + yield(xml) + end + end + end + + builder.to_xml + end + end + end +end diff --git a/lib/active_merchant/billing/gateways/safe_charge.rb b/lib/active_merchant/billing/gateways/safe_charge.rb new file mode 100644 index 00000000000..8d3cccd3cc6 --- /dev/null +++ b/lib/active_merchant/billing/gateways/safe_charge.rb @@ -0,0 +1,262 @@ +require 'nokogiri' + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class SafeChargeGateway < Gateway + self.test_url = 'https://process.sandbox.safecharge.com/service.asmx/Process' + self.live_url = 'https://process.safecharge.com/service.asmx/Process' + + self.supported_countries = ['AT', 'BE', 'BG', 'CY', 'CZ', 'DE', 'DK', 'EE', 'GR', 'ES', 'FI', 'FR', 'HR', 'HU', 'IE', 'IS', 'IT', 'LI', 'LT', 'LU', 'LV', 'MT', 'NL', 'NO', 'PL', 'PT', 'RO', 'SE', 'SI', 'SK', 'GB', 'US'] + self.default_currency = 'USD' + self.supported_cardtypes = [:visa, :master] + + self.homepage_url = 'https://www.safecharge.com' + self.display_name = 'SafeCharge' + + VERSION = '4.1.0' + + def initialize(options={}) + requires!(options, :client_login_id, :client_password) + super + end + + def purchase(money, payment, options={}) + post = {} + post[:sg_APIType] = 1 if options[:three_d_secure] + trans_type = options[:three_d_secure] ? 'Sale3D' : 'Sale' + add_transaction_data(trans_type, post, money, options) + add_payment(post, payment, options) + add_customer_details(post, payment, options) + + commit(post) + end + + def authorize(money, payment, options={}) + post = {} + add_transaction_data('Auth', post, money, options) + add_payment(post, payment, options) + add_customer_details(post, payment, options) + + commit(post) + end + + def capture(money, authorization, options={}) + post = {} + auth, transaction_id, token, exp_month, exp_year, _, original_currency = authorization.split('|') + add_transaction_data('Settle', post, money, options.merge!({currency: original_currency})) + post[:sg_AuthCode] = auth + post[:sg_TransactionID] = transaction_id + post[:sg_CCToken] = token + post[:sg_ExpMonth] = exp_month + post[:sg_ExpYear] = exp_year + + commit(post) + end + + def refund(money, authorization, options={}) + post = {} + auth, transaction_id, token, exp_month, exp_year, _, original_currency = authorization.split('|') + add_transaction_data('Credit', post, money, options.merge!({currency: original_currency})) + post[:sg_CreditType] = 2 + post[:sg_AuthCode] = auth + post[:sg_TransactionID] = transaction_id + post[:sg_CCToken] = token + post[:sg_ExpMonth] = exp_month + post[:sg_ExpYear] = exp_year + + commit(post) + end + + def credit(money, payment, options={}) + post = {} + add_payment(post, payment, options) + add_transaction_data('Credit', post, money, options) + post[:sg_CreditType] = 1 + + commit(post) + end + + def void(authorization, options={}) + post = {} + auth, transaction_id, token, exp_month, exp_year, original_amount, original_currency = authorization.split('|') + add_transaction_data('Void', post, (original_amount.to_f * 100), options.merge!({currency: original_currency})) + post[:sg_CreditType] = 2 + post[:sg_AuthCode] = auth + post[:sg_TransactionID] = transaction_id + post[:sg_CCToken] = token + post[:sg_ExpMonth] = exp_month + post[:sg_ExpYear] = exp_year + + commit(post) + end + + def verify(credit_card, options={}) + MultiResponse.run(:use_first_response) do |r| + r.process { authorize(100, credit_card, options) } + r.process(:ignore_result) { void(r.authorization, options) } + end + end + + def supports_scrubbing? + true + end + + def scrub(transcript) + transcript. + gsub(%r((sg_ClientPassword=)[^&]+(&?)), '\1[FILTERED]\2'). + gsub(%r((sg_CardNumber=)[^&]+(&?)), '\1[FILTERED]\2'). + gsub(%r((sg_CVV2=)\d+), '\1[FILTERED]') + end + + private + + def add_transaction_data(trans_type, post, money, options) + post[:sg_TransType] = trans_type + post[:sg_Currency] = (options[:currency] || currency(money)) + post[:sg_Amount] = amount(money) + post[:sg_ClientLoginID] = @options[:client_login_id] + post[:sg_ClientPassword] = @options[:client_password] + post[:sg_ResponseFormat] = '4' + post[:sg_Version] = VERSION + post[:sg_ClientUniqueID] = options[:order_id] if options[:order_id] + post[:sg_UserID] = options[:user_id] if options[:user_id] + post[:sg_AuthType] = options[:auth_type] if options[:auth_type] + post[:sg_ExpectedFulfillmentCount] = options[:expected_fulfillment_count] if options[:expected_fulfillment_count] + post[:sg_WebsiteID] = options[:website_id] if options[:website_id] + post[:sg_IPAddress] = options[:ip] if options[:ip] + post[:sg_VendorID] = options[:vendor_id] if options[:vendor_id] + post[:sg_Descriptor] = options[:merchant_descriptor] if options[:merchant_descriptor] + post[:sg_MerchantPhoneNumber] = options[:merchant_phone_number] if options[:merchant_phone_number] + post[:sg_MerchantName] = options[:merchant_name] if options[:merchant_name] + end + + def add_payment(post, payment, options={}) + post[:sg_NameOnCard] = payment.name + post[:sg_CardNumber] = payment.number + post[:sg_ExpMonth] = format(payment.month, :two_digits) + post[:sg_ExpYear] = format(payment.year, :two_digits) + post[:sg_CVV2] = payment.verification_value + post[:sg_StoredCredentialMode] = (options[:stored_credential_mode] == true ? 1 : 0) + end + + def add_customer_details(post, payment, options) + if address = options[:billing_address] || options[:address] + post[:sg_FirstName] = payment.first_name + post[:sg_LastName] = payment.last_name + post[:sg_Address] = address[:address1] if address[:address1] + post[:sg_City] = address[:city] if address[:city] + post[:sg_State] = address[:state] if address[:state] + post[:sg_Zip] = address[:zip] if address[:zip] + post[:sg_Country] = address[:country] if address[:country] + post[:sg_Phone] = address[:phone] if address[:phone] + end + + post[:sg_Email] = options[:email] + end + + def parse(xml) + response = {} + + doc = Nokogiri::XML(xml) + doc.root.xpath('*').each do |node| + if node.elements.size == 0 + response[node.name.underscore.downcase.to_sym] = node.text + else + node.traverse do |childnode| + childnode_to_response(response, childnode) + end + end + end + response + end + + def childnode_to_response(response, childnode) + if childnode.elements.size == 0 + element_name_to_symbol(response, childnode) + else + childnode.traverse do |node| + element_name_to_symbol(response, node) + end + end + end + + def element_name_to_symbol(response, childnode) + name = childnode.name.downcase + response[name.to_sym] = childnode.text + end + + def commit(parameters) + url = (test? ? test_url : live_url) + response = parse(ssl_post(url, post_data(parameters))) + + Response.new( + success_from(response), + message_from(response), + response, + authorization: authorization_from(response, parameters), + avs_result: AVSResult.new(code: response[:avs_code]), + cvv_result: CVVResult.new(response[:cvv2_reply]), + test: test?, + error_code: error_code_from(response) + ) + end + + def success_from(response) + response[:status] == 'APPROVED' + end + + def message_from(response) + return 'Success' if success_from(response) + response[:reason_codes] || response[:reason] + end + + def authorization_from(response, parameters) + [ + response[:auth_code], + response[:transaction_id], + response[:token], + parameters[:sg_ExpMonth], + parameters[:sg_ExpYear], + parameters[:sg_Amount], + parameters[:sg_Currency] + ].join('|') + end + + def split_authorization(authorization) + auth_code, transaction_id, token, month, year, original_amount = authorization.split('|') + + { + auth_code: auth_code, + transaction_id: transaction_id, + token: token, + exp_month: month, + exp_year: year, + original_amount: amount(original_amount.to_f * 100) + } + end + + def post_data(params) + return nil unless params + + params.map do |key, value| + next if value != false && value.blank? + "#{key}=#{CGI.escape(value.to_s)}" + end.compact.join('&') + end + + def error_code_from(response) + unless success_from(response) + response[:ex_err_code] || response[:err_code] + end + end + + def underscore(camel_cased_word) + camel_cased_word.to_s.gsub(/::/, '/'). + gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2'). + gsub(/([a-z\d])([A-Z])/, '\1_\2'). + tr('-', '_'). + downcase + end + end + end +end diff --git a/lib/active_merchant/billing/gateways/sage.rb b/lib/active_merchant/billing/gateways/sage.rb index 5fbae0df9e3..3f3c9a96bcf 100644 --- a/lib/active_merchant/billing/gateways/sage.rb +++ b/lib/active_merchant/billing/gateways/sage.rb @@ -1,152 +1,448 @@ -require File.dirname(__FILE__) + '/sage/sage_bankcard' -require File.dirname(__FILE__) + '/sage/sage_virtual_check' - module ActiveMerchant #:nodoc: module Billing #:nodoc: class SageGateway < Gateway - self.supported_countries = SageBankcardGateway.supported_countries - self.supported_cardtypes = SageBankcardGateway.supported_cardtypes - - self.abstract_class = true - - # Creates a new SageGateway - # - # The gateway requires that a valid login and password be passed - # in the +options+ hash. - # - # ==== Options - # - # * :login - The Sage Payment Solutions Merchant ID Number. - # * :password - The Sage Payment Solutions Merchant Key Number. + include Empty + + self.display_name = 'http://www.sagepayments.com' + self.homepage_url = 'Sage Payment Solutions' + self.live_url = 'https://www.sagepayments.net/cgi-bin' + + self.supported_countries = ['US', 'CA'] + self.supported_cardtypes = [:visa, :master, :american_express, :discover, :jcb, :diners_club] + + TRANSACTIONS = { + :purchase => '01', + :authorization => '02', + :capture => '11', + :void => '04', + :credit => '06', + :refund => '10' + } + + SOURCE_CARD = 'bankcard' + SOURCE_ECHECK = 'virtual_check' + def initialize(options = {}) requires!(options, :login, :password) super end - # Performs an authorization transaction - # - # ==== Parameters - # * money - The amount to be authorized as an integer value in cents. - # * credit_card - The CreditCard object to be used as the funding source for the transaction. - # * options - A hash of optional parameters. - # * :order_id - A unique reference for this order. (maximum of 20 characters). - # * :email - The customer's email address - # * :customer - The Customer Number for Purchase Card Level II Transactions - # * :billing_address - The customer's billing address as a hash of address information. - # * :address1 - The billing address street - # * :city - The billing address city - # * :state - The billing address state - # * :country - The 2 digit ISO billing address country code - # * :zip - The billing address zip code - # * :phone - The billing address phone number - # * :fax - The billing address fax number - # * :shipping_address - The customer's shipping address as a hash of address information. - # * :name - The name at the shipping address - # * :address1 - The shipping address street - # * :city - The shipping address city - # * :state - The shipping address state code - # * :country - The 2 digit ISO shipping address country code - # * :zip - The shipping address zip code - # * :tax - The tax amount for the transaction as an Integer value in cents. Maps to Sage T_tax. - # * :shipping - The shipping amount for the transaction as an Integer value in cents. Maps to Sage T_shipping. def authorize(money, credit_card, options = {}) - bankcard.authorize(money, credit_card, options) - end - - # Performs a purchase, which is essentially an authorization and capture in a single operation. - # - # ==== Parameters - # - # * money - The amount to be authorized as an integer value in cents. - # * source - The CreditCard or Check object to be used as the funding source for the transaction. - # * options - A hash of optional parameters. - # * :order_id - A unique reference for this order. (maximum of 20 characters). - # * :email - The customer's email address - # * :customer - The Customer Number for Purchase Card Level II Transactions - # * :billing_address - The customer's billing address as a hash of address information. - # * :address1 - The billing address street - # * :city - The billing address city - # * :state - The billing address state - # * :country - The 2 digit ISO billing address country code - # * :zip - The billing address zip code - # * :phone - The billing address phone number - # * :fax - The billing address fax number - # * :shipping_address - The customer's shipping address as a hash of address information. - # * :name - The name at the shipping address - # * :address1 - The shipping address street - # * :city - The shipping address city - # * :state - The shipping address state code - # * :country - The 2 digit ISO shipping address country code - # * :zip - The shipping address zip code - # * :tax - The tax amount for the transaction as an integer value in cents. Maps to Sage T_tax. - # * :shipping - The shipping amount for the transaction as an integer value in cents. Maps to Sage T_shipping. - # - # ==== Additional options in the +options+ hash for when using a Check as the funding source - # * :originator_id - 10 digit originator. If not provided, Sage will use the default Originator ID for the specific customer type. - # * :addenda - Transaction addenda. - # * :ssn - The customer's Social Security Number. - # * :drivers_license_state - The customer's drivers license state code. - # * :drivers_license_number - The customer's drivers license number. - # * :date_of_birth - The customer's date of birth as a Time or Date object or a string in the format mm/dd/yyyy. - def purchase(money, source, options = {}) - if card_brand(source) == "check" - virtual_check.purchase(money, source, options) + post = {} + add_credit_card(post, credit_card) + add_transaction_data(post, money, options) + commit(:authorization, post, SOURCE_CARD) + end + + def purchase(money, payment_method, options = {}) + post = {} + if card_brand(payment_method) == 'check' + source = SOURCE_ECHECK + add_check(post, payment_method) + add_check_customer_data(post, options) else - bankcard.purchase(money, source, options) + source = SOURCE_CARD + add_credit_card(post, payment_method) end + add_transaction_data(post, money, options) + commit(:purchase, post, source) end - # Captures authorized funds. - # - # ==== Parameters - # - # * money - The amount to be authorized as an integer value in cents. Sage doesn't support changing the capture amount, so the full amount of the initial transaction will be captured. - # * reference - The authorization reference string returned by the original transaction's Response#authorization. + # The +money+ amount is not used. The entire amount of the + # initial authorization will be captured. def capture(money, reference, options = {}) - bankcard.capture(money, reference, options) + post = {} + add_reference(post, reference) + commit(:capture, post, SOURCE_CARD) end - # Voids a prior transaction. Works for both CreditCard and Check transactions. - # - # ==== Parameters - # - # * reference - The authorization reference string returned by the original transaction's Response#authorization. def void(reference, options = {}) - if reference.split(";").last == "virtual_check" - virtual_check.void(reference, options) + post = {} + add_reference(post, reference) + source = reference.split(';').last + commit(:void, post, source) + end + + def credit(money, payment_method, options = {}) + post = {} + if card_brand(payment_method) == 'check' + source = SOURCE_ECHECK + add_check(post, payment_method) + add_check_customer_data(post, options) else - bankcard.void(reference, options) + source = SOURCE_CARD + add_credit_card(post, payment_method) + end + add_transaction_data(post, money, options) + commit(:credit, post, source) + end + + def refund(money, reference, options={}) + post = {} + add_reference(post, reference) + add_transaction_data(post, money, options) + commit(:refund, post, SOURCE_CARD) + end + + def store(credit_card, options = {}) + vault.store(credit_card, options) + end + + def unstore(identification, options = {}) + vault.unstore(identification, options) + end + + def supports_scrubbing? + true + end + + def scrub(transcript) + force_utf8(transcript). + gsub(%r((M_id=)[^&]*), '\1[FILTERED]'). + gsub(%r((M_key=)[^&]*), '\1[FILTERED]'). + gsub(%r((C_cardnumber=)[^&]*), '\1[FILTERED]'). + gsub(%r((C_cvv=)[^&]*), '\1[FILTERED]'). + gsub(%r((C_rte=)[^&]*), '\1[FILTERED]'). + gsub(%r((C_acct=)[^&]*), '\1[FILTERED]'). + gsub(%r((C_ssn=)[^&]*), '\1[FILTERED]'). + gsub(%r(().+()), '\1[FILTERED]\2'). + gsub(%r(().+()), '\1[FILTERED]\2'). + gsub(%r(().+()), '\1[FILTERED]\2') + end + + private + + # use the same method as in pay_conex + def force_utf8(string) + return nil unless string + binary = string.encode('BINARY', invalid: :replace, undef: :replace, replace: '?') # Needed for Ruby 2.0 since #encode is a no-op if the string is already UTF-8. It's not needed for Ruby 2.1 and up since it's not a no-op there. + binary.encode('UTF-8', invalid: :replace, undef: :replace, replace: '?') + end + + def add_credit_card(post, credit_card) + post[:C_name] = credit_card.name + post[:C_cardnumber] = credit_card.number + post[:C_exp] = expdate(credit_card) + post[:C_cvv] = credit_card.verification_value if credit_card.verification_value? + end + + def add_check(post, check) + post[:C_first_name] = check.first_name + post[:C_last_name] = check.last_name + post[:C_rte] = check.routing_number + post[:C_acct] = check.account_number + post[:C_check_number] = check.number + post[:C_acct_type] = account_type(check) + end + + def add_check_customer_data(post, options) + # Required  Customer Type – (NACHA Transaction Class) + # CCD for Commercial, Merchant Initiated + # PPD for Personal, Merchant Initiated + # WEB for Internet, Consumer Initiated + # RCK for Returned Checks + # ARC for Account Receivable Entry + # TEL for TelephoneInitiated + post[:C_customer_type] = 'WEB' + + # Optional  10  Digit Originator  ID – Assigned  By for  each transaction  class  or  business  purpose. If  not provided, the default Originator ID for the specific  Customer Type will be applied.  + post[:C_originator_id] = options[:originator_id] + + # Optional  Transaction Addenda + post[:T_addenda] = options[:addenda] + + # Required  Check  Writer  Social  Security  Number  (  Numbers Only, No Dashes )  + post[:C_ssn] = options[:ssn].to_s.gsub(/[^\d]/, '') + + post[:C_dl_state_code] = options[:drivers_license_state] + post[:C_dl_number] = options[:drivers_license_number] + post[:C_dob] = format_birth_date(options[:date_of_birth]) + end + + def format_birth_date(date) + date.respond_to?(:strftime) ? date.strftime('%m/%d/%Y') : date + end + + # DDA for Checking + # SAV for Savings  + def account_type(check) + case check.account_type + when 'checking' then 'DDA' + when 'savings' then 'SAV' + else raise ArgumentError, "Unknown account type #{check.account_type}" + end + end + + def parse(data, source) + source == SOURCE_ECHECK ? parse_check(data) : parse_credit_card(data) + end + + def parse_check(data) + response = {} + response[:success] = data[1, 1] + response[:code] = data[2, 6].strip + response[:message] = data[8, 32].strip + response[:risk] = data[40, 2] + response[:reference] = data[42, 10] + + extra_data = data[53...-1].split("\034") + response[:order_number] = extra_data[0] + response[:authentication_indicator] = extra_data[1] + response[:authentication_disclosure] = extra_data[2] + response + end + + def parse_credit_card(data) + response = {} + response[:success] = data[1, 1] + response[:code] = data[2, 6] + response[:message] = data[8, 32].strip + response[:front_end] = data[40, 2] + response[:cvv_result] = data[42, 1] + response[:avs_result] = data[43, 1].strip + response[:risk] = data[44, 2] + response[:reference] = data[46, 10] + + response[:order_number], response[:recurring] = data[57...-1].split("\034") + response + end + + def add_invoice(post, options) + post[:T_ordernum] = (options[:order_id] || generate_unique_id).slice(0, 20) + post[:T_tax] = amount(options[:tax]) unless empty?(options[:tax]) + post[:T_shipping] = amount(options[:shipping]) unless empty?(options[:shipping]) + end + + def add_reference(post, reference) + ref, _ = reference.to_s.split(';') + post[:T_reference] = ref + end + + def add_amount(post, money) + post[:T_amt] = amount(money) + end + + def add_customer_data(post, options) + post[:T_customer_number] = options[:customer] if Float(options[:customer]) rescue nil + end + + def add_addresses(post, options) + billing_address = options[:billing_address] || options[:address] || {} + + post[:C_address] = billing_address[:address1] + post[:C_city] = billing_address[:city] + post[:C_state] = empty?(billing_address[:state]) ? 'Outside of US' : billing_address[:state] + post[:C_zip] = billing_address[:zip] + post[:C_country] = billing_address[:country] + post[:C_telephone] = billing_address[:phone] + post[:C_fax] = billing_address[:fax] + post[:C_email] = options[:email] + + if shipping_address = options[:shipping_address] + post[:C_ship_name] = shipping_address[:name] + post[:C_ship_address] = shipping_address[:address1] + post[:C_ship_city] = shipping_address[:city] + post[:C_ship_state] = shipping_address[:state] + post[:C_ship_zip] = shipping_address[:zip] + post[:C_ship_country] = shipping_address[:country] end end - def credit(money, source, options = {}) - deprecated CREDIT_DEPRECATION_MESSAGE - refund(money, source, options) + def add_transaction_data(post, money, options) + add_amount(post, money) + add_invoice(post, options) + add_addresses(post, options) + add_customer_data(post, options) + end + + def commit(action, params, source) + url = url(params, source) + response = parse(ssl_post(url, post_data(action, params)), source) + + Response.new(success?(response), response[:message], response, + :test => test?, + :authorization => authorization_from(response, source), + :avs_result => { :code => response[:avs_result] }, + :cvv_result => response[:cvv_result] + ) end - # Performs a refund transaction. - # - # ==== Parameters - # - # * money - The amount to be authorized as an integer value in cents. - # * source - The CreditCard or Check object to be used as the target for the refund. - def refund(money, source, options = {}) - if card_brand(source) == "check" - virtual_check.refund(money, source, options) + def url(params, source) + if source == SOURCE_ECHECK + "#{live_url}/eftVirtualCheck.dll?transaction" else - bankcard.refund(money, source, options) + "#{live_url}/eftBankcard.dll?transaction" end end - private - def bankcard - @bankcard ||= SageBankcardGateway.new(@options) + def authorization_from(response, source) + "#{response[:reference]};#{source}" + end + + def success?(response) + response[:success] == 'A' + end + + def post_data(action, params = {}) + params[:M_id] = @options[:login] + params[:M_key] = @options[:password] + params[:T_code] = TRANSACTIONS[action] + + params.collect { |key, value| "#{key}=#{CGI.escape(value.to_s)}" }.join('&') end - def virtual_check - @virtual_check ||= SageVirtualCheckGateway.new(@options) + def vault + @vault ||= SageVault.new(@options, self) + end + + class SageVault + + def initialize(options, gateway) + @live_url = 'https://www.sagepayments.net/web_services/wsVault/wsVault.asmx' + @options = options + @gateway = gateway + end + + def store(credit_card, options = {}) + request = build_store_request(credit_card, options) + commit(:store, request) + end + + def unstore(identification, options = {}) + request = build_unstore_request(identification, options) + commit(:unstore, request) + end + + private + + # A valid request example, since the Sage docs have none: + # + # + # + # + # + # 279277516172 + # O3I8G2H8V6A3 + # 4111111111111111 + # 0915 + # + # + # + def build_store_request(credit_card, options) + xml = Builder::XmlMarkup.new + add_credit_card(xml, credit_card, options) + xml.target! + end + + def build_unstore_request(identification, options) + xml = Builder::XmlMarkup.new + add_identification(xml, identification, options) + xml.target! + end + + def add_customer_data(xml) + xml.tag! 'ns1:M_ID', @options[:login] + xml.tag! 'ns1:M_KEY', @options[:password] + end + + def add_credit_card(xml, credit_card, options) + xml.tag! 'ns1:CARDNUMBER', credit_card.number + xml.tag! 'ns1:EXPIRATION_DATE', exp_date(credit_card) + end + + def add_identification(xml, identification, options) + xml.tag! 'ns1:GUID', identification + end + + def exp_date(credit_card) + year = sprintf('%.4i', credit_card.year) + month = sprintf('%.2i', credit_card.month) + + "#{month}#{year[-2..-1]}" + end + + def commit(action, request) + response = parse( + @gateway.ssl_post( + @live_url, + build_soap_request(action, request), + build_headers(action) + ) + ) + + case action + when :store + success = response[:success] == 'true' + message = response[:message].downcase.capitalize if response[:message] + when :unstore + success = response[:delete_data_result] == 'true' + message = success ? 'Succeeded' : 'Failed' + end + + Response.new(success, message, response, + authorization: response[:guid] + ) + end + + ENVELOPE_NAMESPACES = { + 'xmlns:SOAP-ENV' => 'http://schemas.xmlsoap.org/soap/envelope/', + 'xmlns:ns1' => 'https://www.sagepayments.net/web_services/wsVault/wsVault' + } + + ACTION_ELEMENTS = { + store: 'INSERT_CREDIT_CARD_DATA', + unstore: 'DELETE_DATA' + } + + def build_soap_request(action, body) + xml = Builder::XmlMarkup.new + + xml.instruct! + xml.tag! 'SOAP-ENV:Envelope', ENVELOPE_NAMESPACES do + xml.tag! 'SOAP-ENV:Body' do + xml.tag! "ns1:#{ACTION_ELEMENTS[action]}" do + add_customer_data(xml) + xml << body + end + end + end + xml.target! + end + + SOAP_ACTIONS = { + store: 'https://www.sagepayments.net/web_services/wsVault/wsVault/INSERT_CREDIT_CARD_DATA', + unstore: 'https://www.sagepayments.net/web_services/wsVault/wsVault/DELETE_DATA' + } + + def build_headers(action) + { + 'SOAPAction' => SOAP_ACTIONS[action], + 'Content-Type' => 'text/xml; charset=utf-8' + } + end + + def parse(body) + response = {} + hashify_xml!(body, response) + response + end + + def hashify_xml!(xml, response) + xml = REXML::Document.new(xml) + + # Store + xml.elements.each('//Table1/*') do |node| + response[node.name.underscore.to_sym] = node.text + end + + # Unstore + xml.elements.each('//DELETE_DATAResponse/*') do |node| + response[node.name.underscore.to_sym] = node.text + end + end end end end end - diff --git a/lib/active_merchant/billing/gateways/sage/sage_bankcard.rb b/lib/active_merchant/billing/gateways/sage/sage_bankcard.rb deleted file mode 100644 index 893db3ee4bb..00000000000 --- a/lib/active_merchant/billing/gateways/sage/sage_bankcard.rb +++ /dev/null @@ -1,93 +0,0 @@ -require File.dirname(__FILE__) + '/sage_core' - -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - class SageBankcardGateway < Gateway #:nodoc: - include SageCore - self.live_url = 'https://www.sagepayments.net/cgi-bin/eftBankcard.dll?transaction' - self.source = 'bankcard' - - # Credit cards supported by Sage - # * VISA - # * MasterCard - # * AMEX - # * Diners - # * Carte Blanche - # * Discover - # * JCB - # * Sears - self.supported_cardtypes = [:visa, :master, :american_express, :discover, :jcb, :diners_club] - - def authorize(money, credit_card, options = {}) - post = {} - add_credit_card(post, credit_card) - add_transaction_data(post, money, options) - commit(:authorization, post) - end - - def purchase(money, credit_card, options = {}) - post = {} - add_credit_card(post, credit_card) - add_transaction_data(post, money, options) - commit(:purchase, post) - end - - # The +money+ amount is not used. The entire amount of the - # initial authorization will be captured. - def capture(money, reference, options = {}) - post = {} - add_reference(post, reference) - commit(:capture, post) - end - - def void(reference, options = {}) - post = {} - add_reference(post, reference) - commit(:void, post) - end - - def credit(money, credit_card, options = {}) - deprecated CREDIT_DEPRECATION_MESSAGE - refund(money, credit_card, options) - end - - def refund(money, credit_card, options = {}) - post = {} - add_credit_card(post, credit_card) - add_transaction_data(post, money, options) - commit(:credit, post) - end - - private - def exp_date(credit_card) - year = sprintf("%.4i", credit_card.year) - month = sprintf("%.2i", credit_card.month) - - "#{month}#{year[-2..-1]}" - end - - def add_credit_card(post, credit_card) - post[:C_name] = credit_card.name - post[:C_cardnumber] = credit_card.number - post[:C_exp] = exp_date(credit_card) - post[:C_cvv] = credit_card.verification_value if credit_card.verification_value? - end - - def parse(data) - response = {} - response[:success] = data[1,1] - response[:code] = data[2,6] - response[:message] = data[8,32].strip - response[:front_end] = data[40, 2] - response[:cvv_result] = data[42, 1] - response[:avs_result] = data[43, 1].strip - response[:risk] = data[44, 2] - response[:reference] = data[46, 10] - - response[:order_number], response[:recurring] = data[57...-1].split("\034") - response - end - end - end -end - diff --git a/lib/active_merchant/billing/gateways/sage/sage_core.rb b/lib/active_merchant/billing/gateways/sage/sage_core.rb deleted file mode 100644 index 41580d8cc5e..00000000000 --- a/lib/active_merchant/billing/gateways/sage/sage_core.rb +++ /dev/null @@ -1,114 +0,0 @@ -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module SageCore #:nodoc: - def self.included(base) - base.cattr_accessor :source - base.supported_countries = ['US', 'CA'] - base.homepage_url = 'http://www.sagepayments.com' - base.display_name = 'Sage Payment Solutions' - end - - # Transactions types: - # 01 - Sale - # 02 - AuthOnly - # 03 - Force/PriorAuthSale - # 04 - Void - # 06 - Credit - # 11 - PriorAuthSale by Reference* - TRANSACTIONS = { - :purchase => '01', - :authorization => '02', - :capture => '11', - :void => '04', - :credit => '06' - } - - def initialize(options = {}) - requires!(options, :login, :password) - super - end - - private - def add_invoice(post, options) - post[:T_ordernum] = options[:order_id].slice(0, 20) - post[:T_tax] = amount(options[:tax]) unless options[:tax].blank? - post[:T_shipping] = amount(options[:shipping]) unless options[:shipping].blank? - end - - def add_reference(post, reference) - ref, source = reference.to_s.split(";") - post[:T_reference] = ref - end - - def add_amount(post, money) - post[:T_amt] = amount(money) - end - - def add_customer_data(post, options) - post[:T_customer_number] = options[:customer] if Float(options[:customer]) rescue nil - end - - def add_addresses(post, options) - billing_address = options[:billing_address] || options[:address] || {} - - post[:C_address] = billing_address[:address1] - post[:C_city] = billing_address[:city] - - if ['US', 'CA'].include?(billing_address[:country]) - post[:C_state] = billing_address[:state] - else - post[:C_state] = "Outside of United States" - end - - post[:C_zip] = billing_address[:zip] - post[:C_country] = billing_address[:country] - post[:C_telephone] = billing_address[:phone] - post[:C_fax] = billing_address[:fax] - post[:C_email] = options[:email] - - if shipping_address = options[:shipping_address] - post[:C_ship_name] = shipping_address[:name] - post[:C_ship_address] = shipping_address[:address1] - post[:C_ship_city] = shipping_address[:city] - post[:C_ship_state] = shipping_address[:state] - post[:C_ship_zip] = shipping_address[:zip] - post[:C_ship_country] = shipping_address[:country] - end - end - - def add_transaction_data(post, money, options) - add_amount(post, money) - add_invoice(post, options) - add_addresses(post, options) - add_customer_data(post, options) - end - - def commit(action, params) - response = parse(ssl_post(self.live_url, post_data(action, params))) - - Response.new(success?(response), response[:message], response, - :test => test?, - :authorization => authorization_from(response), - :avs_result => { :code => response[:avs_result] }, - :cvv_result => response[:cvv_result] - ) - end - - def authorization_from(response) - "#{response[:reference]};#{source}" - end - - def success?(response) - response[:success] == 'A' - end - - def post_data(action, params = {}) - params[:M_id] = @options[:login] - params[:M_key] = @options[:password] - params[:T_code] = TRANSACTIONS[action] - - params.collect { |key, value| "#{key}=#{CGI.escape(value.to_s)}" }.join("&") - end - end - end -end diff --git a/lib/active_merchant/billing/gateways/sage/sage_virtual_check.rb b/lib/active_merchant/billing/gateways/sage/sage_virtual_check.rb deleted file mode 100644 index e197fe5d1fa..00000000000 --- a/lib/active_merchant/billing/gateways/sage/sage_virtual_check.rb +++ /dev/null @@ -1,102 +0,0 @@ -require File.dirname(__FILE__) + '/sage_core' - -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - class SageVirtualCheckGateway < Gateway #:nodoc: - include SageCore - self.live_url = 'https://www.sagepayments.net/cgi-bin/eftVirtualCheck.dll?transaction' - self.source = 'virtual_check' - - def purchase(money, credit_card, options = {}) - post = {} - add_check(post, credit_card) - add_check_customer_data(post, options) - add_transaction_data(post, money, options) - commit(:purchase, post) - end - - def void(reference, options = {}) - post = {} - add_reference(post, reference) - commit(:void, post) - end - - def credit(money, credit_card, options = {}) - deprecated CREDIT_DEPRECATION_MESSAGE - refund(money, source, options) - end - - def refund(money, credit_card, options = {}) - post = {} - add_check(post, credit_card) - add_check_customer_data(post, options) - add_transaction_data(post, money, options) - commit(:credit, post) - end - - private - def add_check(post, check) - post[:C_first_name] = check.first_name - post[:C_last_name] = check.last_name - post[:C_rte] = check.routing_number - post[:C_acct] = check.account_number - post[:C_check_number] = check.number - post[:C_acct_type] = account_type(check) - end - - def add_check_customer_data(post, options) - # Required  Customer Type – (NACHA Transaction Class) - # CCD for Commercial, Merchant Initiated - # PPD for Personal, Merchant Initiated - # WEB for Internet, Consumer Initiated - # RCK for Returned Checks - # ARC for Account Receivable Entry - # TEL for TelephoneInitiated - post[:C_customer_type] = "WEB" - - # Optional  10  Digit Originator  ID – Assigned  By for  each transaction  class  or  business  purpose. If  not provided, the default Originator ID for the specific  Customer Type will be applied.  - post[:C_originator_id] = options[:originator_id] - - # Optional  Transaction Addenda - post[:T_addenda] = options[:addenda] - - # Required  Check  Writer  Social  Security  Number  (  Numbers Only, No Dashes )  - post[:C_ssn] = options[:ssn].to_s.gsub(/[^\d]/, '') - - post[:C_dl_state_code] = options[:drivers_license_state] - post[:C_dl_number] = options[:drivers_license_number] - post[:C_dob] = format_birth_date(options[:date_of_birth]) - end - - def format_birth_date(date) - date.respond_to?(:strftime) ? date.strftime("%m/%d/%Y") : date - end - - # DDA for Checking - # SAV for Savings  - def account_type(check) - case check.account_type - when 'checking' then 'DDA' - when 'savings' then 'SAV' - else raise ArgumentError, "Unknown account type #{check.account_type}" - end - end - - def parse(data) - response = {} - response[:success] = data[1,1] - response[:code] = data[2,6].strip - response[:message] = data[8,32].strip - response[:risk] = data[40, 2] - response[:reference] = data[42, 10] - - extra_data = data[53...-1].split("\034") - response[:order_number] = extra_data[0] - response[:authentication_indicator] = extra_data[1] - response[:authentication_disclosure] = extra_data[2] - response - end - end - end -end - diff --git a/lib/active_merchant/billing/gateways/sage_pay.rb b/lib/active_merchant/billing/gateways/sage_pay.rb index d56d80318e5..a3cd5f7b657 100644 --- a/lib/active_merchant/billing/gateways/sage_pay.rb +++ b/lib/active_merchant/billing/gateways/sage_pay.rb @@ -18,32 +18,58 @@ class SagePayGateway < Gateway :authorization => 'DEFERRED', :capture => 'RELEASE', :void => 'VOID', - :abort => 'ABORT' + :abort => 'ABORT', + :store => 'TOKEN', + :unstore => 'REMOVETOKEN', + :repeat => 'REPEAT' } CREDIT_CARDS = { - :visa => "VISA", - :master => "MC", - :delta => "DELTA", - :solo => "SOLO", - :switch => "MAESTRO", - :maestro => "MAESTRO", - :american_express => "AMEX", - :electron => "UKE", - :diners_club => "DC", - :jcb => "JCB" + :visa => 'VISA', + :master => 'MC', + :delta => 'DELTA', + :maestro => 'MAESTRO', + :american_express => 'AMEX', + :electron => 'UKE', + :diners_club => 'DC', + :jcb => 'JCB' } - ELECTRON = /^(424519|42496[23]|450875|48440[6-8]|4844[1-5][1-5]|4917[3-5][0-9]|491880)\d{10}(\d{3})?$/ + AVS_CODE = { + 'NOTPROVIDED' => nil, + 'NOTCHECKED' => 'X', + 'MATCHED' => 'Y', + 'NOTMATCHED' => 'N' + } + + CVV_CODE = { + 'NOTPROVIDED' => 'S', + 'NOTCHECKED' => 'X', + 'MATCHED' => 'M', + 'NOTMATCHED' => 'N' + } - AVS_CVV_CODE = { - "NOTPROVIDED" => nil, - "NOTCHECKED" => 'X', - "MATCHED" => 'Y', - "NOTMATCHED" => 'N' + OPTIONAL_REQUEST_FIELDS = { + paypal_callback_url: :PayPalCallbackURL, + basket: :Basket, + gift_aid_payment: :GiftAidPayment, + apply_avscv2: :ApplyAVSCV2, + apply_3d_secure: :Apply3DSecure, + account_type: :AccountType, + billing_agreement: :BillingAgreement, + basket_xml: :BasketXML, + customer_xml: :CustomerXML, + surcharge_xml: :SurchargeXML, + vendor_data: :VendorData, + language: :Language, + website: :Website, + recipient_account_number: :FIRecipientAcctNumber, + recipient_surname: :FIRecipientSurname, + recipient_postcode: :FIRecipientPostcode, + recipient_dob: :FIRecipientDoB } - self.supported_cardtypes = [:visa, :master, :american_express, :discover, :jcb, :switch, :solo, :maestro, :diners_club] + self.supported_cardtypes = [:visa, :master, :american_express, :discover, :jcb, :maestro, :diners_club] self.supported_countries = ['GB', 'IE'] self.default_currency = 'GBP' @@ -55,29 +81,29 @@ def initialize(options = {}) super end - def purchase(money, credit_card, options = {}) + def purchase(money, payment_method, options = {}) requires!(options, :order_id) post = {} add_amount(post, money, options) add_invoice(post, options) - add_credit_card(post, credit_card) + add_payment_method(post, payment_method, options) add_address(post, options) add_customer_data(post, options) add_optional_data(post, options) - commit(:purchase, post) + commit((past_purchase_reference?(payment_method) ? :repeat : :purchase), post) end - def authorize(money, credit_card, options = {}) + def authorize(money, payment_method, options = {}) requires!(options, :order_id) post = {} add_amount(post, money, options) add_invoice(post, options) - add_credit_card(post, credit_card) + add_payment_method(post, payment_method, options) add_address(post, options) add_customer_data(post, options) add_optional_data(post, options) @@ -110,7 +136,7 @@ def refund(money, identification, options = {}) post = {} - add_credit_reference(post, identification) + add_related_reference(post, identification) add_amount(post, money, options) add_invoice(post, options) @@ -118,11 +144,55 @@ def refund(money, identification, options = {}) end def credit(money, identification, options = {}) - deprecated CREDIT_DEPRECATION_MESSAGE + ActiveMerchant.deprecated CREDIT_DEPRECATION_MESSAGE refund(money, identification, options) end + def store(credit_card, options = {}) + post = {} + add_credit_card(post, credit_card) + add_currency(post, 0, options) + + commit(:store, post) + end + + def unstore(token, options = {}) + post = {} + add_token(post, token) + commit(:unstore, post) + end + + def verify(credit_card, options={}) + MultiResponse.run(:use_first_response) do |r| + r.process { authorize(100, credit_card, options) } + r.process(:ignore_result) { void(r.authorization, options) } + end + end + + def supports_scrubbing + true + end + + def scrub(transcript) + transcript. + gsub(%r((Authorization: Basic )\w+), '\1[FILTERED]'). + gsub(%r((&?CardNumber=)\d+(&?)), '\1[FILTERED]\2'). + gsub(%r((&?CV2=)\d+(&?)), '\1[FILTERED]\2') + end + private + + def truncate(value, max_size) + return nil unless value + return value.to_s if CGI.escape(value.to_s).length <= max_size + + if value.size > max_size + truncate(super(value, max_size), max_size) + else + truncate(value.to_s.chop, max_size) + end + end + def add_reference(post, identification) order_id, transaction_id, authorization, security_key = identification.split(';') @@ -132,7 +202,7 @@ def add_reference(post, identification) add_pair(post, :SecurityKey, security_key) end - def add_credit_reference(post, identification) + def add_related_reference(post, identification) order_id, transaction_id, authorization, security_key = identification.split(';') add_pair(post, :RelatedVendorTxCode, order_id) @@ -147,79 +217,115 @@ def add_amount(post, money, options) add_pair(post, :Currency, currency, :required => true) end + def add_currency(post, money, options) + currency = options[:currency] || currency(money) + add_pair(post, :Currency, currency, :required => true) + end + # doesn't actually use the currency -- dodgy! def add_release_amount(post, money, options) add_pair(post, :ReleaseAmount, amount(money), :required => true) end def add_customer_data(post, options) - add_pair(post, :CustomerEMail, options[:email][0,255]) unless options[:email].blank? - add_pair(post, :BillingPhone, options[:phone].gsub(/[^0-9+]/, '')[0,20]) unless options[:phone].blank? + add_pair(post, :CustomerEMail, truncate(options[:email], 255)) unless options[:email].blank? add_pair(post, :ClientIPAddress, options[:ip]) end def add_optional_data(post, options) - add_pair(post, :GiftAidPayment, options[:gift_aid_payment]) unless options[:gift_aid_payment].blank? - add_pair(post, :Apply3DSecure, options[:apply_3d_secure]) unless options[:apply_3d_secure].blank? + add_pair(post, :CreateToken, 1) unless options[:store].blank? + + OPTIONAL_REQUEST_FIELDS.each do |gateway_option, sagepay_field| + add_pair(post, sagepay_field, options[gateway_option]) + end end def add_address(post, options) if billing_address = options[:billing_address] || options[:address] - first_name, last_name = parse_first_and_last_name(billing_address[:name]) - add_pair(post, :BillingSurname, last_name) - add_pair(post, :BillingFirstnames, first_name) - add_pair(post, :BillingAddress1, billing_address[:address1]) - add_pair(post, :BillingAddress2, billing_address[:address2]) - add_pair(post, :BillingCity, billing_address[:city]) - add_pair(post, :BillingState, billing_address[:state]) if billing_address[:country] == 'US' - add_pair(post, :BillingCountry, billing_address[:country]) - add_pair(post, :BillingPostCode, billing_address[:zip]) + first_name, last_name = split_names(billing_address[:name]) + add_pair(post, :BillingSurname, truncate(last_name, 20)) + add_pair(post, :BillingFirstnames, truncate(first_name, 20)) + add_pair(post, :BillingAddress1, truncate(billing_address[:address1], 100)) + add_pair(post, :BillingAddress2, truncate(billing_address[:address2], 100)) + add_pair(post, :BillingCity, truncate(billing_address[:city], 40)) + add_pair(post, :BillingState, truncate(billing_address[:state], 2)) if is_usa(billing_address[:country]) + add_pair(post, :BillingCountry, truncate(billing_address[:country], 2)) + add_pair(post, :BillingPhone, sanitize_phone(billing_address[:phone])) + add_pair(post, :BillingPostCode, truncate(billing_address[:zip], 10)) end if shipping_address = options[:shipping_address] || billing_address - first_name, last_name = parse_first_and_last_name(shipping_address[:name]) - add_pair(post, :DeliverySurname, last_name) - add_pair(post, :DeliveryFirstnames, first_name) - add_pair(post, :DeliveryAddress1, shipping_address[:address1]) - add_pair(post, :DeliveryAddress2, shipping_address[:address2]) - add_pair(post, :DeliveryCity, shipping_address[:city]) - add_pair(post, :DeliveryState, shipping_address[:state]) if shipping_address[:country] == 'US' - add_pair(post, :DeliveryCountry, shipping_address[:country]) - add_pair(post, :DeliveryPostCode, shipping_address[:zip]) + first_name, last_name = split_names(shipping_address[:name]) + add_pair(post, :DeliverySurname, truncate(last_name, 20)) + add_pair(post, :DeliveryFirstnames, truncate(first_name, 20)) + add_pair(post, :DeliveryAddress1, truncate(shipping_address[:address1], 100)) + add_pair(post, :DeliveryAddress2, truncate(shipping_address[:address2], 100)) + add_pair(post, :DeliveryCity, truncate(shipping_address[:city], 40)) + add_pair(post, :DeliveryState, truncate(shipping_address[:state], 2)) if is_usa(shipping_address[:country]) + add_pair(post, :DeliveryCountry, truncate(shipping_address[:country], 2)) + add_pair(post, :DeliveryPhone, sanitize_phone(shipping_address[:phone])) + add_pair(post, :DeliveryPostCode, truncate(shipping_address[:zip], 10)) end end def add_invoice(post, options) add_pair(post, :VendorTxCode, sanitize_order_id(options[:order_id]), :required => true) - add_pair(post, :Description, options[:description] || options[:order_id]) + add_pair(post, :Description, truncate(options[:description] || options[:order_id], 100)) + end + + def add_payment_method(post, payment_method, options) + if payment_method.is_a?(String) + if past_purchase_reference?(payment_method) + add_related_reference(post, payment_method) + else + add_token_details(post, payment_method, options) + end + else + add_credit_card(post, payment_method) + end end def add_credit_card(post, credit_card) - add_pair(post, :CardHolder, credit_card.name, :required => true) + add_pair(post, :CardHolder, truncate(credit_card.name, 50), :required => true) add_pair(post, :CardNumber, credit_card.number, :required => true) add_pair(post, :ExpiryDate, format_date(credit_card.month, credit_card.year), :required => true) - - if requires_start_date_or_issue_number?(credit_card) - add_pair(post, :StartDate, format_date(credit_card.start_month, credit_card.start_year)) - add_pair(post, :IssueNumber, credit_card.issue_number) - end add_pair(post, :CardType, map_card_type(credit_card)) add_pair(post, :CV2, credit_card.verification_value) end + def add_token_details(post, token, options) + add_token(post, token) + add_pair(post, :StoreToken, options[:customer]) + add_pair(post, :CV2, options[:verification_value]) + end + + def add_token(post, token) + add_pair(post, :Token, token) + end + def sanitize_order_id(order_id) - order_id.to_s.gsub(/[^-a-zA-Z0-9._]/, '') + cleansed = order_id.to_s.gsub(/[^-a-zA-Z0-9._]/, '') + truncate(cleansed, 40) + end + + def sanitize_phone(phone) + return nil unless phone + cleansed = phone.to_s.gsub(/[^0-9+]/, '') + truncate(cleansed, 20) + end + + def is_usa(country) + truncate(country, 2) == 'US' end def map_card_type(credit_card) - raise ArgumentError, "The credit card type must be provided" if card_brand(credit_card).blank? + raise ArgumentError, 'The credit card type must be provided' if card_brand(credit_card).blank? card_type = card_brand(credit_card).to_sym - # Check if it is an electron card - if card_type == :visa && credit_card.number =~ ELECTRON + if card_type == :visa && credit_card.electron? CREDIT_CARDS[:electron] else CREDIT_CARDS[card_type] @@ -230,32 +336,37 @@ def map_card_type(credit_card) def format_date(month, year) return nil if year.blank? || month.blank? - year = sprintf("%.4i", year) - month = sprintf("%.2i", month) + year = sprintf('%.4i', year) + month = sprintf('%.2i', month) "#{month}#{year[-2..-1]}" end def commit(action, parameters) - response = parse( ssl_post(url_for(action), post_data(action, parameters)) ) + response = parse(ssl_post(url_for(action), post_data(action, parameters))) - Response.new(response["Status"] == APPROVED, message_from(response), response, + Response.new(response['Status'] == APPROVED, message_from(response), response, :test => test?, :authorization => authorization_from(response, parameters, action), :avs_result => { - :street_match => AVS_CVV_CODE[ response["AddressResult"] ], - :postal_match => AVS_CVV_CODE[ response["PostCodeResult"] ], + :street_match => AVS_CODE[response['AddressResult']], + :postal_match => AVS_CODE[response['PostCodeResult']], }, - :cvv_result => AVS_CVV_CODE[ response["CV2Result"] ] + :cvv_result => CVV_CODE[response['CV2Result']] ) end def authorization_from(response, params, action) - [ params[:VendorTxCode], - response["VPSTxId"], - response["TxAuthNo"], - response["SecurityKey"], - action ].join(";") + case action + when :store + response['Token'] + else + [ params[:VendorTxCode], + response['VPSTxId'] || params[:VPSTxId], + response['TxAuthNo'], + response['SecurityKey'] || params[:SecurityKey], + action ].join(';') + end end def abort_or_void_from(identification) @@ -268,12 +379,16 @@ def url_for(action) end def build_url(action) - endpoint = [ :purchase, :authorization ].include?(action) ? "vspdirect-register" : TRANSACTIONS[action].downcase + endpoint = case action + when :purchase, :authorization then 'vspdirect-register' + when :store then 'directtoken' + else TRANSACTIONS[action].downcase + end "#{test? ? self.test_url : self.live_url}/#{endpoint}.vsp" end def build_simulator_url(action) - endpoint = [ :purchase, :authorization ].include?(action) ? "VSPDirectGateway.asp" : "VSPServerGateway.asp?Service=Vendor#{TRANSACTIONS[action].capitalize}Tx" + endpoint = [ :purchase, :authorization ].include?(action) ? 'VSPDirectGateway.asp' : "VSPServerGateway.asp?Service=Vendor#{TRANSACTIONS[action].capitalize}Tx" "#{self.simulator_url}/#{endpoint}" end @@ -285,10 +400,14 @@ def post_data(action, parameters = {}) parameters.update( :Vendor => @options[:login], :TxType => TRANSACTIONS[action], - :VPSProtocol => "2.23" + :VPSProtocol => @options.fetch(:protocol_version, '3.00') ) - parameters.collect { |key, value| "#{key}=#{CGI.escape(value.to_s)}" }.join("&") + if(application_id && (application_id != Gateway.application_id)) + parameters.update(:ReferrerID => application_id) + end + + parameters.collect { |key, value| "#{key}=#{CGI.escape(value.to_s)}" }.join('&') end # SagePay returns data in the following format @@ -306,19 +425,10 @@ def add_pair(post, key, value, options = {}) post[key] = value if !value.blank? || options[:required] end - def parse_first_and_last_name(value) - name = value.to_s.split(' ') - - last_name = name.pop || '' - first_name = name.join(' ') - [ first_name[0,20], last_name[0,20] ] - end - - def localized_amount(money, currency) - amount = amount(money) - CURRENCIES_WITHOUT_FRACTIONS.include?(currency.to_s) ? amount.split('.').first : amount + def past_purchase_reference?(payment_method) + return false unless payment_method.is_a?(String) + payment_method.split(';').last == 'purchase' end end end end - diff --git a/lib/active_merchant/billing/gateways/sallie_mae.rb b/lib/active_merchant/billing/gateways/sallie_mae.rb index 1098790d292..7e15f08e232 100644 --- a/lib/active_merchant/billing/gateways/sallie_mae.rb +++ b/lib/active_merchant/billing/gateways/sallie_mae.rb @@ -21,7 +21,7 @@ def initialize(options = {}) end def test? - @options[:login] == "TEST0" + @options[:login] == 'TEST0' end def authorize(money, creditcard, options = {}) @@ -94,11 +94,11 @@ def add_creditcard(post, creditcard) def parse(body) h = {} - body.gsub!("", "") + body.gsub!('<html><body><plaintext>', '') body. split("\r\n"). map do |i| - a = i.split("=") + a = i.split('=') h[a.first] = a.last unless a.first.nil? end h @@ -111,33 +111,32 @@ def commit(action, money, parameters) case action when :sale - parameters[:action] = "ns_quicksale_cc" + parameters[:action] = 'ns_quicksale_cc' when :authonly - parameters[:action] = "ns_quicksale_cc" + parameters[:action] = 'ns_quicksale_cc' parameters[:authonly] = 1 when :capture - parameters[:action] = "ns_quicksale_cc" + parameters[:action] = 'ns_quicksale_cc' end - response = parse(ssl_post(self.live_url, parameters.to_post_data) || "") + response = parse(ssl_post(self.live_url, parameters.to_post_data) || '') Response.new(successful?(response), message_from(response), response, :test => test?, - :authorization => response["refcode"] + :authorization => response['refcode'] ) end def successful?(response) - response["Status"] == "Accepted" + response['Status'] == 'Accepted' end def message_from(response) if successful?(response) - "Accepted" + 'Accepted' else - response["Reason"].split(":")[2].capitalize unless response["Reason"].nil? + response['Reason'].split(':')[2].capitalize unless response['Reason'].nil? end end end end end - diff --git a/lib/active_merchant/billing/gateways/samurai.rb b/lib/active_merchant/billing/gateways/samurai.rb deleted file mode 100644 index c263c79f0d3..00000000000 --- a/lib/active_merchant/billing/gateways/samurai.rb +++ /dev/null @@ -1,118 +0,0 @@ -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - class SamuraiGateway < Gateway - - self.homepage_url = 'https://samurai.feefighters.com' - self.display_name = 'Samurai' - self.supported_countries = ['US'] - self.supported_cardtypes = [:visa, :master, :american_express, :discover, :jcb, :diners_club] - self.default_currency = 'USD' - self.money_format = :dollars - - def initialize(options = {}) - begin - require 'samurai' - rescue LoadError - raise "Could not load the samurai gem (>= 0.2.25). Use `gem install samurai` to install it." - end - - requires!(options, :login, :password, :processor_token) - Samurai.options = { - :merchant_key => options[:login], - :merchant_password => options[:password], - :processor_token => options[:processor_token] - } - - super - end - - def authorize(money, credit_card_or_vault_id, options = {}) - token = payment_method_token(credit_card_or_vault_id, options) - return token if token.is_a?(Response) - - authorize = Samurai::Processor.authorize(token, amount(money), processor_options(options)) - handle_result(authorize) - end - - def purchase(money, credit_card_or_vault_id, options = {}) - token = payment_method_token(credit_card_or_vault_id, options) - return token if token.is_a?(Response) - - purchase = Samurai::Processor.purchase(token, amount(money), processor_options(options)) - handle_result(purchase) - end - - def capture(money, authorization_id, options = {}) - transaction = Samurai::Transaction.find(authorization_id) - handle_result(transaction.capture(amount(money))) - end - - def refund(money, transaction_id, options = {}) - transaction = Samurai::Transaction.find(transaction_id) - handle_result(transaction.credit(amount(money))) - end - - def void(transaction_id, options = {}) - transaction = Samurai::Transaction.find(transaction_id) - handle_result(transaction.void) - end - - def store(creditcard, options = {}) - address = options[:billing_address] || options[:address] || {} - - result = Samurai::PaymentMethod.create({ - :card_number => creditcard.number, - :expiry_month => creditcard.month.to_s.rjust(2, "0"), - :expiry_year => creditcard.year.to_s, - :cvv => creditcard.verification_value, - :first_name => creditcard.first_name, - :last_name => creditcard.last_name, - :address_1 => address[:address1], - :address_2 => address[:address2], - :city => address[:city], - :zip => address[:zip], - :sandbox => test? - }) - result.retain if options[:retain] && result.is_sensitive_data_valid && result.payment_method_token - - Response.new(result.is_sensitive_data_valid, - message_from_result(result), - { :payment_method_token => result.is_sensitive_data_valid && result.payment_method_token }) - end - - private - - def payment_method_token(credit_card_or_vault_id, options) - return credit_card_or_vault_id if credit_card_or_vault_id.is_a?(String) - store_result = store(credit_card_or_vault_id, options) - store_result.success? ? store_result.params["payment_method_token"] : store_result - end - - def handle_result(result) - response_params, response_options = {}, {} - if result.success? - response_options[:test] = test? - response_options[:authorization] = result.reference_id - response_params[:reference_id] = result.reference_id - response_params[:transaction_token] = result.transaction_token - response_params[:payment_method_token] = result.payment_method.payment_method_token - end - - response_options[:avs_result] = { :code => result.processor_response && result.processor_response.avs_result_code } - response_options[:cvv_result] = result.processor_response && result.processor_response.cvv_result_code - - message = message_from_result(result) - Response.new(result.success?, message, response_params, response_options) - end - - def message_from_result(result) - return "OK" if result.success? - result.errors.map {|_, messages| messages }.join(" ") - end - - def processor_options(options) - options.slice(:billing_reference, :customer_reference, :custom, :descriptor) - end - end - end -end diff --git a/lib/active_merchant/billing/gateways/secure_net.rb b/lib/active_merchant/billing/gateways/secure_net.rb index fd2aad4e282..f3abdf29935 100644 --- a/lib/active_merchant/billing/gateways/secure_net.rb +++ b/lib/active_merchant/billing/gateways/secure_net.rb @@ -2,21 +2,21 @@ module ActiveMerchant #:nodoc: module Billing #:nodoc: class SecureNetGateway < Gateway - API_VERSION = "4.0" + API_VERSION = '4.0' TRANSACTIONS = { - :auth_only => "0000", - :auth_capture => "0100", - :prior_auth_capture => "0200", - :void => "0400", - :credit => "0500" + :auth_only => '0000', + :auth_capture => '0100', + :prior_auth_capture => '0200', + :void => '0400', + :credit => '0500' } XML_ATTRIBUTES = { - 'xmlns' => "http://gateway.securenet.com/API/Contracts", - 'xmlns:i' => "http://www.w3.org/2001/XMLSchema-instance" + 'xmlns' => 'http://gateway.securenet.com/API/Contracts', + 'xmlns:i' => 'http://www.w3.org/2001/XMLSchema-instance' } - NIL_ATTRIBUTE = { 'i:nil' => "true" } + NIL_ATTRIBUTE = { 'i:nil' => 'true' } self.supported_countries = ['US'] self.supported_cardtypes = [:visa, :master, :american_express, :discover] @@ -57,16 +57,27 @@ def refund(money, authorization, options = {}) end def credit(money, authorization, options = {}) - deprecated CREDIT_DEPRECATION_MESSAGE + ActiveMerchant.deprecated CREDIT_DEPRECATION_MESSAGE refund(money, authorization, options) end + def supports_scrubbing? + true + end + + def scrub(transcript) + transcript. + gsub(%r((<CARDNUMBER>)\d+(</CARDNUMBER>))i, '\1[FILTERED]\2'). + gsub(%r((<CARDCODE>)\d+(</CARDCODE>))i, '\1[FILTERED]\2'). + gsub(%r((<SECUREKEY>).+(</SECUREKEY>))i, '\1[FILTERED]\2') + end private + def commit(request) xml = build_request(request) url = test? ? self.test_url : self.live_url - data = ssl_post(url, xml, "Content-Type" => "text/xml") + data = ssl_post(url, xml, 'Content-Type' => 'text/xml') response = parse(data) Response.new(success?(response), message_from(response), response, @@ -81,7 +92,7 @@ def build_request(request) xml = Builder::XmlMarkup.new xml.instruct! - xml.tag!("TRANSACTION", XML_ATTRIBUTES) do + xml.tag!('TRANSACTION', XML_ATTRIBUTES) do xml << request end @@ -93,9 +104,7 @@ def build_sale_or_authorization(creditcard, options, action, money) xml.tag! 'AMOUNT', amount(money) add_credit_card(xml, creditcard) - add_required_params(xml, action, options) - add_address(xml, creditcard, options) - add_invoice(xml, options) + add_params_in_required_order(xml, action, creditcard, options) add_more_required_params(xml, options) xml.target! @@ -107,11 +116,11 @@ def build_capture_refund_void(authorization, options, action, money = nil) transaction_id, amount_in_ref, last_four = split_authorization(authorization) xml.tag! 'AMOUNT', amount(money) || amount_in_ref - xml.tag!("CARD") do + xml.tag!('CARD') do xml.tag! 'CARDNUMBER', last_four end - add_required_params(xml, action, options) + add_params_in_required_order(xml, action, nil, options) xml.tag! 'REF_TRANSID', transaction_id add_more_required_params(xml, options) @@ -119,20 +128,13 @@ def build_capture_refund_void(authorization, options, action, money = nil) end def add_credit_card(xml, creditcard) - xml.tag!("CARD") do + xml.tag!('CARD') do xml.tag! 'CARDCODE', creditcard.verification_value if creditcard.verification_value? xml.tag! 'CARDNUMBER', creditcard.number xml.tag! 'EXPDATE', expdate(creditcard) end end - def expdate(creditcard) - year = sprintf("%.4i", creditcard.year) - month = sprintf("%.2i", creditcard.month) - - "#{month}#{year[-2..-1]}" - end - def add_customer_data(xml, options) if options.has_key? :customer xml.tag! 'CUSTOMERID', options[:customer] @@ -144,9 +146,10 @@ def add_customer_data(xml, options) end def add_address(xml, creditcard, options) + return unless creditcard if address = options[:billing_address] || options[:address] - xml.tag!("CUSTOMER_BILL") do + xml.tag!('CUSTOMER_BILL') do xml.tag! 'ADDRESS', address[:address1].to_s xml.tag! 'CITY', address[:city].to_s xml.tag! 'COMPANY', address[:company].to_s @@ -164,13 +167,21 @@ def add_address(xml, creditcard, options) end if address = options[:shipping_address] - xml.tag!("CUSTOMER_SHIP") do + xml.tag!('CUSTOMER_SHIP') do xml.tag! 'ADDRESS', address[:address1].to_s xml.tag! 'CITY', address[:city].to_s xml.tag! 'COMPANY', address[:company].to_s xml.tag! 'COUNTRY', address[:country].to_s - xml.tag! 'FIRSTNAME', address[:first_name].to_s - xml.tag! 'LASTNAME', address[:last_name].to_s + + if address[:name] + first_name, last_name = split_names(address[:name]) + xml.tag! 'FIRSTNAME', first_name + xml.tag! 'LASTNAME', last_name + else + xml.tag! 'FIRSTNAME', address[:first_name].to_s + xml.tag! 'LASTNAME', address[:last_name].to_s + end + xml.tag! 'STATE', address[:state].blank? ? 'n/a' : address[:state] xml.tag! 'ZIP', address[:zip].to_s end @@ -178,39 +189,39 @@ def add_address(xml, creditcard, options) xml.tag!('CUSTOMER_SHIP', NIL_ATTRIBUTE) do end end - - end - - def add_invoice(xml, options) - xml.tag! 'NOTE', options[:description] if options[:description] - xml.tag! 'INVOICEDESC', options[:invoice_description] if options[:invoice_description] - xml.tag! 'INVOICENUM', options[:invoice_number] if options[:invoice_number] end def add_merchant_key(xml, options) - xml.tag!("MERCHANT_KEY") do + xml.tag!('MERCHANT_KEY') do xml.tag! 'GROUPID', 0 xml.tag! 'SECUREKEY', @options[:password] xml.tag! 'SECURENETID', @options[:login] end end - def add_required_params(xml, action, options) + # SecureNet requires some of the xml params to be in a certain order. http://cl.ly/image/3K260E0p0a0n/content.png + def add_params_in_required_order(xml, action, creditcard, options) xml.tag! 'CODE', TRANSACTIONS[action] add_customer_data(xml, options) + add_address(xml, creditcard, options) xml.tag! 'DCI', 0 # No duplicate checking will be done, except for ORDERID xml.tag! 'INSTALLMENT_SEQUENCENUM', 1 + xml.tag! 'INVOICEDESC', options[:invoice_description] if options[:invoice_description] + xml.tag! 'INVOICENUM', options[:invoice_number] if options[:invoice_number] add_merchant_key(xml, options) xml.tag! 'METHOD', 'CC' - xml.tag! 'ORDERID', options[:order_id] + xml.tag! 'NOTE', options[:description] if options[:description] + xml.tag! 'ORDERID', truncate(options[:order_id], 25) xml.tag! 'OVERRIDE_FROM', 0 # Docs say not required, but doesn't work without it end def add_more_required_params(xml, options) + test_mode = options[:test_mode].nil? ? test? : options[:test_mode] xml.tag! 'RETAIL_LANENUM', '0' - xml.tag! 'TEST', 'TRUE' if test? + xml.tag! 'TEST', test_mode ? 'TRUE' : 'FALSE' xml.tag! 'TOTAL_INSTALLMENTCOUNT', 0 xml.tag! 'TRANSACTION_SERVICE', 0 + xml.tag! 'DEVELOPERID', options[:developer_id] if options[:developer_id] end def success?(response) @@ -218,18 +229,13 @@ def success?(response) end def message_from(response) - if response[:response_code].to_i == DECLINED - return CVVResult.messages[ response[:card_code_response_code] ] if CARD_CODE_ERRORS.include?(response[:card_code_response_code]) - return AVSResult.messages[ response[:avs_result_code] ] if AVS_ERRORS.include?(response[:avs_result_code]) - end - return response[:response_reason_text].nil? ? '' : response[:response_reason_text][0..-1] end def parse(xml) response = {} xml = REXML::Document.new(xml) - root = REXML::XPath.first(xml, "//GATEWAYRESPONSE") + root = REXML::XPath.first(xml, '//GATEWAYRESPONSE') if root root.elements.to_a.each do |node| recurring_parse_element(response, node) @@ -241,22 +247,21 @@ def parse(xml) def recurring_parse_element(response, node) if node.has_elements? - node.elements.each{|e| recurring_parse_element(response, e) } + node.elements.each { |e| recurring_parse_element(response, e) } else response[node.name.underscore.to_sym] = node.text end end def split_authorization(authorization) - transaction_id, amount, last_four = authorization.split("|") + transaction_id, amount, last_four = authorization.split('|') [transaction_id, amount, last_four] end def build_authorization(response) - [response[:transactionid], response[:transactionamount], response[:last4_digits]].join("|") + [response[:transactionid], response[:transactionamount], response[:last4_digits]].join('|') end end end end - diff --git a/lib/active_merchant/billing/gateways/secure_pay.rb b/lib/active_merchant/billing/gateways/secure_pay.rb index 96aca9e82e1..4cd0c8a63d3 100644 --- a/lib/active_merchant/billing/gateways/secure_pay.rb +++ b/lib/active_merchant/billing/gateways/secure_pay.rb @@ -1,28 +1,200 @@ -require File.dirname(__FILE__) + '/authorize_net' +require 'active_merchant/billing/gateways/authorize_net' module ActiveMerchant #:nodoc: module Billing #:nodoc: - class SecurePayGateway < AuthorizeNetGateway + class SecurePayGateway < Gateway + API_VERSION = '3.1' + self.live_url = self.test_url = 'https://www.securepay.com/AuthSpayAdapter/process.aspx' + class_attribute :duplicate_window + + APPROVED, DECLINED, ERROR, FRAUD_REVIEW = 1, 2, 3, 4 + + RESPONSE_CODE, RESPONSE_REASON_CODE, RESPONSE_REASON_TEXT, AUTHORIZATION_CODE = 0, 2, 3, 4 + AVS_RESULT_CODE, TRANSACTION_ID, CARD_CODE_RESPONSE_CODE, CARDHOLDER_AUTH_CODE = 5, 6, 38, 39 + + self.default_currency = 'USD' + + self.supported_countries = %w(US CA GB AU) + self.supported_cardtypes = [:visa, :master, :american_express, :discover, :diners_club, :jcb] self.homepage_url = 'http://www.securepay.com/' self.display_name = 'SecurePay' - # Limit support to purchase() for the time being - # JRuby chokes here - # undef_method :authorize, :capture, :void, :credit + CARD_CODE_ERRORS = %w( N S ) + AVS_ERRORS = %w( A E N R W Z ) + AVS_REASON_CODES = %w(27 45) + TRANSACTION_ALREADY_ACTIONED = %w(310 311) + + def initialize(options = {}) + requires!(options, :login, :password) + super + end - undef_method :authorize - undef_method :capture - undef_method :void - undef_method :credit + def purchase(money, paysource, options = {}) + post = {} + add_currency_code(post, money, options) + add_invoice(post, options) + add_payment_source(post, paysource, options) + add_address(post, options) + add_customer_data(post, options) + add_duplicate_window(post) + + commit('AUTH_CAPTURE', money, post) + end private + def commit(action, money, parameters) + parameters[:amount] = amount(money) unless action == 'VOID' + + url = (test? ? self.test_url : self.live_url) + data = ssl_post(url, post_data(action, parameters)) + + response = parse(data) + response[:action] = action + + message = message_from(response) + + Response.new(success?(response), message, response, + :test => test?, + :authorization => response[:transaction_id], + :fraud_review => fraud_review?(response), + :avs_result => { :code => response[:avs_result_code] }, + :cvv_result => response[:card_code] + ) + end + + def success?(response) + response[:response_code] == APPROVED && TRANSACTION_ALREADY_ACTIONED.exclude?(response[:response_reason_code]) + end + + def fraud_review?(response) + response[:response_code] == FRAUD_REVIEW + end + + def parse(body) + fields = split(body) + + results = { + :response_code => fields[RESPONSE_CODE].to_i, + :response_reason_code => fields[RESPONSE_REASON_CODE], + :response_reason_text => fields[RESPONSE_REASON_TEXT], + :avs_result_code => fields[AVS_RESULT_CODE], + :transaction_id => fields[TRANSACTION_ID], + :card_code => fields[CARD_CODE_RESPONSE_CODE], + :authorization_code => fields[AUTHORIZATION_CODE], + :cardholder_authentication_code => fields[CARDHOLDER_AUTH_CODE] + } + results + end + + def post_data(action, parameters = {}) + post = {} + + post[:version] = API_VERSION + post[:login] = @options[:login] + post[:tran_key] = @options[:password] + post[:relay_response] = 'FALSE' + post[:type] = action + post[:delim_data] = 'TRUE' + post[:delim_char] = ',' + post[:encap_char] = '$' + post[:solution_ID] = application_id if application_id + + request = post.merge(parameters).collect { |key, value| "x_#{key}=#{CGI.escape(value.to_s)}" }.join('&') + request + end + + def add_currency_code(post, money, options) + post[:currency_code] = options[:currency] || currency(money) + end + + def add_invoice(post, options) + post[:invoice_num] = options[:order_id] + post[:description] = options[:description] + end + + def add_creditcard(post, creditcard, options={}) + post[:card_num] = creditcard.number + post[:card_code] = creditcard.verification_value if creditcard.verification_value? + post[:exp_date] = expdate(creditcard) + post[:first_name] = creditcard.first_name + post[:last_name] = creditcard.last_name + end + + def add_payment_source(params, source, options={}) + add_creditcard(params, source, options) + end + + def add_customer_data(post, options) + if options.has_key? :email + post[:email] = options[:email] + post[:email_customer] = false + end + + if options.has_key? :customer + post[:cust_id] = options[:customer] if Float(options[:customer]) rescue nil + end + + if options.has_key? :ip + post[:customer_ip] = options[:ip] + end + + if options.has_key? :cardholder_authentication_value + post[:cardholder_authentication_value] = options[:cardholder_authentication_value] + end + + if options.has_key? :authentication_indicator + post[:authentication_indicator] = options[:authentication_indicator] + end + end + + # x_duplicate_window won't be sent by default, because sending it changes the response. + # "If this field is present in the request with or without a value, an enhanced duplicate transaction response will be sent." + # (as of 2008-12-30) http://www.authorize.net/support/AIM_guide_SCC.pdf + def add_duplicate_window(post) + post[:duplicate_window] = duplicate_window if duplicate_window + end + + def add_address(post, options) + if address = options[:billing_address] || options[:address] + post[:address] = address[:address1].to_s + post[:company] = address[:company].to_s + post[:phone] = address[:phone].to_s + post[:zip] = address[:zip].to_s + post[:city] = address[:city].to_s + post[:country] = address[:country].to_s + post[:state] = address[:state].blank? ? 'n/a' : address[:state] + end + + if address = options[:shipping_address] + post[:ship_to_first_name] = address[:first_name].to_s + post[:ship_to_last_name] = address[:last_name].to_s + post[:ship_to_address] = address[:address1].to_s + post[:ship_to_company] = address[:company].to_s + post[:ship_to_phone] = address[:phone].to_s + post[:ship_to_zip] = address[:zip].to_s + post[:ship_to_city] = address[:city].to_s + post[:ship_to_country] = address[:country].to_s + post[:ship_to_state] = address[:state].blank? ? 'n/a' : address[:state] + end + end + + def message_from(results) + if results[:response_code] == DECLINED + return CVVResult.messages[results[:card_code]] if CARD_CODE_ERRORS.include?(results[:card_code]) + if AVS_REASON_CODES.include?(results[:response_reason_code]) && AVS_ERRORS.include?(results[:avs_result_code]) + return AVSResult.messages[results[:avs_result_code]] + end + end + + (results[:response_reason_text] ? results[:response_reason_text].chomp('.') : '') + end + def split(response) response.split(',') end end end end - diff --git a/lib/active_merchant/billing/gateways/secure_pay_au.rb b/lib/active_merchant/billing/gateways/secure_pay_au.rb index dd552114edb..37a2845bbd5 100644 --- a/lib/active_merchant/billing/gateways/secure_pay_au.rb +++ b/lib/active_merchant/billing/gateways/secure_pay_au.rb @@ -8,8 +8,8 @@ class SecurePayAuGateway < Gateway class_attribute :test_periodic_url, :live_periodic_url - self.test_url = 'https://www.securepay.com.au/test/payment' - self.live_url = 'https://www.securepay.com.au/xmlapi/payment' + self.test_url = 'https://api.securepay.com.au/test/payment' + self.live_url = 'https://api.securepay.com.au/xmlapi/payment' self.test_periodic_url = 'https://test.securepay.com.au/xmlapi/periodic' self.live_periodic_url = 'https://api.securepay.com.au/xmlapi/periodic' @@ -43,9 +43,9 @@ class SecurePayAuGateway < Gateway } PERIODIC_ACTIONS = { - :add_triggered => "add", - :remove_triggered => "delete", - :trigger => "trigger" + :add_triggered => 'add', + :remove_triggered => 'delete', + :trigger => 'trigger' } PERIODIC_TYPES = { @@ -85,7 +85,7 @@ def refund(money, reference, options = {}) end def credit(money, reference, options = {}) - deprecated CREDIT_DEPRECATION_MESSAGE + ActiveMerchant.deprecated CREDIT_DEPRECATION_MESSAGE refund(money, reference) end @@ -103,13 +103,27 @@ def unstore(identification, options = {}) commit_periodic(build_periodic_item(:remove_triggered, options[:amount], nil, options)) end + def supports_scrubbing? + true + end + + def scrub(transcript) + transcript. + gsub(%r((<merchantID>).+(</merchantID>)), '\1[FILTERED]\2'). + gsub(%r((<password>).+(</password>)), '\1[FILTERED]\2'). + gsub(%r((<cardNumber>).+(</cardNumber>)), '\1[FILTERED]\2'). + gsub(%r((<cvv>).+(</cvv>)), '\1[FILTERED]\2') + end + private def build_purchase_request(money, credit_card, options) xml = Builder::XmlMarkup.new - xml.tag! 'amount', amount(money) - xml.tag! 'currency', options[:currency] || currency(money) + currency = options[:currency] || currency(money) + + xml.tag! 'amount', localized_amount(money, currency) + xml.tag! 'currency', currency xml.tag! 'purchaseOrderNo', options[:order_id].to_s.gsub(/[ ']/, '') xml.tag! 'CreditCardInfo' do @@ -140,7 +154,7 @@ def build_request(action, body) xml.instruct! xml.tag! 'SecurePayMessage' do xml.tag! 'MessageInfo' do - xml.tag! 'messageID', ActiveMerchant::Utils.generate_unique_id.slice(0, 30) + xml.tag! 'messageID', SecureRandom.hex(15) xml.tag! 'messageTimestamp', generate_timestamp xml.tag! 'timeoutValue', request_timeout xml.tag! 'apiVersion', API_VERSION @@ -153,8 +167,8 @@ def build_request(action, body) xml.tag! 'RequestType', 'Payment' xml.tag! 'Payment' do - xml.tag! 'TxnList', "count" => 1 do - xml.tag! 'Txn', "ID" => 1 do + xml.tag! 'TxnList', 'count' => 1 do + xml.tag! 'Txn', 'ID' => 1 do xml.tag! 'txnType', TRANSACTIONS[action] xml.tag! 'txnSource', 23 xml << body @@ -199,7 +213,7 @@ def build_periodic_request(body) xml.instruct! xml.tag! 'SecurePayMessage' do xml.tag! 'MessageInfo' do - xml.tag! 'messageID', ActiveMerchant::Utils.generate_unique_id.slice(0, 30) + xml.tag! 'messageID', SecureRandom.hex(15) xml.tag! 'messageTimestamp', generate_timestamp xml.tag! 'timeoutValue', request_timeout xml.tag! 'apiVersion', PERIODIC_API_VERSION @@ -212,8 +226,8 @@ def build_periodic_request(body) xml.tag! 'RequestType', 'Periodic' xml.tag! 'Periodic' do - xml.tag! 'PeriodicList', "count" => 1 do - xml.tag! 'PeriodicItem', "ID" => 1 do + xml.tag! 'PeriodicList', 'count' => 1 do + xml.tag! 'PeriodicItem', 'ID' => 1 do xml << body end end @@ -224,7 +238,6 @@ def build_periodic_request(body) def commit_periodic(request) my_request = build_periodic_request(request) - #puts my_request response = parse(ssl_post(test? ? self.test_periodic_url : self.live_periodic_url, my_request)) Response.new(success?(response), message_from(response), response, @@ -263,7 +276,7 @@ def parse(body) def parse_element(response, node) if node.has_elements? - node.elements.each{|element| parse_element(response, element) } + node.elements.each { |element| parse_element(response, element) } else response[node.name.underscore.to_sym] = node.text end diff --git a/lib/active_merchant/billing/gateways/secure_pay_tech.rb b/lib/active_merchant/billing/gateways/secure_pay_tech.rb index 7ab6f62e1a5..b6980d4cf99 100644 --- a/lib/active_merchant/billing/gateways/secure_pay_tech.rb +++ b/lib/active_merchant/billing/gateways/secure_pay_tech.rb @@ -8,15 +8,15 @@ class SecurePayTechPostData < PostData self.live_url = self.test_url = 'https://tx.securepaytech.com/web/HttpPostPurchase' PAYMENT_GATEWAY_RESPONSES = { - 1 => "Transaction OK", - 2 => "Insufficient funds", - 3 => "Card expired", - 4 => "Card declined", - 5 => "Server error", - 6 => "Communications error", - 7 => "Unsupported transaction type", - 8 => "Bad or malformed request", - 9 => "Invalid card number" + 1 => 'Transaction OK', + 2 => 'Insufficient funds', + 3 => 'Card expired', + 4 => 'Card declined', + 5 => 'Server error', + 6 => 'Communications error', + 7 => 'Unsupported transaction type', + 8 => 'Bad or malformed request', + 9 => 'Invalid card number' } self.default_currency = 'NZD' @@ -82,7 +82,7 @@ def parse(body) end def commit(action, post) - response = parse( ssl_post(self.live_url, post_data(action, post) ) ) + response = parse(ssl_post(self.live_url, post_data(action, post))) Response.new(response[:result_code] == 1, message_from(response), response, :test => test?, @@ -99,14 +99,6 @@ def post_data(action, post) post[:MerchantKey] = @options[:password] post.to_s end - - def expdate(creditcard) - year = sprintf("%.4i", creditcard.year) - month = sprintf("%.2i", creditcard.month) - - "#{month}#{year[-2..-1]}" - end end end end - diff --git a/lib/active_merchant/billing/gateways/securion_pay.rb b/lib/active_merchant/billing/gateways/securion_pay.rb new file mode 100644 index 00000000000..c59370bad4d --- /dev/null +++ b/lib/active_merchant/billing/gateways/securion_pay.rb @@ -0,0 +1,264 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class SecurionPayGateway < Gateway + self.test_url = 'https://api.securionpay.com/' + self.live_url = 'https://api.securionpay.com/' + + self.supported_countries = %w(AL AD AT BY BE BG HR CY CZ RE DK EE IS FI FR DE GI GR HU IS IE IT IL LV LI LT LU + MK MT MD MC NL NO PL PT RO RU MA RS SK SI ES SE CH UA GB KI CI ME) + + self.default_currency = 'USD' + self.money_format = :cents + self.supported_cardtypes = [:visa, :master, :american_express, :discover, :jcb, :diners_club] + + self.homepage_url = 'https://securionpay.com/' + self.display_name = 'SecurionPay' + + STANDARD_ERROR_CODE_MAPPING = { + 'incorrect_number' => STANDARD_ERROR_CODE[:incorrect_number], + 'invalid_number' => STANDARD_ERROR_CODE[:invalid_number], + 'invalid_expiry_month' => STANDARD_ERROR_CODE[:invalid_expiry_date], + 'invalid_expiry_year' => STANDARD_ERROR_CODE[:invalid_expiry_date], + 'invalid_cvc' => STANDARD_ERROR_CODE[:invalid_cvc], + 'expired_card' => STANDARD_ERROR_CODE[:expired_card], + 'insufficient_funds' => STANDARD_ERROR_CODE[:card_declined], + 'incorrect_cvc' => STANDARD_ERROR_CODE[:incorrect_cvc], + 'incorrect_zip' => STANDARD_ERROR_CODE[:incorrect_zip], + 'card_declined' => STANDARD_ERROR_CODE[:card_declined], + 'processing_error' => STANDARD_ERROR_CODE[:processing_error], + 'lost_or_stolen' => STANDARD_ERROR_CODE[:card_declined], + 'suspected_fraud' => STANDARD_ERROR_CODE[:card_declined], + 'expired_token' => STANDARD_ERROR_CODE[:card_declined] + } + + def initialize(options={}) + requires!(options, :secret_key) + super + end + + def purchase(money, payment, options={}) + post = create_post_for_auth_or_purchase(money, payment, options) + commit('charges', post, options) + end + + def authorize(money, payment, options={}) + post = create_post_for_auth_or_purchase(money, payment, options) + post[:captured] = 'false' + commit('charges', post, options) + end + + def capture(money, authorization, options = {}) + post = {} + add_amount(post, money, options) + commit("charges/#{CGI.escape(authorization)}/capture", post, options) + end + + def refund(money, authorization, options = {}) + post = {} + add_amount(post, money, options) + commit("charges/#{CGI.escape(authorization)}/refund", post, options) + end + + def void(authorization, options = {}) + commit("charges/#{CGI.escape(authorization)}/refund", {}, options) + end + + def verify(credit_card, options={}) + MultiResponse.run(:use_first_response) do |r| + r.process { authorize(100, credit_card, options) } + r.process(:ignore_result) { void(r.authorization, options) } + end + end + + def store(credit_card, options = {}) + if options[:customer_id].blank? + MultiResponse.run() do |r| + # create charge object + r.process { authorize(100, credit_card, options) } + # create customer and save card + r.process { create_customer_add_card(r.authorization, options) } + # void the charge + r.process(:ignore_result) { void(r.params['metadata']['chargeId'], options) } + end + else + verify(credit_card, options) + end + end + + def customer(options = {}) + if options[:customer_id].blank? + return nil + else + commit("customers/#{CGI.escape(options[:customer_id])}", nil, options, :get) + end + end + + def supports_scrubbing? + true + end + + def scrub(transcript) + transcript. + gsub(%r((Authorization: Basic )\w+), '\1[FILTERED]'). + gsub(%r((card\[number\]=)\d+), '\1[FILTERED]'). + gsub(%r((card\[cvc\]=)\d+), '\1[FILTERED]') + end + + private + + def create_customer_add_card(authorization, options) + post = {} + post[:email] = options[:email] + post[:description] = options[:description] + post[:card] = authorization + post[:metadata] = {} + post[:metadata][:chargeId] = authorization + commit('customers', post, options) + end + + def add_customer(post, payment, options) + post[:customerId] = options[:customer_id] if options[:customer_id] + end + + def add_customer_data(post, options) + post[:description] = options[:description] + post[:ip] = options[:ip] + post[:user_agent] = options[:user_agent] + post[:referrer] = options[:referrer] + end + + def create_post_for_auth_or_purchase(money, payment, options) + post = {} + add_amount(post, money, options, true) + add_creditcard(post, payment, options) + add_customer(post, payment, options) + add_customer_data(post, options) + if options[:email] + post[:metadata] = {} + post[:metadata][:email] = options[:email] + end + post + end + + def add_amount(post, money, options, include_currency = false) + currency = (options[:currency] || default_currency) + post[:amount] = localized_amount(money, currency) + post[:currency] = currency.downcase if include_currency + end + + def add_creditcard(post, creditcard, options) + card = {} + if creditcard.respond_to?(:number) + card[:number] = creditcard.number + card[:expMonth] = creditcard.month + card[:expYear] = creditcard.year + card[:cvc] = creditcard.verification_value if creditcard.verification_value? + card[:cardholderName] = creditcard.name if creditcard.name + + post[:card] = card + add_address(post, options) + elsif creditcard.kind_of?(String) + post[:card] = creditcard + else + raise ArgumentError.new("Unhandled payment method #{creditcard.class}.") + end + end + + def add_address(post, options) + return unless post[:card]&.kind_of?(Hash) + if address = options[:billing_address] + post[:card][:addressLine1] = address[:address1] if address[:address1] + post[:card][:addressLine2] = address[:address2] if address[:address2] + post[:card][:addressCountry] = address[:country] if address[:country] + post[:card][:addressZip] = address[:zip] if address[:zip] + post[:card][:addressState] = address[:state] if address[:state] + post[:card][:addressCity] = address[:city] if address[:city] + end + end + + def parse(body) + JSON.parse(body) + end + + def commit(url, parameters = nil, options = {}, method = nil) + response = api_request(url, parameters, options, method) + success = !response.key?('error') + + Response.new(success, + (success ? 'Transaction approved' : response['error']['message']), + response, + test: test?, + authorization: (success ? response['id'] : response['error']['charge']), + error_code: (success ? nil : STANDARD_ERROR_CODE_MAPPING[response['error']['code']]) + ) + end + + def headers(options = {}) + secret_key = options[:secret_key] || @options[:secret_key] + + headers = { + 'Authorization' => 'Basic ' + Base64.encode64(secret_key.to_s + ':').strip, + 'User-Agent' => "SecurionPay/v1 ActiveMerchantBindings/#{ActiveMerchant::VERSION}" + } + headers + end + + def response_error(raw_response) + parse(raw_response) + rescue JSON::ParserError + json_error(raw_response) + end + + def post_data(params) + return nil unless params + + params.map do |key, value| + next if value.blank? + if value.is_a?(Hash) + h = {} + value.each do |k, v| + h["#{key}[#{k}]"] = v unless v.blank? + end + post_data(h) + elsif value.is_a?(Array) + value.map { |v| "#{key}[]=#{CGI.escape(v.to_s)}" }.join('&') + else + "#{key}=#{CGI.escape(value.to_s)}" + end + end.compact.join('&') + end + + def api_request(endpoint, parameters = nil, options = {}, method = nil) + raw_response = response = nil + begin + if method.blank? + raw_response = ssl_post(self.live_url + endpoint, post_data(parameters), headers(options)) + else + raw_response = ssl_request(method, self.live_url + endpoint, post_data(parameters), headers(options)) + end + response = parse(raw_response) + rescue ResponseError => e + raw_response = e.response.body + response = response_error(raw_response) + rescue JSON::ParserError + response = json_error(raw_response) + end + response + end + + def json_error(raw_response) + msg = 'Invalid response received from the SecurionPay API.' + msg += " (The raw response returned by the API was #{raw_response.inspect})" + { + 'error' => { + 'message' => msg + } + } + end + + def test? + (@options[:secret_key]&.include?('_test_')) + end + end + end +end diff --git a/lib/active_merchant/billing/gateways/skip_jack.rb b/lib/active_merchant/billing/gateways/skip_jack.rb index 5450d10fd66..158742d58f7 100644 --- a/lib/active_merchant/billing/gateways/skip_jack.rb +++ b/lib/active_merchant/billing/gateways/skip_jack.rb @@ -1,4 +1,3 @@ -#!ruby19 # encoding: utf-8 module ActiveMerchant #:nodoc: @@ -6,11 +5,11 @@ module Billing #:nodoc: class SkipJackGateway < Gateway API_VERSION = '?.?' - self.live_url = "https://www.skipjackic.com" - self.test_url = "https://developer.skipjackic.com" + self.live_url = 'https://www.skipjackic.com' + self.test_url = 'https://developer.skipjackic.com' - BASIC_PATH = "/scripts/evolvcc.dll" - ADVANCED_PATH = "/evolvcc/evolvcc.aspx" + BASIC_PATH = '/scripts/evolvcc.dll' + ADVANCED_PATH = '/evolvcc/evolvcc.aspx' ACTIONS = { :authorization => 'AuthorizeAPI', @@ -25,35 +24,34 @@ class SkipJackGateway < Gateway CARD_CODE_ERRORS = %w( N S "" ) CARD_CODE_MESSAGES = { - "M" => "Card verification number matched", - "N" => "Card verification number didn't match", - "P" => "Card verification number was not processed", - "S" => "Card verification number should be on card but was not indicated", - "U" => "Issuer was not certified for card verification", - "" => "Transaction failed because incorrect card verification number was entered or no number was entered" + 'M' => 'Card verification number matched', + 'N' => "Card verification number didn't match", + 'P' => 'Card verification number was not processed', + 'S' => 'Card verification number should be on card but was not indicated', + 'U' => 'Issuer was not certified for card verification', + '' => 'Transaction failed because incorrect card verification number was entered or no number was entered' } AVS_ERRORS = %w( A B C E I N O P R W Z ) AVS_MESSAGES = { - "A" => "Street address matches billing information, zip/postal code does not", - "B" => "Street address match for international transaction. Postal code not verified due to incompatible formats", - "C" => "Street address and postal code not verified for internation transaction due to incompatible formats", - "D" => "Street address and postal code match for international transaction", - "E" => "Address verification service error", - "I" => "Address information not verified by international issuer", - "M" => "Street address and postal code match for international transaction", - "N" => "Neither street address nor zip/postal match billing information", - "O" => "Non-US issuer does not participate", - "P" => "Postal codes match for international transaction but street address not verified due to incompatible formats", - "P" => "Address verification not applicable for this transaction", - "R" => "Payment gateway was unavailable or timed out", - "S" => "Address verification service not supported by issuer", - "U" => "Address information is unavailable", - "W" => "9-digit zip/postal code matches billing information, street address does not", - "X" => "Street address and 9-digit zip/postal code matches billing information", - "Y" => "Street address and 5-digit zip/postal code matches billing information", - "Z" => "5-digit zip/postal code matches billing information, street address does not", + 'A' => 'Street address matches billing information, zip/postal code does not', + 'B' => 'Street address match for international transaction. Postal code not verified due to incompatible formats', + 'C' => 'Street address and postal code not verified for internation transaction due to incompatible formats', + 'D' => 'Street address and postal code match for international transaction', + 'E' => 'Address verification service error', + 'I' => 'Address information not verified by international issuer', + 'M' => 'Street address and postal code match for international transaction', + 'N' => 'Neither street address nor zip/postal match billing information', + 'O' => 'Non-US issuer does not participate', + 'P' => 'Postal codes match for international transaction but street address not verified due to incompatible formats', + 'R' => 'Payment gateway was unavailable or timed out', + 'S' => 'Address verification service not supported by issuer', + 'U' => 'Address information is unavailable', + 'W' => '9-digit zip/postal code matches billing information, street address does not', + 'X' => 'Street address and 9-digit zip/postal code matches billing information', + 'Y' => 'Street address and 5-digit zip/postal code matches billing information', + 'Z' => '5-digit zip/postal code matches billing information, street address does not', } CHANGE_STATUS_ERROR_MESSAGES = { @@ -195,7 +193,6 @@ def authorize(money, creditcard, options = {}) end def purchase(money, creditcard, options = {}) - post = {} authorization = authorize(money, creditcard, options) if authorization.success? capture(money, authorization.authorization) @@ -240,7 +237,7 @@ def refund(money, identification, options = {}) end def credit(money, identification, options = {}) - deprecated CREDIT_DEPRECATION_MESSAGE + ActiveMerchant.deprecated CREDIT_DEPRECATION_MESSAGE refund(money, identification, options) end @@ -263,7 +260,7 @@ def add_status_action(post, action) end def commit(action, money, parameters) - response = parse( ssl_post( url_for(action), post_data(action, money, parameters) ), action ) + response = parse(ssl_post(url_for(action), post_data(action, money, parameters)), action) # Pass along the original transaction id in the case an update transaction Response.new(response[:success], message_from(response, action), response, @@ -277,7 +274,7 @@ def commit(action, money, parameters) def url_for(action) result = test? ? self.test_url : self.live_url result += advanced? && action == :authorization ? ADVANCED_PATH : BASIC_PATH - result += "?#{ACTIONS[action]}" + result + "?#{ACTIONS[action]}" end def add_credentials(params, action) @@ -320,7 +317,7 @@ def split_line(line) def authorize_response_map(body) lines = split_lines(body) keys, values = split_line(lines[0]), split_line(lines[1]) - Hash[*(keys.zip(values).flatten)].symbolize_keys + Hash[*keys.zip(values).flatten].symbolize_keys end def parse_authorization_response(body) @@ -335,7 +332,7 @@ def parse_status_response(body, response_keys) keys = [ :szSerialNumber, :szErrorCode, :szNumberRecords] values = split_line(lines[0])[0..2] - result = Hash[*(keys.zip(values).flatten)] + result = Hash[*keys.zip(values).flatten] result[:szErrorMessage] = '' result[:success] = (result[:szErrorCode] == '0') @@ -356,8 +353,8 @@ def parse_status_response(body, response_keys) def post_data(action, money, params = {}) add_credentials(params, action) add_amount(params, action, money) - sorted_params = params.to_a.sort{|a,b| a.to_s <=> b.to_s}.reverse - sorted_params.collect { |key, value| "#{key.to_s}=#{CGI.escape(value.to_s)}" }.join("&") + sorted_params = params.to_a.sort_by(&:to_s).reverse + sorted_params.collect { |key, value| "#{key}=#{CGI.escape(value.to_s)}" }.join('&') end def add_transaction_id(post, transaction_id) @@ -371,7 +368,7 @@ def add_invoice(post, options) post[:OrderDescription] = options[:description] if order_items = options[:items] - post[:OrderString] = order_items.collect { |item| "#{item[:sku]}~#{item[:description].tr('~','-')}~#{item[:declared_value]}~#{item[:quantity]}~#{item[:taxable]}~~~~~~~~#{item[:tax_rate]}~||"}.join + post[:OrderString] = order_items.collect { |item| "#{item[:sku]}~#{item[:description].tr('~', '-')}~#{item[:declared_value]}~#{item[:quantity]}~#{item[:taxable]}~~~~~~~~#{item[:tax_rate]}~||" }.join else post[:OrderString] = '1~None~0.00~0~N~||' end diff --git a/lib/active_merchant/billing/gateways/smart_ps.rb b/lib/active_merchant/billing/gateways/smart_ps.rb index bc1690e9e05..0e1b7c0a699 100644 --- a/lib/active_merchant/billing/gateways/smart_ps.rb +++ b/lib/active_merchant/billing/gateways/smart_ps.rb @@ -23,9 +23,9 @@ def initialize(options = {}) def authorize(money, creditcard, options = {}) post = {} add_invoice(post, options) - add_payment_source(post, creditcard,options) + add_payment_source(post, creditcard, options) add_address(post, options[:billing_address] || options[:address]) - add_address(post, options[:shipping_address], "shipping") + add_address(post, options[:shipping_address], 'shipping') add_customer_data(post, options) add_currency(post, money, options) add_taxes(post, options) @@ -38,11 +38,12 @@ def purchase(money, payment_source, options = {}) add_invoice(post, options) add_payment_source(post, payment_source, options) add_address(post, options[:billing_address] || options[:address]) - add_address(post, options[:shipping_address], "shipping") + add_address(post, options[:shipping_address], 'shipping') add_customer_data(post, options) add_currency(post, money, options) add_taxes(post, options) add_processor(post, options) + add_eci(post, options) commit('sale', money, post) end @@ -64,7 +65,7 @@ def credit(money, payment_source, options = {}) add_payment_source(post, payment_source, options) add_address(post, options[:billing_address] || options[:address]) add_customer_data(post, options) - add_sku(post,options) + add_sku(post, options) add_currency(post, money, options) add_processor(post, options) commit('credit', money, post) @@ -76,13 +77,19 @@ def refund(money, auth, options = {}) commit('refund', money, post) end + def verify(credit_card, options = {}) + MultiResponse.run(:use_first_response) do |r| + r.process { authorize(100, credit_card, options) } + r.process(:ignore_result) { void(r.authorization, options) } + end + end # Update the values (such as CC expiration) stored at # the gateway. The CC number must be supplied in the # CreditCard object. def update(vault_id, creditcard, options = {}) post = {} - post[:customer_vault] = "update_customer" + post[:customer_vault] = 'update_customer' add_customer_vault_id(post, vault_id) add_creditcard(post, creditcard, options) add_address(post, options[:billing_address] || options[:address]) @@ -99,10 +106,9 @@ def amend(auth, options = {}) commit('update', nil, post) end - def delete(vault_id) post = {} - post[:customer_vault] = "delete_customer" + post[:customer_vault] = 'delete_customer' add_customer_vault_id(post, vault_id) commit(nil, nil, post) end @@ -121,6 +127,7 @@ def store(payment_source, options = {}) alias_method :unstore, :delete private + def add_customer_data(post, options) if options.has_key? :email post[:email] = options[:email] @@ -131,17 +138,17 @@ def add_customer_data(post, options) end end - def add_address(post, address,prefix="") - prefix +="_" unless prefix.blank? + def add_address(post, address, prefix='') + prefix +='_' unless prefix.blank? unless address.blank? or address.values.blank? - post[prefix+"address1"] = address[:address1].to_s - post[prefix+"address2"] = address[:address2].to_s unless address[:address2].blank? - post[prefix+"company"] = address[:company].to_s - post[prefix+"phone"] = address[:phone].to_s - post[prefix+"zip"] = address[:zip].to_s - post[prefix+"city"] = address[:city].to_s - post[prefix+"country"] = address[:country].to_s - post[prefix+"state"] = address[:state].blank? ? 'n/a' : address[:state] + post[prefix+'address1'] = address[:address1].to_s + post[prefix+'address2'] = address[:address2].to_s unless address[:address2].blank? + post[prefix+'company'] = address[:company].to_s + post[prefix+'phone'] = address[:phone].to_s + post[prefix+'zip'] = address[:zip].to_s + post[prefix+'city'] = address[:city].to_s + post[prefix+'country'] = address[:country].to_s + post[prefix+'state'] = address[:state].blank? ? 'n/a' : address[:state] end end @@ -175,7 +182,7 @@ def add_customer_vault_id(params, vault_id) def add_creditcard(post, creditcard, options) if options[:store] - post[:customer_vault] = "add_customer" + post[:customer_vault] = 'add_customer' post[:customer_vault_id] = options[:store] unless options[:store] == true end post[:ccnumber] = creditcard.number @@ -187,7 +194,7 @@ def add_creditcard(post, creditcard, options) def add_check(post, check, options) if options[:store] - post[:customer_vault] = "add_customer" + post[:customer_vault] = 'add_customer' post[:customer_vault_id] = options[:store] unless options[:store] == true end @@ -199,18 +206,22 @@ def add_check(post, check, options) post[:account_type] = check.account_type # The customer's type of ACH account end - def add_sku(post,options) - post["product_sku_#"] = options[:sku] || options["product_sku_#"] + def add_sku(post, options) + post['product_sku_#'] = options[:sku] || options['product_sku_#'] end def add_transaction(post, auth) post[:transactionid] = auth end + def add_eci(post, options) + post[:billing_method] = options[:eci] if options[:eci] + end + def parse(body) results = {} body.split(/&/).each do |pair| - key,val = pair.split(/=/) + key, val = pair.split(/=/) results[key] = val end @@ -218,33 +229,31 @@ def parse(body) end def commit(action, money, parameters) - parameters[:amount] = amount(money) if money - response = parse( ssl_post(self.live_url, post_data(action,parameters)) ) - Response.new(response["response"] == "1", message_from(response), response, - :authorization => (response["transactionid"] || response["customer_vault_id"]), + parameters[:amount] = localized_amount(money, parameters[:currency] || default_currency) if money + response = parse(ssl_post(self.live_url, post_data(action, parameters))) + Response.new(response['response'] == '1', message_from(response), response, + :authorization => (response['transactionid'] || response['customer_vault_id']), :test => test?, - :cvv_result => response["cvvresponse"], - :avs_result => { :code => response["avsresponse"] } + :cvv_result => response['cvvresponse'], + :avs_result => { :code => response['avsresponse'] } ) - end def expdate(creditcard) - year = sprintf("%.04i", creditcard.year.to_i) - month = sprintf("%.02i", creditcard.month.to_i) + year = sprintf('%.04i', creditcard.year) + month = sprintf('%.02i', creditcard.month) "#{month}#{year[-2..-1]}" end - def message_from(response) - case response["responsetext"] - when "SUCCESS", "Approved", nil # This is dubious, but responses from UPDATE are nil. - "This transaction has been approved" - when "DECLINE" - "This transaction has been declined" + case response['responsetext'] + when 'SUCCESS', 'Approved', nil # This is dubious, but responses from UPDATE are nil. + 'This transaction has been approved' + when 'DECLINE' + 'This transaction has been declined' else - response["responsetext"] + response['responsetext'] end end @@ -254,19 +263,18 @@ def post_data(action, parameters = {}) post[:password] = @options[:password] post[:type] = action if action - request = post.merge(parameters).map {|key,value| "#{key}=#{CGI.escape(value.to_s)}"}.join("&") + request = post.merge(parameters).map { |key, value| "#{key}=#{CGI.escape(value.to_s)}" }.join('&') request end def determine_funding_source(source) case when source.is_a?(String) then :vault - when CreditCard.card_companies.keys.include?(card_brand(source)) then :credit_card + when CreditCard.card_companies.include?(card_brand(source)) then :credit_card when card_brand(source) == 'check' then :check - else raise ArgumentError, "Unsupported funding source provided" + else raise ArgumentError, 'Unsupported funding source provided' end end end end end - diff --git a/lib/active_merchant/billing/gateways/so_easy_pay.rb b/lib/active_merchant/billing/gateways/so_easy_pay.rb new file mode 100644 index 00000000000..f0fa4523322 --- /dev/null +++ b/lib/active_merchant/billing/gateways/so_easy_pay.rb @@ -0,0 +1,194 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class SoEasyPayGateway < Gateway + self.live_url = self.test_url = 'https://secure.soeasypay.com/gateway.asmx' + self.money_format = :cents + + self.supported_countries = [ + 'US', 'CA', 'AT', 'BE', 'BG', 'HR', 'CY', 'CZ', 'DK', 'EE', + 'FI', 'FR', 'DE', 'GR', 'HU', 'IE', 'IT', 'LV', 'LT', 'LU', + 'MT', 'NL', 'PL', 'PT', 'RO', 'SK', 'SI', 'ES', 'SE', 'GB', + 'IS', 'NO', 'CH' + ] + self.supported_cardtypes = [:visa, :master, :american_express, :discover, :maestro, :jcb, :diners_club] + self.homepage_url = 'http://www.soeasypay.com/' + self.display_name = 'SoEasyPay' + + def initialize(options = {}) + requires!(options, :login, :password) + super + end + + def authorize(money, payment_source, options = {}) + if payment_source.respond_to?(:number) + commit('AuthorizeTransaction', do_authorization(money, payment_source, options), options) + else + commit('ReauthorizeTransaction', do_reauthorization(money, payment_source, options), options) + end + end + + def purchase(money, payment_source, options = {}) + if payment_source.respond_to?(:number) + commit('SaleTransaction', do_sale(money, payment_source, options), options) + else + commit('RebillTransaction', do_rebill(money, payment_source, options), options) + end + end + + def capture(money, authorization, options = {}) + commit('CaptureTransaction', do_capture(money, authorization, options), options) + end + + def refund(money, authorization, options={}) + commit('RefundTransaction', do_refund(money, authorization, options), options) + end + + def void(authorization, options={}) + commit('CancelTransaction', do_void(authorization, options), options) + end + + private + + def do_authorization(money, card, options) + build_soap('AuthorizeTransaction') do |soap| + fill_credentials(soap, options) + fill_order_info(soap, money, options) + fill_cardholder(soap, card, options) + fill_card(soap, card) + end + end + + def do_sale(money, card, options) + build_soap('SaleTransaction') do |soap| + fill_credentials(soap, options) + fill_order_info(soap, money, options) + fill_cardholder(soap, card, options) + fill_card(soap, card) + end + end + + def do_reauthorization(money, authorization, options) + build_soap('ReauthorizeTransaction') do |soap| + fill_credentials(soap, options) + fill_order_info(soap, money, options) + fill_transaction_id(soap, authorization) + end + end + + def do_rebill(money, authorization, options) + build_soap('RebillTransaction') do |soap| + fill_credentials(soap, options) + fill_order_info(soap, money, options) + fill_transaction_id(soap, authorization) + end + end + + def do_capture(money, authorization, options) + build_soap('CaptureTransaction') do |soap| + fill_credentials(soap, options) + fill_order_info(soap, money, options, :no_currency) + fill_transaction_id(soap, authorization) + end + end + + def do_refund(money, authorization, options) + build_soap('RefundTransaction') do |soap| + fill_credentials(soap, options) + fill_order_info(soap, money, options, :no_currency) + fill_transaction_id(soap, authorization) + end + end + + def do_void(authorization, options) + build_soap('CancelTransaction') do |soap| + fill_credentials(soap, options) + fill_transaction_id(soap, authorization) + end + end + + def fill_credentials(soap, options) + soap.tag!('websiteID', @options[:login].to_s) + soap.tag!('password', @options[:password].to_s) + end + + def fill_cardholder(soap, card, options) + ch_info = options[:billing_address] || options[:address] + + soap.tag!('customerIP', options[:ip].to_s) + name = card.name || ch_info[:name] + soap.tag!('cardHolderName', name.to_s) + address = ch_info[:address1] || '' + address << ch_info[:address2] if ch_info[:address2] + soap.tag!('cardHolderAddress', address.to_s) + soap.tag!('cardHolderZipcode', ch_info[:zip].to_s) + soap.tag!('cardHolderCity', ch_info[:city].to_s) + soap.tag!('cardHolderState', ch_info[:state].to_s) + soap.tag!('cardHolderCountryCode', ch_info[:country].to_s) + soap.tag!('cardHolderPhone', ch_info[:phone].to_s) + soap.tag!('cardHolderEmail', options[:email].to_s) + end + + def fill_transaction_id(soap, transaction_id) + soap.tag!('transactionID', transaction_id.to_s) + end + + def fill_card(soap, card) + soap.tag!('cardNumber', card.number.to_s) + soap.tag!('cardSecurityCode', card.verification_value.to_s) + soap.tag!('cardExpireMonth', card.month.to_s.rjust(2, '0')) + soap.tag!('cardExpireYear', card.year.to_s) + end + + def fill_order_info(soap, money, options, skip_currency=false) + soap.tag!('orderID', options[:order_id].to_s) + soap.tag!('orderDescription', "Order #{options[:order_id]}") + soap.tag!('amount', amount(money).to_s) + soap.tag!('currency', (options[:currency] || currency(money)).to_s) unless skip_currency + end + + def parse(response, action) + result = {} + document = REXML::Document.new(response) + response_element = document.root.get_elements("//[@xsi:type='tns:#{action}Response']").first + response_element.elements.each do |element| + result[element.name.underscore] = element.text + end + result + end + + def commit(soap_action, soap, options) + headers = {'SOAPAction' => "\"urn:Interface##{soap_action}\"", + 'Content-Type' => 'text/xml; charset=utf-8'} + response_string = ssl_post(test? ? self.test_url : self.live_url, soap, headers) + response = parse(response_string, soap_action) + return Response.new(response['errorcode'] == '000', + response['errormessage'], + response, + :test => test?, + :authorization => response['transaction_id']) + end + + def build_soap(request) + retval = Builder::XmlMarkup.new(:indent => 2) + retval.instruct!(:xml, :version => '1.0', :encoding => 'utf-8') + retval.tag!('soap:Envelope', { + 'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance', + 'xmlns:xsd' => 'http://www.w3.org/2001/XMLSchema', + 'xmlns:soapenc' => 'http://schemas.xmlsoap.org/soap/encoding/', + 'xmlns:tns' => 'urn:Interface', + 'xmlns:types' => 'urn:Interface/encodedTypes', + 'xmlns:soap' => 'http://schemas.xmlsoap.org/soap/envelope/'}) do + retval.tag!('soap:Body', {'soap:encodingStyle'=>'http://schemas.xmlsoap.org/soap/encoding/'}) do + retval.tag!("tns:#{request}") do + retval.tag!("#{request}Request", {'xsi:type'=>"tns:#{request}Request"}) do + yield retval + end + end + end + end + retval.target! + end + + end + end +end diff --git a/lib/active_merchant/billing/gateways/spreedly_core.rb b/lib/active_merchant/billing/gateways/spreedly_core.rb index 1a07ab0620a..92e3f5ccbcc 100644 --- a/lib/active_merchant/billing/gateways/spreedly_core.rb +++ b/lib/active_merchant/billing/gateways/spreedly_core.rb @@ -35,33 +35,25 @@ def initialize(options = {}) # Public: Run a purchase transaction. # # money - The monetary amount of the transaction in cents. - # payment_method - The CreditCard or the Spreedly payment method token. - # options - A standard ActiveMerchant options hash + # payment_method - The CreditCard or Check or the Spreedly payment method token. + # options - A hash of options: + # :store - Retain the payment method if the purchase + # succeeds. Defaults to false. (optional) def purchase(money, payment_method, options = {}) - if payment_method.is_a?(String) - purchase_with_token(money, payment_method, options) - else - MultiResponse.run do |r| - r.process { save_card(false, payment_method, options) } - r.process { purchase_with_token(money, r.authorization, options) } - end - end + request = build_transaction_request(money, payment_method, options) + commit("gateways/#{options[:gateway_token] || @options[:gateway_token]}/purchase.xml", request) end # Public: Run an authorize transaction. # # money - The monetary amount of the transaction in cents. # payment_method - The CreditCard or the Spreedly payment method token. - # options - A standard ActiveMerchant options hash + # options - A hash of options: + # :store - Retain the payment method if the authorize + # succeeds. Defaults to false. (optional) def authorize(money, payment_method, options = {}) - if payment_method.is_a?(String) - authorize_with_token(money, payment_method, options) - else - MultiResponse.run do |r| - r.process { save_card(false, payment_method, options) } - r.process { authorize_with_token(money, r.authorization, options) } - end - end + request = build_transaction_request(money, payment_method, options) + commit("gateways/#{@options[:gateway_token]}/authorize.xml", request) end def capture(money, authorization, options={}) @@ -75,6 +67,7 @@ def capture(money, authorization, options={}) def refund(money, authorization, options={}) request = build_xml_request('transaction') do |doc| add_invoice(doc, money, options) + add_extra_options(:gateway_specific_fields, doc, options) end commit("transactions/#{authorization}/credit.xml", request) @@ -84,12 +77,30 @@ def void(authorization, options={}) commit("transactions/#{authorization}/void.xml", '') end + # Public: Determine whether a credit card is chargeable card and available for purchases. + # + # payment_method - The CreditCard or the Spreedly payment method token. + # options - A hash of options: + # :store - Retain the payment method if the verify + # succeeds. Defaults to false. (optional) + def verify(payment_method, options = {}) + if payment_method.is_a?(String) + verify_with_token(payment_method, options) + else + MultiResponse.run do |r| + r.process { save_card(options[:store], payment_method, options) } + r.process { verify_with_token(r.authorization, options) } + end + end + end + # Public: Store a credit card in the Spreedly vault and retain it. # # credit_card - The CreditCard to store # options - A standard ActiveMerchant options hash def store(credit_card, options={}) - save_card(true, credit_card, options) + retain = (options.has_key?(:retain) ? options[:retain] : true) + save_card(retain, credit_card, options) end # Public: Redact the CreditCard in Spreedly. This wipes the sensitive @@ -101,42 +112,93 @@ def unstore(authorization, options={}) commit("payment_methods/#{authorization}/redact.xml", '', :put) end + # Public: Get the transaction with the given token. + def find(transaction_token) + commit("transactions/#{transaction_token}.xml", nil, :get) + end + + alias_method :status, :find + + def supports_scrubbing? + true + end + + def scrub(transcript) + transcript. + gsub(%r((Authorization: Basic )\w+), '\1[FILTERED]'). + gsub(%r((<number>).+(</number>)), '\1[FILTERED]\2'). + gsub(%r((<verification_value>).+(</verification_value>)), '\1[FILTERED]\2'). + gsub(%r((<payment_method_token>).+(</payment_method_token>)), '\1[FILTERED]\2') + end + private + def save_card(retain, credit_card, options) request = build_xml_request('payment_method') do |doc| add_credit_card(doc, credit_card, options) - add_data(doc, options) + add_extra_options(:data, doc, options) doc.retained(true) if retain end - commit("payment_methods.xml", request, :post, :payment_method_token) + commit('payment_methods.xml', request, :post, :payment_method_token) end def purchase_with_token(money, payment_method_token, options) - request = auth_purchase_request(money, payment_method_token, options) - commit("gateways/#{@options[:gateway_token]}/purchase.xml", request) + request = build_transaction_request(money, payment_method_token, options) + commit("gateways/#{options[:gateway_token] || @options[:gateway_token]}/purchase.xml", request) end def authorize_with_token(money, payment_method_token, options) - request = auth_purchase_request(money, payment_method_token, options) + request = build_transaction_request(money, payment_method_token, options) commit("gateways/#{@options[:gateway_token]}/authorize.xml", request) end - def auth_purchase_request(money, payment_method_token, options) + def verify_with_token(payment_method_token, options) + request = build_transaction_request(nil, payment_method_token, options) + commit("gateways/#{@options[:gateway_token]}/verify.xml", request) + end + + def build_transaction_request(money, payment_method, options) build_xml_request('transaction') do |doc| add_invoice(doc, money, options) - doc.payment_method_token(payment_method_token) + add_payment_method(doc, payment_method, options) + add_extra_options(:gateway_specific_fields, doc, options) end end def add_invoice(doc, money, options) - doc.amount amount(money) + doc.amount amount(money) unless money.nil? doc.currency_code(options[:currency] || currency(money) || default_currency) + doc.order_id(options[:order_id]) + doc.ip(options[:ip]) if options[:ip] + doc.description(options[:description]) if options[:description] + + if options[:merchant_name_descriptor] + doc.merchant_name_descriptor(options[:merchant_name_descriptor]) + end + if options[:merchant_location_descriptor] + doc.merchant_location_descriptor(options[:merchant_location_descriptor]) + end + end + + def add_payment_method(doc, payment_method, options) + doc.retain_on_success(true) if options[:store] + + if payment_method.is_a?(String) + doc.payment_method_token(payment_method) + elsif payment_method.is_a?(CreditCard) + add_credit_card(doc, payment_method, options) + elsif payment_method.is_a?(Check) + add_bank_account(doc, payment_method, options) + else + raise TypeError, 'Payment method not supported' + end end def add_credit_card(doc, credit_card, options) doc.credit_card do doc.number(credit_card.number) + doc.verification_value(credit_card.verification_value) doc.first_name(credit_card.first_name) doc.last_name(credit_card.last_name) doc.month(credit_card.month) @@ -147,20 +209,32 @@ def add_credit_card(doc, credit_card, options) doc.city(options[:billing_address].try(:[], :city)) doc.state(options[:billing_address].try(:[], :state)) doc.zip(options[:billing_address].try(:[], :zip)) + doc.country(options[:billing_address].try(:[], :country)) end end - def add_data(doc, options) - doc.data do - data_to_doc(doc, options[:data]) + def add_bank_account(doc, bank_account, options) + doc.bank_account do + doc.first_name(bank_account.first_name) + doc.last_name(bank_account.last_name) + doc.bank_routing_number(bank_account.routing_number) + doc.bank_account_number(bank_account.account_number) + doc.bank_account_type(bank_account.account_type) + doc.bank_account_holder_type(bank_account.account_holder_type) end end - def data_to_doc(doc, value) + def add_extra_options(type, doc, options) + doc.send(type) do + extra_options_to_doc(doc, options[type]) + end + end + + def extra_options_to_doc(doc, value) return doc.text value unless value.kind_of? Hash value.each do |k, v| doc.send(k) do - data_to_doc(doc, v) + extra_options_to_doc(doc, v) end end end @@ -170,7 +244,7 @@ def parse(xml) doc = Nokogiri::XML(xml) doc.root.xpath('*').each do |node| - if (node.elements.empty?) + if node.elements.empty? response[node.name.downcase.to_sym] = node.text else node.elements.each do |childnode| @@ -230,4 +304,3 @@ def headers end end end - diff --git a/lib/active_merchant/billing/gateways/stripe.rb b/lib/active_merchant/billing/gateways/stripe.rb index f1feebeadab..ffa022c58d7 100644 --- a/lib/active_merchant/billing/gateways/stripe.rb +++ b/lib/active_merchant/billing/gateways/stripe.rb @@ -2,6 +2,8 @@ module ActiveMerchant #:nodoc: module Billing #:nodoc: + # This gateway uses an older version of the Stripe API. + # To utilize the updated {Payment Intents API}[https://stripe.com/docs/api/payment_intents], integrate with the StripePaymentIntents gateway class StripeGateway < Gateway self.live_url = 'https://api.stripe.com/v1/' @@ -21,14 +23,55 @@ class StripeGateway < Gateway 'unchecked' => 'P' } - self.supported_countries = %w(US CA GB AU IE FR NL BE DE ES) + DEFAULT_API_VERSION = '2015-04-07' + + self.supported_countries = %w(AT AU BE BR CA CH DE DK ES FI FR GB HK IE IT JP LU MX NL NO NZ PT SE SG US) self.default_currency = 'USD' self.money_format = :cents - self.supported_cardtypes = [:visa, :master, :american_express, :discover, :jcb, :diners_club] + self.supported_cardtypes = [:visa, :master, :american_express, :discover, :jcb, :diners_club, :maestro] + self.currencies_without_fractions = %w(BIF CLP DJF GNF JPY KMF KRW MGA PYG RWF VND VUV XAF XOF XPF UGX) self.homepage_url = 'https://stripe.com/' self.display_name = 'Stripe' + STANDARD_ERROR_CODE_MAPPING = { + 'incorrect_number' => STANDARD_ERROR_CODE[:incorrect_number], + 'invalid_number' => STANDARD_ERROR_CODE[:invalid_number], + 'invalid_expiry_month' => STANDARD_ERROR_CODE[:invalid_expiry_date], + 'invalid_expiry_year' => STANDARD_ERROR_CODE[:invalid_expiry_date], + 'invalid_cvc' => STANDARD_ERROR_CODE[:invalid_cvc], + 'expired_card' => STANDARD_ERROR_CODE[:expired_card], + 'incorrect_cvc' => STANDARD_ERROR_CODE[:incorrect_cvc], + 'incorrect_zip' => STANDARD_ERROR_CODE[:incorrect_zip], + 'card_declined' => STANDARD_ERROR_CODE[:card_declined], + 'call_issuer' => STANDARD_ERROR_CODE[:call_issuer], + 'processing_error' => STANDARD_ERROR_CODE[:processing_error], + 'incorrect_pin' => STANDARD_ERROR_CODE[:incorrect_pin], + 'test_mode_live_card' => STANDARD_ERROR_CODE[:test_mode_live_card], + 'pickup_card' => STANDARD_ERROR_CODE[:pickup_card] + } + + BANK_ACCOUNT_HOLDER_TYPE_MAPPING = { + 'personal' => 'individual', + 'business' => 'company', + } + + MINIMUM_AUTHORIZE_AMOUNTS = { + 'USD' => 100, + 'CAD' => 100, + 'GBP' => 60, + 'EUR' => 100, + 'DKK' => 500, + 'NOK' => 600, + 'SEK' => 600, + 'CHF' => 100, + 'AUD' => 100, + 'JPY' => 100, + 'MXN' => 2000, + 'SGD' => 100, + 'HKD' => 800 + } + def initialize(options = {}) requires!(options, :login) @api_key = options[:login] @@ -36,11 +79,22 @@ def initialize(options = {}) super end - def authorize(money, creditcard, options = {}) - post = create_post_for_auth_or_purchase(money, creditcard, options) - post[:capture] = "false" - - commit(:post, 'charges', post, generate_meta(options)) + def authorize(money, payment, options = {}) + MultiResponse.run do |r| + if payment.is_a?(ApplePayPaymentToken) + r.process { tokenize_apple_pay_token(payment) } + payment = StripePaymentToken.new(r.params['token']) if r.success? + end + r.process do + post = create_post_for_auth_or_purchase(money, payment, options) + if emv_payment?(payment) + add_application_fee(post, options) + else + post[:capture] = 'false' + end + commit(:post, 'charges', post, options) + end + end.responses.last end # To create a charge on a card or a token, call @@ -50,102 +104,331 @@ def authorize(money, creditcard, options = {}) # To create a charge on a customer, call # # purchase(money, nil, { :customer => id, ... }) - def purchase(money, creditcard, options = {}) - post = create_post_for_auth_or_purchase(money, creditcard, options) + def purchase(money, payment, options = {}) + if ach?(payment) + direct_bank_error = 'Direct bank account transactions are not supported. Bank accounts must be stored and verified before use.' + return Response.new(false, direct_bank_error) + end - commit(:post, 'charges', post, generate_meta(options)) + MultiResponse.run do |r| + if payment.is_a?(ApplePayPaymentToken) + r.process { tokenize_apple_pay_token(payment) } + payment = StripePaymentToken.new(r.params['token']) if r.success? + end + r.process do + post = create_post_for_auth_or_purchase(money, payment, options) + post[:card][:processing_method] = 'quick_chip' if quickchip_payment?(payment) + commit(:post, 'charges', post, options) + end + end.responses.last end def capture(money, authorization, options = {}) - post = {:amount => amount(money)} - add_application_fee(post, options) + post = {} - commit(:post, "charges/#{CGI.escape(authorization)}/capture", post) + if emv_tc_response = options.delete(:icc_data) + post[:card] = { emv_approval_data: emv_tc_response } + commit(:post, "charges/#{CGI.escape(authorization)}", post, options) + else + add_application_fee(post, options) + add_amount(post, money, options) + add_exchange_rate(post, options) + commit(:post, "charges/#{CGI.escape(authorization)}/capture", post, options) + end end def void(identification, options = {}) - commit(:post, "charges/#{CGI.escape(identification)}/refund", {}) + post = {} + post[:metadata] = options[:metadata] if options[:metadata] + post[:reason] = options[:reason] if options[:reason] + post[:expand] = [:charge] + commit(:post, "charges/#{CGI.escape(identification)}/refunds", post, options) end def refund(money, identification, options = {}) - post = {:amount => amount(money)} - commit_options = generate_meta(options) + post = {} + add_amount(post, money, options) + post[:refund_application_fee] = true if options[:refund_application_fee] + post[:reverse_transfer] = options[:reverse_transfer] if options[:reverse_transfer] + post[:metadata] = options[:metadata] if options[:metadata] + post[:reason] = options[:reason] if options[:reason] + post[:expand] = [:charge] + + response = commit(:post, "charges/#{CGI.escape(identification)}/refunds", post, options) + + if response.success? && options[:refund_fee_amount] && options[:refund_fee_amount].to_s != '0' + charge = api_request(:get, "charges/#{CGI.escape(identification)}", nil, options) + + if application_fee = charge['application_fee'] + fee_refund_options = { + currency: options[:currency], # currency isn't used by Stripe here, but we need it for #add_amount + key: @fee_refund_api_key + } + refund_application_fee(options[:refund_fee_amount].to_i, application_fee, fee_refund_options) + end + end - MultiResponse.run(:first) do |r| - r.process { commit(:post, "charges/#{CGI.escape(identification)}/refund", post, commit_options) } + response + end + + def verify(payment, options = {}) + MultiResponse.run(:use_first_response) do |r| + r.process { authorize(auth_minimum_amount(options), payment, options) } + options[:idempotency_key] = nil + r.process(:ignore_result) { void(r.authorization, options) } + end + end + + def refund_application_fee(money, identification, options = {}) + post = {} + add_amount(post, money, options) + commit(:post, "application_fees/#{CGI.escape(identification)}/refunds", post, options) + end + + # Note: creating a new credit card will not change the customer's existing default credit card (use :set_default => true) + def store(payment, options = {}) + params = {} + post = {} - return r unless options[:refund_fee_amount] + if payment.is_a?(ApplePayPaymentToken) + token_exchange_response = tokenize_apple_pay_token(payment) + params = { card: token_exchange_response.params['token']['id'] } if token_exchange_response.success? + elsif payment.is_a?(StripePaymentToken) + add_payment_token(params, payment, options) + elsif payment.is_a?(Check) + bank_token_response = tokenize_bank_account(payment) + return bank_token_response unless bank_token_response.success? + params = { source: bank_token_response.params['token']['id'] } + else + add_creditcard(params, payment, options) + end + + post[:validate] = options[:validate] unless options[:validate].nil? + post[:description] = options[:description] if options[:description] + post[:email] = options[:email] if options[:email] + + if options[:account] + add_external_account(post, params, payment) + commit(:post, "accounts/#{CGI.escape(options[:account])}/external_accounts", post, options) + elsif options[:customer] + MultiResponse.run(:first) do |r| + # The /cards endpoint does not update other customer parameters. + r.process { commit(:post, "customers/#{CGI.escape(options[:customer])}/cards", params, options) } + + if options[:set_default] and r.success? and !r.params['id'].blank? + post[:default_card] = r.params['id'] + end - r.process { fetch_application_fees(identification, commit_options) } - r.process { refund_application_fee(options[:refund_fee_amount], application_fee_from_response(r), commit_options) } + if post.count > 0 + r.process { update_customer(options[:customer], post) } + end + end + else + commit(:post, 'customers', post.merge(params), options) end end - def application_fee_from_response(response) - return unless response.success? + def update(customer_id, card_id, options = {}) + commit(:post, "customers/#{CGI.escape(customer_id)}/cards/#{CGI.escape(card_id)}", options, options) + end - application_fees = response.params["data"].select { |fee| fee["object"] == "application_fee" } - application_fees.first["id"] unless application_fees.empty? + def update_customer(customer_id, options = {}) + commit(:post, "customers/#{CGI.escape(customer_id)}", options, options) end - def refund_application_fee(money, identification, options = {}) - return Response.new(false, "Application fee id could not be found") unless identification + def unstore(identification, options = {}, deprecated_options = {}) + customer_id, card_id = identification.split('|') - post = {:amount => amount(money)} - options.merge!(:key => @fee_refund_api_key) + if options.kind_of?(String) + ActiveMerchant.deprecated 'Passing the card_id as the 2nd parameter is deprecated. The response authorization includes both the customer_id and the card_id.' + card_id ||= options + options = deprecated_options + end - commit(:post, "application_fees/#{CGI.escape(identification)}/refund", post, options) + commit(:delete, "customers/#{CGI.escape(customer_id)}/cards/#{CGI.escape(card_id)}", nil, options) end - def store(creditcard, options = {}) - post = {} - add_creditcard(post, creditcard, options) - post[:description] = options[:description] - post[:email] = options[:email] + def tokenize_apple_pay_token(apple_pay_payment_token, options = {}) + token_response = api_request(:post, "tokens?pk_token=#{CGI.escape(apple_pay_payment_token.payment_data.to_json)}") + success = !token_response.key?('error') - path = if options[:customer] - "customers/#{CGI.escape(options[:customer])}" + if success && token_response.key?('id') + Response.new(success, nil, token: token_response) else - 'customers' + Response.new(success, token_response['error']['message']) + end + end + + def verify_credentials + begin + ssl_get(live_url + 'charges/nonexistent', headers) + rescue ResponseError => e + return false if e.response.code.to_i == 401 end - commit(:post, path, post, generate_meta(options)) + true + end + + def supports_scrubbing? + true end - def update(customer_id, creditcard, options = {}) - options = options.merge(:customer => customer_id) - store(creditcard, options) + def scrub(transcript) + transcript. + gsub(%r((Authorization: Basic )\w+), '\1[FILTERED]'). + gsub(%r((&?three_d_secure\[cryptogram\]=)[\w=]*(&?)), '\1[FILTERED]\2'). + gsub(%r((card\[cryptogram\]=)[^&]+(&?)), '\1[FILTERED]\2'). + gsub(%r((card\[cvc\]=)\d+), '\1[FILTERED]'). + gsub(%r((card\[emv_approval_data\]=)[^&]+(&?)), '\1[FILTERED]\2'). + gsub(%r((card\[emv_auth_data\]=)[^&]+(&?)), '\1[FILTERED]\2'). + gsub(%r((card\[encrypted_pin\]=)[^&]+(&?)), '\1[FILTERED]\2'). + gsub(%r((card\[encrypted_pin_key_id\]=)[\w=]+(&?)), '\1[FILTERED]\2'). + gsub(%r((card\[number\]=)\d+), '\1[FILTERED]'). + gsub(%r((card\[swipe_data\]=)[^&]+(&?)), '\1[FILTERED]\2') end - def unstore(customer_id, options = {}) - commit(:delete, "customers/#{CGI.escape(customer_id)}", nil, generate_meta(options)) + def supports_network_tokenization? + true end private - def create_post_for_auth_or_purchase(money, creditcard, options) + class StripePaymentToken < PaymentToken + def type + 'stripe' + end + end + + def create_source(money, payment, type, options = {}) post = {} - add_amount(post, money, options) - add_creditcard(post, creditcard, options) - add_customer(post, options) - add_customer_data(post,options) - post[:description] = options[:description] || options[:email] - add_flags(post, options) + add_amount(post, money, options, true) + post[:type] = type + if type == 'card' + add_creditcard(post, payment, options, true) + add_source_owner(post, payment, options) + elsif type == 'three_d_secure' + post[:three_d_secure] = {card: payment} + post[:redirect] = {return_url: options[:redirect_url]} + end + commit(:post, 'sources', post, options) + end + + def show_source(source_id, options) + commit(:get, "sources/#{source_id}", nil, options) + end + + def create_webhook_endpoint(options, events) + post = {} + post[:url] = options[:callback_url] + post[:enabled_events] = events + post[:connect] = true if options[:stripe_account] + options.delete(:stripe_account) + commit(:post, 'webhook_endpoints', post, options) + end + + def delete_webhook_endpoint(options) + commit(:delete, "webhook_endpoints/#{options[:webhook_id]}", {}, options) + end + + def show_webhook_endpoint(options) + options.delete(:stripe_account) + commit(:get, "webhook_endpoints/#{options[:webhook_id]}", nil, options) + end + + def list_webhook_endpoints(options) + params = {} + params[:limit] = options[:limit] if options[:limit] + options.delete(:stripe_account) + commit(:get, "webhook_endpoints?#{post_data(params)}", nil, options) + end + + def create_post_for_auth_or_purchase(money, payment, options) + post = {} + + if payment.is_a?(StripePaymentToken) + add_payment_token(post, payment, options) + else + add_creditcard(post, payment, options) + end + + add_charge_details(post, money, payment, options) + post + end + + # Used internally by Spreedly to populate the charge object for 3DS 1.0 transactions + def add_charge_details(post, money, payment, options) + if emv_payment?(payment) + add_statement_address(post, options) + add_emv_metadata(post, payment) + else + add_amount(post, money, options, true) + add_customer_data(post, options) + post[:description] = options[:description] + post[:statement_descriptor] = options[:statement_description] + post[:receipt_email] = options[:receipt_email] if options[:receipt_email] + add_customer(post, payment, options) + add_flags(post, options) + end + + add_metadata(post, options) add_application_fee(post, options) + add_exchange_rate(post, options) + add_destination(post, options) + add_level_three(post, options) post end - def add_amount(post, money, options) - post[:amount] = amount(money) - post[:currency] = (options[:currency] || currency(money)).downcase + def add_amount(post, money, options, include_currency = false) + currency = options[:currency] || currency(money) + post[:amount] = localized_amount(money, currency) + post[:currency] = currency.downcase if include_currency end def add_application_fee(post, options) post[:application_fee] = options[:application_fee] if options[:application_fee] end + def add_exchange_rate(post, options) + post[:exchange_rate] = options[:exchange_rate] if options[:exchange_rate] + end + + def add_destination(post, options) + if options[:destination] + post[:destination] = {} + post[:destination][:account] = options[:destination] + post[:destination][:amount] = options[:destination_amount] if options[:destination_amount] + end + end + + def add_level_three(post, options) + level_three = {} + + copy_when_present(level_three, [:merchant_reference], options) + copy_when_present(level_three, [:customer_reference], options) + copy_when_present(level_three, [:shipping_address_zip], options) + copy_when_present(level_three, [:shipping_from_zip], options) + copy_when_present(level_three, [:shipping_amount], options) + copy_when_present(level_three, [:line_items], options) + + unless level_three.empty? + post[:level3] = level_three + end + end + + def add_expand_parameters(post, options) + post[:expand] ||= [] + post[:expand].concat(Array.wrap(options[:expand]).map(&:to_sym)).uniq! + end + + def add_external_account(post, card_params, payment) + external_account = {} + external_account[:object] ='card' + external_account[:currency] = (options[:currency] || currency(payment)).downcase + post[:external_account] = external_account.merge(card_params[:card]) + end + def add_customer_data(post, options) - metadata_options = [:description,:browser_ip,:user_agent,:referrer] + metadata_options = [:description, :ip, :user_agent, :referrer] post.update(options.slice(*metadata_options)) post[:external_id] = options[:order_id] @@ -153,7 +436,7 @@ def add_customer_data(post, options) end def add_address(post, options) - return unless post[:card] && post[:card].kind_of?(Hash) + return unless post[:card]&.kind_of?(Hash) if address = options[:billing_address] || options[:address] post[:card][:address_line1] = address[:address1] if address[:address1] post[:card][:address_line2] = address[:address2] if address[:address2] @@ -164,43 +447,116 @@ def add_address(post, options) end end - def add_creditcard(post, creditcard, options) + def add_statement_address(post, options) + return unless statement_address = options[:statement_address] + return unless [:address1, :city, :zip, :state].all? { |key| statement_address[key].present? } + + post[:statement_address] = {} + post[:statement_address][:line1] = statement_address[:address1] + post[:statement_address][:line2] = statement_address[:address2] if statement_address[:address2].present? + post[:statement_address][:city] = statement_address[:city] + post[:statement_address][:postal_code] = statement_address[:zip] + post[:statement_address][:state] = statement_address[:state] + end + + def add_creditcard(post, creditcard, options, use_sources = false) card = {} - if creditcard.respond_to?(:number) + if emv_payment?(creditcard) + add_emv_creditcard(post, creditcard.icc_data) + post[:card][:read_method] = 'contactless' if creditcard.read_method == 'contactless' + post[:card][:read_method] = 'contactless_magstripe_mode' if creditcard.read_method == 'contactless_magstripe' + if creditcard.encrypted_pin_cryptogram.present? && creditcard.encrypted_pin_ksn.present? + post[:card][:encrypted_pin] = creditcard.encrypted_pin_cryptogram + post[:card][:encrypted_pin_key_id] = creditcard.encrypted_pin_ksn + end + elsif creditcard.respond_to?(:number) if creditcard.respond_to?(:track_data) && creditcard.track_data.present? card[:swipe_data] = creditcard.track_data + if creditcard.respond_to?(:read_method) + card[:fallback_reason] = 'no_chip' if creditcard.read_method == 'fallback_no_chip' + card[:fallback_reason] = 'chip_error' if creditcard.read_method == 'fallback_chip_error' + card[:read_method] = 'contactless_magstripe_mode' if creditcard.read_method == 'contactless_magstripe' + end else card[:number] = creditcard.number card[:exp_month] = creditcard.month card[:exp_year] = creditcard.year card[:cvc] = creditcard.verification_value if creditcard.verification_value? - card[:name] = creditcard.name if creditcard.name + card[:name] = creditcard.name if creditcard.name && !use_sources end + if creditcard.is_a?(NetworkTokenizationCreditCard) + card[:cryptogram] = creditcard.payment_cryptogram + card[:eci] = creditcard.eci.rjust(2, '0') if creditcard.eci =~ /^[0-9]+$/ + card[:tokenization_method] = creditcard.source.to_s + end post[:card] = card - add_address(post, options) + + add_address(post, options) unless use_sources elsif creditcard.kind_of?(String) if options[:track_data] card[:swipe_data] = options[:track_data] + elsif creditcard.include?('|') + customer_id, card_id = creditcard.split('|') + card = card_id + post[:customer] = customer_id else - card[:number] = creditcard + card = creditcard end post[:card] = card end end - def add_customer(post, options) - post[:customer] = options[:customer] if options[:customer] && post[:card].blank? + def add_emv_creditcard(post, icc_data, options = {}) + post[:card] = { emv_auth_data: icc_data } + end + + def add_payment_token(post, token, options = {}) + post[:card] = token.payment_data['id'] + end + + def add_customer(post, payment, options) + if options[:customer] && !payment.respond_to?(:number) + ActiveMerchant.deprecated 'Passing the customer in the options is deprecated. Just use the response.authorization instead.' + post[:customer] = options[:customer] + end end def add_flags(post, options) post[:uncaptured] = true if options[:uncaptured] + post[:recurring] = true if options[:eci] == 'recurring' || options[:recurring] end - def fetch_application_fees(identification, options = {}) - options.merge!(:key => @fee_refund_api_key) + def add_metadata(post, options = {}) + post[:metadata] ||= {} + post[:metadata].merge!(options[:metadata]) if options[:metadata] + post[:metadata][:email] = options[:email] if options[:email] + post[:metadata][:order_id] = options[:order_id] if options[:order_id] + post.delete(:metadata) if post[:metadata].empty? + end - commit(:get, "application_fees?charge=#{identification}", nil, options) + def add_emv_metadata(post, creditcard) + post[:metadata] ||= {} + post[:metadata][:card_read_method] = creditcard.read_method if creditcard.respond_to?(:read_method) + end + + def add_source_owner(post, creditcard, options) + post[:owner] = {} + post[:owner][:name] = creditcard.name if creditcard.name + post[:owner][:email] = options[:email] if options[:email] + + if address = options[:billing_address] || options[:address] + owner_address = {} + owner_address[:line1] = address[:address1] if address[:address1] + owner_address[:line2] = address[:address2] if address[:address2] + owner_address[:country] = address[:country] if address[:country] + owner_address[:postal_code] = address[:zip] if address[:zip] + owner_address[:state] = address[:state] if address[:state] + owner_address[:city] = address[:city] if address[:city] + + post[:owner][:phone] = address[:phone] if address[:phone] + post[:owner][:address] = owner_address + end end def parse(body) @@ -209,89 +565,226 @@ def parse(body) def post_data(params) return nil unless params + flatten_params([], params).join('&') + end - params.map do |key, value| - next if value.blank? + def flatten_params(flattened, params, prefix = nil) + params.each do |key, value| + next if value != false && value.blank? + flattened_key = prefix.nil? ? key : "#{prefix}[#{key}]" if value.is_a?(Hash) - h = {} - value.each do |k, v| - h["#{key}[#{k}]"] = v unless v.blank? - end - post_data(h) + flatten_params(flattened, value, flattened_key) + elsif value.is_a?(Array) + flatten_array(flattened, value, flattened_key) else - "#{key}=#{CGI.escape(value.to_s)}" + flattened << "#{flattened_key}=#{CGI.escape(value.to_s)}" end - end.compact.join("&") + end + flattened end - def generate_meta(options) - {:meta => {:ip => options[:ip]}} + def flatten_array(flattened, array, prefix) + array.each_with_index do |item, idx| + key = "#{prefix}[#{idx}]" + if item.is_a?(Hash) + flatten_params(flattened, item, key) + elsif item.is_a?(Array) + flatten_array(flattened, item, key) + else + flattened << "#{key}=#{CGI.escape(item.to_s)}" + end + end end def headers(options = {}) - @@ua ||= JSON.dump({ - :bindings_version => ActiveMerchant::VERSION, - :lang => 'ruby', - :lang_version => "#{RUBY_VERSION} p#{RUBY_PATCHLEVEL} (#{RUBY_RELEASE_DATE})", - :platform => RUBY_PLATFORM, - :publisher => 'active_merchant', - :uname => (RUBY_PLATFORM =~ /linux|darwin/i ? `uname -a 2>/dev/null`.strip : nil) - }) - key = options[:key] || @api_key - - { - "Authorization" => "Basic " + Base64.encode64(key.to_s + ":").strip, - "User-Agent" => "Stripe/v1 ActiveMerchantBindings/#{ActiveMerchant::VERSION}", - "X-Stripe-Client-User-Agent" => @@ua, - "X-Stripe-Client-User-Metadata" => options[:meta].to_json + idempotency_key = options[:idempotency_key] + + headers = { + 'Authorization' => 'Basic ' + Base64.encode64(key.to_s + ':').strip, + 'User-Agent' => "Stripe/v1 ActiveMerchantBindings/#{ActiveMerchant::VERSION}", + 'Stripe-Version' => api_version(options), + 'X-Stripe-Client-User-Agent' => stripe_client_user_agent(options), + 'X-Stripe-Client-User-Metadata' => {:ip => options[:ip]}.to_json } + headers['Idempotency-Key'] = idempotency_key if idempotency_key + headers['Stripe-Account'] = options[:stripe_account] if options[:stripe_account] + headers end - def commit(method, url, parameters=nil, options = {}) + def stripe_client_user_agent(options) + return user_agent unless options[:application] + JSON.dump(JSON.parse(user_agent).merge!({application: options[:application]})) + end + + def api_version(options) + options[:version] || @options[:version] || self.class::DEFAULT_API_VERSION + end + + def api_request(method, endpoint, parameters = nil, options = {}) raw_response = response = nil - success = false begin - raw_response = ssl_request(method, self.live_url + url, post_data(parameters), headers(options)) + raw_response = ssl_request(method, self.live_url + endpoint, post_data(parameters), headers(options)) response = parse(raw_response) - success = !response.key?("error") rescue ResponseError => e raw_response = e.response.body response = response_error(raw_response) rescue JSON::ParserError response = json_error(raw_response) end + response + end + + def commit(method, url, parameters = nil, options = {}) + add_expand_parameters(parameters, options) if parameters + response = api_request(method, url, parameters, options) + response['webhook_id'] = options[:webhook_id] if options[:webhook_id] + success = success_from(response) - card = response["card"] || response["active_card"] || {} + card = card_from_response(response) avs_code = AVS_CODE_TRANSLATOR["line1: #{card["address_line1_check"]}, zip: #{card["address_zip_check"]}"] - cvc_code = CVC_CODE_TRANSLATOR[card["cvc_check"]] + cvc_code = CVC_CODE_TRANSLATOR[card['cvc_check']] + Response.new(success, - success ? "Transaction approved" : response["error"]["message"], + message_from(success, response), response, - :test => response.has_key?("livemode") ? !response["livemode"] : false, - :authorization => response["id"], + :test => response_is_test?(response), + :authorization => authorization_from(success, url, method, response), :avs_result => { :code => avs_code }, - :cvv_result => cvc_code + :cvv_result => cvc_code, + :emv_authorization => emv_authorization_from_response(response), + :error_code => success ? nil : error_code_from(response) ) end - def response_error(raw_response) - begin - parse(raw_response) - rescue JSON::ParserError - json_error(raw_response) + def authorization_from(success, url, method, response) + return response.fetch('error', {})['charge'] unless success + + if url == 'customers' + [response['id'], response.dig('sources', 'data').first&.dig('id')].join('|') + elsif method == :post && (url.match(/customers\/.*\/cards/) || url.match(/payment_methods\/.*\/attach/)) + [response['customer'], response['id']].join('|') + else + response['id'] end end + def message_from(success, response) + success ? 'Transaction approved' : response.fetch('error', {'message' => 'No error details'})['message'] + end + + def success_from(response) + !response.key?('error') && response['status'] != 'failed' + end + + def response_error(raw_response) + parse(raw_response) + rescue JSON::ParserError + json_error(raw_response) + end + def json_error(raw_response) msg = 'Invalid response received from the Stripe API. Please contact support@stripe.com if you continue to receive this message.' msg += " (The raw response returned by the API was #{raw_response.inspect})" { - "error" => { - "message" => msg + 'error' => { + 'message' => msg } } end + + def response_is_test?(response) + if response.has_key?('livemode') + !response['livemode'] + elsif response['charge'].is_a?(Hash) && response['charge'].has_key?('livemode') + !response['charge']['livemode'] + else + false + end + end + + def emv_payment?(payment) + payment.respond_to?(:emv?) && payment.emv? + end + + def quickchip_payment?(payment) + payment.respond_to?(:read_method) && payment.read_method == 'contact_quickchip' + end + + def card_from_response(response) + response['card'] || response['active_card'] || response['source'] || {} + end + + def emv_authorization_from_response(response) + return response['error']['emv_auth_data'] if response['error'] + + card_from_response(response)['emv_auth_data'] + end + + def error_code_from(response) + return STANDARD_ERROR_CODE_MAPPING['processing_error'] unless response['error'] + + code = response['error']['code'] + decline_code = response['error']['decline_code'] if code == 'card_declined' + + error_code = STANDARD_ERROR_CODE_MAPPING[decline_code] + error_code ||= STANDARD_ERROR_CODE_MAPPING[code] + error_code + end + + def tokenize_bank_account(bank_account, options = {}) + account_holder_type = BANK_ACCOUNT_HOLDER_TYPE_MAPPING[bank_account.account_holder_type] + + post = { + bank_account: { + account_number: bank_account.account_number, + country: 'US', + currency: 'usd', + routing_number: bank_account.routing_number, + name: bank_account.name, + account_holder_type: account_holder_type, + } + } + + token_response = api_request(:post, "tokens?#{post_data(post)}") + success = token_response['error'].nil? + + if success && token_response['id'] + Response.new(success, nil, token: token_response) + else + Response.new(success, token_response['error']['message']) + end + end + + def ach?(payment_method) + case payment_method + when String, nil + false + else + card_brand(payment_method) == 'check' + end + end + + def auth_minimum_amount(options) + return 100 unless options[:currency] + return MINIMUM_AUTHORIZE_AMOUNTS[options[:currency].upcase] || 100 + end + + def copy_when_present(dest, dest_path, source, source_path = nil) + source_path ||= dest_path + source_path.each do |key| + return nil unless source[key] + source = source[key] + end + + if source + dest_path.first(dest_path.size - 1).each do |key| + dest[key] ||= {} + dest = dest[key] + end + dest[dest_path.last] = source + end + end end end end diff --git a/lib/active_merchant/billing/gateways/stripe_payment_intents.rb b/lib/active_merchant/billing/gateways/stripe_payment_intents.rb new file mode 100644 index 00000000000..6469e671113 --- /dev/null +++ b/lib/active_merchant/billing/gateways/stripe_payment_intents.rb @@ -0,0 +1,267 @@ +require 'active_support/core_ext/hash/slice' + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + # This gateway uses the current Stripe {Payment Intents API}[https://stripe.com/docs/api/payment_intents]. + # For the legacy API, see the Stripe gateway + class StripePaymentIntentsGateway < StripeGateway + + self.supported_countries = %w(AT AU BE BR CA CH DE DK ES FI FR GB HK IE IT JP LU MX NL NO NZ PT SE SG US) + + ALLOWED_METHOD_STATES = %w[automatic manual].freeze + ALLOWED_CANCELLATION_REASONS = %w[duplicate fraudulent requested_by_customer abandoned].freeze + CREATE_INTENT_ATTRIBUTES = %i[description statement_descriptor receipt_email save_payment_method] + CONFIRM_INTENT_ATTRIBUTES = %i[receipt_email return_url save_payment_method setup_future_usage off_session] + UPDATE_INTENT_ATTRIBUTES = %i[description statement_descriptor receipt_email setup_future_usage] + DEFAULT_API_VERSION = '2019-05-16' + + def create_intent(money, payment_method, options = {}) + post = {} + add_amount(post, money, options, true) + add_capture_method(post, options) + add_confirmation_method(post, options) + add_customer(post, options) + add_payment_method_token(post, payment_method, options) + add_metadata(post, options) + add_return_url(post, options) + add_connected_account(post, options) + add_shipping_address(post, options) + setup_future_usage(post, options) + add_exemption(post, options) + + CREATE_INTENT_ATTRIBUTES.each do |attribute| + add_whitelisted_attribute(post, options, attribute) + end + + commit(:post, 'payment_intents', post, options) + end + + def show_intent(intent_id, options) + commit(:get, "payment_intents/#{intent_id}", nil, options) + end + + def confirm_intent(intent_id, payment_method, options = {}) + post = {} + add_payment_method_token(post, payment_method, options) + CONFIRM_INTENT_ATTRIBUTES.each do |attribute| + add_whitelisted_attribute(post, options, attribute) + end + + commit(:post, "payment_intents/#{intent_id}/confirm", post, options) + end + + def create_payment_method(payment_method, options = {}) + post = {} + post[:type] = 'card' + post[:card] = {} + post[:card][:number] = payment_method.number + post[:card][:exp_month] = payment_method.month + post[:card][:exp_year] = payment_method.year + post[:card][:cvc] = payment_method.verification_value if payment_method.verification_value + add_billing_address(post, options) + + commit(:post, 'payment_methods', post, options) + end + + def update_intent(money, intent_id, payment_method, options = {}) + post = {} + post[:amount] = money if money + + add_payment_method_token(post, payment_method, options) + add_payment_method_types(post, options) + add_customer(post, options) + add_metadata(post, options) + add_shipping_address(post, options) + add_connected_account(post, options) + + UPDATE_INTENT_ATTRIBUTES.each do |attribute| + add_whitelisted_attribute(post, options, attribute) + end + + commit(:post, "payment_intents/#{intent_id}", post, options) + end + + def authorize(money, payment_method, options = {}) + create_intent(money, payment_method, options.merge!(confirm: true, capture_method: 'manual')) + end + + def purchase(money, payment_method, options = {}) + create_intent(money, payment_method, options.merge!(confirm: true, capture_method: 'automatic')) + end + + def capture(money, intent_id, options = {}) + post = {} + post[:amount_to_capture] = money + add_connected_account(post, options) + commit(:post, "payment_intents/#{intent_id}/capture", post, options) + end + + def void(intent_id, options = {}) + post = {} + post[:cancellation_reason] = options[:cancellation_reason] if ALLOWED_CANCELLATION_REASONS.include?(options[:cancellation_reason]) + commit(:post, "payment_intents/#{intent_id}/cancel", post, options) + end + + def refund(money, intent_id, options = {}) + intent = commit(:get, "payment_intents/#{intent_id}", nil, options) + charge_id = intent.params.dig('charges', 'data')[0].dig('id') + super(money, charge_id, options) + end + + # Note: Not all payment methods are currently supported by the {Payment Methods API}[https://stripe.com/docs/payments/payment-methods] + # Current implementation will create a PaymentMethod object if the method is a token or credit card + # All other types will default to legacy Stripe store + def store(payment_method, options = {}) + params = {} + post = {} + + # If customer option is provided, create a payment method and attach to customer id + # Otherwise, create a customer, then attach + if payment_method.is_a?(StripePaymentToken) || payment_method.is_a?(ActiveMerchant::Billing::CreditCard) + add_payment_method_token(params, payment_method, options) + if options[:customer] + customer_id = options[:customer] + else + post[:validate] = options[:validate] unless options[:validate].nil? + post[:description] = options[:description] if options[:description] + post[:email] = options[:email] if options[:email] + customer = commit(:post, 'customers', post, options) + customer_id = customer.params['id'] + end + commit(:post, "payment_methods/#{params[:payment_method]}/attach", { customer: customer_id }, options) + else + super(payment, options) + end + end + + def unstore(identification, options = {}, deprecated_options = {}) + if identification.include?('pm_') + _, payment_method = identification.split('|') + commit(:post, "payment_methods/#{payment_method}/detach", nil, options) + else + super(identification, options, deprecated_options) + end + end + + private + + def add_whitelisted_attribute(post, options, attribute) + post[attribute] = options[attribute] if options[attribute] + post + end + + def add_capture_method(post, options) + capture_method = options[:capture_method].to_s + post[:capture_method] = capture_method if ALLOWED_METHOD_STATES.include?(capture_method) + post + end + + def add_confirmation_method(post, options) + confirmation_method = options[:confirmation_method].to_s + post[:confirmation_method] = confirmation_method if ALLOWED_METHOD_STATES.include?(confirmation_method) + post + end + + def add_customer(post, options) + customer = options[:customer].to_s + post[:customer] = customer if customer.start_with?('cus_') + post + end + + def add_return_url(post, options) + return unless options[:confirm] + post[:confirm] = options[:confirm] + post[:return_url] = options[:return_url] if options[:return_url] + post + end + + def add_payment_method_token(post, payment_method, options) + return if payment_method.nil? + + if payment_method.is_a?(ActiveMerchant::Billing::CreditCard) + p = create_payment_method(payment_method, options) + payment_method = p.params['id'] + end + + if payment_method.is_a?(StripePaymentToken) + post[:payment_method] = payment_method.payment_data['id'] + elsif payment_method.is_a?(String) + if payment_method.include?('|') + customer_id, payment_method_id = payment_method.split('|') + token = payment_method_id + post[:customer] = customer_id + else + token = payment_method + end + post[:payment_method] = token + end + end + + def add_payment_method_types(post, options) + payment_method_types = options[:payment_method_types] if options[:payment_method_types] + return if payment_method_types.nil? + + post[:payment_method_types] = Array(payment_method_types) + post + end + + def add_exemption(post, options = {}) + return unless options[:confirm] + post[:payment_method_options] ||= {} + post[:payment_method_options][:card] ||= {} + post[:payment_method_options][:card][:moto] = true if options[:moto] + end + + def setup_future_usage(post, options = {}) + post[:setup_future_usage] = options[:setup_future_usage] if %w( on_session off_session ).include?(options[:setup_future_usage]) + post[:off_session] = options[:off_session] if options[:off_session] && options[:confirm] == true + post + end + + def add_connected_account(post, options = {}) + return unless options[:transfer_destination] + post[:transfer_data] = {} + post[:transfer_data][:destination] = options[:transfer_destination] + post[:transfer_data][:amount] = options[:transfer_amount] if options[:transfer_amount] + post[:on_behalf_of] = options[:on_behalf_of] if options[:on_behalf_of] + post[:transfer_group] = options[:transfer_group] if options[:transfer_group] + post[:application_fee_amount] = options[:application_fee] if options[:application_fee] + post + end + + def add_billing_address(post, options = {}) + return unless billing = options[:billing_address] || options[:address] + post[:billing_details] = {} + post[:billing_details][:address] = {} + post[:billing_details][:address][:city] = billing[:city] if billing[:city] + post[:billing_details][:address][:country] = billing[:country] if billing[:country] + post[:billing_details][:address][:line1] = billing[:address1] if billing[:address1] + post[:billing_details][:address][:line2] = billing[:address2] if billing[:address2] + post[:billing_details][:address][:postal_code] = billing[:zip] if billing[:zip] + post[:billing_details][:address][:state] = billing[:state] if billing[:state] + post[:billing_details][:email] = billing[:email] if billing[:email] + post[:billing_details][:name] = billing[:name] if billing[:name] + post[:billing_details][:phone] = billing[:phone] if billing[:phone] + post + end + + def add_shipping_address(post, options = {}) + return unless shipping = options[:shipping] + post[:shipping] = {} + post[:shipping][:address] = {} + post[:shipping][:address][:line1] = shipping[:address][:line1] + post[:shipping][:address][:city] = shipping[:address][:city] if shipping[:address][:city] + post[:shipping][:address][:country] = shipping[:address][:country] if shipping[:address][:country] + post[:shipping][:address][:line2] = shipping[:address][:line2] if shipping[:address][:line2] + post[:shipping][:address][:postal_code] = shipping[:address][:postal_code] if shipping[:address][:postal_code] + post[:shipping][:address][:state] = shipping[:address][:state] if shipping[:address][:state] + + post[:shipping][:name] = shipping[:name] + post[:shipping][:carrier] = shipping[:carrier] if shipping[:carrier] + post[:shipping][:phone] = shipping[:phone] if shipping[:phone] + post[:shipping][:tracking_number] = shipping[:tracking_number] if shipping[:tracking_number] + post + end + end + end +end diff --git a/lib/active_merchant/billing/gateways/swipe_checkout.rb b/lib/active_merchant/billing/gateways/swipe_checkout.rb new file mode 100644 index 00000000000..aaf4002d355 --- /dev/null +++ b/lib/active_merchant/billing/gateways/swipe_checkout.rb @@ -0,0 +1,152 @@ +require 'json' + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class SwipeCheckoutGateway < Gateway + TRANSACTION_APPROVED_MSG = 'Transaction approved' + TRANSACTION_DECLINED_MSG = 'Transaction declined' + + self.live_url = 'https://api.swipehq.com' + self.test_url = 'https://api.swipehq.com' + + TRANSACTION_API = '/createShopifyTransaction.php' + + self.supported_countries = %w[ NZ CA ] + self.default_currency = 'NZD' + self.supported_cardtypes = [:visa, :master] + self.homepage_url = 'https://www.swipehq.com/checkout' + self.display_name = 'Swipe Checkout' + self.money_format = :dollars + + # Swipe Checkout requires the merchant's email and API key for authorization. + # This can be found under Settings > API Credentials after logging in to your + # Swipe Checkout merchant console at https://merchant.swipehq.[com|ca] + # + # :region determines which Swipe URL is used, this can be one of "NZ" or "CA". + # Currently Swipe Checkout has New Zealand and Canadian domains (swipehq.com + # and swipehq.ca respectively). Merchants must use the region that they + # signed up in for authentication with their merchant ID and API key to succeed. + def initialize(options = {}) + requires!(options, :login, :api_key, :region) + super + end + + # Transfers funds immediately. + # Note that Swipe Checkout only supports purchase at this stage + def purchase(money, creditcard, options = {}) + post = {} + add_invoice(post, options) + add_creditcard(post, creditcard) + add_customer_data(post, creditcard, options) + add_amount(post, money, options) + + commit('sale', money, post) + end + + private + + def add_customer_data(post, creditcard, options) + post[:email] = options[:email] + post[:ip_address] = options[:ip] + + address = options[:billing_address] || options[:address] + return if address.nil? + + post[:company] = address[:company] + + post[:first_name], post[:last_name] = split_names(address[:name]) + post[:address] = "#{address[:address1]}, #{address[:address2]}" + post[:city] = address[:city] + post[:country] = address[:country] + post[:mobile] = address[:phone] # API only has a "mobile" field, no "phone" + end + + def add_invoice(post, options) + # store shopping-cart order ID in Swipe for merchant's records + post[:td_user_data] = options[:order_id] if options[:order_id] + post[:td_item] = options[:description] if options[:description] + post[:td_description] = options[:description] if options[:description] + post[:item_quantity] = '1' + end + + def add_creditcard(post, creditcard) + post[:card_number] = creditcard.number + post[:card_type] = creditcard.brand + post[:name_on_card] = "#{creditcard.first_name} #{creditcard.last_name}" + post[:card_expiry] = expdate(creditcard) + post[:secure_number] = creditcard.verification_value + end + + def expdate(creditcard) + year = format(creditcard.year, :two_digits) + month = format(creditcard.month, :two_digits) + + "#{month}#{year}" + end + + def add_amount(post, money, options) + post[:amount] = money.to_s + + post[:currency] = (options[:currency] || currency(money)) + end + + def commit(action, money, parameters) + case action + when 'sale' + begin + response = call_api(TRANSACTION_API, parameters) + + # response code and message params should always be present + code = response['response_code'] + message = response['message'] + + if code == 200 + result = response['data']['result'] + success = (result == 'accepted' || (test? && result == 'test-accepted')) + + Response.new(success, + success ? + TRANSACTION_APPROVED_MSG : + TRANSACTION_DECLINED_MSG, + response, + :test => test? + ) + else + build_error_response(message, response) + end + rescue ResponseError => e + build_error_response("ssl_post() with url #{url} raised ResponseError: #{e}") + rescue JSON::ParserError => e + msg = 'Invalid response received from the Swipe Checkout API. ' \ + 'Please contact support@optimizerhq.com if you continue to receive this message.' \ + " (Full error message: #{e})" + build_error_response(msg) + end + end + end + + def call_api(api, params=nil) + params ||= {} + params[:merchant_id] = @options[:login] + params[:api_key] = @options[:api_key] + + # ssl_post() returns the response body as a string on success, + # or raises a ResponseError exception on failure + JSON.parse(ssl_post(url(api), params.to_query)) + end + + def url(api) + (test? ? self.test_url : self.live_url) + api + end + + def build_error_response(message, params={}) + Response.new( + false, + message, + params, + :test => test? + ) + end + end + end +end diff --git a/lib/active_merchant/billing/gateways/telr.rb b/lib/active_merchant/billing/gateways/telr.rb new file mode 100644 index 00000000000..c39b0a17e34 --- /dev/null +++ b/lib/active_merchant/billing/gateways/telr.rb @@ -0,0 +1,274 @@ +require 'nokogiri' + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class TelrGateway < Gateway + self.display_name = 'Telr' + self.homepage_url = 'http://www.telr.com/' + + self.live_url = 'https://secure.telr.com/gateway/remote.xml' + + self.supported_countries = ['AE', 'IN', 'SA'] + self.default_currency = 'AED' + self.money_format = :dollars + self.supported_cardtypes = [:visa, :master, :american_express, :maestro, :jcb] + + CVC_CODE_TRANSLATOR = { + 'Y' => 'M', + 'N' => 'N', + 'X' => 'P', + 'E' => 'U', + } + + AVS_CODE_TRANSLATOR = { + 'Y' => 'M', + 'P' => 'A', + 'N' => 'N', + 'X' => 'I', + 'E' => 'R' + } + + def initialize(options={}) + requires!(options, :merchant_id, :api_key) + super + end + + def purchase(amount, payment_method, options={}) + commit(:purchase, amount, options[:currency]) do |doc| + add_invoice(doc, 'sale', amount, payment_method, options) + add_payment_method(doc, payment_method, options) + add_customer_data(doc, payment_method, options) + end + end + + def authorize(amount, payment_method, options={}) + commit(:authorize, amount, options[:currency]) do |doc| + add_invoice(doc, 'auth', amount, payment_method, options) + add_payment_method(doc, payment_method, options) + add_customer_data(doc, payment_method, options) + end + end + + def capture(amount, authorization, options={}) + commit(:capture) do |doc| + add_invoice(doc, 'capture', amount, authorization, options) + end + end + + def void(authorization, options={}) + _, amount, currency = split_authorization(authorization) + commit(:void) do |doc| + add_invoice(doc, 'void', amount.to_i, authorization, options.merge(currency: currency)) + end + end + + def refund(amount, authorization, options={}) + commit(:refund) do |doc| + add_invoice(doc, 'refund', amount, authorization, options) + end + end + + def verify(credit_card, options={}) + commit(:verify) do |doc| + add_invoice(doc, 'verify', 100, credit_card, options) + add_payment_method(doc, credit_card, options) + add_customer_data(doc, credit_card, options) + end + end + + def verify_credentials + response = void('0') + !['01', '04'].include?(response.error_code) + end + + def supports_scrubbing? + true + end + + def scrub(transcript) + transcript. + gsub(%r((<Number>)[^<]+(<))i, '\1[FILTERED]\2'). + gsub(%r((<CVV>)[^<]+(<))i, '\1[FILTERED]\2'). + gsub(%r((<Key>)[^<]+(<))i, '\1[FILTERED]\2') + end + + private + + def add_invoice(doc, action, money, payment_method, options) + doc.tran do + doc.type(action) + doc.amount(amount(money)) + doc.currency(options[:currency] || currency(money)) + doc.cartid(options[:order_id]) + doc.class_(transaction_class(action, payment_method)) + doc.description(options[:description] || 'Description') + doc.test_(test_mode) + add_ref(doc, action, payment_method) + end + end + + def add_payment_method(doc, payment_method, options) + return if payment_method.is_a?(String) + doc.card do + doc.number(payment_method.number) + doc.cvv(payment_method.verification_value) + doc.expiry do + doc.month(format(payment_method.month, :two_digits)) + doc.year(format(payment_method.year, :four_digits)) + end + end + end + + def add_customer_data(doc, payment_method, options) + return if payment_method.is_a?(String) + doc.billing do + doc.name do + doc.first(payment_method.first_name) + doc.last(payment_method.last_name) + end + doc.email(options[:email] || 'unspecified@email.com') + doc.ip(options[:ip]) if options[:ip] + doc.address do + add_address(doc, options) + end + end + end + + def add_address(doc, options) + address = options[:billing_address] || {} + doc.country(address[:country] ? lookup_country_code(address[:country]) : 'NA') + doc.city(address[:city] || 'City') + doc.line1(address[:address1] || 'Address') + return unless address + doc.line2(address[:address2]) if address[:address2] + doc.zip(address[:zip]) if address[:zip] + doc.region(address[:state]) if address[:state] + end + + def add_ref(doc, action, payment_method) + if ['capture', 'refund', 'void'].include?(action) || payment_method.is_a?(String) + doc.ref(split_authorization(payment_method)[0]) + end + end + + def add_authentication(doc) + doc.store(@options[:merchant_id]) + doc.key(@options[:api_key]) + end + + def lookup_country_code(code) + country = Country.find(code) rescue nil + country.code(:alpha2) + end + + def commit(action, amount=nil, currency=nil) + currency = default_currency if currency == nil + request = build_xml_request { |doc| yield(doc) } + response = ssl_post(live_url, request, headers) + parsed = parse(response) + + succeeded = success_from(parsed) + Response.new( + succeeded, + message_from(succeeded, parsed), + parsed, + authorization: authorization_from(action, parsed, amount, currency), + avs_result: avs_result(parsed), + cvv_result: cvv_result(parsed), + error_code: error_code_from(succeeded, parsed), + test: test? + ) + end + + def root_attributes + { + store: @options[:merchant_id], + key: @options[:api_key] + } + end + + def build_xml_request + builder = Nokogiri::XML::Builder.new(encoding: 'UTF-8') do |xml| + xml.remote do |doc| + add_authentication(doc) + yield(doc) + end + end + + builder.doc.to_xml + end + + def test_mode + test? ? '1' : '0' + end + + def transaction_class(action, payment_method) + if payment_method.is_a?(String) && action == 'sale' + return 'cont' + else + return 'moto' + end + end + + def parse(xml) + response = {} + + doc = Nokogiri::XML(xml) + doc.root&.xpath('*')&.each do |node| + if node.elements.size == 0 + response[node.name.downcase.to_sym] = node.text + else + node.elements.each do |childnode| + name = childnode.name.downcase + response[name.to_sym] = childnode.text + end + end + end + + response + end + + def authorization_from(action, response, amount, currency) + auth = response[:tranref] + auth = [auth, amount, currency].join('|') + auth + end + + def split_authorization(authorization) + authorization.split('|') + end + + def success_from(response) + response[:status] == 'A' + end + + def message_from(succeeded, response) + if succeeded + 'Succeeded' + else + response[:message] + end + end + + def error_code_from(succeeded, response) + unless succeeded + response[:code] + end + end + + def cvv_result(parsed) + CVVResult.new(CVC_CODE_TRANSLATOR[parsed[:cvv]]) + end + + def avs_result(parsed) + AVSResult.new(code: AVS_CODE_TRANSLATOR[parsed[:avs]]) + end + + def headers + { + 'Content-Type' => 'text/xml' + } + end + end + end +end diff --git a/lib/active_merchant/billing/gateways/tns.rb b/lib/active_merchant/billing/gateways/tns.rb new file mode 100644 index 00000000000..25ed79306a9 --- /dev/null +++ b/lib/active_merchant/billing/gateways/tns.rb @@ -0,0 +1,22 @@ +module ActiveMerchant + module Billing + class TnsGateway < Gateway + include MastercardGateway + + class_attribute :live_na_url, :live_ap_url, :test_na_url, :test_ap_url + + self.live_na_url = 'https://secure.na.tnspayments.com/api/rest/version/36/' + self.test_na_url = 'https://secure.na.tnspayments.com/api/rest/version/36/' + + self.live_ap_url = 'https://secure.ap.tnspayments.com/api/rest/version/36/' + self.test_ap_url = 'https://secure.ap.tnspayments.com/api/rest/version/36/' + + self.display_name = 'TNS' + self.homepage_url = 'http://www.tnsi.com/' + self.supported_countries = %w(AR AU BR FR DE HK MX NZ SG GB US) + self.default_currency = 'USD' + self.supported_cardtypes = [:visa, :master, :american_express, :discover, :diners_club, :jcb, :maestro] + + end + end +end diff --git a/lib/active_merchant/billing/gateways/trans_first.rb b/lib/active_merchant/billing/gateways/trans_first.rb index 013fdf5813b..8b2c579683f 100644 --- a/lib/active_merchant/billing/gateways/trans_first.rb +++ b/lib/active_merchant/billing/gateways/trans_first.rb @@ -1,66 +1,146 @@ module ActiveMerchant #:nodoc: module Billing #:nodoc: class TransFirstGateway < Gateway - self.live_url = self.test_url = 'https://webservices.primerchants.com/creditcard.asmx/CCSale' + self.test_url = 'https://ws.cert.transfirst.com' + self.live_url = 'https://webservices.primerchants.com' self.supported_countries = ['US'] self.supported_cardtypes = [:visa, :master, :american_express, :discover] self.homepage_url = 'http://www.transfirst.com/' self.display_name = 'TransFirst' - UNUSED_FIELDS = %w(ECIValue UserId CAVVData TrackData POSInd EComInd MerchZIP MerchCustPNum MCC InstallmentNum InstallmentOf POSEntryMode POSConditionCode AuthCharInd CardCertData) + UNUSED_CREDIT_CARD_FIELDS = %w(UserId TrackData MerchZIP MerchCustPNum MCC InstallmentNum InstallmentOf POSInd POSEntryMode POSConditionCode EComInd AuthCharInd CardCertData CAVVData) DECLINED = 'The transaction was declined' + ACTIONS = { + purchase: 'CCSale', + purchase_echeck: 'ACHDebit', + refund: 'CreditCardCredit', + refund_echeck: 'ACHVoidTransaction', + void: 'CreditCardAutoRefundorVoid', + } + + ENDPOINTS = { + purchase: 'creditcard.asmx', + purchase_echeck: 'checkverifyws/checkverifyws.asmx', + refund: 'creditcard.asmx', + refund_echeck: 'checkverifyws/checkverifyws.asmx', + void: 'creditcard.asmx' + } + def initialize(options = {}) requires!(options, :login, :password) super end - def purchase(money, credit_card, options = {}) + def purchase(money, payment, options = {}) post = {} add_amount(post, money) - add_invoice(post, options) - add_credit_card(post, credit_card) + add_payment(post, payment) add_address(post, options) + add_invoice(post, options) if payment.credit_card? + add_pair(post, :RefID, options[:order_id], required: true) + + commit((payment.is_a?(Check) ? :purchase_echeck : :purchase), post) + end + + def refund(money, authorization, options={}) + post = {} + + transaction_id, payment_type = split_authorization(authorization) + add_amount(post, money) + add_pair(post, :TransID, transaction_id) + add_pair(post, :RefID, options[:order_id], required: true) + + commit((payment_type == 'check' ? :refund_echeck : :refund), post) + end + + def void(authorization, options={}) + post = {} + + transaction_id, _ = split_authorization(authorization) + add_pair(post, :TransID, transaction_id) + + commit(:void, post) + end + + def supports_scrubbing? + true + end - commit(post) + def scrub(transcript) + transcript. + gsub(%r((&?RegKey=)\w*(&?)), '\1[FILTERED]\2'). + gsub(%r((&?CardNumber=)\d*(&?)), '\1[FILTERED]\2'). + gsub(%r((&?CVV2=)\d*(&?)), '\1[FILTERED]\2'). + gsub(%r((&?TransRoute=)\d*(&?)), '\1[FILTERED]\2'). + gsub(%r((&?BankAccountNo=)\d*(&?)), '\1[FILTERED]\2') end private + def add_amount(post, money) - add_pair(post, :Amount, amount(money), :required => true) + add_pair(post, :Amount, amount(money), required: true) end def add_address(post, options) address = options[:billing_address] || options[:address] if address - add_pair(post, :Address, address[:address1]) - add_pair(post, :ZipCode, address[:zip]) + add_pair(post, :Address, address[:address1], required: true) + add_pair(post, :ZipCode, address[:zip], required: true) + else + add_pair(post, :Address, '', required: true) + add_pair(post, :ZipCode, '', required: true) end end def add_invoice(post, options) - add_pair(post, :RefID, options[:order_id], :required => true) - add_pair(post, :PONumber, options[:invoice], :required => true) + add_pair(post, :SECCCode, options[:invoice], required: true) + add_pair(post, :PONumber, options[:invoice], required: true) add_pair(post, :SaleTaxAmount, amount(options[:tax] || 0)) - add_pair(post, :PaymentDesc, options[:description], :required => true) add_pair(post, :TaxIndicator, 0) + add_pair(post, :PaymentDesc, options[:description] || '', required: true) + add_pair(post, :CompanyName, options[:company_name] || '', required: true) + end + + def add_payment(post, payment) + if payment.is_a?(Check) + add_echeck(post, payment) + else + add_credit_card(post, payment) + end end - def add_credit_card(post, credit_card) - add_pair(post, :CardHolderName, credit_card.name, :required => true) - add_pair(post, :CardNumber, credit_card.number, :required => true) + def add_credit_card(post, payment) + add_pair(post, :CardHolderName, payment.name, required: true) + add_pair(post, :CardNumber, payment.number, required: true) + add_pair(post, :Expiration, expdate(payment), required: true) + add_pair(post, :CVV2, payment.verification_value, required: true) + end + + def add_echeck(post, payment) + add_pair(post, :TransRoute, payment.routing_number, required: true) + add_pair(post, :BankAccountNo, payment.account_number, required: true) + add_pair(post, :BankAccountType, add_or_use_default(payment.account_type, 'Checking'), required: true) + add_pair(post, :CheckType, add_or_use_default(payment.account_holder_type, 'Personal'), required: true) + add_pair(post, :Name, payment.name, required: true) + add_pair(post, :ProcessDate, Time.now.strftime('%m%d%y'), required: true) + add_pair(post, :Description, '', required: true) + end - add_pair(post, :Expiration, expdate(credit_card), :required => true) - add_pair(post, :CVV2, credit_card.verification_value) + def add_or_use_default(payment_data, default_value) + return payment_data.capitalize if payment_data + return default_value end - def add_unused_fields(post) - UNUSED_FIELDS.each do |f| - post[f] = "" + def add_unused_fields(action, post) + return unless action == :purchase + + UNUSED_CREDIT_CARD_FIELDS.each do |f| + post[f] = '' end end @@ -75,7 +155,7 @@ def parse(data) response = {} xml = REXML::Document.new(data) - root = REXML::XPath.first(xml, "//CCSaleDebitResponse") + root = REXML::XPath.first(xml, '*') if root.nil? response[:message] = data.to_s.strip @@ -88,17 +168,42 @@ def parse(data) response end - def commit(params) - response = parse( ssl_post(self.live_url, post_data(params)) ) - - Response.new(response[:status] == "Authorized", message_from(response), response, + def commit(action, params) + response = parse(ssl_post(url(action), post_data(action, params))) + Response.new( + success_from(response), + message_from(response), + response, :test => test?, - :authorization => response[:trans_id], + :authorization => authorization_from(response), :avs_result => { :code => response[:avs_code] }, :cvv_result => response[:cvv2_code] ) end + def authorization_from(response) + if response[:status] == 'APPROVED' + "#{response[:trans_id]}|check" + else + "#{response[:trans_id]}|creditcard" + end + end + + def success_from(response) + case response[:status] + when 'Authorized' + true + when 'Voided' + true + when 'APPROVED' + true + when 'VOIDED' + true + else + false + end + end + def message_from(response) case response[:message] when 'Call Voice Center' @@ -108,19 +213,27 @@ def message_from(response) end end - def post_data(params = {}) - add_unused_fields(params) + def post_data(action, params = {}) + add_unused_fields(action, params) params[:MerchantID] = @options[:login] params[:RegKey] = @options[:password] - request = params.collect { |key, value| "#{key}=#{CGI.escape(value.to_s)}" }.join("&") + request = params.collect { |key, value| "#{key}=#{CGI.escape(value.to_s)}" }.join('&') request end def add_pair(post, key, value, options = {}) post[key] = value if !value.blank? || options[:required] end + + def url(action) + base_url = test? ? test_url : live_url + "#{base_url}/#{ENDPOINTS[action]}/#{ACTIONS[action]}" + end + + def split_authorization(authorization) + authorization.split('|') + end end end end - diff --git a/lib/active_merchant/billing/gateways/trans_first_transaction_express.rb b/lib/active_merchant/billing/gateways/trans_first_transaction_express.rb new file mode 100644 index 00000000000..f35fe0d9b78 --- /dev/null +++ b/lib/active_merchant/billing/gateways/trans_first_transaction_express.rb @@ -0,0 +1,608 @@ +require 'nokogiri' + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class TransFirstTransactionExpressGateway < Gateway + self.display_name = 'TransFirst Transaction Express' + self.homepage_url = 'http://transactionexpress.com/' + + self.test_url = 'https://ws.cert.transactionexpress.com/portal/merchantframework/MerchantWebServices-v1?wsdl' + self.live_url = 'https://ws.transactionexpress.com/portal/merchantframework/MerchantWebServices-v1?wsdl' + + self.supported_countries = ['US'] + self.default_currency = 'USD' + self.money_format = :cents + self.supported_cardtypes = [:visa, :master, :american_express, :discover, :diners_club] + + V1_NAMESPACE = 'http://postilion/realtime/merchantframework/xsd/v1/' + SOAPENV_NAMESPACE = 'http://schemas.xmlsoap.org/soap/envelope/' + AUTHORIZATION_FIELD_SEPARATOR = '|' + + APPROVAL_CODES = %w(00 10) + + RESPONSE_MESSAGES = { + '00' => 'Approved', + '01' => 'Refer to card issuer', + '02' => 'Refer to card issuer, special condition', + '03' => 'Invalid merchant', + '04' => 'Pick-up card', + '05' => 'Do not honor', + '06' => 'Error', + '07' => 'Pick-up card, special condition', + '08' => 'Honor with identification', + '09' => 'Request in progress', + '10' => 'Approved, partial authorization', + '11' => 'VIP Approval', + '12' => 'Invalid transaction', + '13' => 'Invalid amount', + '14' => 'Invalid card number', + '15' => 'No such issuer', + '16' => 'Approved, update track 3', + '17' => 'Customer cancellation', + '18' => 'Customer dispute', + '19' => 'Re-enter transaction', + '20' => 'Invalid response', + '21' => 'No action taken', + '22' => 'Suspected malfunction', + '23' => 'Unacceptable transaction fee', + '24' => 'File update not supported', + '25' => 'Unable to locate record', + '26' => 'Duplicate record', + '27' => 'File update field edit error', + '28' => 'File update file locked', + '29' => 'File update failed', + '30' => 'Format error', + '31' => 'Bank not supported', + '33' => 'Expired card, pick-up', + '34' => 'Suspected fraud, pick-up', + '35' => 'Contact acquirer, pick-up', + '36' => 'Restricted card, pick-up', + '37' => 'Call acquirer security, pick-up', + '38' => 'PIN tries exceeded, pick-up', + '39' => 'No credit account', + '40' => 'Function not supported', + '41' => 'Lost card, pick-up', + '42' => 'No universal account', + '43' => 'Stolen card, pick-up', + '44' => 'No investment account', + '45' => 'Account closed', + '46' => 'Identification required', + '47' => 'Identification cross-check required', + '48' => 'No customer record', + '49' => 'Reserved for future Realtime use', + '50' => 'Reserved for future Realtime use', + '51' => 'Not sufficient funds', + '52' => 'No checking account', + '53' => 'No savings account', + '54' => 'Expired card', + '55' => 'Incorrect PIN', + '56' => 'No card record', + '57' => 'Transaction not permitted to cardholder', + '58' => 'Transaction not permitted on terminal', + '59' => 'Suspected fraud', + '60' => 'Contact acquirer', + '61' => 'Exceeds withdrawal limit', + '62' => 'Restricted card', + '63' => 'Security violation', + '64' => 'Original amount incorrect', + '65' => 'Exceeds withdrawal frequency', + '66' => 'Call acquirer security', + '67' => 'Hard capture', + '68' => 'Response received too late', + '69' => 'Advice received too late (the response from a request was received too late )', + '70' => 'Reserved for future use', + '71' => 'Reserved for future Realtime use', + '72' => 'Reserved for future Realtime use', + '73' => 'Reserved for future Realtime use', + '74' => 'Reserved for future Realtime use', + '75' => 'PIN tries exceeded', + '76' => 'Reversal: Unable to locate previous message (no match on Retrieval Reference Number)/ Reserved for future Realtime use', + '77' => 'Previous message located for a repeat or reversal, but repeat or reversal data is inconsistent with original message/ Intervene, bank approval required', + '78' => 'Invalid/non-existent account – Decline (MasterCard specific)/ Intervene, bank approval required for partial amount', + '79' => 'Already reversed (by Switch)/ Reserved for client-specific use (declined)', + '80' => 'No financial Impact (Reserved for declined debit)/ Reserved for client-specific use (declined)', + '81' => 'PIN cryptographic error found by the Visa security module during PIN decryption/ Reserved for client-specific use (declined)', + '82' => 'Incorrect CVV/ Reserved for client-specific use (declined)', + '83' => 'Unable to verify PIN/ Reserved for client-specific use (declined)', + '84' => 'Invalid Authorization Life Cycle – Decline (MasterCard) or Duplicate Transaction Detected (Visa)/ Reserved for client-specific use (declined)', + '85' => 'No reason to decline a request for Account Number Verification or Address Verification/ Reserved for client-specific use (declined)', + '86' => 'Cannot verify PIN/ Reserved for client-specific use (declined)', + '87' => 'Reserved for client-specific use (declined)', + '88' => 'Reserved for client-specific use (declined)', + '89' => 'Reserved for client-specific use (declined)', + '90' => 'Cut-off in progress', + '91' => 'Issuer or switch inoperative', + '92' => 'Routing error', + '93' => 'Violation of law', + '94' => 'Duplicate Transmission (Integrated Debit and MasterCard)', + '95' => 'Reconcile error', + '96' => 'System malfunction', + '97' => 'Reserved for future Realtime use', + '98' => 'Exceeds cash limit', + '99' => 'Reserved for future Realtime use', + '1106' => 'Reserved for future Realtime use', + '0A' => 'Reserved for future Realtime use', + 'A0' => 'Reserved for future Realtime use', + 'A1' => 'ATC not incremented', + 'A2' => 'ATC limit exceeded', + 'A3' => 'ATC configuration error', + 'A4' => 'CVR check failure', + 'A5' => 'CVR configuration error', + 'A6' => 'TVR check failure', + 'A7' => 'TVR configuration error', + 'A8' => 'Reserved for future Realtime use', + 'B1' => 'Surcharge amount not permitted on Visa cards or EBT Food Stamps/ Reserved for future Realtime use', + 'B2' => 'Surcharge amount not supported by debit network issuer/ Reserved for future Realtime use', + 'C1' => 'Unacceptable PIN', + 'C2' => 'PIN Change failed', + 'C3' => 'PIN Unblock failed', + 'D1' => 'MAC Error', + 'E1' => 'Prepay error', + 'N1' => 'Network Error within the TXP platform', + 'N0' => 'Force STIP/ Reserved for client-specific use (declined)', + 'N3' => 'Cash service not available/ Reserved for client-specific use (declined)', + 'N4' => 'Cash request exceeds Issuer limit/ Reserved for client-specific use (declined)', + 'N5' => 'Ineligible for re-submission/ Reserved for client-specific use (declined)', + 'N7' => 'Decline for CVV2 failure/ Reserved for client-specific use (declined)', + 'N8' => 'Transaction amount exceeds preauthorized approval amount/ Reserved for client-specific use (declined)', + 'P0' => 'Approved; PVID code is missing, invalid, or has expired', + 'P1' => 'Declined; PVID code is missing, invalid, or has expired/ Reserved for client-specific use (declined)', + 'P2' => 'Invalid biller Information/ Reserved for client-specific use (declined)/ Reserved for client-specific use (declined)', + 'R0' => 'The transaction was declined or returned, because the cardholder requested that payment of a specific recurring or installment payment transaction be stopped/ Reserved for client-specific use (declined)', + 'R1' => 'The transaction was declined or returned, because the cardholder requested that payment of all recurring or installment payment transactions for a specific merchant account be stopped/ Reserved for client-specific use (declined)', + 'Q1' => 'Card Authentication failed/ Reserved for client-specific use (declined)', + 'XA' => 'Forward to Issuer/ Reserved for client-specific use (declined)', + 'XD' => 'Forward to Issuer/ Reserved for client-specific use (declined)', + } + + EXTENDED_RESPONSE_MESSAGES = { + 'B40K' => 'Declined Post – Credit linked to unextracted settle transaction' + } + + TRANSACTION_CODES = { + authorize: 0, + void_authorize: 2, + + purchase: 1, + capture: 3, + void_purchase: 6, + void_capture: 6, + + refund: 4, + credit: 5, + void_refund: 13, + void_credit: 13, + + verify: 9, + + purchase_echeck: 11, + refund_echeck: 16, + void_echeck: 16, + + wallet_sale: 14, + } + + def initialize(options={}) + requires!(options, :gateway_id, :reg_key) + super + end + + def purchase(amount, payment_method, options={}) + if credit_card?(payment_method) + action = :purchase + request = build_xml_transaction_request do |doc| + add_credit_card(doc, payment_method) + add_contact(doc, payment_method.name, options) + add_amount(doc, amount) + add_order_number(doc, options) + end + elsif echeck?(payment_method) + action = :purchase_echeck + request = build_xml_transaction_request do |doc| + add_echeck(doc, payment_method) + add_contact(doc, payment_method.name, options) + add_amount(doc, amount) + add_order_number(doc, options) + end + else + action = :wallet_sale + wallet_id = split_authorization(payment_method).last + request = build_xml_transaction_request do |doc| + add_amount(doc, amount) + add_wallet_id(doc, wallet_id) + end + end + + commit(action, request) + end + + def authorize(amount, payment_method, options={}) + if credit_card?(payment_method) + request = build_xml_transaction_request do |doc| + add_credit_card(doc, payment_method) + add_contact(doc, payment_method.name, options) + add_amount(doc, amount) + end + else + wallet_id = split_authorization(payment_method).last + request = build_xml_transaction_request do |doc| + add_amount(doc, amount) + add_wallet_id(doc, wallet_id) + end + end + + commit(:authorize, request) + end + + def capture(amount, authorization, options={}) + transaction_id = split_authorization(authorization)[1] + request = build_xml_transaction_request do |doc| + add_amount(doc, amount) + add_original_transaction_data(doc, transaction_id) + end + + commit(:capture, request) + end + + def void(authorization, options={}) + action, transaction_id = split_authorization(authorization) + + request = build_xml_transaction_request do |doc| + add_original_transaction_data(doc, transaction_id) + end + + commit(void_type(action), request) + end + + def refund(amount, authorization, options={}) + action, transaction_id = split_authorization(authorization) + + request = build_xml_transaction_request do |doc| + add_amount(doc, amount) unless action == 'purchase_echeck' + add_original_transaction_data(doc, transaction_id) + end + + commit(refund_type(action), request) + end + + def credit(amount, payment_method, options={}) + request = build_xml_transaction_request do |doc| + add_pan(doc, payment_method) + add_amount(doc, amount) + end + + commit(:credit, request) + end + + def verify(credit_card, options={}) + request = build_xml_transaction_request do |doc| + add_credit_card(doc, credit_card) + add_contact(doc, credit_card.name, options) + end + + commit(:verify, request) + end + + def store(payment_method, options={}) + store_customer_request = build_xml_payment_storage_request do |doc| + store_customer_details(doc, payment_method.name, options) + end + + MultiResponse.run do |r| + r.process { commit(:store, store_customer_request) } + return r unless r.success? && r.params['custId'] + customer_id = r.params['custId'] + + store_payment_method_request = build_xml_payment_storage_request do |doc| + doc['v1'].cust do + add_customer_id(doc, customer_id) + doc['v1'].pmt do + doc['v1'].type 0 # add + add_credit_card(doc, payment_method) + end + end + end + + r.process { commit(:store, store_payment_method_request) } + end + end + + def supports_scrubbing? + true + end + + def scrub(transcript) + transcript. + gsub(%r((<[^>]+pan>)[^<]+(<))i, '\1[FILTERED]\2'). + gsub(%r((<[^>]+sec>)[^<]+(<))i, '\1[FILTERED]\2'). + gsub(%r((<[^>]+id>)[^<]+(<))i, '\1[FILTERED]\2'). + gsub(%r((<[^>]+regKey>)[^<]+(<))i, '\1[FILTERED]\2') + end + + private + + CURRENCY_CODES = Hash.new { |h, k| raise ArgumentError.new("Unsupported currency: #{k}") } + CURRENCY_CODES['USD'] = '840' + + def headers + { + 'Content-Type' => 'text/xml' + } + end + + def commit(action, request) + request = add_transaction_code_to_request(request, action) + + raw_response = begin + ssl_post(url, request, headers) + rescue ActiveMerchant::ResponseError => e + e.response.body + end + + response = parse(raw_response) + + succeeded = success_from(response) + + Response.new( + succeeded, + message_from(succeeded, response), + response, + error_code: error_code_from(succeeded, response), + authorization: authorization_from(action, response), + avs_result: AVSResult.new(code: response['avsRslt']), + cvv_result: CVVResult.new(response['secRslt']), + test: test? + ) + end + + def url + test? ? test_url : live_url + end + + def parse(xml) + response = {} + doc = Nokogiri::XML(xml).remove_namespaces! + + doc.css('Envelope Body *').each do |node| + # node.name is more readable, but uniq_name is occasionally necessary + uniq_name = [node.parent.name, node.name].join('_') + response[uniq_name] = node.text + response[node.name] = node.text + end + + response + end + + def success_from(response) + fault = response['Fault'] + approved_transaction = APPROVAL_CODES.include?(response['rspCode']) + found_contact = response['FndRecurrProfResponse'] + + return !fault && (approved_transaction || found_contact) + end + + def error_code_from(succeeded, response) + return if succeeded + response['errorCode'] || response['rspCode'] + end + + def message_from(succeeded, response) + return 'Succeeded' if succeeded + + if response['rspCode'] + code = response['rspCode'] + extended_code = response['extRspCode'] + + message = RESPONSE_MESSAGES[code] + extended = EXTENDED_RESPONSE_MESSAGES[extended_code] + ach_response = response['achResponse'] + + [message, extended, ach_response].compact.join('. ') + else + response['faultstring'] + end + end + + def authorization_from(action, response) + authorization = response['tranNr'] || response['pmtId'] + + # guard so we don't return something like "purchase|" + return unless authorization + + [action, authorization].join(AUTHORIZATION_FIELD_SEPARATOR) + end + + # -- helper methods ---------------------------------------------------- + def credit_card?(payment_method) + payment_method.respond_to?(:verification_value) + end + + def echeck?(payment_method) + payment_method.respond_to?(:routing_number) + end + + def split_authorization(authorization) + authorization.split(AUTHORIZATION_FIELD_SEPARATOR) + end + + def void_type(action) + action == 'purchase_echeck' ? :void_echeck : :"void_#{action}" + end + + def refund_type(action) + action == 'purchase_echeck' ? :refund_echeck : :refund + end + + # -- request methods --------------------------------------------------- + def build_xml_transaction_request + build_xml_request('SendTranRequest') do |doc| + yield doc + end + end + + def build_xml_payment_storage_request + build_xml_request('UpdtRecurrProfRequest') do |doc| + yield doc + end + end + + def build_xml_payment_update_request + merchant_product_type = 5 # credit card + build_xml_request('UpdtRecurrProfRequest', merchant_product_type) do |doc| + yield doc + end + end + + def build_xml_payment_search_request + build_xml_request('FndRecurrProfRequest') do |doc| + yield doc + end + end + + def build_xml_request(wrapper, merchant_product_type=nil) + Nokogiri::XML::Builder.new(encoding: 'UTF-8') do |xml| + xml['soapenv'].Envelope('xmlns:soapenv' => SOAPENV_NAMESPACE) do + xml['soapenv'].Body do + xml['v1'].send(wrapper, 'xmlns:v1' => V1_NAMESPACE) do + add_merchant(xml) + yield(xml) + end + end + end + end.doc.root.to_xml + end + + def add_transaction_code_to_request(request, action) + # store requests don't get a transaction code + return request if action == :store + + doc = Nokogiri::XML::Document.parse(request) + merc_nodeset = doc.xpath('//v1:merc', 'v1' => V1_NAMESPACE) + merc_nodeset.after "<tranCode>#{TRANSACTION_CODES[action]}</tranCode>" + doc.root.to_xml + end + + def add_merchant(doc, product_type=nil) + doc['v1'].merc do + doc['v1'].id @options[:gateway_id] + doc['v1'].regKey @options[:reg_key] + doc['v1'].inType '1' + doc['v1'].prodType product_type if product_type + end + end + + def add_amount(doc, money) + doc['v1'].reqAmt amount(money) + end + + def add_order_number(doc, options) + return unless options[:order_id] + + doc['v1'].authReq { + doc['v1'].ordNr options[:order_id] + } + end + + def add_credit_card(doc, payment_method) + doc['v1'].card { + doc['v1'].pan payment_method.number + doc['v1'].sec payment_method.verification_value if payment_method.verification_value? + doc['v1'].xprDt expiration_date(payment_method) + } + end + + def add_echeck(doc, payment_method) + doc['v1'].achEcheck { + doc['v1'].bankRtNr payment_method.routing_number + doc['v1'].acctNr payment_method.account_number + } + end + + def expiration_date(payment_method) + yy = format(payment_method.year, :two_digits) + mm = format(payment_method.month, :two_digits) + yy + mm + end + + def add_pan(doc, payment_method) + doc['v1'].card do + doc['v1'].pan payment_method.number + end + end + + def add_contact(doc, fullname, options) + doc['v1'].contact do + doc['v1'].fullName fullname unless fullname.blank? + doc['v1'].coName options[:company_name] if options[:company_name] + doc['v1'].title options[:title] if options[:title] + + if (billing_address = options[:billing_address]) + if billing_address[:phone] + doc['v1'].phone do + doc['v1'].type(options[:phone_number_type] || '4') + doc['v1'].nr billing_address[:phone].gsub(/\D/, '') + end + end + doc['v1'].addrLn1 billing_address[:address1] if billing_address[:address1] + doc['v1'].addrLn2 billing_address[:address2] unless billing_address[:address2].blank? + doc['v1'].city billing_address[:city] if billing_address[:city] + doc['v1'].state billing_address[:state] if billing_address[:state] + doc['v1'].zipCode billing_address[:zip] if billing_address[:zip] + doc['v1'].ctry 'US' + end + + doc['v1'].email options[:email] if options[:email] + doc['v1'].type options[:contact_type] if options[:contact_type] + doc['v1'].stat options[:contact_stat] if options[:contact_stat] + + if (shipping_address = options[:shipping_address]) + doc['v1'].ship do + doc['v1'].fullName fullname unless fullname.blank? + doc['v1'].addrLn1 shipping_address[:address1] if shipping_address[:address1] + doc['v1'].addrLn2 shipping_address[:address2] unless shipping_address[:address2].blank? + doc['v1'].city shipping_address[:city] if shipping_address[:city] + doc['v1'].state shipping_address[:state] if shipping_address[:state] + doc['v1'].zipCode shipping_address[:zip] if shipping_address[:zip] + doc['v1'].phone shipping_address[:phone].gsub(/\D/, '') if shipping_address[:phone] + doc['v1'].email shipping_address[:email] if shipping_address[:email] + end + end + end + end + + def add_name(doc, payment_method) + doc['v1'].contact do + doc['v1'].fullName payment_method.name unless payment_method.name.blank? + end + end + + def add_original_transaction_data(doc, authorization) + doc['v1'].origTranData do + doc['v1'].tranNr authorization + end + end + + def store_customer_details(doc, fullname, options) + options[:contact_type] = 1 # recurring + options[:contact_stat] = 1 # active + + doc['v1'].cust do + doc['v1'].type 0 # add + add_contact(doc, fullname, options) + end + end + + def add_customer_id(doc, customer_id) + doc['v1'].contact do + doc['v1'].id customer_id + end + end + + def add_wallet_id(doc, wallet_id) + doc['v1'].recurMan do + doc['v1'].id wallet_id + end + end + end + end +end diff --git a/lib/active_merchant/billing/gateways/transact_pro.rb b/lib/active_merchant/billing/gateways/transact_pro.rb new file mode 100644 index 00000000000..5aaa36d756d --- /dev/null +++ b/lib/active_merchant/billing/gateways/transact_pro.rb @@ -0,0 +1,224 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + # For more information visit {Transact Pro Services}[https://www.transactpro.lv/business/] + # + # This gateway was formerly associated with www.1stpayments.net + # + # Written by Piers Chambers (Varyonic.com) + class TransactProGateway < Gateway + self.test_url = 'https://gw2sandbox.tpro.lv:8443/gw2test/gwprocessor2.php' + self.live_url = 'https://www2.1stpayments.net/gwprocessor2.php' + + self.supported_countries = ['US'] + self.default_currency = 'USD' + self.money_format = :cents + self.supported_cardtypes = [:visa, :master, :american_express, :discover] + + self.homepage_url = 'https://www.transactpro.lv/business/online-payments-acceptance' + self.display_name = 'Transact Pro' + + def initialize(options={}) + requires!(options, :guid, :password, :terminal) + super + end + + def purchase(amount, payment, options={}) + post = PostData.new + add_invoice(post, amount, options) + add_payment(post, payment) + add_address(post, payment, options) + add_customer_data(post, options) + add_credentials(post) + post[:rs] = @options[:terminal] + + MultiResponse.run do |r| + r.process { commit('init', post) } + r.process do + post = PostData.new + post[:init_transaction_id] = r.authorization + add_payment_cc(post, payment) + post[:f_extended] = '4' + + commit('charge', post, amount) + end + end + end + + def authorize(amount, payment, options={}) + post = PostData.new + add_invoice(post, amount, options) + add_payment(post, payment) + add_address(post, payment, options) + add_customer_data(post, options) + add_credentials(post) + post[:rs] = @options[:terminal] + + MultiResponse.run do |r| + r.process { commit('init_dms', post) } + r.process do + post = PostData.new + post[:init_transaction_id] = r.authorization + add_payment_cc(post, payment) + post[:f_extended] = '4' + + commit('make_hold', post, amount) + end + end + end + + def capture(amount, authorization, options={}) + identifier, original_amount = split_authorization(authorization) + if amount && (amount != original_amount) + raise ArgumentError.new("Partial capture is not supported, and #{amount.inspect} != #{original_amount.inspect}") + end + + post = PostData.new + add_credentials(post) + post[:init_transaction_id] = identifier + post[:f_extended] = '4' + + commit('charge_hold', post, original_amount) + end + + def refund(amount, authorization, options={}) + identifier, original_amount = split_authorization(authorization) + + post = PostData.new + add_credentials(post, :account_guid) + post[:init_transaction_id] = identifier + post[:amount_to_refund] = amount(amount || original_amount) + + commit('refund', post) + end + + def void(authorization, options={}) + identifier, amount = split_authorization(authorization) + + post = PostData.new + add_credentials(post, :account_guid) + post[:init_transaction_id] = identifier + post[:amount_to_refund] = amount(amount) + commit('cancel_dms', post) + end + + def verify(credit_card, options={}) + MultiResponse.run(:use_first_response) do |r| + r.process { authorize(100, credit_card, options) } + r.process(:ignore_result) { void(r.authorization, options) } + end + end + + private + + def add_customer_data(post, options) + post[:email] = (options[:email] || 'noone@example.com') + post[:user_ip] = (options[:ip] || '127.0.0.1') + end + + def add_address(post, creditcard, options) + if address = options[:billing_address] + post[:street] = address[:address1].to_s + post[:city] = address[:city].to_s + post[:state] = (address[:state].blank? ? 'NA' : address[:state].to_s) + post[:zip] = address[:zip].to_s + post[:country] = address[:country].to_s + post[:phone] = (address[:phone].to_s.gsub(/[^0-9]/, '') || '0000000') + end + + if address = options[:shipping_address] + post[:shipping_name] = "#{address.first_name} #{address.last_name}" + post[:shipping_street] = address[:address1].to_s + post[:shipping_phone] = address[:phone].to_s + post[:shipping_zip] = address[:zip].to_s + post[:shipping_city] = address[:city].to_s + post[:shipping_country] = address[:country].to_s + post[:shipping_state] = (address[:state].blank? ? 'NA' : address[:state].to_s) + post[:shipping_email] = (options[:email] || 'noone@example.com') + end + end + + def add_invoice(post, money, options) + post[:merchant_transaction_id] = options[:order_id] if options[:order_id] + post[:amount] = amount(money) + post[:currency] = (options[:currency] || currency(money)) + post[:description] = options[:description] + post[:merchant_site_url] = options[:merchant] + end + + def add_payment(post, payment) + post[:name_on_card] = "#{payment.first_name} #{payment.last_name}" + post[:card_bin] = payment.first_digits + end + + def add_payment_cc(post, credit_card) + post[:cc] = credit_card.number + post[:cvv] = credit_card.verification_value if credit_card.verification_value? + year = sprintf('%.4i', credit_card.year) + month = sprintf('%.2i', credit_card.month) + post[:expire] = "#{month}/#{year[2..3]}" + end + + def add_credentials(post, key=:guid) + post[key] = @options[:guid] + post[:pwd] = Digest::SHA1.hexdigest(@options[:password]) + end + + def parse(body) + if body =~ /^ID:/ + body.split('~').reduce(Hash.new) { |h, v| + m = v.match('(.*?):(.*)') + h.merge!(m[1].underscore.to_sym => m[2]) + } + elsif (m = body.match('(.*?):(.*)')) + m[1] == 'OK' ? + { status: 'success', id: m[2] } : + { status: 'failure', message: m[2] } + else + Hash[status: body] + end + end + + def commit(action, parameters, amount=nil) + url = (test? ? test_url : live_url) + response = parse(ssl_post(url, post_data(action, parameters))) + + Response.new( + success_from(response), + message_from(response), + response, + authorization: authorization_from(parameters, response, amount), + test: test? + ) + end + + def authorization_from(parameters, response, amount) + identifier = (response[:id] || parameters[:init_transaction_id]) + authorization = [identifier] + authorization << amount if amount + authorization.join('|') + end + + def split_authorization(authorization) + if authorization =~ /|/ + identifier, amount = authorization.split('|') + [identifier, amount.to_i] + else + authorization + end + end + + def success_from(response) + (response[:status] =~ /success/i || response[:status] =~ /ok/i) + end + + def message_from(response) + (response[:message] || response[:status]) + end + + def post_data(action, parameters = {}) + parameters[:a] = action + parameters.to_s + end + end + end +end diff --git a/lib/active_merchant/billing/gateways/transax.rb b/lib/active_merchant/billing/gateways/transax.rb index c11c830bac9..336a7fb31c3 100644 --- a/lib/active_merchant/billing/gateways/transax.rb +++ b/lib/active_merchant/billing/gateways/transax.rb @@ -1,23 +1,22 @@ -require File.join(File.dirname(__FILE__),'smart_ps.rb') +require File.join(File.dirname(__FILE__), 'smart_ps.rb') module ActiveMerchant #:nodoc: module Billing #:nodoc: class TransaxGateway < SmartPs self.live_url = self.test_url = 'https://secure.nelixtransax.net/api/transact.php' - + # The countries the gateway supports merchants from as 2 digit ISO country codes self.supported_countries = ['US'] - + # The card types supported by the payment gateway self.supported_cardtypes = [:visa, :master, :american_express, :discover] - + # The homepage URL of the gateway self.homepage_url = 'https://www.nelixtransax.com/' - + # The name of the gateway self.display_name = 'NELiX TransaX' end end end - diff --git a/lib/active_merchant/billing/gateways/transnational.rb b/lib/active_merchant/billing/gateways/transnational.rb index 5c49489ba80..350a2e91857 100644 --- a/lib/active_merchant/billing/gateways/transnational.rb +++ b/lib/active_merchant/billing/gateways/transnational.rb @@ -1,239 +1,9 @@ module ActiveMerchant #:nodoc: module Billing #:nodoc: - class TransnationalGateway < Gateway - self.live_url = self.test_url = 'https://secure.networkmerchants.com/api/transact.php' - - self.supported_countries = ['US'] - self.supported_cardtypes = [:visa, :master, :american_express, :discover] - + class TransnationalGateway < NetworkMerchantsGateway self.homepage_url = 'http://www.tnbci.com/' self.display_name = 'Transnational' - - self.money_format = :dollars - self.default_currency = 'USD' - - def initialize(options = {}) - requires!(options, :login, :password) - super - end - - def authorize(money, creditcard_or_vault_id, options = {}) - post = build_auth_post(money, creditcard_or_vault_id, options) - commit('auth', post) - end - - def purchase(money, creditcard_or_vault_id, options = {}) - post = build_purchase_post(money, creditcard_or_vault_id, options) - commit('sale', post) - end - - def capture(money, authorization, options = {}) - post = build_capture_post(money, authorization, options) - commit('capture', post) - end - - def void(authorization, options = {}) - post = build_void_post(authorization, options) - commit('void', post) - end - - def refund(money, authorization, options = {}) - post = build_refund_post(money, authorization, options) - commit('refund', post) - end - - def store(creditcard, options = {}) - post = build_store_post(creditcard, options) - commit_vault('add_customer', post) - end - - def unstore(customer_vault_id, options = {}) - post = build_unstore_post(customer_vault_id, options) - commit_vault('delete_customer', post) - end - - private - - def build_auth_post(money, creditcard_or_vault_id, options) - post = {} - add_order(post, options) - add_address(post, options) - add_shipping_address(post, options) - add_payment_method(post, creditcard_or_vault_id, options) - add_amount(post, money) - post - end - - def build_purchase_post(money, creditcard, options) - build_auth_post(money, creditcard, options) - end - - def build_capture_post(money, authorization, option) - post = {} - post[:transactionid] = authorization - add_amount(post, money) - post - end - - def build_void_post(authorization, options) - post = {} - post[:transactionid] = authorization - post - end - - def build_refund_post(money, authorization, options) - post = {} - post[:transactionid] = authorization - add_amount(post, money) - post - end - - def build_store_post(creditcard_or_check, options) - post = {} - add_address(post, options) - add_shipping_address(post, options) - add_payment_method(post, creditcard_or_check, options) - post - end - - def build_unstore_post(customer_vault_id, options) - post = {} - post['customer_vault_id'] = customer_vault_id - post - end - - def add_order(post, options) - post[:orderid] = options[:order_id] - post[:orderdescription] = options[:description] - end - - def add_address(post, options) - post[:email] = options[:email] - post[:ipaddress] = options[:ip] - - address = options[:billing_address] || options[:address] || {} - post[:address1] = address[:address1] - post[:address2] = address[:address2] - post[:city] = address[:city] - post[:state] = address[:state] - post[:zip] = address[:zip] - post[:country] = address[:country] - post[:phone] = address[:phone] - end - - def add_shipping_address(post, options) - shipping_address = options[:shipping_address] || {} - post[:shipping_address1] = shipping_address[:address1] - post[:shipping_address2] = shipping_address[:address2] - post[:shipping_city] = shipping_address[:city] - post[:shipping_state] = shipping_address[:state] - post[:shipping_zip] = shipping_address[:zip] - post[:shipping_country] = shipping_address[:country] - end - - def add_swipe_data(post, options) - # unencrypted tracks - post[:track_1] = options[:track_1] - post[:track_2] = options[:track_2] - post[:track_3] = options[:track_3] - - # encrypted tracks - post[:magnesafe_track_1] = options[:magnesafe_track_1] - post[:magnesafe_track_2] = options[:magnesafe_track_2] - post[:magnesafe_track_3] = options[:magnesafe_track_3] - post[:magnesafe_magneprint] = options[:magnesafe_magneprint] - post[:magnesafe_ksn] = options[:magnesafe_ksn] - post[:magnesafe_magneprint_status] = options[:magnesafe_magneprint_status] - end - - def add_payment_method(post, creditcard_or_check_or_vault_id, options) - post[:processor_id] = options[:processor_id] - post[:customer_vault] = 'add_customer' if options[:store] - - add_swipe_data(post, options) - - # creditcard_or_check can be blank if using swipe data - if creditcard_or_check_or_vault_id.is_a?(CreditCard) # creditcard or check - creditcard = creditcard_or_check_or_vault_id - post[:firstname] = creditcard.first_name - post[:lastname] = creditcard.last_name - post[:ccnumber] = creditcard.number - post[:ccexp] = format(creditcard.month, :two_digits) + format(creditcard.year, :two_digits) - post[:cvv] = creditcard.verification_value - post[:payment] = 'creditcard' - elsif creditcard_or_check_or_vault_id.is_a?(Check) - check = creditcard_or_check_or_vault_id - post[:firstname] = check.first_name - post[:lastname] = check.last_name - post[:checkname] = check.name - post[:checkaba] = check.routing_number - post[:checkaccount] = check.account_number - post[:account_type] = check.account_type - post[:account_holder_type] = check.account_holder_type - post[:payment] = 'check' - else - post[:customer_vault_id] = creditcard_or_check_or_vault_id - end - end - - def add_login(post) - post[:username] = @options[:login] - post[:password] = @options[:password] - end - - def add_amount(post, money) - post[:currency] = options[:currency] || currency(money) - post[:amount] = amount(money) - end - - def commit_vault(action, parameters) - commit(nil, parameters.merge(:customer_vault => action)) - end - - def commit(action, parameters) - raw = parse(ssl_post(self.live_url, build_request(action, parameters))) - - success = (raw['response'] == ResponseCodes::APPROVED) - - authorization = authorization_from(success, parameters, raw) - - Response.new(success, raw['responsetext'], raw, - :test => test?, - :authorization => authorization, - :avs_result => { :code => raw['avsresponse']}, - :cvv_result => raw['cvvresponse'] - ) - end - - def build_request(action, parameters) - parameters[:type] = action if action - add_login(parameters) - parameters.to_query - end - - def authorization_from(success, parameters, response) - return nil unless success - - authorization = response['transactionid'] - if(parameters[:customer_vault] && (authorization.nil? || authorization.empty?)) - authorization = response['customer_vault_id'] - end - - authorization - end - - class ResponseCodes - APPROVED = '1' - DENIED = '2' - ERROR = '3' - end - - def parse(raw_response) - rsp = CGI.parse(raw_response) - rsp.keys.each { |k| rsp[k] = rsp[k].first } # flatten out the values - rsp - end + self.supported_countries = ['US'] end end end - diff --git a/lib/active_merchant/billing/gateways/trexle.rb b/lib/active_merchant/billing/gateways/trexle.rb new file mode 100644 index 00000000000..42451539b2c --- /dev/null +++ b/lib/active_merchant/billing/gateways/trexle.rb @@ -0,0 +1,218 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class TrexleGateway < Gateway + self.test_url = 'https://core.trexle.com/api/v1' + self.live_url = 'https://core.trexle.com/api/v1' + + self.default_currency = 'USD' + self.money_format = :cents + self.supported_countries = %w(AD AE AT AU BD BE BG BN CA CH CY CZ DE DK EE EG ES FI FR GB + GI GR HK HU ID IE IL IM IN IS IT JO KW LB LI LK LT LU LV MC + MT MU MV MX MY NL NO NZ OM PH PL PT QA RO SA SE SG SI SK SM + TR TT UM US VA VN ZA) + self.supported_cardtypes = [:visa, :master, :american_express] + self.homepage_url = 'https://trexle.com' + self.display_name = 'Trexle' + + def initialize(options = {}) + requires!(options, :api_key) + super + end + + # Create a charge using a credit card, card token or customer token + # + # To charge a credit card: purchase([money], [creditcard hash], ...) + # To charge a customer: purchase([money], [token], ...) + def purchase(money, creditcard, options = {}) + post = {} + + add_amount(post, money, options) + add_customer_data(post, options) + add_invoice(post, options) + add_creditcard(post, creditcard) + add_address(post, creditcard, options) + commit(:post, 'charges', post, options) + end + + # Create a customer and associated credit card. The token that is returned + # can be used instead of a credit card parameter in the purchase method + def store(creditcard, options = {}) + post = {} + + add_creditcard(post, creditcard) + add_customer_data(post, options) + add_address(post, creditcard, options) + commit(:post, 'customers', post, options) + end + + # Refund a transaction + def refund(money, token, options = {}) + commit(:post, "charges/#{CGI.escape(token)}/refunds", { amount: amount(money) }, options) + end + + # Authorize an amount on a credit card. Once authorized, you can later + # capture this charge using the charge token that is returned. + def authorize(money, creditcard, options = {}) + post = {} + + add_amount(post, money, options) + add_customer_data(post, options) + add_invoice(post, options) + add_creditcard(post, creditcard) + add_address(post, creditcard, options) + post[:capture] = false + commit(:post, 'charges', post, options) + end + + # Captures a previously authorized charge. Capturing only part of the original + # authorization is currently not supported. + def capture(money, token, options = {}) + commit(:put, "charges/#{CGI.escape(token)}/capture", { amount: amount(money) }, options) + end + + # Updates the credit card for the customer. + def update(token, creditcard, options = {}) + post = {} + + add_creditcard(post, creditcard) + add_customer_data(post, options) + add_address(post, creditcard, options) + commit(:put, "customers/#{CGI.escape(token)}", post, options) + end + + def supports_scrubbing + true + end + + def scrub(transcript) + transcript. + gsub(%r((Authorization: Basic )\w+), '\1[FILTERED]'). + gsub(/(number\\?":\\?")(\d*)/, '\1[FILTERED]'). + gsub(/(cvc\\?":\\?")(\d*)/, '\1[FILTERED]') + end + + private + + def add_amount(post, money, options) + post[:amount] = amount(money) + post[:currency] = (options[:currency] || currency(money)) + post[:currency] = post[:currency].upcase if post[:currency] + end + + def add_customer_data(post, options) + post[:email] = options[:email] if options[:email] + post[:ip_address] = options[:ip] if options[:ip] + end + + def add_address(post, creditcard, options) + return if creditcard.kind_of?(String) + address = (options[:billing_address] || options[:address]) + return unless address + + post[:card] ||= {} + post[:card].merge!( + address_line1: address[:address1], + address_line2: address[:address_line2], + address_city: address[:city], + address_postcode: address[:zip], + address_state: address[:state], + address_country: address[:country] + ) + end + + def add_invoice(post, options) + post[:description] = options[:description] || 'Active Merchant Purchase' + end + + def add_creditcard(post, creditcard) + if creditcard.respond_to?(:number) + post[:card] ||= {} + + post[:card].merge!( + number: creditcard.number, + expiry_month: creditcard.month, + expiry_year: creditcard.year, + cvc: creditcard.verification_value, + name: creditcard.name + ) + elsif creditcard.kind_of?(String) + if creditcard =~ /^token_/ + post[:card_token] = creditcard + else + post[:customer_token] = creditcard + end + end + end + + def headers(params = {}) + result = { + 'Content-Type' => 'application/json', + 'Authorization' => "Basic #{Base64.strict_encode64(options[:api_key] + ':').strip}" + } + + result['X-Partner-Key'] = params[:partner_key] if params[:partner_key] + result['X-Safe-Card'] = params[:safe_card] if params[:safe_card] + result + end + + def commit(method, action, params, options) + url = "#{test? ? test_url : live_url}/#{action}" + raw_response = ssl_request(method, url, post_data(params), headers(options)) + parsed_response = parse(raw_response) + success_response(parsed_response) + rescue ResponseError => e + error_response(parse(e.response.body)) + rescue JSON::ParserError + unparsable_response(raw_response) + end + + def success_response(body) + return invalid_response unless body['response'] + + response = body['response'] + Response.new( + true, + response['status_message'], + body, + authorization: token(response), + test: test? + ) + end + + def error_response(body) + return invalid_response unless body['error'] + Response.new( + false, + body['error'], + body, + authorization: nil, + test: test? + ) + end + + def unparsable_response(raw_response) + message = 'Invalid JSON response received from Trexle. Please contact support@trexle.com if you continue to receive this message.' + message += " (The raw response returned by the API was #{raw_response.inspect})" + return Response.new(false, message) + end + + def invalid_response + message = 'Invalid response.' + return Response.new(false, message) + end + + def token(response) + response['token'] + end + + def parse(body) + return {} if body.blank? + JSON.parse(body) + end + + def post_data(parameters = {}) + parameters.to_json + end + end + end +end diff --git a/lib/active_merchant/billing/gateways/trust_commerce.rb b/lib/active_merchant/billing/gateways/trust_commerce.rb index e0a5b9c27dd..3018d9d1690 100644 --- a/lib/active_merchant/billing/gateways/trust_commerce.rb +++ b/lib/active_merchant/billing/gateways/trust_commerce.rb @@ -19,10 +19,10 @@ module Billing #:nodoc: # Next, create a credit card object using a TC approved test card. # # creditcard = ActiveMerchant::Billing::CreditCard.new( - # :number => '4111111111111111', - # :month => 8, - # :year => 2006, - # :first_name => 'Longbob', + # :number => '4111111111111111', + # :month => 8, + # :year => 2006, + # :first_name => 'Longbob', # :last_name => 'Longsen' # ) # @@ -67,43 +67,45 @@ module Billing #:nodoc: class TrustCommerceGateway < Gateway self.live_url = self.test_url = 'https://vault.trustcommerce.com/trans/' - SUCCESS_TYPES = ["approved", "accepted"] + SUCCESS_TYPES = ['approved', 'accepted'] DECLINE_CODES = { - "decline" => "The credit card was declined", - "avs" => "AVS failed; the address entered does not match the billing address on file at the bank", - "cvv" => "CVV failed; the number provided is not the correct verification number for the card", - "call" => "The card must be authorized manually over the phone", - "expiredcard" => "Issuer was not certified for card verification", - "carderror" => "Card number is invalid", - "authexpired" => "Attempt to postauth an expired (more than 14 days old) preauth", - "fraud" => "CrediGuard fraud score was below requested threshold", - "blacklist" => "CrediGuard blacklist value was triggered", - "velocity" => "CrediGuard velocity control value was triggered", - "dailylimit" => "Daily limit in transaction count or amount as been reached", - "weeklylimit" => "Weekly limit in transaction count or amount as been reached", - "monthlylimit" => "Monthly limit in transaction count or amount as been reached" + 'decline' => 'The credit card was declined', + 'avs' => 'AVS failed; the address entered does not match the billing address on file at the bank', + 'cvv' => 'CVV failed; the number provided is not the correct verification number for the card', + 'call' => 'The card must be authorized manually over the phone', + 'expiredcard' => 'Issuer was not certified for card verification', + 'carderror' => 'Card number is invalid', + 'authexpired' => 'Attempt to postauth an expired (more than 14 days old) preauth', + 'fraud' => 'CrediGuard fraud score was below requested threshold', + 'blacklist' => 'CrediGuard blacklist value was triggered', + 'velocity' => 'CrediGuard velocity control value was triggered', + 'dailylimit' => 'Daily limit in transaction count or amount as been reached', + 'weeklylimit' => 'Weekly limit in transaction count or amount as been reached', + 'monthlylimit' => 'Monthly limit in transaction count or amount as been reached' } BADDATA_CODES = { - "missingfields" => "One or more parameters required for this transaction type were not sent", - "extrafields" => "Parameters not allowed for this transaction type were sent", - "badformat" => "A field was improperly formatted, such as non-digit characters in a number field", - "badlength" => "A field was longer or shorter than the server allows", - "merchantcantaccept" => "The merchant can't accept data passed in this field", - "mismatch" => "Data in one of the offending fields did not cross-check with the other offending field" + 'missingfields' => 'One or more parameters required for this transaction type were not sent', + 'extrafields' => 'Parameters not allowed for this transaction type were sent', + 'badformat' => 'A field was improperly formatted, such as non-digit characters in a number field', + 'badlength' => 'A field was longer or shorter than the server allows', + 'merchantcantaccept' => "The merchant can't accept data passed in this field", + 'mismatch' => 'Data in one of the offending fields did not cross-check with the other offending field' } ERROR_CODES = { - "cantconnect" => "Couldn't connect to the TrustCommerce gateway", - "dnsfailure" => "The TCLink software was unable to resolve DNS hostnames", - "linkfailure" => "The connection was established, but was severed before the transaction could complete", - "failtoprocess" => "The bank servers are offline and unable to authorize transactions" + 'cantconnect' => "Couldn't connect to the TrustCommerce gateway", + 'dnsfailure' => 'The TCLink software was unable to resolve DNS hostnames', + 'linkfailure' => 'The connection was established, but was severed before the transaction could complete', + 'failtoprocess' => 'The bank servers are offline and unable to authorize transactions' } TEST_LOGIN = 'TestMerchant' TEST_PASSWORD = 'password' + VOIDABLE_ACTIONS = %w(preauth sale postauth credit) + self.money_format = :cents self.supported_cardtypes = [:visa, :master, :discover, :american_express, :diners_club, :jcb] self.supported_countries = ['US'] @@ -153,9 +155,12 @@ def authorize(money, creditcard_or_billing_id, options = {}) } add_order_id(parameters, options) + add_aggregator(parameters, options) add_customer_data(parameters, options) add_payment_source(parameters, creditcard_or_billing_id) add_addresses(parameters, options) + add_custom_fields(parameters, options) + commit('preauth', parameters) end @@ -167,9 +172,12 @@ def purchase(money, creditcard_or_billing_id, options = {}) } add_order_id(parameters, options) + add_aggregator(parameters, options) add_customer_data(parameters, options) add_payment_source(parameters, creditcard_or_billing_id) add_addresses(parameters, options) + add_custom_fields(parameters, options) + commit('sale', parameters) end @@ -177,10 +185,13 @@ def purchase(money, creditcard_or_billing_id, options = {}) # postauth, we preserve active_merchant's nomenclature of capture() for consistency with the rest of the library. To process # a postauthorization with TC, you need an amount in cents or a money object, and a TC transid. def capture(money, authorization, options = {}) + transaction_id, _ = split_authorization(authorization) parameters = { :amount => amount(money), - :transid => authorization, + :transid => transaction_id, } + add_aggregator(parameters, options) + add_custom_fields(parameters, options) commit('postauth', parameters) end @@ -188,16 +199,21 @@ def capture(money, authorization, options = {}) # refund() allows you to return money to a card that was previously billed. You need to supply the amount, in cents or a money object, # that you want to refund, and a TC transid for the transaction that you are refunding. def refund(money, identification, options = {}) + transaction_id, _ = split_authorization(identification) + parameters = { :amount => amount(money), - :transid => identification + :transid => transaction_id } + add_aggregator(parameters, options) + add_custom_fields(parameters, options) + commit('credit', parameters) end def credit(money, identification, options = {}) - deprecated CREDIT_DEPRECATION_MESSAGE + ActiveMerchant.deprecated CREDIT_DEPRECATION_MESSAGE refund(money, identification, options) end @@ -210,20 +226,29 @@ def credit(money, identification, options = {}) # TrustCommerce to allow for reversal transactions before you can use this # method. # + # void() is also used to to cancel a capture (postauth), purchase (sale), + # or refund (credit) or a before it is sent for settlement. + # # NOTE: AMEX preauth's cannot be reversed. If you want to clear it more # quickly than the automatic expiration (7-10 days), you will have to # capture it and then immediately issue a credit for the same amount # which should clear the customers credit card with 48 hours according to # TC. def void(authorization, options = {}) + transaction_id, original_action = split_authorization(authorization) + action = (VOIDABLE_ACTIONS - ['preauth']).include?(original_action) ? 'void' : 'reversal' + parameters = { - :transid => authorization, + :transid => transaction_id, } - commit('reversal', parameters) + add_aggregator(parameters, options) + add_custom_fields(parameters, options) + + commit(action, parameters) end - # recurring() a TrustCommerce account that is activated for Citatdel, TrustCommerce's + # recurring() a TrustCommerce account that is activated for Citadel, TrustCommerce's # hosted customer billing info database. # # Recurring billing uses the same TC action as a plain-vanilla 'store', but we have a separate method for clarity. It can be called @@ -235,7 +260,9 @@ def void(authorization, options = {}) # # You can optionally specify how long you want payments to continue using 'payments' def recurring(money, creditcard, options = {}) - requires!(options, [:periodicity, :bimonthly, :monthly, :biweekly, :weekly, :yearly, :daily] ) + ActiveMerchant.deprecated RECURRING_DEPRECATION_MESSAGE + + requires!(options, [:periodicity, :bimonthly, :monthly, :biweekly, :weekly, :yearly, :daily]) cycle = case options[:periodicity] when :monthly @@ -265,7 +292,7 @@ def recurring(money, creditcard, options = {}) commit('store', parameters) end - # store() requires a TrustCommerce account that is activated for Citatdel. You can call it with a credit card and a billing ID + # store() requires a TrustCommerce account that is activated for Citadel. You can call it with a credit card and a billing ID # you would like to use to reference the stored credit card info for future captures. Use 'verify' to specify whether you want # to simply store the card in the DB, or you want TC to verify the data first. @@ -277,6 +304,8 @@ def store(creditcard, options = {}) add_creditcard(parameters, creditcard) add_addresses(parameters, options) + add_custom_fields(parameters, options) + commit('store', parameters) end @@ -287,27 +316,58 @@ def unstore(identification, options = {}) :billingid => identification, } + add_custom_fields(parameters, options) + commit('unstore', parameters) end + def supports_scrubbing + true + end + + def scrub(transcript) + transcript. + gsub(%r((Authorization: Basic )\w+), '\1[FILTERED]'). + gsub(%r((&?cc=)\d*(&?)), '\1[FILTERED]\2'). + gsub(%r((&?password=)[^&]+(&?)), '\1[FILTERED]\2'). + gsub(%r((&?cvv=)\d*(&?)), '\1[FILTERED]\2') + end + private + + def add_custom_fields(params, options) + options[:custom_fields]&.each do |key, value| + params[key.to_sym] = value + end + end + + def add_aggregator(params, options) + if @options[:aggregator_id] || application_id != Gateway.application_id + params[:aggregators] = 1 + params[:aggregator1] = @options[:aggregator_id] || application_id + end + end + def add_payment_source(params, source) if source.is_a?(String) add_billing_id(params, source) + elsif card_brand(source) == 'check' + add_check(params, source) else add_creditcard(params, source) end end - def expdate(creditcard) - year = sprintf("%.4i", creditcard.year) - month = sprintf("%.2i", creditcard.month) - - "#{month}#{year[-2..-1]}" + def add_check(params, check) + params[:media] = 'ach' + params[:routing] = check.routing_number + params[:account] = check.account_number + params[:savings] = 'y' if check.account_type == 'savings' + params[:name] = check.name end def add_creditcard(params, creditcard) - params[:media] = "cc" + params[:media] = 'cc' params[:name] = creditcard.name params[:cc] = creditcard.number params[:exp] = expdate(creditcard) @@ -346,7 +406,7 @@ def add_addresses(params, options) params[:shipto_address2] = shipping_address[:address2] unless shipping_address[:address2].blank? params[:shipto_city] = shipping_address[:city] unless shipping_address[:city].blank? params[:shipto_state] = shipping_address[:state] unless shipping_address[:state].blank? - params[:shipto_zip] = shipping_address[:zip] unless shipping_address[:zip].blank? + params[:shipto_zip] = shipping_address[:zip] unless shipping_address[:zip].blank? params[:shipto_country] = shipping_address[:country] unless shipping_address[:country].blank? end end @@ -355,7 +415,7 @@ def clean_and_stringify_params(parameters) # TCLink wants us to send a hash with string keys, and activemerchant pushes everything around with # symbol keys. Before sending our input to TCLink, we convert all our keys to strings and dump the symbol keys. # We also remove any pairs with nil values, as these confuse TCLink. - parameters.keys.reverse.each do |key| + parameters.keys.reverse_each do |key| if parameters[key] parameters[key.to_s] = parameters[key] end @@ -364,7 +424,7 @@ def clean_and_stringify_params(parameters) end def post_data(parameters) - parameters.collect { |key, value| "#{key}=#{ CGI.escape(value.to_s)}" }.join("&") + parameters.collect { |key, value| "#{key}=#{CGI.escape(value.to_s)}" }.join('&') end def commit(action, parameters) @@ -376,19 +436,19 @@ def commit(action, parameters) clean_and_stringify_params(parameters) data = if tclink? - TCLink.send(parameters) - else - parse( ssl_post(self.live_url, post_data(parameters)) ) - end + TCLink.send(parameters) + else + parse(ssl_post(self.live_url, post_data(parameters))) + end # to be considered successful, transaction status must be either "approved" or "accepted" - success = SUCCESS_TYPES.include?(data["status"]) + success = SUCCESS_TYPES.include?(data['status']) message = message_from(data) Response.new(success, message, data, :test => test?, - :authorization => data["transid"], - :cvv_result => data["cvv"], - :avs_result => { :code => data["avs"] } + :authorization => authorization_from(action, data), + :cvv_result => data['cvv'], + :avs_result => { :code => data['avs'] } ) end @@ -396,7 +456,7 @@ def parse(body) results = {} body.split(/\n/).each do |pair| - key,val = pair.split(/=/) + key, val = pair.split(/=/) results[key] = val end @@ -404,18 +464,27 @@ def parse(body) end def message_from(data) - status = case data["status"] - when "decline" - return DECLINE_CODES[data["declinetype"]] - when "baddata" - return BADDATA_CODES[data["error"]] - when "error" - return ERROR_CODES[data["errortype"]] + case data['status'] + when 'decline' + return DECLINE_CODES[data['declinetype']] + when 'baddata' + return BADDATA_CODES[data['error']] + when 'error' + return ERROR_CODES[data['errortype']] else - return "The transaction was successful" + return 'The transaction was successful' end end + def authorization_from(action, data) + authorization = data['transid'] + authorization = "#{authorization}|#{action}" if authorization && VOIDABLE_ACTIONS.include?(action) + authorization + end + + def split_authorization(authorization) + authorization&.split('|') + end end end end diff --git a/lib/active_merchant/billing/gateways/usa_epay.rb b/lib/active_merchant/billing/gateways/usa_epay.rb index 2ca57cdd0cf..0558311bc11 100644 --- a/lib/active_merchant/billing/gateways/usa_epay.rb +++ b/lib/active_merchant/billing/gateways/usa_epay.rb @@ -9,15 +9,15 @@ class UsaEpayGateway < Gateway self.abstract_class = true ## - # Creates an instance of UsaEpayTransactionGateway by default, but if + # Creates an instance of UsaEpayTransactionGateway by default, but if # :software id or :live_url are passed in the options hash it will # create an instance of UsaEpayAdvancedGateway. # def self.new(options={}) - unless options.has_key?(:software_id) || options.has_key?(:live_url) - UsaEpayTransactionGateway.new(options) - else + if options.has_key?(:software_id) || options.has_key?(:live_url) UsaEpayAdvancedGateway.new(options) + else + UsaEpayTransactionGateway.new(options) end end end diff --git a/lib/active_merchant/billing/gateways/usa_epay_advanced.rb b/lib/active_merchant/billing/gateways/usa_epay_advanced.rb index 6b3faeb312b..23077a0fd87 100644 --- a/lib/active_merchant/billing/gateways/usa_epay_advanced.rb +++ b/lib/active_merchant/billing/gateways/usa_epay_advanced.rb @@ -5,7 +5,7 @@ module ActiveMerchant #:nodoc: module Billing #:nodoc: # ==== USA ePay Advanced SOAP Interface # - # This class encapuslates USA ePay's Advanced SOAP Interface. The Advanced Soap Interface allows + # This class encapsulates USA ePay's Advanced SOAP Interface. The Advanced Soap Interface allows # standard transactions, storing customer information, and recurring billing. Storing sensitive # information on USA ePay's servers can help with PCI DSS compliance, since customer and card data # do not need to be stored locally. @@ -62,7 +62,7 @@ module Billing #:nodoc: # * {USA ePay Developer Login}[https://www.usaepay.com/developer/login] # class UsaEpayAdvancedGateway < Gateway - API_VERSION = "1.4" + API_VERSION = '1.4' TEST_URL_BASE = 'https://sandbox.usaepay.com/soap/gate/' #:nodoc: LIVE_URL_BASE = 'https://www.usaepay.com/soap/gate/' #:nodoc: @@ -70,19 +70,21 @@ class UsaEpayAdvancedGateway < Gateway self.test_url = TEST_URL_BASE self.live_url = LIVE_URL_BASE - FAILURE_MESSAGE = "Default Failure" #:nodoc: + FAILURE_MESSAGE = 'Default Failure' #:nodoc: self.supported_countries = ['US'] self.supported_cardtypes = [:visa, :master, :american_express, :discover, :diners_club, :jcb] self.homepage_url = 'http://www.usaepay.com/' self.display_name = 'USA ePay Advanced SOAP Interface' - CUSTOMER_OPTIONS = { + CUSTOMER_PROFILE_OPTIONS = { :id => [:string, 'CustomerID'], # merchant assigned number :notes => [:string, 'Notes'], :data => [:string, 'CustomData'], - :url => [:string, 'URL'], - # Recurring Billing + :url => [:string, 'URL'] + } #:nodoc: + + CUSTOMER_RECURRING_BILLING_OPTIONS = { :enabled => [:boolean, 'Enabled'], :schedule => [:string, 'Schedule'], :number_left => [:integer, 'NumLeft'], @@ -92,18 +94,24 @@ class UsaEpayAdvancedGateway < Gateway :user => [:string, 'User'], :source => [:string, 'Source'], :send_receipt => [:boolean, 'SendReceipt'], - :receipt_note => [:string, 'ReceiptNote'], - # Point of Sale + :receipt_note => [:string, 'ReceiptNote'] + } #:nodoc: + + CUSTOMER_POINT_OF_SALE_OPTIONS = { :price_tier => [:string, 'PriceTier'], :tax_class => [:string, 'TaxClass'], :lookup_code => [:string, 'LookupCode'] } #:nodoc: - ADDRESS_OPTIONS = { + CUSTOMER_OPTIONS = [ + CUSTOMER_PROFILE_OPTIONS, + CUSTOMER_RECURRING_BILLING_OPTIONS, + CUSTOMER_POINT_OF_SALE_OPTIONS + ].inject(:merge) #:nodoc: + + COMMON_ADDRESS_OPTIONS = { :first_name => [:string, 'FirstName'], :last_name => [:string, 'LastName'], - :address1 => [:string, 'Street'], - :address2 => [:string, 'Street2'], :city => [:string, 'City'], :state => [:string, 'State'], :zip => [:string, 'Zip'], @@ -114,6 +122,32 @@ class UsaEpayAdvancedGateway < Gateway :company => [:string, 'Company'] } #:nodoc: + ADDRESS_OPTIONS = [ + COMMON_ADDRESS_OPTIONS, + { + :address1 => [:string, 'Street'], + :address2 => [:string, 'Street2'], + } + ].inject(:merge) #:nodoc + + CUSTOMER_UPDATE_DATA_FIELDS = [ + CUSTOMER_PROFILE_OPTIONS, + CUSTOMER_RECURRING_BILLING_OPTIONS, + COMMON_ADDRESS_OPTIONS, + { + :address1 => [:string, 'Address'], + :address2 => [:string, 'Address2'], + }, + { + :card_number => [:string, 'CardNumber'], + :card_exp => [:string, 'CardExp'], + :account => [:string, 'Account'], + :routing => [:string, 'Routing'], + :check_format => [:string, 'CheckFormat'], + :record_type => [:string, 'RecordType'], + } + ].inject(:merge) #:nodoc + CUSTOMER_TRANSACTION_REQUEST_OPTIONS = { :command => [:string, 'Command'], :ignore_duplicate => [:boolean, 'IgnoreDuplicate'], @@ -124,7 +158,8 @@ class UsaEpayAdvancedGateway < Gateway :merchant_receipt => [:boolean, 'MerchReceipt'], :merchant_email => [:boolean, 'MerchReceiptEmail'], :merchant_template => [:boolean, 'MerchReceiptName'], - :verification_value => [:boolean, 'isRecurring'], + :recurring => [:boolean, 'isRecurring'], + :verification_value => [:string, 'CardCode'], :software => [:string, 'Software'] } #:nodoc: @@ -179,7 +214,6 @@ class UsaEpayAdvancedGateway < Gateway } #:nodoc: CHECK_DATA_OPTIONS = { - :check_number => [:integer, 'CheckNumber'], :drivers_license => [:string, 'DriversLicense'], :drivers_license_state => [:string, 'DriversLicenseState'], :record_type => [:string, 'RecordType'], @@ -238,8 +272,8 @@ def initialize(options = {}) requires!(options, :login, :password) if options[:software_id] - self.live_url = "#{LIVE_URL_BASE}#{options[:software_id].to_s}" - self.test_url = "#{TEST_URL_BASE}#{options[:software_id].to_s}" + self.live_url = "#{LIVE_URL_BASE}#{options[:software_id]}" + self.test_url = "#{TEST_URL_BASE}#{options[:software_id]}" else self.live_url = options[:live_url].to_s self.test_url = options[:test_url].to_s if options[:test_url] @@ -292,7 +326,7 @@ def refund(money, identification, options={}) end def credit(money, identification, options={}) - deprecated CREDIT_DEPRECATION_MESSAGE + ActiveMerchant.deprecated CREDIT_DEPRECATION_MESSAGE refund(money, identification, options) end @@ -339,9 +373,10 @@ def add_customer(options={}) commit(__method__, request) end - # Update a customer by replacing all of the customer details.. + # Update a customer by replacing all of the customer details. # - # Use quickUpdateCustomer to just update a few attributes. + # ==== Required + # * <tt>:customer_number</tt> -- customer to update # # ==== Options # * Same as add_customer @@ -353,9 +388,58 @@ def update_customer(options={}) commit(__method__, request) end + # Update a customer by replacing only the provided fields. + # + # ==== Required + # * <tt>:customer_number</tt> -- customer to update + # * <tt>:update_data</tt> -- FieldValue array of fields to retrieve + # * <tt>:first_name</tt> + # * <tt>:last_name</tt> + # * <tt>:id</tt> + # * <tt>:company</tt> + # * <tt>:address</tt> + # * <tt>:address2</tt> + # * <tt>:city</tt> + # * <tt>:state</tt> + # * <tt>:zip</tt> + # * <tt>:country</tt> + # * <tt>:phone</tt> + # * <tt>:fax</tt> + # * <tt>:email</tt> + # * <tt>:url</tt> + # * <tt>:receipt_note</tt> + # * <tt>:send_receipt</tt> + # * <tt>:notes</tt> + # * <tt>:description</tt> + # * <tt>:order_id</tt> + # * <tt>:enabled</tt> + # * <tt>:schedule</tt> + # * <tt>:next</tt> + # * <tt>:num_left</tt> + # * <tt>:amount</tt> + # * <tt>:custom_data</tt> + # * <tt>:source</tt> + # * <tt>:user</tt> + # * <tt>:card_number</tt> + # * <tt>:card_exp</tt> + # * <tt>:account</tt> + # * <tt>:routing</tt> + # * <tt>:check_format</tt> or <tt>:record_type</tt> + # + # ==== Response + # * <tt>#message</tt> -- boolean; Returns true if successful. Exception thrown all failures. + # + def quick_update_customer(options={}) + requires! options, :customer_number + requires! options, :update_data + + request = build_request(__method__, options) + commit(__method__, request) + end + # Enable a customer for recurring billing. # - # Note: Customer does not need to have all recurring paramerters to succeed. + # Note: Customer does not need to have all recurring parameters to succeed. # # ==== Required # * <tt>:customer_number</tt> @@ -402,7 +486,7 @@ def add_customer_payment_method(options={}) commit(__method__, request) end - # Retrive all of the payment methods belonging to a customer + # Retrieve all of the payment methods belonging to a customer # # ==== Required # * <tt>:customer_number</tt> @@ -417,7 +501,7 @@ def get_customer_payment_methods(options={}) commit(__method__, request) end - # Retrive one of the payment methods belonging to a customer + # Retrieve one of the payment methods belonging to a customer # # ==== Required # * <tt>:customer_number</tt> @@ -454,7 +538,7 @@ def update_customer_payment_method(options={}) commit(__method__, request) end - # Delete one the payment methods beloning to a customer + # Delete one the payment methods belonging to a customer # # ==== Required # * <tt>:customer_number</tt> @@ -543,11 +627,11 @@ def run_customer_transaction(options={}) # will be returned in the response. # # ==== Options - # * <tt>:method</tt> -- credit_card or check + # * <tt>:payment_method</tt> -- credit_card or check # * <tt>:command</tt> -- sale, credit, void, creditvoid, authonly, capture, postauth, check, checkcredit; defaults to sale; only required for run_transaction when other than sale # * <tt>:reference_number</tt> -- for the original transaction; obtained by sale or authonly # * <tt>:authorization_code</tt> -- required for postauth; obtained offline - # * <tt>:ignore_duplicate</tt> -- set +true+ if you want to override the duplicate tranaction handling + # * <tt>:ignore_duplicate</tt> -- set +true+ if you want to override the duplicate transaction handling # * <tt>:account_holder</tt> -- name of account holder # * <tt>:customer_id</tt> -- merchant assigned id # * <tt>:customer_receipt</tt> -- set +true+ to email receipt to billing email address @@ -680,7 +764,7 @@ def refund_transaction(options={}) commit(__method__, request) end - # Override transaction flagged for mananager approval. + # Override transaction flagged for manager approval. # # Note: Checks only! # @@ -948,14 +1032,14 @@ def get_account_details def build_request(action, options = {}) soap = Builder::XmlMarkup.new soap.instruct!(:xml, :version => '1.0', :encoding => 'utf-8') - soap.tag! "SOAP-ENV:Envelope", + soap.tag! 'SOAP-ENV:Envelope', 'xmlns:SOAP-ENV' => 'http://schemas.xmlsoap.org/soap/envelope/', 'xmlns:ns1' => 'urn:usaepay', 'xmlns:xsd' => 'http://www.w3.org/2001/XMLSchema', 'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance', 'xmlns:SOAP-ENC' => 'http://schemas.xmlsoap.org/soap/encoding/', - 'SOAP-ENV:encodingStyle' => 'http://schemas.xmlsoap.org/soap/encoding/' do |soap| - soap.tag! "SOAP-ENV:Body" do |soap| + 'SOAP-ENV:encodingStyle' => 'http://schemas.xmlsoap.org/soap/encoding/' do + soap.tag! 'SOAP-ENV:Body' do send("build_#{action}", soap, options) end end @@ -971,12 +1055,12 @@ def build_tag(soap, type, tag, value) def build_token(soap, options) seed = SecureRandom.base64(32) hash = Digest::SHA1.hexdigest("#{@options[:login]}#{seed}#{@options[:password].to_s.strip}") - soap.Token 'xsi:type' => 'ns1:ueSecurityToken' do |soap| + soap.Token 'xsi:type' => 'ns1:ueSecurityToken' do build_tag soap, :string, 'ClientIP', options[:client_ip] - soap.PinHash 'xsi:type' => 'ns1:ueHash' do |soap| - build_tag soap, :string, "HashValue", hash - build_tag soap, :string, "Seed", seed - build_tag soap, :string, "Type", 'sha1' + soap.PinHash 'xsi:type' => 'ns1:ueHash' do + build_tag soap, :string, 'HashValue', hash + build_tag soap, :string, 'Seed', seed + build_tag soap, :string, 'Type', 'sha1' end build_tag soap, :string, 'SourceKey', @options[:login] end @@ -985,17 +1069,17 @@ def build_token(soap, options) # Customer ====================================================== def build_add_customer(soap, options) - soap.tag! "ns1:addCustomer" do |soap| + soap.tag! 'ns1:addCustomer' do build_token soap, options build_customer_data soap, options build_tag soap, :double, 'Amount', amount(options[:amount]) build_tag soap, :double, 'Tax', amount(options[:tax]) - build_tag soap, :string, 'Next', options[:next].strftime("%Y-%m-%d") if options[:next] + build_tag soap, :string, 'Next', options[:next].strftime('%Y-%m-%d') if options[:next] end end def build_customer(soap, options, type, add_customer_data=false) - soap.tag! "ns1:#{type}" do |soap| + soap.tag! "ns1:#{type}" do build_token soap, options build_tag soap, :integer, 'CustNum', options[:customer_number] build_customer_data soap, options if add_customer_data @@ -1018,8 +1102,16 @@ def build_delete_customer(soap, options) build_customer(soap, options, 'deleteCustomer') end + def build_quick_update_customer(soap, options) + soap.tag! 'ns1:quickUpdateCustomer' do + build_token soap, options + build_tag soap, :integer, 'CustNum', options[:customer_number] + build_field_value_array soap, 'UpdateData', 'FieldValue', options[:update_data], CUSTOMER_UPDATE_DATA_FIELDS + end + end + def build_add_customer_payment_method(soap, options) - soap.tag! "ns1:addCustomerPaymentMethod" do |soap| + soap.tag! 'ns1:addCustomerPaymentMethod' do build_token soap, options build_tag soap, :integer, 'CustNum', options[:customer_number] build_customer_payment_methods soap, options @@ -1029,7 +1121,7 @@ def build_add_customer_payment_method(soap, options) end def build_get_customer_payment_method(soap, options) - soap.tag! 'ns1:getCustomerPaymentMethod' do |soap| + soap.tag! 'ns1:getCustomerPaymentMethod' do build_token soap, options build_tag soap, :integer, 'CustNum', options[:customer_number] build_tag soap, :integer, 'MethodID', options[:method_id] @@ -1041,7 +1133,7 @@ def build_get_customer_payment_methods(soap, options) end def build_update_customer_payment_method(soap, options) - soap.tag! 'ns1:updateCustomerPaymentMethod' do |soap| + soap.tag! 'ns1:updateCustomerPaymentMethod' do build_token soap, options build_customer_payment_methods soap, options build_tag soap, :boolean, 'Verify', options[:verify] @@ -1049,7 +1141,7 @@ def build_update_customer_payment_method(soap, options) end def build_delete_customer_payment_method(soap, options) - soap.tag! "ns1:deleteCustomerPaymentMethod" do |soap| + soap.tag! 'ns1:deleteCustomerPaymentMethod' do build_token soap, options build_tag soap, :integer, 'Custnum', options[:customer_number] build_tag soap, :integer, 'PaymentMethodID', options[:method_id] @@ -1057,7 +1149,7 @@ def build_delete_customer_payment_method(soap, options) end def build_run_customer_transaction(soap, options) - soap.tag! "ns1:runCustomerTransaction" do |soap| + soap.tag! 'ns1:runCustomerTransaction' do build_token soap, options build_tag soap, :integer, 'CustNum', options[:customer_number] build_tag soap, :integer, 'PaymentMethodID', options[:method_id] || 0 @@ -1068,56 +1160,56 @@ def build_run_customer_transaction(soap, options) # Transactions ================================================== def build_run_transaction(soap, options) - soap.tag! 'ns1:runTransaction' do |soap| + soap.tag! 'ns1:runTransaction' do build_token soap, options build_transaction_request_object soap, options, 'Parameters' end end def build_run_sale(soap, options) - soap.tag! 'ns1:runSale' do |soap| + soap.tag! 'ns1:runSale' do build_token soap, options build_transaction_request_object soap, options end end def build_run_auth_only(soap, options) - soap.tag! 'ns1:runAuthOnly' do |soap| + soap.tag! 'ns1:runAuthOnly' do build_token soap, options build_transaction_request_object soap, options end end def build_run_credit(soap, options) - soap.tag! 'ns1:runCredit' do |soap| + soap.tag! 'ns1:runCredit' do build_token soap, options build_transaction_request_object soap, options end end def build_run_check_sale(soap, options) - soap.tag! 'ns1:runCheckSale' do |soap| + soap.tag! 'ns1:runCheckSale' do build_token soap, options build_transaction_request_object soap, options end end def build_run_check_credit(soap, options) - soap.tag! 'ns1:runCheckCredit' do |soap| + soap.tag! 'ns1:runCheckCredit' do build_token soap, options build_transaction_request_object soap, options end end def build_post_auth(soap, options) - soap.tag! 'ns1:postAuth' do |soap| + soap.tag! 'ns1:postAuth' do build_token soap, options build_transaction_request_object soap, options end end def build_run_quick_sale(soap, options) - soap.tag! 'ns1:runQuickSale' do |soap| + soap.tag! 'ns1:runQuickSale' do build_token soap, options build_tag soap, :integer, 'RefNum', options[:reference_number] build_transaction_detail soap, options @@ -1126,7 +1218,7 @@ def build_run_quick_sale(soap, options) end def build_run_quick_credit(soap, options) - soap.tag! 'ns1:runQuickCredit' do |soap| + soap.tag! 'ns1:runQuickCredit' do build_token soap, options build_tag soap, :integer, 'RefNum', options[:reference_number] build_transaction_detail soap, options @@ -1134,21 +1226,21 @@ def build_run_quick_credit(soap, options) end def build_get_transaction(soap, options) - soap.tag! "ns1:getTransaction" do |soap| + soap.tag! 'ns1:getTransaction' do build_token soap, options build_tag soap, :integer, 'RefNum', options[:reference_number] end end def build_get_transaction_status(soap, options) - soap.tag! "ns1:getTransactionStatus" do |soap| + soap.tag! 'ns1:getTransactionStatus' do build_token soap, options build_tag soap, :integer, 'RefNum', options[:reference_number] end end def build_get_transaction_custom(soap, options) - soap.tag! "ns1:getTransactionCustom" do |soap| + soap.tag! 'ns1:getTransactionCustom' do build_token soap, options build_tag soap, :integer, 'RefNum', options[:reference_number] build_transaction_field_array soap, options @@ -1156,29 +1248,29 @@ def build_get_transaction_custom(soap, options) end def build_get_check_trace(soap, options) - soap.tag! "ns1:getCheckTrace" do |soap| + soap.tag! 'ns1:getCheckTrace' do build_token soap, options build_tag soap, :integer, 'RefNum', options[:reference_number] end end def build_capture_transaction(soap, options) - soap.tag! "ns1:captureTransaction" do |soap| + soap.tag! 'ns1:captureTransaction' do build_token soap, options build_tag soap, :integer, 'RefNum', options[:reference_number] - build_tag soap, :double, 'RefNum', amount(options[:amount]) + build_tag soap, :double, 'Amount', amount(options[:amount]) end end def build_void_transaction(soap, options) - soap.tag! "ns1:voidTransaction" do |soap| + soap.tag! 'ns1:voidTransaction' do build_token soap, options build_tag soap, :integer, 'RefNum', options[:reference_number] end end def build_refund_transaction(soap, options) - soap.tag! "ns1:refundTransaction" do |soap| + soap.tag! 'ns1:refundTransaction' do build_token soap, options build_tag soap, :integer, 'RefNum', options[:reference_number] build_tag soap, :integer, 'Amount', amount(options[:amount]) @@ -1186,7 +1278,7 @@ def build_refund_transaction(soap, options) end def build_override_transaction(soap, options) - soap.tag! "ns1:overrideTransaction" do |soap| + soap.tag! 'ns1:overrideTransaction' do build_token soap, options build_tag soap, :integer, 'RefNum', options[:reference_number] build_tag soap, :string, 'Reason', options[:reason] @@ -1196,7 +1288,7 @@ def build_override_transaction(soap, options) # Account ======================================================= def build_get_account_details(soap, options) - soap.tag! "ns1:getAccountDetails" do |soap| + soap.tag! 'ns1:getAccountDetails' do build_token soap, options end end @@ -1205,7 +1297,7 @@ def build_get_account_details(soap, options) def build_customer_data(soap, options) soap.CustomerData 'xsi:type' => 'ns1:CustomerObject' do - CUSTOMER_OPTIONS.each do |k,v| + CUSTOMER_OPTIONS.each do |k, v| build_tag soap, v[0], v[1], options[k] end build_billing_address soap, options @@ -1218,7 +1310,7 @@ def build_customer_payments(soap, options) if options[:payment_methods] length = options[:payment_methods].length soap.PaymentMethods 'SOAP-ENC:arrayType' => "ns1:PaymentMethod[#{length}]", - 'xsi:type' =>"ns1:PaymentMethodArray" do |soap| + 'xsi:type' =>'ns1:PaymentMethodArray' do build_customer_payment_methods soap, options end end @@ -1244,16 +1336,18 @@ def build_credit_card_or_check(soap, payment_method) when payment_method[:method].kind_of?(ActiveMerchant::Billing::CreditCard) build_tag soap, :string, 'CardNumber', payment_method[:method].number build_tag soap, :string, 'CardExpiration', - "#{"%02d" % payment_method[:method].month}#{payment_method[:method].year}" + "#{"%02d" % payment_method[:method].month}#{payment_method[:method].year.to_s[-2..-1]}" if options[:billing_address] build_tag soap, :string, 'AvsStreet', options[:billing_address][:address1] build_tag soap, :string, 'AvsZip', options[:billing_address][:zip] end build_tag soap, :string, 'CardCode', payment_method[:method].verification_value when payment_method[:method].kind_of?(ActiveMerchant::Billing::Check) - build_tag soap, :string, 'Account', payment_method[:method].number + build_tag soap, :string, 'Account', payment_method[:method].account_number build_tag soap, :string, 'Routing', payment_method[:method].routing_number - build_tag soap, :string, 'AccountType', payment_method[:method].account_type.capitalize + unless payment_method[:method].account_type.nil? + build_tag soap, :string, 'AccountType', payment_method[:method].account_type.capitalize + end build_tag soap, :string, 'DriversLicense', options[:drivers_license] build_tag soap, :string, 'DriversLicenseState', options[:drivers_license_state] build_tag soap, :string, 'RecordType', options[:record_type] @@ -1263,7 +1357,7 @@ def build_credit_card_or_check(soap, payment_method) def build_customer_payment_methods(soap, options) payment_methods, tag_name = extract_methods_and_tag(options) payment_methods.each do |payment_method| - soap.tag! tag_name, 'xsi:type' => "ns1:PaymentMethod" do |soap| + soap.tag! tag_name, 'xsi:type' => 'ns1:PaymentMethod' do build_tag soap, :integer, 'MethodID', payment_method[:method_id] build_tag soap, :string, 'MethodType', payment_method[:type] build_tag soap, :string, 'MethodName', payment_method[:name] @@ -1274,9 +1368,9 @@ def build_customer_payment_methods(soap, options) end def build_customer_transaction(soap, options) - soap.Parameters 'xsi:type' => "ns1:CustomerTransactionRequest" do |soap| + soap.Parameters 'xsi:type' => 'ns1:CustomerTransactionRequest' do build_transaction_detail soap, options - CUSTOMER_TRANSACTION_REQUEST_OPTIONS.each do |k,v| + CUSTOMER_TRANSACTION_REQUEST_OPTIONS.each do |k, v| build_tag soap, v[0], v[1], options[k] end build_custom_fields soap, options @@ -1287,12 +1381,13 @@ def build_customer_transaction(soap, options) # Transaction Helpers =========================================== def build_transaction_request_object(soap, options, name='Params') - soap.tag! name, 'xsi:type' => "ns1:TransactionRequestObject" do |soap| - TRANSACTION_REQUEST_OBJECT_OPTIONS.each do |k,v| + soap.tag! name, 'xsi:type' => 'ns1:TransactionRequestObject' do + TRANSACTION_REQUEST_OBJECT_OPTIONS.each do |k, v| build_tag soap, v[0], v[1], options[k] end case - when options[:payment_method] == nil + when options[:payment_method].nil? + nil when options[:payment_method].kind_of?(ActiveMerchant::Billing::CreditCard) build_credit_card_data soap, options when options[:payment_method].kind_of?(ActiveMerchant::Billing::Check) @@ -1310,39 +1405,47 @@ def build_transaction_request_object(soap, options, name='Params') end def build_transaction_detail(soap, options) - soap.Details 'xsi:type' => "ns1:TransactionDetail" do |soap| - TRANSACTION_DETAIL_OPTIONS.each do |k,v| + soap.Details 'xsi:type' => 'ns1:TransactionDetail' do + TRANSACTION_DETAIL_OPTIONS.each do |k, v| build_tag soap, v[0], v[1], options[k] end - TRANSACTION_DETAIL_MONEY_OPTIONS.each do |k,v| + TRANSACTION_DETAIL_MONEY_OPTIONS.each do |k, v| build_tag soap, v[0], v[1], amount(options[k]) end end end def build_credit_card_data(soap, options) - soap.CreditCardData 'xsi:type' => "ns1:CreditCardData" do |soap| + soap.CreditCardData 'xsi:type' => 'ns1:CreditCardData' do build_tag soap, :string, 'CardNumber', options[:payment_method].number - build_tag soap, :string, 'CardExpiration', - "#{"%02d" % options[:payment_method].month}#{options[:payment_method].year}" + build_tag soap, :string, 'CardExpiration', build_card_expiration(options) if options[:billing_address] build_tag soap, :string, 'AvsStreet', options[:billing_address][:address1] build_tag soap, :string, 'AvsZip', options[:billing_address][:zip] end build_tag soap, :string, 'CardCode', options[:payment_method].verification_value build_tag soap, :boolean, 'CardPresent', options[:card_present] || false - CREDIT_CARD_DATA_OPTIONS.each do |k,v| + CREDIT_CARD_DATA_OPTIONS.each do |k, v| build_tag soap, v[0], v[1], options[k] end end end + def build_card_expiration(options) + month = options[:payment_method].month + year = options[:payment_method].year + unless month.nil? || year.nil? + "#{"%02d" % month}#{year.to_s[-2..-1]}" + end + end + def build_check_data(soap, options) - soap.CheckData 'xsi:type' => "ns1:CheckData" do |soap| + soap.CheckData 'xsi:type' => 'ns1:CheckData' do + build_tag soap, :integer, 'CheckNumber', options[:payment_method].number build_tag soap, :string, 'Account', options[:payment_method].account_number build_tag soap, :string, 'Routing', options[:payment_method].routing_number build_tag soap, :string, 'AccountType', options[:payment_method].account_type.capitalize - CHECK_DATA_OPTIONS.each do |k,v| + CHECK_DATA_OPTIONS.each do |k, v| build_tag soap, v[0], v[1], options[k] end end @@ -1350,11 +1453,11 @@ def build_check_data(soap, options) def build_recurring_billing(soap, options) if options[:recurring] - soap.RecurringBilling 'xsi:type' => "ns1:RecurringBilling" do |soap| + soap.RecurringBilling 'xsi:type' => 'ns1:RecurringBilling' do build_tag soap, :double, 'Amount', amount(options[:recurring][:amount]) - build_tag soap, :string, 'Next', options[:recurring][:next].strftime("%Y-%m-%d") if options[:recurring][:next] - build_tag soap, :string, 'Expire', options[:recurring][:expire].strftime("%Y-%m-%d") if options[:recurring][:expire] - RECURRING_BILLING_OPTIONS.each do |k,v| + build_tag soap, :string, 'Next', options[:recurring][:next].strftime('%Y-%m-%d') if options[:recurring][:next] + build_tag soap, :string, 'Expire', options[:recurring][:expire].strftime('%Y-%m-%d') if options[:recurring][:expire] + RECURRING_BILLING_OPTIONS.each do |k, v| build_tag soap, v[0], v[1], options[:recurring][k] end end @@ -1362,7 +1465,7 @@ def build_recurring_billing(soap, options) end def build_transaction_field_array(soap, options) - soap.Fields 'SOAP-ENC:arryType' => "xsd:string[#{options[:fields].length}]", 'xsi:type' => 'ns1:stringArray' do |soap| + soap.Fields 'SOAP-ENC:arryType' => "xsd:string[#{options[:fields].length}]", 'xsi:type' => 'ns1:stringArray' do options[:fields].each do |field| build_tag soap, :string, 'item', field end @@ -1374,11 +1477,10 @@ def build_transaction_field_array(soap, options) def build_billing_address(soap, options) if options[:billing_address] if options[:billing_address][:name] - name = options[:billing_address][:name].split(nil,2) # divide name - options[:billing_address][:first_name], options[:billing_address][:last_name] = name[0], name[1] + options[:billing_address][:first_name], options[:billing_address][:last_name] = split_names(options[:billing_address][:name]) end - soap.BillingAddress 'xsi:type' => "ns1:Address" do - ADDRESS_OPTIONS.each do |k,v| + soap.BillingAddress 'xsi:type' => 'ns1:Address' do + ADDRESS_OPTIONS.each do |k, v| build_tag soap, v[0], v[1], options[:billing_address][k] end end @@ -1388,17 +1490,31 @@ def build_billing_address(soap, options) def build_shipping_address(soap, options) if options[:shipping_address] if options[:shipping_address][:name] - name = options[:shipping_address][:name].split(nil,2) # divide name - options[:shipping_address][:first_name], options[:shipping_address][:last_name] = name[0], name[1] + options[:shipping_address][:first_name], options[:shipping_address][:last_name] = split_names(options[:shipping_address][:name]) end - soap.ShippingAddress 'xsi:type' => "ns1:Address" do - ADDRESS_OPTIONS.each do |k,v| + soap.ShippingAddress 'xsi:type' => 'ns1:Address' do + ADDRESS_OPTIONS.each do |k, v| build_tag soap, v[0], v[1], options[:shipping_address][k] end end end end + def build_field_value_array(soap, tag_name, type, custom_data, fields) + soap.tag! tag_name, 'SOAP-ENC:arryType' => "xsd:#{type}[#{options.length}]", 'xsi:type' => "ns1:#{type}Array" do + custom_data.each do |k, v| + build_field_value soap, fields[k][1], v, fields[k][0] if fields.key?(k) + end + end + end + + def build_field_value(soap, field, value, value_type) + soap.FieldValue 'xsi:type' => 'ns1:FieldValue' do + build_tag soap, :string, 'Field', field + build_tag soap, value_type, 'Value', value + end + end + def build_line_items(soap, options) # TODO end @@ -1411,22 +1527,25 @@ def commit(action, request) url = test? ? test_url : live_url begin - soap = ssl_post(url, request, "Content-Type" => "text/xml") + soap = ssl_post(url, request, 'Content-Type' => 'text/xml') rescue ActiveMerchant::ResponseError => error soap = error.response.body end - response = build_response(action, soap) + build_response(action, soap) end def build_response(action, soap) response_params, success, message, authorization, avs, cvv = parse(action, soap) - response_params.merge!('soap_response' => soap) if @options[:soap_response] + response_params['soap_response'] = soap if @options[:soap_response] - response = Response.new( - success, message, response_params, - :test => test?, :authorization => authorization, + Response.new( + success, + message, + response_params, + :test => test?, + :authorization => authorization, :avs_result => avs_from(avs), :cvv_result => cvv ) @@ -1434,23 +1553,23 @@ def build_response(action, soap) def avs_from(avs) avs_params = { :code => avs } - avs_params.merge!(:message => AVS_CUSTOM_MESSAGES[avs]) if AVS_CUSTOM_MESSAGES.key?(avs) + avs_params[:message] = AVS_CUSTOM_MESSAGES[avs] if AVS_CUSTOM_MESSAGES.key?(avs) avs_params end def parse(action, soap) xml = REXML::Document.new(soap) - root = REXML::XPath.first(xml, "//SOAP-ENV:Body") + root = REXML::XPath.first(xml, '//SOAP-ENV:Body') response = root ? parse_element(root[0]) : { :response => soap } success, message, authorization, avs, cvv = false, FAILURE_MESSAGE, nil, nil, nil - fault = (!response) || (response.length < 1) || response.has_key?('faultcode') + fault = !response || (response.length < 1) || response.has_key?('faultcode') return [response, success, response['faultstring'], authorization, avs, cvv] if fault if response.respond_to?(:[]) && p = response["#{action}_return"] if p.respond_to?(:key?) && p.key?('result_code') - success = p['result_code'] == 'A' ? true : false + success = p['result_code'] == 'A' authorization = p['ref_num'] avs = AVS_RESULTS[p['avs_result_code']] cvv = p['card_code_result_code'] @@ -1461,7 +1580,8 @@ def parse(action, soap) when :get_customer_payment_methods p['item'] when :get_transaction_custom - p['item'].inject({}) { |map, field| map[field['field']] = field['value']; map } + items = p['item'].kind_of?(Array) ? p['item'] : [p['item']] + items.inject({}) { |hash, item| hash[item['field']] = item['value']; hash } else p end @@ -1498,4 +1618,3 @@ def parse_element(node) end end end - diff --git a/lib/active_merchant/billing/gateways/usa_epay_transaction.rb b/lib/active_merchant/billing/gateways/usa_epay_transaction.rb index 8adfeb5167f..e1cade9a52a 100644 --- a/lib/active_merchant/billing/gateways/usa_epay_transaction.rb +++ b/lib/active_merchant/billing/gateways/usa_epay_transaction.rb @@ -1,20 +1,41 @@ module ActiveMerchant #:nodoc: module Billing #:nodoc: - class UsaEpayTransactionGateway < Gateway - self.test_url = self.live_url = 'https://www.usaepay.com/gate.php' + self.live_url = 'https://www.usaepay.com/gate' + self.test_url = 'https://sandbox.usaepay.com/gate' - self.supported_cardtypes = [:visa, :master, :american_express] - self.supported_countries = ['US'] - self.homepage_url = 'http://www.usaepay.com/' - self.display_name = 'USA ePay' + self.supported_cardtypes = [:visa, :master, :american_express] + self.supported_countries = ['US'] + self.homepage_url = 'http://www.usaepay.com/' + self.display_name = 'USA ePay' TRANSACTIONS = { - :authorization => 'authonly', - :purchase => 'sale', - :capture => 'capture', - :refund => 'refund', - :void => 'void' + :authorization => 'cc:authonly', + :purchase => 'cc:sale', + :capture => 'cc:capture', + :refund => 'cc:refund', + :void => 'cc:void', + :void_release => 'cc:void:release', + :check_purchase => 'check:sale' + } + + STANDARD_ERROR_CODE_MAPPING = { + '00011' => STANDARD_ERROR_CODE[:incorrect_number], + '00012' => STANDARD_ERROR_CODE[:incorrect_number], + '00013' => STANDARD_ERROR_CODE[:incorrect_number], + '00014' => STANDARD_ERROR_CODE[:invalid_number], + '00015' => STANDARD_ERROR_CODE[:invalid_expiry_date], + '00016' => STANDARD_ERROR_CODE[:invalid_expiry_date], + '00017' => STANDARD_ERROR_CODE[:expired_card], + '10116' => STANDARD_ERROR_CODE[:incorrect_cvc], + '10107' => STANDARD_ERROR_CODE[:incorrect_zip], + '10109' => STANDARD_ERROR_CODE[:incorrect_address], + '10110' => STANDARD_ERROR_CODE[:incorrect_address], + '10111' => STANDARD_ERROR_CODE[:incorrect_address], + '10127' => STANDARD_ERROR_CODE[:card_declined], + '00043' => STANDARD_ERROR_CODE[:call_issuer], + '10205' => STANDARD_ERROR_CODE[:card_declined], + '10204' => STANDARD_ERROR_CODE[:pickup_card] } def initialize(options = {}) @@ -27,29 +48,44 @@ def authorize(money, credit_card, options = {}) add_amount(post, money) add_invoice(post, options) - add_credit_card(post, credit_card) - add_address(post, credit_card, options) - add_customer_data(post, options) + add_payment(post, credit_card) + unless credit_card.track_data.present? + add_address(post, credit_card, options) + add_customer_data(post, options) + end + add_split_payments(post, options) + add_recurring_fields(post, options) + add_custom_fields(post, options) + add_line_items(post, options) + add_test_mode(post, options) commit(:authorization, post) end - def purchase(money, credit_card, options = {}) + def purchase(money, payment, options = {}) post = {} add_amount(post, money) add_invoice(post, options) - add_credit_card(post, credit_card) - add_address(post, credit_card, options) - add_customer_data(post, options) + add_payment(post, payment, options) + unless payment.respond_to?(:track_data) && payment.track_data.present? + add_address(post, payment, options) + add_customer_data(post, options) + end + add_split_payments(post, options) + add_recurring_fields(post, options) + add_custom_fields(post, options) + add_line_items(post, options) + add_test_mode(post, options) - commit(:purchase, post) + payment.respond_to?(:routing_number) ? commit(:check_purchase, post) : commit(:purchase, post) end def capture(money, authorization, options = {}) post = { :refNum => authorization } add_amount(post, money) + add_test_mode(post, options) commit(:capture, post) end @@ -57,12 +93,36 @@ def refund(money, authorization, options = {}) post = { :refNum => authorization } add_amount(post, money) + add_test_mode(post, options) commit(:refund, post) end + def verify(creditcard, options = {}) + MultiResponse.run(:use_first_response) do |r| + r.process { authorize(1, creditcard, options) } + r.process(:ignore_result) { void(r.authorization, options) } + end + end + + # Pass `no_release: true` to keep the void from immediately settling def void(authorization, options = {}) + command = (options[:no_release] ? :void : :void_release) post = { :refNum => authorization } - commit(:void, post) + add_test_mode(post, options) + commit(command, post) + end + + def supports_scrubbing? + true + end + + def scrub(transcript) + transcript. + gsub(%r((&?UMcard=)\d*(&?))i, '\1[FILTERED]\2'). + gsub(%r((&?UMcvv2=)\d*(&?))i, '\1[FILTERED]\2'). + gsub(%r((&?UMmagstripe=)[^&]*)i, '\1[FILTERED]\2'). + gsub(%r((&?UMaccount=)[^&]*)i, '\1[FILTERED]'). + gsub(%r((&?UMkey=)[^&]*)i, '\1[FILTERED]') end private @@ -85,7 +145,12 @@ def add_customer_data(post, options) if options.has_key? :email post[:custemail] = options[:email] - post[:custreceipt] = 'No' + if options[:cust_receipt] + post[:custreceipt] = options[:cust_receipt] + post[:custreceiptname] = options[:cust_receipt_name] if options[:cust_receipt_name] + else + post[:custreceipt] = 'No' + end end if options.has_key? :customer @@ -97,26 +162,27 @@ def add_customer_data(post, options) end end - def add_address(post, credit_card, options) + def add_address(post, payment, options) billing_address = options[:billing_address] || options[:address] - add_address_for_type(:billing, post, credit_card, billing_address) if billing_address - add_address_for_type(:shipping, post, credit_card, options[:shipping_address]) if options[:shipping_address] + add_address_for_type(:billing, post, payment, billing_address) if billing_address + add_address_for_type(:shipping, post, payment, options[:shipping_address]) if options[:shipping_address] end - def add_address_for_type(type, post, credit_card, address) + def add_address_for_type(type, post, payment, address) prefix = address_key_prefix(type) - - post[address_key(prefix, 'fname')] = credit_card.first_name - post[address_key(prefix, 'lname')] = credit_card.last_name - post[address_key(prefix, 'company')] = address[:company] unless address[:company].blank? - post[address_key(prefix, 'street')] = address[:address1] unless address[:address1].blank? - post[address_key(prefix, 'street2')] = address[:address2] unless address[:address2].blank? - post[address_key(prefix, 'city')] = address[:city] unless address[:city].blank? - post[address_key(prefix, 'state')] = address[:state] unless address[:state].blank? - post[address_key(prefix, 'zip')] = address[:zip] unless address[:zip].blank? - post[address_key(prefix, 'country')] = address[:country] unless address[:country].blank? - post[address_key(prefix, 'phone')] = address[:phone] unless address[:phone].blank? + first_name, last_name = split_names(address[:name]) + + post[address_key(prefix, 'fname')] = first_name.blank? && last_name.blank? ? payment.first_name : first_name + post[address_key(prefix, 'lname')] = first_name.blank? && last_name.blank? ? payment.last_name : last_name + post[address_key(prefix, 'company')] = address[:company] unless address[:company].blank? + post[address_key(prefix, 'street')] = address[:address1] unless address[:address1].blank? + post[address_key(prefix, 'street2')] = address[:address2] unless address[:address2].blank? + post[address_key(prefix, 'city')] = address[:city] unless address[:city].blank? + post[address_key(prefix, 'state')] = address[:state] unless address[:state].blank? + post[address_key(prefix, 'zip')] = address[:zip] unless address[:zip].blank? + post[address_key(prefix, 'country')] = address[:country] unless address[:country].blank? + post[address_key(prefix, 'phone')] = address[:phone] unless address[:phone].blank? end def address_key_prefix(type) @@ -131,55 +197,141 @@ def address_key(prefix, key) end def add_invoice(post, options) - post[:invoice] = options[:order_id] - post[:description] = options[:description] + post[:invoice] = options[:invoice] + post[:orderid] = options[:order_id] + post[:description] = options[:description] + end + + def add_payment(post, payment, options={}) + if payment.respond_to?(:routing_number) + post[:checkformat] = options[:check_format] if options[:check_format] + if payment.account_type + account_type = payment.account_type.to_s.capitalize + raise ArgumentError, 'account_type must be checking or savings' unless %w(Checking Savings).include?(account_type) + post[:accounttype] = account_type + end + post[:account] = payment.account_number + post[:routing] = payment.routing_number + post[:name] = payment.name unless payment.name.blank? + elsif payment.respond_to?(:track_data) && payment.track_data.present? + post[:magstripe] = payment.track_data + post[:cardpresent] = true + else + post[:card] = payment.number + post[:cvv2] = payment.verification_value if payment.verification_value? + post[:expir] = expdate(payment) + post[:name] = payment.name unless payment.name.blank? + post[:cardpresent] = true if payment.manual_entry + end + end + + def add_test_mode(post, options) + post[:testmode] = (options[:test_mode] ? 1 : 0) if options.has_key?(:test_mode) + end + + # see: http://wiki.usaepay.com/developer/transactionapi#split_payments + def add_split_payments(post, options) + return unless options[:split_payments].is_a?(Array) + options[:split_payments].each_with_index do |payment, index| + prefix = '%02d' % (index + 2) + post["#{prefix}key"] = payment[:key] + post["#{prefix}amount"] = amount(payment[:amount]) + post["#{prefix}description"] = payment[:description] + end + + # When blank it's 'Stop'. 'Continue' is another one + post['onError'] = options[:on_error] || 'Void' + end + + def add_recurring_fields(post, options) + return unless options[:recurring_fields].is_a?(Hash) + options[:recurring_fields].each do |key, value| + if value == true + value = 'yes' + elsif value == false + next + end + + if key == :bill_amount + value = amount(value) + end + + post[key.to_s.delete('_')] = value + end end - def add_credit_card(post, credit_card) - post[:card] = credit_card.number - post[:cvv2] = credit_card.verification_value if credit_card.verification_value? - post[:expir] = expdate(credit_card) - post[:name] = credit_card.name + # see: https://wiki.usaepay.com/developer/transactionapi#merchant_defined_custom_fields + def add_custom_fields(post, options) + return unless options[:custom_fields].is_a?(Hash) + + options[:custom_fields].each do |index, custom| + raise ArgumentError.new('Cannot specify custom field with index 0') if index.to_s.to_i.zero? + + post["custom#{index}"] = custom + end + end + + # see: https://wiki.usaepay.com/developer/transactionapi#line_item_details + def add_line_items(post, options) + return unless options[:line_items].is_a?(Array) + options[:line_items].each_with_index do |line_item, index| + %w(product_ref_num sku qty name description taxable tax_rate tax_amount commodity_code discount_rate discount_amount).each do |key| + post["line#{index}#{key.delete('_')}"] = line_item[key.to_sym] if line_item.has_key?(key.to_sym) + end + + { + quantity: 'qty', + unit: 'um', + }.each do |key, umkey| + post["line#{index}#{umkey}"] = line_item[key.to_sym] if line_item.has_key?(key.to_sym) + end + + post["line#{index}cost"] = amount(line_item[:cost]) + end end def parse(body) fields = {} for line in body.split('&') - key, value = *line.scan( %r{^(\w+)\=(.*)$} ).flatten + key, value = *line.scan(%r{^(\w+)\=(.*)$}).flatten fields[key] = CGI.unescape(value.to_s) end { - :status => fields['UMstatus'], - :auth_code => fields['UMauthCode'], - :ref_num => fields['UMrefNum'], - :batch => fields['UMbatch'], - :avs_result => fields['UMavsResult'], - :avs_result_code => fields['UMavsResultCode'], - :cvv2_result => fields['UMcvv2Result'], + :status => fields['UMstatus'], + :auth_code => fields['UMauthCode'], + :ref_num => fields['UMrefNum'], + :batch => fields['UMbatch'], + :avs_result => fields['UMavsResult'], + :avs_result_code => fields['UMavsResultCode'], + :cvv2_result => fields['UMcvv2Result'], :cvv2_result_code => fields['UMcvv2ResultCode'], :vpas_result_code => fields['UMvpasResultCode'], - :result => fields['UMresult'], - :error => fields['UMerror'], - :error_code => fields['UMerrorcode'], - :acs_url => fields['UMacsurl'], - :payload => fields['UMpayload'] - }.delete_if{|k, v| v.nil?} + :result => fields['UMresult'], + :error => fields['UMerror'], + :error_code => fields['UMerrorcode'], + :acs_url => fields['UMacsurl'], + :payload => fields['UMpayload'] + }.delete_if { |k, v| v.nil? } end def commit(action, parameters) - response = parse( ssl_post(self.live_url, post_data(action, parameters)) ) - - Response.new(response[:status] == 'Approved', message_from(response), response, - :test => test?, - :authorization => response[:ref_num], - :cvv_result => response[:cvv2_result_code], - :avs_result => { :code => response[:avs_result_code] } + url = (test? ? self.test_url : self.live_url) + response = parse(ssl_post(url, post_data(action, parameters))) + approved = response[:status] == 'Approved' + error_code = nil + error_code = (STANDARD_ERROR_CODE_MAPPING[response[:error_code]] || STANDARD_ERROR_CODE[:processing_error]) unless approved + Response.new(approved, message_from(response), response, + :test => test?, + :authorization => response[:ref_num], + :cvv_result => response[:cvv2_result_code], + :avs_result => { :code => response[:avs_result_code] }, + :error_code => error_code ) end def message_from(response) - if response[:status] == "Approved" + if response[:status] == 'Approved' return 'Success' else return 'Unspecified error' if response[:error].blank? @@ -189,13 +341,15 @@ def message_from(response) def post_data(action, parameters = {}) parameters[:command] = TRANSACTIONS[action] - parameters[:key] = @options[:login] + parameters[:key] = @options[:login] parameters[:software] = 'Active Merchant' - parameters[:testmode] = (@options[:test] ? 1 : 0) + parameters[:testmode] = (@options[:test] ? 1 : 0) unless parameters.has_key?(:testmode) + seed = SecureRandom.hex(32).upcase + hash = Digest::SHA1.hexdigest("#{parameters[:command]}:#{@options[:password]}:#{parameters[:amount]}:#{parameters[:invoice]}:#{seed}") + parameters[:hash] = "s/#{seed}/#{hash}/n" - parameters.collect { |key, value| "UM#{key}=#{CGI.escape(value.to_s)}" }.join("&") + parameters.collect { |key, value| "UM#{key}=#{CGI.escape(value.to_s)}" }.join('&') end end end end - diff --git a/lib/active_merchant/billing/gateways/vanco.rb b/lib/active_merchant/billing/gateways/vanco.rb new file mode 100644 index 00000000000..546eedbca4e --- /dev/null +++ b/lib/active_merchant/billing/gateways/vanco.rb @@ -0,0 +1,294 @@ +require 'nokogiri' + +module ActiveMerchant + module Billing + class VancoGateway < Gateway + include Empty + + self.test_url = 'https://uat.vancopayments.com/cgi-bin/ws2.vps' + self.live_url = 'https://myvanco.vancopayments.com/cgi-bin/ws2.vps' + + self.supported_countries = ['US'] + self.default_currency = 'USD' + self.supported_cardtypes = [:visa, :master, :american_express, :discover] + + self.homepage_url = 'http://vancopayments.com/' + self.display_name = 'Vanco Payment Solutions' + + def initialize(options={}) + requires!(options, :user_id, :password, :client_id) + super + end + + def purchase(money, payment_method, options={}) + MultiResponse.run do |r| + r.process { login } + r.process { commit(purchase_request(money, payment_method, r.params['response_sessionid'], options), :response_transactionref) } + end + end + + def refund(money, authorization, options={}) + MultiResponse.run do |r| + r.process { login } + r.process { commit(refund_request(money, authorization, r.params['response_sessionid']), :response_creditrequestreceived) } + end + end + + def supports_scrubbing? + true + end + + def scrub(transcript) + transcript. + gsub(%r((<Password>).+(</Password>))i, '\1[FILTERED]\2'). + gsub(%r((<CardCVV2>).+(</CardCVV2>))i, '\1[FILTERED]\2'). + gsub(%r((<AccountNumber>).+(</AccountNumber>))i, '\1[FILTERED]\2') + end + + private + + def parse(xml) + response = {} + + doc = Nokogiri::XML(xml) + doc.root.xpath('*').each do |node| + if node.elements.empty? + response[node.name.downcase.to_sym] = node.text + else + node.elements.each do |childnode| + childnode_to_response(response, node, childnode) + end + end + end + + response + end + + def childnode_to_response(response, node, childnode) + name = "#{node.name.downcase}_#{childnode.name.downcase}" + if name == 'response_errors' && !childnode.elements.empty? + add_errors_to_response(response, childnode.to_s) + else + response[name.downcase.to_sym] = childnode.text + end + end + + def add_errors_to_response(response, errors_xml) + errors_hash = Hash.from_xml(errors_xml).values.first + response[:response_errors] = errors_hash + + error = errors_hash['Error'] + if error.kind_of?(Hash) + response[:error_message] = error['ErrorDescription'] + response[:error_codes] = error['ErrorCode'] + elsif error.kind_of?(Array) + error_str = error.map { |e| e['ErrorDescription'] }.join('. ') + error_codes = error.map { |e| e['ErrorCode'] }.join(', ') + response[:error_message] = "#{error_str}." + response[:error_codes] = error_codes + end + end + + def commit(request, success_field_name) + response = parse(ssl_post(url, request, headers)) + succeeded = success_from(response, success_field_name) + + Response.new( + succeeded, + message_from(succeeded, response), + response, + authorization: authorization_from(response), + test: test? + ) + end + + def success_from(response, success_field_name) + !empty?(response[success_field_name]) + end + + def message_from(succeeded, response) + return 'Success' if succeeded + response[:error_message] + end + + def authorization_from(response) + [ + response[:response_customerref], + response[:response_paymentmethodref], + response[:response_transactionref] + ].join('|') + end + + def split_authorization(authorization) + authorization.to_s.split('|') + end + + def purchase_request(money, payment_method, session_id, options) + build_xml_request do |doc| + add_auth(doc, 'EFTAddCompleteTransaction', session_id) + + doc.Request do + doc.RequestVars do + add_client_id(doc) + add_amount(doc, money, options) + add_payment_method(doc, payment_method, options) + add_options(doc, options) + add_purchase_noise(doc) + end + end + end + end + + def refund_request(money, authorization, session_id) + build_xml_request do |doc| + add_auth(doc, 'EFTAddCredit', session_id) + + doc.Request do + doc.RequestVars do + add_client_id(doc) + add_amount(doc, money, options) + add_reference(doc, authorization) + add_refund_noise(doc) + end + end + end + end + + def add_request(doc, request_type) + doc.RequestType(request_type) + doc.RequestID(SecureRandom.hex(15)) + doc.RequestTime(Time.now) + doc.Version(2) + end + + def add_auth(doc, request_type, session_id) + doc.Auth do + add_request(doc, request_type) + doc.SessionID(session_id) + end + end + + def add_reference(doc, authorization) + customer_ref, payment_method_ref, transaction_ref = split_authorization(authorization) + doc.CustomerRef(customer_ref) + doc.PaymentMethodRef(payment_method_ref) + doc.TransactionRef(transaction_ref) + end + + def add_amount(doc, money, options) + if empty?(options[:fund_id]) + doc.Amount(amount(money)) + else + doc.Funds do + doc.Fund do + doc.FundID(options[:fund_id]) + doc.FundAmount(amount(money)) + end + end + end + end + + def add_payment_method(doc, payment_method, options) + if card_brand(payment_method) == 'check' + add_echeck(doc, payment_method) + else + add_credit_card(doc, payment_method, options) + end + end + + def add_credit_card(doc, credit_card, options) + doc.AccountNumber(credit_card.number) + doc.CustomerName("#{credit_card.last_name}, #{credit_card.first_name}") + doc.CardExpMonth(format(credit_card.month, :two_digits)) + doc.CardExpYear(format(credit_card.year, :two_digits)) + doc.CardCVV2(credit_card.verification_value) + doc.CardBillingName(credit_card.name) + doc.AccountType('CC') + add_billing_address(doc, options) + end + + def add_billing_address(doc, options) + address = options[:billing_address] + return unless address + + doc.CardBillingAddr1(address[:address1]) + doc.CardBillingAddr2(address[:address2]) + doc.CardBillingCity(address[:city]) + doc.CardBillingState(address[:state]) + doc.CardBillingZip(address[:zip]) + doc.CardBillingCountryCode(address[:country]) + end + + def add_echeck(doc, echeck) + if echeck.account_type == 'savings' + doc.AccountType('S') + else + doc.AccountType('C') + end + + doc.CustomerName("#{echeck.last_name}, #{echeck.first_name}") + doc.AccountNumber(echeck.account_number) + doc.RoutingNumber(echeck.routing_number) + doc.TransactionTypeCode('WEB') + end + + def add_purchase_noise(doc) + doc.StartDate('0000-00-00') + doc.FrequencyCode('O') + end + + def add_refund_noise(doc) + doc.ContactName('Bilbo Baggins') + doc.ContactPhone('1234567890') + doc.ContactExtension('None') + doc.ReasonForCredit('Refund requested') + end + + def add_options(doc, options) + doc.CustomerIPAddress(options[:ip]) if options[:ip] + end + + def add_client_id(doc) + doc.ClientID(@options[:client_id]) + end + + def login + commit(login_request, :response_sessionid) + end + + def login_request + build_xml_request do |doc| + doc.Auth do + add_request(doc, 'Login') + end + + doc.Request do + doc.RequestVars do + doc.UserID(@options[:user_id]) + doc.Password(@options[:password]) + end + end + end + end + + def build_xml_request + builder = Nokogiri::XML::Builder.new + builder.__send__('VancoWS') do |doc| + yield(doc) + end + builder.to_xml + end + + def url + (test? ? test_url : live_url) + end + + def headers + { + 'Content-Type' => 'text/xml' + } + end + + end + end +end diff --git a/lib/active_merchant/billing/gateways/verifi.rb b/lib/active_merchant/billing/gateways/verifi.rb index c0ff6765c10..e435505d8be 100644 --- a/lib/active_merchant/billing/gateways/verifi.rb +++ b/lib/active_merchant/billing/gateways/verifi.rb @@ -5,46 +5,46 @@ module Billing #:nodoc: class VerifiGateway < Gateway class VerifiPostData < PostData # Fields that will be sent even if they are blank - self.required_fields = [ :amount, :type, :ccnumber, :ccexp, :firstname, :lastname, - :company, :address1, :address2, :city, :state, :zip, :country, :phone ] + self.required_fields = [:amount, :type, :ccnumber, :ccexp, :firstname, :lastname, + :company, :address1, :address2, :city, :state, :zip, :country, :phone] end self.live_url = self.test_url = 'https://secure.verifi.com/gw/api/transact.php' RESPONSE_CODE_MESSAGES = { - "100" => "Transaction was Approved", - "200" => "Transaction was Declined by Processor", - "201" => "Do Not Honor", - "202" => "Insufficient Funds", - "203" => "Over Limit", - "204" => "Transaction not allowed", - "220" => "Incorrect payment Data", - "221" => "No Such Card Issuer", - "222" => "No Card Number on file with Issuer", - "223" => "Expired Card", - "224" => "Invalid Expiration Date", - "225" => "Invalid Card Security Code", - "240" => "Call Issuer for Further Information", - "250" => "Pick Up Card", - "251" => "Lost Card", - "252" => "Stolen Card", - "253" => "Fraudulent Card", - "260" => "Declined With further Instructions Available (see response text)", - "261" => "Declined - Stop All Recurring Payments", - "262" => "Declined - Stop this Recurring Program", - "263" => "Declined - Update Cardholder Data Available", - "264" => "Declined - Retry in a few days", - "300" => "Transaction was Rejected by Gateway", - "400" => "Transaction Error Returned by Processor", - "410" => "Invalid Merchant Configuration", - "411" => "Merchant Account is Inactive", - "420" => "Communication Error", - "421" => "Communication Error with Issuer", - "430" => "Duplicate Transaction at Processor", - "440" => "Processor Format Error", - "441" => "Invalid Transaction Information", - "460" => "Processor Feature Not Available", - "461" => "Unsupported Card Type" + '100' => 'Transaction was Approved', + '200' => 'Transaction was Declined by Processor', + '201' => 'Do Not Honor', + '202' => 'Insufficient Funds', + '203' => 'Over Limit', + '204' => 'Transaction not allowed', + '220' => 'Incorrect payment Data', + '221' => 'No Such Card Issuer', + '222' => 'No Card Number on file with Issuer', + '223' => 'Expired Card', + '224' => 'Invalid Expiration Date', + '225' => 'Invalid Card Security Code', + '240' => 'Call Issuer for Further Information', + '250' => 'Pick Up Card', + '251' => 'Lost Card', + '252' => 'Stolen Card', + '253' => 'Fraudulent Card', + '260' => 'Declined With further Instructions Available (see response text)', + '261' => 'Declined - Stop All Recurring Payments', + '262' => 'Declined - Stop this Recurring Program', + '263' => 'Declined - Update Cardholder Data Available', + '264' => 'Declined - Retry in a few days', + '300' => 'Transaction was Rejected by Gateway', + '400' => 'Transaction Error Returned by Processor', + '410' => 'Invalid Merchant Configuration', + '411' => 'Merchant Account is Inactive', + '420' => 'Communication Error', + '421' => 'Communication Error with Issuer', + '430' => 'Duplicate Transaction at Processor', + '440' => 'Processor Format Error', + '441' => 'Invalid Transaction Information', + '460' => 'Processor Feature Not Available', + '461' => 'Unsupported Card Type' } SUCCESS = 1 @@ -86,7 +86,7 @@ def void(authorization, options = {}) def credit(money, credit_card_or_authorization, options = {}) if credit_card_or_authorization.is_a?(String) - deprecated CREDIT_DEPRECATION_MESSAGE + ActiveMerchant.deprecated CREDIT_DEPRECATION_MESSAGE refund(money, credit_card_or_authorization, options) else sale_authorization_or_credit_template(:credit, money, credit_card_or_authorization, options) @@ -125,13 +125,6 @@ def add_credit_card(post, credit_card) post[:cvv] = credit_card.verification_value end - def expdate(credit_card) - year = sprintf("%.4i", credit_card.year) - month = sprintf("%.2i", credit_card.month) - - "#{month}#{year[-2..-1]}" - end - def add_addresses(post, options) if billing_address = options[:billing_address] || options[:address] post[:company] = billing_address[:company] @@ -187,10 +180,10 @@ def add_security_key_data(post, options, money) # MD5(username|password|orderid|amount|time) now = Time.now.to_i.to_s md5 = Digest::MD5.new - md5 << @options[:login].to_s + "|" - md5 << @options[:password].to_s + "|" - md5 << options[:order_id].to_s + "|" - md5 << amount(money).to_s + "|" + md5 << @options[:login].to_s + '|' + md5 << @options[:password].to_s + '|' + md5 << options[:order_id].to_s + '|' + md5 << amount(money).to_s + '|' md5 << now post[:key] = md5.hexdigest post[:time] = now @@ -199,7 +192,7 @@ def add_security_key_data(post, options, money) def commit(trx_type, money, post) post[:amount] = amount(money) - response = parse( ssl_post(self.live_url, post_data(trx_type, post)) ) + response = parse(ssl_post(self.live_url, post_data(trx_type, post))) Response.new(response[:response].to_i == SUCCESS, message_from(response), response, :test => test?, @@ -210,7 +203,7 @@ def commit(trx_type, money, post) end def message_from(response) - response[:response_code_message] ? response[:response_code_message] : "" + response[:response_code_message] || '' end def parse(body) diff --git a/lib/active_merchant/billing/gateways/viaklix.rb b/lib/active_merchant/billing/gateways/viaklix.rb index 307933e6506..b68a8ec3ac9 100644 --- a/lib/active_merchant/billing/gateways/viaklix.rb +++ b/lib/active_merchant/billing/gateways/viaklix.rb @@ -50,7 +50,7 @@ def purchase(money, creditcard, options = {}) # Viaklix does not support credits by reference. You must pass in the credit card def credit(money, creditcard, options = {}) if creditcard.is_a?(String) - raise ArgumentError, "Reference credits are not supported. Please supply the original credit card" + raise ArgumentError, 'Reference credits are not supported. Please supply the original credit card' end form = {} @@ -63,6 +63,7 @@ def credit(money, creditcard, options = {}) end private + def add_test_mode(form, options) form[:test_mode] = 'TRUE' if options[:test_mode] end @@ -72,12 +73,12 @@ def add_customer_data(form, options) form[:customer_code] = options[:customer].to_s.slice(0, 10) unless options[:customer].blank? end - def add_invoice(form,options) + def add_invoice(form, options) form[:invoice_number] = (options[:order_id] || options[:invoice]).to_s.slice(0, 10) form[:description] = options[:description].to_s.slice(0, 255) end - def add_address(form,options) + def add_address(form, options) billing_address = options[:billing_address] || options[:address] if billing_address @@ -92,7 +93,7 @@ def add_address(form,options) end if shipping_address = options[:shipping_address] - first_name, last_name = parse_first_and_last_name(shipping_address[:name]) + first_name, last_name = split_names(shipping_address[:name]) form[:ship_to_first_name] = first_name.to_s.slice(0, 20) form[:ship_to_last_name] = last_name.to_s.slice(0, 30) form[:ship_to_address] = shipping_address[:address1].to_s.slice(0, 30) @@ -104,14 +105,6 @@ def add_address(form,options) end end - def parse_first_and_last_name(value) - name = value.to_s.split(' ') - - last_name = name.pop || '' - first_name = name.join(' ') - [ first_name, last_name ] - end - def add_creditcard(form, creditcard) form[:card_number] = creditcard.number form[:exp_date] = expdate(creditcard) @@ -145,7 +138,7 @@ def commit(action, money, parameters) parameters[:amount] = amount(money) parameters[:transaction_type] = self.actions[action] - response = parse( ssl_post(test? ? self.test_url : self.live_url, post_data(parameters)) ) + response = parse(ssl_post(test? ? self.test_url : self.live_url, post_data(parameters))) Response.new(response['result'] == APPROVED, message_from(response), response, :test => @options[:test] || test?, @@ -166,22 +159,16 @@ def message_from(response) def post_data(parameters) result = preamble result.merge!(parameters) - result.collect { |key, value| "ssl_#{key}=#{CGI.escape(value.to_s)}" }.join("&") - end - - def expdate(creditcard) - year = sprintf("%.4i", creditcard.year) - month = sprintf("%.2i", creditcard.month) - "#{month}#{year[2..3]}" + result.collect { |key, value| "ssl_#{key}=#{CGI.escape(value.to_s)}" }.join('&') end # Parse the response message def parse(msg) resp = {} - msg.split(self.delimiter).collect{|li| - key, value = li.split("=") - resp[key.strip.gsub(/^ssl_/, '')] = value.to_s.strip - } + msg.split(self.delimiter).collect { |li| + key, value = li.split('=') + resp[key.strip.gsub(/^ssl_/, '')] = value.to_s.strip + } resp end end diff --git a/lib/active_merchant/billing/gateways/vindicia.rb b/lib/active_merchant/billing/gateways/vindicia.rb deleted file mode 100644 index 1d091ef00e2..00000000000 --- a/lib/active_merchant/billing/gateways/vindicia.rb +++ /dev/null @@ -1,361 +0,0 @@ -begin - require "vindicia-api" -rescue LoadError - raise "Could not load the vindicia-api gem. Use `gem install vindicia-api` to install it." -end - -require 'i18n/core_ext/string/interpolate' - -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - - # For more information on the Vindicia Gateway please visit their {website}[http://vindicia.com/] - # - # The login and password are not the username and password you use to - # login to the Vindicia Merchant Portal. - # - # ==== Recurring Billing - # - # AutoBills are an feature of Vindicia's API that allows for creating and managing subscriptions. - # - # For more information about Vindicia's API and various other services visit their {Resource Center}[http://www.vindicia.com/resources/index.html] - class VindiciaGateway < Gateway - self.supported_countries = %w{US CA GB AU MX BR DE KR CN HK} - self.supported_cardtypes = [:visa, :master, :american_express, :discover] - self.homepage_url = 'http://www.vindicia.com/' - self.display_name = 'Vindicia' - - class_attribute :test_url, :live_url - - self.test_url = "https://soap.prodtest.sj.vindicia.com/soap.pl" - self.live_url = "http://soap.vindicia.com/soap.pl" - - # Creates a new VindiciaGateway - # - # The gateway requires that a valid login and password be passed - # in the +options+ hash. - # - # ==== Options - # - # * <tt>:login</tt> -- Vindicia SOAP login (REQUIRED) - # * <tt>:password</tt> -- Vindicia SOAP password (REQUIRED) - # * <tt>:api_version</tt> -- Vindicia API Version - defaults to 3.6 (OPTIONAL) - # * <tt>:account_id</tt> -- Account Id which all transactions will be run against. (REQUIRED) - # * <tt>:transaction_prefix</tt> -- Prefix to order id for one-time transactions - defaults to 'X' (OPTIONAL - # * <tt>:min_chargeback_probability</tt> -- Minimum score for chargebacks - defaults to 65 (OPTIONAL) - # * <tt>:cvn_success</tt> -- Array of valid CVN Check return values - defaults to [M, P] (OPTIONAL) - # * <tt>:avs_success</tt> -- Array of valid AVS Check return values - defaults to [X, Y, A, W, Z] (OPTIONAL) - def initialize(options = {}) - requires!(options, :login, :password) - super - - config = lambda do |config| - config.login = options[:login] - config.password = options[:password] - config.api_version = options[:api_version] || "3.6" - config.endpoint = test? ? self.test_url : self.live_url - config.namespace = "http://soap.vindicia.com" - end - - if Vindicia.config.is_configured? - config.call(Vindicia.config) - else - Vindicia.configure(&config) - end - - requires!(options, :account_id) - @account_id = options[:account_id] - - @transaction_prefix = options[:transaction_prefix] || "X" - - @min_chargeback_probability = options[:min_chargeback_probability] || 65 - @cvn_success = options[:cvn_success] || %w{M P} - @avs_success = options[:avs_success] || %w{X Y A W Z} - - @allowed_authorization_statuses = %w{Authorized} - end - - # Perform a purchase, which is essentially an authorization and capture in a single operation. - # - # ==== Parameters - # - # * <tt>money</tt> -- The amount to be purchased as an Integer value in cents. - # * <tt>creditcard</tt> -- The CreditCard details for the transaction. - # * <tt>options</tt> -- A hash of optional parameters. - def purchase(money, creditcard, options = {}) - response = authorize(money, creditcard, options) - return response if !response.success? || response.fraud_review? - - capture(money, response.authorization, options) - end - - # Performs an authorization, which reserves the funds on the customer's credit card, but does not - # charge the card. - # - # ==== Parameters - # - # * <tt>money</tt> -- The amount to be authorized as an Integer value in cents. - # * <tt>creditcard</tt> -- The CreditCard details for the transaction. - # * <tt>options</tt> -- A hash of optional parameters. - def authorize(money, creditcard, options = {}) - vindicia_transaction = authorize_transaction(money, creditcard, options) - response = check_transaction(vindicia_transaction) - - # if this response is under fraud review because of our AVS/CVV checks void the transaction - if !response.success? && response.fraud_review? && !response.authorization.blank? - void_response = void([vindicia_transaction[:transaction][:merchantTransactionId]], options) - if void_response.success? - return response - else - return void_response - end - end - - response - end - - # Captures the funds from an authorized transaction. - # - # ==== Parameters - # - # * <tt>money</tt> -- The amount to be captured as an Integer value in cents. - # * <tt>identification</tt> -- The authorization returned from the previous authorize request. - def capture(money, identification, options = {}) - response = post(Vindicia::Transaction.capture({ - :transactions => [{ :merchantTransactionId => identification }] - })) - - if response[:return][:returnCode] != '200' || response[:qtyFail].to_i > 0 - return fail(response) - end - - success(response, identification) - end - - # Void a previous transaction - # - # ==== Parameters - # - # * <tt>identification</tt> - The authorization returned from the previous authorize request. - # * <tt>options</tt> - Extra options (currently only :ip used) - def void(identification, options = {}) - response = post(Vindicia::Transaction.cancel({ - :transactions => [{ - :account => { :merchantAccountId => @account_id }, - :merchantTransactionId => identification, - :sourceIp => options[:ip] - }] - })) - - if response[:return][:returnCode] == '200' && response[:qtyFail].to_i == 0 - success(response, identification) - else - fail(response) - end - end - - # Perform a recurring billing, which is essentially a purchase and autobill setup in a single operation. - # - # ==== Parameters - # - # * <tt>money</tt> -- The amount to be purchased as an Integer value in cents. - # * <tt>creditcard</tt> -- The CreditCard details for the transaction. - # * <tt>options</tt> -- A hash of parameters. - # - # ==== Options - # - # * <tt>:product_sku</tt> -- The subscription product's sku - # * <tt>:autobill_prefix</tt> -- Prefix to order id for subscriptions - defaults to 'A' (OPTIONAL) - def recurring(money, creditcard, options={}) - options[:recurring] = true - @autobill_prefix = options[:autobill_prefix] || "A" - - response = authorize(money, creditcard, options) - return response if !response.success? || response.fraud_review? - - capture_resp = capture(money, response.authorization, options) - return capture_resp if !response.success? - - # Setting up a recurring AutoBill requires an associated product - requires!(options, :product_sku) - autobill_response = check_subscription(authorize_subscription(options.merge(:product_sku => options[:product_sku]))) - - if autobill_response.success? - autobill_response - else - # If the AutoBill fails to set-up, void the transaction and return it as the response - void_response = void(capture_resp.authorization, options) - if void_response.success? - return autobill_response - else - return void_response - end - end - end - - protected - - def post(body) - parse(ssl_post(Vindicia.config.endpoint, body, "Content-Type" => "text/xml")) - end - - def parse(response) - # Vindicia always returns in the form of request_type_response => { actual_response } - Hash.from_xml(response)["Envelope"]["Body"].values.first.with_indifferent_access - end - - def check_transaction(vindicia_transaction) - if vindicia_transaction[:return][:returnCode] == '200' - status_log = vindicia_transaction[:transaction][:statusLog].first - if status_log[:creditCardStatus] - avs = status_log[:creditCardStatus][:avsCode] - cvn = status_log[:creditCardStatus][:cvnCode] - end - - if @allowed_authorization_statuses.include?(status_log[:status]) && - check_cvn(cvn) && check_avs(avs) - - success(vindicia_transaction, - vindicia_transaction[:transaction][:merchantTransactionId], - avs, cvn) - else - # If the transaction is authorized, but it didn't pass our AVS/CVV checks send the authorization along so - # that is gets voided. Otherwise, send no authorization. - fail(vindicia_transaction, avs, cvn, false, - @allowed_authorization_statuses.include?(status_log[:status]) ? vindicia_transaction[:transaction][:merchantTransactionId] : "") - end - else - # 406 = Chargeback risk score is higher than minChargebackProbability, transaction not authorized. - fail(vindicia_transaction, nil, nil, vindicia_transaction[:return][:return_code] == '406') - end - end - - def authorize_transaction(money, creditcard, options) - parameters = { - :amount => amount(money), - :currency => options[:currency] || currency(money) - } - - add_account_data(parameters, options) - add_customer_data(parameters, options) - add_payment_source(parameters, creditcard, options) - - post(Vindicia::Transaction.auth({ - :transaction => parameters, - :minChargebackProbability => @min_chargeback_probability - })) - end - - def add_account_data(parameters, options) - parameters[:account] = { :merchantAccountId => @account_id } - parameters[:sourceIp] = options[:ip] if options[:ip] - end - - def add_customer_data(parameters, options) - parameters[:merchantTransactionId] = transaction_id(options[:order_id]) - parameters[:shippingAddress] = convert_am_address_to_vindicia(options[:shipping_address]) - - # Transaction items must be provided for tax purposes - requires!(options, :line_items) - parameters[:transactionItems] = options[:line_items] - - if options[:recurring] - parameters[:nameValues] = [{:name => 'merchantAutoBillIdentifier', :value => autobill_id(options[:order_id])}] - end - end - - def add_payment_source(parameters, creditcard, options) - parameters[:sourcePaymentMethod] = { - :type => 'CreditCard', - :creditCard => { :account => creditcard.number, :expirationDate => "%4d%02d" % [creditcard.year, creditcard.month] }, - :accountHolderName => creditcard.name, - :nameValues => [{ :name => 'CVN', :value => creditcard.verification_value }], - :billingAddress => convert_am_address_to_vindicia(options[:billing_address] || options[:address]), - :customerSpecifiedType => creditcard.brand.capitalize, - :active => !!options[:recurring] - } - end - - def authorize_subscription(options) - parameters = {} - - add_account_data(parameters, options) - add_subscription_information(parameters, options) - - post(Vindicia::AutoBill.update({ - :autobill => parameters, - :validatePaymentMethod => false, - :minChargebackProbability => 100 - })) - end - - def check_subscription(vindicia_transaction) - if vindicia_transaction[:return][:returnCode] == '200' - if vindicia_transaction[:autobill] && vindicia_transaction[:autobill][:status] == "Active" - success(vindicia_transaction, - vindicia_transaction[:autobill][:merchantAutoBillId]) - else - fail(vindicia_transaction) - end - else - fail(vindicia_transaction) - end - end - - def add_subscription_information(parameters, options) - requires!(options, :product_sku) - - if options[:shipping_address] - parameters[:account][:shipping_address] = options[:shipping_address] - end - - parameters[:merchantAutoBillId] = autobill_id(options[:order_id]) - parameters[:product] = { :merchantProductId => options[:product_sku] } - end - - def check_avs(avs) - avs.blank? || @avs_success.include?(avs) - end - - def check_cvn(cvn) - cvn.blank? || @cvn_success.include?(cvn) - end - - def success(response, authorization, avs_code = nil, cvn_code = nil) - ActiveMerchant::Billing::Response.new(true, response[:return][:returnString], response, - { :fraud_review => false, :authorization => authorization, :test => test?, - :avs_result => { :code => avs_code }, :cvv_result => cvn_code }) - end - - def fail(response, avs_code = nil, cvn_code = nil, fraud_review = false, authorization = "") - ActiveMerchant::Billing::Response.new(false, response[:return][:returnString], response, - { :fraud_review => fraud_review || !authorization.blank?, - :authorization => authorization, :test => test?, - :avs_result => { :code => avs_code }, :cvv_result => cvn_code }) - - end - - def autobill_id(order_id) - "#{@autobill_prefix}#{order_id}" - end - - def transaction_id(order_id) - "#{@transaction_prefix}#{order_id}" - end - - # Converts valid ActiveMerchant address hash to proper Vindicia format - def convert_am_address_to_vindicia(address) - return if address.nil? - - convs = { :address1 => :addr1, :address2 => :addr2, - :state => :district, :zip => :postalCode } - - vindicia_address = {} - address.each do |key, val| - vindicia_address[convs[key] || key] = val - end - vindicia_address - end - end - end -end diff --git a/lib/active_merchant/billing/gateways/visanet_peru.rb b/lib/active_merchant/billing/gateways/visanet_peru.rb new file mode 100644 index 00000000000..49473abc2f6 --- /dev/null +++ b/lib/active_merchant/billing/gateways/visanet_peru.rb @@ -0,0 +1,245 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class VisanetPeruGateway < Gateway + include Empty + self.display_name = 'VisaNet Peru Gateway' + self.homepage_url = 'http://www.visanet.com.pe' + + self.test_url = 'https://devapi.vnforapps.com/api.tokenization/api/v2/merchant' + self.live_url = 'https://api.vnforapps.com/api.tokenization/api/v2/merchant' + + self.supported_countries = ['US', 'PE'] + self.default_currency = 'PEN' + self.money_format = :dollars + self.supported_cardtypes = [:visa, :master, :american_express, :discover] + + def initialize(options={}) + requires!(options, :access_key_id, :secret_access_key, :merchant_id) + super + end + + def purchase(amount, payment_method, options={}) + MultiResponse.run() do |r| + r.process { authorize(amount, payment_method, options) } + r.process { capture(r.authorization, options) } + end + end + + def authorize(amount, payment_method, options={}) + params = {} + + add_invoice(params, amount, options) + add_payment_method(params, payment_method) + add_antifraud_data(params, options) + params[:email] = options[:email] || 'unknown@email.com' + params[:createAlias] = false + + commit('authorize', params, options) + end + + def capture(authorization, options={}) + params = {} + options[:id_unico] = split_authorization(authorization)[1] + add_auth_order_id(params, authorization, options) + commit('deposit', params, options) + end + + def void(authorization, options={}) + params = {} + add_auth_order_id(params, authorization, options) + commit('void', params, options) + end + + def refund(amount, authorization, options={}) + params = {} + params[:amount] = amount(amount) if amount + add_auth_order_id(params, authorization, options) + response = commit('cancelDeposit', params, options) + return response if response.success? || split_authorization(authorization).length == 1 || !options[:force_full_refund_if_unsettled] + + # Attempt RefundSingleTransaction if unsettled (and stash the original + # response message so it will be included it in the follow-up response + # message) + options[:error_message] = response.message + prepare_refund_data(params, authorization, options) + commit('refund', params, options) + end + + def verify(credit_card, options={}) + MultiResponse.run(:use_first_response) do |r| + r.process { authorize(100, credit_card, options) } + r.process(:ignore_result) { void(r.authorization, options) } + end + end + + def supports_scrubbing? + true + end + + def scrub(transcript) + transcript. + gsub(%r((Authorization: Basic )\w+), '\1[FILTERED]'). + gsub(%r((\"cardNumber\\\":\\\")\d+), '\1[FILTERED]'). + gsub(%r((\"cvv2Code\\\":\\\")\d+), '\1[FILTERED]') + end + + private + + CURRENCY_CODES = Hash.new { |h, k| raise ArgumentError.new("Unsupported currency: #{k}") } + CURRENCY_CODES['USD'] = 840 + CURRENCY_CODES['PEN'] = 604 + + def add_invoice(params, money, options) + # Visanet Peru expects a 9-digit numeric purchaseNumber + params[:purchaseNumber] = (SecureRandom.random_number(900_000_000) + 100_000_000).to_s + params[:externalTransactionId] = options[:order_id] + params[:amount] = amount(money) + params[:currencyId] = CURRENCY_CODES[options[:currency] || currency(money)] + end + + def add_auth_order_id(params, authorization, options) + purchase_number, _ = split_authorization(authorization) + params[:purchaseNumber] = purchase_number + params[:externalTransactionId] = options[:order_id] + end + + def add_payment_method(params, payment_method) + params[:firstName] = payment_method.first_name + params[:lastName] = payment_method.last_name + params[:cardNumber] = payment_method.number + params[:cvv2Code] = payment_method.verification_value + params[:expirationYear] = format(payment_method.year, :four_digits) + params[:expirationMonth] = format(payment_method.month, :two_digits) + end + + def add_antifraud_data(params, options) + antifraud = {} + + if billing_address = options[:billing_address] || options[:address] + antifraud[:billTo_street1] = billing_address[:address1] + antifraud[:billTo_city] = billing_address[:city] + antifraud[:billTo_state] = billing_address[:state] + antifraud[:billTo_country] = billing_address[:country] + antifraud[:billTo_postalCode] = billing_address[:zip] + end + + antifraud[:deviceFingerprintId] = options[:device_fingerprint_id] || SecureRandom.hex(16) + antifraud[:merchantDefineData] = options[:merchant_define_data] if options[:merchant_define_data] + + params[:antifraud] = antifraud + end + + def prepare_refund_data(params, authorization, options) + params.delete(:purchaseNumber) + params[:externalReferenceId] = params.delete(:externalTransactionId) + _, transaction_id = split_authorization(authorization) + + options.update(transaction_id: transaction_id) + params[:ruc] = options[:ruc] + end + + def split_authorization(authorization) + authorization.split('|') + end + + def commit(action, params, options={}) + raw_response = ssl_request(method(action), url(action, params, options), params.to_json, headers) + response = parse(raw_response) + rescue ResponseError => e + raw_response = e.response.body + response_error(raw_response, options, action) + rescue JSON::ParserError + unparsable_response(raw_response) + else + Response.new( + success_from(response), + message_from(response, options, action), + response, + :test => test?, + :authorization => authorization_from(params, response, options), + :error_code => response['errorCode'] + ) + end + + def headers + { + 'Authorization' => 'Basic ' + Base64.strict_encode64("#{@options[:access_key_id]}:#{@options[:secret_access_key]}").strip, + 'Content-Type' => 'application/json' + } + end + + def url(action, params, options={}) + if action == 'authorize' + "#{base_url}/#{@options[:merchant_id]}" + elsif action == 'refund' + "#{base_url}/#{@options[:merchant_id]}/#{action}/#{options[:transaction_id]}" + else + "#{base_url}/#{@options[:merchant_id]}/#{action}/#{params[:purchaseNumber]}" + end + end + + def method(action) + %w(authorize refund).include?(action) ? :post : :put + end + + def authorization_from(params, response, options) + id_unico = response['data']['ID_UNICO'] || options[:id_unico] + "#{params[:purchaseNumber]}|#{id_unico}" + end + + def base_url + test? ? test_url : live_url + end + + def parse(body) + JSON.parse(body) + end + + def success_from(response) + response['errorCode'] == 0 + end + + def message_from(response, options, action) + message_from_messages( + response['errorMessage'], + action_code_description(response), + options[:error_message] + ) + end + + def message_from_messages(*args) + args.reject { |m| error_message_empty?(m) }.join(' | ') + end + + def action_code_description(response) + return nil unless response['data'] + response['data']['DSC_COD_ACCION'] + end + + def error_message_empty?(error_message) + empty?(error_message) || error_message == '[ ]' + end + + def response_error(raw_response, options, action) + response = parse(raw_response) + rescue JSON::ParserError + unparsable_response(raw_response) + else + return Response.new( + false, + message_from(response, options, action), + response, + :test => test?, + :authorization => response['transactionUUID'], + :error_code => response['errorCode'] + ) + end + + def unparsable_response(raw_response) + message = 'Invalid JSON response received from VisanetPeruGateway. Please contact VisanetPeruGateway if you continue to receive this message.' + message += " (The raw response returned by the API was #{raw_response.inspect})" + return Response.new(false, message) + end + end + end +end diff --git a/lib/active_merchant/billing/gateways/webpay.rb b/lib/active_merchant/billing/gateways/webpay.rb index 07f8a82f500..29636897892 100644 --- a/lib/active_merchant/billing/gateways/webpay.rb +++ b/lib/active_merchant/billing/gateways/webpay.rb @@ -13,25 +13,23 @@ class WebpayGateway < StripeGateway self.homepage_url = 'https://webpay.jp/' self.display_name = 'WebPay' - def authorize(money, credit_card, options = {}) - raise NotImplementedError.new - end - - def capture(money, credit_card, options = {}) - raise NotImplementedError.new + def capture(money, authorization, options = {}) + post = {} + add_amount(post, money, options) + add_application_fee(post, options) + commit(:post, "charges/#{CGI.escape(authorization)}/capture", post) end def refund(money, identification, options = {}) - post = {:amount => localized_amount(money)} - commit_options = generate_meta(options) - + post = {} + add_amount(post, money, options) MultiResponse.run do |r| - r.process { commit(:post, "charges/#{CGI.escape(identification)}/refund", post, commit_options) } + r.process { commit(:post, "charges/#{CGI.escape(identification)}/refund", post, options) } return r unless options[:refund_fee_amount] - r.process { fetch_application_fees(identification, commit_options) } - r.process { refund_application_fee(options[:refund_fee_amount], application_fee_from_response(r), commit_options) } + r.process { fetch_application_fees(identification, options) } + r.process { refund_application_fee(options[:refund_fee_amount], application_fee_from_response(r), options) } end end @@ -39,40 +37,59 @@ def refund_fee(identification, options, meta) raise NotImplementedError.new end - def localized_amount(money, currency = self.default_currency) - non_fractional_currency?(currency) ? (amount(money).to_f / 100).floor : amount(money) + def add_customer(post, creditcard, options) + post[:customer] = options[:customer] if options[:customer] && !creditcard.respond_to?(:number) end - def add_amount(post, money, options) - post[:currency] = (options[:currency] || currency(money)).downcase - post[:amount] = localized_amount(money, post[:currency].upcase) + def store(creditcard, options = {}) + post = {} + add_creditcard(post, creditcard, options) + post[:description] = options[:description] + post[:email] = options[:email] + + if options[:customer] + MultiResponse.run(:first) do |r| + r.process { commit(:post, "customers/#{CGI.escape(options[:customer])}/", post, options) } + + return r unless options[:set_default] and r.success? and !r.params['id'].blank? + + r.process { update_customer(options[:customer], :default_card => r.params['id']) } + end + else + commit(:post, 'customers', post, options) + end + end + + def update(customer_id, creditcard, options = {}) + post = {} + add_creditcard(post, creditcard, options) + commit(:post, "customers/#{CGI.escape(customer_id)}", post, options) + end + + private + + def create_post_for_auth_or_purchase(money, creditcard, options) + stripe_post = super + stripe_post[:description] ||= stripe_post.delete(:metadata).try(:[], :email) + stripe_post end def json_error(raw_response) msg = 'Invalid response received from the WebPay API. Please contact support@webpay.jp if you continue to receive this message.' msg += " (The raw response returned by the API was #{raw_response.inspect})" { - "error" => { - "message" => msg + 'error' => { + 'message' => msg } } end def headers(options = {}) - @@ua ||= JSON.dump({ - :bindings_version => ActiveMerchant::VERSION, - :lang => 'ruby', - :lang_version => "#{RUBY_VERSION} p#{RUBY_PATCHLEVEL} (#{RUBY_RELEASE_DATE})", - :platform => RUBY_PLATFORM, - :publisher => 'active_merchant', - :uname => (RUBY_PLATFORM =~ /linux|darwin/i ? `uname -a 2>/dev/null`.strip : nil) - }) - { - "Authorization" => "Basic " + Base64.encode64(@api_key.to_s + ":").strip, - "User-Agent" => "Webpay/v1 ActiveMerchantBindings/#{ActiveMerchant::VERSION}", - "X-Webpay-Client-User-Agent" => @@ua, - "X-Webpay-Client-User-Metadata" => options[:meta].to_json + 'Authorization' => 'Basic ' + Base64.encode64(@api_key.to_s + ':').strip, + 'User-Agent' => "Webpay/v1 ActiveMerchantBindings/#{ActiveMerchant::VERSION}", + 'X-Webpay-Client-User-Agent' => user_agent, + 'X-Webpay-Client-User-Metadata' => {:ip => options[:ip]}.to_json } end end diff --git a/lib/active_merchant/billing/gateways/wepay.rb b/lib/active_merchant/billing/gateways/wepay.rb new file mode 100644 index 00000000000..9ec05da7f7f --- /dev/null +++ b/lib/active_merchant/billing/gateways/wepay.rb @@ -0,0 +1,237 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class WepayGateway < Gateway + self.test_url = 'https://stage.wepayapi.com/v2' + self.live_url = 'https://wepayapi.com/v2' + + self.supported_countries = ['US', 'CA'] + self.supported_cardtypes = [:visa, :master, :american_express, :discover] + self.homepage_url = 'https://www.wepay.com/' + self.default_currency = 'USD' + self.display_name = 'WePay' + + def initialize(options = {}) + requires!(options, :client_id, :account_id, :access_token) + super(options) + end + + def purchase(money, payment_method, options = {}) + post = {} + if payment_method.is_a?(String) + MultiResponse.run do |r| + r.process { authorize_with_token(post, money, payment_method, options) } + r.process { capture(money, r.authorization, options) } + end + else + MultiResponse.run do |r| + r.process { store(payment_method, options) } + r.process { authorize_with_token(post, money, r.authorization, options) } + r.process { capture(money, r.authorization, options) } + end + end + end + + def authorize(money, payment_method, options = {}) + post = {} + if payment_method.is_a?(String) + authorize_with_token(post, money, payment_method, options) + else + MultiResponse.run do |r| + r.process { store(payment_method, options) } + r.process { authorize_with_token(post, money, r.authorization, options) } + end + end + end + + def capture(money, identifier, options = {}) + checkout_id, original_amount = split_authorization(identifier) + + post = {} + post[:checkout_id] = checkout_id + if(money && (original_amount != amount(money))) + post[:amount] = amount(money) + end + commit('/checkout/capture', post, options) + end + + def void(identifier, options = {}) + post = {} + post[:checkout_id] = split_authorization(identifier).first + post[:cancel_reason] = (options[:description] || 'Void') + commit('/checkout/cancel', post, options) + end + + def refund(money, identifier, options = {}) + checkout_id, original_amount = split_authorization(identifier) + + post = {} + post[:checkout_id] = checkout_id + if(money && (original_amount != amount(money))) + post[:amount] = amount(money) + end + post[:refund_reason] = (options[:description] || 'Refund') + post[:payer_email_message] = options[:payer_email_message] if options[:payer_email_message] + post[:payee_email_message] = options[:payee_email_message] if options[:payee_email_message] + commit('/checkout/refund', post, options) + end + + def store(creditcard, options = {}) + post = {} + post[:client_id] = @options[:client_id] + post[:user_name] = "#{creditcard.first_name} #{creditcard.last_name}" + post[:email] = options[:email] || 'unspecified@example.com' + post[:cc_number] = creditcard.number + post[:cvv] = creditcard.verification_value unless options[:recurring] + post[:expiration_month] = creditcard.month + post[:expiration_year] = creditcard.year + + if(billing_address = (options[:billing_address] || options[:address])) + post[:address] = {} + post[:address]['address1'] = billing_address[:address1] if billing_address[:address1] + post[:address]['city'] = billing_address[:city] if billing_address[:city] + post[:address]['country'] = billing_address[:country] if billing_address[:country] + post[:address]['region'] = billing_address[:state] if billing_address[:state] + post[:address]['postal_code'] = billing_address[:zip] + end + + if options[:recurring] == true + post[:client_secret] = @options[:client_secret] + commit('/credit_card/transfer', post, options) + else + post[:original_device] = options[:device_fingerprint] if options[:device_fingerprint] + post[:original_ip] = options[:ip] if options[:ip] + commit('/credit_card/create', post, options) + end + end + + def supports_scrubbing? + true + end + + def scrub(transcript) + transcript. + gsub(%r((\\?"cc_number\\?":\\?")[^\\"]+(\\?"))i, '\1[FILTERED]\2'). + gsub(%r((\\?"cvv\\?":\\?")[^\\"]+(\\?"))i, '\1[FILTERED]\2'). + gsub(%r((Authorization: Bearer )\w+)i, '\1[FILTERED]\2') + end + + private + + def authorize_with_token(post, money, token, options) + add_token(post, token) + add_product_data(post, money, options) + commit('/checkout/create', post, options) + end + + def add_product_data(post, money, options) + post[:account_id] = @options[:account_id] + post[:amount] = amount(money) + post[:short_description] = (options[:description] || 'Purchase') + post[:type] = (options[:type] || 'goods') + post[:currency] = (options[:currency] || currency(money)) + post[:long_description] = options[:long_description] if options[:long_description] + post[:payer_email_message] = options[:payer_email_message] if options[:payer_email_message] + post[:payee_email_message] = options[:payee_email_message] if options[:payee_email_message] + post[:reference_id] = options[:order_id] if options[:order_id] + post[:unique_id] = options[:unique_id] if options[:unique_id] + post[:redirect_uri] = options[:redirect_uri] if options[:redirect_uri] + post[:callback_uri] = options[:callback_uri] if options[:callback_uri] + post[:fallback_uri] = options[:fallback_uri] if options[:fallback_uri] + post[:require_shipping] = options[:require_shipping] if options[:require_shipping] + post[:shipping_fee] = options[:shipping_fee] if options[:shipping_fee] + post[:charge_tax] = options[:charge_tax] if options[:charge_tax] + post[:mode] = options[:mode] if options[:mode] + post[:preapproval_id] = options[:preapproval_id] if options[:preapproval_id] + post[:prefill_info] = options[:prefill_info] if options[:prefill_info] + post[:funding_sources] = options[:funding_sources] if options[:funding_sources] + post[:payer_rbits] = options[:payer_rbits] if options[:payer_rbits] + post[:transaction_rbits] = options[:transaction_rbits] if options[:transaction_rbits] + add_fee(post, options) + end + + def add_token(post, token) + payment_method = {} + payment_method[:type] = 'credit_card' + payment_method[:credit_card] = { + id: token, + auto_capture: false + } + + post[:payment_method] = payment_method + end + + def add_fee(post, options) + if options[:application_fee] || options[:fee_payer] + post[:fee] = {} + post[:fee][:app_fee] = options[:application_fee] if options[:application_fee] + post[:fee][:fee_payer] = options[:fee_payer] if options[:fee_payer] + end + end + + def parse(response) + JSON.parse(response) + end + + def commit(action, params, options={}) + begin + response = parse(ssl_post( + ((test? ? test_url : live_url) + action), + params.to_json, + headers(options) + )) + rescue ResponseError => e + response = parse(e.response.body) + end + + return Response.new( + success_from(response), + message_from(response), + response, + authorization: authorization_from(response, params), + test: test? + ) + rescue JSON::ParserError + return unparsable_response(response) + end + + def success_from(response) + (!response['error']) + end + + def message_from(response) + (response['error'] ? response['error_description'] : 'Success') + end + + def authorization_from(response, params) + return response['credit_card_id'].to_s if response['credit_card_id'] + + original_amount = response['amount'].nil? ? nil : sprintf('%0.02f', response['amount']) + [response['checkout_id'], original_amount].join('|') + end + + def split_authorization(authorization) + auth, original_amount = authorization.to_s.split('|') + [auth, original_amount] + end + + def unparsable_response(raw_response) + message = 'Invalid JSON response received from WePay. Please contact WePay support if you continue to receive this message.' + message += " (The raw response returned by the API was #{raw_response.inspect})" + return Response.new(false, message) + end + + def headers(options) + headers = { + 'Content-Type' => 'application/json', + 'User-Agent' => "ActiveMerchantBindings/#{ActiveMerchant::VERSION}", + 'Authorization' => "Bearer #{@options[:access_token]}" + } + headers['Api-Version'] = options[:version] if options[:version] + headers['Client-IP'] = options[:ip] if options[:ip] + headers['WePay-Risk-Token'] = options[:risk_token] if options[:risk_token] + + headers + end + end + end +end diff --git a/lib/active_merchant/billing/gateways/wirecard.rb b/lib/active_merchant/billing/gateways/wirecard.rb index b1f84d536cc..21218896b6a 100644 --- a/lib/active_merchant/billing/gateways/wirecard.rb +++ b/lib/active_merchant/billing/gateways/wirecard.rb @@ -3,10 +3,7 @@ module ActiveMerchant #:nodoc: module Billing #:nodoc: class WirecardGateway < Gateway - # Test server location self.test_url = 'https://c3-test.wirecard.com/secure/ssl-gateway' - - # Live server location self.live_url = 'https://c3.wirecard.com/secure/ssl-gateway' # The Namespaces are not really needed, because it just tells the System, that there's actually no namespace used. @@ -29,57 +26,123 @@ class WirecardGateway < Gateway # number 5551234 within area code 202 (country code 1). VALID_PHONE_FORMAT = /\+\d{1,3}(\(?\d{3}\)?)?\d{3}-\d{4}-\d{3}/ - # The countries the gateway supports merchants from as 2 digit ISO country codes + self.supported_cardtypes = [ :visa, :master, :american_express, :diners_club, :jcb ] self.supported_countries = %w(AD CY GI IM MT RO CH AT DK GR IT MC SM TR BE EE HU LV NL SK GB BG FI IS LI NO SI VA FR IL LT PL ES CZ DE IE LU PT SE) - - # Wirecard supports all major credit and debit cards: - # Visa, Mastercard, American Express, Diners Club, - # JCB, Switch, VISA Carte Bancaire, Visa Electron and UATP cards. - # They also support the latest anti-fraud systems such as Verified by Visa or Master Secure Code. - self.supported_cardtypes = [ - :visa, :master, :american_express, :diners_club, :jcb, :switch - ] - - # The homepage URL of the gateway self.homepage_url = 'http://www.wirecard.com' - - # The name of the gateway self.display_name = 'Wirecard' - - # The currency should normally be EUROs self.default_currency = 'EUR' - - # 100 is 1.00 Euro self.money_format = :cents + # Public: Create a new Wirecard gateway. + # + # options - A hash of options: + # :login - The username + # :password - The password + # :signature - The BusinessCaseSignature def initialize(options = {}) - # verify that username and password are supplied - requires!(options, :login, :password) - # unfortunately Wirecard also requires a BusinessCaseSignature in the XML request - requires!(options, :signature) + requires!(options, :login, :password, :signature) super end - # Authorization - def authorize(money, creditcard, options = {}) - options[:credit_card] = creditcard + # Authorization - the second parameter may be a CreditCard or + # a String which represents a GuWID reference to an earlier + # transaction. If a GuWID is given, rather than a CreditCard, + # then then the :recurring option will be forced to "Repeated" + def authorize(money, payment_method, options = {}) + if payment_method.respond_to?(:number) + options[:credit_card] = payment_method + else + options[:preauthorization] = payment_method + end commit(:preauthorization, money, options) end - # Capture Authorization def capture(money, authorization, options = {}) options[:preauthorization] = authorization commit(:capture, money, options) end - # Purchase - def purchase(money, creditcard, options = {}) - options[:credit_card] = creditcard + # Purchase - the second parameter may be a CreditCard or + # a String which represents a GuWID reference to an earlier + # transaction. If a GuWID is given, rather than a CreditCard, + # then then the :recurring option will be forced to "Repeated" + def purchase(money, payment_method, options = {}) + if payment_method.respond_to?(:number) + options[:credit_card] = payment_method + else + options[:preauthorization] = payment_method + end commit(:purchase, money, options) end + def void(identification, options = {}) + options[:preauthorization] = identification + commit(:reversal, nil, options) + end + + def refund(money, identification, options = {}) + options[:preauthorization] = identification + commit(:bookback, money, options) + end + + # Store card - Wirecard supports the notion of "Recurring + # Transactions" by allowing the merchant to provide a reference + # to an earlier transaction (the GuWID) rather than a credit + # card. A reusable reference (GuWID) can be obtained by sending + # a purchase or authorization transaction with the element + # "RECURRING_TRANSACTION/Type" set to "Initial". Subsequent + # transactions can then use the GuWID in place of a credit + # card by setting "RECURRING_TRANSACTION/Type" to "Repeated". + # + # This implementation of card store utilizes a Wirecard + # "Authorization Check" (a Preauthorization that is automatically + # reversed). It defaults to a check amount of "100" (i.e. + # $1.00) but this can be overriden (see below). + # + # IMPORTANT: In order to reuse the stored reference, the + # +authorization+ from the response should be saved by + # your application code. + # + # ==== Options specific to +store+ + # + # * <tt>:amount</tt> -- The amount, in cents, that should be + # "validated" by the Authorization Check. This amount will + # be reserved and then reversed. Default is 100. + # + # Note: This is not the only way to achieve a card store + # operation at Wirecard. Any +purchase+ or +authorize+ + # can be sent with +options[:recurring] = 'Initial'+ to make + # the returned authorization/GuWID usable in later transactions + # with +options[:recurring] = 'Repeated'+. + def store(creditcard, options = {}) + options[:credit_card] = creditcard + options[:recurring] = 'Initial' + money = options.delete(:amount) || 100 + # Amex does not support authorization_check + if creditcard.brand == 'american_express' + commit(:preauthorization, money, options) + else + commit(:authorization_check, money, options) + end + end + + def supports_scrubbing + true + end + + def scrub(transcript) + transcript. + gsub(%r((Authorization: Basic )\w+), '\1[FILTERED]'). + gsub(%r((<CreditCardNumber>)\d+(</CreditCardNumber>)), '\1[FILTERED]\2'). + gsub(%r((<CVC2>)[^<]+(</CVC2>)), '\1[FILTERED]\2') + end + private + def clean_description(description) + description.to_s.slice(0, 32).encode('US-ASCII', invalid: :replace, undef: :replace, replace: '?') + end + def prepare_options_hash(options) result = @options.merge(options) setup_address_hash!(result) @@ -95,6 +158,13 @@ def setup_address_hash!(options) options[:billing_address][:email] = options[:email] if options[:email] end + # If a GuWID (string-based reference) is passed rather than a + # credit card, then the :recurring type needs to be forced to + # "Repeated" + def setup_recurring_flag(options) + options[:recurring] = 'Repeated' if options[:preauthorization].present? + end + # Contact WireCard, make the XML request, and parse the # reply into a Response object def commit(action, money, options) @@ -105,19 +175,19 @@ def commit(action, money, options) response = parse(ssl_post(test? ? self.test_url : self.live_url, request, headers)) # Pending Status also means Acknowledged (as stated in their specification) - success = response[:FunctionResult] == "ACK" || response[:FunctionResult] == "PENDING" + success = response[:FunctionResult] == 'ACK' || response[:FunctionResult] == 'PENDING' message = response[:Message] authorization = response[:GuWID] Response.new(success, message, response, :test => test?, :authorization => authorization, - :avs_result => { :code => response[:avsCode] }, - :cvv_result => response[:cvCode] + :avs_result => { :code => avs_code(response, options) }, + :cvv_result => response[:CVCResponseCode] ) rescue ResponseError => e - if e.response.code == "401" - return Response.new(false, "Invalid Login") + if e.response.code == '401' + return Response.new(false, 'Invalid Login') else raise end @@ -131,7 +201,7 @@ def build_request(action, money, options) xml.instruct! xml.tag! 'WIRECARD_BXML' do xml.tag! 'W_REQUEST' do - xml.tag! 'W_JOB' do + xml.tag! 'W_JOB' do xml.tag! 'JobID', '' # UserID for this transaction xml.tag! 'BusinessCaseSignature', options[:signature] || options[:login] @@ -148,25 +218,36 @@ def add_transaction_data(xml, money, options) options[:order_id] ||= generate_unique_id xml.tag! "FNC_CC_#{options[:action].to_s.upcase}" do - xml.tag! 'FunctionID', options[:description].to_s.slice(0,32) + xml.tag! 'FunctionID', clean_description(options[:description]) xml.tag! 'CC_TRANSACTION' do xml.tag! 'TransactionID', options[:order_id] + xml.tag! 'CommerceType', options[:commerce_type] if options[:commerce_type] case options[:action] - when :preauthorization, :purchase + when :preauthorization, :purchase, :authorization_check + setup_recurring_flag(options) add_invoice(xml, money, options) - add_creditcard(xml, options[:credit_card]) + + if options[:credit_card] + add_creditcard(xml, options[:credit_card]) + else + xml.tag! 'GuWID', options[:preauthorization] + end + add_address(xml, options[:billing_address]) - when :capture + when :capture, :bookback + xml.tag! 'GuWID', options[:preauthorization] + add_amount(xml, money, options) + when :reversal xml.tag! 'GuWID', options[:preauthorization] - add_amount(xml, money) end + add_customer_data(xml, options) end end end # Includes the payment (amount, currency, country) to the transaction-xml def add_invoice(xml, money, options) - add_amount(xml, money) + add_amount(xml, money, options) xml.tag! 'Currency', options[:currency] || currency(money) xml.tag! 'CountryCode', options[:billing_address][:country] xml.tag! 'RECURRING_TRANSACTION' do @@ -175,13 +256,13 @@ def add_invoice(xml, money, options) end # Include the amount in the transaction-xml - def add_amount(xml, money) - xml.tag! 'Amount', amount(money) + def add_amount(xml, money, options) + xml.tag! 'Amount', localized_amount(money, options[:currency] || currency(money)) end # Includes the credit-card data to the transaction-xml def add_creditcard(xml, creditcard) - raise "Creditcard must be supplied!" if creditcard.nil? + raise 'Creditcard must be supplied!' if creditcard.nil? xml.tag! 'CREDIT_CARD_DATA' do xml.tag! 'CreditCardNumber', creditcard.number xml.tag! 'CVC2', creditcard.verification_value @@ -229,8 +310,8 @@ def parse(xml) xml = REXML::Document.new(xml) if root = REXML::XPath.first(xml, "#{basepath}/W_JOB") parse_response(response, root) - elsif root = REXML::XPath.first(xml, "//ERROR") - parse_error(response, root) + elsif root = REXML::XPath.first(xml, '//ERROR') + parse_error_only_response(response, root) else response[:Message] = "No valid XML response message received. \ Propably wrong credentials supplied with HTTP header." @@ -239,31 +320,49 @@ def parse(xml) response end - # Parse the <ProcessingStatus> Element which containts all important information + def parse_error_only_response(response, root) + error_code = REXML::XPath.first(root, 'Number') + response[:ErrorCode] = error_code.text if error_code + response[:Message] = parse_error(root) + end + + # Parse the <ProcessingStatus> Element which contains all important information def parse_response(response, root) status = nil - # get the root element for this Transaction + root.elements.to_a.each do |node| if node.name =~ /FNC_CC_/ - status = REXML::XPath.first(node, "CC_TRANSACTION/PROCESSING_STATUS") + status = REXML::XPath.first(node, 'CC_TRANSACTION/PROCESSING_STATUS') end end - message = "" + + message = '' if status if info = status.elements['Info'] message << info.text end - # Get basic response information + status.elements.to_a.each do |node| - response[node.name.to_sym] = (node.text || '').strip + if node.elements.size == 0 + response[node.name.to_sym] = (node.text || '').strip + else + node.elements.each do |childnode| + name = "#{node.name}_#{childnode.name}" + response[name.to_sym] = (childnode.text || '').strip + end + end end + + error_code = REXML::XPath.first(status, 'ERROR/Number') + response['ErrorCode'] = error_code.text if error_code end + parse_error(root, message) response[:Message] = message end # Parse a generic error response from the gateway - def parse_error(root, message = "") + def parse_error(root, message = '') # Get errors if available and append them to the message errors = errors_to_string(root) unless errors.strip.blank? @@ -278,7 +377,7 @@ def parse_error(root, message = "") def errors_to_string(root) # Get context error messages (can be 0..*) errors = [] - REXML::XPath.each(root, "//ERROR") do |error_elem| + REXML::XPath.each(root, '//ERROR') do |error_elem| error = {} error[:Advice] = [] error[:Message] = error_elem.elements['Message'].text @@ -290,7 +389,7 @@ def errors_to_string(root) # Convert all messages to a single string string = '' errors.each do |error| - string << error[:Message] + string << error[:Message] if error[:Message] error[:Advice].each_with_index do |advice, index| string << ' (' if index == 0 string << "#{index+1}. #{advice}" @@ -301,13 +400,33 @@ def errors_to_string(root) string end + # Amex have different AVS response codes + AMEX_TRANSLATED_AVS_CODES = { + 'A' => 'B', # CSC and Address Matched + 'F' => 'D', # All Data Matched + 'N' => 'I', # CSC Match + 'U' => 'U', # Data Not Checked + 'Y' => 'D', # All Data Matched + 'Z' => 'P', # CSC and Postcode Matched + } + + # Amex have different AVS response codes to visa etc + def avs_code(response, options) + if response.has_key?(:AVS_ProviderResultCode) + if options[:credit_card].present? && ActiveMerchant::Billing::CreditCard.brand?(options[:credit_card].number) == 'american_express' + AMEX_TRANSLATED_AVS_CODES[response[:AVS_ProviderResultCode]] + else + response[:AVS_ProviderResultCode] + end + end + end + # Encode login and password in Base64 to supply as HTTP header # (for http basic authentication) def encoded_credentials credentials = [@options[:login], @options[:password]].join(':') - "Basic " << Base64.encode64(credentials).strip + 'Basic ' << Base64.encode64(credentials).strip end end end end - diff --git a/lib/active_merchant/billing/gateways/world_net.rb b/lib/active_merchant/billing/gateways/world_net.rb new file mode 100644 index 00000000000..70699b1a139 --- /dev/null +++ b/lib/active_merchant/billing/gateways/world_net.rb @@ -0,0 +1,344 @@ +require 'nokogiri' + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + # See https://helpdesk.worldnettps.com/support/solutions/articles/1000167298-integrator-guide + class WorldNetGateway < Gateway + self.test_url = 'https://testpayments.worldnettps.com/merchant/xmlpayment' + self.live_url = 'https://payments.worldnettps.com/merchant/xmlpayment' + + self.homepage_url = 'http://worldnettps.com/' + self.display_name = 'WorldNet' + + self.supported_countries = %w(IE GB US) + self.default_currency = 'EUR' + + CARD_TYPES = { + visa: 'VISA', + master: 'MASTERCARD', + discover: 'DISCOVER', + american_express: 'AMEX', + maestro: 'MAESTRO', + diners_club: 'DINERS', + jcb: 'JCB', + secure_card: 'SECURECARD' + }.freeze + self.supported_cardtypes = CARD_TYPES.keys + + def initialize(options = {}) + requires!(options, :terminal_id, :secret) + options[:terminal_type] ||= 2 # eCommerce + super + end + + def purchase(money, payment, options = {}) + requires!(options, :order_id) + + post = {} + add_invoice(post, money, options) + add_payment(post, payment) + add_address(post, payment, options) + add_customer_data(post, options) + + commit('PAYMENT', post) + end + + def authorize(money, payment, options = {}) + requires!(options, :order_id) + + post = {} + add_invoice(post, money, options) + add_payment(post, payment) + add_address(post, payment, options) + add_customer_data(post, options) + + commit('PREAUTH', post) + end + + def capture(money, authorization, options = {}) + post = {} + add_invoice(post, money, options) + post[:uniqueref] = authorization + + commit('PREAUTHCOMPLETION', post) + end + + def refund(money, authorization, options = {}) + requires!(options, :operator, :reason) + + post = {} + post[:uniqueref] = authorization + add_invoice(post, money, options) + post[:operator] = options[:operator] + post[:reason] = options[:reason] + + commit('REFUND', post) + end + + def void(authorization, _options = {}) + post = {} + post[:uniqueref] = authorization + commit('VOID', post) + end + + def verify(credit_card, options = {}) + MultiResponse.run(:use_first_response) do |r| + r.process { authorize(100, credit_card, options) } + r.process(:ignore_result) { void(r.authorization, options) } + end + end + + def store(payment, options = {}) + requires!(options, :order_id) + + post = {} + post[:merchantref] = options[:order_id] + add_payment(post, payment) + + commit('SECURECARDREGISTRATION', post) + end + + def unstore(payment, options = {}) + requires!(options, :order_id) + + post = {} + post[:merchantref] = options[:order_id] + add_card_reference(post, payment) + + commit('SECURECARDREMOVAL', post) + end + + def supports_scrubbing? + true + end + + def scrub(transcript) + transcript. + gsub(%r{(<CARDNUMBER>\d{6})\d+(\d{4}</CARDNUMBER>)}, '\1...\2'). + gsub(%r{(<CVV>)\d+(</CVV)}, '\1...\2') + end + + private + + def add_customer_data(post, options) + post[:email] = options[:email] + post[:ipaddress] = options[:ip] + end + + def add_address(post, _creditcard, options) + address = options[:billing_address] || options[:address] + return unless address + post[:address1] = address[:address1] + post[:address2] = address[:address2] + post[:city] = address[:city] + post[:country] = address[:country] # ISO 3166-1-alpha-2 code. + post[:postcode] = address[:zip] + end + + def add_invoice(post, money, options) + post[:orderid] = options[:order_id] + post[:amount] = amount(money) + post[:currency] = (options[:currency] || currency(money)) + post[:description] = options[:description] + end + + def add_payment(post, payment) + # a payment triggered with a secure_card (tokenised card) will not + # respond to `:number` + if payment.respond_to?(:number) + post[:cardholdername] = cardholdername(payment) + post[:cardtype] = CARD_TYPES[payment.brand.to_sym] + post[:cardnumber] = payment.number + post[:cvv] = payment.verification_value if payment.verification_value + post[:cardexpiry] = expdate(payment) + else + post[:cardtype] = CARD_TYPES[:secure_card] + post[:cardnumber] = payment + end + end + + def add_card_reference(post, payment) + post[:cardreference] = payment + end + + def cardholdername(payment) + [payment.first_name, payment.last_name].join(' ').slice(0, 60) + end + + def parse(action, body) + results = {} + xml = Nokogiri::XML(body) + resp = xml.xpath("//#{action}RESPONSE | //ERROR") + resp.children.each do |element| + results[element.name.downcase.to_sym] = element.text + end + results + end + + def commit(action, parameters) + url = (test? ? test_url : live_url) + response = parse(action, ssl_post(url, post_data(action, parameters))) + + Response.new( + success_from(action, response), + message_from(response), + response, + authorization: authorization_from(action, response), + avs_result: AVSResult.new(code: response[:avs_response]), + cvv_result: CVVResult.new(response[:cvv_response]), + test: test?, + error_code: success_from(action, response) ? nil : message_to_standard_error_code_from(response) + ) + end + + def success_from(action, response) + case action + when 'SECURECARDREGISTRATION' + response[:cardreference].present? + when 'SECURECARDREMOVAL' + response[:datetime].present? && response[:hash].present? + else + response[:responsecode] == 'A' + end + end + + def message_to_standard_error_code_from(response) + case message_from(response) + when /DECLINED/ + STANDARD_ERROR_CODE[:card_declined] + when /CVV FAILURE/ + STANDARD_ERROR_CODE[:incorrect_cvc] + when /Invalid CARDEXPIRY field/ + STANDARD_ERROR_CODE[:invalid_expiry_date] + else + STANDARD_ERROR_CODE[:processing_error] + end + end + + def message_from(response) + response[:responsetext] || response[:errorstring] + end + + def authorization_from(action, response) + case action + when 'SECURECARDREGISTRATION' + response[:cardreference] + else + response[:uniqueref] + end + end + + def post_data(action, parameters = {}) + parameters[:terminalid] = @options[:terminal_id] + parameters[:terminaltype] = @options[:terminal_type] + parameters[:transactiontype] = 7 # eCommerce + parameters[:datetime] = create_time_stamp + parameters[:hash] = case action + when 'SECURECARDREGISTRATION' + build_store_signature(parameters) + when 'SECURECARDREMOVAL' + build_unstore_signature(parameters) + else + build_signature(parameters) + end + build_xml_request(action, fields(action), parameters) + end + + def create_time_stamp + Time.now.gmtime.strftime('%d-%m-%Y:%H:%M:%S:%L') + end + + def build_signature(parameters) + str = parameters[:terminalid] + str += (parameters[:uniqueref] || parameters[:orderid]) + str += (parameters[:amount].to_s + parameters[:datetime]) + Digest::MD5.hexdigest(str + @options[:secret]) + end + + def build_store_signature(parameters) + str = parameters[:terminalid] + str += parameters[:merchantref] + str += parameters[:datetime] + str += parameters[:cardnumber] + str += parameters[:cardexpiry] + str += parameters[:cardtype] + str += parameters[:cardholdername] + Digest::MD5.hexdigest(str + @options[:secret]) + end + + def build_unstore_signature(parameters) + str = parameters[:terminalid] + str += parameters[:merchantref] + str += parameters[:datetime] + str += parameters[:cardreference] + Digest::MD5.hexdigest(str + @options[:secret]) + end + + def fields(action) + # Gateway expects fields in fixed order below. + case action + when 'PAYMENT', 'PREAUTH' + [ + :orderid, + :terminalid, + :amount, + :datetime, + :cardnumber, :cardtype, :cardexpiry, :cardholdername, + :hash, + :currency, + :terminaltype, + :transactiontype, + :email, + :cvv, + :address1, :address2, + :postcode, + :description, + :city, :country, + :ipaddress + ] + when 'PREAUTHCOMPLETION' + [:uniqueref, :terminalid, :amount, :datetime, :hash] + when 'REFUND' + [:uniqueref, :terminalid, :amount, :datetime, :hash, + :operator, :reason] + when 'VOID' + [:uniqueref] + when 'SECURECARDREGISTRATION' + [ + :merchantref, + :terminalid, + :datetime, + :cardnumber, :cardexpiry, :cardtype, :cardholdername, + :hash, + :dontchecksecurity, + :cvv, + :issueno + ] + when 'SECURECARDREMOVAL' + [ + :merchantref, + :cardreference, + :terminalid, + :datetime, + :hash + ] + end + end + + def build_xml_request(action, fields, data) + xml = Builder::XmlMarkup.new indent: 2 + xml.instruct!(:xml, version: '1.0', encoding: 'utf-8') + xml.tag!(action) do + fields.each do |field| + xml.tag!(field.to_s.upcase, data[field]) if data[field] + end + end + xml.target! + end + + def expdate(credit_card) + sprintf('%02d%02d', credit_card.month, credit_card.year % 100) + end + end + end +end diff --git a/lib/active_merchant/billing/gateways/worldpay.rb b/lib/active_merchant/billing/gateways/worldpay.rb index 7e62989ed86..bb2ec55a3b5 100644 --- a/lib/active_merchant/billing/gateways/worldpay.rb +++ b/lib/active_merchant/billing/gateways/worldpay.rb @@ -6,10 +6,12 @@ class WorldpayGateway < Gateway self.default_currency = 'GBP' self.money_format = :cents - self.supported_countries = %w(HK US GB AU AD BE CH CY CZ DE DK ES FI FR GI GR HU IE IL IT LI LU MC MT NL NO NZ PL PT SE SG SI SM TR UM VA) - self.supported_cardtypes = [:visa, :master, :american_express, :discover, :jcb, :maestro, :laser] + self.supported_countries = %w(HK GB AU AD AR BE BR CA CH CN CO CR CY CZ DE DK ES FI FR GI GR HU IE IN IT JP LI LU MC MT MY MX NL NO NZ PA PE PL PT SE SG SI SM TR UM VA) + self.supported_cardtypes = [:visa, :master, :american_express, :discover, :jcb, :maestro, :elo, :naranja, :cabal] + self.currencies_without_fractions = %w(HUF IDR ISK JPY KRW) + self.currencies_with_three_decimal_places = %w(BHD KWD OMR RSD TND) self.homepage_url = 'http://www.worldpay.com/' - self.display_name = 'WorldPay' + self.display_name = 'Worldpay Global' CARD_CODES = { 'visa' => 'VISA-SSL', @@ -18,8 +20,31 @@ class WorldpayGateway < Gateway 'american_express' => 'AMEX-SSL', 'jcb' => 'JCB-SSL', 'maestro' => 'MAESTRO-SSL', - 'laser' => 'LASER-SSL', - 'diners_club' => 'DINERS-SSL' + 'diners_club' => 'DINERS-SSL', + 'elo' => 'ELO-SSL', + 'naranja' => 'NARANJA-SSL', + 'cabal' => 'CABAL-SSL', + 'unknown' => 'CARD-SSL' + } + + AVS_CODE_MAP = { + 'A' => 'M', # Match + 'B' => 'P', # Postcode matches, address not verified + 'C' => 'Z', # Postcode matches, address does not match + 'D' => 'B', # Address matched; postcode not checked + 'E' => 'I', # Address and postal code not checked + 'F' => 'A', # Address matches, postcode does not match + 'G' => 'C', # Address does not match, postcode not checked + 'H' => 'I', # Address and postcode not provided + 'I' => 'C', # Address not checked postcode does not match + 'J' => 'C', # Address and postcode does not match + } + + CVC_CODE_MAP = { + 'A' => 'M', # CVV matches + 'B' => 'P', # Not provided + 'C' => 'P', # Not checked + 'D' => 'N', # Does not match } def initialize(options = {}) @@ -29,68 +54,117 @@ def initialize(options = {}) def purchase(money, payment_method, options = {}) MultiResponse.run do |r| - r.process{authorize(money, payment_method, options)} - r.process{capture(money, r.authorization, options.merge(:authorization_validated => true))} + r.process { authorize(money, payment_method, options) } + r.process { capture(money, r.authorization, options.merge(:authorization_validated => true)) } end end def authorize(money, payment_method, options = {}) requires!(options, :order_id) - authorize_request(money, payment_method, options) + payment_details = payment_details_from(payment_method) + authorize_request(money, payment_method, payment_details.merge(options)) end def capture(money, authorization, options = {}) + authorization = order_id_from_authorization(authorization.to_s) MultiResponse.run do |r| - r.process{inquire_request(authorization, options, "AUTHORISED")} unless options[:authorization_validated] + r.process { inquire_request(authorization, options, 'AUTHORISED') } unless options[:authorization_validated] if r.params authorization_currency = r.params['amount_currency_code'] options = options.merge(:currency => authorization_currency) if authorization_currency.present? end - r.process{capture_request(money, authorization, options)} + r.process { capture_request(money, authorization, options) } end end def void(authorization, options = {}) + authorization = order_id_from_authorization(authorization.to_s) MultiResponse.run do |r| - r.process{inquire_request(authorization, options, "AUTHORISED")} - r.process{cancel_request(authorization, options)} + r.process { inquire_request(authorization, options, 'AUTHORISED') } unless options[:authorization_validated] + r.process { cancel_request(authorization, options) } end end def refund(money, authorization, options = {}) - MultiResponse.run do |r| - r.process{inquire_request(authorization, options, "CAPTURED", "SETTLED")} - r.process{refund_request(money, authorization, options)} + authorization = order_id_from_authorization(authorization.to_s) + response = MultiResponse.run do |r| + r.process { inquire_request(authorization, options, 'CAPTURED', 'SETTLED', 'SETTLED_BY_MERCHANT') } + r.process { refund_request(money, authorization, options) } + end + + return response if response.success? + return response unless options[:force_full_refund_if_unsettled] + + void(authorization, options) if response.params['last_event'] == 'AUTHORISED' + end + + # Credits only function on a Merchant ID/login/profile flagged for Payouts + # aka Credit Fund Transfers (CFT), whereas normal purchases, refunds, + # and other transactions should be performed on a normal eCom-flagged + # merchant ID. + def credit(money, payment_method, options = {}) + payment_details = payment_details_from(payment_method) + credit_request(money, payment_method, payment_details.merge(:credit => true, **options)) + end + + def verify(payment_method, options={}) + MultiResponse.run(:use_first_response) do |r| + r.process { authorize(100, payment_method, options) } + r.process(:ignore_result) { void(r.authorization, options.merge(:authorization_validated => true)) } end end + def store(credit_card, options={}) + requires!(options, :customer) + store_request(credit_card, options) + end + + def supports_scrubbing + true + end + + def scrub(transcript) + transcript. + gsub(%r((Authorization: Basic )\w+), '\1[FILTERED]'). + gsub(%r((<cardNumber>)\d+(</cardNumber>)), '\1[FILTERED]\2'). + gsub(%r((<cvc>)[^<]+(</cvc>)), '\1[FILTERED]\2') + end + private def authorize_request(money, payment_method, options) - commit('authorize', build_authorization_request(money, payment_method, options), "AUTHORISED") + commit('authorize', build_authorization_request(money, payment_method, options), 'AUTHORISED', options) end def capture_request(money, authorization, options) - commit('capture', build_capture_request(money, authorization, options), :ok) + commit('capture', build_capture_request(money, authorization, options), :ok, options) end def cancel_request(authorization, options) - commit('cancel', build_void_request(authorization, options), :ok) + commit('cancel', build_void_request(authorization, options), :ok, options) end def inquire_request(authorization, options, *success_criteria) - commit('inquiry', build_order_inquiry_request(authorization, options), *success_criteria) + commit('inquiry', build_order_inquiry_request(authorization, options), *success_criteria, options) end def refund_request(money, authorization, options) - commit('refund', build_refund_request(money, authorization, options), :ok) + commit('refund', build_refund_request(money, authorization, options), :ok, options) + end + + def credit_request(money, payment_method, options) + commit('credit', build_authorization_request(money, payment_method, options), :ok, 'SENT_FOR_REFUND', options) + end + + def store_request(credit_card, options) + commit('store', build_store_request(credit_card, options), options) end def build_request xml = Builder::XmlMarkup.new :indent => 2 - xml.instruct! :xml, :encoding => 'ISO-8859-1' - xml.declare! :DOCTYPE, :paymentService, :PUBLIC, "-//WorldPay//DTD WorldPay PaymentService v1//EN", "http://dtd.wp3.rbsworldpay.com/paymentService_v1.dtd" - xml.tag! 'paymentService', 'version' => "1.4", 'merchantCode' => @options[:login] do + xml.instruct! :xml, :encoding => 'UTF-8' + xml.declare! :DOCTYPE, :paymentService, :PUBLIC, '-//WorldPay//DTD WorldPay PaymentService v1//EN', 'http://dtd.worldpay.com/paymentService_v1.dtd' + xml.tag! 'paymentService', 'version' => '1.4', 'merchantCode' => @options[:login] do yield xml end xml.target! @@ -117,8 +191,8 @@ def build_order_inquiry_request(authorization, options) def build_authorization_request(money, payment_method, options) build_request do |xml| xml.tag! 'submit' do - xml.tag! 'order', {'orderCode' => options[:order_id], 'installationId' => @options[:inst_id]}.reject{|_,v| !v} do - xml.description(options[:description].blank? ? "Purchase" : options[:description]) + xml.tag! 'order', order_tag_attributes(options) do + xml.description(options[:description].blank? ? 'Purchase' : options[:description]) add_amount(xml, money, options) if options[:order_content] xml.tag! 'orderContent' do @@ -126,11 +200,22 @@ def build_authorization_request(money, payment_method, options) end end add_payment_method(xml, money, payment_method, options) + add_shopper(xml, options) + if options[:hcg_additional_data] + add_hcg_additional_data(xml, options) + end + if options[:instalments] + add_instalments_data(xml, options) + end end end end end + def order_tag_attributes(options) + { 'orderCode' => options[:order_id], 'installationId' => options[:inst_id] || @options[:inst_id] }.reject { |_, v| !v } + end + def build_capture_request(money, authorization, options) build_order_modify_request(authorization) do |xml| xml.tag! 'capture' do @@ -150,108 +235,264 @@ def build_void_request(authorization, options) def build_refund_request(money, authorization, options) build_order_modify_request(authorization) do |xml| xml.tag! 'refund' do - add_amount(xml, money, options.merge(:debit_credit_indicator => "credit")) + add_amount(xml, money, options.merge(:debit_credit_indicator => 'credit')) + end + end + end + + def build_store_request(credit_card, options) + build_request do |xml| + xml.tag! 'submit' do + xml.tag! 'paymentTokenCreate' do + add_authenticated_shopper_id(xml, options) + xml.tag! 'createToken' + xml.tag! 'paymentInstrument' do + xml.tag! 'cardDetails' do + add_card(xml, credit_card, options) + end + end + end end end end def add_amount(xml, money, options) currency = options[:currency] || currency(money) - amount = localized_amount(money, currency) amount_hash = { - :value => amount, + :value => localized_amount(money, currency), 'currencyCode' => currency, - 'exponent' => 2 + 'exponent' => currency_exponent(currency) } if options[:debit_credit_indicator] - amount_hash.merge!('debitCreditIndicator' => options[:debit_credit_indicator]) + amount_hash['debitCreditIndicator'] = options[:debit_credit_indicator] end xml.tag! 'amount', amount_hash end def add_payment_method(xml, amount, payment_method, options) - if payment_method.is_a?(String) - xml.tag! 'payAsOrder', 'orderCode' => payment_method do - add_amount(xml, amount, options) + if options[:payment_type] == :pay_as_order + if options[:merchant_code] + xml.tag! 'payAsOrder', 'orderCode' => payment_method, 'merchantCode' => options[:merchant_code] do + add_amount(xml, amount, options) + end + else + xml.tag! 'payAsOrder', 'orderCode' => payment_method do + add_amount(xml, amount, options) + end end else - xml.tag! 'paymentDetails' do - xml.tag! CARD_CODES[card_brand(payment_method)] do - xml.tag! 'cardNumber', payment_method.number - xml.tag! 'expiryDate' do - xml.tag! 'date', 'month' => format(payment_method.month, :two_digits), 'year' => format(payment_method.year, :four_digits) + xml.tag! 'paymentDetails', credit_fund_transfer_attribute(options) do + if options[:payment_type] == :token + xml.tag! 'TOKEN-SSL', 'tokenScope' => options[:token_scope] do + xml.tag! 'paymentTokenID', options[:token_id] end + else + xml.tag! card_code_for(payment_method) do + add_card(xml, payment_method, options) + end + end + add_stored_credential_options(xml, options) + if options[:ip] && options[:session_id] + xml.tag! 'session', 'shopperIPAddress' => options[:ip], 'id' => options[:session_id] + else + xml.tag! 'session', 'shopperIPAddress' => options[:ip] if options[:ip] + xml.tag! 'session', 'id' => options[:session_id] if options[:session_id] + end - xml.tag! 'cardHolderName', payment_method.name - xml.tag! 'cvc', payment_method.verification_value - - add_address(xml, 'cardAddress', (options[:billing_address] || options[:address])) + if three_d_secure = options[:three_d_secure] + add_three_d_secure(three_d_secure, xml) end end end end - def add_address(xml, element, address) - return if address.nil? + def add_three_d_secure(three_d_secure, xml) + xml.tag! 'info3DSecure' do + xml.tag! 'threeDSVersion', three_d_secure[:version] + if three_d_secure[:version] =~ /^2/ + xml.tag! 'dsTransactionId', three_d_secure[:ds_transaction_id] + else + xml.tag! 'xid', three_d_secure[:xid] + end + xml.tag! 'cavv', three_d_secure[:cavv] + xml.tag! 'eci', three_d_secure[:eci] + end + end + + def add_card(xml, payment_method, options) + xml.tag! 'cardNumber', payment_method.number + xml.tag! 'expiryDate' do + xml.tag! 'date', 'month' => format(payment_method.month, :two_digits), 'year' => format(payment_method.year, :four_digits) + end + + xml.tag! 'cardHolderName', options[:execute_threed] ? '3D' : payment_method.name + xml.tag! 'cvc', payment_method.verification_value + + add_address(xml, (options[:billing_address] || options[:address])) + end + + def add_stored_credential_options(xml, options={}) + if options[:stored_credential] + add_stored_credential_using_normalized_fields(xml, options) + else + add_stored_credential_using_gateway_specific_fields(xml, options) + end + end + + def add_stored_credential_using_normalized_fields(xml, options) + if options[:stored_credential][:initial_transaction] + xml.tag! 'storedCredentials', 'usage' => 'FIRST' + else + reason = case options[:stored_credential][:reason_type] + when 'installment' then 'INSTALMENT' + when 'recurring' then 'RECURRING' + when 'unscheduled' then 'UNSCHEDULED' + end + + xml.tag! 'storedCredentials', 'usage' => 'USED', 'merchantInitiatedReason' => reason do + xml.tag! 'schemeTransactionIdentifier', options[:stored_credential][:network_transaction_id] if options[:stored_credential][:network_transaction_id] + end + end + end + + def add_stored_credential_using_gateway_specific_fields(xml, options) + return unless options[:stored_credential_usage] - xml.tag! element do + if options[:stored_credential_initiated_reason] + xml.tag! 'storedCredentials', 'usage' => options[:stored_credential_usage], 'merchantInitiatedReason' => options[:stored_credential_initiated_reason] do + xml.tag! 'schemeTransactionIdentifier', options[:stored_credential_transaction_id] if options[:stored_credential_transaction_id] + end + else + xml.tag! 'storedCredentials', 'usage' => options[:stored_credential_usage] + end + end + + def add_shopper(xml, options) + return unless options[:execute_threed] || options[:email] || options[:customer] + xml.tag! 'shopper' do + xml.tag! 'shopperEmailAddress', options[:email] if options[:email] + add_authenticated_shopper_id(xml, options) + xml.tag! 'browser' do + xml.tag! 'acceptHeader', options[:accept_header] + xml.tag! 'userAgentHeader', options[:user_agent] + end + end + end + + def add_authenticated_shopper_id(xml, options) + xml.tag!('authenticatedShopperID', options[:customer]) if options[:customer] + end + + def add_address(xml, address) + return unless address + + address = address_with_defaults(address) + + xml.tag! 'cardAddress' do xml.tag! 'address' do if m = /^\s*([^\s]+)\s+(.+)$/.match(address[:name]) xml.tag! 'firstName', m[1] xml.tag! 'lastName', m[2] end - if m = /^\s*(\d+)\s+(.+)$/.match(address[:address1]) - xml.tag! 'street', m[2] - house_number = m[1] - else - xml.tag! 'street', address[:address1] - end - xml.tag! 'houseName', address[:address2] if address[:address2] - xml.tag! 'houseNumber', house_number if house_number.present? - xml.tag! 'postalCode', (address[:zip].present? ? address[:zip] : "0000") - xml.tag! 'city', address[:city] if address[:city] - xml.tag! 'state', (address[:state].present? ? address[:state] : 'N/A') + xml.tag! 'address1', address[:address1] + xml.tag! 'address2', address[:address2] if address[:address2] + xml.tag! 'postalCode', address[:zip] + xml.tag! 'city', address[:city] + xml.tag! 'state', address[:state] xml.tag! 'countryCode', address[:country] xml.tag! 'telephoneNumber', address[:phone] if address[:phone] end end end + def add_hcg_additional_data(xml, options) + xml.tag! 'hcgAdditionalData' do + options[:hcg_additional_data].each do |k, v| + xml.tag! 'param', {name: k.to_s}, v + end + end + end + + def add_instalments_data(xml, options) + xml.tag! 'thirdPartyData' do + xml.tag! 'instalments', options[:instalments] + xml.tag! 'cpf', options[:cpf] if options[:cpf] + end + end + + def address_with_defaults(address) + address ||= {} + address.delete_if { |_, v| v.blank? } + address.reverse_merge!(default_address) + end + + def default_address + { + address1: 'N/A', + zip: '0000', + city: 'N/A', + state: 'N/A', + country: 'US' + } + end + def parse(action, xml) parse_element({:action => action}, REXML::Document.new(xml)) end def parse_element(raw, node) + node_name = node.name.underscore node.attributes.each do |k, v| - raw["#{node.name.underscore}_#{k.underscore}".to_sym] = v + raw["#{node_name}_#{k.underscore}".to_sym] = v end if node.has_elements? - raw[node.name.underscore.to_sym] = true unless node.name.blank? - node.elements.each{|e| parse_element(raw, e) } + raw[node_name.to_sym] = true unless node.name.blank? + node.elements.each { |e| parse_element(raw, e) } + elsif node.children.count > 1 + raw[node_name.to_sym] = node.children.join(' ').strip else - raw[node.name.underscore.to_sym] = node.text unless node.text.nil? + raw[node_name.to_sym] = node.text unless node.text.nil? end raw end - def commit(action, request, *success_criteria) - xmr = ssl_post(url, request, 'Content-Type' => 'text/xml', 'Authorization' => encoded_credentials) - raw = parse(action, xmr) - success, message = success_and_message_from(raw, success_criteria) + def headers(options) + headers = { + 'Content-Type' => 'text/xml', + 'Authorization' => encoded_credentials + } + if options[:cookie] + headers['Cookie'] = options[:cookie] if options[:cookie] + end + headers + end + + def commit(action, request, *success_criteria, options) + xml = ssl_post(url, request, headers(options)) + raw = parse(action, xml) + if options[:execute_threed] + raw[:cookie] = @cookie + raw[:session_id] = options[:session_id] + end + success = success_from(action, raw, success_criteria) + message = message_from(success, raw, success_criteria) Response.new( success, message, raw, - :authorization => authorization_from(raw), - :test => test?) - + :authorization => authorization_from(action, raw, options), + :error_code => error_code_from(success, raw), + :test => test?, + :avs_result => AVSResult.new(code: AVS_CODE_MAP[raw[:avs_result_code_description]]), + :cvv_result => CVVResult.new(CVC_CODE_MAP[raw[:cvc_result_code_description]]) + ) rescue ActiveMerchant::ResponseError => e - if e.response.code.to_s == "401" - return Response.new(false, "Invalid credentials", {}, :test => test?) + if e.response.code.to_s == '401' + return Response.new(false, 'Invalid credentials', {}, :test => test?) else raise e end @@ -261,42 +502,132 @@ def url test? ? self.test_url : self.live_url end + # Override the regular handle response so we can access the headers + # Set-Cookie value is needed for 3DS transactions + def handle_response(response) + case response.code.to_i + when 200...300 + @cookie = response['Set-Cookie'] + response.body + else + raise ResponseError.new(response) + end + end + + def success_from(action, raw, success_criteria) + success_criteria_success?(raw, success_criteria) || action_success?(action, raw) + end + + def message_from(success, raw, success_criteria) + return 'SUCCESS' if success + raw[:iso8583_return_code_description] || raw[:error] || required_status_message(raw, success_criteria) + end + # success_criteria can be: # - a string or an array of strings (if one of many responses) # - An array of strings if one of many responses could be considered a # success. - def success_and_message_from(raw, success_criteria) - success = (success_criteria.include?(raw[:last_event]) || raw[:ok].present?) - if success - message = "SUCCESS" + def success_criteria_success?(raw, success_criteria) + success_criteria.include?(raw[:last_event]) || raw[:ok].present? + end + + def action_success?(action, raw) + case action + when 'store' + raw[:token].present? else - message = (raw[:iso8583_return_code_description] || raw[:error] || required_status_message(raw, success_criteria)) + false end + end - [ success, message ] + def error_code_from(success, raw) + unless success == 'SUCCESS' + raw[:iso8583_return_code_code] || raw[:error_code] || nil + end end def required_status_message(raw, success_criteria) if(!success_criteria.include?(raw[:last_event])) - "A transaction status of #{success_criteria.collect{|c| "'#{c}'"}.join(" or ")} is required." + "A transaction status of #{success_criteria.collect { |c| "'#{c}'" }.join(" or ")} is required." + end + end + + def authorization_from(action, raw, options) + order_id = order_id_from(raw) + + case action + when 'store' + authorization_from_token_details( + order_id: order_id, + token_id: raw[:payment_token_id], + token_scope: 'shopper', + customer: options[:customer] + ) + else + order_id end end - def authorization_from(raw) - pair = raw.detect{|k,v| k.to_s =~ /_order_code$/} + def order_id_from(raw) + pair = raw.detect { |k, v| k.to_s =~ /_order_code$/ } (pair ? pair.last : nil) end + def authorization_from_token_details(options={}) + [options[:order_id], options[:token_id], options[:token_scope], options[:customer]].join('|') + end + + def order_id_from_authorization(authorization) + token_details_from_authorization(authorization)[:order_id] + end + + def token_details_from_authorization(authorization) + order_id, token_id, token_scope, customer = authorization.split('|') + + token_details = {} + token_details[:order_id] = order_id if order_id.present? + token_details[:token_id] = token_id if token_id.present? + token_details[:token_scope] = token_scope if token_scope.present? + token_details[:customer] = customer if customer.present? + + token_details + end + + def payment_details_from(payment_method) + payment_details = {} + if payment_method.respond_to?(:number) + payment_details[:payment_type] = :credit + else + token_details = token_details_from_authorization(payment_method) + payment_details.merge!(token_details) + if token_details.has_key?(:token_id) + payment_details[:payment_type] = :token + else + payment_details[:payment_type] = :pay_as_order + end + end + + payment_details + end + + def credit_fund_transfer_attribute(options) + return unless options[:credit] + {'action' => 'REFUND'} + end + def encoded_credentials credentials = "#{@options[:login]}:#{@options[:password]}" "Basic #{[credentials].pack('m').strip}" end - def localized_amount(money, currency) - amount = amount(money) - return amount unless CURRENCIES_WITHOUT_FRACTIONS.include?(currency.to_s) + def currency_exponent(currency) + return 0 if non_fractional_currency?(currency) + return 3 if three_decimal_currency?(currency) + return 2 + end - amount.to_i / 100 * 100 + def card_code_for(payment_method) + CARD_CODES[card_brand(payment_method)] || CARD_CODES['unknown'] end end end diff --git a/lib/active_merchant/billing/gateways/worldpay_online_payments.rb b/lib/active_merchant/billing/gateways/worldpay_online_payments.rb new file mode 100644 index 00000000000..d3a03ffbeec --- /dev/null +++ b/lib/active_merchant/billing/gateways/worldpay_online_payments.rb @@ -0,0 +1,215 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class WorldpayOnlinePaymentsGateway < Gateway + self.live_url = 'https://api.worldpay.com/v1/' + + self.default_currency = 'GBP' + + self.money_format = :cents + + self.supported_countries = %w(HK US GB BE CH CZ DE DK ES FI FR GR HU IE IT LU MT NL NO PL PT SE SG TR) + self.supported_cardtypes = [:visa, :master, :american_express, :discover, :jcb, :maestro] + + self.homepage_url = 'http://online.worldpay.com' + self.display_name = 'Worldpay Online Payments' + + def initialize(options={}) + requires!(options, :client_key, :service_key) + @client_key = options[:client_key] + @service_key = options[:service_key] + super + end + + def authorize(money, credit_card, options={}) + response = create_token(true, credit_card.first_name+' '+credit_card.last_name, credit_card.month, credit_card.year, credit_card.number, credit_card.verification_value) + if response.success? + options[:authorizeOnly] = true + post = create_post_for_auth_or_purchase(response.authorization, money, options) + response = commit(:post, 'orders', post, {}, 'authorize') + end + response + end + + def capture(money, authorization, options={}) + if authorization + commit(:post, "orders/#{CGI.escape(authorization)}/capture", {'captureAmount'=>money}, options, 'capture') + else + Response.new(false, + 'FAILED', + 'FAILED', + :test => test?, + :authorization => false, + :avs_result => {}, + :cvv_result => {}, + :error_code => false + ) + end + end + + def purchase(money, credit_card, options={}) + response = create_token(true, credit_card.first_name+' '+credit_card.last_name, credit_card.month, credit_card.year, credit_card.number, credit_card.verification_value) + if response.success? + post = create_post_for_auth_or_purchase(response.authorization, money, options) + response = commit(:post, 'orders', post, options, 'purchase') + end + response + end + + def refund(money, orderCode, options={}) + obj = money ? {'refundAmount' => money} : {} + commit(:post, "orders/#{CGI.escape(orderCode)}/refund", obj, options, 'refund') + end + + def void(orderCode, options={}) + response = commit(:delete, "orders/#{CGI.escape(orderCode)}", nil, options, 'void') + if !response.success? && (response.params && response.params['customCode'] != 'ORDER_NOT_FOUND') + response = refund(nil, orderCode) + end + response + end + + def verify(credit_card, options={}) + authorize(0, credit_card, options) + end + + private + + def create_token(reusable, name, exp_month, exp_year, number, cvc) + obj = { + 'reusable'=> reusable, + 'paymentMethod'=> { + 'type'=> 'Card', + 'name'=> name, + 'expiryMonth'=> exp_month, + 'expiryYear'=> exp_year, + 'cardNumber'=> number, + 'cvc'=> cvc + }, + 'clientKey'=> @client_key + } + token_response = commit(:post, 'tokens', obj, {'Authorization' => @service_key}, 'token') + token_response + end + + def create_post_for_auth_or_purchase(token, money, options) + { + 'token' => token, + 'orderDescription' => options[:description] || 'Worldpay Order', + 'amount' => money, + 'currencyCode' => options[:currency] || default_currency, + 'name' => options[:billing_address]&&options[:billing_address][:name] ? options[:billing_address][:name] : '', + 'billingAddress' => { + 'address1'=>options[:billing_address]&&options[:billing_address][:address1] ? options[:billing_address][:address1] : '', + 'address2'=>options[:billing_address]&&options[:billing_address][:address2] ? options[:billing_address][:address2] : '', + 'address3'=>'', + 'postalCode'=>options[:billing_address]&&options[:billing_address][:zip] ? options[:billing_address][:zip] : '', + 'city'=>options[:billing_address]&&options[:billing_address][:city] ? options[:billing_address][:city] : '', + 'state'=>options[:billing_address]&&options[:billing_address][:state] ? options[:billing_address][:state] : '', + 'countryCode'=>options[:billing_address]&&options[:billing_address][:country] ? options[:billing_address][:country] : '' + }, + 'customerOrderCode' => options[:order_id], + 'orderType' => 'ECOM', + 'authorizeOnly' => options[:authorizeOnly] ? true : false + } + end + + def parse(body) + body ? JSON.parse(body) : {} + end + + def headers(options = {}) + headers = { + 'Authorization' => @service_key, + 'Content-Type' => 'application/json', + 'User-Agent' => "Worldpay/v1 ActiveMerchantBindings/#{ActiveMerchant::VERSION}", + 'X-Worldpay-Client-User-Agent' => user_agent, + 'X-Worldpay-Client-User-Metadata' => {:ip => options[:ip]}.to_json + } + if options['Authorization'] + headers['Authorization'] = options['Authorization'] + end + headers + end + + def commit(method, url, parameters=nil, options = {}, type = false) + raw_response = response = nil + success = false + begin + json = parameters ? parameters.to_json : nil + + raw_response = ssl_request(method, self.live_url + url, json, headers(options)) + + if raw_response != '' + response = parse(raw_response) + if type == 'token' + success = response.key?('token') + else + if response.key?('httpStatusCode') + success = false + else + if type == 'authorize' && response['paymentStatus'] == 'AUTHORIZED' + success = true + elsif type == 'purchase' && response['paymentStatus'] == 'SUCCESS' + success = true + elsif type == 'capture' || type=='refund' || type=='void' + success = true + end + end + end + else + success = true + response = {} + end + rescue ResponseError => e + raw_response = e.response.body + response = response_error(raw_response) + rescue JSON::ParserError + response = json_error(raw_response) + end + + if response['orderCode'] + authorization = response['orderCode'] + elsif response['token'] + authorization = response['token'] + else + authorization = response['message'] + end + + Response.new(success, + success ? 'SUCCESS' : response['message'], + response, + :test => test?, + :authorization => authorization, + :avs_result => {}, + :cvv_result => {}, + :error_code => success ? nil : response['customCode'] + ) + end + + def test? + @service_key[0] == 'T' + end + + def response_error(raw_response) + parse(raw_response) + rescue JSON::ParserError + json_error(raw_response) + end + + def json_error(raw_response) + msg = 'Invalid response received from the Worldpay Online Payments API. Please contact techsupport.online@worldpay.com if you continue to receive this message.' + msg += " (The raw response returned by the API was #{raw_response.inspect})" + { + 'error' => { + 'message' => msg + } + } + end + + def handle_response(response) + response.body + end + + end + end +end diff --git a/lib/active_merchant/billing/gateways/worldpay_us.rb b/lib/active_merchant/billing/gateways/worldpay_us.rb new file mode 100644 index 00000000000..303b5b1767e --- /dev/null +++ b/lib/active_merchant/billing/gateways/worldpay_us.rb @@ -0,0 +1,221 @@ +require 'nokogiri' + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class WorldpayUsGateway < Gateway + class_attribute :backup_url + + self.display_name = 'Worldpay US' + self.homepage_url = 'http://www.worldpay.com/us' + + # No sandbox, just use test cards. + self.live_url = 'https://trans.worldpay.us/cgi-bin/process.cgi' + self.backup_url = 'https://trans.gwtx01.com/cgi-bin/process.cgi' + + self.supported_countries = ['US'] + self.default_currency = 'USD' + self.money_format = :dollars + self.supported_cardtypes = [:visa, :master, :american_express, :discover, :jcb] + + def initialize(options={}) + requires!(options, :acctid, :subid, :merchantpin) + super + end + + def purchase(money, payment_method, options={}) + post = {} + add_invoice(post, money, options) + add_payment_method(post, payment_method) + add_customer_data(post, options) + + commit('purchase', options, post) + end + + def authorize(money, payment, options={}) + post = {} + add_invoice(post, money, options) + add_credit_card(post, payment) + add_customer_data(post, options) + + commit('authorize', options, post) + end + + def capture(amount, authorization, options={}) + post = {} + add_invoice(post, amount, options) + add_reference(post, authorization) + add_customer_data(post, options) + + commit('capture', options, post) + end + + def refund(amount, authorization, options={}) + post = {} + add_invoice(post, amount, options) + add_reference(post, authorization) + add_customer_data(post, options) + + commit('refund', options, post) + end + + def void(authorization, options={}) + post = {} + add_reference(post, authorization) + + commit('void', options, post) + end + + def verify(credit_card, options={}) + MultiResponse.run(:use_first_response) do |r| + r.process { authorize(100, credit_card, options) } + r.process(:ignore_result) { void(r.authorization, options) } + end + end + + def supports_scrubbing? + true + end + + def scrub(transcript) + transcript. + gsub(%r((&?merchantpin=)[^&]*)i, '\1[FILTERED]'). + gsub(%r((&?ccnum=)[^&]*)i, '\1[FILTERED]'). + gsub(%r((&?ckacct=)[^&]*)i, '\1[FILTERED]'). + gsub(%r((&?cvv2=)[^&]*)i, '\1[FILTERED]') + end + + private + + def url(options) + options[:use_backup_url].to_s == 'true' ? self.backup_url : self.live_url + end + + def add_customer_data(post, options) + if(billing_address = (options[:billing_address] || options[:address])) + post[:ci_companyname] = billing_address[:company] + post[:ci_billaddr1] = billing_address[:address1] + post[:ci_billaddr2] = billing_address[:address2] + post[:ci_billcity] = billing_address[:city] + post[:ci_billstate] = billing_address[:state] + post[:ci_billzip] = billing_address[:zip] + post[:ci_billcountry] = billing_address[:country] + + post[:ci_phone] = billing_address[:phone] + post[:ci_email] = billing_address[:email] + post[:ci_ipaddress] = billing_address[:ip] + end + + if(shipping_address = options[:shipping_address]) + post[:ci_shipaddr1] = shipping_address[:address1] + post[:ci_shipaddr2] = shipping_address[:address2] + post[:ci_shipcity] = shipping_address[:city] + post[:ci_shipstate] = shipping_address[:state] + post[:ci_shipzip] = shipping_address[:zip] + post[:ci_shipcountry] = shipping_address[:country] + end + end + + def add_invoice(post, money, options) + post[:amount] = amount(money) + post[:currencycode] = (options[:currency] || currency(money)) + post[:merchantordernumber] = options[:order_id] if options[:order_id] + end + + def add_payment_method(post, payment_method) + if card_brand(payment_method) == 'check' + add_check(post, payment_method) + else + add_credit_card(post, payment_method) + end + end + + def add_credit_card(post, payment_method) + post[:ccname] = payment_method.name + post[:ccnum] = payment_method.number + post[:cvv2] = payment_method.verification_value + post[:expyear] = format(payment_method.year, :four_digits) + post[:expmon] = format(payment_method.month, :two_digits) + end + + ACCOUNT_TYPES = { + 'checking' => '1', + 'savings' => '2', + } + + def add_check(post, payment_method) + post[:action] = 'ns_quicksale_check' + post[:ckacct] = payment_method.account_number + post[:ckaba] = payment_method.routing_number + post[:ckno] = payment_method.number + post[:ckaccttype] = ACCOUNT_TYPES[payment_method.account_type] if ACCOUNT_TYPES[payment_method.account_type] + end + + def split_authorization(authorization) + historyid, orderid = authorization.split('|') + [historyid, orderid] + end + + def add_reference(post, authorization) + historyid, orderid = split_authorization(authorization) + post[:postonly] = historyid + post[:historykeyid] = historyid + post[:orderkeyid] = orderid + end + + def parse(xml) + response = {} + doc = Nokogiri::XML(xml) + message = doc.xpath('//plaintext') + message.text.split(/\r?\n/).each do |line| + key, value = line.split(%r{=}) + response[key] = value if key + end + response + end + + ACTIONS = { + 'purchase' => 'ns_quicksale_cc', + 'refund' => 'ns_credit', + 'authorize' => 'ns_quicksale_cc', + 'capture' => 'ns_quicksale_cc', + 'void' => 'ns_void', + } + + def commit(action, options, post) + post[:action] = ACTIONS[action] unless post[:action] + post[:acctid] = @options[:acctid] + post[:subid] = @options[:subid] + post[:merchantpin] = @options[:merchantpin] + + post[:authonly] = '1' if action == 'authorize' + + raw = parse(ssl_post(url(options), post.to_query)) + + succeeded = success_from(raw['result']) + Response.new( + succeeded, + message_from(succeeded, raw), + raw, + :authorization => authorization_from(raw), + :test => test? + ) + end + + def success_from(result) + result == '1' + end + + def message_from(succeeded, response) + if succeeded + 'Succeeded' + else + (response['transresult'] || response['Reason'] || 'Unable to read error message') + end + end + + def authorization_from(response) + [response['historyid'], response['orderid']].join('|') + end + end + end +end diff --git a/lib/active_merchant/billing/integrations.rb b/lib/active_merchant/billing/integrations.rb deleted file mode 100644 index 27d8bd2201a..00000000000 --- a/lib/active_merchant/billing/integrations.rb +++ /dev/null @@ -1,17 +0,0 @@ -module ActiveMerchant - module Billing - module Integrations - - Dir[File.dirname(__FILE__) + '/integrations/*.rb'].each do |f| - - # Get camelized class name - filename = File.basename(f, '.rb') - # Camelize the string to get the class name - gateway_class = filename.camelize.to_sym - - # Register for autoloading - autoload gateway_class, f - end - end - end -end diff --git a/lib/active_merchant/billing/integrations/a1agregator.rb b/lib/active_merchant/billing/integrations/a1agregator.rb deleted file mode 100644 index 9ac093aed24..00000000000 --- a/lib/active_merchant/billing/integrations/a1agregator.rb +++ /dev/null @@ -1,26 +0,0 @@ -require File.dirname(__FILE__) + '/a1agregator/helper.rb' -require File.dirname(__FILE__) + '/a1agregator/notification.rb' -require File.dirname(__FILE__) + '/a1agregator/status.rb' - -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - module A1agregator - - mattr_accessor :service_url - self.service_url = 'https://partner.a1agregator.ru/a1lite/input/' - - mattr_accessor :signature_parameter_name - self.signature_parameter_name = 'check' - - def self.notification(*args) - Notification.new(*args) - end - - def self.status(login, password) - Status.new(login, password) - end - end - end - end -end diff --git a/lib/active_merchant/billing/integrations/a1agregator/helper.rb b/lib/active_merchant/billing/integrations/a1agregator/helper.rb deleted file mode 100644 index cddf1f853fa..00000000000 --- a/lib/active_merchant/billing/integrations/a1agregator/helper.rb +++ /dev/null @@ -1,31 +0,0 @@ -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - module A1agregator - class Helper < ActiveMerchant::Billing::Integrations::Helper - - # public key - mapping :account, 'key' - - mapping :amount, 'cost' - - mapping :order, 'order_id' - - mapping :customer, :email => 'email', - :phone => 'phone_number' - - # payment description - mapping :credential2, 'name' - - mapping :credential3, 'comment' - - # on error - # 1 - raise error - # 0 - redirect - mapping :credential4, 'verbose' - - end - end - end - end -end diff --git a/lib/active_merchant/billing/integrations/a1agregator/notification.rb b/lib/active_merchant/billing/integrations/a1agregator/notification.rb deleted file mode 100644 index d714f3d489a..00000000000 --- a/lib/active_merchant/billing/integrations/a1agregator/notification.rb +++ /dev/null @@ -1,186 +0,0 @@ -require 'net/http' - -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - module A1agregator - class Notification < ActiveMerchant::Billing::Integrations::Notification - - self.production_ips = [ - '78.108.178.206', - '79.137.235.129', - '95.163.96.79', - '212.24.38.100' - ] - - def initialize(*args) - super - guess_notification_type - end - - # Simple notification request params: - # tid - # name - # comment - # partner_id - # service_id - # order_id - # type - # partner_income - # system_income - - def complete? - true - end - - def transaction_id - params['tid'] - end - - def title - params['name'] - end - - def comment - params['comment'] - end - - def partner_id - params['partner_id'] - end - - def service_id - params['service_id'] - end - - def item_id - params['order_id'] - end - - def type - params['type'] - end - - def partner_income - params['partner_income'] - end - - def system_income - params['system_income'] - end - - # Additional notification request params: - # tid - # name - # comment - # partner_id - # service_id - # order_id - # type - # cost - # income_total - # income - # partner_income - # system_income - # command - # phone_number - # email - # resultStr - # date_created - # version - # check - - def inclome_total - params['income_total'] - end - - def income - params['income'] - end - - def partner_income - params['partner_income'] - end - - def system_income - params['system_income'] - end - - def command - params['command'] - end - - def phone_number - params['phone_number'] - end - - def payer_email - params['email'] - end - - def result_string - params['resultStr'] - end - - def received_at - params['date_created'] - end - - def version - params['version'] - end - - def security_key - params[A1agregator.signature_parameter_name].to_s.downcase - end - - # the money amount we received in X.2 decimal. - alias_method :gross, :system_income - - def currency - 'RUB' - end - - # Was this a test transaction? - def test? - params['test'] == '1' - end - - def simple_notification? - @notification_type == :simple - end - - def additional_notification? - @notification_type == :additional - end - - def acknowledge - security_key == signature - end - - private - - def signature - data = "#{params['tid']}\ -#{params['name']}\ -#{params['comment']}\ -#{params['partner_id']}\ -#{params['service_id']}\ -#{params['order_id']}\ -#{params['type']}\ -#{params['partner_income']}\ -#{params['system_income']}\ -#{params['test']}\ -#{@options[:secret]}" - Digest::MD5.hexdigest(data) - end - - def guess_notification_type - @notification_type = params['version'] ? :additional : :simple - end - - end - end - end - end -end diff --git a/lib/active_merchant/billing/integrations/a1agregator/status.rb b/lib/active_merchant/billing/integrations/a1agregator/status.rb deleted file mode 100644 index 264db72da29..00000000000 --- a/lib/active_merchant/billing/integrations/a1agregator/status.rb +++ /dev/null @@ -1,38 +0,0 @@ -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - module A1agregator - - class Status - include PostsData - - STATUS_TEST_URL = 'https://partner.a1pay.ru/a1lite/info/' - - attr_accessor :login, :password - - def initialize(login, password) - @login, @password = login, password - end - - # agregator provides two methods: - # by tid - transaction id - # by order_id & service_id - def update(options = {}) - data = PostData.new - data[:user] = @login - data[:pass] = @password - if options[:tid] - data[:tid] = options[:tid] - else - data[:ord_id] = options[:ord_id] - data[:service_id] = options[:service_id] - end - - ssl_post(STATUS_TEST_URL, data.to_post_data) - end - - end - end - end - end -end diff --git a/lib/active_merchant/billing/integrations/action_view_helper.rb b/lib/active_merchant/billing/integrations/action_view_helper.rb deleted file mode 100644 index 7fed361e757..00000000000 --- a/lib/active_merchant/billing/integrations/action_view_helper.rb +++ /dev/null @@ -1,73 +0,0 @@ -require 'action_pack' - -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - module ActionViewHelper - # This helper allows the usage of different payment integrations - # through a single form helper. Payment integrations are the - # type of service where the user is redirected to the secure - # site of the service, like Paypal or Chronopay. - # - # The helper creates a scope around a payment service helper - # which provides the specific mapping for that service. - # - # <% payment_service_for 1000, 'paypalemail@mystore.com', - # :amount => 50.00, - # :currency => 'CAD', - # :service => :paypal, - # :html => { :id => 'payment-form' } do |service| %> - # - # <% service.customer :first_name => 'Cody', - # :last_name => 'Fauser', - # :phone => '(555)555-5555', - # :email => 'cody@example.com' %> - # - # <% service.billing_address :city => 'Ottawa', - # :address1 => '21 Snowy Brook Lane', - # :address2 => 'Apt. 36', - # :state => 'ON', - # :country => 'CA', - # :zip => 'K1J1E5' %> - # - # <% service.invoice '#1000' %> - # <% service.shipping '0.00' %> - # <% service.tax '0.00' %> - # - # <% service.notify_url url_for(:only_path => false, :action => 'notify') %> - # <% service.return_url url_for(:only_path => false, :action => 'done') %> - # <% service.cancel_return_url 'http://mystore.com' %> - # <% end %> - # - def payment_service_for(order, account, options = {}, &proc) - raise ArgumentError, "Missing block" unless block_given? - - integration_module = ActiveMerchant::Billing::Integrations.const_get(options.delete(:service).to_s.camelize) - service_class = integration_module.const_get('Helper') - - form_options = options.delete(:html) || {} - service = service_class.new(order, account, options) - form_options[:method] = service.form_method - result = [] - result << form_tag(integration_module.service_url, form_options) - - result << capture(service, &proc) - - service.form_fields.each do |field, value| - result << hidden_field_tag(field, value) - end - - service.raw_html_fields.each do |field, value| - result << "<input id=\"#{field}\" name=\"#{field}\" type=\"hidden\" value=\"#{value}\" />\n" - end - - result << '</form>' - result= result.join("\n") - - concat(result.respond_to?(:html_safe) ? result.html_safe : result) - nil - end - end - end - end -end diff --git a/lib/active_merchant/billing/integrations/authorize_net_sim.rb b/lib/active_merchant/billing/integrations/authorize_net_sim.rb deleted file mode 100644 index e4a56576ef2..00000000000 --- a/lib/active_merchant/billing/integrations/authorize_net_sim.rb +++ /dev/null @@ -1,38 +0,0 @@ -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - module AuthorizeNetSim - autoload :Helper, 'active_merchant/billing/integrations/authorize_net_sim/helper.rb' - autoload :Notification, 'active_merchant/billing/integrations/authorize_net_sim/notification.rb' - - # Overwrite this if you want to change the ANS test url - mattr_accessor :test_url - self.test_url = 'https://test.authorize.net/gateway/transact.dll' - - # Overwrite this if you want to change the ANS production url - mattr_accessor :production_url - self.production_url = 'https://secure.authorize.net/gateway/transact.dll' - - def self.service_url - mode = ActiveMerchant::Billing::Base.integration_mode - case mode - when :production - self.production_url - when :test - self.test_url - else - raise StandardError, "Integration mode set to an invalid value: #{mode}" - end - end - - def self.notification(post) - Notification.new(post) - end - - def self.return(query_string) - Return.new(query_string) - end - end - end - end -end diff --git a/lib/active_merchant/billing/integrations/authorize_net_sim/helper.rb b/lib/active_merchant/billing/integrations/authorize_net_sim/helper.rb deleted file mode 100644 index 0290de8c74a..00000000000 --- a/lib/active_merchant/billing/integrations/authorize_net_sim/helper.rb +++ /dev/null @@ -1,229 +0,0 @@ -require 'active_support/version' # for ActiveSupport2.3 -require 'active_support/core_ext/float/rounding.rb' unless ActiveSupport::VERSION::MAJOR > 3 # Float#round(precision) - -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - module AuthorizeNetSim - # An example. Note the username as a parameter and transaction key you - # will want to use later. The amount that you pass in will be *rounded*, - # so preferably pass in X.2 decimal so that no rounding occurs. It is - # rounded because if it looks like 00.000 Authorize.Net fails the - # transaction as incorrectly formatted. - # - # payment_service_for('order_id', 'authorize_net_account', :service => :authorize_net_sim, :amount => 157.0) do |service| - # - # # You must call setup_hash and invoice - # - # service.setup_hash :transaction_key => '8CP6zJ7uD875J6tY', - # :order_timestamp => 1206836763 - # service.customer_id 8 - # service.customer :first_name => 'g', - # :last_name => 'g', - # :email => 'g@g.com', - # :phone => '3' - # service.billing_address :zip => 'g', - # :country => 'United States of America', - # :address => 'g' - # - # service.ship_to_address :first_name => 'g', - # :last_name => 'g', - # :city => '', - # :address => 'g', - # :address2 => '', - # :state => address.state, - # :country => 'United States of America', - # :zip => 'g' - # - # service.invoice "516428355" # your invoice number - # # The end-user is presented with the HTML produced by the notify_url. - # service.notify_url "http://t/authorize_net_sim/payment_received_notification_sub_step" - # service.payment_header 'My store name' - # service.add_line_item :name => 'item name', :quantity => 1, :unit_price => 0 - # service.test_request 'true' # only if it's just a test - # service.shipping '25.0' - # # Tell it to display a "0" line item for shipping, with the price in - # # the name, otherwise it isn't shown at all, leaving the end user to - # # wonder why the total is different than the sum of the line items. - # service.add_shipping_as_line_item - # server.add_tax_as_line_item # same with tax - # # See the helper.rb file for various custom fields - # end - - class Helper < ActiveMerchant::Billing::Integrations::Helper - mapping :order, 'x_fp_sequence' - mapping :account, 'x_login' - - mapping :customer, :first_name => 'x_first_name', - :last_name => 'x_last_name', - :email => 'x_email', - :phone => 'x_phone' - - mapping :notify_url, 'x_relay_url' - mapping :return_url, '' # unused - mapping :cancel_return_url, '' # unused - - # Custom fields for Authorize.net SIM. - # See http://www.Authorize.Net/support/SIM_guide.pdf for more descriptions. - mapping :fax, 'x_fax' - mapping :customer_id, 'x_cust_id' - mapping :description, 'x_description' - mapping :tax, 'x_tax' - mapping :shipping, 'x_freight' - - # True or false, or 0 or 1 same effect [not required to send one, - # defaults to false]. - mapping :test_request, 'x_test_request' - - # This one is necessary for the notify url to be able to parse its - # information later! They also pass back customer id, if that's - # useful. - def invoice(number) - add_field 'x_invoice_num', number - end - - # Set the billing address. Call like service.billing_address {:city => - # 'provo, :state => 'UT'}... - def billing_address(options) - for setting in [:city, :state, :zip, :country, :po_num] do - add_field 'x_' + setting.to_s, options[setting] - end - raise 'must use address1 and address2' if options[:address] - add_field 'x_address', (options[:address1].to_s + ' ' + options[:address2].to_s).strip - end - - # Adds a custom field which you submit to Authorize.Net. These fields - # are all passed back to you verbatim when it does its relay - # (callback) to you note that if you call it twice with the same name, - # this function only uses keeps the second value you called it with. - def add_custom_field(name, value) - add_field name, value - end - - # Displays tax as a line item, so they can see it. Otherwise it isn't - # displayed. - def add_tax_as_line_item - raise unless @fields['x_tax'] - add_line_item :name => 'Total Tax', :quantity => 1, :unit_price => @fields['x_tax'], :tax => 0, :line_title => 'Tax' - end - - # Displays shipping as a line item, so they can see it. Otherwise it - # isn't displayed. - def add_shipping_as_line_item(extra_options = {}) - raise 'must set shipping/freight before calling this' unless @fields['x_freight'] - add_line_item extra_options.merge({:name => 'Shipping and Handling Cost', :quantity => 1, :unit_price => @fields['x_freight'], :line_title => 'Shipping'}) - end - - # Add ship_to_address in the same format as the normal address is - # added. - def ship_to_address(options) - for setting in [:first_name, :last_name, :company, :city, :state, :zip, :country] do - if options[setting] then - add_field 'x_ship_to_' + setting.to_s, options[setting] - end - end - raise 'must use :address1 and/or :address2' if options[:address] - add_field 'x_ship_to_address', (options[:address1].to_s + ' ' + options[:address2].to_s).strip - end - - # These control the look of the SIM payment page. Note that you can - # include a CSS header in descriptors, etc. - mapping :color_link, 'x_color_link' - mapping :color_text, 'x_color_text' - mapping :logo_url, 'x_logo_url' - mapping :background_url, 'x_background_url' # background image url for the page - mapping :payment_header, 'x_header_html_payment_form' - mapping :payment_footer, 'x_footer_html_payment_form' - - # For this to work you must have also passed in an email for the - # purchaser. - def yes_email_customer_from_authorizes_side - add_field 'x_email_customer', 'TRUE' - end - - # Add a line item to Authorize.Net. - # Call line add_line_item {:name => 'orange', :unit_price => 30, :tax_value => 'Y', :quantity => 3, } - # Note you can't pass in a negative unit price, and you can add an - # optional :line_title => 'special name' if you don't want it to say - # 'Item 1' or what not, the default coded here. - # Cannot have a negative price, nor a name with "'s or $ - # You can use the :line_title for the product name and then :name for description, if desired - def add_line_item(options) - raise 'needs name' unless options[:name] - - if @line_item_count == 30 - # Add a note that we are not showing at least one -- AN doesn't - # display more than 30 or so. - description_of_last = @raw_html_fields[-1][1] - # Pull off the second to last section, which is the description. - description_of_last =~ />([^>]*)<\|>[YN]$/ - # Create a new description, which can't be too big, so truncate here. - @raw_html_fields[-1][1] = description_of_last.gsub($1, $1[0..200] + ' + more unshown items after this one.') - end - - name = options[:name] - quantity = options[:quantity] || 1 - line_title = options[:line_title] || ('Item ' + (@line_item_count + 1).to_s) # left most field - unit_price = options[:unit_price] || 0 - unit_price = unit_price.to_f.round(2) - tax_value = options[:tax_value] || 'N' - - # Sanitization, in case they include a reserved word here, following - # their guidelines; unfortunately, they require 'raw' fields here, - # not CGI escaped, using their own delimiters. - # - # Authorize.net ignores the second field (sanitized_short_name) - raise 'illegal char for line item <|>' if name.include? '<|>' - raise 'illegal char for line item "' if name.include? '"' - raise 'cannot pass in dollar sign' if unit_price.to_s.include? '$' - raise 'must have positive or 0 unit price' if unit_price.to_f < 0 - # Using CGI::escape causes the output to be formated incorrectly in - # the HTML presented to the end-user's browser (e.g., spaces turn - # into +'s). - sanitized_short_name = name[0..30] - name = name[0..255] - - add_raw_html_field "x_line_item", "#{line_title}<|>#{sanitized_short_name}<|>#{name}<|>#{quantity}<|>#{unit_price}<|>#{tax_value}" - - @line_item_count += 1 - end - - # If you call this it will e-mail to this address a copy of a receipt - # after successful, from Authorize.Net. - def email_merchant_from_authorizes_side(to_this_email) - add_field 'x_email_merchant', to_this_email - end - - # You MUST call this at some point for it to actually work. Options - # must include :transaction_key and :order_timestamp - def setup_hash(options) - raise unless options[:transaction_key] - raise unless options[:order_timestamp] - amount = @fields['x_amount'] - data = "#{@fields['x_login']}^#{@fields['x_fp_sequence']}^#{options[:order_timestamp].to_i}^#{amount}^#{@fields['x_currency_code']}" - hmac = OpenSSL::HMAC.hexdigest(OpenSSL::Digest::Digest.new('md5'), options[:transaction_key], data) - add_field 'x_fp_hash', hmac - add_field 'x_fp_timestamp', options[:order_timestamp].to_i - end - - # Note that you should call #invoice and #setup_hash as well, for the - # response_url to actually work. - def initialize(order, account, options = {}) - super - raise 'missing parameter' unless order and account and options[:amount] - raise 'error -- amount with no digits!' unless options[:amount].to_s =~ /\d/ - add_field('x_type', 'AUTH_CAPTURE') # the only one we deal with, for now. Not refunds or anything else, currently. - add_field 'x_show_form', 'PAYMENT_FORM' - add_field 'x_relay_response', 'TRUE' - add_field 'x_duplicate_window', '28800' # large default duplicate window. - add_field 'x_currency_code', currency_code - add_field 'x_version' , '3.1' # version from doc - add_field 'x_amount', options[:amount].to_f.round(2) - @line_item_count = 0 - end - - end - end - end - end -end diff --git a/lib/active_merchant/billing/integrations/authorize_net_sim/notification.rb b/lib/active_merchant/billing/integrations/authorize_net_sim/notification.rb deleted file mode 100644 index d2e36ab5cb5..00000000000 --- a/lib/active_merchant/billing/integrations/authorize_net_sim/notification.rb +++ /dev/null @@ -1,340 +0,0 @@ -require 'net/http' -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - - # # Example: - # parser = AuthorizeNetSim::Notification.new(request.raw_post) - # passed = parser.complete? - # - # order = Order.find_by_order_number(parser.invoice_num) - # - # unless order - # @message = 'Error--unable to find your transaction! Please contact us directly.' - # return render :partial => 'authorize_net_sim_payment_response' - # end - # - # if order.total != parser.gross.to_f - # logger.error "Authorize.Net sim said they paid for #{parser.gross} and it should have been #{order.total}!" - # passed = false - # end - # - # # Theoretically, Authorize.net will *never* pass us the same transaction - # # ID twice, but we can double check that... by using - # # parser.transaction_id, and checking against previous orders' transaction - # # id's (which you can save when the order is completed).... - # unless parser.acknowledge MD5_HASH_SET_IN_AUTHORIZE_NET, AUTHORIZE_LOGIN - # passed = false - # logger.error "ALERT POSSIBLE FRAUD ATTEMPT either that or you haven't setup your md5 hash setting right in #{__FILE__} - # because a transaction came back from Authorize.Net with the wrong hash value--rejecting!" - # end - # - # unless parser.cavv_matches? and parser.avs_code_matches? - # logger.error 'Warning--non matching CC!' + params.inspect - # # Could fail them here, as well (recommended)... - # end - # - # if passed - # # Set up your session, and render something that will redirect them to - # # your site, most likely. - # else - # # Render failure or redirect them to your site where you will render failure - # end - - module AuthorizeNetSim - class Notification < ActiveMerchant::Billing::Integrations::Notification - - def unescape(val) #:nodoc: - if val - CGI::unescape val - else - val - end - end - - # Passes a hash of the address the user entered in at Authorize.Net - def billing_address - all = {} - [:fax, :city, :company, :last_name, :country, :zip, :first_name, :address, :email, :state].each do |key_out| - all[key_out] = unescape params['x_' + key_out.to_s] - end - all - end - - def customer_id - unescape params['x_cust_id'] - end - - def auth_code - unescape params['x_auth_code'] - end - - def po_num - unescape params['x_po_num'] - end - - def ship_to_address - all = {} - [:city, :last_name, :first_name, :country, :zip, :address].each do |key_out| - all[key_out] = unescape params['x_ship_to_' + key_out.to_s] - end - all - end - - # Tax amount we sent them. - def tax - unescape params['x_tax'] - end - - # Transaction type (probably going to be auth_capture, since that's - # all we set it as). - def transaction_type - unescape params['x_type'] - end - - # Payment method used--almost always CC (for credit card). - def method - unescape params['x_method'] - end - - # Ff our payment method is available. Almost always "true". - def method_available - params['x_method_available'] - end - - # Invoice num we passed in as invoice_num to them. - def invoice_num - item_id - end - - # If you pass any values to authorize that aren't its expected, it - # will pass them back to you verbatim, returned by this method. - # custom values: - def all_custom_values_passed_in_and_now_passed_back_to_us - all = {} - params.each do |key, value| - if key[0..1] != 'x_' - all[key] = unescape value - end - end - all - end - - def duty - unescape params['x_duty'] - end - - # Shipping we sent them. - def freight - unescape params['x_freight'] - end - alias_method :shipping, :freight - - def description - unescape params['x_description'] - end - - # Returns the response code as a symbol. - # {'1' => :approved, '2' => :declined, '3' => :error, '4' => :held_for_review} - def response_code_as_ruby_symbol - map = {'1' => :approved, '2' => :declined, '3' => :error, '4' => :held_for_review} - map[params['x_response_code']] - end - - def response_reason_text - unescape params['x_response_reason_text'] - end - - # The response reason text's numeric id [equivalent--just a number] - def response_reason_code - unescape params['x_response_reason_code'] - end - - # 'used internally by their gateway' - def response_subcode - params['x_response_subcode'] - end - - # They pass back a tax_exempt value. - def tax_exempt - params['x_tax_exempt'] - end - - # avs [address verification] code - # A = Address (Street) - # matches, ZIP does not - # B = Address information - # not provided for AVS - # check - # E = AVS error - # G = Non-U.S. Card Issuing - # Bank - # N = No Match on Address - # (Street) or ZIP - # P = AVS not applicable for - # this transaction - # R = Retry – System - # unavailable or timed out - # S = Service not supported - # by issuer - # U = Address information is - # unavailable - # W = Nine digit ZIP - # matches, Address (Street) - # does not - # X = Address (Street) and - # nine digit ZIP match - # Y = Address (Street) and - # five digit ZIP match - # Z = Five digit ZIP matches - # Address (Street) does not - def avs_code - params['x_avs_code'] - end - - # Returns true if their address completely matched [Y or X, P from - # #avs_code, which mean 'add+zip match', 'address + 9-zip match', and - # not applicable, respectively]. - def avs_code_matches? - return ['Y', 'X', 'P'].include? params['x_avs_code'] - end - - # cvv2 response - # M = Match - # N = No Match - # P = Not Processed - # S = Should have been - # present - # U = Issuer unable to - # process request - def cvv2_resp_code - params['x_cvv2_resp_code'] - end - - # check if #cvv2_resp_code == 'm' for Match. otherwise false - def cvv2_resp_code_matches? - return ['M'].include? cvv2_resp_code - end - - # cavv_response--'cardholder authentication verification response code'--most likely not use for SIM - # Blank or not present = - # CAVV not validated - # 0 = CAVV not validated - # because erroneous data - # was submitted - # 1 = CAVV failed validation - # 2 = CAVV passed - # validation - # 3 = CAVV validation could - # not be performed; issuer - # attempt incomplete - # 4 = CAVV validation could - # not be performed; issuer - # system error - # 5 = Reserved for future - # use - # 6 = Reserved for future - # use - # 7 = CAVV attempt – failed - # validation – issuer - # available (U.S.-issued - # card/non-U.S acquirer) - # 8 = CAVV attempt – - # passed validation – issuer - # available (U.S.-issued - # card/non-U.S. acquirer) - # 9 = CAVV attempt – failed - # validation – issuer - def cavv_response - params['x_cavv_response'] - end - - # Check if #cavv_response == '', '2', '8' one of those [non failing] - # [blank means no validated, 2 is passed, 8 is passed issuer - # available] - def cavv_matches? - ['','2','8'].include? cavv_response - end - - # Payment is complete -- returns true if x_response_code == '1' - def complete? - params["x_response_code"] == '1' - end - - # Alias for invoice number--this is the only id they pass back to us - # that we passed to them, except customer id is also passed back. - def item_id - unescape params['x_invoice_num'] - end - - # They return this number to us [it's unique to Authorize.net]. - def transaction_id - params['x_trans_id'] - end - - # When was this payment was received by the client. --unimplemented -- - # always returns nil - def received_at - nil - end - - # End-user's email - def payer_email - unescape params['x_email'] - end - - # They don't pass merchant email back to us -- unimplemented -- always - # returns nil - def receiver_email - nil - end - - # md5 hash used internally - def security_key - params['x_MD5_Hash'] - end - - # The money amount we received in X.2 decimal. Returns a string - def gross - unescape params['x_amount'] - end - - # Was this a test transaction? - def test? - params['x_test_request'] == 'true' - end - - # #method_available alias - def status - complete? - end - - # Called to request back and check if it was a valid request. - # Authorize.net passes us back a hash that includes a hash of our - # 'unique' MD5 value that we set within their system. - # - # Example: - # acknowledge('my secret md5 hash that I set within Authorize.Net', 'authorize_login') - # - # Note this is somewhat unsafe unless you actually set that md5 hash - # to something (defaults to '' in their system). - def acknowledge(md5_hash_set_in_authorize_net, authorize_net_login_name) - Digest::MD5.hexdigest(md5_hash_set_in_authorize_net + authorize_net_login_name + params['x_trans_id'] + gross) == params['x_MD5_Hash'].downcase - end - - private - - # Take the posted data and move the relevant data into a hash. - def parse(post) - @raw = post - post.split('&').each do |line| - key, value = *line.scan( %r{^(\w+)\=(.*)$} ).flatten - params[key] = value - end - end - - end - end - end - end -end diff --git a/lib/active_merchant/billing/integrations/bogus.rb b/lib/active_merchant/billing/integrations/bogus.rb deleted file mode 100644 index 6de2b0cc889..00000000000 --- a/lib/active_merchant/billing/integrations/bogus.rb +++ /dev/null @@ -1,23 +0,0 @@ - -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - module Bogus - autoload :Return, 'active_merchant/billing/integrations/bogus/return.rb' - autoload :Helper, 'active_merchant/billing/integrations/bogus/helper.rb' - autoload :Notification, 'active_merchant/billing/integrations/bogus/notification.rb' - - mattr_accessor :service_url - self.service_url = 'http://www.bogus.com' - - def self.notification(post, options = {}) - Notification.new(post) - end - - def self.return(query_string, options = {}) - Return.new(query_string) - end - end - end - end -end diff --git a/lib/active_merchant/billing/integrations/bogus/helper.rb b/lib/active_merchant/billing/integrations/bogus/helper.rb deleted file mode 100644 index ffb5e214228..00000000000 --- a/lib/active_merchant/billing/integrations/bogus/helper.rb +++ /dev/null @@ -1,17 +0,0 @@ -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - module Bogus - class Helper < ActiveMerchant::Billing::Integrations::Helper - mapping :account, 'account' - mapping :order, 'order' - mapping :amount, 'amount' - mapping :currency, 'currency' - mapping :customer, :first_name => 'first_name', - :last_name => 'last_name' - - end - end - end - end -end diff --git a/lib/active_merchant/billing/integrations/bogus/notification.rb b/lib/active_merchant/billing/integrations/bogus/notification.rb deleted file mode 100644 index 77630ffad84..00000000000 --- a/lib/active_merchant/billing/integrations/bogus/notification.rb +++ /dev/null @@ -1,11 +0,0 @@ -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - module Bogus - class Notification < ActiveMerchant::Billing::Integrations::Notification - - end - end - end - end -end diff --git a/lib/active_merchant/billing/integrations/bogus/return.rb b/lib/active_merchant/billing/integrations/bogus/return.rb deleted file mode 100644 index d41ef806104..00000000000 --- a/lib/active_merchant/billing/integrations/bogus/return.rb +++ /dev/null @@ -1,10 +0,0 @@ -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - module Bogus - class Return < ActiveMerchant::Billing::Integrations::Return - end - end - end - end -end diff --git a/lib/active_merchant/billing/integrations/chronopay.rb b/lib/active_merchant/billing/integrations/chronopay.rb deleted file mode 100644 index 6137772ac71..00000000000 --- a/lib/active_merchant/billing/integrations/chronopay.rb +++ /dev/null @@ -1,23 +0,0 @@ - -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - module Chronopay - autoload :Return, 'active_merchant/billing/integrations/chronopay/return.rb' - autoload :Helper, 'active_merchant/billing/integrations/chronopay/helper.rb' - autoload :Notification, 'active_merchant/billing/integrations/chronopay/notification.rb' - - mattr_accessor :service_url - self.service_url = 'https://secure.chronopay.com/index_shop.cgi' - - def self.notification(post, options = {}) - Notification.new(post) - end - - def self.return(query_string, options = {}) - Return.new(query_string) - end - end - end - end -end diff --git a/lib/active_merchant/billing/integrations/chronopay/helper.rb b/lib/active_merchant/billing/integrations/chronopay/helper.rb deleted file mode 100644 index 4f62465cbf8..00000000000 --- a/lib/active_merchant/billing/integrations/chronopay/helper.rb +++ /dev/null @@ -1,120 +0,0 @@ -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - module Chronopay - class Helper < ActiveMerchant::Billing::Integrations::Helper - # All currently supported checkout languages: - # es (Spanish) - # en (English) - # de (German) - # pt (Portuguese) - # lv (Latvian) - # cn1 (Chinese Version 1) - # cn2 (Chinese version 2) - # nl (Dutch) - # ru (Russian) - COUNTRIES_FOR_LANG = { - 'ES' => %w( AR BO CL CO CR CU DO EC SV GQ GT HN MX NI PA PY PE ES UY VE), - 'DE' => %w( DE AT CH LI ), - 'PT' => %w( AO BR CV GW MZ PT ST TL), - 'RU' => %w( BY KG KZ RU ), - 'LV' => %w( LV ), - 'CN1' => %w( CN ), - 'NL' => %w( NL ) - } - - LANG_FOR_COUNTRY = COUNTRIES_FOR_LANG.inject(Hash.new("EN")) do |memo, (lang, countries)| - countries.each do |code| - memo[code] = lang - end - memo - end - - - self.country_format = :alpha3 - - def initialize(order, account, options = {}) - super - add_field('cb_type', 'p') - end - - # product_id - mapping :account, 'product_id' - # product_name - mapping :invoice, 'product_name' - # product_price - mapping :amount, 'product_price' - # product_price_currency - mapping :currency, 'product_price_currency' - - # f_name - # s_name - # email - mapping :customer, :first_name => 'f_name', - :last_name => 's_name', - :phone => 'phone', - :email => 'email' - - # city - # street - # state - # zip - # country - The country must be a 3 digit country code - # phone - - mapping :billing_address, :city => 'city', - :address1 => 'street', - :state => 'state', - :zip => 'zip', - :country => 'country' - - def billing_address(mapping = {}) - # Gets the country code in the appropriate format or returns what we were given - # The appropriate format for Chronopay is the alpha 3 country code - country_code = lookup_country_code(mapping.delete(:country)) - add_field(mappings[:billing_address][:country], country_code) - - countries_with_supported_states = ['USA', 'CAN'] - if !countries_with_supported_states.include?(country_code) - mapping.delete(:state) - add_field(mappings[:billing_address][:state], 'XX') - end - mapping.each do |k, v| - field = mappings[:billing_address][k] - add_field(field, v) unless field.nil? - end - add_field('language', checkout_language_from_country(country_code)) - end - - # card_no - # exp_month - # exp_year - mapping :credit_card, :number => 'card_no', - :expiry_month => 'exp_month', - :expiry_year => 'exp_year' - - # cb_url - mapping :notify_url, 'cb_url' - - # cs1 - mapping :order, 'cs1' - - # cs2 - # cs3 - # decline_url - - - private - - def checkout_language_from_country(country_code) - country = Country.find(country_code) - short_code = country.code(:alpha2).to_s - LANG_FOR_COUNTRY[short_code] - rescue InvalidCountryCodeError - 'EN' - end - end - end - end - end -end diff --git a/lib/active_merchant/billing/integrations/chronopay/notification.rb b/lib/active_merchant/billing/integrations/chronopay/notification.rb deleted file mode 100644 index 4942c50a183..00000000000 --- a/lib/active_merchant/billing/integrations/chronopay/notification.rb +++ /dev/null @@ -1,158 +0,0 @@ -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - module Chronopay - class Notification < ActiveMerchant::Billing::Integrations::Notification - def complete? - status == 'Completed' - end - - # Status of transaction. List of possible values: - # <tt>onetime – one time payment has been made, no repayment required;</tt>:: - # <tt>initial – first payment has been made, repayment required in corresponding period;</tt>:: - # <tt>decline – charge request has been rejected;</tt>:: - # <tt>rebill – repayment has been made together with initial transaction;</ttt>:: - # <tt>cancel – repayments has been disabled;</tt>:: - # <tt>expire – customer’s access to restricted zone membership has been expired;</tt>:: - # <tt>refund – request to refund has been received;</tt>:: - # <tt>chargeback – request to chargeback has been received.</tt>:: - # - # This implementation of Chronopay does not support subscriptions. - # The status codes used are matched to the status codes that Paypal - # sends. See Paypal::Notification#status for more details - def status - case params['transaction_type'] - when 'onetime' - 'Completed' - when 'refund' - 'Refunded' - when 'chargeback' - 'Reversed' - else - 'Failed' - end - end - - # Unique ID of transaction - def transaction_id - params['transaction_id'] - end - - # Unique ID of customer - def customer_id - params['customer_id'] - end - - # Unique ID of Merchant’s web-site - def site_id - params['site_id'] - end - - # ID of a product that was purchased - def product_id - params['product_id'] - end - - # Language - def language - params['language'] - end - - def received_at - # Date should be formatted "dd-mm-yy" to be parsed by 1.8 and 1.9 the same way - formatted_date = Date.strptime(date, "%m/%d/%Y").strftime("%d-%m-%Y") - Time.parse("#{formatted_date} #{time}") unless date.blank? || time.blank? - end - - # Date of transaction in MM/DD/YYYY format - def date - params['date'] - end - - # Time of transaction in HH:MM:SS format - def time - params['time'] - end - - # The customer's full name - def name - params['name'] - end - - # The customer's email address - def email - params['email'] - end - - # The customer's street address - def street - params['street'] - end - - # The customer's country - 3 digit country code - def country - params['country'] - end - - # The customer's city - def city - params['city'] - end - - # The customer's zip - def zip - params['zip'] - end - - # The customer's state. Only useful for US Customers - def state - params['state'] - end - - # Customer’s login for restricted access zone of Merchant’s Web-site - def username - params['username'] - end - - # Customer's password for restricted access zone of Merchant’s Web-site, as chosen - def password - params['password'] - end - - # The item id passed in the first custom parameter - def item_id - params['cs1'] - end - - # Additional parameter - def custom2 - params['cs2'] - end - - # Additional parameter - def custom3 - params['cs3'] - end - - # The currency the purchase was made in - def currency - params['currency'] - end - - # the money amount we received in X.2 decimal. - def gross - params['total'] - end - - def test? - date.blank? && time.blank? && transaction_id.blank? - end - - def acknowledge - true - end - end - end - end - end -end diff --git a/lib/active_merchant/billing/integrations/chronopay/return.rb b/lib/active_merchant/billing/integrations/chronopay/return.rb deleted file mode 100644 index 399b258b3b8..00000000000 --- a/lib/active_merchant/billing/integrations/chronopay/return.rb +++ /dev/null @@ -1,10 +0,0 @@ -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - module Chronopay - class Return < ActiveMerchant::Billing::Integrations::Return - end - end - end - end -end diff --git a/lib/active_merchant/billing/integrations/direc_pay.rb b/lib/active_merchant/billing/integrations/direc_pay.rb deleted file mode 100644 index 62e3956ff15..00000000000 --- a/lib/active_merchant/billing/integrations/direc_pay.rb +++ /dev/null @@ -1,41 +0,0 @@ -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - module DirecPay - autoload :Helper, File.dirname(__FILE__) + '/direc_pay/helper.rb' - autoload :Return, File.dirname(__FILE__) + '/direc_pay/return.rb' - autoload :Notification, File.dirname(__FILE__) + '/direc_pay/notification.rb' - autoload :Status, File.dirname(__FILE__) + '/direc_pay/status.rb' - - mattr_accessor :production_url, :test_url - - self.production_url = "https://www.timesofmoney.com/direcpay/secure/dpMerchantTransaction.jsp" - self.test_url = "https://test.direcpay.com/direcpay/secure/dpMerchantTransaction.jsp" - - def self.service_url - mode = ActiveMerchant::Billing::Base.integration_mode - case mode - when :production - self.production_url - when :test - self.test_url - else - raise StandardError, "Integration mode set to an invalid value: #{mode}" - end - end - - def self.notification(post, options = {}) - Notification.new(post) - end - - def self.return(query_string, options = {}) - Return.new(query_string, options) - end - - def self.request_status_update(mid, transaction_id, notification_url) - Status.new(mid).update(transaction_id, notification_url) - end - end - end - end -end diff --git a/lib/active_merchant/billing/integrations/direc_pay/helper.rb b/lib/active_merchant/billing/integrations/direc_pay/helper.rb deleted file mode 100644 index dddbfd9a38c..00000000000 --- a/lib/active_merchant/billing/integrations/direc_pay/helper.rb +++ /dev/null @@ -1,200 +0,0 @@ -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - module DirecPay - class Helper < ActiveMerchant::Billing::Integrations::Helper - mapping :account, 'MID' - mapping :order, 'Merchant Order No' - mapping :amount, 'Amount' - mapping :currency, 'Currency' - mapping :country, 'Country' - - mapping :billing_address, :city => 'custCity', - :address1 => 'custAddress', - :state => 'custState', - :zip => 'custPinCode', - :country => 'custCountry', - :phone => 'custMobileNo' - - mapping :shipping_address, :name => 'deliveryName', - :city => 'deliveryCity', - :address1 => 'deliveryAddress', - :state => 'deliveryState', - :zip => 'deliveryPinCode', - :country => 'deliveryCountry', - :phone => 'deliveryMobileNo' - - mapping :customer, :name => 'custName', - :email => 'custEmailId' - - mapping :description, 'otherNotes' - mapping :edit_allowed, 'editAllowed' - - mapping :return_url, 'Success URL' - mapping :failure_url, 'Failure URL' - - mapping :operating_mode, 'Operating Mode' - mapping :other_details, 'Other Details' - mapping :collaborator, 'Collaborator' - - OPERATING_MODE = 'DOM' - COUNTRY = 'IND' - CURRENCY = 'INR' - OTHER_DETAILS = 'NULL' - EDIT_ALLOWED = 'Y' - - PHONE_CODES = { - 'IN' => '91', - 'US' => '01', - 'CA' => '01' - } - - ENCODED_PARAMS = [ :account, :operating_mode, :country, :currency, :amount, :order, :other_details, :return_url, :failure_url, :collaborator ] - - - def initialize(order, account, options = {}) - super - collaborator = ActiveMerchant::Billing::Base.integration_mode == :test || options[:test] ? 'TOML' : 'DirecPay' - add_field(mappings[:collaborator], collaborator) - add_field(mappings[:country], 'IND') - add_field(mappings[:operating_mode], OPERATING_MODE) - add_field(mappings[:other_details], OTHER_DETAILS) - add_field(mappings[:edit_allowed], EDIT_ALLOWED) - end - - - def customer(params = {}) - add_field(mappings[:customer][:name], full_name(params)) - add_field(mappings[:customer][:email], params[:email]) - end - - # Need to format the amount to have 2 decimal places - def amount=(money) - cents = money.respond_to?(:cents) ? money.cents : money - if money.is_a?(String) or cents.to_i <= 0 - raise ArgumentError, 'money amount must be either a Money object or a positive integer in cents.' - end - add_field(mappings[:amount], sprintf("%.2f", cents.to_f/100)) - end - - def shipping_address(params = {}) - super(update_address(:shipping_address, params)) - end - - def billing_address(params = {}) - super(update_address(:billing_address, params)) - end - - def form_fields - add_failure_url - add_request_parameters - - unencoded_parameters - end - - - private - - def add_request_parameters - params = ENCODED_PARAMS.map{ |param| fields[mappings[param]] } - encoded = encode_value(params.join('|')) - - add_field('requestparameter', encoded) - end - - def unencoded_parameters - params = fields.dup - # remove all encoded params from exported fields - ENCODED_PARAMS.each{ |param| params.delete(mappings[param]) } - # remove all special characters from each field value - params = params.collect{|name, value| [name, remove_special_characters(value)] } - Hash[params] - end - - def add_failure_url - if fields[mappings[:failure_url]].nil? - add_field(mappings[:failure_url], fields[mappings[:return_url]]) - end - end - - def update_address(address_type, params) - params = params.dup - address = params[:address1] - address = "#{address} #{params[:address2]}" if params[:address2].present? - address = "#{params[:company]} #{address}" if params[:company].present? - params[:address1] = address - - params[:phone] = normalize_phone_number(params[:phone]) - add_land_line_phone_for(address_type, params) - - if address_type == :shipping_address - shipping_name = full_name(params) || fields[mappings[:customer][:name]] - add_field(mappings[:shipping_address][:name], shipping_name) - end - params - end - - # Split a single phone number into the country code, area code and local number as best as possible - def add_land_line_phone_for(address_type, params) - address_field = address_type == :billing_address ? 'custPhoneNo' : 'deliveryPhNo' - - if params.has_key?(:phone2) - phone = normalize_phone_number(params[:phone2]) - phone_country_code, phone_area_code, phone_number = nil - - if params[:country] == 'IN' && phone =~ /(91)? *(\d{3}) *(\d{4,})$/ - phone_country_code, phone_area_code, phone_number = $1, $2, $3 - else - numbers = phone.split(' ') - case numbers.size - when 3 - phone_country_code, phone_area_code, phone_number = numbers - when 2 - phone_area_code, phone_number = numbers - else - phone =~ /(\d{3})(\d+)$/ - phone_area_code, phone_number = $1, $2 - end - end - - add_field("#{address_field}1", phone_country_code || phone_code_for_country(params[:country]) || '91') - add_field("#{address_field}2", phone_area_code) - add_field("#{address_field}3", phone_number) - end - end - - def normalize_phone_number(phone) - phone.gsub(/[^\d ]+/, '') if phone - end - - # Special characters are NOT allowed while posting transaction parameters on DirecPay system - def remove_special_characters(string) - string.gsub(/[~"'&#%]/, '-') - end - - def encode_value(value) - encoded = Base64.strict_encode64(value) - string_to_encode = encoded[0, 1] + "T" + encoded[1, encoded.length] - Base64.strict_encode64(string_to_encode) - end - - def decode_value(value) - decoded = Base64.decode64(value) - string_to_decode = decoded[0, 1] + decoded[2, decoded.length] - Base64.decode64(string_to_decode) - end - - def phone_code_for_country(country) - PHONE_CODES[country] - end - - def full_name(params) - return if params[:name].blank? && params[:first_name].blank? && params[:last_name].blank? - - params[:name] || "#{params[:first_name]} #{params[:last_name]}" - end - end - end - end - end -end diff --git a/lib/active_merchant/billing/integrations/direc_pay/notification.rb b/lib/active_merchant/billing/integrations/direc_pay/notification.rb deleted file mode 100644 index bf37e9ad140..00000000000 --- a/lib/active_merchant/billing/integrations/direc_pay/notification.rb +++ /dev/null @@ -1,76 +0,0 @@ -require 'net/http' - -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - module DirecPay - class Notification < ActiveMerchant::Billing::Integrations::Notification - RESPONSE_PARAMS = ['DirecPay Reference ID', 'Flag', 'Country', 'Currency', 'Other Details', 'Merchant Order No', 'Amount'] - - def acknowledge - true - end - - def complete? - status == 'Completed' || status == 'Pending' - end - - def status - case params['Flag'] - when 'SUCCESS' - 'Completed' - when 'PENDING' - 'Pending' - when 'FAIL' - 'Failed' - else - 'Error' - end - end - - def item_id - params['Merchant Order No'] - end - - def transaction_id - params['DirecPay Reference ID'] - end - - # the money amount we received in X.2 decimal - def gross - params['Amount'] - end - - def currency - params['Currency'] - end - - def country - params['Country'] - end - - def other_details - params['Other Details'] - end - - def test? - false - end - - # Take the posted data and move the relevant data into a hash - def parse(post) - super - - values = params['responseparams'].to_s.split('|') - response_params = values.size == 3 ? ['DirecPay Reference ID', 'Flag', 'Error message'] : RESPONSE_PARAMS - response_params.each_with_index do |name, index| - params[name] = values[index] - end - params - end - - end - end - end - end -end diff --git a/lib/active_merchant/billing/integrations/direc_pay/return.rb b/lib/active_merchant/billing/integrations/direc_pay/return.rb deleted file mode 100644 index 2eb2497daeb..00000000000 --- a/lib/active_merchant/billing/integrations/direc_pay/return.rb +++ /dev/null @@ -1,32 +0,0 @@ -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - - module DirecPay - class Return < ActiveMerchant::Billing::Integrations::Return - - def initialize(post_data, options = {}) - @notification = Notification.new(treat_failure_as_pending(post_data), options) - end - - def success? - notification.complete? - end - - def message - notification.status - end - - - private - - # Work around the issue that the initial return from DirecPay is always either SUCCESS or FAIL, there is no PENDING - def treat_failure_as_pending(post_data) - post_data.sub(/FAIL/, 'PENDING') - end - end - end - - end - end -end diff --git a/lib/active_merchant/billing/integrations/direc_pay/status.rb b/lib/active_merchant/billing/integrations/direc_pay/status.rb deleted file mode 100644 index 5b9f85200fd..00000000000 --- a/lib/active_merchant/billing/integrations/direc_pay/status.rb +++ /dev/null @@ -1,37 +0,0 @@ -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - module DirecPay - - class Status - include PostsData - - STATUS_TEST_URL = 'https://test.direcpay.com/direcpay/secure/dpMerchantTransaction.jsp' - STATUS_LIVE_URL = 'https://www.timesofmoney.com/direcpay/secure/dpPullMerchAtrnDtls.jsp' - - attr_reader :account, :options - - def initialize(account, options = {}) - @account, @options = account, options - end - - - # Use this method to manually request a status update to the provided notification_url - def update(authorization, notification_url) - url = test? ? STATUS_TEST_URL : STATUS_LIVE_URL - parameters = [ authorization, account, notification_url ] - data = PostData.new - data[:requestparams] = parameters.join('|') - - response = ssl_get("#{url}?#{data.to_post_data}") - end - - def test? - ActiveMerchant::Billing::Base.integration_mode == :test || options[:test] - end - - end - end - end - end -end diff --git a/lib/active_merchant/billing/integrations/directebanking.rb b/lib/active_merchant/billing/integrations/directebanking.rb deleted file mode 100644 index df9ad5b5d04..00000000000 --- a/lib/active_merchant/billing/integrations/directebanking.rb +++ /dev/null @@ -1,47 +0,0 @@ -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - module Directebanking - autoload :Return, File.dirname(__FILE__) + '/directebanking/return.rb' - autoload :Helper, File.dirname(__FILE__) + '/directebanking/helper.rb' - autoload :Notification, File.dirname(__FILE__) + '/directebanking/notification.rb' - - # Supported countries: - # Germany - DE - # Austria - AT - # Belgium - BE - # Netherlands - NL - # Switzerland - CH - # Great Britain - GB - - # Overwrite this if you want to change the directebanking test url - mattr_accessor :test_url - self.test_url = 'https://www.directebanking.com/payment/start' - - # Overwrite this if you want to change the directebanking production url - mattr_accessor :production_url - self.production_url = 'https://www.directebanking.com/payment/start' - - def self.service_url - mode = ActiveMerchant::Billing::Base.integration_mode - case mode - when :production - self.production_url - when :test - self.test_url - else - raise StandardError, "Integration mode set to an invalid value: #{mode}" - end - end - - def self.notification(post, options = {}) - Notification.new(post, options) - end - - def self.return(post, options = {}) - Return.new(post, options) - end - end - end - end -end diff --git a/lib/active_merchant/billing/integrations/directebanking/helper.rb b/lib/active_merchant/billing/integrations/directebanking/helper.rb deleted file mode 100644 index 5e33720b700..00000000000 --- a/lib/active_merchant/billing/integrations/directebanking/helper.rb +++ /dev/null @@ -1,90 +0,0 @@ -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - module Directebanking - class Helper < ActiveMerchant::Billing::Integrations::Helper - - # All credentials are mandatory and need to be set - # - # credential1: User ID - # credential2: Project ID - # credential3: Project Password (Algorithm: SH1) - # credential4: Notification Password (Algorithm: SH1) - def initialize(order, account, options = {}) - super - add_field('user_variable_0', order) - add_field('project_id', options[:credential2]) - @project_password = options[:credential3] - end - - SIGNATURE_FIELDS = [ - :user_id, - :project_id, - :sender_holder, - :sender_account_number, - :sender_bank_code, - :sender_country_id, - :amount, - :currency_id, - :reason_1, - :reason_2, - :user_variable_0, - :user_variable_1, - :user_variable_2, - :user_variable_3, - :user_variable_4, - :user_variable_5 - ] - - SIGNATURE_IGNORE_AT_METHOD_CREATION_FIELDS = [ - :user_id, - :amount, - :project_id, - :currency_id, - :user_variable_0, - :user_variable_1, - :user_variable_2, - :user_variable_3 - ] - - SIGNATURE_FIELDS.each do |key| - if !SIGNATURE_IGNORE_AT_METHOD_CREATION_FIELDS.include?(key) - mapping "#{key}".to_sym, "#{key.to_s}" - end - end - - # Need to format the amount to have 2 decimal places - def amount=(money) - cents = money.respond_to?(:cents) ? money.cents : money - if money.is_a?(String) or cents.to_i <= 0 - raise ArgumentError, 'money amount must be either a Money object or a positive integer in cents.' - end - add_field mappings[:amount], sprintf("%.2f", cents.to_f/100) - end - - def generate_signature_string - # format of signature: user_id|project_id|sender_holder|sender_account_number|sender_bank_code| sender_country_id|amount|currency_id|reason_1|reason_2|user_variable_0|user_variable_1|user_variable_2|user_variable_3|user_variable_4|user_variable_5|project_password - SIGNATURE_FIELDS.map {|key| @fields[key.to_s]} * "|" + "|#{@project_password}" - end - - def generate_signature - Digest::SHA1.hexdigest(generate_signature_string) - end - - def form_fields - @fields.merge('hash' => generate_signature) - end - - mapping :account, 'user_id' - mapping :amount, 'amount' - mapping :currency, 'currency_id' - mapping :description, 'reason_1' - - mapping :return_url, 'user_variable_1' - mapping :cancel_return_url, 'user_variable_2' - mapping :notify_url, 'user_variable_3' - end - end - end - end -end diff --git a/lib/active_merchant/billing/integrations/directebanking/notification.rb b/lib/active_merchant/billing/integrations/directebanking/notification.rb deleted file mode 100644 index 7b60290d8e8..00000000000 --- a/lib/active_merchant/billing/integrations/directebanking/notification.rb +++ /dev/null @@ -1,120 +0,0 @@ -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - module Directebanking - class Notification < ActiveMerchant::Billing::Integrations::Notification - - def initialize(data, options) - if options[:credential4].nil? - raise ArgumentError, "You need to provide the notification password (SH1) as the option :credential4 to verify that the notification originated from Directebanking (Payment Networks AG)" - end - super - end - - def complete? - status == 'Completed' - end - - def item_id - params['user_variable_0'] - end - - def transaction_id - params['transaction'] - end - - # When was this payment received by the client. - def received_at - Time.parse(params['created']) if params['created'] - end - - # the money amount we received in X.2 decimal. - def gross - "%.2f" % params['amount'].to_f - end - - def status - 'Completed' - end - - def currency - params['currency_id'] - end - - def test? - params['sender_bank_name'] == 'Testbank' - end - - # for verifying the signature of the URL parameters - PAYMENT_HOOK_SIGNATURE_FIELDS = [ - :transaction, - :user_id, - :project_id, - :sender_holder, - :sender_account_number, - :sender_bank_code, - :sender_bank_name, - :sender_bank_bic, - :sender_iban, - :sender_country_id, - :recipient_holder, - :recipient_account_number, - :recipient_bank_code, - :recipient_bank_name, - :recipient_bank_bic, - :recipient_iban, - :recipient_country_id, - :international_transaction, - :amount, - :currency_id, - :reason_1, - :reason_2, - :security_criteria, - :user_variable_0, - :user_variable_1, - :user_variable_2, - :user_variable_3, - :user_variable_4, - :user_variable_5, - :created - ] - - PAYMENT_HOOK_IGNORE_AT_METHOD_CREATION_FIELDS = [ - :transaction, - :amount, - :currency_id, - :user_variable_0, - :user_variable_1, - :user_variable_2, - :user_variable_3, - :created - ] - - # Provide access to raw fields - PAYMENT_HOOK_SIGNATURE_FIELDS.each do |key| - if !PAYMENT_HOOK_IGNORE_AT_METHOD_CREATION_FIELDS.include?(key) - define_method(key.to_s) do - params[key.to_s] - end - end - end - - def generate_signature_string - #format is: transaction|user_id|project_id|sender_holder|sender_account_number|sender_bank_code|sender_bank_name|sender_bank_bic|sender_iban|sender_country_id|recipient_holder|recipient_account_number|recipient_bank_code|recipient_bank_name|recipient_bank_bic|recipient_iban|recipient_country_id|international_transaction|amount|currency_id|reason_1|reason_2|security_criteria|user_variable_0|user_variable_1|user_variable_2|user_variable_3|user_variable_4|user_variable_5|created|notification_password - PAYMENT_HOOK_SIGNATURE_FIELDS.map {|key| params[key.to_s]} * "|" + "|#{@options[:credential4]}" - end - - def generate_signature - Digest::SHA1.hexdigest(generate_signature_string) - end - - def acknowledge - # signature_is_valid? - generate_signature.to_s == params['hash'].to_s - end - - end - end - end - end -end diff --git a/lib/active_merchant/billing/integrations/directebanking/return.rb b/lib/active_merchant/billing/integrations/directebanking/return.rb deleted file mode 100644 index 8f8825ad210..00000000000 --- a/lib/active_merchant/billing/integrations/directebanking/return.rb +++ /dev/null @@ -1,11 +0,0 @@ -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - module Directebanking - class Return < ActiveMerchant::Billing::Integrations::Return - end - end - end - end -end - diff --git a/lib/active_merchant/billing/integrations/dotpay.rb b/lib/active_merchant/billing/integrations/dotpay.rb deleted file mode 100644 index 5a130bd7cbb..00000000000 --- a/lib/active_merchant/billing/integrations/dotpay.rb +++ /dev/null @@ -1,22 +0,0 @@ -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - module Dotpay - autoload :Return, File.dirname(__FILE__) + '/dotpay/return.rb' - autoload :Helper, File.dirname(__FILE__) + '/dotpay/helper.rb' - autoload :Notification, File.dirname(__FILE__) + '/dotpay/notification.rb' - - mattr_accessor :service_url - self.service_url = 'https://ssl.dotpay.pl' - - def self.notification(post, options = {}) - Notification.new(post, options) - end - - def self.return(post, options = {}) - Return.new(post, options) - end - end - end - end -end diff --git a/lib/active_merchant/billing/integrations/dotpay/helper.rb b/lib/active_merchant/billing/integrations/dotpay/helper.rb deleted file mode 100644 index 464106cdcd1..00000000000 --- a/lib/active_merchant/billing/integrations/dotpay/helper.rb +++ /dev/null @@ -1,77 +0,0 @@ -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - module Dotpay - class Helper < ActiveMerchant::Billing::Integrations::Helper - def initialize(order, account, options = {}) - options = {:currency => 'PLN'}.merge options - - super - - add_field('channel', '0') - add_field('ch_lock', '0') - add_field('lang', 'PL') - add_field('onlinetransfer', '0') - add_field('tax', '0') - add_field('type', '2') - end - - mapping :account, 'id' - mapping :amount, 'amount' - - mapping :billing_address, :street => 'street', - :street_n1 => 'street_n1', - :street_n2 => 'street_n2', - :addr2 => 'addr2', - :addr3 => 'addr3', - :city => 'city', - :postcode => 'postcode', - :phone => 'phone', - :country => 'country' - - mapping :buttontext, 'buttontext' - mapping :channel, 'channel' - mapping :ch_lock, 'ch_lock' - mapping :code, 'code' - mapping :control, 'control' - mapping :currency, 'currency' - - mapping :customer, :firstname => 'firstname', - :lastname => 'lastname', - :email => 'email' - - mapping :description, 'description' - mapping :lang, 'lang' - mapping :onlinetransfer, 'onlinetransfer' - mapping :order, 'description' - mapping :p_email, 'p_email' - mapping :p_info, 'p_info' - mapping :tax, 'tax' - mapping :type, 'type' - mapping :url, 'url' - mapping :urlc, 'urlc' - - def billing_address(params = {}) - country = lookup_country_code(params.delete(:country) { 'POL' }, :alpha3) - add_field(mappings[:billing_address][:country], country) - - # Everything else - params.each do |k, v| - field = mappings[:billing_address][k] - add_field(field, v) unless field.nil? - end - end - - private - - def lookup_country_code(name_or_code, format = country_format) - country = Country.find(name_or_code) - country.code(format).to_s - rescue InvalidCountryCodeError - name_or_code - end - end - end - end - end -end diff --git a/lib/active_merchant/billing/integrations/dotpay/notification.rb b/lib/active_merchant/billing/integrations/dotpay/notification.rb deleted file mode 100644 index a403b15c3ef..00000000000 --- a/lib/active_merchant/billing/integrations/dotpay/notification.rb +++ /dev/null @@ -1,86 +0,0 @@ -require 'net/http' - -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - module Dotpay - class Notification < ActiveMerchant::Billing::Integrations::Notification - def complete? - status == 'OK' && %w(2 4 5).include?(t_status) - end - - def currency - orginal_amount.split(' ')[1] - end - - # the money amount we received in X.2 decimal. - def gross - params['amount'] - end - - def pin=(value) - @options[:pin] = value - end - - def status - params['status'] - end - - def test? - params['t_id'].match('.*-TST\d+') ? true : false - end - - PAYMENT_HOOK_FIELDS = [ - :id, - :control, - :t_id, - :orginal_amount, - :email, - :service, - :code, - :username, - :password, - :t_status, - :description, - :md5, - :p_info, - :p_email, - :t_date - ] - - PAYMENT_HOOK_SIGNATURE_FIELDS = [ - :id, - :control, - :t_id, - :amount, - :email, - :service, - :code, - :username, - :password, - :t_status - ] - - # Provide access to raw fields - PAYMENT_HOOK_FIELDS.each do |key| - define_method(key.to_s) do - params[key.to_s] - end - end - - def generate_signature_string - "#{@options[:pin]}:" + PAYMENT_HOOK_SIGNATURE_FIELDS.map {|key| params[key.to_s]} * ":" - end - - def generate_signature - Digest::MD5.hexdigest(generate_signature_string) - end - - def acknowledge - generate_signature.to_s == md5.to_s - end - end - end - end - end -end diff --git a/lib/active_merchant/billing/integrations/dotpay/return.rb b/lib/active_merchant/billing/integrations/dotpay/return.rb deleted file mode 100644 index 236db3d9d8c..00000000000 --- a/lib/active_merchant/billing/integrations/dotpay/return.rb +++ /dev/null @@ -1,11 +0,0 @@ -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - module Dotpay - class Return < ActiveMerchant::Billing::Integrations::Return - end - end - end - end -end - diff --git a/lib/active_merchant/billing/integrations/dwolla.rb b/lib/active_merchant/billing/integrations/dwolla.rb deleted file mode 100644 index e154bddaee6..00000000000 --- a/lib/active_merchant/billing/integrations/dwolla.rb +++ /dev/null @@ -1,23 +0,0 @@ -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - module Dwolla - autoload :Return, 'active_merchant/billing/integrations/dwolla/return.rb' - autoload :Helper, 'active_merchant/billing/integrations/dwolla/helper.rb' - autoload :Notification, 'active_merchant/billing/integrations/dwolla/notification.rb' - autoload :Common, 'active_merchant/billing/integrations/dwolla/common.rb' - - mattr_accessor :service_url - self.service_url = 'https://www.dwolla.com/payment/pay' - - def self.notification(post, options={}) - Notification.new(post, options) - end - - def self.return(query_string, options={}) - Return.new(query_string, options) - end - end - end - end -end diff --git a/lib/active_merchant/billing/integrations/dwolla/common.rb b/lib/active_merchant/billing/integrations/dwolla/common.rb deleted file mode 100644 index fbbb7e79658..00000000000 --- a/lib/active_merchant/billing/integrations/dwolla/common.rb +++ /dev/null @@ -1,23 +0,0 @@ -require "openssl" - -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - module Dwolla - module Common - def verify_signature(checkoutId, amount, notification_signature, secret) - if secret.nil? - raise ArgumentError, "You need to provide the Application secret as the option :credential3 to verify that the notification originated from Dwolla" - end - - expected_signature = OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA1.new, secret, "%s&%.2f" % [checkoutId, amount]) - - if notification_signature != expected_signature - raise StandardError, "Dwolla signature verification failed." - end - end - end - end - end - end -end diff --git a/lib/active_merchant/billing/integrations/dwolla/helper.rb b/lib/active_merchant/billing/integrations/dwolla/helper.rb deleted file mode 100644 index 290b12d511e..00000000000 --- a/lib/active_merchant/billing/integrations/dwolla/helper.rb +++ /dev/null @@ -1,43 +0,0 @@ -require "openssl" - -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - module Dwolla - class Helper < ActiveMerchant::Billing::Integrations::Helper - def initialize(order, account, options = {}) - super - add_field('name', 'Store Purchase') - - timestamp = Time.now.to_i.to_s - - if ActiveMerchant::Billing::Base.integration_mode == :test || options[:test] - add_field('test', 'true') - # timestamp used for test signature generation: - timestamp = "1370726016" - end - - add_field('timestamp', timestamp) - add_field('allowFundingSources', 'true') - - key = options[:credential2].to_s - secret = options[:credential3].to_s - orderid = order.to_s - signature = OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA1.new, secret, "#{key}&#{timestamp}&#{orderid}") - add_field('signature', signature) - end - - mapping :account, 'destinationid' - mapping :credential2, 'key' - mapping :notify_url, 'callback' - mapping :return_url, 'redirect' - mapping :description, 'description' - mapping :amount, 'amount' - mapping :tax, 'tax' - mapping :shipping, 'shipping' - mapping :order, 'orderid' - end - end - end - end -end diff --git a/lib/active_merchant/billing/integrations/dwolla/notification.rb b/lib/active_merchant/billing/integrations/dwolla/notification.rb deleted file mode 100644 index 112657f27ff..00000000000 --- a/lib/active_merchant/billing/integrations/dwolla/notification.rb +++ /dev/null @@ -1,64 +0,0 @@ -require 'net/http' -require 'digest/sha1' - -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - module Dwolla - class Notification < ActiveMerchant::Billing::Integrations::Notification - include Common - - def initialize(data, options) - super - end - - def complete? - (status == "Completed") - end - - def status - params["Status"] - end - - def transaction_id - params['TransactionId'] - end - - def item_id - params['OrderId'] - end - - def currency - "USD" - end - - def gross - params['Amount'] - end - - def error - params['Error'] - end - - def test? - params['TestMode'] != "false" - end - - def acknowledge - true - end - - private - - def parse(post) - @raw = post.to_s - json_post = JSON.parse(post) - verify_signature(json_post['CheckoutId'], json_post['Amount'], json_post['Signature'], @options[:credential3]) - - params.merge!(json_post) - end - end - end - end - end -end diff --git a/lib/active_merchant/billing/integrations/dwolla/return.rb b/lib/active_merchant/billing/integrations/dwolla/return.rb deleted file mode 100644 index 837dc545f26..00000000000 --- a/lib/active_merchant/billing/integrations/dwolla/return.rb +++ /dev/null @@ -1,49 +0,0 @@ -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - module Dwolla - class Return < ActiveMerchant::Billing::Integrations::Return - include Common - - def initialize(data, options) - params = parse(data) - - if params['error'] != 'failure' - verify_signature(params['checkoutId'], params['amount'], params['signature'], options[:credential3]) - end - - super - end - - def success? - (self.error.nil? && self.callback_success?) - end - - def error - params['error'] - end - - def error_description - params['error_description'] - end - - def checkout_id - params['checkoutId'] - end - - def transaction - params['transaction'] - end - - def test? - params['test'] != nil - end - - def callback_success? - (params['postback'] != "failure") - end - end - end - end - end -end diff --git a/lib/active_merchant/billing/integrations/e_payment_plans.rb b/lib/active_merchant/billing/integrations/e_payment_plans.rb deleted file mode 100644 index 5614a0bac3b..00000000000 --- a/lib/active_merchant/billing/integrations/e_payment_plans.rb +++ /dev/null @@ -1,48 +0,0 @@ -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - module EPaymentPlans - autoload :Helper, File.dirname(__FILE__) + '/e_payment_plans/helper.rb' - autoload :Notification, File.dirname(__FILE__) + '/e_payment_plans/notification.rb' - - mattr_accessor :production_url - self.production_url = 'https://www.epaymentplans.com' - - mattr_accessor :test_url - self.test_url = 'https://test.epaymentplans.com' - - def self.service_url - mode = ActiveMerchant::Billing::Base.integration_mode - case mode - when :production - "#{production_url}/order/purchase" - when :test - "#{test_url}/order/purchase" - else - raise StandardError, "Integration mode set to an invalid value: #{mode}" - end - end - - def self.notification_confirmation_url - mode = ActiveMerchant::Billing::Base.integration_mode - case mode - when :production - "#{production_url}/order/confirmation" - when :test - "#{test_url}/order/confirmation" - else - raise StandardError, "Integration mode set to an invalid value: #{mode}" - end - end - - def self.notification(post, options = {}) - Notification.new(post, options) - end - - def self.return(query_string, options = {}) - Return.new(query_string, options) - end - end - end - end -end diff --git a/lib/active_merchant/billing/integrations/e_payment_plans/helper.rb b/lib/active_merchant/billing/integrations/e_payment_plans/helper.rb deleted file mode 100644 index 76a56101450..00000000000 --- a/lib/active_merchant/billing/integrations/e_payment_plans/helper.rb +++ /dev/null @@ -1,34 +0,0 @@ -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - module EPaymentPlans - class Helper < ActiveMerchant::Billing::Integrations::Helper - mapping :account, 'order[account]' - mapping :amount, 'order[amount]' - - mapping :order, 'order[num]' - - mapping :customer, :first_name => 'order[first_name]', - :last_name => 'order[last_name]', - :email => 'order[email]', - :phone => 'order[phone]' - - mapping :billing_address, :city => 'order[city]', - :address1 => 'order[address1]', - :address2 => 'order[address2]', - :company => 'order[company]', - :state => 'order[state]', - :zip => 'order[zip]', - :country => 'order[country]' - - mapping :notify_url, 'order[notify_url]' - mapping :return_url, 'order[return_url]' - mapping :cancel_return_url, 'order[cancel_return_url]' - mapping :description, 'order[description]' - mapping :tax, 'order[tax]' - mapping :shipping, 'order[shipping]' - end - end - end - end -end diff --git a/lib/active_merchant/billing/integrations/e_payment_plans/notification.rb b/lib/active_merchant/billing/integrations/e_payment_plans/notification.rb deleted file mode 100644 index 339e25ee74b..00000000000 --- a/lib/active_merchant/billing/integrations/e_payment_plans/notification.rb +++ /dev/null @@ -1,84 +0,0 @@ -require 'net/http' - -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - module EPaymentPlans - class Notification < ActiveMerchant::Billing::Integrations::Notification - include ActiveMerchant::PostsData - def complete? - status == "Completed" - end - - def transaction_id - params['transaction_id'] - end - - def item_id - params['item_id'] - end - - # When was this payment received by the client. - def received_at - Time.parse(params['received_at'].to_s).utc - end - - def gross - params['gross'] - end - - def currency - params['currency'] - end - - def security_key - params['security_key'] - end - - # Was this a test transaction? - def test? - params['test'] == 'test' - end - - def status - params['status'].capitalize - end - - # Acknowledge the transaction to EPaymentPlans. This method has to be called after a new - # apc arrives. EPaymentPlans will verify that all the information we received are correct - # and will return ok or a fail. - # - # Example: - # - # def ipn - # notify = EPaymentPlans.notification(request.raw_post) - # - # if notify.acknowledge - # ... process order ... if notify.complete? - # else - # ... log possible hacking attempt ... - # end - def acknowledge - payload = raw - - response = ssl_post(EPaymentPlans.notification_confirmation_url, payload) - - # Replace with the appropriate codes - raise StandardError.new("Faulty EPaymentPlans result: #{response}") unless ["AUTHORISED", "DECLINED"].include?(response) - response == "AUTHORISED" - end - - private - # Take the posted data and move the relevant data into a hash - def parse(post) - @raw = post - for line in post.split('&') - key, value = *line.scan( %r{^(\w+)\=(.*)$} ).flatten - params[key] = value - end - end - end - end - end - end -end diff --git a/lib/active_merchant/billing/integrations/easy_pay.rb b/lib/active_merchant/billing/integrations/easy_pay.rb deleted file mode 100644 index 701f1d19b25..00000000000 --- a/lib/active_merchant/billing/integrations/easy_pay.rb +++ /dev/null @@ -1,30 +0,0 @@ -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - - # Documentation: https://ssl.easypay.by/light/ - module EasyPay - autoload :Helper, File.dirname(__FILE__) + '/easy_pay/helper.rb' - autoload :Notification, File.dirname(__FILE__) + '/easy_pay/notification.rb' - autoload :Common, File.dirname(__FILE__) + '/easy_pay/common.rb' - - mattr_accessor :signature_parameter_name - self.signature_parameter_name = 'EP_Hash' - - mattr_accessor :notify_signature_parameter_name - self.notify_signature_parameter_name = 'notify_signature' - - mattr_accessor :service_url - self.service_url = 'https://ssl.easypay.by/weborder/' - - def self.helper(order, account, options = {}) - Helper.new(order, account, options) - end - - def self.notification(query_string, options = {}) - Notification.new(query_string, options) - end - end - end - end -end diff --git a/lib/active_merchant/billing/integrations/easy_pay/common.rb b/lib/active_merchant/billing/integrations/easy_pay/common.rb deleted file mode 100644 index 74a8e4b2278..00000000000 --- a/lib/active_merchant/billing/integrations/easy_pay/common.rb +++ /dev/null @@ -1,40 +0,0 @@ -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - module EasyPay - module Common - def generate_signature(type) - string = case type - when :request - request_signature_string - when :notify - notify_signature_string - end - - Digest::MD5.hexdigest(string) - end - - def request_signature_string - [ - @fields[mappings[:account]], - @secret, - @fields[mappings[:order]], - @fields[mappings[:amount]] - ].join - end - - def notify_signature_string - [ - params['order_mer_code'], - params['sum'], - params['mer_no'], - params['card'], - params['purch_date'], - secret - ].join - end - end - end - end - end -end diff --git a/lib/active_merchant/billing/integrations/easy_pay/helper.rb b/lib/active_merchant/billing/integrations/easy_pay/helper.rb deleted file mode 100644 index 5df2a4f403c..00000000000 --- a/lib/active_merchant/billing/integrations/easy_pay/helper.rb +++ /dev/null @@ -1,36 +0,0 @@ -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - module EasyPay - class Helper < ActiveMerchant::Billing::Integrations::Helper - include Common - - def initialize(order, account, options = {}) - super - @secret = options[:credential2] - end - - def form_fields - @fields.merge(ActiveMerchant::Billing::Integrations::EasyPay.signature_parameter_name => generate_signature(:request)) - end - - def params - @fields - end - - mapping :account, 'EP_MerNo' - mapping :amount, 'EP_Sum' - mapping :order, 'EP_OrderNo' - mapping :comment, 'EP_Comment' - mapping :order_info, 'EP_OrderInfo' - mapping :expires, 'EP_Expires' - mapping :success_url, 'EP_Success_URL' - mapping :cancel_url, 'EP_Cancel_URL' - mapping :debug, 'EP_Debug' - mapping :url_type, 'EP_URL_Type' - mapping :encoding, 'EP_Encoding' - end - end - end - end -end diff --git a/lib/active_merchant/billing/integrations/easy_pay/notification.rb b/lib/active_merchant/billing/integrations/easy_pay/notification.rb deleted file mode 100644 index 90ce7e629a4..00000000000 --- a/lib/active_merchant/billing/integrations/easy_pay/notification.rb +++ /dev/null @@ -1,59 +0,0 @@ -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - module EasyPay - class Notification < ActiveMerchant::Billing::Integrations::Notification - include Common - - def initialize(data, options) - if options[:credential2].nil? - raise ArgumentError, "You need to provide the md5 secret as the option :credential2 to verify that the notification originated from EasyPay" - end - - super - end - - def self.recognizes?(params) - params.has_key?('order_mer_code') && params.has_key?('sum') - end - - def complete? - true - end - - def amount - BigDecimal.new(gross) - end - - def item_id - params['order_mer_code'] - end - - def security_key - params[ActiveMerchant::Billing::Integrations::EasyPay.notify_signature_parameter_name] - end - - def gross - params['sum'] - end - - def status - 'Completed' - end - - def secret - @options[:credential2] - end - - def acknowledge - security_key == generate_signature(:notify) - end - - def success_response(*args) - { :nothing => true } - end - end - end - end - end -end diff --git a/lib/active_merchant/billing/integrations/epay.rb b/lib/active_merchant/billing/integrations/epay.rb deleted file mode 100644 index 24de6aae014..00000000000 --- a/lib/active_merchant/billing/integrations/epay.rb +++ /dev/null @@ -1,21 +0,0 @@ -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - module Epay - autoload :Helper, File.dirname(__FILE__) + '/epay/helper.rb' - autoload :Notification, File.dirname(__FILE__) + '/epay/notification.rb' - - mattr_accessor :service_url - self.service_url = 'https://ssl.ditonlinebetalingssystem.dk/integration/ewindow/Default.aspx' - - def self.notification(post, options = {}) - Notification.new(post) - end - - def self.return(post, options = {}) - Return.new(post, options) - end - end - end - end -end \ No newline at end of file diff --git a/lib/active_merchant/billing/integrations/epay/helper.rb b/lib/active_merchant/billing/integrations/epay/helper.rb deleted file mode 100644 index 650dcd661c0..00000000000 --- a/lib/active_merchant/billing/integrations/epay/helper.rb +++ /dev/null @@ -1,55 +0,0 @@ -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - module Epay - class Helper < ActiveMerchant::Billing::Integrations::Helper - - def initialize(order, merchantnumber, options = {}) - super - add_field('windowstate', 3) - add_field('language', '0') - add_field('orderid', format_order_number(order)) - @fields = Hash[@fields.sort] - end - - def md5secret(value) - @md5secret = value - end - - def form_fields - @fields.merge('hash' => generate_md5hash) - end - - def generate_md5string - @fields.sort.each.map { |key, value| key != 'hash' ? value.to_s : ''} * "" + @md5secret - end - - def generate_md5hash - Digest::MD5.hexdigest(generate_md5string) - end - - # Limited to 20 digits max - def format_order_number(number) - number.to_s.gsub(/[^\w_]/, '').rjust(4, "0")[0...20] - end - - mapping :account, 'merchantnumber' - mapping :language, 'language' - mapping :amount, 'amount' - mapping :currency, 'currency' - mapping :return_url, 'accepturl' - mapping :cancel_return_url, 'cancelurl' - mapping :notify_url, 'callbackurl' - mapping :autocapture, 'instantcapture' - mapping :description, 'description' - mapping :credential3, 'md5secret' - mapping :customer, '' - mapping :billing_address, {} - mapping :tax, '' - mapping :shipping, '' - - end - end - end - end -end diff --git a/lib/active_merchant/billing/integrations/epay/notification.rb b/lib/active_merchant/billing/integrations/epay/notification.rb deleted file mode 100644 index aa868ee51b7..00000000000 --- a/lib/active_merchant/billing/integrations/epay/notification.rb +++ /dev/null @@ -1,110 +0,0 @@ -require 'net/http' - -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - module Epay - class Notification < ActiveMerchant::Billing::Integrations::Notification - - CURRENCY_CODES = { - :ADP => '020', :AED => '784', :AFA => '004', :ALL => '008', :AMD => '051', - :ANG => '532', :AOA => '973', :ARS => '032', :AUD => '036', :AWG => '533', - :AZM => '031', :BAM => '977', :BBD => '052', :BDT => '050', :BGL => '100', - :BGN => '975', :BHD => '048', :BIF => '108', :BMD => '060', :BND => '096', - :BOB => '068', :BOV => '984', :BRL => '986', :BSD => '044', :BTN => '064', - :BWP => '072', :BYR => '974', :BZD => '084', :CAD => '124', :CDF => '976', - :CHF => '756', :CLF => '990', :CLP => '152', :CNY => '156', :COP => '170', - :CRC => '188', :CUP => '192', :CVE => '132', :CYP => '196', :CZK => '203', - :DJF => '262', :DKK => '208', :DOP => '214', :DZD => '012', :ECS => '218', - :ECV => '983', :EEK => '233', :EGP => '818', :ERN => '232', :ETB => '230', - :EUR => '978', :FJD => '242', :FKP => '238', :GBP => '826', :GEL => '981', - :GHC => '288', :GIP => '292', :GMD => '270', :GNF => '324', :GTQ => '320', - :GWP => '624', :GYD => '328', :HKD => '344', :HNL => '340', :HRK => '191', - :HTG => '332', :HUF => '348', :IDR => '360', :ILS => '376', :INR => '356', - :IQD => '368', :IRR => '364', :ISK => '352', :JMD => '388', :JOD => '400', - :JPY => '392', :KES => '404', :KGS => '417', :KHR => '116', :KMF => '174', - :KPW => '408', :KRW => '410', :KWD => '414', :KYD => '136', :KZT => '398', - :LAK => '418', :LBP => '422', :LKR => '144', :LRD => '430', :LSL => '426', - :LTL => '440', :LVL => '428', :LYD => '434', :MAD => '504', :MDL => '498', - :MGF => '450', :MKD => '807', :MMK => '104', :MNT => '496', :MOP => '446', - :MRO => '478', :MTL => '470', :MUR => '480', :MVR => '462', :MWK => '454', - :MXN => '484', :MXV => '979', :MYR => '458', :MZM => '508', :NAD => '516', - :NGN => '566', :NIO => '558', :NOK => '578', :NPR => '524', :NZD => '554', - :OMR => '512', :PAB => '590', :PEN => '604', :PGK => '598', :PHP => '608', - :PKR => '586', :PLN => '985', :PYG => '600', :QAR => '634', :ROL => '642', - :RUB => '643', :RUR => '810', :RWF => '646', :SAR => '682', :SBD => '090', - :SCR => '690', :SDD => '736', :SEK => '752', :SGD => '702', :SHP => '654', - :SIT => '705', :SKK => '703', :SLL => '694', :SOS => '706', :SRG => '740', - :STD => '678', :SVC => '222', :SYP => '760', :SZL => '748', :THB => '764', - :TJS => '972', :TMM => '795', :TND => '788', :TOP => '776', :TPE => '626', - :TRL => '792', :TRY => '949', :TTD => '780', :TWD => '901', :TZS => '834', - :UAH => '980', :UGX => '800', :USD => '840', :UYU => '858', :UZS => '860', - :VEB => '862', :VND => '704', :VUV => '548', :XAF => '950', :XCD => '951', - :XOF => '952', :XPF => '953', :YER => '886', :YUM => '891', :ZAR => '710', - :ZMK => '894', :ZWD => '716' - } - - def complete? - Integer(transaction_id) > 0 - end - - def item_id - params['orderid'] - end - - def transaction_id - params['txnid'] - end - - def received_at - Time.mktime(params['date'][0..3], params['date'][4..5], params['date'][6..7], params['time'][0..1], params['time'][2..3]) - end - - def gross - "%.2f" % (gross_cents / 100.0) - end - - def gross_cents - params['amount'].to_i - end - - def test? - return false - end - - %w(txnid orderid amount currency date time hash fraud payercountry issuercountry txnfee subscriptionid paymenttype cardno).each do |attr| - define_method(attr) do - params[attr] - end - end - - def currency - CURRENCY_CODES.invert[params['currency']].to_s - end - - def amount - Money.new(params['amount'].to_i, currency) - end - - def generate_md5string - md5string = String.new - for line in @raw.split('&') - key, value = *line.scan( %r{^([A-Za-z0-9_.]+)\=(.*)$} ).flatten - md5string += params[key] if key != 'hash' - end - return md5string + @options[:credential3] - end - - def generate_md5hash - Digest::MD5.hexdigest(generate_md5string) - end - - def acknowledge - generate_md5hash == params['hash'] - end - - end - end - end - end -end diff --git a/lib/active_merchant/billing/integrations/first_data.rb b/lib/active_merchant/billing/integrations/first_data.rb deleted file mode 100644 index 65e8f84743e..00000000000 --- a/lib/active_merchant/billing/integrations/first_data.rb +++ /dev/null @@ -1,38 +0,0 @@ -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - module FirstData - autoload :Helper, 'active_merchant/billing/integrations/first_data/helper.rb' - autoload :Notification, 'active_merchant/billing/integrations/first_data/notification.rb' - - # Overwrite this if you want to change the ANS test url - mattr_accessor :test_url - self.test_url = 'https://demo.globalgatewaye4.firstdata.com/payment' - - # Overwrite this if you want to change the ANS production url - mattr_accessor :production_url - self.production_url = 'https://checkout.globalgatewaye4.firstdata.com/payment' - - def self.service_url - mode = ActiveMerchant::Billing::Base.integration_mode - case mode - when :production - self.production_url - when :test - self.test_url - else - raise StandardError, "Integration mode set to an invalid value: #{mode}" - end - end - - def self.notification(post) - Notification.new(post) - end - - def self.return(query_string) - Return.new(query_string) - end - end - end - end -end diff --git a/lib/active_merchant/billing/integrations/first_data/helper.rb b/lib/active_merchant/billing/integrations/first_data/helper.rb deleted file mode 100644 index ae314ae7e6f..00000000000 --- a/lib/active_merchant/billing/integrations/first_data/helper.rb +++ /dev/null @@ -1,61 +0,0 @@ -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - module FirstData - # First Data payment pages emulates the Authorize.Net SIM API. See - # ActiveMerchant::Billing::Integrations::AuthorizeNetSim::Helper for - # more details. - # - # An example. Note the username as a parameter and transaction key you - # will want to use later. - # - # payment_service_for('order_id', 'first_data_payment_page_id', :service => :first_data, :amount => 157.0) do |service| - # - # # You must call setup_hash and invoice - # - # service.setup_hash :transaction_key => '8CP6zJ7uD875J6tY', - # :order_timestamp => 1206836763 - # service.customer_id 8 - # service.customer :first_name => 'g', - # :last_name => 'g', - # :email => 'g@g.com', - # :phone => '3' - # service.billing_address :zip => 'g', - # :country => 'United States of America', - # :address => 'g' - # - # service.ship_to_address :first_name => 'g', - # :last_name => 'g', - # :city => '', - # :address => 'g', - # :address2 => '', - # :state => address.state, - # :country => 'United States of America', - # :zip => 'g' - # - # service.invoice "516428355" # your invoice number - # # The end-user is presented with the HTML produced by the notify_url - # # (using the First Data Receipt Link feature). - # service.return_url "http://mysite/first_data_receipt_generator_page" - # service.payment_header 'My store name' - # service.add_line_item :name => 'item name', :quantity => 1, :unit_price => 0 - # service.test_request 'true' # only if it's just a test - # service.shipping '25.0' - # # Tell it to display a "0" line item for shipping, with the price in - # # the name, otherwise it isn't shown at all, leaving the end user to - # # wonder why the total is different than the sum of the line items. - # service.add_shipping_as_line_item - # server.add_tax_as_line_item # same with tax - # # See the helper.rb file for various custom fields - # end - class Helper < ActiveMerchant::Billing::Integrations::AuthorizeNetSim::Helper - # Configure notify_url to use the "Relay Response" feature - mapping :notify_url, 'x_relay_url' - - # Configure return_url to use the "Receipt Link" feature - mapping :return_url, 'x_receipt_link_url' - end - end - end - end -end diff --git a/lib/active_merchant/billing/integrations/first_data/notification.rb b/lib/active_merchant/billing/integrations/first_data/notification.rb deleted file mode 100644 index 2e786ff3db8..00000000000 --- a/lib/active_merchant/billing/integrations/first_data/notification.rb +++ /dev/null @@ -1,56 +0,0 @@ -require 'net/http' - -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - # First Data payment pages emulates the Authorize.Net SIM API. See - # ActiveMerchant::Billing::Integrations::FirstData::Notification for - # more details. - # - # # Example: - # parser = FirstData::Notification.new(request.raw_post) - # passed = parser.complete? - # - # order = Order.find_by_order_number(parser.invoice_num) - # - # unless order - # @message = 'Error--unable to find your transaction! Please contact us directly.' - # return render :partial => 'first_data_payment_response' - # end - # - # if order.total != parser.gross.to_f - # logger.error "First Data said they paid for #{parser.gross} and it should have been #{order.total}!" - # passed = false - # end - # - # # Theoretically, First Data will *never* pass us the same transaction - # # ID twice, but we can double check that... by using - # # parser.transaction_id, and checking against previous orders' transaction - # # id's (which you can save when the order is completed).... - # unless parser.acknowledge FIRST_DATA_TRANSACTION_KEY, FIRST_DATA_RESPONSE_KEY - # passed = false - # logger.error "ALERT POSSIBLE FRAUD ATTEMPT" - # end - # - # unless parser.cavv_matches? and parser.avs_code_matches? - # logger.error 'Warning--non matching CC!' + params.inspect - # # Could fail them here, as well (recommended)... - # end - # - # if passed - # # Set up your session, and render something that will redirect them to - # # your site, most likely. - # else - # # Render failure or redirect them to your site where you will render failure - # end - - module FirstData - class Notification < ActiveMerchant::Billing::Integrations::AuthorizeNetSim::Notification - def acknowledge(response_key, payment_page_id) - Digest::MD5.hexdigest(response_key + payment_page_id + params['x_trans_id'] + sprintf('%.2f', gross)) == params['x_MD5_Hash'].downcase - end - end - end - end - end -end diff --git a/lib/active_merchant/billing/integrations/gestpay.rb b/lib/active_merchant/billing/integrations/gestpay.rb deleted file mode 100644 index fca773c78a0..00000000000 --- a/lib/active_merchant/billing/integrations/gestpay.rb +++ /dev/null @@ -1,25 +0,0 @@ -# With help from Giovanni Intini and his code for RGestPay - http://medlar.it/it/progetti/rgestpay - -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - module Gestpay - autoload :Return, File.dirname(__FILE__) + '/gestpay/return.rb' - autoload :Common, File.dirname(__FILE__) + '/gestpay/common.rb' - autoload :Helper, File.dirname(__FILE__) + '/gestpay/helper.rb' - autoload :Notification, File.dirname(__FILE__) + '/gestpay/notification.rb' - - mattr_accessor :service_url - self.service_url = 'https://ecomm.sella.it/gestpay/pagam.asp' - - def self.notification(post, options = {}) - Notification.new(post) - end - - def self.return(query_string, options = {}) - Return.new(query_string) - end - end - end - end -end diff --git a/lib/active_merchant/billing/integrations/gestpay/common.rb b/lib/active_merchant/billing/integrations/gestpay/common.rb deleted file mode 100644 index 213e47a1248..00000000000 --- a/lib/active_merchant/billing/integrations/gestpay/common.rb +++ /dev/null @@ -1,42 +0,0 @@ -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - module Gestpay - module Common - VERSION = "2.0" - ENCRYPTION_PATH = "/CryptHTTPS/Encrypt.asp" - DECRYPTION_PATH = "/CryptHTTPS/Decrypt.asp" - DELIMITER = '*P1*' - - CURRENCY_MAPPING = { - 'EUR' => '242', - 'ITL' => '18', - 'BRL' => '234', - 'USD' => '1', - 'JPY' => '71', - 'HKD' => '103' - } - - def parse_response(response) - case response - when /#cryptstring#(.*)#\/cryptstring#/, /#decryptstring#(.*)#\/decryptstring#/ - $1 - when /#error#(.*)#\/error#/ - raise StandardError, "An error occurred retrieving the encrypted string from GestPay: #{$1}" - else - raise StandardError, "No response was received by GestPay" - end - end - - def ssl_get(url, path) - uri = URI.parse(url) - site = Net::HTTP.new(uri.host, uri.port) - site.use_ssl = true - site.verify_mode = OpenSSL::SSL::VERIFY_NONE - site.get(path).body - end - end - end - end - end -end \ No newline at end of file diff --git a/lib/active_merchant/billing/integrations/gestpay/helper.rb b/lib/active_merchant/billing/integrations/gestpay/helper.rb deleted file mode 100644 index 2cb4b04e903..00000000000 --- a/lib/active_merchant/billing/integrations/gestpay/helper.rb +++ /dev/null @@ -1,70 +0,0 @@ -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - module Gestpay - class Helper < ActiveMerchant::Billing::Integrations::Helper - include Common - # Valid language codes - # Italian => 1 - # English => 2 - # Spanish => 3 - # French => 4 - # Tedesco => 5 - def initialize(order, account, options = {}) - super - add_field('PAY1_IDLANGUAGE', 2) - end - - mapping :account, 'ShopLogin' - - mapping :amount, 'PAY1_AMOUNT' - mapping :currency, 'PAY1_UICCODE' - - mapping :order, 'PAY1_SHOPTRANSACTIONID' - - # Buyer name PAY1_CHNAME - mapping :customer, :email => 'PAY1_CHEMAIL' - - mapping :credit_card, :number => 'PAY1_CARDNUMBER', - :expiry_month => 'PAY1_EXPMONTH', - :expiry_year => 'PAY1_EXPYEAR', - :verification_value => 'PAY1_CVV' - - def customer(params = {}) - add_field(mappings[:customer][:email], params[:email]) - add_field('PAY1_CHNAME', "#{params[:first_name]} #{params[:last_name]}") - end - - def currency=(currency_code) - code = CURRENCY_MAPPING[currency_code] - raise StandardError, "Invalid currency code #{currency_code} specified" if code.nil? - - add_field(mappings[:currency], code) - end - - def form_fields - @encrypted_data ||= get_encrypted_string - - { - 'a' => @fields['ShopLogin'], - 'b' => @encrypted_data - } - end - - def get_encrypted_string - response = ssl_get(Gestpay.service_url, encryption_query_string) - parse_response(response) - end - - def encryption_query_string - fields = ['PAY1_AMOUNT', 'PAY1_SHOPTRANSACTIONID', 'PAY1_UICCODE'] - - encoded_params = fields.collect{ |field| "#{field}=#{CGI.escape(@fields[field])}" }.join(DELIMITER) - - "#{ENCRYPTION_PATH}?a=" + CGI.escape(@fields['ShopLogin']) + "&b=" + encoded_params + "&c=" + CGI.escape(VERSION) - end - end - end - end - end -end diff --git a/lib/active_merchant/billing/integrations/gestpay/notification.rb b/lib/active_merchant/billing/integrations/gestpay/notification.rb deleted file mode 100644 index 27ed5773680..00000000000 --- a/lib/active_merchant/billing/integrations/gestpay/notification.rb +++ /dev/null @@ -1,85 +0,0 @@ -require 'net/http' - -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - module Gestpay - class Notification < ActiveMerchant::Billing::Integrations::Notification - include Common - - def complete? - status == 'Completed' - end - - # The important param - def item_id - params['PAY1_SHOPTRANSACTIONID'] - end - - def transaction_id - params['PAY1_BANKTRANSACTIONID'] - end - - # the money amount we received in X.2 decimal. - def gross - params['PAY1_AMOUNT'] - end - - def currency - # Ruby 1.9 compat - method = CURRENCY_MAPPING.respond_to?(:key) ? :key : :index - CURRENCY_MAPPING.send(method, params['PAY1_UICCODE']) - end - - def test? - false - end - - def status - case params['PAY1_TRANSACTIONRESULT'] - when 'OK' - 'Completed' - else - 'Failed' - end - end - - def acknowledge - true - end - - private - # Take the posted data and move the relevant data into a hash - def parse(query_string) - @raw = query_string - - return if query_string.blank? - encrypted_params = parse_delimited_string(query_string) - - return if encrypted_params['a'].blank? || encrypted_params['b'].blank? - @params = decrypt_data(encrypted_params['a'], encrypted_params['b']) - end - - def parse_delimited_string(string, delimiter = '&', unencode_cgi = false) - result = {} - for line in string.split(delimiter) - key, value = *line.scan( %r{^(\w+)\=(.*)$} ).flatten - result[key] = unencode_cgi ? CGI.unescape(value) : value - end - result - end - - def decrypt_data(shop_login, encrypted_string) - response = ssl_get(Gestpay.service_url, decryption_query_string(shop_login, encrypted_string)) - encoded_response = parse_response(response) - parse_delimited_string(encoded_response, DELIMITER, true) - end - - def decryption_query_string(shop_login, encrypted_string) - "#{DECRYPTION_PATH}?a=" + CGI.escape(shop_login) + "&b=" + encrypted_string + "&c=" + CGI.escape(VERSION) - end - end - end - end - end -end diff --git a/lib/active_merchant/billing/integrations/gestpay/return.rb b/lib/active_merchant/billing/integrations/gestpay/return.rb deleted file mode 100644 index 0c01f286eff..00000000000 --- a/lib/active_merchant/billing/integrations/gestpay/return.rb +++ /dev/null @@ -1,10 +0,0 @@ -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - module Gestpay - class Return < ActiveMerchant::Billing::Integrations::Return - end - end - end - end -end diff --git a/lib/active_merchant/billing/integrations/helper.rb b/lib/active_merchant/billing/integrations/helper.rb deleted file mode 100644 index dc742de5597..00000000000 --- a/lib/active_merchant/billing/integrations/helper.rb +++ /dev/null @@ -1,117 +0,0 @@ -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - class Helper #:nodoc: - attr_reader :fields - class_attribute :service_url - class_attribute :mappings - class_attribute :country_format - self.country_format = :alpha2 - - # The application making the calls to the gateway - # Useful for things like the PayPal build notation (BN) id fields - class_attribute :application_id - self.application_id = 'ActiveMerchant' - - def initialize(order, account, options = {}) - options.assert_valid_keys([:amount, :currency, :test, :credential2, :credential3, :credential4, :country, :account_name, :transaction_type]) - @fields = {} - @raw_html_fields = [] - @test = options[:test] - self.order = order - self.account = account - self.amount = options[:amount] - self.currency = options[:currency] - self.credential2 = options[:credential2] - self.credential3 = options[:credential3] - self.credential4 = options[:credential4] - end - - def self.mapping(attribute, options = {}) - self.mappings ||= {} - self.mappings[attribute] = options - end - - def add_field(name, value) - return if name.blank? || value.blank? - @fields[name.to_s] = value.to_s - end - - def add_fields(subkey, params = {}) - params.each do |k, v| - field = mappings[subkey][k] - add_field(field, v) unless field.blank? - end - end - - # Add a field that has characters that CGI::escape would mangle. Allows - # for multiple fields with the same name (e.g., to support line items). - def add_raw_html_field(name, value) - return if name.blank? || value.blank? - @raw_html_fields << [name, value] - end - - def raw_html_fields - @raw_html_fields - end - - def billing_address(params = {}) - add_address(:billing_address, params) - end - - def shipping_address(params = {}) - add_address(:shipping_address, params) - end - - def form_fields - @fields - end - - def test? - @test_mode ||= ActiveMerchant::Billing::Base.integration_mode == :test || @test - end - - def form_method - "POST" - end - - private - - def add_address(key, params) - return if mappings[key].nil? - - code = lookup_country_code(params.delete(:country)) - add_field(mappings[key][:country], code) - add_fields(key, params) - end - - def lookup_country_code(name_or_code, format = country_format) - country = Country.find(name_or_code) - country.code(format).to_s - rescue InvalidCountryCodeError - name_or_code - end - - def method_missing(method_id, *args) - method_id = method_id.to_s.gsub(/=$/, '').to_sym - # Return and do nothing if the mapping was not found. This allows - # For easy substitution of the different integrations - return if mappings[method_id].nil? - - mapping = mappings[method_id] - - case mapping - when Array - mapping.each{ |field| add_field(field, args.last) } - when Hash - options = args.last.is_a?(Hash) ? args.pop : {} - - mapping.each{ |key, field| add_field(field, options[key]) } - else - add_field(mapping, args.last) - end - end - end - end - end -end diff --git a/lib/active_merchant/billing/integrations/hi_trust.rb b/lib/active_merchant/billing/integrations/hi_trust.rb deleted file mode 100644 index b5be207bbd0..00000000000 --- a/lib/active_merchant/billing/integrations/hi_trust.rb +++ /dev/null @@ -1,27 +0,0 @@ - -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - module HiTrust - autoload :Helper, File.dirname(__FILE__) + '/hi_trust/helper.rb' - autoload :Return, File.dirname(__FILE__) + '/hi_trust/return.rb' - autoload :Notification, File.dirname(__FILE__) + '/hi_trust/notification.rb' - - TEST_URL = 'https://testtrustlink.hitrust.com.tw/TrustLink/TrxReqForJava' - LIVE_URL = 'https://trustlink.hitrust.com.tw/TrustLink/TrxReqForJava' - - def self.service_url - ActiveMerchant::Billing::Base.integration_mode == :test ? TEST_URL : LIVE_URL - end - - def self.notification(post, options = {}) - Notification.new(post) - end - - def self.return(query_string, options = {}) - Return.new(query_string) - end - end - end - end -end diff --git a/lib/active_merchant/billing/integrations/hi_trust/helper.rb b/lib/active_merchant/billing/integrations/hi_trust/helper.rb deleted file mode 100644 index a2867296033..00000000000 --- a/lib/active_merchant/billing/integrations/hi_trust/helper.rb +++ /dev/null @@ -1,58 +0,0 @@ -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - module HiTrust - class Helper < ActiveMerchant::Billing::Integrations::Helper - - # Transaction types - # * Auth - # * AuthRe - # * Capture - # * CaptureRe - # * Refund - # * RefundRe - # * Query - def initialize(order, account, options = {}) - super - # Perform an authorization by default - add_field('Type', 'Auth') - - # Capture the payment right away - add_field('depositflag', '1') - - # Disable auto query - who knows what it does? - add_field('queryflag', '1') - - add_field('orderdesc', 'Store purchase') - end - - mapping :account, 'storeid' - mapping :amount, 'amount' - - def amount=(money) - cents = money.respond_to?(:cents) ? money.cents : money - - if money.is_a?(String) or cents.to_i < 0 - raise ArgumentError, 'money amount must be either a Money object or a positive integer in cents.' - end - - add_field(mappings[:amount], cents) - end - # Supported currencies include: - # * CNY:Chinese Yuan (Renminbi) - # * TWD:New Taiwan Dollar - # * HKD:Hong Kong Dollar - # * USD:US Dollar - # * AUD:Austrian Dollar - mapping :currency, 'currency' - - mapping :order, 'ordernumber' - mapping :description, 'orderdesc' - - mapping :notify_url, 'merUpdateURL' - mapping :return_url, 'returnURL' - end - end - end - end -end \ No newline at end of file diff --git a/lib/active_merchant/billing/integrations/hi_trust/notification.rb b/lib/active_merchant/billing/integrations/hi_trust/notification.rb deleted file mode 100644 index 06a3d8f769d..00000000000 --- a/lib/active_merchant/billing/integrations/hi_trust/notification.rb +++ /dev/null @@ -1,59 +0,0 @@ -require 'net/http' - -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - module HiTrust - class Notification < ActiveMerchant::Billing::Integrations::Notification - SUCCESS = '00' - - self.production_ips = [ '203.75.242.8' ] - - def complete? - status == 'Completed' - end - - def transaction_id - params['authRRN'] - end - - def item_id - params['ordernumber'] - end - - def received_at - Time.parse(params['orderdate']) rescue nil - end - - def currency - params['currency'] - end - - def gross - sprintf("%.2f", gross_cents.to_f / 100) - end - - def gross_cents - params['approveamount'].to_i - end - - def account - params['storeid'] - end - - def status - params['retcode'] == SUCCESS ? 'Completed' : 'Failed' - end - - def test? - ActiveMerchant::Billing::Base.integration_mode == :test - end - - def acknowledge - true - end - end - end - end - end -end diff --git a/lib/active_merchant/billing/integrations/hi_trust/return.rb b/lib/active_merchant/billing/integrations/hi_trust/return.rb deleted file mode 100644 index 846013da6d8..00000000000 --- a/lib/active_merchant/billing/integrations/hi_trust/return.rb +++ /dev/null @@ -1,68 +0,0 @@ -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - module HiTrust - class Return < ActiveMerchant::Billing::Integrations::Return - SUCCESS = "00" - CODES = { "00" => "Operation completed successfully", - "-1" => "Unable to initialize winsock dll.", - "-2" => "Can't create stream socket.", - "-3" => "No Request Message.", - "-4" => "Can't connect to server.", - "-5" => "Send socket error.", - "-6" => "Couldn't receive data.", - "-7" => "Receive Broken message.", - "-8" => "Unable to initialize Envirnment.", - "-9" => "Can't Read Server RSA File.", - "-10" => "Can't Read Client RSA File.", - "-11" => "Web Server error.", - "-12" => "Receive Message type error.", - "-13" => "No Request Message.", - "-14" => "No Response Content.", - "-18" => "Merchant Update URL not found.", - "-19" => "Server URL not find Domain or IP.", - "-20" => "Server URL only can fill http or https.", - "-21" => "Server Config File open error.", - "-22" => "Server RSA Key File open error.", - "-23" => "Server RSA Key File read error.", - "-24" => "Server Config File have some errors, Please to check it.", - "-25" => "Merchant Config File open error.", - "-26" => "Merchant RSA Key File open error.", - "-27" => "Merchant RSA Key File read error.", - "-28" => "Merchant Config File has some errors, Please to check it.", - "-29" => "Server Type is unknown.", - "-30" => "Comm Type is unknown.", - "-31" => "Input Parameter [ORDERNO] is null or empty.", - "-32" => "Input Parameter [STOREID] is null or empty.", - "-33" => "Input Parameter [ORDERDESC] is null or empty.", - "-34" => "Input Parameter [CURRENCY] is null or empty.", - "-35" => "Input Parameter [AMOUNT] is null or empty.", - "-36" => "Input Parameter [ORDERURL] is null or empty.", - "-37" => "Input Parameter [RETURNURL] is null or empty.", - "-38" => "Input Parameter [DEPOSIT] is null or empty.", - "-39" => "Input Parameter [QUERYFLAG] is null or empty.", - "-40" => "Input Parameter [UPDATEURL] is null or empty.", - "-41" => "Input Parameter [MERUPDATEURL] is null or empty.", - "-42" => "Input Parameter [KEY] is null or empty.", - "-43" => "Input Parameter [MAC] is null or empty.", - "-44" => "Input Parameter [CIPHER] is null or empty.", - "-45" => "Input Parameter [TrxType] is wrong.", - "-100" => "TrustLink Server is closed. Or Merchant Server IP is not consistent with TrustLink Server setting.", - "-101" => "TrustLink Server receives NULL.", - "-308" => "Order Number already exists.", - "positive" => "Response from Bank. Please contact with Acquirer Bank Service or HiTRUST Call Center." - } - - def success? - params['retcode'] == SUCCESS - end - - def message - return CODES["positive"] if params['retcode'].to_i > 0 - CODES[ params['retcode'] ] - end - end - end - end - end -end diff --git a/lib/active_merchant/billing/integrations/ipay88.rb b/lib/active_merchant/billing/integrations/ipay88.rb deleted file mode 100644 index cc6b77f2c01..00000000000 --- a/lib/active_merchant/billing/integrations/ipay88.rb +++ /dev/null @@ -1,19 +0,0 @@ -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - module Ipay88 - autoload :Return, "active_merchant/billing/integrations/ipay88/return.rb" - autoload :Helper, "active_merchant/billing/integrations/ipay88/helper.rb" - autoload :Notification, "active_merchant/billing/integrations/ipay88/notification.rb" - - def self.service_url - "https://www.mobile88.com/epayment/entry.asp" - end - - def self.return(query_string, options={}) - Return.new(query_string, options) - end - end - end - end -end diff --git a/lib/active_merchant/billing/integrations/ipay88/helper.rb b/lib/active_merchant/billing/integrations/ipay88/helper.rb deleted file mode 100644 index 123f660358b..00000000000 --- a/lib/active_merchant/billing/integrations/ipay88/helper.rb +++ /dev/null @@ -1,114 +0,0 @@ -require "digest/sha1" - -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - module Ipay88 - class Helper < ActiveMerchant::Billing::Integrations::Helper - include RequiresParameters - - # Currencies supported - # MYR (Malaysian Ringgit - for all payment methods except China Union Pay and PayPal) - # USD (US Dollar - only for PayPal) - # CNY (Yuan Renminbi - only for China Union Pay) - SUPPORTED_CURRENCIES = %w[MYR USD CNY] - - # Languages supported - # ISO-8859-1 (English) - # UTF-8 (Unicode) - # GB2312 (Chinese Simplified) - # GD18030 (Chinese Simplified) - # BIG5 (Chinese Traditional) - SUPPORTED_LANGS = %w[ISO-8859-1 UTF-8 GB2312 GD18030 BIG5] - - # Payment methods supported - # 8 (Alliance Online Transfer) - # 10 (AmBank) - # 21 (China Union Pay) - # 20 (CIMB Click) - # 2 (Credit Card MYR) - # 16 (FPX) - # 15 (Hong Leong Bank Transfer) - # 6 (Maybank2u.com) - # 23 (MEPS Cash) - # 17 (Mobile Money) - # 33 (PayPal) - # 14 (RHB) - PAYMENT_METHODS = %w[8 10 21 20 2 16 15 6 23 17 33 14] - - attr_reader :amount_in_cents, :merchant_key - - def initialize(order, account, options = {}) - requires!(options, :amount, :currency, :credential2) - @merchant_key = options[:credential2] - @amount_in_cents = options[:amount] - super - add_field mappings[:signature], signature - end - - def amount=(money) - @amount_in_cents = money.respond_to?(:cents) ? money.cents : money - if money.is_a?(String) or @amount_in_cents.to_i < 0 - raise ArgumentError, "money amount must be either a Money object or a positive integer in cents." - end - add_field mappings[:amount], sprintf("%.2f", @amount_in_cents.to_f/100) - end - - def currency(symbol) - raise ArgumentError, "unsupported currency" unless SUPPORTED_CURRENCIES.include?(symbol) - add_field mappings[:currency], symbol - end - - def language(lang) - raise ArgumentError, "unsupported language" unless SUPPORTED_LANGS.include?(lang) - add_field mappings[:language], lang - end - - def payment(pay_method) - raise ArgumentError, "unsupported payment method" unless PAYMENT_METHODS.include?(pay_method.to_s) - add_field mappings[:payment], pay_method - end - - def customer(params = {}) - add_field(mappings[:customer][:name], "#{params[:first_name]} #{params[:last_name]}") - add_field(mappings[:customer][:email], params[:email]) - add_field(mappings[:customer][:phone], params[:phone]) - end - - def self.sign(str) - [Digest::SHA1.digest(str)].pack("m").chomp - end - - def signature - self.class.sign(self.sig_components) - end - - mapping :account, "MerchantCode" - mapping :amount, "Amount" - mapping :currency, "Currency" - mapping :order, "RefNo" - mapping :description, "ProdDesc" - mapping :customer, :name => "UserName", - :email => "UserEmail", - :phone => "UserContact" - mapping :remark, "Remark" - mapping :language, "Lang" - mapping :payment, "PaymentId" - mapping :return_url, "ResponseURL" - mapping :signature, "Signature" - - protected - - def sig_components - components = [merchant_key] - components << fields[mappings[:account]] - components << fields[mappings[:order]] - components << amount_in_cents.to_s.gsub(/[.,]/, '') - components << fields[mappings[:currency]] - components.join - end - end - end - end - end -end diff --git a/lib/active_merchant/billing/integrations/ipay88/return.rb b/lib/active_merchant/billing/integrations/ipay88/return.rb deleted file mode 100644 index 14e1bc8334b..00000000000 --- a/lib/active_merchant/billing/integrations/ipay88/return.rb +++ /dev/null @@ -1,95 +0,0 @@ -require "digest/sha1" - -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - module Ipay88 - class Return < ActiveMerchant::Billing::Integrations::Return - include ActiveMerchant::PostsData - - def account - params["MerchantCode"] - end - - def payment - params["PaymentId"].to_i - end - - def order - params["RefNo"] - end - - def amount - params["Amount"] - end - - def currency - params["Currency"] - end - - def remark - params["Remark"] - end - - def transaction - params["TransId"] - end - - def auth_code - params["AuthCode"] - end - - def status - params["Status"] - end - - def error - params["ErrDesc"] - end - - def signature - params["Signature"] - end - - def secure? - self.generated_signature == self.signature - end - - def success? - self.secure? && self.requery == "00" && self.status == "1" - end - - protected - - def generated_signature - Helper.sign(self.sig_components) - end - - def sig_components - components = [@options[:credential2]] - [:account, :payment, :order, :amount_in_cents, :currency, :status].each do |i| - components << self.send(i) - end - components.join - end - - def requery - data = { "MerchantCode" => self.account, "RefNo" => self.order, "Amount" => self.amount } - params = parameterize(data) - ssl_post Ipay88.service_url, params, { "Content-Length" => params.size.to_s, "User-Agent" => "Active Merchant -- http://activemerchant.org" } - end - - private - - def parameterize(params) - params.reject { |k, v| v.blank? }.keys.sort.collect { |key| "#{key}=#{CGI.escape(params[key].to_s)}" }.join("&") - end - - def amount_in_cents - @amount_in_cents ||= (self.amount || "").gsub(/[.,]/, "") - end - end - end - end - end -end diff --git a/lib/active_merchant/billing/integrations/liqpay.rb b/lib/active_merchant/billing/integrations/liqpay.rb deleted file mode 100644 index f43e3220ebb..00000000000 --- a/lib/active_merchant/billing/integrations/liqpay.rb +++ /dev/null @@ -1,30 +0,0 @@ -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - # Documentation: https://www.liqpay.com/?do=pages&p=cnb10 - module Liqpay - autoload :Helper, File.dirname(__FILE__) + '/liqpay/helper.rb' - autoload :Notification, File.dirname(__FILE__) + '/liqpay/notification.rb' - autoload :Return, File.dirname(__FILE__) + '/liqpay/return.rb' - - mattr_accessor :service_url - self.service_url = 'https://liqpay.com/?do=clickNbuy' - - mattr_accessor :signature_parameter_name - self.signature_parameter_name = 'signature' - - def self.helper(order, account, options = {}) - Helper.new(order, account, options) - end - - def self.notification(query_string, options = {}) - Notification.new(query_string, options) - end - - def self.return(query_string) - Return.new(query_string) - end - end - end - end -end diff --git a/lib/active_merchant/billing/integrations/liqpay/helper.rb b/lib/active_merchant/billing/integrations/liqpay/helper.rb deleted file mode 100644 index 89d158c68ab..00000000000 --- a/lib/active_merchant/billing/integrations/liqpay/helper.rb +++ /dev/null @@ -1,43 +0,0 @@ -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - module Liqpay - class Helper < ActiveMerchant::Billing::Integrations::Helper - def initialize(order, account, options = {}) - @secret = options.delete(:secret) - super - - add_field 'version', '1.2' - end - - def form_fields - xml = "<request> - <version>1.2</version> - <result_url>#{@fields["result_url"]}</result_url> - <server_url>#{@fields["server_url"]}</server_url> - <merchant_id>#{@fields["merchant_id"]}</merchant_id> - <order_id>#{@fields["order_id"]}</order_id> - <amount>#{@fields["amount"]}</amount> - <currency>#{@fields["currency"]}</currency> - <description>#{@fields["description"]}</description> - <default_phone>#{@fields["default_phone"]}</default_phone> - <pay_way>card</pay_way> - </request>".strip - sign = Base64.encode64(Digest::SHA1.digest("#{@secret}#{xml}#{@secret}")).strip - {"operation_xml" => Base64.encode64(xml), "signature" => sign} - end - - mapping :account, 'merchant_id' - mapping :amount, 'amount' - mapping :currency, 'currency' - mapping :order, 'order_id' - mapping :description, 'description' - mapping :phone, 'default_phone' - - mapping :notify_url, 'server_url' - mapping :return_url, 'result_url' - end - end - end - end -end diff --git a/lib/active_merchant/billing/integrations/liqpay/notification.rb b/lib/active_merchant/billing/integrations/liqpay/notification.rb deleted file mode 100644 index 2cfb7a26192..00000000000 --- a/lib/active_merchant/billing/integrations/liqpay/notification.rb +++ /dev/null @@ -1,89 +0,0 @@ -require 'net/http' - -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - module Liqpay - class Notification < ActiveMerchant::Billing::Integrations::Notification - def self.recognizes?(params) - params.has_key?('amount') && params.has_key?('order_id') - end - - def initialize(post, options = {}) - raise ArgumentError if post.blank? - super - @params.merge!(Hash.from_xml(Base64.decode64(xml))["response"]) - end - - def xml - @params["operation_xml"] - end - - def complete? - status == 'success' - end - - def account - params['merchant_id'] - end - - def amount - BigDecimal.new(gross) - end - - def item_id - params['order_id'] - end - - def transaction_id - params['transaction_id'] - end - - def action_name - params['action_name'] # either 'result_url' or 'server_url' - end - - def version - params['version'] - end - - def sender_phone - params['sender_phone'] - end - - def security_key - params[ActiveMerchant::Billing::Integrations::Liqpay.signature_parameter_name] - end - - def gross - params['amount'] - end - - def currency - params['currency'] - end - - def status - params['status'] # 'success', 'failure' or 'wait_secure' - end - - def code - params['code'] - end - - def generate_signature_string - "#{@options[:secret]}#{Base64.decode64(xml)}#{@options[:secret]}" - end - - def generate_signature - Base64.encode64(Digest::SHA1.digest(generate_signature_string)).strip - end - - def acknowledge - security_key == generate_signature - end - end - end - end - end -end diff --git a/lib/active_merchant/billing/integrations/liqpay/return.rb b/lib/active_merchant/billing/integrations/liqpay/return.rb deleted file mode 100644 index 200862d2572..00000000000 --- a/lib/active_merchant/billing/integrations/liqpay/return.rb +++ /dev/null @@ -1,83 +0,0 @@ -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - module Liqpay - class Return < ActiveMerchant::Billing::Integrations::Return - def self.recognizes?(params) - params.has_key?('amount') && params.has_key?('order_id') - end - - def initialize(post) - super - xml = Base64.decode64(@params["operation_xml"]) - @params.merge!(Hash.from_xml(xml)["response"]) - end - - def complete? - status == 'success' - end - - def account - params['merchant_id'] - end - - def amount - BigDecimal.new(gross) - end - - def item_id - params['order_id'] - end - - def transaction_id - params['transaction_id'] - end - - def action_name - params['action_name'] # either 'result_url' or 'server_url' - end - - def version - params['version'] - end - - def sender_phone - params['sender_phone'] - end - - def security_key - params[ActiveMerchant::Billing::Integrations::Liqpay.signature_parameter_name] - end - - def gross - params['amount'] - end - - def currency - params['currency'] - end - - def status - params['status'] # 'success', 'failure' or 'wait_secure' - end - - def code - params['code'] - end - - def generate_signature_string - ['', version, @options[:secret], action_name, sender_phone, account, gross, currency, item_id, transaction_id, status, code, ''].flatten.compact.join('|') - end - - def generate_signature - Base64.encode64(Digest::SHA1.digest(generate_signature_string)).gsub(/\n/, '') - end - - def acknowledge - security_key == generate_signature - end - end - end - end - end -end diff --git a/lib/active_merchant/billing/integrations/maksuturva.rb b/lib/active_merchant/billing/integrations/maksuturva.rb deleted file mode 100644 index 1bb12bf6480..00000000000 --- a/lib/active_merchant/billing/integrations/maksuturva.rb +++ /dev/null @@ -1,86 +0,0 @@ -require File.dirname(__FILE__) + '/maksuturva/helper.rb' -require File.dirname(__FILE__) + '/maksuturva/notification.rb' - -# USAGE: -# -# First define Maksuturva seller id and authcode in an initializer: -# -# MAKSUTURVA_SELLERID = "testikauppias" -# MAKSUTURVA_AUTHCODE = "11223344556677889900" -# -# Then in view do something like this (use dynamic values for your app) -# -# <% payment_service_for 2, MAKSUTURVA_SELLERID, -# :amount => "200,00", :currency => 'EUR', :credential2 => MAKSUTURVA_AUTHCODE, -# :service => :maksuturva do |service| -# service.pmt_reference = "134662" -# service.pmt_duedate = "24.06.2012" -# service.customer :phone => "0405051909", -# :email => "antti@example.com" -# service.billing_address :city => "Helsinki", -# :address1 => "Lorem street", -# :state => "-", -# :country => 'Finland', -# :zip => "00530" -# service.pmt_orderid = "2" -# service.pmt_buyername = "Antti Akonniemi" -# service.pmt_deliveryname = "Antti Akonniemi" -# service.pmt_deliveryaddress = "Köydenpunojankatu 13" -# service.pmt_deliverypostalcode = "00180" -# service.pmt_deliverycity = "Helsinki" -# service.pmt_deliverycountry = "FI" -# service.pmt_rows = 1 -# service.pmt_row_name1 = "testi" -# service.pmt_row_desc1 = "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum." -# service.pmt_row_articlenr1 = "1" -# service.pmt_row_quantity1 = "1" -# service.pmt_row_deliverydate1 = "26.6.2012" -# service.pmt_row_price_gross1 = "200,00" -# service.pmt_row_vat1= "23,00" -# service.pmt_row_discountpercentage1 = "0,00" -# service.pmt_row_type1 = "1" -# service.pmt_charset = "UTF-8" -# service.pmt_charsethttp = "UTF-8" -# -# service.return_url "http://localhost:3000/process" -# service.cancel_return_url "http://example.com" -# service.pmt_errorreturn "http://example.com" -# -# service.pmt_delayedpayreturn "http://example.com" -# service.pmt_escrow "N" -# service.pmt_escrowchangeallowed "N" -# service.pmt_sellercosts "0,00" -# service.pmt_keygeneration "001" -# %> -# -# Then in the controller handle the return with something like this -# -# def ipn -# notify = ActiveMerchant::Billing::Integrations::Maksuturva::Notification.new(params) -# -# if notify.acknowledge(MAKSUTURVA_AUTHCODE) -# # Process order -# else -# # Show error -# end -# end -# -# For full list of available parameters etc check the integration documents -# here: -# -# https://www.maksuturva.fi/services/vendor_services/integration_guidelines.html - -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - module Maksuturva - mattr_accessor :service_url - self.service_url = 'https://www.maksuturva.fi/NewPaymentExtended.pmt' - - def self.notification(post) - Notification.new(post) - end - end - end - end -end diff --git a/lib/active_merchant/billing/integrations/maksuturva/helper.rb b/lib/active_merchant/billing/integrations/maksuturva/helper.rb deleted file mode 100644 index 4d6128cff21..00000000000 --- a/lib/active_merchant/billing/integrations/maksuturva/helper.rb +++ /dev/null @@ -1,119 +0,0 @@ -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - module Maksuturva - class Helper < ActiveMerchant::Billing::Integrations::Helper - def initialize(order, account, options = {}) - md5secret options.delete(:credential2) - super - add_field("pmt_action", "NEW_PAYMENT_EXTENDED") - add_field("pmt_version", "0004") - add_field("pmt_sellerid", account) - add_field("pmt_hashversion", "MD5") - end - - def md5secret(value) - @md5secret = value - end - - def form_fields - @fields.merge("pmt_hash" => generate_md5string) - end - - def generate_md5string - fields = [@fields["pmt_action"], @fields["pmt_version"]] - fields += [@fields["pmt_selleriban"]] unless @fields["pmt_selleriban"].nil? - fields += [@fields["pmt_id"], @fields["pmt_orderid"], @fields["pmt_reference"], @fields["pmt_duedate"], - @fields["pmt_amount"], @fields["pmt_currency"], @fields["pmt_okreturn"], @fields["pmt_errorreturn"], @fields["pmt_cancelreturn"], - @fields["pmt_delayedpayreturn"], @fields["pmt_escrow"], @fields["pmt_escrowchangeallowed"]] - - fields += [@fields["pmt_invoicefromseller"]] unless @fields["pmt_invoicefromseller"].nil? - fields += [@fields["pmt_paymentmethod"]] unless @fields["pmt_paymentmethod"].nil? - fields += [@fields["pmt_buyeridentificationcode"]] unless @fields["pmt_buyeridentificationcode"].nil? - - - fields += [@fields["pmt_buyername"], @fields["pmt_buyeraddress"], @fields["pmt_buyerpostalcode"], @fields["pmt_buyercity"], - @fields["pmt_buyercountry"], @fields["pmt_deliveryname"], @fields["pmt_deliveryaddress"], @fields["pmt_deliverypostalcode"], @fields["pmt_deliverycity"], - @fields["pmt_deliverycountry"], @fields["pmt_sellercosts"]] - - (1..@fields["pmt_rows"].to_i).each do |i| - fields += [@fields["pmt_row_name#{i}"], @fields["pmt_row_desc#{i}"], @fields["pmt_row_quantity#{i}"]] - fields += [@fields["pmt_row_articlenr#{i}"]] unless @fields["pmt_row_articlenr#{i}"].nil? - fields += [@fields["pmt_row_unit#{i}"]] unless @fields["pmt_row_unit#{i}"].nil? - fields += [@fields["pmt_row_deliverydate#{i}"]] - fields += [@fields["pmt_row_price_gross#{i}"]] unless @fields["pmt_row_price_gross#{i}"].nil? - fields += [@fields["pmt_row_price_net#{i}"]] unless @fields["pmt_row_price_net#{i}"].nil? - fields += [@fields["pmt_row_vat#{i}"], @fields["pmt_row_discountpercentage#{i}"], @fields["pmt_row_type#{i}"]] - end - fields += [@md5secret] - fields = fields.join("&") + "&" - Digest::MD5.hexdigest(fields).upcase - end - - mapping :pmt_selleriban, "pmt_selleriban" - mapping :pmt_reference, "pmt_reference" - mapping :pmt_duedate, "pmt_duedate" - mapping :pmt_userlocale, "pmt_userlocale" - mapping :pmt_escrow, "pmt_escrow" - mapping :pmt_escrowchangeallowed, "pmt_escrowchangeallowed" - mapping :pmt_invoicefromseller, "pmt_invoicefromseller" - mapping :pmt_paymentmethod, "pmt_paymentmethod" - mapping :pmt_buyeridentificationcode, "pmt_buyeridentificationcode" - mapping :pmt_buyername, "pmt_buyername" - - mapping :account, '' - mapping :currency, 'pmt_currency' - mapping :amount, 'pmt_amount' - - mapping :order, 'pmt_id' - mapping :pmt_orderid, 'pmt_orderid' - mapping :pmt_deliveryname, "pmt_deliveryname" - mapping :pmt_deliveryaddress, "pmt_deliveryaddress" - mapping :pmt_deliverypostalcode, "pmt_deliverypostalcode" - mapping :pmt_deliverycity, "pmt_deliverycity" - mapping :pmt_deliverycountry, "pmt_deliverycountry" - mapping :pmt_sellercosts, "pmt_sellercosts" - mapping :pmt_rows, "pmt_rows" - - (1..499.to_i).each do |i| - mapping "pmt_row_name#{i}".to_sym, "pmt_row_name#{i}" - mapping "pmt_row_desc#{i}".to_sym, "pmt_row_desc#{i}" - mapping "pmt_row_quantity#{i}".to_sym, "pmt_row_quantity#{i}" - mapping "pmt_row_articlenr#{i}".to_sym, "pmt_row_articlenr#{i}" - mapping "pmt_row_unit#{i}".to_sym, "pmt_row_unit#{i}" - mapping "pmt_row_deliverydate#{i}".to_sym, "pmt_row_deliverydate#{i}" - mapping "pmt_row_price_gross#{i}".to_sym, "pmt_row_price_gross#{i}" - mapping "pmt_row_price_net#{i}".to_sym, "pmt_row_price_net#{i}" - mapping "pmt_row_vat#{i}".to_sym, "pmt_row_vat#{i}" - mapping "pmt_row_discountpercentage#{i}".to_sym, "pmt_row_discountpercentage#{i}" - mapping "pmt_row_type#{i}".to_sym, "pmt_row_type#{i}" - end - - mapping :pmt_charset, "pmt_charset" - mapping :pmt_charsethttp, "pmt_charsethttp" - mapping :pmt_hashversion, "pmt_hashversion" - mapping :pmt_keygeneration, "pmt_keygeneration" - mapping :customer, :email => 'pmt_buyeremail', - :phone => 'pmt_buyerphone' - - mapping :billing_address, :city => 'pmt_buyercity', - :address1 => "pmt_buyeraddress", - :address2 => '', - :state => '', - :zip => "pmt_buyerpostalcode", - :country => 'pmt_buyercountry' - - mapping :notify_url, '' - mapping :return_url, 'pmt_okreturn' - mapping :pmt_errorreturn, 'pmt_errorreturn' - mapping :pmt_delayedpayreturn, 'pmt_delayedpayreturn' - mapping :cancel_return_url, 'pmt_cancelreturn' - - mapping :description, '' - mapping :tax, '' - mapping :shipping, '' - end - end - end - end -end diff --git a/lib/active_merchant/billing/integrations/maksuturva/notification.rb b/lib/active_merchant/billing/integrations/maksuturva/notification.rb deleted file mode 100644 index ed7cdd2971d..00000000000 --- a/lib/active_merchant/billing/integrations/maksuturva/notification.rb +++ /dev/null @@ -1,48 +0,0 @@ -require "net/http" - -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - module Maksuturva - class Notification < ActiveMerchant::Billing::Integrations::Notification - def complete? - true - end - - def transaction_id - params["pmt_id"] - end - - def security_key - params["pmt_hash"] - end - - def gross - params["pmt_amount"] - end - - def currency - params["pmt_currency"] - end - - def status - "PAID" - end - - def acknowledge(authcode) - return_authcode = [params["pmt_action"], params["pmt_version"], params["pmt_id"], params["pmt_reference"], params["pmt_amount"], params["pmt_currency"], params["pmt_sellercosts"], params["pmt_paymentmethod"], params["pmt_escrow"], authcode].join("&") - (Digest::MD5.hexdigest(return_authcode + "&").upcase == params["pmt_hash"]) - end - - private - - def parse(post) - post.each do |key, value| - params[key] = value - end - end - end - end - end - end -end diff --git a/lib/active_merchant/billing/integrations/moneybookers.rb b/lib/active_merchant/billing/integrations/moneybookers.rb deleted file mode 100644 index 44e034e10f0..00000000000 --- a/lib/active_merchant/billing/integrations/moneybookers.rb +++ /dev/null @@ -1,26 +0,0 @@ -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - module Moneybookers - - autoload :Notification, File.dirname(__FILE__) + '/moneybookers/notification.rb' - autoload :Helper, File.dirname(__FILE__) + '/moneybookers/helper.rb' - - mattr_accessor :production_url - self.production_url = 'https://www.moneybookers.com/app/payment.pl' - - def self.service_url - self.production_url - end - - def self.notification(post, options) - Notification.new(post, options) - end - - def self.return(post, options = {}) - Return.new(post, options) - end - end - end - end -end diff --git a/lib/active_merchant/billing/integrations/moneybookers/helper.rb b/lib/active_merchant/billing/integrations/moneybookers/helper.rb deleted file mode 100644 index 47ae9464835..00000000000 --- a/lib/active_merchant/billing/integrations/moneybookers/helper.rb +++ /dev/null @@ -1,75 +0,0 @@ -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - module Moneybookers - class Helper < ActiveMerchant::Billing::Integrations::Helper - mapping :account, 'pay_to_email' - mapping :order, 'transaction_id' - mapping :amount, 'amount' - mapping :currency, 'currency' - - mapping :customer, - :first_name => 'firstname', - :last_name => 'lastname', - :email => 'pay_from_email', - :phone => 'phone_number' - - mapping :billing_address, - :city => 'city', - :address1 => 'address', - :address2 => 'address2', - :state => 'state', - :zip => 'postal_code', - :country => 'country' - - mapping :notify_url, 'status_url' - mapping :return_url, 'return_url' - mapping :cancel_return_url, 'cancel_url' - mapping :description, 'detail1_text' - - MAPPED_COUNTRY_CODES = { - 'SE' => 'SV', - 'DK' => 'DA' - } - - SUPPORTED_COUNTRY_CODES = [ - 'FI', 'DE', 'ES', 'FR', - 'IT','PL', 'GR', 'RO', - 'RU', 'TR', 'CN', 'CZ', 'NL' - ] - - def initialize(order, account, options = {}) - super - add_tracking_token - add_default_parameters - add_seller_details(options) - end - - private - - def add_tracking_token - return if application_id.blank? || application_id == 'ActiveMerchant' - - add_field('merchant_fields', 'platform') - add_field('platform', application_id) - end - - def add_default_parameters - add_field('hide_login', 1) - end - - def add_seller_details(options) - add_field('recipient_description', options[:account_name]) if options[:account_name] - add_field('country', lookup_country_code(options[:country], :alpha3)) if options[:country] - add_field('language', locale_code(options[:country])) if options[:country] - end - - def locale_code(country_code) - return country_code if SUPPORTED_COUNTRY_CODES.include?(country_code) - MAPPED_COUNTRY_CODES[country_code] || 'EN' - end - end - end - end - end -end diff --git a/lib/active_merchant/billing/integrations/moneybookers/notification.rb b/lib/active_merchant/billing/integrations/moneybookers/notification.rb deleted file mode 100644 index 5d25b4cba89..00000000000 --- a/lib/active_merchant/billing/integrations/moneybookers/notification.rb +++ /dev/null @@ -1,129 +0,0 @@ -require 'net/http' -require 'digest/md5' - -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - module Moneybookers - class Notification < ActiveMerchant::Billing::Integrations::Notification - - def initialize(data, options) - if options[:credential2].nil? - raise ArgumentError, "You need to provide the md5 secret as the option :credential2 to verify that the notification originated from Moneybookers" - end - super - end - - def complete? - status == 'Completed' - end - - # ‘2’ Processed – This status is sent when the transaction is processed and the funds have been received on your Moneybookers account. - # ‘0’ Pending – This status is sent when the customers pays via the pending bank transfer option. Such transactions will auto-process IF the bank transfer is received by Moneybookers. We strongly recommend that you do NOT process the order/transaction in your system upon receipt of a pending status from Moneybookers. - # ‘-1’ Cancelled – Pending transactions can either be cancelled manually by the sender in their online account history or they will auto-cancel after 14 days if still pending. - # ‘-2’ Failed – This status is sent when the customer tries to pay via Credit Card or Direct Debit but our provider declines the transaction. If you do not accept Credit Card or Direct Debit payments via Moneybookers (see page 17) then you will never receive the failed status. - # ‘-3’ Chargeback – This status could be received only if your account is configured to receive chargebacks. If this is the case, whenever a chargeback is received by Moneybookers, a -3 status will be posted on the status_url for the reversed transaction. - def status - case status_code - when '2' - 'Completed' - when '0' - 'Pending' - when '-1' - 'Cancelled' - when '-2' - 'Failed' - when '-3' - 'Reversed' - else - 'Error' - end - end - - def status_code - params['status'] - end - - def item_id - params['transaction_id'] - end - - def transaction_id - params['mb_transaction_id'] - end - - # When was this payment received by the client. - def received_at - nil - end - - def payer_email - params['pay_from_email'] - end - - def receiver_email - params['pay_to_email'] - end - - def md5sig - params['md5sig'] - end - - #Unique ID from the merchant's Moneybookers.com account, needed for calculatinon of md5 sig - def merchant_id - params['merchant_id'] - end - - # currency of the payment as posted by the merchant on the entry form - def currency - params['currency'] - end - - # amount of the payment as posted by the merchant on the entry form (ex. 39.60/39.6/39) - def gross - params['amount'] - end - - # currency of mb_amount, will always be the same as the currency of the beneficiary's account at Moneybookers.com - def merchant_currency - params['mb_currency'] - end - - # total amount of the payment in Merchants currency (ex 25.46/25.4/25) - def merchant_amount - params['mb_amount'] - end - - # Was this a test transaction? - def test? - false - end - - def secret - @options[:credential2] - end - - # Acknowledge the transaction to MoneyBooker. This method has to be called after a new - # apc arrives. It will verify that all the information we received is correct and will return a - # ok or a fail. The secret (second credential) has to be provided in the parameter :credential2 - # when instantiating the Notification object. - # - # Example: - # - # def ipn - # notify = Moneybookers.notification(request.raw_post, :credential2 => 'secret') - # - # if notify.acknowledge - # ... process order ... if notify.complete? - # else - # ... log possible hacking attempt ... - # end - def acknowledge - fields = [merchant_id, item_id, Digest::MD5.hexdigest(secret).upcase, merchant_amount, merchant_currency, status_code].join - md5sig == Digest::MD5.hexdigest(fields).upcase - end - end - end - end - end -end diff --git a/lib/active_merchant/billing/integrations/nochex.rb b/lib/active_merchant/billing/integrations/nochex.rb deleted file mode 100644 index ff3dce6d931..00000000000 --- a/lib/active_merchant/billing/integrations/nochex.rb +++ /dev/null @@ -1,88 +0,0 @@ - -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - # To start with Nochex, follow the instructions for installing - # ActiveMerchant as a plugin, as described on - # http://www.activemerchant.org/. - # - # The plugin will automatically add the ActionView helper for - # ActiveMerchant, which will allow you to make the Nochex payments. - # The idea behind the helper is that it generates an invisible - # forwarding screen that will automatically redirect the user. - # So you would collect all the information about the order and then - # simply render the hidden form, which redirects the user to Nochex. - # - # The syntax of the helper is as follows: - # - # <% payment_service_for 'order id', 'nochex_user_id', - # :amount => 50.00, - # :service => :nochex, - # :html => { :id => 'nochex-form' } do |service| %> - # - # <% service.customer :first_name => 'Cody', - # :last_name => 'Fauser', - # :phone => '(555)555-5555', - # :email => 'cody@example.com' %> - # - # <% service.billing_address :city => 'Ottawa', - # :address1 => '21 Snowy Brook Lane', - # :address2 => 'Apt. 36', - # :state => 'ON', - # :country => 'CA', - # :zip => 'K1J1E5' %> - # - # <% service.invoice '#1000' %> - # <% service.shipping '0.00' %> - # <% service.tax '0.00' %> - # - # <% service.notify_url url_for(:action => 'notify', :only_path => false) %> - # <% service.return_url url_for(:action => 'done', :only_path => false) %> - # <% service.cancel_return_url 'http://mystore.com' %> - # <% end %> - # - # The notify_url is the URL that the Nochex IPN will be sent. You can - # handle the notification in your controller action as follows: - # - # class NotificationController < ApplicationController - # include ActiveMerchant::Billing::Integrations - # - # def notify - # notification = Nochex::Notification.new(request.raw_post) - # - # begin - # # Acknowledge notification with Nochex - # raise StandardError, 'Illegal Notification' unless notification.acknowledge - # # Process the payment - # rescue => e - # logger.warn("Illegal notification received: #{e.message}") - # ensure - # head(:ok) - # end - # end - # end - module Nochex - autoload :Return, File.dirname(__FILE__) + '/nochex/return.rb' - autoload :Helper, File.dirname(__FILE__) + '/nochex/helper.rb' - autoload :Notification, File.dirname(__FILE__) + '/nochex/notification.rb' - - - mattr_accessor :service_url - self.service_url = 'https://secure.nochex.com' - - mattr_accessor :notification_confirmation_url - self.notification_confirmation_url = 'https://www.nochex.com/nochex.dll/apc/apc' - - # Simply a convenience method that returns a new - # ActiveMerchant::Billing::Integrations::Nochex::Notification - def self.notification(post, options = {}) - Notification.new(post) - end - - def self.return(query_string, options = {}) - Return.new(query_string) - end - end - end - end -end \ No newline at end of file diff --git a/lib/active_merchant/billing/integrations/nochex/helper.rb b/lib/active_merchant/billing/integrations/nochex/helper.rb deleted file mode 100644 index e61948021f4..00000000000 --- a/lib/active_merchant/billing/integrations/nochex/helper.rb +++ /dev/null @@ -1,68 +0,0 @@ -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - module Nochex - class Helper < ActiveMerchant::Billing::Integrations::Helper - # Required Parameters - # email - # amount - mapping :account, 'email' - mapping :amount, 'amount' - - # Set the field status = test for testing with accounts: - # Account Password - # test1@nochex.com 123456 - # test2@nochex.com 123456 - # def initialize(order, account, options = {}) - # super - # add_field('status', 'test') - # end - - # Need to format the amount to have 2 decimal places - def amount=(money) - cents = money.respond_to?(:cents) ? money.cents : money - if money.is_a?(String) or cents.to_i <= 0 - raise ArgumentError, 'money amount must be either a Money object or a positive integer in cents.' - end - add_field mappings[:amount], sprintf("%.2f", cents.to_f/100) - end - - # Optional Parameters - # ordernumber - mapping :order, 'ordernumber' - - # firstname - # lastname - # email_address_sender - mapping :customer, :first_name => 'firstname', - :last_name => 'lastname', - :email => 'email_address_sender' - - # town - # firstline - # county - # postcode - mapping :billing_address, :city => 'town', - :address1 => 'firstline', - :state => 'county', - :zip => 'postcode' - - # responderurl - mapping :notify_url, 'responderurl' - - # returnurl - mapping :return_url, 'returnurl' - - # cancelurl - mapping :cancel_return_url, 'cancelurl' - - # description - mapping :description, 'description' - - # Currently unmapped - # logo - end - end - end - end -end diff --git a/lib/active_merchant/billing/integrations/nochex/notification.rb b/lib/active_merchant/billing/integrations/nochex/notification.rb deleted file mode 100644 index cf9334b3de4..00000000000 --- a/lib/active_merchant/billing/integrations/nochex/notification.rb +++ /dev/null @@ -1,94 +0,0 @@ -require 'net/http' -require 'date' - -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - module Nochex - # Parser and handler for incoming Automatic Payment Confirmations from Nochex. - class Notification < ActiveMerchant::Billing::Integrations::Notification - include ActiveMerchant::PostsData - - def complete? - status == 'Completed' - end - - # Id of the order we passed to Nochex - def item_id - params['order_id'] - end - - def transaction_id - params['transaction_id'] - end - - def currency - 'GBP' - end - - # When was this payment received by the client. - def received_at - # U.K. Format: 27/09/2006 22:30:54 - return if params['transaction_date'].blank? - time = params['transaction_date'].scan(/\d+/) - Time.utc(time[2], time[1], time[0], time[3], time[4], time[5]) - end - - def payer_email - params['from_email'] - end - - def receiver_email - params['to_email'] - end - - def security_key - params['security_key'] - end - - # the money amount we received in X.2 decimal. - def gross - sprintf("%.2f", params['amount'].to_f) - end - - # Was this a test transaction? - def test? - params['status'] == 'test' - end - - def status - 'Completed' - end - - # Acknowledge the transaction to Nochex. This method has to be called after a new - # apc arrives. Nochex will verify that all the information we received are correct and will return a - # ok or a fail. This is very similar to the PayPal IPN scheme. - # - # Example: - # - # def nochex_ipn - # notify = NochexNotification.new(request.raw_post) - # - # if notify.acknowledge - # ... process order ... if notify.complete? - # else - # ... log possible hacking attempt ... - # end - def acknowledge - payload = raw - - response = ssl_post(Nochex.notification_confirmation_url, payload, - 'Content-Length' => "#{payload.size}", - 'User-Agent' => "Active Merchant -- http://activemerchant.org", - 'Content-Type' => "application/x-www-form-urlencoded" - ) - - raise StandardError.new("Faulty Nochex result: #{response}") unless ["AUTHORISED", "DECLINED"].include?(response) - - response == "AUTHORISED" - end - end - end - end - end -end diff --git a/lib/active_merchant/billing/integrations/nochex/return.rb b/lib/active_merchant/billing/integrations/nochex/return.rb deleted file mode 100644 index c8fd59e1a90..00000000000 --- a/lib/active_merchant/billing/integrations/nochex/return.rb +++ /dev/null @@ -1,10 +0,0 @@ -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - module Nochex - class Return < ActiveMerchant::Billing::Integrations::Return - end - end - end - end -end diff --git a/lib/active_merchant/billing/integrations/notification.rb b/lib/active_merchant/billing/integrations/notification.rb deleted file mode 100644 index 56df8ee8aa1..00000000000 --- a/lib/active_merchant/billing/integrations/notification.rb +++ /dev/null @@ -1,71 +0,0 @@ -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - class Notification - attr_accessor :params - attr_accessor :raw - - # set this to an array in the subclass, to specify which IPs are allowed - # to send requests - class_attribute :production_ips - - # * *Args* : - # - +doc+ -> raw post string - # - +options+ -> custom options which individual implementations can - # utilize - def initialize(post, options = {}) - @options = options - empty! - parse(post) - end - - def status - raise NotImplementedError, "Must implement this method in the subclass" - end - - # the money amount we received in X.2 decimal. - def gross - raise NotImplementedError, "Must implement this method in the subclass" - end - - def gross_cents - (gross.to_f * 100.0).round - end - - # This combines the gross and currency and returns a proper Money object. - # this requires the money library located at http://dist.leetsoft.com/api/money - def amount - return Money.new(gross_cents, currency) rescue ArgumentError - return Money.new(gross_cents) # maybe you have an own money object which doesn't take a currency? - end - - # reset the notification. - def empty! - @params = Hash.new - @raw = "" - end - - # Check if the request comes from an official IP - def valid_sender?(ip) - return true if ActiveMerchant::Billing::Base.integration_mode == :test || production_ips.blank? - production_ips.include?(ip) - end - - def test? - false - end - - private - - # Take the posted data and move the relevant data into a hash - def parse(post) - @raw = post.to_s - for line in @raw.split('&') - key, value = *line.scan( %r{^([A-Za-z0-9_.]+)\=(.*)$} ).flatten - params[key] = CGI.unescape(value) - end - end - end - end - end -end diff --git a/lib/active_merchant/billing/integrations/paxum.rb b/lib/active_merchant/billing/integrations/paxum.rb deleted file mode 100644 index 78ac7483d1f..00000000000 --- a/lib/active_merchant/billing/integrations/paxum.rb +++ /dev/null @@ -1,44 +0,0 @@ -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - - # Documentation: - # https://www.paxum.com/payment_docs/page.php?name=apiIntroduction - module Paxum - autoload :Helper, File.dirname(__FILE__) + '/paxum/helper.rb' - autoload :Notification, File.dirname(__FILE__) + '/paxum/notification.rb' - autoload :Return, File.dirname(__FILE__) + '/paxum/return.rb' - autoload :Common, File.dirname(__FILE__) + '/paxum/common.rb' - - mattr_accessor :test_url - self.test_url = 'https://paxum.com/payment/phrame.php?action=displayProcessPaymentLogin' - - mattr_accessor :production_url - self.production_url = 'https://paxum.com/payment/phrame.php?action=displayProcessPaymentLogin' - - mattr_accessor :signature_parameter_name - self.signature_parameter_name = 'key' - - def self.service_url - mode = ActiveMerchant::Billing::Base.integration_mode - case mode - when :production - self.production_url - when :test - self.test_url - else - raise StandardError, "Integration mode set to an invalid value: #{mode}" - end - end - - def self.helper(order, account, options = {}) - Helper.new(order, account, options) - end - - def self.notification(query_string, options = {}) - Notification.new(query_string, options) - end - end - end - end -end diff --git a/lib/active_merchant/billing/integrations/paxum/common.rb b/lib/active_merchant/billing/integrations/paxum/common.rb deleted file mode 100644 index e158142afaf..00000000000 --- a/lib/active_merchant/billing/integrations/paxum/common.rb +++ /dev/null @@ -1,24 +0,0 @@ -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - module Paxum - module Common - def generate_signature_string - @raw_post.slice!(0) if @raw_post.starts_with?("&") - @raw_post = CGI.unescape(@raw_post) - @raw_post = "&#{@raw_post}" unless @raw_post.starts_with?("&") - arr = @raw_post.split('&') - arr.delete(arr.last) - data = arr.join('&') - - (data + secret) - end - - def generate_signature - Digest::MD5.hexdigest(generate_signature_string) - end - end - end - end - end -end diff --git a/lib/active_merchant/billing/integrations/paxum/helper.rb b/lib/active_merchant/billing/integrations/paxum/helper.rb deleted file mode 100644 index f69fb96d662..00000000000 --- a/lib/active_merchant/billing/integrations/paxum/helper.rb +++ /dev/null @@ -1,42 +0,0 @@ -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - module Paxum - class Helper < ActiveMerchant::Billing::Integrations::Helper - include Common - - def initialize(order, account, options = {}) - @paxum_options = options.dup - options.delete(:description) - options.delete(:fail_url) - options.delete(:success_url) - options.delete(:result_url) - super - add_field "button_type_id", "1" - add_field "variables", "notify_url=#{@paxum_options[:result_url]}" - @paxum_options.each do |key, value| - add_field mappings[key], value - end - end - - def form_fields - @fields - end - - def params - @fields - end - - mapping :account, 'business_email' - mapping :amount, 'amount' - mapping :currency, 'currency' - mapping :order, 'item_id' - mapping :description, 'item_name' - mapping :fail_url, 'cancel_url' - mapping :success_url, 'finish_url' - mapping :result_url, 'notify_url' - end - end - end - end -end diff --git a/lib/active_merchant/billing/integrations/paxum/notification.rb b/lib/active_merchant/billing/integrations/paxum/notification.rb deleted file mode 100644 index 06c5c64b25e..00000000000 --- a/lib/active_merchant/billing/integrations/paxum/notification.rb +++ /dev/null @@ -1,33 +0,0 @@ -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - module Paxum - class Notification < ActiveMerchant::Billing::Integrations::Notification - include Common - - def initialize(post, options = {}) - @raw_post = post.dup - post.slice!(0) - super - end - - def self.recognizes?(params) - (params.has_key?('transaction_item_id') && params.has_key?('transaction_amount')) - end - - def security_key - params["key"] - end - - def secret - @options[:secret] - end - - def acknowledge - (security_key == generate_signature) - end - end - end - end - end -end diff --git a/lib/active_merchant/billing/integrations/pay_fast.rb b/lib/active_merchant/billing/integrations/pay_fast.rb deleted file mode 100644 index 6e44ef55f38..00000000000 --- a/lib/active_merchant/billing/integrations/pay_fast.rb +++ /dev/null @@ -1,70 +0,0 @@ -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - - # Documentation: - # https://www.payfast.co.za/s/std/integration-guide - module PayFast - autoload :Return, File.dirname(__FILE__) + '/pay_fast/return.rb' - autoload :Helper, File.dirname(__FILE__) + '/pay_fast/helper.rb' - autoload :Notification, File.dirname(__FILE__) + '/pay_fast/notification.rb' - autoload :Common, File.dirname(__FILE__) + '/pay_fast/common.rb' - - # Overwrite this if you want to change the PayFast sandbox url - mattr_accessor :process_test_url - self.process_test_url = 'https://sandbox.payfast.co.za/eng/process' - - # Overwrite this if you want to change the PayFast production url - mattr_accessor :process_production_url - self.process_production_url = 'https://www.payfast.co.za/eng/process' - - # Overwrite this if you want to change the PayFast sandbox url - mattr_accessor :validate_test_url - self.validate_test_url = 'https://sandbox.payfast.co.za/eng/query/validate' - - # Overwrite this if you want to change the PayFast production url - mattr_accessor :validate_production_url - self.validate_production_url = 'https://www.payfast.co.za/eng/query/validate' - - mattr_accessor :signature_parameter_name - self.signature_parameter_name = 'signature' - - def self.service_url - mode = ActiveMerchant::Billing::Base.integration_mode - case mode - when :production - self.process_production_url - when :test - self.process_test_url - else - raise StandardError, "Integration mode set to an invalid value: #{mode}" - end - end - - def self.validate_service_url - mode = ActiveMerchant::Billing::Base.integration_mode - case mode - when :production - self.validate_production_url - when :test - self.validate_test_url - else - raise StandardError, "Integration mode set to an invalid value: #{mode}" - end - end - - def self.helper(order, account, options = {}) - Helper.new(order, account, options) - end - - def self.notification(query_string, options = {}) - Notification.new(query_string, options) - end - - def self.return(post, options = {}) - Return.new(post, options) - end - end - end - end -end diff --git a/lib/active_merchant/billing/integrations/pay_fast/common.rb b/lib/active_merchant/billing/integrations/pay_fast/common.rb deleted file mode 100644 index d5fb5dc5b28..00000000000 --- a/lib/active_merchant/billing/integrations/pay_fast/common.rb +++ /dev/null @@ -1,42 +0,0 @@ -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - module PayFast - module Common - def generate_signature(type) - string = case type - when :request - request_signature_string - when :notify - notify_signature_string - end - - Digest::MD5.hexdigest(string) - end - - def request_attributes - [:merchant_id, :merchant_key, :return_url, :cancel_url, - :notify_url, :name_first, :name_last, :email_address, - :payment_id, :amount, :item_name, :item_description, - :custom_str1, :custom_str2, :custom_str3, :custom_str4, - :custom_str5, :custom_int1, :custom_int2, :custom_int3, - :custom_int4, :custom_int5, :email_confirmation, - :confirmation_address] - end - - def request_signature_string - request_attributes.map do |attr| - "#{mappings[attr]}=#{CGI.escape(@fields[mappings[attr]])}" if @fields[mappings[attr]].present? - end.compact.join('&') - end - - def notify_signature_string - params.map do |key, value| - "#{key}=#{CGI.escape(value)}" unless key == PayFast.signature_parameter_name - end.compact.join('&') - end - end - end - end - end -end diff --git a/lib/active_merchant/billing/integrations/pay_fast/helper.rb b/lib/active_merchant/billing/integrations/pay_fast/helper.rb deleted file mode 100644 index 6d1a43c8649..00000000000 --- a/lib/active_merchant/billing/integrations/pay_fast/helper.rb +++ /dev/null @@ -1,50 +0,0 @@ -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - module PayFast - class Helper < ActiveMerchant::Billing::Integrations::Helper - include Common - - def initialize(order, account, options = {}) - super - add_field('merchant_id', account) - add_field('merchant_key', options.delete(:credential2)) - add_field('m_payment_id', order) - end - - def form_fields - @fields - end - - def params - @fields - end - - mapping :merchant_id, 'merchant_id' - mapping :merchant_key, 'merchant_key' - mapping :return_url, 'return_url' - mapping :cancel_return_url, 'cancel_url' - mapping :notify_url, 'notify_url' - mapping :name_first, 'name_first' - mapping :name_last, 'name_last' - mapping :email_address, 'email_address' - mapping :payment_id, 'm_payment_id' - mapping :amount, 'amount' - mapping :item_name, 'item_name' - mapping :description, 'item_name' - - mapping :customer, :first_name => 'name_first', - :last_name => 'name_last', - :email => 'email_address', - :phone => 'phone' - - 5.times { |i| mapping :"custom_str#{i}", "custom_str#{i}" } - 5.times { |i| mapping :"custom_int#{i}", "custom_int#{i}" } - - mapping :email_confirmation, 'email_confirmation' - mapping :confirmation_address, 'confirmation_address' - end - end - end - end -end diff --git a/lib/active_merchant/billing/integrations/pay_fast/notification.rb b/lib/active_merchant/billing/integrations/pay_fast/notification.rb deleted file mode 100644 index b0df3332193..00000000000 --- a/lib/active_merchant/billing/integrations/pay_fast/notification.rb +++ /dev/null @@ -1,134 +0,0 @@ -require 'net/http' - -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - module PayFast - # Parser and handler for incoming ITN from PayFast. - # The Example shows a typical handler in a rails application. - # - # Example - # - # class BackendController < ApplicationController - # include ActiveMerchant::Billing::Integrations - # - # def pay_fast_itn - # notify = PayFast::Notification.new(request.raw_post) - # - # order = Order.find(notify.item_id) - # - # if notify.acknowledge - # begin - # - # if notify.complete? and order.total == notify.amount - # order.status = 'success' - # - # shop.ship(order) - # else - # logger.error("Failed to verify Paypal's notification, please investigate") - # end - # - # rescue => e - # order.status = 'failed' - # raise - # ensure - # order.save - # end - # end - # - # render :nothing - # end - # end - class Notification < ActiveMerchant::Billing::Integrations::Notification - include PostsData - include Common - - # Was the transaction complete? - def complete? - status == "Completed" - end - - # Status of transaction. List of possible values: - # <tt>COMPLETE</tt>:: - def status - if params['payment_status'] == "COMPLETE" - "Completed" - else - "Failed" - end - end - - # Id of this transaction (uniq PayFast transaction id) - def transaction_id - params['pf_payment_id'] - end - - # Id of this transaction (uniq Shopify transaction id) - def item_id - params['m_payment_id'] - end - - # The total amount which the payer paid. - def gross - params['amount_gross'] - end - - # The total in fees which was deducated from the amount. - def fee - params['amount_fee'] - end - - # The net amount credited to the receiver's account. - def amount - params['amount_net'] - end - - # The name of the item being charged for. - def item_name - params['item_name'] - end - - # The Merchant ID as given by the PayFast system. Used to uniquely identify the receiver's account. - def merchant_id - params['merchant_id'] - end - - def currency - nil - end - # Generated hash depends on params order so use OrderedHash instead of Hash - def empty! - super - @params = ActiveSupport::OrderedHash.new - end - - # Acknowledge the transaction to PayFast. This method has to be called after a new - # ITN arrives. PayFast will verify that all the information we received are correct and will return a - # VERIFIED or INVALID status. - # - # Example: - # - # def pay_fast_itn - # notify = PayFastNotification.new(request.raw_post) - # - # if notify.acknowledge - # ... process order ... if notify.complete? - # else - # ... log possible hacking attempt ... - # end - def acknowledge - if params[PayFast.signature_parameter_name] == generate_signature(:notify) - response = ssl_post(PayFast.validate_service_url, notify_signature_string, - 'Content-Type' => "application/x-www-form-urlencoded", - 'Content-Length' => "#{notify_signature_string.size}" - ) - raise StandardError.new("Faulty PayFast result: #{response}") unless ['VALID', 'INVALID'].include?(response) - - response == "VALID" - end - end - end - end - end - end -end diff --git a/lib/active_merchant/billing/integrations/pay_fast/return.rb b/lib/active_merchant/billing/integrations/pay_fast/return.rb deleted file mode 100644 index 505dc14fa21..00000000000 --- a/lib/active_merchant/billing/integrations/pay_fast/return.rb +++ /dev/null @@ -1,10 +0,0 @@ -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - module PayFast - class Return < ActiveMerchant::Billing::Integrations::Return - end - end - end - end -end diff --git a/lib/active_merchant/billing/integrations/payflow_link.rb b/lib/active_merchant/billing/integrations/payflow_link.rb deleted file mode 100644 index 875eac688fb..00000000000 --- a/lib/active_merchant/billing/integrations/payflow_link.rb +++ /dev/null @@ -1,21 +0,0 @@ -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - module PayflowLink - autoload :Helper, 'active_merchant/billing/integrations/payflow_link/helper.rb' - autoload :Notification, 'active_merchant/billing/integrations/payflow_link/notification.rb' - - mattr_accessor :service_url - self.service_url = 'https://payflowlink.paypal.com' - - def self.notification(post, options = {}) - Notification.new(post) - end - - def self.return(query_string, options = {}) - ActiveMerchant::Billing::Integrations::Return.new(query_string) - end - end - end - end -end diff --git a/lib/active_merchant/billing/integrations/payflow_link/helper.rb b/lib/active_merchant/billing/integrations/payflow_link/helper.rb deleted file mode 100644 index 29326dcf721..00000000000 --- a/lib/active_merchant/billing/integrations/payflow_link/helper.rb +++ /dev/null @@ -1,116 +0,0 @@ -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - module PayflowLink - class Helper < ActiveMerchant::Billing::Integrations::Helper - include PostsData - - def initialize(order, account, options = {}) - super - add_field('login', account) - add_field('echodata', 'True') - add_field('user2', self.test?) - add_field('invoice', order) - add_field('vendor', account) - add_field('user', options[:credential4].presence || account) - add_field('trxtype', options[:transaction_type] || 'S') - end - - mapping :account, 'login' - mapping :credential2, 'pwd' - mapping :credential3, 'partner' - mapping :order, 'user1' - - mapping :amount, 'amt' - - - mapping :billing_address, :city => 'city', - :address => 'address', - :state => 'state', - :zip => 'zip', - :country => 'country', - :phone => 'phone', - :name => 'name' - - mapping :customer, { :first_name => 'first_name', :last_name => 'last_name' } - - def description(value) - add_field('description', normalize("#{value}").delete("#")) - end - - def customer(params = {}) - add_field(mappings[:customer][:first_name], params[:first_name]) - add_field(mappings[:customer][:last_name], params[:last_name]) - end - - def billing_address(params = {}) - # Get the country code in the correct format - # Use what we were given if we can't find anything - country_code = lookup_country_code(params.delete(:country)) - add_field(mappings[:billing_address][:country], country_code) - - add_field(mappings[:billing_address][:address], [params.delete(:address1), params.delete(:address2)].compact.join(' ')) - - province_code = params.delete(:state) - add_field(mappings[:billing_address][:state], province_code.blank? ? 'N/A' : province_code.upcase) - - # Everything else - params.each do |k, v| - field = mappings[:billing_address][k] - add_field(field, v) unless field.nil? - end - end - - def form_fields - token, token_id = request_secure_token - - {"securetoken" => token, "securetokenid" => token_id, "mode" => test? ? "test" : "live"} - end - - private - - def secure_token_id - @secure_token_id ||= Utils.generate_unique_id - end - - def secure_token_url - test? ? "https://pilot-payflowpro.paypal.com" : "https://payflowpro.paypal.com" - end - - def request_secure_token - @fields["securetokenid"] = secure_token_id - @fields["createsecuretoken"] = "Y" - - fields = @fields.collect {|key, value| "#{key}[#{value.length}]=#{value}" }.join("&") - - response = ssl_post(secure_token_url, fields) - - parse_response(response) - end - - def parse_response(response) - response = response.split("&").inject({}) do |hash, param| - key, value = param.split("=") - hash[key] = value - hash - end - - [response['SECURETOKEN'], response['SECURETOKENID']] if response['RESPMSG'] && response['RESPMSG'].downcase == "approved" - end - - def normalize(text) - return unless text - - if ActiveSupport::Inflector.method(:transliterate).arity == -2 - ActiveSupport::Inflector.transliterate(text,'') - elsif RUBY_VERSION >= '1.9' - text.gsub(/[^\x00-\x7F]+/, '') - else - ActiveSupport::Inflector.transliterate(text).to_s - end - end - end - end - end - end -end diff --git a/lib/active_merchant/billing/integrations/payflow_link/notification.rb b/lib/active_merchant/billing/integrations/payflow_link/notification.rb deleted file mode 100644 index f2879869413..00000000000 --- a/lib/active_merchant/billing/integrations/payflow_link/notification.rb +++ /dev/null @@ -1,78 +0,0 @@ -require 'net/http' - -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - module PayflowLink - class Notification < ActiveMerchant::Billing::Integrations::Notification - - # Was the transaction complete? - def complete? - status == "Completed" - end - - # When was this payment received by the client. - # sometimes it can happen that we get the notification much later. - # One possible scenario is that our web application was down. In this case paypal tries several - # times an hour to inform us about the notification - def received_at - DateTime.parse(params['TRANSTIME']) if params['TRANSTIME'] - rescue ArgumentError - nil - end - - def status - params['RESPMSG'] - end - - # Id of this transaction (paypal number) - def transaction_id - params['PNREF'] - end - - # What type of transaction are we dealing with? - def type - params['TYPE'] - end - - # the money amount we received in X.2 decimal. - def gross - params['AMT'] - end - - # What currency have we been dealing with - def currency - nil - end - - def status - params['RESULT'] == '0' ? 'Completed' : 'Failed' - end - - # This is the item number which we submitted to paypal - def item_id - params['USER1'] - end - - # This is the invoice which you passed to paypal - def invoice - params['INVNUM'] - end - - # Was this a test transaction? - def test? - params['USER2'] == 'true' - end - - def account - params["ACCT"] - end - - def acknowledge - true - end - end - end - end - end -end diff --git a/lib/active_merchant/billing/integrations/paypal.rb b/lib/active_merchant/billing/integrations/paypal.rb deleted file mode 100644 index 13a1d277bcb..00000000000 --- a/lib/active_merchant/billing/integrations/paypal.rb +++ /dev/null @@ -1,39 +0,0 @@ -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - module Paypal - autoload :Return, 'active_merchant/billing/integrations/paypal/return.rb' - autoload :Helper, 'active_merchant/billing/integrations/paypal/helper.rb' - autoload :Notification, 'active_merchant/billing/integrations/paypal/notification.rb' - - # Overwrite this if you want to change the Paypal test url - mattr_accessor :test_url - self.test_url = 'https://www.sandbox.paypal.com/cgi-bin/webscr' - - # Overwrite this if you want to change the Paypal production url - mattr_accessor :production_url - self.production_url = 'https://www.paypal.com/cgi-bin/webscr' - - def self.service_url - mode = ActiveMerchant::Billing::Base.integration_mode - case mode - when :production - self.production_url - when :test - self.test_url - else - raise StandardError, "Integration mode set to an invalid value: #{mode}" - end - end - - def self.notification(post, options = {}) - Notification.new(post) - end - - def self.return(query_string, options = {}) - Return.new(query_string) - end - end - end - end -end diff --git a/lib/active_merchant/billing/integrations/paypal/helper.rb b/lib/active_merchant/billing/integrations/paypal/helper.rb deleted file mode 100644 index c905ae75901..00000000000 --- a/lib/active_merchant/billing/integrations/paypal/helper.rb +++ /dev/null @@ -1,119 +0,0 @@ -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - module Paypal - class Helper < ActiveMerchant::Billing::Integrations::Helper - CANADIAN_PROVINCES = { 'AB' => 'Alberta', - 'BC' => 'British Columbia', - 'MB' => 'Manitoba', - 'NB' => 'New Brunswick', - 'NL' => 'Newfoundland', - 'NS' => 'Nova Scotia', - 'NU' => 'Nunavut', - 'NT' => 'Northwest Territories', - 'ON' => 'Ontario', - 'PE' => 'Prince Edward Island', - 'QC' => 'Quebec', - 'SK' => 'Saskatchewan', - 'YT' => 'Yukon' - } - # See https://www.paypal.com/IntegrationCenter/ic_std-variable-reference.html for details on the following options. - mapping :order, [ 'item_number', 'custom' ] - - def initialize(order, account, options = {}) - super - add_field('cmd', '_ext-enter') - add_field('redirect_cmd', '_xclick') - add_field('quantity', 1) - add_field('item_name', 'Store purchase') - add_field('no_shipping', '1') - add_field('no_note', '1') - add_field('charset', 'utf-8') - add_field('address_override', '0') - add_field('bn', application_id.to_s.slice(0,32)) unless application_id.blank? - end - - mapping :amount, 'amount' - mapping :account, 'business' - mapping :currency, 'currency_code' - mapping :notify_url, 'notify_url' - mapping :return_url, 'return' - mapping :cancel_return_url, 'cancel_return' - mapping :invoice, 'invoice' - mapping :item_name, 'item_name' - mapping :quantity, 'quantity' - mapping :no_shipping, 'no_shipping' - mapping :no_note, 'no_note' - mapping :address_override, 'address_override' - - mapping :application_id, 'bn' - - mapping :customer, :first_name => 'first_name', - :last_name => 'last_name', - :email => 'email' - - mapping :shipping_address, :city => 'city', - :address1 => 'address1', - :address2 => 'address2', - :state => 'state', - :zip => 'zip', - :country => 'country' - - def shipping_address(params = {}) - - # Get the country code in the correct format - # Use what we were given if we can't find anything - country_code = lookup_country_code(params.delete(:country)) - add_field(mappings[:shipping_address][:country], country_code) - - if params.has_key?(:phone) - phone = params.delete(:phone).to_s - - # Whipe all non digits - phone.gsub!(/\D+/, '') - - if ['US', 'CA'].include?(country_code) && phone =~ /(\d{3})(\d{3})(\d{4})$/ - add_field('night_phone_a', $1) - add_field('night_phone_b', $2) - add_field('night_phone_c', $3) - else - add_field('night_phone_b', phone) - end - end - - province_code = params.delete(:state) - - case country_code - when 'CA' - add_field(mappings[:shipping_address][:state], CANADIAN_PROVINCES[province_code.upcase]) unless province_code.nil? - when 'US' - add_field(mappings[:shipping_address][:state], province_code) - else - add_field(mappings[:shipping_address][:state], province_code.blank? ? 'N/A' : province_code) - end - - # Everything else - params.each do |k, v| - field = mappings[:shipping_address][k] - add_field(field, v) unless field.nil? - end - end - - mapping :tax, 'tax' - mapping :shipping, 'shipping' - mapping :cmd, 'cmd' - mapping :custom, 'custom' - mapping :src, 'src' - mapping :sra, 'sra' - %w(a p t).each do |l| - (1..3).each do |i| - mapping "#{l}#{i}".to_sym, "#{l}#{i}" - end - end - end - end - end - end -end - - diff --git a/lib/active_merchant/billing/integrations/paypal/notification.rb b/lib/active_merchant/billing/integrations/paypal/notification.rb deleted file mode 100644 index 7fdb217da21..00000000000 --- a/lib/active_merchant/billing/integrations/paypal/notification.rb +++ /dev/null @@ -1,227 +0,0 @@ -require 'net/http' -require 'time' - -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - module Paypal - # Parser and handler for incoming Instant payment notifications from paypal. - # The Example shows a typical handler in a rails application. Note that this - # is an example, please read the Paypal API documentation for all the details - # on creating a safe payment controller. - # - # Example - # - # class BackendController < ApplicationController - # include ActiveMerchant::Billing::Integrations - # - # def paypal_ipn - # notify = Paypal::Notification.new(request.raw_post) - # - # if notify.masspay? - # masspay_items = notify.items - # end - # - # order = Order.find(notify.item_id) - # - # if notify.acknowledge - # begin - # - # if notify.complete? and order.total == notify.amount - # order.status = 'success' - # - # shop.ship(order) - # else - # logger.error("Failed to verify Paypal's notification, please investigate") - # end - # - # rescue => e - # order.status = 'failed' - # raise - # ensure - # order.save - # end - # end - # - # render :nothing - # end - # end - class Notification < ActiveMerchant::Billing::Integrations::Notification - include PostsData - - def initialize(post, options = {}) - super - extend MassPayNotification if masspay? - end - - # Was the transaction complete? - def complete? - status == "Completed" - end - - # Is it a masspay notification? - def masspay? - type == "masspay" - end - - # When was this payment received by the client. - # sometimes it can happen that we get the notification much later. - # One possible scenario is that our web application was down. In this case paypal tries several - # times an hour to inform us about the notification - def received_at - parsed_time_fields = DateTime._strptime(params['payment_date'], "%H:%M:%S %b %d, %Y %Z") - Time.gm( - parsed_time_fields[:year], - parsed_time_fields[:mon], - parsed_time_fields[:mday], - parsed_time_fields[:hour], - parsed_time_fields[:min], - parsed_time_fields[:sec] - ) + Time.zone_offset(parsed_time_fields[:zone]) - end - - # Status of transaction. List of possible values: - # <tt>Canceled-Reversal</tt>:: - # <tt>Completed</tt>:: - # <tt>Denied</tt>:: - # <tt>Expired</tt>:: - # <tt>Failed</tt>:: - # <tt>In-Progress</tt>:: - # <tt>Partially-Refunded</tt>:: - # <tt>Pending</tt>:: - # <tt>Processed</tt>:: - # <tt>Refunded</tt>:: - # <tt>Reversed</tt>:: - # <tt>Voided</tt>:: - def status - params['payment_status'] - end - - # Id of this transaction (paypal number) - def transaction_id - params['txn_id'] - end - - # What type of transaction are we dealing with? - # "cart" "send_money" "web_accept" are possible here. - def type - params['txn_type'] - end - - # the money amount we received in X.2 decimal. - def gross - params['mc_gross'] - end - - # the markup paypal charges for the transaction - def fee - params['mc_fee'] - end - - # What currency have we been dealing with - def currency - params['mc_currency'] - end - - # This is the item number which we submitted to paypal - # The custom field is also mapped to item_id because PayPal - # doesn't return item_number in dispute notifications - def item_id - params['item_number'] || params['custom'] - end - - # This is the invoice which you passed to paypal - def invoice - params['invoice'] - end - - # Was this a test transaction? - def test? - params['test_ipn'] == '1' - end - - def account - params['business'] || params['receiver_email'] - end - - # Acknowledge the transaction to paypal. This method has to be called after a new - # ipn arrives. Paypal will verify that all the information we received are correct and will return a - # ok or a fail. - # - # Example: - # - # def paypal_ipn - # notify = PaypalNotification.new(request.raw_post) - # - # if notify.acknowledge - # ... process order ... if notify.complete? - # else - # ... log possible hacking attempt ... - # end - def acknowledge - payload = raw - - response = ssl_post(Paypal.service_url + '?cmd=_notify-validate', payload, - 'Content-Length' => "#{payload.size}", - 'User-Agent' => "Active Merchant -- http://activemerchant.org" - ) - - raise StandardError.new("Faulty paypal result: #{response}") unless ["VERIFIED", "INVALID"].include?(response) - - response == "VERIFIED" - end - end - - module MassPayNotification - # Mass pay returns a collection of MassPay Items, so inspect items to get the values - def transaction_id - end - - # Mass pay returns a collection of MassPay Items, so inspect items to get the values - def gross - end - - # Mass pay returns a collection of MassPay Items, so inspect items to get the values - def fee - end - - # Mass pay returns a collection of MassPay Items, so inspect items to get the values - def currency - end - - # Mass pay returns a collection of MassPay Items, so inspect items to get the values - def item_id - end - - # Mass pay returns a collection of MassPay Items, so inspect items to get the values - def account - end - - # Collection of notification items returned for MassPay transactions - def items - @items ||= (1..number_of_mass_pay_items).map do |item_number| - MassPayItem.new( - params["masspay_txn_id_#{item_number}"], - params["mc_gross_#{item_number}"], - params["mc_fee_#{item_number}"], - params["mc_currency_#{item_number}"], - params["unique_id_#{item_number}"], - params["receiver_email_#{item_number}"], - params["status_#{item_number}"] - ) - end - end - - private - - def number_of_mass_pay_items - @number_of_mass_pay_items ||= params.keys.select { |k| k.start_with? 'masspay_txn_id' }.size - end - end - - class MassPayItem < Struct.new(:transaction_id, :gross, :fee, :currency, :item_id, :account, :status) - end - end - end - end -end diff --git a/lib/active_merchant/billing/integrations/paypal/return.rb b/lib/active_merchant/billing/integrations/paypal/return.rb deleted file mode 100644 index 2b22a06a610..00000000000 --- a/lib/active_merchant/billing/integrations/paypal/return.rb +++ /dev/null @@ -1,10 +0,0 @@ -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - module Paypal - class Return < ActiveMerchant::Billing::Integrations::Return - end - end - end - end -end diff --git a/lib/active_merchant/billing/integrations/paypal_payments_advanced.rb b/lib/active_merchant/billing/integrations/paypal_payments_advanced.rb deleted file mode 100644 index f1741421e47..00000000000 --- a/lib/active_merchant/billing/integrations/paypal_payments_advanced.rb +++ /dev/null @@ -1,20 +0,0 @@ -module ActiveMerchant - module Billing - module Integrations - module PaypalPaymentsAdvanced - autoload :Helper, 'active_merchant/billing/integrations/paypal_payments_advanced/helper.rb' - - mattr_accessor :service_url - self.service_url = 'https://payflowlink.paypal.com' - - def self.notification(post, options = {}) - PayflowLink::Notification.new(post) - end - - def self.return(query_string, options = {}) - Return.new(query_string) - end - end - end - end -end diff --git a/lib/active_merchant/billing/integrations/paypal_payments_advanced/helper.rb b/lib/active_merchant/billing/integrations/paypal_payments_advanced/helper.rb deleted file mode 100644 index cd1889999b3..00000000000 --- a/lib/active_merchant/billing/integrations/paypal_payments_advanced/helper.rb +++ /dev/null @@ -1,15 +0,0 @@ -module ActiveMerchant - module Billing - module Integrations - module PaypalPaymentsAdvanced - class Helper < PayflowLink::Helper - - def initialize(order, account, options) - super - add_field('partner', 'PayPal') - end - end - end - end - end -end \ No newline at end of file diff --git a/lib/active_merchant/billing/integrations/paysbuy.rb b/lib/active_merchant/billing/integrations/paysbuy.rb deleted file mode 100644 index d74db8c4bcc..00000000000 --- a/lib/active_merchant/billing/integrations/paysbuy.rb +++ /dev/null @@ -1,36 +0,0 @@ -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - module Paysbuy - autoload :Helper, File.dirname(__FILE__) + '/paysbuy/helper.rb' - autoload :Notification, File.dirname(__FILE__) + '/paysbuy/notification.rb' - - mattr_accessor :test_url - self.test_url = 'https://demo.paysbuy.com/paynow.aspx' - - mattr_accessor :production_url - self.production_url = 'https://www.paysbuy.com/paynow.aspx' - - def self.service_url - mode = ActiveMerchant::Billing::Base.integration_mode - case mode - when :production - self.production_url - when :test - self.test_url - else - raise StandardError, "Integration mode set to an invalid value: #{mode}" - end - end - - def self.helper(order, account, options = {}) - Helper.new(order, account, options) - end - - def self.notification(query_string, options = {}) - Notification.new(query_string, options) - end - end - end - end -end diff --git a/lib/active_merchant/billing/integrations/paysbuy/helper.rb b/lib/active_merchant/billing/integrations/paysbuy/helper.rb deleted file mode 100644 index 3cd3430b199..00000000000 --- a/lib/active_merchant/billing/integrations/paysbuy/helper.rb +++ /dev/null @@ -1,15 +0,0 @@ -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - module Paysbuy - class Helper < ActiveMerchant::Billing::Integrations::Helper - mapping :account, 'biz' - mapping :amount, 'amt' - mapping :order, 'inv' - mapping :description, 'itm' - mapping :notify_url, 'postURL' - end - end - end - end -end diff --git a/lib/active_merchant/billing/integrations/paysbuy/notification.rb b/lib/active_merchant/billing/integrations/paysbuy/notification.rb deleted file mode 100644 index c7121a96389..00000000000 --- a/lib/active_merchant/billing/integrations/paysbuy/notification.rb +++ /dev/null @@ -1,28 +0,0 @@ -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - module Paysbuy - class Notification < ActiveMerchant::Billing::Integrations::Notification - SUCCESS = '00' - FAIL = '99' - - def complete? - status == 'Completed' - end - - def item_id - params['result'][2..-1] - end - - def status - params['result'][0..1] == SUCCESS ? 'Completed' : 'Failed' - end - - def acknowledge - true - end - end - end - end - end -end diff --git a/lib/active_merchant/billing/integrations/payu_in.rb b/lib/active_merchant/billing/integrations/payu_in.rb deleted file mode 100755 index 74933f1c7c6..00000000000 --- a/lib/active_merchant/billing/integrations/payu_in.rb +++ /dev/null @@ -1,43 +0,0 @@ -require 'digest/sha2' -require 'bigdecimal' - -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - module PayuIn - autoload :Return, 'active_merchant/billing/integrations/payu_in/return.rb' - autoload :Helper, 'active_merchant/billing/integrations/payu_in/helper.rb' - autoload :Notification, 'active_merchant/billing/integrations/payu_in/notification.rb' - - mattr_accessor :test_url - mattr_accessor :production_url - - self.test_url = 'https://test.payu.in/_payment.php' - self.production_url = 'https://secure.payu.in/_payment.php' - - def self.service_url - ActiveMerchant::Billing::Base.integration_mode == :production ? self.production_url : self.test_url - end - - def self.notification(post, options = {}) - Notification.new(post, options) - end - - def self.return(post, options = {}) - Return.new(post, options) - end - - def self.checksum(merchant_id, secret_key, *payload_items ) - options = payload_items.pop if Hash === payload_items.last - options ||= {} - payload = if options[:reverse] then - payload_items.dup.push( merchant_id || "" ).unshift( secret_key || "" ).collect{ |x| x.to_s }.join("|") - else - payload_items.dup.unshift( merchant_id || "" ).push( secret_key || "" ).collect{ |x| x.to_s }.join("|") - end - Digest::SHA512.hexdigest( payload ) - end - end - end - end -end diff --git a/lib/active_merchant/billing/integrations/payu_in/helper.rb b/lib/active_merchant/billing/integrations/payu_in/helper.rb deleted file mode 100755 index 32048d309bc..00000000000 --- a/lib/active_merchant/billing/integrations/payu_in/helper.rb +++ /dev/null @@ -1,74 +0,0 @@ -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - module PayuIn - class Helper < ActiveMerchant::Billing::Integrations::Helper - - mapping :amount, 'amount' - mapping :account, 'key' - mapping :order, 'txnid' - mapping :credential2, 'productinfo' - - mapping :customer, :first_name => 'firstname', - :last_name => 'lastname', - :email => 'email', - :phone => 'phone' - - mapping :billing_address, :city => 'city', - :address1 => 'address1', - :address2 => 'address2', - :state => 'state', - :zip => 'zip', - :country => 'country' - - # Which tab you want to be open default on PayU - # CC (CreditCard) or NB (NetBanking) - mapping :mode, 'pg' - - mapping :notify_url, 'notify_url' - mapping :return_url, ['surl', 'furl'] - mapping :cancel_return_url, 'curl' - mapping :checksum, 'hash' - - mapping :user_defined, { :var1 => 'udf1', - :var2 => 'udf2', - :var3 => 'udf3', - :var4 => 'udf4', - :var5 => 'udf5', - :var6 => 'udf6', - :var7 => 'udf7', - :var8 => 'udf8', - :var9 => 'udf9', - :var10 => 'udf10' - } - - def initialize(order, account, options = {}) - super - self.pg = 'CC' - end - - def form_fields - @fields.merge(mappings[:checksum] => generate_checksum) - end - - def generate_checksum( options = {} ) - checksum_fields = [ :order, :amount, :credential2, { :customer => [ :first_name, :email ] }, - { :user_defined => [ :var1, :var2, :var3, :var4, :var5, :var6, :var7, :var8, :var9, :var10 ] } ] - checksum_payload_items = checksum_fields.inject( [] ) do | items, field | - if Hash === field then - key = field.keys.first - field[key].inject( items ){ |s,x| items.push( @fields[ mappings[key][x] ] ) } - else - items.push( @fields[ mappings[field] ] ) - end - end - checksum_payload_items.push( options ) - PayuIn.checksum(@fields["key"], @fields["productinfo"], *checksum_payload_items ) - end - - end - - end - end - end -end diff --git a/lib/active_merchant/billing/integrations/payu_in/notification.rb b/lib/active_merchant/billing/integrations/payu_in/notification.rb deleted file mode 100755 index 2c4b3466dd6..00000000000 --- a/lib/active_merchant/billing/integrations/payu_in/notification.rb +++ /dev/null @@ -1,165 +0,0 @@ -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - module PayuIn - class Notification < ActiveMerchant::Billing::Integrations::Notification - - def initialize(post, options = {}) - super(post, options) - @merchant_id = options[:credential1] - @secret_key = options[:credential2] - end - - def complete? - status == "Completed" - end - - def status - @status ||= if checksum_ok? - if transaction_id.blank? - 'Invalid' - else - case transaction_status.downcase - when 'success' then 'Completed' - when 'failure' then 'Failed' - when 'pending' then 'Pending' - end - end - else - 'Tampered' - end - end - - def invoice_ok?( order_id ) - order_id.to_s == invoice.to_s - end - - # Order amount should be equal to gross - discount - def amount_ok?( order_amount, order_discount = BigDecimal.new( '0.0' ) ) - BigDecimal.new( gross ) == order_amount && BigDecimal.new( discount ) == order_discount - end - - # Status of transaction return from the PayU. List of possible values: - # <tt>SUCCESS</tt>:: - # <tt>PENDING</tt>:: - # <tt>FAILURE</tt>:: - def transaction_status - params['status'] - end - - # ID of this transaction (PayU.in number) - def transaction_id - params['mihpayid'] - end - - # Mode of Payment - # - # 'CC' for credit-card - # 'NB' for net-banking - # 'CD' for cheque or DD - # 'CO' for Cash Pickup - def type - params['mode'] - end - - # What currency have we been dealing with - def currency - 'INR' - end - - def item_id - params['txnid'] - end - - # This is the invoice which you passed to PayU.in - def invoice - params['txnid'] - end - - # Merchant Id provided by the PayU.in - def account - params['key'] - end - - # original amount send by merchant - def gross - params['amount'] - end - - # This is discount given to user - based on promotion set by merchants. - def discount - params['discount'] - end - - # Description offer for what PayU given the offer to user - based on promotion set by merchants. - def offer_description - params['offer'] - end - - # Information about the product as send by merchant - def product_info - params['productinfo'] - end - - # Email of the customer - def customer_email - params['email'] - end - - # Phone of the customer - def customer_phone - params['phone'] - end - - # Firstname of the customer - def customer_first_name - params['firstname'] - end - - # Lastname of the customer - def customer_last_name - params['lastname'] - end - - # Full address of the customer - def customer_address - { :address1 => params['address1'], :address2 => params['address2'], - :city => params['city'], :state => params['state'], - :country => params['country'], :zipcode => params['zipcode'] } - end - - def user_defined - return @user_defined if @user_defined - @user_defined = [] - 10.times{ |i| @user_defined.push( params[ "udf#{i+1}" ] ) } - @user_defined - end - - def checksum - params['hash'] - end - - def message - @message || params['error'] - end - - def acknowledge - checksum_ok? - end - - def checksum_ok? - fields = user_defined.dup.push( customer_email, customer_first_name, product_info, gross, invoice, :reverse => true ) - fields.unshift( transaction_status ) - unless PayuIn.checksum(@merchant_id, @secret_key, *fields ) == checksum - @message = 'Return checksum not matching the data provided' - return false - end - true - end - - end - end - end - end -end - diff --git a/lib/active_merchant/billing/integrations/payu_in/return.rb b/lib/active_merchant/billing/integrations/payu_in/return.rb deleted file mode 100755 index e2456f7317f..00000000000 --- a/lib/active_merchant/billing/integrations/payu_in/return.rb +++ /dev/null @@ -1,36 +0,0 @@ -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - module PayuIn - class Return < ActiveMerchant::Billing::Integrations::Return - - def initialize(query_string, options = {}) - super - @notification = Notification.new(query_string, options) - end - - def transaction_id - @notification.transaction_id - end - - def status( order_id, order_amount ) - if @notification.invoice_ok?( order_id ) && @notification.amount_ok?( BigDecimal.new(order_amount) ) - @notification.status - else - 'Mismatch' - end - end - - def success? - status( @params['txnid'], @params['amount'] ) == 'Completed' - end - - def message - @notification.message - end - - end - end - end - end -end diff --git a/lib/active_merchant/billing/integrations/platron.rb b/lib/active_merchant/billing/integrations/platron.rb deleted file mode 100644 index 087a6d76f97..00000000000 --- a/lib/active_merchant/billing/integrations/platron.rb +++ /dev/null @@ -1,28 +0,0 @@ -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - # Platron API: www.platron.ru/PlatronAPI.pdf‎ - module Platron - autoload :Helper, File.dirname(__FILE__) + '/platron/helper.rb' - autoload :Notification, File.dirname(__FILE__) + '/platron/notification.rb' - autoload :Common, File.dirname(__FILE__) + '/platron/common.rb' - - mattr_accessor :service_url - self.service_url = 'https://www.platron.ru/payment.php' - - def self.notification(raw_post) - Notification.new(raw_post) - end - - def self.generate_signature_string(params, path, secret) - sorted_params = params.sort_by{|k,v| k.to_s}.collect{|k,v| v} - [path, sorted_params, secret].flatten.compact.join(';') - end - - def self.generate_signature(params, path, secret) - Digest::MD5.hexdigest(generate_signature_string(params, path, secret)) - end - end - end - end -end diff --git a/lib/active_merchant/billing/integrations/platron/helper.rb b/lib/active_merchant/billing/integrations/platron/helper.rb deleted file mode 100644 index 42cf7d1e085..00000000000 --- a/lib/active_merchant/billing/integrations/platron/helper.rb +++ /dev/null @@ -1,32 +0,0 @@ -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - module Platron - class Helper < ActiveMerchant::Billing::Integrations::Helper - def initialize(order, account, options = {}) - @secret_key = options.delete(:secret) - @path = options.delete(:path) - description = options.delete(:description) - super - self.add_field('pg_salt', rand(36**15).to_s(36)) - self.add_field('pg_description', description) - end - - def form_fields - @fields.merge('pg_sig' => Common.generate_signature(@fields, @path, @secret_key)) - end - - def params - @fields - end - - mapping :account, 'pg_merchant_id' - mapping :amount, 'pg_amount' - mapping :order, 'pg_order_id' - mapping :description, 'pg_description' - mapping :currency, 'pg_currency' - end - end - end - end -end diff --git a/lib/active_merchant/billing/integrations/platron/notification.rb b/lib/active_merchant/billing/integrations/platron/notification.rb deleted file mode 100644 index 7c417a8b5b3..00000000000 --- a/lib/active_merchant/billing/integrations/platron/notification.rb +++ /dev/null @@ -1,113 +0,0 @@ -require 'builder' - -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - module Platron - class Notification < ActiveMerchant::Billing::Integrations::Notification - def initialize(*args) - super - @signature = params.delete('pg_sig') - end - - def complete? - params['pg_result'] - end - - def order_id - params['pg_order_id'] - end - - def platron_payment_id - params['pg_payment_id'] - end - - def currency - params['pg_ps_currency'] - end - - def payment_system - params['pg_payment_system'] - end - - def user_phone - params['pg_user_phone'] - end - - def card_brand - params['pg_card_brand'] - end - - def captured - params['pg_captured'] - end - - def overpayment - params['pg_overpayment'] - end - - def failure_code - params['pg_failure_code'] - end - - def failure_description - params['pg_failure_description'] - end - - def payment_date - params['pg_payment_date'] - end - - def salt - params['pg_salt'] - end - - def signature - @signature - end - - def net_amount - params['pg_net_amount'] - end - - def ps_amount - params['pg_ps_amount'] - end - - def ps_full_amount - params['pg_ps_full_amount'] - end - - def amount - params['pg_amount'] - end - - def secret - @options[:secret] - end - - def path - @options[:path] - end - - def acknowledge - signature == Platron.generate_signature(params, path, secret) - end - - def success_response(path,secret) - salt = rand(36**15).to_s(36) - xml = "" - doc = Builder::XmlMarkup.new(:target => xml) - sign = Platron.generate_signature({:pg_status => 'ok', :pg_salt => salt}, path, secret) - doc.response do - doc.pg_status 'ok' - doc.pg_salt salt - doc.pg_sig sign - end - xml - end - end - end - end - end -end diff --git a/lib/active_merchant/billing/integrations/pxpay.rb b/lib/active_merchant/billing/integrations/pxpay.rb deleted file mode 100644 index 4973a9c967e..00000000000 --- a/lib/active_merchant/billing/integrations/pxpay.rb +++ /dev/null @@ -1,31 +0,0 @@ -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - module Pxpay - autoload :Helper, 'active_merchant/billing/integrations/pxpay/helper.rb' - autoload :Notification, 'active_merchant/billing/integrations/pxpay/notification.rb' - autoload :Return, 'active_merchant/billing/integrations/pxpay/return.rb' - - TOKEN_URL = 'https://sec.paymentexpress.com/pxpay/pxaccess.aspx' - - LIVE_URL = 'https://sec.paymentexpress.com/pxpay/pxpay.aspx' - - def self.token_url - TOKEN_URL - end - - def self.service_url - LIVE_URL - end - - def self.notification(post, options={}) - Notification.new(post, options) - end - - def self.return(query_string, options={}) - Return.new(query_string, options) - end - end - end - end -end diff --git a/lib/active_merchant/billing/integrations/pxpay/helper.rb b/lib/active_merchant/billing/integrations/pxpay/helper.rb deleted file mode 100644 index 71ca2932437..00000000000 --- a/lib/active_merchant/billing/integrations/pxpay/helper.rb +++ /dev/null @@ -1,112 +0,0 @@ -require 'active_support/version' # for ActiveSupport2.3 -require 'active_support/core_ext/float/rounding.rb' unless ActiveSupport::VERSION::MAJOR > 3 # Float#round(precision) - -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - module Pxpay - # An example. Note the username as a parameter and transaction key you - # will want to use later. The amount that you pass in will be *rounded*, - # so preferably pass in X.2 decimal so that no rounding occurs. You need - # to set :credential2 to your PxPay secret key. - # - # PxPay accounts have Failproof Notification enabled by default which means - # in addition to the user being redirected to your return_url, the return_url will - # be accessed by the PxPay servers directly, immediately after transaction success. - # - # payment_service_for('order_id', 'pxpay_user_ID', :service => :pxpay, - # :amount => 157.0, :currency => 'USD', :credential2 => 'pxpay_key') do |service| - # - # service.customer :email => 'customer@email.com' - # - # service.description 'Order 123 for MyStore' - # - # # Must specify both a return_url or PxPay will show an error instead of - # # capturing credit card details. - # - # service.return_url "http://t/pxpay/payment_received_notification_sub_step" - # - # # These fields will be copied verbatim to the Notification - # service.custom1 'custom text 1' - # service.custom2 '' - # service.custom3 '' - # # See the helper.rb file for various custom fields - # end - - class Helper < ActiveMerchant::Billing::Integrations::Helper - include PostsData - mapping :account, 'PxPayUserId' - mapping :credential2, 'PxPayKey' - mapping :currency, 'CurrencyInput' - mapping :description, 'MerchantReference' - mapping :order, 'TxnId' - mapping :customer, :email => 'EmailAddress' - - mapping :custom1, 'TxnData1' - mapping :custom2, 'TxnData2' - mapping :custom3, 'TxnData3' - - def initialize(order, account, options = {}) - super - add_field 'AmountInput', "%.2f" % options[:amount].to_f.round(2) - add_field 'EnableAddBillCard', '0' - add_field 'TxnType', 'Purchase' - end - - def return_url(url) - add_field 'UrlSuccess', url - add_field 'UrlFail', url - end - - def form_fields - # if either return URLs are blank PxPay will generate a token but redirect user to error page. - raise "error - must specify return_url" if @fields['UrlSuccess'].blank? - raise "error - must specify cancel_return_url" if @fields['UrlFail'].blank? - - result = request_secure_redirect - raise "error - failed to get token - message was #{result[:redirect]}" unless result[:valid] == "1" - - url = URI.parse(result[:redirect]) - - CGI.parse(url.query) - end - - def form_method - "GET" - end - - private - def generate_request - xml = REXML::Document.new - root = xml.add_element('GenerateRequest') - - @fields.each do | k, v | - v = v.slice(0, 50) if k == "MerchantReference" - root.add_element(k).text = v - end - - xml.to_s - end - - def request_secure_redirect - request = generate_request - - response = ssl_post(Pxpay.token_url, request) - xml = REXML::Document.new(response) - root = REXML::XPath.first(xml, "//Request") - valid = root.attributes["valid"] - redirect = root.elements["URI"].text - - # example positive response: - # <Request valid="1"><URI>https://sec.paymentexpress.com/pxpay/pxpay.aspx?userid=PxpayUser&amp;request=REQUEST_TOKEN</URI></Request> - - # example negative response: - # <Request valid="0"><URI>Invalid TxnType</URI></Request> - - {:valid => valid, :redirect => redirect} - end - end - end - end - end -end diff --git a/lib/active_merchant/billing/integrations/pxpay/notification.rb b/lib/active_merchant/billing/integrations/pxpay/notification.rb deleted file mode 100644 index 14c075f2886..00000000000 --- a/lib/active_merchant/billing/integrations/pxpay/notification.rb +++ /dev/null @@ -1,157 +0,0 @@ -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - - module Pxpay - class Notification < ActiveMerchant::Billing::Integrations::Notification - include PostsData - include RequiresParameters - - def initialize(query_string, options={}) - # PxPay appends ?result=...&userid=... to whatever return_url was specified, even if that URL ended with a ?query. - # So switch the first ? if present to a & - query_string[/\?/] = '&' if query_string[/\?/] - super - - @encrypted_params = @params - @params = {} - - requires! @encrypted_params, "result" - requires! @options, :credential1, :credential2 - - decrypt_transaction_result(@encrypted_params["result"]) - end - - # was the notification a validly formed request? - def acknowledge - @valid == '1' - end - - def status - return 'Failed' unless success? - return 'Completed' if complete? - 'Error' - end - - def complete? - @params['TxnType'] == 'Purchase' && success? - end - - def cancelled? - !success? - end - - # for field definitions see - # http://www.paymentexpress.com/Technical_Resources/Ecommerce_Hosted/PxPay - - def success? - @params['Success'] == '1' - end - - def gross - @params['AmountSettlement'] - end - - def currency - @params['CurrencySettlement'] - end - - def account - @params['userid'] - end - - def item_id - @params['TxnId'] - end - - def currency_input - @params['CurrencyInput'] - end - - def auth_code - @params['AuthCode'] - end - - def card_type - @params['CardName'] - end - - def card_holder_name - @params['CardHolderName'] - end - - def card_number - @params['CardNumber'] - end - - def expiry_date - @params['DateExpiry'] - end - - def client_ip - @params['ClientInfo'] - end - - def order_id - item_id - end - - def payer_email - @params['EmailAddress'] - end - - def transaction_id - @params['DpsTxnRef'] - end - - def settlement_date - @params['DateSettlement'] - end - - # Indication of the uniqueness of a card number - def txn_mac - @params['TxnMac'] - end - - def message - @params['ResponseText'] - end - - def optional_data - [@params['TxnData1'],@fields['TxnData2'],@fields['TxnData3']] - end - - # When was this payment was received by the client. - def received_at - settlement_date - end - - # Was this a test transaction? - def test? - nil - end - - private - - def decrypt_transaction_result(encrypted_result) - request_xml = REXML::Document.new - root = request_xml.add_element('ProcessResponse') - - root.add_element('PxPayUserId').text = @options[:credential1] - root.add_element('PxPayKey').text = @options[:credential2] - root.add_element('Response').text = encrypted_result - - @raw = ssl_post(Pxpay.token_url, request_xml.to_s) - - response_xml = REXML::Document.new(@raw) - root = REXML::XPath.first(response_xml) - @valid = root.attributes["valid"] - @params = {} - root.elements.each { |e| @params[e.name] = e.text } - end - - end - end - end - end -end diff --git a/lib/active_merchant/billing/integrations/pxpay/return.rb b/lib/active_merchant/billing/integrations/pxpay/return.rb deleted file mode 100644 index 39a546cf382..00000000000 --- a/lib/active_merchant/billing/integrations/pxpay/return.rb +++ /dev/null @@ -1,25 +0,0 @@ -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - module Pxpay - class Return < ActiveMerchant::Billing::Integrations::Return - def initialize(query_string, options={}) - @notification = Notification.new(query_string, options) - end - - def success? - @notification && @notification.complete? - end - - def cancelled? - @notification && @notification.cancelled? - end - - def message - @notification.message - end - end - end - end - end -end diff --git a/lib/active_merchant/billing/integrations/quickpay.rb b/lib/active_merchant/billing/integrations/quickpay.rb deleted file mode 100644 index 74884f8e808..00000000000 --- a/lib/active_merchant/billing/integrations/quickpay.rb +++ /dev/null @@ -1,21 +0,0 @@ -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - module Quickpay - autoload :Helper, File.dirname(__FILE__) + '/quickpay/helper.rb' - autoload :Notification, File.dirname(__FILE__) + '/quickpay/notification.rb' - - mattr_accessor :service_url - self.service_url = 'https://secure.quickpay.dk/form/' - - def self.notification(post, options = {}) - Notification.new(post) - end - - def self.return(post, options = {}) - Return.new(post, options) - end - end - end - end -end diff --git a/lib/active_merchant/billing/integrations/quickpay/helper.rb b/lib/active_merchant/billing/integrations/quickpay/helper.rb deleted file mode 100644 index 8e3f61eb153..00000000000 --- a/lib/active_merchant/billing/integrations/quickpay/helper.rb +++ /dev/null @@ -1,74 +0,0 @@ -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - module Quickpay - class Helper < ActiveMerchant::Billing::Integrations::Helper - - def initialize(order, account, options = {}) - md5secret options.delete(:credential2) - super - add_field('protocol', '6') - add_field('msgtype', 'authorize') - add_field('language', 'da') - add_field('autocapture', 0) - add_field('testmode', test? ? 1 : 0) - add_field('ordernumber', format_order_number(order)) - end - - def md5secret(value) - @md5secret = value - end - - def form_fields - @fields.merge('md5check' => generate_md5check) - end - - def generate_md5string - MD5_CHECK_FIELDS.map {|key| @fields[key.to_s]} * "" + @md5secret - end - - def generate_md5check - Digest::MD5.hexdigest(generate_md5string) - end - - # Limited to 20 digits max - def format_order_number(number) - number.to_s.gsub(/[^\w]/, '').rjust(4, "0")[0...20] - end - - MD5_CHECK_FIELDS = [ - :protocol, :msgtype, :merchant, :language, :ordernumber, - :amount, :currency, :continueurl, :cancelurl, :callbackurl, - :autocapture, :cardtypelock, :description, :ipaddress, :testmode, - :deadline, :cardhash - ] - - mapping :protocol, 'protocol' - mapping :msgtype, 'msgtype' - mapping :account, 'merchant' - mapping :language, 'language' - mapping :amount, 'amount' - mapping :currency, 'currency' - - mapping :return_url, 'continueurl' - mapping :cancel_return_url, 'cancelurl' - mapping :notify_url, 'callbackurl' - - mapping :autocapture, 'autocapture' - mapping :cardtypelock, 'cardtypelock' - - mapping :description, 'description' - mapping :ipaddress, 'ipaddress' - mapping :testmode, 'testmode' - mapping :deadline, 'deadline' - mapping :cardhash, 'cardhash' - - mapping :customer, '' - mapping :billing_address, {} - mapping :tax, '' - mapping :shipping, '' - end - end - end - end -end diff --git a/lib/active_merchant/billing/integrations/quickpay/notification.rb b/lib/active_merchant/billing/integrations/quickpay/notification.rb deleted file mode 100644 index 0d5da68a987..00000000000 --- a/lib/active_merchant/billing/integrations/quickpay/notification.rb +++ /dev/null @@ -1,137 +0,0 @@ -require 'net/http' - -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - module Quickpay - class Notification < ActiveMerchant::Billing::Integrations::Notification - def complete? - status == '000' - end - - def item_id - params['ordernumber'] - end - - def transaction_id - params['transaction'] - end - - def received_at - time = params['time'] - # If time only contains 12 integers then it's pre v5 format - time = "20#{params['time']}" if /[0-9]{12}/.match(params['time']) - Time.parse(time) - end - - def gross - "%.2f" % (gross_cents / 100.0) - end - - def gross_cents - params['amount'].to_i - end - - def status - params['qpstat'] - end - - def currency - params['currency'] - end - - # Provide access to raw fields from quickpay - %w( - msgtype - ordernumber - state - chstat - chstatmsg - qpstat - qpstatmsg - merchant - merchantemail - cardtype - cardnumber - cardhash - cardexpire - splitpayment - fraudprobability - fraudremarks - fraudreport - fee - ).each do |attr| - define_method(attr) do - params[attr] - end - end - - MD5_CHECK_FIELDS = [ - :msgtype, - :ordernumber, - :amount, - :currency, - :time, - :state, - :qpstat, - :qpstatmsg, - :chstat, - :chstatmsg, - :merchant, - :merchantemail, - :transaction, - :cardtype, - :cardnumber, - :cardhash, - :cardexpire, - :splitpayment, - :fraudprobability, - :fraudremarks, - :fraudreport, - :fee - ] - - def generate_md5string - MD5_CHECK_FIELDS.map { |key| params[key.to_s] } * "" + @options[:credential2].to_s - end - - def generate_md5check - Digest::MD5.hexdigest(generate_md5string) - end - - # Quickpay doesn't do acknowledgements of callback notifications - # Instead it uses and MD5 hash of all parameters - def acknowledge - generate_md5check == params['md5check'] - end - - # Take the posted data and move the relevant data into a hash - def parse(post) - # 30 + 12 - #------------------------------8a827a0e6829 - #Content-Disposition: form-data; name="msgtype" - # - #subscribe - #------------------------------8a827a0e6829 - #Content-Disposition: form-data; name="ordernumber" - # - #BILP94406 - - if post =~ /-{20,40}\w{6,24}/ - @raw = post.to_s - post.split(/-{20,40}\w{6,24}[\n\r]*/m).each do |part| - part.scan(/([^\n\r]+)[\n\r]+([^\n\r]*)/m) do |header, value| - if header.match(/name=["'](.*)["']/) - params[$1] = value.strip - end - end - end - else - super - end - end - end - end - end - end -end diff --git a/lib/active_merchant/billing/integrations/rbkmoney.rb b/lib/active_merchant/billing/integrations/rbkmoney.rb deleted file mode 100644 index ff7c73a91ed..00000000000 --- a/lib/active_merchant/billing/integrations/rbkmoney.rb +++ /dev/null @@ -1,17 +0,0 @@ -require File.dirname(__FILE__) + '/rbkmoney/helper.rb' -require File.dirname(__FILE__) + '/rbkmoney/notification.rb' - -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - module Rbkmoney - mattr_accessor :service_url - self.service_url = 'https://rbkmoney.ru/acceptpurchase.aspx' - - def self.notification(*args) - Notification.new(*args) - end - end - end - end -end diff --git a/lib/active_merchant/billing/integrations/rbkmoney/helper.rb b/lib/active_merchant/billing/integrations/rbkmoney/helper.rb deleted file mode 100644 index 4d79584b220..00000000000 --- a/lib/active_merchant/billing/integrations/rbkmoney/helper.rb +++ /dev/null @@ -1,23 +0,0 @@ -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - module Rbkmoney - class Helper < ActiveMerchant::Billing::Integrations::Helper - mapping :account, 'eshopId' - mapping :amount, 'recipientAmount' - - # NOTE: rbkmoney uses outdated currency code 'RUR' - mapping :currency, 'recipientCurrency' - - mapping :order, 'orderId' - - mapping :customer, :email => 'user_email' - - mapping :credential2, 'serviceName' - mapping :credential3, 'successUrl' - mapping :credential4, 'failUrl' - end - end - end - end -end diff --git a/lib/active_merchant/billing/integrations/rbkmoney/notification.rb b/lib/active_merchant/billing/integrations/rbkmoney/notification.rb deleted file mode 100644 index a4ce79d79f0..00000000000 --- a/lib/active_merchant/billing/integrations/rbkmoney/notification.rb +++ /dev/null @@ -1,91 +0,0 @@ -require 'net/http' - -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - module Rbkmoney - class Notification < ActiveMerchant::Billing::Integrations::Notification - %w( - eshopId - paymentId - orderId - eshopAccount - serviceName - recipientAmount - recipientCurrency - paymentStatus - userName - userEmail - paymentData - secretKey - hash - ).each do |param_name| - define_method(param_name.underscore){ params[param_name] } - end - - def complete? - (payment_status == '5') - end - - def test? - false - end - - def status - case payment_status - when '3' - 'pending' - when '5' - 'completed' - else 'unknown' - end - end - - def user_fields - params.inject({}) do |fields, (k,v)| - if /\AuserField_[\d+]\z/.match(k) - fields[k] = v - end - fields - end - end - - alias_method :client_id, :eshop_id - alias_method :item_id, :order_id - alias_method :transaction_id, :payment_id - alias_method :received_at, :payment_data - alias_method :payer_email, :user_email - alias_method :gross, :recipient_amount - alias_method :currency, :recipient_currency - - def acknowledge - string = [ - eshop_id, - order_id, - service_name, - eshop_account, - recipient_amount, - recipient_currency, - payment_status, - user_name, - user_email, - payment_data, - @options[:secret] - ].join '::' - - signature = case hash.to_s.length - when 32 - Digest::MD5.hexdigest(string) - when 128 - Digest::SHA512.hexdigest(string) - else - return false - end - - signature == hash - end - end - end - end - end -end diff --git a/lib/active_merchant/billing/integrations/return.rb b/lib/active_merchant/billing/integrations/return.rb deleted file mode 100644 index 579f5d10c9d..00000000000 --- a/lib/active_merchant/billing/integrations/return.rb +++ /dev/null @@ -1,42 +0,0 @@ -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - class Return - attr_accessor :params - attr_reader :notification - - def initialize(query_string, options = {}) - @params = parse(query_string) - @options = options - end - - # Successful by default. Overridden in the child class - def success? - true - end - - # Not cancelled by default. Overridden in the child class. - def cancelled? - false - end - - def message - - end - - def parse(query_string) - return {} if query_string.blank? - - query_string.split('&').inject({}) do |memo, chunk| - next if chunk.empty? - key, value = chunk.split('=', 2) - next if key.empty? - value = value.nil? ? nil : CGI.unescape(value) - memo[CGI.unescape(key)] = value - memo - end - end - end - end - end -end diff --git a/lib/active_merchant/billing/integrations/robokassa.rb b/lib/active_merchant/billing/integrations/robokassa.rb deleted file mode 100644 index 1b27ffed2e4..00000000000 --- a/lib/active_merchant/billing/integrations/robokassa.rb +++ /dev/null @@ -1,49 +0,0 @@ -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - - # Documentation: http://robokassa.ru/Doc/En/Interface.aspx - module Robokassa - autoload :Helper, File.dirname(__FILE__) + '/robokassa/helper.rb' - autoload :Notification, File.dirname(__FILE__) + '/robokassa/notification.rb' - autoload :Return, File.dirname(__FILE__) + '/robokassa/return.rb' - autoload :Common, File.dirname(__FILE__) + '/robokassa/common.rb' - - # Overwrite this if you want to change the Robokassa test url - mattr_accessor :test_url - self.test_url = 'http://test.robokassa.ru/Index.aspx' - - # Overwrite this if you want to change the Robokassa production url - mattr_accessor :production_url - self.production_url = 'https://merchant.roboxchange.com/Index.aspx' - - mattr_accessor :signature_parameter_name - self.signature_parameter_name = 'SignatureValue' - - def self.service_url - mode = ActiveMerchant::Billing::Base.integration_mode - case mode - when :production - self.production_url - when :test - self.test_url - else - raise StandardError, "Integration mode set to an invalid value: #{mode}" - end - end - - def self.helper(order, account, options = {}) - Helper.new(order, account, options) - end - - def self.notification(query_string, options = {}) - Notification.new(query_string, options) - end - - def self.return(query_string) - Return.new(query_string) - end - end - end - end -end diff --git a/lib/active_merchant/billing/integrations/robokassa/common.rb b/lib/active_merchant/billing/integrations/robokassa/common.rb deleted file mode 100644 index 6c8a79146cc..00000000000 --- a/lib/active_merchant/billing/integrations/robokassa/common.rb +++ /dev/null @@ -1,19 +0,0 @@ -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - module Robokassa - module Common - def generate_signature_string - custom_param_keys = params.keys.select {|key| key =~ /^shp/}.sort - custom_params = custom_param_keys.map {|key| "#{key}=#{params[key]}"} - [main_params, secret, custom_params.compact].flatten.join(':') - end - - def generate_signature - Digest::MD5.hexdigest(generate_signature_string) - end - end - end - end - end -end diff --git a/lib/active_merchant/billing/integrations/robokassa/helper.rb b/lib/active_merchant/billing/integrations/robokassa/helper.rb deleted file mode 100644 index 58102958ceb..00000000000 --- a/lib/active_merchant/billing/integrations/robokassa/helper.rb +++ /dev/null @@ -1,50 +0,0 @@ -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - module Robokassa - class Helper < ActiveMerchant::Billing::Integrations::Helper - include Common - - def initialize(order, account, options = {}) - @md5secret = options.delete(:secret) - super - end - - def form_fields - @fields.merge(ActiveMerchant::Billing::Integrations::Robokassa.signature_parameter_name => generate_signature) - end - - def main_params - [:account, :amount, :order].map {|key| @fields[mappings[key]]} - end - - def params - @fields - end - - def secret - @md5secret - end - - def method_missing(method_id, *args) - method_id = method_id.to_s.gsub(/=$/, '') - - # support for robokassa custom parameters - if method_id =~ /^shp/ - add_field method_id, args.last - end - - super - end - - mapping :account, 'MrchLogin' - mapping :amount, 'OutSum' - mapping :currency, 'IncCurrLabel' - mapping :order, 'InvId' - mapping :description, 'Desc' - mapping :email, 'Email' - end - end - end - end -end diff --git a/lib/active_merchant/billing/integrations/robokassa/notification.rb b/lib/active_merchant/billing/integrations/robokassa/notification.rb deleted file mode 100644 index bc4de6e7fe8..00000000000 --- a/lib/active_merchant/billing/integrations/robokassa/notification.rb +++ /dev/null @@ -1,55 +0,0 @@ -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - module Robokassa - class Notification < ActiveMerchant::Billing::Integrations::Notification - include Common - - def self.recognizes?(params) - params.has_key?('InvId') && params.has_key?('OutSum') - end - - def complete? - true - end - - def amount - BigDecimal.new(gross) - end - - def item_id - params['InvId'] - end - - def security_key - params[ActiveMerchant::Billing::Integrations::Robokassa.signature_parameter_name].to_s.downcase - end - - def gross - params['OutSum'] - end - - def status - 'success' - end - - def secret - @options[:secret] - end - - def main_params - [gross, item_id] - end - - def acknowledge - security_key == generate_signature - end - - def success_response(*args) - "OK#{item_id}" - end - end - end - end - end -end diff --git a/lib/active_merchant/billing/integrations/robokassa/return.rb b/lib/active_merchant/billing/integrations/robokassa/return.rb deleted file mode 100644 index c9321433ccd..00000000000 --- a/lib/active_merchant/billing/integrations/robokassa/return.rb +++ /dev/null @@ -1,17 +0,0 @@ -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - module Robokassa - class Return < ActiveMerchant::Billing::Integrations::Return - def item_id - @params['InvId'] - end - - def amount - @params['OutSum'] - end - end - end - end - end -end diff --git a/lib/active_merchant/billing/integrations/sage_pay_form.rb b/lib/active_merchant/billing/integrations/sage_pay_form.rb deleted file mode 100644 index 48c092da72a..00000000000 --- a/lib/active_merchant/billing/integrations/sage_pay_form.rb +++ /dev/null @@ -1,37 +0,0 @@ -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - module SagePayForm - autoload :Helper, File.dirname(__FILE__) + '/sage_pay_form/helper.rb' - autoload :Return, File.dirname(__FILE__) + '/sage_pay_form/return.rb' - autoload :Notification, File.dirname(__FILE__) + '/sage_pay_form/notification.rb' - autoload :Encryption, File.dirname(__FILE__) + '/sage_pay_form/encryption.rb' - - mattr_accessor :production_url - mattr_accessor :test_url - mattr_accessor :simulate_url - self.production_url = 'https://live.sagepay.com/gateway/service/vspform-register.vsp' - self.test_url = 'https://test.sagepay.com/gateway/service/vspform-register.vsp' - self.simulate_url = 'https://test.sagepay.com/Simulator/VSPFormGateway.asp' - - def self.return(query_string, options = {}) - Return.new(query_string, options) - end - - def self.service_url - mode = ActiveMerchant::Billing::Base.integration_mode - case mode - when :production - self.production_url - when :test - self.test_url - when :simulate - self.simulate_url - else - raise StandardError, "Integration mode set to an invalid value: #{mode}" - end - end - end - end - end -end diff --git a/lib/active_merchant/billing/integrations/sage_pay_form/encryption.rb b/lib/active_merchant/billing/integrations/sage_pay_form/encryption.rb deleted file mode 100644 index 9c933a427c2..00000000000 --- a/lib/active_merchant/billing/integrations/sage_pay_form/encryption.rb +++ /dev/null @@ -1,33 +0,0 @@ -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - module SagePayForm - module Encryption - def sage_encrypt(plaintext, key) - Base64.strict_encode64(sage_encrypt_xor(plaintext, key)) - end - - def sage_decrypt(ciphertext, key) - sage_encrypt_xor(Base64.decode64(ciphertext), key) - end - - def sage_encrypt_salt(min, max) - length = rand(max - min + 1) + min - SecureRandom.base64(length + 4)[0, length] - end - - private - - def sage_encrypt_xor(data, key) - raise 'No key provided' if key.blank? - - key *= (data.bytesize.to_f / key.bytesize.to_f).ceil - key = key[0, data.bytesize] - - data.bytes.zip(key.bytes).map { |b1, b2| (b1 ^ b2).chr }.join - end - end - end - end - end -end diff --git a/lib/active_merchant/billing/integrations/sage_pay_form/helper.rb b/lib/active_merchant/billing/integrations/sage_pay_form/helper.rb deleted file mode 100644 index b0295dbfcfe..00000000000 --- a/lib/active_merchant/billing/integrations/sage_pay_form/helper.rb +++ /dev/null @@ -1,136 +0,0 @@ -require 'uri' - -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - module SagePayForm - class Helper < ActiveMerchant::Billing::Integrations::Helper - include Encryption - - mapping :credential2, 'EncryptKey' - - mapping :account, 'Vendor' - mapping :amount, 'Amount' - mapping :currency, 'Currency' - - mapping :order, 'VendorTxCode' - - mapping :customer, - :first_name => 'BillingFirstnames', - :last_name => 'BillingSurname', - :email => 'CustomerEMail', - :phone => 'BillingPhone', - :send_email_confirmation => 'SendEmail' - - mapping :billing_address, - :city => 'BillingCity', - :address1 => 'BillingAddress1', - :address2 => 'BillingAddress2', - :state => 'BillingState', - :zip => 'BillingPostCode', - :country => 'BillingCountry' - - mapping :shipping_address, - :city => 'DeliveryCity', - :address1 => 'DeliveryAddress1', - :address2 => 'DeliveryAddress2', - :state => 'DeliveryState', - :zip => 'DeliveryPostCode', - :country => 'DeliveryCountry' - - mapping :return_url, 'SuccessURL' - mapping :description, 'Description' - - class_attribute :referrer_id - - def shipping_address(params = {}) - @shipping_address_set = true unless params.empty? - - params.each do |k, v| - field = mappings[:shipping_address][k] - add_field(field, v) unless field.nil? - end - end - - def map_billing_address_to_shipping_address - %w(City Address1 Address2 State PostCode Country).each do |field| - fields["Delivery#{field}"] = fields["Billing#{field}"] - end - end - - def form_fields - map_billing_address_to_shipping_address unless @shipping_address_set - - fields['DeliveryFirstnames'] ||= fields['BillingFirstnames'] - fields['DeliverySurname'] ||= fields['BillingSurname'] - - fields['FailureURL'] ||= fields['SuccessURL'] - - fields['BillingPostCode'] ||= "0000" - fields['DeliveryPostCode'] ||= "0000" - - crypt_skip = ['Vendor', 'EncryptKey', 'SendEmail'] - crypt_skip << 'BillingState' unless fields['BillingCountry'] == 'US' - crypt_skip << 'DeliveryState' unless fields['DeliveryCountry'] == 'US' - crypt_skip << 'CustomerEMail' unless fields['SendEmail'] - key = fields['EncryptKey'] - @crypt ||= create_crypt_field(fields.except(*crypt_skip), key) - - result = { - 'VPSProtocol' => '2.23', - 'TxType' => 'PAYMENT', - 'Vendor' => @fields['Vendor'], - 'Crypt' => @crypt - } - result['ReferrerID'] = referrer_id if referrer_id - result - end - - private - - def create_crypt_field(fields, key) - parts = fields.map { |k, v| "#{k}=#{sanitize(k, v)}" unless v.nil? }.compact.shuffle - parts.unshift(sage_encrypt_salt(key.length, key.length * 2)) - sage_encrypt(parts.join('&'), key) - end - - def sanitize(key, value) - reject = exact = nil - - case key - when /URL$/ - # allow all - when 'VendorTxCode' - reject = /[^A-Za-z0-9{}._-]+/ - when /[Nn]ames?$/ - reject = %r{[^[:alpha:] /\\.'-]+} - when /(?:Address[12]|City)$/ - reject = %r{[^[:alnum:] +'/\\:,.\n()-]+} - when /PostCode$/ - reject = /[^A-Za-z0-9 -]+/ - when /Phone$/ - reject = /[^0-9A-Za-z+ ()-]+/ - when 'Currency' - exact = /^[A-Z]{3}$/ - when /State$/ - exact = /^[A-Z]{2}$/ - when 'Description' - value = value[0...100] - else - reject = /&+/ - end - - if exact - raise ArgumentError, "Invalid value for #{key}: #{value.inspect}" unless value =~ exact - value - elsif reject - value.gsub(reject, ' ') - else - value - end - end - end - end - end - end -end diff --git a/lib/active_merchant/billing/integrations/sage_pay_form/notification.rb b/lib/active_merchant/billing/integrations/sage_pay_form/notification.rb deleted file mode 100644 index f9dc672d78c..00000000000 --- a/lib/active_merchant/billing/integrations/sage_pay_form/notification.rb +++ /dev/null @@ -1,210 +0,0 @@ -require 'net/http' - -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - module SagePayForm - class Notification < ActiveMerchant::Billing::Integrations::Notification - class CryptError < StandardError; end - - include Encryption - - def initialize(post_data, options) - super - load_crypt_params(params['crypt'], options[:credential2]) - end - - # Was the transaction complete? - def complete? - status_code == 'OK' - end - - # Was the transaction cancelled? - # Unfortunately, we can't distinguish "user abort" from "idle too long". - def cancelled? - status_code == 'ABORT' - end - - # Text version of #complete?, since we don't support Pending. - def status - complete? ? 'Completed' : 'Failed' - end - - # Status of transaction. List of possible values: - # <tt>OK</tt>:: Transaction completed successfully. - # <tt>NOTAUTHED</tt>:: Incorrect card details / insufficient funds. - # <tt>MALFORMED</tt>:: Invalid input data. - # <tt>INVALID</tt>:: Valid input data, but some fields are incorrect. - # <tt>ABORT</tt>:: User hit cancel button or went idle for 15+ minutes. - # <tt>REJECTED</tt>:: Rejected by account fraud screening rules. - # <tt>AUTHENTICATED</tt>:: Authenticated card details secured at SagePay. - # <tt>REGISTERED</tt>:: Non-authenticated card details secured at SagePay. - # <tt>ERROR</tt>:: Problem internal to SagePay. - def status_code - params['Status'] - end - - # Check this if #completed? is false. - def message - params['StatusDetail'] - end - - # Vendor-supplied code (:order mapping). - def item_id - params['VendorTxCode'] - end - - # Internal SagePay code, typically "{LONG-UUID}". - def transaction_id - params['VPSTxId'] - end - - # Authorization number (only if #completed?). - def auth_id - params['TxAuthNo'] - end - - # Total amount (no fees). - def gross - params['Amount'] - end - - # AVS and CV2 check results. Possible values: - # <tt>ALL MATCH</tt>:: - # <tt>SECURITY CODE MATCH ONLY</tt>:: - # <tt>ADDRESS MATCH ONLY</tt>:: - # <tt>NO DATA MATCHES</tt>:: - # <tt>DATA NOT CHECKED</tt>:: - def avs_cv2_result - params['AVSCV2'] - end - - # Numeric address check. Possible values: - # <tt>NOTPROVIDED</tt>:: - # <tt>NOTCHECKED</tt>:: - # <tt>MATCHED</tt>:: - # <tt>NOTMATCHED</tt>:: - def address_result - params['AddressResult'] - end - - # Post code check. Possible values: - # <tt>NOTPROVIDED</tt>:: - # <tt>NOTCHECKED</tt>:: - # <tt>MATCHED</tt>:: - # <tt>NOTMATCHED</tt>:: - def post_code_result - params['PostCodeResult'] - end - - # CV2 code check. Possible values: - # <tt>NOTPROVIDED</tt>:: - # <tt>NOTCHECKED</tt>:: - # <tt>MATCHED</tt>:: - # <tt>NOTMATCHED</tt>:: - def cv2_result - params['CV2Result'] - end - - # Was the Gift Aid box checked? - def gift_aid? - params['GiftAid'] == '1' - end - - # Result of 3D Secure checks. Possible values: - # <tt>OK</tt>:: Authenticated correctly. - # <tt>NOTCHECKED</tt>:: Authentication not performed. - # <tt>NOTAVAILABLE</tt>:: Card not auth-capable, or auth is otherwise impossible. - # <tt>NOTAUTHED</tt>:: User failed authentication. - # <tt>INCOMPLETE</tt>:: Authentication unable to complete. - # <tt>ERROR</tt>:: Unable to attempt authentication due to data / service errors. - def buyer_auth_result - params['3DSecureStatus'] - end - - # Encoded 3D Secure result code. - def buyer_auth_result_code - params['CAVV'] - end - - # Address confirmation status. PayPal only. Possible values: - # <tt>NONE</tt>:: - # <tt>CONFIRMED</tt>:: - # <tt>UNCONFIRMED</tt>:: - def address_status - params['AddressStatus'] - end - - # Payer verification. Undocumented. - def payer_verified? - params['PayerStatus'] == 'VERIFIED' - end - - # Credit card type. Possible values: - # <tt>VISA</tt>:: Visa - # <tt>MC</tt>:: MasterCard - # <tt>DELTA</tt>:: Delta - # <tt>SOLO</tt>:: Solo - # <tt>MAESTRO</tt>:: Maestro (UK and International) - # <tt>UKE</tt>:: Visa Electron - # <tt>AMEX</tt>:: American Express - # <tt>DC</tt>:: Diners Club - # <tt>JCB</tt>:: JCB - # <tt>LASER</tt>:: Laser - # <tt>PAYPAL</tt>:: PayPal - def credit_card_type - params['CardType'] - end - - # Last four digits of credit card. - def credit_card_last_4_digits - params['Last4Digits'] - end - - # Used by composition methods, but not supplied by SagePay. - def currency - nil - end - - def test? - false - end - - def acknowledge - true - end - - private - - def load_crypt_params(crypt, key) - raise MissingCryptData if crypt.blank? - raise MissingCryptKey if key.blank? - - crypt_data = sage_decrypt(crypt.gsub(' ', '+'), key) - raise InvalidCryptData unless crypt_data =~ /(^|&)Status=/ - - params.clear - parse(crypt_data) - end - - class MissingCryptKey < CryptError - def message - 'No merchant decryption key supplied' - end - end - class MissingCryptData < CryptError - def message - 'No data received from SagePay' - end - end - class InvalidCryptData < CryptError - def message - 'Invalid data received from SagePay' - end - end - - end - end - end - end -end diff --git a/lib/active_merchant/billing/integrations/sage_pay_form/return.rb b/lib/active_merchant/billing/integrations/sage_pay_form/return.rb deleted file mode 100644 index b2a2432ea9c..00000000000 --- a/lib/active_merchant/billing/integrations/sage_pay_form/return.rb +++ /dev/null @@ -1,31 +0,0 @@ -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - module SagePayForm - class Return < ActiveMerchant::Billing::Integrations::Return - - def initialize(query_string, options) - begin - @notification = Notification.new(query_string, options) - rescue Notification::CryptError => e - @message = e.message - end - end - - def success? - @notification && @notification.complete? - end - - def cancelled? - @notification && @notification.cancelled? - end - - def message - @message || @notification.message - end - - end - end - end - end -end diff --git a/lib/active_merchant/billing/integrations/two_checkout.rb b/lib/active_merchant/billing/integrations/two_checkout.rb deleted file mode 100644 index e5733aad3a9..00000000000 --- a/lib/active_merchant/billing/integrations/two_checkout.rb +++ /dev/null @@ -1,44 +0,0 @@ -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - module TwoCheckout - autoload 'Helper', File.dirname(__FILE__) + '/two_checkout/helper' - autoload 'Return', File.dirname(__FILE__) + '/two_checkout/return' - autoload 'Notification', File.dirname(__FILE__) + '/two_checkout/notification' - - mattr_accessor :payment_routine - self.payment_routine = :single_page - - def self.service_url - case self.payment_routine - when :multi_page - 'https://www.2checkout.com/checkout/purchase' - when :single_page - 'https://www.2checkout.com/checkout/spurchase' - else - raise StandardError, "Integration payment routine set to an invalid value: #{self.payment_routine}" - end - end - - def self.service_url=(service_url) - # Note: do not use this method, it is here for backward compatibility - # Use the payment_routine method to change service_url - if service_url =~ /spurchase/ - self.payment_routine = :single_page - else - self.payment_routine = :multi_page - end - end - - - def self.notification(post, options = {}) - Notification.new(post) - end - - def self.return(query_string, options = {}) - Return.new(query_string) - end - end - end - end -end diff --git a/lib/active_merchant/billing/integrations/two_checkout/helper.rb b/lib/active_merchant/billing/integrations/two_checkout/helper.rb deleted file mode 100644 index 077b9ae424d..00000000000 --- a/lib/active_merchant/billing/integrations/two_checkout/helper.rb +++ /dev/null @@ -1,91 +0,0 @@ -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - module TwoCheckout - class Helper < ActiveMerchant::Billing::Integrations::Helper - def initialize(order, account, options = {}) - super - add_field('fixed', 'Y') - - if ActiveMerchant::Billing::Base.integration_mode == :test || options[:test] - add_field('demo', 'Y') - end - end - - # The 2checkout vendor account number - mapping :account, 'sid' - - # The total amount to be billed, in decimal form, without a currency symbol. (8 characters, decimal, 2 characters: Example: 99999999.99) - mapping :amount, 'total' - - # Pass your order id if you are using Third Part Cart Parameters. (128 characters max) - mapping :order, 'cart_order_id' - - # Pass your order id if you are using the Pass Through Products Parameters. (50 characters max) - mapping :invoice, 'merchant_order_id' - - # Left here for backward compatibility, do not use. The line_item method will add automatically. - mapping :mode, 'mode' - - mapping :customer, :email => 'email', - :phone => 'phone' - - mapping :billing_address, :city => 'city', - :address1 => 'street_address', - :address2 => 'street_address2', - :state => 'state', - :zip => 'zip', - :country => 'country' - - mapping :shipping_address, :city => 'ship_city', - :address1 => 'ship_street_address', - :state => 'ship_state', - :zip => 'ship_zip', - :country => 'ship_country' - - # Does nothing, since we've disabled the Continue Shopping button by using the fixed = Y field - mapping :return_url, 'return_url' - - # Approved URL path - mapping :notification_url, 'x_receipt_link_url' - - def customer(params = {}) - add_field(mappings[:customer][:email], params[:email]) - add_field(mappings[:customer][:phone], params[:phone]) - add_field('card_holder_name', "#{params[:first_name]} #{params[:last_name]}") - end - - # Uses Pass Through Product Parameters to pass in lineitems. - # (must mark tanigble sales as shipped to settle the transaction) - def line_item(params = {}) - add_field('mode', '2CO') - (max_existing_line_item_id = form_fields.keys.map do |key| - i = key.to_s[/^li_(\d+)_/, 1] - (i && i.to_i) - end.compact.max || 0) - - line_item_id = max_existing_line_item_id + 1 - params.each do |key, value| - add_field("li_#{line_item_id}_#{key}", value) - end - end - - # Uses Third Party Cart parameter set to pass in lineitem details. - # (sales settle automatically) - def auto_settle(params = {}) - add_field('id_type', '1') - (max_existing_line_item_id = form_fields.keys.map do |key| - i = key.to_s[/^c_prod_(\d+)/, 1] - (i && i.to_i) - end.compact.max || 0) - - line_item_id = max_existing_line_item_id + 1 - params.each do |key, value| - add_field("c_#{key}_#{line_item_id}", value) - end - end - end - end - end - end -end diff --git a/lib/active_merchant/billing/integrations/two_checkout/notification.rb b/lib/active_merchant/billing/integrations/two_checkout/notification.rb deleted file mode 100644 index 8ba211b3459..00000000000 --- a/lib/active_merchant/billing/integrations/two_checkout/notification.rb +++ /dev/null @@ -1,139 +0,0 @@ -require 'net/http' -require 'base64' -require 'digest/md5' - -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - module TwoCheckout - class Notification < ActiveMerchant::Billing::Integrations::Notification - # card_holder_name - Provides the customer’s name. - # city - Provides the customer’s city. - # country - Provides the customer’s country. - # credit_card_processed - This parameter will always be passed back as Y. - # demo - Defines if an order was live, or if the order was a demo order. If the order was a demo, the MD5 hash will fail. - # email - Provides the email address the customer provided when placing the order. - # fixed - This parameter will only be passed back if it was passed into the purchase routine. - # ip_country - Provides the customer’s IP location. - # key - An MD5 hash used to confirm the validity of a sale. - # lang - Customer language - # merchant_order_id - The order ID you had assigned to the order. - # order_number - The 2Checkout order number associated with the order. - # invoice_id - The 2Checkout invoice number. - # pay_method - Provides seller with the customer’s payment method. CC for Credit Card, PPI for PayPal. - # phone - Provides the phone number the customer provided when placing the order. - # ship_name - Provides the ship to name for the order. - # ship_street_address - Provides ship to address. - # ship_street_address2 - Provides more detailed shipping address if this information was provided by the customer. - # ship_city - Provides ship to city. - # ship_state - Provides ship to state. - # ship_zip - Ship Zip - - # Pass Through Products Only - # li_#_name - Name of the corresponding lineitem. - # li_#_quantity - Quantity of the corresponding lineitem. - # li_#_price - Price of the corresponding lineitem. - # li_#_tangible - Specifies if the corresponding li_#_type is a tangible or intangible. ‘Y’ OR ‘N’ - # li_#_product_id - ID of the corresponding lineitem. - # li_#_product_description - Description of the corresponding lineitem. - # li_#_recurrence - # WEEK | MONTH | YEAR – always singular. - # li_#_duration - Forever or # WEEK | MONTH | YEAR – always singular, defaults to Forever. - # li_#_startup_fee - Amount in account pricing currency. - # li_#_option_#_name - Name of option. 64 characters max – cannot include '<' or '>'. - # li_#_option_#_value - Name of option. 64 characters max – cannot include '<' or '>'. - # li_#_option_#_surcharge - Amount in account pricing currency. - - #Third Party Cart Only - # cart_order_id - The order ID you had assigned to the order. - - # Allow seller to define default currency (should match 2Checkout account pricing currency) - def currency - 'USD' - end - - def complete? - status == 'Completed' - end - - # Third Party Cart parameters will return 'card_order_id' - # Pass Through Product parameters will only return 'merchant_order_id' - def item_id - if (params['cart_order_id'].nil?) - params['merchant_order_id'] - else - params['cart_order_id'] - end - end - - # 2Checkout Sale ID - def transaction_id - params['order_number'] - end - - def received_at - params[''] - end - - #Customer Email - def payer_email - params['email'] - end - - def receiver_email - params[''] - end - - # The MD5 Hash - def security_key - params['key'] - end - - # The money amount we received in X.2 decimal. - def gross - params['total'] - end - - # Was this a test transaction? # Use the hash - # Please note 2Checkout forces the order number computed in the hash to '1' on demo sales. - def test? - params['demo'] == 'Y' - end - - # 2Checkout only returns 'Y' for this parameter. If the sale is not authorized, no passback occurs. - def status - case params['credit_card_processed'] - when 'Y' - 'Completed' - else - 'Failed' - end - end - - # Secret Word defined in 2Checkout account - def secret - @options[:credential2] - end - - # Checks against MD5 Hash - def acknowledge - return false if security_key.blank? - - Digest::MD5.hexdigest("#{secret}#{params['sid']}#{transaction_id}#{gross}").upcase == security_key.upcase - end - - private - - # Parses Header Redirect Query String - def parse(post) - @raw = post.to_s - for line in @raw.split('&') - key, value = *line.scan( %r{^(\w+)\=(.*)$} ).flatten - params[key] = CGI.unescape(value || '') - end - end - - end - end - end - end -end diff --git a/lib/active_merchant/billing/integrations/two_checkout/return.rb b/lib/active_merchant/billing/integrations/two_checkout/return.rb deleted file mode 100644 index 327d1a319e1..00000000000 --- a/lib/active_merchant/billing/integrations/two_checkout/return.rb +++ /dev/null @@ -1,17 +0,0 @@ -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - module TwoCheckout - class Return < ActiveMerchant::Billing::Integrations::Return - def success? - params['credit_card_processed'] == 'Y' - end - - def message - - end - end - end - end - end -end diff --git a/lib/active_merchant/billing/integrations/valitor.rb b/lib/active_merchant/billing/integrations/valitor.rb deleted file mode 100644 index 20a5a8ca3cf..00000000000 --- a/lib/active_merchant/billing/integrations/valitor.rb +++ /dev/null @@ -1,33 +0,0 @@ -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - module Valitor - autoload :Return, 'active_merchant/billing/integrations/valitor/return.rb' - autoload :Helper, 'active_merchant/billing/integrations/valitor/helper.rb' - autoload :Notification, 'active_merchant/billing/integrations/valitor/notification.rb' - - mattr_accessor :test_url - self.test_url = 'https://testvefverslun.valitor.is/1_1/' - - mattr_accessor :production_url - self.production_url = 'https://vefverslun.valitor.is/1_1/' - - def self.test? - (ActiveMerchant::Billing::Base.integration_mode == :test) - end - - def self.service_url - (test? ? test_url : production_url) - end - - def self.notification(params, options={}) - Notification.new(params, options.merge(:test => test?)) - end - - def self.return(query_string, options={}) - Return.new(query_string, options.merge(:test => test?)) - end - end - end - end -end diff --git a/lib/active_merchant/billing/integrations/valitor/helper.rb b/lib/active_merchant/billing/integrations/valitor/helper.rb deleted file mode 100644 index 15630f659de..00000000000 --- a/lib/active_merchant/billing/integrations/valitor/helper.rb +++ /dev/null @@ -1,86 +0,0 @@ -require 'digest/md5' - -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - module Valitor - class Helper < ActiveMerchant::Billing::Integrations::Helper - include RequiresParameters - - DEFAULT_SUCCESS_TEXT = "The transaction has been completed." - - def initialize(order, account, options={}) - options[:currency] ||= 'ISK' - super - add_field 'Adeinsheimild', '0' - add_field 'KaupandaUpplysingar', '0' - add_field 'SlokkvaHaus', '0' - @security_number = options[:credential2] - @amount = options[:amount] - @order = order - end - - mapping :account, 'VefverslunID' - mapping :currency, 'Gjaldmidill' - - mapping :order, 'Tilvisunarnumer' - - mapping :notify_url, 'SlodTokstAdGjaldfaeraServerSide' - mapping :return_url, 'SlodTokstAdGjaldfaera' - mapping :cancel_return_url, 'SlodNotandiHaettirVid' - - mapping :success_text, 'SlodTokstAdGjaldfaeraTexti' - - mapping :language, 'Lang' - - def authorize_only - add_field 'Adeinsheimild', '1' - end - - def collect_customer_info - add_field 'KaupandaUpplysingar', '1' - end - - def hide_header - add_field 'SlokkvaHaus', '1' - end - - def product(id, options={}) - raise ArgumentError, "Product id #{id} is not an integer between 1 and 500" unless id.to_i > 0 && id.to_i <= 500 - requires!(options, :amount, :description) - options.assert_valid_keys([:description, :quantity, :amount, :discount]) - - add_field("Vara_#{id}_Verd", format_amount(options[:amount])) - add_field("Vara_#{id}_Fjoldi", options[:quantity] || "1") - - add_field("Vara_#{id}_Lysing", options[:description]) if options[:description] - add_field("Vara_#{id}_Afslattur", options[:discount] || '0') - - @products ||= [] - @products << id.to_i - end - - def signature - raise ArgumentError, "Security number not set" unless @security_number - parts = [@security_number, @fields['Adeinsheimild']] - @products.sort.uniq.each do |id| - parts.concat(["Vara_#{id}_Fjoldi", "Vara_#{id}_Verd", "Vara_#{id}_Afslattur"].collect{|e| @fields[e]}) - end if @products - parts.concat(%w(VefverslunID Tilvisunarnumer SlodTokstAdGjaldfaera SlodTokstAdGjaldfaeraServerSide Gjaldmidill).collect{|e| @fields[e]}) - Digest::MD5.hexdigest(parts.compact.join('')) - end - - def form_fields - product(1, :amount => @amount, :description => @order) if Array(@products).empty? - @fields[mappings[:success_text]] ||= DEFAULT_SUCCESS_TEXT - @fields.merge('RafraenUndirskrift' => signature) - end - - def format_amount(amount) - amount.to_f.round - end - end - end - end - end -end diff --git a/lib/active_merchant/billing/integrations/valitor/notification.rb b/lib/active_merchant/billing/integrations/valitor/notification.rb deleted file mode 100644 index 6aa643941f5..00000000000 --- a/lib/active_merchant/billing/integrations/valitor/notification.rb +++ /dev/null @@ -1,13 +0,0 @@ -require 'active_merchant/billing/integrations/valitor/response_fields' - -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - module Valitor - class Notification < ActiveMerchant::Billing::Integrations::Notification - include ResponseFields - end - end - end - end -end diff --git a/lib/active_merchant/billing/integrations/valitor/response_fields.rb b/lib/active_merchant/billing/integrations/valitor/response_fields.rb deleted file mode 100644 index 9894644f552..00000000000 --- a/lib/active_merchant/billing/integrations/valitor/response_fields.rb +++ /dev/null @@ -1,97 +0,0 @@ -require 'digest/md5' - -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - module Valitor - module ResponseFields - def success? - status == 'Completed' - end - alias :complete? :success? - - def test? - @options[:test] - end - - def item_id - params['Tilvisunarnumer'] - end - alias :order :item_id - - def transaction_id - params['VefverslunSalaID'] - end - - def currency - nil - end - - def status - "Completed" if acknowledge - end - - def received_at - Time.parse(params['Dagsetning'].to_s) - end - - def gross - "%0.2f" % params['Upphaed'].to_s - end - - def card_type - params['Kortategund'] - end - - def card_last_four - params['KortnumerSidustu'] - end - - def authorization_number - params['Heimildarnumer'] - end - - def transaction_number - params['Faerslunumer'] - end - - def customer_name - params['Nafn'] - end - - def customer_address - params['Heimilisfang'] - end - - def customer_zip - params['Postnumer'] - end - - def customer_city - params['Stadur'] - end - - def customer_country - params['Land'] - end - - def customer_email - params['Tolvupostfang'] - end - - def customer_comment - params['Athugasemdir'] - end - - def password - @options[:credential2] - end - - def acknowledge - password ? Digest::MD5.hexdigest("#{password}#{order}") == params['RafraenUndirskriftSvar'] : true - end - end - end - end - end -end \ No newline at end of file diff --git a/lib/active_merchant/billing/integrations/valitor/return.rb b/lib/active_merchant/billing/integrations/valitor/return.rb deleted file mode 100644 index b81cdfae288..00000000000 --- a/lib/active_merchant/billing/integrations/valitor/return.rb +++ /dev/null @@ -1,13 +0,0 @@ -require 'active_merchant/billing/integrations/valitor/response_fields' - -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - module Valitor - class Return < ActiveMerchant::Billing::Integrations::Return - include ResponseFields - end - end - end - end -end diff --git a/lib/active_merchant/billing/integrations/verkkomaksut.rb b/lib/active_merchant/billing/integrations/verkkomaksut.rb deleted file mode 100644 index 504148e175f..00000000000 --- a/lib/active_merchant/billing/integrations/verkkomaksut.rb +++ /dev/null @@ -1,20 +0,0 @@ -require File.dirname(__FILE__) + '/verkkomaksut/helper.rb' -require File.dirname(__FILE__) + '/verkkomaksut/notification.rb' - -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - - # Usage, see the blog post here: http://blog.kiskolabs.com/post/22374612968/understanding-active-merchant-integrations and E1 API documentation here: http://docs.verkkomaksut.fi/ - module Verkkomaksut - - mattr_accessor :service_url - self.service_url = 'https://payment.verkkomaksut.fi/' - - def self.notification(post) - Notification.new(post) - end - end - end - end -end diff --git a/lib/active_merchant/billing/integrations/verkkomaksut/helper.rb b/lib/active_merchant/billing/integrations/verkkomaksut/helper.rb deleted file mode 100644 index ca88579e8f3..00000000000 --- a/lib/active_merchant/billing/integrations/verkkomaksut/helper.rb +++ /dev/null @@ -1,88 +0,0 @@ -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - module Verkkomaksut - class Helper < ActiveMerchant::Billing::Integrations::Helper - - # Fetches the md5secret and adds MERCHANT_ID and API TYPE to the form - def initialize(order, account, options = {}) - md5secret options.delete(:credential2) - super - add_field("MERCHANT_ID", account) - add_field("TYPE", "E1") - end - - def md5secret(value) - @md5secret = value - end - - # Adds the AUTHCODE to the form - def form_fields - @fields.merge("AUTHCODE" => generate_md5string) - end - - # Calculates the AUTHCODE - def generate_md5string - fields = [@md5secret, @fields["MERCHANT_ID"], @fields["ORDER_NUMBER"], @fields["REFERENCE_NUMBER"], @fields["ORDER_DESCRIPTION"], @fields["CURRENCY"], @fields["RETURN_ADDRESS"], @fields["CANCEL_ADDRESS"], @fields["PENDING_ADDRESS"], - @fields["NOTIFY_ADDRESS"], @fields["TYPE"], @fields["CULTURE"], @fields["PRESELECTED_METHOD"], @fields["MODE"], @fields["VISIBLE_METHODS"], @fields["GROUP"], @fields["CONTACT_TELNO"], @fields["CONTACT_CELLNO"], - @fields["CONTACT_EMAIL"], @fields["CONTACT_FIRSTNAME"], @fields["CONTACT_LASTNAME"], @fields["CONTACT_COMPANY"], @fields["CONTACT_ADDR_STREET"], @fields["CONTACT_ADDR_ZIP"], @fields["CONTACT_ADDR_CITY"], @fields["CONTACT_ADDR_COUNTRY"], @fields["INCLUDE_VAT"], - @fields["ITEMS"]] - - (0..@fields["ITEMS"].to_i-1).each do |i| - fields += [@fields["ITEM_TITLE[#{i}]"], @fields["ITEM_NO[#{i}]"], @fields["ITEM_AMOUNT[#{i}]"], @fields["ITEM_PRICE[#{i}]"], @fields["ITEM_TAX[#{i}]"], @fields["ITEM_DISCOUNT[#{i}]"], @fields["ITEM_TYPE[#{i}]"]] - end - - fields = fields.join("|") - - return Digest::MD5.hexdigest(fields).upcase - end - - # Mappings - mapping :merchant_id, "MERCHANT_ID" - mapping :order, "ORDER_NUMBER" - mapping :reference_number, "REFERENCE_NUMBER" - mapping :customer, :first_name => "CONTACT_FIRSTNAME", - :last_name => "CONTACT_LASTNAME", - :email => "CONTACT_EMAIL", - :phone => "CONTACT_CELLNO", - :tellno => "CONTACT_TELLNO", - :company => "CONTACT_COMPANY" - - - mapping :billing_address, :city => "CONTACT_ADDR_CITY", - :address1 => "CONTACT_ADDR_STREET", - :address2 => "", - :state => "", - :zip => "CONTACT_ADDR_ZIP", - :country => "CONTACT_ADDR_COUNTRY" - - mapping :notify_url, "NOTIFY_ADDRESS" - mapping :currency, "CURRENCY" - mapping :return_url, "RETURN_ADDRESS" - mapping :cancel_return_url, "CANCEL_ADDRESS" - mapping :description, "ORDER_DESCRIPTION" - mapping :tax, "" - mapping :shipping, "" - mapping :include_vat, "INCLUDE_VAT" - mapping :pending_address, "PENDING_ADDRESS" - mapping :culture, "CULTURE" - mapping :items, "ITEMS" - mapping :preselected_method, "PRESELECTED_METHOD" - mapping :mode, "MODE" - mapping :visible_methods, "VISIBLE_METHODS" - mapping :group, "GROUP" - - (0..499.to_i).each do |i| - mapping "item_title_#{i}".to_sym, "ITEM_TITLE[#{i}]" - mapping "item_no_#{i}".to_sym, "ITEM_NO[#{i}]" - mapping "item_amount_#{i}".to_sym, "ITEM_AMOUNT[#{i}]" - mapping "item_price_#{i}".to_sym, "ITEM_PRICE[#{i}]" - mapping "item_tax_#{i}".to_sym, "ITEM_TAX[#{i}]" - mapping "item_discount_#{i}".to_sym, "ITEM_DISCOUNT[#{i}]" - mapping "item_type_#{i}".to_sym, "ITEM_TYPE[#{i}]" - end - end - end - end - end -end diff --git a/lib/active_merchant/billing/integrations/verkkomaksut/notification.rb b/lib/active_merchant/billing/integrations/verkkomaksut/notification.rb deleted file mode 100644 index 466a282308a..00000000000 --- a/lib/active_merchant/billing/integrations/verkkomaksut/notification.rb +++ /dev/null @@ -1,59 +0,0 @@ -require 'net/http' - -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - module Verkkomaksut - class Notification < ActiveMerchant::Billing::Integrations::Notification - - # Is the payment complete or not. Verkkomaksut only has two statuses: random string or 0000000000 which means pending - def complete? - params['PAID'] != "0000000000" - end - - # Order id - def order_id - params['ORDER_NUMBER'] - end - - # Payment method used - def method - params['METHOD'] - end - - # When was this payment received by the client. - def received_at - params['TIMESTAMP'] - end - - # Security key got from Verkkomaksut - def security_key - params['RETURN_AUTHCODE'] - end - - # Another way of asking the payment status - def status - if complete? - "PAID" - else - "PENDING" - end - end - - # Acknowldges the payment. If the authcodes match, returns true. - def acknowledge(authcode) - return_authcode = [params["ORDER_NUMBER"], params["TIMESTAMP"], params["PAID"], params["METHOD"], authcode].join("|") - Digest::MD5.hexdigest(return_authcode).upcase == params["RETURN_AUTHCODE"] - end - private - - def parse(post) - post.each do |key, value| - params[key] = value - end - end - end - end - end - end -end diff --git a/lib/active_merchant/billing/integrations/web_pay.rb b/lib/active_merchant/billing/integrations/web_pay.rb deleted file mode 100644 index 06aca03c324..00000000000 --- a/lib/active_merchant/billing/integrations/web_pay.rb +++ /dev/null @@ -1,45 +0,0 @@ -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - - # Documentation: You will get it after registration steps here: - # http://reg.webpay.by/registration-form.php - module WebPay - autoload :Helper, File.dirname(__FILE__) + '/web_pay/helper.rb' - autoload :Notification, File.dirname(__FILE__) + '/web_pay/notification.rb' - autoload :Common, File.dirname(__FILE__) + '/web_pay/common.rb' - - # Overwrite this if you want to change the WebPay sandbox url - mattr_accessor :test_url - self.test_url = 'https://secure.sandbox.webpay.by:8843' - - # Overwrite this if you want to change the WebPay production url - mattr_accessor :production_url - self.production_url = 'https://secure.webpay.by' - - mattr_accessor :signature_parameter_name - self.signature_parameter_name = 'wsb_signature' - - def self.service_url - mode = ActiveMerchant::Billing::Base.integration_mode - case mode - when :production - self.production_url - when :test - self.test_url - else - raise StandardError, "Integration mode set to an invalid value: #{mode}" - end - end - - def self.helper(order, account, options = {}) - Helper.new(order, account, options) - end - - def self.notification(query_string, options = {}) - Notification.new(query_string, options) - end - end - end - end -end diff --git a/lib/active_merchant/billing/integrations/web_pay/common.rb b/lib/active_merchant/billing/integrations/web_pay/common.rb deleted file mode 100644 index 8a8191f2bf2..00000000000 --- a/lib/active_merchant/billing/integrations/web_pay/common.rb +++ /dev/null @@ -1,50 +0,0 @@ -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - module WebPay - module Common - def generate_signature(type) - string = case type - when :request - request_signature_string - when :notify - notify_signature_string - end - if type != :notify && @fields[mappings[:version]] == '2' - Digest::SHA1.hexdigest(string) - else - Digest::MD5.hexdigest(string) - end - end - - def request_signature_string - [ - @fields[mappings[:seed]], - @fields[mappings[:account]], - @fields[mappings[:order]], - @fields[mappings[:test]], - @fields[mappings[:currency]], - @fields[mappings[:amount]], - secret - ].join - end - - def notify_signature_string - [ - params['batch_timestamp'], - params['currency_id'], - params['amount'], - params['payment_method'], - params['order_id'], - params['site_order_id'], - params['transaction_id'], - params['payment_type'], - params['rrn'], - secret - ].join - end - end - end - end - end -end diff --git a/lib/active_merchant/billing/integrations/web_pay/helper.rb b/lib/active_merchant/billing/integrations/web_pay/helper.rb deleted file mode 100644 index 1082f4355ce..00000000000 --- a/lib/active_merchant/billing/integrations/web_pay/helper.rb +++ /dev/null @@ -1,68 +0,0 @@ -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - module WebPay - class Helper < ActiveMerchant::Billing::Integrations::Helper - include Common - - def initialize(order, account, options = {}) - @md5secret = options.delete(:secret) - @line_item_count = 0 - super - end - - def form_fields - @fields.merge(ActiveMerchant::Billing::Integrations::WebPay.signature_parameter_name => generate_signature(:request)) - end - - def params - @fields - end - - def secret - @md5secret - end - - def add_line_item(options) - options.each do |key, value| - add_field("wsb_invoice_item_#{key}[#{@line_item_count}]", value) - end - - @line_item_count += 1 - end - - def calculate_total - sum = 0 - - @line_item_count.times do |i| - sum += @fields["wsb_invoice_item_quantity[#{i}]"].to_i * @fields["wsb_invoice_item_price[#{i}]"].to_i - end - - sum + @fields[mappings[:tax]].to_i + @fields[mappings[:shipping_price]].to_i - @fields[mappings[:discount_price]].to_i - end - - mapping :scart, '*scart' - mapping :account, 'wsb_storeid' - mapping :store, 'wsb_store' - mapping :order, 'wsb_order_num' - mapping :currency, 'wsb_currency_id' - mapping :version, 'wsb_version' - mapping :language, 'wsb_language_id' - mapping :seed, 'wsb_seed' - mapping :success_url, 'wsb_return_url' - mapping :cancel_url, 'wsb_cancel_return_url' - mapping :notify_url, 'wsb_notify_url' - mapping :test, 'wsb_test' - mapping :tax, 'wsb_tax' - mapping :shipping_name, 'wsb_shipping_name' - mapping :shipping_price, 'wsb_shipping_price' - mapping :discount_name, 'wsb_discount_name' - mapping :discount_price, 'wsb_discount_price' - mapping :amount, 'wsb_total' - mapping :email, 'wsb_email' - mapping :phone, 'wsb_phone' - end - end - end - end -end diff --git a/lib/active_merchant/billing/integrations/web_pay/notification.rb b/lib/active_merchant/billing/integrations/web_pay/notification.rb deleted file mode 100644 index a0230ed663d..00000000000 --- a/lib/active_merchant/billing/integrations/web_pay/notification.rb +++ /dev/null @@ -1,51 +0,0 @@ -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - module WebPay - class Notification < ActiveMerchant::Billing::Integrations::Notification - include Common - - def self.recognizes?(params) - params.has_key?('site_order_id') && params.has_key?('amount') - end - - def complete? - true - end - - def amount - BigDecimal.new(gross) - end - - def item_id - params['site_order_id'] - end - - def security_key - params[ActiveMerchant::Billing::Integrations::WebPay.signature_parameter_name] - end - - def gross - params['amount'] - end - - def status - 'success' - end - - def secret - @options[:secret] - end - - def acknowledge - (security_key == generate_signature(:notify)) - end - - def success_response(*args) - {:nothing => true} - end - end - end - end - end -end diff --git a/lib/active_merchant/billing/integrations/webmoney.rb b/lib/active_merchant/billing/integrations/webmoney.rb deleted file mode 100644 index 3b704846d5f..00000000000 --- a/lib/active_merchant/billing/integrations/webmoney.rb +++ /dev/null @@ -1,43 +0,0 @@ -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - # Documentation: - # http://wiki.webmoney.ru/projects/webmoney/wiki/Web_Merchant_Interface - module Webmoney - autoload :Helper, File.dirname(__FILE__) + '/webmoney/helper.rb' - autoload :Notification, File.dirname(__FILE__) + '/webmoney/notification.rb' - autoload :Return, File.dirname(__FILE__) + '/webmoney/return.rb' - autoload :Common, File.dirname(__FILE__) + '/webmoney/common.rb' - - mattr_accessor :test_url - self.test_url = "https://merchant.webmoney.ru/lmi/payment.asp" - - mattr_accessor :production_url - self.production_url = "https://merchant.webmoney.ru/lmi/payment.asp" - - mattr_accessor :signature_parameter_name - self.signature_parameter_name = 'LMI_HASH' - - def self.service_url - mode = ActiveMerchant::Billing::Base.integration_mode - case mode - when :production - self.production_url - when :test - self.test_url - else - raise StandardError, "Integration mode set to an invalid value: #{mode}" - end - end - - def self.helper(order, account, options = {}) - Helper.new(order, account, options) - end - - def self.notification(query_string, options = {}) - Notification.new(query_string, options) - end - end - end - end -end diff --git a/lib/active_merchant/billing/integrations/webmoney/common.rb b/lib/active_merchant/billing/integrations/webmoney/common.rb deleted file mode 100644 index e6bb96593ac..00000000000 --- a/lib/active_merchant/billing/integrations/webmoney/common.rb +++ /dev/null @@ -1,17 +0,0 @@ -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - module Webmoney - module Common - def generate_signature_string - "#{params['LMI_PAYEE_PURSE']}#{params['LMI_PAYMENT_AMOUNT']}#{params['LMI_PAYMENT_NO']}#{params['LMI_MODE']}#{params['LMI_SYS_INVS_NO']}#{params['LMI_SYS_TRANS_NO']}#{params['LMI_SYS_TRANS_DATE']}#{secret}#{params['LMI_PAYER_PURSE']}#{params['LMI_PAYER_WM']}" - end - - def generate_signature - Digest::MD5.hexdigest(generate_signature_string).upcase - end - end - end - end - end -end diff --git a/lib/active_merchant/billing/integrations/webmoney/helper.rb b/lib/active_merchant/billing/integrations/webmoney/helper.rb deleted file mode 100644 index f8808afce81..00000000000 --- a/lib/active_merchant/billing/integrations/webmoney/helper.rb +++ /dev/null @@ -1,40 +0,0 @@ -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - module Webmoney - class Helper < ActiveMerchant::Billing::Integrations::Helper - include Common - - def initialize(order, account, options = {}) - @webmoney_options = options.dup - options.delete(:description) - options.delete(:fail_url) - options.delete(:success_url) - options.delete(:result_url) - super - @webmoney_options.each do |key, value| - add_field mappings[key], value - end - end - - def form_fields - @fields - end - - def params - @fields - end - - mapping :account, 'LMI_PAYEE_PURSE' - mapping :amount, 'LMI_PAYMENT_AMOUNT' - mapping :order, 'LMI_PAYMENT_NO' - mapping :description, 'LMI_PAYMENT_DESC' - mapping :fail_url, 'LMI_FAIL_URL' - mapping :success_url, 'LMI_SUCCESS_URL' - mapping :result_url, 'LMI_RESULT_URL' - mapping :debug, 'LMI_SIM_MODE' - end - end - end - end -end diff --git a/lib/active_merchant/billing/integrations/webmoney/notification.rb b/lib/active_merchant/billing/integrations/webmoney/notification.rb deleted file mode 100644 index 612f9d1cc0f..00000000000 --- a/lib/active_merchant/billing/integrations/webmoney/notification.rb +++ /dev/null @@ -1,47 +0,0 @@ -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - module Webmoney - class Notification < ActiveMerchant::Billing::Integrations::Notification - include Common - - def recognizes? - (params.has_key?('LMI_PAYMENT_NO') && params.has_key?('LMI_PAYMENT_AMOUNT')) - end - - def amount - BigDecimal.new(gross) - end - - def key_present? - params["LMI_HASH"].present? - end - - def item_id - params['LMI_PAYMENT_NO'] - end - - def gross - params['LMI_PAYMENT_AMOUNT'] - end - - def security_key - params["LMI_HASH"] - end - - def secret - @options[:secret] - end - - def acknowledge - (security_key == generate_signature) - end - - def success_response(*args) - {:nothing => true} - end - end - end - end - end -end diff --git a/lib/active_merchant/billing/integrations/world_pay.rb b/lib/active_merchant/billing/integrations/world_pay.rb deleted file mode 100644 index 73190d31b69..00000000000 --- a/lib/active_merchant/billing/integrations/world_pay.rb +++ /dev/null @@ -1,34 +0,0 @@ -require File.dirname(__FILE__) + '/world_pay/helper.rb' -require File.dirname(__FILE__) + '/world_pay/notification.rb' - -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - module WorldPay - - mattr_accessor :production_url, :test_url - self.production_url = 'https://secure.worldpay.com/wcc/purchase' - self.test_url = 'https://secure-test.worldpay.com/wcc/purchase' - - def self.service_url - case ActiveMerchant::Billing::Base.integration_mode - when :production - self.production_url - when :test - self.test_url - else - raise StandardError, "Integration mode set to an invalid value: #{mode}" - end - end - - def self.notification(post, options = {}) - Notification.new(post, options) - end - - def self.return(post, options = {}) - Return.new(post, options) - end - end - end - end -end diff --git a/lib/active_merchant/billing/integrations/world_pay/helper.rb b/lib/active_merchant/billing/integrations/world_pay/helper.rb deleted file mode 100644 index 68ab9d911dd..00000000000 --- a/lib/active_merchant/billing/integrations/world_pay/helper.rb +++ /dev/null @@ -1,101 +0,0 @@ -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - module WorldPay - class Helper < ActiveMerchant::Billing::Integrations::Helper - mapping :account, 'instId' - mapping :amount, 'amount' - mapping :order, 'cartId' - mapping :currency, 'currency' - - mapping :customer, :email => 'email', - :phone => 'tel' - - mapping :billing_address, :zip => 'postcode', - :country => 'country' - - mapping :description, 'desc' - mapping :notify_url, 'MC_callback' - mapping :return_url, 'MC_return' - - - # WorldPay supports two different test modes - :always_succeed and :always_fail - def initialize(order, account, options = {}) - super - - if ActiveMerchant::Billing::Base.integration_mode == :test || options[:test] - test_mode = case options[:test] - when :always_fail - 101 - when false - 0 - else - 100 - end - add_field('testMode', test_mode.to_s) - elsif ActiveMerchant::Billing::Base.integration_mode == :always_succeed - add_field('testMode', '100') - elsif ActiveMerchant::Billing::Base.integration_mode == :always_fail - add_field('testMode', '101') - end - end - - # WorldPay only supports a single address field so we - # have to concat together - lines are separated using &#10; - def billing_address(params={}) - add_field(mappings[:billing_address][:zip], params[:zip]) - add_field(mappings[:billing_address][:country], lookup_country_code(params[:country])) - - address = [params[:address1], params[:address2], params[:city], params[:state]].compact - add_field('address', address.join('&#10;')) - end - - # WorldPay only supports a single name field so we have to concat - def customer(params={}) - add_field(mappings[:customer][:email], params[:email]) - add_field(mappings[:customer][:phone], params[:phone]) - add_field('name', "#{params[:first_name]} #{params[:last_name]}") - end - - # Support for a MD5 hash of selected fields to prevent tampering - # For futher information read the tech note at the address below: - # http://support.worldpay.com/kb/integration_guides/junior/integration/help/tech_notes/sjig_tn_009.html - def encrypt(secret, fields = [:amount, :currency, :account, :order]) - signature_fields = fields.collect{ |field| mappings[field] } - add_field('signatureFields', signature_fields.join(':')) - - field_values = fields.collect{ |field| form_fields[mappings[field]] } - signature = "#{secret}:#{field_values.join(':')}" - add_field('signature', Digest::MD5.hexdigest(signature)) - end - - # Add a time window for which the payment can be completed. Read the link below for how they work - # http://support.worldpay.com/kb/integration_guides/junior/integration/help/appendicies/sjig_10100.html - def valid_from(from_time) - add_field('authValidFrom', from_time.to_i.to_s + '000') - end - - def valid_to(to_time) - add_field('authValidTo', to_time.to_i.to_s + '000') - end - - # WorldPay supports the passing of custom parameters prefixed with the following: - # C_ : These parameters can be used in the response pages hosted on WorldPay's site - # M_ : These parameters are passed through to the callback script (if enabled) - # MC_ or CM_ : These parameters are availble both in the response and callback contexts - def response_params(params={}) - params.each{|k,v| add_field("C_#{k}",v)} - end - - def callback_params(params={}) - params.each{|k,v| add_field("M_#{k}",v)} - end - - def combined_params(params={}) - params.each{|k,v| add_field("MC_#{k}",v)} - end - end - end - end - end -end \ No newline at end of file diff --git a/lib/active_merchant/billing/integrations/world_pay/notification.rb b/lib/active_merchant/billing/integrations/world_pay/notification.rb deleted file mode 100644 index afd682e9dee..00000000000 --- a/lib/active_merchant/billing/integrations/world_pay/notification.rb +++ /dev/null @@ -1,160 +0,0 @@ -module ActiveMerchant #:nodoc: - module Billing #:nodoc: - module Integrations #:nodoc: - module WorldPay - class Notification < ActiveMerchant::Billing::Integrations::Notification - def complete? - status == 'Completed' - end - - def account - params['instId'] - end - - def item_id - params['cartId'] - end - - def transaction_id - params['transId'] - end - - # Time this payment was received by the client in UTC time. - def received_at - Time.at(params['transTime'].to_i / 1000).utc - end - - # Callback password set in the WorldPay CMS - def security_key - params['callbackPW'] - end - - # the money amount we received in X.2 decimal. - def gross - params['authAmount'] - end - - def currency - params['authCurrency'] - end - - # Was this a test transaction? - def test? - params.key?('testMode') && params['testMode'] != '0' - end - - def status - params['transStatus'] == 'Y' ? 'Completed' : 'Cancelled' - end - - def name - params['name'] - end - - def address - params['address'] - end - - def postcode - params['postcode'] - end - - def country - params['country'] - end - - def phone_number - params['tel'] - end - - def fax_number - params['fax'] - end - - def email_address - params['email'] - end - - def card_type - params['cardType'] - end - - # WorldPay extended fraud checks returned as a 4 character string - # 1st char: Credit card CVV check - # 2nd char: Postcode AVS check - # 3rd char: Address AVS check - # 4th char: Country comparison check - # Possible values are: - # :not_supported - 0 - # :not_checked - 1 - # :matched - 2 - # :not_matched - 4 - # :partial_match - 8 - def cvv_status - return avs_value_to_symbol(params['AVS'][0].chr) - end - - def postcode_status - return avs_value_to_symbol(params['AVS'][1].chr) - end - - def address_status - return avs_value_to_symbol(params['AVS'][2].chr) - end - - def country_status - return avs_value_to_symbol(params['AVS'][3].chr) - end - - def acknowledge - return true - end - - # WorldPay supports the passing of custom parameters through to the callback script - def custom_params - return @custom_params ||= read_custom_params - end - - - private - - # Take the posted data and move the relevant data into a hash - def parse(post) - @raw = post - for line in post.split('&') - key, value = *line.scan( %r{^(\w+)\=(.*)$} ).flatten - params[key] = value - end - end - - # Read the custom params into a hash - def read_custom_params - custom = {} - params.each do |key, value| - if /\A(M_|MC_|CM_)/ === key - custom[key.gsub(/\A(M_|MC_|CM_)/, '').to_sym] = value - end - end - custom - end - - # Convert a AVS value to a symbol - see above for more about AVS - def avs_value_to_symbol(value) - case value.to_s - when '8' - :partial_match - when '4' - :no_match - when '2' - :matched - when '1' - :not_checked - else - :not_supported - end - end - end - end - end - end -end \ No newline at end of file diff --git a/lib/active_merchant/billing/model.rb b/lib/active_merchant/billing/model.rb new file mode 100644 index 00000000000..ba280a1211e --- /dev/null +++ b/lib/active_merchant/billing/model.rb @@ -0,0 +1,30 @@ +require 'active_merchant/billing/compatibility' +require 'active_merchant/empty' + +module ActiveMerchant + module Billing + class Model + include Compatibility::Model + include Empty + + def initialize(attributes = {}) + attributes.each do |key, value| + send("#{key}=", value) + end + end + + def validate + {} + end + + private + + def errors_hash(array) + array.inject({}) do |hash, (attribute, error)| + (hash[attribute] ||= []) << error + hash + end + end + end + end +end diff --git a/lib/active_merchant/billing/network_tokenization_credit_card.rb b/lib/active_merchant/billing/network_tokenization_credit_card.rb new file mode 100644 index 00000000000..0f358948ab8 --- /dev/null +++ b/lib/active_merchant/billing/network_tokenization_credit_card.rb @@ -0,0 +1,39 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class NetworkTokenizationCreditCard < CreditCard + # A +NetworkTokenizationCreditCard+ object represents a tokenized credit card + # using the EMV Network Tokenization specification, http://www.emvco.com/specifications.aspx?id=263. + # + # It includes all fields of the +CreditCard+ class with additional fields for + # verification data that must be given to gateways through existing fields (3DS / EMV). + # + # The only tested usage of this at the moment is with an Apple Pay decrypted PKPaymentToken, + # https://developer.apple.com/library/ios/documentation/PassKit/Reference/PaymentTokenJSON/PaymentTokenJSON.html + + # These are not relevant (verification) or optional (name) for Apple Pay + self.require_verification_value = false + self.require_name = false + + attr_accessor :payment_cryptogram, :eci, :transaction_id + attr_writer :source + + SOURCES = %i(apple_pay android_pay google_pay) + + def source + if defined?(@source) && SOURCES.include?(@source) + @source + else + :apple_pay + end + end + + def credit_card? + true + end + + def type + 'network_tokenization' + end + end + end +end diff --git a/lib/active_merchant/billing/payment_token.rb b/lib/active_merchant/billing/payment_token.rb new file mode 100644 index 00000000000..aea70ff6f66 --- /dev/null +++ b/lib/active_merchant/billing/payment_token.rb @@ -0,0 +1,21 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + # Base class representation of cryptographic payment data tokens that may be used for EMV-style transactions + # like Apple Pay. Payment data may be transmitted via any data type, and may also be padded + # with metadata specific to the cryptographer. This metadata should be parsed and interpreted in concrete + # implementations of your given cryptographer. Like credit cards, you must also return a string representing + # the token's type, like 'apple_pay' or 'stripe' should your target payment gateway process these tokens. + class PaymentToken + attr_reader :payment_data + + def initialize(payment_data, options = {}) + @payment_data = payment_data + @metadata = options.with_indifferent_access + end + + def type + raise NotImplementedError + end + end + end +end diff --git a/lib/active_merchant/billing/rails.rb b/lib/active_merchant/billing/rails.rb new file mode 100644 index 00000000000..afa9eeb973a --- /dev/null +++ b/lib/active_merchant/billing/rails.rb @@ -0,0 +1,3 @@ +require 'active_merchant/billing/compatibility' + +ActiveMerchant::Billing::Compatibility.rails_required! diff --git a/lib/active_merchant/billing/response.rb b/lib/active_merchant/billing/response.rb index 715855511bc..491bb0cab5b 100644 --- a/lib/active_merchant/billing/response.rb +++ b/lib/active_merchant/billing/response.rb @@ -4,7 +4,7 @@ class Error < ActiveMerchantError #:nodoc: end class Response - attr_reader :params, :message, :test, :authorization, :avs_result, :cvv_result + attr_reader :params, :message, :test, :authorization, :avs_result, :cvv_result, :error_code, :emv_authorization def success? @success @@ -23,57 +23,64 @@ def initialize(success, message, params = {}, options = {}) @test = options[:test] || false @authorization = options[:authorization] @fraud_review = options[:fraud_review] + @error_code = options[:error_code] + @emv_authorization = options[:emv_authorization] @avs_result = if options[:avs_result].kind_of?(AVSResult) - options[:avs_result].to_hash - else - AVSResult.new(options[:avs_result]).to_hash + options[:avs_result].to_hash + else + AVSResult.new(options[:avs_result]).to_hash end @cvv_result = if options[:cvv_result].kind_of?(CVVResult) - options[:cvv_result].to_hash - else - CVVResult.new(options[:cvv_result]).to_hash + options[:cvv_result].to_hash + else + CVVResult.new(options[:cvv_result]).to_hash end end end class MultiResponse < Response - def self.run(primary_response = :last, &block) - response = new.tap(&block) - response.primary_response = primary_response - response + def self.run(use_first_response = false, &block) + new(use_first_response).tap(&block) end - attr_reader :responses - attr_writer :primary_response + attr_reader :responses, :primary_response - def initialize + def initialize(use_first_response = false) @responses = [] - @primary_response = :last + @use_first_response = use_first_response + @primary_response = nil end - def process - self << yield if(responses.empty? || success?) + def process(ignore_result=false) + return unless success? + + response = yield + self << response + + unless ignore_result + if(@use_first_response && response.success?) + @primary_response ||= response + else + @primary_response = response + end + end end def <<(response) if response.is_a?(MultiResponse) - response.responses.each{|r| @responses << r} + response.responses.each { |r| @responses << r } else @responses << response end end def success? - @responses.all?{|r| r.success?} - end - - def primary_response - success? && @primary_response == :first ? @responses.first : @responses.last + (primary_response ? primary_response.success? : true) end - %w(params message test authorization avs_result cvv_result test? fraud_review?).each do |m| + %w(params message test authorization avs_result cvv_result error_code emv_authorization test? fraud_review?).each do |m| class_eval %( def #{m} (@responses.empty? ? nil : primary_response.#{m}) diff --git a/lib/active_merchant/connection.rb b/lib/active_merchant/connection.rb new file mode 100644 index 00000000000..e6731ed8566 --- /dev/null +++ b/lib/active_merchant/connection.rb @@ -0,0 +1,195 @@ +require 'uri' +require 'net/http' +require 'net/https' +require 'benchmark' + +module ActiveMerchant + class Connection + using NetHttpSslConnection + include NetworkConnectionRetries + + MAX_RETRIES = 3 + OPEN_TIMEOUT = 60 + READ_TIMEOUT = 60 + VERIFY_PEER = true + CA_FILE = File.expand_path('../certs/cacert.pem', File.dirname(__FILE__)) + CA_PATH = nil + MIN_VERSION = :TLS1_1 + RETRY_SAFE = false + RUBY_184_POST_HEADERS = { 'Content-Type' => 'application/x-www-form-urlencoded' } + + attr_accessor :endpoint + attr_accessor :open_timeout + attr_accessor :read_timeout + attr_accessor :verify_peer + attr_accessor :ssl_version + if Net::HTTP.instance_methods.include?(:min_version=) + attr_accessor :min_version + attr_accessor :max_version + end + attr_reader :ssl_connection + attr_accessor :ca_file + attr_accessor :ca_path + attr_accessor :pem + attr_accessor :pem_password + attr_reader :wiredump_device + attr_accessor :logger + attr_accessor :tag + attr_accessor :ignore_http_status + attr_accessor :max_retries + attr_accessor :proxy_address + attr_accessor :proxy_port + + def initialize(endpoint) + @endpoint = endpoint.is_a?(URI) ? endpoint : URI.parse(endpoint) + @open_timeout = OPEN_TIMEOUT + @read_timeout = READ_TIMEOUT + @retry_safe = RETRY_SAFE + @verify_peer = VERIFY_PEER + @ca_file = CA_FILE + @ca_path = CA_PATH + @max_retries = MAX_RETRIES + @ignore_http_status = false + @ssl_version = nil + if Net::HTTP.instance_methods.include?(:min_version=) + @min_version = MIN_VERSION + @max_version = nil + end + @ssl_connection = {} + @proxy_address = :ENV + @proxy_port = nil + end + + def wiredump_device=(device) + raise ArgumentError, "can't wiredump to frozen #{device.class}" if device&.frozen? + @wiredump_device = device + end + + def request(method, body, headers = {}) + request_start = Process.clock_gettime(Process::CLOCK_MONOTONIC) + + headers = headers.dup + headers['connection'] ||= 'close' + + retry_exceptions(:max_retries => max_retries, :logger => logger, :tag => tag) do + begin + info "connection_http_method=#{method.to_s.upcase} connection_uri=#{endpoint}", tag + + result = nil + + realtime = Benchmark.realtime do + http.start unless http.started? + @ssl_connection = http.ssl_connection + info "connection_ssl_version=#{ssl_connection[:version]} connection_ssl_cipher=#{ssl_connection[:cipher]}", tag + + result = case method + when :get + raise ArgumentError, 'GET requests do not support a request body' if body + http.get(endpoint.request_uri, headers) + when :post + debug body + http.post(endpoint.request_uri, body, RUBY_184_POST_HEADERS.merge(headers)) + when :put + debug body + http.put(endpoint.request_uri, body, headers) + when :patch + debug body + http.patch(endpoint.request_uri, body, headers) + when :delete + # It's kind of ambiguous whether the RFC allows bodies + # for DELETE requests. But Net::HTTP's delete method + # very unambiguously does not. + if body + debug body + req = Net::HTTP::Delete.new(endpoint.request_uri, headers) + req.body = body + http.request(req) + else + http.delete(endpoint.request_uri, headers) + end + else + raise ArgumentError, "Unsupported request method #{method.to_s.upcase}" + end + end + + info '--> %d %s (%d %.4fs)' % [result.code, result.message, result.body ? result.body.length : 0, realtime], tag + debug result.body + result + end + end + ensure + info 'connection_request_total_time=%.4fs' % [Process.clock_gettime(Process::CLOCK_MONOTONIC) - request_start], tag + http.finish if http.started? + end + + private + + def http + @http ||= begin + http = Net::HTTP.new(endpoint.host, endpoint.port, proxy_address, proxy_port) + configure_debugging(http) + configure_timeouts(http) + configure_ssl(http) + configure_cert(http) + http + end + end + + def configure_debugging(http) + http.set_debug_output(wiredump_device) + end + + def configure_timeouts(http) + http.open_timeout = open_timeout + http.read_timeout = read_timeout + end + + def configure_ssl(http) + return unless endpoint.scheme == 'https' + + http.use_ssl = true + http.ssl_version = ssl_version if ssl_version + if http.respond_to?(:min_version=) + http.min_version = min_version if min_version + http.max_version = max_version if max_version + end + + if verify_peer + http.verify_mode = OpenSSL::SSL::VERIFY_PEER + http.ca_file = ca_file + http.ca_path = ca_path + else + http.verify_mode = OpenSSL::SSL::VERIFY_NONE + end + end + + def configure_cert(http) + return if pem.blank? + + http.cert = OpenSSL::X509::Certificate.new(pem) + + if pem_password + http.key = OpenSSL::PKey::RSA.new(pem, pem_password) + else + http.key = OpenSSL::PKey::RSA.new(pem) + end + end + + def debug(message, tag = nil) + log(:debug, message, tag) + end + + def info(message, tag = nil) + log(:info, message, tag) + end + + def error(message, tag = nil) + log(:error, message, tag) + end + + def log(level, message, tag) + message = "[#{tag}] #{message}" if tag + logger&.send(level, message) + end + end +end diff --git a/lib/active_merchant/country.rb b/lib/active_merchant/country.rb new file mode 100644 index 00000000000..eb18ba69665 --- /dev/null +++ b/lib/active_merchant/country.rb @@ -0,0 +1,336 @@ +# encoding: utf-8 + +module ActiveMerchant #:nodoc: + class InvalidCountryCodeError < StandardError + end + + class CountryCodeFormatError < StandardError + end + + class CountryCode + attr_reader :value, :format + def initialize(value) + @value = value.to_s.upcase + detect_format + end + + def to_s + value + end + + private + + def detect_format + case @value + when /^[[:alpha:]]{2}$/ + @format = :alpha2 + when /^[[:alpha:]]{3}$/ + @format = :alpha3 + when /^[[:digit:]]{3}$/ + @format = :numeric + else + raise CountryCodeFormatError, "The country code is not formatted correctly #{@value}" + end + end + end + + class Country + attr_reader :name + + def initialize(options = {}) + @name = options.delete(:name) + @codes = options.collect { |k, v| CountryCode.new(v) } + end + + def code(format) + @codes.detect { |c| c.format == format } + end + + def ==(other) + if other.class == ActiveMerchant::Country + (@name == other.name) + else + super + end + end + + alias eql? == + + def hash + @name.hash + end + + def to_s + @name + end + + COUNTRIES = [ + { alpha2: 'AF', name: 'Afghanistan', alpha3: 'AFG', numeric: '004' }, + { alpha2: 'AL', name: 'Albania', alpha3: 'ALB', numeric: '008' }, + { alpha2: 'DZ', name: 'Algeria', alpha3: 'DZA', numeric: '012' }, + { alpha2: 'AS', name: 'American Samoa', alpha3: 'ASM', numeric: '016' }, + { alpha2: 'AD', name: 'Andorra', alpha3: 'AND', numeric: '020' }, + { alpha2: 'AO', name: 'Angola', alpha3: 'AGO', numeric: '024' }, + { alpha2: 'AI', name: 'Anguilla', alpha3: 'AIA', numeric: '660' }, + { alpha2: 'AQ', name: 'Antarctica', alpha3: 'ATA', numeric: '010' }, + { alpha2: 'AG', name: 'Antigua and Barbuda', alpha3: 'ATG', numeric: '028' }, + { alpha2: 'AR', name: 'Argentina', alpha3: 'ARG', numeric: '032' }, + { alpha2: 'AM', name: 'Armenia', alpha3: 'ARM', numeric: '051' }, + { alpha2: 'AW', name: 'Aruba', alpha3: 'ABW', numeric: '533' }, + { alpha2: 'AU', name: 'Australia', alpha3: 'AUS', numeric: '036' }, + { alpha2: 'AT', name: 'Austria', alpha3: 'AUT', numeric: '040' }, + { alpha2: 'AZ', name: 'Azerbaijan', alpha3: 'AZE', numeric: '031' }, + { alpha2: 'BS', name: 'Bahamas', alpha3: 'BHS', numeric: '044' }, + { alpha2: 'BH', name: 'Bahrain', alpha3: 'BHR', numeric: '048' }, + { alpha2: 'BD', name: 'Bangladesh', alpha3: 'BGD', numeric: '050' }, + { alpha2: 'BB', name: 'Barbados', alpha3: 'BRB', numeric: '052' }, + { alpha2: 'BY', name: 'Belarus', alpha3: 'BLR', numeric: '112' }, + { alpha2: 'BE', name: 'Belgium', alpha3: 'BEL', numeric: '056' }, + { alpha2: 'BZ', name: 'Belize', alpha3: 'BLZ', numeric: '084' }, + { alpha2: 'BJ', name: 'Benin', alpha3: 'BEN', numeric: '204' }, + { alpha2: 'BM', name: 'Bermuda', alpha3: 'BMU', numeric: '060' }, + { alpha2: 'BT', name: 'Bhutan', alpha3: 'BTN', numeric: '064' }, + { alpha2: 'BO', name: 'Bolivia', alpha3: 'BOL', numeric: '068' }, + { alpha2: 'BQ', name: 'Bonaire, Sint Eustatius and Saba', alpha3: 'BES', numeric: '535' }, + { alpha2: 'BA', name: 'Bosnia and Herzegovina', alpha3: 'BIH', numeric: '070' }, + { alpha2: 'BW', name: 'Botswana', alpha3: 'BWA', numeric: '072' }, + { alpha2: 'BV', name: 'Bouvet Island', alpha3: 'BVD', numeric: '074' }, + { alpha2: 'BR', name: 'Brazil', alpha3: 'BRA', numeric: '076' }, + { alpha2: 'IO', name: 'British Indian Ocean Territory', alpha3: 'IOT', numeric: '086' }, + { alpha2: 'BN', name: 'Brunei Darussalam', alpha3: 'BRN', numeric: '096' }, + { alpha2: 'BG', name: 'Bulgaria', alpha3: 'BGR', numeric: '100' }, + { alpha2: 'BF', name: 'Burkina Faso', alpha3: 'BFA', numeric: '854' }, + { alpha2: 'BI', name: 'Burundi', alpha3: 'BDI', numeric: '108' }, + { alpha2: 'KH', name: 'Cambodia', alpha3: 'KHM', numeric: '116' }, + { alpha2: 'CM', name: 'Cameroon', alpha3: 'CMR', numeric: '120' }, + { alpha2: 'CA', name: 'Canada', alpha3: 'CAN', numeric: '124' }, + { alpha2: 'CV', name: 'Cape Verde', alpha3: 'CPV', numeric: '132' }, + { alpha2: 'KY', name: 'Cayman Islands', alpha3: 'CYM', numeric: '136' }, + { alpha2: 'CF', name: 'Central African Republic', alpha3: 'CAF', numeric: '140' }, + { alpha2: 'TD', name: 'Chad', alpha3: 'TCD', numeric: '148' }, + { alpha2: 'CL', name: 'Chile', alpha3: 'CHL', numeric: '152' }, + { alpha2: 'CN', name: 'China', alpha3: 'CHN', numeric: '156' }, + { alpha2: 'CX', name: 'Christmas Island', alpha3: 'CXR', numeric: '162' }, + { alpha2: 'CC', name: 'Cocos (Keeling) Islands', alpha3: 'CCK', numeric: '166' }, + { alpha2: 'CO', name: 'Colombia', alpha3: 'COL', numeric: '170' }, + { alpha2: 'KM', name: 'Comoros', alpha3: 'COM', numeric: '174' }, + { alpha2: 'CG', name: 'Congo', alpha3: 'COG', numeric: '178' }, + { alpha2: 'CD', name: 'Congo, the Democratic Republic of the', alpha3: 'COD', numeric: '180' }, + { alpha2: 'CK', name: 'Cook Islands', alpha3: 'COK', numeric: '184' }, + { alpha2: 'CR', name: 'Costa Rica', alpha3: 'CRI', numeric: '188' }, + { alpha2: 'CI', name: 'Cote D\'Ivoire', alpha3: 'CIV', numeric: '384' }, + { alpha2: 'HR', name: 'Croatia', alpha3: 'HRV', numeric: '191' }, + { alpha2: 'CU', name: 'Cuba', alpha3: 'CUB', numeric: '192' }, + { alpha2: 'CW', name: 'Curaçao', alpha3: 'CUW', numeric: '531' }, + { alpha2: 'CY', name: 'Cyprus', alpha3: 'CYP', numeric: '196' }, + { alpha2: 'CZ', name: 'Czech Republic', alpha3: 'CZE', numeric: '203' }, + { alpha2: 'DK', name: 'Denmark', alpha3: 'DNK', numeric: '208' }, + { alpha2: 'DJ', name: 'Djibouti', alpha3: 'DJI', numeric: '262' }, + { alpha2: 'DM', name: 'Dominica', alpha3: 'DMA', numeric: '212' }, + { alpha2: 'DO', name: 'Dominican Republic', alpha3: 'DOM', numeric: '214' }, + { alpha2: 'EC', name: 'Ecuador', alpha3: 'ECU', numeric: '218' }, + { alpha2: 'EG', name: 'Egypt', alpha3: 'EGY', numeric: '818' }, + { alpha2: 'SV', name: 'El Salvador', alpha3: 'SLV', numeric: '222' }, + { alpha2: 'GQ', name: 'Equatorial Guinea', alpha3: 'GNQ', numeric: '226' }, + { alpha2: 'ER', name: 'Eritrea', alpha3: 'ERI', numeric: '232' }, + { alpha2: 'EE', name: 'Estonia', alpha3: 'EST', numeric: '233' }, + { alpha2: 'ET', name: 'Ethiopia', alpha3: 'ETH', numeric: '231' }, + { alpha2: 'FK', name: 'Falkland Islands (Malvinas)', alpha3: 'FLK', numeric: '238' }, + { alpha2: 'FO', name: 'Faroe Islands', alpha3: 'FRO', numeric: '234' }, + { alpha2: 'FJ', name: 'Fiji', alpha3: 'FJI', numeric: '242' }, + { alpha2: 'FI', name: 'Finland', alpha3: 'FIN', numeric: '246' }, + { alpha2: 'FR', name: 'France', alpha3: 'FRA', numeric: '250' }, + { alpha2: 'GF', name: 'French Guiana', alpha3: 'GUF', numeric: '254' }, + { alpha2: 'PF', name: 'French Polynesia', alpha3: 'PYF', numeric: '258' }, + { alpha2: 'TF', name: 'French Southern Territories', alpha3: 'ATF', numeric: '260' }, + { alpha2: 'GA', name: 'Gabon', alpha3: 'GAB', numeric: '266' }, + { alpha2: 'GM', name: 'Gambia', alpha3: 'GMB', numeric: '270' }, + { alpha2: 'GE', name: 'Georgia', alpha3: 'GEO', numeric: '268' }, + { alpha2: 'DE', name: 'Germany', alpha3: 'DEU', numeric: '276' }, + { alpha2: 'GH', name: 'Ghana', alpha3: 'GHA', numeric: '288' }, + { alpha2: 'GI', name: 'Gibraltar', alpha3: 'GIB', numeric: '292' }, + { alpha2: 'GR', name: 'Greece', alpha3: 'GRC', numeric: '300' }, + { alpha2: 'GL', name: 'Greenland', alpha3: 'GRL', numeric: '304' }, + { alpha2: 'GD', name: 'Grenada', alpha3: 'GRD', numeric: '308' }, + { alpha2: 'GP', name: 'Guadeloupe', alpha3: 'GLP', numeric: '312' }, + { alpha2: 'GU', name: 'Guam', alpha3: 'GUM', numeric: '316' }, + { alpha2: 'GT', name: 'Guatemala', alpha3: 'GTM', numeric: '320' }, + { alpha2: 'GG', name: 'Guernsey', alpha3: 'GGY', numeric: '831' }, + { alpha2: 'GN', name: 'Guinea', alpha3: 'GIN', numeric: '324' }, + { alpha2: 'GW', name: 'Guinea-Bissau', alpha3: 'GNB', numeric: '624' }, + { alpha2: 'GY', name: 'Guyana', alpha3: 'GUY', numeric: '328' }, + { alpha2: 'HT', name: 'Haiti', alpha3: 'HTI', numeric: '332' }, + { alpha2: 'HM', name: 'Heard Island And Mcdonald Islands', alpha3: 'HMD', numeric: '334' }, + { alpha2: 'VA', name: 'Holy See (Vatican City State)', alpha3: 'VAT', numeric: '336' }, + { alpha2: 'HN', name: 'Honduras', alpha3: 'HND', numeric: '340' }, + { alpha2: 'HK', name: 'Hong Kong', alpha3: 'HKG', numeric: '344' }, + { alpha2: 'HU', name: 'Hungary', alpha3: 'HUN', numeric: '348' }, + { alpha2: 'IS', name: 'Iceland', alpha3: 'ISL', numeric: '352' }, + { alpha2: 'IN', name: 'India', alpha3: 'IND', numeric: '356' }, + { alpha2: 'ID', name: 'Indonesia', alpha3: 'IDN', numeric: '360' }, + { alpha2: 'IR', name: 'Iran, Islamic Republic of', alpha3: 'IRN', numeric: '364' }, + { alpha2: 'IQ', name: 'Iraq', alpha3: 'IRQ', numeric: '368' }, + { alpha2: 'IE', name: 'Ireland', alpha3: 'IRL', numeric: '372' }, + { alpha2: 'IM', name: 'Isle Of Man', alpha3: 'IMN', numeric: '833' }, + { alpha2: 'IL', name: 'Israel', alpha3: 'ISR', numeric: '376' }, + { alpha2: 'IT', name: 'Italy', alpha3: 'ITA', numeric: '380' }, + { alpha2: 'JM', name: 'Jamaica', alpha3: 'JAM', numeric: '388' }, + { alpha2: 'JP', name: 'Japan', alpha3: 'JPN', numeric: '392' }, + { alpha2: 'JE', name: 'Jersey', alpha3: 'JEY', numeric: '832' }, + { alpha2: 'JO', name: 'Jordan', alpha3: 'JOR', numeric: '400' }, + { alpha2: 'KZ', name: 'Kazakhstan', alpha3: 'KAZ', numeric: '398' }, + { alpha2: 'KE', name: 'Kenya', alpha3: 'KEN', numeric: '404' }, + { alpha2: 'KI', name: 'Kiribati', alpha3: 'KIR', numeric: '296' }, + { alpha2: 'KP', name: 'Korea, Democratic People\'s Republic of', alpha3: 'PRK', numeric: '408' }, + { alpha2: 'KR', name: 'Korea, Republic of', alpha3: 'KOR', numeric: '410' }, + { alpha2: 'XK', name: 'Kosovo', alpha3: 'XKX', numeric: '900' }, + { alpha2: 'KW', name: 'Kuwait', alpha3: 'KWT', numeric: '414' }, + { alpha2: 'KG', name: 'Kyrgyzstan', alpha3: 'KGZ', numeric: '417' }, + { alpha2: 'LA', name: 'Lao People\'s Democratic Republic', alpha3: 'LAO', numeric: '418' }, + { alpha2: 'LV', name: 'Latvia', alpha3: 'LVA', numeric: '428' }, + { alpha2: 'LB', name: 'Lebanon', alpha3: 'LBN', numeric: '422' }, + { alpha2: 'LS', name: 'Lesotho', alpha3: 'LSO', numeric: '426' }, + { alpha2: 'LR', name: 'Liberia', alpha3: 'LBR', numeric: '430' }, + { alpha2: 'LY', name: 'Libyan Arab Jamahiriya', alpha3: 'LBY', numeric: '434' }, + { alpha2: 'LI', name: 'Liechtenstein', alpha3: 'LIE', numeric: '438' }, + { alpha2: 'LT', name: 'Lithuania', alpha3: 'LTU', numeric: '440' }, + { alpha2: 'LU', name: 'Luxembourg', alpha3: 'LUX', numeric: '442' }, + { alpha2: 'MO', name: 'Macao', alpha3: 'MAC', numeric: '446' }, + { alpha2: 'MK', name: 'Macedonia, the Former Yugoslav Republic of', alpha3: 'MKD', numeric: '807' }, + { alpha2: 'MG', name: 'Madagascar', alpha3: 'MDG', numeric: '450' }, + { alpha2: 'MW', name: 'Malawi', alpha3: 'MWI', numeric: '454' }, + { alpha2: 'MY', name: 'Malaysia', alpha3: 'MYS', numeric: '458' }, + { alpha2: 'MV', name: 'Maldives', alpha3: 'MDV', numeric: '462' }, + { alpha2: 'ML', name: 'Mali', alpha3: 'MLI', numeric: '466' }, + { alpha2: 'MT', name: 'Malta', alpha3: 'MLT', numeric: '470' }, + { alpha2: 'MH', name: 'Marshall Islands', alpha3: 'MHL', numeric: '584' }, + { alpha2: 'MQ', name: 'Martinique', alpha3: 'MTQ', numeric: '474' }, + { alpha2: 'MR', name: 'Mauritania', alpha3: 'MRT', numeric: '478' }, + { alpha2: 'MU', name: 'Mauritius', alpha3: 'MUS', numeric: '480' }, + { alpha2: 'YT', name: 'Mayotte', alpha3: 'MYT', numeric: '175' }, + { alpha2: 'MX', name: 'Mexico', alpha3: 'MEX', numeric: '484' }, + { alpha2: 'FM', name: 'Micronesia, Federated States of', alpha3: 'FSM', numeric: '583' }, + { alpha2: 'MD', name: 'Moldova, Republic of', alpha3: 'MDA', numeric: '498' }, + { alpha2: 'MC', name: 'Monaco', alpha3: 'MCO', numeric: '492' }, + { alpha2: 'MN', name: 'Mongolia', alpha3: 'MNG', numeric: '496' }, + { alpha2: 'ME', name: 'Montenegro', alpha3: 'MNE', numeric: '499' }, + { alpha2: 'MS', name: 'Montserrat', alpha3: 'MSR', numeric: '500' }, + { alpha2: 'MA', name: 'Morocco', alpha3: 'MAR', numeric: '504' }, + { alpha2: 'MZ', name: 'Mozambique', alpha3: 'MOZ', numeric: '508' }, + { alpha2: 'MM', name: 'Myanmar', alpha3: 'MMR', numeric: '104' }, + { alpha2: 'NA', name: 'Namibia', alpha3: 'NAM', numeric: '516' }, + { alpha2: 'NR', name: 'Nauru', alpha3: 'NRU', numeric: '520' }, + { alpha2: 'NP', name: 'Nepal', alpha3: 'NPL', numeric: '524' }, + { alpha2: 'NL', name: 'Netherlands', alpha3: 'NLD', numeric: '528' }, + { alpha2: 'NC', name: 'New Caledonia', alpha3: 'NCL', numeric: '540' }, + { alpha2: 'NZ', name: 'New Zealand', alpha3: 'NZL', numeric: '554' }, + { alpha2: 'NI', name: 'Nicaragua', alpha3: 'NIC', numeric: '558' }, + { alpha2: 'NE', name: 'Niger', alpha3: 'NER', numeric: '562' }, + { alpha2: 'NG', name: 'Nigeria', alpha3: 'NGA', numeric: '566' }, + { alpha2: 'NU', name: 'Niue', alpha3: 'NIU', numeric: '570' }, + { alpha2: 'NF', name: 'Norfolk Island', alpha3: 'NFK', numeric: '574' }, + { alpha2: 'MP', name: 'Northern Mariana Islands', alpha3: 'MNP', numeric: '580' }, + { alpha2: 'NO', name: 'Norway', alpha3: 'NOR', numeric: '578' }, + { alpha2: 'OM', name: 'Oman', alpha3: 'OMN', numeric: '512' }, + { alpha2: 'PK', name: 'Pakistan', alpha3: 'PAK', numeric: '586' }, + { alpha2: 'PW', name: 'Palau', alpha3: 'PLW', numeric: '585' }, + { alpha2: 'PS', name: 'Palestinian Territory, Occupied', alpha3: 'PSE', numeric: '275' }, + { alpha2: 'PA', name: 'Panama', alpha3: 'PAN', numeric: '591' }, + { alpha2: 'PG', name: 'Papua New Guinea', alpha3: 'PNG', numeric: '598' }, + { alpha2: 'PY', name: 'Paraguay', alpha3: 'PRY', numeric: '600' }, + { alpha2: 'PE', name: 'Peru', alpha3: 'PER', numeric: '604' }, + { alpha2: 'PH', name: 'Philippines', alpha3: 'PHL', numeric: '608' }, + { alpha2: 'PN', name: 'Pitcairn', alpha3: 'PCN', numeric: '612' }, + { alpha2: 'PL', name: 'Poland', alpha3: 'POL', numeric: '616' }, + { alpha2: 'PT', name: 'Portugal', alpha3: 'PRT', numeric: '620' }, + { alpha2: 'PR', name: 'Puerto Rico', alpha3: 'PRI', numeric: '630' }, + { alpha2: 'QA', name: 'Qatar', alpha3: 'QAT', numeric: '634' }, + { alpha2: 'RE', name: 'Reunion', alpha3: 'REU', numeric: '638' }, + { alpha2: 'RO', name: 'Romania', alpha3: 'ROU', numeric: '642' }, + { alpha2: 'RO', name: 'Romania', alpha3: 'ROM', numeric: '642' }, + { alpha2: 'RU', name: 'Russian Federation', alpha3: 'RUS', numeric: '643' }, + { alpha2: 'RW', name: 'Rwanda', alpha3: 'RWA', numeric: '646' }, + { alpha2: 'BL', name: 'Saint Barthélemy', alpha3: 'BLM', numeric: '652' }, + { alpha2: 'SH', name: 'Saint Helena', alpha3: 'SHN', numeric: '654' }, + { alpha2: 'KN', name: 'Saint Kitts and Nevis', alpha3: 'KNA', numeric: '659' }, + { alpha2: 'LC', name: 'Saint Lucia', alpha3: 'LCA', numeric: '662' }, + { alpha2: 'MF', name: 'Saint Martin (French part)', alpha3: 'MAF', numeric: '663' }, + { alpha2: 'PM', name: 'Saint Pierre and Miquelon', alpha3: 'SPM', numeric: '666' }, + { alpha2: 'VC', name: 'Saint Vincent and the Grenadines', alpha3: 'VCT', numeric: '670' }, + { alpha2: 'WS', name: 'Samoa', alpha3: 'WSM', numeric: '882' }, + { alpha2: 'SM', name: 'San Marino', alpha3: 'SMR', numeric: '674' }, + { alpha2: 'ST', name: 'Sao Tome and Principe', alpha3: 'STP', numeric: '678' }, + { alpha2: 'SA', name: 'Saudi Arabia', alpha3: 'SAU', numeric: '682' }, + { alpha2: 'SN', name: 'Senegal', alpha3: 'SEN', numeric: '686' }, + { alpha2: 'RS', name: 'Serbia', alpha3: 'SRB', numeric: '688' }, + { alpha2: 'SC', name: 'Seychelles', alpha3: 'SYC', numeric: '690' }, + { alpha2: 'SL', name: 'Sierra Leone', alpha3: 'SLE', numeric: '694' }, + { alpha2: 'SG', name: 'Singapore', alpha3: 'SGP', numeric: '702' }, + { alpha2: 'SX', name: 'Sint Maarten', alpha3: 'SXM', numeric: '534' }, + { alpha2: 'SK', name: 'Slovakia', alpha3: 'SVK', numeric: '703' }, + { alpha2: 'SI', name: 'Slovenia', alpha3: 'SVN', numeric: '705' }, + { alpha2: 'SB', name: 'Solomon Islands', alpha3: 'SLB', numeric: '090' }, + { alpha2: 'SO', name: 'Somalia', alpha3: 'SOM', numeric: '706' }, + { alpha2: 'ZA', name: 'South Africa', alpha3: 'ZAF', numeric: '710' }, + { alpha2: 'GS', name: 'South Georgia and the South Sandwich Islands', alpha3: 'SGS', numeric: '239' }, + { alpha2: 'SS', name: 'South Sudan', alpha3: 'SSD', numeric: '728' }, + { alpha2: 'ES', name: 'Spain', alpha3: 'ESP', numeric: '724' }, + { alpha2: 'LK', name: 'Sri Lanka', alpha3: 'LKA', numeric: '144' }, + { alpha2: 'SD', name: 'Sudan', alpha3: 'SDN', numeric: '729' }, + { alpha2: 'SR', name: 'Suriname', alpha3: 'SUR', numeric: '740' }, + { alpha2: 'SJ', name: 'Svalbard and Jan Mayen', alpha3: 'SJM', numeric: '744' }, + { alpha2: 'SZ', name: 'Swaziland', alpha3: 'SWZ', numeric: '748' }, + { alpha2: 'SE', name: 'Sweden', alpha3: 'SWE', numeric: '752' }, + { alpha2: 'CH', name: 'Switzerland', alpha3: 'CHE', numeric: '756' }, + { alpha2: 'SY', name: 'Syrian Arab Republic', alpha3: 'SYR', numeric: '760' }, + { alpha2: 'TW', name: 'Taiwan, Province of China', alpha3: 'TWN', numeric: '158' }, + { alpha2: 'TJ', name: 'Tajikistan', alpha3: 'TJK', numeric: '762' }, + { alpha2: 'TZ', name: 'Tanzania, United Republic of', alpha3: 'TZA', numeric: '834' }, + { alpha2: 'TH', name: 'Thailand', alpha3: 'THA', numeric: '764' }, + { alpha2: 'TL', name: 'Timor Leste', alpha3: 'TLS', numeric: '626' }, + { alpha2: 'TG', name: 'Togo', alpha3: 'TGO', numeric: '768' }, + { alpha2: 'TK', name: 'Tokelau', alpha3: 'TKL', numeric: '772' }, + { alpha2: 'TO', name: 'Tonga', alpha3: 'TON', numeric: '776' }, + { alpha2: 'TT', name: 'Trinidad and Tobago', alpha3: 'TTO', numeric: '780' }, + { alpha2: 'TN', name: 'Tunisia', alpha3: 'TUN', numeric: '788' }, + { alpha2: 'TR', name: 'Turkey', alpha3: 'TUR', numeric: '792' }, + { alpha2: 'TM', name: 'Turkmenistan', alpha3: 'TKM', numeric: '795' }, + { alpha2: 'TC', name: 'Turks and Caicos Islands', alpha3: 'TCA', numeric: '796' }, + { alpha2: 'TV', name: 'Tuvalu', alpha3: 'TUV', numeric: '798' }, + { alpha2: 'UG', name: 'Uganda', alpha3: 'UGA', numeric: '800' }, + { alpha2: 'UA', name: 'Ukraine', alpha3: 'UKR', numeric: '804' }, + { alpha2: 'AE', name: 'United Arab Emirates', alpha3: 'ARE', numeric: '784' }, + { alpha2: 'GB', name: 'United Kingdom', alpha3: 'GBR', numeric: '826' }, + { alpha2: 'US', name: 'United States', alpha3: 'USA', numeric: '840' }, + { alpha2: 'UM', name: 'United States Minor Outlying Islands', alpha3: 'UMI', numeric: '581' }, + { alpha2: 'UY', name: 'Uruguay', alpha3: 'URY', numeric: '858' }, + { alpha2: 'UZ', name: 'Uzbekistan', alpha3: 'UZB', numeric: '860' }, + { alpha2: 'VU', name: 'Vanuatu', alpha3: 'VUT', numeric: '548' }, + { alpha2: 'VE', name: 'Venezuela', alpha3: 'VEN', numeric: '862' }, + { alpha2: 'VN', name: 'Viet Nam', alpha3: 'VNM', numeric: '704' }, + { alpha2: 'VG', name: 'Virgin Islands, British', alpha3: 'VGB', numeric: '092' }, + { alpha2: 'VI', name: 'Virgin Islands, U.S.', alpha3: 'VIR', numeric: '850' }, + { alpha2: 'WF', name: 'Wallis and Futuna', alpha3: 'WLF', numeric: '876' }, + { alpha2: 'EH', name: 'Western Sahara', alpha3: 'ESH', numeric: '732' }, + { alpha2: 'YE', name: 'Yemen', alpha3: 'YEM', numeric: '887' }, + { alpha2: 'ZM', name: 'Zambia', alpha3: 'ZMB', numeric: '894' }, + { alpha2: 'ZW', name: 'Zimbabwe', alpha3: 'ZWE', numeric: '716' }, + { alpha2: 'AX', name: 'Åland Islands', alpha3: 'ALA', numeric: '248' } + ] + + def self.find(name) + raise InvalidCountryCodeError, 'Cannot lookup country for an empty name' if name.blank? + + case name.length + when 2, 3 + upcase_name = name.upcase + country_code = CountryCode.new(name) + country = COUNTRIES.detect { |c| c[country_code.format] == upcase_name } + else + country = COUNTRIES.detect { |c| c[:name].casecmp(name).zero? } + end + raise InvalidCountryCodeError, "No country could be found for the country #{name}" if country.nil? + Country.new(country.dup) + end + end +end diff --git a/lib/active_merchant/empty.rb b/lib/active_merchant/empty.rb new file mode 100644 index 00000000000..6c5f50b273c --- /dev/null +++ b/lib/active_merchant/empty.rb @@ -0,0 +1,20 @@ +module ActiveMerchant + module Empty + private + + def empty?(value) + case value + when nil + true + when Array, Hash + value.empty? + when String + value.strip.empty? + when Numeric + (value == 0) + else + false + end + end + end +end diff --git a/lib/active_merchant/errors.rb b/lib/active_merchant/errors.rb new file mode 100644 index 00000000000..af4bcb8b1be --- /dev/null +++ b/lib/active_merchant/errors.rb @@ -0,0 +1,35 @@ +module ActiveMerchant #:nodoc: + class ActiveMerchantError < StandardError #:nodoc: + end + + class ConnectionError < ActiveMerchantError # :nodoc: + attr_reader :triggering_exception + + def initialize(message, triggering_exception) + super(message) + @triggering_exception = triggering_exception + end + end + + class RetriableConnectionError < ConnectionError # :nodoc: + end + + class ResponseError < ActiveMerchantError # :nodoc: + attr_reader :response + + def initialize(response, message = nil) + @response = response + @message = message + end + + def to_s + "Failed with #{response.code} #{response.message if response.respond_to?(:message)}" + end + end + + class ClientCertificateError < ActiveMerchantError # :nodoc + end + + class InvalidResponseError < ActiveMerchantError # :nodoc + end +end diff --git a/lib/active_merchant/net_http_ssl_connection.rb b/lib/active_merchant/net_http_ssl_connection.rb new file mode 100644 index 00000000000..c0ae5ce1080 --- /dev/null +++ b/lib/active_merchant/net_http_ssl_connection.rb @@ -0,0 +1,10 @@ +require 'net/http' + +module NetHttpSslConnection + refine Net::HTTP do + def ssl_connection + return {} unless use_ssl? && @socket.present? + { version: @socket.io.ssl_version, cipher: @socket.io.cipher[0] } + end + end +end diff --git a/lib/active_merchant/network_connection_retries.rb b/lib/active_merchant/network_connection_retries.rb new file mode 100644 index 00000000000..09e1b146f30 --- /dev/null +++ b/lib/active_merchant/network_connection_retries.rb @@ -0,0 +1,80 @@ +require 'openssl' + +module ActiveMerchant + module NetworkConnectionRetries + DEFAULT_RETRIES = 3 + DEFAULT_CONNECTION_ERRORS = { + EOFError => 'The remote server dropped the connection', + Errno::ECONNRESET => 'The remote server reset the connection', + Timeout::Error => 'The connection to the remote server timed out', + Errno::ETIMEDOUT => 'The connection to the remote server timed out', + SocketError => 'The connection to the remote server could not be established', + Errno::EHOSTUNREACH => 'The connection to the remote server could not be established', + OpenSSL::SSL::SSLError => 'The SSL connection to the remote server could not be established' + } + + def self.included(base) + base.send(:attr_accessor, :retry_safe) + end + + def retry_exceptions(options={}) + connection_errors = DEFAULT_CONNECTION_ERRORS.merge(options[:connection_exceptions] || {}) + + retry_network_exceptions(options) do + begin + yield + rescue Errno::ECONNREFUSED => e + raise ActiveMerchant::RetriableConnectionError.new('The remote server refused the connection', e) + rescue OpenSSL::X509::CertificateError => e + NetworkConnectionRetries.log(options[:logger], :error, e.message, options[:tag]) + raise ActiveMerchant::ClientCertificateError, 'The remote server did not accept the provided SSL certificate' + rescue Zlib::BufError + raise ActiveMerchant::InvalidResponseError, 'The remote server replied with an invalid response' + rescue *connection_errors.keys => e + raise ActiveMerchant::ConnectionError.new(derived_error_message(connection_errors, e.class), e) + end + end + end + + def self.log(logger, level, message, tag=nil) + tag ||= self.class.to_s + message = "[#{tag}] #{message}" + logger&.send(level, message) + end + + private + + def retry_network_exceptions(options = {}) + initial_retries = options[:max_retries] || DEFAULT_RETRIES + retries = initial_retries + request_start = nil + + begin + request_start = Process.clock_gettime(Process::CLOCK_MONOTONIC) + result = yield + log_with_retry_details(options[:logger], initial_retries-retries + 1, Process.clock_gettime(Process::CLOCK_MONOTONIC) - request_start, 'success', options[:tag]) + result + rescue ActiveMerchant::RetriableConnectionError => e + retries -= 1 + + log_with_retry_details(options[:logger], initial_retries-retries, Process.clock_gettime(Process::CLOCK_MONOTONIC) - request_start, e.message, options[:tag]) + retry unless retries.zero? + raise ActiveMerchant::ConnectionError.new(e.message, e) + rescue ActiveMerchant::ConnectionError, ActiveMerchant::InvalidResponseError => e + retries -= 1 + log_with_retry_details(options[:logger], initial_retries-retries, Process.clock_gettime(Process::CLOCK_MONOTONIC) - request_start, e.message, options[:tag]) + retry if (options[:retry_safe] || retry_safe) && !retries.zero? + raise + end + end + + def log_with_retry_details(logger, attempts, time, message, tag) + NetworkConnectionRetries.log(logger, :info, 'connection_attempt=%d connection_request_time=%.4fs connection_msg="%s"' % [attempts, time, message], tag) + end + + def derived_error_message(errors, klass) + key = (errors.keys & klass.ancestors).first + key ? errors[key] : nil + end + end +end diff --git a/lib/active_merchant/post_data.rb b/lib/active_merchant/post_data.rb new file mode 100644 index 00000000000..c95b85244d2 --- /dev/null +++ b/lib/active_merchant/post_data.rb @@ -0,0 +1,25 @@ +require 'cgi' + +module ActiveMerchant + class PostData < Hash + class_attribute :required_fields, :instance_writer => false + self.required_fields = [] + + def []=(key, value) + return if value.blank? && !required?(key) + super + end + + def to_post_data + collect { |key, value| "#{key}=#{CGI.escape(value.to_s)}" }.join('&') + end + + alias_method :to_s, :to_post_data + + private + + def required?(key) + required_fields.include?(key) + end + end +end diff --git a/lib/active_merchant/posts_data.rb b/lib/active_merchant/posts_data.rb new file mode 100644 index 00000000000..15446c09fd8 --- /dev/null +++ b/lib/active_merchant/posts_data.rb @@ -0,0 +1,92 @@ +module ActiveMerchant #:nodoc: + module PostsData #:nodoc: + def self.included(base) + base.class_attribute :ssl_strict + base.ssl_strict = true + + base.class_attribute :ssl_version + base.ssl_version = nil + + base.class_attribute :min_version + base.min_version = Connection::MIN_VERSION + + base.class_attribute :max_version + base.max_version = nil + + base.class_attribute :retry_safe + base.retry_safe = false + + base.class_attribute :open_timeout + base.open_timeout = Connection::OPEN_TIMEOUT + + base.class_attribute :read_timeout + base.read_timeout = Connection::READ_TIMEOUT + + base.class_attribute :max_retries + base.max_retries = Connection::MAX_RETRIES + + base.class_attribute :logger + base.class_attribute :wiredump_device + + base.class_attribute :proxy_address + base.class_attribute :proxy_port + end + + def ssl_get(endpoint, headers={}) + ssl_request(:get, endpoint, nil, headers) + end + + def ssl_post(endpoint, data, headers = {}) + ssl_request(:post, endpoint, data, headers) + end + + def ssl_request(method, endpoint, data, headers) + handle_response(raw_ssl_request(method, endpoint, data, headers)) + end + + def raw_ssl_request(method, endpoint, data, headers = {}) + logger&.warn "#{self.class} using ssl_strict=false, which is insecure" unless ssl_strict + logger&.warn "#{self.class} posting to plaintext endpoint, which is insecure" unless endpoint.to_s =~ /^https:/ + + connection = new_connection(endpoint) + connection.open_timeout = open_timeout + connection.read_timeout = read_timeout + connection.retry_safe = retry_safe + connection.verify_peer = ssl_strict + connection.ssl_version = ssl_version + connection.logger = logger + connection.max_retries = max_retries + connection.tag = self.class.name + connection.wiredump_device = wiredump_device + if connection.respond_to?(:min_version=) + connection.min_version = min_version + connection.max_version = max_version + end + + connection.pem = @options[:pem] if @options + connection.pem_password = @options[:pem_password] if @options + + connection.ignore_http_status = @options[:ignore_http_status] if @options + + connection.proxy_address = proxy_address + connection.proxy_port = proxy_port + + connection.request(method, data, headers) + end + + private + + def new_connection(endpoint) + Connection.new(endpoint) + end + + def handle_response(response) + case response.code.to_i + when 200...300 + response.body + else + raise ResponseError.new(response) + end + end + end +end diff --git a/lib/active_merchant/version.rb b/lib/active_merchant/version.rb index c6646f5133b..f01a6db39d8 100644 --- a/lib/active_merchant/version.rb +++ b/lib/active_merchant/version.rb @@ -1,3 +1,3 @@ module ActiveMerchant - VERSION = "1.38.1" + VERSION = '1.97.0' end diff --git a/lib/activemerchant.rb b/lib/activemerchant.rb index 0a3f08fee3f..118568f06b3 100644 --- a/lib/activemerchant.rb +++ b/lib/activemerchant.rb @@ -1 +1 @@ -require 'active_merchant' \ No newline at end of file +require 'active_merchant' diff --git a/lib/certs/cacert.pem b/lib/certs/cacert.pem new file mode 100644 index 00000000000..72bbb947fe5 --- /dev/null +++ b/lib/certs/cacert.pem @@ -0,0 +1,3988 @@ +## +## ca-bundle.crt -- Bundle of CA Root Certificates +## +## Certificate data from Mozilla as of: Tue Apr 22 08:29:31 2014 +## +## This is a bundle of X.509 certificates of public Certificate Authorities +## (CA). These were automatically extracted from Mozilla's root certificates +## file (certdata.txt). This file can be found in the mozilla source tree: +## http://mxr.mozilla.org/mozilla-release/source/security/nss/lib/ckfw/builtins/certdata.txt?raw=1 +## +## It contains the certificates in PEM format and therefore +## can be directly used with curl / libcurl / php_curl, or with +## an Apache+mod_ssl webserver for SSL client authentication. +## Just configure this file as the SSLCACertificateFile. +## + + +GTE CyberTrust Global Root +========================== +-----BEGIN CERTIFICATE----- +MIICWjCCAcMCAgGlMA0GCSqGSIb3DQEBBAUAMHUxCzAJBgNVBAYTAlVTMRgwFgYDVQQKEw9HVEUg +Q29ycG9yYXRpb24xJzAlBgNVBAsTHkdURSBDeWJlclRydXN0IFNvbHV0aW9ucywgSW5jLjEjMCEG +A1UEAxMaR1RFIEN5YmVyVHJ1c3QgR2xvYmFsIFJvb3QwHhcNOTgwODEzMDAyOTAwWhcNMTgwODEz +MjM1OTAwWjB1MQswCQYDVQQGEwJVUzEYMBYGA1UEChMPR1RFIENvcnBvcmF0aW9uMScwJQYDVQQL +Ex5HVEUgQ3liZXJUcnVzdCBTb2x1dGlvbnMsIEluYy4xIzAhBgNVBAMTGkdURSBDeWJlclRydXN0 +IEdsb2JhbCBSb290MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCVD6C28FCc6HrHiM3dFw4u +sJTQGz0O9pTAipTHBsiQl8i4ZBp6fmw8U+E3KHNgf7KXUwefU/ltWJTSr41tiGeA5u2ylc9yMcql +HHK6XALnZELn+aks1joNrI1CqiQBOeacPwGFVw1Yh0X404Wqk2kmhXBIgD8SFcd5tB8FLztimQID +AQABMA0GCSqGSIb3DQEBBAUAA4GBAG3rGwnpXtlR22ciYaQqPEh346B8pt5zohQDhT37qw4wxYMW +M4ETCJ57NE7fQMh017l93PR2VX2bY1QY6fDq81yx2YtCHrnAlU66+tXifPVoYb+O7AWXX1uw16OF +NMQkpw0PlZPvy5TYnh+dXIVtx6quTx8itc2VrbqnzPmrC3p/ +-----END CERTIFICATE----- + +Thawte Server CA +================ +-----BEGIN CERTIFICATE----- +MIIDEzCCAnygAwIBAgIBATANBgkqhkiG9w0BAQQFADCBxDELMAkGA1UEBhMCWkExFTATBgNVBAgT +DFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMR0wGwYDVQQKExRUaGF3dGUgQ29uc3Vs +dGluZyBjYzEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjEZMBcGA1UE +AxMQVGhhd3RlIFNlcnZlciBDQTEmMCQGCSqGSIb3DQEJARYXc2VydmVyLWNlcnRzQHRoYXd0ZS5j +b20wHhcNOTYwODAxMDAwMDAwWhcNMjAxMjMxMjM1OTU5WjCBxDELMAkGA1UEBhMCWkExFTATBgNV +BAgTDFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMR0wGwYDVQQKExRUaGF3dGUgQ29u +c3VsdGluZyBjYzEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjEZMBcG +A1UEAxMQVGhhd3RlIFNlcnZlciBDQTEmMCQGCSqGSIb3DQEJARYXc2VydmVyLWNlcnRzQHRoYXd0 +ZS5jb20wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBANOkUG7I/1Zr5s9dtuoMaHVHoqrC2oQl +/Kj0R1HahbUgdJSGHg91yekIYfUGbTBuFRkC6VLAYttNmZ7iagxEOM3+vuNkCXDF/rFrKbYvScg7 +1CcEJRCXL+eQbcAoQpnXTEPew/UhbVSfXcNY4cDk2VuwuNy0e982OsK1ZiIS1ocNAgMBAAGjEzAR +MA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEEBQADgYEAB/pMaVz7lcxG7oWDTSEwjsrZqG9J +GubaUeNgcGyEYRGhGshIPllDfU+VPaGLtwtimHp1it2ITk6eQNuozDJ0uW8NxuOzRAvZim+aKZuZ +GCg70eNAKJpaPNW15yAbi8qkq43pUdniTCxZqdq5snUb9kLy78fyGPmJvKP/iiMucEc= +-----END CERTIFICATE----- + +Thawte Premium Server CA +======================== +-----BEGIN CERTIFICATE----- +MIIDJzCCApCgAwIBAgIBATANBgkqhkiG9w0BAQQFADCBzjELMAkGA1UEBhMCWkExFTATBgNVBAgT +DFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMR0wGwYDVQQKExRUaGF3dGUgQ29uc3Vs +dGluZyBjYzEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjEhMB8GA1UE +AxMYVGhhd3RlIFByZW1pdW0gU2VydmVyIENBMSgwJgYJKoZIhvcNAQkBFhlwcmVtaXVtLXNlcnZl +ckB0aGF3dGUuY29tMB4XDTk2MDgwMTAwMDAwMFoXDTIwMTIzMTIzNTk1OVowgc4xCzAJBgNVBAYT +AlpBMRUwEwYDVQQIEwxXZXN0ZXJuIENhcGUxEjAQBgNVBAcTCUNhcGUgVG93bjEdMBsGA1UEChMU +VGhhd3RlIENvbnN1bHRpbmcgY2MxKDAmBgNVBAsTH0NlcnRpZmljYXRpb24gU2VydmljZXMgRGl2 +aXNpb24xITAfBgNVBAMTGFRoYXd0ZSBQcmVtaXVtIFNlcnZlciBDQTEoMCYGCSqGSIb3DQEJARYZ +cHJlbWl1bS1zZXJ2ZXJAdGhhd3RlLmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA0jY2 +aovXwlue2oFBYo847kkEVdbQ7xwblRZH7xhINTpS9CtqBo87L+pW46+GjZ4X9560ZXUCTe/LCaIh +Udib0GfQug2SBhRz1JPLlyoAnFxODLz6FVL88kRu2hFKbgifLy3j+ao6hnO2RlNYyIkFvYMRuHM/ +qgeN9EJN50CdHDcCAwEAAaMTMBEwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQQFAAOBgQAm +SCwWwlj66BZ0DKqqX1Q/8tfJeGBeXm43YyJ3Nn6yF8Q0ufUIhfzJATj/Tb7yFkJD57taRvvBxhEf +8UqwKEbJw8RCfbz6q1lu1bdRiBHjpIUZa4JMpAwSremkrj/xw0llmozFyD4lt5SZu5IycQfwhl7t +UCemDaYj+bvLpgcUQg== +-----END CERTIFICATE----- + +Equifax Secure CA +================= +-----BEGIN CERTIFICATE----- +MIIDIDCCAomgAwIBAgIENd70zzANBgkqhkiG9w0BAQUFADBOMQswCQYDVQQGEwJVUzEQMA4GA1UE +ChMHRXF1aWZheDEtMCsGA1UECxMkRXF1aWZheCBTZWN1cmUgQ2VydGlmaWNhdGUgQXV0aG9yaXR5 +MB4XDTk4MDgyMjE2NDE1MVoXDTE4MDgyMjE2NDE1MVowTjELMAkGA1UEBhMCVVMxEDAOBgNVBAoT +B0VxdWlmYXgxLTArBgNVBAsTJEVxdWlmYXggU2VjdXJlIENlcnRpZmljYXRlIEF1dGhvcml0eTCB +nzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAwV2xWGcIYu6gmi0fCG2RFGiYCh7+2gRvE4RiIcPR +fM6fBeC4AfBONOziipUEZKzxa1NfBbPLZ4C/QgKO/t0BCezhABRP/PvwDN1Dulsr4R+AcJkVV5MW +8Q+XarfCaCMczE1ZMKxRHjuvK9buY0V7xdlfUNLjUA86iOe/FP3gx7kCAwEAAaOCAQkwggEFMHAG +A1UdHwRpMGcwZaBjoGGkXzBdMQswCQYDVQQGEwJVUzEQMA4GA1UEChMHRXF1aWZheDEtMCsGA1UE +CxMkRXF1aWZheCBTZWN1cmUgQ2VydGlmaWNhdGUgQXV0aG9yaXR5MQ0wCwYDVQQDEwRDUkwxMBoG +A1UdEAQTMBGBDzIwMTgwODIyMTY0MTUxWjALBgNVHQ8EBAMCAQYwHwYDVR0jBBgwFoAUSOZo+SvS +spXXR9gjIBBPM5iQn9QwHQYDVR0OBBYEFEjmaPkr0rKV10fYIyAQTzOYkJ/UMAwGA1UdEwQFMAMB +Af8wGgYJKoZIhvZ9B0EABA0wCxsFVjMuMGMDAgbAMA0GCSqGSIb3DQEBBQUAA4GBAFjOKer89961 +zgK5F7WF0bnj4JXMJTENAKaSbn+2kmOeUJXRmm/kEd5jhW6Y7qj/WsjTVbJmcVfewCHrPSqnI0kB +BIZCe/zuf6IWUrVnZ9NA2zsmWLIodz2uFHdh1voqZiegDfqnc1zqcPGUIWVEX/r87yloqaKHee95 +70+sB3c4 +-----END CERTIFICATE----- + +Verisign Class 3 Public Primary Certification Authority +======================================================= +-----BEGIN CERTIFICATE----- +MIICPDCCAaUCEHC65B0Q2Sk0tjjKewPMur8wDQYJKoZIhvcNAQECBQAwXzELMAkGA1UEBhMCVVMx +FzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAzIFB1YmxpYyBQcmltYXJ5 +IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTk2MDEyOTAwMDAwMFoXDTI4MDgwMTIzNTk1OVow +XzELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAz +IFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGfMA0GCSqGSIb3DQEBAQUA +A4GNADCBiQKBgQDJXFme8huKARS0EN8EQNvjV69qRUCPhAwL0TPZ2RHP7gJYHyX3KqhEBarsAx94 +f56TuZoAqiN91qyFomNFx3InzPRMxnVx0jnvT0Lwdd8KkMaOIG+YD/isI19wKTakyYbnsZogy1Ol +hec9vn2a/iRFM9x2Fe0PonFkTGUugWhFpwIDAQABMA0GCSqGSIb3DQEBAgUAA4GBALtMEivPLCYA +TxQT3ab7/AoRhIzzKBxnki98tsX63/Dolbwdj2wsqFHMc9ikwFPwTtYmwHYBV4GSXiHx0bH/59Ah +WM1pF+NEHJwZRDmJXNycAA9WjQKZ7aKQRUzkuxCkPfAyAw7xzvjoyVGM5mKf5p/AfbdynMk2Omuf +Tqj/ZA1k +-----END CERTIFICATE----- + +Verisign Class 3 Public Primary Certification Authority - G2 +============================================================ +-----BEGIN CERTIFICATE----- +MIIDAjCCAmsCEH3Z/gfPqB63EHln+6eJNMYwDQYJKoZIhvcNAQEFBQAwgcExCzAJBgNVBAYTAlVT +MRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjE8MDoGA1UECxMzQ2xhc3MgMyBQdWJsaWMgUHJpbWFy +eSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEcyMTowOAYDVQQLEzEoYykgMTk5OCBWZXJpU2ln +biwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MR8wHQYDVQQLExZWZXJpU2lnbiBUcnVz +dCBOZXR3b3JrMB4XDTk4MDUxODAwMDAwMFoXDTI4MDgwMTIzNTk1OVowgcExCzAJBgNVBAYTAlVT +MRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjE8MDoGA1UECxMzQ2xhc3MgMyBQdWJsaWMgUHJpbWFy +eSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEcyMTowOAYDVQQLEzEoYykgMTk5OCBWZXJpU2ln +biwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MR8wHQYDVQQLExZWZXJpU2lnbiBUcnVz +dCBOZXR3b3JrMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDMXtERXVxp0KvTuWpMmR9ZmDCO +FoUgRm1HP9SFIIThbbP4pO0M8RcPO/mn+SXXwc+EY/J8Y8+iR/LGWzOOZEAEaMGAuWQcRXfH2G71 +lSk8UOg013gfqLptQ5GVj0VXXn7F+8qkBOvqlzdUMG+7AUcyM83cV5tkaWH4mx0ciU9cZwIDAQAB +MA0GCSqGSIb3DQEBBQUAA4GBAFFNzb5cy5gZnBWyATl4Lk0PZ3BwmcYQWpSkU01UbSuvDV1Ai2TT +1+7eVmGSX6bEHRBhNtMsJzzoKQm5EWR0zLVznxxIqbxhAe7iF6YM40AIOw7n60RzKprxaZLvcRTD +Oaxxp5EJb+RxBrO6WVcmeQD2+A2iMzAo1KpYoJ2daZH9 +-----END CERTIFICATE----- + +GlobalSign Root CA +================== +-----BEGIN CERTIFICATE----- +MIIDdTCCAl2gAwIBAgILBAAAAAABFUtaw5QwDQYJKoZIhvcNAQEFBQAwVzELMAkGA1UEBhMCQkUx +GTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExEDAOBgNVBAsTB1Jvb3QgQ0ExGzAZBgNVBAMTEkds +b2JhbFNpZ24gUm9vdCBDQTAeFw05ODA5MDExMjAwMDBaFw0yODAxMjgxMjAwMDBaMFcxCzAJBgNV +BAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRAwDgYDVQQLEwdSb290IENBMRswGQYD +VQQDExJHbG9iYWxTaWduIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDa +DuaZjc6j40+Kfvvxi4Mla+pIH/EqsLmVEQS98GPR4mdmzxzdzxtIK+6NiY6arymAZavpxy0Sy6sc +THAHoT0KMM0VjU/43dSMUBUc71DuxC73/OlS8pF94G3VNTCOXkNz8kHp1Wrjsok6Vjk4bwY8iGlb +Kk3Fp1S4bInMm/k8yuX9ifUSPJJ4ltbcdG6TRGHRjcdGsnUOhugZitVtbNV4FpWi6cgKOOvyJBNP +c1STE4U6G7weNLWLBYy5d4ux2x8gkasJU26Qzns3dLlwR5EiUWMWea6xrkEmCMgZK9FGqkjWZCrX +gzT/LCrBbBlDSgeF59N89iFo7+ryUp9/k5DPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV +HRMBAf8EBTADAQH/MB0GA1UdDgQWBBRge2YaRQ2XyolQL30EzTSo//z9SzANBgkqhkiG9w0BAQUF +AAOCAQEA1nPnfE920I2/7LqivjTFKDK1fPxsnCwrvQmeU79rXqoRSLblCKOzyj1hTdNGCbM+w6Dj +Y1Ub8rrvrTnhQ7k4o+YviiY776BQVvnGCv04zcQLcFGUl5gE38NflNUVyRRBnMRddWQVDf9VMOyG +j/8N7yy5Y0b2qvzfvGn9LhJIZJrglfCm7ymPAbEVtQwdpf5pLGkkeB6zpxxxYu7KyJesF12KwvhH +hm4qxFYxldBniYUr+WymXUadDKqC5JlR3XC321Y9YeRq4VzW9v493kHMB65jUr9TU/Qr6cf9tveC +X4XSQRjbgbMEHMUfpIBvFSDJ3gyICh3WZlXi/EjJKSZp4A== +-----END CERTIFICATE----- + +GlobalSign Root CA - R2 +======================= +-----BEGIN CERTIFICATE----- +MIIDujCCAqKgAwIBAgILBAAAAAABD4Ym5g0wDQYJKoZIhvcNAQEFBQAwTDEgMB4GA1UECxMXR2xv +YmFsU2lnbiBSb290IENBIC0gUjIxEzARBgNVBAoTCkdsb2JhbFNpZ24xEzARBgNVBAMTCkdsb2Jh +bFNpZ24wHhcNMDYxMjE1MDgwMDAwWhcNMjExMjE1MDgwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxT +aWduIFJvb3QgQ0EgLSBSMjETMBEGA1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2ln +bjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKbPJA6+Lm8omUVCxKs+IVSbC9N/hHD6 +ErPLv4dfxn+G07IwXNb9rfF73OX4YJYJkhD10FPe+3t+c4isUoh7SqbKSaZeqKeMWhG8eoLrvozp +s6yWJQeXSpkqBy+0Hne/ig+1AnwblrjFuTosvNYSuetZfeLQBoZfXklqtTleiDTsvHgMCJiEbKjN +S7SgfQx5TfC4LcshytVsW33hoCmEofnTlEnLJGKRILzdC9XZzPnqJworc5HGnRusyMvo4KD0L5CL +TfuwNhv2GXqF4G3yYROIXJ/gkwpRl4pazq+r1feqCapgvdzZX99yqWATXgAByUr6P6TqBwMhAo6C +ygPCm48CAwEAAaOBnDCBmTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4E +FgQUm+IHV2ccHsBqBt5ZtJot39wZhi4wNgYDVR0fBC8wLTAroCmgJ4YlaHR0cDovL2NybC5nbG9i +YWxzaWduLm5ldC9yb290LXIyLmNybDAfBgNVHSMEGDAWgBSb4gdXZxwewGoG3lm0mi3f3BmGLjAN +BgkqhkiG9w0BAQUFAAOCAQEAmYFThxxol4aR7OBKuEQLq4GsJ0/WwbgcQ3izDJr86iw8bmEbTUsp +9Z8FHSbBuOmDAGJFtqkIk7mpM0sYmsL4h4hO291xNBrBVNpGP+DTKqttVCL1OmLNIG+6KYnX3ZHu +01yiPqFbQfXf5WRDLenVOavSot+3i9DAgBkcRcAtjOj4LaR0VknFBbVPFd5uRHg5h6h+u/N5GJG7 +9G+dwfCMNYxdAfvDbbnvRG15RjF+Cv6pgsH/76tuIMRQyV+dTZsXjAzlAcmgQWpzU/qlULRuJQ/7 +TBj0/VLZjmmx6BEP3ojY+x1J96relc8geMJgEtslQIxq/H5COEBkEveegeGTLg== +-----END CERTIFICATE----- + +ValiCert Class 1 VA +=================== +-----BEGIN CERTIFICATE----- +MIIC5zCCAlACAQEwDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRp +b24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENs +YXNzIDEgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZh +bGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTk5MDYyNTIy +MjM0OFoXDTE5MDYyNTIyMjM0OFowgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTmV0 +d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENsYXNzIDEg +UG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0 +LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMIGfMA0GCSqGSIb3DQEBAQUA +A4GNADCBiQKBgQDYWYJ6ibiWuqYvaG9YLqdUHAZu9OqNSLwxlBfw8068srg1knaw0KWlAdcAAxIi +GQj4/xEjm84H9b9pGib+TunRf50sQB1ZaG6m+FiwnRqP0z/x3BkGgagO4DrdyFNFCQbmD3DD+kCm +DuJWBQ8YTfwggtFzVXSNdnKgHZ0dwN0/cQIDAQABMA0GCSqGSIb3DQEBBQUAA4GBAFBoPUn0LBwG +lN+VYH+Wexf+T3GtZMjdd9LvWVXoP+iOBSoh8gfStadS/pyxtuJbdxdA6nLWI8sogTLDAHkY7FkX +icnGah5xyf23dKUlRWnFSKsZ4UWKJWsZ7uW7EvV/96aNUcPwnXS3qT6gpf+2SQMT2iLM7XGCK5nP +Orf1LXLI +-----END CERTIFICATE----- + +ValiCert Class 2 VA +=================== +-----BEGIN CERTIFICATE----- +MIIC5zCCAlACAQEwDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRp +b24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENs +YXNzIDIgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZh +bGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTk5MDYyNjAw +MTk1NFoXDTE5MDYyNjAwMTk1NFowgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTmV0 +d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENsYXNzIDIg +UG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0 +LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMIGfMA0GCSqGSIb3DQEBAQUA +A4GNADCBiQKBgQDOOnHK5avIWZJV16vYdA757tn2VUdZZUcOBVXc65g2PFxTXdMwzzjsvUGJ7SVC +CSRrCl6zfN1SLUzm1NZ9WlmpZdRJEy0kTRxQb7XBhVQ7/nHk01xC+YDgkRoKWzk2Z/M/VXwbP7Rf +ZHM047QSv4dk+NoS/zcnwbNDu+97bi5p9wIDAQABMA0GCSqGSIb3DQEBBQUAA4GBADt/UG9vUJSZ +SWI4OB9L+KXIPqeCgfYrx+jFzug6EILLGACOTb2oWH+heQC1u+mNr0HZDzTuIYEZoDJJKPTEjlbV +UjP9UNV+mWwD5MlM/Mtsq2azSiGM5bUMMj4QssxsodyamEwCW/POuZ6lcg5Ktz885hZo+L7tdEy8 +W9ViH0Pd +-----END CERTIFICATE----- + +RSA Root Certificate 1 +====================== +-----BEGIN CERTIFICATE----- +MIIC5zCCAlACAQEwDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRp +b24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENs +YXNzIDMgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZh +bGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTk5MDYyNjAw +MjIzM1oXDTE5MDYyNjAwMjIzM1owgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTmV0 +d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENsYXNzIDMg +UG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0 +LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMIGfMA0GCSqGSIb3DQEBAQUA +A4GNADCBiQKBgQDjmFGWHOjVsQaBalfDcnWTq8+epvzzFlLWLU2fNUSoLgRNB0mKOCn1dzfnt6td +3zZxFJmP3MKS8edgkpfs2Ejcv8ECIMYkpChMMFp2bbFc893enhBxoYjHW5tBbcqwuI4V7q0zK89H +BFx1cQqYJJgpp0lZpd34t0NiYfPT4tBVPwIDAQABMA0GCSqGSIb3DQEBBQUAA4GBAFa7AliEZwgs +3x/be0kz9dNnnfS0ChCzycUs4pJqcXgn8nCDQtM+z6lU9PHYkhaM0QTLS6vJn0WuPIqpsHEzXcjF +V9+vqDWzf4mH6eglkrh/hXqu1rweN1gqZ8mRzyqBPu3GOd/APhmcGcwTTYJBtYze4D1gCCAPRX5r +on+jjBXu +-----END CERTIFICATE----- + +Verisign Class 3 Public Primary Certification Authority - G3 +============================================================ +-----BEGIN CERTIFICATE----- +MIIEGjCCAwICEQCbfgZJoz5iudXukEhxKe9XMA0GCSqGSIb3DQEBBQUAMIHKMQswCQYDVQQGEwJV +UzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdv +cmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNl +IG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNh +dGlvbiBBdXRob3JpdHkgLSBHMzAeFw05OTEwMDEwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMIHKMQsw +CQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRy +dXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhv +cml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDMgUHVibGljIFByaW1hcnkg +Q2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC +ggEBAMu6nFL8eB8aHm8bN3O9+MlrlBIwT/A2R/XQkQr1F8ilYcEWQE37imGQ5XYgwREGfassbqb1 +EUGO+i2tKmFZpGcmTNDovFJbcCAEWNF6yaRpvIMXZK0Fi7zQWM6NjPXr8EJJC52XJ2cybuGukxUc +cLwgTS8Y3pKI6GyFVxEa6X7jJhFUokWWVYPKMIno3Nij7SqAP395ZVc+FSBmCC+Vk7+qRy+oRpfw +EuL+wgorUeZ25rdGt+INpsyow0xZVYnm6FNcHOqd8GIWC6fJXwzw3sJ2zq/3avL6QaaiMxTJ5Xpj +055iN9WFZZ4O5lMkdBteHRJTW8cs54NJOxWuimi5V5cCAwEAATANBgkqhkiG9w0BAQUFAAOCAQEA +ERSWwauSCPc/L8my/uRan2Te2yFPhpk0djZX3dAVL8WtfxUfN2JzPtTnX84XA9s1+ivbrmAJXx5f +j267Cz3qWhMeDGBvtcC1IyIuBwvLqXTLR7sdwdela8wv0kL9Sd2nic9TutoAWii/gt/4uhMdUIaC +/Y4wjylGsB49Ndo4YhYYSq3mtlFs3q9i6wHQHiT+eo8SGhJouPtmmRQURVyu565pF4ErWjfJXir0 +xuKhXFSbplQAz/DxwceYMBo7Nhbbo27q/a2ywtrvAkcTisDxszGtTxzhT5yvDwyd93gN2PQ1VoDa +t20Xj50egWTh/sVFuq1ruQp6Tk9LhO5L8X3dEQ== +-----END CERTIFICATE----- + +Verisign Class 4 Public Primary Certification Authority - G3 +============================================================ +-----BEGIN CERTIFICATE----- +MIIEGjCCAwICEQDsoKeLbnVqAc/EfMwvlF7XMA0GCSqGSIb3DQEBBQUAMIHKMQswCQYDVQQGEwJV +UzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdv +cmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNl +IG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDQgUHVibGljIFByaW1hcnkgQ2VydGlmaWNh +dGlvbiBBdXRob3JpdHkgLSBHMzAeFw05OTEwMDEwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMIHKMQsw +CQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRy +dXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhv +cml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDQgUHVibGljIFByaW1hcnkg +Q2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC +ggEBAK3LpRFpxlmr8Y+1GQ9Wzsy1HyDkniYlS+BzZYlZ3tCD5PUPtbut8XzoIfzk6AzufEUiGXaS +tBO3IFsJ+mGuqPKljYXCKtbeZjbSmwL0qJJgfJxptI8kHtCGUvYynEFYHiK9zUVilQhu0GbdU6LM +8BDcVHOLBKFGMzNcF0C5nk3T875Vg+ixiY5afJqWIpA7iCXy0lOIAgwLePLmNxdLMEYH5IBtptiW +Lugs+BGzOA1mppvqySNb247i8xOOGlktqgLw7KSHZtzBP/XYufTsgsbSPZUd5cBPhMnZo0QoBmrX +Razwa2rvTl/4EYIeOGM0ZlDUPpNz+jDDZq3/ky2X7wMCAwEAATANBgkqhkiG9w0BAQUFAAOCAQEA +j/ola09b5KROJ1WrIhVZPMq1CtRK26vdoV9TxaBXOcLORyu+OshWv8LZJxA6sQU8wHcxuzrTBXtt +mhwwjIDLk5Mqg6sFUYICABFna/OIYUdfA5PVWw3g8dShMjWFsjrbsIKr0csKvE+MW8VLADsfKoKm +fjaF3H48ZwC15DtS4KjrXRX5xm3wrR0OhbepmnMUWluPQSjA1egtTaRezarZ7c7c2NU8Qh0XwRJd +RTjDOPP8hS6DRkiy1yBfkjaP53kPmF6Z6PDQpLv1U70qzlmwr25/bLvSHgCwIe34QWKCudiyxLtG +UPMxxY8BqHTr9Xgn2uf3ZkPznoM+IKrDNWCRzg== +-----END CERTIFICATE----- + +Entrust.net Secure Server CA +============================ +-----BEGIN CERTIFICATE----- +MIIE2DCCBEGgAwIBAgIEN0rSQzANBgkqhkiG9w0BAQUFADCBwzELMAkGA1UEBhMCVVMxFDASBgNV +BAoTC0VudHJ1c3QubmV0MTswOQYDVQQLEzJ3d3cuZW50cnVzdC5uZXQvQ1BTIGluY29ycC4gYnkg +cmVmLiAobGltaXRzIGxpYWIuKTElMCMGA1UECxMcKGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRl +ZDE6MDgGA1UEAxMxRW50cnVzdC5uZXQgU2VjdXJlIFNlcnZlciBDZXJ0aWZpY2F0aW9uIEF1dGhv +cml0eTAeFw05OTA1MjUxNjA5NDBaFw0xOTA1MjUxNjM5NDBaMIHDMQswCQYDVQQGEwJVUzEUMBIG +A1UEChMLRW50cnVzdC5uZXQxOzA5BgNVBAsTMnd3dy5lbnRydXN0Lm5ldC9DUFMgaW5jb3JwLiBi +eSByZWYuIChsaW1pdHMgbGlhYi4pMSUwIwYDVQQLExwoYykgMTk5OSBFbnRydXN0Lm5ldCBMaW1p +dGVkMTowOAYDVQQDEzFFbnRydXN0Lm5ldCBTZWN1cmUgU2VydmVyIENlcnRpZmljYXRpb24gQXV0 +aG9yaXR5MIGdMA0GCSqGSIb3DQEBAQUAA4GLADCBhwKBgQDNKIM0VBuJ8w+vN5Ex/68xYMmo6LIQ +aO2f55M28Qpku0f1BBc/I0dNxScZgSYMVHINiC3ZH5oSn7yzcdOAGT9HZnuMNSjSuQrfJNqc1lB5 +gXpa0zf3wkrYKZImZNHkmGw6AIr1NJtl+O3jEP/9uElY3KDegjlrgbEWGWG5VLbmQwIBA6OCAdcw +ggHTMBEGCWCGSAGG+EIBAQQEAwIABzCCARkGA1UdHwSCARAwggEMMIHeoIHboIHYpIHVMIHSMQsw +CQYDVQQGEwJVUzEUMBIGA1UEChMLRW50cnVzdC5uZXQxOzA5BgNVBAsTMnd3dy5lbnRydXN0Lm5l +dC9DUFMgaW5jb3JwLiBieSByZWYuIChsaW1pdHMgbGlhYi4pMSUwIwYDVQQLExwoYykgMTk5OSBF +bnRydXN0Lm5ldCBMaW1pdGVkMTowOAYDVQQDEzFFbnRydXN0Lm5ldCBTZWN1cmUgU2VydmVyIENl +cnRpZmljYXRpb24gQXV0aG9yaXR5MQ0wCwYDVQQDEwRDUkwxMCmgJ6AlhiNodHRwOi8vd3d3LmVu +dHJ1c3QubmV0L0NSTC9uZXQxLmNybDArBgNVHRAEJDAigA8xOTk5MDUyNTE2MDk0MFqBDzIwMTkw +NTI1MTYwOTQwWjALBgNVHQ8EBAMCAQYwHwYDVR0jBBgwFoAU8BdiE1U9s/8KAGv7UISX8+1i0Bow +HQYDVR0OBBYEFPAXYhNVPbP/CgBr+1CEl/PtYtAaMAwGA1UdEwQFMAMBAf8wGQYJKoZIhvZ9B0EA +BAwwChsEVjQuMAMCBJAwDQYJKoZIhvcNAQEFBQADgYEAkNwwAvpkdMKnCqV8IY00F6j7Rw7/JXyN +Ewr75Ji174z4xRAN95K+8cPV1ZVqBLssziY2ZcgxxufuP+NXdYR6Ee9GTxj005i7qIcyunL2POI9 +n9cd2cNgQ4xYDiKWL2KjLB+6rQXvqzJ4h6BUcxm1XAX5Uj5tLUUL9wqT6u0G+bI= +-----END CERTIFICATE----- + +Entrust.net Premium 2048 Secure Server CA +========================================= +-----BEGIN CERTIFICATE----- +MIIEKjCCAxKgAwIBAgIEOGPe+DANBgkqhkiG9w0BAQUFADCBtDEUMBIGA1UEChMLRW50cnVzdC5u +ZXQxQDA+BgNVBAsUN3d3dy5lbnRydXN0Lm5ldC9DUFNfMjA0OCBpbmNvcnAuIGJ5IHJlZi4gKGxp +bWl0cyBsaWFiLikxJTAjBgNVBAsTHChjKSAxOTk5IEVudHJ1c3QubmV0IExpbWl0ZWQxMzAxBgNV +BAMTKkVudHJ1c3QubmV0IENlcnRpZmljYXRpb24gQXV0aG9yaXR5ICgyMDQ4KTAeFw05OTEyMjQx +NzUwNTFaFw0yOTA3MjQxNDE1MTJaMIG0MRQwEgYDVQQKEwtFbnRydXN0Lm5ldDFAMD4GA1UECxQ3 +d3d3LmVudHJ1c3QubmV0L0NQU18yMDQ4IGluY29ycC4gYnkgcmVmLiAobGltaXRzIGxpYWIuKTEl +MCMGA1UECxMcKGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRlZDEzMDEGA1UEAxMqRW50cnVzdC5u +ZXQgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgKDIwNDgpMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A +MIIBCgKCAQEArU1LqRKGsuqjIAcVFmQqK0vRvwtKTY7tgHalZ7d4QMBzQshowNtTK91euHaYNZOL +Gp18EzoOH1u3Hs/lJBQesYGpjX24zGtLA/ECDNyrpUAkAH90lKGdCCmziAv1h3edVc3kw37XamSr +hRSGlVuXMlBvPci6Zgzj/L24ScF2iUkZ/cCovYmjZy/Gn7xxGWC4LeksyZB2ZnuU4q941mVTXTzW +nLLPKQP5L6RQstRIzgUyVYr9smRMDuSYB3Xbf9+5CFVghTAp+XtIpGmG4zU/HoZdenoVve8AjhUi +VBcAkCaTvA5JaJG/+EfTnZVCwQ5N328mz8MYIWJmQ3DW1cAH4QIDAQABo0IwQDAOBgNVHQ8BAf8E +BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUVeSB0RGAvtiJuQijMfmhJAkWuXAwDQYJ +KoZIhvcNAQEFBQADggEBADubj1abMOdTmXx6eadNl9cZlZD7Bh/KM3xGY4+WZiT6QBshJ8rmcnPy +T/4xmf3IDExoU8aAghOY+rat2l098c5u9hURlIIM7j+VrxGrD9cv3h8Dj1csHsm7mhpElesYT6Yf +zX1XEC+bBAlahLVu2B064dae0Wx5XnkcFMXj0EyTO2U87d89vqbllRrDtRnDvV5bu/8j72gZyxKT +J1wDLW8w0B62GqzeWvfRqqgnpv55gcR5mTNXuhKwqeBCbJPKVt7+bYQLCIt+jerXmCHG8+c8eS9e +nNFMFY3h7CI3zJpDC5fcgJCNs2ebb0gIFVbPv/ErfF6adulZkMV8gzURZVE= +-----END CERTIFICATE----- + +Baltimore CyberTrust Root +========================= +-----BEGIN CERTIFICATE----- +MIIDdzCCAl+gAwIBAgIEAgAAuTANBgkqhkiG9w0BAQUFADBaMQswCQYDVQQGEwJJRTESMBAGA1UE +ChMJQmFsdGltb3JlMRMwEQYDVQQLEwpDeWJlclRydXN0MSIwIAYDVQQDExlCYWx0aW1vcmUgQ3li +ZXJUcnVzdCBSb290MB4XDTAwMDUxMjE4NDYwMFoXDTI1MDUxMjIzNTkwMFowWjELMAkGA1UEBhMC +SUUxEjAQBgNVBAoTCUJhbHRpbW9yZTETMBEGA1UECxMKQ3liZXJUcnVzdDEiMCAGA1UEAxMZQmFs +dGltb3JlIEN5YmVyVHJ1c3QgUm9vdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKME +uyKrmD1X6CZymrV51Cni4eiVgLGw41uOKymaZN+hXe2wCQVt2yguzmKiYv60iNoS6zjrIZ3AQSsB +UnuId9Mcj8e6uYi1agnnc+gRQKfRzMpijS3ljwumUNKoUMMo6vWrJYeKmpYcqWe4PwzV9/lSEy/C +G9VwcPCPwBLKBsua4dnKM3p31vjsufFoREJIE9LAwqSuXmD+tqYF/LTdB1kC1FkYmGP1pWPgkAx9 +XbIGevOF6uvUA65ehD5f/xXtabz5OTZydc93Uk3zyZAsuT3lySNTPx8kmCFcB5kpvcY67Oduhjpr +l3RjM71oGDHweI12v/yejl0qhqdNkNwnGjkCAwEAAaNFMEMwHQYDVR0OBBYEFOWdWTCCR1jMrPoI +VDaGezq1BE3wMBIGA1UdEwEB/wQIMAYBAf8CAQMwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEB +BQUAA4IBAQCFDF2O5G9RaEIFoN27TyclhAO992T9Ldcw46QQF+vaKSm2eT929hkTI7gQCvlYpNRh +cL0EYWoSihfVCr3FvDB81ukMJY2GQE/szKN+OMY3EU/t3WgxjkzSswF07r51XgdIGn9w/xZchMB5 +hbgF/X++ZRGjD8ACtPhSNzkE1akxehi/oCr0Epn3o0WC4zxe9Z2etciefC7IpJ5OCBRLbf1wbWsa +Y71k5h+3zvDyny67G7fyUIhzksLi4xaNmjICq44Y3ekQEe5+NauQrz4wlHrQMz2nZQ/1/I6eYs9H +RCwBXbsdtTLSR9I4LtD+gdwyah617jzV/OeBHRnDJELqYzmp +-----END CERTIFICATE----- + +Equifax Secure Global eBusiness CA +================================== +-----BEGIN CERTIFICATE----- +MIICkDCCAfmgAwIBAgIBATANBgkqhkiG9w0BAQQFADBaMQswCQYDVQQGEwJVUzEcMBoGA1UEChMT +RXF1aWZheCBTZWN1cmUgSW5jLjEtMCsGA1UEAxMkRXF1aWZheCBTZWN1cmUgR2xvYmFsIGVCdXNp +bmVzcyBDQS0xMB4XDTk5MDYyMTA0MDAwMFoXDTIwMDYyMTA0MDAwMFowWjELMAkGA1UEBhMCVVMx +HDAaBgNVBAoTE0VxdWlmYXggU2VjdXJlIEluYy4xLTArBgNVBAMTJEVxdWlmYXggU2VjdXJlIEds +b2JhbCBlQnVzaW5lc3MgQ0EtMTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAuucXkAJlsTRV +PEnCUdXfp9E3j9HngXNBUmCbnaEXJnitx7HoJpQytd4zjTov2/KaelpzmKNc6fuKcxtc58O/gGzN +qfTWK8D3+ZmqY6KxRwIP1ORROhI8bIpaVIRw28HFkM9yRcuoWcDNM50/o5brhTMhHD4ePmBudpxn +hcXIw2ECAwEAAaNmMGQwEQYJYIZIAYb4QgEBBAQDAgAHMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0j +BBgwFoAUvqigdHJQa0S3ySPY+6j/s1draGwwHQYDVR0OBBYEFL6ooHRyUGtEt8kj2Puo/7NXa2hs +MA0GCSqGSIb3DQEBBAUAA4GBADDiAVGqx+pf2rnQZQ8w1j7aDRRJbpGTJxQx78T3LUX47Me/okEN +I7SS+RkAZ70Br83gcfxaz2TE4JaY0KNA4gGK7ycH8WUBikQtBmV1UsCGECAhX2xrD2yuCRyv8qIY +NMR1pHMc8Y3c7635s3a0kr/clRAevsvIO1qEYBlWlKlV +-----END CERTIFICATE----- + +Equifax Secure eBusiness CA 1 +============================= +-----BEGIN CERTIFICATE----- +MIICgjCCAeugAwIBAgIBBDANBgkqhkiG9w0BAQQFADBTMQswCQYDVQQGEwJVUzEcMBoGA1UEChMT +RXF1aWZheCBTZWN1cmUgSW5jLjEmMCQGA1UEAxMdRXF1aWZheCBTZWN1cmUgZUJ1c2luZXNzIENB +LTEwHhcNOTkwNjIxMDQwMDAwWhcNMjAwNjIxMDQwMDAwWjBTMQswCQYDVQQGEwJVUzEcMBoGA1UE +ChMTRXF1aWZheCBTZWN1cmUgSW5jLjEmMCQGA1UEAxMdRXF1aWZheCBTZWN1cmUgZUJ1c2luZXNz +IENBLTEwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAM4vGbwXt3fek6lfWg0XTzQaDJj0ItlZ +1MRoRvC0NcWFAyDGr0WlIVFFQesWWDYyb+JQYmT5/VGcqiTZ9J2DKocKIdMSODRsjQBuWqDZQu4a +IZX5UkxVWsUPOE9G+m34LjXWHXzr4vCwdYDIqROsvojvOm6rXyo4YgKwEnv+j6YDAgMBAAGjZjBk +MBEGCWCGSAGG+EIBAQQEAwIABzAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFEp4MlIR21kW +Nl7fwRQ2QGpHfEyhMB0GA1UdDgQWBBRKeDJSEdtZFjZe38EUNkBqR3xMoTANBgkqhkiG9w0BAQQF +AAOBgQB1W6ibAxHm6VZMzfmpTMANmvPMZWnmJXbMWbfWVMMdzZmsGd20hdXgPfxiIKeES1hl8eL5 +lSE/9dR+WB5Hh1Q+WKG1tfgq73HnvMP2sUlG4tega+VWeponmHxGYhTnyfxuAxJ5gDgdSIKN/Bf+ +KpYrtWKmpj29f5JZzVoqgrI3eQ== +-----END CERTIFICATE----- + +AddTrust Low-Value Services Root +================================ +-----BEGIN CERTIFICATE----- +MIIEGDCCAwCgAwIBAgIBATANBgkqhkiG9w0BAQUFADBlMQswCQYDVQQGEwJTRTEUMBIGA1UEChML +QWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBOZXR3b3JrMSEwHwYDVQQDExhBZGRU +cnVzdCBDbGFzcyAxIENBIFJvb3QwHhcNMDAwNTMwMTAzODMxWhcNMjAwNTMwMTAzODMxWjBlMQsw +CQYDVQQGEwJTRTEUMBIGA1UEChMLQWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBO +ZXR3b3JrMSEwHwYDVQQDExhBZGRUcnVzdCBDbGFzcyAxIENBIFJvb3QwggEiMA0GCSqGSIb3DQEB +AQUAA4IBDwAwggEKAoIBAQCWltQhSWDia+hBBwzexODcEyPNwTXH+9ZOEQpnXvUGW2ulCDtbKRY6 +54eyNAbFvAWlA3yCyykQruGIgb3WntP+LVbBFc7jJp0VLhD7Bo8wBN6ntGO0/7Gcrjyvd7ZWxbWr +oulpOj0OM3kyP3CCkplhbY0wCI9xP6ZIVxn4JdxLZlyldI+Yrsj5wAYi56xz36Uu+1LcsRVlIPo1 +Zmne3yzxbrww2ywkEtvrNTVokMsAsJchPXQhI2U0K7t4WaPW4XY5mqRJjox0r26kmqPZm9I4XJui +GMx1I4S+6+JNM3GOGvDC+Mcdoq0Dlyz4zyXG9rgkMbFjXZJ/Y/AlyVMuH79NAgMBAAGjgdIwgc8w +HQYDVR0OBBYEFJWxtPCUtr3H2tERCSG+wa9J/RB7MAsGA1UdDwQEAwIBBjAPBgNVHRMBAf8EBTAD +AQH/MIGPBgNVHSMEgYcwgYSAFJWxtPCUtr3H2tERCSG+wa9J/RB7oWmkZzBlMQswCQYDVQQGEwJT +RTEUMBIGA1UEChMLQWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBOZXR3b3JrMSEw +HwYDVQQDExhBZGRUcnVzdCBDbGFzcyAxIENBIFJvb3SCAQEwDQYJKoZIhvcNAQEFBQADggEBACxt +ZBsfzQ3duQH6lmM0MkhHma6X7f1yFqZzR1r0693p9db7RcwpiURdv0Y5PejuvE1Uhh4dbOMXJ0Ph +iVYrqW9yTkkz43J8KiOavD7/KCrto/8cI7pDVwlnTUtiBi34/2ydYB7YHEt9tTEv2dB8Xfjea4MY +eDdXL+gzB2ffHsdrKpV2ro9Xo/D0UrSpUwjP4E/TelOL/bscVjby/rK25Xa71SJlpz/+0WatC7xr +mYbvP33zGDLKe8bjq2RGlfgmadlVg3sslgf/WSxEo8bl6ancoWOAWiFeIc9TVPC6b4nbqKqVz4vj +ccweGyBECMB6tkD9xOQ14R0WHNC8K47Wcdk= +-----END CERTIFICATE----- + +AddTrust External Root +====================== +-----BEGIN CERTIFICATE----- +MIIENjCCAx6gAwIBAgIBATANBgkqhkiG9w0BAQUFADBvMQswCQYDVQQGEwJTRTEUMBIGA1UEChML +QWRkVHJ1c3QgQUIxJjAkBgNVBAsTHUFkZFRydXN0IEV4dGVybmFsIFRUUCBOZXR3b3JrMSIwIAYD +VQQDExlBZGRUcnVzdCBFeHRlcm5hbCBDQSBSb290MB4XDTAwMDUzMDEwNDgzOFoXDTIwMDUzMDEw +NDgzOFowbzELMAkGA1UEBhMCU0UxFDASBgNVBAoTC0FkZFRydXN0IEFCMSYwJAYDVQQLEx1BZGRU +cnVzdCBFeHRlcm5hbCBUVFAgTmV0d29yazEiMCAGA1UEAxMZQWRkVHJ1c3QgRXh0ZXJuYWwgQ0Eg +Um9vdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALf3GjPm8gAELTngTlvtH7xsD821 ++iO2zt6bETOXpClMfZOfvUq8k+0DGuOPz+VtUFrWlymUWoCwSXrbLpX9uMq/NzgtHj6RQa1wVsfw +Tz/oMp50ysiQVOnGXw94nZpAPA6sYapeFI+eh6FqUNzXmk6vBbOmcZSccbNQYArHE504B4YCqOmo +aSYYkKtMsE8jqzpPhNjfzp/haW+710LXa0Tkx63ubUFfclpxCDezeWWkWaCUN/cALw3CknLa0Dhy +2xSoRcRdKn23tNbE7qzNE0S3ySvdQwAl+mG5aWpYIxG3pzOPVnVZ9c0p10a3CitlttNCbxWyuHv7 +7+ldU9U0WicCAwEAAaOB3DCB2TAdBgNVHQ4EFgQUrb2YejS0Jvf6xCZU7wO94CTLVBowCwYDVR0P +BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wgZkGA1UdIwSBkTCBjoAUrb2YejS0Jvf6xCZU7wO94CTL +VBqhc6RxMG8xCzAJBgNVBAYTAlNFMRQwEgYDVQQKEwtBZGRUcnVzdCBBQjEmMCQGA1UECxMdQWRk +VHJ1c3QgRXh0ZXJuYWwgVFRQIE5ldHdvcmsxIjAgBgNVBAMTGUFkZFRydXN0IEV4dGVybmFsIENB +IFJvb3SCAQEwDQYJKoZIhvcNAQEFBQADggEBALCb4IUlwtYj4g+WBpKdQZic2YR5gdkeWxQHIzZl +j7DYd7usQWxHYINRsPkyPef89iYTx4AWpb9a/IfPeHmJIZriTAcKhjW88t5RxNKWt9x+Tu5w/Rw5 +6wwCURQtjr0W4MHfRnXnJK3s9EK0hZNwEGe6nQY1ShjTK3rMUUKhemPR5ruhxSvCNr4TDea9Y355 +e6cJDUCrat2PisP29owaQgVR1EX1n6diIWgVIEM8med8vSTYqZEXc4g/VhsxOBi0cQ+azcgOno4u +G+GMmIPLHzHxREzGBHNJdmAPx/i9F4BrLunMTA5amnkPIAou1Z5jJh5VkpTYghdae9C8x49OhgQ= +-----END CERTIFICATE----- + +AddTrust Public Services Root +============================= +-----BEGIN CERTIFICATE----- +MIIEFTCCAv2gAwIBAgIBATANBgkqhkiG9w0BAQUFADBkMQswCQYDVQQGEwJTRTEUMBIGA1UEChML +QWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBOZXR3b3JrMSAwHgYDVQQDExdBZGRU +cnVzdCBQdWJsaWMgQ0EgUm9vdDAeFw0wMDA1MzAxMDQxNTBaFw0yMDA1MzAxMDQxNTBaMGQxCzAJ +BgNVBAYTAlNFMRQwEgYDVQQKEwtBZGRUcnVzdCBBQjEdMBsGA1UECxMUQWRkVHJ1c3QgVFRQIE5l +dHdvcmsxIDAeBgNVBAMTF0FkZFRydXN0IFB1YmxpYyBDQSBSb290MIIBIjANBgkqhkiG9w0BAQEF +AAOCAQ8AMIIBCgKCAQEA6Rowj4OIFMEg2Dybjxt+A3S72mnTRqX4jsIMEZBRpS9mVEBV6tsfSlbu +nyNu9DnLoblv8n75XYcmYZ4c+OLspoH4IcUkzBEMP9smcnrHAZcHF/nXGCwwfQ56HmIexkvA/X1i +d9NEHif2P0tEs7c42TkfYNVRknMDtABp4/MUTu7R3AnPdzRGULD4EfL+OHn3Bzn+UZKXC1sIXzSG +Aa2Il+tmzV7R/9x98oTaunet3IAIx6eH1lWfl2royBFkuucZKT8Rs3iQhCBSWxHveNCD9tVIkNAw +HM+A+WD+eeSI8t0A65RF62WUaUC6wNW0uLp9BBGo6zEFlpROWCGOn9Bg/QIDAQABo4HRMIHOMB0G +A1UdDgQWBBSBPjfYkrAfd59ctKtzquf2NGAv+jALBgNVHQ8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB +/zCBjgYDVR0jBIGGMIGDgBSBPjfYkrAfd59ctKtzquf2NGAv+qFopGYwZDELMAkGA1UEBhMCU0Ux +FDASBgNVBAoTC0FkZFRydXN0IEFCMR0wGwYDVQQLExRBZGRUcnVzdCBUVFAgTmV0d29yazEgMB4G +A1UEAxMXQWRkVHJ1c3QgUHVibGljIENBIFJvb3SCAQEwDQYJKoZIhvcNAQEFBQADggEBAAP3FUr4 +JNojVhaTdt02KLmuG7jD8WS6IBh4lSknVwW8fCr0uVFV2ocC3g8WFzH4qnkuCRO7r7IgGRLlk/lL ++YPoRNWyQSW/iHVv/xD8SlTQX/D67zZzfRs2RcYhbbQVuE7PnFylPVoAjgbjPGsye/Kf8Lb93/Ao +GEjwxrzQvzSAlsJKsW2Ox5BF3i9nrEUEo3rcVZLJR2bYGozH7ZxOmuASu7VqTITh4SINhwBk/ox9 +Yjllpu9CtoAlEmEBqCQTcAARJl/6NVDFSMwGR+gn2HCNX2TmoUQmXiLsks3/QppEIW1cxeMiHV9H +EufOX1362KqxMy3ZdvJOOjMMK7MtkAY= +-----END CERTIFICATE----- + +AddTrust Qualified Certificates Root +==================================== +-----BEGIN CERTIFICATE----- +MIIEHjCCAwagAwIBAgIBATANBgkqhkiG9w0BAQUFADBnMQswCQYDVQQGEwJTRTEUMBIGA1UEChML +QWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBOZXR3b3JrMSMwIQYDVQQDExpBZGRU +cnVzdCBRdWFsaWZpZWQgQ0EgUm9vdDAeFw0wMDA1MzAxMDQ0NTBaFw0yMDA1MzAxMDQ0NTBaMGcx +CzAJBgNVBAYTAlNFMRQwEgYDVQQKEwtBZGRUcnVzdCBBQjEdMBsGA1UECxMUQWRkVHJ1c3QgVFRQ +IE5ldHdvcmsxIzAhBgNVBAMTGkFkZFRydXN0IFF1YWxpZmllZCBDQSBSb290MIIBIjANBgkqhkiG +9w0BAQEFAAOCAQ8AMIIBCgKCAQEA5B6a/twJWoekn0e+EV+vhDTbYjx5eLfpMLXsDBwqxBb/4Oxx +64r1EW7tTw2R0hIYLUkVAcKkIhPHEWT/IhKauY5cLwjPcWqzZwFZ8V1G87B4pfYOQnrjfxvM0PC3 +KP0q6p6zsLkEqv32x7SxuCqg+1jxGaBvcCV+PmlKfw8i2O+tCBGaKZnhqkRFmhJePp1tUvznoD1o +L/BLcHwTOK28FSXx1s6rosAx1i+f4P8UWfyEk9mHfExUE+uf0S0R+Bg6Ot4l2ffTQO2kBhLEO+GR +wVY18BTcZTYJbqukB8c10cIDMzZbdSZtQvESa0NvS3GU+jQd7RNuyoB/mC9suWXY6QIDAQABo4HU +MIHRMB0GA1UdDgQWBBQ5lYtii1zJ1IC6WA+XPxUIQ8yYpzALBgNVHQ8EBAMCAQYwDwYDVR0TAQH/ +BAUwAwEB/zCBkQYDVR0jBIGJMIGGgBQ5lYtii1zJ1IC6WA+XPxUIQ8yYp6FrpGkwZzELMAkGA1UE +BhMCU0UxFDASBgNVBAoTC0FkZFRydXN0IEFCMR0wGwYDVQQLExRBZGRUcnVzdCBUVFAgTmV0d29y +azEjMCEGA1UEAxMaQWRkVHJ1c3QgUXVhbGlmaWVkIENBIFJvb3SCAQEwDQYJKoZIhvcNAQEFBQAD +ggEBABmrder4i2VhlRO6aQTvhsoToMeqT2QbPxj2qC0sVY8FtzDqQmodwCVRLae/DLPt7wh/bDxG +GuoYQ992zPlmhpwsaPXpF/gxsxjE1kh9I0xowX67ARRvxdlu3rsEQmr49lx95dr6h+sNNVJn0J6X +dgWTP5XHAeZpVTh/EGGZyeNfpso+gmNIquIISD6q8rKFYqa0p9m9N5xotS1WfbC3P6CxB9bpT9ze +RXEwMn8bLgn5v1Kh7sKAPgZcLlVAwRv1cEWw3F369nJad9Jjzc9YiQBCYz95OdBEsIJuQRno3eDB +iFrRHnGTHyQwdOUeqN48Jzd/g66ed8/wMLH/S5noxqE= +-----END CERTIFICATE----- + +Entrust Root Certification Authority +==================================== +-----BEGIN CERTIFICATE----- +MIIEkTCCA3mgAwIBAgIERWtQVDANBgkqhkiG9w0BAQUFADCBsDELMAkGA1UEBhMCVVMxFjAUBgNV +BAoTDUVudHJ1c3QsIEluYy4xOTA3BgNVBAsTMHd3dy5lbnRydXN0Lm5ldC9DUFMgaXMgaW5jb3Jw +b3JhdGVkIGJ5IHJlZmVyZW5jZTEfMB0GA1UECxMWKGMpIDIwMDYgRW50cnVzdCwgSW5jLjEtMCsG +A1UEAxMkRW50cnVzdCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTA2MTEyNzIwMjM0 +MloXDTI2MTEyNzIwNTM0MlowgbAxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1FbnRydXN0LCBJbmMu +MTkwNwYDVQQLEzB3d3cuZW50cnVzdC5uZXQvQ1BTIGlzIGluY29ycG9yYXRlZCBieSByZWZlcmVu +Y2UxHzAdBgNVBAsTFihjKSAyMDA2IEVudHJ1c3QsIEluYy4xLTArBgNVBAMTJEVudHJ1c3QgUm9v +dCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB +ALaVtkNC+sZtKm9I35RMOVcF7sN5EUFoNu3s/poBj6E4KPz3EEZmLk0eGrEaTsbRwJWIsMn/MYsz +A9u3g3s+IIRe7bJWKKf44LlAcTfFy0cOlypowCKVYhXbR9n10Cv/gkvJrT7eTNuQgFA/CYqEAOww +Cj0Yzfv9KlmaI5UXLEWeH25DeW0MXJj+SKfFI0dcXv1u5x609mhF0YaDW6KKjbHjKYD+JXGIrb68 +j6xSlkuqUY3kEzEZ6E5Nn9uss2rVvDlUccp6en+Q3X0dgNmBu1kmwhH+5pPi94DkZfs0Nw4pgHBN +rziGLp5/V6+eF67rHMsoIV+2HNjnogQi+dPa2MsCAwEAAaOBsDCBrTAOBgNVHQ8BAf8EBAMCAQYw +DwYDVR0TAQH/BAUwAwEB/zArBgNVHRAEJDAigA8yMDA2MTEyNzIwMjM0MlqBDzIwMjYxMTI3MjA1 +MzQyWjAfBgNVHSMEGDAWgBRokORnpKZTgMeGZqTx90tD+4S9bTAdBgNVHQ4EFgQUaJDkZ6SmU4DH +hmak8fdLQ/uEvW0wHQYJKoZIhvZ9B0EABBAwDhsIVjcuMTo0LjADAgSQMA0GCSqGSIb3DQEBBQUA +A4IBAQCT1DCw1wMgKtD5Y+iRDAUgqV8ZyntyTtSx29CW+1RaGSwMCPeyvIWonX9tO1KzKtvn1ISM +Y/YPyyYBkVBs9F8U4pN0wBOeMDpQ47RgxRzwIkSNcUesyBrJ6ZuaAGAT/3B+XxFNSRuzFVJ7yVTa +v52Vr2ua2J7p8eRDjeIRRDq/r72DQnNSi6q7pynP9WQcCk3RvKqsnyrQ/39/2n3qse0wJcGE2jTS +W3iDVuycNsMm4hH2Z0kdkquM++v/eu6FSqdQgPCnXEqULl8FmTxSQeDNtGPPAUO6nIPcj2A781q0 +tHuu2guQOHXvgR1m0vdXcDazv/wor3ElhVsT/h5/WrQ8 +-----END CERTIFICATE----- + +Entrust G2 Root Certificate Authority +===================================== +-----BEGIN CERTIFICATE----- +MIIEPjCCAyagAwIBAgIESlOMKDANBgkqhkiG9w0BAQsFADCBvjELMAkGA1UEBhMC +VVMxFjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3d3cuZW50 +cnVzdC5uZXQvbGVnYWwtdGVybXMxOTA3BgNVBAsTMChjKSAyMDA5IEVudHJ1c3Qs +IEluYy4gLSBmb3IgYXV0aG9yaXplZCB1c2Ugb25seTEyMDAGA1UEAxMpRW50cnVz +dCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzIwHhcNMDkwNzA3MTcy +NTU0WhcNMzAxMjA3MTc1NTU0WjCBvjELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUVu +dHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3d3cuZW50cnVzdC5uZXQvbGVnYWwt +dGVybXMxOTA3BgNVBAsTMChjKSAyMDA5IEVudHJ1c3QsIEluYy4gLSBmb3IgYXV0 +aG9yaXplZCB1c2Ugb25seTEyMDAGA1UEAxMpRW50cnVzdCBSb290IENlcnRpZmlj +YXRpb24gQXV0aG9yaXR5IC0gRzIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK +AoIBAQC6hLZy254Ma+KZ6TABp3bqMriVQRrJ2mFOWHLP/vaCeb9zYQYKpSfYs1/T +RU4cctZOMvJyig/3gxnQaoCAAEUesMfnmr8SVycco2gvCoe9amsOXmXzHHfV1IWN +cCG0szLni6LVhjkCsbjSR87kyUnEO6fe+1R9V77w6G7CebI6C1XiUJgWMhNcL3hW +wcKUs/Ja5CeanyTXxuzQmyWC48zCxEXFjJd6BmsqEZ+pCm5IO2/b1BEZQvePB7/1 +U1+cPvQXLOZprE4yTGJ36rfo5bs0vBmLrpxR57d+tVOxMyLlbc9wPBr64ptntoP0 +jaWvYkxN4FisZDQSA/i2jZRjJKRxAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAP +BgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRqciZ60B7vfec7aVHUbI2fkBJmqzAN +BgkqhkiG9w0BAQsFAAOCAQEAeZ8dlsa2eT8ijYfThwMEYGprmi5ZiXMRrEPR9RP/ +jTkrwPK9T3CMqS/qF8QLVJ7UG5aYMzyorWKiAHarWWluBh1+xLlEjZivEtRh2woZ +Rkfz6/djwUAFQKXSt/S1mja/qYh2iARVBCuch38aNzx+LaUa2NSJXsq9rD1s2G2v +1fN2D807iDginWyTmsQ9v4IbZT+mD12q/OWyFcq1rca8PdCE6OoGcrBNOTJ4vz4R +nAuknZoh8/CbCzB428Hch0P+vGOaysXCHMnHjf87ElgI5rY97HosTvuDls4MPGmH +VHOkc8KT/1EQrBVUAdj8BbGJoX90g5pJ19xOe4pIb4tF9g== +-----END CERTIFICATE----- + +Entrust L1M Chain Root Certificate +================================== +-----BEGIN CERTIFICATE----- +MIIE/zCCA+egAwIBAgIEUdNARDANBgkqhkiG9w0BAQsFADCBsDELMAkGA1UEBhMC +VVMxFjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xOTA3BgNVBAsTMHd3dy5lbnRydXN0 +Lm5ldC9DUFMgaXMgaW5jb3Jwb3JhdGVkIGJ5IHJlZmVyZW5jZTEfMB0GA1UECxMW +KGMpIDIwMDYgRW50cnVzdCwgSW5jLjEtMCsGA1UEAxMkRW50cnVzdCBSb290IENl +cnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTE0MDkyMjE3MTQ1N1oXDTI0MDkyMzAx +MzE1M1owgb4xCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1FbnRydXN0LCBJbmMuMSgw +JgYDVQQLEx9TZWUgd3d3LmVudHJ1c3QubmV0L2xlZ2FsLXRlcm1zMTkwNwYDVQQL +EzAoYykgMjAwOSBFbnRydXN0LCBJbmMuIC0gZm9yIGF1dGhvcml6ZWQgdXNlIG9u +bHkxMjAwBgNVBAMTKUVudHJ1c3QgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0 +eSAtIEcyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuoS2ctueDGvi +mekwAad26jK4lUEaydphTlhyz/72gnm/c2EGCqUn2LNf00VOHHLWTjLycooP94MZ +0GqAgABFHrDH55q/ElcnHKNoLwqHvWprDl5l8xx31dSFjXAhtLMy54ui1YY5ArG4 +0kfO5MlJxDun3vtUfVe+8OhuwnmyOgtV4lCYFjITXC94VsHClLPyWuQnmp8k18bs +0JslguPMwsRFxYyXegZrKhGfqQpuSDtv29QRGUL3jwe/9VNfnD70FyzmaaxOMkxi +d+q36OW7NLwZi66cUee3frVTsTMi5W3PcDwa+uKbZ7aD9I2lr2JMTeBYrGQ0EgP4 +to2UYySkcQIDAQABo4IBDzCCAQswDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQI +MAYBAf8CAQEwMwYIKwYBBQUHAQEEJzAlMCMGCCsGAQUFBzABhhdodHRwOi8vb2Nz +cC5lbnRydXN0Lm5ldDAzBgNVHR8ELDAqMCigJqAkhiJodHRwOi8vY3JsLmVudHJ1 +c3QubmV0L3Jvb3RjYTEuY3JsMDsGA1UdIAQ0MDIwMAYEVR0gADAoMCYGCCsGAQUF +BwIBFhpodHRwOi8vd3d3LmVudHJ1c3QubmV0L0NQUzAdBgNVHQ4EFgQUanImetAe +733nO2lR1GyNn5ASZqswHwYDVR0jBBgwFoAUaJDkZ6SmU4DHhmak8fdLQ/uEvW0w +DQYJKoZIhvcNAQELBQADggEBAGkzg/woem99751V68U+ep11s8zDODbZNKIoaBjq +HmnTvefQd9q4AINOSs9v0fHBIj905PeYSZ6btp7h25h3LVY0sag82f3Azce/BQPU +AsXx5cbaCKUTx2IjEdFhMB1ghEXveajGJpOkt800uGnFE/aRs8lFc3a2kvZ2Clvh +A0e36SlMkTIjN0qcNdh4/R0f5IOJJICtt/nP5F2l1HHEhVtwH9s/HAHrGkUmMRTM +Zb9n3srMM2XlQZHXN75BGpad5oqXnafOrE6aPb0BoGrZTyIAi0TVaWJ7LuvMuueS +fWlnPfy4fN5Bh9Bp6roKGHoalUOzeXEodm2h+1dK7E3IDhA= +-----END CERTIFICATE----- + +RSA Security 2048 v3 +==================== +-----BEGIN CERTIFICATE----- +MIIDYTCCAkmgAwIBAgIQCgEBAQAAAnwAAAAKAAAAAjANBgkqhkiG9w0BAQUFADA6MRkwFwYDVQQK +ExBSU0EgU2VjdXJpdHkgSW5jMR0wGwYDVQQLExRSU0EgU2VjdXJpdHkgMjA0OCBWMzAeFw0wMTAy +MjIyMDM5MjNaFw0yNjAyMjIyMDM5MjNaMDoxGTAXBgNVBAoTEFJTQSBTZWN1cml0eSBJbmMxHTAb +BgNVBAsTFFJTQSBTZWN1cml0eSAyMDQ4IFYzMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC +AQEAt49VcdKA3XtpeafwGFAyPGJn9gqVB93mG/Oe2dJBVGutn3y+Gc37RqtBaB4Y6lXIL5F4iSj7 +Jylg/9+PjDvJSZu1pJTOAeo+tWN7fyb9Gd3AIb2E0S1PRsNO3Ng3OTsor8udGuorryGlwSMiuLgb +WhOHV4PR8CDn6E8jQrAApX2J6elhc5SYcSa8LWrg903w8bYqODGBDSnhAMFRD0xS+ARaqn1y07iH +KrtjEAMqs6FPDVpeRrc9DvV07Jmf+T0kgYim3WBU6JU2PcYJk5qjEoAAVZkZR73QpXzDuvsf9/UP ++Ky5tfQ3mBMY3oVbtwyCO4dvlTlYMNpuAWgXIszACwIDAQABo2MwYTAPBgNVHRMBAf8EBTADAQH/ +MA4GA1UdDwEB/wQEAwIBBjAfBgNVHSMEGDAWgBQHw1EwpKrpRa41JPr/JCwz0LGdjDAdBgNVHQ4E +FgQUB8NRMKSq6UWuNST6/yQsM9CxnYwwDQYJKoZIhvcNAQEFBQADggEBAF8+hnZuuDU8TjYcHnmY +v/3VEhF5Ug7uMYm83X/50cYVIeiKAVQNOvtUudZj1LGqlk2iQk3UUx+LEN5/Zb5gEydxiKRz44Rj +0aRV4VCT5hsOedBnvEbIvz8XDZXmxpBp3ue0L96VfdASPz0+f00/FGj1EVDVwfSQpQgdMWD/YIwj +VAqv/qFuxdF6Kmh4zx6CCiC0H63lhbJqaHVOrSU3lIW+vaHU6rcMSzyd6BIA8F+sDeGscGNz9395 +nzIlQnQFgCi/vcEkllgVsRch6YlL2weIZ/QVrXA+L02FO8K32/6YaCOJ4XQP3vTFhGMpG8zLB8kA +pKnXwiJPZ9d37CAFYd4= +-----END CERTIFICATE----- + +GeoTrust Global CA +================== +-----BEGIN CERTIFICATE----- +MIIDVDCCAjygAwIBAgIDAjRWMA0GCSqGSIb3DQEBBQUAMEIxCzAJBgNVBAYTAlVTMRYwFAYDVQQK +Ew1HZW9UcnVzdCBJbmMuMRswGQYDVQQDExJHZW9UcnVzdCBHbG9iYWwgQ0EwHhcNMDIwNTIxMDQw +MDAwWhcNMjIwNTIxMDQwMDAwWjBCMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5j +LjEbMBkGA1UEAxMSR2VvVHJ1c3QgR2xvYmFsIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB +CgKCAQEA2swYYzD99BcjGlZ+W988bDjkcbd4kdS8odhM+KhDtgPpTSEHCIjaWC9mOSm9BXiLnTjo +BbdqfnGk5sRgprDvgOSJKA+eJdbtg/OtppHHmMlCGDUUna2YRpIuT8rxh0PBFpVXLVDviS2Aelet +8u5fa9IAjbkU+BQVNdnARqN7csiRv8lVK83Qlz6cJmTM386DGXHKTubU1XupGc1V3sjs0l44U+Vc +T4wt/lAjNvxm5suOpDkZALeVAjmRCw7+OC7RHQWa9k0+bw8HHa8sHo9gOeL6NlMTOdReJivbPagU +vTLrGAMoUgRx5aszPeE4uwc2hGKceeoWMPRfwCvocWvk+QIDAQABo1MwUTAPBgNVHRMBAf8EBTAD +AQH/MB0GA1UdDgQWBBTAephojYn7qwVkDBF9qn1luMrMTjAfBgNVHSMEGDAWgBTAephojYn7qwVk +DBF9qn1luMrMTjANBgkqhkiG9w0BAQUFAAOCAQEANeMpauUvXVSOKVCUn5kaFOSPeCpilKInZ57Q +zxpeR+nBsqTP3UEaBU6bS+5Kb1VSsyShNwrrZHYqLizz/Tt1kL/6cdjHPTfStQWVYrmm3ok9Nns4 +d0iXrKYgjy6myQzCsplFAMfOEVEiIuCl6rYVSAlk6l5PdPcFPseKUgzbFbS9bZvlxrFUaKnjaZC2 +mqUPuLk/IH2uSrW4nOQdtqvmlKXBx4Ot2/Unhw4EbNX/3aBd7YdStysVAq45pmp06drE57xNNB6p +XE0zX5IJL4hmXXeXxx12E6nV5fEWCRE11azbJHFwLJhWC9kXtNHjUStedejV0NxPNO3CBWaAocvm +Mw== +-----END CERTIFICATE----- + +GeoTrust Global CA 2 +==================== +-----BEGIN CERTIFICATE----- +MIIDZjCCAk6gAwIBAgIBATANBgkqhkiG9w0BAQUFADBEMQswCQYDVQQGEwJVUzEWMBQGA1UEChMN +R2VvVHJ1c3QgSW5jLjEdMBsGA1UEAxMUR2VvVHJ1c3QgR2xvYmFsIENBIDIwHhcNMDQwMzA0MDUw +MDAwWhcNMTkwMzA0MDUwMDAwWjBEMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5j +LjEdMBsGA1UEAxMUR2VvVHJ1c3QgR2xvYmFsIENBIDIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw +ggEKAoIBAQDvPE1APRDfO1MA4Wf+lGAVPoWI8YkNkMgoI5kF6CsgncbzYEbYwbLVjDHZ3CB5JIG/ +NTL8Y2nbsSpr7iFY8gjpeMtvy/wWUsiRxP89c96xPqfCfWbB9X5SJBri1WeR0IIQ13hLTytCOb1k +LUCgsBDTOEhGiKEMuzozKmKY+wCdE1l/bztyqu6mD4b5BWHqZ38MN5aL5mkWRxHCJ1kDs6ZgwiFA +Vvqgx306E+PsV8ez1q6diYD3Aecs9pYrEw15LNnA5IZ7S4wMcoKK+xfNAGw6EzywhIdLFnopsk/b +HdQL82Y3vdj2V7teJHq4PIu5+pIaGoSe2HSPqht/XvT+RSIhAgMBAAGjYzBhMA8GA1UdEwEB/wQF +MAMBAf8wHQYDVR0OBBYEFHE4NvICMVNHK266ZUapEBVYIAUJMB8GA1UdIwQYMBaAFHE4NvICMVNH +K266ZUapEBVYIAUJMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQUFAAOCAQEAA/e1K6tdEPx7 +srJerJsOflN4WT5CBP51o62sgU7XAotexC3IUnbHLB/8gTKY0UvGkpMzNTEv/NgdRN3ggX+d6Yvh +ZJFiCzkIjKx0nVnZellSlxG5FntvRdOW2TF9AjYPnDtuzywNA0ZF66D0f0hExghAzN4bcLUprbqL +OzRldRtxIR0sFAqwlpW41uryZfspuk/qkZN0abby/+Ea0AzRdoXLiiW9l14sbxWZJue2Kf8i7MkC +x1YAzUm5s2x7UwQa4qjJqhIFI8LO57sEAszAR6LkxCkvW0VXiVHuPOtSCP8HNR6fNWpHSlaY0VqF +H4z1Ir+rzoPz4iIprn2DQKi6bA== +-----END CERTIFICATE----- + +GeoTrust Universal CA +===================== +-----BEGIN CERTIFICATE----- +MIIFaDCCA1CgAwIBAgIBATANBgkqhkiG9w0BAQUFADBFMQswCQYDVQQGEwJVUzEWMBQGA1UEChMN +R2VvVHJ1c3QgSW5jLjEeMBwGA1UEAxMVR2VvVHJ1c3QgVW5pdmVyc2FsIENBMB4XDTA0MDMwNDA1 +MDAwMFoXDTI5MDMwNDA1MDAwMFowRTELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUdlb1RydXN0IElu +Yy4xHjAcBgNVBAMTFUdlb1RydXN0IFVuaXZlcnNhbCBDQTCCAiIwDQYJKoZIhvcNAQEBBQADggIP +ADCCAgoCggIBAKYVVaCjxuAfjJ0hUNfBvitbtaSeodlyWL0AG0y/YckUHUWCq8YdgNY96xCcOq9t +JPi8cQGeBvV8Xx7BDlXKg5pZMK4ZyzBIle0iN430SppyZj6tlcDgFgDgEB8rMQ7XlFTTQjOgNB0e +RXbdT8oYN+yFFXoZCPzVx5zw8qkuEKmS5j1YPakWaDwvdSEYfyh3peFhF7em6fgemdtzbvQKoiFs +7tqqhZJmr/Z6a4LauiIINQ/PQvE1+mrufislzDoR5G2vc7J2Ha3QsnhnGqQ5HFELZ1aD/ThdDc7d +8Lsrlh/eezJS/R27tQahsiFepdaVaH/wmZ7cRQg+59IJDTWU3YBOU5fXtQlEIGQWFwMCTFMNaN7V +qnJNk22CDtucvc+081xdVHppCZbW2xHBjXWotM85yM48vCR85mLK4b19p71XZQvk/iXttmkQ3Cga +Rr0BHdCXteGYO8A3ZNY9lO4L4fUorgtWv3GLIylBjobFS1J72HGrH4oVpjuDWtdYAVHGTEHZf9hB +Z3KiKN9gg6meyHv8U3NyWfWTehd2Ds735VzZC1U0oqpbtWpU5xPKV+yXbfReBi9Fi1jUIxaS5BZu +KGNZMN9QAZxjiRqf2xeUgnA3wySemkfWWspOqGmJch+RbNt+nhutxx9z3SxPGWX9f5NAEC7S8O08 +ni4oPmkmM8V7AgMBAAGjYzBhMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFNq7LqqwDLiIJlF0 +XG0D08DYj3rWMB8GA1UdIwQYMBaAFNq7LqqwDLiIJlF0XG0D08DYj3rWMA4GA1UdDwEB/wQEAwIB +hjANBgkqhkiG9w0BAQUFAAOCAgEAMXjmx7XfuJRAyXHEqDXsRh3ChfMoWIawC/yOsjmPRFWrZIRc +aanQmjg8+uUfNeVE44B5lGiku8SfPeE0zTBGi1QrlaXv9z+ZhP015s8xxtxqv6fXIwjhmF7DWgh2 +qaavdy+3YL1ERmrvl/9zlcGO6JP7/TG37FcREUWbMPEaiDnBTzynANXH/KttgCJwpQzgXQQpAvvL +oJHRfNbDflDVnVi+QTjruXU8FdmbyUqDWcDaU/0zuzYYm4UPFd3uLax2k7nZAY1IEKj79TiG8dsK +xr2EoyNB3tZ3b4XUhRxQ4K5RirqNPnbiucon8l+f725ZDQbYKxek0nxru18UGkiPGkzns0ccjkxF +KyDuSN/n3QmOGKjaQI2SJhFTYXNd673nxE0pN2HrrDktZy4W1vUAg4WhzH92xH3kt0tm7wNFYGm2 +DFKWkoRepqO1pD4r2czYG0eq8kTaT/kD6PAUyz/zg97QwVTjt+gKN02LIFkDMBmhLMi9ER/frslK +xfMnZmaGrGiR/9nmUxwPi1xpZQomyB40w11Re9epnAahNt3ViZS82eQtDF4JbAiXfKM9fJP/P6EU +p8+1Xevb2xzEdt+Iub1FBZUbrvxGakyvSOPOrg/SfuvmbJxPgWp6ZKy7PtXny3YuxadIwVyQD8vI +P/rmMuGNG2+k5o7Y+SlIis5z/iw= +-----END CERTIFICATE----- + +GeoTrust Universal CA 2 +======================= +-----BEGIN CERTIFICATE----- +MIIFbDCCA1SgAwIBAgIBATANBgkqhkiG9w0BAQUFADBHMQswCQYDVQQGEwJVUzEWMBQGA1UEChMN +R2VvVHJ1c3QgSW5jLjEgMB4GA1UEAxMXR2VvVHJ1c3QgVW5pdmVyc2FsIENBIDIwHhcNMDQwMzA0 +MDUwMDAwWhcNMjkwMzA0MDUwMDAwWjBHMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNR2VvVHJ1c3Qg +SW5jLjEgMB4GA1UEAxMXR2VvVHJ1c3QgVW5pdmVyc2FsIENBIDIwggIiMA0GCSqGSIb3DQEBAQUA +A4ICDwAwggIKAoICAQCzVFLByT7y2dyxUxpZKeexw0Uo5dfR7cXFS6GqdHtXr0om/Nj1XqduGdt0 +DE81WzILAePb63p3NeqqWuDW6KFXlPCQo3RWlEQwAx5cTiuFJnSCegx2oG9NzkEtoBUGFF+3Qs17 +j1hhNNwqCPkuwwGmIkQcTAeC5lvO0Ep8BNMZcyfwqph/Lq9O64ceJHdqXbboW0W63MOhBW9Wjo8Q +JqVJwy7XQYci4E+GymC16qFjwAGXEHm9ADwSbSsVsaxLse4YuU6W3Nx2/zu+z18DwPw76L5GG//a +QMJS9/7jOvdqdzXQ2o3rXhhqMcceujwbKNZrVMaqW9eiLBsZzKIC9ptZvTdrhrVtgrrY6slWvKk2 +WP0+GfPtDCapkzj4T8FdIgbQl+rhrcZV4IErKIM6+vR7IVEAvlI4zs1meaj0gVbi0IMJR1FbUGrP +20gaXT73y/Zl92zxlfgCOzJWgjl6W70viRu/obTo/3+NjN8D8WBOWBFM66M/ECuDmgFz2ZRthAAn +ZqzwcEAJQpKtT5MNYQlRJNiS1QuUYbKHsu3/mjX/hVTK7URDrBs8FmtISgocQIgfksILAAX/8sgC +SqSqqcyZlpwvWOB94b67B9xfBHJcMTTD7F8t4D1kkCLm0ey4Lt1ZrtmhN79UNdxzMk+MBB4zsslG +8dhcyFVQyWi9qLo2CQIDAQABo2MwYTAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBR281Xh+qQ2 ++/CfXGJx7Tz0RzgQKzAfBgNVHSMEGDAWgBR281Xh+qQ2+/CfXGJx7Tz0RzgQKzAOBgNVHQ8BAf8E +BAMCAYYwDQYJKoZIhvcNAQEFBQADggIBAGbBxiPz2eAubl/oz66wsCVNK/g7WJtAJDday6sWSf+z +dXkzoS9tcBc0kf5nfo/sm+VegqlVHy/c1FEHEv6sFj4sNcZj/NwQ6w2jqtB8zNHQL1EuxBRa3ugZ +4T7GzKQp5y6EqgYweHZUcyiYWTjgAA1i00J9IZ+uPTqM1fp3DRgrFg5fNuH8KrUwJM/gYwx7WBr+ +mbpCErGR9Hxo4sjoryzqyX6uuyo9DRXcNJW2GHSoag/HtPQTxORb7QrSpJdMKu0vbBKJPfEncKpq +A1Ihn0CoZ1Dy81of398j9tx4TuaYT1U6U+Pv8vSfx3zYWK8pIpe44L2RLrB27FcRz+8pRPPphXpg +Y+RdM4kX2TGq2tbzGDVyz4crL2MjhF2EjD9XoIj8mZEoJmmZ1I+XRL6O1UixpCgp8RW04eWe3fiP +pm8m1wk8OhwRDqZsN/etRIcsKMfYdIKz0G9KV7s1KSegi+ghp4dkNl3M2Basx7InQJJVOCiNUW7d +FGdTbHFcJoRNdVq2fmBWqU2t+5sel/MN2dKXVHfaPRK34B7vCAas+YWH6aLcr34YEoP9VhdBLtUp +gn2Z9DH2canPLAEnpQW5qrJITirvn5NSUZU8UnOOVkwXQMAJKOSLakhT2+zNVVXxxvjpoixMptEm +X36vWkzaH6byHCx+rgIW0lbQL1dTR+iS +-----END CERTIFICATE----- + +America Online Root Certification Authority 1 +============================================= +-----BEGIN CERTIFICATE----- +MIIDpDCCAoygAwIBAgIBATANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJVUzEcMBoGA1UEChMT +QW1lcmljYSBPbmxpbmUgSW5jLjE2MDQGA1UEAxMtQW1lcmljYSBPbmxpbmUgUm9vdCBDZXJ0aWZp +Y2F0aW9uIEF1dGhvcml0eSAxMB4XDTAyMDUyODA2MDAwMFoXDTM3MTExOTIwNDMwMFowYzELMAkG +A1UEBhMCVVMxHDAaBgNVBAoTE0FtZXJpY2EgT25saW5lIEluYy4xNjA0BgNVBAMTLUFtZXJpY2Eg +T25saW5lIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgMTCCASIwDQYJKoZIhvcNAQEBBQAD +ggEPADCCAQoCggEBAKgv6KRpBgNHw+kqmP8ZonCaxlCyfqXfaE0bfA+2l2h9LaaLl+lkhsmj76CG +v2BlnEtUiMJIxUo5vxTjWVXlGbR0yLQFOVwWpeKVBeASrlmLojNoWBym1BW32J/X3HGrfpq/m44z +DyL9Hy7nBzbvYjnF3cu6JRQj3gzGPTzOggjmZj7aUTsWOqMFf6Dch9Wc/HKpoH145LcxVR5lu9Rh +sCFg7RAycsWSJR74kEoYeEfffjA3PlAb2xzTa5qGUwew76wGePiEmf4hjUyAtgyC9mZweRrTT6PP +8c9GsEsPPt2IYriMqQkoO3rHl+Ee5fSfwMCuJKDIodkP1nsmgmkyPacCAwEAAaNjMGEwDwYDVR0T +AQH/BAUwAwEB/zAdBgNVHQ4EFgQUAK3Zo/Z59m50qX8zPYEX10zPM94wHwYDVR0jBBgwFoAUAK3Z +o/Z59m50qX8zPYEX10zPM94wDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBBQUAA4IBAQB8itEf +GDeC4Liwo+1WlchiYZwFos3CYiZhzRAW18y0ZTTQEYqtqKkFZu90821fnZmv9ov761KyBZiibyrF +VL0lvV+uyIbqRizBs73B6UlwGBaXCBOMIOAbLjpHyx7kADCVW/RFo8AasAFOq73AI25jP4BKxQft +3OJvx8Fi8eNy1gTIdGcL+oiroQHIb/AUr9KZzVGTfu0uOMe9zkZQPXLjeSWdm4grECDdpbgyn43g +Kd8hdIaC2y+CMMbHNYaz+ZZfRtsMRf3zUMNvxsNIrUam4SdHCh0Om7bCd39j8uB9Gr784N/Xx6ds +sPmuujz9dLQR6FgNgLzTqIA6me11zEZ7 +-----END CERTIFICATE----- + +America Online Root Certification Authority 2 +============================================= +-----BEGIN CERTIFICATE----- +MIIFpDCCA4ygAwIBAgIBATANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJVUzEcMBoGA1UEChMT +QW1lcmljYSBPbmxpbmUgSW5jLjE2MDQGA1UEAxMtQW1lcmljYSBPbmxpbmUgUm9vdCBDZXJ0aWZp +Y2F0aW9uIEF1dGhvcml0eSAyMB4XDTAyMDUyODA2MDAwMFoXDTM3MDkyOTE0MDgwMFowYzELMAkG +A1UEBhMCVVMxHDAaBgNVBAoTE0FtZXJpY2EgT25saW5lIEluYy4xNjA0BgNVBAMTLUFtZXJpY2Eg +T25saW5lIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgMjCCAiIwDQYJKoZIhvcNAQEBBQAD +ggIPADCCAgoCggIBAMxBRR3pPU0Q9oyxQcngXssNt79Hc9PwVU3dxgz6sWYFas14tNwC206B89en +fHG8dWOgXeMHDEjsJcQDIPT/DjsS/5uN4cbVG7RtIuOx238hZK+GvFciKtZHgVdEglZTvYYUAQv8 +f3SkWq7xuhG1m1hagLQ3eAkzfDJHA1zEpYNI9FdWboE2JxhP7JsowtS013wMPgwr38oE18aO6lhO +qKSlGBxsRZijQdEt0sdtjRnxrXm3gT+9BoInLRBYBbV4Bbkv2wxrkJB+FFk4u5QkE+XRnRTf04JN +RvCAOVIyD+OEsnpD8l7eXz8d3eOyG6ChKiMDbi4BFYdcpnV1x5dhvt6G3NRI270qv0pV2uh9UPu0 +gBe4lL8BPeraunzgWGcXuVjgiIZGZ2ydEEdYMtA1fHkqkKJaEBEjNa0vzORKW6fIJ/KD3l67Xnfn +6KVuY8INXWHQjNJsWiEOyiijzirplcdIz5ZvHZIlyMbGwcEMBawmxNJ10uEqZ8A9W6Wa6897Gqid +FEXlD6CaZd4vKL3Ob5Rmg0gp2OpljK+T2WSfVVcmv2/LNzGZo2C7HK2JNDJiuEMhBnIMoVxtRsX6 +Kc8w3onccVvdtjc+31D1uAclJuW8tf48ArO3+L5DwYcRlJ4jbBeKuIonDFRH8KmzwICMoCfrHRnj +B453cMor9H124HhnAgMBAAGjYzBhMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFE1FwWg4u3Op +aaEg5+31IqEjFNeeMB8GA1UdIwQYMBaAFE1FwWg4u3OpaaEg5+31IqEjFNeeMA4GA1UdDwEB/wQE +AwIBhjANBgkqhkiG9w0BAQUFAAOCAgEAZ2sGuV9FOypLM7PmG2tZTiLMubekJcmnxPBUlgtk87FY +T15R/LKXeydlwuXK5w0MJXti4/qftIe3RUavg6WXSIylvfEWK5t2LHo1YGwRgJfMqZJS5ivmae2p ++DYtLHe/YUjRYwu5W1LtGLBDQiKmsXeu3mnFzcccobGlHBD7GL4acN3Bkku+KVqdPzW+5X1R+FXg +JXUjhx5c3LqdsKyzadsXg8n33gy8CNyRnqjQ1xU3c6U1uPx+xURABsPr+CKAXEfOAuMRn0T//Zoy +zH1kUQ7rVyZ2OuMeIjzCpjbdGe+n/BLzJsBZMYVMnNjP36TMzCmT/5RtdlwTCJfy7aULTd3oyWgO +ZtMADjMSW7yV5TKQqLPGbIOtd+6Lfn6xqavT4fG2wLHqiMDn05DpKJKUe2h7lyoKZy2FAjgQ5ANh +1NolNscIWC2hp1GvMApJ9aZphwctREZ2jirlmjvXGKL8nDgQzMY70rUXOm/9riW99XJZZLF0Kjhf +GEzfz3EEWjbUvy+ZnOjZurGV5gJLIaFb1cFPj65pbVPbAZO1XB4Y3WRayhgoPmMEEf0cjQAPuDff +Z4qdZqkCapH/E8ovXYO8h5Ns3CRRFgQlZvqz2cK6Kb6aSDiCmfS/O0oxGfm/jiEzFMpPVF/7zvuP +cX/9XhmgD0uRuMRUvAawRY8mkaKO/qk= +-----END CERTIFICATE----- + +Visa eCommerce Root +=================== +-----BEGIN CERTIFICATE----- +MIIDojCCAoqgAwIBAgIQE4Y1TR0/BvLB+WUF1ZAcYjANBgkqhkiG9w0BAQUFADBrMQswCQYDVQQG +EwJVUzENMAsGA1UEChMEVklTQTEvMC0GA1UECxMmVmlzYSBJbnRlcm5hdGlvbmFsIFNlcnZpY2Ug +QXNzb2NpYXRpb24xHDAaBgNVBAMTE1Zpc2EgZUNvbW1lcmNlIFJvb3QwHhcNMDIwNjI2MDIxODM2 +WhcNMjIwNjI0MDAxNjEyWjBrMQswCQYDVQQGEwJVUzENMAsGA1UEChMEVklTQTEvMC0GA1UECxMm +VmlzYSBJbnRlcm5hdGlvbmFsIFNlcnZpY2UgQXNzb2NpYXRpb24xHDAaBgNVBAMTE1Zpc2EgZUNv +bW1lcmNlIFJvb3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvV95WHm6h2mCxlCfL +F9sHP4CFT8icttD0b0/Pmdjh28JIXDqsOTPHH2qLJj0rNfVIsZHBAk4ElpF7sDPwsRROEW+1QK8b +RaVK7362rPKgH1g/EkZgPI2h4H3PVz4zHvtH8aoVlwdVZqW1LS7YgFmypw23RuwhY/81q6UCzyr0 +TP579ZRdhE2o8mCP2w4lPJ9zcc+U30rq299yOIzzlr3xF7zSujtFWsan9sYXiwGd/BmoKoMWuDpI +/k4+oKsGGelT84ATB+0tvz8KPFUgOSwsAGl0lUq8ILKpeeUYiZGo3BxN77t+Nwtd/jmliFKMAGzs +GHxBvfaLdXe6YJ2E5/4tAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEG +MB0GA1UdDgQWBBQVOIMPPyw/cDMezUb+B4wg4NfDtzANBgkqhkiG9w0BAQUFAAOCAQEAX/FBfXxc +CLkr4NWSR/pnXKUTwwMhmytMiUbPWU3J/qVAtmPN3XEolWcRzCSs00Rsca4BIGsDoo8Ytyk6feUW +YFN4PMCvFYP3j1IzJL1kk5fui/fbGKhtcbP3LBfQdCVp9/5rPJS+TUtBjE7ic9DjkCJzQ83z7+pz +zkWKsKZJ/0x9nXGIxHYdkFsd7v3M9+79YKWxehZx0RbQfBI8bGmX265fOZpwLwU8GUYEmSA20GBu +YQa7FkKMcPcw++DbZqMAAb3mLNqRX6BGi01qnD093QVG/na/oAo85ADmJ7f/hC3euiInlhBx6yLt +398znM/jra6O1I7mT1GvFpLgXPYHDw== +-----END CERTIFICATE----- + +Certum Root CA +============== +-----BEGIN CERTIFICATE----- +MIIDDDCCAfSgAwIBAgIDAQAgMA0GCSqGSIb3DQEBBQUAMD4xCzAJBgNVBAYTAlBMMRswGQYDVQQK +ExJVbml6ZXRvIFNwLiB6IG8uby4xEjAQBgNVBAMTCUNlcnR1bSBDQTAeFw0wMjA2MTExMDQ2Mzla +Fw0yNzA2MTExMDQ2MzlaMD4xCzAJBgNVBAYTAlBMMRswGQYDVQQKExJVbml6ZXRvIFNwLiB6IG8u +by4xEjAQBgNVBAMTCUNlcnR1bSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAM6x +wS7TT3zNJc4YPk/EjG+AanPIW1H4m9LcuwBcsaD8dQPugfCI7iNS6eYVM42sLQnFdvkrOYCJ5JdL +kKWoePhzQ3ukYbDYWMzhbGZ+nPMJXlVjhNWo7/OxLjBos8Q82KxujZlakE403Daaj4GIULdtlkIJ +89eVgw1BS7Bqa/j8D35in2fE7SZfECYPCE/wpFcozo+47UX2bu4lXapuOb7kky/ZR6By6/qmW6/K +Uz/iDsaWVhFu9+lmqSbYf5VT7QqFiLpPKaVCjF62/IUgAKpoC6EahQGcxEZjgoi2IrHu/qpGWX7P +NSzVttpd90gzFFS269lvzs2I1qsb2pY7HVkCAwEAAaMTMBEwDwYDVR0TAQH/BAUwAwEB/zANBgkq +hkiG9w0BAQUFAAOCAQEAuI3O7+cUus/usESSbLQ5PqKEbq24IXfS1HeCh+YgQYHu4vgRt2PRFze+ +GXYkHAQaTOs9qmdvLdTN/mUxcMUbpgIKumB7bVjCmkn+YzILa+M6wKyrO7Do0wlRjBCDxjTgxSvg +GrZgFCdsMneMvLJymM/NzD+5yCRCFNZX/OYmQ6kd5YCQzgNUKD73P9P4Te1qCjqTE5s7FCMTY5w/ +0YcneeVMUeMBrYVdGjux1XMQpNPyvG5k9VpWkKjHDkx0Dy5xO/fIR/RpbxXyEV6DHpx8Uq79AtoS +qFlnGNu8cN2bsWntgM6JQEhqDjXKKWYVIZQs6GAqm4VKQPNriiTsBhYscw== +-----END CERTIFICATE----- + +Comodo AAA Services root +======================== +-----BEGIN CERTIFICATE----- +MIIEMjCCAxqgAwIBAgIBATANBgkqhkiG9w0BAQUFADB7MQswCQYDVQQGEwJHQjEbMBkGA1UECAwS +R3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRowGAYDVQQKDBFDb21vZG8gQ0Eg +TGltaXRlZDEhMB8GA1UEAwwYQUFBIENlcnRpZmljYXRlIFNlcnZpY2VzMB4XDTA0MDEwMTAwMDAw +MFoXDTI4MTIzMTIzNTk1OVowezELMAkGA1UEBhMCR0IxGzAZBgNVBAgMEkdyZWF0ZXIgTWFuY2hl +c3RlcjEQMA4GA1UEBwwHU2FsZm9yZDEaMBgGA1UECgwRQ29tb2RvIENBIExpbWl0ZWQxITAfBgNV +BAMMGEFBQSBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC +ggEBAL5AnfRu4ep2hxxNRUSOvkbIgwadwSr+GB+O5AL686tdUIoWMQuaBtDFcCLNSS1UY8y2bmhG +C1Pqy0wkwLxyTurxFa70VJoSCsN6sjNg4tqJVfMiWPPe3M/vg4aijJRPn2jymJBGhCfHdr/jzDUs +i14HZGWCwEiwqJH5YZ92IFCokcdmtet4YgNW8IoaE+oxox6gmf049vYnMlhvB/VruPsUK6+3qszW +Y19zjNoFmag4qMsXeDZRrOme9Hg6jc8P2ULimAyrL58OAd7vn5lJ8S3frHRNG5i1R8XlKdH5kBjH +Ypy+g8cmez6KJcfA3Z3mNWgQIJ2P2N7Sw4ScDV7oL8kCAwEAAaOBwDCBvTAdBgNVHQ4EFgQUoBEK +Iz6W8Qfs4q8p74Klf9AwpLQwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wewYDVR0f +BHQwcjA4oDagNIYyaHR0cDovL2NybC5jb21vZG9jYS5jb20vQUFBQ2VydGlmaWNhdGVTZXJ2aWNl +cy5jcmwwNqA0oDKGMGh0dHA6Ly9jcmwuY29tb2RvLm5ldC9BQUFDZXJ0aWZpY2F0ZVNlcnZpY2Vz +LmNybDANBgkqhkiG9w0BAQUFAAOCAQEACFb8AvCb6P+k+tZ7xkSAzk/ExfYAWMymtrwUSWgEdujm +7l3sAg9g1o1QGE8mTgHj5rCl7r+8dFRBv/38ErjHT1r0iWAFf2C3BUrz9vHCv8S5dIa2LX1rzNLz +Rt0vxuBqw8M0Ayx9lt1awg6nCpnBBYurDC/zXDrPbDdVCYfeU0BsWO/8tqtlbgT2G9w84FoVxp7Z +8VlIMCFlA2zs6SFz7JsDoeA3raAVGI/6ugLOpyypEBMs1OUIJqsil2D4kF501KKaU73yqWjgom7C +12yxow+ev+to51byrvLjKzg6CYG1a4XXvi3tPxq3smPi9WIsgtRqAEFQ8TmDn5XpNpaYbg== +-----END CERTIFICATE----- + +Comodo Secure Services root +=========================== +-----BEGIN CERTIFICATE----- +MIIEPzCCAyegAwIBAgIBATANBgkqhkiG9w0BAQUFADB+MQswCQYDVQQGEwJHQjEbMBkGA1UECAwS +R3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRowGAYDVQQKDBFDb21vZG8gQ0Eg +TGltaXRlZDEkMCIGA1UEAwwbU2VjdXJlIENlcnRpZmljYXRlIFNlcnZpY2VzMB4XDTA0MDEwMTAw +MDAwMFoXDTI4MTIzMTIzNTk1OVowfjELMAkGA1UEBhMCR0IxGzAZBgNVBAgMEkdyZWF0ZXIgTWFu +Y2hlc3RlcjEQMA4GA1UEBwwHU2FsZm9yZDEaMBgGA1UECgwRQ29tb2RvIENBIExpbWl0ZWQxJDAi +BgNVBAMMG1NlY3VyZSBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczCCASIwDQYJKoZIhvcNAQEBBQADggEP +ADCCAQoCggEBAMBxM4KK0HDrc4eCQNUd5MvJDkKQ+d40uaG6EfQlhfPMcm3ye5drswfxdySRXyWP +9nQ95IDC+DwN879A6vfIUtFyb+/Iq0G4bi4XKpVpDM3SHpR7LZQdqnXXs5jLrLxkU0C8j6ysNstc +rbvd4JQX7NFc0L/vpZXJkMWwrPsbQ996CF23uPJAGysnnlDOXmWCiIxe004MeuoIkbY2qitC++rC +oznl2yY4rYsK7hljxxwk3wN42ubqwUcaCwtGCd0C/N7Lh1/XMGNooa7cMqG6vv5Eq2i2pRcV/b3V +p6ea5EQz6YiO/O1R65NxTq0B50SOqy3LqP4BSUjwwN3HaNiS/j0CAwEAAaOBxzCBxDAdBgNVHQ4E +FgQUPNiTiMLAggnMAZkGkyDpnnAJY08wDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8w +gYEGA1UdHwR6MHgwO6A5oDeGNWh0dHA6Ly9jcmwuY29tb2RvY2EuY29tL1NlY3VyZUNlcnRpZmlj +YXRlU2VydmljZXMuY3JsMDmgN6A1hjNodHRwOi8vY3JsLmNvbW9kby5uZXQvU2VjdXJlQ2VydGlm +aWNhdGVTZXJ2aWNlcy5jcmwwDQYJKoZIhvcNAQEFBQADggEBAIcBbSMdflsXfcFhMs+P5/OKlFlm +4J4oqF7Tt/Q05qo5spcWxYJvMqTpjOev/e/C6LlLqqP05tqNZSH7uoDrJiiFGv45jN5bBAS0VPmj +Z55B+glSzAVIqMk/IQQezkhr/IXownuvf7fM+F86/TXGDe+X3EyrEeFryzHRbPtIgKvcnDe4IRRL +DXE97IMzbtFuMhbsmMcWi1mmNKsFVy2T96oTy9IT4rcuO81rUBcJaD61JlfutuC23bkpgHl9j6Pw +pCikFcSF9CfUa7/lXORlAnZUtOM3ZiTTGWHIUhDlizeauan5Hb/qmZJhlv8BzaFfDbxxvA6sCx1H +RR3B7Hzs/Sk= +-----END CERTIFICATE----- + +Comodo Trusted Services root +============================ +-----BEGIN CERTIFICATE----- +MIIEQzCCAyugAwIBAgIBATANBgkqhkiG9w0BAQUFADB/MQswCQYDVQQGEwJHQjEbMBkGA1UECAwS +R3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRowGAYDVQQKDBFDb21vZG8gQ0Eg +TGltaXRlZDElMCMGA1UEAwwcVHJ1c3RlZCBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczAeFw0wNDAxMDEw +MDAwMDBaFw0yODEyMzEyMzU5NTlaMH8xCzAJBgNVBAYTAkdCMRswGQYDVQQIDBJHcmVhdGVyIE1h +bmNoZXN0ZXIxEDAOBgNVBAcMB1NhbGZvcmQxGjAYBgNVBAoMEUNvbW9kbyBDQSBMaW1pdGVkMSUw +IwYDVQQDDBxUcnVzdGVkIENlcnRpZmljYXRlIFNlcnZpY2VzMIIBIjANBgkqhkiG9w0BAQEFAAOC +AQ8AMIIBCgKCAQEA33FvNlhTWvI2VFeAxHQIIO0Yfyod5jWaHiWsnOWWfnJSoBVC21ndZHoa0Lh7 +3TkVvFVIxO06AOoxEbrycXQaZ7jPM8yoMa+j49d/vzMtTGo87IvDktJTdyR0nAducPy9C1t2ul/y +/9c3S0pgePfw+spwtOpZqqPOSC+pw7ILfhdyFgymBwwbOM/JYrc/oJOlh0Hyt3BAd9i+FHzjqMB6 +juljatEPmsbS9Is6FARW1O24zG71++IsWL1/T2sr92AkWCTOJu80kTrV44HQsvAEAtdbtz6SrGsS +ivnkBbA7kUlcsutT6vifR4buv5XAwAaf0lteERv0xwQ1KdJVXOTt6wIDAQABo4HJMIHGMB0GA1Ud +DgQWBBTFe1i97doladL3WRaoszLAeydb9DAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB +/zCBgwYDVR0fBHwwejA8oDqgOIY2aHR0cDovL2NybC5jb21vZG9jYS5jb20vVHJ1c3RlZENlcnRp +ZmljYXRlU2VydmljZXMuY3JsMDqgOKA2hjRodHRwOi8vY3JsLmNvbW9kby5uZXQvVHJ1c3RlZENl +cnRpZmljYXRlU2VydmljZXMuY3JsMA0GCSqGSIb3DQEBBQUAA4IBAQDIk4E7ibSvuIQSTI3S8Ntw +uleGFTQQuS9/HrCoiWChisJ3DFBKmwCL2Iv0QeLQg4pKHBQGsKNoBXAxMKdTmw7pSqBYaWcOrp32 +pSxBvzwGa+RZzG0Q8ZZvH9/0BAKkn0U+yNj6NkZEUD+Cl5EfKNsYEYwq5GWDVxISjBc/lDb+XbDA +BHcTuPQV1T84zJQ6VdCsmPW6AF/ghhmBeC8owH7TzEIK9a5QoNE+xqFx7D+gIIxmOom0jtTYsU0l +R+4viMi14QVFwL4Ucd56/Y57fU0IlqUSc/AtyjcndBInTMu2l+nZrghtWjlA3QVHdWpaIbOjGM9O +9y5Xt5hwXsjEeLBi +-----END CERTIFICATE----- + +QuoVadis Root CA +================ +-----BEGIN CERTIFICATE----- +MIIF0DCCBLigAwIBAgIEOrZQizANBgkqhkiG9w0BAQUFADB/MQswCQYDVQQGEwJCTTEZMBcGA1UE +ChMQUXVvVmFkaXMgTGltaXRlZDElMCMGA1UECxMcUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0 +eTEuMCwGA1UEAxMlUXVvVmFkaXMgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wMTAz +MTkxODMzMzNaFw0yMTAzMTcxODMzMzNaMH8xCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRp +cyBMaW1pdGVkMSUwIwYDVQQLExxSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MS4wLAYDVQQD +EyVRdW9WYWRpcyBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEF +AAOCAQ8AMIIBCgKCAQEAv2G1lVO6V/z68mcLOhrfEYBklbTRvM16z/Ypli4kVEAkOPcahdxYTMuk +J0KX0J+DisPkBgNbAKVRHnAEdOLB1Dqr1607BxgFjv2DrOpm2RgbaIr1VxqYuvXtdj182d6UajtL +F8HVj71lODqV0D1VNk7feVcxKh7YWWVJWCCYfqtffp/p1k3sg3Spx2zY7ilKhSoGFPlU5tPaZQeL +YzcS19Dsw3sgQUSj7cugF+FxZc4dZjH3dgEZyH0DWLaVSR2mEiboxgx24ONmy+pdpibu5cxfvWen +AScOospUxbF6lR1xHkopigPcakXBpBlebzbNw6Kwt/5cOOJSvPhEQ+aQuwIDAQABo4ICUjCCAk4w +PQYIKwYBBQUHAQEEMTAvMC0GCCsGAQUFBzABhiFodHRwczovL29jc3AucXVvdmFkaXNvZmZzaG9y +ZS5jb20wDwYDVR0TAQH/BAUwAwEB/zCCARoGA1UdIASCAREwggENMIIBCQYJKwYBBAG+WAABMIH7 +MIHUBggrBgEFBQcCAjCBxxqBxFJlbGlhbmNlIG9uIHRoZSBRdW9WYWRpcyBSb290IENlcnRpZmlj +YXRlIGJ5IGFueSBwYXJ0eSBhc3N1bWVzIGFjY2VwdGFuY2Ugb2YgdGhlIHRoZW4gYXBwbGljYWJs +ZSBzdGFuZGFyZCB0ZXJtcyBhbmQgY29uZGl0aW9ucyBvZiB1c2UsIGNlcnRpZmljYXRpb24gcHJh +Y3RpY2VzLCBhbmQgdGhlIFF1b1ZhZGlzIENlcnRpZmljYXRlIFBvbGljeS4wIgYIKwYBBQUHAgEW +Fmh0dHA6Ly93d3cucXVvdmFkaXMuYm0wHQYDVR0OBBYEFItLbe3TKbkGGew5Oanwl4Rqy+/fMIGu +BgNVHSMEgaYwgaOAFItLbe3TKbkGGew5Oanwl4Rqy+/foYGEpIGBMH8xCzAJBgNVBAYTAkJNMRkw +FwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMSUwIwYDVQQLExxSb290IENlcnRpZmljYXRpb24gQXV0 +aG9yaXR5MS4wLAYDVQQDEyVRdW9WYWRpcyBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5ggQ6 +tlCLMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQUFAAOCAQEAitQUtf70mpKnGdSkfnIYj9lo +fFIk3WdvOXrEql494liwTXCYhGHoG+NpGA7O+0dQoE7/8CQfvbLO9Sf87C9TqnN7Az10buYWnuul +LsS/VidQK2K6vkscPFVcQR0kvoIgR13VRH56FmjffU1RcHhXHTMe/QKZnAzNCgVPx7uOpHX6Sm2x +gI4JVrmcGmD+XcHXetwReNDWXcG31a0ymQM6isxUJTkxgXsTIlG6Rmyhu576BGxJJnSP0nPrzDCi +5upZIof4l/UO/erMkqQWxFIY6iHOsfHmhIHluqmGKPJDWl0Snawe2ajlCmqnf6CHKc/yiU3U7MXi +5nrQNiOKSnQ2+Q== +-----END CERTIFICATE----- + +QuoVadis Root CA 2 +================== +-----BEGIN CERTIFICATE----- +MIIFtzCCA5+gAwIBAgICBQkwDQYJKoZIhvcNAQEFBQAwRTELMAkGA1UEBhMCQk0xGTAXBgNVBAoT +EFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMTElF1b1ZhZGlzIFJvb3QgQ0EgMjAeFw0wNjExMjQx +ODI3MDBaFw0zMTExMjQxODIzMzNaMEUxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM +aW1pdGVkMRswGQYDVQQDExJRdW9WYWRpcyBSb290IENBIDIwggIiMA0GCSqGSIb3DQEBAQUAA4IC +DwAwggIKAoICAQCaGMpLlA0ALa8DKYrwD4HIrkwZhR0In6spRIXzL4GtMh6QRr+jhiYaHv5+HBg6 +XJxgFyo6dIMzMH1hVBHL7avg5tKifvVrbxi3Cgst/ek+7wrGsxDp3MJGF/hd/aTa/55JWpzmM+Yk +lvc/ulsrHHo1wtZn/qtmUIttKGAr79dgw8eTvI02kfN/+NsRE8Scd3bBrrcCaoF6qUWD4gXmuVbB +lDePSHFjIuwXZQeVikvfj8ZaCuWw419eaxGrDPmF60Tp+ARz8un+XJiM9XOva7R+zdRcAitMOeGy +lZUtQofX1bOQQ7dsE/He3fbE+Ik/0XX1ksOR1YqI0JDs3G3eicJlcZaLDQP9nL9bFqyS2+r+eXyt +66/3FsvbzSUr5R/7mp/iUcw6UwxI5g69ybR2BlLmEROFcmMDBOAENisgGQLodKcftslWZvB1Jdxn +wQ5hYIizPtGo/KPaHbDRsSNU30R2be1B2MGyIrZTHN81Hdyhdyox5C315eXbyOD/5YDXC2Og/zOh +D7osFRXql7PSorW+8oyWHhqPHWykYTe5hnMz15eWniN9gqRMgeKh0bpnX5UHoycR7hYQe7xFSkyy +BNKr79X9DFHOUGoIMfmR2gyPZFwDwzqLID9ujWc9Otb+fVuIyV77zGHcizN300QyNQliBJIWENie +J0f7OyHj+OsdWwIDAQABo4GwMIGtMA8GA1UdEwEB/wQFMAMBAf8wCwYDVR0PBAQDAgEGMB0GA1Ud +DgQWBBQahGK8SEwzJQTU7tD2A8QZRtGUazBuBgNVHSMEZzBlgBQahGK8SEwzJQTU7tD2A8QZRtGU +a6FJpEcwRTELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMT +ElF1b1ZhZGlzIFJvb3QgQ0EgMoICBQkwDQYJKoZIhvcNAQEFBQADggIBAD4KFk2fBluornFdLwUv +Z+YTRYPENvbzwCYMDbVHZF34tHLJRqUDGCdViXh9duqWNIAXINzng/iN/Ae42l9NLmeyhP3ZRPx3 +UIHmfLTJDQtyU/h2BwdBR5YM++CCJpNVjP4iH2BlfF/nJrP3MpCYUNQ3cVX2kiF495V5+vgtJodm +VjB3pjd4M1IQWK4/YY7yarHvGH5KWWPKjaJW1acvvFYfzznB4vsKqBUsfU16Y8Zsl0Q80m/DShcK ++JDSV6IZUaUtl0HaB0+pUNqQjZRG4T7wlP0QADj1O+hA4bRuVhogzG9Yje0uRY/W6ZM/57Es3zrW +IozchLsib9D45MY56QSIPMO661V6bYCZJPVsAfv4l7CUW+v90m/xd2gNNWQjrLhVoQPRTUIZ3Ph1 +WVaj+ahJefivDrkRoHy3au000LYmYjgahwz46P0u05B/B5EqHdZ+XIWDmbA4CD/pXvk1B+TJYm5X +f6dQlfe6yJvmjqIBxdZmv3lh8zwc4bmCXF2gw+nYSL0ZohEUGW6yhhtoPkg3Goi3XZZenMfvJ2II +4pEZXNLxId26F0KCl3GBUzGpn/Z9Yr9y4aOTHcyKJloJONDO1w2AFrR4pTqHTI2KpdVGl/IsELm8 +VCLAAVBpQ570su9t+Oza8eOx79+Rj1QqCyXBJhnEUhAFZdWCEOrCMc0u +-----END CERTIFICATE----- + +QuoVadis Root CA 3 +================== +-----BEGIN CERTIFICATE----- +MIIGnTCCBIWgAwIBAgICBcYwDQYJKoZIhvcNAQEFBQAwRTELMAkGA1UEBhMCQk0xGTAXBgNVBAoT +EFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMTElF1b1ZhZGlzIFJvb3QgQ0EgMzAeFw0wNjExMjQx +OTExMjNaFw0zMTExMjQxOTA2NDRaMEUxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM +aW1pdGVkMRswGQYDVQQDExJRdW9WYWRpcyBSb290IENBIDMwggIiMA0GCSqGSIb3DQEBAQUAA4IC +DwAwggIKAoICAQDMV0IWVJzmmNPTTe7+7cefQzlKZbPoFog02w1ZkXTPkrgEQK0CSzGrvI2RaNgg +DhoB4hp7Thdd4oq3P5kazethq8Jlph+3t723j/z9cI8LoGe+AaJZz3HmDyl2/7FWeUUrH556VOij +KTVopAFPD6QuN+8bv+OPEKhyq1hX51SGyMnzW9os2l2ObjyjPtr7guXd8lyyBTNvijbO0BNO/79K +DDRMpsMhvVAEVeuxu537RR5kFd5VAYwCdrXLoT9CabwvvWhDFlaJKjdhkf2mrk7AyxRllDdLkgbv +BNDInIjbC3uBr7E9KsRlOni27tyAsdLTmZw67mtaa7ONt9XOnMK+pUsvFrGeaDsGb659n/je7Mwp +p5ijJUMv7/FfJuGITfhebtfZFG4ZM2mnO4SJk8RTVROhUXhA+LjJou57ulJCg54U7QVSWllWp5f8 +nT8KKdjcT5EOE7zelaTfi5m+rJsziO+1ga8bxiJTyPbH7pcUsMV8eFLI8M5ud2CEpukqdiDtWAEX +MJPpGovgc2PZapKUSU60rUqFxKMiMPwJ7Wgic6aIDFUhWMXhOp8q3crhkODZc6tsgLjoC2SToJyM +Gf+z0gzskSaHirOi4XCPLArlzW1oUevaPwV/izLmE1xr/l9A4iLItLRkT9a6fUg+qGkM17uGcclz +uD87nSVL2v9A6wIDAQABo4IBlTCCAZEwDwYDVR0TAQH/BAUwAwEB/zCB4QYDVR0gBIHZMIHWMIHT +BgkrBgEEAb5YAAMwgcUwgZMGCCsGAQUFBwICMIGGGoGDQW55IHVzZSBvZiB0aGlzIENlcnRpZmlj +YXRlIGNvbnN0aXR1dGVzIGFjY2VwdGFuY2Ugb2YgdGhlIFF1b1ZhZGlzIFJvb3QgQ0EgMyBDZXJ0 +aWZpY2F0ZSBQb2xpY3kgLyBDZXJ0aWZpY2F0aW9uIFByYWN0aWNlIFN0YXRlbWVudC4wLQYIKwYB +BQUHAgEWIWh0dHA6Ly93d3cucXVvdmFkaXNnbG9iYWwuY29tL2NwczALBgNVHQ8EBAMCAQYwHQYD +VR0OBBYEFPLAE+CCQz777i9nMpY1XNu4ywLQMG4GA1UdIwRnMGWAFPLAE+CCQz777i9nMpY1XNu4 +ywLQoUmkRzBFMQswCQYDVQQGEwJCTTEZMBcGA1UEChMQUXVvVmFkaXMgTGltaXRlZDEbMBkGA1UE +AxMSUXVvVmFkaXMgUm9vdCBDQSAzggIFxjANBgkqhkiG9w0BAQUFAAOCAgEAT62gLEz6wPJv92ZV +qyM07ucp2sNbtrCD2dDQ4iH782CnO11gUyeim/YIIirnv6By5ZwkajGxkHon24QRiSemd1o417+s +hvzuXYO8BsbRd2sPbSQvS3pspweWyuOEn62Iix2rFo1bZhfZFvSLgNLd+LJ2w/w4E6oM3kJpK27z +POuAJ9v1pkQNn1pVWQvVDVJIxa6f8i+AxeoyUDUSly7B4f/xI4hROJ/yZlZ25w9Rl6VSDE1JUZU2 +Pb+iSwwQHYaZTKrzchGT5Or2m9qoXadNt54CrnMAyNojA+j56hl0YgCUyyIgvpSnWbWCar6ZeXqp +8kokUvd0/bpO5qgdAm6xDYBEwa7TIzdfu4V8K5Iu6H6li92Z4b8nby1dqnuH/grdS/yO9SbkbnBC +bjPsMZ57k8HkyWkaPcBrTiJt7qtYTcbQQcEr6k8Sh17rRdhs9ZgC06DYVYoGmRmioHfRMJ6szHXu +g/WwYjnPbFfiTNKRCw51KBuav/0aQ/HKd/s7j2G4aSgWQgRecCocIdiP4b0jWy10QJLZYxkNc91p +vGJHvOB0K7Lrfb5BG7XARsWhIstfTsEokt4YutUqKLsRixeTmJlglFwjz1onl14LBQaTNx47aTbr +qZ5hHY8y2o4M1nQ+ewkk2gF3R8Q7zTSMmfXK4SVhM7JZG+Ju1zdXtg2pEto= +-----END CERTIFICATE----- + +Security Communication Root CA +============================== +-----BEGIN CERTIFICATE----- +MIIDWjCCAkKgAwIBAgIBADANBgkqhkiG9w0BAQUFADBQMQswCQYDVQQGEwJKUDEYMBYGA1UEChMP +U0VDT00gVHJ1c3QubmV0MScwJQYDVQQLEx5TZWN1cml0eSBDb21tdW5pY2F0aW9uIFJvb3RDQTEw +HhcNMDMwOTMwMDQyMDQ5WhcNMjMwOTMwMDQyMDQ5WjBQMQswCQYDVQQGEwJKUDEYMBYGA1UEChMP +U0VDT00gVHJ1c3QubmV0MScwJQYDVQQLEx5TZWN1cml0eSBDb21tdW5pY2F0aW9uIFJvb3RDQTEw +ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCzs/5/022x7xZ8V6UMbXaKL0u/ZPtM7orw +8yl89f/uKuDp6bpbZCKamm8sOiZpUQWZJtzVHGpxxpp9Hp3dfGzGjGdnSj74cbAZJ6kJDKaVv0uM +DPpVmDvY6CKhS3E4eayXkmmziX7qIWgGmBSWh9JhNrxtJ1aeV+7AwFb9Ms+k2Y7CI9eNqPPYJayX +5HA49LY6tJ07lyZDo6G8SVlyTCMwhwFY9k6+HGhWZq/NQV3Is00qVUarH9oe4kA92819uZKAnDfd +DJZkndwi92SL32HeFZRSFaB9UslLqCHJxrHty8OVYNEP8Ktw+N/LTX7s1vqr2b1/VPKl6Xn62dZ2 +JChzAgMBAAGjPzA9MB0GA1UdDgQWBBSgc0mZaNyFW2XjmygvV5+9M7wHSDALBgNVHQ8EBAMCAQYw +DwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQUFAAOCAQEAaECpqLvkT115swW1F7NgE+vGkl3g +0dNq/vu+m22/xwVtWSDEHPC32oRYAmP6SBbvT6UL90qY8j+eG61Ha2POCEfrUj94nK9NrvjVT8+a +mCoQQTlSxN3Zmw7vkwGusi7KaEIkQmywszo+zenaSMQVy+n5Bw+SUEmK3TGXX8npN6o7WWWXlDLJ +s58+OmJYxUmtYg5xpTKqL8aJdkNAExNnPaJUJRDL8Try2frbSVa7pv6nQTXD4IhhyYjH3zYQIphZ +6rBK+1YWc26sTfcioU+tHXotRSflMMFe8toTyyVCUZVHA4xsIcx0Qu1T/zOLjw9XARYvz6buyXAi +FL39vmwLAw== +-----END CERTIFICATE----- + +Sonera Class 2 Root CA +====================== +-----BEGIN CERTIFICATE----- +MIIDIDCCAgigAwIBAgIBHTANBgkqhkiG9w0BAQUFADA5MQswCQYDVQQGEwJGSTEPMA0GA1UEChMG +U29uZXJhMRkwFwYDVQQDExBTb25lcmEgQ2xhc3MyIENBMB4XDTAxMDQwNjA3Mjk0MFoXDTIxMDQw +NjA3Mjk0MFowOTELMAkGA1UEBhMCRkkxDzANBgNVBAoTBlNvbmVyYTEZMBcGA1UEAxMQU29uZXJh +IENsYXNzMiBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJAXSjWdyvANlsdE+hY3 +/Ei9vX+ALTU74W+oZ6m/AxxNjG8yR9VBaKQTBME1DJqEQ/xcHf+Js+gXGM2RX/uJ4+q/Tl18GybT +dXnt5oTjV+WtKcT0OijnpXuENmmz/V52vaMtmdOQTiMofRhj8VQ7Jp12W5dCsv+u8E7s3TmVToMG +f+dJQMjFAbJUWmYdPfz56TwKnoG4cPABi+QjVHzIrviQHgCWctRUz2EjvOr7nQKV0ba5cTppCD8P +tOFCx4j1P5iop7oc4HFx71hXgVB6XGt0Rg6DA5jDjqhu8nYybieDwnPz3BjotJPqdURrBGAgcVeH +nfO+oJAjPYok4doh28MCAwEAAaMzMDEwDwYDVR0TAQH/BAUwAwEB/zARBgNVHQ4ECgQISqCqWITT +XjwwCwYDVR0PBAQDAgEGMA0GCSqGSIb3DQEBBQUAA4IBAQBazof5FnIVV0sd2ZvnoiYw7JNn39Yt +0jSv9zilzqsWuasvfDXLrNAPtEwr/IDva4yRXzZ299uzGxnq9LIR/WFxRL8oszodv7ND6J+/3DEI +cbCdjdY0RzKQxmUk96BKfARzjzlvF4xytb1LyHr4e4PDKE6cCepnP7JnBBvDFNr450kkkdAdavph +Oe9r5yF1BgfYErQhIHBCcYHaPJo2vqZbDWpsmh+Re/n570K6Tk6ezAyNlNzZRZxe7EJQY670XcSx +EtzKO6gunRRaBXW37Ndj4ro1tgQIkejanZz2ZrUYrAqmVCY0M9IbwdR/GjqOC6oybtv8TyWf2TLH +llpwrN9M +-----END CERTIFICATE----- + +Staat der Nederlanden Root CA +============================= +-----BEGIN CERTIFICATE----- +MIIDujCCAqKgAwIBAgIEAJiWijANBgkqhkiG9w0BAQUFADBVMQswCQYDVQQGEwJOTDEeMBwGA1UE +ChMVU3RhYXQgZGVyIE5lZGVybGFuZGVuMSYwJAYDVQQDEx1TdGFhdCBkZXIgTmVkZXJsYW5kZW4g +Um9vdCBDQTAeFw0wMjEyMTcwOTIzNDlaFw0xNTEyMTYwOTE1MzhaMFUxCzAJBgNVBAYTAk5MMR4w +HAYDVQQKExVTdGFhdCBkZXIgTmVkZXJsYW5kZW4xJjAkBgNVBAMTHVN0YWF0IGRlciBOZWRlcmxh +bmRlbiBSb290IENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAmNK1URF6gaYUmHFt +vsznExvWJw56s2oYHLZhWtVhCb/ekBPHZ+7d89rFDBKeNVU+LCeIQGv33N0iYfXCxw719tV2U02P +jLwYdjeFnejKScfST5gTCaI+Ioicf9byEGW07l8Y1Rfj+MX94p2i71MOhXeiD+EwR+4A5zN9RGca +C1Hoi6CeUJhoNFIfLm0B8mBF8jHrqTFoKbt6QZ7GGX+UtFE5A3+y3qcym7RHjm+0Sq7lr7HcsBth +vJly3uSJt3omXdozSVtSnA71iq3DuD3oBmrC1SoLbHuEvVYFy4ZlkuxEK7COudxwC0barbxjiDn6 +22r+I/q85Ej0ZytqERAhSQIDAQABo4GRMIGOMAwGA1UdEwQFMAMBAf8wTwYDVR0gBEgwRjBEBgRV +HSAAMDwwOgYIKwYBBQUHAgEWLmh0dHA6Ly93d3cucGtpb3ZlcmhlaWQubmwvcG9saWNpZXMvcm9v +dC1wb2xpY3kwDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBSofeu8Y6R0E3QA7Jbg0zTBLL9s+DAN +BgkqhkiG9w0BAQUFAAOCAQEABYSHVXQ2YcG70dTGFagTtJ+k/rvuFbQvBgwp8qiSpGEN/KtcCFtR +EytNwiphyPgJWPwtArI5fZlmgb9uXJVFIGzmeafR2Bwp/MIgJ1HI8XxdNGdphREwxgDS1/PTfLbw +MVcoEoJz6TMvplW0C5GUR5z6u3pCMuiufi3IvKwUv9kP2Vv8wfl6leF9fpb8cbDCTMjfRTTJzg3y +nGQI0DvDKcWy7ZAEwbEpkcUwb8GpcjPM/l0WFywRaed+/sWDCN+83CI6LiBpIzlWYGeQiy52OfsR +iJf2fL1LuCAWZwWN4jvBcj+UlTfHXbme2JOhF4//DGYVwSR8MnwDHTuhWEUykw== +-----END CERTIFICATE----- + +TDC Internet Root CA +==================== +-----BEGIN CERTIFICATE----- +MIIEKzCCAxOgAwIBAgIEOsylTDANBgkqhkiG9w0BAQUFADBDMQswCQYDVQQGEwJESzEVMBMGA1UE +ChMMVERDIEludGVybmV0MR0wGwYDVQQLExRUREMgSW50ZXJuZXQgUm9vdCBDQTAeFw0wMTA0MDUx +NjMzMTdaFw0yMTA0MDUxNzAzMTdaMEMxCzAJBgNVBAYTAkRLMRUwEwYDVQQKEwxUREMgSW50ZXJu +ZXQxHTAbBgNVBAsTFFREQyBJbnRlcm5ldCBSb290IENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A +MIIBCgKCAQEAxLhAvJHVYx/XmaCLDEAedLdInUaMArLgJF/wGROnN4NrXceO+YQwzho7+vvOi20j +xsNuZp+Jpd/gQlBn+h9sHvTQBda/ytZO5GhgbEaqHF1j4QeGDmUApy6mcca8uYGoOn0a0vnRrEvL +znWv3Hv6gXPU/Lq9QYjUdLP5Xjg6PEOo0pVOd20TDJ2PeAG3WiAfAzc14izbSysseLlJ28TQx5yc +5IogCSEWVmb/Bexb4/DPqyQkXsN/cHoSxNK1EKC2IeGNeGlVRGn1ypYcNIUXJXfi9i8nmHj9eQY6 +otZaQ8H/7AQ77hPv01ha/5Lr7K7a8jcDR0G2l8ktCkEiu7vmpwIDAQABo4IBJTCCASEwEQYJYIZI +AYb4QgEBBAQDAgAHMGUGA1UdHwReMFwwWqBYoFakVDBSMQswCQYDVQQGEwJESzEVMBMGA1UEChMM +VERDIEludGVybmV0MR0wGwYDVQQLExRUREMgSW50ZXJuZXQgUm9vdCBDQTENMAsGA1UEAxMEQ1JM +MTArBgNVHRAEJDAigA8yMDAxMDQwNTE2MzMxN1qBDzIwMjEwNDA1MTcwMzE3WjALBgNVHQ8EBAMC +AQYwHwYDVR0jBBgwFoAUbGQBx/2FbazI2p5QCIUItTxWqFAwHQYDVR0OBBYEFGxkAcf9hW2syNqe +UAiFCLU8VqhQMAwGA1UdEwQFMAMBAf8wHQYJKoZIhvZ9B0EABBAwDhsIVjUuMDo0LjADAgSQMA0G +CSqGSIb3DQEBBQUAA4IBAQBOQ8zR3R0QGwZ/t6T609lN+yOfI1Rb5osvBCiLtSdtiaHsmGnc540m +gwV5dOy0uaOXwTUA/RXaOYE6lTGQ3pfphqiZdwzlWqCE/xIWrG64jcN7ksKsLtB9KOy282A4aW8+ +2ARVPp7MVdK6/rtHBNcK2RYKNCn1WBPVT8+PVkuzHu7TmHnaCB4Mb7j4Fifvwm899qNLPg7kbWzb +O0ESm70NRyN/PErQr8Cv9u8btRXE64PECV90i9kR+8JWsTz4cMo0jUNAE4z9mQNUecYu6oah9jrU +Cbz0vGbMPVjQV0kK7iXiQe4T+Zs4NNEA9X7nlB38aQNiuJkFBT1reBK9sG9l +-----END CERTIFICATE----- + +UTN DATACorp SGC Root CA +======================== +-----BEGIN CERTIFICATE----- +MIIEXjCCA0agAwIBAgIQRL4Mi1AAIbQR0ypoBqmtaTANBgkqhkiG9w0BAQUFADCBkzELMAkGA1UE +BhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2UgQ2l0eTEeMBwGA1UEChMVVGhl +IFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExhodHRwOi8vd3d3LnVzZXJ0cnVzdC5jb20xGzAZ +BgNVBAMTElVUTiAtIERBVEFDb3JwIFNHQzAeFw05OTA2MjQxODU3MjFaFw0xOTA2MjQxOTA2MzBa +MIGTMQswCQYDVQQGEwJVUzELMAkGA1UECBMCVVQxFzAVBgNVBAcTDlNhbHQgTGFrZSBDaXR5MR4w +HAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxITAfBgNVBAsTGGh0dHA6Ly93d3cudXNlcnRy +dXN0LmNvbTEbMBkGA1UEAxMSVVROIC0gREFUQUNvcnAgU0dDMIIBIjANBgkqhkiG9w0BAQEFAAOC +AQ8AMIIBCgKCAQEA3+5YEKIrblXEjr8uRgnn4AgPLit6E5Qbvfa2gI5lBZMAHryv4g+OGQ0SR+ys +raP6LnD43m77VkIVni5c7yPeIbkFdicZD0/Ww5y0vpQZY/KmEQrrU0icvvIpOxboGqBMpsn0GFlo +wHDyUwDAXlCCpVZvNvlK4ESGoE1O1kduSUrLZ9emxAW5jh70/P/N5zbgnAVssjMiFdC04MwXwLLA +9P4yPykqlXvY8qdOD1R8oQ2AswkDwf9c3V6aPryuvEeKaq5xyh+xKrhfQgUL7EYw0XILyulWbfXv +33i+Ybqypa4ETLyorGkVl73v67SMvzX41MPRKA5cOp9wGDMgd8SirwIDAQABo4GrMIGoMAsGA1Ud +DwQEAwIBxjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRTMtGzz3/64PGgXYVOktKeRR20TzA9 +BgNVHR8ENjA0MDKgMKAuhixodHRwOi8vY3JsLnVzZXJ0cnVzdC5jb20vVVROLURBVEFDb3JwU0dD +LmNybDAqBgNVHSUEIzAhBggrBgEFBQcDAQYKKwYBBAGCNwoDAwYJYIZIAYb4QgQBMA0GCSqGSIb3 +DQEBBQUAA4IBAQAnNZcAiosovcYzMB4p/OL31ZjUQLtgyr+rFywJNn9Q+kHcrpY6CiM+iVnJowft +Gzet/Hy+UUla3joKVAgWRcKZsYfNjGjgaQPpxE6YsjuMFrMOoAyYUJuTqXAJyCyjj98C5OBxOvG0 +I3KgqgHf35g+FFCgMSa9KOlaMCZ1+XtgHI3zzVAmbQQnmt/VDUVHKWss5nbZqSl9Mt3JNjy9rjXx +EZ4du5A/EkdOjtd+D2JzHVImOBwYSf0wdJrE5SIv2MCN7ZF6TACPcn9d2t0bi0Vr591pl6jFVkwP +DPafepE39peC4N1xaf92P2BNPM/3mfnGV/TJVTl4uix5yaaIK/QI +-----END CERTIFICATE----- + +UTN USERFirst Hardware Root CA +============================== +-----BEGIN CERTIFICATE----- +MIIEdDCCA1ygAwIBAgIQRL4Mi1AAJLQR0zYq/mUK/TANBgkqhkiG9w0BAQUFADCBlzELMAkGA1UE +BhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2UgQ2l0eTEeMBwGA1UEChMVVGhl +IFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExhodHRwOi8vd3d3LnVzZXJ0cnVzdC5jb20xHzAd +BgNVBAMTFlVUTi1VU0VSRmlyc3QtSGFyZHdhcmUwHhcNOTkwNzA5MTgxMDQyWhcNMTkwNzA5MTgx +OTIyWjCBlzELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2UgQ2l0 +eTEeMBwGA1UEChMVVGhlIFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExhodHRwOi8vd3d3LnVz +ZXJ0cnVzdC5jb20xHzAdBgNVBAMTFlVUTi1VU0VSRmlyc3QtSGFyZHdhcmUwggEiMA0GCSqGSIb3 +DQEBAQUAA4IBDwAwggEKAoIBAQCx98M4P7Sof885glFn0G2f0v9Y8+efK+wNiVSZuTiZFvfgIXlI +wrthdBKWHTxqctU8EGc6Oe0rE81m65UJM6Rsl7HoxuzBdXmcRl6Nq9Bq/bkqVRcQVLMZ8Jr28bFd +tqdt++BxF2uiiPsA3/4aMXcMmgF6sTLjKwEHOG7DpV4jvEWbe1DByTCP2+UretNb+zNAHqDVmBe8 +i4fDidNdoI6yqqr2jmmIBsX6iSHzCJ1pLgkzmykNRg+MzEk0sGlRvfkGzWitZky8PqxhvQqIDsjf +Pe58BEydCl5rkdbux+0ojatNh4lz0G6k0B4WixThdkQDf2Os5M1JnMWS9KsyoUhbAgMBAAGjgbkw +gbYwCwYDVR0PBAQDAgHGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFKFyXyYbKJhDlV0HN9WF +lp1L0sNFMEQGA1UdHwQ9MDswOaA3oDWGM2h0dHA6Ly9jcmwudXNlcnRydXN0LmNvbS9VVE4tVVNF +UkZpcnN0LUhhcmR3YXJlLmNybDAxBgNVHSUEKjAoBggrBgEFBQcDAQYIKwYBBQUHAwUGCCsGAQUF +BwMGBggrBgEFBQcDBzANBgkqhkiG9w0BAQUFAAOCAQEARxkP3nTGmZev/K0oXnWO6y1n7k57K9cM +//bey1WiCuFMVGWTYGufEpytXoMs61quwOQt9ABjHbjAbPLPSbtNk28GpgoiskliCE7/yMgUsogW +XecB5BKV5UU0s4tpvc+0hY91UZ59Ojg6FEgSxvunOxqNDYJAB+gECJChicsZUN/KHAG8HQQZexB2 +lzvukJDKxA4fFm517zP4029bHpbj4HR3dHuKom4t3XbWOTCC8KucUvIqx69JXn7HaOWCgchqJ/kn +iCrVWFCVH/A7HFe7fRQ5YiuayZSSKqMiDP+JJn1fIytH1xUdqWqeUQ0qUZ6B+dQ7XnASfxAynB67 +nfhmqA== +-----END CERTIFICATE----- + +Camerfirma Chambers of Commerce Root +==================================== +-----BEGIN CERTIFICATE----- +MIIEvTCCA6WgAwIBAgIBADANBgkqhkiG9w0BAQUFADB/MQswCQYDVQQGEwJFVTEnMCUGA1UEChMe +QUMgQ2FtZXJmaXJtYSBTQSBDSUYgQTgyNzQzMjg3MSMwIQYDVQQLExpodHRwOi8vd3d3LmNoYW1i +ZXJzaWduLm9yZzEiMCAGA1UEAxMZQ2hhbWJlcnMgb2YgQ29tbWVyY2UgUm9vdDAeFw0wMzA5MzAx +NjEzNDNaFw0zNzA5MzAxNjEzNDRaMH8xCzAJBgNVBAYTAkVVMScwJQYDVQQKEx5BQyBDYW1lcmZp +cm1hIFNBIENJRiBBODI3NDMyODcxIzAhBgNVBAsTGmh0dHA6Ly93d3cuY2hhbWJlcnNpZ24ub3Jn +MSIwIAYDVQQDExlDaGFtYmVycyBvZiBDb21tZXJjZSBSb290MIIBIDANBgkqhkiG9w0BAQEFAAOC +AQ0AMIIBCAKCAQEAtzZV5aVdGDDg2olUkfzIx1L4L1DZ77F1c2VHfRtbunXF/KGIJPov7coISjlU +xFF6tdpg6jg8gbLL8bvZkSM/SAFwdakFKq0fcfPJVD0dBmpAPrMMhe5cG3nCYsS4No41XQEMIwRH +NaqbYE6gZj3LJgqcQKH0XZi/caulAGgq7YN6D6IUtdQis4CwPAxaUWktWBiP7Zme8a7ileb2R6jW +DA+wWFjbw2Y3npuRVDM30pQcakjJyfKl2qUMI/cjDpwyVV5xnIQFUZot/eZOKjRa3spAN2cMVCFV +d9oKDMyXroDclDZK9D7ONhMeU+SsTjoF7Nuucpw4i9A5O4kKPnf+dQIBA6OCAUQwggFAMBIGA1Ud +EwEB/wQIMAYBAf8CAQwwPAYDVR0fBDUwMzAxoC+gLYYraHR0cDovL2NybC5jaGFtYmVyc2lnbi5v +cmcvY2hhbWJlcnNyb290LmNybDAdBgNVHQ4EFgQU45T1sU3p26EpW1eLTXYGduHRooowDgYDVR0P +AQH/BAQDAgEGMBEGCWCGSAGG+EIBAQQEAwIABzAnBgNVHREEIDAegRxjaGFtYmVyc3Jvb3RAY2hh +bWJlcnNpZ24ub3JnMCcGA1UdEgQgMB6BHGNoYW1iZXJzcm9vdEBjaGFtYmVyc2lnbi5vcmcwWAYD +VR0gBFEwTzBNBgsrBgEEAYGHLgoDATA+MDwGCCsGAQUFBwIBFjBodHRwOi8vY3BzLmNoYW1iZXJz +aWduLm9yZy9jcHMvY2hhbWJlcnNyb290Lmh0bWwwDQYJKoZIhvcNAQEFBQADggEBAAxBl8IahsAi +fJ/7kPMa0QOx7xP5IV8EnNrJpY0nbJaHkb5BkAFyk+cefV/2icZdp0AJPaxJRUXcLo0waLIJuvvD +L8y6C98/d3tGfToSJI6WjzwFCm/SlCgdbQzALogi1djPHRPH8EjX1wWnz8dHnjs8NMiAT9QUu/wN +UPf6s+xCX6ndbcj0dc97wXImsQEcXCz9ek60AcUFV7nnPKoF2YjpB0ZBzu9Bga5Y34OirsrXdx/n +ADydb47kMgkdTXg0eDQ8lJsm7U9xxhl6vSAiSFr+S30Dt+dYvsYyTnQeaN2oaFuzPu5ifdmA6Ap1 +erfutGWaIZDgqtCYvDi1czyL+Nw= +-----END CERTIFICATE----- + +Camerfirma Global Chambersign Root +================================== +-----BEGIN CERTIFICATE----- +MIIExTCCA62gAwIBAgIBADANBgkqhkiG9w0BAQUFADB9MQswCQYDVQQGEwJFVTEnMCUGA1UEChMe +QUMgQ2FtZXJmaXJtYSBTQSBDSUYgQTgyNzQzMjg3MSMwIQYDVQQLExpodHRwOi8vd3d3LmNoYW1i +ZXJzaWduLm9yZzEgMB4GA1UEAxMXR2xvYmFsIENoYW1iZXJzaWduIFJvb3QwHhcNMDMwOTMwMTYx +NDE4WhcNMzcwOTMwMTYxNDE4WjB9MQswCQYDVQQGEwJFVTEnMCUGA1UEChMeQUMgQ2FtZXJmaXJt +YSBTQSBDSUYgQTgyNzQzMjg3MSMwIQYDVQQLExpodHRwOi8vd3d3LmNoYW1iZXJzaWduLm9yZzEg +MB4GA1UEAxMXR2xvYmFsIENoYW1iZXJzaWduIFJvb3QwggEgMA0GCSqGSIb3DQEBAQUAA4IBDQAw +ggEIAoIBAQCicKLQn0KuWxfH2H3PFIP8T8mhtxOviteePgQKkotgVvq0Mi+ITaFgCPS3CU6gSS9J +1tPfnZdan5QEcOw/Wdm3zGaLmFIoCQLfxS+EjXqXd7/sQJ0lcqu1PzKY+7e3/HKE5TWH+VX6ox8O +by4o3Wmg2UIQxvi1RMLQQ3/bvOSiPGpVeAp3qdjqGTK3L/5cPxvusZjsyq16aUXjlg9V9ubtdepl +6DJWk0aJqCWKZQbua795B9Dxt6/tLE2Su8CoX6dnfQTyFQhwrJLWfQTSM/tMtgsL+xrJxI0DqX5c +8lCrEqWhz0hQpe/SyBoT+rB/sYIcd2oPX9wLlY/vQ37mRQklAgEDo4IBUDCCAUwwEgYDVR0TAQH/ +BAgwBgEB/wIBDDA/BgNVHR8EODA2MDSgMqAwhi5odHRwOi8vY3JsLmNoYW1iZXJzaWduLm9yZy9j +aGFtYmVyc2lnbnJvb3QuY3JsMB0GA1UdDgQWBBRDnDafsJ4wTcbOX60Qq+UDpfqpFDAOBgNVHQ8B +Af8EBAMCAQYwEQYJYIZIAYb4QgEBBAQDAgAHMCoGA1UdEQQjMCGBH2NoYW1iZXJzaWducm9vdEBj +aGFtYmVyc2lnbi5vcmcwKgYDVR0SBCMwIYEfY2hhbWJlcnNpZ25yb290QGNoYW1iZXJzaWduLm9y +ZzBbBgNVHSAEVDBSMFAGCysGAQQBgYcuCgEBMEEwPwYIKwYBBQUHAgEWM2h0dHA6Ly9jcHMuY2hh +bWJlcnNpZ24ub3JnL2Nwcy9jaGFtYmVyc2lnbnJvb3QuaHRtbDANBgkqhkiG9w0BAQUFAAOCAQEA +PDtwkfkEVCeR4e3t/mh/YV3lQWVPMvEYBZRqHN4fcNs+ezICNLUMbKGKfKX0j//U2K0X1S0E0T9Y +gOKBWYi+wONGkyT+kL0mojAt6JcmVzWJdJYY9hXiryQZVgICsroPFOrGimbBhkVVi76SvpykBMdJ +PJ7oKXqJ1/6v/2j1pReQvayZzKWGVwlnRtvWFsJG8eSpUPWP0ZIV018+xgBJOm5YstHRJw0lyDL4 +IBHNfTIzSJRUTN3cecQwn+uOuFW114hcxWokPbLTBQNRxgfvzBRydD1ucs4YKIxKoHflCStFREes +t2d/AYoFWpO+ocH/+OcOZ6RHSXZddZAa9SaP8A== +-----END CERTIFICATE----- + +NetLock Notary (Class A) Root +============================= +-----BEGIN CERTIFICATE----- +MIIGfTCCBWWgAwIBAgICAQMwDQYJKoZIhvcNAQEEBQAwga8xCzAJBgNVBAYTAkhVMRAwDgYDVQQI +EwdIdW5nYXJ5MREwDwYDVQQHEwhCdWRhcGVzdDEnMCUGA1UEChMeTmV0TG9jayBIYWxvemF0Yml6 +dG9uc2FnaSBLZnQuMRowGAYDVQQLExFUYW51c2l0dmFueWtpYWRvazE2MDQGA1UEAxMtTmV0TG9j +ayBLb3pqZWd5em9pIChDbGFzcyBBKSBUYW51c2l0dmFueWtpYWRvMB4XDTk5MDIyNDIzMTQ0N1oX +DTE5MDIxOTIzMTQ0N1owga8xCzAJBgNVBAYTAkhVMRAwDgYDVQQIEwdIdW5nYXJ5MREwDwYDVQQH +EwhCdWRhcGVzdDEnMCUGA1UEChMeTmV0TG9jayBIYWxvemF0Yml6dG9uc2FnaSBLZnQuMRowGAYD +VQQLExFUYW51c2l0dmFueWtpYWRvazE2MDQGA1UEAxMtTmV0TG9jayBLb3pqZWd5em9pIChDbGFz +cyBBKSBUYW51c2l0dmFueWtpYWRvMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvHSM +D7tM9DceqQWC2ObhbHDqeLVu0ThEDaiDzl3S1tWBxdRL51uUcCbbO51qTGL3cfNk1mE7PetzozfZ +z+qMkjvN9wfcZnSX9EUi3fRc4L9t875lM+QVOr/bmJBVOMTtplVjC7B4BPTjbsE/jvxReB+SnoPC +/tmwqcm8WgD/qaiYdPv2LD4VOQ22BFWoDpggQrOxJa1+mm9dU7GrDPzr4PN6s6iz/0b2Y6LYOph7 +tqyF/7AlT3Rj5xMHpQqPBffAZG9+pyeAlt7ULoZgx2srXnN7F+eRP2QM2EsiNCubMvJIH5+hCoR6 +4sKtlz2O1cH5VqNQ6ca0+pii7pXmKgOM3wIDAQABo4ICnzCCApswDgYDVR0PAQH/BAQDAgAGMBIG +A1UdEwEB/wQIMAYBAf8CAQQwEQYJYIZIAYb4QgEBBAQDAgAHMIICYAYJYIZIAYb4QgENBIICURaC +Ak1GSUdZRUxFTSEgRXplbiB0YW51c2l0dmFueSBhIE5ldExvY2sgS2Z0LiBBbHRhbGFub3MgU3pv +bGdhbHRhdGFzaSBGZWx0ZXRlbGVpYmVuIGxlaXJ0IGVsamFyYXNvayBhbGFwamFuIGtlc3p1bHQu +IEEgaGl0ZWxlc2l0ZXMgZm9seWFtYXRhdCBhIE5ldExvY2sgS2Z0LiB0ZXJtZWtmZWxlbG9zc2Vn +LWJpenRvc2l0YXNhIHZlZGkuIEEgZGlnaXRhbGlzIGFsYWlyYXMgZWxmb2dhZGFzYW5hayBmZWx0 +ZXRlbGUgYXogZWxvaXJ0IGVsbGVub3J6ZXNpIGVsamFyYXMgbWVndGV0ZWxlLiBBeiBlbGphcmFz +IGxlaXJhc2EgbWVndGFsYWxoYXRvIGEgTmV0TG9jayBLZnQuIEludGVybmV0IGhvbmxhcGphbiBh +IGh0dHBzOi8vd3d3Lm5ldGxvY2submV0L2RvY3MgY2ltZW4gdmFneSBrZXJoZXRvIGF6IGVsbGVu +b3J6ZXNAbmV0bG9jay5uZXQgZS1tYWlsIGNpbWVuLiBJTVBPUlRBTlQhIFRoZSBpc3N1YW5jZSBh +bmQgdGhlIHVzZSBvZiB0aGlzIGNlcnRpZmljYXRlIGlzIHN1YmplY3QgdG8gdGhlIE5ldExvY2sg +Q1BTIGF2YWlsYWJsZSBhdCBodHRwczovL3d3dy5uZXRsb2NrLm5ldC9kb2NzIG9yIGJ5IGUtbWFp +bCBhdCBjcHNAbmV0bG9jay5uZXQuMA0GCSqGSIb3DQEBBAUAA4IBAQBIJEb3ulZv+sgoA0BO5TE5 +ayZrU3/b39/zcT0mwBQOxmd7I6gMc90Bu8bKbjc5VdXHjFYgDigKDtIqpLBJUsY4B/6+CgmM0ZjP +ytoUMaFP0jn8DxEsQ8Pdq5PHVT5HfBgaANzze9jyf1JsIPQLX2lS9O74silg6+NJMSEN1rUQQeJB +CWziGppWS3cC9qCbmieH6FUpccKQn0V4GuEVZD3QDtigdp+uxdAu6tYPVuxkf1qbFFgBJ34TUMdr +KuZoPL9coAob4Q566eKAw+np9v1sEZ7Q5SgnK1QyQhSCdeZK8CtmdWOMovsEPoMOmzbwGOQmIMOM +8CgHrTwXZoi1/baI +-----END CERTIFICATE----- + +NetLock Business (Class B) Root +=============================== +-----BEGIN CERTIFICATE----- +MIIFSzCCBLSgAwIBAgIBaTANBgkqhkiG9w0BAQQFADCBmTELMAkGA1UEBhMCSFUxETAPBgNVBAcT +CEJ1ZGFwZXN0MScwJQYDVQQKEx5OZXRMb2NrIEhhbG96YXRiaXp0b25zYWdpIEtmdC4xGjAYBgNV +BAsTEVRhbnVzaXR2YW55a2lhZG9rMTIwMAYDVQQDEylOZXRMb2NrIFV6bGV0aSAoQ2xhc3MgQikg +VGFudXNpdHZhbnlraWFkbzAeFw05OTAyMjUxNDEwMjJaFw0xOTAyMjAxNDEwMjJaMIGZMQswCQYD +VQQGEwJIVTERMA8GA1UEBxMIQnVkYXBlc3QxJzAlBgNVBAoTHk5ldExvY2sgSGFsb3phdGJpenRv +bnNhZ2kgS2Z0LjEaMBgGA1UECxMRVGFudXNpdHZhbnlraWFkb2sxMjAwBgNVBAMTKU5ldExvY2sg +VXpsZXRpIChDbGFzcyBCKSBUYW51c2l0dmFueWtpYWRvMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCB +iQKBgQCx6gTsIKAjwo84YM/HRrPVG/77uZmeBNwcf4xKgZjupNTKihe5In+DCnVMm8Bp2GQ5o+2S +o/1bXHQawEfKOml2mrriRBf8TKPV/riXiK+IA4kfpPIEPsgHC+b5sy96YhQJRhTKZPWLgLViqNhr +1nGTLbO/CVRY7QbrqHvcQ7GhaQIDAQABo4ICnzCCApswEgYDVR0TAQH/BAgwBgEB/wIBBDAOBgNV +HQ8BAf8EBAMCAAYwEQYJYIZIAYb4QgEBBAQDAgAHMIICYAYJYIZIAYb4QgENBIICURaCAk1GSUdZ +RUxFTSEgRXplbiB0YW51c2l0dmFueSBhIE5ldExvY2sgS2Z0LiBBbHRhbGFub3MgU3pvbGdhbHRh +dGFzaSBGZWx0ZXRlbGVpYmVuIGxlaXJ0IGVsamFyYXNvayBhbGFwamFuIGtlc3p1bHQuIEEgaGl0 +ZWxlc2l0ZXMgZm9seWFtYXRhdCBhIE5ldExvY2sgS2Z0LiB0ZXJtZWtmZWxlbG9zc2VnLWJpenRv +c2l0YXNhIHZlZGkuIEEgZGlnaXRhbGlzIGFsYWlyYXMgZWxmb2dhZGFzYW5hayBmZWx0ZXRlbGUg +YXogZWxvaXJ0IGVsbGVub3J6ZXNpIGVsamFyYXMgbWVndGV0ZWxlLiBBeiBlbGphcmFzIGxlaXJh +c2EgbWVndGFsYWxoYXRvIGEgTmV0TG9jayBLZnQuIEludGVybmV0IGhvbmxhcGphbiBhIGh0dHBz +Oi8vd3d3Lm5ldGxvY2submV0L2RvY3MgY2ltZW4gdmFneSBrZXJoZXRvIGF6IGVsbGVub3J6ZXNA +bmV0bG9jay5uZXQgZS1tYWlsIGNpbWVuLiBJTVBPUlRBTlQhIFRoZSBpc3N1YW5jZSBhbmQgdGhl +IHVzZSBvZiB0aGlzIGNlcnRpZmljYXRlIGlzIHN1YmplY3QgdG8gdGhlIE5ldExvY2sgQ1BTIGF2 +YWlsYWJsZSBhdCBodHRwczovL3d3dy5uZXRsb2NrLm5ldC9kb2NzIG9yIGJ5IGUtbWFpbCBhdCBj +cHNAbmV0bG9jay5uZXQuMA0GCSqGSIb3DQEBBAUAA4GBAATbrowXr/gOkDFOzT4JwG06sPgzTEdM +43WIEJessDgVkcYplswhwG08pXTP2IKlOcNl40JwuyKQ433bNXbhoLXan3BukxowOR0w2y7jfLKR +stE3Kfq51hdcR0/jHTjrn9V7lagonhVK0dHQKwCXoOKSNitjrFgBazMpUIaD8QFI +-----END CERTIFICATE----- + +NetLock Express (Class C) Root +============================== +-----BEGIN CERTIFICATE----- +MIIFTzCCBLigAwIBAgIBaDANBgkqhkiG9w0BAQQFADCBmzELMAkGA1UEBhMCSFUxETAPBgNVBAcT +CEJ1ZGFwZXN0MScwJQYDVQQKEx5OZXRMb2NrIEhhbG96YXRiaXp0b25zYWdpIEtmdC4xGjAYBgNV +BAsTEVRhbnVzaXR2YW55a2lhZG9rMTQwMgYDVQQDEytOZXRMb2NrIEV4cHJlc3N6IChDbGFzcyBD +KSBUYW51c2l0dmFueWtpYWRvMB4XDTk5MDIyNTE0MDgxMVoXDTE5MDIyMDE0MDgxMVowgZsxCzAJ +BgNVBAYTAkhVMREwDwYDVQQHEwhCdWRhcGVzdDEnMCUGA1UEChMeTmV0TG9jayBIYWxvemF0Yml6 +dG9uc2FnaSBLZnQuMRowGAYDVQQLExFUYW51c2l0dmFueWtpYWRvazE0MDIGA1UEAxMrTmV0TG9j +ayBFeHByZXNzeiAoQ2xhc3MgQykgVGFudXNpdHZhbnlraWFkbzCBnzANBgkqhkiG9w0BAQEFAAOB +jQAwgYkCgYEA6+ywbGGKIyWvYCDj2Z/8kwvbXY2wobNAOoLO/XXgeDIDhlqGlZHtU/qdQPzm6N3Z +W3oDvV3zOwzDUXmbrVWg6dADEK8KuhRC2VImESLH0iDMgqSaqf64gXadarfSNnU+sYYJ9m5tfk63 +euyucYT2BDMIJTLrdKwWRMbkQJMdf60CAwEAAaOCAp8wggKbMBIGA1UdEwEB/wQIMAYBAf8CAQQw +DgYDVR0PAQH/BAQDAgAGMBEGCWCGSAGG+EIBAQQEAwIABzCCAmAGCWCGSAGG+EIBDQSCAlEWggJN +RklHWUVMRU0hIEV6ZW4gdGFudXNpdHZhbnkgYSBOZXRMb2NrIEtmdC4gQWx0YWxhbm9zIFN6b2xn +YWx0YXRhc2kgRmVsdGV0ZWxlaWJlbiBsZWlydCBlbGphcmFzb2sgYWxhcGphbiBrZXN6dWx0LiBB +IGhpdGVsZXNpdGVzIGZvbHlhbWF0YXQgYSBOZXRMb2NrIEtmdC4gdGVybWVrZmVsZWxvc3NlZy1i +aXp0b3NpdGFzYSB2ZWRpLiBBIGRpZ2l0YWxpcyBhbGFpcmFzIGVsZm9nYWRhc2FuYWsgZmVsdGV0 +ZWxlIGF6IGVsb2lydCBlbGxlbm9yemVzaSBlbGphcmFzIG1lZ3RldGVsZS4gQXogZWxqYXJhcyBs +ZWlyYXNhIG1lZ3RhbGFsaGF0byBhIE5ldExvY2sgS2Z0LiBJbnRlcm5ldCBob25sYXBqYW4gYSBo +dHRwczovL3d3dy5uZXRsb2NrLm5ldC9kb2NzIGNpbWVuIHZhZ3kga2VyaGV0byBheiBlbGxlbm9y +emVzQG5ldGxvY2submV0IGUtbWFpbCBjaW1lbi4gSU1QT1JUQU5UISBUaGUgaXNzdWFuY2UgYW5k +IHRoZSB1c2Ugb2YgdGhpcyBjZXJ0aWZpY2F0ZSBpcyBzdWJqZWN0IHRvIHRoZSBOZXRMb2NrIENQ +UyBhdmFpbGFibGUgYXQgaHR0cHM6Ly93d3cubmV0bG9jay5uZXQvZG9jcyBvciBieSBlLW1haWwg +YXQgY3BzQG5ldGxvY2submV0LjANBgkqhkiG9w0BAQQFAAOBgQAQrX/XDDKACtiG8XmYta3UzbM2 +xJZIwVzNmtkFLp++UOv0JhQQLdRmF/iewSf98e3ke0ugbLWrmldwpu2gpO0u9f38vf5NNwgMvOOW +gyL1SRt/Syu0VMGAfJlOHdCM7tCs5ZL6dVb+ZKATj7i4Fp1hBWeAyNDYpQcCNJgEjTME1A== +-----END CERTIFICATE----- + +XRamp Global CA Root +==================== +-----BEGIN CERTIFICATE----- +MIIEMDCCAxigAwIBAgIQUJRs7Bjq1ZxN1ZfvdY+grTANBgkqhkiG9w0BAQUFADCBgjELMAkGA1UE +BhMCVVMxHjAcBgNVBAsTFXd3dy54cmFtcHNlY3VyaXR5LmNvbTEkMCIGA1UEChMbWFJhbXAgU2Vj +dXJpdHkgU2VydmljZXMgSW5jMS0wKwYDVQQDEyRYUmFtcCBHbG9iYWwgQ2VydGlmaWNhdGlvbiBB +dXRob3JpdHkwHhcNMDQxMTAxMTcxNDA0WhcNMzUwMTAxMDUzNzE5WjCBgjELMAkGA1UEBhMCVVMx +HjAcBgNVBAsTFXd3dy54cmFtcHNlY3VyaXR5LmNvbTEkMCIGA1UEChMbWFJhbXAgU2VjdXJpdHkg +U2VydmljZXMgSW5jMS0wKwYDVQQDEyRYUmFtcCBHbG9iYWwgQ2VydGlmaWNhdGlvbiBBdXRob3Jp +dHkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCYJB69FbS638eMpSe2OAtp87ZOqCwu +IR1cRN8hXX4jdP5efrRKt6atH67gBhbim1vZZ3RrXYCPKZ2GG9mcDZhtdhAoWORlsH9KmHmf4MMx +foArtYzAQDsRhtDLooY2YKTVMIJt2W7QDxIEM5dfT2Fa8OT5kavnHTu86M/0ay00fOJIYRyO82FE +zG+gSqmUsE3a56k0enI4qEHMPJQRfevIpoy3hsvKMzvZPTeL+3o+hiznc9cKV6xkmxnr9A8ECIqs +AxcZZPRaJSKNNCyy9mgdEm3Tih4U2sSPpuIjhdV6Db1q4Ons7Be7QhtnqiXtRYMh/MHJfNViPvry +xS3T/dRlAgMBAAGjgZ8wgZwwEwYJKwYBBAGCNxQCBAYeBABDAEEwCwYDVR0PBAQDAgGGMA8GA1Ud +EwEB/wQFMAMBAf8wHQYDVR0OBBYEFMZPoj0GY4QJnM5i5ASsjVy16bYbMDYGA1UdHwQvMC0wK6Ap +oCeGJWh0dHA6Ly9jcmwueHJhbXBzZWN1cml0eS5jb20vWEdDQS5jcmwwEAYJKwYBBAGCNxUBBAMC +AQEwDQYJKoZIhvcNAQEFBQADggEBAJEVOQMBG2f7Shz5CmBbodpNl2L5JFMn14JkTpAuw0kbK5rc +/Kh4ZzXxHfARvbdI4xD2Dd8/0sm2qlWkSLoC295ZLhVbO50WfUfXN+pfTXYSNrsf16GBBEYgoyxt +qZ4Bfj8pzgCT3/3JknOJiWSe5yvkHJEs0rnOfc5vMZnT5r7SHpDwCRR5XCOrTdLaIR9NmXmd4c8n +nxCbHIgNsIpkQTG4DmyQJKSbXHGPurt+HBvbaoAPIbzp26a3QPSyi6mx5O+aGtA9aZnuqCij4Tyz +8LIRnM98QObd50N9otg6tamN8jSZxNQQ4Qb9CYQQO+7ETPTsJ3xCwnR8gooJybQDJbw= +-----END CERTIFICATE----- + +Go Daddy Class 2 CA +=================== +-----BEGIN CERTIFICATE----- +MIIEADCCAuigAwIBAgIBADANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJVUzEhMB8GA1UEChMY +VGhlIEdvIERhZGR5IEdyb3VwLCBJbmMuMTEwLwYDVQQLEyhHbyBEYWRkeSBDbGFzcyAyIENlcnRp +ZmljYXRpb24gQXV0aG9yaXR5MB4XDTA0MDYyOTE3MDYyMFoXDTM0MDYyOTE3MDYyMFowYzELMAkG +A1UEBhMCVVMxITAfBgNVBAoTGFRoZSBHbyBEYWRkeSBHcm91cCwgSW5jLjExMC8GA1UECxMoR28g +RGFkZHkgQ2xhc3MgMiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASAwDQYJKoZIhvcNAQEBBQAD +ggENADCCAQgCggEBAN6d1+pXGEmhW+vXX0iG6r7d/+TvZxz0ZWizV3GgXne77ZtJ6XCAPVYYYwhv +2vLM0D9/AlQiVBDYsoHUwHU9S3/Hd8M+eKsaA7Ugay9qK7HFiH7Eux6wwdhFJ2+qN1j3hybX2C32 +qRe3H3I2TqYXP2WYktsqbl2i/ojgC95/5Y0V4evLOtXiEqITLdiOr18SPaAIBQi2XKVlOARFmR6j +YGB0xUGlcmIbYsUfb18aQr4CUWWoriMYavx4A6lNf4DD+qta/KFApMoZFv6yyO9ecw3ud72a9nmY +vLEHZ6IVDd2gWMZEewo+YihfukEHU1jPEX44dMX4/7VpkI+EdOqXG68CAQOjgcAwgb0wHQYDVR0O +BBYEFNLEsNKR1EwRcbNhyz2h/t2oatTjMIGNBgNVHSMEgYUwgYKAFNLEsNKR1EwRcbNhyz2h/t2o +atTjoWekZTBjMQswCQYDVQQGEwJVUzEhMB8GA1UEChMYVGhlIEdvIERhZGR5IEdyb3VwLCBJbmMu +MTEwLwYDVQQLEyhHbyBEYWRkeSBDbGFzcyAyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5ggEAMAwG +A1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBADJL87LKPpH8EsahB4yOd6AzBhRckB4Y9wim +PQoZ+YeAEW5p5JYXMP80kWNyOO7MHAGjHZQopDH2esRU1/blMVgDoszOYtuURXO1v0XJJLXVggKt +I3lpjbi2Tc7PTMozI+gciKqdi0FuFskg5YmezTvacPd+mSYgFFQlq25zheabIZ0KbIIOqPjCDPoQ +HmyW74cNxA9hi63ugyuV+I6ShHI56yDqg+2DzZduCLzrTia2cyvk0/ZM/iZx4mERdEr/VxqHD3VI +Ls9RaRegAhJhldXRQLIQTO7ErBBDpqWeCtWVYpoNz4iCxTIM5CufReYNnyicsbkqWletNw+vHX/b +vZ8= +-----END CERTIFICATE----- + +Starfield Class 2 CA +==================== +-----BEGIN CERTIFICATE----- +MIIEDzCCAvegAwIBAgIBADANBgkqhkiG9w0BAQUFADBoMQswCQYDVQQGEwJVUzElMCMGA1UEChMc +U3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UECxMpU3RhcmZpZWxkIENsYXNzIDIg +Q2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDQwNjI5MTczOTE2WhcNMzQwNjI5MTczOTE2WjBo +MQswCQYDVQQGEwJVUzElMCMGA1UEChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAG +A1UECxMpU3RhcmZpZWxkIENsYXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggEgMA0GCSqG +SIb3DQEBAQUAA4IBDQAwggEIAoIBAQC3Msj+6XGmBIWtDBFk385N78gDGIc/oav7PKaf8MOh2tTY +bitTkPskpD6E8J7oX+zlJ0T1KKY/e97gKvDIr1MvnsoFAZMej2YcOadN+lq2cwQlZut3f+dZxkqZ +JRRU6ybH838Z1TBwj6+wRir/resp7defqgSHo9T5iaU0X9tDkYI22WY8sbi5gv2cOj4QyDvvBmVm +epsZGD3/cVE8MC5fvj13c7JdBmzDI1aaK4UmkhynArPkPw2vCHmCuDY96pzTNbO8acr1zJ3o/WSN +F4Azbl5KXZnJHoe0nRrA1W4TNSNe35tfPe/W93bC6j67eA0cQmdrBNj41tpvi/JEoAGrAgEDo4HF +MIHCMB0GA1UdDgQWBBS/X7fRzt0fhvRbVazc1xDCDqmI5zCBkgYDVR0jBIGKMIGHgBS/X7fRzt0f +hvRbVazc1xDCDqmI56FspGowaDELMAkGA1UEBhMCVVMxJTAjBgNVBAoTHFN0YXJmaWVsZCBUZWNo +bm9sb2dpZXMsIEluYy4xMjAwBgNVBAsTKVN0YXJmaWVsZCBDbGFzcyAyIENlcnRpZmljYXRpb24g +QXV0aG9yaXR5ggEAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAAWdP4id0ckaVaGs +afPzWdqbAYcaT1epoXkJKtv3L7IezMdeatiDh6GX70k1PncGQVhiv45YuApnP+yz3SFmH8lU+nLM +PUxA2IGvd56Deruix/U0F47ZEUD0/CwqTRV/p2JdLiXTAAsgGh1o+Re49L2L7ShZ3U0WixeDyLJl +xy16paq8U4Zt3VekyvggQQto8PT7dL5WXXp59fkdheMtlb71cZBDzI0fmgAKhynpVSJYACPq4xJD +KVtHCN2MQWplBqjlIapBtJUhlbl90TSrE9atvNziPTnNvT51cKEYWQPJIrSPnNVeKtelttQKbfi3 +QBFGmh95DmK/D5fs4C8fF5Q= +-----END CERTIFICATE----- + +StartCom Certification Authority +================================ +-----BEGIN CERTIFICATE----- +MIIHyTCCBbGgAwIBAgIBATANBgkqhkiG9w0BAQUFADB9MQswCQYDVQQGEwJJTDEWMBQGA1UEChMN +U3RhcnRDb20gTHRkLjErMCkGA1UECxMiU2VjdXJlIERpZ2l0YWwgQ2VydGlmaWNhdGUgU2lnbmlu +ZzEpMCcGA1UEAxMgU3RhcnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDYwOTE3MTk0 +NjM2WhcNMzYwOTE3MTk0NjM2WjB9MQswCQYDVQQGEwJJTDEWMBQGA1UEChMNU3RhcnRDb20gTHRk +LjErMCkGA1UECxMiU2VjdXJlIERpZ2l0YWwgQ2VydGlmaWNhdGUgU2lnbmluZzEpMCcGA1UEAxMg +U3RhcnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAw +ggIKAoICAQDBiNsJvGxGfHiflXu1M5DycmLWwTYgIiRezul38kMKogZkpMyONvg45iPwbm2xPN1y +o4UcodM9tDMr0y+v/uqwQVlntsQGfQqedIXWeUyAN3rfOQVSWff0G0ZDpNKFhdLDcfN1YjS6LIp/ +Ho/u7TTQEceWzVI9ujPW3U3eCztKS5/CJi/6tRYccjV3yjxd5srhJosaNnZcAdt0FCX+7bWgiA/d +eMotHweXMAEtcnn6RtYTKqi5pquDSR3l8u/d5AGOGAqPY1MWhWKpDhk6zLVmpsJrdAfkK+F2PrRt +2PZE4XNiHzvEvqBTViVsUQn3qqvKv3b9bZvzndu/PWa8DFaqr5hIlTpL36dYUNk4dalb6kMMAv+Z +6+hsTXBbKWWc3apdzK8BMewM69KN6Oqce+Zu9ydmDBpI125C4z/eIT574Q1w+2OqqGwaVLRcJXrJ +osmLFqa7LH4XXgVNWG4SHQHuEhANxjJ/GP/89PrNbpHoNkm+Gkhpi8KWTRoSsmkXwQqQ1vp5Iki/ +untp+HDH+no32NgN0nZPV/+Qt+OR0t3vwmC3Zzrd/qqc8NSLf3Iizsafl7b4r4qgEKjZ+xjGtrVc +UjyJthkqcwEKDwOzEmDyei+B26Nu/yYwl/WL3YlXtq09s68rxbd2AvCl1iuahhQqcvbjM4xdCUsT +37uMdBNSSwIDAQABo4ICUjCCAk4wDAYDVR0TBAUwAwEB/zALBgNVHQ8EBAMCAa4wHQYDVR0OBBYE +FE4L7xqkQFulF2mHMMo0aEPQQa7yMGQGA1UdHwRdMFswLKAqoCiGJmh0dHA6Ly9jZXJ0LnN0YXJ0 +Y29tLm9yZy9zZnNjYS1jcmwuY3JsMCugKaAnhiVodHRwOi8vY3JsLnN0YXJ0Y29tLm9yZy9zZnNj +YS1jcmwuY3JsMIIBXQYDVR0gBIIBVDCCAVAwggFMBgsrBgEEAYG1NwEBATCCATswLwYIKwYBBQUH +AgEWI2h0dHA6Ly9jZXJ0LnN0YXJ0Y29tLm9yZy9wb2xpY3kucGRmMDUGCCsGAQUFBwIBFilodHRw +Oi8vY2VydC5zdGFydGNvbS5vcmcvaW50ZXJtZWRpYXRlLnBkZjCB0AYIKwYBBQUHAgIwgcMwJxYg +U3RhcnQgQ29tbWVyY2lhbCAoU3RhcnRDb20pIEx0ZC4wAwIBARqBl0xpbWl0ZWQgTGlhYmlsaXR5 +LCByZWFkIHRoZSBzZWN0aW9uICpMZWdhbCBMaW1pdGF0aW9ucyogb2YgdGhlIFN0YXJ0Q29tIENl +cnRpZmljYXRpb24gQXV0aG9yaXR5IFBvbGljeSBhdmFpbGFibGUgYXQgaHR0cDovL2NlcnQuc3Rh +cnRjb20ub3JnL3BvbGljeS5wZGYwEQYJYIZIAYb4QgEBBAQDAgAHMDgGCWCGSAGG+EIBDQQrFilT +dGFydENvbSBGcmVlIFNTTCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTANBgkqhkiG9w0BAQUFAAOC +AgEAFmyZ9GYMNPXQhV59CuzaEE44HF7fpiUFS5Eyweg78T3dRAlbB0mKKctmArexmvclmAk8jhvh +3TaHK0u7aNM5Zj2gJsfyOZEdUauCe37Vzlrk4gNXcGmXCPleWKYK34wGmkUWFjgKXlf2Ysd6AgXm +vB618p70qSmD+LIU424oh0TDkBreOKk8rENNZEXO3SipXPJzewT4F+irsfMuXGRuczE6Eri8sxHk +fY+BUZo7jYn0TZNmezwD7dOaHZrzZVD1oNB1ny+v8OqCQ5j4aZyJecRDjkZy42Q2Eq/3JR44iZB3 +fsNrarnDy0RLrHiQi+fHLB5LEUTINFInzQpdn4XBidUaePKVEFMy3YCEZnXZtWgo+2EuvoSoOMCZ +EoalHmdkrQYuL6lwhceWD3yJZfWOQ1QOq92lgDmUYMA0yZZwLKMS9R9Ie70cfmu3nZD0Ijuu+Pwq +yvqCUqDvr0tVk+vBtfAii6w0TiYiBKGHLHVKt+V9E9e4DGTANtLJL4YSjCMJwRuCO3NJo2pXh5Tl +1njFmUNj403gdy3hZZlyaQQaRwnmDwFWJPsfvw55qVguucQJAX6Vum0ABj6y6koQOdjQK/W/7HW/ +lwLFCRsI3FU34oH7N4RDYiDK51ZLZer+bMEkkyShNOsF/5oirpt9P/FlUQqmMGqz9IgcgA38coro +g14= +-----END CERTIFICATE----- + +Taiwan GRCA +=========== +-----BEGIN CERTIFICATE----- +MIIFcjCCA1qgAwIBAgIQH51ZWtcvwgZEpYAIaeNe9jANBgkqhkiG9w0BAQUFADA/MQswCQYDVQQG +EwJUVzEwMC4GA1UECgwnR292ZXJubWVudCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4X +DTAyMTIwNTEzMjMzM1oXDTMyMTIwNTEzMjMzM1owPzELMAkGA1UEBhMCVFcxMDAuBgNVBAoMJ0dv +dmVybm1lbnQgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCAiIwDQYJKoZIhvcNAQEBBQAD +ggIPADCCAgoCggIBAJoluOzMonWoe/fOW1mKydGGEghU7Jzy50b2iPN86aXfTEc2pBsBHH8eV4qN +w8XRIePaJD9IK/ufLqGU5ywck9G/GwGHU5nOp/UKIXZ3/6m3xnOUT0b3EEk3+qhZSV1qgQdW8or5 +BtD3cCJNtLdBuTK4sfCxw5w/cP1T3YGq2GN49thTbqGsaoQkclSGxtKyyhwOeYHWtXBiCAEuTk8O +1RGvqa/lmr/czIdtJuTJV6L7lvnM4T9TjGxMfptTCAtsF/tnyMKtsc2AtJfcdgEWFelq16TheEfO +htX7MfP6Mb40qij7cEwdScevLJ1tZqa2jWR+tSBqnTuBto9AAGdLiYa4zGX+FVPpBMHWXx1E1wov +J5pGfaENda1UhhXcSTvxls4Pm6Dso3pdvtUqdULle96ltqqvKKyskKw4t9VoNSZ63Pc78/1Fm9G7 +Q3hub/FCVGqY8A2tl+lSXunVanLeavcbYBT0peS2cWeqH+riTcFCQP5nRhc4L0c/cZyu5SHKYS1t +B6iEfC3uUSXxY5Ce/eFXiGvviiNtsea9P63RPZYLhY3Naye7twWb7LuRqQoHEgKXTiCQ8P8NHuJB +O9NAOueNXdpm5AKwB1KYXA6OM5zCppX7VRluTI6uSw+9wThNXo+EHWbNxWCWtFJaBYmOlXqYwZE8 +lSOyDvR5tMl8wUohAgMBAAGjajBoMB0GA1UdDgQWBBTMzO/MKWCkO7GStjz6MmKPrCUVOzAMBgNV +HRMEBTADAQH/MDkGBGcqBwAEMTAvMC0CAQAwCQYFKw4DAhoFADAHBgVnKgMAAAQUA5vwIhP/lSg2 +09yewDL7MTqKUWUwDQYJKoZIhvcNAQEFBQADggIBAECASvomyc5eMN1PhnR2WPWus4MzeKR6dBcZ +TulStbngCnRiqmjKeKBMmo4sIy7VahIkv9Ro04rQ2JyftB8M3jh+Vzj8jeJPXgyfqzvS/3WXy6Tj +Zwj/5cAWtUgBfen5Cv8b5Wppv3ghqMKnI6mGq3ZW6A4M9hPdKmaKZEk9GhiHkASfQlK3T8v+R0F2 +Ne//AHY2RTKbxkaFXeIksB7jSJaYV0eUVXoPQbFEJPPB/hprv4j9wabak2BegUqZIJxIZhm1AHlU +D7gsL0u8qV1bYH+Mh6XgUmMqvtg7hUAV/h62ZT/FS9p+tXo1KaMuephgIqP0fSdOLeq0dDzpD6Qz +DxARvBMB1uUO07+1EqLhRSPAzAhuYbeJq4PjJB7mXQfnHyA+z2fI56wwbSdLaG5LKlwCCDTb+Hbk +Z6MmnD+iMsJKxYEYMRBWqoTvLQr/uB930r+lWKBi5NdLkXWNiYCYfm3LU05er/ayl4WXudpVBrkk +7tfGOB5jGxI7leFYrPLfhNVfmS8NVVvmONsuP3LpSIXLuykTjx44VbnzssQwmSNOXfJIoRIM3BKQ +CZBUkQM8R+XVyWXgt0t97EfTsws+rZ7QdAAO671RrcDeLMDDav7v3Aun+kbfYNucpllQdSNpc5Oy ++fwC00fmcc4QAu4njIT/rEUNE1yDMuAlpYYsfPQS +-----END CERTIFICATE----- + +Swisscom Root CA 1 +================== +-----BEGIN CERTIFICATE----- +MIIF2TCCA8GgAwIBAgIQXAuFXAvnWUHfV8w/f52oNjANBgkqhkiG9w0BAQUFADBkMQswCQYDVQQG +EwJjaDERMA8GA1UEChMIU3dpc3Njb20xJTAjBgNVBAsTHERpZ2l0YWwgQ2VydGlmaWNhdGUgU2Vy +dmljZXMxGzAZBgNVBAMTElN3aXNzY29tIFJvb3QgQ0EgMTAeFw0wNTA4MTgxMjA2MjBaFw0yNTA4 +MTgyMjA2MjBaMGQxCzAJBgNVBAYTAmNoMREwDwYDVQQKEwhTd2lzc2NvbTElMCMGA1UECxMcRGln +aXRhbCBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczEbMBkGA1UEAxMSU3dpc3Njb20gUm9vdCBDQSAxMIIC +IjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA0LmwqAzZuz8h+BvVM5OAFmUgdbI9m2BtRsiM +MW8Xw/qabFbtPMWRV8PNq5ZJkCoZSx6jbVfd8StiKHVFXqrWW/oLJdihFvkcxC7mlSpnzNApbjyF +NDhhSbEAn9Y6cV9Nbc5fuankiX9qUvrKm/LcqfmdmUc/TilftKaNXXsLmREDA/7n29uj/x2lzZAe +AR81sH8A25Bvxn570e56eqeqDFdvpG3FEzuwpdntMhy0XmeLVNxzh+XTF3xmUHJd1BpYwdnP2IkC +b6dJtDZd0KTeByy2dbcokdaXvij1mB7qWybJvbCXc9qukSbraMH5ORXWZ0sKbU/Lz7DkQnGMU3nn +7uHbHaBuHYwadzVcFh4rUx80i9Fs/PJnB3r1re3WmquhsUvhzDdf/X/NTa64H5xD+SpYVUNFvJbN +cA78yeNmuk6NO4HLFWR7uZToXTNShXEuT46iBhFRyePLoW4xCGQMwtI89Tbo19AOeCMgkckkKmUp +WyL3Ic6DXqTz3kvTaI9GdVyDCW4pa8RwjPWd1yAv/0bSKzjCL3UcPX7ape8eYIVpQtPM+GP+HkM5 +haa2Y0EQs3MevNP6yn0WR+Kn1dCjigoIlmJWbjTb2QK5MHXjBNLnj8KwEUAKrNVxAmKLMb7dxiNY +MUJDLXT5xp6mig/p/r+D5kNXJLrvRjSq1xIBOO0CAwEAAaOBhjCBgzAOBgNVHQ8BAf8EBAMCAYYw +HQYDVR0hBBYwFDASBgdghXQBUwABBgdghXQBUwABMBIGA1UdEwEB/wQIMAYBAf8CAQcwHwYDVR0j +BBgwFoAUAyUv3m+CATpcLNwroWm1Z9SM0/0wHQYDVR0OBBYEFAMlL95vggE6XCzcK6FptWfUjNP9 +MA0GCSqGSIb3DQEBBQUAA4ICAQA1EMvspgQNDQ/NwNurqPKIlwzfky9NfEBWMXrrpA9gzXrzvsMn +jgM+pN0S734edAY8PzHyHHuRMSG08NBsl9Tpl7IkVh5WwzW9iAUPWxAaZOHHgjD5Mq2eUCzneAXQ +MbFamIp1TpBcahQq4FJHgmDmHtqBsfsUC1rxn9KVuj7QG9YVHaO+htXbD8BJZLsuUBlL0iT43R4H +VtA4oJVwIHaM190e3p9xxCPvgxNcoyQVTSlAPGrEqdi3pkSlDfTgnXceQHAm/NrZNuR55LU/vJtl +vrsRls/bxig5OgjOR1tTWsWZ/l2p3e9M1MalrQLmjAcSHm8D0W+go/MpvRLHUKKwf4ipmXeascCl +OS5cfGniLLDqN2qk4Vrh9VDlg++luyqI54zb/W1elxmofmZ1a3Hqv7HHb6D0jqTsNFFbjCYDcKF3 +1QESVwA12yPeDooomf2xEG9L/zgtYE4snOtnta1J7ksfrK/7DZBaZmBwXarNeNQk7shBoJMBkpxq +nvy5JMWzFYJ+vq6VK+uxwNrjAWALXmmshFZhvnEX/h0TD/7Gh0Xp/jKgGg0TpJRVcaUWi7rKibCy +x/yP2FS1k2Kdzs9Z+z0YzirLNRWCXf9UIltxUvu3yf5gmwBBZPCqKuy2QkPOiWaByIufOVQDJdMW +NY6E0F/6MBr1mmz0DlP5OlvRHA== +-----END CERTIFICATE----- + +DigiCert Assured ID Root CA +=========================== +-----BEGIN CERTIFICATE----- +MIIDtzCCAp+gAwIBAgIQDOfg5RfYRv6P5WD8G/AwOTANBgkqhkiG9w0BAQUFADBlMQswCQYDVQQG +EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSQw +IgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgQ0EwHhcNMDYxMTEwMDAwMDAwWhcNMzEx +MTEwMDAwMDAwWjBlMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQL +ExB3d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgQ0Ew +ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCtDhXO5EOAXLGH87dg+XESpa7cJpSIqvTO +9SA5KFhgDPiA2qkVlTJhPLWxKISKityfCgyDF3qPkKyK53lTXDGEKvYPmDI2dsze3Tyoou9q+yHy +UmHfnyDXH+Kx2f4YZNISW1/5WBg1vEfNoTb5a3/UsDg+wRvDjDPZ2C8Y/igPs6eD1sNuRMBhNZYW +/lmci3Zt1/GiSw0r/wty2p5g0I6QNcZ4VYcgoc/lbQrISXwxmDNsIumH0DJaoroTghHtORedmTpy +oeb6pNnVFzF1roV9Iq4/AUaG9ih5yLHa5FcXxH4cDrC0kqZWs72yl+2qp/C3xag/lRbQ/6GW6whf +GHdPAgMBAAGjYzBhMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRF +66Kv9JLLgjEtUYunpyGd823IDzAfBgNVHSMEGDAWgBRF66Kv9JLLgjEtUYunpyGd823IDzANBgkq +hkiG9w0BAQUFAAOCAQEAog683+Lt8ONyc3pklL/3cmbYMuRCdWKuh+vy1dneVrOfzM4UKLkNl2Bc +EkxY5NM9g0lFWJc1aRqoR+pWxnmrEthngYTffwk8lOa4JiwgvT2zKIn3X/8i4peEH+ll74fg38Fn +SbNd67IJKusm7Xi+fT8r87cmNW1fiQG2SVufAQWbqz0lwcy2f8Lxb4bG+mRo64EtlOtCt/qMHt1i +8b5QZ7dsvfPxH2sMNgcWfzd8qVttevESRmCD1ycEvkvOl77DZypoEd+A5wwzZr8TDRRu838fYxAe ++o0bJW1sj6W3YQGx0qMmoRBxna3iw/nDmVG3KwcIzi7mULKn+gpFL6Lw8g== +-----END CERTIFICATE----- + +DigiCert Global Root CA +======================= +-----BEGIN CERTIFICATE----- +MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBhMQswCQYDVQQG +EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSAw +HgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBDQTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAw +MDAwMDBaMGExCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3 +dy5kaWdpY2VydC5jb20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkq +hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsBCSDMAZOn +TjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97nh6Vfe63SKMI2tavegw5 +BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt43C/dxC//AH2hdmoRBBYMql1GNXRor5H +4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7PT19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y +7vrTC0LUq7dBMtoM1O/4gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQAB +o2MwYTAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbRTLtm +8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUwDQYJKoZIhvcNAQEF +BQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/EsrhMAtudXH/vTBH1jLuG2cenTnmCmr +EbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIt +tep3Sp+dWOIrWcBAI+0tKIJFPnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886 +UAb3LujEV0lsYSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk +CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4= +-----END CERTIFICATE----- + +DigiCert Global Root G2 +======================= +-----BEGIN CERTIFICATE----- +MIIDjjCCAnagAwIBAgIQAzrx5qcRqaC7KGSxHQn65TANBgkqhkiG9w0BAQsFADBh +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBH +MjAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGExCzAJBgNVBAYTAlVT +MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j +b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEcyMIIBIjANBgkqhkiG +9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuzfNNNx7a8myaJCtSnX/RrohCgiN9RlUyfuI +2/Ou8jqJkTx65qsGGmvPrC3oXgkkRLpimn7Wo6h+4FR1IAWsULecYxpsMNzaHxmx +1x7e/dfgy5SDN67sH0NO3Xss0r0upS/kqbitOtSZpLYl6ZtrAGCSYP9PIUkY92eQ +q2EGnI/yuum06ZIya7XzV+hdG82MHauVBJVJ8zUtluNJbd134/tJS7SsVQepj5Wz +tCO7TG1F8PapspUwtP1MVYwnSlcUfIKdzXOS0xZKBgyMUNGPHgm+F6HmIcr9g+UQ +vIOlCsRnKPZzFBQ9RnbDhxSJITRNrw9FDKZJobq7nMWxM4MphQIDAQABo0IwQDAP +BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUTiJUIBiV +5uNu5g/6+rkS7QYXjzkwDQYJKoZIhvcNAQELBQADggEBAGBnKJRvDkhj6zHd6mcY +1Yl9PMWLSn/pvtsrF9+wX3N3KjITOYFnQoQj8kVnNeyIv/iPsGEMNKSuIEyExtv4 +NeF22d+mQrvHRAiGfzZ0JFrabA0UWTW98kndth/Jsw1HKj2ZL7tcu7XUIOGZX1NG +Fdtom/DzMNU+MeKNhJ7jitralj41E6Vf8PlwUHBHQRFXGU7Aj64GxJUTFy8bJZ91 +8rGOmaFvE7FBcf6IKshPECBV1/MUReXgRPTqh5Uykw7+U0b6LJ3/iyK5S9kJRaTe +pLiaWN0bfVKfjllDiIGknibVb63dDcY3fe0Dkhvld1927jyNxF1WW6LZZm6zNTfl +MrY= +-----END CERTIFICATE----- + +DigiCert High Assurance EV Root CA +================================== +-----BEGIN CERTIFICATE----- +MIIDxTCCAq2gAwIBAgIQAqxcJmoLQJuPC3nyrkYldzANBgkqhkiG9w0BAQUFADBsMQswCQYDVQQG +EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSsw +KQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5jZSBFViBSb290IENBMB4XDTA2MTExMDAwMDAw +MFoXDTMxMTExMDAwMDAwMFowbDELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZ +MBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTErMCkGA1UEAxMiRGlnaUNlcnQgSGlnaCBBc3N1cmFu +Y2UgRVYgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMbM5XPm+9S75S0t +Mqbf5YE/yc0lSbZxKsPVlDRnogocsF9ppkCxxLeyj9CYpKlBWTrT3JTWPNt0OKRKzE0lgvdKpVMS +OO7zSW1xkX5jtqumX8OkhPhPYlG++MXs2ziS4wblCJEMxChBVfvLWokVfnHoNb9Ncgk9vjo4UFt3 +MRuNs8ckRZqnrG0AFFoEt7oT61EKmEFBIk5lYYeBQVCmeVyJ3hlKV9Uu5l0cUyx+mM0aBhakaHPQ +NAQTXKFx01p8VdteZOE3hzBWBOURtCmAEvF5OYiiAhF8J2a3iLd48soKqDirCmTCv2ZdlYTBoSUe +h10aUAsgEsxBu24LUTi4S8sCAwEAAaNjMGEwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQFMAMB +Af8wHQYDVR0OBBYEFLE+w2kD+L9HAdSYJhoIAu9jZCvDMB8GA1UdIwQYMBaAFLE+w2kD+L9HAdSY +JhoIAu9jZCvDMA0GCSqGSIb3DQEBBQUAA4IBAQAcGgaX3NecnzyIZgYIVyHbIUf4KmeqvxgydkAQ +V8GK83rZEWWONfqe/EW1ntlMMUu4kehDLI6zeM7b41N5cdblIZQB2lWHmiRk9opmzN6cN82oNLFp +myPInngiK3BD41VHMWEZ71jFhS9OMPagMRYjyOfiZRYzy78aG6A9+MpeizGLYAiJLQwGXFK3xPkK +mNEVX58Svnw2Yzi9RKR/5CYrCsSXaQ3pjOLAEFe4yHYSkVXySGnYvCoCWw9E1CAx2/S6cCZdkGCe +vEsXCS+0yx5DaMkHJ8HSXPfqIbloEpw8nL+e/IBcm2PN7EeqJSdnoDfzAIJ9VNep+OkuE6N36B9K +-----END CERTIFICATE----- + +Certplus Class 2 Primary CA +=========================== +-----BEGIN CERTIFICATE----- +MIIDkjCCAnqgAwIBAgIRAIW9S/PY2uNp9pTXX8OlRCMwDQYJKoZIhvcNAQEFBQAwPTELMAkGA1UE +BhMCRlIxETAPBgNVBAoTCENlcnRwbHVzMRswGQYDVQQDExJDbGFzcyAyIFByaW1hcnkgQ0EwHhcN +OTkwNzA3MTcwNTAwWhcNMTkwNzA2MjM1OTU5WjA9MQswCQYDVQQGEwJGUjERMA8GA1UEChMIQ2Vy +dHBsdXMxGzAZBgNVBAMTEkNsYXNzIDIgUHJpbWFyeSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEP +ADCCAQoCggEBANxQltAS+DXSCHh6tlJw/W/uz7kRy1134ezpfgSN1sxvc0NXYKwzCkTsA18cgCSR +5aiRVhKC9+Ar9NuuYS6JEI1rbLqzAr3VNsVINyPi8Fo3UjMXEuLRYE2+L0ER4/YXJQyLkcAbmXuZ +Vg2v7tK8R1fjeUl7NIknJITesezpWE7+Tt9avkGtrAjFGA7v0lPubNCdEgETjdyAYveVqUSISnFO +YFWe2yMZeVYHDD9jC1yw4r5+FfyUM1hBOHTE4Y+L3yasH7WLO7dDWWuwJKZtkIvEcupdM5i3y95e +e++U8Rs+yskhwcWYAqqi9lt3m/V+llU0HGdpwPFC40es/CgcZlUCAwEAAaOBjDCBiTAPBgNVHRME +CDAGAQH/AgEKMAsGA1UdDwQEAwIBBjAdBgNVHQ4EFgQU43Mt38sOKAze3bOkynm4jrvoMIkwEQYJ +YIZIAYb4QgEBBAQDAgEGMDcGA1UdHwQwMC4wLKAqoCiGJmh0dHA6Ly93d3cuY2VydHBsdXMuY29t +L0NSTC9jbGFzczIuY3JsMA0GCSqGSIb3DQEBBQUAA4IBAQCnVM+IRBnL39R/AN9WM2K191EBkOvD +P9GIROkkXe/nFL0gt5o8AP5tn9uQ3Nf0YtaLcF3n5QRIqWh8yfFC82x/xXp8HVGIutIKPidd3i1R +TtMTZGnkLuPT55sJmabglZvOGtd/vjzOUrMRFcEPF80Du5wlFbqidon8BvEY0JNLDnyCt6X09l/+ +7UCmnYR0ObncHoUW2ikbhiMAybuJfm6AiB4vFLQDJKgybwOaRywwvlbGp0ICcBvqQNi6BQNwB6SW +//1IMwrh3KWBkJtN3X3n57LNXMhqlfil9o3EXXgIvnsG1knPGTZQIy4I5p4FTUcY1Rbpsda2ENW7 +l7+ijrRU +-----END CERTIFICATE----- + +DST Root CA X3 +============== +-----BEGIN CERTIFICATE----- +MIIDSjCCAjKgAwIBAgIQRK+wgNajJ7qJMDmGLvhAazANBgkqhkiG9w0BAQUFADA/MSQwIgYDVQQK +ExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMTDkRTVCBSb290IENBIFgzMB4X +DTAwMDkzMDIxMTIxOVoXDTIxMDkzMDE0MDExNVowPzEkMCIGA1UEChMbRGlnaXRhbCBTaWduYXR1 +cmUgVHJ1c3QgQ28uMRcwFQYDVQQDEw5EU1QgUm9vdCBDQSBYMzCCASIwDQYJKoZIhvcNAQEBBQAD +ggEPADCCAQoCggEBAN+v6ZdQCINXtMxiZfaQguzH0yxrMMpb7NnDfcdAwRgUi+DoM3ZJKuM/IUmT +rE4Orz5Iy2Xu/NMhD2XSKtkyj4zl93ewEnu1lcCJo6m67XMuegwGMoOifooUMM0RoOEqOLl5CjH9 +UL2AZd+3UWODyOKIYepLYYHsUmu5ouJLGiifSKOeDNoJjj4XLh7dIN9bxiqKqy69cK3FCxolkHRy +xXtqqzTWMIn/5WgTe1QLyNau7Fqckh49ZLOMxt+/yUFw7BZy1SbsOFU5Q9D8/RhcQPGX69Wam40d +utolucbY38EVAjqr2m7xPi71XAicPNaDaeQQmxkqtilX4+U9m5/wAl0CAwEAAaNCMEAwDwYDVR0T +AQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFMSnsaR7LHH62+FLkHX/xBVghYkQ +MA0GCSqGSIb3DQEBBQUAA4IBAQCjGiybFwBcqR7uKGY3Or+Dxz9LwwmglSBd49lZRNI+DT69ikug +dB/OEIKcdBodfpga3csTS7MgROSR6cz8faXbauX+5v3gTt23ADq1cEmv8uXrAvHRAosZy5Q6XkjE +GB5YGV8eAlrwDPGxrancWYaLbumR9YbK+rlmM6pZW87ipxZzR8srzJmwN0jP41ZL9c8PDHIyh8bw +RLtTcm1D9SZImlJnt1ir/md2cXjbDaJWFBM5JDGFoqgCWjBH4d1QB7wCCZAA62RjYJsWvIjJEubS +fZGL+T0yjWW06XyxV3bqxbYoOb8VZRzI9neWagqNdwvYkQsEjgfbKbYK7p2CNTUQ +-----END CERTIFICATE----- + +DST ACES CA X6 +============== +-----BEGIN CERTIFICATE----- +MIIECTCCAvGgAwIBAgIQDV6ZCtadt3js2AdWO4YV2TANBgkqhkiG9w0BAQUFADBbMQswCQYDVQQG +EwJVUzEgMB4GA1UEChMXRGlnaXRhbCBTaWduYXR1cmUgVHJ1c3QxETAPBgNVBAsTCERTVCBBQ0VT +MRcwFQYDVQQDEw5EU1QgQUNFUyBDQSBYNjAeFw0wMzExMjAyMTE5NThaFw0xNzExMjAyMTE5NTha +MFsxCzAJBgNVBAYTAlVTMSAwHgYDVQQKExdEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdDERMA8GA1UE +CxMIRFNUIEFDRVMxFzAVBgNVBAMTDkRTVCBBQ0VTIENBIFg2MIIBIjANBgkqhkiG9w0BAQEFAAOC +AQ8AMIIBCgKCAQEAuT31LMmU3HWKlV1j6IR3dma5WZFcRt2SPp/5DgO0PWGSvSMmtWPuktKe1jzI +DZBfZIGxqAgNTNj50wUoUrQBJcWVHAx+PhCEdc/BGZFjz+iokYi5Q1K7gLFViYsx+tC3dr5BPTCa +pCIlF3PoHuLTrCq9Wzgh1SpL11V94zpVvddtawJXa+ZHfAjIgrrep4c9oW24MFbCswKBXy314pow +GCi4ZtPLAZZv6opFVdbgnf9nKxcCpk4aahELfrd755jWjHZvwTvbUJN+5dCOHze4vbrGn2zpfDPy +MjwmR/onJALJfh1biEITajV8fTXpLmaRcpPVMibEdPVTo7NdmvYJywIDAQABo4HIMIHFMA8GA1Ud +EwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgHGMB8GA1UdEQQYMBaBFHBraS1vcHNAdHJ1c3Rkc3Qu +Y29tMGIGA1UdIARbMFkwVwYKYIZIAWUDAgEBATBJMEcGCCsGAQUFBwIBFjtodHRwOi8vd3d3LnRy +dXN0ZHN0LmNvbS9jZXJ0aWZpY2F0ZXMvcG9saWN5L0FDRVMtaW5kZXguaHRtbDAdBgNVHQ4EFgQU +CXIGThhDD+XWzMNqizF7eI+og7gwDQYJKoZIhvcNAQEFBQADggEBAKPYjtay284F5zLNAdMEA+V2 +5FYrnJmQ6AgwbN99Pe7lv7UkQIRJ4dEorsTCOlMwiPH1d25Ryvr/ma8kXxug/fKshMrfqfBfBC6t +Fr8hlxCBPeP/h40y3JTlR4peahPJlJU90u7INJXQgNStMgiAVDzgvVJT11J8smk/f3rPanTK+gQq +nExaBqXpIK1FZg9p8d2/6eMyi/rgwYZNcjwu2JN4Cir42NInPRmJX1p7ijvMDNpRrscL9yuwNwXs +vFcj4jjSm2jzVhKIT0J8uDHEtdvkyCE06UgRNe76x5JXxZ805Mf29w4LTJxoeHtxMcfrHuBnQfO3 +oKfN5XozNmr6mis= +-----END CERTIFICATE----- + +TURKTRUST Certificate Services Provider Root 1 +============================================== +-----BEGIN CERTIFICATE----- +MIID+zCCAuOgAwIBAgIBATANBgkqhkiG9w0BAQUFADCBtzE/MD0GA1UEAww2VMOcUktUUlVTVCBF +bGVrdHJvbmlrIFNlcnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sxc8SxMQswCQYDVQQGDAJUUjEP +MA0GA1UEBwwGQU5LQVJBMVYwVAYDVQQKDE0oYykgMjAwNSBUw5xSS1RSVVNUIEJpbGdpIMSwbGV0 +acWfaW0gdmUgQmlsacWfaW0gR8O8dmVubGnEn2kgSGl6bWV0bGVyaSBBLsWeLjAeFw0wNTA1MTMx +MDI3MTdaFw0xNTAzMjIxMDI3MTdaMIG3MT8wPQYDVQQDDDZUw5xSS1RSVVNUIEVsZWt0cm9uaWsg +U2VydGlmaWthIEhpem1ldCBTYcSfbGF5xLFjxLFzxLExCzAJBgNVBAYMAlRSMQ8wDQYDVQQHDAZB +TktBUkExVjBUBgNVBAoMTShjKSAyMDA1IFTDnFJLVFJVU1QgQmlsZ2kgxLBsZXRpxZ9pbSB2ZSBC +aWxpxZ9pbSBHw7x2ZW5sacSfaSBIaXptZXRsZXJpIEEuxZ4uMIIBIjANBgkqhkiG9w0BAQEFAAOC +AQ8AMIIBCgKCAQEAylIF1mMD2Bxf3dJ7XfIMYGFbazt0K3gNfUW9InTojAPBxhEqPZW8qZSwu5GX +yGl8hMW0kWxsE2qkVa2kheiVfrMArwDCBRj1cJ02i67L5BuBf5OI+2pVu32Fks66WJ/bMsW9Xe8i +Si9BB35JYbOG7E6mQW6EvAPs9TscyB/C7qju6hJKjRTP8wrgUDn5CDX4EVmt5yLqS8oUBt5CurKZ +8y1UiBAG6uEaPj1nH/vO+3yC6BFdSsG5FOpU2WabfIl9BJpiyelSPJ6c79L1JuTm5Rh8i27fbMx4 +W09ysstcP4wFjdFMjK2Sx+F4f2VsSQZQLJ4ywtdKxnWKWU51b0dewQIDAQABoxAwDjAMBgNVHRME +BTADAQH/MA0GCSqGSIb3DQEBBQUAA4IBAQAV9VX/N5aAWSGk/KEVTCD21F/aAyT8z5Aa9CEKmu46 +sWrv7/hg0Uw2ZkUd82YCdAR7kjCo3gp2D++Vbr3JN+YaDayJSFvMgzbC9UZcWYJWtNX+I7TYVBxE +q8Sn5RTOPEFhfEPmzcSBCYsk+1Ql1haolgxnB2+zUEfjHCQo3SqYpGH+2+oSN7wBGjSFvW5P55Fy +B0SFHljKVETd96y5y4khctuPwGkplyqjrhgjlxxBKot8KsF8kOipKMDTkcatKIdAaLX/7KfS0zgY +nNN9aV3wxqUeJBujR/xpB2jn5Jq07Q+hh4cCzofSSE7hvP/L8XKSRGQDJereW26fyfJOrN3H +-----END CERTIFICATE----- + +TURKTRUST Certificate Services Provider Root 2 +============================================== +-----BEGIN CERTIFICATE----- +MIIEPDCCAySgAwIBAgIBATANBgkqhkiG9w0BAQUFADCBvjE/MD0GA1UEAww2VMOcUktUUlVTVCBF +bGVrdHJvbmlrIFNlcnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sxc8SxMQswCQYDVQQGEwJUUjEP +MA0GA1UEBwwGQW5rYXJhMV0wWwYDVQQKDFRUw5xSS1RSVVNUIEJpbGdpIMSwbGV0acWfaW0gdmUg +QmlsacWfaW0gR8O8dmVubGnEn2kgSGl6bWV0bGVyaSBBLsWeLiAoYykgS2FzxLFtIDIwMDUwHhcN +MDUxMTA3MTAwNzU3WhcNMTUwOTE2MTAwNzU3WjCBvjE/MD0GA1UEAww2VMOcUktUUlVTVCBFbGVr +dHJvbmlrIFNlcnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sxc8SxMQswCQYDVQQGEwJUUjEPMA0G +A1UEBwwGQW5rYXJhMV0wWwYDVQQKDFRUw5xSS1RSVVNUIEJpbGdpIMSwbGV0acWfaW0gdmUgQmls +acWfaW0gR8O8dmVubGnEn2kgSGl6bWV0bGVyaSBBLsWeLiAoYykgS2FzxLFtIDIwMDUwggEiMA0G +CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCpNn7DkUNMwxmYCMjHWHtPFoylzkkBH3MOrHUTpvqe +LCDe2JAOCtFp0if7qnefJ1Il4std2NiDUBd9irWCPwSOtNXwSadktx4uXyCcUHVPr+G1QRT0mJKI +x+XlZEdhR3n9wFHxwZnn3M5q+6+1ATDcRhzviuyV79z/rxAc653YsKpqhRgNF8k+v/Gb0AmJQv2g +QrSdiVFVKc8bcLyEVK3BEx+Y9C52YItdP5qtygy/p1Zbj3e41Z55SZI/4PGXJHpsmxcPbe9TmJEr +5A++WXkHeLuXlfSfadRYhwqp48y2WBmfJiGxxFmNskF1wK1pzpwACPI2/z7woQ8arBT9pmAPAgMB +AAGjQzBBMB0GA1UdDgQWBBTZN7NOBf3Zz58SFq62iS/rJTqIHDAPBgNVHQ8BAf8EBQMDBwYAMA8G +A1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAHJglrfJ3NgpXiOFX7KzLXb7iNcX/ntt +Rbj2hWyfIvwqECLsqrkw9qtY1jkQMZkpAL2JZkH7dN6RwRgLn7Vhy506vvWolKMiVW4XSf/SKfE4 +Jl3vpao6+XF75tpYHdN0wgH6PmlYX63LaL4ULptswLbcoCb6dxriJNoaN+BnrdFzgw2lGh1uEpJ+ +hGIAF728JRhX8tepb1mIvDS3LoV4nZbcFMMsilKbloxSZj2GFotHuFEJjOp9zYhys2AzsfAKRO8P +9Qk3iCQOLGsgOqL6EfJANZxEaGM7rDNvY7wsu/LSy3Z9fYjYHcgFHW68lKlmjHdxx/qR+i9Rnuk5 +UrbnBEI= +-----END CERTIFICATE----- + +SwissSign Gold CA - G2 +====================== +-----BEGIN CERTIFICATE----- +MIIFujCCA6KgAwIBAgIJALtAHEP1Xk+wMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNVBAYTAkNIMRUw +EwYDVQQKEwxTd2lzc1NpZ24gQUcxHzAdBgNVBAMTFlN3aXNzU2lnbiBHb2xkIENBIC0gRzIwHhcN +MDYxMDI1MDgzMDM1WhcNMzYxMDI1MDgzMDM1WjBFMQswCQYDVQQGEwJDSDEVMBMGA1UEChMMU3dp +c3NTaWduIEFHMR8wHQYDVQQDExZTd2lzc1NpZ24gR29sZCBDQSAtIEcyMIICIjANBgkqhkiG9w0B +AQEFAAOCAg8AMIICCgKCAgEAr+TufoskDhJuqVAtFkQ7kpJcyrhdhJJCEyq8ZVeCQD5XJM1QiyUq +t2/876LQwB8CJEoTlo8jE+YoWACjR8cGp4QjK7u9lit/VcyLwVcfDmJlD909Vopz2q5+bbqBHH5C +jCA12UNNhPqE21Is8w4ndwtrvxEvcnifLtg+5hg3Wipy+dpikJKVyh+c6bM8K8vzARO/Ws/BtQpg +vd21mWRTuKCWs2/iJneRjOBiEAKfNA+k1ZIzUd6+jbqEemA8atufK+ze3gE/bk3lUIbLtK/tREDF +ylqM2tIrfKjuvqblCqoOpd8FUrdVxyJdMmqXl2MT28nbeTZ7hTpKxVKJ+STnnXepgv9VHKVxaSvR +AiTysybUa9oEVeXBCsdtMDeQKuSeFDNeFhdVxVu1yzSJkvGdJo+hB9TGsnhQ2wwMC3wLjEHXuend +jIj3o02yMszYF9rNt85mndT9Xv+9lz4pded+p2JYryU0pUHHPbwNUMoDAw8IWh+Vc3hiv69yFGkO +peUDDniOJihC8AcLYiAQZzlG+qkDzAQ4embvIIO1jEpWjpEA/I5cgt6IoMPiaG59je883WX0XaxR +7ySArqpWl2/5rX3aYT+YdzylkbYcjCbaZaIJbcHiVOO5ykxMgI93e2CaHt+28kgeDrpOVG2Y4OGi +GqJ3UM/EY5LsRxmd6+ZrzsECAwEAAaOBrDCBqTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUw +AwEB/zAdBgNVHQ4EFgQUWyV7lqRlUX64OfPAeGZe6Drn8O4wHwYDVR0jBBgwFoAUWyV7lqRlUX64 +OfPAeGZe6Drn8O4wRgYDVR0gBD8wPTA7BglghXQBWQECAQEwLjAsBggrBgEFBQcCARYgaHR0cDov +L3JlcG9zaXRvcnkuc3dpc3NzaWduLmNvbS8wDQYJKoZIhvcNAQEFBQADggIBACe645R88a7A3hfm +5djV9VSwg/S7zV4Fe0+fdWavPOhWfvxyeDgD2StiGwC5+OlgzczOUYrHUDFu4Up+GC9pWbY9ZIEr +44OE5iKHjn3g7gKZYbge9LgriBIWhMIxkziWMaa5O1M/wySTVltpkuzFwbs4AOPsF6m43Md8AYOf +Mke6UiI0HTJ6CVanfCU2qT1L2sCCbwq7EsiHSycR+R4tx5M/nttfJmtS2S6K8RTGRI0Vqbe/vd6m +Gu6uLftIdxf+u+yvGPUqUfA5hJeVbG4bwyvEdGB5JbAKJ9/fXtI5z0V9QkvfsywexcZdylU6oJxp +mo/a77KwPJ+HbBIrZXAVUjEaJM9vMSNQH4xPjyPDdEFjHFWoFN0+4FFQz/EbMFYOkrCChdiDyyJk +vC24JdVUorgG6q2SpCSgwYa1ShNqR88uC1aVVMvOmttqtKay20EIhid392qgQmwLOM7XdVAyksLf +KzAiSNDVQTglXaTpXZ/GlHXQRf0wl0OPkKsKx4ZzYEppLd6leNcG2mqeSz53OiATIgHQv2ieY2Br +NU0LbbqhPcCT4H8js1WtciVORvnSFu+wZMEBnunKoGqYDs/YYPIvSbjkQuE4NRb0yG5P94FW6Lqj +viOvrv1vA+ACOzB2+httQc8Bsem4yWb02ybzOqR08kkkW8mw0FfB+j564ZfJ +-----END CERTIFICATE----- + +SwissSign Silver CA - G2 +======================== +-----BEGIN CERTIFICATE----- +MIIFvTCCA6WgAwIBAgIITxvUL1S7L0swDQYJKoZIhvcNAQEFBQAwRzELMAkGA1UEBhMCQ0gxFTAT +BgNVBAoTDFN3aXNzU2lnbiBBRzEhMB8GA1UEAxMYU3dpc3NTaWduIFNpbHZlciBDQSAtIEcyMB4X +DTA2MTAyNTA4MzI0NloXDTM2MTAyNTA4MzI0NlowRzELMAkGA1UEBhMCQ0gxFTATBgNVBAoTDFN3 +aXNzU2lnbiBBRzEhMB8GA1UEAxMYU3dpc3NTaWduIFNpbHZlciBDQSAtIEcyMIICIjANBgkqhkiG +9w0BAQEFAAOCAg8AMIICCgKCAgEAxPGHf9N4Mfc4yfjDmUO8x/e8N+dOcbpLj6VzHVxumK4DV644 +N0MvFz0fyM5oEMF4rhkDKxD6LHmD9ui5aLlV8gREpzn5/ASLHvGiTSf5YXu6t+WiE7brYT7QbNHm ++/pe7R20nqA1W6GSy/BJkv6FCgU+5tkL4k+73JU3/JHpMjUi0R86TieFnbAVlDLaYQ1HTWBCrpJH +6INaUFjpiou5XaHc3ZlKHzZnu0jkg7Y360g6rw9njxcH6ATK72oxh9TAtvmUcXtnZLi2kUpCe2Uu +MGoM9ZDulebyzYLs2aFK7PayS+VFheZteJMELpyCbTapxDFkH4aDCyr0NQp4yVXPQbBH6TCfmb5h +qAaEuSh6XzjZG6k4sIN/c8HDO0gqgg8hm7jMqDXDhBuDsz6+pJVpATqJAHgE2cn0mRmrVn5bi4Y5 +FZGkECwJMoBgs5PAKrYYC51+jUnyEEp/+dVGLxmSo5mnJqy7jDzmDrxHB9xzUfFwZC8I+bRHHTBs +ROopN4WSaGa8gzj+ezku01DwH/teYLappvonQfGbGHLy9YR0SslnxFSuSGTfjNFusB3hB48IHpmc +celM2KX3RxIfdNFRnobzwqIjQAtz20um53MGjMGg6cFZrEb65i/4z3GcRm25xBWNOHkDRUjvxF3X +CO6HOSKGsg0PWEP3calILv3q1h8CAwEAAaOBrDCBqTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/ +BAUwAwEB/zAdBgNVHQ4EFgQUF6DNweRBtjpbO8tFnb0cwpj6hlgwHwYDVR0jBBgwFoAUF6DNweRB +tjpbO8tFnb0cwpj6hlgwRgYDVR0gBD8wPTA7BglghXQBWQEDAQEwLjAsBggrBgEFBQcCARYgaHR0 +cDovL3JlcG9zaXRvcnkuc3dpc3NzaWduLmNvbS8wDQYJKoZIhvcNAQEFBQADggIBAHPGgeAn0i0P +4JUw4ppBf1AsX19iYamGamkYDHRJ1l2E6kFSGG9YrVBWIGrGvShpWJHckRE1qTodvBqlYJ7YH39F +kWnZfrt4csEGDyrOj4VwYaygzQu4OSlWhDJOhrs9xCrZ1x9y7v5RoSJBsXECYxqCsGKrXlcSH9/L +3XWgwF15kIwb4FDm3jH+mHtwX6WQ2K34ArZv02DdQEsixT2tOnqfGhpHkXkzuoLcMmkDlm4fS/Bx +/uNncqCxv1yL5PqZIseEuRuNI5c/7SXgz2W79WEE790eslpBIlqhn10s6FvJbakMDHiqYMZWjwFa +DGi8aRl5xB9+lwW/xekkUV7U1UtT7dkjWjYDZaPBA61BMPNGG4WQr2W11bHkFlt4dR2Xem1ZqSqP +e97Dh4kQmUlzeMg9vVE1dCrV8X5pGyq7O70luJpaPXJhkGaH7gzWTdQRdAtq/gsD/KNVV4n+Ssuu +WxcFyPKNIzFTONItaj+CuY0IavdeQXRuwxF+B6wpYJE/OMpXEA29MC/HpeZBoNquBYeaoKRlbEwJ +DIm6uNO5wJOKMPqN5ZprFQFOZ6raYlY+hAhm0sQ2fac+EPyI4NSA5QC9qvNOBqN6avlicuMJT+ub +DgEj8Z+7fNzcbBGXJbLytGMU0gYqZ4yD9c7qB9iaah7s5Aq7KkzrCWA5zspi2C5u +-----END CERTIFICATE----- + +GeoTrust Primary Certification Authority +======================================== +-----BEGIN CERTIFICATE----- +MIIDfDCCAmSgAwIBAgIQGKy1av1pthU6Y2yv2vrEoTANBgkqhkiG9w0BAQUFADBYMQswCQYDVQQG +EwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjExMC8GA1UEAxMoR2VvVHJ1c3QgUHJpbWFyeSBD +ZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNjExMjcwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMFgx +CzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMTEwLwYDVQQDEyhHZW9UcnVzdCBQ +cmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB +CgKCAQEAvrgVe//UfH1nrYNke8hCUy3f9oQIIGHWAVlqnEQRr+92/ZV+zmEwu3qDXwK9AWbK7hWN +b6EwnL2hhZ6UOvNWiAAxz9juapYC2e0DjPt1befquFUWBRaa9OBesYjAZIVcFU2Ix7e64HXprQU9 +nceJSOC7KMgD4TCTZF5SwFlwIjVXiIrxlQqD17wxcwE07e9GceBrAqg1cmuXm2bgyxx5X9gaBGge +RwLmnWDiNpcB3841kt++Z8dtd1k7j53WkBWUvEI0EME5+bEnPn7WinXFsq+W06Lem+SYvn3h6YGt +tm/81w7a4DSwDRp35+MImO9Y+pyEtzavwt+s0vQQBnBxNQIDAQABo0IwQDAPBgNVHRMBAf8EBTAD +AQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQULNVQQZcVi/CPNmFbSvtr2ZnJM5IwDQYJKoZI +hvcNAQEFBQADggEBAFpwfyzdtzRP9YZRqSa+S7iq8XEN3GHHoOo0Hnp3DwQ16CePbJC/kRYkRj5K +Ts4rFtULUh38H2eiAkUxT87z+gOneZ1TatnaYzr4gNfTmeGl4b7UVXGYNTq+k+qurUKykG/g/CFN +NWMziUnWm07Kx+dOCQD32sfvmWKZd7aVIl6KoKv0uHiYyjgZmclynnjNS6yvGaBzEi38wkG6gZHa +Floxt/m0cYASSJlyc1pZU8FjUjPtp8nSOQJw+uCxQmYpqptR7TBUIhRf2asdweSU8Pj1K/fqynhG +1riR/aYNKxoUAT6A8EKglQdebc3MS6RFjasS6LPeWuWgfOgPIh1a6Vk= +-----END CERTIFICATE----- + +thawte Primary Root CA +====================== +-----BEGIN CERTIFICATE----- +MIIEIDCCAwigAwIBAgIQNE7VVyDV7exJ9C/ON9srbTANBgkqhkiG9w0BAQUFADCBqTELMAkGA1UE +BhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2 +aWNlcyBEaXZpc2lvbjE4MDYGA1UECxMvKGMpIDIwMDYgdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhv +cml6ZWQgdXNlIG9ubHkxHzAdBgNVBAMTFnRoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EwHhcNMDYxMTE3 +MDAwMDAwWhcNMzYwNzE2MjM1OTU5WjCBqTELMAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwg +SW5jLjEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjE4MDYGA1UECxMv +KGMpIDIwMDYgdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxHzAdBgNVBAMT +FnRoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCs +oPD7gFnUnMekz52hWXMJEEUMDSxuaPFsW0hoSVk3/AszGcJ3f8wQLZU0HObrTQmnHNK4yZc2AreJ +1CRfBsDMRJSUjQJib+ta3RGNKJpchJAQeg29dGYvajig4tVUROsdB58Hum/u6f1OCyn1PoSgAfGc +q/gcfomk6KHYcWUNo1F77rzSImANuVud37r8UVsLr5iy6S7pBOhih94ryNdOwUxkHt3Ph1i6Sk/K +aAcdHJ1KxtUvkcx8cXIcxcBn6zL9yZJclNqFwJu/U30rCfSMnZEfl2pSy94JNqR32HuHUETVPm4p +afs5SSYeCaWAe0At6+gnhcn+Yf1+5nyXHdWdAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYD +VR0PAQH/BAQDAgEGMB0GA1UdDgQWBBR7W0XPr87Lev0xkhpqtvNG61dIUDANBgkqhkiG9w0BAQUF +AAOCAQEAeRHAS7ORtvzw6WfUDW5FvlXok9LOAz/t2iWwHVfLHjp2oEzsUHboZHIMpKnxuIvW1oeE +uzLlQRHAd9mzYJ3rG9XRbkREqaYB7FViHXe4XI5ISXycO1cRrK1zN44veFyQaEfZYGDm/Ac9IiAX +xPcW6cTYcvnIc3zfFi8VqT79aie2oetaupgf1eNNZAqdE8hhuvU5HIe6uL17In/2/qxAeeWsEG89 +jxt5dovEN7MhGITlNgDrYyCZuen+MwS7QcjBAvlEYyCegc5C09Y/LHbTY5xZ3Y+m4Q6gLkH3LpVH +z7z9M/P2C2F+fpErgUfCJzDupxBdN49cOSvkBPB7jVaMaA== +-----END CERTIFICATE----- + +VeriSign Class 3 Public Primary Certification Authority - G5 +============================================================ +-----BEGIN CERTIFICATE----- +MIIE0zCCA7ugAwIBAgIQGNrRniZ96LtKIVjNzGs7SjANBgkqhkiG9w0BAQUFADCByjELMAkGA1UE +BhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZWZXJpU2lnbiBUcnVzdCBO +ZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVk +IHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJpU2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRp +ZmljYXRpb24gQXV0aG9yaXR5IC0gRzUwHhcNMDYxMTA4MDAwMDAwWhcNMzYwNzE2MjM1OTU5WjCB +yjELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZWZXJpU2ln +biBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJpU2lnbiwgSW5jLiAtIEZvciBh +dXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJpU2lnbiBDbGFzcyAzIFB1YmxpYyBQcmlt +YXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw +ggEKAoIBAQCvJAgIKXo1nmAMqudLO07cfLw8RRy7K+D+KQL5VwijZIUVJ/XxrcgxiV0i6CqqpkKz +j/i5Vbext0uz/o9+B1fs70PbZmIVYc9gDaTY3vjgw2IIPVQT60nKWVSFJuUrjxuf6/WhkcIzSdhD +Y2pSS9KP6HBRTdGJaXvHcPaz3BJ023tdS1bTlr8Vd6Gw9KIl8q8ckmcY5fQGBO+QueQA5N06tRn/ +Arr0PO7gi+s3i+z016zy9vA9r911kTMZHRxAy3QkGSGT2RT+rCpSx4/VBEnkjWNHiDxpg8v+R70r +fk/Fla4OndTRQ8Bnc+MUCH7lP59zuDMKz10/NIeWiu5T6CUVAgMBAAGjgbIwga8wDwYDVR0TAQH/ +BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwbQYIKwYBBQUHAQwEYTBfoV2gWzBZMFcwVRYJaW1hZ2Uv +Z2lmMCEwHzAHBgUrDgMCGgQUj+XTGoasjY5rw8+AatRIGCx7GS4wJRYjaHR0cDovL2xvZ28udmVy +aXNpZ24uY29tL3ZzbG9nby5naWYwHQYDVR0OBBYEFH/TZafC3ey78DAJ80M5+gKvMzEzMA0GCSqG +SIb3DQEBBQUAA4IBAQCTJEowX2LP2BqYLz3q3JktvXf2pXkiOOzEp6B4Eq1iDkVwZMXnl2YtmAl+ +X6/WzChl8gGqCBpH3vn5fJJaCGkgDdk+bW48DW7Y5gaRQBi5+MHt39tBquCWIMnNZBU4gcmU7qKE +KQsTb47bDN0lAtukixlE0kF6BWlKWE9gyn6CagsCqiUXObXbf+eEZSqVir2G3l6BFoMtEMze/aiC +Km0oHw0LxOXnGiYZ4fQRbxC1lfznQgUy286dUV4otp6F01vvpX1FQHKOtw5rDgb7MzVIcbidJ4vE +ZV8NhnacRHr2lVz2XTIIM6RUthg/aFzyQkqFOFSDX9HoLPKsEdao7WNq +-----END CERTIFICATE----- + +SecureTrust CA +============== +-----BEGIN CERTIFICATE----- +MIIDuDCCAqCgAwIBAgIQDPCOXAgWpa1Cf/DrJxhZ0DANBgkqhkiG9w0BAQUFADBIMQswCQYDVQQG +EwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24xFzAVBgNVBAMTDlNlY3VyZVRy +dXN0IENBMB4XDTA2MTEwNzE5MzExOFoXDTI5MTIzMTE5NDA1NVowSDELMAkGA1UEBhMCVVMxIDAe +BgNVBAoTF1NlY3VyZVRydXN0IENvcnBvcmF0aW9uMRcwFQYDVQQDEw5TZWN1cmVUcnVzdCBDQTCC +ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKukgeWVzfX2FI7CT8rU4niVWJxB4Q2ZQCQX +OZEzZum+4YOvYlyJ0fwkW2Gz4BERQRwdbvC4u/jep4G6pkjGnx29vo6pQT64lO0pGtSO0gMdA+9t +DWccV9cGrcrI9f4Or2YlSASWC12juhbDCE/RRvgUXPLIXgGZbf2IzIaowW8xQmxSPmjL8xk037uH +GFaAJsTQ3MBv396gwpEWoGQRS0S8Hvbn+mPeZqx2pHGj7DaUaHp3pLHnDi+BeuK1cobvomuL8A/b +01k/unK8RCSc43Oz969XL0Imnal0ugBS8kvNU3xHCzaFDmapCJcWNFfBZveA4+1wVMeT4C4oFVmH +ursCAwEAAaOBnTCBmjATBgkrBgEEAYI3FAIEBh4EAEMAQTALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/ +BAUwAwEB/zAdBgNVHQ4EFgQUQjK2FvoE/f5dS3rD/fdMQB1aQ68wNAYDVR0fBC0wKzApoCegJYYj +aHR0cDovL2NybC5zZWN1cmV0cnVzdC5jb20vU1RDQS5jcmwwEAYJKwYBBAGCNxUBBAMCAQAwDQYJ +KoZIhvcNAQEFBQADggEBADDtT0rhWDpSclu1pqNlGKa7UTt36Z3q059c4EVlew3KW+JwULKUBRSu +SceNQQcSc5R+DCMh/bwQf2AQWnL1mA6s7Ll/3XpvXdMc9P+IBWlCqQVxyLesJugutIxq/3HcuLHf +mbx8IVQr5Fiiu1cprp6poxkmD5kuCLDv/WnPmRoJjeOnnyvJNjR7JLN4TJUXpAYmHrZkUjZfYGfZ +nMUFdAvnZyPSCPyI6a6Lf+Ew9Dd+/cYy2i2eRDAwbO4H3tI0/NL/QPZL9GZGBlSm8jIKYyYwa5vR +3ItHuuG51WLQoqD0ZwV4KWMabwTW+MZMo5qxN7SN5ShLHZ4swrhovO0C7jE= +-----END CERTIFICATE----- + +Secure Global CA +================ +-----BEGIN CERTIFICATE----- +MIIDvDCCAqSgAwIBAgIQB1YipOjUiolN9BPI8PjqpTANBgkqhkiG9w0BAQUFADBKMQswCQYDVQQG +EwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24xGTAXBgNVBAMTEFNlY3VyZSBH +bG9iYWwgQ0EwHhcNMDYxMTA3MTk0MjI4WhcNMjkxMjMxMTk1MjA2WjBKMQswCQYDVQQGEwJVUzEg +MB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24xGTAXBgNVBAMTEFNlY3VyZSBHbG9iYWwg +Q0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvNS7YrGxVaQZx5RNoJLNP2MwhR/jx +YDiJiQPpvepeRlMJ3Fz1Wuj3RSoC6zFh1ykzTM7HfAo3fg+6MpjhHZevj8fcyTiW89sa/FHtaMbQ +bqR8JNGuQsiWUGMu4P51/pinX0kuleM5M2SOHqRfkNJnPLLZ/kG5VacJjnIFHovdRIWCQtBJwB1g +8NEXLJXr9qXBkqPFwqcIYA1gBBCWeZ4WNOaptvolRTnIHmX5k/Wq8VLcmZg9pYYaDDUz+kulBAYV +HDGA76oYa8J719rO+TMg1fW9ajMtgQT7sFzUnKPiXB3jqUJ1XnvUd+85VLrJChgbEplJL4hL/VBi +0XPnj3pDAgMBAAGjgZ0wgZowEwYJKwYBBAGCNxQCBAYeBABDAEEwCwYDVR0PBAQDAgGGMA8GA1Ud +EwEB/wQFMAMBAf8wHQYDVR0OBBYEFK9EBMJBfkiD2045AuzshHrmzsmkMDQGA1UdHwQtMCswKaAn +oCWGI2h0dHA6Ly9jcmwuc2VjdXJldHJ1c3QuY29tL1NHQ0EuY3JsMBAGCSsGAQQBgjcVAQQDAgEA +MA0GCSqGSIb3DQEBBQUAA4IBAQBjGghAfaReUw132HquHw0LURYD7xh8yOOvaliTFGCRsoTciE6+ +OYo68+aCiV0BN7OrJKQVDpI1WkpEXk5X+nXOH0jOZvQ8QCaSmGwb7iRGDBezUqXbpZGRzzfTb+cn +CDpOGR86p1hcF895P4vkp9MmI50mD1hp/Ed+stCNi5O/KU9DaXR2Z0vPB4zmAve14bRDtUstFJ/5 +3CYNv6ZHdAbYiNE6KTCEztI5gGIbqMdXSbxqVVFnFUq+NQfk1XWYN3kwFNspnWzFacxHVaIw98xc +f8LDmBxrThaA63p4ZUWiABqvDA1VZDRIuJK58bRQKfJPIx/abKwfROHdI3hRW8cW +-----END CERTIFICATE----- + +COMODO Certification Authority +============================== +-----BEGIN CERTIFICATE----- +MIIEHTCCAwWgAwIBAgIQToEtioJl4AsC7j41AkblPTANBgkqhkiG9w0BAQUFADCBgTELMAkGA1UE +BhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgG +A1UEChMRQ09NT0RPIENBIExpbWl0ZWQxJzAlBgNVBAMTHkNPTU9ETyBDZXJ0aWZpY2F0aW9uIEF1 +dGhvcml0eTAeFw0wNjEyMDEwMDAwMDBaFw0yOTEyMzEyMzU5NTlaMIGBMQswCQYDVQQGEwJHQjEb +MBkGA1UECBMSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHEwdTYWxmb3JkMRowGAYDVQQKExFD +T01PRE8gQ0EgTGltaXRlZDEnMCUGA1UEAxMeQ09NT0RPIENlcnRpZmljYXRpb24gQXV0aG9yaXR5 +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0ECLi3LjkRv3UcEbVASY06m/weaKXTuH ++7uIzg3jLz8GlvCiKVCZrts7oVewdFFxze1CkU1B/qnI2GqGd0S7WWaXUF601CxwRM/aN5VCaTww +xHGzUvAhTaHYujl8HJ6jJJ3ygxaYqhZ8Q5sVW7euNJH+1GImGEaaP+vB+fGQV+useg2L23IwambV +4EajcNxo2f8ESIl33rXp+2dtQem8Ob0y2WIC8bGoPW43nOIv4tOiJovGuFVDiOEjPqXSJDlqR6sA +1KGzqSX+DT+nHbrTUcELpNqsOO9VUCQFZUaTNE8tja3G1CEZ0o7KBWFxB3NH5YoZEr0ETc5OnKVI +rLsm9wIDAQABo4GOMIGLMB0GA1UdDgQWBBQLWOWLxkwVN6RAqTCpIb5HNlpW/zAOBgNVHQ8BAf8E +BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zBJBgNVHR8EQjBAMD6gPKA6hjhodHRwOi8vY3JsLmNvbW9k +b2NhLmNvbS9DT01PRE9DZXJ0aWZpY2F0aW9uQXV0aG9yaXR5LmNybDANBgkqhkiG9w0BAQUFAAOC +AQEAPpiem/Yb6dc5t3iuHXIYSdOH5EOC6z/JqvWote9VfCFSZfnVDeFs9D6Mk3ORLgLETgdxb8CP +OGEIqB6BCsAvIC9Bi5HcSEW88cbeunZrM8gALTFGTO3nnc+IlP8zwFboJIYmuNg4ON8qa90SzMc/ +RxdMosIGlgnW2/4/PEZB31jiVg88O8EckzXZOFKs7sjsLjBOlDW0JB9LeGna8gI4zJVSk/BwJVmc +IGfE7vmLV2H0knZ9P4SNVbfo5azV8fUZVqZa+5Acr5Pr5RzUZ5ddBA6+C4OmF4O5MBKgxTMVBbkN ++8cFduPYSo38NBejxiEovjBFMR7HeL5YYTisO+IBZQ== +-----END CERTIFICATE----- + +Network Solutions Certificate Authority +======================================= +-----BEGIN CERTIFICATE----- +MIID5jCCAs6gAwIBAgIQV8szb8JcFuZHFhfjkDFo4DANBgkqhkiG9w0BAQUFADBiMQswCQYDVQQG +EwJVUzEhMB8GA1UEChMYTmV0d29yayBTb2x1dGlvbnMgTC5MLkMuMTAwLgYDVQQDEydOZXR3b3Jr +IFNvbHV0aW9ucyBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwHhcNMDYxMjAxMDAwMDAwWhcNMjkxMjMx +MjM1OTU5WjBiMQswCQYDVQQGEwJVUzEhMB8GA1UEChMYTmV0d29yayBTb2x1dGlvbnMgTC5MLkMu +MTAwLgYDVQQDEydOZXR3b3JrIFNvbHV0aW9ucyBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwggEiMA0G +CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDkvH6SMG3G2I4rC7xGzuAnlt7e+foS0zwzc7MEL7xx +jOWftiJgPl9dzgn/ggwbmlFQGiaJ3dVhXRncEg8tCqJDXRfQNJIg6nPPOCwGJgl6cvf6UDL4wpPT +aaIjzkGxzOTVHzbRijr4jGPiFFlp7Q3Tf2vouAPlT2rlmGNpSAW+Lv8ztumXWWn4Zxmuk2GWRBXT +crA/vGp97Eh/jcOrqnErU2lBUzS1sLnFBgrEsEX1QV1uiUV7PTsmjHTC5dLRfbIR1PtYMiKagMnc +/Qzpf14Dl847ABSHJ3A4qY5usyd2mFHgBeMhqxrVhSI8KbWaFsWAqPS7azCPL0YCorEMIuDTAgMB +AAGjgZcwgZQwHQYDVR0OBBYEFCEwyfsA106Y2oeqKtCnLrFAMadMMA4GA1UdDwEB/wQEAwIBBjAP +BgNVHRMBAf8EBTADAQH/MFIGA1UdHwRLMEkwR6BFoEOGQWh0dHA6Ly9jcmwubmV0c29sc3NsLmNv +bS9OZXR3b3JrU29sdXRpb25zQ2VydGlmaWNhdGVBdXRob3JpdHkuY3JsMA0GCSqGSIb3DQEBBQUA +A4IBAQC7rkvnt1frf6ott3NHhWrB5KUd5Oc86fRZZXe1eltajSU24HqXLjjAV2CDmAaDn7l2em5Q +4LqILPxFzBiwmZVRDuwduIj/h1AcgsLj4DKAv6ALR8jDMe+ZZzKATxcheQxpXN5eNK4CtSbqUN9/ +GGUsyfJj4akH/nxxH2szJGoeBfcFaMBqEssuXmHLrijTfsK0ZpEmXzwuJF/LWA/rKOyvEZbz3Htv +wKeI8lN3s2Berq4o2jUsbzRF0ybh3uxbTydrFny9RAQYgrOJeRcQcT16ohZO9QHNpGxlaKFJdlxD +ydi8NmdspZS11My5vWo1ViHe2MPr+8ukYEywVaCge1ey +-----END CERTIFICATE----- + +WellsSecure Public Root Certificate Authority +============================================= +-----BEGIN CERTIFICATE----- +MIIEvTCCA6WgAwIBAgIBATANBgkqhkiG9w0BAQUFADCBhTELMAkGA1UEBhMCVVMxIDAeBgNVBAoM +F1dlbGxzIEZhcmdvIFdlbGxzU2VjdXJlMRwwGgYDVQQLDBNXZWxscyBGYXJnbyBCYW5rIE5BMTYw +NAYDVQQDDC1XZWxsc1NlY3VyZSBQdWJsaWMgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwHhcN +MDcxMjEzMTcwNzU0WhcNMjIxMjE0MDAwNzU0WjCBhTELMAkGA1UEBhMCVVMxIDAeBgNVBAoMF1dl +bGxzIEZhcmdvIFdlbGxzU2VjdXJlMRwwGgYDVQQLDBNXZWxscyBGYXJnbyBCYW5rIE5BMTYwNAYD +VQQDDC1XZWxsc1NlY3VyZSBQdWJsaWMgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwggEiMA0G +CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDub7S9eeKPCCGeOARBJe+rWxxTkqxtnt3CxC5FlAM1 +iGd0V+PfjLindo8796jE2yljDpFoNoqXjopxaAkH5OjUDk/41itMpBb570OYj7OeUt9tkTmPOL13 +i0Nj67eT/DBMHAGTthP796EfvyXhdDcsHqRePGj4S78NuR4uNuip5Kf4D8uCdXw1LSLWwr8L87T8 +bJVhHlfXBIEyg1J55oNjz7fLY4sR4r1e6/aN7ZVyKLSsEmLpSjPmgzKuBXWVvYSV2ypcm44uDLiB +K0HmOFafSZtsdvqKXfcBeYF8wYNABf5x/Qw/zE5gCQ5lRxAvAcAFP4/4s0HvWkJ+We/SlwxlAgMB +AAGjggE0MIIBMDAPBgNVHRMBAf8EBTADAQH/MDkGA1UdHwQyMDAwLqAsoCqGKGh0dHA6Ly9jcmwu +cGtpLndlbGxzZmFyZ28uY29tL3dzcHJjYS5jcmwwDgYDVR0PAQH/BAQDAgHGMB0GA1UdDgQWBBQm +lRkQ2eihl5H/3BnZtQQ+0nMKajCBsgYDVR0jBIGqMIGngBQmlRkQ2eihl5H/3BnZtQQ+0nMKaqGB +i6SBiDCBhTELMAkGA1UEBhMCVVMxIDAeBgNVBAoMF1dlbGxzIEZhcmdvIFdlbGxzU2VjdXJlMRww +GgYDVQQLDBNXZWxscyBGYXJnbyBCYW5rIE5BMTYwNAYDVQQDDC1XZWxsc1NlY3VyZSBQdWJsaWMg +Um9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHmCAQEwDQYJKoZIhvcNAQEFBQADggEBALkVsUSRzCPI +K0134/iaeycNzXK7mQDKfGYZUMbVmO2rvwNa5U3lHshPcZeG1eMd/ZDJPHV3V3p9+N701NX3leZ0 +bh08rnyd2wIDBSxxSyU+B+NemvVmFymIGjifz6pBA4SXa5M4esowRBskRDPQ5NHcKDj0E0M1NSlj +qHyita04pO2t/caaH/+Xc/77szWnk4bGdpEA5qxRFsQnMlzbc9qlk1eOPm01JghZ1edE13YgY+es +E2fDbbFwRnzVlhE9iW9dqKHrjQrawx0zbKPqZxmamX9LPYNRKh3KL4YMon4QLSvUFpULB6ouFJJJ +tylv2G0xffX8oRAHh84vWdw+WNs= +-----END CERTIFICATE----- + +COMODO ECC Certification Authority +================================== +-----BEGIN CERTIFICATE----- +MIICiTCCAg+gAwIBAgIQH0evqmIAcFBUTAGem2OZKjAKBggqhkjOPQQDAzCBhTELMAkGA1UEBhMC +R0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UE +ChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBFQ0MgQ2VydGlmaWNhdGlvbiBB +dXRob3JpdHkwHhcNMDgwMzA2MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBhTELMAkGA1UEBhMCR0Ix +GzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMR +Q09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBFQ0MgQ2VydGlmaWNhdGlvbiBBdXRo +b3JpdHkwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQDR3svdcmCFYX7deSRFtSrYpn1PlILBs5BAH+X +4QokPB0BBO490o0JlwzgdeT6+3eKKvUDYEs2ixYjFq0JcfRK9ChQtP6IHG4/bC8vCVlbpVsLM5ni +wz2J+Wos77LTBumjQjBAMB0GA1UdDgQWBBR1cacZSBm8nZ3qQUfflMRId5nTeTAOBgNVHQ8BAf8E +BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjEA7wNbeqy3eApyt4jf/7VG +FAkK+qDmfQjGGoe9GKhzvSbKYAydzpmfz1wPMOG+FDHqAjAU9JM8SaczepBGR7NjfRObTrdvGDeA +U/7dIOA1mjbRxwG55tzd8/8dLDoWV9mSOdY= +-----END CERTIFICATE----- + +IGC/A +===== +-----BEGIN CERTIFICATE----- +MIIEAjCCAuqgAwIBAgIFORFFEJQwDQYJKoZIhvcNAQEFBQAwgYUxCzAJBgNVBAYTAkZSMQ8wDQYD +VQQIEwZGcmFuY2UxDjAMBgNVBAcTBVBhcmlzMRAwDgYDVQQKEwdQTS9TR0ROMQ4wDAYDVQQLEwVE +Q1NTSTEOMAwGA1UEAxMFSUdDL0ExIzAhBgkqhkiG9w0BCQEWFGlnY2FAc2dkbi5wbS5nb3V2LmZy +MB4XDTAyMTIxMzE0MjkyM1oXDTIwMTAxNzE0MjkyMlowgYUxCzAJBgNVBAYTAkZSMQ8wDQYDVQQI +EwZGcmFuY2UxDjAMBgNVBAcTBVBhcmlzMRAwDgYDVQQKEwdQTS9TR0ROMQ4wDAYDVQQLEwVEQ1NT +STEOMAwGA1UEAxMFSUdDL0ExIzAhBgkqhkiG9w0BCQEWFGlnY2FAc2dkbi5wbS5nb3V2LmZyMIIB +IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsh/R0GLFMzvABIaIs9z4iPf930Pfeo2aSVz2 +TqrMHLmh6yeJ8kbpO0px1R2OLc/mratjUMdUC24SyZA2xtgv2pGqaMVy/hcKshd+ebUyiHDKcMCW +So7kVc0dJ5S/znIq7Fz5cyD+vfcuiWe4u0dzEvfRNWk68gq5rv9GQkaiv6GFGvm/5P9JhfejcIYy +HF2fYPepraX/z9E0+X1bF8bc1g4oa8Ld8fUzaJ1O/Id8NhLWo4DoQw1VYZTqZDdH6nfK0LJYBcNd +frGoRpAxVs5wKpayMLh35nnAvSk7/ZR3TL0gzUEl4C7HG7vupARB0l2tEmqKm0f7yd1GQOGdPDPQ +tQIDAQABo3cwdTAPBgNVHRMBAf8EBTADAQH/MAsGA1UdDwQEAwIBRjAVBgNVHSAEDjAMMAoGCCqB +egF5AQEBMB0GA1UdDgQWBBSjBS8YYFDCiQrdKyFP/45OqDAxNjAfBgNVHSMEGDAWgBSjBS8YYFDC +iQrdKyFP/45OqDAxNjANBgkqhkiG9w0BAQUFAAOCAQEABdwm2Pp3FURo/C9mOnTgXeQp/wYHE4RK +q89toB9RlPhJy3Q2FLwV3duJL92PoF189RLrn544pEfMs5bZvpwlqwN+Mw+VgQ39FuCIvjfwbF3Q +MZsyK10XZZOYYLxuj7GoPB7ZHPOpJkL5ZB3C55L29B5aqhlSXa/oovdgoPaN8In1buAKBQGVyYsg +Crpa/JosPL3Dt8ldeCUFP1YUmwza+zpI/pdpXsoQhvdOlgQITeywvl3cO45Pwf2aNjSaTFR+FwNI +lQgRHAdvhQh+XU3Endv7rs6y0bO4g2wdsrN58dhwmX7wEwLOXt1R0982gaEbeC9xs/FZTEYYKKuF +0mBWWg== +-----END CERTIFICATE----- + +Security Communication EV RootCA1 +================================= +-----BEGIN CERTIFICATE----- +MIIDfTCCAmWgAwIBAgIBADANBgkqhkiG9w0BAQUFADBgMQswCQYDVQQGEwJKUDElMCMGA1UEChMc +U0VDT00gVHJ1c3QgU3lzdGVtcyBDTy4sTFRELjEqMCgGA1UECxMhU2VjdXJpdHkgQ29tbXVuaWNh +dGlvbiBFViBSb290Q0ExMB4XDTA3MDYwNjAyMTIzMloXDTM3MDYwNjAyMTIzMlowYDELMAkGA1UE +BhMCSlAxJTAjBgNVBAoTHFNFQ09NIFRydXN0IFN5c3RlbXMgQ08uLExURC4xKjAoBgNVBAsTIVNl +Y3VyaXR5IENvbW11bmljYXRpb24gRVYgUm9vdENBMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC +AQoCggEBALx/7FebJOD+nLpCeamIivqA4PUHKUPqjgo0No0c+qe1OXj/l3X3L+SqawSERMqm4miO +/VVQYg+kcQ7OBzgtQoVQrTyWb4vVog7P3kmJPdZkLjjlHmy1V4qe70gOzXppFodEtZDkBp2uoQSX +WHnvIEqCa4wiv+wfD+mEce3xDuS4GBPMVjZd0ZoeUWs5bmB2iDQL87PRsJ3KYeJkHcFGB7hj3R4z +ZbOOCVVSPbW9/wfrrWFVGCypaZhKqkDFMxRldAD5kd6vA0jFQFTcD4SQaCDFkpbcLuUCRarAX1T4 +bepJz11sS6/vmsJWXMY1VkJqMF/Cq/biPT+zyRGPMUzXn0kCAwEAAaNCMEAwHQYDVR0OBBYEFDVK +9U2vP9eCOKyrcWUXdYydVZPmMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MA0GCSqG +SIb3DQEBBQUAA4IBAQCoh+ns+EBnXcPBZsdAS5f8hxOQWsTvoMpfi7ent/HWtWS3irO4G8za+6xm +iEHO6Pzk2x6Ipu0nUBsCMCRGef4Eh3CXQHPRwMFXGZpppSeZq51ihPZRwSzJIxXYKLerJRO1RuGG +Av8mjMSIkh1W/hln8lXkgKNrnKt34VFxDSDbEJrbvXZ5B3eZKK2aXtqxT0QsNY6llsf9g/BYxnnW +mHyojf6GPgcWkuF75x3sM3Z+Qi5KhfmRiWiEA4Glm5q+4zfFVKtWOxgtQaQM+ELbmaDgcm+7XeEW +T1MKZPlO9L9OVL14bIjqv5wTJMJwaaJ/D8g8rQjJsJhAoyrniIPtd490 +-----END CERTIFICATE----- + +OISTE WISeKey Global Root GA CA +=============================== +-----BEGIN CERTIFICATE----- +MIID8TCCAtmgAwIBAgIQQT1yx/RrH4FDffHSKFTfmjANBgkqhkiG9w0BAQUFADCBijELMAkGA1UE +BhMCQ0gxEDAOBgNVBAoTB1dJU2VLZXkxGzAZBgNVBAsTEkNvcHlyaWdodCAoYykgMjAwNTEiMCAG +A1UECxMZT0lTVEUgRm91bmRhdGlvbiBFbmRvcnNlZDEoMCYGA1UEAxMfT0lTVEUgV0lTZUtleSBH +bG9iYWwgUm9vdCBHQSBDQTAeFw0wNTEyMTExNjAzNDRaFw0zNzEyMTExNjA5NTFaMIGKMQswCQYD +VQQGEwJDSDEQMA4GA1UEChMHV0lTZUtleTEbMBkGA1UECxMSQ29weXJpZ2h0IChjKSAyMDA1MSIw +IAYDVQQLExlPSVNURSBGb3VuZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBXSVNlS2V5 +IEdsb2JhbCBSb290IEdBIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAy0+zAJs9 +Nt350UlqaxBJH+zYK7LG+DKBKUOVTJoZIyEVRd7jyBxRVVuuk+g3/ytr6dTqvirdqFEr12bDYVxg +Asj1znJ7O7jyTmUIms2kahnBAbtzptf2w93NvKSLtZlhuAGio9RN1AU9ka34tAhxZK9w8RxrfvbD +d50kc3vkDIzh2TbhmYsFmQvtRTEJysIA2/dyoJaqlYfQjse2YXMNdmaM3Bu0Y6Kff5MTMPGhJ9vZ +/yxViJGg4E8HsChWjBgbl0SOid3gF27nKu+POQoxhILYQBRJLnpB5Kf+42TMwVlxSywhp1t94B3R +LoGbw9ho972WG6xwsRYUC9tguSYBBQIDAQABo1EwTzALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUw +AwEB/zAdBgNVHQ4EFgQUswN+rja8sHnR3JQmthG+IbJphpQwEAYJKwYBBAGCNxUBBAMCAQAwDQYJ +KoZIhvcNAQEFBQADggEBAEuh/wuHbrP5wUOxSPMowB0uyQlB+pQAHKSkq0lPjz0e701vvbyk9vIm +MMkQyh2I+3QZH4VFvbBsUfk2ftv1TDI6QU9bR8/oCy22xBmddMVHxjtqD6wU2zz0c5ypBd8A3HR4 ++vg1YFkCExh8vPtNsCBtQ7tgMHpnM1zFmdH4LTlSc/uMqpclXHLZCB6rTjzjgTGfA6b7wP4piFXa +hNVQA7bihKOmNqoROgHhGEvWRGizPflTdISzRpFGlgC3gCy24eMQ4tui5yiPAZZiFj4A4xylNoEY +okxSdsARo27mHbrjWr42U8U+dY+GaSlYU7Wcu2+fXMUY7N0v4ZjJ/L7fCg0= +-----END CERTIFICATE----- + +Microsec e-Szigno Root CA +========================= +-----BEGIN CERTIFICATE----- +MIIHqDCCBpCgAwIBAgIRAMy4579OKRr9otxmpRwsDxEwDQYJKoZIhvcNAQEFBQAwcjELMAkGA1UE +BhMCSFUxETAPBgNVBAcTCEJ1ZGFwZXN0MRYwFAYDVQQKEw1NaWNyb3NlYyBMdGQuMRQwEgYDVQQL +EwtlLVN6aWdubyBDQTEiMCAGA1UEAxMZTWljcm9zZWMgZS1Temlnbm8gUm9vdCBDQTAeFw0wNTA0 +MDYxMjI4NDRaFw0xNzA0MDYxMjI4NDRaMHIxCzAJBgNVBAYTAkhVMREwDwYDVQQHEwhCdWRhcGVz +dDEWMBQGA1UEChMNTWljcm9zZWMgTHRkLjEUMBIGA1UECxMLZS1Temlnbm8gQ0ExIjAgBgNVBAMT +GU1pY3Jvc2VjIGUtU3ppZ25vIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB +AQDtyADVgXvNOABHzNuEwSFpLHSQDCHZU4ftPkNEU6+r+ICbPHiN1I2uuO/TEdyB5s87lozWbxXG +d36hL+BfkrYn13aaHUM86tnsL+4582pnS4uCzyL4ZVX+LMsvfUh6PXX5qqAnu3jCBspRwn5mS6/N +oqdNAoI/gqyFxuEPkEeZlApxcpMqyabAvjxWTHOSJ/FrtfX9/DAFYJLG65Z+AZHCabEeHXtTRbjc +QR/Ji3HWVBTji1R4P770Yjtb9aPs1ZJ04nQw7wHb4dSrmZsqa/i9phyGI0Jf7Enemotb9HI6QMVJ +PqW+jqpx62z69Rrkav17fVVA71hu5tnVvCSrwe+3AgMBAAGjggQ3MIIEMzBnBggrBgEFBQcBAQRb +MFkwKAYIKwYBBQUHMAGGHGh0dHBzOi8vcmNhLmUtc3ppZ25vLmh1L29jc3AwLQYIKwYBBQUHMAKG +IWh0dHA6Ly93d3cuZS1zemlnbm8uaHUvUm9vdENBLmNydDAPBgNVHRMBAf8EBTADAQH/MIIBcwYD +VR0gBIIBajCCAWYwggFiBgwrBgEEAYGoGAIBAQEwggFQMCgGCCsGAQUFBwIBFhxodHRwOi8vd3d3 +LmUtc3ppZ25vLmh1L1NaU1ovMIIBIgYIKwYBBQUHAgIwggEUHoIBEABBACAAdABhAG4A+gBzAO0A +dAB2AOEAbgB5ACAA6QByAHQAZQBsAG0AZQB6AOkAcwDpAGgAZQB6ACAA6QBzACAAZQBsAGYAbwBn +AGEAZADhAHMA4QBoAG8AegAgAGEAIABTAHoAbwBsAGcA4QBsAHQAYQB0APMAIABTAHoAbwBsAGcA +4QBsAHQAYQB0AOEAcwBpACAAUwB6AGEAYgDhAGwAeQB6AGEAdABhACAAcwB6AGUAcgBpAG4AdAAg +AGsAZQBsAGwAIABlAGwAagDhAHIAbgBpADoAIABoAHQAdABwADoALwAvAHcAdwB3AC4AZQAtAHMA +egBpAGcAbgBvAC4AaAB1AC8AUwBaAFMAWgAvMIHIBgNVHR8EgcAwgb0wgbqggbeggbSGIWh0dHA6 +Ly93d3cuZS1zemlnbm8uaHUvUm9vdENBLmNybIaBjmxkYXA6Ly9sZGFwLmUtc3ppZ25vLmh1L0NO +PU1pY3Jvc2VjJTIwZS1Temlnbm8lMjBSb290JTIwQ0EsT1U9ZS1Temlnbm8lMjBDQSxPPU1pY3Jv +c2VjJTIwTHRkLixMPUJ1ZGFwZXN0LEM9SFU/Y2VydGlmaWNhdGVSZXZvY2F0aW9uTGlzdDtiaW5h +cnkwDgYDVR0PAQH/BAQDAgEGMIGWBgNVHREEgY4wgYuBEGluZm9AZS1zemlnbm8uaHWkdzB1MSMw +IQYDVQQDDBpNaWNyb3NlYyBlLVN6aWduw7MgUm9vdCBDQTEWMBQGA1UECwwNZS1TemlnbsOzIEhT +WjEWMBQGA1UEChMNTWljcm9zZWMgS2Z0LjERMA8GA1UEBxMIQnVkYXBlc3QxCzAJBgNVBAYTAkhV +MIGsBgNVHSMEgaQwgaGAFMegSXUWYYTbMUuE0vE3QJDvTtz3oXakdDByMQswCQYDVQQGEwJIVTER +MA8GA1UEBxMIQnVkYXBlc3QxFjAUBgNVBAoTDU1pY3Jvc2VjIEx0ZC4xFDASBgNVBAsTC2UtU3pp +Z25vIENBMSIwIAYDVQQDExlNaWNyb3NlYyBlLVN6aWdubyBSb290IENBghEAzLjnv04pGv2i3Gal +HCwPETAdBgNVHQ4EFgQUx6BJdRZhhNsxS4TS8TdAkO9O3PcwDQYJKoZIhvcNAQEFBQADggEBANMT +nGZjWS7KXHAM/IO8VbH0jgdsZifOwTsgqRy7RlRw7lrMoHfqaEQn6/Ip3Xep1fvj1KcExJW4C+FE +aGAHQzAxQmHl7tnlJNUb3+FKG6qfx1/4ehHqE5MAyopYse7tDk2016g2JnzgOsHVV4Lxdbb9iV/a +86g4nzUGCM4ilb7N1fy+W955a9x6qWVmvrElWl/tftOsRm1M9DKHtCAE4Gx4sHfRhUZLphK3dehK +yVZs15KrnfVJONJPU+NVkBHbmJbGSfI+9J8b4PeI3CVimUTYc78/MPMMNz7UwiiAc7EBt51alhQB +S6kRnSlqLtBdgcDPsiBDxwPgN05dCtxZICU= +-----END CERTIFICATE----- + +Certigna +======== +-----BEGIN CERTIFICATE----- +MIIDqDCCApCgAwIBAgIJAP7c4wEPyUj/MA0GCSqGSIb3DQEBBQUAMDQxCzAJBgNVBAYTAkZSMRIw +EAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25hMB4XDTA3MDYyOTE1MTMwNVoXDTI3 +MDYyOTE1MTMwNVowNDELMAkGA1UEBhMCRlIxEjAQBgNVBAoMCURoaW15b3RpczERMA8GA1UEAwwI +Q2VydGlnbmEwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDIaPHJ1tazNHUmgh7stL7q +XOEm7RFHYeGifBZ4QCHkYJ5ayGPhxLGWkv8YbWkj4Sti993iNi+RB7lIzw7sebYs5zRLcAglozyH +GxnygQcPOJAZ0xH+hrTy0V4eHpbNgGzOOzGTtvKg0KmVEn2lmsxryIRWijOp5yIVUxbwzBfsV1/p +ogqYCd7jX5xv3EjjhQsVWqa6n6xI4wmy9/Qy3l40vhx4XUJbzg4ij02Q130yGLMLLGq/jj8UEYkg +DncUtT2UCIf3JR7VsmAA7G8qKCVuKj4YYxclPz5EIBb2JsglrgVKtOdjLPOMFlN+XPsRGgjBRmKf +Irjxwo1p3Po6WAbfAgMBAAGjgbwwgbkwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUGu3+QTmQ +tCRZvgHyUtVF9lo53BEwZAYDVR0jBF0wW4AUGu3+QTmQtCRZvgHyUtVF9lo53BGhOKQ2MDQxCzAJ +BgNVBAYTAkZSMRIwEAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25hggkA/tzjAQ/J +SP8wDgYDVR0PAQH/BAQDAgEGMBEGCWCGSAGG+EIBAQQEAwIABzANBgkqhkiG9w0BAQUFAAOCAQEA +hQMeknH2Qq/ho2Ge6/PAD/Kl1NqV5ta+aDY9fm4fTIrv0Q8hbV6lUmPOEvjvKtpv6zf+EwLHyzs+ +ImvaYS5/1HI93TDhHkxAGYwP15zRgzB7mFncfca5DClMoTOi62c6ZYTTluLtdkVwj7Ur3vkj1klu +PBS1xp81HlDQwY9qcEQCYsuuHWhBp6pX6FOqB9IG9tUUBguRA3UsbHK1YZWaDYu5Def131TN3ubY +1gkIl2PlwS6wt0QmwCbAr1UwnjvVNioZBPRcHv/PLLf/0P2HQBHVESO7SMAhqaQoLf0V+LBOK/Qw +WyH8EZE0vkHve52Xdf+XlcCWWC/qu0bXu+TZLg== +-----END CERTIFICATE----- + +AC Ra\xC3\xADz Certic\xC3\xA1mara S.A. +====================================== +-----BEGIN CERTIFICATE----- +MIIGZjCCBE6gAwIBAgIPB35Sk3vgFeNX8GmMy+wMMA0GCSqGSIb3DQEBBQUAMHsxCzAJBgNVBAYT +AkNPMUcwRQYDVQQKDD5Tb2NpZWRhZCBDYW1lcmFsIGRlIENlcnRpZmljYWNpw7NuIERpZ2l0YWwg +LSBDZXJ0aWPDoW1hcmEgUy5BLjEjMCEGA1UEAwwaQUMgUmHDrXogQ2VydGljw6FtYXJhIFMuQS4w +HhcNMDYxMTI3MjA0NjI5WhcNMzAwNDAyMjE0MjAyWjB7MQswCQYDVQQGEwJDTzFHMEUGA1UECgw+ +U29jaWVkYWQgQ2FtZXJhbCBkZSBDZXJ0aWZpY2FjacOzbiBEaWdpdGFsIC0gQ2VydGljw6FtYXJh +IFMuQS4xIzAhBgNVBAMMGkFDIFJhw616IENlcnRpY8OhbWFyYSBTLkEuMIICIjANBgkqhkiG9w0B +AQEFAAOCAg8AMIICCgKCAgEAq2uJo1PMSCMI+8PPUZYILrgIem08kBeGqentLhM0R7LQcNzJPNCN +yu5LF6vQhbCnIwTLqKL85XXbQMpiiY9QngE9JlsYhBzLfDe3fezTf3MZsGqy2IiKLUV0qPezuMDU +2s0iiXRNWhU5cxh0T7XrmafBHoi0wpOQY5fzp6cSsgkiBzPZkc0OnB8OIMfuuzONj8LSWKdf/WU3 +4ojC2I+GdV75LaeHM/J4Ny+LvB2GNzmxlPLYvEqcgxhaBvzz1NS6jBUJJfD5to0EfhcSM2tXSExP +2yYe68yQ54v5aHxwD6Mq0Do43zeX4lvegGHTgNiRg0JaTASJaBE8rF9ogEHMYELODVoqDA+bMMCm +8Ibbq0nXl21Ii/kDwFJnmxL3wvIumGVC2daa49AZMQyth9VXAnow6IYm+48jilSH5L887uvDdUhf +HjlvgWJsxS3EF1QZtzeNnDeRyPYL1epjb4OsOMLzP96a++EjYfDIJss2yKHzMI+ko6Kh3VOz3vCa +Mh+DkXkwwakfU5tTohVTP92dsxA7SH2JD/ztA/X7JWR1DhcZDY8AFmd5ekD8LVkH2ZD6mq093ICK +5lw1omdMEWux+IBkAC1vImHFrEsm5VoQgpukg3s0956JkSCXjrdCx2bD0Omk1vUgjcTDlaxECp1b +czwmPS9KvqfJpxAe+59QafMCAwEAAaOB5jCB4zAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQE +AwIBBjAdBgNVHQ4EFgQU0QnQ6dfOeXRU+Tows/RtLAMDG2gwgaAGA1UdIASBmDCBlTCBkgYEVR0g +ADCBiTArBggrBgEFBQcCARYfaHR0cDovL3d3dy5jZXJ0aWNhbWFyYS5jb20vZHBjLzBaBggrBgEF +BQcCAjBOGkxMaW1pdGFjaW9uZXMgZGUgZ2FyYW507WFzIGRlIGVzdGUgY2VydGlmaWNhZG8gc2Ug +cHVlZGVuIGVuY29udHJhciBlbiBsYSBEUEMuMA0GCSqGSIb3DQEBBQUAA4ICAQBclLW4RZFNjmEf +AygPU3zmpFmps4p6xbD/CHwso3EcIRNnoZUSQDWDg4902zNc8El2CoFS3UnUmjIz75uny3XlesuX +EpBcunvFm9+7OSPI/5jOCk0iAUgHforA1SBClETvv3eiiWdIG0ADBaGJ7M9i4z0ldma/Jre7Ir5v +/zlXdLp6yQGVwZVR6Kss+LGGIOk/yzVb0hfpKv6DExdA7ohiZVvVO2Dpezy4ydV/NgIlqmjCMRW3 +MGXrfx1IebHPOeJCgBbT9ZMj/EyXyVo3bHwi2ErN0o42gzmRkBDI8ck1fj+404HGIGQatlDCIaR4 +3NAvO2STdPCWkPHv+wlaNECW8DYSwaN0jJN+Qd53i+yG2dIPPy3RzECiiWZIHiCznCNZc6lEc7wk +eZBWN7PGKX6jD/EpOe9+XCgycDWs2rjIdWb8m0w5R44bb5tNAlQiM+9hup4phO9OSzNHdpdqy35f +/RWmnkJDW2ZaiogN9xa5P1FlK2Zqi9E4UqLWRhH6/JocdJ6PlwsCT2TG9WjTSy3/pDceiz+/RL5h +RqGEPQgnTIEgd4kI6mdAXmwIUV80WoyWaM3X94nCHNMyAK9Sy9NgWyo6R35rMDOhYil/SrnhLecU +Iw4OGEfhefwVVdCx/CVxY3UzHCMrr1zZ7Ud3YA47Dx7SwNxkBYn8eNZcLCZDqQ== +-----END CERTIFICATE----- + +TC TrustCenter Class 2 CA II +============================ +-----BEGIN CERTIFICATE----- +MIIEqjCCA5KgAwIBAgIOLmoAAQACH9dSISwRXDswDQYJKoZIhvcNAQEFBQAwdjELMAkGA1UEBhMC +REUxHDAaBgNVBAoTE1RDIFRydXN0Q2VudGVyIEdtYkgxIjAgBgNVBAsTGVRDIFRydXN0Q2VudGVy +IENsYXNzIDIgQ0ExJTAjBgNVBAMTHFRDIFRydXN0Q2VudGVyIENsYXNzIDIgQ0EgSUkwHhcNMDYw +MTEyMTQzODQzWhcNMjUxMjMxMjI1OTU5WjB2MQswCQYDVQQGEwJERTEcMBoGA1UEChMTVEMgVHJ1 +c3RDZW50ZXIgR21iSDEiMCAGA1UECxMZVEMgVHJ1c3RDZW50ZXIgQ2xhc3MgMiBDQTElMCMGA1UE +AxMcVEMgVHJ1c3RDZW50ZXIgQ2xhc3MgMiBDQSBJSTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC +AQoCggEBAKuAh5uO8MN8h9foJIIRszzdQ2Lu+MNF2ujhoF/RKrLqk2jftMjWQ+nEdVl//OEd+DFw +IxuInie5e/060smp6RQvkL4DUsFJzfb95AhmC1eKokKguNV/aVyQMrKXDcpK3EY+AlWJU+MaWss2 +xgdW94zPEfRMuzBwBJWl9jmM/XOBCH2JXjIeIqkiRUuwZi4wzJ9l/fzLganx4Duvo4bRierERXlQ +Xa7pIXSSTYtZgo+U4+lK8edJsBTj9WLL1XK9H7nSn6DNqPoByNkN39r8R52zyFTfSUrxIan+GE7u +SNQZu+995OKdy1u2bv/jzVrndIIFuoAlOMvkaZ6vQaoahPUCAwEAAaOCATQwggEwMA8GA1UdEwEB +/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBTjq1RMgKHbVkO3kUrL84J6E1wIqzCB +7QYDVR0fBIHlMIHiMIHfoIHcoIHZhjVodHRwOi8vd3d3LnRydXN0Y2VudGVyLmRlL2NybC92Mi90 +Y19jbGFzc18yX2NhX0lJLmNybIaBn2xkYXA6Ly93d3cudHJ1c3RjZW50ZXIuZGUvQ049VEMlMjBU +cnVzdENlbnRlciUyMENsYXNzJTIwMiUyMENBJTIwSUksTz1UQyUyMFRydXN0Q2VudGVyJTIwR21i +SCxPVT1yb290Y2VydHMsREM9dHJ1c3RjZW50ZXIsREM9ZGU/Y2VydGlmaWNhdGVSZXZvY2F0aW9u +TGlzdD9iYXNlPzANBgkqhkiG9w0BAQUFAAOCAQEAjNfffu4bgBCzg/XbEeprS6iSGNn3Bzn1LL4G +dXpoUxUc6krtXvwjshOg0wn/9vYua0Fxec3ibf2uWWuFHbhOIprtZjluS5TmVfwLG4t3wVMTZonZ +KNaL80VKY7f9ewthXbhtvsPcW3nS7Yblok2+XnR8au0WOB9/WIFaGusyiC2y8zl3gK9etmF1Kdsj +TYjKUCjLhdLTEKJZbtOTVAB6okaVhgWcqRmY5TFyDADiZ9lA4CQze28suVyrZZ0srHbqNZn1l7kP +JOzHdiEoZa5X6AeIdUpWoNIFOqTmjZKILPPy4cHGYdtBxceb9w4aUUXCYWvcZCcXjFq32nQozZfk +vQ== +-----END CERTIFICATE----- + +TC TrustCenter Class 3 CA II +============================ +-----BEGIN CERTIFICATE----- +MIIEqjCCA5KgAwIBAgIOSkcAAQAC5aBd1j8AUb8wDQYJKoZIhvcNAQEFBQAwdjELMAkGA1UEBhMC +REUxHDAaBgNVBAoTE1RDIFRydXN0Q2VudGVyIEdtYkgxIjAgBgNVBAsTGVRDIFRydXN0Q2VudGVy +IENsYXNzIDMgQ0ExJTAjBgNVBAMTHFRDIFRydXN0Q2VudGVyIENsYXNzIDMgQ0EgSUkwHhcNMDYw +MTEyMTQ0MTU3WhcNMjUxMjMxMjI1OTU5WjB2MQswCQYDVQQGEwJERTEcMBoGA1UEChMTVEMgVHJ1 +c3RDZW50ZXIgR21iSDEiMCAGA1UECxMZVEMgVHJ1c3RDZW50ZXIgQ2xhc3MgMyBDQTElMCMGA1UE +AxMcVEMgVHJ1c3RDZW50ZXIgQ2xhc3MgMyBDQSBJSTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC +AQoCggEBALTgu1G7OVyLBMVMeRwjhjEQY0NVJz/GRcekPewJDRoeIMJWHt4bNwcwIi9v8Qbxq63W +yKthoy9DxLCyLfzDlml7forkzMA5EpBCYMnMNWju2l+QVl/NHE1bWEnrDgFPZPosPIlY2C8u4rBo +6SI7dYnWRBpl8huXJh0obazovVkdKyT21oQDZogkAHhg8fir/gKya/si+zXmFtGt9i4S5Po1auUZ +uV3bOx4a+9P/FRQI2AlqukWdFHlgfa9Aigdzs5OW03Q0jTo3Kd5c7PXuLjHCINy+8U9/I1LZW+Jk +2ZyqBwi1Rb3R0DHBq1SfqdLDYmAD8bs5SpJKPQq5ncWg/jcCAwEAAaOCATQwggEwMA8GA1UdEwEB +/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBTUovyfs8PYA9NXXAek0CSnwPIA1DCB +7QYDVR0fBIHlMIHiMIHfoIHcoIHZhjVodHRwOi8vd3d3LnRydXN0Y2VudGVyLmRlL2NybC92Mi90 +Y19jbGFzc18zX2NhX0lJLmNybIaBn2xkYXA6Ly93d3cudHJ1c3RjZW50ZXIuZGUvQ049VEMlMjBU +cnVzdENlbnRlciUyMENsYXNzJTIwMyUyMENBJTIwSUksTz1UQyUyMFRydXN0Q2VudGVyJTIwR21i +SCxPVT1yb290Y2VydHMsREM9dHJ1c3RjZW50ZXIsREM9ZGU/Y2VydGlmaWNhdGVSZXZvY2F0aW9u +TGlzdD9iYXNlPzANBgkqhkiG9w0BAQUFAAOCAQEANmDkcPcGIEPZIxpC8vijsrlNirTzwppVMXzE +O2eatN9NDoqTSheLG43KieHPOh6sHfGcMrSOWXaiQYUlN6AT0PV8TtXqluJucsG7Kv5sbviRmEb8 +yRtXW+rIGjs/sFGYPAfaLFkB2otE6OF0/ado3VS6g0bsyEa1+K+XwDsJHI/OcpY9M1ZwvJbL2NV9 +IJqDnxrcOfHFcqMRA/07QlIp2+gB95tejNaNhk4Z+rwcvsUhpYeeeC422wlxo3I0+GzjBgnyXlal +092Y+tTmBvTwtiBjS+opvaqCZh77gaqnN60TGOaSw4HBM7uIHqHn4rS9MWwOUT1v+5ZWgOI2F9Hc +5A== +-----END CERTIFICATE----- + +TC TrustCenter Universal CA I +============================= +-----BEGIN CERTIFICATE----- +MIID3TCCAsWgAwIBAgIOHaIAAQAC7LdggHiNtgYwDQYJKoZIhvcNAQEFBQAweTELMAkGA1UEBhMC +REUxHDAaBgNVBAoTE1RDIFRydXN0Q2VudGVyIEdtYkgxJDAiBgNVBAsTG1RDIFRydXN0Q2VudGVy +IFVuaXZlcnNhbCBDQTEmMCQGA1UEAxMdVEMgVHJ1c3RDZW50ZXIgVW5pdmVyc2FsIENBIEkwHhcN +MDYwMzIyMTU1NDI4WhcNMjUxMjMxMjI1OTU5WjB5MQswCQYDVQQGEwJERTEcMBoGA1UEChMTVEMg +VHJ1c3RDZW50ZXIgR21iSDEkMCIGA1UECxMbVEMgVHJ1c3RDZW50ZXIgVW5pdmVyc2FsIENBMSYw +JAYDVQQDEx1UQyBUcnVzdENlbnRlciBVbml2ZXJzYWwgQ0EgSTCCASIwDQYJKoZIhvcNAQEBBQAD +ggEPADCCAQoCggEBAKR3I5ZEr5D0MacQ9CaHnPM42Q9e3s9B6DGtxnSRJJZ4Hgmgm5qVSkr1YnwC +qMqs+1oEdjneX/H5s7/zA1hV0qq34wQi0fiU2iIIAI3TfCZdzHd55yx4Oagmcw6iXSVphU9VDprv +xrlE4Vc93x9UIuVvZaozhDrzznq+VZeujRIPFDPiUHDDSYcTvFHe15gSWu86gzOSBnWLknwSaHtw +ag+1m7Z3W0hZneTvWq3zwZ7U10VOylY0Ibw+F1tvdwxIAUMpsN0/lm7mlaoMwCC2/T42J5zjXM9O +gdwZu5GQfezmlwQek8wiSdeXhrYTCjxDI3d+8NzmzSQfO4ObNDqDNOMCAwEAAaNjMGEwHwYDVR0j +BBgwFoAUkqR1LKSevoFE63n8isWVpesQdXMwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC +AYYwHQYDVR0OBBYEFJKkdSyknr6BROt5/IrFlaXrEHVzMA0GCSqGSIb3DQEBBQUAA4IBAQAo0uCG +1eb4e/CX3CJrO5UUVg8RMKWaTzqwOuAGy2X17caXJ/4l8lfmXpWMPmRgFVp/Lw0BxbFg/UU1z/Cy +vwbZ71q+s2IhtNerNXxTPqYn8aEt2hojnczd7Dwtnic0XQ/CNnm8yUpiLe1r2X1BQ3y2qsrtYbE3 +ghUJGooWMNjsydZHcnhLEEYUjl8Or+zHL6sQ17bxbuyGssLoDZJz3KL0Dzq/YSMQiZxIQG5wALPT +ujdEWBF6AmqI8Dc08BnprNRlc/ZpjGSUOnmFKbAWKwyCPwacx/0QK54PLLae4xW/2TYcuiUaUj0a +7CIMHOCkoj3w6DnPgcB77V0fb8XQC9eY +-----END CERTIFICATE----- + +Deutsche Telekom Root CA 2 +========================== +-----BEGIN CERTIFICATE----- +MIIDnzCCAoegAwIBAgIBJjANBgkqhkiG9w0BAQUFADBxMQswCQYDVQQGEwJERTEcMBoGA1UEChMT +RGV1dHNjaGUgVGVsZWtvbSBBRzEfMB0GA1UECxMWVC1UZWxlU2VjIFRydXN0IENlbnRlcjEjMCEG +A1UEAxMaRGV1dHNjaGUgVGVsZWtvbSBSb290IENBIDIwHhcNOTkwNzA5MTIxMTAwWhcNMTkwNzA5 +MjM1OTAwWjBxMQswCQYDVQQGEwJERTEcMBoGA1UEChMTRGV1dHNjaGUgVGVsZWtvbSBBRzEfMB0G +A1UECxMWVC1UZWxlU2VjIFRydXN0IENlbnRlcjEjMCEGA1UEAxMaRGV1dHNjaGUgVGVsZWtvbSBS +b290IENBIDIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCrC6M14IspFLEUha88EOQ5 +bzVdSq7d6mGNlUn0b2SjGmBmpKlAIoTZ1KXleJMOaAGtuU1cOs7TuKhCQN/Po7qCWWqSG6wcmtoI +KyUn+WkjR/Hg6yx6m/UTAtB+NHzCnjwAWav12gz1MjwrrFDa1sPeg5TKqAyZMg4ISFZbavva4VhY +AUlfckE8FQYBjl2tqriTtM2e66foai1SNNs671x1Udrb8zH57nGYMsRUFUQM+ZtV7a3fGAigo4aK +Se5TBY8ZTNXeWHmb0mocQqvF1afPaA+W5OFhmHZhyJF81j4A4pFQh+GdCuatl9Idxjp9y7zaAzTV +jlsB9WoHtxa2bkp/AgMBAAGjQjBAMB0GA1UdDgQWBBQxw3kbuvVT1xfgiXotF2wKsyudMzAPBgNV +HRMECDAGAQH/AgEFMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQUFAAOCAQEAlGRZrTlk5ynr +E/5aw4sTV8gEJPB0d8Bg42f76Ymmg7+Wgnxu1MM9756AbrsptJh6sTtU6zkXR34ajgv8HzFZMQSy +zhfzLMdiNlXiItiJVbSYSKpk+tYcNthEeFpaIzpXl/V6ME+un2pMSyuOoAPjPuCp1NJ70rOo4nI8 +rZ7/gFnkm0W09juwzTkZmDLl6iFhkOQxIY40sfcvNUqFENrnijchvllj4PKFiDFT1FQUhXB59C4G +dyd1Lx+4ivn+xbrYNuSD7Odlt79jWvNGr4GUN9RBjNYj1h7P9WgbRGOiWrqnNVmh5XAFmw4jV5mU +Cm26OWMohpLzGITY+9HPBVZkVw== +-----END CERTIFICATE----- + +ComSign Secured CA +================== +-----BEGIN CERTIFICATE----- +MIIDqzCCApOgAwIBAgIRAMcoRwmzuGxFjB36JPU2TukwDQYJKoZIhvcNAQEFBQAwPDEbMBkGA1UE +AxMSQ29tU2lnbiBTZWN1cmVkIENBMRAwDgYDVQQKEwdDb21TaWduMQswCQYDVQQGEwJJTDAeFw0w +NDAzMjQxMTM3MjBaFw0yOTAzMTYxNTA0NTZaMDwxGzAZBgNVBAMTEkNvbVNpZ24gU2VjdXJlZCBD +QTEQMA4GA1UEChMHQ29tU2lnbjELMAkGA1UEBhMCSUwwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw +ggEKAoIBAQDGtWhfHZQVw6QIVS3joFd67+l0Kru5fFdJGhFeTymHDEjWaueP1H5XJLkGieQcPOqs +49ohgHMhCu95mGwfCP+hUH3ymBvJVG8+pSjsIQQPRbsHPaHA+iqYHU4Gk/v1iDurX8sWv+bznkqH +7Rnqwp9D5PGBpX8QTz7RSmKtUxvLg/8HZaWSLWapW7ha9B20IZFKF3ueMv5WJDmyVIRD9YTC2LxB +kMyd1mja6YJQqTtoz7VdApRgFrFD2UNd3V2Hbuq7s8lr9gOUCXDeFhF6K+h2j0kQmHe5Y1yLM5d1 +9guMsqtb3nQgJT/j8xH5h2iGNXHDHYwt6+UarA9z1YJZQIDTAgMBAAGjgacwgaQwDAYDVR0TBAUw +AwEB/zBEBgNVHR8EPTA7MDmgN6A1hjNodHRwOi8vZmVkaXIuY29tc2lnbi5jby5pbC9jcmwvQ29t +U2lnblNlY3VyZWRDQS5jcmwwDgYDVR0PAQH/BAQDAgGGMB8GA1UdIwQYMBaAFMFL7XC29z58ADsA +j8c+DkWfHl3sMB0GA1UdDgQWBBTBS+1wtvc+fAA7AI/HPg5Fnx5d7DANBgkqhkiG9w0BAQUFAAOC +AQEAFs/ukhNQq3sUnjO2QiBq1BW9Cav8cujvR3qQrFHBZE7piL1DRYHjZiM/EoZNGeQFsOY3wo3a +BijJD4mkU6l1P7CW+6tMM1X5eCZGbxs2mPtCdsGCuY7e+0X5YxtiOzkGynd6qDwJz2w2PQ8KRUtp +FhpFfTMDZflScZAmlaxMDPWLkz/MdXSFmLr/YnpNH4n+rr2UAJm/EaXc4HnFFgt9AmEd6oX5AhVP +51qJThRv4zdLhfXBPGHg/QVBspJ/wx2g0K5SZGBrGMYmnNj1ZOQ2GmKfig8+/21OGVZOIJFsnzQz +OjRXUDpvgV4GxvU+fE6OK85lBi5d0ipTdF7Tbieejw== +-----END CERTIFICATE----- + +Cybertrust Global Root +====================== +-----BEGIN CERTIFICATE----- +MIIDoTCCAomgAwIBAgILBAAAAAABD4WqLUgwDQYJKoZIhvcNAQEFBQAwOzEYMBYGA1UEChMPQ3li +ZXJ0cnVzdCwgSW5jMR8wHQYDVQQDExZDeWJlcnRydXN0IEdsb2JhbCBSb290MB4XDTA2MTIxNTA4 +MDAwMFoXDTIxMTIxNTA4MDAwMFowOzEYMBYGA1UEChMPQ3liZXJ0cnVzdCwgSW5jMR8wHQYDVQQD +ExZDeWJlcnRydXN0IEdsb2JhbCBSb290MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA ++Mi8vRRQZhP/8NN57CPytxrHjoXxEnOmGaoQ25yiZXRadz5RfVb23CO21O1fWLE3TdVJDm71aofW +0ozSJ8bi/zafmGWgE07GKmSb1ZASzxQG9Dvj1Ci+6A74q05IlG2OlTEQXO2iLb3VOm2yHLtgwEZL +AfVJrn5GitB0jaEMAs7u/OePuGtm839EAL9mJRQr3RAwHQeWP032a7iPt3sMpTjr3kfb1V05/Iin +89cqdPHoWqI7n1C6poxFNcJQZZXcY4Lv3b93TZxiyWNzFtApD0mpSPCzqrdsxacwOUBdrsTiXSZT +8M4cIwhhqJQZugRiQOwfOHB3EgZxpzAYXSUnpQIDAQABo4GlMIGiMA4GA1UdDwEB/wQEAwIBBjAP +BgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBS2CHsNesysIEyGVjJez6tuhS1wVzA/BgNVHR8EODA2 +MDSgMqAwhi5odHRwOi8vd3d3Mi5wdWJsaWMtdHJ1c3QuY29tL2NybC9jdC9jdHJvb3QuY3JsMB8G +A1UdIwQYMBaAFLYIew16zKwgTIZWMl7Pq26FLXBXMA0GCSqGSIb3DQEBBQUAA4IBAQBW7wojoFRO +lZfJ+InaRcHUowAl9B8Tq7ejhVhpwjCt2BWKLePJzYFa+HMjWqd8BfP9IjsO0QbE2zZMcwSO5bAi +5MXzLqXZI+O4Tkogp24CJJ8iYGd7ix1yCcUxXOl5n4BHPa2hCwcUPUf/A2kaDAtE52Mlp3+yybh2 +hO0j9n0Hq0V+09+zv+mKts2oomcrUtW3ZfA5TGOgkXmTUg9U3YO7n9GPp1Nzw8v/MOx8BLjYRB+T +X3EJIrduPuocA06dGiBh+4E37F78CkWr1+cXVdCg6mCbpvbjjFspwgZgFJ0tl0ypkxWdYcQBX0jW +WL1WMRJOEcgh4LMRkWXbtKaIOM5V +-----END CERTIFICATE----- + +ePKI Root Certification Authority +================================= +-----BEGIN CERTIFICATE----- +MIIFsDCCA5igAwIBAgIQFci9ZUdcr7iXAF7kBtK8nTANBgkqhkiG9w0BAQUFADBeMQswCQYDVQQG +EwJUVzEjMCEGA1UECgwaQ2h1bmdod2EgVGVsZWNvbSBDby4sIEx0ZC4xKjAoBgNVBAsMIWVQS0kg +Um9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNDEyMjAwMjMxMjdaFw0zNDEyMjAwMjMx +MjdaMF4xCzAJBgNVBAYTAlRXMSMwIQYDVQQKDBpDaHVuZ2h3YSBUZWxlY29tIENvLiwgTHRkLjEq +MCgGA1UECwwhZVBLSSBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIICIjANBgkqhkiG9w0B +AQEFAAOCAg8AMIICCgKCAgEA4SUP7o3biDN1Z82tH306Tm2d0y8U82N0ywEhajfqhFAHSyZbCUNs +IZ5qyNUD9WBpj8zwIuQf5/dqIjG3LBXy4P4AakP/h2XGtRrBp0xtInAhijHyl3SJCRImHJ7K2RKi +lTza6We/CKBk49ZCt0Xvl/T29de1ShUCWH2YWEtgvM3XDZoTM1PRYfl61dd4s5oz9wCGzh1NlDiv +qOx4UXCKXBCDUSH3ET00hl7lSM2XgYI1TBnsZfZrxQWh7kcT1rMhJ5QQCtkkO7q+RBNGMD+XPNjX +12ruOzjjK9SXDrkb5wdJfzcq+Xd4z1TtW0ado4AOkUPB1ltfFLqfpo0kR0BZv3I4sjZsN/+Z0V0O +WQqraffAsgRFelQArr5T9rXn4fg8ozHSqf4hUmTFpmfwdQcGlBSBVcYn5AGPF8Fqcde+S/uUWH1+ +ETOxQvdibBjWzwloPn9s9h6PYq2lY9sJpx8iQkEeb5mKPtf5P0B6ebClAZLSnT0IFaUQAS2zMnao +lQ2zepr7BxB4EW/hj8e6DyUadCrlHJhBmd8hh+iVBmoKs2pHdmX2Os+PYhcZewoozRrSgx4hxyy/ +vv9haLdnG7t4TY3OZ+XkwY63I2binZB1NJipNiuKmpS5nezMirH4JYlcWrYvjB9teSSnUmjDhDXi +Zo1jDiVN1Rmy5nk3pyKdVDECAwEAAaNqMGgwHQYDVR0OBBYEFB4M97Zn8uGSJglFwFU5Lnc/Qkqi +MAwGA1UdEwQFMAMBAf8wOQYEZyoHAAQxMC8wLQIBADAJBgUrDgMCGgUAMAcGBWcqAwAABBRFsMLH +ClZ87lt4DJX5GFPBphzYEDANBgkqhkiG9w0BAQUFAAOCAgEACbODU1kBPpVJufGBuvl2ICO1J2B0 +1GqZNF5sAFPZn/KmsSQHRGoqxqWOeBLoR9lYGxMqXnmbnwoqZ6YlPwZpVnPDimZI+ymBV3QGypzq +KOg4ZyYr8dW1P2WT+DZdjo2NQCCHGervJ8A9tDkPJXtoUHRVnAxZfVo9QZQlUgjgRywVMRnVvwdV +xrsStZf0X4OFunHB2WyBEXYKCrC/gpf36j36+uwtqSiUO1bd0lEursC9CBWMd1I0ltabrNMdjmEP +NXubrjlpC2JgQCA2j6/7Nu4tCEoduL+bXPjqpRugc6bY+G7gMwRfaKonh+3ZwZCc7b3jajWvY9+r +GNm65ulK6lCKD2GTHuItGeIwlDWSXQ62B68ZgI9HkFFLLk3dheLSClIKF5r8GrBQAuUBo2M3IUxE +xJtRmREOc5wGj1QupyheRDmHVi03vYVElOEMSyycw5KFNGHLD7ibSkNS/jQ6fbjpKdx2qcgw+BRx +gMYeNkh0IkFch4LoGHGLQYlE535YW6i4jRPpp2zDR+2zGp1iro2C6pSe3VkQw63d4k3jMdXH7Ojy +sP6SHhYKGvzZ8/gntsm+HbRsZJB/9OTEW9c3rkIO3aQab3yIVMUWbuF6aC74Or8NpDyJO3inTmOD +BCEIZ43ygknQW/2xzQ+DhNQ+IIX3Sj0rnP0qCglN6oH4EZw= +-----END CERTIFICATE----- + +T\xc3\x9c\x42\xC4\xB0TAK UEKAE K\xC3\xB6k Sertifika Hizmet Sa\xC4\x9Flay\xc4\xb1\x63\xc4\xb1s\xc4\xb1 - S\xC3\xBCr\xC3\xBCm 3 +============================================================================================================================= +-----BEGIN CERTIFICATE----- +MIIFFzCCA/+gAwIBAgIBETANBgkqhkiG9w0BAQUFADCCASsxCzAJBgNVBAYTAlRSMRgwFgYDVQQH +DA9HZWJ6ZSAtIEtvY2FlbGkxRzBFBgNVBAoMPlTDvHJraXllIEJpbGltc2VsIHZlIFRla25vbG9q +aWsgQXJhxZ90xLFybWEgS3VydW11IC0gVMOcQsSwVEFLMUgwRgYDVQQLDD9VbHVzYWwgRWxla3Ry +b25payB2ZSBLcmlwdG9sb2ppIEFyYcWfdMSxcm1hIEVuc3RpdMO8c8O8IC0gVUVLQUUxIzAhBgNV +BAsMGkthbXUgU2VydGlmaWthc3lvbiBNZXJrZXppMUowSAYDVQQDDEFUw5xCxLBUQUsgVUVLQUUg +S8O2ayBTZXJ0aWZpa2EgSGl6bWV0IFNhxJ9sYXnEsWPEsXPEsSAtIFPDvHLDvG0gMzAeFw0wNzA4 +MjQxMTM3MDdaFw0xNzA4MjExMTM3MDdaMIIBKzELMAkGA1UEBhMCVFIxGDAWBgNVBAcMD0dlYnpl +IC0gS29jYWVsaTFHMEUGA1UECgw+VMO8cmtpeWUgQmlsaW1zZWwgdmUgVGVrbm9sb2ppayBBcmHF +n3TEsXJtYSBLdXJ1bXUgLSBUw5xCxLBUQUsxSDBGBgNVBAsMP1VsdXNhbCBFbGVrdHJvbmlrIHZl +IEtyaXB0b2xvamkgQXJhxZ90xLFybWEgRW5zdGl0w7xzw7wgLSBVRUtBRTEjMCEGA1UECwwaS2Ft +dSBTZXJ0aWZpa2FzeW9uIE1lcmtlemkxSjBIBgNVBAMMQVTDnELEsFRBSyBVRUtBRSBLw7ZrIFNl +cnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sxc8SxIC0gU8O8csO8bSAzMIIBIjANBgkqhkiG9w0B +AQEFAAOCAQ8AMIIBCgKCAQEAim1L/xCIOsP2fpTo6iBkcK4hgb46ezzb8R1Sf1n68yJMlaCQvEhO +Eav7t7WNeoMojCZG2E6VQIdhn8WebYGHV2yKO7Rm6sxA/OOqbLLLAdsyv9Lrhc+hDVXDWzhXcLh1 +xnnRFDDtG1hba+818qEhTsXOfJlfbLm4IpNQp81McGq+agV/E5wrHur+R84EpW+sky58K5+eeROR +6Oqeyjh1jmKwlZMq5d/pXpduIF9fhHpEORlAHLpVK/swsoHvhOPc7Jg4OQOFCKlUAwUp8MmPi+oL +hmUZEdPpCSPeaJMDyTYcIW7OjGbxmTDY17PDHfiBLqi9ggtm/oLL4eAagsNAgQIDAQABo0IwQDAd +BgNVHQ4EFgQUvYiHyY/2pAoLquvF/pEjnatKijIwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQF +MAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAB18+kmPNOm3JpIWmgV050vQbTlswyb2zrgxvMTfvCr4 +N5EY3ATIZJkrGG2AA1nJrvhY0D7twyOfaTyGOBye79oneNGEN3GKPEs5z35FBtYt2IpNeBLWrcLT +y9LQQfMmNkqblWwM7uXRQydmwYj3erMgbOqwaSvHIOgMA8RBBZniP+Rr+KCGgceExh/VS4ESshYh +LBOhgLJeDEoTniDYYkCrkOpkSi+sDQESeUWoL4cZaMjihccwsnX5OD+ywJO0a+IDRM5noN+J1q2M +dqMTw5RhK2vZbMEHCiIHhWyFJEapvj+LeISCfiQMnf2BN+MlqO02TpUsyZyQ2uypQjyttgI= +-----END CERTIFICATE----- + +Buypass Class 2 CA 1 +==================== +-----BEGIN CERTIFICATE----- +MIIDUzCCAjugAwIBAgIBATANBgkqhkiG9w0BAQUFADBLMQswCQYDVQQGEwJOTzEdMBsGA1UECgwU +QnV5cGFzcyBBUy05ODMxNjMzMjcxHTAbBgNVBAMMFEJ1eXBhc3MgQ2xhc3MgMiBDQSAxMB4XDTA2 +MTAxMzEwMjUwOVoXDTE2MTAxMzEwMjUwOVowSzELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1eXBh +c3MgQVMtOTgzMTYzMzI3MR0wGwYDVQQDDBRCdXlwYXNzIENsYXNzIDIgQ0EgMTCCASIwDQYJKoZI +hvcNAQEBBQADggEPADCCAQoCggEBAIs8B0XY9t/mx8q6jUPFR42wWsE425KEHK8T1A9vNkYgxC7M +cXA0ojTTNy7Y3Tp3L8DrKehc0rWpkTSHIln+zNvnma+WwajHQN2lFYxuyHyXA8vmIPLXl18xoS83 +0r7uvqmtqEyeIWZDO6i88wmjONVZJMHCR3axiFyCO7srpgTXjAePzdVBHfCuuCkslFJgNJQ72uA4 +0Z0zPhX0kzLFANq1KWYOOngPIVJfAuWSeyXTkh4vFZ2B5J2O6O+JzhRMVB0cgRJNcKi+EAUXfh/R +uFdV7c27UsKwHnjCTTZoy1YmwVLBvXb3WNVyfh9EdrsAiR0WnVE1703CVu9r4Iw7DekCAwEAAaNC +MEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUP42aWYv8e3uco684sDntkHGA1sgwDgYDVR0P +AQH/BAQDAgEGMA0GCSqGSIb3DQEBBQUAA4IBAQAVGn4TirnoB6NLJzKyQJHyIdFkhb5jatLPgcIV +1Xp+DCmsNx4cfHZSldq1fyOhKXdlyTKdqC5Wq2B2zha0jX94wNWZUYN/Xtm+DKhQ7SLHrQVMdvvt +7h5HZPb3J31cKA9FxVxiXqaakZG3Uxcu3K1gnZZkOb1naLKuBctN518fV4bVIJwo+28TOPX2EZL2 +fZleHwzoq0QkKXJAPTZSr4xYkHPB7GEseaHsh7U/2k3ZIQAw3pDaDtMaSKk+hQsUi4y8QZ5q9w5w +wDX3OaJdZtB7WZ+oRxKaJyOkLY4ng5IgodcVf/EuGO70SH8vf/GhGLWhC5SgYiAynB321O+/TIho +-----END CERTIFICATE----- + +Buypass Class 3 CA 1 +==================== +-----BEGIN CERTIFICATE----- +MIIDUzCCAjugAwIBAgIBAjANBgkqhkiG9w0BAQUFADBLMQswCQYDVQQGEwJOTzEdMBsGA1UECgwU +QnV5cGFzcyBBUy05ODMxNjMzMjcxHTAbBgNVBAMMFEJ1eXBhc3MgQ2xhc3MgMyBDQSAxMB4XDTA1 +MDUwOTE0MTMwM1oXDTE1MDUwOTE0MTMwM1owSzELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1eXBh +c3MgQVMtOTgzMTYzMzI3MR0wGwYDVQQDDBRCdXlwYXNzIENsYXNzIDMgQ0EgMTCCASIwDQYJKoZI +hvcNAQEBBQADggEPADCCAQoCggEBAKSO13TZKWTeXx+HgJHqTjnmGcZEC4DVC69TB4sSveZn8AKx +ifZgisRbsELRwCGoy+Gb72RRtqfPFfV0gGgEkKBYouZ0plNTVUhjP5JW3SROjvi6K//zNIqeKNc0 +n6wv1g/xpC+9UrJJhW05NfBEMJNGJPO251P7vGGvqaMU+8IXF4Rs4HyI+MkcVyzwPX6UvCWThOia +AJpFBUJXgPROztmuOfbIUxAMZTpHe2DC1vqRycZxbL2RhzyRhkmr8w+gbCZ2Xhysm3HljbybIR6c +1jh+JIAVMYKWsUnTYjdbiAwKYjT+p0h+mbEwi5A3lRyoH6UsjfRVyNvdWQrCrXig9IsCAwEAAaNC +MEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUOBTmyPCppAP0Tj4io1vy1uCtQHQwDgYDVR0P +AQH/BAQDAgEGMA0GCSqGSIb3DQEBBQUAA4IBAQABZ6OMySU9E2NdFm/soT4JXJEVKirZgCFPBdy7 +pYmrEzMqnji3jG8CcmPHc3ceCQa6Oyh7pEfJYWsICCD8igWKH7y6xsL+z27sEzNxZy5p+qksP2bA +EllNC1QCkoS72xLvg3BweMhT+t/Gxv/ciC8HwEmdMldg0/L2mSlf56oBzKwzqBwKu5HEA6BvtjT5 +htOzdlSY9EqBs1OdTUDs5XcTRa9bqh/YL0yCe/4qxFi7T/ye/QNlGioOw6UgFpRreaaiErS7GqQj +el/wroQk5PMr+4okoyeYZdowdXb8GZHo2+ubPzK/QJcHJrrM85SFSnonk8+QQtS4Wxam58tAA915 +-----END CERTIFICATE----- + +EBG Elektronik Sertifika Hizmet Sa\xC4\x9Flay\xc4\xb1\x63\xc4\xb1s\xc4\xb1 +========================================================================== +-----BEGIN CERTIFICATE----- +MIIF5zCCA8+gAwIBAgIITK9zQhyOdAIwDQYJKoZIhvcNAQEFBQAwgYAxODA2BgNVBAMML0VCRyBF +bGVrdHJvbmlrIFNlcnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sxc8SxMTcwNQYDVQQKDC5FQkcg +QmlsacWfaW0gVGVrbm9sb2ppbGVyaSB2ZSBIaXptZXRsZXJpIEEuxZ4uMQswCQYDVQQGEwJUUjAe +Fw0wNjA4MTcwMDIxMDlaFw0xNjA4MTQwMDMxMDlaMIGAMTgwNgYDVQQDDC9FQkcgRWxla3Ryb25p +ayBTZXJ0aWZpa2EgSGl6bWV0IFNhxJ9sYXnEsWPEsXPEsTE3MDUGA1UECgwuRUJHIEJpbGnFn2lt +IFRla25vbG9qaWxlcmkgdmUgSGl6bWV0bGVyaSBBLsWeLjELMAkGA1UEBhMCVFIwggIiMA0GCSqG +SIb3DQEBAQUAA4ICDwAwggIKAoICAQDuoIRh0DpqZhAy2DE4f6en5f2h4fuXd7hxlugTlkaDT7by +X3JWbhNgpQGR4lvFzVcfd2NR/y8927k/qqk153nQ9dAktiHq6yOU/im/+4mRDGSaBUorzAzu8T2b +gmmkTPiab+ci2hC6X5L8GCcKqKpE+i4stPtGmggDg3KriORqcsnlZR9uKg+ds+g75AxuetpX/dfr +eYteIAbTdgtsApWjluTLdlHRKJ2hGvxEok3MenaoDT2/F08iiFD9rrbskFBKW5+VQarKD7JK/oCZ +TqNGFav4c0JqwmZ2sQomFd2TkuzbqV9UIlKRcF0T6kjsbgNs2d1s/OsNA/+mgxKb8amTD8UmTDGy +Y5lhcucqZJnSuOl14nypqZoaqsNW2xCaPINStnuWt6yHd6i58mcLlEOzrz5z+kI2sSXFCjEmN1Zn +uqMLfdb3ic1nobc6HmZP9qBVFCVMLDMNpkGMvQQxahByCp0OLna9XvNRiYuoP1Vzv9s6xiQFlpJI +qkuNKgPlV5EQ9GooFW5Hd4RcUXSfGenmHmMWOeMRFeNYGkS9y8RsZteEBt8w9DeiQyJ50hBs37vm +ExH8nYQKE3vwO9D8owrXieqWfo1IhR5kX9tUoqzVegJ5a9KK8GfaZXINFHDk6Y54jzJ0fFfy1tb0 +Nokb+Clsi7n2l9GkLqq+CxnCRelwXQIDAJ3Zo2MwYTAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB +/wQEAwIBBjAdBgNVHQ4EFgQU587GT/wWZ5b6SqMHwQSny2re2kcwHwYDVR0jBBgwFoAU587GT/wW +Z5b6SqMHwQSny2re2kcwDQYJKoZIhvcNAQEFBQADggIBAJuYml2+8ygjdsZs93/mQJ7ANtyVDR2t +FcU22NU57/IeIl6zgrRdu0waypIN30ckHrMk2pGI6YNw3ZPX6bqz3xZaPt7gyPvT/Wwp+BVGoGgm +zJNSroIBk5DKd8pNSe/iWtkqvTDOTLKBtjDOWU/aWR1qeqRFsIImgYZ29fUQALjuswnoT4cCB64k +XPBfrAowzIpAoHMEwfuJJPaaHFy3PApnNgUIMbOv2AFoKuB4j3TeuFGkjGwgPaL7s9QJ/XvCgKqT +bCmYIai7FvOpEl90tYeY8pUm3zTvilORiF0alKM/fCL414i6poyWqD1SNGKfAB5UVUJnxk1Gj7sU +RT0KlhaOEKGXmdXTMIXM3rRyt7yKPBgpaP3ccQfuJDlq+u2lrDgv+R4QDgZxGhBM/nV+/x5XOULK +1+EVoVZVWRvRo68R2E7DpSvvkL/A7IITW43WciyTTo9qKd+FPNMN4KIYEsxVL0e3p5sC/kH2iExt +2qkBR4NkJ2IQgtYSe14DHzSpyZH+r11thie3I6p1GMog57AP14kOpmciY/SDQSsGS7tY1dHXt7kQ +Y9iJSrSq3RZj9W6+YKH47ejWkE8axsWgKdOnIaj1Wjz3x0miIZpKlVIglnKaZsv30oZDfCK+lvm9 +AahH3eU7QPl1K5srRmSGjR70j/sHd9DqSaIcjVIUpgqT +-----END CERTIFICATE----- + +certSIGN ROOT CA +================ +-----BEGIN CERTIFICATE----- +MIIDODCCAiCgAwIBAgIGIAYFFnACMA0GCSqGSIb3DQEBBQUAMDsxCzAJBgNVBAYTAlJPMREwDwYD +VQQKEwhjZXJ0U0lHTjEZMBcGA1UECxMQY2VydFNJR04gUk9PVCBDQTAeFw0wNjA3MDQxNzIwMDRa +Fw0zMTA3MDQxNzIwMDRaMDsxCzAJBgNVBAYTAlJPMREwDwYDVQQKEwhjZXJ0U0lHTjEZMBcGA1UE +CxMQY2VydFNJR04gUk9PVCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALczuX7I +JUqOtdu0KBuqV5Do0SLTZLrTk+jUrIZhQGpgV2hUhE28alQCBf/fm5oqrl0Hj0rDKH/v+yv6efHH +rfAQUySQi2bJqIirr1qjAOm+ukbuW3N7LBeCgV5iLKECZbO9xSsAfsT8AzNXDe3i+s5dRdY4zTW2 +ssHQnIFKquSyAVwdj1+ZxLGt24gh65AIgoDzMKND5pCCrlUoSe1b16kQOA7+j0xbm0bqQfWwCHTD +0IgztnzXdN/chNFDDnU5oSVAKOp4yw4sLjmdjItuFhwvJoIQ4uNllAoEwF73XVv4EOLQunpL+943 +AAAaWyjj0pxzPjKHmKHJUS/X3qwzs08CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8B +Af8EBAMCAcYwHQYDVR0OBBYEFOCMm9slSbPxfIbWskKHC9BroNnkMA0GCSqGSIb3DQEBBQUAA4IB +AQA+0hyJLjX8+HXd5n9liPRyTMks1zJO890ZeUe9jjtbkw9QSSQTaxQGcu8J06Gh40CEyecYMnQ8 +SG4Pn0vU9x7Tk4ZkVJdjclDVVc/6IJMCopvDI5NOFlV2oHB5bc0hH88vLbwZ44gx+FkagQnIl6Z0 +x2DEW8xXjrJ1/RsCCdtZb3KTafcxQdaIOL+Hsr0Wefmq5L6IJd1hJyMctTEHBDa0GpC9oHRxUIlt +vBTjD4au8as+x6AJzKNI0eDbZOeStc+vckNwi/nDhDwTqn6Sm1dTk/pwwpEOMfmbZ13pljheX7Nz +TogVZ96edhBiIL5VaZVDADlN9u6wWk5JRFRYX0KD +-----END CERTIFICATE----- + +CNNIC ROOT +========== +-----BEGIN CERTIFICATE----- +MIIDVTCCAj2gAwIBAgIESTMAATANBgkqhkiG9w0BAQUFADAyMQswCQYDVQQGEwJDTjEOMAwGA1UE +ChMFQ05OSUMxEzARBgNVBAMTCkNOTklDIFJPT1QwHhcNMDcwNDE2MDcwOTE0WhcNMjcwNDE2MDcw +OTE0WjAyMQswCQYDVQQGEwJDTjEOMAwGA1UEChMFQ05OSUMxEzARBgNVBAMTCkNOTklDIFJPT1Qw +ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDTNfc/c3et6FtzF8LRb+1VvG7q6KR5smzD +o+/hn7E7SIX1mlwhIhAsxYLO2uOabjfhhyzcuQxauohV3/2q2x8x6gHx3zkBwRP9SFIhxFXf2tiz +VHa6dLG3fdfA6PZZxU3Iva0fFNrfWEQlMhkqx35+jq44sDB7R3IJMfAw28Mbdim7aXZOV/kbZKKT +VrdvmW7bCgScEeOAH8tjlBAKqeFkgjH5jCftppkA9nCTGPihNIaj3XrCGHn2emU1z5DrvTOTn1Or +czvmmzQgLx3vqR1jGqCA2wMv+SYahtKNu6m+UjqHZ0gNv7Sg2Ca+I19zN38m5pIEo3/PIKe38zrK +y5nLAgMBAAGjczBxMBEGCWCGSAGG+EIBAQQEAwIABzAfBgNVHSMEGDAWgBRl8jGtKvf33VKWCscC +wQ7vptU7ETAPBgNVHRMBAf8EBTADAQH/MAsGA1UdDwQEAwIB/jAdBgNVHQ4EFgQUZfIxrSr3991S +lgrHAsEO76bVOxEwDQYJKoZIhvcNAQEFBQADggEBAEs17szkrr/Dbq2flTtLP1se31cpolnKOOK5 +Gv+e5m4y3R6u6jW39ZORTtpC4cMXYFDy0VwmuYK36m3knITnA3kXr5g9lNvHugDnuL8BV8F3RTIM +O/G0HAiw/VGgod2aHRM2mm23xzy54cXZF/qD1T0VoDy7HgviyJA/qIYM/PmLXoXLT1tLYhFHxUV8 +BS9BsZ4QaRuZluBVeftOhpm4lNqGOGqTo+fLbuXf6iFViZx9fX+Y9QCJ7uOEwFyWtcVG6kbghVW2 +G8kS1sHNzYDzAgE8yGnLRUhj2JTQ7IUOO04RZfSCjKY9ri4ilAnIXOo8gV0WKgOXFlUJ24pBgp5m +mxE= +-----END CERTIFICATE----- + +ApplicationCA - Japanese Government +=================================== +-----BEGIN CERTIFICATE----- +MIIDoDCCAoigAwIBAgIBMTANBgkqhkiG9w0BAQUFADBDMQswCQYDVQQGEwJKUDEcMBoGA1UEChMT +SmFwYW5lc2UgR292ZXJubWVudDEWMBQGA1UECxMNQXBwbGljYXRpb25DQTAeFw0wNzEyMTIxNTAw +MDBaFw0xNzEyMTIxNTAwMDBaMEMxCzAJBgNVBAYTAkpQMRwwGgYDVQQKExNKYXBhbmVzZSBHb3Zl +cm5tZW50MRYwFAYDVQQLEw1BcHBsaWNhdGlvbkNBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB +CgKCAQEAp23gdE6Hj6UG3mii24aZS2QNcfAKBZuOquHMLtJqO8F6tJdhjYq+xpqcBrSGUeQ3DnR4 +fl+Kf5Sk10cI/VBaVuRorChzoHvpfxiSQE8tnfWuREhzNgaeZCw7NCPbXCbkcXmP1G55IrmTwcrN +wVbtiGrXoDkhBFcsovW8R0FPXjQilbUfKW1eSvNNcr5BViCH/OlQR9cwFO5cjFW6WY2H/CPek9AE +jP3vbb3QesmlOmpyM8ZKDQUXKi17safY1vC+9D/qDihtQWEjdnjDuGWk81quzMKq2edY3rZ+nYVu +nyoKb58DKTCXKB28t89UKU5RMfkntigm/qJj5kEW8DOYRwIDAQABo4GeMIGbMB0GA1UdDgQWBBRU +WssmP3HMlEYNllPqa0jQk/5CdTAOBgNVHQ8BAf8EBAMCAQYwWQYDVR0RBFIwUKROMEwxCzAJBgNV +BAYTAkpQMRgwFgYDVQQKDA/ml6XmnKzlm73mlL/lupwxIzAhBgNVBAsMGuOCouODl+ODquOCseOD +vOOCt+ODp+ODs0NBMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBADlqRHZ3ODrs +o2dGD/mLBqj7apAxzn7s2tGJfHrrLgy9mTLnsCTWw//1sogJhyzjVOGjprIIC8CFqMjSnHH2HZ9g +/DgzE+Ge3Atf2hZQKXsvcJEPmbo0NI2VdMV+eKlmXb3KIXdCEKxmJj3ekav9FfBv7WxfEPjzFvYD +io+nEhEMy/0/ecGc/WLuo89UDNErXxc+4z6/wCs+CZv+iKZ+tJIX/COUgb1up8WMwusRRdv4QcmW +dupwX3kSa+SjB1oF7ydJzyGfikwJcGapJsErEU4z0g781mzSDjJkaP+tBXhfAx2o45CsJOAPQKdL +rosot4LKGAfmt1t06SAZf7IbiVQ= +-----END CERTIFICATE----- + +GeoTrust Primary Certification Authority - G3 +============================================= +-----BEGIN CERTIFICATE----- +MIID/jCCAuagAwIBAgIQFaxulBmyeUtB9iepwxgPHzANBgkqhkiG9w0BAQsFADCBmDELMAkGA1UE +BhMCVVMxFjAUBgNVBAoTDUdlb1RydXN0IEluYy4xOTA3BgNVBAsTMChjKSAyMDA4IEdlb1RydXN0 +IEluYy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25seTE2MDQGA1UEAxMtR2VvVHJ1c3QgUHJpbWFy +eSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEczMB4XDTA4MDQwMjAwMDAwMFoXDTM3MTIwMTIz +NTk1OVowgZgxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMTkwNwYDVQQLEzAo +YykgMjAwOCBHZW9UcnVzdCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxNjA0BgNVBAMT +LUdlb1RydXN0IFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMzCCASIwDQYJKoZI +hvcNAQEBBQADggEPADCCAQoCggEBANziXmJYHTNXOTIz+uvLh4yn1ErdBojqZI4xmKU4kB6Yzy5j +K/BGvESyiaHAKAxJcCGVn2TAppMSAmUmhsalifD614SgcK9PGpc/BkTVyetyEH3kMSj7HGHmKAdE +c5IiaacDiGydY8hS2pgn5whMcD60yRLBxWeDXTPzAxHsatBT4tG6NmCUgLthY2xbF37fQJQeqw3C +IShwiP/WJmxsYAQlTlV+fe+/lEjetx3dcI0FX4ilm/LC7urRQEFtYjgdVgbFA0dRIBn8exALDmKu +dlW/X3e+PkkBUz2YJQN2JFodtNuJ6nnltrM7P7pMKEF/BqxqjsHQ9gUdfeZChuOl1UcCAwEAAaNC +MEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFMR5yo6hTgMdHNxr +2zFblD4/MH8tMA0GCSqGSIb3DQEBCwUAA4IBAQAtxRPPVoB7eni9n64smefv2t+UXglpp+duaIy9 +cr5HqQ6XErhK8WTTOd8lNNTBzU6B8A8ExCSzNJbGpqow32hhc9f5joWJ7w5elShKKiePEI4ufIbE +Ap7aDHdlDkQNkv39sxY2+hENHYwOB4lqKVb3cvTdFZx3NWZXqxNT2I7BQMXXExZacse3aQHEerGD +AWh9jUGhlBjBJVz88P6DAod8DQ3PLghcSkANPuyBYeYk28rgDi0Hsj5W3I31QYUHSJsMC8tJP33s +t/3LjWeJGqvtux6jAAgIFyqCXDFdRootD4abdNlF+9RAsXqqaC2Gspki4cErx5z481+oghLrGREt +-----END CERTIFICATE----- + +thawte Primary Root CA - G2 +=========================== +-----BEGIN CERTIFICATE----- +MIICiDCCAg2gAwIBAgIQNfwmXNmET8k9Jj1Xm67XVjAKBggqhkjOPQQDAzCBhDELMAkGA1UEBhMC +VVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjE4MDYGA1UECxMvKGMpIDIwMDcgdGhhd3RlLCBJbmMu +IC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxJDAiBgNVBAMTG3RoYXd0ZSBQcmltYXJ5IFJvb3Qg +Q0EgLSBHMjAeFw0wNzExMDUwMDAwMDBaFw0zODAxMTgyMzU5NTlaMIGEMQswCQYDVQQGEwJVUzEV +MBMGA1UEChMMdGhhd3RlLCBJbmMuMTgwNgYDVQQLEy8oYykgMjAwNyB0aGF3dGUsIEluYy4gLSBG +b3IgYXV0aG9yaXplZCB1c2Ugb25seTEkMCIGA1UEAxMbdGhhd3RlIFByaW1hcnkgUm9vdCBDQSAt +IEcyMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEotWcgnuVnfFSeIf+iha/BebfowJPDQfGAFG6DAJS +LSKkQjnE/o/qycG+1E3/n3qe4rF8mq2nhglzh9HnmuN6papu+7qzcMBniKI11KOasf2twu8x+qi5 +8/sIxpHR+ymVo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQU +mtgAMADna3+FGO6Lts6KDPgR4bswCgYIKoZIzj0EAwMDaQAwZgIxAN344FdHW6fmCsO99YCKlzUN +G4k8VIZ3KMqh9HneteY4sPBlcIx/AlTCv//YoT7ZzwIxAMSNlPzcU9LcnXgWHxUzI1NS41oxXZ3K +rr0TKUQNJ1uo52icEvdYPy5yAlejj6EULg== +-----END CERTIFICATE----- + +thawte Primary Root CA - G3 +=========================== +-----BEGIN CERTIFICATE----- +MIIEKjCCAxKgAwIBAgIQYAGXt0an6rS0mtZLL/eQ+zANBgkqhkiG9w0BAQsFADCBrjELMAkGA1UE +BhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2 +aWNlcyBEaXZpc2lvbjE4MDYGA1UECxMvKGMpIDIwMDggdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhv +cml6ZWQgdXNlIG9ubHkxJDAiBgNVBAMTG3RoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EgLSBHMzAeFw0w +ODA0MDIwMDAwMDBaFw0zNzEyMDEyMzU5NTlaMIGuMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMdGhh +d3RlLCBJbmMuMSgwJgYDVQQLEx9DZXJ0aWZpY2F0aW9uIFNlcnZpY2VzIERpdmlzaW9uMTgwNgYD +VQQLEy8oYykgMjAwOCB0aGF3dGUsIEluYy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25seTEkMCIG +A1UEAxMbdGhhd3RlIFByaW1hcnkgUm9vdCBDQSAtIEczMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A +MIIBCgKCAQEAsr8nLPvb2FvdeHsbnndmgcs+vHyu86YnmjSjaDFxODNi5PNxZnmxqWWjpYvVj2At +P0LMqmsywCPLLEHd5N/8YZzic7IilRFDGF/Eth9XbAoFWCLINkw6fKXRz4aviKdEAhN0cXMKQlkC ++BsUa0Lfb1+6a4KinVvnSr0eAXLbS3ToO39/fR8EtCab4LRarEc9VbjXsCZSKAExQGbY2SS99irY +7CFJXJv2eul/VTV+lmuNk5Mny5K76qxAwJ/C+IDPXfRa3M50hqY+bAtTyr2SzhkGcuYMXDhpxwTW +vGzOW/b3aJzcJRVIiKHpqfiYnODz1TEoYRFsZ5aNOZnLwkUkOQIDAQABo0IwQDAPBgNVHRMBAf8E +BTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUrWyqlGCc7eT/+j4KdCtjA/e2Wb8wDQYJ +KoZIhvcNAQELBQADggEBABpA2JVlrAmSicY59BDlqQ5mU1143vokkbvnRFHfxhY0Cu9qRFHqKweK +A3rD6z8KLFIWoCtDuSWQP3CpMyVtRRooOyfPqsMpQhvfO0zAMzRbQYi/aytlryjvsvXDqmbOe1bu +t8jLZ8HJnBoYuMTDSQPxYA5QzUbF83d597YV4Djbxy8ooAw/dyZ02SUS2jHaGh7cKUGRIjxpp7sC +8rZcJwOJ9Abqm+RyguOhCcHpABnTPtRwa7pxpqpYrvS76Wy274fMm7v/OeZWYdMKp8RcTGB7BXcm +er/YB1IsYvdwY9k5vG8cwnncdimvzsUsZAReiDZuMdRAGmI0Nj81Aa6sY6A= +-----END CERTIFICATE----- + +GeoTrust Primary Certification Authority - G2 +============================================= +-----BEGIN CERTIFICATE----- +MIICrjCCAjWgAwIBAgIQPLL0SAoA4v7rJDteYD7DazAKBggqhkjOPQQDAzCBmDELMAkGA1UEBhMC +VVMxFjAUBgNVBAoTDUdlb1RydXN0IEluYy4xOTA3BgNVBAsTMChjKSAyMDA3IEdlb1RydXN0IElu +Yy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25seTE2MDQGA1UEAxMtR2VvVHJ1c3QgUHJpbWFyeSBD +ZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEcyMB4XDTA3MTEwNTAwMDAwMFoXDTM4MDExODIzNTk1 +OVowgZgxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMTkwNwYDVQQLEzAoYykg +MjAwNyBHZW9UcnVzdCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxNjA0BgNVBAMTLUdl +b1RydXN0IFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMjB2MBAGByqGSM49AgEG +BSuBBAAiA2IABBWx6P0DFUPlrOuHNxFi79KDNlJ9RVcLSo17VDs6bl8VAsBQps8lL33KSLjHUGMc +KiEIfJo22Av+0SbFWDEwKCXzXV2juLaltJLtbCyf691DiaI8S0iRHVDsJt/WYC69IaNCMEAwDwYD +VR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFBVfNVdRVfslsq0DafwBo/q+ +EVXVMAoGCCqGSM49BAMDA2cAMGQCMGSWWaboCd6LuvpaiIjwH5HTRqjySkwCY/tsXzjbLkGTqQ7m +ndwxHLKgpxgceeHHNgIwOlavmnRs9vuD4DPTCF+hnMJbn0bWtsuRBmOiBuczrD6ogRLQy7rQkgu2 +npaqBA+K +-----END CERTIFICATE----- + +VeriSign Universal Root Certification Authority +=============================================== +-----BEGIN CERTIFICATE----- +MIIEuTCCA6GgAwIBAgIQQBrEZCGzEyEDDrvkEhrFHTANBgkqhkiG9w0BAQsFADCBvTELMAkGA1UE +BhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZWZXJpU2lnbiBUcnVzdCBO +ZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwOCBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVk +IHVzZSBvbmx5MTgwNgYDVQQDEy9WZXJpU2lnbiBVbml2ZXJzYWwgUm9vdCBDZXJ0aWZpY2F0aW9u +IEF1dGhvcml0eTAeFw0wODA0MDIwMDAwMDBaFw0zNzEyMDEyMzU5NTlaMIG9MQswCQYDVQQGEwJV +UzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdv +cmsxOjA4BgNVBAsTMShjKSAyMDA4IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNl +IG9ubHkxODA2BgNVBAMTL1ZlcmlTaWduIFVuaXZlcnNhbCBSb290IENlcnRpZmljYXRpb24gQXV0 +aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAx2E3XrEBNNti1xWb/1hajCMj +1mCOkdeQmIN65lgZOIzF9uVkhbSicfvtvbnazU0AtMgtc6XHaXGVHzk8skQHnOgO+k1KxCHfKWGP +MiJhgsWHH26MfF8WIFFE0XBPV+rjHOPMee5Y2A7Cs0WTwCznmhcrewA3ekEzeOEz4vMQGn+HLL72 +9fdC4uW/h2KJXwBL38Xd5HVEMkE6HnFuacsLdUYI0crSK5XQz/u5QGtkjFdN/BMReYTtXlT2NJ8I +AfMQJQYXStrxHXpma5hgZqTZ79IugvHw7wnqRMkVauIDbjPTrJ9VAMf2CGqUuV/c4DPxhGD5WycR +tPwW8rtWaoAljQIDAQABo4GyMIGvMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMG0G +CCsGAQUFBwEMBGEwX6FdoFswWTBXMFUWCWltYWdlL2dpZjAhMB8wBwYFKw4DAhoEFI/l0xqGrI2O +a8PPgGrUSBgsexkuMCUWI2h0dHA6Ly9sb2dvLnZlcmlzaWduLmNvbS92c2xvZ28uZ2lmMB0GA1Ud +DgQWBBS2d/ppSEefUxLVwuoHMnYH0ZcHGTANBgkqhkiG9w0BAQsFAAOCAQEASvj4sAPmLGd75JR3 +Y8xuTPl9Dg3cyLk1uXBPY/ok+myDjEedO2Pzmvl2MpWRsXe8rJq+seQxIcaBlVZaDrHC1LGmWazx +Y8u4TB1ZkErvkBYoH1quEPuBUDgMbMzxPcP1Y+Oz4yHJJDnp/RVmRvQbEdBNc6N9Rvk97ahfYtTx +P/jgdFcrGJ2BtMQo2pSXpXDrrB2+BxHw1dvd5Yzw1TKwg+ZX4o+/vqGqvz0dtdQ46tewXDpPaj+P +wGZsY6rp2aQW9IHRlRQOfc2VNNnSj3BzgXucfr2YYdhFh5iQxeuGMMY1v/D/w1WIg0vvBZIGcfK4 +mJO37M2CYfE45k+XmCpajQ== +-----END CERTIFICATE----- + +VeriSign Class 3 Public Primary Certification Authority - G4 +============================================================ +-----BEGIN CERTIFICATE----- +MIIDhDCCAwqgAwIBAgIQL4D+I4wOIg9IZxIokYesszAKBggqhkjOPQQDAzCByjELMAkGA1UEBhMC +VVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZWZXJpU2lnbiBUcnVzdCBOZXR3 +b3JrMTowOAYDVQQLEzEoYykgMjAwNyBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVz +ZSBvbmx5MUUwQwYDVQQDEzxWZXJpU2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmlj +YXRpb24gQXV0aG9yaXR5IC0gRzQwHhcNMDcxMTA1MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCByjEL +MAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZWZXJpU2lnbiBU +cnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNyBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRo +b3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJpU2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5 +IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzQwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAASnVnp8 +Utpkmw4tXNherJI9/gHmGUo9FANL+mAnINmDiWn6VMaaGF5VKmTeBvaNSjutEDxlPZCIBIngMGGz +rl0Bp3vefLK+ymVhAIau2o970ImtTR1ZmkGxvEeA3J5iw/mjgbIwga8wDwYDVR0TAQH/BAUwAwEB +/zAOBgNVHQ8BAf8EBAMCAQYwbQYIKwYBBQUHAQwEYTBfoV2gWzBZMFcwVRYJaW1hZ2UvZ2lmMCEw +HzAHBgUrDgMCGgQUj+XTGoasjY5rw8+AatRIGCx7GS4wJRYjaHR0cDovL2xvZ28udmVyaXNpZ24u +Y29tL3ZzbG9nby5naWYwHQYDVR0OBBYEFLMWkf3upm7ktS5Jj4d4gYDs5bG1MAoGCCqGSM49BAMD +A2gAMGUCMGYhDBgmYFo4e1ZC4Kf8NoRRkSAsdk1DPcQdhCPQrNZ8NQbOzWm9kA3bbEhCHQ6qQgIx +AJw9SDkjOVgaFRJZap7v1VmyHVIsmXHNxynfGyphe3HR3vPA5Q06Sqotp9iGKt0uEA== +-----END CERTIFICATE----- + +NetLock Arany (Class Gold) Főtanúsítvány +============================================ +-----BEGIN CERTIFICATE----- +MIIEFTCCAv2gAwIBAgIGSUEs5AAQMA0GCSqGSIb3DQEBCwUAMIGnMQswCQYDVQQGEwJIVTERMA8G +A1UEBwwIQnVkYXBlc3QxFTATBgNVBAoMDE5ldExvY2sgS2Z0LjE3MDUGA1UECwwuVGFuw7pzw610 +dsOhbnlraWFkw7NrIChDZXJ0aWZpY2F0aW9uIFNlcnZpY2VzKTE1MDMGA1UEAwwsTmV0TG9jayBB +cmFueSAoQ2xhc3MgR29sZCkgRsWRdGFuw7pzw610dsOhbnkwHhcNMDgxMjExMTUwODIxWhcNMjgx +MjA2MTUwODIxWjCBpzELMAkGA1UEBhMCSFUxETAPBgNVBAcMCEJ1ZGFwZXN0MRUwEwYDVQQKDAxO +ZXRMb2NrIEtmdC4xNzA1BgNVBAsMLlRhbsO6c8OtdHbDoW55a2lhZMOzayAoQ2VydGlmaWNhdGlv +biBTZXJ2aWNlcykxNTAzBgNVBAMMLE5ldExvY2sgQXJhbnkgKENsYXNzIEdvbGQpIEbFkXRhbsO6 +c8OtdHbDoW55MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxCRec75LbRTDofTjl5Bu +0jBFHjzuZ9lk4BqKf8owyoPjIMHj9DrTlF8afFttvzBPhCf2nx9JvMaZCpDyD/V/Q4Q3Y1GLeqVw +/HpYzY6b7cNGbIRwXdrzAZAj/E4wqX7hJ2Pn7WQ8oLjJM2P+FpD/sLj916jAwJRDC7bVWaaeVtAk +H3B5r9s5VA1lddkVQZQBr17s9o3x/61k/iCa11zr/qYfCGSji3ZVrR47KGAuhyXoqq8fxmRGILdw +fzzeSNuWU7c5d+Qa4scWhHaXWy+7GRWF+GmF9ZmnqfI0p6m2pgP8b4Y9VHx2BJtr+UBdADTHLpl1 +neWIA6pN+APSQnbAGwIDAKiLo0UwQzASBgNVHRMBAf8ECDAGAQH/AgEEMA4GA1UdDwEB/wQEAwIB +BjAdBgNVHQ4EFgQUzPpnk/C2uNClwB7zU/2MU9+D15YwDQYJKoZIhvcNAQELBQADggEBAKt/7hwW +qZw8UQCgwBEIBaeZ5m8BiFRhbvG5GK1Krf6BQCOUL/t1fC8oS2IkgYIL9WHxHG64YTjrgfpioTta +YtOUZcTh5m2C+C8lcLIhJsFyUR+MLMOEkMNaj7rP9KdlpeuY0fsFskZ1FSNqb4VjMIDw1Z4fKRzC +bLBQWV2QWzuoDTDPv31/zvGdg73JRm4gpvlhUbohL3u+pRVjodSVh/GeufOJ8z2FuLjbvrW5Kfna +NwUASZQDhETnv0Mxz3WLJdH0pmT1kvarBes96aULNmLazAZfNou2XjG4Kvte9nHfRCaexOYNkbQu +dZWAUWpLMKawYqGT8ZvYzsRjdT9ZR7E= +-----END CERTIFICATE----- + +Staat der Nederlanden Root CA - G2 +================================== +-----BEGIN CERTIFICATE----- +MIIFyjCCA7KgAwIBAgIEAJiWjDANBgkqhkiG9w0BAQsFADBaMQswCQYDVQQGEwJOTDEeMBwGA1UE +CgwVU3RhYXQgZGVyIE5lZGVybGFuZGVuMSswKQYDVQQDDCJTdGFhdCBkZXIgTmVkZXJsYW5kZW4g +Um9vdCBDQSAtIEcyMB4XDTA4MDMyNjExMTgxN1oXDTIwMDMyNTExMDMxMFowWjELMAkGA1UEBhMC +TkwxHjAcBgNVBAoMFVN0YWF0IGRlciBOZWRlcmxhbmRlbjErMCkGA1UEAwwiU3RhYXQgZGVyIE5l +ZGVybGFuZGVuIFJvb3QgQ0EgLSBHMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMVZ +5291qj5LnLW4rJ4L5PnZyqtdj7U5EILXr1HgO+EASGrP2uEGQxGZqhQlEq0i6ABtQ8SpuOUfiUtn +vWFI7/3S4GCI5bkYYCjDdyutsDeqN95kWSpGV+RLufg3fNU254DBtvPUZ5uW6M7XxgpT0GtJlvOj +CwV3SPcl5XCsMBQgJeN/dVrlSPhOewMHBPqCYYdu8DvEpMfQ9XQ+pV0aCPKbJdL2rAQmPlU6Yiil +e7Iwr/g3wtG61jj99O9JMDeZJiFIhQGp5Rbn3JBV3w/oOM2ZNyFPXfUib2rFEhZgF1XyZWampzCR +OME4HYYEhLoaJXhena/MUGDWE4dS7WMfbWV9whUYdMrhfmQpjHLYFhN9C0lK8SgbIHRrxT3dsKpI +CT0ugpTNGmXZK4iambwYfp/ufWZ8Pr2UuIHOzZgweMFvZ9C+X+Bo7d7iscksWXiSqt8rYGPy5V65 +48r6f1CGPqI0GAwJaCgRHOThuVw+R7oyPxjMW4T182t0xHJ04eOLoEq9jWYv6q012iDTiIJh8BIi +trzQ1aTsr1SIJSQ8p22xcik/Plemf1WvbibG/ufMQFxRRIEKeN5KzlW/HdXZt1bv8Hb/C3m1r737 +qWmRRpdogBQ2HbN/uymYNqUg+oJgYjOk7Na6B6duxc8UpufWkjTYgfX8HV2qXB72o007uPc5AgMB +AAGjgZcwgZQwDwYDVR0TAQH/BAUwAwEB/zBSBgNVHSAESzBJMEcGBFUdIAAwPzA9BggrBgEFBQcC +ARYxaHR0cDovL3d3dy5wa2lvdmVyaGVpZC5ubC9wb2xpY2llcy9yb290LXBvbGljeS1HMjAOBgNV +HQ8BAf8EBAMCAQYwHQYDVR0OBBYEFJFoMocVHYnitfGsNig0jQt8YojrMA0GCSqGSIb3DQEBCwUA +A4ICAQCoQUpnKpKBglBu4dfYszk78wIVCVBR7y29JHuIhjv5tLySCZa59sCrI2AGeYwRTlHSeYAz ++51IvuxBQ4EffkdAHOV6CMqqi3WtFMTC6GY8ggen5ieCWxjmD27ZUD6KQhgpxrRW/FYQoAUXvQwj +f/ST7ZwaUb7dRUG/kSS0H4zpX897IZmflZ85OkYcbPnNe5yQzSipx6lVu6xiNGI1E0sUOlWDuYaN +kqbG9AclVMwWVxJKgnjIFNkXgiYtXSAfea7+1HAWFpWD2DU5/1JddRwWxRNVz0fMdWVSSt7wsKfk +CpYL+63C4iWEst3kvX5ZbJvw8NjnyvLplzh+ib7M+zkXYT9y2zqR2GUBGR2tUKRXCnxLvJxxcypF +URmFzI79R6d0lR2o0a9OF7FpJsKqeFdbxU2n5Z4FF5TKsl+gSRiNNOkmbEgeqmiSBeGCc1qb3Adb +CG19ndeNIdn8FCCqwkXfP+cAslHkwvgFuXkajDTznlvkN1trSt8sV4pAWja63XVECDdCcAz+3F4h +oKOKwJCcaNpQ5kUQR3i2TtJlycM33+FCY7BXN0Ute4qcvwXqZVUz9zkQxSgqIXobisQk+T8VyJoV +IPVVYpbtbZNQvOSqeK3Zywplh6ZmwcSBo3c6WB4L7oOLnR7SUqTMHW+wmG2UMbX4cQrcufx9MmDm +66+KAQ== +-----END CERTIFICATE----- + +CA Disig +======== +-----BEGIN CERTIFICATE----- +MIIEDzCCAvegAwIBAgIBATANBgkqhkiG9w0BAQUFADBKMQswCQYDVQQGEwJTSzETMBEGA1UEBxMK +QnJhdGlzbGF2YTETMBEGA1UEChMKRGlzaWcgYS5zLjERMA8GA1UEAxMIQ0EgRGlzaWcwHhcNMDYw +MzIyMDEzOTM0WhcNMTYwMzIyMDEzOTM0WjBKMQswCQYDVQQGEwJTSzETMBEGA1UEBxMKQnJhdGlz +bGF2YTETMBEGA1UEChMKRGlzaWcgYS5zLjERMA8GA1UEAxMIQ0EgRGlzaWcwggEiMA0GCSqGSIb3 +DQEBAQUAA4IBDwAwggEKAoIBAQCS9jHBfYj9mQGp2HvycXXxMcbzdWb6UShGhJd4NLxs/LxFWYgm +GErENx+hSkS943EE9UQX4j/8SFhvXJ56CbpRNyIjZkMhsDxkovhqFQ4/61HhVKndBpnXmjxUizkD +Pw/Fzsbrg3ICqB9x8y34dQjbYkzo+s7552oftms1grrijxaSfQUMbEYDXcDtab86wYqg6I7ZuUUo +hwjstMoVvoLdtUSLLa2GDGhibYVW8qwUYzrG0ZmsNHhWS8+2rT+MitcE5eN4TPWGqvWP+j1scaMt +ymfraHtuM6kMgiioTGohQBUgDCZbg8KpFhXAJIJdKxatymP2dACw30PEEGBWZ2NFAgMBAAGjgf8w +gfwwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUjbJJaJ1yCCW5wCf1UJNWSEZx+Y8wDgYDVR0P +AQH/BAQDAgEGMDYGA1UdEQQvMC2BE2Nhb3BlcmF0b3JAZGlzaWcuc2uGFmh0dHA6Ly93d3cuZGlz +aWcuc2svY2EwZgYDVR0fBF8wXTAtoCugKYYnaHR0cDovL3d3dy5kaXNpZy5zay9jYS9jcmwvY2Ff +ZGlzaWcuY3JsMCygKqAohiZodHRwOi8vY2EuZGlzaWcuc2svY2EvY3JsL2NhX2Rpc2lnLmNybDAa +BgNVHSAEEzARMA8GDSuBHpGT5goAAAABAQEwDQYJKoZIhvcNAQEFBQADggEBAF00dGFMrzvY/59t +WDYcPQuBDRIrRhCA/ec8J9B6yKm2fnQwM6M6int0wHl5QpNt/7EpFIKrIYwvF/k/Ji/1WcbvgAa3 +mkkp7M5+cTxqEEHA9tOasnxakZzArFvITV734VP/Q3f8nktnbNfzg9Gg4H8l37iYC5oyOGwwoPP/ +CBUz91BKez6jPiCp3C9WgArtQVCwyfTssuMmRAAOb54GvCKWU3BlxFAKRmukLyeBEicTXxChds6K +ezfqwzlhA5WYOudsiCUI/HloDYd9Yvi0X/vF2Ey9WLw/Q1vUHgFNPGO+I++MzVpQuGhU+QqZMxEA +4Z7CRneC9VkGjCFMhwnN5ag= +-----END CERTIFICATE----- + +Juur-SK +======= +-----BEGIN CERTIFICATE----- +MIIE5jCCA86gAwIBAgIEO45L/DANBgkqhkiG9w0BAQUFADBdMRgwFgYJKoZIhvcNAQkBFglwa2lA +c2suZWUxCzAJBgNVBAYTAkVFMSIwIAYDVQQKExlBUyBTZXJ0aWZpdHNlZXJpbWlza2Vza3VzMRAw +DgYDVQQDEwdKdXVyLVNLMB4XDTAxMDgzMDE0MjMwMVoXDTE2MDgyNjE0MjMwMVowXTEYMBYGCSqG +SIb3DQEJARYJcGtpQHNrLmVlMQswCQYDVQQGEwJFRTEiMCAGA1UEChMZQVMgU2VydGlmaXRzZWVy +aW1pc2tlc2t1czEQMA4GA1UEAxMHSnV1ci1TSzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC +ggEBAIFxNj4zB9bjMI0TfncyRsvPGbJgMUaXhvSYRqTCZUXP00B841oiqBB4M8yIsdOBSvZiF3tf +TQou0M+LI+5PAk676w7KvRhj6IAcjeEcjT3g/1tf6mTll+g/mX8MCgkzABpTpyHhOEvWgxutr2TC ++Rx6jGZITWYfGAriPrsfB2WThbkasLnE+w0R9vXW+RvHLCu3GFH+4Hv2qEivbDtPL+/40UceJlfw +UR0zlv/vWT3aTdEVNMfqPxZIe5EcgEMPPbgFPtGzlc3Yyg/CQ2fbt5PgIoIuvvVoKIO5wTtpeyDa +Tpxt4brNj3pssAki14sL2xzVWiZbDcDq5WDQn/413z8CAwEAAaOCAawwggGoMA8GA1UdEwEB/wQF +MAMBAf8wggEWBgNVHSAEggENMIIBCTCCAQUGCisGAQQBzh8BAQEwgfYwgdAGCCsGAQUFBwICMIHD +HoHAAFMAZQBlACAAcwBlAHIAdABpAGYAaQBrAGEAYQB0ACAAbwBuACAAdgDkAGwAagBhAHMAdABh +AHQAdQBkACAAQQBTAC0AaQBzACAAUwBlAHIAdABpAGYAaQB0AHMAZQBlAHIAaQBtAGkAcwBrAGUA +cwBrAHUAcwAgAGEAbABhAG0ALQBTAEsAIABzAGUAcgB0AGkAZgBpAGsAYQBhAHQAaQBkAGUAIABr +AGkAbgBuAGkAdABhAG0AaQBzAGUAawBzMCEGCCsGAQUFBwIBFhVodHRwOi8vd3d3LnNrLmVlL2Nw +cy8wKwYDVR0fBCQwIjAgoB6gHIYaaHR0cDovL3d3dy5zay5lZS9qdXVyL2NybC8wHQYDVR0OBBYE +FASqekej5ImvGs8KQKcYP2/v6X2+MB8GA1UdIwQYMBaAFASqekej5ImvGs8KQKcYP2/v6X2+MA4G +A1UdDwEB/wQEAwIB5jANBgkqhkiG9w0BAQUFAAOCAQEAe8EYlFOiCfP+JmeaUOTDBS8rNXiRTHyo +ERF5TElZrMj3hWVcRrs7EKACr81Ptcw2Kuxd/u+gkcm2k298gFTsxwhwDY77guwqYHhpNjbRxZyL +abVAyJRld/JXIWY7zoVAtjNjGr95HvxcHdMdkxuLDF2FvZkwMhgJkVLpfKG6/2SSmuz+Ne6ML678 +IIbsSt4beDI3poHSna9aEhbKmVv8b20OxaAehsmR0FyYgl9jDIpaq9iVpszLita/ZEuOyoqysOkh +Mp6qqIWYNIE5ITuoOlIyPfZrN4YGWhWY3PARZv40ILcD9EEQfTmEeZZyY7aWAuVrua0ZTbvGRNs2 +yyqcjg== +-----END CERTIFICATE----- + +Hongkong Post Root CA 1 +======================= +-----BEGIN CERTIFICATE----- +MIIDMDCCAhigAwIBAgICA+gwDQYJKoZIhvcNAQEFBQAwRzELMAkGA1UEBhMCSEsxFjAUBgNVBAoT +DUhvbmdrb25nIFBvc3QxIDAeBgNVBAMTF0hvbmdrb25nIFBvc3QgUm9vdCBDQSAxMB4XDTAzMDUx +NTA1MTMxNFoXDTIzMDUxNTA0NTIyOVowRzELMAkGA1UEBhMCSEsxFjAUBgNVBAoTDUhvbmdrb25n +IFBvc3QxIDAeBgNVBAMTF0hvbmdrb25nIFBvc3QgUm9vdCBDQSAxMIIBIjANBgkqhkiG9w0BAQEF +AAOCAQ8AMIIBCgKCAQEArP84tulmAknjorThkPlAj3n54r15/gK97iSSHSL22oVyaf7XPwnU3ZG1 +ApzQjVrhVcNQhrkpJsLj2aDxaQMoIIBFIi1WpztUlVYiWR8o3x8gPW2iNr4joLFutbEnPzlTCeqr +auh0ssJlXI6/fMN4hM2eFvz1Lk8gKgifd/PFHsSaUmYeSF7jEAaPIpjhZY4bXSNmO7ilMlHIhqqh +qZ5/dpTCpmy3QfDVyAY45tQM4vM7TG1QjMSDJ8EThFk9nnV0ttgCXjqQesBCNnLsak3c78QA3xMY +V18meMjWCnl3v/evt3a5pQuEF10Q6m/hq5URX208o1xNg1vysxmKgIsLhwIDAQABoyYwJDASBgNV +HRMBAf8ECDAGAQH/AgEDMA4GA1UdDwEB/wQEAwIBxjANBgkqhkiG9w0BAQUFAAOCAQEADkbVPK7i +h9legYsCmEEIjEy82tvuJxuC52pF7BaLT4Wg87JwvVqWuspube5Gi27nKi6Wsxkz67SfqLI37pio +l7Yutmcn1KZJ/RyTZXaeQi/cImyaT/JaFTmxcdcrUehtHJjA2Sr0oYJ71clBoiMBdDhViw+5Lmei +IAQ32pwL0xch4I+XeTRvhEgCIDMb5jREn5Fw9IBehEPCKdJsEhTkYY2sEJCehFC78JZvRZ+K88ps +T/oROhUVRsPNH4NbLUES7VBnQRM9IauUiqpOfMGx+6fWtScvl6tu4B3i0RwsH0Ti/L6RoZz71ilT +c4afU9hDDl3WY4JxHYB0yvbiAmvZWg== +-----END CERTIFICATE----- + +SecureSign RootCA11 +=================== +-----BEGIN CERTIFICATE----- +MIIDbTCCAlWgAwIBAgIBATANBgkqhkiG9w0BAQUFADBYMQswCQYDVQQGEwJKUDErMCkGA1UEChMi +SmFwYW4gQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcywgSW5jLjEcMBoGA1UEAxMTU2VjdXJlU2lnbiBS +b290Q0ExMTAeFw0wOTA0MDgwNDU2NDdaFw0yOTA0MDgwNDU2NDdaMFgxCzAJBgNVBAYTAkpQMSsw +KQYDVQQKEyJKYXBhbiBDZXJ0aWZpY2F0aW9uIFNlcnZpY2VzLCBJbmMuMRwwGgYDVQQDExNTZWN1 +cmVTaWduIFJvb3RDQTExMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA/XeqpRyQBTvL +TJszi1oURaTnkBbR31fSIRCkF/3frNYfp+TbfPfs37gD2pRY/V1yfIw/XwFndBWW4wI8h9uuywGO +wvNmxoVF9ALGOrVisq/6nL+k5tSAMJjzDbaTj6nU2DbysPyKyiyhFTOVMdrAG/LuYpmGYz+/3ZMq +g6h2uRMft85OQoWPIucuGvKVCbIFtUROd6EgvanyTgp9UK31BQ1FT0Zx/Sg+U/sE2C3XZR1KG/rP +O7AxmjVuyIsG0wCR8pQIZUyxNAYAeoni8McDWc/V1uinMrPmmECGxc0nEovMe863ETxiYAcjPitA +bpSACW22s293bzUIUPsCh8U+iQIDAQABo0IwQDAdBgNVHQ4EFgQUW/hNT7KlhtQ60vFjmqC+CfZX +t94wDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAKCh +OBZmLqdWHyGcBvod7bkixTgm2E5P7KN/ed5GIaGHd48HCJqypMWvDzKYC3xmKbabfSVSSUOrTC4r +bnpwrxYO4wJs+0LmGJ1F2FXI6Dvd5+H0LgscNFxsWEr7jIhQX5Ucv+2rIrVls4W6ng+4reV6G4pQ +Oh29Dbx7VFALuUKvVaAYga1lme++5Jy/xIWrQbJUb9wlze144o4MjQlJ3WN7WmmWAiGovVJZ6X01 +y8hSyn+B/tlr0/cR7SXf+Of5pPpyl4RTDaXQMhhRdlkUbA/r7F+AjHVDg8OFmP9Mni0N5HeDk061 +lgeLKBObjBmNQSdJQO7e5iNEOdyhIta6A/I= +-----END CERTIFICATE----- + +ACEDICOM Root +============= +-----BEGIN CERTIFICATE----- +MIIFtTCCA52gAwIBAgIIYY3HhjsBggUwDQYJKoZIhvcNAQEFBQAwRDEWMBQGA1UEAwwNQUNFRElD +T00gUm9vdDEMMAoGA1UECwwDUEtJMQ8wDQYDVQQKDAZFRElDT00xCzAJBgNVBAYTAkVTMB4XDTA4 +MDQxODE2MjQyMloXDTI4MDQxMzE2MjQyMlowRDEWMBQGA1UEAwwNQUNFRElDT00gUm9vdDEMMAoG +A1UECwwDUEtJMQ8wDQYDVQQKDAZFRElDT00xCzAJBgNVBAYTAkVTMIICIjANBgkqhkiG9w0BAQEF +AAOCAg8AMIICCgKCAgEA/5KV4WgGdrQsyFhIyv2AVClVYyT/kGWbEHV7w2rbYgIB8hiGtXxaOLHk +WLn709gtn70yN78sFW2+tfQh0hOR2QetAQXW8713zl9CgQr5auODAKgrLlUTY4HKRxx7XBZXehuD +YAQ6PmXDzQHe3qTWDLqO3tkE7hdWIpuPY/1NFgu3e3eM+SW10W2ZEi5PGrjm6gSSrj0RuVFCPYew +MYWveVqc/udOXpJPQ/yrOq2lEiZmueIM15jO1FillUAKt0SdE3QrwqXrIhWYENiLxQSfHY9g5QYb +m8+5eaA9oiM/Qj9r+hwDezCNzmzAv+YbX79nuIQZ1RXve8uQNjFiybwCq0Zfm/4aaJQ0PZCOrfbk +HQl/Sog4P75n/TSW9R28MHTLOO7VbKvU/PQAtwBbhTIWdjPp2KOZnQUAqhbm84F9b32qhm2tFXTT +xKJxqvQUfecyuB+81fFOvW8XAjnXDpVCOscAPukmYxHqC9FK/xidstd7LzrZlvvoHpKuE1XI2Sf2 +3EgbsCTBheN3nZqk8wwRHQ3ItBTutYJXCb8gWH8vIiPYcMt5bMlL8qkqyPyHK9caUPgn6C9D4zq9 +2Fdx/c6mUlv53U3t5fZvie27k5x2IXXwkkwp9y+cAS7+UEaeZAwUswdbxcJzbPEHXEUkFDWug/Fq +TYl6+rPYLWbwNof1K1MCAwEAAaOBqjCBpzAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFKaz +4SsrSbbXc6GqlPUB53NlTKxQMA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUprPhKytJttdzoaqU +9QHnc2VMrFAwRAYDVR0gBD0wOzA5BgRVHSAAMDEwLwYIKwYBBQUHAgEWI2h0dHA6Ly9hY2VkaWNv +bS5lZGljb21ncm91cC5jb20vZG9jMA0GCSqGSIb3DQEBBQUAA4ICAQDOLAtSUWImfQwng4/F9tqg +aHtPkl7qpHMyEVNEskTLnewPeUKzEKbHDZ3Ltvo/Onzqv4hTGzz3gvoFNTPhNahXwOf9jU8/kzJP +eGYDdwdY6ZXIfj7QeQCM8htRM5u8lOk6e25SLTKeI6RF+7YuE7CLGLHdztUdp0J/Vb77W7tH1Pwk +zQSulgUV1qzOMPPKC8W64iLgpq0i5ALudBF/TP94HTXa5gI06xgSYXcGCRZj6hitoocf8seACQl1 +ThCojz2GuHURwCRiipZ7SkXp7FnFvmuD5uHorLUwHv4FB4D54SMNUI8FmP8sX+g7tq3PgbUhh8oI +KiMnMCArz+2UW6yyetLHKKGKC5tNSixthT8Jcjxn4tncB7rrZXtaAWPWkFtPF2Y9fwsZo5NjEFIq +nxQWWOLcpfShFosOkYuByptZ+thrkQdlVV9SH686+5DdaaVbnG0OLLb6zqylfDJKZ0DcMDQj3dcE +I2bw/FWAp/tmGYI1Z2JwOV5vx+qQQEQIHriy1tvuWacNGHk0vFQYXlPKNFHtRQrmjseCNj6nOGOp +MCwXEGCSn1WHElkQwg9naRHMTh5+Spqtr0CodaxWkHS4oJyleW/c6RrIaQXpuvoDs3zk4E7Czp3o +tkYNbn5XOmeUwssfnHdKZ05phkOTOPu220+DkdRgfks+KzgHVZhepA== +-----END CERTIFICATE----- + +Verisign Class 3 Public Primary Certification Authority +======================================================= +-----BEGIN CERTIFICATE----- +MIICPDCCAaUCEDyRMcsf9tAbDpq40ES/Er4wDQYJKoZIhvcNAQEFBQAwXzELMAkGA1UEBhMCVVMx +FzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAzIFB1YmxpYyBQcmltYXJ5 +IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTk2MDEyOTAwMDAwMFoXDTI4MDgwMjIzNTk1OVow +XzELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAz +IFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGfMA0GCSqGSIb3DQEBAQUA +A4GNADCBiQKBgQDJXFme8huKARS0EN8EQNvjV69qRUCPhAwL0TPZ2RHP7gJYHyX3KqhEBarsAx94 +f56TuZoAqiN91qyFomNFx3InzPRMxnVx0jnvT0Lwdd8KkMaOIG+YD/isI19wKTakyYbnsZogy1Ol +hec9vn2a/iRFM9x2Fe0PonFkTGUugWhFpwIDAQABMA0GCSqGSIb3DQEBBQUAA4GBABByUqkFFBky +CEHwxWsKzH4PIRnN5GfcX6kb5sroc50i2JhucwNhkcV8sEVAbkSdjbCxlnRhLQ2pRdKkkirWmnWX +bj9T/UWZYB2oK0z5XqcJ2HUw19JlYD1n1khVdWk/kfVIC0dpImmClr7JyDiGSnoscxlIaU5rfGW/ +D/xwzoiQ +-----END CERTIFICATE----- + +Microsec e-Szigno Root CA 2009 +============================== +-----BEGIN CERTIFICATE----- +MIIECjCCAvKgAwIBAgIJAMJ+QwRORz8ZMA0GCSqGSIb3DQEBCwUAMIGCMQswCQYDVQQGEwJIVTER +MA8GA1UEBwwIQnVkYXBlc3QxFjAUBgNVBAoMDU1pY3Jvc2VjIEx0ZC4xJzAlBgNVBAMMHk1pY3Jv +c2VjIGUtU3ppZ25vIFJvb3QgQ0EgMjAwOTEfMB0GCSqGSIb3DQEJARYQaW5mb0BlLXN6aWduby5o +dTAeFw0wOTA2MTYxMTMwMThaFw0yOTEyMzAxMTMwMThaMIGCMQswCQYDVQQGEwJIVTERMA8GA1UE +BwwIQnVkYXBlc3QxFjAUBgNVBAoMDU1pY3Jvc2VjIEx0ZC4xJzAlBgNVBAMMHk1pY3Jvc2VjIGUt +U3ppZ25vIFJvb3QgQ0EgMjAwOTEfMB0GCSqGSIb3DQEJARYQaW5mb0BlLXN6aWduby5odTCCASIw +DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOn4j/NjrdqG2KfgQvvPkd6mJviZpWNwrZuuyjNA +fW2WbqEORO7hE52UQlKavXWFdCyoDh2Tthi3jCyoz/tccbna7P7ofo/kLx2yqHWH2Leh5TvPmUpG +0IMZfcChEhyVbUr02MelTTMuhTlAdX4UfIASmFDHQWe4oIBhVKZsTh/gnQ4H6cm6M+f+wFUoLAKA +pxn1ntxVUwOXewdI/5n7N4okxFnMUBBjjqqpGrCEGob5X7uxUG6k0QrM1XF+H6cbfPVTbiJfyyvm +1HxdrtbCxkzlBQHZ7Vf8wSN5/PrIJIOV87VqUQHQd9bpEqH5GoP7ghu5sJf0dgYzQ0mg/wu1+rUC +AwEAAaOBgDB+MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBTLD8bf +QkPMPcu1SCOhGnqmKrs0aDAfBgNVHSMEGDAWgBTLD8bfQkPMPcu1SCOhGnqmKrs0aDAbBgNVHREE +FDASgRBpbmZvQGUtc3ppZ25vLmh1MA0GCSqGSIb3DQEBCwUAA4IBAQDJ0Q5eLtXMs3w+y/w9/w0o +lZMEyL/azXm4Q5DwpL7v8u8hmLzU1F0G9u5C7DBsoKqpyvGvivo/C3NqPuouQH4frlRheesuCDfX +I/OMn74dseGkddug4lQUsbocKaQY9hK6ohQU4zE1yED/t+AFdlfBHFny+L/k7SViXITwfn4fs775 +tyERzAMBVnCnEJIeGzSBHq2cGsMEPO0CYdYeBvNfOofyK/FFh+U9rNHHV4S9a67c2Pm2G2JwCz02 +yULyMtd6YebS2z3PyKnJm9zbWETXbzivf3jTo60adbocwTZ8jx5tHMN1Rq41Bab2XD0h7lbwyYIi +LXpUq3DDfSJlgnCW +-----END CERTIFICATE----- + +E-Guven Kok Elektronik Sertifika Hizmet Saglayicisi +=================================================== +-----BEGIN CERTIFICATE----- +MIIDtjCCAp6gAwIBAgIQRJmNPMADJ72cdpW56tustTANBgkqhkiG9w0BAQUFADB1MQswCQYDVQQG +EwJUUjEoMCYGA1UEChMfRWxla3Ryb25payBCaWxnaSBHdXZlbmxpZ2kgQS5TLjE8MDoGA1UEAxMz +ZS1HdXZlbiBLb2sgRWxla3Ryb25payBTZXJ0aWZpa2EgSGl6bWV0IFNhZ2xheWljaXNpMB4XDTA3 +MDEwNDExMzI0OFoXDTE3MDEwNDExMzI0OFowdTELMAkGA1UEBhMCVFIxKDAmBgNVBAoTH0VsZWt0 +cm9uaWsgQmlsZ2kgR3V2ZW5saWdpIEEuUy4xPDA6BgNVBAMTM2UtR3V2ZW4gS29rIEVsZWt0cm9u +aWsgU2VydGlmaWthIEhpem1ldCBTYWdsYXlpY2lzaTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC +AQoCggEBAMMSIJ6wXgBljU5Gu4Bc6SwGl9XzcslwuedLZYDBS75+PNdUMZTe1RK6UxYC6lhj71vY +8+0qGqpxSKPcEC1fX+tcS5yWCEIlKBHMilpiAVDV6wlTL/jDj/6z/P2douNffb7tC+Bg62nsM+3Y +jfsSSYMAyYuXjDtzKjKzEve5TfL0TW3H5tYmNwjy2f1rXKPlSFxYvEK+A1qBuhw1DADT9SN+cTAI +JjjcJRFHLfO6IxClv7wC90Nex/6wN1CZew+TzuZDLMN+DfIcQ2Zgy2ExR4ejT669VmxMvLz4Bcpk +9Ok0oSy1c+HCPujIyTQlCFzz7abHlJ+tiEMl1+E5YP6sOVkCAwEAAaNCMEAwDgYDVR0PAQH/BAQD +AgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFJ/uRLOU1fqRTy7ZVZoEVtstxNulMA0GCSqG +SIb3DQEBBQUAA4IBAQB/X7lTW2M9dTLn+sR0GstG30ZpHFLPqk/CaOv/gKlR6D1id4k9CnU58W5d +F4dvaAXBlGzZXd/aslnLpRCKysw5zZ/rTt5S/wzw9JKp8mxTq5vSR6AfdPebmvEvFZ96ZDAYBzwq +D2fK/A+JYZ1lpTzlvBNbCNvj/+27BrtqBrF6T2XGgv0enIu1De5Iu7i9qgi0+6N8y5/NkHZchpZ4 +Vwpm+Vganf2XKWDeEaaQHBkc7gGWIjQ0LpH5t8Qn0Xvmv/uARFoW5evg1Ao4vOSR49XrXMGs3xtq +fJ7lddK2l4fbzIcrQzqECK+rPNv3PGYxhrCdU3nt+CPeQuMtgvEP5fqX +-----END CERTIFICATE----- + +GlobalSign Root CA - R3 +======================= +-----BEGIN CERTIFICATE----- +MIIDXzCCAkegAwIBAgILBAAAAAABIVhTCKIwDQYJKoZIhvcNAQELBQAwTDEgMB4GA1UECxMXR2xv +YmFsU2lnbiBSb290IENBIC0gUjMxEzARBgNVBAoTCkdsb2JhbFNpZ24xEzARBgNVBAMTCkdsb2Jh +bFNpZ24wHhcNMDkwMzE4MTAwMDAwWhcNMjkwMzE4MTAwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxT +aWduIFJvb3QgQ0EgLSBSMzETMBEGA1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2ln +bjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMwldpB5BngiFvXAg7aEyiie/QV2EcWt +iHL8RgJDx7KKnQRfJMsuS+FggkbhUqsMgUdwbN1k0ev1LKMPgj0MK66X17YUhhB5uzsTgHeMCOFJ +0mpiLx9e+pZo34knlTifBtc+ycsmWQ1z3rDI6SYOgxXG71uL0gRgykmmKPZpO/bLyCiR5Z2KYVc3 +rHQU3HTgOu5yLy6c+9C7v/U9AOEGM+iCK65TpjoWc4zdQQ4gOsC0p6Hpsk+QLjJg6VfLuQSSaGjl +OCZgdbKfd/+RFO+uIEn8rUAVSNECMWEZXriX7613t2Saer9fwRPvm2L7DWzgVGkWqQPabumDk3F2 +xmmFghcCAwEAAaNCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYE +FI/wS3+oLkUkrk1Q+mOai97i3Ru8MA0GCSqGSIb3DQEBCwUAA4IBAQBLQNvAUKr+yAzv95ZURUm7 +lgAJQayzE4aGKAczymvmdLm6AC2upArT9fHxD4q/c2dKg8dEe3jgr25sbwMpjjM5RcOO5LlXbKr8 +EpbsU8Yt5CRsuZRj+9xTaGdWPoO4zzUhw8lo/s7awlOqzJCK6fBdRoyV3XpYKBovHd7NADdBj+1E +bddTKJd+82cEHhXXipa0095MJ6RMG3NzdvQXmcIfeg7jLQitChws/zyrVQ4PkX4268NXSb7hLi18 +YIvDQVETI53O9zJrlAGomecsMx86OyXShkDOOyyGeMlhLxS67ttVb9+E7gUJTb0o2HLO02JQZR7r +kpeDMdmztcpHWD9f +-----END CERTIFICATE----- + +Autoridad de Certificacion Firmaprofesional CIF A62634068 +========================================================= +-----BEGIN CERTIFICATE----- +MIIGFDCCA/ygAwIBAgIIU+w77vuySF8wDQYJKoZIhvcNAQEFBQAwUTELMAkGA1UEBhMCRVMxQjBA +BgNVBAMMOUF1dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uIEZpcm1hcHJvZmVzaW9uYWwgQ0lGIEE2 +MjYzNDA2ODAeFw0wOTA1MjAwODM4MTVaFw0zMDEyMzEwODM4MTVaMFExCzAJBgNVBAYTAkVTMUIw +QAYDVQQDDDlBdXRvcmlkYWQgZGUgQ2VydGlmaWNhY2lvbiBGaXJtYXByb2Zlc2lvbmFsIENJRiBB +NjI2MzQwNjgwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDKlmuO6vj78aI14H9M2uDD +Utd9thDIAl6zQyrET2qyyhxdKJp4ERppWVevtSBC5IsP5t9bpgOSL/UR5GLXMnE42QQMcas9UX4P +B99jBVzpv5RvwSmCwLTaUbDBPLutN0pcyvFLNg4kq7/DhHf9qFD0sefGL9ItWY16Ck6WaVICqjaY +7Pz6FIMMNx/Jkjd/14Et5cS54D40/mf0PmbR0/RAz15iNA9wBj4gGFrO93IbJWyTdBSTo3OxDqqH +ECNZXyAFGUftaI6SEspd/NYrspI8IM/hX68gvqB2f3bl7BqGYTM+53u0P6APjqK5am+5hyZvQWyI +plD9amML9ZMWGxmPsu2bm8mQ9QEM3xk9Dz44I8kvjwzRAv4bVdZO0I08r0+k8/6vKtMFnXkIoctX +MbScyJCyZ/QYFpM6/EfY0XiWMR+6KwxfXZmtY4laJCB22N/9q06mIqqdXuYnin1oKaPnirjaEbsX +LZmdEyRG98Xi2J+Of8ePdG1asuhy9azuJBCtLxTa/y2aRnFHvkLfuwHb9H/TKI8xWVvTyQKmtFLK +bpf7Q8UIJm+K9Lv9nyiqDdVF8xM6HdjAeI9BZzwelGSuewvF6NkBiDkal4ZkQdU7hwxu+g/GvUgU +vzlN1J5Bto+WHWOWk9mVBngxaJ43BjuAiUVhOSPHG0SjFeUc+JIwuwIDAQABo4HvMIHsMBIGA1Ud +EwEB/wQIMAYBAf8CAQEwDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBRlzeurNR4APn7VdMActHNH +DhpkLzCBpgYDVR0gBIGeMIGbMIGYBgRVHSAAMIGPMC8GCCsGAQUFBwIBFiNodHRwOi8vd3d3LmZp +cm1hcHJvZmVzaW9uYWwuY29tL2NwczBcBggrBgEFBQcCAjBQHk4AUABhAHMAZQBvACAAZABlACAA +bABhACAAQgBvAG4AYQBuAG8AdgBhACAANAA3ACAAQgBhAHIAYwBlAGwAbwBuAGEAIAAwADgAMAAx +ADcwDQYJKoZIhvcNAQEFBQADggIBABd9oPm03cXF661LJLWhAqvdpYhKsg9VSytXjDvlMd3+xDLx +51tkljYyGOylMnfX40S2wBEqgLk9am58m9Ot/MPWo+ZkKXzR4Tgegiv/J2Wv+xYVxC5xhOW1//qk +R71kMrv2JYSiJ0L1ILDCExARzRAVukKQKtJE4ZYm6zFIEv0q2skGz3QeqUvVhyj5eTSSPi5E6PaP +T481PyWzOdxjKpBrIF/EUhJOlywqrJ2X3kjyo2bbwtKDlaZmp54lD+kLM5FlClrD2VQS3a/DTg4f +Jl4N3LON7NWBcN7STyQF82xO9UxJZo3R/9ILJUFI/lGExkKvgATP0H5kSeTy36LssUzAKh3ntLFl +osS88Zj0qnAHY7S42jtM+kAiMFsRpvAFDsYCA0irhpuF3dvd6qJ2gHN99ZwExEWN57kci57q13XR +crHedUTnQn3iV2t93Jm8PYMo6oCTjcVMZcFwgbg4/EMxsvYDNEeyrPsiBsse3RdHHF9mudMaotoR +saS8I8nkvof/uZS2+F0gStRf571oe2XyFR7SOqkt6dhrJKyXWERHrVkY8SFlcN7ONGCoQPHzPKTD +KCOM/iczQ0CgFzzr6juwcqajuUpLXhZI9LK8yIySxZ2frHI2vDSANGupi5LAuBft7HZT9SQBjLMi +6Et8Vcad+qMUu2WFbm5PEn4KPJ2V +-----END CERTIFICATE----- + +Izenpe.com +========== +-----BEGIN CERTIFICATE----- +MIIF8TCCA9mgAwIBAgIQALC3WhZIX7/hy/WL1xnmfTANBgkqhkiG9w0BAQsFADA4MQswCQYDVQQG +EwJFUzEUMBIGA1UECgwLSVpFTlBFIFMuQS4xEzARBgNVBAMMCkl6ZW5wZS5jb20wHhcNMDcxMjEz +MTMwODI4WhcNMzcxMjEzMDgyNzI1WjA4MQswCQYDVQQGEwJFUzEUMBIGA1UECgwLSVpFTlBFIFMu +QS4xEzARBgNVBAMMCkl6ZW5wZS5jb20wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDJ +03rKDx6sp4boFmVqscIbRTJxldn+EFvMr+eleQGPicPK8lVx93e+d5TzcqQsRNiekpsUOqHnJJAK +ClaOxdgmlOHZSOEtPtoKct2jmRXagaKH9HtuJneJWK3W6wyyQXpzbm3benhB6QiIEn6HLmYRY2xU ++zydcsC8Lv/Ct90NduM61/e0aL6i9eOBbsFGb12N4E3GVFWJGjMxCrFXuaOKmMPsOzTFlUFpfnXC +PCDFYbpRR6AgkJOhkEvzTnyFRVSa0QUmQbC1TR0zvsQDyCV8wXDbO/QJLVQnSKwv4cSsPsjLkkxT +OTcj7NMB+eAJRE1NZMDhDVqHIrytG6P+JrUV86f8hBnp7KGItERphIPzidF0BqnMC9bC3ieFUCbK +F7jJeodWLBoBHmy+E60QrLUk9TiRodZL2vG70t5HtfG8gfZZa88ZU+mNFctKy6lvROUbQc/hhqfK +0GqfvEyNBjNaooXlkDWgYlwWTvDjovoDGrQscbNYLN57C9saD+veIR8GdwYDsMnvmfzAuU8Lhij+ +0rnq49qlw0dpEuDb8PYZi+17cNcC1u2HGCgsBCRMd+RIihrGO5rUD8r6ddIBQFqNeb+Lz0vPqhbB +leStTIo+F5HUsWLlguWABKQDfo2/2n+iD5dPDNMN+9fR5XJ+HMh3/1uaD7euBUbl8agW7EekFwID +AQABo4H2MIHzMIGwBgNVHREEgagwgaWBD2luZm9AaXplbnBlLmNvbaSBkTCBjjFHMEUGA1UECgw+ +SVpFTlBFIFMuQS4gLSBDSUYgQTAxMzM3MjYwLVJNZXJjLlZpdG9yaWEtR2FzdGVpeiBUMTA1NSBG +NjIgUzgxQzBBBgNVBAkMOkF2ZGEgZGVsIE1lZGl0ZXJyYW5lbyBFdG9yYmlkZWEgMTQgLSAwMTAx +MCBWaXRvcmlhLUdhc3RlaXowDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0O +BBYEFB0cZQ6o8iV7tJHP5LGx5r1VdGwFMA0GCSqGSIb3DQEBCwUAA4ICAQB4pgwWSp9MiDrAyw6l +Fn2fuUhfGI8NYjb2zRlrrKvV9pF9rnHzP7MOeIWblaQnIUdCSnxIOvVFfLMMjlF4rJUT3sb9fbga +kEyrkgPH7UIBzg/YsfqikuFgba56awmqxinuaElnMIAkejEWOVt+8Rwu3WwJrfIxwYJOubv5vr8q +hT/AQKM6WfxZSzwoJNu0FXWuDYi6LnPAvViH5ULy617uHjAimcs30cQhbIHsvm0m5hzkQiCeR7Cs +g1lwLDXWrzY0tM07+DKo7+N4ifuNRSzanLh+QBxh5z6ikixL8s36mLYp//Pye6kfLqCTVyvehQP5 +aTfLnnhqBbTFMXiJ7HqnheG5ezzevh55hM6fcA5ZwjUukCox2eRFekGkLhObNA5me0mrZJfQRsN5 +nXJQY6aYWwa9SG3YOYNw6DXwBdGqvOPbyALqfP2C2sJbUjWumDqtujWTI6cfSN01RpiyEGjkpTHC +ClguGYEQyVB1/OpaFs4R1+7vUIgtYf8/QnMFlEPVjjxOAToZpR9GTnfQXeWBIiGH/pR9hNiTrdZo +Q0iy2+tzJOeRf1SktoA+naM8THLCV8Sg1Mw4J87VBp6iSNnpn86CcDaTmjvfliHjWbcM2pE38P1Z +WrOZyGlsQyYBNWNgVYkDOnXYukrZVP/u3oDYLdE41V4tC5h9Pmzb/CaIxw== +-----END CERTIFICATE----- + +Chambers of Commerce Root - 2008 +================================ +-----BEGIN CERTIFICATE----- +MIIHTzCCBTegAwIBAgIJAKPaQn6ksa7aMA0GCSqGSIb3DQEBBQUAMIGuMQswCQYDVQQGEwJFVTFD +MEEGA1UEBxM6TWFkcmlkIChzZWUgY3VycmVudCBhZGRyZXNzIGF0IHd3dy5jYW1lcmZpcm1hLmNv +bS9hZGRyZXNzKTESMBAGA1UEBRMJQTgyNzQzMjg3MRswGQYDVQQKExJBQyBDYW1lcmZpcm1hIFMu +QS4xKTAnBgNVBAMTIENoYW1iZXJzIG9mIENvbW1lcmNlIFJvb3QgLSAyMDA4MB4XDTA4MDgwMTEy +Mjk1MFoXDTM4MDczMTEyMjk1MFowga4xCzAJBgNVBAYTAkVVMUMwQQYDVQQHEzpNYWRyaWQgKHNl +ZSBjdXJyZW50IGFkZHJlc3MgYXQgd3d3LmNhbWVyZmlybWEuY29tL2FkZHJlc3MpMRIwEAYDVQQF +EwlBODI3NDMyODcxGzAZBgNVBAoTEkFDIENhbWVyZmlybWEgUy5BLjEpMCcGA1UEAxMgQ2hhbWJl +cnMgb2YgQ29tbWVyY2UgUm9vdCAtIDIwMDgwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoIC +AQCvAMtwNyuAWko6bHiUfaN/Gh/2NdW928sNRHI+JrKQUrpjOyhYb6WzbZSm891kDFX29ufyIiKA +XuFixrYp4YFs8r/lfTJqVKAyGVn+H4vXPWCGhSRv4xGzdz4gljUha7MI2XAuZPeEklPWDrCQiorj +h40G072QDuKZoRuGDtqaCrsLYVAGUvGef3bsyw/QHg3PmTA9HMRFEFis1tPo1+XqxQEHd9ZR5gN/ +ikilTWh1uem8nk4ZcfUyS5xtYBkL+8ydddy/Js2Pk3g5eXNeJQ7KXOt3EgfLZEFHcpOrUMPrCXZk +NNI5t3YRCQ12RcSprj1qr7V9ZS+UWBDsXHyvfuK2GNnQm05aSd+pZgvMPMZ4fKecHePOjlO+Bd5g +D2vlGts/4+EhySnB8esHnFIbAURRPHsl18TlUlRdJQfKFiC4reRB7noI/plvg6aRArBsNlVq5331 +lubKgdaX8ZSD6e2wsWsSaR6s+12pxZjptFtYer49okQ6Y1nUCyXeG0+95QGezdIp1Z8XGQpvvwyQ +0wlf2eOKNcx5Wk0ZN5K3xMGtr/R5JJqyAQuxr1yW84Ay+1w9mPGgP0revq+ULtlVmhduYJ1jbLhj +ya6BXBg14JC7vjxPNyK5fuvPnnchpj04gftI2jE9K+OJ9dC1vX7gUMQSibMjmhAxhduub+84Mxh2 +EQIDAQABo4IBbDCCAWgwEgYDVR0TAQH/BAgwBgEB/wIBDDAdBgNVHQ4EFgQU+SSsD7K1+HnA+mCI +G8TZTQKeFxkwgeMGA1UdIwSB2zCB2IAU+SSsD7K1+HnA+mCIG8TZTQKeFxmhgbSkgbEwga4xCzAJ +BgNVBAYTAkVVMUMwQQYDVQQHEzpNYWRyaWQgKHNlZSBjdXJyZW50IGFkZHJlc3MgYXQgd3d3LmNh +bWVyZmlybWEuY29tL2FkZHJlc3MpMRIwEAYDVQQFEwlBODI3NDMyODcxGzAZBgNVBAoTEkFDIENh +bWVyZmlybWEgUy5BLjEpMCcGA1UEAxMgQ2hhbWJlcnMgb2YgQ29tbWVyY2UgUm9vdCAtIDIwMDiC +CQCj2kJ+pLGu2jAOBgNVHQ8BAf8EBAMCAQYwPQYDVR0gBDYwNDAyBgRVHSAAMCowKAYIKwYBBQUH +AgEWHGh0dHA6Ly9wb2xpY3kuY2FtZXJmaXJtYS5jb20wDQYJKoZIhvcNAQEFBQADggIBAJASryI1 +wqM58C7e6bXpeHxIvj99RZJe6dqxGfwWPJ+0W2aeaufDuV2I6A+tzyMP3iU6XsxPpcG1Lawk0lgH +3qLPaYRgM+gQDROpI9CF5Y57pp49chNyM/WqfcZjHwj0/gF/JM8rLFQJ3uIrbZLGOU8W6jx+ekbU +RWpGqOt1glanq6B8aBMz9p0w8G8nOSQjKpD9kCk18pPfNKXG9/jvjA9iSnyu0/VU+I22mlaHFoI6 +M6taIgj3grrqLuBHmrS1RaMFO9ncLkVAO+rcf+g769HsJtg1pDDFOqxXnrN2pSB7+R5KBWIBpih1 +YJeSDW4+TTdDDZIVnBgizVGZoCkaPF+KMjNbMMeJL0eYD6MDxvbxrN8y8NmBGuScvfaAFPDRLLmF +9dijscilIeUcE5fuDr3fKanvNFNb0+RqE4QGtjICxFKuItLcsiFCGtpA8CnJ7AoMXOLQusxI0zcK +zBIKinmwPQN/aUv0NCB9szTqjktk9T79syNnFQ0EuPAtwQlRPLJsFfClI9eDdOTlLsn+mCdCxqvG +nrDQWzilm1DefhiYtUU79nm06PcaewaD+9CL2rvHvRirCG88gGtAPxkZumWK5r7VXNM21+9AUiRg +OGcEMeyP84LG3rlV8zsxkVrctQgVrXYlCg17LofiDKYGvCYQbTed7N14jHyAxfDZd0jQ +-----END CERTIFICATE----- + +Global Chambersign Root - 2008 +============================== +-----BEGIN CERTIFICATE----- +MIIHSTCCBTGgAwIBAgIJAMnN0+nVfSPOMA0GCSqGSIb3DQEBBQUAMIGsMQswCQYDVQQGEwJFVTFD +MEEGA1UEBxM6TWFkcmlkIChzZWUgY3VycmVudCBhZGRyZXNzIGF0IHd3dy5jYW1lcmZpcm1hLmNv +bS9hZGRyZXNzKTESMBAGA1UEBRMJQTgyNzQzMjg3MRswGQYDVQQKExJBQyBDYW1lcmZpcm1hIFMu +QS4xJzAlBgNVBAMTHkdsb2JhbCBDaGFtYmVyc2lnbiBSb290IC0gMjAwODAeFw0wODA4MDExMjMx +NDBaFw0zODA3MzExMjMxNDBaMIGsMQswCQYDVQQGEwJFVTFDMEEGA1UEBxM6TWFkcmlkIChzZWUg +Y3VycmVudCBhZGRyZXNzIGF0IHd3dy5jYW1lcmZpcm1hLmNvbS9hZGRyZXNzKTESMBAGA1UEBRMJ +QTgyNzQzMjg3MRswGQYDVQQKExJBQyBDYW1lcmZpcm1hIFMuQS4xJzAlBgNVBAMTHkdsb2JhbCBD +aGFtYmVyc2lnbiBSb290IC0gMjAwODCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMDf +VtPkOpt2RbQT2//BthmLN0EYlVJH6xedKYiONWwGMi5HYvNJBL99RDaxccy9Wglz1dmFRP+RVyXf +XjaOcNFccUMd2drvXNL7G706tcuto8xEpw2uIRU/uXpbknXYpBI4iRmKt4DS4jJvVpyR1ogQC7N0 +ZJJ0YPP2zxhPYLIj0Mc7zmFLmY/CDNBAspjcDahOo7kKrmCgrUVSY7pmvWjg+b4aqIG7HkF4ddPB +/gBVsIdU6CeQNR1MM62X/JcumIS/LMmjv9GYERTtY/jKmIhYF5ntRQOXfjyGHoiMvvKRhI9lNNgA +TH23MRdaKXoKGCQwoze1eqkBfSbW+Q6OWfH9GzO1KTsXO0G2Id3UwD2ln58fQ1DJu7xsepeY7s2M +H/ucUa6LcL0nn3HAa6x9kGbo1106DbDVwo3VyJ2dwW3Q0L9R5OP4wzg2rtandeavhENdk5IMagfe +Ox2YItaswTXbo6Al/3K1dh3ebeksZixShNBFks4c5eUzHdwHU1SjqoI7mjcv3N2gZOnm3b2u/GSF +HTynyQbehP9r6GsaPMWis0L7iwk+XwhSx2LE1AVxv8Rk5Pihg+g+EpuoHtQ2TS9x9o0o9oOpE9Jh +wZG7SMA0j0GMS0zbaRL/UJScIINZc+18ofLx/d33SdNDWKBWY8o9PeU1VlnpDsogzCtLkykPAgMB +AAGjggFqMIIBZjASBgNVHRMBAf8ECDAGAQH/AgEMMB0GA1UdDgQWBBS5CcqcHtvTbDprru1U8VuT +BjUuXjCB4QYDVR0jBIHZMIHWgBS5CcqcHtvTbDprru1U8VuTBjUuXqGBsqSBrzCBrDELMAkGA1UE +BhMCRVUxQzBBBgNVBAcTOk1hZHJpZCAoc2VlIGN1cnJlbnQgYWRkcmVzcyBhdCB3d3cuY2FtZXJm +aXJtYS5jb20vYWRkcmVzcykxEjAQBgNVBAUTCUE4Mjc0MzI4NzEbMBkGA1UEChMSQUMgQ2FtZXJm +aXJtYSBTLkEuMScwJQYDVQQDEx5HbG9iYWwgQ2hhbWJlcnNpZ24gUm9vdCAtIDIwMDiCCQDJzdPp +1X0jzjAOBgNVHQ8BAf8EBAMCAQYwPQYDVR0gBDYwNDAyBgRVHSAAMCowKAYIKwYBBQUHAgEWHGh0 +dHA6Ly9wb2xpY3kuY2FtZXJmaXJtYS5jb20wDQYJKoZIhvcNAQEFBQADggIBAICIf3DekijZBZRG +/5BXqfEv3xoNa/p8DhxJJHkn2EaqbylZUohwEurdPfWbU1Rv4WCiqAm57OtZfMY18dwY6fFn5a+6 +ReAJ3spED8IXDneRRXozX1+WLGiLwUePmJs9wOzL9dWCkoQ10b42OFZyMVtHLaoXpGNR6woBrX/s +dZ7LoR/xfxKxueRkf2fWIyr0uDldmOghp+G9PUIadJpwr2hsUF1Jz//7Dl3mLEfXgTpZALVza2Mg +9jFFCDkO9HB+QHBaP9BrQql0PSgvAm11cpUJjUhjxsYjV5KTXjXBjfkK9yydYhz2rXzdpjEetrHH +foUm+qRqtdpjMNHvkzeyZi99Bffnt0uYlDXA2TopwZ2yUDMdSqlapskD7+3056huirRXhOukP9Du +qqqHW2Pok+JrqNS4cnhrG+055F3Lm6qH1U9OAP7Zap88MQ8oAgF9mOinsKJknnn4SPIVqczmyETr +P3iZ8ntxPjzxmKfFGBI/5rsoM0LpRQp8bfKGeS/Fghl9CYl8slR2iK7ewfPM4W7bMdaTrpmg7yVq +c5iJWzouE4gev8CSlDQb4ye3ix5vQv/n6TebUB0tovkC7stYWDpxvGjjqsGvHCgfotwjZT+B6q6Z +09gwzxMNTxXJhLynSC34MCN32EZLeW32jO06f2ARePTpm67VVMB0gNELQp/B +-----END CERTIFICATE----- + +Go Daddy Root Certificate Authority - G2 +======================================== +-----BEGIN CERTIFICATE----- +MIIDxTCCAq2gAwIBAgIBADANBgkqhkiG9w0BAQsFADCBgzELMAkGA1UEBhMCVVMxEDAOBgNVBAgT +B0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxGjAYBgNVBAoTEUdvRGFkZHkuY29tLCBJbmMu +MTEwLwYDVQQDEyhHbyBEYWRkeSBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5 +MDkwMTAwMDAwMFoXDTM3MTIzMTIzNTk1OVowgYMxCzAJBgNVBAYTAlVTMRAwDgYDVQQIEwdBcml6 +b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMRowGAYDVQQKExFHb0RhZGR5LmNvbSwgSW5jLjExMC8G +A1UEAxMoR28gRGFkZHkgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIwDQYJKoZI +hvcNAQEBBQADggEPADCCAQoCggEBAL9xYgjx+lk09xvJGKP3gElY6SKDE6bFIEMBO4Tx5oVJnyfq +9oQbTqC023CYxzIBsQU+B07u9PpPL1kwIuerGVZr4oAH/PMWdYA5UXvl+TW2dE6pjYIT5LY/qQOD ++qK+ihVqf94Lw7YZFAXK6sOoBJQ7RnwyDfMAZiLIjWltNowRGLfTshxgtDj6AozO091GB94KPutd +fMh8+7ArU6SSYmlRJQVhGkSBjCypQ5Yj36w6gZoOKcUcqeldHraenjAKOc7xiID7S13MMuyFYkMl +NAJWJwGRtDtwKj9useiciAF9n9T521NtYJ2/LOdYq7hfRvzOxBsDPAnrSTFcaUaz4EcCAwEAAaNC +MEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFDqahQcQZyi27/a9 +BUFuIMGU2g/eMA0GCSqGSIb3DQEBCwUAA4IBAQCZ21151fmXWWcDYfF+OwYxdS2hII5PZYe096ac +vNjpL9DbWu7PdIxztDhC2gV7+AJ1uP2lsdeu9tfeE8tTEH6KRtGX+rcuKxGrkLAngPnon1rpN5+r +5N9ss4UXnT3ZJE95kTXWXwTrgIOrmgIttRD02JDHBHNA7XIloKmf7J6raBKZV8aPEjoJpL1E/QYV +N8Gb5DKj7Tjo2GTzLH4U/ALqn83/B2gX2yKQOC16jdFU8WnjXzPKej17CuPKf1855eJ1usV2GDPO +LPAvTK33sefOT6jEm0pUBsV/fdUID+Ic/n4XuKxe9tQWskMJDE32p2u0mYRlynqI4uJEvlz36hz1 +-----END CERTIFICATE----- + +Starfield Root Certificate Authority - G2 +========================================= +-----BEGIN CERTIFICATE----- +MIID3TCCAsWgAwIBAgIBADANBgkqhkiG9w0BAQsFADCBjzELMAkGA1UEBhMCVVMxEDAOBgNVBAgT +B0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoTHFN0YXJmaWVsZCBUZWNobm9s +b2dpZXMsIEluYy4xMjAwBgNVBAMTKVN0YXJmaWVsZCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0 +eSAtIEcyMB4XDTA5MDkwMTAwMDAwMFoXDTM3MTIzMTIzNTk1OVowgY8xCzAJBgNVBAYTAlVTMRAw +DgYDVQQIEwdBcml6b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUwIwYDVQQKExxTdGFyZmllbGQg +VGVjaG5vbG9naWVzLCBJbmMuMTIwMAYDVQQDEylTdGFyZmllbGQgUm9vdCBDZXJ0aWZpY2F0ZSBB +dXRob3JpdHkgLSBHMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL3twQP89o/8ArFv +W59I2Z154qK3A2FWGMNHttfKPTUuiUP3oWmb3ooa/RMgnLRJdzIpVv257IzdIvpy3Cdhl+72WoTs +bhm5iSzchFvVdPtrX8WJpRBSiUZV9Lh1HOZ/5FSuS/hVclcCGfgXcVnrHigHdMWdSL5stPSksPNk +N3mSwOxGXn/hbVNMYq/NHwtjuzqd+/x5AJhhdM8mgkBj87JyahkNmcrUDnXMN/uLicFZ8WJ/X7Nf +ZTD4p7dNdloedl40wOiWVpmKs/B/pM293DIxfJHP4F8R+GuqSVzRmZTRouNjWwl2tVZi4Ut0HZbU +JtQIBFnQmA4O5t78w+wfkPECAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC +AQYwHQYDVR0OBBYEFHwMMh+n2TB/xH1oo2Kooc6rB1snMA0GCSqGSIb3DQEBCwUAA4IBAQARWfol +TwNvlJk7mh+ChTnUdgWUXuEok21iXQnCoKjUsHU48TRqneSfioYmUeYs0cYtbpUgSpIB7LiKZ3sx +4mcujJUDJi5DnUox9g61DLu34jd/IroAow57UvtruzvE03lRTs2Q9GcHGcg8RnoNAX3FWOdt5oUw +F5okxBDgBPfg8n/Uqgr/Qh037ZTlZFkSIHc40zI+OIF1lnP6aI+xy84fxez6nH7PfrHxBy22/L/K +pL/QlwVKvOoYKAKQvVR4CSFx09F9HdkWsKlhPdAKACL8x3vLCWRFCztAgfd9fDL1mMpYjn0q7pBZ +c2T5NnReJaH1ZgUufzkVqSr7UIuOhWn0 +-----END CERTIFICATE----- + +Starfield Services Root Certificate Authority - G2 +================================================== +-----BEGIN CERTIFICATE----- +MIID7zCCAtegAwIBAgIBADANBgkqhkiG9w0BAQsFADCBmDELMAkGA1UEBhMCVVMxEDAOBgNVBAgT +B0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoTHFN0YXJmaWVsZCBUZWNobm9s +b2dpZXMsIEluYy4xOzA5BgNVBAMTMlN0YXJmaWVsZCBTZXJ2aWNlcyBSb290IENlcnRpZmljYXRl +IEF1dGhvcml0eSAtIEcyMB4XDTA5MDkwMTAwMDAwMFoXDTM3MTIzMTIzNTk1OVowgZgxCzAJBgNV +BAYTAlVTMRAwDgYDVQQIEwdBcml6b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUwIwYDVQQKExxT +dGFyZmllbGQgVGVjaG5vbG9naWVzLCBJbmMuMTswOQYDVQQDEzJTdGFyZmllbGQgU2VydmljZXMg +Um9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC +AQoCggEBANUMOsQq+U7i9b4Zl1+OiFOxHz/Lz58gE20pOsgPfTz3a3Y4Y9k2YKibXlwAgLIvWX/2 +h/klQ4bnaRtSmpDhcePYLQ1Ob/bISdm28xpWriu2dBTrz/sm4xq6HZYuajtYlIlHVv8loJNwU4Pa +hHQUw2eeBGg6345AWh1KTs9DkTvnVtYAcMtS7nt9rjrnvDH5RfbCYM8TWQIrgMw0R9+53pBlbQLP +LJGmpufehRhJfGZOozptqbXuNC66DQO4M99H67FrjSXZm86B0UVGMpZwh94CDklDhbZsc7tk6mFB +rMnUVN+HL8cisibMn1lUaJ/8viovxFUcdUBgF4UCVTmLfwUCAwEAAaNCMEAwDwYDVR0TAQH/BAUw +AwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFJxfAN+qAdcwKziIorhtSpzyEZGDMA0GCSqG +SIb3DQEBCwUAA4IBAQBLNqaEd2ndOxmfZyMIbw5hyf2E3F/YNoHN2BtBLZ9g3ccaaNnRbobhiCPP +E95Dz+I0swSdHynVv/heyNXBve6SbzJ08pGCL72CQnqtKrcgfU28elUSwhXqvfdqlS5sdJ/PHLTy +xQGjhdByPq1zqwubdQxtRbeOlKyWN7Wg0I8VRw7j6IPdj/3vQQF3zCepYoUz8jcI73HPdwbeyBkd +iEDPfUYd/x7H4c7/I9vG+o1VTqkC50cRRj70/b17KSa7qWFiNyi2LSr2EIZkyXCn0q23KXB56jza +YyWf/Wi3MOxw+3WKt21gZ7IeyLnp2KhvAotnDU0mV3HaIPzBSlCNsSi6 +-----END CERTIFICATE----- + +AffirmTrust Commercial +====================== +-----BEGIN CERTIFICATE----- +MIIDTDCCAjSgAwIBAgIId3cGJyapsXwwDQYJKoZIhvcNAQELBQAwRDELMAkGA1UEBhMCVVMxFDAS +BgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVzdCBDb21tZXJjaWFsMB4XDTEw +MDEyOTE0MDYwNloXDTMwMTIzMTE0MDYwNlowRDELMAkGA1UEBhMCVVMxFDASBgNVBAoMC0FmZmly +bVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVzdCBDb21tZXJjaWFsMIIBIjANBgkqhkiG9w0BAQEF +AAOCAQ8AMIIBCgKCAQEA9htPZwcroRX1BiLLHwGy43NFBkRJLLtJJRTWzsO3qyxPxkEylFf6Eqdb +DuKPHx6GGaeqtS25Xw2Kwq+FNXkyLbscYjfysVtKPcrNcV/pQr6U6Mje+SJIZMblq8Yrba0F8PrV +C8+a5fBQpIs7R6UjW3p6+DM/uO+Zl+MgwdYoic+U+7lF7eNAFxHUdPALMeIrJmqbTFeurCA+ukV6 +BfO9m2kVrn1OIGPENXY6BwLJN/3HR+7o8XYdcxXyl6S1yHp52UKqK39c/s4mT6NmgTWvRLpUHhww +MmWd5jyTXlBOeuM61G7MGvv50jeuJCqrVwMiKA1JdX+3KNp1v47j3A55MQIDAQABo0IwQDAdBgNV +HQ4EFgQUnZPGU4teyq8/nx4P5ZmVvCT2lI8wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC +AQYwDQYJKoZIhvcNAQELBQADggEBAFis9AQOzcAN/wr91LoWXym9e2iZWEnStB03TX8nfUYGXUPG +hi4+c7ImfU+TqbbEKpqrIZcUsd6M06uJFdhrJNTxFq7YpFzUf1GO7RgBsZNjvbz4YYCanrHOQnDi +qX0GJX0nof5v7LMeJNrjS1UaADs1tDvZ110w/YETifLCBivtZ8SOyUOyXGsViQK8YvxO8rUzqrJv +0wqiUOP2O+guRMLbZjipM1ZI8W0bM40NjD9gN53Tym1+NH4Nn3J2ixufcv1SNUFFApYvHLKac0kh +sUlHRUe072o0EclNmsxZt9YCnlpOZbWUrhvfKbAW8b8Angc6F2S1BLUjIZkKlTuXfO8= +-----END CERTIFICATE----- + +AffirmTrust Networking +====================== +-----BEGIN CERTIFICATE----- +MIIDTDCCAjSgAwIBAgIIfE8EORzUmS0wDQYJKoZIhvcNAQEFBQAwRDELMAkGA1UEBhMCVVMxFDAS +BgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVzdCBOZXR3b3JraW5nMB4XDTEw +MDEyOTE0MDgyNFoXDTMwMTIzMTE0MDgyNFowRDELMAkGA1UEBhMCVVMxFDASBgNVBAoMC0FmZmly +bVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVzdCBOZXR3b3JraW5nMIIBIjANBgkqhkiG9w0BAQEF +AAOCAQ8AMIIBCgKCAQEAtITMMxcua5Rsa2FSoOujz3mUTOWUgJnLVWREZY9nZOIG41w3SfYvm4SE +Hi3yYJ0wTsyEheIszx6e/jarM3c1RNg1lho9Nuh6DtjVR6FqaYvZ/Ls6rnla1fTWcbuakCNrmreI +dIcMHl+5ni36q1Mr3Lt2PpNMCAiMHqIjHNRqrSK6mQEubWXLviRmVSRLQESxG9fhwoXA3hA/Pe24 +/PHxI1Pcv2WXb9n5QHGNfb2V1M6+oF4nI979ptAmDgAp6zxG8D1gvz9Q0twmQVGeFDdCBKNwV6gb +h+0t+nvujArjqWaJGctB+d1ENmHP4ndGyH329JKBNv3bNPFyfvMMFr20FQIDAQABo0IwQDAdBgNV +HQ4EFgQUBx/S55zawm6iQLSwelAQUHTEyL0wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC +AQYwDQYJKoZIhvcNAQEFBQADggEBAIlXshZ6qML91tmbmzTCnLQyFE2npN/svqe++EPbkTfOtDIu +UFUaNU52Q3Eg75N3ThVwLofDwR1t3Mu1J9QsVtFSUzpE0nPIxBsFZVpikpzuQY0x2+c06lkh1QF6 +12S4ZDnNye2v7UsDSKegmQGA3GWjNq5lWUhPgkvIZfFXHeVZLgo/bNjR9eUJtGxUAArgFU2HdW23 +WJZa3W3SAKD0m0i+wzekujbgfIeFlxoVot4uolu9rxj5kFDNcFn4J2dHy8egBzp90SxdbBk6ZrV9 +/ZFvgrG+CJPbFEfxojfHRZ48x3evZKiT3/Zpg4Jg8klCNO1aAFSFHBY2kgxc+qatv9s= +-----END CERTIFICATE----- + +AffirmTrust Premium +=================== +-----BEGIN CERTIFICATE----- +MIIFRjCCAy6gAwIBAgIIbYwURrGmCu4wDQYJKoZIhvcNAQEMBQAwQTELMAkGA1UEBhMCVVMxFDAS +BgNVBAoMC0FmZmlybVRydXN0MRwwGgYDVQQDDBNBZmZpcm1UcnVzdCBQcmVtaXVtMB4XDTEwMDEy +OTE0MTAzNloXDTQwMTIzMTE0MTAzNlowQTELMAkGA1UEBhMCVVMxFDASBgNVBAoMC0FmZmlybVRy +dXN0MRwwGgYDVQQDDBNBZmZpcm1UcnVzdCBQcmVtaXVtMIICIjANBgkqhkiG9w0BAQEFAAOCAg8A +MIICCgKCAgEAxBLfqV/+Qd3d9Z+K4/as4Tx4mrzY8H96oDMq3I0gW64tb+eT2TZwamjPjlGjhVtn +BKAQJG9dKILBl1fYSCkTtuG+kU3fhQxTGJoeJKJPj/CihQvL9Cl/0qRY7iZNyaqoe5rZ+jjeRFcV +5fiMyNlI4g0WJx0eyIOFJbe6qlVBzAMiSy2RjYvmia9mx+n/K+k8rNrSs8PhaJyJ+HoAVt70VZVs ++7pk3WKL3wt3MutizCaam7uqYoNMtAZ6MMgpv+0GTZe5HMQxK9VfvFMSF5yZVylmd2EhMQcuJUmd +GPLu8ytxjLW6OQdJd/zvLpKQBY0tL3d770O/Nbua2Plzpyzy0FfuKE4mX4+QaAkvuPjcBukumj5R +p9EixAqnOEhss/n/fauGV+O61oV4d7pD6kh/9ti+I20ev9E2bFhc8e6kGVQa9QPSdubhjL08s9NI +S+LI+H+SqHZGnEJlPqQewQcDWkYtuJfzt9WyVSHvutxMAJf7FJUnM7/oQ0dG0giZFmA7mn7S5u04 +6uwBHjxIVkkJx0w3AJ6IDsBz4W9m6XJHMD4Q5QsDyZpCAGzFlH5hxIrff4IaC1nEWTJ3s7xgaVY5 +/bQGeyzWZDbZvUjthB9+pSKPKrhC9IK31FOQeE4tGv2Bb0TXOwF0lkLgAOIua+rF7nKsu7/+6qqo ++Nz2snmKtmcCAwEAAaNCMEAwHQYDVR0OBBYEFJ3AZ6YMItkm9UWrpmVSESfYRaxjMA8GA1UdEwEB +/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEBDAUAA4ICAQCzV00QYk465KzquByv +MiPIs0laUZx2KI15qldGF9X1Uva3ROgIRL8YhNILgM3FEv0AVQVhh0HctSSePMTYyPtwni94loMg +Nt58D2kTiKV1NpgIpsbfrM7jWNa3Pt668+s0QNiigfV4Py/VpfzZotReBA4Xrf5B8OWycvpEgjNC +6C1Y91aMYj+6QrCcDFx+LmUmXFNPALJ4fqENmS2NuB2OosSw/WDQMKSOyARiqcTtNd56l+0OOF6S +L5Nwpamcb6d9Ex1+xghIsV5n61EIJenmJWtSKZGc0jlzCFfemQa0W50QBuHCAKi4HEoCChTQwUHK ++4w1IX2COPKpVJEZNZOUbWo6xbLQu4mGk+ibyQ86p3q4ofB4Rvr8Ny/lioTz3/4E2aFooC8k4gmV +BtWVyuEklut89pMFu+1z6S3RdTnX5yTb2E5fQ4+e0BQ5v1VwSJlXMbSc7kqYA5YwH2AG7hsj/oFg +IxpHYoWlzBk0gG+zrBrjn/B7SK3VAdlntqlyk+otZrWyuOQ9PLLvTIzq6we/qzWaVYa8GKa1qF60 +g2xraUDTn9zxw2lrueFtCfTxqlB2Cnp9ehehVZZCmTEJ3WARjQUwfuaORtGdFNrHF+QFlozEJLUb +zxQHskD4o55BhrwE0GuWyCqANP2/7waj3VjFhT0+j/6eKeC2uAloGRwYQw== +-----END CERTIFICATE----- + +AffirmTrust Premium ECC +======================= +-----BEGIN CERTIFICATE----- +MIIB/jCCAYWgAwIBAgIIdJclisc/elQwCgYIKoZIzj0EAwMwRTELMAkGA1UEBhMCVVMxFDASBgNV +BAoMC0FmZmlybVRydXN0MSAwHgYDVQQDDBdBZmZpcm1UcnVzdCBQcmVtaXVtIEVDQzAeFw0xMDAx +MjkxNDIwMjRaFw00MDEyMzExNDIwMjRaMEUxCzAJBgNVBAYTAlVTMRQwEgYDVQQKDAtBZmZpcm1U +cnVzdDEgMB4GA1UEAwwXQWZmaXJtVHJ1c3QgUHJlbWl1bSBFQ0MwdjAQBgcqhkjOPQIBBgUrgQQA +IgNiAAQNMF4bFZ0D0KF5Nbc6PJJ6yhUczWLznCZcBz3lVPqj1swS6vQUX+iOGasvLkjmrBhDeKzQ +N8O9ss0s5kfiGuZjuD0uL3jET9v0D6RoTFVya5UdThhClXjMNzyR4ptlKymjQjBAMB0GA1UdDgQW +BBSaryl6wBE1NSZRMADDav5A1a7WPDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAK +BggqhkjOPQQDAwNnADBkAjAXCfOHiFBar8jAQr9HX/VsaobgxCd05DhT1wV/GzTjxi+zygk8N53X +57hG8f2h4nECMEJZh0PUUd+60wkyWs6Iflc9nF9Ca/UHLbXwgpP5WW+uZPpY5Yse42O+tYHNbwKM +eQ== +-----END CERTIFICATE----- + +Certum Trusted Network CA +========================= +-----BEGIN CERTIFICATE----- +MIIDuzCCAqOgAwIBAgIDBETAMA0GCSqGSIb3DQEBBQUAMH4xCzAJBgNVBAYTAlBMMSIwIAYDVQQK +ExlVbml6ZXRvIFRlY2hub2xvZ2llcyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlv +biBBdXRob3JpdHkxIjAgBgNVBAMTGUNlcnR1bSBUcnVzdGVkIE5ldHdvcmsgQ0EwHhcNMDgxMDIy +MTIwNzM3WhcNMjkxMjMxMTIwNzM3WjB+MQswCQYDVQQGEwJQTDEiMCAGA1UEChMZVW5pemV0byBU +ZWNobm9sb2dpZXMgUy5BLjEnMCUGA1UECxMeQ2VydHVtIENlcnRpZmljYXRpb24gQXV0aG9yaXR5 +MSIwIAYDVQQDExlDZXJ0dW0gVHJ1c3RlZCBOZXR3b3JrIENBMIIBIjANBgkqhkiG9w0BAQEFAAOC +AQ8AMIIBCgKCAQEA4/t9o3K6wvDJFIf1awFO4W5AB7ptJ11/91sts1rHUV+rpDKmYYe2bg+G0jAC +l/jXaVehGDldamR5xgFZrDwxSjh80gTSSyjoIF87B6LMTXPb865Px1bVWqeWifrzq2jUI4ZZJ88J +J7ysbnKDHDBy3+Ci6dLhdHUZvSqeexVUBBvXQzmtVSjF4hq79MDkrjhJM8x2hZ85RdKknvISjFH4 +fOQtf/WsX+sWn7Et0brMkUJ3TCXJkDhv2/DM+44el1k+1WBO5gUo7Ul5E0u6SNsv+XLTOcr+H9g0 +cvW0QM8xAcPs3hEtF10fuFDRXhmnad4HMyjKUJX5p1TLVIZQRan5SQIDAQABo0IwQDAPBgNVHRMB +Af8EBTADAQH/MB0GA1UdDgQWBBQIds3LB/8k9sXN7buQvOKEN0Z19zAOBgNVHQ8BAf8EBAMCAQYw +DQYJKoZIhvcNAQEFBQADggEBAKaorSLOAT2mo/9i0Eidi15ysHhE49wcrwn9I0j6vSrEuVUEtRCj +jSfeC4Jj0O7eDDd5QVsisrCaQVymcODU0HfLI9MA4GxWL+FpDQ3Zqr8hgVDZBqWo/5U30Kr+4rP1 +mS1FhIrlQgnXdAIv94nYmem8J9RHjboNRhx3zxSkHLmkMcScKHQDNP8zGSal6Q10tz6XxnboJ5aj +Zt3hrvJBW8qYVoNzcOSGGtIxQbovvi0TWnZvTuhOgQ4/WwMioBK+ZlgRSssDxLQqKi2WF+A5VLxI +03YnnZotBqbJ7DnSq9ufmgsnAjUpsUCV5/nonFWIGUbWtzT1fs45mtk48VH3Tyw= +-----END CERTIFICATE----- + +Certinomis - Autorité Racine +============================= +-----BEGIN CERTIFICATE----- +MIIFnDCCA4SgAwIBAgIBATANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJGUjETMBEGA1UEChMK +Q2VydGlub21pczEXMBUGA1UECxMOMDAwMiA0MzM5OTg5MDMxJjAkBgNVBAMMHUNlcnRpbm9taXMg +LSBBdXRvcml0w6kgUmFjaW5lMB4XDTA4MDkxNzA4Mjg1OVoXDTI4MDkxNzA4Mjg1OVowYzELMAkG +A1UEBhMCRlIxEzARBgNVBAoTCkNlcnRpbm9taXMxFzAVBgNVBAsTDjAwMDIgNDMzOTk4OTAzMSYw +JAYDVQQDDB1DZXJ0aW5vbWlzIC0gQXV0b3JpdMOpIFJhY2luZTCCAiIwDQYJKoZIhvcNAQEBBQAD +ggIPADCCAgoCggIBAJ2Fn4bT46/HsmtuM+Cet0I0VZ35gb5j2CN2DpdUzZlMGvE5x4jYF1AMnmHa +wE5V3udauHpOd4cN5bjr+p5eex7Ezyh0x5P1FMYiKAT5kcOrJ3NqDi5N8y4oH3DfVS9O7cdxbwly +Lu3VMpfQ8Vh30WC8Tl7bmoT2R2FFK/ZQpn9qcSdIhDWerP5pqZ56XjUl+rSnSTV3lqc2W+HN3yNw +2F1MpQiD8aYkOBOo7C+ooWfHpi2GR+6K/OybDnT0K0kCe5B1jPyZOQE51kqJ5Z52qz6WKDgmi92N +jMD2AR5vpTESOH2VwnHu7XSu5DaiQ3XV8QCb4uTXzEIDS3h65X27uK4uIJPT5GHfceF2Z5c/tt9q +c1pkIuVC28+BA5PY9OMQ4HL2AHCs8MF6DwV/zzRpRbWT5BnbUhYjBYkOjUjkJW+zeL9i9Qf6lSTC +lrLooyPCXQP8w9PlfMl1I9f09bze5N/NgL+RiH2nE7Q5uiy6vdFrzPOlKO1Enn1So2+WLhl+HPNb +xxaOu2B9d2ZHVIIAEWBsMsGoOBvrbpgT1u449fCfDu/+MYHB0iSVL1N6aaLwD4ZFjliCK0wi1F6g +530mJ0jfJUaNSih8hp75mxpZuWW/Bd22Ql095gBIgl4g9xGC3srYn+Y3RyYe63j3YcNBZFgCQfna +4NH4+ej9Uji29YnfAgMBAAGjWzBZMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0G +A1UdDgQWBBQNjLZh2kS40RR9w759XkjwzspqsDAXBgNVHSAEEDAOMAwGCiqBegFWAgIAAQEwDQYJ +KoZIhvcNAQEFBQADggIBACQ+YAZ+He86PtvqrxyaLAEL9MW12Ukx9F1BjYkMTv9sov3/4gbIOZ/x +WqndIlgVqIrTseYyCYIDbNc/CMf4uboAbbnW/FIyXaR/pDGUu7ZMOH8oMDX/nyNTt7buFHAAQCva +R6s0fl6nVjBhK4tDrP22iCj1a7Y+YEq6QpA0Z43q619FVDsXrIvkxmUP7tCMXWY5zjKn2BCXwH40 +nJ+U8/aGH88bc62UeYdocMMzpXDn2NU4lG9jeeu/Cg4I58UvD0KgKxRA/yHgBcUn4YQRE7rWhh1B +CxMjidPJC+iKunqjo3M3NYB9Ergzd0A4wPpeMNLytqOx1qKVl4GbUu1pTP+A5FPbVFsDbVRfsbjv +JL1vnxHDx2TCDyhihWZeGnuyt++uNckZM6i4J9szVb9o4XVIRFb7zdNIu0eJOqxp9YDG5ERQL1TE +qkPFMTFYvZbF6nVsmnWxTfj3l/+WFvKXTej28xH5On2KOG4Ey+HTRRWqpdEdnV1j6CTmNhTih60b +WfVEm/vXd3wfAXBioSAaosUaKPQhA+4u2cGA6rnZgtZbdsLLO7XSAPCjDuGtbkD326C00EauFddE +wk01+dIL8hf2rGbVJLJP0RyZwG71fet0BLj5TXcJ17TPBzAJ8bgAVtkXFhYKK4bfjwEZGuW7gmP/ +vgt2Fl43N+bYdJeimUV5 +-----END CERTIFICATE----- + +Root CA Generalitat Valenciana +============================== +-----BEGIN CERTIFICATE----- +MIIGizCCBXOgAwIBAgIEO0XlaDANBgkqhkiG9w0BAQUFADBoMQswCQYDVQQGEwJFUzEfMB0GA1UE +ChMWR2VuZXJhbGl0YXQgVmFsZW5jaWFuYTEPMA0GA1UECxMGUEtJR1ZBMScwJQYDVQQDEx5Sb290 +IENBIEdlbmVyYWxpdGF0IFZhbGVuY2lhbmEwHhcNMDEwNzA2MTYyMjQ3WhcNMjEwNzAxMTUyMjQ3 +WjBoMQswCQYDVQQGEwJFUzEfMB0GA1UEChMWR2VuZXJhbGl0YXQgVmFsZW5jaWFuYTEPMA0GA1UE +CxMGUEtJR1ZBMScwJQYDVQQDEx5Sb290IENBIEdlbmVyYWxpdGF0IFZhbGVuY2lhbmEwggEiMA0G +CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDGKqtXETcvIorKA3Qdyu0togu8M1JAJke+WmmmO3I2 +F0zo37i7L3bhQEZ0ZQKQUgi0/6iMweDHiVYQOTPvaLRfX9ptI6GJXiKjSgbwJ/BXufjpTjJ3Cj9B +ZPPrZe52/lSqfR0grvPXdMIKX/UIKFIIzFVd0g/bmoGlu6GzwZTNVOAydTGRGmKy3nXiz0+J2ZGQ +D0EbtFpKd71ng+CT516nDOeB0/RSrFOyA8dEJvt55cs0YFAQexvba9dHq198aMpunUEDEO5rmXte +JajCq+TA81yc477OMUxkHl6AovWDfgzWyoxVjr7gvkkHD6MkQXpYHYTqWBLI4bft75PelAgxAgMB +AAGjggM7MIIDNzAyBggrBgEFBQcBAQQmMCQwIgYIKwYBBQUHMAGGFmh0dHA6Ly9vY3NwLnBraS5n +dmEuZXMwEgYDVR0TAQH/BAgwBgEB/wIBAjCCAjQGA1UdIASCAiswggInMIICIwYKKwYBBAG/VQIB +ADCCAhMwggHoBggrBgEFBQcCAjCCAdoeggHWAEEAdQB0AG8AcgBpAGQAYQBkACAAZABlACAAQwBl +AHIAdABpAGYAaQBjAGEAYwBpAPMAbgAgAFIAYQDtAHoAIABkAGUAIABsAGEAIABHAGUAbgBlAHIA +YQBsAGkAdABhAHQAIABWAGEAbABlAG4AYwBpAGEAbgBhAC4ADQAKAEwAYQAgAEQAZQBjAGwAYQBy +AGEAYwBpAPMAbgAgAGQAZQAgAFAAcgDhAGMAdABpAGMAYQBzACAAZABlACAAQwBlAHIAdABpAGYA +aQBjAGEAYwBpAPMAbgAgAHEAdQBlACAAcgBpAGcAZQAgAGUAbAAgAGYAdQBuAGMAaQBvAG4AYQBt +AGkAZQBuAHQAbwAgAGQAZQAgAGwAYQAgAHAAcgBlAHMAZQBuAHQAZQAgAEEAdQB0AG8AcgBpAGQA +YQBkACAAZABlACAAQwBlAHIAdABpAGYAaQBjAGEAYwBpAPMAbgAgAHMAZQAgAGUAbgBjAHUAZQBu +AHQAcgBhACAAZQBuACAAbABhACAAZABpAHIAZQBjAGMAaQDzAG4AIAB3AGUAYgAgAGgAdAB0AHAA +OgAvAC8AdwB3AHcALgBwAGsAaQAuAGcAdgBhAC4AZQBzAC8AYwBwAHMwJQYIKwYBBQUHAgEWGWh0 +dHA6Ly93d3cucGtpLmd2YS5lcy9jcHMwHQYDVR0OBBYEFHs100DSHHgZZu90ECjcPk+yeAT8MIGV +BgNVHSMEgY0wgYqAFHs100DSHHgZZu90ECjcPk+yeAT8oWykajBoMQswCQYDVQQGEwJFUzEfMB0G +A1UEChMWR2VuZXJhbGl0YXQgVmFsZW5jaWFuYTEPMA0GA1UECxMGUEtJR1ZBMScwJQYDVQQDEx5S +b290IENBIEdlbmVyYWxpdGF0IFZhbGVuY2lhbmGCBDtF5WgwDQYJKoZIhvcNAQEFBQADggEBACRh +TvW1yEICKrNcda3FbcrnlD+laJWIwVTAEGmiEi8YPyVQqHxK6sYJ2fR1xkDar1CdPaUWu20xxsdz +Ckj+IHLtb8zog2EWRpABlUt9jppSCS/2bxzkoXHPjCpaF3ODR00PNvsETUlR4hTJZGH71BTg9J63 +NI8KJr2XXPR5OkowGcytT6CYirQxlyric21+eLj4iIlPsSKRZEv1UN4D2+XFducTZnV+ZfsBn5OH +iJ35Rld8TWCvmHMTI6QgkYH60GFmuH3Rr9ZvHmw96RH9qfmCIoaZM3Fa6hlXPZHNqcCjbgcTpsnt ++GijnsNacgmHKNHEc8RzGF9QdRYxn7fofMM= +-----END CERTIFICATE----- + +A-Trust-nQual-03 +================ +-----BEGIN CERTIFICATE----- +MIIDzzCCAregAwIBAgIDAWweMA0GCSqGSIb3DQEBBQUAMIGNMQswCQYDVQQGEwJBVDFIMEYGA1UE +Cgw/QS1UcnVzdCBHZXMuIGYuIFNpY2hlcmhlaXRzc3lzdGVtZSBpbSBlbGVrdHIuIERhdGVudmVy +a2VociBHbWJIMRkwFwYDVQQLDBBBLVRydXN0LW5RdWFsLTAzMRkwFwYDVQQDDBBBLVRydXN0LW5R +dWFsLTAzMB4XDTA1MDgxNzIyMDAwMFoXDTE1MDgxNzIyMDAwMFowgY0xCzAJBgNVBAYTAkFUMUgw +RgYDVQQKDD9BLVRydXN0IEdlcy4gZi4gU2ljaGVyaGVpdHNzeXN0ZW1lIGltIGVsZWt0ci4gRGF0 +ZW52ZXJrZWhyIEdtYkgxGTAXBgNVBAsMEEEtVHJ1c3QtblF1YWwtMDMxGTAXBgNVBAMMEEEtVHJ1 +c3QtblF1YWwtMDMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCtPWFuA/OQO8BBC4SA +zewqo51ru27CQoT3URThoKgtUaNR8t4j8DRE/5TrzAUjlUC5B3ilJfYKvUWG6Nm9wASOhURh73+n +yfrBJcyFLGM/BWBzSQXgYHiVEEvc+RFZznF/QJuKqiTfC0Li21a8StKlDJu3Qz7dg9MmEALP6iPE +SU7l0+m0iKsMrmKS1GWH2WrX9IWf5DMiJaXlyDO6w8dB3F/GaswADm0yqLaHNgBid5seHzTLkDx4 +iHQF63n1k3Flyp3HaxgtPVxO59X4PzF9j4fsCiIvI+n+u33J4PTs63zEsMMtYrWacdaxaujs2e3V +cuy+VwHOBVWf3tFgiBCzAgMBAAGjNjA0MA8GA1UdEwEB/wQFMAMBAf8wEQYDVR0OBAoECERqlWdV +eRFPMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQUFAAOCAQEAVdRU0VlIXLOThaq/Yy/kgM40 +ozRiPvbY7meIMQQDbwvUB/tOdQ/TLtPAF8fGKOwGDREkDg6lXb+MshOWcdzUzg4NCmgybLlBMRmr +sQd7TZjTXLDR8KdCoLXEjq/+8T/0709GAHbrAvv5ndJAlseIOrifEXnzgGWovR/TeIGgUUw3tKZd +JXDRZslo+S4RFGjxVJgIrCaSD96JntT6s3kr0qN51OyLrIdTaEJMUVF0HhsnLuP1Hyl0Te2v9+GS +mYHovjrHF1D2t8b8m7CKa9aIA5GPBnc6hQLdmNVDeD/GMBWsm2vLV7eJUYs66MmEDNuxUCAKGkq6 +ahq97BvIxYSazQ== +-----END CERTIFICATE----- + +TWCA Root Certification Authority +================================= +-----BEGIN CERTIFICATE----- +MIIDezCCAmOgAwIBAgIBATANBgkqhkiG9w0BAQUFADBfMQswCQYDVQQGEwJUVzESMBAGA1UECgwJ +VEFJV0FOLUNBMRAwDgYDVQQLDAdSb290IENBMSowKAYDVQQDDCFUV0NBIFJvb3QgQ2VydGlmaWNh +dGlvbiBBdXRob3JpdHkwHhcNMDgwODI4MDcyNDMzWhcNMzAxMjMxMTU1OTU5WjBfMQswCQYDVQQG +EwJUVzESMBAGA1UECgwJVEFJV0FOLUNBMRAwDgYDVQQLDAdSb290IENBMSowKAYDVQQDDCFUV0NB +IFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK +AoIBAQCwfnK4pAOU5qfeCTiRShFAh6d8WWQUe7UREN3+v9XAu1bihSX0NXIP+FPQQeFEAcK0HMMx +QhZHhTMidrIKbw/lJVBPhYa+v5guEGcevhEFhgWQxFnQfHgQsIBct+HHK3XLfJ+utdGdIzdjp9xC +oi2SBBtQwXu4PhvJVgSLL1KbralW6cH/ralYhzC2gfeXRfwZVzsrb+RH9JlF/h3x+JejiB03HFyP +4HYlmlD4oFT/RJB2I9IyxsOrBr/8+7/zrX2SYgJbKdM1o5OaQ2RgXbL6Mv87BK9NQGr5x+PvI/1r +y+UPizgN7gr8/g+YnzAx3WxSZfmLgb4i4RxYA7qRG4kHAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIB +BjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRqOFsmjd6LWvJPelSDGRjjCDWmujANBgkqhkiG +9w0BAQUFAAOCAQEAPNV3PdrfibqHDAhUaiBQkr6wQT25JmSDCi/oQMCXKCeCMErJk/9q56YAf4lC +mtYR5VPOL8zy2gXE/uJQxDqGfczafhAJO5I1KlOy/usrBdlsXebQ79NqZp4VKIV66IIArB6nCWlW +QtNoURi+VJq/REG6Sb4gumlc7rh3zc5sH62Dlhh9DrUUOYTxKOkto557HnpyWoOzeW/vtPzQCqVY +T0bf+215WfKEIlKuD8z7fDvnaspHYcN6+NOSBB+4IIThNlQWx0DeO4pz3N/GCUzf7Nr/1FNCocny +Yh0igzyXxfkZYiesZSLX0zzG5Y6yU8xJzrww/nsOM5D77dIUkR8Hrw== +-----END CERTIFICATE----- + +Security Communication RootCA2 +============================== +-----BEGIN CERTIFICATE----- +MIIDdzCCAl+gAwIBAgIBADANBgkqhkiG9w0BAQsFADBdMQswCQYDVQQGEwJKUDElMCMGA1UEChMc +U0VDT00gVHJ1c3QgU3lzdGVtcyBDTy4sTFRELjEnMCUGA1UECxMeU2VjdXJpdHkgQ29tbXVuaWNh +dGlvbiBSb290Q0EyMB4XDTA5MDUyOTA1MDAzOVoXDTI5MDUyOTA1MDAzOVowXTELMAkGA1UEBhMC +SlAxJTAjBgNVBAoTHFNFQ09NIFRydXN0IFN5c3RlbXMgQ08uLExURC4xJzAlBgNVBAsTHlNlY3Vy +aXR5IENvbW11bmljYXRpb24gUm9vdENBMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB +ANAVOVKxUrO6xVmCxF1SrjpDZYBLx/KWvNs2l9amZIyoXvDjChz335c9S672XewhtUGrzbl+dp++ ++T42NKA7wfYxEUV0kz1XgMX5iZnK5atq1LXaQZAQwdbWQonCv/Q4EpVMVAX3NuRFg3sUZdbcDE3R +3n4MqzvEFb46VqZab3ZpUql6ucjrappdUtAtCms1FgkQhNBqyjoGADdH5H5XTz+L62e4iKrFvlNV +spHEfbmwhRkGeC7bYRr6hfVKkaHnFtWOojnflLhwHyg/i/xAXmODPIMqGplrz95Zajv8bxbXH/1K +EOtOghY6rCcMU/Gt1SSwawNQwS08Ft1ENCcadfsCAwEAAaNCMEAwHQYDVR0OBBYEFAqFqXdlBZh8 +QIH4D5csOPEK7DzPMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEB +CwUAA4IBAQBMOqNErLlFsceTfsgLCkLfZOoc7llsCLqJX2rKSpWeeo8HxdpFcoJxDjrSzG+ntKEj +u/Ykn8sX/oymzsLS28yN/HH8AynBbF0zX2S2ZTuJbxh2ePXcokgfGT+Ok+vx+hfuzU7jBBJV1uXk +3fs+BXziHV7Gp7yXT2g69ekuCkO2r1dcYmh8t/2jioSgrGK+KwmHNPBqAbubKVY8/gA3zyNs8U6q +tnRGEmyR7jTV7JqR50S+kDFy1UkC9gLl9B/rfNmWVan/7Ir5mUf/NVoCqgTLiluHcSmRvaS0eg29 +mvVXIwAHIRc/SjnRBUkLp7Y3gaVdjKozXoEofKd9J+sAro03 +-----END CERTIFICATE----- + +EC-ACC +====== +-----BEGIN CERTIFICATE----- +MIIFVjCCBD6gAwIBAgIQ7is969Qh3hSoYqwE893EATANBgkqhkiG9w0BAQUFADCB8zELMAkGA1UE +BhMCRVMxOzA5BgNVBAoTMkFnZW5jaWEgQ2F0YWxhbmEgZGUgQ2VydGlmaWNhY2lvIChOSUYgUS0w +ODAxMTc2LUkpMSgwJgYDVQQLEx9TZXJ2ZWlzIFB1YmxpY3MgZGUgQ2VydGlmaWNhY2lvMTUwMwYD +VQQLEyxWZWdldSBodHRwczovL3d3dy5jYXRjZXJ0Lm5ldC92ZXJhcnJlbCAoYykwMzE1MDMGA1UE +CxMsSmVyYXJxdWlhIEVudGl0YXRzIGRlIENlcnRpZmljYWNpbyBDYXRhbGFuZXMxDzANBgNVBAMT +BkVDLUFDQzAeFw0wMzAxMDcyMzAwMDBaFw0zMTAxMDcyMjU5NTlaMIHzMQswCQYDVQQGEwJFUzE7 +MDkGA1UEChMyQWdlbmNpYSBDYXRhbGFuYSBkZSBDZXJ0aWZpY2FjaW8gKE5JRiBRLTA4MDExNzYt +SSkxKDAmBgNVBAsTH1NlcnZlaXMgUHVibGljcyBkZSBDZXJ0aWZpY2FjaW8xNTAzBgNVBAsTLFZl +Z2V1IGh0dHBzOi8vd3d3LmNhdGNlcnQubmV0L3ZlcmFycmVsIChjKTAzMTUwMwYDVQQLEyxKZXJh +cnF1aWEgRW50aXRhdHMgZGUgQ2VydGlmaWNhY2lvIENhdGFsYW5lczEPMA0GA1UEAxMGRUMtQUND +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsyLHT+KXQpWIR4NA9h0X84NzJB5R85iK +w5K4/0CQBXCHYMkAqbWUZRkiFRfCQ2xmRJoNBD45b6VLeqpjt4pEndljkYRm4CgPukLjbo73FCeT +ae6RDqNfDrHrZqJyTxIThmV6PttPB/SnCWDaOkKZx7J/sxaVHMf5NLWUhdWZXqBIoH7nF2W4onW4 +HvPlQn2v7fOKSGRdghST2MDk/7NQcvJ29rNdQlB50JQ+awwAvthrDk4q7D7SzIKiGGUzE3eeml0a +E9jD2z3Il3rucO2n5nzbcc8tlGLfbdb1OL4/pYUKGbio2Al1QnDE6u/LDsg0qBIimAy4E5S2S+zw +0JDnJwIDAQABo4HjMIHgMB0GA1UdEQQWMBSBEmVjX2FjY0BjYXRjZXJ0Lm5ldDAPBgNVHRMBAf8E +BTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUoMOLRKo3pUW/l4Ba0fF4opvpXY0wfwYD +VR0gBHgwdjB0BgsrBgEEAfV4AQMBCjBlMCwGCCsGAQUFBwIBFiBodHRwczovL3d3dy5jYXRjZXJ0 +Lm5ldC92ZXJhcnJlbDA1BggrBgEFBQcCAjApGidWZWdldSBodHRwczovL3d3dy5jYXRjZXJ0Lm5l +dC92ZXJhcnJlbCAwDQYJKoZIhvcNAQEFBQADggEBAKBIW4IB9k1IuDlVNZyAelOZ1Vr/sXE7zDkJ +lF7W2u++AVtd0x7Y/X1PzaBB4DSTv8vihpw3kpBWHNzrKQXlxJ7HNd+KDM3FIUPpqojlNcAZQmNa +Al6kSBg6hW/cnbw/nZzBh7h6YQjpdwt/cKt63dmXLGQehb+8dJahw3oS7AwaboMMPOhyRp/7SNVe +l+axofjk70YllJyJ22k4vuxcDlbHZVHlUIiIv0LVKz3l+bqeLrPK9HOSAgu+TGbrIP65y7WZf+a2 +E/rKS03Z7lNGBjvGTq2TWoF+bCpLagVFjPIhpDGQh2xlnJ2lYJU6Un/10asIbvPuW/mIPX64b24D +5EI= +-----END CERTIFICATE----- + +Hellenic Academic and Research Institutions RootCA 2011 +======================================================= +-----BEGIN CERTIFICATE----- +MIIEMTCCAxmgAwIBAgIBADANBgkqhkiG9w0BAQUFADCBlTELMAkGA1UEBhMCR1IxRDBCBgNVBAoT +O0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgQ2VydC4gQXV0aG9y +aXR5MUAwPgYDVQQDEzdIZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0dXRpb25z +IFJvb3RDQSAyMDExMB4XDTExMTIwNjEzNDk1MloXDTMxMTIwMTEzNDk1MlowgZUxCzAJBgNVBAYT +AkdSMUQwQgYDVQQKEztIZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0dXRpb25z +IENlcnQuIEF1dGhvcml0eTFAMD4GA1UEAxM3SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNo +IEluc3RpdHV0aW9ucyBSb290Q0EgMjAxMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB +AKlTAOMupvaO+mDYLZU++CwqVE7NuYRhlFhPjz2L5EPzdYmNUeTDN9KKiE15HrcS3UN4SoqS5tdI +1Q+kOilENbgH9mgdVc04UfCMJDGFr4PJfel3r+0ae50X+bOdOFAPplp5kYCvN66m0zH7tSYJnTxa +71HFK9+WXesyHgLacEnsbgzImjeN9/E2YEsmLIKe0HjzDQ9jpFEw4fkrJxIH2Oq9GGKYsFk3fb7u +8yBRQlqD75O6aRXxYp2fmTmCobd0LovUxQt7L/DICto9eQqakxylKHJzkUOap9FNhYS5qXSPFEDH +3N6sQWRstBmbAmNtJGSPRLIl6s5ddAxjMlyNh+UCAwEAAaOBiTCBhjAPBgNVHRMBAf8EBTADAQH/ +MAsGA1UdDwQEAwIBBjAdBgNVHQ4EFgQUppFC/RNhSiOeCKQp5dgTBCPuQSUwRwYDVR0eBEAwPqA8 +MAWCAy5ncjAFggMuZXUwBoIELmVkdTAGggQub3JnMAWBAy5ncjAFgQMuZXUwBoEELmVkdTAGgQQu +b3JnMA0GCSqGSIb3DQEBBQUAA4IBAQAf73lB4XtuP7KMhjdCSk4cNx6NZrokgclPEg8hwAOXhiVt +XdMiKahsog2p6z0GW5k6x8zDmjR/qw7IThzh+uTczQ2+vyT+bOdrwg3IBp5OjWEopmr95fZi6hg8 +TqBTnbI6nOulnJEWtk2C4AwFSKls9cz4y51JtPACpf1wA+2KIaWuE4ZJwzNzvoc7dIsXRSZMFpGD +/md9zU1jZ/rzAxKWeAaNsWftjj++n08C9bMJL/NMh98qy5V8AcysNnq/onN694/BtZqhFLKPM58N +7yLcZnuEvUUXBj08yrl3NI/K6s8/MT7jiOOASSXIl7WdmplNsDz4SgCbZN2fOUvRJ9e4 +-----END CERTIFICATE----- + +Actalis Authentication Root CA +============================== +-----BEGIN CERTIFICATE----- +MIIFuzCCA6OgAwIBAgIIVwoRl0LE48wwDQYJKoZIhvcNAQELBQAwazELMAkGA1UEBhMCSVQxDjAM +BgNVBAcMBU1pbGFuMSMwIQYDVQQKDBpBY3RhbGlzIFMucC5BLi8wMzM1ODUyMDk2NzEnMCUGA1UE +AwweQWN0YWxpcyBBdXRoZW50aWNhdGlvbiBSb290IENBMB4XDTExMDkyMjExMjIwMloXDTMwMDky +MjExMjIwMlowazELMAkGA1UEBhMCSVQxDjAMBgNVBAcMBU1pbGFuMSMwIQYDVQQKDBpBY3RhbGlz +IFMucC5BLi8wMzM1ODUyMDk2NzEnMCUGA1UEAwweQWN0YWxpcyBBdXRoZW50aWNhdGlvbiBSb290 +IENBMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAp8bEpSmkLO/lGMWwUKNvUTufClrJ +wkg4CsIcoBh/kbWHuUA/3R1oHwiD1S0eiKD4j1aPbZkCkpAW1V8IbInX4ay8IMKx4INRimlNAJZa +by/ARH6jDuSRzVju3PvHHkVH3Se5CAGfpiEd9UEtL0z9KK3giq0itFZljoZUj5NDKd45RnijMCO6 +zfB9E1fAXdKDa0hMxKufgFpbOr3JpyI/gCczWw63igxdBzcIy2zSekciRDXFzMwujt0q7bd9Zg1f +YVEiVRvjRuPjPdA1YprbrxTIW6HMiRvhMCb8oJsfgadHHwTrozmSBp+Z07/T6k9QnBn+locePGX2 +oxgkg4YQ51Q+qDp2JE+BIcXjDwL4k5RHILv+1A7TaLndxHqEguNTVHnd25zS8gebLra8Pu2Fbe8l +EfKXGkJh90qX6IuxEAf6ZYGyojnP9zz/GPvG8VqLWeICrHuS0E4UT1lF9gxeKF+w6D9Fz8+vm2/7 +hNN3WpVvrJSEnu68wEqPSpP4RCHiMUVhUE4Q2OM1fEwZtN4Fv6MGn8i1zeQf1xcGDXqVdFUNaBr8 +EBtiZJ1t4JWgw5QHVw0U5r0F+7if5t+L4sbnfpb2U8WANFAoWPASUHEXMLrmeGO89LKtmyuy/uE5 +jF66CyCU3nuDuP/jVo23Eek7jPKxwV2dpAtMK9myGPW1n0sCAwEAAaNjMGEwHQYDVR0OBBYEFFLY +iDrIn3hm7YnzezhwlMkCAjbQMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUUtiIOsifeGbt +ifN7OHCUyQICNtAwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEBCwUAA4ICAQALe3KHwGCmSUyI +WOYdiPcUZEim2FgKDk8TNd81HdTtBjHIgT5q1d07GjLukD0R0i70jsNjLiNmsGe+b7bAEzlgqqI0 +JZN1Ut6nna0Oh4lScWoWPBkdg/iaKWW+9D+a2fDzWochcYBNy+A4mz+7+uAwTc+G02UQGRjRlwKx +K3JCaKygvU5a2hi/a5iB0P2avl4VSM0RFbnAKVy06Ij3Pjaut2L9HmLecHgQHEhb2rykOLpn7VU+ +Xlff1ANATIGk0k9jpwlCCRT8AKnCgHNPLsBA2RF7SOp6AsDT6ygBJlh0wcBzIm2Tlf05fbsq4/aC +4yyXX04fkZT6/iyj2HYauE2yOE+b+h1IYHkm4vP9qdCa6HCPSXrW5b0KDtst842/6+OkfcvHlXHo +2qN8xcL4dJIEG4aspCJTQLas/kx2z/uUMsA1n3Y/buWQbqCmJqK4LL7RK4X9p2jIugErsWx0Hbhz +lefut8cl8ABMALJ+tguLHPPAUJ4lueAI3jZm/zel0btUZCzJJ7VLkn5l/9Mt4blOvH+kQSGQQXem +OR/qnuOf0GZvBeyqdn6/axag67XH/JJULysRJyU3eExRarDzzFhdFPFqSBX/wge2sY0PjlxQRrM9 +vwGYT7JZVEc+NHt4bVaTLnPqZih4zR0Uv6CPLy64Lo7yFIrM6bV8+2ydDKXhlg== +-----END CERTIFICATE----- + +Trustis FPS Root CA +=================== +-----BEGIN CERTIFICATE----- +MIIDZzCCAk+gAwIBAgIQGx+ttiD5JNM2a/fH8YygWTANBgkqhkiG9w0BAQUFADBFMQswCQYDVQQG +EwJHQjEYMBYGA1UEChMPVHJ1c3RpcyBMaW1pdGVkMRwwGgYDVQQLExNUcnVzdGlzIEZQUyBSb290 +IENBMB4XDTAzMTIyMzEyMTQwNloXDTI0MDEyMTExMzY1NFowRTELMAkGA1UEBhMCR0IxGDAWBgNV +BAoTD1RydXN0aXMgTGltaXRlZDEcMBoGA1UECxMTVHJ1c3RpcyBGUFMgUm9vdCBDQTCCASIwDQYJ +KoZIhvcNAQEBBQADggEPADCCAQoCggEBAMVQe547NdDfxIzNjpvto8A2mfRC6qc+gIMPpqdZh8mQ +RUN+AOqGeSoDvT03mYlmt+WKVoaTnGhLaASMk5MCPjDSNzoiYYkchU59j9WvezX2fihHiTHcDnlk +H5nSW7r+f2C/revnPDgpai/lkQtV/+xvWNUtyd5MZnGPDNcE2gfmHhjjvSkCqPoc4Vu5g6hBSLwa +cY3nYuUtsuvffM/bq1rKMfFMIvMFE/eC+XN5DL7XSxzA0RU8k0Fk0ea+IxciAIleH2ulrG6nS4zt +o3Lmr2NNL4XSFDWaLk6M6jKYKIahkQlBOrTh4/L68MkKokHdqeMDx4gVOxzUGpTXn2RZEm0CAwEA +AaNTMFEwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBS6+nEleYtXQSUhhgtx67JkDoshZzAd +BgNVHQ4EFgQUuvpxJXmLV0ElIYYLceuyZA6LIWcwDQYJKoZIhvcNAQEFBQADggEBAH5Y//01GX2c +GE+esCu8jowU/yyg2kdbw++BLa8F6nRIW/M+TgfHbcWzk88iNVy2P3UnXwmWzaD+vkAMXBJV+JOC +yinpXj9WV4s4NvdFGkwozZ5BuO1WTISkQMi4sKUraXAEasP41BIy+Q7DsdwyhEQsb8tGD+pmQQ9P +8Vilpg0ND2HepZ5dfWWhPBfnqFVO76DH7cZEf1T1o+CP8HxVIo8ptoGj4W1OLBuAZ+ytIJ8MYmHV +l/9D7S3B2l0pKoU/rGXuhg8FjZBf3+6f9L/uHfuY5H+QK4R4EA5sSVPvFVtlRkpdr7r7OnIdzfYl +iB6XzCGcKQENZetX2fNXlrtIzYE= +-----END CERTIFICATE----- + +StartCom Certification Authority +================================ +-----BEGIN CERTIFICATE----- +MIIHhzCCBW+gAwIBAgIBLTANBgkqhkiG9w0BAQsFADB9MQswCQYDVQQGEwJJTDEWMBQGA1UEChMN +U3RhcnRDb20gTHRkLjErMCkGA1UECxMiU2VjdXJlIERpZ2l0YWwgQ2VydGlmaWNhdGUgU2lnbmlu +ZzEpMCcGA1UEAxMgU3RhcnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDYwOTE3MTk0 +NjM3WhcNMzYwOTE3MTk0NjM2WjB9MQswCQYDVQQGEwJJTDEWMBQGA1UEChMNU3RhcnRDb20gTHRk +LjErMCkGA1UECxMiU2VjdXJlIERpZ2l0YWwgQ2VydGlmaWNhdGUgU2lnbmluZzEpMCcGA1UEAxMg +U3RhcnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAw +ggIKAoICAQDBiNsJvGxGfHiflXu1M5DycmLWwTYgIiRezul38kMKogZkpMyONvg45iPwbm2xPN1y +o4UcodM9tDMr0y+v/uqwQVlntsQGfQqedIXWeUyAN3rfOQVSWff0G0ZDpNKFhdLDcfN1YjS6LIp/ +Ho/u7TTQEceWzVI9ujPW3U3eCztKS5/CJi/6tRYccjV3yjxd5srhJosaNnZcAdt0FCX+7bWgiA/d +eMotHweXMAEtcnn6RtYTKqi5pquDSR3l8u/d5AGOGAqPY1MWhWKpDhk6zLVmpsJrdAfkK+F2PrRt +2PZE4XNiHzvEvqBTViVsUQn3qqvKv3b9bZvzndu/PWa8DFaqr5hIlTpL36dYUNk4dalb6kMMAv+Z +6+hsTXBbKWWc3apdzK8BMewM69KN6Oqce+Zu9ydmDBpI125C4z/eIT574Q1w+2OqqGwaVLRcJXrJ +osmLFqa7LH4XXgVNWG4SHQHuEhANxjJ/GP/89PrNbpHoNkm+Gkhpi8KWTRoSsmkXwQqQ1vp5Iki/ +untp+HDH+no32NgN0nZPV/+Qt+OR0t3vwmC3Zzrd/qqc8NSLf3Iizsafl7b4r4qgEKjZ+xjGtrVc +UjyJthkqcwEKDwOzEmDyei+B26Nu/yYwl/WL3YlXtq09s68rxbd2AvCl1iuahhQqcvbjM4xdCUsT +37uMdBNSSwIDAQABo4ICEDCCAgwwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYD +VR0OBBYEFE4L7xqkQFulF2mHMMo0aEPQQa7yMB8GA1UdIwQYMBaAFE4L7xqkQFulF2mHMMo0aEPQ +Qa7yMIIBWgYDVR0gBIIBUTCCAU0wggFJBgsrBgEEAYG1NwEBATCCATgwLgYIKwYBBQUHAgEWImh0 +dHA6Ly93d3cuc3RhcnRzc2wuY29tL3BvbGljeS5wZGYwNAYIKwYBBQUHAgEWKGh0dHA6Ly93d3cu +c3RhcnRzc2wuY29tL2ludGVybWVkaWF0ZS5wZGYwgc8GCCsGAQUFBwICMIHCMCcWIFN0YXJ0IENv +bW1lcmNpYWwgKFN0YXJ0Q29tKSBMdGQuMAMCAQEagZZMaW1pdGVkIExpYWJpbGl0eSwgcmVhZCB0 +aGUgc2VjdGlvbiAqTGVnYWwgTGltaXRhdGlvbnMqIG9mIHRoZSBTdGFydENvbSBDZXJ0aWZpY2F0 +aW9uIEF1dGhvcml0eSBQb2xpY3kgYXZhaWxhYmxlIGF0IGh0dHA6Ly93d3cuc3RhcnRzc2wuY29t +L3BvbGljeS5wZGYwEQYJYIZIAYb4QgEBBAQDAgAHMDgGCWCGSAGG+EIBDQQrFilTdGFydENvbSBG +cmVlIFNTTCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTANBgkqhkiG9w0BAQsFAAOCAgEAjo/n3JR5 +fPGFf59Jb2vKXfuM/gTFwWLRfUKKvFO3lANmMD+x5wqnUCBVJX92ehQN6wQOQOY+2IirByeDqXWm +N3PH/UvSTa0XQMhGvjt/UfzDtgUx3M2FIk5xt/JxXrAaxrqTi3iSSoX4eA+D/i+tLPfkpLst0OcN +Org+zvZ49q5HJMqjNTbOx8aHmNrs++myziebiMMEofYLWWivydsQD032ZGNcpRJvkrKTlMeIFw6T +tn5ii5B/q06f/ON1FE8qMt9bDeD1e5MNq6HPh+GlBEXoPBKlCcWw0bdT82AUuoVpaiF8H3VhFyAX +e2w7QSlc4axa0c2Mm+tgHRns9+Ww2vl5GKVFP0lDV9LdJNUso/2RjSe15esUBppMeyG7Oq0wBhjA +2MFrLH9ZXF2RsXAiV+uKa0hK1Q8p7MZAwC+ITGgBF3f0JBlPvfrhsiAhS90a2Cl9qrjeVOwhVYBs +HvUwyKMQ5bLmKhQxw4UtjJixhlpPiVktucf3HMiKf8CdBUrmQk9io20ppB+Fq9vlgcitKj1MXVuE +JnHEhV5xJMqlG2zYYdMa4FTbzrqpMrUi9nNBCV24F10OD5mQ1kfabwo6YigUZ4LZ8dCAWZvLMdib +D4x3TrVoivJs9iQOLWxwxXPR3hTQcY+203sC9uO41Alua551hDnmfyWl8kgAwKQB2j8= +-----END CERTIFICATE----- + +StartCom Certification Authority G2 +=================================== +-----BEGIN CERTIFICATE----- +MIIFYzCCA0ugAwIBAgIBOzANBgkqhkiG9w0BAQsFADBTMQswCQYDVQQGEwJJTDEWMBQGA1UEChMN +U3RhcnRDb20gTHRkLjEsMCoGA1UEAxMjU3RhcnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkg +RzIwHhcNMTAwMTAxMDEwMDAxWhcNMzkxMjMxMjM1OTAxWjBTMQswCQYDVQQGEwJJTDEWMBQGA1UE +ChMNU3RhcnRDb20gTHRkLjEsMCoGA1UEAxMjU3RhcnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3Jp +dHkgRzIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC2iTZbB7cgNr2Cu+EWIAOVeq8O +o1XJJZlKxdBWQYeQTSFgpBSHO839sj60ZwNq7eEPS8CRhXBF4EKe3ikj1AENoBB5uNsDvfOpL9HG +4A/LnooUCri99lZi8cVytjIl2bLzvWXFDSxu1ZJvGIsAQRSCb0AgJnooD/Uefyf3lLE3PbfHkffi +Aez9lInhzG7TNtYKGXmu1zSCZf98Qru23QumNK9LYP5/Q0kGi4xDuFby2X8hQxfqp0iVAXV16iul +Q5XqFYSdCI0mblWbq9zSOdIxHWDirMxWRST1HFSr7obdljKF+ExP6JV2tgXdNiNnvP8V4so75qbs +O+wmETRIjfaAKxojAuuKHDp2KntWFhxyKrOq42ClAJ8Em+JvHhRYW6Vsi1g8w7pOOlz34ZYrPu8H +vKTlXcxNnw3h3Kq74W4a7I/htkxNeXJdFzULHdfBR9qWJODQcqhaX2YtENwvKhOuJv4KHBnM0D4L +nMgJLvlblnpHnOl68wVQdJVznjAJ85eCXuaPOQgeWeU1FEIT/wCc976qUM/iUUjXuG+v+E5+M5iS +FGI6dWPPe/regjupuznixL0sAA7IF6wT700ljtizkC+p2il9Ha90OrInwMEePnWjFqmveiJdnxMa +z6eg6+OGCtP95paV1yPIN93EfKo2rJgaErHgTuixO/XWb/Ew1wIDAQABo0IwQDAPBgNVHRMBAf8E +BTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUS8W0QGutHLOlHGVuRjaJhwUMDrYwDQYJ +KoZIhvcNAQELBQADggIBAHNXPyzVlTJ+N9uWkusZXn5T50HsEbZH77Xe7XRcxfGOSeD8bpkTzZ+K +2s06Ctg6Wgk/XzTQLwPSZh0avZyQN8gMjgdalEVGKua+etqhqaRpEpKwfTbURIfXUfEpY9Z1zRbk +J4kd+MIySP3bmdCPX1R0zKxnNBFi2QwKN4fRoxdIjtIXHfbX/dtl6/2o1PXWT6RbdejF0mCy2wl+ +JYt7ulKSnj7oxXehPOBKc2thz4bcQ///If4jXSRK9dNtD2IEBVeC2m6kMyV5Sy5UGYvMLD0w6dEG +/+gyRr61M3Z3qAFdlsHB1b6uJcDJHgoJIIihDsnzb02CVAAgp9KP5DlUFy6NHrgbuxu9mk47EDTc +nIhT76IxW1hPkWLIwpqazRVdOKnWvvgTtZ8SafJQYqz7Fzf07rh1Z2AQ+4NQ+US1dZxAF7L+/Xld +blhYXzD8AK6vM8EOTmy6p6ahfzLbOOCxchcKK5HsamMm7YnUeMx0HgX4a/6ManY5Ka5lIxKVCCIc +l85bBu4M4ru8H0ST9tg4RQUh7eStqxK2A6RCLi3ECToDZ2mEmuFZkIoohdVddLHRDiBYmxOlsGOm +7XtH/UVVMKTumtTm4ofvmMkyghEpIrwACjFeLQ/Ajulrso8uBtjRkcfGEvRM/TAXw8HaOFvjqerm +obp573PYtlNXLfbQ4ddI +-----END CERTIFICATE----- + +Buypass Class 2 Root CA +======================= +-----BEGIN CERTIFICATE----- +MIIFWTCCA0GgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJOTzEdMBsGA1UECgwU +QnV5cGFzcyBBUy05ODMxNjMzMjcxIDAeBgNVBAMMF0J1eXBhc3MgQ2xhc3MgMiBSb290IENBMB4X +DTEwMTAyNjA4MzgwM1oXDTQwMTAyNjA4MzgwM1owTjELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1 +eXBhc3MgQVMtOTgzMTYzMzI3MSAwHgYDVQQDDBdCdXlwYXNzIENsYXNzIDIgUm9vdCBDQTCCAiIw +DQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANfHXvfBB9R3+0Mh9PT1aeTuMgHbo4Yf5FkNuud1 +g1Lr6hxhFUi7HQfKjK6w3Jad6sNgkoaCKHOcVgb/S2TwDCo3SbXlzwx87vFKu3MwZfPVL4O2fuPn +9Z6rYPnT8Z2SdIrkHJasW4DptfQxh6NR/Md+oW+OU3fUl8FVM5I+GC911K2GScuVr1QGbNgGE41b +/+EmGVnAJLqBcXmQRFBoJJRfuLMR8SlBYaNByyM21cHxMlAQTn/0hpPshNOOvEu/XAFOBz3cFIqU +CqTqc/sLUegTBxj6DvEr0VQVfTzh97QZQmdiXnfgolXsttlpF9U6r0TtSsWe5HonfOV116rLJeff +awrbD02TTqigzXsu8lkBarcNuAeBfos4GzjmCleZPe4h6KP1DBbdi+w0jpwqHAAVF41og9JwnxgI +zRFo1clrUs3ERo/ctfPYV3Me6ZQ5BL/T3jjetFPsaRyifsSP5BtwrfKi+fv3FmRmaZ9JUaLiFRhn +Bkp/1Wy1TbMz4GHrXb7pmA8y1x1LPC5aAVKRCfLf6o3YBkBjqhHk/sM3nhRSP/TizPJhk9H9Z2vX +Uq6/aKtAQ6BXNVN48FP4YUIHZMbXb5tMOA1jrGKvNouicwoN9SG9dKpN6nIDSdvHXx1iY8f93ZHs +M+71bbRuMGjeyNYmsHVee7QHIJihdjK4TWxPAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYD +VR0OBBYEFMmAd+BikoL1RpzzuvdMw964o605MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsF +AAOCAgEAU18h9bqwOlI5LJKwbADJ784g7wbylp7ppHR/ehb8t/W2+xUbP6umwHJdELFx7rxP462s +A20ucS6vxOOto70MEae0/0qyexAQH6dXQbLArvQsWdZHEIjzIVEpMMpghq9Gqx3tOluwlN5E40EI +osHsHdb9T7bWR9AUC8rmyrV7d35BH16Dx7aMOZawP5aBQW9gkOLo+fsicdl9sz1Gv7SEr5AcD48S +aq/v7h56rgJKihcrdv6sVIkkLE8/trKnToyokZf7KcZ7XC25y2a2t6hbElGFtQl+Ynhw/qlqYLYd +DnkM/crqJIByw5c/8nerQyIKx+u2DISCLIBrQYoIwOula9+ZEsuK1V6ADJHgJgg2SMX6OBE1/yWD +LfJ6v9r9jv6ly0UsH8SIU653DtmadsWOLB2jutXsMq7Aqqz30XpN69QH4kj3Io6wpJ9qzo6ysmD0 +oyLQI+uUWnpp3Q+/QFesa1lQ2aOZ4W7+jQF5JyMV3pKdewlNWudLSDBaGOYKbeaP4NK75t98biGC +wWg5TbSYWGZizEqQXsP6JwSxeRV0mcy+rSDeJmAc61ZRpqPq5KM/p/9h3PFaTWwyI0PurKju7koS +CTxdccK+efrCh2gdC/1cacwG0Jp9VJkqyTkaGa9LKkPzY11aWOIv4x3kqdbQCtCev9eBCfHJxyYN +rJgWVqA= +-----END CERTIFICATE----- + +Buypass Class 3 Root CA +======================= +-----BEGIN CERTIFICATE----- +MIIFWTCCA0GgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJOTzEdMBsGA1UECgwU +QnV5cGFzcyBBUy05ODMxNjMzMjcxIDAeBgNVBAMMF0J1eXBhc3MgQ2xhc3MgMyBSb290IENBMB4X +DTEwMTAyNjA4Mjg1OFoXDTQwMTAyNjA4Mjg1OFowTjELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1 +eXBhc3MgQVMtOTgzMTYzMzI3MSAwHgYDVQQDDBdCdXlwYXNzIENsYXNzIDMgUm9vdCBDQTCCAiIw +DQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAKXaCpUWUOOV8l6ddjEGMnqb8RB2uACatVI2zSRH +sJ8YZLya9vrVediQYkwiL944PdbgqOkcLNt4EemOaFEVcsfzM4fkoF0LXOBXByow9c3EN3coTRiR +5r/VUv1xLXA+58bEiuPwKAv0dpihi4dVsjoT/Lc+JzeOIuOoTyrvYLs9tznDDgFHmV0ST9tD+leh +7fmdvhFHJlsTmKtdFoqwNxxXnUX/iJY2v7vKB3tvh2PX0DJq1l1sDPGzbjniazEuOQAnFN44wOwZ +ZoYS6J1yFhNkUsepNxz9gjDthBgd9K5c/3ATAOux9TN6S9ZV+AWNS2mw9bMoNlwUxFFzTWsL8TQH +2xc519woe2v1n/MuwU8XKhDzzMro6/1rqy6any2CbgTUUgGTLT2G/H783+9CHaZr77kgxve9oKeV +/afmiSTYzIw0bOIjL9kSGiG5VZFvC5F5GQytQIgLcOJ60g7YaEi7ghM5EFjp2CoHxhLbWNvSO1UQ +RwUVZ2J+GGOmRj8JDlQyXr8NYnon74Do29lLBlo3WiXQCBJ31G8JUJc9yB3D34xFMFbG02SrZvPA +Xpacw8Tvw3xrizp5f7NJzz3iiZ+gMEuFuZyUJHmPfWupRWgPK9Dx2hzLabjKSWJtyNBjYt1gD1iq +j6G8BaVmos8bdrKEZLFMOVLAMLrwjEsCsLa3AgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYD +VR0OBBYEFEe4zf/lb+74suwvTg75JbCOPGvDMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsF +AAOCAgEAACAjQTUEkMJAYmDv4jVM1z+s4jSQuKFvdvoWFqRINyzpkMLyPPgKn9iB5btb2iUspKdV +cSQy9sgL8rxq+JOssgfCX5/bzMiKqr5qb+FJEMwx14C7u8jYog5kV+qi9cKpMRXSIGrs/CIBKM+G +uIAeqcwRpTzyFrNHnfzSgCHEy9BHcEGhyoMZCCxt8l13nIoUE9Q2HJLw5QY33KbmkJs4j1xrG0aG +Q0JfPgEHU1RdZX33inOhmlRaHylDFCfChQ+1iHsaO5S3HWCntZznKWlXWpuTekMwGwPXYshApqr8 +ZORK15FTAaggiG6cX0S5y2CBNOxv033aSF/rtJC8LakcC6wc1aJoIIAE1vyxjy+7SjENSoYc6+I2 +KSb12tjE8nVhz36udmNKekBlk4f4HoCMhuWG1o8O/FMsYOgWYRqiPkN7zTlgVGr18okmAWiDSKIz +6MkEkbIRNBE+6tBDGR8Dk5AM/1E9V/RBbuHLoL7ryWPNbczk+DaqaJ3tvV2XcEQNtg413OEMXbug +UZTLfhbrES+jkkXITHHZvMmZUldGL1DPvTVp9D0VzgalLA8+9oG6lLvDu79leNKGef9JOxqDDPDe +eOzI8k1MGt6CKfjBWtrt7uYnXuhF0J0cUahoq0Tj0Itq4/g7u9xN12TyUb7mqqta6THuBrxzvxNi +Cp/HuZc= +-----END CERTIFICATE----- + +T-TeleSec GlobalRoot Class 3 +============================ +-----BEGIN CERTIFICATE----- +MIIDwzCCAqugAwIBAgIBATANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoM +IlQtU3lzdGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBU +cnVzdCBDZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDMwHhcNMDgx +MDAxMTAyOTU2WhcNMzMxMDAxMjM1OTU5WjCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoMIlQtU3lz +dGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBD +ZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDMwggEiMA0GCSqGSIb3 +DQEBAQUAA4IBDwAwggEKAoIBAQC9dZPwYiJvJK7genasfb3ZJNW4t/zN8ELg63iIVl6bmlQdTQyK +9tPPcPRStdiTBONGhnFBSivwKixVA9ZIw+A5OO3yXDw/RLyTPWGrTs0NvvAgJ1gORH8EGoel15YU +NpDQSXuhdfsaa3Ox+M6pCSzyU9XDFES4hqX2iys52qMzVNn6chr3IhUciJFrf2blw2qAsCTz34ZF +iP0Zf3WHHx+xGwpzJFu5ZeAsVMhg02YXP+HMVDNzkQI6pn97djmiH5a2OK61yJN0HZ65tOVgnS9W +0eDrXltMEnAMbEQgqxHY9Bn20pxSN+f6tsIxO0rUFJmtxxr1XV/6B7h8DR/Wgx6zAgMBAAGjQjBA +MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBS1A/d2O2GCahKqGFPr +AyGUv/7OyjANBgkqhkiG9w0BAQsFAAOCAQEAVj3vlNW92nOyWL6ukK2YJ5f+AbGwUgC4TeQbIXQb +fsDuXmkqJa9c1h3a0nnJ85cp4IaH3gRZD/FZ1GSFS5mvJQQeyUapl96Cshtwn5z2r3Ex3XsFpSzT +ucpH9sry9uetuUg/vBa3wW306gmv7PO15wWeph6KU1HWk4HMdJP2udqmJQV0eVp+QD6CSyYRMG7h +P0HHRwA11fXT91Q+gT3aSWqas+8QPebrb9HIIkfLzM8BMZLZGOMivgkeGj5asuRrDFR6fUNOuIml +e9eiPZaGzPImNC1qkp2aGtAw4l1OBLBfiyB+d8E9lYLRRpo7PHi4b6HQDWSieB4pTpPDpFQUWw== +-----END CERTIFICATE----- + +EE Certification Centre Root CA +=============================== +-----BEGIN CERTIFICATE----- +MIIEAzCCAuugAwIBAgIQVID5oHPtPwBMyonY43HmSjANBgkqhkiG9w0BAQUFADB1MQswCQYDVQQG +EwJFRTEiMCAGA1UECgwZQVMgU2VydGlmaXRzZWVyaW1pc2tlc2t1czEoMCYGA1UEAwwfRUUgQ2Vy +dGlmaWNhdGlvbiBDZW50cmUgUm9vdCBDQTEYMBYGCSqGSIb3DQEJARYJcGtpQHNrLmVlMCIYDzIw +MTAxMDMwMTAxMDMwWhgPMjAzMDEyMTcyMzU5NTlaMHUxCzAJBgNVBAYTAkVFMSIwIAYDVQQKDBlB +UyBTZXJ0aWZpdHNlZXJpbWlza2Vza3VzMSgwJgYDVQQDDB9FRSBDZXJ0aWZpY2F0aW9uIENlbnRy +ZSBSb290IENBMRgwFgYJKoZIhvcNAQkBFglwa2lAc2suZWUwggEiMA0GCSqGSIb3DQEBAQUAA4IB +DwAwggEKAoIBAQDIIMDs4MVLqwd4lfNE7vsLDP90jmG7sWLqI9iroWUyeuuOF0+W2Ap7kaJjbMeM +TC55v6kF/GlclY1i+blw7cNRfdCT5mzrMEvhvH2/UpvObntl8jixwKIy72KyaOBhU8E2lf/slLo2 +rpwcpzIP5Xy0xm90/XsY6KxX7QYgSzIwWFv9zajmofxwvI6Sc9uXp3whrj3B9UiHbCe9nyV0gVWw +93X2PaRka9ZP585ArQ/dMtO8ihJTmMmJ+xAdTX7Nfh9WDSFwhfYggx/2uh8Ej+p3iDXE/+pOoYtN +P2MbRMNE1CV2yreN1x5KZmTNXMWcg+HCCIia7E6j8T4cLNlsHaFLAgMBAAGjgYowgYcwDwYDVR0T +AQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFBLyWj7qVhy/zQas8fElyalL1BSZ +MEUGA1UdJQQ+MDwGCCsGAQUFBwMCBggrBgEFBQcDAQYIKwYBBQUHAwMGCCsGAQUFBwMEBggrBgEF +BQcDCAYIKwYBBQUHAwkwDQYJKoZIhvcNAQEFBQADggEBAHv25MANqhlHt01Xo/6tu7Fq1Q+e2+Rj +xY6hUFaTlrg4wCQiZrxTFGGVv9DHKpY5P30osxBAIWrEr7BSdxjhlthWXePdNl4dp1BUoMUq5KqM +lIpPnTX/dqQGE5Gion0ARD9V04I8GtVbvFZMIi5GQ4okQC3zErg7cBqklrkar4dBGmoYDQZPxz5u +uSlNDUmJEYcyW+ZLBMjkXOZ0c5RdFpgTlf7727FE5TpwrDdr5rMzcijJs1eg9gIWiAYLtqZLICjU +3j2LrTcFU3T+bsy8QxdxXvnFzBqpYe73dgzzcvRyrc9yAjYHR8/vGVCJYMzpJJUPwssd8m92kMfM +dcGWxZ0= +-----END CERTIFICATE----- + +TURKTRUST Certificate Services Provider Root 2007 +================================================= +-----BEGIN CERTIFICATE----- +MIIEPTCCAyWgAwIBAgIBATANBgkqhkiG9w0BAQUFADCBvzE/MD0GA1UEAww2VMOcUktUUlVTVCBF +bGVrdHJvbmlrIFNlcnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sxc8SxMQswCQYDVQQGEwJUUjEP +MA0GA1UEBwwGQW5rYXJhMV4wXAYDVQQKDFVUw5xSS1RSVVNUIEJpbGdpIMSwbGV0acWfaW0gdmUg +QmlsacWfaW0gR8O8dmVubGnEn2kgSGl6bWV0bGVyaSBBLsWeLiAoYykgQXJhbMSxayAyMDA3MB4X +DTA3MTIyNTE4MzcxOVoXDTE3MTIyMjE4MzcxOVowgb8xPzA9BgNVBAMMNlTDnFJLVFJVU1QgRWxl +a3Ryb25payBTZXJ0aWZpa2EgSGl6bWV0IFNhxJ9sYXnEsWPEsXPEsTELMAkGA1UEBhMCVFIxDzAN +BgNVBAcMBkFua2FyYTFeMFwGA1UECgxVVMOcUktUUlVTVCBCaWxnaSDEsGxldGnFn2ltIHZlIEJp +bGnFn2ltIEfDvHZlbmxpxJ9pIEhpem1ldGxlcmkgQS7Fni4gKGMpIEFyYWzEsWsgMjAwNzCCASIw +DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKu3PgqMyKVYFeaK7yc9SrToJdPNM8Ig3BnuiD9N +YvDdE3ePYakqtdTyuTFYKTsvP2qcb3N2Je40IIDu6rfwxArNK4aUyeNgsURSsloptJGXg9i3phQv +KUmi8wUG+7RP2qFsmmaf8EMJyupyj+sA1zU511YXRxcw9L6/P8JorzZAwan0qafoEGsIiveGHtya +KhUG9qPw9ODHFNRRf8+0222vR5YXm3dx2KdxnSQM9pQ/hTEST7ruToK4uT6PIzdezKKqdfcYbwnT +rqdUKDT74eA7YH2gvnmJhsifLfkKS8RQouf9eRbHegsYz85M733WB2+Y8a+xwXrXgTW4qhe04MsC +AwEAAaNCMEAwHQYDVR0OBBYEFCnFkKslrxHkYb+j/4hhkeYO/pyBMA4GA1UdDwEB/wQEAwIBBjAP +BgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBBQUAA4IBAQAQDdr4Ouwo0RSVgrESLFF6QSU2TJ/s +Px+EnWVUXKgWAkD6bho3hO9ynYYKVZ1WKKxmLNA6VpM0ByWtCLCPyA8JWcqdmBzlVPi5RX9ql2+I +aE1KBiY3iAIOtsbWcpnOa3faYjGkVh+uX4132l32iPwa2Z61gfAyuOOI0JzzaqC5mxRZNTZPz/OO +Xl0XrRWV2N2y1RVuAE6zS89mlOTgzbUF2mNXi+WzqtvALhyQRNsaXRik7r4EW5nVcV9VZWRi1aKb +BFmGyGJ353yCRWo9F7/snXUMrqNvWtMvmDb08PUZqxFdyKbjKlhqQgnDvZImZjINXQhVdP+MmNAK +poRq0Tl9 +-----END CERTIFICATE----- + +D-TRUST Root Class 3 CA 2 2009 +============================== +-----BEGIN CERTIFICATE----- +MIIEMzCCAxugAwIBAgIDCYPzMA0GCSqGSIb3DQEBCwUAME0xCzAJBgNVBAYTAkRFMRUwEwYDVQQK +DAxELVRydXN0IEdtYkgxJzAlBgNVBAMMHkQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgMjAwOTAe +Fw0wOTExMDUwODM1NThaFw0yOTExMDUwODM1NThaME0xCzAJBgNVBAYTAkRFMRUwEwYDVQQKDAxE +LVRydXN0IEdtYkgxJzAlBgNVBAMMHkQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgMjAwOTCCASIw +DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANOySs96R+91myP6Oi/WUEWJNTrGa9v+2wBoqOAD +ER03UAifTUpolDWzU9GUY6cgVq/eUXjsKj3zSEhQPgrfRlWLJ23DEE0NkVJD2IfgXU42tSHKXzlA +BF9bfsyjxiupQB7ZNoTWSPOSHjRGICTBpFGOShrvUD9pXRl/RcPHAY9RySPocq60vFYJfxLLHLGv +KZAKyVXMD9O0Gu1HNVpK7ZxzBCHQqr0ME7UAyiZsxGsMlFqVlNpQmvH/pStmMaTJOKDfHR+4CS7z +p+hnUquVH+BGPtikw8paxTGA6Eian5Rp/hnd2HN8gcqW3o7tszIFZYQ05ub9VxC1X3a/L7AQDcUC +AwEAAaOCARowggEWMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFP3aFMSfMN4hvR5COfyrYyNJ +4PGEMA4GA1UdDwEB/wQEAwIBBjCB0wYDVR0fBIHLMIHIMIGAoH6gfIZ6bGRhcDovL2RpcmVjdG9y +eS5kLXRydXN0Lm5ldC9DTj1ELVRSVVNUJTIwUm9vdCUyMENsYXNzJTIwMyUyMENBJTIwMiUyMDIw +MDksTz1ELVRydXN0JTIwR21iSCxDPURFP2NlcnRpZmljYXRlcmV2b2NhdGlvbmxpc3QwQ6BBoD+G +PWh0dHA6Ly93d3cuZC10cnVzdC5uZXQvY3JsL2QtdHJ1c3Rfcm9vdF9jbGFzc18zX2NhXzJfMjAw +OS5jcmwwDQYJKoZIhvcNAQELBQADggEBAH+X2zDI36ScfSF6gHDOFBJpiBSVYEQBrLLpME+bUMJm +2H6NMLVwMeniacfzcNsgFYbQDfC+rAF1hM5+n02/t2A7nPPKHeJeaNijnZflQGDSNiH+0LS4F9p0 +o3/U37CYAqxva2ssJSRyoWXuJVrl5jLn8t+rSfrzkGkj2wTZ51xY/GXUl77M/C4KzCUqNQT4YJEV +dT1B/yMfGchs64JTBKbkTCJNjYy6zltz7GRUUG3RnFX7acM2w4y8PIWmawomDeCTmGCufsYkl4ph +X5GOZpIJhzbNi5stPvZR1FDUWSi9g/LMKHtThm3YJohw1+qRzT65ysCQblrGXnRl11z+o+I= +-----END CERTIFICATE----- + +D-TRUST Root Class 3 CA 2 EV 2009 +================================= +-----BEGIN CERTIFICATE----- +MIIEQzCCAyugAwIBAgIDCYP0MA0GCSqGSIb3DQEBCwUAMFAxCzAJBgNVBAYTAkRFMRUwEwYDVQQK +DAxELVRydXN0IEdtYkgxKjAoBgNVBAMMIUQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgRVYgMjAw +OTAeFw0wOTExMDUwODUwNDZaFw0yOTExMDUwODUwNDZaMFAxCzAJBgNVBAYTAkRFMRUwEwYDVQQK +DAxELVRydXN0IEdtYkgxKjAoBgNVBAMMIUQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgRVYgMjAw +OTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJnxhDRwui+3MKCOvXwEz75ivJn9gpfS +egpnljgJ9hBOlSJzmY3aFS3nBfwZcyK3jpgAvDw9rKFs+9Z5JUut8Mxk2og+KbgPCdM03TP1YtHh +zRnp7hhPTFiu4h7WDFsVWtg6uMQYZB7jM7K1iXdODL/ZlGsTl28So/6ZqQTMFexgaDbtCHu39b+T +7WYxg4zGcTSHThfqr4uRjRxWQa4iN1438h3Z0S0NL2lRp75mpoo6Kr3HGrHhFPC+Oh25z1uxav60 +sUYgovseO3Dvk5h9jHOW8sXvhXCtKSb8HgQ+HKDYD8tSg2J87otTlZCpV6LqYQXY+U3EJ/pure35 +11H3a6UCAwEAAaOCASQwggEgMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFNOUikxiEyoZLsyv +cop9NteaHNxnMA4GA1UdDwEB/wQEAwIBBjCB3QYDVR0fBIHVMIHSMIGHoIGEoIGBhn9sZGFwOi8v +ZGlyZWN0b3J5LmQtdHJ1c3QubmV0L0NOPUQtVFJVU1QlMjBSb290JTIwQ2xhc3MlMjAzJTIwQ0El +MjAyJTIwRVYlMjAyMDA5LE89RC1UcnVzdCUyMEdtYkgsQz1ERT9jZXJ0aWZpY2F0ZXJldm9jYXRp +b25saXN0MEagRKBChkBodHRwOi8vd3d3LmQtdHJ1c3QubmV0L2NybC9kLXRydXN0X3Jvb3RfY2xh +c3NfM19jYV8yX2V2XzIwMDkuY3JsMA0GCSqGSIb3DQEBCwUAA4IBAQA07XtaPKSUiO8aEXUHL7P+ +PPoeUSbrh/Yp3uDx1MYkCenBz1UbtDDZzhr+BlGmFaQt77JLvyAoJUnRpjZ3NOhk31KxEcdzes05 +nsKtjHEh8lprr988TlWvsoRlFIm5d8sqMb7Po23Pb0iUMkZv53GMoKaEGTcH8gNFCSuGdXzfX2lX +ANtu2KZyIktQ1HWYVt+3GP9DQ1CuekR78HlR10M9p9OB0/DJT7naxpeG0ILD5EJt/rDiZE4OJudA +NCa1CInXCGNjOCd1HjPqbqjdn5lPdE2BiYBL3ZqXKVwvvoFBuYz/6n1gBp7N1z3TLqMVvKjmJuVv +w9y4AyHqnxbxLFS1 +-----END CERTIFICATE----- + +PSCProcert +========== +-----BEGIN CERTIFICATE----- +MIIJhjCCB26gAwIBAgIBCzANBgkqhkiG9w0BAQsFADCCAR4xPjA8BgNVBAMTNUF1dG9yaWRhZCBk +ZSBDZXJ0aWZpY2FjaW9uIFJhaXogZGVsIEVzdGFkbyBWZW5lem9sYW5vMQswCQYDVQQGEwJWRTEQ +MA4GA1UEBxMHQ2FyYWNhczEZMBcGA1UECBMQRGlzdHJpdG8gQ2FwaXRhbDE2MDQGA1UEChMtU2lz +dGVtYSBOYWNpb25hbCBkZSBDZXJ0aWZpY2FjaW9uIEVsZWN0cm9uaWNhMUMwQQYDVQQLEzpTdXBl +cmludGVuZGVuY2lhIGRlIFNlcnZpY2lvcyBkZSBDZXJ0aWZpY2FjaW9uIEVsZWN0cm9uaWNhMSUw +IwYJKoZIhvcNAQkBFhZhY3JhaXpAc3VzY2VydGUuZ29iLnZlMB4XDTEwMTIyODE2NTEwMFoXDTIw +MTIyNTIzNTk1OVowgdExJjAkBgkqhkiG9w0BCQEWF2NvbnRhY3RvQHByb2NlcnQubmV0LnZlMQ8w +DQYDVQQHEwZDaGFjYW8xEDAOBgNVBAgTB01pcmFuZGExKjAoBgNVBAsTIVByb3ZlZWRvciBkZSBD +ZXJ0aWZpY2Fkb3MgUFJPQ0VSVDE2MDQGA1UEChMtU2lzdGVtYSBOYWNpb25hbCBkZSBDZXJ0aWZp +Y2FjaW9uIEVsZWN0cm9uaWNhMQswCQYDVQQGEwJWRTETMBEGA1UEAxMKUFNDUHJvY2VydDCCAiIw +DQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANW39KOUM6FGqVVhSQ2oh3NekS1wwQYalNo97BVC +wfWMrmoX8Yqt/ICV6oNEolt6Vc5Pp6XVurgfoCfAUFM+jbnADrgV3NZs+J74BCXfgI8Qhd19L3uA +3VcAZCP4bsm+lU/hdezgfl6VzbHvvnpC2Mks0+saGiKLt38GieU89RLAu9MLmV+QfI4tL3czkkoh +RqipCKzx9hEC2ZUWno0vluYC3XXCFCpa1sl9JcLB/KpnheLsvtF8PPqv1W7/U0HU9TI4seJfxPmO +EO8GqQKJ/+MMbpfg353bIdD0PghpbNjU5Db4g7ayNo+c7zo3Fn2/omnXO1ty0K+qP1xmk6wKImG2 +0qCZyFSTXai20b1dCl53lKItwIKOvMoDKjSuc/HUtQy9vmebVOvh+qBa7Dh+PsHMosdEMXXqP+UH +0quhJZb25uSgXTcYOWEAM11G1ADEtMo88aKjPvM6/2kwLkDd9p+cJsmWN63nOaK/6mnbVSKVUyqU +td+tFjiBdWbjxywbk5yqjKPK2Ww8F22c3HxT4CAnQzb5EuE8XL1mv6JpIzi4mWCZDlZTOpx+FIyw +Bm/xhnaQr/2v/pDGj59/i5IjnOcVdo/Vi5QTcmn7K2FjiO/mpF7moxdqWEfLcU8UC17IAggmosvp +r2uKGcfLFFb14dq12fy/czja+eevbqQ34gcnAgMBAAGjggMXMIIDEzASBgNVHRMBAf8ECDAGAQH/ +AgEBMDcGA1UdEgQwMC6CD3N1c2NlcnRlLmdvYi52ZaAbBgVghl4CAqASDBBSSUYtRy0yMDAwNDAz +Ni0wMB0GA1UdDgQWBBRBDxk4qpl/Qguk1yeYVKIXTC1RVDCCAVAGA1UdIwSCAUcwggFDgBStuyId +xuDSAaj9dlBSk+2YwU2u06GCASakggEiMIIBHjE+MDwGA1UEAxM1QXV0b3JpZGFkIGRlIENlcnRp +ZmljYWNpb24gUmFpeiBkZWwgRXN0YWRvIFZlbmV6b2xhbm8xCzAJBgNVBAYTAlZFMRAwDgYDVQQH +EwdDYXJhY2FzMRkwFwYDVQQIExBEaXN0cml0byBDYXBpdGFsMTYwNAYDVQQKEy1TaXN0ZW1hIE5h +Y2lvbmFsIGRlIENlcnRpZmljYWNpb24gRWxlY3Ryb25pY2ExQzBBBgNVBAsTOlN1cGVyaW50ZW5k +ZW5jaWEgZGUgU2VydmljaW9zIGRlIENlcnRpZmljYWNpb24gRWxlY3Ryb25pY2ExJTAjBgkqhkiG +9w0BCQEWFmFjcmFpekBzdXNjZXJ0ZS5nb2IudmWCAQowDgYDVR0PAQH/BAQDAgEGME0GA1UdEQRG +MESCDnByb2NlcnQubmV0LnZloBUGBWCGXgIBoAwMClBTQy0wMDAwMDKgGwYFYIZeAgKgEgwQUklG +LUotMzE2MzUzNzMtNzB2BgNVHR8EbzBtMEagRKBChkBodHRwOi8vd3d3LnN1c2NlcnRlLmdvYi52 +ZS9sY3IvQ0VSVElGSUNBRE8tUkFJWi1TSEEzODRDUkxERVIuY3JsMCOgIaAfhh1sZGFwOi8vYWNy +YWl6LnN1c2NlcnRlLmdvYi52ZTA3BggrBgEFBQcBAQQrMCkwJwYIKwYBBQUHMAGGG2h0dHA6Ly9v +Y3NwLnN1c2NlcnRlLmdvYi52ZTBBBgNVHSAEOjA4MDYGBmCGXgMBAjAsMCoGCCsGAQUFBwIBFh5o +dHRwOi8vd3d3LnN1c2NlcnRlLmdvYi52ZS9kcGMwDQYJKoZIhvcNAQELBQADggIBACtZ6yKZu4Sq +T96QxtGGcSOeSwORR3C7wJJg7ODU523G0+1ng3dS1fLld6c2suNUvtm7CpsR72H0xpkzmfWvADmN +g7+mvTV+LFwxNG9s2/NkAZiqlCxB3RWGymspThbASfzXg0gTB1GEMVKIu4YXx2sviiCtxQuPcD4q +uxtxj7mkoP3YldmvWb8lK5jpY5MvYB7Eqvh39YtsL+1+LrVPQA3uvFd359m21D+VJzog1eWuq2w1 +n8GhHVnchIHuTQfiSLaeS5UtQbHh6N5+LwUeaO6/u5BlOsju6rEYNxxik6SgMexxbJHmpHmJWhSn +FFAFTKQAVzAswbVhltw+HoSvOULP5dAssSS830DD7X9jSr3hTxJkhpXzsOfIt+FTvZLm8wyWuevo +5pLtp4EJFAv8lXrPj9Y0TzYS3F7RNHXGRoAvlQSMx4bEqCaJqD8Zm4G7UaRKhqsLEQ+xrmNTbSjq +3TNWOByyrYDT13K9mmyZY+gAu0F2BbdbmRiKw7gSXFbPVgx96OLP7bx0R/vu0xdOIk9W/1DzLuY5 +poLWccret9W6aAjtmcz9opLLabid+Qqkpj5PkygqYWwHJgD/ll9ohri4zspV4KuxPX+Y1zMOWj3Y +eMLEYC/HYvBhkdI4sPaeVdtAgAUSM84dkpvRabP/v/GSCmE1P93+hvS84Bpxs2Km +-----END CERTIFICATE----- + +China Internet Network Information Center EV Certificates Root +============================================================== +-----BEGIN CERTIFICATE----- +MIID9zCCAt+gAwIBAgIESJ8AATANBgkqhkiG9w0BAQUFADCBijELMAkGA1UEBhMCQ04xMjAwBgNV +BAoMKUNoaW5hIEludGVybmV0IE5ldHdvcmsgSW5mb3JtYXRpb24gQ2VudGVyMUcwRQYDVQQDDD5D +aGluYSBJbnRlcm5ldCBOZXR3b3JrIEluZm9ybWF0aW9uIENlbnRlciBFViBDZXJ0aWZpY2F0ZXMg +Um9vdDAeFw0xMDA4MzEwNzExMjVaFw0zMDA4MzEwNzExMjVaMIGKMQswCQYDVQQGEwJDTjEyMDAG +A1UECgwpQ2hpbmEgSW50ZXJuZXQgTmV0d29yayBJbmZvcm1hdGlvbiBDZW50ZXIxRzBFBgNVBAMM +PkNoaW5hIEludGVybmV0IE5ldHdvcmsgSW5mb3JtYXRpb24gQ2VudGVyIEVWIENlcnRpZmljYXRl +cyBSb290MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAm35z7r07eKpkQ0H1UN+U8i6y +jUqORlTSIRLIOTJCBumD1Z9S7eVnAztUwYyZmczpwA//DdmEEbK40ctb3B75aDFk4Zv6dOtouSCV +98YPjUesWgbdYavi7NifFy2cyjw1l1VxzUOFsUcW9SxTgHbP0wBkvUCZ3czY28Sf1hNfQYOL+Q2H +klY0bBoQCxfVWhyXWIQ8hBouXJE0bhlffxdpxWXvayHG1VA6v2G5BY3vbzQ6sm8UY78WO5upKv23 +KzhmBsUs4qpnHkWnjQRmQvaPK++IIGmPMowUc9orhpFjIpryp9vOiYurXccUwVswah+xt54ugQEC +7c+WXmPbqOY4twIDAQABo2MwYTAfBgNVHSMEGDAWgBR8cks5x8DbYqVPm6oYNJKiyoOCWTAPBgNV +HRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUfHJLOcfA22KlT5uqGDSSosqD +glkwDQYJKoZIhvcNAQEFBQADggEBACrDx0M3j92tpLIM7twUbY8opJhJywyA6vPtI2Z1fcXTIWd5 +0XPFtQO3WKwMVC/GVhMPMdoG52U7HW8228gd+f2ABsqjPWYWqJ1MFn3AlUa1UeTiH9fqBk1jjZaM +7+czV0I664zBechNdn3e9rG3geCg+aF4RhcaVpjwTj2rHO3sOdwHSPdj/gauwqRcalsyiMXHM4Ws +ZkJHwlgkmeHlPuV1LI5D1l08eB6olYIpUNHRFrrvwb562bTYzB5MRuF3sTGrvSrIzo9uoV1/A3U0 +5K2JRVRevq4opbs/eHnrc7MKDf2+yfdWrPa37S+bISnHOLaVxATywy39FCqQmbkHzJ8= +-----END CERTIFICATE----- + +Swisscom Root CA 2 +================== +-----BEGIN CERTIFICATE----- +MIIF2TCCA8GgAwIBAgIQHp4o6Ejy5e/DfEoeWhhntjANBgkqhkiG9w0BAQsFADBkMQswCQYDVQQG +EwJjaDERMA8GA1UEChMIU3dpc3Njb20xJTAjBgNVBAsTHERpZ2l0YWwgQ2VydGlmaWNhdGUgU2Vy +dmljZXMxGzAZBgNVBAMTElN3aXNzY29tIFJvb3QgQ0EgMjAeFw0xMTA2MjQwODM4MTRaFw0zMTA2 +MjUwNzM4MTRaMGQxCzAJBgNVBAYTAmNoMREwDwYDVQQKEwhTd2lzc2NvbTElMCMGA1UECxMcRGln +aXRhbCBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczEbMBkGA1UEAxMSU3dpc3Njb20gUm9vdCBDQSAyMIIC +IjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAlUJOhJ1R5tMJ6HJaI2nbeHCOFvErjw0DzpPM +LgAIe6szjPTpQOYXTKueuEcUMncy3SgM3hhLX3af+Dk7/E6J2HzFZ++r0rk0X2s682Q2zsKwzxNo +ysjL67XiPS4h3+os1OD5cJZM/2pYmLcX5BtS5X4HAB1f2uY+lQS3aYg5oUFgJWFLlTloYhyxCwWJ +wDaCFCE/rtuh/bxvHGCGtlOUSbkrRsVPACu/obvLP+DHVxxX6NZp+MEkUp2IVd3Chy50I9AU/SpH +Wrumnf2U5NGKpV+GY3aFy6//SSj8gO1MedK75MDvAe5QQQg1I3ArqRa0jG6F6bYRzzHdUyYb3y1a +SgJA/MTAtukxGggo5WDDH8SQjhBiYEQN7Aq+VRhxLKX0srwVYv8c474d2h5Xszx+zYIdkeNL6yxS +NLCK/RJOlrDrcH+eOfdmQrGrrFLadkBXeyq96G4DsguAhYidDMfCd7Camlf0uPoTXGiTOmekl9Ab +mbeGMktg2M7v0Ax/lZ9vh0+Hio5fCHyqW/xavqGRn1V9TrALacywlKinh/LTSlDcX3KwFnUey7QY +Ypqwpzmqm59m2I2mbJYV4+by+PGDYmy7Velhk6M99bFXi08jsJvllGov34zflVEpYKELKeRcVVi3 +qPyZ7iVNTA6z00yPhOgpD/0QVAKFyPnlw4vP5w8CAwEAAaOBhjCBgzAOBgNVHQ8BAf8EBAMCAYYw +HQYDVR0hBBYwFDASBgdghXQBUwIBBgdghXQBUwIBMBIGA1UdEwEB/wQIMAYBAf8CAQcwHQYDVR0O +BBYEFE0mICKJS9PVpAqhb97iEoHF8TwuMB8GA1UdIwQYMBaAFE0mICKJS9PVpAqhb97iEoHF8Twu +MA0GCSqGSIb3DQEBCwUAA4ICAQAyCrKkG8t9voJXiblqf/P0wS4RfbgZPnm3qKhyN2abGu2sEzsO +v2LwnN+ee6FTSA5BesogpxcbtnjsQJHzQq0Qw1zv/2BZf82Fo4s9SBwlAjxnffUy6S8w5X2lejjQ +82YqZh6NM4OKb3xuqFp1mrjX2lhIREeoTPpMSQpKwhI3qEAMw8jh0FcNlzKVxzqfl9NX+Ave5XLz +o9v/tdhZsnPdTSpxsrpJ9csc1fV5yJmz/MFMdOO0vSk3FQQoHt5FRnDsr7p4DooqzgB53MBfGWcs +a0vvaGgLQ+OswWIJ76bdZWGgr4RVSJFSHMYlkSrQwSIjYVmvRRGFHQEkNI/Ps/8XciATwoCqISxx +OQ7Qj1zB09GOInJGTB2Wrk9xseEFKZZZ9LuedT3PDTcNYtsmjGOpI99nBjx8Oto0QuFmtEYE3saW +mA9LSHokMnWRn6z3aOkquVVlzl1h0ydw2Df+n7mvoC5Wt6NlUe07qxS/TFED6F+KBZvuim6c779o ++sjaC+NCydAXFJy3SuCvkychVSa1ZC+N8f+mQAWFBVzKBxlcCxMoTFh/wqXvRdpg065lYZ1Tg3TC +rvJcwhbtkj6EPnNgiLx29CzP0H1907he0ZESEOnN3col49XtmS++dYFLJPlFRpTJKSFTnCZFqhMX +5OfNeOI5wSsSnqaeG8XmDtkx2Q== +-----END CERTIFICATE----- + +Swisscom Root EV CA 2 +===================== +-----BEGIN CERTIFICATE----- +MIIF4DCCA8igAwIBAgIRAPL6ZOJ0Y9ON/RAdBB92ylgwDQYJKoZIhvcNAQELBQAwZzELMAkGA1UE +BhMCY2gxETAPBgNVBAoTCFN3aXNzY29tMSUwIwYDVQQLExxEaWdpdGFsIENlcnRpZmljYXRlIFNl +cnZpY2VzMR4wHAYDVQQDExVTd2lzc2NvbSBSb290IEVWIENBIDIwHhcNMTEwNjI0MDk0NTA4WhcN +MzEwNjI1MDg0NTA4WjBnMQswCQYDVQQGEwJjaDERMA8GA1UEChMIU3dpc3Njb20xJTAjBgNVBAsT +HERpZ2l0YWwgQ2VydGlmaWNhdGUgU2VydmljZXMxHjAcBgNVBAMTFVN3aXNzY29tIFJvb3QgRVYg +Q0EgMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMT3HS9X6lds93BdY7BxUglgRCgz +o3pOCvrY6myLURYaVa5UJsTMRQdBTxB5f3HSek4/OE6zAMaVylvNwSqD1ycfMQ4jFrclyxy0uYAy +Xhqdk/HoPGAsp15XGVhRXrwsVgu42O+LgrQ8uMIkqBPHoCE2G3pXKSinLr9xJZDzRINpUKTk4Rti +GZQJo/PDvO/0vezbE53PnUgJUmfANykRHvvSEaeFGHR55E+FFOtSN+KxRdjMDUN/rhPSays/p8Li +qG12W0OfvrSdsyaGOx9/5fLoZigWJdBLlzin5M8J0TbDC77aO0RYjb7xnglrPvMyxyuHxuxenPaH +Za0zKcQvidm5y8kDnftslFGXEBuGCxobP/YCfnvUxVFkKJ3106yDgYjTdLRZncHrYTNaRdHLOdAG +alNgHa/2+2m8atwBz735j9m9W8E6X47aD0upm50qKGsaCnw8qyIL5XctcfaCNYGu+HuB5ur+rPQa +m3Rc6I8k9l2dRsQs0h4rIWqDJ2dVSqTjyDKXZpBy2uPUZC5f46Fq9mDU5zXNysRojddxyNMkM3Ox +bPlq4SjbX8Y96L5V5jcb7STZDxmPX2MYWFCBUWVv8p9+agTnNCRxunZLWB4ZvRVgRaoMEkABnRDi +xzgHcgplwLa7JSnaFp6LNYth7eVxV4O1PHGf40+/fh6Bn0GXAgMBAAGjgYYwgYMwDgYDVR0PAQH/ +BAQDAgGGMB0GA1UdIQQWMBQwEgYHYIV0AVMCAgYHYIV0AVMCAjASBgNVHRMBAf8ECDAGAQH/AgED +MB0GA1UdDgQWBBRF2aWBbj2ITY1x0kbBbkUe88SAnTAfBgNVHSMEGDAWgBRF2aWBbj2ITY1x0kbB +bkUe88SAnTANBgkqhkiG9w0BAQsFAAOCAgEAlDpzBp9SSzBc1P6xXCX5145v9Ydkn+0UjrgEjihL +j6p7jjm02Vj2e6E1CqGdivdj5eu9OYLU43otb98TPLr+flaYC/NUn81ETm484T4VvwYmneTwkLbU +wp4wLh/vx3rEUMfqe9pQy3omywC0Wqu1kx+AiYQElY2NfwmTv9SoqORjbdlk5LgpWgi/UOGED1V7 +XwgiG/W9mR4U9s70WBCCswo9GcG/W6uqmdjyMb3lOGbcWAXH7WMaLgqXfIeTK7KK4/HsGOV1timH +59yLGn602MnTihdsfSlEvoqq9X46Lmgxk7lq2prg2+kupYTNHAq4Sgj5nPFhJpiTt3tm7JFe3VE/ +23MPrQRYCd0EApUKPtN236YQHoA96M2kZNEzx5LH4k5E4wnJTsJdhw4Snr8PyQUQ3nqjsTzyP6Wq +J3mtMX0f/fwZacXduT98zca0wjAefm6S139hdlqP65VNvBFuIXxZN5nQBrz5Bm0yFqXZaajh3DyA +HmBR3NdUIR7KYndP+tiPsys6DXhyyWhBWkdKwqPrGtcKqzwyVcgKEZzfdNbwQBUdyLmPtTbFr/gi +uMod89a2GQ+fYWVq6nTIfI/DT11lgh/ZDYnadXL77/FHZxOzyNEZiCcmmpl5fx7kLD977vHeTYuW +l8PVP3wbI+2ksx0WckNLIOFZfsLorSa/ovc= +-----END CERTIFICATE----- + +CA Disig Root R1 +================ +-----BEGIN CERTIFICATE----- +MIIFaTCCA1GgAwIBAgIJAMMDmu5QkG4oMA0GCSqGSIb3DQEBBQUAMFIxCzAJBgNVBAYTAlNLMRMw +EQYDVQQHEwpCcmF0aXNsYXZhMRMwEQYDVQQKEwpEaXNpZyBhLnMuMRkwFwYDVQQDExBDQSBEaXNp +ZyBSb290IFIxMB4XDTEyMDcxOTA5MDY1NloXDTQyMDcxOTA5MDY1NlowUjELMAkGA1UEBhMCU0sx +EzARBgNVBAcTCkJyYXRpc2xhdmExEzARBgNVBAoTCkRpc2lnIGEucy4xGTAXBgNVBAMTEENBIERp +c2lnIFJvb3QgUjEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCqw3j33Jijp1pedxiy +3QRkD2P9m5YJgNXoqqXinCaUOuiZc4yd39ffg/N4T0Dhf9Kn0uXKE5Pn7cZ3Xza1lK/oOI7bm+V8 +u8yN63Vz4STN5qctGS7Y1oprFOsIYgrY3LMATcMjfF9DCCMyEtztDK3AfQ+lekLZWnDZv6fXARz2 +m6uOt0qGeKAeVjGu74IKgEH3G8muqzIm1Cxr7X1r5OJeIgpFy4QxTaz+29FHuvlglzmxZcfe+5nk +CiKxLU3lSCZpq+Kq8/v8kiky6bM+TR8noc2OuRf7JT7JbvN32g0S9l3HuzYQ1VTW8+DiR0jm3hTa +YVKvJrT1cU/J19IG32PK/yHoWQbgCNWEFVP3Q+V8xaCJmGtzxmjOZd69fwX3se72V6FglcXM6pM6 +vpmumwKjrckWtc7dXpl4fho5frLABaTAgqWjR56M6ly2vGfb5ipN0gTco65F97yLnByn1tUD3AjL +LhbKXEAz6GfDLuemROoRRRw1ZS0eRWEkG4IupZ0zXWX4Qfkuy5Q/H6MMMSRE7cderVC6xkGbrPAX +ZcD4XW9boAo0PO7X6oifmPmvTiT6l7Jkdtqr9O3jw2Dv1fkCyC2fg69naQanMVXVz0tv/wQFx1is +XxYb5dKj6zHbHzMVTdDypVP1y+E9Tmgt2BLdqvLmTZtJ5cUoobqwWsagtQIDAQABo0IwQDAPBgNV +HRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUiQq0OJMa5qvum5EY+fU8PjXQ +04IwDQYJKoZIhvcNAQEFBQADggIBADKL9p1Kyb4U5YysOMo6CdQbzoaz3evUuii+Eq5FLAR0rBNR +xVgYZk2C2tXck8An4b58n1KeElb21Zyp9HWc+jcSjxyT7Ff+Bw+r1RL3D65hXlaASfX8MPWbTx9B +LxyE04nH4toCdu0Jz2zBuByDHBb6lM19oMgY0sidbvW9adRtPTXoHqJPYNcHKfyyo6SdbhWSVhlM +CrDpfNIZTUJG7L399ldb3Zh+pE3McgODWF3vkzpBemOqfDqo9ayk0d2iLbYq/J8BjuIQscTK5Gfb +VSUZP/3oNn6z4eGBrxEWi1CXYBmCAMBrTXO40RMHPuq2MU/wQppt4hF05ZSsjYSVPCGvxdpHyN85 +YmLLW1AL14FABZyb7bq2ix4Eb5YgOe2kfSnbSM6C3NQCjR0EMVrHS/BsYVLXtFHCgWzN4funodKS +ds+xDzdYpPJScWc/DIh4gInByLUfkmO+p3qKViwaqKactV2zY9ATIKHrkWzQjX2v3wvkF7mGnjix +lAxYjOBVqjtjbZqJYLhkKpLGN/R+Q0O3c+gB53+XD9fyexn9GtePyfqFa3qdnom2piiZk4hA9z7N +UaPK6u95RyG1/jLix8NRb76AdPCkwzryT+lf3xkK8jsTQ6wxpLPn6/wY1gGp8yqPNg7rtLG8t0zJ +a7+h89n07eLw4+1knj0vllJPgFOL +-----END CERTIFICATE----- + +CA Disig Root R2 +================ +-----BEGIN CERTIFICATE----- +MIIFaTCCA1GgAwIBAgIJAJK4iNuwisFjMA0GCSqGSIb3DQEBCwUAMFIxCzAJBgNVBAYTAlNLMRMw +EQYDVQQHEwpCcmF0aXNsYXZhMRMwEQYDVQQKEwpEaXNpZyBhLnMuMRkwFwYDVQQDExBDQSBEaXNp +ZyBSb290IFIyMB4XDTEyMDcxOTA5MTUzMFoXDTQyMDcxOTA5MTUzMFowUjELMAkGA1UEBhMCU0sx +EzARBgNVBAcTCkJyYXRpc2xhdmExEzARBgNVBAoTCkRpc2lnIGEucy4xGTAXBgNVBAMTEENBIERp +c2lnIFJvb3QgUjIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCio8QACdaFXS1tFPbC +w3OeNcJxVX6B+6tGUODBfEl45qt5WDza/3wcn9iXAng+a0EE6UG9vgMsRfYvZNSrXaNHPWSb6Wia +xswbP7q+sos0Ai6YVRn8jG+qX9pMzk0DIaPY0jSTVpbLTAwAFjxfGs3Ix2ymrdMxp7zo5eFm1tL7 +A7RBZckQrg4FY8aAamkw/dLukO8NJ9+flXP04SXabBbeQTg06ov80egEFGEtQX6sx3dOy1FU+16S +GBsEWmjGycT6txOgmLcRK7fWV8x8nhfRyyX+hk4kLlYMeE2eARKmK6cBZW58Yh2EhN/qwGu1pSqV +g8NTEQxzHQuyRpDRQjrOQG6Vrf/GlK1ul4SOfW+eioANSW1z4nuSHsPzwfPrLgVv2RvPN3YEyLRa +5Beny912H9AZdugsBbPWnDTYltxhh5EF5EQIM8HauQhl1K6yNg3ruji6DOWbnuuNZt2Zz9aJQfYE +koopKW1rOhzndX0CcQ7zwOe9yxndnWCywmZgtrEE7snmhrmaZkCo5xHtgUUDi/ZnWejBBhG93c+A +Ak9lQHhcR1DIm+YfgXvkRKhbhZri3lrVx/k6RGZL5DJUfORsnLMOPReisjQS1n6yqEm70XooQL6i +Fh/f5DcfEXP7kAplQ6INfPgGAVUzfbANuPT1rqVCV3w2EYx7XsQDnYx5nQIDAQABo0IwQDAPBgNV +HRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUtZn4r7CU9eMg1gqtzk5WpC5u +Qu0wDQYJKoZIhvcNAQELBQADggIBACYGXnDnZTPIgm7ZnBc6G3pmsgH2eDtpXi/q/075KMOYKmFM +tCQSin1tERT3nLXK5ryeJ45MGcipvXrA1zYObYVybqjGom32+nNjf7xueQgcnYqfGopTpti72TVV +sRHFqQOzVju5hJMiXn7B9hJSi+osZ7z+Nkz1uM/Rs0mSO9MpDpkblvdhuDvEK7Z4bLQjb/D907Je +dR+Zlais9trhxTF7+9FGs9K8Z7RiVLoJ92Owk6Ka+elSLotgEqv89WBW7xBci8QaQtyDW2QOy7W8 +1k/BfDxujRNt+3vrMNDcTa/F1balTFtxyegxvug4BkihGuLq0t4SOVga/4AOgnXmt8kHbA7v/zjx +mHHEt38OFdAlab0inSvtBfZGR6ztwPDUO+Ls7pZbkBNOHlY667DvlruWIxG68kOGdGSVyCh13x01 +utI3gzhTODY7z2zp+WsO0PsE6E9312UBeIYMej4hYvF/Y3EMyZ9E26gnonW+boE+18DrG5gPcFw0 +sorMwIUY6256s/daoQe/qUKS82Ail+QUoQebTnbAjn39pCXHR+3/H3OszMOl6W8KjptlwlCFtaOg +UxLMVYdh84GuEEZhvUQhuMI9dM9+JDX6HAcOmz0iyu8xL4ysEr3vQCj8KWefshNPZiTEUxnpHikV +7+ZtsH8tZ/3zbBt1RqPlShfppNcL +-----END CERTIFICATE----- + +ACCVRAIZ1 +========= +-----BEGIN CERTIFICATE----- +MIIH0zCCBbugAwIBAgIIXsO3pkN/pOAwDQYJKoZIhvcNAQEFBQAwQjESMBAGA1UEAwwJQUNDVlJB +SVoxMRAwDgYDVQQLDAdQS0lBQ0NWMQ0wCwYDVQQKDARBQ0NWMQswCQYDVQQGEwJFUzAeFw0xMTA1 +MDUwOTM3MzdaFw0zMDEyMzEwOTM3MzdaMEIxEjAQBgNVBAMMCUFDQ1ZSQUlaMTEQMA4GA1UECwwH +UEtJQUNDVjENMAsGA1UECgwEQUNDVjELMAkGA1UEBhMCRVMwggIiMA0GCSqGSIb3DQEBAQUAA4IC +DwAwggIKAoICAQCbqau/YUqXry+XZpp0X9DZlv3P4uRm7x8fRzPCRKPfmt4ftVTdFXxpNRFvu8gM +jmoYHtiP2Ra8EEg2XPBjs5BaXCQ316PWywlxufEBcoSwfdtNgM3802/J+Nq2DoLSRYWoG2ioPej0 +RGy9ocLLA76MPhMAhN9KSMDjIgro6TenGEyxCQ0jVn8ETdkXhBilyNpAlHPrzg5XPAOBOp0KoVdD +aaxXbXmQeOW1tDvYvEyNKKGno6e6Ak4l0Squ7a4DIrhrIA8wKFSVf+DuzgpmndFALW4ir50awQUZ +0m/A8p/4e7MCQvtQqR0tkw8jq8bBD5L/0KIV9VMJcRz/RROE5iZe+OCIHAr8Fraocwa48GOEAqDG +WuzndN9wrqODJerWx5eHk6fGioozl2A3ED6XPm4pFdahD9GILBKfb6qkxkLrQaLjlUPTAYVtjrs7 +8yM2x/474KElB0iryYl0/wiPgL/AlmXz7uxLaL2diMMxs0Dx6M/2OLuc5NF/1OVYm3z61PMOm3WR +5LpSLhl+0fXNWhn8ugb2+1KoS5kE3fj5tItQo05iifCHJPqDQsGH+tUtKSpacXpkatcnYGMN285J +9Y0fkIkyF/hzQ7jSWpOGYdbhdQrqeWZ2iE9x6wQl1gpaepPluUsXQA+xtrn13k/c4LOsOxFwYIRK +Q26ZIMApcQrAZQIDAQABo4ICyzCCAscwfQYIKwYBBQUHAQEEcTBvMEwGCCsGAQUFBzAChkBodHRw +Oi8vd3d3LmFjY3YuZXMvZmlsZWFkbWluL0FyY2hpdm9zL2NlcnRpZmljYWRvcy9yYWl6YWNjdjEu +Y3J0MB8GCCsGAQUFBzABhhNodHRwOi8vb2NzcC5hY2N2LmVzMB0GA1UdDgQWBBTSh7Tj3zcnk1X2 +VuqB5TbMjB4/vTAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFNKHtOPfNyeTVfZW6oHlNsyM +Hj+9MIIBcwYDVR0gBIIBajCCAWYwggFiBgRVHSAAMIIBWDCCASIGCCsGAQUFBwICMIIBFB6CARAA +QQB1AHQAbwByAGkAZABhAGQAIABkAGUAIABDAGUAcgB0AGkAZgBpAGMAYQBjAGkA8wBuACAAUgBh +AO0AegAgAGQAZQAgAGwAYQAgAEEAQwBDAFYAIAAoAEEAZwBlAG4AYwBpAGEAIABkAGUAIABUAGUA +YwBuAG8AbABvAGcA7QBhACAAeQAgAEMAZQByAHQAaQBmAGkAYwBhAGMAaQDzAG4AIABFAGwAZQBj +AHQAcgDzAG4AaQBjAGEALAAgAEMASQBGACAAUQA0ADYAMAAxADEANQA2AEUAKQAuACAAQwBQAFMA +IABlAG4AIABoAHQAdABwADoALwAvAHcAdwB3AC4AYQBjAGMAdgAuAGUAczAwBggrBgEFBQcCARYk +aHR0cDovL3d3dy5hY2N2LmVzL2xlZ2lzbGFjaW9uX2MuaHRtMFUGA1UdHwROMEwwSqBIoEaGRGh0 +dHA6Ly93d3cuYWNjdi5lcy9maWxlYWRtaW4vQXJjaGl2b3MvY2VydGlmaWNhZG9zL3JhaXphY2N2 +MV9kZXIuY3JsMA4GA1UdDwEB/wQEAwIBBjAXBgNVHREEEDAOgQxhY2N2QGFjY3YuZXMwDQYJKoZI +hvcNAQEFBQADggIBAJcxAp/n/UNnSEQU5CmH7UwoZtCPNdpNYbdKl02125DgBS4OxnnQ8pdpD70E +R9m+27Up2pvZrqmZ1dM8MJP1jaGo/AaNRPTKFpV8M9xii6g3+CfYCS0b78gUJyCpZET/LtZ1qmxN +YEAZSUNUY9rizLpm5U9EelvZaoErQNV/+QEnWCzI7UiRfD+mAM/EKXMRNt6GGT6d7hmKG9Ww7Y49 +nCrADdg9ZuM8Db3VlFzi4qc1GwQA9j9ajepDvV+JHanBsMyZ4k0ACtrJJ1vnE5Bc5PUzolVt3OAJ +TS+xJlsndQAJxGJ3KQhfnlmstn6tn1QwIgPBHnFk/vk4CpYY3QIUrCPLBhwepH2NDd4nQeit2hW3 +sCPdK6jT2iWH7ehVRE2I9DZ+hJp4rPcOVkkO1jMl1oRQQmwgEh0q1b688nCBpHBgvgW1m54ERL5h +I6zppSSMEYCUWqKiuUnSwdzRp+0xESyeGabu4VXhwOrPDYTkF7eifKXeVSUG7szAh1xA2syVP1Xg +Nce4hL60Xc16gwFy7ofmXx2utYXGJt/mwZrpHgJHnyqobalbz+xFd3+YJ5oyXSrjhO7FmGYvliAd +3djDJ9ew+f7Zfc3Qn48LFFhRny+Lwzgt3uiP1o2HpPVWQxaZLPSkVrQ0uGE3ycJYgBugl6H8WY3p +EfbRD0tVNEYqi4Y7 +-----END CERTIFICATE----- + +TWCA Global Root CA +=================== +-----BEGIN CERTIFICATE----- +MIIFQTCCAymgAwIBAgICDL4wDQYJKoZIhvcNAQELBQAwUTELMAkGA1UEBhMCVFcxEjAQBgNVBAoT +CVRBSVdBTi1DQTEQMA4GA1UECxMHUm9vdCBDQTEcMBoGA1UEAxMTVFdDQSBHbG9iYWwgUm9vdCBD +QTAeFw0xMjA2MjcwNjI4MzNaFw0zMDEyMzExNTU5NTlaMFExCzAJBgNVBAYTAlRXMRIwEAYDVQQK +EwlUQUlXQU4tQ0ExEDAOBgNVBAsTB1Jvb3QgQ0ExHDAaBgNVBAMTE1RXQ0EgR2xvYmFsIFJvb3Qg +Q0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCwBdvI64zEbooh745NnHEKH1Jw7W2C +nJfF10xORUnLQEK1EjRsGcJ0pDFfhQKX7EMzClPSnIyOt7h52yvVavKOZsTuKwEHktSz0ALfUPZV +r2YOy+BHYC8rMjk1Ujoog/h7FsYYuGLWRyWRzvAZEk2tY/XTP3VfKfChMBwqoJimFb3u/Rk28OKR +Q4/6ytYQJ0lM793B8YVwm8rqqFpD/G2Gb3PpN0Wp8DbHzIh1HrtsBv+baz4X7GGqcXzGHaL3SekV +tTzWoWH1EfcFbx39Eb7QMAfCKbAJTibc46KokWofwpFFiFzlmLhxpRUZyXx1EcxwdE8tmx2RRP1W +KKD+u4ZqyPpcC1jcxkt2yKsi2XMPpfRaAok/T54igu6idFMqPVMnaR1sjjIsZAAmY2E2TqNGtz99 +sy2sbZCilaLOz9qC5wc0GZbpuCGqKX6mOL6OKUohZnkfs8O1CWfe1tQHRvMq2uYiN2DLgbYPoA/p +yJV/v1WRBXrPPRXAb94JlAGD1zQbzECl8LibZ9WYkTunhHiVJqRaCPgrdLQABDzfuBSO6N+pjWxn +kjMdwLfS7JLIvgm/LCkFbwJrnu+8vyq8W8BQj0FwcYeyTbcEqYSjMq+u7msXi7Kx/mzhkIyIqJdI +zshNy/MGz19qCkKxHh53L46g5pIOBvwFItIm4TFRfTLcDwIDAQABoyMwITAOBgNVHQ8BAf8EBAMC +AQYwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEAXzSBdu+WHdXltdkCY4QWwa6g +cFGn90xHNcgL1yg9iXHZqjNB6hQbbCEAwGxCGX6faVsgQt+i0trEfJdLjbDorMjupWkEmQqSpqsn +LhpNgb+E1HAerUf+/UqdM+DyucRFCCEK2mlpc3INvjT+lIutwx4116KD7+U4x6WFH6vPNOw/KP4M +8VeGTslV9xzU2KV9Bnpv1d8Q34FOIWWxtuEXeZVFBs5fzNxGiWNoRI2T9GRwoD2dKAXDOXC4Ynsg +/eTb6QihuJ49CcdP+yz4k3ZB3lLg4VfSnQO8d57+nile98FRYB/e2guyLXW3Q0iT5/Z5xoRdgFlg +lPx4mI88k1HtQJAH32RjJMtOcQWh15QaiDLxInQirqWm2BJpTGCjAu4r7NRjkgtevi92a6O2JryP +A9gK8kxkRr05YuWW6zRjESjMlfGt7+/cgFhI6Uu46mWs6fyAtbXIRfmswZ/ZuepiiI7E8UuDEq3m +i4TWnsLrgxifarsbJGAzcMzs9zLzXNl5fe+epP7JI8Mk7hWSsT2RTyaGvWZzJBPqpK5jwa19hAM8 +EHiGG3njxPPyBJUgriOCxLM6AGK/5jYk4Ve6xx6QddVfP5VhK8E7zeWzaGHQRiapIVJpLesux+t3 +zqY6tQMzT3bR51xUAV3LePTJDL/PEo4XLSNolOer/qmyKwbQBM0= +-----END CERTIFICATE----- + +TeliaSonera Root CA v1 +====================== +-----BEGIN CERTIFICATE----- +MIIFODCCAyCgAwIBAgIRAJW+FqD3LkbxezmCcvqLzZYwDQYJKoZIhvcNAQEFBQAwNzEUMBIGA1UE +CgwLVGVsaWFTb25lcmExHzAdBgNVBAMMFlRlbGlhU29uZXJhIFJvb3QgQ0EgdjEwHhcNMDcxMDE4 +MTIwMDUwWhcNMzIxMDE4MTIwMDUwWjA3MRQwEgYDVQQKDAtUZWxpYVNvbmVyYTEfMB0GA1UEAwwW +VGVsaWFTb25lcmEgUm9vdCBDQSB2MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMK+ +6yfwIaPzaSZVfp3FVRaRXP3vIb9TgHot0pGMYzHw7CTww6XScnwQbfQ3t+XmfHnqjLWCi65ItqwA +3GV17CpNX8GH9SBlK4GoRz6JI5UwFpB/6FcHSOcZrr9FZ7E3GwYq/t75rH2D+1665I+XZ75Ljo1k +B1c4VWk0Nj0TSO9P4tNmHqTPGrdeNjPUtAa9GAH9d4RQAEX1jF3oI7x+/jXh7VB7qTCNGdMJjmhn +Xb88lxhTuylixcpecsHHltTbLaC0H2kD7OriUPEMPPCs81Mt8Bz17Ww5OXOAFshSsCPN4D7c3TxH +oLs1iuKYaIu+5b9y7tL6pe0S7fyYGKkmdtwoSxAgHNN/Fnct7W+A90m7UwW7XWjH1Mh1Fj+JWov3 +F0fUTPHSiXk+TT2YqGHeOh7S+F4D4MHJHIzTjU3TlTazN19jY5szFPAtJmtTfImMMsJu7D0hADnJ +oWjiUIMusDor8zagrC/kb2HCUQk5PotTubtn2txTuXZZNp1D5SDgPTJghSJRt8czu90VL6R4pgd7 +gUY2BIbdeTXHlSw7sKMXNeVzH7RcWe/a6hBle3rQf5+ztCo3O3CLm1u5K7fsslESl1MpWtTwEhDc +TwK7EpIvYtQ/aUN8Ddb8WHUBiJ1YFkveupD/RwGJBmr2X7KQarMCpgKIv7NHfirZ1fpoeDVNAgMB +AAGjPzA9MA8GA1UdEwEB/wQFMAMBAf8wCwYDVR0PBAQDAgEGMB0GA1UdDgQWBBTwj1k4ALP1j5qW +DNXr+nuqF+gTEjANBgkqhkiG9w0BAQUFAAOCAgEAvuRcYk4k9AwI//DTDGjkk0kiP0Qnb7tt3oNm +zqjMDfz1mgbldxSR651Be5kqhOX//CHBXfDkH1e3damhXwIm/9fH907eT/j3HEbAek9ALCI18Bmx +0GtnLLCo4MBANzX2hFxc469CeP6nyQ1Q6g2EdvZR74NTxnr/DlZJLo961gzmJ1TjTQpgcmLNkQfW +pb/ImWvtxBnmq0wROMVvMeJuScg/doAmAyYp4Db29iBT4xdwNBedY2gea+zDTYa4EzAvXUYNR0PV +G6pZDrlcjQZIrXSHX8f8MVRBE+LHIQ6e4B4N4cB7Q4WQxYpYxmUKeFfyxiMPAdkgS94P+5KFdSpc +c41teyWRyu5FrgZLAMzTsVlQ2jqIOylDRl6XK1TOU2+NSueW+r9xDkKLfP0ooNBIytrEgUy7onOT +JsjrDNYmiLbAJM+7vVvrdX3pCI6GMyx5dwlppYn8s3CQh3aP0yK7Qs69cwsgJirQmz1wHiRszYd2 +qReWt88NkvuOGKmYSdGe/mBEciG5Ge3C9THxOUiIkCR1VBatzvT4aRRkOfujuLpwQMcnHL/EVlP6 +Y2XQ8xwOFvVrhlhNGNTkDY6lnVuR3HYkUD/GKvvZt5y11ubQ2egZixVxSK236thZiNSQvxaz2ems +WWFUyBy6ysHK4bkgTI86k4mloMy/0/Z1pHWWbVY= +-----END CERTIFICATE----- + +E-Tugra Certification Authority +=============================== +-----BEGIN CERTIFICATE----- +MIIGSzCCBDOgAwIBAgIIamg+nFGby1MwDQYJKoZIhvcNAQELBQAwgbIxCzAJBgNVBAYTAlRSMQ8w +DQYDVQQHDAZBbmthcmExQDA+BgNVBAoMN0UtVHXEn3JhIEVCRyBCaWxpxZ9pbSBUZWtub2xvamls +ZXJpIHZlIEhpem1ldGxlcmkgQS7Fni4xJjAkBgNVBAsMHUUtVHVncmEgU2VydGlmaWthc3lvbiBN +ZXJrZXppMSgwJgYDVQQDDB9FLVR1Z3JhIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTEzMDMw +NTEyMDk0OFoXDTIzMDMwMzEyMDk0OFowgbIxCzAJBgNVBAYTAlRSMQ8wDQYDVQQHDAZBbmthcmEx +QDA+BgNVBAoMN0UtVHXEn3JhIEVCRyBCaWxpxZ9pbSBUZWtub2xvamlsZXJpIHZlIEhpem1ldGxl +cmkgQS7Fni4xJjAkBgNVBAsMHUUtVHVncmEgU2VydGlmaWthc3lvbiBNZXJrZXppMSgwJgYDVQQD +DB9FLVR1Z3JhIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIICIjANBgkqhkiG9w0BAQEFAAOCAg8A +MIICCgKCAgEA4vU/kwVRHoViVF56C/UYB4Oufq9899SKa6VjQzm5S/fDxmSJPZQuVIBSOTkHS0vd +hQd2h8y/L5VMzH2nPbxHD5hw+IyFHnSOkm0bQNGZDbt1bsipa5rAhDGvykPL6ys06I+XawGb1Q5K +CKpbknSFQ9OArqGIW66z6l7LFpp3RMih9lRozt6Plyu6W0ACDGQXwLWTzeHxE2bODHnv0ZEoq1+g +ElIwcxmOj+GMB6LDu0rw6h8VqO4lzKRG+Bsi77MOQ7osJLjFLFzUHPhdZL3Dk14opz8n8Y4e0ypQ +BaNV2cvnOVPAmJ6MVGKLJrD3fY185MaeZkJVgkfnsliNZvcHfC425lAcP9tDJMW/hkd5s3kc91r0 +E+xs+D/iWR+V7kI+ua2oMoVJl0b+SzGPWsutdEcf6ZG33ygEIqDUD13ieU/qbIWGvaimzuT6w+Gz +rt48Ue7LE3wBf4QOXVGUnhMMti6lTPk5cDZvlsouDERVxcr6XQKj39ZkjFqzAQqptQpHF//vkUAq +jqFGOjGY5RH8zLtJVor8udBhmm9lbObDyz51Sf6Pp+KJxWfXnUYTTjF2OySznhFlhqt/7x3U+Lzn +rFpct1pHXFXOVbQicVtbC/DP3KBhZOqp12gKY6fgDT+gr9Oq0n7vUaDmUStVkhUXU8u3Zg5mTPj5 +dUyQ5xJwx0UCAwEAAaNjMGEwHQYDVR0OBBYEFC7j27JJ0JxUeVz6Jyr+zE7S6E5UMA8GA1UdEwEB +/wQFMAMBAf8wHwYDVR0jBBgwFoAULuPbsknQnFR5XPonKv7MTtLoTlQwDgYDVR0PAQH/BAQDAgEG +MA0GCSqGSIb3DQEBCwUAA4ICAQAFNzr0TbdF4kV1JI+2d1LoHNgQk2Xz8lkGpD4eKexd0dCrfOAK +kEh47U6YA5n+KGCRHTAduGN8qOY1tfrTYXbm1gdLymmasoR6d5NFFxWfJNCYExL/u6Au/U5Mh/jO +XKqYGwXgAEZKgoClM4so3O0409/lPun++1ndYYRP0lSWE2ETPo+Aab6TR7U1Q9Jauz1c77NCR807 +VRMGsAnb/WP2OogKmW9+4c4bU2pEZiNRCHu8W1Ki/QY3OEBhj0qWuJA3+GbHeJAAFS6LrVE1Uweo +a2iu+U48BybNCAVwzDk/dr2l02cmAYamU9JgO3xDf1WKvJUawSg5TB9D0pH0clmKuVb8P7Sd2nCc +dlqMQ1DujjByTd//SffGqWfZbawCEeI6FiWnWAjLb1NBnEg4R2gz0dfHj9R0IdTDBZB6/86WiLEV +KV0jq9BgoRJP3vQXzTLlyb/IQ639Lo7xr+L0mPoSHyDYwKcMhcWQ9DstliaxLL5Mq+ux0orJ23gT +Dx4JnW2PAJ8C2sH6H3p6CcRK5ogql5+Ji/03X186zjhZhkuvcQu02PJwT58yE+Owp1fl2tpDy4Q0 +8ijE6m30Ku/Ba3ba+367hTzSU8JNvnHhRdH9I2cNE3X7z2VnIp2usAnRCf8dNL/+I5c30jn6PQ0G +C7TbO6Orb1wdtn7os4I07QZcJA== +-----END CERTIFICATE----- + +T-TeleSec GlobalRoot Class 2 +============================ +-----BEGIN CERTIFICATE----- +MIIDwzCCAqugAwIBAgIBATANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoM +IlQtU3lzdGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBU +cnVzdCBDZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDIwHhcNMDgx +MDAxMTA0MDE0WhcNMzMxMDAxMjM1OTU5WjCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoMIlQtU3lz +dGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBD +ZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDIwggEiMA0GCSqGSIb3 +DQEBAQUAA4IBDwAwggEKAoIBAQCqX9obX+hzkeXaXPSi5kfl82hVYAUdAqSzm1nzHoqvNK38DcLZ +SBnuaY/JIPwhqgcZ7bBcrGXHX+0CfHt8LRvWurmAwhiCFoT6ZrAIxlQjgeTNuUk/9k9uN0goOA/F +vudocP05l03Sx5iRUKrERLMjfTlH6VJi1hKTXrcxlkIF+3anHqP1wvzpesVsqXFP6st4vGCvx970 +2cu+fjOlbpSD8DT6IavqjnKgP6TeMFvvhk1qlVtDRKgQFRzlAVfFmPHmBiiRqiDFt1MmUUOyCxGV +WOHAD3bZwI18gfNycJ5v/hqO2V81xrJvNHy+SE/iWjnX2J14np+GPgNeGYtEotXHAgMBAAGjQjBA +MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBS/WSA2AHmgoCJrjNXy +YdK4LMuCSjANBgkqhkiG9w0BAQsFAAOCAQEAMQOiYQsfdOhyNsZt+U2e+iKo4YFWz827n+qrkRk4 +r6p8FU3ztqONpfSO9kSpp+ghla0+AGIWiPACuvxhI+YzmzB6azZie60EI4RYZeLbK4rnJVM3YlNf +vNoBYimipidx5joifsFvHZVwIEoHNN/q/xWA5brXethbdXwFeilHfkCoMRN3zUA7tFFHei4R40cR +3p1m0IvVVGb6g1XqfMIpiRvpb7PO4gWEyS8+eIVibslfwXhjdFjASBgMmTnrpMwatXlajRWc2BQN +9noHV8cigwUtPJslJj0Ys6lDfMjIq2SPDqO/nBudMNva0Bkuqjzx+zOAduTNrRlPBSeOE6Fuwg== +-----END CERTIFICATE----- + +Atos TrustedRoot 2011 +===================== +-----BEGIN CERTIFICATE----- +MIIDdzCCAl+gAwIBAgIIXDPLYixfszIwDQYJKoZIhvcNAQELBQAwPDEeMBwGA1UEAwwVQXRvcyBU +cnVzdGVkUm9vdCAyMDExMQ0wCwYDVQQKDARBdG9zMQswCQYDVQQGEwJERTAeFw0xMTA3MDcxNDU4 +MzBaFw0zMDEyMzEyMzU5NTlaMDwxHjAcBgNVBAMMFUF0b3MgVHJ1c3RlZFJvb3QgMjAxMTENMAsG +A1UECgwEQXRvczELMAkGA1UEBhMCREUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCV +hTuXbyo7LjvPpvMpNb7PGKw+qtn4TaA+Gke5vJrf8v7MPkfoepbCJI419KkM/IL9bcFyYie96mvr +54rMVD6QUM+A1JX76LWC1BTFtqlVJVfbsVD2sGBkWXppzwO3bw2+yj5vdHLqqjAqc2K+SZFhyBH+ +DgMq92og3AIVDV4VavzjgsG1xZ1kCWyjWZgHJ8cblithdHFsQ/H3NYkQ4J7sVaE3IqKHBAUsR320 +HLliKWYoyrfhk/WklAOZuXCFteZI6o1Q/NnezG8HDt0Lcp2AMBYHlT8oDv3FdU9T1nSatCQujgKR +z3bFmx5VdJx4IbHwLfELn8LVlhgf8FQieowHAgMBAAGjfTB7MB0GA1UdDgQWBBSnpQaxLKYJYO7R +l+lwrrw7GWzbITAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFKelBrEspglg7tGX6XCuvDsZ +bNshMBgGA1UdIAQRMA8wDQYLKwYBBAGwLQMEAQEwDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEB +CwUAA4IBAQAmdzTblEiGKkGdLD4GkGDEjKwLVLgfuXvTBznk+j57sj1O7Z8jvZfza1zv7v1Apt+h +k6EKhqzvINB5Ab149xnYJDE0BAGmuhWawyfc2E8PzBhj/5kPDpFrdRbhIfzYJsdHt6bPWHJxfrrh +TZVHO8mvbaG0weyJ9rQPOLXiZNwlz6bb65pcmaHFCN795trV1lpFDMS3wrUU77QR/w4VtfX128a9 +61qn8FYiqTxlVMYVqL2Gns2Dlmh6cYGJ4Qvh6hEbaAjMaZ7snkGeRDImeuKHCnE96+RapNLbxc3G +3mB/ufNPRJLvKrcYPqcZ2Qt9sTdBQrC6YB3y/gkRsPCHe6ed +-----END CERTIFICATE----- + +COMODO RSA Certification Authority +================================== +-----BEGIN CERTIFICATE----- +MIIF2DCCA8CgAwIBAgIQTKr5yttjb+Af907YWwOGnTANBgkqhkiG9w0BAQwFADCB +hTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G +A1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNV +BAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAwMTE5 +MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBhTELMAkGA1UEBhMCR0IxGzAZBgNVBAgT +EkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMR +Q09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNh +dGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCR +6FSS0gpWsawNJN3Fz0RndJkrN6N9I3AAcbxT38T6KhKPS38QVr2fcHK3YX/JSw8X +pz3jsARh7v8Rl8f0hj4K+j5c+ZPmNHrZFGvnnLOFoIJ6dq9xkNfs/Q36nGz637CC +9BR++b7Epi9Pf5l/tfxnQ3K9DADWietrLNPtj5gcFKt+5eNu/Nio5JIk2kNrYrhV +/erBvGy2i/MOjZrkm2xpmfh4SDBF1a3hDTxFYPwyllEnvGfDyi62a+pGx8cgoLEf +Zd5ICLqkTqnyg0Y3hOvozIFIQ2dOciqbXL1MGyiKXCJ7tKuY2e7gUYPDCUZObT6Z ++pUX2nwzV0E8jVHtC7ZcryxjGt9XyD+86V3Em69FmeKjWiS0uqlWPc9vqv9JWL7w +qP/0uK3pN/u6uPQLOvnoQ0IeidiEyxPx2bvhiWC4jChWrBQdnArncevPDt09qZah +SL0896+1DSJMwBGB7FY79tOi4lu3sgQiUpWAk2nojkxl8ZEDLXB0AuqLZxUpaVIC +u9ffUGpVRr+goyhhf3DQw6KqLCGqR84onAZFdr+CGCe01a60y1Dma/RMhnEw6abf +Fobg2P9A3fvQQoh/ozM6LlweQRGBY84YcWsr7KaKtzFcOmpH4MN5WdYgGq/yapiq +crxXStJLnbsQ/LBMQeXtHT1eKJ2czL+zUdqnR+WEUwIDAQABo0IwQDAdBgNVHQ4E +FgQUu69+Aj36pvE8hI6t7jiY7NkyMtQwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB +/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAArx1UaEt65Ru2yyTUEUAJNMnMvl +wFTPoCWOAvn9sKIN9SCYPBMtrFaisNZ+EZLpLrqeLppysb0ZRGxhNaKatBYSaVqM +4dc+pBroLwP0rmEdEBsqpIt6xf4FpuHA1sj+nq6PK7o9mfjYcwlYRm6mnPTXJ9OV +2jeDchzTc+CiR5kDOF3VSXkAKRzH7JsgHAckaVd4sjn8OoSgtZx8jb8uk2Intzna +FxiuvTwJaP+EmzzV1gsD41eeFPfR60/IvYcjt7ZJQ3mFXLrrkguhxuhoqEwWsRqZ +CuhTLJK7oQkYdQxlqHvLI7cawiiFwxv/0Cti76R7CZGYZ4wUAc1oBmpjIXUDgIiK +boHGhfKppC3n9KUkEEeDys30jXlYsQab5xoq2Z0B15R97QNKyvDb6KkBPvVWmcke +jkk9u+UJueBPSZI9FoJAzMxZxuY67RIuaTxslbH9qh17f4a+Hg4yRvv7E491f0yL +S0Zj/gA0QHDBw7mh3aZw4gSzQbzpgJHqZJx64SIDqZxubw5lT2yHh17zbqD5daWb +QOhTsiedSrnAdyGN/4fy3ryM7xfft0kL0fJuMAsaDk527RH89elWsn2/x20Kk4yl +0MC2Hb46TpSi125sC8KKfPog88Tk5c0NqMuRkrF8hey1FGlmDoLnzc7ILaZRfyHB +NVOFBkpdn627G190 +-----END CERTIFICATE----- diff --git a/lib/support/gateway_support.rb b/lib/support/gateway_support.rb index ef649e59adf..b3bb28e54cf 100644 --- a/lib/support/gateway_support.rb +++ b/lib/support/gateway_support.rb @@ -2,7 +2,6 @@ require 'active_support' require 'active_merchant' - class GatewaySupport #:nodoc: ACTIONS = [:purchase, :authorize, :capture, :void, :credit, :recurring] @@ -15,9 +14,9 @@ def initialize filename = File.basename(f, '.rb') gateway_name = filename + '_gateway' begin - gateway_class = ('ActiveMerchant::Billing::' + gateway_name.camelize).constantize + ('ActiveMerchant::Billing::' + gateway_name.camelize).constantize rescue NameError - puts "Could not load gateway " + gateway_name.camelize + " from " + f + "." + puts 'Could not load gateway ' + gateway_name.camelize + ' from ' + f + '.' end end @gateways = Gateway.implementations.sort_by(&:name) @@ -25,20 +24,20 @@ def initialize end def each_gateway - @gateways.each{|g| yield g } + @gateways.each { |g| yield g } end def features width = 15 - print "Name".center(width + 20) - ACTIONS.each{|f| print "#{f.to_s.capitalize.center(width)}" } + print 'Name'.center(width + 20) + ACTIONS.each { |f| print f.to_s.capitalize.center(width) } puts each_gateway do |g| - print "#{g.display_name.ljust(width + 20)}" + print g.display_name.ljust(width + 20) ACTIONS.each do |f| - print "#{(g.instance_methods.include?(f.to_s) ? "Y" : "N").center(width)}" + print((g.instance_methods.include?(f.to_s) ? 'Y' : 'N').center(width)) end puts end @@ -68,4 +67,3 @@ def to_s end end end - diff --git a/lib/support/outbound_hosts.rb b/lib/support/outbound_hosts.rb index bac790662b6..844df856bb3 100644 --- a/lib/support/outbound_hosts.rb +++ b/lib/support/outbound_hosts.rb @@ -2,24 +2,27 @@ require 'set' class OutboundHosts - def self.list - uris = Set.new + def self.list + hosts = Set.new + invalid_lines = Set.new Dir['lib/**/*.rb'].each do |file| content = File.read(file) content.each_line do |line| next if line =~ /homepage_url/ - - if line =~ /("|')(https:\/\/.*)("|')/ - uri = URI.parse($2) - uris << [uri.host, uri.port] + + if line =~ /("|')(https:\/\/[^'"]*)("|')/ + begin + uri = URI.parse($2) + hosts << "#{uri.host}:#{uri.port}" + rescue URI::InvalidURIError + invalid_lines << line + end end end end - uris.each do |uri| - puts "#{uri.first} #{uri.last}" - end + [hosts, invalid_lines] end -end \ No newline at end of file +end diff --git a/lib/support/ssl_verify.rb b/lib/support/ssl_verify.rb index 1ba28878a72..5570e7fde47 100644 --- a/lib/support/ssl_verify.rb +++ b/lib/support/ssl_verify.rb @@ -23,16 +23,16 @@ def test_gateways end uri = URI.parse(g.live_url) - result,message = ssl_verify_peer?(uri) + result, message = ssl_verify_peer?(uri) case result when :success - print "." + print '.' success << g when :fail - print "F" + print 'F' failed << {:gateway => g, :message => message} when :error - print "E" + print 'E' errored << {:gateway => g, :message => message} end end @@ -60,13 +60,12 @@ def test_gateways puts d.name end end - end def try_host(http, path) http.get(path) rescue Net::HTTPBadResponse, EOFError, SocketError - http.post(path, "") + http.post(path, '') end def ssl_verify_peer?(uri) @@ -78,7 +77,7 @@ def ssl_verify_peer?(uri) http.read_timeout = 60 if uri.path.blank? - try_host(http, "/") + try_host(http, '/') else try_host(http, uri.path) end diff --git a/lib/support/ssl_version.rb b/lib/support/ssl_version.rb new file mode 100644 index 00000000000..ed7c716c9c0 --- /dev/null +++ b/lib/support/ssl_version.rb @@ -0,0 +1,87 @@ +require 'active_merchant' +require 'support/gateway_support' + +class SSLVersion + attr_accessor :success, :failed, :missing, :errored + + def initialize + @gateways = GatewaySupport.new.gateways + @success, @failed, @missing, @errored = [], [], [], [] + end + + def test_gateways(min_version = :TLS1_1) + raise 'Requires Ruby 2.5 or better' unless Net::HTTP.instance_methods.include?(:min_version=) + + puts "Verifying #{@gateways.count} gateways for SSL min_version=#{min_version}\n\n" + + @gateways.each do |g| + unless g.live_url + missing << g unless g.abstract_class + next + end + + uri = URI.parse(g.live_url) + result, message = test_min_version(uri, min_version) + + case result + when :success + print '.' + success << g + when :fail + print 'F' + failed << {:gateway => g, :message => message} + when :error + print 'E' + errored << {:gateway => g, :message => message} + end + end + + print_summary + end + + def print_summary + puts "\n\nSucceeded gateways (#{success.length})" + + puts "\n\nFailed Gateways (#{failed.length}):" + failed.each do |f| + puts "#{f[:gateway].name} - #{f[:message]}" + end + + puts "\n\nError Gateways (#{errored.length}):" + errored.each do |e| + puts "#{e[:gateway].name} - #{e[:message]}" + end + + if missing.length > 0 + puts "\n\nGateways missing live_url (#{missing.length}):" + missing.each do |m| + puts m.name + end + end + end + + private + + def test_min_version(uri, min_version) + http = Net::HTTP.new(uri.host, uri.port) + http.use_ssl = true + http.verify_mode = OpenSSL::SSL::VERIFY_NONE # don't care about certificate validity, just protocol version + http.min_version = min_version + http.open_timeout = 10 + http.read_timeout = 10 + + http.post('/', '') + + return :success + rescue Net::HTTPBadResponse + return :success # version negotiation succeeded + rescue OpenSSL::SSL::SSLError => ex + return :fail, ex.inspect + rescue Interrupt => ex + print_summary + raise ex + rescue StandardError => ex + return :error, ex.inspect + end + +end diff --git a/rails/init.rb b/rails/init.rb deleted file mode 100644 index f03a3eb9431..00000000000 --- a/rails/init.rb +++ /dev/null @@ -1,3 +0,0 @@ -# If Active Merchant is included into a Rails project as a gem, then -# this file gets loaded instead of the top-level init.rb -require File.dirname(__FILE__) + '/../init' diff --git a/script/generate b/script/generate index e5cfc875a99..6312f82aa0c 100755 --- a/script/generate +++ b/script/generate @@ -1,16 +1,15 @@ #!/usr/bin/env ruby -require "rubygems" -require "thor" +require 'rubygems' +require 'thor' -require File.expand_path("../../generators/active_merchant_generator", __FILE__) +require File.expand_path('../../generators/active_merchant_generator', __FILE__) -Dir[File.expand_path("../..", __FILE__) + "/generators/*/*.rb"].each do |generator| +Dir[File.expand_path('../..', __FILE__) + '/generators/*/*.rb'].each do |generator| require generator end class Generate < Thor - register(GatewayGenerator, "gateway", "gateway NAME", "Generates a new gateway.") - register(IntegrationGenerator, "integration", "integration NAME", "Generates a new integration.") + register(GatewayGenerator, 'gateway', 'gateway NAME', 'Generates a new gateway.') end Generate.start diff --git a/shipit.rubygems.yml b/shipit.rubygems.yml new file mode 100644 index 00000000000..c3f42d3795a --- /dev/null +++ b/shipit.rubygems.yml @@ -0,0 +1 @@ +# using the default shipit config diff --git a/test/comm_stub.rb b/test/comm_stub.rb index f96c4c8f951..9293ef77955 100644 --- a/test/comm_stub.rb +++ b/test/comm_stub.rb @@ -5,6 +5,7 @@ def initialize(gateway, method_to_stub, action) @action = action @complete = false @method_to_stub = method_to_stub + @check = nil end def check_request(&block) @@ -15,8 +16,10 @@ def check_request(&block) def respond_with(*responses) @complete = true check = @check - (class << @gateway; self; end).send(:define_method, @method_to_stub) do |*args| - check.call(*args) if check + singleton_class = (class << @gateway; self; end) + singleton_class.send(:undef_method, @method_to_stub) + singleton_class.send(:define_method, @method_to_stub) do |*args| + check&.call(*args) (responses.size == 1 ? responses.last : responses.shift) end @action.call @@ -25,16 +28,24 @@ def respond_with(*responses) def complete? @complete end - end - def stub_comms(method_to_stub=:ssl_post, &action) - if @last_comm_stub - assert @last_comm_stub.complete?, "Tried to stub communications when there's a stub already in progress." + class Complete + def complete? + true + end end - @last_comm_stub = Stub.new(@gateway, method_to_stub, action) + end + + def last_comm_stub + @last_comm_stub ||= Stub::Complete.new + end + + def stub_comms(gateway=@gateway, method_to_stub=:ssl_post, &action) + assert last_comm_stub.complete?, "Tried to stub communications when there's a stub already in progress." + @last_comm_stub = Stub.new(gateway, method_to_stub, action) end def teardown - assert(@last_comm_stub.complete?) if @last_comm_stub + assert(last_comm_stub.complete?) end -end \ No newline at end of file +end diff --git a/test/fixtures.yml b/test/fixtures.yml index 814be2c77f9..7f8439a76eb 100644 --- a/test/fixtures.yml +++ b/test/fixtures.yml @@ -10,91 +10,301 @@ # # Paste any required PEM certificates after the pem key. # + +adyen: + username: '' + password: '' + merchant_account: '' + +allied_wallet: + site_id: site_id + merchant_id: merchant_id + token: token + +# Working credentials, no need to replace as of Oct 28 2014 authorize_net: - login: X - password: Y + login: 7Tt72zseSzH + password: 7gTh55rdy92ZkP4z + +axcessms: + channel: channel + sender: sender + login: login + password: password # Working credentials, no need to replace balanced: login: 'e1c5ad38d1c711e1b36c026ba7e239a9' +bambora_apac: + username: nmi.api + password: qwerty123 + +# Bank Frick doesn't provide public testing data +bank_frick: + sender: sender-uuid + channel: channel-uuid + userid: user-uuid + userpwd: password + banwire: login: "desarrollo" -barclays_epdq: - login: test - password: test - client_id: 1234 +# Non-alphanumeric characters may cause an authentication error +barclaycard_smartpay: + company: company + merchant: merchant + password: password barclays_epdq_extra_plus: login: merchant number user: username password: password +be2bill: + login: your_test_login + password: your_test_password + beanstream: login: merchant id user: username password: password secure_profile_api_key: API Access Passcode recurring_api_key: API Access Passcode + api_key: API KEY beanstream_interac: login: merchant id user: username password: password +bit_pay: + api_key: 'rKrahSl7WRrYeKRUhGzbvW3nBzo0jG4FPaL8uPYaoPk' + blue_pay: login: '100096218902' password: 'MBUVE4G1BACMFM4W3XQHUUL2SMQRP.LW' -braintree_orange: - login: demo - password: password +# Working credentials, no need to replace +blue_snap: + api_username: 'API_146117371665767340423' + api_password: 'nfYwTx8fFAvJqBXcxqwC8' + +borgun: + processor: 118 + merchant_id: 118 + username: spreedly + password: Qxi.34k + +bpoint: + username: 'A' + password: 'B' + merchant_number: 'C' + biller_code: '' braintree_blue: merchant_id: X public_key: Y private_key: Z + merchant_account_id: A braintree_blue_with_processing_rules: merchant_id: X public_key: Y private_key: Z +braintree_orange: + login: demo + password: password + +# Working credentials, no need to replace +bridge_pay: + user_name: Spre3676 + password: H3392nc5 + +# Working credentials, no need to replace +cams: + username: testintegrationc + password: password9 + +# Working credentials, no need to replace +card_connect: + merchant_id: "496160873888" + username: testing + password: testing123 + #Username/Password given in sign up email. Not MMS credentials card_save: login: merchant_id password: password card_stream: - login: X - password: Y + login: 103191 + shared_secret: Dear0Coming15Edge -card_stream_modern: - login: "0000992" +# Working credentials, no need to replace +cardknox: + api_key: ActiveMerchant_Test + +# Working credentials, no need to replace +cardprocess: + user_id: 8a8294174e735d0c014e78beb6c5154f + password: cTZjAm9c87 + entity_id: 8a8294174e735d0c014e78beb6b9154b + +# Cashnet doesn't provide public testing data +cashnet: + merchant: 'X' + operator: 'X' + password: 'X' + merchant_gateway_name: 'X' + +cecabank: + merchant_id: MERCHANTID + acquirer_bin: ACQUIRERBIN + terminal_id: TERMINALID + key: KEY + +cenpos: + merchant_id: SOMECREDENTIAL + password: ANOTHERCREDENTIAL + user_id: ANOTHERCREDENTIAL certo_direct: login: 1 password: vP6OwK3 +checkout: + merchant_id: SBMTEST + password: Password1! + +checkout_v2: + secret_key: secret_key + +citrus_pay: + userid: CPF00001 + password: 7c70414732de7e0ba3a04db5f24fcec8 + +# Working credentials, no need to replace +clearhaus: &clearhaus + api_key: a2c583e7-23f2-4097-b327-84a87128cfb4 + +clearhaus_secure: + <<: *clearhaus + signing_key: 7e51b92e-ca7e-48e3-8a96-7d66cf1f2da2 + private_key: | + -----BEGIN RSA PRIVATE KEY----- + MIIBOwIBAAJBALYK0zmwuYkH3YWcFNLLddx5cwDxEY7Gi1xITuQqRrU4yD3uSw+J + WYKknb4Tbndb6iEHY+e6gIGD+49TojnNeIUCAwEAAQJARyuYRRe4kcBHdPL+mSL+ + Y0IAGkAlUyKAXYXPghidKD/v/oLrFaZWALGM2clv6UoYYpPnInSgbcud4sTcfeUm + QQIhAN2JZ2qv0WGcbIopBpwpQ5jDxMGVkmkVVUEWWABGF8+pAiEA0lySxTELZm8b + Gx9UEDRghN+Qv/OuIKFldu1Ba4f8W30CIQCaQFIBtunTTVdF28r+cLzgYW9eWwbW + pEP4TdZ4WlW6AQIhAMDCTUdeUpjxlH/87BXROORozAXocBW8bvJUI486U5ctAiAd + InviQqJd1KTGRDmWIGrE5YACVmW2JSszD9t5VKxkAA== + -----END RSA PRIVATE KEY----- + + +# Contact Support at it_support@commercegate.com for credentials and offer/site +commercegate: + login: "XXXXXXX" + password: "XXXXXXX" + site_id: "XXXXXXX" + offer_id: "XXXXXXX" + card_number: "XXXXXXXXXXXXXXXX" + +conekta: + key: key_6FTbuwqhYs6zvyyeL3PySg + +# Working credentials, no need to replace +creditcall: + terminal_id: '99961426' + transaction_key: '9drdRU9wJ65SNRw3' + +# NOTE: the IP address you run the remote tests from will need to be +# whitelisted by Credorax; contact support@credorax.com as necessary to request +# your IP address be added to the whitelist for your test account. +credorax: + merchant_id: 'merchant_id' + cipher_key: 'cipher_key' + +ct_payment: + api_key: SOMECREDENTIAL + company_number: '12345' + merchant_number: '12345678' + +# Culqi does not provide public testing data +culqi: + merchant_id: MERCHANT + terminal_id: TERMINAL + partner_id: PARTNER + secret_key: SECRET + +# To get 100% passing Cybersource remote tests, you must ask +# Cybersource support to enable the recurring and pinless debit +# services on your test account. cyber_source: login: X password: Y +# Working credentials, no need to replace +d_local: + login: aeaf9bbfa1 + trans_key: 9de3769b7e + secret_key: ae132899f56162a669b38dab5927862f3 + data_cash: login: X password: Y +# Working credentials, no need to replace +decidir_authorize: + api_key: 5a15fbc227224edabdb6f2e8219e8b28 + preauth_mode: true + +decidir_purchase: + api_key: 5df6b5764c3f4822aecdc82d56f26b9d + +# No working test credentials +dibs: + merchant_id: SOMECREDENTIAL + secret_key: NOPUBLICCREDENTIAL + +# Working credentials, no need to replace +digitzs: + app_key: tcwtTux8SPZYO44Gf0UHZH74Z1HSutqCxmIV2PFj2jRc9Poroh3Z3R1BBQNRQ98Q + api_key: 0HhRdOU2AsWVEu3gRIKi2UpMMmj8Fj48qggBYTo4 + direc_pay: login: 200904281000001 +# Working credentials, no need to replace +ebanx: + integration_key: 1231000 + efsnet: login: X password: Y +# Provided for url update test + elavon: - login: LOGIN - password: PASSWORD + login: "009005" + user: "devportal" + password: "BDDZY5KOUDCNPV4L3821K7PETO4Z7TPYOJB06TYBI1CW771IDHXBVBP51HZ6ZANJ" + +elavon_multi_currency: + login: "009006" + user: "devportal" + password: "XWJS3QTFCH40HW0QGHJKXAYADCTDH0TXXAKXAEZCGCCJ29CFNPCZT4KA9D5KQMDA" + multi_currency: true + +element: + account_id: "1013963" + account_token: "683EED8A1A357EB91575A168E74482A74836FD72B1AD11B41B29B473CA9D65B9FE067701" + application_id: "5211" + acceptor_id: "3928907" + application_name: "Spreedly" + application_version: "1" # login: merchant number # password: referrer url (for authorize authentication) @@ -115,16 +325,19 @@ eway_managed: username: 'test@eway.com.au' password: 'test123' -# Working credentials, no need to replace eway_rapid: - login: "F9802CIVgUgNaD8y/H52MBMnL5OvMoy4cYimpi1L/dCXeNNR6or3vLPoC9GjeLVdA7ymi+" - password: "sandbox1" + login: LOGIN + password: PASSWORD # Working credentials, no need to replace exact: login: "A00427-01" password: testus +# Working credentials, no need to replace +ezic: + account_id: "120536457270" + # Working credentials, no need to replace fat_zebra: username: TEST @@ -135,56 +348,118 @@ federated_canada: password: password finansbank: - login: TEST - password: TEST - client_id: TEST + login: FINANSAPI + password: FINANS06 + client_id: 600100000 + +first_giving: + application_key: '' + security_token: '' + charity_id: "1234" first_pay: - login: - password: + transaction_center_id: 1264 + gateway_id: "a91c38c3-7d7f-4d29-acc7-927b4dca0dbe" firstdata_e4: - login: - password: + login: SD8821-67 + password: T6bxSywbcccbJ19eDXNIGaCDOBg1W7T8 + +firstdata_e4_v27: + login: ALOGIN + password: APASSWORD + key_id: ANINTEGER + hmac_key: AMAGICALKEY + +flo2cash: + username: SOMECREDENTIAL + password: ANOTHERCREDENTIAL + account_id: ANOTHERCREDENTIAL + +flo2cash_simple: + username: SOMECREDENTIAL + password: ANOTHERCREDENTIAL + account_id: ANOTHERCREDENTIAL + +forte: + location_id: "176008" + account_id: "300111" + api_key: "f087a90f00f0ae57050c937ed3815c9f" + secret: "d793d64064e3113a74fa72035cfc3a1d" garanti: login: "PROVAUT" - terminal_id: 111995 - merchant_id: 600218 + terminal_id: 30691300 + merchant_id: 7000679 password: "123qweASD" -# login: merchant number -# password: password for the PEM secret key -# pem: public certificate and PEM secret key -ideal_rabobank: +global_collect: + merchant_id: 2196 + api_key_id: c91d6752cbbf9cf1 + secret_api_key: xHjQr5gL9Wcihkqoj4w/UQugdSCNXM2oUQHG5C82jy4= + +global_transport: + global_user_name: "USERNAME" + global_password: "PASSWORD" + term_type: "ABC" + +hdfc: login: LOGIN password: PASSWORD - pem: | - PASTE YOUR PEM FILE HERE -instapay: - login: TEST0 - password: +# Working credentials, no need to replace +hps: + secret_api_key: "skapi_cert_MYl2AQAowiQAbLp5JesGKh7QFkcizOP2jcX9BrEMqQ" + +iats_payments: + agent_code: TEST88 + password: TEST88 + region: na inspire: login: demo password: password +instapay: + login: TEST0 + password: + +# Working credentials, no need to replace +ipp: + username: nmi.api + password: qwerty123 + +iridium: + login: LOGIN + password: PASSWORD + itransact: login: API_ACCESS_USERNAME password: API_ACCESS_KEY gateway_id: GATEWAY_ID +# Working credentials, no need to replace +iveri: + cert_id: CB69E68D-C7E7-46B9-9B7A-025DCABAD6EF + app_id: d10a603d-4ade-405b-93f1-826dfc0181e8 + jetpay: login: TESTTERMINAL -hdfc: - login: LOGIN - password: PASSWORD +jetpay_v2: + login: TESTMCC3136X -iridium: - login: LOGIN - password: PASSWORD +komoju: + login: sk_f1dd75ce3d5cad477eac0c827c1cac8eaa51ede3 + +kushki: + public_merchant_id: "Your Public Merchant Id" + private_merchant_id: "Your Private Merchant Id" + +latitude19: + account_number: "03022016" + configuration_id: "380835424362" + secret: "&2016(march)02" linkpoint: login: STOREID @@ -196,6 +471,15 @@ litle: password: MERCHANT merchant_id: 101 +# Working test credentials, no need to replace +maxipago: + login: "100" + password: "21g8u6gh6szw1gywfs165vui" + +# Working credentials, no need to replace +mercado_pago: + access_token: "TEST-8527269031909288-071213-0fc96cb7cd3633189bfbe29f63722700__LB_LA__-263489584" + # Working test credentials, no need to replace merchant_esolutions: login: "94100008043900000004" @@ -205,15 +489,21 @@ merchant_one: username: 'demo' password: 'password' +# Working credentials, no need to replace +merchant_partners: + account_id: TEST0 + merchant_pin: "1234567890" + merchant_ware: login: password: name: +# Working credentials, no need to replace merchant_ware_version_four: - login: - password: - name: + login: 'BK34Z768' + password: 'TCTTS-IDYQV-RDFY1-6DS01-WTPVH' + name: 'Test Spreedly PayItSimple' merchant_warrior: merchant_uuid: '50c5b9f0b52ea' @@ -222,8 +512,8 @@ merchant_warrior: # Working credentials, no need to replace mercury: - login: '395347306=TOKEN' - password: '123TOKEN' + login: '089716741701445' + password: 'xyz' mercury_no_tokenization: login: '595901' @@ -234,49 +524,74 @@ metrics_global: login: 'demo' password: 'password' -# Working credentials, no need to replace -migs_purchase: # MiGS gateway for purchase mode - login: TESTANZTEST3 - password: '6447E199' - secure_hash: '76AF3392002D202A60D0AB5F9D81653C' - advanced_login: noack_ama - advanced_password: test1234 +micropayment: + access_key: "0b4832ca37a31e748c4490b58d743986" # Working credentials, no need to replace -migs_capture: # MiGS gateway for auth/capture mode - login: TESTANZTEST2 - password: '8ED23813' - secure_hash: '5816DF2E29819E43EA7C1F6E0DC2F1B5' - advanced_login: noack_ama - advanced_password: test1234 +migs: + login: TESTH-STATION + password: 'F1CE6F32' + secure_hash: 'D8CF972645E69EA15A7D6005059E18DF' + advanced_login: activemerchant + advanced_password: test12345 modern_payments: login: login password: password +# Working credentials, no need to replace +monei: + sender_id: 8a829417488d34c401489a5cd1350977 + channel_id: 8a829417488d34c401489a5de5dd097b + login: 8a829417488d34c401489a5cd1360979 + pwd: GyNSEAp2 + # Working credentials, no need to replace moneris: - login: store1 + login: store3 password: yesguy moneris_us: login: monusqa002 password: qatoken +money_movers: + login: demo + password: password + +mundipagg: + api_key: api_key + gateway_affiliation_id: gateway_affiliation_id + # left for backward-compatibility + gateway_id: gateway_id + # Working credentials, no need to replace nab_transact: login: ABC0001 password: changeit # Working credentials, no need to replace -nab_transact_card_acceptor: +nab_transact_privileged: login: XYZ0010 password: abcd1234 +# Working credentials, no need to replace +ncr_secure_pay: + username: test_ecomm:public + password: publ1ct3st + +net_registry: + login: X + password: Y + netaxept: login: LOGIN password: PASSWORD +netbanx: + api_key: APIKEY + account_number: ACCOUNTNUMBER + # Working credentials, no need to replace # Contact Netbilling for login info to the admin area netbilling: @@ -287,9 +602,9 @@ netpay: login: POS password: plaza2020 -net_registry: - login: X - password: Y +network_merchants: + login: demo + password: password nmi: login: demo @@ -301,70 +616,180 @@ ogone: password: PASSWORD signature: SIGNATURE +# Get credentials or api keys from https://dashboard.omise.co +omise: + public_key: pkey_test_4zt0fss8gs0z6b4zlsq + secret_key: skey_test_4zt0fss8eklsj88dx9l + api_version: '2015-11-17' + +# Working credentials, no need to replace +openpay: + key: 'sk_e568c42a6c384b7ab02cd47d2e407cab' + merchant_id: 'mzdtln0bmtms6o3kck8f' + +opp: + user_id: '8a8294174b7ecb28014b9699220015cc' + password: 'sy6KJsT8' + entity_id: '8a8294174d0a8edd014d242337942575' + optimal_payment: - login: test + store_id: test password: test - account: 'ACCOUNT_NUMBER' - -paybox_direct: - login: 199988899 - password: 1999888I + account_number: ACCOUNTNUMBER orbital_gateway: login: LOGIN password: PASSWORD merchant_id: MERCHANTID +# Working credentials, no need to replace +pagarme: + api_key: 'ak_test_e1QGU2gL98MDCHZxHLJ9sofPUFJ7tH' + +# Working credentials, no need to replace +pago_facil: + branch_id: 60f961360ca187d533d5adba7d969d6334771370 + merchant_id: 62ad6f592ecf2faa87ef2437ed85a4d175e73c58 + service_id: 3 + +# Working credentials, no need to replace +pay_conex: + account_id: '220614968961' + api_accesskey: '69e9c4dd6b8ab9ab47da4e288df78315' + # Working credentials, no need to replace pay_gate_xml: login: '10011021600' password: 'test' -payflow: +# Working credentials, no need to replace +pay_hub: + orgid: '123456' + username: 'abc123DEF' + password: 'abc123DEF' + tid: '123' + +# Working credentials, no need to replace +pay_junction: + login: 'pj-ql-01' + password: 'pj-ql-01p' + +# Working credentials, no need to replace +pay_junction_v2: + api_login: 'pj-ql-01' + api_password: 'pj-ql-01p' + api_key: '' + +pay_secure: login: LOGIN password: PASSWORD - partner: PayPal + +paybox_direct: + login: 199988863 + password: 1999888I + rang: 85 + +# Working credentials, no need to replace +payeezy: + apikey: UyDMTXx6TD9WErF6ynw7xeEfCAn8fcGs + apisecret: 2a4974e242c91cab7f38910938f2a5db79e89756b084bbf7cab7849b50a9930f + token: fdoa-a480ce8951daa73262734cf102641994c1e55e7cdf4c02b6 + +payex: + account: ACCOUNT + # encryption key is generated via the PayEx Admin console + encryption_key: ENCRYPTION_KEY + +# Working credentials, no need to replace +payflow: + login: 'spreedlyIntegrations' + password: 'L9DjqEKjXCkU' + partner: 'PayPal' payflow_uk: login: LOGIN password: PASSWORD partner: PayPalUk -# Working credentials, no need to replace -pay_junction: - login: 'pj-ql-01' - password: 'pj-ql-01p' - payment_express: login: LOGIN password: PASSWORD +paymentez: + application_code: APPCODE + app_key: APPKEY + paymill: - private_key: PRIVATE_KEY - public_key: PUBLIC_KEY + private_key: a9580be4a7b9d0151a3da88c6c935ce0 + public_key: 57313835619696ac361dc591bc973626 -# You can use either your API PEM file or API signature with PayPal. paypal_certificate: - login: LOGIN - password: PASSWORD + login: activemerchant-cert-test_api1.example.com + password: ERDD3JRFU5H5DQXS subject: pem: | - PASTE YOUR PEM FILE HERE + -----BEGIN RSA PRIVATE KEY----- + MIICXQIBAAKBgQD/fK2V+joi32b3hXwyZdWTrjX0eXF1NTY7iSvBDPipJalFcopa + RRQV41WHB7Ard8g4tsw9uP4aDqil/MJqoLm2pttOokKO1CX1R4xVz8kITefTDrMT + uGs43Sz6Kd/GLKeYpc+kk7vLgP9CqUNnuBWPLzw98t9XKTcdSf140WciPwIDAQAB + AoGAMxWK3+IYncBtpjBalPknq0+6GhfuR7FMFrtmtEMTtT6CihBM+Z+2VGoQP9+Z + qhdZQX3LeMv0guFLd2UCuq9IcobEuBL5Oyxvd1MYcSVuMHa8DQBYQfL2Dtq/m8K9 + vTGdsvL3IyVlWs9xxZRtuEoBpmb3axjohVS62rq7CzGbMYECQQD/054sW/V7MZD7 + 8Siz1Dhe3CJpGJ3RIkBzuMi5vtJftx6S4GX4jsbE8tyMSwX7TnO1xvHP7s1OCSEC + gUwIMQQlAkEA/6kAVG37ErrN5WX0kw+UIrBYQhnyZvjqVIqSSm8ZAn1cDbx84GbX + UTbPpJBbpcP5PDxeQnBqpvnLuIZfzSZtkwJAYSfD5ULTOoL7dcMDWzAYbGYbp2AS + 506jvY8KpAgFKxaHRO51q2zFrhwxiBIh5mvH49v3D6m4TI+I+sOR1XaQBQJBAIvh + 2i5X5rH+x70mJcV5FqJMPl4ceEbjFsOe9iAH3XVBRea2JNVbL6BeDwqJebufGHVe + ymwrug8WSeLykuRajEUCQQDlDXignvtCZlXlOCxohPuJ+o+vnF3R/L6MBtQmRjWI + mv6epzBqjXcB9Dv3n10k7M9H6YKfAZbuZ23PDTxrv3o+ + -----END RSA PRIVATE KEY----- + -----BEGIN CERTIFICATE----- + MIICsDCCAhmgAwIBAgIDEBZBMA0GCSqGSIb3DQEBBQUAMIGfMQswCQYDVQQGEwJV + UzETMBEGA1UECBMKQ2FsaWZvcm5pYTERMA8GA1UEBxMIU2FuIEpvc2UxFTATBgNV + BAoTDFBheVBhbCwgSW5jLjEWMBQGA1UECxQNc2FuZGJveF9jZXJ0czEbMBkGA1UE + AxQSc2FuZGJveF9jYW1lcmNoYXBpMRwwGgYJKoZIhvcNAQkBFg1yZUBwYXlwYWwu + Y29tMB4XDTE0MTAyMzE0MzQxM1oXDTI0MTAyMDE0MzQxM1owgYoxMjAwBgNVBAMU + KWFjdGl2ZW1lcmNoYW50LWNlcnQtdGVzdF9hcGkxLmV4YW1wbGUuY29tMScwJQYD + VQQKEx5OYXRoYW5pZWwgVGFsYm90dCdzIFRlc3QgU3RvcmUxETAPBgNVBAcTCFNh + biBKb3NlMQswCQYDVQQIEwJDQTELMAkGA1UEBhMCVVMwgZ8wDQYJKoZIhvcNAQEB + BQADgY0AMIGJAoGBAP98rZX6OiLfZveFfDJl1ZOuNfR5cXU1NjuJK8EM+KklqUVy + ilpFFBXjVYcHsCt3yDi2zD24/hoOqKX8wmqgubam206iQo7UJfVHjFXPyQhN59MO + sxO4azjdLPop38Ysp5ilz6STu8uA/0KpQ2e4FY8vPD3y31cpNx1J/XjRZyI/AgMB + AAGjDTALMAkGA1UdEwQCMAAwDQYJKoZIhvcNAQEFBQADgYEABR7D4mbOHBHYgMsG + sSxxYqqvhFSF7/HBQW4G132w6lxKVqdF3a6ICJYC/gOjOQ609kowDOrCwag9cbiO + 3gPzIjyqNemdkTJxiTaq9+fG7G+r6f+pXo0ZVvfFjgtoSUVdMlqgu572EEZOQmSL + w00oJt7RcWBpIkpS18Qpmk6KZ7o= + -----END CERTIFICATE----- paypal_signature: - login: LOGIN - password: PASSWORD - signature: SIGNATURE + login: activemerchant-test_api1.example.com + password: HBC6A84QLRWC923A + signature: AFcWxV21C7fd0v3bYYYRCpSSRl31AC-11AKBL8FFO9tjImL311y8a0hx + +payscout: + username: demo + password: password + +# There are all kind of special options that must be set on a test account, and +# PayU is not transparent about what they are. At this point you need to tell +# them specifically that the account will be used with Spreedly, which should +# hopefully get you the right options. +payu_in: + key: KEY + salt: SALT + +# Working credentials, no need to replace +payu_latam: + merchant_id: "508029" + account_id: "512322" + api_login: "pRRXKOl8ikMmt9u" + api_key: "4Vj8eK4rloUd272L48hsrarnUA" payway: username: password: pem: -pay_secure: - login: LOGIN - password: PASSWORD - pin: api_key: "I_mo9BUUUXIwXF-avcs3LA" @@ -372,16 +797,9 @@ plugnpay: login: LOGIN password: PASSWORD -pxpay: - login: LOGIN - password: PASSWORD - -redsys: - login: MERCHANT CODE - secret_key: SECRET KEY - -sage_pay: - login: LOGIN +# Working credentials, no need to replace +pro_pay: + cert_str: "5ab9cddef2e4911b77e0c4ffb70f03" # Working credentials, no need to replace psigate: @@ -448,103 +866,258 @@ psl_visa_debit_address: state: zip: +pxpay: + login: LOGIN + password: PASSWORD + +quantum: + login: X + password: Y + +# You will need to create a developer sandbox at https://developer.intuit.com/ and +# successfully generate an OAuth 1.0a access token and token secret. +quickbooks: + consumer_key: + consumer_secret: + access_token: + token_secret: + realm: + # Quickpay offers a test account. # To log in to the manager, use demo@quickpay.net and test234 quickpay: login: 89898978 password: "29p61DveBZ79c3144LW61lVz1qrwk2gfAFCxPyi5sn49m3Y3IRK5M6SN5d8a68u7" + +quickpay_v10_api_key: + api_key: "6e4e4f0a7cf692b0118ffe2c56c45b0cd3171badf65626de2ead023fd99b8bbc" + # To get the right apikey, log in to the manager. # Go to Indstillinger > API adgang and make sure you have the right key in the fixture. quickpay_with_api_key: login: 89898978 password: "29p61DveBZ79c3144LW61lVz1qrwk2gfAFCxPyi5sn49m3Y3IRK5M6SN5d8a68u7" apikey: "fB46983ZwR5dzy46A3r6ngDx7P37N5YTu1F4S9W2JKCs9v4t5L9m2Q8Mlbjpa2I1" - version: 6 + version: 7 -quantum: - login: X - password: Y +qvalent: + username: "QRSL" + password: "QRSLTEST" + merchant: 24436057 + pem_password: "Ceic4s4ig" + pem: | + Bag Attributes + localKeyID: 01 00 00 00 + friendlyName: CCAPI Client Certificate + Key Attributes + X509v3 Key Usage: 10 + -----BEGIN RSA PRIVATE KEY----- + MIIEpAIBAAKCAQEAtgPNEzLEFAm2CXCiaJeEFQxzkxXHn+Lyf65FjTDIOMknXnmw + gtoPTrFHtDVLJqWHaccLg9/lMKHivW1iGPTeZj4Uke07EcmIaDQD3Gq33gukDbgx + d5QDN+mEPRs0TF6FHlKKELKGIZdw4BlM+Jg7hebwZnBC/g9cetmB3ny99g0e5ANk + pPESguo+HTFk5vwHkLjMtDB+0zM5y6AyU6z+PaCDr3Q7+vcR50kQLNxpSWH1Il1g + BewIHAroN4lJjPvKLnDWvK5SsxW9XB14gLAyQg6qCHDYOdTsdiG6BFG1eijT+3W7 + iXm8uTDiXSj+PiADEeKvnbEsWqXu+DvUsR/m8wIDAQABAoIBADtC/ZBUpRbJGqX0 + MEzRmEWqKi8nljlukPoVabvQuEAU7maKRHg2O2mpuujnuTI6Dt7X2d30FhFBhCuc + 46WwhIDRkaz5ipP+BBW5adBoRrlbHO0CnciLPokD1PR4WQzMcZcv1JgfKCDjx/KP + CkqedjLgwED6KDXEFp5BF1GzV742dfux0Bgq+5kzLp0uAe0+ADxGll1Fx1wWbP/e + 4XSTWz4P1q3fmGAJB7fgXcfdp+Yri1x0RNDjBUa6YsR5fFVZBXcFEmPcRGbcsO3J + ApJTrskt4H0rQafcx/LmUj3pBVsl/tcyMBHHGHUb0N5pKypf2X+79IYhV+b9K9Ll + 5EHVV+ECgYEA8TTci5PsysRvzN5HIgpfsd3ZK/JrTkJ2z3Xx2HFL9XU8EAx9gO5h + Qa7CnJY+myoes+x90I7uKqfJdB1JDq2Bek6Jfj8stVd/YTtrOMt7WhHc6OW2S+qY + TpvtdeoVcJ2rKGFyo5/mj0aO+Hg1i6wxsIhDPKvSEqSqUrbjmLi5AfkCgYEAwS2V + IVzmMZIaRg0FxRccWl/XgpK9JNVjNr4PRiSkjsbFktVNLkbBcFOvrbmPPkBuHW8t + XDK6goRcNraxkkzB81mx0glW/zHfEQS4GWuQQEqIWwBfC9QEvC+AlGLaNhQYMleC + omLzTDTkBPtQSc72H0ov8TpowgeyM1+U2uozq0sCgYEAm+Szag64KzEcpQdAaDLW + OIoO04WBbvor+dfb8C0Bj+ouYJ0B/HOVLjN6GmRMoFJvt4/wnPvT2IPLAx3uWusu + 1NKvsIW6KpYbgMc7fGCfH86NvYTB9nzv5VaH+f7JzphIx/d7dV9iT1WmD9b5nIU1 + NEhNVIgkZOJCJuWHYex5vlkCgYAF4uqxapBFIGuWiN0NJWgixNrfSrNixPHSADac + 747oHtx0XfWNHHDWiGZJB+d6gSIZ2YJrVcxjH79jl2uPxrD+RlRpzwkMm6ttbFRj + yehKXTsMctVymdJPHa9wVhbKIRCfsBT198fsIYx1LmdC6ICNcYhGdH4us2dVs2ro + xMwwQwKBgQDgNA9bGhiajsOI//M9ndH8KZCGdOoYM+6JBurHKX2fF8INRX+sxWix + DbUr9rQXkDrX1CY5RNs9kZpNxLJpOSHOpYlIytnGghYTrU1nzXoKLVopkO9Oz1A6 + WzrOd297XdEoyEbAeCgcLr2WtjMiFHonesjeWIgbqXSKZ+W1oek0vQ== + -----END RSA PRIVATE KEY----- + Bag Attributes + localKeyID: 01 00 00 00 + friendlyName: CCAPI Client Certificate + subject=/CN=Returned &#38; Services League of Australia (Queensland Branch) - Support/OU=IT/O=Returned &#38; Services League of Australia (Queensland Branch)/ST=NSW/C=AU/EMAILADDRESS=pp_support@qvalent.com + issuer=/CN=eQvalent/OU=Operations/O=QValent Pty Ltd/L=Wallsend/ST=NSW/C=AU + -----BEGIN CERTIFICATE----- + MIIE0DCCA7igAwIBAgIEWLX2pzANBgkqhkiG9w0BAQUFADBwMQswCQYDVQQGEwJB + VTEMMAoGA1UECBMDTlNXMREwDwYDVQQHEwhXYWxsc2VuZDEYMBYGA1UEChMPUVZh + bGVudCBQdHkgTHRkMRMwEQYDVQQLEwpPcGVyYXRpb25zMREwDwYDVQQDEwhlUXZh + bGVudDAeFw0xNzAyMjgyMjE2MDdaFw0xOTAyMjgyMjE2MDdaMIHtMSUwIwYJKoZI + hvcNAQkBFhZwcF9zdXBwb3J0QHF2YWxlbnQuY29tMQswCQYDVQQGEwJBVTEMMAoG + A1UECAwDTlNXMUgwRgYDVQQKDD9SZXR1cm5lZCAmIzM4OyBTZXJ2aWNlcyBMZWFn + dWUgb2YgQXVzdHJhbGlhIChRdWVlbnNsYW5kIEJyYW5jaCkxCzAJBgNVBAsMAklU + MVIwUAYDVQQDDElSZXR1cm5lZCAmIzM4OyBTZXJ2aWNlcyBMZWFndWUgb2YgQXVz + dHJhbGlhIChRdWVlbnNsYW5kIEJyYW5jaCkgLSBTdXBwb3J0MIIBIjANBgkqhkiG + 9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtgPNEzLEFAm2CXCiaJeEFQxzkxXHn+Lyf65F + jTDIOMknXnmwgtoPTrFHtDVLJqWHaccLg9/lMKHivW1iGPTeZj4Uke07EcmIaDQD + 3Gq33gukDbgxd5QDN+mEPRs0TF6FHlKKELKGIZdw4BlM+Jg7hebwZnBC/g9cetmB + 3ny99g0e5ANkpPESguo+HTFk5vwHkLjMtDB+0zM5y6AyU6z+PaCDr3Q7+vcR50kQ + LNxpSWH1Il1gBewIHAroN4lJjPvKLnDWvK5SsxW9XB14gLAyQg6qCHDYOdTsdiG6 + BFG1eijT+3W7iXm8uTDiXSj+PiADEeKvnbEsWqXu+DvUsR/m8wIDAQABo4HzMIHw + MBMGA1UdJQQMMAoGCCsGAQUFBwMCMB0GA1UdDgQWBBTujPN/magozRJ8XJxAVYPw + v5/HsDCBqQYDVR0jBIGhMIGegBQ2QKUf9O24xKZrWTyUqRmZeq04IqF0pHIwcDEL + MAkGA1UEBhMCQVUxDDAKBgNVBAgTA05TVzERMA8GA1UEBxMIV2FsbHNlbmQxGDAW + BgNVBAoTD1FWYWxlbnQgUHR5IEx0ZDETMBEGA1UECxMKT3BlcmF0aW9uczERMA8G + A1UEAxMIZVF2YWxlbnSCEG49SZXSVViNQE7XcOvVjwYwDgYDVR0PAQH/BAQDAgTw + MA0GCSqGSIb3DQEBBQUAA4IBAQCQwdv8D4B5sRnA9/ppDfwlix6omzh/SdA3SI4m + BNcImoqzBEq1OokdWKDkRQXlP6822aJn8fzDMV1/YsmomC4fT0wdag0DvBEvRlhy + roQRFjQai6CKRUgoH/p0q4EwxKLOa/H0kix+cn6Nszl07wu6YtHzvISXf4MC72au + /xTf/qwXI1uXnQghb4sFM9/ubYlsNEFiNHt7CHBK7ivhFGcT7eI9rmSOTMKsZfmV + pALJ58Ynz08xLYRMq54FxzwhN3CZ7AWRA+9JpzJacJ6lzHX9Y4FAjzRzoqn5h/IN + dgMIW+HoTgCe4+M1aDx2A0SKJCSK9tYZCYSsPMi9JXdLDU+k + -----END CERTIFICATE----- + Bag Attributes + localKeyID: 02 00 00 00 + subject=/CN=eQvalent/OU=Operations/O=QValent Pty Ltd/L=Wallsend/ST=NSW/C=AU + issuer=/CN=eQvalent/OU=Operations/O=QValent Pty Ltd/L=Wallsend/ST=NSW/C=AU + -----BEGIN CERTIFICATE----- + MIIDuzCCAqOgAwIBAgIQbj1JldJVWI1ATtdw69WPBjANBgkqhkiG9w0BAQUFADBw + MQswCQYDVQQGEwJBVTEMMAoGA1UECBMDTlNXMREwDwYDVQQHEwhXYWxsc2VuZDEY + MBYGA1UEChMPUVZhbGVudCBQdHkgTHRkMRMwEQYDVQQLEwpPcGVyYXRpb25zMREw + DwYDVQQDEwhlUXZhbGVudDAeFw0xMDA0MDcwNTUyMThaFw0zMDA0MDcwNjAyMTda + MHAxCzAJBgNVBAYTAkFVMQwwCgYDVQQIEwNOU1cxETAPBgNVBAcTCFdhbGxzZW5k + MRgwFgYDVQQKEw9RVmFsZW50IFB0eSBMdGQxEzARBgNVBAsTCk9wZXJhdGlvbnMx + ETAPBgNVBAMTCGVRdmFsZW50MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC + AQEAxKuwonFlg53dyb0zFyS0JPUbxKmHwBDZcTT78eV4hs9TveOR34yT9xv+y7R1 + GbUdtXxidGaXW6lfkWvVnS1mkDbj84OY2FzRTHeKuNmkcPddQDUgE+gaOGV6GKW7 + v/s5jMt6pz075teLKvNO2Gs2alpPl4NYU5zJfa+AGFzqE1z7Zxbl9N/Sc6Y8ZbWI + IGWKJFeaTic8tE05hIGuWw1KfxbhH0QMKnTSzW5fJi4Pce3iIftkCTjIXdTg+El8 + 9b+Jn5VMk+bQZQiusPbPbHZTvAADX/WCUuzAlFqyyNtutJmUlD/TiEB12YYp9R6U + 1MGRvC9+c+sz2bBccW3c+Zm7cQIDAQABo1EwTzALBgNVHQ8EBAMCAYYwDwYDVR0T + AQH/BAUwAwEB/zAdBgNVHQ4EFgQUNkClH/TtuMSma1k8lKkZmXqtOCIwEAYJKwYB + BAGCNxUBBAMCAQAwDQYJKoZIhvcNAQEFBQADggEBAAJRMGZzxO3lcTXoFdmOpo8P + Ptf/agwrlGT8krwKcZt42YZ98VoIaCTI83kkACYlTlkTTNkDdHtAwF7ROteZmbfQ + 5ky0nqZTpdQ2IPfGXpxcMIrGqrSJ6GbLTxrm0kXDH6TgYeY1dVTHtJD9wEgWwdI9 + coRkKQVjK/QucPuVX0uoRppCz0rCiJfCQf2825BrcbaY4HyDHvNxRXKV9ECuYwrk + HYDy3bnYWVB2ekk8xOEiocjQI3T//V36ZfiGubKlUZ4xsSed410hkLtB6ttCyZB1 + RFjxWKtn9pXbM1PLmUXCkKQnSJSeD1K0NjV+g8KFChTEgmhnLogyF/7YYw/amfc= + -----END CERTIFICATE----- + +raven_pac_net: + user: ernest + secret: "all good men die young" + prn: 987743 realex: login: X password: Y -realex_with_account: - login: X - password: Y - account: testaccount - -# Realex doesn't provide public testing data -# Fill in the card numbers with the Realex test -# data. -realex_visa: - number: +realex_mastercard: + number: '5425230000004415' month: '6' year: '2020' verification_value: '123' + brand: 'master' -realex_visa_declined: - number: +realex_mastercard_coms_error: + number: '5135020000005871' month: '6' year: '2020' verification_value: '123' + brand: 'master' -realex_visa_referral_a: - number: +realex_mastercard_declined: + number: '5114610000004778' month: '6' year: '2020' verification_value: '123' + brand: 'master' -realex_visa_referral_b: - number: +realex_mastercard_referral_a: + number: '5121220000006921' month: '6' year: '2020' verification_value: '123' + brand: 'master' -realex_visa_coms_error: - number: +realex_mastercard_referral_b: + number: '5114630000009791' month: '6' year: '2020' verification_value: '123' + brand: 'master' -realex_mastercard: - number: +# Realex doesn't provide public testing data +# Fill in the card numbers with the Realex test +# data. +realex_visa: + number: '4263970000005262' month: '6' year: '2020' verification_value: '123' -realex_mastercard_declined: - number: +realex_visa_3ds_enrolled: + number: '4012001037141112' + month: '9' + year: '2021' + verification_value: '123' + +realex_visa_coms_error: + number: '4009830000001985' month: '6' year: '2020' verification_value: '123' -realex_mastercard_referral_a: - number: +realex_visa_declined: + number: '4000120000001154' month: '6' year: '2020' verification_value: '123' -realex_mastercard_referral_b: - number: +realex_visa_referral_a: + number: '4000160000004147' month: '6' year: '2020' verification_value: '123' -realex_mastercard_coms_error: - number: +realex_visa_referral_b: + number: '4000130000001724' month: '6' year: '2020' verification_value: '123' -samurai: - login: 'a1ebafb6da5238fb8a3ac9f6' - password: 'ae1aa640f6b735c4730fbb56' - processor_token: '5a0e1ca1e5a11a2997bbf912' +realex_with_account: + login: X + password: Y + account: testaccount + +redsys: + login: MERCHANT CODE + secret_key: SECRET KEY + +redsys_sha256: + login: MERCHANT CODE + secret_key: SECRET KEY + signature_algorithm: 'sha256' + +# Working credentials, no need to replace +s5: + mode: CONNECTOR_TEST + sender: ff80808142b2c03c0142b7a7339603e0 + channel: ff80808142b2c03c0142b7a7339803e5 + login: 8a82941847c4d0780147cea1d1730dcc + password: n3yNMBGK + +# Working credentials, no need to replace +safe_charge: + client_login_id: 'SpreedlyTestTRX' + client_password: '5Jp5xKmgqY' + +safe_charge_three_ds: + client_login_id: 'SpreedlyManTestTRX' + client_password: 'iGx9DQQHQG' sage: - login: login - password: password + login: 214282982451 + password: 'Z5W2S8J7X8T5' + +sage_pay: + login: spreedly sallie_mae: login: TEST0 @@ -557,49 +1130,100 @@ secure_pay: login: LOGIN password: PASSWORD +secure_pay_au: + login: ABC0030 + password: abc123 + secure_pay_tech: login: TESTDIGISPL1 password: d557591484cb2cd12bba445aba420d2c69cd6a88 -secure_pay_au: - login: ABC0030 - password: abc123 +securion_pay: + secret_key: pr_test_qZN4VVIKCySfCeXCBoHO9DBe # Replace with your serial numbers for the skipjack test environment skipjack: login: X password: Y +so_easy_pay: + login: 110159 + password: b1fe9de37c234ef8fd53668377076687d6314dc8f6146023338d61b5969719e0 + # Working credentials, no need to replace spreedly_core: login: "4Y9bVkOCpYesPQOfDi7TXQyUw50" password: "Y2i7AjgU03SUjwY4xnOPqzdsv4dMbPDCQzorAk8Bcoy0U8EIVE4innGjuoMQv7MN" gateway_token: "3gLeg4726V5P0HK7cq7QzHsL0a6" +# Working credentials, no need to replace stripe: - login: - fee_refund_login: + login: sk_test_3OD4TdKSIOhDOL2146JJcC79 + +# Working credentials, no need to replace +stripe_destination: + stripe_user_id: "acct_17FRNfIPBJTitsen" + +# Externally verified bank account for testing +stripe_verified_bank_account: + customer_id: "cus_7s22nNueP2Hjj6" + bank_account_id: "ba_17cHxeAWOtgoysogv3NM8CJ1" + +# Working credentials, no need to replace +swipe_checkout: + login: 2077103073D8B5 + api_key: f2fe4efd5033edfaed9e4aad319ef4d34536a10eea07f90f182616d7216ae2b8 + region: NZ + +# Working credentials, no need to replace +telr: + merchant_id: 16715 + api_key: WZV7W#gbMVw^kSBk + +tns: + userid: TESTSPREEDLY01 + password: 3f34fe50334fbe6cbe04c283411a5860 + +tns_ap: + userid: TESTUNISOLMAA01 + password: b7f8119fda3bd27c17656badb52c95bb trans_first: - login: LOGIN + login: 45567 + password: TNYYKYMFZ59HSN7Q + +# Working credentials, no need to replace +trans_first_transaction_express: + gateway_id: 7777778764 + reg_key: M84PKPDMD5BY86HN + +# Transact Pro doesn't provide public testing data +transact_pro: + guid: GUID password: PASSWORD + terminal: TERMINALID + card_number: "TESTCARDNUMBER" + verification_value: "TESTCARDCVV" + month: "TESTCARDMONTH" + year: "TESTCARDYEAR" transax: login: transaxdemo password: nelix123 -transnational: - login: demo - password: password +# Working credentials, no need to replace +trexle: + api_key: "J5RGMpDlFlTfv9mEFvNWYoqHufyukPP4" # Working credentials, no need to replace trust_commerce: login: 'TestMerchant' password: 'password' + aggregator_id: 'abc123' # Working credentials, no need to replace usa_epay: - login: 'yCaWGYQsSVR0S48B6AKMK07RQhaxHvGu' + login: '4EoZ5U2Q55j976W7eplC71i6b7kn4pcV' # Get credentials here: https://www.usaepay.com/developer/login usa_epay_advanced: @@ -611,6 +1235,12 @@ valitor: login: WebsiteID password: SecurityNumber +# Working credentials, no need to replace +vanco: + user_id: SPREEDWS + password: v@nco2oo + client_id: SPREEDLY + # Working credentials, no need to replace verify: login: 'demo' @@ -621,22 +1251,52 @@ viaklix: password: PASSWORD user: USER -vindicia: - login: LOGIN - password: PASSWORD +# test credentails +visanet_peru: + access_key_id: "AKIAJLJTVQYHO4P76YYA" + secret_access_key: "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" + merchant_id: "543025501" + ruc: '20341198217' webpay: login: "test_secret_eHn4TTgsGguBcW764a2KA8Yd" # Working test credentials, no need to replace +wepay: + client_id: "44716" + account_id: "2080478981" + access_token: "STAGE_c91882b0bed3584b8aed0f7f515f2f05a1d40924ee6f394ce82d91018cb0f2d3" + client_secret: "d48fefe743" + +# Working test credentials with AVS/CVV support, no need to replace wirecard: - login: 56500 + login: 00000031629CA9FA password: TestXAPTER + signature: 00000031629CAFD5 + +wirecard_checkout_page: + secret: B8AKTPWBRMNBV455FG6M2DANE99WU2 + shop_id: '' + paymenttype: IDL + +# Working credentials, no need to replace +world_net: + terminal_id: '6001' + secret: 'sandboxEUR' world_pay_gateway: - login: LOGIN - password: PASSWORD + login: 'SPREEDLY' + password: 'KZ#P2aR+' -iats_payments: - login: TEST88 - password: TEST88 +world_pay_gateway_cft: + login: 'SPREEDLYCFT' + password: 'Xbf+6#pD' + +worldpay_online_payments: + client_key: "T_C_b9f629e7-cea7-4edb-8206-24bbe351d699" + service_key: "T_S_eea7c405-25a9-4428-918f-27a9d63fe64f" + +worldpay_us: + acctid: MPNAB + subid: SPREE + merchantpin: "1234567890" diff --git a/test/remote/gateways/remote_adyen_test.rb b/test/remote/gateways/remote_adyen_test.rb new file mode 100644 index 00000000000..584d9a1e96a --- /dev/null +++ b/test/remote/gateways/remote_adyen_test.rb @@ -0,0 +1,708 @@ +require 'test_helper' + +class RemoteAdyenTest < Test::Unit::TestCase + def setup + @gateway = AdyenGateway.new(fixtures(:adyen)) + + @amount = 100 + + @credit_card = credit_card('4111111111111111', + :month => 10, + :year => 2020, + :first_name => 'John', + :last_name => 'Smith', + :verification_value => '737', + :brand => 'visa' + ) + + @avs_credit_card = credit_card('4400000000000008', + :month => 10, + :year => 2020, + :first_name => 'John', + :last_name => 'Smith', + :verification_value => '737', + :brand => 'visa' + ) + + @elo_credit_card = credit_card('5066 9911 1111 1118', + :month => 10, + :year => 2020, + :first_name => 'John', + :last_name => 'Smith', + :verification_value => '737', + :brand => 'elo' + ) + + @three_ds_enrolled_card = credit_card('4917610000000000', brand: :visa) + + @declined_card = credit_card('4000300011112220') + + @improperly_branded_maestro = credit_card( + '5500000000000004', + month: 8, + year: 2018, + first_name: 'John', + last_name: 'Smith', + verification_value: '737', + brand: 'mastercard' + ) + + @apple_pay_card = network_tokenization_credit_card('4111111111111111', + :payment_cryptogram => 'YwAAAAAABaYcCMX/OhNRQAAAAAA=', + :month => '08', + :year => '2018', + :source => :apple_pay, + :verification_value => nil + ) + + @google_pay_card = network_tokenization_credit_card('4111111111111111', + :payment_cryptogram => 'YwAAAAAABaYcCMX/OhNRQAAAAAA=', + :month => '08', + :year => '2018', + :source => :google_pay, + :verification_value => nil + ) + + @options = { + reference: '345123', + shopper_email: 'john.smith@test.com', + shopper_ip: '77.110.174.153', + shopper_reference: 'John Smith', + billing_address: address(), + order_id: '123', + stored_credential: {reason_type: 'unscheduled'}, + } + + @normalized_3ds_2_options = { + reference: '345123', + shopper_email: 'john.smith@test.com', + shopper_ip: '77.110.174.153', + shopper_reference: 'John Smith', + billing_address: address(), + order_id: '123', + stored_credential: {reason_type: 'unscheduled'}, + three_ds_2: { + channel: 'browser', + notification_url: 'https://example.com/notification', + browser_info: { + accept_header: 'unknown', + depth: 100, + java: false, + language: 'US', + height: 1000, + width: 500, + timezone: '-120', + user_agent: 'unknown' + } + } + } + end + + def test_successful_authorize + response = @gateway.authorize(@amount, @credit_card, @options) + assert_success response + assert_equal 'Authorised', response.message + end + + def test_successful_authorize_avs + # Account configuration may need to be done: https://docs.adyen.com/developers/api-reference/payments-api#paymentresultadditionaldata + options = @options.update({ + billing_address: { + address1: 'Infinite Loop', + address2: 1, + country: 'US', + city: 'Cupertino', + state: 'CA', + zip: '95014' + } + }) + response = @gateway.authorize(@amount, @avs_credit_card, options) + assert_success response + assert_equal 'Authorised', response.message + assert_equal 'D', response.avs_result['code'] + end + + def test_successful_authorize_with_idempotency_key + options = @options.merge(idempotency_key: SecureRandom.hex) + response = @gateway.authorize(@amount, @credit_card, options) + assert_success response + assert_equal 'Authorised', response.message + first_auth = response.authorization + + response = @gateway.authorize(@amount, @credit_card, options) + assert_success response + assert_equal response.authorization, first_auth + end + + def test_successful_authorize_with_3ds + assert response = @gateway.authorize(@amount, @three_ds_enrolled_card, @options.merge(execute_threed: true)) + assert response.test? + refute response.authorization.blank? + assert_equal response.params['resultCode'], 'RedirectShopper' + refute response.params['issuerUrl'].blank? + refute response.params['md'].blank? + refute response.params['paRequest'].blank? + end + + def test_successful_authorize_with_3ds_dynamic + assert response = @gateway.authorize(@amount, @three_ds_enrolled_card, @options.merge(threed_dynamic: true)) + assert response.test? + refute response.authorization.blank? + assert_equal response.params['resultCode'], 'RedirectShopper' + refute response.params['issuerUrl'].blank? + refute response.params['md'].blank? + refute response.params['paRequest'].blank? + end + + def test_successful_authorize_with_3ds2_browser_client_data + assert response = @gateway.authorize(@amount, @three_ds_enrolled_card, @normalized_3ds_2_options) + assert response.test? + refute response.authorization.blank? + + assert_equal response.params['resultCode'], 'IdentifyShopper' + refute response.params['additionalData']['threeds2.threeDS2Token'].blank? + refute response.params['additionalData']['threeds2.threeDSServerTransID'].blank? + refute response.params['additionalData']['threeds2.threeDSMethodURL'].blank? + end + + def test_successful_authorize_with_3ds2_app_based_request + three_ds_app_based_options = { + reference: '345123', + shopper_email: 'john.smith@test.com', + shopper_ip: '77.110.174.153', + shopper_reference: 'John Smith', + billing_address: address(), + order_id: '123', + stored_credential: {reason_type: 'unscheduled'}, + three_ds_2: { + channel: 'app', + } + } + + assert response = @gateway.authorize(@amount, @three_ds_enrolled_card, three_ds_app_based_options) + assert response.test? + refute response.authorization.blank? + assert_equal response.params['resultCode'], 'IdentifyShopper' + refute response.params['additionalData']['threeds2.threeDS2Token'].blank? + refute response.params['additionalData']['threeds2.threeDSServerTransID'].blank? + refute response.params['additionalData']['threeds2.threeDS2DirectoryServerInformation.algorithm'].blank? + refute response.params['additionalData']['threeds2.threeDS2DirectoryServerInformation.directoryServerId'].blank? + refute response.params['additionalData']['threeds2.threeDS2DirectoryServerInformation.publicKey'].blank? + end + + # with rule set in merchant account to skip 3DS for cards of this brand + def test_successful_authorize_with_3ds_dynamic_rule_broken + mastercard_threed = credit_card('5212345678901234', + :month => 10, + :year => 2020, + :first_name => 'John', + :last_name => 'Smith', + :verification_value => '737', + :brand => 'mastercard' + ) + assert response = @gateway.authorize(@amount, mastercard_threed, @options.merge(threed_dynamic: true)) + assert response.test? + refute response.authorization.blank? + assert_equal response.params['resultCode'], 'Authorised' + end + + def test_successful_purchase_with_auth_data_via_threeds1_standalone + eci = '05' + cavv = '3q2+78r+ur7erb7vyv66vv\/\/\/\/8=' + cavv_algorithm = '1' + xid = 'ODUzNTYzOTcwODU5NzY3Qw==' + directory_response_status = 'Y' + authentication_response_status = 'Y' + options = @options.merge( + three_d_secure: { + eci: eci, + cavv: cavv, + cavv_algorithm: cavv_algorithm, + xid: xid, + directory_response_status: directory_response_status, + authentication_response_status: authentication_response_status + } + ) + + auth = @gateway.authorize(@amount, @credit_card, options) + assert_success auth + assert_equal 'Authorised', auth.message + assert_equal 'true', auth.params['additionalData']['liabilityShift'] + + response = @gateway.purchase(@amount, @credit_card, options) + assert_success response + assert_equal '[capture-received]', response.message + end + + def test_successful_purchase_with_auth_data_via_threeds2_standalone + version = '2.1.0' + eci = '02' + cavv = 'jJ81HADVRtXfCBATEp01CJUAAAA=' + ds_transaction_id = '97267598-FAE6-48F2-8083-C23433990FBC' + directory_response_status = 'C' + authentication_response_status = 'Y' + + options = @options.merge( + three_d_secure: { + version: version, + eci: eci, + cavv: cavv, + ds_transaction_id: ds_transaction_id, + directory_response_status: directory_response_status, + authentication_response_status: authentication_response_status + } + ) + + auth = @gateway.authorize(@amount, @credit_card, options) + assert_success auth + assert_equal 'Authorised', auth.message + assert_equal 'true', auth.params['additionalData']['liabilityShift'] + + response = @gateway.purchase(@amount, @credit_card, options) + assert_success response + end + + def test_successful_authorize_with_no_address + options = { + reference: '345123', + shopper_email: 'john.smith@test.com', + shopper_ip: '77.110.174.153', + shopper_reference: 'John Smith', + order_id: '123', + recurring_processing_model: 'CardOnFile' + } + response = @gateway.authorize(@amount, @credit_card, options) + assert_success response + assert_equal 'Authorised', response.message + end + + def test_failed_authorize + response = @gateway.authorize(@amount, @declined_card, @options) + assert_failure response + assert_equal 'CVC Declined', response.message + end + + def test_successful_purchase + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal '[capture-received]', response.message + end + + def test_successful_purchase_no_cvv + credit_card = @credit_card + credit_card.verification_value = nil + response = @gateway.purchase(@amount, credit_card, @options) + assert_success response + assert_equal '[capture-received]', response.message + end + + def test_successful_purchase_with_more_options + options = @options.merge!(fraudOffset: '1', installments: 2, shopper_statement: 'statement note', device_fingerprint: 'm7Cmrf++0cW4P6XfF7m/rA') + response = @gateway.purchase(@amount, @credit_card, options) + assert_success response + assert_equal '[capture-received]', response.message + end + + def test_successful_purchase_with_risk_data + options = @options.merge( + risk_data: + { + 'operatingSystem' => 'HAL9000', + 'destinationLatitude' => '77.641423', + 'destinationLongitude' => '12.9503376' + } + ) + + response = @gateway.purchase(@amount, @credit_card, options) + assert_success response + assert_equal '[capture-received]', response.message + end + + def test_successful_purchase_with_idempotency_key + options = @options.merge(idempotency_key: SecureRandom.hex) + response = @gateway.purchase(@amount, @credit_card, options) + assert_success response + assert_equal '[capture-received]', response.message + first_auth = response.authorization + + response = @gateway.purchase(@amount, @credit_card, options) + assert_success response + assert_equal response.authorization, first_auth + end + + def test_successful_purchase_with_apple_pay + response = @gateway.purchase(@amount, @apple_pay_card, @options) + assert_success response + assert_equal '[capture-received]', response.message + end + + def test_succesful_purchase_with_brand_override + response = @gateway.purchase(@amount, @improperly_branded_maestro, @options.merge({overwrite_brand: true, selected_brand: 'maestro'})) + assert_success response + assert_equal '[capture-received]', response.message + end + + def test_successful_purchase_with_google_pay + response = @gateway.purchase(@amount, @google_pay_card, @options) + assert_success response + assert_equal '[capture-received]', response.message + end + + def test_successful_purchase_with_elo_card + response = @gateway.purchase(@amount, @elo_credit_card, @options.merge(currency: 'BRL')) + assert_success response + assert_equal '[capture-received]', response.message + end + + def test_failed_purchase + response = @gateway.purchase(@amount, @declined_card, @options) + assert_failure response + assert_equal 'CVC Declined', response.message + end + + def test_successful_authorize_and_capture + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + assert capture = @gateway.capture(@amount, auth.authorization) + assert_success capture + assert_equal '[capture-received]', capture.message + end + + def test_successful_authorize_and_capture_with_elo_card + auth = @gateway.authorize(@amount, @elo_credit_card, @options) + assert_success auth + + assert capture = @gateway.capture(@amount, auth.authorization) + assert_success capture + assert_equal '[capture-received]', capture.message + end + + def test_partial_capture + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + assert capture = @gateway.capture(@amount-1, auth.authorization) + assert_success capture + end + + def test_failed_capture + response = @gateway.capture(@amount, '') + assert_failure response + assert_equal 'Original pspReference required for this operation', response.message + end + + def test_successful_refund + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + + assert refund = @gateway.refund(@amount, purchase.authorization) + assert_success refund + assert_equal '[refund-received]', refund.message + end + + def test_successful_refund_with_elo_card + purchase = @gateway.purchase(@amount, @elo_credit_card, @options) + assert_success purchase + + assert refund = @gateway.refund(@amount, purchase.authorization) + assert_success refund + assert_equal '[refund-received]', refund.message + end + + def test_partial_refund + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + + assert refund = @gateway.refund(@amount-1, purchase.authorization) + assert_success refund + end + + def test_failed_refund + response = @gateway.refund(@amount, '') + assert_failure response + assert_equal 'Original pspReference required for this operation', response.message + end + + def test_successful_void + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + assert void = @gateway.void(auth.authorization) + assert_success void + assert_equal '[cancel-received]', void.message + end + + def test_successful_void_with_elo_card + auth = @gateway.authorize(@amount, @elo_credit_card, @options) + assert_success auth + + assert void = @gateway.void(auth.authorization) + assert_success void + assert_equal '[cancel-received]', void.message + end + + def test_failed_void + response = @gateway.void('') + assert_failure response + assert_equal 'Original pspReference required for this operation', response.message + end + + def test_successful_asynchronous_adjust + authorize = @gateway.authorize(@amount, @credit_card, @options.merge(authorisation_type: 'PreAuth')) + assert_success authorize + + assert adjust = @gateway.adjust(200, authorize.authorization, @options) + assert_success adjust + assert_equal '[adjustAuthorisation-received]', adjust.message + end + + def test_successful_asynchronous_adjust_and_capture + authorize = @gateway.authorize(@amount, @credit_card, @options.merge(authorisation_type: 'PreAuth')) + assert_success authorize + + assert adjust = @gateway.adjust(200, authorize.authorization, @options) + assert_success adjust + assert_equal '[adjustAuthorisation-received]', adjust.message + + assert capture = @gateway.capture(200, authorize.authorization) + assert_success capture + end + + def test_failed_asynchronous_adjust + authorize = @gateway.authorize(@amount, @credit_card, @options.merge(authorisation_type: 'PreAuth')) + assert_success authorize + + assert response = @gateway.adjust(200, '', @options) + assert_failure response + assert_equal 'Original pspReference required for this operation', response.message + end + + # Requires Adyen to set your test account to Synchronous Adjust mode. + def test_successful_synchronous_adjust_using_adjust_data + authorize = @gateway.authorize(@amount, @credit_card, @options.merge(authorisation_type: 'PreAuth', shopper_statement: 'statement note')) + assert_success authorize + + options = @options.merge(adjust_authorisation_data: authorize.params['additionalData']['adjustAuthorisationData'], update_shopper_statement: 'new statement note', industry_usage: 'DelayedCharge') + assert adjust = @gateway.adjust(200, authorize.authorization, options) + assert_success adjust + assert_equal 'Authorised', adjust.message + end + + # Requires Adyen to set your test account to Synchronous Adjust mode. + def test_successful_synchronous_adjust_and_capture + authorize = @gateway.authorize(@amount, @credit_card, @options.merge(authorisation_type: 'PreAuth')) + assert_success authorize + + options = @options.merge(adjust_authorisation_data: authorize.params['additionalData']['adjustAuthorisationData']) + assert adjust = @gateway.adjust(200, authorize.authorization, options) + assert_success adjust + assert_equal 'Authorised', adjust.message + + assert capture = @gateway.capture(200, authorize.authorization) + assert_success capture + end + + # Requires Adyen to set your test account to Synchronous Adjust mode. + def test_failed_synchronous_adjust_using_adjust_data + authorize = @gateway.authorize(@amount, @credit_card, @options.merge(authorisation_type: 'PreAuth')) + assert_success authorize + + options = @options.merge(adjust_authorisation_data: authorize.params['additionalData']['adjustAuthorisationData'], + requested_test_acquirer_response_code: '2') + assert adjust = @gateway.adjust(200, authorize.authorization, options) + assert_failure adjust + assert_equal 'Refused', adjust.message + end + + def test_successful_store + assert response = @gateway.store(@credit_card, @options) + + assert_success response + assert !response.authorization.split('#')[2].nil? + assert_equal 'Authorised', response.message + end + + def test_successful_store_with_elo_card + assert response = @gateway.store(@elo_credit_card, @options) + + assert_success response + assert !response.authorization.split('#')[2].nil? + assert_equal 'Authorised', response.message + end + + def test_failed_store + assert response = @gateway.store(@declined_card, @options) + + assert_failure response + assert_equal 'CVC Declined', response.message + end + + def test_successful_purchase_using_stored_card + assert store_response = @gateway.store(@credit_card, @options) + assert_success store_response + + response = @gateway.purchase(@amount, store_response.authorization, @options) + assert_success response + assert_equal '[capture-received]', response.message + end + + def test_successful_purchase_using_stored_elo_card + assert store_response = @gateway.store(@elo_credit_card, @options) + assert_success store_response + + response = @gateway.purchase(@amount, store_response.authorization, @options) + assert_success response + assert_equal '[capture-received]', response.message + end + + def test_successful_authorize_using_stored_card + assert store_response = @gateway.store(@credit_card, @options) + assert_success store_response + + response = @gateway.authorize(@amount, store_response.authorization, @options) + assert_success response + assert_equal 'Authorised', response.message + end + + def test_successful_verify + response = @gateway.verify(@credit_card, @options) + assert_success response + assert_match 'Authorised', response.message + end + + def test_failed_verify + response = @gateway.verify(@declined_card, @options) + assert_failure response + assert_match 'CVC Declined', response.message + end + + def test_verify_with_idempotency_key + options = @options.merge(idempotency_key: SecureRandom.hex) + response = @gateway.authorize(0, @credit_card, options) + assert_success response + assert_equal 'Authorised', response.message + first_auth = response.authorization + + response = @gateway.verify(@credit_card, options) + assert_success response + assert_equal response.authorization, first_auth + + response = @gateway.void(first_auth, @options) + assert_success response + end + + def test_invalid_login + gateway = AdyenGateway.new(username: '', password: '', merchant_account: '') + + response = gateway.purchase(@amount, @credit_card, @options) + assert_failure response + end + + def test_transcript_scrubbing + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @credit_card, @options) + end + transcript = @gateway.scrub(transcript) + + assert_scrubbed(@credit_card.number, transcript) + assert_scrubbed(@credit_card.verification_value, transcript) + assert_scrubbed(@gateway.options[:password], transcript) + end + + def test_transcript_scrubbing_network_tokenization_card + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @apple_pay_card, @options) + end + transcript = @gateway.scrub(transcript) + + assert_scrubbed(@apple_pay_card.number, transcript) + assert_scrubbed(@apple_pay_card.payment_cryptogram, transcript) + assert_scrubbed(@gateway.options[:password], transcript) + end + + def test_incorrect_number_for_purchase + card = credit_card('4242424242424241') + assert response = @gateway.purchase(@amount, card, @options) + assert_failure response + assert_match Gateway::STANDARD_ERROR_CODE[:incorrect_number], response.error_code + end + + def test_invalid_number_for_purchase + card = credit_card('-1') + assert response = @gateway.purchase(@amount, card, @options) + assert_failure response + assert_match Gateway::STANDARD_ERROR_CODE[:incorrect_number], response.error_code + end + + def test_invalid_expiry_month_for_purchase + card = credit_card('4242424242424242', month: 16) + assert response = @gateway.purchase(@amount, card, @options) + assert_failure response + assert_equal 'Expiry Date Invalid: Expiry month should be between 1 and 12 inclusive', response.message + end + + def test_invalid_expiry_year_for_purchase + card = credit_card('4242424242424242', year: 'xx') + assert response = @gateway.purchase(@amount, card, @options) + assert_failure response + assert response.message.include?('Expiry year should be a 4 digit number greater than') + end + + def test_invalid_cvc_for_purchase + card = credit_card('4242424242424242', verification_value: -1) + assert response = @gateway.purchase(@amount, card, @options) + assert_failure response + assert_match Gateway::STANDARD_ERROR_CODE[:invalid_cvc], response.error_code + end + + def test_missing_address_for_purchase + @options[:billing_address].delete(:address1) + @options[:billing_address].delete(:address2) + response = @gateway.authorize(@amount, @credit_card, @options) + assert_success response + end + + def test_missing_city_for_purchase + @options[:billing_address].delete(:city) + response = @gateway.authorize(@amount, @credit_card, @options) + assert_success response + end + + def test_missing_house_number_or_name_for_purchase + @options[:billing_address].delete(:address2) + response = @gateway.authorize(@amount, @credit_card, @options) + assert_success response + end + + def test_missing_state_for_purchase + @options[:billing_address].delete(:state) + response = @gateway.authorize(@amount, @credit_card, @options) + assert_success response + end + + def test_blank_country_for_purchase + @options[:billing_address][:country] = '' + response = @gateway.authorize(@amount, @credit_card, @options) + assert_failure response + assert_match Gateway::STANDARD_ERROR_CODE[:incorrect_address], response.error_code + end + + def test_nil_state_for_purchase + @options[:billing_address][:state] = nil + response = @gateway.authorize(@amount, @credit_card, @options) + assert_success response + end + + def test_blank_state_for_purchase + @options[:billing_address][:state] = '' + response = @gateway.authorize(@amount, @credit_card, @options) + assert_success response + end + + def test_missing_phone_for_purchase + @options[:billing_address].delete(:phone) + response = @gateway.authorize(@amount, @credit_card, @options) + assert_success response + end +end diff --git a/test/remote/gateways/remote_allied_wallet_test.rb b/test/remote/gateways/remote_allied_wallet_test.rb new file mode 100644 index 00000000000..8ab761e508b --- /dev/null +++ b/test/remote/gateways/remote_allied_wallet_test.rb @@ -0,0 +1,149 @@ +require 'test_helper' + +class RemoteAlliedWalletTest < Test::Unit::TestCase + def setup + @gateway = AlliedWalletGateway.new(fixtures(:allied_wallet)) + + @amount = 100 + @credit_card = credit_card + @declined_card = credit_card('4242424242424242', verification_value: '555') + + @options = { + billing_address: address, + } + end + + def test_successful_purchase + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal 'Succeeded', response.message + end + + def test_failed_purchase + response = @gateway.purchase(@amount, @declined_card, @options) + assert_failure response + assert_equal 'The test operation was declined.', response.message + end + + def test_failed_purchase_no_address + response = @gateway.purchase(@amount, @declined_card) + assert_failure response + assert_match(/Address.* should not be empty/, response.message) + end + + def test_successful_purchase_with_more_options + response = @gateway.purchase(@amount, @credit_card, @options.merge( + order_id: generate_unique_id, + ip: '127.0.0.1', + email: 'jim_smith@example.com' + )) + assert_success response + assert_equal 'Succeeded', response.message + end + + def test_successful_authorize_and_capture + response = @gateway.authorize(@amount, @credit_card, @options) + assert_success response + assert_equal 'Succeeded', response.message + assert response.authorization + + capture = @gateway.capture(@amount, response.authorization) + assert_success capture + assert_equal 'Succeeded', capture.message + end + + def test_failed_authorize + response = @gateway.authorize(@amount, @declined_card, @options) + assert_failure response + assert_equal 'The test operation was declined.', response.message + end + + def test_failed_capture + response = @gateway.capture(@amount, '') + assert_failure response + assert_equal "'Authorize Transaction Id' should not be empty.", response.message + end + + def test_successful_void + response = @gateway.authorize(@amount, @credit_card, @options) + assert_success response + + void = @gateway.void(response.authorization) + assert_success void + assert_equal 'Succeeded', void.message + end + + def test_failed_void + response = @gateway.void('') + assert_failure response + assert_equal "'Authorize Transaction Id' should not be empty.", response.message + end + + def test_successful_refund + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + + refund = @gateway.refund(@amount, response.authorization) + assert_success refund + assert_equal 'Succeeded', refund.message + end + + def test_failed_refund + response = @gateway.refund(@amount, 'UnknownAuthorization') + assert_failure response + assert_match(/An internal exception has occurred/, response.message) + end + + def test_successful_verify + response = @gateway.verify(@credit_card, @options) + assert_success response + assert_match %r{Succeeded}, response.message + end + + def test_failed_verify + response = @gateway.verify(@declined_card, @options) + assert_failure response + assert_equal 'The test operation was declined.', response.message + end + + def test_transcript_scrubbing + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @credit_card, @options) + end + clean_transcript = @gateway.scrub(transcript) + + assert_scrubbed(@gateway.options[:token], clean_transcript) + assert_scrubbed(@credit_card.number, clean_transcript) + assert_scrubbed(@credit_card.verification_value.to_s, clean_transcript) + end + + def test_nil_cvv_transcript_scrubbing + @credit_card.verification_value = nil + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @credit_card, @options) + end + transcript = @gateway.scrub(transcript) + + assert_equal transcript.include?('\"cVVCode\":[BLANK]'), true + end + + def test_empty_string_cvv_transcript_scrubbing + @credit_card.verification_value = '' + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @credit_card, @options) + end + transcript = @gateway.scrub(transcript) + + assert_equal transcript.include?('\"cVVCode\":\"[BLANK]'), true + end + + def test_whitespace_string_cvv_transcript_scrubbing + @credit_card.verification_value = ' ' + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @credit_card, @options) + end + transcript = @gateway.scrub(transcript) + + assert_equal transcript.include?('\"cVVCode\":\"[BLANK]'), true + end +end diff --git a/test/remote/gateways/remote_authorize_net_apple_pay_test.rb b/test/remote/gateways/remote_authorize_net_apple_pay_test.rb new file mode 100644 index 00000000000..cf430b4a975 --- /dev/null +++ b/test/remote/gateways/remote_authorize_net_apple_pay_test.rb @@ -0,0 +1,92 @@ +require 'test_helper' + +class RemoteAuthorizeNetApplePayTest < Test::Unit::TestCase + def setup + @gateway = AuthorizeNetGateway.new(fixtures(:authorize_net)) + + @amount = 100 + @apple_pay_payment_token = apple_pay_payment_token + + @options = { + order_id: '1', + duplicate_window: 0, + billing_address: address, + description: 'Store Purchase' + } + end + + def test_successful_apple_pay_authorization + response = @gateway.authorize(5, @apple_pay_payment_token, @options) + assert_success response + assert_equal 'This transaction has been approved', response.message + assert response.authorization + end + + def test_successful_apple_pay_purchase + response = @gateway.purchase(5, @apple_pay_payment_token, @options) + assert_success response + assert_equal 'This transaction has been approved', response.message + end + + def test_successful_apple_pay_authorization_and_capture + assert authorization = @gateway.authorize(@amount, @apple_pay_payment_token, @options) + assert_success authorization + + assert capture = @gateway.capture(@amount, authorization.authorization) + assert_success capture + assert_equal 'This transaction has been approved', capture.message + end + + def test_successful_apple_pay_authorization_and_void + assert authorization = @gateway.authorize(@amount, @apple_pay_payment_token, @options) + assert_success authorization + + assert void = @gateway.void(authorization.authorization) + assert_success void + assert_equal 'This transaction has been approved', void.message + end + + def test_failed_apple_pay_authorization + response = @gateway.authorize(@amount, apple_pay_payment_token(payment_data: {data: 'garbage'}), @options) + assert_failure response + assert_equal 'There was an error processing the payment data', response.message + assert_equal 'processing_error', response.error_code + end + + def test_failed_apple_pay_purchase + response = @gateway.purchase(@amount, apple_pay_payment_token(payment_data: {data: 'garbage'}), @options) + assert_failure response + assert_equal 'There was an error processing the payment data', response.message + assert_equal 'processing_error', response.error_code + end + + private + + def apple_pay_payment_token(options = {}) + # The payment_data field below is sourced from: http://developer.authorize.net/api/reference/#apple-pay-transactions + # Other fields are motivated by https://developer.apple.com/library/ios/documentation/PassKit/Reference/PKPaymentToken_Ref/index.html + defaults = { + payment_data: { + 'data' => 'BDPNWStMmGewQUWGg4o7E/j+1cq1T78qyU84b67itjcYI8wPYAOhshjhZPrqdUr4XwPMbj4zcGMdy++1H2VkPOY+BOMF25ub19cX4nCvkXUUOTjDllB1TgSr8JHZxgp9rCgsSUgbBgKf60XKutXf6aj/o8ZIbKnrKQ8Sh0ouLAKloUMn+vPu4+A7WKrqrauz9JvOQp6vhIq+HKjUcUNCITPyFhmOEtq+H+w0vRa1CE6WhFBNnCHqzzWKckB/0nqLZRTYbF0p+vyBiVaWHeghERfHxRtbzpeczRPPuFsfpHVs48oPLC/k/1MNd47kz/pHDcR/Dy6aUM+lNfoily/QJN+tS3m0HfOtISAPqOmXemvr6xJCjCZlCuw0C9mXz/obHpofuIES8r9cqGGsUAPDpw7g642m4PzwKF+HBuYUneWDBNSD2u6jbAG3', + 'version' => 'EC_v1', + 'header' => { + 'applicationData' => '94ee059335e587e501cc4bf90613e0814f00a7b08bc7c648fd865a2af6a22cc2', + 'transactionId' => 'c1caf5ae72f0039a82bad92b828363734f85bf2f9cadf193d1bad9ddcb60a795', + 'ephemeralPublicKey' => 'MIIBSzCCAQMGByqGSM49AgEwgfcCAQEwLAYHKoZIzj0BAQIhAP////8AAAABAAAAAAAAAAAAAAAA////////////////MFsEIP////8AAAABAAAAAAAAAAAAAAAA///////////////8BCBaxjXYqjqT57PrvVV2mIa8ZR0GsMxTsPY7zjw+J9JgSwMVAMSdNgiG5wSTamZ44ROdJreBn36QBEEEaxfR8uEsQkf4vOblY6RA8ncDfYEt6zOg9KE5RdiYwpZP40Li/hp/m47n60p8D54WK84zV2sxXs7LtkBoN79R9QIhAP////8AAAAA//////////+85vqtpxeehPO5ysL8YyVRAgEBA0IABGm+gsl0PZFT/kDdUSkxwyfo8JpwTQQzBm9lJJnmTl4DGUvAD4GseGj/pshBZ0K3TeuqDt/tDLbE+8/m0yCmoxw=', + 'publicKeyHash' => '/bb9CNC36uBheHFPbmohB7Oo1OsX2J+kJqv48zOVViQ=' + }, + 'signature' => 'MIIDQgYJKoZIhvcNAQcCoIIDMzCCAy8CAQExCzAJBgUrDgMCGgUAMAsGCSqGSIb3DQEHAaCCAiswggInMIIBlKADAgECAhBcl+Pf3+U4pk13nVD9nwQQMAkGBSsOAwIdBQAwJzElMCMGA1UEAx4cAGMAaABtAGEAaQBAAHYAaQBzAGEALgBjAG8AbTAeFw0xNDAxMDEwNjAwMDBaFw0yNDAxMDEwNjAwMDBaMCcxJTAjBgNVBAMeHABjAGgAbQBhAGkAQAB2AGkAcwBhAC4AYwBvAG0wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBANC8+kgtgmvWF1OzjgDNrjTEBRuo/5MKvlM146pAf7Gx41blE9w4fIXJAD7FfO7QKjIXYNt39rLyy7xDwb/5IkZM60TZ2iI1pj55Uc8fd4fzOpk3ftZaQGXNLYptG1d9V7IS82Oup9MMo1BPVrXTPHNcsM99EPUnPqdbeGc87m0rAgMBAAGjXDBaMFgGA1UdAQRRME+AEHZWPrWtJd7YZ431hCg7YFShKTAnMSUwIwYDVQQDHhwAYwBoAG0AYQBpAEAAdgBpAHMAYQAuAGMAbwBtghBcl+Pf3+U4pk13nVD9nwQQMAkGBSsOAwIdBQADgYEAbUKYCkuIKS9QQ2mFcMYREIm2l+Xg8/JXv+GBVQJkOKoscY4iNDFA/bQlogf9LLU84THwNRnsvV3Prv7RTY81gq0dtC8zYcAaAkCHII3yqMnJ4AOu6EOW9kJk232gSE7WlCtHbfLSKfuSgQX8KXQYuZLk2Rr63N8ApXsXwBL3cJ0xgeAwgd0CAQEwOzAnMSUwIwYDVQQDHhwAYwBoAG0AYQBpAEAAdgBpAHMAYQAuAGMAbwBtAhBcl+Pf3+U4pk13nVD9nwQQMAkGBSsOAwIaBQAwDQYJKoZIhvcNAQEBBQAEgYBaK3ElOstbH8WooseDABf+Jg/129JcIawm7c6Vxn7ZasNbAq3tAt8Pty+uQCgssXqZkLA7kz2GzMolNtv9wYmu9Ujwar1PHYS+B/oGnoz591wjagXWRz0nMo5y3O1KzX0d8CRHAVa88SrV1a5JIiRev3oStIqwv5xuZldag6Tr8w==' + }, + payment_instrument_name: 'SomeBank Points Card', + payment_network: 'MasterCard', + transaction_identifier: 'uniqueidentifier123' + }.update(options) + + ActiveMerchant::Billing::ApplePayPaymentToken.new(defaults[:payment_data], + payment_instrument_name: defaults[:payment_instrument_name], + payment_network: defaults[:payment_network], + transaction_identifier: defaults[:transaction_identifier] + ) + end + +end diff --git a/test/remote/gateways/remote_authorize_net_arb_test.rb b/test/remote/gateways/remote_authorize_net_arb_test.rb new file mode 100644 index 00000000000..5a04f9de419 --- /dev/null +++ b/test/remote/gateways/remote_authorize_net_arb_test.rb @@ -0,0 +1,60 @@ +require 'test_helper' + +class AuthorizeNetArbTest < Test::Unit::TestCase + def setup + @gateway = AuthorizeNetArbGateway.new(fixtures(:authorize_net)) + @amount = 100 + @credit_card = credit_card('4242424242424242') + @check = check + + @options = { + :amount => 100, + :subscription_name => 'Test Subscription 1', + :credit_card => @credit_card, + :billing_address => address.merge(:first_name => 'Jim', :last_name => 'Smith'), + :interval => { + :length => 1, + :unit => :months + }, + :duration => { + :start_date => Date.today, + :occurrences => 1 + } + } + end + + def test_successful_recurring + assert response = @gateway.recurring(@amount, @credit_card, @options) + assert_success response + assert response.test? + + subscription_id = response.authorization + + assert response = @gateway.update_recurring(:subscription_id => subscription_id, :amount => @amount * 2) + assert_success response + + assert response = @gateway.status_recurring(subscription_id) + assert_success response + + assert response = @gateway.cancel_recurring(subscription_id) + assert_success response + end + + def test_recurring_should_fail_expired_credit_card + @credit_card.year = 2004 + assert response = @gateway.recurring(@amount, @credit_card, @options) + assert_failure response + assert response.test? + assert_equal 'E00018', response.params['code'] + end + + def test_bad_login + gateway = AuthorizeNetArbGateway.new( + :login => 'X', + :password => 'Y' + ) + + assert response = gateway.recurring(@amount, @credit_card, @options) + assert_failure response + end +end diff --git a/test/remote/gateways/remote_authorize_net_cim_test.rb b/test/remote/gateways/remote_authorize_net_cim_test.rb index 24a173d4dee..124b81c7753 100644 --- a/test/remote/gateways/remote_authorize_net_cim_test.rb +++ b/test/remote/gateways/remote_authorize_net_cim_test.rb @@ -100,11 +100,11 @@ def test_successful_create_customer_profile_transaction_auth_only_and_then_captu assert response.test? assert_success response assert_equal response.authorization, response.params['direct_response']['transaction_id'] - assert_equal "This transaction has been approved.", response.params['direct_response']['message'] + assert_match %r{(?:(TESTMODE) )?This transaction has been approved.}, response.params['direct_response']['message'] assert response.params['direct_response']['approval_code'] =~ /\w{6}/ - assert_equal "auth_only", response.params['direct_response']['transaction_type'] - assert_equal "100.00", response.params['direct_response']['amount'] - assert_match /\d+/, response.params['direct_response']['transaction_id'] + assert_equal 'auth_only', response.params['direct_response']['transaction_type'] + assert_equal '100.00', response.params['direct_response']['amount'] + assert_match %r{\d+}, response.params['direct_response']['transaction_id'] approval_code = response.params['direct_response']['approval_code'] @@ -123,10 +123,10 @@ def test_successful_create_customer_profile_transaction_auth_only_and_then_captu assert response.test? assert_success response assert_equal response.authorization, response.params['direct_response']['transaction_id'] - assert_equal "This transaction has been approved.", response.params['direct_response']['message'] + assert_match %r{(?:(TESTMODE) )?This transaction has been approved.}, response.params['direct_response']['message'] assert_equal approval_code, response.params['direct_response']['approval_code'] - assert_equal "capture_only", response.params['direct_response']['transaction_type'] - assert_equal "100.00", response.params['direct_response']['amount'] + assert_equal 'capture_only', response.params['direct_response']['transaction_type'] + assert_equal '100.00', response.params['direct_response']['amount'] end def test_successful_create_customer_profile_transaction_auth_capture_request @@ -146,6 +146,7 @@ def test_successful_create_customer_profile_transaction_auth_capture_request :description => 'Test Order Description', :purchase_order_number => '4321' }, + :recurring_billing => true, :card_code => '900', # authorize.net says this is a matching CVV :amount => @amount } @@ -154,10 +155,10 @@ def test_successful_create_customer_profile_transaction_auth_capture_request assert response.test? assert_success response assert_equal response.authorization, response.params['direct_response']['transaction_id'] - assert_equal "This transaction has been approved.", response.params['direct_response']['message'] + assert_match %r{(?:(TESTMODE) )?This transaction has been approved.}, response.params['direct_response']['message'] assert response.params['direct_response']['approval_code'] =~ /\w{6}/ - assert_equal "auth_capture", response.params['direct_response']['transaction_type'] - assert_equal "100.00", response.params['direct_response']['amount'] + assert_equal 'auth_capture', response.params['direct_response']['transaction_type'] + assert_equal '100.00', response.params['direct_response']['amount'] assert_equal response.params['direct_response']['invoice_number'], '1234' assert_equal response.params['direct_response']['order_description'], 'Test Order Description' assert_equal response.params['direct_response']['purchase_order_number'], '4321' @@ -184,7 +185,7 @@ def test_successful_create_customer_payment_profile_request end def test_successful_create_customer_payment_profile_request_with_bank_account - payment_profile = @options[:profile].delete(:payment_profiles) + @options[:profile].delete(:payment_profiles) assert response = @gateway.create_customer_profile(@options) @customer_profile_id = response.authorization @@ -253,7 +254,7 @@ def test_successful_get_customer_profile_with_multiple_payment_profiles assert response = @gateway.create_customer_profile(@options) @customer_profile_id = response.authorization - assert response = @gateway.get_customer_profile(:customer_profile_id => @customer_profile_id) + assert @gateway.get_customer_profile(:customer_profile_id => @customer_profile_id) assert response = @gateway.create_customer_payment_profile( :customer_profile_id => @customer_profile_id, @@ -330,6 +331,29 @@ def test_successful_get_customer_payment_profile_request assert response.params['payment_profile']['customer_payment_profile_id'] =~ /\d+/, 'The customer_payment_profile_id should be a number' assert_equal "XXXX#{@credit_card.last_digits}", response.params['payment_profile']['payment']['credit_card']['card_number'], "The card number should contain the last 4 digits of the card we passed in #{@credit_card.last_digits}" assert_equal @profile[:payment_profiles][:customer_type], response.params['payment_profile']['customer_type'] + assert_equal 'XXXX', response.params['payment_profile']['payment']['credit_card']['expiration_date'] + end + + def test_successful_get_customer_payment_profile_unmasked_request + assert response = @gateway.create_customer_profile(@options) + @customer_profile_id = response.authorization + + assert response = @gateway.get_customer_profile(:customer_profile_id => @customer_profile_id) + assert customer_payment_profile_id = response.params['profile']['payment_profiles']['customer_payment_profile_id'] + + assert response = @gateway.get_customer_payment_profile( + :customer_profile_id => @customer_profile_id, + :customer_payment_profile_id => customer_payment_profile_id, + :unmask_expiration_date => true + ) + + assert response.test? + assert_success response + assert_nil response.authorization + assert response.params['payment_profile']['customer_payment_profile_id'] =~ /\d+/, 'The customer_payment_profile_id should be a number' + assert_equal "XXXX#{@credit_card.last_digits}", response.params['payment_profile']['payment']['credit_card']['card_number'], "The card number should contain the last 4 digits of the card we passed in #{@credit_card.last_digits}" + assert_equal @profile[:payment_profiles][:customer_type], response.params['payment_profile']['customer_type'] + assert_equal formatted_expiration_date(@credit_card), response.params['payment_profile']['payment']['credit_card']['expiration_date'] end def test_successful_get_customer_shipping_address_request @@ -367,7 +391,7 @@ def test_successful_update_customer_payment_profile_request ) # The value before updating - assert_equal "XXXX4242", response.params['payment_profile']['payment']['credit_card']['card_number'], "The card number should contain the last 4 digits of the card we passed in 4242" + assert_equal 'XXXX4242', response.params['payment_profile']['payment']['credit_card']['card_number'], 'The card number should contain the last 4 digits of the card we passed in 4242' # Update the payment profile assert response = @gateway.update_customer_payment_profile( @@ -390,7 +414,7 @@ def test_successful_update_customer_payment_profile_request ) # Show that the payment profile was updated - assert_equal "XXXX1234", response.params['payment_profile']['payment']['credit_card']['card_number'], "The card number should contain the last 4 digits of the card we passed in: 1234" + assert_equal 'XXXX1234', response.params['payment_profile']['payment']['credit_card']['card_number'], 'The card number should contain the last 4 digits of the card we passed in: 1234' # Show that fields that were left out of the update were cleared assert_nil response.params['payment_profile']['customer_type'] @@ -399,7 +423,7 @@ def test_successful_update_customer_payment_profile_request masked_credit_card = ActiveMerchant::Billing::CreditCard.new(:number => response.params['payment_profile']['payment']['credit_card']['card_number']) # Update only the billing address with a masked card and expiration date - assert response = @gateway.update_customer_payment_profile( + assert @gateway.update_customer_payment_profile( :customer_profile_id => @customer_profile_id, :payment_profile => { :customer_payment_profile_id => customer_payment_profile_id, @@ -417,7 +441,53 @@ def test_successful_update_customer_payment_profile_request ) # Show that the billing address on the payment profile was updated - assert_equal "Frank", response.params['payment_profile']['bill_to']['first_name'], "The billing address should contain the first name we passed in: Frank" + assert_equal 'Frank', response.params['payment_profile']['bill_to']['first_name'], 'The billing address should contain the first name we passed in: Frank' + end + + def test_successful_update_customer_payment_profile_request_with_credit_card_last_four + # Create a new Customer Profile with Payment Profile + assert response = @gateway.create_customer_profile(@options) + @customer_profile_id = response.authorization + + # Get the customerPaymentProfileId + assert response = @gateway.get_customer_profile(:customer_profile_id => @customer_profile_id) + assert customer_payment_profile_id = response.params['profile']['payment_profiles']['customer_payment_profile_id'] + + # Get the customerPaymentProfile + assert response = @gateway.get_customer_payment_profile( + :customer_profile_id => @customer_profile_id, + :customer_payment_profile_id => customer_payment_profile_id + ) + + # Card number last 4 digits is 4242 + assert_equal 'XXXX4242', response.params['payment_profile']['payment']['credit_card']['card_number'], 'The card number should contain the last 4 digits of the card we passed in 4242' + + new_billing_address = response.params['payment_profile']['bill_to'] + new_billing_address.update(:first_name => 'Frank', :last_name => 'Brown') + + # Initialize credit card with only last 4 digits as the number + last_four_credit_card = ActiveMerchant::Billing::CreditCard.new(:number => '4242') # Credit card with only last four digits + + # Update only the billing address with a card with the last 4 digits and expiration date + assert @gateway.update_customer_payment_profile( + :customer_profile_id => @customer_profile_id, + :payment_profile => { + :customer_payment_profile_id => customer_payment_profile_id, + :bill_to => new_billing_address, + :payment => { + :credit_card => last_four_credit_card + } + } + ) + + # Get the updated payment profile + assert response = @gateway.get_customer_payment_profile( + :customer_profile_id => @customer_profile_id, + :customer_payment_profile_id => customer_payment_profile_id + ) + + # Show that the billing address on the payment profile was updated + assert_equal 'Frank', response.params['payment_profile']['bill_to']['first_name'], 'The billing address should contain the first name we passed in: Frank' end def test_successful_update_customer_shipping_address_request @@ -437,7 +507,7 @@ def test_successful_update_customer_shipping_address_request assert address = response.params['address'] # The value before updating - assert_equal "1234 Fake Street", address['address'] + assert_equal '1234 Fake Street', address['address'] # Update the address and remove the phone_number new_address = address.symbolize_keys.merge!( @@ -445,7 +515,7 @@ def test_successful_update_customer_shipping_address_request ) new_address.delete(:phone_number) - #Update the shipping address + # Update the shipping address assert response = @gateway.update_customer_shipping_address( :customer_profile_id => @customer_profile_id, :address => new_address @@ -461,7 +531,7 @@ def test_successful_update_customer_shipping_address_request ) # Show that the shipping address was updated - assert_equal "5678 Fake Street", response.params['address']['address'] + assert_equal '5678 Fake Street', response.params['address']['address'] # Show that fields that were left out of the update were cleared assert_nil response.params['address']['phone_number'] end @@ -484,7 +554,7 @@ def test_successful_validate_customer_payment_profile_request_live assert response.test? assert_success response assert_equal response.authorization, response.params['direct_response']['transaction_id'] - assert_equal "This transaction has been approved.", response.params['direct_response']['message'] + assert_match %r{(?:(TESTMODE) )?This transaction has been approved.}, response.params['direct_response']['message'] end def test_validate_customer_payment_profile_request_live_requires_billing_address @@ -505,7 +575,7 @@ def test_validate_customer_payment_profile_request_live_requires_billing_address assert response.test? assert_failure response - assert_equal "There is one or more missing or invalid required fields.", response.message + assert_equal 'There is one or more missing or invalid required fields.', response.message end def test_validate_customer_payment_profile_request_old_does_not_require_billing_address @@ -526,7 +596,7 @@ def test_validate_customer_payment_profile_request_old_does_not_require_billing_ assert response.test? assert_success response - assert_equal "Successful.", response.message + assert_equal 'Successful.', response.message end def test_should_create_duplicate_customer_profile_transactions_with_duplicate_window_alteration @@ -545,23 +615,59 @@ def test_should_create_duplicate_customer_profile_transactions_with_duplicate_wi :type => :auth_capture, :order => { :invoice_number => key.to_s, - :description => "Test Order Description #{key.to_s}", + :description => "Test Order Description #{key}", :purchase_order_number => key.to_s }, :amount => @amount }, - :extra_options => { "x_duplicate_window" => 1 } + :extra_options => { 'x_duplicate_window' => 1 } } assert response = @gateway.create_customer_profile_transaction(customer_profile_transaction) assert_success response - assert_equal "Successful.", response.message + assert_equal 'Successful.', response.message sleep(5) assert response = @gateway.create_customer_profile_transaction(customer_profile_transaction) assert_success response - assert_equal "Successful.", response.message + assert_equal 'Successful.', response.message + assert_nil response.error_code + end + + def test_should_not_create_duplicate_customer_profile_transactions_without_duplicate_window_alteration + assert response = @gateway.create_customer_profile(@options) + @customer_profile_id = response.authorization + + assert response = @gateway.get_customer_profile(:customer_profile_id => @customer_profile_id) + assert @customer_payment_profile_id = response.params['profile']['payment_profiles']['customer_payment_profile_id'] + + key = (Time.now.to_f * 1000000).to_i.to_s + + customer_profile_transaction = { + :transaction => { + :customer_profile_id => @customer_profile_id, + :customer_payment_profile_id => @customer_payment_profile_id, + :type => :auth_capture, + :order => { + :invoice_number => key.to_s, + :description => "Test Order Description #{key}", + :purchase_order_number => key.to_s + }, + :amount => @amount + } + } + + assert response = @gateway.create_customer_profile_transaction(customer_profile_transaction) + assert_success response + assert_equal 'Successful.', response.message + + sleep(5) + + assert response = @gateway.create_customer_profile_transaction(customer_profile_transaction) + assert_failure response + assert_equal 'A duplicate transaction has been submitted.', response.message + assert_equal 'E00027', response.error_code end def test_should_create_customer_profile_transaction_auth_capture_and_then_void_request @@ -660,7 +766,7 @@ def test_should_create_customer_profile_transaction_auth_only_and_then_prior_aut end def get_and_validate_customer_payment_profile_request_with_bank_account_response - payment_profile = @options[:profile].delete(:payment_profiles) + @options[:profile].delete(:payment_profiles) assert response = @gateway.create_customer_profile(@options) @customer_profile_id = response.authorization @@ -715,7 +821,7 @@ def get_and_validate_auth_capture_response :type => :auth_capture, :order => { :invoice_number => key.to_s, - :description => "Test Order Description #{key.to_s}", + :description => "Test Order Description #{key}", :purchase_order_number => key.to_s }, :amount => @amount @@ -725,12 +831,12 @@ def get_and_validate_auth_capture_response assert response.test? assert_success response assert_equal response.authorization, response.params['direct_response']['transaction_id'] - assert_equal "This transaction has been approved.", response.params['direct_response']['message'] + assert_match %r{(?:(TESTMODE) )?This transaction has been approved.}, response.params['direct_response']['message'] assert response.params['direct_response']['approval_code'] =~ /\w{6}/ - assert_equal "auth_capture", response.params['direct_response']['transaction_type'] - assert_equal "100.00", response.params['direct_response']['amount'] + assert_equal 'auth_capture', response.params['direct_response']['transaction_type'] + assert_equal '100.00', response.params['direct_response']['amount'] assert_equal response.params['direct_response']['invoice_number'], key.to_s - assert_equal response.params['direct_response']['order_description'], "Test Order Description #{key.to_s}" + assert_equal response.params['direct_response']['order_description'], "Test Order Description #{key}" assert_equal response.params['direct_response']['purchase_order_number'], key.to_s return response end @@ -744,28 +850,27 @@ def get_and_validate_auth_only_response assert response = @gateway.get_customer_profile(:customer_profile_id => @customer_profile_id) @customer_payment_profile_id = response.params['profile']['payment_profiles']['customer_payment_profile_id'] assert response = @gateway.create_customer_profile_transaction( - :transaction => { - :customer_profile_id => @customer_profile_id, - :customer_payment_profile_id => @customer_payment_profile_id, - :type => :auth_only, - :order => { + :transaction => { + :customer_profile_id => @customer_profile_id, + :customer_payment_profile_id => @customer_payment_profile_id, + :type => :auth_only, + :order => { :invoice_number => key.to_s, - :description => "Test Order Description #{key.to_s}", + :description => "Test Order Description #{key}", :purchase_order_number => key.to_s }, - :amount => @amount - } + :amount => @amount + } ) assert response.test? assert_success response assert_equal response.authorization, response.params['direct_response']['transaction_id'] assert response.params['direct_response']['approval_code'] =~ /\w{6}/ - assert_equal "auth_only", response.params['direct_response']['transaction_type'] - assert_equal "100.00", response.params['direct_response']['amount'] + assert_equal 'auth_only', response.params['direct_response']['transaction_type'] + assert_equal '100.00', response.params['direct_response']['amount'] return response end - end diff --git a/test/remote/gateways/remote_authorize_net_test.rb b/test/remote/gateways/remote_authorize_net_test.rb index a18b04e3d36..2b239ad18bb 100644 --- a/test/remote/gateways/remote_authorize_net_test.rb +++ b/test/remote/gateways/remote_authorize_net_test.rb @@ -1,79 +1,263 @@ require 'test_helper' -class AuthorizeNetTest < Test::Unit::TestCase +class RemoteAuthorizeNetTest < Test::Unit::TestCase def setup - Base.mode = :test - @gateway = AuthorizeNetGateway.new(fixtures(:authorize_net)) + @amount = 100 - @credit_card = credit_card('4242424242424242') + @credit_card = credit_card('4000100011112224') @check = check + @declined_card = credit_card('400030001111222') + @options = { - :order_id => generate_unique_id, - :billing_address => address, - :description => 'Store purchase' + order_id: '1', + email: 'anet@example.com', + duplicate_window: 0, + billing_address: address, + description: 'Store Purchase' } - @recurring_options = { - :amount => 100, - :subscription_name => 'Test Subscription 1', - :credit_card => @credit_card, - :billing_address => address.merge(:first_name => 'Jim', :last_name => 'Smith'), - :interval => { - :length => 1, - :unit => :months + @level_2_options = { + tax: { + amount: '100', + name: 'tax name', + description: 'tax description' + }, + duty: { + amount: '200', + name: 'duty name', + description: 'duty description' + }, + shipping: { + amount: '300', + name: 'shipping name', + description: 'shipping description', }, - :duration => { - :start_date => Date.today, - :occurrences => 1 - } + tax_exempt: 'false', + po_number: '123' } + + @level_3_options = { + ship_from_address: { + zip: '27701', + country: 'US' + }, + summary_commodity_code: 'CODE' + } + + @level_2_and_3_options = @level_2_options.merge(@level_3_options) end def test_successful_purchase - assert response = @gateway.purchase(@amount, @credit_card, @options) + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert response.test? + assert_equal 'This transaction has been approved', response.message + assert response.authorization + end + + def test_successful_purchase_with_minimal_options + response = @gateway.purchase(@amount, @credit_card, duplicate_window: 0, email: 'anet@example.com', billing_address: address) + assert_success response + assert response.test? + assert_equal 'This transaction has been approved', response.message + assert response.authorization + end + + def test_successful_purchase_with_email_customer + response = @gateway.purchase(@amount, @credit_card, duplicate_window: 0, email_customer: true, email: 'anet@example.com', billing_address: address) assert_success response assert response.test? assert_equal 'This transaction has been approved', response.message assert response.authorization end + def test_successful_purchase_with_false_email_customer + response = @gateway.purchase(@amount, @credit_card, duplicate_window: 0, email_customer: false, email: 'anet@example.com', billing_address: address) + assert_success response + assert response.test? + assert_equal 'This transaction has been approved', response.message + assert response.authorization + end + + def test_successful_purchase_with_header_email_receipt + response = @gateway.purchase(@amount, @credit_card, duplicate_window: 0, header_email_receipt: 'subject line', email: 'anet@example.com', billing_address: address) + assert_success response + assert response.test? + assert_equal 'This transaction has been approved', response.message + assert response.authorization + end + + def test_successful_purchase_with_line_items + additional_options = { + email: 'anet@example.com', + line_items: [ + { + item_id: '1', + name: 'mug', + description: 'coffee', + quantity: '100', + unit_price: '10' + }, + { + item_id: '2', + name: 'vase', + description: 'floral', + quantity: '200', + unit_price: '20' + } + ] + } + response = @gateway.purchase(@amount, @credit_card, @options.merge(additional_options)) + assert_success response + assert response.test? + assert_equal 'This transaction has been approved', response.message + assert response.authorization + end + + def test_successful_purchase_with_level_3_line_item_data + additional_options = { + email: 'anet@example.com', + line_items: [ + { + item_id: '1', + name: 'mug', + description: 'coffee', + quantity: '100', + unit_price: '10', + unit_of_measure: 'yards', + total_amount: '1000', + product_code: 'coupon' + } + ] + } + response = @gateway.purchase(@amount, @credit_card, @options.merge(additional_options)) + assert_success response + assert response.test? + assert_equal 'This transaction has been approved', response.message + assert response.authorization + end + + def test_successful_purchase_with_level_2_and_3_data + response = @gateway.purchase(@amount, @credit_card, @options.merge(@level_2_and_3_options)) + assert_success response + assert_equal 'This transaction has been approved', response.message + end + + def test_successful_purchase_with_customer + response = @gateway.purchase(@amount, @credit_card, @options.merge(customer: 'abcd_123')) + assert_success response + assert_equal 'This transaction has been approved', response.message + end + + def test_failed_purchase + response = @gateway.purchase(@amount, @declined_card, @options) + assert_failure response + assert_equal 'The credit card number is invalid', response.message + assert_equal 'incorrect_number', response.error_code + end + + def test_successful_purchase_with_utf_character + card = credit_card('4000100011112224', last_name: 'Wåhlin') + response = @gateway.purchase(@amount, card, @options) + assert_success response + assert_match %r{This transaction has been approved}, response.message + end def test_successful_echeck_purchase - assert response = @gateway.purchase(@amount, @check, @options) + response = @gateway.purchase(@amount, @check, @options) assert_success response assert response.test? assert_equal 'This transaction has been approved', response.message assert response.authorization end + def test_card_present_purchase_with_no_data + no_data_credit_card = ActiveMerchant::Billing::CreditCard.new + response = @gateway.purchase(@amount, no_data_credit_card, @options) + assert_failure response + assert_match %r{invalid}, response.message + end + def test_expired_credit_card @credit_card.year = 2004 - assert response = @gateway.purchase(@amount, @credit_card, @options) + response = @gateway.purchase(@amount, @credit_card, @options) assert_failure response assert response.test? assert_equal 'The credit card has expired', response.message + assert_equal 'expired_card', response.error_code + end + + def test_successful_authorize_and_capture + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + assert_equal 'This transaction has been approved', auth.message + + capture = @gateway.capture(@amount, auth.authorization) + assert_success capture + end + + def test_successful_purchase_with_disable_partial_authorize + purchase = @gateway.purchase(46225, @credit_card, @options.merge(disable_partial_auth: true)) + assert_success purchase end - def test_forced_test_mode_purchase - gateway = AuthorizeNetGateway.new(fixtures(:authorize_net).update(:test => true)) - assert response = gateway.purchase(@amount, @credit_card, @options) + def test_successful_authorize_with_email_and_ip + options = @options.merge({email: 'hello@example.com', ip: '127.0.0.1'}) + auth = @gateway.authorize(@amount, @credit_card, options) + assert_success auth + + assert_equal 'This transaction has been approved', auth.message + + capture = @gateway.capture(@amount, auth.authorization) + assert_success capture + end + + def test_failed_authorize + response = @gateway.authorize(@amount, @declined_card, @options) + assert_failure response + assert_equal 'The credit card number is invalid', response.message + end + + def test_card_present_authorize_and_capture_with_track_data_only + track_credit_card = ActiveMerchant::Billing::CreditCard.new(:track_data => '%B378282246310005^LONGSON/LONGBOB^1705101130504392?') + assert authorization = @gateway.authorize(@amount, track_credit_card, @options) + assert_success authorization + + capture = @gateway.capture(@amount, authorization.authorization) + assert_success capture + + assert_equal 'This transaction has been approved', capture.message + end + + def test_successful_echeck_authorization + response = @gateway.authorize(@amount, @check, @options) assert_success response - assert response.test? - assert_match(/TESTMODE/, response.message) + assert_equal 'This transaction has been approved', response.message assert response.authorization end - def test_successful_authorization - assert response = @gateway.authorize(@amount, @credit_card, @options) - assert_success response + def test_failed_echeck_authorization + response = @gateway.authorize(@amount, check(routing_number: '121042883'), @options) + assert_failure response + assert_equal 'The ABA code is invalid', response.message + assert response.authorization + end + + def test_card_present_purchase_with_track_data_only + track_credit_card = ActiveMerchant::Billing::CreditCard.new(:track_data => '%B378282246310005^LONGSON/LONGBOB^1705101130504392?') + response = @gateway.purchase(@amount, track_credit_card, @options) + assert response.test? assert_equal 'This transaction has been approved', response.message assert response.authorization end - def test_successfule_echeck_authorization - assert response = @gateway.authorize(@amount, @check, @options) + def test_successful_purchase_with_moto_retail_type + @credit_card.manual_entry = true + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert response.test? assert_equal 'This transaction has been approved', response.message assert response.authorization end @@ -96,80 +280,339 @@ def test_authorization_and_void assert_equal 'This transaction has been approved', void.message end - def test_bad_login - gateway = AuthorizeNetGateway.new( - :login => 'X', - :password => 'Y' - ) + def test_successful_authorization_with_moto_retail_type + @credit_card.manual_entry = true + response = @gateway.authorize(@amount, @credit_card, @options) - assert response = gateway.purchase(@amount, @credit_card) + assert_success response + assert response.test? + assert_equal 'This transaction has been approved', response.message + assert response.authorization + end - assert_equal Response, response.class - assert_equal ["action", - "authorization_code", - "avs_result_code", - "card_code", - "response_code", - "response_reason_code", - "response_reason_text", - "transaction_id"], response.params.keys.sort + def test_successful_verify + response = @gateway.verify(@credit_card, @options) + assert_success response + assert_equal 'This transaction has been approved', response.message + assert_success response.responses.last, 'The void should succeed' + end - assert_match(/The merchant login ID or password is invalid/, response.message) + def test_failed_verify + bogus_card = credit_card('4424222222222222') + response = @gateway.verify(bogus_card, @options) + assert_failure response + assert_match %r{The credit card number is invalid}, response.message + end - assert_equal false, response.success? + def test_successful_store + assert response = @gateway.store(@credit_card) + assert_success response + assert response.authorization + assert_equal 'Successful', response.message + assert_equal '1', response.params['message_code'] end - def test_using_test_request - gateway = AuthorizeNetGateway.new( - :login => 'X', - :password => 'Y' - ) + def test_successful_store_new_payment_profile + assert store = @gateway.store(@credit_card) + assert_success store + assert store.authorization - assert response = gateway.purchase(@amount, @credit_card) + new_card = credit_card('4424222222222222') + customer_profile_id, _, _ = store.authorization.split('#') - assert_equal Response, response.class - assert_equal ["action", - "authorization_code", - "avs_result_code", - "card_code", - "response_code", - "response_reason_code", - "response_reason_text", - "transaction_id"], response.params.keys.sort + assert response = @gateway.store(new_card, customer_profile_id: customer_profile_id) + assert_success response + assert_equal 'Successful', response.message + assert_equal '1', response.params['message_code'] + end - assert_match(/The merchant login ID or password is invalid/, response.message) + def test_failed_store_new_payment_profile + assert store = @gateway.store(@credit_card) + assert_success store + assert store.authorization - assert_equal false, response.success? + new_card = credit_card('141241') + customer_profile_id, _, _ = store.authorization.split('#') + + assert response = @gateway.store(new_card, customer_profile_id: customer_profile_id) + assert_failure response + assert_equal 'The field length is invalid for Card Number', response.message end - def test_successful_recurring - assert response = @gateway.recurring(@amount, @credit_card, @recurring_options) + def test_failed_store + assert response = @gateway.store(credit_card('141241')) + assert_failure response + assert_equal 'The field length is invalid for Card Number', response.message + assert_equal '15', response.params['message_code'] + end + + def test_successful_purchase_using_stored_card + response = @gateway.store(@credit_card, @options) assert_success response - assert response.test? - subscription_id = response.authorization + response = @gateway.purchase(@amount, response.authorization, @options) + assert_success response + assert_equal 'This transaction has been approved.', response.message + end - assert response = @gateway.update_recurring(:subscription_id => subscription_id, :amount => @amount * 2) + def test_successful_purchase_using_stored_card_with_delimiter + response = @gateway.store(@credit_card, @options.merge(delimiter: '|')) assert_success response - assert response = @gateway.status_recurring(subscription_id) + response = @gateway.purchase(@amount, response.authorization, @options.merge(delimiter: '|', description: 'description, with, commas')) assert_success response + assert_equal 'This transaction has been approved.', response.message + assert_equal 'description, with, commas', response.params['order_description'] + end - assert response = @gateway.cancel_recurring(subscription_id) + def test_failed_purchase_using_stored_card + response = @gateway.store(@declined_card) assert_success response + + response = @gateway.purchase(@amount, response.authorization, @options) + assert_failure response + assert_equal 'The credit card number is invalid.', response.message + assert_equal 'incorrect_number', response.error_code + assert_equal '27', response.params['message_code'] + assert_equal '6', response.params['response_reason_code'] + assert_match %r{Address not verified}, response.avs_result['message'] end - def test_recurring_should_fail_expired_credit_card - @credit_card.year = 2004 - assert response = @gateway.recurring(@amount, @credit_card, @recurring_options) + def test_successful_purchase_using_stored_card_new_payment_profile + assert store = @gateway.store(@credit_card, @options) + assert_success store + assert store.authorization + + new_card = credit_card('4007000000027') + customer_profile_id, _, _ = store.authorization.split('#') + + assert response = @gateway.store(new_card, customer_profile_id: customer_profile_id, email: 'anet@example.com', billing_address: address) + assert_success response + + response = @gateway.purchase(@amount, response.authorization, @options) + assert_success response + assert_equal 'This transaction has been approved.', response.message + end + + def test_successful_purchase_with_stored_card_and_level_2_and_3_data + store_response = @gateway.store(@credit_card, @options) + assert_success store_response + + response = @gateway.purchase(@amount, store_response.authorization, @options.merge(@level_2_and_3_options)) + assert_success response + assert_equal 'This transaction has been approved.', response.message + end + + def test_successful_authorize_and_capture_using_stored_card + store = @gateway.store(@credit_card, @options) + assert_success store + + auth = @gateway.authorize(@amount, store.authorization, @options) + assert_success auth + assert_equal 'This transaction has been approved.', auth.message + + capture = @gateway.capture(@amount, auth.authorization, @options) + assert_success capture + assert_equal 'This transaction has been approved.', capture.message + end + + def test_successful_authorize_and_capture_using_stored_card_with_level_2_and_3_data + store = @gateway.store(@credit_card, @options) + assert_success store + + auth = @gateway.authorize(@amount, store.authorization, @options.merge(@level_2_and_3_options)) + assert_success auth + assert_equal 'This transaction has been approved.', auth.message + + capture = @gateway.capture(@amount, auth.authorization, @options.merge(@level_2_and_3_options)) + assert_success capture + assert_equal 'This transaction has been approved.', capture.message + end + + def test_failed_authorize_using_stored_card + response = @gateway.store(@declined_card) + assert_success response + + response = @gateway.authorize(@amount, response.authorization, @options) + assert_failure response + + assert_equal 'The credit card number is invalid.', response.message + assert_equal 'incorrect_number', response.error_code + assert_equal '27', response.params['message_code'] + assert_equal '6', response.params['response_reason_code'] + assert_match %r{Address not verified}, response.avs_result['message'] + end + + def test_failed_authorize_using_wrong_token + response = @gateway.store(@declined_card) + assert_success response + + responseA = @gateway.authorize(@amount, response.authorization, @options.merge(customer_payment_profile_id: 12345)) + responseB = @gateway.authorize(@amount, response.authorization, @options.merge(customer_profile_id: 12345)) + assert_failure responseA + assert_failure responseB + + assert_equal 'Customer Profile ID or Customer Payment Profile ID not found', responseA.message + assert_equal 'Customer Profile ID or Customer Payment Profile ID not found', responseB.message + end + + def test_failed_capture_using_stored_card + store = @gateway.store(@credit_card, @options) + assert_success store + + auth = @gateway.authorize(@amount, store.authorization, @options) + assert_success auth + + capture = @gateway.capture(@amount + 4000, auth.authorization, @options) + assert_failure capture + assert_match %r{The amount requested for settlement cannot be greater}, capture.message + end + + def test_faux_successful_refund_with_billing_address + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + + refund = @gateway.refund(@amount, purchase.authorization, @options.merge(first_name: 'Jim', last_name: 'Smith')) + assert_failure refund + assert_match %r{does not meet the criteria for issuing a credit}, refund.message, 'Only allowed to refund transactions that have settled. This is the best we can do for now testing wise.' + end + + def test_faux_successful_refund_without_billing_address + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + + @options[:billing_address] = nil + + refund = @gateway.refund(@amount, purchase.authorization, @options.merge(first_name: 'Jim', last_name: 'Smith')) + assert_failure refund + assert_match %r{does not meet the criteria for issuing a credit}, refund.message, 'Only allowed to refund transactions that have settled. This is the best we can do for now testing wise.' + end + + def test_faux_successful_refund_using_stored_card + store = @gateway.store(@credit_card, @options) + assert_success store + + purchase = @gateway.purchase(@amount, store.authorization, @options) + assert_success purchase + + refund = @gateway.refund(@amount, purchase.authorization, @options) + assert_failure refund + assert_match %r{does not meet the criteria for issuing a credit}, refund.message, 'Only allowed to refund transactions that have settled. This is the best we can do for now testing wise.' + end + + def test_faux_successful_refund_using_stored_card_and_level_2_and_3_data + store = @gateway.store(@credit_card, @options) + assert_success store + + purchase = @gateway.purchase(@amount, store.authorization, @options.merge(@level_2_and_3_options)) + assert_success purchase + + refund = @gateway.refund(@amount, purchase.authorization, @options.merge(@level_2_and_3_options)) + assert_failure refund + assert_match %r{does not meet the criteria for issuing a credit}, refund.message, 'Only allowed to refund transactions that have settled. This is the best we can do for now testing wise.' + end + + def test_failed_refund_using_stored_card + store = @gateway.store(@credit_card, @options) + assert_success store + + purchase = @gateway.purchase(@amount, store.authorization, @options) + assert_success purchase + + unknown_authorization = '2235494048#XXXX2224#cim_purchase' + refund = @gateway.refund(@amount, unknown_authorization, @options) + assert_failure refund + assert_equal 'The record cannot be found', refund.message + end + + def test_successful_void_using_stored_card + store = @gateway.store(@credit_card, @options) + assert_success store + + auth = @gateway.authorize(@amount, store.authorization, @options) + assert_success auth + + void = @gateway.void(auth.authorization, @options) + assert_success void + assert_equal 'This transaction has been approved.', void.message + end + + def test_failed_void_using_stored_card + store = @gateway.store(@credit_card, @options) + assert_success store + + auth = @gateway.authorize(@amount, store.authorization, @options) + assert_success auth + + void = @gateway.void(auth.authorization, @options) + assert_success void + + another_void = @gateway.void(auth.authorization, @options) + assert_failure another_void + assert_equal 'This transaction has already been voided.', another_void.message + end + + def test_bad_login + gateway = AuthorizeNetGateway.new( + :login => 'X', + :password => 'Y' + ) + + response = gateway.purchase(@amount, @credit_card) + assert_failure response + + assert_equal %w( + account_number + action + authorization_code + avs_result_code + card_code + cardholder_authentication_code + full_response_code + response_code + response_reason_code + response_reason_text + test_request + transaction_id + ), response.params.keys.sort + + assert_equal 'User authentication failed due to invalid authentication values', response.message + end + + def test_partial_capture + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + capture = @gateway.capture(@amount-1, auth.authorization) + assert_success capture + end + + def test_failed_capture + response = @gateway.capture(20, '23124#1234') + assert_failure response + end + + def test_successful_void + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + assert void = @gateway.void(auth.authorization) + assert_success void + end + + def test_failed_void + response = @gateway.void('') + assert_failure response + end + + def test_failed_refund + response = @gateway.refund(nil, '') assert_failure response - assert response.test? - assert_equal 'E00018', response.params['code'] end def test_successful_purchase_with_solution_id ActiveMerchant::Billing::AuthorizeNetGateway.application_id = 'A1000000' - assert response = @gateway.purchase(@amount, @credit_card, @options) + response = @gateway.purchase(@amount, @credit_card, @options) assert_success response assert response.test? assert_equal 'This transaction has been approved', response.message @@ -178,17 +621,127 @@ def test_successful_purchase_with_solution_id ActiveMerchant::Billing::AuthorizeNetGateway.application_id = nil end + def test_successful_credit + response = @gateway.credit(@amount, @credit_card, @options) + assert_success response + assert_equal 'This transaction has been approved', response.message + assert response.authorization + end + + def test_successful_echeck_credit + response = @gateway.credit(@amount, @check, @options) + assert_equal 'The transaction is currently under review', response.message + assert response.authorization + end + + def test_successful_echeck_refund + purchase = @gateway.purchase(@amount, @check, @options) + assert_success purchase + + @options.update(transaction_id: purchase.params['transaction_id'], test_request: true) + refund = @gateway.credit(@amount, @check, @options) + assert_failure refund + assert_match %r{The transaction cannot be found}, refund.message, 'Only allowed to refund transactions that have settled. This is the best we can do for now testing wise.' + end + + def test_failed_credit + response = @gateway.credit(@amount, @declined_card, @options) + assert_failure response + assert_equal 'The credit card number is invalid', response.message + assert response.authorization + end + def test_bad_currency - @options[:currency] = "XYZ" - assert response = @gateway.purchase(@amount, @credit_card, @options) + response = @gateway.purchase(@amount, @credit_card, currency: 'XYZ') assert_failure response assert_equal 'The supplied currency code is either invalid, not supported, not allowed for this merchant or doesn\'t have an exchange rate', response.message end def test_usd_currency - @options[:currency] = "USD" - assert response = @gateway.purchase(@amount, @credit_card, @options) + @options[:currency] = 'USD' + response = @gateway.purchase(@amount, @credit_card, @options) assert_success response assert response.authorization end + + def test_dump_transcript + # dump_transcript_and_fail(@gateway, @amount, @credit_card, @options) + end + + def test_successful_authorize_and_capture_with_network_tokenization + credit_card = network_tokenization_credit_card('4000100011112224', + payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=', + verification_value: nil + ) + auth = @gateway.authorize(@amount, credit_card, @options) + assert_success auth + assert_equal 'This transaction has been approved', auth.message + + capture = @gateway.capture(@amount, auth.authorization) + assert_success capture + end + + def test_successful_refund_with_network_tokenization + credit_card = network_tokenization_credit_card('4000100011112224', + payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=', + verification_value: nil + ) + + purchase = @gateway.purchase(@amount, credit_card, @options) + assert_success purchase + + @options[:billing_address] = nil + + refund = @gateway.refund(@amount, purchase.authorization, @options.merge(first_name: 'Jim', last_name: 'Smith')) + assert_failure refund + assert_match %r{does not meet the criteria for issuing a credit}, refund.message, 'Only allowed to refund transactions that have settled. This is the best we can do for now testing wise.' + end + + def test_successful_credit_with_network_tokenization + credit_card = network_tokenization_credit_card('4000100011112224', + payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=', + verification_value: nil + ) + + response = @gateway.credit(@amount, credit_card, @options) + assert_success response + assert_equal 'This transaction has been approved', response.message + assert response.authorization + end + + def test_network_tokenization_transcript_scrubbing + credit_card = network_tokenization_credit_card('4111111111111111', + :brand => 'visa', + :eci => '05', + :payment_cryptogram => 'EHuWW9PiBkWvqE5juRwDzAUFBAk=' + ) + + transcript = capture_transcript(@gateway) do + @gateway.authorize(@amount, credit_card, @options) + end + transcript = @gateway.scrub(transcript) + + assert_scrubbed(credit_card.number, transcript) + assert_scrubbed(credit_card.payment_cryptogram, transcript) + assert_scrubbed(@gateway.options[:password], transcript) + end + + def test_purchase_scrubbing + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @credit_card, @options) + end + transcript = @gateway.scrub(transcript) + + assert_scrubbed(credit_card.number, transcript) + assert_scrubbed(credit_card.verification_value, transcript) + assert_scrubbed(@gateway.options[:password], transcript) + end + + def test_verify_credentials + assert @gateway.verify_credentials + + gateway = AuthorizeNetGateway.new(login: 'unknown_login', password: 'not_right') + assert !gateway.verify_credentials + end + end diff --git a/test/remote/gateways/remote_axcessms_test.rb b/test/remote/gateways/remote_axcessms_test.rb new file mode 100644 index 00000000000..54b1c82213a --- /dev/null +++ b/test/remote/gateways/remote_axcessms_test.rb @@ -0,0 +1,177 @@ +require 'test_helper' + +class RemoteAxcessmsTest < Test::Unit::TestCase + def setup + @gateway = AxcessmsGateway.new(fixtures(:axcessms)) + + @amount = 1500 + @credit_card = credit_card('4200000000000000', month: 05, year: 2022) + @declined_card = credit_card('4444444444444444', month: 05, year: 2022) + @mode = 'CONNECTOR_TEST' + + @options = { + order_id: generate_unique_id, + email: 'customer@example.com', + description: "Order Number #{Time.now.to_f.divmod(2473)[1]}", + ip: '0.0.0.0', + mode: @mode, + billing_address: address + } + end + + def test_successful_authorization + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth, 'Authorize failed' + assert_match %r{Successful Processing - Request successfully processed}, auth.message + end + + def test_successful_authorize_and_capture + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth, 'Authorize failed' + assert_match %r{Successful Processing - Request successfully processed}, auth.message + + assert capture = @gateway.capture(@amount, auth.authorization, {mode: @mode}) + assert_success capture, 'Capture failed' + assert_match %r{Successful Processing - Request successfully processed}, capture.message + end + + def test_successful_authorize_and_partial_capture + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth, 'Authorize failed' + assert_match %r{Successful Processing - Request successfully processed}, auth.message + + assert capture = @gateway.capture(@amount-30, auth.authorization, {mode: @mode}) + assert_success capture, 'Capture failed' + assert_match %r{Successful Processing - Request successfully processed}, capture.message + end + + def test_successful_authorize_and_void + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth, 'Authorize failed' + assert_match %r{Successful Processing - Request successfully processed}, auth.message + + assert void = @gateway.void(auth.authorization, {mode: @mode}) + assert_success void, 'Void failed' + assert_match %r{Successful Processing - Request successfully processed}, void.message + end + + def test_successful_purchase + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_match %r{Successful Processing - Request successfully processed}, response.message + end + + def test_successful_purchase_with_minimal_options + response = @gateway.purchase(@amount, @credit_card, billing_address: address) + assert_success response + assert_match %r{Successful Processing - Request successfully processed}, response.message + end + + def test_successful_reference_purchase + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + assert_match %r{Successful Processing - Request successfully processed}, purchase.message + + repeat_purchase = @gateway.purchase(@amount, purchase.authorization, @options) + assert_success repeat_purchase + assert_match %r{Successful Processing - Request successfully processed}, repeat_purchase.message + end + + def test_successful_purchase_and_refund + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase, 'Purchase failed' + assert_match %r{Successful Processing - Request successfully processed}, purchase.message + + assert refund = @gateway.refund(@amount, purchase.authorization, {mode: @mode}) + assert_success refund, 'Refund failed' + assert_match %r{Successful Processing - Request successfully processed}, refund.message + end + + def test_successful_purchase_and_partial_refund + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase, 'Purchase failed' + assert_match %r{Successful Processing - Request successfully processed}, purchase.message + + assert refund = @gateway.refund(@amount-50, purchase.authorization, {mode: @mode}) + assert_success refund, 'Refund failed' + assert_match %r{Successful Processing - Request successfully processed}, refund.message + end + + # Failure tested + + def test_utf8_description_does_not_blow_up + assert response = @gateway.purchase(@amount, @credit_card, @options.merge(description: 'Habitación')) + assert_success response + assert_match %r{Successful Processing - Request successfully processed}, response.message + end + + def test_failed_capture + assert capture = @gateway.capture(@amount, 'invalid authorization') + assert_failure capture + assert_match %r{Reference Error - capture}, capture.message + end + + def test_failed_bigger_capture_then_authorised + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth, 'Authorize failed' + + assert capture = @gateway.capture(@amount+30, auth.authorization, {mode: @mode}) + assert_failure capture, 'Capture failed' + assert_match %r{PA value exceeded}, capture.message + end + + def test_failed_authorize + authorize = @gateway.authorize(@amount, @declined_card, @options) + assert_failure authorize + assert_match %r{invalid creditcard}, authorize.message + end + + def test_failed_refund + assert refund = @gateway.refund(@amount, 'invalid authorization', {mode: @mode}) + assert_failure refund + assert_match %r{Configuration Validation - Invalid payment data}, refund.message + end + + def test_failed_void + void = @gateway.void('invalid authorization', {mode: @mode}) + assert_failure void + assert_match %r{Reference Error - reversal}, void.message + end + + def test_unauthorized_capture + assert response = @gateway.capture(@amount, '1234567890123456789012') + assert_failure response + assert_equal 'Reference Error - capture needs at least one successful transaction of type (PA)', response.message + end + + def test_unauthorized_purchase_by_reference + assert response = @gateway.purchase(@amount, '1234567890123456789012') + assert_failure response + assert_equal 'Reference Error - reference id not existing', response.message + end + + def test_failed_purchase_by_card + purchase = @gateway.purchase(@amount, @declined_card, @options) + assert_failure purchase + assert_match %r{Account Validation - invalid creditcard}, purchase.message + end + + def test_invalid_login + credentials = fixtures(:axcessms).merge(password: 'invalid') + response = AxcessmsGateway.new(credentials).purchase(@amount, @credit_card, @options) + assert_failure response + end + + def test_successful_verify + assert response = @gateway.verify(@credit_card, @options) + assert_success response + assert_match %r{success}i, response.message + assert_success response.responses.last, 'The void should succeed' + end + + def test_failed_verify + assert response = @gateway.verify(@bad_credit_card, @options) + assert_failure response + assert_match %r{invalid}i, response.message + end +end diff --git a/test/remote/gateways/remote_balanced_test.rb b/test/remote/gateways/remote_balanced_test.rb index 1ea8135dcf3..d5930116419 100644 --- a/test/remote/gateways/remote_balanced_test.rb +++ b/test/remote/gateways/remote_balanced_test.rb @@ -1,7 +1,6 @@ require 'test_helper' class RemoteBalancedTest < Test::Unit::TestCase - def setup @gateway = BalancedGateway.new(fixtures(:balanced)) @@ -11,110 +10,171 @@ def setup @declined_card = credit_card('4444444444444448') @options = { - :email => 'john.buyer@example.org', - :billing_address => address, - :description => 'Shopify Purchase' + email: 'john.buyer@example.org', + billing_address: address, + description: 'Shopify Purchase' } end def test_successful_purchase - assert response = @gateway.purchase(@amount, @credit_card, @options) + response = @gateway.purchase(@amount, @credit_card, @options) assert_success response - assert_equal 'Transaction approved', response.message - assert_equal @amount, response.params['amount'] + assert_equal 'Success', response.message + assert_equal @amount, response.params['debits'][0]['amount'] end - def test_invalid_card - assert response = @gateway.purchase(@amount, @invalid_card, @options) - assert_failure response - assert_match /Customer call bank/, response.message + def test_successful_purchase_with_outside_token + outside_token = @gateway.store(@credit_card).params['cards'][0]['href'] + response = @gateway.purchase(@amount, outside_token, @options) + assert_success response + assert_equal 'Success', response.message + assert_equal @amount, response.params['debits'][0]['amount'] end - def test_invalid_email - assert response = @gateway.purchase(@amount, @credit_card, @options.merge(:email => 'invalid_email')) + def test_purchase_with_invalid_card + response = @gateway.purchase(@amount, @invalid_card, @options) assert_failure response - assert_match /Invalid field.*email_address/, response.message + assert_match %r{call bank}i, response.message end def test_unsuccessful_purchase - assert response = @gateway.purchase(@amount, @declined_card, @options) + response = @gateway.purchase(@amount, @declined_card, @options) assert_failure response - assert_match /Account Frozen/, response.message + assert_match %r{Account Frozen}, response.message + end + + def test_passing_appears_on_statement + options = @options.merge(appears_on_statement_as: 'Homer Electric') + response = @gateway.purchase(@amount, @credit_card, options) + + assert_success response + assert_equal 'BAL*Homer Electric', response.params['debits'][0]['appears_on_statement_as'] + end + + def test_passing_meta + options = @options.merge(meta: { 'order_number' => '12345' }) + response = @gateway.purchase(@amount, @credit_card, options) + + assert_success response + assert_equal options[:meta], response.params['debits'][0]['meta'] end def test_authorize_and_capture - amount = @amount - assert auth = @gateway.authorize(amount, @credit_card, @options) - assert_success auth - assert_equal 'Transaction approved', auth.message - assert auth.authorization - assert capture = @gateway.capture(amount, auth.authorization) + assert authorize = @gateway.authorize(@amount, @credit_card, @options) + assert_success authorize + assert_equal 'Success', authorize.message + + assert capture = @gateway.capture(@amount, authorize.authorization) assert_success capture - assert_equal amount, capture.params['amount'] - assert_equal auth.authorization, capture.params['hold']['uri'] + assert_equal @amount, capture.params['debits'][0]['amount'] end def test_authorize_and_capture_partial - amount = @amount - assert auth = @gateway.authorize(amount, @credit_card, @options) - assert_success auth - assert_equal 'Transaction approved', auth.message - assert auth.authorization - assert capture = @gateway.capture(amount / 2, auth.authorization) + assert authorize = @gateway.authorize(@amount, @credit_card, @options) + assert_success authorize + assert_equal 'Success', authorize.message + + assert capture = @gateway.capture(@amount / 2, authorize.authorization) assert_success capture - assert_equal amount / 2, capture.params['amount'] - assert_equal auth.authorization, capture.params['hold']['uri'] + assert_equal @amount / 2, capture.params['debits'][0]['amount'] end def test_failed_capture - assert response = @gateway.capture(@amount, '') + response = @gateway.capture(@amount, '') assert_failure response - assert response.message.index('Missing required field') != nil end def test_void_authorization - amount = @amount - assert auth = @gateway.authorize(amount, @credit_card, @options) - assert_success auth - assert void = @gateway.void(auth.authorization) + assert authorize = @gateway.authorize(@amount, @credit_card, @options) + assert_success authorize + + assert void = @gateway.void(authorize.authorization) assert_success void - assert void.params['is_void'] + assert void.params['card_holds'][0]['voided_at'], void.inspect + end + + def test_voiding_a_capture_not_allowed + assert authorize = @gateway.authorize(@amount, @credit_card, @options) + assert_success authorize + assert authorize.authorization + + assert capture = @gateway.capture(@amount, authorize.authorization) + assert_success capture + assert capture.authorization + + void = @gateway.void(capture.authorization) + assert_failure void + assert_match %r{not found}i, void.message + end + + def test_authorize_authorization + assert auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + assert auth.authorization + assert capture = @gateway.capture(@amount, auth.authorization) + assert_success capture end def test_refund_purchase - assert debit = @gateway.purchase(@amount, @credit_card, @options) - assert_success debit - assert refund = @gateway.refund(nil, debit.authorization) + assert purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + + assert refund = @gateway.refund(@amount, purchase.authorization) + assert_success refund + assert_equal @amount, refund.params['refunds'][0]['amount'] + end + + def test_refund_authorization + assert auth = @gateway.purchase(@amount, @credit_card, @options) + assert_success auth + assert auth.authorization + assert refund = @gateway.refund(@amount, auth.authorization) assert_success refund - assert_equal @amount, refund.params['amount'] end def test_refund_partial_purchase - assert debit = @gateway.purchase(@amount, @credit_card, @options) - assert_success debit - assert refund = @gateway.refund(@amount / 2, debit.authorization) + assert purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + + assert refund = @gateway.refund(@amount / 2, purchase.authorization) assert_success refund - assert_equal @amount / 2, refund.params['amount'] + assert_equal @amount / 2, refund.params['refunds'][0]['amount'] end def test_store new_email_address = '%d@example.org' % Time.now - assert card_uri = @gateway.store(@credit_card, { - :email => new_email_address + store = @gateway.store(@credit_card, { + email: new_email_address }) - assert_instance_of String, card_uri + assert_instance_of String, store.authorization + end + + def test_store_and_purchase + store = @gateway.store(@credit_card) + assert_success store + + purchase = @gateway.purchase(@amount, store.authorization) + assert_success purchase + end + + def test_store_and_authorize + store = @gateway.store(@credit_card) + assert_success store + + authorize = @gateway.authorize(@amount, store.authorization) + assert_success authorize + end + + def test_passing_address_with_no_zip + response = @gateway.purchase(@amount, @credit_card, address(zip: nil)) + assert_success response end def test_invalid_login - begin - BalancedGateway.new( - :login => '' - ) - rescue BalancedGateway::Error => ex - msg = ex.message - else - msg = nil - end - assert_equal 'Invalid login credentials supplied', msg + gateway = BalancedGateway.new( + login: '' + ) + response = gateway.store(@credit_card) + assert_match %r{credentials}i, response.message end end diff --git a/test/remote/gateways/remote_bambora_apac_test.rb b/test/remote/gateways/remote_bambora_apac_test.rb new file mode 100644 index 00000000000..896f0d17865 --- /dev/null +++ b/test/remote/gateways/remote_bambora_apac_test.rb @@ -0,0 +1,122 @@ +require 'test_helper' + +class RemoteBamboraApacTest < Test::Unit::TestCase + def setup + @gateway = BamboraApacGateway.new(fixtures(:bambora_apac)) + + @credit_card = credit_card('4005550000000001') + + @options = { + order_id: '1', + billing_address: address, + description: 'Store Purchase', + } + end + + def test_transcript_scrubbing + transcript = capture_transcript(@gateway) do + @gateway.purchase(200, @credit_card, @options) + end + transcript = @gateway.scrub(transcript) + + assert_scrubbed(@credit_card.number, transcript) + assert_scrubbed(@credit_card.verification_value, transcript) + assert_scrubbed(@gateway.options[:password], transcript) + end + + def test_successful_purchase + response = @gateway.purchase(200, @credit_card, @options) + assert_success response + assert_equal 'Succeeded', response.message + end + + def test_failed_purchase + response = @gateway.purchase(105, @credit_card, @options) + assert_failure response + end + + def test_successful_authorize_and_capture + response = @gateway.authorize(200, @credit_card, @options) + assert_success response + response = @gateway.capture(200, response.authorization) + assert_success response + end + + def test_failed_authorize + response = @gateway.authorize(105, @credit_card, @options) + assert_failure response + end + + def test_failed_capture + response = @gateway.capture(200, '') + assert_failure response + end + + def test_successful_refund + response = @gateway.purchase(200, @credit_card, @options) + response = @gateway.refund(200, response.authorization, @options) + assert_success response + assert_equal 'Succeeded', response.message + end + + def test_failed_refund + response = @gateway.purchase(200, @credit_card, @options) + response = @gateway.refund(105, response.authorization, @options) + assert_failure response + assert_equal 'Do Not Honour', response.message + end + + def test_successful_void + response = @gateway.purchase(200, @credit_card, @options) + assert_success response + response = @gateway.void(200, response.authorization) + assert_success response + end + + def test_failed_void + response = @gateway.purchase(200, @credit_card, @options) + assert_success response + response = @gateway.void(200, 123) + assert_failure response + assert_equal 'Cannot find matching transaction to VOID', response.message + end + + def test_successful_store + response = @gateway.store(@credit_card, @options) + assert_success response + end + + def test_failed_store + bad_credit_card = credit_card(nil) + + response = @gateway.store(bad_credit_card, @options) + assert_failure response + end + + def test_successful_purchase_using_stored_card + assert store_response = @gateway.store(@credit_card, @options) + assert_success store_response + + response = @gateway.purchase(500, store_response.authorization, @options) + assert_success response + assert_equal 'Succeeded', response.message + end + + def test_successful_authorize_using_stored_card + assert store_response = @gateway.store(@credit_card, @options) + assert_success store_response + + response = @gateway.authorize(500, store_response.authorization, @options) + assert_success response + assert_equal 'Succeeded', response.message + end + + def test_invalid_login + gateway = BamboraApacGateway.new( + username: '', + password: '' + ) + response = gateway.purchase(200, @credit_card, @options) + assert_failure response + end +end diff --git a/test/remote/gateways/remote_bank_frick_test.rb b/test/remote/gateways/remote_bank_frick_test.rb new file mode 100644 index 00000000000..d1bb6447d4e --- /dev/null +++ b/test/remote/gateways/remote_bank_frick_test.rb @@ -0,0 +1,131 @@ +require 'test_helper' + +class RemoteBankFrickTest < Test::Unit::TestCase + def setup + @gateway = BankFrickGateway.new(fixtures(:bank_frick)) + + @amount = 100 + @credit_card = credit_card('4000100011112224') + @declined_card = credit_card('4222222222222') + + @options = { + order_id: Time.now.to_i, # avoid duplicates + billing_address: address, + description: 'Store Purchase' + } + end + + def test_successful_purchase + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert response.test? + assert_match %r{Transaction succeeded}, response.message + assert response.authorization + end + + def test_successful_purchase_with_minimal_options + assert response = @gateway.purchase(@amount, @credit_card, {address: address}) + assert_success response + assert response.test? + assert_match %r{Transaction succeeded}, response.message + assert response.authorization + end + + def test_failed_purchase + response = @gateway.purchase(@amount, @declined_card, @options) + assert_failure response + assert_match %r{account or user is blacklisted}, response.message + end + + def test_successful_authorize + response = @gateway.authorize(@amount, @credit_card, @options) + assert_success response + assert_match %r{Transaction succeeded}, response.message + assert response.authorization + end + + def test_successful_authorize_and_capture + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + assert capture = @gateway.capture(@amount, auth.authorization) + assert_success capture + assert_match %r{Transaction succeeded}, capture.message + end + + def test_failed_authorize + response = @gateway.authorize(@amount, @declined_card, @options) + assert_failure response + assert_equal 'account or user is blacklisted', response.message + end + + def test_partial_capture + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + assert capture = @gateway.capture(@amount-1, auth.authorization) + assert_success capture + end + + def test_failed_capture + response = @gateway.capture(nil, '') + assert_failure response + end + + def test_successful_refund + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + + assert refund = @gateway.refund(@amount, purchase.authorization) + assert_success refund + end + + def test_partial_refund + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + + assert refund = @gateway.refund(@amount-1, purchase.authorization) + assert_success refund + end + + def test_failed_refund + response = @gateway.refund(nil, '') + assert_failure response + end + + def test_successful_void + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + assert void = @gateway.void(auth.authorization) + assert_success void + end + + def test_failed_void + response = @gateway.void('') + assert_failure response + end + + def test_successful_verify + response = @gateway.verify(@credit_card, @options) + assert_success response + assert_match %r{Transaction succeeded}, response.message + end + + def test_failed_verify + response = @gateway.verify(@declined_card, @options) + assert_failure response + assert_match %r{account or user is blacklisted}, response.message + end + + def test_invalid_login + gateway = BankFrickGateway.new( + sender: '', + channel: '', + userid: '', + userpwd: '' + ) + response = gateway.purchase(@amount, @credit_card, @options) + assert_failure response + end +end diff --git a/test/remote/gateways/remote_banwire_test.rb b/test/remote/gateways/remote_banwire_test.rb index 398c68502a4..0af462e9a7b 100644 --- a/test/remote/gateways/remote_banwire_test.rb +++ b/test/remote/gateways/remote_banwire_test.rb @@ -1,4 +1,5 @@ # encoding: utf-8 + require 'test_helper' class RemoteBanwireTest < Test::Unit::TestCase @@ -6,46 +7,12 @@ def setup @gateway = BanwireGateway.new(fixtures(:banwire)) @amount = 100 - @credit_card = credit_card('5204164299999999', - :month => 11, - :year => 2012, - :verification_value => '999', - :brand => 'mastercard') - - @visa_credit_card = credit_card('4485814063899108', - :month => 12, - :year => 2016, - :verification_value => '434') + @credit_card = credit_card('5204164299999999', :verification_value => '999', :brand => 'mastercard') + @visa_credit_card = credit_card('4485814063899108', :verification_value => '434') @declined_card = credit_card('4000300011112220') - @options = { - :order_id => '1', - :email => "test@email.com", - :billing_address => address, - :description => 'Store Purchase' - } - - @amex_credit_card = credit_card('375932134599999', - :month => 10, - :year => 2014, - :first_name => "Banwire", - :last_name => "Test Card", - :verification_value => '9999', - :brand => 'american_express') - - @amex_successful_options = { - :order_id => '3', - :email => 'test@email.com', - :billing_address => address(:address1 => 'Horacio', :zip => '11560'), - :description => 'Store purchase amex' - } - - @amex_options = { - :order_id => '2', - :email => 'test@email.com', - :billing_address => address, - :description => 'Store purchase amex' + billing_address: address, } end @@ -59,15 +26,21 @@ def test_successful_visa_purchase assert_success response end - def test_successful_amex_purchase - assert response = @gateway.purchase(@amount, @amex_credit_card, @amex_successful_options) + def test_successful_purchase_with_extra_options + options = { + order_id: '1', + email: 'test@email.com', + billing_address: address, + description: 'Store Purchase' + } + assert response = @gateway.purchase(@amount, @credit_card, options) assert_success response end def test_unsuccessful_purchase assert response = @gateway.purchase(@amount, @declined_card, @options) assert_failure response - assert_equal 'denied', response.message + assert_equal 'Pago Denegado.', response.message end def test_invalid_login @@ -80,8 +53,14 @@ def test_invalid_login assert_equal 'ID de cuenta invalido', response.message end - def test_invalid_amex_address - assert response = @gateway.purchase(@amount, @amex_credit_card, @amex_options) - assert_equal 'Error en los datos de facturación de la tarjeta, por favor inserte su dirección y código postal tal y como viene en su estado de cuenta de American Express. En caso de que persista el error, por favor comuníquese con American Express.', response.message + def test_transcript_scrubbing + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @credit_card, @options) + end + clean_transcript = @gateway.scrub(transcript) + + assert_scrubbed(@credit_card.number, clean_transcript) + assert_scrubbed(@credit_card.verification_value.to_s, clean_transcript) end + end diff --git a/test/remote/gateways/remote_barclaycard_smartpay_test.rb b/test/remote/gateways/remote_barclaycard_smartpay_test.rb new file mode 100644 index 00000000000..0cd7a6ab001 --- /dev/null +++ b/test/remote/gateways/remote_barclaycard_smartpay_test.rb @@ -0,0 +1,418 @@ +require 'test_helper' + +class RemoteBarclaycardSmartpayTest < Test::Unit::TestCase + def setup + @gateway = BarclaycardSmartpayGateway.new(fixtures(:barclaycard_smartpay)) + BarclaycardSmartpayGateway.ssl_strict = false + + @amount = 100 + @error_amount = 1_000_000_000_000_000_000_000 + @credit_card = credit_card('4111111111111111', :month => 10, :year => 2020, :verification_value => 737) + @declined_card = credit_card('4000300011112220', :month => 3, :year => 2030, :verification_value => 737) + @three_ds_enrolled_card = credit_card('4212345678901237', brand: :visa) + @three_ds_2_enrolled_card = credit_card('4917610000000000', brand: :visa) + + @options = { + order_id: '1', + billing_address: { + name: 'Jim Smith', + address1: '100 Street', + company: 'Widgets Inc', + city: 'Ottawa', + state: 'ON', + zip: 'K1C2N6', + country: 'CA', + phone: '(555)555-5555', + fax: '(555)555-6666'}, + email: 'long@bob.com', + customer: 'Longbob Longsen', + description: 'Store Purchase' + } + + @options_with_alternate_address = { + order_id: '1', + billing_address: { + name: 'PU JOI SO', + address1: '新北市店溪路3579號139樓', + company: 'Widgets Inc', + city: '新北市', + zip: '231509', + country: 'TW', + phone: '(555)555-5555', + fax: '(555)555-6666' + }, + email: 'pujoi@so.com', + customer: 'PU JOI SO', + description: 'Store Purchase' + } + + @options_with_house_number_and_street = { + order_id: '1', + house_number: '100', + street: 'Top Level Drive', + billing_address: { + name: 'Jim Smith', + address1: '100 Top Level Dr', + company: 'Widgets Inc', + city: 'Ottawa', + state: 'ON', + zip: 'K1C2N6', + country: 'CA', + phone: '(555)555-5555', + fax: '(555)555-6666' + }, + email: 'long@deb.com', + customer: 'Longdeb Longsen', + description: 'Store Purchase' + } + + @options_with_no_address = { + order_id: '1', + email: 'long@bob.com', + customer: 'Longbob Longsen', + description: 'Store Purchase' + } + + @options_with_credit_fields = { + order_id: '1', + billing_address: { + name: 'Jim Smith', + address1: '100 Street', + company: 'Widgets Inc', + city: 'Ottawa', + state: 'ON', + zip: 'K1C2N6', + country: 'CA', + phone: '(555)555-5555', + fax: '(555)555-6666'}, + email: 'long@bob.com', + customer: 'Longbob Longsen', + description: 'Store Purchase', + date_of_birth: '1990-10-11', + entity_type: 'NaturalPerson', + nationality: 'US', + shopper_name: { + firstName: 'Longbob', + lastName: 'Longsen', + gender: 'MALE' + } + } + + @avs_credit_card = credit_card('4400000000000008', + :month => 8, + :year => 2018, + :verification_value => 737) + + @avs_address = @options.clone + @avs_address.update(billing_address: { + name: 'Jim Smith', + street: 'Test AVS result', + houseNumberOrName: '2', + city: 'Cupertino', + state: 'CA', + zip: '95014', + country: 'US' + }) + + @normalized_3ds_2_options = { + reference: '345123', + shopper_email: 'john.smith@test.com', + shopper_ip: '77.110.174.153', + shopper_reference: 'John Smith', + billing_address: address(), + order_id: '123', + stored_credential: {reason_type: 'unscheduled'}, + three_ds_2: { + channel: 'browser', + browser_info: { + accept_header: 'unknown', + depth: 100, + java: false, + language: 'US', + height: 1000, + width: 500, + timezone: '-120', + user_agent: 'unknown' + }, + notification_url: 'https://example.com/notification' + } + } + end + + def teardown + BarclaycardSmartpayGateway.ssl_strict = true + end + + def test_successful_purchase + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal '[capture-received]', response.message + end + + def test_failed_purchase + response = @gateway.purchase(@amount, @declined_card, @options) + assert_failure response + assert_equal 'Refused', response.message + end + + def test_successful_purchase_with_unusual_address + response = @gateway.purchase(@amount, + @credit_card, + @options_with_alternate_address) + assert_success response + assert_equal '[capture-received]', response.message + end + + def test_successful_purchase_with_house_number_and_street + response = @gateway.purchase(@amount, + @credit_card, + @options.merge(street: 'Top Level Drive', house_number: '100')) + assert_success response + assert_equal '[capture-received]', response.message + end + + def test_successful_purchase_with_no_address + response = @gateway.purchase(@amount, + @credit_card, + @options_with_no_address) + assert_success response + assert_equal '[capture-received]', response.message + end + + def test_successful_purchase_with_shopper_interaction + response = @gateway.purchase(@amount, @credit_card, @options.merge(shopper_interaction: 'ContAuth')) + assert_success response + assert_equal '[capture-received]', response.message + end + + def test_successful_purchase_with_device_fingerprint + response = @gateway.purchase(@amount, @credit_card, @options.merge(device_fingerprint: 'abcde1123')) + assert_success response + assert_equal '[capture-received]', response.message + end + + def test_successful_purchase_with_shopper_statement + response = @gateway.purchase( + @amount, + @credit_card, + @options.merge(shopper_statement: 'One-year premium subscription') + ) + + assert_success response + assert_equal '[capture-received]', response.message + end + + def test_successful_authorize_with_3ds + assert response = @gateway.authorize(@amount, @three_ds_enrolled_card, @options.merge(execute_threed: true)) + assert_equal 'RedirectShopper', response.message + assert response.test? + refute response.authorization.blank? + refute response.params['issuerUrl'].blank? + refute response.params['md'].blank? + refute response.params['paRequest'].blank? + end + + def test_successful_authorize_with_3ds2_browser_client_data + assert response = @gateway.authorize(@amount, @three_ds_2_enrolled_card, @normalized_3ds_2_options) + assert response.test? + refute response.authorization.blank? + assert_equal response.params['resultCode'], 'IdentifyShopper' + refute response.params['additionalData']['threeds2.threeDS2Token'].blank? + refute response.params['additionalData']['threeds2.threeDSServerTransID'].blank? + refute response.params['additionalData']['threeds2.threeDSMethodURL'].blank? + end + + def test_successful_authorize_with_3ds2_app_based_request + three_ds_app_based_options = { + reference: '345123', + shopper_email: 'john.smith@test.com', + shopper_ip: '77.110.174.153', + shopper_reference: 'John Smith', + billing_address: address(), + order_id: '123', + stored_credential: {reason_type: 'unscheduled'}, + three_ds_2: { + channel: 'app', + } + } + + assert response = @gateway.authorize(@amount, @three_ds_2_enrolled_card, three_ds_app_based_options) + assert response.test? + refute response.authorization.blank? + assert_equal response.params['resultCode'], 'IdentifyShopper' + refute response.params['additionalData']['threeds2.threeDS2Token'].blank? + refute response.params['additionalData']['threeds2.threeDSServerTransID'].blank? + refute response.params['additionalData']['threeds2.threeDS2DirectoryServerInformation.algorithm'].blank? + refute response.params['additionalData']['threeds2.threeDS2DirectoryServerInformation.directoryServerId'].blank? + refute response.params['additionalData']['threeds2.threeDS2DirectoryServerInformation.publicKey'].blank? + end + + def test_successful_authorize_and_capture + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + assert capture = @gateway.capture(@amount, auth.authorization, @options) + assert_success capture + end + + def test_failed_authorize + response = @gateway.authorize(@amount, @declined_card, @options) + assert_failure response + end + + def test_partial_capture + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + assert capture = @gateway.capture(@amount - 1, auth.authorization, @options) + assert_success capture + end + + def test_failed_capture_with_bad_auth + response = @gateway.capture(100, '0000000000000000', @options) + assert_failure response + assert_equal('167: Original pspReference required for this operation', response.message) + end + + def test_failed_capture_with_bad_amount + response = @gateway.capture(nil, '', @options) + assert_failure response + assert_equal('137: Invalid amount specified', response.message) + end + + def test_successful_refund + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + + assert refund = @gateway.refund(@amount, purchase.authorization, @options) + assert_success refund + end + + def test_partial_refund + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + + assert refund = @gateway.refund(@amount - 1, purchase.authorization, @options) + assert_success refund + end + + def test_failed_refund + response = @gateway.refund(nil, nil, @options) + assert_failure response + end + + def test_successful_credit + response = @gateway.credit(@amount, @credit_card, @options_with_credit_fields) + assert_success response + end + + def test_failed_credit + response = @gateway.credit(nil, @declined_card, @options) + assert_failure response + end + + def test_failed_credit_insufficient_validation + # This test will fail currently (the credit will succeed), but it should succeed after October 29th + # response = @gateway.credit(@amount, @credit_card, @options) + # assert_failure response + end + + def test_successful_third_party_payout + response = @gateway.credit(@amount, @credit_card, @options_with_credit_fields.merge({third_party_payout: true})) + assert_success response + end + + def test_successful_void + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + assert void = @gateway.void(auth.authorization, @options) + assert_success void + end + + def test_failed_void + response = @gateway.void(nil, @options) + assert_failure response + end + + def test_successful_verify + assert response = @gateway.verify(@credit_card, @options) + assert_success response + + assert_equal 'Authorised', response.message + assert response.authorization + end + + def test_unsuccessful_verify + assert response = @gateway.verify(@declined_card, @options) + assert_failure response + assert_equal 'Refused', response.message + end + + def test_invalid_login + gateway = BarclaycardSmartpayGateway.new( + company: '', + merchant: '', + password: '' + ) + response = gateway.purchase(@amount, @credit_card, @options) + assert_failure response + end + + def test_successful_store + response = @gateway.store(@credit_card, @options) + assert_success response + assert_equal 'Success', response.message + end + + def test_failed_store + response = @gateway.store(credit_card('4111111111111111', :month => '', :year => '', :verification_value => ''), @options) + assert_failure response + assert_equal '129: Expiry Date Invalid', response.message + end + + # AVS must be enabled on the gateway's end for the test account used + def test_avs_result + response = @gateway.authorize(@amount, @avs_credit_card, @avs_address) + assert_equal 'N', response.avs_result['code'] + end + + def test_avs_no_with_house_number + avs_nohousenumber = @avs_address + avs_nohousenumber[:billing_address].delete(:houseNumberOrName) + response = @gateway.authorize(@amount, @avs_credit_card, avs_nohousenumber) + assert_equal 'Z', response.avs_result['code'] + end + + def test_nonfractional_currency + response = @gateway.authorize(1234, @credit_card, @options.merge(:currency => 'JPY')) + assert_success response + response = @gateway.purchase(1234, @credit_card, @options.merge(:currency => 'JPY')) + assert_success response + end + + def test_three_decimal_currency + response = @gateway.authorize(1234, @credit_card, @options.merge(:currency => 'OMR')) + assert_success response + + response = @gateway.purchase(1234, @credit_card, @options.merge(:currency => 'OMR')) + assert_success response + end + + def test_transcript_scrubbing + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @credit_card, @options) + end + clean_transcript = @gateway.scrub(transcript) + + assert_scrubbed(@credit_card.number, clean_transcript) + assert_scrubbed(@credit_card.verification_value.to_s, clean_transcript) + assert_scrubbed(@gateway.options[:password], clean_transcript) + end + + def test_proper_error_response_handling + response = @gateway.purchase(@error_amount, @credit_card, @options) + assert_equal('702: Internal error', response.message) + assert_not_equal(response.message, 'Unable to communicate with the payment system.') + end +end diff --git a/test/remote/gateways/remote_barclays_epdq_extra_plus_test.rb b/test/remote/gateways/remote_barclays_epdq_extra_plus_test.rb index bc81ac22fff..dd06e16d724 100644 --- a/test/remote/gateways/remote_barclays_epdq_extra_plus_test.rb +++ b/test/remote/gateways/remote_barclays_epdq_extra_plus_test.rb @@ -1,11 +1,13 @@ # coding: utf-8 + require 'test_helper' class RemoteBarclaysEpdqExtraPlusTest < Test::Unit::TestCase def setup @gateway = BarclaysEpdqExtraPlusGateway.new(fixtures(:barclays_epdq_extra_plus)) @amount = 100 - @credit_card = credit_card('4000100011112224') + @credit_card = credit_card('4000100011112224', :verification_value => '987') + @mastercard = credit_card('5399999999999999', :brand => 'mastercard') @declined_card = credit_card('1111111111111111') @credit_card_d3d = credit_card('4000000000000002', :verification_value => '111') @options = { @@ -20,7 +22,7 @@ def test_successful_purchase assert response = @gateway.purchase(@amount, @credit_card, @options) assert_success response assert_equal BarclaysEpdqExtraPlusGateway::SUCCESS_MESSAGE, response.message - assert_equal @options[:currency], response.params["currency"] + assert_equal @options[:currency], response.params['currency'] assert_equal @options[:order_id], response.order_id end @@ -30,18 +32,18 @@ def test_successful_purchase_with_minimal_info assert response = @gateway.purchase(@amount, @credit_card, @options) assert_success response assert_equal BarclaysEpdqExtraPlusGateway::SUCCESS_MESSAGE, response.message - assert_equal @options[:currency], response.params["currency"] + assert_equal @options[:currency], response.params['currency'] assert_equal @options[:order_id], response.order_id end def test_successful_purchase_with_utf8_encoding_1 - assert response = @gateway.purchase(@amount, credit_card('4000100011112224', :first_name => "Rémy", :last_name => "Fröåïør"), @options) + assert response = @gateway.purchase(@amount, credit_card('4000100011112224', :first_name => 'Rémy', :last_name => 'Fröåïør'), @options) assert_success response assert_equal BarclaysEpdqExtraPlusGateway::SUCCESS_MESSAGE, response.message end def test_successful_purchase_with_utf8_encoding_2 - assert response = @gateway.purchase(@amount, credit_card('4000100011112224', :first_name => "ワタシ", :last_name => "ёжзийклмнопрсуфхцч"), @options) + assert response = @gateway.purchase(@amount, credit_card('4000100011112224', :first_name => 'ワタシ', :last_name => 'ёжзийклмнопрсуфхцч'), @options) assert_success response assert_equal BarclaysEpdqExtraPlusGateway::SUCCESS_MESSAGE, response.message end @@ -93,6 +95,12 @@ def test_unsuccessful_purchase assert_equal 'No brand', response.message end + def test_successful_authorize_with_mastercard + assert auth = @gateway.authorize(@amount, @mastercard, @options) + assert_success auth + assert_equal BarclaysEpdqExtraPlusGateway::SUCCESS_MESSAGE, auth.message + end + def test_authorize_and_capture assert auth = @gateway.authorize(@amount, @credit_card, @options) assert_success auth @@ -211,10 +219,20 @@ def test_invalid_login :login => '', :user => '', :password => '', - :signature_encryptor => "none" + :signature_encryptor => 'none' ) assert response = gateway.purchase(@amount, @credit_card, @options) assert_failure response assert_equal 'Some of the data entered is incorrect. please retry.', response.message end + + def test_transcript_scrubbing + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @credit_card, @options) + end + transcript = @gateway.scrub(transcript) + + assert_scrubbed(@credit_card.number, transcript) + assert_scrubbed(@credit_card.verification_value, transcript) + end end diff --git a/test/remote/gateways/remote_barclays_epdq_test.rb b/test/remote/gateways/remote_barclays_epdq_test.rb deleted file mode 100644 index 346390280c8..00000000000 --- a/test/remote/gateways/remote_barclays_epdq_test.rb +++ /dev/null @@ -1,212 +0,0 @@ -require 'test_helper' - -class RemoteBarclaysEpdqTest < Test::Unit::TestCase - def setup - @gateway = BarclaysEpdqGateway.new(fixtures(:barclays_epdq).merge(:test => true)) - - @approved_amount = 3900 - @declined_amount = 4205 - @approved_card = credit_card('4715320629000001') - @declined_card = credit_card('4715320629000027') - - @options = { - :order_id => generate_unique_id, - :billing_address => address, - :description => 'Store Purchase' - } - - @periodic_options = @options.merge( - :payment_number => 1, - :total_payments => 3, - :group_id => 'MyTestPaymentGroup' - ) - end - - def test_successful_purchase - assert response = @gateway.purchase(@approved_amount, @approved_card, @options) - assert_success response - assert_equal 'Approved.', response.message - assert_equal @options[:order_id], response.authorization - assert_no_match(/PaymentNoFraud/, response.params["raw_response"]) - end - - def test_successful_purchase_with_mastercard - assert response = @gateway.purchase(@approved_amount, credit_card('5301250070000050', :brand => :master), @options) - assert_success response - end - - def test_successful_purchase_with_maestro - assert response = @gateway.purchase(@approved_amount, credit_card('675938410597000022', :brand => :maestro, :issue_number => '5'), @options) - assert_success response - end - - def test_successful_purchase_with_switch - assert response = @gateway.purchase(@approved_amount, credit_card('6759560045005727054', :brand => :switch, :issue_number => '1'), @options) - assert_success response - end - - def test_successful_purchase_with_minimal_options - delete_address_details! - - assert response = @gateway.purchase(@approved_amount, @approved_card, @options) - assert_success response - assert_equal 'Approved.', response.message - assert_equal @options[:order_id], response.authorization - assert_no_match(/PaymentNoFraud/, response.params["raw_response"]) - end - - def test_successful_purchase_with_no_fraud - @options[:no_fraud] = true - assert response = @gateway.purchase(@approved_amount, @approved_card, @options) - assert_success response - assert_equal 'Approved.', response.message - assert_equal @options[:order_id], response.authorization - assert_match(/PaymentNoFraud/, response.params["raw_response"]) - end - - def test_successful_purchase_with_no_fraud_and_minimal_options - delete_address_details! - - @options[:no_fraud] = true - assert response = @gateway.purchase(@approved_amount, @approved_card, @options) - assert_success response - assert_equal 'Approved.', response.message - assert_equal @options[:order_id], response.authorization - assert_match(/PaymentNoFraud/, response.params["raw_response"]) - end - - def test_successful_purchase_with_no_address_or_order_id_or_description - assert response = @gateway.purchase(@approved_amount, @approved_card, {}) - assert_success response - assert_equal 'Approved.', response.message - end - - def test_unsuccessful_purchase - assert response = @gateway.purchase(@declined_amount, @declined_card, @options) - assert_failure response - assert_match(/^Declined/, response.message) - end - - def test_credit_new_order - assert response = @gateway.credit(@approved_amount, @approved_card, @options) - assert_success response - assert_equal 'Approved.', response.message - end - - def test_refund_existing_order - assert response = @gateway.purchase(@approved_amount, @approved_card, @options) - assert_success response - - assert refund = @gateway.refund(@approved_amount, response.authorization) - assert_success refund - assert_equal 'Approved.', refund.message - end - - def test_refund_nonexisting_order_fails - assert refund = @gateway.refund(@approved_amount, "DOESNOTEXIST", @options) - assert_failure refund - assert_match(/^Payment Mechanism CreditCard information not found/, refund.message) - end - - def test_authorize_and_capture - amount = @approved_amount - assert auth = @gateway.authorize(amount, @approved_card, @options) - assert_success auth - assert_equal 'Approved.', auth.message - assert auth.authorization - assert_equal @options[:order_id], auth.authorization - - assert capture = @gateway.capture(amount, auth.authorization) - assert_success capture - assert_equal 'Approved.', capture.message - end - - def test_authorize_and_capture_without_order_id - @options.delete(:order_id) - amount = @approved_amount - assert auth = @gateway.authorize(amount, @approved_card, @options) - assert_success auth - assert_equal 'Approved.', auth.message - assert auth.authorization - assert_match(/[0-9a-f\-]{36}/, auth.authorization) - - assert capture = @gateway.capture(amount, auth.authorization) - assert_success capture - assert_equal 'Approved.', capture.message - end - - def test_authorize_void_and_failed_capture - amount = @approved_amount - assert auth = @gateway.authorize(amount, @approved_card, @options) - assert_success auth - - assert void = @gateway.void(auth.authorization) - assert_success void - assert_equal 'Approved.', void.message - - assert capture = @gateway.capture(amount, auth.authorization) - assert_failure capture - assert_match(/^Did not find a unique, qualifying transaction for Order/, capture.message) - end - - def test_failed_authorize - assert auth = @gateway.authorize(@declined_amount, @approved_card, @options) - assert_failure auth - assert_match(/^Declined/, auth.message) - end - - def test_failed_capture - amount = @approved_amount - assert auth = @gateway.authorize(amount, @approved_card, @options) - assert_success auth - - @too_much = amount * 10 - assert capture = @gateway.capture(@too_much, auth.authorization) - assert_success capture - assert_match(/^The PostAuth is not valid because the amount/, capture.message) - end - - def test_three_successful_periodic_orders - amount = @approved_amount - assert auth1 = @gateway.purchase(amount, @approved_card, @periodic_options) - assert auth1.success? - assert_equal 'Approved.', auth1.message - - @periodic_options[:payment_number] = 2 - @periodic_options[:order_id] = generate_unique_id - assert auth2 = @gateway.purchase(amount, @approved_card, @periodic_options) - assert auth2.success? - assert_equal 'Approved.', auth2.message - - @periodic_options[:payment_number] = 3 - @periodic_options[:order_id] = generate_unique_id - assert auth3 = @gateway.purchase(amount, @approved_card, @periodic_options) - assert auth3.success? - assert_equal 'Approved.', auth3.message - end - - def test_invalid_login - gateway = BarclaysEpdqGateway.new( - :login => 'NOBODY', - :password => 'HOME', - :client_id => '1234' - ) - assert response = gateway.purchase(@approved_amount, @approved_card, @options) - assert_failure response - assert_equal 'Insufficient permissions to perform requested operation.', response.message - end - - protected - def delete_address_details! - @options[:billing_address].delete :city - @options[:billing_address].delete :state - @options[:billing_address].delete :country - @options[:billing_address].delete :address1 - @options[:billing_address].delete :phone - @options[:billing_address].delete :address1 - @options[:billing_address].delete :address2 - @options[:billing_address].delete :name - @options[:billing_address].delete :fax - @options[:billing_address].delete :company - end -end diff --git a/test/remote/gateways/remote_be2bill_test.rb b/test/remote/gateways/remote_be2bill_test.rb new file mode 100644 index 00000000000..6e48bdcba5e --- /dev/null +++ b/test/remote/gateways/remote_be2bill_test.rb @@ -0,0 +1,59 @@ +require 'test_helper' + +class RemoteBe2billTest < Test::Unit::TestCase + def setup + @gateway = Be2billGateway.new(fixtures(:be2bill)) + + @amount = 100 + @credit_card = credit_card('4000100011112224') + @declined_card = credit_card('5555557376384001') + + @options = { + :order_id => '1', + :description => 'Store Purchase', + :client_id => '1', + :referrer => 'google.com', + :user_agent => 'Firefox 25', + :ip => '127.0.0.1', + :email => 'customer@yopmail.com' + } + end + + def test_successful_purchase + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal 'Approved : The transaction has been accepted.', response.message + end + + def test_unsuccessful_purchase + assert response = @gateway.purchase(@amount, @declined_card, @options) + assert_failure response + assert_equal 'Declined (4001 - The bank refused the transaction.', response.message + end + + def test_authorize_and_capture + amount = @amount + assert auth = @gateway.authorize(amount, @credit_card, @options) + assert_success auth + assert_equal 'Approved : The transaction has been accepted.', auth.message + assert auth.authorization + assert capture = @gateway.capture(amount, auth.authorization, @options) + assert_success capture + end + + def test_failed_capture + assert response = @gateway.capture(@amount, '') + assert_failure response + assert_equal 'Declined (1001 - The parameter "TRANSACTIONID" is missing.', response.message + end + + def test_invalid_login + gateway = Be2billGateway.new( + :login => '', + :password => '' + ) + assert response = gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_equal 'Declined (1001 - The parameter "IDENTIFIER" is missing.', response.message + end +end diff --git a/test/remote/gateways/remote_beanstream_interac_test.rb b/test/remote/gateways/remote_beanstream_interac_test.rb index a4a053d6b70..4f4d1b9b79e 100644 --- a/test/remote/gateways/remote_beanstream_interac_test.rb +++ b/test/remote/gateways/remote_beanstream_interac_test.rb @@ -1,13 +1,13 @@ require 'test_helper' class RemoteBeanstreamInteracTest < Test::Unit::TestCase - + def setup @gateway = BeanstreamInteracGateway.new(fixtures(:beanstream_interac)) - + @amount = 100 - - @options = { + + @options = { :order_id => generate_unique_id, :billing_address => { :name => 'xiaobo zzz', @@ -27,19 +27,19 @@ def setup :custom => 'reference one' } end - + def test_successful_purchase assert response = @gateway.purchase(@amount, @options) assert_success response - assert_equal "R", response.params["responseType"] + assert_equal 'R', response.params['responseType'] assert_false response.redirect.blank? end - + def test_failed_confirmation - assert response = @gateway.confirm("") + assert response = @gateway.confirm('') assert_failure response end - + def test_invalid_login gateway = BeanstreamInteracGateway.new( :merchant_id => '', diff --git a/test/remote/gateways/remote_beanstream_test.rb b/test/remote/gateways/remote_beanstream_test.rb index 2210c7f2e8a..eed3b72531f 100644 --- a/test/remote/gateways/remote_beanstream_test.rb +++ b/test/remote/gateways/remote_beanstream_test.rb @@ -6,40 +6,51 @@ # only work the first time you run them since the profile, if created again, becomes a duplicate. There is a setting in order settings which, when unchecked will allow the tests to be run any number # of times without needing the manual deletion step between test runs. The setting is: Do not allow profile to be created with card data duplicated from an existing profile. class RemoteBeanstreamTest < Test::Unit::TestCase - + def setup @gateway = BeanstreamGateway.new(fixtures(:beanstream)) - + # Beanstream test cards. Cards require a CVV of 123, which is the default of the credit card helper @visa = credit_card('4030000010001234') @declined_visa = credit_card('4003050500040005') - + @visa_no_cvv = credit_card('4030000010001234', verification_value: nil) + @mastercard = credit_card('5100000010001004') @declined_mastercard = credit_card('5100000020002000') - + @amex = credit_card('371100001000131', {:verification_value => 1234}) - @declined_amex = credit_card('342400001000180') - + @declined_amex = credit_card('342400001000180', {:verification_value => 1234}) + # Canadian EFT @check = check( :institution_number => '001', :transit_number => '26729' ) - + @amount = 1500 - - @options = { + + @options = { :order_id => generate_unique_id, :billing_address => { :name => 'xiaobo zzz', :phone => '555-555-5555', - :address1 => '1234 Levesque St.', + :address1 => '4444 Levesque St.', :address2 => 'Apt B', :city => 'Montreal', - :state => 'QC', + :state => 'Quebec', :country => 'CA', :zip => 'H2C1X8' }, + :shipping_address => { + :name => 'shippy', + :phone => '888-888-8888', + :address1 => '777 Foster Street', + :address2 => 'Ste #100', + :city => 'Durham', + :state => 'North Carolina', + :country => 'US', + :zip => '27701' + }, :email => 'xiaobozzz@example.com', :subtotal => 800, :shipping => 100, @@ -52,12 +63,32 @@ def setup :interval => { :unit => :months, :length => 1 }, :occurences => 5) end - + def test_successful_visa_purchase assert response = @gateway.purchase(@amount, @visa, @options) assert_success response assert_false response.authorization.blank? - assert_equal "Approved", response.message + assert_equal 'Approved', response.message + end + + def test_successful_visa_purchase_with_recurring + assert response = @gateway.purchase(@amount, @visa, @options.merge(recurring: true)) + assert_success response + assert_false response.authorization.blank? + assert_equal 'Approved', response.message + end + + def test_successful_visa_purchase_no_cvv + assert response = @gateway.purchase(@amount, @visa_no_cvv, @options.merge(recurring: true)) + assert_success response + assert_false response.authorization.blank? + assert_equal 'Approved', response.message + end + + def test_unsuccessful_visa_purchase_with_no_cvv + assert response = @gateway.purchase(@amount, @visa_no_cvv, @options) + assert_failure response + assert_equal 'Card CVD is invalid.', response.message end def test_unsuccessful_visa_purchase @@ -65,12 +96,19 @@ def test_unsuccessful_visa_purchase assert_failure response assert_equal 'DECLINE', response.message end - + def test_successful_mastercard_purchase assert response = @gateway.purchase(@amount, @mastercard, @options) assert_success response assert_false response.authorization.blank? - assert_equal "Approved", response.message + assert_equal 'Approved', response.message + end + + def test_successful_mastercard_purchase_with_recurring + assert response = @gateway.purchase(@amount, @mastercard, @options.merge(recurring: true)) + assert_success response + assert_false response.authorization.blank? + assert_equal 'Approved', response.message end def test_unsuccessful_mastercard_purchase @@ -78,12 +116,19 @@ def test_unsuccessful_mastercard_purchase assert_failure response assert_equal 'DECLINE', response.message end - + def test_successful_amex_purchase assert response = @gateway.purchase(@amount, @amex, @options) assert_success response assert_false response.authorization.blank? - assert_equal "Approved", response.message + assert_equal 'Approved', response.message + end + + def test_successful_amex_purchase_with_recurring + assert response = @gateway.purchase(@amount, @amex, @options.merge(recurring: true)) + assert_success response + assert_false response.authorization.blank? + assert_equal 'Approved', response.message end def test_unsuccessful_amex_purchase @@ -92,82 +137,163 @@ def test_unsuccessful_amex_purchase assert_equal 'DECLINE', response.message end + def test_successful_purchase_with_state_in_iso_format + assert response = @gateway.purchase(@amount, @visa, @options.merge(billing_address: address, shipping_address: address)) + assert_success response + assert_false response.authorization.blank? + assert_equal 'Approved', response.message + end + + def test_successful_purchase_with_only_email + options = { + :order_id => generate_unique_id, + :email => 'xiaobozzz@example.com', + :shipping_email => 'ship@mail.com' + } + + assert response = @gateway.purchase(@amount, @visa, options) + assert_success response + assert_false response.authorization.blank? + assert_equal 'Approved', response.message + end + + def test_successful_purchase_with_no_addresses + @options[:billing_address] = {} + @options[:shipping_address] = {} + assert response = @gateway.purchase(@amount, @visa, @options) + assert_success response + assert_false response.authorization.blank? + assert_equal 'Approved', response.message + end + + def test_failed_purchase_due_to_invalid_billing_state + @options[:billing_address][:state] = 'Invalid' + assert response = @gateway.purchase(@amount, @visa, @options) + assert_failure response + assert_match %r{province does not match country}, response.message + end + + def test_failed_purchase_due_to_invalid_shipping_state + @options[:shipping_address][:state] = 'North' + assert response = @gateway.purchase(@amount, @visa, @options) + assert_failure response + assert_match %r{Invalid shipping province}, response.message + end + + def test_failed_purchase_due_to_missing_country_with_state + @options[:shipping_address][:country] = nil + assert response = @gateway.purchase(@amount, @visa, @options) + assert_failure response + assert_match %r{Invalid shipping country id}, response.message + end + def test_authorize_and_capture assert auth = @gateway.authorize(@amount, @visa, @options) assert_success auth - assert_equal "Approved", auth.message + assert_equal 'Approved', auth.message assert_false auth.authorization.blank? - + assert capture = @gateway.capture(@amount, auth.authorization) assert_success capture assert_false capture.authorization.blank? end - + + def test_authorize_and_capture_with_recurring + assert auth = @gateway.authorize(@amount, @visa, @options.merge(recurring: true)) + assert_success auth + assert_equal 'Approved', auth.message + assert_false auth.authorization.blank? + + assert capture = @gateway.capture(@amount, auth.authorization, recurring: true) + assert_success capture + assert_false capture.authorization.blank? + end + + def test_successful_verify + response = @gateway.verify(@visa, @options) + assert_success response + assert_match 'Approved', response.message + end + + def test_failed_verify + response = @gateway.verify(@declined_amex, @options) + assert_failure response + assert_match 'DECLINE', response.message + end + def test_failed_capture assert response = @gateway.capture(@amount, '') assert_failure response - assert_no_match %r{You are not authorized}, response.message, "You need to enable username/password validation" + assert_no_match %r{You are not authorized}, response.message, 'You need to enable username/password validation' assert_match %r{Missing or invalid adjustment id.}, response.message end - + def test_successful_purchase_and_void assert purchase = @gateway.purchase(@amount, @visa, @options) assert_success purchase - + + assert void = @gateway.void(purchase.authorization) + assert_success void + end + + def test_successful_purchase_and_void_with_recurring + assert purchase = @gateway.purchase(@amount, @visa, @options.merge(recurring: true)) + assert_success purchase + assert void = @gateway.void(purchase.authorization) assert_success void end - + def test_successful_purchase_and_refund_and_void_refund assert purchase = @gateway.purchase(@amount, @visa, @options) assert_success purchase - + assert refund = @gateway.refund(@amount, purchase.authorization) assert_success purchase - + assert void = @gateway.void(refund.authorization) assert_success void end - + def test_successful_check_purchase assert response = @gateway.purchase(@amount, @check, @options) assert_success response assert response.test? assert_false response.authorization.blank? end - + def test_successful_check_purchase_and_refund assert purchase = @gateway.purchase(@amount, @check, @options) assert_success purchase - + assert refund = @gateway.refund(@amount, purchase.authorization) - assert_success credit + assert_success refund end - + def test_successful_recurring assert response = @gateway.recurring(@amount, @visa, @recurring_options) assert_success response assert response.test? assert_false response.authorization.blank? end - + def test_successful_update_recurring assert response = @gateway.recurring(@amount, @visa, @recurring_options) assert_success response assert response.test? assert_false response.authorization.blank? - - assert response = @gateway.update_recurring(@amount + 500, @visa, @recurring_options.merge(:account_id => response.params["rbAccountId"])) + + assert response = @gateway.update_recurring(@amount + 500, @visa, @recurring_options.merge(:account_id => response.params['rbAccountId'])) assert_success response end - + def test_successful_cancel_recurring assert response = @gateway.recurring(@amount, @visa, @recurring_options) assert_success response assert response.test? assert_false response.authorization.blank? - - assert response = @gateway.cancel_recurring(:account_id => response.params["rbAccountId"]) + + assert response = @gateway.cancel_recurring(:account_id => response.params['rbAccountId']) assert_success response end @@ -179,22 +305,29 @@ def test_invalid_login ) assert response = gateway.purchase(@amount, @visa, @options) assert_failure response - assert_equal 'Invalid merchant id (merchant_id = 0)', response.message + assert_equal 'merchantid=Invalid merchant id (merchant_id = )', response.message end - + def test_successful_add_to_vault_with_store_method - assert response = @gateway.store(@visa,@options) + assert response = @gateway.store(@visa, @options) assert_equal 'Operation Successful', response.message assert_success response - assert_not_nil response.params["customer_vault_id"] + assert_not_nil response.params['customer_vault_id'] end - + def test_add_to_vault_with_custom_vault_id_with_store_method - @options[:vault_id] = rand(100000)+10001 + @options[:vault_id] = rand(10001..110000) assert response = @gateway.store(@visa, @options.dup) assert_equal 'Operation Successful', response.message assert_success response - assert_equal @options[:vault_id], response.params["customer_vault_id"].to_i + assert_equal @options[:vault_id], response.params['customer_vault_id'].to_i + end + + def test_successful_add_to_vault_with_single_use_token + assert response = @gateway.store(generate_single_use_token(@visa)) + assert_equal 'Operation Successful', response.message, response.inspect + assert_success response + assert_not_nil response.params['customer_vault_id'] end def test_update_vault @@ -203,41 +336,81 @@ def test_update_vault assert_success response assert_equal 'Operation Successful', response.message end - + + def test_update_vault_with_single_use_token + test_add_to_vault_with_custom_vault_id_with_store_method + assert response = @gateway.update(@options[:vault_id], generate_single_use_token(@mastercard)) + assert_success response + assert_equal 'Operation Successful', response.message + end + def test_delete_from_vault test_add_to_vault_with_custom_vault_id_with_store_method assert response = @gateway.delete(@options[:vault_id]) assert_success response assert_equal 'Operation Successful', response.message end - + def test_delete_from_vault_with_unstore_method test_add_to_vault_with_custom_vault_id_with_store_method assert response = @gateway.unstore(@options[:vault_id]) assert_success response assert_equal 'Operation Successful', response.message end - + def test_successful_add_to_vault_and_use test_add_to_vault_with_custom_vault_id_with_store_method assert second_response = @gateway.purchase(@amount*2, @options[:vault_id], @options) assert_equal 'Approved', second_response.message - assert second_response.success? + assert second_response.success? end - + def test_unsuccessful_visa_with_vault test_add_to_vault_with_custom_vault_id_with_store_method assert response = @gateway.update(@options[:vault_id], @declined_visa) assert_success response - + assert second_response = @gateway.purchase(@amount*2, @options[:vault_id], @options) assert_equal 'DECLINE', second_response.message end - + def test_unsuccessful_closed_profile_charge test_delete_from_vault assert second_response = @gateway.purchase(@amount*2, @options[:vault_id], @options) assert_failure second_response - assert_equal "Invalid customer code.", second_response.message + assert_match %r{Invalid customer code\.}, second_response.message + end + + def test_transcript_scrubbing + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @visa, @options) + end + clean_transcript = @gateway.scrub(transcript) + + assert_scrubbed(@visa.number, clean_transcript) + assert_scrubbed(@visa.verification_value.to_s, clean_transcript) + assert_scrubbed(@gateway.options[:password], clean_transcript) + assert_scrubbed(@gateway.options[:api_key], clean_transcript) + end + + private + + def generate_single_use_token(credit_card) + uri = URI.parse('https://www.beanstream.com/scripts/tokenization/tokens') + http = Net::HTTP.new(uri.host, uri.port) + http.use_ssl = true + http.verify_mode = OpenSSL::SSL::VERIFY_NONE + + request = Net::HTTP::Post.new(uri.path) + request.content_type = 'application/json' + request.body = { + 'number' => credit_card.number, + 'expiry_month' => '01', + 'expiry_year' => (Time.now.year + 1) % 100, + 'cvd' => credit_card.verification_value, + }.to_json + + response = http.request(request) + JSON.parse(response.body)['token'] end end diff --git a/test/remote/gateways/remote_blue_pay_test.rb b/test/remote/gateways/remote_blue_pay_test.rb index ab0ced7e927..4c0fc7b5477 100644 --- a/test/remote/gateways/remote_blue_pay_test.rb +++ b/test/remote/gateways/remote_blue_pay_test.rb @@ -1,172 +1,183 @@ -require 'test_helper' - -class BluePayTest < Test::Unit::TestCase - def setup - Base.mode = :test - - @gateway = BluePayGateway.new(fixtures(:blue_pay)) - @amount = 100 - @credit_card = credit_card('4242424242424242') - @options = { - :order_id => generate_unique_id, - :billing_address => address, - :description => 'Store purchase' - } - - @recurring_options = { - :rebill_amount => 100, - :rebill_start_date => Date.today, - :rebill_expression => '1 DAY', - :rebill_cycles => '4', - :billing_address => address.merge(:first_name => 'Jim', :last_name => 'Smith'), - :duplicate_override => 1 - } - end - - def test_successful_purchase - assert response = @gateway.purchase(@amount, @credit_card, @options) - assert_success response - assert response.test? - assert_equal 'This transaction has been approved', response.message - assert response.authorization - end - - # The included test account credentials do not support ACH processor. - def test_successful_purchase_with_check - assert response = @gateway.purchase(@amount, check, @options.merge(:email=>'foo@example.com')) - assert_success response - assert response.test? - assert_equal 'This transaction has been approved', response.message - assert response.authorization - end - - def test_expired_credit_card - @credit_card.year = 2004 - assert response = @gateway.purchase(@amount, @credit_card, @options) - assert_failure response - assert response.test? - assert_equal 'The credit card has expired', response.message - end - - def test_forced_test_mode_purchase - gateway = BluePayGateway.new(fixtures(:blue_pay).update(:test => true)) - assert response = gateway.purchase(@amount, @credit_card, @options) - assert_success response - assert response.test? - assert_equal(true, response.test) - assert response.authorization - end - - def test_successful_authorization - assert response = @gateway.authorize(@amount, @credit_card, @options) - assert_success response - assert_equal 'This transaction has been approved', response.message - assert response.authorization - end - - def test_that_we_understand_and_parse_all_keys_in_standard_response - assert response = @gateway.authorize(@amount, @credit_card, @options) - assert_success response - - response_keys = response.params.keys.map(&:to_sym) - unknown_response_keys = response_keys - BluePayGateway::FIELD_MAP.values - missing_response_keys = BluePayGateway::FIELD_MAP.values - response_keys - - assert_empty unknown_response_keys, "unknown_response_keys" - assert_empty missing_response_keys, "missing response_keys" - end - - def test_that_we_understand_and_parse_all_keys_in_rebilling_response - assert response = @gateway.recurring(@amount, @credit_card, @recurring_options) - assert_success response - rebill_id = response.params['rebid'] - assert response = @gateway.update_recurring(:rebill_id => rebill_id, :rebill_amount => @amount * 2) - assert_success response - - response_keys = response.params.keys.map(&:to_sym) - unknown_response_keys = response_keys - BluePayGateway::REBILL_FIELD_MAP.values - missing_response_keys = BluePayGateway::REBILL_FIELD_MAP.values - response_keys - - assert_empty unknown_response_keys, "unknown_response_keys" - assert_empty missing_response_keys, "missing response_keys" - end - - def test_authorization_and_capture - assert authorization = @gateway.authorize(@amount, @credit_card, @options) - assert_success authorization - assert capture = @gateway.capture(@amount, authorization.authorization) - assert_success capture - assert_equal 'This transaction has been approved', capture.message - end - - def test_authorization_and_void - assert authorization = @gateway.authorize(@amount, @credit_card, @options) - assert_success authorization - - assert void = @gateway.void(authorization.authorization) - assert_success void - assert_equal 'This transaction has been approved', void.message - end - - def test_bad_login - gateway = BluePayGateway.new( - :login => 'X', - :password => 'Y' - ) - assert response = gateway.purchase(@amount, @credit_card) - - assert_equal Response, response.class - assert_match(/The merchant login ID or password is invalid/, response.message) - assert_failure response - end - - def test_using_test_request - gateway = BluePayGateway.new( - :login => 'X', - :password => 'Y' - ) - assert response = gateway.purchase(@amount, @credit_card) - assert_equal Response, response.class - - assert_match(/The merchant login ID or password is invalid/, response.message) - assert_failure response - end - - def test_successful_recurring - assert response = @gateway.recurring(@amount, @credit_card, @recurring_options) - assert_success response - assert response.test? - - rebill_id = response.params['rebid'] - - assert response = @gateway.update_recurring(:rebill_id => rebill_id, :rebill_amount => @amount * 2) - assert_success response - - assert response = @gateway.status_recurring(rebill_id) - assert_success response - assert_equal response.params['status'], 'active' - - assert response = @gateway.cancel_recurring(rebill_id) - assert_success response - assert_equal response.params['status'], 'stopped' - end - - def test_recurring_should_fail_expired_credit_card - @credit_card.year = 2004 - assert response = @gateway.recurring(@amount, @credit_card, @recurring_options) - assert_failure response - assert response.test? - assert_equal 'The credit card has expired', response.message - end - - def test_successful_purchase_with_solution_id - ActiveMerchant::Billing::BluePayGateway.application_id = 'A1000000' - assert response = @gateway.purchase(@amount, @credit_card, @options) - assert_success response - assert response.test? - assert_equal 'This transaction has been approved', response.message - assert response.authorization - ensure - ActiveMerchant::Billing::BluePayGateway.application_id = nil - end -end +require 'test_helper' + +class BluePayTest < Test::Unit::TestCase + def setup + Base.mode = :test + + @gateway = BluePayGateway.new(fixtures(:blue_pay)) + @amount = 100 + @credit_card = credit_card('4242424242424242') + @options = { + :order_id => generate_unique_id, + :billing_address => address, + :description => 'Store purchase', + :ip => '192.168.0.1' + } + + @recurring_options = { + :rebill_amount => 100, + :rebill_start_date => Date.today, + :rebill_expression => '1 DAY', + :rebill_cycles => '4', + :billing_address => address.merge(:first_name => 'Jim', :last_name => 'Smith'), + :duplicate_override => 1 + } + end + + def test_successful_purchase + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert response.test? + assert_equal 'This transaction has been approved', response.message + assert response.authorization + end + + # The included test account credentials do not support ACH processor. + def test_successful_purchase_with_check + assert response = @gateway.purchase(@amount, check, @options.merge(:email=>'foo@example.com')) + assert_success response + assert response.test? + assert_equal 'App ACH Sale', response.message + assert response.authorization + end + + def test_expired_credit_card + @credit_card.year = 2004 + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert response.test? + assert_equal 'The credit card has expired', response.message + end + + def test_forced_test_mode_purchase + gateway = BluePayGateway.new(fixtures(:blue_pay).update(:test => true)) + assert response = gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert response.test? + assert_equal(true, response.test) + assert response.authorization + end + + def test_successful_authorization + assert response = @gateway.authorize(@amount, @credit_card, @options) + assert_success response + assert_equal 'This transaction has been approved', response.message + assert response.authorization + end + + def test_that_we_understand_and_parse_all_keys_in_standard_response + assert response = @gateway.authorize(@amount, @credit_card, @options) + assert_success response + + response_keys = response.params.keys.map(&:to_sym) + unknown_response_keys = response_keys - BluePayGateway::FIELD_MAP.values + missing_response_keys = BluePayGateway::FIELD_MAP.values - response_keys + + assert_empty unknown_response_keys, 'unknown_response_keys' + assert_empty missing_response_keys, 'missing response_keys' + end + + def test_that_we_understand_and_parse_all_keys_in_rebilling_response + assert response = @gateway.recurring(@amount, @credit_card, @recurring_options) + assert_success response + rebill_id = response.params['rebid'] + assert response = @gateway.update_recurring(:rebill_id => rebill_id, :rebill_amount => @amount * 2) + assert_success response + + response_keys = response.params.keys.map(&:to_sym) + unknown_response_keys = response_keys - BluePayGateway::REBILL_FIELD_MAP.values + missing_response_keys = BluePayGateway::REBILL_FIELD_MAP.values - response_keys + + assert_empty unknown_response_keys, 'unknown_response_keys' + assert_empty missing_response_keys, 'missing response_keys' + end + + def test_authorization_and_capture + assert authorization = @gateway.authorize(@amount, @credit_card, @options) + assert_success authorization + assert capture = @gateway.capture(@amount, authorization.authorization) + assert_success capture + assert_equal 'This transaction has been approved', capture.message + end + + def test_authorization_and_void + assert authorization = @gateway.authorize(@amount, @credit_card, @options) + assert_success authorization + + assert void = @gateway.void(authorization.authorization) + assert_success void + assert_equal 'This transaction has been approved', void.message + end + + def test_bad_login + gateway = BluePayGateway.new( + :login => 'X', + :password => 'Y' + ) + assert response = gateway.purchase(@amount, @credit_card) + + assert_equal Response, response.class + assert_match(/The merchant login ID or password is invalid/, response.message) + assert_failure response + end + + def test_using_test_request + gateway = BluePayGateway.new( + :login => 'X', + :password => 'Y' + ) + assert response = gateway.purchase(@amount, @credit_card) + assert_equal Response, response.class + + assert_match(/The merchant login ID or password is invalid/, response.message) + assert_failure response + end + + def test_successful_recurring + assert response = @gateway.recurring(@amount, @credit_card, @recurring_options) + assert_success response + assert response.test? + + rebill_id = response.params['rebid'] + + assert response = @gateway.update_recurring(:rebill_id => rebill_id, :rebill_amount => @amount * 2) + assert_success response + + assert response = @gateway.status_recurring(rebill_id) + assert_success response + assert_equal response.params['status'], 'active' + + assert response = @gateway.cancel_recurring(rebill_id) + assert_success response + assert_equal response.params['status'], 'stopped' + end + + def test_recurring_should_fail_expired_credit_card + @credit_card.year = 2004 + assert response = @gateway.recurring(@amount, @credit_card, @recurring_options) + assert_failure response + assert response.test? + assert_equal 'The credit card has expired', response.message + end + + def test_successful_purchase_with_solution_id + ActiveMerchant::Billing::BluePayGateway.application_id = 'A1000000' + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert response.test? + assert_equal 'This transaction has been approved', response.message + assert response.authorization + ensure + ActiveMerchant::Billing::BluePayGateway.application_id = nil + end + + def test_transcript_scrubbing + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @credit_card, @options) + end + clean_transcript = @gateway.scrub(transcript) + + assert_scrubbed(@credit_card.number, clean_transcript) + assert_scrubbed(@credit_card.verification_value.to_s, clean_transcript) + end +end diff --git a/test/remote/gateways/remote_blue_snap_test.rb b/test/remote/gateways/remote_blue_snap_test.rb new file mode 100644 index 00000000000..da9bf2c2ce1 --- /dev/null +++ b/test/remote/gateways/remote_blue_snap_test.rb @@ -0,0 +1,395 @@ +require 'test_helper' + +class RemoteBlueSnapTest < Test::Unit::TestCase + def setup + @gateway = BlueSnapGateway.new(fixtures(:blue_snap)) + + @amount = 100 + @credit_card = credit_card('4263982640269299') + @cabal_credit_card = credit_card('6271701225979642') + @declined_card = credit_card('4917484589897107', month: 1, year: 2023) + @invalid_card = credit_card('4917484589897106', month: 1, year: 2023) + @three_ds_visa_card = credit_card('4000000000001091', month: 1) + @three_ds_master_card = credit_card('5200000000001096', month: 1) + @invalid_cabal_card = credit_card('5896 5700 0000 0000', month: 1, year: 2023) + + @options = { billing_address: address } + @options_3ds2 = @options.merge( + three_d_secure: { + eci: '05', + cavv: 'AAABAWFlmQAAAABjRWWZEEFgFz+A', + xid: 'MGpHWm5ZWVpKclo0aUk0VmltVDA=', + ds_transaction_id: 'jhg34-sdgds87-sdg87-sdfg7', + version: '2.2.0' + } + ) + + @check = check + @invalid_check = check(:routing_number => '123456', :account_number => '123456789') + @valid_check_options = { + billing_address: { + address1: '123 Street', + address2: 'Apt 1', + city: 'Happy City', + state: 'CA', + zip: '94901' + }, + authorized_by_shopper: true + } + end + + def test_successful_purchase + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal 'Success', response.message + end + + def test_successful_purchase_sans_options + response = @gateway.purchase(@amount, @credit_card) + assert_success response + assert_equal 'Success', response.message + end + + def test_successful_purchase_with_more_options + more_options = @options.merge({ + order_id: '1', + ip: '127.0.0.1', + email: 'joe@example.com', + description: 'Product Description', + soft_descriptor: 'OnCardStatement', + personal_identification_number: 'CNPJ' + }) + + response = @gateway.purchase(@amount, @credit_card, more_options) + assert_success response + assert_equal 'Success', response.message + end + + def test_successful_purchase_with_3ds2_auth + response = @gateway.purchase(@amount, @three_ds_visa_card, @options_3ds2) + assert_success response + assert_equal 'Success', response.message + end + + def test_successful_purchase_with_currency + response = @gateway.purchase(@amount, @credit_card, @options.merge(currency: 'CAD')) + assert_success response + + assert_equal 'Success', response.message + assert_equal 'CAD', response.params['currency'] + end + + def test_successful_purchase_with_level3_data + l_three_visa = credit_card('4111111111111111', month: 2, year: 2023) + options = @options.merge({ + customer_reference_number: '1234A', + sales_tax_amount: 0.6, + freight_amount: 0, + duty_amount: 0, + destination_zip_code: 12345, + destination_country_code: 'us', + ship_from_zip_code: 12345, + discount_amount: 0, + tax_amount: 0.6, + tax_rate: 6.0, + level_3_data_items: [ + { + line_item_total: 9.00, + description: 'test_desc', + product_code: 'test_code', + item_quantity: 1.0, + tax_rate: 6.0, + tax_amount: 0.60, + unit_of_measure: 'lb', + commodity_code: 123, + discount_indicator: 'Y', + gross_net_indicator: 'Y', + tax_type: 'test', + unit_cost: 10.00 + }, + { + line_item_total: 9.00, + description: 'test_2', + product_code: 'test_2', + item_quantity: 1.0, + tax_rate: 7.0, + tax_amount: 0.70, + unit_of_measure: 'lb', + commodity_code: 123, + discount_indicator: 'Y', + gross_net_indicator: 'Y', + tax_type: 'test', + unit_cost: 14.00 + } + ] + }) + response = @gateway.purchase(@amount, l_three_visa, options) + + assert_success response + assert_equal 'Success', response.message + assert_equal '1234A', response.params['customer-reference-number'] + assert_equal '9', response.params['line-item-total'] + end + + def test_successful_purchase_with_unused_state_code + unrecognized_state_code_options = { + billing_address: { + city: 'Dresden', + state: 'Sachsen', + country: 'DE', + zip: '01069' + } + } + + response = @gateway.purchase(@amount, @credit_card, unrecognized_state_code_options) + assert_success response + assert_equal 'Success', response.message + end + + def test_successful_echeck_purchase + response = @gateway.purchase(@amount, @check, @options.merge(@valid_check_options)) + assert_success response + assert_equal 'Success', response.message + end + + def test_failed_purchase + response = @gateway.purchase(@amount, @declined_card, @options) + assert_failure response + assert_match(/Authorization has failed for this transaction/, response.message) + assert_equal '14002', response.error_code + end + + def test_failed_purchase_with_invalid_cabal_card + response = @gateway.purchase(@amount, @invalid_cabal_card, @options) + assert_failure response + assert_match(/'Card Number' should be a valid Credit Card/, response.message) + assert_equal '10001', response.error_code + end + + def test_cvv_result + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal 'CVV not processed', response.cvv_result['message'] + assert_equal 'P', response.cvv_result['code'] + end + + def test_avs_result + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal 'Address not verified.', response.avs_result['message'] + assert_equal 'I', response.avs_result['code'] + end + + def test_failed_echeck_purchase + response = @gateway.purchase(@amount, @invalid_check, @options.merge(@valid_check_options)) + assert_failure response + assert_match(/ECP data validity check failed/, response.message) + assert_equal '10001', response.error_code + end + + def test_failed_unauthorized_echeck_purchase + response = @gateway.purchase(@amount, @check, @options.merge({authorized_by_shopper: false})) + assert_failure response + assert_match(/The payment was not authorized by shopper/, response.message) + assert_equal '16004', response.error_code + end + + def test_successful_authorize_and_capture + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + assert capture = @gateway.capture(@amount, auth.authorization) + assert_success capture + assert_equal 'Success', capture.message + end + + def test_successful_authorize_and_partial_capture + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + assert capture = @gateway.capture(@amount - 1, auth.authorization) + assert_success capture + assert_equal 'Success', capture.message + end + + def test_successful_authorize_and_capture_with_3ds2_auth + auth = @gateway.authorize(@amount, @three_ds_master_card, @options_3ds2) + assert_success auth + + assert capture = @gateway.capture(@amount, auth.authorization) + assert_success capture + assert_equal 'Success', capture.message + end + + def test_failed_authorize + response = @gateway.authorize(@amount, @declined_card, @options) + assert_failure response + assert_match(/Authorization has failed for this transaction/, response.message) + end + + def test_partial_capture_succeeds_even_though_amount_is_ignored_by_gateway + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + assert capture = @gateway.capture(@amount-1, auth.authorization) + assert_success capture + end + + def test_failed_capture + response = @gateway.capture(@amount, '') + assert_failure response + assert_match(/due to missing transaction ID/, response.message) + end + + def test_successful_refund + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + + assert refund = @gateway.refund(@amount, purchase.authorization, @options) + assert_success refund + assert_equal 'Success', refund.message + end + + def test_partial_refund + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + + assert refund = @gateway.refund(@amount-1, purchase.authorization) + assert_success refund + end + + def test_failed_refund + response = @gateway.refund(@amount, '') + assert_failure response + assert_match(/cannot be completed due to missing transaction ID/, response.message) + end + + def test_successful_void + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + assert void = @gateway.void(auth.authorization) + assert_success void + assert_equal 'Success', void.message + end + + def test_failed_void + response = @gateway.void('') + assert_failure response + assert_match(/cannot be completed due to missing transaction ID/, response.message) + end + + def test_successful_verify + response = @gateway.verify(@credit_card, @options) + assert_success response + assert_equal 'Success', response.message + end + + def test_failed_verify + response = @gateway.verify(@declined_card, @options) + assert_failure response + assert_match(/Transaction failed because of payment processing failure/, response.message) + end + + def test_successful_store + assert response = @gateway.store(@credit_card, @options) + + assert_success response + assert_equal 'Success', response.message + assert response.authorization + assert_equal 'I', response.avs_result['code'] + assert_equal 'P', response.cvv_result['code'] + assert_match(/services\/2\/vaulted-shoppers/, response.params['content-location-header']) + end + + def test_successful_echeck_store + assert response = @gateway.store(@check, @options.merge(@valid_check_options)) + + assert_success response + assert_equal 'Success', response.message + assert response.authorization + assert_match(/services\/2\/vaulted-shoppers/, response.params['content-location-header']) + end + + def test_failed_store + assert response = @gateway.store(@invalid_card, @options) + + assert_failure response + assert_match(/'Card Number' should be a valid Credit Card/, response.message) + assert_equal '10001', response.error_code + end + + def test_failed_echeck_store + assert response = @gateway.store(@invalid_check, @options) + + assert_failure response + assert_match(/ECP data validity check failed/, response.message) + assert_equal '10001', response.error_code + end + + def test_successful_purchase_using_stored_card + assert store_response = @gateway.store(@credit_card, @options) + assert_success store_response + + response = @gateway.purchase(@amount, store_response.authorization, @options) + assert_success response + assert_equal 'Success', response.message + end + + def test_successful_purchase_using_stored_echeck + assert store_response = @gateway.store(@check, @options.merge(@valid_check_options)) + assert_success store_response + assert_match(/check/, store_response.authorization) + + response = @gateway.purchase(@amount, store_response.authorization, @options.merge({authorized_by_shopper: true})) + assert_success response + assert_equal 'Success', response.message + end + + def test_successful_authorize_using_stored_card + assert store_response = @gateway.store(@credit_card, @options) + assert_success store_response + + response = @gateway.authorize(@amount, store_response.authorization, @options) + assert_success response + assert_equal 'Success', response.message + end + + def test_invalid_login + gateway = BlueSnapGateway.new(api_username: 'unknown', api_password: 'unknown') + + response = gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_match 'Unable to authenticate. Please check your credentials.', response.message + end + + def test_verify_credentials + assert @gateway.verify_credentials + + gateway = BlueSnapGateway.new(api_username: 'unknown', api_password: 'unknown') + assert !gateway.verify_credentials + end + + def test_transcript_scrubbing + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @credit_card, @options) + end + transcript = @gateway.scrub(transcript) + + assert_scrubbed(@credit_card.number, transcript) + assert_scrubbed(@credit_card.verification_value, transcript) + assert_scrubbed(@gateway.options[:api_password], transcript) + end + + def test_transcript_scrubbing_with_echeck + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @check, @valid_check_options) + end + transcript = @gateway.scrub(transcript) + + assert_scrubbed(@check.account_number, transcript) + assert_scrubbed(@check.routing_number, transcript) + assert_scrubbed(@gateway.options[:api_password], transcript) + end + +end diff --git a/test/remote/gateways/remote_borgun_test.rb b/test/remote/gateways/remote_borgun_test.rb new file mode 100644 index 00000000000..32f7b1b6fc7 --- /dev/null +++ b/test/remote/gateways/remote_borgun_test.rb @@ -0,0 +1,176 @@ +require 'test_helper' + +class RemoteBorgunTest < Test::Unit::TestCase + def setup + # Borgun's test server has an improperly installed cert + BorgunGateway.ssl_strict = false + + @gateway = BorgunGateway.new(fixtures(:borgun)) + + @amount = 100 + @credit_card = credit_card('5587402000012011', year: 2018, month: 9, verification_value: 415) + @declined_card = credit_card('4155520000000002') + + @options = { + order_id: '1', + billing_address: address, + description: 'Store Purchase' + } + end + + def teardown + BorgunGateway.ssl_strict = true + end + + def test_successful_purchase + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal 'Succeeded', response.message + end + + def test_successful_purchase_usd + response = @gateway.purchase(@amount, @credit_card, @options.merge(currency: 'USD')) + assert_success response + assert_equal 'Succeeded', response.message + end + + def test_successful_purchase_without_options + response = @gateway.purchase(@amount, @credit_card) + assert_success response + assert_equal 'Succeeded', response.message + end + + def test_failed_purchase + response = @gateway.purchase(@amount, @declined_card, @options) + assert_failure response + assert_equal 'Error with ActionCode=121', response.message + end + + def test_successful_authorize_and_capture + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + assert capture = @gateway.capture(@amount, auth.authorization) + assert_success capture + end + + def test_successful_authorize_and_capture_usd + auth = @gateway.authorize(@amount, @credit_card, @options.merge(currency: 'USD')) + assert_success auth + + assert capture = @gateway.capture(@amount, auth.authorization, currency: 'USD') + assert_success capture + end + + def test_failed_authorize + response = @gateway.authorize(@amount, @declined_card, @options) + assert_failure response + end + + def test_partial_capture + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + assert capture = @gateway.capture(@amount-1, auth.authorization) + assert_success capture + end + + def test_failed_capture + response = @gateway.capture(nil, '') + assert_failure response + end + + def test_successful_refund + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + + assert refund = @gateway.refund(@amount, purchase.authorization) + assert_success refund + end + + def test_successful_refund_usd + purchase = @gateway.purchase(@amount, @credit_card, @options.merge(currency: 'USD')) + assert_success purchase + + assert refund = @gateway.refund(@amount, purchase.authorization, currency: 'USD') + assert_success refund + end + + def test_partial_refund + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + + assert refund = @gateway.refund(@amount-1, purchase.authorization) + assert_success refund + end + + def test_failed_refund + response = @gateway.refund(nil, '') + assert_failure response + end + + def test_successful_void + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + assert void = @gateway.void(auth.authorization) + assert_success void + end + + def test_successful_void_with_no_currency_in_authorization + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + *new_auth, _ = auth.authorization.split('|') + assert void = @gateway.void(new_auth.join('|')) + assert_success void + end + + def test_successful_void_usd + auth = @gateway.authorize(@amount, @credit_card, @options.merge(currency: 'USD')) + assert_success auth + + assert void = @gateway.void(auth.authorization) + assert_success void + end + + def test_successful_void_usd_with_options + auth = @gateway.authorize(@amount, @credit_card, @options.merge(currency: 'USD')) + assert_success auth + + assert void = @gateway.void(auth.authorization, @options.merge(currency: 'USD')) + assert_success void + end + + def test_failed_void + response = @gateway.void('') + assert_failure response + end + + # This test does not consistently pass. When run multiple times within 1 minute, + # an ActiveMerchant::ConnectionError(<The remote server reset the connection>) + # exception is raised. + def test_invalid_login + gateway = BorgunGateway.new( + processor: '0', + merchant_id: '0', + username: 'not', + password: 'right' + ) + authentication_exception = assert_raise ActiveMerchant::ResponseError, 'Failed with 401 [ISS.0084.9001] Invalid credentials' do + gateway.purchase(@amount, @credit_card, @options) + end + assert response = authentication_exception.response + assert_match(/Access Denied/, response.body) + end + + def test_transcript_scrubbing + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @credit_card, @options) + end + transcript = @gateway.scrub(transcript) + + assert_scrubbed(@credit_card.number, transcript) + assert_scrubbed(@credit_card.verification_value, transcript) + end +end diff --git a/test/remote/gateways/remote_bpoint_test.rb b/test/remote/gateways/remote_bpoint_test.rb new file mode 100644 index 00000000000..c2ba913c86c --- /dev/null +++ b/test/remote/gateways/remote_bpoint_test.rb @@ -0,0 +1,148 @@ +require 'test_helper' + +class RemoteBpointTest < Test::Unit::TestCase + def setup + @gateway = BpointGateway.new(fixtures(:bpoint)) + + @amount = 100 + approved_year = '00' + declined_year = '01' + @credit_card = credit_card('4987654321098769', month: '99', year: approved_year) + @declined_card = credit_card('4987654321098769', month: '99', year: declined_year) + @error_card = credit_card('498765432109', month: '99', year: approved_year) + + @options = { + order_id: '1', + billing_address: address, + description: 'Store Purchase' + } + end + + def test_successful_store + response = @gateway.store(@credit_card, { crn1: 'TEST' }) + assert_success response + assert_equal 'Success', response.message + token_key = 'AddTokenResult_Token' + assert_not_nil response.params[token_key] + assert_not_nil response.authorization + assert_equal response.params[token_key], response.authorization + end + + def test_failed_store + response = @gateway.store(@error_card) + assert_failure response + assert_equal 'Error', response.message + end + + def test_successful_purchase + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal 'Approved', response.message + end + + def test_successful_purchase_with_more_options + response = @gateway.purchase(@amount, @credit_card, @options.merge({ crn1: 'ref'})) + assert_success response + assert_equal 'Approved', response.message + end + + def test_failed_purchase + response = @gateway.purchase(@amount, @declined_card, @options) + assert_failure response + assert_equal 'Declined', response.message + end + + def test_successful_authorize_and_capture + response = @gateway.authorize(@amount, @credit_card, @options) + assert_success response + + assert capture = @gateway.capture(@amount, response.authorization) + assert_success capture + end + + def test_failed_authorize + response = @gateway.authorize(@amount, @declined_card, @options) + assert_failure response + end + + def test_partial_capture + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + assert capture = @gateway.capture(@amount-1, auth.authorization) + assert_success capture + end + + def test_failed_capture + response = @gateway.capture(@amount, '') + assert_failure response + end + + def test_successful_refund + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + + assert refund = @gateway.refund(@amount, purchase.authorization) + assert_success refund + end + + def test_partial_refund + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + + assert refund = @gateway.refund(@amount-1, purchase.authorization) + assert_success refund + end + + def test_failed_refund + response = @gateway.refund(@amount, '') + assert_failure response + end + + def test_successful_void + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + assert void = @gateway.void(@amount, auth.authorization) + assert_success void + end + + def test_failed_void + response = @gateway.void(@amount, '') + assert_failure response + end + + def test_successful_verify + response = @gateway.verify(@credit_card, @options) + assert_success response + assert_equal 'Approved', response.message + end + + def test_failed_verify + response = @gateway.verify(@declined_card, @options) + assert_failure response + assert_equal 'Declined', response.message + end + + def test_invalid_login + gateway = BpointGateway.new( + username: 'abc', + password: '123', + merchant_number: 'xyz' + ) + response = gateway.purchase(@amount, @credit_card, @options) + assert_equal 'invalid login', response.message + assert_failure response + end + + def test_transcript_scrubbing + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @credit_card, @options) + end + transcript = @gateway.scrub(transcript) + + assert_scrubbed(@credit_card.number, transcript) + assert_scrubbed(@credit_card.verification_value, transcript) + assert_scrubbed(@gateway.options[:password], transcript) + end +end diff --git a/test/remote/gateways/remote_braintree_blue_test.rb b/test/remote/gateways/remote_braintree_blue_test.rb index 352f170166c..d1031516fab 100644 --- a/test/remote/gateways/remote_braintree_blue_test.rb +++ b/test/remote/gateways/remote_braintree_blue_test.rb @@ -3,6 +3,7 @@ class RemoteBraintreeBlueTest < Test::Unit::TestCase def setup @gateway = BraintreeGateway.new(fixtures(:braintree_blue)) + @braintree_backend = @gateway.instance_eval { @braintree_gateway } @amount = 100 @declined_amount = 2000_00 @@ -11,7 +12,7 @@ def setup @options = { :order_id => '1', - :billing_address => address(:country_name => "United States of America"), + :billing_address => address(:country_name => 'Canada'), :description => 'Store Purchase' } end @@ -19,82 +20,182 @@ def setup def test_credit_card_details_on_store assert response = @gateway.store(@credit_card) assert_success response - assert_equal '5100', response.params["braintree_customer"]["credit_cards"].first["last_4"] - assert_equal('510510******5100', response.params["braintree_customer"]["credit_cards"].first["masked_number"]) - assert_equal('5100', response.params["braintree_customer"]["credit_cards"].first["last_4"]) - assert_equal('MasterCard', response.params["braintree_customer"]["credit_cards"].first["card_type"]) - assert_equal('510510', response.params["braintree_customer"]["credit_cards"].first["bin"]) - assert_match %r{^\d+$}, response.params["customer_vault_id"] - assert_equal response.params["customer_vault_id"], response.authorization + assert_equal '5100', response.params['braintree_customer']['credit_cards'].first['last_4'] + assert_equal('510510******5100', response.params['braintree_customer']['credit_cards'].first['masked_number']) + assert_equal('5100', response.params['braintree_customer']['credit_cards'].first['last_4']) + assert_equal('MasterCard', response.params['braintree_customer']['credit_cards'].first['card_type']) + assert_equal('510510', response.params['braintree_customer']['credit_cards'].first['bin']) + assert_match %r{^\d+$}, response.params['customer_vault_id'] + assert_equal response.params['customer_vault_id'], response.authorization + assert_match %r{^\w+$}, response.params['credit_card_token'] + assert_equal response.params['credit_card_token'], response.params['braintree_customer']['credit_cards'].first['token'] end def test_successful_authorize assert response = @gateway.authorize(@amount, @credit_card, @options) assert_success response assert_equal '1000 Approved', response.message - assert_equal 'authorized', response.params["braintree_transaction"]["status"] + assert_equal 'authorized', response.params['braintree_transaction']['status'] + end + + def test_successful_authorize_with_nil_and_empty_billing_address_options + credit_card = credit_card('5105105105105100') + options = { + :billing_address => { + :name => 'John Smith', + :phone => '123-456-7890', + :company => nil, + :address1 => nil, + :address2 => '', + :city => nil, + :state => nil, + :zip => nil, + :country => '' + } + } + assert response = @gateway.authorize(@amount, credit_card, options) + assert_success response + assert_equal '1000 Approved', response.message + assert_equal 'authorized', response.params['braintree_transaction']['status'] end def test_masked_card_number assert response = @gateway.authorize(@amount, @credit_card, @options) - assert_equal('510510******5100', response.params["braintree_transaction"]["credit_card_details"]["masked_number"]) - assert_equal('5100', response.params["braintree_transaction"]["credit_card_details"]["last_4"]) - assert_equal('MasterCard', response.params["braintree_transaction"]["credit_card_details"]["card_type"]) - assert_equal('510510', response.params["braintree_transaction"]["credit_card_details"]["bin"]) + assert_equal('510510******5100', response.params['braintree_transaction']['credit_card_details']['masked_number']) + assert_equal('5100', response.params['braintree_transaction']['credit_card_details']['last_4']) + assert_equal('MasterCard', response.params['braintree_transaction']['credit_card_details']['card_type']) + assert_equal('510510', response.params['braintree_transaction']['credit_card_details']['bin']) end def test_successful_authorize_with_order_id assert response = @gateway.authorize(@amount, @credit_card, :order_id => '123') assert_success response assert_equal '1000 Approved', response.message - assert_equal '123', response.params["braintree_transaction"]["order_id"] + assert_equal '123', response.params['braintree_transaction']['order_id'] end - def test_successful_authorize_with_merchant_account_id - assert response = @gateway.authorize(@amount, @credit_card, :merchant_account_id => 'sandbox_credit_card_non_default') + def test_successful_purchase_with_hold_in_escrow + @options.merge({:merchant_account_id => fixtures(:braintree_blue)[:merchant_account_id], :hold_in_escrow => true}) + assert response = @gateway.authorize(@amount, @credit_card, @options) assert_success response assert_equal '1000 Approved', response.message - assert_equal 'sandbox_credit_card_non_default', response.params["braintree_transaction"]["merchant_account_id"] end def test_successful_purchase_using_vault_id assert response = @gateway.store(@credit_card) assert_success response assert_equal 'OK', response.message - customer_vault_id = response.params["customer_vault_id"] - assert_match(/\A\d{6,7}\z/, customer_vault_id) + customer_vault_id = response.params['customer_vault_id'] + assert_match(/\A\d+\z/, customer_vault_id) assert response = @gateway.purchase(@amount, customer_vault_id) assert_success response assert_equal '1000 Approved', response.message - assert_equal 'submitted_for_settlement', response.params["braintree_transaction"]["status"] - assert_equal customer_vault_id, response.params["braintree_transaction"]["customer_details"]["id"] + assert_equal 'submitted_for_settlement', response.params['braintree_transaction']['status'] + assert_equal customer_vault_id, response.params['braintree_transaction']['customer_details']['id'] end def test_successful_purchase_using_vault_id_as_integer assert response = @gateway.store(@credit_card) assert_success response assert_equal 'OK', response.message - customer_vault_id = response.params["customer_vault_id"] - assert_match /\A\d{6,7}\z/, customer_vault_id + customer_vault_id = response.params['customer_vault_id'] + assert_match %r{\A\d+\z}, customer_vault_id assert response = @gateway.purchase(@amount, customer_vault_id.to_i) assert_success response assert_equal '1000 Approved', response.message - assert_equal 'submitted_for_settlement', response.params["braintree_transaction"]["status"] - assert_equal customer_vault_id, response.params["braintree_transaction"]["customer_details"]["id"] + assert_equal 'submitted_for_settlement', response.params['braintree_transaction']['status'] + assert_equal customer_vault_id, response.params['braintree_transaction']['customer_details']['id'] end - def test_successful_validate_on_store - card = credit_card('4111111111111111', :verification_value => '101') - assert response = @gateway.store(card, :verify_card => true) + def test_successful_purchase_using_card_token + assert response = @gateway.store(@credit_card) assert_success response assert_equal 'OK', response.message + credit_card_token = response.params['credit_card_token'] + assert_match %r{^\w+$}, credit_card_token + + assert response = @gateway.purchase(@amount, credit_card_token, :payment_method_token => true) + assert_success response + assert_equal '1000 Approved', response.message + assert_equal 'submitted_for_settlement', response.params['braintree_transaction']['status'] end - def test_successful_validate_on_store_with_verification_merchant_account + def test_successful_purchase_with_level_2_data + assert response = @gateway.purchase(@amount, @credit_card, @options.merge(:tax_amount => '20', :purchase_order_number => '6789')) + assert_success response + assert_equal '1000 Approved', response.message + end + + def test_successful_purchase_with_level_2_and_3_data + options = { + :tax_amount => '20', + :purchase_order_number => '6789', + :shipping_amount => '300', + :discount_amount => '150', + :ships_from_postal_code => '90210', + :line_items => [ + { + :name => 'Product Name', + :kind => 'debit', + :quantity => '10.0000', + :unit_amount => '9.5000', + :unit_of_measure => 'unit', + :total_amount => '95.00', + :tax_amount => '5.00', + :discount_amount => '0.00', + :product_code => '54321', + :commodity_code => '98765' + }, + { + :name => 'Other Product Name', + :kind => 'debit', + :quantity => '1.0000', + :unit_amount => '2.5000', + :unit_of_measure => 'unit', + :total_amount => '90.00', + :tax_amount => '2.00', + :discount_amount => '1.00', + :product_code => '54322', + :commodity_code => '98766' + } + ] + } + assert response = @gateway.purchase(@amount, @credit_card, @options.merge(options)) + assert_success response + assert_equal '1000 Approved', response.message + end + + def test_successful_verify + assert response = @gateway.verify(@credit_card, @options) + assert_success response + assert_equal '1000 Approved', response.message + end + + def test_failed_verify + assert response = @gateway.verify(@declined_card, @options) + assert_failure response + assert_match %r{number is not an accepted test number}, response.message + end + + def test_successful_verify_with_device_data + # Requires Advanced Fraud Tools to be enabled + assert response = @gateway.verify(@credit_card, @options.merge({device_data: 'device data for verify'})) + assert_success response + assert_equal '1000 Approved', response.message + + assert transaction = response.params['braintree_transaction'] + assert transaction['risk_data'] + assert transaction['risk_data']['id'] + assert_equal 'Approve', transaction['risk_data']['decision'] + assert_equal false, transaction['risk_data']['device_data_captured'] + assert_equal 'kount', transaction['risk_data']['fraud_service_provider'] + end + + def test_successful_validate_on_store card = credit_card('4111111111111111', :verification_value => '101') - assert response = @gateway.store(card, :verify_card => true, :verification_merchant_account_id => 'sandbox_credit_card_non_default') + assert response = @gateway.store(card, :verify_card => true) assert_success response assert_equal 'OK', response.message end @@ -121,12 +222,12 @@ def test_successful_store_with_invalid_card def test_successful_store_with_billing_address billing_address = { - :address1 => "1 E Main St", - :address2 => "Suite 403", - :city => "Chicago", - :state => "Illinois", - :zip => "60622", - :country_name => "United States of America" + :address1 => '1 E Main St', + :address2 => 'Suite 403', + :city => 'Chicago', + :state => 'Illinois', + :zip => '60622', + :country_name => 'United States of America' } credit_card = credit_card('5105105105105100') assert response = @gateway.store(credit_card, :billing_address => billing_address) @@ -136,56 +237,142 @@ def test_successful_store_with_billing_address vault_id = response.params['customer_vault_id'] purchase_response = @gateway.purchase(@amount, vault_id) response_billing_details = { - "country_name"=>"United States of America", - "region"=>"Illinois", - "company"=>nil, - "postal_code"=>"60622", - "extended_address"=>"Suite 403", - "street_address"=>"1 E Main St", - "locality"=>"Chicago" + 'country_name'=>'United States of America', + 'region'=>'Illinois', + 'company'=>nil, + 'postal_code'=>'60622', + 'extended_address'=>'Suite 403', + 'street_address'=>'1 E Main St', + 'locality'=>'Chicago' } assert_equal purchase_response.params['braintree_transaction']['billing_details'], response_billing_details end + def test_successful_store_with_nil_billing_address_options + billing_address = { + :name => 'John Smith', + :phone => '123-456-7890', + :company => nil, + :address1 => nil, + :address2 => nil, + :city => nil, + :state => nil, + :zip => nil, + :country_name => nil + } + credit_card = credit_card('5105105105105100') + assert response = @gateway.store(credit_card, :billing_address => billing_address) + assert_success response + assert_equal 'OK', response.message + + vault_id = response.params['customer_vault_id'] + purchase_response = @gateway.purchase(@amount, vault_id) + assert_success purchase_response + end + + def test_successful_store_with_credit_card_token + credit_card = credit_card('5105105105105100') + credit_card_token = generate_unique_id + assert response = @gateway.store(credit_card, credit_card_token: credit_card_token) + assert_success response + assert_equal 'OK', response.message + assert_equal credit_card_token, response.params['braintree_customer']['credit_cards'][0]['token'] + end + + def test_successful_store_with_new_customer_id + credit_card = credit_card('5105105105105100') + customer_id = generate_unique_id + assert response = @gateway.store(credit_card, customer: customer_id) + assert_success response + assert_equal 'OK', response.message + assert_equal customer_id, response.authorization + assert_equal customer_id, response.params['braintree_customer']['id'] + end + + def test_successful_store_with_existing_customer_id + credit_card = credit_card('5105105105105100') + customer_id = generate_unique_id + assert response = @gateway.store(credit_card, customer: customer_id) + assert_success response + assert_equal 1, @braintree_backend.customer.find(customer_id).credit_cards.size + + assert response = @gateway.store(credit_card, customer: customer_id) + assert_success response + assert_equal 2, @braintree_backend.customer.find(customer_id).credit_cards.size + assert_equal customer_id, response.params['customer_vault_id'] + assert_equal customer_id, response.authorization + assert_not_nil response.params['credit_card_token'] + end + + def test_successful_store_with_existing_customer_id_and_nil_billing_address_options + credit_card = credit_card('5105105105105100') + customer_id = generate_unique_id + options = { + :customer => customer_id, + :billing_address => { + :name => 'John Smith', + :phone => '123-456-7890', + :company => nil, + :address1 => nil, + :address2 => nil, + :city => nil, + :state => nil, + :zip => nil, + :country_name => nil + } + } + assert response = @gateway.store(credit_card, options) + assert_success response + assert_equal 1, @braintree_backend.customer.find(customer_id).credit_cards.size + + assert response = @gateway.store(credit_card, options) + assert_success response + assert_equal 2, @braintree_backend.customer.find(customer_id).credit_cards.size + assert_equal customer_id, response.params['customer_vault_id'] + assert_equal customer_id, response.authorization + assert_not_nil response.params['credit_card_token'] + end + def test_successful_purchase assert response = @gateway.purchase(@amount, @credit_card, @options) assert_success response assert_equal '1000 Approved', response.message - assert_equal 'submitted_for_settlement', response.params["braintree_transaction"]["status"] + assert_equal 'submitted_for_settlement', response.params['braintree_transaction']['status'] end - def test_avs_match - assert response = @gateway.purchase(@amount, @credit_card, - @options.merge( - :billing_address => {:address1 => "1 E Main St", :zip => "60622"} - ) - ) + def test_successful_purchase_with_solution_id + ActiveMerchant::Billing::BraintreeBlueGateway.application_id = 'ABC123' + assert response = @gateway.purchase(@amount, @credit_card, @options) assert_success response - assert_equal({'code' => nil, 'message' => nil, 'street_match' => 'M', 'postal_match' => 'M'}, response.avs_result) + assert_equal '1000 Approved', response.message + assert_equal 'submitted_for_settlement', response.params['braintree_transaction']['status'] + ensure + ActiveMerchant::Billing::BraintreeBlueGateway.application_id = nil end - def test_transaction_succeeds_with_bad_avs_without_avs_rules - assert response = @gateway.purchase(@amount, @credit_card, - @options.merge( - :billing_address => {:address1 => "200 E Main St", :zip => "20000"} - ) - ) - assert_success response - assert_equal({'code' => nil, 'message' => nil, 'street_match' => 'N', 'postal_match' => 'N'}, response.avs_result) - end + def test_avs + assert_avs('1 Elm', '60622', 'M') + assert_avs('1 Elm', '20000', 'A') + assert_avs('1 Elm', '20001', 'B') + assert_avs('1 Elm', '', 'B') - def test_transaction_fails_with_bad_avs_with_avs_rules - gateway = BraintreeGateway.new(fixtures(:braintree_blue_with_processing_rules)) + assert_avs('200 Elm', '60622', 'Z') + assert_avs('200 Elm', '20000', 'C') + assert_avs('200 Elm', '20001', 'C') + assert_avs('200 Elm', '', 'C') - assert response = gateway.purchase(@amount, @credit_card, - @options.merge( - :billing_address => {:address1 => "200 E Main St", :zip => "20000"} - ) - ) + assert_avs('201 Elm', '60622', 'P') + assert_avs('201 Elm', '20000', 'N') + assert_avs('201 Elm', '20001', 'I') + assert_avs('201 Elm', '', 'I') - assert_failure response - assert_equal("Transaction declined - gateway rejected", response.message) - assert_equal({'code' => nil, 'message' => nil, 'street_match' => 'N', 'postal_match' => 'N'}, response.avs_result) + assert_avs('', '60622', 'P') + assert_avs('', '20000', 'C') + assert_avs('', '20001', 'I') + assert_avs('', '', 'I') + + assert_avs('1 Elm', '30000', 'E') + assert_avs('1 Elm', '30001', 'S') end def test_cvv_match @@ -200,22 +387,62 @@ def test_cvv_no_match assert_equal({'code' => 'N', 'message' => ''}, response.cvv_result) end - def test_transaction_fails_with_bad_cvv_with_cvv_rules - gateway = BraintreeGateway.new(fixtures(:braintree_blue_with_processing_rules)) - - assert response = gateway.purchase(@amount, credit_card('5105105105105100', :verification_value => '200')) - assert_failure response - assert_equal("Transaction declined - gateway rejected", response.message) - assert_equal({'code' => 'N', 'message' => ''}, response.cvv_result) - end - def test_successful_purchase_with_email assert response = @gateway.purchase(@amount, @credit_card, - :email => "customer@example.com" + :email => 'customer@example.com' ) assert_success response - transaction = response.params["braintree_transaction"] - assert_equal 'customer@example.com', transaction["customer_details"]["email"] + transaction = response.params['braintree_transaction'] + assert_equal 'customer@example.com', transaction['customer_details']['email'] + end + + def test_successful_purchase_with_phone + assert response = @gateway.purchase(@amount, @credit_card, :phone => '123-345-5678') + assert_success response + transaction = response.params['braintree_transaction'] + assert_equal '123-345-5678', transaction['customer_details']['phone'] + end + + def test_successful_purchase_with_phone_from_address + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + transaction = response.params['braintree_transaction'] + assert_equal '(555)555-5555', transaction['customer_details']['phone'] + end + + def test_successful_purchase_with_skip_advanced_fraud_checking_option + assert response = @gateway.purchase(@amount, @credit_card, @options.merge(skip_advanced_fraud_checking: true)) + assert_success response + assert_equal '1000 Approved', response.message + assert_equal 'submitted_for_settlement', response.params['braintree_transaction']['status'] + end + + def test_successful_purchase_with_skip_avs + assert response = @gateway.purchase(@amount, @credit_card, @options.merge(skip_avs: true)) + assert_success response + assert_equal '1000 Approved', response.message + assert_equal 'B', response.avs_result['code'] + end + + def test_successful_purchase_with_skip_cvv + assert response = @gateway.purchase(@amount, @credit_card, @options.merge(skip_cvv: true)) + assert_success response + assert_equal '1000 Approved', response.message + assert_equal 'B', response.cvv_result['code'] + end + + def test_successful_purchase_with_device_data + # Requires Advanced Fraud Tools to be enabled + assert response = @gateway.purchase(@amount, @credit_card, @options.merge(device_data: 'device data for purchase')) + assert_success response + assert_equal '1000 Approved', response.message + + assert transaction = response.params['braintree_transaction'] + assert transaction['risk_data'] + assert transaction['risk_data']['id'] + assert_equal 'Approve', transaction['risk_data']['decision'] + assert_equal false, transaction['risk_data']['device_data_captured'] + assert_equal 'kount', transaction['risk_data']['fraud_service_provider'] end def test_purchase_with_store_using_random_customer_id @@ -224,9 +451,9 @@ def test_purchase_with_store_using_random_customer_id ) assert_success response assert_equal '1000 Approved', response.message - assert_match(/\A\d{6,7}\z/, response.params["customer_vault_id"]) - assert_equal '510510', response.params["braintree_transaction"]["vault_customer"]["credit_cards"][0]["bin"] - assert_equal '510510', Braintree::Customer.find(response.params["customer_vault_id"]).credit_cards[0].bin + assert_match(/\A\d+\z/, response.params['customer_vault_id']) + assert_equal '510510', response.params['braintree_transaction']['vault_customer']['credit_cards'][0]['bin'] + assert_equal '510510', @braintree_backend.customer.find(response.params['customer_vault_id']).credit_cards[0].bin end def test_purchase_with_store_using_specified_customer_id @@ -236,9 +463,37 @@ def test_purchase_with_store_using_specified_customer_id ) assert_success response assert_equal '1000 Approved', response.message - assert_equal customer_id, response.params["customer_vault_id"] - assert_equal '510510', response.params["braintree_transaction"]["vault_customer"]["credit_cards"][0]["bin"] - assert_equal '510510', Braintree::Customer.find(response.params["customer_vault_id"]).credit_cards[0].bin + assert_equal customer_id, response.params['customer_vault_id'] + assert_equal '510510', response.params['braintree_transaction']['vault_customer']['credit_cards'][0]['bin'] + assert_equal '510510', @braintree_backend.customer.find(response.params['customer_vault_id']).credit_cards[0].bin + end + + def test_purchase_with_transaction_source + assert response = @gateway.store(@credit_card) + assert_success response + customer_vault_id = response.params['customer_vault_id'] + + assert response = @gateway.purchase(@amount, customer_vault_id, @options.merge(:transaction_source => 'unscheduled')) + assert_success response + assert_equal '1000 Approved', response.message + end + + def test_purchase_using_specified_payment_method_token + assert response = @gateway.store( + credit_card('4111111111111111', + :first_name => 'Old First', :last_name => 'Old Last', + :month => 9, :year => 2012 + ), + :email => 'old@example.com', + :phone => '321-654-0987' + ) + payment_method_token = response.params['braintree_customer']['credit_cards'][0]['token'] + assert response = @gateway.purchase( + @amount, payment_method_token, @options.merge(payment_method_token: true) + ) + assert_success response + assert_equal '1000 Approved', response.message + assert_equal payment_method_token, response.params['braintree_transaction']['credit_card_details']['token'] end def test_successful_purchase_with_addresses @@ -265,72 +520,155 @@ def test_successful_purchase_with_addresses :shipping_address => shipping_address ) assert_success response - transaction = response.params["braintree_transaction"] - assert_equal '1 E Main St', transaction["billing_details"]["street_address"] - assert_equal 'Suite 101', transaction["billing_details"]["extended_address"] - assert_equal 'Widgets Co', transaction["billing_details"]["company"] - assert_equal 'Chicago', transaction["billing_details"]["locality"] - assert_equal 'IL', transaction["billing_details"]["region"] - assert_equal '60622', transaction["billing_details"]["postal_code"] - assert_equal 'United States of America', transaction["billing_details"]["country_name"] - assert_equal '1 W Main St', transaction["shipping_details"]["street_address"] - assert_equal 'Suite 102', transaction["shipping_details"]["extended_address"] - assert_equal 'Widgets Company', transaction["shipping_details"]["company"] - assert_equal 'Bartlett', transaction["shipping_details"]["locality"] - assert_equal 'Illinois', transaction["shipping_details"]["region"] - assert_equal '60103', transaction["shipping_details"]["postal_code"] - assert_equal 'Mexico', transaction["shipping_details"]["country_name"] + transaction = response.params['braintree_transaction'] + assert_equal '1 E Main St', transaction['billing_details']['street_address'] + assert_equal 'Suite 101', transaction['billing_details']['extended_address'] + assert_equal 'Widgets Co', transaction['billing_details']['company'] + assert_equal 'Chicago', transaction['billing_details']['locality'] + assert_equal 'IL', transaction['billing_details']['region'] + assert_equal '60622', transaction['billing_details']['postal_code'] + assert_equal 'United States of America', transaction['billing_details']['country_name'] + assert_equal '1 W Main St', transaction['shipping_details']['street_address'] + assert_equal 'Suite 102', transaction['shipping_details']['extended_address'] + assert_equal 'Widgets Company', transaction['shipping_details']['company'] + assert_equal 'Bartlett', transaction['shipping_details']['locality'] + assert_equal 'Illinois', transaction['shipping_details']['region'] + assert_equal '60103', transaction['shipping_details']['postal_code'] + assert_equal 'Mexico', transaction['shipping_details']['country_name'] + end + + def test_successful_purchase_with_three_d_secure_pass_thru + three_d_secure_params = { version: '2.0', cavv: 'cavv', eci: '02', ds_transaction_id: 'trans_id', cavv_algorithm: 'algorithm', directory_response_status: 'directory', authentication_response_status: 'auth' } + response = @gateway.purchase(@amount, @credit_card, + three_d_secure: three_d_secure_params + ) + assert_success response + end + + def test_successful_purchase_with_some_three_d_secure_pass_thru_fields + three_d_secure_params = { version: '2.0', cavv: 'cavv', eci: '02', ds_transaction_id: 'trans_id' } + response = @gateway.purchase(@amount, @credit_card, + three_d_secure: three_d_secure_params + ) + assert_success response end def test_unsuccessful_purchase_declined assert response = @gateway.purchase(@declined_amount, @credit_card, @options) assert_failure response + assert response.authorization.present? assert_equal '2000 Do Not Honor', response.message end def test_unsuccessful_purchase_validation_error - assert response = @gateway.purchase(@amount, @credit_card, - @options.merge(:email => "invalid_email") - ) + assert response = @gateway.purchase(@amount, credit_card('51051051051051000')) assert_failure response - assert_equal 'Email is an invalid format. (81604)', response.message - assert_equal nil, response.params["braintree_transaction"] + assert_match %r{Credit card number is invalid\. \(81715\)}, response.message + assert_equal({'processor_response_code'=>'91577'}, response.params['braintree_transaction']) end def test_authorize_and_capture - amount = @amount - assert auth = @gateway.authorize(amount, @credit_card, @options) + assert auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + assert_equal '1000 Approved', auth.message + assert auth.authorization + assert capture = @gateway.capture(@amount, auth.authorization) + assert_success capture + end + + def test_authorize_and_capture_with_apple_pay_card + credit_card = network_tokenization_credit_card('4111111111111111', + :brand => 'visa', + :eci => '05', + :payment_cryptogram => 'EHuWW9PiBkWvqE5juRwDzAUFBAk=' + ) + + assert auth = @gateway.authorize(@amount, credit_card, @options) + assert_success auth + assert_equal '1000 Approved', auth.message + assert auth.authorization + assert capture = @gateway.capture(@amount, auth.authorization) + assert_success capture + end + + def test_authorize_and_capture_with_android_pay_card + credit_card = network_tokenization_credit_card('4111111111111111', + :payment_cryptogram => 'EHuWW9PiBkWvqE5juRwDzAUFBAk=', + :month => '01', + :year => '2024', + :source => :android_pay, + :transaction_id => '123456789', + :eci => '05' + ) + + assert auth = @gateway.authorize(@amount, credit_card, @options) + assert_success auth + assert_equal '1000 Approved', auth.message + assert auth.authorization + assert capture = @gateway.capture(@amount, auth.authorization) + assert_success capture + end + + def test_authorize_and_capture_with_google_pay_card + credit_card = network_tokenization_credit_card('4111111111111111', + :payment_cryptogram => 'EHuWW9PiBkWvqE5juRwDzAUFBAk=', + :month => '01', + :year => '2024', + :source => :google_pay, + :transaction_id => '123456789', + :eci => '05' + ) + + assert auth = @gateway.authorize(@amount, credit_card, @options) assert_success auth assert_equal '1000 Approved', auth.message assert auth.authorization - assert capture = @gateway.capture(amount, auth.authorization) + assert capture = @gateway.capture(@amount, auth.authorization) assert_success capture end def test_authorize_and_void - amount = @amount - assert auth = @gateway.authorize(amount, @credit_card, @options) + assert auth = @gateway.authorize(@amount, @credit_card, @options) assert_success auth assert_equal '1000 Approved', auth.message assert auth.authorization assert void = @gateway.void(auth.authorization) assert_success void - assert_equal 'voided', void.params["braintree_transaction"]["status"] + assert_equal 'voided', void.params['braintree_transaction']['status'] + end + + def test_purchase_and_void + assert purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + assert void = @gateway.void(purchase.authorization) + assert_success void + assert_equal 'voided', void.params['braintree_transaction']['status'] + end + + def test_capture_and_void + assert auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + assert capture = @gateway.capture(@amount, auth.authorization) + assert_success capture + + assert void = @gateway.void(capture.authorization) + assert_success void + assert_equal 'voided', void.params['braintree_transaction']['status'] end def test_failed_void - amount = @amount - assert auth = @gateway.authorize(amount, @credit_card, @options) + assert auth = @gateway.authorize(@amount, @credit_card, @options) assert_success auth assert_equal '1000 Approved', auth.message assert auth.authorization assert void = @gateway.void(auth.authorization) assert_success void - assert_equal 'voided', void.params["braintree_transaction"]["status"] + assert_equal 'voided', void.params['braintree_transaction']['status'] assert failed_void = @gateway.void(auth.authorization) assert_failure failed_void - assert_equal 'Transaction can only be voided if status is authorized or submitted_for_settlement. (91504)', failed_void.message - assert_equal nil, failed_void.params["braintree_transaction"] + assert_match('Transaction can only be voided if status is authorized', failed_void.message) + assert_equal({'processor_response_code'=>'91504'}, failed_void.params['braintree_transaction']) end def test_failed_capture_with_invalid_transaction_id @@ -340,7 +678,7 @@ def test_failed_capture_with_invalid_transaction_id end def test_invalid_login - gateway = BraintreeBlueGateway.new(:merchant_id => "invalid", :public_key => "invalid", :private_key => "invalid") + gateway = BraintreeBlueGateway.new(:merchant_id => 'invalid', :public_key => 'invalid', :private_key => 'invalid') assert response = gateway.purchase(@amount, @credit_card, @options) assert_failure response assert_equal 'Braintree::AuthenticationError', response.message @@ -350,53 +688,42 @@ def test_successful_add_to_vault_with_store_method assert response = @gateway.store(@credit_card) assert_success response assert_equal 'OK', response.message - assert_match(/\A\d{6,7}\z/, response.params["customer_vault_id"]) + assert_match(/\A\d+\z/, response.params['customer_vault_id']) end def test_failed_add_to_vault assert response = @gateway.store(credit_card('5105105105105101')) assert_failure response assert_equal 'Credit card number is invalid. (81715)', response.message - assert_equal nil, response.params["braintree_customer"] - assert_equal nil, response.params["customer_vault_id"] + assert_equal nil, response.params['braintree_customer'] + assert_equal nil, response.params['customer_vault_id'] end - def test_unstore + def test_unstore_customer assert response = @gateway.store(@credit_card) assert_success response assert_equal 'OK', response.message - assert customer_vault_id = response.params["customer_vault_id"] + assert customer_vault_id = response.params['customer_vault_id'] assert delete_response = @gateway.unstore(customer_vault_id) assert_success delete_response end - def test_unstore_with_delete_method + def test_unstore_credit_card assert response = @gateway.store(@credit_card) assert_success response assert_equal 'OK', response.message - assert customer_vault_id = response.params["customer_vault_id"] - assert delete_response = @gateway.delete(customer_vault_id) + assert credit_card_token = response.params['credit_card_token'] + assert delete_response = @gateway.unstore(nil, credit_card_token: credit_card_token) assert_success delete_response end - def test_successful_credit - assert response = @gateway.credit(@amount, @credit_card, @options) - assert_success response - assert_equal '1002 Processed', response.message - assert_equal 'submitted_for_settlement', response.params["braintree_transaction"]["status"] - end - - def test_successful_credit_with_merchant_account_id - assert response = @gateway.credit(@amount, @credit_card, :merchant_account_id => 'sandbox_credit_card_non_default') + def test_unstore_with_delete_method + assert response = @gateway.store(@credit_card) assert_success response - assert_equal '1002 Processed', response.message - assert_equal 'submitted_for_settlement', response.params["braintree_transaction"]["status"] - end - - def test_failed_credit - assert response = @gateway.credit(@amount, credit_card('5105105105105101'), @options) - assert_failure response - assert_equal 'Credit card number is invalid. (81715)', response.message + assert_equal 'OK', response.message + assert customer_vault_id = response.params['customer_vault_id'] + assert delete_response = @gateway.delete(customer_vault_id) + assert_success delete_response end def test_successful_update @@ -405,19 +732,21 @@ def test_successful_update :first_name => 'Old First', :last_name => 'Old Last', :month => 9, :year => 2012 ), - :email => "old@example.com" + :email => 'old@example.com', + :phone => '321-654-0987' ) assert_success response assert_equal 'OK', response.message - customer_vault_id = response.params["customer_vault_id"] - assert_match(/\A\d{6,7}\z/, customer_vault_id) - assert_equal "old@example.com", response.params["braintree_customer"]["email"] - assert_equal "Old First", response.params["braintree_customer"]["first_name"] - assert_equal "Old Last", response.params["braintree_customer"]["last_name"] - assert_equal "411111", response.params["braintree_customer"]["credit_cards"][0]["bin"] - assert_equal "09/2012", response.params["braintree_customer"]["credit_cards"][0]["expiration_date"] - assert_not_nil response.params["braintree_customer"]["credit_cards"][0]["token"] - assert_equal customer_vault_id, response.params["braintree_customer"]["id"] + customer_vault_id = response.params['customer_vault_id'] + assert_match(/\A\d+\z/, customer_vault_id) + assert_equal 'old@example.com', response.params['braintree_customer']['email'] + assert_equal '321-654-0987', response.params['braintree_customer']['phone'] + assert_equal 'Old First', response.params['braintree_customer']['first_name'] + assert_equal 'Old Last', response.params['braintree_customer']['last_name'] + assert_equal '411111', response.params['braintree_customer']['credit_cards'][0]['bin'] + assert_equal '09/2012', response.params['braintree_customer']['credit_cards'][0]['expiration_date'] + assert_not_nil response.params['braintree_customer']['credit_cards'][0]['token'] + assert_equal customer_vault_id, response.params['braintree_customer']['id'] assert response = @gateway.update( customer_vault_id, @@ -425,33 +754,34 @@ def test_successful_update :first_name => 'New First', :last_name => 'New Last', :month => 10, :year => 2014 ), - :email => "new@example.com" + :email => 'new@example.com', + :phone => '987-765-5432' ) assert_success response - assert_equal "new@example.com", response.params["braintree_customer"]["email"] - assert_equal "New First", response.params["braintree_customer"]["first_name"] - assert_equal "New Last", response.params["braintree_customer"]["last_name"] - assert_equal "510510", response.params["braintree_customer"]["credit_cards"][0]["bin"] - assert_equal "10/2014", response.params["braintree_customer"]["credit_cards"][0]["expiration_date"] - assert_not_nil response.params["braintree_customer"]["credit_cards"][0]["token"] - assert_equal customer_vault_id, response.params["braintree_customer"]["id"] + assert_equal 'new@example.com', response.params['braintree_customer']['email'] + assert_equal '987-765-5432', response.params['braintree_customer']['phone'] + assert_equal 'New First', response.params['braintree_customer']['first_name'] + assert_equal 'New Last', response.params['braintree_customer']['last_name'] + assert_equal '510510', response.params['braintree_customer']['credit_cards'][0]['bin'] + assert_equal '10/2014', response.params['braintree_customer']['credit_cards'][0]['expiration_date'] + assert_not_nil response.params['braintree_customer']['credit_cards'][0]['token'] + assert_equal customer_vault_id, response.params['braintree_customer']['id'] end def test_failed_customer_update - assert response = @gateway.store(credit_card('4111111111111111'), :email => "email@example.com") + assert response = @gateway.store(credit_card('4111111111111111'), :email => 'email@example.com', :phone => '321-654-0987') assert_success response assert_equal 'OK', response.message - assert customer_vault_id = response.params["customer_vault_id"] + assert customer_vault_id = response.params['customer_vault_id'] assert response = @gateway.update( customer_vault_id, - credit_card('5105105105105100'), - :email => "invalid-email" + credit_card('51051051051051001') ) assert_failure response - assert_equal 'Email is an invalid format. (81604)', response.message - assert_equal nil, response.params["braintree_customer"] - assert_equal nil, response.params["customer_vault_id"] + assert_equal 'Credit card number is invalid. (81715)', response.message + assert_equal nil, response.params['braintree_customer'] + assert_equal nil, response.params['customer_vault_id'] end def test_failed_customer_update_invalid_vault_id @@ -464,7 +794,7 @@ def test_failed_credit_card_update assert response = @gateway.store(credit_card('4111111111111111')) assert_success response assert_equal 'OK', response.message - assert customer_vault_id = response.params["customer_vault_id"] + assert customer_vault_id = response.params['customer_vault_id'] assert response = @gateway.update( customer_vault_id, @@ -478,7 +808,7 @@ def test_failed_credit_card_update_on_verify assert response = @gateway.store(credit_card('4111111111111111')) assert_success response assert_equal 'OK', response.message - assert customer_vault_id = response.params["customer_vault_id"] + assert customer_vault_id = response.params['customer_vault_id'] assert response = @gateway.update( customer_vault_id, @@ -490,9 +820,129 @@ def test_failed_credit_card_update_on_verify end def test_customer_does_not_have_credit_card_failed_update - customer_without_credit_card = Braintree::Customer.create! - assert response = @gateway.update(customer_without_credit_card.id, credit_card('5105105105105100')) + customer_without_credit_card = @braintree_backend.customer.create + assert response = @gateway.update(customer_without_credit_card.customer.id, credit_card('5105105105105100')) assert_failure response assert_equal 'Braintree::NotFoundError', response.message end + + def test_successful_credit + assert response = @gateway.credit(@amount, @credit_card, @options) + assert_success response, 'You must get credits enabled in your Sandbox account for this to pass.' + assert_equal '1002 Processed', response.message + assert_equal 'submitted_for_settlement', response.params['braintree_transaction']['status'] + end + + def test_failed_credit + assert response = @gateway.credit(@amount, credit_card('5105105105105101'), @options) + assert_failure response + assert_equal 'Credit card number is invalid. (81715)', response.message, 'You must get credits enabled in your Sandbox account for this to pass' + end + + def test_successful_credit_with_merchant_account_id + assert response = @gateway.credit(@amount, @credit_card, :merchant_account_id => fixtures(:braintree_blue)[:merchant_account_id]) + assert_success response, 'You must specify a valid :merchant_account_id key in your fixtures.yml AND get credits enabled in your Sandbox account for this to pass.' + assert_equal '1002 Processed', response.message + assert_equal 'submitted_for_settlement', response.params['braintree_transaction']['status'] + end + + def test_successful_authorize_with_merchant_account_id + assert response = @gateway.authorize(@amount, @credit_card, :merchant_account_id => fixtures(:braintree_blue)[:merchant_account_id]) + assert_success response, 'You must specify a valid :merchant_account_id key in your fixtures.yml for this to pass.' + assert_equal '1000 Approved', response.message + assert_equal 'authorized', response.params['braintree_transaction']['status'] + end + + def test_authorize_with_descriptor + assert auth = @gateway.authorize(@amount, @credit_card, descriptor_name: 'company*theproduct', descriptor_phone: '1331131131', descriptor_url: 'company.com') + assert_success auth + end + + def test_successful_validate_on_store_with_verification_merchant_account + card = credit_card('4111111111111111', :verification_value => '101') + assert response = @gateway.store(card, :verify_card => true, :verification_merchant_account_id => fixtures(:braintree_blue)[:merchant_account_id]) + assert_success response, 'You must specify a valid :merchant_account_id key in your fixtures.yml for this to pass.' + assert_equal 'OK', response.message + end + + def test_transcript_scrubbing + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @credit_card, @options) + end + clean_transcript = @gateway.scrub(transcript) + + assert_scrubbed(@credit_card.number, clean_transcript) + assert_scrubbed(@credit_card.verification_value.to_s, clean_transcript) + end + + def test_verify_credentials + assert @gateway.verify_credentials + + gateway = BraintreeGateway.new(merchant_id: 'UNKNOWN', public_key: 'UNKONWN', private_key: 'UNKONWN') + assert !gateway.verify_credentials + end + + def test_successful_merchant_purchase_initial + creds_options = stored_credential_options(:merchant, :recurring, :initial) + response = @gateway.purchase(@amount, credit_card('4111111111111111'), @options.merge(stored_credential: creds_options)) + + assert_success response + assert_equal '1000 Approved', response.message + assert_equal 'submitted_for_settlement', response.params['braintree_transaction']['status'] + assert_not_nil response.params['braintree_transaction']['network_transaction_id'] + end + + def test_successful_subsequent_merchant_unscheduled_transaction + creds_options = stored_credential_options(:merchant, :unscheduled, id: '020190722142652') + response = @gateway.purchase(@amount, credit_card('4111111111111111'), @options.merge(stored_credential: creds_options)) + assert_success response + assert_equal '1000 Approved', response.message + assert_equal 'submitted_for_settlement', response.params['braintree_transaction']['status'] + end + + def test_successful_subsequent_merchant_recurring_transaction + creds_options = stored_credential_options(:cardholder, :recurring, id: '020190722142652') + response = @gateway.purchase(@amount, credit_card('4111111111111111'), @options.merge(stored_credential: creds_options)) + assert_success response + assert_equal '1000 Approved', response.message + assert_equal 'submitted_for_settlement', response.params['braintree_transaction']['status'] + end + + def test_successful_cardholder_purchase_initial + creds_options = stored_credential_options(:cardholder, :recurring, :initial) + response = @gateway.purchase(@amount, credit_card('4111111111111111'), @options.merge(stored_credential: creds_options)) + assert_success response + assert_equal '1000 Approved', response.message + assert_not_nil response.params['braintree_transaction']['network_transaction_id'] + assert_equal 'submitted_for_settlement', response.params['braintree_transaction']['status'] + end + + def test_successful_cardholder_purchase_recurring + creds_options = stored_credential_options(:cardholder, :recurring, id: '020190722142652') + response = @gateway.purchase(@amount, credit_card('4111111111111111'), @options.merge(stored_credential: creds_options)) + assert_success response + assert_equal '1000 Approved', response.message + assert_equal 'submitted_for_settlement', response.params['braintree_transaction']['status'] + end + + def test_successful_cardholder_purchase_unscheduled + creds_options = stored_credential_options(:cardholder, :unscheduled, id: '020190722142652') + response = @gateway.purchase(@amount, credit_card('4111111111111111'), @options.merge(stored_credential: creds_options)) + assert_success response + assert_equal '1000 Approved', response.message + assert_equal 'submitted_for_settlement', response.params['braintree_transaction']['status'] + end + + private + + def stored_credential_options(*args, id: nil) + stored_credential(*args, id: id) + end + + def assert_avs(address1, zip, expected_avs_code) + response = @gateway.purchase(@amount, @credit_card, billing_address: {address1: address1, zip: zip}) + + assert_success response + assert_equal expected_avs_code, response.avs_result['code'] + end end diff --git a/test/remote/gateways/remote_braintree_orange_test.rb b/test/remote/gateways/remote_braintree_orange_test.rb index 460dc55636a..0816eb1821b 100644 --- a/test/remote/gateways/remote_braintree_orange_test.rb +++ b/test/remote/gateways/remote_braintree_orange_test.rb @@ -4,7 +4,7 @@ class RemoteBraintreeOrangeTest < Test::Unit::TestCase def setup @gateway = BraintreeGateway.new(fixtures(:braintree_orange)) - @amount = rand(10000) + 1001 + @amount = rand(1001..11000) @credit_card = credit_card('4111111111111111') @check = check() @declined_amount = rand(99) @@ -27,7 +27,7 @@ def test_successful_purchase_with_echeck :account_holder_type => 'personal', :account_type => 'checking' ) - assert response = @gateway.purchase(@amount, @check, @options) + assert response = @gateway.purchase(@amount, check, @options) assert_equal 'This transaction has been approved', response.message assert_success response end @@ -37,15 +37,15 @@ def test_successful_add_to_vault assert response = @gateway.purchase(@amount, @credit_card, @options) assert_equal 'This transaction has been approved', response.message assert_success response - assert_not_nil response.params["customer_vault_id"] + assert_not_nil response.params['customer_vault_id'] end def test_successful_add_to_vault_with_store_method assert response = @gateway.store(@credit_card) assert_equal 'Customer Added', response.message assert_success response - assert_match %r{^\d+$}, response.params["customer_vault_id"] - assert_equal response.params["customer_vault_id"], response.authorization + assert_match %r{^\d+$}, response.params['customer_vault_id'] + assert_equal response.params['customer_vault_id'], response.authorization end def test_failed_add_to_vault_with_store_method @@ -59,7 +59,7 @@ def test_successful_add_to_vault_and_use assert response = @gateway.purchase(@amount, @credit_card, @options) assert_equal 'This transaction has been approved', response.message assert_success response - assert_not_nil customer_id = response.params["customer_vault_id"] + assert_not_nil customer_id = response.params['customer_vault_id'] assert second_response = @gateway.purchase(@amount*2, customer_id, @options) assert_equal 'This transaction has been approved', second_response.message @@ -67,19 +67,19 @@ def test_successful_add_to_vault_and_use end def test_add_to_vault_with_custom_vault_id - @options[:store] = rand(100000)+10001 + @options[:store] = rand(10001..110000) assert response = @gateway.purchase(@amount, @credit_card, @options) assert_equal 'This transaction has been approved', response.message assert_success response - assert_equal @options[:store], response.params["customer_vault_id"].to_i + assert_equal @options[:store], response.params['customer_vault_id'].to_i end def test_add_to_vault_with_custom_vault_id_with_store_method - @options[:billing_id] = rand(100000)+10001 + @options[:billing_id] = rand(10001..110000) assert response = @gateway.store(@credit_card, @options.dup) assert_equal 'Customer Added', response.message assert_success response - assert_equal @options[:billing_id], response.params["customer_vault_id"].to_i + assert_equal @options[:billing_id], response.params['customer_vault_id'].to_i end def test_add_to_vault_with_store_and_check @@ -139,7 +139,26 @@ def test_authorize_and_void def test_failed_capture assert response = @gateway.capture(@amount, '') assert_failure response - assert response.message.match(/Invalid Transaction ID \/ Object ID specified:/) + assert response.message.match(/Invalid Transaction ID \/ Object ID specified:/) + end + + def test_authorize_with_three_d_secure_pass_thru + assert auth = @gateway.authorize(@amount, @credit_card, @options.merge(eci: '05', xid: 'xid', cavv: 'cavv')) + assert_success auth + assert_equal 'This transaction has been approved', auth.message + end + + def test_successful_verify + assert response = @gateway.verify(@credit_card, @options) + assert_success response + assert_equal 'This transaction has been approved', response.message + end + + def test_failed_verify + bogus_card = credit_card('4424222222222222') + assert response = @gateway.verify(bogus_card, @options) + assert_failure response + assert_match %r{Invalid Credit Card Number}, response.message end def test_invalid_login @@ -151,4 +170,14 @@ def test_invalid_login assert_equal 'Invalid Username', response.message assert_failure response end + + def test_transcript_scrubbing + transcript = capture_transcript(@gateway) do + @gateway.purchase(@declined_amount, @credit_card, @options) + end + clean_transcript = @gateway.scrub(transcript) + + assert_scrubbed(@credit_card.number, clean_transcript) + assert_scrubbed(@credit_card.verification_value.to_s, clean_transcript) + end end diff --git a/test/remote/gateways/remote_bridge_pay_test.rb b/test/remote/gateways/remote_bridge_pay_test.rb new file mode 100644 index 00000000000..88f91a8212c --- /dev/null +++ b/test/remote/gateways/remote_bridge_pay_test.rb @@ -0,0 +1,145 @@ +require 'test_helper' + +class RemoteBridgePayTest < Test::Unit::TestCase + def setup + @gateway = BridgePayGateway.new(fixtures(:bridge_pay)) + + @amount = 100 + @credit_card = credit_card('4005550000000019') + @declined_card = credit_card('4000300011100000') + + @check = check( + :name => 'John Doe', + :routing_number => '490000018', + :account_number => '1234567890', + :number => '1001' + ) + + @options = { + order_id: generate_unique_id, + billing_address: address, + description: 'Store Purchase' + } + end + + def test_successful_purchase + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal 'Approved', response.message + end + + def test_failed_purchase + response = @gateway.purchase(@amount, @declined_card, @options) + assert_failure response + assert_equal 'Invalid Account Number', response.message + end + + def test_successful_purchase_with_echeck + response = @gateway.purchase(150, @check, @options) + assert_success response + assert_equal 'APPROVAL', response.message + end + + def test_successful_authorize_and_capture + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + assert capture = @gateway.capture(nil, auth.authorization) + assert_success capture + end + + def test_failed_authorize + response = @gateway.authorize(@amount, @declined_card, @options) + assert_failure response + end + + def test_partial_capture + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + assert capture = @gateway.capture(@amount-1, auth.authorization) + assert_success capture + end + + def test_failed_capture + response = @gateway.capture(nil, '') + assert_failure response + end + + def test_successful_refund + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + + assert refund = @gateway.refund(nil, purchase.authorization) + assert_success refund + end + + def test_partial_refund + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + + assert refund = @gateway.refund(@amount-1, purchase.authorization) + assert_success refund + end + + def test_failed_refund + response = @gateway.refund(nil, '') + assert_failure response + end + + def test_successful_void + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + assert void = @gateway.void(auth.authorization) + assert_success void + end + + def test_failed_void + response = @gateway.void('') + assert_failure response + end + + def test_successful_verify + assert response = @gateway.verify(@credit_card, @options) + assert_success response + assert_equal 'Approved', response.message + + assert_success response.responses.last, 'The void should succeed' + assert_equal 'Approved', response.responses.last.params['respmsg'] + end + + def test_unsuccessful_verify + assert response = @gateway.verify(@declined_card, @options) + assert_failure response + assert_match %r{Invalid Account Number}, response.message + end + + def test_invalid_login + gateway = BridgePayGateway.new( + user_name: '', + password: '' + ) + response = gateway.purchase(@amount, @credit_card, @options) + assert_failure response + end + + def test_successful_store_and_purchase + store = @gateway.store(@credit_card) + assert_success store + + purchase = @gateway.purchase(@amount, store.authorization) + assert_success purchase + end + + def test_transcript_scrubbing + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @credit_card, @options) + end + transcript = @gateway.scrub(transcript) + + assert_scrubbed(@credit_card.number, transcript) + assert_scrubbed(@credit_card.verification_value, transcript) + assert_scrubbed(@gateway.options[:password], transcript) + end +end diff --git a/test/remote/gateways/remote_cams_test.rb b/test/remote/gateways/remote_cams_test.rb new file mode 100644 index 00000000000..e193a39ce7e --- /dev/null +++ b/test/remote/gateways/remote_cams_test.rb @@ -0,0 +1,140 @@ +require 'test_helper' + +class RemoteCamsTest < Test::Unit::TestCase + @@amount = 100 + def setup + @gateway = CamsGateway.new(fixtures(:cams)) + + @amount = rand(100..100000) + + @credit_card = credit_card('4111111111111111') + @declined_card = credit_card('4000300015555555') + + @options = { + order_id: generate_unique_id, + billing_address: address, + description: 'Store Purchase' + } + end + + def test_transcript_scrubbing + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @credit_card, @options) + end + transcript = @gateway.scrub(transcript) + + assert_scrubbed(@credit_card.number, transcript) + assert_scrubbed(%r{=#{@credit_card.verification_value}\b}, transcript) + assert_scrubbed(@gateway.options[:password], transcript) + end + + def test_successful_purchase + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal 'SUCCESS', response.message + end + + def test_failed_purchase + response = @gateway.purchase(@amount, @declined_card, @options) + assert_failure response + assert !(%r(Invalid Credit Card Number) =~ response.message).nil? + end + + def test_successful_purchase_with_reference + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal 'SUCCESS', response.message + + setup + response2 = @gateway.purchase(@amount, response.authorization, @options) + assert_equal 'SUCCESS', response2.message + end + + def test_failed_purchase_with_reference + response = @gateway.purchase(@amount, '00000', @options) + assert_failure response + assert !(%r(Invalid Transaction ID) =~ response.message).nil? + end + + def test_successful_authorize_and_capture + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + assert capture = @gateway.capture(@amount, auth.authorization) + assert_success capture + end + + def test_failed_authorize + response = @gateway.authorize(@amount, @declined_card, @options) + assert_failure response + end + + def test_partial_capture + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + assert capture = @gateway.capture(@amount-1, auth.authorization) + assert_success capture + end + + def test_failed_capture + response = @gateway.capture(nil, '') + assert_failure response + end + + def test_successful_refund + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + + assert refund = @gateway.refund(nil, purchase.authorization) + assert_success refund + end + + def test_partial_refund + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + + assert refund = @gateway.refund(@amount-1, purchase.authorization) + assert_success refund + end + + def test_failed_refund + response = @gateway.refund(nil, '') + assert_failure response + end + + def test_successful_void + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + assert void = @gateway.void(auth.authorization) + assert_success void + end + + def test_failed_void + response = @gateway.void('') + assert_failure response + end + + def test_successful_verify + response = @gateway.verify(@credit_card, @options) + assert_success response + assert_equal nil, response.message + end + + def test_failed_verify + response = @gateway.verify(@declined_card, @options) + assert_failure response + assert_match %r{Invalid Credit Card Number}, response.message + assert_equal Gateway::STANDARD_ERROR_CODE[:card_declined], response.error_code + end + + def test_invalid_login + gateway = CamsGateway.new( + username: '', + password: '' + ) + response = gateway.purchase(@amount, @credit_card, @options) + assert_failure response + end +end diff --git a/test/remote/gateways/remote_card_connect_test.rb b/test/remote/gateways/remote_card_connect_test.rb new file mode 100644 index 00000000000..4ec9496c19d --- /dev/null +++ b/test/remote/gateways/remote_card_connect_test.rb @@ -0,0 +1,228 @@ +require 'test_helper' + +class RemoteCardConnectTest < Test::Unit::TestCase + def setup + @gateway = CardConnectGateway.new(fixtures(:card_connect)) + + @amount = 100 + @credit_card = credit_card('4788250000121443') + @declined_card = credit_card('4387751111111053') + @options = { + billing_address: address, + description: 'Store Purchase' + } + @check = check(routing_number: '053000196') + @invalid_txn = '23221' + end + + def test_successful_purchase + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal 'Approval', response.message + end + + def test_successful_purchase_with_more_options + options = { + order_id: '1', + ip: '127.0.0.1', + email: 'joe@example.com', + po_number: '5FSD4', + tax_amount: '50', + freight_amount: '29', + duty_amount: '67', + order_date: '20170507', + ship_from_date: '20877', + items: [ + { + lineno: '1', + material: 'MATERIAL-1', + description: 'DESCRIPTION-1', + upc: 'UPC-1', + quantity: '1000', + uom: 'CS', + unitcost: '900', + netamnt: '150', + taxamnt: '117', + discamnt: '0' + }, + { + lineno: '2', + material: 'MATERIAL-2', + description: 'DESCRIPTION-2', + upc: 'UPC-1', + quantity: '2000', + uom: 'CS', + unitcost: '450', + netamnt: '300', + taxamnt: '117', + discamnt: '0' + } + ] + } + + response = @gateway.purchase(@amount, @credit_card, options) + assert_success response + assert_equal 'Approval Queued for Capture', response.message + end + + def test_successful_purchase_3DS + three_ds_options = @options.merge( + secure_flag: 'se3453', + secure_value: '233frdf', + secure_xid: '334ef34' + ) + response = @gateway.purchase(@amount, @credit_card, three_ds_options) + assert_success response + assert_equal 'Approval', response.message + end + + def test_successful_purchase_with_profile + store_response = @gateway.store(@credit_card, @options) + assert_success store_response + purchase_response = @gateway.purchase(@amount, store_response.authorization, @options) + assert_success purchase_response + end + + def test_failed_purchase + response = @gateway.purchase(@amount, @declined_card, @options) + assert_failure response + assert_equal 'Insufficient funds', response.message + end + + def test_successful_authorize_and_capture + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + assert capture = @gateway.capture(@amount, auth.authorization) + assert_success capture + assert_includes ['Approval Queued for Capture', 'Approval Accepted'], capture.message + end + + def test_failed_authorize + response = @gateway.authorize(@amount, @declined_card, @options) + assert_failure response + assert_equal 'Insufficient funds', response.message + end + + def test_partial_capture + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + assert capture = @gateway.capture(@amount - 1, auth.authorization) + assert_success capture + end + + def test_failed_capture + response = @gateway.capture(@amount, @invalid_txn) + assert_failure response + assert_equal 'Txn not found', response.message + end + + def test_successful_echeck_purchase + response = @gateway.purchase(@amount, @check, @options) + assert_equal 'Success', response.message + assert_success response + end + + def test_failed_echeck_purchase + response = @gateway.purchase(@amount, check(routing_number: '23433'), @options) + assert_failure response + assert_equal 'Invalid card', response.message + end + + def test_successful_refund + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + + assert refund = @gateway.refund(@amount, purchase.authorization) + assert_success refund + assert_equal 'Approval', refund.message + end + + def test_partial_refund + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + + assert refund = @gateway.refund(@amount - 1, purchase.authorization) + assert_success refund + end + + def test_failed_refund + response = @gateway.refund(@amount, @invalid_txn) + assert_failure response + assert_equal 'Txn not found', response.message + end + + def test_successful_void + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + assert void = @gateway.void(auth.authorization) + assert_success void + assert_equal 'Approval', void.message + end + + def test_failed_void + response = @gateway.void(@invalid_txn) + assert_failure response + end + + def test_successful_verify + response = @gateway.verify(@credit_card, @options) + assert_success response + assert_match %r{Approval}, response.message + end + + def test_failed_verify + response = @gateway.verify(@declined_card, @options) + assert_failure response + assert_match %r{Insufficient funds}, response.message + end + + def test_successful_store + response = @gateway.store(@credit_card, @options) + + assert_success response + assert_equal 'Profile Saved', response.message + end + + def test_successful_unstore + store_response = @gateway.store(@credit_card, @options) + assert_success store_response + + unstore_response = @gateway.unstore(store_response.authorization, @options) + assert_success unstore_response + end + + def test_failed_unstore + response = @gateway.unstore('0|abcdefghijklmnopq', @options) + assert_failure response + end + + def test_invalid_login + gateway = CardConnectGateway.new(username: '', password: '', merchant_id: '') + response = gateway.purchase(@amount, @credit_card, @options) + + assert_failure response + assert_match %r{Unable to authenticate. Please check your credentials.}, response.message + end + + def test_transcript_scrubbing + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @credit_card, @options) + end + transcript = @gateway.scrub(transcript) + + assert_scrubbed(@credit_card.number, transcript) + assert_scrubbed(@credit_card.verification_value, transcript) + assert_scrubbed(@gateway.options[:password], transcript) + + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @check, @options) + end + transcript = @gateway.scrub(transcript) + + assert_scrubbed(@check.account_number, transcript) + assert_scrubbed(@gateway.options[:password], transcript) + end +end diff --git a/test/remote/gateways/remote_card_save_test.rb b/test/remote/gateways/remote_card_save_test.rb index 5774414fc17..06ee56d1053 100644 --- a/test/remote/gateways/remote_card_save_test.rb +++ b/test/remote/gateways/remote_card_save_test.rb @@ -1,23 +1,23 @@ require 'test_helper' -class RemoteCardSaveTest < Test::Unit::TestCase +class RemoteCardSaveTest < Test::Unit::TestCase def setup @gateway = CardSaveGateway.new(fixtures(:card_save)) - + @amount = 100 @credit_card = credit_card('4976000000003436', :verification_value => '452') @declined_card = credit_card('4221690000004963', :verification_value => '125') @addresses = {'4976000000003436' => { :name => 'John Watson', :address1 => '32 Edward Street', :city => 'Camborne,', :state => 'Cornwall', :country => 'GB', :zip => 'TR14 8PA' }, '4221690000004963' => { :name => 'Ian Lee', :address1 => '274 Lymington Avenue', :city => 'London', :state => 'London', :country => 'GB', :zip => 'N22 6JN' }} - - @options = { + + @options = { :order_id => '1', :billing_address => @addresses[@credit_card.number], :description => 'Store Purchase' } end - + def test_successful_purchase assert response = @gateway.purchase(@amount, @credit_card, @options) assert_success response @@ -25,7 +25,7 @@ def test_successful_purchase end def test_unsuccessful_purchase - @options.merge!(:billing_address => @addresses[@declined_card.number]) + @options[:billing_address] = @addresses[@declined_card.number] assert response = @gateway.purchase(@amount, @declined_card, @options) assert_failure response assert_equal 'Card declined', response.message @@ -49,8 +49,8 @@ def test_failed_capture def test_invalid_login gateway = CardSaveGateway.new( - :login => '', - :password => '' + :login => '', + :password => '' ) assert response = gateway.purchase(@amount, @credit_card, @options) assert_failure response diff --git a/test/remote/gateways/remote_card_stream_modern_test.rb b/test/remote/gateways/remote_card_stream_modern_test.rb deleted file mode 100644 index 3b65bd9b279..00000000000 --- a/test/remote/gateways/remote_card_stream_modern_test.rb +++ /dev/null @@ -1,286 +0,0 @@ -require 'test_helper' - -class RemoteCardStreamModernTest < Test::Unit::TestCase - def setup - Base.mode = :test - - @gateway = CardStreamModernGateway.new(fixtures(:card_stream_modern)) - - @amex = credit_card('374245455400001', - :month => '12', - :year => '2014', - :verification_value => '4887', - :brand => :american_express - ) - - @uk_maestro = credit_card('6759015050123445002', - :month => '12', - :year => '2014', - :issue_number => '0', - :verification_value => '309', - :brand => :switch - ) - - @mastercard = credit_card('5301250070000191', - :month => '12', - :year => '2014', - :verification_value => '419', - :brand => :master - ) - - @visacreditcard = credit_card('4929421234600821', - :month => '12', - :year => '2014', - :verification_value => '356', - :brand => :visa - ) - - @visadebitcard = credit_card('4539791001730106', - :month => '12', - :year => '2014', - :verification_value => '289', - :brand => :visa - ) - - @declined_card = credit_card('4000300011112220', - :month => '9', - :year => '2014' - ) - - @amex_options = { - :billing_address => { - :address1 => 'The Hunts Way', - :city => "", - :state => "Leicester", - :zip => 'SO18 1GW' - }, - :order_id => generate_unique_id, - :description => 'AM test purchase' - } - - @visacredit_options = { - :billing_address => { - :address1 => "Flat 6, Primrose Rise", - :address2 => "347 Lavender Road", - :city => "", - :state => "Northampton", - :zip => 'NN17 8YG ' - }, - :order_id => generate_unique_id, - :description => 'AM test purchase' - } - - @visadebit_options = { - :billing_address => { - :address1 => 'Unit 5, Pickwick Walk', - :address2 => "120 Uxbridge Road", - :city => "Hatch End", - :state => "Middlesex", - :zip => "HA6 7HJ" - }, - :order_id => generate_unique_id, - :description => 'AM test purchase' - } - - @mastercard_options = { - :billing_address => { - :address1 => '25 The Larches', - :city => "Narborough", - :state => "Leicester", - :zip => 'LE10 2RT' - }, - :order_id => generate_unique_id, - :description => 'AM test purchase' - } - - @uk_maestro_options = { - :billing_address => { - :address1 => 'The Parkway', - :address2 => "5258 Larches Approach", - :city => "Hull", - :state => "North Humberside", - :zip => 'HU10 5OP' - }, - :order_id => generate_unique_id, - :description => 'AM test purchase' - } - end - - def test_successful_visacreditcard_authorization_and_capture - assert responseAuthorization = @gateway.authorize(142, @visacreditcard, @visacredit_options) - assert_equal 'APPROVED', responseAuthorization.message - assert_success responseAuthorization - assert responseAuthorization.test? - assert !responseAuthorization.authorization.blank? - assert responseCapture = @gateway.capture(142, responseAuthorization.authorization, @visacredit_options) - assert_equal 'APPROVED', responseCapture.message - assert_success responseCapture - assert responseCapture.test? - end - - def test_successful_visacreditcard_authorization_and_refund - assert responseAuthorization = @gateway.authorize(284, @visacreditcard, @visacredit_options) - assert_equal 'APPROVED', responseAuthorization.message - assert_success responseAuthorization - assert responseAuthorization.test? - assert !responseAuthorization.authorization.blank? - assert responseRefund = @gateway.refund(142, responseAuthorization.authorization, @visacredit_options) - assert_equal 'APPROVED', responseRefund.message - assert_success responseRefund - assert responseRefund.test? - end - - def test_successful_visacreditcard_authorization_and_void - assert responseAuthorization = @gateway.authorize(284, @visacreditcard, @visacredit_options) - assert_equal 'APPROVED', responseAuthorization.message - assert_success responseAuthorization - assert responseAuthorization.test? - assert !responseAuthorization.authorization.blank? - assert responseRefund = @gateway.void(responseAuthorization.authorization, @visacredit_options) - assert_equal 'APPROVED', responseRefund.message - assert_success responseRefund - assert responseRefund.test? - end - - def test_successful_visadebitcard_authorization_and_capture - assert responseAuthorization = @gateway.authorize(142, @visadebitcard, @visadebit_options) - assert_equal 'APPROVED', responseAuthorization.message - assert_success responseAuthorization - assert responseAuthorization.test? - assert !responseAuthorization.authorization.blank? - assert responseCapture = @gateway.capture(142, responseAuthorization.authorization, @visadebit_options) - assert_equal 'APPROVED', responseCapture.message - assert_success responseCapture - assert responseCapture.test? - end - - def test_successful_visadebitcard_authorization_and_refund - assert responseAuthorization = @gateway.authorize(284, @visadebitcard, @visadebit_options) - assert_equal 'APPROVED', responseAuthorization.message - assert_success responseAuthorization - assert responseAuthorization.test? - assert !responseAuthorization.authorization.blank? - assert responseRefund = @gateway.refund(142, responseAuthorization.authorization, @visadebit_options) - assert_equal 'APPROVED', responseRefund.message - assert_success responseRefund - assert responseRefund.test? - end - - def test_successful_amex_authorization_and_capture - assert responseAuthorization = @gateway.authorize(142, @amex, @amex_options) - assert_equal 'APPROVED', responseAuthorization.message - assert_success responseAuthorization - assert responseAuthorization.test? - assert !responseAuthorization.authorization.blank? - assert responseCapture = @gateway.capture(142, responseAuthorization.authorization, @amex_options) - assert_equal 'APPROVED', responseCapture.message - assert_success responseCapture - assert responseCapture.test? - end - - def test_successful_amex_authorization_and_refund - assert responseAuthorization = @gateway.authorize(284, @amex, @amex_options) - assert_equal 'APPROVED', responseAuthorization.message - assert_success responseAuthorization - assert responseAuthorization.test? - assert !responseAuthorization.authorization.blank? - assert responseRefund = @gateway.refund(142, responseAuthorization.authorization, @amex_options) - assert_equal 'APPROVED', responseRefund.message - assert_success responseRefund - assert responseRefund.test? - end - - def test_successful_mastercard_authorization_and_capture - assert responseAuthorization = @gateway.authorize(142, @mastercard, @mastercard_options) - assert_equal 'APPROVED', responseAuthorization.message - assert_success responseAuthorization - assert responseAuthorization.test? - assert !responseAuthorization.authorization.blank? - assert responseCapture = @gateway.capture(142, responseAuthorization.authorization, @mastercard_options) - assert_equal 'APPROVED', responseCapture.message - assert_success responseCapture - assert responseCapture.test? - end - - def test_successful_mastercard_authorization_and_refund - assert responseAuthorization = @gateway.authorize(284, @mastercard, @mastercard_options) - assert_equal 'APPROVED', responseAuthorization.message - assert_success responseAuthorization - assert responseAuthorization.test? - assert !responseAuthorization.authorization.blank? - assert responseRefund = @gateway.refund(142, responseAuthorization.authorization, @mastercard_options) - assert_equal 'APPROVED', responseRefund.message - assert_success responseRefund - assert responseRefund.test? - end - - def test_successful_visacreditcard_purchase - assert response = @gateway.purchase(142, @visacreditcard, @visacredit_options) - assert_equal 'APPROVED', response.message - assert_success response - assert response.test? - assert !response.authorization.blank? - end - - def test_successful_visadebitcard_purchase - assert response = @gateway.purchase(142, @visadebitcard, @visadebit_options) - assert_equal 'APPROVED', response.message - assert_success response - assert response.test? - assert !response.authorization.blank? - end - - def test_successful_mastercard_purchase - assert response = @gateway.purchase(142, @mastercard, @mastercard_options) - assert_equal 'APPROVED', response.message - assert_success response - assert response.test? - assert !response.authorization.blank? - end - - def test_declined_mastercard_purchase - assert response = @gateway.purchase(10000, @mastercard, @mastercard_options) - assert_equal 'CARD DECLINED', response.message - assert_failure response - assert response.test? - end - - def test_expired_mastercard - @mastercard.year = 2012 - assert response = @gateway.purchase(142, @mastercard, @mastercard_options) - assert_equal 'INVALID CARDEXPIRYDATE', response.message - assert_failure response - assert response.test? - end - - def test_successful_maestro_purchase - assert response = @gateway.purchase(142, @uk_maestro, @uk_maestro_options) - assert_equal 'APPROVED', response.message - assert_success response - end - - def test_successful_amex_purchase - assert response = @gateway.purchase(142, @amex, @amex_options) - assert_equal 'APPROVED', response.message - assert_success response - assert response.test? - assert !response.authorization.blank? - end - - def test_invalid_login - gateway = CardStreamModernGateway.new( - :login => '', - :password => '' - ) - assert response = gateway.purchase(142, @mastercard, @mastercard_options) - assert_equal 'MISSING MERCHANTID', response.message - assert_failure response - end - - def test_usd_merchant_currency - assert response = @gateway.purchase(142, @mastercard, @mastercard_options.update(:currency => 'USD')) - assert_equal 'APPROVED', response.message - assert_success response - assert response.test? - end -end diff --git a/test/remote/gateways/remote_card_stream_test.rb b/test/remote/gateways/remote_card_stream_test.rb index 1ae1de60cee..fd6d3e8e3ef 100644 --- a/test/remote/gateways/remote_card_stream_test.rb +++ b/test/remote/gateways/remote_card_stream_test.rb @@ -3,146 +3,429 @@ class RemoteCardStreamTest < Test::Unit::TestCase def setup Base.mode = :test - + @gateway = CardStreamGateway.new(fixtures(:card_stream)) - + @amex = credit_card('374245455400001', - :month => '12', - :year => '2009', - :verification_value => '4887', - :brand => :american_express - ) - - @uk_maestro = credit_card('675940410531100173', - :month => '12', - :year => '2008', - :issue_number => '0', - :verification_value => '134', - :brand => :switch - ) - - @solo = credit_card('676740340572345678', - :month => '12', - :year => '2008', - :issue_number => '1', - :verification_value => '773', - :brand => :solo - ) + :month => '12', + :year => '2014', + :verification_value => '4887', + :brand => :american_express + ) @mastercard = credit_card('5301250070000191', - :month => '12', - :year => '2009', - :verification_value => '419', - :brand => :master - ) + :month => '12', + :year => '2014', + :verification_value => '419', + :brand => :master + ) + + @visacreditcard = credit_card('4929421234600821', + :month => '12', + :year => '2014', + :verification_value => '356', + :brand => :visa + ) + + @visadebitcard = credit_card('4539791001730106', + :month => '12', + :year => '2014', + :verification_value => '289', + :brand => :visa + ) @declined_card = credit_card('4000300011112220', - :month => '9', - :year => '2009' - ) + :month => '9', + :year => '2014' + ) - @mastercard_options = { - :billing_address => { - :address1 => '25 The Larches', - :city => "Narborough", - :state => "Leicester", - :zip => 'LE10 2RT' + @amex_options = { + :billing_address => { + :address1 => 'The Hunts Way', + :city => '', + :state => 'Leicester', + :zip => 'SO18 1GW', + :country => 'GB' }, :order_id => generate_unique_id, - :description => 'Store purchase' + :description => 'AM test purchase', + :ip => '1.1.1.1' } - - @uk_maestro_options = { - :billing_address => { - :address1 => 'The Parkway', - :address2 => "Larches Approach", - :city => "Hull", - :state => "North Humberside", - :zip => 'HU7 9OP' + + @visacredit_options = { + :billing_address => { + :address1 => 'Flat 6, Primrose Rise', + :address2 => '347 Lavender Road', + :city => '', + :state => 'Northampton', + :zip => 'NN17 8YG', + :country => 'GB' }, :order_id => generate_unique_id, - :description => 'Store purchase' + :description => 'AM test purchase', + :ip => '1.1.1.1' } - - @solo_options = { + + @visacredit_descriptor_options = { :billing_address => { - :address1 => '5 Zigzag Road', - :city => 'Isleworth', + :address1 => 'Flat 6, Primrose Rise', + :address2 => '347 Lavender Road', + :city => '', + :state => 'Northampton', + :zip => 'NN17 8YG', + :country => 'GB' + }, + :merchant_name => 'merchant', + :dynamic_descriptor => 'product', + :ip => '1.1.1.1', + } + + @visacredit_reference_options = { + :order_id => generate_unique_id, + :description => 'AM test purchase', + :ip => '1.1.1.1' + } + + @visadebit_options = { + :billing_address => { + :address1 => 'Unit 5, Pickwick Walk', + :address2 => '120 Uxbridge Road', + :city => 'Hatch End', :state => 'Middlesex', - :zip => 'TW7 8FF' + :zip => 'HA6 7HJ', + :country => 'GB' + }, + :order_id => generate_unique_id, + :description => 'AM test purchase', + :ip => '1.1.1.1' + } + + @mastercard_options = { + :billing_address => { + :address1 => '25 The Larches', + :city => 'Narborough', + :state => 'Leicester', + :zip => 'LE10 2RT', + :country => 'GB' }, :order_id => generate_unique_id, - :description => 'Store purchase' + :description => 'AM test purchase', + :ip => '1.1.1.1' } + + @three_ds_enrolled_card = credit_card('4012001037141112', + :month => '12', + :year => '2020', + :brand => :visa + ) end - - def test_successful_mastercard_purchase - assert response = @gateway.purchase(100, @mastercard, @mastercard_options) + + def test_successful_visacreditcard_authorization_and_capture + assert responseAuthorization = @gateway.authorize(142, @visacreditcard, @visacredit_options) + assert_equal 'APPROVED', responseAuthorization.message + assert_success responseAuthorization + assert responseAuthorization.test? + assert !responseAuthorization.authorization.blank? + assert responseCapture = @gateway.capture(142, responseAuthorization.authorization, @visacredit_options) + assert_equal 'APPROVED', responseCapture.message + assert_success responseCapture + assert responseCapture.test? + end + + def test_successful_visacreditcard_authorization_and_capture_no_billing_address + assert responseAuthorization = @gateway.authorize(142, @visacreditcard, @visacredit_options.delete(:billing_address)) + assert_equal 'APPROVED', responseAuthorization.message + assert_success responseAuthorization + assert responseAuthorization.test? + assert !responseAuthorization.authorization.blank? + assert responseCapture = @gateway.capture(142, responseAuthorization.authorization, @visacredit_options) + assert_equal 'APPROVED', responseCapture.message + assert_success responseCapture + assert responseCapture.test? + end + + def test_successful_visacreditcard_purchase_and_refund_with_force_refund + assert responsePurchase = @gateway.purchase(284, @visacreditcard, @visacredit_options) + assert_equal 'APPROVED', responsePurchase.message + assert_success responsePurchase + assert responsePurchase.test? + assert !responsePurchase.authorization.blank? + + assert responseRefund = @gateway.refund(142, responsePurchase.authorization, @visacredit_options.merge(force_full_refund_if_unsettled: true)) + assert_equal 'APPROVED', responseRefund.message + assert_success responseRefund + assert responseRefund.test? + end + + def test_failed_visacreditcard_purchase_and_refund + assert responsePurchase = @gateway.purchase(284, @visacreditcard, @visacredit_options) + assert_equal 'APPROVED', responsePurchase.message + assert_success responsePurchase + assert responsePurchase.test? + assert !responsePurchase.authorization.blank? + + assert responseRefund = @gateway.refund(142, responsePurchase.authorization, @visacredit_options) + assert_failure responseRefund + assert_equal 'Can not REFUND this SALE transaction', responseRefund.message + assert responseRefund.test? + end + + def test_successful_visacreditcard_purchase_with_dynamic_descriptors + assert responsePurchase = @gateway.purchase(284, @visacreditcard, @visacredit_descriptor_options) + assert_equal 'APPROVED', responsePurchase.message + assert_success responsePurchase + assert responsePurchase.test? + assert !responsePurchase.authorization.blank? + end + + def test_successful_visacreditcard_authorization_and_void + assert responseAuthorization = @gateway.authorize(284, @visacreditcard, @visacredit_options) + assert_equal 'APPROVED', responseAuthorization.message + assert_success responseAuthorization + assert responseAuthorization.test? + assert !responseAuthorization.authorization.blank? + assert responseVoid = @gateway.void(responseAuthorization.authorization, @visacredit_options) + assert_equal 'APPROVED', responseVoid.message + assert_success responseVoid + assert responseVoid.test? + end + + def test_successful_visadebitcard_authorization_and_capture + assert responseAuthorization = @gateway.authorize(142, @visadebitcard, @visadebit_options) + assert_equal 'APPROVED', responseAuthorization.message + assert_success responseAuthorization + assert responseAuthorization.test? + assert !responseAuthorization.authorization.blank? + assert responseCapture = @gateway.capture(142, responseAuthorization.authorization, @visadebit_options) + assert_equal 'APPROVED', responseCapture.message + assert_success responseCapture + assert responseCapture.test? + end + + def test_successful_visadebitcard_purchase_and_refund_with_force_refund + assert responsePurchase = @gateway.purchase(284, @visadebitcard, @visadebit_options) + assert_equal 'APPROVED', responsePurchase.message + assert_success responsePurchase + assert responsePurchase.test? + assert !responsePurchase.authorization.blank? + + assert responseRefund = @gateway.refund(142, responsePurchase.authorization, @visadebit_options.merge(force_full_refund_if_unsettled: true)) + assert_equal 'APPROVED', responseRefund.message + assert_success responseRefund + assert responseRefund.test? + end + + def test_failed_visadebitcard_purchase_and_refund + assert responsePurchase = @gateway.purchase(284, @visadebitcard, @visadebit_options) + assert_equal 'APPROVED', responsePurchase.message + assert_success responsePurchase + assert responsePurchase.test? + assert !responsePurchase.authorization.blank? + + assert responseRefund = @gateway.refund(142, responsePurchase.authorization, @visadebit_options) + assert_equal 'Can not REFUND this SALE transaction', responseRefund.message + assert_failure responseRefund + assert responseRefund.test? + end + + def test_successful_amex_authorization_and_capture + assert responseAuthorization = @gateway.authorize(142, @amex, @amex_options) + assert_equal 'APPROVED', responseAuthorization.message + assert_success responseAuthorization + assert responseAuthorization.test? + assert !responseAuthorization.authorization.blank? + assert responseCapture = @gateway.capture(142, responseAuthorization.authorization, @amex_options) + assert_equal 'APPROVED', responseCapture.message + assert_success responseCapture + assert responseCapture.test? + end + + def test_successful_amex_purchase_and_refund_with_force_refund + assert responsePurchase = @gateway.purchase(284, @amex, @amex_options) + assert_equal 'APPROVED', responsePurchase.message + assert_success responsePurchase + assert responsePurchase.test? + assert !responsePurchase.authorization.blank? + + assert responseRefund = @gateway.refund(142, responsePurchase.authorization, @amex_options.merge(force_full_refund_if_unsettled: true)) + assert_equal 'APPROVED', responseRefund.message + assert_success responseRefund + assert responseRefund.test? + end + + def test_failed_amex_purchase_and_refund + assert responsePurchase = @gateway.purchase(284, @amex, @amex_options) + assert_equal 'APPROVED', responsePurchase.message + assert_success responsePurchase + assert responsePurchase.test? + assert !responsePurchase.authorization.blank? + + assert responseRefund = @gateway.refund(142, responsePurchase.authorization, @amex_options) + assert_equal 'Can not REFUND this SALE transaction', responseRefund.message + assert_failure responseRefund + assert responseRefund.test? + end + + def test_successful_mastercard_authorization_and_capture + assert responseAuthorization = @gateway.authorize(142, @mastercard, @mastercard_options) + assert_equal 'APPROVED', responseAuthorization.message + assert_success responseAuthorization + assert responseAuthorization.test? + assert !responseAuthorization.authorization.blank? + assert responseCapture = @gateway.capture(142, responseAuthorization.authorization, @mastercard_options) + assert_equal 'APPROVED', responseCapture.message + assert_success responseCapture + assert responseCapture.test? + end + + def test_successful_mastercard_purchase_and_refund_with_force_refund + assert responsePurchase = @gateway.purchase(284, @mastercard, @mastercard_options) + assert_equal 'APPROVED', responsePurchase.message + assert_success responsePurchase + assert responsePurchase.test? + assert !responsePurchase.authorization.blank? + + assert responseRefund = @gateway.refund(142, responsePurchase.authorization, @mastercard_options.merge(force_full_refund_if_unsettled: true)) + assert_equal 'APPROVED', responseRefund.message + assert_success responseRefund + assert responseRefund.test? + end + + def test_failed_mastercard_purchase_and_refund + assert responsePurchase = @gateway.purchase(284, @mastercard, @mastercard_options) + assert_equal 'APPROVED', responsePurchase.message + assert_success responsePurchase + assert responsePurchase.test? + assert !responsePurchase.authorization.blank? + + assert responseRefund = @gateway.refund(142, responsePurchase.authorization, @mastercard_options) + assert_equal 'Can not REFUND this SALE transaction', responseRefund.message + assert_failure responseRefund + assert responseRefund.test? + end + + def test_successful_visacreditcard_purchase + assert response = @gateway.purchase(142, @visacreditcard, @visacredit_options) assert_equal 'APPROVED', response.message assert_success response assert response.test? assert !response.authorization.blank? end - - def test_declined_mastercard_purchase - assert response = @gateway.purchase(10000, @mastercard, @mastercard_options) - assert_equal 'CARD DECLINED', response.message - assert_failure response + + def test_successful_visacreditcard_purchase_via_reference + assert response = @gateway.purchase(142, @visacreditcard, @visacredit_options.merge({:type => '9'})) + assert_equal 'APPROVED', response.message + assert_success response + assert response.test? + assert response = @gateway.purchase(142, response.authorization, @visacredit_reference_options) + assert_equal 'APPROVED', response.message + assert_success response assert response.test? end - - def test_expired_mastercard - @mastercard.year = 2005 - assert response = @gateway.purchase(100, @mastercard, @mastercard_options) - assert_equal 'CARD EXPIRED', response.message + + def test_failed_visacreditcard_purchase_via_reference + assert response = @gateway.purchase(142, 123, @visacredit_reference_options) + assert_match %r{INVALID_XREF}, response.message assert_failure response assert response.test? end - def test_successful_maestro_purchase - assert response = @gateway.purchase(100, @uk_maestro, @uk_maestro_options) - assert_equal 'APPROVED', response.message + def test_purchase_no_currency_specified_defaults_to_GBP + assert response = @gateway.purchase(142, @visacreditcard, @visacredit_options.merge(currency: nil)) assert_success response + assert_equal '826', response.params['currencyCode'] + assert_equal 'APPROVED', response.message end - - def test_successful_solo_purchase - assert response = @gateway.purchase(100, @solo, @solo_options) + + def test_failed_purchase_non_existent_currency + assert response = @gateway.purchase(142, @visacreditcard, @visacredit_options.merge(currency: 'CEO')) + assert_failure response + assert_match %r{MISSING_CURRENCYCODE}, response.message + end + + def test_successful_visadebitcard_purchase + assert response = @gateway.purchase(142, @visadebitcard, @visadebit_options) assert_equal 'APPROVED', response.message assert_success response assert response.test? assert !response.authorization.blank? end - - def test_successful_amex_purchase - assert response = @gateway.purchase(100, @amex, :order_id => generate_unique_id) + + def test_successful_mastercard_purchase + assert response = @gateway.purchase(142, @mastercard, @mastercard_options) assert_equal 'APPROVED', response.message assert_success response assert response.test? assert !response.authorization.blank? end - - def test_maestro_missing_start_date_and_issue_date - @uk_maestro.issue_number = nil - assert response = @gateway.purchase(100, @uk_maestro, @uk_maestro_options) - assert_equal 'ISSUE NUMBER MISSING', response.message + + def test_declined_mastercard_purchase + assert response = @gateway.purchase(10000, @mastercard, @mastercard_options) + assert_equal 'CARD DECLINED', response.message assert_failure response assert response.test? end - + + def test_successful_amex_purchase + assert response = @gateway.purchase(142, @amex, @amex_options) + assert_equal 'APPROVED', response.message + assert_success response + assert response.test? + assert !response.authorization.blank? + end + def test_invalid_login gateway = CardStreamGateway.new( :login => '', - :password => '' + :shared_secret => '' ) - assert response = gateway.purchase(100, @mastercard, @mastercard_options) - assert_equal 'MERCHANT ID MISSING', response.message + assert response = gateway.purchase(142, @mastercard, @mastercard_options) + assert_match %r{MISSING_MERCHANTID}, response.message assert_failure response end - - def test_unsupported_merchant_currency - assert response = @gateway.purchase(100, @mastercard, @mastercard_options.update(:currency => 'USD')) - assert_equal "ERROR 1052", response.message + + def test_successful_verify + response = @gateway.verify(@mastercard, @mastercard_options) + assert_success response + assert_equal 'APPROVED', response.message + end + + def test_failed_verify + response = @gateway.verify(@declined_card, @mastercard_options) assert_failure response + assert_match %r{INVALID_CARDNUMBER}, response.message + end + + def test_successful_3dsecure_purchase + assert response = @gateway.purchase(1202, @three_ds_enrolled_card, @mastercard_options.merge(threeds_required: true)) + assert_equal '3DS AUTHENTICATION REQUIRED', response.message + assert_equal '65802', response.params['responseCode'] + assert response.test? + assert !response.authorization.blank? + assert !response.params['threeDSACSURL'].blank? + assert !response.params['threeDSMD'].blank? + assert !response.params['threeDSPaReq'].blank? + end + + def test_successful_3dsecure_auth + assert response = @gateway.authorize(1202, @three_ds_enrolled_card, @mastercard_options.merge(threeds_required: true)) + assert_equal '3DS AUTHENTICATION REQUIRED', response.message + assert_equal '65802', response.params['responseCode'] assert response.test? + assert !response.authorization.blank? + assert !response.params['threeDSACSURL'].blank? + assert !response.params['threeDSMD'].blank? + assert !response.params['threeDSPaReq'].blank? + end + + def test_transcript_scrubbing + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @visacreditcard, @visacredit_options) + end + clean_transcript = @gateway.scrub(transcript) + + assert_scrubbed(@visacreditcard.number, clean_transcript) + assert_scrubbed(@visacreditcard.verification_value.to_s, clean_transcript) + assert_scrubbed(@gateway.options[:shared_secret], clean_transcript) end end diff --git a/test/remote/gateways/remote_cardknox_test.rb b/test/remote/gateways/remote_cardknox_test.rb new file mode 100644 index 00000000000..211e1e9afcb --- /dev/null +++ b/test/remote/gateways/remote_cardknox_test.rb @@ -0,0 +1,301 @@ +require 'test_helper' + +class RemoteCardknoxTest < Test::Unit::TestCase + def setup + @gateway = CardknoxGateway.new(fixtures(:cardknox)) + + @amount = rand(100..499) + @declined_amount = 500 + @credit_card = credit_card('4000100011112224') + @declined_card = credit_card('4000300011112220', verification_value: '518') + @check = check(number: rand(0..100000)) + + @more_options = { + billing_address: address, + shipping_address: address, + order_id: generate_unique_id, + invoice: generate_unique_id, + name: 'Jim Smith', + ip: '127.0.0.1', + email: 'joe@example.com', + tip: 2, + tax: 3, + custom02: 'mycustom', + custom13: 'spelled right', + custom25: 'test 25', + pin: '312lkjasdnotvalid', + address: { + address1: '19 Laurel Valley Dr', + address2: 'Apt 1', + company: 'Widgets Inc', + city: 'Brownsburg', + state: 'IN', + zip: '46112', + country: 'US', + phone: '(555)555-5555', + fax: '(555)555-6666', + } + } + + @options = {} + end + + def test_successful_credit_card_purchase + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal 'Success', response.message + end + + def test_successful_credit_card_purchase_with_more_options + response = @gateway.purchase(@amount, @credit_card, @more_options) + assert_success response + assert_equal 'Success', response.message + end + + def test_successful_credit_card_track_data_purchase + response = @gateway.purchase(@amount, credit_card_with_track_data, @options) + assert_success response + assert_equal 'Success', response.message + end + + def test_successful_credit_card_token_purchase + response = @gateway.store(@credit_card, @options) + assert_success response + assert_equal 'Success', response.message + + assert purchase = @gateway.purchase(@amount, response.authorization, @options) + assert_success purchase + assert_equal 'Success', purchase.message + end + + def test_successful_check_purchase_with_options + response = @gateway.purchase(@amount, @check, @more_options) + assert_success response + assert_equal 'Success', response.message + end + + def test_successful_check_token_purchase + response = @gateway.store(@check, @options) + assert_success response + assert_equal 'Success', response.message + + assert purchase = @gateway.purchase(@amount, response.authorization) + assert_success purchase + assert_equal 'Success', purchase.message + end + + def test_failed_credit_card_purchase + response = @gateway.purchase(@amount, @declined_card, @options) + assert_failure response + assert_equal 'Invalid CVV', response.message + end + + def test_successful_credit_card_authorize_and_capture + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + assert_equal 'Success', auth.message + + assert capture = @gateway.capture(@amount, auth.authorization) + assert_success capture + assert_equal 'Success', auth.message + end + + def test_successful_cardknox_token_authorize_and_capture + response = @gateway.store(@credit_card, @options) + assert_success response + assert_equal 'Success', response.message + + auth = @gateway.authorize(@amount, response.authorization, @options) + assert_success auth + assert_equal 'Success', auth.message + + assert capture = @gateway.capture(@amount, auth.authorization) + assert_success capture + assert_equal 'Success', auth.message + end + + def test_failed_authorize + response = @gateway.authorize(@amount, @declined_card, @options) + assert_failure response + assert_equal 'Invalid CVV', response.message + end + + def test_partial_capture + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + assert capture = @gateway.capture(@amount-1, auth.authorization) + assert_success capture + end + + def test_failed_capture + response = @gateway.capture(@amount, '') + assert_failure response + assert_equal 'Original transaction not specified', response.message + end + + def test_credit_card_purchase_partial_refund + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + + assert refund = @gateway.refund(@amount-1, purchase.authorization) + assert_success refund + end + + def test_failed_credit_card_authorize_partial_refund + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + assert refund = @gateway.refund(@amount-1, auth.authorization) + assert_failure refund + assert_equal 'Refund not allowed on non-captured auth.', refund.message + end + + def test_failed_partial_check_refund # the gate way does not support this transaction + purchase = @gateway.purchase(@amount, @check, @options) + assert_success purchase + + assert refund = @gateway.refund(@amount-1, purchase.authorization) + assert_failure refund + assert_equal "Transaction is in a state that cannot be refunded\nParameter name: originalReferenceNumber", refund.message # "Only allowed to refund transactions that have settled. This is the best we can do for now testing wise." + end + + def test_credit_card_capture_partial_refund + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + assert capture = @gateway.capture(@amount, auth.authorization) + assert_success capture + + assert refund = @gateway.refund(@amount-1, capture.authorization) + assert_success refund + end + + def test_failed_refund + response = @gateway.refund(@amount, '') + assert_failure response + assert_equal 'UNSUPPORTED CARD TYPE', response.message + end + + def test_successful_credit_card_authorize_void + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + assert void = @gateway.void(auth.authorization, @options) + assert_success void + assert_equal 'Success', void.message + end + + def test_successful_credit_card_capture_void + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + assert capture = @gateway.capture(@amount-1, auth.authorization) + assert_success capture + + assert void = @gateway.void(capture.authorization, @options) + assert_success void + assert_equal 'Success', void.message + end + + def test_successful_credit_card_purchase_void + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + + assert void = @gateway.void(purchase.authorization, @options) + assert_success void + assert_equal 'Success', void.message + end + + def test_successful_credit_card_refund_void + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + assert capture = @gateway.capture(@amount, auth.authorization) + assert_success capture + + assert refund = @gateway.refund(@amount-1, capture.authorization) + assert_success refund + + assert void = @gateway.void(refund.authorization, @options) + assert_success void + assert_equal 'Success', void.message + end + + def test_successful_check_void + purchase = @gateway.purchase(@amount, @check, @options) + assert_success purchase + + assert void = @gateway.void(purchase.authorization, @options) + assert_success void + assert_equal 'Success', void.message + end + + def test_failed_void + response = @gateway.void('') + assert_failure response + assert_equal 'Original transaction not specified', response.message + end + + def test_successful_verify + response = @gateway.verify(@credit_card, @options) + assert_success response + assert_match %r{Success}, response.message + end + + def test_failed_verify + response = @gateway.verify(@declined_card, @options) + assert_failure response + assert_match %r{Invalid CVV}, response.message + end + + def test_successful_credit_card_store + response = @gateway.store(@credit_card, @options) + assert_success response + assert_equal 'Success', response.message + end + + def test_successful_credit_card_token_store + response = @gateway.store(@credit_card, @options) + assert_success response + assert_equal 'Success', response.message + + assert store = @gateway.store(response.authorization) + assert_success store + assert_equal 'Success', store.message + end + + def test_successful_check_store + response = @gateway.store(@check, @options) + assert_success response + assert_equal 'Success', response.message + end + + def test_failed_store + response = @gateway.store('', @options) + assert_failure response + assert_equal 'Card or Magstripe Required', response.message + end + + def test_invalid_login + gateway = CardknoxGateway.new(api_key: '') + + response = gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_match %r{Required: xKey}, response.message + end + + def test_transcript_scrubbing + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @credit_card, @options) + @gateway.purchase(@amount, @check, @options) + end + transcript = @gateway.scrub(transcript) + + assert_scrubbed(@credit_card.number, transcript) + assert_scrubbed(@credit_card.verification_value, transcript) + assert_scrubbed(@check.routing_number, transcript) + assert_scrubbed(@check.account_number, transcript) + assert_scrubbed(@gateway.options[:api_key], transcript) + end +end diff --git a/test/remote/gateways/remote_cardprocess_test.rb b/test/remote/gateways/remote_cardprocess_test.rb new file mode 100644 index 00000000000..951c07e0ab3 --- /dev/null +++ b/test/remote/gateways/remote_cardprocess_test.rb @@ -0,0 +1,149 @@ +require 'test_helper' + +class RemoteCardprocessTest < Test::Unit::TestCase + def setup + @gateway = CardprocessGateway.new(fixtures(:cardprocess)) + + @amount = 100 + @credit_card = credit_card('4200000000000000') + @credit_card_3ds = credit_card('4711100000000000') + + @options = { + billing_address: address, + description: 'Store Purchase' + } + end + + def test_successful_purchase + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_match %r{^Request successfully processed}, response.message + end + + def test_successful_purchase_with_more_options + options = { + order_id: '1', + ip: '127.0.0.1', + email: 'joe@example.com' + } + + response = @gateway.purchase(@amount, @credit_card, options) + assert_success response + assert_match %r{^Request successfully processed}, response.message + end + + def test_failed_purchase + bad_credit_card = credit_card('4200000000000001') + response = @gateway.purchase(@amount, bad_credit_card, @options) + assert_failure response + assert_equal Gateway::STANDARD_ERROR_CODE[:incorrect_number], response.error_code + assert_equal 'invalid creditcard, bank account number or bank name', response.message + end + + def test_successful_authorize_and_capture + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + assert capture = @gateway.capture(@amount, auth.authorization) + assert_success capture + assert_match %r{^Request successfully processed}, capture.message + end + + def test_failed_authorize + @gateway.instance_variable_set(:@test_options, {'customParameters[forceResultCode]' => '800.100.151'}) + response = @gateway.authorize(@amount, @credit_card, @options) + assert_failure response + assert_equal 'transaction declined (invalid card)', response.message + end + + def test_partial_capture + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + assert capture = @gateway.capture(@amount-1, auth.authorization) + assert_success capture + end + + def test_failed_capture + response = @gateway.capture(@amount, '12345678123456781234567812345678') + assert_failure response + assert_equal 'capture needs at least one successful transaction of type (PA)', response.message + end + + def test_successful_refund + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + + assert refund = @gateway.refund(@amount, purchase.authorization) + assert_success refund + assert_match %r{^Request successfully processed}, refund.message + end + + def test_successful_credit + response = @gateway.credit(@amount, @credit_card, @options) + assert_success response + assert_match %r{^Request successfully processed}, response.message + end + + def test_partial_refund + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + + assert refund = @gateway.refund(@amount-1, purchase.authorization) + assert_success refund + end + + def test_failed_refund + response = @gateway.refund(@amount, '') + assert_failure response + assert_equal 'invalid or missing parameter', response.message + end + + def test_successful_void + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + assert void = @gateway.void(auth.authorization) + assert_success void + assert_match %r{^Request successfully processed}, void.message + end + + def test_failed_void + response = @gateway.void('') + assert_failure response + assert_equal 'invalid or missing parameter', response.message + end + + def test_successful_verify + response = @gateway.verify(@credit_card, @options) + assert_success response + assert_match %r{^Request successfully processed}, response.message + end + + def test_failed_verify + @gateway.instance_variable_set(:@test_options, {'customParameters[forceResultCode]' => '600.200.100'}) + response = @gateway.verify(@credit_card, @options) + assert_failure response + assert_match %r{invalid Payment Method}, response.message + end + + def test_invalid_login + gateway = CardprocessGateway.new(user_id: '00000000000000000000000000000000', password: 'qwerty', entity_id: '00000000000000000000000000000000') + + response = gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_equal 'invalid authentication information', response.message + end + + def test_transcript_scrubbing + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @credit_card, @options) + end + transcript = @gateway.scrub(transcript) + + assert_scrubbed(@credit_card.number, transcript) + assert_scrubbed(@credit_card.verification_value, transcript) + assert_scrubbed(@gateway.options[:password], transcript) + assert_scrubbed(@gateway.options[:entity_id], transcript) + end +end diff --git a/test/remote/gateways/remote_cashnet_test.rb b/test/remote/gateways/remote_cashnet_test.rb new file mode 100644 index 00000000000..87aea788007 --- /dev/null +++ b/test/remote/gateways/remote_cashnet_test.rb @@ -0,0 +1,66 @@ +require 'test_helper' + +class CashnetTest < Test::Unit::TestCase + def setup + @gateway = CashnetGateway.new(fixtures(:cashnet)) + @amount = 100 + @credit_card = credit_card( + '5454545454545454', + month: 12, + year: 2015 + ) + @options = { + order_id: generate_unique_id, + billing_address: address + } + end + + def test_successful_purchase_and_refund + assert purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + assert purchase.test? + assert_equal 'Success', purchase.message + assert purchase.authorization + + assert refund = @gateway.refund(@amount, purchase.authorization, {}) + assert_success refund + assert refund.test? + assert_equal 'Success', refund.message + end + + def test_successful_refund_with_options + assert purchase = @gateway.purchase(@amount, @credit_card, custcode: 'TheCustCode') + assert_success purchase + + assert refund = @gateway.refund(@amount, purchase.authorization, email: 'wow@example.com', custcode: 'TheCustCode') + assert_success refund + end + + def test_failed_purchase + assert response = @gateway.purchase(-44, @credit_card, @options) + assert_failure response + assert_match %r{Negative amount is not allowed}, response.message + assert_equal '5', response.params['result'] + end + + def test_failed_refund + assert purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + + assert refund = @gateway.refund(@amount + 50, purchase.authorization) + assert_failure refund + assert_match %r{Amount to refund exceeds}, refund.message + assert_equal '302', refund.params['result'] + end + + def test_transcript_scrubbing + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @credit_card, @options) + end + transcript = @gateway.scrub(transcript) + + assert_scrubbed(@credit_card.number, transcript) + assert_scrubbed(@credit_card.verification_value, transcript) + assert_scrubbed(@gateway.options[:password], transcript) + end +end diff --git a/test/remote/gateways/remote_cecabank_test.rb b/test/remote/gateways/remote_cecabank_test.rb new file mode 100644 index 00000000000..b44ff568ead --- /dev/null +++ b/test/remote/gateways/remote_cecabank_test.rb @@ -0,0 +1,54 @@ +require 'test_helper' + +class RemoteCecabankTest < Test::Unit::TestCase + def setup + @gateway = CecabankGateway.new(fixtures(:cecabank)) + + @amount = 100 + @credit_card = credit_card('5540500001000004', {:month => 12, :year => Time.now.year, :verification_value => 989}) + @declined_card = credit_card('5540500001000004', {:month => 11, :year => Time.now.year + 1, :verification_value => 001}) + + @options = { + :order_id => generate_unique_id, + :description => 'Active Merchant Test Purchase' + } + end + + def test_successful_purchase + assert response = @gateway.purchase(@amount, @credit_card, order_id: generate_unique_id) + assert_success response + assert_equal 'OK', response.message + end + + def test_unsuccessful_purchase + assert response = @gateway.purchase(@amount, @declined_card, @options) + assert_failure response + assert_match 'ERROR', response.message + end + + def test_successful_refund + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + + assert response = @gateway.refund(@amount, purchase.authorization, order_id: @options[:order_id]) + assert_success response + assert_equal 'OK', response.message + end + + def test_unsuccessful_refund + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + + assert response = @gateway.refund(@amount, purchase.authorization, @options.merge(currency: 'USD')) + assert_failure response + assert_match 'ERROR', response.message + end + + def test_invalid_login + gateway = CecabankGateway.new(fixtures(:cecabank).merge(key: 'invalid')) + + assert response = gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_match 'ERROR', response.message + end +end diff --git a/test/remote/gateways/remote_cenpos_test.rb b/test/remote/gateways/remote_cenpos_test.rb new file mode 100644 index 00000000000..a3474658bbc --- /dev/null +++ b/test/remote/gateways/remote_cenpos_test.rb @@ -0,0 +1,192 @@ +require 'test_helper' + +class RemoteCenposTest < Test::Unit::TestCase + def setup + @gateway = CenposGateway.new(fixtures(:cenpos)) + + @amount = SecureRandom.random_number(10000) + @credit_card = credit_card('4111111111111111', month: 02, year: 18, verification_value: 999) + @declined_card = credit_card('4000300011112220') + @invalid_card = credit_card('9999999999999999') + + @options = { + order_id: SecureRandom.random_number(1000000), + billing_address: address + } + end + + def test_invalid_login + gateway = CenposGateway.new( + merchant_id: '', + password: '', + user_id: '' + ) + response = gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_equal 'See transcript for detailed error description.', response.message + end + + def test_successful_purchase + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal 'Succeeded', response.message + end + + def test_successful_purchase_cvv_result + response = @gateway.purchase(@amount, @credit_card, @options) + assert_equal 'M', response.cvv_result['code'] + end + + def test_successful_purchase_avs_result + response = @gateway.purchase(@amount, @credit_card, @options) + assert_equal 'D', response.avs_result['code'] + end + + def test_successful_purchase_with_invoice_detail + response = @gateway.purchase(@amount, @credit_card, @options.merge(invoice_detail: '<xml><description/></xml>')) + assert_success response + assert_equal 'Succeeded', response.message + end + + def test_successful_purchase_with_customer_code + response = @gateway.purchase(@amount, @credit_card, @options.merge(customer_code: '3214')) + assert_success response + assert_equal 'Succeeded', response.message + end + + def test_successful_purchase_with_currency + response = @gateway.purchase(@amount, @credit_card, @options.merge(currency: 'EUR')) + assert_success response + assert_equal 'Succeeded', response.message + end + + def test_failed_purchase + response = @gateway.purchase(@amount, @declined_card, @options) + assert_failure response + assert_equal 'Decline transaction', response.message + assert_equal Gateway::STANDARD_ERROR_CODE[:card_declined], response.error_code + end + + def test_failed_purchase_cvv_result + response = @gateway.purchase(@amount, @declined_card, @options) + %w(code message).each do |key| + assert_equal nil, response.cvv_result[key] + end + end + + def test_failed_purchase_avs_result + response = @gateway.purchase(@amount, @declined_card, @options) + %w(code message).each do |key| + assert_equal nil, response.avs_result[key] + end + end + + def test_successful_authorize_and_capture + response = @gateway.authorize(@amount, @credit_card, @options) + assert_success response + assert_equal 'Succeeded', response.message + assert_match %r(^\d+\|.+$), response.authorization + + capture = @gateway.capture(@amount, response.authorization) + assert_success capture + assert_equal 'Succeeded', capture.message + end + + def test_failed_authorize + response = @gateway.authorize(@amount, @declined_card, @options) + assert_failure response + assert_equal 'Decline transaction', response.message + assert_equal Gateway::STANDARD_ERROR_CODE[:card_declined], response.error_code + end + + def test_failed_capture + response = @gateway.authorize(@amount, @credit_card, @options) + assert_success response + assert_equal 'Succeeded', response.message + + @gateway.capture(@amount, response.authorization) + capture = @gateway.capture(@amount, response.authorization) + assert_failure capture + assert_equal 'Duplicated force transaction.', capture.message + end + + def test_successful_void + response = @gateway.authorize(@amount, @credit_card, @options) + assert_success response + + void = @gateway.void(response.authorization) + assert_success void + assert_equal 'Succeeded', void.message + end + + def test_void_can_receive_order_id + response = @gateway.authorize(@amount, @credit_card, @options) + assert_success response + + void = @gateway.void(response.authorization, order_id: SecureRandom.random_number(1000000)) + assert_success void + assert_equal 'Succeeded', void.message + end + + def test_failed_void + response = @gateway.authorize(@amount, @credit_card, @options) + assert_success response + + @gateway.void(response.authorization) + void = @gateway.void(response.authorization) + assert_failure void + assert_equal 'Original Transaction not found', void.message + end + + def test_successful_refund + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + + refund = @gateway.refund(@amount, response.authorization) + assert_success refund + assert_equal 'Succeeded', refund.message + end + + def test_failed_refund + response = @gateway.refund(nil, '') + assert_failure response + assert_equal 'See transcript for detailed error description.', response.message + end + + def test_successful_credit + response = @gateway.credit(@amount, @credit_card, @options) + assert_success response + assert_equal 'Succeeded', response.message + end + + def test_failed_credit + response = @gateway.credit(@amount, @invalid_card, @options) + assert_failure response + assert_equal 'Invalid card number', response.message + assert_equal Gateway::STANDARD_ERROR_CODE[:invalid_number], response.error_code + end + + def test_successful_verify + response = @gateway.verify(@credit_card, @options) + assert_success response + assert_match %r{Succeeded}, response.message + end + + def test_failed_verify + response = @gateway.verify(@declined_card, @options) + assert_failure response + assert_equal 'Decline transaction', response.message + assert_equal Gateway::STANDARD_ERROR_CODE[:card_declined], response.error_code + end + + def test_transcript_scrubbing + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @credit_card, @options) + end + transcript = @gateway.scrub(transcript) + + assert_scrubbed(@credit_card.number, transcript) + assert_scrubbed(@credit_card.verification_value.to_s, transcript) + assert_scrubbed(@gateway.options[:password], transcript) + end +end diff --git a/test/remote/gateways/remote_certo_direct_test.rb b/test/remote/gateways/remote_certo_direct_test.rb deleted file mode 100644 index 40df73ea669..00000000000 --- a/test/remote/gateways/remote_certo_direct_test.rb +++ /dev/null @@ -1,117 +0,0 @@ -require 'test_helper' - -class CertoDirectTest < Test::Unit::TestCase - def setup - Base.mode = :test - - @gateway = CertoDirectGateway.new(fixtures(:certo_direct)) - @amount = 100 - @credit_card = credit_card('4012888888881881', :month => 1) - @options = { - :billing_address => { - :address1 => 'Infinite Loop 1', - :country => 'US', - :state => 'TX', - :city => 'Gotham', - :zip => '23456', - :phone => '+1-132-12345678', - :first_name => 'John', - :last_name => 'Doe' - }, - :email => 'john.doe@example.com', - :currency => 'USD', - :ip => '127.0.0.1', - :description => 'Test Order of ActiveMerchant.' - } - end - - def test_successful_purchase - assert response = @gateway.purchase(@amount, @credit_card, @options) - assert_success response - assert response.test? - assert_equal 'Transaction was successfully processed', response.message - assert response.authorization - end - - def test_expired_credit_card - @credit_card.year = 2004 - assert response = @gateway.purchase(@amount, @credit_card, @options) - assert_failure response - assert response.test? - assert_equal 'invalid transaction data', response.message - end - - def test_bad_login - gateway = CertoDirectGateway.new(:login => 'X', :password => 'Y') - - assert response = gateway.purchase(@amount, @credit_card, @options) - - assert_equal Response, response.class - assert_match(/Authentication was failed/, response.message) - assert_equal false, response.success? - end - - def test_fail_purchase - @credit_card.month = 2 - - assert response = @gateway.purchase(@amount, @credit_card, @options) - assert_failure response - assert response.test? - assert_equal 'Transaction was declined', response.message - end - - def test_purchase_and_refund - # purchase - assert response = @gateway.purchase(@amount, @credit_card, @options) - assert_success response - - assert_equal 'Transaction was successfully processed', response.message - assert order_id = response.authorization - - # refund - assert response = @gateway.refund(@amount, order_id, :reason => 'Merchant request.') - assert_success response - assert_equal 'Transaction was successfully processed', response.message - assert response.authorization - end - - def test_authorization_and_capture - assert authorization = @gateway.authorize(@amount, @credit_card, @options) - assert_success authorization - - assert capture = @gateway.capture(@amount, authorization.authorization) - assert_success capture - assert_equal 'Transaction was successfully processed', capture.message - end - - def test_authorization_and_void - assert authorization = @gateway.authorize(@amount, @credit_card, @options) - assert_success authorization - - assert void = @gateway.void(@amount, authorization.authorization) - assert_success void - assert_equal 'Transaction was successfully processed', void.message - end - - def test_sale_and_recurring - assert sale = @gateway.purchase(@amount, @credit_card, @options) - assert_success sale - - assert recurring = @gateway.recurring(sale.authorization) - assert_success recurring - assert_equal 'Recurring Transaction was successfully processed', recurring.message - end - - def test_sale_and_recurring_overriding_details - assert sale = @gateway.purchase(@amount, @credit_card, @options) - assert_success sale - - assert recurring = @gateway.recurring(sale.authorization, - :amount => 99, - :currency => 'USD', - :shipping => 1) - - assert_success recurring - assert_equal 'Recurring Transaction was successfully processed', recurring.message - end -end diff --git a/test/remote/gateways/remote_checkout_test.rb b/test/remote/gateways/remote_checkout_test.rb new file mode 100644 index 00000000000..206442a70fa --- /dev/null +++ b/test/remote/gateways/remote_checkout_test.rb @@ -0,0 +1,130 @@ +require 'test_helper' + +class RemoteCheckoutTest < Test::Unit::TestCase + def setup + @gateway = ActiveMerchant::Billing::CheckoutGateway.new(fixtures(:checkout)) + @credit_card = credit_card( + '4543474002249996', + month: '06', + year: '2017', + verification_value: '956' + ) + @declined_card = credit_card( + '4543474002249996', + month: '06', + year: '2018', + verification_value: '958' + ) + @options = { + currency: 'CAD' + } + end + + def test_successful_purchase + response = @gateway.purchase(100, @credit_card, @options) + assert_success response + assert_equal 'Successful', response.message + end + + def test_successful_purchase_with_extra_options + response = @gateway.purchase(100, @credit_card, @options.merge( + currency: 'EUR', + email: 'bob@example.com', + order_id: generate_unique_id, + customer: generate_unique_id, + ip: '127.0.0.1' + )) + assert_success response + assert_equal 'Successful', response.message + end + + def test_successful_purchase_without_billing_address + response = @gateway.purchase(100, @credit_card, @options) + assert_success response + assert_equal 'Successful', response.message + end + + def test_successful_purchase_with_descriptors + response = @gateway.purchase(100, @credit_card, descriptor_name: 'TheName', descriptor_city: 'Wanaque') + assert_success response + assert_equal 'Successful', response.message + end + + def test_failed_purchase + response = @gateway.purchase(100, @declined_card, @options) + assert_failure response + assert_equal 'Not Successful', response.message + end + + def test_successful_authorize_and_capture + auth = @gateway.authorize(100, @credit_card, @options) + assert_success auth + + assert capture = @gateway.capture(100, auth.authorization, {currency: 'CAD'}) + assert_success capture + assert_equal 'Successful', capture.message + end + + # For backwards compatability with previous version where authorization was just transid. + def test_successful_authorize_and_capture_transid_only + auth = @gateway.authorize(100, @credit_card, @options) + assert_success auth + + capture = @gateway.capture(100, auth.authorization, @options) + assert_success capture + assert_equal 'Successful', capture.message + end + + def test_failed_authorize + response = @gateway.authorize(100, @declined_card, @options) + assert_failure response + assert_equal 'Not Successful', response.message + end + + def test_successful_void + auth = @gateway.authorize(100, @credit_card, @options) + assert_success auth + + assert void = @gateway.void(auth.authorization) + assert_success void + end + + def test_failed_void + auth = @gateway.authorize(100, @credit_card, @options) + assert_success auth + + assert void = @gateway.void('||||') + assert_failure void + end + + def test_successful_refund + assert response = @gateway.purchase(100, @credit_card, @options) + assert_success response + + assert refund = @gateway.refund(100, response.authorization, {currency: 'CAD'}) + assert_success refund + assert_equal 'Successful', refund.message + end + + def test_failed_refund + assert response = @gateway.purchase(100, @credit_card, @options) + assert_success response + + assert refund = @gateway.refund(100, '||||', {currency: 'CAD'}) + assert_failure refund + end + + def test_successful_verify + assert response = @gateway.verify(@credit_card, @options) + assert_success response + assert_equal 'Successful', response.message + + assert_success response.responses.last, 'The void should succeed' + assert_equal 'Successful', response.responses.last.params['result'] + end + + def test_failed_verify + assert response = @gateway.verify(@declined_card, @options) + assert_failure response + end +end diff --git a/test/remote/gateways/remote_checkout_v2_test.rb b/test/remote/gateways/remote_checkout_v2_test.rb new file mode 100644 index 00000000000..bec80aba153 --- /dev/null +++ b/test/remote/gateways/remote_checkout_v2_test.rb @@ -0,0 +1,248 @@ +require 'test_helper' + +class RemoteCheckoutV2Test < Test::Unit::TestCase + def setup + @gateway = CheckoutV2Gateway.new(fixtures(:checkout_v2)) + + @amount = 200 + @credit_card = credit_card('4242424242424242', verification_value: '100', month: '6', year: '2025') + @expired_card = credit_card('4242424242424242', verification_value: '100', month: '6', year: '2010') + @declined_card = credit_card('42424242424242424', verification_value: '234', month: '6', year: '2025') + + @options = { + order_id: '1', + billing_address: address, + description: 'Purchase', + email: 'longbob.longsen@example.com' + } + @additional_options = @options.merge( + card_on_file: true, + transaction_indicator: 2, + previous_charge_id: 'pay_123' + ) + @additional_options_3ds = @options.merge( + execute_threed: true, + three_d_secure: { + version: '1.0.2', + eci: '06', + cavv: 'AgAAAAAAAIR8CQrXcIhbQAAAAAA', + xid: 'MDAwMDAwMDAwMDAwMDAwMzIyNzY=' + } + ) + @additional_options_3ds2 = @options.merge( + execute_threed: true, + three_d_secure: { + version: '2.0.0', + eci: '06', + cavv: 'AgAAAAAAAIR8CQrXcIhbQAAAAAA', + ds_transaction_id: 'MDAwMDAwMDAwMDAwMDAwMzIyNzY=' + } + ) + end + + def test_transcript_scrubbing + declined_card = credit_card('4000300011112220', verification_value: '423') + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, declined_card, @options) + end + transcript = @gateway.scrub(transcript) + assert_scrubbed(declined_card.number, transcript) + assert_scrubbed(declined_card.verification_value, transcript) + assert_scrubbed(@gateway.options[:secret_key], transcript) + end + + def test_successful_purchase + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal 'Succeeded', response.message + end + + def test_successful_purchase_with_additional_options + response = @gateway.purchase(@amount, @credit_card, @additional_options) + assert_success response + assert_equal 'Succeeded', response.message + end + + def test_successful_purchase_with_moto_flag + response = @gateway.authorize(@amount, @credit_card, @options.merge(transaction_indicator: 3)) + + assert_success response + assert_equal 'Succeeded', response.message + end + + def test_successful_purchase_includes_avs_result + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal 'Succeeded', response.message + assert_equal 'S', response.avs_result['code'] + assert_equal 'U.S.-issuing bank does not support AVS.', response.avs_result['message'] + end + + def test_successful_authorize_includes_avs_result + response = @gateway.authorize(@amount, @credit_card, @options) + assert_success response + assert_equal 'Succeeded', response.message + assert_equal 'S', response.avs_result['code'] + assert_equal 'U.S.-issuing bank does not support AVS.', response.avs_result['message'] + end + + def test_successful_purchase_includes_cvv_result + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal 'Succeeded', response.message + assert_equal 'Y', response.cvv_result['code'] + end + + def test_successful_authorize_includes_cvv_result + response = @gateway.authorize(@amount, @credit_card, @options) + assert_success response + assert_equal 'Succeeded', response.message + assert_equal 'Y', response.cvv_result['code'] + end + + def test_successful_purchase_with_descriptors + options = @options.merge(descriptor_name: 'shop', descriptor_city: 'london') + response = @gateway.purchase(@amount, @credit_card, options) + assert_success response + assert_equal 'Succeeded', response.message + end + + def test_successful_purchase_with_minimal_options + response = @gateway.purchase(@amount, @credit_card, billing_address: address) + assert_success response + assert_equal 'Succeeded', response.message + end + + def test_successful_purchase_without_phone_number + response = @gateway.purchase(@amount, @credit_card, billing_address: address.update(phone: '')) + assert_success response + assert_equal 'Succeeded', response.message + end + + def test_successful_purchase_with_ip + response = @gateway.purchase(@amount, @credit_card, ip: '96.125.185.52') + assert_success response + assert_equal 'Succeeded', response.message + end + + def test_failed_purchase + response = @gateway.purchase(@amount, @declined_card, @options) + assert_failure response + assert_equal 'request_invalid: card_number_invalid', response.message + end + + def test_avs_failed_purchase + response = @gateway.purchase(@amount, @declined_card, billing_address: address.update(address1: 'Test_A')) + assert_failure response + assert_equal 'request_invalid: card_number_invalid', response.message + end + + def test_avs_failed_authorize + response = @gateway.authorize(@amount, @declined_card, billing_address: address.update(address1: 'Test_A')) + assert_failure response + assert_equal 'request_invalid: card_number_invalid', response.message + end + + def test_successful_authorize_and_capture + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + assert capture = @gateway.capture(nil, auth.authorization) + assert_success capture + end + + def test_successful_authorize_and_capture_with_additional_options + auth = @gateway.authorize(@amount, @credit_card, @additional_options) + assert_success auth + + assert capture = @gateway.capture(nil, auth.authorization) + assert_success capture + end + + def test_successful_authorize_and_capture_with_3ds + auth = @gateway.authorize(@amount, @credit_card, @additional_options_3ds) + assert_success auth + + assert capture = @gateway.capture(nil, auth.authorization) + assert_success capture + end + + def test_successful_authorize_and_capture_with_3ds2 + auth = @gateway.authorize(@amount, @credit_card, @additional_options_3ds2) + assert_success auth + + assert capture = @gateway.capture(nil, auth.authorization) + assert_success capture + end + + def test_failed_authorize + response = @gateway.authorize(@amount, @declined_card, @options) + assert_failure response + end + + def test_partial_capture + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + assert capture = @gateway.capture(@amount-1, auth.authorization) + assert_success capture + end + + def test_failed_capture + response = @gateway.capture(nil, '') + assert_failure response + end + + def test_successful_refund + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + + assert refund = @gateway.refund(@amount, purchase.authorization) + assert_success refund + end + + def test_partial_refund + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + + assert refund = @gateway.refund(@amount-1, purchase.authorization) + assert_success refund + end + + def test_failed_refund + response = @gateway.refund(nil, '') + assert_failure response + end + + def test_successful_void + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + assert void = @gateway.void(auth.authorization) + assert_success void + end + + def test_failed_void + response = @gateway.void('') + assert_failure response + end + + def test_successful_verify + response = @gateway.verify(@credit_card, @options) + assert_success response + assert_match %r{Succeeded}, response.message + end + + def test_failed_verify + response = @gateway.verify(@declined_card, @options) + assert_failure response + assert_match %r{request_invalid: card_number_invalid}, response.message + end + + def test_expired_card_returns_error_code + response = @gateway.purchase(@amount, @expired_card, @options) + assert_failure response + assert_equal 'request_invalid: card_expired', response.message + assert_equal 'request_invalid: card_expired', response.error_code + end +end diff --git a/test/remote/gateways/remote_citrus_pay_test.rb b/test/remote/gateways/remote_citrus_pay_test.rb new file mode 100644 index 00000000000..cf7d131b942 --- /dev/null +++ b/test/remote/gateways/remote_citrus_pay_test.rb @@ -0,0 +1,134 @@ +require 'test_helper' + +class RemoteCitrusPayTest < Test::Unit::TestCase + def setup + CitrusPayGateway.ssl_strict = false # Sandbox has an improperly installed cert + @gateway = CitrusPayGateway.new(fixtures(:citrus_pay)) + + @amount = 100 + @credit_card = credit_card('4987654321098769') + @declined_card = credit_card('5019994000124034') + @options = { + order_id: generate_unique_id, + billing_address: address, + description: 'Store Purchase' + } + end + + def teardown + CitrusPayGateway.ssl_strict = true + end + + def test_successful_purchase + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal 'Succeeded', response.message + end + + def test_successful_purchase_sans_options + assert response = @gateway.purchase(@amount, @credit_card) + assert_success response + assert_equal 'Succeeded', response.message + end + + def test_successful_purchase_with_more_options + more_options = @options.merge({ + ip: '127.0.0.1', + email: 'joe@example.com', + }) + + assert response = @gateway.purchase(@amount, @credit_card, @options.merge(more_options)) + assert_success response + assert_equal 'Succeeded', response.message + end + + def test_adds_3dsecure_id_to_authorize + more_options = @options.merge({ + ip: '127.0.0.1', + email: 'joe@example.com', + threed_secure_id: 'abc123' + }) + + assert response = @gateway.purchase(@amount, @credit_card, @options.merge(more_options)) + assert_match 'No check has been performed for this merchant and 3D Secure Id.', response.message + end + + def test_failed_purchase + assert response = @gateway.purchase(@amount, @declined_card, @options) + assert_failure response + assert_match %r{FAILURE}, response.message + end + + def test_successful_authorize_and_capture + assert response = @gateway.authorize(@amount, @credit_card, @options) + assert_success response + assert_equal 'Succeeded', response.message + assert_match %r(^.+\|\d+$), response.authorization + + assert capture = @gateway.capture(@amount, response.authorization) + assert_success capture + assert_equal 'Succeeded', capture.message + end + + def test_failed_authorize + assert response = @gateway.authorize(@amount, @declined_card, @options) + assert_failure response + assert_match(/FAILURE/, response.message) + end + + def test_successful_refund + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + + assert refund = @gateway.refund(@amount, response.authorization) + assert_success refund + assert_equal 'Succeeded', refund.message + end + + def test_successful_void + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + assert void = @gateway.void(auth.authorization) + assert_success void + end + + def test_successful_verify + assert response = @gateway.verify(@credit_card, @options) + assert_success response + assert_equal 'Succeeded', response.message + + assert_success response.responses.last, 'The void should succeed' + assert_equal 'SUCCESS', response.responses.last.params['result'] + end + + def test_invalid_login + gateway = CitrusPayGateway.new( + :userid => 'nosuch', + :password => 'thing' + ) + response = gateway.authorize(@amount, @credit_card, @options) + assert_failure response + assert_equal 'ERROR - INVALID_REQUEST - Invalid credentials.', response.message + end + + def test_transcript_scrubbing + card = credit_card('4987654321098769', verification_value: '134') + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, card, @options) + end + transcript = @gateway.scrub(transcript) + + assert_scrubbed(@credit_card.number, transcript) + assert_scrubbed(card.verification_value, transcript) + assert_scrubbed(@gateway.options[:password], transcript) + end + + def test_verify_credentials + assert @gateway.verify_credentials + + gateway = CitrusPayGateway.new(userid: 'unknown', password: 'unknown') + assert !gateway.verify_credentials + end + +end diff --git a/test/remote/gateways/remote_clearhaus_test.rb b/test/remote/gateways/remote_clearhaus_test.rb new file mode 100644 index 00000000000..9a716294dcb --- /dev/null +++ b/test/remote/gateways/remote_clearhaus_test.rb @@ -0,0 +1,209 @@ +require 'test_helper' + +class RemoteClearhausTest < Test::Unit::TestCase + def setup + @gateway = ClearhausGateway.new(fixtures(:clearhaus)) + + @amount = 100 + @credit_card = credit_card('4111111111111111') + @declined_card = credit_card('4200000000000000') + @options = {} + end + + def test_successful_purchase + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal 'Approved', response.message + end + + def test_successful_signing_request + gateway = ClearhausGateway.new(fixtures(:clearhaus_secure)) + + assert gateway.options[:private_key] + assert auth = gateway.authorize(@amount, @credit_card, @options) + assert_success auth + assert_equal 'Approved', auth.message + end + + def test_cleans_whitespace_from_private_key + credentials = fixtures(:clearhaus_secure) + credentials[:private_key] = " #{credentials[:private_key]} " + gateway = ClearhausGateway.new(credentials) + + assert gateway.options[:private_key] + assert auth = gateway.authorize(@amount, @credit_card, @options) + assert_success auth + assert_equal 'Approved', auth.message + end + + def test_unsuccessful_signing_request + credentials = fixtures(:clearhaus_secure) + credentials[:private_key] = 'foo' + gateway = ClearhausGateway.new(credentials) + + assert gateway.options[:private_key] + assert auth = gateway.authorize(@amount, @credit_card, @options) + assert_failure auth + assert_equal 'Neither PUB key nor PRIV key: not enough data', auth.message + + credentials = fixtures(:clearhaus_secure) + credentials[:signing_key] = 'foo' + gateway = ClearhausGateway.new(credentials) + + assert gateway.options[:signing_key] + assert auth = gateway.authorize(@amount, @credit_card, @options) + assert_failure auth + assert_equal 'invalid signing api-key', auth.message + end + + def test_successful_purchase_without_cvv + gateway = ClearhausGateway.new(fixtures(:clearhaus_secure)) + credit_card = credit_card('4111111111111111', verification_value: nil) + response = gateway.purchase(@amount, credit_card, @options) + + assert_success response + assert_equal 'Approved', response.message + end + + def test_successful_purchase_with_text_on_statement + options = { text_on_statement: 'hello' } + + response = @gateway.purchase(@amount, @credit_card, @options.merge(options)) + assert_success response + assert_equal response.params['text_on_statement'], 'hello' + assert_equal 'Approved', response.message + end + + def test_successful_purchase_with_more_options + options = { + order_id: '1', + ip: '127.0.0.1', + } + + response = @gateway.purchase(@amount, @credit_card, @options.merge(options)) + assert_success response + assert_equal 'Approved', response.message + end + + def test_failed_purchase + response = @gateway.purchase(@amount, @declined_card, @options) + assert_failure response + assert_equal 'Invalid card number', response.message + assert_equal 40110, response.error_code + end + + def test_successful_authorize_and_capture + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + assert_equal 'Approved', auth.message + + assert capture = @gateway.capture(@amount, auth.authorization) + assert_success capture + assert_equal 'Approved', capture.message + end + + def test_failed_authorize + response = @gateway.authorize(@amount, @credit_card, @options.merge(currency: 'ABC')) + assert_failure response + assert_equal 'invalid currency', response.message + assert_equal 40140, response.error_code + end + + def test_partial_capture + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + assert capture = @gateway.capture(@amount-1, auth.authorization) + assert_success capture + end + + def test_failed_capture + response = @gateway.capture(@amount, 'z') + assert_failure response + assert_equal 'invalid transaction id', response.message + end + + def test_successful_refund + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + + assert refund = @gateway.refund(@amount, purchase.authorization) + assert_success refund + assert_equal 'Approved', refund.message + end + + def test_partial_refund + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + + assert refund = @gateway.refund(@amount-1, purchase.authorization) + assert_success refund + end + + def test_failed_refund + response = @gateway.refund(@amount, '123') + assert_failure response + assert_equal 'invalid transaction id', response.message + end + + def test_successful_refund_of_capture + auth = @gateway.authorize(@amount, @credit_card, @options) + capture = @gateway.capture(@amount, auth.authorization) + assert_success capture + + assert refund = @gateway.refund(@amount, capture.authorization) + assert_success refund + assert_equal 'Approved', refund.message + end + + def test_successful_void + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + assert void = @gateway.void(auth.authorization) + assert_success void + assert_equal 'Approved', void.message + end + + def test_failed_void + response = @gateway.void('123') + assert_failure response + assert_equal 'invalid transaction id', response.message + end + + def test_successful_verify + response = @gateway.verify(@credit_card, @options) + assert_success response + assert_match 'Approved', response.message + end + + def test_failed_verify + response = @gateway.verify(@declined_card, @options) + assert_failure response + assert_match %r{Invalid card number}, response.message + end + + def test_successful_authorize_with_nonfractional_currency + assert response = @gateway.authorize(100, @credit_card, @options.merge(:currency => 'KRW')) + assert_equal 1, response.params['amount'] + assert_success response + end + + def test_invalid_login + gateway = ClearhausGateway.new(api_key: 'test') + + assert_raise ActiveMerchant::ResponseError do + gateway.purchase(@amount, @credit_card, @options) + end + end + + def test_transcript_scrubbing + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @credit_card, @options) + end + transcript = @gateway.scrub(transcript) + + assert_scrubbed(@credit_card.number, transcript) + assert_scrubbed(@credit_card.verification_value, transcript) + end +end diff --git a/test/remote/gateways/remote_commercegate_test.rb b/test/remote/gateways/remote_commercegate_test.rb new file mode 100644 index 00000000000..3ca37b555c2 --- /dev/null +++ b/test/remote/gateways/remote_commercegate_test.rb @@ -0,0 +1,91 @@ +require 'test_helper' + +class RemoteCommercegateTest < Test::Unit::TestCase + def setup + @gateway = CommercegateGateway.new(fixtures(:commercegate)) + + @amount = 1000 + + @options = { + address: address + } + + @credit_card = credit_card(fixtures(:commercegate)[:card_number]) + @expired_credit_card = credit_card(fixtures(:commercegate)[:card_number], year: Time.now.year-1) + end + + def test_successful_authorize + assert response = @gateway.authorize(@amount, @credit_card, @options) + assert_success response + assert_equal response.params['action'], 'AUTH' + assert_equal 'U', response.avs_result['code'] + assert_equal 'M', response.cvv_result['code'] + end + + def test_successful_authorize_without_options + assert response = @gateway.authorize(@amount, @credit_card) + assert_success response + assert_equal response.params['action'], 'AUTH' + assert_nil response.avs_result['code'] + assert_equal 'M', response.cvv_result['code'] + end + + def test_successful_purchase + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal response.params['action'], 'SALE' + assert_equal 'U', response.avs_result['code'] + assert_equal 'M', response.cvv_result['code'] + end + + def test_unsuccessful_purchase + assert response = @gateway.purchase(@amount, @expired_credit_card, @options) + assert_failure response + assert_not_nil response.message + end + + def test_authorize_and_capture + assert auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + assert_equal 'Success', auth.message + assert auth.authorization + assert capture = @gateway.capture(@amount, auth.authorization, @options) + assert_success capture + end + + def test_failed_capture + assert response = @gateway.capture(@amount, '123', @options) + assert_failure response + assert_equal 'Previous transaction not found', response.message + end + + def test_successful_refund + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert trans_id = response.params['transID'] + + assert response = @gateway.refund(@amount, trans_id, @options) + assert_success response + assert_equal response.params['action'], 'REFUND' + end + + def test_successful_void + assert response = @gateway.authorize(@amount, @credit_card, @options) + assert_success response + assert trans_id = response.params['transID'] + assert response = @gateway.void(trans_id) + assert_success response + assert_equal response.params['action'], 'VOID_AUTH' + end + + def test_invalid_login + gateway = CommercegateGateway.new( + login: '', + password: '', + site_id: '', + offer_id: '' + ) + assert response = gateway.purchase(@amount, @credit_card, @options) + assert_failure response + end +end diff --git a/test/remote/gateways/remote_conekta_test.rb b/test/remote/gateways/remote_conekta_test.rb new file mode 100644 index 00000000000..c466b8ad74c --- /dev/null +++ b/test/remote/gateways/remote_conekta_test.rb @@ -0,0 +1,197 @@ +require 'test_helper' + +class RemoteConektaTest < Test::Unit::TestCase + def setup + @gateway = ConektaGateway.new(fixtures(:conekta)) + + @amount = 300 + + @credit_card = ActiveMerchant::Billing::CreditCard.new( + number: '4242424242424242', + verification_value: '183', + month: '01', + year: '2019', + first_name: 'Mario F.', + last_name: 'Moreno Reyes' + ) + + @declined_card = ActiveMerchant::Billing::CreditCard.new( + number: '4000000000000002', + verification_value: '183', + month: '01', + year: '2019', + first_name: 'Mario F.', + last_name: 'Moreno Reyes' + ) + + @options = { + :device_fingerprint => '41l9l92hjco6cuekf0c7dq68v4', + description: 'Blue clip', + billing_address: { + address1: 'Rio Missisipi #123', + address2: 'Paris', + city: 'Guerrero', + country: 'Mexico', + zip: '5555', + name: 'Mario Reyes', + phone: '12345678', + }, + carrier: 'Estafeta', + email: 'bob@something.com', + line_items: [{ + name: 'Box of Cohiba S1s', + description: 'Imported From Mex.', + unit_price: 20000, + quantity: 1, + sku: '7500244909', + type: 'food' + }] + } + end + + def test_successful_purchase + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal nil, response.message + end + + def test_successful_purchase_with_installments + assert response = @gateway.purchase(@amount * 300, @credit_card, @options.merge({monthly_installments: 3})) + assert_success response + assert_equal nil, response.message + end + + def test_unsuccessful_purchase + assert response = @gateway.purchase(@amount, @declined_card, @options) + assert_failure response + end + + def test_successful_refund + assert response = @gateway.purchase(@amount, @credit_card, @options) + @options[:order_id] = response.params['id'] + assert_success response + assert_equal nil, response.message + + assert response = @gateway.refund(@amount, response.authorization, @options) + assert_success response + assert_equal nil, response.message + end + + def test_successful_void + assert response = @gateway.authorize(@amount, @credit_card, @options) + assert_success response + assert_equal nil, response.message + + identifier = response.params['id'] + + assert response = @gateway.void(identifier) + assert_success response + assert_equal nil, response.message + end + + def test_unsuccessful_void + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal nil, response.message + + identifier = response.params['id'] + + assert response = @gateway.void(identifier) + assert_failure response + assert_equal 'El cargo no existe o no es apto para esta operación.', response.message + end + + def test_unsuccessful_refund + assert response = @gateway.refund(@amount, '1', @options) + assert_failure response + assert_equal 'El recurso no ha sido encontrado.', response.message + end + + def test_successful_authorize + assert response = @gateway.authorize(@amount, @credit_card, @options) + assert_success response + assert_equal nil, response.message + end + + def test_unsuccessful_authorize + assert response = @gateway.authorize(@amount, @declined_card, @options) + assert_failure response + end + + def test_successful_capture + assert response = @gateway.authorize(@amount, @credit_card, @options) + assert_success response + assert_equal nil, response.message + + assert response = @gateway.capture(@amount, response.authorization, @options) + assert_success response + assert_equal nil, response.message + end + + def test_unsuccessful_capture + assert response = @gateway.capture(@amount, '1', @options) + assert_failure response + assert_equal 'El recurso no ha sido encontrado.', response.message + end + + def test_successful_purchase_passing_more_details + more_options = { + customer: 'TheCustomerName', + shipping_address: { + address1: '33 Main Street', + address2: 'Apartment 3', + city: 'Wanaque', + state: 'NJ', + country: 'USA', + zip: '01085', + }, + line_items: [ + { + name: 'Box of Cohiba S1s', + description: 'Imported From Mex.', + unit_price: 20000, + quantity: 1, + sku: 'cohb_s1', + type: 'other_human_consumption' + }, + { + name: 'Basic Toothpicks', + description: 'Wooden', + unit_price: 100, + quantity: 250, + sku: 'tooth_r3', + type: 'Extra pointy' + } + ] + } + assert response = @gateway.purchase(@amount, @credit_card, @options.merge(more_options)) + assert_success response + assert_equal 'Wanaque', response.params['details']['shipment']['address']['city'] + assert_equal 'Wooden', response.params['details']['line_items'][-1]['description'] + assert_equal 'TheCustomerName', response.params['details']['name'] + assert_equal 'Guerrero', response.params['details']['billing_address']['city'] + end + + def test_failed_purchase_with_no_details + assert response = @gateway.purchase(@amount, @credit_card, {}) + assert_failure response + assert_equal 'Falta el correo del comprador.', response.message + end + + def test_invalid_key + gateway = ConektaGateway.new(key: 'invalid_token') + assert response = gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_equal 'Acceso no autorizado.', response.message + end + + def test_transcript_scrubbing + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @credit_card, @options) + end + clean_transcript = @gateway.scrub(transcript) + + assert_scrubbed(@credit_card.number, clean_transcript) + assert_scrubbed(@credit_card.verification_value.to_s, clean_transcript) + end +end diff --git a/test/remote/gateways/remote_creditcall_test.rb b/test/remote/gateways/remote_creditcall_test.rb new file mode 100644 index 00000000000..527d60979a6 --- /dev/null +++ b/test/remote/gateways/remote_creditcall_test.rb @@ -0,0 +1,171 @@ +require 'test_helper' + +class RemoteCreditcallTest < Test::Unit::TestCase + def setup + @gateway = CreditcallGateway.new(fixtures(:creditcall)) + + @amount = 100 + @credit_card = credit_card('4000100011112224') + @declined_card = credit_card('4000300011112220') + @options = { + billing_address: address, + description: 'Store Purchase' + } + end + + def test_successful_purchase + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal 'Succeeded', response.message + end + + def test_successful_purchase_sans_options + response = @gateway.purchase(@amount, @credit_card) + assert_success response + assert_equal response.params['Zip'], 'notchecked' + assert_equal response.params['Address'], 'notchecked' + assert_equal 'Succeeded', response.message + end + + def test_successful_purchase_with_more_options + options = { + order_id: '1', + ip: '127.0.0.1', + email: 'joe@example.com', + manual_type: 'cnp' + } + + response = @gateway.purchase(@amount, @credit_card, options) + assert_success response + assert_equal 'Succeeded', response.message + end + + def test_failed_purchase + @amount = 1001 + response = @gateway.purchase(@amount, @declined_card, @options) + assert_failure response + assert_equal 'ExpiredCard', response.message + end + + def test_successful_authorize + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + assert_equal 'Succeeded', auth.message + end + + def test_successful_authorize_with_zip_verification + response = @gateway.authorize(@amount, @credit_card, @options.merge(verify_zip: 'true')) + assert_success response + assert_equal response.params['Zip'], 'matched' + assert_equal response.params['Address'], 'notchecked' + assert_equal 'Succeeded', response.message + end + + def test_successful_authorize_with_address_verification + response = @gateway.authorize(@amount, @credit_card, @options.merge(verify_address: 'true')) + assert_success response + assert_equal response.params['Zip'], 'notchecked' + assert_equal response.params['Address'], 'matched' + assert_equal 'Succeeded', response.message + end + + def test_successful_authorize_and_capture + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + assert capture = @gateway.capture(@amount, auth.authorization) + assert_success capture + assert_equal 'Succeeded', capture.message + end + + def test_failed_authorize + @amount = 1001 + response = @gateway.authorize(@amount, @declined_card, @options) + assert_failure response + assert_equal 'ExpiredCard', response.message + end + + def test_partial_capture + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + assert capture = @gateway.capture(@amount-1, auth.authorization) + assert_success capture + end + + def test_failed_capture + response = @gateway.capture(@amount, '') + assert_failure response + assert_equal 'CardEaseReferenceInvalid', response.message + end + + def test_successful_refund + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + + assert refund = @gateway.refund(@amount, purchase.authorization) + assert_success refund + assert_equal 'Succeeded', refund.message + end + + def test_partial_refund + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + + assert refund = @gateway.refund(@amount-1, purchase.authorization) + assert_success refund + end + + def test_failed_refund + assert refund = @gateway.refund(@amount, '') + assert_failure refund + assert_equal 'CardEaseReferenceInvalid', refund.message + end + + def test_successful_void + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + assert void = @gateway.void(auth.authorization) + assert_success void + assert_equal 'Succeeded', void.message + end + + def test_failed_void + response = @gateway.void('') + assert_failure response + assert_equal 'CardEaseReferenceInvalid', response.message + end + + def test_successful_verify + response = @gateway.verify(@credit_card, @options) + assert_success response + assert_match %r{Succeeded}, response.message + end + + def test_failed_verify + @declined_card.number = '' + response = @gateway.verify(@declined_card, @options) + assert_failure response + assert_match %r{PAN Must be >= 13 Digits}, response.message + end + + def test_invalid_login + gateway = CreditcallGateway.new(terminal_id: '', transaction_key: '') + + response = gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_match %r{Invalid TerminalID - Must be 8 digit number}, response.message + end + + def test_transcript_scrubbing + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @credit_card, @options) + end + transcript = @gateway.scrub(transcript) + + assert_scrubbed(@credit_card.number, transcript) + assert_scrubbed(@credit_card.verification_value, transcript) + assert_scrubbed(@gateway.options[:transaction_key], transcript) + end +end diff --git a/test/remote/gateways/remote_credorax_test.rb b/test/remote/gateways/remote_credorax_test.rb new file mode 100644 index 00000000000..f826f157987 --- /dev/null +++ b/test/remote/gateways/remote_credorax_test.rb @@ -0,0 +1,587 @@ +require 'test_helper' + +class RemoteCredoraxTest < Test::Unit::TestCase + def setup + @gateway = CredoraxGateway.new(fixtures(:credorax)) + + @amount = 100 + @credit_card = credit_card('4176661000001015', verification_value: '281', month: '12', year: '2022') + @fully_auth_card = credit_card('5223450000000007', brand: 'mastercard', verification_value: '090', month: '12', year: '2025') + @declined_card = credit_card('4176661000001111', verification_value: '681', month: '12', year: '2022') + @options = { + order_id: '1', + currency: 'EUR', + billing_address: address, + description: 'Store Purchase' + } + end + + def test_invalid_login + gateway = CredoraxGateway.new(merchant_id: '', cipher_key: '') + response = gateway.purchase(@amount, @credit_card, @options) + assert_failure response + end + + def test_successful_purchase + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal '1', response.params['H9'] + assert_equal 'Succeeded', response.message + end + + def test_successful_purchase_with_extra_options + response = @gateway.purchase(@amount, @credit_card, @options.merge(transaction_type: '10')) + assert_success response + assert_equal '1', response.params['H9'] + assert_equal 'Succeeded', response.message + end + + def test_successful_purchase_with_auth_data_via_3ds1_fields + options = @options.merge( + eci: '02', + cavv: 'jJ81HADVRtXfCBATEp01CJUAAAA=', + xid: '00000000000000000501' + ) + + response = @gateway.purchase(@amount, @fully_auth_card, options) + assert_success response + assert_equal '1', response.params['H9'] + assert_equal 'Succeeded', response.message + end + + def test_successful_purchase_with_auth_data_via_normalized_3ds2_options + version = '2.0' + eci = '02' + cavv = 'jJ81HADVRtXfCBATEp01CJUAAAA=' + ds_transaction_id = '97267598-FAE6-48F2-8083-C23433990FBC' + options = @options.merge( + three_d_secure: { + version: version, + eci: eci, + cavv: cavv, + ds_transaction_id: ds_transaction_id + } + ) + + response = @gateway.purchase(@amount, @fully_auth_card, options) + assert_success response + assert_equal '1', response.params['H9'] + assert_equal 'Succeeded', response.message + end + + def test_failed_purchase + response = @gateway.purchase(@amount, @declined_card, @options) + assert_failure response + assert_equal 'Transaction not allowed for cardholder', response.message + end + + def test_failed_purchase_invalid_auth_data_via_3ds1_fields + options = @options.merge( + eci: '02', + cavv: 'jJ81HADVRtXfCBATEp01CJUAAAA=', + xid: 'this is not a valid xid, it will be rejected' + ) + + response = @gateway.purchase(@amount, @fully_auth_card, options) + assert_failure response + assert_equal '-9', response.params['Z2'] + assert_match 'Parameter i8 is invalid', response.message + end + + def test_failed_purchase_invalid_auth_data_via_normalized_3ds2_options + version = '2.0' + eci = '02' + cavv = 'BOGUS' + ds_transaction_id = '97267598-FAE6-48F2-8083-C23433990FBC' + options = @options.merge( + three_d_secure: { + version: version, + eci: eci, + cavv: cavv, + ds_transaction_id: ds_transaction_id + } + ) + + response = @gateway.purchase(@amount, @fully_auth_card, options) + assert_failure response + assert_equal '-9', response.params['Z2'] + assert_match 'malformed', response.message + end + + def test_successful_authorize_and_capture + response = @gateway.authorize(@amount, @credit_card, @options) + assert_success response + assert_equal 'Succeeded', response.message + assert response.authorization + + capture = @gateway.capture(@amount, response.authorization) + assert_success capture + assert_equal 'Succeeded', capture.message + end + + def test_successful_authorize_with_auth_data_via_3ds1_fields + options = @options.merge( + eci: '02', + cavv: 'jJ81HADVRtXfCBATEp01CJUAAAA=', + xid: '00000000000000000501' + ) + + response = @gateway.authorize(@amount, @fully_auth_card, options) + assert_success response + assert_equal 'Succeeded', response.message + assert response.authorization + end + + def test_successful_authorize_with_auth_data_via_normalized_3ds2_options + version = '2.0' + eci = '02' + cavv = 'jJ81HADVRtXfCBATEp01CJUAAAA=' + ds_transaction_id = '97267598-FAE6-48F2-8083-C23433990FBC' + options = @options.merge( + three_d_secure: { + version: version, + eci: eci, + cavv: cavv, + ds_transaction_id: ds_transaction_id + } + ) + + response = @gateway.authorize(@amount, @fully_auth_card, options) + assert_success response + assert_equal 'Succeeded', response.message + assert response.authorization + end + + def test_failed_authorize + response = @gateway.authorize(@amount, @declined_card, @options) + assert_failure response + assert_equal 'Transaction not allowed for cardholder', response.message + end + + def test_failed_capture + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + capture = @gateway.capture(0, auth.authorization) + assert_failure capture + assert_equal 'Invalid amount', capture.message + end + + def test_successful_purchase_and_void + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + + void = @gateway.void(response.authorization) + assert_success void + assert_equal 'Succeeded', void.message + end + + def test_successful_authorize_and_void + response = @gateway.authorize(@amount, @credit_card, @options) + assert_success response + + void = @gateway.void(response.authorization) + assert_success void + assert_equal 'Succeeded', void.message + end + + def test_successful_capture_and_void + response = @gateway.authorize(@amount, @credit_card, @options) + assert_success response + assert_equal 'Succeeded', response.message + assert response.authorization + + capture = @gateway.capture(@amount, response.authorization) + assert_success capture + assert_equal 'Succeeded', capture.message + + void = @gateway.void(capture.authorization) + assert_success void + assert_equal 'Succeeded', void.message + end + + def test_failed_void + response = @gateway.void('') + assert_failure response + assert_equal 'Referred to transaction has not been found.', response.message + end + + def test_successful_refund + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + + refund = @gateway.refund(@amount, response.authorization) + assert_success refund + assert_equal 'Succeeded', refund.message + end + + def test_successful_refund_and_void + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + + refund = @gateway.refund(@amount, response.authorization) + assert_success refund + assert_equal 'Succeeded', refund.message + + void = @gateway.void(refund.authorization) + assert_success void + assert_equal 'Succeeded', void.message + end + + def test_failed_refund + response = @gateway.refund(nil, '123;123;123') + assert_failure response + assert_equal 'Referred to transaction has not been found.', response.message + end + + def test_successful_credit + response = @gateway.credit(@amount, @credit_card, @options) + assert_success response + assert_equal 'Succeeded', response.message + end + + def test_failed_credit + response = @gateway.credit(0, @declined_card, @options) + assert_failure response + assert_equal 'Invalid amount', response.message + end + + def test_successful_verify + response = @gateway.verify(@credit_card, @options) + assert_success response + assert_equal 'Succeeded', response.message + end + + def test_failed_verify + response = @gateway.verify(@declined_card, @options) + assert_failure response + assert_equal 'Transaction not allowed for cardholder', response.message + end + + def test_transcript_scrubbing + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @credit_card, @options) + end + clean_transcript = @gateway.scrub(transcript) + + assert_scrubbed(@credit_card.number, clean_transcript) + assert_cvv_scrubbed(clean_transcript) + end + + # ######################################################################### + # # CERTIFICATION SPECIFIC REMOTE TESTS + # ######################################################################### + # + # # Send [a5] currency code parameter as "AFN" + # def test_certification_error_unregistered_currency + # @options[:echo] = "33BE888" + # @options[:currency] = "AFN" + # response = @gateway.purchase(@amount, @credit_card, @options) + # assert_failure response + # end + # + # # Send [b2] parameter as "6" + # def test_certification_error_unregistered_card + # @options[:echo] = "33BE889" + # response = @gateway.purchase(@amount, @credit_card, @options) + # assert_failure response + # end + # + # # In the future, merchant expected to investigate each such case offline. + # def test_certification_error_no_response_from_the_gate + # @options[:echo] = "33BE88A" + # response = @gateway.purchase(@amount, @credit_card, @options) + # assert_failure response + # end + # + # # Merchant is expected to verify if the code is "0" - in this case the + # # transaction should be considered approved. In all other cases the + # # offline investigation should take place. + # def test_certification_error_unknown_result_code + # @options[:echo] = "33BE88B" + # response = @gateway.purchase(@amount, @credit_card, @options) + # assert_failure response + # end + # + # # Merchant is expected to verify if the code is "00" - in this case the + # # transaction should be considered approved. In all other cases the + # # transaction is declined. The exact reason should be investigated offline. + # def test_certification_error_unknown_response_reason_code + # @options[:echo] = "33BE88C" + # @options[:email] = "brucewayne@dccomics.com" + # @options[:billing_address] = { + # address1: "5050 Gotham Drive", + # city: "Toronto", + # zip: "B2M 1Y9", + # state: "ON", + # country: "CA", + # phone: "(0800)228626" + # } + # + # credit_card = credit_card('4176661000001015', + # brand: "visa", + # verification_value: "281", + # month: "12", + # year: "17", + # first_name: "Bruce", + # last_name: "Wayne") + # + # response = @gateway.purchase(@amount, credit_card, @options) + # assert_failure response + # end + # + # # All fields marked as mandatory are expected to be populated with the + # # above default values. Mandatory fields with no values on the + # # certification template should be populated with your own meaningful + # # values and comply with our API specifications. The d2 parameter is + # # mandatory during certification only to allow for tracking of tests. + # # Expected result of this test: Time out + # def test_certification_time_out + # @options[:echo] = "33BE88D" + # @options[:email] = "brucewayne@dccomics.com" + # @options[:billing_address] = { + # address1: "5050 Gotham Drive", + # city: "Toronto", + # zip: "B2M 1Y9", + # state: "ON", + # country: "CA", + # phone: "(0800)228626" + # } + # + # credit_card = credit_card('5473470000000010', + # brand: "master", + # verification_value: "939", + # month: "12", + # year: "17", + # first_name: "Bruce", + # last_name: "Wayne") + # + # response = @gateway.purchase(@amount, credit_card, @options) + # assert_failure response + # end + # + # # All fields marked as mandatory are expected to be populated + # # with the above default values. Mandatory fields with no values + # # on the certification template should be populated with your + # # own meaningful values and comply with our API specifications. + # # The d2 parameter is mandatory during certification only to + # # allow for tracking of tests. + # def test_certification_za_zb_zc + # @options[:echo] = "33BE88E" + # @options[:email] = "brucewayne@dccomics.com" + # @options[:billing_address] = { + # address1: "5050 Gotham Drive", + # city: "Toronto", + # zip: "B2M 1Y9", + # state: "ON", + # country: "CA", + # phone: "(0800)228626" + # } + # + # credit_card = credit_card('5473470000000010', + # verification_value: "939", + # month: "12", + # year: "17", + # first_name: "Bruce", + # last_name: "Wayne") + # + # purchase = @gateway.purchase(@amount, credit_card, @options) + # assert_success purchase + # assert_equal "Succeeded", purchase.message + # + # refund_options = {echo: "33BE892"} + # refund = @gateway.refund(@amount, purchase.authorization, refund_options) + # assert_success refund + # assert_equal "Succeeded", refund.message + # + # void_options = {echo: "33BE895"} + # void = @gateway.void(refund.authorization, void_options) + # assert_success void + # assert_equal "Succeeded", refund.message + # end + # + # # All fields marked as mandatory are expected to be populated + # # with the above default values. Mandatory fields with no values + # # on the certification template should be populated with your + # # own meaningful values and comply with our API specifications. + # # The d2 parameter is mandatory during certification only to + # # allow for tracking of tests. + # def test_certification_zg_zh + # @options[:echo] = "33BE88F" + # @options[:email] = "clark.kent@dccomics.com" + # @options[:billing_address] = { + # address1: "2020 Krypton Drive", + # city: "Toronto", + # zip: "S2M 1YR", + # state: "ON", + # country: "CA", + # phone: "(0800) 78737626" + # } + # + # credit_card = credit_card('4176661000001015', + # brand: "visa", + # verification_value: "281", + # month: "12", + # year: "17", + # first_name: "Clark", + # last_name: "Kent") + # + # response = @gateway.authorize(@amount, credit_card, @options) + # assert_success response + # assert_equal "Succeeded", response.message + # + # capture_options = {echo: "33BE890"} + # capture = @gateway.capture(@amount, response.authorization, capture_options) + # assert_success capture + # assert_equal "Succeeded", capture.message + # end + # + # # All fields marked as mandatory are expected to be populated + # # with the above default values. Mandatory fields with no values + # # on the certification template should be populated with your + # # own meaningful values and comply with our API specifications. + # # The d2 parameter is mandatory during certification only to + # # allow for tracking of tests. + # def test_certification_zg_zj + # @options[:echo] = "33BE88F" + # @options[:email] = "clark.kent@dccomics.com" + # @options[:billing_address] = { + # address1: "2020 Krypton Drive", + # city: "Toronto", + # zip: "S2M 1YR", + # state: "ON", + # country: "CA", + # phone: "(0800) 78737626" + # } + # + # credit_card = credit_card('4176661000001015', + # brand: "visa", + # verification_value: "281", + # month: "12", + # year: "17", + # first_name: "Clark", + # last_name: "Kent") + # + # response = @gateway.authorize(@amount, credit_card, @options) + # assert_success response + # assert_equal "Succeeded", response.message + # + # auth_void_options = {echo: "33BE891"} + # auth_void = @gateway.void(response.authorization, auth_void_options) + # assert_success auth_void + # assert_equal "Succeeded", auth_void.message + # end + # + # # All fields marked as mandatory are expected to be populated + # # with the above default values. Mandatory fields with no values + # # on the certification template should be populated with your + # # own meaningful values and comply with our API specifications. + # # The d2 parameter is mandatory during certification only to + # # allow for tracking of tests. + # # + # # Certification for independent credit (credit) + # def test_certification_zd + # @options[:echo] = "33BE893" + # @options[:email] = "wadewilson@marvel.com" + # @options[:billing_address] = { + # address1: "5050 Deadpool Drive", + # city: "Toronto", + # zip: "D2P 1Y9", + # state: "ON", + # country: "CA", + # phone: "+1(555)123-4567" + # } + # + # credit_card = credit_card('4176661000001015', + # brand: "visa", + # verification_value: "281", + # month: "12", + # year: "17", + # first_name: "Wade", + # last_name: "Wilson") + # + # response = @gateway.credit(@amount, credit_card, @options) + # assert_success response + # assert_equal "Succeeded", response.message + # end + # + # # Use the above values to fill the mandatory parameters in your + # # certification test transactions. Note:The d2 parameter is only + # # mandatory during certification to allow for tracking of tests. + # # + # # Certification for purchase void + # def test_certification_zf + # @options[:echo] = "33BE88E" + # @options[:email] = "brucewayne@dccomics.com" + # @options[:billing_address] = { + # address1: "5050 Gotham Drive", + # city: "Toronto", + # zip: "B2M 1Y9", + # state: "ON", + # country: "CA", + # phone: "(0800)228626" + # } + # + # credit_card = credit_card('5473470000000010', + # verification_value: "939", + # month: "12", + # year: "17", + # first_name: "Bruce", + # last_name: "Wayne") + # + # response = @gateway.purchase(@amount, credit_card, @options) + # assert_success response + # assert_equal "Succeeded", response.message + # + # void_options = {echo: "33BE894"} + # void = @gateway.void(response.authorization, void_options) + # assert_success void + # assert_equal "Succeeded", void.message + # end + # + # # Use the above values to fill the mandatory parameters in your + # # certification test transactions. Note:The d2 parameter is only + # # mandatory during certification to allow for tracking of tests. + # # + # # Certification for capture void + # def test_certification_zi + # @options[:echo] = "33BE88F" + # @options[:email] = "clark.kent@dccomics.com" + # @options[:billing_address] = { + # address1: "2020 Krypton Drive", + # city: "Toronto", + # zip: "S2M 1YR", + # state: "ON", + # country: "CA", + # phone: "(0800) 78737626" + # } + # + # credit_card = credit_card('4176661000001015', + # brand: "visa", + # verification_value: "281", + # month: "12", + # year: "17", + # first_name: "Clark", + # last_name: "Kent") + # + # authorize = @gateway.authorize(@amount, credit_card, @options) + # assert_success authorize + # assert_equal "Succeeded", authorize.message + # + # capture_options = {echo: "33BE890"} + # capture = @gateway.capture(@amount, authorize.authorization, capture_options) + # assert_success capture + # assert_equal "Succeeded", capture.message + # + # void_options = {echo: "33BE896"} + # void = @gateway.void(capture.authorization, void_options) + # assert_success void + # assert_equal "Succeeded", void.message + # end + + private + + def assert_cvv_scrubbed(transcript) + assert_match(/b5=\[FILTERED\]/, transcript) + end +end diff --git a/test/remote/gateways/remote_ct_payment_certification_test.rb b/test/remote/gateways/remote_ct_payment_certification_test.rb new file mode 100644 index 00000000000..7a4ef3988f4 --- /dev/null +++ b/test/remote/gateways/remote_ct_payment_certification_test.rb @@ -0,0 +1,243 @@ +require 'test_helper' + +class RemoteCtPaymentCertificationTest < Test::Unit::TestCase + def setup + @gateway = CtPaymentGateway.new(fixtures(:ct_payment)) + + @amount = 100 + @declined_card = credit_card('4502244713161718') + @options = { + billing_address: address, + description: 'Store Purchase', + merchant_terminal_number: ' ', + order_id: generate_unique_id[0, 11] + } + end + + def test1 + @credit_card = credit_card('4501161107217214', month: '07', year: 2025) + response = @gateway.purchase(@amount, @credit_card, @options) + print_result(1, response) + end + + def test2 + @credit_card = credit_card('5194419000000007', month: '07', year: 2025) + @credit_card.brand = 'master' + response = @gateway.purchase(@amount, @credit_card, @options) + print_result(2, response) + end + + def test3 + @credit_card = credit_card('341400000000000', month: '07', year: 2025, verification_value: '1234') + @credit_card.brand = 'american_express' + response = @gateway.purchase(@amount, @credit_card, @options) + print_result(3, response) + end + + def test6 + @credit_card = credit_card('341400000000000', month: '07', year: 2025, verification_value: '1234') + @credit_card.brand = 'american_express' + response = @gateway.credit(@amount, @credit_card, @options) + print_result(6, response) + end + + def test4 + @credit_card = credit_card('4501161107217214', month: '07', year: 2025) + response = @gateway.credit(@amount, @credit_card, @options) + print_result(4, response) + end + + def test5 + @credit_card = credit_card('5194419000000007', month: '07', year: 2025) + @credit_card.brand = 'master' + response = @gateway.credit(@amount, @credit_card, @options) + print_result(5, response) + end + + def test7 + @credit_card = credit_card('4501161107217214', month: '07', year: 2025) + response = @gateway.authorize(@amount, @credit_card, @options) + print_result(7, response) + + capture_response = @gateway.capture(@amount, response.authorization, @options.merge(order_id: generate_unique_id[0, 11])) + print_result(10, capture_response) + end + + def test8 + @credit_card = credit_card('5194419000000007', month: '07', year: 2025) + @credit_card.brand = 'master' + response = @gateway.authorize(@amount, @credit_card, @options) + print_result(8, response) + + capture_response = @gateway.capture(@amount, response.authorization, @options.merge(order_id: generate_unique_id[0, 11])) + print_result(11, capture_response) + end + + def test9 + @credit_card = credit_card('341400000000000', month: '07', year: 2025, verification_value: '1234') + @credit_card.brand = 'american_express' + response = @gateway.authorize(@amount, @credit_card, @options.merge(order_id: generate_unique_id[0, 11])) + print_result(9, response) + + capture_response = @gateway.capture(@amount, response.authorization, @options) + print_result(12, capture_response) + end + + def test13 + @credit_card = credit_card('4501161107217214', month: '07', year: 2025) + response = @gateway.purchase('000', @credit_card, @options) + print_result(13, response) + end + + def test14 + @credit_card = credit_card('4501161107217214', month: '07', year: 2025) + response = @gateway.purchase(-100, @credit_card, @options) + print_result(14, response) + end + + def test15 + @credit_card = credit_card('4501161107217214', month: '07', year: 2025) + response = @gateway.purchase('-1A0', @credit_card, @options) + print_result(15, response) + end + + def test16 + @credit_card = credit_card('5194419000000007', month: '07', year: 2025) + @credit_card.brand = 'visa' + response = @gateway.purchase(@amount, @credit_card, @options) + print_result(16, response) + end + + def test17 + @credit_card = credit_card('4501161107217214', month: '07', year: 2025) + @credit_card.brand = 'master' + response = @gateway.purchase(@amount, @credit_card, @options) + print_result(17, response) + end + + def test18 + @credit_card = credit_card('', month: '07', year: 2025) + response = @gateway.purchase(@amount, @credit_card, @options) + print_result(18, response) + end + + def test19 + @credit_card = credit_card('4501123412341234', month: '07', year: 2025) + response = @gateway.purchase(@amount, @credit_card, @options) + print_result(19, response) + end + + def test20 + # requires editing the model to run with a 3 digit expiration date + @credit_card = credit_card('4501161107217214', month: '07', year: 2) + response = @gateway.purchase(@amount, @credit_card, @options) + print_result(20, response) + end + + def test21 + @credit_card = credit_card('4501161107217214', month: 17, year: 2017) + response = @gateway.purchase(@amount, @credit_card, @options) + print_result(21, response) + end + + def test22 + @credit_card = credit_card('4501161107217214', month: '01', year: 2016) + response = @gateway.purchase(@amount, @credit_card, @options) + print_result(22, response) + end + + def test24 + @credit_card = credit_card('4502244713161718', month: '07', year: 2025) + response = @gateway.purchase(@amount, @credit_card, @options) + print_result(24, response) + end + + def test25 + # Needs an edit to the Model to run + @credit_card = credit_card('4501161107217214', month: '07', year: 2025) + response = @gateway.purchase(@amount, @credit_card, @options) + print_result(25, response) + end + + def test26 + @credit_card = credit_card('4501161107217214', month: '07', year: 2025) + response = @gateway.credit('000', @credit_card, @options) + print_result(26, response) + end + + def test27 + @credit_card = credit_card('4501161107217214', month: '07', year: 2025) + response = @gateway.credit(-100, @credit_card, @options) + print_result(27, response) + end + + def test28 + @credit_card = credit_card('4501161107217214', month: '07', year: 2025) + response = @gateway.credit('-1A0', @credit_card, @options) + print_result(28, response) + end + + def test29 + @credit_card = credit_card('5194419000000007', month: '07', year: 2025) + @credit_card.brand = 'visa' + response = @gateway.credit(@amount, @credit_card, @options) + print_result(29, response) + end + + def test30 + @credit_card = credit_card('4501161107217214', month: '07', year: 2025) + @credit_card.brand = 'master' + response = @gateway.credit(@amount, @credit_card, @options) + print_result(30, response) + end + + def test31 + @credit_card = credit_card('', month: '07', year: 2025) + response = @gateway.purchase(@amount, @credit_card, @options) + print_result(31, response) + end + + def test32 + @credit_card = credit_card('4501123412341234', month: '07', year: 2025) + response = @gateway.credit(@amount, @credit_card, @options) + print_result(32, response) + end + + def test33 + # requires edit to model to make 3 digit expiration date + @credit_card = credit_card('4501161107217214', month: '07', year: 2) + response = @gateway.credit(@amount, @credit_card, @options) + print_result(33, response) + end + + def test34 + @credit_card = credit_card('4501161107217214', month: 17, year: 2017) + response = @gateway.credit(@amount, @credit_card, @options) + print_result(34, response) + end + + def test35 + @credit_card = credit_card('4501161107217214', month: '01', year: 2016) + response = @gateway.credit(@amount, @credit_card, @options) + print_result(35, response) + end + + def test37 + @credit_card = credit_card('4502244713161718', month: '07', year: 2025) + response = @gateway.purchase(@amount, @credit_card, @options) + print_result(37, response) + end + + def test38 + # Needs an edit to the Model to run + @credit_card = credit_card('4501161107217214', month: '07', year: 2025) + response = @gateway.purchase(@amount, @credit_card, @options) + print_result(38, response) + end + + def print_result(test_number, response) + puts "Test #{test_number} | transaction number: #{response.params['transactionNumber']}, invoice number #{response.params['invoiceNumber']}, timestamp: #{response.params['timeStamp']}, result: #{response.params['returnCode']}" + puts response.inspect + end + +end diff --git a/test/remote/gateways/remote_ct_payment_test.rb b/test/remote/gateways/remote_ct_payment_test.rb new file mode 100644 index 00000000000..4fd0ba8738a --- /dev/null +++ b/test/remote/gateways/remote_ct_payment_test.rb @@ -0,0 +1,173 @@ +require 'test_helper' + +class RemoteCtPaymentTest < Test::Unit::TestCase + def setup + @gateway = CtPaymentGateway.new(fixtures(:ct_payment)) + + @amount = 100 + @credit_card = credit_card('4501161107217214', month: '07', year: 2020) + @declined_card = credit_card('4502244713161718') + @options = { + billing_address: address, + description: 'Store Purchase', + order_id: generate_unique_id[0, 11], + email: 'bigbird@sesamestreet.com' + + } + end + + def test_successful_purchase + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal 'APPROVED', response.message + end + + def test_failed_purchase + response = @gateway.purchase(@amount, @declined_card, @options) + assert_failure response + assert_equal 'Transaction declined', response.message + end + + def test_successful_authorize_and_capture + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + assert capture = @gateway.capture(@amount, auth.authorization, @options.merge(order_id: generate_unique_id[0, 11])) + assert_success capture + assert_equal 'APPROVED', capture.message + end + + def test_failed_authorize + response = @gateway.authorize(@amount, @declined_card, @options) + assert_failure response + assert_equal 'Transaction declined', response.message + end + + def test_partial_capture + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + assert capture = @gateway.capture(@amount-1, auth.authorization, @options.merge(order_id: generate_unique_id[0, 11])) + assert_success capture + end + + def test_failed_capture + response = @gateway.capture(@amount, '0123456789asd;0123456789asdf;12345678', @options) + assert_failure response + assert_equal 'The original transaction number does not match any actual transaction', response.message + end + + def test_successful_refund + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + + assert refund = @gateway.refund(@amount, purchase.authorization, @options.merge(order_id: generate_unique_id[0, 11])) + assert_success refund + assert_equal 'APPROVED', refund.message + end + + def test_partial_refund + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + + assert refund = @gateway.refund(@amount-1, purchase.authorization, @options.merge(order_id: generate_unique_id[0, 11])) + assert_success refund + end + + def test_failed_refund + response = @gateway.refund(@amount, '0123456789asd;0123456789asdf;12345678', @options.merge(order_id: generate_unique_id[0, 11])) + assert_failure response + assert_equal 'The original transaction number does not match any actual transaction', response.message + end + + def test_successful_void + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + + assert void = @gateway.void(purchase.authorization) + assert_success void + assert_equal 'APPROVED', void.message + end + + def test_failed_void + response = @gateway.void('0123456789asd;0123456789asdf;12345678') + assert_failure response + assert_equal 'The original transaction number does not match any actual transaction', response.message + end + + def test_successful_credit + assert response = @gateway.credit(@amount, @credit_card, @options) + assert_success response + assert_equal 'APPROVED', response.message + end + + def test_successful_store + assert response = @gateway.store(@credit_card, @options) + + assert_success response + assert !response.authorization.split(';')[3].nil? + end + + def test_successful_purchase_using_stored_card + assert store_response = @gateway.store(@credit_card, @options) + assert_success store_response + + response = @gateway.purchase(@amount, store_response.authorization, @options) + assert_success response + assert_equal 'APPROVED', response.message + end + + def test_successful_authorize_using_stored_card + assert store_response = @gateway.store(@credit_card, @options) + assert_success store_response + + response = @gateway.authorize(@amount, store_response.authorization, @options) + assert_success response + assert_equal 'APPROVED', response.message + end + + def test_successful_verify + response = @gateway.verify(@credit_card, @options) + assert_success response + assert_match %r{APPROVED}, response.message + end + + def test_failed_verify + response = @gateway.verify(@declined_card, @options) + assert_failure response + assert_match %r{Transaction declined}, response.message + end + + def test_invalid_login + gateway = CtPaymentGateway.new(api_key: '', company_number: '12345', merchant_number: '12345') + + response = gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_match %r{Invalid API KEY}, response.message + end + + def test_transcript_scrubbing + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @credit_card, @options) + end + transcript = @gateway.scrub(transcript) + + assert_scrubbed(@credit_card.number, transcript) + assert_scrubbed(@credit_card.verification_value, transcript) + assert_scrubbed(Base64.strict_encode64(@credit_card.number), transcript) + assert_scrubbed(@gateway.options[:api_key], transcript) + end + + def test_transcript_scrubbing_store + transcript = capture_transcript(@gateway) do + @gateway.store(@credit_card, @options) + end + transcript = @gateway.scrub(transcript) + + assert_scrubbed(@credit_card.number, transcript) + assert_scrubbed(@credit_card.verification_value, transcript) + assert_scrubbed(Base64.strict_encode64(@credit_card.number), transcript) + assert_scrubbed(@gateway.options[:api_key], transcript) + end + +end diff --git a/test/remote/gateways/remote_culqi_test.rb b/test/remote/gateways/remote_culqi_test.rb new file mode 100644 index 00000000000..d8241b2aef3 --- /dev/null +++ b/test/remote/gateways/remote_culqi_test.rb @@ -0,0 +1,176 @@ +require 'test_helper' + +class RemoteCulqiTest < Test::Unit::TestCase + def setup + CulqiGateway.ssl_strict = false # Sandbox has an improperly installed cert + @gateway = CulqiGateway.new(fixtures(:culqi)) + + @amount = 1000 + @credit_card = credit_card('4111111111111111') + @declined_card = credit_card('4000300011112220', month: 06, year: 2016) + + @options = { + order_id: generate_unique_id, + billing_address: address + } + end + + def teardown + CulqiGateway.ssl_strict = true + end + + def test_invalid_login + gateway = CulqiGateway.new(merchant_id: '', terminal_id: '', secret_key: '') + response = gateway.purchase(@amount, @credit_card, @options) + assert_failure response + end + + def test_successful_purchase + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + assert_match %r{Approved}, purchase.message + end + + def test_failed_purchase + response = @gateway.purchase(@amount, @declined_card, @options) + assert_failure response + assert_match %r{Failed}, response.message + end + + def test_successful_authorize_and_capture + response = @gateway.authorize(@amount, @credit_card, @options) + assert_success response + assert_match %r{Approved}, response.message + assert_match %r(^\d+$), response.authorization + + capture = @gateway.capture(@amount, response.authorization) + assert_success capture + assert_match %r{Transaction has been successfully captured}, capture.message + end + + def test_failed_authorize + response = @gateway.authorize(@amount, @declined_card, @options) + assert_failure response + assert_match %r{Failed}, response.message + end + + def test_partial_capture + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + assert capture = @gateway.capture(@amount-1, auth.authorization) + assert_success capture + assert_match %r{Transaction has been successfully captured}, capture.message + end + + def test_failed_capture + response = @gateway.capture(@amount, '0') + assert_failure response + assert_match %r{Transaction not found}, response.message + end + + def test_successful_void + response = @gateway.authorize(@amount, @credit_card, @options) + assert_success response + + void = @gateway.void(response.authorization, @options) + assert_success void + assert_match %r{cancelled}, void.message + end + + def test_failed_void + response = @gateway.void('0', @options) + assert_failure response + assert_match %r{Transaction not found}, response.message + end + + def test_successful_refund + auth = @gateway.authorize(@amount, @credit_card, @options) + capture = @gateway.capture(@amount, auth.authorization) + + refund = @gateway.refund(@amount, capture.authorization) + assert_success refund + assert_match %r{reversed}, refund.message + end + + def test_partial_refund + auth = @gateway.authorize(@amount, @credit_card, @options) + capture = @gateway.capture(@amount, auth.authorization) + + refund = @gateway.refund(@amount-1, capture.authorization) + assert_success refund + assert_match %r{reversed}, refund.message + end + + def test_failed_refund + response = @gateway.refund(@amount, '0') + assert_failure response + assert_match %r{Transaction not found}, response.message + end + + def test_successful_verify + response = @gateway.verify(@credit_card, @options) + assert_success response + assert_match %r{Approved}, response.message + end + + def test_failed_verify + response = @gateway.verify(@declined_card, @options) + assert_failure response + assert_match %r{Failed}, response.message + end + + def test_verify_credentials + assert @gateway.verify_credentials + + gateway = CulqiGateway.new(merchant_id: 'unknown', terminal_id: 'unknown', secret_key: 'unknown') + assert !gateway.verify_credentials + gateway = CulqiGateway.new(merchant_id: fixtures(:culqi)[:merchant_id], terminal_id: fixtures(:culqi)[:terminal_id], secret_key: 'unknown') + assert !gateway.verify_credentials + end + + def test_successful_store_and_purchase + credit_card = credit_card('4929927409600297') + + response = @gateway.store(credit_card, @options.merge(partner_id: fixtures(:culqi)[:partner_id])) + assert_success response + assert_match %r{Card tokenized successfully}, response.message + + purchase = @gateway.purchase(@amount, response.authorization, @options.merge(cvv: credit_card.verification_value)) + assert_success purchase + assert_match %r{Successful}, purchase.message + + response = @gateway.invalidate(response.authorization, @options.merge(partner_id: fixtures(:culqi)[:partner_id])) + assert_success response + assert_match %r{Token invalidated successfully}, response.message + end + + def test_failed_store + credit_card = credit_card('4929927409600297') + + store = @gateway.store(credit_card, @options.merge(partner_id: fixtures(:culqi)[:partner_id])) + assert_success store + assert_match %r{Card tokenized successfully}, store.message + + response = @gateway.store(credit_card, @options.merge(partner_id: fixtures(:culqi)[:partner_id])) + assert_failure response + assert_match %r{Card already tokenized for same merchant}, response.message + + response = @gateway.invalidate(store.authorization, @options.merge(partner_id: fixtures(:culqi)[:partner_id])) + assert_success response + assert_match %r{Token invalidated successfully}, response.message + end + + def test_transcript_scrubbing + assert @gateway.supports_scrubbing? + + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @credit_card, @options) + end + clean_transcript = @gateway.scrub(transcript) + + assert_scrubbed(@credit_card.number, clean_transcript) + assert_scrubbed(@credit_card.verification_value.to_s, clean_transcript) + assert_scrubbed(@gateway.options[:secret_key], clean_transcript) + end +end diff --git a/test/remote/gateways/remote_cyber_source_test.rb b/test/remote/gateways/remote_cyber_source_test.rb index 0bf44938fd5..c295737a0a6 100644 --- a/test/remote/gateways/remote_cyber_source_test.rb +++ b/test/remote/gateways/remote_cyber_source_test.rb @@ -2,19 +2,41 @@ class RemoteCyberSourceTest < Test::Unit::TestCase def setup - Base.gateway_mode = :test + Base.mode = :test - @gateway = CyberSourceGateway.new(fixtures(:cyber_source)) + @gateway = CyberSourceGateway.new({nexus: 'NC'}.merge(fixtures(:cyber_source))) - @credit_card = credit_card('4111111111111111') + @credit_card = credit_card('4111111111111111', verification_value: '321') @declined_card = credit_card('801111111111111') @pinless_debit_card = credit_card('4002269999999999') + @elo_credit_card = credit_card('5067310000000010', + verification_value: '321', + month: '12', + year: (Time.now.year + 2).to_s, + brand: :elo + ) + @three_ds_unenrolled_card = credit_card('4000000000000051', + verification_value: '321', + month: '12', + year: (Time.now.year + 2).to_s, + brand: :visa + ) + @three_ds_enrolled_card = credit_card('4000000000000002', + verification_value: '321', + month: '12', + year: (Time.now.year + 2).to_s, + brand: :visa + ) + @three_ds_invalid_card = credit_card('4000000000000010', + verification_value: '321', + month: '12', + year: (Time.now.year + 2).to_s, + brand: :visa + ) @amount = 100 @options = { - :billing_address => address, - :order_id => generate_unique_id, :line_items => [ { @@ -32,44 +54,71 @@ def setup } ], :currency => 'USD', - :email => 'someguy1232@fakeemail.net', :ignore_avs => 'true', :ignore_cvv => 'true' } @subscription_options = { :order_id => generate_unique_id, - :email => 'someguy1232@fakeemail.net', :credit_card => @credit_card, - :billing_address => address, :subscription => { - :frequency => "weekly", + :frequency => 'weekly', :start_date => Date.today.next_week, :occurrences => 4, :auto_renew => true, :amount => 100 } } + + @issuer_additional_data = 'PR25000000000011111111111112222222sk111111111111111111111111111' + + '1111111115555555222233101abcdefghijkl7777777777777777777777777promotionCde' + end + + def test_transcript_scrubbing + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @credit_card, @options) + end + transcript = @gateway.scrub(transcript) + + assert_scrubbed(@credit_card.number, transcript) + assert_scrubbed(@credit_card.verification_value, transcript) + assert_scrubbed(@gateway.options[:password], transcript) + end + + def test_network_tokenization_transcript_scrubbing + credit_card = network_tokenization_credit_card('4111111111111111', + :brand => 'visa', + :eci => '05', + :payment_cryptogram => 'EHuWW9PiBkWvqE5juRwDzAUFBAk=' + ) + + transcript = capture_transcript(@gateway) do + @gateway.authorize(@amount, credit_card, @options) + end + transcript = @gateway.scrub(transcript) + + assert_scrubbed(credit_card.number, transcript) + assert_scrubbed(credit_card.payment_cryptogram, transcript) + assert_scrubbed(@gateway.options[:password], transcript) end def test_successful_authorization assert response = @gateway.authorize(@amount, @credit_card, @options) - assert_equal 'Successful transaction', response.message - assert_success response - assert response.test? + assert_successful_response(response) assert !response.authorization.blank? end - def test_successful_subscription_authorization - assert response = @gateway.store(@credit_card, @subscription_options) - assert_equal 'Successful transaction', response.message - assert_success response - assert response.test? + def test_successful_authorization_with_issuer_additional_data + @options[:issuer_additional_data] = @issuer_additional_data - assert response = @gateway.authorize(@amount, response.authorization, :order_id => generate_unique_id) - assert_equal 'Successful transaction', response.message - assert_success response - assert response.test? + assert response = @gateway.authorize(@amount, @credit_card, @options) + assert_successful_response(response) + assert !response.authorization.blank? + end + + def test_successful_authorization_with_elo + assert response = @gateway.authorize(@amount, @elo_credit_card, @options) + assert_successful_response(response) assert !response.authorization.blank? end @@ -80,91 +129,106 @@ def test_unsuccessful_authorization assert_equal false, response.success? end - def test_authorize_and_auth_reversal + def test_authorize_and_void assert auth = @gateway.authorize(@amount, @credit_card, @options) - assert_equal 'Successful transaction', auth.message assert_success auth - assert auth.test? - - assert auth_reversal = @gateway.auth_reversal(@amount, auth.authorization) - assert_equal 'Successful transaction', auth_reversal.message - assert_success auth_reversal - assert auth_reversal.test? + assert void = @gateway.void(auth.authorization, @options) + assert_equal 'Successful transaction', void.message + assert_success void + assert void.test? end - def test_successful_authorization_and_failed_auth_reversal + def test_capture_and_void assert auth = @gateway.authorize(@amount, @credit_card, @options) assert_success auth - assert_equal 'Successful transaction', auth.message + assert capture = @gateway.capture(@amount, auth.authorization, @options) + assert_success capture + assert void = @gateway.void(capture.authorization, @options) + assert_equal 'Successful transaction', void.message + assert_success void + assert void.test? + end - assert auth_reversal = @gateway.auth_reversal(@amount + 10, auth.authorization) - assert_failure auth_reversal - assert_equal 'One or more fields contains invalid data', auth_reversal.message + def test_capture_and_void_with_elo + assert auth = @gateway.authorize(@amount, @elo_credit_card, @options) + assert_success auth + assert capture = @gateway.capture(@amount, auth.authorization, @options) + assert_success capture + assert void = @gateway.void(capture.authorization, @options) + assert_equal 'Successful transaction', void.message + assert_success void + assert void.test? end def test_successful_tax_calculation assert response = @gateway.calculate_tax(@credit_card, @options) assert_equal 'Successful transaction', response.message assert response.params['totalTaxAmount'] - assert_not_equal "0", response.params['totalTaxAmount'] + assert_not_equal '0', response.params['totalTaxAmount'] assert_success response - assert response.test? end - def test_successful_tax_calculation_with_nexus - total_line_items_value = @options[:line_items].inject(0) do |sum, item| - sum += item[:declared_value] * item[:quantity] - end + def test_successful_purchase + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_successful_response(response) + end - canada_gst_rate = 0.05 - ontario_pst_rate = 0.08 + def test_successful_purchase_with_issuer_additional_data + @options[:issuer_additional_data] = @issuer_additional_data + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_successful_response(response) + end - total_pst = total_line_items_value.to_f * ontario_pst_rate / 100 - total_gst = total_line_items_value.to_f * canada_gst_rate / 100 - total_tax = total_pst + total_gst + def test_successful_purchase_with_elo + assert response = @gateway.purchase(@amount, @elo_credit_card, @options) + assert_successful_response(response) + end - assert response = @gateway.calculate_tax(@credit_card, @options.merge(:nexus => 'ON')) + def test_successful_purchase_sans_options + assert response = @gateway.purchase(@amount, @credit_card) assert_equal 'Successful transaction', response.message - assert response.params['totalTaxAmount'] - assert_equal total_pst, response.params['totalCountyTaxAmount'].to_f - assert_equal total_gst, response.params['totalStateTaxAmount'].to_f - assert_equal total_tax, response.params['totalTaxAmount'].to_f assert_success response - assert response.test? end - def test_successful_purchase + def test_successful_purchase_with_billing_address_override + @options[:billing_address] = address + @options[:email] = 'override@example.com' assert response = @gateway.purchase(@amount, @credit_card, @options) assert_equal 'Successful transaction', response.message assert_success response - assert response.test? end - def test_successful_pinless_debit_card_puchase - assert response = @gateway.purchase(@amount, @pinless_debit_card, @options.merge(:pinless_debit_card => true)) + def test_successful_purchase_with_long_country_name + @options[:billing_address] = address(country: 'united states', state: 'NC') + assert response = @gateway.purchase(@amount, @credit_card, @options) assert_equal 'Successful transaction', response.message assert_success response - assert response.test? end - def test_successful_subscription_purchase - assert response = @gateway.store(@credit_card, @subscription_options) - assert_equal 'Successful transaction', response.message - assert_success response - assert response.test? + def test_successful_purchase_without_decision_manager + @options[:decision_manager_enabled] = 'false' + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_successful_response(response) + end - assert response = @gateway.purchase(@amount, response.authorization, :order_id => generate_unique_id) + def test_successful_purchase_with_decision_manager_profile + @options[:decision_manager_enabled] = 'true' + @options[:decision_manager_profile] = 'Regular' + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_successful_response(response) + end + + def test_successful_pinless_debit_card_puchase + assert response = @gateway.purchase(@amount, @pinless_debit_card, @options.merge(:pinless_debit_card => true)) assert_equal 'Successful transaction', response.message assert_success response - assert response.test? end def test_unsuccessful_purchase assert response = @gateway.purchase(@amount, @declined_card, @options) assert_equal 'Invalid account number', response.message assert_failure response - assert response.test? end def test_authorize_and_capture @@ -176,6 +240,15 @@ def test_authorize_and_capture assert_success capture end + def test_authorize_and_capture_with_elo + assert auth = @gateway.authorize(@amount, @elo_credit_card, @options) + assert_success auth + assert_equal 'Successful transaction', auth.message + + assert capture = @gateway.capture(@amount, auth.authorization) + assert_success capture + end + def test_successful_authorization_and_failed_capture assert auth = @gateway.authorize(@amount, @credit_card, @options) assert_success auth @@ -183,96 +256,161 @@ def test_successful_authorization_and_failed_capture assert capture = @gateway.capture(@amount + 10, auth.authorization, @options) assert_failure capture - assert_equal "The requested amount exceeds the originally authorized amount", capture.message + assert_equal 'The requested amount exceeds the originally authorized amount', capture.message end def test_failed_capture_bad_auth_info - assert auth = @gateway.authorize(@amount, @credit_card, @options) - assert capture = @gateway.capture(@amount, "a;b;c", @options) + assert @gateway.authorize(@amount, @credit_card, @options) + assert capture = @gateway.capture(@amount, 'a;b;c', @options) assert_failure capture end def test_invalid_login - gateway = CyberSourceGateway.new( :login => '', :password => '' ) - authentication_exception = assert_raise ActiveMerchant::ResponseError, 'Failed with 500 Internal Server Error' do - gateway.purchase(@amount, @credit_card, @options) - end - assert response = authentication_exception.response - assert_match(/wsse:InvalidSecurity/, response.body) + gateway = CyberSourceGateway.new(:login => 'asdf', :password => 'qwer') + assert response = gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_equal "wsse:FailedCheck: \nSecurity Data : UsernameToken authentication failed.\n", response.message end + # Unable to test refunds for Elo cards, as the test account is setup to have + # Elo transactions routed to Comercio Latino which has very specific rules on + # refunds (i.e. that you cannot do a "Stand-Alone" refund). This means we need + # to go through a Capture cycle at least a day before submitting a refund. def test_successful_refund assert response = @gateway.purchase(@amount, @credit_card, @options) - assert_equal 'Successful transaction', response.message - assert_success response - assert response.test? + assert_successful_response(response) + assert response = @gateway.refund(@amount, response.authorization) assert_equal 'Successful transaction', response.message assert_success response - assert response.test? end - # Pinless debit payment can never be refunded. - def test_unsuccessful_pinless_debit_card_refund - assert response = @gateway.purchase(@amount, @pinless_debit_card, @options.merge(:pinless_debit_card => true)) - assert_equal 'Successful transaction', response.message - assert_success response + def test_successful_validate_pinless_debit_card + assert response = @gateway.validate_pinless_debit_card(@pinless_debit_card, @options) assert response.test? - assert response = @gateway.refund(@amount, response.authorization) - assert_equal 'One or more fields contains invalid data', response.message - assert_equal false, response.success? + assert_equal 'Y', response.params['status'] + assert_equal true, response.success? end - def test_successful_subscription_credit - assert response = @gateway.store(@credit_card, @subscription_options) - assert_equal 'Successful transaction', response.message + def test_network_tokenization_authorize_and_capture + credit_card = network_tokenization_credit_card('4111111111111111', + :brand => 'visa', + :eci => '05', + :payment_cryptogram => 'EHuWW9PiBkWvqE5juRwDzAUFBAk=' + ) + + assert auth = @gateway.authorize(@amount, credit_card, @options) + assert_success auth + assert_equal 'Successful transaction', auth.message + + assert capture = @gateway.capture(@amount, auth.authorization) + assert_success capture + end + + def test_successful_authorize_with_mdd_fields + (1..20).each { |e| @options["mdd_field_#{e}".to_sym] = "value #{e}" } + + assert response = @gateway.authorize(@amount, @credit_card, @options) assert_success response - assert response.test? + end - assert response = @gateway.credit(@amount, response.authorization, :order_id => generate_unique_id) + def test_successful_purchase_with_mdd_fields + (1..20).each { |e| @options["mdd_field_#{e}".to_sym] = "value #{e}" } + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + end - assert_equal 'Successful transaction', response.message + def test_successful_authorize_with_nonfractional_currency + assert response = @gateway.authorize(100, @credit_card, @options.merge(:currency => 'JPY')) + assert_equal '1', response.params['amount'] assert_success response + end + + def test_successful_subscription_authorization + assert response = @gateway.store(@credit_card, @subscription_options) + assert_successful_response(response) + + assert response = @gateway.authorize(@amount, response.authorization, :order_id => generate_unique_id) + assert_successful_response(response) + assert !response.authorization.blank? + end + + def test_successful_subscription_purchase + assert response = @gateway.store(@credit_card, @subscription_options) + assert_successful_response(response) + + assert response = @gateway.purchase(@amount, response.authorization, :order_id => generate_unique_id) + assert_successful_response(response) + end + + def test_successful_subscription_purchase_with_elo + assert response = @gateway.store(@elo_credit_card, @subscription_options) + assert_successful_response(response) + + assert response = @gateway.purchase(@amount, response.authorization, :order_id => generate_unique_id) + assert_successful_response(response) + end + + def test_successful_standalone_credit_to_card + assert response = @gateway.credit(@amount, @credit_card, @options) + assert_successful_response(response) + end + + def test_failed_standalone_credit_to_card + assert response = @gateway.credit(@amount, @declined_card, @options) + + assert_equal 'Invalid account number', response.message + assert_failure response assert response.test? end + def test_successful_standalone_credit_to_subscription + assert response = @gateway.store(@credit_card, @subscription_options) + assert_successful_response(response) + + assert response = @gateway.credit(@amount, response.authorization, :order_id => generate_unique_id) + assert_successful_response(response) + end + def test_successful_create_subscription assert response = @gateway.store(@credit_card, @subscription_options) - assert_equal 'Successful transaction', response.message - assert_success response - assert response.test? + assert_successful_response(response) + end + + def test_successful_create_subscription_with_elo + assert response = @gateway.store(@elo_credit_card, @subscription_options) + assert_successful_response(response) end def test_successful_create_subscription_with_setup_fee assert response = @gateway.store(@credit_card, @subscription_options.merge(:setup_fee => 100)) + assert_successful_response(response) + end + + def test_successful_create_subscription_with_monthly_options + response = @gateway.store(@credit_card, @subscription_options.merge(:setup_fee => 99.0, :subscription => {:amount => 49.0, :automatic_renew => false, frequency: 'monthly'})) assert_equal 'Successful transaction', response.message - assert_success response - assert response.test? + response = @gateway.retrieve(response.authorization, order_id: @subscription_options[:order_id]) + assert_equal '0.49', response.params['recurringAmount'] + assert_equal 'monthly', response.params['frequency'] end def test_successful_update_subscription_creditcard assert response = @gateway.store(@credit_card, @subscription_options) - assert_equal 'Successful transaction', response.message - assert_success response - assert response.test? + assert_successful_response(response) assert response = @gateway.update(response.authorization, @credit_card, {:order_id => generate_unique_id, :setup_fee => 100}) - assert_equal 'Successful transaction', response.message - assert_success response - assert response.test? + assert_successful_response(response) end def test_successful_update_subscription_billing_address assert response = @gateway.store(@credit_card, @subscription_options) - assert_equal 'Successful transaction', response.message - assert_success response - assert response.test? + assert_successful_response(response) assert response = @gateway.update(response.authorization, nil, {:order_id => generate_unique_id, :setup_fee => 100, billing_address: address, email: 'someguy1232@fakeemail.net'}) - assert_equal 'Successful transaction', response.message - assert_success response - assert response.test? + + assert_successful_response(response) end def test_successful_delete_subscription @@ -285,6 +423,16 @@ def test_successful_delete_subscription assert response.test? end + def test_successful_delete_subscription_with_elo + assert response = @gateway.store(@elo_credit_card, @subscription_options) + assert response.success? + assert response.test? + + assert response = @gateway.unstore(response.authorization, :order_id => generate_unique_id) + assert response.success? + assert response.test? + end + def test_successful_retrieve_subscription assert response = @gateway.store(@credit_card, @subscription_options) assert response.success? @@ -295,10 +443,130 @@ def test_successful_retrieve_subscription assert response.test? end - def test_successful_validate_pinless_debit_card - assert response = @gateway.validate_pinless_debit_card(@pinless_debit_card, @options) + def test_3ds_enroll_request_via_purchase + assert response = @gateway.purchase(1202, @three_ds_enrolled_card, @options.merge(payer_auth_enroll_service: true)) + assert_equal '475', response.params['reasonCode'] + assert !response.params['acsURL'].blank? + assert !response.params['paReq'].blank? + assert !response.params['xid'].blank? + assert !response.success? + end + + def test_3ds_enroll_request_via_authorize + assert response = @gateway.authorize(1202, @three_ds_enrolled_card, @options.merge(payer_auth_enroll_service: true)) + assert_equal '475', response.params['reasonCode'] + assert !response.params['acsURL'].blank? + assert !response.params['paReq'].blank? + assert !response.params['xid'].blank? + assert !response.success? + end + + def test_successful_3ds_requests_with_unenrolled_card + assert response = @gateway.purchase(1202, @three_ds_unenrolled_card, @options.merge(payer_auth_enroll_service: true)) + assert response.success? + + assert response = @gateway.authorize(1202, @three_ds_unenrolled_card, @options.merge(payer_auth_enroll_service: true)) + assert response.success? + end + + def test_successful_3ds_validate_purchase_request + assert response = @gateway.purchase(1202, @three_ds_enrolled_card, @options.merge(payer_auth_validate_service: true, pares: pares)) + assert_equal '100', response.params['reasonCode'] + assert_equal '0', response.params['authenticationResult'] + assert response.success? + end + + def test_failed_3ds_validate_purchase_request + assert response = @gateway.purchase(1202, @three_ds_invalid_card, @options.merge(payer_auth_validate_service: true, pares: pares)) + assert_equal '476', response.params['reasonCode'] + assert !response.success? + end + + def test_successful_3ds_validate_authorize_request + assert response = @gateway.authorize(1202, @three_ds_enrolled_card, @options.merge(payer_auth_validate_service: true, pares: pares)) + assert_equal '100', response.params['reasonCode'] + assert_equal '0', response.params['authenticationResult'] + assert response.success? + end + + def test_failed_3ds_validate_authorize_request + assert response = @gateway.authorize(1202, @three_ds_invalid_card, @options.merge(payer_auth_validate_service: true, pares: pares)) + assert_equal '476', response.params['reasonCode'] + assert !response.success? + end + + def test_successful_first_unscheduled_cof_transaction + @options[:stored_credential] = { + :initiator => 'cardholder', + :reason_type => 'unscheduled', + :initial_transaction => true, + :network_transaction_id => '' + } + assert response = @gateway.authorize(@amount, @credit_card, @options) + assert_equal 'Successful transaction', response.message + assert_success response + end + + def test_successful_subsequent_unscheduled_cof_transaction + @options[:stored_credential] = { + :initiator => 'merchant', + :reason_type => 'unscheduled', + :initial_transaction => false, + :network_transaction_id => '016150703802094' + } + assert response = @gateway.authorize(@amount, @credit_card, @options) + assert_equal 'Successful transaction', response.message + assert_success response + end + + def test_successful_first_recurring_cof_transaction + @options[:stored_credential] = { + :initiator => 'cardholder', + :reason_type => 'recurring', + :initial_transaction => true, + :network_transaction_id => '' + } + assert response = @gateway.authorize(@amount, @credit_card, @options) + assert_equal 'Successful transaction', response.message + assert_success response + end + + def test_successful_subsequent_recurring_cof_transaction + @options[:stored_credential] = { + :initiator => 'merchant', + :reason_type => 'recurring', + :initial_transaction => false, + :network_transaction_id => '016150703802094' + } + assert response = @gateway.authorize(@amount, @credit_card, @options) + assert_equal 'Successful transaction', response.message + assert_success response + end + + def pares + <<-PARES +eNqdmFuTqkgSgN+N8D90zD46M4B3J+yOKO6goNyFN25yEUHkUsiv31K7T/ec6dg9u75YlWRlZVVmflWw1uNrGNJa6DfX8G0thVXlRuFLErz+tgm67sRlbJr3ky4G9LWn8N/e1nughtVD4dFawFAodT8OqbBx4NLdj/o8y3JqKlavSLsNr1VS5G/En/if4zX20UUTXf3Yzeu3teuXpCC/TeerMTFfY+/d9Tm8CvRbEB7dJqvX2LO7xj7H7Zt7q0JOd0nwpo3VacjVvMc4pZcXfcjFpMqLc6UHr2vsrrEO3Dp8G+P4Ap+PZy/E9C+c+AtfrrGHfH25mwPnokG2CRxfY18Fa7Q71zD3b2/LKXr0o7cOu0uRh0gDre1He419+nZx8zf87z+kepeu9cPbuk7OX31a3X0iFmvsIV9XtVs31Zu9xt5ba99t2zcAAAksNjsr4N5MVctyGIaN2H6E1vpQWYd+8obPkFPo/zEKZFFxTer4fHf174I1dncFe4Tzba0lUY4mu4Yv3TnLURDjur78hWEQwj/h5M/iGmHIYRzDVxhSCKok+tdvz1FhIOTH4n8aRrl5kSe+myW9W6PEkMI6LoKXH759Z0ZX75YITGWoP5CpP3ximv9xl+ATYoZsYt8b/bKyX5nlZ2evlftHFbvEfYKfDL2t1fAY3jMifDFU4fW3f/1KZdBJFFb1/+PKhxtfLXzYM92sCd8qN5U5lrrNDZOFzkiecUIszvyCVJjXj3FPzTX2w/f3hT2j+GW3noobXm8xXJ3KK2aZztNbVdsLWbbOASZgzSY45eYqFNiK5ReRNLKbzvZSIDJj+zqBzIEkIx1L9ZTabYeDJa/MV51fF9A0dxDvxzf5CiPmttuVVBLHxmZSNp53lnBcJzh+IS3YpejKebycHjQlvMggwkvHdZjhYBHf8M1R4ikKjHxMGxlCfuCv+IqmxjTRk9GMnO2ynnXsWMvZSYdlk+Vmvpz1pVns4v05ugRWIGZNMhxUGLzoqs+VDe14Jtzli63TT06WBvpJg2+2UVLie+5mgGDlEVjip+7EmZhCvRdndtQHmKm0vaUDejhYTRgglbR5qysx6I1gf+vTyWJ3ahaXNOWBUrXRYnwasbKlbi3XsJLNuA3g6+uXrHqPzCa8PSNxmKElubX7bGmNl4Z+LbuIEJT8SrnXIMnd7IUOz8XLI4DX3192xucDQGlI8NmnijOiqR/+/rJ9lRCvCqSv6a+7OCl+f6FeDW2N/TzPY2IqvNbJEdUVwqUkCLTVo32vtAhAgQSRQAFNgLRii5vCEeLWl4HCsKQCoJMyWwmcOEAYDBlLlGlKHa2DLRnJ5nCAhkoksypca9nxKfDvUhIUEmvIsX9WL96ZrZTxqvYs82aPjQi1bz7NaBIJHhYpCEXplJ2GA8ea4a7lXCRVgUxk06ai0DSoDecg4wIvE3ZC0ooOQhbinUQzNyn1OzkFM5kWXSS7PWVKNxx8SCV+2VE9EJ8+2TrITF1ScEjBh3WBgere5bJWUpb3ld9lPAMd+e6JNxGQJS4F9vuKdObLigRGbj2LyPyznEmqAZmnxS0DO9o+iCfXmsUeRZIKIXW8Djy0Tw8rks4yX62omWctI2Oc5d7ZvKGokEIKZDI6lfEp4VYQJ+9RAGBHAWUJ7s+HAyraoB4DSmYSEIl4LuOMDMYCIZJ71pj7U99OwbapLHXFMLI66s7eKosO9qmWU56LwmJCul2tccin+XTKE4tV7EatfZaSNCQFH9bYXMNCetuoK2kl0SN6An3f3xmIMwGIT8KlZZS5pV/wpTIz8FzIF9fhIK6EhVLuzEDAg4MI+sybxjVzA/TGuEmsEHDZbZFBtjKxdKfgilSRZDLRoGjQmpWlzUEZGeJ+7CK6jCNPPgQe2ZInYsxH5YEWZoId7i5G2RJNax3USyCJo1OXS/jNLKdCtZiMSaCR4jKPaXvXqjl/6Et+OMBDRoth7MfSnLa3o7ItpxyV8CZcmjrVbJtyWykIypti158qotvx1VkJTm48GzeYBAUaKIAsJhUcDkL9mUO8KjEgBUCiIEdZFKcBjhsxAkpL5cjGxN7nzMYgZElgguweT/ugZg5F0s5BfGT2cGCPWdzRQfCwpkzRoa8YasSpRuIhBMUdRVxBGyn1FouIkytA/p5XKp4iAEO2AMZRSKQkIPDhgLC0ZSKTIV5IsXXC55ue+a566chmgKyLBwZfHlr7igWzo4Dn4m63WjXm3kMV3G7GNc3KJz9Ur5pt1AxBnafhdFf03bi2pnQlT8pZhWNWN7Mu+6RtWe/I6AbUz1wcFd6puR7FdrSYDwcYP5lcIsJ0ZNh7zOxcqcSFOjoUhaui645OzZ5qHGeazOnrqlxJ1+2eSJtTNOo7bBrgyvIanQyHuh9xP/PqO4BROI0Alp6/AOzbLYAh/asAo/t78d0L1ZdQ/mVerrZ+yoQSCZ+wiqCpjNmbw2WNbXW0NyZqFNzU0Uh0dHgTEUqqABnwhAENTjfNUu9WLs751LE60N8xINGsmvkTJTLOqzag/g624UDS72hjelmXP9GmKz9kEmf/R7DR4Ak2ZEmdQv7pz4YmzU84fQHYHWZ+DjomBcrTYiVRuig6KJ1R5Z5dhD5kiRQeewAg3Jqc2SOv+8ASIgVnYOQsf9558pl8OIIWJ4KCQ4u+QWKmIqgK7g5MOZ+0XJ4jemPuucVRUPf5rma5LL6U7RxuXQ4ax+NodrIvC4k53wRDanhGdkGrnhJRq2/UajccHM67ebQItvRyk3PEnFrl1y5dFuT0PEFYMqbn0dG2dlx+js/7Yt7HZFuSVXvsV5OYiTYHec4EG7kxo+GgKfvamoPtDhry3CPLjaJN7okBAJeGPTl7z5+AgQolAQC3wBZtwRGA7U2ViJFJcmnxxgo+jjHdwGGkjs0G5UYccOYJ7XDmP7IgS+9QkEj8YY2OFIsk1WUi3MTJQTed7U3A2YUW3Vh3OND14irp4PiAhSYxHA2siFSZKN1jhOVFme2MOa7LKcst80SEKId+OjqM+9GBjoxIIZfNxsBWkyVmbmYUa4iJghm7gzu+8jeiAxMvJwhiR80zcl4FSr2Q01jx442ebHWlimZHrNQymRgOto7dtFMgbPTdxmG4ayKWQJ+Lp3K0OcQ1rU2jtLyw+XKXOqWoLo7ulVFHgTebYaLWXho+Sr1OPy7AcHCGCar/njbEqWk2ib1Z6iWb3cbm1eTZ6PVXIdCmCAJJ+AEBEYh0tx8xmanGGwngHKWVnCZ4E/qRkgaQ+OgfpYOS+5vi+XoroMHnreA/3XIQBP7LPefzlvPj1oBuOd3zlsOKrYegcC+p4YCPfRmFv5NSZiLpNpR1cLPusvQhw3/IUnIqKRWknr5yDBRNo2dkCVSPmdGNAUBGH8cXr2f29z15gBBCTrfuBb66/SokhoP/gglTIqUPSEjvkNC88QpHo0kEguNHRIaDj5igJAWIBjKgKTJRNmSkUNPwevRaVWGow9Vezev9QtlZJaWDcZpjs3SywiKsxD0p8RVKHQ6u49ExWZz6zY28KaVz4ntbnC0nGDi0G9GFeM2id5cJkwbRKezMS2ZrYcnsZzuDlqaRqx0XJS9F5h6VycYt8nF7TfnOCimzY5NpNyWLIBPzY4ZhNZdu8FKm+3pxwqZyqLHWzSsT5f2mQACop8+THcXu42wXhB5bmeepaHFBHFcOzM7lZZr4DPOPs/073eHgQ5sGD22dBAZE4SSx/vtijxSQsEuSy0gWSqEshkxiw9xVEJhqg78mbmrU3nxGzJe1fLxwDDO59rxHzgrpzPiHrvK8WlDJpo33y3MdhU7GZ81W6fFSHfnjYpbBcDjo4CLNjoAvSxRlLaU2W76plphc5At/tEhKra8VXiLN0FuM59Ddt5zgHZitL1vFyttHamkZ44sToxvD5ubwK/BtsWOfr03Yj1epz5esx7ekx8eu+/ePrx/B/g0UAjN8 + PARES + end + + def test_successful_verify_with_elo + response = @gateway.verify(@elo_credit_card, @options) + assert_equal 'Successful transaction', response.message + assert_success response + end + + def test_verify_credentials + assert @gateway.verify_credentials + + gateway = CyberSourceGateway.new(login: 'an_unknown_login', password: 'unknown_password') + assert !gateway.verify_credentials + end + + private + + def assert_successful_response(response) + assert_equal 'Successful transaction', response.message + assert_success response assert response.test? - assert_equal 'Y', response.params["status"] - assert_equal true, response.success? end end diff --git a/test/remote/gateways/remote_d_local_test.rb b/test/remote/gateways/remote_d_local_test.rb new file mode 100644 index 00000000000..2dc9708d940 --- /dev/null +++ b/test/remote/gateways/remote_d_local_test.rb @@ -0,0 +1,246 @@ +require 'test_helper' + +class RemoteDLocalTest < Test::Unit::TestCase + def setup + @gateway = DLocalGateway.new(fixtures(:d_local)) + + @amount = 200 + @credit_card = credit_card('4111111111111111') + @credit_card_naranja = credit_card('5895627823453005') + @cabal_credit_card = credit_card('5896 5700 0000 0004') + @invalid_cabal_card = credit_card('6035 2277 0000 0000') + # No test card numbers, all txns are approved by default, + # but errors can be invoked directly with the `description` field + @options = { + billing_address: address(country: 'Brazil'), + document: '42243309114', + currency: 'BRL' + } + @options_colombia = { + billing_address: address(country: 'Colombia'), + document: '11186456', + currency: 'COP' + } + @options_argentina = { + billing_address: address(country: 'Argentina'), + document: '10563145', + currency: 'ARS' + } + @options_mexico = { + billing_address: address(country: 'Mexico'), + document: '128475869794933', + currency: 'MXN' + } + @options_peru = { + billing_address: address(country: 'Peru'), + document: '184853849', + currency: 'PEN' + } + end + + def test_successful_purchase + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_match 'The payment was paid', response.message + end + + def test_successful_purchase_naranja + response = @gateway.purchase(@amount, @credit_card_naranja, @options) + assert_success response + assert_match 'The payment was paid', response.message + end + + def test_successful_purchase_cabal + response = @gateway.purchase(@amount, @cabal_credit_card, @options) + assert_success response + assert_match 'The payment was paid', response.message + end + + def test_successful_purchase_with_more_options + options = @options.merge( + order_id: '1', + ip: '127.0.0.1', + email: 'joe@example.com', + birth_date: '03-01-1970', + document2: '87648987569', + idempotency_key: generate_unique_id, + user_reference: generate_unique_id + ) + + response = @gateway.purchase(@amount, @credit_card, options) + assert_success response + assert_match 'The payment was paid', response.message + end + + # You may need dLocal to enable your test account to support individual countries + def test_successful_purchase_colombia + response = @gateway.purchase(100000, @credit_card, @options_colombia) + assert_success response + assert_match 'The payment was paid', response.message + end + + def test_successful_purchase_argentina + response = @gateway.purchase(@amount, @credit_card, @options_argentina) + assert_success response + assert_match 'The payment was paid', response.message + end + + def test_successful_purchase_mexico + response = @gateway.purchase(@amount, @credit_card, @options_mexico) + assert_success response + assert_match 'The payment was paid', response.message + end + + def test_successful_purchase_peru + response = @gateway.purchase(@amount, @credit_card, @options_peru) + assert_success response + assert_match 'The payment was paid', response.message + end + + def test_failed_purchase + response = @gateway.purchase(@amount, @credit_card, @options.merge(description: '300')) + assert_failure response + assert_match 'The payment was rejected', response.message + end + + def test_failed_purchase_with_cabal + response = @gateway.purchase(@amount, @invalid_cabal_card, @options) + assert_failure response + assert_match 'Payment not found', response.message + end + + def test_failed_document_format + response = @gateway.purchase(@amount, @credit_card, @options.merge(document: 'bad_document')) + assert_failure response + assert_match 'Invalid parameter: payer.document', response.message + end + + def test_successful_authorize_and_capture + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + assert_match 'The payment was authorized', auth.message + + assert capture = @gateway.capture(@amount, auth.authorization, @options) + assert_success capture + assert_match 'The payment was paid', capture.message + end + + def test_successful_authorize_and_capture_with_cabal + auth = @gateway.authorize(@amount, @cabal_credit_card, @options) + assert_success auth + assert_match 'The payment was authorized', auth.message + + assert capture = @gateway.capture(@amount, auth.authorization, @options) + assert_success capture + assert_match 'The payment was paid', capture.message + end + + def test_failed_authorize + response = @gateway.authorize(@amount, @credit_card, @options.merge(description: '309')) + assert_failure response + assert_equal '309', response.error_code + assert_match 'Card expired', response.message + end + + def test_partial_capture + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + assert capture = @gateway.capture(@amount-1, auth.authorization, @options) + assert_success capture + end + + def test_failed_capture + response = @gateway.capture(@amount, 'bad_id') + assert_failure response + + assert_equal '4000', response.error_code + assert_match 'Payment not found', response.message + end + + def test_successful_refund + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + + assert refund = @gateway.refund(@amount, purchase.authorization, @options.merge(notification_url: 'http://example.com')) + assert_success refund + assert_match 'The refund was paid', refund.message + end + + def test_partial_refund + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + + assert refund = @gateway.refund(@amount-1, purchase.authorization, @options.merge(notification_url: 'http://example.com')) + assert_success refund + end + + def test_failed_refund + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + + response = @gateway.refund(@amount+1, purchase.authorization, @options.merge(notification_url: 'http://example.com')) + assert_failure response + assert_match 'Amount exceeded', response.message + end + + def test_successful_void + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + assert void = @gateway.void(auth.authorization) + assert_success void + assert_match 'The payment was cancelled', void.message + end + + def test_failed_void + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + assert capture = @gateway.capture(@amount, auth.authorization, @options) + assert_success capture + + response = @gateway.void(auth.authorization) + assert_failure response + assert_match 'Invalid transaction status', response.message + end + + def test_successful_verify + response = @gateway.verify(@credit_card, @options) + assert_success response + assert_match %r{The payment was authorized}, response.message + end + + def test_successful_verify_with_cabal + response = @gateway.verify(@cabal_credit_card, @options) + assert_success response + assert_match %r{The payment was authorized}, response.message + end + + def test_failed_verify + response = @gateway.verify(@credit_card, @options.merge(description: '315')) + assert_failure response + assert_equal '315', response.error_code + assert_match %r{Invalid security code}, response.message + end + + def test_invalid_login + gateway = DLocalGateway.new(login: '', trans_key: '', secret_key: '') + + response = gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_match %r{Invalid parameter}, response.message + end + + def test_transcript_scrubbing + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @credit_card, @options) + end + transcript = @gateway.scrub(transcript) + + assert_scrubbed(@credit_card.number, transcript) + assert_scrubbed(@credit_card.verification_value, transcript) + assert_scrubbed(@gateway.options[:trans_key], transcript) + end + +end diff --git a/test/remote/gateways/remote_data_cash_test.rb b/test/remote/gateways/remote_data_cash_test.rb index 60e3701c485..9fb77b19a6f 100644 --- a/test/remote/gateways/remote_data_cash_test.rb +++ b/test/remote/gateways/remote_data_cash_test.rb @@ -9,18 +9,17 @@ def setup @mastercard = CreditCard.new( :number => '5120790000000034', :month => 3, - :year => Date.today.year + 2, - :first_name => 'Mark', + :year => Date.today.year + 2, + :first_name => 'Mark', :last_name => 'McBride', - :brand => :master, - :verification_value => '444' + :brand => :master ) @mastercard_declined = CreditCard.new( :number => '5473000000000106', :month => 3, - :year => Date.today.year + 2, - :first_name => 'Mark', + :year => Date.today.year + 2, + :first_name => 'Mark', :last_name => 'McBride', :brand => :master, :verification_value => '547' @@ -29,8 +28,8 @@ def setup @visa_delta = CreditCard.new( :number => '4539792100000003', :month => 3, - :year => Date.today.year + 2, - :first_name => 'Mark', + :year => Date.today.year + 2, + :first_name => 'Mark', :last_name => 'McBride', :brand => :visa, :verification_value => '444' @@ -45,11 +44,10 @@ def setup :brand => :solo, :issue_number => 5, :start_month => 12, - :start_year => 2006, - :verification_value => 444 + :start_year => 2006 ) - @address = { + @address = { :name => 'Mark McBride', :address1 => 'Flat 12/3', :address2 => '45 Main Road', @@ -60,8 +58,7 @@ def setup } @params = { - :order_id => generate_unique_id, - :billing_address => @address + :order_id => generate_unique_id } @amount = 198 @@ -75,19 +72,17 @@ def test_successful_purchase assert response.test? end - #the amount is changed to £1.99 - the DC test server won't check the - #address details - this is more a check on the passed ExtendedPolicy + # the amount is changed to £1.99 - the DC test server won't check the + # address details - this is more a check on the passed ExtendedPolicy def test_successful_purchase_without_address_check response = @gateway.purchase(199, @mastercard, @params) assert_success response - assert response.test? end # Note the Datacash test server regularly times out on switch requests def test_successful_purchase_with_solo_card response = @gateway.purchase(@amount, @solo, @params) assert_success response - assert response.test? end # this card number won't check the address details - testing extended @@ -97,7 +92,6 @@ def test_successful_purchase_without_address_check2 response = @gateway.purchase(@amount, @solo, @params) assert_success response - assert response.test? end # Testing purchase with request to set up recurring payment account @@ -105,7 +99,6 @@ def test_successful_purchase_without_account_set_up_and_repeat_payments response = @gateway.purchase(@amount, @mastercard, @params) assert_success response assert response.authorization.to_s.split(';')[2].blank? - assert response.test? end # Testing purchase with request to set up recurring payment account @@ -116,49 +109,45 @@ def test_successful_purchase_with_account_set_up_and_repeat_payments assert !response.authorization.to_s.split(';')[2].blank? assert response.test? - #Make second payment on the continuous authorization that was set up in the first purchase + # Make second payment on the continuous authorization that was set up in the first purchase second_order_params = { :order_id => generate_unique_id } purchase = @gateway.purchase(201, response.authorization, second_order_params) assert_success purchase - assert purchase.test? end def test_successful_purchase_with_account_set_up_and_repeat_payments_with_visa_delta_card @params[:set_up_continuous_authority] = true response = @gateway.purchase(@amount, @visa_delta, @params) assert_success response - assert !response.authorization.to_s.split(';')[2].blank? - assert response.test? + assert !response.authorization.to_s.split(';')[2].blank? - #Make second payment on the continuous authorization that was set up in the first purchase + # Make second payment on the continuous authorization that was set up in the first purchase second_order_params = { :order_id => generate_unique_id } purchase = @gateway.purchase(201, response.authorization, second_order_params) assert_success purchase - assert purchase.test? end def test_purchase_with_account_set_up_for_repeat_payments_fails_for_solo_card @params[:set_up_continuous_authority] = true response = @gateway.purchase(@amount, @solo, @params) assert_equal '92', response.params['status'] # Error code for CA not supported - assert_equal 'CA Not Supported', response.message - assert response.test? + assert_equal 'CA Not Supported', response.message end def test_successful_authorization_and_capture_with_account_set_up_and_second_purchase - #Authorize first payment + # Authorize first payment @params[:set_up_continuous_authority] = true first_authorization = @gateway.authorize(@amount, @mastercard, @params) assert_success first_authorization assert !first_authorization.authorization.to_s.split(';')[2].blank? assert first_authorization.test? - #Capture first payment + # Capture first payment capture = @gateway.capture(@amount, first_authorization.authorization, @params) assert_success capture assert capture.test? - #Collect second purchase + # Collect second purchase second_order_params = { :order_id => generate_unique_id } purchase = @gateway.purchase(201, first_authorization.authorization, second_order_params) assert_success purchase @@ -192,10 +181,10 @@ def test_invalid_expiry_month end def test_invalid_expiry_year - @mastercard.year = 1999 + @mastercard.number = 1000350000000353 response = @gateway.purchase(@amount, @mastercard, @params) assert_failure response - assert_equal 'Card has already expired', response.message + assert_equal 'expired', response.message assert response.test? end @@ -206,7 +195,7 @@ def test_declined_card assert response.test? end - def test_successful_authorization_and_capture + def test_successful_authorization_and_capture authorization = @gateway.authorize(@amount, @mastercard, @params) assert_success authorization assert authorization.test? @@ -219,11 +208,11 @@ def test_successful_authorization_and_capture def test_unsuccessful_capture response = @gateway.capture(@amount, ';1234', @params) assert_failure response - assert_equal 'AUTHCODE field required', response.message + assert_equal 'Invalid fulfill ref', response.message assert response.test? end - def test_successful_authorization_and_void + def test_successful_authorization_and_void authorization = @gateway.authorize(@amount, @mastercard, @params) assert_success authorization assert authorization.test? @@ -243,113 +232,114 @@ def test_successfully_purchase_and_void assert void.test? end - def test_successful_refund + def test_successful_credit response = @gateway.credit(@amount, @mastercard, @params) assert_success response assert !response.params['datacash_reference'].blank? assert !response.params['merchantreference'].blank? - + assert response.test? end - def test_successful_transaction_refund + def test_successful_refund purchase = @gateway.purchase(@amount, @mastercard, @params) assert_success purchase - assert purchase.test? - refund = @gateway.credit(@amount, purchase.params['datacash_reference']) + refund = @gateway.refund(@amount, purchase.authorization) assert_success refund assert !refund.params['datacash_reference'].blank? assert !refund.params['merchantreference'].blank? - assert refund.test? end - def test_successful_transaction_refund_with_money_set_to_nil + def test_successful_refund_with_money_set_to_nil purchase = @gateway.purchase(@amount, @mastercard, @params) assert_success purchase - assert purchase.test? - refund = @gateway.credit(nil, purchase.params['datacash_reference']) + refund = @gateway.refund(nil, purchase.authorization) assert_success refund - assert refund.test? end - def test_successful_transaction_refund_in_two_stages + def test_successful_partial_refund purchase = @gateway.purchase(@amount, @mastercard, @params) assert_success purchase - assert purchase.test? - first_partial_refund = @gateway.credit(100, purchase.params['datacash_reference']) + first_partial_refund = @gateway.refund(100, purchase.authorization) assert_success first_partial_refund - assert first_partial_refund.test? - second_partial_refund = @gateway.credit(98, purchase.params['datacash_reference']) + second_partial_refund = @gateway.refund(98, purchase.authorization) assert_success second_partial_refund - assert second_partial_refund.test? - end - - def test_successful_partial_transaction_refund - purchase = @gateway.purchase(@amount, @mastercard, @params) - assert_success purchase - assert purchase.test? - - partial_refund = @gateway.credit(100, purchase.params['datacash_reference']) - assert_success partial_refund - assert partial_refund.test? end - def test_fail_to_refund_too_much + def test_failed_refund purchase = @gateway.purchase(@amount, @mastercard, @params) assert_success purchase - assert purchase.test? - refund_too_much = @gateway.credit(500, purchase.params['datacash_reference']) + refund_too_much = @gateway.refund(500, purchase.authorization) assert_failure refund_too_much assert_equal 'Refund amount > orig 1.98', refund_too_much.message - assert refund_too_much.test? end - def test_fail_to_refund_with_declined_purchase_reference + def test_fail_to_refund_with_declined_purchase_reference declined_purchase = @gateway.purchase(@amount, @mastercard_declined, @params) assert_failure declined_purchase - assert declined_purchase.test? - refund = @gateway.credit(@amount, declined_purchase.params['datacash_reference']) + refund = @gateway.refund(@amount, declined_purchase.authorization) assert_failure refund assert_equal 'Cannot refund transaction', refund.message - assert refund.test? end - def test_fail_to_refund_purchase_which_is_already_refunded + def test_fail_to_refund_purchase_which_is_already_refunded purchase = @gateway.purchase(@amount, @mastercard, @params) assert_success purchase assert purchase.test? - first_refund = @gateway.credit(nil, purchase.params['datacash_reference']) + first_refund = @gateway.refund(nil, purchase.authorization) assert_success first_refund - assert first_refund.test? - second_refund = @gateway.credit(@amount, purchase.params['datacash_reference']) + second_refund = @gateway.refund(@amount, purchase.authorization) assert_failure second_refund assert_equal '1.98 > remaining funds 0.00', second_refund.message - assert second_refund.test? end - # Check short merchant references are reformatted - def test_merchant_reference_that_is_too_short - @params[:order_id] = @params[:order_id].first(5) + def test_successful_refund_of_a_repeat_payment + @params[:set_up_continuous_authority] = true response = @gateway.purchase(@amount, @mastercard, @params) assert_success response + assert !response.authorization.to_s.split(';')[2].blank? assert response.test? + + # Make second payment on the continuous authorization that was set up in the first purchase + second_order_params = { :order_id => generate_unique_id } + purchase = @gateway.purchase(201, response.authorization, second_order_params) + assert_success purchase + + # Refund payment that was made via the continuous authorization payment above + refund = @gateway.refund(201, purchase.authorization) + assert_success refund end - # Check long merchant references are reformatted - def test_merchant_reference_that_is_too_long + def test_order_id_that_is_too_short + @params[:order_id] = @params[:order_id].first(5) + response = @gateway.purchase(@amount, @mastercard, @params) + assert_success response + end + + def test_order_id_that_is_too_long @params[:order_id] = "#{@params[:order_id]}1234356" response = @gateway.purchase(@amount, @mastercard, @params) assert_success response assert response.test? end + def test_transcript_scrubbing + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @visa_delta, @params) + end + transcript = @gateway.scrub(transcript) + + assert_scrubbed(@visa_delta.number, transcript) + assert_scrubbed(@visa_delta.verification_value, transcript) + assert_scrubbed(@gateway.options[:password], transcript) + end end diff --git a/test/remote/gateways/remote_decidir_test.rb b/test/remote/gateways/remote_decidir_test.rb new file mode 100644 index 00000000000..d8555619fe0 --- /dev/null +++ b/test/remote/gateways/remote_decidir_test.rb @@ -0,0 +1,168 @@ +require 'test_helper' + +class RemoteDecidirTest < Test::Unit::TestCase + def setup + @gateway_for_purchase = DecidirGateway.new(fixtures(:decidir_purchase)) + @gateway_for_auth = DecidirGateway.new(fixtures(:decidir_authorize)) + + @amount = 100 + @credit_card = credit_card('4507990000004905') + @declined_card = credit_card('4000300011112220') + @options = { + order_id: SecureRandom.uuid, + billing_address: address, + description: 'Store Purchase' + } + end + + def test_successful_purchase + response = @gateway_for_purchase.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal 'approved', response.message + assert response.authorization + end + + def test_successful_purchase_with_more_options + options = { + ip: '127.0.0.1', + email: 'joe@example.com', + card_holder_door_number: '1234', + card_holder_birthday: '01011980', + card_holder_identification_type: 'dni', + card_holder_identification_number: '123456', + installments: '12' + } + + response = @gateway_for_purchase.purchase(@amount, credit_card('4509790112684851'), @options.merge(options)) + assert_success response + assert_equal 'approved', response.message + assert response.authorization + end + + def test_failed_purchase + response = @gateway_for_purchase.purchase(@amount, @declined_card, @options) + assert_failure response + assert_equal 'TARJETA INVALIDA', response.message + assert_match Gateway::STANDARD_ERROR_CODE[:invalid_number], response.error_code + end + + def test_failed_purchase_with_invalid_field + response = @gateway_for_purchase.purchase(@amount, @declined_card, @options.merge(installments: -1)) + assert_failure response + assert_equal 'invalid_param: installments', response.message + assert_match 'invalid_request_error', response.error_code + end + + def test_successful_authorize_and_capture + auth = @gateway_for_auth.authorize(@amount, @credit_card, @options) + assert_success auth + assert_equal 'pre_approved', auth.message + assert auth.authorization + + assert capture = @gateway_for_auth.capture(@amount, auth.authorization) + assert_success capture + assert_equal 'approved', capture.message + assert capture.authorization + end + + def test_failed_authorize + response = @gateway_for_auth.authorize(@amount, @declined_card, @options) + assert_failure response + assert_equal 'TARJETA INVALIDA', response.message + assert_match Gateway::STANDARD_ERROR_CODE[:invalid_number], response.error_code + end + + def test_failed_partial_capture + auth = @gateway_for_auth.authorize(@amount, @credit_card, @options) + assert_success auth + + assert capture = @gateway_for_auth.capture(1, auth.authorization) + assert_failure capture + assert_equal 'amount: Amount out of ranges: 80 - 105', capture.message + assert_equal 'invalid_request_error', capture.error_code + assert_nil capture.authorization + end + + def test_failed_capture + response = @gateway_for_auth.capture(@amount, '') + + assert_equal 'not_found_error', response.message + assert_match Gateway::STANDARD_ERROR_CODE[:processing_error], response.error_code + end + + def test_successful_refund + purchase = @gateway_for_purchase.purchase(@amount, @credit_card, @options) + assert_success purchase + + assert refund = @gateway_for_purchase.refund(@amount, purchase.authorization) + assert_success refund + assert_equal 'approved', refund.message + assert refund.authorization + end + + def test_partial_refund + purchase = @gateway_for_purchase.purchase(@amount, @credit_card, @options) + assert_success purchase + + assert refund = @gateway_for_purchase.refund(@amount-1, purchase.authorization) + assert_success refund + assert_equal 'approved', refund.message + assert refund.authorization + end + + def test_failed_refund + response = @gateway_for_purchase.refund(@amount, '') + assert_failure response + assert_equal 'not_found_error', response.message + assert_match Gateway::STANDARD_ERROR_CODE[:processing_error], response.error_code + end + + def test_successful_void + auth = @gateway_for_auth.authorize(@amount, @credit_card, @options) + assert_success auth + + assert void = @gateway_for_auth.void(auth.authorization) + assert_success void + assert_equal 'approved', void.message + assert void.authorization + end + + def test_failed_void + response = @gateway_for_auth.void('') + assert_failure response + assert_equal 'not_found_error', response.message + assert_match Gateway::STANDARD_ERROR_CODE[:processing_error], response.error_code + end + + def test_successful_verify + response = @gateway_for_auth.verify(@credit_card, @options) + assert_success response + assert_match %r{pre_approved}, response.message + end + + def test_failed_verify + response = @gateway_for_auth.verify(@declined_card, @options) + assert_failure response + assert_match %r{TARJETA INVALIDA}, response.message + end + + def test_invalid_login + gateway = DecidirGateway.new(api_key: '') + + response = gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_match %r{Invalid authentication credentials}, response.message + end + + def test_transcript_scrubbing + transcript = capture_transcript(@gateway_for_purchase) do + @gateway_for_purchase.purchase(@amount, @credit_card, @options) + end + transcript = @gateway_for_purchase.scrub(transcript) + + assert_scrubbed(@credit_card.number, transcript) + assert_scrubbed(@credit_card.verification_value, transcript) + assert_scrubbed(@gateway_for_purchase.options[:api_key], transcript) + end + +end diff --git a/test/remote/gateways/remote_dibs_test.rb b/test/remote/gateways/remote_dibs_test.rb new file mode 100644 index 00000000000..1e6b16e36b1 --- /dev/null +++ b/test/remote/gateways/remote_dibs_test.rb @@ -0,0 +1,180 @@ +require 'test_helper' + +class RemoteDibsTest < Test::Unit::TestCase + def setup + @gateway = DibsGateway.new(fixtures(:dibs)) + + cc_options = { + :month => 6, + :year => 24, + :verification_value => '684', + :brand => 'visa' + } + + @amount = 100 + @credit_card = credit_card('4711100000000000', cc_options) + @declined_card_auth = credit_card('4711000000000000', cc_options) + @declined_card_capture = credit_card('4711100000000001', cc_options) + + @options = { + order_id: generate_unique_id + } + end + + def test_invalid_login + gateway = DibsGateway.new( + merchant_id: '123456789', + secret_key: '987654321' + ) + response = gateway.authorize(@amount, @credit_card, @options) + assert_failure response + end + + def test_successful_purchase + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal 'Succeeded', response.message + end + + def test_failed_purchase + response = @gateway.purchase(@amount, @declined_card_capture, @options) + assert_failure response + assert_match %r(DECLINE.+), response.message + end + + def test_successful_authorize + response = @gateway.authorize(@amount, @credit_card, @options) + assert_success response + assert_equal 'Succeeded', response.message + assert_match %r(^\d+$), response.authorization + end + + def test_successful_authorize_and_capture + response = @gateway.authorize(@amount, @credit_card, @options) + assert_success response + assert_equal 'Succeeded', response.message + assert_match %r(^\d+$), response.authorization + + capture = @gateway.capture(@amount, response.authorization) + assert_success capture + assert_equal 'Succeeded', capture.message + end + + def test_failed_authorize + response = @gateway.authorize(@amount, @declined_card_auth, @options) + assert_failure response + assert_match %r(DECLINE.+), response.message + end + + def test_successful_authorize_and_failed_capture + response = @gateway.authorize(@amount, @declined_card_capture, @options) + assert_success response + assert_equal 'Succeeded', response.message + assert_match %r(^\d+$), response.authorization + + capture = @gateway.capture(@amount, response.authorization) + assert_failure capture + assert_match %r(DECLINE.+), capture.message + end + + def test_failed_capture + response = @gateway.capture(@amount, '') + assert_failure response + assert_match %r(ERROR.+), response.message + end + + def test_successful_void + response = @gateway.authorize(@amount, @credit_card, @options) + assert_success response + + void = @gateway.void(response.authorization) + assert_success void + assert_equal 'Succeeded', void.message + + capture = @gateway.capture(@amount, response.authorization) + assert_failure capture + assert_match %r(DECLINE.+), capture.message + end + + def test_failed_void + response = @gateway.void('') + assert_failure response + assert_match %r(ERROR.+), response.message + end + + def test_successful_refund + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + refund = @gateway.refund(@amount, response.authorization) + assert_success refund + assert_equal 'Succeeded', refund.message + end + + def test_failed_refund + response = @gateway.refund(nil, '') + assert_failure response + assert_match %r(ERROR.+), response.message + end + + def test_successful_verify + response = @gateway.verify(@credit_card, @options) + assert_success response + assert_match %r{Succeeded}, response.message + end + + def test_failed_verify + response = @gateway.verify(@declined_card_auth, @options) + assert_failure response + assert_match %r(DECLINE.+), response.message + end + + def test_successful_store + response = @gateway.store(@credit_card, @options) + assert_success response + assert_equal 'Succeeded', response.message + assert_not_nil response.params['ticketId'] + assert_not_nil response.authorization + assert_equal response.params['ticketId'], response.authorization + end + + def test_failed_store + response = @gateway.store(@declined_card_auth, @options) + assert_failure response + assert_match %r(DECLINE.+), response.message + end + + def test_successful_authorize_with_stored_card + response = @gateway.store(@credit_card, @options) + assert_success response + assert_not_nil response.authorization + response = @gateway.authorize(@amount, response.authorization, @options) + assert_success response + assert_equal 'Succeeded', response.message + assert_match %r(^\d+$), response.authorization + end + + def test_successful_authorize_and_capture_with_stored_card + response = @gateway.store(@credit_card, @options) + assert_success response + assert_not_nil response.authorization + response = @gateway.authorize(@amount, response.authorization, @options) + assert_success response + assert_equal 'Succeeded', response.message + assert_match %r(^\d+$), response.authorization + + capture = @gateway.capture(@amount, response.authorization) + assert_success capture + assert_equal 'Succeeded', capture.message + end + + def test_transcript_scrubbing + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @credit_card, @options) + end + clean_transcript = @gateway.scrub(transcript) + + assert_scrubbed(@credit_card.number, clean_transcript) + assert_scrubbed(@credit_card.verification_value.to_s, clean_transcript) + assert_scrubbed(@gateway.options[:secret_key], clean_transcript) + end +end diff --git a/test/remote/gateways/remote_digitzs_test.rb b/test/remote/gateways/remote_digitzs_test.rb new file mode 100644 index 00000000000..285dd840f9d --- /dev/null +++ b/test/remote/gateways/remote_digitzs_test.rb @@ -0,0 +1,136 @@ +require 'test_helper' + +class RemoteDigitzsTest < Test::Unit::TestCase + def setup + @gateway = DigitzsGateway.new(fixtures(:digitzs)) + + @amount = 500 + @credit_card = credit_card('4747474747474747', verification_value: '999') + @declined_card = credit_card('4616161616161616') + @options = { + merchant_id: 'spreedly-susanswidg-32268973-2091076-148408385', + billing_address: address, + description: 'Store Purchase' + } + + @options_card_split = { + merchant_id: 'spreedly-susanswidg-32268973-2091076-148408385', + billing_address: address, + description: 'Split Purchase', + payment_type: 'card_split', + split_amount: 100, + split_merchant_id: 'spreedly-susanswidg-32270590-2095203-148657924' + } + + @options_token_split = { + merchant_id: 'spreedly-susanswidg-32268973-2091076-148408385', + billing_address: address, + description: 'Token Split Purchase', + payment_type: 'token_split', + split_amount: 100, + split_merchant_id: 'spreedly-susanswidg-32270590-2095203-148657924' + } + end + + def test_successful_purchase + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal 'Success', response.message + end + + def test_successful_token_purchase + assert store = @gateway.store(@credit_card, @options) + assert_success store + + response = @gateway.purchase(@amount, store.authorization, @options) + assert_success response + assert_equal 'Success', response.message + end + + def test_successful_token_split_purchase + assert store = @gateway.store(@credit_card, @options) + assert_success store + + response = @gateway.purchase(@amount, store.authorization, @options_token_split) + assert_success response + assert_equal 'Success', response.message + assert response.params['data']['attributes']['split']['splitId'] + end + + def test_successful_card_split_purchase + response = @gateway.purchase(@amount, @credit_card, @options_card_split) + assert_success response + assert_equal 'Success', response.message + assert response.params['data']['attributes']['split']['splitId'] + end + + def test_failed_purchase + response = @gateway.purchase(@amount, @declined_card, @options) + assert_failure response + assert_equal 'Partner error: Credit card declined (transaction element shows reason for decline)', response.message + end + + def test_successful_refund + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + + assert refund = @gateway.refund(@amount, purchase.authorization, @options) + assert_success refund + assert_equal 'Success', refund.message + end + + def test_partial_refund + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + + assert refund = @gateway.refund(@amount-1, purchase.authorization, @options) + assert_success refund + end + + def test_failed_refund + response = @gateway.refund(@amount, '', @options) + assert_failure response + assert_equal '"id" is not allowed to be empty', response.message + end + + def test_successful_store + assert response = @gateway.store(@credit_card, @options) + assert_success response + end + + def test_successful_store_without_billing_address + assert response = @gateway.store(@credit_card, {merchant_id: 'spreedly-susanswidg-32268973-2091076-148408385'}) + assert_success response + end + + def test_store_adds_card_to_existing_customer + assert response = @gateway.store(@credit_card, @options.merge({customer_id: 'spreedly-susanswidg-32268973-2091076-148408385-5980208887457495-148700575'})) + assert_success response + end + + def test_store_creates_new_customer_and_adds_card + assert response = @gateway.store(@credit_card, @options.merge({customer_id: 'nonexistant'})) + assert_success response + end + + def test_invalid_login + gateway = DigitzsGateway.new(app_key: '', api_key: '', merchant_id: '') + + response = gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_match %r{Forbidden}, response.message + end + + def test_transcript_scrubbing + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @credit_card, @options) + end + transcript = @gateway.scrub(transcript) + + assert_scrubbed(@credit_card.number, transcript) + assert_scrubbed(@credit_card.verification_value, transcript) + assert_scrubbed(@gateway.options[:api_key], transcript) + assert_scrubbed(@gateway.options[:app_key], transcript) + end + +end diff --git a/test/remote/gateways/remote_ebanx_test.rb b/test/remote/gateways/remote_ebanx_test.rb new file mode 100644 index 00000000000..d1035d9b62b --- /dev/null +++ b/test/remote/gateways/remote_ebanx_test.rb @@ -0,0 +1,218 @@ +require 'test_helper' + +class RemoteEbanxTest < Test::Unit::TestCase + def setup + @gateway = EbanxGateway.new(fixtures(:ebanx)) + + @amount = 100 + @credit_card = credit_card('4111111111111111') + @declined_card = credit_card('5102026827345142') + @options = { + billing_address: address({ + address1: '1040 Rua E', + city: 'Maracanaú', + state: 'CE', + zip: '61919-230', + country: 'BR', + phone_number: '8522847035' + }), + order_id: generate_unique_id, + document: '853.513.468-93' + } + end + + def test_successful_purchase + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal 'Accepted', response.message + end + + def test_successful_purchase_with_more_options + options = @options.merge({ + order_id: generate_unique_id, + ip: '127.0.0.1', + email: 'joe@example.com', + birth_date: '10/11/1980', + person_type: 'personal' + }) + + response = @gateway.purchase(@amount, @credit_card, options) + assert_success response + assert_equal 'Accepted', response.message + end + + def test_successful_purchase_as_brazil_business_with_responsible_fields + options = @options.update(document: '32593371000110', + person_type: 'business', + responsible_name: 'Business Person', + responsible_document: '32593371000111', + responsible_birth_date: '1/11/1975') + + response = @gateway.purchase(@amount, @credit_card, options) + assert_success response + assert_equal 'Accepted', response.message + end + + def test_successful_purchase_as_colombian + options = @options.merge({ + order_id: generate_unique_id, + ip: '127.0.0.1', + email: 'jose@example.com.co', + birth_date: '10/11/1980', + billing_address: address({ + address1: '1040 Rua E', + city: 'Medellín', + state: 'AN', + zip: '29269', + country: 'CO', + phone_number: '8522847035' + }) + }) + + response = @gateway.purchase(500, @credit_card, options) + assert_success response + assert_equal 'Sandbox - Test credit card, transaction captured', response.message + end + + def test_failed_purchase + response = @gateway.purchase(@amount, @declined_card, @options) + assert_failure response + assert_equal 'Sandbox - Test credit card, transaction declined reason insufficientFunds', response.message + assert_equal 'NOK', response.error_code + end + + def test_successful_authorize_and_capture + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + assert_equal 'Accepted', auth.message + + assert capture = @gateway.capture(@amount, auth.authorization) + assert_success capture + assert_equal 'Accepted', capture.message + end + + def test_failed_authorize + response = @gateway.authorize(@amount, @declined_card, @options) + assert_failure response + assert_equal 'Sandbox - Test credit card, transaction declined reason insufficientFunds', response.message + assert_equal 'NOK', response.error_code + end + + def test_partial_capture + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + assert capture = @gateway.capture(@amount-1, auth.authorization) + assert_success capture + end + + def test_failed_capture + response = @gateway.capture(@amount, '') + assert_failure response + assert_equal 'Parameters hash or merchant_payment_code not informed', response.message + end + + def test_successful_refund + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + + refund_options = @options.merge({description: 'full refund'}) + assert refund = @gateway.refund(@amount, purchase.authorization, refund_options) + assert_success refund + assert_equal 'Accepted', refund.message + end + + def test_partial_refund + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + + refund_options = @options.merge(description: 'refund due to returned item') + assert refund = @gateway.refund(@amount-1, purchase.authorization, refund_options) + assert_success refund + end + + def test_failed_refund + response = @gateway.refund(@amount, '') + assert_failure response + assert_match('Parameter hash not informed', response.message) + end + + def test_successful_void + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + assert void = @gateway.void(auth.authorization) + assert_success void + assert_equal 'Accepted', void.message + end + + def test_failed_void + response = @gateway.void('') + assert_failure response + assert_equal 'Parameter hash not informed', response.message + end + + def test_successful_store_and_purchase + store = @gateway.store(@credit_card, @options) + assert_success store + + assert purchase = @gateway.purchase(@amount, store.authorization, @options) + assert_success purchase + assert_equal 'Accepted', purchase.message + end + + def test_successful_store_and_purchase_as_brazil_business + options = @options.update(document: '32593371000110', + person_type: 'business', + responsible_name: 'Business Person', + responsible_document: '32593371000111', + responsible_birth_date: '1/11/1975') + + store = @gateway.store(@credit_card, options) + assert_success store + + assert purchase = @gateway.purchase(@amount, store.authorization, options) + assert_success purchase + assert_equal 'Accepted', purchase.message + end + + def test_failed_purchase_with_stored_card + store = @gateway.store(@declined_card, @options) + assert_success store + + assert purchase = @gateway.purchase(@amount, store.authorization, @options) + assert_failure purchase + end + + def test_successful_verify + response = @gateway.verify(@credit_card, @options) + assert_success response + assert_match %r{Accepted}, response.message + end + + def test_failed_verify + response = @gateway.verify(@declined_card, @options) + assert_failure response + assert_match %r{Accepted}, response.message + end + + def test_invalid_login + gateway = EbanxGateway.new(integration_key: '') + + response = gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_match %r{Field integration_key is required}, response.message + end + + def test_transcript_scrubbing + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @credit_card, @options) + end + transcript = @gateway.scrub(transcript) + + assert_scrubbed(@credit_card.number, transcript) + assert_scrubbed(@credit_card.verification_value, transcript) + assert_scrubbed(@gateway.options[:integration_key], transcript) + end + +end diff --git a/test/remote/gateways/remote_efsnet_test.rb b/test/remote/gateways/remote_efsnet_test.rb index f36387d9385..4e6f22499b7 100644 --- a/test/remote/gateways/remote_efsnet_test.rb +++ b/test/remote/gateways/remote_efsnet_test.rb @@ -1,22 +1,22 @@ require 'test_helper' class RemoteEfsnetTest < Test::Unit::TestCase - + def setup - Base.gateway_mode = :test + Base.mode = :test @gateway = EfsnetGateway.new(fixtures(:efsnet)) - + @credit_card = credit_card('4000100011112224') - + @amount = 100 @declined_amount = 156 - @options = { :order_id => generate_unique_id, + @options = { :order_id => generate_unique_id, :billing_address => address } end - + def test_successful_purchase assert response = @gateway.purchase(@amount, @credit_card, @options) assert_success response @@ -45,10 +45,10 @@ def test_unsuccessful_purchase def test_authorize_and_capture amount = @amount assert auth = @gateway.authorize(amount, @credit_card, @options) - assert_success auth + assert_success auth assert_equal 'Approved', auth.message assert auth.authorization - + assert capture = @gateway.capture(amount, auth.authorization, @options) assert_success capture end diff --git a/test/remote/gateways/remote_elavon_test.rb b/test/remote/gateways/remote_elavon_test.rb index 60159f36e87..1f89405e99a 100644 --- a/test/remote/gateways/remote_elavon_test.rb +++ b/test/remote/gateways/remote_elavon_test.rb @@ -3,14 +3,16 @@ class RemoteElavonTest < Test::Unit::TestCase def setup @gateway = ElavonGateway.new(fixtures(:elavon)) + @multi_currency_gateway = ElavonGateway.new(fixtures(:elavon_multi_currency)) - @credit_card = credit_card('4222222222222') + @credit_card = credit_card('4124939999999990') @bad_credit_card = credit_card('invalid') @options = { - :email => "paul@domain.com", + :email => 'paul@domain.com', :description => 'Test Transaction', - :billing_address => address + :billing_address => address, + :ip => '203.0.113.0' } @amount = 100 end @@ -42,6 +44,16 @@ def test_authorize_and_capture assert_success capture end + def test_authorize_and_capture_with_auth_code + assert auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + assert_equal 'APPROVAL', auth.message + assert auth.authorization + + assert capture = @gateway.capture(@amount, auth.authorization) + assert_success capture + end + def test_unsuccessful_capture assert response = @gateway.capture(@amount, '', :credit_card => @credit_card) assert_failure response @@ -49,12 +61,24 @@ def test_unsuccessful_capture end def test_unsuccessful_authorization - @credit_card.number = "1234567890123" - assert response = @gateway.authorize(@amount, @credit_card, @options) + assert response = @gateway.authorize(@amount, @bad_credit_card, @options) assert_failure response assert_equal 'The Credit Card Number supplied in the authorization request appears to be invalid.', response.message end + def test_successful_verify + assert response = @gateway.verify(@credit_card, @options) + assert_success response + assert_equal 'APPROVAL', response.message + assert_success response.responses.last, 'The void should succeed' + end + + def test_failed_verify + assert response = @gateway.verify(@bad_credit_card, @options) + assert_failure response + assert_match %r{appears to be invalid}, response.message + end + def test_purchase_and_credit assert purchase = @gateway.purchase(@amount, @credit_card, @options) assert_success purchase @@ -80,7 +104,7 @@ def test_purchase_and_failed_refund assert refund = @gateway.refund(@amount + 5, purchase.authorization) assert_failure refund - assert_equal 'The refund amount exceeds the original transaction amount.', refund.message + assert_match %r{exceed}i, refund.message end def test_purchase_and_successful_void @@ -97,9 +121,128 @@ def test_purchase_and_unsuccessful_void assert purchase = @gateway.purchase(@amount, @credit_card, @options) assert_success purchase - assert response = @gateway.void(purchase.authorization) + assert @gateway.void(purchase.authorization) assert response = @gateway.void(purchase.authorization) assert_failure response assert_equal 'The transaction ID is invalid for this transaction type', response.message end + + def test_authorize_and_successful_void + assert authorize = @gateway.authorize(@amount, @credit_card, @options) + assert_success authorize + + assert response = @gateway.void(authorize.authorization) + + assert_success response + assert response.authorization + end + + def test_successful_store_without_verify + assert response = @gateway.store(@credit_card, @options) + assert_success response + assert_nil response.message + assert response.test? + end + + def test_successful_store_with_verify_false + assert response = @gateway.store(@credit_card, @options.merge(verify: false)) + assert_success response + assert_nil response.message + assert response.test? + end + + def test_successful_store_with_verify_true + assert response = @gateway.store(@credit_card, @options.merge(verify: true)) + assert_success response + assert_equal 'APPROVAL', response.message + assert response.test? + end + + def test_unsuccessful_store + assert response = @gateway.store(@bad_credit_card, @options) + assert_failure response + assert_equal 'The Credit Card Number supplied in the authorization request appears to be invalid.', response.message + assert response.test? + end + + def test_successful_update + store_response = @gateway.store(@credit_card, @options) + token = store_response.params['token'] + credit_card = credit_card('4124939999999990', :month => 10) + assert response = @gateway.update(token, credit_card, @options) + assert_success response + assert response.test? + end + + def test_unsuccessful_update + assert response = @gateway.update('ABC123', @credit_card, @options) + assert_failure response + assert_match %r{invalid}i, response.message + assert response.test? + end + + def test_successful_purchase_with_token + store_response = @gateway.store(@credit_card, @options) + token = store_response.params['token'] + assert response = @gateway.purchase(@amount, token, @options) + assert_success response + assert response.test? + assert_equal 'APPROVAL', response.message + end + + def test_failed_purchase_with_token + assert response = @gateway.purchase(@amount, 'ABC123', @options) + assert_failure response + assert response.test? + assert_match %r{invalid}i, response.message + end + + def test_successful_purchase_with_custom_fields + assert response = @gateway.purchase(@amount, @credit_card, @options.merge(custom_fields: {a_key: 'a value'})) + + assert_success response + assert response.test? + assert_equal 'APPROVAL', response.message + assert response.authorization + end + + def test_failed_purchase_with_multi_currency_terminal_setting_disabled + assert response = @gateway.purchase(@amount, @credit_card, @options.merge(currency: 'USD', multi_currency: true)) + + assert_failure response + assert response.test? + assert_equal 'Transaction currency is not allowed for this terminal. Your terminal must be setup with Multi currency', response.message + assert response.authorization + end + + def test_successful_purchase_with_multi_currency_gateway_setting + assert response = @multi_currency_gateway.purchase(@amount, @credit_card, @options.merge(currency: 'JPY')) + + assert_success response + assert response.test? + assert_equal 'APPROVAL', response.message + assert response.authorization + end + + def test_successful_purchase_with_multi_currency_transaction_setting + @multi_currency_gateway.options[:multi_currency] = false + assert response = @multi_currency_gateway.purchase(@amount, @credit_card, @options.merge(currency: 'JPY', multi_currency: true)) + + assert_success response + assert response.test? + assert_equal 'APPROVAL', response.message + assert response.authorization + end + + def test_transcript_scrubbing + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @credit_card, @options) + end + transcript = @gateway.scrub(transcript) + + assert_scrubbed(@credit_card.number, transcript) + assert_scrubbed(@credit_card.verification_value, transcript) + assert_scrubbed(@gateway.options[:password], transcript) + end + end diff --git a/test/remote/gateways/remote_element_test.rb b/test/remote/gateways/remote_element_test.rb new file mode 100644 index 00000000000..4cc1b565471 --- /dev/null +++ b/test/remote/gateways/remote_element_test.rb @@ -0,0 +1,162 @@ +require 'test_helper' + +class RemoteElementTest < Test::Unit::TestCase + def setup + @gateway = ElementGateway.new(fixtures(:element)) + + @amount = 100 + @credit_card = credit_card('4000100011112224') + @check = check + @options = { + order_id: '1', + billing_address: address, + description: 'Store Purchase' + } + end + + def test_successful_purchase + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal 'Approved', response.message + assert_match %r{Street address and postal code do not match}, response.avs_result['message'] + assert_match %r{CVV matches}, response.cvv_result['message'] + end + + def test_failed_purchase + @amount = 20 + response = @gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_equal 'Declined', response.message + end + + def test_successful_purchase_with_echeck + response = @gateway.purchase(@amount, @check, @options) + assert_success response + assert_equal 'Success', response.message + end + + def test_successful_purchase_with_payment_account_token + response = @gateway.store(@credit_card, @options) + assert_success response + + response = @gateway.purchase(@amount, response.authorization, @options) + assert_success response + assert_equal 'Approved', response.message + end + + def test_successful_purchase_with_shipping_address + response = @gateway.purchase(@amount, @credit_card, @options.merge(shipping_address: address(address1: 'Shipping'))) + assert_success response + assert_equal 'Approved', response.message + end + + def test_successful_authorize_and_capture + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + assert capture = @gateway.capture(@amount, auth.authorization) + assert_success capture + assert_equal 'Success', capture.message + end + + def test_failed_authorize + @amount = 20 + response = @gateway.authorize(@amount, @credit_card, @options) + assert_failure response + assert_equal 'Declined', response.message + end + + def test_partial_capture + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + assert capture = @gateway.capture(@amount-1, auth.authorization) + assert_success capture + end + + def test_failed_capture + response = @gateway.capture(@amount, '') + assert_failure response + assert_equal 'TransactionID required', response.message + end + + def test_successful_refund + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + + assert refund = @gateway.refund(@amount, purchase.authorization) + assert_success refund + assert_equal 'Approved', refund.message + end + + def test_partial_refund + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + + assert refund = @gateway.refund(@amount-1, purchase.authorization) + assert_success refund + end + + def test_failed_refund + response = @gateway.refund(@amount, '') + assert_failure response + assert_equal 'TransactionID required', response.message + end + + def test_successful_void + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + assert void = @gateway.void(auth.authorization) + assert_success void + assert_equal 'Success', void.message + end + + def test_failed_void + response = @gateway.void('') + assert_failure response + assert_equal 'TransactionAmount required', response.message + end + + def test_successful_verify + response = @gateway.verify(@credit_card, @options) + assert_success response + assert_match %r{Approved}, response.message + end + + def test_successful_store + response = @gateway.store(@credit_card, @options) + assert_success response + assert_match %r{PaymentAccount created}, response.message + end + + def test_invalid_login + gateway = ElementGateway.new(account_id: '', account_token: '', application_id: '', acceptor_id: '', application_name: '', application_version: '') + + response = gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_match %r{Invalid Request}, response.message + end + + def test_transcript_scrubbing + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @credit_card, @options) + end + transcript = @gateway.scrub(transcript) + + assert_scrubbed(@credit_card.number, transcript) + assert_scrubbed(@credit_card.verification_value, transcript) + assert_scrubbed(@gateway.options[:account_token], transcript) + end + + def test_transcript_scrubbing_with_echeck + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @check, @options) + end + transcript = @gateway.scrub(transcript) + + assert_scrubbed(@check.account_number, transcript) + assert_scrubbed(@check.routing_number, transcript) + assert_scrubbed(@gateway.options[:account_token], transcript) + end +end diff --git a/test/remote/gateways/remote_epay_test.rb b/test/remote/gateways/remote_epay_test.rb index 8d150946904..3cde00ce108 100644 --- a/test/remote/gateways/remote_epay_test.rb +++ b/test/remote/gateways/remote_epay_test.rb @@ -2,7 +2,7 @@ class RemoteEpayTest < Test::Unit::TestCase def setup - Base.gateway_mode = :test + Base.mode = :test @gateway = EpayGateway.new(fixtures(:epay)) @@ -10,96 +10,63 @@ def setup @credit_card_declined = credit_card('3333333333333102') @amount = 100 - @options = { :order_id => '1' } + @options = {order_id: '1'} end - def test_successful_authorization - assert response = @gateway.authorize(@amount, @credit_card, @options) - assert_equal "1", response.params['accept'] - assert_not_nil response.params['tid'] - assert_not_nil response.params['cur'] - assert_not_nil response.params['amount'] - assert_not_nil response.params['orderid'] - assert !response.authorization.blank? + def test_successful_purchase + response = @gateway.purchase(@amount, @credit_card, @options) assert_success response + assert !response.authorization.blank? assert response.test? end - def test_failed_authorization - assert response = @gateway.authorize(@amount, @credit_card_declined, @options) - assert_equal '1', response.params['decline'] - assert_not_nil response.params['error'] - assert_not_nil response.params['errortext'] - assert_failure response - assert response.test? - end - - def test_successful_purchase - assert response = @gateway.purchase(@amount, @credit_card, @options) - assert_equal '1', response.params['accept'] - assert_not_nil response.params['tid'] - assert_not_nil response.params['cur'] - assert_not_nil response.params['amount'] - assert_not_nil response.params['orderid'] - assert !response.authorization.blank? + def test_successful_authorize_and_capture + response = @gateway.authorize(@amount, @credit_card, @options) assert_success response - assert response.test? + assert !response.authorization.blank? + + capture_response = @gateway.capture(@amount, response.authorization) + assert_success capture_response end - def test_failed_purchase - assert response = @gateway.purchase(@amount, @credit_card_declined, @options) - assert_equal '1', response.params['decline'] - assert_not_nil response.params['error'] - assert_not_nil response.params['errortext'] + def test_failed_authorization + response = @gateway.authorize(@amount, @credit_card_declined, @options) assert_failure response - assert response.test? end - def test_successful_capture - authorize_response = @gateway.authorize(@amount, @credit_card, @options) - - assert response = @gateway.capture(@amount, authorize_response.authorization) - assert_equal 'true', response.params['result'] - assert_success response - assert response.test? + def test_failed_purchase + response = @gateway.purchase(@amount, @credit_card_declined, @options) + assert_failure response end def test_failed_capture - assert response = @gateway.capture(@amount, 0) - assert_equal 'false', response.params['result'] + response = @gateway.capture(@amount, 0) assert_failure response - assert response.test? end def test_successful_refund - authorize_response = @gateway.purchase(@amount, @credit_card, @options) - - assert response = @gateway.refund(@amount, authorize_response.authorization) - assert_equal 'true', response.params['result'] + response = @gateway.purchase(@amount, @credit_card, @options) assert_success response - assert response.test? + + refund_response = @gateway.refund(@amount, response.authorization) + assert_success refund_response end def test_failed_refund - assert response_refund = @gateway.refund(@amount, 0) - assert_equal 'false', response_refund.params['result'] - assert_failure response_refund - assert response_refund.test? + response = @gateway.refund(@amount, 0) + assert_failure response end def test_successful_void - authorize_response = @gateway.authorize(@amount, @credit_card, @options) - - assert response = @gateway.void(authorize_response.authorization) - assert_equal 'true', response.params['result'] + response = @gateway.authorize(@amount, @credit_card, @options) assert_success response - assert response.test? + + void_response = @gateway.void(response.authorization) + assert_success void_response end def test_failed_void - assert response_void = @gateway.void(0) - assert_equal 'false', response_void.params['result'] - assert_failure response_void - assert response_void.test? + response = @gateway.void(0) + assert_failure response end end diff --git a/test/remote/gateways/remote_eway_managed_test.rb b/test/remote/gateways/remote_eway_managed_test.rb index f4914ac6eff..1322e32676c 100644 --- a/test/remote/gateways/remote_eway_managed_test.rb +++ b/test/remote/gateways/remote_eway_managed_test.rb @@ -21,7 +21,7 @@ def setup def test_successful_purchase assert response = @gateway.purchase(@amount, @valid_customer_id, @options) - assert_equal "00,Transaction Approved(Test Gateway)", response.message + assert_equal '00,Transaction Approved(Test Gateway)', response.message assert_success response assert response.test? assert_not_nil response.authorization @@ -41,7 +41,7 @@ def test_invalid_login def test_store_credit_card assert response = @gateway.store(@credit_card, @options) assert_success response - assert_equal "OK", response.message + assert_equal 'OK', response.message assert !response.token.blank? assert_not_nil response.token end @@ -49,7 +49,7 @@ def test_store_credit_card def test_update_credit_card assert response = @gateway.update(@valid_customer_id, @credit_card, @options) assert_success response - assert_equal "OK", response.message + assert_equal 'OK', response.message assert response.token.blank? end @@ -64,7 +64,7 @@ def test_store_invalid_credit_card def test_retrieve assert response = @gateway.retrieve(@valid_customer_id) assert_success response - assert_equal "OK", response.message + assert_equal 'OK', response.message assert response.test? end end diff --git a/test/remote/gateways/remote_eway_rapid_test.rb b/test/remote/gateways/remote_eway_rapid_test.rb index e768c8ae329..4feec8d4e6b 100644 --- a/test/remote/gateways/remote_eway_rapid_test.rb +++ b/test/remote/gateways/remote_eway_rapid_test.rb @@ -5,117 +5,395 @@ def setup @gateway = EwayRapidGateway.new(fixtures(:eway_rapid)) @amount = 100 - @failed_amount = 105 - @credit_card = credit_card("4444333322221111") + @failed_amount = -100 + @credit_card = credit_card('4444333322221111') @options = { - :order_id => "1", - :billing_address => address, - :description => "Store Purchase", - :redirect_url => "http://bogus.com" + order_id: '1', + invoice: 'I1234', + billing_address: address, + description: 'Store Purchase', + redirect_url: 'http://bogus.com' } end - def test_successful_purchase - assert response = @gateway.purchase(@amount, @credit_card, @options) + def test_successful_purchase_with_billing_address + response = @gateway.purchase(@amount, @credit_card, @options) + + assert_success response + assert_equal 'Transaction Approved Successful', response.message + + customer = response.params['Customer'] + + assert_address_match(customer, @options[:billing_address]) + end + + def test_successful_purchase_with_address + @options[:billing_address] = nil + @options[:address] = address + + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response - assert_equal "Transaction Approved", response.message + assert_equal 'Transaction Approved Successful', response.message + + customer = response.params['Customer'] + + assert_address_match(customer, @options[:address]) + end + + def test_successful_purchase_without_address + email = 'test@example.com' + + @options[:billing_address] = nil + @options[:email] = email + + response = @gateway.purchase(@amount, @credit_card, @options) + + assert_success response + assert_equal 'Transaction Approved Successful', response.message + + customer = response.params['Customer'] + + assert_equal customer['FirstName'], @credit_card.first_name + assert_equal customer['LastName'], @credit_card.last_name + assert_equal customer['Email'], email + end + + def test_successful_purchase_with_shipping_address + @options[:shipping_address] = address + + response = @gateway.purchase(@amount, @credit_card, @options) + + assert_success response + assert_equal 'Transaction Approved Successful', response.message + + # eWAY Rapid does not include the shipping address in the request response, + # so we can only test that the transaction is successful. end def test_fully_loaded_purchase - assert response = @gateway.purchase(@amount, @credit_card, - :redirect_url => "http://awesomesauce.com", - :ip => "0.0.0.0", - :application_id => "Woohoo", - :description => "Description", - :order_id => "orderid1", - :currency => "AUD", - :email => "jim@example.com", - :billing_address => { - :title => "Mr.", - :name => "Jim Awesome Smith", - :company => "Awesome Co", - :address1 => "1234 My Street", - :address2 => "Apt 1", - :city => "Ottawa", - :state => "ON", - :zip => "K1C2N6", - :country => "CA", - :phone => "(555)555-5555", - :fax => "(555)555-6666" + response = @gateway.purchase(@amount, @credit_card, + redirect_url: 'http://awesomesauce.com', + ip: '0.0.0.0', + application_id: 'Woohoo', + partner_id: 'Woohoo', + transaction_type: 'Purchase', + description: 'Description', + order_id: 'orderid1', + invoice: 'I1234', + currency: 'AUD', + email: 'jim@example.com', + billing_address: { + title: 'Mr.', + name: 'Jim Awesome Smith', + company: 'Awesome Co', + address1: '1234 My Street', + address2: 'Apt 1', + city: 'Ottawa', + state: 'ON', + zip: 'K1C2N6', + country: 'CA', + phone: '(555)555-5555', + fax: '(555)555-6666' }, - :shipping_address => { - :title => "Ms.", - :name => "Baker", - :company => "Elsewhere Inc.", - :address1 => "4321 Their St.", - :address2 => "Apt 2", - :city => "Chicago", - :state => "IL", - :zip => "60625", - :country => "US", - :phone => "1115555555", - :fax => "1115556666" + shipping_address: { + title: 'Ms.', + name: 'Baker', + company: 'Elsewhere Inc.', + address1: '4321 Their St.', + address2: 'Apt 2', + city: 'Chicago', + state: 'IL', + zip: '60625', + country: 'US', + phone: '1115555555', + fax: '1115556666' } ) assert_success response end + def test_successful_purchase_with_overly_long_fields + options = { + order_id: 'OrderId must be less than 50 characters otherwise it fails', + invoice: 'Max 12 chars', + description: 'EWay Rapid transactions fail if the description is more than 64 characters.', + billing_address: { + address1: 'The Billing Address 1 Cannot Be More Than Fifty Characters.', + address2: 'The Billing Address 2 Cannot Be More Than Fifty Characters.', + city: 'TheCityCannotBeMoreThanFiftyCharactersOrItAllFallsApart', + }, + shipping_address: { + address1: 'The Shipping Address 1 Cannot Be More Than Fifty Characters.', + address2: 'The Shipping Address 2 Cannot Be More Than Fifty Characters.', + city: 'TheCityCannotBeMoreThanFiftyCharactersOrItAllFallsApart', + } + } + @credit_card.first_name = 'FullNameOnACardMustBeLessThanFiftyCharacters' + @credit_card.last_name = 'LastName' + + response = @gateway.purchase(@amount, @credit_card, options) + assert_success response + assert_equal 'Transaction Approved Successful', response.message + end + def test_failed_purchase - assert response = @gateway.purchase(@failed_amount, @credit_card, @options) + response = @gateway.purchase(@failed_amount, @credit_card, @options) assert_failure response - assert_equal "Do Not Honour", response.message + assert_equal 'Invalid Payment TotalAmount', response.message end - def test_failed_setup_purchase - assert response = @gateway.setup_purchase(@amount, :redirect_url => "") + def test_successful_authorize_with_billing_address + response = @gateway.authorize(@amount, @credit_card, @options) + + assert_success response + assert_equal 'Transaction Approved Successful', response.message + + customer = response.params['Customer'] + + assert_address_match(customer, @options[:billing_address]) + end + + def test_successful_authorize_with_address + @options[:billing_address] = nil + @options[:address] = address + + response = @gateway.authorize(@amount, @credit_card, @options) + + assert_success response + assert_equal 'Transaction Approved Successful', response.message + + customer = response.params['Customer'] + + assert_address_match(customer, @options[:address]) + end + + def test_successful_authorize_without_address + email = 'test@example.com' + + @options[:billing_address] = nil + @options[:email] = email + + response = @gateway.authorize(@amount, @credit_card, @options) + + assert_success response + assert_equal 'Transaction Approved Successful', response.message + + customer = response.params['Customer'] + + assert_equal customer['FirstName'], @credit_card.first_name + assert_equal customer['LastName'], @credit_card.last_name + assert_equal customer['Email'], email + end + + def test_successful_authorize_with_shipping_address + @options[:shipping_address] = address + + response = @gateway.authorize(@amount, @credit_card, @options) + + assert_success response + assert_equal 'Transaction Approved Successful', response.message + + # eWAY Rapid does not include the shipping address in the request response, + # so we can only test that the transaction is successful. + end + + def test_successful_authorize_and_capture + authorize = @gateway.authorize(@amount, @credit_card, @options) + assert_success authorize + assert_equal 'Transaction Approved Successful', authorize.message + + capture = @gateway.capture(nil, authorize.authorization) + assert_success capture + end + + def test_failed_authorize + response = @gateway.authorize(@failed_amount, @credit_card, @options) assert_failure response - assert_equal "V6047", response.message + assert_equal '', response.message end - def test_failed_run_purchase - setup_response = @gateway.setup_purchase(@amount, @options) - assert_success setup_response + def test_failed_capture + response = @gateway.capture(@amount, 'bogus') + assert_failure response + assert_equal 'Invalid Auth Transaction ID for Capture/Void', response.message + end + + def test_successful_void + authorize = @gateway.authorize(@amount, @credit_card, @options) + assert_success authorize + + void = @gateway.void(authorize.authorization) + assert_success void + end - assert response = @gateway.send(:run_purchase, "bogus", @credit_card, setup_response.params["formactionurl"]) + def test_failed_void + response = @gateway.void('bogus') assert_failure response - assert_match(%r{Not Found}, response.message) + assert_equal 'Failed', response.message end - def test_failed_status - setup_response = @gateway.setup_purchase(@failed_amount, @options) - assert_success setup_response + def test_successful_refund + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal 'Transaction Approved Successful', response.message - assert run_response = @gateway.send(:run_purchase, setup_response.authorization, @credit_card, setup_response.params["formactionurl"]) - assert_success run_response + response = @gateway.refund(@amount, response.authorization, @options) + assert_success response + assert_equal 'Transaction Approved Successful', response.message + end - response = @gateway.status(run_response.authorization) + def test_failed_refund + response = @gateway.refund(@amount, 'fakeid', @options) assert_failure response - assert_equal "Do Not Honour", response.message - assert_equal run_response.authorization, response.authorization + assert_equal 'Invalid DirectRefundRequest, Transaction ID', response.message end def test_successful_store - @options[:billing_address].merge!(:title => "Dr.") - assert response = @gateway.store(@credit_card, @options) + response = @gateway.store(@credit_card, @options) + assert_success response + assert_equal 'Transaction Approved Successful', response.message + + customer = response.params['Customer'] + + assert_address_match(customer, @options[:billing_address]) + end + + def test_successful_store_with_shipping_address + @options[:shipping_address] = address + + response = @gateway.store(@credit_card, @options) + assert_success response - assert_equal "Transaction Approved", response.message + assert_equal 'Transaction Approved Successful', response.message + + # eWAY Rapid does not include the shipping address in the request response, + # so we can only test that the transaction is successful. end def test_failed_store - @options[:billing_address].merge!(:country => nil) - assert response = @gateway.store(@credit_card, @options) + @options[:billing_address][:country] = nil + response = @gateway.store(@credit_card, @options) assert_failure response - assert_equal "V6045,V6044", response.message + assert_equal 'V6044', response.params['Errors'] + assert_equal 'Customer CountryCode Required', response.message + end + + def test_successful_update_with_billing_address + response = @gateway.store(@credit_card, @options) + assert_success response + assert_equal 'Transaction Approved Successful', response.message + response = @gateway.update(response.authorization, @credit_card, @options) + assert_success response + assert_equal 'Transaction Approved Successful', response.message + + customer = response.params['Customer'] + assert_address_match(customer, @options[:billing_address]) + end + + def test_successful_update_with_address + @options[:billing_address] = nil + @options[:address] = address + + response = @gateway.store(@credit_card, @options) + assert_success response + assert_equal 'Transaction Approved Successful', response.message + + response = @gateway.update(response.authorization, @credit_card, @options) + + assert_success response + assert_equal 'Transaction Approved Successful', response.message + + customer = response.params['Customer'] + + assert_address_match(customer, @options[:address]) + end + + def test_successful_update_without_address + email = 'test@example.com' + @options[:email] = email + + response = @gateway.store(@credit_card, @options) + assert_success response + assert_equal 'Transaction Approved Successful', response.message + + @options[:billing_address] = nil + + response = @gateway.update(response.authorization, @credit_card, @options) + + assert_success response + assert_equal 'Transaction Approved Successful', response.message + + customer = response.params['Customer'] + + assert_equal customer['FirstName'], @credit_card.first_name + assert_equal customer['LastName'], @credit_card.last_name + assert_equal customer['Email'], email + end + + def test_successful_update_with_shipping_address + @options[:shipping_address] = address + + response = @gateway.store(@credit_card, @options) + assert_success response + assert_equal 'Transaction Approved Successful', response.message + + response = @gateway.update(response.authorization, @credit_card, @options) + + assert_success response + assert_equal 'Transaction Approved Successful', response.message + + # eWAY Rapid does not include the shipping address in the request response, + # so we can only test that the transaction is successful. + end + + def test_successful_store_purchase + response = @gateway.store(@credit_card, @options) + assert_success response + assert_equal 'Transaction Approved Successful', response.message + + response = @gateway.purchase(@amount, response.authorization, transaction_type: 'MOTO') + assert_success response + assert_equal 'Transaction Approved Successful', response.message end def test_invalid_login gateway = EwayRapidGateway.new( - :login => "bogus", - :password => "bogus" - ) - assert response = gateway.purchase(@amount, @credit_card, @options) + login: 'bogus', + password: 'bogus' + ) + response = gateway.purchase(@amount, @credit_card, @options) assert_failure response - assert_equal "Unauthorized", response.message + assert_equal 'Unauthorized', response.message + end + + def test_transcript_scrubbing + credit_card_success = credit_card('4444333322221111', verification_value: 976225) + + transcript = capture_transcript(@gateway) do + @gateway.purchase(100, credit_card_success, {}) + end + + clean_transcript = @gateway.scrub(transcript) + + assert_scrubbed(credit_card_success.number, clean_transcript) + assert_scrubbed(credit_card_success.verification_value.to_s, clean_transcript) + end + + private + + def assert_address_match(customer, address) + assert_equal customer['FirstName'], address[:name].split[0] + assert_equal customer['LastName'], address[:name].split[1] + assert_equal customer['CompanyName'], address[:company] + assert_equal customer['Street1'], address[:address1] + assert_equal customer['Street2'], address[:address2] + assert_equal customer['City'], address[:city] + assert_equal customer['State'], address[:state] + assert_equal customer['PostalCode'], address[:zip] + assert_equal customer['Country'], address[:country].to_s.downcase + assert_equal customer['Phone'], address[:phone] + assert_equal customer['Fax'], address[:fax] end end diff --git a/test/remote/gateways/remote_eway_test.rb b/test/remote/gateways/remote_eway_test.rb index af0396d0124..a8d7d583874 100644 --- a/test/remote/gateways/remote_eway_test.rb +++ b/test/remote/gateways/remote_eway_test.rb @@ -17,7 +17,7 @@ def setup :state => 'WA', :country => 'AU', :zip => '2000' - } , + }, :description => 'purchased items' } end @@ -26,7 +26,7 @@ def test_invalid_amount assert response = @gateway.purchase(101, @credit_card_success, @params) assert_failure response assert response.test? - assert_equal EwayGateway::MESSAGES["01"], response.message + assert_equal EwayGateway::MESSAGES['01'], response.message end def test_purchase_success_with_verification_value @@ -34,7 +34,7 @@ def test_purchase_success_with_verification_value assert response.authorization assert_success response assert response.test? - assert_equal EwayGateway::MESSAGES["00"], response.message + assert_equal EwayGateway::MESSAGES['00'], response.message end def test_purchase_success_without_verification_value @@ -44,7 +44,7 @@ def test_purchase_success_without_verification_value assert response.authorization assert_success response assert response.test? - assert_equal EwayGateway::MESSAGES["00"], response.message + assert_equal EwayGateway::MESSAGES['00'], response.message end def test_purchase_error @@ -68,6 +68,17 @@ def test_failed_refund assert response = @gateway.refund(200, response.authorization) assert_failure response - assert_match /Error.*Your refund could not be processed./, response.message + assert_match %r{Error.*Your refund could not be processed.}, response.message + end + + def test_transcript_scrubbing + @credit_card_success.verification_value = '431' + transcript = capture_transcript(@gateway) do + @gateway.purchase(100, @credit_card_success, @params) + end + clean_transcript = @gateway.scrub(transcript) + + assert_scrubbed(@credit_card_success.number, clean_transcript) + assert_scrubbed(@credit_card_success.verification_value.to_s, clean_transcript) end end diff --git a/test/remote/gateways/remote_exact_test.rb b/test/remote/gateways/remote_exact_test.rb index 2b9be676c8f..8843e170d74 100644 --- a/test/remote/gateways/remote_exact_test.rb +++ b/test/remote/gateways/remote_exact_test.rb @@ -1,30 +1,28 @@ require 'test_helper' class RemoteExactTest < Test::Unit::TestCase - def setup - @gateway = ExactGateway.new(fixtures(:exact)) @credit_card = credit_card @amount = 100 - @options = { + @options = { :order_id => '1', :billing_address => address, :description => 'Store Purchase' } end - + def test_successful_purchase assert response = @gateway.purchase(@amount, @credit_card, @options) - assert_match /Transaction Normal/, response.message + assert_match %r{Transaction Normal}, response.message assert_success response end def test_unsuccessful_purchase # ask for error 13 response (Amount Error) via dollar amount 5,000 + error @amount = 501300 - assert response = @gateway.purchase(@amount, @credit_card, @options ) - assert_match /Transaction Normal/, response.message + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_match %r{Transaction Normal}, response.message assert_failure response end @@ -35,7 +33,7 @@ def test_purchase_and_credit assert credit = @gateway.credit(@amount, purchase.authorization) assert_success credit end - + def test_authorize_and_capture assert auth = @gateway.authorize(@amount, @credit_card, @options) assert_success auth @@ -43,18 +41,18 @@ def test_authorize_and_capture assert capture = @gateway.capture(@amount, auth.authorization) assert_success capture end - + def test_failed_capture - assert response = @gateway.capture(@amount, '') + assert response = @gateway.capture(@amount, 'bogus') assert_failure response - assert_match /Precondition Failed/i, response.message + assert_match %r{Invalid Transaction Tag}i, response.message end - + def test_invalid_login - gateway = ExactGateway.new( :login => "NotARealUser", - :password => "NotARealPassword" ) + gateway = ExactGateway.new(:login => 'NotARealUser', + :password => 'NotARealPassword') assert response = gateway.purchase(@amount, @credit_card, @options) - assert_equal "Invalid Logon", response.message + assert_match %r{^Invalid Login}, response.message assert_failure response end end diff --git a/test/remote/gateways/remote_ezic_test.rb b/test/remote/gateways/remote_ezic_test.rb new file mode 100644 index 00000000000..7aacb3f3a74 --- /dev/null +++ b/test/remote/gateways/remote_ezic_test.rb @@ -0,0 +1,119 @@ +require 'test_helper' + +class RemoteEzicTest < Test::Unit::TestCase + def setup + @gateway = EzicGateway.new(fixtures(:ezic)) + + @amount = 100 + @failed_amount = 19088 + @credit_card = credit_card('4000100011112224') + + @options = { + order_id: '1', + billing_address: address, + description: 'Store Purchase' + } + end + + def test_successful_purchase + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal 'TEST APPROVED', response.message + end + + def test_failed_purchase + response = @gateway.purchase(@failed_amount, @credit_card, @options) + assert_failure response + assert_equal 'TEST DECLINED', response.message + end + + def test_successful_authorize_and_capture + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + assert capture = @gateway.capture(@amount, auth.authorization) + assert_success capture + assert_equal 'TEST CAPTURED', capture.message + end + + def test_failed_authorize + response = @gateway.authorize(@failed_amount, @credit_card, @options) + assert_failure response + assert_equal 'TEST DECLINED', response.message + end + + def test_failed_capture + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + assert capture = @gateway.capture(@amount+30, auth.authorization) + assert_failure capture + assert_match(/Settlement amount cannot exceed authorized amount/, capture.message) + end + + def test_successful_refund + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + + assert refund = @gateway.refund(@amount, purchase.authorization) + assert_success refund + assert_equal 'TEST RETURNED', refund.message + end + + def test_partial_refund + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + + assert refund = @gateway.refund(@amount-1, purchase.authorization) + assert_success refund + assert_equal 'TEST RETURNED', refund.message + assert_equal '-0.99', refund.params['settle_amount'] + end + + def test_failed_refund + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + + assert refund = @gateway.refund(@amount + 49, purchase.authorization) + assert_failure refund + assert_match(/Amount of refunds exceed original sale/, refund.message) + end + + def test_failed_void + authorize = @gateway.authorize(@amount, @credit_card, @options) + assert_success authorize + + assert void = @gateway.void(authorize.authorization) + assert_failure void + assert_equal 'Processor/Network Error', void.message + end + + def test_successful_verify + response = @gateway.verify(@credit_card, @options) + assert_success response + assert_equal 'TEST APPROVED', response.message + end + + def test_failed_verify + response = @gateway.verify(credit_card(''), @options) + assert_failure response + assert_match %r{Missing card or check number}, response.message + end + + def test_transcript_scrubbing + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @credit_card, @options) + end + transcript = @gateway.scrub(transcript) + + assert_scrubbed(@credit_card.number, transcript) + assert_scrubbed(@credit_card.verification_value, transcript) + end + + def test_invalid_login + gateway = EzicGateway.new(account_id: '11231') + response = gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_match(/Invalid account number/, response.message) + end +end diff --git a/test/remote/gateways/remote_fat_zebra_test.rb b/test/remote/gateways/remote_fat_zebra_test.rb index 13515c43936..28943103a33 100644 --- a/test/remote/gateways/remote_fat_zebra_test.rb +++ b/test/remote/gateways/remote_fat_zebra_test.rb @@ -10,7 +10,7 @@ def setup @options = { :order_id => rand(100000).to_s, - :ip => "123.1.2.3" + :ip => '1.2.3.4' } end @@ -20,40 +20,123 @@ def test_successful_purchase assert_equal 'Approved', response.message end - def test_unsuccessful_purchase - assert response = @gateway.purchase(@amount, @declined_card, @options) + def test_successful_multi_currency_purchase + assert response = @gateway.purchase(@amount, @credit_card, @options.merge(:currency => 'USD')) + assert_success response + assert_equal 'Approved', response.message + assert_equal 'USD', response.params['response']['currency'] + end + + def test_unsuccessful_multi_currency_purchase + assert response = @gateway.purchase(@amount, @credit_card, @options.merge(:currency => 'XYZ')) assert_failure response - assert_equal 'Declined', response.message + assert_match(/Currency XYZ is not valid for this merchant/, response.message) + end + + def test_successful_purchase_sans_cvv + @credit_card.verification_value = nil + assert response = @gateway.purchase(@amount, @credit_card, recurring: true) + assert_success response + assert_equal 'Approved', response.message + end + + def test_failed_purchase_sans_cvv + @credit_card.verification_value = nil + assert response = @gateway.purchase(@amount, @credit_card) + assert_failure response + assert_equal 'CVV is required', response.message + end + + def test_successful_purchase_with_no_options + assert response = @gateway.purchase(@amount, @credit_card) + assert_success response + assert_equal 'Approved', response.message + end + + def test_successful_authorize_and_capture + assert auth_response = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth_response + assert_equal 'Approved', auth_response.message + + assert capture_response = @gateway.capture(@amount, auth_response.authorization, @options) + assert_success capture_response + assert_equal 'Approved', capture_response.message end - def test_invalid_data - @options.delete(:ip) + def test_multi_currency_authorize_and_capture + assert auth_response = @gateway.authorize(@amount, @credit_card, @options.merge(:currency => 'USD')) + assert_success auth_response + assert_equal 'Approved', auth_response.message + assert_equal 'USD', auth_response.params['response']['currency'] + + assert capture_response = @gateway.capture(@amount, auth_response.authorization, @options.merge(:currency => 'USD')) + assert_success capture_response + assert_equal 'Approved', capture_response.message + assert_equal 'USD', capture_response.params['response']['currency'] + end + + def test_successful_partial_capture + assert auth_response = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth_response + assert_equal 'Approved', auth_response.message + + assert capture_response = @gateway.capture(@amount - 1, auth_response.authorization, @options) + assert_success capture_response + assert_equal 'Approved', capture_response.message + assert_equal @amount - 1, capture_response.params['response']['captured_amount'] + end + + def test_unsuccessful_purchase assert response = @gateway.purchase(@amount, @declined_card, @options) assert_failure response - assert_equal "Customer ip can't be blank", response.message + assert_equal 'Declined', response.message end def test_refund - purchase = @gateway.purchase(@amount, @credit_card, @options) + assert purchase = @gateway.purchase(@amount, @credit_card, @options) - assert response = @gateway.refund(@amount, purchase.authorization, rand(1000000).to_s) + assert response = @gateway.refund(@amount, purchase.authorization, @options) assert_success response - assert_match /Approved/, response.message + assert_match %r{Approved}, response.message end def test_invalid_refund + @gateway.purchase(@amount, @credit_card, @options) + + assert response = @gateway.refund(@amount, '', @options) + assert_failure response + assert_match %r{Invalid credit card for unmatched refund}, response.message + end + + def test_successful_void + auth = @gateway.authorize(@amount, @credit_card, @options) + + assert response = @gateway.void(auth.authorization, @options) + assert_success response + assert_match %r{Voided}, response.message + end + + def test_successful_void_refund purchase = @gateway.purchase(@amount, @credit_card, @options) + puts purchase.inspect + refund = @gateway.refund(@amount, purchase.authorization, @options) - assert response = @gateway.refund(@amount, nil, rand(1000000).to_s) + assert response = @gateway.void(refund.authorization, @options) + assert_success response + assert_match %r{Voided}, response.message + end + + def test_failed_void + assert response = @gateway.void('123', @options) assert_failure response - assert_match /Original transaction is required/, response.message + assert_match %r{Not Found}, response.message end def test_store assert card = @gateway.store(@credit_card) assert_success card - assert_false card.authorization.nil? + assert_not_nil card.authorization end def test_purchase_with_token @@ -62,6 +145,31 @@ def test_purchase_with_token assert_success purchase end + def test_successful_purchase_with_descriptor + assert response = @gateway.purchase(@amount, @credit_card, @options.merge(:merchant => 'Merchant', :merchant_location => 'Location')) + assert_success response + assert_equal 'Approved', response.message + end + + def test_successful_purchase_with_metadata + assert response = @gateway.purchase(@amount, @credit_card, @options.merge(:metadata => { :description => 'Invoice #1234356' })) + assert_success response + assert_equal 'Approved', response.message + assert_equal 'Invoice #1234356', response.params['response']['metadata']['description'] + end + + def test_successful_purchase_with_3DS_information + assert response = @gateway.purchase(@amount, @credit_card, @options.merge(:cavv => 'MDRjN2MxZTAxYjllNTBkNmM2MTA=', :xid => 'MGVmMmNlMzI4NjAyOWU2ZDgwNTQ=', :sli => '05')) + assert_success response + assert_equal 'Approved', response.message + end + + def test_failed_purchase_with_incomplete_3DS_information + assert response = @gateway.purchase(@amount, @credit_card, @options.merge(:cavv => 'MDRjN2MxZTAxYjllNTBkNmM2MTA=', :sli => '05')) + assert_failure response + assert_match %r{Extra/xid is required for SLI 05}, response.message + end + def test_invalid_login gateway = FatZebraGateway.new( :username => 'invalid', @@ -71,4 +179,14 @@ def test_invalid_login assert_failure response assert_equal 'Invalid Login', response.message end + + def test_transcript_scrubbing + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @credit_card, @options) + end + transcript = @gateway.scrub(transcript) + + assert_scrubbed(@credit_card.number, transcript) + assert_scrubbed(@credit_card.verification_value, transcript) + end end diff --git a/test/remote/gateways/remote_federated_canada_test.rb b/test/remote/gateways/remote_federated_canada_test.rb index b39c9dd846c..3b48d1795af 100644 --- a/test/remote/gateways/remote_federated_canada_test.rb +++ b/test/remote/gateways/remote_federated_canada_test.rb @@ -10,8 +10,8 @@ def setup @credit_card = credit_card('4111111111111111') # Visa - @options = { - :order_id => ActiveMerchant::Utils.generate_unique_id, + @options = { + :order_id => generate_unique_id, :billing_address => address, :description => 'Active Merchant Remote Test Purchase' } @@ -21,61 +21,57 @@ def test_gateway_should_exist assert @gateway end - def test_validity_of_credit_card - assert @credit_card.valid? - end - def test_successful_purchase assert response = @gateway.purchase(@amount, @credit_card, @options) assert_success response - assert_equal "Transaction Approved", response.message + assert_equal 'Transaction Approved', response.message end def test_unsuccessful_purchase assert response = @gateway.purchase(@declined_amount, @credit_card, @options) assert_failure response - assert_equal "Transaction Declined", response.message + assert_equal 'Transaction Declined', response.message end def test_successful_authorization assert response = @gateway.authorize(@amount, @credit_card, @options) assert_success response - assert_equal "Transaction Approved", response.message + assert_equal 'Transaction Approved', response.message end def test_failed_capture assert response = @gateway.capture(@amount, '') assert_failure response - assert_equal "Error in transaction data or system error", response.message + assert_equal 'Error in transaction data or system error', response.message end def test_purchase_and_refund assert auth = @gateway.purchase(@amount, @credit_card, @options) assert_success auth - assert_equal "Transaction Approved", auth.message + assert_equal 'Transaction Approved', auth.message assert auth.authorization assert capture = @gateway.refund(@amount, auth.authorization) - assert_equal "Transaction Approved", capture.message + assert_equal 'Transaction Approved', capture.message assert_success capture end def test_authorize_and_void assert auth = @gateway.authorize(@amount, @credit_card, @options) assert_success auth - assert_equal "Transaction Approved", auth.message + assert_equal 'Transaction Approved', auth.message assert auth.authorization assert capture = @gateway.void(auth.authorization) - assert_equal "Transaction Approved", capture.message + assert_equal 'Transaction Approved', capture.message assert_success capture end def test_authorize_and_capture assert auth = @gateway.authorize(@amount, @credit_card, @options) assert_success auth - assert_equal "Transaction Approved", auth.message + assert_equal 'Transaction Approved', auth.message assert auth.authorization assert capture = @gateway.capture(@amount, auth.authorization) - assert_equal "Transaction Approved", capture.message + assert_equal 'Transaction Approved', capture.message assert_success capture end @@ -86,6 +82,6 @@ def test_invalid_login ) assert response = gateway.purchase(@amount, @credit_card, @options) assert_failure response - assert_equal "Error in transaction data or system error", response.message + assert_equal 'Error in transaction data or system error', response.message end end diff --git a/test/remote/gateways/remote_finansbank_test.rb b/test/remote/gateways/remote_finansbank_test.rb index c5b80782b86..753df513fe9 100644 --- a/test/remote/gateways/remote_finansbank_test.rb +++ b/test/remote/gateways/remote_finansbank_test.rb @@ -1,21 +1,18 @@ +# encoding: utf-8 + require 'test_helper' class RemoteFinansbankTest < Test::Unit::TestCase def setup - if RUBY_VERSION < '1.9' && $KCODE == "NONE" - @original_kcode = $KCODE - $KCODE = 'u' - end - @gateway = FinansbankGateway.new(fixtures(:finansbank)) @amount = 100 - @credit_card = credit_card('4000100011112224') + @credit_card = credit_card('4022774022774026', month: 12, year: 14, verification_value: '000') @declined_card = credit_card('4000300011112220') @options = { - :order_id => '#' + ActiveMerchant::Utils.generate_unique_id, + :order_id => '#' + generate_unique_id, :billing_address => address, :description => 'Store Purchase', :email => 'xyz@gmail.com' @@ -29,6 +26,14 @@ def teardown def test_successful_purchase assert response = @gateway.purchase(@amount, @credit_card, @options) assert_success response + assert_not_nil response.params['order_id'] + assert_not_nil response.params['response'] + assert_not_nil response.params['auth_code'] + assert_not_nil response.params['trxdate'] + assert_not_nil response.params['numcode'] + assert_not_nil response.params['trans_id'] + assert_nil response.params['errorcode'] + assert_equal response.params['order_id'], response.authorization end def test_unsuccessful_purchase @@ -51,6 +56,37 @@ def test_failed_capture assert_failure response end + def test_credit + assert response = @gateway.credit(@amount, @credit_card) + assert_success response + end + + def test_successful_refund + assert response = @gateway.purchase(@amount, @credit_card, @options) + + assert void = @gateway.refund(@amount, response.authorization) + assert_success void + assert_equal response.params['order_id'], void.params['order_id'] + end + + def test_unsuccessful_refund + assert response = @gateway.purchase(@amount, @credit_card, @options) + + assert void = @gateway.refund(@amount + 100, response.authorization) + assert_failure void + assert_nil void.params['order_id'] + assert_equal 'Declined (Reason: 99 - Net miktardan fazlasi iade edilemez.)', void.message + assert_equal 'CORE-2503', void.params['errorcode'] + end + + def test_void + assert response = @gateway.authorize(@amount, @credit_card, @options) + + assert void = @gateway.void(response.authorization) + assert_success void + assert_equal response.params['order_id'], void.params['order_id'] + end + def test_invalid_login gateway = FinansbankGateway.new( :login => '', @@ -59,5 +95,7 @@ def test_invalid_login ) assert response = gateway.purchase(@amount, @credit_card, @options) assert_failure response + assert_equal 'Declined (Reason: 99 - System based initialization problem. Please try again later.)', response.message + assert_equal '2100', response.params['errorcode'] end end diff --git a/test/remote/gateways/remote_first_giving_test.rb b/test/remote/gateways/remote_first_giving_test.rb new file mode 100644 index 00000000000..aa37014c925 --- /dev/null +++ b/test/remote/gateways/remote_first_giving_test.rb @@ -0,0 +1,58 @@ +require 'test_helper' + +class RemoteFirstGivingTest < Test::Unit::TestCase + + def setup + @gateway = FirstGivingGateway.new(fixtures(:first_giving)) + + @amount = 100 + @credit_card = credit_card('4457010000000009') + @declined_card = credit_card('445701000000000') + + @options = { + billing_address: address(state: 'MA', zip: '01803', country: 'US'), + ip: '127.0.0.1' + } + end + + def test_successful_purchase + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal 'Success', response.message + end + + def test_failed_purchase + assert response = @gateway.purchase(@amount, @declined_card, @options) + assert_failure response + assert_equal( + 'Unfortunately, we were unable to perform credit card number validation. The credit card number validator responded with the following message ccNumber failed data validation for the following reasons : creditcardLength: 445701000000000 contains an invalid amount of digits.', + response.message, + response.params.inspect + ) + end + + def test_successful_refund + assert purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + + assert response = @gateway.refund(@amount, purchase.authorization) + assert_equal 'REFUND_REQUESTED_AWAITING_REFUND', response.message + end + + def test_failed_refund + assert response = @gateway.refund(@amount, '1234') + assert_failure response + assert_equal 'An error occurred. Please check your input and try again.', response.message + end + + def test_invalid_login + gateway = FirstGivingGateway.new( + application_key: '25151616', + security_token: '63131jnkj', + charity_id: '1234' + ) + assert response = gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_equal 'An error occurred. Please check your input and try again.', response.message + end +end diff --git a/test/remote/gateways/remote_first_pay_test.rb b/test/remote/gateways/remote_first_pay_test.rb index 6baab89c057..6e174f76976 100644 --- a/test/remote/gateways/remote_first_pay_test.rb +++ b/test/remote/gateways/remote_first_pay_test.rb @@ -3,85 +3,130 @@ class RemoteFirstPayTest < Test::Unit::TestCase def setup @gateway = FirstPayGateway.new(fixtures(:first_pay)) - + @amount = 100 - @credit_card = credit_card('4111111111111111', {:first_name => 'Test', :last_name => 'Person'}) - @declined_card = credit_card('4111111111111111') - - @options = { - :order_id => '1', - :billing_address => address({:name => 'Test Person', :city => 'New York', :state => 'NY', :zip => '10002', :country => 'US'}), - :description => 'Test Purchase' + @credit_card = credit_card('4111111111111111') + @declined_card = credit_card('4000300011112220') + + @options = { + order_id: SecureRandom.hex(24), + billing_address: address, + description: 'Store Purchase' } end - + def test_successful_purchase - assert response = @gateway.purchase(@amount, @credit_card, @options) + response = @gateway.purchase(@amount, @credit_card, @options) assert_success response - assert_equal('CAPTURED', response.message) + assert_equal 'Approved', response.message end - def test_unsuccessful_purchase - # > $500 results in decline - @amount = 51000 - assert response = @gateway.purchase(@amount, @declined_card, @options) + def test_failed_purchase + response = @gateway.purchase(@amount, @declined_card, @options) assert_failure response - assert_equal("51-INSUFFICIENT FUNDS", response.message) + assert_equal 'Declined', response.message end - - def test_invalid_login - gateway = FirstPayGateway.new(:login => '', :password => '') - assert response = gateway.purchase(@amount, @credit_card, @options) + + def test_failed_purchase_with_no_address + @options.delete(:billing_address) + response = @gateway.purchase(@amount, @credit_card, @options) + assert_failure response - assert_equal '703-INVALID VENDOR ID AND PASS CODE', response.message + assert_equal 'Address is invalid (street, city, zip, state and or country fields)', response.message end - - def test_successful_credit - # purchase first - assert response = @gateway.purchase(@amount, @credit_card, @options) - assert_success response - assert_equal('CAPTURED', response.message) - assert_not_nil(response.params["auth"]) - assert_not_nil(response.authorization) - - @options[:credit_card] = @credit_card - - assert response = @gateway.credit(@amount, response.authorization, @options) - assert_success response - assert_not_nil(response.authorization) + + def test_successful_authorize_and_capture + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + assert capture = @gateway.capture(@amount, auth.authorization) + assert_success capture + end + + def test_failed_authorize + response = @gateway.authorize(@amount, @declined_card, @options) + assert_failure response end - - def test_failed_credit - @options[:credit_card] = @credit_card - - assert response = @gateway.credit(@amount, '000000', @options) + + def test_failed_capture + response = @gateway.capture(@amount, '1234') assert_failure response - assert_nil(response.authorization) - assert_equal('PARENT TRANSACTION NOT FOUND', response.message) end - - def test_failed_unlinked_credit - assert_raise ArgumentError do - @gateway.credit(@amount, @credit_card) - end + + def test_successful_refund + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + assert capture = @gateway.capture(@amount, auth.authorization) + assert_success capture + + # Not sure why purchase tx is not refundable?? + # purchase = @gateway.purchase(@amount, @credit_card, @options) + # assert_success purchase + + assert refund = @gateway.refund(@amount, capture.authorization) + assert_success refund end - + + def test_partial_refund + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + assert capture = @gateway.capture(@amount, auth.authorization) + assert_success capture + + # Not sure why purchase tx is not refundable?? + # purchase = @gateway.purchase(@amount, @credit_card, @options) + # assert_success purchase + + assert refund = @gateway.refund(@amount / 2, capture.authorization) + assert_success refund + end + + def test_failed_refund + response = @gateway.refund(@amount, '1234') + assert_failure response + end + def test_successful_void - # purchase first - assert response = @gateway.purchase(@amount, @credit_card, @options) - assert_success response - assert_equal('CAPTURED', response.message) - assert_not_nil(response.params["auth"]) - assert_not_nil(response.authorization) - - assert_success response - assert_not_nil(response.authorization) + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + assert void = @gateway.void(auth.authorization) + assert_success void end - - def test_failed_void - assert response = @gateway.void(@amount, @credit_card, @options) + + def test_failed_void + response = @gateway.void('1') assert_failure response - assert_equal('PARENT TRANSACTION NOT FOUND', response.message) end - + + def test_invalid_login + gateway = FirstPayGateway.new( + transaction_center_id: '1234', + gateway_id: 'abcd' + ) + response = gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_match(/Merchant: 1234 has encountered error #DTO-200-TC./, response.error_code) + end + + def test_recurring_payment + @options.merge!({recurring: 1, recurring_start_date: DateTime.now.strftime('%m/%d/%Y'), recurring_end_date: DateTime.now.strftime('%m/%d/%Y'), recurring_type: 'monthly'}) + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal 'Approved', response.message + end + + def test_transcript_scrubbing + @credit_card.verification_value = 789 + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @credit_card, @options) + end + transcript = @gateway.scrub(transcript) + + assert_scrubbed(@credit_card.number, transcript) + assert_scrubbed(@credit_card.verification_value, transcript) + assert_scrubbed(@gateway.options[:gateway_id], transcript) + end end diff --git a/test/remote/gateways/remote_firstdata_e4_test.rb b/test/remote/gateways/remote_firstdata_e4_test.rb old mode 100644 new mode 100755 index 6b402bb2968..f7d88e60197 --- a/test/remote/gateways/remote_firstdata_e4_test.rb +++ b/test/remote/gateways/remote_firstdata_e4_test.rb @@ -5,12 +5,18 @@ def setup @gateway = FirstdataE4Gateway.new(fixtures(:firstdata_e4)) @credit_card = credit_card @bad_credit_card = credit_card('4111111111111113') + @credit_card_with_track_data = credit_card_with_track_data('4003000123456781') @amount = 100 @options = { :order_id => '1', :billing_address => address, :description => 'Store Purchase' } + @options_with_authentication_data = @options.merge({ + eci: '5', + cavv: 'TESTCAVV', + xid: 'TESTXID' + }) end def test_successful_purchase @@ -19,10 +25,71 @@ def test_successful_purchase assert_success response end + def test_successful_purchase_with_network_tokenization + @credit_card = network_tokenization_credit_card('4242424242424242', + payment_cryptogram: 'BwABB4JRdgAAAAAAiFF2AAAAAAA=', + verification_value: nil + ) + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal 'Transaction Normal - Approved', response.message + assert_false response.authorization.blank? + end + + def test_successful_purchase_with_specified_currency + options_with_specified_currency = @options.merge({currency: 'GBP'}) + assert response = @gateway.purchase(@amount, @credit_card, options_with_specified_currency) + assert_match(/Transaction Normal/, response.message) + assert_success response + assert_equal 'GBP', response.params['currency'] + end + + def test_successful_purchase_with_track_data + assert response = @gateway.purchase(@amount, @credit_card_with_track_data, @options) + assert_match(/Transaction Normal/, response.message) + assert_success response + end + + def test_successful_purchase_with_level_3 + level_3_xml = <<-LEVEL3 + <LineItem> + <LineItemTotal>107.20</LineItemTotal> + <Quantity>3</Quantity> + <Description>The Description</Description> + <UnitCost>2.33</UnitCost> + </LineItem> + LEVEL3 + + response = @gateway.purchase(500, @credit_card, @options.merge(level_3: level_3_xml)) + assert_success response + assert_equal 'Transaction Normal - Approved', response.message + end + + def test_successful_purchase_with_tax_fields + response = @gateway.purchase(500, @credit_card, @options.merge(tax1_amount: 50, tax1_number: 'A458')) + assert_success response + assert_equal '50.0', response.params['tax1_amount'] + assert_equal '', response.params['tax1_number'], 'E4 blanks this out in the response' + end + + def test_successful_purchase_with_customer_ref + response = @gateway.purchase(500, @credit_card, @options.merge(customer: '267')) + assert_success response + assert_equal '267', response.params['customer_ref'] + end + + def test_successful_purchase_with_card_authentication + assert response = @gateway.purchase(@amount, @credit_card, @options_with_authentication_data) + assert_equal response.params['cavv'], @options_with_authentication_data[:cavv] + assert_equal response.params['ecommerce_flag'], @options_with_authentication_data[:eci] + assert_equal response.params['xid'], @options_with_authentication_data[:xid] + assert_success response + end + def test_unsuccessful_purchase # ask for error 13 response (Amount Error) via dollar amount 5,000 + error @amount = 501300 - assert response = @gateway.purchase(@amount, @credit_card, @options ) + assert response = @gateway.purchase(@amount, @credit_card, @options) assert_match(/Transaction Normal/, response.message) assert_failure response end @@ -31,14 +98,16 @@ def test_bad_creditcard_number assert response = @gateway.purchase(@amount, @bad_credit_card, @options) assert_match(/Invalid Credit Card/, response.message) assert_failure response + assert_equal response.error_code, 'invalid_number' end def test_trans_error # ask for error 42 (unable to send trans) as the cents bit... @amount = 500042 - assert response = @gateway.purchase(@amount, @credit_card, @options ) + assert response = @gateway.purchase(@amount, @credit_card, @options) assert_match(/Unable to Send Transaction/, response.message) # 42 is 'unable to send trans' assert_failure response + assert_equal response.error_code, 'processing_error' end def test_purchase_and_credit @@ -49,9 +118,30 @@ def test_purchase_and_credit assert_success credit end + def test_purchase_and_credit_with_specified_currency + options_with_specified_currency = @options.merge({currency: 'GBP'}) + assert purchase = @gateway.purchase(@amount, @credit_card, options_with_specified_currency) + assert_success purchase + assert purchase.authorization + assert_equal 'GBP', purchase.params['currency'] + assert credit = @gateway.refund(@amount, purchase.authorization, options_with_specified_currency) + assert_success credit + assert_equal 'GBP', credit.params['currency'] + end + def test_purchase_and_void - assert purchase = @gateway.purchase(@amount, @credit_card, @options) + assert purchase = @gateway.purchase(29234, @credit_card, @options) assert_success purchase + + assert purchase.authorization + assert void = @gateway.void(purchase.authorization) + assert_success void + end + + def test_purchase_and_void_with_even_dollar_amount + assert purchase = @gateway.purchase(5000, @credit_card, @options) + assert_success purchase + assert purchase.authorization assert void = @gateway.void(purchase.authorization) assert_success void @@ -71,19 +161,92 @@ def test_failed_capture assert_match(/Invalid Authorization Number/i, response.message) end + def test_successful_verify + assert response = @gateway.verify(@credit_card, @options) + assert_success response + + assert_equal 'Transaction Normal - Approved', response.message + assert_equal '0.0', response.params['dollar_amount'] + assert_equal '05', response.params['transaction_type'] + end + + def test_failed_verify + assert response = @gateway.verify(@bad_credit_card, @options) + assert_failure response + assert_match %r{Invalid Credit Card Number}, response.message + assert_equal response.error_code, 'invalid_number' + end + def test_invalid_login - gateway = FirstdataE4Gateway.new(:login => "NotARealUser", - :password => "NotARealPassword" ) + gateway = FirstdataE4Gateway.new(:login => 'NotARealUser', + :password => 'NotARealPassword') assert response = gateway.purchase(@amount, @credit_card, @options) - assert_match /Unauthorized Request/, response.message + assert_match %r{Unauthorized Request}, response.message assert_failure response end def test_response_contains_cvv_and_avs_results response = @gateway.purchase(@amount, @credit_card, @options) assert_success response - assert_equal 'M', response.cvv_result["code"] - assert_equal '1', response.avs_result["code"] + assert_equal 'M', response.cvv_result['code'] + assert_equal '4', response.avs_result['code'] + end + + def test_refund + assert purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_match(/Transaction Normal/, purchase.message) + assert_success purchase + + assert response = @gateway.refund(50, purchase.authorization) + assert_success response + assert_match(/Transaction Normal/, response.message) + assert response.authorization + end + + def test_refund_with_specified_currency + options_with_specified_currency = @options.merge({currency: 'GBP'}) + assert purchase = @gateway.purchase(@amount, @credit_card, options_with_specified_currency) + assert_match(/Transaction Normal/, purchase.message) + assert_success purchase + assert_equal 'GBP', purchase.params['currency'] + + assert response = @gateway.refund(50, purchase.authorization, options_with_specified_currency) + assert_success response + assert_match(/Transaction Normal/, response.message) + assert response.authorization + assert_equal 'GBP', response.params['currency'] + end + + def test_refund_with_track_data + assert purchase = @gateway.purchase(@amount, @credit_card_with_track_data, @options) + assert_match(/Transaction Normal/, purchase.message) + assert_success purchase + + assert response = @gateway.refund(50, purchase.authorization) + assert_success response + assert_match(/Transaction Normal/, response.message) + assert response.authorization + end + + def test_verify_credentials + assert @gateway.verify_credentials + + gateway = FirstdataE4Gateway.new(login: 'unknown', password: 'unknown') + assert !gateway.verify_credentials + gateway = FirstdataE4Gateway.new(login: fixtures(:firstdata_e4)[:login], password: 'unknown') + assert !gateway.verify_credentials + end + + def test_transcript_scrubbing + cc_with_different_cvc = credit_card(verification_value: '999') + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, cc_with_different_cvc, @options) + end + transcript = @gateway.scrub(transcript) + + assert_scrubbed(cc_with_different_cvc.number, transcript) + assert_scrubbed(cc_with_different_cvc.verification_value, transcript) + assert_scrubbed(@gateway.options[:password], transcript) end end diff --git a/test/remote/gateways/remote_firstdata_e4_v27_test.rb b/test/remote/gateways/remote_firstdata_e4_v27_test.rb new file mode 100644 index 00000000000..0bf4fc8c79c --- /dev/null +++ b/test/remote/gateways/remote_firstdata_e4_v27_test.rb @@ -0,0 +1,271 @@ +require 'test_helper' + +class RemoteFirstdataE4V27Test < Test::Unit::TestCase + def setup + @gateway = FirstdataE4V27Gateway.new(fixtures(:firstdata_e4_v27)) + @credit_card = credit_card + @credit_card_master = credit_card('5500000000000004', :brand => 'master') + @bad_credit_card = credit_card('4111111111111113') + @credit_card_with_track_data = credit_card_with_track_data('4003000123456781') + @amount = 100 + @options = { + :order_id => '1', + :billing_address => address, + :description => 'Store Purchase' + } + @options_with_authentication_data = @options.merge({ + eci: '5', + cavv: 'TESTCAVV', + xid: 'TESTXID' + }) + end + + def test_successful_purchase + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_match(/Transaction Normal/, response.message) + assert_success response + end + + def test_successful_purchase_with_network_tokenization + @credit_card = network_tokenization_credit_card('4242424242424242', + payment_cryptogram: 'BwABB4JRdgAAAAAAiFF2AAAAAAA=', + verification_value: nil + ) + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal 'Transaction Normal - Approved', response.message + assert_false response.authorization.blank? + end + + def test_successful_purchase_with_track_data + assert response = @gateway.purchase(@amount, @credit_card_with_track_data, @options) + assert_match(/Transaction Normal/, response.message) + assert_success response + end + + def test_successful_purchase_with_level_3 + level_3_xml = <<-LEVEL3 + <LineItem> + <LineItemTotal>107.20</LineItemTotal> + <Quantity>3</Quantity> + <Description>The Description</Description> + <UnitCost>2.33</UnitCost> + </LineItem> + LEVEL3 + + response = @gateway.purchase(500, @credit_card, @options.merge(level_3: level_3_xml)) + assert_success response + assert_equal 'Transaction Normal - Approved', response.message + end + + def test_successful_purchase_with_tax_fields + response = @gateway.purchase(500, @credit_card, @options.merge(tax1_amount: 50, tax1_number: 'A458')) + assert_success response + assert_equal '50.0', response.params['tax1_amount'] + assert_equal '', response.params['tax1_number'], 'E4 blanks this out in the response' + end + + def test_successful_purchase_with_customer_ref + response = @gateway.purchase(500, @credit_card, @options.merge(customer: '267')) + assert_success response + assert_equal '267', response.params['customer_ref'] + end + + def test_successful_purchase_with_card_authentication + assert response = @gateway.purchase(@amount, @credit_card, @options_with_authentication_data) + assert_equal response.params['cavv'], @options_with_authentication_data[:cavv] + assert_equal response.params['ecommerce_flag'], @options_with_authentication_data[:eci] + assert_equal response.params['xid'], @options_with_authentication_data[:xid] + assert_success response + end + + def test_successful_purchase_with_stored_credentials_initial + stored_credential = { + stored_credential: { + initial_transaction: true, + reason_type: 'unscheduled', + initiator: 'customer' + } + } + assert response = @gateway.purchase(@amount, @credit_card, @options.merge(stored_credential)) + assert_match(/Transaction Normal/, response.message) + assert_success response + assert_equal '1', response.params['stored_credentials_indicator'] + assert_equal 'U', response.params['stored_credentials_schedule'] + assert_not_nil response.params['stored_credentials_transaction_id'] + end + + def test_successful_purchase_with_stored_credentials_initial_master + stored_credential = { + stored_credential: { + initial_transaction: true, + reason_type: 'unscheduled', + initiator: 'customer' + } + } + assert response = @gateway.purchase(@amount, @credit_card_master, @options.merge(stored_credential)) + assert_match(/Transaction Normal/, response.message) + assert_success response + assert_equal 'S', response.params['stored_credentials_indicator'] + assert_equal 'U', response.params['stored_credentials_schedule'] + assert_not_nil response.params['stored_credentials_transaction_id'] + end + + def test_successful_purchase_with_stored_credentials_subsequent_recurring + stored_credential = { + stored_credential: { + initial_transaction: false, + reason_type: 'recurring', + initiator: 'merchant' + } + } + assert response = @gateway.purchase(@amount, @credit_card, @options.merge(stored_credential)) + assert_match(/Transaction Normal/, response.message) + assert_success response + assert_equal 'S', response.params['stored_credentials_indicator'] + assert_equal 'S', response.params['stored_credentials_schedule'] + assert_not_nil response.params['stored_credentials_transaction_id'] + end + + def test_unsuccessful_purchase + # ask for error 13 response (Amount Error) via dollar amount 5,000 + error + @amount = 501300 + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_match(/Transaction Normal/, response.message) + assert_failure response + end + + def test_bad_creditcard_number + assert response = @gateway.purchase(@amount, @bad_credit_card, @options) + assert_match(/Invalid Credit Card/, response.message) + assert_failure response + assert_equal response.error_code, 'invalid_number' + end + + def test_trans_error + # ask for error 42 (unable to send trans) as the cents bit... + @amount = 500042 + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_match(/Unable to Send Transaction/, response.message) # 42 is 'unable to send trans' + assert_failure response + assert_equal response.error_code, 'processing_error' + end + + def test_purchase_and_credit + assert purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + assert purchase.authorization + assert credit = @gateway.refund(@amount, purchase.authorization) + assert_success credit + end + + def test_purchase_and_void + assert purchase = @gateway.purchase(29234, @credit_card, @options) + assert_success purchase + + assert purchase.authorization + assert void = @gateway.void(purchase.authorization) + assert_success void + end + + def test_purchase_and_void_with_even_dollar_amount + assert purchase = @gateway.purchase(5000, @credit_card, @options) + assert_success purchase + + assert purchase.authorization + assert void = @gateway.void(purchase.authorization) + assert_success void + end + + def test_authorize_and_capture + assert auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + assert auth.authorization + assert capture = @gateway.capture(@amount, auth.authorization) + assert_success capture + end + + def test_failed_capture + assert response = @gateway.capture(@amount, 'ET838747474;frob') + assert_failure response + assert_match(/Invalid Authorization Number/i, response.message) + end + + def test_successful_verify + assert response = @gateway.verify(@credit_card, @options) + assert_success response + + assert_equal 'Transaction Normal - Approved', response.message + assert_equal '0.0', response.params['dollar_amount'] + assert_equal '05', response.params['transaction_type'] + end + + def test_failed_verify + assert response = @gateway.verify(@bad_credit_card, @options) + assert_failure response + assert_match %r{Invalid Credit Card Number}, response.message + assert_equal response.error_code, 'invalid_number' + end + + def test_invalid_login + gateway = FirstdataE4V27Gateway.new(:login => 'NotARealUser', + :password => 'NotARealPassword', + :key_id => 'NotARealKey', + :hmac_key => 'NotARealHMAC') + assert response = gateway.purchase(@amount, @credit_card, @options) + assert_match %r{Unauthorized Request}, response.message + assert_failure response + end + + def test_response_contains_cvv_and_avs_results + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal 'M', response.cvv_result['code'] + assert_equal '4', response.avs_result['code'] + end + + def test_refund + assert purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_match(/Transaction Normal/, purchase.message) + assert_success purchase + + assert response = @gateway.refund(50, purchase.authorization) + assert_success response + assert_match(/Transaction Normal/, response.message) + assert response.authorization + end + + def test_refund_with_track_data + assert purchase = @gateway.purchase(@amount, @credit_card_with_track_data, @options) + assert_match(/Transaction Normal/, purchase.message) + assert_success purchase + + assert response = @gateway.refund(50, purchase.authorization) + assert_success response + assert_match(/Transaction Normal/, response.message) + assert response.authorization + end + + def test_verify_credentials + assert @gateway.verify_credentials + + gateway = FirstdataE4V27Gateway.new(login: 'unknown', password: 'unknown', key_id: 'unknown', hmac_key: 'unknown') + assert !gateway.verify_credentials + gateway = FirstdataE4V27Gateway.new(login: fixtures(:firstdata_e4)[:login], password: 'unknown', key_id: 'unknown', hmac_key: 'unknown') + assert !gateway.verify_credentials + end + + def test_transcript_scrubbing + cc_with_different_cvc = credit_card(verification_value: '999') + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, cc_with_different_cvc, @options) + end + transcript = @gateway.scrub(transcript) + + assert_scrubbed(cc_with_different_cvc.number, transcript) + assert_scrubbed(cc_with_different_cvc.verification_value, transcript) + assert_scrubbed(@gateway.options[:password], transcript) + assert_scrubbed(@gateway.options[:hmac_key], transcript) + end + +end diff --git a/test/remote/gateways/remote_flo2cash_simple_test.rb b/test/remote/gateways/remote_flo2cash_simple_test.rb new file mode 100644 index 00000000000..f952d7c0be1 --- /dev/null +++ b/test/remote/gateways/remote_flo2cash_simple_test.rb @@ -0,0 +1,69 @@ +require 'test_helper' + +class RemoteFlo2cashSimpleTest < Test::Unit::TestCase + def setup + Base.mode = :test + + @gateway = Flo2cashSimpleGateway.new(fixtures(:flo2cash_simple)) + + @amount = 100 + @credit_card = credit_card('5123456789012346', brand: :master, month: 5, year: 2017, verification_value: 111) + @declined_card = credit_card('4000300011112220') + + @options = { + order_id: generate_unique_id, + billing_address: address, + description: 'Store Purchase' + } + end + + def test_invalid_login + gateway = Flo2cashSimpleGateway.new( + username: 'N/A', + password: 'N/A', + account_id: '100' + ) + response = gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_equal 'Authentication error. Username and/or Password are incorrect', response.message + end + + def test_successful_purchase + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal 'Succeeded', response.message + end + + def test_failed_purchase + assert response = @gateway.purchase(@amount, @declined_card, @options) + assert_failure response + assert_equal 'Transaction Declined - Bank Error', response.message + assert_equal Gateway::STANDARD_ERROR_CODE[:processing_error], response.error_code + end + + def test_successful_refund + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + + assert refund = @gateway.refund(@amount, response.authorization) + assert_success refund + assert_equal 'Succeeded', refund.message + end + + def test_failed_refund + response = @gateway.refund(@amount, '') + assert_failure response + assert_equal 'Original transaction not found', response.message + end + + def test_transcript_scrubbing + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @credit_card, @options) + end + transcript = @gateway.scrub(transcript) + + assert_scrubbed(@credit_card.number, transcript) + assert_scrubbed(@credit_card.verification_value.to_s, transcript) + assert_scrubbed(@gateway.options[:password], transcript) + end +end diff --git a/test/remote/gateways/remote_flo2cash_test.rb b/test/remote/gateways/remote_flo2cash_test.rb new file mode 100644 index 00000000000..0c0fe0972f3 --- /dev/null +++ b/test/remote/gateways/remote_flo2cash_test.rb @@ -0,0 +1,93 @@ +require 'test_helper' + +class RemoteFlo2cashTest < Test::Unit::TestCase + def setup + Base.mode = :test + + @gateway = Flo2cashGateway.new(fixtures(:flo2cash)) + + @amount = 100 + @credit_card = credit_card('5123456789012346', brand: :master, month: 5, year: 2017, verification_value: 111) + @declined_card = credit_card('4000300011112220') + + @options = { + order_id: generate_unique_id, + billing_address: address, + description: 'Store Purchase' + } + end + + def test_invalid_login + gateway = Flo2cashGateway.new( + username: 'N/A', + password: 'N/A', + account_id: '100' + ) + response = gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_equal 'Authentication error. Username and/or Password are incorrect', response.message + end + + def test_successful_purchase + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal 'Succeeded', response.message + end + + def test_failed_purchase + response = @gateway.purchase(@amount, @declined_card, @options) + assert_failure response + assert_equal 'Transaction Declined - Bank Error', response.message + assert_equal Gateway::STANDARD_ERROR_CODE[:processing_error], response.responses.first.error_code + end + + def test_successful_authorize_and_capture + response = @gateway.authorize(@amount, @credit_card, @options) + assert_success response + assert_equal 'Succeeded', response.message + assert_match %r(^\w+$), response.authorization + + assert capture = @gateway.capture(@amount, response.authorization) + assert_success capture + assert_equal 'Succeeded', capture.message + end + + def test_failed_authorize + response = @gateway.authorize(@amount, @declined_card, @options) + assert_failure response + assert_equal 'Transaction Declined - Bank Error', response.message + assert_equal Gateway::STANDARD_ERROR_CODE[:processing_error], response.error_code + end + + def test_failed_capture + response = @gateway.capture(@amount, '') + assert_failure response + assert_equal 'Original transaction not found', response.message + end + + def test_successful_refund + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + + assert refund = @gateway.refund(@amount, response.authorization) + assert_success refund + assert_equal 'Succeeded', refund.message + end + + def test_failed_refund + response = @gateway.refund(@amount, '') + assert_failure response + assert_equal 'Original transaction not found', response.message + end + + def test_transcript_scrubbing + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @credit_card, @options) + end + transcript = @gateway.scrub(transcript) + + assert_scrubbed(@credit_card.number, transcript) + assert_scrubbed(@credit_card.verification_value.to_s, transcript) + assert_scrubbed(@gateway.options[:password], transcript) + end +end diff --git a/test/remote/gateways/remote_forte_test.rb b/test/remote/gateways/remote_forte_test.rb new file mode 100644 index 00000000000..8a2df5bccc8 --- /dev/null +++ b/test/remote/gateways/remote_forte_test.rb @@ -0,0 +1,211 @@ +require 'test_helper' + +class RemoteForteTest < Test::Unit::TestCase + def setup + @gateway = ForteGateway.new(fixtures(:forte)) + + @amount = 100 + @credit_card = credit_card('4000100011112224') + @declined_card = credit_card('1111111111111111') + + @check = check + @bad_check = check({ + :name => 'Jim Smith', + :bank_name => 'Bank of Elbonia', + :routing_number => '1234567890', + :account_number => '0987654321', + :account_holder_type => '', + :account_type => 'checking', + :number => '0' + }) + + @options = { + billing_address: address, + description: 'Store Purchase', + order_id: '1' + } + end + + def test_invalid_login + gateway = ForteGateway.new(api_key: 'InvalidKey', secret: 'InvalidSecret', location_id: '11', account_id: '323') + assert response = gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_match 'combination not found.', response.message + end + + def test_successful_purchase + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal 'TEST APPROVAL', response.message + end + + def test_successful_purchase_with_echeck + response = @gateway.purchase(@amount, @check, @options) + assert_success response + assert_equal 'APPROVED', response.message + end + + def test_failed_purchase_with_echeck + response = @gateway.purchase(@amount, @bad_check, @options) + assert_failure response + assert_equal 'INVALID TRN', response.message + end + + def test_successful_purchase_with_more_options + options = { + order_id: '1', + ip: '127.0.0.1', + email: 'joe@example.com', + address: address + } + + response = @gateway.purchase(@amount, @credit_card, options) + assert_success response + assert_equal '1', response.params['order_number'] + assert_equal 'TEST APPROVAL', response.message + end + + def test_failed_purchase + response = @gateway.purchase(@amount, @declined_card, @options) + assert_failure response + assert_equal 'INVALID CREDIT CARD NUMBER', response.message + end + + def test_successful_authorize_and_capture + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + wait_for_authorization_to_clear + + assert capture = @gateway.capture(@amount, auth.authorization, @options) + assert_success capture + assert_equal 'APPROVED', capture.message + end + + def test_successful_authorize_capture_void + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + wait_for_authorization_to_clear + + assert capture = @gateway.capture(@amount, auth.authorization, @options) + assert_success capture + assert_match auth.authorization.split('#')[0], capture.authorization + assert_match auth.authorization.split('#')[1], capture.authorization + assert_equal 'APPROVED', capture.message + + void = @gateway.void(capture.authorization) + assert_success void + end + + def test_failed_authorize + @amount = 1985 + response = @gateway.authorize(@amount, @declined_card, @options) + assert_failure response + assert_equal 'INVALID CREDIT CARD NUMBER', response.message + end + + def test_partial_capture + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + wait_for_authorization_to_clear + + assert capture = @gateway.capture(@amount-1, auth.authorization, @options) + assert_success capture + end + + def test_failed_capture + response = @gateway.capture(@amount, '', @options) + assert_failure response + assert_match 'field transaction_id', response.message + end + + def test_successful_credit + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + + assert refund = @gateway.credit(@amount, @credit_card, @options) + assert_success refund + assert_equal 'TEST APPROVAL', refund.message + end + + def test_partial_credit + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + + assert refund = @gateway.credit(@amount-1, @credit_card, @options) + assert_success refund + end + + def test_failed_credit + response = @gateway.credit(@amount, @declined_card, @options) + assert_failure response + end + + def test_successful_void + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + wait_for_authorization_to_clear + + assert void = @gateway.void(auth.authorization) + assert_success void + assert_equal 'APPROVED', void.message + end + + def test_failed_void + response = @gateway.void('') + assert_failure response + assert_match 'field transaction_id', response.message + end + + def test_successful_refund + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + + wait_for_authorization_to_clear + + assert refund = @gateway.refund(@amount, purchase.authorization, @options) + assert_success refund + assert_equal 'TEST APPROVAL', refund.message + end + + def test_failed_refund + response = @gateway.refund(@amount, '', @options) + assert_failure response + assert_match 'field authorization_code', response.message + assert_match 'field original_transaction_id', response.message + end + + def test_successful_verify + response = @gateway.verify(@credit_card, @options) + assert_success response + assert_match %r{TEST APPROVAL}, response.message + end + + def test_failed_verify + response = @gateway.verify(@declined_card, @options) + assert_failure response + assert_match %r{INVALID CREDIT CARD NUMBER}, response.message + end + + def test_transcript_scrubbing + @credit_card.verification_value = 789 + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @credit_card, @options) + end + + transcript = @gateway.scrub(transcript) + + assert_scrubbed(@credit_card.number, transcript) + assert_scrubbed(@credit_card.verification_value, transcript) + end + + private + + def wait_for_authorization_to_clear + sleep(10) + end + +end diff --git a/test/remote/gateways/remote_garanti_test.rb b/test/remote/gateways/remote_garanti_test.rb index 45db100bcc6..189fba6ddd0 100644 --- a/test/remote/gateways/remote_garanti_test.rb +++ b/test/remote/gateways/remote_garanti_test.rb @@ -4,19 +4,14 @@ class RemoteGarantiTest < Test::Unit::TestCase def setup - if RUBY_VERSION < '1.9' && $KCODE == "NONE" - @original_kcode = $KCODE - $KCODE = 'u' - end - @gateway = GarantiGateway.new(fixtures(:garanti)) - @amount = 1 # 1 cents = 0.01$ - @declined_card = credit_card('4000100011112224') - @credit_card = credit_card('4000300011112220') + @amount = 100 # 1 cents = 0.01$ + @declined_card = credit_card('4282209027132017') + @credit_card = credit_card('4282209027132016', month: 5, year: 2018, verification_value: 358) @options = { - :order_id => ActiveMerchant::Utils.generate_unique_id, + :order_id => generate_unique_id, :billing_address => address, :description => 'Store Purchase' } @@ -35,7 +30,7 @@ def test_successful_purchase def test_unsuccessful_purchase assert response = @gateway.purchase(@amount, @declined_card, @options) assert_failure response - assert_match /Declined/, response.message + assert_match %r{Declined}, response.message end def test_authorize_and_capture @@ -51,19 +46,18 @@ def test_authorize_and_capture def test_failed_capture assert response = @gateway.capture(@amount, '') assert_failure response - assert_match /Declined/, response.message + assert_match %r{Declined}, response.message end def test_invalid_login gateway = GarantiGateway.new( - :provision_user_id => 'PROVAUT', - :user_id => 'PROVAUT', - :terminal_id => '10000174', + :login => 'PROVAUT', + :terminal_id => '30691300', :merchant_id => '', :password => '' ) assert response = gateway.purchase(@amount, @credit_card, @options) assert_failure response - assert_equal '0651', response.params["reason_code"] + assert_equal '0651', response.params['reason_code'] end end diff --git a/test/remote/gateways/remote_global_collect_test.rb b/test/remote/gateways/remote_global_collect_test.rb new file mode 100644 index 00000000000..19eab0296fa --- /dev/null +++ b/test/remote/gateways/remote_global_collect_test.rb @@ -0,0 +1,179 @@ +require 'test_helper' + +class RemoteGlobalCollectTest < Test::Unit::TestCase + def setup + @gateway = GlobalCollectGateway.new(fixtures(:global_collect)) + + @amount = 100 + @credit_card = credit_card('4567350000427977') + @declined_card = credit_card('5424180279791732') + @accepted_amount = 4005 + @rejected_amount = 2997 + @options = { + email: 'example@example.com', + billing_address: address, + description: 'Store Purchase' + } + end + + def test_successful_purchase + response = @gateway.purchase(@accepted_amount, @credit_card, @options) + assert_success response + assert_equal 'Succeeded', response.message + end + + def test_successful_purchase_with_fraud_fields + options = @options.merge( + fraud_fields: + { + 'website' => 'www.example.com', + 'giftMessage' => 'Happy Day!' + } + ) + + response = @gateway.purchase(@amount, @credit_card, options) + assert_success response + assert_equal 'Succeeded', response.message + end + + def test_successful_purchase_with_more_options + options = @options.merge( + order_id: '1', + ip: '127.0.0.1', + email: 'joe@example.com', + sdk_identifier: 'Channel', + sdk_creator: 'Bob', + integrator: 'Bill', + creator: 'Super', + name: 'Cala', + version: '1.0', + extension_ID: '5555555' + ) + + response = @gateway.purchase(@amount, @credit_card, options) + assert_success response + assert_equal 'Succeeded', response.message + end + + def test_successful_purchase_with_very_long_name + credit_card = credit_card('4567350000427977', { first_name: 'thisisaverylongfirstname'}) + + response = @gateway.purchase(@amount, credit_card, @options) + assert_success response + assert_equal 'Succeeded', response.message + end + + def test_successful_purchase_with_blank_name + credit_card = credit_card('4567350000427977', { first_name: nil, last_name: nil}) + + response = @gateway.purchase(@amount, credit_card, @options) + assert_success response + assert_equal 'Succeeded', response.message + end + + def test_failed_purchase + response = @gateway.purchase(@rejected_amount, @declined_card, @options) + assert_failure response + assert_equal 'Not authorised', response.message + end + + def test_successful_authorize_and_capture + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + assert capture = @gateway.capture(@amount, auth.authorization, @options) + assert_success capture + assert_equal 'Succeeded', capture.message + end + + def test_failed_authorize + response = @gateway.authorize(@amount, @declined_card, @options) + assert_failure response + assert_equal 'Not authorised', response.message + end + + def test_partial_capture + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + assert capture = @gateway.capture(@amount - 1, auth.authorization) + assert_success capture + assert_equal 99, capture.params['payment']['paymentOutput']['amountOfMoney']['amount'] + end + + def test_failed_capture + response = @gateway.capture(@amount, '123', @options) + assert_failure response + assert_match %r{UNKNOWN_PAYMENT_ID}, response.message + end + + # Because payments are not fully authorized immediately, refunds can only be + # tested on older transactions (~24hrs old should be fine) + # + # def test_successful_refund + # txn = REPLACE WITH PREVIOUS TRANSACTION AUTHORIZATION + # + # assert refund = @gateway.refund(@accepted_amount, txn) + # assert_success refund + # assert_equal 'Succeeded', refund.message + # end + # + # def test_partial_refund + # txn = REPLACE WITH PREVIOUS TRANSACTION AUTHORIZATION + # + # assert refund = @gateway.refund(@amount-1, REPLACE WITH PREVIOUS TRANSACTION AUTHORIZATION) + # assert_success refund + # end + + def test_failed_refund + response = @gateway.refund(@amount, '123') + assert_failure response + assert_match %r{UNKNOWN_PAYMENT_ID}, response.message + end + + def test_successful_void + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + assert void = @gateway.void(auth.authorization) + assert_success void + assert_equal 'Succeeded', void.message + end + + def test_failed_void + response = @gateway.void('123') + assert_failure response + assert_match %r{UNKNOWN_PAYMENT_ID}, response.message + end + + def test_successful_verify + response = @gateway.verify(@credit_card, @options) + assert_success response + assert_equal 'Succeeded', response.message + end + + def test_failed_verify + response = @gateway.verify(@declined_card, @options) + assert_failure response + assert_equal 'Not authorised', response.message + end + + def test_invalid_login + gateway = GlobalCollectGateway.new(merchant_id: '', api_key_id: '', secret_api_key: '') + + response = gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_match %r{MISSING_OR_INVALID_AUTHORIZATION}, response.message + end + + def test_transcript_scrubbing + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @credit_card, @options) + end + transcript = @gateway.scrub(transcript) + + assert_scrubbed(@credit_card.number, transcript) + assert_scrubbed(@gateway.options[:secret_api_key], transcript) + end + +end diff --git a/test/remote/gateways/remote_global_transport_test.rb b/test/remote/gateways/remote_global_transport_test.rb new file mode 100644 index 00000000000..eac5264fe9c --- /dev/null +++ b/test/remote/gateways/remote_global_transport_test.rb @@ -0,0 +1,141 @@ +require 'test_helper' + +class RemoteGlobalTransportTest < Test::Unit::TestCase + def setup + @gateway = GlobalTransportGateway.new(fixtures(:global_transport)) + + @credit_card = credit_card('4003002345678903') + + @options = { + email: 'john@example.com', + order_id: '1', + billing_address: address, + } + end + + def test_successful_purchase + response = @gateway.purchase(500, @credit_card, @options) + assert_success response + assert_equal 'Approved', response.message + end + + def test_successful_partial_purchase + @credit_card = credit_card('4111111111111111') + response = @gateway.purchase(2354, @credit_card, @options) + assert_success response + assert_equal 'Partial Approval', response.message + end + + def test_failed_purchase + response = @gateway.purchase(2304, @credit_card, @options) + assert_failure response + assert_equal 'Declined', response.message + end + + def test_successful_authorize_and_capture + auth = @gateway.authorize(500, @credit_card, @options) + assert_success auth + + assert capture = @gateway.capture(500, auth.authorization) + assert_success capture + assert_equal 'Approved', capture.message + end + + def test_failed_authorize + response = @gateway.authorize(40000, @credit_card, @options) + assert_failure response + assert_equal 'Declined', response.message + end + + def test_successful_partial_authorize_and_capture + @credit_card = credit_card('4111111111111111') + auth = @gateway.authorize(2354, @credit_card, @options) + assert_success auth + assert_equal 'Partial Approval', auth.message + + assert capture = @gateway.capture(2000, auth.authorization) + assert_success capture + end + + def test_failed_capture + auth = @gateway.authorize(500, @credit_card, @options) + assert_success auth + + assert capture = @gateway.capture(1000, auth.authorization) + assert_failure capture + assert_match(/must be less than or equal to the original amount/, capture.message) + end + + def test_successful_refund + purchase = @gateway.purchase(500, @credit_card, @options) + assert_success purchase + + assert refund = @gateway.refund(500, purchase.authorization) + assert_success refund + assert_equal 'Approved', refund.message + end + + def test_partial_refund + purchase = @gateway.purchase(500, @credit_card, @options) + assert_success purchase + + assert refund = @gateway.refund(490, purchase.authorization) + assert_success refund + assert_equal 'Approved', refund.message + end + + def test_failed_refund + purchase = @gateway.purchase(500, @credit_card, @options) + assert_success purchase + + assert refund = @gateway.refund(1000, purchase.authorization) + assert_failure refund + assert_match(/Refund Exceeds Available Refund Amount/, refund.message) + end + + def test_successful_void + auth = @gateway.authorize(500, @credit_card, @options) + assert_success auth + + assert void = @gateway.void(auth.authorization) + assert_success void + assert_equal 'Approved', void.message + end + + def test_failed_void + assert void = @gateway.void('UnknownAuthorization') + assert_failure void + assert_equal 'Invalid PNRef', void.message + end + + def test_successful_verify + response = @gateway.verify(@credit_card, @options) + assert_success response + assert_equal 'Approved', response.message + end + + def test_failed_verify + response = @gateway.verify(credit_card('4003'), @options) + assert_failure response + assert_equal 'Invalid Account Number', response.message + end + + def test_invalid_login + gateway = GlobalTransportGateway.new(global_user_name: '', global_password: '', term_type: '') + response = gateway.purchase(500, @credit_card, @options) + assert_failure response + assert_equal('Invalid Login Information', response.message) + end + + def test_transcript_scrubbing + transcript = capture_transcript(@gateway) do + @gateway.purchase(500, @credit_card, @options) + end + transcript = @gateway.scrub(transcript) + + assert_scrubbed(@credit_card.number, transcript) + assert_scrubbed(@credit_card.verification_value, transcript) + assert_scrubbed(@gateway.options[:global_password], transcript) + end + +end diff --git a/test/remote/gateways/remote_hdfc_test.rb b/test/remote/gateways/remote_hdfc_test.rb index 67488f56c25..e3f2ed395d5 100644 --- a/test/remote/gateways/remote_hdfc_test.rb +++ b/test/remote/gateways/remote_hdfc_test.rb @@ -1,5 +1,4 @@ require 'test_helper' -require 'remote/integrations/remote_integration_helper' class RemoteHdfcTest < Test::Unit::TestCase def setup @@ -8,48 +7,48 @@ def setup @gateway = HdfcGateway.new(fixtures(:hdfc)) @amount = 100 - @credit_card = credit_card("4012001037141112") + @credit_card = credit_card('4012001037141112') # Use an American Express card to simulate a failure since HDFC does not # support any proper decline cards outside of 3D secure failures. - @declined_card = credit_card("377182068239368", :brand => :american_express) + @declined_card = credit_card('377182068239368', :brand => :american_express) @options = { :order_id => generate_unique_id, :billing_address => address, - :description => "Store Purchase" + :description => 'Store Purchase' } end def test_successful_purchase assert response = @gateway.purchase(@amount, @credit_card, @options) assert_success response - assert_equal "Succeeded", response.message + assert_equal 'Succeeded', response.message end def test_failed_purchase assert response = @gateway.purchase(@amount, @declined_card, @options) assert_failure response - assert_equal "Invalid Brand.", response.message - assert_equal "GW00160", response.params["error_code_tag"] + assert_equal 'Invalid Brand.', response.message + assert_equal 'GW00160', response.params['error_code_tag'] end def test_successful_authorize_and_capture assert response = @gateway.authorize(@amount, @credit_card, @options) assert_success response - assert_equal "Succeeded", response.message + assert_equal 'Succeeded', response.message assert_match %r(^\d+\|.+$), response.authorization assert capture = @gateway.capture(@amount, response.authorization) assert_success capture - assert_equal "Succeeded", capture.message + assert_equal 'Succeeded', capture.message end def test_failed_authorize assert response = @gateway.authorize(@amount, @declined_card, @options) assert_failure response - assert_equal "Invalid Brand.", response.message - assert_equal "GW00160", response.params["error_code_tag"] + assert_equal 'Invalid Brand.', response.message + assert_equal 'GW00160', response.params['error_code_tag'] end def test_successful_refund @@ -58,7 +57,7 @@ def test_successful_refund assert refund = @gateway.refund(@amount, response.authorization) assert_success refund - assert_equal "Succeeded", refund.message + assert_equal 'Succeeded', refund.message end def test_passing_billing_address @@ -68,11 +67,11 @@ def test_passing_billing_address def test_invalid_login gateway = HdfcGateway.new( - :login => "", - :password => "" + :login => '', + :password => '' ) assert response = gateway.purchase(@amount, @credit_card, @options) assert_failure response - assert_equal "TranPortal ID required.", response.message + assert_equal 'TranPortal ID required.', response.message end end diff --git a/test/remote/gateways/remote_hps_test.rb b/test/remote/gateways/remote_hps_test.rb new file mode 100644 index 00000000000..832d73703a9 --- /dev/null +++ b/test/remote/gateways/remote_hps_test.rb @@ -0,0 +1,412 @@ +require 'test_helper' + +class RemoteHpsTest < Test::Unit::TestCase + def setup + @gateway = HpsGateway.new(fixtures(:hps)) + + @amount = 100 + @declined_amount = 1034 + @credit_card = credit_card('4000100011112224') + + @options = { + order_id: '1', + billing_address: address, + description: 'Store Purchase' + } + end + + def test_successful_purchase + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal 'Success', response.message + end + + def test_successful_purchase_without_cardholder + response = @gateway.purchase(@amount, @credit_card) + assert_success response + assert_equal 'Success', response.message + end + + def test_successful_purchase_with_details + @options[:description] = 'Description' + @options[:order_id] = '12345' + @options[:customer_id] = '654321' + + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal 'Success', response.message + end + + def test_successful_purchase_with_descriptor + response = @gateway.purchase(@amount, @credit_card, @options.merge(descriptor_name: 'Location Name')) + assert_success response + assert_equal 'Success', response.message + end + + def test_successful_purchase_no_address + options = { + order_id: '1', + description: 'Store Purchase' + } + response = @gateway.purchase(@amount, @credit_card, options) + assert_instance_of Response, response + assert_success response + assert_equal 'Success', response.message + end + + def test_failed_purchase + response = @gateway.purchase(@declined_amount, @credit_card, @options) + assert_failure response + assert_equal 'The card was declined.', response.message + end + + def test_successful_authorize_with_details + @options[:description] = 'Description' + @options[:order_id] = '12345' + @options[:customer_id] = '654321' + + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + end + + def test_successful_authorize_and_capture + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + assert capture = @gateway.capture(nil, auth.authorization) + assert_success capture + end + + def test_successful_authorize_no_address + options = { + order_id: '1', + description: 'Store Authorize' + } + response = @gateway.authorize(@amount, @credit_card, options) + assert_instance_of Response, response + assert_success response + assert_equal 'Success', response.message + end + + def test_failed_authorize + response = @gateway.authorize(@declined_amount, @credit_card, @options) + assert_failure response + end + + def test_partial_capture + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + assert capture = @gateway.capture(@amount-1, auth.authorization) + assert_success capture + end + + def test_failed_capture + response = @gateway.capture(nil, '') + assert_failure response + end + + def test_successful_refund + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + + assert refund = @gateway.refund(@amount, purchase.authorization) + assert_success refund + assert_equal 'Success', refund.params['GatewayRspMsg'] + assert_equal '0', refund.params['GatewayRspCode'] + end + + def test_partial_refund + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + + assert refund = @gateway.refund(@amount-1, purchase.authorization) + assert_success refund + assert_equal 'Success', refund.params['GatewayRspMsg'] + assert_equal '0', refund.params['GatewayRspCode'] + end + + def test_failed_refund + response = @gateway.refund(nil, '') + assert_failure response + end + + def test_successful_void + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + assert void = @gateway.void(auth.authorization) + assert_success void + assert_equal 'Success', void.params['GatewayRspMsg'] + end + + def test_failed_void + response = @gateway.void('123') + assert_failure response + assert_match %r{rejected}i, response.message + end + + def test_empty_login + gateway = HpsGateway.new(secret_api_key: '') + response = gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_equal 'Authentication error. Please double check your service configuration.', response.message + end + + def test_nil_login + gateway = HpsGateway.new(secret_api_key: nil) + response = gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_equal 'Authentication error. Please double check your service configuration.', response.message + end + + def test_invalid_login + gateway = HpsGateway.new(secret_api_key: 'Bad_API_KEY') + response = gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_equal 'Authentication error. Please double check your service configuration.', response.message + end + + def test_successful_get_token_from_auth + response = @gateway.authorize(@amount, @credit_card, @options.merge(store: true)) + + assert_success response + assert_equal 'Visa', response.params['CardType'] + assert_equal 'Success', response.params['TokenRspMsg'] + assert_not_nil response.params['TokenValue'] + end + + def test_successful_get_token_from_purchase + response = @gateway.purchase(@amount, @credit_card, @options.merge(store: true)) + + assert_success response + assert_equal 'Visa', response.params['CardType'] + assert_equal 'Success', response.params['TokenRspMsg'] + assert_not_nil response.params['TokenValue'] + end + + def test_successful_purchase_with_token_from_auth + response = @gateway.authorize(@amount, @credit_card, @options.merge(store: true)) + + assert_success response + assert_equal 'Visa', response.params['CardType'] + assert_equal 'Success', response.params['TokenRspMsg'] + assert_not_nil response.params['TokenValue'] + token = response.params['TokenValue'] + + purchase = @gateway.purchase(@amount, token, @options) + assert_success purchase + assert_equal 'Success', purchase.message + end + + def test_successful_purchase_with_swipe_no_encryption + @credit_card.track_data = '%B547888879888877776?;5473500000000014=25121019999888877776?' + response = @gateway.purchase(@amount, @credit_card, @options) + + assert_success response + assert_equal 'Success', response.message + end + + def test_failed_purchase_with_swipe_bad_track_data + @credit_card.track_data = '%B547888879888877776?;?' + response = @gateway.purchase(@amount, @credit_card, @options) + + assert_failure response + assert_equal 'Transaction was rejected because the track data could not be read.', response.message + end + + def test_successful_purchase_with_swipe_encryption_type_01 + @options[:encryption_type] = '01' + @credit_card.track_data = '&lt;E1052711%B5473501000000014^MC TEST CARD^251200000000000000000000000000000000?|GVEY/MKaKXuqqjKRRueIdCHPPoj1gMccgNOtHC41ymz7bIvyJJVdD3LW8BbwvwoenI+|+++++++C4cI2zjMp|11;5473501000000014=25120000000000000000?|8XqYkQGMdGeiIsgM0pzdCbEGUDP|+++++++C4cI2zjMp|00|||/wECAQECAoFGAgEH2wYcShV78RZwb3NAc2VjdXJlZXhjaGFuZ2UubmV0PX50qfj4dt0lu9oFBESQQNkpoxEVpCW3ZKmoIV3T93zphPS3XKP4+DiVlM8VIOOmAuRrpzxNi0TN/DWXWSjUC8m/PI2dACGdl/hVJ/imfqIs68wYDnp8j0ZfgvM26MlnDbTVRrSx68Nzj2QAgpBCHcaBb/FZm9T7pfMr2Mlh2YcAt6gGG1i2bJgiEJn8IiSDX5M2ybzqRT86PCbKle/XCTwFFe1X|&gt;' + response = @gateway.purchase(@amount, @credit_card, @options) + + assert_success response + assert_equal 'Success', response.message + end + + def test_successful_purchase_with_swipe_encryption_type_02 + @options[:encryption_type] = '02' + @options[:encrypted_track_number] = 2 + @options[:ktb] = '/wECAQECAoFGAgEH3QgVTDT6jRZwb3NAc2VjdXJlZXhjaGFuZ2UubmV0Nkt08KRSPigRYcr1HVgjRFEvtUBy+VcCKlOGA3871r3SOkqDvH2+30insdLHmhTLCc4sC2IhlobvWnutAfylKk2GLspH/pfEnVKPvBv0hBnF4413+QIRlAuGX6+qZjna2aMl0kIsjEY4N6qoVq2j5/e5I+41+a2pbm61blv2PEMAmyuCcAbN3/At/1kRZNwN6LSUg9VmJO83kOglWBe1CbdFtncq' + @credit_card.track_data = '7SV2BK6ESQPrq01iig27E74SxMg' + response = @gateway.purchase(@amount, @credit_card, @options) + + assert_success response + assert_equal 'Success', response.message + end + + def tests_successful_verify + response = @gateway.verify(@credit_card, @options) + + assert_success response + assert_equal 'Success', response.message + end + + def tests_failed_verify + @credit_card.number = 12345 + + response = @gateway.verify(@credit_card, @options) + + assert_failure response + assert_equal 'The card number is not a valid credit card number.', response.message + end + + def test_transcript_scrubbing + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @credit_card, @options) + end + transcript = @gateway.scrub(transcript) + + assert_scrubbed(@credit_card.number, transcript) + assert_scrubbed(@credit_card.verification_value, transcript) + assert_scrubbed(@gateway.options[:secret_api_key], transcript) + end + + def test_transcript_scrubbing_with_cryptogram + credit_card = network_tokenization_credit_card('4242424242424242', + payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=', + verification_value: nil, + eci: '05', + source: :apple_pay + ) + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, credit_card, @options) + end + transcript = @gateway.scrub(transcript) + + assert_scrubbed(credit_card.number, transcript) + assert_scrubbed(@gateway.options[:secret_api_key], transcript) + assert_scrubbed(credit_card.payment_cryptogram, transcript) + end + + def test_successful_purchase_with_apple_pay_raw_cryptogram_with_eci + credit_card = network_tokenization_credit_card('4242424242424242', + payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=', + verification_value: nil, + eci: '05', + source: :apple_pay + ) + assert response = @gateway.purchase(@amount, credit_card, @options) + assert_success response + assert_equal 'Success', response.message + end + + def test_successful_purchase_with_apple_pay_raw_cryptogram_without_eci + credit_card = network_tokenization_credit_card('4242424242424242', + payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=', + verification_value: nil, + source: :apple_pay + ) + assert response = @gateway.purchase(@amount, credit_card, @options) + assert_success response + assert_equal 'Success', response.message + end + + def test_successful_auth_with_apple_pay_raw_cryptogram_with_eci + credit_card = network_tokenization_credit_card('4242424242424242', + payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=', + verification_value: nil, + eci: '05', + source: :apple_pay + ) + assert response = @gateway.authorize(@amount, credit_card, @options) + assert_success response + assert_equal 'Success', response.message + end + + def test_successful_auth_with_apple_pay_raw_cryptogram_without_eci + credit_card = network_tokenization_credit_card('4242424242424242', + payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=', + verification_value: nil, + source: :apple_pay + ) + assert response = @gateway.authorize(@amount, credit_card, @options) + assert_success response + assert_equal 'Success', response.message + end + + def test_three_d_secure_visa + @credit_card.number = '4012002000060016' + @credit_card.brand = 'visa' + + options = { + :three_d_secure => { + :cavv => 'EHuWW9PiBkWvqE5juRwDzAUFBAk=', + :eci => '05', + :xid => 'TTBCSkVTa1ZpbDI1bjRxbGk5ODE=' + } + } + + response = @gateway.purchase(@amount, @credit_card, options) + assert_success response + assert_equal 'Success', response.message + end + + def test_three_d_secure_mastercard + @credit_card.number = '5473500000000014' + @credit_card.brand = 'master' + + options = { + :three_d_secure => { + :cavv => 'EHuWW9PiBkWvqE5juRwDzAUFBAk=', + :eci => '05', + :xid => 'TTBCSkVTa1ZpbDI1bjRxbGk5ODE=' + } + } + + response = @gateway.purchase(@amount, @credit_card, options) + assert_success response + assert_equal 'Success', response.message + end + + def test_three_d_secure_discover + @credit_card.number = '6011000990156527' + @credit_card.brand = 'discover' + + options = { + :three_d_secure => { + :cavv => 'EHuWW9PiBkWvqE5juRwDzAUFBAk=', + :eci => '05', + :xid => 'TTBCSkVTa1ZpbDI1bjRxbGk5ODE=' + } + } + + response = @gateway.purchase(@amount, @credit_card, options) + assert_success response + assert_equal 'Success', response.message + end + + def test_three_d_secure_amex + @credit_card.number = '372700699251018' + @credit_card.brand = 'american_express' + + options = { + :three_d_secure => { + :cavv => 'EHuWW9PiBkWvqE5juRwDzAUFBAk=', + :eci => '05', + :xid => 'TTBCSkVTa1ZpbDI1bjRxbGk5ODE=' + } + } + + response = @gateway.purchase(@amount, @credit_card, options) + assert_success response + assert_equal 'Success', response.message + end + + def test_three_d_secure_jcb + @credit_card.number = '372700699251018' + @credit_card.brand = 'jcb' + + options = { + :three_d_secure => { + :cavv => 'EHuWW9PiBkWvqE5juRwDzAUFBAk=', + :eci => '05', + :xid => 'TTBCSkVTa1ZpbDI1bjRxbGk5ODE=' + } + } + + response = @gateway.purchase(@amount, @credit_card, options) + assert_success response + assert_equal 'Success', response.message + end +end diff --git a/test/remote/gateways/remote_iats_payments_test.rb b/test/remote/gateways/remote_iats_payments_test.rb index 28842f46d27..34d604b7b92 100644 --- a/test/remote/gateways/remote_iats_payments_test.rb +++ b/test/remote/gateways/remote_iats_payments_test.rb @@ -6,8 +6,8 @@ def setup @gateway = IatsPaymentsGateway.new(fixtures(:iats_payments)) @amount = 100 - @credit_card = credit_card('4111111111111111') - @check = check + @credit_card = credit_card('4222222222222220') + @check = check(routing_number: '111111111', account_number: '12345678') @options = { :order_id => generate_unique_id, :billing_address => address, @@ -19,37 +19,122 @@ def test_successful_purchase assert response = @gateway.purchase(@amount, @credit_card, @options) assert_success response assert response.test? - assert_equal 'This transaction has been approved', response.message + assert_equal 'Success', response.message assert response.authorization end def test_failed_purchase - assert response = @gateway.purchase(200, @credit_card, @options) + credit_card = credit_card('4111111111111111') + assert response = @gateway.purchase(200, credit_card, @options) assert_failure response assert response.test? - assert_equal 'This transaction has been declined', response.message + assert response.message.include?('REJECT') end - def test_bad_login + def test_successful_check_purchase + response = @gateway.purchase(@amount, @check, @options) + assert_success response + assert response.test? + assert_equal 'Success', response.message + assert response.authorization + end + + # Not possible to test failure case since tx failure is delayed from time of + # submission w/ ACH, even for test txs. + # def test_failed_check_purchase + # response = @gateway.purchase(125, @check, @options) + # assert_failure response + # assert response.test? + # assert_nil response.authorization + # end + + def test_successful_refund + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + + assert refund = @gateway.refund(@amount, purchase.authorization) + assert_success refund + end + + def test_failed_refund + credit_card = credit_card('4111111111111111') + purchase = @gateway.purchase(@amount, credit_card, @options) + assert_success purchase + + assert refund = @gateway.refund(@amount, purchase.authorization) + assert_failure refund + end + + def test_successful_check_refund + purchase = @gateway.purchase(@amount, @check, @options) + assert_success purchase + + assert refund = @gateway.refund(@amount, purchase.authorization) + + # This is a dubious test. Basically testing that the refund failed b/c + # the original purchase hadn't yet cleared. No way to test immediate failure + # due to the delay in original tx processing, even for text txs. + assert_failure refund + assert_equal 'REJECT: 3', refund.message + end + + def test_failed_check_refund + assert refund = @gateway.refund(@amount, 'invalidref') + assert_failure refund + assert_equal 'REJECT: 39', refund.message + end + + def test_successful_store_and_unstore + assert store = @gateway.store(@credit_card, @options) + assert_success store + assert store.authorization + assert_equal 'Success', store.message + + assert unstore = @gateway.unstore(store.authorization, @options) + assert_success unstore + assert_equal 'Success', unstore.message + end + + def test_failed_store + credit_card = credit_card('4111') + assert store = @gateway.store(credit_card, @options) + assert_failure store + assert_match(/Invalid credit card number/, store.message) + end + + def test_invalid_login gateway = IatsPaymentsGateway.new( - :login => 'X', - :password => 'Y' + :agent_code => 'X', + :password => 'Y', + :region => 'na' ) assert response = gateway.purchase(@amount, @credit_card) + assert_failure response + end - assert_equal Response, response.class - assert_equal ["action", - "authorization_code", - "avs_result_code", - "card_code", - "response_code", - "response_reason_code", - "response_reason_text", - "transaction_id"], response.params.keys.sort + def test_purchase_scrubbing + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @credit_card, @options) + end + transcript = @gateway.scrub(transcript) - assert_match(/The merchant API Login ID is invalid or the account is inactive/, response.message) + assert_scrubbed(credit_card.number, transcript) + assert_scrubbed(credit_card.verification_value, transcript) + assert_scrubbed(@gateway.options[:agent_code], transcript) + assert_scrubbed(@gateway.options[:password], transcript) + end + + def test_check_purchase_scrubbing + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @check, @options) + end + transcript = @gateway.scrub(transcript) - assert_equal false, response.success? + assert_scrubbed(@check.routing_number, transcript) + assert_scrubbed(@check.account_number, transcript) + assert_scrubbed(@gateway.options[:agent_code], transcript) + assert_scrubbed(@gateway.options[:password], transcript) end + end diff --git a/test/remote/gateways/remote_ideal_rabobank_test.rb b/test/remote/gateways/remote_ideal_rabobank_test.rb deleted file mode 100644 index 4afa4dd084b..00000000000 --- a/test/remote/gateways/remote_ideal_rabobank_test.rb +++ /dev/null @@ -1,121 +0,0 @@ -require 'test_helper' - -class RemoteIdealRabobankTest < Test::Unit::TestCase - def setup - Base.gateway_mode = :test - - @gateway = IdealRabobankGateway.new(fixtures(:ideal_rabobank)) - - @options = { - :issuer_id => '0151', - :return_url => 'http://www.return.url', - :order_id => '1234567890123456', - :currency => 'EUR', - :description => 'A description', - :entrance_code => '1234' - } - end - - def test_issuers - response = @gateway.issuers - list = response.issuer_list - - assert_equal 3, list.length - assert_equal 'Test Issuer', list[0]['issuerName'] - assert_equal '0121', list[0]['issuerID'] - assert_equal 'Short', list[0]['issuerList'] - end - - - def test_set_purchase - response = @gateway.setup_purchase(550, @options) - - assert_success response - assert response.test? - assert_nil response.error, "Response should not have an error" - end - - def test_return_errors - response = @gateway.setup_purchase(0.5, @options) - assert_failure response - assert_equal 'BR1210', response.error[ 'errorCode'] - assert_not_nil response.error['errorMessage'], "Response should contain an Error message" - assert_not_nil response.error['errorDetail'], "Response should contain an Error Detail message" - assert_not_nil response.error['consumerMessage'],"Response should contain an Consumer Error message" - end - - # default payment should succeed - def test_purchase_successful - response = @gateway.setup_purchase(100, @options) - - assert_success response - - assert_equal '1234567890123456', response.transaction['purchaseID'] - assert_equal '0020', response.params['AcquirerTrxRes']['Acquirer']['acquirerID'] - assert_not_nil response.service_url, "Response should contain a service url for payment" - - # now authorize the payment, issuer simulator has completed the payment - response = @gateway.capture(response.transaction['transactionID']) - - assert_success response - assert_equal 'Success', response.transaction['status'] - assert_equal 'DEN HAAG', response.transaction['consumerCity'] - assert_equal "Hr J A T Verf\303\274rth en/of Mw T V Chet", response.transaction['consumerName'] - end - - # payment of 200 should cancel - def test_purchase_cancel - response = @gateway.setup_purchase(200, @options) - - assert_success response - # now try to authorize the payment, issuer simulator has cancelled the payment - response = @gateway.capture(response.transaction['transactionID']) - - assert_failure response - assert_equal 'Cancelled', response.transaction['status'], 'Transaction should cancel' - end - - # payment of 300 should expire - def test_transaction_expired - response = @gateway.setup_purchase(300, @options) - - # now try to authorize the payment, issuer simulator let the payment expire - response = @gateway.capture(response.transaction['transactionID']) - - assert_failure response - assert_equal 'Expired', response.transaction['status'], 'Transaction should expire' - end - - # payment of 400 should remain open - def test_transaction_opened - response = @gateway.setup_purchase(400, @options) - - # now try to authorize the payment, issuer simulator keeps the payment open - response = @gateway.capture(response.transaction['transactionID']) - - assert_failure response - assert_equal 'Open', response.transaction['status'], 'Transaction should remain open' - end - - # payment of 500 should fail at issuer - def test_transaction_failed - response = @gateway.setup_purchase(500, @options) - - # now try to authorize the payment, issuer simulator lets the payment fail - response = @gateway.capture(response.transaction['transactionID']) - assert_failure response - assert_equal 'Failure', response.transaction['status'], 'Transaction should fail' - end - - # payment of 700 should be unknown at issuer - def test_transaction_unknown - response = @gateway.setup_purchase(700, @options) - - # now try to authorize the payment, issuer simulator lets the payment fail - response = @gateway.capture(response.transaction['transactionID']) - - assert_failure response - assert_equal 'SO1000', response.error[ 'errorCode'], 'ErrorCode should be correct' - end - -end diff --git a/test/remote/gateways/remote_inspire_test.rb b/test/remote/gateways/remote_inspire_test.rb index 678f1118780..4e137b4d3ff 100644 --- a/test/remote/gateways/remote_inspire_test.rb +++ b/test/remote/gateways/remote_inspire_test.rb @@ -1,30 +1,23 @@ -require File.dirname(__FILE__) + '/../../test_helper' +require 'test_helper' class RemoteBraintreeTest < Test::Unit::TestCase def setup @gateway = InspireGateway.new(fixtures(:inspire)) - @amount = rand(10000) + 1001 + @amount = rand(1001..11000) @credit_card = credit_card('4111111111111111', :brand => 'visa') @declined_amount = rand(99) @options = { :order_id => generate_unique_id, :billing_address => address } end - + def test_successful_purchase - assert response = @gateway.purchase(@amount, @credit_card, @options) - assert_equal 'This transaction has been approved', response.message + response = @gateway.purchase(@amount, @credit_card, @options) assert_success response - end - - def test_successful_purchase_with_old_naming - gateway = InspireGateway.new(fixtures(:inspire)) - assert response = gateway.purchase(@amount, @credit_card, @options) assert_equal 'This transaction has been approved', response.message - assert_success response end - + def test_successful_purchase_with_echeck check = ActiveMerchant::Billing::Check.new( :name => 'Fredd Bloggs', @@ -33,106 +26,127 @@ def test_successful_purchase_with_echeck :account_holder_type => 'personal', :account_type => 'checking' ) - assert response = @gateway.purchase(@amount, check, @options) - assert_equal 'This transaction has been approved', response.message + response = @gateway.purchase(@amount, check, @options) assert_success response + assert_equal 'This transaction has been approved', response.message end - + def test_successful_add_to_vault @options[:store] = true - assert response = @gateway.purchase(@amount, @credit_card, @options) - assert_equal 'This transaction has been approved', response.message + response = @gateway.purchase(@amount, @credit_card, @options) assert_success response - assert_not_nil response.params["customer_vault_id"] + assert_equal 'This transaction has been approved', response.message end def test_successful_add_to_vault_with_store_method - assert response = @gateway.store(@credit_card) - assert_equal 'This transaction has been approved', response.message + response = @gateway.store(@credit_card) assert_success response - assert_not_nil response.params["customer_vault_id"] + assert_equal 'This transaction has been approved', response.message end def test_successful_add_to_vault_and_use @options[:store] = true - assert response = @gateway.purchase(@amount, @credit_card, @options) - assert_equal 'This transaction has been approved', response.message + response = @gateway.purchase(@amount, @credit_card, @options) assert_success response - assert_not_nil customer_id = response.params["customer_vault_id"] - - assert second_response = @gateway.purchase(@amount*2, customer_id, @options) + assert_equal 'This transaction has been approved', response.message + assert_not_nil customer_id = response.params['customer_vault_id'] + + second_response = @gateway.purchase(@amount*2, customer_id, @options) assert_equal 'This transaction has been approved', second_response.message - assert second_response.success? + assert_success second_response end - + def test_add_to_vault_with_custom_vault_id - @options[:store] = rand(100000)+10001 - assert response = @gateway.purchase(@amount, @credit_card, @options) - assert_equal 'This transaction has been approved', response.message + @options[:store] = rand(10001..110000) + response = @gateway.purchase(@amount, @credit_card, @options) assert_success response - assert_equal @options[:store], response.params["customer_vault_id"].to_i + assert_equal 'This transaction has been approved', response.message + assert_equal @options[:store], response.params['customer_vault_id'].to_i end - + def test_add_to_vault_with_custom_vault_id_with_store_method - @options[:billing_id] = rand(100000)+10001 - assert response = @gateway.store(@credit_card, @options.dup) - assert_equal 'This transaction has been approved', response.message + @options[:billing_id] = rand(10001..110000) + response = @gateway.store(@credit_card, @options.dup) assert_success response - assert_equal @options[:billing_id], response.params["customer_vault_id"].to_i + assert_equal 'This transaction has been approved', response.message + assert_equal @options[:billing_id], response.params['customer_vault_id'].to_i end - + def test_update_vault test_add_to_vault_with_custom_vault_id @credit_card = credit_card('4111111111111111', :month => 10) - assert response = @gateway.update(@options[:store], @credit_card) + response = @gateway.update(@options[:store], @credit_card) assert_success response assert_equal 'Customer Update Successful', response.message end - + def test_delete_from_vault test_add_to_vault_with_custom_vault_id - assert response = @gateway.delete(@options[:store]) + response = @gateway.delete(@options[:store]) assert_success response assert_equal 'Customer Deleted', response.message end def test_delete_from_vault_with_unstore_method test_add_to_vault_with_custom_vault_id - assert response = @gateway.unstore(@options[:store]) + response = @gateway.unstore(@options[:store]) assert_success response assert_equal 'Customer Deleted', response.message end def test_declined_purchase - assert response = @gateway.purchase(@declined_amount, @credit_card, @options) - assert_equal 'This transaction has been declined', response.message + response = @gateway.purchase(@declined_amount, @credit_card, @options) assert_failure response + assert_equal 'This transaction has been declined', response.message end def test_authorize_and_capture - assert auth = @gateway.authorize(@amount, @credit_card, @options) + auth = @gateway.authorize(@amount, @credit_card, @options) assert_success auth assert_equal 'This transaction has been approved', auth.message assert auth.authorization - assert capture = @gateway.capture(@amount, auth.authorization) - assert_equal 'This transaction has been approved', capture.message + + capture = @gateway.capture(@amount, auth.authorization) assert_success capture + assert_equal 'This transaction has been approved', capture.message end - + def test_authorize_and_void - assert auth = @gateway.authorize(@amount, @credit_card, @options) + auth = @gateway.authorize(@amount, @credit_card, @options) assert_success auth assert_equal 'This transaction has been approved', auth.message assert auth.authorization - assert void = @gateway.void(auth.authorization) - assert_equal 'Transaction Void Successful', void.message + + void = @gateway.void(auth.authorization) assert_success void + assert_equal 'Transaction Void Successful', void.message end def test_failed_capture - assert response = @gateway.capture(@amount, '') + response = @gateway.capture(@amount, '') + assert_failure response + assert_match %r{Invalid Transaction ID \/ Object ID specified:}, response.message + end + + def test_refund + response = @gateway.purchase(@amount, @credit_card) + assert_success response + + response = @gateway.refund(nil, response.authorization) + assert_success response + end + + def test_partial_refund + response = @gateway.purchase(@amount, @credit_card) + assert_success response + + response = @gateway.refund(@amount-500, response.authorization) + assert_success response + end + + def test_failed_refund + response = @gateway.refund(nil, 'bogus') assert_failure response - assert response.message.match(/Invalid Transaction ID \/ Object ID specified:/) end def test_invalid_login @@ -140,10 +154,8 @@ def test_invalid_login :login => '', :password => '' ) - assert response = gateway.purchase(@amount, @credit_card, @options) + response = gateway.purchase(@amount, @credit_card, @options) assert_equal 'Invalid Username', response.message assert_failure response end end - - diff --git a/test/remote/gateways/remote_instapay_test.rb b/test/remote/gateways/remote_instapay_test.rb old mode 100755 new mode 100644 index e43ad00d8ec..5a8286cf234 --- a/test/remote/gateways/remote_instapay_test.rb +++ b/test/remote/gateways/remote_instapay_test.rb @@ -1,7 +1,7 @@ require 'test_helper' class RemoteInstapayTest < Test::Unit::TestCase - + def setup @gateway = InstapayGateway.new(fixtures(:instapay)) @@ -38,24 +38,24 @@ def test_failed_authorization assert response = @gateway.authorize(@amount, @declined_card, @options) assert_failure response end - + def test_authorization_and_capture assert authorization = @gateway.authorize(@amount, @credit_card, @options) assert_success authorization - + assert capture = @gateway.capture(@amount, authorization.authorization) assert_success capture assert_equal InstapayGateway::SUCCESS_MESSAGE, capture.message end - + def test_invalid_login gateway = InstapayGateway.new( :login => 'X', :password => 'Y' ) - + assert response = gateway.purchase(@amount, @credit_card) assert_failure response - assert_equal "Invalid merchant", response.message + assert_equal 'Invalid merchant', response.message end end diff --git a/test/remote/gateways/remote_ipp_test.rb b/test/remote/gateways/remote_ipp_test.rb new file mode 100644 index 00000000000..f7e62cad46b --- /dev/null +++ b/test/remote/gateways/remote_ipp_test.rb @@ -0,0 +1,84 @@ +require 'test_helper' + +class RemoteIppTest < Test::Unit::TestCase + def setup + @gateway = IppGateway.new(fixtures(:ipp)) + + @credit_card = credit_card('4005550000000001') + + @options = { + order_id: '1', + billing_address: address, + description: 'Store Purchase', + } + end + + def test_dump_transcript + skip('Transcript scrubbing for this gateway has been tested.') + dump_transcript_and_fail(@gateway, @amount, @credit_card, @options) + end + + def test_transcript_scrubbing + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @credit_card, @options) + end + transcript = @gateway.scrub(transcript) + + assert_scrubbed(@credit_card.number, transcript) + assert_scrubbed(@credit_card.verification_value, transcript) + assert_scrubbed(@gateway.options[:password], transcript) + end + + def test_successful_purchase + response = @gateway.purchase(200, @credit_card, @options) + assert_success response + assert_equal '', response.message + end + + def test_failed_purchase + response = @gateway.purchase(105, @credit_card, @options) + assert_failure response + assert_equal 'Do Not Honour', response.message + assert_equal Gateway::STANDARD_ERROR_CODE[:card_declined], response.error_code + end + + def test_successful_authorize_and_capture + response = @gateway.authorize(200, @credit_card, @options) + assert_success response + response = @gateway.capture(200, response.authorization) + assert_success response + end + + def test_failed_authorize + response = @gateway.authorize(105, @credit_card, @options) + assert_failure response + end + + def test_failed_capture + response = @gateway.capture(200, '') + assert_failure response + end + + def test_successful_refund + response = @gateway.purchase(200, @credit_card, @options) + response = @gateway.refund(200, response.authorization, @options) + assert_success response + assert_equal '', response.message + end + + def test_failed_refund + response = @gateway.purchase(200, @credit_card, @options) + response = @gateway.refund(105, response.authorization, @options) + assert_failure response + assert_equal 'Do Not Honour', response.message + end + + def test_invalid_login + gateway = IppGateway.new( + username: '', + password: '' + ) + response = gateway.purchase(200, @credit_card, @options) + assert_failure response + end +end diff --git a/test/remote/gateways/remote_iridium_test.rb b/test/remote/gateways/remote_iridium_test.rb index 793613ded61..dc033cabe3a 100644 --- a/test/remote/gateways/remote_iridium_test.rb +++ b/test/remote/gateways/remote_iridium_test.rb @@ -7,14 +7,17 @@ def setup @gateway = IridiumGateway.new(fixtures(:iridium)) @amount = 100 + @avs_card = credit_card('4921810000005462', {:verification_value => '441'}) + @cv2_card = credit_card('4976000000003436', {:verification_value => '777'}) + @avs_cv2_card = credit_card('4921810000005462', {:verification_value => '777'}) @credit_card = credit_card('4976000000003436', {:verification_value => '452'}) @declined_card = credit_card('4221690000004963') - our_address = address(:address1 => "32 Edward Street", - :address2 => "Camborne", - :state => "Cornwall", - :zip => "TR14 8PA", - :country => "826") + our_address = address(:address1 => '32 Edward Street', + :address2 => 'Camborne', + :state => 'Cornwall', + :zip => 'TR14 8PA', + :country => '826') @options = { :order_id => generate_unique_id, :billing_address => our_address, @@ -32,7 +35,28 @@ def test_successful_purchase def test_failed_purchase assert response = @gateway.purchase(@amount, @declined_card, @options) assert_failure response - assert_equal 'Card declined', response.message + assert_match %r{Card declined}i, response.message + end + + def test_avs_failure + assert response = @gateway.purchase(@amount, @avs_card, @options) + assert_failure response + assert_equal response.avs_result['street_match'], 'N' + assert_equal response.avs_result['postal_match'], 'N' + end + + def test_cv2_failure + assert response = @gateway.purchase(@amount, @cv2_card, @options) + assert_failure response + assert_equal response.cvv_result['code'], 'N' + end + + def test_avs_cv2_failure + assert response = @gateway.purchase(@amount, @avs_cv2_card, @options) + assert_failure response + assert_equal response.avs_result['street_match'], 'N' + assert_equal response.avs_result['postal_match'], 'N' + assert_equal response.cvv_result['code'], 'N' end def test_authorize_and_capture @@ -62,7 +86,7 @@ def test_successful_authorization def test_failed_authorization assert response = @gateway.authorize(@amount, @declined_card, @options) assert response.test? - assert_equal 'Card declined', response.message + assert_match %r{Card declined}i, response.message assert_equal false, response.success? end @@ -77,8 +101,8 @@ def test_successful_authorization_and_failed_capture end def test_failed_capture_bad_auth_info - assert auth = @gateway.authorize(@amount, @credit_card, @options) - assert capture = @gateway.capture(@amount, "a;b;c", @options) + assert @gateway.authorize(@amount, @credit_card, @options) + assert capture = @gateway.capture(@amount, 'a;b;c', @options) assert_failure capture end @@ -94,7 +118,7 @@ def test_successful_purchase_by_reference def test_failed_purchase_by_reference assert response = @gateway.authorize(1, @credit_card, @options) assert_success response - assert(reference = response.authorization) + assert response.authorization assert response = @gateway.purchase(@amount, 'bogusref', {:order_id => generate_unique_id}) assert_failure response @@ -134,7 +158,7 @@ def test_successful_void end def test_failed_void - assert response = @gateway.void("bogus") + assert response = @gateway.void('bogus') assert_failure response end diff --git a/test/remote/gateways/remote_itransact_test.rb b/test/remote/gateways/remote_itransact_test.rb index 3ec0172e6b8..d949b17232d 100644 --- a/test/remote/gateways/remote_itransact_test.rb +++ b/test/remote/gateways/remote_itransact_test.rb @@ -1,36 +1,35 @@ require 'test_helper' class RemoteItransactTest < Test::Unit::TestCase - def setup @gateway = ItransactGateway.new(fixtures(:itransact)) - + @amount = 1065 @credit_card = credit_card('4000100011112224') @declined_card = credit_card('4000300011112220') - - @options = { + + @options = { :order_id => '1', :billing_address => address, :description => 'Store Purchase' } end - + def test_successful_purchase assert response = @gateway.purchase(@amount, @credit_card, @options) assert_success response assert_nil response.message end -# As of March 8, 2012, iTransact does not provide a way to generate unsuccessful transactions through use of a -# production gateway account in test mode. -# def test_unsuccessful_purchase -# assert response = @gateway.purchase(@amount, @credit_card, @options) -# assert_failure response -# assert_equal 'DECLINE', response.params['error_category'] -# assert_equal 'Code: NBE001 Your credit card was declined by the credit card processing network. Please use another card and resubmit your transaction.', response.message -# end + # As of March 8, 2012, iTransact does not provide a way to generate unsuccessful transactions through use of a + # production gateway account in test mode. + # def test_unsuccessful_purchase + # assert response = @gateway.purchase(@amount, @credit_card, @options) + # assert_failure response + # assert_equal 'DECLINE', response.params['error_category'] + # assert_equal 'Code: NBE001 Your credit card was declined by the credit card processing network. Please use another card and resubmit your transaction.', response.message + # end def test_authorize_and_capture amount = @amount @@ -42,13 +41,13 @@ def test_authorize_and_capture assert_success capture end -# As of March 8, 2012, iTransact does not provide a way to generate unsuccessful transactions through use of a -# production gateway account in test mode. -# def test_failed_capture -# assert response = @gateway.capture(@amount, '9999999999') -# assert_failure response -# assert_equal 'REPLACE WITH GATEWAY FAILURE MESSAGE', response.message -# end + # As of March 8, 2012, iTransact does not provide a way to generate unsuccessful transactions through use of a + # production gateway account in test mode. + # def test_failed_capture + # assert response = @gateway.capture(@amount, '9999999999') + # assert_failure response + # assert_equal 'REPLACE WITH GATEWAY FAILURE MESSAGE', response.message + # end def test_authorize_and_void amount = @amount @@ -65,11 +64,11 @@ def test_void assert_success void end -# As of Sep 19, 2012, iTransact REQUIRES the total amount for the refund. -# def test_refund -# assert refund = @gateway.refund(nil, '9999999999') -# assert_success refund -# end + # As of Sep 19, 2012, iTransact REQUIRES the total amount for the refund. + # def test_refund + # assert refund = @gateway.refund(nil, '9999999999') + # assert_success refund + # end def test_refund_partial assert refund = @gateway.refund(555, '9999999999') # $5.55 in cents diff --git a/test/remote/gateways/remote_iveri_test.rb b/test/remote/gateways/remote_iveri_test.rb new file mode 100644 index 00000000000..16733284a46 --- /dev/null +++ b/test/remote/gateways/remote_iveri_test.rb @@ -0,0 +1,164 @@ +require 'test_helper' + +class RemoteIveriTest < Test::Unit::TestCase + def setup + @gateway = IveriGateway.new(fixtures(:iveri)) + + @amount = 100 + @credit_card = credit_card('4242424242424242') + @bad_card = credit_card('2121212121212121') + @timeout_card = credit_card('5454545454545454') + @invalid_card = credit_card('1111222233334444') + @options = { + order_id: generate_unique_id, + billing_address: address, + description: 'Store Purchase' + } + end + + def test_successful_purchase + response = @gateway.purchase(@amount, @credit_card, @options) + + assert_success response + assert_equal 'Succeeded', response.message + assert_equal '100', response.params['amount'] + end + + def test_successful_purchase_with_more_options + options = { + ip: '127.0.0.1', + email: 'joe@example.com', + currency: 'ZAR' + } + + response = @gateway.purchase(@amount, @credit_card, options) + assert_success response + assert_equal 'Succeeded', response.message + end + + def test_successful_purchase_with_3ds_params + options = { + eci: 'ThreeDSecure', + xid: SecureRandom.hex(14), + cavv: SecureRandom.hex(14) + } + + response = @gateway.purchase(@amount, @credit_card, options) + assert_success response + assert_equal 'Succeeded', response.message + end + + def test_failed_purchase + response = @gateway.purchase(@amount, @bad_card, @options) + assert_failure response + assert_includes ['Denied', 'Hot card', 'Please call'], response.message + end + + def test_successful_authorize_and_capture + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + assert capture = @gateway.capture(@amount, auth.authorization) + assert_success capture + assert_equal 'Succeeded', capture.message + end + + def test_failed_authorize + response = @gateway.authorize(@amount, @bad_card, @options) + assert_failure response + assert_includes ['Denied', 'Hot card', 'Please call'], response.message + end + + def test_partial_capture + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + assert capture = @gateway.capture(@amount-1, auth.authorization) + assert_success capture + end + + def test_failed_capture + response = @gateway.capture(@amount, '') + assert_failure response + assert_equal 'Missing PAN', response.message + end + + def test_successful_refund + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + + assert refund = @gateway.refund(@amount, purchase.authorization, @options) + assert_success refund + assert_equal 'Succeeded', refund.message + end + + def test_partial_refund + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + + assert refund = @gateway.refund(@amount-1, purchase.authorization) + assert_success refund + end + + def test_failed_refund + response = @gateway.refund(@amount, '', @options) + assert_failure response + assert_match %r{Credit is not supported}, response.message + end + + def test_successful_void + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + assert void = @gateway.void(auth.authorization) + assert_success void + assert_equal 'Succeeded', void.message + end + + def test_failed_void + response = @gateway.void('') + assert_failure response + assert_equal 'Missing OriginalMerchantTrace', response.message + end + + def test_successful_verify + response = @gateway.verify(@credit_card, @options) + assert_success response + assert_equal 'Succeeded', response.message + end + + def test_failed_verify + response = @gateway.verify(@bad_card, @options) + assert_failure response + assert_includes ['Denied', 'Hot card', 'Please call'], response.message + end + + def test_successful_verify_credentials + assert @gateway.verify_credentials + end + + def test_failed_verify_credentials + gateway = IveriGateway.new(app_id: '11111111-1111-1111-1111-111111111111', cert_id: '11111111-1111-1111-1111-111111111111') + assert !gateway.verify_credentials + end + + def test_invalid_login + gateway = IveriGateway.new(app_id: '', cert_id: '') + + response = gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_equal 'No CertificateID specified', response.message + end + + def test_transcript_scrubbing + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @credit_card, @options) + end + transcript = @gateway.scrub(transcript) + + assert_scrubbed(@credit_card.number, transcript) + assert_scrubbed(@credit_card.verification_value, transcript) + assert_scrubbed(@gateway.options[:cert_id], transcript) + end + +end diff --git a/test/remote/gateways/remote_jetpay_test.rb b/test/remote/gateways/remote_jetpay_test.rb index 3ba2729f3a8..d5d331a72f1 100644 --- a/test/remote/gateways/remote_jetpay_test.rb +++ b/test/remote/gateways/remote_jetpay_test.rb @@ -4,12 +4,11 @@ class RemoteJetpayTest < Test::Unit::TestCase def setup @gateway = JetpayGateway.new(fixtures(:jetpay)) - + @credit_card = credit_card('4000300020001000') @declined_card = credit_card('4000300020001000') - + @options = { - :order_id => '1', :billing_address => address(:country => 'US', :zip => '75008'), :shipping_address => address(:country => 'US'), :email => 'test@test.com', @@ -18,73 +17,141 @@ def setup :tax => 7 } end - + def test_successful_purchase assert response = @gateway.purchase(9900, @credit_card, @options) assert_success response - assert_equal "APPROVED", response.message + assert_equal 'APPROVED', response.message assert_not_nil response.authorization - assert_not_nil response.params["approval"] + assert_not_nil response.params['approval'] end - + def test_unsuccessful_purchase assert response = @gateway.purchase(5205, @declined_card, @options) assert_failure response - assert_equal "Do not honor.", response.message + assert_equal 'Do not honor.', response.message + end + + def test_successful_purchase_with_origin + assert response = @gateway.purchase(9900, @credit_card, {:origin => 'RECURRING'}) + assert_success response + assert_equal 'APPROVED', response.message + assert_not_nil response.authorization + assert_not_nil response.params['approval'] end - + def test_authorize_and_capture assert auth = @gateway.authorize(9900, @credit_card, @options) assert_success auth assert_equal 'APPROVED', auth.message assert_not_nil auth.authorization - assert_not_nil auth.params["approval"] - + assert_not_nil auth.params['approval'] + assert capture = @gateway.capture(9900, auth.authorization) assert_success capture end - + + def test_partial_capture + assert auth = @gateway.authorize(9900, @credit_card, @options) + assert_success auth + assert_equal 'APPROVED', auth.message + assert_not_nil auth.authorization + assert_not_nil auth.params['approval'] + + assert capture = @gateway.capture(4400, auth.authorization) + assert_success capture + end + + def test_ud_fields_on_purchase + assert response = @gateway.purchase(9900, @credit_card, @options.merge(ud_field_1: 'Value1', ud_field_2: 'Value2', ud_field3: 'Value3')) + assert_success response + end + + def test_ud_fields_on_capture + assert auth = @gateway.authorize(9900, @credit_card, @options) + assert_success auth + + assert capture = @gateway.capture(9900, auth.authorization, @options.merge(ud_field_1: 'Value1', ud_field_2: 'Value2', ud_field3: 'Value3')) + assert_success capture + end + def test_void # must void a valid auth assert auth = @gateway.authorize(9900, @credit_card, @options) assert_success auth assert_equal 'APPROVED', auth.message assert_not_nil auth.authorization - assert_not_nil auth.params["approval"] - + assert_not_nil auth.params['approval'] + assert void = @gateway.void(auth.authorization) assert_success void end - - def test_refund + + def test_purchase_refund_with_token + assert response = @gateway.purchase(9900, @credit_card, @options) + assert_success response + assert_equal 'APPROVED', response.message + assert_not_nil response.authorization + assert_not_nil response.params['approval'] + + # linked to a specific transaction_id + assert credit = @gateway.refund(9900, response.authorization) + assert_success credit + assert_not_nil(credit.authorization) + assert_not_nil(response.params['approval']) + assert_equal [response.params['transaction_id'], response.params['approval'], 9900, response.params['token']].join(';'), response.authorization + end + + def test_capture_refund_with_token + assert auth = @gateway.authorize(9900, @credit_card, @options) + assert_success auth + assert_equal 'APPROVED', auth.message + assert_not_nil auth.authorization + assert_not_nil auth.params['approval'] + assert_equal [auth.params['transaction_id'], auth.params['approval'], 9900, auth.params['token']].join(';'), auth.authorization + + assert capture = @gateway.capture(9900, auth.authorization) + assert_success capture + assert_equal [capture.params['transaction_id'], capture.params['approval'], 9900, auth.params['token']].join(';'), capture.authorization + + # linked to a specific transaction_id + assert refund = @gateway.refund(9900, capture.authorization) + assert_success refund + assert_not_nil(refund.authorization) + assert_not_nil(refund.params['approval']) + end + + def test_refund_backwards_compatible # no need for csv card = credit_card('4242424242424242', :verification_value => nil) - + assert response = @gateway.purchase(9900, card, @options) assert_success response - assert_equal "APPROVED", response.message + assert_equal 'APPROVED', response.message assert_not_nil response.authorization - assert_not_nil response.params["approval"] - + assert_not_nil response.params['approval'] + + old_authorization = [response.params['transaction_id'], response.params['approval'], 9900].join(';') + # linked to a specific transaction_id - assert credit = @gateway.refund(9900, response.authorization, :credit_card => card) + assert credit = @gateway.refund(9900, old_authorization, :credit_card => card) assert_success credit assert_not_nil(credit.authorization) - assert_not_nil(response.params["approval"]) - assert_equal [response.params['transaction_id'], response.params["approval"], 9900].join(";"), response.authorization + assert_not_nil(response.params['approval']) + assert_equal [response.params['transaction_id'], response.params['approval'], 9900, response.params['token']].join(';'), response.authorization end - + def test_credit # no need for csv card = credit_card('4242424242424242', :verification_value => nil) - + # no link to a specific transaction_id assert credit = @gateway.credit(9900, card) assert_success credit assert_not_nil(credit.authorization) - assert_not_nil(credit.params["approval"]) + assert_not_nil(credit.params['approval']) end - + def test_failed_capture assert response = @gateway.capture(9900, '7605f7c5d6e8f74deb') assert_failure response @@ -95,15 +162,27 @@ def test_invalid_login gateway = JetpayGateway.new(:login => 'bogus') assert response = gateway.purchase(9900, @credit_card, @options) assert_failure response - - assert_equal 'Terminal ID Not Found.', response.message + + assert_equal 'Bad Terminal ID.', response.message end - + def test_missing_login gateway = JetpayGateway.new(:login => '') assert response = gateway.purchase(9900, @credit_card, @options) assert_failure response - + assert_equal 'No response returned (missing credentials?).', response.message end + + def test_transcript_scrubbing + @amount = 9900 + @credit_card.verification_value = '421' + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @credit_card, @options) + end + clean_transcript = @gateway.scrub(transcript) + + assert_scrubbed(@credit_card.number, clean_transcript) + assert_scrubbed(@credit_card.verification_value.to_s, clean_transcript) + end end diff --git a/test/remote/gateways/remote_jetpay_v2_certification_test.rb b/test/remote/gateways/remote_jetpay_v2_certification_test.rb new file mode 100644 index 00000000000..be182a8be37 --- /dev/null +++ b/test/remote/gateways/remote_jetpay_v2_certification_test.rb @@ -0,0 +1,348 @@ +require 'test_helper' + +class RemoteJetpayV2CertificationTest < Test::Unit::TestCase + + def setup + @gateway = JetpayV2Gateway.new(fixtures(:jetpay_v2)) + + @unique_id = '' + + @options = { + :device => 'spreedly', + :application => 'spreedly', + :developer_id => 'GenkID', + :billing_address => address(:address1 => '1234 Fifth Street', :address2 => '', :city => 'Beaumont', :state => 'TX', :country => 'US', :zip => '77708'), + :shipping_address => address(:address1 => '1234 Fifth Street', :address2 => '', :city => 'Beaumont', :state => 'TX', :country => 'US', :zip => '77708'), + :email => 'test@test.com', + :ip => '127.0.0.1' + } + end + + def teardown + puts "\n#{@options[:order_id]}: #{@unique_id}" + end + + def test_certification_cnp1_authorize_mastercard + @options[:order_id] = 'CNP1' + amount = 1000 + master = credit_card('5111111111111118', :month => 12, :year => 2017, :brand => 'master', :verification_value => '121') + assert response = @gateway.authorize(amount, master, @options) + assert_success response + assert_equal 'APPROVED', response.message + @unique_id = response.params['unique_id'] + end + + def test_certification_cnp2_authorize_visa + @options[:order_id] = 'CNP2' + amount = 1105 + visa = credit_card('4111111111111111', :month => 12, :year => 2017, :brand => 'visa', :verification_value => '121') + assert response = @gateway.authorize(amount, visa, @options) + assert_failure response + assert_equal 'Do not honor.', response.message + @unique_id = response.params['unique_id'] + end + + def test_certification_cnp3_cnp4_authorize_and_capture_amex + @options[:order_id] = 'CNP3' + amount = 1200 + amex = credit_card('378282246310005', :month => 12, :year => 2017, :brand => 'american_express', :verification_value => '1221') + assert response = @gateway.authorize(amount, amex, @options) + assert_success response + assert_equal 'APPROVED', response.message + @unique_id = response.params['unique_id'] + puts "\n#{@options[:order_id]}: #{@unique_id}" + + @options[:order_id] = 'CNP4' + assert response = @gateway.capture(amount, response.authorization, @options) + assert_success response + @unique_id = response.params['unique_id'] + end + + def test_certification_cnp5_purchase_discover + @options[:order_id] = 'CNP5' + amount = 1300 + discover = credit_card('6011111111111117', :month => 12, :year => 2017, :brand => 'discover', :verification_value => '121') + assert response = @gateway.purchase(amount, discover, @options) + assert_success response + assert_equal 'APPROVED', response.message + @unique_id = response.params['unique_id'] + end + + def test_certification_cnp6_purchase_visa + @options[:order_id] = 'CNP6' + amount = 1405 + visa = credit_card('4111111111111111', :month => 12, :year => 2017, :brand => 'visa', :verification_value => '120') + assert response = @gateway.purchase(amount, visa, @options) + assert_failure response + assert_equal 'Do not honor.', response.message + @unique_id = response.params['unique_id'] + end + + def test_certification_cnp7_authorize_mastercard + @options[:order_id] = 'CNP7' + amount = 1500 + master = credit_card('5111111111111118', :month => 12, :year => 2017, :brand => 'master', :verification_value => '120') + assert response = @gateway.authorize(amount, master, @options) + assert_success response + assert_equal 'APPROVED', response.message + @unique_id = response.params['unique_id'] + end + + def test_certification_cnp8_authorize_visa + @options[:order_id] = 'CNP8' + amount = 1605 + visa = credit_card('4111111111111111', :month => 12, :year => 2017, :brand => 'visa', :verification_value => '120') + assert response = @gateway.authorize(amount, visa, @options) + assert_failure response + assert_equal 'Do not honor.', response.message + @unique_id = response.params['unique_id'] + end + + def test_certification_cnp9_cnp10_authorize_and_capture_amex + @options[:order_id] = 'CNP9' + amount = 1700 + amex = credit_card('378282246310005', :month => 12, :year => 2017, :brand => 'american_express', :verification_value => '1220') + assert response = @gateway.authorize(amount, amex, @options) + assert_success response + assert_equal 'APPROVED', response.message + @unique_id = response.params['unique_id'] + puts "\n#{@options[:order_id]}: #{@unique_id}" + + @options[:order_id] = 'CNP10' + assert response = @gateway.capture(amount, response.authorization, @options) + assert_success response + @unique_id = response.params['unique_id'] + end + + def test_certification_cnp11_purchase_discover + @options[:order_id] = 'CNP11' + amount = 1800 + discover = credit_card('6011111111111117', :month => 12, :year => 2017, :brand => 'discover', :verification_value => '120') + assert response = @gateway.purchase(amount, discover, @options) + assert_success response + assert_equal 'APPROVED', response.message + @unique_id = response.params['unique_id'] + end + + def test_certification_rec01_recurring_mastercard + @options[:order_id] = 'REC01' + @options[:origin] = 'RECURRING' + @options[:billing_address] = nil + @options[:shipping_address] = nil + amount = 2000 + master = credit_card('5111111111111118', :month => 12, :year => 2017, :brand => 'master', :verification_value => '120') + assert response = @gateway.purchase(amount, master, @options) + assert_success response + assert_equal 'APPROVED', response.message + @unique_id = response.params['unique_id'] + end + + def test_certification_rec02_recurring_visa + @options[:order_id] = 'REC02' + @options[:origin] = 'RECURRING' + amount = 2100 + visa = credit_card('4111111111111111', :month => 12, :year => 2017, :brand => 'visa', :verification_value => '') + assert response = @gateway.purchase(amount, visa, @options) + assert_success response + assert_equal 'APPROVED', response.message + @unique_id = response.params['unique_id'] + end + + def test_certification_rec03_recurring_amex + @options[:order_id] = 'REC03' + @options[:origin] = 'RECURRING' + amount = 2200 + amex = credit_card('378282246310005', :month => 12, :year => 2017, :brand => 'american_express', :verification_value => '1221') + assert response = @gateway.purchase(amount, amex, @options) + assert_success response + assert_equal 'APPROVED', response.message + @unique_id = response.params['unique_id'] + end + + def test_certification_corp07_corp08_authorize_and_capture_discover + @options[:order_id] = 'CORP07' + amount = 2500 + discover = credit_card('6011111111111117', :month => 12, :year => 2018, :brand => 'discover', :verification_value => '120') + assert response = @gateway.authorize(amount, discover, @options) + assert_success response + assert_equal 'APPROVED', response.message + @unique_id = response.params['unique_id'] + puts "\n#{@options[:order_id]}: #{@unique_id}" + + @options[:order_id] = 'CORP08' + assert response = @gateway.capture(amount, response.authorization, @options.merge(:tax_amount => '200')) + assert_success response + @unique_id = response.params['unique_id'] + end + + def test_certification_corp09_corp10_authorize_and_capture_visa + @options[:order_id] = 'CORP09' + amount = 5000 + visa = credit_card('4111111111111111', :month => 12, :year => 2018, :brand => 'visa', :verification_value => '120') + assert response = @gateway.authorize(amount, visa, @options) + assert_success response + assert_equal 'APPROVED', response.message + @unique_id = response.params['unique_id'] + puts "\n#{@options[:order_id]}: #{@unique_id}" + + @options[:order_id] = 'CORP10' + assert response = @gateway.capture(amount, response.authorization, @options.merge(:tax_amount => '0', :tax_exempt => 'true')) + assert_success response + @unique_id = response.params['unique_id'] + end + + def test_certification_corp11_corp12_authorize_and_capture_mastercard + @options[:order_id] = 'CORP11' + amount = 7500 + master = credit_card('5111111111111118', :month => 12, :year => 2018, :brand => 'master', :verification_value => '120') + assert response = @gateway.authorize(amount, master, @options) + assert_success response + assert_equal 'APPROVED', response.message + @unique_id = response.params['unique_id'] + puts "\n#{@options[:order_id]}: #{@unique_id}" + + @options[:order_id] = 'CORP12' + assert response = @gateway.capture(amount, response.authorization, @options.merge(:tax_amount => '0', :tax_exempt => 'false', :purchase_order => '456456')) + assert_success response + @unique_id = response.params['unique_id'] + end + + def test_certification_cred02_credit_visa + @options[:order_id] = 'CRED02' + amount = 100 + visa = credit_card('4111111111111111', :month => 12, :year => 2017, :brand => 'visa', :verification_value => '120') + assert response = @gateway.credit(amount, visa, @options) + assert_success response + assert_equal 'APPROVED', response.message + @unique_id = response.params['unique_id'] + end + + def test_certification_cred03_credit_amex + @options[:order_id] = 'CRED03' + amount = 200 + amex = credit_card('378282246310005', :month => 12, :year => 2017, :brand => 'american_express', :verification_value => '1220') + assert response = @gateway.credit(amount, amex, @options) + assert_success response + assert_equal 'APPROVED', response.message + @unique_id = response.params['unique_id'] + end + + def test_certification_void03_void04_purchase_void_visa + @options[:order_id] = 'VOID03' + amount = 300 + visa = credit_card('4111111111111111', :month => 12, :year => 2017, :brand => 'visa', :verification_value => '120') + assert response = @gateway.purchase(amount, visa, @options) + assert_success response + assert_equal 'APPROVED', response.message + @unique_id = response.params['unique_id'] + puts "\n#{@options[:order_id]}: #{@unique_id}" + + @options[:order_id] = 'VOID04' + transaction_id, approval, _amount, token = response.authorization.split(';') + amount = 500 + authorization = [transaction_id, approval, amount, token].join(';') + assert response = @gateway.void(authorization, @options) + assert_failure response + @unique_id = response.params['unique_id'] + end + + def test_certification_void07_void08_void09_authorize_capture_void_discover + @options[:order_id] = 'VOID07' + amount = 400 + discover = credit_card('6011111111111117', :month => 12, :year => 2017, :brand => 'discover', :verification_value => '120') + assert response = @gateway.authorize(amount, discover, @options) + assert_success response + assert_equal 'APPROVED', response.message + @unique_id = response.params['unique_id'] + puts "\n#{@options[:order_id]}: #{@unique_id}" + + @options[:order_id] = 'VOID08' + amount = 600 + assert response = @gateway.capture(amount, response.authorization, @options) + assert_success response + @unique_id = response.params['unique_id'] + puts "\n#{@options[:order_id]}: #{@unique_id}" + + @options[:order_id] = 'VOID09' + assert response = @gateway.void(response.authorization, @options) + assert_success response + @unique_id = response.params['unique_id'] + end + + def test_certification_void12_void13_credit_void_visa + @options[:order_id] = 'VOID12' + amount = 800 + visa = credit_card('4111111111111111', :month => 12, :year => 2017, :brand => 'visa', :verification_value => '120') + assert response = @gateway.credit(amount, visa, @options) + assert_success response + assert_equal 'APPROVED', response.message + @unique_id = response.params['unique_id'] + puts "\n#{@options[:order_id]}: #{@unique_id}" + + @options[:order_id] = 'VOID13' + assert response = @gateway.void(response.authorization, @options) + assert_success response + @unique_id = response.params['unique_id'] + end + + def test_certification_tok15_tokenize_mastercard + @options[:order_id] = 'TOK15' + master = credit_card('5111111111111118', :month => 12, :year => 2017, :brand => 'master', :verification_value => '101') + assert response = @gateway.store(master, @options) + assert_success response + assert_equal 'APPROVED', response.message + assert_equal 'TOKENIZED', response.params['response_text'] + @unique_id = response.params['unique_id'] + end + + def test_certification_tok16_authorize_with_token_request_visa + @options[:order_id] = 'TOK16' + amount = 3100 + visa = credit_card('4111111111111111', :month => 12, :year => 2017, :brand => 'visa', :verification_value => '101') + assert response = @gateway.authorize(amount, visa, @options) + assert_success response + assert_equal 'APPROVED', response.message + _transaction_id, _approval, _amount, token = response.authorization.split(';') + assert_equal token, response.params['token'] + @unique_id = response.params['unique_id'] + end + + def test_certification_tok17_purchase_with_token_request_amex + @options[:order_id] = 'TOK17' + amount = 3200 + amex = credit_card('378282246310005', :month => 12, :year => 2017, :brand => 'american_express', :verification_value => '1001') + assert response = @gateway.purchase(amount, amex, @options) + assert_success response + assert_equal 'APPROVED', response.message + _transaction_id, _approval, _amount, token = response.authorization.split(';') + assert_equal token, response.params['token'] + @unique_id = response.params['unique_id'] + end + + def test_certification_tok18_authorize_using_token_mastercard + master = credit_card('5111111111111118', :month => 12, :year => 2017, :brand => 'master', :verification_value => '101') + assert response = @gateway.store(master, @options) + assert_success response + + @options[:order_id] = 'TOK18' + amount = 3300 + assert response = @gateway.authorize(amount, response.authorization, @options) + assert_success response + assert_equal 'APPROVED', response.message + @unique_id = response.params['unique_id'] + end + + def test_certification_tok19_purchase_using_token_visa + visa = credit_card('4111111111111111', :month => 12, :year => 2017, :brand => 'visa', :verification_value => '101') + assert response = @gateway.store(visa, @options) + assert_success response + + @options[:order_id] = 'TOK19' + amount = 3400 + assert response = @gateway.purchase(amount, response.authorization, @options) + assert_success response + assert_equal 'APPROVED', response.message + @unique_id = response.params['unique_id'] + end + +end diff --git a/test/remote/gateways/remote_jetpay_v2_test.rb b/test/remote/gateways/remote_jetpay_v2_test.rb new file mode 100644 index 00000000000..10f1ea6322f --- /dev/null +++ b/test/remote/gateways/remote_jetpay_v2_test.rb @@ -0,0 +1,205 @@ +require 'test_helper' + +class RemoteJetpayV2Test < Test::Unit::TestCase + + def setup + @gateway = JetpayV2Gateway.new(fixtures(:jetpay_v2)) + + @credit_card = credit_card('4000300020001000') + + @amount_approved = 9900 + @amount_declined = 5205 + + @options = { + :device => 'spreedly', + :application => 'spreedly', + :developer_id => 'GenkID', + :billing_address => address(:city => 'Durham', :state => 'NC', :country => 'US', :zip => '27701'), + :shipping_address => address(:city => 'Durham', :state => 'NC', :country => 'US', :zip => '27701'), + :email => 'test@test.com', + :ip => '127.0.0.1', + :order_id => '12345' + } + end + + def test_successful_purchase + assert response = @gateway.purchase(@amount_approved, @credit_card, @options) + assert_success response + assert_equal 'APPROVED', response.message + assert_not_nil response.authorization + assert_not_nil response.params['approval'] + end + + def test_failed_purchase + assert response = @gateway.purchase(@amount_declined, @credit_card, @options) + assert_failure response + assert_equal 'Do not honor.', response.message + end + + def test_successful_purchase_with_minimal_options + assert response = @gateway.purchase(@amount_approved, @credit_card, {:device => 'spreedly', :application => 'spreedly'}) + assert_success response + assert_equal 'APPROVED', response.message + assert_not_nil response.authorization + assert_not_nil response.params['approval'] + end + + def test_successful_purchase_with_additional_options + options = @options.merge( + ud_field_1: 'Value1', + ud_field_2: 'Value2', + ud_field_3: 'Value3' + ) + assert response = @gateway.purchase(@amount_approved, @credit_card, options) + assert_success response + end + + def test_successful_authorize_and_capture + assert auth = @gateway.authorize(@amount_approved, @credit_card, @options) + assert_success auth + assert_equal 'APPROVED', auth.message + assert_not_nil auth.authorization + assert_not_nil auth.params['approval'] + + assert capture = @gateway.capture(@amount_approved, auth.authorization, @options) + assert_success capture + end + + def test_successful_authorize_and_capture_with_tax + assert auth = @gateway.authorize(@amount_approved, @credit_card, @options) + assert_success auth + assert_equal 'APPROVED', auth.message + assert_not_nil auth.authorization + assert_not_nil auth.params['approval'] + + assert capture = @gateway.capture(@amount_approved, auth.authorization, @options.merge(:tax_amount => '990', :purchase_order => 'ABC12345')) + assert_success capture + end + + def test_successful_partial_capture + assert auth = @gateway.authorize(9900, @credit_card, @options) + assert_success auth + assert_equal 'APPROVED', auth.message + assert_not_nil auth.authorization + assert_not_nil auth.params['approval'] + + assert capture = @gateway.capture(4400, auth.authorization, @options) + assert_success capture + end + + def test_failed_capture + assert response = @gateway.capture(@amount_approved, '7605f7c5d6e8f74deb', @options) + assert_failure response + assert_equal 'Transaction Not Found.', response.message + end + + def test_successful_void + assert auth = @gateway.authorize(@amount_approved, @credit_card, @options) + assert_success auth + assert_equal 'APPROVED', auth.message + assert_not_nil auth.authorization + assert_not_nil auth.params['approval'] + + assert void = @gateway.void(auth.authorization, @options) + assert_success void + end + + def test_failed_void + assert void = @gateway.void('bogus', @options) + assert_failure void + end + + def test_successful_purchase_refund + assert response = @gateway.purchase(@amount_approved, @credit_card, @options) + assert_success response + assert_equal 'APPROVED', response.message + assert_not_nil response.authorization + assert_not_nil response.params['approval'] + + assert refund = @gateway.refund(@amount_approved, response.authorization, @options) + assert_success refund + assert_not_nil(refund.authorization) + assert_not_nil(response.params['approval']) + assert_equal [response.params['transaction_id'], response.params['approval'], @amount_approved, response.params['token']].join(';'), response.authorization + end + + def test_successful_capture_refund + assert auth = @gateway.authorize(@amount_approved, @credit_card, @options) + assert_success auth + assert_equal 'APPROVED', auth.message + assert_not_nil auth.authorization + assert_not_nil auth.params['approval'] + assert_equal [auth.params['transaction_id'], auth.params['approval'], @amount_approved, auth.params['token']].join(';'), auth.authorization + + assert capture = @gateway.capture(@amount_approved, auth.authorization, @options) + assert_success capture + assert_equal [capture.params['transaction_id'], capture.params['approval'], @amount_approved, auth.params['token']].join(';'), capture.authorization + + assert refund = @gateway.refund(@amount_approved, capture.authorization, @options) + assert_success refund + assert_not_nil(refund.authorization) + assert_not_nil(refund.params['approval']) + end + + def test_failed_refund + assert refund = @gateway.refund(@amount_approved, 'bogus', @options) + assert_failure refund + end + + def test_successful_credit + card = credit_card('4242424242424242', :verification_value => nil) + + assert credit = @gateway.credit(@amount_approved, card, @options) + assert_success credit + assert_not_nil(credit.authorization) + assert_not_nil(credit.params['approval']) + end + + def test_failed_credit + card = credit_card('2424242424242424', :verification_value => nil) + + assert credit = @gateway.credit(@amount_approved, card, @options) + assert_failure credit + assert_match %r{Invalid card format}, credit.message + end + + def test_successful_verify + assert verify = @gateway.verify(@credit_card, @options) + assert_success verify + end + + def test_failed_verify + card = credit_card('2424242424242424', :verification_value => nil) + + assert verify = @gateway.verify(card, @options) + assert_failure verify + assert_match %r{Invalid card format}, verify.message + end + + def test_invalid_login + gateway = JetpayV2Gateway.new(:login => 'bogus') + assert response = gateway.purchase(@amount_approved, @credit_card, @options) + assert_failure response + + assert_equal 'Bad Terminal ID.', response.message + end + + def test_missing_login + gateway = JetpayV2Gateway.new(:login => '') + assert response = gateway.purchase(@amount_approved, @credit_card, @options) + assert_failure response + + assert_equal 'No response returned (missing credentials?).', response.message + end + + def test_transcript_scrubbing + @credit_card.verification_value = '421' + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount_approved, @credit_card, @options) + end + clean_transcript = @gateway.scrub(transcript) + + assert_scrubbed(@credit_card.number, clean_transcript) + assert_scrubbed(@credit_card.verification_value.to_s, clean_transcript) + end +end diff --git a/test/remote/gateways/remote_komoju_test.rb b/test/remote/gateways/remote_komoju_test.rb new file mode 100644 index 00000000000..5214eb01fbf --- /dev/null +++ b/test/remote/gateways/remote_komoju_test.rb @@ -0,0 +1,72 @@ +require 'test_helper' +require 'securerandom' + +class RemoteKomojuTest < Test::Unit::TestCase + def setup + @gateway = KomojuGateway.new(fixtures(:komoju)) + + @amount = 100 + @credit_card = credit_card('4111111111111111') + @declined_card = credit_card('4123111111111059') + @fraudulent_card = credit_card('4123111111111083') + + @options = { + :order_id => generate_unique_id, + :description => 'Store Purchase', + :tax => '10.0', + :ip => '192.168.0.1', + :email => 'valid@email.com', + :browser_language => 'en', + :browser_user_agent => 'user_agent' + } + end + + def test_successful_credit_card_purchase + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert response.authorization.present? + assert_equal 'Transaction succeeded', response.message + assert_equal 100, response.params['amount'] + assert_equal '1111', response.params['payment_details']['last_four_digits'] + assert_equal true, response.params['captured_at'].present? + end + + def test_successful_credit_card_purchase_with_minimal_options + response = @gateway.purchase(@amount, @credit_card, {}) + assert_success response + assert response.authorization.present? + assert_equal 'Transaction succeeded', response.message + assert_equal 100, response.params['amount'] + assert_equal '1111', response.params['payment_details']['last_four_digits'] + assert_equal true, response.params['captured_at'].present? + end + + def test_successful_credit_card_refund + purchase_response = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase_response + assert purchase_response.authorization.present? + refund_response = @gateway.refund(@amount, purchase_response.authorization, {}) + assert_success refund_response + assert_equal 'refunded', refund_response.params['status'] + end + + def test_failed_credit_card_purchase + response = @gateway.purchase(@amount, @declined_card, @options) + assert_failure response + assert response.authorization.blank? + assert_equal 'card_declined', response.error_code + end + + def test_detected_fraud + response = @gateway.purchase(@amount, @fraudulent_card, @options) + assert_failure response + assert response.authorization.blank? + assert_equal 'fraudulent', response.error_code + end + + def test_invalid_login + gateway = KomojuGateway.new(:login => 'abc') + response = gateway.purchase(@amount, @credit_card, @options) + assert_failure response + end +end diff --git a/test/remote/gateways/remote_kushki_test.rb b/test/remote/gateways/remote_kushki_test.rb new file mode 100644 index 00000000000..b4eb060de00 --- /dev/null +++ b/test/remote/gateways/remote_kushki_test.rb @@ -0,0 +1,107 @@ +require 'test_helper' + +class RemoteKushkiTest < Test::Unit::TestCase + def setup + @gateway = KushkiGateway.new(fixtures(:kushki)) + @amount = 100 + @credit_card = credit_card('4000100011112224', verification_value: '777') + @declined_card = credit_card('4000300011112220') + end + + def test_successful_purchase + response = @gateway.purchase(@amount, @credit_card) + assert_success response + assert_equal 'Succeeded', response.message + assert_match %r(^\d+$), response.authorization + end + + def test_successful_purchase_with_options + options = { + currency: 'USD', + amount: { + subtotal_iva_0: '4.95', + subtotal_iva: '10', + iva: '1.54', + ice: '3.50' + } + } + + amount = 100 * ( + options[:amount][:subtotal_iva_0].to_f + + options[:amount][:subtotal_iva].to_f + + options[:amount][:iva].to_f + + options[:amount][:ice].to_f + ) + + response = @gateway.purchase(amount, @credit_card, options) + assert_success response + assert_equal 'Succeeded', response.message + assert_match %r(^\d+$), response.authorization + end + + def test_failed_purchase + options = { + amount: { + subtotal_iva: '200' + } + } + + response = @gateway.purchase(@amount, @declined_card, options) + assert_failure response + assert_equal 'Monto de la transacción es diferente al monto de la venta inicial', response.message + end + + def test_successful_refund + purchase = @gateway.purchase(@amount, @credit_card) + assert_success purchase + + assert refund = @gateway.refund(@amount, purchase.authorization) + assert_success refund + assert_equal 'Succeeded', refund.message + end + + def test_failed_refund + purchase = @gateway.purchase(@amount, @credit_card) + assert_success purchase + + assert refund = @gateway.refund(@amount, nil) + assert_failure refund + assert_equal 'Missing Authentication Token', refund.message + end + + def test_successful_void + purchase = @gateway.purchase(@amount, @credit_card) + assert_success purchase + + assert void = @gateway.void(purchase.authorization) + assert_success void + assert_equal 'Succeeded', void.message + end + + def test_failed_void + response = @gateway.void('000') + assert_failure response + assert_equal 'El monto de la transacción es requerido', response.message + end + + def test_invalid_login + gateway = KushkiGateway.new(public_merchant_id: '', private_merchant_id: '') + + response = gateway.purchase(@amount, @credit_card) + assert_failure response + assert_match %r{ID de comercio no válido}, response.message + end + + def test_transcript_scrubbing + assert @gateway.supports_scrubbing? + + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @credit_card) + end + transcript = @gateway.scrub(transcript) + + assert_scrubbed(@credit_card.number, transcript) + assert_scrubbed(@credit_card.verification_value, transcript) + assert_scrubbed(@gateway.options[:private_merchant_id], transcript) + end +end diff --git a/test/remote/gateways/remote_latitude19_test.rb b/test/remote/gateways/remote_latitude19_test.rb new file mode 100644 index 00000000000..ac977badebc --- /dev/null +++ b/test/remote/gateways/remote_latitude19_test.rb @@ -0,0 +1,172 @@ +require 'test_helper' + +class RemoteLatitude19Test < Test::Unit::TestCase + def setup + @gateway = Latitude19Gateway.new(fixtures(:latitude19)) + + @amount = 100 + @credit_card = credit_card('4000100011112224', verification_value: '747') + @declined_card = credit_card('0000000000000000') + + @options = { + order_id: generate_unique_id, + billing_address: address + } + end + + def test_invalid_login + gateway = Latitude19Gateway.new(account_number: '', configuration_id: '', secret: '') + response = gateway.purchase(@amount, @credit_card, @options) + assert_failure response + end + + def test_successful_purchase + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal 'Approved', response.message + assert response.test? + end + + # def test_failed_purchase + # response = @gateway.purchase(@amount, @declined_card, @options) + # assert_failure response + # assert_equal "REPLACE WITH FAILED MESSAGE", response.message + # end + + def test_successful_authorize_and_capture + response = @gateway.authorize(@amount, @credit_card, @options) + assert_success response + assert_equal 'Approved', response.message + assert_match %r(^auth\|\w+$), response.authorization + + capture = @gateway.capture(@amount, response.authorization, @options) + assert_success capture + assert_equal 'Approved', capture.message + end + + # def test_failed_authorize + # response = @gateway.authorize(@amount, @declined_card, @options) + # assert_failure response + # assert_equal "REPLACE WITH FAILED MESSAGE", response.message + # assert_equal "REPLACE WITH FAILED CODE", response.params["error"] + # end + + def test_failed_capture + authorization = 'auth' + '|' + SecureRandom.hex(6) + response = @gateway.capture(@amount, authorization, @options) + assert_failure response + assert_equal 'Not submitted', response.message + assert_equal '400', response.error_code + end + + def test_successful_void + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + void = @gateway.void(auth.authorization, @options) + assert_success void + assert_equal 'Approved', void.message + + # response = @gateway.authorize(@amount, @credit_card, @options) + # assert_success response + # assert_equal "pgwResponseCodeDescription|Approved|responseText|00 -- APPROVAL|processorResponseCode|00", response.message + + # capture = @gateway.capture(@amount, response.authorization, @options) + # assert_success capture + # assert_equal "pgwResponseCodeDescription|Approved|responseText|00 -- APPROVAL|processorResponseCode|00", capture.message + + # void = @gateway.void(capture.authorization, @options) + # assert_success void + # assert_equal "pgwResponseCodeDescription|Approved|responseText|00 -- APPROVAL|processorResponseCode|00", void.message + + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + + void = @gateway.void(purchase.authorization, @options) + assert_success void + assert_equal 'Approved', void.message + end + + def test_failed_void + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + authorization = auth.authorization[0..9] + 'XX' + response = @gateway.void(authorization, @options) + + assert_failure response + assert_equal 'Not submitted', response.message + assert_equal '400', response.error_code + end + + def test_successful_credit + response = @gateway.credit(@amount, @credit_card, @options) + assert_success response + assert_equal 'Approved', response.message + end + + # def test_failed_credit + # response = @gateway.credit(@amount, @declined_card, @options) + # assert_failure response + # assert_equal "REPLACE WITH FAILED MESSAGE", response.message + # end + + def test_successful_verify + response = @gateway.verify(@credit_card, @options) + assert_success response + assert_equal 'Approved', response.message + end + + # def test_failed_verify + # response = @gateway.verify(@declined_card, @options) + # assert_failure response + # assert_equal "REPLACE WITH FAILED MESSAGE", response.message + # assert_equal "REPLACE WITH FAILED CODE", response.params["error"] + # end + + def test_successful_store + store = @gateway.store(@credit_card, @options) + assert_success store + assert_equal 'Approved', store.message + + purchase = @gateway.purchase(@amount, store.authorization, @options) + assert_success purchase + assert_equal 'Approved', purchase.message + + credit = @gateway.credit(@amount, store.authorization, @options) + assert_success credit + assert_equal 'Approved', credit.message + + verify = @gateway.verify(store.authorization, @options) + assert_success verify + assert_equal 'Approved', verify.message + end + + # def test_failed_store + # response = @gateway.store(@declined_card, @options) + # assert_failure response + # assert_equal "REPLACE WITH FAILED MESSAGE", response.message + # assert_equal "REPLACE WITH FAILED CODE", response.params["error"] + # end + + # def test_dump_transcript + # #skip("Transcript scrubbing for this gateway has been tested.") + + # # This test will run a purchase transaction on your gateway + # # and dump a transcript of the HTTP conversation so that + # # you can use that transcript as a reference while + # # implementing your scrubbing logic + # dump_transcript_and_fail(@gateway, @amount, @credit_card, @options) + # end + + def test_transcript_scrubbing + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @credit_card, @options) + end + clean_transcript = @gateway.scrub(transcript) + + assert_scrubbed(@credit_card.number, clean_transcript) + assert_scrubbed(@credit_card.verification_value.to_s, clean_transcript) + assert_scrubbed(@gateway.options[:secret], clean_transcript) + end +end diff --git a/test/remote/gateways/remote_linkpoint_test.rb b/test/remote/gateways/remote_linkpoint_test.rb index feb126ad850..1a51911712d 100644 --- a/test/remote/gateways/remote_linkpoint_test.rb +++ b/test/remote/gateways/remote_linkpoint_test.rb @@ -1,16 +1,16 @@ # -# In order for this test to pass, a valid store number and PEM file -# are required. Unfortunately, with LinkPoint YOU CAN'T JUST USE ANY -# OLD STORE NUMBER. Also, you can't just generate your own PEM file. -# You'll need to use a special PEM file provided by LinkPoint. +# In order for this test to pass, a valid store number and PEM file +# are required. Unfortunately, with LinkPoint YOU CAN'T JUST USE ANY +# OLD STORE NUMBER. Also, you can't just generate your own PEM file. +# You'll need to use a special PEM file provided by LinkPoint. # -# Go to http://www.linkpoint.com/support/sup_teststore.asp to set up +# Go to http://www.linkpoint.com/support/sup_teststore.asp to set up # a test account. Once you receive your test account you can get your # pem file by clicking the Support link on the navigation menu and then # clicking the Download Center link. # # You will also want to change your test account's fraud settings -# while running these tests. Click the admin link at the top of +# while running these tests. Click the admin link at the top of # LinkPoint Central. Then click "set lockout times" under Fraud Settings # You will want to set Duplicate lockout time to 0 so that you can run # the tests more than once without triggering this fraud detection. @@ -23,9 +23,9 @@ class LinkpointTest < Test::Unit::TestCase def setup Base.mode = :test - + @gateway = LinkpointGateway.new(fixtures(:linkpoint)) - + # Test credit card numbers # American Express: 371111111111111 # Discover: 6011-1111-1111-1111 @@ -38,84 +38,100 @@ def setup @credit_card = credit_card('4111111111111111') @options = { :order_id => generate_unique_id, :billing_address => address } end - + def test_successful_authorization assert response = @gateway.authorize(1000, @credit_card, @options) - + assert_instance_of Response, response assert_success response - assert_equal "APPROVED", response.params["approved"] + assert_equal 'APPROVED', response.params['approved'] end - + def test_successful_authorization_and_capture assert authorization = @gateway.authorize(@amount, @credit_card, @options) assert_success authorization assert authorization.test? - + assert capture = @gateway.capture(@amount, authorization.authorization) assert_success capture assert_equal 'ACCEPTED', capture.message end - + def test_successful_purchase_without_cvv2_code @credit_card.verification_value = nil - + assert response = @gateway.purchase(@amount, @credit_card, @options) assert_success response - assert_equal "APPROVED", response.params["approved"] - assert_equal 'NNN', response.params["avs"] + assert_equal 'APPROVED', response.params['approved'] + assert_equal 'NNN', response.params['avs'] end - + def test_successful_purchase_with_cvv2_code assert response = @gateway.purchase(@amount, @credit_card, @options) assert_success response - assert_equal "APPROVED", response.params["approved"] - assert_equal 'NNNM', response.params["avs"] + assert_equal 'APPROVED', response.params['approved'] + assert_equal 'NNNM', response.params['avs'] end - + def test_successful_purchase_and_void purchase = @gateway.purchase(@amount, @credit_card, @options) assert_success purchase - + assert void = @gateway.void(purchase.authorization) assert_success void end - + def test_successfull_purchase_and_credit assert purchase = @gateway.purchase(2400, @credit_card, @options) assert_success purchase - + assert credit = @gateway.credit(2400, purchase.authorization) assert_success credit end def test_successfull_purchase_with_item_entity - @options.merge!({:line_items => [ - {:id => '123456', :description => "Logo T-Shirt", :price => "12.00", :quantity => '1', :options => - [{:name => "Color", :value => "Red"}, {:name => "Size", :value => "XL"}]}, - {:id => '111', :description => "keychain", :price => "3.00", :quantity => '1'}]}) + @options[:line_items] = [ + {:id => '123456', :description => 'Logo T-Shirt', :price => '12.00', :quantity => '1', + :options => [{:name => 'Color', :value => 'Red'}, {:name => 'Size', :value => 'XL'}]}, + {:id => '111', :description => 'keychain', :price => '3.00', :quantity => '1'} + ] assert purchase = @gateway.purchase(1500, @credit_card, @options) assert_success purchase - end def test_successful_recurring_payment - assert response = @gateway.recurring(2400, @credit_card, - :order_id => generate_unique_id, + assert response = @gateway.recurring(2400, @credit_card, + :order_id => generate_unique_id, :installments => 12, - :startdate => "immediate", + :startdate => 'immediate', :periodicity => :monthly, :billing_address => address ) - + assert_success response - assert_equal "APPROVED", response.params["approved"] + assert_equal 'APPROVED', response.params['approved'] end - + def test_declined_purchase_with_invalid_credit_card @credit_card.number = '1111111111111111' assert response = @gateway.purchase(@amount, @credit_card, @options) assert_failure response - assert_equal "DECLINED", response.params["approved"] + assert_equal 'DECLINED', response.params['approved'] + end + + def test_cleans_whitespace_from_pem + @gateway = LinkpointGateway.new(fixtures(:linkpoint).merge(pem: ' ' + fixtures(:linkpoint)[:pem])) + assert response = @gateway.authorize(1000, @credit_card, @options) + assert_success response + end + + def test_transcript_scrubbing + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @credit_card, @options) + end + clean_transcript = @gateway.scrub(transcript) + + assert_scrubbed(@credit_card.number, clean_transcript) + assert_scrubbed(@credit_card.verification_value.to_s, clean_transcript) end end diff --git a/test/remote/gateways/remote_litle_certification_test.rb b/test/remote/gateways/remote_litle_certification_test.rb index 4ae6eb5b1ea..3a9558c701f 100644 --- a/test/remote/gateways/remote_litle_certification_test.rb +++ b/test/remote/gateways/remote_litle_certification_test.rb @@ -2,15 +2,16 @@ class RemoteLitleCertification < Test::Unit::TestCase def setup - Base.gateway_mode = :test - @gateway = LitleGateway.new(fixtures(:litle).merge(:url => "https://cert.litle.com/vap/communicator/online")) + Base.mode = :test + @gateway = LitleGateway.new(fixtures(:litle)) + @gateway.test_url = 'https://payments.vantivprelive.com/vap/communicator/online' end def test1 credit_card = CreditCard.new( :number => '4457010000000009', :month => '01', - :year => '2014', + :year => '2021', :verification_value => '349', :brand => 'visa' ) @@ -18,7 +19,7 @@ def test1 options = { :order_id => '1', :billing_address => { - :name => 'John Smith', + :name => 'John & Mary Smith', :address1 => '1 Main St.', :city => 'Burlington', :state => 'MA', @@ -27,24 +28,24 @@ def test1 } } - auth_assertions(10010, credit_card, options, :avs => "X", :cvv => "M") + auth_assertions(10100, credit_card, options, :avs => 'X', :cvv => 'M') - # 1: authorize avs - authorize_avs_assertions(credit_card, options, :avs => "X", :cvv => "M") + authorize_avs_assertions(credit_card, options, :avs => 'X', :cvv => 'M') - sale_assertions(10010, credit_card, options, :avs => "X", :cvv => "M") + sale_assertions(10100, credit_card, options, :avs => 'X', :cvv => 'M') end def test2 credit_card = CreditCard.new(:number => '5112010000000003', :month => '02', - :year => '2014', :brand => 'master', - :verification_value => '261') + :year => '2021', :brand => 'master', + :verification_value => '261', + :name => 'Mike J. Hammer') options = { :order_id => '2', :billing_address => { - :name => 'Mike J. Hammer', :address1 => '2 Main St.', + :address2 => 'Apt. 222', :city => 'Riverside', :state => 'RI', :zip => '02915', @@ -52,19 +53,18 @@ def test2 } } - auth_assertions(20020, credit_card, options, :avs => "Z", :cvv => "M") + auth_assertions(10100, credit_card, options, :avs => 'Z', :cvv => 'M') - # 2: authorize avs - authorize_avs_assertions(credit_card, options, :avs => "Z", :cvv => "M") + authorize_avs_assertions(credit_card, options, :avs => 'Z', :cvv => 'M') - sale_assertions(20020, credit_card, options, :avs => "Z", :cvv => "M") + sale_assertions(10100, credit_card, options, :avs => 'Z', :cvv => 'M') end def test3 credit_card = CreditCard.new( :number => '6011010000000003', :month => '03', - :year => '2014', + :year => '2021', :verification_value => '758', :brand => 'discover' ) @@ -80,19 +80,18 @@ def test3 :country => 'US' } } - auth_assertions(30030, credit_card, options, :avs => "Z", :cvv => "M") + auth_assertions(10100, credit_card, options, :avs => 'Z', :cvv => 'M') - # 3: authorize avs - authorize_avs_assertions(credit_card, options, :avs => "Z", :cvv => "M") + authorize_avs_assertions(credit_card, options, :avs => 'Z', :cvv => 'M') - sale_assertions(30030, credit_card, options, :avs => "Z", :cvv => "M") + sale_assertions(10100, credit_card, options, :avs => 'Z', :cvv => 'M') end def test4 credit_card = CreditCard.new( :number => '375001000000005', :month => '04', - :year => '2014', + :year => '2021', :brand => 'american_express' ) @@ -108,17 +107,37 @@ def test4 } } - auth_assertions(40040, credit_card, options, :avs => "A", :cvv => nil) + auth_assertions(10100, credit_card, options, :avs => 'A', :cvv => nil) + + authorize_avs_assertions(credit_card, options, :avs => 'A') + + sale_assertions(10100, credit_card, options, :avs => 'A', :cvv => nil) + end + + def test5 + credit_card = ActiveMerchant::Billing::NetworkTokenizationCreditCard.new( + :number => '4100200300011001', + :month => '05', + :year => '2021', + :verification_value => '463', + :brand => 'visa', + :payment_cryptogram => 'BwABBJQ1AgAAAAAgJDUCAAAAAAA=' + ) + + options = { + :order_id => '5' + } + + auth_assertions(10100, credit_card, options, :avs => 'U', :cvv => 'M') - # 4: authorize avs - authorize_avs_assertions(credit_card, options, :avs => "A") + authorize_avs_assertions(credit_card, options, :avs => 'U', :cvv => 'M') - sale_assertions(40040, credit_card, options, :avs => "A", :cvv => nil) + sale_assertions(10100, credit_card, options, :avs => 'U', :cvv => 'M') end def test6 credit_card = CreditCard.new(:number => '4457010100000008', :month => '06', - :year => '2014', :brand => 'visa', + :year => '2021', :brand => 'visa', :verification_value => '992') options = { @@ -134,30 +153,33 @@ def test6 } # 6: authorize - assert response = @gateway.authorize(60060, credit_card, options) + assert response = @gateway.authorize(10100, credit_card, options) assert !response.success? - assert_equal '110', response.params['litleOnlineResponse']['authorizationResponse']['response'] + assert_equal '110', response.params['response'] assert_equal 'Insufficient Funds', response.message - assert_equal "I", response.avs_result["code"] - assert_equal "P", response.cvv_result["code"] + assert_equal 'I', response.avs_result['code'] + assert_equal 'P', response.cvv_result['code'] + puts "Test #{options[:order_id]} Authorize: #{txn_id(response)}" # 6. sale - assert response = @gateway.purchase(60060, credit_card, options) + assert response = @gateway.purchase(10100, credit_card, options) assert !response.success? - assert_equal '110', response.params['litleOnlineResponse']['saleResponse']['response'] + assert_equal '110', response.params['response'] assert_equal 'Insufficient Funds', response.message - assert_equal "I", response.avs_result["code"] - assert_equal "P", response.cvv_result["code"] + assert_equal 'I', response.avs_result['code'] + assert_equal 'P', response.cvv_result['code'] + puts "Test #{options[:order_id]} Sale: #{txn_id(response)}" # 6A. void assert response = @gateway.void(response.authorization, {:order_id => '6A'}) - assert_equal '360', response.params['litleOnlineResponse']['voidResponse']['response'] - assert_equal 'No transaction found with specified litleTxnId', response.message + assert_equal '360', response.params['response'] + assert_equal 'No transaction found with specified transaction Id', response.message + puts "Test #{options[:order_id]}A: #{txn_id(response)}" end def test7 credit_card = CreditCard.new(:number => '5112010100000002', :month => '07', - :year => '2014', :brand => 'master', + :year => '2021', :brand => 'master', :verification_value => '251') options = { @@ -173,28 +195,30 @@ def test7 } # 7: authorize - assert response = @gateway.authorize(70070, credit_card, options) + assert response = @gateway.authorize(10100, credit_card, options) assert !response.success? - assert_equal '301', response.params['litleOnlineResponse']['authorizationResponse']['response'] + assert_equal '301', response.params['response'] assert_equal 'Invalid Account Number', response.message - assert_equal "I", response.avs_result["code"] - assert_equal "N", response.cvv_result["code"] + assert_equal 'I', response.avs_result['code'] + assert_equal 'N', response.cvv_result['code'] + puts "Test #{options[:order_id]} Authorize: #{txn_id(response)}" # 7: authorize avs - authorize_avs_assertions(credit_card, options, :avs => "I", :cvv => "N", :message => "Invalid Account Number", :success => false) + authorize_avs_assertions(credit_card, options, :avs => 'I', :cvv => 'N', :message => 'Invalid Account Number', :success => false) # 7. sale - assert response = @gateway.purchase(70070, credit_card, options) + assert response = @gateway.purchase(10100, credit_card, options) assert !response.success? - assert_equal '301', response.params['litleOnlineResponse']['saleResponse']['response'] + assert_equal '301', response.params['response'] assert_equal 'Invalid Account Number', response.message - assert_equal "I", response.avs_result["code"] - assert_equal "N", response.cvv_result["code"] + assert_equal 'I', response.avs_result['code'] + assert_equal 'N', response.cvv_result['code'] + puts "Test #{options[:order_id]} Sale: #{txn_id(response)}" end def test8 credit_card = CreditCard.new(:number => '6011010100000002', :month => '08', - :year => '2014', :brand => 'discover', + :year => '2021', :brand => 'discover', :verification_value => '184') options = { @@ -210,28 +234,30 @@ def test8 } # 8: authorize - assert response = @gateway.authorize(80080, credit_card, options) + assert response = @gateway.authorize(10100, credit_card, options) assert !response.success? - assert_equal '123', response.params['litleOnlineResponse']['authorizationResponse']['response'] + assert_equal '123', response.params['response'] assert_equal 'Call Discover', response.message - assert_equal "I", response.avs_result["code"] - assert_equal "P", response.cvv_result["code"] + assert_equal 'I', response.avs_result['code'] + assert_equal 'P', response.cvv_result['code'] + puts "Test #{options[:order_id]} Authorize: #{txn_id(response)}" # 8: authorize avs - authorize_avs_assertions(credit_card, options, :avs => "I", :cvv => "P", :message => "Call Discover", :success => false) + authorize_avs_assertions(credit_card, options, :avs => 'I', :cvv => 'P', :message => 'Call Discover', :success => false) # 8: sale assert response = @gateway.purchase(80080, credit_card, options) assert !response.success? - assert_equal '123', response.params['litleOnlineResponse']['saleResponse']['response'] + assert_equal '123', response.params['response'] assert_equal 'Call Discover', response.message - assert_equal "I", response.avs_result["code"] - assert_equal "P", response.cvv_result["code"] + assert_equal 'I', response.avs_result['code'] + assert_equal 'P', response.cvv_result['code'] + puts "Test #{options[:order_id]} Sale: #{txn_id(response)}" end def test9 credit_card = CreditCard.new(:number => '375001010000003', :month => '09', - :year => '2014', :brand => 'american_express', + :year => '2021', :brand => 'american_express', :verification_value => '0421') options = { @@ -247,92 +273,597 @@ def test9 } # 9: authorize - assert response = @gateway.authorize(90090, credit_card, options) - + assert response = @gateway.authorize(10100, credit_card, options) assert !response.success? - assert_equal '303', response.params['litleOnlineResponse']['authorizationResponse']['response'] + assert_equal '303', response.params['response'] assert_equal 'Pick Up Card', response.message - assert_equal "I", response.avs_result["code"] + assert_equal 'I', response.avs_result['code'] + puts "Test #{options[:order_id]} Authorize: #{txn_id(response)}" # 9: authorize avs - authorize_avs_assertions(credit_card, options, :avs => "I", :message => "Pick Up Card", :success => false) + authorize_avs_assertions(credit_card, options, :avs => 'I', :message => 'Pick Up Card', :success => false) # 9: sale - assert response = @gateway.purchase(90090, credit_card, options) + assert response = @gateway.purchase(10100, credit_card, options) assert !response.success? - assert_equal '303', response.params['litleOnlineResponse']['saleResponse']['response'] + assert_equal '303', response.params['response'] assert_equal 'Pick Up Card', response.message - assert_equal "I", response.avs_result["code"] + assert_equal 'I', response.avs_result['code'] + puts "Test #{options[:order_id]} Sale: #{txn_id(response)}" end # Authorization Reversal Tests + def test32 + credit_card = CreditCard.new(:number => '4457010000000009', :month => '01', + :year => '2021', :brand => 'visa', + :verification_value => '349') + + options = { + :order_id => '32', + :billing_address => { + :name => 'John Smith', + :address1 => '1 Main St.', + :city => 'Burlington', + :state => 'MA', + :zip => '01803-3747', + :country => 'US' + } + } + + assert auth_response = @gateway.authorize(10010, credit_card, options) + assert_success auth_response + assert_equal '11111 ', auth_response.params['authCode'] + puts "Test #{options[:order_id]}: #{txn_id(auth_response)}" + + assert capture_response = @gateway.capture(5050, auth_response.authorization, options) + assert_success capture_response + puts "Test #{options[:order_id]}A: #{txn_id(capture_response)}" + + assert reversal_response = @gateway.void(auth_response.authorization, options) + assert_failure reversal_response + assert 'Authorization amount has already been depleted', reversal_response.message + puts "Test #{options[:order_id]}B: #{txn_id(reversal_response)}" + end + + def test33 + credit_card = CreditCard.new(:number => '5112010000000003', :month => '01', + :year => '2021', :brand => 'master', + :verification_value => '261') + + options = { + :order_id => '33', + :billing_address => { + :name => 'Mike J. Hammer', + :address1 => '2 Main St.', + :address2 => 'Apt. 222', + :city => 'Riverside', + :state => 'RI', + :zip => '02915', + :country => 'US', + :payment_cryptogram => 'BwABBJQ1AgAAAAAgJDUCAAAAAAA=' + } + } + + assert auth_response = @gateway.authorize(20020, credit_card, options) + assert_success auth_response + assert_equal '22222 ', auth_response.params['authCode'] + puts "Test #{options[:order_id]}: #{txn_id(auth_response)}" + + assert reversal_response = @gateway.void(auth_response.authorization, options) + assert_success reversal_response + puts "Test #{options[:order_id]}A: #{txn_id(reversal_response)}" + end + def test34 - credit_card = CreditCard.new(:number => '6011010000000003', :month => '03', - :year => '2014', :brand => 'discover', + credit_card = CreditCard.new(:number => '6011010000000003', :month => '01', + :year => '2021', :brand => 'discover', :verification_value => '758') options = { - :order_id => '34', - :billing_address => { - :name => 'Eileen Jones', - :address1 => '3 Main St.', - :city => 'Bloomfield', - :state => 'CT', - :zip => '06002', - :country => 'US' - } + :order_id => '34', + :billing_address => { + :name => 'Eileen Jones', + :address1 => '3 Main St.', + :city => 'Bloomfield', + :state => 'CT', + :zip => '06002', + :country => 'US' + } } assert auth_response = @gateway.authorize(30030, credit_card, options) assert_success auth_response + assert '33333 ', auth_response.params['authCode'] + puts "Test #{options[:order_id]}: #{txn_id(auth_response)}" - credit_card = CreditCard.new(:number => '4024720001231239', :month => '12', - :year => '2014', :brand => 'visa') - assert auth_response2 = @gateway.authorize(18699, credit_card, :order_id => '29') - - assert reversal_response = @gateway.void(auth_response2.authorization) + assert reversal_response = @gateway.void(auth_response.authorization, options) assert_success reversal_response + puts "Test #{options[:order_id]}A: #{txn_id(reversal_response)}" end - def test36 + def test35 + credit_card = CreditCard.new(:number => '375001000000005', :month => '01', + :year => '2021', :brand => 'american_express') + options = { - :order_id => '36' + :order_id => '35', + :billing_address => { + :name => 'Bob Black', + :address1 => '4 Main St.', + :city => 'Laurel', + :state => 'MD', + :zip => '20708', + :country => 'US' + } } - credit_card = CreditCard.new(:number => '375000026600004', :month => '05', - :year => '2014', :brand => 'american_express', - :verification_value => '261') + assert auth_response = @gateway.authorize(10100, credit_card, options) + assert_success auth_response + assert_equal '44444 ', auth_response.params['authCode'] + assert_equal 'A', auth_response.avs_result['code'] + puts "Test #{options[:order_id]}: #{txn_id(auth_response)}" + + assert capture_response = @gateway.capture(5050, auth_response.authorization, options) + assert_success capture_response + puts "Test #{options[:order_id]}A: #{txn_id(capture_response)}" + + assert reversal_response = @gateway.void(auth_response.authorization, options) + assert_failure reversal_response + assert 'Reversal amount does not match Authorization amount', reversal_response.message + puts "Test #{options[:order_id]}B: #{txn_id(reversal_response)}" + end + + def test36 + credit_card = CreditCard.new(:number => '375000026600004', :month => '01', + :year => '2021', :brand => 'american_express') + + options = { + :order_id => '36' + } assert auth_response = @gateway.authorize(20500, credit_card, options) assert_success auth_response + puts "Test #{options[:order_id]}: #{txn_id(auth_response)}" + + assert reversal_response = @gateway.void(auth_response.authorization, options) + assert_failure reversal_response + assert 'Reversal amount does not match Authorization amount', reversal_response.message + puts "Test #{options[:order_id]}A: #{txn_id(reversal_response)}" + end + + # Echeck + def test37 + check = check( + name: 'Tom Black', + routing_number: '053100300', + account_number: '10@BC99999', + account_type: 'Checking' + ) + options = { + :order_id => '37', + :billing_address => { + :name => 'Tom Black', + :address1 => '8 Main St.', + :city => 'Manchester', + :state => 'NH', + :zip => '03101', + :country => 'US', + :email => 'test@test.com', + :phone => '2233334444' + } + } + assert auth_response = @gateway.authorize(3001, check, options) + assert_failure auth_response + assert_equal 'Invalid Account Number', auth_response.message + assert_equal '301', auth_response.params['response'] + puts "Test #{options[:order_id]}: #{txn_id(auth_response)}" + end + + def test38 + check = check( + name: 'John Smith', + routing_number: '011075150', + account_number: '1099999999', + account_type: 'Checking' + ) + options = { + :order_id => '38', + :billing_address => { + :name => 'John Smith', + :address1 => '8 Main St.', + :city => 'Manchester', + :state => 'NH', + :zip => '03101', + :country => 'US', + :email => 'test@test.com', + :phone => '2233334444' + } + } + assert auth_response = @gateway.authorize(3002, check, options) + assert_success auth_response + assert_equal 'Approved', auth_response.message + assert_equal '000', auth_response.params['response'] + puts "Test #{options[:order_id]}: #{txn_id(auth_response)}" + end - assert reversal_response = @gateway.void(auth_response.authorization, amount: 10000) - assert !reversal_response.success? - assert_equal '336', reversal_response.params['litleOnlineResponse']['authReversalResponse']['response'] + def test39 + check = check( + name: 'Robert Jones', + routing_number: '053100300', + account_number: '3099999999', + account_type: 'Corporate' + ) + options = { + :order_id => '39', + :billing_address => { + :name => 'John Smith', + :address1 => '8 Main St.', + :city => 'Manchester', + :state => 'NH', + :zip => '03101', + :country => 'US', + :company => 'Good Goods Inc', + :email => 'test@test.com', + :phone => '2233334444' + } + } + assert auth_response = @gateway.authorize(3003, check, options) + assert_failure auth_response + assert_equal 'Decline - Negative Information on File', auth_response.message + assert_equal '950', auth_response.params['response'] + puts "Test #{options[:order_id]}: #{txn_id(auth_response)}" + end + + def test40 + declined_authorize_check = check( + name: 'Peter Green', + routing_number: '011075150', + account_number: '8099999999', + account_type: 'Corporate' + ) + options = { + :order_id => '40', + :billing_address => { + :name => 'Peter Green', + :address1 => '8 Main St.', + :city => 'Manchester', + :state => 'NH', + :zip => '03101', + :country => 'US', + :company => 'Green Co', + :email => 'test@test.com', + :phone => '2233334444' + } + } + assert auth_response = @gateway.authorize(3004, declined_authorize_check, options) + assert_failure auth_response + assert_equal 'Absolute Decline', auth_response.message + assert_equal '951', auth_response.params['response'] + puts "Test #{options[:order_id]}: #{txn_id(auth_response)}" + end + + def test41 + check = check( + name: 'Mike Hammer', + routing_number: '053100300', + account_number: '10@BC99999', + account_type: 'Checking' + ) + options = { + :order_id => '41', + :billing_address => { + :name => 'Mike Hammer', + :address1 => '8 Main St.', + :city => 'Manchester', + :state => 'NH', + :zip => '03101', + :country => 'US', + :email => 'test@test.com', + :phone => '2233334444' + } + } + assert purchase_response = @gateway.purchase(2008, check, options) + assert_failure purchase_response + assert_equal 'Invalid Account Number', purchase_response.message + assert_equal '301', purchase_response.params['response'] + puts "Test #{options[:order_id]}: #{txn_id(purchase_response)}" + end + + def test42 + check = check( + name: 'Tom Black', + routing_number: '011075150', + account_number: '4099999992', + account_type: 'Checking' + ) + options = { + :order_id => '42', + :billing_address => { + :name => 'Tom Black', + :address1 => '8 Main St.', + :city => 'Manchester', + :state => 'NH', + :zip => '03101', + :country => 'US', + :email => 'test@test.com', + :phone => '2233334444' + } + } + assert purchase_response = @gateway.purchase(2004, check, options) + assert_success purchase_response + assert_equal 'Approved', purchase_response.message + assert_equal '000', purchase_response.params['response'] + puts "Test #{options[:order_id]}: #{txn_id(purchase_response)}" + end + + def test43 + check = check( + name: 'Peter Green', + routing_number: '011075150', + account_number: '6099999992', + account_type: 'Corporate' + ) + options = { + :order_id => '43', + :billing_address => { + :name => 'Peter Green', + :address1 => '8 Main St.', + :city => 'Manchester', + :state => 'NH', + :zip => '03101', + :country => 'US', + :company => 'Green Co', + :email => 'test@test.com', + :phone => '2233334444' + } + } + assert purchase_response = @gateway.purchase(2007, check, options) + assert_success purchase_response + assert_equal 'Approved', purchase_response.message + assert_equal '000', purchase_response.params['response'] + puts "Test #{options[:order_id]}: #{txn_id(purchase_response)}" + end + + def test44 + check = check( + name: 'Peter Green', + routing_number: '053133052', + account_number: '9099999992', + account_type: 'Corporate' + ) + options = { + :order_id => '44', + :billing_address => { + :name => 'Peter Green', + :address1 => '8 Main St.', + :city => 'Manchester', + :state => 'NH', + :zip => '03101', + :country => 'US', + :company => 'Green Co', + :email => 'test@test.com', + :phone => '2233334444' + } + } + assert purchase_response = @gateway.purchase(2009, check, options) + assert_failure purchase_response + assert_equal 'Invalid Bank Routing Number', purchase_response.message + assert_equal '900', purchase_response.params['response'] + puts "Test #{options[:order_id]}: #{txn_id(purchase_response)}" + end + + def test45 + check = check( + name: 'John Smith', + routing_number: '053100300', + account_number: '10@BC99999', + account_type: 'Checking' + ) + options = { + :order_id => '45', + :billing_address => { + :name => 'John Smith', + :address1 => '8 Main St.', + :city => 'Manchester', + :state => 'NH', + :zip => '03101', + :country => 'US', + :email => 'test@test.com', + :phone => '2233334444' + } + } + assert refund_response = @gateway.refund(1001, check, options) + assert_failure refund_response + assert_equal 'Invalid Account Number', refund_response.message + assert_equal '301', refund_response.params['response'] + puts "Test #{options[:order_id]}: #{txn_id(refund_response)}" + end + + def test46 + check = check( + name: 'Robert Jones', + routing_number: '011075150', + account_number: '3099999999', + account_type: 'Corporate' + ) + options = { + :order_id => '46', + :order_source => 'telephone', + :billing_address => { + :name => 'Robert Jones', + :address1 => '8 Main St.', + :city => 'Manchester', + :state => 'NH', + :zip => '03101', + :country => 'US', + :email => 'test@test.com', + :phone => '2233334444', + :company => 'Widget Inc' + } + } + assert purchase_response = @gateway.purchase(1003, check, options) + sleep(10) + assert refund_response = @gateway.refund(1003, purchase_response.authorization, options) + assert_success refund_response + assert_equal 'Approved', refund_response.message + assert_equal '000', refund_response.params['response'] + puts "Test #{options[:order_id]}: #{txn_id(refund_response)}" + end + + def test47 + check = check( + name: 'Peter Green', + routing_number: '211370545', + account_number: '6099999993', + account_type: 'Corporate' + ) + options = { + :order_id => '47', + :billing_address => { + :name => 'Peter Green', + :address1 => '8 Main St.', + :city => 'Manchester', + :state => 'NH', + :zip => '03101', + :country => 'US', + :company => 'Green Co', + :email => 'test@test.com', + :phone => '2233334444' + } + } + assert purchase_response = @gateway.purchase(1007, check, options) + assert refund_response = @gateway.refund(1007, purchase_response.authorization, options) + assert_success refund_response + assert_equal 'Approved', refund_response.message + assert_equal '000', refund_response.params['response'] + puts "Test #{options[:order_id]}: #{txn_id(refund_response)}" + end + + def test48 + check = check( + name: 'Peter Green', + routing_number: '011075150', + account_number: '6099999992', + account_type: 'Corporate' + ) + options = { + :order_id => '43', + :billing_address => { + :name => 'Peter Green', + :address1 => '8 Main St.', + :city => 'Manchester', + :state => 'NH', + :zip => '03101', + :country => 'US', + :company => 'Green Co', + :email => 'test@test.com', + :phone => '2233334444' + } + } + assert purchase_response = @gateway.purchase(2007, check, options) + assert_success purchase_response + assert refund_response = @gateway.refund(2007, purchase_response.authorization, options) + assert_equal '000', refund_response.params['response'] + puts "Test 48: #{txn_id(refund_response)}" + end + + def test49 + assert refund_response = @gateway.refund(2007, 2) + assert_failure refund_response + assert_equal '360', refund_response.params['response'] + assert_equal 'No transaction found with specified transaction Id', refund_response.message + puts "Test 49: #{txn_id(refund_response)}" + end + + def test_echeck_void1 + check = check( + name: 'Tom Black', + routing_number: '011075150', + account_number: '4099999992', + account_type: 'Checking' + ) + options = { + :order_id => '42', + :id => '236222', + :billing_address => { + :name => 'Tom Black', + :address1 => '8 Main St.', + :city => 'Manchester', + :state => 'NH', + :zip => '03101', + :country => 'US', + :email => 'test@test.com', + :phone => '2233334444' + } + } + assert purchase_response = @gateway.purchase(2004, check, options) + assert_success purchase_response + sleep(10) + assert void_response = @gateway.void(purchase_response.authorization) + assert_equal '000', void_response.params['response'] + puts "Test void1: #{txn_id(void_response)}" + end + + def test_echeck_void2 + check = check( + name: 'Robert Jones', + routing_number: '011075150', + account_number: '3099999999', + account_type: 'Checking' + ) + options = { + :order_id => '46', + :id => '232222', + :billing_address => { + :name => 'Robert Jones', + :address1 => '8 Main St.', + :city => 'Manchester', + :state => 'NH', + :zip => '03101', + :country => 'US', + :email => 'test@test.com', + :phone => '2233334444' + } + } + assert purchase_response = @gateway.purchase(1003, check, options) + assert_success purchase_response + sleep(20) + assert void_response = @gateway.void(purchase_response.authorization) + assert_equal '000', void_response.params['response'] + puts "Test void2: #{txn_id(void_response)}" + end + + def test_echeck_void3 + assert void_response = @gateway.void(2) + assert_failure void_response + assert_equal '360', void_response.params['response'] + assert_equal 'No transaction found with specified transaction Id', void_response.message + puts "Test void3: #{txn_id(void_response)}" end # Explicit Token Registration Tests def test50 credit_card = CreditCard.new(:number => '4457119922390123') options = { - :order_id => '50' + :order_id => '50' } # store store_response = @gateway.store(credit_card, options) assert_success store_response + assert_equal '445711', store_response.params['bin'] + assert_equal 'VI', store_response.params['type'] + assert_equal '0123', store_response.params['litleToken'][-4, 4] + assert_equal '801', store_response.params['response'] assert_equal 'Account number was successfully registered', store_response.message - assert_equal '445711', store_response.params['litleOnlineResponse']['registerTokenResponse']['bin'] - assert_equal 'VI', store_response.params['litleOnlineResponse']['registerTokenResponse']['type'] #type is on Object in 1.8.7 - later versions can use .registerTokenResponse.type - assert_equal '801', store_response.params['litleOnlineResponse']['registerTokenResponse']['response'] - assert_equal '0123', store_response.params['litleOnlineResponse']['registerTokenResponse']['litleToken'][-4,4] + puts "Test #{options[:order_id]}: #{txn_id(response)}" end def test51 credit_card = CreditCard.new(:number => '4457119999999999') - options = { - :order_id => '51' + options = { + :order_id => '51' } # store @@ -340,14 +871,14 @@ def test51 assert_failure store_response assert_equal 'Credit card number was invalid', store_response.message - assert_equal '820', store_response.params['litleOnlineResponse']['registerTokenResponse']['response'] - assert_equal nil, store_response.params['litleOnlineResponse']['registerTokenResponse']['litleToken'] + assert_equal '820', store_response.params['response'] + assert_equal nil, store_response.params['litleToken'] end def test52 credit_card = CreditCard.new(:number => '4457119922390123') - options = { - :order_id => '52' + options = { + :order_id => '52' } # store @@ -355,10 +886,47 @@ def test52 assert_success store_response assert_equal 'Account number was previously registered', store_response.message - assert_equal '445711', store_response.params['litleOnlineResponse']['registerTokenResponse']['bin'] - assert_equal 'VI', store_response.params['litleOnlineResponse']['registerTokenResponse']['type'] #type is on Object in 1.8.7 - later versions can use .registerTokenResponse.type - assert_equal '802', store_response.params['litleOnlineResponse']['registerTokenResponse']['response'] - assert_equal '0123', store_response.params['litleOnlineResponse']['registerTokenResponse']['litleToken'][-4,4] + assert_equal '445711', store_response.params['bin'] + assert_equal 'VI', store_response.params['type'] + assert_equal '802', store_response.params['response'] + assert_equal '0123', store_response.params['litleToken'][-4, 4] + puts "Test #{options[:order_id]}: #{txn_id(store_response)}" + end + + def test53 + check = check( + routing_number: '011100012', + account_number: '1099999998' + ) + options = { + :order_id => '53' + } + + store_response = @gateway.store(check, options) + + assert_success store_response + assert_equal '998', store_response.params['eCheckAccountSuffix'] + assert_equal 'EC', store_response.params['type'] + assert_equal '801', store_response.params['response'] + assert_equal 'Account number was successfully registered', store_response.message + puts "Test #{options[:order_id]}: #{txn_id(store_response)}" + end + + def test54 + check = check( + routing_number: '1145_7895', + account_number: '1022222102' + ) + options = { + :order_id => '54' + } + + store_response = @gateway.store(check, options) + + assert_failure store_response + assert_equal '900', store_response.params['response'] + assert_equal 'Invalid Bank Routing Number', store_response.message + puts "Test #{options[:order_id]}: #{txn_id(store_response)}" end # Implicit Token Registration Tests @@ -368,23 +936,19 @@ def test55 :year => '2014', :brand => 'master', :verification_value => '987') - options = { - :order_id => '55' + options = { + :order_id => '55' } # authorize assert response = @gateway.authorize(15000, credit_card, options) - #"tokenResponse" => { "litleToken" => "1712000118270196", - # "tokenResponseCode" => "802", - # "tokenMessage" => "Account number was previously registered", - # "type" => "MC", - # "bin" => "543510" } assert_success response assert_equal 'Approved', response.message - assert_equal '0196', response.params['litleOnlineResponse']['authorizationResponse']['tokenResponse']['litleToken'][-4,4] - assert %w(801 802).include? response.params['litleOnlineResponse']['authorizationResponse']['tokenResponse']['tokenResponseCode'] - assert_equal 'MC', response.params['litleOnlineResponse']['authorizationResponse']['tokenResponse']['type'] - assert_equal '543510', response.params['litleOnlineResponse']['authorizationResponse']['tokenResponse']['bin'] + assert_equal '0196', response.params['tokenResponse_litleToken'][-4, 4] + assert %w(801 802).include? response.params['tokenResponse_tokenResponseCode'] + assert_equal 'MC', response.params['tokenResponse_type'] + assert_equal '543510', response.params['tokenResponse_bin'] + puts "Test #{options[:order_id]}: #{txn_id(response)}" end def test56 @@ -393,15 +957,16 @@ def test56 :year => '2014', :brand => 'master', :verification_value => '987') - options = { - :order_id => '56' + options = { + :order_id => '56' } # authorize assert response = @gateway.authorize(15000, credit_card, options) assert_failure response - assert_equal '301', response.params['litleOnlineResponse']['authorizationResponse']['response'] + assert_equal '301', response.params['response'] + puts "Test #{options[:order_id]}: #{txn_id(response)}" end def test57_58 @@ -410,8 +975,8 @@ def test57_58 :year => '2014', :brand => 'master', :verification_value => '987') - options = { - :order_id => '57' + options = { + :order_id => '57' } # authorize card @@ -419,19 +984,20 @@ def test57_58 assert_success response assert_equal 'Approved', response.message - assert_equal '0196', response.params['litleOnlineResponse']['authorizationResponse']['tokenResponse']['litleToken'][-4,4] - assert %w(801 802).include? response.params['litleOnlineResponse']['authorizationResponse']['tokenResponse']['tokenResponseCode'] - assert_equal 'MC', response.params['litleOnlineResponse']['authorizationResponse']['tokenResponse']['type'] - assert_equal '543510', response.params['litleOnlineResponse']['authorizationResponse']['tokenResponse']['bin'] + assert_equal '0196', response.params['tokenResponse_litleToken'][-4, 4] + assert %w(801 802).include? response.params['tokenResponse_tokenResponseCode'] + assert_equal 'MC', response.params['tokenResponse_type'] + assert_equal '543510', response.params['tokenResponse_bin'] + puts "Test #{options[:order_id]}: #{txn_id(response)}" # authorize token - token = response.params['litleOnlineResponse']['authorizationResponse']['tokenResponse']['litleToken'] + token = response.params['tokenResponse_litleToken'] options = { - :order_id => '58', - :token => { - :month => credit_card.month, - :year => credit_card.year - } + :order_id => '58', + :token => { + :month => credit_card.month, + :year => credit_card.year + } } # authorize @@ -439,55 +1005,114 @@ def test57_58 assert_success response assert_equal 'Approved', response.message + puts "Test #{options[:order_id]}: #{txn_id(response)}" end def test59 - token = '1712990000040196' + token = '1111000100092332' options = { - :order_id => '59', - :token => { - :month => '11', - :year => '2014' - } + :order_id => '59', + :token => { + :month => '11', + :year => '2021' + } } # authorize assert response = @gateway.authorize(15000, token, options) assert_failure response - assert_equal '822', response.params['litleOnlineResponse']['authorizationResponse']['response'] + assert_equal '822', response.params['response'] assert_equal 'Token was not found', response.message + puts "Test #{options[:order_id]}: #{txn_id(response)}" end def test60 token = '171299999999999' options = { - :order_id => '60', - :token => { - :month => '11', - :year => '2014' - } + :order_id => '60', + :token => { + :month => '11', + :year => '2014' + } } # authorize assert response = @gateway.authorize(15000, token, options) assert_failure response - assert_equal '823', response.params['litleOnlineResponse']['authorizationResponse']['response'] + assert_equal '823', response.params['response'] assert_equal 'Token was invalid', response.message + puts "Test #{options[:order_id]}: #{txn_id(response)}" + end + + def test_apple_pay_purchase + options = { + :order_id => transaction_id, + } + decrypted_apple_pay = ActiveMerchant::Billing::NetworkTokenizationCreditCard.new( + { + month: '01', + year: '2021', + brand: 'visa', + number: '4457000300000007', + payment_cryptogram: 'BwABBJQ1AgAAAAAgJDUCAAAAAAA=' + }) + + assert response = @gateway.purchase(10010, decrypted_apple_pay, options) + assert_success response + assert_equal 'Approved', response.message + end + + def test_android_pay_purchase + options = { + :order_id => transaction_id, + } + decrypted_android_pay = ActiveMerchant::Billing::NetworkTokenizationCreditCard.new( + { + source: :android_pay, + month: '01', + year: '2021', + brand: 'visa', + number: '4457000300000007', + payment_cryptogram: 'BwABBJQ1AgAAAAAgJDUCAAAAAAA=' + }) + + assert response = @gateway.purchase(10010, decrypted_android_pay, options) + assert_success response + assert_equal 'Approved', response.message + end + + def test_three_d_secure + three_d_secure_assertions('3DS1', '4100200300000004', 'visa', '3dsAuthenticated', '0') + three_d_secure_assertions('3DS2', '4100200300000012', 'visa', '3dsAuthenticated', '1') + three_d_secure_assertions('3DS3', '4100200300000103', 'visa', '3dsAuthenticated', '2') + three_d_secure_assertions('3DS4', '4100200300001002', 'visa', '3dsAuthenticated', 'A') + three_d_secure_assertions('3DS5', '4100200300000020', 'visa', '3dsAuthenticated', '3') + three_d_secure_assertions('3DS6', '4100200300000038', 'visa', '3dsAuthenticated', '4') + three_d_secure_assertions('3DS7', '4100200300000046', 'visa', '3dsAuthenticated', '5') + three_d_secure_assertions('3DS8', '4100200300000053', 'visa', '3dsAuthenticated', '6') + three_d_secure_assertions('3DS9', '4100200300000061', 'visa', '3dsAuthenticated', '7') + three_d_secure_assertions('3DS10', '4100200300000079', 'visa', '3dsAuthenticated', '8') + three_d_secure_assertions('3DS11', '4100200300000087', 'visa', '3dsAuthenticated', '9') + three_d_secure_assertions('3DS12', '4100200300000095', 'visa', '3dsAuthenticated', 'B') + three_d_secure_assertions('3DS13', '4100200300000111', 'visa', '3dsAuthenticated', 'C') + three_d_secure_assertions('3DS14', '4100200300000129', 'visa', '3dsAuthenticated', 'D') + three_d_secure_assertions('3DS15', '5112010200000001', 'master', '3dsAttempted', nil) + three_d_secure_assertions('3DS16', '5112010200000001', 'master', '3dsAttempted', nil) end def test_authorize_and_purchase_and_credit_with_token options = { - :order_id => transaction_id, - :billing_address => { - :name => 'John Smith', - :address1 => '1 Main St.', - :city => 'Burlington', - :state => 'MA', - :zip => '01803-3747', - :country => 'US' - } + :order_id => transaction_id, + :billing_address => { + :name => 'John Smith', + :address1 => '1 Main St.', + :city => 'Burlington', + :state => 'MA', + :zip => '01803-3747', + :country => 'US' + } } credit_card = CreditCard.new(:number => '5435101234510196', @@ -536,63 +1161,71 @@ def test_authorize_and_purchase_and_credit_with_token private - def auth_assertions(amount, card, options, assertions) + def auth_assertions(amount, card, options, assertions={}) # 1: authorize assert response = @gateway.authorize(amount, card, options) assert_success response assert_equal 'Approved', response.message - assert_equal assertions[:avs], response.avs_result["code"] - assert_equal assertions[:cvv], response.cvv_result["code"] if assertions[:cvv] - assert_equal options[:order_id], response.params['litleOnlineResponse']['authorizationResponse']['id'] + assert_equal assertions[:avs], response.avs_result['code'] if assertions[:avs] + assert_equal assertions[:cvv], response.cvv_result['code'] if assertions[:cvv] + assert_equal auth_code(options[:order_id]), response.params['authCode'] # 1A: capture - id = transaction_id - assert response = @gateway.capture(amount, response.authorization, {:id => id}) + assert response = @gateway.capture(amount, response.authorization, {:id => transaction_id}) assert_equal 'Approved', response.message - assert_equal id, response.params['litleOnlineResponse']['captureResponse']['id'] # 1B: credit - id = transaction_id - assert response = @gateway.credit(amount, response.authorization, {:id => id}) + assert response = @gateway.credit(amount, response.authorization, {:id => transaction_id}) assert_equal 'Approved', response.message - assert_equal id, response.params['litleOnlineResponse']['creditResponse']['id'] # 1C: void - id = transaction_id - assert response = @gateway.void(response.authorization, {:id => id}) + assert response = @gateway.void(response.authorization, {:id => transaction_id}) assert_equal 'Approved', response.message - assert_equal id, response.params['litleOnlineResponse']['voidResponse']['id'] end def authorize_avs_assertions(credit_card, options, assertions={}) - assert response = @gateway.authorize(0, credit_card, options) + assert response = @gateway.authorize(000, credit_card, options) assert_equal assertions.key?(:success) ? assertions[:success] : true, response.success? assert_equal assertions[:message] || 'Approved', response.message - assert_equal assertions[:avs], response.avs_result["code"], caller.inspect - assert_equal assertions[:cvv], response.cvv_result["code"], caller.inspect if assertions[:cvv] - assert_equal options[:order_id], response.params['litleOnlineResponse']['authorizationResponse']['id'] + assert_equal assertions[:avs], response.avs_result['code'], caller.inspect + assert_equal assertions[:cvv], response.cvv_result['code'], caller.inspect if assertions[:cvv] end - def sale_assertions(amount, card, options, assertions) + def sale_assertions(amount, card, options, assertions={}) # 1: sale assert response = @gateway.purchase(amount, card, options) assert_success response assert_equal 'Approved', response.message - assert_equal assertions[:avs], response.avs_result["code"] - assert_equal assertions[:cvv], response.cvv_result["code"] if assertions[:cvv] - assert_equal options[:order_id], response.params['litleOnlineResponse']['saleResponse']['id'] + assert_equal assertions[:avs], response.avs_result['code'] if assertions[:avs] + assert_equal assertions[:cvv], response.cvv_result['code'] if assertions[:cvv] + # assert_equal auth_code(options[:order_id]), response.params['authCode'] # 1B: credit - id = transaction_id - assert response = @gateway.credit(amount, response.authorization, {:id => id}) + assert response = @gateway.credit(amount, response.authorization, {:id => transaction_id}) assert_equal 'Approved', response.message - assert_equal id, response.params['litleOnlineResponse']['creditResponse']['id'] # 1C: void - id = transaction_id - assert response = @gateway.void(response.authorization, {:id => id}) + assert response = @gateway.void(response.authorization, {:id => transaction_id}) assert_equal 'Approved', response.message - assert_equal id, response.params['litleOnlineResponse']['voidResponse']['id'] + end + + def three_d_secure_assertions(test_id, card, type, source, result) + credit_card = CreditCard.new(:number => card, :month => '01', + :year => '2021', :brand => type, + :verification_value => '261', + :name => 'Mike J. Hammer') + + options = { + order_id: test_id, + order_source: source, + cavv: 'BwABBJQ1AgAAAAAgJDUCAAAAAAA=', + xid: 'BwABBJQ1AgAAAAAgJDUCAAAAAAA=' + } + + assert response = @gateway.authorize(10100, credit_card, options) + assert_success response + assert_equal result, response.params['fraudResult_authenticationResult'] + puts "Test #{test_id}: #{txn_id(response)}" end def transaction_id @@ -604,4 +1237,12 @@ def transaction_id # minLength = N/A maxLength = 25 generate_unique_id[0, 24] end + + def auth_code(order_id) + order_id * 5 + ' ' + end + + def txn_id(response) + response.authorization.split(';')[0] + end end diff --git a/test/remote/gateways/remote_litle_test.rb b/test/remote/gateways/remote_litle_test.rb old mode 100755 new mode 100644 index 44bcdca4542..d5014b59420 --- a/test/remote/gateways/remote_litle_test.rb +++ b/test/remote/gateways/remote_litle_test.rb @@ -2,41 +2,82 @@ class RemoteLitleTest < Test::Unit::TestCase def setup - Base.gateway_mode = :test @gateway = LitleGateway.new(fixtures(:litle)) @credit_card_hash = { - :first_name => 'John', - :last_name => 'Smith', - :month => '01', - :year => '2012', - :brand => 'visa', - :number => '4457010000000009', - :verification_value => '349' + first_name: 'John', + last_name: 'Smith', + month: '01', + year: '2012', + brand: 'visa', + number: '4457010000000009', + verification_value: '349' } @options = { - :order_id=>'1', - :billing_address=>{ - :name => 'John Smith', - :company => 'testCompany', - :address1 => '1 Main St.', - :city => 'Burlington', - :state => 'MA', - :country => 'USA', - :zip => '01803-3747', - :phone => '1234567890' + order_id: '1', + email: 'wow@example.com', + billing_address: { + company: 'testCompany', + address1: '1 Main St.', + city: 'Burlington', + state: 'MA', + country: 'USA', + zip: '01803-3747', + phone: '1234567890' } } @credit_card1 = CreditCard.new(@credit_card_hash) @credit_card2 = CreditCard.new( - :first_name => "Joe", - :last_name => "Green", - :month => "06", - :year => "2012", - :brand => "visa", - :number => "4457010100000008", - :verification_value => "992" + first_name: 'Joe', + last_name: 'Green', + month: '06', + year: '2012', + brand: 'visa', + number: '4457010100000008', + verification_value: '992' + ) + @credit_card_nsf = CreditCard.new( + first_name: 'Joe', + last_name: 'Green', + month: '06', + year: '2012', + brand: 'visa', + number: '4488282659650110', + verification_value: '992' + ) + @decrypted_apple_pay = ActiveMerchant::Billing::NetworkTokenizationCreditCard.new( + { + month: '01', + year: '2012', + brand: 'visa', + number: '44444444400009', + payment_cryptogram: 'BwABBJQ1AgAAAAAgJDUCAAAAAAA=' + }) + @decrypted_android_pay = ActiveMerchant::Billing::NetworkTokenizationCreditCard.new( + { + source: :android_pay, + month: '01', + year: '2021', + brand: 'visa', + number: '4457000300000007', + payment_cryptogram: 'BwABBJQ1AgAAAAAgJDUCAAAAAAA=' + }) + @check = check( + name: 'Tom Black', + routing_number: '011075150', + account_number: '4099999992', + account_type: 'checking' + ) + @authorize_check = check( + name: 'John Smith', + routing_number: '011075150', + account_number: '1099999999', + account_type: 'checking' + ) + @store_check = check( + routing_number: '011100012', + account_number: '1099999998' ) end @@ -46,6 +87,55 @@ def test_successful_authorization assert_equal 'Approved', response.message end + def test_successful_authorization_with_merchant_data + options = @options.merge( + affiliate: 'some-affiliate', + campaign: 'super-awesome-campaign', + merchant_grouping_id: 'brilliant-group' + ) + assert @gateway.authorize(10010, @credit_card1, options) + end + + def test_successful_authorization_with_echeck + options = @options.merge({ + order_id: '38', + order_source: 'telephone' + }) + assert response = @gateway.authorize(3002, @authorize_check, options) + assert_success response + assert_equal 'Approved', response.message + end + + def test_successful_authorization_with_paypage_registration_with_month_year_verification + paypage_registration = ActiveMerchant::Billing::LitlePaypageRegistration.new( + 'XkNDRGZDTGZyS2RBSTVCazJNSmdWam5TQ2gyTGhydFh0Mk5qZ0Z3cVp5VlNBN00rcGRZdHF6amFRWEttbVBnYw==', + month: '11', + year: '12', + verification_value: '123', + name: 'Joe Payer' + ) + + assert response = @gateway.authorize(5090, paypage_registration, billing_address: address) + assert_success response + assert_equal 'Approved', response.message + end + + def test_successful_authorization_with_paypage_registration_without_month_year_verification_name + paypage_registration = ActiveMerchant::Billing::LitlePaypageRegistration.new( + 'XkNDRGZDTGZyS2RBSTVCazJNSmdWam5TQ2gyTGhydFh0Mk5qZ0Z3cVp5VlNBN00rcGRZdHF6amFRWEttbVBnYw==' + ) + + assert response = @gateway.authorize(5090, paypage_registration, billing_address: address) + assert_success response + assert_equal 'Approved', response.message + end + + def test_avs_and_cvv_result + assert response = @gateway.authorize(10010, @credit_card1, @options) + assert_equal 'X', response.avs_result['code'] + assert_equal 'M', response.cvv_result['code'] + end + def test_unsuccessful_authorization assert response = @gateway.authorize(60060, @credit_card2, { @@ -65,68 +155,438 @@ def test_unsuccessful_authorization end def test_successful_purchase - #Litle sale assert response = @gateway.purchase(10010, @credit_card1, @options) assert_success response assert_equal 'Approved', response.message end - def test_unsuccessful_purchase - assert response = @gateway.purchase(60060, @credit_card2, { - :order_id=>'6', - :billing_address=>{ - :name => 'Joe Green', - :address1 => '6 Main St.', - :city => 'Derry', - :state => 'NH', - :zip => '03038', - :country => 'US' - }, + def test_successful_purchase_with_some_empty_address_parts + assert response = @gateway.purchase(10010, @credit_card1, { + order_id: '1', + email: 'wow@example.com', + billing_address: { } + }) + assert_success response + assert_equal 'Approved', response.message + end + + def test_successful_purchase_with_debt_repayment_flag + assert response = @gateway.purchase(10010, @credit_card1, @options.merge(debt_repayment: true)) + assert_success response + assert_equal 'Approved', response.message + end + + def test_successful_purchase_with_3ds_fields + options = @options.merge({ + order_source: '3dsAuthenticated', + xid: 'BwABBJQ1AgAAAAAgJDUCAAAAAAA=', + cavv: 'BwABBJQ1AgAAAAAgJDUCAAAAAAA=' + }) + assert response = @gateway.purchase(10010, @credit_card1, options) + assert_success response + assert_equal 'Approved', response.message + end + + def test_successful_purchase_with_apple_pay + assert response = @gateway.purchase(10010, @decrypted_apple_pay) + assert_success response + assert_equal 'Approved', response.message + end + + def test_successful_purchase_with_android_pay + assert response = @gateway.purchase(10000, @decrypted_android_pay) + assert_success response + assert_equal 'Approved', response.message + end + + def test_successful_purchase_with_paypage_registration_with_month_year_verification + paypage_registration = ActiveMerchant::Billing::LitlePaypageRegistration.new( + 'XkNDRGZDTGZyS2RBSTVCazJNSmdWam5TQ2gyTGhydFh0Mk5qZ0Z3cVp5VlNBN00rcGRZdHF6amFRWEttbVBnYw==', + month: '11', + year: '12', + verification_value: '123', + name: 'Joe Payer' + ) + + assert response = @gateway.purchase(5090, paypage_registration, billing_address: address) + assert_success response + assert_equal 'Approved', response.message + end + + def test_successful_purchase_with_merchant_data + options = @options.merge( + affiliate: 'some-affiliate', + campaign: 'super-awesome-campaign', + merchant_grouping_id: 'brilliant-group' ) + assert response = @gateway.purchase(10010, @credit_card1, options) + assert_success response + assert_equal 'Approved', response.message + end + + def test_successful_purchase_with_echeck + options = @options.merge({ + order_id: '42', + order_source: 'telephone' + }) + assert response = @gateway.purchase(2004, @check, options) + assert_success response + assert_equal 'Approved', response.message + end + + def test_successful_purchase_with_paypage_registration_without_month_year_verification + paypage_registration = ActiveMerchant::Billing::LitlePaypageRegistration.new( + 'XkNDRGZDTGZyS2RBSTVCazJNSmdWam5TQ2gyTGhydFh0Mk5qZ0Z3cVp5VlNBN00rcGRZdHF6amFRWEttbVBnYw==' + ) + + assert response = @gateway.purchase(5090, paypage_registration, billing_address: address) + assert_success response + assert_equal 'Approved', response.message + end + + def test_unsuccessful_purchase + assert response = @gateway.purchase(60060, @credit_card2, { + :order_id=>'6', + :billing_address=>{ + :name => 'Joe Green', + :address1 => '6 Main St.', + :city => 'Derry', + :state => 'NH', + :zip => '03038', + :country => 'US' + }, + }) assert_failure response assert_equal 'Insufficient Funds', response.message end - def test_authorization_capture_refund_void - #Auth - assert auth_response = @gateway.authorize(10010, @credit_card1, @options) + def test_authorize_capture_refund_void + assert auth = @gateway.authorize(10010, @credit_card1, @options) + assert_success auth + assert_equal 'Approved', auth.message + + assert capture = @gateway.capture(nil, auth.authorization) + assert_success capture + assert_equal 'Approved', capture.message + + assert refund = @gateway.refund(nil, capture.authorization) + assert_success refund + assert_equal 'Approved', refund.message + + assert void = @gateway.void(refund.authorization) + assert_success void + assert_equal 'Approved', void.message + end + + def test_authorize_and_capture_with_stored_credential_recurring + credit_card = CreditCard.new(@credit_card_hash.merge( + number: '4100200300011001', + month: '05', + year: '2021', + verification_value: '463' + )) + + initial_options = @options.merge( + order_id: 'Net_Id1', + stored_credential: { + initial_transaction: true, + reason_type: 'recurring', + initiator: 'merchant', + network_transaction_id: nil + } + ) + assert auth = @gateway.authorize(4999, credit_card, initial_options) + assert_success auth + assert_equal 'Approved', auth.message + assert network_transaction_id = auth.params['networkTransactionId'] + + assert capture = @gateway.capture(4999, auth.authorization) + assert_success capture + assert_equal 'Approved', capture.message + + used_options = @options.merge( + order_id: 'Net_Id1a', + stored_credential: { + initial_transaction: false, + reason_type: 'recurring', + initiator: 'merchant', + network_transaction_id: network_transaction_id + } + ) + assert auth = @gateway.authorize(4999, credit_card, used_options) + assert_success auth + assert_equal 'Approved', auth.message + + assert capture = @gateway.capture(4999, auth.authorization) + assert_success capture + assert_equal 'Approved', capture.message + end + + def test_authorize_and_capture_with_stored_credential_installment + credit_card = CreditCard.new(@credit_card_hash.merge( + number: '4457010000000009', + month: '01', + year: '2021', + verification_value: '349' + )) + + initial_options = @options.merge( + order_id: 'Net_Id2', + stored_credential: { + initial_transaction: true, + reason_type: 'installment', + initiator: 'merchant', + network_transaction_id: nil + } + ) + assert auth = @gateway.authorize(5500, credit_card, initial_options) + assert_success auth + assert_equal 'Approved', auth.message + assert network_transaction_id = auth.params['networkTransactionId'] + + assert capture = @gateway.capture(5500, auth.authorization) + assert_success capture + assert_equal 'Approved', capture.message + + used_options = @options.merge( + order_id: 'Net_Id2a', + stored_credential: { + initial_transaction: false, + reason_type: 'installment', + initiator: 'merchant', + network_transaction_id: network_transaction_id + } + ) + assert auth = @gateway.authorize(5500, credit_card, used_options) + assert_success auth + assert_equal 'Approved', auth.message + + assert capture = @gateway.capture(5500, auth.authorization) + assert_success capture + assert_equal 'Approved', capture.message + end + + def test_authorize_and_capture_with_stored_credential_mit_card_on_file + credit_card = CreditCard.new(@credit_card_hash.merge( + number: '4457000800000002', + month: '01', + year: '2021', + verification_value: '349' + )) + + initial_options = @options.merge( + order_id: 'Net_Id3', + stored_credential: { + initial_transaction: true, + reason_type: 'unscheduled', + initiator: 'merchant', + network_transaction_id: nil + } + ) + assert auth = @gateway.authorize(5500, credit_card, initial_options) + assert_success auth + assert_equal 'Approved', auth.message + assert network_transaction_id = auth.params['networkTransactionId'] + + assert capture = @gateway.capture(5500, auth.authorization) + assert_success capture + assert_equal 'Approved', capture.message + + used_options = @options.merge( + order_id: 'Net_Id3a', + stored_credential: { + initial_transaction: false, + reason_type: 'unscheduled', + initiator: 'merchant', + network_transaction_id: network_transaction_id + } + ) + assert auth = @gateway.authorize(2500, credit_card, used_options) + assert_success auth + assert_equal 'Approved', auth.message + + assert capture = @gateway.capture(2500, auth.authorization) + assert_success capture + assert_equal 'Approved', capture.message + end + + def test_authorize_and_capture_with_stored_credential_cit_card_on_file + credit_card = CreditCard.new(@credit_card_hash.merge( + number: '4457000800000002', + month: '01', + year: '2021', + verification_value: '349' + )) + + initial_options = @options.merge( + order_id: 'Net_Id3', + stored_credential: { + initial_transaction: true, + reason_type: 'unscheduled', + initiator: 'cardholder', + network_transaction_id: nil + } + ) + assert auth = @gateway.authorize(5500, credit_card, initial_options) + assert_success auth + assert_equal 'Approved', auth.message + assert network_transaction_id = auth.params['networkTransactionId'] + + assert capture = @gateway.capture(5500, auth.authorization) + assert_success capture + assert_equal 'Approved', capture.message + + used_options = @options.merge( + order_id: 'Net_Id3b', + stored_credential: { + initial_transaction: false, + reason_type: 'unscheduled', + initiator: 'cardholder', + network_transaction_id: network_transaction_id + } + ) + assert auth = @gateway.authorize(4000, credit_card, used_options) + assert_success auth + assert_equal 'Approved', auth.message + + assert capture = @gateway.capture(4000, auth.authorization) + assert_success capture + assert_equal 'Approved', capture.message + end + + def test_purchase_with_stored_credential_cit_card_on_file_non_ecommerce + credit_card = CreditCard.new(@credit_card_hash.merge( + number: '4457000800000002', + month: '01', + year: '2021', + verification_value: '349' + )) + + initial_options = @options.merge( + order_id: 'Net_Id3', + order_source: '3dsAuthenticated', + xid: 'BwABBJQ1AgAAAAAgJDUCAAAAAAA=', + cavv: 'BwABBJQ1AgAAAAAgJDUCAAAAAAA=', + stored_credential: { + initial_transaction: true, + reason_type: 'unscheduled', + initiator: 'cardholder', + network_transaction_id: nil + } + ) + assert auth = @gateway.purchase(5500, credit_card, initial_options) + assert_success auth + assert_equal 'Approved', auth.message + assert network_transaction_id = auth.params['networkTransactionId'] + + used_options = @options.merge( + order_id: 'Net_Id3b', + order_source: '3dsAuthenticated', + xid: 'BwABBJQ1AgAAAAAgJDUCAAAAAAA=', + cavv: 'BwABBJQ1AgAAAAAgJDUCAAAAAAA=', + stored_credential: { + initial_transaction: false, + reason_type: 'unscheduled', + initiator: 'cardholder', + network_transaction_id: network_transaction_id + } + ) + assert auth = @gateway.purchase(4000, credit_card, used_options) + assert_success auth + assert_equal 'Approved', auth.message + end + + def test_void_with_echeck + options = @options.merge({ + order_id: '42', + order_source: 'telephone' + }) + assert sale = @gateway.purchase(2004, @check, options) + + assert void = @gateway.void(sale.authorization) + assert_success void + assert_equal 'Approved', void.message + end + + def test_void_authorization + assert auth = @gateway.authorize(10010, @credit_card1, @options) + + assert void = @gateway.void(auth.authorization) + assert_success void + assert_equal 'Approved', void.message + end + + def test_unsuccessful_void + assert void = @gateway.void('123456789012345360;authorization;100') + assert_failure void + assert_equal 'No transaction found with specified litleTxnId', void.message + end + + def test_partial_refund + assert purchase = @gateway.purchase(10010, @credit_card1, @options) + + assert refund = @gateway.refund(444, purchase.authorization) + assert_success refund + assert_equal 'Approved', refund.message + end + + def test_partial_refund_with_echeck + options = @options.merge({ + order_id: '82', + order_source: 'telephone' + }) + assert purchase = @gateway.purchase(2004, @check, options) - assert_success auth_response - assert_equal 'Approved', auth_response.message + assert refund = @gateway.refund(444, purchase.authorization) + assert_success refund + assert_equal 'Approved', refund.message + end + + def test_partial_capture + assert auth = @gateway.authorize(10010, @credit_card1, @options) + assert_success auth + assert_equal 'Approved', auth.message + + assert capture = @gateway.capture(5005, auth.authorization) + assert_success capture + assert_equal 'Approved', capture.message + end + + def test_full_amount_capture + assert auth = @gateway.authorize(10010, @credit_card1, @options) + assert_success auth + assert_equal 'Approved', auth.message - #Capture the auth - assert capture_response = @gateway.capture(10010, auth_response.authorization) - assert_success capture_response - assert_equal 'Approved', capture_response.message + assert capture = @gateway.capture(10010, auth.authorization) + assert_success capture + assert_equal 'Approved', capture.message + end - #Credit against the Capture - capture_litle_txn_id = capture_response.params['litleOnlineResponse']['captureResponse']['litleTxnId'] - assert credit_response = @gateway.refund(10010, capture_litle_txn_id) - assert_success credit_response - assert_equal 'Approved', credit_response.message + def test_nil_amount_capture + assert auth = @gateway.authorize(10010, @credit_card1, @options) + assert_success auth + assert_equal 'Approved', auth.message - #Void that credit - credit_litle_txn_id = credit_response.params['litleOnlineResponse']['creditResponse']['litleTxnId'] - assert void_response = @gateway.void(credit_litle_txn_id) - assert_success void_response - assert_equal 'Approved', void_response.message + assert capture = @gateway.capture(nil, auth.authorization) + assert_success capture + assert_equal 'Approved', capture.message end def test_capture_unsuccessful - assert capture_response = @gateway.capture(10010, 123456789012345360) + assert capture_response = @gateway.capture(10010, '123456789012345360') assert_failure capture_response assert_equal 'No transaction found with specified litleTxnId', capture_response.message end def test_refund_unsuccessful - assert credit_response = @gateway.refund(10010, 123456789012345360) + assert credit_response = @gateway.refund(10010, '123456789012345360') assert_failure credit_response assert_equal 'No transaction found with specified litleTxnId', credit_response.message end def test_void_unsuccessful - assert void_response = @gateway.void(123456789012345360) + assert void_response = @gateway.void('123456789012345360') assert_failure void_response assert_equal 'No transaction found with specified litleTxnId', void_response.message end @@ -137,10 +597,20 @@ def test_store_successful assert_success store_response assert_equal 'Account number was successfully registered', store_response.message - assert_equal '445711', store_response.params['litleOnlineResponse']['registerTokenResponse']['bin'] - assert_equal 'VI', store_response.params['litleOnlineResponse']['registerTokenResponse']['type'] #type is on Object in 1.8.7 - later versions can use .registerTokenResponse.type - assert_equal '801', store_response.params['litleOnlineResponse']['registerTokenResponse']['response'] - assert_equal '1111222233330123', store_response.params['litleOnlineResponse']['registerTokenResponse']['litleToken'] + assert_equal '445711', store_response.params['bin'] + assert_equal 'VI', store_response.params['type'] + assert_equal '801', store_response.params['response'] + assert_equal '1111222233330123', store_response.params['litleToken'] + end + + def test_store_with_paypage_registration_id_successful + paypage_registration_id = 'cDZJcmd1VjNlYXNaSlRMTGpocVZQY1NNlYE4ZW5UTko4NU9KK3p1L1p1VzE4ZWVPQVlSUHNITG1JN2I0NzlyTg=' + assert store_response = @gateway.store(paypage_registration_id, :order_id => '50') + + assert_success store_response + assert_equal 'Account number was successfully registered', store_response.message + assert_equal '801', store_response.params['response'] + assert_equal '1111222233334444', store_response.params['litleToken'] end def test_store_unsuccessful @@ -149,29 +619,92 @@ def test_store_unsuccessful assert_failure store_response assert_equal 'Credit card number was invalid', store_response.message - assert_equal '820', store_response.params['litleOnlineResponse']['registerTokenResponse']['response'] + assert_equal '820', store_response.params['response'] end def test_store_and_purchase_with_token_successful credit_card = CreditCard.new(@credit_card_hash.merge(:number => '4100280190123000')) assert store_response = @gateway.store(credit_card, :order_id => '50') + assert_success store_response + + token = store_response.authorization + assert_equal store_response.params['litleToken'], token + + assert response = @gateway.purchase(10010, token) + assert_success response + assert_equal 'Approved', response.message + end + def test_echeck_store_and_purchase + assert store_response = @gateway.store(@store_check) assert_success store_response + assert_equal 'Account number was successfully registered', store_response.message token = store_response.authorization - assert_equal store_response.params['litleOnlineResponse']['registerTokenResponse']['litleToken'], token - - options = { - :order_id => '12345', - :token => { - :month => credit_card.month, - :year => credit_card.year - } - } + assert_equal store_response.params['litleToken'], token + + assert response = @gateway.purchase(10010, token) + assert_success response + assert_equal 'Approved', response.message + end - assert response = @gateway.purchase(10010, token, options) + def test_successful_verify + assert response = @gateway.verify(@credit_card1, @options) assert_success response assert_equal 'Approved', response.message + assert_success response.responses.last, 'The void should succeed' end + def test_unsuccessful_verify + assert response = @gateway.verify(@credit_card_nsf, @options) + assert_failure response + assert_match %r{Insufficient Funds}, response.message + end + + def test_successful_purchase_with_dynamic_descriptors + assert response = @gateway.purchase(10010, @credit_card1, @options.merge( + descriptor_name: 'SuperCompany', + descriptor_phone: '9193341121' + )) + assert_success response + assert_equal 'Approved', response.message + end + + def test_unsuccessful_xml_schema_validation + credit_card = CreditCard.new(@credit_card_hash.merge(:number => '123456')) + assert store_response = @gateway.store(credit_card, :order_id => '51') + + assert_failure store_response + assert_match(/^Error validating xml data against the schema/, store_response.message) + assert_equal '1', store_response.params['response'] + end + + def test_purchase_scrubbing + credit_card = CreditCard.new(@credit_card_hash.merge(verification_value: '999')) + transcript = capture_transcript(@gateway) do + @gateway.purchase(10010, credit_card, @options) + end + transcript = @gateway.scrub(transcript) + + assert_scrubbed(credit_card.number, transcript) + assert_scrubbed(credit_card.verification_value, transcript) + assert_scrubbed(@gateway.options[:login], transcript) + assert_scrubbed(@gateway.options[:password], transcript) + end + + def test_echeck_scrubbing + options = @options.merge({ + order_id: '42', + order_source: 'telephone' + }) + transcript = capture_transcript(@gateway) do + @gateway.purchase(2004, @check, options) + end + transcript = @gateway.scrub(transcript) + + assert_scrubbed(@check.account_number, transcript) + assert_scrubbed(@check.routing_number, transcript) + assert_scrubbed(@gateway.options[:login], transcript) + assert_scrubbed(@gateway.options[:password], transcript) + end end diff --git a/test/remote/gateways/remote_maxipago_test.rb b/test/remote/gateways/remote_maxipago_test.rb new file mode 100644 index 00000000000..4e6c852937a --- /dev/null +++ b/test/remote/gateways/remote_maxipago_test.rb @@ -0,0 +1,131 @@ +require 'test_helper' + +class RemoteMaxipagoTest < Test::Unit::TestCase + def setup + @gateway = MaxipagoGateway.new(fixtures(:maxipago)) + + @amount = 1000 + @invalid_amount = 2009 + @credit_card = credit_card('4111111111111111', verification_value: '444') + @invalid_card = credit_card('4111111111111111', year: Time.now.year - 1) + + @options = { + order_id: '12345', + billing_address: address, + description: 'Store Purchase', + installments: 3 + } + end + + def test_successful_authorize + assert response = @gateway.authorize(@amount, @credit_card, @options) + assert_success response + assert_equal 'AUTHORIZED', response.message + end + + def test_failed_authorize + assert response = @gateway.authorize(@amount, @invalid_card, @options) + assert_failure response + assert_equal 'The transaction has an expired credit card.', response.message + end + + def test_successful_authorize_and_capture + amount = @amount + authorize = @gateway.authorize(amount, @credit_card, @options) + assert_success authorize + + capture = @gateway.capture(amount, authorize.authorization, @options) + assert_success capture + end + + def test_successful_purchase + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + end + + def test_successful_purchase_sans_options + assert response = @gateway.purchase(@amount, @credit_card) + assert_success response + end + + def test_successful_purchase_with_currency + assert response = @gateway.purchase(@amount, @credit_card, @options.merge(currency: 'CLP')) + assert_success response + end + + def test_failed_purchase + assert response = @gateway.purchase(@invalid_amount, @credit_card, @options) + assert_failure response + end + + def test_failed_capture + assert response = @gateway.capture(@amount, 'bogus') + assert_failure response + end + + def test_successful_void + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + void = @gateway.void(auth.authorization) + assert_success void + assert_equal 'VOIDED', void.message + end + + def test_failed_void + response = @gateway.void('NOAUTH|0000000') + assert_failure response + assert_equal 'Unable to validate, original void transaction not found', response.message + end + + def test_successful_refund + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + + refund = @gateway.refund(@amount, purchase.authorization, @options) + assert_success refund + assert_equal 'CAPTURED', refund.message + end + + def test_failed_refund + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + + refund_amount = @amount + 10 + refund = @gateway.refund(refund_amount, purchase.authorization, @options) + assert_failure refund + assert_equal 'The Return amount is greater than the amount that can be returned.', refund.message + end + + def test_successful_verify + response = @gateway.verify(@credit_card, @options) + assert_success response + assert_equal 'AUTHORIZED', response.message + end + + def test_failed_verify + response = @gateway.verify(@invalid_card, @options) + assert_failure response + assert_equal 'The transaction has an expired credit card.', response.message + end + + def test_transcript_scrubbing + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @credit_card, @options) + end + clean_transcript = @gateway.scrub(transcript) + + assert_scrubbed(@credit_card.number, clean_transcript) + assert_scrubbed(@credit_card.verification_value.to_s, clean_transcript) + assert_scrubbed(@gateway.options[:password], clean_transcript) + end + + def test_invalid_login + gateway = MaxipagoGateway.new( + login: '', + password: '' + ) + response = gateway.purchase(@amount, @credit_card, @options) + assert_failure response + end +end diff --git a/test/remote/gateways/remote_mercado_pago_test.rb b/test/remote/gateways/remote_mercado_pago_test.rb new file mode 100644 index 00000000000..f6a9e0fc0bb --- /dev/null +++ b/test/remote/gateways/remote_mercado_pago_test.rb @@ -0,0 +1,243 @@ +require 'test_helper' + +class RemoteMercadoPagoTest < Test::Unit::TestCase + def setup + @gateway = MercadoPagoGateway.new(fixtures(:mercado_pago)) + @argentina_gateway = MercadoPagoGateway.new(fixtures(:mercado_pago_argentina)) + + @amount = 500 + @credit_card = credit_card('4509953566233704') + @elo_credit_card = credit_card('5067268650517446', + :month => 10, + :year => 2020, + :first_name => 'John', + :last_name => 'Smith', + :verification_value => '737' + ) + @cabal_credit_card = credit_card('6035227716427021', + :month => 10, + :year => 2020, + :first_name => 'John', + :last_name => 'Smith', + :verification_value => '737' + ) + @declined_card = credit_card('4000300011112220') + @options = { + billing_address: address, + shipping_address: address, + email: 'user+br@example.com', + description: 'Store Purchase' + } + @processing_options = { + binary_mode: false, + processing_mode: 'gateway', + merchant_account_id: fixtures(:mercado_pago)[:merchant_account_id], + fraud_scoring: true, + fraud_manual_review: true + } + end + + def test_successful_purchase + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal 'accredited', response.message + end + + def test_successful_purchase_with_elo + response = @gateway.purchase(@amount, @elo_credit_card, @options) + assert_success response + assert_equal 'accredited', response.message + end + + def test_successful_purchase_with_cabal + response = @argentina_gateway.purchase(@amount, @cabal_credit_card, @options) + assert_success response + assert_equal 'accredited', response.message + end + + def test_successful_purchase_with_binary_false + @options.update(binary_mode: false) + response = @gateway.authorize(@amount, @credit_card, @options) + assert_success response + assert_equal 'pending_capture', response.message + end + + # Requires setup on merchant account + def test_successful_purchase_with_processing_mode_gateway + response = @gateway.purchase(@amount, @credit_card, @options.merge(@processing_options)) + assert_success response + assert_equal 'accredited', response.message + end + + def test_successful_purchase_with_american_express + amex_card = credit_card('375365153556885', brand: 'american_express', verification_value: '1234') + + response = @gateway.purchase(@amount, amex_card, @options) + assert_success response + assert_equal 'accredited', response.message + end + + def test_failed_purchase + response = @gateway.purchase(@amount, @declined_card, @options) + assert_failure response + assert_equal 'rejected', response.error_code + assert_equal 'cc_rejected_other_reason', response.message + end + + def test_successful_authorize_and_capture + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + assert_equal 'pending_capture', auth.message + + assert capture = @gateway.capture(@amount, auth.authorization) + assert_success capture + assert_equal 'accredited', capture.message + end + + def test_successful_authorize_and_capture_with_elo + auth = @gateway.authorize(@amount, @elo_credit_card, @options) + assert_success auth + assert_equal 'pending_capture', auth.message + + assert capture = @gateway.capture(@amount, auth.authorization) + assert_success capture + assert_equal 'accredited', capture.message + end + + def test_successful_authorize_and_capture_with_cabal + auth = @argentina_gateway.authorize(@amount, @cabal_credit_card, @options) + assert_success auth + assert_equal 'pending_capture', auth.message + + assert capture = @argentina_gateway.capture(@amount, auth.authorization) + assert_success capture + assert_equal 'accredited', capture.message + end + + def test_failed_authorize + response = @gateway.authorize(@amount, @declined_card, @options) + assert_failure response + assert_equal 'cc_rejected_other_reason', response.message + end + + def test_partial_capture + auth = @gateway.authorize(@amount+1, @credit_card, @options) + assert_success auth + + assert capture = @gateway.capture(@amount, auth.authorization) + assert_success capture + assert_equal 'accredited', capture.message + end + + def test_failed_capture + response = @gateway.capture(@amount, '') + assert_failure response + assert_equal 'json_parse_error', response.message + end + + def test_successful_refund + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + + assert refund = @gateway.refund(@amount, purchase.authorization) + assert_success refund + assert_equal nil, refund.message + end + + def test_successful_refund_with_elo + purchase = @gateway.purchase(@amount, @elo_credit_card, @options) + assert_success purchase + + assert refund = @gateway.refund(@amount, purchase.authorization) + assert_success refund + assert_equal nil, refund.message + end + + def test_successful_refund_with_cabal + purchase = @argentina_gateway.purchase(@amount, @cabal_credit_card, @options) + assert_success purchase + + assert refund = @argentina_gateway.refund(@amount, purchase.authorization) + assert_success refund + assert_equal nil, refund.message + end + + def test_partial_refund + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + + assert refund = @gateway.refund(@amount-1, purchase.authorization) + assert_success refund + end + + def test_failed_refund + response = @gateway.refund(@amount, '') + assert_failure response + assert_equal 'Not Found', response.message + end + + def test_successful_void + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + assert void = @gateway.void(auth.authorization) + assert_success void + assert_equal 'by_collector', void.message + end + + def test_successful_void_with_elo + auth = @gateway.authorize(@amount, @elo_credit_card, @options) + assert_success auth + + assert void = @gateway.void(auth.authorization) + assert_success void + assert_equal 'by_collector', void.message + end + + def test_successful_void_with_cabal + auth = @argentina_gateway.authorize(@amount, @cabal_credit_card, @options) + assert_success auth + + assert void = @argentina_gateway.void(auth.authorization) + assert_success void + assert_equal 'by_collector', void.message + end + + def test_failed_void + response = @gateway.void('') + assert_failure response + assert_equal 'json_parse_error', response.message + end + + def test_successful_verify + response = @gateway.verify(@credit_card, @options) + assert_success response + assert_match %r{pending_capture}, response.message + end + + def test_failed_verify + response = @gateway.verify(@declined_card, @options) + assert_failure response + assert_match %r{cc_rejected_other_reason}, response.message + end + + def test_invalid_login + gateway = MercadoPagoGateway.new(access_token: '') + + response = gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_match %r{Invalid access parameters}, response.message + end + + def test_transcript_scrubbing + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @credit_card, @options) + end + transcript = @gateway.scrub(transcript) + + assert_scrubbed(@credit_card.number, transcript) + assert_scrubbed(@credit_card.verification_value, transcript) + assert_scrubbed(@gateway.options[:access_token], transcript) + end + +end diff --git a/test/remote/gateways/remote_merchant_e_solutions_test.rb b/test/remote/gateways/remote_merchant_e_solutions_test.rb index 7ce2c5aaf8c..7226be98b4d 100644 --- a/test/remote/gateways/remote_merchant_e_solutions_test.rb +++ b/test/remote/gateways/remote_merchant_e_solutions_test.rb @@ -2,7 +2,7 @@ class RemoteMerchantESolutionTest < Test::Unit::TestCase def setup - Base.gateway_mode = :test + Base.mode = :test @gateway = MerchantESolutionsGateway.new(fixtures(:merchant_esolutions)) @@ -11,6 +11,7 @@ def setup @declined_card = credit_card('4111111111111112') @options = { + :order_id => '123', :billing_address => { :name => 'John Doe', :address1 => '123 State Street', @@ -39,7 +40,14 @@ def test_successful_purchase def test_unsuccessful_purchase assert response = @gateway.purchase(@amount, @declined_card, @options) assert_failure response - assert_equal 'Card No. Error', response.message + assert_equal 'Invalid card number', response.message + end + + def test_purchase_with_long_order_id + options = {order_id: 'thisislongerthan17characters'} + assert response = @gateway.purchase(@amount, @credit_card, options) + assert_success response + assert_equal 'This transaction has been approved', response.message end def test_authorize_and_capture @@ -49,7 +57,7 @@ def test_authorize_and_capture assert_equal 'This transaction has been approved', auth.message assert auth.authorization let_mes_catch_up - assert capture = @gateway.capture(amount, auth.authorization) + assert capture = @gateway.capture(amount, auth.authorization, @options) assert_success capture assert_equal 'This transaction has been approved', capture.message end @@ -141,7 +149,7 @@ def test_unsuccessful_avs_check_with_bad_zip } assert response = @gateway.purchase(@amount, @credit_card, options) assert_equal 'A', response.avs_result['code'] - assert_equal 'Street address matches, but 5-digit and 9-digit postal code do not match.', response.avs_result['message'] + assert_equal 'Street address matches, but postal code does not match.', response.avs_result['message'] assert_equal 'Y', response.avs_result['street_match'] assert_equal 'N', response.avs_result['postal_match'] end @@ -149,7 +157,7 @@ def test_unsuccessful_avs_check_with_bad_zip def test_successful_cvv_check assert response = @gateway.purchase(@amount, @credit_card, @options) assert_equal 'M', response.cvv_result['code'] - assert_equal 'Match', response.cvv_result['message'] + assert_equal 'CVV matches', response.cvv_result['message'] end def test_unsuccessful_cvv_check @@ -163,7 +171,7 @@ def test_unsuccessful_cvv_check }) assert response = @gateway.purchase(@amount, credit_card, @options) assert_equal 'N', response.cvv_result['code'] - assert_equal 'No Match', response.cvv_result['message'] + assert_equal 'CVV does not match', response.cvv_result['message'] end def test_invalid_login @@ -181,4 +189,25 @@ def test_connection_failure_404_notfound_with_purchase assert_failure response assert_equal 'Failed with 404 Not Found', response.message end + + def test_successful_purchase_with_3dsecure_params + assert response = @gateway.purchase(@amount, @credit_card, @options.merge( + { :xid => 'ERERERERERERERERERERERERERE=', + :cavv => 'ERERERERERERERERERERERERERE=' + })) + assert_success response + assert_equal 'This transaction has been approved', response.message + end + + def test_transcript_scrubbing + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @credit_card, @options) + end + transcript = @gateway.scrub(transcript) + + assert_scrubbed(@credit_card.number, transcript) + assert_match(%r{cvv2\=\[FILTERED\]}, transcript) + assert_no_match(%r{cvv2=#{@credit_card.verification_value}}, transcript) + assert_scrubbed(@gateway.options[:password], transcript) + end end diff --git a/test/remote/gateways/remote_merchant_one_test.rb b/test/remote/gateways/remote_merchant_one_test.rb index 936cb70235e..0e4f98fb23e 100644 --- a/test/remote/gateways/remote_merchant_one_test.rb +++ b/test/remote/gateways/remote_merchant_one_test.rb @@ -31,34 +31,34 @@ def test_successful_purchase assert_equal 'SUCCESS', response.message end - def test_unsuccessful_purchase - assert response = @gateway.purchase(@amount, @declined_card, @options) - assert_failure response - assert response.message.include? 'Invalid Credit Card Number' - end + def test_unsuccessful_purchase + assert response = @gateway.purchase(@amount, @declined_card, @options) + assert_failure response + assert response.message.include? 'Invalid Credit Card Number' + end - def test_authorize_and_capture - amount = @amount - assert auth = @gateway.authorize(amount, @credit_card, @options) - assert_success auth - assert_equal 'SUCCESS', auth.message - assert auth.authorization, auth.to_yaml - assert capture = @gateway.capture(amount, auth.authorization) - assert_success capture - end + def test_authorize_and_capture + amount = @amount + assert auth = @gateway.authorize(amount, @credit_card, @options) + assert_success auth + assert_equal 'SUCCESS', auth.message + assert auth.authorization, auth.to_yaml + assert capture = @gateway.capture(amount, auth.authorization) + assert_success capture + end - def test_failed_capture + def test_failed_capture assert response = @gateway.capture(@amount, '') assert_failure response - end + end - def test_invalid_login - gateway = MerchantOneGateway.new( - :username => 'nnn', - :password => 'nnn' - ) - assert response = gateway.purchase(@amount, @credit_card, @options) - assert_failure response - assert_equal 'Authentication Failed', response.message - end + def test_invalid_login + gateway = MerchantOneGateway.new( + :username => 'nnn', + :password => 'nnn' + ) + assert response = gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_equal 'Authentication Failed', response.message + end end diff --git a/test/remote/gateways/remote_merchant_partners_test.rb b/test/remote/gateways/remote_merchant_partners_test.rb new file mode 100644 index 00000000000..846415b674b --- /dev/null +++ b/test/remote/gateways/remote_merchant_partners_test.rb @@ -0,0 +1,162 @@ +require 'test_helper' + +class RemoteMerchantPartnersTest < Test::Unit::TestCase + def setup + @gateway = MerchantPartnersGateway.new(fixtures(:merchant_partners)) + + @amount = 100 + @credit_card = credit_card('4003000123456781') + @declined_card = credit_card('4003000123456782') + + @options = { + order_id: generate_unique_id, + billing_address: address, + description: 'Store Purchase' + } + end + + def test_invalid_login + gateway = MerchantPartnersGateway.new( + account_id: 'TEST0', + merchant_pin: '1' + ) + response = gateway.purchase(@amount, @credit_card, @options) + assert_failure response + end + + def test_successful_purchase + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal 'Succeeded', response.message + end + + def test_failed_purchase + response = @gateway.purchase(@amount, @declined_card, @options) + assert_failure response + assert_match(/Invalid account/, response.message) + assert response.params['result'].start_with?('DECLINED') + end + + def test_successful_authorize_and_capture + response = @gateway.authorize(@amount, @credit_card, @options) + assert_success response + assert_equal 'Succeeded', response.message + assert_match %r(^\d+), response.authorization + + capture = @gateway.capture(@amount, response.authorization) + assert_success capture + assert_equal 'Succeeded', capture.message + end + + def test_failed_authorize + response = @gateway.authorize(@amount, @declined_card, @options) + assert_failure response + assert_match(/Invalid account/, response.message) + assert response.params['result'].start_with?('DECLINED') + end + + def test_failed_capture + response = @gateway.capture(@amount, 'BAD') + assert_failure response + assert_equal 'Missing account number', response.message + assert response.params['result'].start_with?('DECLINED') + end + + def test_successful_void + response = @gateway.authorize(@amount, @credit_card, @options) + assert_success response + + void = @gateway.void(response.authorization) + assert_success void + assert_equal 'Succeeded', void.message + end + + def test_failed_void + response = @gateway.void('') + assert_failure response + assert_equal 'Invalid acct type', response.message + assert response.params['result'].start_with?('DECLINED') + end + + def test_successful_refund + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + + refund = @gateway.refund(@amount, response.authorization) + assert_success refund + assert_equal 'Succeeded', refund.message + end + + def test_failed_refund + response = @gateway.refund(nil, '') + assert_failure response + assert_equal 'Missing account number', response.message + assert response.params['result'].start_with?('DECLINED') + end + + def test_successful_credit + response = @gateway.credit(@amount, @credit_card, @options) + assert_success response + assert_equal 'Succeeded', response.message + end + + def test_failed_credit + response = @gateway.credit(@amount, @declined_card, @options) + assert_failure response + assert_match(/Invalid account/, response.message) + assert response.params['result'].start_with?('DECLINED') + end + + def test_successful_verify + response = @gateway.verify(@credit_card, @options) + assert_success response + assert_match %r{Succeeded}, response.message + end + + def test_failed_verify + response = @gateway.verify(@declined_card, @options) + assert_failure response + assert_match(/Invalid account/, response.message) + assert response.params['result'].start_with?('DECLINED') + end + + def test_successful_store_and_purchase + response = @gateway.store(@credit_card, @options) + assert_success response + assert_equal 'Succeeded', response.message + + purchase = @gateway.purchase(@amount, response.authorization, @options) + assert_success purchase + assert_equal 'Succeeded', purchase.message + end + + def test_successful_store_and_credit + response = @gateway.store(@credit_card, @options) + assert_success response + assert_equal 'Succeeded', response.message + + credit = @gateway.credit(@amount, response.authorization, @options) + assert_success credit + assert_equal 'Succeeded', credit.message + end + + def test_failed_store + response = @gateway.store(@declined_card, @options) + assert_failure response + + # Test gateway bombs w/ live-transaction error so can't test + # assert_equal "Invalid account number", response.message + # assert_equal "", response.error_code + end + + def test_transcript_scrubbing + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @credit_card, @options) + end + clean_transcript = @gateway.scrub(transcript) + + assert_scrubbed(@credit_card.number, clean_transcript) + assert_scrubbed(@credit_card.verification_value.to_s, clean_transcript) + assert_scrubbed(@gateway.options[:merchant_pin], clean_transcript) + end +end diff --git a/test/remote/gateways/remote_merchant_ware_test.rb b/test/remote/gateways/remote_merchant_ware_test.rb index 399f0552ce6..b4b4dc2c899 100644 --- a/test/remote/gateways/remote_merchant_ware_test.rb +++ b/test/remote/gateways/remote_merchant_ware_test.rb @@ -4,7 +4,7 @@ class RemoteMerchantWareTest < Test::Unit::TestCase def setup @gateway = MerchantWareGateway.new(fixtures(:merchant_ware)) - @amount = rand(1000) + 200 + @amount = rand(200..1199) @credit_card = credit_card('5424180279791732', {:brand => 'master'}) @@ -21,7 +21,7 @@ def test_successful_authorization end def test_unsuccessful_authorization - @credit_card.number = "1234567890123" + @credit_card.number = '1234567890123' assert response = @gateway.authorize(@amount, @credit_card, @options) assert_failure response end @@ -33,7 +33,7 @@ def test_successful_purchase end def test_unsuccessful_purchase - @credit_card.number = "1234567890123" + @credit_card.number = '1234567890123' assert response = @gateway.purchase(@amount, @credit_card, @options) assert_failure response end diff --git a/test/remote/gateways/remote_merchant_ware_version_four_test.rb b/test/remote/gateways/remote_merchant_ware_version_four_test.rb index 117ef58205f..155bdc2e460 100644 --- a/test/remote/gateways/remote_merchant_ware_version_four_test.rb +++ b/test/remote/gateways/remote_merchant_ware_version_four_test.rb @@ -3,18 +3,17 @@ class RemoteMerchantWareVersionFourTest < Test::Unit::TestCase def setup @gateway = MerchantWareVersionFourGateway.new(fixtures(:merchant_ware_version_four)) - - @amount = rand(1000) + 200 - + @amount = rand(200..1199) @credit_card = credit_card('5424180279791732', {:brand => 'master'}) + @declined_card = credit_card('1234567890123') @options = { - :order_id => generate_unique_id[0,8], + :order_id => generate_unique_id[0, 8], :billing_address => address } @reference_purchase_options = { - :order_id => generate_unique_id[0,8] + :order_id => generate_unique_id[0, 8] } end @@ -25,7 +24,7 @@ def test_successful_authorization end def test_unsuccessful_authorization - @credit_card.number = "1234567890123" + @credit_card.number = '1234567890123' assert response = @gateway.authorize(@amount, @credit_card, @options) assert_failure response end @@ -37,7 +36,7 @@ def test_successful_purchase end def test_unsuccessful_purchase - @credit_card.number = "1234567890123" + @credit_card.number = '1234567890123' assert response = @gateway.purchase(@amount, @credit_card, @options) assert_failure response end @@ -71,8 +70,8 @@ def test_purchase_and_reference_purchase assert purchase.authorization assert reference_purchase = @gateway.purchase(@amount, - purchase.authorization, - @reference_purchase_options) + purchase.authorization, + @reference_purchase_options) assert_success reference_purchase assert_not_nil reference_purchase.authorization end @@ -101,4 +100,27 @@ def test_invalid_login assert_failure response assert_equal 'Invalid Credentials.', response.message end + + def test_successful_verify + response = @gateway.verify(@credit_card, @options) + assert_success response + assert_equal 'APPROVED', response.message + end + + def test_failed_verify + response = @gateway.verify(@declined_card, @options) + assert_failure response + assert_equal 'Invalid card number.', response.message + end + + def test_transcript_scrubbing + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @credit_card, @options) + end + transcript = @gateway.scrub(transcript) + + assert_scrubbed(@credit_card.number, transcript) + assert_scrubbed(@credit_card.verification_value, transcript) + assert_scrubbed(@gateway.options[:password], transcript) + end end diff --git a/test/remote/gateways/remote_merchant_warrior_test.rb b/test/remote/gateways/remote_merchant_warrior_test.rb index dadef6c86a4..d9c5e826c4a 100644 --- a/test/remote/gateways/remote_merchant_warrior_test.rb +++ b/test/remote/gateways/remote_merchant_warrior_test.rb @@ -10,13 +10,13 @@ def setup @credit_card = credit_card( '5123456789012346', :month => 5, - :year => 17, + :year => Time.now.year + 2, :verification_value => '123', :brand => 'master' ) @options = { - :address => { + :billing_address => { :name => 'Longbob Longsen', :country => 'AU', :state => 'Queensland', @@ -24,7 +24,7 @@ def setup :address1 => '123 test st', :zip => '4000' }, - :transaction_product => 'TestProduct' + :description => 'TestProduct' } end @@ -32,13 +32,13 @@ def test_successful_authorize assert auth = @gateway.authorize(@success_amount, @credit_card, @options) assert_success auth assert_equal 'Transaction approved', auth.message - assert_not_nil auth.params["transaction_id"] - assert_equal auth.params["transaction_id"], auth.authorization + assert_not_nil auth.params['transaction_id'] + assert_equal auth.params['transaction_id'], auth.authorization assert capture = @gateway.capture(@success_amount, auth.authorization) assert_success capture - assert_not_nil capture.params["transaction_id"] - assert_equal capture.params["transaction_id"], capture.authorization + assert_not_nil capture.params['transaction_id'] + assert_equal capture.params['transaction_id'], capture.authorization assert_not_equal auth.authorization, capture.authorization end @@ -46,16 +46,16 @@ def test_successful_purchase assert purchase = @gateway.purchase(@success_amount, @credit_card, @options) assert_equal 'Transaction approved', purchase.message assert_success purchase - assert_not_nil purchase.params["transaction_id"] - assert_equal purchase.params["transaction_id"], purchase.authorization + assert_not_nil purchase.params['transaction_id'] + assert_equal purchase.params['transaction_id'], purchase.authorization end def test_failed_purchase assert purchase = @gateway.purchase(@failure_amount, @credit_card, @options) assert_equal 'Transaction declined', purchase.message assert_failure purchase - assert_not_nil purchase.params["transaction_id"] - assert_equal purchase.params["transaction_id"], purchase.authorization + assert_not_nil purchase.params['transaction_id'] + assert_equal purchase.params['transaction_id'], purchase.authorization end def test_successful_refund @@ -68,7 +68,7 @@ def test_successful_refund def test_failed_refund assert refund = @gateway.refund(@success_amount, 'invalid-transaction-id') - assert_match /Invalid transactionID/, refund.message + assert_match %r{Invalid transactionID}, refund.message assert_failure refund end @@ -78,7 +78,7 @@ def test_capture_too_much assert_equal 'Transaction approved', auth.message assert capture = @gateway.capture(400, auth.authorization) - assert_match /Capture amount is greater than the transaction amount/, capture.message + assert_match %r{Capture amount is greater than the transaction amount}, capture.message assert_failure capture end @@ -103,4 +103,39 @@ def test_token_auth assert capture = @gateway.capture(@success_amount, auth.authorization) assert_success capture end + + def test_successful_purchase_with_funky_names + @credit_card.first_name = 'Phillips & Sons' + @credit_card.last_name = "Other-Things; MW. doesn't like" + @options[:billing_address][:name] = 'Merchant Warrior wants % alphanumerics' + + assert purchase = @gateway.purchase(@success_amount, @credit_card, @options) + assert_equal 'Transaction approved', purchase.message + assert_success purchase + end + + def test_transcript_scrubbing + transcript = capture_transcript(@gateway) do + @gateway.purchase(@success_amount, @credit_card, @options) + end + transcript = @gateway.scrub(transcript) + + assert_scrubbed(@credit_card.number, transcript) + assert_match(%r{paymentCardCSC\=\[FILTERED\]}, transcript) + assert_no_match(%r{paymentCardCSC=#{@credit_card.verification_value}}, transcript) + assert_scrubbed(@gateway.options[:api_passphrase], transcript) + assert_scrubbed(@gateway.options[:api_key], transcript) + end + + def test_transcript_scrubbing_store + transcript = capture_transcript(@gateway) do + @gateway.store(@credit_card, @options) + end + transcript = @gateway.scrub(transcript) + + assert_scrubbed(@credit_card.number, transcript) + assert_scrubbed(@credit_card.verification_value, transcript) + assert_scrubbed(@gateway.options[:api_passphrase], transcript) + assert_scrubbed(@gateway.options[:api_key], transcript) + end end diff --git a/test/remote/gateways/remote_mercury_certification_test.rb b/test/remote/gateways/remote_mercury_certification_test.rb index 9020387e86c..56cb9de3234 100644 --- a/test/remote/gateways/remote_mercury_certification_test.rb +++ b/test/remote/gateways/remote_mercury_certification_test.rb @@ -1,5 +1,5 @@ require 'test_helper' -require "support/mercury_helper" +require 'support/mercury_helper' class RemoteMercuryCertificationTest < Test::Unit::TestCase include MercuryHelper @@ -9,130 +9,106 @@ class RemoteMercuryCertificationTest < Test::Unit::TestCase def test_sale_and_reversal close_batch(tokenization_gateway) - sale = tokenization_gateway.purchase(101, visa, options("1")) + sale = tokenization_gateway.purchase(101, visa, options('1')) assert_success sale - assert_equal "AP", sale.params["text_response"] + assert_equal 'AP', sale.params['text_response'] reversal = tokenization_gateway.void(sale.authorization, options.merge(:try_reversal => true)) assert_success reversal - assert_equal "REVERSED", reversal.params["text_response"] + assert_equal 'REVERSED', reversal.params['text_response'] end def test_sale_and_void close_batch(tokenization_gateway) - sale = tokenization_gateway.purchase(103, visa, options("1")) + sale = tokenization_gateway.purchase(103, visa, options('1')) assert_success sale - assert_equal "AP", sale.params["text_response"] + assert_equal 'AP', sale.params['text_response'] void = tokenization_gateway.void(sale.authorization, options) assert_success void - assert_equal "AP", void.params["text_response"] - end - - def test_preauth_capture_and_reversal - close_batch(tokenization_gateway) - - cc = credit_card( - "4005550000000480", - :brand => "visa", - :month => "12", - :year => "15", - :verification_value => "123" - ) - - preauth = tokenization_gateway.authorize(106, cc, options("1")) - assert_success preauth - assert_equal "AP", preauth.params["text_response"] - - capture = tokenization_gateway.capture(106, preauth.authorization, options) - assert_success capture - assert_equal "AP", capture.params["text_response"] - - reversal = tokenization_gateway.void(capture.authorization, options.merge(:try_reversal => true)) - assert_success reversal - assert_equal "REVERSED", reversal.params["text_response"] + assert_equal 'AP', void.params['text_response'] end def test_return close_batch(tokenization_gateway) - credit = tokenization_gateway.credit(109, visa, options("1")) + credit = tokenization_gateway.credit(109, visa, options('1')) assert_success credit - assert_equal "AP", credit.params["text_response"] + assert_equal 'AP', credit.params['text_response'] end def test_preauth_and_reversal close_batch(tokenization_gateway) - preauth = tokenization_gateway.authorize(113, disc, options("1")) + preauth = tokenization_gateway.authorize(113, disc, options('1')) assert_success preauth - assert_equal "AP", preauth.params["text_response"] + assert_equal 'AP', preauth.params['text_response'] reversal = tokenization_gateway.void(preauth.authorization, options.merge(:try_reversal => true)) assert_success reversal - assert_equal "REVERSED", reversal.params["text_response"] + assert_equal 'REVERSED', reversal.params['text_response'] end def test_preauth_capture_and_reversal close_batch(tokenization_gateway) - preauth = tokenization_gateway.authorize(106, visa, options("1")) + preauth = tokenization_gateway.authorize(106, visa, options('1')) assert_success preauth - assert_equal "AP", preauth.params["text_response"] + assert_equal 'AP', preauth.params['text_response'] capture = tokenization_gateway.capture(206, preauth.authorization, options) assert_success capture - assert_equal "AP", capture.params["text_response"] + assert_equal 'AP', capture.params['text_response'] void = tokenization_gateway.void(capture.authorization, options) assert_success void - assert_equal "AP", void.params["text_response"] + assert_equal 'AP', void.params['text_response'] end private def tokenization_gateway @tokenization_gateway ||= MercuryGateway.new( - :login => "023358150511666", - :password => "xyz" + :login => '023358150511666', + :password => 'xyz' ) end def visa @visa ||= credit_card( - "4003000123456781", - :brand => "visa", - :month => "12", - :year => "15", - :verification_value => "123" + '4003000123456781', + :brand => 'visa', + :month => '12', + :year => '15', + :verification_value => '123' ) end def disc @disc ||= credit_card( - "6011000997235373", - :brand => "discover", - :month => "12", - :year => "15", - :verification_value => "362" + '6011000997235373', + :brand => 'discover', + :month => '12', + :year => '15', + :verification_value => '362' ) end def mc @mc ||= credit_card( - "5439750001500248", - :brand => "master", - :month => "12", - :year => "15", - :verification_value => "123" + '5439750001500248', + :brand => 'master', + :month => '12', + :year => '15', + :verification_value => '123' ) end def options(order_id=nil, other={}) { :order_id => order_id, - :description => "ActiveMerchant", + :description => 'ActiveMerchant', }.merge(other) end end diff --git a/test/remote/gateways/remote_mercury_test.rb b/test/remote/gateways/remote_mercury_test.rb index 5fda0efb03c..4dd1a95b80c 100644 --- a/test/remote/gateways/remote_mercury_test.rb +++ b/test/remote/gateways/remote_mercury_test.rb @@ -1,5 +1,5 @@ require 'test_helper' -require "support/mercury_helper" +require 'support/mercury_helper' class RemoteMercuryTest < Test::Unit::TestCase include MercuryHelper @@ -9,24 +9,27 @@ def setup @amount = 100 - @credit_card = credit_card("4003000123456781", :brand => "visa", :month => "12", :year => "15") + @credit_card = credit_card('4003000123456781', :brand => 'visa', :month => '12', :year => '18') + + @track_1_data = '%B4003000123456781^LONGSEN/L. ^18121200000000000000**123******?*' + @track_2_data = ';5413330089010608=2512101097750213?' @options = { - :order_id => "1", - :description => "ActiveMerchant" + :order_id => 'c111111111.1', + :description => 'ActiveMerchant' } @options_with_billing = @options.merge( :merchant => '999', :billing_address => { - :address1 => '4 Corporate Square', + :address1 => '4 Corporate SQ', :zip => '30329' } ) @full_options = @options_with_billing.merge( :ip => '123.123.123.123', - :merchant => "Open Dining", - :customer => "Tim", - :tax => "5" + :merchant => 'Open Dining', + :customer => 'Tim', + :tax => '5' ) close_batch @@ -45,15 +48,7 @@ def test_successful_authorize_and_capture def test_failed_authorize response = @gateway.authorize(1100, @credit_card, @options) assert_failure response - assert_equal "DECLINE", response.message - end - - def test_reversal - response = @gateway.authorize(100, @credit_card, @options) - assert_success response - - void = @gateway.void(response.authorization, @options.merge(:try_reversal => true)) - assert_success void + assert_equal 'DECLINE', response.message end def test_purchase_and_void @@ -68,20 +63,29 @@ def test_successful_purchase response = @gateway.purchase(50, @credit_card, @options) assert_success response - assert_equal "0.50", response.params["purchase"] + assert_equal '0.50', response.params['purchase'] + end + + def test_store + response = @gateway.store(@credit_card, @options) + + assert_success response + assert response.params.has_key?('record_no') + assert response.params['record_no'] != '' end def test_credit response = @gateway.credit(50, @credit_card, @options) assert_success response - assert_equal "0.50", response.params["purchase"], response.inspect + assert_equal '0.50', response.params['purchase'], response.inspect end def test_failed_purchase response = @gateway.purchase(1100, @credit_card, @options) assert_failure response - assert_equal "DECLINE", response.message + assert_equal 'DECLINE', response.message + assert_equal Gateway::STANDARD_ERROR_CODE[:card_declined], response.error_code end def test_avs_and_cvv_results @@ -90,18 +94,35 @@ def test_avs_and_cvv_results assert_success response assert_equal( { - "code" => "Y", - "postal_match" => "Y", - "street_match" => "Y", - "message" => "Street address and 5-digit postal code match." + 'code' => 'Y', + 'postal_match' => 'Y', + 'street_match' => 'Y', + 'message' => 'Street address and 5-digit postal code match.' }, response.avs_result ) - assert_equal({"code"=>"M", "message"=>"Match"}, response.cvv_result) + assert_equal({'code'=>'M', 'message'=>'CVV matches'}, response.cvv_result) + end + + def test_avs_and_cvv_results_with_track_data + @credit_card.track_data = @track_1_data + response = @gateway.authorize(333, @credit_card, @options_with_billing) + + assert_success response + assert_equal( + { + 'code' => nil, + 'postal_match' => nil, + 'street_match' => nil, + 'message' => nil + }, + response.avs_result + ) + assert_equal({'code'=>'P', 'message'=>'CVV not processed'}, response.cvv_result) end def test_partial_capture - visa_partial_card = credit_card("4005550000000480") + visa_partial_card = credit_card('4005550000000480') response = @gateway.authorize(2354, visa_partial_card, @options) @@ -119,11 +140,11 @@ def test_authorize_with_bad_expiration_date @credit_card.year = 2001 response = @gateway.authorize(575, @credit_card, @options_with_billing) assert_failure response - assert_equal "INVLD EXP DATE", response.message + assert_equal 'INVLD EXP DATE', response.message end def test_mastercard_authorize_and_capture_with_refund - mc = credit_card("5499990123456781", :brand => "master") + mc = credit_card('5499990123456781', :brand => 'master') response = @gateway.authorize(200, mc, @options) assert_success response @@ -140,7 +161,7 @@ def test_mastercard_authorize_and_capture_with_refund end def test_amex_authorize_and_capture_with_refund - amex = credit_card("373953244361001", :brand => "american_express", :verification_value => "1234") + amex = credit_card('373953244361001', :brand => 'american_express', :verification_value => '1234') response = @gateway.authorize(201, amex, @options) assert_success response @@ -156,7 +177,7 @@ def test_amex_authorize_and_capture_with_refund end def test_discover_authorize_and_capture - discover = credit_card("6011000997235373", :brand => "discover") + discover = credit_card('6011000997235373', :brand => 'discover') response = @gateway.authorize(225, discover, @options_with_billing) assert_success response @@ -189,4 +210,46 @@ def test_authorize_and_capture_without_tokenization assert_success capture assert_equal '1.00', capture.params['authorize'] end + + def test_successful_authorize_and_capture_with_track_1_data + @credit_card.track_data = @track_1_data + response = @gateway.authorize(100, @credit_card, @options) + assert_success response + assert_equal '1.00', response.params['authorize'] + + capture = @gateway.capture(nil, response.authorization) + assert_success capture + assert_equal '1.00', capture.params['authorize'] + end + + def test_successful_authorize_and_capture_with_track_2_data + @credit_card.track_data = @track_2_data + response = @gateway.authorize(100, @credit_card, @options) + assert_success response + assert_equal '1.00', response.params['authorize'] + + capture = @gateway.capture(nil, response.authorization) + assert_success capture + assert_equal '1.00', capture.params['authorize'] + end + + def test_authorize_and_void + response = @gateway.authorize(100, @credit_card, @options) + assert_success response + assert_equal '1.00', response.params['authorize'] + + void = @gateway.void(response.authorization) + assert_success void + end + + def test_transcript_scrubbing + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @credit_card, @options) + end + transcript = @gateway.scrub(transcript) + + assert_scrubbed(@credit_card.number, transcript) + assert_scrubbed(@credit_card.verification_value, transcript) + assert_scrubbed(@gateway.options[:password], transcript) + end end diff --git a/test/remote/gateways/remote_metrics_global_test.rb b/test/remote/gateways/remote_metrics_global_test.rb index 4748cdc23c3..3669fd41a11 100644 --- a/test/remote/gateways/remote_metrics_global_test.rb +++ b/test/remote/gateways/remote_metrics_global_test.rb @@ -3,7 +3,7 @@ class MetricsGlobalTest < Test::Unit::TestCase def setup Base.mode = :test - + @gateway = MetricsGlobalGateway.new(fixtures(:metrics_global)) @amount = 100 @credit_card = credit_card('4111111111111111', :verification_value => '999') @@ -13,7 +13,7 @@ def setup :description => 'Store purchase' } end - + def test_successful_purchase assert response = @gateway.purchase(@amount, @credit_card, @options) assert_success response @@ -21,7 +21,7 @@ def test_successful_purchase assert_equal 'This transaction has been approved', response.message assert response.authorization end - + def test_declined_authorization @amount = 10 assert response = @gateway.purchase(@amount, @credit_card, @options) @@ -29,71 +29,71 @@ def test_declined_authorization assert response.test? assert_equal 'This transaction has been declined', response.message end - + def test_successful_authorization assert response = @gateway.authorize(@amount, @credit_card, @options) assert_success response assert_equal 'This transaction has been approved', response.message assert response.authorization end - + def test_authorization_and_capture assert authorization = @gateway.authorize(@amount, @credit_card, @options) assert_success authorization - + assert capture = @gateway.capture(@amount, authorization.authorization) assert_success capture assert_equal 'This transaction has been approved', capture.message end - + def test_authorization_and_void assert authorization = @gateway.authorize(@amount, @credit_card, @options) assert_success authorization - + assert void = @gateway.void(authorization.authorization) assert_success void assert_equal 'This transaction has been approved', void.message end - + def test_bad_login gateway = MetricsGlobalGateway.new( :login => 'X', :password => 'Y' ) - + assert response = gateway.purchase(@amount, @credit_card) - + assert_equal Response, response.class - assert_equal ["avs_result_code", - "card_code", - "response_code", - "response_reason_code", - "response_reason_text", - "transaction_id"], response.params.keys.sort + assert_equal ['avs_result_code', + 'card_code', + 'response_code', + 'response_reason_code', + 'response_reason_text', + 'transaction_id'], response.params.keys.sort assert_match(/Authentication Failed/, response.message) - + assert_equal false, response.success? end - + def test_using_test_request gateway = MetricsGlobalGateway.new( :login => 'X', :password => 'Y' ) - + assert response = gateway.purchase(@amount, @credit_card) - + assert_equal Response, response.class - assert_equal ["avs_result_code", - "card_code", - "response_code", - "response_reason_code", - "response_reason_text", - "transaction_id"], response.params.keys.sort - + assert_equal ['avs_result_code', + 'card_code', + 'response_code', + 'response_reason_code', + 'response_reason_text', + 'transaction_id'], response.params.keys.sort + assert_match(/Authentication Failed/, response.message) - - assert_equal false, response.success? + + assert_equal false, response.success? end end diff --git a/test/remote/gateways/remote_micropayment_test.rb b/test/remote/gateways/remote_micropayment_test.rb new file mode 100644 index 00000000000..fb4589e7d71 --- /dev/null +++ b/test/remote/gateways/remote_micropayment_test.rb @@ -0,0 +1,151 @@ +require 'test_helper' + +class RemoteMicropaymentTest < Test::Unit::TestCase + def setup + @gateway = MicropaymentGateway.new(fixtures(:micropayment)) + + @amount = 250 + @credit_card = credit_card('4111111111111111', verification_value: '666') + @declined_card = credit_card('4111111111111111') + + @options = { + order_id: generate_unique_id, + description: 'Eggcellent', + billing_address: address + } + end + + def test_invalid_login + gateway = MicropaymentGateway.new(access_key: 'invalid', api_key: 'invalid') + response = gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_equal 'Authorization failed - Reason: api accesskey wrong', response.message + end + + def test_successful_purchase + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal 'Succeeded', response.message + end + + def test_failed_purchase + response = @gateway.purchase(@amount, @declined_card, @options) + assert_failure response + assert_equal 'AS stellt falsches Routing fest', response.message + end + + def test_successful_authorize_and_capture + response = @gateway.authorize(@amount, @credit_card) + assert_success response + assert_equal 'Succeeded', response.message + assert_match %r(^\w+\|.+$), response.authorization + + capture = @gateway.capture(@amount, response.authorization) + assert_success capture + assert_equal 'Succeeded', capture.message + end + + def test_successful_authorize_and_capture_with_recurring + @credit_card.verification_value = '' + response = @gateway.authorize(@amount, @credit_card, @options.merge(recurring: true)) + assert_success response + assert_equal 'Succeeded', response.message + assert_match %r(^\w+\|.+$), response.authorization + + capture = @gateway.capture(@amount, response.authorization) + assert_success capture + assert_equal 'Succeeded', capture.message + end + + def test_partial_capture + response = @gateway.authorize(@amount, @credit_card, @options) + capture = @gateway.capture(100, response.authorization) + assert_success capture + assert_equal 'Succeeded', capture.message + end + + def test_failed_authorize + response = @gateway.authorize(@amount, @declined_card, @options) + assert_failure response + assert_equal 'AS stellt falsches Routing fest', response.message + assert_equal 'ipg92', response.params['transactionResultCode'] + end + + def test_failed_capture + response = @gateway.capture(@amount, '1|2') + assert_failure response + assert_equal '"sessionId" with the value "1" does not exist', response.message + assert_equal '3110', response.params['error'] + end + + def test_successful_void_for_purchase + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal 'Succeeded', response.message + + void = @gateway.void(response.authorization) + assert_success void + assert_equal 'Succeeded', void.message + end + + def test_successful_authorize_and_capture_and_refund + response = @gateway.authorize(@amount, @credit_card, @options.merge(recurring: false)) + assert_success response + assert_equal 'Succeeded', response.message + assert_match %r(^\w+\|.+$), response.authorization + + capture = @gateway.capture(@amount, response.authorization) + assert_success capture + assert_equal 'Succeeded', capture.message + + refund = @gateway.refund(@amount, capture.authorization) + assert_success refund + assert_equal 'Succeeded', refund.message + end + + def test_failed_void + response = @gateway.void('') + assert_failure response + assert_equal '"transactionId" is empty', response.message + assert_equal '3101', response.params['error'] + end + + def test_successful_refund + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + + refund = @gateway.refund(@amount, response.authorization) + assert_success refund + assert_equal 'Succeeded', refund.message + end + + def test_failed_refund + response = @gateway.refund(nil, '') + assert_failure response + assert_equal '"transactionId" is empty', response.message + assert_equal '3101', response.params['error'] + end + + def test_successful_verify + response = @gateway.verify(@credit_card, @options) + assert_success response + assert_match 'Succeeded', response.message + end + + def test_failed_verify + response = @gateway.verify(@declined_card, @options) + assert_failure response + assert_equal 'AS stellt falsches Routing fest', response.message + end + + def test_transcript_scrubbing + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @credit_card, @options) + end + clean_transcript = @gateway.scrub(transcript) + + assert_scrubbed(@credit_card.number, clean_transcript) + assert_scrubbed(@credit_card.verification_value.to_s, clean_transcript) + assert_scrubbed(@gateway.options[:access_key], clean_transcript) + end +end diff --git a/test/remote/gateways/remote_migs_test.rb b/test/remote/gateways/remote_migs_test.rb index adbf66b1f38..69ea544201c 100644 --- a/test/remote/gateways/remote_migs_test.rb +++ b/test/remote/gateways/remote_migs_test.rb @@ -2,20 +2,35 @@ require 'net/http' class RemoteMigsTest < Test::Unit::TestCase + include ActiveMerchant::NetworkConnectionRetries + include ActiveMerchant::PostsData + def setup - @gateway = MigsGateway.new(fixtures(:migs_purchase)) - @capture_gateway = MigsGateway.new(fixtures(:migs_capture)) + @gateway = MigsGateway.new(fixtures(:migs)) @amount = 100 @declined_amount = 105 - @visa = credit_card('4005550000000001', :month => 5, :year => 2017, :brand => 'visa') - @master = credit_card('5123456789012346', :month => 5, :year => 2017, :brand => 'master') - @amex = credit_card('371449635311004', :month => 5, :year => 2017, :brand => 'american_express') - @diners = credit_card('30123456789019', :month => 5, :year => 2017, :brand => 'diners_club') + @visa = credit_card('4987654321098769', :month => 5, :year => 2021, :brand => 'visa') + @master = credit_card('5123456789012346', :month => 5, :year => 2021, :brand => 'master') + @amex = credit_card('371449635311004', :month => 5, :year => 2021, :brand => 'american_express') + @diners = credit_card('30123456789019', :month => 5, :year => 2021, :brand => 'diners_club') @credit_card = @visa + @valid_tx_source = 'MOTO' + @invalid_tx_source = 'penguin' + @options = { - :order_id => '1' + :order_id => '1', + :currency => 'SAR' + } + + @three_ds_options = { + :VerType => '3DS', + :VerToken => 'AAACAFBEUBgoAhEAIURQAAAAAAA=', + '3DSXID' => 'NWJlZDJmYzkyMTU1NGEwNzk1YjA=', + '3DSECI' => '02', + '3DSenrolled' => 'Y', + '3DSstatus' => 'A' } end @@ -23,22 +38,22 @@ def test_server_purchase_url options = { :order_id => 1, :unique_id => 9, - :return_url => 'http://localhost:8080/payments/return' + :return_url => 'http://localhost:8080/payments/return', + :currency => 'SAR' } choice_url = @gateway.purchase_offsite_url(@amount, options) - assert_response_contains 'Pay securely by clicking on the card logo below', choice_url + + assert_response_match(/Pay securely .* by clicking on the card logo below/, choice_url) responses = { - 'visa' => 'You have chosen <B>VISA</B>', - 'master' => 'You have chosen <B>MasterCard</B>', - 'diners_club' => 'You have chosen <B>Diners Club</B>', - 'american_express' => 'You have chosen <B>American Express</B>' + 'visa' => /You have chosen .*VISA.*/, + 'master' => /You have chosen .*MasterCard.*/ } responses.each_pair do |card_type, response_text| - url = @capture_gateway.purchase_offsite_url(@amount, options.merge(:card_type => card_type)) - assert_response_contains response_text, url + url = @gateway.purchase_offsite_url(@amount, options.merge(:card_type => card_type)) + assert_response_match response_text, url end end @@ -55,25 +70,83 @@ def test_unsuccessful_purchase end def test_authorize_and_capture - assert auth = @capture_gateway.authorize(@amount, @credit_card, @options) + assert auth = @gateway.authorize(@amount, @credit_card, @options) assert_success auth assert_equal 'Approved', auth.message - assert capture = @capture_gateway.capture(@amount, auth.authorization, @options) + assert capture = @gateway.capture(@amount, auth.authorization, @options) assert_success capture end + def test_authorize_and_void + assert auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + assert_equal 'Approved', auth.message + assert void = @gateway.void(auth.authorization, @options) + assert_success void + assert_equal 'Approved', void.message + end + + def test_verify + assert verify = @gateway.verify(@credit_card, @options) + assert_success verify + assert_equal 'Approved', verify.message + end + def test_failed_authorize - assert response = @capture_gateway.authorize(@declined_amount, @credit_card, @options) + assert response = @gateway.authorize(@declined_amount, @credit_card, @options) assert_failure response assert_equal 'Declined', response.message end def test_refund - assert payment_response = @gateway.purchase(@amount, @credit_card, @options) - assert_success payment_response - assert response = @gateway.refund(@amount, payment_response.authorization, @options) - assert_success response - assert_equal 'Approved', response.message + # skip "Refunds are not working in the testing envirnment" + # assert payment_response = @gateway.purchase(@amount, @credit_card, @options) + # assert_success payment_response + # assert response = @gateway.refund(@amount, payment_response.authorization, @options) + # refute_success response + # assert_equal 'Approved', response.message + end + + def test_purchase_passes_tx_source + # returns a successful response when a valid tx_source parameter is sent + assert good_response = @gateway.purchase(@amount, @credit_card, @options.merge(tx_source: @valid_tx_source)) + assert_success good_response + assert_equal 'Approved', good_response.message + + # returns a failed response when an invalid tx_source parameter is sent + assert bad_response = @gateway.purchase(@amount, @credit_card, @options.merge(tx_source: @invalid_tx_source)) + assert_failure bad_response + end + + def test_capture_passes_tx_source + # authorize the credit card in order to then run capture + assert auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + assert_equal 'Approved', auth.message + + # returns a successful response when a valid tx_source paramater is sent + assert good_response = @gateway.capture(@amount, auth.authorization, @options.merge(tx_source: @valid_tx_source)) + assert_success good_response + + # returns a failed response when an invalid tx_source parameter is sent + assert bad_response = @gateway.capture(@amount, auth.authorization, @options.merge(tx_source: @invalid_tx_source)) + assert_failure bad_response + end + + def test_void_passes_tx_source + # authorize the credit card in order to then run capture + assert auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + assert_equal 'Approved', auth.message + + # returns a successful response when a valid tx_source paramater is sent + assert good_response = @gateway.void(auth.authorization, @options.merge(tx_source: @valid_tx_source)) + assert_success good_response + assert_equal 'Approved', good_response.message + + # returns a failed response when an invalid tx_source parameter is sent + assert bad_response = @gateway.void(auth.authorization, @options.merge(tx_source: @invalid_tx_source)) + assert_failure bad_response end def test_status @@ -90,21 +163,61 @@ def test_invalid_login assert_equal 'Required field vpc_Merchant was not present in the request', response.message end + def test_transcript_scrubbing + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @credit_card, @options) + end + transcript = @gateway.scrub(transcript) + + assert_scrubbed(@credit_card.number, transcript) + assert_scrubbed(@credit_card.verification_value, transcript) + assert_scrubbed(@gateway.options[:password], transcript) + end + + def test_transcript_scrubbing_of_advanced_password + gateway = MigsGateway.new(fixtures(:migs).merge(advanced_login: 'advlogin', advanced_password: 'advpass')) + purchase = gateway.purchase(@amount, @credit_card, @options) + + transcript = capture_transcript(@gateway) do + gateway.refund(@amount, purchase.authorization, @options) + end + transcript = @gateway.scrub(transcript) + + assert_scrubbed(@credit_card.number, transcript) + assert_scrubbed(@credit_card.verification_value, transcript) + assert_scrubbed(@gateway.options[:advanced_password], transcript) + end + + def test_transcript_scrubbing_of_3ds_cavv_and_xid_values + opts = @options.merge(@three_ds_options) + + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @credit_card, opts) + end + transcript = @gateway.scrub(transcript) + + assert_scrubbed(@credit_card.number, transcript) + assert_scrubbed(@credit_card.verification_value, transcript) + assert_scrubbed(opts[:VerToken], transcript) + assert_scrubbed(opts['3DSXID'], transcript) + end + private - include ActiveMerchant::PostsData - def assert_response_contains(text, url) + def assert_response_match(regexp, url) response = https_response(url) - assert response.body.include?(text) + assert_match regexp, response.body end def https_response(url, cookie = nil) - headers = cookie ? {'Cookie' => cookie} : {} - response = raw_ssl_request(:get, url, nil, headers) - if response.is_a?(Net::HTTPRedirection) - new_cookie = [cookie, response['Set-Cookie']].compact.join(';') - response = https_response(response['Location'], new_cookie) + retry_exceptions do + headers = cookie ? {'Cookie' => cookie} : {} + response = raw_ssl_request(:get, url, nil, headers) + if response.is_a?(Net::HTTPRedirection) + new_cookie = [cookie, response['Set-Cookie']].compact.join(';') + response = https_response(response['Location'], new_cookie) + end + response end - response end end diff --git a/test/remote/gateways/remote_modern_payments_cim_test.rb b/test/remote/gateways/remote_modern_payments_cim_test.rb index efd3bec9c6f..8c310d0fe17 100644 --- a/test/remote/gateways/remote_modern_payments_cim_test.rb +++ b/test/remote/gateways/remote_modern_payments_cim_test.rb @@ -1,47 +1,46 @@ require 'test_helper' class RemoteModernPaymentsCimTest < Test::Unit::TestCase - def setup @gateway = ModernPaymentsCimGateway.new(fixtures(:modern_payments)) - + @amount = 100 @credit_card = credit_card('4111111111111111') @declined_card = credit_card('4000000000000000') - - @options = { + + @options = { :billing_address => address, :customer => 'JIMSMITH2000' } end - + def test_successful_create_customer response = @gateway.create_customer(@options) assert_success response - assert !response.params["create_customer_result"].blank? + assert !response.params['create_customer_result'].blank? end - + def test_successful_modify_customer_credit_card customer = @gateway.create_customer(@options) assert_success customer - - customer_id = customer.params["create_customer_result"] - + + customer_id = customer.params['create_customer_result'] + credit_card = @gateway.modify_customer_credit_card(customer_id, @credit_card) assert_success credit_card - assert !credit_card.params["modify_customer_credit_card_result"].blank? + assert !credit_card.params['modify_customer_credit_card_result'].blank? end - + def test_succsessful_authorize_credit_card_payment customer = @gateway.create_customer(@options) assert_success customer - - customer_id = customer.params["create_customer_result"] - + + customer_id = customer.params['create_customer_result'] + credit_card = @gateway.modify_customer_credit_card(customer_id, @credit_card) assert_success credit_card - + payment = @gateway.authorize_credit_card_payment(customer_id, @amount) assert_success payment end diff --git a/test/remote/gateways/remote_modern_payments_test.rb b/test/remote/gateways/remote_modern_payments_test.rb index 0f13927ecdf..e0fff6eeb95 100644 --- a/test/remote/gateways/remote_modern_payments_test.rb +++ b/test/remote/gateways/remote_modern_payments_test.rb @@ -4,54 +4,42 @@ class RemoteModernPaymentTest < Test::Unit::TestCase def setup @gateway = ModernPaymentsGateway.new(fixtures(:modern_payments)) - + @amount = 100 @credit_card = credit_card('4111111111111111') @declined_card = credit_card('4000000000000000') - - @options = { + + @options = { :order_id => '1', :billing_address => address, :description => 'Store Purchase' } - end - + def test_successful_purchase assert response = @gateway.purchase(@amount, @credit_card, @options) - + # Test mode seems to not return "approved = true" assert_failure response - assert_match /RESPONSECODE=A/, response.params["auth_string"] + assert_match %r{RESPONSECODE=A}, response.params['auth_string'] assert_equal ModernPaymentsCimGateway::FAILURE_MESSAGE, response.message end def test_unsuccessful_purchase assert response = @gateway.purchase(@amount, @declined_card, @options) assert_failure response - assert_match /RESPONSECODE=D/, response.params["auth_string"] + assert_match %r{RESPONSECODE=D}, response.params['auth_string'] assert_equal ModernPaymentsCimGateway::FAILURE_MESSAGE, response.message end - def test_invalid_login - gateway = ModernPaymentsGateway.new( - :login => '5000', - :password => 'password' - ) - assert response = gateway.purchase(@amount, @credit_card, @options) - assert_failure response - assert_equal ModernPaymentsCimGateway::FAILURE_MESSAGE, response.message - end - def test_invalid_login gateway = ModernPaymentsGateway.new( :login => '', :password => '' ) - + assert_raises(ActiveMerchant::ResponseError) do gateway.purchase(@amount, @credit_card, @options) end end - end diff --git a/test/remote/gateways/remote_monei_test.rb b/test/remote/gateways/remote_monei_test.rb new file mode 100755 index 00000000000..984187d4dee --- /dev/null +++ b/test/remote/gateways/remote_monei_test.rb @@ -0,0 +1,166 @@ +require 'test_helper' + +class RemoteMoneiTest < Test::Unit::TestCase + def setup + @gateway = MoneiGateway.new( + fixtures(:monei) + ) + + @amount = 100 + @credit_card = credit_card('4000100011112224') + @declined_card = credit_card('5453010000059675') + + @options = { + order_id: '1', + billing_address: address, + description: 'Store Purchase' + } + end + + def test_successful_purchase + response = @gateway.purchase(@amount, @credit_card, @options) + + assert_success response + assert_equal 'Request successfully processed in \'Merchant in Connector Test Mode\'', response.message + end + + def test_successful_purchase_with_3ds + options = @options.merge!({ + three_d_secure: { + eci: '05', + cavv: 'AAACAgSRBklmQCFgMpEGAAAAAAA=', + xid: 'CAACCVVUlwCXUyhQNlSXAAAAAAA=' + } + }) + response = @gateway.purchase(@amount, @credit_card, options) + + assert_success response + assert_equal 'Request successfully processed in \'Merchant in Connector Test Mode\'', response.message + end + + def test_failed_purchase + response = @gateway.purchase(@amount, @declined_card, @options) + assert_failure response + assert_equal 'invalid cc number/brand combination', response.message + end + + def test_failed_purchase_with_3ds + options = @options.merge!({ + three_d_secure: { + eci: '05', + cavv: 'INVALID_Verification_ID', + xid: 'CAACCVVUlwCXUyhQNlSXAAAAAAA=' + } + }) + response = @gateway.purchase(@amount, @credit_card, options) + assert_failure response + assert_equal 'Invalid 3DSecure Verification_ID. Must have Base64 encoding a Length of 28 digits', response.message + end + + def test_successful_authorize_and_capture + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + assert capture = @gateway.capture(@amount, auth.authorization) + assert_success capture + end + + def test_failed_authorize + response = @gateway.authorize(@amount, @declined_card, @options) + assert_failure response + end + + def test_partial_capture + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + assert capture = @gateway.capture(@amount-1, auth.authorization) + assert_success capture + end + + def test_multi_partial_capture + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + assert capture = @gateway.capture(@amount-1, auth.authorization) + assert_success capture + + assert capture = @gateway.capture(@amount-1, auth.authorization) + assert_failure capture + end + + def test_failed_capture + response = @gateway.capture(nil, '') + assert_failure response + end + + def test_successful_refund + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + + assert refund = @gateway.refund(@amount, purchase.authorization) + assert_success refund + end + + def test_partial_refund + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + + assert refund = @gateway.refund(@amount-1, purchase.authorization) + assert_success refund + end + + def test_multi_partial_refund + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + + assert refund = @gateway.refund(@amount-1, purchase.authorization) + assert_success refund + + assert refund = @gateway.refund(@amount-1, purchase.authorization) + assert_failure refund + end + + def test_failed_refund + response = @gateway.refund(nil, '') + assert_failure response + end + + def test_successful_void + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + assert void = @gateway.void(auth.authorization) + assert_success void + end + + def test_failed_void + response = @gateway.void('') + assert_failure response + end + + def test_successful_verify + response = @gateway.verify(@credit_card, @options) + assert_success response + assert_equal 'Request successfully processed in \'Merchant in Connector Test Mode\'', response.message + end + + def test_failed_verify + response = @gateway.verify(@declined_card, @options) + assert_failure response + + assert_equal 'invalid cc number/brand combination', response.message + end + + def test_invalid_login + gateway = MoneiGateway.new( + :sender_id => 'mother', + :channel_id => 'there is no other', + :login => 'like mother', + :pwd => 'so treat Her right' + ) + response = gateway.purchase(@amount, @credit_card, @options) + assert_failure response + end + +end diff --git a/test/remote/gateways/remote_moneris_test.rb b/test/remote/gateways/remote_moneris_test.rb index b41ed619018..161f17f6807 100644 --- a/test/remote/gateways/remote_moneris_test.rb +++ b/test/remote/gateways/remote_moneris_test.rb @@ -8,8 +8,9 @@ def setup @amount = 100 @credit_card = credit_card('4242424242424242') @options = { - :order_id => generate_unique_id, - :customer => generate_unique_id + :order_id => generate_unique_id, + :customer => generate_unique_id, + :billing_address => address } end @@ -20,6 +21,87 @@ def test_successful_purchase assert_false response.authorization.blank? end + def test_successful_first_purchase_with_credential_on_file + gateway = MonerisGateway.new(fixtures(:moneris)) + assert response = gateway.purchase(@amount, @credit_card, @options.merge(issuer_id: '', payment_indicator: 'C', payment_information: '0')) + assert_success response + assert_equal 'Approved', response.message + assert_false response.authorization.blank? + assert_not_empty response.params['issuer_id'] + end + + def test_successful_purchase_with_cof_enabled_and_no_cof_options + gateway = MonerisGateway.new(fixtures(:moneris)) + assert response = gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal 'Approved', response.message + assert_false response.authorization.blank? + end + + def test_successful_non_cof_purchase_with_cof_enabled_and_only_issuer_id_sent + gateway = MonerisGateway.new(fixtures(:moneris)) + assert response = gateway.purchase(@amount, @credit_card, @options.merge(issuer_id: '')) + assert_success response + assert_equal 'Approved', response.message + assert_false response.authorization.blank? + assert_nil response.params['issuer_id'] + end + + def test_successful_subsequent_purchase_with_credential_on_file + gateway = MonerisGateway.new(fixtures(:moneris)) + assert response = gateway.authorize( + @amount, + @credit_card, + @options.merge( + issuer_id: '', + payment_indicator: 'C', + payment_information: '0' + ) + ) + assert_success response + assert_equal 'Approved', response.message + assert_false response.authorization.blank? + + assert response2 = gateway.purchase( + @amount, + @credit_card, + @options.merge( + order_id: response.authorization, + issuer_id: response.params['issuer_id'], + payment_indicator: 'U', + payment_information: '2' + ) + ) + assert_success response2 + assert_equal 'Approved', response2.message + assert_false response2.authorization.blank? + end + + def test_successful_purchase_with_network_tokenization + @credit_card = network_tokenization_credit_card( + '4242424242424242', + payment_cryptogram: 'BwABB4JRdgAAAAAAiFF2AAAAAAA=', + verification_value: nil + ) + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal 'Approved', response.message + assert_false response.authorization.blank? + end + + def test_successful_purchase_with_network_tokenization_apple_pay_source + @credit_card = network_tokenization_credit_card( + '4242424242424242', + payment_cryptogram: 'BwABB4JRdgAAAAAAiFF2AAAAAAA=', + verification_value: nil, + source: :apple_pay + ) + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal 'Approved', response.message + assert_false response.authorization.blank? + end + def test_successful_authorization response = @gateway.authorize(@amount, @credit_card, @options) assert_success response @@ -40,6 +122,18 @@ def test_successful_authorization_and_capture assert_success response end + def test_successful_authorization_and_capture_and_void + response = @gateway.authorize(@amount, @credit_card, @options) + assert_success response + assert response.authorization + + response = @gateway.capture(@amount, response.authorization) + assert_success response + + void = @gateway.void(response.authorization, :purchasecorrection => true) + assert_success void + end + def test_successful_authorization_and_void response = @gateway.authorize(@amount, @credit_card, @options) assert_success response @@ -49,6 +143,34 @@ def test_successful_authorization_and_void assert_success void end + def test_successful_authorization_with_network_tokenization + @credit_card = network_tokenization_credit_card( + '4242424242424242', + payment_cryptogram: 'BwABB4JRdgAAAAAAiFF2AAAAAAA=', + verification_value: nil + ) + assert response = @gateway.authorize(@amount, @credit_card, @options) + assert_success response + assert_equal 'Approved', response.message + assert_false response.authorization.blank? + end + + def test_successful_purchase_and_void + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + + void = @gateway.void(purchase.authorization, :purchasecorrection => true) + assert_success void + end + + def test_failed_purchase_and_void + purchase = @gateway.purchase(101, @credit_card, @options) + assert_failure purchase + + void = @gateway.void(purchase.authorization) + assert_failure void + end + def test_successful_purchase_and_refund purchase = @gateway.purchase(@amount, @credit_card, @options) assert_success purchase @@ -63,35 +185,41 @@ def test_failed_purchase_from_error assert_equal 'Declined', response.message end + def test_successful_verify + response = @gateway.verify(@credit_card, @options) + assert_success response + assert_match 'Approved', response.message + end + def test_successful_store assert response = @gateway.store(@credit_card) assert_success response - assert_equal "Successfully registered cc details", response.message - assert response.params["data_key"].present? - @data_key = response.params["data_key"] + assert_equal 'Successfully registered cc details', response.message + assert response.params['data_key'].present? + @data_key = response.params['data_key'] end def test_successful_unstore test_successful_store assert response = @gateway.unstore(@data_key) assert_success response - assert_equal "Successfully deleted cc details", response.message - assert response.params["data_key"].present? + assert_equal 'Successfully deleted cc details', response.message + assert response.params['data_key'].present? end def test_update test_successful_store assert response = @gateway.update(@data_key, @credit_card) assert_success response - assert_equal "Successfully updated cc details", response.message - assert response.params["data_key"].present? + assert_equal 'Successfully updated cc details', response.message + assert response.params['data_key'].present? end def test_successful_purchase_with_vault test_successful_store assert response = @gateway.purchase(@amount, @data_key, @options) assert_success response - assert_equal "Approved", response.message + assert_equal 'Approved', response.message assert_false response.authorization.blank? end @@ -124,14 +252,54 @@ def test_cvv_match_when_enabled gateway = MonerisGateway.new(fixtures(:moneris).merge(cvv_enabled: true)) assert response = gateway.purchase(1039, @credit_card, @options) assert_success response - assert_equal({'code' => 'M', 'message' => 'Match'}, response.cvv_result) + assert_equal({'code' => 'M', 'message' => 'CVV matches'}, response.cvv_result) end - def test_cvv_no_match_when_enabled - gateway = MonerisGateway.new(fixtures(:moneris).merge(cvv_enabled: true)) - assert response = gateway.purchase(1053, @credit_card, @options) + def test_avs_result_valid_when_enabled + gateway = MonerisGateway.new(fixtures(:moneris).merge(avs_enabled: true)) + + assert response = gateway.purchase(1010, @credit_card, @options) + assert_success response + assert_equal(response.avs_result, { + 'code' => 'A', + 'message' => 'Street address matches, but postal code does not match.', + 'street_match' => 'Y', + 'postal_match' => 'N' + }) + end + + def test_avs_result_nil_when_address_absent + gateway = MonerisGateway.new(fixtures(:moneris).merge(avs_enabled: true)) + + assert response = gateway.purchase(1010, @credit_card, @options.tap { |x| x.delete(:billing_address) }) assert_success response - assert_equal({'code' => 'N', 'message' => 'No Match'}, response.cvv_result) + assert_equal(response.avs_result, { + 'code' => nil, + 'message' => nil, + 'street_match' => nil, + 'postal_match' => nil + }) end + def test_avs_result_nil_when_efraud_disabled + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal(response.avs_result, { + 'code' => nil, + 'message' => nil, + 'street_match' => nil, + 'postal_match' => nil + }) + end + + def test_purchase_scrubbing + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @credit_card, @options) + end + transcript = @gateway.scrub(transcript) + + assert_scrubbed(credit_card.number, transcript) + assert_scrubbed(credit_card.verification_value, transcript) + assert_scrubbed(@gateway.options[:password], transcript) + end end diff --git a/test/remote/gateways/remote_moneris_us_test.rb b/test/remote/gateways/remote_moneris_us_test.rb index 6479141661f..dd5c30614f3 100644 --- a/test/remote/gateways/remote_moneris_us_test.rb +++ b/test/remote/gateways/remote_moneris_us_test.rb @@ -7,26 +7,59 @@ def setup @gateway = MonerisUsGateway.new(fixtures(:moneris_us)) @amount = 100 @credit_card = credit_card('4242424242424242') - @options = { + @options = { :order_id => generate_unique_id, :billing_address => address, :description => 'Store Purchase' } + @check = check({ + routing_number: '011000015', + account_number: '1234455', + number: 123 + }) end - + def test_successful_purchase assert response = @gateway.purchase(@amount, @credit_card, @options) assert_success response assert_equal 'Approved', response.message assert_false response.authorization.blank? end - + + def test_successful_echeck_purchase + response = @gateway.purchase(@amount, @check, @options) + assert_success response + assert response.test? + assert_equal 'Registered', response.message + assert response.authorization + end + + def test_failed_echeck_purchase + response = @gateway.purchase(105, check(routing_number: 5), @options) + assert_failure response + assert response.test? + assert_equal 'Unspecified error', response.message + end + def test_successful_authorization response = @gateway.authorize(@amount, @credit_card, @options) assert_success response assert_false response.authorization.blank? end + def test_successful_verify + response = @gateway.verify(@credit_card, @options) + assert_success response + assert_equal 'Approved', response.message + assert response.authorization + end + + def test_failed_verify + response = @gateway.verify(credit_card(nil), @options) + assert_failure response + assert_match %r{Invalid pan parameter}, response.message + end + def test_failed_authorization response = @gateway.authorize(105, @credit_card, @options) assert_failure response @@ -40,40 +73,40 @@ def test_successful_authorization_and_capture response = @gateway.capture(@amount, response.authorization) assert_success response end - + def test_successful_authorization_and_void response = @gateway.authorize(@amount, @credit_card, @options) assert_success response assert response.authorization - + # Moneris cannot void a preauthorization # You must capture the auth transaction with an amount of $0.00 void = @gateway.capture(0, response.authorization) assert_success void end - + def test_successful_purchase_and_void purchase = @gateway.purchase(@amount, @credit_card, @options) assert_success purchase - + void = @gateway.void(purchase.authorization) assert_success void end - + def test_failed_purchase_and_void purchase = @gateway.purchase(101, @credit_card, @options) assert_failure purchase - + void = @gateway.void(purchase.authorization) assert_failure void end - - def test_successful_purchase_and_credit + + def test_successful_purchase_and_refund purchase = @gateway.purchase(@amount, @credit_card, @options) assert_success purchase - - credit = @gateway.credit(@amount, purchase.authorization) - assert_success credit + + refund = @gateway.refund(@amount, purchase.authorization) + assert_success refund end def test_failed_purchase_from_error @@ -81,4 +114,148 @@ def test_failed_purchase_from_error assert_failure response assert_equal 'Declined', response.message end + + def test_successful_store + assert response = @gateway.store(@credit_card) + assert_success response + assert_equal 'Successfully registered cc details', response.message + assert response.params['data_key'].present? + @data_key = response.params['data_key'] + end + + def test_successful_echeck_store + assert response = @gateway.store(@check) + assert_success response + assert_equal 'Successfully registered ach details', response.message + assert response.params['data_key'].present? + @data_key_echeck = response.params['data_key'] + end + + def test_successful_unstore + test_successful_store + assert response = @gateway.unstore(@data_key) + assert_success response + assert_equal 'Successfully deleted cc details', response.message + assert response.params['data_key'].present? + end + + def test_successful_echeck_unstore + test_successful_echeck_store + assert response = @gateway.unstore(@data_key_echeck) + assert_success response + assert_equal 'Successfully deleted ach details', response.message + assert response.params['data_key'].present? + end + + def test_update + test_successful_store + assert response = @gateway.update(@data_key, @credit_card) + assert_success response + assert_equal 'Successfully updated cc details', response.message + assert response.params['data_key'].present? + end + + def test_echeck_update + test_successful_echeck_store + assert response = @gateway.update(@data_key_echeck, @check) + assert_success response + assert_equal 'Successfully updated ach details', response.message + assert response.params['data_key'].present? + end + + def test_successful_purchase_with_vault + test_successful_store + assert response = @gateway.purchase(@amount, @data_key, @options) + assert_success response + assert_equal 'Approved', response.message + assert_false response.authorization.blank? + end + + def test_successful_authorization_with_vault + test_successful_store + assert response = @gateway.authorize(@amount, @data_key, @options) + assert_success response + assert_false response.authorization.blank? + end + + def test_failed_authorization_with_vault + test_successful_store + response = @gateway.authorize(105, @data_key, @options) + assert_failure response + end + + def test_cvv_match_when_not_enabled + assert response = @gateway.purchase(1039, @credit_card, @options) + assert_success response + assert_equal({'code' => nil, 'message' => nil}, response.cvv_result) + end + + def test_cvv_no_match_when_not_enabled + assert response = @gateway.purchase(1053, @credit_card, @options) + assert_success response + assert_equal({'code' => nil, 'message' => nil}, response.cvv_result) + end + + def test_cvv_match_when_enabled + gateway = MonerisGateway.new(fixtures(:moneris).merge(cvv_enabled: true)) + assert response = gateway.purchase(1039, @credit_card, @options) + assert_success response + assert_equal({'code' => 'M', 'message' => 'CVV matches'}, response.cvv_result) + end + + def test_cvv_no_match_when_enabled + gateway = MonerisGateway.new(fixtures(:moneris).merge(cvv_enabled: true)) + assert response = gateway.purchase(1053, @credit_card, @options) + assert_success response + assert_equal({'code' => 'N', 'message' => 'CVV does not match'}, response.cvv_result) + end + + def test_avs_result_valid_when_enabled + gateway = MonerisGateway.new(fixtures(:moneris).merge(avs_enabled: true)) + + assert response = gateway.purchase(1010, @credit_card, @options) + assert_success response + assert_equal(response.avs_result, { + 'code' => 'A', + 'message' => 'Street address matches, but postal code does not match.', + 'street_match' => 'Y', + 'postal_match' => 'N' + }) + end + + def test_avs_result_nil_when_address_absent + gateway = MonerisGateway.new(fixtures(:moneris).merge(avs_enabled: true)) + + assert response = gateway.purchase(1010, @credit_card, @options.tap { |x| x.delete(:billing_address) }) + assert_success response + assert_equal(response.avs_result, { + 'code' => nil, + 'message' => nil, + 'street_match' => nil, + 'postal_match' => nil + }) + end + + def test_avs_result_nil_when_efraud_disabled + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal(response.avs_result, { + 'code' => nil, + 'message' => nil, + 'street_match' => nil, + 'postal_match' => nil + }) + end + + def test_transcript_scrubbing + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @credit_card, @options) + end + transcript = @gateway.scrub(transcript) + + assert_scrubbed(@credit_card.number, transcript) + assert_scrubbed(@credit_card.verification_value, transcript) + assert_scrubbed(@gateway.options[:password], transcript) + end + end diff --git a/test/remote/gateways/remote_money_movers_test.rb b/test/remote/gateways/remote_money_movers_test.rb new file mode 100644 index 00000000000..64165cb50d8 --- /dev/null +++ b/test/remote/gateways/remote_money_movers_test.rb @@ -0,0 +1,82 @@ +require 'test_helper' + +class RemoteMoneyMoversTest < Test::Unit::TestCase + def setup + @gateway = MoneyMoversGateway.new(fixtures(:money_movers)) + + @amount = 100 + @declined_amount = 99 + + @credit_card = credit_card('4111111111111111') + + @options = { + :order_id => generate_unique_id, + :billing_address => address, + :description => 'Active Merchant Remote Test Purchase' + } + end + + def test_successful_purchase + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal 'Transaction Approved', response.message + end + + def test_unsuccessful_purchase + assert response = @gateway.purchase(@declined_amount, @credit_card, @options) + assert_failure response + assert_equal 'Transaction Declined', response.message + end + + def test_successful_authorization + assert response = @gateway.authorize(@amount, @credit_card, @options) + assert_success response + assert_equal 'Transaction Approved', response.message + end + + def test_failed_capture + assert response = @gateway.capture(@amount, '') + assert_failure response + assert_equal 'Error in transaction data or system error', response.message + end + + def test_purchase_and_refund + assert auth = @gateway.purchase(@amount, @credit_card, @options) + assert_success auth + assert_equal 'Transaction Approved', auth.message + assert auth.authorization + assert capture = @gateway.refund(@amount, auth.authorization) + assert_equal 'Transaction Approved', capture.message + assert_success capture + end + + def test_authorize_and_void + assert auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + assert_equal 'Transaction Approved', auth.message + assert auth.authorization + assert capture = @gateway.void(auth.authorization) + assert_equal 'Transaction Approved', capture.message + assert_success capture + end + + def test_authorize_and_capture + assert auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + assert_equal 'Transaction Approved', auth.message + assert auth.authorization + assert capture = @gateway.capture(@amount, auth.authorization) + assert_equal 'Transaction Approved', capture.message + assert_success capture + end + + def test_invalid_login + gateway = MoneyMoversGateway.new( + :login => '', + :password => '' + ) + assert response = gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_equal 'Error in transaction data or system error', response.message + end +end diff --git a/test/remote/gateways/remote_mundipagg_test.rb b/test/remote/gateways/remote_mundipagg_test.rb new file mode 100644 index 00000000000..e1c16f2cc67 --- /dev/null +++ b/test/remote/gateways/remote_mundipagg_test.rb @@ -0,0 +1,297 @@ +require 'test_helper' + +class RemoteMundipaggTest < Test::Unit::TestCase + def setup + @gateway = MundipaggGateway.new(fixtures(:mundipagg)) + + @amount = 100 + @credit_card = credit_card('4000100011112224') + @declined_card = credit_card('4000300011112220') + @sodexo_voucher = credit_card('6060704495764400', brand: 'sodexo') + + # Mundipagg only allows certain card numbers for success and failure scenarios. + # As such, we cannot use card numbers with BINs belonging to VR or Alelo. + # See https://docs.mundipagg.com/docs/simulador-de-voucher. + @vr_voucher = credit_card('4000000000000010', brand: 'vr') + @alelo_voucher = credit_card('4000000000000010', brand: 'alelo') + @declined_alelo_voucher = credit_card('4000000000000028', brand: 'alelo') + + @options = { + gateway_affiliation_id: fixtures(:mundipagg)[:gateway_affiliation_id], + billing_address: address({neighborhood: 'Sesame Street'}), + description: 'Store Purchase' + } + end + + def test_successful_purchase + test_successful_purchase_with(@credit_card) + end + + def test_successful_purchase_with_alelo_card + test_successful_purchase_with(@alelo_voucher) + end + + def test_successful_purchase_no_address + @options.delete(:billing_address) + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal 'Simulator|Transação de simulação autorizada com sucesso', response.message + end + + def test_successful_purchase_with_more_options + options = @options.update({ + order_id: '1', + ip: '127.0.0.1', + email: 'joe@example.com', + shipping_address: address + }) + + response = @gateway.purchase(@amount, @credit_card, options) + assert_success response + end + + def test_successful_purchase_with_sodexo_voucher + @options.update(holder_document: '93095135270') + response = @gateway.purchase(@amount, @sodexo_voucher, @options) + assert_success response + assert_equal 'Simulator|Transação de simulação autorizada com sucesso', response.message + end + + def test_successful_purchase_with_vr_voucher + @options.update(holder_document: '93095135270') + response = @gateway.purchase(@amount, @vr_voucher, @options) + assert_success response + assert_equal 'Simulator|Transação de simulação autorizada com sucesso', response.message + end + + def test_failed_purchase + test_failed_purchase_with(@declined_card) + end + + def test_failed_purchase_with_alelo_card + test_failed_purchase_with(@declined_alelo_voucher) + end + + def test_successful_authorize_and_capture + test_successful_authorize_and_capture_with(@credit_card) + end + + def test_successful_authorize_and_capture_with_alelo_card + test_successful_authorize_and_capture_with(@alelo_voucher) + end + + def test_failed_authorize + test_failed_authorize_with(@declined_card) + end + + def test_failed_authorize_with_alelo_card + test_failed_authorize_with(@declined_alelo_voucher) + end + + def test_partial_capture + test_partial_capture_with(@credit_card) + end + + def test_partial_capture_with_alelo_card + test_partial_capture_with(@alelo_voucher) + end + + def test_failed_capture + response = @gateway.capture(@amount, 'abc') + assert_failure response + assert_equal 'The requested resource does not exist; Charge not found.', response.message + end + + def test_successful_refund + test_successful_refund_with(@credit_card) + end + + def test_successful_refund_with_alelo_card + test_successful_refund_with(@alelo_voucher) + end + + def test_partial_refund + test_partial_refund_with(@credit_card) + end + + def test_partial_refund_with_alelo_card + test_partial_refund_with(@alelo_voucher) + end + + def test_failed_refund + response = @gateway.refund(@amount, 'abc') + assert_failure response + assert_equal 'The requested resource does not exist; Charge not found.', response.message + end + + def test_successful_void + test_successful_void_with(@credit_card) + end + + def test_successful_void_with_alelo_card + test_successful_void_with(@alelo_voucher) + end + + def test_successful_void_with_sodexo_voucher + @options.update(holder_document: '93095135270') + auth = @gateway.purchase(@amount, @sodexo_voucher, @options) + assert_success auth + + assert void = @gateway.void(auth.authorization) + assert_success void + end + + def test_successful_void_with_vr_voucher + @options.update(holder_document: '93095135270') + auth = @gateway.purchase(@amount, @vr_voucher, @options) + assert_success auth + + assert void = @gateway.void(auth.authorization) + assert_success void + end + + def test_successful_refund_with_sodexo_voucher + @options.update(holder_document: '93095135270') + auth = @gateway.purchase(@amount, @sodexo_voucher, @options) + assert_success auth + + assert void = @gateway.refund(1, auth.authorization) + assert_success void + end + + def test_successful_refund_with_vr_voucher + @options.update(holder_document: '93095135270') + auth = @gateway.purchase(@amount, @vr_voucher, @options) + assert_success auth + + assert void = @gateway.refund(1, auth.authorization) + assert_success void + end + + def test_failed_void + response = @gateway.void('abc') + assert_failure response + assert_equal 'The requested resource does not exist; Charge not found.', response.message + end + + def test_successful_verify + test_successful_verify_with(@credit_card) + end + + def test_successful_verify_with_alelo_card + test_successful_verify_with(@alelo_voucher) + end + + def test_successful_store_and_purchase + test_successful_store_and_purchase_with(@credit_card) + end + + def test_successful_store_and_purchase_with_alelo_card + test_successful_store_and_purchase_with(@alelo_voucher) + end + + def test_invalid_login + gateway = MundipaggGateway.new(api_key: '') + + response = gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_match %r{Invalid API key; Authorization has been denied for this request.}, response.message + end + + def test_gateway_id_fallback + gateway = MundipaggGateway.new(api_key: fixtures(:mundipagg)[:api_key], gateway_id: fixtures(:mundipagg)[:gateway_id]) + options = { + billing_address: address({neighborhood: 'Sesame Street'}), + description: 'Store Purchase' + } + response = gateway.purchase(@amount, @credit_card, options) + assert_success response + end + + def test_transcript_scrubbing + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @credit_card, @options) + end + transcript = @gateway.scrub(transcript) + + assert_scrubbed(@credit_card.number, transcript) + assert_scrubbed(@credit_card.verification_value, transcript) + assert_scrubbed(@gateway.options[:api_key], transcript) + end + + private + + def test_successful_purchase_with(card) + response = @gateway.purchase(@amount, card, @options) + assert_success response + assert_equal 'Simulator|Transação de simulação autorizada com sucesso', response.message + end + + def test_failed_purchase_with(card) + response = @gateway.purchase(105200, card, @options) + assert_failure response + assert_equal 'Simulator|Transação de simulada negada por falta de crédito, utilizado para realizar simulação de autorização parcial.', response.message + end + + def test_successful_authorize_and_capture_with(card) + auth = @gateway.authorize(@amount, card, @options) + assert_success auth + + assert capture = @gateway.capture(@amount, auth.authorization) + assert_success capture + assert_equal 'Simulator|Transação de simulação capturada com sucesso', capture.message + end + + def test_failed_authorize_with(card) + response = @gateway.authorize(105200, card, @options) + assert_failure response + assert_equal 'Simulator|Transação de simulada negada por falta de crédito, utilizado para realizar simulação de autorização parcial.', response.message + end + + def test_partial_capture_with(card) + auth = @gateway.authorize(@amount, card, @options) + assert_success auth + + assert capture = @gateway.capture(@amount-1, auth.authorization) + assert_success capture + end + + def test_successful_refund_with(card) + purchase = @gateway.purchase(@amount, card, @options) + assert_success purchase + + assert refund = @gateway.refund(@amount, purchase.authorization) + assert_success refund + end + + def test_partial_refund_with(card) + purchase = @gateway.purchase(@amount, card, @options) + assert_success purchase + + assert refund = @gateway.refund(@amount - 1, purchase.authorization) + assert_success refund + end + + def test_successful_void_with(card) + auth = @gateway.authorize(@amount, card, @options) + assert_success auth + + assert void = @gateway.void(auth.authorization) + assert_success void + end + + def test_successful_verify_with(card) + response = @gateway.verify(card, @options) + assert_success response + assert_match %r{Simulator|Transação de simulação autorizada com sucesso}, response.message + end + + def test_successful_store_and_purchase_with(card) + store = @gateway.store(card, @options) + assert_success store + + assert purchase = @gateway.purchase(@amount, store.authorization, @options) + assert_success purchase + assert_equal 'Simulator|Transação de simulação autorizada com sucesso', purchase.message + end +end diff --git a/test/remote/gateways/remote_nab_transact_test.rb b/test/remote/gateways/remote_nab_transact_test.rb index 67d1f1cd3f0..55289ec9ff0 100644 --- a/test/remote/gateways/remote_nab_transact_test.rb +++ b/test/remote/gateways/remote_nab_transact_test.rb @@ -4,7 +4,7 @@ class RemoteNabTransactTest < Test::Unit::TestCase def setup @gateway = NabTransactGateway.new(fixtures(:nab_transact)) - @card_acceptor_gateway = NabTransactGateway.new(fixtures(:nab_transact_card_acceptor)) + @privileged_gateway = NabTransactGateway.new(fixtures(:nab_transact_privileged)) @amount = 200 @credit_card = credit_card('4444333322221111') @@ -31,16 +31,16 @@ def test_successful_purchase end def test_unsuccessful_purchase_insufficient_funds - #Any total not ending in 00/08/11/16 - failing_amount = 151 #Specifically tests 'Insufficient Funds' + # Any total not ending in 00/08/11/16 + failing_amount = 151 # Specifically tests 'Insufficient Funds' assert response = @gateway.purchase(failing_amount, @credit_card, @options) assert_failure response assert_equal 'Insufficient Funds', response.message end def test_unsuccessful_purchase_do_not_honour - #Any total not ending in 00/08/11/16 - failing_amount = 105 #Specifically tests 'do not honour' + # Any total not ending in 00/08/11/16 + failing_amount = 105 # Specifically tests 'do not honour' assert response = @gateway.purchase(failing_amount, @credit_card, @options) assert_failure response assert_equal 'Do Not Honour', response.message @@ -73,7 +73,7 @@ def test_successful_purchase_with_card_acceptor assert_failure response assert_equal 'Permission denied', response.message - assert response = @card_acceptor_gateway.purchase(@amount, @credit_card, options) + assert response = @privileged_gateway.purchase(@amount, @credit_card, options) assert_success response assert_equal 'Approved', response.message end @@ -138,13 +138,13 @@ def test_authorize_and_capture_with_card_acceptor assert_failure response assert_equal 'Permission denied', response.message - assert response = @card_acceptor_gateway.authorize(@amount, @credit_card, options) + assert response = @privileged_gateway.authorize(@amount, @credit_card, options) assert_success response assert_equal 'Approved', response.message authorization = response.authorization - assert response = @card_acceptor_gateway.capture(@amount, authorization) + assert response = @privileged_gateway.capture(@amount, authorization) assert_success response assert_equal 'Approved', response.message end @@ -159,13 +159,25 @@ def test_successful_refund assert_equal 'Approved', response.message end + # You need to speak to NAB Transact to have this feature enabled on + # your account otherwise you will receive a "Permission denied" error + def test_credit + assert response = @gateway.credit(@amount, @credit_card, {:order_id => '1'}) + assert_failure response + assert_equal 'Permission denied', response.message + + assert response = @privileged_gateway.credit(@amount, @credit_card, {:order_id => '1'}) + assert_success response + assert_equal 'Approved', response.message + end + def test_failed_refund assert response = @gateway.purchase(@amount, @credit_card, @options) assert_success response authorization = response.authorization assert response = @gateway.refund(@amount+1, authorization) assert_failure response - assert_equal 'Only $2.0 available for refund', response.message + assert_equal 'Only 2.00 AUD available for refund', response.message end def test_invalid_login @@ -179,17 +191,13 @@ def test_invalid_login end def test_successful_store - @gateway.unstore(1234) - - assert response = @gateway.store(@credit_card, {:billing_id => 1234, :amount => 150}) + assert response = @gateway.store(@credit_card, @options) assert_success response assert_equal 'Successful', response.message end def test_unsuccessful_store - @gateway.unstore(1235) - - assert response = @gateway.store(@declined_card, {:billing_id => 1235, :amount => 150}) + assert response = @gateway.store(@declined_card) assert_failure response assert_equal 'Invalid Credit Card Number', response.message end @@ -197,41 +205,32 @@ def test_unsuccessful_store def test_duplicate_store @gateway.unstore(1236) - assert response = @gateway.store(@credit_card, {:billing_id => 1236, :amount => 150}) + assert response = @gateway.store(@credit_card, {:billing_id => 1236}) assert_success response assert_equal 'Successful', response.message - assert response = @gateway.store(@credit_card, {:billing_id => 1236, :amount => 150}) + assert response = @gateway.store(@credit_card, {:billing_id => 1236}) assert_failure response assert_equal 'Duplicate CRN Found', response.message end def test_unstore - gateway_id = '1234' - @gateway.unstore(gateway_id) - - assert response = @gateway.store(@credit_card, {:billing_id => gateway_id, :amount => 150}) + assert response = @gateway.store(@credit_card) assert_success response assert_equal 'Successful', response.message - assert gateway_id = response.params["crn"] - assert unstore_response = @gateway.unstore(gateway_id) + assert card_id = response.authorization + assert unstore_response = @gateway.unstore(card_id) assert_success unstore_response + assert_equal 'Successful', unstore_response.message end - def test_successful_trigger_purchase - gateway_id = '1234' - trigger_amount = 12000 - @gateway.unstore(gateway_id) - - assert response = @gateway.store(@credit_card, {:billing_id => gateway_id, :amount => 150}) + def test_successful_purchase_using_stored_card + assert response = @gateway.store(@credit_card) assert_success response assert_equal 'Successful', response.message - purchase_response = @gateway.purchase(trigger_amount, gateway_id) - - assert gateway_id = purchase_response.params["crn"] - assert trigger_amount = purchase_response.params["amount"] + purchase_response = @gateway.purchase(12000, response.authorization) assert_success purchase_response assert_equal 'Approved', purchase_response.message end @@ -247,9 +246,19 @@ def test_failure_trigger_purchase purchase_response = @gateway.purchase(trigger_amount, gateway_id) - assert gateway_id = purchase_response.params["crn"] + assert purchase_response.params['crn'] assert_failure purchase_response assert_equal 'Invalid Amount', purchase_response.message end + def test_transcript_scrubbing + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @credit_card, @options) + end + clean_transcript = @gateway.scrub(transcript) + + assert_scrubbed(@credit_card.number, clean_transcript) + assert_scrubbed(@credit_card.verification_value.to_s, clean_transcript) + end + end diff --git a/test/remote/gateways/remote_ncr_secure_pay_test.rb b/test/remote/gateways/remote_ncr_secure_pay_test.rb new file mode 100644 index 00000000000..239063f06e8 --- /dev/null +++ b/test/remote/gateways/remote_ncr_secure_pay_test.rb @@ -0,0 +1,122 @@ +require 'test_helper' + +class RemoteMonetraTest < Test::Unit::TestCase + def setup + @gateway = NcrSecurePayGateway.new(fixtures(:ncr_secure_pay)) + + @amount = 100 + @credit_card = credit_card('4111111111111111') + @bad_credit_card = credit_card('1234567890123456') + @options = { + billing_address: address, + description: 'Store Purchase' + } + end + + def test_successful_purchase + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal 'APPROVED', response.message + end + + def test_failed_purchase + response = @gateway.purchase(601, @credit_card, @options) + assert_failure response + assert_equal 'DECLINE', response.message + end + + def test_successful_authorize_and_capture + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + assert capture = @gateway.capture(@amount, auth.authorization) + assert_success capture + assert_equal 'SUCCESS', capture.message + end + + def test_failed_authorize + response = @gateway.authorize(601, @credit_card, @options) + assert_failure response + assert_equal 'DECLINE', response.message + end + + def test_partial_capture + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + assert capture = @gateway.capture(@amount-1, auth.authorization) + assert_success capture + end + + def test_failed_capture + response = @gateway.capture(@amount, 'invalid') + assert_failure response + assert_equal 'This transaction requires an apprcode ttid or unique ptrannum', response.message + end + + # Unable to test this case since have to wait for original tx to settle + # for refund + def test_successful_refund + end + + # Unable to test this case since have to wait for original tx to settle + # for refund + def test_partial_refund + end + + def test_failed_refund + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + + assert refund = @gateway.refund(@amount, purchase.authorization) + assert_failure refund + assert_equal 'USE VOID OR REVERSAL TO REFUND UNSETTLED TRANSACTIONS', refund.message + end + + def test_successful_void + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + + assert void = @gateway.void(purchase.authorization) + assert_success void + assert_equal 'SUCCESS', void.message + end + + def test_failed_void + response = @gateway.void('invalid') + assert_failure response + assert_equal 'Must specify ttid or ptrannum', response.message + end + + def test_successful_verify + response = @gateway.verify(@credit_card, @options) + assert_success response + assert_match 'APPROVED', response.message + end + + def test_failed_verify + response = @gateway.verify(@bad_credit_card, @options) + assert_failure response + assert_match 'UNSUPPORTED CARD TYPE', response.message + end + + def test_invalid_login + gateway = NcrSecurePayGateway.new(username: '', password: '') + + response = gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_match 'AUTHENTICATION FAILED', response.message + end + + def test_transcript_scrubbing + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @credit_card, @options) + end + transcript = @gateway.scrub(transcript) + + assert_scrubbed(@credit_card.number, transcript) + assert_scrubbed(@credit_card.verification_value, transcript) + assert_scrubbed(@gateway.options[:password], transcript) + end + +end diff --git a/test/remote/gateways/remote_net_registry_test.rb b/test/remote/gateways/remote_net_registry_test.rb index 6525b4fdd3a..aeadc59b83d 100644 --- a/test/remote/gateways/remote_net_registry_test.rb +++ b/test/remote/gateways/remote_net_registry_test.rb @@ -27,7 +27,7 @@ def test_successful_purchase_and_credit assert_success response assert_match(/\A\d{16}\z/, response.authorization) - assert_deprecation_warning(Gateway::CREDIT_DEPRECATION_MESSAGE, @gateway) do + assert_deprecation_warning(Gateway::CREDIT_DEPRECATION_MESSAGE) do response = @gateway.credit(@amount, response.authorization) assert_equal 'approved', response.params['status'] assert_success response @@ -58,8 +58,8 @@ def test_successful_authorization_and_capture assert_match(/\A\d{6}\z/, response.authorization) response = @gateway.capture(@amount, - response.authorization, - :credit_card => @valid_creditcard) + response.authorization, + :credit_card => @valid_creditcard) assert_success response assert_equal 'approved', response.params['status'] end diff --git a/test/remote/gateways/remote_netaxept_test.rb b/test/remote/gateways/remote_netaxept_test.rb index a7ea2c36715..0ae763f5876 100644 --- a/test/remote/gateways/remote_netaxept_test.rb +++ b/test/remote/gateways/remote_netaxept_test.rb @@ -20,24 +20,24 @@ def test_successful_purchase end def test_failed_purchase - assert response = @gateway.purchase(@amount, @declined_card, @options) - assert_failure response - assert_match(/failure/i, response.message) + assert response = @gateway.purchase(@amount, @declined_card, @options) + assert_failure response + assert_match(/failure/i, response.message) end def test_authorize_and_capture - assert auth = @gateway.authorize(@amount, @credit_card, @options) - assert_success auth - assert_equal 'OK', auth.message - assert auth.authorization - assert capture = @gateway.capture(@amount, auth.authorization) - assert_success capture + assert auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + assert_equal 'OK', auth.message + assert auth.authorization + assert capture = @gateway.capture(@amount, auth.authorization) + assert_success capture end def test_failed_capture - assert response = @gateway.capture(@amount, '') - assert_failure response - assert_equal "Unable to find transaction", response.message + assert response = @gateway.capture(@amount, '') + assert_failure response + assert_equal 'Unable to find transaction', response.message end def test_successful_refund @@ -49,12 +49,12 @@ def test_successful_refund end def test_failed_refund - assert response = @gateway.purchase(@amount, @credit_card, @options) - assert_success response + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response - response = @gateway.refund(@amount+100, response.authorization) - assert_failure response - assert_equal "Unable to credit more than captured amount", response.message + response = @gateway.refund(@amount+100, response.authorization) + assert_failure response + assert_equal 'Unable to credit more than captured amount', response.message end def test_successful_void @@ -66,12 +66,18 @@ def test_successful_void end def test_failed_void - assert response = @gateway.purchase(@amount, @credit_card, @options) - assert_success response + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + + response = @gateway.void(response.authorization) + assert_failure response + assert_equal 'Unable to annul, wrong state', response.message + end - response = @gateway.void(response.authorization) - assert_failure response - assert_equal "Unable to annul, wrong state", response.message + def test_error_in_transaction_setup + assert response = @gateway.purchase(@amount, @credit_card, @options.merge(:currency => 'BOGG')) + assert_failure response + assert_match(/currency code/, response.message) end def test_successful_amex_purchase @@ -88,53 +94,33 @@ def test_successful_master_purchase assert_equal 'OK', response.message end - def test_error_in_transaction_setup - assert response = @gateway.purchase(@amount, @credit_card, @options.merge(:currency => 'BOGG')) - assert_failure response - assert_match(/currency code/, response.message) - end - - def test_successful_amex_purchase - credit_card = credit_card('378282246310005', :brand => 'american_express') - assert response = @gateway.purchase(@amount, credit_card, @options) - assert_success response - assert_equal 'OK', response.message - end - - def test_successful_master_purchase - credit_card = credit_card('5413000000000000', :brand => 'master') - assert response = @gateway.purchase(@amount, credit_card, @options) - assert_success response - assert_equal 'OK', response.message - end - def test_error_in_payment_details - assert response = @gateway.purchase(@amount, credit_card(''), @options) - assert_failure response - assert_equal "Cardnumber:Required", response.message + assert response = @gateway.purchase(@amount, credit_card(''), @options) + assert_failure response + assert_equal 'Cardnumber:Required', response.message end def test_amount_is_not_required_again_when_capturing_authorization - assert response = @gateway.authorize(@amount, @credit_card, @options) - assert_success response + assert response = @gateway.authorize(@amount, @credit_card, @options) + assert_success response - assert response = @gateway.capture(nil, response.authorization) - assert_equal "OK", response.message + assert response = @gateway.capture(nil, response.authorization) + assert_equal 'OK', response.message end def test_query_fails - query = @gateway.send(:query_transaction, "bogus", @options) + query = @gateway.send(:query_transaction, 'bogus', @options) assert_failure query assert_match(/unable to find/i, query.message) end def test_invalid_login - gateway = NetaxeptGateway.new( - :login => '', - :password => '' - ) - assert response = gateway.purchase(@amount, @credit_card, @options) - assert_failure response - assert_match(/Unable to authenticate merchant/, response.message) + gateway = NetaxeptGateway.new( + :login => '', + :password => '' + ) + assert response = gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_match(/Unable to authenticate merchant/, response.message) end end diff --git a/test/remote/gateways/remote_netbanx_test.rb b/test/remote/gateways/remote_netbanx_test.rb new file mode 100644 index 00000000000..19b4faddd5c --- /dev/null +++ b/test/remote/gateways/remote_netbanx_test.rb @@ -0,0 +1,205 @@ +require 'test_helper' + +class RemoteNetbanxTest < Test::Unit::TestCase + def setup + @gateway = NetbanxGateway.new(fixtures(:netbanx)) + @amount = 100 + @credit_card = credit_card('4530910000012345') + @declined_amount = 11 + @options = { + billing_address: address, + description: 'Store Purchase', + currency: 'CAD' + } + end + + def test_successful_purchase + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal 'OK', response.message + assert_equal response.authorization, response.params['id'] + end + + def test_successful_purchase_with_more_options + options = { + order_id: SecureRandom.uuid, + ip: '127.0.0.1', + billing_address: address, + email: 'joe@example.com' + } + + response = @gateway.purchase(@amount, @credit_card, options) + assert_equal 'OK', response.message + assert_equal response.authorization, response.params['id'] + end + + def test_failed_purchase + response = @gateway.purchase(@declined_amount, @credit_card, @options) + assert_failure response + assert_equal 'The card has been declined due to insufficient funds.', response.message + end + + def test_successful_authorize + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + end + + def test_failed_authorize + response = @gateway.authorize(@declined_amount, @credit_card, @options) + assert_failure response + assert_equal 'The card has been declined due to insufficient funds.', response.message + end + + def test_successful_authorize_and_capture + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + assert capture = @gateway.capture(@amount, auth.authorization, @options) + assert_success capture + assert_equal 'OK', capture.message + end + + def test_partial_capture + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + assert capture = @gateway.capture(@amount-1, auth.authorization, @options) + assert_success capture + end + + def test_failed_capture + response = @gateway.capture(@amount, SecureRandom.uuid) + assert_failure response + assert_equal 'The authorization ID included in this settlement request could not be found.', response.message + end + + # def test_successful_refund + # # Unfortunately when testing a refund, you need to wait until the transaction + # # if batch settled by the test system, this can take up to 2h. + # # This is the reason why these tests are commented out. You can run them + # # manually once you have batched/completed transactions. + # # + # # Otherwise you will get an error like: + # # You tried a credit transaction for a settlement that has not been batched, + # # so there is no balance available to be credited. A settlement is typically + # # in a pending state until midnight of the day that it is requested, at + # # which point it is batched. You cannot credit that settlement until it has + # # been batched. Verify the credit card for which you are attempting the + # # credit, and retry the transaction. Otherwise, wait until the settlement + # # has been batched and retry the transaction. + + # auth = @gateway.authorize(@amount, @credit_card, @options) + # assert_success auth + + # assert capture = @gateway.capture(@amount, auth.authorization) + # assert_success capture + + # # replace this with the transaction that you can verify via the back-office + # # or API that it's in `completed` state. And use this in the refund + # # call below + # # authorization = "fd5d6776-29b7-4108-b98a-7a4603db9ff0" + + # assert refund = @gateway.refund(@amount, authorization) + # assert_success refund + # assert_equal 'OK', refund.message + # end + + # def test_partial_refund + # # Read comment in `test_successful_refund` method. + # auth = @gateway.authorize(@amount, @credit_card, @options) + # assert_success auth + + # assert capture = @gateway.capture(@amount, auth.authorization) + # assert_success capture + + # # replace this with the transaction that you can verify via the back-office + # # or API that it's in `completed` state. And use this in the refund + # # call below + # # authorization = "REPLACE-ME" + + # assert refund = @gateway.refund(@amount-1, capture.authorization) + # assert_success refund + # assert_equal 'OK', refund.message + # end + + def test_failed_refund + # Read comment in `test_successful_refund` method. + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + assert capture = @gateway.capture(@amount, auth.authorization, @options) + assert_success capture + + # the following shall fail if you run it immediately after the capture + # as noted in the comment from `test_successful_refund` + assert refund = @gateway.refund(@amount, capture.authorization) + assert_failure refund + assert_equal 'The settlement you are attempting to refund has not been batched yet. There are no settled funds available to refund.', refund.message + end + + def test_successful_void + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + assert void = @gateway.void(auth.authorization, @options) + assert_success void + assert_equal 'OK', void.message + end + + def test_failed_void + response = @gateway.void(SecureRandom.uuid) + assert_failure response + assert_equal 'The confirmation number included in this request could not be found.', response.message + end + + def test_invalid_login + gateway = NetbanxGateway.new(api_key: 'foobar', account_number: '12345') + + response = gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_match %r{Invalid Login}, response.message + end + + def test_transcript_scrubbing + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @credit_card, @options) + end + transcript = @gateway.scrub(transcript) + + assert_scrubbed(@credit_card.number, transcript) + assert_scrubbed(Base64.strict_encode64(@gateway.options[:api_key]).strip, transcript) + end + + def test_successful_store + merchant_customer_id = SecureRandom.hex + assert response = @gateway.store(@credit_card, locale: 'en_GB', merchant_customer_id: merchant_customer_id, email: 'email@example.com') + assert_success response + assert_equal merchant_customer_id, response.params['merchantCustomerId'] + first_card = response.params['cards'].first + assert_equal @credit_card.last_digits, first_card['lastDigits'] + end + + def test_successful_unstore + merchant_customer_id = SecureRandom.hex + assert response = @gateway.store(@credit_card, locale: 'en_GB', merchant_customer_id: merchant_customer_id, email: 'email@example.com') + assert_success response + assert_equal merchant_customer_id, response.params['merchantCustomerId'] + first_card = response.params['cards'].first + assert_equal @credit_card.last_digits, first_card['lastDigits'] + identification = "#{response.params['id']}|#{first_card['id']}" + assert unstore_card = @gateway.unstore(identification) + assert_success unstore_card + assert unstore_profile = @gateway.unstore(response.params['id']) + assert_success unstore_profile + end + + def test_successful_purchase_using_stored_card + merchant_customer_id = SecureRandom.hex + assert store = @gateway.store(@credit_card, @options.merge({locale: 'en_GB', merchant_customer_id: merchant_customer_id, email: 'email@example.com'})) + assert_success store + + assert response = @gateway.purchase(@amount, store.authorization.split('|').last) + assert_success response + assert_equal 'OK', response.message + end +end diff --git a/test/remote/gateways/remote_netbilling_test.rb b/test/remote/gateways/remote_netbilling_test.rb index 9eaeadd651b..7004e213627 100644 --- a/test/remote/gateways/remote_netbilling_test.rb +++ b/test/remote/gateways/remote_netbilling_test.rb @@ -16,7 +16,8 @@ def setup @options = { :billing_address => @address, - :description => 'Internet purchase' + :description => 'Internet purchase', + :order_id => 987654321 } @amount = 100 @@ -30,6 +31,39 @@ def test_successful_purchase assert response.test? end + def test_successful_repeat_purchase + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_false response.authorization.blank? + assert_equal NetbillingGateway::SUCCESS_MESSAGE, response.message + assert response.test? + + transaction_id = response.authorization + assert response = @gateway.purchase(@amount, transaction_id, @options) + assert_false response.authorization.blank? + assert_equal NetbillingGateway::SUCCESS_MESSAGE, response.message + assert response.test? + end + + def test_unsuccessful_repeat_purchase + assert response = @gateway.purchase(@amount, '1111', @options) + assert_failure response + assert_match(/no record found/i, response.message) + end + + def test_successful_store + assert response = @gateway.store(@credit_card, @options) + assert_success response + assert_false response.authorization.blank? + assert_equal NetbillingGateway::SUCCESS_MESSAGE, response.message + end + + def test_unsuccessful_store + assert response = @gateway.store(credit_card('123'), @options) + assert_failure response + assert_match(/invalid credit card number/i, response.message) + end + def test_unsuccessful_purchase @credit_card.year = '2006' assert response = @gateway.purchase(@amount, @credit_card, @options) @@ -91,4 +125,14 @@ def test_successful_void assert_failure void_response assert_match(/error/i, void_response.message) end + + def test_transcript_scrubbing + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @credit_card, @options) + end + clean_transcript = @gateway.scrub(transcript) + + assert_scrubbed(@credit_card.number, clean_transcript) + assert_scrubbed(@credit_card.verification_value.to_s, clean_transcript) + end end diff --git a/test/remote/gateways/remote_netpay_test.rb b/test/remote/gateways/remote_netpay_test.rb index 754d11d0086..cb87c5ca3f7 100644 --- a/test/remote/gateways/remote_netpay_test.rb +++ b/test/remote/gateways/remote_netpay_test.rb @@ -60,7 +60,7 @@ def test_unsuccessful_authorize opts[:mode] = 'D' assert response = @gateway.authorize(@amount, @declined_card, opts) assert_failure response - assert_match /Declinada/, response.message + assert_match %r{Declinada}, response.message end def test_successful_authorize_and_capture diff --git a/test/remote/gateways/remote_transnational_test.rb b/test/remote/gateways/remote_network_merchants_test.rb similarity index 61% rename from test/remote/gateways/remote_transnational_test.rb rename to test/remote/gateways/remote_network_merchants_test.rb index 57d058a87a9..290a0cad4c6 100644 --- a/test/remote/gateways/remote_transnational_test.rb +++ b/test/remote/gateways/remote_network_merchants_test.rb @@ -1,12 +1,13 @@ require 'test_helper' -class RemoteTransnationalTest < Test::Unit::TestCase +class RemoteNetworkMerchantsTest < Test::Unit::TestCase def setup - @gateway = TransnationalGateway.new(fixtures(:transnational)) + @gateway = NetworkMerchantsGateway.new(fixtures(:network_merchants)) @amount = 100 @decline_amount = 1 @credit_card = credit_card('4111111111111111') + @credit_card_with_track_data = credit_card_with_track_data('4111111111111111') @check = check @options = { @@ -22,6 +23,18 @@ def test_successful_purchase assert_equal 'SUCCESS', response.message end + def test_successful_purchase_with_track_data + assert response = @gateway.purchase(@amount, @credit_card_with_track_data, @options) + assert_success response + assert_equal 'SUCCESS', response.message + end + + def test_successful_purchase_with_non_default_currency + @options.update(currency: 'EUR') + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + end + def test_successful_check_purchase assert response = @gateway.purchase(@amount, @check, @options) assert_success response @@ -34,6 +47,12 @@ def test_unsuccessful_purchase assert_equal 'DECLINE', response.message end + def test_unsuccessful_purchase_with_track_data + assert response = @gateway.purchase(@decline_amount, @credit_card_with_track_data, @options) + assert_failure response + assert_equal 'DECLINE', response.message + end + def test_purchase_and_store assert response = @gateway.purchase(@amount, @credit_card, @options.merge(:store => true)) assert_success response @@ -64,7 +83,7 @@ def test_void assert response = @gateway.void(purchase.authorization) assert_success response - assert_equal "Transaction Void Successful", response.message + assert_equal 'Transaction Void Successful', response.message end def test_refund @@ -74,7 +93,18 @@ def test_refund assert response = @gateway.refund(50, purchase.authorization) assert_success response - assert_equal "SUCCESS", response.message + assert_equal 'SUCCESS', response.message + assert response.authorization + end + + def test_refund_with_track_data + assert purchase = @gateway.purchase(@amount, @credit_card_with_track_data, @options) + assert_success purchase + assert purchase.authorization + + assert response = @gateway.refund(50, purchase.authorization) + assert_success response + assert_equal 'SUCCESS', response.message assert response.authorization end @@ -93,11 +123,11 @@ def test_store_check end def test_store_failure - @credit_card.number = "123" - assert store = @gateway.store(@creditcard, @options) + @credit_card.number = '123' + assert store = @gateway.store(@credit_card, @options) assert_failure store - assert store.message.include?('Billing Information missing') - assert_equal '', store.params['customer_vault_id'] + assert store.message.include?('Invalid Credit Card Number') + assert store.params['customer_vault_id'].blank? assert_nil store.authorization end @@ -108,7 +138,7 @@ def test_unstore assert unstore = @gateway.unstore(store.params['customer_vault_id']) assert_success unstore - assert_equal "Customer Deleted", unstore.message + assert_equal 'Customer Deleted', unstore.message end def test_purchase_on_stored_card @@ -118,11 +148,11 @@ def test_purchase_on_stored_card assert purchase = @gateway.purchase(@amount, store.params['customer_vault_id'], @options) assert_success purchase - assert_equal "SUCCESS", purchase.message + assert_equal 'SUCCESS', purchase.message end def test_invalid_login - gateway = TransnationalGateway.new( + gateway = NetworkMerchantsGateway.new( :login => '', :password => '' ) @@ -130,4 +160,23 @@ def test_invalid_login assert_failure response assert_equal 'Invalid Username', response.message end + + def test_successful_purchase_without_state + @options[:billing_address] = { + :name => 'Jim Smith', + :address1 => 'Gullhauggrenda 30', + :address2 => 'Apt 1', + :company => 'Widgets Inc', + :city => 'Baerums Verk', + :state => nil, + :zip => '1354', + :country => 'NO', + :phone => '(555)555-5555', + :fax => '(555)555-6666' + } + + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal 'SUCCESS', response.message + end end diff --git a/test/remote/gateways/remote_nmi_test.rb b/test/remote/gateways/remote_nmi_test.rb index e96b9b79fe9..4c4784b216a 100644 --- a/test/remote/gateways/remote_nmi_test.rb +++ b/test/remote/gateways/remote_nmi_test.rb @@ -3,45 +3,135 @@ class RemoteNmiTest < Test::Unit::TestCase def setup @gateway = NmiGateway.new(fixtures(:nmi)) - @amount = 100 - @credit_card = credit_card('4000100011112224') + @amount = Random.rand(100...1000) + @credit_card = credit_card('4111111111111111', verification_value: 917) + @check = check( + :routing_number => '123123123', + :account_number => '123123123' + ) + @apple_pay_card = network_tokenization_credit_card('4111111111111111', + :payment_cryptogram => 'EHuWW9PiBkWvqE5juRwDzAUFBAk=', + :month => '01', + :year => '2024', + :source => :apple_pay, + :eci => '5', + :transaction_id => '123456789' + ) @options = { :order_id => generate_unique_id, :billing_address => address, :description => 'Store purchase' } + @level3_options = { + tax: 5.25, shipping: 10.51, ponumber: 1002 + } + end + + def test_invalid_login + @gateway = NmiGateway.new(login: 'invalid', password: 'no') + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_equal 'Authentication Failed', response.message end def test_successful_purchase + options = @options.merge(@level3_options) + + assert response = @gateway.purchase(@amount, @credit_card, options) + assert_success response + assert response.test? + assert_equal 'Succeeded', response.message + assert response.authorization + end + + def test_successful_purchase_sans_cvv + @credit_card.verification_value = nil assert response = @gateway.purchase(@amount, @credit_card, @options) assert_success response assert response.test? - assert_equal 'This transaction has been approved', response.message + assert_equal 'Succeeded', response.message assert response.authorization end - def test_forced_test_mode_purchase - gateway = NmiGateway.new(fixtures(:nmi).update(:test => true)) - assert response = gateway.purchase(@amount, @credit_card, @options) + def test_failed_purchase + assert response = @gateway.purchase(99, @credit_card, @options) + assert_failure response + assert response.test? + assert_equal 'DECLINE', response.message + end + + def test_successful_purchase_with_echeck + assert response = @gateway.purchase(@amount, @check, @options) + assert_success response + assert response.test? + assert_equal 'Succeeded', response.message + assert response.authorization + end + + def test_failed_purchase_with_echeck + assert response = @gateway.purchase(99, @check, @options) + assert_failure response + assert response.test? + assert_equal 'FAILED', response.message + end + + def test_successful_purchase_with_apple_pay_card + assert @gateway.supports_network_tokenization? + assert response = @gateway.purchase(@amount, @apple_pay_card, @options) + assert_success response + assert response.test? + assert_equal 'Succeeded', response.message + assert response.authorization + end + + def test_failed_purchase_with_apple_pay_card + assert response = @gateway.purchase(99, @apple_pay_card, @options) + assert_failure response + assert response.test? + assert_equal 'DECLINE', response.message + end + + def test_successful_purchase_with_additional_options + options = @options.merge({ + customer_id: '234', + vendor_id: '456', + recurring: true, + }) + assert response = @gateway.purchase(@amount, @credit_card, options) assert_success response assert response.test? + assert_equal 'Succeeded', response.message assert response.authorization end def test_successful_authorization - assert response = @gateway.authorize(@amount, @credit_card, @options) + options = @options.merge(@level3_options) + + assert response = @gateway.authorize(@amount, @credit_card, options) assert_success response - assert_equal 'This transaction has been approved', response.message + assert_equal 'Succeeded', response.message assert response.authorization end + def test_failed_authorization + assert response = @gateway.authorize(99, @credit_card, @options) + assert_failure response + assert response.test? + assert_equal 'DECLINE', response.message + end + def test_authorization_and_capture assert authorization = @gateway.authorize(@amount, @credit_card, @options) assert_success authorization assert capture = @gateway.capture(@amount, authorization.authorization) assert_success capture - assert_equal 'This transaction has been approved', capture.message + assert_equal 'Succeeded', capture.message + end + + def test_failed_capture + assert capture = @gateway.capture(@amount, 'badauth') + assert_failure capture end def test_authorization_and_void @@ -50,28 +140,265 @@ def test_authorization_and_void assert void = @gateway.void(authorization.authorization) assert_success void - assert_equal 'This transaction has been approved', void.message + assert_equal 'Succeeded', void.message + end + + def test_failed_void + assert void = @gateway.void('badauth') + assert_failure void end - def test_refund + def test_successful_void_with_echeck + assert response = @gateway.purchase(@amount, @check, @options) + assert_success response + + assert response = @gateway.void(response.authorization) + assert_success response + assert_equal 'Succeeded', response.message + end + + def test_successful_refund assert response = @gateway.purchase(@amount, @credit_card, @options) assert_success response - assert response = @gateway.refund(@amount, response.authorization, :card_number => @credit_card.number) + assert response = @gateway.refund(@amount, response.authorization) assert_success response - assert_equal 'This transaction has been approved', response.message + assert_equal 'Succeeded', response.message end - def test_bad_login - gateway = NmiGateway.new( - :login => 'X', - :password => 'Y' - ) + def test_failed_refund + assert response = @gateway.refund(@amount, 'badauth') + assert_failure response + end + + def test_successful_refund_with_echeck + assert response = @gateway.purchase(@amount, @check, @options) + assert_success response + + assert response = @gateway.refund(@amount, response.authorization) + assert_success response + assert_equal 'Succeeded', response.message + end + + def test_successful_credit + options = @options.merge(@level3_options) + + response = @gateway.credit(@amount, @credit_card, options) + assert_success response + assert_equal 'Succeeded', response.message + end + + def test_failed_credit + card = credit_card(year: 2010) + response = @gateway.credit(@amount, card, @options) + assert_failure response + end + + def test_successful_verify + options = @options.merge(@level3_options) + + response = @gateway.verify(@credit_card, options) + assert_success response + assert_match 'Succeeded', response.message + end - assert response = gateway.purchase(@amount, @credit_card) - assert_equal Response, response.class - assert_match(/Authentication Failed/, response.message) - assert_equal false, response.success? + def test_failed_verify + card = credit_card(year: 2010) + response = @gateway.verify(card, @options) + assert_failure response + assert_match 'Invalid Credit Card', response.message end + def test_successful_store + response = @gateway.store(@credit_card, @options) + assert_success response + assert_equal 'Succeeded', response.message + assert response.authorization.include?(response.params['customer_vault_id']) + end + + def test_failed_store + card = credit_card(year: 2010) + response = @gateway.store(card, @options) + assert_failure response + end + + def test_successful_store_with_echeck + response = @gateway.store(@check, @options) + assert_success response + assert_equal 'Succeeded', response.message + assert response.authorization.include?(response.params['customer_vault_id']) + end + + def test_successful_store_and_purchase + vault_id = @gateway.store(@credit_card, @options).authorization + purchase = @gateway.purchase(@amount, vault_id, @options) + assert_success purchase + assert_equal 'Succeeded', purchase.message + end + + def test_successful_store_and_auth + vault_id = @gateway.store(@credit_card, @options).authorization + auth = @gateway.authorize(@amount, vault_id, @options) + assert_success auth + assert_equal 'Succeeded', auth.message + end + + def test_successful_store_and_credit + vault_id = @gateway.store(@credit_card, @options).authorization + credit = @gateway.credit(@amount, vault_id, @options) + assert_success credit + assert_equal 'Succeeded', credit.message + end + + def test_merchant_defined_fields + (1..20).each { |e| @options["merchant_defined_field_#{e}".to_sym] = "value #{e}" } + assert_success @gateway.purchase(@amount, @credit_card, @options) + end + + def test_verify_credentials + assert @gateway.verify_credentials + + gateway = NmiGateway.new(login: 'unknown', password: 'unknown') + assert !gateway.verify_credentials + gateway = NmiGateway.new(login: fixtures(:nmi)[:login], password: 'unknown') + assert !gateway.verify_credentials + end + + def test_purchase_using_stored_credential_recurring_cit + initial_options = stored_credential_options(:cardholder, :recurring, :initial) + assert purchase = @gateway.purchase(@amount, @credit_card, initial_options) + assert_success purchase + assert network_transaction_id = purchase.params['transactionid'] + + used_options = stored_credential_options(:recurring, :cardholder, id: network_transaction_id) + assert purchase = @gateway.purchase(@amount, @credit_card, used_options) + assert_success purchase + end + + def test_purchase_using_stored_credential_recurring_mit + initial_options = stored_credential_options(:merchant, :recurring, :initial) + assert purchase = @gateway.purchase(@amount, @credit_card, initial_options) + assert_success purchase + assert network_transaction_id = purchase.params['transactionid'] + + used_options = stored_credential_options(:merchant, :recurring, id: network_transaction_id) + assert purchase = @gateway.purchase(@amount, @credit_card, used_options) + assert_success purchase + end + + def test_purchase_using_stored_credential_installment_cit + initial_options = stored_credential_options(:cardholder, :installment, :initial) + assert purchase = @gateway.purchase(@amount, @credit_card, initial_options) + assert_success purchase + assert network_transaction_id = purchase.params['transactionid'] + + used_options = stored_credential_options(:cardholder, :installment, id: network_transaction_id) + assert purchase = @gateway.purchase(@amount, @credit_card, used_options) + assert_success purchase + end + + def test_purchase_using_stored_credential_installment_mit + initial_options = stored_credential_options(:merchant, :installment, :initial) + assert purchase = @gateway.purchase(@amount, @credit_card, initial_options) + assert_success purchase + assert network_transaction_id = purchase.params['transactionid'] + + used_options = stored_credential_options(:merchant, :installment, id: network_transaction_id) + assert purchase = @gateway.purchase(@amount, @credit_card, used_options) + assert_success purchase + end + + def test_purchase_using_stored_credential_unscheduled_cit + initial_options = stored_credential_options(:cardholder, :unscheduled, :initial) + assert purchase = @gateway.purchase(@amount, @credit_card, initial_options) + assert_success purchase + assert network_transaction_id = purchase.params['transactionid'] + + used_options = stored_credential_options(:cardholder, :unscheduled, id: network_transaction_id) + assert purchase = @gateway.purchase(@amount, @credit_card, used_options) + assert_success purchase + end + + def test_purchase_using_stored_credential_unscheduled_mit + initial_options = stored_credential_options(:merchant, :unscheduled, :initial) + assert purchase = @gateway.purchase(@amount, @credit_card, initial_options) + assert_success purchase + assert network_transaction_id = purchase.params['transactionid'] + + used_options = stored_credential_options(:merchant, :unscheduled, id: network_transaction_id) + assert purchase = @gateway.purchase(@amount, @credit_card, used_options) + assert_success purchase + end + + def test_authorize_and_capture_with_stored_credential + initial_options = stored_credential_options(:cardholder, :recurring, :initial) + assert authorization = @gateway.authorize(@amount, @credit_card, initial_options) + assert_success authorization + assert network_transaction_id = authorization.params['transactionid'] + + assert capture = @gateway.capture(@amount, authorization.authorization) + assert_success capture + + used_options = stored_credential_options(:cardholder, :recurring, id: network_transaction_id) + assert authorization = @gateway.authorize(@amount, @credit_card, used_options) + assert_success authorization + assert @gateway.capture(@amount, authorization.authorization) + end + + def test_card_transcript_scrubbing + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @credit_card, @options) + end + clean_transcript = @gateway.scrub(transcript) + + assert_scrubbed(@credit_card.number, clean_transcript) + assert_cvv_scrubbed(clean_transcript) + assert_password_scrubbed(clean_transcript) + end + + def test_check_transcript_scrubbing + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @check, @options) + end + clean_transcript = @gateway.scrub(transcript) + + assert_scrubbed(@check.account_number, clean_transcript) + assert_scrubbed(@check.routing_number, clean_transcript) + assert_password_scrubbed(clean_transcript) + end + + def test_network_tokenization_transcript_scrubbing + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @apple_pay_card, @options) + end + clean_transcript = @gateway.scrub(transcript) + + assert_scrubbed(@apple_pay_card.number, clean_transcript) + assert_scrubbed(@apple_pay_card.payment_cryptogram, clean_transcript) + assert_password_scrubbed(clean_transcript) + end + + private + + # "password=password is filtered, but can't be tested via normal + # `assert_scrubbed` b/c of key match" + def assert_password_scrubbed(transcript) + assert_match(/password=\[FILTERED\]/, transcript) + end + + # Because the cvv is a simple three digit number, sometimes there are random + # failures using `assert_scrubbed` because of natural collisions with a + # substring within orderid in transcript; e.g. + # + # Expected the value to be scrubbed out of the transcript. + # </917/> was expected to not match + # <"opening connection to secure.nmi.com:443...\nopened\nstarting SSL for secure.nmi.com:443...\nSSL established\n<- \"POST /api/transact.php HTTP/1.1\\r\\nContent-Type: application/x-www-form-urlencoded;charset=UTF-8\\r\\nConnection: close\\r\\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\\r\\nAccept: */*\\r\\nUser-Agent: Ruby\\r\\nHost: secure.nmi.com\\r\\nContent-Length: 394\\r\\n\\r\\n\"\n<- \"amount=7.96&orderid=9bb4c3bf6fbb26b91796ae9442cb1941&orderdescription=Store+purchase&currency=USD&payment=creditcard&firstname=Longbob&lastname=Longsen&ccnumber=[FILTERED]&cvv=[FILTERED]&ccexp=0920&email=&ipaddress=&customer_id=&company=Widgets+Inc&address1=456+My+Street&address2=Apt+1&city=Ottawa&state=ON&country=CA&zip=K1C2N6&phone=%28555%29555-5555&type=sale&username=demo&password=[FILTERED]\"\n-> \"HTTP/1.1 200 OK\\r\\n\"\n-> \"Date: Wed, 12 Jun 2019 21:10:29 GMT\\r\\n\"\n-> \"Server: Apache\\r\\n\"\n-> \"Content-Length: 169\\r\\n\"\n-> \"Connection: close\\r\\n\"\n-> \"Content-Type: text/html; charset=UTF-8\\r\\n\"\n-> \"\\r\\n\"\nreading 169 bytes...\n-> \"response=1&responsetext=SUCCESS&authcode=123456&transactionid=4743046890&avsresponse=N&cvvresponse=N&orderid=9bb4c3bf6fbb26b91796ae9442cb1941&type=sale&response_code=100\"\nread 169 bytes\nConn close\n">. + def assert_cvv_scrubbed(transcript) + assert_match(/cvv=\[FILTERED\]/, transcript) + end + + def stored_credential_options(*args, id: nil) + @options.merge(order_id: generate_unique_id, + stored_credential: stored_credential(*args, id: id)) + end end diff --git a/test/remote/gateways/remote_ogone_test.rb b/test/remote/gateways/remote_ogone_test.rb index 8003596642c..3d2b878db33 100644 --- a/test/remote/gateways/remote_ogone_test.rb +++ b/test/remote/gateways/remote_ogone_test.rb @@ -1,4 +1,5 @@ # coding: utf-8 + require 'test_helper' class RemoteOgoneTest < Test::Unit::TestCase @@ -7,13 +8,15 @@ def setup @gateway = OgoneGateway.new(fixtures(:ogone)) @amount = 100 @credit_card = credit_card('4000100011112224') + @mastercard = credit_card('5399999999999999', :brand => 'mastercard') @declined_card = credit_card('1111111111111111') @credit_card_d3d = credit_card('4000000000000002', :verification_value => '111') @options = { :order_id => generate_unique_id[0...30], :billing_address => address, :description => 'Store Purchase', - :currency => fixtures(:ogone)[:currency] || 'EUR' + :currency => fixtures(:ogone)[:currency] || 'EUR', + :origin => 'STORE' } end @@ -21,40 +24,40 @@ def test_successful_purchase assert response = @gateway.purchase(@amount, @credit_card, @options) assert_success response assert_equal OgoneGateway::SUCCESS_MESSAGE, response.message - assert_equal '7', response.params['ECI'] - assert_equal @options[:currency], response.params["currency"] assert_equal @options[:order_id], response.order_id end def test_successful_purchase_with_utf8_encoding_1 - assert response = @gateway.purchase(@amount, credit_card('4000100011112224', :first_name => "Rémy", :last_name => "Fröåïør"), @options) + assert response = @gateway.purchase(@amount, credit_card('4000100011112224', :first_name => 'Rémy', :last_name => 'Fröåïør'), @options) assert_success response assert_equal OgoneGateway::SUCCESS_MESSAGE, response.message end def test_successful_purchase_with_utf8_encoding_2 - assert response = @gateway.purchase(@amount, credit_card('4000100011112224', :first_name => "ワタシ", :last_name => "ёжзийклмнопрсуфхцч"), @options) + assert response = @gateway.purchase(@amount, credit_card('4000100011112224', :first_name => 'ワタシ', :last_name => 'ёжзийклмнопрсуфхцч'), @options) assert_success response assert_equal OgoneGateway::SUCCESS_MESSAGE, response.message end + # This test is commented out since it is mutually exclusive with the other signature tests. # NOTE: You have to set the "Hash algorithm" to "SHA-1" in the "Technical information"->"Global security parameters" # section of your account admin on https://secure.ogone.com/ncol/test/frame_ogone.asp before running this test - def test_successful_purchase_with_signature_encryptor_to_sha1 - gateway = OgoneGateway.new(fixtures(:ogone).merge(:signature_encryptor => 'sha1')) - assert response = gateway.purchase(@amount, @credit_card, @options) - assert_success response - assert_equal OgoneGateway::SUCCESS_MESSAGE, response.message - end - + # def test_successful_purchase_with_signature_encryptor_to_sha1 + # gateway = OgoneGateway.new(fixtures(:ogone).merge(:signature_encryptor => 'sha1')) + # assert response = gateway.purchase(@amount, @credit_card, @options) + # assert_success response + # assert_equal OgoneGateway::SUCCESS_MESSAGE, response.message + # end + + # This test is commented out since it is mutually exclusive with the other signature tests. # NOTE: You have to set the "Hash algorithm" to "SHA-256" in the "Technical information"->"Global security parameters" # section of your account admin on https://secure.ogone.com/ncol/test/frame_ogone.asp before running this test - def test_successful_purchase_with_signature_encryptor_to_sha256 - gateway = OgoneGateway.new(fixtures(:ogone).merge(:signature_encryptor => 'sha256')) - assert response = gateway.purchase(@amount, @credit_card, @options) - assert_success response - assert_equal OgoneGateway::SUCCESS_MESSAGE, response.message - end + # def test_successful_purchase_with_signature_encryptor_to_sha256 + # gateway = OgoneGateway.new(fixtures(:ogone).merge(:signature_encryptor => 'sha256')) + # assert response = gateway.purchase(@amount, @credit_card, @options) + # assert_success response + # assert_equal OgoneGateway::SUCCESS_MESSAGE, response.message + # end # NOTE: You have to set the "Hash algorithm" to "SHA-512" in the "Technical information"->"Global security parameters" # section of your account admin on https://secure.ogone.com/ncol/test/frame_ogone.asp before running this test @@ -69,9 +72,9 @@ def test_successful_purchase_with_signature_encryptor_to_sha512 def test_successful_purchase_with_3d_secure assert response = @gateway.purchase(@amount, @credit_card_d3d, @options.merge(:d3d => true)) assert_success response - assert_equal '46', response.params["STATUS"] + assert_equal '46', response.params['STATUS'] assert_equal OgoneGateway::SUCCESS_MESSAGE, response.message - assert response.params["HTML_ANSWER"] + assert response.params['HTML_ANSWER'] end def test_successful_with_non_numeric_order_id @@ -92,7 +95,6 @@ def test_successful_purchase_with_custom_eci assert response = @gateway.purchase(@amount, @credit_card, @options.merge(:eci => 4)) assert_success response assert_equal OgoneGateway::SUCCESS_MESSAGE, response.message - assert_equal '4', response.params['ECI'] end # NOTE: You have to allow USD as a supported currency in the "Account"->"Currencies" @@ -102,7 +104,6 @@ def test_successful_purchase_with_custom_currency_at_the_gateway_level assert response = gateway.purchase(@amount, @credit_card) assert_success response assert_equal OgoneGateway::SUCCESS_MESSAGE, response.message - assert_equal "USD", response.params["currency"] end # NOTE: You have to allow USD as a supported currency in the "Account"->"Currencies" @@ -112,7 +113,6 @@ def test_successful_purchase_with_custom_currency assert response = gateway.purchase(@amount, @credit_card, @options.merge(:currency => 'USD')) assert_success response assert_equal OgoneGateway::SUCCESS_MESSAGE, response.message - assert_equal "USD", response.params["currency"] end def test_unsuccessful_purchase @@ -121,11 +121,16 @@ def test_unsuccessful_purchase assert_equal 'No brand', response.message end + def test_successful_authorize_with_mastercard + assert auth = @gateway.authorize(@amount, @mastercard, @options) + assert_success auth + assert_equal BarclaysEpdqExtraPlusGateway::SUCCESS_MESSAGE, auth.message + end + def test_authorize_and_capture assert auth = @gateway.authorize(@amount, @credit_card, @options) assert_success auth assert_equal OgoneGateway::SUCCESS_MESSAGE, auth.message - assert_equal '7', auth.params['ECI'] assert auth.authorization assert capture = @gateway.capture(@amount, auth.authorization) assert_success capture @@ -135,7 +140,6 @@ def test_authorize_and_capture_with_custom_eci assert auth = @gateway.authorize(@amount, @credit_card, @options.merge(:eci => 4)) assert_success auth assert_equal OgoneGateway::SUCCESS_MESSAGE, auth.message - assert_equal '4', auth.params['ECI'] assert auth.authorization assert capture = @gateway.capture(@amount, auth.authorization, @options) assert_success capture @@ -163,68 +167,89 @@ def test_successful_store assert_success purchase end - def test_successful_store_generated_alias - assert response = @gateway.store(@credit_card) + def test_successful_store_with_store_amount_at_the_gateway_level + gateway = OgoneGateway.new(fixtures(:ogone).merge(:store_amount => 100)) + assert response = gateway.store(@credit_card, :billing_id => 'test_alias') assert_success response - assert purchase = @gateway.purchase(@amount, response.billing_id) + assert purchase = gateway.purchase(@amount, 'test_alias') assert_success purchase end - def test_successful_store - assert response = @gateway.store(@credit_card, :billing_id => 'test_alias') + def test_successful_store_generated_alias + assert response = @gateway.store(@credit_card) assert_success response - assert purchase = @gateway.purchase(@amount, 'test_alias') + assert purchase = @gateway.purchase(@amount, response.billing_id) assert_success purchase end - def test_successful_referenced_credit + def test_successful_refund assert purchase = @gateway.purchase(@amount, @credit_card, @options) assert_success purchase - assert credit = @gateway.credit(@amount, purchase.authorization, @options) - assert_success credit - assert credit.authorization - assert_equal OgoneGateway::SUCCESS_MESSAGE, credit.message + assert refund = @gateway.refund(@amount, purchase.authorization, @options) + assert_success refund + assert refund.authorization + assert_equal OgoneGateway::SUCCESS_MESSAGE, refund.message end - def test_unsuccessful_referenced_credit + def test_unsuccessful_refund assert purchase = @gateway.purchase(@amount, @credit_card, @options) assert_success purchase - assert credit = @gateway.credit(@amount+1, purchase.authorization, @options) # too much refund requested - assert_failure credit - assert credit.authorization - assert_equal 'Overflow in refunds requests', credit.message + assert refund = @gateway.refund(@amount+1, purchase.authorization, @options) # too much refund requested + assert_failure refund + assert refund.authorization + assert_equal 'Overflow in refunds requests', refund.message end - def test_successful_unreferenced_credit + def test_successful_credit assert credit = @gateway.credit(@amount, @credit_card, @options) assert_success credit assert credit.authorization assert_equal OgoneGateway::SUCCESS_MESSAGE, credit.message end + def test_successful_verify + response = @gateway.verify(@credit_card, @options) + assert_success response + assert_equal 'The transaction was successful', response.message + end + + def test_failed_verify + response = @gateway.verify(@declined_card, @options) + assert_failure response + assert_equal 'No brand', response.message + end + def test_reference_transactions # Setting an alias - assert response = @gateway.purchase(@amount, credit_card('4000100011112224'), @options.merge(:billing_id => "awesomeman", :order_id=>Time.now.to_i.to_s+"1")) + assert response = @gateway.purchase(@amount, credit_card('4000100011112224'), @options.merge(:billing_id => 'awesomeman', :order_id=>Time.now.to_i.to_s+'1')) assert_success response - assert_equal '7', response.params['ECI'] # Updating an alias - assert response = @gateway.purchase(@amount, credit_card('4111111111111111'), @options.merge(:billing_id => "awesomeman", :order_id=>Time.now.to_i.to_s+"2")) + assert response = @gateway.purchase(@amount, credit_card('4111111111111111'), @options.merge(:billing_id => 'awesomeman', :order_id=>Time.now.to_i.to_s+'2')) assert_success response - assert_equal '7', response.params['ECI'] # Using an alias (i.e. don't provide the credit card) - assert response = @gateway.purchase(@amount, "awesomeman", @options.merge(:order_id => Time.now.to_i.to_s + "3")) + assert response = @gateway.purchase(@amount, 'awesomeman', @options.merge(:order_id => Time.now.to_i.to_s + '3')) assert_success response - assert_equal '9', response.params['ECI'] end def test_invalid_login gateway = OgoneGateway.new( - :login => '', - :user => '', - :password => '' + login: 'login', + user: 'user', + password: 'password', + signature: 'signature' ) assert response = gateway.purchase(@amount, @credit_card, @options) assert_failure response - assert_equal 'Some of the data entered is incorrect. please retry.', response.message + end + + def test_transcript_scrubbing + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @credit_card, @options) + end + transcript = @gateway.scrub(transcript) + + assert_scrubbed(@credit_card.number, transcript) + assert_scrubbed(@credit_card.verification_value, transcript) + assert_scrubbed(@gateway.options[:password], transcript) end end diff --git a/test/remote/gateways/remote_omise_test.rb b/test/remote/gateways/remote_omise_test.rb new file mode 100644 index 00000000000..4b5be3064b0 --- /dev/null +++ b/test/remote/gateways/remote_omise_test.rb @@ -0,0 +1,103 @@ +require 'test_helper' + +class RemoteOmiseTest < Test::Unit::TestCase + def setup + @gateway = OmiseGateway.new(fixtures(:omise)) + @amount = 8888 + @credit_card = credit_card('4242424242424242') + @declined_card = credit_card('4255555555555555') + @invalid_cvc = credit_card('4111111111160001', {verification_value: ''}) + @options = { + description: 'Active Merchant', + email: 'active.merchant@testing.test', + currency: 'thb' + } + end + + def test_transcript_scrubbing + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @credit_card, @options) + end + transcript = @gateway.scrub(transcript) + + assert_scrubbed(@credit_card.number, transcript) + assert_scrubbed(@credit_card.verification_value, transcript) + assert_scrubbed(@gateway.options[:public_key], transcript) + end + + def test_missing_secret_key + assert_raise ArgumentError do + OmiseGateway.new() + end + end + + def test_successful_purchase + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal 'Success', response.message + assert_equal response.params['amount'], @amount + assert response.params['paid'], 'paid should be true' + assert response.params['authorized'], 'authorized should be true' + end + + def test_failed_purchase + response = @gateway.purchase(@amount, @invalid_cvc) + assert_failure response + assert_equal Gateway::STANDARD_ERROR_CODE[:invalid_cvc], response.error_code + end + + def test_successful_purchase_after_store + response = @gateway.store(@credit_card) + response = @gateway.purchase(@amount, nil, { customer_id: response.authorization }) + assert_success response + assert_equal response.params['amount'], @amount + end + + def test_failed_purchase_with_token + response = @gateway.purchase(@amount, nil, {token_id: 'tokn_invalid_12345'}) + assert_failure response + end + + def test_successful_store + response = @gateway.store(@credit_card, @options) + assert_success response + assert response.params['id'].match(/cust_test_[1-9a-z]+/) + end + + def test_failed_store + response = @gateway.store(@declined_card, @options) + assert_failure response + end + + def test_successful_unstore + response = @gateway.store(@credit_card, @options) + customer = @gateway.unstore(response.params['id']) + assert customer.params['deleted'] + end + + def test_authorize + authorize = @gateway.authorize(@amount, @credit_card, @options) + assert_success authorize + assert_equal authorize.params['amount'], @amount + assert !authorize.params['paid'], 'paid should be false' + assert authorize.params['authorized'], 'authorized should be true' + end + + def test_authorize_and_capture + authorize = @gateway.authorize(@amount, @credit_card, @options) + capture = @gateway.capture(@amount, authorize.authorization, @options) + assert_success capture + assert capture.params['paid'], 'paid should be true' + assert capture.params['authorized'], 'authorized should be true' + end + + def test_successful_refund + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + assert_equal purchase.params['amount'], @amount + response = @gateway.refund(@amount-1000, purchase.authorization) + assert_success response + assert_equal @amount-1000, response.params['amount'] + end + +end diff --git a/test/remote/gateways/remote_openpay_test.rb b/test/remote/gateways/remote_openpay_test.rb new file mode 100644 index 00000000000..079b4d09279 --- /dev/null +++ b/test/remote/gateways/remote_openpay_test.rb @@ -0,0 +1,221 @@ +require 'test_helper' + +class RemoteOpenpayTest < Test::Unit::TestCase + def setup + @gateway = OpenpayGateway.new(fixtures(:openpay)) + + @amount = 100 + @credit_card = credit_card('4111111111111111') + @store_card = credit_card('5105105105105100') + @declined_card = credit_card('4222222222222220') + + @options = { + billing_address: address, + description: 'Store Purchase' + } + end + + def test_successful_purchase + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_nil response.message + end + + def test_successful_purchase_with_email + @options[:email] = '%d@example.org' % Time.now + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_nil response.message + end + + def test_unsuccessful_purchase + assert response = @gateway.purchase(@amount, @declined_card, @options) + assert_failure response + assert_equal 'The card was declined', response.message + end + + def test_successful_refund + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_nil response.message + + assert response = @gateway.refund(@amount, response.authorization, @options) + assert_success response + assert_nil response.message + assert response.params['refund'] + assert_equal 'completed', response.params['status'] + assert_equal 'completed', response.params['refund']['status'] + end + + def test_unsuccessful_refund + assert response = @gateway.refund(@amount, '1', @options) + assert_failure response + assert_not_nil response.message + end + + def test_successful_authorize + assert response = @gateway.authorize(@amount, @credit_card, @options) + assert_success response + assert_nil response.message + end + + def test_successful_authorize_with_email + @options[:email] = '%d@example.org' % Time.now + assert response = @gateway.authorize(@amount, @credit_card, @options) + assert_success response + assert_nil response.message + end + + def test_unsuccessful_authorize + assert response = @gateway.authorize(@amount, @declined_card, @options) + assert_failure response + assert_equal 'The card was declined', response.message + end + + def test_successful_capture + assert response = @gateway.authorize(@amount, @credit_card, @options) + assert_success response + assert_nil response.message + + assert response = @gateway.capture(@amount, response.authorization, @options) + assert_success response + assert_nil response.message + end + + def test_unsuccessful_capture + assert response = @gateway.capture(@amount, '1') + assert_failure response + assert_equal 'The requested resource doesn\'t exist', response.message + end + + def test_successful_void + assert response = @gateway.authorize(@amount, @credit_card, @options) + assert_success response + assert_nil response.message + + assert response = @gateway.void(response.authorization, @options) + assert_success response + assert_equal 'cancelled', response.params['status'] + end + + def test_successful_purchase_with_card_stored + @options[:email] = '%d@example.org' % Time.now + @options[:name] = 'Customer name' + response_store = @gateway.store(@store_card, @options) + assert_success response_store + assert_instance_of MultiResponse, response_store + + customer_stored = response_store.responses[0] + card_stored = response_store.responses[1] + assert response = @gateway.purchase(@amount, card_stored.authorization, @options) + assert_success response + assert_nil response.message + + assert_success @gateway.unstore(customer_stored.authorization, card_stored.authorization) + assert_success @gateway.unstore(customer_stored.authorization) + end + + def test_successful_purchase_with_device_session_id + assert response = @gateway.purchase(@amount, @credit_card, @options.merge(device_session_id: 'weur2ty732yu2y47824u23yu4i')) + assert_success response + end + + def test_successful_purchase_with_card_points + assert response = @gateway.purchase(@amount, @credit_card, @options.merge(use_card_points: 'NONE')) + assert_success response + end + + def test_failed_purchase_with_card_points + assert response = @gateway.purchase(@amount, @credit_card, @options.merge(use_card_points: 'MIXED')) + assert_failure response + assert_match %r{cardNumber not allowed for Card points}, response.message + end + + def test_successful_purchase_with_installments + assert response = @gateway.purchase(@amount * 300, @store_card, @options.merge(payments: '3')) + assert_success response + end + + def test_successful_store + new_email_address = '%d@example.org' % Time.now + assert response = @gateway.store(@credit_card, name: 'Test User', email: new_email_address) + assert_success response + assert_instance_of MultiResponse, response + assert response.authorization + + @options[:customer] = response.authorization + assert second_card = @gateway.store(@store_card, @options) + assert_success second_card + assert_instance_of Response, second_card + assert second_card.authorization + + customer_stored = response.responses[0] + first_card = response.responses[1] + + assert_success @gateway.unstore(customer_stored.authorization, first_card.authorization) + assert_success @gateway.unstore(customer_stored.authorization, second_card.authorization) + assert_success @gateway.unstore(customer_stored.authorization) + end + + def test_successful_verify + response = @gateway.verify(@credit_card, @options) + assert_success response + end + + def test_unsuccessful_verify + response = @gateway.verify(@declined_card, @options) + assert_failure response + assert_match(/The card was declined/, response.message) + end + + def test_invalid_login + gateway = OpenpayGateway.new( + key: '123456789', + merchant_id: 'mwfxtxcoom7dh47pcds1', + production: true + ) + assert response = gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_equal 'The api key or merchant id are invalid', response.message + end + + def test_transcript_scrubbing + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @credit_card, @options) + end + clean_transcript = @gateway.scrub(transcript) + + assert_scrubbed(@credit_card.number, clean_transcript) + assert_scrubbed(@credit_card.verification_value.to_s, clean_transcript) + end + + def test_nil_cvv_scrubbing + @credit_card.verification_value = nil + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @credit_card, @options) + end + clean_transcript = @gateway.scrub(transcript) + + assert_equal clean_transcript.include?('\"cvv2\":[BLANK]'), true + end + + def test_empty_string_cvv_scrubbing + @credit_card.verification_value = '' + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @credit_card, @options) + end + clean_transcript = @gateway.scrub(transcript) + + assert_equal clean_transcript.include?('\"cvv2\":\"[BLANK]'), true + end + + def test_whitespace_string_cvv_scrubbing + @credit_card.verification_value = ' ' + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @credit_card, @options) + end + clean_transcript = @gateway.scrub(transcript) + + assert_equal clean_transcript.include?('\"cvv2\":\"[BLANK]'), true + end +end diff --git a/test/remote/gateways/remote_opp_test.rb b/test/remote/gateways/remote_opp_test.rb new file mode 100644 index 00000000000..652059e87ad --- /dev/null +++ b/test/remote/gateways/remote_opp_test.rb @@ -0,0 +1,216 @@ +require 'test_helper' + +class RemoteOppTest < Test::Unit::TestCase + + def setup + @gateway = OppGateway.new(fixtures(:opp)) + @amount = 100 + + @valid_card = credit_card('4200000000000000', month: 05, year: 2018) + @invalid_card = credit_card('4444444444444444', month: 05, year: 2018) + @amex_card = credit_card('377777777777770 ', month: 05, year: 2018, brand: 'amex', verification_value: '1234') + + request_type = 'complete' # 'minimal' || 'complete' + time = Time.now.to_i + ip = '101.102.103.104' + @complete_request_options = { + order_id: "Order #{time}", + merchant_transaction_id: "active_merchant_test_complete #{time}", + address: address, + description: 'Store Purchase - Books', + # riskWorkflow: true, + # testMode: 'EXTERNAL' # or 'INTERNAL', valid only for test system + + billing_address: { + address1: '123 Test Street', + city: 'Test', + state: 'TE', + zip: 'AB12CD', + country: 'GB', + }, + shipping_address: { + name: 'Muton DeMicelis', + address1: 'My Street On Upiter, Apt 3.14/2.78', + city: 'Munich', + state: 'Bov', + zip: '81675', + country: 'DE', + }, + customer: { + merchant_customer_id: 'your merchant/customer id', + givenName: 'Jane', + surname: 'Jones', + birthDate: '1965-05-01', + phone: '(?!?)555-5555', + mobile: '(?!?)234-23423', + email: 'jane@jones.com', + company_name: 'JJ Ltd.', + identification_doctype: 'PASSPORT', + identification_docid: 'FakeID2342431234123', + ip: ip, + }, + } + + @minimal_request_options = { + order_id: "Order #{time}", + description: 'Store Purchase - Books', + } + + @complete_request_options['customParameters[SHOPPER_test124TestName009]'] = 'customParameters_test' + @complete_request_options['customParameters[SHOPPER_otherCustomerParameter]'] = 'otherCustomerParameter_test' + + @test_success_id = '8a82944a4e008ca9014e1273e0696122' + @test_failure_id = '8a8294494e0078a6014e12b371fb6a8e' + @test_wrong_reference_id = '8a8444494a0033a6014e12b371fb6a1e' + + @options = @minimal_request_options if request_type == 'minimal' + @options = @complete_request_options if request_type == 'complete' + end + + # ****************************************** SUCCESSFUL TESTS ****************************************** + def test_successful_purchase + @options[:description] = __method__ + + response = @gateway.purchase(@amount, @valid_card, @options) + assert_success response, 'Failed purchase' + assert_match %r{Request successfully processed}, response.message + + assert response.test? + end + + def test_successful_purchase_sans_options + response = @gateway.purchase(@amount, @valid_card) + assert_success response + assert_match %r{Request successfully processed}, response.message + + assert response.test? + end + + def test_successful_authorize + @options[:description] = __method__ + + response = @gateway.authorize(@amount, @valid_card, @options) + assert_success response, 'Authorization Failed' + assert_match %r{Request successfully processed}, response.message + + assert response.test? + end + + def test_successful_capture + @options[:description] = __method__ + auth = @gateway.authorize(@amount, @valid_card, @options) + assert_success auth, 'Authorization Failed' + assert auth.test? + + capt = @gateway.capture(@amount, auth.authorization, @options) + assert_success capt, 'Capture failed' + assert_match %r{Request successfully processed}, capt.message + + assert capt.test? + end + + def test_successful_refund + @options[:description] = __method__ + purchase = @gateway.purchase(@amount, @valid_card, @options) + assert_success purchase, 'Purchase failed' + assert purchase.test? + + refund = @gateway.refund(@amount, purchase.authorization, @options) + assert_success refund, 'Refund failed' + assert_match %r{Request successfully processed}, refund.message + + assert refund.test? + end + + def test_successful_void + @options[:description] = __method__ + purchase = @gateway.purchase(@amount, @valid_card, @options) + assert_success purchase, 'Purchase failed' + assert purchase.test? + + void = @gateway.void(purchase.authorization, @options) + assert_success void, 'Void failed' + assert_match %r{Request successfully processed}, void.message + + assert void.test? + end + + def test_successful_partial_capture + @options[:description] = __method__ + auth = @gateway.authorize(@amount, @valid_card, @options) + assert_success auth + + assert capture = @gateway.capture(@amount-1, auth.authorization) + assert_success capture + assert_match %r{Request successfully processed}, capture.message + end + + def test_successful_partial_refund + @options[:description] = __method__ + purchase = @gateway.purchase(@amount, @valid_card, @options) + assert_success purchase + + assert refund = @gateway.refund(@amount-1, purchase.authorization) + assert_success refund + assert_match %r{Request successfully processed}, refund.message + end + + def test_successful_verify + @options[:description] = __method__ + response = @gateway.verify(@valid_card, @options) + assert_success response + assert_match %r{Request successfully processed}, response.message + end + + # ****************************************** FAILURE TESTS ****************************************** + + def test_failed_purchase + @options[:description] = __method__ + response = @gateway.purchase(@amount, @invalid_card, @options) + assert_failure response + assert_match %r{invalid creditcard}, response.message + end + + def test_failed_authorize + @options[:description] = __method__ + response = @gateway.authorize(@amount, @invalid_card, @options) + assert_failure response + assert_match %r{invalid creditcard}, response.message + end + + def test_failed_capture + @options[:description] = __method__ + response = @gateway.capture(@amount, @test_wrong_reference_id) + assert_failure response + assert_match %r{capture needs at least one successful transaction}, response.message + end + + def test_failed_refund + @options[:description] = __method__ + response = @gateway.refund(@amount, @test_wrong_reference_id) + assert_failure response + assert_match %r{Invalid payment data}, response.message + end + + def test_failed_void + @options[:description] = __method__ + response = @gateway.void(@test_wrong_reference_id, @options) + assert_failure response + assert_match %r{reversal needs at least one successful transaction}, response.message + end + + # ************************************** TRANSCRIPT SCRUB ****************************************** + + def test_transcript_scrubbing + assert @gateway.supports_scrubbing? + + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @valid_card) + end + transcript = @gateway.scrub(transcript) + + assert_scrubbed(@valid_card.number, transcript) + assert_scrubbed(@valid_card.verification_value, transcript) + assert_scrubbed(@gateway.options[:password], transcript) + end +end diff --git a/test/remote/gateways/remote_optimal_payment_test.rb b/test/remote/gateways/remote_optimal_payment_test.rb index 7aad015a162..6061a306300 100644 --- a/test/remote/gateways/remote_optimal_payment_test.rb +++ b/test/remote/gateways/remote_optimal_payment_test.rb @@ -12,7 +12,8 @@ def setup :order_id => '1', :billing_address => address, :description => 'Basic Subscription', - :email => 'email@example.com' + :email => 'email@example.com', + :ip => '1.2.3.4' } end @@ -23,47 +24,39 @@ def test_successful_purchase end def test_unsuccessful_purchase_with_shipping_address - @options.merge!(:shipping_address => address) + @options[:shipping_address] = address assert response = @gateway.purchase(@amount, @credit_card, @options) assert_success response assert_equal 'no_error', response.message end def test_successful_great_britain - @options[:billing_address][:country] = "GB" - @options[:billing_address][:state] = "North West England" + @options[:billing_address][:country] = 'GB' + @options[:billing_address][:state] = 'North West England' assert response = @gateway.purchase(@amount, @credit_card, @options) assert_success response assert_equal 'no_error', response.message end - def test_minimal_successful_purchase - options = { - :order_id => '1', - :description => 'Basic Subscription', - :billing_address => { - :zip => 'K1C2N6', - } - } - credit_card = CreditCard.new( - :number => '4242424242424242', - :month => 9, - :year => Time.now.year + 1, - :first_name => 'Longbob', - :last_name => 'Longsen', - :brand => 'visa' - ) - assert response = @gateway.purchase(@amount, credit_card, options) - assert_success response - assert_equal 'no_error', response.message - end - def test_unsuccessful_purchase assert response = @gateway.purchase(@declined_amount, @credit_card, @options) assert_failure response assert_equal 'auth declined', response.message end + def test_purchase_with_no_cvv + @credit_card.verification_value = '' + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal 'no_error', response.message + end + + def test_successful_verify + response = @gateway.verify(@credit_card, @options) + assert_success response + assert_equal 'no_error', response.message + end + def test_authorize_and_capture assert auth = @gateway.authorize(@amount, @credit_card, @options) assert_success auth @@ -148,12 +141,24 @@ def test_overloaded_stored_data_authorize_and_capture def test_invalid_login gateway = OptimalPaymentGateway.new( - :account => '1', - :login => 'bad', + :account_number => '1', + :store_id => 'bad', :password => 'bad' ) assert response = gateway.purchase(@amount, @credit_card, @options) assert_failure response - assert_equal 'invalid credentials', response.message + assert_equal 'invalid merchant account', response.message + end + + # Password assertion hard-coded due to the value being the same as the login, which would cause a false-positive + def test_transcript_scrubbing + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @credit_card, @options) + end + transcript = @gateway.scrub(transcript) + + assert_scrubbed(@credit_card.number, transcript) + assert_scrubbed(@credit_card.verification_value, transcript) + assert_scrubbed('%3CstorePwd%3Etest%3C/storePwd%3E', transcript) end end diff --git a/test/remote/gateways/remote_orbital_test.rb b/test/remote/gateways/remote_orbital_test.rb index 84b22a10e48..92e7c3a7d9b 100644 --- a/test/remote/gateways/remote_orbital_test.rb +++ b/test/remote/gateways/remote_orbital_test.rb @@ -1,37 +1,55 @@ -require "test_helper.rb" +require 'test_helper.rb' class RemoteOrbitalGatewayTest < Test::Unit::TestCase def setup Base.mode = :test - @gateway = ActiveMerchant::Billing::OrbitalGateway.new(fixtures(:orbital)) + @gateway = ActiveMerchant::Billing::OrbitalGateway.new(fixtures(:orbital_gateway)) @amount = 100 - @credit_card = credit_card('4111111111111111') + @credit_card = credit_card('4112344112344113') @declined_card = credit_card('4000300011112220') @options = { :order_id => generate_unique_id, :address => address, + :merchant_id => 'merchant1234' } @cards = { - :visa => "4788250000028291", - :mc => "5454545454545454", - :amex => "371449635398431", - :ds => "6011000995500000", - :diners => "36438999960016", - :jcb => "3566002020140006"} + :visa => '4788250000028291', + :mc => '5454545454545454', + :amex => '371449635398431', + :ds => '6011000995500000', + :diners => '36438999960016', + :jcb => '3566002020140006'} + + @level_2_options = { + tax_indicator: '1', + tax: '75', + advice_addendum_1: 'taa1 - test', + advice_addendum_2: 'taa2 - test', + advice_addendum_3: 'taa3 - test', + advice_addendum_4: 'taa4 - test', + purchase_order: '123abc', + name: address[:name], + address1: address[:address1], + address2: address[:address2], + city: address[:city], + state: address[:state], + zip: address[:zip], + } @test_suite = [ - {:card => :visa, :AVSzip => 11111, :CVD => 111, :amount => 3000}, - {:card => :visa, :AVSzip => 33333, :CVD => nil, :amount => 3801}, - {:card => :mc, :AVSzip => 44444, :CVD => nil, :amount => 4100}, - {:card => :mc, :AVSzip => 88888, :CVD => 666, :amount => 1102}, - {:card => :amex, :AVSzip => 55555, :CVD => nil, :amount => 105500}, - {:card => :amex, :AVSzip => 66666, :CVD => 2222, :amount => 7500}, - {:card => :ds, :AVSzip => 77777, :CVD => nil, :amount => 1000}, - {:card => :ds, :AVSzip => 88888, :CVD => 444, :amount => 6303}, - {:card => :jcb, :AVSzip => 33333, :CVD => nil, :amount => 2900}] + {:card => :visa, :AVSzip => 11111, :CVD => 111, :amount => 3000}, + {:card => :visa, :AVSzip => 33333, :CVD => nil, :amount => 3801}, + {:card => :mc, :AVSzip => 44444, :CVD => nil, :amount => 4100}, + {:card => :mc, :AVSzip => 88888, :CVD => 666, :amount => 1102}, + {:card => :amex, :AVSzip => 55555, :CVD => nil, :amount => 105500}, + {:card => :amex, :AVSzip => 66666, :CVD => 2222, :amount => 7500}, + {:card => :ds, :AVSzip => 77777, :CVD => nil, :amount => 1000}, + {:card => :ds, :AVSzip => 88888, :CVD => 444, :amount => 6303}, + {:card => :jcb, :AVSzip => 33333, :CVD => nil, :amount => 2900} + ] end def test_successful_purchase @@ -40,11 +58,255 @@ def test_successful_purchase assert_equal 'Approved', response.message end + def test_successful_purchase_with_soft_descriptor_hash + assert response = @gateway.purchase( + @amount, @credit_card, @options.merge( + soft_descriptors: { + merchant_name: 'Merch', + product_description: 'Description', + merchant_email: 'email@example', + } + ) + ) + assert_success response + assert_equal 'Approved', response.message + end + + def test_successful_purchase_with_level_2_data + response = @gateway.purchase(@amount, @credit_card, @options.merge(level_2_data: @level_2_options)) + + assert_success response + assert_equal 'Approved', response.message + end + + def test_successful_purchase_with_visa_network_tokenization_credit_card_with_eci + network_card = network_tokenization_credit_card('4788250000028291', + payment_cryptogram: 'BwABB4JRdgAAAAAAiFF2AAAAAAA=', + transaction_id: 'BwABB4JRdgAAAAAAiFF2AAAAAAA=', + verification_value: '111', + brand: 'visa', + eci: '5' + ) + assert response = @gateway.purchase(3000, network_card, @options) + assert_success response + assert_equal 'Approved', response.message + assert_false response.authorization.blank? + end + + def test_successful_purchase_with_master_card_network_tokenization_credit_card + network_card = network_tokenization_credit_card('4788250000028291', + payment_cryptogram: 'BwABB4JRdgAAAAAAiFF2AAAAAAA=', + transaction_id: 'BwABB4JRdgAAAAAAiFF2AAAAAAA=', + verification_value: '111', + brand: 'master' + ) + assert response = @gateway.purchase(3000, network_card, @options) + assert_success response + assert_equal 'Approved', response.message + assert_false response.authorization.blank? + end + + def test_successful_purchase_with_american_express_network_tokenization_credit_card + network_card = network_tokenization_credit_card('4788250000028291', + payment_cryptogram: 'BwABB4JRdgAAAAAAiFF2AAAAAAA=', + transaction_id: 'BwABB4JRdgAAAAAAiFF2AAAAAAA=', + verification_value: '111', + brand: 'american_express' + ) + assert response = @gateway.purchase(3000, network_card, @options) + assert_success response + assert_equal 'Approved', response.message + assert_false response.authorization.blank? + end + + def test_successful_purchase_with_discover_network_tokenization_credit_card + network_card = network_tokenization_credit_card('4788250000028291', + payment_cryptogram: 'BwABB4JRdgAAAAAAiFF2AAAAAAA=', + transaction_id: 'BwABB4JRdgAAAAAAiFF2AAAAAAA=', + verification_value: '111', + brand: 'discover' + ) + assert response = @gateway.purchase(3000, network_card, @options) + assert_success response + assert_equal 'Approved', response.message + assert_false response.authorization.blank? + end + + [ + { + card: { + number: '4112344112344113', + verification_value: '411', + brand: 'visa', + }, + three_d_secure: { + eci: '5', + cavv: 'AAABAIcJIoQDIzAgVAkiAAAAAAA=', + xid: 'AAABAIcJIoQDIzAgVAkiAAAAAAA=', + }, + address: { + address1: '55 Forever Ave', + address2: '', + city: 'Concord', + state: 'NH', + zip: '03301', + country: 'US', + }, + }, + { + card: { + number: '5112345112345114', + verification_value: '823', + brand: 'master', + }, + three_d_secure: { + eci: '6', + cavv: 'Asju1ljfl86bAAAAAACm9zU6aqY=', + xid: 'Asju1ljfl86bAAAAAACm9zU6aqY=', + }, + address: { + address1: 'Byway Street', + address2: '', + city: 'Portsmouth', + state: 'MA', + zip: '', + country: 'US', + }, + }, + { + card: { + number: '371144371144376', + verification_value: '1234', + brand: 'american_express', + }, + three_d_secure: { + eci: '5', + cavv: 'AAABBWcSNIdjeUZThmNHAAAAAAA=', + xid: 'AAABBWcSNIdjeUZThmNHAAAAAAA=', + }, + address: { + address1: '4 Northeastern Blvd', + address2: '', + city: 'Salem', + state: 'NH', + zip: '03105', + country: 'US', + }, + } + ].each do |fixture| + define_method("test_successful_#{fixture[:card][:brand]}_authorization_with_3ds") do + cc = credit_card(fixture[:card][:number], { + verification_value: fixture[:card][:verification_value], + brand: fixture[:card][:brand] + }) + assert response = @gateway.authorize(100, cc, @options.merge( + order_id: '2', + currency: 'USD', + three_d_secure: fixture[:three_d_secure], + address: fixture[:address] + )) + + assert_success response + assert_equal 'Approved', response.message + assert_false response.authorization.blank? + end + + define_method("test_successful_#{fixture[:card][:brand]}_purchase_with_3ds") do + cc = credit_card(fixture[:card][:number], { + verification_value: fixture[:card][:verification_value], + brand: fixture[:card][:brand] + }) + assert response = @gateway.purchase(100, cc, @options.merge( + order_id: '2', + currency: 'USD', + three_d_secure: fixture[:three_d_secure], + address: fixture[:address] + )) + + assert_success response + assert_equal 'Approved', response.message + assert_false response.authorization.blank? + end + end + + def test_successful_purchase_with_mit_stored_credentials + mit_stored_credentials = { + mit_msg_type: 'MUSE', + mit_stored_credential_ind: 'Y', + mit_submitted_transaction_id: 'abcdefg12345678' + } + + response = @gateway.purchase(@amount, @credit_card, @options.merge(mit_stored_credentials)) + + assert_success response + assert_equal 'Approved', response.message + end + + def test_successful_purchase_with_cit_stored_credentials + cit_options = { + mit_msg_type: 'CUSE', + mit_stored_credential_ind: 'Y' + } + + response = @gateway.purchase(@amount, @credit_card, @options.merge(cit_options)) + + assert_success response + assert_equal 'Approved', response.message + end + + def test_successful_purchase_with_normalized_mit_stored_credentials + stored_credential = { + stored_credential: { + initial_transaction: false, + initiator: 'merchant', + reason_type: 'unscheduled', + network_transaction_id: 'abcdefg12345678' + } + } + + response = @gateway.purchase(@amount, @credit_card, @options.merge(stored_credential)) + + assert_success response + assert_equal 'Approved', response.message + end + + def test_successful_purchase_with_normalized_cit_stored_credentials + stored_credential = { + stored_credential: { + initial_transaction: true, + initiator: 'customer', + reason_type: 'unscheduled' + } + } + + response = @gateway.purchase(@amount, @credit_card, @options.merge(stored_credential)) + + assert_success response + assert_equal 'Approved', response.message + end + + def test_successful_purchase_with_overridden_normalized_stored_credentials + stored_credential = { + stored_credential: { + initial_transaction: false, + initiator: 'merchant', + reason_type: 'unscheduled', + network_transaction_id: 'abcdefg12345678' + }, + mit_msg_type: 'MRSB' + } + + response = @gateway.purchase(@amount, @credit_card, @options.merge(stored_credential)) + + assert_success response + assert_equal 'Approved', response.message + end + # Amounts of x.01 will fail def test_unsuccessful_purchase assert response = @gateway.purchase(101, @declined_card, @options) assert_failure response - assert_equal 'AUTH DECLINED 12001', response.message + assert_equal 'Invalid CC Number', response.message end def test_authorize_and_capture @@ -57,6 +319,15 @@ def test_authorize_and_capture assert_success capture end + def test_successful_authorize_and_capture_with_level_2_data + auth = @gateway.authorize(@amount, @credit_card, @options.merge(level_2_data: @level_2_options)) + assert_success auth + assert_equal 'Approved', auth.message + + capture = @gateway.capture(@amount, auth.authorization, @options.merge(level_2_data: @level_2_options)) + assert_success capture + end + def test_authorize_and_void assert auth = @gateway.authorize(@amount, @credit_card, @options.merge(:order_id => '2')) assert_success auth @@ -75,21 +346,21 @@ def test_refund assert_success refund end + def test_successful_refund_with_level_2_data + amount = @amount + assert response = @gateway.purchase(amount, @credit_card, @options.merge(level_2_data: @level_2_options)) + assert_success response + assert response.authorization + assert refund = @gateway.refund(amount, response.authorization, @options.merge(level_2_data: @level_2_options)) + assert_success refund + end + def test_failed_capture assert response = @gateway.capture(@amount, '') assert_failure response assert_equal 'Bad data error', response.message end - def test_successful_purchase_with_money - response = nil - silence_warnings do - assert response = @gateway.purchase(Money.new(100), @credit_card, @options) - end - assert_success response - assert_equal 'Approved', response.message - end - # == Certification Tests # ==== Section A @@ -97,7 +368,7 @@ def test_auth_only_transactions for suite in @test_suite do amount = suite[:amount] card = credit_card(@cards[suite[:card]], :verification_value => suite[:CVD]) - @options[:address].merge!(:zip => suite[:AVSzip]) + @options[:address][:zip] = suite[:AVSzip] assert response = @gateway.authorize(amount, card, @options) assert_kind_of Response, response @@ -115,7 +386,7 @@ def test_auth_capture_transactions for suite in @test_suite do amount = suite[:amount] card = credit_card(@cards[suite[:card]], :verification_value => suite[:CVD]) - options = @options; options[:address].merge!(:zip => suite[:AVSzip]) + options = @options; options[:address][:zip] = suite[:AVSzip] assert response = @gateway.purchase(amount, card, options) assert_kind_of Response, response @@ -130,7 +401,7 @@ def test_auth_capture_transactions # ==== Section C def test_mark_for_capture_transactions - [[:visa, 3000],[:mc, 4100],[:amex, 105500],[:ds, 1000],[:jcb, 2900]].each do |suite| + [[:visa, 3000], [:mc, 4100], [:amex, 105500], [:ds, 1000], [:jcb, 2900]].each do |suite| amount = suite[1] card = credit_card(@cards[suite[0]]) assert auth_response = @gateway.authorize(amount, card, @options) @@ -146,7 +417,7 @@ def test_mark_for_capture_transactions # ==== Section D def test_refund_transactions - [[:visa, 1200],[:mc, 1100],[:amex, 105500],[:ds, 1000],[:jcb, 2900]].each do |suite| + [[:visa, 1200], [:mc, 1100], [:amex, 105500], [:ds, 1000], [:jcb, 2900]].each do |suite| amount = suite[1] card = credit_card(@cards[suite[0]]) assert purchase_response = @gateway.purchase(amount, card, @options) @@ -172,4 +443,42 @@ def test_void_transactions # puts end end + + def test_successful_verify + response = @gateway.verify(@credit_card, @options) + assert_success response + assert_equal 'Approved', response.message + end + + def test_failed_verify + response = @gateway.verify(@declined_card, @options) + assert_failure response + assert_equal 'Invalid CC Number', response.message + end + + def test_transcript_scrubbing + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @credit_card, @options) + end + transcript = @gateway.scrub(transcript) + + assert_scrubbed(@credit_card.number, transcript) + assert_scrubbed(@credit_card.verification_value, transcript) + assert_scrubbed(@gateway.options[:password], transcript) + assert_scrubbed(@gateway.options[:login], transcript) + assert_scrubbed(@gateway.options[:merchant_id], transcript) + end + + def test_transcript_scrubbing_profile + transcript = capture_transcript(@gateway) do + @gateway.add_customer_profile(@credit_card, @options) + end + transcript = @gateway.scrub(transcript) + + assert_scrubbed(@credit_card.number, transcript) + assert_scrubbed(@credit_card.verification_value, transcript) + assert_scrubbed(@gateway.options[:password], transcript) + assert_scrubbed(@gateway.options[:login], transcript) + assert_scrubbed(@gateway.options[:merchant_id], transcript) + end end diff --git a/test/remote/gateways/remote_pac_net_raven_test.rb b/test/remote/gateways/remote_pac_net_raven_test.rb new file mode 100644 index 00000000000..a1473031fbc --- /dev/null +++ b/test/remote/gateways/remote_pac_net_raven_test.rb @@ -0,0 +1,207 @@ +require 'test_helper' + +class RemotePacNetRavenGatewayTest < Test::Unit::TestCase + def setup + @gateway = PacNetRavenGateway.new(fixtures(:raven_pac_net)) + + @amount = 100 + @credit_card = credit_card('4000000000000028') + @declined_card = credit_card('5100000000000040') + + @options = { + billing_address: address + } + end + + def test_successful_purchase + assert purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + assert purchase.params['ApprovalCode'] + assert purchase.params['TrackingNumber'] + assert_nil purchase.params['ErrorCode'] + assert_equal 'Approved', purchase.params['Status'] + assert_equal 'ok', purchase.params['RequestResult'] + assert_nil purchase.params['Message'] + assert_equal 'This transaction has been approved', purchase.message + end + + def test_invalid_credit_card_number_purchese + @credit_card = credit_card('0000') + assert purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_failure purchase + assert_nil purchase.params['ApprovalCode'] + assert purchase.params['TrackingNumber'] + assert_equal 'invalid:cardNumber', purchase.params['ErrorCode'] + assert_equal 'Invalid:CardNumber', purchase.params['Status'] + assert_equal 'ok', purchase.params['RequestResult'] + assert_equal 'Error processing transaction because CardNumber "0000" is not between 12 and 19 in length.', purchase.params['Message'] + end + + def test_expired_credit_card_purchese + @credit_card.month = 9 + @credit_card.year = 2012 + assert purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_failure purchase + assert_nil purchase.params['ApprovalCode'] + assert purchase.params['TrackingNumber'] + assert_equal 'invalid:CustomerCardExpiryDate', purchase.params['ErrorCode'] + assert_equal 'Invalid:CustomerCardExpiryDate', purchase.params['Status'] + assert_equal 'ok', purchase.params['RequestResult'] + assert_equal 'Invalid because the card expiry date (mmyy) "0912" is not a date in the future', purchase.params['Message'] + end + + def test_declined_purchase + assert purchase = @gateway.purchase(@amount, @declined_card, @options) + assert_failure purchase + assert_equal 'This transaction has been declined', purchase.message + end + + def test_successful_authorization + assert auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + assert auth.params['ApprovalCode'] + assert auth.params['TrackingNumber'] + assert_nil auth.params['ErrorCode'] + assert_nil auth.params['Message'] + assert_equal 'Approved', auth.params['Status'] + assert_equal 'ok', auth.params['RequestResult'] + assert_equal 'This transaction has been approved', auth.message + end + + def test_invalid_credit_card_number_authorization + @credit_card = credit_card('0000') + assert auth = @gateway.authorize(@amount, @credit_card, @options) + assert_failure auth + assert_nil auth.params['ApprovalCode'] + assert auth.params['TrackingNumber'] + assert_equal 'invalid:cardNumber', auth.params['ErrorCode'] + assert_equal 'Invalid:CardNumber', auth.params['Status'] + assert_equal 'ok', auth.params['RequestResult'] + assert_equal 'Error processing transaction because CardNumber "0000" is not between 12 and 19 in length.', auth.params['Message'] + end + + def test_expired_credit_card_authorization + @credit_card.month = 9 + @credit_card.year = 2012 + assert auth = @gateway.authorize(@amount, @credit_card, @options) + assert_failure auth + assert_nil auth.params['ApprovalCode'] + assert auth.params['TrackingNumber'] + assert_equal 'invalid:CustomerCardExpiryDate', auth.params['ErrorCode'] + assert_equal 'Invalid:CustomerCardExpiryDate', auth.params['Status'] + assert_equal 'ok', auth.params['RequestResult'] + assert_equal 'Invalid because the card expiry date (mmyy) "0912" is not a date in the future', auth.params['Message'] + end + + def test_declined_authorization + assert auth = @gateway.authorize(@amount, @declined_card, @options) + assert_failure auth + assert_equal 'This transaction has been declined', auth.message + end + + def test_successful_refund + assert purchase = @gateway.purchase(@amount, @credit_card, @options) + assert refund = @gateway.refund(@amount, purchase.authorization) + assert_success refund + assert refund.params['ApprovalCode'] + assert refund.params['TrackingNumber'] + assert_nil refund.params['ErrorCode'] + assert_equal 'Approved', refund.params['Status'] + assert_equal 'ok', refund.params['RequestResult'] + assert_nil refund.params['Message'] + assert_equal 'This transaction has been approved', refund.message + end + + def test_amount_greater_than_original_amount_refund + assert purchase = @gateway.purchase(100, @credit_card, @options) + assert_success purchase + + assert refund = @gateway.refund(200, purchase.authorization) + assert_failure refund + assert_nil refund.params['ApprovalCode'] + assert refund.params['TrackingNumber'] + assert_equal 'invalid:RefundAmountGreaterThanOriginalAmount', refund.params['ErrorCode'] + assert_equal 'Invalid:RefundAmountGreaterThanOriginalAmount', refund.params['Status'] + assert_equal 'ok', refund.params['RequestResult'] + assert_equal 'Invalid because the payment amount cannot be greater than the original charge.', refund.params['Message'] + assert_equal 'Invalid because the payment amount cannot be greater than the original charge.', refund.message + end + + def test_purchase_and_void + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert void = @gateway.void(purchase.authorization) + assert_success void + assert void.params['ApprovalCode'] + assert void.params['TrackingNumber'] + assert_nil void.params['ErrorCode'] + assert_equal 'ok', void.params['RequestResult'] + assert_nil void.params['Message'] + assert_equal 'Voided', void.params['Status'] + assert_equal 'This transaction has been voided', void.message + end + + def test_authorize_and_void + auth = @gateway.authorize(@amount, @credit_card, @options) + assert void = @gateway.void(auth.authorization) + assert_success void + assert void.params['ApprovalCode'] + assert void.params['TrackingNumber'] + assert_nil void.params['ErrorCode'] + assert_equal 'ok', void.params['RequestResult'] + assert_nil void.params['Message'] + assert_equal 'Voided', void.params['Status'] + assert_equal 'This transaction has been voided', void.message + end + + def test_authorize_capture_and_void + auth = @gateway.authorize(@amount, @credit_card, @options) + assert capture = @gateway.capture(@amount, auth.authorization) + assert void = @gateway.void(capture.authorization) + assert_success void + assert void.params['ApprovalCode'] + assert void.params['TrackingNumber'] + assert_nil void.params['ErrorCode'] + assert_equal 'ok', void.params['RequestResult'] + assert_nil void.params['Message'] + assert_equal 'Voided', void.params['Status'] + assert_equal 'This transaction has been voided', void.message + end + + def test_successful_capture + auth = @gateway.authorize(@amount, @credit_card, @options) + assert capture = @gateway.capture(@amount, auth.authorization) + assert_success capture + assert capture.params['ApprovalCode'] + assert capture.params['TrackingNumber'] + assert_nil capture.params['ErrorCode'] + assert_equal 'Approved', capture.params['Status'] + assert_equal 'ok', capture.params['RequestResult'] + assert_nil capture.params['Message'] + assert_equal 'This transaction has been approved', capture.message + end + + def test_invalid_preauth_number_capture + assert capture = @gateway.capture(@amount, '') + assert_failure capture + assert_nil capture.params['ApprovalCode'] + assert capture.params['TrackingNumber'] + assert_equal 'rejected:UnknownPreauthNumber', capture.params['ErrorCode'] + assert_equal 'Rejected:UnknownPreauthNumber', capture.params['Status'] + assert_equal 'ok', capture.params['RequestResult'] + assert_equal 'Invalid because the preauthorization # does not exist', capture.params['Message'] + assert_equal 'Invalid because the preauthorization # does not exist', capture.message + end + + def test_insufficient_preauth_amount_capture + auth = @gateway.authorize(100, @credit_card, @options) + assert capture = @gateway.capture(200, auth.authorization) + assert_failure capture + assert_nil capture.params['ApprovalCode'] + assert capture.params['TrackingNumber'] + assert_equal 'rejected:PreauthAmountInsufficient', capture.params['ErrorCode'] + assert_equal 'Rejected:PreauthAmountInsufficient', capture.params['Status'] + assert_equal 'ok', capture.params['RequestResult'] + assert_equal 'Invalid because the preauthorization amount 100 is not identical to the amount to be settled', capture.params['Message'] + assert_equal 'Invalid because the preauthorization amount 100 is not identical to the amount to be settled', capture.message + end +end diff --git a/test/remote/gateways/remote_pagarme_test.rb b/test/remote/gateways/remote_pagarme_test.rb new file mode 100644 index 00000000000..868ff0a48c3 --- /dev/null +++ b/test/remote/gateways/remote_pagarme_test.rb @@ -0,0 +1,157 @@ +require 'test_helper' + +class RemotePagarmeTest < Test::Unit::TestCase + def setup + @gateway = PagarmeGateway.new(fixtures(:pagarme)) + + @amount = 1000 + + @credit_card = credit_card('4242424242424242', { + first_name: 'Richard', + last_name: 'Deschamps' + }) + + @declined_card = credit_card('4242424242424242', { + first_name: 'Richard', + last_name: 'Deschamps', + :verification_value => '688' + }) + + @options = { + billing_address: address(), + description: 'ActiveMerchant Teste de Compra' + } + end + + def test_successful_purchase + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal 'Transação aprovada', response.message + + # Assert metadata + assert_equal response.params['metadata']['description'], @options[:description] + end + + def test_successful_purchase_with_more_options + options = { + order_id: '1', + ip: '127.0.0.1', + customer: 'Richard Deschamps', + invoice: '1', + merchant: 'Richard\'s', + description: 'ActiveMerchant Teste de Compra', + email: 'suporte@pagar.me', + billing_address: address() + } + + response = @gateway.purchase(@amount, @credit_card, options) + assert_success response + assert_equal 'Transação aprovada', response.message + + # Assert metadata + assert_equal response.params['metadata']['order_id'], options[:order_id] + assert_equal response.params['metadata']['ip'], options[:ip] + assert_equal response.params['metadata']['customer'], options[:customer] + assert_equal response.params['metadata']['invoice'], options[:invoice] + assert_equal response.params['metadata']['merchant'], options[:merchant] + assert_equal response.params['metadata']['description'], options[:description] + assert_equal response.params['metadata']['email'], options[:email] + end + + def test_successful_purchase_without_options + response = @gateway.purchase(@amount, @credit_card) + assert_success response + assert_equal 'Transação aprovada', response.message + end + + def test_failed_purchase + response = @gateway.purchase(@amount, @declined_card, @options) + assert_failure response + assert_equal 'Transação recusada', response.message + end + + def test_successful_authorize_and_capture + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + assert_equal 'Transação autorizada', auth.message + + assert capture = @gateway.capture(@amount, auth.authorization) + assert_success capture + assert_equal 'Transação aprovada', capture.message + end + + def test_failed_authorize + response = @gateway.authorize(@amount, @declined_card, @options) + assert_failure response + assert_equal 'Transação recusada', response.message + end + + def test_failed_capture + response = @gateway.capture(@amount, nil) + assert_failure response + assert_equal 'Não é possível capturar uma transação sem uma prévia autorização.', response.message + end + + def test_successful_refund + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + + assert refund = @gateway.refund(@amount, purchase.authorization) + assert_success refund + assert_equal 'Transação estornada', refund.message + end + + def test_failed_refund + response = @gateway.refund(@amount, nil) + assert_failure response + assert_equal 'Não é possível estornar uma transação sem uma prévia captura.', response.message + end + + def test_successful_void + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + assert void = @gateway.void(auth.authorization) + assert_success void + assert_equal 'Transação estornada', void.message + end + + def test_failed_void + response = @gateway.void(nil) + assert_failure response + assert_equal 'Não é possível estornar uma transação autorizada sem uma prévia autorização.', response.message + end + + def test_successful_verify + response = @gateway.verify(@credit_card, @options) + assert_success response + assert_equal 'Transação autorizada', response.message + end + + def test_failed_verify + response = @gateway.verify(@declined_card, @options) + assert_failure response + assert_equal 'Transação recusada', response.message + end + + def test_invalid_login + gateway = PagarmeGateway.new(api_key: '') + + response = gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_match %r{401 Authorization Required}, response.message + end + + def test_transcript_scrubbing + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @credit_card, @options) + end + transcript = @gateway.scrub(transcript) + + assert_scrubbed(@credit_card.number, transcript) + assert_scrubbed(@credit_card.verification_value, transcript) + assert_scrubbed(@gateway.options[:api_key], transcript) + end + +end diff --git a/test/remote/gateways/remote_pago_facil_test.rb b/test/remote/gateways/remote_pago_facil_test.rb new file mode 100644 index 00000000000..254ebd33dec --- /dev/null +++ b/test/remote/gateways/remote_pago_facil_test.rb @@ -0,0 +1,97 @@ +require 'test_helper' + +class RemotePagoFacilTest < Test::Unit::TestCase + def setup + @gateway = PagoFacilGateway.new(fixtures(:pago_facil)) + + @amount = 100 + + @credit_card = ActiveMerchant::Billing::CreditCard.new( + number: '4111111111111111', + verification_value: '123', + first_name: 'Juan', + last_name: 'Reyes Garza', + month: 9, + year: Time.now.year + 1 + ) + + @declined_card = ActiveMerchant::Billing::CreditCard.new( + number: '1111111111111111', + verification_value: '123', + first_name: 'Juan', + last_name: 'Reyes Garza', + month: 9, + year: Time.now.year + 1 + ) + + @options = { + order_id: '1', + billing_address: { + address1: 'Anatole France 311', + address2: 'Polanco', + city: 'Miguel Hidalgo', + state: 'Distrito Federal', + country: 'Mexico', + zip: '11560', + phone: '5550220910' + }, + email: 'comprador@correo.com', + cellphone: '5550123456' + } + end + + def test_successful_purchase + response = successful_response_to do + @gateway.purchase(@amount, @credit_card, @options) + end + + assert response.authorization + assert_equal 'Transaction has been successful!-Approved', response.message + end + + def test_failed_purchase + response = @gateway.purchase(@amount, @declined_card, @options) + assert_failure response + assert_equal 'Errores en los datos de entrada Validaciones', response.message + end + + def test_invalid_login + gateway = PagoFacilGateway.new( + branch_id: '', + merchant_id: '', + service_id: 3 + ) + response = gateway.purchase(@amount, @credit_card, @options) + assert_failure response + end + + def test_successful_purchase_usd + options = @options.merge(currency: 'USD') + response = successful_response_to do + @gateway.purchase(@amount, @credit_card, options) + end + + assert_equal 'USD', response.params['dataVal']['divisa'] + assert response.authorization + assert_equal 'Transaction has been successful!-Approved', response.message + end + + # Even when all the parameters are correct the PagoFacil's test service will + # respond randomly (can be approved or declined). When for this reason the + # service returns a "declined" response, the response should have the error + # message 'Declined_(General)' + def successful_response_to + attempts = 0 + loop do + random_response = yield + if random_response.success? + return random_response + elsif(attempts > 2) + raise 'Unable to get a successful response' + else + assert_equal 'Declined_(General).', random_response.params.fetch('error') + attempts += 1 + end + end + end +end diff --git a/test/remote/gateways/remote_pay_conex_test.rb b/test/remote/gateways/remote_pay_conex_test.rb new file mode 100644 index 00000000000..22ed010bcbd --- /dev/null +++ b/test/remote/gateways/remote_pay_conex_test.rb @@ -0,0 +1,202 @@ +require 'test_helper' + +class RemotePayConexTest < Test::Unit::TestCase + def setup + @gateway = PayConexGateway.new(fixtures(:pay_conex)) + @credit_card = credit_card('4000100011112224') + @check = check + + @amount = 100 + @failed_amount = 101 + + @options = { + order_id: '1', + billing_address: address, + description: 'Store Purchase', + email: 'joe@example.com' + } + end + + def test_transcript_scrubbing + @credit_card.verification_value = '447' + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @credit_card, @options) + end + transcript = @gateway.scrub(transcript) + + assert_scrubbed(@credit_card.number, transcript) + assert_scrubbed(@credit_card.verification_value, transcript) + assert_scrubbed(@gateway.options[:api_accesskey], transcript) + end + + def test_successful_purchase + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal 'APPROVED', response.message + end + + def test_failed_purchase + response = @gateway.purchase(@failed_amount, @credit_card, @options) + assert_failure response + assert_equal 'DECLINED', response.message + end + + def test_successful_authorize_and_capture + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + assert_equal 'APPROVED', auth.message + + assert capture = @gateway.capture(@amount, auth.authorization) + assert_success capture + assert_equal 'CAPTURED', capture.message + end + + def test_failed_authorize + response = @gateway.authorize(@failed_amount, @credit_card, @options) + assert_failure response + assert_equal 'DECLINED', response.message + end + + def test_partial_capture + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + assert capture = @gateway.capture(@amount-1, auth.authorization) + assert_success capture + assert_equal 'CAPTURED', capture.message + end + + def test_failed_capture + response = @gateway.capture(@amount, 'UnknownAuth') + assert_failure response + assert_equal 'Invalid token_id', response.message + end + + def test_successful_refund + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + + assert refund = @gateway.refund(@amount, purchase.authorization) + assert_success refund + assert_equal 'VOID', refund.message + end + + def test_partial_refund + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + + assert refund = @gateway.refund(@amount-1, purchase.authorization) + assert_success refund + assert_equal 'REFUND', refund.message + end + + def test_failed_refund + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + + response = @gateway.refund(@amount + 400, purchase.authorization) + assert_failure response + assert_equal 'INVALID REFUND AMOUNT', response.message + end + + def test_successful_void + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + assert void = @gateway.void(auth.authorization) + assert_success void + assert_equal 'APPROVED', void.message + end + + def test_failed_void + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + response = @gateway.void(auth.authorization) + assert_success response + + response = @gateway.void(auth.authorization) + assert_failure response + assert_equal 'TRANSACTION ID ALREADY REVERSED', response.message + end + + def test_successful_verify + response = @gateway.verify(@credit_card, @options) + assert_success response + assert_equal 'APPROVED', response.message + end + + def test_failed_verify + response = @gateway.verify(credit_card('BogusCard'), @options) + assert_failure response + assert_equal 'INVALID CARD NUMBER', response.message + end + + def test_successful_store + assert response = @gateway.store(@credit_card) + assert_success response + assert response.authorization + assert_equal '2224', response.params['last4'] + end + + def test_failed_store + assert response = @gateway.store(credit_card('141241')) + assert_failure response + assert_equal 'CARD DATA UNREADABLE', response.message + end + + def test_purchase_using_stored_card + assert response = @gateway.store(@credit_card) + assert_success response + + response = @gateway.purchase(@amount, response.authorization, @options) + assert_success response + assert_equal 'APPROVED', response.message + end + + def test_successful_credit + response = @gateway.credit(@amount, @credit_card, @options) + assert_success response + assert_equal 'CREDIT', response.message + end + + def test_failed_credit + response = @gateway.credit(@amount, credit_card('12321'), @options) + assert_failure response + assert_equal 'CARD DATA UNREADABLE', response.message + end + + def test_successful_card_present_purchase + response = @gateway.purchase(@amount, credit_card_with_track_data('4000100011112224'), @options) + assert_success response + assert_equal 'APPROVED', response.message + end + + def test_failed_card_present_purchase + card = CreditCard.new(track_data: '%B37826310005^LOB^17001130504392?') + response = @gateway.purchase(@amount, card, @options) + assert_failure response + assert_equal 'CARD DATA UNREADABLE', response.message + end + + def test_successful_echeck_purchase + response = @gateway.purchase(@amount, @check, @options) + assert_success response + assert response.test? + assert_equal 'PENDING', response.message + assert response.authorization + end + + def test_failed_echeck_purchase + response = @gateway.purchase(@amount, check(routing_number: '23433'), @options) + assert_failure response + assert_equal 'Invalid bank_routing_number', response.message + end + + def test_invalid_login + gateway = PayConexGateway.new(account_id: 'Unknown', api_accesskey: 'Incorrect') + response = gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_equal 'Invalid account_id', response.message + end +end diff --git a/test/remote/gateways/remote_pay_gate_xml_test.rb b/test/remote/gateways/remote_pay_gate_xml_test.rb index 3be6b3cb48d..b16cedb0678 100644 --- a/test/remote/gateways/remote_pay_gate_xml_test.rb +++ b/test/remote/gateways/remote_pay_gate_xml_test.rb @@ -11,6 +11,8 @@ def setup @options = { :order_id => generate_unique_id, :billing_address => address, + :email => 'john.doe@example.com', + :ip => '127.0.0.1', :description => 'Store Purchase', } end @@ -24,7 +26,7 @@ def test_successful_purchase def test_unsuccessful_purchase assert response = @gateway.purchase(@amount, @declined_card, @options) assert_failure response - assert_equal "Declined", response.message + assert_equal 'Declined', response.message end def test_authorize_and_capture @@ -45,11 +47,20 @@ def test_failed_capture def test_invalid_login gateway = PayGateXmlGateway.new( - :login => '', - :password => '' - ) + :login => '', + :password => '' + ) assert response = gateway.authorize(@amount, @credit_card, @options) assert_failure response assert_equal 'Incorrect Credentials Supplied', response.message end + + def test_successful_purchase_and_refund + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + + credit = @gateway.refund(@amount, purchase.authorization, :note => 'Sorry') + assert_success credit + assert credit.test? + end end diff --git a/test/remote/gateways/remote_pay_hub_test.rb b/test/remote/gateways/remote_pay_hub_test.rb new file mode 100644 index 00000000000..e57fe048e4a --- /dev/null +++ b/test/remote/gateways/remote_pay_hub_test.rb @@ -0,0 +1,82 @@ +require 'test_helper' + +class RemotePayHubTest < Test::Unit::TestCase + def setup + @gateway = PayHubGateway.new(fixtures(:pay_hub)) + @amount = 100 + @credit_card = credit_card('5466410004374507', verification_value: '998') + @invalid_card = credit_card('371449635398431', verification_value: '9997') + @options = { + :first_name => 'Garrya', + :last_name => 'Barrya', + :email => 'payhubtest@mailinator.com', + :address => { + :address1 => '123a ahappy St.', + :city => 'Happya City', + :state => 'CA', + :zip => '94901' + } + } + end + + def test_successful_purchase + response = @gateway.purchase(@amount, @credit_card, @options) + + assert_success response + assert_equal 'SUCCESS', response.message + end + + def test_unsuccessful_purchase + amount = 20 + response = @gateway.purchase(amount, @invalid_card, @options) + assert_failure response + assert_equal 'DECLINE', response.message + end + + def test_successful_auth + response = @gateway.authorize(@amount, @credit_card, @options) + + assert_success response + assert_equal 'SUCCESS', response.message + end + + def test_unsuccessful_auth + response = @gateway.authorize(20, @invalid_card, @options) + assert_failure response + assert_equal 'DECLINE', response.message + end + + def test_unsuccessful_capture + assert_failure @gateway.capture(@amount, 'bogus') + end + + def test_partial_capture + auth_response = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth_response + + response = @gateway.capture(10, auth_response.authorization) + assert_success response + assert_equal 'TRANSACTION CAPTURED SUCCESSFULLY', response.message + end + + def test_successful_refund + response = @gateway.purchase(@amount, @credit_card) + assert_success response + + response = @gateway.refund(nil, response.authorization) + assert_success response + assert_equal 'SUCCESS', response.message + end + + def test_unsuccessful_refund + assert_failure @gateway.refund(@amount, 'bogus') + end + + def test_successful_verify + assert_success @gateway.verify(@credit_card) + end + + def test_failed_verify + assert_failure @gateway.verify(credit_card('4111111111111111')) + end +end diff --git a/test/remote/gateways/remote_pay_junction_test.rb b/test/remote/gateways/remote_pay_junction_test.rb index db8724e1811..1db822d8bb1 100644 --- a/test/remote/gateways/remote_pay_junction_test.rb +++ b/test/remote/gateways/remote_pay_junction_test.rb @@ -37,8 +37,8 @@ def setup def test_successful_purchase assert response = @gateway.purchase(AMOUNT, @credit_card, @options) assert_equal PayJunctionGateway::SUCCESS_MESSAGE, response.message - assert_equal 'capture', response.params["posture"], 'Should be captured funds' - assert_equal 'charge', response.params["transaction_action"] + assert_equal 'capture', response.params['posture'], 'Should be captured funds' + assert_equal 'charge', response.params['transaction_action'] assert_success response assert response.test? end @@ -48,18 +48,18 @@ def test_successful_purchase_with_cvv assert response = @gateway.purchase(AMOUNT, @credit_card, @options) assert_equal PayJunctionGateway::SUCCESS_MESSAGE, response.message - assert_equal 'capture', response.params["posture"], 'Should be captured funds' - assert_equal 'charge', response.params["transaction_action"] + assert_equal 'capture', response.params['posture'], 'Should be captured funds' + assert_equal 'charge', response.params['transaction_action'] assert_success response end def test_successful_authorize - assert response = @gateway.authorize( AMOUNT, @credit_card, @options) + assert response = @gateway.authorize(AMOUNT, @credit_card, @options) assert_equal PayJunctionGateway::SUCCESS_MESSAGE, response.message - assert_equal 'hold', response.params["posture"], 'Should be a held charge' - assert_equal 'charge', response.params["transaction_action"] + assert_equal 'hold', response.params['posture'], 'Should be a held charge' + assert_equal 'charge', response.params['transaction_action'] assert_success response end @@ -70,9 +70,9 @@ def test_successful_capture response = @gateway.capture(AMOUNT, auth.authorization, @options) assert_success response - assert_equal 'capture', response.params["posture"], 'Should be a capture' + assert_equal 'capture', response.params['posture'], 'Should be a capture' assert_equal auth.authorization, response.authorization, - "Should maintain transaction ID across request" + 'Should maintain transaction ID across request' end def test_successful_credit @@ -80,7 +80,7 @@ def test_successful_credit assert_success purchase assert response = @gateway.credit(success_price, purchase.authorization) - assert_equal 'refund', response.params["transaction_action"] + assert_equal 'refund', response.params['transaction_action'] assert_success response end @@ -92,39 +92,39 @@ def test_successful_void assert response = @gateway.void(purchase.authorization, :order_id => order_id) assert_success response - assert_equal 'void', response.params["posture"], 'Should be a capture' + assert_equal 'void', response.params['posture'], 'Should be a capture' assert_equal purchase.authorization, response.authorization, - "Should maintain transaction ID across request" + 'Should maintain transaction ID across request' end def test_successful_instant_purchase - # this takes advatange of the PayJunction feature where another + # this takes advantage of the PayJunction feature where another # transaction can be executed if you have the transaction ID of a # previous successful transaction. - purchase = @gateway.purchase( AMOUNT, @credit_card, @options) + purchase = @gateway.purchase(AMOUNT, @credit_card, @options) assert_success purchase assert response = @gateway.purchase(AMOUNT, purchase.authorization, :order_id => generate_unique_id) assert_equal PayJunctionGateway::SUCCESS_MESSAGE, response.message - assert_equal 'capture', response.params["posture"], 'Should be captured funds' - assert_equal 'charge', response.params["transaction_action"] + assert_equal 'capture', response.params['posture'], 'Should be captured funds' + assert_equal 'charge', response.params['transaction_action'] assert_not_equal purchase.authorization, response.authorization, - 'Should have recieved new transaction ID' + 'Should have recieved new transaction ID' assert_success response end def test_successful_recurring assert response = @gateway.recurring(AMOUNT, @credit_card, - :periodicity => :monthly, - :payments => 12, - :order_id => generate_unique_id[0..15] - ) + :periodicity => :monthly, + :payments => 12, + :order_id => generate_unique_id[0..15] + ) assert_equal PayJunctionGateway::SUCCESS_MESSAGE, response.message - assert_equal 'charge', response.params["transaction_action"] + assert_equal 'charge', response.params['transaction_action'] assert_success response end @@ -132,11 +132,12 @@ def test_should_send_invoice response = @gateway.purchase(AMOUNT, @credit_card, @options) assert_success response - assert_equal @options[:order_id], response.params["invoice_number"], 'Should have set invoice' + assert_equal @options[:order_id], response.params['invoice_number'], 'Should have set invoice' end private + def success_price - 200 + rand(200) + rand(200..399) end end diff --git a/test/remote/gateways/remote_pay_junction_v2_test.rb b/test/remote/gateways/remote_pay_junction_v2_test.rb new file mode 100644 index 00000000000..62954ff4496 --- /dev/null +++ b/test/remote/gateways/remote_pay_junction_v2_test.rb @@ -0,0 +1,170 @@ +require 'test_helper' + +class RemotePayJunctionV2Test < Test::Unit::TestCase + def setup + @gateway = PayJunctionV2Gateway.new(fixtures(:pay_junction_v2)) + + @amount = 99 + @credit_card = credit_card('4444333322221111', month: 01, year: 2020, verification_value: 999) + @options = { + order_id: generate_unique_id + } + end + + def test_invalid_login + gateway = PayJunctionV2Gateway.new(api_login: '', api_password: '', api_key: '') + + response = gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_match %r{invalid application key}, response.message + end + + def test_successful_purchase + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal 'Approved', response.message + assert response.test? + end + + def test_successful_purchase_sans_options + amount = SecureRandom.random_number(100) + 100 + response = @gateway.purchase(amount, @credit_card) + assert_success response + assert_equal 'Approved', response.message + end + + def test_failed_purchase + amount = 5 + response = @gateway.purchase(amount, @credit_card, @options) + assert_failure response + assert_equal 'Declined (do not honor)', response.message + end + + def test_successful_authorize_and_capture + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + assert capture = @gateway.capture(@amount, auth.authorization) + assert_success capture + assert_equal 'Approved', capture.message + end + + def test_failed_authorize + amount = 10 + response = @gateway.authorize(amount, @credit_card, @options) + assert_failure response + assert_equal 'Declined (restricted)', response.message + end + + def test_partial_capture + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + assert capture = @gateway.capture(@amount-1, auth.authorization) + assert_success capture + assert_equal sprintf('%.2f', (@amount-1).to_f / 100), capture.params['amountTotal'] + end + + def test_failed_capture + response = @gateway.capture(@amount, 'invalid_authorization') + assert_failure response + assert_equal '404 Not Found|', response.message + end + + def test_successful_refund + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + + assert refund = @gateway.refund(@amount, purchase.authorization) + assert_success refund + assert_equal 'Approved', refund.message + end + + def test_partial_refund + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + + assert refund = @gateway.refund(@amount-50, purchase.authorization) + assert_success refund + assert_equal 'Approved', refund.message + assert_equal sprintf('%.2f', (@amount-50).to_f / 100), refund.params['amountTotal'] + end + + def test_failed_refund + response = @gateway.refund(@amount, '0000') + assert_failure response + assert_match %r{Transaction Id 0 does not exist}, response.message + end + + def test_successful_credit + response = @gateway.credit(@amount, @credit_card, @options) + assert_success response + assert_equal 'Approved', response.message + end + + def test_failed_credit + amount = 0 + response = @gateway.credit(amount, @credit_card, @options) + assert_failure response + assert_equal 'Amount Base must be greater than 0.|', response.message + end + + def test_successful_void + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + assert void = @gateway.void(auth.authorization) + assert_success void + assert_equal 'Approved', void.message + end + + def test_failed_void + response = @gateway.void('invalid_authorization') + assert_failure response + assert_equal '404 Not Found|', response.message + end + + def test_successful_verify + response = @gateway.verify(@credit_card, @options) + assert_success response + assert_equal 'Approved', response.message + end + + def test_failed_verify + credit_card = credit_card('444433332222111') + response = @gateway.verify(credit_card, @options) + assert_failure response + assert_match %r{Card Number is not a valid card number}, response.message + end + + def test_successful_store_and_purchase + response = @gateway.store(@credit_card, @options) + assert_success response + assert response.authorization + assert_equal 'Approved', response.message + + response = @gateway.purchase(@amount, response.authorization, @options) + assert_success response + assert_equal 'Approved', response.message + end + + def test_failed_store + credit_card = credit_card('444433332222111') + response = @gateway.store(credit_card, @options) + assert_failure response + assert_match %r{Card Number is not a valid card number}, response.message + end + + def test_transcript_scrubbing + assert @gateway.supports_scrubbing? + + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @credit_card, @options) + end + transcript = @gateway.scrub(transcript) + + assert_scrubbed(@credit_card.number, transcript) + assert_scrubbed(@credit_card.verification_value, transcript) + assert_scrubbed(@gateway.options[:api_key], transcript) + end +end diff --git a/test/remote/gateways/remote_pay_secure_test.rb b/test/remote/gateways/remote_pay_secure_test.rb index 8dd8837adc1..ee153752bb9 100644 --- a/test/remote/gateways/remote_pay_secure_test.rb +++ b/test/remote/gateways/remote_pay_secure_test.rb @@ -4,15 +4,15 @@ class RemotePaySecureTest < Test::Unit::TestCase def setup @gateway = PaySecureGateway.new(fixtures(:pay_secure)) - + @credit_card = credit_card('4000100011112224') - @options = { + @options = { :billing_address => address, :order_id => generate_unique_id } @amount = 100 end - + def test_successful_purchase assert response = @gateway.purchase(@amount, @credit_card, @options) assert_success response @@ -26,7 +26,7 @@ def test_unsuccessful_purchase assert_equal 'Declined, card expired', response.message assert_failure response end - + def test_invalid_login gateway = PaySecureGateway.new( :login => '', diff --git a/test/remote/gateways/remote_paybox_direct_test.rb b/test/remote/gateways/remote_paybox_direct_test.rb index e1ccf73ad21..25c03d67804 100644 --- a/test/remote/gateways/remote_paybox_direct_test.rb +++ b/test/remote/gateways/remote_paybox_direct_test.rb @@ -3,22 +3,21 @@ require 'test_helper' class RemotePayboxDirectTest < Test::Unit::TestCase - def setup @gateway = PayboxDirectGateway.new(fixtures(:paybox_direct)) - + @amount = 100 @credit_card = credit_card('1111222233334444') @declined_card = credit_card('1111222233334445') - - @options = { + + @options = { :order_id => '1', :billing_address => address, :description => 'Store Purchase' } end - + def test_successful_purchase assert response = @gateway.purchase(@amount, @credit_card, @options) assert_success response @@ -28,7 +27,7 @@ def test_successful_purchase def test_unsuccessful_purchase assert response = @gateway.purchase(@amount, @declined_card, @options) assert_failure response - assert_equal "PAYBOX : Numéro de porteur invalide", response.message + assert_equal "PAYBOX : Num\xE9ro de porteur invalide".force_encoding('ASCII-8BIT'), response.message end def test_authorize_and_capture @@ -40,7 +39,7 @@ def test_authorize_and_capture assert capture = @gateway.capture(amount, auth.authorization, :order_id => '1') assert_success capture end - + def test_purchase_and_void assert purchase = @gateway.purchase(@amount, @credit_card, @options) assert_success purchase @@ -55,9 +54,9 @@ def test_purchase_and_void def test_failed_capture assert response = @gateway.capture(@amount, '', :order_id => '1') assert_failure response - assert_equal "Mandatory values missing keyword:13 Type:1", response.message + assert_equal 'Invalid data', response.message end - + def test_purchase_and_partial_credit assert purchase = @gateway.purchase(@amount, @credit_card, @options) assert_success purchase @@ -67,15 +66,47 @@ def test_purchase_and_partial_credit assert_equal 'The transaction was approved', credit.message assert_success credit end - + + def test_successful_refund + assert purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + + assert refund = @gateway.refund(@amount, purchase.authorization, order_id: '1') + assert_success refund + end + + def test_partial_refund + assert purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + + assert refund = @gateway.refund(@amount/2, purchase.authorization, order_id: '1') + assert_success refund + end + + def test_failed_refund + refund = @gateway.refund(@amount, '', order_id: '2') + assert_failure refund + assert_equal 'Invalid data', refund.message + end def test_invalid_login gateway = PayboxDirectGateway.new( - :login => '199988899', - :password => '1999888F' + login: '199988899', + password: '1999888F', + rang: 100 + ) + assert response = gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_equal 'Non autorise', response.message + end + + def test_invalid_login_without_rang + gateway = PayboxDirectGateway.new( + login: '199988899', + password: '1999888F' ) assert response = gateway.purchase(@amount, @credit_card, @options) assert_failure response - assert_equal "PAYBOX : Accès refusé ou site/rang/clé invalide", response.message + assert_equal 'Non autorise', response.message end end diff --git a/test/remote/gateways/remote_payeezy_test.rb b/test/remote/gateways/remote_payeezy_test.rb new file mode 100644 index 00000000000..789e500a456 --- /dev/null +++ b/test/remote/gateways/remote_payeezy_test.rb @@ -0,0 +1,330 @@ +require 'test_helper' + +class RemotePayeezyTest < Test::Unit::TestCase + def setup + @gateway = PayeezyGateway.new(fixtures(:payeezy)) + @credit_card = credit_card + @bad_credit_card = credit_card('4111111111111113') + @check = check + @amount = 100 + @reversal_id = "REV-#{SecureRandom.random_number(1000000)}" + @options = { + :billing_address => address, + :merchant_ref => 'Store Purchase', + :ta_token => 'NOIW' + } + @options_mdd = { + soft_descriptors: { + dba_name: 'Caddyshack', + street: '1234 Any Street', + city: 'Durham', + region: 'North Carolina', + mid: 'mid_1234', + mcc: 'mcc_5678', + postal_code: '27701', + country_code: 'US', + merchant_contact_info: '8885551212' + } + } + @options_stored_credentials = { + cardbrand_original_transaction_id: 'abc123', + sequence: 'FIRST', + is_scheduled: true, + initiator: 'MERCHANT', + auth_type_override: 'A' + } + end + + def test_successful_store + assert response = @gateway.store(@credit_card, @options) + assert_success response + assert_equal 'Token successfully created.', response.message + assert response.authorization + end + + def test_successful_store_and_purchase + assert response = @gateway.store(@credit_card, @options) + assert_success response + assert !response.authorization.blank? + assert purchase = @gateway.purchase(@amount, response.authorization, @options) + assert_success purchase + end + + def test_unsuccessful_store + assert response = @gateway.store(@bad_credit_card, @options) + assert_failure response + assert_equal 'The credit card number check failed', response.message + end + + def test_successful_purchase + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_match(/Transaction Normal/, response.message) + assert_success response + end + + def test_successful_purchase_with_echeck + options = @options.merge({customer_id_type: '1', customer_id_number: '1', client_email: 'test@example.com'}) + assert response = @gateway.purchase(@amount, @check, options) + assert_match(/Transaction Normal/, response.message) + assert_success response + end + + def test_successful_purchase_with_soft_descriptors + assert response = @gateway.purchase(@amount, @credit_card, @options.merge(@options_mdd)) + assert_match(/Transaction Normal/, response.message) + assert_success response + end + + def test_successful_purchase_with_stored_credentials + assert response = @gateway.purchase(@amount, @credit_card, @options.merge(@options_stored_credentials)) + assert_match(/Transaction Normal/, response.message) + assert_success response + end + + def test_failed_purchase + @amount = 501300 + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_match(/Transaction not approved/, response.message) + assert_failure response + end + + def test_authorize_and_capture + assert auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + assert auth.authorization + assert capture = @gateway.capture(@amount, auth.authorization) + assert_success capture + end + + def test_successful_store_and_auth_and_capture + assert response = @gateway.store(@credit_card, @options) + assert_success response + + assert auth = @gateway.authorize(@amount, response.authorization, @options) + assert_success auth + assert auth.authorization + + assert capture = @gateway.capture(@amount, auth.authorization) + assert_success capture + end + + def test_failed_authorize + @amount = 501300 + assert auth = @gateway.authorize(@amount, @credit_card, @options) + assert_failure auth + assert auth.authorization + end + + def test_partial_capture + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + assert capture = @gateway.capture(@amount-1, auth.authorization) + assert_success capture + end + + def test_failed_capture + assert response = @gateway.capture(@amount, '1|1') + assert_failure response + end + + def test_successful_refund + assert purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_match(/Transaction Normal/, purchase.message) + assert_success purchase + + assert response = @gateway.refund(50, purchase.authorization) + assert_success response + assert_match(/Transaction Normal/, response.message) + assert response.authorization + end + + def test_successful_refund_with_echeck + assert purchase = @gateway.purchase(@amount, @check, @options) + assert_match(/Transaction Normal/, purchase.message) + assert_success purchase + + assert response = @gateway.refund(50, purchase.authorization) + assert_success response + assert_match(/Transaction Normal/, response.message) + assert response.authorization + end + + def test_successful_refund_with_stored_card + response = @gateway.store(@credit_card, @options) + assert_success response + + assert purchase = @gateway.purchase(@amount, response.authorization, @options) + assert_match(/Transaction Normal/, purchase.message) + assert_success purchase + + assert response = @gateway.refund(50, purchase.authorization) + assert_success response + assert_match(/Transaction Normal/, response.message) + assert response.authorization + end + + def test_partial_refund + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + + assert refund = @gateway.refund(@amount-1, purchase.authorization) + assert_success refund + end + + def test_failed_refund + assert purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_match(/Transaction Normal/, purchase.message) + assert_success purchase + + assert response = @gateway.refund(50, 'bad-authorization') + assert_failure response + assert_match(/The transaction tag is not provided/, response.message) + assert response.authorization + end + + def test_successful_void + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + assert void = @gateway.void(auth.authorization) + assert_success void + assert_equal 'Transaction Normal - Approved', void.message + end + + def test_successful_auth_void_with_reversal_id + auth = @gateway.authorize(@amount, @credit_card, @options.merge(reversal_id: @reversal_id)) + assert_success auth + + assert void = @gateway.void(auth.authorization, reversal_id: @reversal_id) + assert_success void + assert_equal 'Transaction Normal - Approved', void.message + end + + def test_successful_void_purchase_with_reversal_id + response = @gateway.purchase(@amount, @credit_card, @options.merge(reversal_id: @reversal_id)) + assert_success response + + assert void = @gateway.void(response.authorization, reversal_id: @reversal_id) + assert_success void + assert_equal 'Transaction Normal - Approved', void.message + end + + def test_successful_void_with_stored_card_and_reversal_id + response = @gateway.store(@credit_card, @options) + assert_success response + + auth = @gateway.authorize(@amount, response.authorization, @options.merge(reversal_id: @reversal_id)) + assert_success auth + + assert void = @gateway.void(auth.authorization, reversal_id: @reversal_id) + assert_success void + assert_equal 'Transaction Normal - Approved', void.message + end + + def test_successful_void_with_stored_card + response = @gateway.store(@credit_card, @options) + assert_success response + + auth = @gateway.authorize(@amount, response.authorization, @options) + assert_success auth + + assert void = @gateway.void(auth.authorization) + assert_success void + assert_equal 'Transaction Normal - Approved', void.message + end + + def test_failed_void + response = @gateway.void('') + assert_failure response + assert_equal 'The transaction id is not provided', response.message + end + + def test_successful_verify + response = @gateway.verify(@credit_card, @options) + assert_success response + assert_match %r{Transaction Normal - Approved}, response.message + end + + def test_failed_verify + response = @gateway.verify(@bad_credit_card, @options) + assert_failure response + assert_match %r{The credit card number check failed}, response.message + end + + def test_bad_creditcard_number + assert response = @gateway.purchase(@amount, @bad_credit_card, @options) + assert_failure response + assert_equal response.error_code, 'invalid_card_number' + end + + def test_invalid_login + gateway = PayeezyGateway.new(apikey: 'NotARealUser', apisecret: 'NotARealPassword', token: 'token') + assert response = gateway.purchase(@amount, @credit_card, @options) + assert_match %r{Invalid Api Key}, response.message + assert_failure response + end + + def test_response_contains_cvv_and_avs_results + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal 'M', response.cvv_result['code'] + assert_equal '4', response.avs_result['code'] + end + + def test_trans_error + # ask for error 42 (unable to send trans) as the cents bit... + @amount = 500042 + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_match(/Server Error/, response.message) # 42 is 'unable to send trans' + assert_failure response + assert_equal '500', response.error_code + end + + def test_transcript_scrubbing_store + transcript = capture_transcript(@gateway) do + @gateway.store(@credit_card, @options) + end + + transcript = @gateway.scrub(transcript) + assert_scrubbed(@credit_card.number, transcript) + assert_scrubbed(@credit_card.verification_value, transcript) + assert_scrubbed(@gateway.options[:token], transcript) + assert_scrubbed(@gateway.options[:apikey], transcript) + end + + def test_transcript_scrubbing_store_with_missing_ta_token + transcript = capture_transcript(@gateway) do + @options.delete(:ta_token) + @gateway.store(@credit_card, @options) + end + + transcript = @gateway.scrub(transcript) + assert_scrubbed(@credit_card.number, transcript) + assert_scrubbed(@credit_card.verification_value, transcript) + assert_scrubbed(@gateway.options[:token], transcript) + assert_scrubbed(@gateway.options[:apikey], transcript) + end + + def test_transcript_scrubbing + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @credit_card, @options) + end + transcript = @gateway.scrub(transcript) + + assert_scrubbed(@credit_card.number, transcript) + assert_scrubbed(@credit_card.verification_value, transcript) + assert_scrubbed(@gateway.options[:token], transcript) + end + + def test_transcript_scrubbing_echeck + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @check, @options) + end + transcript = @gateway.scrub(transcript) + + assert_scrubbed(@check.account_number, transcript) + assert_scrubbed(@check.routing_number, transcript) + assert_scrubbed(@gateway.options[:token], transcript) + end +end diff --git a/test/remote/gateways/remote_payex_test.rb b/test/remote/gateways/remote_payex_test.rb new file mode 100644 index 00000000000..fd37ddb6164 --- /dev/null +++ b/test/remote/gateways/remote_payex_test.rb @@ -0,0 +1,119 @@ +require 'test_helper' + +class RemotePayexTest < Test::Unit::TestCase + + def setup + @gateway = PayexGateway.new(fixtures(:payex)) + + @amount = 1000 + @credit_card = credit_card('4581090329655682') + @declined_card = credit_card('4000300011112220') + + @options = { + :order_id => '1234', + } + end + + def test_successful_purchase + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal 'OK', response.message + end + + def test_unsuccessful_purchase + assert response = @gateway.purchase(@amount, @declined_card, @options) + assert_failure response + # we can't test for a message since the messages vary so much + assert_not_equal 'OK', response.message + end + + def test_authorize_and_capture + amount = @amount + assert response = @gateway.authorize(amount, @credit_card, @options) + assert_success response + assert_equal 'OK', response.message + + assert response.authorization + assert response = @gateway.capture(amount, response.authorization) + assert_success response + end + + def test_failed_capture + assert response = @gateway.capture(@amount, '1') + assert_failure response + assert_not_equal 'OK', response.message + assert_not_equal 'RecordNotFound', response.params[:status_errorcode] + end + + def test_authorization_and_void + assert response = @gateway.authorize(@amount, @credit_card, @options) + assert_success response + + assert response = @gateway.void(response.authorization) + assert_success response + end + + def test_unsuccessful_void + assert response = @gateway.void('1') + assert_failure response + assert_not_equal 'OK', response.message + assert_match %r{1}, response.message + end + + def test_successful_refund + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert response.authorization + assert response = @gateway.refund(@amount - 200, response.authorization, order_id: '123') + assert_success response + end + + def test_unsuccessful_refund + assert response = @gateway.refund(@amount, '1', order_id: '123') + assert_failure response + assert_not_equal 'OK', response.message + assert_match %r{1}, response.message + end + + def test_successful_store_and_purchase + assert response = @gateway.store(@credit_card, @options) + assert_success response + assert_equal 'OK', response.message + + assert response = @gateway.purchase(@amount, response.authorization, @options.merge({order_id: '5678'})) + assert_success response + assert_equal 'OK', response.message + end + + def test_successful_store_and_authorize_and_capture + assert response = @gateway.store(@credit_card, @options) + assert_success response + assert_equal 'OK', response.message + + assert response = @gateway.authorize(@amount, response.authorization, @options.merge({order_id: '5678'})) + assert_success response + assert_equal 'OK', response.message + assert response.authorization + + assert response = @gateway.capture(@amount, response.authorization) + assert_success response + end + + def test_successful_unstore + assert response = @gateway.store(@credit_card, @options) + assert_equal 'OK', response.message + assert response = @gateway.unstore(response.authorization) + assert_success response + assert_equal 'OK', response.message + end + + def test_invalid_login + gateway = PayexGateway.new( + :account => '1', + :encryption_key => '1' + ) + assert response = gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_not_equal 'OK', response.message + end +end diff --git a/test/remote/gateways/remote_payflow_express_test.rb b/test/remote/gateways/remote_payflow_express_test.rb index 19eac916479..827907d02d9 100644 --- a/test/remote/gateways/remote_payflow_express_test.rb +++ b/test/remote/gateways/remote_payflow_express_test.rb @@ -2,23 +2,25 @@ class RemotePayflowExpressTest < Test::Unit::TestCase def setup - Base.gateway_mode = :test - + Base.mode = :test + @gateway = PayflowExpressGateway.new(fixtures(:payflow)) - @options = { :billing_address => { - :name => 'Cody Fauser', - :address1 => '1234 Shady Brook Lane', - :city => 'Ottawa', - :state => 'ON', - :country => 'CA', - :zip => '90210', - :phone => '555-555-5555' - }, - :email => 'cody@example.com' - } + @options = { + :billing_address => + { + :name => 'Cody Fauser', + :address1 => '1234 Shady Brook Lane', + :city => 'Ottawa', + :state => 'ON', + :country => 'CA', + :zip => '90210', + :phone => '555-555-5555' + }, + :email => 'cody@example.com' + } end - + # Only works with a Payflow 2.0 account or by requesting the addition # of Express checkout to an existing Payflow Pro account. This can be done # by contacting Payflow sales. The PayPal account used must be a business @@ -35,7 +37,7 @@ def test_set_express_authorization assert response.test? assert !response.params['token'].blank? end - + def test_set_express_purchase @options.update( :return_url => 'http://example.com', @@ -50,66 +52,81 @@ def test_set_express_purchase def test_setup_authorization_discount_taxes_included_free_shipping amount = 2518 - options = {:ip=>"127.0.0.1", - :return_url=>"http://localhost:3000/orders/1/8e06ea26f8add7608671d433f13c2193/commit_paypal?buyer_accepts_marketing=true&utm_nooverride=1", - :cancel_return_url=>"http://localhost:3000/orders/1/8e06ea26f8add7608671d433f13c2193/commit_paypal?utm_nooverride=1", - :customer=>"test6@test.com", - :email=>"test6@test.com", - :order_id=>"#1092", - :currency=>"USD", + options = { + :ip=>'127.0.0.1', + :return_url=>'http://localhost:3000/orders/1/8e06ea26f8add7608671d433f13c2193/commit_paypal?buyer_accepts_marketing=true&utm_nooverride=1', + :cancel_return_url=>'http://localhost:3000/orders/1/8e06ea26f8add7608671d433f13c2193/commit_paypal?utm_nooverride=1', + :customer=>'test6@test.com', + :email=>'test6@test.com', + :order_id=>'#1092', + :currency=>'USD', :subtotal=>2798, :items => [ - {:name => "test4", - :description => "test4", - :quantity=>2 , - :amount=> 1399 , - :url=>"http://localhost:3000/products/test4"}], + { + :name => 'test4', + :description => 'test4', + :quantity=>2, + :amount=> 1399, + :url=>'http://localhost:3000/products/test4' + } + ], :discount=>280, - :no_shipping=>true} + :no_shipping=>true + } response = @gateway.setup_authorization(amount, options) assert response.success?, response.message end def test_setup_authorization_with_discount_taxes_additional amount = 2518 - options = {:ip=>"127.0.0.1", - :return_url=>"http://localhost:3000/orders/1/8e06ea26f8add7608671d433f13c2193/commit_paypal?buyer_accepts_marketing=true&utm_nooverride=1", - :cancel_return_url=>"http://localhost:3000/orders/1/8e06ea26f8add7608671d433f13c2193/commit_paypal?utm_nooverride=1", - :customer=>"test6@test.com", - :email=>"test6@test.com", - :order_id=>"#1092", - :currency=>"USD", + options = { + :ip=>'127.0.0.1', + :return_url=>'http://localhost:3000/orders/1/8e06ea26f8add7608671d433f13c2193/commit_paypal?buyer_accepts_marketing=true&utm_nooverride=1', + :cancel_return_url=>'http://localhost:3000/orders/1/8e06ea26f8add7608671d433f13c2193/commit_paypal?utm_nooverride=1', + :customer=>'test6@test.com', + :email=>'test6@test.com', + :order_id=>'#1092', + :currency=>'USD', :subtotal=>2798, :items => [ - {:name => "test4", - :description => "test4", - :quantity=>2 , - :amount=> 1399 , - :url=>"http://localhost:3000/products/test4"}], + { + :name => 'test4', + :description => 'test4', + :quantity=>2, + :amount=> 1399, + :url=>'http://localhost:3000/products/test4' + } + ], :discount=>280, - :no_shipping=>true} + :no_shipping=>true + } response = @gateway.setup_authorization(amount, options) assert response.success?, response.message end def test_setup_authorization_with_discount_taxes_and_shipping_addtiional amount = 2518 - options = {:ip=>"127.0.0.1", - :return_url=>"http://localhost:3000/orders/1/8e06ea26f8add7608671d433f13c2193/commit_paypal?buyer_accepts_marketing=true&utm_nooverride=1", - :cancel_return_url=>"http://localhost:3000/orders/1/8e06ea26f8add7608671d433f13c2193/commit_paypal?utm_nooverride=1", - :customer=>"test6@test.com", - :email=>"test6@test.com", - :order_id=>"#1092", - :currency=>"USD", + options = { + :ip=>'127.0.0.1', + :return_url=>'http://localhost:3000/orders/1/8e06ea26f8add7608671d433f13c2193/commit_paypal?buyer_accepts_marketing=true&utm_nooverride=1', + :cancel_return_url=>'http://localhost:3000/orders/1/8e06ea26f8add7608671d433f13c2193/commit_paypal?utm_nooverride=1', + :customer=>'test6@test.com', + :email=>'test6@test.com', + :order_id=>'#1092', + :currency=>'USD', :subtotal=>2798, :items => [ - {:name => "test4", - :description => "test4", - :quantity=>2 , - :amount=> 1399 , - :url=>"http://localhost:3000/products/test4"}], + { + :name => 'test4', + :description => 'test4', + :quantity=>2, + :amount=> 1399, + :url=>'http://localhost:3000/products/test4' + } + ], :discount=>280, - :no_shipping=>false} + :no_shipping=>false + } response = @gateway.setup_authorization(amount, options) assert response.success?, response.message end @@ -123,69 +140,81 @@ def setup def test_setup_authorization_discount_taxes_included_free_shipping amount = 2518 - options = {:ip=>"127.0.0.1", - :return_url=>"http://localhost:3000/orders/1/8e06ea26f8add7608671d433f13c2193/commit_paypal?buyer_accepts_marketing=true&utm_nooverride=1", - :cancel_return_url=>"http://localhost:3000/orders/1/8e06ea26f8add7608671d433f13c2193/commit_paypal?utm_nooverride=1", - :customer=>"test6@test.com", - :email=>"test6@test.com", - :order_id=>"#1092", - :currency=>"GBP", + options = { + :ip=>'127.0.0.1', + :return_url=>'http://localhost:3000/orders/1/8e06ea26f8add7608671d433f13c2193/commit_paypal?buyer_accepts_marketing=true&utm_nooverride=1', + :cancel_return_url=>'http://localhost:3000/orders/1/8e06ea26f8add7608671d433f13c2193/commit_paypal?utm_nooverride=1', + :customer=>'test6@test.com', + :email=>'test6@test.com', + :order_id=>'#1092', + :currency=>'GBP', :subtotal=>2798, :items=> [ - {:name=>"test4", - :description=>"test4", + { + :name=>'test4', + :description=>'test4', :quantity=>2, :amount=>1399, - :url=>"http://localhost:3000/products/test4"}, - ], + :url=>'http://localhost:3000/products/test4' + } + ], :discount=>280, - :no_shipping=>true} + :no_shipping=>true + } response = @gateway.setup_authorization(amount, options) assert response.success?, response.message end def test_setup_authorization_with_discount_taxes_additional amount = 2518 - options = {:ip=>"127.0.0.1", - :return_url=>"http://localhost:3000/orders/1/8e06ea26f8add7608671d433f13c2193/commit_paypal?buyer_accepts_marketing=true&utm_nooverride=1", - :cancel_return_url=>"http://localhost:3000/orders/1/8e06ea26f8add7608671d433f13c2193/commit_paypal?utm_nooverride=1", - :customer=>"test6@test.com", - :email=>"test6@test.com", - :order_id=>"#1092", - :currency=>"GBP", + options = { + :ip=>'127.0.0.1', + :return_url=>'http://localhost:3000/orders/1/8e06ea26f8add7608671d433f13c2193/commit_paypal?buyer_accepts_marketing=true&utm_nooverride=1', + :cancel_return_url=>'http://localhost:3000/orders/1/8e06ea26f8add7608671d433f13c2193/commit_paypal?utm_nooverride=1', + :customer=>'test6@test.com', + :email=>'test6@test.com', + :order_id=>'#1092', + :currency=>'GBP', :subtotal=>2798, :items=> [ - {:name=>"test4", - :description=>"test4", + { + :name=>'test4', + :description=>'test4', :quantity=>2, :amount=>1399, - :url=>"http://localhost:3000/products/test4"}, - ], + :url=>'http://localhost:3000/products/test4' + } + ], :discount=>280, - :no_shipping=>true} + :no_shipping=>true + } response = @gateway.setup_authorization(amount, options) assert response.success?, response.message end def test_setup_authorization_with_discount_taxes_and_shipping_addtiional amount = 2518 - options = {:ip=>"127.0.0.1", - :return_url=>"http://localhost:3000/orders/1/8e06ea26f8add7608671d433f13c2193/commit_paypal?buyer_accepts_marketing=true&utm_nooverride=1", - :cancel_return_url=>"http://localhost:3000/orders/1/8e06ea26f8add7608671d433f13c2193/commit_paypal?utm_nooverride=1", - :customer=>"test6@test.com", - :email=>"test6@test.com", - :order_id=>"#1092", - :currency=>"GBP", + options = { + :ip=>'127.0.0.1', + :return_url=>'http://localhost:3000/orders/1/8e06ea26f8add7608671d433f13c2193/commit_paypal?buyer_accepts_marketing=true&utm_nooverride=1', + :cancel_return_url=>'http://localhost:3000/orders/1/8e06ea26f8add7608671d433f13c2193/commit_paypal?utm_nooverride=1', + :customer=>'test6@test.com', + :email=>'test6@test.com', + :order_id=>'#1092', + :currency=>'GBP', :subtotal=>2798, :items=> [ - {:name=>"test4", - :description=>"test4", + { + :name=>'test4', + :description=>'test4', :quantity=>2, :amount=>1399, - :url=>"http://localhost:3000/products/test4"}, - ], + :url=>'http://localhost:3000/products/test4' + } + ], :discount=>280, - :no_shipping=>false} + :no_shipping=>false + } response = @gateway.setup_authorization(amount, options) assert response.success?, response.message end diff --git a/test/remote/gateways/remote_payflow_test.rb b/test/remote/gateways/remote_payflow_test.rb index b3d49a939d3..e1370e4d4ce 100644 --- a/test/remote/gateways/remote_payflow_test.rb +++ b/test/remote/gateways/remote_payflow_test.rb @@ -2,38 +2,157 @@ class RemotePayflowTest < Test::Unit::TestCase def setup - ActiveMerchant::Billing::Base.gateway_mode = :test + Base.mode = :test @gateway = PayflowGateway.new(fixtures(:payflow)) - - @credit_card = credit_card('5105105105105100', + + @credit_card = credit_card( + '5105105105105100', :brand => 'master' ) - @options = { :billing_address => address, - :email => 'cody@example.com', - :customer => 'codyexample' - } + @options = { + :billing_address => address, + :email => 'cody@example.com', + :customer => 'codyexample' + } + + @extra_options = { + :order_id => '123', + :description => 'Description string', + :order_desc => 'OrderDesc string', + :comment => 'Comment string', + :comment2 => 'Comment2 string' + } + + @check = check( + :routing_number => '111111118', + :account_number => '1111111111' + ) + + @l2_json = '{ + "Tender": { + "ACH": { + "AcctType": "C", + "AcctNum": "6355059797", + "ABA": "021000021" + } + } + }' + + @l3_json = '{ + "Invoice": { + "Date": "20190104", + "Level3Invoice": { + "CountyTax": {"Amount": "3.23"} + }, + "Items": + "<Item Number=\"1\"><SKU>1111</SKU><UPC>9999</UPC><Description>Widget</Description><Quantity>2</Quantity><UnitOfMeasurement>INQ</UnitOfMeasurement><UnitPrice>49.99</UnitPrice><DiscountAmt>9.98</DiscountAmt><FreightAmt>3.00</FreightAmt><HandlingAmt>8.00</HandlingAmt><TotalAmt>101.00</TotalAmt><PickUp> <Address> <Street>500 Main St.</Street><City>Anytown</City><State>NY</State><Zip>67890</Zip><Country>US</Country></Address><Time>15:30</Time><Date>20030630</Date><RecordNumber>24680</RecordNumber></PickUp><TrackingNumber>ABC0123</TrackingNumber><Delivery><Date>20030714</Date><Time>12:00</Time></Delivery><UNSPSCCode>54.10.15.05</UNSPSCCode></Item><Item Number=\"2\"><SKU>2222</SKU><UPC>8888</UPC><Description>Gizmo</Description><Quantity>5</Quantity><UnitOfMeasurement>INQ</UnitOfMeasurement><UnitPrice>9.99</UnitPrice><DiscountAmt>2.50</DiscountAmt><FreightAmt>3.00</FreightAmt><HandlingAmt>2.50</HandlingAmt><TotalAmt>52.95</TotalAmt><PickUp> <Address> <Street>500 Main St.</Street><City>Anytown</City><State>NY</State><Zip>67890</Zip><Country>US</Country></Address><Time>09:00</Time><Date>20030628</Date><RecordNumber>13579</RecordNumber></PickUp><TrackingNumber>XYZ7890</TrackingNumber><Delivery><Date>20030711</Date><Time>09:00</Time></Delivery><UNSPSCCode>54.10.16.05</UNSPSCCode></Item>" + } + }' end - + def test_successful_purchase assert response = @gateway.purchase(100000, @credit_card, @options) - assert_equal "Approved", response.message + assert_equal 'Approved', response.message assert_success response assert response.test? assert_not_nil response.authorization + assert !response.fraud_review? end - + + def test_successful_purchase_with_extra_options + assert response = @gateway.purchase(100000, @credit_card, @options.merge(@extra_options)) + assert_equal 'Approved', response.message + assert_success response + assert response.test? + assert_not_nil response.authorization + assert !response.fraud_review? + end + + # In order for this remote test to pass, you must go into your Payflow test + # backend and enable the correct filter. Once logged in: + # "Service Settings" -> + # "Fraud Protection" -> + # "Test Setup" -> + # "Edit Standard Filters" -> + # Check "BIN Risk List Match" filter *only*, set to "Review" -> + # "Deploy" -> + # WAIT AT LEAST AN HOUR. FOR REALZ. + def test_successful_purchase_with_fraud_review + assert response = @gateway.purchase( + 100000, + credit_card('5555555555554444', verification_value: '') + ) + assert_success response, 'This is probably failing due to your Payflow test account not being set up for fraud filters.' + assert_equal '126', response.params['result'] + assert response.fraud_review? + end + + def test_successful_purchase_with_l2_fields + options = @options.merge(level_two_fields: @l2_json) + + assert response = @gateway.purchase(100000, @credit_card, options) + assert_equal 'Approved', response.message + assert_success response + assert response.test? + assert_not_nil response.authorization + end + + def test_successful_purchase_with_l3_fields + options = @options.merge(level_three_fields: @l3_json) + + assert response = @gateway.purchase(100000, @credit_card, options) + assert_equal 'Approved', response.message + assert_success response + assert response.test? + assert_not_nil response.authorization + end + + def test_successful_purchase_with_l2_l3_fields + options = @options.merge(level_two_fields: @l2_json).merge(level_three_fields: @l3_json) + + assert response = @gateway.purchase(100000, @credit_card, options) + assert_equal 'Approved', response.message + assert_success response + assert response.test? + assert_not_nil response.authorization + end + def test_declined_purchase assert response = @gateway.purchase(210000, @credit_card, @options) assert_equal 'Declined', response.message assert_failure response assert response.test? end - + + # Additional steps are required to enable ACH in a Payflow Pro account. + # See the "Payflow ACH Payment Service Guide" for more details: + # http://www.paypalobjects.com/webstatic/en_US/developer/docs/pdf/pp_achpayment_guide.pdf + # + # Also, when testing against the pilot-payflowpro.paypal.com endpoint, ACH must be enabled by Payflow support. + # This can be accomplished by sending an email to payflow-support@paypal.com with your Merchant Login. + def test_successful_ach_purchase + assert response = @gateway.purchase(50, @check) + assert_success response, 'This is probably failing due to your Payflow test account not being set up for ACH.' + assert_equal 'Approved', response.message + assert response.test? + assert_not_nil response.authorization + end + + def test_ach_purchase_and_refund + assert response = @gateway.purchase(50, @check) + assert_success response + assert_equal 'Approved', response.message + assert !response.authorization.blank? + + assert credit = @gateway.refund(50, response.authorization) + assert_success credit + end + def test_successful_authorization assert response = @gateway.authorize(100, @credit_card, @options) - assert_equal "Approved", response.message + assert_equal 'Approved', response.message assert_success response assert response.test? assert_not_nil response.authorization @@ -47,23 +166,58 @@ def test_authorize_and_capture assert capture = @gateway.capture(100, auth.authorization) assert_success capture end - + + def test_authorize_and_capture_with_three_d_secure_option + assert auth = @gateway.authorize(100, @credit_card, @options.merge(three_d_secure_option)) + assert_success auth + assert_equal 'Approved', auth.message + assert auth.authorization + assert capture = @gateway.capture(100, auth.authorization) + assert_success capture + end + def test_authorize_and_partial_capture assert auth = @gateway.authorize(100 * 2, @credit_card, @options) assert_success auth assert_equal 'Approved', auth.message assert auth.authorization - + + assert capture = @gateway.capture(100, auth.authorization) + assert_success capture + end + + def test_authorize_and_complete_capture + assert auth = @gateway.authorize(100 * 2, @credit_card, @options) + assert_success auth + assert_equal 'Approved', auth.message + assert auth.authorization + + assert capture = @gateway.capture(100, auth.authorization, :capture_complete => 'Y') + assert_success capture + + assert capture = @gateway.capture(100, auth.authorization) + assert_failure capture + end + + def test_authorize_and_uncomplete_capture + assert auth = @gateway.authorize(100 * 2, @credit_card, @options) + assert_success auth + assert_equal 'Approved', auth.message + assert auth.authorization + + assert capture = @gateway.capture(100, auth.authorization, :capture_complete => 'N') + assert_success capture + assert capture = @gateway.capture(100, auth.authorization) assert_success capture end - + def test_failed_capture assert response = @gateway.capture(100, '999') assert_failure response assert_equal 'Invalid tender', response.message end - + def test_authorize_and_void assert auth = @gateway.authorize(100, @credit_card, @options) assert_success auth @@ -72,7 +226,29 @@ def test_authorize_and_void assert void = @gateway.void(auth.authorization) assert_success void end - + + def test_successful_verify + assert response = @gateway.verify(@credit_card, @options) + assert_success response + assert_equal 'Verified', response.message + end + + def test_successful_verify_amex + @amex_credit_card = credit_card( + '378282246310005', + :brand => 'american_express' + ) + assert response = @gateway.verify(@amex_credit_card, @options) + assert_success response + assert_equal 'Approved', response.message + end + + def test_failed_verify + assert response = @gateway.verify(credit_card('4000056655665556'), @options) + assert_failure response + assert_equal 'Declined', response.message + end + def test_invalid_login gateway = PayflowGateway.new( :login => '', @@ -82,62 +258,72 @@ def test_invalid_login assert_equal 'Invalid vendor account', response.message assert_failure response end - + def test_duplicate_request_id - request_id = Digest::MD5.hexdigest(rand.to_s) - @gateway.expects(:generate_unique_id).times(2).returns(request_id) - + request_id = SecureRandom.hex(16) + SecureRandom.expects(:hex).times(2).returns(request_id) + response1 = @gateway.purchase(100, @credit_card, @options) - assert response1.success? + assert response1.success? assert_nil response1.params['duplicate'] - + response2 = @gateway.purchase(100, @credit_card, @options) assert response2.success? - assert response2.params['duplicate'] + assert response2.params['duplicate'], response2.inspect end - + def test_create_recurring_profile - response = @gateway.recurring(1000, @credit_card, :periodicity => :monthly) + response = assert_deprecation_warning(Gateway::RECURRING_DEPRECATION_MESSAGE) do + @gateway.recurring(1000, @credit_card, :periodicity => :monthly) + end assert_success response assert !response.params['profile_id'].blank? assert response.test? end - + def test_create_recurring_profile_with_invalid_date - response = @gateway.recurring(1000, @credit_card, :periodicity => :monthly, :starting_at => Time.now) + response = assert_deprecation_warning(Gateway::RECURRING_DEPRECATION_MESSAGE) do + @gateway.recurring(1000, @credit_card, :periodicity => :monthly, :starting_at => Time.now) + end assert_failure response assert_equal 'Field format error: Start or next payment date must be a valid future date', response.message assert response.params['profile_id'].blank? assert response.test? end - + def test_create_and_cancel_recurring_profile - response = @gateway.recurring(1000, @credit_card, :periodicity => :monthly) + response = assert_deprecation_warning(Gateway::RECURRING_DEPRECATION_MESSAGE) do + @gateway.recurring(1000, @credit_card, :periodicity => :monthly) + end assert_success response assert !response.params['profile_id'].blank? assert response.test? - - response = @gateway.cancel_recurring(response.params['profile_id']) + + response = assert_deprecation_warning(Gateway::RECURRING_DEPRECATION_MESSAGE) do + @gateway.cancel_recurring(response.params['profile_id']) + end assert_success response assert response.test? end - + def test_full_feature_set_for_recurring_profiles # Test add @options.update( :periodicity => :weekly, :payments => '12', :starting_at => Time.now + 1.day, - :comment => "Test Profile" + :comment => 'Test Profile' ) - response = @gateway.recurring(100, @credit_card, @options) - assert_equal "Approved", response.params['message'] - assert_equal "0", response.params['result'] + response = assert_deprecation_warning(Gateway::RECURRING_DEPRECATION_MESSAGE) do + @gateway.recurring(100, @credit_card, @options) + end + assert_equal 'Approved', response.params['message'] + assert_equal '0', response.params['result'] assert_success response assert response.test? assert !response.params['profile_id'].blank? @recurring_profile_id = response.params['profile_id'] - + # Test modify @options.update( :periodicity => :monthly, @@ -145,93 +331,156 @@ def test_full_feature_set_for_recurring_profiles :payments => '4', :profile_id => @recurring_profile_id ) - response = @gateway.recurring(400, @credit_card, @options) - assert_equal "Approved", response.params['message'] - assert_equal "0", response.params['result'] + response = assert_deprecation_warning(Gateway::RECURRING_DEPRECATION_MESSAGE) do + @gateway.recurring(400, @credit_card, @options) + end + assert_equal 'Approved', response.params['message'] + assert_equal '0', response.params['result'] assert_success response assert response.test? - + # Test inquiry - response = @gateway.recurring_inquiry(@recurring_profile_id) - assert_equal "0", response.params['result'] + response = assert_deprecation_warning(Gateway::RECURRING_DEPRECATION_MESSAGE) do + @gateway.recurring_inquiry(@recurring_profile_id) + end + assert_equal '0', response.params['result'] assert_success response assert response.test? - + # Test payment history inquiry - response = @gateway.recurring_inquiry(@recurring_profile_id, :history => true) + response = assert_deprecation_warning(Gateway::RECURRING_DEPRECATION_MESSAGE) do + @gateway.recurring_inquiry(@recurring_profile_id, :history => true) + end assert_equal '0', response.params['result'] assert_success response assert response.test? - + # Test cancel - response = @gateway.cancel_recurring(@recurring_profile_id) - assert_equal "Approved", response.params['message'] - assert_equal "0", response.params['result'] + response = assert_deprecation_warning(Gateway::RECURRING_DEPRECATION_MESSAGE) do + @gateway.cancel_recurring(@recurring_profile_id) + end + assert_equal 'Approved', response.params['message'] + assert_equal '0', response.params['result'] assert_success response assert response.test? end - + # Note that this test will only work if you enable reference transactions!! def test_reference_purchase assert response = @gateway.purchase(10000, @credit_card, @options) - assert_equal "Approved", response.message + assert_equal 'Approved', response.message assert_success response assert response.test? assert_not_nil pn_ref = response.authorization - + # now another purchase, by reference assert response = @gateway.purchase(10000, pn_ref) - assert_equal "Approved", response.message - assert_success response - assert response.test? - end - - def test_recurring_with_initial_authorization - response = @gateway.recurring(1000, @credit_card, - :periodicity => :monthly, - :initial_transaction => { - :brand => :authorization - } - ) - + assert_equal 'Approved', response.message assert_success response - assert !response.params['profile_id'].blank? assert response.test? end - + def test_recurring_with_initial_authorization - response = @gateway.recurring(1000, @credit_card, - :periodicity => :monthly, - :initial_transaction => { - :brand => :purchase, - :amount => 500 - } - ) - + response = assert_deprecation_warning(Gateway::RECURRING_DEPRECATION_MESSAGE) do + @gateway.recurring(1000, @credit_card, + :periodicity => :monthly, + :initial_transaction => { + :type => :purchase, + :amount => 500 + } + ) + end + assert_success response assert !response.params['profile_id'].blank? assert response.test? end - - def test_purchase_and_referenced_credit + + def test_purchase_and_refund amount = 100 - + assert purchase = @gateway.purchase(amount, @credit_card, @options) assert_success purchase assert_equal 'Approved', purchase.message assert !purchase.authorization.blank? - - assert credit = @gateway.credit(amount, purchase.authorization) + + assert credit = @gateway.refund(amount, purchase.authorization) assert_success credit end - - # The default security setting for Payflow Pro accounts is Allow + + def test_verify_credentials + assert @gateway.verify_credentials + + gateway = PayflowGateway.new(login: 'unknown_login', password: 'unknown_password', partner: 'PayPal') + assert !gateway.verify_credentials + end + + def test_purchase_and_refund_with_three_d_secure_option + amount = 100 + + assert purchase = @gateway.purchase(amount, @credit_card, @options.merge(three_d_secure_option)) + assert_success purchase + assert_equal 'Approved', purchase.message + assert !purchase.authorization.blank? + + assert credit = @gateway.refund(amount, purchase.authorization) + assert_success credit + end + + # The default security setting for Payflow Pro accounts is Allow # non-referenced credits = No. # - # Non-referenced credits will fail with Result code 117 (failed the security + # Non-referenced credits will fail with Result code 117 (failed the security # check) unless Allow non-referenced credits = Yes in PayPal manager - def test_purchase_and_non_referenced_credit + def test_purchase_and_credit assert credit = @gateway.credit(100, @credit_card, @options) - assert_success credit + assert_success credit, 'This is probably failing due to your Payflow test account not being set up to allow non-referenced credits.' + end + + def test_successful_ach_credit + assert response = @gateway.credit(50, @check) + assert_success response, 'This is probably failing due to your Payflow test account not being set up for ACH.' + assert_equal 'Approved', response.message + assert response.test? + assert_not_nil response.authorization + end + + def test_high_verbosity + assert response = @gateway.purchase(100, @credit_card, @options) + assert_nil response.params['transaction_time'] + @gateway.options[:verbosity] = 'HIGH' + assert response = @gateway.purchase(100, @credit_card, @options) + assert_match %r{^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}}, response.params['transaction_time'] + end + + def three_d_secure_option + { + :three_d_secure => { + :status => 'Y', + :authentication_id => 'QvDbSAxSiaQs241899E0', + :eci => '02', + :cavv => 'jGvQIvG/5UhjAREALGYa6Vu/hto=', + :xid => 'UXZEYlNBeFNpYVFzMjQxODk5RTA=' + } + } + end + + def test_transcript_scrubbing + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @credit_card, @options) + end + transcript = @gateway.scrub(transcript) + + assert_scrubbed(@credit_card.number, transcript) + assert_scrubbed(@credit_card.verification_value, transcript) + assert_scrubbed(@gateway.options[:password], transcript) + + transcript = capture_transcript(@gateway) do + @gateway.purchase(50, @check) + end + transcript = @gateway.scrub(transcript) + + assert_scrubbed(@check.account_number, transcript) + assert_scrubbed(@gateway.options[:password], transcript) end end diff --git a/test/remote/gateways/remote_payflow_uk_test.rb b/test/remote/gateways/remote_payflow_uk_test.rb index 485411b275e..afe6791cfc8 100644 --- a/test/remote/gateways/remote_payflow_uk_test.rb +++ b/test/remote/gateways/remote_payflow_uk_test.rb @@ -2,11 +2,11 @@ class RemotePayflowUkTest < Test::Unit::TestCase def setup - ActiveMerchant::Billing::Base.gateway_mode = :test + Base.mode = :test # The default partner is PayPalUk @gateway = PayflowUkGateway.new(fixtures(:payflow_uk)) - + @creditcard = CreditCard.new( :number => '5105105105105100', :month => 11, @@ -16,28 +16,8 @@ def setup :verification_value => '000', :brand => 'master' ) - - @solo = CreditCard.new( - :brand => "solo", - :number => "6334900000000005", - :month => Time.now.month, - :year => Time.now.year + 1, - :first_name => "Test", - :last_name => "Mensch", - :issue_number => '01' - ) - - @switch = CreditCard.new( - :brand => "switch", - :number => "5641820000000005", - :verification_value => "000", - :month => 1, - :year => 2008, - :first_name => 'Fred', - :last_name => 'Brooks' - ) - - @options = { + + @options = { :billing_address => { :name => 'Cody Fauser', :address1 => '1234 Shady Brook Lane', @@ -50,72 +30,72 @@ def setup :email => 'cody@example.com' } end - + def test_successful_purchase assert response = @gateway.purchase(100000, @creditcard, @options) - assert_equal "Approved", response.message + assert_equal 'Approved', response.message assert_success response assert response.test? assert_not_nil response.authorization end - + def test_declined_purchase assert response = @gateway.purchase(210000, @creditcard, @options) assert_equal 'Failed merchant rule check', response.message assert_failure response assert response.test? end - + def test_successful_purchase_solo - assert response = @gateway.purchase(100000, @solo, @options) - assert_equal "Approved", response.message - assert_success response - assert response.test? - assert_not_nil response.authorization - end - + assert response = @gateway.purchase(100000, @solo, @options) + assert_equal 'Approved', response.message + assert_success response + assert response.test? + assert_not_nil response.authorization + end + def test_no_card_issue_or_card_start_with_switch assert response = @gateway.purchase(100000, @switch, @options) assert_failure response - - assert_equal "Field format error: CARDSTART or CARDISSUE must be present", response.message + + assert_equal 'Field format error: CARDSTART or CARDISSUE must be present', response.message assert_failure response assert response.test? end - + def test_successful_purchase_switch_with_issue_number @switch.issue_number = '01' assert response = @gateway.purchase(100000, @switch, @options) - assert_equal "Approved", response.message + assert_equal 'Approved', response.message assert_success response assert response.test? assert_not_nil response.authorization end - + def test_successful_purchase_switch_with_start_date @switch.start_month = 12 @switch.start_year = 1999 assert response = @gateway.purchase(100000, @switch, @options) - assert_equal "Approved", response.message + assert_equal 'Approved', response.message assert_success response assert response.test? assert_not_nil response.authorization end - + def test_successful_purchase_switch_with_start_date_and_issue_number @switch.issue_number = '05' @switch.start_month = 12 @switch.start_year = 1999 assert response = @gateway.purchase(100000, @switch, @options) - assert_equal "Approved", response.message + assert_equal 'Approved', response.message assert_success response assert response.test? assert_not_nil response.authorization end - + def test_successful_authorization assert response = @gateway.authorize(100, @creditcard, @options) - assert_equal "Approved", response.message + assert_equal 'Approved', response.message assert_success response assert response.test? assert_not_nil response.authorization @@ -130,13 +110,13 @@ def test_authorize_and_capture assert capture = @gateway.capture(amount, auth.authorization) assert_success capture end - + def test_failed_capture assert response = @gateway.capture(100, '999') assert_failure response assert_equal 'Invalid tender', response.message end - + def test_authorize_and_void assert auth = @gateway.authorize(100, @creditcard, @options) assert_success auth @@ -145,7 +125,7 @@ def test_authorize_and_void assert void = @gateway.void(auth.authorization) assert_success void end - + def test_invalid_login gateway = PayflowGateway.new( :login => '', @@ -155,16 +135,16 @@ def test_invalid_login assert_equal 'Invalid vendor account', response.message assert_failure response end - + def test_duplicate_request_id gateway = PayflowUkGateway.new( :login => @login, :password => @password ) - - request_id = Digest::SHA1.hexdigest(rand.to_s).slice(0,32) + + request_id = Digest::SHA1.hexdigest(rand.to_s).slice(0, 32) gateway.expects(:generate_unique_id).times(2).returns(request_id) - + response1 = gateway.purchase(100, @creditcard, @options) assert_nil response1.params['duplicate'] response2 = gateway.purchase(100, @creditcard, @options) diff --git a/test/remote/gateways/remote_payment_express_test.rb b/test/remote/gateways/remote_payment_express_test.rb index adf073e1bdb..a2d2a3b0135 100644 --- a/test/remote/gateways/remote_payment_express_test.rb +++ b/test/remote/gateways/remote_payment_express_test.rb @@ -20,26 +20,33 @@ def setup def test_successful_purchase assert response = @gateway.purchase(@amount, @credit_card, @options) assert_success response - assert_equal "The Transaction was approved", response.message + assert_equal 'The Transaction was approved', response.message assert_not_nil response.authorization end def test_successful_purchase_with_reference_id assert response = @gateway.purchase(@amount, @credit_card, @options) - assert_equal "The Transaction was approved", response.message + assert_equal 'The Transaction was approved', response.message assert_success response assert_not_nil response.authorization end + def test_successful_purchase_with_ip + assert response = @gateway.purchase(@amount, @credit_card, @options.merge(:ip => '192.168.0.1')) + assert_success response + assert_equal 'The Transaction was approved', response.message + assert_not_nil response.authorization + end + def test_declined_purchase - assert response = @gateway.purchase(@amount, credit_card("5431111111111228"), @options) + assert response = @gateway.purchase(@amount, credit_card('5431111111111228'), @options) assert_match %r{declined}i, response.message assert_failure response end def test_successful_authorization assert response = @gateway.authorize(@amount, @credit_card, @options) - assert_equal "The Transaction was approved", response.message + assert_equal 'The Transaction was approved', response.message assert_success response assert_not_nil response.authorization end @@ -59,7 +66,7 @@ def test_purchase_and_refund assert_success purchase assert_equal 'The Transaction was approved', purchase.message assert !purchase.authorization.blank? - assert refund = @gateway.refund(amount, purchase.authorization, :description => "Giving a refund") + assert refund = @gateway.refund(amount, purchase.authorization, :description => 'Giving a refund') assert_success refund end @@ -75,29 +82,28 @@ def test_invalid_login :password => '' ) assert response = gateway.purchase(@amount, @credit_card, @options) - assert_match %r{error}i, response.message + assert_match %r{Invalid Credentials}i, response.message assert_failure response end def test_store_credit_card assert response = @gateway.store(@credit_card) assert_success response - assert_equal "The Transaction was approved", response.message + assert_equal 'The Transaction was approved', response.message assert_not_nil token = response.authorization assert_equal token, response.token end def test_store_with_custom_token - token = Time.now.to_i.to_s #hehe + token = Time.now.to_i.to_s assert response = @gateway.store(@credit_card, :billing_id => token) assert_success response - assert_equal "The Transaction was approved", response.message + assert_equal 'The Transaction was approved', response.message assert_not_nil response.authorization assert_equal token, response.authorization end def test_store_invalid_credit_card - original_number = @credit_card.number @credit_card.number = 2 assert response = @gateway.store(@credit_card) @@ -107,11 +113,11 @@ def test_store_invalid_credit_card def test_store_and_charge assert response = @gateway.store(@credit_card) assert_success response - assert_equal "The Transaction was approved", response.message - assert (token = response.authorization) + assert_equal 'The Transaction was approved', response.message + assert(token = response.authorization) - assert purchase = @gateway.purchase( @amount, token) - assert_equal "The Transaction was approved", purchase.message + assert purchase = @gateway.purchase(@amount, token) + assert_equal 'The Transaction was approved', purchase.message assert_success purchase assert_not_nil purchase.authorization end @@ -119,8 +125,8 @@ def test_store_and_charge def test_store_and_authorize_and_capture assert response = @gateway.store(@credit_card) assert_success response - assert_equal "The Transaction was approved", response.message - assert (token = response.authorization) + assert_equal 'The Transaction was approved', response.message + assert(token = response.authorization) assert auth = @gateway.authorize(@amount, token, @options) assert_success auth @@ -130,4 +136,15 @@ def test_store_and_authorize_and_capture assert_success capture end + def test_transcript_scrubbing + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @credit_card, @options) + end + clean_transcript = @gateway.scrub(transcript) + + assert_scrubbed(@credit_card.number, clean_transcript) + assert_scrubbed(@credit_card.verification_value.to_s, clean_transcript) + assert_scrubbed(@gateway.options[:password], clean_transcript) + end + end diff --git a/test/remote/gateways/remote_paymentez_test.rb b/test/remote/gateways/remote_paymentez_test.rb new file mode 100644 index 00000000000..831b61b07ca --- /dev/null +++ b/test/remote/gateways/remote_paymentez_test.rb @@ -0,0 +1,239 @@ +require 'test_helper' + +class RemotePaymentezTest < Test::Unit::TestCase + def setup + @gateway = PaymentezGateway.new(fixtures(:paymentez)) + + @amount = 100 + @credit_card = credit_card('4111111111111111', verification_value: '666') + @elo_credit_card = credit_card('6362970000457013', + :month => 10, + :year => 2020, + :first_name => 'John', + :last_name => 'Smith', + :verification_value => '737', + :brand => 'elo' + ) + @declined_card = credit_card('4242424242424242', verification_value: '666') + @options = { + billing_address: address, + description: 'Store Purchase', + user_id: '998', + email: 'joe@example.com', + vat: 0, + dev_reference: 'Testing' + } + end + + def test_successful_purchase + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + end + + def test_successful_purchase_with_elo + response = @gateway.purchase(@amount, @elo_credit_card, @options) + assert_success response + end + + def test_successful_purchase_with_more_options + options = { + order_id: '1', + ip: '127.0.0.1', + tax_percentage: 0.07, + phone: '333 333 3333' + } + + response = @gateway.purchase(@amount, @credit_card, @options.merge(options)) + assert_success response + end + + def test_successful_purchase_without_phone_billing_address_option + options = { + order_id: '1', + ip: '127.0.0.1', + tax_percentage: 0.07, + billing_address: { + phone: nil + } + } + + response = @gateway.purchase(@amount, @credit_card, @options.merge(options)) + assert_success response + end + + def test_successful_purchase_without_phone_option + options = { + order_id: '1', + ip: '127.0.0.1', + tax_percentage: 0.07 + } + + response = @gateway.purchase(@amount, @credit_card, @options.merge(options)) + assert_success response + end + + def test_successful_purchase_with_extra_params + options = { + extra_params: { + configuration1: 'value1', + configuration2: 'value2', + configuration3: 'value3' + }} + + response = @gateway.purchase(@amount, @credit_card, @options.merge(options)) + assert_success response + end + + def test_successful_purchase_with_token + store_response = @gateway.store(@credit_card, @options) + assert_success store_response + token = store_response.authorization + purchase_response = @gateway.purchase(@amount, token, @options) + assert_success purchase_response + end + + def test_failed_purchase + response = @gateway.purchase(@amount, @declined_card, @options) + assert_failure response + assert_equal Gateway::STANDARD_ERROR_CODE[:card_declined], response.error_code + end + + def test_successful_refund + auth = @gateway.purchase(@amount, @credit_card, @options) + assert_success auth + + assert refund = @gateway.refund(@amount, auth.authorization, @options) + assert_success refund + end + + def test_successful_refund_with_elo + auth = @gateway.purchase(@amount, @elo_credit_card, @options) + assert_success auth + + assert refund = @gateway.refund(@amount, auth.authorization, @options) + assert_success refund + end + + def test_successful_void + auth = @gateway.purchase(@amount, @credit_card, @options) + assert_success auth + + assert void = @gateway.void(auth.authorization) + assert_success void + end + + def test_successful_void_with_elo + auth = @gateway.purchase(@amount, @elo_credit_card, @options) + assert_success auth + + assert void = @gateway.void(auth.authorization) + assert_success void + end + + def test_failed_void + response = @gateway.void('') + assert_failure response + assert_equal 'Carrier not supported', response.message + assert_equal Gateway::STANDARD_ERROR_CODE[:config_error], response.error_code + end + + def test_successful_authorize_and_capture + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + assert capture = @gateway.capture(@amount, auth.authorization) + assert_success capture + assert_equal 'Response by mock', capture.message + end + + def test_successful_authorize_and_capture_with_elo + auth = @gateway.authorize(@amount, @elo_credit_card, @options) + assert_success auth + assert capture = @gateway.capture(@amount, auth.authorization) + assert_success capture + assert_equal 'Response by mock', capture.message + end + + def test_successful_authorize_and_capture_with_token + store_response = @gateway.store(@credit_card, @options) + assert_success store_response + token = store_response.authorization + auth = @gateway.authorize(@amount, token, @options) + assert_success auth + assert capture = @gateway.capture(@amount, auth.authorization) + assert_success capture + assert_equal 'Response by mock', capture.message + end + + def test_successful_authorize_and_capture_with_different_amount + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + assert capture = @gateway.capture(@amount + 100, auth.authorization) + assert_success capture + assert_equal 'Response by mock', capture.message + end + + def test_failed_authorize + response = @gateway.authorize(@amount, @declined_card, @options) + assert_failure response + assert_equal 'Response by mock', response.message + end + + def test_partial_capture + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + assert capture = @gateway.capture(@amount - 1, auth.authorization) + assert_failure capture # Paymentez explicitly does not support partial capture; only GREATER than auth capture + end + + def test_failed_capture + response = @gateway.capture(@amount, '') + assert_failure response + assert_equal 'The modification of the amount is not supported by carrier', response.message + end + + def test_store + response = @gateway.store(@credit_card, @options) + assert_success response + end + + def test_store_with_elo + response = @gateway.store(@elo_credit_card, @options) + assert_success response + end + + def test_unstore + response = @gateway.store(@credit_card, @options) + assert_success response + auth = response.authorization + response = @gateway.unstore(auth, @options) + assert_success response + end + + def test_unstore_with_elo + response = @gateway.store(@elo_credit_card, @options) + assert_success response + auth = response.authorization + response = @gateway.unstore(auth, @options) + assert_success response + end + + def test_invalid_login + gateway = PaymentezGateway.new(application_code: '9z8y7w6x', app_key: '1a2b3c4d') + + response = gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_equal 'BackendResponseError', response.message + assert_equal Gateway::STANDARD_ERROR_CODE[:config_error], response.error_code + end + + def test_transcript_scrubbing + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @credit_card, @options) + end + transcript = @gateway.scrub(transcript) + + assert_scrubbed(@credit_card.number, transcript) + assert_scrubbed(@credit_card.verification_value, transcript) + assert_scrubbed(@gateway.options[:app_key], transcript) + end +end diff --git a/test/remote/gateways/remote_paymill_test.rb b/test/remote/gateways/remote_paymill_test.rb index 77b201edfc5..880f346a47d 100644 --- a/test/remote/gateways/remote_paymill_test.rb +++ b/test/remote/gateways/remote_paymill_test.rb @@ -2,38 +2,86 @@ class RemotePaymillTest < Test::Unit::TestCase def setup - @gateway = PaymillGateway.new(fixtures(:paymill)) - + params = fixtures(:paymill) + @gateway = PaymillGateway.new(public_key: params[:public_key], private_key: params[:private_key]) @amount = 100 - @credit_card = credit_card('5105105105105100') + @credit_card = credit_card('5500000000000004') + @options = { + :email => 'Longbob.Longse@example.com' + } + @declined_card = credit_card('5105105105105100', month: 5, year: 2020) + + uri = URI.parse("https://test-token.paymill.com?transaction.mode=CONNECTOR_TEST&channel.id=#{params[:public_key]}&jsonPFunction=paymilljstests&account.number=4111111111111111&account.expiry.month=12&account.expiry.year=2018&account.verification=123&account.holder=John%20Rambo&presentation.amount3D=#{@amount}&presentation.currency3D=EUR") + https = Net::HTTP.new(uri.host, uri.port) + https.use_ssl = true + request = Net::HTTP::Get.new(uri.request_uri) + request.basic_auth(params[:private_key], '') + response = https.request(request) + @token = response.body.match('tok_[a-z|0-9]+')[0] end def test_successful_purchase - assert response = @gateway.purchase(@amount, @credit_card) + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal 'Operation successful', response.message + end + + def test_successful_purchase_with_token + assert response = @gateway.purchase(@amount, @token) assert_success response - assert_equal 'Transaction approved', response.message + assert_equal 'Operation successful', response.message end - def test_failed_purchase_with_invalid_card + def test_failed_store_card_attempting_purchase @credit_card.number = '' - assert response = @gateway.purchase(@amount, @credit_card) + assert response = @gateway.purchase(@amount, @credit_card, @options) assert_failure response - assert_equal 'Account or Bank Details Incorrect', response.message + assert_equal '[account.number] This field is missing.', response.message + end + + def test_failed_purchase + assert response = @gateway.purchase(@amount, @declined_card, @options) + assert_failure response + assert_equal 'Card declined', response.message end def test_successful_authorize_and_capture - assert response = @gateway.authorize(@amount, @credit_card) + assert response = @gateway.authorize(@amount, @credit_card, @options) + assert_success response + assert_equal 'Operation successful', response.message + assert response.authorization + + assert capture_response = @gateway.capture(@amount, response.authorization) + assert_success capture_response + assert_equal 'Operation successful', capture_response.message + end + + def test_successful_authorize_and_capture_with_token + assert response = @gateway.authorize(@amount, @token, @options) assert_success response - assert_equal 'Transaction approved', response.message + assert_equal 'Operation successful', response.message assert response.authorization assert capture_response = @gateway.capture(@amount, response.authorization) assert_success capture_response - assert_equal 'Transaction approved', capture_response.message + assert_equal 'Operation successful', capture_response.message + end + + def test_successful_authorize_with_token + assert response = @gateway.authorize(@amount, @token, @options) + assert_success response + assert_equal 'Operation successful', response.message + assert response.authorization + end + + def test_failed_authorize + assert response = @gateway.authorize(@amount, @declined_card, @options) + assert_failure response + assert_equal 'Preauthorisation failed', response.message end def test_failed_capture - assert response = @gateway.authorize(@amount, @credit_card) + assert response = @gateway.authorize(@amount, @credit_card, @options) assert_success response assert capture_response = @gateway.capture(@amount, response.authorization) @@ -41,21 +89,32 @@ def test_failed_capture assert capture_response = @gateway.capture(@amount, response.authorization) assert_failure capture_response - assert_equal 'Preauthorization has already been used', capture_response.message + assert_equal 'Transaction duplicate', capture_response.message + end + + def test_successful_authorize_and_void + assert response = @gateway.authorize(@amount, @credit_card, @options) + assert_success response + assert_equal 'Operation successful', response.message + assert response.authorization + + assert void_response = @gateway.void(response.authorization) + assert_success void_response + assert_equal 'Transaction approved.', void_response.message end def test_successful_refund - assert response = @gateway.purchase(@amount, @credit_card) + assert response = @gateway.purchase(@amount, @credit_card, @options) assert_success response assert response.authorization assert refund = @gateway.refund(@amount, response.authorization) assert_success refund - assert_equal 'Transaction approved', refund.message + assert_equal 'Operation successful', refund.message end def test_failed_refund - assert response = @gateway.purchase(@amount, @credit_card) + assert response = @gateway.purchase(@amount, @credit_card, @options) assert_success response assert response.authorization @@ -65,7 +124,7 @@ def test_failed_refund end def test_invalid_login - gateway = PaymillGateway.new(fixtures(:paymill).merge(:private_key => "SomeBogusValue")) + gateway = PaymillGateway.new(public_key: fixtures(:paymill)[:public_key], private_key: 'SomeBogusValue') response = gateway.purchase(@amount, @credit_card) assert_failure response assert_equal 'Access Denied', response.message @@ -84,7 +143,7 @@ def test_failed_store_with_invalid_card @credit_card.number = '' assert response = @gateway.store(@credit_card) assert_failure response - assert_equal 'Account or Bank Details Incorrect', response.message + assert_equal '[account.number] This field is missing.', response.message end def test_successful_store_and_authorize @@ -96,10 +155,21 @@ def test_successful_store_and_authorize assert_success authorize end - # Paymill doesn't yet offer a way to trigger a decline on a test account. - # def test_failed_purchase_with_declined_credit_card - # assert response = @gateway.purchase(@amount, @declined_card) - # assert_failure response - # assert_equal 'Unable to process the purchase transaction.', response.message - # end + def test_transcript_scrubbing + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @credit_card) + end + clean_transcript = @gateway.scrub(transcript) + + assert_scrubbed(@credit_card.number, clean_transcript) + assert_scrubbed(@credit_card.verification_value.to_s, clean_transcript) + end + + def test_verify_credentials + assert @gateway.verify_credentials + + gateway = PaymillGateway.new(public_key: 'unknown_key', private_key: 'unknown_key') + assert !gateway.verify_credentials + end + end diff --git a/test/remote/gateways/remote_paypal_express_test.rb b/test/remote/gateways/remote_paypal_express_test.rb index d2199a48821..c599649a814 100644 --- a/test/remote/gateways/remote_paypal_express_test.rb +++ b/test/remote/gateways/remote_paypal_express_test.rb @@ -2,27 +2,29 @@ class PaypalExpressTest < Test::Unit::TestCase def setup - Base.gateway_mode = :test - + Base.mode = :test + @gateway = PaypalExpressGateway.new(fixtures(:paypal_certificate)) - + @options = { :order_id => '230000', :email => 'buyer@jadedpallet.com', - :billing_address => { :name => 'Fred Brooks', - :address1 => '1234 Penny Lane', - :city => 'Jonsetown', - :state => 'NC', - :country => 'US', - :zip => '23456' - } , + :billing_address => + { + :name => 'Fred Brooks', + :address1 => '1234 Penny Lane', + :city => 'Jonsetown', + :state => 'NC', + :country => 'US', + :zip => '23456' + }, :description => 'Stuff that you purchased, yo!', :ip => '10.0.0.1', :return_url => 'http://example.com/return', :cancel_return_url => 'http://example.com/cancel' } end - + def test_set_express_authorization @options.update( :return_url => 'http://example.com', @@ -34,7 +36,7 @@ def test_set_express_authorization assert response.test? assert !response.params['token'].blank? end - + def test_set_express_purchase @options.update( :return_url => 'http://example.com', @@ -46,4 +48,14 @@ def test_set_express_purchase assert response.test? assert !response.params['token'].blank? end -end + + def test_transcript_scrubbing + transcript = capture_transcript(@gateway) do + @gateway.setup_authorization(500, @options) + end + transcript = @gateway.scrub(transcript) + + assert_scrubbed(@gateway.options[:login], transcript) + assert_scrubbed(@gateway.options[:password], transcript) + end +end diff --git a/test/remote/gateways/remote_paypal_test.rb b/test/remote/gateways/remote_paypal_test.rb index 1ce1310a46d..220337bb2d9 100644 --- a/test/remote/gateways/remote_paypal_test.rb +++ b/test/remote/gateways/remote_paypal_test.rb @@ -2,74 +2,85 @@ class PaypalTest < Test::Unit::TestCase def setup - Base.gateway_mode = :test - @gateway = PaypalGateway.new(fixtures(:paypal_signature)) - @creditcard = CreditCard.new( - :brand => "visa", - :number => "4381258770269608", # Use a generated CC from the paypal Sandbox - :verification_value => "000", - :month => 1, - :year => Time.now.year + 1, - :first_name => 'Fred', - :last_name => 'Brooks' - ) - + @credit_card = credit_card('4381258770269608') # Use a generated CC from the paypal Sandbox + @declined_card = credit_card('234234234234') + @params = { :order_id => generate_unique_id, :email => 'buyer@jadedpallet.com', - :billing_address => { :name => 'Fred Brooks', - :address1 => '1234 Penny Lane', - :city => 'Jonsetown', - :state => 'NC', - :country => 'US', - :zip => '23456' - } , + :billing_address => + { + :name => 'Longbob Longsen', + :address1 => '4321 Penny Lane', + :city => 'Jonsetown', + :state => 'NC', + :country => 'US', + :zip => '23456' + }, :description => 'Stuff that you purchased, yo!', :ip => '10.0.0.1' } - + @amount = 100 + # test re-authorization, auth-id must be more than 3 days old. # each auth-id can only be reauthorized and tested once. # leave it commented if you don't want to test reauthorization. - # - #@three_days_old_auth_id = "9J780651TU4465545" - #@three_days_old_auth_id2 = "62503445A3738160X" + # + # @three_days_old_auth_id = "9J780651TU4465545" + # @three_days_old_auth_id2 = "62503445A3738160X" + end + + def test_transcript_scrubbing + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @credit_card, @params) + end + transcript = @gateway.scrub(transcript) + + assert_scrubbed(@credit_card.number, transcript) + assert_scrubbed(@credit_card.verification_value, transcript) + assert_scrubbed(@gateway.options[:login], transcript) + assert_scrubbed(@gateway.options[:password], transcript) + assert_scrubbed(@gateway.options[:signature], transcript) end def test_successful_purchase - response = @gateway.purchase(@amount, @creditcard, @params) + response = @gateway.purchase(@amount, @credit_card, @params) assert_success response assert response.params['transaction_id'] end - - def test_successful_purchase_with_api_signature - gateway = PaypalGateway.new(fixtures(:paypal_signature)) - response = gateway.purchase(@amount, @creditcard, @params) + + def test_successful_purchase_sans_cvv + @credit_card.verification_value = nil + response = @gateway.purchase(@amount, @credit_card, @params) + assert_success response + assert response.params['transaction_id'] + end + + def test_successful_purchase_with_descriptors + response = @gateway.purchase(@amount, @credit_card, @params.merge(soft_descriptor: 'Active Merchant TXN', soft_descriptor_city: '800-883-3931')) assert_success response assert response.params['transaction_id'] end - + def test_failed_purchase - @creditcard.number = '234234234234' - response = @gateway.purchase(@amount, @creditcard, @params) + response = @gateway.purchase(@amount, @declined_card, @params) assert_failure response assert_nil response.params['transaction_id'] end def test_successful_authorization - response = @gateway.authorize(@amount, @creditcard, @params) + response = @gateway.authorize(@amount, @credit_card, @params) assert_success response assert response.params['transaction_id'] assert_equal '1.00', response.params['amount'] assert_equal 'USD', response.params['amount_currency_id'] end - + def test_failed_authorization - @creditcard.number = '234234234234' - response = @gateway.authorize(@amount, @creditcard, @params) + response = @gateway.authorize(@amount, @declined_card, @params) assert_failure response assert_nil response.params['transaction_id'] end @@ -79,23 +90,23 @@ def test_successful_reauthorization auth = @gateway.reauthorize(1000, @three_days_old_auth_id) assert_success auth assert auth.authorization - + response = @gateway.capture(1000, auth.authorization) assert_success response assert response.params['transaction_id'] assert_equal '10.00', response.params['gross_amount'] assert_equal 'USD', response.params['gross_amount_currency_id'] end - + def test_failed_reauthorization return if not @three_days_old_auth_id2 # was authed for $10, attempt $20 auth = @gateway.reauthorize(2000, @three_days_old_auth_id2) assert_false auth? assert !auth.authorization end - + def test_successful_capture - auth = @gateway.authorize(@amount, @creditcard, @params) + auth = @gateway.authorize(@amount, @credit_card, @params) assert_success auth response = @gateway.capture(@amount, auth.authorization) assert_success response @@ -105,9 +116,9 @@ def test_successful_capture end def test_successful_incomplete_captures - auth = @gateway.authorize(100, @creditcard, @params) + auth = @gateway.authorize(100, @credit_card, @params) assert_success auth - response = @gateway.capture(60, auth.authorization, {:complete_type => "NotComplete"}) + response = @gateway.capture(60, auth.authorization, {:complete_type => 'NotComplete'}) assert_success response assert response.params['transaction_id'] assert_equal '0.60', response.params['gross_amount'] @@ -116,83 +127,102 @@ def test_successful_incomplete_captures assert response_2.params['transaction_id'] assert_equal '0.40', response_2.params['gross_amount'] end - - # NOTE THIS SETTING: http://skitch.com/jimmybaker/nysus/payment-receiving-preferences-paypal - # PayPal doesn't return the InvoiceID in the response, so I am unable to check for it. Looking at the transaction - # on PayPal's site will show "NEWID123" as the InvoiceID. + def test_successful_capture_updating_the_invoice_id - auth = @gateway.authorize(@amount, @creditcard, @params) + auth = @gateway.authorize(@amount, @credit_card, @params) assert_success auth - response = @gateway.capture(@amount, auth.authorization, :order_id => "NEWID123") + response = @gateway.capture(@amount, auth.authorization, :order_id => "NEWID#{generate_unique_id}") assert_success response assert response.params['transaction_id'] assert_equal '1.00', response.params['gross_amount'] assert_equal 'USD', response.params['gross_amount_currency_id'] end - + def test_successful_voiding - auth = @gateway.authorize(@amount, @creditcard, @params) + auth = @gateway.authorize(@amount, @credit_card, @params) assert_success auth response = @gateway.void(auth.authorization) assert_success response end - + def test_purchase_and_full_credit - purchase = @gateway.purchase(@amount, @creditcard, @params) + purchase = @gateway.purchase(@amount, @credit_card, @params) assert_success purchase - + credit = @gateway.refund(@amount, purchase.authorization, :note => 'Sorry') assert_success credit assert credit.test? assert_equal 'USD', credit.params['net_refund_amount_currency_id'] - assert_equal '0.67', credit.params['net_refund_amount'] + assert_equal '0.97', credit.params['net_refund_amount'] assert_equal 'USD', credit.params['gross_refund_amount_currency_id'] assert_equal '1.00', credit.params['gross_refund_amount'] assert_equal 'USD', credit.params['fee_refund_amount_currency_id'] - assert_equal '0.33', credit.params['fee_refund_amount'] + assert_equal '0.03', credit.params['fee_refund_amount'] # As of August 2010, PayPal keeps the flat fee ($0.30) end - + def test_failed_voiding response = @gateway.void('foo') assert_failure response end - + + def test_successful_verify + assert response = @gateway.verify(@credit_card, @params) + assert_success response + assert_equal '0.00', response.params['amount'] + assert_match %r{This card authorization verification is not a payment transaction}, response.message + end + + def test_failed_verify + assert response = @gateway.verify(@declined_card, @params) + assert_failure response + assert_match %r{This transaction cannot be processed}, response.message + end + + def test_successful_verify_non_visa_mc + amex_card = credit_card('371449635398431', brand: nil, verification_value: '1234') + assert response = @gateway.verify(amex_card, @params) + assert_success response + assert_equal '1.00', response.params['amount'] + assert_match %r{Success}, response.message + assert_success response.responses.last, 'The void should succeed' + end + def test_successful_transfer - response = @gateway.purchase(@amount, @creditcard, @params) + response = @gateway.purchase(@amount, @credit_card, @params) assert_success response - + response = @gateway.transfer(@amount, 'joe@example.com', :subject => 'Your money', :note => 'Thanks for taking care of that') assert_success response end def test_failed_transfer - # paypal allows a max transfer of $10,000 + # paypal allows a max transfer of $10,000 response = @gateway.transfer(1000001, 'joe@example.com') assert_failure response end - + def test_successful_multiple_transfer - response = @gateway.purchase(900, @creditcard, @params) + response = @gateway.purchase(900, @credit_card, @params) assert_success response - + response = @gateway.transfer([@amount, 'joe@example.com'], [600, 'jane@example.com', {:note => 'Thanks for taking care of that'}], :subject => 'Your money') assert_success response end - + def test_failed_multiple_transfer - response = @gateway.purchase(25100, @creditcard, @params) + response = @gateway.purchase(25100, @credit_card, @params) assert_success response # You can only include up to 250 recipients - recipients = (1..251).collect {|i| [100, "person#{i}@example.com"]} + recipients = (1..251).collect { |i| [100, "person#{i}@example.com"] } response = @gateway.transfer(*recipients) assert_failure response end def test_successful_email_transfer - response = @gateway.purchase(@amount, @creditcard, @params) + response = @gateway.purchase(@amount, @credit_card, @params) assert_success response response = @gateway.transfer([@amount, 'joe@example.com'], :receiver_type => 'EmailAddress', :subject => 'Your money', :note => 'Thanks for taking care of that') @@ -200,7 +230,7 @@ def test_successful_email_transfer end def test_successful_userid_transfer - response = @gateway.purchase(@amount, @creditcard, @params) + response = @gateway.purchase(@amount, @credit_card, @params) assert_success response response = @gateway.transfer([@amount, '4ET96X3PQEN8H'], :receiver_type => 'UserID', :subject => 'Your money', :note => 'Thanks for taking care of that') @@ -208,21 +238,35 @@ def test_successful_userid_transfer end def test_failed_userid_transfer - response = @gateway.purchase(@amount, @creditcard, @params) + response = @gateway.purchase(@amount, @credit_card, @params) assert_success response response = @gateway.transfer([@amount, 'joe@example.com'], :receiver_type => 'UserID', :subject => 'Your money', :note => 'Thanks for taking care of that') assert_failure response end - + # Makes a purchase then makes another purchase adding $1.00 using just a reference id (transaction id) def test_successful_referenced_id_purchase - response = @gateway.purchase(@amount, @creditcard, @params) + response = @gateway.purchase(@amount, @credit_card, @params) assert_success response id_for_reference = response.params['transaction_id'] - + @params.delete(:order_id) response2 = @gateway.purchase(@amount + 100, id_for_reference, @params) assert_success response2 end + + def test_successful_purchase_with_3ds_version_1 + params = @params.merge!({ + three_d_secure: { + trans_status: 'Y', + eci: '05', + cavv: 'AgAAAAAAAIR8CQrXcIhbQAAAAAA', + xid: 'MDAwMDAwMDAwMDAwMDAwMzIyNzY=' + } + }) + response = @gateway.purchase(@amount, @credit_card, params) + assert_success response + assert response.params['transaction_id'] + end end diff --git a/test/remote/gateways/remote_payscout_test.rb b/test/remote/gateways/remote_payscout_test.rb new file mode 100644 index 00000000000..ce0a0768a1e --- /dev/null +++ b/test/remote/gateways/remote_payscout_test.rb @@ -0,0 +1,158 @@ +require 'test_helper' + +class RemotePayscoutTest < Test::Unit::TestCase + def setup + @gateway = PayscoutGateway.new(fixtures(:payscout)) + + @amount = 100 + @credit_card = credit_card('4111111111111111', verification_value: 999) + @declined_card = credit_card('34343') + + @options = { + :order_id => '1', + :description => 'Store Purchase', + :billing_address => address + } + end + + ########## Purchase ########## + + def test_cvv_fail_purchase + @credit_card = credit_card('4111111111111111') + assert response = @gateway.purchase(@amount, @credit_card, @options) + + assert_success response + assert_equal 'The transaction has been approved', response.message + assert_equal 'N', response.cvv_result['code'] + end + + def test_approved_purchase + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal 'The transaction has been approved', response.message + end + + def test_declined_purchase + @amount = 60 + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_equal 'The transaction has been declined', response.message + end + + ########## Authorize ########## + + def test_approved_authorization + assert response = @gateway.authorize(@amount, @credit_card, @options) + assert_success response + assert_equal 'The transaction has been approved', response.message + end + + def test_declined_authorization + @amount = 60 + assert response = @gateway.authorize(@amount, @credit_card, @options) + assert_failure response + assert_equal 'The transaction has been declined', response.message + end + + ########## Capture ########## + + def test_approved_capture + amount = @amount + assert auth = @gateway.authorize(amount, @credit_card, @options) + assert_success auth + assert_equal 'The transaction has been approved', auth.message + assert auth.authorization + assert capture = @gateway.capture(amount, auth.authorization) + assert_success capture + end + + def test_invalid_amount_capture + amount = @amount + assert auth = @gateway.authorize(amount, @credit_card, @options) + assert_success auth + assert_equal 'The transaction has been approved', auth.message + assert auth.authorization + amount = 200 + assert capture = @gateway.capture(amount, auth.authorization) + assert_failure capture + assert_match 'The specified amount of 2.00 exceeds the authorization amount of 1.00', capture.message + end + + def test_not_found_transaction_id_capture + assert capture = @gateway.capture(@amount, '1234567890') + assert_failure capture + assert_match 'Transaction not found', capture.message + end + + def test_invalid_transaction_id_capture + assert capture = @gateway.capture(@amount, '') + assert_failure capture + assert_match 'Invalid Transaction ID', capture.message + end + + ########## Refund ########## + + def test_approved_refund + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert refund = @gateway.refund(@amount, purchase.authorization) + assert_success refund + assert_equal 'The transaction has been approved', refund.message + end + + def test_not_found_transaction_id_refund + assert refund = @gateway.refund(@amount, '1234567890') + assert_failure refund + assert_match 'Transaction not found', refund.message + end + + def test_invalid_transaction_id_refund + assert refund = @gateway.refund(@amount, '') + assert_failure refund + assert_match 'Invalid Transaction ID', refund.message + end + + def test_invalid_amount_refund + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert refund = @gateway.refund(200, purchase.authorization) + assert_failure refund + assert_match 'Refund amount may not exceed the transaction balance', refund.message + end + + ########## Void ########## + + def test_approved_void_purchase + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert void = @gateway.void(purchase.authorization) + assert_success void + assert_equal 'The transaction has been approved', void.message + end + + def test_approved_void_authorization + auth = @gateway.authorize(@amount, @credit_card, @options) + assert void = @gateway.void(auth.authorization) + assert_success void + assert_equal 'The transaction has been approved', void.message + end + + def test_invalid_transaction_id_void + assert void = @gateway.void('') + assert_failure void + assert_match 'Invalid Transaction ID', void.message + end + + def test_not_found_transaction_id_void + assert void = @gateway.void('1234567890') + assert_failure void + assert_match 'Transaction not found', void.message + end + + def test_invalid_credentials + gateway = PayscoutGateway.new( + :username => 'xxx', + :password => 'xxx' + ) + assert response = gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_equal 'Authentication Failed', response.message + end +end diff --git a/test/remote/gateways/remote_paystation_test.rb b/test/remote/gateways/remote_paystation_test.rb index 40b3a732080..84285905003 100644 --- a/test/remote/gateways/remote_paystation_test.rb +++ b/test/remote/gateways/remote_paystation_test.rb @@ -1,113 +1,126 @@ require 'test_helper' class RemotePaystationTest < Test::Unit::TestCase - def setup @gateway = PaystationGateway.new(fixtures(:paystation)) - + @credit_card = credit_card('5123456789012346', :month => 5, :year => 13, :verification_value => 123) - + @successful_amount = 10000 @insufficient_funds_amount = 10051 @invalid_transaction_amount = 10012 @expired_card_amount = 10054 @bank_error_amount = 10091 - - @options = { + + @options = { :billing_address => address, :description => 'Store Purchase' } end - + def test_successful_purchase - assert response = @gateway.purchase(@successful_amount, @credit_card, @options.merge(:order_id => get_uid)) + assert response = @gateway.purchase(@successful_amount, @credit_card, @options) assert_success response - + assert_equal 'Transaction successful', response.message end - + def test_successful_purchase_in_gbp - assert response = @gateway.purchase(@successful_amount, @credit_card, @options.merge(:currency => "GBP", :order_id => get_uid)) + assert response = @gateway.purchase(@successful_amount, @credit_card, @options.merge(:currency => 'GBP')) assert_success response - + assert_equal 'Transaction successful', response.message end - + def test_failed_purchases - [ - ["insufficient_funds", @insufficient_funds_amount, "Insufficient Funds"], - ["invalid_transaction", @invalid_transaction_amount, "Transaction Type Not Supported"], - ["expired_card", @expired_card_amount, "Expired Card"], - ["bank_error", @bank_error_amount, "Error Communicating with Bank"] + [ + ['insufficient_funds', @insufficient_funds_amount, 'Insufficient Funds'], + ['invalid_transaction', @invalid_transaction_amount, 'Transaction Type Not Supported'], + ['expired_card', @expired_card_amount, 'Expired Card'], + ['bank_error', @bank_error_amount, 'Error Communicating with Bank'] ].each do |name, amount, message| - - assert response = @gateway.purchase(amount, @credit_card, @options.merge(:order_id => get_uid)) - assert_failure response - assert_equal message, response.message - + assert response = @gateway.purchase(amount, @credit_card, @options) + assert_failure response + assert_equal message, response.message end end - - def test_storing_token + + def test_storing_token time = Time.now.to_i - assert response = @gateway.store(@credit_card, @options.merge(:order_id => get_uid, :token => "justatest#{time}")) + assert response = @gateway.store(@credit_card, @options.merge(:token => "justatest#{time}")) assert_success response - - assert_equal "Future Payment Saved Ok", response.message + + assert_equal 'Future Payment Saved Ok', response.message assert_equal "justatest#{time}", response.token end - + def test_billing_stored_token - assert store_response = @gateway.store(@credit_card, @options.merge(:order_id => get_uid)) + assert store_response = @gateway.store(@credit_card, @options) assert_success store_response - - assert charge_response = @gateway.purchase(@successful_amount, store_response.token, @options.merge(:order_id => get_uid)) + + assert charge_response = @gateway.purchase(@successful_amount, store_response.token, @options) assert_success charge_response - assert_equal "Transaction successful", charge_response.message + assert_equal 'Transaction successful', charge_response.message end - + def test_authorize_and_capture - assert auth = @gateway.authorize(@successful_amount, @credit_card, @options.merge(:order_id => get_uid)) - + assert auth = @gateway.authorize(@successful_amount, @credit_card, @options) + assert_success auth - assert auth.authorization - - assert capture = @gateway.capture(@successful_amount, auth.authorization, @options.merge(:order_id => get_uid, :credit_card_verification => 123)) + assert auth.authorization + + assert capture = @gateway.capture(@successful_amount, auth.authorization, @options.merge(:credit_card_verification => 123)) assert_success capture end - + def test_capture_without_cvv - # for some merchant accounts, paystation requires you send through the card vertification value - # on a capture request - - assert auth = @gateway.authorize(@successful_amount, @credit_card, @options.merge(:order_id => get_uid)) - + assert auth = @gateway.authorize(@successful_amount, @credit_card, @options) + assert_success auth assert auth.authorization - - assert capture = @gateway.capture(@successful_amount, auth.authorization, @options.merge(:order_id => get_uid)) + + assert capture = @gateway.capture(@successful_amount, auth.authorization, @options) assert_failure capture - - assert_equal "Card Security Code (CVV/CSC) Required", capture.message + + assert_equal 'Card Security Code (CVV/CSC) Required', capture.message end - def test_invalid_login - gateway = PaystationGateway.new( - :paystation_id => '', - :gateway_id => '' - ) - assert response = gateway.purchase(@amount, @credit_card, @options.merge(:order_id => get_uid)) - + gateway = PaystationGateway.new(paystation_id: '', gateway_id: '') + assert response = gateway.purchase(@amount, @credit_card, @options) + assert_failure response assert_nil response.authorization end - - private - - # should be unique enough for test purposes - def get_uid - ActiveSupport::SecureRandom.hex(16) + + def test_successful_refund + assert response = @gateway.purchase(@successful_amount, @credit_card, @options) + assert_success response + refund = @gateway.refund(@successful_amount, response.authorization, @options) + assert_success refund + assert_equal 'Transaction successful', refund.message + end + + def test_failed_refund + response = @gateway.refund(nil, '', @options) + assert_failure response + assert_equal 'Error 11:', response.params['strong'] + end + + def test_successful_verify + response = @gateway.verify(@credit_card, @options) + assert_success response + assert_match %r{Transaction successful}, response.message + end + + def test_transcript_scrubbing + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @credit_card, @options) end + transcript = @gateway.scrub(transcript) + + assert_scrubbed(@credit_card.number, transcript) + assert_scrubbed(@credit_card.verification_value, transcript) + end end diff --git a/test/remote/gateways/remote_payu_in_test.rb b/test/remote/gateways/remote_payu_in_test.rb new file mode 100644 index 00000000000..182c3a1cc17 --- /dev/null +++ b/test/remote/gateways/remote_payu_in_test.rb @@ -0,0 +1,129 @@ +require 'test_helper' + +class RemotePayuInTest < Test::Unit::TestCase + def setup + @gateway = PayuInGateway.new(fixtures(:payu_in)) + + @amount = 1100 + @credit_card = credit_card('5123456789012346', month: 5, year: 2017, verification_value: 564) + + @options = { + order_id: generate_unique_id + } + end + + def test_successful_purchase + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal 'No Error', response.message + end + + def test_successful_purchase_with_full_options + response = @gateway.purchase( + @amount, + @credit_card, + order_id: generate_unique_id, + description: 'Awesome!', + email: 'jim@example.com', + billing_address: { + name: 'Jim Smith', + address1: '123 Road', + address2: 'Suite 123', + city: 'Somewhere', + state: 'ZZ', + country: 'US', + zip: '12345', + phone: '12223334444' + }, + shipping_address: { + name: 'Joe Bob', + address1: '987 Street', + address2: 'Suite 987', + city: 'Anyplace', + state: 'AA', + country: 'IN', + zip: '98765', + phone: '98887776666' + } + ) + + assert_success response + end + + def test_failed_purchase + response = @gateway.purchase(0, @credit_card, @options) + assert_failure response + assert_match %r{invalid amount}i, response.message + end + + def test_successful_refund + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + + refund = @gateway.refund(@amount, purchase.authorization) + assert_success refund + end + + def test_partial_refund + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + + refund = @gateway.refund(@amount-1, purchase.authorization) + assert_success refund + end + + def test_failed_refund + response = @gateway.refund(100, '') + assert_failure response + end + + def test_3ds_enrolled_card_fails + response = @gateway.purchase(@amount, credit_card('4012001037141112'), @options) + assert_failure response + assert_equal '3D-secure enrolled cards are not supported.', response.message + +=begin + # This is handy for testing that 3DS is working with PayU + response = response.responses.first + + # You'll probably need a new bin from http://requestb.in + bin = "<requestb.in key>" + File.open("3ds.html", "w") do |f| + f.puts %( + <html> + <body> + <form action="#{response.params["post_uri"]}" method="POST"> + <input type="hidden" name="PaReq" value="#{response.params["form_post_vars"]["PaReq"]}" /> + <input type="hidden" name="MD" value="#{response.params["form_post_vars"]["MD"]}" /> + <input type="hidden" name="TermUrl" value="http://requestb.in/#{bin}" /> + <input type="submit" /> + </form> + </body> + </html> + ) + end + puts "Test 3D-secure via `open 3ds.html`" + puts "View results at http://requestb.in/#{bin}?inspect" + puts "Finalize with: `curl -v -d PaRes='' -d MD='' '#{response.params["form_post_vars"]["TermUrl"]}'`" +=end + end + + def test_transcript_scrubbing + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @credit_card, @options) + end + transcript = @gateway.scrub(transcript) + + assert_scrubbed(@credit_card.number, transcript) + refute_match %r{[^\d]#{@credit_card.verification_value}(?:[^\d]|$)}, 'Expected CVV to be scrubbed out of transcript' + end + + def test_invalid_login + gateway = PayuInGateway.new( + key: '', + salt: '' + ) + response = gateway.purchase(@amount, @credit_card, @options) + assert_failure response + end +end diff --git a/test/remote/gateways/remote_payu_latam_test.rb b/test/remote/gateways/remote_payu_latam_test.rb new file mode 100644 index 00000000000..e78fd67fffa --- /dev/null +++ b/test/remote/gateways/remote_payu_latam_test.rb @@ -0,0 +1,411 @@ +require 'test_helper' + +class RemotePayuLatamTest < Test::Unit::TestCase + def setup + @gateway = PayuLatamGateway.new(fixtures(:payu_latam).update(payment_country: 'AR')) + + @amount = 4000 + @credit_card = credit_card('4097440000000004', verification_value: '444', first_name: 'APPROVED', last_name: '') + @declined_card = credit_card('4097440000000004', verification_value: '444', first_name: 'REJECTED', last_name: '') + @pending_card = credit_card('4097440000000004', verification_value: '444', first_name: 'PENDING', last_name: '') + @naranja_credit_card = credit_card('5895620000000002', :verification_value => '123', :first_name => 'APPROVED', :last_name => '', :brand => 'naranja') + @cabal_credit_card = credit_card('5896570000000004', :verification_value => '123', :first_name => 'APPROVED', :last_name => '', :brand => 'cabal') + @invalid_cabal_card = credit_card('6271700000000000', :verification_value => '123', :first_name => 'APPROVED', :last_name => '', :brand => 'cabal') + + @options = { + dni_number: '5415668464654', + merchant_buyer_id: '1', + currency: 'ARS', + order_id: generate_unique_id, + description: 'Active Merchant Transaction', + installments_number: 1, + tax: 0, + email: 'username@domain.com', + ip: '127.0.0.1', + device_session_id: 'vghs6tvkcle931686k1900o6e1', + cookie: 'pt1t38347bs6jc9ruv2ecpv7o2', + user_agent: 'Mozilla/5.0 (Windows NT 5.1; rv:18.0) Gecko/20100101 Firefox/18.0', + billing_address: address( + address1: 'Viamonte', + address2: '1366', + city: 'Plata', + state: 'Buenos Aires', + country: 'AR', + zip: '64000', + phone: '7563126' + ) + } + end + + # At the time of writing this test, gateway sandbox + # supports auth and purchase transactions only + + def test_invalid_login + gateway = PayuLatamGateway.new(merchant_id: '', account_id: '', api_login: 'U', api_key: 'U', payment_country: 'AR') + response = gateway.purchase(@amount, @credit_card, @options) + assert_failure response + end + + def test_successful_purchase + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal 'APPROVED', response.message + assert response.test? + end + + def test_successful_purchase_with_naranja_card + response = @gateway.purchase(@amount, @naranja_credit_card, @options) + assert_success response + assert_equal 'APPROVED', response.message + assert response.test? + end + + def test_successful_purchase_with_cabal_card + response = @gateway.purchase(@amount, @cabal_credit_card, @options) + assert_success response + assert_equal 'APPROVED', response.message + assert response.test? + end + + def test_successful_purchase_with_specified_language + response = @gateway.purchase(@amount, @credit_card, @options.merge(language: 'es')) + assert_success response + assert_equal 'APPROVED', response.message + assert response.test? + end + + def test_successful_purchase_with_buyer + gateway = PayuLatamGateway.new(fixtures(:payu_latam).update(:account_id => '512327', payment_country: 'BR')) + + options_buyer = { + currency: 'BRL', + billing_address: address( + address1: 'Calle 100', + address2: 'BL4', + city: 'Sao Paulo', + state: 'SP', + country: 'BR', + zip: '09210710', + phone: '(11)756312633' + ), + shipping_address: address( + address1: 'Calle 200', + address2: 'N107', + city: 'Sao Paulo', + state: 'SP', + country: 'BR', + zip: '01019-030', + phone: '(11)756312633' + ), + buyer: { + name: 'Jorge Borges', + dni_number: '5415668464123', + dni_type: 'TI', + merchant_buyer_id: '2', + cnpj: '32593371000110', + email: 'axaxaxas@mlo.org' + } + } + + response = gateway.purchase(@amount, @credit_card, @options.update(options_buyer)) + assert_success response + assert_equal 'APPROVED', response.message + assert response.test? + end + + def test_successful_purchase_brazil + gateway = PayuLatamGateway.new(fixtures(:payu_latam).update(:account_id => '512327', payment_country: 'BR')) + + options_brazil = { + payment_country: 'BR', + currency: 'BRL', + billing_address: address( + address1: 'Calle 100', + address2: 'BL4', + city: 'Sao Paulo', + state: 'SP', + country: 'BR', + zip: '09210710', + phone: '(11)756312633' + ), + shipping_address: address( + address1: 'Calle 200', + address2: 'N107', + city: 'Sao Paulo', + state: 'SP', + country: 'BR', + zip: '01019-030', + phone: '(11)756312633' + ), + buyer: { + cnpj: '32593371000110' + } + } + + response = gateway.purchase(@amount, @credit_card, @options.update(options_brazil)) + assert_success response + assert_equal 'APPROVED', response.message + assert response.test? + end + + def test_successful_purchase_colombia + gateway = PayuLatamGateway.new(fixtures(:payu_latam).update(:account_id => '512321', payment_country: 'CO')) + + options_colombia = { + payment_country: 'CO', + currency: 'COP', + billing_address: address( + address1: 'Calle 100', + address2: 'BL4', + city: 'Bogota', + state: 'Bogota DC', + country: 'CO', + zip: '09210710', + phone: '(11)756312633' + ), + shipping_address: address( + address1: 'Calle 200', + address2: 'N107', + city: 'Bogota', + state: 'Bogota DC', + country: 'CO', + zip: '01019-030', + phone: '(11)756312633' + ), + tax: '3193', + tax_return_base: '16806' + } + + response = gateway.purchase(2000000, @credit_card, @options.update(options_colombia)) + assert_success response + assert_equal 'APPROVED', response.message + assert response.test? + end + + def test_successful_purchase_mexico + gateway = PayuLatamGateway.new(fixtures(:payu_latam).update(:account_id => '512324', payment_country: 'MX')) + + options_mexico = { + payment_country: 'MX', + currency: 'MXN', + billing_address: address( + address1: 'Calle 100', + address2: 'BL4', + city: 'Guadalajara', + state: 'Jalisco', + country: 'MX', + zip: '09210710', + phone: '(11)756312633' + ), + shipping_address: address( + address1: 'Calle 200', + address2: 'N107', + city: 'Guadalajara', + state: 'Jalisco', + country: 'MX', + zip: '01019-030', + phone: '(11)756312633' + ), + birth_date: '1985-05-25' + } + + response = gateway.purchase(@amount, @credit_card, @options.update(options_mexico)) + assert_success response + assert_equal 'APPROVED', response.message + assert response.test? + end + + def test_successful_purchase_no_description + options = @options + options.delete(:description) + response = @gateway.purchase(@amount, @credit_card, options) + assert_success response + assert_equal 'APPROVED', response.message + assert response.test? + end + + def test_failed_purchase + response = @gateway.purchase(@amount, @declined_card, @options) + assert_failure response + assert_equal 'DECLINED', response.params['transactionResponse']['state'] + end + + def test_failed_purchase_with_cabal_card + response = @gateway.purchase(@amount, @invalid_cabal_card, @options) + assert_failure response + assert_equal 'DECLINED', response.params['transactionResponse']['state'] + end + + def test_failed_purchase_with_no_options + response = @gateway.purchase(@amount, @declined_card, {}) + assert_failure response + assert_equal 'DECLINED', response.params['transactionResponse']['state'] + end + + def test_failed_purchase_with_specified_language + gateway = PayuLatamGateway.new(merchant_id: '', account_id: '', api_login: 'U', api_key: 'U', payment_country: 'AR') + response = gateway.purchase(@amount, @declined_card, @options.merge(language: 'es')) + assert_failure response + assert_equal 'Credenciales inválidas', response.message + end + + def test_successful_authorize + response = @gateway.authorize(@amount, @credit_card, @options) + assert_success response + assert_equal 'APPROVED', response.message + assert_match %r(^\d+\|(\w|-)+$), response.authorization + end + + def test_successful_authorize_with_naranja_card + response = @gateway.authorize(@amount, @naranja_credit_card, @options) + assert_success response + assert_equal 'APPROVED', response.message + assert_match %r(^\d+\|(\w|-)+$), response.authorization + end + + def test_successful_authorize_with_cabal_card + response = @gateway.authorize(@amount, @cabal_credit_card, @options) + assert_success response + assert_equal 'APPROVED', response.message + assert_match %r(^\d+\|(\w|-)+$), response.authorization + end + + def test_successful_authorize_with_specified_language + response = @gateway.authorize(@amount, @credit_card, @options.merge(language: 'es')) + assert_success response + assert_equal 'APPROVED', response.message + assert_match %r(^\d+\|(\w|-)+$), response.authorization + end + + def test_failed_authorize + response = @gateway.authorize(@amount, @pending_card, @options) + assert_failure response + assert_equal 'DECLINED', response.params['transactionResponse']['state'] + end + + def test_failed_authorize_with_specified_language + gateway = PayuLatamGateway.new(merchant_id: '', account_id: '', api_login: 'U', api_key: 'U', payment_country: 'AR') + response = gateway.authorize(@amount, @pending_card, @options.merge(language: 'es')) + assert_failure response + assert_equal 'Credenciales inválidas', response.message + end + + # As noted above, capture transactions are currently not supported, but in the hope + # they will one day be, here you go + + # def test_successful_capture + # response = @gateway.authorize(@amount, @credit_card, @options) + # assert_success response + # assert_equal 'APPROVED', response.message + # assert_match %r(^\d+\|(\w|-)+$), response.authorization + + # capture = @gateway.capture(@amount, response.authorization, @options) + # assert_success capture + # assert_equal 'APPROVED', response.message + # assert response.test? + # end + + # def test_successful_partial_capture + # response = @gateway.authorize(@amount, @credit_card, @options) + # assert_success response + # assert_equal 'APPROVED', response.message + # assert_match %r(^\d+\|(\w|-)+$), response.authorization + + # capture = @gateway.capture(@amount - 1, response.authorization, @options) + # assert_success capture + # assert_equal 'APPROVED', response.message + # assert_equal '39.99', response.params['TX_VALUE']['value'] + # assert response.test? + # end + + def test_well_formed_refund_fails_as_expected + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + + assert refund = @gateway.refund(@amount, purchase.authorization, @options) + assert_equal 'The payment plan id cannot be empty', refund.message + end + + def test_failed_refund + response = @gateway.refund(@amount, '') + assert_failure response + assert_match(/property: parentTransactionId, message: must not be null/, response.message) + end + + def test_failed_refund_with_specified_language + response = @gateway.refund(@amount, '', language: 'es') + assert_failure response + assert_match(/property: parentTransactionId, message: No puede ser vacio/, response.message) + end + + def test_failed_void + response = @gateway.void('') + assert_failure response + assert_match(/property: parentTransactionId, message: must not be null/, response.message) + end + + def test_failed_void_with_specified_language + response = @gateway.void('', language: 'es') + assert_failure response + assert_match(/property: parentTransactionId, message: No puede ser vacio/, response.message) + end + + def test_failed_capture + response = @gateway.capture(@amount, '') + assert_failure response + assert_match(/must not be null/, response.message) + end + + def test_verify_credentials + assert @gateway.verify_credentials + + gateway = PayuLatamGateway.new(merchant_id: 'X', account_id: '512322', api_login: 'X', api_key: 'X', payment_country: 'AR') + assert !gateway.verify_credentials + end + + def test_successful_verify + verify = @gateway.verify(@credit_card, @options) + + assert_success verify + assert_equal 'APPROVED', verify.message + end + + def test_successful_verify_with_specified_amount + verify = @gateway.verify(@credit_card, @options.merge(verify_amount: 5100)) + + assert_success verify + assert_equal 'APPROVED', verify.message + end + + def test_successful_verify_with_specified_language + verify = @gateway.verify(@credit_card, @options.merge(language: 'es')) + + assert_success verify + assert_equal 'APPROVED', verify.message + end + + def test_failed_verify_with_specified_amount + verify = @gateway.verify(@credit_card, @options.merge(verify_amount: 499)) + + assert_failure verify + assert_equal 'The order value is less than minimum allowed. Minimum value allowed 5 ARS', verify.message + end + + def test_failed_verify_with_specified_language + verify = @gateway.verify(@credit_card, @options.merge(verify_amount: 499, language: 'es')) + + assert_failure verify + assert_equal 'The order value is less than minimum allowed. Minimum value allowed 5 ARS', verify.message + end + + def test_transcript_scrubbing + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @credit_card, @options) + end + clean_transcript = @gateway.scrub(transcript) + + assert_scrubbed(@credit_card.number, clean_transcript) + assert_scrubbed(@credit_card.verification_value.to_s, clean_transcript) + assert_scrubbed(@gateway.options[:api_key], clean_transcript) + end +end diff --git a/test/remote/gateways/remote_payway_test.rb b/test/remote/gateways/remote_payway_test.rb index a0dc5e6b086..9d900c8528b 100644 --- a/test/remote/gateways/remote_payway_test.rb +++ b/test/remote/gateways/remote_payway_test.rb @@ -8,42 +8,42 @@ def setup @gateway = ActiveMerchant::Billing::PaywayGateway.new(fixtures(:payway)) - @visa = credit_card("4564710000000004", + @visa = credit_card('4564710000000004', :month => 2, :year => 2019, - :verification_value => "847" + :verification_value => '847' ) - @mastercard = credit_card("5163200000000008", + @mastercard = credit_card('5163200000000008', :month => 8, :year => 2020, - :verification_value => "070", - :brand => "master" + :verification_value => '070', + :brand => 'master' ) - @expired = credit_card("4564710000000012", + @expired = credit_card('4564710000000012', :month => 2, :year => 2005, - :verification_value => "963" + :verification_value => '963' ) - @low = credit_card("4564710000000020", + @low = credit_card('4564710000000020', :month => 5, :year => 2020, - :verification_value => "234" + :verification_value => '234' ) - @stolen_mastercard = credit_card("5163200000000016", + @stolen_mastercard = credit_card('5163200000000016', :month => 12, :year => 2019, - :verification_value => "728", - :brand => "master" + :verification_value => '728', + :brand => 'master' ) - @invalid = credit_card("4564720000000037", + @invalid = credit_card('4564720000000037', :month => 9, :year => 2019, - :verification_value => "030" + :verification_value => '030' ) end diff --git a/test/remote/gateways/remote_pin_test.rb b/test/remote/gateways/remote_pin_test.rb index 49b428c51ef..2539bc8b616 100644 --- a/test/remote/gateways/remote_pin_test.rb +++ b/test/remote/gateways/remote_pin_test.rb @@ -5,11 +5,12 @@ def setup @gateway = PinGateway.new(fixtures(:pin)) @amount = 100 - @credit_card = credit_card('5520000000000000') + @credit_card = credit_card('5520000000000000', :year => Time.now.year + 2) + @visa_credit_card = credit_card('4200000000000000', :year => Time.now.year + 3) @declined_card = credit_card('4100000000000001') @options = { - :email => 'roland@pin.net.au', + :email => 'roland@pinpayments.com', :ip => '203.59.39.62', :order_id => '1', :billing_address => address, @@ -18,18 +19,68 @@ def setup end def test_successful_purchase - assert response = @gateway.purchase(@amount, @credit_card, @options) + response = @gateway.purchase(@amount, @credit_card, @options) assert_success response + assert_equal true, response.params['response']['captured'] + end + + def test_successful_purchase_with_metadata + options_with_metadata = { + metadata: { + order_id: generate_unique_id, + purchase_number: generate_unique_id + } + } + response = @gateway.purchase(@amount, @credit_card, @options.merge(options_with_metadata)) + assert_success response + assert_equal true, response.params['response']['captured'] + assert_equal options_with_metadata[:metadata][:order_id], response.params['response']['metadata']['order_id'] + assert_equal options_with_metadata[:metadata][:purchase_number], response.params['response']['metadata']['purchase_number'] + end + + def test_successful_purchase_with_reference + response = @gateway.purchase(@amount, @credit_card, @options.merge(reference: 'statement descriptor')) + assert_success response + end + + def test_successful_authorize_and_capture + authorization = @gateway.authorize(@amount, @credit_card, @options) + assert_success authorization + assert_equal false, authorization.params['response']['captured'] + + response = @gateway.capture(@amount, authorization.authorization, @options) + assert_success response + assert_equal true, response.params['response']['captured'] + end + + def test_failed_authorize + response = @gateway.authorize(@amount, @declined_card, @options) + assert_failure response + end + + def test_failed_capture_due_to_invalid_token + response = @gateway.capture(@amount, 'bogus', @options) + assert_failure response + end + + def test_failed_capture_due_to_invalid_amount + authorization = @gateway.authorize(@amount, @credit_card, @options) + assert_success authorization + assert_equal authorization.params['response']['captured'], false + + response = @gateway.capture(@amount - 1, authorization.authorization, @options) + assert_failure response + assert_equal 'invalid_capture_amount', response.params['error'] end def test_successful_purchase_without_description @options.delete(:description) - assert response = @gateway.purchase(@amount, @credit_card, @options) + response = @gateway.purchase(@amount, @credit_card, @options) assert_success response end def test_unsuccessful_purchase - assert response = @gateway.purchase(@amount, @declined_card, @options) + response = @gateway.purchase(@amount, @declined_card, @options) assert_failure response end @@ -38,8 +89,8 @@ def test_unsuccessful_purchase # falls outside of active merchant def test_store_and_charge_with_pinjs_card_token headers = { - "Content-Type" => "application/json", - "Authorization" => "Basic #{Base64.strict_encode64(@gateway.options[:api_key] + ':').strip}" + 'Content-Type' => 'application/json', + 'Authorization' => "Basic #{Base64.strict_encode64(@gateway.options[:api_key] + ':').strip}" } # Get a token equivalent to what is returned by Pin.js card_attrs = { @@ -48,17 +99,17 @@ def test_store_and_charge_with_pinjs_card_token :expiry_year => @credit_card.year, :cvc => @credit_card.verification_value, :name => "#{@credit_card.first_name} #{@credit_card.last_name}", - :address_line1 => "42 Sevenoaks St", - :address_city => "Lathlain", - :address_postcode => "6454", - :address_start => "WA", - :address_country => "Australia" + :address_line1 => '42 Sevenoaks St', + :address_city => 'Lathlain', + :address_postcode => '6454', + :address_start => 'WA', + :address_country => 'Australia' } - url = @gateway.test_url + "/cards" + url = @gateway.test_url + '/cards' body = JSON.parse(@gateway.ssl_post(url, card_attrs.to_json, headers)) - card_token = body["response"]["token"] + card_token = body['response']['token'] store = @gateway.store(card_token, @options) assert_success store @@ -70,7 +121,7 @@ def test_store_and_charge_with_pinjs_card_token end def test_store_and_customer_token_charge - assert response = @gateway.store(@credit_card, @options) + response = @gateway.store(@credit_card, @options) assert_success response assert_not_nil response.authorization @@ -84,32 +135,54 @@ def test_store_and_customer_token_charge assert_not_equal response1.authorization, response2.authorization end + def test_store_and_update + response = @gateway.store(@credit_card, @options) + assert_success response + assert_not_nil response.authorization + assert_equal @credit_card.year, response.params['response']['card']['expiry_year'] + + response = @gateway.update(response.authorization, @visa_credit_card, :address => address) + assert_success response + assert_not_nil response.authorization + assert_equal @visa_credit_card.year, response.params['response']['card']['expiry_year'] + end + def test_refund - assert response = @gateway.purchase(@amount, @credit_card, @options) + response = @gateway.purchase(@amount, @credit_card, @options) assert_success response assert_not_nil response.authorization token = response.authorization - assert response = @gateway.refund(@amount, token, @options) + response = @gateway.refund(@amount, token, @options) assert_success response assert_not_nil response.authorization end def test_failed_refund - assert response = @gateway.purchase(@amount, @credit_card, @options) + response = @gateway.purchase(@amount, @credit_card, @options) assert_success response assert_not_nil response.authorization token = response.authorization - assert response = @gateway.refund(@amount, token.reverse, @options) + response = @gateway.refund(@amount, token.reverse, @options) assert_failure response end def test_invalid_login gateway = PinGateway.new(:api_key => '') - assert response = gateway.purchase(@amount, @credit_card, @options) + response = gateway.purchase(@amount, @credit_card, @options) assert_failure response end + + def test_transcript_scrubbing + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @credit_card, @options) + end + clean_transcript = @gateway.scrub(transcript) + + assert_scrubbed(@credit_card.number, clean_transcript) + assert_scrubbed(@credit_card.verification_value.to_s, clean_transcript) + end end diff --git a/test/remote/gateways/remote_plugnpay_test.rb b/test/remote/gateways/remote_plugnpay_test.rb index 4340855dc29..f05f4dc7701 100644 --- a/test/remote/gateways/remote_plugnpay_test.rb +++ b/test/remote/gateways/remote_plugnpay_test.rb @@ -3,8 +3,8 @@ class PlugnpayTest < Test::Unit::TestCase def setup @gateway = PlugnpayGateway.new(fixtures(:plugnpay)) - @good_credit_card = credit_card('4242424242424242') - @bad_credit_card = credit_card('1234123412341234') + @good_card = credit_card('4111111111111111', first_name: 'cardtest') + @bad_card = credit_card('1234123412341234') @options = { :billing_address => address, :description => 'Store purchaes' @@ -12,51 +12,57 @@ def setup @amount = 100 end - def test_bad_credit_card - assert response = @gateway.authorize(@amount, @bad_credit_card, @options) + def test_successful_authorize + assert response = @gateway.authorize(@amount, @good_card, @options) + assert_success response + assert !response.authorization.blank? + assert_equal 'Success', response.message + end + + def test_failed_authorize + assert response = @gateway.authorize(@amount, @bad_card, @options) assert_failure response assert_equal 'Invalid Credit Card No.', response.message end - def test_good_credit_card - assert response = @gateway.authorize(@amount, @good_credit_card, @options) + def test_successful_purchase + assert response = @gateway.purchase(@amount, @good_card, @options) assert_success response assert !response.authorization.blank? assert_equal 'Success', response.message end - def test_purchase_transaction - assert response = @gateway.purchase(@amount, @good_credit_card, @options) - assert_success response - assert !response.authorization.blank? - assert_equal 'Success', response.message + def test_failed_purchase + assert response = @gateway.purchase(@amount, @bad_card, @options) + assert_failure response + assert_equal 'Invalid Credit Card No.', response.message end # Capture, and Void require that you Whitelist your IP address. # In the gateway admin tool, you must add your IP address to the allowed addresses and uncheck "Remote client" under the # "Auth Transactions" section of the "Security Requirements" area in the test account Security Administration Area. def test_authorization_and_capture - assert authorization = @gateway.authorize(@amount, @good_credit_card, @options) + assert authorization = @gateway.authorize(@amount, @good_card, @options) assert_success authorization assert capture = @gateway.capture(@amount, authorization.authorization) assert_success capture - assert capture.params['aux_msg'].include? "has been successfully marked for settlement." + assert capture.params['aux_msg'].include? 'has been successfully marked for settlement.' assert_equal 'Success', capture.message end def test_authorization_and_partial_capture - assert authorization = @gateway.authorize(@amount, @good_credit_card, @options) + assert authorization = @gateway.authorize(@amount, @good_card, @options) assert_success authorization assert capture = @gateway.capture(@amount - 1, authorization.authorization) assert_success capture - assert capture.params['aux_msg'].include? "has been successfully reauthed for usd 0.99" + assert capture.params['aux_msg'].include? 'has been successfully reauthed for usd 0.99' assert_equal 'Success', capture.message end def test_authorization_and_void - assert authorization = @gateway.authorize(@amount, @good_credit_card, @options) + assert authorization = @gateway.authorize(@amount, @good_card, @options) assert_success authorization assert void = @gateway.void(authorization.authorization) @@ -64,19 +70,19 @@ def test_authorization_and_void assert_equal 'Success', void.message end - def test_purchase_and_credit - assert purchase = @gateway.purchase(@amount, @good_credit_card, @options) + def test_purchase_and_refund + assert purchase = @gateway.purchase(@amount, @good_card, @options) assert_success purchase - assert credit = @gateway.credit(@amount, purchase.authorization) - assert_success credit - assert_equal 'Success', credit.message + assert refund = @gateway.refund(@amount, purchase.authorization) + assert_success refund + assert_equal 'Success', refund.message end - def test_credit_with_no_previous_transaction - assert credit = @gateway.credit(@amount, @good_credit_card, @options) + def test_refund_with_no_previous_transaction + assert refund = @gateway.refund(@amount, @good_card, @options) - assert_success credit - assert_equal 'Success', credit.message + assert_success refund + assert_equal 'Success', refund.message end end diff --git a/test/remote/gateways/remote_pro_pay_test.rb b/test/remote/gateways/remote_pro_pay_test.rb new file mode 100644 index 00000000000..c447f996862 --- /dev/null +++ b/test/remote/gateways/remote_pro_pay_test.rb @@ -0,0 +1,160 @@ +require 'test_helper' + +class RemoteProPayTest < Test::Unit::TestCase + def setup + @gateway = ProPayGateway.new(fixtures(:pro_pay)) + + @amount = 100 + @credit_card = credit_card('4747474747474747', verification_value: 999) + @declined_card = credit_card('4616161616161616') + @credit_card_without_cvv = credit_card('4747474747474747', verification_value: nil) + @options = { + billing_address: address, + account_num: '32287391' + } + end + + def test_successful_purchase + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal 'Success', response.message + end + + def test_successful_purchase_with_more_options + options = { + order_id: '1', + ip: '127.0.0.1', + email: 'joe@example.com', + account_num: '32287391' + } + + response = @gateway.purchase(@amount, @credit_card, options) + assert_success response + assert_equal 'Success', response.message + end + + def test_successful_recurring_purchase_without_cvv + @options[:recurring_payment] = 'Y' + response = @gateway.purchase(@amount, @credit_card_without_cvv, @options) + assert_success response + assert_equal 'Success', response.message + end + + def test_failed_purchase + response = @gateway.purchase(@amount, @declined_card, @options) + assert_failure response + assert_match(/declined/, response.message) + assert_match(/Insufficient funds/, response.message) + end + + def test_successful_authorize_and_capture + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + assert capture = @gateway.capture(@amount, auth.authorization, @options) + assert_success capture + assert_equal 'Success', capture.message + end + + def test_failed_authorize + response = @gateway.authorize(@amount, @declined_card, @options) + assert_failure response + assert_match(/declined/, response.message) + assert_match(/Insufficient funds/, response.message) + end + + def test_partial_capture + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + assert capture = @gateway.capture(@amount-1, auth.authorization, @options) + assert_success capture + end + + def test_failed_capture + response = @gateway.capture(@amount, '', @options) + assert_failure response + assert_match(/Invalid/, response.message) + end + + def test_successful_refund + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + + assert refund = @gateway.refund(@amount, purchase.authorization, @options) + assert_success refund + assert_equal 'Success', refund.message + end + + def test_partial_refund + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + + assert refund = @gateway.refund(@amount-1, purchase.authorization, @options) + assert_success refund + end + + def test_failed_refund + response = @gateway.refund(@amount, '', @options) + assert_failure response + assert_match(/Invalid/, response.message) + end + + def test_successful_void + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + assert void = @gateway.void(auth.authorization, @options) + assert_success void + assert_equal 'Success', void.message + end + + def test_failed_void + response = @gateway.void('', @options) + assert_failure response + assert_match(/Invalid/, response.message) + end + + def test_successful_credit + response = @gateway.credit(@amount, @credit_card, @options) + assert_success response + assert_equal 'Success', response.message + end + + def test_failed_credit + response = @gateway.credit(@amount, credit_card(''), @options) + assert_failure response + assert_equal 'Invalid ccNum', response.message + end + + def test_successful_verify + response = @gateway.verify(@credit_card, @options) + assert_success response + assert_match 'Success', response.message + end + + def test_failed_verify + response = @gateway.verify(@declined_card, @options) + assert_failure response + assert_match(/declined/, response.message) + assert_match(/Insufficient funds/, response.message) + end + + def test_invalid_login + gateway = ProPayGateway.new(cert_str: 'bad_cert_str') + + response = gateway.purchase(@amount, @credit_card, @options) + assert_failure response + end + + def test_transcript_scrubbing + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @credit_card, @options) + end + transcript = @gateway.scrub(transcript) + + assert_scrubbed(@credit_card.number, transcript) + assert_scrubbed(@credit_card.verification_value, transcript) + assert_scrubbed(@gateway.options[:cert_str], transcript) + end +end diff --git a/test/remote/gateways/remote_psigate_test.rb b/test/remote/gateways/remote_psigate_test.rb index ec740a3ed37..74d643794fe 100644 --- a/test/remote/gateways/remote_psigate_test.rb +++ b/test/remote/gateways/remote_psigate_test.rb @@ -5,9 +5,10 @@ class PsigateRemoteTest < Test::Unit::TestCase def setup Base.mode = :test @gateway = PsigateGateway.new(fixtures(:psigate)) + PsigateGateway.ssl_strict = false @amount = 2400 - @creditcard = credit_card('4242424242424242') + @creditcard = credit_card('4111111111111111') @options = { :order_id => generate_unique_id, :billing_address => address, @@ -55,4 +56,15 @@ def test_successful_void assert void = @gateway.void(authorization.authorization) assert_success void end + + def test_transcript_scrubbing + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @creditcard, @options) + end + transcript = @gateway.scrub(transcript) + + assert_scrubbed(@creditcard.number, transcript) + assert_scrubbed(@creditcard.verification_value, transcript) + assert_scrubbed(@gateway.options[:password], transcript) + end end diff --git a/test/remote/gateways/remote_psl_card_test.rb b/test/remote/gateways/remote_psl_card_test.rb index 36af7abf443..6b1b9e6e215 100644 --- a/test/remote/gateways/remote_psl_card_test.rb +++ b/test/remote/gateways/remote_psl_card_test.rb @@ -1,29 +1,29 @@ require 'test_helper' class RemotePslCardTest < Test::Unit::TestCase - + def setup @gateway = PslCardGateway.new(fixtures(:psl_card)) - + @uk_maestro = CreditCard.new(fixtures(:psl_maestro)) @uk_maestro_address = fixtures(:psl_maestro_address) - + @solo = CreditCard.new(fixtures(:psl_solo)) @solo_address = fixtures(:psl_solo_address) - + @visa = CreditCard.new(fixtures(:psl_visa)) @visa_address = fixtures(:psl_visa_address) - + @visa_debit = CreditCard.new(fixtures(:psl_visa_debit)) @visa_address = fixtures(:psl_visa_debit_address) - + # The test results are determined by the amount of the transaction @accept_amount = 1000 @referred_amount = 6000 @declined_amount = 11000 @keep_card_amount = 15000 end - + def test_successful_visa_purchase response = @gateway.purchase(@accept_amount, @visa, :billing_address => @visa_address @@ -31,23 +31,23 @@ def test_successful_visa_purchase assert_success response assert response.test? end - + def test_successful_visa_debit_purchase response = @gateway.purchase(@accept_amount, @visa_debit, :billing_address => @visa_debit_address ) assert_success response end - + # Fix regression discovered in production def test_visa_debit_purchase_should_not_send_debit_info_if_present - @visa_debit.start_month = "07" + @visa_debit.start_month = '07' response = @gateway.purchase(@accept_amount, @visa_debit, :billing_address => @visa_debit_address ) assert_success response end - + def test_successful_visa_purchase_specifying_currency response = @gateway.purchase(@accept_amount, @visa, :billing_address => @visa_address, @@ -56,67 +56,67 @@ def test_successful_visa_purchase_specifying_currency assert_success response assert response.test? end - + def test_successful_solo_purchase - response = @gateway.purchase(@accept_amount, @solo, + response = @gateway.purchase(@accept_amount, @solo, :billing_address => @solo_address ) assert_success response assert response.test? end - + def test_referred_purchase - response = @gateway.purchase(@referred_amount, @uk_maestro, + response = @gateway.purchase(@referred_amount, @uk_maestro, :billing_address => @uk_maestro_address ) assert_failure response assert response.test? end - + def test_declined_purchase - response = @gateway.purchase(@declined_amount, @uk_maestro, + response = @gateway.purchase(@declined_amount, @uk_maestro, :billing_address => @uk_maestro_address ) assert_failure response assert response.test? end - + def test_declined_keep_card_purchase - response = @gateway.purchase(@keep_card_amount, @uk_maestro, + response = @gateway.purchase(@keep_card_amount, @uk_maestro, :billing_address => @uk_maestro_address ) assert_failure response assert response.test? end - + def test_successful_authorization - response = @gateway.authorize(@accept_amount, @visa, + response = @gateway.authorize(@accept_amount, @visa, :billing_address => @visa_address ) assert_success response assert response.test? end - + def test_no_login @gateway = PslCardGateway.new( :login => '' ) - response = @gateway.authorize(@accept_amount, @uk_maestro, + response = @gateway.authorize(@accept_amount, @uk_maestro, :billing_address => @uk_maestro_address ) assert_failure response assert response.test? end - + def test_successful_authorization_and_capture authorization = @gateway.authorize(@accept_amount, @visa, :billing_address => @visa_address ) assert_success authorization assert authorization.test? - + capture = @gateway.capture(@accept_amount, authorization.authorization) - + assert_success capture assert capture.test? end diff --git a/test/remote/gateways/remote_qbms_test.rb b/test/remote/gateways/remote_qbms_test.rb index d88553ec1c8..017ebd1fc95 100644 --- a/test/remote/gateways/remote_qbms_test.rb +++ b/test/remote/gateways/remote_qbms_test.rb @@ -66,31 +66,42 @@ def test_successful_credit end def test_invalid_ticket - gateway = QbmsGateway.new(@gateway_options.merge(:ticket => "test123")) + gateway = QbmsGateway.new(@gateway_options.merge(:ticket => 'test123')) assert response = gateway.authorize(@amount, @card, @options) assert_instance_of Response, response assert_failure response - assert_equal "Application agent not found test123", response.message + assert_equal 'Application agent not found test123', response.message end def test_invalid_card_number assert response = @gateway.authorize(@amount, error_card('10301_ccinvalid'), @options) assert_instance_of Response, response assert_failure response - assert_equal "This credit card number is invalid.", response.message + assert_equal 'This credit card number is invalid.', response.message end def test_decline assert response = @gateway.authorize(@amount, error_card('10401_decline'), @options) assert_instance_of Response, response assert_failure response - assert_equal "The request to process this transaction has been declined.", response.message + assert_equal 'The request to process this transaction has been declined.', response.message + end + + def test_transcript_scrubbing + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @credit_card, @options) + end + transcript = @gateway.scrub(transcript) + + assert_scrubbed(@credit_card.number, transcript) + assert_scrubbed(@credit_card.verification_value, transcript) + assert_scrubbed(@gateway.options[:ticket], transcript) end private def error_card(config_id) - credit_card('4111111111111111', :first_name => "configid=#{config_id}", :last_name => "") + credit_card('4111111111111111', :first_name => "configid=#{config_id}", :last_name => '') end end diff --git a/test/remote/gateways/remote_quantum_test.rb b/test/remote/gateways/remote_quantum_test.rb index dfa3b5682a5..370e802d66c 100644 --- a/test/remote/gateways/remote_quantum_test.rb +++ b/test/remote/gateways/remote_quantum_test.rb @@ -1,15 +1,14 @@ require 'test_helper' class RemoteQuantumTest < Test::Unit::TestCase - def setup @gateway = QuantumGateway.new(fixtures(:quantum)) - + @amount = 100 @credit_card = credit_card('4000100011112224') end - + def test_successful_purchase assert response = @gateway.purchase(@amount, @credit_card) assert_success response @@ -53,7 +52,7 @@ def test_void assert response = @gateway.void(response.authorization) assert_success response end - + def test_passing_billing_address options = {:billing_address => address} assert response = @gateway.purchase(@amount, @credit_card, options) diff --git a/test/remote/gateways/remote_quickbooks_test.rb b/test/remote/gateways/remote_quickbooks_test.rb new file mode 100644 index 00000000000..c9e95c959c8 --- /dev/null +++ b/test/remote/gateways/remote_quickbooks_test.rb @@ -0,0 +1,112 @@ +require 'test_helper' + +class RemoteTest < Test::Unit::TestCase + def setup + @gateway = QuickbooksGateway.new(fixtures(:quickbooks)) + @amount = 100 + @credit_card = credit_card('4000100011112224') + @declined_card = credit_card('4000000000000001') + + @partial_amount = @amount - 1 + + @options = { + order_id: '1', + billing_address: address({ zip: 90210, + country: 'US', + state: 'CA' + }), + description: 'Store Purchase' + } + end + + def test_successful_purchase + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal 'CAPTURED', response.message + end + + def test_failed_purchase + response = @gateway.purchase(@amount, @declined_card, @options) + assert_failure response + assert_equal 'cardNumber is invalid.', response.message + end + + def test_successful_authorize_and_capture + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + assert capture = @gateway.capture(nil, auth.authorization) + assert_success capture + end + + def test_failed_authorize + response = @gateway.authorize(@amount, @declined_card, @options) + assert_failure response + end + + def test_partial_capture + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + assert capture = @gateway.capture(@partial_amount, auth.authorization) + assert_equal capture.params['captureDetail']['amount'], sprintf('%.2f', @partial_amount.to_f / 100) + assert_success capture + end + + def test_failed_capture + response = @gateway.capture(nil, '') + assert_failure response + end + + def test_successful_refund + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + + assert refund = @gateway.refund(nil, purchase.authorization) + assert_success refund + end + + def test_partial_refund + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + + assert refund = @gateway.refund(@partial_amount, purchase.authorization) + assert_equal refund.params['amount'], sprintf('%.2f', @partial_amount.to_f / 100) + assert_success refund + end + + def test_failed_refund + response = @gateway.refund(nil, '') + assert_failure response + end + + def test_successful_verify + response = @gateway.verify(@credit_card, @options) + assert_success response + assert_match %r{AUTHORIZED}, response.message + end + + def test_failed_verify + response = @gateway.verify(@declined_card, @options) + assert_failure response + assert_match %r{cardNumber is invalid.}, response.message + assert_equal Gateway::STANDARD_ERROR_CODE[:processing_error], response.error_code + end + + def test_invalid_login + gateway = QuickbooksGateway.new( + consumer_key: '', + consumer_secret: '', + access_token: '', + token_secret: '', + realm: '' + ) + assert_raises ActiveMerchant::ResponseError do + gateway.purchase(@amount, @credit_card, @options) + end + end + + def test_dump_transcript + # See quickbooks_test.rb for an example of a scrubbed transcript + end +end diff --git a/test/remote/gateways/remote_quickpay_test.rb b/test/remote/gateways/remote_quickpay_test.rb index 55c7edb6223..760c71f096e 100644 --- a/test/remote/gateways/remote_quickpay_test.rb +++ b/test/remote/gateways/remote_quickpay_test.rb @@ -56,7 +56,7 @@ def test_successful_visa_dankort_authorization assert response = @gateway.authorize(@amount, @visa_dankort, @options) assert_success response assert !response.authorization.blank? - assert_equal 'dankort', response.params['cardtype'] + assert_equal 'visa-dk', response.params['cardtype'] end def test_successful_visa_electron_authorization @@ -174,22 +174,22 @@ def test_successful_purchase_and_credit end def test_successful_store_and_reference_purchase - assert store = @gateway.store(@visa, @options.merge(:description => "New subscription")) + assert store = @gateway.store(@visa, @options.merge(:description => 'New subscription')) assert_success store assert purchase = @gateway.purchase(@amount, store.authorization, @options.merge(:order_id => generate_unique_id[0...10])) assert_success purchase end def test_failed_store - assert store = @gateway.store(credit_card('400010001111222a'), @options.merge(:description => "New subscription")) + assert store = @gateway.store(credit_card('4'), @options.merge(:description => 'New subscription')) assert_failure store - assert_equal "Error in field: cardnumber", store.message + assert_equal 'Error in field: cardnumber', store.message end def test_invalid_login gateway = QuickpayGateway.new( - :login => '', - :password => '' + :login => '', + :password => '' ) assert response = gateway.purchase(@amount, @visa, @options) assert_equal 'Invalid merchant id', response.message diff --git a/test/remote/gateways/remote_quickpay_v10_test.rb b/test/remote/gateways/remote_quickpay_v10_test.rb new file mode 100644 index 00000000000..8c71fcb1742 --- /dev/null +++ b/test/remote/gateways/remote_quickpay_v10_test.rb @@ -0,0 +1,272 @@ +require 'test_helper' + +class RemoteQuickPayV10Test < Test::Unit::TestCase + + def setup + @gateway = QuickpayV10Gateway.new(fixtures(:quickpay_v10_api_key)) + @amount = 100 + @options = { + :order_id => generate_unique_id[0...10], + :billing_address => address(country: 'DNK') + } + + @valid_card = credit_card('1000000000000008') + @invalid_card = credit_card('1000000000000016') + @expired_card = credit_card('1000000000000024') + @capture_rejected_card = credit_card('1000000000000032') + @refund_rejected_card = credit_card('1000000000000040') + + @valid_address = address(:phone => '4500000001') + @invalid_address = address(:phone => '4500000002') + end + + def card_brand(response) + response.params['metadata']['brand'] + end + + def test_successful_purchase_with_short_country + options = @options.merge({billing_address: address(country: 'DK')}) + assert response = @gateway.purchase(@amount, @valid_card, options) + + assert_equal 'OK', response.message + assert_equal 'DKK', response.params['currency'] + assert_success response + assert !response.authorization.blank? + end + + def test_successful_purchase_with_order_id_format + options = @options.merge({order_id: "##{Time.new.to_f}"}) + assert response = @gateway.purchase(@amount, @valid_card, options) + + assert_equal 'OK', response.message + assert_equal 'DKK', response.params['currency'] + assert_success response + assert !response.authorization.blank? + end + + def test_successful_purchase + assert response = @gateway.purchase(@amount, @valid_card, @options) + + assert_equal 'OK', response.message + assert_equal 'DKK', response.params['currency'] + assert_success response + assert !response.authorization.blank? + end + + def test_unsuccessful_purchase_with_invalid_card + assert response = @gateway.purchase(@amount, @invalid_card, @options) + assert_failure response + assert_match(/Rejected test operation/, response.message) + end + + def test_successful_usd_purchase + assert response = @gateway.purchase(@amount, @valid_card, @options.update(:currency => 'USD')) + assert_equal 'OK', response.message + assert_equal 'USD', response.params['currency'] + assert_success response + assert !response.authorization.blank? + end + + def test_successful_purchase_with_acquirers + assert response = @gateway.purchase(@amount, @valid_card, @options.update(:acquirer => 'nets')) + assert_equal 'OK', response.message + assert_success response + end + + def test_unsuccessful_purchase_with_invalid_acquirers + assert response = @gateway.purchase(@amount, @valid_card, @options.update(:acquirer => 'invalid')) + assert_failure response + assert_equal 'Validation error', response.message + end + + def test_unsuccessful_authorize_with_invalid_card + assert response = @gateway.authorize(@amount, @invalid_card, @options) + assert_failure response + assert_match(/Rejected test operation/, response.message) + end + + def test_successful_authorize_and_capture + assert auth = @gateway.authorize(@amount, @valid_card, @options) + assert_success auth + assert_equal 'OK', auth.message + assert auth.authorization + assert capture = @gateway.capture(@amount, auth.authorization) + assert_success capture + assert_equal 'OK', capture.message + end + + def test_successful_authorize_and_capture_with_3ds + options = @options.merge( + three_d_secure: { + cavv: '1234', + eci: '1234', + xid: '1234' + } + ) + assert auth = @gateway.authorize(@amount, @valid_card, options) + assert_success auth + assert_equal 'OK', auth.message + assert auth.authorization + assert capture = @gateway.capture(@amount, auth.authorization) + assert_success capture + assert_equal 'OK', capture.message + end + + def test_unsuccessful_authorize_and_capture + assert auth = @gateway.authorize(@amount, @capture_rejected_card, @options) + assert_success auth + assert_equal 'OK', auth.message + assert auth.authorization + assert capture = @gateway.capture(@amount, auth.authorization) + assert_failure capture + assert_equal 'Rejected test operation', capture.message + end + + def test_failed_capture + assert response = @gateway.capture(@amount, '1111') + assert_failure response + assert_equal 'Not found: No Payment with id 1111', response.message + end + + def test_successful_purchase_and_void + assert auth = @gateway.authorize(@amount, @valid_card, @options) + assert_success auth + assert_equal 'OK', auth.message + assert auth.authorization + assert void = @gateway.void(auth.authorization) + assert_success void + assert_equal 'OK', void.message + end + + def test_unsuccessful_void + assert void = @gateway.void('123') + assert_failure void + assert_equal 'Not found: No Payment with id 123', void.message + end + + def test_successful_authorization_capture_and_credit + assert auth = @gateway.authorize(@amount, @valid_card, @options) + assert_success auth + assert !auth.authorization.blank? + assert capture = @gateway.capture(@amount, auth.authorization) + assert_success capture + assert credit = @gateway.refund(@amount, auth.authorization) + assert_success credit + assert_equal 'OK', credit.message + end + + def test_successful_purchase_and_credit + assert purchase = @gateway.purchase(@amount, @valid_card, @options) + assert_success purchase + assert credit = @gateway.refund(@amount, purchase.authorization) + assert_success credit + end + + def test_unsuccessful_authorization_capture_and_credit + assert auth = @gateway.authorize(@amount, @refund_rejected_card, @options) + assert_success auth + assert !auth.authorization.blank? + assert capture = @gateway.capture(@amount, auth.authorization) + assert_success capture + assert refund = @gateway.refund(@amount, auth.authorization) + assert_failure refund + assert_equal 'Rejected test operation', refund.message + end + + def test_successful_verify + response = @gateway.verify(@valid_card, @options) + assert_success response + assert_match %r{OK}, response.message + end + + def test_failed_verify + response = @gateway.verify(@invalid_card, @options) + assert_failure response + assert_equal 'Rejected test operation', response.message + end + + def test_successful_store + assert response = @gateway.store(@valid_card, @options) + assert_success response + end + + def test_successful_store_and_reference_purchase + assert store = @gateway.store(@valid_card, @options) + assert_success store + assert purchase = @gateway.purchase(@amount, store.authorization, @options) + assert_success purchase + end + + def test_successful_store_and_reference_recurring_purchase + assert store = @gateway.store(@valid_card, @options) + assert_success store + assert signup = @gateway.purchase(@amount, store.authorization, @options) + assert_success signup + @options[:order_id] = generate_unique_id[0...10] + assert renewal = @gateway.purchase(@amount, store.authorization, @options) + assert_success renewal + end + + def test_successful_store_and_reference_authorize + assert store = @gateway.store(@valid_card, @options) + assert_success store + assert authorization = @gateway.authorize(@amount, store.authorization, @options) + assert_success authorization + end + + def test_successful_store_and_credit + assert store = @gateway.store(@valid_card, @options) + assert_success store + assert purchase = @gateway.purchase(@amount, store.authorization, @options) + assert_success purchase + assert credit = @gateway.refund(@amount, purchase.authorization) + assert_success credit + end + + def test_unsuccessful_store_and_credit + assert store = @gateway.store(@refund_rejected_card, @options) + assert_success store + assert purchase = @gateway.purchase(@amount, store.authorization, @options) + assert_success purchase + assert credit = @gateway.refund(@amount, purchase.authorization) + assert_failure credit + assert_match(/Rejected test operation/, credit.message) + end + + def test_successful_store_and_void_authorize + assert store = @gateway.store(@valid_card, @options) + assert_success store + assert authorize = @gateway.authorize(@amount, store.authorization, @options) + assert_success authorize + assert void = @gateway.void(authorize.authorization) + assert_success void + assert_equal 'OK', void.message + end + + def test_successful_unstore + assert response = @gateway.store(@valid_card, @options) + assert_success response + + assert response = @gateway.unstore(response.authorization) + assert_success response + end + + def test_invalid_login + gateway = QuickpayV10Gateway.new(api_key: '**') + assert response = gateway.purchase(@amount, @valid_card, @options) + assert_equal 'Invalid API key', response.message + assert_failure response + end + + def test_transcript_scrubbing + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @valid_card, @options) + end + clean_transcript = @gateway.scrub(transcript) + + assert_scrubbed(@valid_card.number, clean_transcript) + assert_scrubbed(@valid_card.verification_value.to_s, clean_transcript) + assert_scrubbed(@gateway.options[:api_key], clean_transcript) + end + +end diff --git a/test/remote/gateways/remote_quickpay_v4_test.rb b/test/remote/gateways/remote_quickpay_v4_test.rb index 448527ef320..9eb4ca69970 100644 --- a/test/remote/gateways/remote_quickpay_v4_test.rb +++ b/test/remote/gateways/remote_quickpay_v4_test.rb @@ -24,9 +24,7 @@ def setup @mastercard_dk = credit_card('5413031000000000') @amex_dk = credit_card('3747100000000000') @amex = credit_card('3700100000000000') - - # forbrugsforeningen doesn't use a verification value - @forbrugsforeningen = credit_card('6007221000000000', :verification_value => nil) + @fbg1886 = credit_card('6007221000000000') end def test_successful_purchase @@ -41,10 +39,10 @@ def test_successful_purchase_with_all_fraud_parameters @options[:fraud_http_referer] = 'http://www.excample.com' @options[:fraud_remote_addr] = '127.0.0.1' @options[:fraud_http_accept] = 'foo' - @options[:fraud_http_accept_language] = "DK" - @options[:fraud_http_accept_encoding] = "UFT8" - @options[:fraud_http_accept_charset] = "Latin" - @options[:fraud_http_user_agent] = "Safari" + @options[:fraud_http_accept_language] = 'DK' + @options[:fraud_http_accept_encoding] = 'UFT8' + @options[:fraud_http_accept_charset] = 'Latin' + @options[:fraud_http_user_agent] = 'Safari' assert response = @gateway.purchase(@amount, @visa, @options) assert_equal 'OK', response.message @@ -53,8 +51,6 @@ def test_successful_purchase_with_all_fraud_parameters assert !response.authorization.blank? end - - def test_successful_usd_purchase assert response = @gateway.purchase(@amount, @visa, @options.update(:currency => 'USD')) assert_equal 'OK', response.message @@ -74,6 +70,7 @@ def test_successful_visa_dankort_authorization assert response = @gateway.authorize(@amount, @visa_dankort, @options) assert_success response assert !response.authorization.blank? + # A Visa-Dankort is considered a Dankort when processed by Nets assert_equal 'dankort', response.params['cardtype'] end @@ -134,7 +131,7 @@ def test_successful_american_express_authorization end def test_successful_forbrugsforeningen_authorization - assert response = @gateway.authorize(@amount, @forbrugsforeningen, @options) + assert response = @gateway.authorize(@amount, @fbg1886, @options) assert_success response assert !response.authorization.blank? assert_equal 'fbg1886', response.params['cardtype'] @@ -192,7 +189,7 @@ def test_successful_purchase_and_credit end def test_successful_store_and_reference_purchase - assert store = @gateway.store(@visa, @options.merge(:description => "New subscription")) + assert store = @gateway.store(@visa, @options.merge(:description => 'New subscription')) assert_success store assert purchase = @gateway.purchase(@amount, store.authorization, @options.merge(:order_id => generate_unique_id[0...10])) assert_success purchase @@ -200,8 +197,8 @@ def test_successful_store_and_reference_purchase def test_invalid_login gateway = QuickpayGateway.new( - :login => '', - :password => '' + :login => '999999999', + :password => '' ) assert response = gateway.purchase(@amount, @visa, @options) assert_equal 'Invalid merchant id', response.message diff --git a/test/remote/gateways/remote_quickpay_v5_test.rb b/test/remote/gateways/remote_quickpay_v5_test.rb index e59611a35a4..02838bf2bb8 100644 --- a/test/remote/gateways/remote_quickpay_v5_test.rb +++ b/test/remote/gateways/remote_quickpay_v5_test.rb @@ -24,9 +24,7 @@ def setup @mastercard_dk = credit_card('5413031000000000') @amex_dk = credit_card('3747100000000000') @amex = credit_card('3700100000000000') - - # forbrugsforeningen doesn't use a verification value - @forbrugsforeningen = credit_card('6007221000000000', :verification_value => nil) + @fbg1886 = credit_card('6007221000000000') end def test_successful_purchase @@ -41,10 +39,10 @@ def test_successful_purchase_with_all_fraud_parameters @options[:fraud_http_referer] = 'http://www.excample.com' @options[:fraud_remote_addr] = '127.0.0.1' @options[:fraud_http_accept] = 'foo' - @options[:fraud_http_accept_language] = "DK" - @options[:fraud_http_accept_encoding] = "UFT8" - @options[:fraud_http_accept_charset] = "Latin" - @options[:fraud_http_user_agent] = "Safari" + @options[:fraud_http_accept_language] = 'DK' + @options[:fraud_http_accept_encoding] = 'UFT8' + @options[:fraud_http_accept_charset] = 'Latin' + @options[:fraud_http_user_agent] = 'Safari' assert response = @gateway.purchase(@amount, @visa, @options) assert_equal 'OK', response.message @@ -53,8 +51,6 @@ def test_successful_purchase_with_all_fraud_parameters assert !response.authorization.blank? end - - def test_successful_usd_purchase assert response = @gateway.purchase(@amount, @visa, @options.update(:currency => 'USD')) assert_equal 'OK', response.message @@ -74,6 +70,7 @@ def test_successful_visa_dankort_authorization assert response = @gateway.authorize(@amount, @visa_dankort, @options) assert_success response assert !response.authorization.blank? + # A Visa-Dankort is considered a Dankort when processed by Nets assert_equal 'dankort', response.params['cardtype'] end @@ -134,7 +131,7 @@ def test_successful_american_express_authorization end def test_successful_forbrugsforeningen_authorization - assert response = @gateway.authorize(@amount, @forbrugsforeningen, @options) + assert response = @gateway.authorize(@amount, @fbg1886, @options) assert_success response assert !response.authorization.blank? assert_equal 'fbg1886', response.params['cardtype'] @@ -192,7 +189,7 @@ def test_successful_purchase_and_credit end def test_successful_store_and_reference_purchase - assert store = @gateway.store(@visa, @options.merge(:description => "New subscription")) + assert store = @gateway.store(@visa, @options.merge(:description => 'New subscription')) assert_success store assert purchase = @gateway.purchase(@amount, store.authorization, @options.merge(:order_id => generate_unique_id[0...10])) assert_success purchase @@ -200,8 +197,8 @@ def test_successful_store_and_reference_purchase def test_invalid_login gateway = QuickpayGateway.new( - :login => '', - :password => '' + :login => '999999999', + :password => '' ) assert response = gateway.purchase(@amount, @visa, @options) assert_equal 'Invalid merchant id', response.message diff --git a/test/remote/gateways/remote_quickpay_v6_test.rb b/test/remote/gateways/remote_quickpay_v6_test.rb index fd1a4b93952..2d113657c0c 100644 --- a/test/remote/gateways/remote_quickpay_v6_test.rb +++ b/test/remote/gateways/remote_quickpay_v6_test.rb @@ -4,7 +4,7 @@ class RemoteQuickpayV6Test < Test::Unit::TestCase # These test assumes that you have not added your development IP in # the Quickpay Manager. def setup - @gateway = QuickpayGateway.new(fixtures(:quickpay_with_api_key)) + @gateway = QuickpayGateway.new(fixtures(:quickpay_with_api_key).merge(:version => 6)) @amount = 100 @options = { @@ -24,9 +24,7 @@ def setup @mastercard_dk = credit_card('5413031000000000') @amex_dk = credit_card('3747100000000000') @amex = credit_card('3700100000000000') - - # forbrugsforeningen doesn't use a verification value - @forbrugsforeningen = credit_card('6007221000000000', :verification_value => nil) + @fbg1886 = credit_card('6007221000000000') end def test_successful_purchase @@ -41,10 +39,10 @@ def test_successful_purchase_with_all_fraud_parameters @options[:fraud_http_referer] = 'http://www.excample.com' @options[:fraud_remote_addr] = '127.0.0.1' @options[:fraud_http_accept] = 'foo' - @options[:fraud_http_accept_language] = "DK" - @options[:fraud_http_accept_encoding] = "UFT8" - @options[:fraud_http_accept_charset] = "Latin" - @options[:fraud_http_user_agent] = "Safari" + @options[:fraud_http_accept_language] = 'DK' + @options[:fraud_http_accept_encoding] = 'UFT8' + @options[:fraud_http_accept_charset] = 'Latin' + @options[:fraud_http_user_agent] = 'Safari' assert response = @gateway.purchase(@amount, @visa, @options) assert_equal 'OK', response.message @@ -53,8 +51,6 @@ def test_successful_purchase_with_all_fraud_parameters assert !response.authorization.blank? end - - def test_successful_usd_purchase assert response = @gateway.purchase(@amount, @visa, @options.update(:currency => 'USD')) assert_equal 'OK', response.message @@ -74,6 +70,7 @@ def test_successful_visa_dankort_authorization assert response = @gateway.authorize(@amount, @visa_dankort, @options) assert_success response assert !response.authorization.blank? + # A Visa-Dankort is considered a Dankort when processed by Nets assert_equal 'dankort', response.params['cardtype'] end @@ -134,7 +131,7 @@ def test_successful_american_express_authorization end def test_successful_forbrugsforeningen_authorization - assert response = @gateway.authorize(@amount, @forbrugsforeningen, @options) + assert response = @gateway.authorize(@amount, @fbg1886, @options) assert_success response assert !response.authorization.blank? assert_equal 'fbg1886', response.params['cardtype'] @@ -192,7 +189,7 @@ def test_successful_purchase_and_credit end def test_successful_store_and_reference_purchase - assert store = @gateway.store(@visa, @options.merge(:description => "New subscription")) + assert store = @gateway.store(@visa, @options.merge(:description => 'New subscription')) assert_success store assert purchase = @gateway.purchase(@amount, store.authorization, @options.merge(:order_id => generate_unique_id[0...10])) assert_success purchase @@ -200,8 +197,8 @@ def test_successful_store_and_reference_purchase def test_invalid_login gateway = QuickpayGateway.new( - :login => '', - :password => '' + :login => '999999999', + :password => '' ) assert response = gateway.purchase(@amount, @visa, @options) assert_equal 'Invalid merchant id', response.message diff --git a/test/remote/gateways/remote_quickpay_v7_test.rb b/test/remote/gateways/remote_quickpay_v7_test.rb new file mode 100644 index 00000000000..8b8def4f168 --- /dev/null +++ b/test/remote/gateways/remote_quickpay_v7_test.rb @@ -0,0 +1,229 @@ +require 'test_helper' + +class RemoteQuickpayV7Test < Test::Unit::TestCase + # These test assumes that you have not added your development IP in + # the Quickpay Manager. + def setup + @gateway = QuickpayGateway.new(fixtures(:quickpay_with_api_key)) + + @amount = 100 + @options = { + :order_id => generate_unique_id[0...10], + :billing_address => address + } + + @visa_no_cvv2 = credit_card('4000300011112220', :verification_value => nil) + @visa = credit_card('4000100011112224') + @dankort = credit_card('5019717010103742') + @visa_dankort = credit_card('4571100000000000') + @electron_dk = credit_card('4175001000000000') + @diners_club = credit_card('30401000000000') + @diners_club_dk = credit_card('36148010000000') + @maestro = credit_card('5020100000000000') + @maestro_dk = credit_card('6769271000000000') + @mastercard_dk = credit_card('5413031000000000') + @amex_dk = credit_card('3747100000000000') + @amex = credit_card('3700100000000000') + @fbg1886 = credit_card('6007221000000000') + end + + def test_successful_purchase + assert response = @gateway.purchase(@amount, @visa, @options) + assert_equal 'OK', response.message + assert_equal 'DKK', response.params['currency'] + assert_success response + assert !response.authorization.blank? + end + + def test_successful_purchase_with_all_fraud_parameters + @options[:ip] = '127.0.0.1' # will set :fraud_remote_addr + @options[:fraud_http_referer] = 'http://www.excample.com' + @options[:fraud_http_accept] = 'foo' + @options[:fraud_http_accept_language] = 'DK' + @options[:fraud_http_accept_encoding] = 'UFT8' + @options[:fraud_http_accept_charset] = 'Latin' + @options[:fraud_http_user_agent] = 'Safari' + + assert response = @gateway.purchase(@amount, @visa, @options) + assert_equal 'OK', response.message + assert_equal 'DKK', response.params['currency'] + assert_success response + assert !response.authorization.blank? + end + + def test_successful_usd_purchase + assert response = @gateway.purchase(@amount, @visa, @options.update(:currency => 'USD')) + assert_equal 'OK', response.message + assert_equal 'USD', response.params['currency'] + assert_success response + assert !response.authorization.blank? + end + + def test_successful_purchase_with_acquirers + assert response = @gateway.purchase(@amount, @visa, @options.update(:acquirers => 'nets')) + assert_equal 'OK', response.message + assert_success response + end + + def test_unsuccessful_purchase_with_invalid_acquirers + assert response = @gateway.purchase(@amount, @visa, @options.update(:acquirers => 'invalid')) + assert_equal 'Error in field: acquirers', response.message + assert_failure response + end + + def test_successful_dankort_authorization + assert response = @gateway.authorize(@amount, @dankort, @options) + assert_success response + assert !response.authorization.blank? + assert_equal 'dankort', response.params['cardtype'] + end + + def test_successful_visa_dankort_authorization + assert response = @gateway.authorize(@amount, @visa_dankort, @options) + assert_success response + assert !response.authorization.blank? + # A Visa-Dankort is considered a Dankort when processed by Nets + assert_equal 'dankort', response.params['cardtype'] + end + + def test_successful_visa_electron_authorization + assert response = @gateway.authorize(@amount, @electron_dk, @options) + assert_success response + assert !response.authorization.blank? + assert_equal 'visa-electron-dk', response.params['cardtype'] + end + + def test_successful_diners_club_authorization + assert response = @gateway.authorize(@amount, @diners_club, @options) + assert_success response + assert !response.authorization.blank? + assert_equal 'diners', response.params['cardtype'] + end + + def test_successful_diners_club_dk_authorization + assert response = @gateway.authorize(@amount, @diners_club_dk, @options) + assert_success response + assert !response.authorization.blank? + assert_equal 'diners-dk', response.params['cardtype'] + end + + def test_successful_maestro_authorization + assert response = @gateway.authorize(@amount, @maestro, @options) + assert_success response + assert !response.authorization.blank? + assert_equal 'maestro', response.params['cardtype'] + end + + def test_successful_maestro_dk_authorization + assert response = @gateway.authorize(@amount, @maestro_dk, @options) + assert_success response + assert !response.authorization.blank? + assert_equal 'maestro-dk', response.params['cardtype'] + end + + def test_successful_mastercard_dk_authorization + assert response = @gateway.authorize(@amount, @mastercard_dk, @options) + assert_success response + assert !response.authorization.blank? + assert_equal 'mastercard-dk', response.params['cardtype'] + end + + def test_successful_american_express_dk_authorization + assert response = @gateway.authorize(@amount, @amex_dk, @options) + assert_success response + assert !response.authorization.blank? + assert_equal 'american-express-dk', response.params['cardtype'] + end + + def test_successful_american_express_authorization + assert response = @gateway.authorize(@amount, @amex, @options) + assert_success response + assert !response.authorization.blank? + assert_equal 'american-express', response.params['cardtype'] + end + + def test_successful_forbrugsforeningen_authorization + assert response = @gateway.authorize(@amount, @fbg1886, @options) + assert_success response + assert !response.authorization.blank? + assert_equal 'fbg1886', response.params['cardtype'] + end + + def test_unsuccessful_purchase_with_missing_cvv2 + assert response = @gateway.purchase(@amount, @visa_no_cvv2, @options) + # Quickpay has made the cvd field optional in order to support forbrugsforeningen cards which don't have them + assert_equal 'OK', response.message + assert_success response + assert !response.authorization.blank? + end + + def test_successful_authorize_and_capture + assert auth = @gateway.authorize(@amount, @visa, @options) + assert_success auth + assert_equal 'OK', auth.message + assert auth.authorization + assert capture = @gateway.capture(@amount, auth.authorization) + assert_success capture + assert_equal 'OK', capture.message + end + + def test_failed_capture + assert response = @gateway.capture(@amount, '') + assert_failure response + assert_equal 'Missing field: transaction', response.message + end + + def test_successful_purchase_and_void + assert auth = @gateway.authorize(@amount, @visa, @options) + assert_success auth + assert_equal 'OK', auth.message + assert auth.authorization + assert void = @gateway.void(auth.authorization) + assert_success void + assert_equal 'OK', void.message + end + + def test_successful_authorization_capture_and_credit + assert auth = @gateway.authorize(@amount, @visa, @options) + assert_success auth + assert capture = @gateway.capture(@amount, auth.authorization) + assert_success capture + assert credit = @gateway.refund(@amount, auth.authorization) + assert_success credit + assert_equal 'OK', credit.message + end + + def test_successful_purchase_and_credit + assert purchase = @gateway.purchase(@amount, @visa, @options) + assert_success purchase + assert credit = @gateway.refund(@amount, purchase.authorization) + assert_success credit + end + + def test_successful_store_and_reference_purchase + assert store = @gateway.store(@visa, @options.merge(:description => 'New subscription')) + assert_success store + assert purchase = @gateway.purchase(@amount, store.authorization, @options.merge(:order_id => generate_unique_id[0...10])) + assert_success purchase + end + + def test_successful_store_with_acquirers + assert store = @gateway.store(@visa, @options.merge(:description => 'New subscription', :acquirers => 'nets')) + assert_success store + end + + def test_successful_store_sans_description + assert store = @gateway.store(@visa, @options.merge(:acquirers => 'nets')) + assert_success store + end + + def test_invalid_login + gateway = QuickpayGateway.new( + :login => '999999999', + :password => '' + ) + assert response = gateway.purchase(@amount, @visa, @options) + assert_equal 'Invalid merchant id', response.message + assert_failure response + end +end diff --git a/test/remote/gateways/remote_qvalent_test.rb b/test/remote/gateways/remote_qvalent_test.rb new file mode 100644 index 00000000000..95653323048 --- /dev/null +++ b/test/remote/gateways/remote_qvalent_test.rb @@ -0,0 +1,242 @@ +require 'test_helper' + +class RemoteQvalentTest < Test::Unit::TestCase + def setup + @gateway = QvalentGateway.new(fixtures(:qvalent)) + + @amount = 100 + @credit_card = credit_card('4242424242424242') + @mastercard = credit_card('5163200000000008', brand: 'master') + @declined_card = credit_card('4000000000000000') + @expired_card = credit_card('4111111113444494') + + @options = { + order_id: generate_unique_id, + billing_address: address, + description: 'Store Purchase' + } + end + + def test_invalid_login + gateway = QvalentGateway.new( + username: 'bad', + password: 'bad', + merchant: '101', + pem: 'bad', + pem_password: 'bad' + ) + + assert_raise OpenSSL::X509::CertificateError do + gateway.purchase(@amount, @credit_card, @options) + end + end + + def test_successful_purchase + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal 'Succeeded', response.message + end + + def test_successful_purchase_with_soft_descriptors + options = { + order_id: generate_unique_id, + billing_address: address, + description: 'Store Purchase', + customer_merchant_name: 'Some Merchant', + customer_merchant_street_address: '42 Wallaby Way', + customer_merchant_location: 'Sydney', + customer_merchant_country: 'AU', + customer_merchant_post_code: '2060', + customer_merchant_state: 'NSW' + } + + response = @gateway.purchase(@amount, @credit_card, options) + assert_success response + assert_equal 'Succeeded', response.message + end + + def test_successful_purchase_with_3d_secure + options = { + order_id: generate_unique_id, + billing_address: address, + description: 'Store Purchase', + xid: '123', + cavv: '456', + eci: '5' + } + + response = @gateway.purchase(@amount, @credit_card, options) + assert_success response + assert_equal 'Succeeded', response.message + end + + def test_failed_purchase + response = @gateway.purchase(@amount, @declined_card, @options) + assert_failure response + assert_equal 'Invalid card number (no such number)', response.message + assert_equal Gateway::STANDARD_ERROR_CODE[:invalid_number], response.error_code + end + + def test_successful_authorize + response = @gateway.authorize(@amount, @credit_card, @options) + assert_success response + assert_equal 'Succeeded', response.message + end + + def test_failed_authorize + response = @gateway.authorize(@amount, @declined_card, @options) + assert_failure response + assert_equal 'Invalid card number (no such number)', response.message + end + + def test_successful_capture + assert auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + assert_equal 'Succeeded', auth.message + assert_not_nil auth.authorization + + assert capture = @gateway.capture(@amount, auth.authorization, @options.merge({ order_id: generate_unique_id })) + assert_success capture + end + + def test_failed_capture + assert auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + assert_equal 'Succeeded', auth.message + assert_not_nil auth.authorization + + assert capture = @gateway.capture(@amount, '', @options.merge({ order_id: generate_unique_id })) + assert_failure capture + end + + def test_successful_partial_capture + assert auth = @gateway.authorize(200, @credit_card, @options) + assert_success auth + assert_equal 'Succeeded', auth.message + assert_not_nil auth.authorization + + assert capture = @gateway.capture(100, auth.authorization, @options.merge({ order_id: generate_unique_id })) + assert_success capture + end + + def test_successful_void + assert auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + assert_equal 'Succeeded', auth.message + assert_not_nil auth.authorization + + assert void = @gateway.void(auth.authorization, @options.merge({ order_id: generate_unique_id })) + assert_success void + end + + def test_failed_void + assert auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + assert_equal 'Succeeded', auth.message + assert_not_nil auth.authorization + + assert void = @gateway.void('', @options.merge({ order_id: generate_unique_id })) + assert_failure void + end + + def test_successful_refund + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + + assert refund = @gateway.refund(@amount, response.authorization) + assert_success refund + assert_equal 'Succeeded', refund.message + end + + def test_failed_refund + response = @gateway.refund(nil, '') + assert_failure response + assert_match %r{Invalid card number}, response.message + assert_equal Gateway::STANDARD_ERROR_CODE[:invalid_number], response.error_code + end + + def test_successful_credit + response = @gateway.credit(@amount, @credit_card, @options) + assert_success response + assert_equal 'Succeeded', response.message + end + + def test_failed_credit + response = @gateway.credit(@amount, @declined_card, @options) + assert_failure response + assert_equal 'Invalid card number (no such number)', response.message + end + + def test_successful_store + response = @gateway.store(@credit_card, @options) + assert_success response + assert_equal 'Succeeded', response.message + end + + def test_failed_store + response = @gateway.store(@declined_card, @options) + assert_failure response + assert_equal 'Invalid card number (no such number)', response.message + assert_equal Gateway::STANDARD_ERROR_CODE[:invalid_number], response.error_code + end + + def test_transcript_scrubbing + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @credit_card, @options) + end + clean_transcript = @gateway.scrub(transcript) + + assert_scrubbed(@credit_card.number, clean_transcript) + assert_scrubbed(@credit_card.verification_value, clean_transcript) + assert_scrubbed(@gateway.options[:password], clean_transcript) + end + + def test_successful_purchase_initial + stored_credential = { + stored_credential: { + initial_transaction: true, + initiator: 'merchant', + reason_type: 'unscheduled' + } + } + + response = @gateway.purchase(@amount, @credit_card, @options.merge(stored_credential)) + + assert_success response + assert_equal 'Succeeded', response.message + assert_not_nil response.params['response.authTraceId'] + end + + def test_successful_purchase_cardholder + stored_credential = { + stored_credential: { + initial_transaction: false, + initiator: 'cardholder', + reason_type: 'unscheduled', + network_transaction_id: 'qwerty7890' + } + } + + response = @gateway.purchase(@amount, @credit_card, @options.merge(stored_credential)) + + assert_success response + assert_equal 'Succeeded', response.message + end + + def test_successful_purchase_mastercard + stored_credential = { + stored_credential: { + initial_transaction: false, + initiator: 'merchant', + reason_type: 'recurring', + network_transaction_id: 'qwerty7890' + } + } + + response = @gateway.purchase(@amount, @mastercard, @options.merge(stored_credential)) + + assert_success response + assert_equal 'Succeeded', response.message + end + +end diff --git a/test/remote/gateways/remote_realex_test.rb b/test/remote/gateways/remote_realex_test.rb index 3f30d9f7122..92b415ec0c2 100644 --- a/test/remote/gateways/remote_realex_test.rb +++ b/test/remote/gateways/remote_realex_test.rb @@ -3,14 +3,15 @@ class RemoteRealexTest < Test::Unit::TestCase def setup - @gateway = RealexGateway.new(fixtures(:realex)) + @gateway = RealexGateway.new(fixtures(:realex_with_account)) # Replace the card numbers with the test account numbers from Realex - @visa = card_fixtures(:realex_visa) - @visa_declined = card_fixtures(:realex_visa_declined) - @visa_referral_b = card_fixtures(:realex_visa_referral_b) - @visa_referral_a = card_fixtures(:realex_visa_referral_a) - @visa_coms_error = card_fixtures(:realex_visa_coms_error) + @visa = card_fixtures(:realex_visa) + @visa_declined = card_fixtures(:realex_visa_declined) + @visa_referral_b = card_fixtures(:realex_visa_referral_b) + @visa_referral_a = card_fixtures(:realex_visa_referral_a) + @visa_coms_error = card_fixtures(:realex_visa_coms_error) + @visa_3ds_enrolled = card_fixtures(:realex_visa_3ds_enrolled) @mastercard = card_fixtures(:realex_mastercard) @mastercard_declined = card_fixtures(:realex_mastercard_declined) @@ -18,6 +19,19 @@ def setup @mastercard_referral_a = card_fixtures(:realex_mastercard_referral_a) @mastercard_coms_error = card_fixtures(:realex_mastercard_coms_error) + @apple_pay = network_tokenization_credit_card('4242424242424242', + payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=', + verification_value: nil, + eci: '05', + source: :apple_pay + ) + + @declined_apple_pay = network_tokenization_credit_card('4000120000001154', + payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=', + verification_value: nil, + eci: '05', + source: :apple_pay + ) @amount = 10000 end @@ -27,7 +41,6 @@ def card_fixtures(name) def test_realex_purchase [ @visa, @mastercard ].each do |card| - response = @gateway.purchase(@amount, card, :order_id => generate_unique_id, :description => 'Test Realex Purchase', @@ -40,7 +53,7 @@ def test_realex_purchase assert_success response assert response.test? assert response.authorization.length > 0 - assert_equal "Successful", response.message + assert_equal 'Successful', response.message end end @@ -58,26 +71,31 @@ def test_realex_purchase_with_invalid_login assert_failure response assert_equal '504', response.params['result'] - assert_equal "There is no such merchant id. Please contact realex payments if you continue to experience this problem.", response.message + assert_match %r{no such}i, response.message end def test_realex_purchase_with_invalid_account - response = RealexGateway.new(fixtures(:realex_with_account)).purchase(@amount, @visa, + response = RealexGateway.new(fixtures(:realex_with_account).merge(account: 'invalid')).purchase(@amount, @visa, :order_id => generate_unique_id, - :description => 'Test Realex purchase with invalid acocunt' + :description => 'Test Realex purchase with invalid account' ) assert_not_nil response assert_failure response assert_equal '506', response.params['result'] - assert_equal "There is no such merchant account. Please contact realex payments if you continue to experience this problem.", response.message + assert_match %r{no such}i, response.message end - def test_realex_purchase_declined + def test_realex_purchase_with_apple_pay + response = @gateway.purchase(1000, @apple_pay, :order_id => generate_unique_id, :description => 'Test Realex with ApplePay') + assert_success response + assert response.test? + assert_equal 'Successful', response.message + end + def test_realex_purchase_declined [ @visa_declined, @mastercard_declined ].each do |card| - response = @gateway.purchase(@amount, card, :order_id => generate_unique_id, :description => 'Test Realex purchase declined' @@ -88,12 +106,54 @@ def test_realex_purchase_declined assert_equal '101', response.params['result'] assert_equal response.params['message'], response.message end + end + def test_realex_purchase_with_apple_pay_declined + response = @gateway.purchase(1101, @declined_apple_pay, :order_id => generate_unique_id, :description => 'Test Realex with ApplePay') + assert_failure response + assert response.test? + assert_equal '101', response.params['result'] + assert_match %r{DECLINED}i, response.message + end + + def test_realex_purchase_with_three_d_secure_1 + response = @gateway.purchase( + 1000, + @visa_3ds_enrolled, + three_d_secure: { + eci: '05', + cavv: 'AgAAAAAAAIR8CQrXcIhbQAAAAAA', + xid: 'MDAwMDAwMDAwMDAwMDAwMzIyNzY=', + version: '1.0.2', + }, + :order_id => generate_unique_id, + :description => 'Test Realex with 3DS' + ) + assert_success response + assert response.test? + assert_equal 'Successful', response.message + end + + def test_realex_purchase_with_three_d_secure_2 + response = @gateway.purchase( + 1000, + @visa_3ds_enrolled, + three_d_secure: { + eci: '05', + cavv: 'AgAAAAAAAIR8CQrXcIhbQAAAAAA', + ds_transaction_id: 'bDE9Aa1A-C5Ac-AD3a-4bBC-aC918ab1de3E', + version: '2.1.0', + }, + :order_id => generate_unique_id, + :description => 'Test Realex with 3DS' + ) + assert_success response + assert response.test? + assert_equal 'Successful', response.message end def test_realex_purchase_referral_b [ @visa_referral_b, @mastercard_referral_b ].each do |card| - response = @gateway.purchase(@amount, card, :order_id => generate_unique_id, :description => 'Test Realex Referral B' @@ -108,7 +168,6 @@ def test_realex_purchase_referral_b def test_realex_purchase_referral_a [ @visa_referral_a, @mastercard_referral_a ].each do |card| - response = @gateway.purchase(@amount, card, :order_id => generate_unique_id, :description => 'Test Realex Rqeferral A' @@ -118,39 +177,20 @@ def test_realex_purchase_referral_a assert_equal '103', response.params['result'] assert_equal RealexGateway::DECLINED, response.message end - end def test_realex_purchase_coms_error - [ @visa_coms_error, @mastercard_coms_error ].each do |card| - response = @gateway.purchase(@amount, card, :order_id => generate_unique_id, :description => 'Test Realex coms error' ) - assert_not_nil response assert_failure response assert_equal '200', response.params['result'] assert_equal RealexGateway::BANK_ERROR, response.message end - - end - - def test_realex_ccn_error - @visa.number = '5' - - response = @gateway.purchase(@amount, @visa, - :order_id => generate_unique_id, - :description => 'Test Realex ccn error' - ) - assert_not_nil response - assert_failure response - - assert_equal '508', response.params['result'] - assert_match(/invalid/i, response.message) end def test_realex_expiry_month_error @@ -164,7 +204,7 @@ def test_realex_expiry_month_error assert_failure response assert_equal '509', response.params['result'] - assert_equal "Expiry date invalid", response.message + assert_match %r{invalid}i, response.message end def test_realex_expiry_year_error @@ -178,12 +218,12 @@ def test_realex_expiry_year_error assert_failure response assert_equal '509', response.params['result'] - assert_equal "Expiry date invalid", response.message + assert_equal 'Expiry date invalid', response.message end def test_invalid_credit_card_name - @visa.first_name = "" - @visa.last_name = "" + @visa.first_name = '' + @visa.last_name = '' response = @gateway.purchase(@amount, @visa, :order_id => generate_unique_id, @@ -192,13 +232,13 @@ def test_invalid_credit_card_name assert_not_nil response assert_failure response - assert_equal '502', response.params['result'] - assert_match(/mandatory field not present/i, response.message) + assert_equal '506', response.params['result'] + assert_match(/does not conform/i, response.message) end def test_cvn @visa_cvn = @visa.clone - @visa_cvn.verification_value = "111" + @visa_cvn.verification_value = '111' response = @gateway.purchase(@amount, @visa_cvn, :order_id => generate_unique_id, :description => 'test_cvn' @@ -247,12 +287,34 @@ def test_realex_authorize_then_capture :country => 'US' } ) + assert auth_response.test? + + capture_response = @gateway.capture(nil, auth_response.authorization) + + assert_not_nil capture_response + assert_success capture_response + assert capture_response.authorization.length > 0 + assert_equal 'Successful', capture_response.message + assert_match(/Settled Successfully/, capture_response.params['message']) + end + + def test_realex_authorize_then_capture_with_extra_amount + order_id = generate_unique_id + + auth_response = @gateway.authorize(@amount*115, @visa, + :order_id => order_id, + :description => 'Test Realex Purchase', + :billing_address => { + :zip => '90210', + :country => 'US' + } + ) + assert auth_response.test? capture_response = @gateway.capture(@amount, auth_response.authorization) assert_not_nil capture_response assert_success capture_response - assert capture_response.test? assert capture_response.authorization.length > 0 assert_equal 'Successful', capture_response.message assert_match(/Settled Successfully/, capture_response.params['message']) @@ -269,13 +331,12 @@ def test_realex_purchase_then_void :country => 'US' } ) + assert purchase_response.test? void_response = @gateway.void(purchase_response.authorization) assert_not_nil void_response assert_success void_response - assert void_response.test? - assert void_response.authorization.length > 0 assert_equal 'Successful', void_response.message assert_match(/Voided Successfully/, void_response.params['message']) end @@ -283,7 +344,7 @@ def test_realex_purchase_then_void def test_realex_purchase_then_refund order_id = generate_unique_id - gateway_with_refund_password = RealexGateway.new(fixtures(:realex).merge(:rebate_secret => 'refund')) + gateway_with_refund_password = RealexGateway.new(fixtures(:realex).merge(:rebate_secret => 'rebate')) purchase_response = gateway_with_refund_password.purchase(@amount, @visa, :order_id => order_id, @@ -293,14 +354,107 @@ def test_realex_purchase_then_refund :country => 'US' } ) + assert purchase_response.test? rebate_response = gateway_with_refund_password.refund(@amount, purchase_response.authorization) assert_not_nil rebate_response assert_success rebate_response - assert rebate_response.test? assert rebate_response.authorization.length > 0 assert_equal 'Successful', rebate_response.message end + def test_realex_verify + response = @gateway.verify(@visa, + :order_id => generate_unique_id, + :description => 'Test Realex verify' + ) + + assert_not_nil response + assert_success response + assert response.test? + assert response.authorization.length > 0 + assert_equal 'Successful', response.message + end + + def test_realex_verify_declined + response = @gateway.verify(@visa_declined, + :order_id => generate_unique_id, + :description => 'Test Realex verify declined' + ) + + assert_not_nil response + assert_failure response + assert response.test? + assert_equal '101', response.params['result'] + assert_match %r{DECLINED}i, response.message + end + + def test_successful_credit + gateway_with_refund_password = RealexGateway.new(fixtures(:realex).merge(:refund_secret => 'refund')) + + credit_response = gateway_with_refund_password.credit(@amount, @visa, + :order_id => generate_unique_id, + :description => 'Test Realex Credit', + :billing_address => { + :zip => '90210', + :country => 'US' + } + ) + + assert_not_nil credit_response + assert_success credit_response + assert credit_response.authorization.length > 0 + assert_equal 'Successful', credit_response.message + end + + def test_failed_credit + credit_response = @gateway.credit(@amount, @visa, + :order_id => generate_unique_id, + :description => 'Test Realex Credit', + :billing_address => { + :zip => '90210', + :country => 'US' + } + ) + + assert_not_nil credit_response + assert_failure credit_response + assert credit_response.authorization.length > 0 + assert_equal 'Refund Hash not present.', credit_response.message + end + + def test_maps_avs_and_cvv_response_codes + [ @visa, @mastercard ].each do |card| + response = @gateway.purchase(@amount, card, + :order_id => generate_unique_id, + :description => 'Test Realex Purchase', + :billing_address => { + :zip => '90210', + :country => 'US' + } + ) + assert_not_nil response + assert_success response + assert_equal 'M', response.avs_result['code'] + assert_equal 'M', response.cvv_result['code'] + end + end + + def test_transcript_scrubbing + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @visa_declined, + :order_id => generate_unique_id, + :description => 'Test Realex Purchase', + :billing_address => { + :zip => '90210', + :country => 'US' + } + ) + end + clean_transcript = @gateway.scrub(transcript) + + assert_scrubbed(@visa_declined.number, clean_transcript) + assert_scrubbed(@visa_declined.verification_value.to_s, clean_transcript) + end end diff --git a/test/remote/gateways/remote_redsys_sha256_test.rb b/test/remote/gateways/remote_redsys_sha256_test.rb new file mode 100644 index 00000000000..43718a5f67a --- /dev/null +++ b/test/remote/gateways/remote_redsys_sha256_test.rb @@ -0,0 +1,186 @@ +require 'test_helper' + +class RemoteRedsysSHA256Test < Test::Unit::TestCase + def setup + @gateway = RedsysGateway.new(fixtures(:redsys_sha256)) + @credit_card = credit_card('4548812049400004') + @declined_card = credit_card + @options = { + order_id: generate_order_id, + } + end + + def test_successful_purchase + response = @gateway.purchase(100, @credit_card, @options) + assert_success response + assert_equal 'Transaction Approved', response.message + end + + def test_purchase_with_invalid_order_id + response = @gateway.purchase(100, @credit_card, order_id: "a%4#{generate_order_id}") + assert_success response + assert_equal 'Transaction Approved', response.message + end + + def test_successful_purchase_using_vault_id + response = @gateway.purchase(100, @credit_card, @options.merge(store: true)) + assert_success response + assert_equal 'Transaction Approved', response.message + + credit_card_token = response.params['ds_merchant_identifier'] + assert_not_nil credit_card_token + + @options[:order_id] = generate_order_id + response = @gateway.purchase(100, credit_card_token, @options) + assert_success response + assert_equal 'Transaction Approved', response.message + end + + def test_failed_purchase + response = @gateway.purchase(100, @declined_card, @options) + assert_failure response + assert_equal 'SIS0093 ERROR', response.message + end + + def test_purchase_and_refund + purchase = @gateway.purchase(100, @credit_card, @options) + assert_success purchase + refund = @gateway.refund(100, purchase.authorization) + assert_success refund + end + + # Multiple currencies are not supported in test, but should at least fail. + def test_purchase_and_refund_with_currency + response = @gateway.purchase(600, @credit_card, @options.merge(:currency => 'PEN')) + assert_failure response + assert_equal 'SIS0027 ERROR', response.message + end + + def test_successful_authorise_and_capture + authorize = @gateway.authorize(100, @credit_card, @options) + assert_success authorize + assert_equal 'Transaction Approved', authorize.message + assert_not_nil authorize.authorization + + capture = @gateway.capture(100, authorize.authorization) + assert_success capture + assert_match(/Refund.*approved/, capture.message) + end + + def test_successful_authorise_using_vault_id + authorize = @gateway.authorize(100, @credit_card, @options.merge(store: true)) + assert_success authorize + assert_equal 'Transaction Approved', authorize.message + assert_not_nil authorize.authorization + + credit_card_token = authorize.params['ds_merchant_identifier'] + assert_not_nil credit_card_token + + @options[:order_id] = generate_order_id + authorize = @gateway.authorize(100, credit_card_token, @options) + assert_success authorize + assert_equal 'Transaction Approved', authorize.message + assert_not_nil authorize.authorization + end + + def test_failed_authorize + response = @gateway.authorize(100, @declined_card, @options) + assert_failure response + assert_equal 'SIS0093 ERROR', response.message + end + + def test_successful_void + authorize = @gateway.authorize(100, @credit_card, @options) + assert_success authorize + + void = @gateway.void(authorize.authorization) + assert_success void + assert_equal '100', void.params['ds_amount'] + assert_equal 'Cancellation Accepted', void.message + end + + def test_failed_void + authorize = @gateway.authorize(100, @credit_card, @options) + assert_success authorize + + void = @gateway.void(authorize.authorization) + assert_success void + + another_void = @gateway.void(authorize.authorization) + assert_failure another_void + assert_equal 'SIS0222 ERROR', another_void.message + end + + def test_successful_verify + assert response = @gateway.verify(@credit_card, @options) + assert_success response + + assert_equal 'Transaction Approved', response.message + assert_success response.responses.last, 'The void should succeed' + assert_equal 'Cancellation Accepted', response.responses.last.message + end + + def test_unsuccessful_verify + assert response = @gateway.verify(@declined_card, @options) + assert_failure response + assert_equal 'SIS0093 ERROR', response.message + end + + def test_transcript_scrubbing + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @credit_card, @options) + end + clean_transcript = @gateway.scrub(transcript) + + assert_scrubbed(@gateway.options[:secret_key], clean_transcript) + assert_scrubbed(@credit_card.number, clean_transcript) + assert_scrubbed(@credit_card.verification_value.to_s, clean_transcript) + end + + def test_transcript_scrubbing_on_failed_transactions + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @declined_card, @options) + end + clean_transcript = @gateway.scrub(transcript) + + assert_scrubbed(@gateway.options[:secret_key], clean_transcript) + assert_scrubbed(@credit_card.number, clean_transcript) + assert_scrubbed(@credit_card.verification_value.to_s, clean_transcript) + end + + def test_nil_cvv_transcript_scrubbing + @credit_card.verification_value = nil + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @credit_card, @options) + end + clean_transcript = @gateway.scrub(transcript) + + assert_equal clean_transcript.include?('[BLANK]'), true + end + + def test_empty_string_cvv_transcript_scrubbing + @credit_card.verification_value = '' + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @credit_card, @options) + end + clean_transcript = @gateway.scrub(transcript) + + assert_equal clean_transcript.include?('[BLANK]'), true + end + + def test_whitespace_string_cvv_transcript_scrubbing + @credit_card.verification_value = ' ' + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @credit_card, @options) + end + clean_transcript = @gateway.scrub(transcript) + + assert_equal clean_transcript.include?('[BLANK]'), true + end + + private + + def generate_order_id + (Time.now.to_f * 100).to_i.to_s + end +end diff --git a/test/remote/gateways/remote_redsys_test.rb b/test/remote/gateways/remote_redsys_test.rb index 4c5afbd5883..cfaf52723b2 100644 --- a/test/remote/gateways/remote_redsys_test.rb +++ b/test/remote/gateways/remote_redsys_test.rb @@ -5,54 +5,179 @@ def setup @gateway = RedsysGateway.new(fixtures(:redsys)) @credit_card = credit_card('4548812049400004') @declined_card = credit_card + @options = { + order_id: generate_order_id, + description: 'Test Description' + } + @amount = 100 end def test_successful_purchase - order_id = generate_order_id - result = @gateway.purchase(100, @credit_card, :order_id => order_id) - assert_success result - assert_equal "#{order_id}|100|978", result.authorization + response = @gateway.purchase(100, @credit_card, @options) + assert_success response + assert_equal 'Transaction Approved', response.message + end + + def test_purchase_with_invalid_order_id + response = @gateway.purchase(100, @credit_card, order_id: "a%4#{generate_order_id}") + assert_success response + assert_equal 'Transaction Approved', response.message + end + + def test_successful_purchase_using_vault_id + response = @gateway.purchase(100, @credit_card, @options.merge(store: true)) + assert_success response + assert_equal 'Transaction Approved', response.message + + credit_card_token = response.params['ds_merchant_identifier'] + assert_not_nil credit_card_token + + @options[:order_id] = generate_order_id + response = @gateway.purchase(100, credit_card_token, @options) + assert_success response + assert_equal 'Transaction Approved', response.message end def test_failed_purchase - order_id = generate_order_id - result = @gateway.purchase(100, @declined_card, :order_id => order_id) - assert_failure result - assert_nil result.authorization + response = @gateway.purchase(100, @declined_card, @options) + assert_failure response + assert_equal 'SIS0093 ERROR', response.message end def test_purchase_and_refund - order_id = generate_order_id - result = @gateway.purchase(100, @credit_card, :order_id => order_id) - assert_success result - result = @gateway.refund(100, order_id) - assert_success result + purchase = @gateway.purchase(100, @credit_card, @options) + assert_success purchase + refund = @gateway.refund(100, purchase.authorization) + assert_success refund end # Multiple currencies are not supported in test, but should at least fail. def test_purchase_and_refund_with_currency - order_id = generate_order_id - result = @gateway.purchase(600, @credit_card, :order_id => order_id, :currency => 'PEN') - assert_failure result - assert_equal "SIS0027 ERROR", result.message - end - - def test_authorise_and_capture - order_id = generate_order_id - result = @gateway.authorize(100, @credit_card, :order_id => order_id) - assert_success result - assert_equal "#{order_id}|100|978", result.authorization - result = @gateway.capture(100, order_id) - assert_success result - end - - def test_authorise_and_void - order_id = generate_order_id - result = @gateway.authorize(100, @credit_card, :order_id => order_id) - assert_success result - result = @gateway.void(result.authorization) - assert_success result - assert_equal "100", result.params["ds_amount"] + response = @gateway.purchase(600, @credit_card, @options.merge(:currency => 'PEN')) + assert_failure response + assert_equal 'SIS0027 ERROR', response.message + end + + def test_successful_authorise_and_capture + authorize = @gateway.authorize(100, @credit_card, @options) + assert_success authorize + assert_equal 'Transaction Approved', authorize.message + assert_not_nil authorize.authorization + + capture = @gateway.capture(100, authorize.authorization) + assert_success capture + assert_match(/Refund.*approved/, capture.message) + end + + def test_successful_authorise_using_vault_id + authorize = @gateway.authorize(100, @credit_card, @options.merge(store: true)) + assert_success authorize + assert_equal 'Transaction Approved', authorize.message + assert_not_nil authorize.authorization + + credit_card_token = authorize.params['ds_merchant_identifier'] + assert_not_nil credit_card_token + + @options[:order_id] = generate_order_id + authorize = @gateway.authorize(100, credit_card_token, @options) + assert_success authorize + assert_equal 'Transaction Approved', authorize.message + assert_not_nil authorize.authorization + end + + def test_failed_authorize + response = @gateway.authorize(100, @declined_card, @options) + assert_failure response + assert_equal 'SIS0093 ERROR', response.message + end + + def test_successful_void + authorize = @gateway.authorize(100, @credit_card, @options) + assert_success authorize + + void = @gateway.void(authorize.authorization) + assert_success void + assert_equal '100', void.params['ds_amount'] + assert_equal 'Cancellation Accepted', void.message + end + + def test_failed_void + authorize = @gateway.authorize(100, @credit_card, @options) + assert_success authorize + + void = @gateway.void(authorize.authorization) + assert_success void + + another_void = @gateway.void(authorize.authorization) + assert_failure another_void + assert_equal 'SIS0222 ERROR', another_void.message + end + + def test_successful_verify + assert response = @gateway.verify(@credit_card, @options) + assert_success response + + assert_equal 'Transaction Approved', response.message + assert_success response.responses.last, 'The void should succeed' + assert_equal 'Cancellation Accepted', response.responses.last.message + end + + def test_unsuccessful_verify + assert response = @gateway.verify(@declined_card, @options) + assert_failure response + assert_equal 'SIS0093 ERROR', response.message + end + + def test_transcript_scrubbing + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @credit_card, @options) + end + clean_transcript = @gateway.scrub(transcript) + + assert_scrubbed(@gateway.options[:secret_key], clean_transcript) + assert_scrubbed(@credit_card.number, clean_transcript) + assert_scrubbed(@credit_card.verification_value.to_s, clean_transcript) + end + + def test_transcript_scrubbing_on_failed_transactions + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @declined_card, @options) + end + clean_transcript = @gateway.scrub(transcript) + + assert_scrubbed(@gateway.options[:secret_key], clean_transcript) + assert_scrubbed(@credit_card.number, clean_transcript) + assert_scrubbed(@credit_card.verification_value.to_s, clean_transcript) + end + + def test_nil_cvv_transcript_scrubbing + @credit_card.verification_value = nil + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @credit_card, @options) + end + clean_transcript = @gateway.scrub(transcript) + + assert_equal clean_transcript.include?('[BLANK]'), true + end + + def test_empty_string_cvv_transcript_scrubbing + @credit_card.verification_value = '' + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @credit_card, @options) + end + clean_transcript = @gateway.scrub(transcript) + + assert_equal clean_transcript.include?('[BLANK]'), true + end + + def test_whitespace_string_cvv_transcript_scrubbing + @credit_card.verification_value = ' ' + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @credit_card, @options) + end + clean_transcript = @gateway.scrub(transcript) + + assert_equal clean_transcript.include?('[BLANK]'), true end private diff --git a/test/remote/gateways/remote_s5_test.rb b/test/remote/gateways/remote_s5_test.rb new file mode 100644 index 00000000000..1c9f31bf0ab --- /dev/null +++ b/test/remote/gateways/remote_s5_test.rb @@ -0,0 +1,181 @@ +require 'test_helper' + +class RemoteS5Test < Test::Unit::TestCase + def setup + @gateway = S5Gateway.new(fixtures(:s5)) + + @amount = 100 + @credit_card = credit_card('4000100011112224') + @declined_card = credit_card('4000300011112220') + + @options = { + order_id: '1', + billing_address: address, + description: 'Store Purchase' + } + end + + def test_transcript_scrubbing + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @credit_card, @options) + end + transcript = @gateway.scrub(transcript) + + assert_scrubbed(@credit_card.number, transcript) + assert_scrubbed(@credit_card.verification_value, transcript) + assert_scrubbed(@gateway.options[:password], transcript) + end + + def test_successful_purchase + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_match %r{Request successfully processed}, response.message + end + + def test_successful_purchase_sans_cvv + @options[:recurring] = true + @credit_card.verification_value = nil + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_match %r{Request successfully processed}, response.message + end + + def test_successful_purchase_with_utf_character + card = credit_card('4000100011112224', last_name: 'Wåhlin') + response = @gateway.purchase(@amount, card, @options) + assert_success response + assert_match %r{Request successfully processed}, response.message + end + + def test_successful_purchase_without_address + response = @gateway.purchase(@amount, @credit_card, {}) + assert_success response + assert_match %r{Request successfully processed}, response.message + end + + def test_failed_purchase + @options[:memo] = '800.100.151' + response = @gateway.purchase(@amount, @declined_card, @options) + assert_failure response + assert_equal 'transaction declined (invalid card)', response.message + end + + def test_failed_purchase_sans_cvv + @credit_card.verification_value = nil + response = @gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_match %r{empty CVV .* not allowed}, response.message + end + + def test_successful_authorize_without_address + auth = @gateway.authorize(@amount, @credit_card, {}) + assert_success auth + end + + def test_successful_authorize_and_capture + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + assert capture = @gateway.capture(@amount, auth.authorization) + assert_success capture + end + + def test_failed_authorize + @options[:memo] = '100.400.080' + response = @gateway.authorize(@amount, @declined_card, @options) + assert_failure response + end + + def test_partial_capture + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + assert capture = @gateway.capture(@amount-1, auth.authorization) + assert_success capture + end + + def test_failed_capture + response = @gateway.capture(nil, '') + assert_failure response + end + + def test_successful_refund + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + + assert refund = @gateway.refund(100, purchase.authorization) + assert_success refund + end + + def test_partial_refund + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + + assert refund = @gateway.refund(@amount-1, purchase.authorization) + assert_success refund + end + + def test_failed_refund + response = @gateway.refund(nil, '') + assert_failure response + end + + def test_successful_void + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + assert void = @gateway.void(auth.authorization) + assert_success void + end + + def test_failed_void + response = @gateway.void('') + assert_failure response + end + + def test_successful_verify + response = @gateway.verify(@credit_card, @options) + assert_success response + assert_match %r{Request successfully processed}, response.message + end + + def test_failed_verify + @options[:memo] = '100.400.080' + response = @gateway.verify(@declined_card, @options) + assert_failure response + assert_match %r{authorization failure}, response.message + end + + def test_successful_store + response = @gateway.store(@credit_card, @options) + assert_success response + assert_match %r{Request successfully processed}, response.message + end + + def test_purchase_using_stored_card + assert response = @gateway.store(@credit_card) + assert_success response + + response = @gateway.purchase(@amount, response.authorization, @options) + assert_success response + assert_match %r{Request successfully processed}, response.message + end + + def test_failed_store + credit_card = credit_card('4111') + response = @gateway.store(credit_card, @options) + assert_failure response + assert_match %r{invalid creditcard}, response.message + end + + def test_invalid_login + gateway = S5Gateway.new( + sender: '', + channel: '', + login: '', + password: '' + ) + response = gateway.purchase(@amount, @credit_card, @options) + assert_failure response + end +end diff --git a/test/remote/gateways/remote_safe_charge_test.rb b/test/remote/gateways/remote_safe_charge_test.rb new file mode 100644 index 00000000000..fc64bacb1c1 --- /dev/null +++ b/test/remote/gateways/remote_safe_charge_test.rb @@ -0,0 +1,234 @@ +require 'test_helper' + +class RemoteSafeChargeTest < Test::Unit::TestCase + def setup + @gateway = SafeChargeGateway.new(fixtures(:safe_charge)) + + @amount = 100 + @credit_card = credit_card('4000100011112224', verification_value: '912') + @declined_card = credit_card('4000300011112220') + @options = { + order_id: generate_unique_id, + billing_address: address, + description: 'Store Purchase', + currency: 'EUR' + } + + @three_ds_options = @options.merge(three_d_secure: true) + @three_ds_gateway = SafeChargeGateway.new(fixtures(:safe_charge_three_ds)) + @three_ds_enrolled_card = credit_card('4012 0010 3749 0014') + @three_ds_non_enrolled_card = credit_card('5333 3062 3122 6927') + @three_ds_invalid_pa_res_card = credit_card('4012 0010 3749 0006') + end + + def test_successful_3ds_purchase + response = @three_ds_gateway.purchase(@amount, @three_ds_enrolled_card, @three_ds_options) + assert_success response + assert !response.params['acsurl'].blank? + assert !response.params['pareq'].blank? + assert !response.params['xid'].blank? + assert_equal 'Success', response.message + end + + def test_successful_regular_purchase_through_3ds_flow_with_non_enrolled_card + response = @three_ds_gateway.purchase(@amount, @three_ds_non_enrolled_card, @three_ds_options) + assert_success response + assert response.params['acsurl'].blank? + assert response.params['pareq'].blank? + assert response.params['xid'].blank? + assert response.params['threedflow'] = 1 + assert_equal 'Success', response.message + end + + def test_successful_regular_purchase_through_3ds_flow_with_invalid_pa_res + response = @three_ds_gateway.purchase(@amount, @three_ds_invalid_pa_res_card, @three_ds_options) + assert_success response + assert !response.params['acsurl'].blank? + assert !response.params['pareq'].blank? + assert !response.params['xid'].blank? + assert response.params['threedflow'] = 1 + assert_equal 'Success', response.message + end + + def test_successful_purchase + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal 'Success', response.message + end + + def test_successful_purchase_with_more_options + options = { + order_id: '1', + ip: '127.0.0.1', + email: 'joe@example.com', + user_id: '123', + auth_type: '2', + expected_fulfillment_count: '3', + merchant_descriptor: 'Test Descriptor', + merchant_phone_number: '(555)555-5555', + merchant_name: 'Test Merchant', + stored_credential_mode: true + } + + response = @gateway.purchase(@amount, @credit_card, options) + assert_success response + assert_equal 'Success', response.message + end + + def test_failed_purchase + response = @gateway.purchase(@amount, @declined_card, @options) + assert_failure response + assert_equal 'Decline', response.message + end + + def test_successful_authorize_and_capture + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + assert capture = @gateway.capture(@amount, auth.authorization) + assert_success capture + assert_equal 'Success', capture.message + end + + def test_successful_authorize_and_capture_with_more_options + extra = { + order_id: '1', + ip: '127.0.0.1', + email: 'joe@example.com', + user_id: '123', + auth_type: '2', + expected_fulfillment_count: '3', + merchant_descriptor: 'Test Descriptor', + merchant_phone_number: '(555)555-5555', + merchant_name: 'Test Merchant', + stored_credential_mode: true + } + auth = @gateway.authorize(@amount, @credit_card, extra) + assert_success auth + + assert capture = @gateway.capture(@amount, auth.authorization) + assert_success capture + assert_equal 'Success', capture.message + end + + def test_failed_authorize + response = @gateway.authorize(@amount, @declined_card, @options) + assert_failure response + assert_equal 'Decline', response.message + end + + def test_partial_capture + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + assert capture = @gateway.capture(@amount-1, auth.authorization) + assert_success capture + end + + def test_failed_capture + response = @gateway.capture(@amount, '') + assert_failure response + assert_equal 'Transaction must contain a Card/Token/Account', response.message + end + + def test_successful_refund + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + + assert refund = @gateway.refund(@amount, purchase.authorization) + assert_success refund + assert_equal 'Success', refund.message + end + + def test_partial_refund + purchase = @gateway.purchase(200, @credit_card, @options) + assert_success purchase + + assert refund = @gateway.refund(100, purchase.authorization) + assert_success refund + end + + def test_failed_refund + response = @gateway.refund(@amount, '') + assert_failure response + assert_equal 'Transaction must contain a Card/Token/Account', response.message + end + + def test_successful_credit + response = @gateway.credit(@amount, credit_card('4444436501403986'), @options) + assert_success response + assert_equal 'Success', response.message + end + + def test_successful_credit_with_extra_options + extra = { + order_id: '1', + ip: '127.0.0.1', + email: 'joe@example.com', + user_id: '123', + auth_type: '2', + expected_fulfillment_count: '3', + merchant_descriptor: 'Test Descriptor', + merchant_phone_number: '(555)555-5555', + merchant_name: 'Test Merchant', + stored_credential_mode: true + } + + response = @gateway.credit(@amount, credit_card('4444436501403986'), extra) + assert_success response + assert_equal 'Success', response.message + end + + def test_failed_credit + response = @gateway.credit(@amount, @declined_card, @options) + assert_failure response + assert_equal 'Decline', response.message + end + + def test_successful_void + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + assert void = @gateway.void(auth.authorization) + assert_success void + assert_equal 'Success', void.message + end + + def test_failed_void + response = @gateway.void('') + assert_failure response + assert_equal 'Invalid Amount', response.message + end + + def test_successful_verify + response = @gateway.verify(@credit_card, @options) + assert_success response + assert_match 'Success', response.message + end + + def test_failed_verify + response = @gateway.verify(@declined_card, @options) + assert_failure response + assert_match 'Decline', response.message + end + + def test_invalid_login + gateway = SafeChargeGateway.new(client_login_id: '', client_password: '') + + response = gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_match 'Invalid login', response.message + end + + def test_transcript_scrubbing + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @credit_card, @options) + end + transcript = @gateway.scrub(transcript) + + assert_scrubbed(@credit_card.number, transcript) + assert_scrubbed(@credit_card.verification_value, transcript) + assert_scrubbed(@gateway.options[:client_password], transcript) + end + +end diff --git a/test/remote/gateways/remote_sage_bankcard_test.rb b/test/remote/gateways/remote_sage_bankcard_test.rb deleted file mode 100644 index 1a37aaaaba0..00000000000 --- a/test/remote/gateways/remote_sage_bankcard_test.rb +++ /dev/null @@ -1,109 +0,0 @@ -require 'test_helper' - -class RemoteSageBankcardTest < Test::Unit::TestCase - - def setup - @gateway = SageBankcardGateway.new(fixtures(:sage)) - - @amount = 100 - - @visa = credit_card("4111111111111111") - @mastercard = credit_card("5499740000000057") - @discover = credit_card("6011000993026909") - @amex = credit_card("371449635392376") - - @options = { - :order_id => generate_unique_id, - :billing_address => address, - :shipping_address => address, - :email => 'longbob@example.com' - } - end - - def test_successful_visa_purchase - assert response = @gateway.purchase(@amount, @visa, @options) - assert_success response - assert response.test? - assert_false response.authorization.blank? - end - - def test_successful_visa_authorization - assert response = @gateway.authorize(@amount, @visa, @options) - assert_success response - assert response.test? - assert_false response.authorization.blank? - end - - def test_declined_visa_purchase - @amount = 200 - - assert response = @gateway.purchase(@amount, @visa, @options) - assert_failure response - assert response.test? - end - - def test_successful_mastercard_purchase - assert response = @gateway.purchase(@amount, @mastercard, @options) - assert_success response - assert response.test? - assert_false response.authorization.blank? - end - - def test_successful_discover_purchase - assert response = @gateway.purchase(@amount, @discover, @options) - assert_success response - assert response.test? - assert_false response.authorization.blank? - end - - def test_successful_amex_purchase - assert response = @gateway.purchase(@amount, @amex, @options) - assert_success response - assert response.test? - assert_false response.authorization.blank? - end - - def test_authorization_and_capture - assert auth = @gateway.authorize(@amount, @visa, @options) - assert_success auth - - assert capture = @gateway.capture(@amount, auth.authorization) - assert_success capture - end - - def test_failed_capture - assert response = @gateway.capture(@amount, '') - assert_failure response - assert_equal 'INVALID T_REFERENCE', response.message - end - - def test_authorization_and_void - assert auth = @gateway.authorize(@amount, @visa, @options) - assert_success auth - - assert void = @gateway.void(auth.authorization) - assert_success void - end - - def test_failed_void - assert response = @gateway.void('') - assert_failure response - assert_equal 'INVALID T_REFERENCE', response.message - end - - def test_successful_refund - assert response = @gateway.refund(@amount, @visa, @options) - assert_success response - assert response.test? - end - - def test_invalid_login - gateway = SageBankcardGateway.new( - :login => '', - :password => '' - ) - assert response = gateway.purchase(@amount, @visa, @options) - assert_failure response - assert_equal 'SECURITY VIOLATION', response.message - end -end diff --git a/test/remote/gateways/remote_sage_pay_test.rb b/test/remote/gateways/remote_sage_pay_test.rb index 369b02e3c2b..c7647d53969 100644 --- a/test/remote/gateways/remote_sage_pay_test.rb +++ b/test/remote/gateways/remote_sage_pay_test.rb @@ -2,15 +2,15 @@ # Some of the standard tests have been removed at SagePay test # server is pants and accepts anything and says Status=OK. (shift) -# The tests for American Express will only pass if your account is +# The tests for American Express will only pass if your account is # American express enabled. class RemoteSagePayTest < Test::Unit::TestCase # set to true to run the tests in the simulated environment SagePayGateway.simulate = false - + def setup @gateway = SagePayGateway.new(fixtures(:sage_pay)) - + @amex = CreditCard.new( :number => '374200000000004', :month => 12, @@ -44,19 +44,6 @@ def setup :brand => 'visa' ) - @solo = CreditCard.new( - :number => '6334900000000005', - :month => 6, - :year => next_year, - :issue_number => 1, - :start_month => 12, - :start_year => next_year - 2, - :verification_value => 227, - :first_name => 'Tekin', - :last_name => 'Suleyman', - :brand => 'solo' - ) - @mastercard = CreditCard.new( :number => '5404000000000001', :month => 12, @@ -66,7 +53,7 @@ def setup :last_name => 'Suleyman', :brand => 'master' ) - + @electron = CreditCard.new( :number => '4917300000000008', :month => 12, @@ -86,20 +73,20 @@ def setup :brand => 'visa' ) - @options = { - :billing_address => { + @options = { + :billing_address => { :name => 'Tekin Suleyman', :address1 => 'Flat 10 Lapwing Court', :address2 => 'West Didsbury', - :city => "Manchester", + :city => 'Manchester', :county => 'Greater Manchester', :country => 'GB', :zip => 'M20 2PS' }, - :shipping_address => { + :shipping_address => { :name => 'Tekin Suleyman', :address1 => '120 Grosvenor St', - :city => "Manchester", + :city => 'Manchester', :county => 'Greater Manchester', :country => 'GB', :zip => 'M1 7QW' @@ -110,61 +97,75 @@ def setup :email => 'tekin@tekin.co.uk', :phone => '0161 123 4567' } - + @amount = 100 end def test_successful_mastercard_purchase assert response = @gateway.purchase(@amount, @mastercard, @options) assert_success response - + assert response.test? assert !response.authorization.blank? end - + def test_unsuccessful_purchase assert response = @gateway.purchase(@amount, @declined_card, @options) assert_failure response - + assert response.test? end - + def test_successful_authorization_and_capture assert auth = @gateway.authorize(@amount, @mastercard, @options) assert_success auth - + assert capture = @gateway.capture(@amount, auth.authorization) assert_success capture end - + + def test_successful_authorization_and_capture_and_refund + assert auth = @gateway.authorize(@amount, @mastercard, @options) + assert_success auth + + assert capture = @gateway.capture(@amount, auth.authorization) + assert_success capture + + assert refund = @gateway.refund(@amount, capture.authorization, + :description => 'Crediting trx', + :order_id => generate_unique_id + ) + assert_success refund + end + def test_successful_authorization_and_void assert auth = @gateway.authorize(@amount, @mastercard, @options) - assert_success auth - + assert_success auth + assert abort = @gateway.void(auth.authorization) assert_success abort end - + def test_successful_purchase_and_void assert purchase = @gateway.purchase(@amount, @mastercard, @options) - assert_success purchase - + assert_success purchase + assert void = @gateway.void(purchase.authorization) assert_success void end - - def test_successful_purchase_and_credit + + def test_successful_purchase_and_refund assert purchase = @gateway.purchase(@amount, @mastercard, @options) - assert_success purchase - - assert credit = @gateway.credit(@amount, purchase.authorization, - :description => 'Crediting trx', + assert_success purchase + + assert refund = @gateway.refund(@amount, purchase.authorization, + :description => 'Crediting trx', :order_id => generate_unique_id ) - - assert_success credit + + assert_success refund end - + def test_successful_visa_purchase assert response = @gateway.purchase(@amount, @visa, @options) assert_success response @@ -172,48 +173,297 @@ def test_successful_visa_purchase assert !response.authorization.blank? end - def test_successful_maestro_purchase - assert response = @gateway.purchase(@amount, @maestro, @options) + def test_successful_amex_purchase + assert response = @gateway.purchase(@amount, @amex, @options) assert_success response assert response.test? assert !response.authorization.blank? end - def test_successful_solo_purchase - assert response = @gateway.purchase(@amount, @solo, @options) + def test_successful_electron_purchase + assert response = @gateway.purchase(@amount, @electron, @options) assert_success response assert response.test? assert !response.authorization.blank? end - - def test_successful_amex_purchase - assert response = @gateway.purchase(@amount, @amex, @options) + + def test_successful_purchase_with_overly_long_fields + options = { + description: 'SagePay transactions fail if the description is more than 100 characters. Therefore, we truncate it to 100 characters.', + order_id: "#{generate_unique_id} SagePay order_id cannot be more than 40 characters.", + billing_address: { + name: 'FirstNameCannotBeMoreThanTwentyChars SurnameCannotBeMoreThanTwenty', + address1: 'The Billing Address 1 Cannot Be More Than One Hundred Characters if it is it will fail. Therefore, we truncate it.', + address2: 'The Billing Address 2 Cannot Be More Than One Hundred Characters if it is it will fail. Therefore, we truncate it.', + phone: '111222333444555666777888999', + city: 'TheCityCannotBeMoreThanFortyCharactersReally', + state: 'NCStateIsTwoChars', + country: 'USMustBeTwoChars', + zip: 'PostalCodeCannotExceedTenChars' + }, + shipping_address: { + name: 'FirstNameCannotBeMoreThanTwentyChars SurnameCannotBeMoreThanTwenty', + address1: 'The Shipping Address 1 Cannot Be More Than One Hundred Characters if it is it will fail. Therefore, we truncate it.', + address2: 'The Shipping Address 2 Cannot Be More Than One Hundred Characters if it is it will fail. Therefore, we truncate it.', + phone: '111222333444555666777888999', + city: 'TheCityCannotBeMoreThanFortyCharactersReally', + state: 'NCStateIsTwoChars', + country: 'USMustBeTwoChars', + zip: 'PostalCodeCannotExceedTenChars' + } + } + + @visa.first_name = 'FullNameOnACardMustBeLessThanFiftyCharacters' + @visa.last_name = 'OtherwiseSagePayFailsIt' + + assert response = @gateway.purchase(@amount, @visa, options) assert_success response - assert response.test? - assert !response.authorization.blank? end - - def test_successful_electron_purchase - assert response = @gateway.purchase(@amount, @electron, @options) + + def test_successful_mastercard_purchase_with_optional_FIxxxx_fields + @options[:recipient_account_number] = '1234567890' + @options[:recipient_surname] = 'Withnail' + @options[:recipient_postcode] = 'AB11AB' + @options[:recipient_dob] = '19701223' + assert response = @gateway.purchase(@amount, @mastercard, @options) assert_success response + assert response.test? assert !response.authorization.blank? end - + + def test_successful_purchase_with_apply_avscv2_field + @options[:apply_avscv2] = 1 + response = @gateway.purchase(@amount, @visa, @options) + assert_success response + assert_equal 'Y', response.cvv_result['code'] + end + + def test_successful_purchase_with_pay_pal_callback_url + @options[:paypal_callback_urll] = 'callback.com' + response = @gateway.purchase(@amount, @visa, @options) + assert_success response + end + + def test_successful_purchase_with_basket + # Example from "Sage Pay Direct Integration and Protocol Guidelines 3.00" + # Published: 27/08/2015 + @options[:basket] = '4:Pioneer NSDV99 DVD-Surround Sound System:1:424.68:' \ + '74.32:499.00: 499.00:Donnie Darko Director’s Cut:3:11.91:2.08:13.99:' \ + '41.97: Finding Nemo:2:11.05:1.94:12.99:25.98: Delivery:---:---:---:---' \ + ':4.99' + response = @gateway.purchase(@amount, @visa, @options) + assert_success response + end + + def test_successful_purchase_with_gift_aid_payment + @options[:gift_aid_payment] = 1 + response = @gateway.purchase(@amount, @visa, @options) + assert_success response + end + + def test_successful_transaction_registration_with_apply_3d_secure + @options[:apply_3d_secure] = 1 + response = @gateway.purchase(@amount, @visa, @options) + # We receive a different type of response for 3D Secure requiring to + # redirect the user to the ACSURL given inside the response + assert response.params.include?('ACSURL') + assert_equal 'OK', response.params['3DSecureStatus'] + assert_equal '3DAUTH', response.params['Status'] + end + + def test_successful_purchase_with_account_type + @options[:account_type] = 'E' + response = @gateway.purchase(@amount, @visa, @options) + assert_success response + end + + def test_successful_purchase_with_billing_agreement + @options[:billing_agreement] = 1 + response = @gateway.purchase(@amount, @visa, @options) + assert_success response + end + + def test_successful_purchase_with_basket_xml + @options[:basket_xml] = basket_xml + response = @gateway.purchase(@amount, @visa, @options) + assert_success response + end + + def test_successful_purchase_with_customer_xml + @options[:customer_xml] = customer_xml + response = @gateway.purchase(@amount, @visa, @options) + assert_success response + end + + def test_successful_purchase_with_surcharge_xml + @options[:surcharge_xml] = surcharge_xml + response = @gateway.purchase(@amount, @visa, @options) + assert_success response + end + + def test_successful_purchase_with_vendor_data + @options[:vendor_data] = 'Data displayed against the transaction in MySagePay' + response = @gateway.purchase(@amount, @visa, @options) + assert_success response + end + + def test_successful_purchase_with_language + @options[:language] = 'FR' + response = @gateway.purchase(@amount, @visa, @options) + assert_success response + end + + def test_successful_purchase_with_website + @options[:website] = 'origin-of-transaction.com' + response = @gateway.purchase(@amount, @visa, @options) + assert_success response + end + + def test_successful_repeat_purchase + response = @gateway.purchase(@amount, @visa, @options) + assert_success response + repeat = @gateway.purchase(@amount, response.authorization, @options.merge(order_id: generate_unique_id)) + assert_success repeat + end + def test_invalid_login - message = SagePayGateway.simulate ? 'VSP Simulator cannot find your vendor name. Ensure you have have supplied a Vendor field with your VSP Vendor name assigned to it.' : '3034 : The Vendor or VendorName value is required.' - + message = SagePayGateway.simulate ? 'VSP Simulator cannot find your vendor name. Ensure you have have supplied a Vendor field with your VSP Vendor name assigned to it.' : '3034 : The Vendor or VendorName value is required.' + gateway = SagePayGateway.new( - :login => '' + :login => '' ) assert response = gateway.purchase(@amount, @mastercard, @options) assert_equal message, response.message assert_failure response end - + + def test_successful_store_and_purchace + assert response = @gateway.store(@visa) + assert_success response + assert !response.authorization.blank? + assert purchase = @gateway.purchase(@amount, response.authorization, @options) + assert_success purchase + end + + def test_successful_store_and_repurchase_with_resupplied_verification_value + assert response = @gateway.store(@visa) + assert_success response + assert !response.authorization.blank? + assert @gateway.purchase(@amount, response.authorization, @options.merge(customer: 1)) + assert purchase = @gateway.purchase(@amount, response.authorization, @options.merge(verification_value: '123', order_id: generate_unique_id)) + assert_success purchase + end + + def test_successful_store_and_authorize + assert response = @gateway.store(@visa) + assert_success response + assert !response.authorization.blank? + assert authorize = @gateway.authorize(@amount, response.authorization, @options) + assert_success authorize + end + + def test_successful_token_creation_from_purchase + assert response = @gateway.purchase(@amount, @visa, @options.merge(:store => true)) + assert_success response + assert !response.authorization.blank? + end + + def test_successful_token_creation_from_authorize + assert response = @gateway.authorize(@amount, @visa, @options.merge(:store => true)) + assert_success response + assert !response.authorization.blank? + end + + def test_successful_unstore + assert response = @gateway.store(@visa) + assert_success response + assert !response.authorization.blank? + assert unstore = @gateway.unstore(response.authorization) + assert_success unstore + end + + def test_successful_verify + response = @gateway.verify(@visa, @options) + assert_success response + assert_equal 'Success', response.message + end + + def test_failed_verify + response = @gateway.verify(@declined_card, @options) + assert_failure response + assert_match(/Card Range not supported/, response.message) + end + + def test_transcript_scrubbing + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @visa, @options) + end + clean_transcript = @gateway.scrub(transcript) + + assert_scrubbed(@visa.number, clean_transcript) + assert_scrubbed(@visa.verification_value.to_s, clean_transcript) + end + private def next_year Date.today.year + 1 end + + # Based on example from http://www.sagepay.co.uk/support/basket-xml + # Only kept required fields to make sense + def basket_xml + <<-XML +<basket> + <item> + <description>DVD 1</description> + <quantity>2</quantity> + <unitNetAmount>24.50</unitNetAmount> + <unitTaxAmount>00.50</unitTaxAmount> + <unitGrossAmount>25.00</unitGrossAmount> + <totalGrossAmount>50.00</totalGrossAmount> + </item> + </basket> + XML + end + + # Example from http://www.sagepay.co.uk/support/customer-xml + def customer_xml + <<-XML +<customer> + <customerMiddleInitial>W</customerMiddleInitial> + <customerBirth>1983-01-01</customerBirth> + <customerWorkPhone>020 1234567</customerWorkPhone> + <customerMobilePhone>0799 1234567</customerMobilePhone> + <previousCust>0</previousCust> + <timeOnFile>10</timeOnFile> + <customerId>CUST123</customerId> +</customer> + XML + end + + # Example from https://www.sagepay.co.uk/support/12/36/protocol-3-00-surcharge-xml + def surcharge_xml + <<-XML +<surcharges> + <surcharge> + <paymentType>DELTA</paymentType> + <fixed>2.50</fixed> + </surcharge> + <surcharge> + <paymentType>VISA</paymentType> + <fixed>2.50</fixed> + </surcharge> + <surcharge> + <paymentType>AMEX</paymentType> + <percentage>1.50</percentage> + </surcharge> + <surcharge> + <paymentType>MC</paymentType> + <percentage>1.50</percentage> + </surcharge> +</surcharges> + XML + end end diff --git a/test/remote/gateways/remote_sage_test.rb b/test/remote/gateways/remote_sage_test.rb index dd106096d94..5107a12e97d 100644 --- a/test/remote/gateways/remote_sage_test.rb +++ b/test/remote/gateways/remote_sage_test.rb @@ -4,77 +4,192 @@ class RemoteSageTest < Test::Unit::TestCase def setup @gateway = SageGateway.new(fixtures(:sage)) - + @amount = 100 - - @visa = credit_card("4111111111111111") + + @visa = credit_card('4111111111111111') @check = check - - @options = { + @mastercard = credit_card('5499740000000057') + @discover = credit_card('6011000993026909') + @amex = credit_card('371449635392376') + + @declined_card = credit_card('4000') + + @options = { :order_id => generate_unique_id, :billing_address => address, :shipping_address => address, :email => 'longbob@example.com' } end - + def test_successful_visa_purchase assert response = @gateway.purchase(@amount, @visa, @options) assert_success response assert response.test? assert_false response.authorization.blank? end - + + def test_declined_visa_purchase + @amount = 200 + + assert response = @gateway.purchase(@amount, @visa, @options) + assert_failure response + assert response.test? + end + + def test_successful_mastercard_purchase + assert response = @gateway.purchase(@amount, @mastercard, @options) + assert_success response + assert response.test? + assert_false response.authorization.blank? + end + + def test_successful_discover_purchase + assert response = @gateway.purchase(@amount, @discover, @options) + assert_success response + assert response.test? + assert_false response.authorization.blank? + end + + def test_successful_amex_purchase + assert response = @gateway.purchase(@amount, @amex, @options) + assert_success response + assert response.test? + assert_false response.authorization.blank? + end + def test_successful_check_purchase assert response = @gateway.purchase(@amount, @check, @options) assert_success response assert response.test? assert_false response.authorization.blank? end - + def test_successful_visa_authorization assert response = @gateway.authorize(@amount, @visa, @options) assert_success response assert response.test? assert_false response.authorization.blank? end - + + def test_successful_with_minimal_options + assert response = @gateway.purchase(@amount, @visa, billing_address: address) + assert_success response + assert response.test? + assert_false response.authorization.blank? + end + + def test_successful_purchase_with_blank_state + assert response = @gateway.purchase(@amount, @visa, billing_address: address(state: '')) + assert_success response + assert response.test? + assert_false response.authorization.blank? + end + def test_authorization_and_capture assert auth = @gateway.authorize(@amount, @visa, @options) assert_success auth - + assert capture = @gateway.capture(@amount, auth.authorization) assert_success capture end - + + def test_failed_capture + assert response = @gateway.capture(@amount, '') + assert_failure response + assert_equal 'INVALID T_REFERENCE', response.message + end + def test_visa_authorization_and_void assert auth = @gateway.authorize(@amount, @visa, @options) assert_success auth - + assert void = @gateway.void(auth.authorization) assert_success void end - + + def test_failed_void + assert response = @gateway.void('') + assert_failure response + assert_equal 'INVALID T_REFERENCE', response.message + end + def test_check_purchase_and_void assert purchase = @gateway.purchase(@amount, @check, @options) assert_success purchase - + assert void = @gateway.void(purchase.authorization) assert_success void end - def test_visa_refund - assert response = @gateway.refund(@amount, @visa, @options) + def test_visa_credit + assert response = @gateway.credit(@amount, @visa, @options) assert_success response assert response.test? end - - def test_check_refund - assert response = @gateway.refund(@amount, @check, @options) + + def test_check_credit + assert response = @gateway.credit(@amount, @check, @options) assert_success response assert response.test? end - + + def test_visa_refund + purchase = @gateway.purchase(@amount, @visa, @options) + assert_success purchase + + assert refund = @gateway.refund(@amount, purchase.authorization, @options) + assert_success refund + assert_equal 'APPROVED', refund.message + end + + def test_visa_failed_refund + purchase = @gateway.purchase(@amount, @visa, @options) + assert_success purchase + + response = @gateway.refund(@amount, 'UnknownReference', @options) + assert_failure response + assert_equal 'INVALID T_REFERENCE', response.message + end + + def test_partial_refund + purchase = @gateway.purchase(@amount, @visa, @options) + assert_success purchase + + assert refund = @gateway.refund(@amount-1, purchase.authorization, @options) + assert_success refund + assert_equal 'APPROVED', refund.message + end + + def test_store_visa + assert response = @gateway.store(@visa, @options) + assert_success response + assert response.authorization, + 'Store card authorization should not be nil' + assert_not_nil response.message + end + + def test_failed_store + assert response = @gateway.store(@declined_card, @options) + assert_failure response + assert_nil response.authorization + end + + def test_unstore_visa + assert auth = @gateway.store(@visa, @options).authorization, + 'Unstore card authorization should not be nil' + assert response = @gateway.unstore(auth, @options) + assert_success response + end + + def test_failed_unstore_visa + assert auth = @gateway.store(@visa, @options).authorization, + 'Unstore card authorization should not be nil' + assert response = @gateway.unstore(auth, @options) + assert_success response + end + def test_invalid_login gateway = SageGateway.new( :login => '', @@ -84,4 +199,37 @@ def test_invalid_login assert_failure response assert_equal 'SECURITY VIOLATION', response.message end + + def test_transcript_scrubbing + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @visa, @options) + end + transcript = @gateway.scrub(transcript) + + assert_scrubbed(@visa.number, transcript) + assert_scrubbed(@visa.verification_value, transcript) + assert_scrubbed(@gateway.options[:password], transcript) + end + + def test_transcript_scrubbing_store + transcript = capture_transcript(@gateway) do + @gateway.store(@visa, @options) + end + transcript = @gateway.scrub(transcript) + + assert_scrubbed(@visa.number, transcript) + assert_scrubbed(@gateway.options[:password], transcript) + end + + def test_echeck_scrubbing + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @check, @options) + end + transcript = @gateway.scrub(transcript) + + assert_scrubbed(@check.account_number, transcript) + assert_scrubbed(@check.routing_number, transcript) + assert_scrubbed(@gateway.options[:password], transcript) + end + end diff --git a/test/remote/gateways/remote_sage_virtual_check_test.rb b/test/remote/gateways/remote_sage_virtual_check_test.rb deleted file mode 100644 index 934012b5c93..00000000000 --- a/test/remote/gateways/remote_sage_virtual_check_test.rb +++ /dev/null @@ -1,62 +0,0 @@ -require 'test_helper' - -class RemoteSageCheckTest < Test::Unit::TestCase - - def setup - @gateway = SageVirtualCheckGateway.new(fixtures(:sage)) - - @amount = 100 - @check = check - - @options = { - :order_id => generate_unique_id, - :billing_address => address, - :shipping_address => address, - :email => 'longbob@example.com', - :drivers_license_state => 'CA', - :drivers_license_number => '12345689', - :date_of_birth => Date.new(1978, 8, 11), - :ssn => '078051120' - } - end - - def test_successful_check_purchase - assert response = @gateway.purchase(@amount, @check, @options) - assert_success response - assert response.test? - assert_false response.authorization.blank? - end - - def test_failed_check_purchase - @check.routing_number = "" - - assert response = @gateway.purchase(@amount, @check, @options) - assert_failure response - assert response.test? - assert_false response.authorization.blank? - end - - def test_purchase_and_void - assert purchase = @gateway.purchase(@amount, @check, @options) - assert_success purchase - - assert void = @gateway.void(purchase.authorization) - assert_success void - end - - def test_credit - assert response = @gateway.credit(@amount, @check, @options) - assert_success response - assert response.test? - end - - def test_invalid_login - gateway = SageVirtualCheckGateway.new( - :login => '', - :password => '' - ) - assert response = gateway.purchase(@amount, @check, @options) - assert_failure response - assert_equal 'SECURITY VIOLATION', response.message - end -end diff --git a/test/remote/gateways/remote_sallie_mae_test.rb b/test/remote/gateways/remote_sallie_mae_test.rb index 09f14cb2337..f2b47acef22 100644 --- a/test/remote/gateways/remote_sallie_mae_test.rb +++ b/test/remote/gateways/remote_sallie_mae_test.rb @@ -3,12 +3,12 @@ class RemoteSallieMaeTest < Test::Unit::TestCase def setup @gateway = SallieMaeGateway.new(fixtures(:sallie_mae)) - + @amount = 100 @credit_card = credit_card('5454545454545454') @declined_card = credit_card('4000300011112220') - - @options = { + + @options = { :billing_address => address, :description => 'Store Purchase' } diff --git a/test/remote/gateways/remote_samurai_test.rb b/test/remote/gateways/remote_samurai_test.rb deleted file mode 100644 index 3d049461852..00000000000 --- a/test/remote/gateways/remote_samurai_test.rb +++ /dev/null @@ -1,62 +0,0 @@ -require 'test_helper' - -class RemoteSamuraiTest < Test::Unit::TestCase - - def setup - @gateway = SamuraiGateway.new(fixtures(:samurai)) - - @amount = 100 - @declined_amount = 102 - @invalid_card_amount = 107 - @expired_card_amount = 108 - @credit_card = credit_card('4111111111111111', :verification_value => '111') - end - - def test_successful_purchase - assert response = @gateway.purchase(@amount, @credit_card) - assert_success response - assert_equal 'OK', response.message - end - - def test_declined_purchase - assert response = @gateway.purchase(@declined_amount, @credit_card) - assert_failure response - assert_equal 'The card was declined.', response.message - end - - def test_invalid_purchase - assert response = @gateway.purchase(@invalid_card_amount, @credit_card) - assert_failure response - assert_equal 'The card number was invalid.', response.message - end - - def test_expired_purchase - assert response = @gateway.purchase(@expired_card_amount, @credit_card) - assert_failure response - assert_equal 'The expiration date month was invalid, or prior to today. The expiration date year was invalid, or prior to today.', response.message - end - - def test_successful_auth_and_capture - assert authorize = @gateway.authorize(@amount, @credit_card) - assert_success authorize - assert_equal 'OK', authorize.message - assert capture = @gateway.capture(@amount, authorize.authorization) - assert_success capture - assert_equal 'OK', capture.message - end - - def test_successful_auth_and_void - assert_success authorize = @gateway.authorize(@amount, @credit_card) - assert_success @gateway.void(authorize.authorization) - end - - def test_successful_store_and_retain - assert_success @gateway.store(@credit_card, :retain => true) - end - - def test_invalid_login - assert_raise(ArgumentError) do - SamuraiGateway.new( :login => '', :password => '' ) - end - end -end diff --git a/test/remote/gateways/remote_secure_net_test.rb b/test/remote/gateways/remote_secure_net_test.rb index 4b82cb59ad8..fc92a5276e1 100644 --- a/test/remote/gateways/remote_secure_net_test.rb +++ b/test/remote/gateways/remote_secure_net_test.rb @@ -12,15 +12,15 @@ def setup n = Time.now order_id = n.to_i.to_s + n.usec.to_s - @options = { - :order_id => order_id, - :billing_address => address, - :description => 'Store Purchase' + @options = { + order_id: order_id, + billing_address: address, + description: 'Store Purchase' } end def test_expired_credit_card - @credit_card.year = 2004 + @credit_card.year = 2004 assert response = @gateway.purchase(@amount, @credit_card, @options) assert_failure response assert response.test? @@ -97,7 +97,7 @@ def test_unsuccessful_capture def test_unsuccessful_purchase assert response = @gateway.purchase(@amount, @bad_card_number, @options) assert_failure response - assert_equal "CARD TYPE COULD NOT BE IDENTIFIED", response.message + assert_equal 'CARD TYPE COULD NOT BE IDENTIFIED', response.message end def test_unsuccessful_purchase_and_credit @@ -111,4 +111,29 @@ def test_unsuccessful_purchase_and_credit assert_equal 'CREDIT CANNOT BE COMPLETED ON AN UNSETTLED TRANSACTION', refund.message end + def test_invoice_description_and_number + options = @options.merge({ + invoice_description: 'TheInvoiceDescriptions', + invoice_number: 'TheInvoiceNumber' + }) + + assert auth = @gateway.authorize(@amount, @credit_card, options) + assert_success auth + + assert capture = @gateway.capture(@amount, auth.authorization, options) + assert_success capture + assert_equal 'Approved', capture.message + end + + def test_transcript_scrubbing + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @credit_card, @options) + end + transcript = @gateway.scrub(transcript) + + assert_scrubbed(@credit_card.number, transcript) + assert_scrubbed(@credit_card.verification_value, transcript) + assert_scrubbed(@gateway.options[:password], transcript) + end + end diff --git a/test/remote/gateways/remote_secure_pay_au_test.rb b/test/remote/gateways/remote_secure_pay_au_test.rb index b3c2c771da0..fc99db2ef81 100644 --- a/test/remote/gateways/remote_secure_pay_au_test.rb +++ b/test/remote/gateways/remote_secure_pay_au_test.rb @@ -4,7 +4,6 @@ class RemoteSecurePayAuTest < Test::Unit::TestCase class MyCreditCard include ActiveMerchant::Billing::CreditCardMethods - include ActiveMerchant::Validateable attr_accessor :number, :month, :year, :first_name, :last_name, :verification_value, :brand def verification_value? @@ -131,7 +130,7 @@ def test_successful_unstore end def test_repeat_unstore - @gateway.unstore('test1234') rescue nil #Ensure it is already missing + @gateway.unstore('test1234') rescue nil # Ensure it is already missing response = @gateway.unstore('test1234') @@ -148,7 +147,7 @@ def test_successful_store end def test_failed_store - @gateway.store(@credit_card, {:billing_id => 'test1234', :amount => 15000}) rescue nil #Ensure it already exists + @gateway.store(@credit_card, {:billing_id => 'test1234', :amount => 15000}) rescue nil # Ensure it already exists assert response = @gateway.store(@credit_card, {:billing_id => 'test1234', :amount => 15000}) assert_failure response @@ -157,7 +156,7 @@ def test_failed_store end def test_successful_triggered_payment - @gateway.store(@credit_card, {:billing_id => 'test1234', :amount => 15000}) rescue nil #Ensure it already exists + @gateway.store(@credit_card, {:billing_id => 'test1234', :amount => 15000}) rescue nil # Ensure it already exists assert response = @gateway.purchase(12300, 'test1234', @options) assert_success response @@ -167,7 +166,7 @@ def test_successful_triggered_payment end def test_failure_triggered_payment - @gateway.unstore('test1234') rescue nil #Ensure its no longer there + @gateway.unstore('test1234') rescue nil # Ensure its no longer there assert response = @gateway.purchase(12300, 'test1234', @options) assert_failure response @@ -182,6 +181,17 @@ def test_invalid_login ) assert response = gateway.purchase(@amount, @credit_card, @options) assert_failure response - assert_equal "Invalid merchant ID", response.message + assert_equal 'Invalid merchant ID', response.message + end + + def test_purchase_scrubbing + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @credit_card, @options) + end + transcript = @gateway.scrub(transcript) + + assert_scrubbed(credit_card.number, transcript) + assert_scrubbed(@gateway.options[:login], transcript) + assert_scrubbed(@gateway.options[:password], transcript) end end diff --git a/test/remote/gateways/remote_secure_pay_tech_test.rb b/test/remote/gateways/remote_secure_pay_tech_test.rb index 737d2d92280..3b76bf77eef 100644 --- a/test/remote/gateways/remote_secure_pay_tech_test.rb +++ b/test/remote/gateways/remote_secure_pay_tech_test.rb @@ -5,14 +5,14 @@ class RemoteSecurePayTechTest < Test::Unit::TestCase def setup @gateway = SecurePayTechGateway.new(fixtures(:secure_pay_tech)) - + @accepted_amount = 10000 @declined_amount = 10075 - + @credit_card = credit_card('4987654321098769', :month => '5', :year => '2013') @options = { :billing_address => address } end - + def test_successful_purchase assert response = @gateway.purchase(@accepted_amount, @credit_card, @options) assert_equal 'Transaction OK', response.message diff --git a/test/remote/gateways/remote_secure_pay_test.rb b/test/remote/gateways/remote_secure_pay_test.rb index 5806fd0ab72..63f83f5e40b 100644 --- a/test/remote/gateways/remote_secure_pay_test.rb +++ b/test/remote/gateways/remote_secure_pay_test.rb @@ -1,7 +1,7 @@ require 'test_helper' -class RemoteSecurePayTest < Test::Unit::TestCase - +class RemoteSecurePayTest < Test::Unit::TestCase + def setup @gateway = SecurePayGateway.new(fixtures(:secure_pay)) @@ -9,15 +9,16 @@ def setup :month => '7', :year => '2014' ) - - @options = { :order_id => generate_unique_id, + + @options = { + :order_id => generate_unique_id, :description => 'Store purchase', :billing_address => address } - + @amount = 100 end - + def test_successful_purchase assert response = @gateway.purchase(@amount, @credit_card, @options) assert response.success? diff --git a/test/remote/gateways/remote_securion_pay_test.rb b/test/remote/gateways/remote_securion_pay_test.rb new file mode 100644 index 00000000000..59b600899f1 --- /dev/null +++ b/test/remote/gateways/remote_securion_pay_test.rb @@ -0,0 +1,256 @@ +require 'test_helper' + +class RemoteSecurionPayTest < Test::Unit::TestCase + CHARGE_ID_REGEX = /char_[a-zA-Z\d]+/ + TOKEN_ID_REGEX = /tok_[a-zA-Z\d]+/ + + def setup + @gateway = SecurionPayGateway.new(fixtures(:securion_pay)) + + @amount = 2000 + @refund_amount = 300 + @credit_card = credit_card('4242424242424242') + @declined_card = credit_card('4916018475814056') + @new_credit_card = credit_card('4012888888881881') + @invalid_token = 'tok_invalid' + + @options = { + description: 'ActiveMerchant test charge', + email: 'foo@example.com' + } + end + + def test_successful_store + response = @gateway.store(@credit_card, @options) + assert_success response + assert_match %r(^cust_\w+$), response.authorization + assert_equal 'customer', response.params['objectType'] + assert_match %r(^card_\w+$), response.params['cards'][0]['id'] + assert_equal 'card', response.params['cards'][0]['objectType'] + + @options[:customer_id] = response.authorization + response = @gateway.store(@new_credit_card, @options) + assert_success response + assert_match %r(^card_\w+$), response.params['card']['id'] + assert_equal @options[:customer_id], response.params['card']['customerId'] + + response = @gateway.customer(@options) + assert_success response + assert_equal @options[:customer_id], response.params['id'] + assert_equal '401288', response.params['cards'][0]['first6'] + assert_equal '1881', response.params['cards'][0]['last4'] + assert_equal '424242', response.params['cards'][1]['first6'] + assert_equal '4242', response.params['cards'][1]['last4'] + end + + # def test_dump_transcript + # skip("Transcript scrubbing for this gateway has been tested.") + # dump_transcript_and_fail(@gateway, @amount, @credit_card, @options) + # end + + def test_transcript_scrubbing + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @credit_card, @options) + end + transcript = @gateway.scrub(transcript) + assert_scrubbed(@credit_card.number, transcript) + assert_scrubbed(@credit_card.verification_value, transcript) + assert_scrubbed(@gateway.options[:secret_key], transcript) + end + + def test_successful_purchase + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal 'Transaction approved', response.message + assert_equal 'foo@example.com', response.params['metadata']['email'] + assert_match CHARGE_ID_REGEX, response.authorization + end + + def test_unsuccessful_purchase + response = @gateway.purchase(@amount, @declined_card, @options) + assert_failure response + assert_match %r{The card was declined for other reason.}, response.message + assert_match Gateway::STANDARD_ERROR_CODE[:card_declined], response.error_code + end + + def test_authorization_and_capture + authorization = @gateway.authorize(@amount, @credit_card, @options) + assert_success authorization + assert !authorization.params['captured'] + assert_equal @options[:description], authorization.params['description'] + assert_equal @options[:email], authorization.params['metadata']['email'] + + response = @gateway.capture(@amount, authorization.authorization) + assert_success response + end + + def test_failed_authorize + response = @gateway.authorize(@amount, @declined_card, @options) + assert_failure response + end + + def test_failed_capture + response = @gateway.capture(@amount, 'invalid_authorization_token') + assert_failure response + assert_match %r{Requested Charge does not exist}, response.message + end + + def test_successful_full_refund + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + assert purchase.authorization + + refund = @gateway.refund(@amount, purchase.authorization) + assert_success refund + + assert refund.params['refunded'] + assert_equal 0, refund.params['amount'] + assert_equal 1, refund.params['refunds'].size + assert_equal @amount, refund.params['refunds'].map { |r| r['amount'] }.sum + + assert refund.authorization + end + + def test_successful_partially_refund + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + assert purchase.authorization + + first_refund = @gateway.refund(@refund_amount, purchase.authorization) + assert_success first_refund + + second_refund = @gateway.refund(@refund_amount, purchase.authorization) + assert_success second_refund + assert second_refund.params['refunded'] + assert_equal @amount - 2 * @refund_amount, second_refund.params['amount'] + assert_equal 2, second_refund.params['refunds'].size + assert_equal 2 * @refund_amount, second_refund.params['refunds'].map { |r| r['amount'] }.sum + assert second_refund.authorization + end + + def test_unsuccessful_authorize_refund + response = @gateway.refund(@amount, 'invalid_authorization_token') + assert_failure response + assert_match %r{Requested Charge does not exist}, response.message + end + + def test_unsuccessful_refund + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + assert purchase.authorization + + refund = @gateway.refund(@amount + 1, purchase.authorization, @options) + assert_failure refund + assert_match %r{Invalid Refund data}, refund.message + end + + def test_successful_void + authorization = @gateway.authorize(@amount, @credit_card, @options) + assert_success authorization + assert !authorization.params['captured'] + + void = @gateway.void(authorization.authorization, @options) + assert_success void + assert void.params['refunded'] + assert_equal 0, void.params['amount'] + assert_equal 1, void.params['refunds'].size + assert_equal @amount, void.params['refunds'].map { |r| r['amount'] }.sum + assert void.authorization + end + + def test_failed_void + response = @gateway.void('invalid_authorization_token', @options) + assert_failure response + assert_match %r{Requested Charge does not exist}, response.message + end + + def test_successful_verify + response = @gateway.verify(@credit_card, @options) + assert_success response + assert_match %r{Transaction approved}, response.responses.last.message + end + + def test_failed_verify + response = @gateway.verify(@declined_card, @options) + assert_failure response + assert_match %r{The card was declined for other reason.}, response.message + assert_match Gateway::STANDARD_ERROR_CODE[:card_declined], response.primary_response.error_code + end + + def test_incorrect_number_for_purchase + card = credit_card('4242424242424241') + response = @gateway.purchase(@amount, card, @options) + assert_failure response + assert_match Gateway::STANDARD_ERROR_CODE[:invalid_number], response.error_code + end + + def test_invalid_login + gateway = SecurionPayGateway.new(secret_key: 'active_merchant_test') + response = gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_match 'Provided API key is invalid', response.message + end + + def test_invalid_number_for_purchase + card = credit_card('-1') + response = @gateway.purchase(@amount, card, @options) + assert_failure response + assert_match Gateway::STANDARD_ERROR_CODE[:invalid_number], response.error_code + end + + def test_invalid_expiry_month_for_purchase + card = credit_card('4242424242424242', month: 16) + response = @gateway.purchase(@amount, card, @options) + assert_failure response + assert_match Gateway::STANDARD_ERROR_CODE[:invalid_expiry_date], response.error_code + end + + def test_invalid_expiry_year_for_purchase + card = credit_card('4242424242424242', year: 'xx') + response = @gateway.purchase(@amount, card, @options) + assert_failure response + assert_match Gateway::STANDARD_ERROR_CODE[:invalid_expiry_date], response.error_code + end + + def test_expired_card_for_purchase + card = credit_card('4916487051294548') + response = @gateway.purchase(@amount, card, @options) + assert_failure response + assert_match Gateway::STANDARD_ERROR_CODE[:expired_card], response.error_code + end + + def test_invalid_cvc_for_purchase + card = credit_card('4242424242424242', verification_value: -1) + response = @gateway.purchase(@amount, card, @options) + assert_failure response + assert_match Gateway::STANDARD_ERROR_CODE[:invalid_cvc], response.error_code + end + + def test_incorrect_cvc_for_purchase + card = credit_card('4024007134364842') + response = @gateway.purchase(@amount, card, @options) + assert_failure response + assert_match Gateway::STANDARD_ERROR_CODE[:incorrect_cvc], response.error_code + end + + def test_processing_error + card = credit_card('4024007114166316') + response = @gateway.purchase(@amount, card, @options) + assert_failure response + assert_match Gateway::STANDARD_ERROR_CODE[:processing_error], response.error_code + end + + def test_incorrect_zip + card = credit_card('4929225021529113') + response = @gateway.purchase(@amount, card, @options) + assert_failure response + assert_match Gateway::STANDARD_ERROR_CODE[:incorrect_zip], response.error_code + end + + def test_card_declined + card = credit_card('4916018475814056') + response = @gateway.purchase(@amount, card, @options) + assert_failure response + assert_match Gateway::STANDARD_ERROR_CODE[:card_declined], response.error_code + end +end diff --git a/test/remote/gateways/remote_skipjack_test.rb b/test/remote/gateways/remote_skipjack_test.rb index 760aed91a7c..d8ec78f732b 100644 --- a/test/remote/gateways/remote_skipjack_test.rb +++ b/test/remote/gateways/remote_skipjack_test.rb @@ -7,46 +7,46 @@ def setup @gateway = SkipJackGateway.new(fixtures(:skip_jack)) @credit_card = credit_card('4445999922225', - :verification_value => '999' - ) + :verification_value => '999' + ) @amount = 100 - + @options = { :order_id => generate_unique_id, :email => 'email@foo.com', :billing_address => address } end - + def test_successful_authorization authorization = @gateway.authorize(@amount, @credit_card, @options) assert_success authorization assert_false authorization.authorization.blank? end - + def test_unsuccessful_authorization @credit_card.number = '1234' authorization = @gateway.authorize(@amount, @credit_card, @options) assert_failure authorization end - + def test_authorization_fails_without_phone_number @options[:billing_address][:phone] = nil authorization = @gateway.authorize(@amount, @credit_card, @options) assert_failure authorization end - + def test_successful_purchase assert_success @gateway.purchase(@amount, @credit_card, @options) end def test_successful_authorization_and_capture authorization = @gateway.authorize(@amount, @credit_card, @options) - + assert_success authorization assert_false authorization.authorization.blank? - + capture = @gateway.capture(@amount, authorization.authorization) assert_success capture end @@ -54,16 +54,16 @@ def test_successful_authorization_and_capture def test_successful_authorization_and_partial_capture @amount = 2000 authorization = @gateway.authorize(@amount, @credit_card, @options) - + assert_success authorization assert_false authorization.authorization.blank? - + capture = @gateway.capture(1000, authorization.authorization) assert_success capture - assert_equal "1000", capture.params["TransactionAmount"] + assert_equal '1000', capture.params['TransactionAmount'] end - + def test_authorization_and_void authorization = @gateway.authorize(101, @credit_card, @options) assert_success authorization @@ -72,11 +72,11 @@ def test_authorization_and_void end def test_successful_authorization_and_credit - authorization = @gateway.authorize(@amount, @credit_card, @options) + authorization = @gateway.authorize(@amount, @credit_card, @options) assert_success authorization - + capture = @gateway.capture(@amount, authorization.authorization, :force_settlement => true) - assert_success capture + assert_success capture # developer login won't change transaction immediately to settled, so status will have to mismatch credit = @gateway.credit(@amount, authorization.authorization) @@ -85,7 +85,7 @@ def test_successful_authorization_and_credit def test_authorization_with_invalid_verification_value @credit_card.verification_value = '123' - + response = @gateway.authorize(@amount, @credit_card, @options) assert_failure response assert_equal "Card verification number didn't match", response.message @@ -94,7 +94,7 @@ def test_authorization_with_invalid_verification_value def test_authorization_and_status authorization = @gateway.authorize(101, @credit_card, @options) assert_success authorization - + status = @gateway.status(@options[:order_id]) assert_success status end @@ -102,9 +102,9 @@ def test_authorization_and_status def test_status_unkown_order status = @gateway.status(generate_unique_id) assert_failure status - assert_match /No Records Found/, status.message + assert_match %r{No Records Found}, status.message end - + def test_invalid_login gateway = SkipJackGateway.new( :login => '555555555555', @@ -113,6 +113,6 @@ def test_invalid_login response = gateway.authorize(@amount, @credit_card, @options) assert_failure response - assert_match /Invalid serial number/, response.message + assert_match %r{Invalid serial number}, response.message end end diff --git a/test/remote/gateways/remote_so_easy_pay_test.rb b/test/remote/gateways/remote_so_easy_pay_test.rb new file mode 100644 index 00000000000..e24d837703b --- /dev/null +++ b/test/remote/gateways/remote_so_easy_pay_test.rb @@ -0,0 +1,64 @@ +require 'test_helper' + +class RemoteSoEasyPayTest < Test::Unit::TestCase + + def setup + @gateway = SoEasyPayGateway.new(fixtures(:so_easy_pay)) + + @amount = 100 + @credit_card = credit_card('4111111111111111', {:verification_value => '000', :month => '12', :year => '2015'}) + @declined_card = credit_card('4000300011112220') + + @options = { + :currency => 'EUR', + :ip => '192.168.19.123', + :email => 'test@blaha.com', + :order_id => generate_unique_id, + :billing_address => address, + :description => 'Store Purchase' + } + end + + def test_successful_purchase + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal 'Transaction successful', response.message + end + + def test_unsuccessful_purchase + assert response = @gateway.purchase(@amount, @declined_card, @options) + assert_failure response + end + + def test_authorize_and_capture + amount = @amount + assert auth = @gateway.authorize(amount, @credit_card, @options) + assert_success auth + assert auth.authorization + assert capture = @gateway.capture(amount, auth.authorization) + assert_success capture + end + + def test_failed_capture + assert response = @gateway.capture(@amount, '') + assert_failure response + end + + def test_successful_void + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + + assert response = @gateway.void(response.authorization) + assert_success response + end + + def test_invalid_login + gateway = SoEasyPayGateway.new( + :login => 'one', + :password => 'wrong' + ) + assert response = gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_equal 'Website verification failed, wrong websiteID or password', response.message + end +end diff --git a/test/remote/gateways/remote_spreedly_core_test.rb b/test/remote/gateways/remote_spreedly_core_test.rb index 73f48e56042..a162d35a6fc 100644 --- a/test/remote/gateways/remote_spreedly_core_test.rb +++ b/test/remote/gateways/remote_spreedly_core_test.rb @@ -8,8 +8,11 @@ def setup @amount = 100 @credit_card = credit_card('5555555555554444') @declined_card = credit_card('4012888888881881') - @existing_payment_method = '9AjLflWs7SOKuqJLveOZya9bixa' - @declined_payment_method = 'n3JElNt9joT1mJ3CxvWjyEN39N' + @check = check({routing_number: '021000021', account_number: '9876543210'}) + @existing_payment_method = '3rEkRlZur2hXKbwwRBidHJAIUTO' + @declined_payment_method = 'UPfh3J3JbekLeYC88BP741JWnS5' + @existing_transaction = 'PJ5ICgM6h7v9pBNxDCJjRHDDxBC' + @not_found_transaction = 'AdyQXaG0SVpSoMPdmFlvd3aA3uz' end def test_successful_purchase_with_token @@ -21,7 +24,7 @@ def test_successful_purchase_with_token def test_failed_purchase_with_token assert response = @gateway.purchase(@amount, @declined_payment_method) assert_failure response - assert_match %r(Unable to process the transaction), response.message + assert_match %r(Unable to process the purchase transaction), response.message end def test_successful_authorize_with_token_and_capture @@ -38,7 +41,7 @@ def test_successful_authorize_with_token_and_capture def test_failed_authorize_with_token assert response = @gateway.authorize(@amount, @declined_payment_method) assert_failure response - assert_match %r(Unable to process the transaction), response.message + assert_match %r(Unable to process the authorize transaction), response.message end def test_failed_capture @@ -55,6 +58,15 @@ def test_successful_purchase_with_credit_card assert_success response assert_equal 'Succeeded!', response.message assert_equal 'Purchase', response.params['transaction_type'] + assert_equal 'cached', response.params['payment_method_storage_state'] + end + + def test_successful_purchase_with_check + assert response = @gateway.purchase(@amount, @check) + assert_success response + assert_equal 'Succeeded!', response.message + assert_equal 'Purchase', response.params['transaction_type'] + assert_equal 'cached', response.params['payment_method_storage_state'] end def test_successful_purchase_with_card_and_address @@ -68,7 +80,7 @@ def test_successful_purchase_with_card_and_address assert_equal 'Succeeded!', response.message assert_equal 'joebob@example.com', response.params['payment_method_email'] - assert_equal '1234 My Street', response.params['payment_method_address1'] + assert_equal '456 My Street', response.params['payment_method_address1'] assert_equal 'Apt 1', response.params['payment_method_address2'] assert_equal 'Ottawa', response.params['payment_method_city'] assert_equal 'ON', response.params['payment_method_state'] @@ -85,7 +97,17 @@ def test_failed_purchase_with_invalid_credit_card @credit_card.first_name = ' ' assert response = @gateway.purchase(@amount, @credit_card) assert_failure response - assert_equal "First name can't be blank", response.message + assert_equal 'The payment method is invalid.', response.message + assert_equal "First name can't be blank", response.params['payment_method_errors'].strip + end + + def test_successful_purchase_with_store + assert response = @gateway.purchase(@amount, @credit_card, store: true) + assert_success response + assert_equal 'Succeeded!', response.message + assert_equal 'Purchase', response.params['transaction_type'] + assert %w(retained cached).include?(response.params['payment_method_storage_state']) + assert !response.params['payment_method_token'].blank? end def test_successful_authorize_and_capture_with_credit_card @@ -112,7 +134,7 @@ def test_successful_authorize_with_card_and_address assert_equal 'Authorization', response.params['transaction_type'] assert_equal 'joebob@example.com', response.params['payment_method_email'] - assert_equal '1234 My Street', response.params['payment_method_address1'] + assert_equal '456 My Street', response.params['payment_method_address1'] assert_equal 'Apt 1', response.params['payment_method_address2'] assert_equal 'Ottawa', response.params['payment_method_city'] assert_equal 'ON', response.params['payment_method_state'] @@ -129,7 +151,17 @@ def test_failed_authrorize_with_invalid_credit_card @credit_card.first_name = ' ' assert response = @gateway.authorize(@amount, @credit_card) assert_failure response - assert_equal "First name can't be blank", response.message + assert_equal 'The payment method is invalid.', response.message + assert_equal "First name can't be blank", response.params['payment_method_errors'].strip + end + + def test_successful_authorize_with_store + assert response = @gateway.authorize(@amount, @credit_card, store: true) + assert_success response + assert_equal 'Succeeded!', response.message + assert_equal 'Authorization', response.params['transaction_type'] + assert %w(retained cached).include?(response.params['payment_method_storage_state']) + assert !response.params['payment_method_token'].blank? end def test_successful_store @@ -169,7 +201,7 @@ def test_successful_store_with_address assert response = @gateway.store(@credit_card, options) assert_success response assert_equal 'joebob@example.com', response.params['payment_method_email'] - assert_equal '1234 My Street', response.params['payment_method_address1'] + assert_equal '456 My Street', response.params['payment_method_address1'] assert_equal 'Apt 1', response.params['payment_method_address2'] assert_equal 'Ottawa', response.params['payment_method_city'] assert_equal 'ON', response.params['payment_method_state'] @@ -219,11 +251,74 @@ def test_successful_void assert_equal 'Succeeded!', response.message end + def test_successful_verify_with_token + assert response = @gateway.verify(@existing_payment_method) + assert_success response + assert_equal 'Succeeded!', response.message + assert_equal 'Verification', response.params['transaction_type'] + assert_includes %w(cached retained), response.params['payment_method_storage_state'] + end + + def test_failed_verify_with_token + assert response = @gateway.verify(@declined_payment_method) + assert_failure response + end + + def test_successful_verify_with_credit_card + assert response = @gateway.verify(@credit_card) + assert_success response + assert_equal 'Succeeded!', response.message + assert_equal 'Verification', response.params['transaction_type'] + assert_includes %w(cached retained), response.params['payment_method_storage_state'] + end + + def test_failed_verify_with_declined_credit_card + assert response = @gateway.verify(@declined_card) + assert_failure response + assert_match %r(Unable to process the verify transaction), response.message + end + + def test_successful_find_transaction + assert response = @gateway.find(@existing_transaction) + assert_success response + assert_equal 'Succeeded!', response.message + assert_equal 'Purchase', response.params['transaction_type'] + end + + def test_failed_find_transaction + assert response = @gateway.find(@not_found_transaction) + assert_failure response + assert_match %r(Unable to find the transaction), response.message + end + def test_invalid_login gateway = SpreedlyCoreGateway.new(:login => 'Bogus', :password => 'MoreBogus', :gateway_token => 'EvenMoreBogus') assert response = gateway.purchase(@amount, @existing_payment_method) assert_failure response - assert_equal 'HTTP Basic: Access denied.', response.message + assert_match %r{Unable to authenticate}, response.message + end + + def test_scrubbing_purchase + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @credit_card) + end + transcript = @gateway.scrub(transcript) + + assert_scrubbed(@credit_card.number, transcript) + assert_scrubbed(@credit_card.verification_value, transcript) + assert_scrubbed(@gateway.options[:login], transcript) + assert_scrubbed(@gateway.options[:password], transcript) + end + + def test_scrubbing_purchase_with_token + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @existing_payment_method) + end + transcript = @gateway.scrub(transcript) + + assert_scrubbed(@existing_payment_method, transcript) + assert_scrubbed(@gateway.options[:login], transcript) + assert_scrubbed(@gateway.options[:password], transcript) end end diff --git a/test/remote/gateways/remote_stripe_3ds_test.rb b/test/remote/gateways/remote_stripe_3ds_test.rb new file mode 100644 index 00000000000..fb596dc05ae --- /dev/null +++ b/test/remote/gateways/remote_stripe_3ds_test.rb @@ -0,0 +1,194 @@ +require 'test_helper' +require 'mechanize' + +class RemoteStripe3DSTest < Test::Unit::TestCase + CHARGE_ID_REGEX = /ch_[a-zA-Z\d]{24}/ + + def setup + @gateway = StripeGateway.new(fixtures(:stripe)) + @amount = 100 + @billing_details = address() + + @options = { + :currency => 'USD', + :description => 'ActiveMerchant Test Purchase', + :email => 'wow@example.com', + :execute_threed => true, + :redirect_url => 'http://www.example.com/redirect', + :callback_url => 'http://www.example.com/callback', + :billing_address => @billing_details + } + @credit_card = credit_card('4000000000003063') + @non_3ds_card = credit_card('378282246310005') + + @stripe_account = fixtures(:stripe_destination)[:stripe_user_id] + end + + def test_create_3ds_card_source + assert response = @gateway.send(:create_source, @amount, @credit_card, 'card', @options) + assert_card_source(response) + end + + def test_create_non3ds_card_source + assert response = @gateway.send(:create_source, @amount, @non_3ds_card, 'card', @options) + assert_card_source(response, 'not_supported') + end + + def test_create_3ds_source + card_source = @gateway.send(:create_source, @amount, @credit_card, 'card', @options) + assert response = @gateway.send(:create_source, @amount, card_source.params['id'], 'three_d_secure', @options) + assert_success response + assert_three_ds_source(response) + end + + def test_show_3ds_source + card_source = @gateway.send(:create_source, @amount, @credit_card, 'card', @options) + assert three_d_secure_source = @gateway.send(:create_source, @amount, card_source.params['id'], 'three_d_secure', @options) + assert_success three_d_secure_source + assert_three_ds_source(three_d_secure_source) + + assert response = @gateway.send(:show_source, three_d_secure_source.params['id'], @options) + assert_three_ds_source(response) + end + + def test_create_webhook_endpoint + response = @gateway.send(:create_webhook_endpoint, @options, ['source.chargeable']) + assert_includes response.params['enabled_events'], 'source.chargeable' + assert_equal @options[:callback_url], response.params['url'] + assert_equal 'enabled', response.params['status'] + assert_nil response.params['application'] + + deleted_response = @gateway.send(:delete_webhook_endpoint, @options.merge(:webhook_id => response.params['id'])) + assert_equal true, deleted_response.params['deleted'] + end + + def test_create_webhook_endpoint_on_connected_account + response = @gateway.send(:create_webhook_endpoint, @options.merge({stripe_account: @stripe_account}), ['source.chargeable']) + assert_includes response.params['enabled_events'], 'source.chargeable' + assert_equal @options[:callback_url], response.params['url'] + assert_equal 'enabled', response.params['status'] + assert_not_nil response.params['application'] + + deleted_response = @gateway.send(:delete_webhook_endpoint, @options.merge(:webhook_id => response.params['id'])) + assert_equal true, deleted_response.params['deleted'] + end + + def test_delete_webhook_endpoint + webhook = @gateway.send(:create_webhook_endpoint, @options, ['source.chargeable']) + response = @gateway.send(:delete_webhook_endpoint, @options.merge(:webhook_id => webhook.params['id'])) + assert_equal response.params['id'], webhook.params['id'] + assert_equal true, response.params['deleted'] + end + + def test_delete_webhook_endpoint_on_connected_account + webhook = @gateway.send(:create_webhook_endpoint, @options.merge({stripe_account: @stripe_account}), ['source.chargeable']) + response = @gateway.send(:delete_webhook_endpoint, @options.merge(:webhook_id => webhook.params['id'])) + assert_equal response.params['id'], webhook.params['id'] + assert_equal true, response.params['deleted'] + end + + def test_show_webhook_endpoint + webhook = @gateway.send(:create_webhook_endpoint, @options, ['source.chargeable']) + response = @gateway.send(:show_webhook_endpoint, @options.merge(:webhook_id => webhook.params['id'])) + assert_includes response.params['enabled_events'], 'source.chargeable' + assert_equal @options[:callback_url], response.params['url'] + assert_equal 'enabled', response.params['status'] + assert_nil response.params['application'] + + deleted_response = @gateway.send(:delete_webhook_endpoint, @options.merge(:webhook_id => response.params['id'])) + assert_equal true, deleted_response.params['deleted'] + end + + def test_show_webhook_endpoint_on_connected_account + webhook = @gateway.send(:create_webhook_endpoint, @options.merge({stripe_account: @stripe_account}), ['source.chargeable']) + response = @gateway.send(:show_webhook_endpoint, @options.merge({:webhook_id => webhook.params['id'], stripe_account: @stripe_account})) + + assert_includes response.params['enabled_events'], 'source.chargeable' + assert_equal @options[:callback_url], response.params['url'] + assert_equal 'enabled', response.params['status'] + assert_not_nil response.params['application'] + + deleted_response = @gateway.send(:delete_webhook_endpoint, @options.merge(:webhook_id => response.params['id'])) + assert_equal true, deleted_response.params['deleted'] + end + + def test_list_webhook_endpoints + webhook1 = @gateway.send(:create_webhook_endpoint, @options, ['source.chargeable']) + webhook2 = @gateway.send(:create_webhook_endpoint, @options.merge({stripe_account: @stripe_account}), ['source.chargeable']) + assert_nil webhook1.params['application'] + assert_not_nil webhook2.params['application'] + + response = @gateway.send(:list_webhook_endpoints, @options.merge({limit: 100})) + assert_not_nil response.params + assert_equal 'list', response.params['object'] + assert response.params['data'].size >= 2 + webhook_id_set = Set.new(response.params['data'].map { |webhook| webhook['id'] }.uniq) + assert Set[webhook1.params['id'], webhook2.params['id']].subset?(webhook_id_set) + + deleted_response1 = @gateway.send(:delete_webhook_endpoint, @options.merge(:webhook_id => webhook1.params['id'])) + deleted_response2 = @gateway.send(:delete_webhook_endpoint, @options.merge(:webhook_id => webhook2.params['id'])) + assert_equal true, deleted_response1.params['deleted'] + assert_equal true, deleted_response2.params['deleted'] + end + + def test_3ds_purchase + card_source_response = @gateway.send(:create_source, @amount, @credit_card, 'card', @options) + assert_card_source(card_source_response) + + assert three_ds_source_response = @gateway.send(:create_source, @amount, card_source_response.params['id'], 'three_d_secure', @options) + assert_success three_ds_source_response + assert_three_ds_source(three_ds_source_response) + + # Simulate 3DS 1.0 authentication in the test environment + authentication_url = three_ds_source_response.params['redirect']['url'] + agent = Mechanize.new + page = agent.get(authentication_url) + + form = page.forms.first + form.submit.tap do |result_page| + assert_equal '200', result_page.code + end + + # Test charging of the 3DS source + threeds_params = {} + threeds_params[:source] = three_ds_source_response.params['id'] + threeds_params[:capture] = 'true' + + @gateway.send(:add_charge_details, threeds_params, @amount, @credit_card, @options) + + assert response = @gateway.send(:commit, :post, 'charges', threeds_params, @options) + assert_equal 'charge', response.params['object'] + assert_equal 'succeeded', response.params['status'] + assert_equal true, response.params['captured'] + assert_equal 'three_d_secure', response.params.dig('source', 'type') + assert_equal true, response.params.dig('payment_method_details', 'card', 'three_d_secure', 'authenticated') + + # Check that billing details have been propagated from the card source to the charge + billing_details = response.params['billing_details'] + assert_equal @options[:email], billing_details['email'] + assert_equal @credit_card.name, billing_details['name'] + assert_equal @billing_details[:phone], billing_details['phone'] + assert_equal @billing_details[:address1], billing_details['address']['line1'] + assert_equal @billing_details[:address2], billing_details['address']['line2'] + assert_equal @billing_details[:city], billing_details['address']['city'] + assert_equal @billing_details[:state], billing_details['address']['state'] + assert_equal @billing_details[:zip], billing_details['address']['postal_code'] + assert_equal @billing_details[:country], billing_details['address']['country'] + end + + def assert_card_source(response, three_d_secure_status = 'required') + assert_success response + assert_equal 'source', response.params['object'] + assert_equal 'chargeable', response.params['status'] + assert_equal three_d_secure_status, response.params['card']['three_d_secure'] + assert_equal 'card', response.params['type'] + end + + def assert_three_ds_source(response) + assert_equal 'source', response.params['object'] + assert_equal 'pending', response.params['status'] + assert_equal 'three_d_secure', response.params['type'] + assert_equal false, response.params['three_d_secure']['authenticated'] + end + +end diff --git a/test/remote/gateways/remote_stripe_android_pay_test.rb b/test/remote/gateways/remote_stripe_android_pay_test.rb new file mode 100644 index 00000000000..5abf0974a17 --- /dev/null +++ b/test/remote/gateways/remote_stripe_android_pay_test.rb @@ -0,0 +1,48 @@ +require 'test_helper' + +class RemoteStripeAndroidPayTest < Test::Unit::TestCase + CHARGE_ID_REGEX = /ch_[a-zA-Z\d]{24}/ + + def setup + @gateway = StripeGateway.new(fixtures(:stripe)) + @amount = 100 + + @options = { + :currency => 'USD', + :description => 'ActiveMerchant Test Purchase', + :email => 'wow@example.com' + } + end + + def test_successful_purchase_with_android_pay_raw_cryptogram + credit_card = network_tokenization_credit_card('4242424242424242', + payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=', + verification_value: nil, + eci: '05', + source: :android_pay + ) + assert response = @gateway.purchase(@amount, credit_card, @options) + assert_success response + assert_equal 'charge', response.params['object'] + assert response.params['paid'] + assert_equal 'ActiveMerchant Test Purchase', response.params['description'] + assert_equal 'wow@example.com', response.params['metadata']['email'] + assert_match CHARGE_ID_REGEX, response.authorization + end + + def test_successful_auth_with_android_pay_raw_cryptogram + credit_card = network_tokenization_credit_card('4242424242424242', + payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=', + verification_value: nil, + eci: '05', + source: :android_pay + ) + assert response = @gateway.authorize(@amount, credit_card, @options) + assert_success response + assert_equal 'charge', response.params['object'] + assert response.params['paid'] + assert_equal 'ActiveMerchant Test Purchase', response.params['description'] + assert_equal 'wow@example.com', response.params['metadata']['email'] + assert_match CHARGE_ID_REGEX, response.authorization + end +end diff --git a/test/remote/gateways/remote_stripe_apple_pay_test.rb b/test/remote/gateways/remote_stripe_apple_pay_test.rb new file mode 100644 index 00000000000..c5231114490 --- /dev/null +++ b/test/remote/gateways/remote_stripe_apple_pay_test.rb @@ -0,0 +1,165 @@ +require 'test_helper' + +class RemoteStripeApplePayTest < Test::Unit::TestCase + CHARGE_ID_REGEX = /ch_[a-zA-Z\d]{24}/ + + def setup + @gateway = StripeGateway.new(fixtures(:stripe)) + @amount = 100 + + @options = { + :currency => 'USD', + :description => 'ActiveMerchant Test Purchase', + :email => 'wow@example.com' + } + @apple_pay_payment_token = apple_pay_payment_token + end + + def test_successful_purchase_with_apple_pay_payment_token + assert response = @gateway.purchase(@amount, @apple_pay_payment_token, @options) + assert_success response + assert_equal 'charge', response.params['object'] + assert response.params['paid'] + assert_equal 'ActiveMerchant Test Purchase', response.params['description'] + assert_equal 'wow@example.com', response.params['metadata']['email'] + assert_match CHARGE_ID_REGEX, response.authorization + end + + def test_authorization_and_capture_with_apple_pay_payment_token + assert authorization = @gateway.authorize(@amount, @apple_pay_payment_token, @options) + assert_success authorization + refute authorization.params['captured'] + assert_equal 'ActiveMerchant Test Purchase', authorization.params['description'] + assert_equal 'wow@example.com', authorization.params['metadata']['email'] + + assert capture = @gateway.capture(@amount, authorization.authorization) + assert_success capture + end + + def test_authorization_and_void_with_apple_pay_payment_token + assert authorization = @gateway.authorize(@amount, @apple_pay_payment_token, @options) + assert_success authorization + refute authorization.params['captured'] + + assert void = @gateway.void(authorization.authorization) + assert_success void + end + + def test_successful_void_with_apple_pay_payment_token + assert response = @gateway.purchase(@amount, @apple_pay_payment_token, @options) + assert_success response + assert response.authorization + assert void = @gateway.void(response.authorization) + assert_success void + end + + def test_successful_store_with_apple_pay_payment_token + assert response = @gateway.store(@apple_pay_payment_token, {:description => 'Active Merchant Test Customer', :email => 'email@example.com'}) + assert_success response + assert_equal 'customer', response.params['object'] + assert_equal 'Active Merchant Test Customer', response.params['description'] + assert_equal 'email@example.com', response.params['email'] + first_card = response.params['cards']['data'].first + assert_equal response.params['default_card'], first_card['id'] + assert_equal '4242', first_card['dynamic_last4'] # when stripe is in test mode, token exchanged will return a test card with dynamic_last4 4242 + assert_equal '0000', first_card['last4'] # last4 is 0000 when using an apple pay token + end + + def test_successful_store_with_existing_customer_and_apple_pay_payment_token + assert response = @gateway.store(@credit_card, {:description => 'Active Merchant Test Customer'}) + assert_success response + + assert response = @gateway.store(@apple_pay_payment_token, {:customer => response.params['id'], :description => 'Active Merchant Test Customer', :email => 'email@example.com'}) + assert_success response + assert_equal 2, response.responses.size + + card_response = response.responses[0] + assert_equal 'card', card_response.params['object'] + assert_equal '4242', card_response.params['dynamic_last4'] # when stripe is in test mode, token exchanged will return a test card with dynamic_last4 4242 + assert_equal '0000', card_response.params['last4'] # last4 is 0000 when using an apple pay token + + customer_response = response.responses[1] + assert_equal 'customer', customer_response.params['object'] + assert_equal 'Active Merchant Test Customer', customer_response.params['description'] + assert_equal 'email@example.com', customer_response.params['email'] + assert_equal 2, customer_response.params['cards']['count'] + end + + def test_successful_recurring_with_apple_pay_payment_token + assert response = @gateway.store(@apple_pay_payment_token, {:description => 'Active Merchant Test Customer', :email => 'email@example.com'}) + assert_success response + assert recharge_options = @options.merge(:customer => response.params['id']) + assert response = @gateway.purchase(@amount, nil, recharge_options) + assert_success response + assert_equal 'charge', response.params['object'] + assert response.params['paid'] + end + + def test_purchase_with_unsuccessful_apple_pay_token_exchange + assert response = @gateway.purchase(@amount, ApplePayPaymentToken.new('garbage'), @options) + assert_failure response + end + + def test_successful_purchase_with_apple_pay_raw_cryptogram_with_eci + credit_card = network_tokenization_credit_card('4242424242424242', + payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=', + verification_value: nil, + eci: '05', + source: :apple_pay + ) + assert response = @gateway.purchase(@amount, credit_card, @options) + assert_success response + assert_equal 'charge', response.params['object'] + assert response.params['paid'] + assert_equal 'ActiveMerchant Test Purchase', response.params['description'] + assert_equal 'wow@example.com', response.params['metadata']['email'] + assert_match CHARGE_ID_REGEX, response.authorization + end + + def test_successful_purchase_with_apple_pay_raw_cryptogram_without_eci + credit_card = network_tokenization_credit_card('4242424242424242', + payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=', + verification_value: nil, + source: :apple_pay + ) + assert response = @gateway.purchase(@amount, credit_card, @options) + assert_success response + assert_equal 'charge', response.params['object'] + assert response.params['paid'] + assert_equal 'ActiveMerchant Test Purchase', response.params['description'] + assert_equal 'wow@example.com', response.params['metadata']['email'] + assert_match CHARGE_ID_REGEX, response.authorization + end + + def test_successful_auth_with_apple_pay_raw_cryptogram_with_eci + credit_card = network_tokenization_credit_card('4242424242424242', + payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=', + verification_value: nil, + eci: '05', + source: :apple_pay + ) + assert response = @gateway.authorize(@amount, credit_card, @options) + assert_success response + assert_equal 'charge', response.params['object'] + assert response.params['paid'] + assert_equal 'ActiveMerchant Test Purchase', response.params['description'] + assert_equal 'wow@example.com', response.params['metadata']['email'] + assert_match CHARGE_ID_REGEX, response.authorization + end + + def test_successful_auth_with_apple_pay_raw_cryptogram_without_eci + credit_card = network_tokenization_credit_card('4242424242424242', + payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=', + verification_value: nil, + source: :apple_pay + ) + assert response = @gateway.authorize(@amount, credit_card, @options) + assert_success response + assert_equal 'charge', response.params['object'] + assert response.params['paid'] + assert_equal 'ActiveMerchant Test Purchase', response.params['description'] + assert_equal 'wow@example.com', response.params['metadata']['email'] + assert_match CHARGE_ID_REGEX, response.authorization + end + +end diff --git a/test/remote/gateways/remote_stripe_connect_test.rb b/test/remote/gateways/remote_stripe_connect_test.rb new file mode 100644 index 00000000000..8309d62f98d --- /dev/null +++ b/test/remote/gateways/remote_stripe_connect_test.rb @@ -0,0 +1,60 @@ +require 'test_helper' + +class RemoteStripeConnectTest < Test::Unit::TestCase + def setup + @gateway = StripeGateway.new(fixtures(:stripe)) + + @amount = 100 + @credit_card = credit_card('4242424242424242') + @declined_card = credit_card('4000000000000002') + @new_credit_card = credit_card('5105105105105100') + + @options = { + :currency => 'USD', + :description => 'ActiveMerchant Test Purchase', + :email => 'wow@example.com', + :stripe_account => fixtures(:stripe_destination)[:stripe_user_id] + } + end + + def test_application_fee_for_stripe_connect + assert response = @gateway.purchase(@amount, @credit_card, @options.merge(:application_fee => 12)) + assert_success response + end + + def test_successful_refund_with_application_fee + assert response = @gateway.purchase(@amount, @credit_card, @options.merge(:application_fee => 12)) + assert refund = @gateway.refund(@amount, response.authorization, @options.merge(:refund_application_fee => true)) + assert_success refund + + # Verify the application fee is refunded + fetch_fee_id = @gateway.send(:fetch_application_fee, response.authorization, @options) + fee_id = @gateway.send(:application_fee_from_response, fetch_fee_id) + refund_check = @gateway.send(:refund_application_fee, 10, fee_id, @options) + assert_equal 'Application fee could not be refunded: Refund amount ($0.10) is greater than unrefunded amount on fee ($0.00)', refund_check.message + end + + def test_refund_partial_application_fee + assert response = @gateway.purchase(@amount, @credit_card, @options.merge(:application_fee => 12)) + assert refund = @gateway.refund(@amount-20, response.authorization, @options.merge(:refund_fee_amount => '10')) + assert_success refund + + # Verify the application fee is partially refunded + fetch_fee_id = @gateway.send(:fetch_application_fee, response.authorization, @options) + fee_id = @gateway.send(:application_fee_from_response, fetch_fee_id) + refund_check = @gateway.send(:refund_application_fee, 10, fee_id, @options) + assert_equal 'Application fee could not be refunded: Refund amount ($0.10) is greater than unrefunded amount on fee ($0.02)', refund_check.message + end + + def test_refund_application_fee_amount_zero + assert response = @gateway.purchase(@amount, @credit_card, @options.merge(:application_fee => 12)) + assert refund = @gateway.refund(@amount-20, response.authorization, @options.merge(:refund_fee_amount => '0')) + assert_success refund + + # Verify the application fee is not refunded + fetch_fee_id = @gateway.send(:fetch_application_fee, response.authorization, @options) + fee_id = @gateway.send(:application_fee_from_response, fetch_fee_id) + refund_check = @gateway.send(:refund_application_fee, 14, fee_id, @options) + assert_equal 'Application fee could not be refunded: Refund amount ($0.14) is greater than fee amount ($0.12)', refund_check.message + end +end diff --git a/test/remote/gateways/remote_stripe_emv_test.rb b/test/remote/gateways/remote_stripe_emv_test.rb new file mode 100644 index 00000000000..6d2742d7d35 --- /dev/null +++ b/test/remote/gateways/remote_stripe_emv_test.rb @@ -0,0 +1,146 @@ +require 'test_helper' + +class RemoteStripeEmvTest < Test::Unit::TestCase + CHARGE_ID_REGEX = /ch_[a-zA-Z\d]{24}/ + + def setup + @gateway = StripeGateway.new(fixtures(:stripe)) + + @amount = 100 + @emv_credit_cards = { + uk: ActiveMerchant::Billing::CreditCard.new(icc_data: '5A08476173900101011957114761739001010119D221220117589288899F131F3137353839383930303238383030303030304F07A0000000031010500B564953412043524544495482025C008407A00000000310108A025A318E0E00000000000000001E0302031F00950542000080009A031707259B02E8009C01005F24032212315F25030907015F2A0208265F3401019F01060000000000019F02060000000001009F03060000000000009F0607A00000000310109F0702FF009F0902008C9F0D05F0400088009F0E0500100000009F0F05F0400098009F100706010A03A000009F120F4352454449544F20444520564953419F160F3132333435363738393031323334359F1A0208269F1C0831313232333334349F1E0831323334353637389F21031137269F26084A3000C111F061539F2701809F33036028C89F34031E03009F3501219F360200029F370467D5DD109F3901059F40057E0000A0019F4104000001979F4E0D54657374204D65726368616E749F110101DF834F0F434842313136373235303030343439DF83620100'), + us: ActiveMerchant::Billing::CreditCard.new(icc_data: '5A08476173900101011957114761739001010119D221220117589288899F131F3137353839383930303238383030303030304F07A0000000031010500B564953412043524544495482025C008407A00000000310108A025A318E0E00000000000000001E0302031F00950542000080009A031707259B02E8009C01005F24032212315F25030907015F2A0208405F3401019F01060000000000019F02060000000001009F03060000000000009F0607A00000000310109F0702FF009F0902008C9F0D05F0400088009F0E0500100000009F0F05F0400098009F100706010A03A000009F120F4352454449544F20444520564953419F160F3132333435363738393031323334359F1A0208409F1C0831313232333334349F1E0831323334353637389F21031137269F26084A3000C111F061539F2701809F33036028C89F34031E03009F3501219F360200029F370467D5DD109F3901059F40057E0000A0019F4104000001979F4E0D54657374204D65726368616E749F110101DF834F0F434842313136373235303030343439DF83620100'), + contactless: ActiveMerchant::Billing::CreditCard.new(icc_data: '5A08476173900101011957114761739001010119D22122011758928889500D5649534120454C454354524F4E5F20175649534120434445542032312F434152443035202020205F2A0208405F340111820200008407A00000000320109A031505119C01009F02060000000006009F0607A00000000320109F090200019F100706011103A000009F1A0200569F1C0831323334353637389F1E0831303030333236389F260852A5A96394EDA96D9F2701809F3303E0B8C89F3501229F360200069F3704A4428D7A9F410400000289') + } + + @options = { + :currency => 'USD', + :description => 'ActiveMerchant Test Purchase', + :email => 'wow@example.com' + } + + # This capture hex says that the payload is a transaction cryptogram (TC) but does not + # provide the actual cryptogram. This will only work in test mode and would cause real + # cards to be declined. + @capture_options = { icc_data: '9F270140' } + end + + # for EMV contact transactions, it's advised to do a separate auth + capture + # to satisfy the EMV chip's transaction flow, but this works as a legal + # API call. You shouldn't use it in a real EMV implementation, though. + def test_successful_purchase_with_emv_credit_card_in_uk + assert response = @gateway.purchase(@amount, @emv_credit_cards[:uk], @options) + assert_success response + assert_equal 'charge', response.params['object'] + assert response.params['paid'] + assert_match CHARGE_ID_REGEX, response.authorization + end + + # perform separate auth & capture rather than a purchase in practice for the + # reasons mentioned above. + def test_successful_purchase_with_emv_credit_card_in_us + assert response = @gateway.purchase(@amount, @emv_credit_cards[:us], @options) + assert_success response + assert_equal 'charge', response.params['object'] + assert response.params['paid'] + assert_match CHARGE_ID_REGEX, response.authorization + end + + def test_successful_purchase_with_quickchip_credit_card_in_us + credit_card = @emv_credit_cards[:us] + credit_card.read_method = 'contact_quickchip' + assert response = @gateway.purchase(@amount, credit_card, @options) + assert_success response + assert_equal 'charge', response.params['object'] + assert response.params['captured'] + assert response.params['paid'] + assert_match CHARGE_ID_REGEX, response.authorization + end + + # For EMV contactless transactions, generally a purchase is preferred since + # a TC is typically generated at the point of sale. + def test_successful_purchase_with_emv_contactless_credit_card + emv_credit_card = @emv_credit_cards[:contactless] + emv_credit_card.read_method = 'contactless' + assert response = @gateway.purchase(@amount, emv_credit_card, @options) + assert_success response + assert_equal 'charge', response.params['object'] + assert response.params['paid'] + assert_match CHARGE_ID_REGEX, response.authorization + end + + def test_authorization_and_capture_with_emv_credit_card_in_uk + assert authorization = @gateway.authorize(@amount, @emv_credit_cards[:uk], @options) + assert_success authorization + assert authorization.emv_authorization, 'Authorization should contain emv_authorization containing the EMV ARPC' + refute authorization.params['captured'] + + assert capture = @gateway.capture(@amount, authorization.authorization, @capture_options) + assert_success capture + assert capture.emv_authorization, 'Capture should contain emv_authorization containing the EMV TC' + end + + def test_authorization_and_capture_with_emv_credit_card_in_us + assert authorization = @gateway.authorize(@amount, @emv_credit_cards[:us], @options) + assert_success authorization + assert authorization.emv_authorization, 'Authorization should contain emv_authorization containing the EMV ARPC' + refute authorization.params['captured'] + + assert capture = @gateway.capture(@amount, authorization.authorization, @capture_options) + assert_success capture + assert capture.emv_authorization, 'Capture should contain emv_authorization containing the EMV TC' + end + + def test_authorization_and_capture_of_online_pin_with_emv_credit_card_in_us + emv_credit_card = @emv_credit_cards[:us] + emv_credit_card.encrypted_pin_cryptogram = '8b68af72199529b8' + emv_credit_card.encrypted_pin_ksn = 'ffff0102628d12000001' + + assert authorization = @gateway.authorize(@amount, emv_credit_card, @options) + assert_success authorization + assert authorization.emv_authorization, 'Authorization should contain emv_authorization containing the EMV ARPC' + refute authorization.params['captured'] + + assert capture = @gateway.capture(@amount, authorization.authorization, @capture_options) + assert_success capture + assert capture.emv_authorization, 'Capture should contain emv_authorization containing the EMV TC' + end + + def test_authorization_and_void_with_emv_credit_card_in_us + assert authorization = @gateway.authorize(@amount, @emv_credit_cards[:us], @options) + assert_success authorization + assert authorization.emv_authorization, 'Authorization should contain emv_authorization containing the EMV ARPC' + refute authorization.params['captured'] + + assert void = @gateway.void(authorization.authorization) + assert_success void + end + + def test_authorization_and_void_with_emv_credit_card_in_uk + assert authorization = @gateway.authorize(@amount, @emv_credit_cards[:uk], @options) + assert_success authorization + assert authorization.emv_authorization, 'Authorization should contain emv_authorization containing the EMV ARPC' + refute authorization.params['captured'] + + assert void = @gateway.void(authorization.authorization) + assert_success void + end + + def test_purchase_and_void_with_emv_contactless_credit_card + emv_credit_card = @emv_credit_cards[:contactless] + emv_credit_card.read_method = 'contactless' + assert purchase = @gateway.purchase(@amount, emv_credit_card, @options) + assert_success purchase + assert purchase.emv_authorization, 'Authorization should contain emv_authorization containing the EMV ARPC' + assert purchase.params['captured'] + assert purchase.params['paid'] + + assert void = @gateway.void(purchase.authorization) + assert_success void + end + + def test_authorization_emv_credit_card_in_us_with_metadata + assert authorization = @gateway.authorize(@amount, @emv_credit_cards[:us], @options.merge({:metadata => {:this_is_a_random_key_name => 'with a random value', :i_made_up_this_key_too => 'canyoutell'}, :order_id => '42', :email => 'foo@wonderfullyfakedomain.com'})) + assert_success authorization + end +end diff --git a/test/remote/gateways/remote_stripe_payment_intents_test.rb b/test/remote/gateways/remote_stripe_payment_intents_test.rb new file mode 100644 index 00000000000..a8928f5cf3e --- /dev/null +++ b/test/remote/gateways/remote_stripe_payment_intents_test.rb @@ -0,0 +1,439 @@ +require 'test_helper' + +class RemoteStripeIntentsTest < Test::Unit::TestCase + def setup + @gateway = StripePaymentIntentsGateway.new(fixtures(:stripe)) + @customer = fixtures(:stripe)[:customer_id] + @amount = 2000 + @three_ds_payment_method = 'pm_card_threeDSecure2Required' + @visa_payment_method = 'pm_card_visa' + @declined_payment_method = 'pm_card_chargeDeclined' + @three_ds_moto_enabled = 'pm_card_authenticationRequiredOnSetup' + @three_ds_authentication_required = 'pm_card_authenticationRequired' + @three_ds_credit_card = credit_card('4000000000003220', + verification_value: '737', + month: 10, + year: 2020 + ) + @visa_card = credit_card('4242424242424242', + verification_value: '737', + month: 10, + year: 2020 + ) + @destination_account = fixtures(:stripe_destination)[:stripe_user_id] + end + + def test_authorization_and_void + options = { + currency: 'GBP', + customer: @customer, + } + assert authorization = @gateway.authorize(@amount, @visa_payment_method, options) + + assert_equal 'requires_capture', authorization.params['status'] + refute authorization.params.dig('charges', 'data')[0]['captured'] + + assert void = @gateway.void(authorization.authorization) + assert_success void + end + + def test_successful_purchase + options = { + currency: 'GBP', + customer: @customer, + } + assert purchase = @gateway.purchase(@amount, @visa_payment_method, options) + + assert_equal 'succeeded', purchase.params['status'] + assert purchase.params.dig('charges', 'data')[0]['captured'] + end + + def test_unsuccessful_purchase + options = { + currency: 'GBP', + customer: @customer, + } + assert purchase = @gateway.purchase(@amount, @declined_payment_method, options) + + assert_equal 'Your card was declined.', purchase.message + refute purchase.params.dig('error', 'payment_intent', 'charges', 'data')[0]['captured'] + end + + def test_create_payment_intent_manual_capture_method + options = { + currency: 'USD', + capture_method: 'manual' + } + + assert response = @gateway.create_intent(@amount, nil, options) + + assert_success response + assert_equal 'payment_intent', response.params['object'] + assert_equal 'manual', response.params['capture_method'] + end + + def test_create_payment_intent_manual_confimation_method + options = { + currency: 'USD', + description: 'ActiveMerchant Test Purchase', + confirmation_method: 'manual' + } + + assert response = @gateway.create_intent(@amount, nil, options) + + assert_success response + assert_equal 'payment_intent', response.params['object'] + assert_equal 'manual', response.params['confirmation_method'] + end + + def test_create_payment_intent_with_customer + options = { + currency: 'USD', + customer: @customer || 'set customer in fixtures' + } + + assert response = @gateway.create_intent(@amount, nil, options) + + assert_success response + assert_equal 'payment_intent', response.params['object'] + assert_equal @customer, response.params['customer'] + end + + def test_create_payment_intent_with_credit_card + options = { + currency: 'USD', + customer: @customer, + } + + assert response = @gateway.create_intent(@amount, @three_ds_credit_card, options) + + assert_success response + assert_equal 'payment_intent', response.params['object'] + end + + def test_create_payment_intent_with_return_url + options = { + currency: 'USD', + customer: @customer, + confirm: true, + return_url: 'https://www.example.com' + } + + assert response = @gateway.create_intent(@amount, @three_ds_credit_card, options) + + assert_success response + assert_equal 'https://www.example.com', response.params['next_action']['redirect_to_url']['return_url'] + end + + def test_create_payment_intent_with_metadata + options = { + currency: 'USD', + customer: @customer, + description: 'ActiveMerchant Test Purchase', + receipt_email: 'test@example.com', + statement_descriptor: 'Statement Descriptor', + metadata: { key_1: 'value_1', key_2: 'value_2' } + } + + assert response = @gateway.create_intent(@amount, nil, options) + + assert_success response + assert_equal 'value_1', response.params['metadata']['key_1'] + assert_equal 'ActiveMerchant Test Purchase', response.params['description'] + assert_equal 'test@example.com', response.params['receipt_email'] + assert_equal 'Statement Descriptor', response.params['statement_descriptor'] + end + + def test_create_payment_intent_that_saves_payment_method + options = { + currency: 'USD', + customer: @customer, + save_payment_method: true + } + + assert response = @gateway.create_intent(@amount, @three_ds_credit_card, options) + assert_success response + + assert response = @gateway.create_intent(@amount, nil, options) + assert_failure response + assert_equal 'A payment method must be provided or already '\ + 'attached to the PaymentIntent when `save_payment_method=true`.', response.message + + options.delete(:customer) + assert response = @gateway.create_intent(@amount, @three_ds_credit_card, options) + assert_failure response + assert_equal 'A valid `customer` must be provided when `save_payment_method=true`.', response.message + end + + def test_create_payment_intent_with_setup_future_usage + options = { + currency: 'USD', + customer: @customer, + setup_future_usage: 'on_session' + } + + assert response = @gateway.create_intent(@amount, @three_ds_credit_card, options) + assert_success response + assert_equal 'on_session', response.params['setup_future_usage'] + end + + def test_3ds_unauthenticated_authorize_with_off_session + options = { + currency: 'USD', + customer: @customer, + off_session: true, + } + + assert response = @gateway.authorize(@amount, @three_ds_credit_card, options) + assert_failure response + end + + def test_create_payment_intent_with_shipping_address + options = { + currency: 'USD', + customer: @customer, + shipping: { + address: { + line1: '1 Test Ln', + city: 'Durham' + }, + name: 'John Doe', + tracking_number: '123456789' + } + } + + assert response = @gateway.create_intent(@amount, nil, options) + assert_success response + assert response.params['shipping']['address'] + assert_equal 'John Doe', response.params['shipping']['name'] + end + + def test_create_payment_intent_with_billing_address + options = { + currency: 'USD', + customer: @customer, + billing_address: address, + confirm: true + } + + assert response = @gateway.create_intent(@amount, @visa_card, options) + assert_success response + assert billing = response.params.dig('charges', 'data')[0].dig('billing_details', 'address') + assert_equal 'Ottawa', billing['city'] + end + + def test_create_payment_intent_with_connected_account + options = { + currency: 'USD', + customer: @customer, + application_fee: 100, + transfer_destination: @destination_account + } + + assert response = @gateway.create_intent(@amount, nil, options) + + assert_success response + assert_equal 100, response.params['application_fee_amount'] + assert_equal @destination_account, response.params.dig('transfer_data', 'destination') + end + + def test_create_a_payment_intent_and_confirm + options = { + currency: 'GBP', + customer: @customer, + return_url: 'https://www.example.com', + confirmation_method: 'manual', + capture_method: 'manual', + } + assert create_response = @gateway.create_intent(@amount, @three_ds_payment_method, options) + assert_equal 'requires_confirmation', create_response.params['status'] + intent_id = create_response.params['id'] + + assert get_response = @gateway.show_intent(intent_id, options) + assert_equal 'requires_confirmation', get_response.params['status'] + + assert confirm_response = @gateway.confirm_intent(intent_id, nil, return_url: 'https://example.com/return-to-me') + assert_equal 'redirect_to_url', confirm_response.params.dig('next_action', 'type') + end + + def test_create_a_payment_intent_and_manually_capture + options = { + currency: 'GBP', + customer: @customer, + confirmation_method: 'manual', + capture_method: 'manual', + confirm: true + } + assert create_response = @gateway.create_intent(@amount, @visa_payment_method, options) + intent_id = create_response.params['id'] + assert_equal 'requires_capture', create_response.params['status'] + + assert capture_response = @gateway.capture(@amount, intent_id, options) + assert_equal 'succeeded', capture_response.params['status'] + assert_equal 'Payment complete.', capture_response.params.dig('charges', 'data')[0].dig('outcome', 'seller_message') + end + + def test_create_a_payment_intent_and_automatically_capture + options = { + currency: 'GBP', + customer: @customer, + confirmation_method: 'manual', + confirm: true + } + assert create_response = @gateway.create_intent(@amount, @visa_payment_method, options) + assert_nil create_response.params['next_action'] + assert_equal 'succeeded', create_response.params['status'] + assert_equal 'Payment complete.', create_response.params.dig('charges', 'data')[0].dig('outcome', 'seller_message') + end + + def test_failed_capture_after_creation + options = { + currency: 'GBP', + customer: @customer, + confirmation_method: 'manual', + confirm: true + } + assert create_response = @gateway.create_intent(@amount, 'pm_card_chargeDeclined', options) + assert_equal 'requires_payment_method', create_response.params.dig('error', 'payment_intent', 'status') + assert_equal false, create_response.params.dig('error', 'payment_intent', 'charges', 'data')[0].dig('captured') + end + + def test_create_a_payment_intent_and_update + update_amount = 2050 + options = { + currency: 'GBP', + customer: @customer, + confirmation_method: 'manual', + capture_method: 'manual', + } + assert create_response = @gateway.create_intent(@amount, @visa_payment_method, options) + intent_id = create_response.params['id'] + assert_equal @amount, create_response.params['amount'] + + assert update_response = @gateway.update_intent(update_amount, intent_id, nil, options.merge(payment_method_types: 'card')) + assert_equal update_amount, update_response.params['amount'] + assert_equal 'requires_confirmation', update_response.params['status'] + end + + def test_create_a_payment_intent_and_void + options = { + currency: 'GBP', + customer: @customer, + confirmation_method: 'manual', + capture_method: 'manual', + confirm: true + } + assert create_response = @gateway.create_intent(@amount, @visa_payment_method, options) + intent_id = create_response.params['id'] + + assert cancel_response = @gateway.void(intent_id, cancellation_reason: 'requested_by_customer') + assert_equal @amount, cancel_response.params.dig('charges', 'data')[0].dig('amount_refunded') + assert_equal 'canceled', cancel_response.params['status'] + assert_equal 'requested_by_customer', cancel_response.params['cancellation_reason'] + end + + def test_failed_void_after_capture + options = { + currency: 'GBP', + customer: @customer, + confirmation_method: 'manual', + confirm: true + } + assert create_response = @gateway.create_intent(@amount, @visa_payment_method, options) + assert_equal 'succeeded', create_response.params['status'] + intent_id = create_response.params['id'] + + assert cancel_response = @gateway.void(intent_id, cancellation_reason: 'requested_by_customer') + assert_equal 'You cannot cancel this PaymentIntent because ' \ + 'it has a status of succeeded. Only a PaymentIntent with ' \ + 'one of the following statuses may be canceled: ' \ + 'requires_payment_method, requires_capture, requires_confirmation, requires_action.', cancel_response.message + end + + def test_refund_a_payment_intent + options = { + currency: 'GBP', + customer: @customer, + confirmation_method: 'manual', + capture_method: 'manual', + confirm: true + } + assert create_response = @gateway.create_intent(@amount, @visa_payment_method, options) + intent_id = create_response.params['id'] + + assert @gateway.capture(@amount, intent_id, options) + + assert refund = @gateway.refund(@amount - 20, intent_id) + assert_equal @amount - 20, refund.params['charge']['amount_refunded'] + assert_equal true, refund.params['charge']['captured'] + refund_id = refund.params['id'] + assert_equal refund.authorization, refund_id + end + + def test_successful_store_purchase_and_unstore + options = { + currency: 'GBP', + } + assert store = @gateway.store(@visa_card, options) + assert store.params['customer'].start_with?('cus_') + + assert purchase = @gateway.purchase(@amount, store.authorization, options) + assert 'succeeded', purchase.params['status'] + + assert unstore = @gateway.unstore(store.authorization) + assert_nil unstore.params['customer'] + end + + def test_moto_enabled_card_requires_action_when_not_marked + options = { + currency: 'GBP', + confirm: true, + } + assert purchase = @gateway.purchase(@amount, @three_ds_moto_enabled, options) + + assert_equal 'requires_action', purchase.params['status'] + end + + def test_moto_enabled_card_succeeds_when_marked + options = { + currency: 'GBP', + confirm: true, + moto: true, + } + assert purchase = @gateway.purchase(@amount, @three_ds_moto_enabled, options) + + assert_equal 'succeeded', purchase.params['status'] + assert purchase.params.dig('charges', 'data')[0]['captured'] + end + + def test_certain_cards_require_action_even_when_marked_as_moto + options = { + currency: 'GBP', + confirm: true, + moto: true, + } + assert purchase = @gateway.purchase(@amount, @three_ds_authentication_required, options) + + assert_failure purchase + assert_equal 'Your card was declined. This transaction requires authentication.', purchase.message + end + + def test_transcript_scrubbing + options = { + currency: 'GBP', + customer: @customer, + confirmation_method: 'manual', + capture_method: 'manual', + return_url: 'https://www.example.com/return', + confirm: true + } + transcript = capture_transcript(@gateway) do + @gateway.create_intent(@amount, @three_ds_credit_card, options) + end + transcript = @gateway.scrub(transcript) + + assert_scrubbed(@three_ds_credit_card.number, transcript) + assert_scrubbed(@three_ds_credit_card.verification_value, transcript) + assert_scrubbed(@gateway.options[:login], transcript) + end +end diff --git a/test/remote/gateways/remote_stripe_test.rb b/test/remote/gateways/remote_stripe_test.rb index a2ca420cab5..04a5b144138 100644 --- a/test/remote/gateways/remote_stripe_test.rb +++ b/test/remote/gateways/remote_stripe_test.rb @@ -1,51 +1,197 @@ require 'test_helper' class RemoteStripeTest < Test::Unit::TestCase - def setup @gateway = StripeGateway.new(fixtures(:stripe)) @amount = 100 @credit_card = credit_card('4242424242424242') - @declined_card = credit_card('4000') + @declined_card = credit_card('4000000000000002') @new_credit_card = credit_card('5105105105105100') + @debit_card = credit_card('4000056655665556') + + @check = check({ + bank_name: 'STRIPE TEST BANK', + account_number: '000123456789', + routing_number: '110000000', + }) + @verified_bank_account = fixtures(:stripe_verified_bank_account) @options = { - :currency => 'CAD', + :currency => 'USD', :description => 'ActiveMerchant Test Purchase', - :email => 'wow@example.com', - :currency => 'CAD' + :email => 'wow@example.com' } end + def test_transcript_scrubbing + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @credit_card, @options) + end + transcript = @gateway.scrub(transcript) + + assert_scrubbed(@credit_card.number, transcript) + assert_scrubbed(@credit_card.verification_value, transcript) + assert_scrubbed(@gateway.options[:login], transcript) + end + def test_successful_purchase assert response = @gateway.purchase(@amount, @credit_card, @options) assert_success response - assert_equal "charge", response.params["object"] - assert response.params["paid"] + assert_equal 'charge', response.params['object'] + assert_equal response.authorization, response.params['id'] + assert response.params['paid'] + assert_equal 'ActiveMerchant Test Purchase', response.params['description'] + assert_equal 'wow@example.com', response.params['metadata']['email'] + end + + def test_successful_purchase_with_blank_referer + options = @options.merge({referrer: ''}) + assert response = @gateway.purchase(@amount, @credit_card, options) + assert_success response + assert_equal 'charge', response.params['object'] + assert_equal response.authorization, response.params['id'] + assert response.params['paid'] + assert_equal 'ActiveMerchant Test Purchase', response.params['description'] + assert_equal 'wow@example.com', response.params['metadata']['email'] + end + + def test_successful_purchase_with_recurring_flag + custom_options = @options.merge(:eci => 'recurring') + assert response = @gateway.purchase(@amount, @credit_card, custom_options) + assert_success response + assert_equal 'charge', response.params['object'] + assert response.params['paid'] + assert_equal 'ActiveMerchant Test Purchase', response.params['description'] + assert_equal 'wow@example.com', response.params['metadata']['email'] + end + + def test_successful_purchase_with_destination + destination = fixtures(:stripe_destination)[:stripe_user_id] + custom_options = @options.merge(:destination => destination) + assert response = @gateway.purchase(@amount, @credit_card, custom_options) + assert_success response + assert_equal 'charge', response.params['object'] + assert_equal destination, response.params['destination'] + assert response.params['paid'] + assert_equal 'ActiveMerchant Test Purchase', response.params['description'] + assert_equal 'wow@example.com', response.params['metadata']['email'] end - def test_purchase_description - assert response = @gateway.purchase(@amount, @credit_card, { :currency => 'CAD', :description => "TheDescription", :email => "email@example.com" }) - assert_equal "TheDescription", response.params["description"], "Use the description if it's specified." + def test_successful_purchase_with_destination_and_amount + destination = fixtures(:stripe_destination)[:stripe_user_id] + custom_options = @options.merge(:destination => destination, :destination_amount => @amount - 20) + assert response = @gateway.purchase(@amount, @credit_card, custom_options) + assert_success response + assert_equal 'charge', response.params['object'] + assert_equal destination, response.params['destination'] + assert response.params['paid'] + assert_equal 'ActiveMerchant Test Purchase', response.params['description'] + assert_equal 'wow@example.com', response.params['metadata']['email'] + end - assert response = @gateway.purchase(@amount, @credit_card, { :currency => 'CAD', :email => "email@example.com" }) - assert_equal "email@example.com", response.params["description"], "Use the email if no description is specified." + def test_successful_purchase_with_level3_data + @options[:merchant_reference] = 123 + @options[:customer_reference] = 456 + @options[:shipping_address_zip] = 98765 + @options[:shipping_from_zip] = 54321 + @options[:shipping_amount] = 10 + @options[:line_items] = [ + { + 'product_code' => 1234, + 'product_description' => 'An item', + 'unit_cost' => 15, + 'quantity' => 2, + 'tax_amount' => 0 + }, + { + 'product_code' => 999, + 'product_description' => 'A totes different item', + 'tax_amount' => 10, + 'unit_cost' => 50, + 'quantity' => 1, + } + ] - assert response = @gateway.purchase(@amount, @credit_card, { :currency => 'CAD' }) - assert_nil response.params["description"], "No description or email specified." + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal 'charge', response.params['object'] + assert_equal response.authorization, response.params['id'] + assert response.params['paid'] + assert_equal 'ActiveMerchant Test Purchase', response.params['description'] + assert_equal 'wow@example.com', response.params['metadata']['email'] end def test_unsuccessful_purchase assert response = @gateway.purchase(@amount, @declined_card, @options) assert_failure response - assert_match /card number.* invalid/, response.message + assert_match %r{Your card was declined}, response.message + assert_match Gateway::STANDARD_ERROR_CODE[:card_declined], response.error_code + assert_match(/ch_[a-zA-Z\d]+/, response.authorization) + end + + def test_unsuccessful_purchase_with_destination_and_amount + destination = fixtures(:stripe_destination)[:stripe_user_id] + custom_options = @options.merge(:destination => destination, :destination_amount => @amount + 20) + assert response = @gateway.purchase(@amount, @credit_card, custom_options) + assert_failure response + assert_match %r{must be less than or equal to the charge amount}, response.message + end + + def test_successful_echeck_purchase_with_verified_account + customer_id = @verified_bank_account[:customer_id] + bank_account_id = @verified_bank_account[:bank_account_id] + + payment = [customer_id, bank_account_id].join('|') + + response = @gateway.purchase(@amount, payment, @options) + assert_success response + assert response.test? + assert_equal 'Transaction approved', response.message + end + + def test_unsuccessful_direct_bank_account_purchase + response = @gateway.purchase(@amount, @check, @options) + assert_failure response + assert_equal 'Direct bank account transactions are not supported. Bank accounts must be stored and verified before use.', response.message end def test_authorization_and_capture assert authorization = @gateway.authorize(@amount, @credit_card, @options) assert_success authorization - assert !authorization.params["captured"] + refute authorization.params['captured'] + assert_equal 'ActiveMerchant Test Purchase', authorization.params['description'] + assert_equal 'wow@example.com', authorization.params['metadata']['email'] + + assert capture = @gateway.capture(@amount, authorization.authorization) + assert_success capture + end + + def test_authorization_and_capture_with_destination + destination = fixtures(:stripe_destination)[:stripe_user_id] + custom_options = @options.merge(:destination => destination) + + assert authorization = @gateway.authorize(@amount, @credit_card, custom_options) + assert_success authorization + refute authorization.params['captured'] + assert_equal destination, authorization.params['destination'] + assert_equal 'ActiveMerchant Test Purchase', authorization.params['description'] + assert_equal 'wow@example.com', authorization.params['metadata']['email'] + + assert capture = @gateway.capture(@amount, authorization.authorization) + assert_success capture + end + + def test_authorization_and_capture_with_destination_and_amount + destination = fixtures(:stripe_destination)[:stripe_user_id] + custom_options = @options.merge(:destination => destination, :destination_amount => @amount - 20) + + assert authorization = @gateway.authorize(@amount, @credit_card, custom_options) + assert_success authorization + refute authorization.params['captured'] + assert_equal destination, authorization.params['destination'] + assert_equal 'ActiveMerchant Test Purchase', authorization.params['description'] + assert_equal 'wow@example.com', authorization.params['metadata']['email'] assert capture = @gateway.capture(@amount, authorization.authorization) assert_success capture @@ -54,7 +200,7 @@ def test_authorization_and_capture def test_authorization_and_void assert authorization = @gateway.authorize(@amount, @credit_card, @options) assert_success authorization - assert !authorization.params["captured"] + refute authorization.params['captured'] assert void = @gateway.void(authorization.authorization) assert_success void @@ -65,108 +211,459 @@ def test_successful_void assert_success response assert response.authorization assert void = @gateway.void(response.authorization) + assert void.test? + assert_success void + end + + def test_successful_void_with_metadata + assert response = @gateway.authorize(@amount, @credit_card, @options) + assert_success response + assert response.authorization + + assert void = @gateway.void(response.authorization, metadata: { test_metadata: 123 }) + assert void.test? + assert_success void + assert_equal '123', void.params['metadata']['test_metadata'] + end + + def test_successful_void_with_reason + assert response = @gateway.authorize(@amount, @credit_card, @options) + assert_success response + assert response.authorization + + assert void = @gateway.void(response.authorization, reason: 'fraudulent') + assert void.test? assert_success void + assert_equal 'fraudulent', void.params['reason'] end def test_unsuccessful_void - assert void = @gateway.void("active_merchant_fake_charge") + assert void = @gateway.void('active_merchant_fake_charge') assert_failure void - assert_match /active_merchant_fake_charge/, void.message + assert_match %r{active_merchant_fake_charge}, void.message end def test_successful_refund assert response = @gateway.purchase(@amount, @credit_card, @options) assert_success response assert response.authorization - assert void = @gateway.refund(@amount - 20, response.authorization) - assert_success void + assert refund = @gateway.refund(@amount - 20, response.authorization) + assert refund.test? + refund_id = refund.params['id'] + assert_equal refund.authorization, refund_id + assert_success refund + end + + def test_successful_refund_with_reason + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert response.authorization + + assert refund = @gateway.refund(@amount - 20, response.authorization, reason: 'fraudulent') + assert refund.test? + refund_id = refund.params['id'] + assert_equal refund.authorization, refund_id + assert_success refund + assert_equal 'fraudulent', refund.params['reason'] + end + + def test_successful_refund_on_verified_bank_account + customer_id = @verified_bank_account[:customer_id] + bank_account_id = @verified_bank_account[:bank_account_id] + payment = [customer_id, bank_account_id].join('|') + + purchase = @gateway.purchase(@amount, payment, @options) + assert_success purchase + + refund = @gateway.refund(@amount, purchase.authorization) + assert_success refund + assert refund.test? + refund_id = refund.params['id'] + assert_equal refund.authorization, refund_id + end + + def test_refund_with_reverse_transfer + destination = fixtures(:stripe_destination)[:stripe_user_id] + assert response = @gateway.purchase(@amount, @credit_card, @options.merge(destination: destination)) + assert_success response + + assert refund = @gateway.refund(@amount - 20, response.authorization, reverse_transfer: true) + assert_success refund + assert_equal 'Transaction approved', refund.message end def test_unsuccessful_refund - assert refund = @gateway.refund(@amount, "active_merchant_fake_charge") + assert refund = @gateway.refund(@amount, 'active_merchant_fake_charge') assert_failure refund - assert_match /active_merchant_fake_charge/, refund.message + assert_match %r{active_merchant_fake_charge}, refund.message + end + + def test_successful_verify + assert response = @gateway.verify(@credit_card, @options) + assert_success response + + assert_equal 'Transaction approved', response.message + assert_equal 'wow@example.com', response.params['metadata']['email'] + assert_equal 'charge', response.params['object'] + assert_success response.responses.last, 'The void should succeed' + assert_equal 'refund', response.responses.last.params['object'] + end + + def test_successful_verify_cad + assert response = @gateway.verify(@credit_card, @options.merge(currency: 'cad')) + assert_success response + assert_equal 100, response.params['amount'] + assert_equal 'cad', response.params['currency'] + end + + def test_successful_verify_gbp + assert response = @gateway.verify(@credit_card, @options.merge(currency: 'gbp')) + assert_success response + assert_equal 60, response.params['amount'] + assert_equal 'gbp', response.params['currency'] + end + + def test_successful_verify_eur + assert response = @gateway.verify(@credit_card, @options.merge(currency: 'eur')) + assert_success response + assert_equal 100, response.params['amount'] + assert_equal 'eur', response.params['currency'] + end + + def test_successful_verify_dkk + assert response = @gateway.verify(@credit_card, @options.merge(currency: 'dkk')) + assert_success response + assert_equal 500, response.params['amount'] + assert_equal 'dkk', response.params['currency'] + end + + def test_successful_verify_nok + assert response = @gateway.verify(@credit_card, @options.merge(currency: 'nok')) + assert_success response + assert_equal 600, response.params['amount'] + assert_equal 'nok', response.params['currency'] + end + + def test_successful_verify_sek + assert response = @gateway.verify(@credit_card, @options.merge(currency: 'sek')) + assert_success response + assert_equal 600, response.params['amount'] + assert_equal 'sek', response.params['currency'] + end + + def test_successful_verify_chf + assert response = @gateway.verify(@credit_card, @options.merge(currency: 'chf')) + assert_success response + assert_equal 100, response.params['amount'] + assert_equal 'chf', response.params['currency'] + end + + def test_successful_verify_aud + assert response = @gateway.verify(@credit_card, @options.merge(currency: 'aud')) + assert_success response + assert_equal 100, response.params['amount'] + assert_equal 'aud', response.params['currency'] + end + + def test_successful_verify_jpy + assert response = @gateway.verify(@credit_card, @options.merge(currency: 'jpy')) + assert_success response + assert_equal 100, response.params['amount'] + assert_equal 'jpy', response.params['currency'] + end + + def test_successful_verify_mxn + assert response = @gateway.verify(@credit_card, @options.merge(currency: 'mxn')) + assert_success response + assert_equal 2000, response.params['amount'] + assert_equal 'mxn', response.params['currency'] + end + + def test_successful_verify_sgd + assert response = @gateway.verify(@credit_card, @options.merge(currency: 'sgd')) + assert_success response + assert_equal 100, response.params['amount'] + assert_equal 'sgd', response.params['currency'] + end + + def test_successful_verify_hkd + assert response = @gateway.verify(@credit_card, @options.merge(currency: 'hkd')) + assert_success response + assert_equal 800, response.params['amount'] + assert_equal 'hkd', response.params['currency'] + end + + def test_unsuccessful_verify + assert response = @gateway.verify(@declined_card, @options) + assert_failure response + assert_match %r{Your card was declined}, response.message end def test_successful_store - assert response = @gateway.store(@credit_card, {:currency => 'CAD', :description => "Active Merchant Test Customer", :email => "email@example.com"}) + assert response = @gateway.store(@credit_card, description: 'TheDescription', email: 'email@example.com') assert_success response - assert_equal "customer", response.params["object"] - assert_equal "Active Merchant Test Customer", response.params["description"] - assert_equal "email@example.com", response.params["email"] - assert_equal @credit_card.last_digits, response.params["active_card"]["last4"] + assert_equal 'customer', response.params['object'] + assert_equal 'TheDescription', response.params['description'] + assert_equal 'email@example.com', response.params['email'] + first_card = response.params['sources']['data'].first + assert_equal response.params['default_source'], first_card['id'] + assert_equal @credit_card.last_digits, first_card['last4'] end - def test_successful_update - creation = @gateway.store(@credit_card, {:description => "Active Merchant Update Customer"}) - assert response = @gateway.update(creation.params['id'], @new_credit_card) + def test_successful_store_with_validate_false + assert response = @gateway.store(@credit_card, validate: false) assert_success response - assert_equal "Active Merchant Update Customer", response.params["description"] - assert_equal @new_credit_card.last_digits, response.params["active_card"]["last4"] + assert_equal 'customer', response.params['object'] end - def test_successful_unstore - creation = @gateway.store(@credit_card, {:description => "Active Merchant Unstore Customer"}) - assert response = @gateway.unstore(creation.params['id']) + def test_successful_store_with_existing_customer + assert response = @gateway.store(@credit_card) + assert_success response + + assert response = @gateway.store(@new_credit_card, customer: response.params['id'], email: 'email@example.com', description: 'TheDesc') assert_success response - assert_equal true, response.params["deleted"] + assert_equal 2, response.responses.size + + card_response = response.responses[0] + assert_equal 'card', card_response.params['object'] + assert_equal @new_credit_card.last_digits, card_response.params['last4'] + + customer_response = response.responses[1] + assert_equal 'customer', customer_response.params['object'] + assert_equal 'TheDesc', customer_response.params['description'] + assert_equal 'email@example.com', customer_response.params['email'] + assert_equal 2, customer_response.params['sources']['total_count'] end - def test_successful_recurring - assert response = @gateway.store(@credit_card, {:description => "Active Merchant Test Customer", :email => "email@example.com"}) + def test_successful_store_with_existing_account + account = fixtures(:stripe_destination)[:stripe_user_id] + + assert response = @gateway.store(@debit_card, account: account) assert_success response - assert recharge_options = @options.merge(:customer => response.params["id"]) - assert response = @gateway.purchase(@amount, nil, recharge_options) + assert_equal 'card', response.params['object'] + end + + def test_successful_purchase_using_stored_card + assert store = @gateway.store(@credit_card) + assert_success store + + assert response = @gateway.purchase(@amount, store.authorization) assert_success response - assert_equal "charge", response.params["object"] - assert response.params["paid"] + assert_equal 'Transaction approved', response.message + + assert response.params['paid'] + assert_equal '4242', response.params['source']['last4'] + end + + def test_successful_purchase_using_stored_card_on_existing_customer + assert first_store_response = @gateway.store(@credit_card) + assert_success first_store_response + + assert second_store_response = @gateway.store(@new_credit_card, customer: first_store_response.params['id']) + assert_success second_store_response + + assert response = @gateway.purchase(@amount, second_store_response.authorization) + assert_success response + assert_equal '5100', response.params['source']['last4'] + end + + def test_successful_purchase_using_stored_card_and_deprecated_api + assert store = @gateway.store(@credit_card) + assert_success store + + recharge_options = @options.merge(:customer => store.params['id']) + assert_deprecation_warning do + response = @gateway.purchase(@amount, nil, recharge_options) + assert_success response + assert_equal '4242', response.params['source']['last4'] + end + end + + def test_successful_unstore + creation = @gateway.store(@credit_card, {:description => 'Active Merchant Unstore Customer'}) + card_id = creation.params['sources']['data'].first['id'] + + assert response = @gateway.unstore(creation.authorization) + assert_success response + assert_equal card_id, response.params['id'] + assert_equal true, response.params['deleted'] + assert_equal 'Transaction approved', response.message + end + + def test_successful_unstore_using_deprecated_api + creation = @gateway.store(@credit_card, {:description => 'Active Merchant Unstore Customer'}) + card_id = creation.params['sources']['data'].first['id'] + customer_id = creation.params['id'] + + assert_deprecation_warning do + response = @gateway.unstore(customer_id, card_id) + assert_success response + assert_equal true, response.params['deleted'] + end + end + + def test_successful_store_of_bank_account + response = @gateway.store(@check, @options) + assert_success response + customer_id, bank_account_id = response.authorization.split('|') + assert_match(/^cus_/, customer_id) + assert_match(/^ba_/, bank_account_id) + end + + def test_unsuccessful_purchase_from_stored_but_unverified_bank_account + store = @gateway.store(@check) + assert_success store + + purchase = @gateway.purchase(@amount, store.authorization, @options) + assert_failure purchase + assert_match "The customer's bank account must be verified", purchase.message + end + + def test_successful_purchase_from_stored_and_verified_bank_account + store = @gateway.store(@check) + assert_success store + + # verify the account using special test amounts from Stripe + # https://stripe.com/docs/guides/ach#manually-collecting-and-verifying-bank-accounts + customer_id, bank_account_id = store.authorization.split('|') + verify_url = "customers/#{customer_id}/sources/#{bank_account_id}/verify" + verify_response = @gateway.send(:api_request, :post, verify_url, { amounts: [32, 45] }) + assert_match 'verified', verify_response['status'] + + purchase = @gateway.purchase(@amount, store.authorization, @options) + assert_success purchase end def test_invalid_login gateway = StripeGateway.new(:login => 'active_merchant_test') assert response = gateway.purchase(@amount, @credit_card, @options) assert_failure response - assert_match "Invalid API Key provided", response.message - end - - def test_application_fee_for_stripe_connect - assert response = @gateway.purchase(@amount, @credit_card, @options.merge(:application_fee => 12 )) - assert response.params['fee_details'], 'This test will only work if your gateway login is a Stripe Connect access_token.' - assert response.params['fee_details'].any? do |fee| - (fee['type'] == 'application_fee') && (fee['amount'] == 12) - end + assert_match 'Invalid API Key provided', response.message end + # These "track data present" tests fail with invalid expiration dates. The + # test track data probably needs to be updated. def test_card_present_purchase - @credit_card.track_data = '%B378282246310005^LONGSON/LONGBOB^1705101130504392?' + @credit_card.track_data = '%B378282246310005^LONGSON/LONGBOB^2205101130504392?' assert response = @gateway.purchase(@amount, @credit_card, @options) assert_success response - assert_equal "charge", response.params["object"] - assert response.params["paid"] + assert_equal 'charge', response.params['object'] + assert response.params['paid'] end def test_card_present_authorize_and_capture - @credit_card.track_data = '%B378282246310005^LONGSON/LONGBOB^1705101130504392?' + @credit_card.track_data = '%B378282246310005^LONGSON/LONGBOB^2205101130504392?' assert authorization = @gateway.authorize(@amount, @credit_card, @options) assert_success authorization - assert !authorization.params["captured"] + refute authorization.params['captured'] assert capture = @gateway.capture(@amount, authorization.authorization) assert_success capture end - def test_successful_refund_with_application_fee - assert response = @gateway.purchase(@amount, @credit_card, @options.merge(:application_fee => 12)) - assert response.params['fee_details'], 'This test will only work if your gateway login is a Stripe Connect access_token.' - assert refund = @gateway.refund(@amount, response.authorization, :refund_application_fee => true) - assert_success refund - assert_equal 12, refund.params["fee_details"].first["amount_refunded"] + def test_creditcard_purchase_with_customer + assert response = @gateway.purchase(@amount, @credit_card, @options.merge(:customer => '1234')) + assert_success response + assert_equal 'charge', response.params['object'] + assert response.params['paid'] end - def test_refund_partial_application_fee - assert response = @gateway.purchase(@amount, @credit_card, @options.merge(:application_fee => 12)) - assert response.params['fee_details'], 'This test will only work if your gateway login is a Stripe Connect access_token.' - assert refund = @gateway.refund(@amount - 20, response.authorization, { :refund_fee_amount => 10 }) - assert_success refund + def test_expanding_objects + assert response = @gateway.purchase(@amount, @credit_card, @options.merge(:expand => 'balance_transaction')) + assert_success response + assert response.params['balance_transaction'].is_a?(Hash) + assert_equal 'balance_transaction', response.params['balance_transaction']['object'] + end + + def test_successful_update + creation = @gateway.store(@credit_card, {:description => 'Active Merchant Update Credit Card'}) + customer_id = creation.params['id'] + card_id = creation.params['sources']['data'].first['id'] + + assert response = @gateway.update(customer_id, card_id, { :name => 'John Doe', :address_line1 => '123 Main Street', :address_city => 'Pleasantville', :address_state => 'NY', :address_zip => '12345', :exp_year => Time.now.year + 2, :exp_month => 6 }) + assert_success response + assert_equal 'John Doe', response.params['name'] + assert_equal '123 Main Street', response.params['address_line1'] + assert_equal 'Pleasantville', response.params['address_city'] + assert_equal 'NY', response.params['address_state'] + assert_equal '12345', response.params['address_zip'] + assert_equal Time.now.year + 2, response.params['exp_year'] + assert_equal 6, response.params['exp_month'] + end + + def test_incorrect_number_for_purchase + card = credit_card('4242424242424241') + assert response = @gateway.purchase(@amount, card, @options) + assert_failure response + assert_match Gateway::STANDARD_ERROR_CODE[:incorrect_number], response.error_code + end + + def test_invalid_number_for_purchase + card = credit_card('-1') + assert response = @gateway.purchase(@amount, card, @options) + assert_failure response + assert_match Gateway::STANDARD_ERROR_CODE[:invalid_number], response.error_code + end + + def test_invalid_expiry_month_for_purchase + card = credit_card('4242424242424242', month: 16) + assert response = @gateway.purchase(@amount, card, @options) + assert_failure response + assert_match Gateway::STANDARD_ERROR_CODE[:invalid_expiry_date], response.error_code end + + def test_invalid_expiry_year_for_purchase + card = credit_card('4242424242424242', year: 'xx') + assert response = @gateway.purchase(@amount, card, @options) + assert_failure response + assert_match Gateway::STANDARD_ERROR_CODE[:invalid_expiry_date], response.error_code + end + + def test_expired_card_for_purchase + card = credit_card('4000000000000069') + assert response = @gateway.purchase(@amount, card, @options) + assert_failure response + assert_match Gateway::STANDARD_ERROR_CODE[:expired_card], response.error_code + end + + def test_invalid_cvc_for_purchase + card = credit_card('4242424242424242', verification_value: -1) + assert response = @gateway.purchase(@amount, card, @options) + assert_failure response + assert_match Gateway::STANDARD_ERROR_CODE[:invalid_cvc], response.error_code + end + + def test_incorrect_cvc_for_purchase + card = credit_card('4000000000000127') + assert response = @gateway.purchase(@amount, card, @options) + assert_failure response + assert_match Gateway::STANDARD_ERROR_CODE[:incorrect_cvc], response.error_code + end + + def test_processing_error + card = credit_card('4000000000000119') + assert response = @gateway.purchase(@amount, card, @options) + assert_failure response + assert_match Gateway::STANDARD_ERROR_CODE[:processing_error], response.error_code + end + + def test_statement_description + assert response = @gateway.purchase(@amount, @credit_card, statement_description: 'Eggcellent Description') + assert_success response + assert_equal 'Eggcellent Description', response.params['statement_descriptor'] + end + + def test_stripe_account_header + account = fixtures(:stripe_destination)[:stripe_user_id] + assert response = @gateway.purchase(@amount, @credit_card, stripe_account: account) + assert_success response + end + + def test_verify_credentials + assert @gateway.verify_credentials + + gateway = StripeGateway.new(login: 'an_unknown_api_key') + assert !gateway.verify_credentials + end + end diff --git a/test/remote/gateways/remote_swipe_checkout_test.rb b/test/remote/gateways/remote_swipe_checkout_test.rb new file mode 100644 index 00000000000..6299a506fd2 --- /dev/null +++ b/test/remote/gateways/remote_swipe_checkout_test.rb @@ -0,0 +1,69 @@ +require 'test_helper' + +class RemoteSwipeCheckoutTest < Test::Unit::TestCase + def setup + @gateway = SwipeCheckoutGateway.new(fixtures(:swipe_checkout)) + + @amount = 100 + @accepted_card = credit_card('1234123412341234') + @declined_card = credit_card('1111111111111111') + @invalid_card = credit_card('1000000000000000') + @empty_card = credit_card('') + + @options = { + order_id: '1', + billing_address: address, + description: 'Store Purchase' + } + end + + def test_successful_purchase + assert response = @gateway.purchase(@amount, @accepted_card, @options) + assert_success response + assert_equal 'Transaction approved', response.message + end + + def test_region_switching + assert response = @gateway.purchase(@amount, @accepted_card, @options.merge(:region => 'CA')) + assert_success response + assert_equal 'Transaction approved', response.message + end + + def test_unsuccessful_purchase + assert response = @gateway.purchase(@amount, @declined_card, @options) + assert_failure response + assert_equal 'Transaction declined', response.message + end + + def test_invalid_login + gateway = SwipeCheckoutGateway.new( + login: 'invalid', + api_key: 'invalid', + region: 'NZ' + ) + assert response = gateway.purchase(@amount, @accepted_card, @options) + assert_failure response + assert_equal 'Access Denied', response.message + end + + def test_invalid_card + # Note: Swipe Checkout transaction API returns declined if the card number + # is invalid, and "invalid card data" if the card number is empty + assert response = @gateway.purchase(@amount, @invalid_card, @options) + assert_failure response + assert_equal 'Transaction declined', response.message + assert_equal 200, response.params['response_code'] + end + + def test_empty_card + assert response = @gateway.purchase(@amount, @empty_card, @options) + assert_failure response + assert_equal 'Invalid card data', response.message + assert_equal 303, response.params['response_code'] + end + + def test_no_options + assert response = @gateway.purchase(@amount, @accepted_card, {}) + assert_success response + end +end diff --git a/test/remote/gateways/remote_telr_test.rb b/test/remote/gateways/remote_telr_test.rb new file mode 100644 index 00000000000..bf0477ac36f --- /dev/null +++ b/test/remote/gateways/remote_telr_test.rb @@ -0,0 +1,167 @@ +require 'test_helper' + +class RemoteTelrTest < Test::Unit::TestCase + def setup + @gateway = TelrGateway.new(fixtures(:telr)) + + @amount = 100 + @credit_card = credit_card('5105105105105100') + @declined_card = credit_card('5105105105105100', verification_value: '031') + + @options = { + order_id: generate_unique_id, + billing_address: address, + description: 'Test transaction', + email: 'email@address.com' + } + end + + def test_invalid_login + gateway = TelrGateway.new(merchant_id: '', api_key: '') + response = gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_equal 'Invalid request', response.message + end + + def test_successful_purchase + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal 'Succeeded', response.message + end + + def test_successful_purchase_sans_options + response = @gateway.purchase(@amount, @credit_card) + assert_success response + assert_equal 'Succeeded', response.message + end + + def test_failed_purchase + response = @gateway.purchase(@amount, @declined_card, @options) + assert_failure response + assert_equal 'Not authorised', response.message + assert_equal '31', response.error_code + end + + def test_successful_reference_purchase + assert ref_response = @gateway.purchase(@amount, @credit_card, @options) + assert_success ref_response + + response = @gateway.purchase(@amount, ref_response.authorization, @options) + assert_success response + assert_equal 'Succeeded', response.message + end + + def test_successful_authorize_and_capture + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + capture = @gateway.capture(@amount, auth.authorization) + assert_success capture + end + + def test_failed_authorize + response = @gateway.authorize(@amount, @declined_card, @options) + assert_failure response + assert_equal 'Not authorised', response.message + end + + def test_failed_capture + response = @gateway.capture(@amount, '') + assert_failure response + assert_equal 'Invalid transaction reference', response.message + assert_equal '22', response.error_code + end + + def test_successful_void + response = @gateway.authorize(@amount, @credit_card, @options) + assert_success response + void = @gateway.void(response.authorization) + assert_success void + assert_equal 'Succeeded', void.message + end + + def test_failed_void + response = @gateway.void('') + assert_failure response + assert_equal 'Transaction cost or currency not valid', response.message + assert_equal '05', response.error_code + end + + def test_successful_refund + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + + refund = @gateway.refund(@amount, response.authorization) + assert_success refund + end + + def test_partial_refund + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + + refund = @gateway.refund(50, response.authorization) + assert_success refund + end + + def test_failed_refund + response = @gateway.refund(@amount, '0') + assert_failure response + assert_equal 'Invalid transaction reference', response.message + assert_equal '22', response.error_code + end + + def test_excess_refund + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + + refund = @gateway.refund(200, response.authorization) + assert_failure refund + assert_equal 'Amount greater than available balance', refund.message + assert_equal '29', refund.error_code + end + + def test_successful_verify + response = @gateway.verify(@credit_card, @options) + assert_success response + assert_match %r{Succeeded}, response.message + end + + def test_failed_verify + response = @gateway.verify(@declined_card, @options) + assert_failure response + assert_equal 'Not authorised', response.message + assert_equal '31', response.error_code + end + + def test_verify_credentials + assert @gateway.verify_credentials + + gateway = TelrGateway.new(merchant_id: 'unknown', api_key: 'unknown') + assert !gateway.verify_credentials + gateway = TelrGateway.new(merchant_id: fixtures(:telr)[:merchant_id], api_key: 'unknown') + assert !gateway.verify_credentials + end + + def test_cvv_result + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal 'M', response.cvv_result['code'] + end + + def test_avs_result + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal 'I', response.avs_result['code'] + end + + def test_transcript_scrubbing + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @credit_card, @options) + end + clean_transcript = @gateway.scrub(transcript) + + assert_scrubbed(@credit_card.number, clean_transcript) + assert_scrubbed(@credit_card.verification_value.to_s, clean_transcript) + assert_scrubbed(@gateway.options[:api_key], clean_transcript) + end +end diff --git a/test/remote/gateways/remote_tns_test.rb b/test/remote/gateways/remote_tns_test.rb new file mode 100644 index 00000000000..cc1aa80efa1 --- /dev/null +++ b/test/remote/gateways/remote_tns_test.rb @@ -0,0 +1,133 @@ +require 'test_helper' + +class RemoteTnsTest < Test::Unit::TestCase + + def setup + TnsGateway.ssl_strict = false # Sandbox has an improperly installed cert + @gateway = TnsGateway.new(fixtures(:tns)) + + @amount = 100 + @credit_card = credit_card('5123456789012346') + @ap_credit_card = credit_card('5424180279791732', month: 05, year: 2017, verification_value: 222) + @declined_card = credit_card('4000300011112220') + + @options = { + order_id: generate_unique_id, + billing_address: address, + description: 'Store Purchase' + } + end + + def teardown + TnsGateway.ssl_strict = true + end + + def test_successful_purchase + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal 'Succeeded', response.message + end + + def test_successful_purchase_sans_options + assert response = @gateway.purchase(@amount, @credit_card) + assert_success response + assert_equal 'Succeeded', response.message + end + + def test_successful_purchase_with_more_options + more_options = @options.merge({ + ip: '127.0.0.1', + email: 'joe@example.com', + }) + + assert response = @gateway.purchase(@amount, @credit_card, @options.merge(more_options)) + assert_success response + assert_equal 'Succeeded', response.message + end + + def test_successful_purchase_with_region + @gateway = TnsGateway.new(fixtures(:tns_ap).merge(region: 'asia_pacific')) + + assert response = @gateway.purchase(@amount, @ap_credit_card, @options.merge(currency: 'AUD')) + assert_success response + assert_equal 'Succeeded', response.message + end + + def test_failed_purchase + assert response = @gateway.purchase(@amount, @declined_card, @options) + assert_failure response + assert_equal 'FAILURE - DECLINED', response.message + end + + def test_successful_authorize_and_capture + assert response = @gateway.authorize(@amount, @credit_card, @options) + assert_success response + assert_equal 'Succeeded', response.message + assert_match %r(^.+\|\d+$), response.authorization + + assert capture = @gateway.capture(@amount, response.authorization) + assert_success capture + assert_equal 'Succeeded', capture.message + end + + def test_failed_authorize + assert response = @gateway.authorize(@amount, @declined_card, @options) + assert_failure response + assert_equal 'FAILURE - DECLINED', response.message + end + + def test_successful_refund + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + + assert refund = @gateway.refund(@amount, response.authorization) + assert_success refund + assert_equal 'Succeeded', refund.message + end + + def test_successful_void + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + assert void = @gateway.void(auth.authorization) + assert_success void + end + + def test_successful_verify + assert response = @gateway.verify(@credit_card, @options) + assert_success response + assert_equal 'Succeeded', response.message + + assert_success response.responses.last, 'The void should succeed' + assert_equal 'SUCCESS', response.responses.last.params['result'] + end + + def test_invalid_login + gateway = TnsGateway.new( + :userid => 'nosuch', + :password => 'thing' + ) + response = gateway.authorize(@amount, @credit_card, @options) + assert_failure response + assert_equal 'ERROR - INVALID_REQUEST - Invalid credentials.', response.message + end + + def test_transcript_scrubbing + card = credit_card('5123456789012346', verification_value: '834') + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, card, @options) + end + transcript = @gateway.scrub(transcript) + + assert_scrubbed(@credit_card.number, transcript) + assert_scrubbed(card.verification_value, transcript) + assert_scrubbed(@gateway.options[:password], transcript) + end + + def test_verify_credentials + assert @gateway.verify_credentials + + gateway = TnsGateway.new(userid: 'unknown', password: 'unknown') + assert !gateway.verify_credentials + end +end diff --git a/test/remote/gateways/remote_trans_first_test.rb b/test/remote/gateways/remote_trans_first_test.rb index e5c13209ba9..3423954560e 100644 --- a/test/remote/gateways/remote_trans_first_test.rb +++ b/test/remote/gateways/remote_trans_first_test.rb @@ -5,21 +5,104 @@ class RemoteTransFirstTest < Test::Unit::TestCase def setup @gateway = TransFirstGateway.new(fixtures(:trans_first)) - @credit_card = credit_card('4111111111111111') - @amount = 100 - @options = { + @credit_card = credit_card('4485896261017708', verification_value: 999) + @check = check + @amount = 1201 + @options = { :order_id => generate_unique_id, :invoice => 'ActiveMerchant Sale', :billing_address => address } end - + def test_successful_purchase assert response = @gateway.purchase(@amount, @credit_card, @options) - assert_equal 'test transaction', response.message assert response.test? assert_success response assert !response.authorization.blank? + + @gateway.void(response.authorization) + end + + def test_successful_purchase_no_address + @options.delete(:billing_address) + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert response.test? + assert_success response + assert !response.authorization.blank? + + @gateway.void(response.authorization) + end + + def test_successful_purchase_sans_cvv + @credit_card.verification_value = '' + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + end + + def test_successful_purchase_with_echeck + assert response = @gateway.purchase(@amount, @check, @options) + assert response.test? + assert_success response + assert !response.authorization.blank? + end + + def test_successful_purchase_with_echeck_no_address + @options.delete(:billing_address) + assert response = @gateway.purchase(@amount, @check, @options) + assert response.test? + assert_success response + assert !response.authorization.blank? + end + + def test_successful_purchase_with_echeck_defaults + @check = check(account_holder_type: nil, account_type: nil) + assert response = @gateway.purchase(@amount, @check, @options) + assert response.test? + assert_success response + assert !response.authorization.blank? + end + + def test_failed_purchase + @amount = 21 + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_equal 'Insufficient funds', response.message + end + + def test_successful_void + assert purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + + assert void = @gateway.void(purchase.authorization) + assert_success void + end + + # Refunds can only be successfully run on settled transactions which take 24 hours + # def test_successful_refund + # assert purchase = @gateway.purchase(@amount, @credit_card, @options) + # assert_success purchase + + # assert refund = @gateway.refund(@amount, purchase.authorization) + # assert_equal @amount, refund.params["amount"].to_i*100 + # assert_success refund + # end + + # def test_successful_partial_refund + # assert purchase = @gateway.purchase(@amount, @credit_card, @options) + # assert_success purchase + + # assert refund = @gateway.refund(@amount-1, purchase.authorization) + # assert_equal @amount-1, refund.params["amount"].to_i*100 + # assert_success refund + # end + + def test_successful_refund_with_echeck + assert purchase = @gateway.purchase(@amount, @check, @options) + assert_success purchase + + assert refund = @gateway.refund(@amount, purchase.authorization) + assert_success refund end def test_invalid_login @@ -27,8 +110,29 @@ def test_invalid_login :login => '', :password => '' ) - assert response = gateway.purchase(@amount, @credit_card, @options) - assert_equal 'invalid account', response.message + assert response = gateway.purchase(1100, @credit_card, @options) assert_failure response end + + def test_transcript_scrubbing + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @credit_card, @options) + end + transcript = @gateway.scrub(transcript) + + assert_scrubbed(@credit_card.number, transcript) + assert_scrubbed(@credit_card.verification_value, transcript) + assert_scrubbed(@gateway.options[:password], transcript) + end + + def test_transcript_scrubbing_echecks + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @check, @options) + end + transcript = @gateway.scrub(transcript) + + assert_scrubbed(@check.account_number, transcript) + assert_scrubbed(@check.routing_number, transcript) + assert_scrubbed(@gateway.options[:password], transcript) + end end diff --git a/test/remote/gateways/remote_trans_first_transaction_express_test.rb b/test/remote/gateways/remote_trans_first_transaction_express_test.rb new file mode 100644 index 00000000000..3e69c82ecee --- /dev/null +++ b/test/remote/gateways/remote_trans_first_transaction_express_test.rb @@ -0,0 +1,397 @@ +require 'test_helper' + +class RemoteTransFirstTransactionExpressTest < Test::Unit::TestCase + + def setup + @gateway = TransFirstTransactionExpressGateway.new(fixtures(:trans_first_transaction_express)) + + @amount = 100 + @declined_amount = 21 + @credit_card = credit_card('4485896261017708', verification_value: 999) + @check = check + + billing_address = address({ + address1: '450 Main', + address2: 'Suite 100', + city: 'Broomfield', + state: 'CO', + zip: '85284', + phone: '(333) 444-5555', + }) + + @options = { + order_id: generate_unique_id, + company_name: 'Acme', + title: 'QA Manager', + billing_address: billing_address, + shipping_address: billing_address, + email: 'example@example.com', + description: 'Store Purchase' + } + end + + def test_invalid_login + gateway = TransFirstTransactionExpressGateway.new(gateway_id: '', reg_key: '') + response = gateway.purchase(@amount, @credit_card, @options) + assert_failure response + end + + def test_successful_purchase + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal 'Succeeded', response.message + assert_not_nil response.avs_result + assert_not_nil response.cvv_result + assert_equal 'Street address does not match, but 5-digit postal code matches.', response.avs_result['message'] + assert_equal 'CVV matches', response.cvv_result['message'] + end + + def test_successful_purchase_no_avs + options = @options.dup + options[:shipping_address] = nil + options[:billing_address] = nil + response = @gateway.purchase(@amount, @credit_card, options) + assert_success response + end + + def test_successful_purchase_with_only_required + # Test the purchase with only the required billing and shipping information + options = @options.dup + options[:shipping_address] = { + address1: '450 Main', + zip: '85284', + } + + options[:billing_address] = { + address1: '450 Main', + zip: '85284', + } + + response = @gateway.purchase(@amount, @credit_card, options) + assert_success response + assert_equal 'Succeeded', response.message + assert_not_nil response.avs_result + assert_not_nil response.cvv_result + assert_equal 'Street address does not match, but 5-digit postal code matches.', response.avs_result['message'] + assert_equal 'CVV matches', response.cvv_result['message'] + end + + def test_successful_purchase_without_address2 + # Test that empty string in `address2` doesn't cause transaction failure + options = @options.dup + options[:shipping_address] = { + address1: '450 Main', + address2: '', + zip: '85284', + } + + options[:billing_address] = { + address1: '450 Main', + address2: '', + zip: '85284', + } + + response = @gateway.purchase(@amount, @credit_card, options) + assert_success response + assert_equal 'Succeeded', response.message + end + + def test_successful_purchase_without_cvv + credit_card_opts = { + :number => 4485896261017708, + :month => Date.new((Time.now.year + 1), 9, 30).month, + :year => Date.new((Time.now.year + 1), 9, 30).year, + :first_name => 'Longbob', + :last_name => 'Longsen', + :brand => 'visa' + } + + credit_card = CreditCard.new(credit_card_opts) + response = @gateway.purchase(@amount, credit_card, @options) + assert_success response + assert_equal 'Succeeded', response.message + end + + def test_successful_purchase_with_empty_string_cvv + credit_card_opts = { + :number => 4485896261017708, + :month => Date.new((Time.now.year + 1), 9, 30).month, + :year => Date.new((Time.now.year + 1), 9, 30).year, + :first_name => 'Longbob', + :last_name => 'Longsen', + :verification_value => '', + :brand => 'visa' + } + + credit_card = CreditCard.new(credit_card_opts) + response = @gateway.purchase(@amount, credit_card, @options) + assert_success response + assert_equal 'Succeeded', response.message + end + + def test_successful_purchase_without_name + credit_card_opts = { + :number => 4485896261017708, + :month => Date.new((Time.now.year + 1), 9, 30).month, + :year => Date.new((Time.now.year + 1), 9, 30).year, + :first_name => '', + :last_name => '' + } + + credit_card = CreditCard.new(credit_card_opts) + response = @gateway.purchase(@amount, credit_card, @options) + assert_success response + assert_equal 'Succeeded', response.message + + credit_card_opts = { + :number => 4485896261017708, + :month => Date.new((Time.now.year + 1), 9, 30).month, + :year => Date.new((Time.now.year + 1), 9, 30).year + } + + credit_card = CreditCard.new(credit_card_opts) + response = @gateway.purchase(@amount, credit_card, @options) + assert_success response + assert_equal 'Succeeded', response.message + end + + def test_successful_purchase_with_echeck + assert response = @gateway.purchase(@amount, @check, @options) + assert_success response + assert_equal 'Succeeded', response.message + end + + def test_failed_purchase + response = @gateway.purchase(@declined_amount, @credit_card, @options) + assert_failure response + assert_equal 'Not sufficient funds', response.message + assert_equal '51', response.params['rspCode'] + end + + def test_failed_purchase_with_echeck + assert response = @gateway.purchase(@amount, check(routing_number: '121042883'), @options) + assert_failure response + assert_equal 'Error. Bank routing number validation negative (ABA).', response.message + end + + def test_successful_authorize_and_capture + response = @gateway.authorize(@amount, @credit_card, @options) + assert_success response + assert_equal 'Succeeded', response.message + assert_match %r(^authorize\|\d+$), response.authorization + + capture = @gateway.capture(@amount, response.authorization) + assert_success capture + assert_equal 'Succeeded', capture.message + end + + def test_failed_authorize + response = @gateway.authorize(@declined_amount, @credit_card, @options) + assert_failure response + assert_equal 'Not sufficient funds', response.message + assert_equal '51', response.error_code + end + + def test_failed_capture + authorize = @gateway.authorize(@declined_amount, @credit_card, @options) + assert_failure authorize + + response = @gateway.capture(@amount, authorize.authorization) + assert_failure response + assert_equal 'Invalid transaction', response.message + assert_equal '12', response.error_code + end + + def test_successful_purchase_void + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + + void = @gateway.void(response.authorization) + assert_success void + assert_equal 'Succeeded', void.message + end + + def test_successful_authorization_void + authorize = @gateway.authorize(@amount, @credit_card, @options) + assert_success authorize + + void = @gateway.void(authorize.authorization) + assert_success void + assert_equal 'Succeeded', void.message + end + + def test_successful_capture_void + authorize = @gateway.authorize(@amount, @credit_card, @options) + assert_success authorize + + capture = @gateway.capture(@amount, authorize.authorization) + assert_success capture + + void = @gateway.void(capture.authorization, void_type: :void_capture) + assert_success void + assert_equal 'Succeeded', void.message + end + + def test_failed_void + response = @gateway.void('purchase|000015212561') + assert_failure response + assert_equal 'Invalid transaction', response.message + assert_equal '12', response.error_code + end + + def test_successful_echeck_purchase_void + response = @gateway.purchase(@amount, @check, @options) + assert_success response + + void = @gateway.void(response.authorization) + assert_success void + assert_equal 'Succeeded', void.message + end + + # gateway does not settle fast enough to test refunds + # def test_successful_refund + # response = @gateway.purchase(@amount, @credit_card, @options) + # assert_success response + + # refund = @gateway.refund(@amount, response.authorization) + # assert_success refund + # assert_equal "Succeeded", refund.message + # end + + def test_helpful_message_when_refunding_unsettled_purchase + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + + refund = @gateway.refund(@amount, purchase.authorization) + + assert_failure refund + assert_equal 'Invalid transaction. Declined Post – Credit linked to unextracted settle transaction', refund.message + end + + def test_failed_refund + response = @gateway.refund(nil, '') + assert_failure response + assert_equal 'Validation Failure', response.message + assert_equal '50011', response.error_code + end + + def test_successful_refund_with_echeck + purchase = @gateway.purchase(@amount, @check, @options) + assert_success purchase + assert_match(/purchase_echeck/, purchase.authorization) + + refund = @gateway.refund(@amount, purchase.authorization) + assert_success refund + end + + def test_failed_refund_with_echeck + refund = @gateway.refund(@amount, 'purchase_echeck|000028706091') + assert_failure refund + assert_equal 'Invalid transaction', refund.message + end + + # Credit is only supported with specific approval from Transaction Express + # def test_successful_credit + # response = @gateway.credit(@amount, @credit_card, @options) + # assert_success response + # assert_equal "Succeeded", response.message + # end + + def test_failed_credit + response = @gateway.credit(0, @credit_card, @options) + assert_failure response + assert_equal '51334', response.error_code + assert_equal 'Validation Error', response.message + end + + def test_successful_verify + visa = credit_card('4485896261017708') + amex = credit_card('371449635392376', verification_value: 1234) + mastercard = credit_card('5499740000000057') + discover = credit_card('6011000991001201') + + [visa, amex, mastercard, discover].each do |credit_card| + response = @gateway.verify(credit_card, @options) + assert_success response + assert_match 'Succeeded', response.message + end + end + + def test_failed_verify + response = @gateway.verify(credit_card(''), @options) + assert_failure response + assert_equal 'Validation Failure', response.message + assert_equal '51308', response.error_code + end + + def test_successful_store + response = @gateway.store(@credit_card, @options) + assert_success response + assert_equal 'Succeeded', response.message + assert response.authorization + end + + def test_successful_authorize_using_stored_card + assert response = @gateway.store(@credit_card) + assert_success response + + response = @gateway.authorize(@amount, response.authorization, @options) + assert_success response + assert_match 'Succeeded', response.message + end + + def test_failed_authorize_using_stored_card + assert response = @gateway.store(@credit_card) + assert_success response + + response = @gateway.authorize(@declined_amount, response.authorization, @options) + assert_failure response + assert_match 'Not sufficient funds', response.message + end + + def test_successful_purchase_using_stored_card + assert response = @gateway.store(@credit_card) + assert_success response + + response = @gateway.purchase(@amount, response.authorization, @options) + assert_success response + assert_match 'Succeeded', response.message + end + + def test_failed_purchase_using_stored_card + assert response = @gateway.store(@credit_card) + assert_success response + + response = @gateway.purchase(@declined_amount, response.authorization, @options) + assert_failure response + assert_match 'Not sufficient funds', response.message + end + + def test_failed_store + response = @gateway.store(credit_card('123'), @options) + assert_failure response + assert_equal 'Validation Failure', response.message + assert_equal '51308', response.error_code + end + + # def test_dump_transcript + # skip("Transcript scrubbing for this gateway has been tested.") + # # This test will run a purchase transaction on your gateway + # # and dump a transcript of the HTTP conversation so that + # # you can use that transcript as a reference while + # # implementing your scrubbing logic + # dump_transcript_and_fail(@gateway, @amount, @credit_card, @options) + # end + + def test_transcript_scrubbing + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @credit_card, @options) + end + clean_transcript = @gateway.scrub(transcript) + + assert_scrubbed(@credit_card.number, clean_transcript) + assert_scrubbed(@credit_card.verification_value.to_s, clean_transcript) + assert_scrubbed(@gateway.options[:gateway_id], clean_transcript) + assert_scrubbed(@gateway.options[:reg_key], clean_transcript) + end +end diff --git a/test/remote/gateways/remote_transact_pro_test.rb b/test/remote/gateways/remote_transact_pro_test.rb new file mode 100644 index 00000000000..5e5c428ae29 --- /dev/null +++ b/test/remote/gateways/remote_transact_pro_test.rb @@ -0,0 +1,130 @@ +require 'test_helper' +require 'active_support/core_ext/hash/slice' + +class RemoteTransactProTest < Test::Unit::TestCase + def setup + test_credentials = fixtures(:transact_pro).slice(:guid, :password, :terminal) + test_card = fixtures(:transact_pro).slice(:card_number, :verification_value, :month, :year) + + @gateway = TransactProGateway.new(test_credentials) + + @amount = 100 + @credit_card = credit_card(test_card.delete(:card_number), test_card) + @declined_card = credit_card('4000300011112220') + + @options = { + order_id: Time.now.to_i, + billing_address: address, + description: 'Store Purchase' + } + end + + def test_successful_purchase + response = @gateway.purchase(@amount, @credit_card, @options) + + assert_success response + assert response.authorization + assert_equal 'Success', response.message + assert response.test? + end + + def test_failed_purchase + response = @gateway.purchase(@amount, @declined_card, @options) + + assert_failure response + assert_equal 'Failed', response.message + assert_equal '908', response.params['result_code'] + end + + def test_successful_authorize_and_capture + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + capture = @gateway.capture(nil, auth.authorization) + assert_success capture + end + + def test_failed_authorize + response = @gateway.authorize(@amount, @declined_card, @options) + + assert_failure response + assert_equal 'Failed', response.message + assert_equal '908', response.params['result_code'] + end + + def test_partial_capture + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + assert_raise(ArgumentError) do + @gateway.capture(@amount-1, auth.authorization) + end + end + + def test_failed_capture + response = @gateway.capture(nil, 'bogus|100') + assert_failure response + assert_equal 'bogus|100', response.authorization + end + + def test_successful_refund + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + + refund = @gateway.refund(nil, purchase.authorization) + assert_success refund + assert_equal 'Refund Success', refund.message + end + + def test_partial_refund + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + + refund = @gateway.refund(@amount-1, purchase.authorization) + assert_success refund + assert_equal 'Refund Success', refund.message + end + + def test_failed_refund + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + + refund = @gateway.refund(@amount+1, purchase.authorization) + assert_failure refund + end + + def test_successful_void + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + void = @gateway.void(auth.authorization) + assert_success void + end + + def test_failed_void + response = @gateway.void('') + assert_failure response + end + + def test_successful_verify + response = @gateway.verify(@credit_card, @options) + assert_success response + end + + def test_failed_verify + response = @gateway.verify(@declined_card, @options) + assert_failure response + assert_equal 'Failed', response.message + end + + def test_invalid_login + gateway = TransactProGateway.new( + guid: '', + password: '', + terminal: '' + ) + response = gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_match %r{bad access data}, response.message + end +end diff --git a/test/remote/gateways/remote_transax_test.rb b/test/remote/gateways/remote_transax_test.rb index 0e9a0e4e16b..1e7bbc00399 100644 --- a/test/remote/gateways/remote_transax_test.rb +++ b/test/remote/gateways/remote_transax_test.rb @@ -20,19 +20,19 @@ def setup def test_successful_purchase assert response = @gateway.purchase(@amount, @credit_card, @options) assert_success response - assert_equal "This transaction has been approved", response.message + assert_equal 'This transaction has been approved', response.message end def test_unsuccessful_purchase assert response = @gateway.purchase(@amount, @declined_card, @options) assert_failure response - assert_match /invalid/i, response.message + assert_match %r{invalid}i, response.message end def test_authorize_and_capture assert auth = @gateway.authorize(@amount, @credit_card, @options) assert_success auth - assert_equal "This transaction has been approved", auth.message + assert_equal 'This transaction has been approved', auth.message assert auth.authorization assert capture = @gateway.capture(@amount, auth.authorization) assert_success capture @@ -41,43 +41,43 @@ def test_authorize_and_capture def test_authorize_and_void assert auth = @gateway.authorize(@amount, @credit_card, @options) assert_success auth - assert_equal "This transaction has been approved", auth.message + assert_equal 'This transaction has been approved', auth.message assert auth.authorization assert void = @gateway.void(auth.authorization) - assert_equal "Transaction Void Successful", void.message + assert_equal 'Transaction Void Successful', void.message assert_success void end def test_purchase_and_refund assert response = @gateway.purchase(@amount, @credit_card, @options) assert_success response - assert_equal "This transaction has been approved", response.message + assert_equal 'This transaction has been approved', response.message assert response.authorization assert refund = @gateway.refund(nil, response.authorization) - assert_equal "This transaction has been approved", refund.message + assert_equal 'This transaction has been approved', refund.message assert_success refund end def test_failed_capture assert response = @gateway.capture(@amount, '') assert_failure response - assert_match /Invalid Transaction ID/, response.message + assert_match %r{Invalid Transaction ID}, response.message end def test_credit assert response = @gateway.credit(@amount, @credit_card, @options) assert_success response assert response.authorization - assert_equal "This transaction has been approved", response.message + assert_equal 'This transaction has been approved', response.message end def test_purchase_and_update assert response = @gateway.purchase(@amount, @credit_card, @options) assert_success response - assert_equal "This transaction has been approved", response.message + assert_equal 'This transaction has been approved', response.message assert response.authorization assert update = @gateway.amend(response.authorization, :shipping_carrier => 'usps') - assert_equal "This transaction has been approved", update.message + assert_equal 'This transaction has been approved', update.message assert_success update end @@ -85,7 +85,7 @@ def test_successful_purchase_with_sku @options['product_sku_#']='123456' assert response = @gateway.purchase(@amount, @credit_card, @options) assert_success response - assert_equal "This transaction has been approved", response.message + assert_equal 'This transaction has been approved', response.message end def test_store_credit_card @@ -100,6 +100,19 @@ def test_store_check assert !response.params['customer_vault_id'].blank? end + def test_successful_verify + assert response = @gateway.verify(@credit_card, @options) + assert_success response + assert_equal 'This transaction has been approved', response.message + end + + def test_failed_verify + bogus_card = credit_card('4424222222222222') + assert response = @gateway.verify(bogus_card, @options) + assert_failure response + assert_match %r{Invalid Credit Card Number}, response.message + end + def test_invalid_login gateway = TransaxGateway.new( :login => '', @@ -107,6 +120,6 @@ def test_invalid_login ) assert response = gateway.purchase(@amount, @credit_card, @options) assert_failure response - assert_equal "Invalid Username", response.message + assert_equal 'Invalid Username', response.message end end diff --git a/test/remote/gateways/remote_trexle_test.rb b/test/remote/gateways/remote_trexle_test.rb new file mode 100644 index 00000000000..149a8530797 --- /dev/null +++ b/test/remote/gateways/remote_trexle_test.rb @@ -0,0 +1,170 @@ +require 'test_helper' + +class RemoteTrexleTest < Test::Unit::TestCase + def setup + @gateway = TrexleGateway.new(fixtures(:trexle)) + + @amount = 100 + @credit_card = credit_card('5555555555554444', year: Time.now.year + 2) + @visa_credit_card = credit_card('4242424242424242', year: Time.now.year + 3) + @declined_card = credit_card('4000000000000119') + + @options = { + email: 'john@trexle.com', + ip: '66.249.79.118', + order_id: '1', + billing_address: address, + description: "ActiveMerchant Demo Purchase #{DateTime.now.to_i}" + } + end + + def test_successful_purchase + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal true, response.params['response']['captured'] + end + + def test_successful_authorize_and_capture + authorization = @gateway.authorize(@amount, @credit_card, @options) + assert_success authorization + assert_equal false, authorization.params['response']['captured'] + + response = @gateway.capture(@amount, authorization.authorization, @options) + assert_success response + assert_equal true, response.params['response']['captured'] + end + + def test_failed_authorize + response = @gateway.authorize(@amount, @declined_card, @options) + assert_failure response + end + + def test_failed_capture_due_to_invalid_token + response = @gateway.capture(@amount, 'bogus', @options) + assert_failure response + end + + def test_failed_capture_due_to_invalid_amount + authorization = @gateway.authorize(@amount, @credit_card, @options) + assert_success authorization + assert_equal authorization.params['response']['captured'], false + + response = @gateway.capture(@amount + 1, authorization.authorization, @options) + assert_failure response + assert_equal 'Payment failed', response.params['error'] + end + + def test_successful_purchase_without_description + @options.delete(:description) + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + end + + def test_unsuccessful_purchase + response = @gateway.purchase(@amount, @declined_card, @options) + assert_failure response + end + + # This is a bit manual as we have to create a working card token as + # would be returned from trexle.js / the card tokens API which + # falls outside of active merchant + def test_store_and_charge_with_trexle_js_card_token + headers = { + 'Content-Type' => 'application/json', + 'Authorization' => "Basic #{Base64.strict_encode64(@gateway.options[:api_key] + ':').strip}" + } + # Get a token equivalent to what is returned by trexle.js + card_attrs = { + number: @credit_card.number, + expiry_month: @credit_card.month, + expiry_year: @credit_card.year, + cvc: @credit_card.verification_value, + name: "#{@credit_card.first_name} #{@credit_card.last_name}", + address_line1: '321 Shoreline Park', + address_line2: 'suite #7', + address_city: 'Mountain View', + address_postcode: '94043', + address_state: 'CA', + address_country: 'United States' + } + url = @gateway.test_url + '/tokens' + + body = JSON.parse(@gateway.ssl_post(url, card_attrs.to_json, headers)) + + card_token = body['response']['token'] + + store = @gateway.store(card_token, @options) + assert_success store + assert_not_nil store.authorization + + purchase = @gateway.purchase(@amount, card_token, @options) + assert_success purchase + assert_not_nil purchase.authorization + end + + def test_store_and_customer_token_charge + response = @gateway.store(@credit_card, @options) + assert_success response + assert_not_nil response.authorization + + token = response.params['response']['card']['token'] + + assert response1 = @gateway.purchase(@amount, token, @options) + assert_success response1 + + assert response2 = @gateway.purchase(@amount, token, @options) + assert_success response2 + assert_not_equal response1.authorization, response2.authorization + end + + def test_store_and_update + response = @gateway.store(@credit_card, @options) + assert_success response + assert_not_nil response.authorization + assert_equal @credit_card.year, response.params['response']['card']['expiry_year'] + + response = @gateway.update(response.authorization, @credit_card, address: address) + assert_success response + assert_not_nil response.authorization + assert_equal @credit_card.year, response.params['response']['card']['expiry_year'] + end + + def test_refund + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_not_nil response.authorization + + token = response.authorization + + response = @gateway.refund(@amount, token, @options) + assert_success response + assert_not_nil response.authorization + end + + def test_failed_refund + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_not_nil response.authorization + + token = response.authorization + + response = @gateway.refund(@amount, token.reverse, @options) + assert_failure response + end + + def test_invalid_login + gateway = TrexleGateway.new(api_key: '') + response = gateway.purchase(@amount, @credit_card, @options) + assert_failure response + end + + def test_transcript_scrubbing + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @credit_card, @options) + end + clean_transcript = @gateway.scrub(transcript) + + assert_scrubbed(@credit_card.number, clean_transcript) + assert_scrubbed(@credit_card.verification_value.to_s, clean_transcript) + end +end diff --git a/test/remote/gateways/remote_trust_commerce_test.rb b/test/remote/gateways/remote_trust_commerce_test.rb index d62c61af52a..9625597ffc1 100644 --- a/test/remote/gateways/remote_trust_commerce_test.rb +++ b/test/remote/gateways/remote_trust_commerce_test.rb @@ -3,22 +3,24 @@ class TrustCommerceTest < Test::Unit::TestCase def setup @gateway = TrustCommerceGateway.new(fixtures(:trust_commerce)) - + @credit_card = credit_card('4111111111111111') - + @declined_credit_card = credit_card('4111111111111112') + @check = check({account_number: 55544433221, routing_number: 789456124}) + @amount = 100 - + @valid_verification_value = '123' @invalid_verification_value = '1234' - + @valid_address = { - :address1 => '123 Test St.', - :address2 => nil, - :city => 'Somewhere', - :state => 'CA', + :address1 => '123 Test St.', + :address2 => nil, + :city => 'Somewhere', + :state => 'CA', :zip => '90001' } - + @invalid_address = { :address1 => '187 Apple Tree Lane.', :address2 => nil, @@ -26,127 +28,196 @@ def setup :state => 'CA', :zip => '94062' } - - @options = { + + # The Trust Commerce API does not return anything different when custom fields are present. + # To confirm that the field values are being stored with the transactions, add a custom + # field in your account in the Vault UI, then examine the transactions after running the + # test suite. + custom_fields = { + 'customfield1' => 'test1' + } + + @options = { :ip => '10.10.10.10', :order_id => '#1000.1', - :email => 'cody@example.com', + :email => 'cody@example.com', :billing_address => @valid_address, - :shipping_address => @valid_address + :shipping_address => @valid_address, + :custom_fields => custom_fields } end - + def test_bad_login @gateway.options[:login] = 'X' assert response = @gateway.purchase(@amount, @credit_card, @options) - + assert_equal Response, response.class - assert_equal ["error", - "offenders", - "status"], response.params.keys.sort + assert_equal ['error', + 'offenders', + 'status'], response.params.keys.sort + + assert_match %r{A field was improperly formatted, such as non-digit characters in a number field}, response.message - assert_match /A field was improperly formatted, such as non-digit characters in a number field/, response.message - assert_failure response end - + def test_successful_purchase_with_avs assert response = @gateway.purchase(@amount, @credit_card, @options) assert_equal 'Y', response.avs_result['code'] - assert_match /The transaction was successful/, response.message - + assert_match %r{The transaction was successful}, response.message + assert_success response assert !response.authorization.blank? end - + + def test_successful_purchase_with_check + assert response = @gateway.purchase(@amount, @check, @options) + assert_match %r{The transaction was successful}, response.message + + assert_success response + assert !response.authorization.blank? + end + def test_unsuccessful_purchase_with_invalid_cvv @credit_card.verification_value = @invalid_verification_value assert response = @gateway.purchase(@amount, @credit_card, @options) - + assert_equal Response, response.class - assert_match /CVV failed; the number provided is not the correct verification number for the card/, response.message + assert_match %r{CVV failed; the number provided is not the correct verification number for the card}, response.message assert_failure response end - + def test_purchase_with_avs_for_invalid_address assert response = @gateway.purchase(@amount, @credit_card, @options.update(:billing_address => @invalid_address)) - assert_equal "N", response.params["avs"] - assert_match /The transaction was successful/, response.message + assert_equal 'N', response.params['avs'] + assert_match %r{The transaction was successful}, response.message assert_success response end - + + # Requires enabling the setting: 'Allow voids to process or settle on processing node' in the Trust Commerce vault UI + def test_purchase_and_void + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + + void = @gateway.void(purchase.authorization) + assert_success void + assert_equal 'The transaction was successful', void.message + assert_equal 'accepted', void.params['status'] + assert void.params['transid'] + end + def test_successful_authorize_with_avs assert response = @gateway.authorize(@amount, @credit_card, :billing_address => @valid_address) - - assert_equal "Y", response.avs_result["code"] - assert_match /The transaction was successful/, response.message + + assert_equal 'Y', response.avs_result['code'] + assert_match %r{The transaction was successful}, response.message assert_success response assert !response.authorization.blank? end - + def test_unsuccessful_authorize_with_invalid_cvv @credit_card.verification_value = @invalid_verification_value assert response = @gateway.authorize(@amount, @credit_card, @options) - assert_match /CVV failed; the number provided is not the correct verification number for the card/, response.message + assert_match %r{CVV failed; the number provided is not the correct verification number for the card}, response.message assert_failure response end - + def test_authorization_with_avs_for_invalid_address assert response = @gateway.authorize(@amount, @credit_card, @options.update(:billing_address => @invalid_address)) - assert_equal "N", response.params["avs"] - assert_match /The transaction was successful/, response.message + assert_equal 'N', response.params['avs'] + assert_match %r{The transaction was successful}, response.message assert_success response end - + def test_successful_capture auth = @gateway.authorize(300, @credit_card) assert_success auth response = @gateway.capture(300, auth.authorization) - + assert_success response - assert_equal 'The transaction was successful', response.message + assert_equal 'The transaction was successful', response.message assert_equal 'accepted', response.params['status'] assert response.params['transid'] end - + def test_authorization_and_void auth = @gateway.authorize(300, @credit_card, @options) assert_success auth - + void = @gateway.void(auth.authorization) assert_success void - assert_equal 'The transaction was successful', void.message + assert_equal 'The transaction was successful', void.message assert_equal 'accepted', void.params['status'] assert void.params['transid'] end - + def test_successful_credit assert response = @gateway.credit(@amount, '011-0022698151') - - assert_match /The transaction was successful/, response.message - assert_success response + + assert_match %r{The transaction was successful}, response.message + assert_success response end - - def test_store_failure + + def test_successful_check_refund + purchase = @gateway.purchase(@amount, @check, @options) + + assert response = @gateway.refund(@amount, purchase.authorization) + + assert_match %r{The transaction was successful}, response.message + assert_success response + end + + def test_successful_store assert response = @gateway.store(@credit_card) - + assert_equal Response, response.class - assert_match %r{The merchant can't accept data passed in this field}, response.message - assert_failure response + assert_equal 'approved', response.params['status'] + assert_match %r{The transaction was successful}, response.message end - + + def test_failed_store + assert response = @gateway.store(@declined_credit_card) + + assert_bad_data_response(response) + end + def test_unstore_failure - assert response = @gateway.unstore('testme') + assert response = @gateway.unstore('does-not-exist') - assert_match %r{The merchant can't accept data passed in this field}, response.message + assert_match %r{A field was longer or shorter than the server allows}, response.message assert_failure response end - - def test_recurring_failure + + def test_successful_recurring assert response = @gateway.recurring(@amount, @credit_card, :periodicity => :weekly) - assert_match %r{The merchant can't accept data passed in this field}, response.message - assert_failure response + assert_match %r{The transaction was successful}, response.message + assert_success response + end + + def test_failed_recurring + assert response = @gateway.recurring(@amount, @declined_credit_card, :periodicity => :weekly) + + assert_bad_data_response(response) + end + + def test_transcript_scrubbing + @credit_card.verification_value = @invalid_verification_value + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @credit_card, @options) + end + clean_transcript = @gateway.scrub(transcript) + + assert_scrubbed(@credit_card.number, clean_transcript) + assert_scrubbed(@credit_card.verification_value.to_s, clean_transcript) + end + + private + + def assert_bad_data_response(response) + assert_equal Response, response.class + assert_equal 'A field was improperly formatted, such as non-digit characters in a number field', response.message + assert_equal 'baddata', response.params['status'] end -end \ No newline at end of file +end diff --git a/test/remote/gateways/remote_usa_epay_advanced_test.rb b/test/remote/gateways/remote_usa_epay_advanced_test.rb index 440c9a350c3..021f73d910a 100644 --- a/test/remote/gateways/remote_usa_epay_advanced_test.rb +++ b/test/remote/gateways/remote_usa_epay_advanced_test.rb @@ -2,54 +2,45 @@ require 'logger' class RemoteUsaEpayAdvancedTest < Test::Unit::TestCase - def setup - # Optional Logger Setup - # UsaEpayAdvancedGateway.logger = Logger.new('/tmp/usa_epay.log') - # UsaEpayAdvancedGateway.logger.level = Logger::DEBUG - - # Optional Wiredump Setup - # UsaEpayAdvancedGateway.wiredump_device = File.open('/tmp/usa_epay_dump.log', 'a+') - # UsaEpayAdvancedGateway.wiredump_device.sync = true - @gateway = UsaEpayAdvancedGateway.new(fixtures(:usa_epay_advanced)) @amount = 2111 - + @credit_card = ActiveMerchant::Billing::CreditCard.new( :number => '4000100011112224', - :month => 12, - :year => 12, + :month => 9, + :year => Time.now.year + 1, :brand => 'visa', :verification_value => '123', - :first_name => "Fred", - :last_name => "Flintstone" + :first_name => 'Fred', + :last_name => 'Flintstone' ) @bad_credit_card = ActiveMerchant::Billing::CreditCard.new( :number => '4000300011112220', - :month => 12, - :year => 12, + :month => 9, + :year => 14, :brand => 'visa', :verification_value => '999', - :first_name => "Fred", - :last_name => "Flintstone" + :first_name => 'Fred', + :last_name => 'Flintstone' ) @check = ActiveMerchant::Billing::Check.new( - :number => '123456789', + :account_number => '123456789', :routing_number => '120450780', :account_type => 'checking', - :first_name => "Fred", - :last_name => "Flintstone" + :first_name => 'Fred', + :last_name => 'Flintstone' ) - + cc_method = [ - {:name => "My CC", :sort => 5, :method => @credit_card}, - {:name => "Other CC", :sort => 12, :method => @credit_card} + {:name => 'My CC', :sort => 5, :method => @credit_card}, + {:name => 'Other CC', :sort => 12, :method => @credit_card} ] - @options = { + @options = { :client_ip => '127.0.0.1', :billing_address => address, } @@ -61,20 +52,20 @@ def setup @customer_options = { :id => 123, - :notes => "Customer note.", - :data => "complex data", - :url => "somesite.com", + :notes => 'Customer note.', + :data => 'complex data', + :url => 'somesite.com', :payment_methods => cc_method } @update_customer_options = { - :notes => "NEW NOTE!" + :notes => 'NEW NOTE!' } @add_payment_options = { :make_default => true, :payment_method => { - :name => "My new card.", + :name => 'My new card.', :sort => 10, :method => @credit_card } @@ -86,6 +77,12 @@ def setup :amount => 10000 } + @run_transaction_check_options = { + :payment_method => @check, + :command => 'check', + :amount => 10000 + } + @run_sale_options = { :payment_method => @credit_card, :amount => 5000 @@ -95,22 +92,10 @@ def setup :payment_method => @check, :amount => 2500 } - - payment_methods = [ - { - :name => "My Visa", # optional - :sort => 2, # optional - :method => @credit_card - }, - { - :name => "My Checking", - :method => @check - } - ] end # Standard Gateway ================================================== - + def test_purchase assert response = @gateway.purchase(@amount, @credit_card, @options) assert_equal 'A', response.params['run_sale_return']['result_code'] @@ -130,7 +115,7 @@ def test_capture def test_void assert purchase = @gateway.purchase(@amount, @credit_card, @options.dup) - + assert credit = @gateway.void(purchase.authorization, @options) assert_equal 'true', credit.params['void_transaction_return'] end @@ -138,7 +123,7 @@ def test_void def test_credit assert purchase = @gateway.purchase(@amount, @credit_card, @options.dup) - assert_deprecation_warning(Gateway::CREDIT_DEPRECATION_MESSAGE, @gateway) do + assert_deprecation_warning(Gateway::CREDIT_DEPRECATION_MESSAGE) do assert credit = @gateway.credit(@amount, purchase.authorization, @options) assert_equal 'A', credit.params['refund_transaction_return']['result_code'] end @@ -161,7 +146,7 @@ def test_invalid_login assert_failure response assert_equal 'Invalid software ID', response.message end - + # Customer ========================================================== def test_add_customer @@ -172,12 +157,20 @@ def test_add_customer def test_update_customer response = @gateway.add_customer(@options.merge(@customer_options)) customer_number = response.params['add_customer_return'] - + @options.merge!(@update_customer_options.merge!(:customer_number => customer_number)) response = @gateway.update_customer(@options) assert response.params['update_customer_return'] end + def test_quick_update_customer + response = @gateway.add_customer(@options.merge(@customer_options)) + customer_number = response.params['add_customer_return'] + + response = @gateway.quick_update_customer({customer_number: customer_number, update_data: @update_customer_options}) + assert response.params['quick_update_customer_return'] + end + def test_enable_disable_customer response = @gateway.add_customer(@options.merge(@customer_options)) customer_number = response.params['add_customer_return'] @@ -192,7 +185,7 @@ def test_enable_disable_customer def test_add_customer_payment_method response = @gateway.add_customer(@options.merge(@customer_options)) customer_number = response.params['add_customer_return'] - + @options.merge!(:customer_number => customer_number).merge!(@add_payment_options) response = @gateway.add_customer_payment_method(@options) assert response.params['add_customer_payment_method_return'] @@ -201,7 +194,7 @@ def test_add_customer_payment_method def test_add_customer_payment_method_verify response = @gateway.add_customer(@options.merge(@customer_options)) customer_number = response.params['add_customer_return'] - + @add_payment_options[:payment_method][:method] = @bad_credit_card @options.merge!(:customer_number => customer_number, :verify => true).merge!(@add_payment_options) response = @gateway.add_customer_payment_method(@options) @@ -211,7 +204,7 @@ def test_add_customer_payment_method_verify def test_get_customer_payment_methods response = @gateway.add_customer(@options.merge(@customer_options)) customer_number = response.params['add_customer_return'] - + response = @gateway.get_customer_payment_methods(:customer_number => customer_number) assert response.params['get_customer_payment_methods_return']['item'] end @@ -230,13 +223,14 @@ def test_get_customer_payment_method def test_update_customer_payment_method response = @gateway.add_customer(@options.merge(@customer_options)) customer_number = response.params['add_customer_return'] - + @options.merge!(:customer_number => customer_number).merge!(@add_payment_options) response = @gateway.add_customer_payment_method(@options) payment_method_id = response.params['add_customer_payment_method_return'] - update_payment_options = @add_payment_options[:payment_method].merge(:method_id => payment_method_id, - :name => "Updated Card.") + update_payment_options = @add_payment_options[:payment_method].merge(:method_id => payment_method_id, + :name => 'Updated Card.') + response = @gateway.update_customer_payment_method(update_payment_options) assert response.params['update_customer_payment_method_return'] end @@ -244,7 +238,7 @@ def test_update_customer_payment_method def test_delete_customer_payment_method response = @gateway.add_customer(@options.merge(@customer_options)) customer_number = response.params['add_customer_return'] - + @options.merge!(:customer_number => customer_number).merge!(@add_payment_options) response = @gateway.add_customer_payment_method(@options) id = response.params['add_customer_payment_method_return'] @@ -256,7 +250,7 @@ def test_delete_customer_payment_method def test_delete_customer response = @gateway.add_customer(@options.merge(@customer_options)) customer_number = response.params['add_customer_return'] - + response = @gateway.delete_customer(:customer_number => customer_number) assert response.params['delete_customer_return'] end @@ -265,8 +259,8 @@ def test_run_customer_transaction response = @gateway.add_customer(@options.merge(@customer_options)) customer_number = response.params['add_customer_return'] - response = @gateway.run_customer_transaction(:customer_number => customer_number,# :method_id => 0, # optional - :command => "Sale", :amount => 3000) + response = @gateway.run_customer_transaction(:customer_number => customer_number, # :method_id => 0, # optional + :command => 'Sale', :amount => 3000) assert response.params['run_customer_transaction_return'] end @@ -276,6 +270,14 @@ def test_run_transaction @options.merge!(@run_transaction_options) response = @gateway.run_transaction(@options) assert response.params['run_transaction_return'] + assert response.success? + end + + def test_run_transaction_check + @options.merge!(@run_transaction_check_options) + response = @gateway.run_transaction(@options) + assert response.params['run_transaction_return'] + assert response.success? end def test_run_sale @@ -310,7 +312,7 @@ def test_run_check_credit # TODO get offline auth_code? def test_post_auth - @options.merge!(:authorization_code => 123456) + @options[:authorization_code] = 123456 response = @gateway.post_auth(@options) assert response.params['post_auth_return'] end @@ -345,13 +347,13 @@ def test_refund_transaction assert response.params['refund_transaction_return'] end - # TODO how to test override_transction + # TODO how to test override_transaction def test_override_transaction options = @options.merge(@run_check_sale_options) response = @gateway.run_check_sale(options) reference_number = response.params['run_check_sale_return']['ref_num'] - response = @gateway.override_transaction(:reference_number => reference_number, :reason => "Because I said so") + response = @gateway.override_transaction(:reference_number => reference_number, :reason => 'Because I said so') assert response.params['faultstring'] end @@ -413,9 +415,12 @@ def test_get_transaction_custom response = @gateway.run_sale(@options.merge(@run_sale_options)) reference_number = response.params['run_sale_return']['ref_num'] - response = @gateway.get_transaction_custom(:reference_number => reference_number, + response = @gateway.get_transaction_custom(:reference_number => reference_number, :fields => ['Response.StatusCode', 'Response.Status']) assert response.params['get_transaction_custom_return'] + response = @gateway.get_transaction_custom(:reference_number => reference_number, + :fields => ['Response.StatusCode']) + assert response.params['get_transaction_custom_return'] end def test_get_check_trace diff --git a/test/remote/gateways/remote_usa_epay_transaction_test.rb b/test/remote/gateways/remote_usa_epay_transaction_test.rb index 0be0bd1e161..476b2461206 100644 --- a/test/remote/gateways/remote_usa_epay_transaction_test.rb +++ b/test/remote/gateways/remote_usa_epay_transaction_test.rb @@ -3,20 +3,108 @@ class RemoteUsaEpayTransactionTest < Test::Unit::TestCase def setup @gateway = UsaEpayTransactionGateway.new(fixtures(:usa_epay)) - @creditcard = credit_card('4000100011112224') + @credit_card = credit_card('4000100011112224') @declined_card = credit_card('4000300011112220') - @options = { :billing_address => address(:zip => "27614", :state => "NC") } + @credit_card_with_track_data = credit_card_with_track_data('4000100011112224') + @invalid_transaction_card = credit_card('4000300511112225') + @check = check + @options = { :billing_address => address(:zip => '27614', :state => 'NC'), :shipping_address => address } @amount = 100 end def test_successful_purchase - assert response = @gateway.purchase(@amount, @creditcard, @options) + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_equal 'Success', response.message + assert_success response + end + + def test_successful_purchase_with_track_data + assert response = @gateway.purchase(@amount, @credit_card_with_track_data, @options) + assert_equal 'Success', response.message + assert_success response + end + + def test_successful_purchase_with_echeck + assert response = @gateway.purchase(@amount, @check, @options) + assert_equal 'Success', response.message + assert_success response + end + + def test_successful_purchase_with_echeck_and_extra_options + extra_options = @options.merge(check_format: 'ARC', account_type: 'savings') + assert response = @gateway.purchase(@amount, @check, extra_options) + assert_equal 'Success', response.message + assert_success response + end + + def test_successful_authorization_with_manual_entry + @credit_card.manual_entry = true + assert response = @gateway.authorize(@amount, @credit_card, @options) + assert_equal 'Success', response.message + assert_success response + end + + def test_successful_purchase_with_manual_entry + @credit_card.manual_entry = true + assert response = @gateway.purchase(@amount, @credit_card, @options) assert_equal 'Success', response.message assert_success response end def test_successful_purchase_with_extra_details - assert response = @gateway.purchase(@amount, @creditcard, @options.merge(:order_id => generate_unique_id, :description => "socool")) + assert response = @gateway.purchase(@amount, @credit_card, @options.merge(:order_id => generate_unique_id, :description => 'socool')) + assert_equal 'Success', response.message + assert_success response + end + + def test_successful_purchase_with_extra_test_mode + assert response = @gateway.purchase(@amount, @credit_card, @options.merge(:test_mode => true)) + assert_equal 'Success', response.message + assert_success response + end + + def test_successful_purchase_with_email_receipt + assert response = @gateway.purchase(@amount, @credit_card, @options.merge(:email => 'hank@hill.com', :cust_receipt => 'Yes')) + assert_equal 'Success', response.message + assert_success response + end + + def test_successful_purchase_with_recurring_fields + recurring_fields = [ + add_customer: true, + schedule: 'quarterly', + bill_source_key: 'bill source key', + bill_amount: 123, + num_left: 5, + start: '20501212', + recurring_receipt: true + ] + + assert response = @gateway.purchase(@amount, @credit_card, @options.merge(recurring_fields: recurring_fields)) + assert_equal 'Success', response.message + assert_success response + end + + def test_successful_purchase_with_custom_fields + custom_fields = { + 1 => 'multi', + 2 => 'pass', + 3 => 'korben', + 4 => 'dallas' + } + + assert response = @gateway.purchase(@amount, @credit_card, @options.merge(custom_fields: custom_fields)) + assert_equal 'Success', response.message + assert_success response + end + + def test_successful_purchase_with_line_items + line_items = [ + {sku: 'abc123', cost: 119, quantity: 1}, + {sku: 'def456', cost: 200, quantity: 2, name: 'an item' } + ] + + assert response = @gateway.purchase(@amount, @credit_card, @options.merge(line_items: line_items)) assert_equal 'Success', response.message assert_success response end @@ -28,10 +116,11 @@ def test_unsuccessful_purchase assert response = @gateway.purchase(@amount, @declined_card, @options.merge(:order_id => generate_unique_id)) assert_failure response assert_match(/declined/i, response.message) + assert Gateway::STANDARD_ERROR_CODE[:card_declined], response.error_code end def test_authorize_and_capture - assert auth = @gateway.authorize(@amount, @creditcard, @options) + assert auth = @gateway.authorize(@amount, @credit_card, @options) assert_success auth assert_equal 'Success', auth.message assert auth.authorization @@ -46,7 +135,23 @@ def test_failed_capture end def test_successful_refund - assert response = @gateway.purchase(@amount, @creditcard, @options) + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert response.authorization + assert refund = @gateway.refund(@amount - 20, response.authorization) + assert_success refund + end + + def test_successful_refund_with_track_data + assert response = @gateway.purchase(@amount, @credit_card_with_track_data, @options) + assert_success response + assert response.authorization + assert refund = @gateway.refund(@amount - 20, response.authorization) + assert_success refund + end + + def test_successful_refund_of_echeck + assert response = @gateway.purchase(@amount, @check, @options) assert_success response assert response.authorization assert refund = @gateway.refund(@amount - 20, response.authorization) @@ -54,13 +159,21 @@ def test_successful_refund end def test_unsuccessful_refund - assert refund = @gateway.refund(@amount - 20, "unknown_authorization") + assert refund = @gateway.refund(@amount - 20, 'unknown_authorization') assert_failure refund assert_match(/Unable to find original transaction/, refund.message) end def test_successful_void - assert response = @gateway.purchase(@amount, @creditcard, @options) + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert response.authorization + assert void = @gateway.void(response.authorization) + assert_success void + end + + def test_successful_void_with_echeck + assert response = @gateway.purchase(@amount, @check, @options) assert_success response assert response.authorization assert void = @gateway.void(response.authorization) @@ -68,15 +181,83 @@ def test_successful_void end def test_unsuccessful_void - assert void = @gateway.void("unknown_authorization") + assert void = @gateway.void('unknown_authorization') + assert_failure void + assert_match(/Unable to locate transaction/, void.message) + end + + def test_successful_void_release + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert response.authorization + assert void = @gateway.void(response.authorization, void_mode: :void_release) + assert_success void + end + + def test_successful_void_release_with_echeck + assert response = @gateway.purchase(@amount, @check, @options) + assert_success response + assert response.authorization + assert void = @gateway.void(response.authorization, void_mode: :void_release) + assert_success void + end + + def test_unsuccessful_void_release + assert void = @gateway.void('unknown_authorization', void_mode: :void_release) assert_failure void assert_match(/Unable to locate transaction/, void.message) end def test_invalid_key gateway = UsaEpayTransactionGateway.new(:login => '') - assert response = gateway.purchase(@amount, @creditcard, @options) + assert response = gateway.purchase(@amount, @credit_card, @options) assert_equal 'Specified source key not found.', response.message assert_failure response end + + def test_successful_verify + assert response = @gateway.verify(@credit_card, @options) + assert_success response + assert_equal 'Success', response.message + assert_success response.responses.last, 'The void should succeed' + end + + def test_failed_verify + assert response = @gateway.verify(@declined_card, @options) + assert_failure response + assert_match 'Card Declined (00)', response.message + end + + def test_transcript_scrubbing + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @credit_card, @options) + end + transcript = @gateway.scrub(transcript) + + assert_scrubbed(@credit_card.number, transcript) + assert_scrubbed(@credit_card.verification_value, transcript) + assert_scrubbed(@gateway.options[:login], transcript) + + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @credit_card_with_track_data, @options) + end + transcript = @gateway.scrub(transcript) + + assert_scrubbed(@credit_card_with_track_data.track_data, transcript) + assert_scrubbed(@gateway.options[:login], transcript) + + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @check, @options) + end + transcript = @gateway.scrub(transcript) + + assert_scrubbed(@check.account_number, transcript) + assert_scrubbed(@gateway.options[:login], transcript) + end + + def test_processing_error + assert response = @gateway.purchase(@amount, @invalid_transaction_card, @options) + assert_equal 'processing_error', response.error_code + assert_failure response + end end diff --git a/test/remote/gateways/remote_vanco_test.rb b/test/remote/gateways/remote_vanco_test.rb new file mode 100644 index 00000000000..077b9b1a248 --- /dev/null +++ b/test/remote/gateways/remote_vanco_test.rb @@ -0,0 +1,110 @@ +require 'test_helper' + +class RemoteVancoTest < Test::Unit::TestCase + def setup + @gateway = VancoGateway.new(fixtures(:vanco)) + + @amount = 10005 + @credit_card = credit_card('4111111111111111') + @declined_card = credit_card('4111111111111111', year: 2011) + @check = check + + @options = { + order_id: '1', + billing_address: address(country: 'US', state: 'NC', zip: '06085'), + description: 'Store Purchase' + } + end + + def test_successful_purchase + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal 'Success', response.message + end + + def test_successful_purchase_with_fund_id + response = @gateway.purchase(@amount, @credit_card, @options.merge(fund_id: 'TheFund')) + assert_success response + assert_equal 'Success', response.message + end + + def test_successful_purchase_with_ip_address + response = @gateway.purchase(@amount, @credit_card, @options.merge(ip: '192.168.19.123')) + assert_success response + assert_equal 'Success', response.message + end + + def test_successful_purchase_sans_minimal_options + response = @gateway.purchase(@amount, @credit_card) + assert_success response + assert_equal 'Success', response.message + end + + def test_failed_purchase + response = @gateway.purchase(@amount, @declined_card) + assert_failure response + assert_equal('Invalid Expiration Date', response.message) + assert_equal('183', response.params['error_codes']) + end + + def test_successful_echeck_purchase + response = @gateway.purchase(@amount, @check, @options) + assert_success response + assert response.test? + assert_equal 'Success', response.message + end + + def test_failed_echeck_purchase + response = @gateway.purchase(@amount, check(routing_number: '121042883'), @options) + assert_failure response + assert_equal 'Invalid Routing Number', response.message + end + + def test_successful_refund + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + + refund = @gateway.refund(@amount, purchase.authorization) + assert_success refund + assert_equal 'Success', refund.message + end + + def test_partial_refund + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + + refund = @gateway.refund(@amount-1, purchase.authorization) + assert_success refund + end + + def test_failed_refund + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + + refund = @gateway.refund(@amount+500, purchase.authorization) + assert_failure refund + assert_match(/Amount Cannot Be Greater Than/, refund.message) + end + + def test_transcript_scrubbing + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @credit_card, @options) + end + transcript = @gateway.scrub(transcript) + + assert_scrubbed(@credit_card.number, transcript) + assert_scrubbed(@credit_card.verification_value, transcript) + assert_scrubbed(@gateway.options[:password], transcript) + end + + def test_invalid_login + gateway = VancoGateway.new( + user_id: 'unknown_id', + password: 'unknown_pwd', + client_id: '' + ) + response = gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_equal 'Invalid Login Key', response.message + end +end diff --git a/test/remote/gateways/remote_verifi_test.rb b/test/remote/gateways/remote_verifi_test.rb index a941fdcb143..2514864a96f 100644 --- a/test/remote/gateways/remote_verifi_test.rb +++ b/test/remote/gateways/remote_verifi_test.rb @@ -5,103 +5,94 @@ class VerifiTest < Test::Unit::TestCase def setup @gateway = VerifiGateway.new(fixtures(:verify)) - + @credit_card = credit_card('4111111111111111') - + # Replace with your login and password for the Verifi test environment @options = { :order_id => '37', - :email => "test@example.com", + :email => 'test@example.com', :billing_address => address } - + @amount = 100 end - + def test_successful_purchase assert response = @gateway.purchase(@amount, @credit_card, @options) assert_success response assert_equal 'Transaction was Approved', response.message assert !response.authorization.blank? end - + def test_unsuccessful_purchase @credit_card.number = 'invalid' - + assert response = @gateway.purchase(@amount, @credit_card, @options) assert_failure response assert_equal 'Transaction was Rejected by Gateway', response.message end - + # FOR SOME REASON Verify DOESN'T MIND EXPIRED CARDS # I talked to support and they said that they are loose on expiration dates being expired. def test_expired_credit_card - @credit_card.year = (Time.now.year - 3) + @credit_card.year = (Time.now.year - 3) assert response = @gateway.purchase(@amount, @credit_card, @options) assert_success response - assert_equal 'Transaction was Approved', response.message + assert_equal 'Transaction was Approved', response.message end - + def test_successful_authorization assert response = @gateway.authorize(@amount, @credit_card, @options) assert_success response assert_equal 'Transaction was Approved', response.message assert response.authorization end - + def test_authorization_and_capture assert authorization = @gateway.authorize(@amount, @credit_card, @options) assert_success authorization assert authorization - assert capture = @gateway.capture(@amount, authorization.authorization, @options) + assert capture = @gateway.capture(@amount, authorization.authorization, @options) assert_success capture assert_equal 'Transaction was Approved', capture.message end - - def test_authorization_and_void - assert authorization = @gateway.authorize(@amount, @credit_card, @options) - assert_success authorization - assert authorization - assert void = @gateway.void(authorization.authorization, @options) - assert_success void - assert_equal 'Transaction was Approved', void.message - end - - # Credits are not enabled on test accounts, so this should always fail + + # Credits are not enabled on test accounts, so this should always fail def test_credit assert response = @gateway.credit(@amount, @credit_card, @options) - assert_match /Credits are not enabled/, response.params['responsetext'] - assert_failure response + assert_match %r{Credits are not enabled}, response.params['responsetext'] + assert_failure response end - + def test_authorization_and_void assert authorization = @gateway.authorize(@amount, @credit_card, @options) assert_success authorization assert void = @gateway.void(authorization.authorization, @options) assert_success void assert_equal 'Transaction was Approved', void.message - assert_match /Transaction Void Successful/, void.params['responsetext'] + assert_match %r{Transaction Void Successful}, void.params['responsetext'] end - + def test_purchase_and_credit assert purchase = @gateway.purchase(@amount, @credit_card, @options) assert_success purchase - + assert credit = @gateway.credit(@amount, purchase.authorization, @options) assert_success credit assert_equal 'Transaction was Approved', credit.message end - + def test_bad_login gateway = VerifiGateway.new( :login => 'X', :password => 'Y' ) - + assert response = gateway.purchase(@amount, @credit_card, @options) assert_equal 'Transaction was Rejected by Gateway', response.message assert_equal 'Authentication Failed', response.params['responsetext'] - + assert_failure response end end diff --git a/test/remote/gateways/remote_viaklix_test.rb b/test/remote/gateways/remote_viaklix_test.rb index 319914004dc..c3dc47c1a43 100644 --- a/test/remote/gateways/remote_viaklix_test.rb +++ b/test/remote/gateways/remote_viaklix_test.rb @@ -3,41 +3,41 @@ class RemoteViaklixTest < Test::Unit::TestCase def setup @gateway = ViaklixGateway.new(fixtures(:viaklix)) - - @credit_card = credit_card + + @credit_card = credit_card @bad_credit_card = credit_card('invalid') - + @options = { :order_id => '#1000.1', - :email => "paul@domain.com", + :email => 'paul@domain.com', :description => 'Test Transaction', :billing_address => address } @amount = 100 end - + def test_successful_purchase assert response = @gateway.purchase(@amount, @credit_card, @options) - + assert_success response assert response.test? assert_equal 'APPROVED', response.message assert response.authorization end - + def test_failed_purchase assert response = @gateway.purchase(@amount, @bad_credit_card, @options) - + assert_failure response assert response.test? assert_equal 'The Credit Card Number supplied in the authorization request appears invalid.', response.message end - + def test_credit assert purchase = @gateway.purchase(@amount, @credit_card, @options) assert_success purchase - + assert credit = @gateway.credit(@amount, @credit_card) assert_success credit end -end \ No newline at end of file +end diff --git a/test/remote/gateways/remote_vindicia_test.rb b/test/remote/gateways/remote_vindicia_test.rb deleted file mode 100644 index 9201bb709f3..00000000000 --- a/test/remote/gateways/remote_vindicia_test.rb +++ /dev/null @@ -1,97 +0,0 @@ -require 'test_helper' - -class RemoteVindiciaTest < Test::Unit::TestCase - def setup - @account_id = rand(9000000) - - @gateway = VindiciaGateway.new(fixtures(:vindicia).merge( - :account_id => @account_id, :avs_success => %{IU} - )) - - @amount = 500 - @credit_card = credit_card('4112344112344113') - @declined_card = credit_card('4000300011112220') - - @recurring_product_sku = 'CHANGE TO A VALID PRODUCT SKU' - - @options = { - :order_id => rand(4000000), - :billing_address => address, - :shipping_address => address, - :line_items => { - :name => 'Test Product', - :sku => 'CHANGE TO A VALID PRODUCT SKU', - :price => 5, - :quantity => 1 - } - } - end - - def test_successful_purchase - assert response = @gateway.purchase(@amount, @credit_card, @options) - assert_success response - assert_equal 'Ok', response.message - end - - def test_unsuccessful_purchase - assert response = @gateway.purchase(@amount, @declined_card, @options) - assert_failure response - assert_equal 'OK', response.message - end - - def test_authorize_and_capture - assert auth = @gateway.authorize(@amount, @credit_card, @options) - assert_success auth - assert_equal 'OK', auth.message - assert auth.authorization - - assert capture = @gateway.capture(@amount, auth.authorization) - assert_success capture - end - - def test_failed_capture - assert response = @gateway.capture(@amount, '') - assert_equal response.params["qtyFail"].to_i, 1 - assert_failure response - assert_equal 'Ok', response.message - end - - def test_successful_void - assert auth = @gateway.authorize(@amount, @credit_card, @options) - assert_success auth - assert_equal 'OK', auth.message - assert auth.authorization - - assert void = @gateway.void(auth.authorization) - assert_equal void.params["qtySuccess"].to_i, 1 - assert_success void - assert_equal 'Ok', void.message - end - - def test_failed_void - assert void = @gateway.void('') - assert_equal void.params["qtyFail"].to_i, 1 - assert_failure void - assert_equal 'Ok', void.message - end - - def test_recurrence_setup - @options.merge!(:product_sku => @recurring_product_sku) - - assert response = @gateway.recurring(@amount, @credit_card, @options) - assert_success response - assert_equal 'OK', response.message - end - - def test_invalid_login - gateway = VindiciaGateway.new( - :login => '', - :password => '', - :account_id => 1 - ) - - assert response = gateway.purchase(@amount, @credit_card, @options) - assert_failure response - assert response.message.include?("Permission denied") - end -end diff --git a/test/remote/gateways/remote_visanet_peru_test.rb b/test/remote/gateways/remote_visanet_peru_test.rb new file mode 100644 index 00000000000..950705e68b5 --- /dev/null +++ b/test/remote/gateways/remote_visanet_peru_test.rb @@ -0,0 +1,167 @@ +require 'test_helper' + +class RemoteVisanetPeruTest < Test::Unit::TestCase + def setup + @gateway = VisanetPeruGateway.new(fixtures(:visanet_peru)) + + @amount = 100 + @credit_card = credit_card('4500340090000016', verification_value: '377') + @declined_card = credit_card('4111111111111111') + + @options = { + billing_address: address, + order_id: generate_unique_id, + email: 'visanetperutest@mailinator.com' + } + end + + def test_invalid_login + gateway = VisanetPeruGateway.new(access_key_id: '', secret_access_key: '', merchant_id: '') + response = gateway.purchase(@amount, @credit_card, @options) + assert_failure response + end + + def test_successful_purchase + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal 'OK', response.message + assert response.authorization + assert_equal @options[:order_id], response.params['externalTransactionId'] + assert response.test? + end + + def test_successful_purchase_with_merchant_define_data + options = @options.merge(merchant_define_data: { field3: 'movil', field91: '101266802', field92: 'TheMerchant' }) + response = @gateway.purchase(@amount, @credit_card, options) + assert_success response + assert_equal 'OK', response.message + end + + def test_successful_purchase_sans_options + response = @gateway.purchase(@amount, @credit_card) + assert_success response + assert_equal 'OK', response.message + end + + def test_failed_purchase + response = @gateway.purchase(@amount, @declined_card, @options) + assert_failure response + assert_equal 400, response.error_code + assert_equal 'Operacion Denegada.', response.message + end + + def test_successful_authorize_and_capture + response = @gateway.authorize(@amount, @credit_card, @options) + assert_success response + assert_equal 'OK', response.message + assert response.authorization + assert_equal @options[:order_id], response.params['externalTransactionId'] + assert_equal '1.00', response.params['data']['IMP_AUTORIZADO'] + + capture = @gateway.capture(response.authorization, @options) + assert_success capture + assert_equal 'OK', capture.message + assert capture.authorization + assert_equal @options[:order_id], capture.params['externalTransactionId'] + end + + def test_successful_authorize_fractional_amount + amount = 199 + response = @gateway.authorize(amount, @credit_card) + assert_success response + assert_equal 'OK', response.message + assert_equal '1.99', response.params['data']['IMP_AUTORIZADO'] + end + + def test_failed_authorize_declined_card + response = @gateway.authorize(@amount, @declined_card, @options) + assert_failure response + assert_equal 400, response.error_code + assert_equal 'Operacion Denegada.', response.message + end + + def test_failed_authorize_bad_email + @options[:email] = 'cybersource@reject.com' + response = @gateway.authorize(@amount, @credit_card, @options) + assert_failure response + assert_equal 400, response.error_code + + # this also exercises message joining for errorMessage and DSC_COD_ACCION when both are present + assert_equal 'REJECT | Operacion denegada', response.message + end + + def test_failed_capture + response = @gateway.capture('900000044') + assert_failure response + assert_match(/NUMORDEN 900000044 no se encuentra registrado/, response.message) + assert_equal 400, response.error_code + end + + def test_successful_refund + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + + refund = @gateway.refund(@amount, response.authorization) + assert_success refund + assert_equal 'OK', refund.message + end + + def test_successful_refund_unsettled + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + + new_auth = "_|#{response.authorization.split('|')[1]}" + @gateway.refund(@amount, new_auth, @options.merge(force_full_refund_if_unsettled: true, ruc: '20341198217')) + # this test will fail currently because there is no E2E test working for visanet + # assert_success refund + # assert_equal "OK", refund.message + end + + def test_failed_refund + response = @gateway.refund(@amount, '900000044') + assert_failure response + assert_match(/NUMORDEN 900000044 no se encuentra registrado/, response.message) + assert_equal 400, response.error_code + end + + def test_successful_void + response = @gateway.authorize(@amount, @credit_card, @options) + assert_success response + + void = @gateway.void(response.authorization) + assert_success void + assert_equal 'OK', void.message + end + + def test_failed_void + response = @gateway.void('900000044') + assert_failure response + assert_match(/NUMORDEN no se encuentra registrado/, response.message) + assert_equal 400, response.error_code + end + + def test_successful_verify + response = @gateway.verify(@credit_card, @options) + assert_success response + assert_equal 'OK', response.message + assert_equal @options[:order_id], response.params['externalTransactionId'] + end + + def test_failed_verify + response = @gateway.verify(@declined_card, @options) + assert_failure response + assert_equal 400, response.error_code + assert_equal 'Operacion Denegada.', response.message + end + + def test_transcript_scrubbing + transcript = capture_transcript(@gateway) do + @gateway.authorize(@amount, @credit_card, @options) + end + clean_transcript = @gateway.scrub(transcript) + + assert_scrubbed(@credit_card.number, clean_transcript) + assert_scrubbed(@credit_card.verification_value.to_s, clean_transcript) + assert_scrubbed(@gateway.options[:secret_access_key], clean_transcript) + end +end diff --git a/test/remote/gateways/remote_webpay_test.rb b/test/remote/gateways/remote_webpay_test.rb index 3aa0edf27ea..3b0e099ea33 100644 --- a/test/remote/gateways/remote_webpay_test.rb +++ b/test/remote/gateways/remote_webpay_test.rb @@ -1,4 +1,5 @@ # coding: utf-8 + require 'test_helper' class RemoteWebpayTest < Test::Unit::TestCase @@ -21,31 +22,49 @@ def setup def test_successful_purchase assert response = @gateway.purchase(@amount, @credit_card, @options) assert_success response - assert_equal "charge", response.params["object"] - assert response.params["paid"] + assert_equal 'charge', response.params['object'] + assert response.params['paid'] end def test_appropriate_purchase_amount assert response = @gateway.purchase(@amount, @credit_card, @options) assert_success response - assert_equal @amount / 100, response.params["amount"] + assert_equal @amount / 100, response.params['amount'] end def test_purchase_description - assert response = @gateway.purchase(@amount, @credit_card, { :description => "TheDescription", :email => "email@example.com" }) - assert_equal "TheDescription", response.params["description"], "Use the description if it's specified." + assert response = @gateway.purchase(@amount, @credit_card, { :description => 'TheDescription', :email => 'email@example.com' }) + assert_equal 'TheDescription', response.params['description'], "Use the description if it's specified." - assert response = @gateway.purchase(@amount, @credit_card, { :email => "email@example.com" }) - assert_equal "email@example.com", response.params["description"], "Use the email if no description is specified." + assert response = @gateway.purchase(@amount, @credit_card, { :email => 'email@example.com' }) + assert_equal 'email@example.com', response.params['description'], 'Use the email if no description is specified.' assert response = @gateway.purchase(@amount, @credit_card, { }) - assert_nil response.params["description"], "No description or email specified." + assert_nil response.params['description'], 'No description or email specified.' end def test_unsuccessful_purchase assert response = @gateway.purchase(@amount, @declined_card, @options) assert_failure response - assert_equal 'Your card number is incorrect', response.message + assert_equal 'The card number is invalid. Make sure the number entered matches your credit card.', response.message + end + + def test_authorization_and_capture + assert authorization = @gateway.authorize(@amount, @credit_card, @options) + assert_success authorization + assert !authorization.params['captured'] + + assert capture = @gateway.capture(@amount, authorization.authorization) + assert_success capture + end + + def test_authorization_and_void + assert authorization = @gateway.authorize(@amount, @credit_card, @options) + assert_success authorization + assert !authorization.params['captured'] + + assert void = @gateway.void(authorization.authorization) + assert_success void end def test_successful_void @@ -57,9 +76,9 @@ def test_successful_void end def test_unsuccessful_void - assert void = @gateway.void("active_merchant_fake_charge") + assert void = @gateway.void('active_merchant_fake_charge') assert_failure void - assert_match 'No such charge: active_merchant_fake_charge', void.message + assert_match 'No such charge', void.message end def test_successful_refund @@ -76,44 +95,44 @@ def test_appropriate_refund_amount assert response.authorization assert void = @gateway.refund(@refund_amount, response.authorization) assert_success void - assert_equal @refund_amount / 100, void.params["amount_refunded"] + assert_equal @refund_amount / 100, void.params['amount_refunded'] end def test_unsuccessful_refund - assert refund = @gateway.refund(@amount, "active_merchant_fake_charge") + assert refund = @gateway.refund(@amount, 'active_merchant_fake_charge') assert_failure refund - assert_match 'No such charge: active_merchant_fake_charge', refund.message + assert_match 'No such charge', refund.message end def test_successful_store - assert response = @gateway.store(@credit_card, {:description => "Active Merchant Test Customer", :email => "email@example.com"}) + assert response = @gateway.store(@credit_card, {:description => 'Active Merchant Test Customer', :email => 'email@example.com'}) assert_success response - assert_equal "customer", response.params["object"] - assert_equal "Active Merchant Test Customer", response.params["description"] - assert_equal "email@example.com", response.params["email"] - assert_equal @credit_card.last_digits, response.params["active_card"]["last4"] + assert_equal 'customer', response.params['object'] + assert_equal 'Active Merchant Test Customer', response.params['description'] + assert_equal 'email@example.com', response.params['email'] + assert_equal @credit_card.last_digits, response.params['active_card']['last4'] end def test_successful_update - creation = @gateway.store(@credit_card, {:description => "Active Merchant Update Customer"}) + creation = @gateway.store(@credit_card, {:description => 'Active Merchant Update Customer'}) assert response = @gateway.update(creation.params['id'], @new_credit_card) assert_success response - assert_equal "Active Merchant Update Customer", response.params["description"] - assert_equal @new_credit_card.last_digits, response.params["active_card"]["last4"] + assert_equal 'Active Merchant Update Customer', response.params['description'] + assert_equal @new_credit_card.last_digits, response.params['active_card']['last4'] end def test_successful_unstore - creation = @gateway.store(@credit_card, {:description => "Active Merchant Unstore Customer"}) + creation = @gateway.store(@credit_card, {:description => 'Active Merchant Unstore Customer'}) assert response = @gateway.unstore(creation.params['id']) assert_success response - assert_equal true, response.params["deleted"] + assert_equal true, response.params['deleted'] end def test_invalid_login gateway = WebpayGateway.new(:login => 'active_merchant_test') assert response = gateway.purchase(@amount, @credit_card, @options) assert_failure response - assert_equal "Invalid API key provided. Check your API key is correct.", response.message + assert_equal 'Invalid API key provided. Check your API key is correct.', response.message end end diff --git a/test/remote/gateways/remote_wepay_test.rb b/test/remote/gateways/remote_wepay_test.rb new file mode 100644 index 00000000000..4a1b01f061d --- /dev/null +++ b/test/remote/gateways/remote_wepay_test.rb @@ -0,0 +1,212 @@ +require 'test_helper' + +class RemoteWepayTest < Test::Unit::TestCase + def setup + @gateway = WepayGateway.new(fixtures(:wepay)) + + @amount = 2000 + @credit_card = credit_card('5496198584584769', verification_value: '321') + @credit_card_without_cvv = credit_card('5496198584584769', verification_value: nil) + + @declined_card = credit_card('') + + @options = { + billing_address: address, + email: 'test@example.com' + } + end + + def test_successful_purchase + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal 'Success', response.message + end + + def test_failed_purchase + response = @gateway.purchase(@amount, @declined_card, @options) + assert_failure response + end + + def test_successful_purchase_with_token + store = @gateway.store(@credit_card, @options) + assert_success store + + response = @gateway.purchase(@amount, store.authorization, @options) + assert_success response + end + + def test_successful_purchase_with_recurring_and_ip + store = @gateway.store(@credit_card, @options.merge(recurring: true, ip: '127.0.0.1')) + assert_success store + + response = @gateway.purchase(@amount, store.authorization, @options) + assert_success response + end + + def test_successful_purchase_sans_cvv + @options[:recurring] = true + store = @gateway.store(@credit_card, @options) + assert_success store + + response = @gateway.purchase(@amount, store.authorization, @options) + assert_success response + end + + def test_successful_purchase_with_few_options + options = { address: { zip: '27701' }, email: 'test@example.com' } + response = @gateway.purchase(@amount, @credit_card, options) + assert_success response + assert_equal 'Success', response.message + end + + def test_failed_purchase_sans_ccv + @credit_card.verification_value = nil + response = @gateway.purchase(@amount, @credit_card, @options) + assert_failure response + end + + def test_failed_purchase_with_token + response = @gateway.purchase(@amount, '12345', @options) + assert_failure response + end + + def test_successful_purchase_with_fee + response = @gateway.purchase(@amount, @credit_card, @options.merge(application_fee: 3, fee_payer: 'payee')) + assert_success response + assert_equal 'Success', response.message + end + + def test_successful_purchase_with_unique_id + response = @gateway.purchase(@amount, @credit_card, @options.merge(unique_id: generate_unique_id)) + assert_success response + assert_equal 'Success', response.message + end + + def test_successful_purchase_with_ip_and_risk_token + response = @gateway.purchase(@amount, @credit_card, @options.merge(ip: '100.166.99.123', risk_token: '123e4567-e89b-12d3-a456-426655440000')) + assert_success response + assert_equal 'Success', response.message + end + + def test_successful_authorize + response = @gateway.authorize(@amount, @credit_card, @options) + assert_success response + assert_equal 'Success', response.message + end + + def test_failed_authorize + response = @gateway.authorize(@amount, @declined_card, @options) + assert_failure response + end + + def test_successful_store_via_create_with_cvv + # POST /credit_card/create + response = @gateway.store(@credit_card, @options) + assert_success response + end + + def test_successful_store_via_transfer_without_cvv + # special permission required + # POST /credit_card/transfer + response = @gateway.store(@credit_card_without_cvv, @options.merge(recurring: true)) + assert_success response + end + + def test_unsuccessful_store_via_create_with_cvv + response = @gateway.store(@credit_card_without_cvv, @options) + + assert_failure response + assert_equal('This app does not have permissions to create credit cards without a cvv', response.message) + end + + # # Requires commenting out `unless options[:recurring]` when building post hash in `store` method. + # def test_unsuccessful_store_via_transfer_with_cvv + # response = @gateway.store(@credit_card, @options.merge(recurring: true)) + # + # assert_failure response + # assert_equal('cvv parameter is unexpected', response.message) + # end + + def test_successful_store_with_defaulted_email + response = @gateway.store(@credit_card, {billing_address: address}) + assert_success response + end + + def test_failed_store + response = @gateway.store(@declined_card, @options) + assert_failure response + end + + def test_successful_refund + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + + sleep 30 # Wait for purchase to clear. Doesn't always work. + response = @gateway.refund(@amount - 100, purchase.authorization) + assert_success response + end + + def test_successful_full_refund + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + + sleep 30 # Wait for purchase to clear. Doesn't always work. + response = @gateway.refund(@amount, purchase.authorization) + assert_success response + end + + def test_failed_capture + response = @gateway.capture(nil, '123') + assert_failure response + end + + def test_failed_void + response = @gateway.void('123') + assert_failure response + end + + def test_authorize_and_capture + authorize = @gateway.authorize(@amount, @credit_card, @options) + assert_success authorize + + sleep 30 # Wait for authorization to clear. Doesn't always work. + assert capture = @gateway.capture(nil, authorize.authorization) + assert_success capture + end + + def test_authorize_and_void + authorize = @gateway.authorize(@amount, @credit_card, @options) + assert_success authorize + + void = @gateway.void(authorize.authorization, cancel_reason: 'Cancel') + assert_success void + end + + # Version sent here will need to match or be one ahead of the version set in the test account's dashboard + def test_successful_purchase_with_version + response = @gateway.purchase(@amount, @credit_card, @options.merge(version: '2017-05-31')) + assert_success response + assert_equal 'Success', response.message + end + + def test_invalid_login + gateway = WepayGateway.new( + client_id: 12515, + account_id: 'abc', + access_token: 'def' + ) + response = gateway.purchase(@amount, @credit_card, @options) + assert_failure response + end + + def test_transcript_scrubbing + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @credit_card, @options) + end + transcript = @gateway.scrub(transcript) + + assert_scrubbed(@credit_card.number, transcript) + assert_scrubbed(@credit_card.verification_value, transcript) + assert_scrubbed(@gateway.options[:access_token], transcript) + end +end diff --git a/test/remote/gateways/remote_wirecard_test.rb b/test/remote/gateways/remote_wirecard_test.rb index 0499974350e..586e1567351 100644 --- a/test/remote/gateways/remote_wirecard_test.rb +++ b/test/remote/gateways/remote_wirecard_test.rb @@ -1,32 +1,35 @@ +# encoding: UTF-8 + require 'test_helper' class RemoteWirecardTest < Test::Unit::TestCase def setup test_account = fixtures(:wirecard) - test_account[:signature] = test_account[:login] @gateway = WirecardGateway.new(test_account) @amount = 100 @credit_card = credit_card('4200000000000000') @declined_card = credit_card('4000300011112220') + @amex_card = credit_card('370000000000010', brand: 'american_express') @options = { - :order_id => 1, - :billing_address => address, - :description => 'Wirecard remote test purchase', - :email => 'soleone@example.com' + order_id: 1, + billing_address: address, + description: 'Wirecard remote test purchase', + email: 'soleone@example.com', + ip: '127.0.0.1' } @german_address = { - :name => 'Jim Deutsch', - :address1 => '1234 Meine Street', - :company => 'Widgets Inc', - :city => 'Koblenz', - :state => 'Rheinland-Pfalz', - :zip => '56070', - :country => 'DE', - :phone => '0261 12345 23', - :fax => '0261 12345 23-4' + name: 'Jim Deutsch', + address1: '1234 Meine Street', + company: 'Widgets Inc', + city: 'Koblenz', + state: 'Rheinland-Pfalz', + zip: '56070', + country: 'DE', + phone: '0261 12345 23', + fax: '0261 12345 23-4' } end @@ -43,7 +46,7 @@ def test_successful_authorize_and_capture amount = @amount assert auth = @gateway.authorize(amount, @credit_card, @options) assert_success auth - assert auth.message[/THIS IS A DEMO/] + assert_match %r{THIS IS A DEMO}, auth.message assert auth.authorization assert capture = @gateway.capture(amount, auth.authorization, @options) assert_success capture @@ -52,42 +55,152 @@ def test_successful_authorize_and_capture def test_successful_authorize_and_partial_capture assert auth = @gateway.authorize(@amount, @credit_card, @options) assert_success auth - assert auth.message[/THIS IS A DEMO/] + assert_match %r{THIS IS A DEMO}, auth.message assert auth.authorization - #Capture some of the authorized amount + # Capture some of the authorized amount assert capture = @gateway.capture(@amount - 10, auth.authorization, @options) assert_success capture end + def test_successful_void + assert response = @gateway.authorize(@amount, @credit_card, @options) + assert_success response + assert response.authorization + + assert void = @gateway.void(response.authorization) + assert_success void + assert_match %r{THIS IS A DEMO}, void.message + end + + def test_successful_refund + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert response.authorization + + assert refund = @gateway.refund(@amount - 20, response.authorization) + assert_success refund + assert_match %r{THIS IS A DEMO}, refund.message + end + def test_successful_purchase assert response = @gateway.purchase(@amount, @credit_card, @options) - # puts response.message assert_success response - assert response.message[/THIS IS A DEMO/] + assert_match %r{THIS IS A DEMO}, response.message + end + + def test_successful_purchase_with_commerce_type + assert response = @gateway.purchase(@amount, @credit_card, @options.merge(commerce_type: 'MOTO')) + assert_success response + assert_match %r{THIS IS A DEMO}, response.message + end + + def test_successful_reference_purchase + assert purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + assert purchase.authorization + + assert reference_purchase = @gateway.purchase(@amount, purchase.authorization) + assert_success reference_purchase + assert_match %r{THIS IS A DEMO}, reference_purchase.message + end + + def test_utf8_description_does_not_blow_up + assert response = @gateway.purchase(@amount, @credit_card, @options.merge(description: 'Habitación')) + assert_success response + assert_match %r{THIS IS A DEMO}, response.message end def test_successful_purchase_with_german_address_german_state_and_german_phone - assert response = @gateway.purchase(@amount, @credit_card, @options.merge(:billing_address => @german_address)) + assert response = @gateway.purchase(@amount, @credit_card, @options.merge(billing_address: @german_address)) assert_success response assert response.message[/THIS IS A DEMO/] end def test_successful_purchase_with_german_address_no_state_and_invalid_phone - assert response = @gateway.purchase(@amount, @credit_card, @options.merge(:billing_address => @german_address.merge({:state => nil, :phone => '1234'}))) + assert response = @gateway.purchase(@amount, @credit_card, @options.merge(billing_address: @german_address.merge({state: nil, phone: '1234'}))) assert_success response assert response.message[/THIS IS A DEMO/] end def test_successful_purchase_with_german_address_and_valid_phone - assert response = @gateway.purchase(@amount, @credit_card, @options.merge(:billing_address => @german_address.merge({:phone => '+049-261-1234-123'}))) + assert response = @gateway.purchase(@amount, @credit_card, @options.merge(billing_address: @german_address.merge({phone: '+049-261-1234-123'}))) assert_success response assert response.message[/THIS IS A DEMO/] end + def test_successful_cvv_result + @credit_card.verification_value = '666' # Magic Value = "Matched (correct) CVC-2" + assert response = @gateway.purchase(@amount, @credit_card, @options) + + assert_success response + assert_equal 'M', response.cvv_result['code'] + end + + def test_successful_visa_avs_result + # Magic Wirecard address to return an AVS 'M' result + m_address = { + address1: '99 DERRY STREET', + state: 'London', + zip: 'W8 5TE', + country: 'GB' + } + + assert response = @gateway.purchase(@amount, @credit_card, @options.merge(billing_address: m_address)) + + assert_success response + assert_equal 'M', response.avs_result['code'] + end + + def test_successful_amex_avs_result + a_address = { + address1: '10 Edward Street', + state: 'London', + zip: 'BN66 6AB', + country: 'GB' + } + + assert response = @gateway.purchase(@amount, @amex_card, @options.merge(billing_address: a_address)) + + assert_success response + assert_equal 'U', response.avs_result['code'] + end + + def test_successful_store + assert response = @gateway.store(@credit_card) + assert_success response + assert response.authorization + end + + def test_successful_store_with_amex + assert response = @gateway.store(@amex_card) + assert_success response + assert response.authorization + end + + def test_successful_store_then_purchase_by_reference + assert auth = @gateway.store(@credit_card, @options.dup) + assert_success auth + assert auth.authorization + assert purchase = @gateway.purchase(@amount, auth.authorization, @options.dup) + assert_success purchase + end + + def test_successful_authorization_as_recurring_transaction_type_initial + assert response = @gateway.authorize(@amount, @credit_card, @options.merge(:recurring => 'Initial')) + assert_success response + assert response.authorization + end + + def test_successful_purchase_as_recurring_transaction_type_initial + assert response = @gateway.purchase(@amount, @credit_card, @options.merge(:recurring => 'Initial')) + assert_success response + assert response.authorization + end + # Failure tested def test_wrong_creditcard_authorization @@ -101,19 +214,55 @@ def test_wrong_creditcard_purchase assert response = @gateway.purchase(@amount, @declined_card, @options) assert response.test? assert_failure response - assert response.message[ /Credit card number not allowed in demo mode/ ], "Got wrong response message" + assert response.message[/Credit card number not allowed in demo mode/], 'Got wrong response message' + assert_equal '24997', response.params['ErrorCode'] + end + + def test_wrong_creditcard_store + assert response = @gateway.store(@declined_card, @options) + assert response.test? + assert_failure response + assert response.message[/Credit card number not allowed in demo mode/], 'Got wrong response message' end def test_unauthorized_capture - assert response = @gateway.capture(@amount, "1234567890123456789012") + assert response = @gateway.capture(@amount, '1234567890123456789012') + assert_failure response + assert_equal 'Could not find referenced transaction for GuWID 1234567890123456789012.', response.message + end + + def test_failed_refund + assert refund = @gateway.refund(@amount - 20, 'C428094138244444404448') + assert_failure refund + assert_match %r{Could not find referenced transaction}, refund.message + end + + def test_failed_void + assert void = @gateway.void('C428094138244444404448') + assert_failure void + assert_match %r{Could not find referenced transaction}, void.message + end + + def test_unauthorized_purchase + assert response = @gateway.purchase(@amount, '1234567890123456789012') assert_failure response - assert_equal "Could not find referenced transaction for GuWID 1234567890123456789012.", response.message + assert_equal 'Could not find referenced transaction for GuWID 1234567890123456789012.', response.message end def test_invalid_login - gateway = WirecardGateway.new(:login => '', :password => '', :signature => '') + gateway = WirecardGateway.new(login: '', password: '', signature: '') assert response = gateway.purchase(@amount, @credit_card, @options) assert_failure response - assert_equal "Invalid Login", response.message + assert_equal 'Invalid Login', response.message + end + + def test_transcript_scrubbing + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @credit_card, @options) + end + clean_transcript = @gateway.scrub(transcript) + + assert_scrubbed(@credit_card.number, clean_transcript) + assert_scrubbed(@credit_card.verification_value.to_s, clean_transcript) end end diff --git a/test/remote/gateways/remote_world_net_test.rb b/test/remote/gateways/remote_world_net_test.rb new file mode 100644 index 00000000000..67d198f1fbf --- /dev/null +++ b/test/remote/gateways/remote_world_net_test.rb @@ -0,0 +1,189 @@ +require 'test_helper' + +class RemoteWorldNetTest < Test::Unit::TestCase + def setup + @gateway = WorldNetGateway.new(fixtures(:world_net)) + + @amount = 100 + @declined_amount = 101 + @credit_card = credit_card('3779810000000005') + @options = { + order_id: generate_order_id, + } + @refund_options = { + operator: 'mr.nobody', + reason: 'returned' + } + end + + def test_successful_purchase + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal 'APPROVAL', response.message + end + + def test_successful_purchase_with_more_options + options = { + order_id: generate_order_id, + email: 'joe@example.com', + billing_address: address, + description: 'Store Purchase', + ip: '127.0.0.1', + } + + response = @gateway.purchase(@amount, @credit_card, options) + assert_success response + assert_equal 'APPROVAL', response.message + end + + def test_failed_purchase + response = @gateway.purchase(@declined_amount, @credit_card, @options) + assert_failure response + assert_equal 'DECLINED', response.message + assert_equal Gateway::STANDARD_ERROR_CODE[:card_declined], response.error_code + + response = @gateway.purchase(103, @credit_card, @options) + assert_failure response + assert_equal 'CVV FAILURE', response.message + assert_equal Gateway::STANDARD_ERROR_CODE[:incorrect_cvc], response.error_code + + response = @gateway.purchase(@amount, credit_card('3779810000000005', month: '13'), @options) + assert_failure response + assert_equal 'Invalid CARDEXPIRY field', response.message + assert_equal Gateway::STANDARD_ERROR_CODE[:invalid_expiry_date], response.error_code + end + + def test_successful_authorize_and_capture + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + assert capture = @gateway.capture(@amount, auth.authorization) + assert_success capture + assert_equal 'APPROVAL', capture.message + end + + def test_failed_authorize + response = @gateway.authorize(@declined_amount, @credit_card, @options) + assert_failure response + assert_equal 'DECLINED', response.message + end + + def test_partial_capture + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + assert capture = @gateway.capture(@amount-1, auth.authorization) + assert_success capture + end + + def test_failed_capture + response = @gateway.capture(@amount, '') + assert_failure response + assert_match %r{not facet-valid with respect to minLength}, response.message + end + + def test_successful_refund + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + + assert refund = @gateway.refund(@amount, purchase.authorization, @refund_options) + assert_success refund + assert_equal 'SUCCESS', refund.message + end + + def test_partial_refund + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + + assert refund = @gateway.refund(@amount-1, purchase.authorization, @refund_options) + assert_success refund + end + + def test_failed_refund + response = @gateway.refund(@amount, '', @refund_options) + assert_failure response + assert_match %r{not facet-valid with respect to minLength}, response.message + end + + def test_successful_void + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + assert @gateway.void(auth.authorization) + # UNSUPPORTED + # assert_success void + # assert_equal 'REPLACE WITH SUCCESSFUL VOID MESSAGE', response.message + end + + def test_failed_void + response = @gateway.void('') + assert_failure response + assert_match %r{Cannot find the declaration of element}, response.message + end + + def test_invalid_login + gateway = WorldNetGateway.new(terminal_id: '', secret: '') + + response = gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_match %r{not facet-valid with respect to minLength}, response.message + end + + def test_successful_store + store = @gateway.store(@credit_card, @options) + assert_success store + end + + def test_unsuccessful_store + store = @gateway.store(credit_card('3779810000000005', month: '13'), @options) + assert_failure store + end + + def test_successful_unstore + response = @gateway.store(@credit_card, @options) + assert_success response + assert_equal nil, response.message + card_reference = response.authorization + + assert response = @gateway.unstore(card_reference, @options) + assert_success response + + assert response = @gateway.purchase(@amount, card_reference, @options) + assert_failure response + end + + def test_unsuccessful_unstore + response = @gateway.store(@credit_card, @options) + assert_success response + assert_equal nil, response.message + + assert response = @gateway.unstore('123456789', @options) + assert_failure response + end + + def test_purchase_with_stored_card + response = @gateway.store(@credit_card, @options) + assert_success response + assert_equal nil, response.message + card_reference = response.authorization + + assert response = @gateway.purchase(@amount, card_reference, @options) + assert_success response + assert_equal 'APPROVAL', response.message + end + + def test_transcript_scrubbing + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @credit_card, @options) + end + transcript = @gateway.scrub(transcript) + + assert_scrubbed(@credit_card.number, transcript) + assert_scrubbed(@credit_card.verification_value, transcript) + assert_scrubbed(@gateway.options[:secret], transcript) + end + + def generate_order_id + (Time.now.to_f * 100).to_i.to_s + end +end diff --git a/test/remote/gateways/remote_worldpay_online_payments_test.rb b/test/remote/gateways/remote_worldpay_online_payments_test.rb new file mode 100644 index 00000000000..4f0b2ff05c2 --- /dev/null +++ b/test/remote/gateways/remote_worldpay_online_payments_test.rb @@ -0,0 +1,163 @@ +require 'test_helper' + +class RemoteWorldpayOnlinePaymentsTest < Test::Unit::TestCase + def setup + @gateway = WorldpayOnlinePaymentsGateway.new(fixtures(:worldpay_online_payments)) + + @amount = 1000 + @credit_card = credit_card('4444333322221111') + @declined_card = credit_card('2424242424242424') + + @options = { + order_id: '1', + currency: 'GBP', + billing_address: address, + description: 'Store Purchase' + } + end + + def test_successful_purchase + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal 'SUCCESS', response.message + end + + def test_failed_purchase + response = @gateway.purchase(@amount, @declined_card, @options) + assert_failure response + assert_not_equal 'SUCCESS', response.message + end + + def test_failed_card_purchase + @options[:billing_address][:name] = 'FAILED' + response = @gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_not_equal 'SUCCESS', response.message + end + + def test_error_card_purchase + @options[:billing_address][:name] = 'ERROR' + response = @gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_not_equal 'SUCCESS', response.message + end + + def test_successful_authorize_and_capture + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + assert capture = @gateway.capture(@amount, auth.authorization) + assert_success capture + end + + def test_failed_authorize_and_capture + auth = @gateway.authorize(@amount, @declined_card, @options) + assert_failure auth + + assert capture = @gateway.capture(@amount, auth.authorization) + assert_not_equal 'SUCCESS', capture.message + end + + def test_failed_authorize + response = @gateway.authorize(@amount, @declined_card, @options) + assert_failure response + end + + def test_partial_capture + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + assert capture = @gateway.capture(@amount-1, auth.authorization) + assert_success capture + end + + def test_failed_capture + response = @gateway.capture(nil, '') + assert_failure response + end + + def test_successful_refund + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + + assert refund = @gateway.refund(nil, purchase.authorization) + assert_success refund + end + + def test_failed_refund + response = @gateway.refund(nil, '') + assert_failure response + end + + def test_failed_double_refund + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + + assert refund = @gateway.refund(nil, purchase.authorization) + assert_success refund + + assert refund = @gateway.refund(nil, purchase.authorization) + assert_failure refund + end + + def test_failed_partial_refund + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + + refund = @gateway.refund(@amount+1, purchase.authorization) + assert_failure refund + end + + def test_successful_void + authorize = @gateway.authorize(@amount, @credit_card, @options) + assert_success authorize + + void = @gateway.void(authorize.authorization) + assert_success void + end + + def test_successful_order_void + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + + void = @gateway.void(purchase.authorization) + assert_success void + end + + def test_failed_void + void = @gateway.void('InvalidOrderCode') + assert_failure void + end + + def test_failed_double_void + authorize = @gateway.authorize(@amount, @credit_card, @options) + assert_success authorize + + void = @gateway.void(authorize.authorization) + assert_success void + + void = @gateway.void(authorize.authorization) + assert_failure void + end + + def test_successful_verify + response = @gateway.verify(@credit_card, @options) + assert_success response + assert_match %r{SUCCESS}, response.message + end + + def test_failed_verify + response = @gateway.verify(@declined_card, @options) + assert_failure response + assert_not_match %r{SUCCESS}, response.message + end + + def test_invalid_login + badgateway = WorldpayOnlinePaymentsGateway.new( + client_key: 'T_C_NOT_VALID', + service_key: 'T_S_NOT_VALID' + ) + response = badgateway.purchase(@amount, @credit_card, @options) + assert_failure response + end +end diff --git a/test/remote/gateways/remote_worldpay_test.rb b/test/remote/gateways/remote_worldpay_test.rb index 2b5ee8d98cb..c93542ce7bd 100644 --- a/test/remote/gateways/remote_worldpay_test.rb +++ b/test/remote/gateways/remote_worldpay_test.rb @@ -4,12 +4,31 @@ class RemoteWorldpayTest < Test::Unit::TestCase def setup @gateway = WorldpayGateway.new(fixtures(:world_pay_gateway)) + @cftgateway = WorldpayGateway.new(fixtures(:world_pay_gateway_cft)) @amount = 100 @credit_card = credit_card('4111111111111111') + @elo_credit_card = credit_card('4514 1600 0000 0008', + :month => 10, + :year => 2020, + :first_name => 'John', + :last_name => 'Smith', + :verification_value => '737', + :brand => 'elo' + ) + @sodexo_voucher = credit_card('6060704495764400', brand: 'sodexo') @declined_card = credit_card('4111111111111111', :first_name => nil, :last_name => 'REFUSED') + @threeDS_card = credit_card('4111111111111111', :first_name => nil, :last_name => '3D') + @threeDS_card_external_MPI = credit_card('4444333322221111', :first_name => 'AA', :last_name => 'BD') - @options = {:order_id => generate_unique_id} + @options = { + order_id: generate_unique_id, + email: 'wow@example.com' + } + @store_options = { + customer: generate_unique_id, + email: 'wow@example.com' + } end def test_successful_purchase @@ -18,9 +37,37 @@ def test_successful_purchase assert_equal 'SUCCESS', response.message end + def test_successful_purchase_with_elo + assert response = @gateway.purchase(@amount, @elo_credit_card, @options.merge(currency: 'BRL')) + assert_success response + assert_equal 'SUCCESS', response.message + end + + def test_successful_authorize_avs_and_cvv + card = credit_card('4111111111111111', :verification_value => 555) + assert response = @gateway.authorize(@amount, card, @options.merge(billing_address: address.update(zip: 'CCCC'))) + assert_success response + assert_equal 'SUCCESS', response.message + assert_match %r{Street address does not match, but 5-digit postal code matches}, response.avs_result['message'] + assert_match %r{CVV matches}, response.cvv_result['message'] + end + + def test_successful_purchase_with_hcg_additional_data + @options[:hcg_additional_data] = { + key1: 'value1', + key2: 'value2', + key3: 'value3' + } + + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal 'SUCCESS', response.message + end + def test_failed_purchase assert response = @gateway.purchase(@amount, @declined_card, @options) assert_failure response + assert_equal '5', response.error_code assert_equal 'REFUSED', response.message end @@ -29,10 +76,257 @@ def test_authorize_and_capture assert_success auth assert_equal 'SUCCESS', auth.message assert auth.authorization - assert capture = @gateway.capture(@amount, auth.authorization) + + assert capture = @gateway.capture(@amount, auth.authorization, authorization_validated: true) assert_success capture end + def test_authorize_and_capture_by_reference + assert auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + assert_equal 'SUCCESS', auth.message + + assert capture = @gateway.capture(@amount, auth.authorization, authorization_validated: true) + assert_success capture + assert reference = auth.authorization + @options[:order_id] = generate_unique_id + + assert auth = @gateway.authorize(@amount, reference, @options) + assert capture = @gateway.capture(@amount, auth.authorization, authorization_validated: true) + assert_success capture + end + + def test_authorize_and_purchase_by_reference + assert auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + assert_equal 'SUCCESS', auth.message + + assert capture = @gateway.capture(@amount, auth.authorization, authorization_validated: true) + assert_success capture + assert reference = auth.authorization + + @options[:order_id] = generate_unique_id + assert auth = @gateway.authorize(@amount, reference, @options) + + @options[:order_id] = generate_unique_id + assert capture = @gateway.purchase(@amount, auth.authorization, @options) + assert_success capture + end + + def test_authorize_and_purchase_with_instalments + assert auth = @gateway.authorize(@amount, @credit_card, @options.merge(instalment: 3)) + assert_success auth + assert_equal 'SUCCESS', auth.message + assert auth.authorization + + assert capture = @gateway.capture(@amount, auth.authorization, authorization_validated: true) + assert_success capture + end + + def test_successful_authorize_with_3ds + session_id = generate_unique_id + options = @options.merge( + { + execute_threed: true, + accept_header: 'text/html', + user_agent: 'Mozilla/5.0', + session_id: session_id, + ip: '127.0.0.1', + cookie: 'machine=32423423' + }) + assert first_message = @gateway.authorize(@amount, @threeDS_card, options) + assert_equal "A transaction status of 'AUTHORISED' is required.", first_message.message + assert first_message.test? + refute first_message.authorization.blank? + refute first_message.params['issuer_url'].blank? + refute first_message.params['pa_request'].blank? + refute first_message.params['cookie'].blank? + refute first_message.params['session_id'].blank? + end + + def test_successful_auth_and_capture_with_normalized_stored_credential + stored_credential_params = { + initial_transaction: true, + reason_type: 'unscheduled', + initiator: 'merchant', + network_transaction_id: nil + } + + assert auth = @gateway.authorize(@amount, @credit_card, @options.merge({stored_credential: stored_credential_params})) + assert_success auth + assert auth.authorization + assert auth.params['scheme_response'] + assert auth.params['transaction_identifier'] + + assert capture = @gateway.capture(@amount, auth.authorization, authorization_validated: true) + assert_success capture + + @options[:order_id] = generate_unique_id + @options[:stored_credential] = { + initial_transaction: false, + reason_type: 'installment', + initiator: 'merchant', + network_transaction_id: auth.params['transaction_identifier'] + } + + assert next_auth = @gateway.authorize(@amount, @credit_card, @options) + assert next_auth.authorization + assert next_auth.params['scheme_response'] + assert next_auth.params['transaction_identifier'] + + assert capture = @gateway.capture(@amount, next_auth.authorization, authorization_validated: true) + assert_success capture + end + + def test_successful_auth_and_capture_with_gateway_specific_stored_credentials + assert auth = @gateway.authorize(@amount, @credit_card, @options.merge(stored_credential_usage: 'FIRST')) + assert_success auth + assert auth.authorization + assert auth.params['scheme_response'] + assert auth.params['transaction_identifier'] + + assert capture = @gateway.capture(@amount, auth.authorization, authorization_validated: true) + assert_success capture + + options = @options.merge( + order_id: generate_unique_id, + stored_credential_usage: 'USED', + stored_credential_initiated_reason: 'UNSCHEDULED', + stored_credential_transaction_id: auth.params['transaction_identifier'] + ) + assert next_auth = @gateway.authorize(@amount, @credit_card, options) + assert next_auth.authorization + assert next_auth.params['scheme_response'] + assert next_auth.params['transaction_identifier'] + + assert capture = @gateway.capture(@amount, next_auth.authorization, authorization_validated: true) + assert_success capture + end + + def test_successful_authorize_with_3ds_with_normalized_stored_credentials + session_id = generate_unique_id + stored_credential_params = { + initial_transaction: true, + reason_type: 'unscheduled', + initiator: 'merchant', + network_transaction_id: nil + } + options = @options.merge( + { + execute_threed: true, + accept_header: 'text/html', + user_agent: 'Mozilla/5.0', + session_id: session_id, + ip: '127.0.0.1', + cookie: 'machine=32423423', + stored_credential: stored_credential_params + }) + assert first_message = @gateway.authorize(@amount, @threeDS_card, options) + assert_equal "A transaction status of 'AUTHORISED' is required.", first_message.message + assert first_message.test? + refute first_message.authorization.blank? + refute first_message.params['issuer_url'].blank? + refute first_message.params['pa_request'].blank? + refute first_message.params['cookie'].blank? + refute first_message.params['session_id'].blank? + end + + def test_successful_authorize_with_3ds_with_gateway_specific_stored_credentials + session_id = generate_unique_id + options = @options.merge( + { + execute_threed: true, + accept_header: 'text/html', + user_agent: 'Mozilla/5.0', + session_id: session_id, + ip: '127.0.0.1', + cookie: 'machine=32423423', + stored_credential_usage: 'FIRST' + }) + assert first_message = @gateway.authorize(@amount, @threeDS_card, options) + assert_equal "A transaction status of 'AUTHORISED' is required.", first_message.message + assert first_message.test? + refute first_message.authorization.blank? + refute first_message.params['issuer_url'].blank? + refute first_message.params['pa_request'].blank? + refute first_message.params['cookie'].blank? + refute first_message.params['session_id'].blank? + end + + # Fails currently because the sandbox doesn't actually validate the stored_credential options + # def test_failed_authorize_with_bad_stored_cred_options + # assert auth = @gateway.authorize(@amount, @credit_card, @options.merge(stored_credential_usage: 'FIRST')) + # assert_success auth + # assert auth.authorization + # assert auth.params['scheme_response'] + # assert auth.params['transaction_identifier'] + # + # assert capture = @gateway.capture(@amount, auth.authorization, authorization_validated: true) + # assert_success capture + # + # options = @options.merge( + # order_id: generate_unique_id, + # stored_credential_usage: 'MEH', + # stored_credential_initiated_reason: 'BLAH', + # stored_credential_transaction_id: 'nah' + # ) + # assert next_auth = @gateway.authorize(@amount, @credit_card, options) + # assert_failure next_auth + # end + + def test_failed_authorize_with_3ds + session_id = generate_unique_id + options = @options.merge( + { + execute_threed: true, + accept_header: 'text/html', + session_id: session_id, + ip: '127.0.0.1', + cookie: 'machine=32423423' + }) + assert first_message = @gateway.authorize(@amount, @threeDS_card, options) + assert_match %r{missing info for 3D-secure transaction}i, first_message.message + assert first_message.test? + assert first_message.params['issuer_url'].blank? + assert first_message.params['pa_request'].blank? + end + + def test_3ds_version_1_parameters_pass_thru + options = @options.merge( + { + three_d_secure: { + version: '1.0.2', + xid: '', + cavv: 'MAAAAAAAAAAAAAAAAAAAAAAAAAA=', + eci: '05' + } + } + ) + + assert response = @gateway.authorize(@amount, @threeDS_card_external_MPI, @options.merge(options)) + assert response.test? + assert response.success? + assert response.params['last_event'] || response.params['ok'] + end + + def test_3ds_version_2_parameters_pass_thru + options = @options.merge( + { + three_d_secure: { + version: '2.1.0', + xid: 'A' * 40, + cavv: 'MAAAAAAAAAAAAAAAAAAAAAAAAAA=', + eci: '05' + } + } + ) + + assert response = @gateway.authorize(@amount, @threeDS_card_external_MPI, @options.merge(options)) + assert response.test? + assert response.success? + assert response.params['last_event'] || response.params['ok'] + end + def test_failed_capture assert response = @gateway.capture(@amount, 'bogus') assert_failure response @@ -43,46 +337,56 @@ def test_billing_address assert_success @gateway.authorize(@amount, @credit_card, @options.merge(:billing_address => address)) end - def test_void - assert_success(response = @gateway.authorize(@amount, @credit_card, @options)) - assert_success (void = @gateway.void(response.authorization)) - assert_equal "SUCCESS", void.message - assert void.params["cancel_received_order_code"] + def test_partial_address + billing_address = address + billing_address.delete(:address1) + billing_address.delete(:zip) + billing_address.delete(:country) + assert_success @gateway.authorize(@amount, @credit_card, @options.merge(:billing_address => billing_address)) end - def test_void_nonexistent_transaction - assert_failure response = @gateway.void('non_existent_authorization') - assert_equal "Could not find payment for order", response.message + def test_ip_address + assert_success @gateway.authorize(@amount, @credit_card, @options.merge(ip: '192.18.123.12')) end - def test_currency - assert_success(result = @gateway.authorize(@amount, @credit_card, @options.merge(:currency => 'USD'))) - assert_equal "USD", result.params['amount_currency_code'] + def test_void + assert_success response = @gateway.authorize(@amount, @credit_card, @options) + assert_success void = @gateway.void(response.authorization, authorization_validated: true) + assert_equal 'SUCCESS', void.message + assert void.params['cancel_received_order_code'] end - def test_authorize_currency_without_fractional_units - assert_success(result = @gateway.authorize(1200, @credit_card, @options.merge(:currency => 'HUF'))) - assert_equal "HUF", result.params['amount_currency_code'] - assert_equal "12", result.params['amount_value'] + def test_void_with_elo + assert_success response = @gateway.authorize(@amount, @elo_credit_card, @options.merge(currency: 'BRL')) + assert_success void = @gateway.void(response.authorization, authorization_validated: true) + assert_equal 'SUCCESS', void.message + assert void.params['cancel_received_order_code'] end - def test_authorize_currency_without_fractional_units_and_fractions_in_amount - assert_success(result = @gateway.authorize(1234, @credit_card, @options.merge(:currency => 'HUF'))) - assert_equal "HUF", result.params['amount_currency_code'] - assert_equal "12", result.params['amount_value'] + def test_void_nonexistent_transaction + assert_failure response = @gateway.void('non_existent_authorization') + assert_equal 'Could not find payment for order', response.message end - def test_authorize_and_capture_currency_without_fractional_units_and_fractions_in_amount - assert_success(auth = @gateway.authorize(1234, @credit_card, @options.merge(:currency => 'HUF'))) - assert_equal "12", auth.params['amount_value'] + def test_authorize_fractional_currency + assert_success(result = @gateway.authorize(1234, @credit_card, @options.merge(:currency => 'USD'))) + assert_equal 'USD', result.params['amount_currency_code'] + assert_equal '1234', result.params['amount_value'] + assert_equal '2', result.params['amount_exponent'] + end - assert_success(result = @gateway.capture(1234, auth.authorization)) - assert_equal "12", result.params['amount_value'] + def test_authorize_nonfractional_currency + assert_success(result = @gateway.authorize(1234, @credit_card, @options.merge(:currency => 'IDR'))) + assert_equal 'IDR', result.params['amount_currency_code'] + assert_equal '12', result.params['amount_value'] + assert_equal '0', result.params['amount_exponent'] end - def test_purchase_currency_without_fractional_units_and_fractions_in_amount - assert_success(result = @gateway.purchase(1234, @credit_card, @options.merge(:currency => 'HUF'))) - assert_equal "12", result.params['amount_value'] + def test_authorize_three_decimal_currency + assert_success(result = @gateway.authorize(1234, @credit_card, @options.merge(:currency => 'OMR'))) + assert_equal 'OMR', result.params['amount_currency_code'] + assert_equal '1234', result.params['amount_value'] + assert_equal '3', result.params['amount_exponent'] end def test_reference_transaction @@ -103,30 +407,182 @@ def test_refund_fails_unless_status_is_captured assert refund = @gateway.refund(30, response.authorization) assert_failure refund - assert_equal "A transaction status of 'CAPTURED' or 'SETTLED' is required.", refund.message + assert_equal 'Order not ready', refund.message end def test_refund_nonexistent_transaction - assert_failure response = @gateway.refund(@amount, "non_existent_authorization") - assert_equal "Could not find payment for order", response.message + assert_failure response = @gateway.refund(@amount, 'non_existent_authorization') + assert_equal 'Could not find payment for order', response.message + end + + def test_successful_verify + response = @gateway.verify(@credit_card, @options) + assert_success response + assert_match %r{SUCCESS}, response.message end + def test_successful_verify_with_elo + response = @gateway.verify(@elo_credit_card, @options.merge(currency: 'BRL')) + assert_success response + assert_match %r{SUCCESS}, response.message + end - # Worldpay has a delay between asking for a transaction to be captured and actually marking it as captured - # These 2 tests work if you take the auth code, wait some time and then perform the next operation. + def test_failed_verify + response = @gateway.verify(@declined_card, @options) + assert_failure response + assert_match %r{REFUSED}, response.message + end - # def test_refund - # assert_success(response = @gateway.purchase(@amount, @credit_card, @options)) + def test_successful_visa_credit_on_cft_gateway + credit = @cftgateway.credit(@amount, @credit_card, @options) + assert_success credit + assert_equal 'SUCCESS', credit.message + end + + def test_successful_mastercard_credit_on_cft_gateway + cc = credit_card('5555555555554444') + credit = @cftgateway.credit(@amount, cc, @options) + assert_success credit + assert_equal 'SUCCESS', credit.message + end + + def test_transcript_scrubbing + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @credit_card, @options) + end + clean_transcript = @gateway.scrub(transcript) + + assert_scrubbed(@credit_card.number, clean_transcript) + assert_scrubbed(@credit_card.verification_value.to_s, clean_transcript) + end + + def test_failed_authorize_with_unknown_card + assert auth = @gateway.authorize(@amount, @sodexo_voucher, @options) + assert_failure auth + assert_equal '5', auth.error_code + assert_match %r{XML failed validation: Invalid payment details : Card number not recognised:}, auth.message + end + + def test_failed_purchase_with_unknown_card + assert response = @gateway.purchase(@amount, @sodexo_voucher, @options) + assert_failure response + assert_equal '5', response.error_code + assert_match %r{XML failed validation: Invalid payment details : Card number not recognised:}, response.message + end + + def test_failed_verify_with_unknown_card + response = @gateway.verify(@sodexo_voucher, @options) + assert_failure response + assert_equal '5', response.error_code + assert_match %r{XML failed validation: Invalid payment details : Card number not recognised:}, response.message + end + + # Worldpay has a delay between asking for a transaction to be captured and actually marking it as captured + # These 2 tests work if you get authorizations from a purchase, wait some time and then perform the refund/void operation. + # + # def test_get_authorization + # response = @gateway.purchase(@amount, @credit_card, @options) # assert response.authorization - # refund = @gateway.refund(@amount, capture.authorization) + # puts 'auth: ' + response.authorization + # end + # + # def test_refund + # refund = @gateway.refund(@amount, '39270fd70be13aab55f84e28be45cad3') # assert_success refund - # assert_equal "SUCCESS", refund.message + # assert_equal 'SUCCESS', refund.message # end - + # # def test_void_fails_unless_status_is_authorised - # response = @gateway.void("33d6dfa9726198d44a743488cf611d3b") # existing transaction in CAPTURED state + # response = @gateway.void('replace_with_authorization') # existing transaction in CAPTURED state # assert_failure response - # assert_equal "A transaction status of 'AUTHORISED' is required.", response.message + # assert_equal 'A transaction status of 'AUTHORISED' is required.', response.message # end + def test_successful_store + assert response = @gateway.store(@credit_card, @store_options) + assert_success response + assert_equal 'SUCCESS', response.message + assert_match response.params['payment_token_id'], response.authorization + assert_match 'shopper', response.authorization + assert_match @store_options[:customer], response.authorization + end + + def test_successful_authorize_using_token + assert store = @gateway.store(@credit_card, @store_options) + assert_success store + + assert response = @gateway.authorize(@amount, store.authorization, @options) + assert_success response + assert_equal 'SUCCESS', response.message + end + + def test_successful_authorize_using_token_and_minimum_options + assert store = @gateway.store(@credit_card, @store_options) + assert_success store + + assert response = @gateway.authorize(@amount, store.authorization, order_id: generate_unique_id) + assert_success response + assert_equal 'SUCCESS', response.message + end + + def test_successful_purchase_using_token + assert store = @gateway.store(@credit_card, @store_options) + assert_success store + + assert response = @gateway.authorize(@amount, store.authorization, @options) + assert_success response + assert_equal 'SUCCESS', response.message + end + + def test_successful_verify_using_token + assert store = @gateway.store(@credit_card, @store_options) + assert_success store + + response = @gateway.verify(store.authorization, @options) + assert_success response + assert_match %r{SUCCESS}, response.message + end + + def test_successful_credit_using_token + assert store = @cftgateway.store(@credit_card, @store_options) + assert_success store + + credit = @cftgateway.credit(@amount, store.authorization, @options) + assert_success credit + assert_equal 'SUCCESS', credit.message + end + + def test_failed_store + assert response = @gateway.store(@credit_card, @store_options.merge(customer: '_invalidId')) + assert_failure response + assert_equal '2', response.error_code + assert_equal 'authenticatedShopperID cannot start with an underscore', response.message + end + + def test_failed_authorize_using_token + assert store = @gateway.store(@declined_card, @store_options) + assert_success store + + assert response = @gateway.authorize(@amount, store.authorization, @options) + assert_failure response + assert_equal '5', response.error_code + assert_equal 'REFUSED', response.message + end + + def test_failed_authorize_using_bogus_token + assert response = @gateway.authorize(@amount, '|this|is|bogus', @options) + assert_failure response + assert_equal '2', response.error_code + assert_match 'tokenScope', response.message + end + + def test_failed_verify_using_token + assert store = @gateway.store(@declined_card, @store_options) + assert_success store + + response = @gateway.verify(store.authorization, @options) + assert_failure response + assert_equal '5', response.error_code + assert_match %r{REFUSED}, response.message + end end diff --git a/test/remote/gateways/remote_worldpay_us_test.rb b/test/remote/gateways/remote_worldpay_us_test.rb new file mode 100644 index 00000000000..a0e50945fa6 --- /dev/null +++ b/test/remote/gateways/remote_worldpay_us_test.rb @@ -0,0 +1,137 @@ +require 'test_helper' + +class RemoteWorldpayUsTest < Test::Unit::TestCase + def setup + @gateway = WorldpayUsGateway.new(fixtures(:worldpay_us)) + + @amount = 100 + @credit_card = credit_card('4446661234567892', :verification_value => '987') + @declined_card = credit_card('4000300011112220') + @check = check(:number => '12345654321') + + @options = { + order_id: generate_unique_id, + billing_address: address, + description: 'Store Purchase' + } + end + + def test_successful_purchase + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal 'Succeeded', response.message + end + + def test_successful_purchase_on_backup_url + gateway = WorldpayUsGateway.new(fixtures(:worldpay_us).merge({ use_backup_url: true})) + response = gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal 'Succeeded', response.message + end + + def test_failed_purchase + response = @gateway.purchase(@amount, @declined_card, @options) + assert_failure response + assert response.message =~ /DECLINED/ + end + + def test_successful_echeck_purchase + response = @gateway.purchase(@amount, @check, @options) + assert_equal 'Succeeded', response.message + assert_success response + end + + def test_failed_echeck_purchase + response = @gateway.purchase(@amount, check(routing_number: '23433'), @options) + assert_failure response + assert response.message =~ /DECLINED/ + end + + def test_successful_authorize_and_capture + assert response = @gateway.authorize(@amount, @credit_card, @options) + assert_success response + assert_equal 'Succeeded', response.message + assert_match %r(^\d+\|.+$), response.authorization + + assert capture = @gateway.capture(@amount, response.authorization) + assert_success capture + assert_equal 'Succeeded', capture.message + end + + def test_failed_authorize + assert response = @gateway.authorize(@amount, @declined_card, @options) + assert_failure response + assert response.message =~ /DECLINED/ + end + + def test_successful_refund + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + + assert refund = @gateway.refund(@amount, response.authorization) + assert_success refund + assert_equal 'Succeeded', refund.message + end + + def test_successful_void + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + assert void = @gateway.void(auth.authorization) + assert_success void + end + + def test_failed_void + response = @gateway.void('') + assert_failure response + end + + def test_successful_verify + assert response = @gateway.verify(@credit_card, @options) + assert_success response + assert_equal 'Succeeded', response.message + assert_success response.responses.last, 'The void should succeed' + end + + def test_failed_verify + bogus_card = credit_card('4424222222222222') + assert response = @gateway.verify(bogus_card, @options) + assert_failure response + assert response.message =~ /DECLINED/ + end + + def test_passing_billing_address + assert response = @gateway.purchase(@amount, @credit_card, @options.merge(:billing_address => address)) + assert_success response + end + + def test_invalid_login + gateway = WorldpayUsGateway.new( + :acctid => '', + :subid => '', + :merchantpin => '' + ) + assert response = gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert response.message =~ /DECLINED/ + end + + def test_transcript_scrubbing + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @credit_card, @options) + end + transcript = @gateway.scrub(transcript) + + assert_scrubbed(@credit_card.number, transcript) + assert_scrubbed(@credit_card.verification_value, transcript) + assert_scrubbed(@gateway.options[:merchantpin], transcript) + + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @check, @options) + end + transcript = @gateway.scrub(transcript) + + assert_scrubbed(@check.account_number, transcript) + assert_scrubbed(@gateway.options[:merchantpin], transcript) + end +end diff --git a/test/remote/integrations/remote_direc_pay_integration_test.rb b/test/remote/integrations/remote_direc_pay_integration_test.rb deleted file mode 100644 index 863b43cb708..00000000000 --- a/test/remote/integrations/remote_direc_pay_integration_test.rb +++ /dev/null @@ -1,22 +0,0 @@ -require 'test_helper' - -class RemoteDirecPayIntegrationTest < Test::Unit::TestCase - include ActiveMerchant::Billing::Integrations - - def setup - @helper = DirecPay::Helper.new('#1234', fixtures(:direc_pay)[:mid], :amount => 500, :currency => 'INR') - @notification = DirecPay::Notification.new('test=dummy-value') - end - - def tear_down - ActiveMerchant::Billing::Base.integration_mode = :test - end - - def test_return_is_always_acknowledged - assert_equal "https://test.timesofmoney.com/direcpay/secure/dpMerchantTransaction.jsp", DirecPay.service_url - assert_nothing_raised do - assert_equal true, @notification.acknowledge - end - end - -end diff --git a/test/remote/integrations/remote_gestpay_integration_test.rb b/test/remote/integrations/remote_gestpay_integration_test.rb deleted file mode 100644 index a56b1d742a0..00000000000 --- a/test/remote/integrations/remote_gestpay_integration_test.rb +++ /dev/null @@ -1,37 +0,0 @@ -require 'test_helper' - -class RemoteGestpayIntegrationTest < Test::Unit::TestCase - include ActiveMerchant::Billing::Integrations - - def setup - # Your Gestpay ShopLogin - @shop_login = 'SHOPLOGIN' - - @helper = Gestpay::Helper.new('order-500', @shop_login, :amount => '5.00', :currency => 'EUR') - end - - def test_get_encryption_string - # Extra fields don't work yet - # @helper.customer :first_name => 'Cody', :last_name => 'Fauser', :email => 'cody@example.com' - response = @helper.send(:get_encrypted_string) - assert !response.blank? - end - - def test_get_encryption_string_fails - @helper = Gestpay::Helper.new('order-500','99999999', :amount => '5.00', :currency => 'EUR') - assert_raise(StandardError) do - assert @helper.send(:get_encrypted_string).blank? - end - end - - def test_unknown_shop_for_decryption_request - assert_raise(StandardError) do - Gestpay::Notification.new(raw_query_string) - end - end - - private - def raw_query_string - "a=900000&b=F7DEB36478FD84760F9134F23C922697272D57DE6D4518EB9B4D468B769D9A3A8071B6EB160B35CB412FC1820C7CC12D17B3141855B1ED55468613702A2E213DDE9DE5B0209E13C416448AE833525959F05693172D7F0656" - end -end diff --git a/test/remote/integrations/remote_integration_helper.rb b/test/remote/integrations/remote_integration_helper.rb deleted file mode 100644 index 2dedd3de04b..00000000000 --- a/test/remote/integrations/remote_integration_helper.rb +++ /dev/null @@ -1,99 +0,0 @@ -require 'mechanize' -require 'action_view/base' -require 'launchy' -require 'mongrel' unless defined?(JRUBY_VERSION) - -module RemoteIntegrationHelper - class FakeView < ActionView::Base - include ActiveMerchant::Billing::Integrations::ActionViewHelper - end - - def submit(string) - view = FakeView.new - body = view.render(:inline => string) - page = Mechanize::Page.new(nil, {'content-type' => 'text/html; charset=utf-8'}, body, nil, agent) - page.forms.first.submit - end - - def agent - @agent ||= Mechanize.new{|a| a.log = Logger.new(STDERR) if verbose? } - end - - def listen_for_notification(external_port=42063) - exception = nil - requests = [] - test = self - server = notification_server do |request, response| - begin - test.log("[HANDLER] request received") - requests << request - response.start(200, true){|h,b| b << "OK"} - rescue Exception => e - exception = e - end - end - listener = server.run - - mapper = Thread.new do - require 'UPnP.rb' - upnp = UPnP::UPnP.new(true, 10) - begin - log "[MAPPER] adding port mapping" - upnp.addPortMapping(external_port, 42063, UPnP::Protocol::TCP, "AM Test Port") - log "[MAPPER] yielding" - yield("http://#{upnp.externalIP}#{":#{external_port}" unless external_port == 80}/") - count = 0 - 20.times do - log "[MAPPER] waiting" - sleep 1 - break if requests.size > 0 && requests.size == count - count = requests.size - end - log "[MAPPER] returned" - rescue Exception => e - log "[MAPPER] exception #{e}" - exception = e - ensure - log "[MAPPER] deleting port mapping" - upnp.deletePortMapping(external_port, UPnP::Protocol::TCP) - log "[MAPPER] stopping server" - server.stop(true) - log "[MAPPER] server stopped" - end - end - - [listener, mapper].each{|t| t.abort_on_exception = true} - [listener, mapper].each{|t| t.join} - - raise exception if exception - - assert requests.size > 0 - - request = requests.last - log "[REQUEST] QUERY: #{request.params["QUERY_STRING"]}" - log "[REQUEST] BODY: #{request.body.string}" - request - end - - def notification_server(&handler_body) - http_server = Mongrel::HttpServer.new('0.0.0.0', 42063) - handler = Mongrel::HttpHandler.new - handler.class_eval do - define_method(:process, &handler_body) - end - http_server.register('/', handler) - end - - def log(message) - puts message if verbose? - end - - def verbose? - (ENV["ACTIVE_MERCHANT_DEBUG"] == "true") - end - - def open_in_browser(body) - File.open('tmp.html', 'w'){|f| f.write body} - Launchy::Browser.run("file:///#{Dir.pwd}/tmp.html") - end -end diff --git a/test/remote/integrations/remote_paypal_integration_test.rb b/test/remote/integrations/remote_paypal_integration_test.rb deleted file mode 100644 index 006ca399d84..00000000000 --- a/test/remote/integrations/remote_paypal_integration_test.rb +++ /dev/null @@ -1,26 +0,0 @@ -require 'test_helper' - -class RemotePaypalIntegrationTest < Test::Unit::TestCase - include ActiveMerchant::Billing::Integrations - - def setup - @paypal = Paypal::Notification.new('') - end - - def tear_down - ActiveMerchant::Billing::Base.integration_mode = :test - end - - def test_raw - assert_equal "https://www.sandbox.paypal.com/cgi-bin/webscr", Paypal.service_url - assert_nothing_raised do - assert_equal false, @paypal.acknowledge - end - end - - def test_valid_sender_always_true - ActiveMerchant::Billing::Base.integration_mode = :production - assert @paypal.valid_sender?(nil) - assert @paypal.valid_sender?('127.0.0.1') - end -end diff --git a/test/remote/integrations/remote_payu_in_integration_test.rb b/test/remote/integrations/remote_payu_in_integration_test.rb deleted file mode 100644 index be8aa9c8547..00000000000 --- a/test/remote/integrations/remote_payu_in_integration_test.rb +++ /dev/null @@ -1,26 +0,0 @@ -require 'test_helper' - -class RemotePayuInIntegrationTest < Test::Unit::TestCase - include ActiveMerchant::Billing::Integrations - - def setup - @payu_in = PayuIn::Notification.new(http_raw_data, :credential1 => 'C0Dr8m', :credential2 => '3sf0jURk') - end - - def test_raw - ActiveMerchant::Billing::Base.integration_mode = :production - assert_equal "https://secure.payu.in/_payment.php", PayuIn.service_url - - ActiveMerchant::Billing::Base.integration_mode = :test - assert_equal "https://test.payu.in/_payment.php", PayuIn.service_url - - assert_nothing_raised do - assert @payu_in.checksum_ok? - end - end - - private - def http_raw_data - "mihpayid=403993715508030204&mode=CC&status=success&unmappedstatus=captured&key=C0Dr8m&txnid=4ba4afe87f7e73468f2a&amount=10.00&discount=0.00&addedon=2013-05-10 18 32 30&productinfo=Product Info&firstname=Payu-Admin&lastname=&address1=&address2=&city=&state=&country=&zipcode=&email=test@example.com&phone=1234567890&udf1=&udf2=&udf3=&udf4=&udf5=&udf6=&udf7=&udf8=&udf9=&udf10=&hash=ef0c1b509a42b802a4938c25dc9bb9efe40b75a7dfb8bde1a6f126fa1f86cee264c5e5a17e87db85150d6d8912eafda838416e669712f1989dcb9cbdb8c24219&field1=313069903923&field2=999999&field3=59117331831301&field4=-1&field5=&field6=&field7=&field8=&PG_TYPE=HDFC&bank_ref_num=59117331831301&bankcode=CC&error=E000&cardnum=512345XXXXXX2346&cardhash=766f0227cc4b4c5f773a04cb31d8d1c5be071dd8d08fe365ecf5e2e5c947546d" - end -end diff --git a/test/remote/integrations/remote_pxpay_integration_test.rb b/test/remote/integrations/remote_pxpay_integration_test.rb deleted file mode 100644 index 77ace6e631e..00000000000 --- a/test/remote/integrations/remote_pxpay_integration_test.rb +++ /dev/null @@ -1,148 +0,0 @@ -require 'test_helper' -require 'remote/integrations/remote_integration_helper' - -class RemotePxpayIntegrationTest < Test::Unit::TestCase - include ActiveMerchant::Billing::Integrations - include ActionViewHelperTestHelper - - def setup - @output_buffer = "" - @options = fixtures(:pxpay) - - @helper = Pxpay::Helper.new('500', @options[:login], :amount => "120.99", :currency => 'USD', :credential2 => @options[:password]) - end - - def test_merchant_references_longer_than_50_characters_should_be_trimmed - @helper.description = "more than 50 chars--------------------40--------50---55" - request = @helper.send(:generate_request) - assert_match /<MerchantReference>more than 50 chars--------------------40--------50<\/MerchantReference>/, request - end - - def test_valid_credentials_returns_secure_token - @helper.return_url "http://t/pxpay/return_url" - @helper.cancel_return_url "http://t/pxpay/cancel_url" - - response = @helper.send :request_secure_redirect - - assert_equal "1", response[:valid] - assert response[:redirect].present? - end - - def test_redirect_url_matches_expected - @helper.return_url "http://t/pxpay/return_url" - @helper.cancel_return_url "http://t/pxpay/cancel_url" - - response = @helper.send :request_secure_redirect - - url = URI.parse(response[:redirect]) - assert_equal Pxpay.service_url, "#{url.scheme}://#{url.host}#{url.path}" - end - - def test_entire_payment - @order_id = Digest::MD5.hexdigest("#{Time.now}+#{rand}")[0,6] - - # generate form to redirect customer to pxpay gateway - generate_valid_redirect_form @order_id - - # submit generated form and ensure we're redirected to the CC info page - agent = Mechanize.new { |a| - a.user_agent_alias = 'Mac Safari' - } - - page = Mechanize::Page.new(nil,{'content-type'=>'text/html'}, @output_buffer, nil, agent) - gateway_page = agent.submit page.forms.first - - # entire valid test CC credentials and submit form - - assert gateway_page.forms.size > 0 - - confirm_page = gateway_page.form_with(:name => 'PmtEnt') do |form| - form.CardNum = '4111111111111111' - form.ExMnth = '12' - form.ExYr = '20' - form.NmeCard = 'Firstname Lastname' - form.Cvc2 = '123' - end.submit - - # pull out redirected URL params - return_url = confirm_page.link_with(:text => 'Click Here to Proceed to the Next step').href - - assert !return_url.empty? - - param_string = return_url.sub(/.*\?/, "") - - notification = Pxpay.notification(param_string, :credential1 => @options[:login], :credential2 => @options[:password]) - - assert notification.acknowledge - assert notification.complete? - assert_match "Completed", notification.status - assert_match "157.00", notification.gross - assert notification.transaction_id.present? - assert_match @order_id, notification.item_id - end - - def test_failed_payment - - @order_id = Digest::MD5.hexdigest("#{Time.now}+#{rand}")[0,6] - - # generate form to redirect customer to pxpay gateway - generate_valid_redirect_form @order_id - - # submit generated form and ensure we're redirected to the CC info page - agent = Mechanize.new { |a| - a.user_agent_alias = 'Mac Safari' - } - - page = Mechanize::Page.new(nil,{'content-type'=>'text/html'}, @output_buffer, nil, agent) - gateway_page = agent.submit page.forms.first - - # entire valid test CC credentials and submit form - - assert gateway_page.forms.size > 0 - - confirm_page = gateway_page.form_with(:name => 'PmtEnt') do |form| - form.CardNum = '4111111111111112' - form.ExMnth = '12' - form.ExYr = '10' - form.NmeCard = 'Firstname Lastname' - form.Cvc2 = '123' - end.submit - - # pull out redirected URL params - return_url = confirm_page.link_with(:text => 'Click Here to Proceed to the Next step').href - - assert !return_url.empty? - - param_string = return_url.sub(/.*\?/, "") - - notification = Pxpay.notification(param_string, :credential1 => @options[:login], :credential2 => @options[:password]) - - assert_false notification.complete? - assert notification.acknowledge - assert_match "Failed", notification.status - assert_match @order_id, notification.item_id - end - - private - - def generate_valid_redirect_form(order_id) - payment_service_for(order_id, @options[:login], :service => :pxpay, :amount => "157.0") do |service| - # You must set :credential2 to your pxpay key - service.credential2 @options[:password] - - service.customer_id 8 - service.customer :first_name => 'g', - :last_name => 'g', - :email => 'g@g.com', - :phone => '3' - - service.description "Order Description" - - # The end-user is presented with the HTML produced by the notify_url. - service.return_url "http://t/pxpay/payment_received_notification_sub_step" - service.cancel_return_url "http://t/pxpay/payment_cancelled" - service.currency 'USD' - end - end -end - diff --git a/test/remote/integrations/remote_quickpay_integration_test.rb b/test/remote/integrations/remote_quickpay_integration_test.rb deleted file mode 100644 index 71cfa0b4c8e..00000000000 --- a/test/remote/integrations/remote_quickpay_integration_test.rb +++ /dev/null @@ -1,26 +0,0 @@ -require 'test_helper' - -class RemoteQuickPayIntegrationTest < Test::Unit::TestCase - include ActiveMerchant::Billing::Integrations - - def setup - @quickpay = Quickpay::Notification.new('') - end - - def tear_down - ActiveMerchant::Billing::Base.integration_mode = :test - end - - def test_raw - assert_equal "https://secure.quickpay.dk/form/", Quickpay.service_url - assert_nothing_raised do - assert_equal false, @quickpay.acknowledge - end - end - - def test_valid_sender_always_true - ActiveMerchant::Billing::Base.integration_mode = :production - assert @quickpay.valid_sender?(nil) - assert @quickpay.valid_sender?('127.0.0.1') - end -end diff --git a/test/remote/integrations/remote_valitor_integration_test.rb b/test/remote/integrations/remote_valitor_integration_test.rb deleted file mode 100644 index 2f93cfd4372..00000000000 --- a/test/remote/integrations/remote_valitor_integration_test.rb +++ /dev/null @@ -1,157 +0,0 @@ -require 'test_helper' -require 'remote/integrations/remote_integration_helper' -require 'nokogiri' - -class RemoteValitorIntegrationTest < Test::Unit::TestCase - include RemoteIntegrationHelper - - def setup - @order = "order#{generate_unique_id}" - @login = fixtures(:valitor)[:login] - @password = fixtures(:valitor)[:password] - end - - def test_full_purchase - notification_request = listen_for_notification(80) do |notify_url| - payment_page = submit %( - <% payment_service_for('#{@order}', '#{@login}', :service => :valitor, :credential2 => #{@password}, :html => {:method => 'GET'}) do |service| %> - <% service.product(1, :amount => 100, :description => 'PRODUCT1', :discount => '0') %> - <% service.return_url = 'http://example.org/return' %> - <% service.cancel_return_url = 'http://example.org/cancel' %> - <% service.notify_url = '#{notify_url}' %> - <% service.success_text = 'SuccessText!' %> - <% service.language = 'en' %> - <% end %> - ) - - assert_match(%r(http://example.org/cancel)i, payment_page.body) - assert_match(%r(PRODUCT1), payment_page.body) - - form = payment_page.forms.first - form['tbKortnumer'] = '4111111111111111' - form['drpGildistimiManudur'] = '12' - form['drpGildistimiAr'] = Time.now.year - form['tbOryggisnumer'] = '000' - result_page = form.submit(form.submits.first) - - assert continue_link = result_page.links.detect{|e| e.text =~ /successtext!/i} - assert_match(%r(^http://example.org/return\?)i, continue_link.href) - - check_common_fields(return_from(continue_link.href)) - end - - check_common_fields(notification_from(notification_request)) - end - - def test_customer_fields - payment_page = submit %( - <% payment_service_for('#{@order}', '#{@login}', :service => :valitor, :credential2 => #{@password}, :html => {:method => 'GET'}) do |service| %> - <% service.product(1, :amount => 100, :description => 'test', :discount => '0') %> - <% service.return_url = 'http://example.org/return' %> - <% service.cancel_return_url = 'http://example.org/cancel' %> - <% service.success_text = 'SuccessText!' %> - <% service.language = 'en' %> - <% service.collect_customer_info %> - <% end %> - ) - - form = payment_page.forms.first - form['tbKortnumer'] = '4111111111111111' - form['drpGildistimiManudur'] = '12' - form['drpGildistimiAr'] = Time.now.year - form['tbOryggisnumer'] = '000' - form['tbKaupNafn'] = "NAME" - form['tbKaupHeimilisfang'] = "123 ADDRESS" - form['tbKaupPostnumer'] = "98765" - form['tbKaupStadur'] = "CITY" - form['tbKaupLand'] = "COUNTRY" - form['tbKaupTolvupostfang'] = "EMAIL@EXAMPLE.COM" - form['tbAthugasemdir'] = "COMMENTS" - result_page = form.submit(form.submits.first) - - assert continue_link = result_page.links.detect{|e| e.text =~ /successtext!/i} - assert_match(%r(^http://example.org/return\?)i, continue_link.href) - - ret = return_from(continue_link.href) - check_common_fields(ret) - assert_equal "NAME", ret.customer_name - assert_equal "123 ADDRESS", ret.customer_address - assert_equal "98765", ret.customer_zip - assert_equal "CITY", ret.customer_city - assert_equal "COUNTRY", ret.customer_country - assert_equal "EMAIL@EXAMPLE.COM", ret.customer_email - assert_equal "COMMENTS", ret.customer_comment - end - - def test_products - payment_page = submit %( - <% payment_service_for('#{@order}', '#{@login}', :service => :valitor, :credential2 => #{@password}, :html => {:method => 'GET'}) do |service| %> - <% service.product(1, :amount => 100, :description => 'PRODUCT1') %> - <% service.product(2, :amount => 200, :description => 'PRODUCT2', :discount => '50') %> - <% service.product(3, :amount => 300, :description => 'PRODUCT3', :quantity => '6') %> - <% service.return_url = 'http://example.org/return' %> - <% service.cancel_return_url = 'http://example.org/cancel' %> - <% service.success_text = 'SuccessText!' %> - <% service.language = 'en' %> - <% service.collect_customer_info %> - <% end %> - ) - - assert_match(%r(http://example.org/cancel)i, payment_page.body) - - doc = Nokogiri::HTML(payment_page.body) - rows = doc.xpath("//table[@class='VoruTafla']//tr") - assert_equal 5, rows.size - check_product_row(rows[1], "PRODUCT1", "1", "100 ISK", "0 ISK", "100 ISK") - check_product_row(rows[2], "PRODUCT2", "1", "200 ISK", "50 ISK", "150 ISK") - check_product_row(rows[3], "PRODUCT3", "6", "300 ISK", "0 ISK", "1.800 ISK") - assert_match /2.050 ISK/, rows[4].element_children.first.text - end - - def test_default_product_if_none_provided - payment_page = submit %( - <% payment_service_for('#{@order}', '#{@login}', :service => :valitor, :credential2 => #{@password}, :html => {:method => 'GET'}) do |service| %> - <% service.return_url = 'http://example.org/return' %> - <% service.cancel_return_url = 'http://example.org/cancel' %> - <% service.success_text = 'SuccessText!' %> - <% service.language = 'en' %> - <% service.collect_customer_info %> - <% end %> - ) - - assert_match(%r(http://example.org/cancel)i, payment_page.body) - - doc = Nokogiri::HTML(payment_page.body) - rows = doc.xpath("//table[@class='VoruTafla']//tr") - assert_equal 5, rows.size - check_product_row(rows[1], "PRODUCT1", "1", "100 ISK", "0 ISK", "100 ISK") - assert_match /2.050 ISK/, rows[4].element_children.first.text - end - - def check_product_row(row, desc, quantity, amount, discount, total) - assert_equal desc, row.element_children[0].text.strip - assert_equal quantity, row.element_children[1].text.strip - assert_equal amount, row.element_children[2].text.strip - assert_equal discount, row.element_children[3].text.strip - assert_equal total, row.element_children[4].text.strip - end - - def check_common_fields(response) - assert response.success? - assert_equal 'VISA', response.card_type - assert_equal '9999', response.card_last_four # No idea why this comes back with 9's - assert_equal @order, response.order - assert response.received_at.length > 0 - assert response.authorization_number.length > 0 - assert response.transaction_number.length > 0 - assert response.transaction_id.length > 0 - end - - def return_from(uri) - ActiveMerchant::Billing::Integrations::Valitor.return(uri.split('?').last, :credential2 => @password) - end - - def notification_from(request) - ActiveMerchant::Billing::Integrations::Valitor.notification(request.params["QUERY_STRING"], :credential2 => @password) - end -end \ No newline at end of file diff --git a/test/schema/cyber_source/CyberSourceTransaction_1.121.xsd b/test/schema/cyber_source/CyberSourceTransaction_1.121.xsd new file mode 100644 index 00000000000..dbf57b71011 --- /dev/null +++ b/test/schema/cyber_source/CyberSourceTransaction_1.121.xsd @@ -0,0 +1,3627 @@ +<?xml version="1.0" encoding="utf-8"?> +<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:tns="urn:schemas-cybersource-com:transaction-data-1.121" targetNamespace="urn:schemas-cybersource-com:transaction-data-1.121" elementFormDefault="qualified" attributeFormDefault="unqualified"> + <xsd:simpleType name="amount"> + <xsd:restriction base="xsd:string"/> + </xsd:simpleType> + <xsd:simpleType name="boolean"> + <xsd:restriction base="xsd:string"/> + </xsd:simpleType> + <xsd:simpleType name="dateTime"> + <xsd:restriction base="xsd:string"/> + </xsd:simpleType> + <xsd:complexType name="Item"> + <xsd:sequence> + <xsd:element name="unitPrice" type="tns:amount" minOccurs="0"/> + <xsd:element name="quantity" type="tns:amount" minOccurs="0"/> + <xsd:element name="productCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="productName" type="xsd:string" minOccurs="0"/> + <xsd:element name="productSKU" type="xsd:string" minOccurs="0"/> + <xsd:element name="productRisk" type="xsd:string" minOccurs="0"/> + <xsd:element name="taxAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="cityOverrideAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="cityOverrideRate" type="tns:amount" minOccurs="0"/> + <xsd:element name="countyOverrideAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="countyOverrideRate" type="tns:amount" minOccurs="0"/> + <xsd:element name="districtOverrideAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="districtOverrideRate" type="tns:amount" minOccurs="0"/> + <xsd:element name="stateOverrideAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="stateOverrideRate" type="tns:amount" minOccurs="0"/> + <xsd:element name="countryOverrideAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="countryOverrideRate" type="tns:amount" minOccurs="0"/> + <xsd:element name="orderAcceptanceCity" type="xsd:string" minOccurs="0"/> + <xsd:element name="orderAcceptanceCounty" type="xsd:string" minOccurs="0"/> + <xsd:element name="orderAcceptanceCountry" type="xsd:string" minOccurs="0"/> + <xsd:element name="orderAcceptanceState" type="xsd:string" minOccurs="0"/> + <xsd:element name="orderAcceptancePostalCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="orderOriginCity" type="xsd:string" minOccurs="0"/> + <xsd:element name="orderOriginCounty" type="xsd:string" minOccurs="0"/> + <xsd:element name="orderOriginCountry" type="xsd:string" minOccurs="0"/> + <xsd:element name="orderOriginState" type="xsd:string" minOccurs="0"/> + <xsd:element name="orderOriginPostalCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="shipFromCity" type="xsd:string" minOccurs="0"/> + <xsd:element name="shipFromCounty" type="xsd:string" minOccurs="0"/> + <xsd:element name="shipFromCountry" type="xsd:string" minOccurs="0"/> + <xsd:element name="shipFromState" type="xsd:string" minOccurs="0"/> + <xsd:element name="shipFromPostalCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="export" type="xsd:string" minOccurs="0"/> + <xsd:element name="noExport" type="xsd:string" minOccurs="0"/> + <xsd:element name="nationalTax" type="tns:amount" minOccurs="0"/> + <xsd:element name="vatRate" type="tns:amount" minOccurs="0"/> + <xsd:element name="sellerRegistration" type="xsd:string" minOccurs="0"/> + <xsd:element name="sellerRegistration0" type="xsd:string" minOccurs="0"/> + <xsd:element name="sellerRegistration1" type="xsd:string" minOccurs="0"/> + <xsd:element name="sellerRegistration2" type="xsd:string" minOccurs="0"/> + <xsd:element name="sellerRegistration3" type="xsd:string" minOccurs="0"/> + <xsd:element name="sellerRegistration4" type="xsd:string" minOccurs="0"/> + <xsd:element name="sellerRegistration5" type="xsd:string" minOccurs="0"/> + <xsd:element name="sellerRegistration6" type="xsd:string" minOccurs="0"/> + <xsd:element name="sellerRegistration7" type="xsd:string" minOccurs="0"/> + <xsd:element name="sellerRegistration8" type="xsd:string" minOccurs="0"/> + <xsd:element name="sellerRegistration9" type="xsd:string" minOccurs="0"/> + <xsd:element name="buyerRegistration" type="xsd:string" minOccurs="0"/> + <xsd:element name="middlemanRegistration" type="xsd:string" minOccurs="0"/> + <xsd:element name="pointOfTitleTransfer" type="xsd:string" minOccurs="0"/> + <xsd:element name="giftCategory" type="tns:boolean" minOccurs="0"/> + <xsd:element name="timeCategory" type="xsd:string" minOccurs="0"/> + <xsd:element name="hostHedge" type="xsd:string" minOccurs="0"/> + <xsd:element name="timeHedge" type="xsd:string" minOccurs="0"/> + <xsd:element name="velocityHedge" type="xsd:string" minOccurs="0"/> + <xsd:element name="nonsensicalHedge" type="xsd:string" minOccurs="0"/> + <xsd:element name="phoneHedge" type="xsd:string" minOccurs="0"/> + <xsd:element name="obscenitiesHedge" type="xsd:string" minOccurs="0"/> + <xsd:element name="unitOfMeasure" type="xsd:string" minOccurs="0"/> + <xsd:element name="taxRate" type="tns:amount" minOccurs="0"/> + <xsd:element name="totalAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="discountAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="discountRate" type="tns:amount" minOccurs="0"/> + <xsd:element name="commodityCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="grossNetIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="taxTypeApplied" type="xsd:string" minOccurs="0"/> + <xsd:element name="discountIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="alternateTaxID" type="xsd:string" minOccurs="0"/> + <xsd:element name="alternateTaxAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="alternateTaxTypeApplied" type="xsd:string" minOccurs="0"/> + <xsd:element name="alternateTaxRate" type="tns:amount" minOccurs="0"/> + <xsd:element name="alternateTaxType" type="xsd:string" minOccurs="0"/> + <xsd:element name="localTax" type="tns:amount" minOccurs="0"/> + <xsd:element name="zeroCostToCustomerIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="passengerFirstName" type="xsd:string" minOccurs="0"/> + <xsd:element name="passengerLastName" type="xsd:string" minOccurs="0"/> + <xsd:element name="passengerID" type="xsd:string" minOccurs="0"/> + <xsd:element name="passengerStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="passengerType" type="xsd:string" minOccurs="0"/> + <xsd:element name="passengerEmail" type="xsd:string" minOccurs="0"/> + <xsd:element name="passengerPhone" type="xsd:string" minOccurs="0"/> + <xsd:element name="invoiceNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="productDescription" type="xsd:string" minOccurs="0"/> + <xsd:element name="taxStatusIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="discountManagementIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="typeOfSupply" type="xsd:string" minOccurs="0"/> + <xsd:element name="sign" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="id" type="xsd:integer" use="optional"/> + </xsd:complexType> + <xsd:complexType name="CCAuthService"> + <xsd:sequence> + <xsd:element name="cavv" type="xsd:string" minOccurs="0"/> + <xsd:element name="cavvAlgorithm" type="xsd:string" minOccurs="0"/> + <xsd:element name="commerceIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="eciRaw" type="xsd:string" minOccurs="0"/> + <xsd:element name="xid" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="avsLevel" type="xsd:string" minOccurs="0"/> + <xsd:element name="fxQuoteID" type="xsd:string" minOccurs="0"/> + <xsd:element name="returnAuthRecord" type="tns:boolean" minOccurs="0"/> + <xsd:element name="authType" type="xsd:string" minOccurs="0"/> + <xsd:element name="verbalAuthCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="billPayment" type="tns:boolean" minOccurs="0"/> + <xsd:element name="authenticationXID" type="xsd:string" minOccurs="0"/> + <xsd:element name="authorizationXID" type="xsd:string" minOccurs="0"/> + <xsd:element name="industryDatatype" type="xsd:string" minOccurs="0"/> + <xsd:element name="traceNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="checksumKey" type="xsd:string" minOccurs="0"/> + <xsd:element name="aggregatorID" type="xsd:string" minOccurs="0"/> + <xsd:element name="aggregatorName" type="xsd:string" minOccurs="0"/> + <xsd:element name="splitTenderIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="veresEnrolled" type="xsd:string" minOccurs="0"/> + <xsd:element name="paresStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="partialAuthIndicator" type="tns:boolean" minOccurs="0"/> + <xsd:element name="captureDate" type="xsd:string" minOccurs="0"/> + <xsd:element name="firstRecurringPayment" type="xsd:string" minOccurs="0"/> + <xsd:element name="duration" type="xsd:integer" minOccurs="0"/> + <xsd:element name="overridePaymentMethod" type="xsd:string" minOccurs="0"/> + <xsd:element name="mobileRemotePaymentType" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="OCTService"> + <xsd:sequence> + <xsd:element name="commerceIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + + <xsd:complexType name="CCSaleService"> + <xsd:sequence> + <xsd:element name="overridePaymentMethod" type="xsd:string" minOccurs="0"/> + <xsd:element name="commerceIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="partialAuthIndicator" type="tns:boolean" minOccurs="0"/> + <xsd:element name="cavv" type="xsd:string" minOccurs="0"/> + <xsd:element name="xid" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> +</xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + + <xsd:complexType name="CCSaleCreditService"> + <xsd:sequence> + <xsd:element name="overridePaymentMethod" type="xsd:string" minOccurs="0"/> + <xsd:element name="commerceIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + + <xsd:complexType name="CCSaleReversalService"> + <xsd:sequence> + <xsd:element name="saleRequestID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + + <xsd:complexType name="CCIncrementalAuthService"> + <xsd:sequence> + <xsd:element name="authRequestID" type="xsd:string" minOccurs="0" /> + <xsd:element name="duration" type="xsd:integer" minOccurs="0" /> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required" /> + </xsd:complexType> + <xsd:complexType name="CCCaptureService"> + <xsd:sequence> + <xsd:element name="authType" type="xsd:string" minOccurs="0"/> + <xsd:element name="verbalAuthCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="authRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="partialPaymentID" type="xsd:string" minOccurs="0"/> + <xsd:element name="purchasingLevel" type="xsd:string" minOccurs="0"/> + <xsd:element name="industryDatatype" type="xsd:string" minOccurs="0"/> + <xsd:element name="authRequestToken" type="xsd:string" minOccurs="0"/> + <xsd:element name="merchantReceiptNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="posData" type="xsd:string" minOccurs="0"/> + <xsd:element name="transactionID" type="xsd:string" minOccurs="0"/> + <xsd:element name="checksumKey" type="xsd:string" minOccurs="0"/> + <xsd:element name="gratuityAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="duration" type="xsd:integer" minOccurs="0"/> + <xsd:element name="dpdeBillingMonth" type="xsd:integer" minOccurs="0"/> + <xsd:element name="sequence" type="xsd:string" minOccurs="0"/> + <xsd:element name="totalCount" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationIDAlternate" type="xsd:string" minOccurs="0"/> + <xsd:element name="aggregatorID" type="xsd:string" minOccurs="0"/> + <xsd:element name="aggregatorName" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="CCCreditService"> + <xsd:sequence> + <xsd:element name="captureRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="partialPaymentID" type="xsd:string" minOccurs="0"/> + <xsd:element name="purchasingLevel" type="xsd:string" minOccurs="0"/> + <xsd:element name="industryDatatype" type="xsd:string" minOccurs="0"/> + <xsd:element name="commerceIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="billPayment" type="tns:boolean" minOccurs="0"/> + <xsd:element name="authorizationXID" type="xsd:string" minOccurs="0"/> + <xsd:element name="occurrenceNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="authCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="captureRequestToken" type="xsd:string" minOccurs="0"/> + <xsd:element name="merchantReceiptNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="checksumKey" type="xsd:string" minOccurs="0"/> + <xsd:element name="aggregatorID" type="xsd:string" minOccurs="0"/> + <xsd:element name="aggregatorName" type="xsd:string" minOccurs="0"/> + <xsd:element name="duration" type="xsd:integer" minOccurs="0"/> + <xsd:element name="dpdeBillingMonth" type="xsd:integer" minOccurs="0"/> + <xsd:element name="reconciliationIDAlternate" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="CCAuthReversalService"> + <xsd:sequence> + <xsd:element name="authRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="authRequestToken" type="xsd:string" minOccurs="0"/> + <xsd:element name="reversalReason" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="CCAutoAuthReversalService"> + <xsd:sequence> + <xsd:element name="authPaymentServiceData" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="authAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="commerceIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="authRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="billAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="authCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="authType" type="xsd:string" minOccurs="0"/> + <xsd:element name="billPayment" type="tns:boolean" minOccurs="0"/> + <xsd:element name="dateAdded" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="CCDCCService"> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="ServiceFeeCalculateService"> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="ECDebitService"> + <xsd:sequence> + <xsd:element name="paymentMode" type="xsd:integer" minOccurs="0"/> + <xsd:element name="referenceNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="settlementMethod" type="xsd:string" minOccurs="0"/> + <xsd:element name="transactionToken" type="xsd:string" minOccurs="0"/> + <xsd:element name="verificationLevel" type="xsd:integer" minOccurs="0"/> + <xsd:element name="partialPaymentID" type="xsd:string" minOccurs="0"/> + <xsd:element name="commerceIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="debitRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="effectiveDate" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="ECCreditService"> + <xsd:sequence> + <xsd:element name="referenceNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="settlementMethod" type="xsd:string" minOccurs="0"/> + <xsd:element name="transactionToken" type="xsd:string" minOccurs="0"/> + <xsd:element name="debitRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="partialPaymentID" type="xsd:string" minOccurs="0"/> + <xsd:element name="commerceIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="debitRequestToken" type="xsd:string" minOccurs="0"/> + <xsd:element name="effectiveDate" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="ECAuthenticateService"> + <xsd:sequence> + <xsd:element name="referenceNumber" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="PayerAuthEnrollService"> + <xsd:sequence> + <xsd:element name="httpAccept" type="xsd:string" minOccurs="0"/> + <xsd:element name="httpUserAgent" type="xsd:string" minOccurs="0"/> + <xsd:element name="merchantName" type="xsd:string" minOccurs="0"/> + <xsd:element name="merchantURL" type="xsd:string" minOccurs="0"/> + <xsd:element name="purchaseDescription" type="xsd:string" minOccurs="0"/> + <xsd:element name="purchaseTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="countryCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="acquirerBin" type="xsd:string" minOccurs="0"/> + <xsd:element name="loginID" type="xsd:string" minOccurs="0"/> + <xsd:element name="password" type="xsd:string" minOccurs="0"/> + <xsd:element name="merchantID" type="xsd:string" minOccurs="0"/> + <xsd:element name="overridePaymentMethod" type="xsd:string" minOccurs="0"/> + <xsd:element name="mobilePhone" type="xsd:string" minOccurs="0"/> + <xsd:element name="MCC" type="xsd:string" minOccurs="0"/> + <xsd:element name="productCode" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="PayerAuthValidateService"> + <xsd:sequence> + <xsd:element name="signedPARes" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="TaxService"> + <xsd:sequence> + <xsd:element name="nexus" type="xsd:string" minOccurs="0"/> + <xsd:element name="noNexus" type="xsd:string" minOccurs="0"/> + <xsd:element name="orderAcceptanceCity" type="xsd:string" minOccurs="0"/> + <xsd:element name="orderAcceptanceCounty" type="xsd:string" minOccurs="0"/> + <xsd:element name="orderAcceptanceCountry" type="xsd:string" minOccurs="0"/> + <xsd:element name="orderAcceptanceState" type="xsd:string" minOccurs="0"/> + <xsd:element name="orderAcceptancePostalCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="orderOriginCity" type="xsd:string" minOccurs="0"/> + <xsd:element name="orderOriginCounty" type="xsd:string" minOccurs="0"/> + <xsd:element name="orderOriginCountry" type="xsd:string" minOccurs="0"/> + <xsd:element name="orderOriginState" type="xsd:string" minOccurs="0"/> + <xsd:element name="orderOriginPostalCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="sellerRegistration" type="xsd:string" minOccurs="0"/> + <xsd:element name="sellerRegistration0" type="xsd:string" minOccurs="0"/> + <xsd:element name="sellerRegistration1" type="xsd:string" minOccurs="0"/> + <xsd:element name="sellerRegistration2" type="xsd:string" minOccurs="0"/> + <xsd:element name="sellerRegistration3" type="xsd:string" minOccurs="0"/> + <xsd:element name="sellerRegistration4" type="xsd:string" minOccurs="0"/> + <xsd:element name="sellerRegistration5" type="xsd:string" minOccurs="0"/> + <xsd:element name="sellerRegistration6" type="xsd:string" minOccurs="0"/> + <xsd:element name="sellerRegistration7" type="xsd:string" minOccurs="0"/> + <xsd:element name="sellerRegistration8" type="xsd:string" minOccurs="0"/> + <xsd:element name="sellerRegistration9" type="xsd:string" minOccurs="0"/> + <xsd:element name="buyerRegistration" type="xsd:string" minOccurs="0"/> + <xsd:element name="middlemanRegistration" type="xsd:string" minOccurs="0"/> + <xsd:element name="pointOfTitleTransfer" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <!-- DME --> + <xsd:complexType name="DMEService"> + <xsd:sequence> + <xsd:element name="eventType" type="xsd:string" minOccurs="0" /> + <xsd:element name="eventPolicy" type="xsd:string" minOccurs="0" /> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required" /> + </xsd:complexType> + <xsd:complexType name="AFSService"> + <xsd:sequence> + <xsd:element name="avsCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="cvCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="disableAVSScoring" type="tns:boolean" minOccurs="0"/> + <xsd:element name="customRiskModel" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="DAVService"> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="ExportService"> + <xsd:sequence> + <xsd:element name="addressOperator" type="xsd:string" minOccurs="0"/> + <xsd:element name="addressWeight" type="xsd:string" minOccurs="0"/> + <xsd:element name="companyWeight" type="xsd:string" minOccurs="0"/> + <xsd:element name="nameWeight" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="FXRatesService"> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="BankTransferService"> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="BankTransferRefundService"> + <xsd:sequence> + <xsd:element name="bankTransferRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="bankTransferRealTimeRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="bankTransferRealTimeReconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="bankTransferRequestToken" type="xsd:string" minOccurs="0"/> + <xsd:element name="bankTransferRealTimeRequestToken" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="BankTransferRealTimeService"> + <xsd:sequence> + <xsd:element name="bankTransferRealTimeType" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="DirectDebitMandateService"> + <xsd:sequence> + <xsd:element name="mandateDescriptor" type="xsd:string" minOccurs="0"/> + <xsd:element name="firstDebitDate" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="DirectDebitService"> + <xsd:sequence> + <xsd:element name="dateCollect" type="xsd:string" minOccurs="0"/> + <xsd:element name="directDebitText" type="xsd:string" minOccurs="0"/> + <xsd:element name="authorizationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="transactionType" type="xsd:string" minOccurs="0"/> + <xsd:element name="directDebitType" type="xsd:string" minOccurs="0"/> + <xsd:element name="validateRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="recurringType" type="xsd:string" minOccurs="0"/> + <xsd:element name="mandateID" type="xsd:string" minOccurs="0"/> + <xsd:element name="validateRequestToken" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="mandateAuthenticationDate" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="DirectDebitRefundService"> + <xsd:sequence> + <xsd:element name="directDebitRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="directDebitRequestToken" type="xsd:string" minOccurs="0"/> + <xsd:element name="directDebitType" type="xsd:string" minOccurs="0"/> + <xsd:element name="recurringType" type="xsd:string" minOccurs="0"/> + <xsd:element name="mandateID" type="xsd:string" minOccurs="0"/> + <xsd:element name="mandateAuthenticationDate" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="DirectDebitValidateService"> + <xsd:sequence> + <xsd:element name="directDebitValidateText" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="PaySubscriptionCreateService"> + <xsd:sequence> + <xsd:element name="paymentRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="paymentRequestToken" type="xsd:string" minOccurs="0"/> + <xsd:element name="disableAutoAuth" type="tns:boolean" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="PaySubscriptionUpdateService"> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="PaySubscriptionEventUpdateService"> + <xsd:sequence> + <xsd:element name="action" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="PaySubscriptionRetrieveService"> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="PaySubscriptionDeleteService"> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="PayPalPaymentService"> + <xsd:sequence> + <xsd:element name="cancelURL" type="xsd:string" minOccurs="0"/> + <xsd:element name="successURL" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="PayPalCreditService"> + <xsd:sequence> + <xsd:element name="payPalPaymentRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="payPalPaymentRequestToken" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <!--PayPalEcSet--> + <xsd:complexType name="PayPalEcSetService"> + <xsd:sequence> + <xsd:element name="paypalReturn" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalCancelReturn" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalMaxamt" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalCustomerEmail" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalDesc" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalReqconfirmshipping" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalNoshipping" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalAddressOverride" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalToken" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalLc" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalPagestyle" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalHdrimg" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalHdrbordercolor" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalHdrbackcolor" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalPayflowcolor" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalEcSetRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalEcSetRequestToken" type="xsd:string" minOccurs="0"/> + <xsd:element name="promoCode0" type="xsd:string" minOccurs="0"/> + <xsd:element name="requestBillingAddress" type="xsd:string" minOccurs="0"/> + <xsd:element name="invoiceNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalBillingType" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalBillingAgreementDesc" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalPaymentType" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalBillingAgreementCustom" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalLogoimg" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <!--PayPalEcGetDetails--> + <xsd:complexType name="PayPalEcGetDetailsService"> + <xsd:sequence> + <xsd:element name="paypalToken" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalEcSetRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalEcSetRequestToken" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <!--PayPalEcDoPayment--> + <xsd:complexType name="PayPalEcDoPaymentService"> + <xsd:sequence> + <xsd:element name="paypalToken" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalPayerId" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalCustomerEmail" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalDesc" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalEcSetRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalEcSetRequestToken" type="xsd:string" minOccurs="0"/> + <xsd:element name="promoCode0" type="xsd:string" minOccurs="0"/> + <xsd:element name="invoiceNumber" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <!--PayPalDoCapture--> + <xsd:complexType name="PayPalDoCaptureService"> + <xsd:sequence> + <xsd:element name="paypalAuthorizationId" type="xsd:string" minOccurs="0"/> + <xsd:element name="completeType" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalEcDoPaymentRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalEcDoPaymentRequestToken" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalAuthorizationRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalAuthorizationRequestToken" type="xsd:string" minOccurs="0"/> + <xsd:element name="invoiceNumber" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <!--PayPalAuthReversal--> + <xsd:complexType name="PayPalAuthReversalService"> + <xsd:sequence> + <xsd:element name="paypalAuthorizationId" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalEcDoPaymentRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalEcDoPaymentRequestToken" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalAuthorizationRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalAuthorizationRequestToken" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalEcOrderSetupRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalEcOrderSetupRequestToken" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <!--PayPalRefund--> + <xsd:complexType name="PayPalRefundService"> + <xsd:sequence> + <xsd:element name="paypalDoCaptureRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalDoCaptureRequestToken" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalCaptureId" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalNote" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <!--PayPalEcOrderSetup--> + <xsd:complexType name="PayPalEcOrderSetupService"> + <xsd:sequence> + <xsd:element name="paypalToken" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalPayerId" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalCustomerEmail" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalDesc" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalEcSetRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalEcSetRequestToken" type="xsd:string" minOccurs="0"/> + <xsd:element name="promoCode0" type="xsd:string" minOccurs="0"/> + <xsd:element name="invoiceNumber" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <!--PayPalAuthorization--> + <xsd:complexType name="PayPalAuthorizationService"> + <xsd:sequence> + <xsd:element name="paypalOrderId" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalEcOrderSetupRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalEcOrderSetupRequestToken" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalDoRefTransactionRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalDoRefTransactionRequestToken" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalCustomerEmail" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <!--PayPalUpdateAgreement--> + <xsd:complexType name="PayPalUpdateAgreementService"> + <xsd:sequence> + <xsd:element name="paypalBillingAgreementId" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalBillingAgreementStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalBillingAgreementDesc" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalBillingAgreementCustom" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <!--PayPalCreateAgreement--> + <xsd:complexType name="PayPalCreateAgreementService"> + <xsd:sequence> + <xsd:element name="paypalToken" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalEcSetRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalEcSetRequestToken" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <!--PayPalDoRefTransaction--> + <xsd:complexType name="PayPalDoRefTransactionService"> + <xsd:sequence> + <xsd:element name="paypalBillingAgreementId" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalPaymentType" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalReqconfirmshipping" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalReturnFmfDetails" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalSoftDescriptor" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalShippingdiscount" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalDesc" type="xsd:string" minOccurs="0"/> + <xsd:element name="invoiceNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalEcNotifyUrl" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="VoidService"> + <xsd:sequence> + <xsd:element name="voidRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="voidRequestToken" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="PinlessDebitService"> + <xsd:sequence> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="commerceIndicator" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="PinlessDebitValidateService"> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="PinlessDebitReversalService"> + <xsd:sequence> + <xsd:element name="pinlessDebitRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="pinlessDebitRequestToken" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + + <!--PinDebitPurchaseService--> + <xsd:complexType name="PinDebitPurchaseService"> + <xsd:sequence> + <xsd:element name="networkOrder" type="xsd:string" minOccurs="0"/> + <xsd:element name="commerceIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="partialAuthIndicator" type="tns:boolean" minOccurs="0"/> + <xsd:element name="overridePaymentMethod" type="xsd:string" minOccurs="0"/> + <xsd:element name="paymentType" type="xsd:string" minOccurs="0"/> + <xsd:element name="ebtCategory" type="xsd:string" minOccurs="0"/> + <xsd:element name="transactionType" type="xsd:string" minOccurs="0"/> + <xsd:element name="ebtVoucherSerialNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="authorizationCode" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <!--End of PinDebitPurchaseService--> + <!--PinDebitCreditService--> + <xsd:complexType name="PinDebitCreditService"> + <xsd:sequence> + <xsd:element name="networkOrder" type="xsd:string" minOccurs="0"/> + <xsd:element name="commerceIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="overridePaymentMethod" type="xsd:string" minOccurs="0"/> + <xsd:element name="paymentType" type="xsd:string" minOccurs="0"/> + <xsd:element name="ebtCategory" type="xsd:string" minOccurs="0"/> + <xsd:element name="transactionType" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <!--End of PinDebitCreditService--> + <!--PinDebitReversalService--> + <xsd:complexType name="PinDebitReversalService"> + <xsd:sequence> + <xsd:element name="pinDebitRequestID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <!--End of PinDebitReversalService--> + + <!--PayPal upgrade services --> + <xsd:complexType name="PayPalButtonCreateService"> + <xsd:sequence> + <xsd:element name="buttonType" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="PayPalPreapprovedPaymentService"> + <xsd:sequence> + <xsd:element name="mpID" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="PayPalPreapprovedUpdateService"> + <xsd:sequence> + <xsd:element name="mpID" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <!-- China Payment --> + <xsd:complexType name="ChinaPaymentService"> + <xsd:sequence> + <xsd:element name="paymentMode" type="xsd:string" minOccurs="0"/> + <xsd:element name="returnURL" type="xsd:string" minOccurs="0"/> + <xsd:element name="pickUpAddress" type="xsd:string" minOccurs="0"/> + <xsd:element name="pickUpPhoneNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="pickUpPostalCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="pickUpName" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <!-- China Refund --> + <xsd:complexType name="ChinaRefundService"> + <xsd:sequence> + <xsd:element name="chinaPaymentRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="chinaPaymentRequestToken" type="xsd:string" minOccurs="0"/> + <xsd:element name="refundReason" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <!--Boleto Payment --> + <xsd:complexType name="BoletoPaymentService"> + <xsd:sequence> + <xsd:element name="instruction" type="xsd:string" minOccurs="0"/> + <xsd:element name="expirationDate" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="Address"> + <xsd:sequence> + <xsd:element name="street1" type="xsd:string" minOccurs="0"/> + <xsd:element name="street2" type="xsd:string" minOccurs="0"/> + <xsd:element name="city" type="xsd:string" minOccurs="0"/> + <xsd:element name="state" type="xsd:string" minOccurs="0"/> + <xsd:element name="postalCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="country" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="APInitiateService"> + <xsd:sequence> + <xsd:element name="returnURL" type="xsd:string" minOccurs="0"/> + <xsd:element name="productName" type="xsd:string" minOccurs="0"/> + <xsd:element name="productDescription" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="bankID" type="xsd:string" minOccurs="0"/> + <xsd:element name="countryCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="escrowAgreement" type="xsd:string" minOccurs="0"/> + <xsd:element name="languageInterface" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="APCheckStatusService"> + <xsd:sequence> + <xsd:element name="apInitiateRequestID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="RiskUpdateService"> + <xsd:sequence> + <xsd:element name="actionCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="recordID" type="xsd:string" minOccurs="0"/> + <xsd:element name="recordName" type="xsd:string" minOccurs="0"/> + <xsd:element name="negativeAddress" type="tns:Address" minOccurs="0"/> + <xsd:element name="markingReason" type="xsd:string" minOccurs="0"/> + <xsd:element name="markingNotes" type="xsd:string" minOccurs="0"/> + <xsd:element name="markingRequestID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="FraudUpdateService"> + <xsd:sequence> + <xsd:element name="actionCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="markedData" type="xsd:string" minOccurs="0"/> + <xsd:element name="markingReason" type="xsd:string" minOccurs="0"/> + <xsd:element name="markingNotes" type="xsd:string" minOccurs="0"/> + <xsd:element name="markingRequestID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="CaseManagementActionService"> + <xsd:sequence> + <xsd:element name="actionCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="requestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="comments" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="EncryptPaymentDataService"> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="InvoiceHeader"> + <xsd:sequence> + <xsd:element name="merchantDescriptor" type="xsd:string" minOccurs="0"/> + <xsd:element name="merchantDescriptorContact" type="xsd:string" minOccurs="0"/> + <xsd:element name="merchantDescriptorAlternate" type="xsd:string" minOccurs="0"/> + <xsd:element name="merchantDescriptorStreet" type="xsd:string" minOccurs="0"/> + <xsd:element name="merchantDescriptorCity" type="xsd:string" minOccurs="0"/> + <xsd:element name="merchantDescriptorState" type="xsd:string" minOccurs="0"/> + <xsd:element name="merchantDescriptorPostalCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="merchantDescriptorCountry" type="xsd:string" minOccurs="0"/> + <xsd:element name="isGift" type="tns:boolean" minOccurs="0"/> + <xsd:element name="returnsAccepted" type="tns:boolean" minOccurs="0"/> + <xsd:element name="tenderType" type="xsd:string" minOccurs="0"/> + <xsd:element name="merchantVATRegistrationNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="purchaserOrderDate" type="xsd:string" minOccurs="0"/> + <!-- xsd:date --> + <xsd:element name="purchaserVATRegistrationNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="vatInvoiceReferenceNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="summaryCommodityCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="supplierOrderReference" type="xsd:string" minOccurs="0"/> + <xsd:element name="userPO" type="xsd:string" minOccurs="0"/> + <xsd:element name="costCenter" type="xsd:string" minOccurs="0"/> + <xsd:element name="purchaserCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="taxable" type="tns:boolean" minOccurs="0"/> + <xsd:element name="amexDataTAA1" type="xsd:string" minOccurs="0"/> + <xsd:element name="amexDataTAA2" type="xsd:string" minOccurs="0"/> + <xsd:element name="amexDataTAA3" type="xsd:string" minOccurs="0"/> + <xsd:element name="amexDataTAA4" type="xsd:string" minOccurs="0"/> + <xsd:element name="invoiceDate" type="xsd:string" minOccurs="0"/> + <xsd:element name="totalTaxTypeCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardAcceptorRefNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="authorizedContactName" type="xsd:string" minOccurs="0"/> + <xsd:element name="businessApplicationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="salesOrganizationID" type="xsd:integer" minOccurs="0"/> + <xsd:element name="submerchantID" type="xsd:string" minOccurs="0"/> + <xsd:element name="submerchantName" type="xsd:string" minOccurs="0"/> + <xsd:element name="submerchantStreet" type="xsd:string" minOccurs="0"/> + <xsd:element name="submerchantCity" type="xsd:string" minOccurs="0"/> + <xsd:element name="submerchantPostalCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="submerchantState" type="xsd:string" minOccurs="0"/> + <xsd:element name="submerchantCountry" type="xsd:string" minOccurs="0"/> + <xsd:element name="submerchantEmail" type="xsd:string" minOccurs="0"/> + <xsd:element name="submerchantTelephoneNumber" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="BusinessRules"> + <xsd:sequence> + <xsd:element name="ignoreAVSResult" type="tns:boolean" minOccurs="0"/> + <xsd:element name="ignoreCVResult" type="tns:boolean" minOccurs="0"/> + <xsd:element name="ignoreDAVResult" type="tns:boolean" minOccurs="0"/> + <xsd:element name="ignoreExportResult" type="tns:boolean" minOccurs="0"/> + <xsd:element name="ignoreValidateResult" type="tns:boolean" minOccurs="0"/> + <xsd:element name="declineAVSFlags" type="xsd:string" minOccurs="0"/> + <xsd:element name="scoreThreshold" type="xsd:integer" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="BillTo"> + <xsd:sequence> + <xsd:element name="title" type="xsd:string" minOccurs="0"/> + <xsd:element name="firstName" type="xsd:string" minOccurs="0"/> + <xsd:element name="middleName" type="xsd:string" minOccurs="0"/> + <xsd:element name="lastName" type="xsd:string" minOccurs="0"/> + <xsd:element name="suffix" type="xsd:string" minOccurs="0"/> + <xsd:element name="buildingNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="street1" type="xsd:string" minOccurs="0"/> + <xsd:element name="street2" type="xsd:string" minOccurs="0"/> + <xsd:element name="street3" type="xsd:string" minOccurs="0"/> + <xsd:element name="street4" type="xsd:string" minOccurs="0"/> + <xsd:element name="city" type="xsd:string" minOccurs="0"/> + <xsd:element name="district" type="xsd:string" minOccurs="0"/> + <xsd:element name="county" type="xsd:string" minOccurs="0"/> + <xsd:element name="state" type="xsd:string" minOccurs="0"/> + <xsd:element name="postalCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="country" type="xsd:string" minOccurs="0"/> + <xsd:element name="company" type="xsd:string" minOccurs="0"/> + <xsd:element name="companyTaxID" type="xsd:string" minOccurs="0"/> + <xsd:element name="phoneNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="email" type="xsd:string" minOccurs="0"/> + <xsd:element name="ipAddress" type="xsd:string" minOccurs="0"/> + <xsd:element name="customerUserName" type="xsd:string" minOccurs="0"/> + <xsd:element name="customerPassword" type="xsd:string" minOccurs="0"/> + <xsd:element name="ipNetworkAddress" type="xsd:string" minOccurs="0"/> + <xsd:element name="hostname" type="xsd:string" minOccurs="0"/> + <xsd:element name="domainName" type="xsd:string" minOccurs="0"/> + <xsd:element name="dateOfBirth" type="xsd:string" minOccurs="0"/> + <!-- xsd:date --> + <xsd:element name="driversLicenseNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="driversLicenseState" type="xsd:string" minOccurs="0"/> + <xsd:element name="ssn" type="xsd:string" minOccurs="0"/> + <xsd:element name="customerID" type="xsd:string" minOccurs="0"/> + <xsd:element name="httpBrowserType" type="xsd:string" minOccurs="0"/> + <xsd:element name="httpBrowserEmail" type="xsd:string" minOccurs="0"/> + <xsd:element name="httpBrowserCookiesAccepted" type="tns:boolean" minOccurs="0"/> + <xsd:element name="nif" type="xsd:string" minOccurs="0"/> + <xsd:element name="personalID" type="xsd:string" minOccurs="0"/> + <xsd:element name="language" type="xsd:string" minOccurs="0"/> + <xsd:element name="name" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="ShipTo"> + <xsd:sequence> + <xsd:element name="title" type="xsd:string" minOccurs="0"/> + <xsd:element name="firstName" type="xsd:string" minOccurs="0"/> + <xsd:element name="middleName" type="xsd:string" minOccurs="0"/> + <xsd:element name="lastName" type="xsd:string" minOccurs="0"/> + <xsd:element name="suffix" type="xsd:string" minOccurs="0"/> + <xsd:element name="street1" type="xsd:string" minOccurs="0"/> + <xsd:element name="street2" type="xsd:string" minOccurs="0"/> + <xsd:element name="street3" type="xsd:string" minOccurs="0"/> + <xsd:element name="street4" type="xsd:string" minOccurs="0"/> + <xsd:element name="city" type="xsd:string" minOccurs="0"/> + <xsd:element name="county" type="xsd:string" minOccurs="0"/> + <xsd:element name="state" type="xsd:string" minOccurs="0"/> + <xsd:element name="postalCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="country" type="xsd:string" minOccurs="0"/> + <xsd:element name="company" type="xsd:string" minOccurs="0"/> + <xsd:element name="phoneNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="email" type="xsd:string" minOccurs="0"/> + <xsd:element name="shippingMethod" type="xsd:string" minOccurs="0"/> + <xsd:element name="name" type="xsd:string" minOccurs="0"/> + <xsd:element name="id" type="xsd:string" minOccurs="0"/> + <xsd:element name="addressVerificationStatus" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="ShipFrom"> + <xsd:sequence> + <xsd:element name="title" type="xsd:string" minOccurs="0"/> + <xsd:element name="firstName" type="xsd:string" minOccurs="0"/> + <xsd:element name="middleName" type="xsd:string" minOccurs="0"/> + <xsd:element name="lastName" type="xsd:string" minOccurs="0"/> + <xsd:element name="suffix" type="xsd:string" minOccurs="0"/> + <xsd:element name="street1" type="xsd:string" minOccurs="0"/> + <xsd:element name="street2" type="xsd:string" minOccurs="0"/> + <xsd:element name="street3" type="xsd:string" minOccurs="0"/> + <xsd:element name="street4" type="xsd:string" minOccurs="0"/> + <xsd:element name="city" type="xsd:string" minOccurs="0"/> + <xsd:element name="county" type="xsd:string" minOccurs="0"/> + <xsd:element name="state" type="xsd:string" minOccurs="0"/> + <xsd:element name="postalCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="country" type="xsd:string" minOccurs="0"/> + <xsd:element name="company" type="xsd:string" minOccurs="0"/> + <xsd:element name="phoneNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="email" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="Card"> + <xsd:sequence> + <xsd:element name="fullName" type="xsd:string" minOccurs="0"/> + <xsd:element name="accountNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="expirationMonth" type="xsd:integer" minOccurs="0"/> + <xsd:element name="expirationYear" type="xsd:integer" minOccurs="0"/> + <xsd:element name="cvIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="cvNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardType" type="xsd:string" minOccurs="0"/> + <xsd:element name="issueNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="startMonth" type="xsd:integer" minOccurs="0"/> + <xsd:element name="startYear" type="xsd:integer" minOccurs="0"/> + <xsd:element name="pin" type="xsd:string" minOccurs="0"/> + <xsd:element name="accountEncoderID" type="xsd:string" minOccurs="0"/> + <xsd:element name="bin" type="xsd:string" minOccurs="0"/> + <xsd:element name="encryptedData" type="xsd:string" minOccurs="0"/> + <xsd:element name="suffix" type="xsd:string" minOccurs="0"/> + <xsd:element name="prefix" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="Check"> + <xsd:sequence> + <xsd:element name="fullName" type="xsd:string" minOccurs="0"/> + <xsd:element name="accountNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="accountType" type="xsd:string" minOccurs="0"/> + <xsd:element name="bankTransitNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="checkNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="secCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="accountEncoderID" type="xsd:string" minOccurs="0"/> + <xsd:element name="authenticateID" type="xsd:string" minOccurs="0"/> + <xsd:element name="paymentInfo" type="xsd:string" minOccurs="0"/> + <xsd:element name="imageReferenceNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="terminalCity" type="xsd:string" minOccurs="0"/> + <xsd:element name="terminalState" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="BML"> + <xsd:sequence> + <xsd:element name="customerBillingAddressChange" type="tns:boolean" minOccurs="0"/> + <xsd:element name="customerEmailChange" type="tns:boolean" minOccurs="0"/> + <xsd:element name="customerHasCheckingAccount" type="tns:boolean" minOccurs="0"/> + <xsd:element name="customerHasSavingsAccount" type="tns:boolean" minOccurs="0"/> + <xsd:element name="customerPasswordChange" type="tns:boolean" minOccurs="0"/> + <xsd:element name="customerPhoneChange" type="tns:boolean" minOccurs="0"/> + <xsd:element name="customerRegistrationDate" type="xsd:string" minOccurs="0"/> + <!-- xsd:date --> + <xsd:element name="customerTypeFlag" type="xsd:string" minOccurs="0"/> + <xsd:element name="grossHouseholdIncome" type="tns:amount" minOccurs="0"/> + <xsd:element name="householdIncomeCurrency" type="xsd:string" minOccurs="0"/> + <xsd:element name="itemCategory" type="xsd:string" minOccurs="0"/> + <xsd:element name="merchantPromotionCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="preapprovalNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="productDeliveryTypeIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="residenceStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="tcVersion" type="xsd:string" minOccurs="0"/> + <xsd:element name="yearsAtCurrentResidence" type="xsd:integer" minOccurs="0"/> + <xsd:element name="yearsWithCurrentEmployer" type="xsd:integer" minOccurs="0"/> + <xsd:element name="employerStreet1" type="xsd:string" minOccurs="0"/> + <xsd:element name="employerStreet2" type="xsd:string" minOccurs="0"/> + <xsd:element name="employerCity" type="xsd:string" minOccurs="0"/> + <xsd:element name="employerCompanyName" type="xsd:string" minOccurs="0"/> + <xsd:element name="employerCountry" type="xsd:string" minOccurs="0"/> + <xsd:element name="employerPhoneNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="employerPhoneType" type="xsd:string" minOccurs="0"/> + <xsd:element name="employerState" type="xsd:string" minOccurs="0"/> + <xsd:element name="employerPostalCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="shipToPhoneType" type="xsd:string" minOccurs="0"/> + <xsd:element name="billToPhoneType" type="xsd:string" minOccurs="0"/> + <xsd:element name="methodOfPayment" type="xsd:string" minOccurs="0"/> + <xsd:element name="productType" type="xsd:string" minOccurs="0"/> + <xsd:element name="customerAuthenticatedByMerchant" type="xsd:string" minOccurs="0"/> + <xsd:element name="backOfficeIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="shipToEqualsBillToNameIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="shipToEqualsBillToAddressIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="alternateIPAddress" type="xsd:string" minOccurs="0"/> + <xsd:element name="businessLegalName" type="xsd:string" minOccurs="0"/> + <xsd:element name="dbaName" type="xsd:string" minOccurs="0"/> + <xsd:element name="businessAddress1" type="xsd:string" minOccurs="0"/> + <xsd:element name="businessAddress2" type="xsd:string" minOccurs="0"/> + <xsd:element name="businessCity" type="xsd:string" minOccurs="0"/> + <xsd:element name="businessState" type="xsd:string" minOccurs="0"/> + <xsd:element name="businessPostalCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="businessCountry" type="xsd:string" minOccurs="0"/> + <xsd:element name="businessMainPhone" type="xsd:string" minOccurs="0"/> + <xsd:element name="userID" type="xsd:string" minOccurs="0"/> + <xsd:element name="pin" type="xsd:string" minOccurs="0"/> + <xsd:element name="adminLastName" type="xsd:string" minOccurs="0"/> + <xsd:element name="adminFirstName" type="xsd:string" minOccurs="0"/> + <xsd:element name="adminPhone" type="xsd:string" minOccurs="0"/> + <xsd:element name="adminFax" type="xsd:string" minOccurs="0"/> + <xsd:element name="adminEmailAddress" type="xsd:string" minOccurs="0"/> + <xsd:element name="adminTitle" type="xsd:string" minOccurs="0"/> + <xsd:element name="supervisorLastName" type="xsd:string" minOccurs="0"/> + <xsd:element name="supervisorFirstName" type="xsd:string" minOccurs="0"/> + <xsd:element name="supervisorEmailAddress" type="xsd:string" minOccurs="0"/> + <xsd:element name="businessDAndBNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="businessTaxID" type="xsd:string" minOccurs="0"/> + <xsd:element name="businessNAICSCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="businessType" type="xsd:string" minOccurs="0"/> + <xsd:element name="businessYearsInBusiness" type="xsd:string" minOccurs="0"/> + <xsd:element name="businessNumberOfEmployees" type="xsd:string" minOccurs="0"/> + <xsd:element name="businessPONumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="businessLoanType" type="xsd:string" minOccurs="0"/> + <xsd:element name="businessApplicationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="businessProductCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="pgLastName" type="xsd:string" minOccurs="0"/> + <xsd:element name="pgFirstName" type="xsd:string" minOccurs="0"/> + <xsd:element name="pgSSN" type="xsd:string" minOccurs="0"/> + <xsd:element name="pgDateOfBirth" type="xsd:string" minOccurs="0"/> + <xsd:element name="pgAnnualIncome" type="xsd:string" minOccurs="0"/> + <xsd:element name="pgIncomeCurrencyType" type="xsd:string" minOccurs="0"/> + <xsd:element name="pgResidenceStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="pgCheckingAccountIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="pgSavingsAccountIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="pgYearsAtEmployer" type="xsd:string" minOccurs="0"/> + <xsd:element name="pgYearsAtResidence" type="xsd:string" minOccurs="0"/> + <xsd:element name="pgHomeAddress1" type="xsd:string" minOccurs="0"/> + <xsd:element name="pgHomeAddress2" type="xsd:string" minOccurs="0"/> + <xsd:element name="pgHomeCity" type="xsd:string" minOccurs="0"/> + <xsd:element name="pgHomeState" type="xsd:string" minOccurs="0"/> + <xsd:element name="pgHomePostalCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="pgHomeCountry" type="xsd:string" minOccurs="0"/> + <xsd:element name="pgEmailAddress" type="xsd:string" minOccurs="0"/> + <xsd:element name="pgHomePhone" type="xsd:string" minOccurs="0"/> + <xsd:element name="pgTitle" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="OtherTax"> + <xsd:sequence> + <xsd:element name="vatTaxAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="vatTaxRate" type="tns:amount" minOccurs="0"/> + <xsd:element name="vatTaxAmountSign" type="xsd:string" minOccurs="0"/> + <xsd:element name="alternateTaxAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="alternateTaxIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="alternateTaxID" type="xsd:string" minOccurs="0"/> + <xsd:element name="localTaxAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="localTaxIndicator" type="xsd:integer" minOccurs="0"/> + <xsd:element name="nationalTaxAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="nationalTaxIndicator" type="xsd:integer" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="Aft"> + <xsd:sequence> + <xsd:element name="indicator" type="xsd:string" minOccurs="0" /> + <xsd:element name="serviceFee" type="xsd:string" minOccurs="0" /> + <xsd:element name="foreignExchangeFee" type="xsd:string" minOccurs="0" /> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="Wallet"> + <xsd:sequence> + <xsd:element name="type" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="PurchaseTotals"> + <xsd:sequence> + <xsd:element name="currency" type="xsd:string" minOccurs="0"/> + <xsd:element name="discountAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="discountAmountSign" type="xsd:string" minOccurs="0"/> + <xsd:element name="discountManagementIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="taxAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="dutyAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="dutyAmountSign" type="xsd:string" minOccurs="0"/> + <xsd:element name="grandTotalAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="freightAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="freightAmountSign" type="xsd:string" minOccurs="0"/> + <xsd:element name="foreignAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="foreignCurrency" type="xsd:string" minOccurs="0"/> + <xsd:element name="originalAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="originalCurrency" type="xsd:string" minOccurs="0"/> + <xsd:element name="exchangeRate" type="tns:amount" minOccurs="0"/> + <xsd:element name="exchangeRateTimeStamp" type="xsd:string" minOccurs="0"/> + <xsd:element name="additionalAmountType0" type="xsd:string" minOccurs="0"/> + <xsd:element name="additionalAmount0" type="xsd:string" minOccurs="0"/> + <xsd:element name="additionalAmountType1" type="xsd:string" minOccurs="0"/> + <xsd:element name="additionalAmount1" type="xsd:string" minOccurs="0"/> + <xsd:element name="additionalAmountType2" type="xsd:string" minOccurs="0"/> + <xsd:element name="additionalAmount2" type="xsd:string" minOccurs="0"/> + <xsd:element name="additionalAmountType3" type="xsd:string" minOccurs="0"/> + <xsd:element name="additionalAmount3" type="xsd:string" minOccurs="0"/> + <xsd:element name="additionalAmountType4" type="xsd:string" minOccurs="0"/> + <xsd:element name="additionalAmount4" type="xsd:string" minOccurs="0"/> + <xsd:element name="serviceFeeAmount" type="tns:amount" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="FundingTotals"> + <xsd:sequence> + <xsd:element name="currency" type="xsd:string" minOccurs="0"/> + <xsd:element name="grandTotalAmount" type="tns:amount" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="GECC"> + <xsd:sequence> + <xsd:element name="saleType" type="xsd:string" minOccurs="0"/> + <xsd:element name="planNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="sequenceNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="promotionEndDate" type="xsd:string" minOccurs="0"/> + <xsd:element name="promotionPlan" type="xsd:string" minOccurs="0"/> + <xsd:element name="line" type="xsd:string" minOccurs="0" maxOccurs="7"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="UCAF"> + <xsd:sequence> + <xsd:element name="authenticationData" type="xsd:string" minOccurs="0"/> + <xsd:element name="collectionIndicator" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="FundTransfer"> + <xsd:sequence> + <xsd:element name="accountNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="accountName" type="xsd:string" minOccurs="0"/> + <xsd:element name="bankCheckDigit" type="xsd:string" minOccurs="0"/> + <xsd:element name="iban" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="BankInfo"> + <xsd:sequence> + <xsd:element name="bankCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="name" type="xsd:string" minOccurs="0"/> + <xsd:element name="address" type="xsd:string" minOccurs="0"/> + <xsd:element name="city" type="xsd:string" minOccurs="0"/> + <xsd:element name="country" type="xsd:string" minOccurs="0"/> + <xsd:element name="branchCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="swiftCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="sortCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="issuerID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="RecurringSubscriptionInfo"> + <xsd:sequence> + <xsd:element name="subscriptionID" type="xsd:string" minOccurs="0"/> + <xsd:element name="status" type="xsd:string" minOccurs="0"/> + <xsd:element name="amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="numberOfPayments" type="xsd:integer" minOccurs="0"/> + <xsd:element name="numberOfPaymentsToAdd" type="xsd:integer" minOccurs="0"/> + <xsd:element name="automaticRenew" type="tns:boolean" minOccurs="0"/> + <xsd:element name="frequency" type="xsd:string" minOccurs="0"/> + <xsd:element name="startDate" type="xsd:string" minOccurs="0"/> + <xsd:element name="endDate" type="xsd:string" minOccurs="0"/> + <xsd:element name="approvalRequired" type="tns:boolean" minOccurs="0"/> + <xsd:element name="event" type="tns:PaySubscriptionEvent" minOccurs="0"/> + <xsd:element name="billPayment" type="tns:boolean" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="PaySubscriptionEvent"> + <xsd:sequence> + <xsd:element name="amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="approvedBy" type="xsd:string" minOccurs="0"/> + <xsd:element name="number" type="xsd:integer" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="Subscription"> + <xsd:sequence> + <xsd:element name="title" type="xsd:string" minOccurs="0"/> + <xsd:element name="paymentMethod" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="PaymentNetworkToken"> + <xsd:sequence> + <xsd:element name="requestorID" type="xsd:string" minOccurs="0"/> + <xsd:element name="transactionType" type="xsd:string" minOccurs="0"/> + <xsd:element name="assuranceLevel" type="xsd:string" minOccurs="0"/> + <xsd:element name="accountStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="originalCardCategory" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="DecisionManager"> + <xsd:sequence> + <xsd:element name="enabled" type="tns:boolean" minOccurs="0"/> + <xsd:element name="profile" type="xsd:string" minOccurs="0"/> + <xsd:element name="travelData" type="tns:DecisionManagerTravelData" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="DecisionManagerTravelData"> + <xsd:sequence> + <xsd:element name="leg" type="tns:DecisionManagerTravelLeg" minOccurs="0" maxOccurs="100"/> + <xsd:element name="departureDateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="completeRoute" type="xsd:string" minOccurs="0"/> + <xsd:element name="journeyType" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="DecisionManagerTravelLeg"> + <xsd:sequence> + <xsd:element name="origin" type="xsd:string" minOccurs="0"/> + <xsd:element name="destination" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="id" type="xsd:integer" use="optional"/> + </xsd:complexType> + <xsd:complexType name="Batch"> + <xsd:sequence> + <xsd:element name="batchID" type="xsd:string" minOccurs="0"/> + <xsd:element name="recordID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="PayPal"> + <xsd:sequence> + <xsd:any processContents="skip" minOccurs="0" maxOccurs="999"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="JPO"> + <xsd:sequence> + <xsd:element name="paymentMethod" type="xsd:integer" minOccurs="0"/> + <xsd:element name="bonusAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="bonuses" type="xsd:integer" minOccurs="0"/> + <xsd:element name="installments" type="xsd:integer" minOccurs="0"/> + <xsd:element name="firstBillingMonth" type="xsd:integer" minOccurs="0"/> + <xsd:element name="jccaTerminalID" type="xsd:integer" minOccurs="0"/> + <xsd:element name="issuerMessage" type="xsd:integer" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <!-- Vme Reseller Service--> + <xsd:complexType name="AP"> + <xsd:sequence> + <xsd:element name="orderID" type="xsd:string" minOccurs="0"/> + <xsd:element name="customerRepresentativeID" type="xsd:string" minOccurs="0" /> + <xsd:element name="productDescription" type="xsd:string" minOccurs="0" /> + <xsd:element name="settlementCurrency" type="xsd:string" minOccurs="0" /> + <xsd:element name="subtotalAmount" type="xsd:string" minOccurs="0" /> + <xsd:element name="shippingAmount" type="xsd:string" minOccurs="0" /> + <xsd:element name="handlingAmount" type="xsd:string" minOccurs="0" /> + <xsd:element name="shippingHandlingAmount" type="xsd:string" minOccurs="0" /> + <xsd:element name="additionalAmount" type="xsd:string" minOccurs="0" /> + <xsd:element name="taxAmount" type="xsd:string" minOccurs="0" /> + <xsd:element name="giftWrapAmount" type="xsd:string" minOccurs="0" /> + <xsd:element name="discountAmount" type="xsd:string" minOccurs="0" /> + <xsd:element name="purchaseID" type="xsd:string" minOccurs="0" /> + <xsd:element name="productID" type="xsd:string" minOccurs="0" /> + </xsd:sequence> + </xsd:complexType> + <!-- apAuthService --> + <xsd:complexType name="APAuthService"> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <!-- End of apAuthService --> + <!-- apAuthReversalService --> + <xsd:complexType name="APAuthReversalService"> + <xsd:sequence> + <xsd:element name="authRequestID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <!-- End of apAuthReversalService --> + <!-- apCaptureService --> + <xsd:complexType name="APCaptureService"> + <xsd:sequence> + <xsd:element name="authRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <!-- End of apCaptureService --> + <!-- apRefundService --> + <xsd:complexType name="APRefundService"> + <xsd:sequence> + <xsd:element name="captureRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="reason" type="xsd:string" minOccurs="0"/> + <xsd:element name="note" type="xsd:string" minOccurs="0"/> + <xsd:element name="apInitiateRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="returnRef" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <!-- End of apRefundService --> + <xsd:complexType name="APCheckOutDetailsService"> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <!-- End of apCheckoutDetailsService --> + <xsd:complexType name="APTransactionDetailsService"> + <xsd:sequence> + <xsd:element name="transactionDetailsRequestID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <!-- APConfirmPurchaseService --> + <xsd:complexType name="APConfirmPurchaseService"> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <!-- End of APConfirmPurchaseService --> + <!--PayPalGetTxnDetails--> + <xsd:complexType name="PayPalGetTxnDetailsService"> + <xsd:sequence> + <xsd:element name="transactionID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <!-- End of PayPalGetTxnDetails --> + <!--PayPalTransactionSearch--> + <xsd:complexType name="PayPalTransactionSearchService"> + <xsd:sequence> + <xsd:element name="startDate" type="xsd:string" minOccurs="0"/> + <xsd:element name="endDate" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalCustomerEmail" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalReceiptId" type="xsd:string" minOccurs="0"/> + <xsd:element name="transactionID" type="xsd:string" minOccurs="0"/> + <xsd:element name="invoiceNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="grandTotalAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="currency" type="xsd:string" minOccurs="0"/> + <xsd:element name="paymentStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerSalutation" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerFirstname" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerMiddlename" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerLastname" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerSuffix" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <!-- End of PayPalTransactionSearch --> + <!-- Credit card recipient data --> + <xsd:complexType name="Recipient"> +<xsd:sequence> + <xsd:element name="dateOfBirth" type="xsd:string" minOccurs="0"/> + <xsd:element name="postalCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="accountID" type="xsd:string" minOccurs="0"/> + <xsd:element name="lastName" type="xsd:string" minOccurs="0"/> + <xsd:element name="name" type="xsd:string" minOccurs="0"/> +</xsd:sequence> + </xsd:complexType> + <!-- End of Credit card recipient data --> + <xsd:complexType name="Sender"> + <xsd:sequence> + <xsd:element name="referenceNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="sourceOfFunds" type="xsd:string" minOccurs="0"/> + <xsd:element name="name" type="xsd:string" minOccurs="0"/> + <xsd:element name="address" type="xsd:string" minOccurs="0"/> + <xsd:element name="city" type="xsd:string" minOccurs="0"/> + <xsd:element name="state" type="xsd:string" minOccurs="0"/> + <xsd:element name="postalCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="country" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="RequestMessage"> + <xsd:sequence> + <xsd:element name="merchantID" type="xsd:string" + minOccurs="0" /> + <xsd:element name="merchantReferenceCode" type="xsd:string" + minOccurs="0" /> + <xsd:element name="debtIndicator" type="tns:boolean" + minOccurs="0" /> + <xsd:element name="clientLibrary" type="xsd:string" + minOccurs="0" /> + <xsd:element name="clientLibraryVersion" type="xsd:string" + minOccurs="0" /> + <xsd:element name="clientEnvironment" type="xsd:string" + minOccurs="0" /> + <xsd:element name="clientSecurityLibraryVersion" + type="xsd:string" minOccurs="0" /> + <xsd:element name="clientApplication" type="xsd:string" + minOccurs="0" /> + <xsd:element name="clientApplicationVersion" + type="xsd:string" minOccurs="0" /> + <xsd:element name="clientApplicationUser" type="xsd:string" + minOccurs="0" /> + <xsd:element name="routingCode" type="xsd:string" + minOccurs="0" /> + <xsd:element name="comments" type="xsd:string" + minOccurs="0" /> + <xsd:element name="returnURL" type="xsd:string" + minOccurs="0" /> + <xsd:element name="invoiceHeader" type="tns:InvoiceHeader" + minOccurs="0" /> + <xsd:element name="aggregatorMerchantIdentifier" type="xsd:string" minOccurs="0"/> + <xsd:element name="customerID" type="xsd:string" minOccurs="0"/> + <xsd:element name="customerFirstName" type="xsd:string" minOccurs="0"/> + <xsd:element name="customerLastName" type="xsd:string" minOccurs="0"/> + <xsd:element name="billTo" type="tns:BillTo" minOccurs="0" /> + <xsd:element name="shipTo" type="tns:ShipTo" minOccurs="0" /> + <xsd:element name="shipFrom" type="tns:ShipFrom" + minOccurs="0" /> + <xsd:element name="item" type="tns:Item" minOccurs="0" + maxOccurs="1000" /> + <xsd:element name="purchaseTotals" type="tns:PurchaseTotals" + minOccurs="0" /> + <xsd:element name="fundingTotals" type="tns:FundingTotals" + minOccurs="0" /> + <xsd:element name="dcc" type="tns:DCC" minOccurs="0" /> + <xsd:element name="pos" type="tns:Pos" minOccurs="0" /> + <xsd:element name="encryptedPayment" type="tns:EncryptedPayment" minOccurs="0" /> + <xsd:element name="installment" type="tns:Installment" + minOccurs="0" /> + <xsd:element name="card" type="tns:Card" minOccurs="0" /> + <xsd:element name="check" type="tns:Check" minOccurs="0" /> + <xsd:element name="bml" type="tns:BML" minOccurs="0" /> + <xsd:element name="gecc" type="tns:GECC" minOccurs="0" /> + <xsd:element name="ucaf" type="tns:UCAF" minOccurs="0" /> + <xsd:element name="fundTransfer" type="tns:FundTransfer" + minOccurs="0" /> + <xsd:element name="bankInfo" type="tns:BankInfo" + minOccurs="0" /> + <xsd:element name="subscription" type="tns:Subscription" + minOccurs="0" /> + <xsd:element name="recurringSubscriptionInfo" + type="tns:RecurringSubscriptionInfo" minOccurs="0" /> + <xsd:element name="decisionManager" + type="tns:DecisionManager" minOccurs="0" /> + <xsd:element name="otherTax" type="tns:OtherTax" + minOccurs="0" /> + <xsd:element name="paypal" type="tns:PayPal" minOccurs="0" /> + <xsd:element name="merchantDefinedData" + type="tns:MerchantDefinedData" minOccurs="0" /> + <xsd:element name="merchantSecureData" + type="tns:MerchantSecureData" minOccurs="0" /> + <xsd:element name="jpo" type="tns:JPO" minOccurs="0" /> + <xsd:element name="orderRequestToken" type="xsd:string" + minOccurs="0" /> + <xsd:element name="linkToRequest" type="xsd:string" + minOccurs="0" /> + <xsd:element name="serviceFee" type="tns:ServiceFee" minOccurs="0" /> + <xsd:element name="ccAuthService" type="tns:CCAuthService" + minOccurs="0" /> + <xsd:element name="octService" type="tns:OCTService" + minOccurs="0" /> + <xsd:element name="ccSaleService" type="tns:CCSaleService" minOccurs="0" /> + + <xsd:element name="ccSaleCreditService" type="tns:CCSaleCreditService" minOccurs="0" /> + + <xsd:element name="ccSaleReversalService" type="tns:CCSaleReversalService" minOccurs="0" /> + <xsd:element name="ccIncrementalAuthService" type="tns:CCIncrementalAuthService" minOccurs="0" /> + <xsd:element name="ccCaptureService" + type="tns:CCCaptureService" minOccurs="0" /> + <xsd:element name="ccCreditService" + type="tns:CCCreditService" minOccurs="0" /> + <xsd:element name="ccAuthReversalService" + type="tns:CCAuthReversalService" minOccurs="0" /> + <xsd:element name="ccAutoAuthReversalService" + type="tns:CCAutoAuthReversalService" minOccurs="0" /> + <xsd:element name="ccDCCService" type="tns:CCDCCService" + minOccurs="0" /> + <xsd:element name="serviceFeeCalculateService" type="tns:ServiceFeeCalculateService" + minOccurs="0" /> + <xsd:element name="ecDebitService" type="tns:ECDebitService" + minOccurs="0" /> + <xsd:element name="ecCreditService" + type="tns:ECCreditService" minOccurs="0" /> + <xsd:element name="ecAuthenticateService" + type="tns:ECAuthenticateService" minOccurs="0" /> + <xsd:element name="payerAuthEnrollService" + type="tns:PayerAuthEnrollService" minOccurs="0" /> + <xsd:element name="payerAuthValidateService" + type="tns:PayerAuthValidateService" minOccurs="0" /> + <xsd:element name="taxService" type="tns:TaxService" + minOccurs="0" /> + <xsd:element name="dmeService" type="tns:DMEService" + minOccurs="0" /> + <xsd:element name="afsService" type="tns:AFSService" + minOccurs="0" /> + <xsd:element name="davService" type="tns:DAVService" + minOccurs="0" /> + <xsd:element name="exportService" type="tns:ExportService" + minOccurs="0" /> + <xsd:element name="fxRatesService" type="tns:FXRatesService" + minOccurs="0" /> + <xsd:element name="bankTransferService" + type="tns:BankTransferService" minOccurs="0" /> + <xsd:element name="bankTransferRefundService" + type="tns:BankTransferRefundService" minOccurs="0" /> + <xsd:element name="bankTransferRealTimeService" + type="tns:BankTransferRealTimeService" minOccurs="0" /> + <xsd:element name="directDebitMandateService" + type="tns:DirectDebitMandateService" minOccurs="0" /> + <xsd:element name="directDebitService" + type="tns:DirectDebitService" minOccurs="0" /> + <xsd:element name="directDebitRefundService" + type="tns:DirectDebitRefundService" minOccurs="0" /> + <xsd:element name="directDebitValidateService" + type="tns:DirectDebitValidateService" minOccurs="0" /> + <xsd:element name="paySubscriptionCreateService" + type="tns:PaySubscriptionCreateService" minOccurs="0" /> + <xsd:element name="paySubscriptionUpdateService" + type="tns:PaySubscriptionUpdateService" minOccurs="0" /> + <xsd:element name="paySubscriptionEventUpdateService" + type="tns:PaySubscriptionEventUpdateService" minOccurs="0" /> + <xsd:element name="paySubscriptionRetrieveService" + type="tns:PaySubscriptionRetrieveService" minOccurs="0" /> + <xsd:element name="paySubscriptionDeleteService" + type="tns:PaySubscriptionDeleteService" minOccurs="0" /> + <xsd:element name="payPalPaymentService" + type="tns:PayPalPaymentService" minOccurs="0" /> + <xsd:element name="payPalCreditService" + type="tns:PayPalCreditService" minOccurs="0" /> + <xsd:element name="voidService" type="tns:VoidService" + minOccurs="0" /> + <xsd:element name="businessRules" type="tns:BusinessRules" + minOccurs="0" /> + <xsd:element name="pinlessDebitService" + type="tns:PinlessDebitService" minOccurs="0" /> + <xsd:element name="pinlessDebitValidateService" + type="tns:PinlessDebitValidateService" minOccurs="0" /> + <xsd:element name="pinlessDebitReversalService" + type="tns:PinlessDebitReversalService" minOccurs="0" /> + <xsd:element name="batch" type="tns:Batch" minOccurs="0" /> + <xsd:element name="airlineData" type="tns:AirlineData" + minOccurs="0" /> + <xsd:element name="ancillaryData" type="tns:AncillaryData" + minOccurs="0" /> + <xsd:element name="lodgingData" type="tns:LodgingData" minOccurs="0" /> + <xsd:element name="payPalButtonCreateService" + type="tns:PayPalButtonCreateService" minOccurs="0" /> + <xsd:element name="payPalPreapprovedPaymentService" + type="tns:PayPalPreapprovedPaymentService" minOccurs="0" /> + <xsd:element name="payPalPreapprovedUpdateService" + type="tns:PayPalPreapprovedUpdateService" minOccurs="0" /> + <xsd:element name="riskUpdateService" + type="tns:RiskUpdateService" minOccurs="0" /> + <xsd:element name="fraudUpdateService" + type="tns:FraudUpdateService" minOccurs="0" /> + <xsd:element name="caseManagementActionService" + type="tns:CaseManagementActionService" minOccurs="0" /> + <xsd:element name="reserved" type="tns:RequestReserved" + minOccurs="0" maxOccurs="999" /> + <xsd:element name="deviceFingerprintID" type="xsd:string" + minOccurs="0" /> + <xsd:element name="deviceFingerprintRaw" type="tns:boolean" + minOccurs="0" /> + <xsd:element name="deviceFingerprintHash" type="xsd:string" + minOccurs="0" /> + <xsd:element name="payPalRefundService" + type="tns:PayPalRefundService" minOccurs="0" /> + <xsd:element name="payPalAuthReversalService" + type="tns:PayPalAuthReversalService" minOccurs="0" /> + <xsd:element name="payPalDoCaptureService" + type="tns:PayPalDoCaptureService" minOccurs="0" /> + <xsd:element name="payPalEcDoPaymentService" + type="tns:PayPalEcDoPaymentService" minOccurs="0" /> + <xsd:element name="payPalEcGetDetailsService" + type="tns:PayPalEcGetDetailsService" minOccurs="0" /> + <xsd:element name="payPalEcSetService" + type="tns:PayPalEcSetService" minOccurs="0" /> + <xsd:element name="payPalEcOrderSetupService" + type="tns:PayPalEcOrderSetupService" minOccurs="0" /> + <xsd:element name="payPalAuthorizationService" + type="tns:PayPalAuthorizationService" minOccurs="0" /> + <xsd:element name="payPalUpdateAgreementService" + type="tns:PayPalUpdateAgreementService" minOccurs="0" /> + <xsd:element name="payPalCreateAgreementService" + type="tns:PayPalCreateAgreementService" minOccurs="0" /> + <xsd:element name="payPalDoRefTransactionService" + type="tns:PayPalDoRefTransactionService" minOccurs="0" /> + <xsd:element name="chinaPaymentService" + type="tns:ChinaPaymentService" minOccurs="0" /> + <xsd:element name="chinaRefundService" + type="tns:ChinaRefundService" minOccurs="0" /> + <xsd:element name="boletoPaymentService" + type="tns:BoletoPaymentService" minOccurs="0" /> + <xsd:element name="apPaymentType" type="xsd:string" + minOccurs="0"/> + <xsd:element name="apInitiateService" + type="tns:APInitiateService" minOccurs="0" /> + <xsd:element name="apCheckStatusService" + type="tns:APCheckStatusService" minOccurs="0" /> + <xsd:element name="ignoreCardExpiration" type="tns:boolean" + minOccurs="0" /> + <xsd:element name="reportGroup" type="xsd:string" + minOccurs="0" /> + <xsd:element name="processorID" type="xsd:string" + minOccurs="0" /> + <xsd:element name="thirdPartyCertificationNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="transactionLocalDateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="solutionProviderTransactionID" type="xsd:string" minOccurs="0" /> + <xsd:element name="surchargeAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="surchargeSign" type="xsd:string" minOccurs="0"/> + <xsd:element name="pinDataEncryptedPIN" type="xsd:string" minOccurs="0"/> + <xsd:element name="pinDataKeySerialNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="cashbackAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="pinDebitPurchaseService" type="tns:PinDebitPurchaseService" minOccurs="0"/> + <xsd:element name="pinDebitCreditService" type="tns:PinDebitCreditService" minOccurs="0"/> + <xsd:element name="pinDebitReversalService" type="tns:PinDebitReversalService" minOccurs="0"/> + <xsd:element name="ap" type="tns:AP" minOccurs="0" /> + <xsd:element name="apAuthService" type="tns:APAuthService" minOccurs="0" /> + <xsd:element name="apAuthReversalService" type="tns:APAuthReversalService" minOccurs="0" /> + <xsd:element name="apCaptureService" type="tns:APCaptureService" minOccurs="0" /> + <xsd:element name="apRefundService" type="tns:APRefundService" minOccurs="0" /> + <xsd:element name="apCheckoutDetailsService" type="tns:APCheckOutDetailsService" minOccurs="0" /> + <xsd:element name="apTransactionDetailsService" type="tns:APTransactionDetailsService" minOccurs="0" /> + <xsd:element name="apConfirmPurchaseService" type="tns:APConfirmPurchaseService" minOccurs="0" /> + <xsd:element name="payPalGetTxnDetailsService" type="tns:PayPalGetTxnDetailsService" minOccurs="0" /> + <xsd:element name="payPalTransactionSearchService" type="tns:PayPalTransactionSearchService" minOccurs="0" /> + <xsd:element name="ccDCCUpdateService" type="tns:CCDCCUpdateService" minOccurs="0"/> + <xsd:element name="emvRequest" type="tns:EmvRequest" minOccurs="0" /> + <xsd:element name="merchantTransactionIdentifier" type="xsd:string" minOccurs="0" /> + <xsd:element name="hostedDataCreateService" type="tns:HostedDataCreateService" minOccurs="0"/> + <xsd:element name="hostedDataRetrieveService" type="tns:HostedDataRetrieveService" minOccurs="0"/> + <xsd:element name="merchantCategoryCode" type="xsd:string" minOccurs="0" /> + <xsd:element name="salesSlipNumber" type="xsd:string" minOccurs="0" /> + <xsd:element name="merchandiseCode" type="xsd:string" minOccurs="0" /> + <xsd:element name="paymentInitiationChannel" type="xsd:string" minOccurs="0" /> + <xsd:element name="extendedCreditTotalCount" type="xsd:string" minOccurs="0" /> + <xsd:element name="authIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="paymentNetworkToken" type="tns:PaymentNetworkToken" minOccurs="0"/> + <xsd:element name="recipient" type="tns:Recipient" minOccurs="0"/> + <xsd:element name="sender" type="tns:Sender" minOccurs="0"/> + <xsd:element name="autoRentalData" type="tns:AutoRentalData" minOccurs="0" /> + <xsd:element name="paymentSolution" type="xsd:string" minOccurs="0" /> + <xsd:element name="vc" type="tns:VC" minOccurs="0" /> + <xsd:element name="decryptVisaCheckoutDataService" type="tns:DecryptVisaCheckoutDataService" minOccurs="0" /> + <xsd:element name="taxManagementIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="promotionGroup" type="tns:PromotionGroup" minOccurs="0" maxOccurs="100"/> + <xsd:element name="wallet" type="tns:Wallet" minOccurs="0" /> + <xsd:element name="aft" type="tns:Aft" minOccurs="0" /> + <xsd:element name="balanceInquiry" type="tns:boolean" minOccurs="0" /> + <xsd:element name="prenoteTransaction" type="tns:boolean" minOccurs="0"/> + <xsd:element name="encryptPaymentDataService" type="tns:EncryptPaymentDataService" minOccurs="0"/> + <xsd:element name="nationalNetDomesticData" type="xsd:string" minOccurs="0"/> + <xsd:element name="subsequentAuth" type="xsd:string" minOccurs="0"/> + <xsd:element name="binLookupService" type="tns:BinLookupService" minOccurs="0" /> + </xsd:sequence> + </xsd:complexType> + + <!-- added for Visa Checkout --> + <xsd:complexType name="VC"> + <xsd:sequence> + <xsd:element name="orderID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="DecryptVisaCheckoutDataService"> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + + <xsd:complexType name="DCC"> + <xsd:sequence> + <xsd:element name="dccIndicator" type="xsd:integer" minOccurs="0"/> + <xsd:element name="referenceNumber" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="Promotion"> + <xsd:sequence> + <xsd:element name="discountedAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="type" type="xsd:string" minOccurs="0"/> + <xsd:element name="code" type="xsd:string" minOccurs="0"/> + <xsd:element name="receiptData" type="xsd:string" minOccurs="0"/> + <xsd:element name="discountApplied" type="tns:amount" minOccurs="0"/> + <xsd:element name="description" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="PromotionGroup"> + <xsd:sequence> + <xsd:element name="subtotalAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="taxRate" type="tns:amount" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="id" type="xsd:integer" use="optional"/> + </xsd:complexType> + + <xsd:complexType name="PromotionGroupReply"> + <xsd:sequence> + <xsd:element name="discountApplied" type="tns:amount" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="id" type="xsd:integer" use="optional"/> + </xsd:complexType> + + <xsd:complexType name="CCAuthReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="authorizationCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="avsCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="avsCodeRaw" type="xsd:string" minOccurs="0"/> + <xsd:element name="cvCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="cvCodeRaw" type="xsd:string" minOccurs="0"/> + <xsd:element name="personalIDCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="authorizedDateTime" type="tns:dateTime" minOccurs="0"/> + <!-- dateTime --> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="bmlAccountNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="authFactorCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="fundingTotals" type="tns:FundingTotals" minOccurs="0"/> + <xsd:element name="fxQuoteID" type="xsd:string" minOccurs="0"/> + <xsd:element name="fxQuoteRate" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="fxQuoteType" type="xsd:string" minOccurs="0"/> + <xsd:element name="fxQuoteExpirationDateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="authRecord" type="xsd:string" minOccurs="0"/> + <xsd:element name="merchantAdviceCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="merchantAdviceCodeRaw" type="xsd:string" minOccurs="0"/> + <xsd:element name="cavvResponseCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="cavvResponseCodeRaw" type="xsd:string" minOccurs="0"/> + <xsd:element name="authenticationXID" type="xsd:string" minOccurs="0"/> + <xsd:element name="authorizationXID" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorCardType" type="xsd:string" minOccurs="0"/> + <xsd:element name="accountBalance" type="tns:amount" minOccurs="0"/> + <xsd:element name="forwardCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="enhancedDataEnabled" type="xsd:string" minOccurs="0"/> + <xsd:element name="referralResponseNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="subResponseCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="approvedAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="creditLine" type="xsd:string" minOccurs="0"/> + <xsd:element name="approvedTerms" type="xsd:string" minOccurs="0"/> + <xsd:element name="paymentNetworkTransactionID" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardCategory" type="xsd:string" minOccurs="0"/> + <xsd:element name="ownerMerchantID" type="xsd:string" minOccurs="0"/> + <xsd:element name="requestAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="requestCurrency" type="xsd:string" minOccurs="0"/> + <xsd:element name="accountBalanceCurrency" type="xsd:string" minOccurs="0"/> + <xsd:element name="accountBalanceSign" type="xsd:string" minOccurs="0"/> + <xsd:element name="amountType" type="xsd:string" minOccurs="0"/> + <xsd:element name="accountType" type="xsd:string" minOccurs="0"/> + <xsd:element name="affluenceIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="evEmail" type="xsd:string" minOccurs="0"/> + <xsd:element name="evPhoneNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="evPostalCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="evName" type="xsd:string" minOccurs="0"/> + <xsd:element name="evStreet" type="xsd:string" minOccurs="0"/> + <xsd:element name="evEmailRaw" type="xsd:string" minOccurs="0"/> + <xsd:element name="evPhoneNumberRaw" type="xsd:string" minOccurs="0"/> + <xsd:element name="evPostalCodeRaw" type="xsd:string" minOccurs="0"/> + <xsd:element name="evNameRaw" type="xsd:string" minOccurs="0"/> + <xsd:element name="evStreetRaw" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardGroup" type="xsd:string" minOccurs="0"/> + <xsd:element name="posData" type="xsd:string" minOccurs="0"/> + <xsd:element name="transactionID" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardIssuerCountry" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardRegulated" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardCommercial" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardPrepaid" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardPayroll" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardHealthcare" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardSignatureDebit" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardPINlessDebit" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardLevel3Eligible" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorTransactionID" type="xsd:string" minOccurs="0"/> + <xsd:element name="providerReasonCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="providerReasonDescription" type="xsd:string" minOccurs="0"/> + <xsd:element name="providerPassThroughData" type="xsd:string" minOccurs="0"/> + <xsd:element name="providerCVNResponseCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="providerAVSResponseCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="providerAcquirerBankCode" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="OCTReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="requestDateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="approvalCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="amount" type="tns:amount" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="CCSaleReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="authorizationCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="avsCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="avsCodeRaw" type="xsd:string" minOccurs="0"/> + <xsd:element name="cvCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="cvCodeRaw" type="xsd:string" minOccurs="0"/> + <xsd:element name="cavvResponseCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="cavvResponseCodeRaw" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardGroup" type="xsd:string" minOccurs="0"/> + <xsd:element name="paymentNetworkTransactionID" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardCategory" type="xsd:string" minOccurs="0"/> + <xsd:element name="accountBalance" type="tns:amount" minOccurs="0"/> + <xsd:element name="authorizedDateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="requestAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="accountBalanceCurrency" type="xsd:string" minOccurs="0"/> + <xsd:element name="accountBalanceSign" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="CCSaleCreditReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="authorizationCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="authorizedDateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="paymentNetworkTransactionID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="CCSaleReversalReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="authorizationCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="requestDateTime" type="tns:dateTime" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="CCIncrementalAuthReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="amount" type="tns:amount" minOccurs="0" /> + <xsd:element name="authorizationCode" type="xsd:string" minOccurs="0" /> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0" /> + <xsd:element name="authorizedDateTime" type="tns:dateTime" minOccurs="0" /> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0" /> + <xsd:element name="paymentNetworkTransactionID" type="xsd:string" minOccurs="0" /> + <xsd:element name="cardCategory" type="xsd:string" minOccurs="0" /> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="CCCaptureReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="requestDateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="fundingTotals" type="tns:FundingTotals" minOccurs="0"/> + <xsd:element name="fxQuoteID" type="xsd:string" minOccurs="0"/> + <xsd:element name="fxQuoteRate" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="fxQuoteType" type="xsd:string" minOccurs="0"/> + <xsd:element name="fxQuoteExpirationDateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="purchasingLevel3Enabled" type="xsd:string" minOccurs="0"/> + <xsd:element name="enhancedDataEnabled" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="ServiceFeeCalculateReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer" /> + <xsd:element name="amount" type="tns:amount" minOccurs="0" /> + <xsd:element name="requestDateTime" type="tns:dateTime" minOccurs="0" /> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="CCCreditReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="requestDateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="purchasingLevel3Enabled" type="xsd:string" minOccurs="0"/> + <xsd:element name="enhancedDataEnabled" type="xsd:string" minOccurs="0"/> + <xsd:element name="authorizationXID" type="xsd:string" minOccurs="0"/> + <xsd:element name="forwardCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="ownerMerchantID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="PinDebitPurchaseReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="authorizationCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="networkCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="transactionID" type="xsd:string" minOccurs="0"/> + <xsd:element name="requestAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="requestCurrency" type="xsd:string" minOccurs="0"/> + <xsd:element name="amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="dateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="accountType" type="xsd:string" minOccurs="0"/> + <xsd:element name="amountType" type="xsd:string" minOccurs="0"/> + <xsd:element name="accountBalance" type="xsd:string" minOccurs="0"/> + <xsd:element name="accountBalanceCurrency" type="xsd:string" minOccurs="0"/> + <xsd:element name="accountBalanceSign" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="PinDebitCreditReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="authorizationCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="networkCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="transactionID" type="xsd:string" minOccurs="0"/> + <xsd:element name="amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="dateTime" type="tns:dateTime" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="PinDebitReversalReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="dateTime" type="tns:dateTime" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="CCAuthReversalReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="authorizationCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="requestDateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="forwardCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorTransactionID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="CCAutoAuthReversalReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="result" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="ECDebitReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="settlementMethod" type="xsd:string" minOccurs="0"/> + <xsd:element name="requestDateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="verificationLevel" type="xsd:integer" minOccurs="0"/> + <xsd:element name="processorTransactionID" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="avsCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="avsCodeRaw" type="xsd:string" minOccurs="0"/> + <xsd:element name="verificationCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="verificationCodeRaw" type="xsd:string" minOccurs="0"/> + <xsd:element name="correctedAccountNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="correctedRoutingNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="ownerMerchantID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="ECCreditReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="settlementMethod" type="xsd:string" minOccurs="0"/> + <xsd:element name="requestDateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="processorTransactionID" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="verificationCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="verificationCodeRaw" type="xsd:string" minOccurs="0"/> + <xsd:element name="correctedAccountNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="correctedRoutingNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="ownerMerchantID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="ECAuthenticateReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="requestDateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="checkpointSummary" type="xsd:string" minOccurs="0"/> + <xsd:element name="fraudShieldIndicators" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="PayerAuthEnrollReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="acsURL" type="xsd:string" minOccurs="0"/> + <xsd:element name="commerceIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="eci" type="xsd:string" minOccurs="0"/> + <xsd:element name="paReq" type="xsd:string" minOccurs="0"/> + <xsd:element name="proxyPAN" type="xsd:string" minOccurs="0"/> + <xsd:element name="xid" type="xsd:string" minOccurs="0"/> + <xsd:element name="proofXML" type="xsd:string" minOccurs="0"/> + <xsd:element name="ucafCollectionIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="veresEnrolled" type="xsd:string" minOccurs="0"/> + <xsd:element name="authenticationPath" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="PayerAuthValidateReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="authenticationResult" type="xsd:string" minOccurs="0"/> + <xsd:element name="authenticationStatusMessage" type="xsd:string" minOccurs="0"/> + <xsd:element name="cavv" type="xsd:string" minOccurs="0"/> + <xsd:element name="cavvAlgorithm" type="xsd:string" minOccurs="0"/> + <xsd:element name="commerceIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="eci" type="xsd:string" minOccurs="0"/> + <xsd:element name="eciRaw" type="xsd:string" minOccurs="0"/> + <xsd:element name="xid" type="xsd:string" minOccurs="0"/> + <xsd:element name="ucafAuthenticationData" type="xsd:string" minOccurs="0"/> + <xsd:element name="ucafCollectionIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="paresStatus" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="TaxReplyItem"> + <xsd:sequence> + <xsd:element name="cityTaxAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="countyTaxAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="districtTaxAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="stateTaxAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="totalTaxAmount" type="tns:amount"/> + </xsd:sequence> + <xsd:attribute name="id" type="xsd:integer" use="required"/> + </xsd:complexType> + <xsd:complexType name="TaxReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="currency" type="xsd:string" minOccurs="0"/> + <xsd:element name="grandTotalAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="totalCityTaxAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="city" type="xsd:string" minOccurs="0"/> + <xsd:element name="totalCountyTaxAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="county" type="xsd:string" minOccurs="0"/> + <xsd:element name="totalDistrictTaxAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="totalStateTaxAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="state" type="xsd:string" minOccurs="0"/> + <xsd:element name="totalTaxAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="postalCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="geocode" type="xsd:string" minOccurs="0"/> + <xsd:element name="item" type="tns:TaxReplyItem" minOccurs="0" maxOccurs="1000"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="DeviceFingerprint"> + <xsd:sequence> + <xsd:element name="cookiesEnabled" type="tns:boolean" minOccurs="0"/> + <xsd:element name="flashEnabled" type="tns:boolean" minOccurs="0"/> + <xsd:element name="hash" type="xsd:string" minOccurs="0"/> + <xsd:element name="imagesEnabled" type="tns:boolean" minOccurs="0"/> + <xsd:element name="javascriptEnabled" type="tns:boolean" minOccurs="0"/> + <xsd:element name="proxyIPAddress" type="xsd:string" minOccurs="0"/> + <xsd:element name="proxyIPAddressActivities" type="xsd:string" minOccurs="0"/> + <xsd:element name="proxyIPAddressAttributes" type="xsd:string" minOccurs="0"/> + <xsd:element name="proxyServerType" type="xsd:string" minOccurs="0"/> + <xsd:element name="trueIPAddress" type="xsd:string" minOccurs="0"/> + <xsd:element name="trueIPAddressActivities" type="xsd:string" minOccurs="0"/> + <xsd:element name="trueIPAddressAttributes" type="xsd:string" minOccurs="0"/> + <xsd:element name="trueIPAddressCity" type="xsd:string" minOccurs="0"/> + <xsd:element name="trueIPAddressCountry" type="xsd:string" minOccurs="0"/> + <xsd:element name="smartID" type="xsd:string" minOccurs="0"/> + <xsd:element name="smartIDConfidenceLevel" type="xsd:string" minOccurs="0"/> + <xsd:element name="screenResolution" type="xsd:string" minOccurs="0"/> + <xsd:element name="browserLanguage" type="xsd:string" minOccurs="0"/> + <xsd:element name="agentType" type="xsd:string" minOccurs="0"/> + <xsd:element name="dateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="profileDuration" type="xsd:integer" minOccurs="0"/> + <xsd:element name="profiledURL" type="xsd:string" minOccurs="0"/> + <xsd:element name="timeOnPage" type="xsd:integer" minOccurs="0"/> + <xsd:element name="deviceMatch" type="xsd:string" minOccurs="0"/> + <xsd:element name="firstEncounter" type="xsd:string" minOccurs="0"/> + <xsd:element name="flashOS" type="xsd:string" minOccurs="0"/> + <xsd:element name="flashVersion" type="xsd:string" minOccurs="0"/> + <xsd:element name="deviceLatitude" type="xsd:string" minOccurs="0"/> + <xsd:element name="deviceLongitude" type="xsd:string" minOccurs="0"/> + <xsd:element name="gpsAccuracy" type="xsd:string" minOccurs="0"/> + <xsd:element name="jbRoot" type="xsd:integer" minOccurs="0"/> + <xsd:element name="jbRootReason" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="AFSReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="afsResult" type="xsd:integer" minOccurs="0"/> + <xsd:element name="hostSeverity" type="xsd:integer" minOccurs="0"/> + <xsd:element name="consumerLocalTime" type="xsd:string" minOccurs="0"/> + <!-- xsd:time --> + <xsd:element name="afsFactorCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="addressInfoCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="hotlistInfoCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="internetInfoCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="phoneInfoCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="suspiciousInfoCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="velocityInfoCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="identityInfoCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="ipCountry" type="xsd:string" minOccurs="0"/> + <xsd:element name="ipState" type="xsd:string" minOccurs="0"/> + <xsd:element name="ipCity" type="xsd:string" minOccurs="0"/> + <xsd:element name="ipRoutingMethod" type="xsd:string" minOccurs="0"/> + <xsd:element name="ipAnonymizerStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="scoreModelUsed" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardBin" type="xsd:string" minOccurs="0"/> + <xsd:element name="binCountry" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardAccountType" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardScheme" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardIssuer" type="xsd:string" minOccurs="0"/> + <xsd:element name="deviceFingerprint" type="tns:DeviceFingerprint" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="DAVReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="addressType" type="xsd:string" minOccurs="0"/> + <xsd:element name="apartmentInfo" type="xsd:string" minOccurs="0"/> + <xsd:element name="barCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="barCodeCheckDigit" type="xsd:string" minOccurs="0"/> + <xsd:element name="careOf" type="xsd:string" minOccurs="0"/> + <xsd:element name="cityInfo" type="xsd:string" minOccurs="0"/> + <xsd:element name="countryInfo" type="xsd:string" minOccurs="0"/> + <xsd:element name="directionalInfo" type="xsd:string" minOccurs="0"/> + <xsd:element name="lvrInfo" type="xsd:string" minOccurs="0"/> + <xsd:element name="matchScore" type="xsd:integer" minOccurs="0"/> + <xsd:element name="standardizedAddress1" type="xsd:string" minOccurs="0"/> + <xsd:element name="standardizedAddress2" type="xsd:string" minOccurs="0"/> + <xsd:element name="standardizedAddress3" type="xsd:string" minOccurs="0"/> + <xsd:element name="standardizedAddress4" type="xsd:string" minOccurs="0"/> + <xsd:element name="standardizedAddressNoApt" type="xsd:string" minOccurs="0"/> + <xsd:element name="standardizedCity" type="xsd:string" minOccurs="0"/> + <xsd:element name="standardizedCounty" type="xsd:string" minOccurs="0"/> + <xsd:element name="standardizedCSP" type="xsd:string" minOccurs="0"/> + <xsd:element name="standardizedState" type="xsd:string" minOccurs="0"/> + <xsd:element name="standardizedPostalCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="standardizedCountry" type="xsd:string" minOccurs="0"/> + <xsd:element name="standardizedISOCountry" type="xsd:string" minOccurs="0"/> + <xsd:element name="stateInfo" type="xsd:string" minOccurs="0"/> + <xsd:element name="streetInfo" type="xsd:string" minOccurs="0"/> + <xsd:element name="suffixInfo" type="xsd:string" minOccurs="0"/> + <xsd:element name="postalCodeInfo" type="xsd:string" minOccurs="0"/> + <xsd:element name="overallInfo" type="xsd:string" minOccurs="0"/> + <xsd:element name="usInfo" type="xsd:string" minOccurs="0"/> + <xsd:element name="caInfo" type="xsd:string" minOccurs="0"/> + <xsd:element name="intlInfo" type="xsd:string" minOccurs="0"/> + <xsd:element name="usErrorInfo" type="xsd:string" minOccurs="0"/> + <xsd:element name="caErrorInfo" type="xsd:string" minOccurs="0"/> + <xsd:element name="intlErrorInfo" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="DeniedPartiesMatch"> + <xsd:sequence> + <xsd:element name="list" type="xsd:string" minOccurs="0"/> + <xsd:element name="name" type="xsd:string" minOccurs="0" maxOccurs="100"/> + <xsd:element name="address" type="xsd:string" minOccurs="0" maxOccurs="100"/> + <xsd:element name="program" type="xsd:string" minOccurs="0" maxOccurs="100"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="ExportReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="ipCountryConfidence" type="xsd:integer" minOccurs="0"/> + <xsd:element name="infoCode" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="FXQuote"> + <xsd:sequence> + <xsd:element name="id" type="xsd:string" minOccurs="0"/> + <xsd:element name="rate" type="xsd:string" minOccurs="0"/> + <xsd:element name="type" type="xsd:string" minOccurs="0"/> + <xsd:element name="expirationDateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="currency" type="xsd:string" minOccurs="0"/> + <xsd:element name="fundingCurrency" type="xsd:string" minOccurs="0"/> + <xsd:element name="receivedDateTime" type="tns:dateTime" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="FXRatesReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="quote" type="tns:FXQuote" minOccurs="0" maxOccurs="999"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="BankTransferReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="accountHolder" type="xsd:string" minOccurs="0"/> + <xsd:element name="accountNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="bankName" type="xsd:string" minOccurs="0"/> + <xsd:element name="bankCity" type="xsd:string" minOccurs="0"/> + <xsd:element name="bankCountry" type="xsd:string" minOccurs="0"/> + <xsd:element name="paymentReference" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="bankSwiftCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="bankSpecialID" type="xsd:string" minOccurs="0"/> + <xsd:element name="requestDateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="iban" type="xsd:string" minOccurs="0"/> + <xsd:element name="bankCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="branchCode" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="BankTransferRealTimeReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="formMethod" type="xsd:string" minOccurs="0"/> + <xsd:element name="formAction" type="xsd:string" minOccurs="0"/> + <xsd:element name="requestDateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="paymentReference" type="xsd:string" minOccurs="0"/> + <xsd:element name="amount" type="tns:amount" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="DirectDebitMandateReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="mandateID" type="xsd:string" minOccurs="0"/> + <xsd:element name="mandateMaturationDate" type="xsd:string" minOccurs="0"/> + <xsd:element name="requestDateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="BankTransferRefundReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="requestDateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="iban" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="DirectDebitReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="requestDateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorTransactionID" type="xsd:string" minOccurs="0"/> + <xsd:element name="mandateAuthenticationDate" type="xsd:string" minOccurs="0"/> + <xsd:element name="mandateID" type="xsd:string" minOccurs="0"/> + <xsd:element name="iban" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="DirectDebitValidateReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="requestDateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="iban" type="xsd:string" minOccurs="0"/> + <xsd:element name="bankSwiftCode" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="DirectDebitRefundReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="requestDateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorTransactionID" type="xsd:string" minOccurs="0"/> + <xsd:element name="iban" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="PaySubscriptionCreateReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="subscriptionID" type="xsd:string"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="PaySubscriptionUpdateReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="subscriptionID" type="xsd:string"/> + <xsd:element name="subscriptionIDNew" type="xsd:string" minOccurs="0"/> + <xsd:element name="ownerMerchantID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="PaySubscriptionEventUpdateReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="ownerMerchantID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="PaySubscriptionRetrieveReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="approvalRequired" type="xsd:string" minOccurs="0"/> + <xsd:element name="automaticRenew" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardAccountNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardExpirationMonth" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardExpirationYear" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardIssueNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardStartMonth" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardStartYear" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardType" type="xsd:string" minOccurs="0"/> + <xsd:element name="checkAccountNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="checkAccountType" type="xsd:string" minOccurs="0"/> + <xsd:element name="checkBankTransitNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="checkSecCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="checkAuthenticateID" type="xsd:string" minOccurs="0"/> + <xsd:element name="city" type="xsd:string" minOccurs="0"/> + <xsd:element name="comments" type="xsd:string" minOccurs="0"/> + <xsd:element name="companyName" type="xsd:string" minOccurs="0"/> + <xsd:element name="country" type="xsd:string" minOccurs="0"/> + <xsd:element name="currency" type="xsd:string" minOccurs="0"/> + <xsd:element name="customerAccountID" type="xsd:string" minOccurs="0"/> + <xsd:element name="email" type="xsd:string" minOccurs="0"/> + <xsd:element name="endDate" type="xsd:string" minOccurs="0"/> + <xsd:element name="firstName" type="xsd:string" minOccurs="0"/> + <xsd:element name="frequency" type="xsd:string" minOccurs="0"/> + <xsd:element name="lastName" type="xsd:string" minOccurs="0"/> + <xsd:element name="merchantReferenceCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="paymentMethod" type="xsd:string" minOccurs="0"/> + <xsd:element name="paymentsRemaining" type="xsd:string" minOccurs="0"/> + <xsd:element name="phoneNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="postalCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="recurringAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="setupAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="startDate" type="xsd:string" minOccurs="0"/> + <xsd:element name="state" type="xsd:string" minOccurs="0"/> + <xsd:element name="status" type="xsd:string" minOccurs="0"/> + <xsd:element name="street1" type="xsd:string" minOccurs="0"/> + <xsd:element name="street2" type="xsd:string" minOccurs="0"/> + <xsd:element name="subscriptionID" type="xsd:string" minOccurs="0"/> + <xsd:element name="subscriptionIDNew" type="xsd:string"/> + <xsd:element name="title" type="xsd:string" minOccurs="0"/> + <xsd:element name="totalPayments" type="xsd:string" minOccurs="0"/> + <xsd:element name="shipToFirstName" type="xsd:string" minOccurs="0"/> + <xsd:element name="shipToLastName" type="xsd:string" minOccurs="0"/> + <xsd:element name="shipToStreet1" type="xsd:string" minOccurs="0"/> + <xsd:element name="shipToStreet2" type="xsd:string" minOccurs="0"/> + <xsd:element name="shipToCity" type="xsd:string" minOccurs="0"/> + <xsd:element name="shipToState" type="xsd:string" minOccurs="0"/> + <xsd:element name="shipToPostalCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="shipToCompany" type="xsd:string" minOccurs="0"/> + <xsd:element name="shipToCountry" type="xsd:string" minOccurs="0"/> + <xsd:element name="billPayment" type="xsd:string" minOccurs="0"/> + <xsd:element name="merchantDefinedDataField1" type="xsd:string" minOccurs="0"/> + <xsd:element name="merchantDefinedDataField2" type="xsd:string" minOccurs="0"/> + <xsd:element name="merchantDefinedDataField3" type="xsd:string" minOccurs="0"/> + <xsd:element name="merchantDefinedDataField4" type="xsd:string" minOccurs="0"/> + <xsd:element name="merchantSecureDataField1" type="xsd:string" minOccurs="0"/> + <xsd:element name="merchantSecureDataField2" type="xsd:string" minOccurs="0"/> + <xsd:element name="merchantSecureDataField3" type="xsd:string" minOccurs="0"/> + <xsd:element name="merchantSecureDataField4" type="xsd:string" minOccurs="0"/> + <xsd:element name="ownerMerchantID" type="xsd:string" minOccurs="0"/> + <xsd:element name="companyTaxID" type="xsd:string" minOccurs="0"/> + <xsd:element name="driversLicenseNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="driversLicenseState" type="xsd:string" minOccurs="0"/> + <xsd:element name="dateOfBirth" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="PaySubscriptionDeleteReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="subscriptionID" type="xsd:string"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="PayPalPaymentReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="secureData" type="xsd:string" minOccurs="0"/> + <xsd:element name="amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="requestDateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="PayPalCreditReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="requestDateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="VoidReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="requestDateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="currency" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="PinlessDebitReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="authorizationCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="requestDateTime" type="tns:dateTime" minOccurs="0"/> + <!-- dateTime --> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="receiptNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="ownerMerchantID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="PinlessDebitValidateReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="status" type="xsd:string" minOccurs="0"/> + <xsd:element name="requestDateTime" type="tns:dateTime" minOccurs="0"/> + <!-- dateTime --> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="PinlessDebitReversalReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="requestDateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <!-- payPal Upgrade Services --> + <xsd:complexType name="PayPalButtonCreateReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="encryptedFormData" type="xsd:string" minOccurs="0"/> + <xsd:element name="unencryptedFormData" type="xsd:string" minOccurs="0"/> + <xsd:element name="requestDateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="buttonType" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="PayPalPreapprovedPaymentReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="requestDateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerName" type="xsd:string" minOccurs="0"/> + <xsd:element name="transactionType" type="xsd:string" minOccurs="0"/> + <xsd:element name="feeAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerCountry" type="xsd:string" minOccurs="0"/> + <xsd:element name="pendingReason" type="xsd:string" minOccurs="0"/> + <xsd:element name="paymentStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="mpStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="payer" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerID" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerBusiness" type="xsd:string" minOccurs="0"/> + <xsd:element name="transactionID" type="xsd:string" minOccurs="0"/> + <xsd:element name="desc" type="xsd:string" minOccurs="0"/> + <xsd:element name="mpMax" type="xsd:string" minOccurs="0"/> + <xsd:element name="paymentType" type="xsd:string" minOccurs="0"/> + <xsd:element name="paymentDate" type="xsd:string" minOccurs="0"/> + <xsd:element name="paymentGrossAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="settleAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="taxAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="exchangeRate" type="xsd:string" minOccurs="0"/> + <xsd:element name="paymentSourceID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="PayPalPreapprovedUpdateReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="requestDateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerName" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerCountry" type="xsd:string" minOccurs="0"/> + <xsd:element name="mpStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="payer" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerID" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerBusiness" type="xsd:string" minOccurs="0"/> + <xsd:element name="desc" type="xsd:string" minOccurs="0"/> + <xsd:element name="mpMax" type="xsd:string" minOccurs="0"/> + <xsd:element name="paymentSourceID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <!-- PayPalEcSet --> + <xsd:complexType name="PayPalEcSetReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="paypalToken" type="xsd:string" minOccurs="0"/> + <xsd:element name="amount" type="xsd:string" minOccurs="0"/> + <xsd:element name="currency" type="xsd:string" minOccurs="0"/> + <xsd:element name="correlationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="errorCode" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <!-- end of PayPalEcSet --> + <!-- PayPalEcGetDetails --> + <xsd:complexType name="PayPalEcGetDetailsReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="paypalToken" type="xsd:string" minOccurs="0"/> + <xsd:element name="payer" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerId" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerSalutation" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerFirstname" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerMiddlename" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerLastname" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerSuffix" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerCountry" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerBusiness" type="xsd:string" minOccurs="0"/> + <xsd:element name="shipToName" type="xsd:string" minOccurs="0"/> + <xsd:element name="shipToAddress1" type="xsd:string" minOccurs="0"/> + <xsd:element name="shipToAddress2" type="xsd:string" minOccurs="0"/> + <xsd:element name="shipToCity" type="xsd:string" minOccurs="0"/> + <xsd:element name="shipToState" type="xsd:string" minOccurs="0"/> + <xsd:element name="shipToCountry" type="xsd:string" minOccurs="0"/> + <xsd:element name="shipToZip" type="xsd:string" minOccurs="0"/> + <xsd:element name="addressStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerPhone" type="xsd:string" minOccurs="0"/> + <xsd:element name="avsCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="correlationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="errorCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="street1" type="xsd:string" minOccurs="0"/> + <xsd:element name="street2" type="xsd:string" minOccurs="0"/> + <xsd:element name="city" type="xsd:string" minOccurs="0"/> + <xsd:element name="state" type="xsd:string" minOccurs="0"/> + <xsd:element name="postalCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="countryCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="countryName" type="xsd:string" minOccurs="0"/> + <xsd:element name="addressID" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalBillingAgreementAcceptedStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalTaxAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="item" type="tns:Item" minOccurs="0" maxOccurs="1000" /> + </xsd:sequence> + </xsd:complexType> + <!-- end of PayPalEcGetDetails --> + <!-- PayPalEcDoPayment --> + <xsd:complexType name="PayPalEcDoPaymentReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="paypalToken" type="xsd:string" minOccurs="0"/> + <xsd:element name="transactionId" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalTransactiontype" type="xsd:string" minOccurs="0"/> + <xsd:element name="paymentType" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalOrderTime" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalFeeAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalTaxAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalExchangeRate" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalPaymentStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalPendingReason" type="xsd:string" minOccurs="0"/> + <xsd:element name="orderId" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalReasonCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="amount" type="xsd:string" minOccurs="0"/> + <xsd:element name="currency" type="xsd:string" minOccurs="0"/> + <xsd:element name="correlationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="errorCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalBillingAgreementId" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <!-- end of PayPalEcDoPayment --> + <!-- PayPalDoCapture --> + <xsd:complexType name="PayPalDoCaptureReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="authorizationId" type="xsd:string" minOccurs="0"/> + <xsd:element name="transactionId" type="xsd:string" minOccurs="0"/> + <xsd:element name="parentTransactionId" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalReceiptId" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalTransactiontype" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalPaymentType" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalOrderTime" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalPaymentGrossAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalFeeAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalTaxAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalExchangeRate" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalPaymentStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="amount" type="xsd:string" minOccurs="0"/> + <xsd:element name="currency" type="xsd:string" minOccurs="0"/> + <xsd:element name="correlationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="errorCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalPendingReason" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <!-- end of PayPalDoCapture --> + <!-- PayPalAuthReversal --> + <xsd:complexType name="PayPalAuthReversalReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="authorizationId" type="xsd:string" minOccurs="0"/> + <xsd:element name="correlationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="errorCode" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <!-- end of PayPalAuthReversal --> + <!-- PayPalRefund --> + <xsd:complexType name="PayPalRefundReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="transactionId" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalNetRefundAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalFeeRefundAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalGrossRefundAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="correlationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="errorCode" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <!-- end of PayPalRefund --> + <!-- PayPalEcOrderSetup --> + <xsd:complexType name="PayPalEcOrderSetupReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="paypalToken" type="xsd:string" minOccurs="0"/> + <xsd:element name="transactionId" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalTransactiontype" type="xsd:string" minOccurs="0"/> + <xsd:element name="paymentType" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalOrderTime" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalFeeAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalTaxAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalExchangeRate" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalPaymentStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalPendingReason" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalReasonCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="amount" type="xsd:string" minOccurs="0"/> + <xsd:element name="currency" type="xsd:string" minOccurs="0"/> + <xsd:element name="correlationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="errorCode" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <!-- end of PayPalEcOrderSetup --> + <!-- PayPalAuthorization--> + <xsd:complexType name="PayPalAuthorizationReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="transactionId" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="amount" type="xsd:string" minOccurs="0"/> + <xsd:element name="currency" type="xsd:string" minOccurs="0"/> + <xsd:element name="correlationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="errorCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="protectionEligibility" type="xsd:string" minOccurs="0"/> + <xsd:element name="protectionEligibilityType" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <!-- end of PayPalAuthorization --> + <!-- PayPalUpdateAgreement--> + <xsd:complexType name="PayPalUpdateAgreementReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="paypalBillingAgreementId" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalBillingAgreementDesc" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalBillingAgreementCustom" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalBillingAgreementStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="payer" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerId" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerCountry" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerBusiness" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerSalutation" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerFirstname" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerMiddlename" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerLastname" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerSuffix" type="xsd:string" minOccurs="0"/> + <xsd:element name="addressStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="errorCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="correlationID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <!-- end of PayPalUpdateAgreement--> + <!-- PayPalCreateAgreement--> + <xsd:complexType name="PayPalCreateAgreementReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="paypalBillingAgreementId" type="xsd:string" minOccurs="0"/> + <xsd:element name="errorCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="correlationID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <!-- end of PayPalCreateAgreement--> + <!-- PayPalDoRefTransaction--> + <xsd:complexType name="PayPalDoRefTransactionReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="paypalBillingAgreementId" type="xsd:string" minOccurs="0"/> + <xsd:element name="transactionId" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalTransactionType" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalPaymentType" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalOrderTime" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="currency" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalTaxAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalExchangeRate" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalPaymentStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalPendingReason" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalReasonCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="errorCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="correlationID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <!-- end of PayPalDoRefTransaction--> + <xsd:complexType name="RiskUpdateReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="FraudUpdateReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="CaseManagementActionReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="RuleResultItem"> + <xsd:sequence> + <xsd:element name="name" type="xsd:string" minOccurs="0"/> + <xsd:element name="decision" type="xsd:string" minOccurs="0"/> + <xsd:element name="evaluation" type="xsd:string" minOccurs="0"/> + <xsd:element name="ruleID" type="xsd:integer" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="RuleResultItems"> + <xsd:sequence> + <xsd:element name="ruleResultItem" type="tns:RuleResultItem" minOccurs="0" maxOccurs="1000"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="DecisionReply"> + <xsd:sequence> + <xsd:element name="casePriority" type="xsd:integer" minOccurs="0"/> + <xsd:element name="activeProfileReply" type="tns:ProfileReply" minOccurs="0"/> + <xsd:element name="velocityInfoCode" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <!-- DME --> + <xsd:complexType name="AdditionalFields"> + <xsd:sequence> + <xsd:element name="field" type="tns:Field" minOccurs="0" maxOccurs="3000"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="Field"> + <xsd:sequence> + <xsd:element name="provider" type="xsd:string" minOccurs="1" maxOccurs="1"/> + <xsd:element name="name" type="xsd:string" minOccurs="1" maxOccurs="1"/> + <xsd:element name="value" type="xsd:string" minOccurs="1" maxOccurs="1"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="DMEReply"> + <xsd:sequence> + <xsd:element name="eventType" type="xsd:string" minOccurs="0"/> + <xsd:element name="eventInfo" type="xsd:string" minOccurs="0"/> + <xsd:element name="eventHotlistInfo" type="xsd:string" minOccurs="0"/> + <xsd:element name="eventPolicy" type="xsd:string" minOccurs="0"/> + <xsd:element name="additionalFields" type="tns:AdditionalFields" minOccurs="0" maxOccurs="1" /> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="ProfileReply"> + <xsd:sequence> + <xsd:element name="selectedBy" type="xsd:string" minOccurs="0"/> + <xsd:element name="name" type="xsd:string" minOccurs="0"/> + <xsd:element name="destinationQueue" type="xsd:string" minOccurs="0"/> + <xsd:element name="profileScore" type="xsd:string" minOccurs="0"/> + <xsd:element name="rulesTriggered" type="tns:RuleResultItems" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="CCDCCReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="dccSupported" type="tns:boolean" minOccurs="0"/> + <xsd:element name="validHours" type="xsd:string" minOccurs="0"/> + <xsd:element name="marginRatePercentage" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="CCDCCUpdateReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="ChinaPaymentReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="requestDateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="currency" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="formData" type="xsd:string" minOccurs="0"/> + <xsd:element name="verifyFailure" type="xsd:string" minOccurs="0"/> + <xsd:element name="verifyInProcess" type="xsd:string" minOccurs="0"/> + <xsd:element name="verifySuccess" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="ChinaRefundReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="requestDateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="currency" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="BoletoPaymentReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="requestDateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="boletoNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="expirationDate" type="xsd:string" minOccurs="0"/> + <xsd:element name="url" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="APInitiateReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="merchantURL" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="dateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="signature" type="xsd:string" minOccurs="0"/> + <xsd:element name="publicKey" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="APCheckStatusReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="reconciliationID" type="xsd:string"/> + <xsd:element name="paymentStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorTradeNo" type="xsd:string"/> + <xsd:element name="processorTransactionID" type="xsd:string"/> + </xsd:sequence> + </xsd:complexType> + <!-- Vme Reseller Reply--> + <xsd:complexType name="APReply"> + <xsd:sequence> + <xsd:element name="orderID" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardGroup" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardType" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardNumberSuffix" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardExpirationMonth" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardExpirationYear" type="xsd:string" minOccurs="0"/> + <xsd:element name="avsCodeRaw" type="xsd:string" minOccurs="0"/> + <xsd:element name="purchaseID" type="xsd:string" minOccurs="0"/> + <xsd:element name="productID" type="xsd:string" minOccurs="0"/> + <xsd:element name="productDescription" type="xsd:string" minOccurs="0"/> + <xsd:element name="shippingAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="handlingAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="shippingHandlingAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="additionalAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="taxAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="subtotalAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="totalPurchaseAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="giftWrapAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="discountAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardNumberPrefix" type="xsd:string" minOccurs="0"/> + <xsd:element name="riskIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="merchantUUID" type="xsd:string" minOccurs="0"/> + <xsd:element name="merchantSiteID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <!-- AP Auth Service --> + <xsd:complexType name="APAuthReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="transactionID" type="xsd:string" minOccurs="0"/> + <xsd:element name="status" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="dateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="providerResponse" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <!-- End of AP Auth Service --> + <!-- AP Auth Reversal Service --> + <xsd:complexType name="APAuthReversalReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="transactionID" type="xsd:string" minOccurs="0"/> + <xsd:element name="status" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="dateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="providerResponse" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <!-- End of AP Auth Reversal Service --> + <!-- AP Capture Service --> + <xsd:complexType name="APCaptureReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="transactionID" type="xsd:string" minOccurs="0"/> + <xsd:element name="status" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="dateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="providerResponse" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <!-- End of AP Capture Service --> + <!-- AP Refund Service --> + <xsd:complexType name="APRefundReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="transactionID" type="xsd:string" minOccurs="0"/> + <xsd:element name="status" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="dateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="returnRef" type="xsd:string" minOccurs="0"/> + <xsd:element name="providerResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorTransactionID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <!-- End of AP Refund Service --> + <!-- AP CheckOutDetailsReply Service --> + <xsd:complexType name="APCheckOutDetailsReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="status" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="dateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="providerResponse" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <!-- End of AP CheckOutDetailsReply Service --> + <xsd:complexType name="APTransactionDetailsReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="transactionID" type="xsd:string" minOccurs="0"/> + <xsd:element name="status" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="dateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="providerResponse" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <!-- AP ConfirmPurchase Service --> + <xsd:complexType name="APConfirmPurchaseReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="status" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="dateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="providerResponse" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <!-- End of AP ConfirmPurchase Service --> + <xsd:complexType name="ReplyMessage"> + <xsd:sequence> + <xsd:element name="merchantReferenceCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="requestID" type="xsd:string"/> + <xsd:element name="decision" type="xsd:string"/> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="missingField" type="xsd:string" minOccurs="0" maxOccurs="1000"/> + <xsd:element name="invalidField" type="xsd:string" minOccurs="0" maxOccurs="1000"/> + <xsd:element name="requestToken" type="xsd:string"/> + <xsd:element name="purchaseTotals" type="tns:PurchaseTotals" minOccurs="0"/> + <xsd:element name="deniedPartiesMatch" type="tns:DeniedPartiesMatch" minOccurs="0" maxOccurs="100"/> + <xsd:element name="ccAuthReply" type="tns:CCAuthReply" minOccurs="0"/> + <xsd:element name="octReply" type="tns:OCTReply" minOccurs="0"/> + <xsd:element name="ccSaleReply" type="tns:CCSaleReply" minOccurs="0"/> + <xsd:element name="ccSaleCreditReply" type="tns:CCSaleCreditReply" minOccurs="0"/> + <xsd:element name="ccSaleReversalReply" type="tns:CCSaleReversalReply" minOccurs="0"/> + <xsd:element name="ccIncrementalAuthReply" type="tns:CCIncrementalAuthReply" minOccurs="0"/> + <xsd:element name="serviceFeeCalculateReply" type="tns:ServiceFeeCalculateReply" minOccurs="0"/> + <xsd:element name="ccCaptureReply" type="tns:CCCaptureReply" minOccurs="0"/> + <xsd:element name="ccCreditReply" type="tns:CCCreditReply" minOccurs="0"/> + <xsd:element name="ccAuthReversalReply" type="tns:CCAuthReversalReply" minOccurs="0"/> + <xsd:element name="ccAutoAuthReversalReply" type="tns:CCAutoAuthReversalReply" minOccurs="0"/> + <xsd:element name="ccDCCReply" type="tns:CCDCCReply" minOccurs="0"/> + <xsd:element name="ccDCCUpdateReply" type="tns:CCDCCUpdateReply" minOccurs="0"/> + <xsd:element name="ecDebitReply" type="tns:ECDebitReply" minOccurs="0"/> + <xsd:element name="ecCreditReply" type="tns:ECCreditReply" minOccurs="0"/> + <xsd:element name="ecAuthenticateReply" type="tns:ECAuthenticateReply" minOccurs="0"/> + <xsd:element name="payerAuthEnrollReply" type="tns:PayerAuthEnrollReply" minOccurs="0"/> + <xsd:element name="payerAuthValidateReply" type="tns:PayerAuthValidateReply" minOccurs="0"/> + <xsd:element name="taxReply" type="tns:TaxReply" minOccurs="0"/> + <xsd:element name="encryptedPayment" type="tns:EncryptedPayment" minOccurs="0" /> + <xsd:element name="encryptPaymentDataReply" type="tns:EncryptPaymentDataReply" minOccurs="0"/> + <xsd:element name="dmeReply" type="tns:DMEReply" minOccurs="0"/> + <xsd:element name="afsReply" type="tns:AFSReply" minOccurs="0"/> + <xsd:element name="davReply" type="tns:DAVReply" minOccurs="0"/> + <xsd:element name="exportReply" type="tns:ExportReply" minOccurs="0"/> + <xsd:element name="fxRatesReply" type="tns:FXRatesReply" minOccurs="0"/> + <xsd:element name="bankTransferReply" type="tns:BankTransferReply" minOccurs="0"/> + <xsd:element name="bankTransferRefundReply" type="tns:BankTransferRefundReply" minOccurs="0"/> + <xsd:element name="bankTransferRealTimeReply" type="tns:BankTransferRealTimeReply" minOccurs="0"/> + <xsd:element name="directDebitMandateReply" type="tns:DirectDebitMandateReply" minOccurs="0"/> + <xsd:element name="directDebitReply" type="tns:DirectDebitReply" minOccurs="0"/> + <xsd:element name="directDebitValidateReply" type="tns:DirectDebitValidateReply" minOccurs="0"/> + <xsd:element name="directDebitRefundReply" type="tns:DirectDebitRefundReply" minOccurs="0"/> + <xsd:element name="paySubscriptionCreateReply" type="tns:PaySubscriptionCreateReply" minOccurs="0"/> + <xsd:element name="paySubscriptionUpdateReply" type="tns:PaySubscriptionUpdateReply" minOccurs="0"/> + <xsd:element name="paySubscriptionEventUpdateReply" type="tns:PaySubscriptionEventUpdateReply" minOccurs="0"/> + <xsd:element name="paySubscriptionRetrieveReply" type="tns:PaySubscriptionRetrieveReply" minOccurs="0"/> + <xsd:element name="paySubscriptionDeleteReply" type="tns:PaySubscriptionDeleteReply" minOccurs="0"/> + <xsd:element name="payPalPaymentReply" type="tns:PayPalPaymentReply" minOccurs="0"/> + <xsd:element name="payPalCreditReply" type="tns:PayPalCreditReply" minOccurs="0"/> + <xsd:element name="voidReply" type="tns:VoidReply" minOccurs="0"/> + <xsd:element name="pinlessDebitReply" type="tns:PinlessDebitReply" minOccurs="0"/> + <xsd:element name="pinlessDebitValidateReply" type="tns:PinlessDebitValidateReply" minOccurs="0"/> + <xsd:element name="pinlessDebitReversalReply" type="tns:PinlessDebitReversalReply" minOccurs="0"/> + <xsd:element name="payPalButtonCreateReply" type="tns:PayPalButtonCreateReply" minOccurs="0"/> + <xsd:element name="payPalPreapprovedPaymentReply" type="tns:PayPalPreapprovedPaymentReply" minOccurs="0"/> + <xsd:element name="payPalPreapprovedUpdateReply" type="tns:PayPalPreapprovedUpdateReply" minOccurs="0"/> + <xsd:element name="riskUpdateReply" type="tns:RiskUpdateReply" minOccurs="0"/> + <xsd:element name="fraudUpdateReply" type="tns:FraudUpdateReply" minOccurs="0"/> + <xsd:element name="caseManagementActionReply" type="tns:CaseManagementActionReply" minOccurs="0"/> + <xsd:element name="decisionReply" type="tns:DecisionReply" minOccurs="0"/> + <xsd:element name="payPalRefundReply" type="tns:PayPalRefundReply" minOccurs="0"/> + <xsd:element name="payPalAuthReversalReply" type="tns:PayPalAuthReversalReply" minOccurs="0"/> + <xsd:element name="payPalDoCaptureReply" type="tns:PayPalDoCaptureReply" minOccurs="0"/> + <xsd:element name="payPalEcDoPaymentReply" type="tns:PayPalEcDoPaymentReply" minOccurs="0"/> + <xsd:element name="payPalEcGetDetailsReply" type="tns:PayPalEcGetDetailsReply" minOccurs="0"/> + <xsd:element name="payPalEcSetReply" type="tns:PayPalEcSetReply" minOccurs="0"/> + <xsd:element name="payPalAuthorizationReply" type="tns:PayPalAuthorizationReply" minOccurs="0"/> + <xsd:element name="payPalEcOrderSetupReply" type="tns:PayPalEcOrderSetupReply" minOccurs="0"/> + <xsd:element name="payPalUpdateAgreementReply" type="tns:PayPalUpdateAgreementReply" minOccurs="0"/> + <xsd:element name="payPalCreateAgreementReply" type="tns:PayPalCreateAgreementReply" minOccurs="0"/> + <xsd:element name="payPalDoRefTransactionReply" type="tns:PayPalDoRefTransactionReply" minOccurs="0"/> + <xsd:element name="chinaPaymentReply" type="tns:ChinaPaymentReply" minOccurs="0"/> + <xsd:element name="chinaRefundReply" type="tns:ChinaRefundReply" minOccurs="0"/> + <xsd:element name="boletoPaymentReply" type="tns:BoletoPaymentReply" minOccurs="0"/> + <xsd:element name="pinDebitPurchaseReply" type="tns:PinDebitPurchaseReply" minOccurs="0"/> + <xsd:element name="pinDebitCreditReply" type="tns:PinDebitCreditReply" minOccurs="0"/> + <xsd:element name="pinDebitReversalReply" type="tns:PinDebitReversalReply" minOccurs="0"/> + <xsd:element name="apInitiateReply" type="tns:APInitiateReply" minOccurs="0"/> + <xsd:element name="apCheckStatusReply" type="tns:APCheckStatusReply" minOccurs="0"/> + <xsd:element name="receiptNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="additionalData" type="xsd:string" minOccurs="0"/> + <xsd:element name="solutionProviderTransactionID" type="xsd:string" minOccurs="0"/> + <xsd:element name="apReply" type="tns:APReply" minOccurs="0"/> + <xsd:element name="shipTo" type="tns:ShipTo" minOccurs="0" /> + <xsd:element name="billTo" type="tns:BillTo" minOccurs="0" /> + <xsd:element name="apAuthReply" type="tns:APAuthReply" minOccurs="0"/> + <xsd:element name="apAuthReversalReply" type="tns:APAuthReversalReply" minOccurs="0"/> + <xsd:element name="apCaptureReply" type="tns:APCaptureReply" minOccurs="0"/> + <xsd:element name="apRefundReply" type="tns:APRefundReply" minOccurs="0"/> + <xsd:element name="apCheckoutDetailsReply" type="tns:APCheckOutDetailsReply" minOccurs="0"/> + <xsd:element name="apTransactionDetailsReply" type="tns:APTransactionDetailsReply" minOccurs="0"/> + <xsd:element name="apConfirmPurchaseReply" type="tns:APConfirmPurchaseReply" minOccurs="0"/> + <xsd:element name="promotion" type="tns:Promotion" minOccurs="0"/> + <xsd:element name="promotionGroup" type="tns:PromotionGroupReply" minOccurs="0" maxOccurs="100"/> + <xsd:element name="payPalGetTxnDetailsReply" type="tns:PayPalGetTxnDetailsReply" minOccurs="0"/> + <xsd:element name="payPalTransactionSearchReply" type="tns:PayPalTransactionSearchReply" minOccurs="0"/> + <xsd:element name="emvReply" type="tns:EmvReply" minOccurs="0" /> + <xsd:element name="originalTransaction" type="tns:OriginalTransaction" minOccurs="0" /> + <xsd:element name="hostedDataCreateReply" type="tns:HostedDataCreateReply" minOccurs="0" /> + <xsd:element name="hostedDataRetrieveReply" type="tns:HostedDataRetrieveReply" minOccurs="0" /> + <xsd:element name="salesSlipNumber" type="xsd:string" minOccurs="0" /> + <xsd:element name="additionalProcessorResponse" type="xsd:string" minOccurs="0" /> + <xsd:element name="jpo" type="tns:JPO" minOccurs="0" /> + <xsd:element name="card" type="tns:Card" minOccurs="0" /> + <xsd:element name="paymentNetworkToken" type="tns:PaymentNetworkToken" minOccurs="0"/> + <xsd:element name="vcReply" type="tns:VCReply" minOccurs="0" /> + <xsd:element name="decryptVisaCheckoutDataReply" type="tns:DecryptVisaCheckoutDataReply" minOccurs="0"/> + <xsd:element name="binLookupReply" type="tns:BinLookupReply" minOccurs="0"/> + <xsd:element name="issuerMessage" type="xsd:string" minOccurs="0" /> + <xsd:element name="reserved" type="tns:ReplyReserved" minOccurs="0"/> + <!--ReplyReserved should always be the last element in the xsd, new elements should be added before this--> + </xsd:sequence> + </xsd:complexType> + <xsd:element name="requestMessage" type="tns:RequestMessage"> + <xsd:unique name="unique-item-id"> + <xsd:selector xpath="tns:item"/> + <xsd:field xpath="@id"/> + </xsd:unique> + </xsd:element> + <xsd:element name="replyMessage" type="tns:ReplyMessage"> + <xsd:unique name="unique-tax-item-id"> + <xsd:selector xpath="tns:taxReplyItem"/> + <xsd:field xpath="@id"/> + </xsd:unique> + </xsd:element> + <xsd:element name="nvpRequest" type="xsd:string"/> + <xsd:element name="nvpReply" type="xsd:string"/> + <!-- used in SOAP faults --> + <xsd:complexType name="FaultDetails"> + <xsd:sequence> + <xsd:element name="requestID" type="xsd:string"/> + </xsd:sequence> + </xsd:complexType> + <xsd:element name="faultDetails" type="tns:FaultDetails"/> + <xsd:complexType name="AirlineData"> + <xsd:sequence> + <xsd:element name="agentCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="agentName" type="xsd:string" minOccurs="0"/> + <xsd:element name="ticketIssuerCity" type="xsd:string" minOccurs="0"/> + <xsd:element name="ticketIssuerState" type="xsd:string" minOccurs="0"/> + <xsd:element name="ticketIssuerPostalCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="ticketIssuerCountry" type="xsd:string" minOccurs="0"/> + <xsd:element name="ticketIssuerAddress" type="xsd:string" minOccurs="0"/> + <xsd:element name="ticketIssuerCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="ticketIssuerName" type="xsd:string" minOccurs="0"/> + <xsd:element name="ticketNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="checkDigit" type="xsd:integer" minOccurs="0"/> + <xsd:element name="restrictedTicketIndicator" type="xsd:integer" minOccurs="0"/> + <xsd:element name="transactionType" type="xsd:string" minOccurs="0"/> + <xsd:element name="extendedPaymentCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="carrierName" type="xsd:string" minOccurs="0"/> + <xsd:element name="passengerName" type="xsd:string" minOccurs="0"/> + <xsd:element name="customerCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="documentType" type="xsd:string" minOccurs="0"/> + <xsd:element name="documentNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="documentNumberOfParts" type="xsd:string" minOccurs="0"/> + <xsd:element name="invoiceNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="invoiceDate" type="xsd:string" minOccurs="0"/> + <xsd:element name="chargeDetails" type="xsd:string" minOccurs="0"/> + <xsd:element name="bookingReference" type="xsd:string" minOccurs="0"/> + <xsd:element name="totalFee" type="tns:amount" minOccurs="0"/> + <xsd:element name="clearingSequence" type="xsd:string" minOccurs="0"/> + <xsd:element name="clearingCount" type="xsd:integer" minOccurs="0"/> + <xsd:element name="totalClearingAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="leg" type="tns:Leg" minOccurs="0" maxOccurs="1000"/> + <xsd:element name="numberOfPassengers" type="xsd:string" minOccurs="0"/> + <xsd:element name="reservationSystem" type="xsd:string" minOccurs="0"/> + <xsd:element name="processIdentifier" type="xsd:string" minOccurs="0"/> + <xsd:element name="iataNumericCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="ticketIssueDate" type="xsd:string" minOccurs="0"/> + <xsd:element name="electronicTicket" type="xsd:string" minOccurs="0"/> + <xsd:element name="originalTicketNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="purchaseType" type="xsd:string" minOccurs="0"/> + <xsd:element name="creditReasonIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="ticketUpdateIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="planNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="arrivalDate" type="xsd:string" minOccurs="0"/> + <xsd:element name="ticketRestrictionText" type="xsd:string" minOccurs="0"/> + <xsd:element name="exchangeTicketAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="exchangeTicketFee" type="tns:amount" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="Leg"> + <xsd:sequence> + <xsd:element name="carrierCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="flightNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="originatingAirportCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="class" type="xsd:string" minOccurs="0"/> + <xsd:element name="stopoverCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="departureDate" type="xsd:string" minOccurs="0"/> + <xsd:element name="destination" type="xsd:string" minOccurs="0"/> + <xsd:element name="fareBasis" type="xsd:string" minOccurs="0"/> + <xsd:element name="departTax" type="xsd:string" minOccurs="0"/> + <xsd:element name="conjunctionTicket" type="xsd:string" minOccurs="0"/> + <xsd:element name="exchangeTicket" type="xsd:string" minOccurs="0"/> + <xsd:element name="couponNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="departureTime" type="xsd:string" minOccurs="0"/> + <xsd:element name="departureTimeSegment" type="xsd:string" minOccurs="0"/> + <xsd:element name="arrivalTime" type="xsd:string" minOccurs="0"/> + <xsd:element name="arrivalTimeSegment" type="xsd:string" minOccurs="0"/> + <xsd:element name="endorsementsRestrictions" type="xsd:string" minOccurs="0"/> + <xsd:element name="fare" type="xsd:string" minOccurs="0"/> + <xsd:element name="fee" type="xsd:string" minOccurs="0"/> + <xsd:element name="tax" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="id" type="xsd:integer" use="required"/> + </xsd:complexType> + <xsd:complexType name="AncillaryData"> + <xsd:sequence> + <xsd:element name="ticketNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="passengerName" type="xsd:string" minOccurs="0"/> + <xsd:element name="connectedTicketNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="creditReasonIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="service" type="tns:Service" minOccurs="0" maxOccurs="1000"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="Service"> + <xsd:sequence> + <xsd:element name="categoryCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="subcategoryCode" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="id" type="xsd:integer" use="required"/> + </xsd:complexType> + <xsd:complexType name="LodgingData"> + <xsd:sequence> + <xsd:element name="checkInDate" type="xsd:string" minOccurs="0"/> + <xsd:element name="checkOutDate" type="xsd:string" minOccurs="0"/> + <xsd:element name="dailyRoomRate1" type="tns:amount" minOccurs="0"/> + <xsd:element name="dailyRoomRate2" type="tns:amount" minOccurs="0"/> + <xsd:element name="dailyRoomRate3" type="tns:amount" minOccurs="0"/> + <xsd:element name="roomNights1" type="xsd:integer" minOccurs="0"/> + <xsd:element name="roomNights2" type="xsd:integer" minOccurs="0"/> + <xsd:element name="roomNights3" type="xsd:integer" minOccurs="0"/> + <xsd:element name="guestSmokingPreference" type="xsd:string" minOccurs="0"/> + <xsd:element name="numberOfRoomsBooked" type="xsd:integer" minOccurs="0"/> + <xsd:element name="numberOfGuests" type="xsd:integer" minOccurs="0"/> + <xsd:element name="roomBedType" type="xsd:string" minOccurs="0"/> + <xsd:element name="roomTaxElements" type="xsd:string" minOccurs="0"/> + <xsd:element name="roomRateType" type="xsd:string" minOccurs="0"/> + <xsd:element name="guestName" type="xsd:string" minOccurs="0"/> + <xsd:element name="customerServicePhoneNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="corporateClientCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="promotionalCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="additionalCoupon" type="xsd:string" minOccurs="0"/> + <xsd:element name="roomLocation" type="xsd:string" minOccurs="0"/> + <xsd:element name="specialProgramCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="tax" type="tns:amount" minOccurs="0"/> + <xsd:element name="prepaidCost" type="tns:amount" minOccurs="0"/> + <xsd:element name="foodAndBeverageCost" type="tns:amount" minOccurs="0"/> + <xsd:element name="roomTax" type="tns:amount" minOccurs="0"/> + <xsd:element name="adjustmentAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="phoneCost" type="tns:amount" minOccurs="0"/> + <xsd:element name="restaurantCost" type="tns:amount" minOccurs="0"/> + <xsd:element name="roomServiceCost" type="tns:amount" minOccurs="0"/> + <xsd:element name="miniBarCost" type="tns:amount" minOccurs="0"/> + <xsd:element name="laundryCost" type="tns:amount" minOccurs="0"/> + <xsd:element name="miscellaneousCost" type="tns:amount" minOccurs="0"/> + <xsd:element name="giftShopCost" type="tns:amount" minOccurs="0"/> + <xsd:element name="movieCost" type="tns:amount" minOccurs="0"/> + <xsd:element name="healthClubCost" type="tns:amount" minOccurs="0"/> + <xsd:element name="valetParkingCost" type="tns:amount" minOccurs="0"/> + <xsd:element name="cashDisbursementCost" type="tns:amount" minOccurs="0"/> + <xsd:element name="nonRoomCost" type="tns:amount" minOccurs="0"/> + <xsd:element name="businessCenterCost" type="tns:amount" minOccurs="0"/> + <xsd:element name="loungeBarCost" type="tns:amount" minOccurs="0"/> + <xsd:element name="transportationCost" type="tns:amount" minOccurs="0"/> + <xsd:element name="gratuityCost" type="tns:amount" minOccurs="0"/> + <xsd:element name="conferenceRoomCost" type="tns:amount" minOccurs="0"/> + <xsd:element name="audioVisualCost" type="tns:amount" minOccurs="0"/> + <xsd:element name="banquetCost" type="tns:amount" minOccurs="0"/> + <xsd:element name="internetAccessCost" type="tns:amount" minOccurs="0"/> + <xsd:element name="earlyCheckOutCost" type="tns:amount" minOccurs="0"/> + <xsd:element name="nonRoomTax" type="tns:amount" minOccurs="0"/> + <xsd:element name="travelAgencyCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="travelAgencyName" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="Pos"> + <xsd:sequence> + <xsd:element name="entryMode" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardPresent" type="xsd:string" minOccurs="0"/> + <xsd:element name="terminalCapability" type="xsd:string" minOccurs="0"/> + <xsd:element name="trackData" type="xsd:string" minOccurs="0"/> + <xsd:element name="terminalID" type="xsd:string" minOccurs="0"/> + <xsd:element name="terminalType" type="xsd:string" minOccurs="0"/> + <xsd:element name="terminalLocation" type="xsd:string" minOccurs="0"/> + <xsd:element name="transactionSecurity" type="xsd:string" minOccurs="0"/> + <xsd:element name="catLevel" type="xsd:string" minOccurs="0"/> + <xsd:element name="conditionCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="environment" type="xsd:string" minOccurs="0"/> + <xsd:element name="paymentData" type="xsd:string" minOccurs="0"/> + <xsd:element name="deviceReaderData" type="xsd:string" minOccurs="0"/> + <xsd:element name="encryptionAlgorithm" type="xsd:string" minOccurs="0"/> + <xsd:element name="encodingMethod" type="xsd:string" minOccurs="0"/> + <xsd:element name="deviceID" type="xsd:string" minOccurs="0"/> + <xsd:element name="serviceCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="terminalIDAlternate" type="xsd:string" minOccurs="0" /> + <xsd:element name="terminalCompliance" type="xsd:integer" minOccurs="0" /> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="EncryptedPayment"> + <xsd:sequence> + <xsd:element name="descriptor" type="xsd:string" minOccurs="0"/> + <xsd:element name="data" type="xsd:string" minOccurs="0"/> + <xsd:element name="encoding" type="xsd:string" minOccurs="0"/> + <xsd:element name="wrappedKey" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="Installment"> + <xsd:sequence> + <xsd:element name="sequence" type="xsd:string" minOccurs="0"/> + <xsd:element name="totalCount" type="xsd:string" minOccurs="0"/> + <xsd:element name="totalAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="frequency" type="xsd:string" minOccurs="0"/> + <xsd:element name="amount" type="xsd:string" minOccurs="0"/> + <xsd:element name="planType" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="MDDField"> + <xsd:simpleContent> + <xsd:extension base="xsd:string"> + <xsd:attribute name="id" type="xsd:integer" use="required"/> + </xsd:extension> + </xsd:simpleContent> + </xsd:complexType> + <xsd:complexType name="MerchantDefinedData"> + <xsd:sequence> + <xsd:element name="field1" type="xsd:string" minOccurs="0"/> + <xsd:element name="field2" type="xsd:string" minOccurs="0"/> + <xsd:element name="field3" type="xsd:string" minOccurs="0"/> + <xsd:element name="field4" type="xsd:string" minOccurs="0"/> + <xsd:element name="field5" type="xsd:string" minOccurs="0"/> + <xsd:element name="field6" type="xsd:string" minOccurs="0"/> + <xsd:element name="field7" type="xsd:string" minOccurs="0"/> + <xsd:element name="field8" type="xsd:string" minOccurs="0"/> + <xsd:element name="field9" type="xsd:string" minOccurs="0"/> + <xsd:element name="field10" type="xsd:string" minOccurs="0"/> + <xsd:element name="field11" type="xsd:string" minOccurs="0"/> + <xsd:element name="field12" type="xsd:string" minOccurs="0"/> + <xsd:element name="field13" type="xsd:string" minOccurs="0"/> + <xsd:element name="field14" type="xsd:string" minOccurs="0"/> + <xsd:element name="field15" type="xsd:string" minOccurs="0"/> + <xsd:element name="field16" type="xsd:string" minOccurs="0"/> + <xsd:element name="field17" type="xsd:string" minOccurs="0"/> + <xsd:element name="field18" type="xsd:string" minOccurs="0"/> + <xsd:element name="field19" type="xsd:string" minOccurs="0"/> + <xsd:element name="field20" type="xsd:string" minOccurs="0"/> + <xsd:element name="mddField" type="tns:MDDField" minOccurs="0" maxOccurs="100"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="MerchantSecureData"> + <xsd:sequence> + <xsd:element name="field1" type="xsd:string" minOccurs="0"/> + <xsd:element name="field2" type="xsd:string" minOccurs="0"/> + <xsd:element name="field3" type="xsd:string" minOccurs="0"/> + <xsd:element name="field4" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="ReplyReserved"> + <xsd:sequence> + <xsd:any processContents="skip" minOccurs="0" maxOccurs="999"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="RequestReserved"> + <xsd:sequence> + <xsd:element name="name" type="xsd:string"/> + <xsd:element name="value" type="xsd:string"/> + </xsd:sequence> + </xsd:complexType> + <!-- PayPalGetTxnDetails --> + <xsd:complexType name="PayPalGetTxnDetailsReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="payer" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerId" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerCountry" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerBusiness" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerSalutation" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerFirstname" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerMiddlename" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerLastname" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerSuffix" type="xsd:string" minOccurs="0"/> + <xsd:element name="addressID" type="xsd:string" minOccurs="0"/> + <xsd:element name="addressStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="shipToName" type="xsd:string" minOccurs="0"/> + <xsd:element name="shipToAddress1" type="xsd:string" minOccurs="0"/> + <xsd:element name="shipToAddress2" type="xsd:string" minOccurs="0"/> + <xsd:element name="shipToCity" type="xsd:string" minOccurs="0"/> + <xsd:element name="shipToState" type="xsd:string" minOccurs="0"/> + <xsd:element name="shipToCountry" type="xsd:string" minOccurs="0"/> + <xsd:element name="shipToZip" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerPhone" type="xsd:string" minOccurs="0"/> + <xsd:element name="transactionId" type="xsd:string" minOccurs="0"/> + <xsd:element name="parentTransactionId" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalReceiptId" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalTransactiontype" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalPaymentType" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalOrderTime" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalPaymentGrossAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalFeeAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="currency" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalSettleAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalTaxAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalExchangeRate" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalPaymentStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalPendingReason" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalReasonCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="protectionEligibility" type="xsd:string" minOccurs="0"/> + <xsd:element name="protectionEligibilityType" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalNote" type="xsd:string" minOccurs="0"/> + <xsd:element name="invoiceNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="item" type="tns:Item" minOccurs="0" maxOccurs="1000" /> + <xsd:element name="errorCode" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + + <!-- end of PayPalGetTxnDetails --> + + <!-- PayPalTransactionSearchReply --> + <xsd:complexType name="PayPalTransactionSearchReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="transaction" type="tns:PaypalTransaction" minOccurs="0" maxOccurs="999" /> + <xsd:element name="errorCode" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="PaypalTransaction"> + <xsd:sequence> + <xsd:element name="transactionTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="transactionTimeZone" type="xsd:string" minOccurs="0"/> + <xsd:element name="transactionType" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalPayerOrPayeeEmail" type="xsd:string" minOccurs="0"/> + <xsd:element name="customerDisplayName" type="xsd:string" minOccurs="0"/> + <xsd:element name="transactionID" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalPaymentStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="grandTotalAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="currency" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalFeeAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalNetAmount" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="id" type="xsd:integer" use="optional"/> + </xsd:complexType> + <!-- end of PayPalTransactionSearchReply --> + + <xsd:complexType name="CCDCCUpdateService"> + <xsd:sequence> + <xsd:element name="reason" type="xsd:string" minOccurs="0"/> + <xsd:element name="action" type="xsd:string" minOccurs="0"/> + <xsd:element name="dccRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="captureRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="creditRequestID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <!-- Merchant Descriptor fields for Service Fee. goes into RequestMessage--> + <xsd:complexType name="ServiceFee"> + <xsd:sequence> + <xsd:element name="merchantDescriptor" type="xsd:string" minOccurs="0"/> + <xsd:element name="merchantDescriptorContact" type="xsd:string" minOccurs="0"/> + <xsd:element name="merchantDescriptorState" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <!-- EMV transaction data request/reply start --> + <xsd:complexType name="EmvRequest"> + <xsd:sequence> + <xsd:element name="combinedTags" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardSequenceNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="aidAndDFname" type="xsd:string" minOccurs="0"/> + <xsd:element name="fallback" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="EmvReply"> + <xsd:sequence> + <xsd:element name="combinedTags" type="xsd:string" minOccurs="0"/> + <xsd:element name="chipValidationResults" type="xsd:string" minOccurs="0"/> + <xsd:element name="chipValidationType" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <!-- EMV transaction data request/reply end --> + <!-- Auth Reversal time out merchant intitated --> + <xsd:complexType name="OriginalTransaction"> + <xsd:sequence> + <xsd:element name="amount" type="tns:amount" minOccurs="0" /> + <xsd:element name="reasonCode" type="xsd:string" minOccurs="0" /> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="HostedDataCreateService"> + <xsd:sequence> + <xsd:element name="profileID" type="xsd:string" minOccurs="0"/> + <xsd:element name="paymentMethod" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="HostedDataRetrieveService"> + <xsd:sequence> + <xsd:element name="profileID" type="xsd:string" minOccurs="0"/> + <xsd:element name="tokenValue" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + + <xsd:complexType name="HostedDataCreateReply"> + <xsd:sequence> + <xsd:element name="responseMessage" type="xsd:string" minOccurs="0" /> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="cardAccountNumberToken" type="xsd:string" minOccurs="0" /> + <xsd:element name="customerID" type="xsd:string" minOccurs="0" /> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="HostedDataRetrieveReply"> + <xsd:sequence> + <xsd:element name="responseMessage" type="xsd:string" minOccurs="0" /> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="aggregatorMerchantIdentifier" type="xsd:string" minOccurs="0" /> + <xsd:element name="customerFirstName" type="xsd:string" minOccurs="0" /> + <xsd:element name="customerLastName" type="xsd:string" minOccurs="0" /> + <xsd:element name="customerID" type="xsd:string" minOccurs="0" /> + <xsd:element name="paymentMethod" type="xsd:string" minOccurs="0" /> + <xsd:element name="billToStreet1" type="xsd:string" minOccurs="0"/> + <xsd:element name="billToStreet2" type="xsd:string" minOccurs="0"/> + <xsd:element name="billToEmail" type="xsd:string" minOccurs="0"/> + <xsd:element name="billToState" type="xsd:string" minOccurs="0"/> + <xsd:element name="billToFirstName" type="xsd:string" minOccurs="0"/> + <xsd:element name="billToLastName" type="xsd:string" minOccurs="0"/> + <xsd:element name="billToCity" type="xsd:string" minOccurs="0"/> + <xsd:element name="billToCountry" type="xsd:string" minOccurs="0"/> + <xsd:element name="billToPostalCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardAccountNumber" type="xsd:string" minOccurs="0" /> + <xsd:element name="cardType" type="xsd:string" minOccurs="0" /> + <xsd:element name="cardExpirationMonth" type="xsd:string" minOccurs="0" /> + <xsd:element name="cardExpirationYear" type="xsd:string" minOccurs="0" /> + <xsd:element name="cardIssueNumber" type="xsd:string" minOccurs="0" /> + <xsd:element name="cardStartMonth" type="xsd:string" minOccurs="0" /> + <xsd:element name="cardStartYear" type="xsd:string" minOccurs="0" /> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="AutoRentalData"> + <xsd:sequence> + <xsd:element name="adjustmentCost" type="tns:amount" minOccurs="0" /> + <xsd:element name="adjustmentCode" type="xsd:string" minOccurs="0" /> + <xsd:element name="agreementNumber" type="xsd:string" minOccurs="0" /> + <xsd:element name="classCode" type="xsd:string" minOccurs="0" /> + <xsd:element name="customerServicePhoneNumber" type="xsd:string" minOccurs="0" /> + <xsd:element name="dailyRate" type="tns:amount" minOccurs="0" /> + <xsd:element name="mileageCost" type="tns:amount" minOccurs="0" /> + <xsd:element name="gasCost" type="tns:amount" minOccurs="0" /> + <xsd:element name="insuranceCost" type="tns:amount" minOccurs="0" /> + <xsd:element name="lateReturnCost" type="tns:amount" minOccurs="0" /> + <xsd:element name="maximumFreeMiles" type="xsd:integer" minOccurs="0" /> + <xsd:element name="milesTraveled" type="xsd:integer" minOccurs="0" /> + <xsd:element name="oneWayCost" type="tns:amount" minOccurs="0" /> + <xsd:element name="parkingViolationCost" type="tns:amount" minOccurs="0" /> + <xsd:element name="pickUpCity" type="xsd:string" minOccurs="0" /> + <xsd:element name="pickUpCountry" type="xsd:string" minOccurs="0" /> + <xsd:element name="pickUpDate" type="xsd:string" minOccurs="0" /> + <xsd:element name="pickUpState" type="xsd:string" minOccurs="0" /> + <xsd:element name="pickUpTime" type="xsd:integer" minOccurs="0" /> + <xsd:element name="ratePerMile" type="tns:amount" minOccurs="0" /> + <xsd:element name="renterName" type="xsd:string" minOccurs="0" /> + <xsd:element name="returnCity" type="xsd:string" minOccurs="0" /> + <xsd:element name="returnCountry" type="xsd:string" minOccurs="0" /> + <xsd:element name="returnDate" type="xsd:string" minOccurs="0" /> + <xsd:element name="returnLocationID" type="xsd:string" minOccurs="0" /> + <xsd:element name="returnState" type="xsd:string" minOccurs="0" /> + <xsd:element name="returnTime" type="xsd:integer" minOccurs="0" /> + <xsd:element name="specialProgramCode" type="xsd:string" minOccurs="0" /> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="VCReply"> + <xsd:sequence> + <xsd:element name="creationTimeStamp" type="xsd:string" minOccurs="0" /> + <xsd:element name="alternateShippingAddressCountryCode" type="xsd:string" minOccurs="0" /> + <xsd:element name="alternateShippingAddressPostalCode" type="xsd:string" minOccurs="0" /> + <xsd:element name="vcAccountLoginName" type="xsd:string" minOccurs="0" /> + <xsd:element name="vcAccountFirstName" type="xsd:string" minOccurs="0" /> + <xsd:element name="vcAccountLastName" type="xsd:string" minOccurs="0" /> + <xsd:element name="vcAccountEncryptedID" type="xsd:string" minOccurs="0" /> + <xsd:element name="vcAccountEmail" type="xsd:string" minOccurs="0" /> + <xsd:element name="vcAccountMobilePhoneNumber" type="xsd:string" minOccurs="0" /> + <xsd:element name="merchantReferenceID" type="xsd:string" minOccurs="0" /> + <xsd:element name="subtotalAmount" type="xsd:string" minOccurs="0" /> + <xsd:element name="shippingHandlingAmount" type="xsd:string" minOccurs="0" /> + <xsd:element name="taxAmount" type="xsd:string" minOccurs="0" /> + <xsd:element name="discountAmount" type="xsd:string" minOccurs="0" /> + <xsd:element name="giftWrapAmount" type="xsd:string" minOccurs="0" /> + <xsd:element name="uncategorizedAmount" type="xsd:string" minOccurs="0" /> + <xsd:element name="totalPurchaseAmount" type="xsd:string" minOccurs="0" /> + <xsd:element name="walletReferenceID" type="xsd:string" minOccurs="0" /> + <xsd:element name="promotionCode" type="xsd:string" minOccurs="0" /> + <xsd:element name="paymentInstrumentID" type="xsd:string" minOccurs="0" /> + <xsd:element name="cardVerificationStatus" type="xsd:string" minOccurs="0" /> + <xsd:element name="issuerID" type="xsd:string" minOccurs="0" /> + <xsd:element name="paymentInstrumentNickName" type="xsd:string" minOccurs="0" /> + <xsd:element name="nameOnCard" type="xsd:string" minOccurs="0" /> + <xsd:element name="cardType" type="xsd:string" minOccurs="0" /> + <xsd:element name="cardGroup" type="xsd:string" minOccurs="0" /> + <xsd:element name="cardArt" type="tns:VCCardArt" minOccurs="0" /> + <xsd:element name="riskAdvice" type="xsd:string" minOccurs="0" /> + <xsd:element name="riskScore" type="xsd:string" minOccurs="0" /> + <xsd:element name="riskAdditionalData" type="xsd:string" minOccurs="0" /> + <xsd:element name="avsCodeRaw" type="xsd:string" minOccurs="0" /> + <xsd:element name="cvnCodeRaw" type="xsd:string" minOccurs="0" /> + <xsd:element name="eciRaw" type="xsd:string" minOccurs="0" /> + <xsd:element name="eci" type="xsd:string" minOccurs="0" /> + <xsd:element name="cavv" type="xsd:string" minOccurs="0" /> + <xsd:element name="veresEnrolled" type="xsd:string" minOccurs="0" /> + <xsd:element name="veresTimeStamp" type="xsd:string" minOccurs="0" /> + <xsd:element name="paresStatus" type="xsd:string" minOccurs="0" /> + <xsd:element name="paresTimeStamp" type="xsd:string" minOccurs="0" /> + <xsd:element name="xid" type="xsd:string" minOccurs="0" /> + <xsd:element name="customData" type="tns:VCCustomData" minOccurs="0" /> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="VCCardArt"> + <xsd:sequence> + <xsd:element name="fileName" type="xsd:string" minOccurs="0" /> + <xsd:element name="height" type="xsd:string" minOccurs="0" /> + <xsd:element name="width" type="xsd:string" minOccurs="0" /> + </xsd:sequence> + <xsd:attribute name="id" type="xsd:integer" use="optional"/> + </xsd:complexType> + + <xsd:complexType name="VCCustomData"> + <xsd:sequence> + <xsd:element name="name" type="xsd:string" minOccurs="0" /> + <xsd:element name="value" type="xsd:string" minOccurs="0" /> + </xsd:sequence> + <xsd:attribute name="id" type="xsd:integer" use="optional"/> + </xsd:complexType> + + <xsd:complexType name="DecryptVisaCheckoutDataReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="EncryptPaymentDataReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="requestDateTime" type="tns:dateTime" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="BinLookupService"> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="BinLookupReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="cardType" type="xsd:string" minOccurs="0" /> + <xsd:element name="cardGroup" type="xsd:string" minOccurs="0" /> + <xsd:element name="issuerName" type="xsd:string" minOccurs="0" /> + <xsd:element name="issuerCountry" type="xsd:string" minOccurs="0" /> + <xsd:element name="issuedCurrency" type="xsd:string" minOccurs="0" /> + <xsd:element name="level2Eligibility" type="xsd:string" minOccurs="0" /> + <xsd:element name="level3Eligibility" type="xsd:string" minOccurs="0" /> + <xsd:element name="cardCategory" type="xsd:string" minOccurs="0" /> + <xsd:element name="crossBorderIndicator" type="xsd:string" minOccurs="0" /> + <xsd:element name="issuerPhoneNumber" type="xsd:string" minOccurs="0" /> + <xsd:element name="billingCurrency" type="xsd:string" minOccurs="0" /> + <xsd:element name="billingCurrencyMinorDigits" type="xsd:string" minOccurs="0" /> + <xsd:element name="fastFundsIndicator" type="xsd:string" minOccurs="0" /> + <xsd:element name="octBlockIndicator" type="xsd:string" minOccurs="0" /> + <xsd:element name="onlineGamblingBlockIndicator" type="xsd:string" minOccurs="0" /> + <xsd:element name="requestDateTime" type="tns:dateTime" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> +</xsd:schema> + diff --git a/test/schema/cyber_source/CyberSourceTransaction_1.153.xsd b/test/schema/cyber_source/CyberSourceTransaction_1.153.xsd new file mode 100644 index 00000000000..1bd9f4a1b04 --- /dev/null +++ b/test/schema/cyber_source/CyberSourceTransaction_1.153.xsd @@ -0,0 +1,4770 @@ +<?xml version="1.0" encoding="utf-8"?> +<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:tns="urn:schemas-cybersource-com:transaction-data-1.153" targetNamespace="urn:schemas-cybersource-com:transaction-data-1.153" elementFormDefault="qualified" attributeFormDefault="unqualified"> + <xsd:simpleType name="amount"> + <xsd:restriction base="xsd:string"/> + </xsd:simpleType> + <xsd:simpleType name="boolean"> + <xsd:restriction base="xsd:string"/> + </xsd:simpleType> + <xsd:simpleType name="dateTime"> + <xsd:restriction base="xsd:string"/> + </xsd:simpleType> + <xsd:complexType name="Item"> + <xsd:sequence> + <xsd:element name="unitPrice" type="tns:amount" minOccurs="0"/> + <xsd:element name="quantity" type="tns:amount" minOccurs="0"/> + <xsd:element name="productCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="productName" type="xsd:string" minOccurs="0"/> + <xsd:element name="productSKU" type="xsd:string" minOccurs="0"/> + <xsd:element name="productRisk" type="xsd:string" minOccurs="0"/> + <xsd:element name="taxAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="cityOverrideAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="cityOverrideRate" type="tns:amount" minOccurs="0"/> + <xsd:element name="countyOverrideAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="countyOverrideRate" type="tns:amount" minOccurs="0"/> + <xsd:element name="districtOverrideAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="districtOverrideRate" type="tns:amount" minOccurs="0"/> + <xsd:element name="stateOverrideAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="stateOverrideRate" type="tns:amount" minOccurs="0"/> + <xsd:element name="countryOverrideAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="countryOverrideRate" type="tns:amount" minOccurs="0"/> + <xsd:element name="orderAcceptanceCity" type="xsd:string" minOccurs="0"/> + <xsd:element name="orderAcceptanceCounty" type="xsd:string" minOccurs="0"/> + <xsd:element name="orderAcceptanceCountry" type="xsd:string" minOccurs="0"/> + <xsd:element name="orderAcceptanceState" type="xsd:string" minOccurs="0"/> + <xsd:element name="orderAcceptancePostalCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="orderOriginCity" type="xsd:string" minOccurs="0"/> + <xsd:element name="orderOriginCounty" type="xsd:string" minOccurs="0"/> + <xsd:element name="orderOriginCountry" type="xsd:string" minOccurs="0"/> + <xsd:element name="orderOriginState" type="xsd:string" minOccurs="0"/> + <xsd:element name="orderOriginPostalCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="shipFromCity" type="xsd:string" minOccurs="0"/> + <xsd:element name="shipFromCounty" type="xsd:string" minOccurs="0"/> + <xsd:element name="shipFromCountry" type="xsd:string" minOccurs="0"/> + <xsd:element name="shipFromState" type="xsd:string" minOccurs="0"/> + <xsd:element name="shipFromPostalCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="export" type="xsd:string" minOccurs="0"/> + <xsd:element name="noExport" type="xsd:string" minOccurs="0"/> + <xsd:element name="nationalTax" type="tns:amount" minOccurs="0"/> + <xsd:element name="vatRate" type="tns:amount" minOccurs="0"/> + <xsd:element name="sellerRegistration" type="xsd:string" minOccurs="0"/> + <xsd:element name="sellerRegistration0" type="xsd:string" minOccurs="0"/> + <xsd:element name="sellerRegistration1" type="xsd:string" minOccurs="0"/> + <xsd:element name="sellerRegistration2" type="xsd:string" minOccurs="0"/> + <xsd:element name="sellerRegistration3" type="xsd:string" minOccurs="0"/> + <xsd:element name="sellerRegistration4" type="xsd:string" minOccurs="0"/> + <xsd:element name="sellerRegistration5" type="xsd:string" minOccurs="0"/> + <xsd:element name="sellerRegistration6" type="xsd:string" minOccurs="0"/> + <xsd:element name="sellerRegistration7" type="xsd:string" minOccurs="0"/> + <xsd:element name="sellerRegistration8" type="xsd:string" minOccurs="0"/> + <xsd:element name="sellerRegistration9" type="xsd:string" minOccurs="0"/> + <xsd:element name="buyerRegistration" type="xsd:string" minOccurs="0"/> + <xsd:element name="middlemanRegistration" type="xsd:string" minOccurs="0"/> + <xsd:element name="pointOfTitleTransfer" type="xsd:string" minOccurs="0"/> + <xsd:element name="giftCategory" type="tns:boolean" minOccurs="0"/> + <xsd:element name="timeCategory" type="xsd:string" minOccurs="0"/> + <xsd:element name="hostHedge" type="xsd:string" minOccurs="0"/> + <xsd:element name="timeHedge" type="xsd:string" minOccurs="0"/> + <xsd:element name="velocityHedge" type="xsd:string" minOccurs="0"/> + <xsd:element name="nonsensicalHedge" type="xsd:string" minOccurs="0"/> + <xsd:element name="phoneHedge" type="xsd:string" minOccurs="0"/> + <xsd:element name="obscenitiesHedge" type="xsd:string" minOccurs="0"/> + <xsd:element name="unitOfMeasure" type="xsd:string" minOccurs="0"/> + <xsd:element name="taxRate" type="tns:amount" minOccurs="0"/> + <xsd:element name="totalAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="discountAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="discountRate" type="tns:amount" minOccurs="0"/> + <xsd:element name="commodityCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="grossNetIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="taxTypeApplied" type="xsd:string" minOccurs="0"/> + <xsd:element name="discountIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="alternateTaxID" type="xsd:string" minOccurs="0"/> + <xsd:element name="alternateTaxAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="alternateTaxTypeApplied" type="xsd:string" minOccurs="0"/> + <xsd:element name="alternateTaxRate" type="tns:amount" minOccurs="0"/> + <xsd:element name="alternateTaxType" type="xsd:string" minOccurs="0"/> + <xsd:element name="localTax" type="tns:amount" minOccurs="0"/> + <xsd:element name="zeroCostToCustomerIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="passengerFirstName" type="xsd:string" minOccurs="0"/> + <xsd:element name="passengerLastName" type="xsd:string" minOccurs="0"/> + <xsd:element name="passengerID" type="xsd:string" minOccurs="0"/> + <xsd:element name="passengerStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="passengerType" type="xsd:string" minOccurs="0"/> + <xsd:element name="passengerEmail" type="xsd:string" minOccurs="0"/> + <xsd:element name="passengerPhone" type="xsd:string" minOccurs="0"/> + <xsd:element name="invoiceNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="productDescription" type="xsd:string" minOccurs="0"/> + <xsd:element name="taxStatusIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="discountManagementIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="typeOfSupply" type="xsd:string" minOccurs="0"/> + <xsd:element name="sign" type="xsd:string" minOccurs="0"/> + <xsd:element name="unitTaxAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="weightAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="weightID" type="xsd:string" minOccurs="0"/> + <xsd:element name="weightUnitMeasurement" type="xsd:string" minOccurs="0"/> + + <xsd:element name="otherTax_1_type" type="xsd:string" minOccurs="0"/> + <xsd:element name="otherTax_1_amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="otherTax_1_rate" type="tns:amount" minOccurs="0"/> + <xsd:element name="otherTax_1_statusIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="otherTax_2_type" type="xsd:string" minOccurs="0"/> + <xsd:element name="otherTax_2_amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="otherTax_2_rate" type="tns:amount" minOccurs="0"/> + <xsd:element name="otherTax_2_statusIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="otherTax_3_type" type="xsd:string" minOccurs="0"/> + <xsd:element name="otherTax_3_amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="otherTax_3_rate" type="tns:amount" minOccurs="0"/> + <xsd:element name="otherTax_3_statusIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="otherTax_4_type" type="xsd:string" minOccurs="0"/> + <xsd:element name="otherTax_4_amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="otherTax_4_rate" type="tns:amount" minOccurs="0"/> + <xsd:element name="otherTax_4_statusIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="otherTax_5_type" type="xsd:string" minOccurs="0"/> + <xsd:element name="otherTax_5_amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="otherTax_5_rate" type="tns:amount" minOccurs="0"/> + <xsd:element name="otherTax_5_statusIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="otherTax_6_type" type="xsd:string" minOccurs="0"/> + <xsd:element name="otherTax_6_amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="otherTax_6_rate" type="tns:amount" minOccurs="0"/> + <xsd:element name="otherTax_6_statusIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="otherTax_7_type" type="xsd:string" minOccurs="0"/> + <xsd:element name="otherTax_7_amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="otherTax_7_rate" type="tns:amount" minOccurs="0"/> + <xsd:element name="otherTax_7_statusIndicator" type="xsd:string" minOccurs="0"/> + + + <xsd:element name="referenceData_1_number" type="xsd:string" minOccurs="0"/> + <xsd:element name="referenceData_1_code" type="xsd:string" minOccurs="0"/> + + <xsd:element name="referenceData_2_number" type="xsd:string" minOccurs="0"/> + <xsd:element name="referenceData_2_code" type="xsd:string" minOccurs="0"/> + + <xsd:element name="referenceData_3_number" type="xsd:string" minOccurs="0"/> + <xsd:element name="referenceData_3_code" type="xsd:string" minOccurs="0"/> + + <xsd:element name="referenceData_4_number" type="xsd:string" minOccurs="0"/> + <xsd:element name="referenceData_4_code" type="xsd:string" minOccurs="0"/> + + <xsd:element name="referenceData_5_number" type="xsd:string" minOccurs="0"/> + <xsd:element name="referenceData_5_code" type="xsd:string" minOccurs="0"/> + + <xsd:element name="referenceData_6_number" type="xsd:string" minOccurs="0"/> + <xsd:element name="referenceData_6_code" type="xsd:string" minOccurs="0"/> + + <xsd:element name="referenceData_7_number" type="xsd:string" minOccurs="0"/> + <xsd:element name="referenceData_7_code" type="xsd:string" minOccurs="0"/> + + </xsd:sequence> + <xsd:attribute name="id" type="xsd:integer" use="optional"/> + </xsd:complexType> + + <xsd:complexType name="CCAuthService"> + <xsd:sequence> + <xsd:element name="cavv" type="xsd:string" minOccurs="0"/> + <xsd:element name="cavvAlgorithm" type="xsd:string" minOccurs="0"/> + <xsd:element name="networkTokenCryptogram" type="xsd:string" minOccurs="0"/> + <xsd:element name="paSpecificationVersion" type="xsd:string" minOccurs="0"/> + <xsd:element name="directoryServerTransactionID" type="xsd:string" minOccurs="0"/> + <xsd:element name="commerceIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="eciRaw" type="xsd:string" minOccurs="0"/> + <xsd:element name="xid" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="avsLevel" type="xsd:string" minOccurs="0"/> + <xsd:element name="fxQuoteID" type="xsd:string" minOccurs="0"/> + <xsd:element name="returnAuthRecord" type="tns:boolean" minOccurs="0"/> + <xsd:element name="authType" type="xsd:string" minOccurs="0"/> + <xsd:element name="verbalAuthCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="billPayment" type="tns:boolean" minOccurs="0"/> + <xsd:element name="authenticationXID" type="xsd:string" minOccurs="0"/> + <xsd:element name="authorizationXID" type="xsd:string" minOccurs="0"/> + <xsd:element name="industryDatatype" type="xsd:string" minOccurs="0"/> + <xsd:element name="traceNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="checksumKey" type="xsd:string" minOccurs="0"/> + <xsd:element name="aggregatorID" type="xsd:string" minOccurs="0"/> + <xsd:element name="aggregatorName" type="xsd:string" minOccurs="0"/> + <xsd:element name="splitTenderIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="veresEnrolled" type="xsd:string" minOccurs="0"/> + <xsd:element name="paresStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="partialAuthIndicator" type="tns:boolean" minOccurs="0"/> + <xsd:element name="captureDate" type="xsd:string" minOccurs="0"/> + <xsd:element name="firstRecurringPayment" type="xsd:string" minOccurs="0"/> + <xsd:element name="duration" type="xsd:integer" minOccurs="0"/> + <xsd:element name="overridePaymentMethod" type="xsd:string" minOccurs="0"/> + <xsd:element name="mobileRemotePaymentType" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardholderVerificationMethod" type="xsd:string" minOccurs="0"/> + <xsd:element name="dccRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="overridePaymentDetails" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardholderAuthenticationMethod" type="xsd:string" minOccurs="0"/> + <xsd:element name="leastCostRouting" type="tns:boolean" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="OCTService"> + <xsd:sequence> + <xsd:element name="commerceIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="networkOrder" type="xsd:string" minOccurs="0"/> + <xsd:element name="overridePaymentMethod" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + + <xsd:complexType name="VerificationService"> + <xsd:sequence> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0" /> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required" /> + </xsd:complexType> + + <xsd:complexType name="CCSaleService"> + <xsd:sequence> + <xsd:element name="overridePaymentMethod" type="xsd:string" minOccurs="0"/> + <xsd:element name="commerceIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="partialAuthIndicator" type="tns:boolean" minOccurs="0"/> + <xsd:element name="cavv" type="xsd:string" minOccurs="0"/> + <xsd:element name="xid" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="industryDatatype" type="xsd:string" minOccurs="0"/> + <xsd:element name="networkTokenCryptogram" type="xsd:string" minOccurs="0"/> + <xsd:element name="paSpecificationVersion" type="xsd:string" minOccurs="0"/> + <xsd:element name="directoryServerTransactionID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + + <xsd:complexType name="CCSaleCreditService"> + <xsd:sequence> + <xsd:element name="overridePaymentMethod" type="xsd:string" minOccurs="0"/> + <xsd:element name="commerceIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="refundReason" type="xsd:string" minOccurs="0"/> + <xsd:element name="saleRequestID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + + <xsd:complexType name="CCSaleReversalService"> + <xsd:sequence> + <xsd:element name="saleRequestID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + + <xsd:complexType name="CCIncrementalAuthService"> + <xsd:sequence> + <xsd:element name="authRequestID" type="xsd:string" minOccurs="0" /> + <xsd:element name="duration" type="xsd:integer" minOccurs="0" /> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required" /> + </xsd:complexType> + <xsd:complexType name="CCCaptureService"> + <xsd:sequence> + <xsd:element name="authType" type="xsd:string" minOccurs="0"/> + <xsd:element name="verbalAuthCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="authRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="partialPaymentID" type="xsd:string" minOccurs="0"/> + <xsd:element name="purchasingLevel" type="xsd:string" minOccurs="0"/> + <xsd:element name="industryDatatype" type="xsd:string" minOccurs="0"/> + <xsd:element name="authRequestToken" type="xsd:string" minOccurs="0"/> + <xsd:element name="merchantReceiptNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="posData" type="xsd:string" minOccurs="0"/> + <xsd:element name="transactionID" type="xsd:string" minOccurs="0"/> + <xsd:element name="checksumKey" type="xsd:string" minOccurs="0"/> + <xsd:element name="gratuityAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="duration" type="xsd:integer" minOccurs="0"/> + <xsd:element name="dpdeBillingMonth" type="xsd:integer" minOccurs="0"/> + <xsd:element name="sequence" type="xsd:string" minOccurs="0"/> + <xsd:element name="totalCount" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationIDAlternate" type="xsd:string" minOccurs="0"/> + <xsd:element name="aggregatorID" type="xsd:string" minOccurs="0"/> + <xsd:element name="aggregatorName" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="CCCreditService"> + <xsd:sequence> + <xsd:element name="captureRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="partialPaymentID" type="xsd:string" minOccurs="0"/> + <xsd:element name="purchasingLevel" type="xsd:string" minOccurs="0"/> + <xsd:element name="industryDatatype" type="xsd:string" minOccurs="0"/> + <xsd:element name="commerceIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="billPayment" type="tns:boolean" minOccurs="0"/> + <xsd:element name="authorizationXID" type="xsd:string" minOccurs="0"/> + <xsd:element name="occurrenceNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="authCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="captureRequestToken" type="xsd:string" minOccurs="0"/> + <xsd:element name="merchantReceiptNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="checksumKey" type="xsd:string" minOccurs="0"/> + <xsd:element name="aggregatorID" type="xsd:string" minOccurs="0"/> + <xsd:element name="aggregatorName" type="xsd:string" minOccurs="0"/> + <xsd:element name="duration" type="xsd:integer" minOccurs="0"/> + <xsd:element name="dpdeBillingMonth" type="xsd:integer" minOccurs="0"/> + <xsd:element name="reconciliationIDAlternate" type="xsd:string" minOccurs="0"/> + <xsd:element name="refundReason" type="xsd:string" minOccurs="0"/> + <xsd:element name="overridePaymentMethod" type="xsd:string" minOccurs="0"/> + <xsd:element name="overridePaymentDetails" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="CCAuthReversalService"> + <xsd:sequence> + <xsd:element name="authRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="authRequestToken" type="xsd:string" minOccurs="0"/> + <xsd:element name="reversalReason" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="CCAutoAuthReversalService"> + <xsd:sequence> + <xsd:element name="authPaymentServiceData" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="authAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="commerceIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="authRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="billAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="authCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="authType" type="xsd:string" minOccurs="0"/> + <xsd:element name="billPayment" type="tns:boolean" minOccurs="0"/> + <xsd:element name="dateAdded" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="CCDCCService"> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="ServiceFeeCalculateService"> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="ECDebitService"> + <xsd:sequence> + <xsd:element name="paymentMode" type="xsd:integer" minOccurs="0"/> + <xsd:element name="referenceNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="settlementMethod" type="xsd:string" minOccurs="0"/> + <xsd:element name="transactionToken" type="xsd:string" minOccurs="0"/> + <xsd:element name="verificationLevel" type="xsd:integer" minOccurs="0"/> + <xsd:element name="partialPaymentID" type="xsd:string" minOccurs="0"/> + <xsd:element name="commerceIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="debitRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="effectiveDate" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="ECCreditService"> + <xsd:sequence> + <xsd:element name="referenceNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="settlementMethod" type="xsd:string" minOccurs="0"/> + <xsd:element name="transactionToken" type="xsd:string" minOccurs="0"/> + <xsd:element name="debitRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="partialPaymentID" type="xsd:string" minOccurs="0"/> + <xsd:element name="commerceIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="debitRequestToken" type="xsd:string" minOccurs="0"/> + <xsd:element name="effectiveDate" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="ECAuthenticateService"> + <xsd:sequence> + <xsd:element name="referenceNumber" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="PayerAuthEnrollService"> + <xsd:sequence> + <xsd:element name="httpAccept" type="xsd:string" minOccurs="0"/> + <xsd:element name="httpUserAgent" type="xsd:string" minOccurs="0"/> + <xsd:element name="merchantName" type="xsd:string" minOccurs="0"/> + <xsd:element name="merchantURL" type="xsd:string" minOccurs="0"/> + <xsd:element name="purchaseDescription" type="xsd:string" minOccurs="0"/> + <xsd:element name="purchaseTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="countryCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="acquirerBin" type="xsd:string" minOccurs="0"/> + <xsd:element name="loginID" type="xsd:string" minOccurs="0"/> + <xsd:element name="password" type="xsd:string" minOccurs="0"/> + <xsd:element name="merchantID" type="xsd:string" minOccurs="0"/> + <xsd:element name="overridePaymentMethod" type="xsd:string" minOccurs="0"/> + <xsd:element name="mobilePhone" type="xsd:string" minOccurs="0"/> + <xsd:element name="MCC" type="xsd:string" minOccurs="0"/> + <xsd:element name="productCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="referenceID" type="xsd:string" minOccurs="0"/> + <xsd:element name="marketingOptIn" type="tns:boolean" minOccurs="0"/> + <xsd:element name="marketingSource" type="xsd:string" minOccurs="0"/> + <xsd:element name="defaultCard" type="tns:boolean" minOccurs="0"/> + <xsd:element name="shipAddressUsageDate" type="xsd:string" minOccurs="0"/> + <xsd:element name="transactionCountDay" type="xsd:string" minOccurs="0"/> + <xsd:element name="transactionCountYear" type="xsd:string" minOccurs="0"/> + <xsd:element name="addCardAttempts" type="xsd:string" minOccurs="0"/> + <xsd:element name="accountPurchases" type="xsd:string" minOccurs="0"/> + <xsd:element name="fraudActivity" type="tns:boolean" minOccurs="0"/> + <xsd:element name="paymentAccountDate" type="xsd:string" minOccurs="0"/> + <xsd:element name="alternateAuthenticationMethod" type="xsd:string" minOccurs="0"/> + <xsd:element name="alternateAuthenticationDate" type="xsd:string" minOccurs="0"/> + <xsd:element name="alternateAuthenticationData" type="xsd:string" minOccurs="0"/> + <xsd:element name="challengeRequired" type="tns:boolean" minOccurs="0"/> + <xsd:element name="challengeCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="preorder" type="xsd:string" minOccurs="0"/> + <xsd:element name="reorder" type="xsd:string" minOccurs="0"/> + <xsd:element name="preorderDate" type="xsd:string" minOccurs="0"/> + <xsd:element name="giftCardAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="giftCardCurrency" type="xsd:string" minOccurs="0"/> + <xsd:element name="giftCardCount" type="xsd:string" minOccurs="0"/> + <xsd:element name="messageCategory" type="xsd:string" minOccurs="0"/> + <xsd:element name="npaCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="recurringOriginalPurchaseDate" type="xsd:string" minOccurs="0"/> + <xsd:element name="transactionMode" type="xsd:string" minOccurs="0"/> + <xsd:element name="recurringEndDate" type="xsd:string" minOccurs="0"/> + <xsd:element name="recurringFrequency" type="xsd:string" minOccurs="0"/> + <xsd:element name="merchantNewCustomer" type="xsd:string" minOccurs="0"/> + <xsd:element name="customerCCAlias" type="xsd:string" minOccurs="0"/> + <xsd:element name="installmentTotalCount" type="xsd:string" minOccurs="0"/> + <xsd:element name="authenticationTransactionID" type="xsd:string" minOccurs="0"/> + <xsd:element name="httpUserAccept" type="xsd:string" minOccurs="0"/> + <xsd:element name="mobilePhoneDomestic" type="xsd:string" minOccurs="0"/> + <xsd:element name="pareqChannel" type="xsd:string" minOccurs="0"/> + <xsd:element name="shoppingChannel" type="xsd:string" minOccurs="0"/> + <xsd:element name="authenticationChannel" type="xsd:string" minOccurs="0"/> + <xsd:element name="merchantTTPCredential" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="PayerAuthValidateService"> + <xsd:sequence> + <xsd:element name="signedPARes" type="xsd:string" minOccurs="0"/> + + <xsd:element name="authenticationTransactionID" type="xsd:string" minOccurs="0"/> + + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="TaxService"> + <xsd:sequence> + <xsd:element name="nexus" type="xsd:string" minOccurs="0"/> + <xsd:element name="noNexus" type="xsd:string" minOccurs="0"/> + <xsd:element name="orderAcceptanceCity" type="xsd:string" minOccurs="0"/> + <xsd:element name="orderAcceptanceCounty" type="xsd:string" minOccurs="0"/> + <xsd:element name="orderAcceptanceCountry" type="xsd:string" minOccurs="0"/> + <xsd:element name="orderAcceptanceState" type="xsd:string" minOccurs="0"/> + <xsd:element name="orderAcceptancePostalCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="orderOriginCity" type="xsd:string" minOccurs="0"/> + <xsd:element name="orderOriginCounty" type="xsd:string" minOccurs="0"/> + <xsd:element name="orderOriginCountry" type="xsd:string" minOccurs="0"/> + <xsd:element name="orderOriginState" type="xsd:string" minOccurs="0"/> + <xsd:element name="orderOriginPostalCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="sellerRegistration" type="xsd:string" minOccurs="0"/> + <xsd:element name="sellerRegistration0" type="xsd:string" minOccurs="0"/> + <xsd:element name="sellerRegistration1" type="xsd:string" minOccurs="0"/> + <xsd:element name="sellerRegistration2" type="xsd:string" minOccurs="0"/> + <xsd:element name="sellerRegistration3" type="xsd:string" minOccurs="0"/> + <xsd:element name="sellerRegistration4" type="xsd:string" minOccurs="0"/> + <xsd:element name="sellerRegistration5" type="xsd:string" minOccurs="0"/> + <xsd:element name="sellerRegistration6" type="xsd:string" minOccurs="0"/> + <xsd:element name="sellerRegistration7" type="xsd:string" minOccurs="0"/> + <xsd:element name="sellerRegistration8" type="xsd:string" minOccurs="0"/> + <xsd:element name="sellerRegistration9" type="xsd:string" minOccurs="0"/> + <xsd:element name="buyerRegistration" type="xsd:string" minOccurs="0"/> + <xsd:element name="middlemanRegistration" type="xsd:string" minOccurs="0"/> + <xsd:element name="pointOfTitleTransfer" type="xsd:string" minOccurs="0"/> + <xsd:element name="commitIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="refundIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="dateOverrideReason" type="xsd:string" minOccurs="0"/> + <xsd:element name="reportingDate" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <!-- DME --> + <xsd:complexType name="DMEService"> + <xsd:sequence> + <xsd:element name="eventType" type="xsd:string" minOccurs="0" /> + <xsd:element name="eventPolicy" type="xsd:string" minOccurs="0" /> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required" /> + </xsd:complexType> + <xsd:complexType name="AFSService"> + <xsd:sequence> + <xsd:element name="avsCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="cvCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="disableAVSScoring" type="tns:boolean" minOccurs="0"/> + <xsd:element name="customRiskModel" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="DAVService"> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="ExportService"> + <xsd:sequence> + <xsd:element name="addressOperator" type="xsd:string" minOccurs="0"/> + <xsd:element name="addressWeight" type="xsd:string" minOccurs="0"/> + <xsd:element name="companyWeight" type="xsd:string" minOccurs="0"/> + <xsd:element name="nameWeight" type="xsd:string" minOccurs="0"/> + <xsd:element name="sanctionsLists" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="FXRatesService"> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="BankTransferService"> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="BankTransferRefundService"> + <xsd:sequence> + <xsd:element name="bankTransferRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="bankTransferRealTimeRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="bankTransferRealTimeReconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="bankTransferRequestToken" type="xsd:string" minOccurs="0"/> + <xsd:element name="bankTransferRealTimeRequestToken" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="BankTransferRealTimeService"> + <xsd:sequence> + <xsd:element name="bankTransferRealTimeType" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="DirectDebitMandateService"> + <xsd:sequence> + <xsd:element name="mandateDescriptor" type="xsd:string" minOccurs="0"/> + <xsd:element name="firstDebitDate" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="DirectDebitService"> + <xsd:sequence> + <xsd:element name="dateCollect" type="xsd:string" minOccurs="0"/> + <xsd:element name="directDebitText" type="xsd:string" minOccurs="0"/> + <xsd:element name="authorizationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="transactionType" type="xsd:string" minOccurs="0"/> + <xsd:element name="directDebitType" type="xsd:string" minOccurs="0"/> + <xsd:element name="validateRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="recurringType" type="xsd:string" minOccurs="0"/> + <xsd:element name="mandateID" type="xsd:string" minOccurs="0"/> + <xsd:element name="validateRequestToken" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="mandateAuthenticationDate" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="DirectDebitRefundService"> + <xsd:sequence> + <xsd:element name="directDebitRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="directDebitRequestToken" type="xsd:string" minOccurs="0"/> + <xsd:element name="directDebitType" type="xsd:string" minOccurs="0"/> + <xsd:element name="recurringType" type="xsd:string" minOccurs="0"/> + <xsd:element name="mandateID" type="xsd:string" minOccurs="0"/> + <xsd:element name="mandateAuthenticationDate" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="DirectDebitValidateService"> + <xsd:sequence> + <xsd:element name="directDebitValidateText" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="DeviceFingerprintData"> + <xsd:sequence> + <xsd:element name="data" type="xsd:string" minOccurs="0"/> + <xsd:element name="provider" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="id" type="xsd:integer" use="required"/> + </xsd:complexType> + <xsd:complexType name="PaySubscriptionCreateService"> + <xsd:sequence> + <xsd:element name="paymentRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="paymentRequestToken" type="xsd:string" minOccurs="0"/> + <xsd:element name="disableAutoAuth" type="tns:boolean" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="PaySubscriptionUpdateService"> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="PaySubscriptionEventUpdateService"> + <xsd:sequence> + <xsd:element name="action" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="PaySubscriptionRetrieveService"> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="PaySubscriptionDeleteService"> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="PayPalPaymentService"> + <xsd:sequence> + <xsd:element name="cancelURL" type="xsd:string" minOccurs="0"/> + <xsd:element name="successURL" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="PayPalCreditService"> + <xsd:sequence> + <xsd:element name="payPalPaymentRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="payPalPaymentRequestToken" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <!--PayPalEcSet--> + <xsd:complexType name="PayPalEcSetService"> + <xsd:sequence> + <xsd:element name="paypalReturn" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalCancelReturn" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalMaxamt" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalCustomerEmail" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalDesc" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalReqconfirmshipping" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalNoshipping" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalAddressOverride" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalToken" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalLc" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalPagestyle" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalHdrimg" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalHdrbordercolor" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalHdrbackcolor" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalPayflowcolor" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalEcSetRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalEcSetRequestToken" type="xsd:string" minOccurs="0"/> + <xsd:element name="promoCode0" type="xsd:string" minOccurs="0"/> + <xsd:element name="requestBillingAddress" type="xsd:string" minOccurs="0"/> + <xsd:element name="invoiceNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalBillingType" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalBillingAgreementDesc" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalPaymentType" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalBillingAgreementCustom" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalLogoimg" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <!--PayPalEcGetDetails--> + <xsd:complexType name="PayPalEcGetDetailsService"> + <xsd:sequence> + <xsd:element name="paypalToken" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalEcSetRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalEcSetRequestToken" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <!--PayPalEcDoPayment--> + <xsd:complexType name="PayPalEcDoPaymentService"> + <xsd:sequence> + <xsd:element name="paypalToken" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalPayerId" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalCustomerEmail" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalDesc" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalEcSetRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalEcSetRequestToken" type="xsd:string" minOccurs="0"/> + <xsd:element name="promoCode0" type="xsd:string" minOccurs="0"/> + <xsd:element name="invoiceNumber" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <!--PayPalDoCapture--> + <xsd:complexType name="PayPalDoCaptureService"> + <xsd:sequence> + <xsd:element name="paypalAuthorizationId" type="xsd:string" minOccurs="0"/> + <xsd:element name="completeType" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalEcDoPaymentRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalEcDoPaymentRequestToken" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalAuthorizationRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalAuthorizationRequestToken" type="xsd:string" minOccurs="0"/> + <xsd:element name="invoiceNumber" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <!--PayPalAuthReversal--> + <xsd:complexType name="PayPalAuthReversalService"> + <xsd:sequence> + <xsd:element name="paypalAuthorizationId" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalEcDoPaymentRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalEcDoPaymentRequestToken" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalAuthorizationRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalAuthorizationRequestToken" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalEcOrderSetupRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalEcOrderSetupRequestToken" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <!--PayPalRefund--> + <xsd:complexType name="PayPalRefundService"> + <xsd:sequence> + <xsd:element name="paypalDoCaptureRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalDoCaptureRequestToken" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalCaptureId" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalNote" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <!--PayPalEcOrderSetup--> + <xsd:complexType name="PayPalEcOrderSetupService"> + <xsd:sequence> + <xsd:element name="paypalToken" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalPayerId" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalCustomerEmail" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalDesc" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalEcSetRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalEcSetRequestToken" type="xsd:string" minOccurs="0"/> + <xsd:element name="promoCode0" type="xsd:string" minOccurs="0"/> + <xsd:element name="invoiceNumber" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <!--PayPalAuthorization--> + <xsd:complexType name="PayPalAuthorizationService"> + <xsd:sequence> + <xsd:element name="paypalOrderId" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalEcOrderSetupRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalEcOrderSetupRequestToken" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalDoRefTransactionRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalDoRefTransactionRequestToken" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalCustomerEmail" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <!--PayPalUpdateAgreement--> + <xsd:complexType name="PayPalUpdateAgreementService"> + <xsd:sequence> + <xsd:element name="paypalBillingAgreementId" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalBillingAgreementStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalBillingAgreementDesc" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalBillingAgreementCustom" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <!--PayPalCreateAgreement--> + <xsd:complexType name="PayPalCreateAgreementService"> + <xsd:sequence> + <xsd:element name="paypalToken" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalEcSetRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalEcSetRequestToken" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <!--PayPalDoRefTransaction--> + <xsd:complexType name="PayPalDoRefTransactionService"> + <xsd:sequence> + <xsd:element name="paypalBillingAgreementId" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalPaymentType" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalReqconfirmshipping" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalReturnFmfDetails" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalSoftDescriptor" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalShippingdiscount" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalDesc" type="xsd:string" minOccurs="0"/> + <xsd:element name="invoiceNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalEcNotifyUrl" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="VoidService"> + <xsd:sequence> + <xsd:element name="voidRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="voidRequestToken" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="PinlessDebitService"> + <xsd:sequence> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="commerceIndicator" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="PinlessDebitValidateService"> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="PinlessDebitReversalService"> + <xsd:sequence> + <xsd:element name="pinlessDebitRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="pinlessDebitRequestToken" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + + <!--PinDebitPurchaseService--> + <xsd:complexType name="PinDebitPurchaseService"> + <xsd:sequence> + <xsd:element name="networkOrder" type="xsd:string" minOccurs="0"/> + <xsd:element name="commerceIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="partialAuthIndicator" type="tns:boolean" minOccurs="0"/> + <xsd:element name="overridePaymentMethod" type="xsd:string" minOccurs="0"/> + <xsd:element name="paymentType" type="xsd:string" minOccurs="0"/> + <xsd:element name="ebtCategory" type="xsd:string" minOccurs="0"/> + <xsd:element name="transactionType" type="xsd:string" minOccurs="0"/> + <xsd:element name="ebtVoucherSerialNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="authorizationCode" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <!--End of PinDebitPurchaseService--> + <!--PinDebitCreditService--> + <xsd:complexType name="PinDebitCreditService"> + <xsd:sequence> + <xsd:element name="networkOrder" type="xsd:string" minOccurs="0"/> + <xsd:element name="commerceIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="overridePaymentMethod" type="xsd:string" minOccurs="0"/> + <xsd:element name="paymentType" type="xsd:string" minOccurs="0"/> + <xsd:element name="ebtCategory" type="xsd:string" minOccurs="0"/> + <xsd:element name="transactionType" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <!--End of PinDebitCreditService--> + <!--PinDebitReversalService--> + <xsd:complexType name="PinDebitReversalService"> + <xsd:sequence> + <xsd:element name="pinDebitRequestID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <!--End of PinDebitReversalService--> + + <!--PayPal upgrade services --> + <xsd:complexType name="PayPalButtonCreateService"> + <xsd:sequence> + <xsd:element name="buttonType" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="PayPalPreapprovedPaymentService"> + <xsd:sequence> + <xsd:element name="mpID" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="PayPalPreapprovedUpdateService"> + <xsd:sequence> + <xsd:element name="mpID" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <!-- China Payment --> + <xsd:complexType name="ChinaPaymentService"> + <xsd:sequence> + <xsd:element name="paymentMode" type="xsd:string" minOccurs="0"/> + <xsd:element name="returnURL" type="xsd:string" minOccurs="0"/> + <xsd:element name="pickUpAddress" type="xsd:string" minOccurs="0"/> + <xsd:element name="pickUpPhoneNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="pickUpPostalCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="pickUpName" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <!-- China Refund --> + <xsd:complexType name="ChinaRefundService"> + <xsd:sequence> + <xsd:element name="chinaPaymentRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="chinaPaymentRequestToken" type="xsd:string" minOccurs="0"/> + <xsd:element name="refundReason" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <!--Boleto Payment --> + <xsd:complexType name="BoletoPaymentService"> + <xsd:sequence> + <xsd:element name="instruction" type="xsd:string" minOccurs="0"/> + <xsd:element name="expirationDate" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="PersonalID"> + <xsd:sequence> + <xsd:element name="number" type="xsd:string" minOccurs="0"/> + <xsd:element name="type" type="xsd:string" minOccurs="0"/> + <xsd:element name="name" type="xsd:string" minOccurs="0"/> + <xsd:element name="country" type="xsd:string" minOccurs="0"/> + <xsd:element name="address" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="Routing"> + <xsd:sequence> + <xsd:element name="networkType" type="xsd:string" minOccurs="0"/> + <xsd:element name="networkLabel" type="xsd:string" minOccurs="0"/> + <xsd:element name="signatureCVMRequired" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="Address"> + <xsd:sequence> + <xsd:element name="street1" type="xsd:string" minOccurs="0"/> + <xsd:element name="street2" type="xsd:string" minOccurs="0"/> + <xsd:element name="city" type="xsd:string" minOccurs="0"/> + <xsd:element name="state" type="xsd:string" minOccurs="0"/> + <xsd:element name="postalCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="country" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="APInitiateService"> + <xsd:sequence> + <xsd:element name="returnURL" type="xsd:string" minOccurs="0"/> + <xsd:element name="productName" type="xsd:string" minOccurs="0"/> + <xsd:element name="productDescription" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="bankID" type="xsd:string" minOccurs="0"/> + <xsd:element name="countryCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="escrowAgreement" type="xsd:string" minOccurs="0"/> + <xsd:element name="languageInterface" type="xsd:string" minOccurs="0"/> + <xsd:element name="intent" type="xsd:string" minOccurs="0"/> + <xsd:element name="successURL" type="xsd:string" minOccurs="0"/> + <xsd:element name="cancelURL" type="xsd:string" minOccurs="0"/> + <xsd:element name="failureURL" type="xsd:string" minOccurs="0"/> + <xsd:element name="overridePaymentMethod" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="APCheckStatusService"> + <xsd:sequence> + <xsd:element name="apInitiateRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="checkStatusRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="sessionsRequestID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="RiskUpdateService"> + <xsd:sequence> + <xsd:element name="actionCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="recordID" type="xsd:string" minOccurs="0"/> + <xsd:element name="recordName" type="xsd:string" minOccurs="0"/> + <xsd:element name="negativeAddress" type="tns:Address" minOccurs="0"/> + <xsd:element name="markingReason" type="xsd:string" minOccurs="0"/> + <xsd:element name="markingNotes" type="xsd:string" minOccurs="0"/> + <xsd:element name="markingRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="deviceFingerprintSmartID" type="xsd:string" minOccurs="0"/> + <xsd:element name="deviceFingerprintTrueIPAddress" type="xsd:string" minOccurs="0"/> + <xsd:element name="deviceFingerprintProxyIPAddress" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="FraudUpdateService"> + <xsd:sequence> + <xsd:element name="actionCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="markedData" type="xsd:string" minOccurs="0"/> + <xsd:element name="markingReason" type="xsd:string" minOccurs="0"/> + <xsd:element name="markingNotes" type="xsd:string" minOccurs="0"/> + <xsd:element name="markingRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="markingTransactionDate" type="xsd:string" minOccurs="0"/> + <xsd:element name="markingAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="markingCurrency" type="xsd:string" minOccurs="0"/> + <xsd:element name="markingIndicator" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="CaseManagementActionService"> + <xsd:sequence> + <xsd:element name="actionCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="requestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="comments" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="EncryptPaymentDataService"> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="InvoiceHeader"> + <xsd:sequence> + <xsd:element name="merchantDescriptor" type="xsd:string" minOccurs="0"/> + <xsd:element name="merchantDescriptorContact" type="xsd:string" minOccurs="0"/> + <xsd:element name="merchantDescriptorAlternate" type="xsd:string" minOccurs="0"/> + <xsd:element name="merchantDescriptorStreet" type="xsd:string" minOccurs="0"/> + <xsd:element name="merchantDescriptorCity" type="xsd:string" minOccurs="0"/> + <xsd:element name="merchantDescriptorState" type="xsd:string" minOccurs="0"/> + <xsd:element name="merchantDescriptorPostalCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="merchantDescriptorCountry" type="xsd:string" minOccurs="0"/> + <xsd:element name="isGift" type="tns:boolean" minOccurs="0"/> + <xsd:element name="returnsAccepted" type="tns:boolean" minOccurs="0"/> + <xsd:element name="tenderType" type="xsd:string" minOccurs="0"/> + <xsd:element name="merchantVATRegistrationNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="purchaserOrderDate" type="xsd:string" minOccurs="0"/> + <!-- xsd:date --> + <xsd:element name="purchaserVATRegistrationNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="vatInvoiceReferenceNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="summaryCommodityCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="supplierOrderReference" type="xsd:string" minOccurs="0"/> + <xsd:element name="userPO" type="xsd:string" minOccurs="0"/> + <xsd:element name="costCenter" type="xsd:string" minOccurs="0"/> + <xsd:element name="purchaserCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="taxable" type="tns:boolean" minOccurs="0"/> + <xsd:element name="amexDataTAA1" type="xsd:string" minOccurs="0"/> + <xsd:element name="amexDataTAA2" type="xsd:string" minOccurs="0"/> + <xsd:element name="amexDataTAA3" type="xsd:string" minOccurs="0"/> + <xsd:element name="amexDataTAA4" type="xsd:string" minOccurs="0"/> + <xsd:element name="invoiceDate" type="xsd:string" minOccurs="0"/> + <xsd:element name="totalTaxTypeCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardAcceptorRefNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="authorizedContactName" type="xsd:string" minOccurs="0"/> + <xsd:element name="businessApplicationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="salesOrganizationID" type="xsd:integer" minOccurs="0"/> + <xsd:element name="submerchantID" type="xsd:string" minOccurs="0"/> + <xsd:element name="submerchantName" type="xsd:string" minOccurs="0"/> + <xsd:element name="submerchantStreet" type="xsd:string" minOccurs="0"/> + <xsd:element name="submerchantCity" type="xsd:string" minOccurs="0"/> + <xsd:element name="submerchantPostalCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="submerchantState" type="xsd:string" minOccurs="0"/> + <xsd:element name="submerchantCountry" type="xsd:string" minOccurs="0"/> + <xsd:element name="submerchantEmail" type="xsd:string" minOccurs="0"/> + <xsd:element name="submerchantTelephoneNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="submerchantRegion" type="xsd:string" minOccurs="0"/> + <xsd:element name="submerchantMerchantID" type="xsd:string" minOccurs="0"/> + <xsd:element name="merchantDescriptorCounty" type="xsd:string" minOccurs="0"/> + <xsd:element name="referenceDataCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="referenceDataNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="merchantDescriptorStoreID" type="xsd:string" minOccurs="0"/> + <xsd:element name="clerkID" type="xsd:string" minOccurs="0"/> + <xsd:element name="customData_1" type="xsd:string" minOccurs="0" /> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="BusinessRules"> + <xsd:sequence> + <xsd:element name="ignoreAVSResult" type="tns:boolean" minOccurs="0"/> + <xsd:element name="ignoreCVResult" type="tns:boolean" minOccurs="0"/> + <xsd:element name="ignoreDAVResult" type="tns:boolean" minOccurs="0"/> + <xsd:element name="ignoreExportResult" type="tns:boolean" minOccurs="0"/> + <xsd:element name="ignoreValidateResult" type="tns:boolean" minOccurs="0"/> + <xsd:element name="declineAVSFlags" type="xsd:string" minOccurs="0"/> + <xsd:element name="scoreThreshold" type="xsd:integer" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="BillTo"> + <xsd:sequence> + <xsd:element name="title" type="xsd:string" minOccurs="0"/> + <xsd:element name="firstName" type="xsd:string" minOccurs="0"/> + <xsd:element name="middleName" type="xsd:string" minOccurs="0"/> + <xsd:element name="lastName" type="xsd:string" minOccurs="0"/> + <xsd:element name="suffix" type="xsd:string" minOccurs="0"/> + <xsd:element name="buildingNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="street1" type="xsd:string" minOccurs="0"/> + <xsd:element name="street2" type="xsd:string" minOccurs="0"/> + <xsd:element name="street3" type="xsd:string" minOccurs="0"/> + <xsd:element name="street4" type="xsd:string" minOccurs="0"/> + <xsd:element name="street5" type="xsd:string" minOccurs="0"/> + <xsd:element name="city" type="xsd:string" minOccurs="0"/> + <xsd:element name="district" type="xsd:string" minOccurs="0"/> + <xsd:element name="county" type="xsd:string" minOccurs="0"/> + <xsd:element name="state" type="xsd:string" minOccurs="0"/> + <xsd:element name="postalCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="country" type="xsd:string" minOccurs="0"/> + <xsd:element name="company" type="xsd:string" minOccurs="0"/> + <xsd:element name="companyTaxID" type="xsd:string" minOccurs="0"/> + <xsd:element name="phoneNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="email" type="xsd:string" minOccurs="0"/> + <xsd:element name="ipAddress" type="xsd:string" minOccurs="0"/> + <xsd:element name="customerUserName" type="xsd:string" minOccurs="0"/> + <xsd:element name="customerPassword" type="xsd:string" minOccurs="0"/> + <xsd:element name="ipNetworkAddress" type="xsd:string" minOccurs="0"/> + <xsd:element name="hostname" type="xsd:string" minOccurs="0"/> + <xsd:element name="domainName" type="xsd:string" minOccurs="0"/> + <xsd:element name="dateOfBirth" type="xsd:string" minOccurs="0"/> + <!-- xsd:date --> + <xsd:element name="driversLicenseNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="driversLicenseState" type="xsd:string" minOccurs="0"/> + <xsd:element name="ssn" type="xsd:string" minOccurs="0"/> + <xsd:element name="customerID" type="xsd:string" minOccurs="0"/> + <xsd:element name="httpBrowserType" type="xsd:string" minOccurs="0"/> + <xsd:element name="httpBrowserEmail" type="xsd:string" minOccurs="0"/> + <xsd:element name="httpBrowserCookiesAccepted" type="tns:boolean" minOccurs="0"/> + <xsd:element name="nif" type="xsd:string" minOccurs="0"/> + <xsd:element name="personalID" type="xsd:string" minOccurs="0"/> + <xsd:element name="language" type="xsd:string" minOccurs="0"/> + <xsd:element name="name" type="xsd:string" minOccurs="0"/> + <xsd:element name="gender" type="xsd:string" minOccurs="0"/> + <xsd:element name="merchantTaxID" type="xsd:string" minOccurs="0"/> + + <xsd:element name="passportNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="passportCountry" type="xsd:string" minOccurs="0"/> + <xsd:element name="customerAccountCreateDate" type="xsd:string" minOccurs="0"/> + <xsd:element name="customerAccountChangeDate" type="xsd:string" minOccurs="0"/> + <xsd:element name="customerAccountPasswordChangeDate" type="xsd:string" minOccurs="0"/> + <xsd:element name="pointOfReference" type="tns:boolean" minOccurs="0"/> + <xsd:element name="defaultIndicator" type="tns:boolean" minOccurs="0"/> + + <xsd:element name="companyStreet1" type="xsd:string" minOccurs="0"/> + <xsd:element name="companyStreet2" type="xsd:string" minOccurs="0"/> + <xsd:element name="companyCity" type="xsd:string" minOccurs="0"/> + <xsd:element name="companyCountry" type="xsd:string" minOccurs="0"/> + <xsd:element name="companyState" type="xsd:string" minOccurs="0"/> + <xsd:element name="companyPostalCode" type="xsd:string" minOccurs="0"/> + + + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="ShipTo"> + <xsd:sequence> + <xsd:element name="title" type="xsd:string" minOccurs="0"/> + <xsd:element name="firstName" type="xsd:string" minOccurs="0"/> + <xsd:element name="middleName" type="xsd:string" minOccurs="0"/> + <xsd:element name="lastName" type="xsd:string" minOccurs="0"/> + <xsd:element name="suffix" type="xsd:string" minOccurs="0"/> + <xsd:element name="street1" type="xsd:string" minOccurs="0"/> + <xsd:element name="street2" type="xsd:string" minOccurs="0"/> + <xsd:element name="street3" type="xsd:string" minOccurs="0"/> + <xsd:element name="street4" type="xsd:string" minOccurs="0"/> + <xsd:element name="street5" type="xsd:string" minOccurs="0"/> + <xsd:element name="city" type="xsd:string" minOccurs="0"/> + <xsd:element name="county" type="xsd:string" minOccurs="0"/> + <xsd:element name="state" type="xsd:string" minOccurs="0"/> + <xsd:element name="buildingNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="district" type="xsd:string" minOccurs="0"/> + <xsd:element name="postalCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="country" type="xsd:string" minOccurs="0"/> + <xsd:element name="company" type="xsd:string" minOccurs="0"/> + <xsd:element name="phoneNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="email" type="xsd:string" minOccurs="0"/> + <xsd:element name="shippingMethod" type="xsd:string" minOccurs="0"/> + <xsd:element name="name" type="xsd:string" minOccurs="0"/> + <xsd:element name="id" type="xsd:string" minOccurs="0"/> + <xsd:element name="addressVerificationStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="notApplicable" type="tns:boolean" minOccurs="0"/> + <xsd:element name="immutable" type="tns:boolean" minOccurs="0"/> + <xsd:element name="destinationCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="pointOfReference" type="tns:boolean" minOccurs="0"/> + <xsd:element name="default" type="tns:boolean" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="ShipFrom"> + <xsd:sequence> + <xsd:element name="title" type="xsd:string" minOccurs="0"/> + <xsd:element name="firstName" type="xsd:string" minOccurs="0"/> + <xsd:element name="middleName" type="xsd:string" minOccurs="0"/> + <xsd:element name="lastName" type="xsd:string" minOccurs="0"/> + <xsd:element name="suffix" type="xsd:string" minOccurs="0"/> + <xsd:element name="street1" type="xsd:string" minOccurs="0"/> + <xsd:element name="street2" type="xsd:string" minOccurs="0"/> + <xsd:element name="street3" type="xsd:string" minOccurs="0"/> + <xsd:element name="street4" type="xsd:string" minOccurs="0"/> + <xsd:element name="city" type="xsd:string" minOccurs="0"/> + <xsd:element name="county" type="xsd:string" minOccurs="0"/> + <xsd:element name="state" type="xsd:string" minOccurs="0"/> + <xsd:element name="postalCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="country" type="xsd:string" minOccurs="0"/> + <xsd:element name="company" type="xsd:string" minOccurs="0"/> + <xsd:element name="phoneNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="email" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="Card"> + <xsd:sequence> + <xsd:element name="fullName" type="xsd:string" minOccurs="0"/> + <xsd:element name="accountNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="expirationMonth" type="xsd:integer" minOccurs="0"/> + <xsd:element name="expirationYear" type="xsd:integer" minOccurs="0"/> + <xsd:element name="cvIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="cvNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardType" type="xsd:string" minOccurs="0"/> + <xsd:element name="issueNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="startMonth" type="xsd:integer" minOccurs="0"/> + <xsd:element name="startYear" type="xsd:integer" minOccurs="0"/> + <xsd:element name="pin" type="xsd:string" minOccurs="0"/> + <xsd:element name="accountEncoderID" type="xsd:string" minOccurs="0"/> + <xsd:element name="bin" type="xsd:string" minOccurs="0"/> + <xsd:element name="encryptedData" type="xsd:string" minOccurs="0"/> + <xsd:element name="suffix" type="xsd:string" minOccurs="0"/> + <xsd:element name="virtual" type="tns:boolean" minOccurs="0"/> + <xsd:element name="prefix" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardTypeName" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardSubType" type="xsd:string" minOccurs="0"/> + <xsd:element name="level2Eligible" type="xsd:string" minOccurs="0"/> + <xsd:element name="level3Eligible" type="xsd:string" minOccurs="0"/> + <xsd:element name="productCategory" type="xsd:string" minOccurs="0"/> + <xsd:element name="crossBorderIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="billingCurrency" type="xsd:string" minOccurs="0"/> + <xsd:element name="billingCurrencyNumericCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="billingCurrencyMinorDigits" type="xsd:string" minOccurs="0"/> + <xsd:element name="octFastFundsIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="octBlockIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="onlineGamblingBlockIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="productName" type="xsd:string" minOccurs="0"/> + <xsd:element name="usage" type="xsd:string" minOccurs="0"/> + <xsd:element name="prepaidReloadable" type="xsd:string" minOccurs="0"/> + <xsd:element name="prepaidType" type="xsd:string" minOccurs="0"/> + <xsd:element name="brands" type="tns:Brands" minOccurs="0" maxOccurs="5"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="Check"> + <xsd:sequence> + <xsd:element name="fullName" type="xsd:string" minOccurs="0"/> + <xsd:element name="accountNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="accountType" type="xsd:string" minOccurs="0"/> + <xsd:element name="bankTransitNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="checkNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="secCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="accountEncoderID" type="xsd:string" minOccurs="0"/> + <xsd:element name="authenticateID" type="xsd:string" minOccurs="0"/> + <xsd:element name="paymentInfo" type="xsd:string" minOccurs="0"/> + <xsd:element name="imageReferenceNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="terminalCity" type="xsd:string" minOccurs="0"/> + <xsd:element name="terminalState" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="BML"> + <xsd:sequence> + <xsd:element name="customerBillingAddressChange" type="tns:boolean" minOccurs="0"/> + <xsd:element name="customerEmailChange" type="tns:boolean" minOccurs="0"/> + <xsd:element name="customerHasCheckingAccount" type="tns:boolean" minOccurs="0"/> + <xsd:element name="customerHasSavingsAccount" type="tns:boolean" minOccurs="0"/> + <xsd:element name="customerPasswordChange" type="tns:boolean" minOccurs="0"/> + <xsd:element name="customerPhoneChange" type="tns:boolean" minOccurs="0"/> + <xsd:element name="customerRegistrationDate" type="xsd:string" minOccurs="0"/> + <!-- xsd:date --> + <xsd:element name="customerTypeFlag" type="xsd:string" minOccurs="0"/> + <xsd:element name="grossHouseholdIncome" type="tns:amount" minOccurs="0"/> + <xsd:element name="householdIncomeCurrency" type="xsd:string" minOccurs="0"/> + <xsd:element name="itemCategory" type="xsd:string" minOccurs="0"/> + <xsd:element name="merchantPromotionCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="preapprovalNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="productDeliveryTypeIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="residenceStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="tcVersion" type="xsd:string" minOccurs="0"/> + <xsd:element name="yearsAtCurrentResidence" type="xsd:integer" minOccurs="0"/> + <xsd:element name="yearsWithCurrentEmployer" type="xsd:integer" minOccurs="0"/> + <xsd:element name="employerStreet1" type="xsd:string" minOccurs="0"/> + <xsd:element name="employerStreet2" type="xsd:string" minOccurs="0"/> + <xsd:element name="employerCity" type="xsd:string" minOccurs="0"/> + <xsd:element name="employerCompanyName" type="xsd:string" minOccurs="0"/> + <xsd:element name="employerCountry" type="xsd:string" minOccurs="0"/> + <xsd:element name="employerPhoneNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="employerPhoneType" type="xsd:string" minOccurs="0"/> + <xsd:element name="employerState" type="xsd:string" minOccurs="0"/> + <xsd:element name="employerPostalCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="shipToPhoneType" type="xsd:string" minOccurs="0"/> + <xsd:element name="billToPhoneType" type="xsd:string" minOccurs="0"/> + <xsd:element name="methodOfPayment" type="xsd:string" minOccurs="0"/> + <xsd:element name="productType" type="xsd:string" minOccurs="0"/> + <xsd:element name="customerAuthenticatedByMerchant" type="xsd:string" minOccurs="0"/> + <xsd:element name="backOfficeIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="shipToEqualsBillToNameIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="shipToEqualsBillToAddressIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="alternateIPAddress" type="xsd:string" minOccurs="0"/> + <xsd:element name="businessLegalName" type="xsd:string" minOccurs="0"/> + <xsd:element name="dbaName" type="xsd:string" minOccurs="0"/> + <xsd:element name="businessAddress1" type="xsd:string" minOccurs="0"/> + <xsd:element name="businessAddress2" type="xsd:string" minOccurs="0"/> + <xsd:element name="businessCity" type="xsd:string" minOccurs="0"/> + <xsd:element name="businessState" type="xsd:string" minOccurs="0"/> + <xsd:element name="businessPostalCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="businessCountry" type="xsd:string" minOccurs="0"/> + <xsd:element name="businessMainPhone" type="xsd:string" minOccurs="0"/> + <xsd:element name="userID" type="xsd:string" minOccurs="0"/> + <xsd:element name="pin" type="xsd:string" minOccurs="0"/> + <xsd:element name="adminLastName" type="xsd:string" minOccurs="0"/> + <xsd:element name="adminFirstName" type="xsd:string" minOccurs="0"/> + <xsd:element name="adminPhone" type="xsd:string" minOccurs="0"/> + <xsd:element name="adminFax" type="xsd:string" minOccurs="0"/> + <xsd:element name="adminEmailAddress" type="xsd:string" minOccurs="0"/> + <xsd:element name="adminTitle" type="xsd:string" minOccurs="0"/> + <xsd:element name="supervisorLastName" type="xsd:string" minOccurs="0"/> + <xsd:element name="supervisorFirstName" type="xsd:string" minOccurs="0"/> + <xsd:element name="supervisorEmailAddress" type="xsd:string" minOccurs="0"/> + <xsd:element name="businessDAndBNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="businessTaxID" type="xsd:string" minOccurs="0"/> + <xsd:element name="businessNAICSCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="businessType" type="xsd:string" minOccurs="0"/> + <xsd:element name="businessYearsInBusiness" type="xsd:string" minOccurs="0"/> + <xsd:element name="businessNumberOfEmployees" type="xsd:string" minOccurs="0"/> + <xsd:element name="businessPONumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="businessLoanType" type="xsd:string" minOccurs="0"/> + <xsd:element name="businessApplicationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="businessProductCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="pgLastName" type="xsd:string" minOccurs="0"/> + <xsd:element name="pgFirstName" type="xsd:string" minOccurs="0"/> + <xsd:element name="pgSSN" type="xsd:string" minOccurs="0"/> + <xsd:element name="pgDateOfBirth" type="xsd:string" minOccurs="0"/> + <xsd:element name="pgAnnualIncome" type="xsd:string" minOccurs="0"/> + <xsd:element name="pgIncomeCurrencyType" type="xsd:string" minOccurs="0"/> + <xsd:element name="pgResidenceStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="pgCheckingAccountIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="pgSavingsAccountIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="pgYearsAtEmployer" type="xsd:string" minOccurs="0"/> + <xsd:element name="pgYearsAtResidence" type="xsd:string" minOccurs="0"/> + <xsd:element name="pgHomeAddress1" type="xsd:string" minOccurs="0"/> + <xsd:element name="pgHomeAddress2" type="xsd:string" minOccurs="0"/> + <xsd:element name="pgHomeCity" type="xsd:string" minOccurs="0"/> + <xsd:element name="pgHomeState" type="xsd:string" minOccurs="0"/> + <xsd:element name="pgHomePostalCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="pgHomeCountry" type="xsd:string" minOccurs="0"/> + <xsd:element name="pgEmailAddress" type="xsd:string" minOccurs="0"/> + <xsd:element name="pgHomePhone" type="xsd:string" minOccurs="0"/> + <xsd:element name="pgTitle" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="OtherTax"> + <xsd:sequence> + <xsd:element name="vatTaxAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="vatTaxRate" type="tns:amount" minOccurs="0"/> + <xsd:element name="vatTaxAmountSign" type="xsd:string" minOccurs="0"/> + <xsd:element name="alternateTaxAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="alternateTaxIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="alternateTaxID" type="xsd:string" minOccurs="0"/> + <xsd:element name="localTaxAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="localTaxIndicator" type="xsd:integer" minOccurs="0"/> + <xsd:element name="nationalTaxAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="nationalTaxIndicator" type="xsd:integer" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="Aft"> + <xsd:sequence> + <xsd:element name="indicator" type="xsd:string" minOccurs="0" /> + <xsd:element name="serviceFee" type="xsd:string" minOccurs="0" /> + <xsd:element name="foreignExchangeFee" type="xsd:string" minOccurs="0" /> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="Wallet"> + <xsd:sequence> + <xsd:element name="type" type="xsd:string" minOccurs="0"/> + <xsd:element name="orderID" type="xsd:string" minOccurs="0"/> + <xsd:element name="merchantReferenceID" type="xsd:string" minOccurs="0"/> + <xsd:element name="userPhone" type="xsd:string" minOccurs="0"/> + <xsd:element name="avv" type="xsd:string" minOccurs="0"/> + <xsd:element name="eciRaw" type="xsd:string" minOccurs="0"/> + <xsd:element name="authenticatonMethod" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardEnrollmentMethod" type="xsd:string" minOccurs="0"/> + <xsd:element name="paresStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="veresEnrolled" type="xsd:string" minOccurs="0"/> + <xsd:element name="xid" type="xsd:string" minOccurs="0"/> + <xsd:element name="totalPurchaseAmount" type="xsd:string" minOccurs="0" /> + <xsd:element name="subtotalAmount" type="xsd:string" minOccurs="0" /> + <xsd:element name="discountAmount" type="xsd:string" minOccurs="0" /> + <xsd:element name="giftWrapAmount" type="xsd:string" minOccurs="0" /> + <xsd:element name="eventType" type="xsd:string" minOccurs="0" /> + <xsd:element name="promotionCode" type="xsd:string" minOccurs="0" /> + </xsd:sequence> + </xsd:complexType> + + + <xsd:complexType name="PurchaseTotals"> + <xsd:sequence> + <xsd:element name="currency" type="xsd:string" minOccurs="0"/> + <xsd:element name="discountAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="discountAmountSign" type="xsd:string" minOccurs="0"/> + <xsd:element name="discountManagementIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="taxAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="dutyAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="dutyAmountSign" type="xsd:string" minOccurs="0"/> + <xsd:element name="grandTotalAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="freightAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="freightAmountSign" type="xsd:string" minOccurs="0"/> + <xsd:element name="foreignAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="foreignCurrency" type="xsd:string" minOccurs="0"/> + <xsd:element name="originalAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="originalCurrency" type="xsd:string" minOccurs="0"/> + <xsd:element name="exchangeRate" type="tns:amount" minOccurs="0"/> + <xsd:element name="exchangeRateTimeStamp" type="xsd:string" minOccurs="0"/> + <xsd:element name="additionalAmountType0" type="xsd:string" minOccurs="0"/> + <xsd:element name="additionalAmount0" type="xsd:string" minOccurs="0"/> + <xsd:element name="additionalAmountType1" type="xsd:string" minOccurs="0"/> + <xsd:element name="additionalAmount1" type="xsd:string" minOccurs="0"/> + <xsd:element name="additionalAmountType2" type="xsd:string" minOccurs="0"/> + <xsd:element name="additionalAmount2" type="xsd:string" minOccurs="0"/> + <xsd:element name="additionalAmountType3" type="xsd:string" minOccurs="0"/> + <xsd:element name="additionalAmount3" type="xsd:string" minOccurs="0"/> + <xsd:element name="additionalAmountType4" type="xsd:string" minOccurs="0"/> + <xsd:element name="additionalAmount4" type="xsd:string" minOccurs="0"/> + <xsd:element name="serviceFeeAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="subtotalAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="shippingAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="handlingAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="shippingHandlingAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="shippingDiscountAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="giftWrapAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="insuranceAmount" type="tns:amount" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="FundingTotals"> + <xsd:sequence> + <xsd:element name="currency" type="xsd:string" minOccurs="0"/> + <xsd:element name="grandTotalAmount" type="tns:amount" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="GECC"> + <xsd:sequence> + <xsd:element name="saleType" type="xsd:string" minOccurs="0"/> + <xsd:element name="planNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="sequenceNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="promotionEndDate" type="xsd:string" minOccurs="0"/> + <xsd:element name="promotionPlan" type="xsd:string" minOccurs="0"/> + <xsd:element name="line" type="xsd:string" minOccurs="0" maxOccurs="7"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="UCAF"> + <xsd:sequence> + <xsd:element name="authenticationData" type="xsd:string" minOccurs="0"/> + <xsd:element name="collectionIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="downgradeReasonCode" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="Network"> + <xsd:all> + <xsd:element name="octDomesticIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="octCrossBorderIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="aftDomesticIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="aftCrossBorderIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="networkID" type="xsd:string" minOccurs="0"/> + <xsd:element name="networkOrder" type="xsd:string" minOccurs="0"/> + </xsd:all> + <xsd:attribute name="id" type="xsd:integer" use="optional"/> + </xsd:complexType> + + <xsd:complexType name="Brands"> + <xsd:all> + <xsd:element name="name" type="xsd:string" minOccurs="0"/> + <xsd:element name="type" type="xsd:string" minOccurs="0"/> + </xsd:all> + <xsd:attribute name="id" type="xsd:integer" use="optional"/> + </xsd:complexType> + + <xsd:complexType name="FundTransfer"> + <xsd:sequence> + <xsd:element name="accountNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="accountName" type="xsd:string" minOccurs="0"/> + <xsd:element name="bankCheckDigit" type="xsd:string" minOccurs="0"/> + <xsd:element name="iban" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="BankInfo"> + <xsd:sequence> + <xsd:element name="bankCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="name" type="xsd:string" minOccurs="0"/> + <xsd:element name="address" type="xsd:string" minOccurs="0"/> + <xsd:element name="city" type="xsd:string" minOccurs="0"/> + <xsd:element name="country" type="xsd:string" minOccurs="0"/> + <xsd:element name="branchCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="swiftCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="sortCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="issuerID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="RecurringSubscriptionInfo"> + <xsd:sequence> + <xsd:element name="subscriptionID" type="xsd:string" minOccurs="0"/> + <xsd:element name="status" type="xsd:string" minOccurs="0"/> + <xsd:element name="amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="numberOfPayments" type="xsd:integer" minOccurs="0"/> + <xsd:element name="numberOfPaymentsToAdd" type="xsd:integer" minOccurs="0"/> + <xsd:element name="automaticRenew" type="tns:boolean" minOccurs="0"/> + <xsd:element name="frequency" type="xsd:string" minOccurs="0"/> + <xsd:element name="startDate" type="xsd:string" minOccurs="0"/> + <xsd:element name="endDate" type="xsd:string" minOccurs="0"/> + <xsd:element name="approvalRequired" type="tns:boolean" minOccurs="0"/> + <xsd:element name="event" type="tns:PaySubscriptionEvent" minOccurs="0"/> + <xsd:element name="billPayment" type="tns:boolean" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="PaySubscriptionEvent"> + <xsd:sequence> + <xsd:element name="amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="approvedBy" type="xsd:string" minOccurs="0"/> + <xsd:element name="number" type="xsd:integer" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="Subscription"> + <xsd:sequence> + <xsd:element name="title" type="xsd:string" minOccurs="0"/> + <xsd:element name="paymentMethod" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="PaymentNetworkToken"> + <xsd:sequence> + <xsd:element name="requestorID" type="xsd:string" minOccurs="0"/> + <xsd:element name="transactionType" type="xsd:string" minOccurs="0"/> + <xsd:element name="assuranceLevel" type="xsd:string" minOccurs="0"/> + <xsd:element name="accountStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="originalCardCategory" type="xsd:string" minOccurs="0"/> + <xsd:element name="deviceTechType" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="DecisionManager"> + <xsd:sequence> + <xsd:element name="enabled" type="tns:boolean" minOccurs="0"/> + <xsd:element name="profile" type="xsd:string" minOccurs="0"/> + <xsd:element name="travelData" type="tns:DecisionManagerTravelData" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="DecisionManagerTravelData"> + <xsd:sequence> + <xsd:element name="leg" type="tns:DecisionManagerTravelLeg" minOccurs="0" maxOccurs="100"/> + <xsd:element name="departureDateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="completeRoute" type="xsd:string" minOccurs="0"/> + <xsd:element name="journeyType" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="DecisionManagerTravelLeg"> + <xsd:sequence> + <xsd:element name="origin" type="xsd:string" minOccurs="0"/> + <xsd:element name="destination" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="id" type="xsd:integer" use="optional"/> + </xsd:complexType> + <xsd:complexType name="Batch"> + <xsd:sequence> + <xsd:element name="batchID" type="xsd:string" minOccurs="0"/> + <xsd:element name="recordID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="PayPal"> + <xsd:sequence> + <xsd:any processContents="skip" minOccurs="0" maxOccurs="999"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="JPO"> + <xsd:sequence> + <xsd:element name="paymentMethod" type="xsd:integer" minOccurs="0"/> + <xsd:element name="bonusAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="bonuses" type="xsd:integer" minOccurs="0"/> + <xsd:element name="installments" type="xsd:integer" minOccurs="0"/> + <xsd:element name="firstBillingMonth" type="xsd:integer" minOccurs="0"/> + <xsd:element name="jccaTerminalID" type="xsd:integer" minOccurs="0"/> + <xsd:element name="issuerMessage" type="xsd:integer" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="Token"> + <xsd:sequence> + <xsd:element name="prefix" type="xsd:string" minOccurs="0"/> + <xsd:element name="suffix" type="xsd:string" minOccurs="0"/> + <xsd:element name="expirationMonth" type="xsd:string" minOccurs="0"/> + <xsd:element name="expirationYear" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + + <!-- Vme Reseller Service--> + <xsd:complexType name="AP"> + <xsd:sequence> + <xsd:element name="orderID" type="xsd:string" minOccurs="0"/> + <xsd:element name="pspBarcodeID" type="xsd:string" minOccurs="0"/> + <xsd:element name="customerRepresentativeID" type="xsd:string" minOccurs="0" /> + <xsd:element name="productDescription" type="xsd:string" minOccurs="0" /> + <xsd:element name="settlementCurrency" type="xsd:string" minOccurs="0" /> + <xsd:element name="subtotalAmount" type="xsd:string" minOccurs="0" /> + <xsd:element name="shippingAmount" type="xsd:string" minOccurs="0" /> + <xsd:element name="handlingAmount" type="xsd:string" minOccurs="0" /> + <xsd:element name="shippingHandlingAmount" type="xsd:string" minOccurs="0" /> + <xsd:element name="additionalAmount" type="xsd:string" minOccurs="0" /> + <xsd:element name="taxAmount" type="xsd:string" minOccurs="0" /> + <xsd:element name="giftWrapAmount" type="xsd:string" minOccurs="0" /> + <xsd:element name="discountAmount" type="xsd:string" minOccurs="0" /> + <xsd:element name="purchaseID" type="xsd:string" minOccurs="0" /> + <xsd:element name="productID" type="xsd:string" minOccurs="0" /> + <xsd:element name="device" type="tns:APDevice" minOccurs="0" /> + <xsd:element name="apiKey" type="xsd:string" minOccurs="0" /> + <xsd:element name="insuranceAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="billingAgreementIndicator" type="tns:boolean" minOccurs="0"/> + <xsd:element name="billingAgreementID" type="xsd:string" minOccurs="0" /> + <xsd:element name="billingAgreementDescription" type="xsd:string" minOccurs="0" /> + <xsd:element name="payerID" type="xsd:string" minOccurs="0" /> + <xsd:element name="fundingSource" type="xsd:string" minOccurs="0"/> + <xsd:element name="shippingAddressImmutable" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="APDevice"> + <xsd:sequence> + <xsd:element name="id" type="xsd:string" minOccurs="0" /> + <xsd:element name="type" type="xsd:string" minOccurs="0" /> + <xsd:element name="userAgent" type="xsd:string" minOccurs="0" /> + </xsd:sequence> + </xsd:complexType> + + <!-- apAuthService --> + <xsd:complexType name="APAuthService"> + <xsd:sequence> + <xsd:element name="cancelURL" type="xsd:string" minOccurs="0"/> + <xsd:element name="successURL" type="xsd:string" minOccurs="0"/> + <xsd:element name="failureURL" type="xsd:string" minOccurs="0"/> + <xsd:element name="overridePaymentMethod" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="preapprovalToken" type="xsd:string" minOccurs="0"/> + <xsd:element name="orderRequestID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <!-- End of apAuthService --> + + <!-- Start of AP Import Mandate Service --> + + + <xsd:complexType name="APImportMandateService"> + <xsd:sequence> + <xsd:element name="dateSigned" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + + <!-- End of of AP Import Mandate Service --> + + <!-- apAuthReversalService --> + <xsd:complexType name="APAuthReversalService"> + <xsd:sequence> + <xsd:element name="authRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <!-- End of apAuthReversalService --> + <!-- apCaptureService --> + <xsd:complexType name="APCaptureService"> + <xsd:sequence> + <xsd:element name="authRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="isFinal" type="tns:boolean" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <!-- End of apCaptureService --> + <!-- apOptionsService --> + <xsd:complexType name="APOptionsService"> + <xsd:sequence> + <xsd:element name="limit" type="xsd:string" minOccurs="0"/> + <xsd:element name="offset" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <!-- End of apOptionsService --> + <!-- apRefundService --> + <xsd:complexType name="APRefundService"> + <xsd:sequence> + <xsd:element name="captureRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="refundRequestID" type="xsd:string" minOccurs="0" /> + <xsd:element name="reason" type="xsd:string" minOccurs="0"/> + <xsd:element name="instant" type="xsd:string" minOccurs="0"/> + <xsd:element name="note" type="xsd:string" minOccurs="0"/> + <xsd:element name="apInitiateRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="returnRef" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="saleRequestID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <!-- End of apRefundService --> + <!-- apSaleService --> + <xsd:complexType name="APSaleService"> + <xsd:sequence> + <xsd:element name="cancelURL" type="xsd:string" minOccurs="0"/> + <xsd:element name="successURL" type="xsd:string" minOccurs="0"/> + <xsd:element name="failureURL" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="overridePaymentMethod" type="xsd:string" minOccurs="0"/> + <xsd:element name="paymentOptionID" type="xsd:string" minOccurs="0" /> + <xsd:element name="transactionTimeout" type="xsd:string" minOccurs="0" /> + <xsd:element name="orderRequestID" type="xsd:string" minOccurs="0" /> + <xsd:element name="billingAgreementID" type="xsd:string" minOccurs="0" /> + <xsd:element name="mandateID" type="xsd:string" minOccurs="0" /> + <xsd:element name="dateCollect" type="xsd:string" minOccurs="0" /> + <xsd:element name="preapprovalToken" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <!-- End of apAuthService --> + + <xsd:complexType name="APCheckOutDetailsService"> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <!-- End of apCheckoutDetailsService --> + <xsd:complexType name="APTransactionDetailsService"> + <xsd:sequence> + <xsd:element name="transactionDetailsRequestID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <!-- APConfirmPurchaseService --> + <xsd:complexType name="APConfirmPurchaseService"> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <!-- End of APConfirmPurchaseService --> + <xsd:complexType name="APSessionsService"> + <xsd:sequence> + <xsd:element name="cancelURL" type="xsd:string" minOccurs="0"/> + <xsd:element name="successURL" type="xsd:string" minOccurs="0"/> + <xsd:element name="failureURL" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="overridePaymentMethod" type="xsd:string" minOccurs="0"/> + <xsd:element name="paymentOptionID" type="xsd:string" minOccurs="0"/> + <xsd:element name="sessionsType" type="xsd:string" minOccurs="0"/> + <xsd:element name="sessionsRequestID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <!-- APUIStyle --> + <xsd:complexType name="APUI"> + <xsd:sequence> + <xsd:element name="colorBorder" type="xsd:string" minOccurs="0"/> + <xsd:element name="colorBorderSelected" type="xsd:string" minOccurs="0"/> + <xsd:element name="colorButton" type="xsd:string" minOccurs="0"/> + <xsd:element name="colorButtonText" type="xsd:string" minOccurs="0"/> + <xsd:element name="colorCheckbox" type="xsd:string" minOccurs="0"/> + <xsd:element name="colorCheckboxCheckMark" type="xsd:string" minOccurs="0"/> + <xsd:element name="colorHeader" type="xsd:string" minOccurs="0"/> + <xsd:element name="colorLink" type="xsd:string" minOccurs="0"/> + <xsd:element name="colorText" type="xsd:string" minOccurs="0"/> + <xsd:element name="borderRadius" type="xsd:string" minOccurs="0"/> + <xsd:element name="theme" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <!-- End of APUIStyle --> + <!--PayPalGetTxnDetails--> + <xsd:complexType name="PayPalGetTxnDetailsService"> + <xsd:sequence> + <xsd:element name="transactionID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <!-- End of PayPalGetTxnDetails --> + <!--PayPalTransactionSearch--> + <xsd:complexType name="PayPalTransactionSearchService"> + <xsd:sequence> + <xsd:element name="startDate" type="xsd:string" minOccurs="0"/> + <xsd:element name="endDate" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalCustomerEmail" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalReceiptId" type="xsd:string" minOccurs="0"/> + <xsd:element name="transactionID" type="xsd:string" minOccurs="0"/> + <xsd:element name="invoiceNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="grandTotalAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="currency" type="xsd:string" minOccurs="0"/> + <xsd:element name="paymentStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerSalutation" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerFirstname" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerMiddlename" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerLastname" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerSuffix" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <!-- End of PayPalTransactionSearch --> + <!-- Credit card recipient data --> + <xsd:complexType name="Recipient"> + <xsd:sequence> + <xsd:element name="dateOfBirth" type="xsd:string" minOccurs="0"/> + <xsd:element name="postalCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="accountID" type="xsd:string" minOccurs="0"/> + <xsd:element name="lastName" type="xsd:string" minOccurs="0"/> + <xsd:element name="name" type="xsd:string" minOccurs="0"/> + <xsd:element name="billingAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="billingCurrency" type="xsd:string" minOccurs="0"/> + <xsd:element name="billingConversionRate" type="tns:amount" minOccurs="0"/> + + <xsd:element name="firstName" type="xsd:string" minOccurs="0"/> + <xsd:element name="middleInitial" type="xsd:string" minOccurs="0"/> + <xsd:element name="address" type="xsd:string" minOccurs="0"/> + <xsd:element name="city" type="xsd:string" minOccurs="0"/> + <xsd:element name="state" type="xsd:string" minOccurs="0"/> + <xsd:element name="country" type="xsd:string" minOccurs="0"/> + <xsd:element name="phoneNumber" type="xsd:string" minOccurs="0"/> + + + </xsd:sequence> + </xsd:complexType> + <!-- End of Credit card recipient data --> + <xsd:complexType name="Sender"> + <xsd:sequence> + <xsd:element name="referenceNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="sourceOfFunds" type="xsd:string" minOccurs="0"/> + <xsd:element name="name" type="xsd:string" minOccurs="0"/> + <xsd:element name="address" type="xsd:string" minOccurs="0"/> + <xsd:element name="city" type="xsd:string" minOccurs="0"/> + <xsd:element name="state" type="xsd:string" minOccurs="0"/> + <xsd:element name="postalCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="country" type="xsd:string" minOccurs="0"/> + <xsd:element name="accountNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="dateOfBirth" type="xsd:string" minOccurs="0"/> + + <xsd:element name="firstName" type="xsd:string" minOccurs="0"/> + <xsd:element name="middleInitial" type="xsd:string" minOccurs="0"/> + <xsd:element name="lastName" type="xsd:string" minOccurs="0"/> + <xsd:element name="phoneNumber" type="xsd:string" minOccurs="0"/> + + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="CCCheckStatusService"> + <xsd:sequence> + <xsd:element name="authRequestID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="RequestMessage"> + <xsd:sequence> + <xsd:element name="merchantID" type="xsd:string" + minOccurs="0" /> + <xsd:element name="merchantReferenceCode" type="xsd:string" + minOccurs="0" /> + <xsd:element name="debtIndicator" type="tns:boolean" + minOccurs="0" /> + <xsd:element name="clientLibrary" type="xsd:string" + minOccurs="0" /> + <xsd:element name="clientLibraryVersion" type="xsd:string" + minOccurs="0" /> + <xsd:element name="clientEnvironment" type="xsd:string" + minOccurs="0" /> + <xsd:element name="clientSecurityLibraryVersion" + type="xsd:string" minOccurs="0" /> + <xsd:element name="clientApplication" type="xsd:string" + minOccurs="0" /> + <xsd:element name="clientApplicationVersion" + type="xsd:string" minOccurs="0" /> + <xsd:element name="clientApplicationUser" type="xsd:string" + minOccurs="0" /> + <xsd:element name="routingCode" type="xsd:string" + minOccurs="0" /> + <xsd:element name="comments" type="xsd:string" + minOccurs="0" /> + <xsd:element name="returnURL" type="xsd:string" + minOccurs="0" /> + <xsd:element name="invoiceHeader" type="tns:InvoiceHeader" + minOccurs="0" /> + <xsd:element name="paymentScheme" type="xsd:string" minOccurs="0"/> + <xsd:element name="mandateID" type="xsd:string" minOccurs="0"/> + <xsd:element name="aggregatorMerchantIdentifier" type="xsd:string" minOccurs="0"/> + <xsd:element name="customerID" type="xsd:string" minOccurs="0"/> + <xsd:element name="customerFirstName" type="xsd:string" minOccurs="0"/> + <xsd:element name="customerLastName" type="xsd:string" minOccurs="0"/> + <xsd:element name="billTo" type="tns:BillTo" minOccurs="0" /> + <xsd:element name="shipTo" type="tns:ShipTo" minOccurs="0" /> + <xsd:element name="personalID" type="tns:PersonalID" minOccurs="0" /> + <xsd:element name="shipFrom" type="tns:ShipFrom" + minOccurs="0" /> + <xsd:element name="item" type="tns:Item" minOccurs="0" + maxOccurs="1000" /> + <xsd:element name="purchaseTotals" type="tns:PurchaseTotals" + minOccurs="0" /> + <xsd:element name="fundingTotals" type="tns:FundingTotals" + minOccurs="0" /> + <xsd:element name="dcc" type="tns:DCC" minOccurs="0" /> + <xsd:element name="pos" type="tns:Pos" minOccurs="0" /> + <xsd:element name="pin" type="tns:Pin" minOccurs="0" /> + <xsd:element name="encryptedPayment" type="tns:EncryptedPayment" minOccurs="0" /> + <xsd:element name="installment" type="tns:Installment" + minOccurs="0" /> + <xsd:element name="card" type="tns:Card" minOccurs="0" /> + <xsd:element name="category" type="tns:Category" minOccurs="0" /> + <xsd:element name="check" type="tns:Check" minOccurs="0" /> + <xsd:element name="bml" type="tns:BML" minOccurs="0" /> + <xsd:element name="gecc" type="tns:GECC" minOccurs="0" /> + <xsd:element name="ucaf" type="tns:UCAF" minOccurs="0" /> + <xsd:element name="fundTransfer" type="tns:FundTransfer" + minOccurs="0" /> + <xsd:element name="bankInfo" type="tns:BankInfo" + minOccurs="0" /> + <xsd:element name="subscription" type="tns:Subscription" + minOccurs="0" /> + <xsd:element name="recurringSubscriptionInfo" + type="tns:RecurringSubscriptionInfo" minOccurs="0" /> + <xsd:element name="decisionManager" + type="tns:DecisionManager" minOccurs="0" /> + <xsd:element name="otherTax" type="tns:OtherTax" + minOccurs="0" /> + <xsd:element name="paypal" type="tns:PayPal" minOccurs="0" /> + <xsd:element name="merchantDefinedData" + type="tns:MerchantDefinedData" minOccurs="0" /> + <xsd:element name="merchantSecureData" + type="tns:MerchantSecureData" minOccurs="0" /> + <xsd:element name="jpo" type="tns:JPO" minOccurs="0" /> + <xsd:element name="orderRequestToken" type="xsd:string" + minOccurs="0" /> + <xsd:element name="linkToRequest" type="xsd:string" + minOccurs="0" /> + <xsd:element name="serviceFee" type="tns:ServiceFee" minOccurs="0" /> + <xsd:element name="giftCard" type="tns:GiftCard" minOccurs="0" /> + <xsd:element name="ccAuthService" type="tns:CCAuthService" + minOccurs="0" /> + <xsd:element name="octService" type="tns:OCTService" + minOccurs="0" /> + + <xsd:element name="giftCardActivationService" type="tns:GiftCardActivationService" + minOccurs="0" /> + <xsd:element name="giftCardBalanceInquiryService" type="tns:GiftCardBalanceInquiryService" + minOccurs="0" /> + <xsd:element name="giftCardRedemptionService" type="tns:GiftCardRedemptionService" + minOccurs="0" /> + <xsd:element name="giftCardVoidService" type="tns:GiftCardVoidService" + minOccurs="0" /> + <xsd:element name="giftCardReversalService" type="tns:GiftCardReversalService" + minOccurs="0" /> + <xsd:element name="verificationService" type="tns:VerificationService" minOccurs="0" /> + <xsd:element name="ccSaleService" type="tns:CCSaleService" minOccurs="0" /> + + <xsd:element name="ccSaleCreditService" type="tns:CCSaleCreditService" minOccurs="0" /> + + <xsd:element name="ccSaleReversalService" type="tns:CCSaleReversalService" minOccurs="0" /> + <xsd:element name="ccIncrementalAuthService" type="tns:CCIncrementalAuthService" minOccurs="0" /> + <xsd:element name="ccCaptureService" + type="tns:CCCaptureService" minOccurs="0" /> + <xsd:element name="ccCreditService" + type="tns:CCCreditService" minOccurs="0" /> + <xsd:element name="ccAuthReversalService" + type="tns:CCAuthReversalService" minOccurs="0" /> + <xsd:element name="ccAutoAuthReversalService" + type="tns:CCAutoAuthReversalService" minOccurs="0" /> + <xsd:element name="ccDCCService" type="tns:CCDCCService" + minOccurs="0" /> + <xsd:element name="serviceFeeCalculateService" type="tns:ServiceFeeCalculateService" + minOccurs="0" /> + <xsd:element name="ecDebitService" type="tns:ECDebitService" + minOccurs="0" /> + <xsd:element name="ecCreditService" + type="tns:ECCreditService" minOccurs="0" /> + <xsd:element name="ecAuthenticateService" + type="tns:ECAuthenticateService" minOccurs="0" /> + <xsd:element name="payerAuthEnrollService" + type="tns:PayerAuthEnrollService" minOccurs="0" /> + <xsd:element name="payerAuthValidateService" + type="tns:PayerAuthValidateService" minOccurs="0" /> + <xsd:element name="taxService" type="tns:TaxService" + minOccurs="0" /> + <xsd:element name="dmeService" type="tns:DMEService" + minOccurs="0" /> + <xsd:element name="afsService" type="tns:AFSService" + minOccurs="0" /> + <xsd:element name="davService" type="tns:DAVService" + minOccurs="0" /> + <xsd:element name="exportService" type="tns:ExportService" + minOccurs="0" /> + <xsd:element name="fxRatesService" type="tns:FXRatesService" + minOccurs="0" /> + <xsd:element name="bankTransferService" + type="tns:BankTransferService" minOccurs="0" /> + <xsd:element name="bankTransferRefundService" + type="tns:BankTransferRefundService" minOccurs="0" /> + <xsd:element name="bankTransferRealTimeService" + type="tns:BankTransferRealTimeService" minOccurs="0" /> + <xsd:element name="directDebitMandateService" + type="tns:DirectDebitMandateService" minOccurs="0" /> + <xsd:element name="directDebitService" + type="tns:DirectDebitService" minOccurs="0" /> + <xsd:element name="directDebitRefundService" + type="tns:DirectDebitRefundService" minOccurs="0" /> + <xsd:element name="directDebitValidateService" + type="tns:DirectDebitValidateService" minOccurs="0" /> + <xsd:element name="deviceFingerprintData" + type="tns:DeviceFingerprintData" minOccurs="0" maxOccurs="10" /> + <xsd:element name="paySubscriptionCreateService" + type="tns:PaySubscriptionCreateService" minOccurs="0" /> + <xsd:element name="paySubscriptionUpdateService" + type="tns:PaySubscriptionUpdateService" minOccurs="0" /> + <xsd:element name="paySubscriptionEventUpdateService" + type="tns:PaySubscriptionEventUpdateService" minOccurs="0" /> + <xsd:element name="paySubscriptionRetrieveService" + type="tns:PaySubscriptionRetrieveService" minOccurs="0" /> + <xsd:element name="paySubscriptionDeleteService" + type="tns:PaySubscriptionDeleteService" minOccurs="0" /> + <xsd:element name="payPalPaymentService" + type="tns:PayPalPaymentService" minOccurs="0" /> + <xsd:element name="payPalCreditService" + type="tns:PayPalCreditService" minOccurs="0" /> + <xsd:element name="voidService" type="tns:VoidService" + minOccurs="0" /> + <xsd:element name="businessRules" type="tns:BusinessRules" + minOccurs="0" /> + <xsd:element name="pinlessDebitService" + type="tns:PinlessDebitService" minOccurs="0" /> + <xsd:element name="pinlessDebitValidateService" + type="tns:PinlessDebitValidateService" minOccurs="0" /> + <xsd:element name="pinlessDebitReversalService" + type="tns:PinlessDebitReversalService" minOccurs="0" /> + <xsd:element name="batch" type="tns:Batch" minOccurs="0" /> + <xsd:element name="airlineData" type="tns:AirlineData" + minOccurs="0" /> + <xsd:element name="ancillaryData" type="tns:AncillaryData" + minOccurs="0" /> + <xsd:element name="lodgingData" type="tns:LodgingData" minOccurs="0" /> + <xsd:element name="payPalButtonCreateService" + type="tns:PayPalButtonCreateService" minOccurs="0" /> + <xsd:element name="payPalPreapprovedPaymentService" + type="tns:PayPalPreapprovedPaymentService" minOccurs="0" /> + <xsd:element name="payPalPreapprovedUpdateService" + type="tns:PayPalPreapprovedUpdateService" minOccurs="0" /> + <xsd:element name="riskUpdateService" + type="tns:RiskUpdateService" minOccurs="0" /> + <xsd:element name="fraudUpdateService" + type="tns:FraudUpdateService" minOccurs="0" /> + <xsd:element name="caseManagementActionService" + type="tns:CaseManagementActionService" minOccurs="0" /> + <xsd:element name="reserved" type="tns:RequestReserved" + minOccurs="0" maxOccurs="999" /> + <xsd:element name="deviceFingerprintID" type="xsd:string" + minOccurs="0" /> + <xsd:element name="deviceFingerprintRaw" type="tns:boolean" + minOccurs="0" /> + <xsd:element name="deviceFingerprintHash" type="xsd:string" + minOccurs="0" /> + <xsd:element name="payPalRefundService" + type="tns:PayPalRefundService" minOccurs="0" /> + <xsd:element name="payPalAuthReversalService" + type="tns:PayPalAuthReversalService" minOccurs="0" /> + <xsd:element name="payPalDoCaptureService" + type="tns:PayPalDoCaptureService" minOccurs="0" /> + <xsd:element name="payPalEcDoPaymentService" + type="tns:PayPalEcDoPaymentService" minOccurs="0" /> + <xsd:element name="payPalEcGetDetailsService" + type="tns:PayPalEcGetDetailsService" minOccurs="0" /> + <xsd:element name="payPalEcSetService" + type="tns:PayPalEcSetService" minOccurs="0" /> + <xsd:element name="payPalEcOrderSetupService" + type="tns:PayPalEcOrderSetupService" minOccurs="0" /> + <xsd:element name="payPalAuthorizationService" + type="tns:PayPalAuthorizationService" minOccurs="0" /> + <xsd:element name="payPalUpdateAgreementService" + type="tns:PayPalUpdateAgreementService" minOccurs="0" /> + <xsd:element name="payPalCreateAgreementService" + type="tns:PayPalCreateAgreementService" minOccurs="0" /> + <xsd:element name="payPalDoRefTransactionService" + type="tns:PayPalDoRefTransactionService" minOccurs="0" /> + <xsd:element name="chinaPaymentService" + type="tns:ChinaPaymentService" minOccurs="0" /> + <xsd:element name="chinaRefundService" + type="tns:ChinaRefundService" minOccurs="0" /> + <xsd:element name="boletoPaymentService" + type="tns:BoletoPaymentService" minOccurs="0" /> + <xsd:element name="apPaymentType" type="xsd:string" + minOccurs="0"/> + <xsd:element name="apInitiateService" + type="tns:APInitiateService" minOccurs="0" /> + <xsd:element name="apCheckStatusService" + type="tns:APCheckStatusService" minOccurs="0" /> + <xsd:element name="ignoreCardExpiration" type="tns:boolean" + minOccurs="0" /> + <xsd:element name="reportGroup" type="xsd:string" + minOccurs="0" /> + <xsd:element name="processorID" type="xsd:string" + minOccurs="0" /> + <xsd:element name="thirdPartyCertificationNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="transactionLocalDateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="solutionProviderTransactionID" type="xsd:string" minOccurs="0" /> + <xsd:element name="surchargeAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="surchargeSign" type="xsd:string" minOccurs="0"/> + <xsd:element name="pinDataEncryptedPIN" type="xsd:string" minOccurs="0"/> + <xsd:element name="pinDataKeySerialNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="pinDataPinBlockEncodingFormat" type="xsd:integer" minOccurs="0"/> + <xsd:element name="cashbackAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="pinDebitPurchaseService" type="tns:PinDebitPurchaseService" minOccurs="0"/> + <xsd:element name="pinDebitCreditService" type="tns:PinDebitCreditService" minOccurs="0"/> + <xsd:element name="pinDebitReversalService" type="tns:PinDebitReversalService" minOccurs="0"/> + <xsd:element name="ap" type="tns:AP" minOccurs="0" /> + <xsd:element name="apAuthService" type="tns:APAuthService" minOccurs="0" /> + <xsd:element name="apAuthReversalService" type="tns:APAuthReversalService" minOccurs="0" /> + <xsd:element name="apCaptureService" type="tns:APCaptureService" minOccurs="0" /> + <xsd:element name="apOptionsService" type="tns:APOptionsService" minOccurs="0" /> + <xsd:element name="apRefundService" type="tns:APRefundService" minOccurs="0" /> + <xsd:element name="apSaleService" type="tns:APSaleService" minOccurs="0" /> + <xsd:element name="apCheckoutDetailsService" type="tns:APCheckOutDetailsService" minOccurs="0" /> + <xsd:element name="apSessionsService" type="tns:APSessionsService" minOccurs="0" /> + <xsd:element name="apUI" type="tns:APUI" minOccurs="0" /> + <xsd:element name="apTransactionDetailsService" type="tns:APTransactionDetailsService" minOccurs="0" /> + <xsd:element name="apConfirmPurchaseService" type="tns:APConfirmPurchaseService" minOccurs="0" /> + <xsd:element name="payPalGetTxnDetailsService" type="tns:PayPalGetTxnDetailsService" minOccurs="0" /> + <xsd:element name="payPalTransactionSearchService" type="tns:PayPalTransactionSearchService" minOccurs="0" /> + <xsd:element name="ccDCCUpdateService" type="tns:CCDCCUpdateService" minOccurs="0"/> + <xsd:element name="emvRequest" type="tns:EmvRequest" minOccurs="0" /> + <xsd:element name="merchantTransactionIdentifier" type="xsd:string" minOccurs="0" /> + <xsd:element name="hostedDataCreateService" type="tns:HostedDataCreateService" minOccurs="0"/> + <xsd:element name="hostedDataRetrieveService" type="tns:HostedDataRetrieveService" minOccurs="0"/> + <xsd:element name="merchantCategoryCode" type="xsd:string" minOccurs="0" /> + <xsd:element name="merchantCategoryCodeDomestic" type="xsd:string" minOccurs="0" /> + <xsd:element name="salesSlipNumber" type="xsd:string" minOccurs="0" /> + <xsd:element name="merchandiseCode" type="xsd:string" minOccurs="0" /> + <xsd:element name="merchandiseDescription" type="xsd:string" minOccurs="0" /> + <xsd:element name="paymentInitiationChannel" type="xsd:string" minOccurs="0" /> + <xsd:element name="extendedCreditTotalCount" type="xsd:string" minOccurs="0" /> + <xsd:element name="authIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="paymentNetworkToken" type="tns:PaymentNetworkToken" minOccurs="0"/> + <xsd:element name="recipient" type="tns:Recipient" minOccurs="0"/> + <xsd:element name="sender" type="tns:Sender" minOccurs="0"/> + <xsd:element name="autoRentalData" type="tns:AutoRentalData" minOccurs="0" /> + <xsd:element name="paymentSolution" type="xsd:string" minOccurs="0" /> + <xsd:element name="vc" type="tns:VC" minOccurs="0" /> + <xsd:element name="decryptVisaCheckoutDataService" type="tns:DecryptVisaCheckoutDataService" minOccurs="0" /> + <xsd:element name="taxManagementIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="promotionGroup" type="tns:PromotionGroup" minOccurs="0" maxOccurs="100"/> + <xsd:element name="wallet" type="tns:Wallet" minOccurs="0" /> + <xsd:element name="aft" type="tns:Aft" minOccurs="0" /> + <xsd:element name="balanceInquiry" type="tns:boolean" minOccurs="0" /> + <xsd:element name="prenoteTransaction" type="tns:boolean" minOccurs="0"/> + <xsd:element name="encryptPaymentDataService" type="tns:EncryptPaymentDataService" minOccurs="0"/> + <xsd:element name="nationalNetDomesticData" type="xsd:string" minOccurs="0"/> + <xsd:element name="subsequentAuth" type="xsd:string" minOccurs="0"/> + <xsd:element name="subsequentAuthOriginalAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="binLookupService" type="tns:BinLookupService" minOccurs="0" /> + <xsd:element name="verificationCode" type="xsd:string" minOccurs="0" /> + <xsd:element name="mobileNumber" type="xsd:string" minOccurs="0" /> + <xsd:element name="issuer" type="tns:issuer" minOccurs="0" /> + <xsd:element name="partnerSolutionID" type="xsd:string" minOccurs="0" /> + <xsd:element name="developerID" type="xsd:string" minOccurs="0" /> + <xsd:element name="getVisaCheckoutDataService" type="tns:GETVisaCheckoutDataService" minOccurs="0" /> + <xsd:element name="customerSignatureImage" type="xsd:string" minOccurs="0"/> + <xsd:element name="transactionMetadataService" type="tns:TransactionMetadataService" minOccurs="0" /> + <xsd:element name="subsequentAuthFirst" type="xsd:string" minOccurs="0"/> + <xsd:element name="subsequentAuthReason" type="xsd:string" minOccurs="0"/> + <xsd:element name="subsequentAuthTransactionID" type="xsd:string" minOccurs="0"/> + <xsd:element name="subsequentAuthStoredCredential" type="xsd:string" minOccurs="0"/> + <xsd:element name="loan" type="tns:Loan" minOccurs="0" /> + <xsd:element name="eligibilityInquiry" type="xsd:string" minOccurs="0" /> + <xsd:element name="redemptionInquiry" type="xsd:string" minOccurs="0" /> + <xsd:element name="feeProgramIndicator" type="xsd:string" minOccurs="0" /> + <xsd:element name="apOrderService" type="tns:APOrderService" minOccurs="0" /> + <xsd:element name="apCancelService" type="tns:APCancelService" minOccurs="0" /> + <xsd:element name="apBillingAgreementService" type="tns:APBillingAgreementService" minOccurs="0" /> + <xsd:element name="note_toPayee" type="xsd:string" minOccurs="0" /> + <xsd:element name="note_toPayer" type="xsd:string" minOccurs="0" /> + <xsd:element name="clientMetadataID" type="xsd:string" minOccurs="0" /> + + <xsd:element name="partnerSDKversion" type="xsd:string" minOccurs="0" /> + <xsd:element name="partnerOriginalTransactionID" type="xsd:string" minOccurs="0" /> + <xsd:element name="cardTypeSelectionIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="apCreateMandateService" type="tns:APCreateMandateService" minOccurs="0" /> + <xsd:element name="apMandateStatusService" type="tns:APMandateStatusService" minOccurs="0" /> + <xsd:element name="apUpdateMandateService" type="tns:APUpdateMandateService" minOccurs="0" /> + <xsd:element name="apImportMandateService" type="tns:APImportMandateService" minOccurs="0" /> + <xsd:element name="apRevokeMandateService" type="tns:APRevokeMandateService" minOccurs="0" /> + <xsd:element name="billPaymentType" type="xsd:string" minOccurs="0" /> + <xsd:element name="postdatedTransaction" type="tns:PostdatedTransaction" minOccurs="0" /> + <xsd:element name="getMasterpassDataService" type="tns:GetMasterpassDataService" minOccurs="0" /> + <xsd:element name="ccCheckStatusService" type="tns:CCCheckStatusService" + minOccurs="0" /> + </xsd:sequence> + + </xsd:complexType> + + <!-- added for Visa Checkout --> + <xsd:complexType name="VC"> + <xsd:sequence> + <xsd:element name="orderID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="DecryptVisaCheckoutDataService"> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + + <xsd:complexType name="DCC"> + <xsd:sequence> + <xsd:element name="dccIndicator" type="xsd:integer" minOccurs="0"/> + <xsd:element name="referenceNumber" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="Promotion"> + <xsd:sequence> + <xsd:element name="discountedAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="type" type="xsd:string" minOccurs="0"/> + <xsd:element name="code" type="xsd:string" minOccurs="0"/> + <xsd:element name="receiptData" type="xsd:string" minOccurs="0"/> + <xsd:element name="discountApplied" type="tns:amount" minOccurs="0"/> + <xsd:element name="description" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="PromotionGroup"> + <xsd:sequence> + <xsd:element name="subtotalAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="taxRate" type="tns:amount" minOccurs="0"/> + <xsd:element name="prohibitDiscount" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="id" type="xsd:integer" use="optional"/> + </xsd:complexType> + + <xsd:complexType name="PromotionGroupReply"> + <xsd:sequence> + <xsd:element name="discountApplied" type="tns:amount" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="id" type="xsd:integer" use="optional"/> + </xsd:complexType> + + <xsd:complexType name="CCAuthReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="authorizationCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="avsCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="avsCodeRaw" type="xsd:string" minOccurs="0"/> + <xsd:element name="cvCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="cvCodeRaw" type="xsd:string" minOccurs="0"/> + <xsd:element name="personalIDCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="authorizedDateTime" type="tns:dateTime" minOccurs="0"/> + <!-- dateTime --> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="bmlAccountNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="authFactorCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="fundingTotals" type="tns:FundingTotals" minOccurs="0"/> + <xsd:element name="fxQuoteID" type="xsd:string" minOccurs="0"/> + <xsd:element name="fxQuoteRate" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="fxQuoteType" type="xsd:string" minOccurs="0"/> + <xsd:element name="fxQuoteExpirationDateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="authRecord" type="xsd:string" minOccurs="0"/> + <xsd:element name="merchantAdviceCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="merchantAdviceCodeRaw" type="xsd:string" minOccurs="0"/> + <xsd:element name="cavvResponseCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="cavvResponseCodeRaw" type="xsd:string" minOccurs="0"/> + <xsd:element name="authenticationXID" type="xsd:string" minOccurs="0"/> + <xsd:element name="authorizationXID" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorCardType" type="xsd:string" minOccurs="0"/> + <xsd:element name="accountBalance" type="tns:amount" minOccurs="0"/> + <xsd:element name="forwardCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="enhancedDataEnabled" type="xsd:string" minOccurs="0"/> + <xsd:element name="referralResponseNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="subResponseCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="approvedAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="creditLine" type="xsd:string" minOccurs="0"/> + <xsd:element name="approvedTerms" type="xsd:string" minOccurs="0"/> + <xsd:element name="paymentNetworkTransactionID" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardCategory" type="xsd:string" minOccurs="0"/> + <xsd:element name="ownerMerchantID" type="xsd:string" minOccurs="0"/> + <xsd:element name="requestAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="requestCurrency" type="xsd:string" minOccurs="0"/> + <xsd:element name="accountBalanceCurrency" type="xsd:string" minOccurs="0"/> + <xsd:element name="accountBalanceSign" type="xsd:string" minOccurs="0"/> + <xsd:element name="amountType" type="xsd:string" minOccurs="0"/> + <xsd:element name="accountType" type="xsd:string" minOccurs="0"/> + <xsd:element name="affluenceIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="evEmail" type="xsd:string" minOccurs="0"/> + <xsd:element name="evPhoneNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="evPostalCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="evName" type="xsd:string" minOccurs="0"/> + <xsd:element name="evStreet" type="xsd:string" minOccurs="0"/> + <xsd:element name="evEmailRaw" type="xsd:string" minOccurs="0"/> + <xsd:element name="evPhoneNumberRaw" type="xsd:string" minOccurs="0"/> + <xsd:element name="evPostalCodeRaw" type="xsd:string" minOccurs="0"/> + <xsd:element name="evNameRaw" type="xsd:string" minOccurs="0"/> + <xsd:element name="evStreetRaw" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardGroup" type="xsd:string" minOccurs="0"/> + <xsd:element name="posData" type="xsd:string" minOccurs="0"/> + <xsd:element name="transactionID" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardIssuerCountry" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardRegulated" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardCommercial" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardPrepaid" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardPayroll" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardHealthcare" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardSignatureDebit" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardPINlessDebit" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardLevel3Eligible" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorTransactionID" type="xsd:string" minOccurs="0"/> + <xsd:element name="providerReasonCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="providerReasonDescription" type="xsd:string" minOccurs="0"/> + <xsd:element name="providerPassThroughData" type="xsd:string" minOccurs="0"/> + <xsd:element name="providerCVNResponseCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="providerAVSResponseCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="providerAcquirerBankCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="paymentCardService" type="xsd:string" minOccurs="0"/> + <xsd:element name="paymentCardServiceResult" type="xsd:string" minOccurs="0"/> + <xsd:element name="transactionQualification" type="xsd:string" minOccurs="0"/> + <xsd:element name="transactionIntegrity" type="xsd:string" minOccurs="0"/> + <xsd:element name="emsTransactionRiskScore" type="xsd:string" minOccurs="0" /> + <xsd:element name="reconciliationReferenceNumber" type="xsd:string" minOccurs="0" /> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="OCTReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="requestDateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="approvalCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="paymentNetworkTransactionID" type="xsd:string" minOccurs="0"/> + <xsd:element name="prepaidBalanceCurrency" type="xsd:string" minOccurs="0"/> + <xsd:element name="prepaidBalanceAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorResponseSource" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationIdType" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="VerificationReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer" /> + <xsd:element name="processorTransactionID" type="xsd:string" minOccurs="0" /> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0" /> + <xsd:element name="verifiedDateTime" type="xsd:string" minOccurs="0" /> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0" /> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="CCSaleReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="authorizationCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="avsCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="avsCodeRaw" type="xsd:string" minOccurs="0"/> + <xsd:element name="cvCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="cvCodeRaw" type="xsd:string" minOccurs="0"/> + <xsd:element name="cavvResponseCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="cavvResponseCodeRaw" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardGroup" type="xsd:string" minOccurs="0"/> + <xsd:element name="paymentNetworkTransactionID" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardCategory" type="xsd:string" minOccurs="0"/> + <xsd:element name="accountBalance" type="tns:amount" minOccurs="0"/> + <xsd:element name="authorizedDateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="requestAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="accountBalanceCurrency" type="xsd:string" minOccurs="0"/> + <xsd:element name="accountBalanceSign" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="CCSaleCreditReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="authorizationCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="authorizedDateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="paymentNetworkTransactionID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="CCSaleReversalReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="authorizationCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="requestDateTime" type="tns:dateTime" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="CCIncrementalAuthReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="amount" type="tns:amount" minOccurs="0" /> + <xsd:element name="authorizationCode" type="xsd:string" minOccurs="0" /> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0" /> + <xsd:element name="authorizedDateTime" type="tns:dateTime" minOccurs="0" /> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0" /> + <xsd:element name="paymentNetworkTransactionID" type="xsd:string" minOccurs="0" /> + <xsd:element name="cardCategory" type="xsd:string" minOccurs="0" /> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="CCCaptureReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="requestDateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="fundingTotals" type="tns:FundingTotals" minOccurs="0"/> + <xsd:element name="fxQuoteID" type="xsd:string" minOccurs="0"/> + <xsd:element name="fxQuoteRate" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="fxQuoteType" type="xsd:string" minOccurs="0"/> + <xsd:element name="fxQuoteExpirationDateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="purchasingLevel3Enabled" type="xsd:string" minOccurs="0"/> + <xsd:element name="enhancedDataEnabled" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationReferenceNumber" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="ServiceFeeCalculateReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer" /> + <xsd:element name="amount" type="tns:amount" minOccurs="0" /> + <xsd:element name="requestDateTime" type="tns:dateTime" minOccurs="0" /> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="CCCreditReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="requestDateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="purchasingLevel3Enabled" type="xsd:string" minOccurs="0"/> + <xsd:element name="enhancedDataEnabled" type="xsd:string" minOccurs="0"/> + <xsd:element name="authorizationXID" type="xsd:string" minOccurs="0"/> + <xsd:element name="forwardCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="ownerMerchantID" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationReferenceNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="authorizationCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="paymentNetworkTransactionID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="PinDebitPurchaseReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="authorizationCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="networkCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="transactionID" type="xsd:string" minOccurs="0"/> + <xsd:element name="requestAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="requestCurrency" type="xsd:string" minOccurs="0"/> + <xsd:element name="amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="dateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="accountType" type="xsd:string" minOccurs="0"/> + <xsd:element name="amountType" type="xsd:string" minOccurs="0"/> + <xsd:element name="accountBalance" type="xsd:string" minOccurs="0"/> + <xsd:element name="accountBalanceCurrency" type="xsd:string" minOccurs="0"/> + <xsd:element name="accountBalanceSign" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="PinDebitCreditReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="authorizationCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="networkCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="transactionID" type="xsd:string" minOccurs="0"/> + <xsd:element name="amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="dateTime" type="tns:dateTime" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="PinDebitReversalReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="dateTime" type="tns:dateTime" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="CCAuthReversalReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="authorizationCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="requestDateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="forwardCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorTransactionID" type="xsd:string" minOccurs="0"/> + <xsd:element name="paymentCardService" type="xsd:string" minOccurs="0"/> + <xsd:element name="paymentCardServiceResult" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="CCAutoAuthReversalReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="result" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="ECDebitReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="settlementMethod" type="xsd:string" minOccurs="0"/> + <xsd:element name="requestDateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="verificationLevel" type="xsd:integer" minOccurs="0"/> + <xsd:element name="processorTransactionID" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="avsCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="avsCodeRaw" type="xsd:string" minOccurs="0"/> + <xsd:element name="verificationCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="verificationCodeRaw" type="xsd:string" minOccurs="0"/> + <xsd:element name="correctedAccountNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="correctedRoutingNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="ownerMerchantID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="ECCreditReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="settlementMethod" type="xsd:string" minOccurs="0"/> + <xsd:element name="requestDateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="processorTransactionID" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="verificationCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="verificationCodeRaw" type="xsd:string" minOccurs="0"/> + <xsd:element name="correctedAccountNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="correctedRoutingNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="ownerMerchantID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="ECAuthenticateReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="requestDateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="checkpointSummary" type="xsd:string" minOccurs="0"/> + <xsd:element name="fraudShieldIndicators" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="PayerAuthEnrollReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="acsURL" type="xsd:string" minOccurs="0"/> + <xsd:element name="authenticationResult" type="xsd:string" minOccurs="0"/> + <xsd:element name="authenticationStatusMessage" type="xsd:string" minOccurs="0"/> + <xsd:element name="cavv" type="xsd:string" minOccurs="0"/> + <xsd:element name="cavvAlgorithm" type="xsd:string" minOccurs="0"/> + <xsd:element name="commerceIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="eci" type="xsd:string" minOccurs="0"/> + <xsd:element name="eciRaw" type="xsd:string" minOccurs="0"/> + <xsd:element name="paReq" type="xsd:string" minOccurs="0"/> + <xsd:element name="proxyPAN" type="xsd:string" minOccurs="0"/> + <xsd:element name="xid" type="xsd:string" minOccurs="0"/> + <xsd:element name="proofXML" type="xsd:string" minOccurs="0"/> + <xsd:element name="ucafAuthenticationData" type="xsd:string" minOccurs="0"/> + <xsd:element name="ucafCollectionIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="paresStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="veresEnrolled" type="xsd:string" minOccurs="0"/> + <xsd:element name="authenticationPath" type="xsd:string" minOccurs="0"/> + <xsd:element name="specificationVersion" type="xsd:string" minOccurs="0"/> + <xsd:element name="authenticationTransactionID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="PayerAuthValidateReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="authenticationResult" type="xsd:string" minOccurs="0"/> + <xsd:element name="authenticationStatusMessage" type="xsd:string" minOccurs="0"/> + <xsd:element name="cavv" type="xsd:string" minOccurs="0"/> + <xsd:element name="cavvAlgorithm" type="xsd:string" minOccurs="0"/> + <xsd:element name="commerceIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="eci" type="xsd:string" minOccurs="0"/> + <xsd:element name="eciRaw" type="xsd:string" minOccurs="0"/> + <xsd:element name="xid" type="xsd:string" minOccurs="0"/> + <xsd:element name="ucafAuthenticationData" type="xsd:string" minOccurs="0"/> + <xsd:element name="ucafCollectionIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="paresStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="specificationVersion" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="TaxReplyItem"> + <xsd:sequence> + <xsd:element name="taxableAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="exemptAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="specialTaxAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="cityTaxAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="countyTaxAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="districtTaxAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="stateTaxAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="countryTaxAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="totalTaxAmount" type="tns:amount"/> + <xsd:element name="jurisdiction" type="tns:TaxReplyItemJurisdiction" minOccurs="0" maxOccurs="1000"/> + </xsd:sequence> + <xsd:attribute name="id" type="xsd:integer" use="required"/> + </xsd:complexType> + <xsd:complexType name="TaxReplyItemJurisdiction"> + <xsd:sequence> + <xsd:element name="country" type="xsd:string" minOccurs="0"/> + <xsd:element name="region" type="xsd:string" minOccurs="0"/> + <xsd:element name="type" type="xsd:string" minOccurs="0"/> + <xsd:element name="code" type="xsd:string" minOccurs="0"/> + <xsd:element name="taxable" type="tns:amount" minOccurs="0"/> + <xsd:element name="rate" type="tns:amount" minOccurs="0"/> + <xsd:element name="taxAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="name" type="xsd:string"/> + <xsd:element name="taxName" type="xsd:string"/> + </xsd:sequence> + <xsd:attribute name="jurisId" type="xsd:integer" use="required"/> + </xsd:complexType> + <xsd:complexType name="TaxReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="currency" type="xsd:string" minOccurs="0"/> + <xsd:element name="grandTotalAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="totalTaxableAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="totalExemptAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="totalSpecialTaxAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="totalCityTaxAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="city" type="xsd:string" minOccurs="0"/> + <xsd:element name="totalCountyTaxAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="county" type="xsd:string" minOccurs="0"/> + <xsd:element name="totalDistrictTaxAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="totalStateTaxAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="state" type="xsd:string" minOccurs="0"/> + <xsd:element name="totalCountryTaxAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="totalTaxAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="commitIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="refundIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="postalCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="geocode" type="xsd:string" minOccurs="0"/> + <xsd:element name="item" type="tns:TaxReplyItem" minOccurs="0" maxOccurs="1000"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="DeviceFingerprint"> + <xsd:sequence> + <xsd:element name="cookiesEnabled" type="tns:boolean" minOccurs="0"/> + <xsd:element name="flashEnabled" type="tns:boolean" minOccurs="0"/> + <xsd:element name="hash" type="xsd:string" minOccurs="0"/> + <xsd:element name="imagesEnabled" type="tns:boolean" minOccurs="0"/> + <xsd:element name="javascriptEnabled" type="tns:boolean" minOccurs="0"/> + <xsd:element name="proxyIPAddress" type="xsd:string" minOccurs="0"/> + <xsd:element name="proxyIPAddressActivities" type="xsd:string" minOccurs="0"/> + <xsd:element name="proxyIPAddressAttributes" type="xsd:string" minOccurs="0"/> + <xsd:element name="proxyServerType" type="xsd:string" minOccurs="0"/> + <xsd:element name="trueIPAddress" type="xsd:string" minOccurs="0"/> + <xsd:element name="trueIPAddressActivities" type="xsd:string" minOccurs="0"/> + <xsd:element name="trueIPAddressAttributes" type="xsd:string" minOccurs="0"/> + <xsd:element name="trueIPAddressCity" type="xsd:string" minOccurs="0"/> + <xsd:element name="trueIPAddressState" type="xsd:string" minOccurs="0"/> + <xsd:element name="trueIPAddressCountry" type="xsd:string" minOccurs="0"/> + <xsd:element name="smartID" type="xsd:string" minOccurs="0"/> + <xsd:element name="smartIDConfidenceLevel" type="xsd:string" minOccurs="0"/> + <xsd:element name="screenResolution" type="xsd:string" minOccurs="0"/> + <xsd:element name="browserLanguage" type="xsd:string" minOccurs="0"/> + <xsd:element name="agentType" type="xsd:string" minOccurs="0"/> + <xsd:element name="dateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="profileDuration" type="xsd:integer" minOccurs="0"/> + <xsd:element name="profiledURL" type="xsd:string" minOccurs="0"/> + <xsd:element name="timeOnPage" type="xsd:integer" minOccurs="0"/> + <xsd:element name="deviceMatch" type="xsd:string" minOccurs="0"/> + <xsd:element name="firstEncounter" type="xsd:string" minOccurs="0"/> + <xsd:element name="flashOS" type="xsd:string" minOccurs="0"/> + <xsd:element name="flashVersion" type="xsd:string" minOccurs="0"/> + <xsd:element name="deviceLatitude" type="xsd:string" minOccurs="0"/> + <xsd:element name="deviceLongitude" type="xsd:string" minOccurs="0"/> + <xsd:element name="gpsAccuracy" type="xsd:string" minOccurs="0"/> + <xsd:element name="jbRoot" type="xsd:integer" minOccurs="0"/> + <xsd:element name="jbRootReason" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="AFSReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="afsResult" type="xsd:integer" minOccurs="0"/> + <xsd:element name="hostSeverity" type="xsd:integer" minOccurs="0"/> + <xsd:element name="consumerLocalTime" type="xsd:string" minOccurs="0"/> + <!-- xsd:time --> + <xsd:element name="afsFactorCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="addressInfoCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="hotlistInfoCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="internetInfoCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="phoneInfoCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="suspiciousInfoCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="velocityInfoCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="identityInfoCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="ipCountry" type="xsd:string" minOccurs="0"/> + <xsd:element name="ipState" type="xsd:string" minOccurs="0"/> + <xsd:element name="ipCity" type="xsd:string" minOccurs="0"/> + <xsd:element name="ipRoutingMethod" type="xsd:string" minOccurs="0"/> + <xsd:element name="ipAnonymizerStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="scoreModelUsed" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardBin" type="xsd:string" minOccurs="0"/> + <xsd:element name="binCountry" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardAccountType" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardScheme" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardIssuer" type="xsd:string" minOccurs="0"/> + <xsd:element name="deviceFingerprint" type="tns:DeviceFingerprint" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="DAVReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="addressType" type="xsd:string" minOccurs="0"/> + <xsd:element name="apartmentInfo" type="xsd:string" minOccurs="0"/> + <xsd:element name="barCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="barCodeCheckDigit" type="xsd:string" minOccurs="0"/> + <xsd:element name="careOf" type="xsd:string" minOccurs="0"/> + <xsd:element name="cityInfo" type="xsd:string" minOccurs="0"/> + <xsd:element name="countryInfo" type="xsd:string" minOccurs="0"/> + <xsd:element name="directionalInfo" type="xsd:string" minOccurs="0"/> + <xsd:element name="lvrInfo" type="xsd:string" minOccurs="0"/> + <xsd:element name="matchScore" type="xsd:integer" minOccurs="0"/> + <xsd:element name="standardizedAddress1" type="xsd:string" minOccurs="0"/> + <xsd:element name="standardizedAddress2" type="xsd:string" minOccurs="0"/> + <xsd:element name="standardizedAddress3" type="xsd:string" minOccurs="0"/> + <xsd:element name="standardizedAddress4" type="xsd:string" minOccurs="0"/> + <xsd:element name="standardizedAddressNoApt" type="xsd:string" minOccurs="0"/> + <xsd:element name="standardizedCity" type="xsd:string" minOccurs="0"/> + <xsd:element name="standardizedCounty" type="xsd:string" minOccurs="0"/> + <xsd:element name="standardizedCSP" type="xsd:string" minOccurs="0"/> + <xsd:element name="standardizedState" type="xsd:string" minOccurs="0"/> + <xsd:element name="standardizedPostalCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="standardizedCountry" type="xsd:string" minOccurs="0"/> + <xsd:element name="standardizedISOCountry" type="xsd:string" minOccurs="0"/> + <xsd:element name="stateInfo" type="xsd:string" minOccurs="0"/> + <xsd:element name="streetInfo" type="xsd:string" minOccurs="0"/> + <xsd:element name="suffixInfo" type="xsd:string" minOccurs="0"/> + <xsd:element name="postalCodeInfo" type="xsd:string" minOccurs="0"/> + <xsd:element name="overallInfo" type="xsd:string" minOccurs="0"/> + <xsd:element name="usInfo" type="xsd:string" minOccurs="0"/> + <xsd:element name="caInfo" type="xsd:string" minOccurs="0"/> + <xsd:element name="intlInfo" type="xsd:string" minOccurs="0"/> + <xsd:element name="usErrorInfo" type="xsd:string" minOccurs="0"/> + <xsd:element name="caErrorInfo" type="xsd:string" minOccurs="0"/> + <xsd:element name="intlErrorInfo" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="DeniedPartiesMatch"> + <xsd:sequence> + <xsd:element name="list" type="xsd:string" minOccurs="0"/> + <xsd:element name="name" type="xsd:string" minOccurs="0" maxOccurs="100"/> + <xsd:element name="address" type="xsd:string" minOccurs="0" maxOccurs="100"/> + <xsd:element name="program" type="xsd:string" minOccurs="0" maxOccurs="100"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="ExportReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="ipCountryConfidence" type="xsd:integer" minOccurs="0"/> + <xsd:element name="infoCode" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="FXQuote"> + <xsd:sequence> + <xsd:element name="id" type="xsd:string" minOccurs="0"/> + <xsd:element name="rate" type="xsd:string" minOccurs="0"/> + <xsd:element name="type" type="xsd:string" minOccurs="0"/> + <xsd:element name="expirationDateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="currency" type="xsd:string" minOccurs="0"/> + <xsd:element name="fundingCurrency" type="xsd:string" minOccurs="0"/> + <xsd:element name="receivedDateTime" type="tns:dateTime" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="FXRatesReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="quote" type="tns:FXQuote" minOccurs="0" maxOccurs="999"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="BankTransferReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="accountHolder" type="xsd:string" minOccurs="0"/> + <xsd:element name="accountNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="bankName" type="xsd:string" minOccurs="0"/> + <xsd:element name="bankCity" type="xsd:string" minOccurs="0"/> + <xsd:element name="bankCountry" type="xsd:string" minOccurs="0"/> + <xsd:element name="paymentReference" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="bankSwiftCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="bankSpecialID" type="xsd:string" minOccurs="0"/> + <xsd:element name="requestDateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="iban" type="xsd:string" minOccurs="0"/> + <xsd:element name="bankCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="branchCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationReferenceNumber" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="BankTransferRealTimeReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="formMethod" type="xsd:string" minOccurs="0"/> + <xsd:element name="formAction" type="xsd:string" minOccurs="0"/> + <xsd:element name="requestDateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="paymentReference" type="xsd:string" minOccurs="0"/> + <xsd:element name="amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="reconciliationReferenceNumber" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="DirectDebitMandateReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="mandateID" type="xsd:string" minOccurs="0"/> + <xsd:element name="mandateMaturationDate" type="xsd:string" minOccurs="0"/> + <xsd:element name="requestDateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="BankTransferRefundReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="requestDateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="iban" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationReferenceNumber" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="DirectDebitReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="requestDateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorTransactionID" type="xsd:string" minOccurs="0"/> + <xsd:element name="mandateAuthenticationDate" type="xsd:string" minOccurs="0"/> + <xsd:element name="mandateID" type="xsd:string" minOccurs="0"/> + <xsd:element name="iban" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationReferenceNumber" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="DirectDebitValidateReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="requestDateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="iban" type="xsd:string" minOccurs="0"/> + <xsd:element name="bankSwiftCode" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="DirectDebitRefundReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="requestDateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorTransactionID" type="xsd:string" minOccurs="0"/> + <xsd:element name="iban" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationReferenceNumber" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="PaySubscriptionCreateReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="subscriptionID" type="xsd:string"/> + <xsd:element name="instrumentIdentifierID" type="xsd:string" minOccurs="0"/> + <xsd:element name="instrumentIdentifierStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="instrumentIdentifierNew" type="xsd:string" minOccurs="0"/> + <xsd:element name="instrumentIdentifierSuccessorID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="PaySubscriptionUpdateReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="subscriptionID" type="xsd:string"/> + <xsd:element name="subscriptionIDNew" type="xsd:string" minOccurs="0"/> + <xsd:element name="ownerMerchantID" type="xsd:string" minOccurs="0"/> + <xsd:element name="instrumentIdentifierID" type="xsd:string" minOccurs="0"/> + <xsd:element name="instrumentIdentifierStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="instrumentIdentifierNew" type="xsd:string" minOccurs="0"/> + <xsd:element name="instrumentIdentifierSuccessorID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="PaySubscriptionEventUpdateReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="ownerMerchantID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="PaySubscriptionRetrieveReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="approvalRequired" type="xsd:string" minOccurs="0"/> + <xsd:element name="automaticRenew" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardAccountNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardExpirationMonth" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardExpirationYear" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardIssueNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardStartMonth" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardStartYear" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardType" type="xsd:string" minOccurs="0"/> + <xsd:element name="checkAccountNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="checkAccountType" type="xsd:string" minOccurs="0"/> + <xsd:element name="checkBankTransitNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="checkSecCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="checkAuthenticateID" type="xsd:string" minOccurs="0"/> + <xsd:element name="city" type="xsd:string" minOccurs="0"/> + <xsd:element name="comments" type="xsd:string" minOccurs="0"/> + <xsd:element name="companyName" type="xsd:string" minOccurs="0"/> + <xsd:element name="country" type="xsd:string" minOccurs="0"/> + <xsd:element name="currency" type="xsd:string" minOccurs="0"/> + <xsd:element name="customerAccountID" type="xsd:string" minOccurs="0"/> + <xsd:element name="email" type="xsd:string" minOccurs="0"/> + <xsd:element name="endDate" type="xsd:string" minOccurs="0"/> + <xsd:element name="firstName" type="xsd:string" minOccurs="0"/> + <xsd:element name="frequency" type="xsd:string" minOccurs="0"/> + <xsd:element name="lastName" type="xsd:string" minOccurs="0"/> + <xsd:element name="merchantReferenceCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="paymentMethod" type="xsd:string" minOccurs="0"/> + <xsd:element name="paymentsRemaining" type="xsd:string" minOccurs="0"/> + <xsd:element name="phoneNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="postalCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="recurringAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="setupAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="startDate" type="xsd:string" minOccurs="0"/> + <xsd:element name="state" type="xsd:string" minOccurs="0"/> + <xsd:element name="status" type="xsd:string" minOccurs="0"/> + <xsd:element name="street1" type="xsd:string" minOccurs="0"/> + <xsd:element name="street2" type="xsd:string" minOccurs="0"/> + <xsd:element name="subscriptionID" type="xsd:string" minOccurs="0"/> + <xsd:element name="subscriptionIDNew" type="xsd:string" minOccurs="0"/> + <xsd:element name="title" type="xsd:string" minOccurs="0"/> + <xsd:element name="totalPayments" type="xsd:string" minOccurs="0"/> + <xsd:element name="shipToFirstName" type="xsd:string" minOccurs="0"/> + <xsd:element name="shipToLastName" type="xsd:string" minOccurs="0"/> + <xsd:element name="shipToStreet1" type="xsd:string" minOccurs="0"/> + <xsd:element name="shipToStreet2" type="xsd:string" minOccurs="0"/> + <xsd:element name="shipToCity" type="xsd:string" minOccurs="0"/> + <xsd:element name="shipToState" type="xsd:string" minOccurs="0"/> + <xsd:element name="shipToPostalCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="shipToCompany" type="xsd:string" minOccurs="0"/> + <xsd:element name="shipToCountry" type="xsd:string" minOccurs="0"/> + <xsd:element name="billPayment" type="xsd:string" minOccurs="0"/> + <xsd:element name="merchantDefinedDataField1" type="xsd:string" minOccurs="0"/> + <xsd:element name="merchantDefinedDataField2" type="xsd:string" minOccurs="0"/> + <xsd:element name="merchantDefinedDataField3" type="xsd:string" minOccurs="0"/> + <xsd:element name="merchantDefinedDataField4" type="xsd:string" minOccurs="0"/> + <xsd:element name="merchantSecureDataField1" type="xsd:string" minOccurs="0"/> + <xsd:element name="merchantSecureDataField2" type="xsd:string" minOccurs="0"/> + <xsd:element name="merchantSecureDataField3" type="xsd:string" minOccurs="0"/> + <xsd:element name="merchantSecureDataField4" type="xsd:string" minOccurs="0"/> + <xsd:element name="ownerMerchantID" type="xsd:string" minOccurs="0"/> + <xsd:element name="companyTaxID" type="xsd:string" minOccurs="0"/> + <xsd:element name="driversLicenseNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="driversLicenseState" type="xsd:string" minOccurs="0"/> + <xsd:element name="dateOfBirth" type="xsd:string" minOccurs="0"/> + <xsd:element name="instrumentIdentifierID" type="xsd:string" minOccurs="0"/> + <xsd:element name="instrumentIdentifierStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="instrumentIdentifierSuccessorID" type="xsd:string" minOccurs="0"/> + <xsd:element name="subsequentAuthTransactionID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="PaySubscriptionDeleteReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="subscriptionID" type="xsd:string"/> + <xsd:element name="instrumentIdentifierID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="PayPalPaymentReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="secureData" type="xsd:string" minOccurs="0"/> + <xsd:element name="amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="requestDateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="PayPalCreditReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="requestDateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="VoidReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="requestDateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="currency" type="xsd:string" minOccurs="0"/> + <xsd:element name="reversalSubmitted" type="tns:boolean" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="PinlessDebitReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="authorizationCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="requestDateTime" type="tns:dateTime" minOccurs="0"/> + <!-- dateTime --> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="receiptNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="ownerMerchantID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="PinlessDebitValidateReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="status" type="xsd:string" minOccurs="0"/> + <xsd:element name="requestDateTime" type="tns:dateTime" minOccurs="0"/> + <!-- dateTime --> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="PinlessDebitReversalReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="requestDateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <!-- payPal Upgrade Services --> + <xsd:complexType name="PayPalButtonCreateReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="encryptedFormData" type="xsd:string" minOccurs="0"/> + <xsd:element name="unencryptedFormData" type="xsd:string" minOccurs="0"/> + <xsd:element name="requestDateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="buttonType" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="PayPalPreapprovedPaymentReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="requestDateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerName" type="xsd:string" minOccurs="0"/> + <xsd:element name="transactionType" type="xsd:string" minOccurs="0"/> + <xsd:element name="feeAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerCountry" type="xsd:string" minOccurs="0"/> + <xsd:element name="pendingReason" type="xsd:string" minOccurs="0"/> + <xsd:element name="paymentStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="mpStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="payer" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerID" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerBusiness" type="xsd:string" minOccurs="0"/> + <xsd:element name="transactionID" type="xsd:string" minOccurs="0"/> + <xsd:element name="desc" type="xsd:string" minOccurs="0"/> + <xsd:element name="mpMax" type="xsd:string" minOccurs="0"/> + <xsd:element name="paymentType" type="xsd:string" minOccurs="0"/> + <xsd:element name="paymentDate" type="xsd:string" minOccurs="0"/> + <xsd:element name="paymentGrossAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="settleAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="taxAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="exchangeRate" type="xsd:string" minOccurs="0"/> + <xsd:element name="paymentSourceID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="PayPalPreapprovedUpdateReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="requestDateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerName" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerCountry" type="xsd:string" minOccurs="0"/> + <xsd:element name="mpStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="payer" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerID" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerBusiness" type="xsd:string" minOccurs="0"/> + <xsd:element name="desc" type="xsd:string" minOccurs="0"/> + <xsd:element name="mpMax" type="xsd:string" minOccurs="0"/> + <xsd:element name="paymentSourceID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <!-- PayPalEcSet --> + <xsd:complexType name="PayPalEcSetReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="paypalToken" type="xsd:string" minOccurs="0"/> + <xsd:element name="amount" type="xsd:string" minOccurs="0"/> + <xsd:element name="currency" type="xsd:string" minOccurs="0"/> + <xsd:element name="correlationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="errorCode" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <!-- end of PayPalEcSet --> + <!-- PayPalEcGetDetails --> + <xsd:complexType name="PayPalEcGetDetailsReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="paypalToken" type="xsd:string" minOccurs="0"/> + <xsd:element name="payer" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerId" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerSalutation" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerFirstname" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerMiddlename" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerLastname" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerSuffix" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerCountry" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerBusiness" type="xsd:string" minOccurs="0"/> + <xsd:element name="shipToName" type="xsd:string" minOccurs="0"/> + <xsd:element name="shipToAddress1" type="xsd:string" minOccurs="0"/> + <xsd:element name="shipToAddress2" type="xsd:string" minOccurs="0"/> + <xsd:element name="shipToCity" type="xsd:string" minOccurs="0"/> + <xsd:element name="shipToState" type="xsd:string" minOccurs="0"/> + <xsd:element name="shipToCountry" type="xsd:string" minOccurs="0"/> + <xsd:element name="shipToZip" type="xsd:string" minOccurs="0"/> + <xsd:element name="addressStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerPhone" type="xsd:string" minOccurs="0"/> + <xsd:element name="avsCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="street1" type="xsd:string" minOccurs="0"/> + <xsd:element name="street2" type="xsd:string" minOccurs="0"/> + <xsd:element name="city" type="xsd:string" minOccurs="0"/> + <xsd:element name="state" type="xsd:string" minOccurs="0"/> + <xsd:element name="postalCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="countryCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="countryName" type="xsd:string" minOccurs="0"/> + <xsd:element name="addressID" type="xsd:string" minOccurs="0"/> + <xsd:element name="errorCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="correlationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalBillingAgreementAcceptedStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalTaxAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="item" type="tns:Item" minOccurs="0" maxOccurs="1000" /> + </xsd:sequence> + </xsd:complexType> + <!-- end of PayPalEcGetDetails --> + <!-- PayPalEcDoPayment --> + <xsd:complexType name="PayPalEcDoPaymentReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="paypalToken" type="xsd:string" minOccurs="0"/> + <xsd:element name="transactionId" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalTransactiontype" type="xsd:string" minOccurs="0"/> + <xsd:element name="paymentType" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalOrderTime" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalFeeAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalTaxAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalExchangeRate" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalPaymentStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalPendingReason" type="xsd:string" minOccurs="0"/> + <xsd:element name="orderId" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalReasonCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="amount" type="xsd:string" minOccurs="0"/> + <xsd:element name="currency" type="xsd:string" minOccurs="0"/> + <xsd:element name="correlationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="errorCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalBillingAgreementId" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <!-- end of PayPalEcDoPayment --> + <!-- PayPalDoCapture --> + <xsd:complexType name="PayPalDoCaptureReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="authorizationId" type="xsd:string" minOccurs="0"/> + <xsd:element name="transactionId" type="xsd:string" minOccurs="0"/> + <xsd:element name="parentTransactionId" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalReceiptId" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalTransactiontype" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalPaymentType" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalOrderTime" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalPaymentGrossAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalFeeAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalTaxAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalExchangeRate" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalPaymentStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="amount" type="xsd:string" minOccurs="0"/> + <xsd:element name="currency" type="xsd:string" minOccurs="0"/> + <xsd:element name="correlationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="errorCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalPendingReason" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <!-- end of PayPalDoCapture --> + <!-- PayPalAuthReversal --> + <xsd:complexType name="PayPalAuthReversalReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="authorizationId" type="xsd:string" minOccurs="0"/> + <xsd:element name="correlationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="errorCode" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <!-- end of PayPalAuthReversal --> + <!-- PayPalRefund --> + <xsd:complexType name="PayPalRefundReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="transactionId" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalNetRefundAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalFeeRefundAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalGrossRefundAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="correlationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="errorCode" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <!-- end of PayPalRefund --> + <!-- PayPalEcOrderSetup --> + <xsd:complexType name="PayPalEcOrderSetupReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="paypalToken" type="xsd:string" minOccurs="0"/> + <xsd:element name="transactionId" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalTransactiontype" type="xsd:string" minOccurs="0"/> + <xsd:element name="paymentType" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalOrderTime" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalFeeAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalTaxAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalExchangeRate" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalPaymentStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalPendingReason" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalReasonCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="amount" type="xsd:string" minOccurs="0"/> + <xsd:element name="currency" type="xsd:string" minOccurs="0"/> + <xsd:element name="correlationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="errorCode" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <!-- end of PayPalEcOrderSetup --> + <!-- PayPalAuthorization--> + <xsd:complexType name="PayPalAuthorizationReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="transactionId" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="amount" type="xsd:string" minOccurs="0"/> + <xsd:element name="currency" type="xsd:string" minOccurs="0"/> + <xsd:element name="correlationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="errorCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="protectionEligibility" type="xsd:string" minOccurs="0"/> + <xsd:element name="protectionEligibilityType" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <!-- end of PayPalAuthorization --> + <!-- PayPalUpdateAgreement--> + <xsd:complexType name="PayPalUpdateAgreementReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="paypalBillingAgreementId" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalBillingAgreementDesc" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalBillingAgreementCustom" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalBillingAgreementStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="payer" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerId" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerCountry" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerBusiness" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerSalutation" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerFirstname" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerMiddlename" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerLastname" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerSuffix" type="xsd:string" minOccurs="0"/> + <xsd:element name="addressStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="errorCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="correlationID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <!-- end of PayPalUpdateAgreement--> + <!-- PayPalCreateAgreement--> + <xsd:complexType name="PayPalCreateAgreementReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="paypalBillingAgreementId" type="xsd:string" minOccurs="0"/> + <xsd:element name="errorCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="correlationID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <!-- end of PayPalCreateAgreement--> + <!-- PayPalDoRefTransaction--> + <xsd:complexType name="PayPalDoRefTransactionReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="paypalBillingAgreementId" type="xsd:string" minOccurs="0"/> + <xsd:element name="transactionId" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalTransactionType" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalPaymentType" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalOrderTime" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="currency" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalTaxAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalExchangeRate" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalPaymentStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalPendingReason" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalReasonCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="errorCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="correlationID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <!-- end of PayPalDoRefTransaction--> + <xsd:complexType name="RiskUpdateReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="FraudUpdateReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="CaseManagementActionReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="RuleResultItem"> + <xsd:sequence> + <xsd:element name="name" type="xsd:string" minOccurs="0"/> + <xsd:element name="decision" type="xsd:string" minOccurs="0"/> + <xsd:element name="evaluation" type="xsd:string" minOccurs="0"/> + <xsd:element name="ruleID" type="xsd:integer" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="RuleResultItems"> + <xsd:sequence> + <xsd:element name="ruleResultItem" type="tns:RuleResultItem" minOccurs="0" maxOccurs="1000"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="DecisionReply"> + <xsd:sequence> + <xsd:element name="casePriority" type="xsd:integer" minOccurs="0"/> + <xsd:element name="activeProfileReply" type="tns:ProfileReply" minOccurs="0"/> + <xsd:element name="velocityInfoCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="additionalFields" type="tns:AdditionalFields" minOccurs="0" maxOccurs="1" /> + <xsd:element name="providerFields" type="tns:ProviderFields" minOccurs="0" maxOccurs="1" /> + <xsd:element name="morphingElement" type="tns:MorphingElement" minOccurs="0" maxOccurs="1" /> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="ProviderFields"> + <xsd:sequence> + <xsd:element name="provider" type="tns:Provider" minOccurs="0" maxOccurs="30"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="Provider"> + <xsd:sequence> + <xsd:element name="name" type="xsd:string" minOccurs="1" maxOccurs="1"/> + <xsd:element name="field" type="tns:ProviderField" minOccurs="0" maxOccurs="500"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="ProviderField"> + <xsd:sequence> + <xsd:element name="name" type="xsd:string" minOccurs="1" maxOccurs="1"/> + <xsd:element name="value" type="xsd:string" minOccurs="1" maxOccurs="1"/> + </xsd:sequence> + </xsd:complexType> + <!-- DME --> + <xsd:complexType name="AdditionalFields"> + <xsd:sequence> + <xsd:element name="field" type="tns:Field" minOccurs="0" maxOccurs="3000"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="Field"> + <xsd:sequence> + <xsd:element name="provider" type="xsd:string" minOccurs="1" maxOccurs="1"/> + <xsd:element name="name" type="xsd:string" minOccurs="1" maxOccurs="1"/> + <xsd:element name="value" type="xsd:string" minOccurs="1" maxOccurs="1"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="MorphingElement"> + <xsd:sequence> + <xsd:element name="element" type="tns:Element" minOccurs="0" maxOccurs="1000"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="Element"> + <xsd:sequence> + <xsd:element name="infoCode" type="xsd:string" minOccurs="1" maxOccurs="1"/> + <xsd:element name="fieldName" type="xsd:string" minOccurs="1" maxOccurs="1"/> + <xsd:element name="count" type="xsd:integer" minOccurs="1" maxOccurs="1"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="DMEReply"> + <xsd:sequence> + <xsd:element name="eventType" type="xsd:string" minOccurs="0"/> + <xsd:element name="eventInfo" type="xsd:string" minOccurs="0"/> + <xsd:element name="eventHotlistInfo" type="xsd:string" minOccurs="0"/> + <xsd:element name="eventPolicy" type="xsd:string" minOccurs="0"/> + <xsd:element name="eventVelocityInfoCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="additionalFields" type="tns:AdditionalFields" minOccurs="0" maxOccurs="1" /> + <xsd:element name="morphingElement" type="tns:MorphingElement" minOccurs="0" maxOccurs="1" /> + <xsd:element name="cardBin" type="xsd:string" minOccurs="0"/> + <xsd:element name="binCountry" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardAccountType" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardScheme" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardIssuer" type="xsd:string" minOccurs="0"/> + <xsd:element name="providerFields" type="tns:ProviderFields" minOccurs="0" maxOccurs="1"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="ProfileReply"> + <xsd:sequence> + <xsd:element name="selectedBy" type="xsd:string" minOccurs="0"/> + <xsd:element name="name" type="xsd:string" minOccurs="0"/> + <xsd:element name="destinationQueue" type="xsd:string" minOccurs="0"/> + <xsd:element name="profileScore" type="xsd:string" minOccurs="0"/> + <xsd:element name="rulesTriggered" type="tns:RuleResultItems" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="CCDCCReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="dccSupported" type="tns:boolean" minOccurs="0"/> + <xsd:element name="validHours" type="xsd:string" minOccurs="0"/> + <xsd:element name="marginRatePercentage" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="CCDCCUpdateReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="ChinaPaymentReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="requestDateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="currency" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="formData" type="xsd:string" minOccurs="0"/> + <xsd:element name="verifyFailure" type="xsd:string" minOccurs="0"/> + <xsd:element name="verifyInProcess" type="xsd:string" minOccurs="0"/> + <xsd:element name="verifySuccess" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="ChinaRefundReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="requestDateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="currency" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="BoletoPaymentReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="requestDateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="boletoNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="expirationDate" type="xsd:string" minOccurs="0"/> + <xsd:element name="url" type="xsd:string" minOccurs="0"/> + <xsd:element name="avsCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="avsCodeRaw" type="xsd:string" minOccurs="0"/> + <xsd:element name="barCodeNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="assignor" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="APInitiateReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="merchantURL" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="dateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="signature" type="xsd:string" minOccurs="0"/> + <xsd:element name="publicKey" type="xsd:string" minOccurs="0"/> + <xsd:element name="paymentStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="responseCode" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="APCheckStatusReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="paymentStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorTradeNo" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorTransactionID" type="xsd:string" minOccurs="0"/> + <xsd:element name="ibanSuffix" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="dateTime" type="tns:dateTime" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <!-- Vme Reseller Reply--> + + <xsd:complexType name="SellerProtection"> + <xsd:sequence> + <xsd:element name="eligibility" type="xsd:string" minOccurs="0" /> + <xsd:element name="type" type="xsd:string" minOccurs="0" /> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="APReply"> + <xsd:sequence> + <xsd:element name="orderID" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardGroup" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardType" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardNumberSuffix" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardExpirationMonth" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardExpirationYear" type="xsd:string" minOccurs="0"/> + <xsd:element name="avsCodeRaw" type="xsd:string" minOccurs="0"/> + <xsd:element name="purchaseID" type="xsd:string" minOccurs="0"/> + <xsd:element name="productID" type="xsd:string" minOccurs="0"/> + <xsd:element name="productDescription" type="xsd:string" minOccurs="0"/> + <xsd:element name="shippingAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="handlingAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="shippingHandlingAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="additionalAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="taxAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="subtotalAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="totalPurchaseAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="giftWrapAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="discountAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardNumberPrefix" type="xsd:string" minOccurs="0"/> + <xsd:element name="riskIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="merchantUUID" type="xsd:string" minOccurs="0"/> + <xsd:element name="merchantSiteID" type="xsd:string" minOccurs="0"/> + <xsd:element name="transactionExpirationDate" type="xsd:string" minOccurs="0"/> + <xsd:element name="sellerProtection" type="tns:SellerProtection" minOccurs="0" /> + <xsd:element name="processorFraudDecision" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorFraudDecisionReason" type="xsd:string" minOccurs="0"/> + <xsd:element name="customerID" type="xsd:string" minOccurs="0" /> + <xsd:element name="billingAgreementID" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerID" type="xsd:string" minOccurs="0"/> + <xsd:element name="fundingSource" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <!-- AP Auth Service --> + <xsd:complexType name="APAuthReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="transactionID" type="xsd:string" minOccurs="0"/> + <xsd:element name="status" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="dateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="providerResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="paymentStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="responseCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="authorizationCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="merchantURL" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorTransactionID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <!-- End of AP Auth Service --> + <!-- AP Auth Reversal Service --> + <xsd:complexType name="APAuthReversalReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="transactionID" type="xsd:string" minOccurs="0"/> + <xsd:element name="status" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="dateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="providerResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="paymentStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="responseCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorTransactionID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <!-- End of AP Auth Reversal Service --> + <!-- AP Capture Service --> + <xsd:complexType name="APCaptureReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="processorTransactionID" type="xsd:string" minOccurs="0"/> + <xsd:element name="status" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="dateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="providerResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="paymentStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="responseCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorTransactionFee" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <!-- End of AP Capture Service --> + <!-- AP Options Service --> + <xsd:complexType name="APOptionsReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="responseCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="offset" type="xsd:string" minOccurs="0"/> + <xsd:element name="count" type="xsd:string" minOccurs="0"/> + <xsd:element name="totalCount" type="xsd:string" minOccurs="0"/> + <xsd:element name="option" type="tns:APOptionsOption" minOccurs="0" maxOccurs="250"/> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="APOptionsOption"> + <xsd:sequence> + <xsd:element name="id" type="xsd:string" minOccurs="0" /> + <xsd:element name="name" type="xsd:string" minOccurs="0" /> + </xsd:sequence> + <xsd:attribute name="data" type="xsd:integer" use="optional"/> + </xsd:complexType> + + + <!-- End of Options Service --> + <!-- AP Refund Service --> + <xsd:complexType name="APRefundReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="transactionID" type="xsd:string" minOccurs="0"/> + <xsd:element name="status" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="dateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="returnRef" type="xsd:string" minOccurs="0"/> + <xsd:element name="providerResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorTransactionID" type="xsd:string" minOccurs="0"/> + <xsd:element name="paymentStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="responseCode" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <!-- End of AP Refund Service --> + <!-- AP Sale Service --> + <xsd:complexType name="APSaleReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="paymentStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="responseCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="merchantURL" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorTransactionID" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorTransactionFee" type="xsd:string" minOccurs="0"/> + <xsd:element name="amount" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="exchangeRate" type="xsd:string" minOccurs="0"/> + <xsd:element name="foreignCurrency" type="xsd:string" minOccurs="0"/> + <xsd:element name="foreignAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="discountAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="dateTime" type="tns:dateTime" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <!-- End of AP Sale Service --> + + <!-- AP CheckOutDetailsReply Service --> + <xsd:complexType name="APCheckOutDetailsReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="status" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="dateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="providerResponse" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <!-- End of AP CheckOutDetailsReply Service --> + <xsd:complexType name="APTransactionDetailsReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="transactionID" type="xsd:string" minOccurs="0"/> + <xsd:element name="status" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="dateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="providerResponse" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <!-- AP ConfirmPurchase Service --> + <xsd:complexType name="APConfirmPurchaseReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="status" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="dateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="providerResponse" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <!-- End of AP ConfirmPurchase Service --> + <xsd:complexType name="APSessionsReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="responseCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="merchantURL" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorToken" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorTransactionID" type="xsd:string" minOccurs="0"/> + <xsd:element name="amount" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="status" type="xsd:string" minOccurs="0"/> + <xsd:element name="dateTime" type="tns:dateTime" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="CCCheckStatusReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="paymentStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="authorizationCode" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="ReplyMessage"> + <xsd:sequence> + <xsd:element name="merchantReferenceCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="requestID" type="xsd:string"/> + <xsd:element name="decision" type="xsd:string"/> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="missingField" type="xsd:string" minOccurs="0" maxOccurs="1000"/> + <xsd:element name="invalidField" type="xsd:string" minOccurs="0" maxOccurs="1000"/> + <xsd:element name="requestToken" type="xsd:string"/> + <xsd:element name="purchaseTotals" type="tns:PurchaseTotals" minOccurs="0"/> + <xsd:element name="deniedPartiesMatch" type="tns:DeniedPartiesMatch" minOccurs="0" maxOccurs="100"/> + <xsd:element name="ccAuthReply" type="tns:CCAuthReply" minOccurs="0"/> + <xsd:element name="octReply" type="tns:OCTReply" minOccurs="0"/> + <xsd:element name="verificationReply" type="tns:VerificationReply" minOccurs="0"/> + <xsd:element name="ccSaleReply" type="tns:CCSaleReply" minOccurs="0"/> + <xsd:element name="ccSaleCreditReply" type="tns:CCSaleCreditReply" minOccurs="0"/> + <xsd:element name="ccSaleReversalReply" type="tns:CCSaleReversalReply" minOccurs="0"/> + <xsd:element name="ccIncrementalAuthReply" type="tns:CCIncrementalAuthReply" minOccurs="0"/> + <xsd:element name="serviceFeeCalculateReply" type="tns:ServiceFeeCalculateReply" minOccurs="0"/> + <xsd:element name="ccCaptureReply" type="tns:CCCaptureReply" minOccurs="0"/> + <xsd:element name="ccCreditReply" type="tns:CCCreditReply" minOccurs="0"/> + <xsd:element name="ccAuthReversalReply" type="tns:CCAuthReversalReply" minOccurs="0"/> + <xsd:element name="ccAutoAuthReversalReply" type="tns:CCAutoAuthReversalReply" minOccurs="0"/> + <xsd:element name="ccDCCReply" type="tns:CCDCCReply" minOccurs="0"/> + <xsd:element name="ccDCCUpdateReply" type="tns:CCDCCUpdateReply" minOccurs="0"/> + <xsd:element name="ecDebitReply" type="tns:ECDebitReply" minOccurs="0"/> + <xsd:element name="ecCreditReply" type="tns:ECCreditReply" minOccurs="0"/> + <xsd:element name="ecAuthenticateReply" type="tns:ECAuthenticateReply" minOccurs="0"/> + <xsd:element name="payerAuthEnrollReply" type="tns:PayerAuthEnrollReply" minOccurs="0"/> + <xsd:element name="payerAuthValidateReply" type="tns:PayerAuthValidateReply" minOccurs="0"/> + <xsd:element name="taxReply" type="tns:TaxReply" minOccurs="0"/> + <xsd:element name="encryptedPayment" type="tns:EncryptedPayment" minOccurs="0" /> + <xsd:element name="encryptPaymentDataReply" type="tns:EncryptPaymentDataReply" minOccurs="0"/> + <xsd:element name="dmeReply" type="tns:DMEReply" minOccurs="0"/> + <xsd:element name="afsReply" type="tns:AFSReply" minOccurs="0"/> + <xsd:element name="davReply" type="tns:DAVReply" minOccurs="0"/> + <xsd:element name="exportReply" type="tns:ExportReply" minOccurs="0"/> + <xsd:element name="fxRatesReply" type="tns:FXRatesReply" minOccurs="0"/> + <xsd:element name="bankTransferReply" type="tns:BankTransferReply" minOccurs="0"/> + <xsd:element name="bankTransferRefundReply" type="tns:BankTransferRefundReply" minOccurs="0"/> + <xsd:element name="bankTransferRealTimeReply" type="tns:BankTransferRealTimeReply" minOccurs="0"/> + <xsd:element name="directDebitMandateReply" type="tns:DirectDebitMandateReply" minOccurs="0"/> + <xsd:element name="directDebitReply" type="tns:DirectDebitReply" minOccurs="0"/> + <xsd:element name="directDebitValidateReply" type="tns:DirectDebitValidateReply" minOccurs="0"/> + <xsd:element name="directDebitRefundReply" type="tns:DirectDebitRefundReply" minOccurs="0"/> + <xsd:element name="paySubscriptionCreateReply" type="tns:PaySubscriptionCreateReply" minOccurs="0"/> + <xsd:element name="paySubscriptionUpdateReply" type="tns:PaySubscriptionUpdateReply" minOccurs="0"/> + <xsd:element name="paySubscriptionEventUpdateReply" type="tns:PaySubscriptionEventUpdateReply" minOccurs="0"/> + <xsd:element name="paySubscriptionRetrieveReply" type="tns:PaySubscriptionRetrieveReply" minOccurs="0"/> + <xsd:element name="paySubscriptionDeleteReply" type="tns:PaySubscriptionDeleteReply" minOccurs="0"/> + <xsd:element name="payPalPaymentReply" type="tns:PayPalPaymentReply" minOccurs="0"/> + <xsd:element name="payPalCreditReply" type="tns:PayPalCreditReply" minOccurs="0"/> + <xsd:element name="voidReply" type="tns:VoidReply" minOccurs="0"/> + <xsd:element name="pinlessDebitReply" type="tns:PinlessDebitReply" minOccurs="0"/> + <xsd:element name="pinlessDebitValidateReply" type="tns:PinlessDebitValidateReply" minOccurs="0"/> + <xsd:element name="pinlessDebitReversalReply" type="tns:PinlessDebitReversalReply" minOccurs="0"/> + <xsd:element name="payPalButtonCreateReply" type="tns:PayPalButtonCreateReply" minOccurs="0"/> + <xsd:element name="payPalPreapprovedPaymentReply" type="tns:PayPalPreapprovedPaymentReply" minOccurs="0"/> + <xsd:element name="payPalPreapprovedUpdateReply" type="tns:PayPalPreapprovedUpdateReply" minOccurs="0"/> + <xsd:element name="riskUpdateReply" type="tns:RiskUpdateReply" minOccurs="0"/> + <xsd:element name="fraudUpdateReply" type="tns:FraudUpdateReply" minOccurs="0"/> + <xsd:element name="caseManagementActionReply" type="tns:CaseManagementActionReply" minOccurs="0"/> + <xsd:element name="decisionReply" type="tns:DecisionReply" minOccurs="0"/> + <xsd:element name="payPalRefundReply" type="tns:PayPalRefundReply" minOccurs="0"/> + <xsd:element name="payPalAuthReversalReply" type="tns:PayPalAuthReversalReply" minOccurs="0"/> + <xsd:element name="payPalDoCaptureReply" type="tns:PayPalDoCaptureReply" minOccurs="0"/> + <xsd:element name="payPalEcDoPaymentReply" type="tns:PayPalEcDoPaymentReply" minOccurs="0"/> + <xsd:element name="payPalEcGetDetailsReply" type="tns:PayPalEcGetDetailsReply" minOccurs="0"/> + <xsd:element name="payPalEcSetReply" type="tns:PayPalEcSetReply" minOccurs="0"/> + <xsd:element name="payPalAuthorizationReply" type="tns:PayPalAuthorizationReply" minOccurs="0"/> + <xsd:element name="payPalEcOrderSetupReply" type="tns:PayPalEcOrderSetupReply" minOccurs="0"/> + <xsd:element name="payPalUpdateAgreementReply" type="tns:PayPalUpdateAgreementReply" minOccurs="0"/> + <xsd:element name="payPalCreateAgreementReply" type="tns:PayPalCreateAgreementReply" minOccurs="0"/> + <xsd:element name="payPalDoRefTransactionReply" type="tns:PayPalDoRefTransactionReply" minOccurs="0"/> + <xsd:element name="chinaPaymentReply" type="tns:ChinaPaymentReply" minOccurs="0"/> + <xsd:element name="chinaRefundReply" type="tns:ChinaRefundReply" minOccurs="0"/> + <xsd:element name="boletoPaymentReply" type="tns:BoletoPaymentReply" minOccurs="0"/> + <xsd:element name="pinDebitPurchaseReply" type="tns:PinDebitPurchaseReply" minOccurs="0"/> + <xsd:element name="pinDebitCreditReply" type="tns:PinDebitCreditReply" minOccurs="0"/> + <xsd:element name="pinDebitReversalReply" type="tns:PinDebitReversalReply" minOccurs="0"/> + <xsd:element name="apInitiateReply" type="tns:APInitiateReply" minOccurs="0"/> + <xsd:element name="apCheckStatusReply" type="tns:APCheckStatusReply" minOccurs="0"/> + <xsd:element name="receiptNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="additionalData" type="xsd:string" minOccurs="0"/> + <xsd:element name="solutionProviderTransactionID" type="xsd:string" minOccurs="0"/> + <xsd:element name="apReply" type="tns:APReply" minOccurs="0"/> + <xsd:element name="shipTo" type="tns:ShipTo" minOccurs="0" /> + <xsd:element name="billTo" type="tns:BillTo" minOccurs="0" /> + <xsd:element name="apAuthReply" type="tns:APAuthReply" minOccurs="0"/> + <xsd:element name="apSessionsReply" type="tns:APSessionsReply" minOccurs="0" /> + <xsd:element name="apAuthReversalReply" type="tns:APAuthReversalReply" minOccurs="0"/> + <xsd:element name="apCaptureReply" type="tns:APCaptureReply" minOccurs="0"/> + <xsd:element name="apOptionsReply" type="tns:APOptionsReply" minOccurs="0"/> + <xsd:element name="apRefundReply" type="tns:APRefundReply" minOccurs="0"/> + <xsd:element name="apSaleReply" type="tns:APSaleReply" minOccurs="0"/> + <xsd:element name="apCheckoutDetailsReply" type="tns:APCheckOutDetailsReply" minOccurs="0"/> + <xsd:element name="apTransactionDetailsReply" type="tns:APTransactionDetailsReply" minOccurs="0"/> + <xsd:element name="apConfirmPurchaseReply" type="tns:APConfirmPurchaseReply" minOccurs="0"/> + <xsd:element name="promotion" type="tns:Promotion" minOccurs="0"/> + <xsd:element name="promotionGroup" type="tns:PromotionGroupReply" minOccurs="0" maxOccurs="100"/> + <xsd:element name="payPalGetTxnDetailsReply" type="tns:PayPalGetTxnDetailsReply" minOccurs="0"/> + <xsd:element name="payPalTransactionSearchReply" type="tns:PayPalTransactionSearchReply" minOccurs="0"/> + <xsd:element name="emvReply" type="tns:EmvReply" minOccurs="0" /> + <xsd:element name="originalTransaction" type="tns:OriginalTransaction" minOccurs="0" /> + <xsd:element name="hostedDataCreateReply" type="tns:HostedDataCreateReply" minOccurs="0" /> + <xsd:element name="hostedDataRetrieveReply" type="tns:HostedDataRetrieveReply" minOccurs="0" /> + <xsd:element name="salesSlipNumber" type="xsd:string" minOccurs="0" /> + <xsd:element name="additionalProcessorResponse" type="xsd:string" minOccurs="0" /> + <xsd:element name="jpo" type="tns:JPO" minOccurs="0" /> + <xsd:element name="card" type="tns:Card" minOccurs="0" /> + <xsd:element name="paymentNetworkToken" type="tns:PaymentNetworkToken" minOccurs="0"/> + <xsd:element name="vcReply" type="tns:VCReply" minOccurs="0" /> + <xsd:element name="decryptVisaCheckoutDataReply" type="tns:DecryptVisaCheckoutDataReply" minOccurs="0"/> + <xsd:element name="getVisaCheckoutDataReply" type="tns:GetVisaCheckoutDataReply" minOccurs="0"/> + <xsd:element name="binLookupReply" type="tns:BinLookupReply" minOccurs="0"/> + <xsd:element name="issuerMessage" type="xsd:string" minOccurs="0" /> + <xsd:element name="token" type="tns:Token" minOccurs="0" /> + <xsd:element name="issuer" type="tns:issuer" minOccurs="0" /> + <xsd:element name="recipient" type="tns:Recipient" minOccurs="0"/> + <xsd:element name="feeProgramIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="installment" type="tns:Installment" minOccurs="0" /> + <xsd:element name="paymentAccountReference" type="xsd:string" minOccurs="0"/> + <xsd:element name="authIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="ucaf" type="tns:UCAF" minOccurs="0"/> + <xsd:element name="network" type="tns:Network" minOccurs="0" maxOccurs="100"/> + <xsd:element name="invoiceHeader" type="tns:InvoiceHeader" minOccurs="0" /> + <xsd:element name="apOrderReply" type="tns:APOrderReply" minOccurs="0" /> + <xsd:element name="apCancelReply" type="tns:APCancelReply" minOccurs="0" /> + <xsd:element name="apBillingAgreementReply" type="tns:APBillingAgreementReply" minOccurs="0" /> + <xsd:element name="customerVerificationStatus" type="xsd:string" minOccurs="0" /> + <xsd:element name="personalID" type="tns:PersonalID" minOccurs="0" /> + <xsd:element name="acquirerMerchantNumber" type="xsd:string" minOccurs="0" /> + <xsd:element name="pos" type="tns:Pos" minOccurs="0" /> + <xsd:element name="issuerMessageAction" type="xsd:string" minOccurs="0" /> + <xsd:element name="customerID" type="xsd:string" minOccurs="0" /> + + <xsd:element name="routing" type="tns:Routing" minOccurs="0"/> + <xsd:element name="transactionLocalDateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="apCreateMandateReply" type="tns:APCreateMandateReply" minOccurs="0"/> + <xsd:element name="apMandateStatusReply" type="tns:APMandateStatusReply" minOccurs="0"/> + <xsd:element name="apUpdateMandateReply" type="tns:APUpdateMandateReply" minOccurs="0"/> + <xsd:element name="apImportMandateReply" type="tns:APImportMandateReply" minOccurs="0"/> + <xsd:element name="apRevokeMandateReply" type="tns:APRevokeMandateReply" minOccurs="0"/> + <xsd:element name="getMasterpassDataReply" type="tns:GetMasterpassDataReply" minOccurs="0"/> + <xsd:element name="paymentNetworkMerchantID" type="xsd:string" minOccurs="0"/> + <xsd:element name="wallet" type="tns:Wallet" minOccurs="0" /> + <xsd:element name="cashbackAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="giftCard" type="tns:GiftCard" minOccurs="0" /> + <xsd:element name="giftCardActivationReply" type="tns:GiftCardActivationReply" minOccurs="0"/> + <xsd:element name="giftCardBalanceInquiryReply" type="tns:GiftCardBalanceInquiryReply" minOccurs="0"/> + <xsd:element name="giftCardRedemptionReply" type="tns:GiftCardRedemptionReply" minOccurs="0"/> + <xsd:element name="giftCardVoidReply" type="tns:GiftCardVoidReply" minOccurs="0"/> + <xsd:element name="giftCardReversalReply" type="tns:GiftCardReversalReply" minOccurs="0"/> + <xsd:element name="ccCheckStatusReply" type="tns:CCCheckStatusReply" minOccurs="0"/> + <xsd:element name="reserved" type="tns:ReplyReserved" minOccurs="0"/> + + <!--ReplyReserved should always be the last element in the xsd, new elements should be added before this--> + </xsd:sequence> + </xsd:complexType> + <xsd:element name="requestMessage" type="tns:RequestMessage"> + </xsd:element> + <xsd:element name="replyMessage" type="tns:ReplyMessage"> + <xsd:unique name="unique-tax-item-id"> + <xsd:selector xpath="tns:taxReplyItem"/> + <xsd:field xpath="@id"/> + </xsd:unique> + </xsd:element> + <xsd:element name="nvpRequest" type="xsd:string"/> + <xsd:element name="nvpReply" type="xsd:string"/> + <!-- used in SOAP faults --> + <xsd:complexType name="FaultDetails"> + <xsd:sequence> + <xsd:element name="requestID" type="xsd:string"/> + </xsd:sequence> + </xsd:complexType> + <xsd:element name="faultDetails" type="tns:FaultDetails"/> + <xsd:complexType name="AirlineData"> + <xsd:sequence> + <xsd:element name="agentCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="agentName" type="xsd:string" minOccurs="0"/> + <xsd:element name="ticketIssuerCity" type="xsd:string" minOccurs="0"/> + <xsd:element name="ticketIssuerState" type="xsd:string" minOccurs="0"/> + <xsd:element name="ticketIssuerPostalCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="ticketIssuerCountry" type="xsd:string" minOccurs="0"/> + <xsd:element name="ticketIssuerAddress" type="xsd:string" minOccurs="0"/> + <xsd:element name="ticketIssuerCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="ticketIssuerName" type="xsd:string" minOccurs="0"/> + <xsd:element name="ticketNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="checkDigit" type="xsd:integer" minOccurs="0"/> + <xsd:element name="restrictedTicketIndicator" type="xsd:integer" minOccurs="0"/> + <xsd:element name="transactionType" type="xsd:string" minOccurs="0"/> + <xsd:element name="extendedPaymentCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="carrierName" type="xsd:string" minOccurs="0"/> + <xsd:element name="passengerName" type="xsd:string" minOccurs="0"/> + <xsd:element name="passenger" type="tns:Passenger" minOccurs="0" maxOccurs="1000"/> + <xsd:element name="customerCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="documentType" type="xsd:string" minOccurs="0"/> + <xsd:element name="documentNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="documentNumberOfParts" type="xsd:string" minOccurs="0"/> + <xsd:element name="invoiceNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="invoiceDate" type="xsd:string" minOccurs="0"/> + <xsd:element name="chargeDetails" type="xsd:string" minOccurs="0"/> + <xsd:element name="bookingReference" type="xsd:string" minOccurs="0"/> + <xsd:element name="totalFee" type="tns:amount" minOccurs="0"/> + <xsd:element name="clearingSequence" type="xsd:string" minOccurs="0"/> + <xsd:element name="clearingCount" type="xsd:integer" minOccurs="0"/> + <xsd:element name="totalClearingAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="leg" type="tns:Leg" minOccurs="0" maxOccurs="1000"/> + <xsd:element name="numberOfPassengers" type="xsd:string" minOccurs="0"/> + <xsd:element name="reservationSystem" type="xsd:string" minOccurs="0"/> + <xsd:element name="processIdentifier" type="xsd:string" minOccurs="0"/> + <xsd:element name="iataNumericCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="ticketIssueDate" type="xsd:string" minOccurs="0"/> + <xsd:element name="electronicTicket" type="xsd:string" minOccurs="0"/> + <xsd:element name="originalTicketNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="purchaseType" type="xsd:string" minOccurs="0"/> + <xsd:element name="creditReasonIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="ticketUpdateIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="planNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="arrivalDate" type="xsd:string" minOccurs="0"/> + <xsd:element name="ticketRestrictionText" type="xsd:string" minOccurs="0"/> + <xsd:element name="exchangeTicketAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="exchangeTicketFee" type="tns:amount" minOccurs="0"/> + <xsd:element name="journeyType" type="xsd:string" minOccurs="0"/> + <xsd:element name="boardingFee" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="Leg"> + <xsd:sequence> + <xsd:element name="carrierCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="flightNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="originatingAirportCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="class" type="xsd:string" minOccurs="0"/> + <xsd:element name="stopoverCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="departureDate" type="xsd:string" minOccurs="0"/> + <xsd:element name="destination" type="xsd:string" minOccurs="0"/> + <xsd:element name="fareBasis" type="xsd:string" minOccurs="0"/> + <xsd:element name="departTax" type="xsd:string" minOccurs="0"/> + <xsd:element name="conjunctionTicket" type="xsd:string" minOccurs="0"/> + <xsd:element name="exchangeTicket" type="xsd:string" minOccurs="0"/> + <xsd:element name="couponNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="departureTime" type="xsd:string" minOccurs="0"/> + <xsd:element name="departureTimeSegment" type="xsd:string" minOccurs="0"/> + <xsd:element name="arrivalTime" type="xsd:string" minOccurs="0"/> + <xsd:element name="arrivalTimeSegment" type="xsd:string" minOccurs="0"/> + <xsd:element name="endorsementsRestrictions" type="xsd:string" minOccurs="0"/> + <xsd:element name="fare" type="xsd:string" minOccurs="0"/> + <xsd:element name="fee" type="xsd:string" minOccurs="0"/> + <xsd:element name="tax" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="id" type="xsd:integer" use="required"/> + </xsd:complexType> + <xsd:complexType name="AncillaryData"> + <xsd:sequence> + <xsd:element name="ticketNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="passengerName" type="xsd:string" minOccurs="0"/> + <xsd:element name="connectedTicketNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="creditReasonIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="service" type="tns:Service" minOccurs="0" maxOccurs="1000"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="Service"> + <xsd:sequence> + <xsd:element name="categoryCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="subcategoryCode" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="id" type="xsd:integer" use="required"/> + </xsd:complexType> + <xsd:complexType name="LodgingData"> + <xsd:sequence> + <xsd:element name="checkInDate" type="xsd:string" minOccurs="0"/> + <xsd:element name="checkOutDate" type="xsd:string" minOccurs="0"/> + <xsd:element name="dailyRoomRate1" type="tns:amount" minOccurs="0"/> + <xsd:element name="dailyRoomRate2" type="tns:amount" minOccurs="0"/> + <xsd:element name="dailyRoomRate3" type="tns:amount" minOccurs="0"/> + <xsd:element name="roomNights1" type="xsd:integer" minOccurs="0"/> + <xsd:element name="roomNights2" type="xsd:integer" minOccurs="0"/> + <xsd:element name="roomNights3" type="xsd:integer" minOccurs="0"/> + <xsd:element name="guestSmokingPreference" type="xsd:string" minOccurs="0"/> + <xsd:element name="numberOfRoomsBooked" type="xsd:integer" minOccurs="0"/> + <xsd:element name="numberOfGuests" type="xsd:integer" minOccurs="0"/> + <xsd:element name="roomBedType" type="xsd:string" minOccurs="0"/> + <xsd:element name="roomTaxElements" type="xsd:string" minOccurs="0"/> + <xsd:element name="roomRateType" type="xsd:string" minOccurs="0"/> + <xsd:element name="guestName" type="xsd:string" minOccurs="0"/> + <xsd:element name="customerServicePhoneNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="corporateClientCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="promotionalCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="additionalCoupon" type="xsd:string" minOccurs="0"/> + <xsd:element name="roomLocation" type="xsd:string" minOccurs="0"/> + <xsd:element name="specialProgramCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="tax" type="tns:amount" minOccurs="0"/> + <xsd:element name="prepaidCost" type="tns:amount" minOccurs="0"/> + <xsd:element name="foodAndBeverageCost" type="tns:amount" minOccurs="0"/> + <xsd:element name="roomTax" type="tns:amount" minOccurs="0"/> + <xsd:element name="adjustmentAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="phoneCost" type="tns:amount" minOccurs="0"/> + <xsd:element name="restaurantCost" type="tns:amount" minOccurs="0"/> + <xsd:element name="roomServiceCost" type="tns:amount" minOccurs="0"/> + <xsd:element name="miniBarCost" type="tns:amount" minOccurs="0"/> + <xsd:element name="laundryCost" type="tns:amount" minOccurs="0"/> + <xsd:element name="miscellaneousCost" type="tns:amount" minOccurs="0"/> + <xsd:element name="giftShopCost" type="tns:amount" minOccurs="0"/> + <xsd:element name="movieCost" type="tns:amount" minOccurs="0"/> + <xsd:element name="healthClubCost" type="tns:amount" minOccurs="0"/> + <xsd:element name="valetParkingCost" type="tns:amount" minOccurs="0"/> + <xsd:element name="cashDisbursementCost" type="tns:amount" minOccurs="0"/> + <xsd:element name="nonRoomCost" type="tns:amount" minOccurs="0"/> + <xsd:element name="businessCenterCost" type="tns:amount" minOccurs="0"/> + <xsd:element name="loungeBarCost" type="tns:amount" minOccurs="0"/> + <xsd:element name="transportationCost" type="tns:amount" minOccurs="0"/> + <xsd:element name="gratuityCost" type="tns:amount" minOccurs="0"/> + <xsd:element name="conferenceRoomCost" type="tns:amount" minOccurs="0"/> + <xsd:element name="audioVisualCost" type="tns:amount" minOccurs="0"/> + <xsd:element name="banquetCost" type="tns:amount" minOccurs="0"/> + <xsd:element name="internetAccessCost" type="tns:amount" minOccurs="0"/> + <xsd:element name="earlyCheckOutCost" type="tns:amount" minOccurs="0"/> + <xsd:element name="nonRoomTax" type="tns:amount" minOccurs="0"/> + <xsd:element name="travelAgencyCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="travelAgencyName" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="Pos"> + <xsd:sequence> + <xsd:element name="entryMode" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardPresent" type="xsd:string" minOccurs="0"/> + <xsd:element name="terminalCapability" type="xsd:string" minOccurs="0"/> + <xsd:element name="trackData" type="xsd:string" minOccurs="0"/> + <xsd:element name="terminalID" type="xsd:string" minOccurs="0"/> + <xsd:element name="terminalType" type="xsd:string" minOccurs="0"/> + <xsd:element name="terminalLocation" type="xsd:string" minOccurs="0"/> + <xsd:element name="transactionSecurity" type="xsd:string" minOccurs="0"/> + <xsd:element name="catLevel" type="xsd:string" minOccurs="0"/> + <xsd:element name="conditionCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="environment" type="xsd:string" minOccurs="0"/> + <xsd:element name="paymentData" type="xsd:string" minOccurs="0"/> + <xsd:element name="deviceReaderData" type="xsd:string" minOccurs="0"/> + <xsd:element name="encryptionAlgorithm" type="xsd:string" minOccurs="0"/> + <xsd:element name="encodingMethod" type="xsd:string" minOccurs="0"/> + <xsd:element name="deviceID" type="xsd:string" minOccurs="0"/> + <xsd:element name="serviceCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="terminalIDAlternate" type="xsd:string" minOccurs="0" /> + <xsd:element name="terminalCompliance" type="xsd:integer" minOccurs="0" /> + <xsd:element name="terminalCardCaptureCapability" type="xsd:string" minOccurs="0" /> + <xsd:element name="terminalOutputCapability" type="xsd:string" minOccurs="0" /> + <xsd:element name="terminalPINcapability" type="xsd:string" minOccurs="0" /> + <xsd:element name="terminalCVMcapabilities_0" type="xsd:string" minOccurs="0" /> + <xsd:element name="terminalCVMcapabilities_1" type="xsd:string" minOccurs="0" /> + <xsd:element name="terminalCVMcapabilities_2" type="xsd:string" minOccurs="0" /> + <xsd:element name="terminalInputCapabilities_0" type="xsd:string" minOccurs="0" /> + <xsd:element name="terminalInputCapabilities_1" type="xsd:string" minOccurs="0" /> + <xsd:element name="terminalInputCapabilities_2" type="xsd:string" minOccurs="0" /> + <xsd:element name="terminalInputCapabilities_3" type="xsd:string" minOccurs="0" /> + <xsd:element name="terminalInputCapabilities_4" type="xsd:string" minOccurs="0" /> + <xsd:element name="terminalInputCapabilities_5" type="xsd:string" minOccurs="0" /> + <xsd:element name="terminalInputCapabilities_6" type="xsd:string" minOccurs="0" /> + <xsd:element name="terminalSerialNumber" type="xsd:string" minOccurs="0" /> + <xsd:element name="storeAndForwardIndicator" type="xsd:string" minOccurs="0" /> + <xsd:element name="panEntryMode" type="xsd:string" minOccurs="0" /> + <xsd:element name="endlessAisleTransactionIndicator" type="tns:boolean" minOccurs="0" /> + <xsd:element name="terminalModel" type="xsd:string" minOccurs="0" /> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="Pin"> + <xsd:sequence> + <xsd:element name="entryCapability" type="xsd:string" minOccurs="0"/> + + + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="EncryptedPayment"> + <xsd:sequence> + <xsd:element name="descriptor" type="xsd:string" minOccurs="0"/> + <xsd:element name="data" type="xsd:string" minOccurs="0"/> + <xsd:element name="encoding" type="xsd:string" minOccurs="0"/> + <xsd:element name="wrappedKey" type="xsd:string" minOccurs="0"/> + <xsd:element name="referenceID" type="xsd:integer" minOccurs="0"/> + <xsd:element name="errorCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="keySerialNumber" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="Installment"> + <xsd:sequence> + <xsd:element name="sequence" type="xsd:string" minOccurs="0"/> + <xsd:element name="totalCount" type="xsd:string" minOccurs="0"/> + <xsd:element name="totalAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="frequency" type="xsd:string" minOccurs="0"/> + <xsd:element name="amount" type="xsd:string" minOccurs="0"/> + <xsd:element name="planType" type="xsd:string" minOccurs="0"/> + + <xsd:element name="firstInstallmentDate" type="xsd:string" minOccurs="0"/> + <xsd:element name="amountFunded" type="xsd:string" minOccurs="0"/> + <xsd:element name="amountRequestedPercentage" type="xsd:string" minOccurs="0"/> + <xsd:element name="expenses" type="xsd:string" minOccurs="0"/> + <xsd:element name="expensesPercentage" type="xsd:string" minOccurs="0"/> + <xsd:element name="fees" type="xsd:string" minOccurs="0"/> + <xsd:element name="feesPercentage" type="xsd:string" minOccurs="0"/> + <xsd:element name="taxes" type="xsd:string" minOccurs="0"/> + <xsd:element name="taxesPercentage" type="xsd:string" minOccurs="0"/> + <xsd:element name="insurance" type="xsd:string" minOccurs="0"/> + <xsd:element name="insurancePercentage" type="xsd:string" minOccurs="0"/> + <xsd:element name="additionalCosts" type="xsd:string" minOccurs="0"/> + <xsd:element name="additionalCostsPercentage" type="xsd:string" minOccurs="0"/> + <xsd:element name="monthlyInterestRate" type="xsd:string" minOccurs="0"/> + <xsd:element name="annualInterestRate" type="xsd:string" minOccurs="0"/> + <xsd:element name="annualFinancingCost" type="xsd:string" minOccurs="0"/> + <xsd:element name="paymentType" type="xsd:string" minOccurs="0"/> + <xsd:element name="invoiceData" type="xsd:string" minOccurs="0"/> + <xsd:element name="downPayment" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="MDDField"> + <xsd:simpleContent> + <xsd:extension base="xsd:string"> + <xsd:attribute name="id" type="xsd:integer" use="required"/> + </xsd:extension> + </xsd:simpleContent> + </xsd:complexType> + <xsd:complexType name="MerchantDefinedData"> + <xsd:sequence> + <xsd:element name="field1" type="xsd:string" minOccurs="0"/> + <xsd:element name="field2" type="xsd:string" minOccurs="0"/> + <xsd:element name="field3" type="xsd:string" minOccurs="0"/> + <xsd:element name="field4" type="xsd:string" minOccurs="0"/> + <xsd:element name="field5" type="xsd:string" minOccurs="0"/> + <xsd:element name="field6" type="xsd:string" minOccurs="0"/> + <xsd:element name="field7" type="xsd:string" minOccurs="0"/> + <xsd:element name="field8" type="xsd:string" minOccurs="0"/> + <xsd:element name="field9" type="xsd:string" minOccurs="0"/> + <xsd:element name="field10" type="xsd:string" minOccurs="0"/> + <xsd:element name="field11" type="xsd:string" minOccurs="0"/> + <xsd:element name="field12" type="xsd:string" minOccurs="0"/> + <xsd:element name="field13" type="xsd:string" minOccurs="0"/> + <xsd:element name="field14" type="xsd:string" minOccurs="0"/> + <xsd:element name="field15" type="xsd:string" minOccurs="0"/> + <xsd:element name="field16" type="xsd:string" minOccurs="0"/> + <xsd:element name="field17" type="xsd:string" minOccurs="0"/> + <xsd:element name="field18" type="xsd:string" minOccurs="0"/> + <xsd:element name="field19" type="xsd:string" minOccurs="0"/> + <xsd:element name="field20" type="xsd:string" minOccurs="0"/> + <xsd:element name="mddField" type="tns:MDDField" minOccurs="0" maxOccurs="100"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="MerchantSecureData"> + <xsd:sequence> + <xsd:element name="field1" type="xsd:string" minOccurs="0"/> + <xsd:element name="field2" type="xsd:string" minOccurs="0"/> + <xsd:element name="field3" type="xsd:string" minOccurs="0"/> + <xsd:element name="field4" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="ReplyReserved"> + <xsd:sequence> + <xsd:any processContents="skip" minOccurs="0" maxOccurs="999"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="RequestReserved"> + <xsd:sequence> + <xsd:element name="name" type="xsd:string"/> + <xsd:element name="value" type="xsd:string"/> + </xsd:sequence> + </xsd:complexType> + <!-- PayPalGetTxnDetails --> + <xsd:complexType name="PayPalGetTxnDetailsReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="payer" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerId" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerCountry" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerBusiness" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerSalutation" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerFirstname" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerMiddlename" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerLastname" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerSuffix" type="xsd:string" minOccurs="0"/> + <xsd:element name="addressID" type="xsd:string" minOccurs="0"/> + <xsd:element name="addressStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="shipToName" type="xsd:string" minOccurs="0"/> + <xsd:element name="shipToAddress1" type="xsd:string" minOccurs="0"/> + <xsd:element name="shipToAddress2" type="xsd:string" minOccurs="0"/> + <xsd:element name="shipToCity" type="xsd:string" minOccurs="0"/> + <xsd:element name="shipToState" type="xsd:string" minOccurs="0"/> + <xsd:element name="shipToCountry" type="xsd:string" minOccurs="0"/> + <xsd:element name="shipToZip" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerPhone" type="xsd:string" minOccurs="0"/> + <xsd:element name="transactionId" type="xsd:string" minOccurs="0"/> + <xsd:element name="parentTransactionId" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalReceiptId" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalTransactiontype" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalPaymentType" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalOrderTime" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalPaymentGrossAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalFeeAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="currency" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalSettleAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalTaxAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalExchangeRate" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalPaymentStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalPendingReason" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalReasonCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="protectionEligibility" type="xsd:string" minOccurs="0"/> + <xsd:element name="protectionEligibilityType" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalNote" type="xsd:string" minOccurs="0"/> + <xsd:element name="invoiceNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="item" type="tns:Item" minOccurs="0" maxOccurs="1000" /> + <xsd:element name="errorCode" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + + <!-- end of PayPalGetTxnDetails --> + + <!-- PayPalTransactionSearchReply --> + <xsd:complexType name="PayPalTransactionSearchReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="transaction" type="tns:PaypalTransaction" minOccurs="0" maxOccurs="999" /> + <xsd:element name="errorCode" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="PaypalTransaction"> + <xsd:sequence> + <xsd:element name="transactionTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="transactionTimeZone" type="xsd:string" minOccurs="0"/> + <xsd:element name="transactionType" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalPayerOrPayeeEmail" type="xsd:string" minOccurs="0"/> + <xsd:element name="customerDisplayName" type="xsd:string" minOccurs="0"/> + <xsd:element name="transactionID" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalPaymentStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="grandTotalAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="currency" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalFeeAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalNetAmount" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="id" type="xsd:integer" use="optional"/> + </xsd:complexType> + <!-- end of PayPalTransactionSearchReply --> + + <xsd:complexType name="CCDCCUpdateService"> + <xsd:sequence> + <xsd:element name="reason" type="xsd:string" minOccurs="0"/> + <xsd:element name="action" type="xsd:string" minOccurs="0"/> + <xsd:element name="dccRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="captureRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="creditRequestID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <!-- Merchant Descriptor fields for Service Fee. goes into RequestMessage--> + <xsd:complexType name="ServiceFee"> + <xsd:sequence> + <xsd:element name="merchantDescriptor" type="xsd:string" minOccurs="0"/> + <xsd:element name="merchantDescriptorContact" type="xsd:string" minOccurs="0"/> + <xsd:element name="merchantDescriptorState" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <!-- EMV transaction data request/reply start --> + <xsd:complexType name="EmvRequest"> + <xsd:sequence> + <xsd:element name="combinedTags" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardSequenceNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="aidAndDFname" type="xsd:string" minOccurs="0"/> + <xsd:element name="fallback" type="xsd:string" minOccurs="0"/> + <xsd:element name="fallbackCondition" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="EmvReply"> + <xsd:sequence> + <xsd:element name="combinedTags" type="xsd:string" minOccurs="0"/> + <xsd:element name="decryptedRequestTags" type="xsd:string" minOccurs="0"/> + <xsd:element name="chipValidationResults" type="xsd:string" minOccurs="0"/> + <xsd:element name="chipValidationType" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <!-- EMV transaction data request/reply end --> + <!-- Auth Reversal time out merchant intitated --> + <xsd:complexType name="OriginalTransaction"> + <xsd:sequence> + <xsd:element name="amount" type="tns:amount" minOccurs="0" /> + <xsd:element name="reasonCode" type="xsd:string" minOccurs="0" /> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="HostedDataCreateService"> + <xsd:sequence> + <xsd:element name="profileID" type="xsd:string" minOccurs="0"/> + <xsd:element name="paymentMethod" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="HostedDataRetrieveService"> + <xsd:sequence> + <xsd:element name="profileID" type="xsd:string" minOccurs="0"/> + <xsd:element name="tokenValue" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + + <xsd:complexType name="HostedDataCreateReply"> + <xsd:sequence> + <xsd:element name="responseMessage" type="xsd:string" minOccurs="0" /> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="cardAccountNumberToken" type="xsd:string" minOccurs="0" /> + <xsd:element name="customerID" type="xsd:string" minOccurs="0" /> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="HostedDataRetrieveReply"> + <xsd:sequence> + <xsd:element name="responseMessage" type="xsd:string" minOccurs="0" /> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="aggregatorMerchantIdentifier" type="xsd:string" minOccurs="0" /> + <xsd:element name="customerFirstName" type="xsd:string" minOccurs="0" /> + <xsd:element name="customerLastName" type="xsd:string" minOccurs="0" /> + <xsd:element name="customerID" type="xsd:string" minOccurs="0" /> + <xsd:element name="paymentMethod" type="xsd:string" minOccurs="0" /> + <xsd:element name="billToStreet1" type="xsd:string" minOccurs="0"/> + <xsd:element name="billToStreet2" type="xsd:string" minOccurs="0"/> + <xsd:element name="billToEmail" type="xsd:string" minOccurs="0"/> + <xsd:element name="billToState" type="xsd:string" minOccurs="0"/> + <xsd:element name="billToFirstName" type="xsd:string" minOccurs="0"/> + <xsd:element name="billToLastName" type="xsd:string" minOccurs="0"/> + <xsd:element name="billToCity" type="xsd:string" minOccurs="0"/> + <xsd:element name="billToCountry" type="xsd:string" minOccurs="0"/> + <xsd:element name="billToPostalCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardAccountNumber" type="xsd:string" minOccurs="0" /> + <xsd:element name="cardType" type="xsd:string" minOccurs="0" /> + <xsd:element name="cardExpirationMonth" type="xsd:string" minOccurs="0" /> + <xsd:element name="cardExpirationYear" type="xsd:string" minOccurs="0" /> + <xsd:element name="cardIssueNumber" type="xsd:string" minOccurs="0" /> + <xsd:element name="cardStartMonth" type="xsd:string" minOccurs="0" /> + <xsd:element name="cardStartYear" type="xsd:string" minOccurs="0" /> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="AutoRentalData"> + <xsd:sequence> + <xsd:element name="adjustmentCost" type="tns:amount" minOccurs="0" /> + <xsd:element name="adjustmentCode" type="xsd:string" minOccurs="0" /> + <xsd:element name="agreementNumber" type="xsd:string" minOccurs="0" /> + <xsd:element name="classCode" type="xsd:string" minOccurs="0" /> + <xsd:element name="customerServicePhoneNumber" type="xsd:string" minOccurs="0" /> + <xsd:element name="dailyRate" type="tns:amount" minOccurs="0" /> + <xsd:element name="mileageCost" type="tns:amount" minOccurs="0" /> + <xsd:element name="gasCost" type="tns:amount" minOccurs="0" /> + <xsd:element name="insuranceCost" type="tns:amount" minOccurs="0" /> + <xsd:element name="lateReturnCost" type="tns:amount" minOccurs="0" /> + <xsd:element name="maximumFreeMiles" type="xsd:integer" minOccurs="0" /> + <xsd:element name="milesTraveled" type="xsd:integer" minOccurs="0" /> + <xsd:element name="oneWayCost" type="tns:amount" minOccurs="0" /> + <xsd:element name="parkingViolationCost" type="tns:amount" minOccurs="0" /> + <xsd:element name="pickUpCity" type="xsd:string" minOccurs="0" /> + <xsd:element name="pickUpCountry" type="xsd:string" minOccurs="0" /> + <xsd:element name="pickUpDate" type="xsd:string" minOccurs="0" /> + <xsd:element name="pickUpState" type="xsd:string" minOccurs="0" /> + <xsd:element name="pickUpTime" type="xsd:integer" minOccurs="0" /> + <xsd:element name="ratePerMile" type="tns:amount" minOccurs="0" /> + <xsd:element name="renterName" type="xsd:string" minOccurs="0" /> + <xsd:element name="returnCity" type="xsd:string" minOccurs="0" /> + <xsd:element name="returnCountry" type="xsd:string" minOccurs="0" /> + <xsd:element name="returnDate" type="xsd:string" minOccurs="0" /> + <xsd:element name="returnLocationID" type="xsd:string" minOccurs="0" /> + <xsd:element name="returnState" type="xsd:string" minOccurs="0" /> + <xsd:element name="returnTime" type="xsd:integer" minOccurs="0" /> + <xsd:element name="specialProgramCode" type="xsd:string" minOccurs="0" /> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="VCReply"> + <xsd:sequence> + <xsd:element name="creationTimeStamp" type="xsd:string" minOccurs="0" /> + <xsd:element name="alternateShippingAddressCountryCode" type="xsd:string" minOccurs="0" /> + <xsd:element name="alternateShippingAddressPostalCode" type="xsd:string" minOccurs="0" /> + <xsd:element name="vcAccountLoginName" type="xsd:string" minOccurs="0" /> + <xsd:element name="vcAccountFirstName" type="xsd:string" minOccurs="0" /> + <xsd:element name="vcAccountLastName" type="xsd:string" minOccurs="0" /> + <xsd:element name="vcAccountEncryptedID" type="xsd:string" minOccurs="0" /> + <xsd:element name="vcAccountEmail" type="xsd:string" minOccurs="0" /> + <xsd:element name="vcAccountMobilePhoneNumber" type="xsd:string" minOccurs="0" /> + <xsd:element name="merchantReferenceID" type="xsd:string" minOccurs="0" /> + <xsd:element name="subtotalAmount" type="xsd:string" minOccurs="0" /> + <xsd:element name="shippingHandlingAmount" type="xsd:string" minOccurs="0" /> + <xsd:element name="taxAmount" type="xsd:string" minOccurs="0" /> + <xsd:element name="discountAmount" type="xsd:string" minOccurs="0" /> + <xsd:element name="giftWrapAmount" type="xsd:string" minOccurs="0" /> + <xsd:element name="uncategorizedAmount" type="xsd:string" minOccurs="0" /> + <xsd:element name="totalPurchaseAmount" type="xsd:string" minOccurs="0" /> + <xsd:element name="walletReferenceID" type="xsd:string" minOccurs="0" /> + <xsd:element name="promotionCode" type="xsd:string" minOccurs="0" /> + <xsd:element name="paymentInstrumentID" type="xsd:string" minOccurs="0" /> + <xsd:element name="cardVerificationStatus" type="xsd:string" minOccurs="0" /> + <xsd:element name="issuerID" type="xsd:string" minOccurs="0" /> + <xsd:element name="paymentInstrumentNickName" type="xsd:string" minOccurs="0" /> + <xsd:element name="nameOnCard" type="xsd:string" minOccurs="0" /> + <xsd:element name="cardType" type="xsd:string" minOccurs="0" /> + <xsd:element name="cardGroup" type="xsd:string" minOccurs="0" /> + <xsd:element name="cardArt" type="tns:VCCardArt" minOccurs="0" /> + <xsd:element name="riskAdvice" type="xsd:string" minOccurs="0" /> + <xsd:element name="riskScore" type="xsd:string" minOccurs="0" /> + <xsd:element name="riskAdditionalData" type="xsd:string" minOccurs="0" /> + <xsd:element name="avsCodeRaw" type="xsd:string" minOccurs="0" /> + <xsd:element name="cvnCodeRaw" type="xsd:string" minOccurs="0" /> + <xsd:element name="eciRaw" type="xsd:string" minOccurs="0" /> + <xsd:element name="eci" type="xsd:string" minOccurs="0" /> + <xsd:element name="cavv" type="xsd:string" minOccurs="0" /> + <xsd:element name="veresEnrolled" type="xsd:string" minOccurs="0" /> + <xsd:element name="veresTimeStamp" type="xsd:string" minOccurs="0" /> + <xsd:element name="paresStatus" type="xsd:string" minOccurs="0" /> + <xsd:element name="paresTimeStamp" type="xsd:string" minOccurs="0" /> + <xsd:element name="xid" type="xsd:string" minOccurs="0" /> + <xsd:element name="customData" type="tns:VCCustomData" minOccurs="0" /> + <xsd:element name="vcAccountFullName" type="xsd:string" minOccurs="0" /> + <xsd:element name="paymentDescription" type="xsd:string" minOccurs="0" /> + <xsd:element name="billingAddressStreetName" type="xsd:string" minOccurs="0" /> + <xsd:element name="billingAddressAdditionalLocation" type="xsd:string" minOccurs="0" /> + <xsd:element name="billingAddressStreetNumber" type="xsd:string" minOccurs="0" /> + <xsd:element name="expiredCard" type="xsd:string" minOccurs="0" /> + <xsd:element name="cardFirstName" type="xsd:string" minOccurs="0" /> + <xsd:element name="cardLastName" type="xsd:string" minOccurs="0" /> + <xsd:element name="shippingAddressStreetName" type="xsd:string" minOccurs="0" /> + <xsd:element name="shippingAddressAdditionalLocation" type="xsd:string" minOccurs="0" /> + <xsd:element name="shippingAddressStreetNumber" type="xsd:string" minOccurs="0" /> + <xsd:element name="ageOfAccount" type="xsd:string" minOccurs="0" /> + <xsd:element name="newUser" type="xsd:string" minOccurs="0" /> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="VCCardArt"> + <xsd:sequence> + <xsd:element name="fileName" type="xsd:string" minOccurs="0" /> + <xsd:element name="height" type="xsd:string" minOccurs="0" /> + <xsd:element name="width" type="xsd:string" minOccurs="0" /> + </xsd:sequence> + <xsd:attribute name="id" type="xsd:integer" use="optional"/> + </xsd:complexType> + + <xsd:complexType name="VCCustomData"> + <xsd:sequence> + <xsd:element name="name" type="xsd:string" minOccurs="0" /> + <xsd:element name="value" type="xsd:string" minOccurs="0" /> + </xsd:sequence> + <xsd:attribute name="id" type="xsd:integer" use="optional"/> + </xsd:complexType> + + <xsd:complexType name="DecryptVisaCheckoutDataReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="GetVisaCheckoutDataReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="EncryptPaymentDataReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="requestDateTime" type="tns:dateTime" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="BinLookupService"> + <xsd:sequence> + <xsd:element name="mode" type="xsd:string" minOccurs="0" /> + <xsd:element name="networkOrder" type="xsd:string" minOccurs="0" /> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + + <xsd:complexType name="BinLookupReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="requestDateTime" type="tns:dateTime" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="issuer"> + <xsd:sequence> + <xsd:element name="additionalData" type="xsd:string" minOccurs="0"/> + <xsd:element name="name" type="xsd:string" minOccurs="0"/> + <xsd:element name="country" type="xsd:string" minOccurs="0"/> + <xsd:element name="countryNumericCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="phoneNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="responseCode" type="xsd:string" minOccurs="0" /> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="GETVisaCheckoutDataService"> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="TransactionMetadataService"> + <xsd:sequence> + <xsd:element name="authRequestID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="Loan"> + <xsd:sequence> + <xsd:element name="assetType" type="xsd:string" minOccurs="0"/> + <xsd:element name="type" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="APOrderService"> + <xsd:sequence> + <xsd:element name="sessionsRequestID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + + <xsd:complexType name="APOrderReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="amount" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="status" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="dateTime" type="tns:dateTime" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="APCancelService"> + <xsd:sequence> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="orderRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="saleRequestID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + + <xsd:complexType name="APCancelReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="processorTransactionID" type="xsd:string" minOccurs="0"/> + <xsd:element name="status" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="dateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="paymentStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="responseCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="APBillingAgreementService"> + <xsd:sequence> + <xsd:element name="sessionsRequestID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + + <xsd:complexType name="APBillingAgreementReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="amount" type="xsd:string" minOccurs="0"/> + <xsd:element name="status" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="dateTime" type="tns:dateTime" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="Passenger"> + <xsd:sequence> + <xsd:element name="firstName" type="xsd:string" minOccurs="0" maxOccurs="1"/> + <xsd:element name="lastName" type="xsd:string" minOccurs="0" maxOccurs="1"/> + </xsd:sequence> + <xsd:attribute name="id" type="xsd:integer" use="required"/> + </xsd:complexType> + + <xsd:complexType name="PostdatedTransaction"> + <xsd:sequence> + <xsd:element name="guaranteeIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="guaranteeAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="settlementDate" type="xsd:integer" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="APCreateMandateService"> + <xsd:sequence> + <xsd:element name="saleRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="cancelURL" type="xsd:string" minOccurs="0"/> + <xsd:element name="successURL" type="xsd:string" minOccurs="0"/> + <xsd:element name="failureURL" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + + <xsd:complexType name="APCreateMandateReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="mandateID" type="xsd:string"/> + <xsd:element name="status" type="xsd:string"/> + <xsd:element name="merchantURL" type="xsd:string"/> + <xsd:element name="responseCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorTransactionID" type="xsd:string" minOccurs="0"/> + <xsd:element name="riskScore" type="xsd:string" minOccurs="0"/> + <xsd:element name="encodedHTML" type="xsd:string" minOccurs="0"/> + <xsd:element name="encodedPopupHTML" type="xsd:string" minOccurs="0"/> + <xsd:element name="dateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="dateSigned" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="dateCreated" type="tns:dateTime" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="APMandateStatusService"> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + + <xsd:complexType name="APMandateStatusReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="mandateID" type="xsd:string"/> + <xsd:element name="status" type="xsd:string"/> + <xsd:element name="responseCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorTransactionID" type="xsd:string" minOccurs="0"/> + <xsd:element name="dateCreated" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="dateSigned" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="dateRevoked" type="tns:dateTime" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="APUpdateMandateService"> + <xsd:sequence> + <xsd:element name="esign" type="xsd:string" minOccurs="0"/> + <xsd:element name="cancelURL" type="xsd:string" minOccurs="0"/> + <xsd:element name="successURL" type="xsd:string" minOccurs="0"/> + <xsd:element name="failureURL" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + + <xsd:complexType name="GetMasterpassDataService"> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + + <xsd:complexType name="GetMasterpassDataReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="APUpdateMandateReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="mandateID" type="xsd:string"/> + <xsd:element name="status" type="xsd:string"/> + <xsd:element name="merchantURL" type="xsd:string"/> + <xsd:element name="responseCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorTransactionID" type="xsd:string" minOccurs="0"/> + <xsd:element name="riskScore" type="xsd:string" minOccurs="0"/> + <xsd:element name="encodedHTML" type="xsd:string" minOccurs="0"/> + <xsd:element name="encodedPopupHTML" type="xsd:string" minOccurs="0"/> + <xsd:element name="dateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="dateSigned" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="dateCreated" type="tns:dateTime" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="APImportMandateReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="mandateID" type="xsd:string"/> + <xsd:element name="status" type="xsd:string"/> + <xsd:element name="responseCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorTransactionID" type="xsd:string" minOccurs="0"/> + <xsd:element name="dateSigned" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="dateCreated" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="dateTime" type="tns:dateTime" minOccurs="0"/> + + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="APRevokeMandateService"> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + + <xsd:complexType name="APRevokeMandateReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="mandateID" type="xsd:string"/> + <xsd:element name="status" type="xsd:string"/> + <xsd:element name="responseCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorTransactionID" type="xsd:string" minOccurs="0"/> + <xsd:element name="dateSigned" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="dateCreated" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="dateRevoked" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="dateTime" type="tns:dateTime" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="Category"> + <xsd:sequence> + <xsd:element name="affiliate" type="xsd:string" minOccurs="0"/> + <xsd:element name="campaign" type="xsd:string" minOccurs="0"/> + <xsd:element name="group" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + + + <xsd:complexType name="GiftCardActivationService"> + <xsd:sequence> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="commerceIndicator" type="xsd:string" minOccurs="0"/> + + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + + <xsd:complexType name="GiftCardBalanceInquiryService"> + <xsd:sequence> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="commerceIndicator" type="xsd:string" minOccurs="0"/> + + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + + <xsd:complexType name="GiftCardVoidService"> + + <xsd:attribute name="run" type="tns:boolean" use="required"/> + + </xsd:complexType> + + <xsd:complexType name="GiftCardReversalService"> + + <xsd:attribute name="run" type="tns:boolean" use="required"/> + + </xsd:complexType> + + <xsd:complexType name="GiftCardRedemptionService"> + <xsd:sequence> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="commerceIndicator" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + + <xsd:complexType name="GiftCard"> + <xsd:sequence> + <xsd:element name="originalRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="redemptionType" type="xsd:string" minOccurs="0"/> + <xsd:element name="count" type="xsd:string" minOccurs="0"/> + <xsd:element name="escheatable" type="tns:boolean" minOccurs="0"/> + <xsd:element name="groupID" type="xsd:string" minOccurs="0"/> + <xsd:element name="securityValue" type="xsd:string" minOccurs="0"/> + <xsd:element name="transactionPostingDate" type="xsd:string" minOccurs="0"/> + <xsd:element name="additionalAccountNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="promoCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="balanceCurrency" type="xsd:string" minOccurs="0"/> + <xsd:element name="extendedAccountNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="previousBalance" type="xsd:string" minOccurs="0"/> + <xsd:element name="currentBalance" type="xsd:string" minOccurs="0"/> + <xsd:element name="baseCurrencyPreviousBalance" type="xsd:string" minOccurs="0"/> + <xsd:element name="baseCurrencyCurrentBalance" type="xsd:string" minOccurs="0"/> + <xsd:element name="baseCurrencyCashbackAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="baseCurrency" type="xsd:string" minOccurs="0"/> + <xsd:element name="expirationDate" type="xsd:string" minOccurs="0"/> + <xsd:element name="exchangeRate" type="xsd:string" minOccurs="0"/> + <xsd:element name="bonusAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="discountAmount" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="GiftCardActivationReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="authorizationCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="requestDateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="GiftCardBalanceInquiryReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="authorizationCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="requestDateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="GiftCardRedemptionReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="authorizationCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="requestDateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="GiftCardReversalReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="authorizationCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="requestDateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="GiftCardVoidReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="authorizationCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="requestDeTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + + +</xsd:schema> + diff --git a/test/schema/cyber_source/CyberSourceTransaction_1.155.xsd b/test/schema/cyber_source/CyberSourceTransaction_1.155.xsd new file mode 100644 index 00000000000..577ae9fc8a6 --- /dev/null +++ b/test/schema/cyber_source/CyberSourceTransaction_1.155.xsd @@ -0,0 +1,4857 @@ +<?xml version="1.0" encoding="utf-8"?> +<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:tns="urn:schemas-cybersource-com:transaction-data-1.155" targetNamespace="urn:schemas-cybersource-com:transaction-data-1.155" elementFormDefault="qualified" attributeFormDefault="unqualified"> + <xsd:simpleType name="amount"> + <xsd:restriction base="xsd:string"/> + </xsd:simpleType> + <xsd:simpleType name="boolean"> + <xsd:restriction base="xsd:string"/> + </xsd:simpleType> + <xsd:simpleType name="dateTime"> + <xsd:restriction base="xsd:string"/> + </xsd:simpleType> + <xsd:complexType name="Item"> + <xsd:sequence> + <xsd:element name="unitPrice" type="tns:amount" minOccurs="0"/> + <xsd:element name="quantity" type="tns:amount" minOccurs="0"/> + <xsd:element name="productCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="productName" type="xsd:string" minOccurs="0"/> + <xsd:element name="productSKU" type="xsd:string" minOccurs="0"/> + <xsd:element name="productRisk" type="xsd:string" minOccurs="0"/> + <xsd:element name="taxAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="cityOverrideAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="cityOverrideRate" type="tns:amount" minOccurs="0"/> + <xsd:element name="countyOverrideAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="countyOverrideRate" type="tns:amount" minOccurs="0"/> + <xsd:element name="districtOverrideAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="districtOverrideRate" type="tns:amount" minOccurs="0"/> + <xsd:element name="stateOverrideAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="stateOverrideRate" type="tns:amount" minOccurs="0"/> + <xsd:element name="countryOverrideAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="countryOverrideRate" type="tns:amount" minOccurs="0"/> + <xsd:element name="orderAcceptanceCity" type="xsd:string" minOccurs="0"/> + <xsd:element name="orderAcceptanceCounty" type="xsd:string" minOccurs="0"/> + <xsd:element name="orderAcceptanceCountry" type="xsd:string" minOccurs="0"/> + <xsd:element name="orderAcceptanceState" type="xsd:string" minOccurs="0"/> + <xsd:element name="orderAcceptancePostalCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="orderOriginCity" type="xsd:string" minOccurs="0"/> + <xsd:element name="orderOriginCounty" type="xsd:string" minOccurs="0"/> + <xsd:element name="orderOriginCountry" type="xsd:string" minOccurs="0"/> + <xsd:element name="orderOriginState" type="xsd:string" minOccurs="0"/> + <xsd:element name="orderOriginPostalCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="shipFromCity" type="xsd:string" minOccurs="0"/> + <xsd:element name="shipFromCounty" type="xsd:string" minOccurs="0"/> + <xsd:element name="shipFromCountry" type="xsd:string" minOccurs="0"/> + <xsd:element name="shipFromState" type="xsd:string" minOccurs="0"/> + <xsd:element name="shipFromPostalCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="export" type="xsd:string" minOccurs="0"/> + <xsd:element name="noExport" type="xsd:string" minOccurs="0"/> + <xsd:element name="nationalTax" type="tns:amount" minOccurs="0"/> + <xsd:element name="vatRate" type="tns:amount" minOccurs="0"/> + <xsd:element name="sellerRegistration" type="xsd:string" minOccurs="0"/> + <xsd:element name="sellerRegistration0" type="xsd:string" minOccurs="0"/> + <xsd:element name="sellerRegistration1" type="xsd:string" minOccurs="0"/> + <xsd:element name="sellerRegistration2" type="xsd:string" minOccurs="0"/> + <xsd:element name="sellerRegistration3" type="xsd:string" minOccurs="0"/> + <xsd:element name="sellerRegistration4" type="xsd:string" minOccurs="0"/> + <xsd:element name="sellerRegistration5" type="xsd:string" minOccurs="0"/> + <xsd:element name="sellerRegistration6" type="xsd:string" minOccurs="0"/> + <xsd:element name="sellerRegistration7" type="xsd:string" minOccurs="0"/> + <xsd:element name="sellerRegistration8" type="xsd:string" minOccurs="0"/> + <xsd:element name="sellerRegistration9" type="xsd:string" minOccurs="0"/> + <xsd:element name="buyerRegistration" type="xsd:string" minOccurs="0"/> + <xsd:element name="middlemanRegistration" type="xsd:string" minOccurs="0"/> + <xsd:element name="pointOfTitleTransfer" type="xsd:string" minOccurs="0"/> + <xsd:element name="giftCategory" type="tns:boolean" minOccurs="0"/> + <xsd:element name="timeCategory" type="xsd:string" minOccurs="0"/> + <xsd:element name="hostHedge" type="xsd:string" minOccurs="0"/> + <xsd:element name="timeHedge" type="xsd:string" minOccurs="0"/> + <xsd:element name="velocityHedge" type="xsd:string" minOccurs="0"/> + <xsd:element name="nonsensicalHedge" type="xsd:string" minOccurs="0"/> + <xsd:element name="phoneHedge" type="xsd:string" minOccurs="0"/> + <xsd:element name="obscenitiesHedge" type="xsd:string" minOccurs="0"/> + <xsd:element name="unitOfMeasure" type="xsd:string" minOccurs="0"/> + <xsd:element name="taxRate" type="tns:amount" minOccurs="0"/> + <xsd:element name="totalAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="discountAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="discountRate" type="tns:amount" minOccurs="0"/> + <xsd:element name="commodityCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="grossNetIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="taxTypeApplied" type="xsd:string" minOccurs="0"/> + <xsd:element name="discountIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="alternateTaxID" type="xsd:string" minOccurs="0"/> + <xsd:element name="alternateTaxAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="alternateTaxTypeApplied" type="xsd:string" minOccurs="0"/> + <xsd:element name="alternateTaxRate" type="tns:amount" minOccurs="0"/> + <xsd:element name="alternateTaxType" type="xsd:string" minOccurs="0"/> + <xsd:element name="localTax" type="tns:amount" minOccurs="0"/> + <xsd:element name="zeroCostToCustomerIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="passengerFirstName" type="xsd:string" minOccurs="0"/> + <xsd:element name="passengerLastName" type="xsd:string" minOccurs="0"/> + <xsd:element name="passengerID" type="xsd:string" minOccurs="0"/> + <xsd:element name="passengerStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="passengerType" type="xsd:string" minOccurs="0"/> + <xsd:element name="passengerEmail" type="xsd:string" minOccurs="0"/> + <xsd:element name="passengerPhone" type="xsd:string" minOccurs="0"/> + <xsd:element name="invoiceNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="productDescription" type="xsd:string" minOccurs="0"/> + <xsd:element name="taxStatusIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="discountManagementIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="typeOfSupply" type="xsd:string" minOccurs="0"/> + <xsd:element name="sign" type="xsd:string" minOccurs="0"/> + <xsd:element name="unitTaxAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="weightAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="weightID" type="xsd:string" minOccurs="0"/> + <xsd:element name="weightUnitMeasurement" type="xsd:string" minOccurs="0"/> + + <xsd:element name="otherTax_1_type" type="xsd:string" minOccurs="0"/> + <xsd:element name="otherTax_1_amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="otherTax_1_rate" type="tns:amount" minOccurs="0"/> + <xsd:element name="otherTax_1_statusIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="otherTax_2_type" type="xsd:string" minOccurs="0"/> + <xsd:element name="otherTax_2_amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="otherTax_2_rate" type="tns:amount" minOccurs="0"/> + <xsd:element name="otherTax_2_statusIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="otherTax_3_type" type="xsd:string" minOccurs="0"/> + <xsd:element name="otherTax_3_amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="otherTax_3_rate" type="tns:amount" minOccurs="0"/> + <xsd:element name="otherTax_3_statusIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="otherTax_4_type" type="xsd:string" minOccurs="0"/> + <xsd:element name="otherTax_4_amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="otherTax_4_rate" type="tns:amount" minOccurs="0"/> + <xsd:element name="otherTax_4_statusIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="otherTax_5_type" type="xsd:string" minOccurs="0"/> + <xsd:element name="otherTax_5_amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="otherTax_5_rate" type="tns:amount" minOccurs="0"/> + <xsd:element name="otherTax_5_statusIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="otherTax_6_type" type="xsd:string" minOccurs="0"/> + <xsd:element name="otherTax_6_amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="otherTax_6_rate" type="tns:amount" minOccurs="0"/> + <xsd:element name="otherTax_6_statusIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="otherTax_7_type" type="xsd:string" minOccurs="0"/> + <xsd:element name="otherTax_7_amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="otherTax_7_rate" type="tns:amount" minOccurs="0"/> + <xsd:element name="otherTax_7_statusIndicator" type="xsd:string" minOccurs="0"/> + + + <xsd:element name="referenceData_1_number" type="xsd:string" minOccurs="0"/> + <xsd:element name="referenceData_1_code" type="xsd:string" minOccurs="0"/> + + <xsd:element name="referenceData_2_number" type="xsd:string" minOccurs="0"/> + <xsd:element name="referenceData_2_code" type="xsd:string" minOccurs="0"/> + + <xsd:element name="referenceData_3_number" type="xsd:string" minOccurs="0"/> + <xsd:element name="referenceData_3_code" type="xsd:string" minOccurs="0"/> + + <xsd:element name="referenceData_4_number" type="xsd:string" minOccurs="0"/> + <xsd:element name="referenceData_4_code" type="xsd:string" minOccurs="0"/> + + <xsd:element name="referenceData_5_number" type="xsd:string" minOccurs="0"/> + <xsd:element name="referenceData_5_code" type="xsd:string" minOccurs="0"/> + + <xsd:element name="referenceData_6_number" type="xsd:string" minOccurs="0"/> + <xsd:element name="referenceData_6_code" type="xsd:string" minOccurs="0"/> + + <xsd:element name="referenceData_7_number" type="xsd:string" minOccurs="0"/> + <xsd:element name="referenceData_7_code" type="xsd:string" minOccurs="0"/> + + </xsd:sequence> + <xsd:attribute name="id" type="xsd:integer" use="optional"/> + </xsd:complexType> + + <xsd:complexType name="CCAuthService"> + <xsd:sequence> + <xsd:element name="cavv" type="xsd:string" minOccurs="0"/> + <xsd:element name="cavvAlgorithm" type="xsd:string" minOccurs="0"/> + <xsd:element name="networkTokenCryptogram" type="xsd:string" minOccurs="0"/> + <xsd:element name="paSpecificationVersion" type="xsd:string" minOccurs="0"/> + <xsd:element name="directoryServerTransactionID" type="xsd:string" minOccurs="0"/> + <xsd:element name="commerceIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="eciRaw" type="xsd:string" minOccurs="0"/> + <xsd:element name="xid" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="avsLevel" type="xsd:string" minOccurs="0"/> + <xsd:element name="fxQuoteID" type="xsd:string" minOccurs="0"/> + <xsd:element name="returnAuthRecord" type="tns:boolean" minOccurs="0"/> + <xsd:element name="authType" type="xsd:string" minOccurs="0"/> + <xsd:element name="verbalAuthCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="billPayment" type="tns:boolean" minOccurs="0"/> + <xsd:element name="authenticationXID" type="xsd:string" minOccurs="0"/> + <xsd:element name="authorizationXID" type="xsd:string" minOccurs="0"/> + <xsd:element name="industryDatatype" type="xsd:string" minOccurs="0"/> + <xsd:element name="traceNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="checksumKey" type="xsd:string" minOccurs="0"/> + <xsd:element name="aggregatorID" type="xsd:string" minOccurs="0"/> + <xsd:element name="aggregatorName" type="xsd:string" minOccurs="0"/> + <xsd:element name="splitTenderIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="veresEnrolled" type="xsd:string" minOccurs="0"/> + <xsd:element name="paresStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="partialAuthIndicator" type="tns:boolean" minOccurs="0"/> + <xsd:element name="captureDate" type="xsd:string" minOccurs="0"/> + <xsd:element name="firstRecurringPayment" type="xsd:string" minOccurs="0"/> + <xsd:element name="duration" type="xsd:integer" minOccurs="0"/> + <xsd:element name="overridePaymentMethod" type="xsd:string" minOccurs="0"/> + <xsd:element name="mobileRemotePaymentType" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardholderVerificationMethod" type="xsd:string" minOccurs="0"/> + <xsd:element name="dccRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="overridePaymentDetails" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardholderAuthenticationMethod" type="xsd:string" minOccurs="0"/> + <xsd:element name="leastCostRouting" type="tns:boolean" minOccurs="0"/> + <xsd:element name="verificationType" type="xsd:string" minOccurs="0" /> + <xsd:element name="cryptocurrencyPurchase" type="xsd:string" minOccurs="0"/> + <xsd:element name="lowValueExemptionIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="riskAnalysisExemptionIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="trustedMerchantExemptionIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="secureCorporatePaymentIndicator" type="xsd:string" minOccurs="0"/> + + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="OCTService"> + <xsd:sequence> + <xsd:element name="commerceIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="networkOrder" type="xsd:string" minOccurs="0"/> + <xsd:element name="overridePaymentMethod" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + + <xsd:complexType name="VerificationService"> + <xsd:sequence> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0" /> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required" /> + </xsd:complexType> + + <xsd:complexType name="CCSaleService"> + <xsd:sequence> + <xsd:element name="overridePaymentMethod" type="xsd:string" minOccurs="0"/> + <xsd:element name="commerceIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="partialAuthIndicator" type="tns:boolean" minOccurs="0"/> + <xsd:element name="cavv" type="xsd:string" minOccurs="0"/> + <xsd:element name="xid" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="industryDatatype" type="xsd:string" minOccurs="0"/> + <xsd:element name="networkTokenCryptogram" type="xsd:string" minOccurs="0"/> + <xsd:element name="paSpecificationVersion" type="xsd:string" minOccurs="0"/> + <xsd:element name="directoryServerTransactionID" type="xsd:string" minOccurs="0"/> + <xsd:element name="cryptocurrencyPurchase" type="xsd:string" minOccurs="0"/> + <xsd:element name="lowValueExemptionIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="riskAnalysisExemptionIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="trustedMerchantExemptionIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="secureCorporatePaymentIndicator" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + + <xsd:complexType name="CCSaleCreditService"> + <xsd:sequence> + <xsd:element name="overridePaymentMethod" type="xsd:string" minOccurs="0"/> + <xsd:element name="commerceIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="refundReason" type="xsd:string" minOccurs="0"/> + <xsd:element name="saleRequestID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + + <xsd:complexType name="CCSaleReversalService"> + <xsd:sequence> + <xsd:element name="saleRequestID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + + <xsd:complexType name="CCIncrementalAuthService"> + <xsd:sequence> + <xsd:element name="authRequestID" type="xsd:string" minOccurs="0" /> + <xsd:element name="duration" type="xsd:integer" minOccurs="0" /> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required" /> + </xsd:complexType> + <xsd:complexType name="CCCaptureService"> + <xsd:sequence> + <xsd:element name="authType" type="xsd:string" minOccurs="0"/> + <xsd:element name="verbalAuthCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="authRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="partialPaymentID" type="xsd:string" minOccurs="0"/> + <xsd:element name="purchasingLevel" type="xsd:string" minOccurs="0"/> + <xsd:element name="industryDatatype" type="xsd:string" minOccurs="0"/> + <xsd:element name="authRequestToken" type="xsd:string" minOccurs="0"/> + <xsd:element name="merchantReceiptNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="posData" type="xsd:string" minOccurs="0"/> + <xsd:element name="transactionID" type="xsd:string" minOccurs="0"/> + <xsd:element name="checksumKey" type="xsd:string" minOccurs="0"/> + <xsd:element name="gratuityAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="duration" type="xsd:integer" minOccurs="0"/> + <xsd:element name="dpdeBillingMonth" type="xsd:integer" minOccurs="0"/> + <xsd:element name="sequence" type="xsd:string" minOccurs="0"/> + <xsd:element name="totalCount" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationIDAlternate" type="xsd:string" minOccurs="0"/> + <xsd:element name="aggregatorID" type="xsd:string" minOccurs="0"/> + <xsd:element name="aggregatorName" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="CCCreditService"> + <xsd:sequence> + <xsd:element name="captureRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="partialPaymentID" type="xsd:string" minOccurs="0"/> + <xsd:element name="purchasingLevel" type="xsd:string" minOccurs="0"/> + <xsd:element name="industryDatatype" type="xsd:string" minOccurs="0"/> + <xsd:element name="commerceIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="billPayment" type="tns:boolean" minOccurs="0"/> + <xsd:element name="authorizationXID" type="xsd:string" minOccurs="0"/> + <xsd:element name="occurrenceNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="authCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="captureRequestToken" type="xsd:string" minOccurs="0"/> + <xsd:element name="merchantReceiptNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="checksumKey" type="xsd:string" minOccurs="0"/> + <xsd:element name="aggregatorID" type="xsd:string" minOccurs="0"/> + <xsd:element name="aggregatorName" type="xsd:string" minOccurs="0"/> + <xsd:element name="duration" type="xsd:integer" minOccurs="0"/> + <xsd:element name="dpdeBillingMonth" type="xsd:integer" minOccurs="0"/> + <xsd:element name="reconciliationIDAlternate" type="xsd:string" minOccurs="0"/> + <xsd:element name="refundReason" type="xsd:string" minOccurs="0"/> + <xsd:element name="overridePaymentMethod" type="xsd:string" minOccurs="0"/> + <xsd:element name="overridePaymentDetails" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="CCAuthReversalService"> + <xsd:sequence> + <xsd:element name="authRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="authRequestToken" type="xsd:string" minOccurs="0"/> + <xsd:element name="reversalReason" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="CCAutoAuthReversalService"> + <xsd:sequence> + <xsd:element name="authPaymentServiceData" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="authAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="commerceIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="authRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="billAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="authCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="authType" type="xsd:string" minOccurs="0"/> + <xsd:element name="billPayment" type="tns:boolean" minOccurs="0"/> + <xsd:element name="dateAdded" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="CCDCCService"> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="ServiceFeeCalculateService"> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="ECDebitService"> + <xsd:sequence> + <xsd:element name="paymentMode" type="xsd:integer" minOccurs="0"/> + <xsd:element name="referenceNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="settlementMethod" type="xsd:string" minOccurs="0"/> + <xsd:element name="transactionToken" type="xsd:string" minOccurs="0"/> + <xsd:element name="verificationLevel" type="xsd:integer" minOccurs="0"/> + <xsd:element name="partialPaymentID" type="xsd:string" minOccurs="0"/> + <xsd:element name="commerceIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="debitRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="effectiveDate" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="ECCreditService"> + <xsd:sequence> + <xsd:element name="referenceNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="settlementMethod" type="xsd:string" minOccurs="0"/> + <xsd:element name="transactionToken" type="xsd:string" minOccurs="0"/> + <xsd:element name="debitRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="partialPaymentID" type="xsd:string" minOccurs="0"/> + <xsd:element name="commerceIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="debitRequestToken" type="xsd:string" minOccurs="0"/> + <xsd:element name="effectiveDate" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="ECAuthenticateService"> + <xsd:sequence> + <xsd:element name="referenceNumber" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="PayerAuthEnrollService"> + <xsd:sequence> + <xsd:element name="httpAccept" type="xsd:string" minOccurs="0"/> + <xsd:element name="httpUserAgent" type="xsd:string" minOccurs="0"/> + <xsd:element name="merchantName" type="xsd:string" minOccurs="0"/> + <xsd:element name="merchantURL" type="xsd:string" minOccurs="0"/> + <xsd:element name="purchaseDescription" type="xsd:string" minOccurs="0"/> + <xsd:element name="purchaseTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="countryCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="acquirerBin" type="xsd:string" minOccurs="0"/> + <xsd:element name="loginID" type="xsd:string" minOccurs="0"/> + <xsd:element name="password" type="xsd:string" minOccurs="0"/> + <xsd:element name="merchantID" type="xsd:string" minOccurs="0"/> + <xsd:element name="overridePaymentMethod" type="xsd:string" minOccurs="0"/> + <xsd:element name="mobilePhone" type="xsd:string" minOccurs="0"/> + <xsd:element name="MCC" type="xsd:string" minOccurs="0"/> + <xsd:element name="productCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="referenceID" type="xsd:string" minOccurs="0"/> + <xsd:element name="marketingOptIn" type="tns:boolean" minOccurs="0"/> + <xsd:element name="marketingSource" type="xsd:string" minOccurs="0"/> + <xsd:element name="defaultCard" type="tns:boolean" minOccurs="0"/> + <xsd:element name="shipAddressUsageDate" type="xsd:string" minOccurs="0"/> + <xsd:element name="transactionCountDay" type="xsd:string" minOccurs="0"/> + <xsd:element name="transactionCountYear" type="xsd:string" minOccurs="0"/> + <xsd:element name="addCardAttempts" type="xsd:string" minOccurs="0"/> + <xsd:element name="accountPurchases" type="xsd:string" minOccurs="0"/> + <xsd:element name="fraudActivity" type="tns:boolean" minOccurs="0"/> + <xsd:element name="paymentAccountDate" type="xsd:string" minOccurs="0"/> + <xsd:element name="alternateAuthenticationMethod" type="xsd:string" minOccurs="0"/> + <xsd:element name="alternateAuthenticationDate" type="xsd:string" minOccurs="0"/> + <xsd:element name="alternateAuthenticationData" type="xsd:string" minOccurs="0"/> + <xsd:element name="challengeRequired" type="tns:boolean" minOccurs="0"/> + <xsd:element name="challengeCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="preorder" type="xsd:string" minOccurs="0"/> + <xsd:element name="reorder" type="xsd:string" minOccurs="0"/> + <xsd:element name="preorderDate" type="xsd:string" minOccurs="0"/> + <xsd:element name="giftCardAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="giftCardCurrency" type="xsd:string" minOccurs="0"/> + <xsd:element name="giftCardCount" type="xsd:string" minOccurs="0"/> + <xsd:element name="messageCategory" type="xsd:string" minOccurs="0"/> + <xsd:element name="npaCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="recurringOriginalPurchaseDate" type="xsd:string" minOccurs="0"/> + <xsd:element name="transactionMode" type="xsd:string" minOccurs="0"/> + <xsd:element name="recurringEndDate" type="xsd:string" minOccurs="0"/> + <xsd:element name="recurringFrequency" type="xsd:string" minOccurs="0"/> + <xsd:element name="merchantNewCustomer" type="xsd:string" minOccurs="0"/> + <xsd:element name="customerCCAlias" type="xsd:string" minOccurs="0"/> + <xsd:element name="installmentTotalCount" type="xsd:string" minOccurs="0"/> + <xsd:element name="authenticationTransactionID" type="xsd:string" minOccurs="0"/> + <xsd:element name="httpUserAccept" type="xsd:string" minOccurs="0"/> + <xsd:element name="mobilePhoneDomestic" type="xsd:string" minOccurs="0"/> + <xsd:element name="pareqChannel" type="xsd:string" minOccurs="0"/> + <xsd:element name="shoppingChannel" type="xsd:string" minOccurs="0"/> + <xsd:element name="authenticationChannel" type="xsd:string" minOccurs="0"/> + <xsd:element name="merchantTTPCredential" type="xsd:string" minOccurs="0"/> + <xsd:element name="requestorID" type="xsd:string" minOccurs="0"/> + <xsd:element name="requestorName" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="PayerAuthValidateService"> + <xsd:sequence> + <xsd:element name="signedPARes" type="xsd:string" minOccurs="0"/> + + <xsd:element name="authenticationTransactionID" type="xsd:string" minOccurs="0"/> + + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="TaxService"> + <xsd:sequence> + <xsd:element name="nexus" type="xsd:string" minOccurs="0"/> + <xsd:element name="noNexus" type="xsd:string" minOccurs="0"/> + <xsd:element name="orderAcceptanceCity" type="xsd:string" minOccurs="0"/> + <xsd:element name="orderAcceptanceCounty" type="xsd:string" minOccurs="0"/> + <xsd:element name="orderAcceptanceCountry" type="xsd:string" minOccurs="0"/> + <xsd:element name="orderAcceptanceState" type="xsd:string" minOccurs="0"/> + <xsd:element name="orderAcceptancePostalCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="orderOriginCity" type="xsd:string" minOccurs="0"/> + <xsd:element name="orderOriginCounty" type="xsd:string" minOccurs="0"/> + <xsd:element name="orderOriginCountry" type="xsd:string" minOccurs="0"/> + <xsd:element name="orderOriginState" type="xsd:string" minOccurs="0"/> + <xsd:element name="orderOriginPostalCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="sellerRegistration" type="xsd:string" minOccurs="0"/> + <xsd:element name="sellerRegistration0" type="xsd:string" minOccurs="0"/> + <xsd:element name="sellerRegistration1" type="xsd:string" minOccurs="0"/> + <xsd:element name="sellerRegistration2" type="xsd:string" minOccurs="0"/> + <xsd:element name="sellerRegistration3" type="xsd:string" minOccurs="0"/> + <xsd:element name="sellerRegistration4" type="xsd:string" minOccurs="0"/> + <xsd:element name="sellerRegistration5" type="xsd:string" minOccurs="0"/> + <xsd:element name="sellerRegistration6" type="xsd:string" minOccurs="0"/> + <xsd:element name="sellerRegistration7" type="xsd:string" minOccurs="0"/> + <xsd:element name="sellerRegistration8" type="xsd:string" minOccurs="0"/> + <xsd:element name="sellerRegistration9" type="xsd:string" minOccurs="0"/> + <xsd:element name="buyerRegistration" type="xsd:string" minOccurs="0"/> + <xsd:element name="middlemanRegistration" type="xsd:string" minOccurs="0"/> + <xsd:element name="pointOfTitleTransfer" type="xsd:string" minOccurs="0"/> + <xsd:element name="commitIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="refundIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="dateOverrideReason" type="xsd:string" minOccurs="0"/> + <xsd:element name="reportingDate" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <!-- DME --> + <xsd:complexType name="DMEService"> + <xsd:sequence> + <xsd:element name="eventType" type="xsd:string" minOccurs="0" /> + <xsd:element name="eventPolicy" type="xsd:string" minOccurs="0" /> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required" /> + </xsd:complexType> + <xsd:complexType name="AFSService"> + <xsd:sequence> + <xsd:element name="avsCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="cvCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="disableAVSScoring" type="tns:boolean" minOccurs="0"/> + <xsd:element name="customRiskModel" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="DAVService"> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="ExportService"> + <xsd:sequence> + <xsd:element name="addressOperator" type="xsd:string" minOccurs="0"/> + <xsd:element name="addressWeight" type="xsd:string" minOccurs="0"/> + <xsd:element name="companyWeight" type="xsd:string" minOccurs="0"/> + <xsd:element name="nameWeight" type="xsd:string" minOccurs="0"/> + <xsd:element name="sanctionsLists" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="FXRatesService"> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="BankTransferService"> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="BankTransferRefundService"> + <xsd:sequence> + <xsd:element name="bankTransferRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="bankTransferRealTimeRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="bankTransferRealTimeReconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="bankTransferRequestToken" type="xsd:string" minOccurs="0"/> + <xsd:element name="bankTransferRealTimeRequestToken" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="BankTransferRealTimeService"> + <xsd:sequence> + <xsd:element name="bankTransferRealTimeType" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="DirectDebitMandateService"> + <xsd:sequence> + <xsd:element name="mandateDescriptor" type="xsd:string" minOccurs="0"/> + <xsd:element name="firstDebitDate" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="DirectDebitService"> + <xsd:sequence> + <xsd:element name="dateCollect" type="xsd:string" minOccurs="0"/> + <xsd:element name="directDebitText" type="xsd:string" minOccurs="0"/> + <xsd:element name="authorizationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="transactionType" type="xsd:string" minOccurs="0"/> + <xsd:element name="directDebitType" type="xsd:string" minOccurs="0"/> + <xsd:element name="validateRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="recurringType" type="xsd:string" minOccurs="0"/> + <xsd:element name="mandateID" type="xsd:string" minOccurs="0"/> + <xsd:element name="validateRequestToken" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="mandateAuthenticationDate" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="DirectDebitRefundService"> + <xsd:sequence> + <xsd:element name="directDebitRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="directDebitRequestToken" type="xsd:string" minOccurs="0"/> + <xsd:element name="directDebitType" type="xsd:string" minOccurs="0"/> + <xsd:element name="recurringType" type="xsd:string" minOccurs="0"/> + <xsd:element name="mandateID" type="xsd:string" minOccurs="0"/> + <xsd:element name="mandateAuthenticationDate" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="DirectDebitValidateService"> + <xsd:sequence> + <xsd:element name="directDebitValidateText" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="DeviceFingerprintData"> + <xsd:sequence> + <xsd:element name="data" type="xsd:string" minOccurs="0"/> + <xsd:element name="provider" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="id" type="xsd:integer" use="required"/> + </xsd:complexType> + <xsd:complexType name="PaySubscriptionCreateService"> + <xsd:sequence> + <xsd:element name="paymentRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="paymentRequestToken" type="xsd:string" minOccurs="0"/> + <xsd:element name="disableAutoAuth" type="tns:boolean" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="PaySubscriptionUpdateService"> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="PaySubscriptionEventUpdateService"> + <xsd:sequence> + <xsd:element name="action" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="PaySubscriptionRetrieveService"> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="PaySubscriptionDeleteService"> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="PayPalPaymentService"> + <xsd:sequence> + <xsd:element name="cancelURL" type="xsd:string" minOccurs="0"/> + <xsd:element name="successURL" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="PayPalCreditService"> + <xsd:sequence> + <xsd:element name="payPalPaymentRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="payPalPaymentRequestToken" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <!--PayPalEcSet--> + <xsd:complexType name="PayPalEcSetService"> + <xsd:sequence> + <xsd:element name="paypalReturn" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalCancelReturn" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalMaxamt" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalCustomerEmail" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalDesc" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalReqconfirmshipping" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalNoshipping" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalAddressOverride" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalToken" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalLc" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalPagestyle" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalHdrimg" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalHdrbordercolor" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalHdrbackcolor" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalPayflowcolor" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalEcSetRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalEcSetRequestToken" type="xsd:string" minOccurs="0"/> + <xsd:element name="promoCode0" type="xsd:string" minOccurs="0"/> + <xsd:element name="requestBillingAddress" type="xsd:string" minOccurs="0"/> + <xsd:element name="invoiceNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalBillingType" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalBillingAgreementDesc" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalPaymentType" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalBillingAgreementCustom" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalLogoimg" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <!--PayPalEcGetDetails--> + <xsd:complexType name="PayPalEcGetDetailsService"> + <xsd:sequence> + <xsd:element name="paypalToken" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalEcSetRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalEcSetRequestToken" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <!--PayPalEcDoPayment--> + <xsd:complexType name="PayPalEcDoPaymentService"> + <xsd:sequence> + <xsd:element name="paypalToken" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalPayerId" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalCustomerEmail" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalDesc" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalEcSetRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalEcSetRequestToken" type="xsd:string" minOccurs="0"/> + <xsd:element name="promoCode0" type="xsd:string" minOccurs="0"/> + <xsd:element name="invoiceNumber" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <!--PayPalDoCapture--> + <xsd:complexType name="PayPalDoCaptureService"> + <xsd:sequence> + <xsd:element name="paypalAuthorizationId" type="xsd:string" minOccurs="0"/> + <xsd:element name="completeType" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalEcDoPaymentRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalEcDoPaymentRequestToken" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalAuthorizationRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalAuthorizationRequestToken" type="xsd:string" minOccurs="0"/> + <xsd:element name="invoiceNumber" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <!--PayPalAuthReversal--> + <xsd:complexType name="PayPalAuthReversalService"> + <xsd:sequence> + <xsd:element name="paypalAuthorizationId" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalEcDoPaymentRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalEcDoPaymentRequestToken" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalAuthorizationRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalAuthorizationRequestToken" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalEcOrderSetupRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalEcOrderSetupRequestToken" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <!--PayPalRefund--> + <xsd:complexType name="PayPalRefundService"> + <xsd:sequence> + <xsd:element name="paypalDoCaptureRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalDoCaptureRequestToken" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalCaptureId" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalNote" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <!--PayPalEcOrderSetup--> + <xsd:complexType name="PayPalEcOrderSetupService"> + <xsd:sequence> + <xsd:element name="paypalToken" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalPayerId" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalCustomerEmail" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalDesc" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalEcSetRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalEcSetRequestToken" type="xsd:string" minOccurs="0"/> + <xsd:element name="promoCode0" type="xsd:string" minOccurs="0"/> + <xsd:element name="invoiceNumber" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <!--PayPalAuthorization--> + <xsd:complexType name="PayPalAuthorizationService"> + <xsd:sequence> + <xsd:element name="paypalOrderId" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalEcOrderSetupRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalEcOrderSetupRequestToken" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalDoRefTransactionRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalDoRefTransactionRequestToken" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalCustomerEmail" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <!--PayPalUpdateAgreement--> + <xsd:complexType name="PayPalUpdateAgreementService"> + <xsd:sequence> + <xsd:element name="paypalBillingAgreementId" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalBillingAgreementStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalBillingAgreementDesc" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalBillingAgreementCustom" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <!--PayPalCreateAgreement--> + <xsd:complexType name="PayPalCreateAgreementService"> + <xsd:sequence> + <xsd:element name="paypalToken" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalEcSetRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalEcSetRequestToken" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <!--PayPalDoRefTransaction--> + <xsd:complexType name="PayPalDoRefTransactionService"> + <xsd:sequence> + <xsd:element name="paypalBillingAgreementId" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalPaymentType" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalReqconfirmshipping" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalReturnFmfDetails" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalSoftDescriptor" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalShippingdiscount" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalDesc" type="xsd:string" minOccurs="0"/> + <xsd:element name="invoiceNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalEcNotifyUrl" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="VoidService"> + <xsd:sequence> + <xsd:element name="voidRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="voidRequestToken" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="PinlessDebitService"> + <xsd:sequence> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="commerceIndicator" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="PinlessDebitValidateService"> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="PinlessDebitReversalService"> + <xsd:sequence> + <xsd:element name="pinlessDebitRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="pinlessDebitRequestToken" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + + <!--PinDebitPurchaseService--> + <xsd:complexType name="PinDebitPurchaseService"> + <xsd:sequence> + <xsd:element name="networkOrder" type="xsd:string" minOccurs="0"/> + <xsd:element name="commerceIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="partialAuthIndicator" type="tns:boolean" minOccurs="0"/> + <xsd:element name="overridePaymentMethod" type="xsd:string" minOccurs="0"/> + <xsd:element name="paymentType" type="xsd:string" minOccurs="0"/> + <xsd:element name="ebtCategory" type="xsd:string" minOccurs="0"/> + <xsd:element name="transactionType" type="xsd:string" minOccurs="0"/> + <xsd:element name="ebtVoucherSerialNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="authorizationCode" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <!--End of PinDebitPurchaseService--> + <!--PinDebitCreditService--> + <xsd:complexType name="PinDebitCreditService"> + <xsd:sequence> + <xsd:element name="networkOrder" type="xsd:string" minOccurs="0"/> + <xsd:element name="commerceIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="overridePaymentMethod" type="xsd:string" minOccurs="0"/> + <xsd:element name="paymentType" type="xsd:string" minOccurs="0"/> + <xsd:element name="ebtCategory" type="xsd:string" minOccurs="0"/> + <xsd:element name="transactionType" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <!--End of PinDebitCreditService--> + <!--PinDebitReversalService--> + <xsd:complexType name="PinDebitReversalService"> + <xsd:sequence> + <xsd:element name="pinDebitRequestID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <!--End of PinDebitReversalService--> + + <!--PayPal upgrade services --> + <xsd:complexType name="PayPalButtonCreateService"> + <xsd:sequence> + <xsd:element name="buttonType" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="PayPalPreapprovedPaymentService"> + <xsd:sequence> + <xsd:element name="mpID" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="PayPalPreapprovedUpdateService"> + <xsd:sequence> + <xsd:element name="mpID" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <!-- China Payment --> + <xsd:complexType name="ChinaPaymentService"> + <xsd:sequence> + <xsd:element name="paymentMode" type="xsd:string" minOccurs="0"/> + <xsd:element name="returnURL" type="xsd:string" minOccurs="0"/> + <xsd:element name="pickUpAddress" type="xsd:string" minOccurs="0"/> + <xsd:element name="pickUpPhoneNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="pickUpPostalCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="pickUpName" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <!-- China Refund --> + <xsd:complexType name="ChinaRefundService"> + <xsd:sequence> + <xsd:element name="chinaPaymentRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="chinaPaymentRequestToken" type="xsd:string" minOccurs="0"/> + <xsd:element name="refundReason" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <!--Boleto Payment --> + <xsd:complexType name="BoletoPaymentService"> + <xsd:sequence> + <xsd:element name="instruction" type="xsd:string" minOccurs="0"/> + <xsd:element name="expirationDate" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="PersonalID"> + <xsd:sequence> + <xsd:element name="number" type="xsd:string" minOccurs="0"/> + <xsd:element name="type" type="xsd:string" minOccurs="0"/> + <xsd:element name="name" type="xsd:string" minOccurs="0"/> + <xsd:element name="country" type="xsd:string" minOccurs="0"/> + <xsd:element name="address" type="xsd:string" minOccurs="0"/> + <xsd:element name="issuedBy" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="Routing"> + <xsd:sequence> + <xsd:element name="networkType" type="xsd:string" minOccurs="0"/> + <xsd:element name="networkLabel" type="xsd:string" minOccurs="0"/> + <xsd:element name="signatureCVMRequired" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="Address"> + <xsd:sequence> + <xsd:element name="street1" type="xsd:string" minOccurs="0"/> + <xsd:element name="street2" type="xsd:string" minOccurs="0"/> + <xsd:element name="city" type="xsd:string" minOccurs="0"/> + <xsd:element name="state" type="xsd:string" minOccurs="0"/> + <xsd:element name="postalCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="country" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="APInitiateService"> + <xsd:sequence> + <xsd:element name="returnURL" type="xsd:string" minOccurs="0"/> + <xsd:element name="productName" type="xsd:string" minOccurs="0"/> + <xsd:element name="productDescription" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="bankID" type="xsd:string" minOccurs="0"/> + <xsd:element name="countryCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="escrowAgreement" type="xsd:string" minOccurs="0"/> + <xsd:element name="languageInterface" type="xsd:string" minOccurs="0"/> + <xsd:element name="intent" type="xsd:string" minOccurs="0"/> + <xsd:element name="successURL" type="xsd:string" minOccurs="0"/> + <xsd:element name="cancelURL" type="xsd:string" minOccurs="0"/> + <xsd:element name="failureURL" type="xsd:string" minOccurs="0"/> + <xsd:element name="overridePaymentMethod" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="APCheckStatusService"> + <xsd:sequence> + <xsd:element name="apInitiateRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="checkStatusRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="sessionsRequestID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="RiskUpdateService"> + <xsd:sequence> + <xsd:element name="actionCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="recordID" type="xsd:string" minOccurs="0"/> + <xsd:element name="recordName" type="xsd:string" minOccurs="0"/> + <xsd:element name="negativeAddress" type="tns:Address" minOccurs="0"/> + <xsd:element name="markingReason" type="xsd:string" minOccurs="0"/> + <xsd:element name="markingNotes" type="xsd:string" minOccurs="0"/> + <xsd:element name="markingRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="deviceFingerprintSmartID" type="xsd:string" minOccurs="0"/> + <xsd:element name="deviceFingerprintTrueIPAddress" type="xsd:string" minOccurs="0"/> + <xsd:element name="deviceFingerprintProxyIPAddress" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="FraudUpdateService"> + <xsd:sequence> + <xsd:element name="actionCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="markedData" type="xsd:string" minOccurs="0"/> + <xsd:element name="markingReason" type="xsd:string" minOccurs="0"/> + <xsd:element name="markingNotes" type="xsd:string" minOccurs="0"/> + <xsd:element name="markingRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="markingTransactionDate" type="xsd:string" minOccurs="0"/> + <xsd:element name="markingAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="markingCurrency" type="xsd:string" minOccurs="0"/> + <xsd:element name="markingIndicator" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="CaseManagementActionService"> + <xsd:sequence> + <xsd:element name="actionCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="requestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="comments" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="EncryptPaymentDataService"> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="InvoiceHeader"> + <xsd:sequence> + <xsd:element name="merchantDescriptor" type="xsd:string" minOccurs="0"/> + <xsd:element name="merchantDescriptorContact" type="xsd:string" minOccurs="0"/> + <xsd:element name="merchantDescriptorAlternate" type="xsd:string" minOccurs="0"/> + <xsd:element name="merchantDescriptorStreet" type="xsd:string" minOccurs="0"/> + <xsd:element name="merchantDescriptorCity" type="xsd:string" minOccurs="0"/> + <xsd:element name="merchantDescriptorState" type="xsd:string" minOccurs="0"/> + <xsd:element name="merchantDescriptorPostalCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="merchantDescriptorCountry" type="xsd:string" minOccurs="0"/> + <xsd:element name="isGift" type="tns:boolean" minOccurs="0"/> + <xsd:element name="returnsAccepted" type="tns:boolean" minOccurs="0"/> + <xsd:element name="tenderType" type="xsd:string" minOccurs="0"/> + <xsd:element name="merchantVATRegistrationNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="purchaserOrderDate" type="xsd:string" minOccurs="0"/> + <!-- xsd:date --> + <xsd:element name="purchaserVATRegistrationNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="vatInvoiceReferenceNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="summaryCommodityCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="supplierOrderReference" type="xsd:string" minOccurs="0"/> + <xsd:element name="userPO" type="xsd:string" minOccurs="0"/> + <xsd:element name="costCenter" type="xsd:string" minOccurs="0"/> + <xsd:element name="purchaserCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="taxable" type="tns:boolean" minOccurs="0"/> + <xsd:element name="amexDataTAA1" type="xsd:string" minOccurs="0"/> + <xsd:element name="amexDataTAA2" type="xsd:string" minOccurs="0"/> + <xsd:element name="amexDataTAA3" type="xsd:string" minOccurs="0"/> + <xsd:element name="amexDataTAA4" type="xsd:string" minOccurs="0"/> + <xsd:element name="invoiceDate" type="xsd:string" minOccurs="0"/> + <xsd:element name="totalTaxTypeCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardAcceptorRefNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="authorizedContactName" type="xsd:string" minOccurs="0"/> + <xsd:element name="businessApplicationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="salesOrganizationID" type="xsd:integer" minOccurs="0"/> + <xsd:element name="submerchantID" type="xsd:string" minOccurs="0"/> + <xsd:element name="submerchantName" type="xsd:string" minOccurs="0"/> + <xsd:element name="submerchantStreet" type="xsd:string" minOccurs="0"/> + <xsd:element name="submerchantCity" type="xsd:string" minOccurs="0"/> + <xsd:element name="submerchantPostalCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="submerchantState" type="xsd:string" minOccurs="0"/> + <xsd:element name="submerchantCountry" type="xsd:string" minOccurs="0"/> + <xsd:element name="submerchantEmail" type="xsd:string" minOccurs="0"/> + <xsd:element name="submerchantTelephoneNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="submerchantRegion" type="xsd:string" minOccurs="0"/> + <xsd:element name="submerchantMerchantID" type="xsd:string" minOccurs="0"/> + <xsd:element name="merchantDescriptorCounty" type="xsd:string" minOccurs="0"/> + <xsd:element name="referenceDataCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="referenceDataNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="merchantDescriptorStoreID" type="xsd:string" minOccurs="0"/> + <xsd:element name="clerkID" type="xsd:string" minOccurs="0"/> + <xsd:element name="customData_1" type="xsd:string" minOccurs="0" /> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="BusinessRules"> + <xsd:sequence> + <xsd:element name="ignoreAVSResult" type="tns:boolean" minOccurs="0"/> + <xsd:element name="ignoreCVResult" type="tns:boolean" minOccurs="0"/> + <xsd:element name="ignoreDAVResult" type="tns:boolean" minOccurs="0"/> + <xsd:element name="ignoreExportResult" type="tns:boolean" minOccurs="0"/> + <xsd:element name="ignoreValidateResult" type="tns:boolean" minOccurs="0"/> + <xsd:element name="declineAVSFlags" type="xsd:string" minOccurs="0"/> + <xsd:element name="scoreThreshold" type="xsd:integer" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="BillTo"> + <xsd:sequence> + <xsd:element name="title" type="xsd:string" minOccurs="0"/> + <xsd:element name="firstName" type="xsd:string" minOccurs="0"/> + <xsd:element name="middleName" type="xsd:string" minOccurs="0"/> + <xsd:element name="lastName" type="xsd:string" minOccurs="0"/> + <xsd:element name="suffix" type="xsd:string" minOccurs="0"/> + <xsd:element name="buildingNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="street1" type="xsd:string" minOccurs="0"/> + <xsd:element name="street2" type="xsd:string" minOccurs="0"/> + <xsd:element name="street3" type="xsd:string" minOccurs="0"/> + <xsd:element name="street4" type="xsd:string" minOccurs="0"/> + <xsd:element name="street5" type="xsd:string" minOccurs="0"/> + <xsd:element name="city" type="xsd:string" minOccurs="0"/> + <xsd:element name="district" type="xsd:string" minOccurs="0"/> + <xsd:element name="county" type="xsd:string" minOccurs="0"/> + <xsd:element name="state" type="xsd:string" minOccurs="0"/> + <xsd:element name="postalCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="country" type="xsd:string" minOccurs="0"/> + <xsd:element name="company" type="xsd:string" minOccurs="0"/> + <xsd:element name="companyTaxID" type="xsd:string" minOccurs="0"/> + <xsd:element name="phoneNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="email" type="xsd:string" minOccurs="0"/> + <xsd:element name="ipAddress" type="xsd:string" minOccurs="0"/> + <xsd:element name="customerUserName" type="xsd:string" minOccurs="0"/> + <xsd:element name="customerPassword" type="xsd:string" minOccurs="0"/> + <xsd:element name="ipNetworkAddress" type="xsd:string" minOccurs="0"/> + <xsd:element name="hostname" type="xsd:string" minOccurs="0"/> + <xsd:element name="domainName" type="xsd:string" minOccurs="0"/> + <xsd:element name="dateOfBirth" type="xsd:string" minOccurs="0"/> + <!-- xsd:date --> + <xsd:element name="driversLicenseNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="driversLicenseState" type="xsd:string" minOccurs="0"/> + <xsd:element name="ssn" type="xsd:string" minOccurs="0"/> + <xsd:element name="customerID" type="xsd:string" minOccurs="0"/> + <xsd:element name="httpBrowserType" type="xsd:string" minOccurs="0"/> + <xsd:element name="httpBrowserEmail" type="xsd:string" minOccurs="0"/> + <xsd:element name="httpBrowserCookiesAccepted" type="tns:boolean" minOccurs="0"/> + <xsd:element name="nif" type="xsd:string" minOccurs="0"/> + <xsd:element name="personalID" type="xsd:string" minOccurs="0"/> + <xsd:element name="language" type="xsd:string" minOccurs="0"/> + <xsd:element name="name" type="xsd:string" minOccurs="0"/> + <xsd:element name="gender" type="xsd:string" minOccurs="0"/> + <xsd:element name="merchantTaxID" type="xsd:string" minOccurs="0"/> + + <xsd:element name="passportNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="passportCountry" type="xsd:string" minOccurs="0"/> + <xsd:element name="customerAccountCreateDate" type="xsd:string" minOccurs="0"/> + <xsd:element name="customerAccountChangeDate" type="xsd:string" minOccurs="0"/> + <xsd:element name="customerAccountPasswordChangeDate" type="xsd:string" minOccurs="0"/> + <xsd:element name="pointOfReference" type="tns:boolean" minOccurs="0"/> + <xsd:element name="defaultIndicator" type="tns:boolean" minOccurs="0"/> + + <xsd:element name="companyStreet1" type="xsd:string" minOccurs="0"/> + <xsd:element name="companyStreet2" type="xsd:string" minOccurs="0"/> + <xsd:element name="companyCity" type="xsd:string" minOccurs="0"/> + <xsd:element name="companyCountry" type="xsd:string" minOccurs="0"/> + <xsd:element name="companyState" type="xsd:string" minOccurs="0"/> + <xsd:element name="companyPostalCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="prefix" type="xsd:string" minOccurs="0"/> + <xsd:element name="companyPhoneNumber" type="xsd:string" minOccurs="0"/> + + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="ShipTo"> + <xsd:sequence> + <xsd:element name="title" type="xsd:string" minOccurs="0"/> + <xsd:element name="firstName" type="xsd:string" minOccurs="0"/> + <xsd:element name="middleName" type="xsd:string" minOccurs="0"/> + <xsd:element name="lastName" type="xsd:string" minOccurs="0"/> + <xsd:element name="suffix" type="xsd:string" minOccurs="0"/> + <xsd:element name="street1" type="xsd:string" minOccurs="0"/> + <xsd:element name="street2" type="xsd:string" minOccurs="0"/> + <xsd:element name="street3" type="xsd:string" minOccurs="0"/> + <xsd:element name="street4" type="xsd:string" minOccurs="0"/> + <xsd:element name="street5" type="xsd:string" minOccurs="0"/> + <xsd:element name="city" type="xsd:string" minOccurs="0"/> + <xsd:element name="county" type="xsd:string" minOccurs="0"/> + <xsd:element name="state" type="xsd:string" minOccurs="0"/> + <xsd:element name="buildingNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="district" type="xsd:string" minOccurs="0"/> + <xsd:element name="postalCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="country" type="xsd:string" minOccurs="0"/> + <xsd:element name="company" type="xsd:string" minOccurs="0"/> + <xsd:element name="phoneNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="email" type="xsd:string" minOccurs="0"/> + <xsd:element name="shippingMethod" type="xsd:string" minOccurs="0"/> + <xsd:element name="name" type="xsd:string" minOccurs="0"/> + <xsd:element name="id" type="xsd:string" minOccurs="0"/> + <xsd:element name="addressVerificationStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="notApplicable" type="tns:boolean" minOccurs="0"/> + <xsd:element name="immutable" type="tns:boolean" minOccurs="0"/> + <xsd:element name="destinationCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="pointOfReference" type="tns:boolean" minOccurs="0"/> + <xsd:element name="default" type="tns:boolean" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="ShipFrom"> + <xsd:sequence> + <xsd:element name="title" type="xsd:string" minOccurs="0"/> + <xsd:element name="firstName" type="xsd:string" minOccurs="0"/> + <xsd:element name="middleName" type="xsd:string" minOccurs="0"/> + <xsd:element name="lastName" type="xsd:string" minOccurs="0"/> + <xsd:element name="suffix" type="xsd:string" minOccurs="0"/> + <xsd:element name="street1" type="xsd:string" minOccurs="0"/> + <xsd:element name="street2" type="xsd:string" minOccurs="0"/> + <xsd:element name="street3" type="xsd:string" minOccurs="0"/> + <xsd:element name="street4" type="xsd:string" minOccurs="0"/> + <xsd:element name="city" type="xsd:string" minOccurs="0"/> + <xsd:element name="county" type="xsd:string" minOccurs="0"/> + <xsd:element name="state" type="xsd:string" minOccurs="0"/> + <xsd:element name="postalCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="country" type="xsd:string" minOccurs="0"/> + <xsd:element name="company" type="xsd:string" minOccurs="0"/> + <xsd:element name="phoneNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="email" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="Card"> + <xsd:sequence> + <xsd:element name="fullName" type="xsd:string" minOccurs="0"/> + <xsd:element name="accountNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="expirationMonth" type="xsd:integer" minOccurs="0"/> + <xsd:element name="expirationYear" type="xsd:integer" minOccurs="0"/> + <xsd:element name="cvIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="cvNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardType" type="xsd:string" minOccurs="0"/> + <xsd:element name="issueNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="startMonth" type="xsd:integer" minOccurs="0"/> + <xsd:element name="startYear" type="xsd:integer" minOccurs="0"/> + <xsd:element name="pin" type="xsd:string" minOccurs="0"/> + <xsd:element name="accountEncoderID" type="xsd:string" minOccurs="0"/> + <xsd:element name="bin" type="xsd:string" minOccurs="0"/> + <xsd:element name="encryptedData" type="xsd:string" minOccurs="0"/> + <xsd:element name="suffix" type="xsd:string" minOccurs="0"/> + <xsd:element name="virtual" type="tns:boolean" minOccurs="0"/> + <xsd:element name="prefix" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardTypeName" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardSubType" type="xsd:string" minOccurs="0"/> + <xsd:element name="level2Eligible" type="xsd:string" minOccurs="0"/> + <xsd:element name="level3Eligible" type="xsd:string" minOccurs="0"/> + <xsd:element name="productCategory" type="xsd:string" minOccurs="0"/> + <xsd:element name="crossBorderIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="billingCurrency" type="xsd:string" minOccurs="0"/> + <xsd:element name="billingCurrencyNumericCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="billingCurrencyMinorDigits" type="xsd:string" minOccurs="0"/> + <xsd:element name="octFastFundsIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="octBlockIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="onlineGamblingBlockIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="productName" type="xsd:string" minOccurs="0"/> + <xsd:element name="usage" type="xsd:string" minOccurs="0"/> + <xsd:element name="prepaidReloadable" type="xsd:string" minOccurs="0"/> + <xsd:element name="prepaidType" type="xsd:string" minOccurs="0"/> + <xsd:element name="brands" type="tns:Brands" minOccurs="0" maxOccurs="5"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="Check"> + <xsd:sequence> + <xsd:element name="fullName" type="xsd:string" minOccurs="0"/> + <xsd:element name="accountNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="accountType" type="xsd:string" minOccurs="0"/> + <xsd:element name="bankTransitNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="checkNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="secCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="accountEncoderID" type="xsd:string" minOccurs="0"/> + <xsd:element name="authenticateID" type="xsd:string" minOccurs="0"/> + <xsd:element name="paymentInfo" type="xsd:string" minOccurs="0"/> + <xsd:element name="imageReferenceNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="terminalCity" type="xsd:string" minOccurs="0"/> + <xsd:element name="terminalState" type="xsd:string" minOccurs="0"/> + <xsd:element name="customerPresent" type="xsd:string" minOccurs="0"/> + <xsd:element name="checkTransactionCode" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="BML"> + <xsd:sequence> + <xsd:element name="customerBillingAddressChange" type="tns:boolean" minOccurs="0"/> + <xsd:element name="customerEmailChange" type="tns:boolean" minOccurs="0"/> + <xsd:element name="customerHasCheckingAccount" type="tns:boolean" minOccurs="0"/> + <xsd:element name="customerHasSavingsAccount" type="tns:boolean" minOccurs="0"/> + <xsd:element name="customerPasswordChange" type="tns:boolean" minOccurs="0"/> + <xsd:element name="customerPhoneChange" type="tns:boolean" minOccurs="0"/> + <xsd:element name="customerRegistrationDate" type="xsd:string" minOccurs="0"/> + <!-- xsd:date --> + <xsd:element name="customerTypeFlag" type="xsd:string" minOccurs="0"/> + <xsd:element name="grossHouseholdIncome" type="tns:amount" minOccurs="0"/> + <xsd:element name="householdIncomeCurrency" type="xsd:string" minOccurs="0"/> + <xsd:element name="itemCategory" type="xsd:string" minOccurs="0"/> + <xsd:element name="merchantPromotionCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="preapprovalNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="productDeliveryTypeIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="residenceStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="tcVersion" type="xsd:string" minOccurs="0"/> + <xsd:element name="yearsAtCurrentResidence" type="xsd:integer" minOccurs="0"/> + <xsd:element name="yearsWithCurrentEmployer" type="xsd:integer" minOccurs="0"/> + <xsd:element name="employerStreet1" type="xsd:string" minOccurs="0"/> + <xsd:element name="employerStreet2" type="xsd:string" minOccurs="0"/> + <xsd:element name="employerCity" type="xsd:string" minOccurs="0"/> + <xsd:element name="employerCompanyName" type="xsd:string" minOccurs="0"/> + <xsd:element name="employerCountry" type="xsd:string" minOccurs="0"/> + <xsd:element name="employerPhoneNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="employerPhoneType" type="xsd:string" minOccurs="0"/> + <xsd:element name="employerState" type="xsd:string" minOccurs="0"/> + <xsd:element name="employerPostalCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="shipToPhoneType" type="xsd:string" minOccurs="0"/> + <xsd:element name="billToPhoneType" type="xsd:string" minOccurs="0"/> + <xsd:element name="methodOfPayment" type="xsd:string" minOccurs="0"/> + <xsd:element name="productType" type="xsd:string" minOccurs="0"/> + <xsd:element name="customerAuthenticatedByMerchant" type="xsd:string" minOccurs="0"/> + <xsd:element name="backOfficeIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="shipToEqualsBillToNameIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="shipToEqualsBillToAddressIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="alternateIPAddress" type="xsd:string" minOccurs="0"/> + <xsd:element name="businessLegalName" type="xsd:string" minOccurs="0"/> + <xsd:element name="dbaName" type="xsd:string" minOccurs="0"/> + <xsd:element name="businessAddress1" type="xsd:string" minOccurs="0"/> + <xsd:element name="businessAddress2" type="xsd:string" minOccurs="0"/> + <xsd:element name="businessCity" type="xsd:string" minOccurs="0"/> + <xsd:element name="businessState" type="xsd:string" minOccurs="0"/> + <xsd:element name="businessPostalCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="businessCountry" type="xsd:string" minOccurs="0"/> + <xsd:element name="businessMainPhone" type="xsd:string" minOccurs="0"/> + <xsd:element name="userID" type="xsd:string" minOccurs="0"/> + <xsd:element name="pin" type="xsd:string" minOccurs="0"/> + <xsd:element name="adminLastName" type="xsd:string" minOccurs="0"/> + <xsd:element name="adminFirstName" type="xsd:string" minOccurs="0"/> + <xsd:element name="adminPhone" type="xsd:string" minOccurs="0"/> + <xsd:element name="adminFax" type="xsd:string" minOccurs="0"/> + <xsd:element name="adminEmailAddress" type="xsd:string" minOccurs="0"/> + <xsd:element name="adminTitle" type="xsd:string" minOccurs="0"/> + <xsd:element name="supervisorLastName" type="xsd:string" minOccurs="0"/> + <xsd:element name="supervisorFirstName" type="xsd:string" minOccurs="0"/> + <xsd:element name="supervisorEmailAddress" type="xsd:string" minOccurs="0"/> + <xsd:element name="businessDAndBNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="businessTaxID" type="xsd:string" minOccurs="0"/> + <xsd:element name="businessNAICSCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="businessType" type="xsd:string" minOccurs="0"/> + <xsd:element name="businessYearsInBusiness" type="xsd:string" minOccurs="0"/> + <xsd:element name="businessNumberOfEmployees" type="xsd:string" minOccurs="0"/> + <xsd:element name="businessPONumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="businessLoanType" type="xsd:string" minOccurs="0"/> + <xsd:element name="businessApplicationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="businessProductCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="pgLastName" type="xsd:string" minOccurs="0"/> + <xsd:element name="pgFirstName" type="xsd:string" minOccurs="0"/> + <xsd:element name="pgSSN" type="xsd:string" minOccurs="0"/> + <xsd:element name="pgDateOfBirth" type="xsd:string" minOccurs="0"/> + <xsd:element name="pgAnnualIncome" type="xsd:string" minOccurs="0"/> + <xsd:element name="pgIncomeCurrencyType" type="xsd:string" minOccurs="0"/> + <xsd:element name="pgResidenceStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="pgCheckingAccountIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="pgSavingsAccountIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="pgYearsAtEmployer" type="xsd:string" minOccurs="0"/> + <xsd:element name="pgYearsAtResidence" type="xsd:string" minOccurs="0"/> + <xsd:element name="pgHomeAddress1" type="xsd:string" minOccurs="0"/> + <xsd:element name="pgHomeAddress2" type="xsd:string" minOccurs="0"/> + <xsd:element name="pgHomeCity" type="xsd:string" minOccurs="0"/> + <xsd:element name="pgHomeState" type="xsd:string" minOccurs="0"/> + <xsd:element name="pgHomePostalCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="pgHomeCountry" type="xsd:string" minOccurs="0"/> + <xsd:element name="pgEmailAddress" type="xsd:string" minOccurs="0"/> + <xsd:element name="pgHomePhone" type="xsd:string" minOccurs="0"/> + <xsd:element name="pgTitle" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="OtherTax"> + <xsd:sequence> + <xsd:element name="vatTaxAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="vatTaxRate" type="tns:amount" minOccurs="0"/> + <xsd:element name="vatTaxAmountSign" type="xsd:string" minOccurs="0"/> + <xsd:element name="alternateTaxAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="alternateTaxIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="alternateTaxID" type="xsd:string" minOccurs="0"/> + <xsd:element name="localTaxAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="localTaxIndicator" type="xsd:integer" minOccurs="0"/> + <xsd:element name="nationalTaxAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="nationalTaxIndicator" type="xsd:integer" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="Aft"> + <xsd:sequence> + <xsd:element name="indicator" type="xsd:string" minOccurs="0" /> + <xsd:element name="serviceFee" type="xsd:string" minOccurs="0" /> + <xsd:element name="foreignExchangeFee" type="xsd:string" minOccurs="0" /> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="Wallet"> + <xsd:sequence> + <xsd:element name="type" type="xsd:string" minOccurs="0"/> + <xsd:element name="orderID" type="xsd:string" minOccurs="0"/> + <xsd:element name="merchantReferenceID" type="xsd:string" minOccurs="0"/> + <xsd:element name="userPhone" type="xsd:string" minOccurs="0"/> + <xsd:element name="avv" type="xsd:string" minOccurs="0"/> + <xsd:element name="eciRaw" type="xsd:string" minOccurs="0"/> + <xsd:element name="authenticatonMethod" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardEnrollmentMethod" type="xsd:string" minOccurs="0"/> + <xsd:element name="paresStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="veresEnrolled" type="xsd:string" minOccurs="0"/> + <xsd:element name="xid" type="xsd:string" minOccurs="0"/> + <xsd:element name="totalPurchaseAmount" type="xsd:string" minOccurs="0" /> + <xsd:element name="subtotalAmount" type="xsd:string" minOccurs="0" /> + <xsd:element name="discountAmount" type="xsd:string" minOccurs="0" /> + <xsd:element name="giftWrapAmount" type="xsd:string" minOccurs="0" /> + <xsd:element name="eventType" type="xsd:string" minOccurs="0" /> + <xsd:element name="promotionCode" type="xsd:string" minOccurs="0" /> + </xsd:sequence> + </xsd:complexType> + + + <xsd:complexType name="PurchaseTotals"> + <xsd:sequence> + <xsd:element name="currency" type="xsd:string" minOccurs="0"/> + <xsd:element name="discountAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="discountAmountSign" type="xsd:string" minOccurs="0"/> + <xsd:element name="discountManagementIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="taxAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="dutyAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="dutyAmountSign" type="xsd:string" minOccurs="0"/> + <xsd:element name="grandTotalAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="freightAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="freightAmountSign" type="xsd:string" minOccurs="0"/> + <xsd:element name="foreignAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="foreignCurrency" type="xsd:string" minOccurs="0"/> + <xsd:element name="originalAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="originalCurrency" type="xsd:string" minOccurs="0"/> + <xsd:element name="exchangeRate" type="tns:amount" minOccurs="0"/> + <xsd:element name="exchangeRateTimeStamp" type="xsd:string" minOccurs="0"/> + <xsd:element name="additionalAmountType0" type="xsd:string" minOccurs="0"/> + <xsd:element name="additionalAmount0" type="xsd:string" minOccurs="0"/> + <xsd:element name="additionalAmountType1" type="xsd:string" minOccurs="0"/> + <xsd:element name="additionalAmount1" type="xsd:string" minOccurs="0"/> + <xsd:element name="additionalAmountType2" type="xsd:string" minOccurs="0"/> + <xsd:element name="additionalAmount2" type="xsd:string" minOccurs="0"/> + <xsd:element name="additionalAmountType3" type="xsd:string" minOccurs="0"/> + <xsd:element name="additionalAmount3" type="xsd:string" minOccurs="0"/> + <xsd:element name="additionalAmountType4" type="xsd:string" minOccurs="0"/> + <xsd:element name="additionalAmount4" type="xsd:string" minOccurs="0"/> + <xsd:element name="serviceFeeAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="subtotalAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="shippingAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="handlingAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="shippingHandlingAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="shippingDiscountAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="giftWrapAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="insuranceAmount" type="tns:amount" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="FundingTotals"> + <xsd:sequence> + <xsd:element name="currency" type="xsd:string" minOccurs="0"/> + <xsd:element name="grandTotalAmount" type="tns:amount" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="GECC"> + <xsd:sequence> + <xsd:element name="saleType" type="xsd:string" minOccurs="0"/> + <xsd:element name="planNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="sequenceNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="promotionEndDate" type="xsd:string" minOccurs="0"/> + <xsd:element name="promotionPlan" type="xsd:string" minOccurs="0"/> + <xsd:element name="line" type="xsd:string" minOccurs="0" maxOccurs="7"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="UCAF"> + <xsd:sequence> + <xsd:element name="authenticationData" type="xsd:string" minOccurs="0"/> + <xsd:element name="collectionIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="downgradeReasonCode" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="Network"> + <xsd:all> + <xsd:element name="octDomesticIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="octCrossBorderIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="aftDomesticIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="aftCrossBorderIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="networkID" type="xsd:string" minOccurs="0"/> + <xsd:element name="networkOrder" type="xsd:string" minOccurs="0"/> + </xsd:all> + <xsd:attribute name="id" type="xsd:integer" use="optional"/> + </xsd:complexType> + + <xsd:complexType name="Brands"> + <xsd:all> + <xsd:element name="name" type="xsd:string" minOccurs="0"/> + <xsd:element name="type" type="xsd:string" minOccurs="0"/> + </xsd:all> + <xsd:attribute name="id" type="xsd:integer" use="optional"/> + </xsd:complexType> + + <xsd:complexType name="FundTransfer"> + <xsd:sequence> + <xsd:element name="accountNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="accountName" type="xsd:string" minOccurs="0"/> + <xsd:element name="bankCheckDigit" type="xsd:string" minOccurs="0"/> + <xsd:element name="iban" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="BankInfo"> + <xsd:sequence> + <xsd:element name="bankCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="name" type="xsd:string" minOccurs="0"/> + <xsd:element name="address" type="xsd:string" minOccurs="0"/> + <xsd:element name="city" type="xsd:string" minOccurs="0"/> + <xsd:element name="country" type="xsd:string" minOccurs="0"/> + <xsd:element name="branchCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="swiftCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="sortCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="issuerID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="RecurringSubscriptionInfo"> + <xsd:sequence> + <xsd:element name="subscriptionID" type="xsd:string" minOccurs="0"/> + <xsd:element name="status" type="xsd:string" minOccurs="0"/> + <xsd:element name="amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="numberOfPayments" type="xsd:integer" minOccurs="0"/> + <xsd:element name="numberOfPaymentsToAdd" type="xsd:integer" minOccurs="0"/> + <xsd:element name="automaticRenew" type="tns:boolean" minOccurs="0"/> + <xsd:element name="frequency" type="xsd:string" minOccurs="0"/> + <xsd:element name="startDate" type="xsd:string" minOccurs="0"/> + <xsd:element name="endDate" type="xsd:string" minOccurs="0"/> + <xsd:element name="approvalRequired" type="tns:boolean" minOccurs="0"/> + <xsd:element name="event" type="tns:PaySubscriptionEvent" minOccurs="0"/> + <xsd:element name="billPayment" type="tns:boolean" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="PaySubscriptionEvent"> + <xsd:sequence> + <xsd:element name="amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="approvedBy" type="xsd:string" minOccurs="0"/> + <xsd:element name="number" type="xsd:integer" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="Subscription"> + <xsd:sequence> + <xsd:element name="title" type="xsd:string" minOccurs="0"/> + <xsd:element name="paymentMethod" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="TokenSource"> + <xsd:sequence> + <xsd:element name="transientToken" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="PaymentNetworkToken"> + <xsd:sequence> + <xsd:element name="requestorID" type="xsd:string" minOccurs="0"/> + <xsd:element name="transactionType" type="xsd:string" minOccurs="0"/> + <xsd:element name="assuranceLevel" type="xsd:string" minOccurs="0"/> + <xsd:element name="accountStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="originalCardCategory" type="xsd:string" minOccurs="0"/> + <xsd:element name="deviceTechType" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="DecisionManager"> + <xsd:sequence> + <xsd:element name="enabled" type="tns:boolean" minOccurs="0"/> + <xsd:element name="profile" type="xsd:string" minOccurs="0"/> + <xsd:element name="travelData" type="tns:DecisionManagerTravelData" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="DecisionManagerTravelData"> + <xsd:sequence> + <xsd:element name="leg" type="tns:DecisionManagerTravelLeg" minOccurs="0" maxOccurs="100"/> + <xsd:element name="departureDateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="completeRoute" type="xsd:string" minOccurs="0"/> + <xsd:element name="journeyType" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="DecisionManagerTravelLeg"> + <xsd:sequence> + <xsd:element name="origin" type="xsd:string" minOccurs="0"/> + <xsd:element name="destination" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="id" type="xsd:integer" use="optional"/> + </xsd:complexType> + <xsd:complexType name="Batch"> + <xsd:sequence> + <xsd:element name="batchID" type="xsd:string" minOccurs="0"/> + <xsd:element name="recordID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="PayPal"> + <xsd:sequence> + <xsd:any processContents="skip" minOccurs="0" maxOccurs="999"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="JPO"> + <xsd:sequence> + <xsd:element name="paymentMethod" type="xsd:integer" minOccurs="0"/> + <xsd:element name="bonusAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="bonuses" type="xsd:integer" minOccurs="0"/> + <xsd:element name="installments" type="xsd:integer" minOccurs="0"/> + <xsd:element name="firstBillingMonth" type="xsd:integer" minOccurs="0"/> + <xsd:element name="jccaTerminalID" type="xsd:integer" minOccurs="0"/> + <xsd:element name="issuerMessage" type="xsd:integer" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="Token"> + <xsd:sequence> + <xsd:element name="prefix" type="xsd:string" minOccurs="0"/> + <xsd:element name="suffix" type="xsd:string" minOccurs="0"/> + <xsd:element name="expirationMonth" type="xsd:string" minOccurs="0"/> + <xsd:element name="expirationYear" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + + <!-- Vme Reseller Service--> + <xsd:complexType name="AP"> + <xsd:sequence> + <xsd:element name="orderID" type="xsd:string" minOccurs="0"/> + <xsd:element name="pspBarcodeID" type="xsd:string" minOccurs="0"/> + <xsd:element name="customerRepresentativeID" type="xsd:string" minOccurs="0" /> + <xsd:element name="productDescription" type="xsd:string" minOccurs="0" /> + <xsd:element name="settlementCurrency" type="xsd:string" minOccurs="0" /> + <xsd:element name="subtotalAmount" type="xsd:string" minOccurs="0" /> + <xsd:element name="shippingAmount" type="xsd:string" minOccurs="0" /> + <xsd:element name="handlingAmount" type="xsd:string" minOccurs="0" /> + <xsd:element name="shippingHandlingAmount" type="xsd:string" minOccurs="0" /> + <xsd:element name="additionalAmount" type="xsd:string" minOccurs="0" /> + <xsd:element name="taxAmount" type="xsd:string" minOccurs="0" /> + <xsd:element name="giftWrapAmount" type="xsd:string" minOccurs="0" /> + <xsd:element name="discountAmount" type="xsd:string" minOccurs="0" /> + <xsd:element name="purchaseID" type="xsd:string" minOccurs="0" /> + <xsd:element name="productID" type="xsd:string" minOccurs="0" /> + <xsd:element name="device" type="tns:APDevice" minOccurs="0" /> + <xsd:element name="apiKey" type="xsd:string" minOccurs="0" /> + <xsd:element name="insuranceAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="billingAgreementIndicator" type="tns:boolean" minOccurs="0"/> + <xsd:element name="billingAgreementID" type="xsd:string" minOccurs="0" /> + <xsd:element name="billingAgreementDescription" type="xsd:string" minOccurs="0" /> + <xsd:element name="payerID" type="xsd:string" minOccurs="0" /> + <xsd:element name="fundingSource" type="xsd:string" minOccurs="0"/> + <xsd:element name="shippingAddressImmutable" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="APDevice"> + <xsd:sequence> + <xsd:element name="id" type="xsd:string" minOccurs="0" /> + <xsd:element name="type" type="xsd:string" minOccurs="0" /> + <xsd:element name="userAgent" type="xsd:string" minOccurs="0" /> + </xsd:sequence> + </xsd:complexType> + + <!-- apAuthService --> + <xsd:complexType name="APAuthService"> + <xsd:sequence> + <xsd:element name="cancelURL" type="xsd:string" minOccurs="0"/> + <xsd:element name="successURL" type="xsd:string" minOccurs="0"/> + <xsd:element name="failureURL" type="xsd:string" minOccurs="0"/> + <xsd:element name="overridePaymentMethod" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="preapprovalToken" type="xsd:string" minOccurs="0"/> + <xsd:element name="orderRequestID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <!-- End of apAuthService --> + + <!-- Start of AP Import Mandate Service --> + + + <xsd:complexType name="APImportMandateService"> + <xsd:sequence> + <xsd:element name="dateSigned" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + + <!-- End of of AP Import Mandate Service --> + + <!-- apAuthReversalService --> + <xsd:complexType name="APAuthReversalService"> + <xsd:sequence> + <xsd:element name="authRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <!-- End of apAuthReversalService --> + <!-- apCaptureService --> + <xsd:complexType name="APCaptureService"> + <xsd:sequence> + <xsd:element name="authRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="isFinal" type="tns:boolean" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <!-- End of apCaptureService --> + <!-- apOptionsService --> + <xsd:complexType name="APOptionsService"> + <xsd:sequence> + <xsd:element name="limit" type="xsd:string" minOccurs="0"/> + <xsd:element name="offset" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <!-- End of apOptionsService --> + <!-- apRefundService --> + <xsd:complexType name="APRefundService"> + <xsd:sequence> + <xsd:element name="captureRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="refundRequestID" type="xsd:string" minOccurs="0" /> + <xsd:element name="reason" type="xsd:string" minOccurs="0"/> + <xsd:element name="instant" type="xsd:string" minOccurs="0"/> + <xsd:element name="note" type="xsd:string" minOccurs="0"/> + <xsd:element name="apInitiateRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="returnRef" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="saleRequestID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <!-- End of apRefundService --> + <!-- apSaleService --> + <xsd:complexType name="APSaleService"> + <xsd:sequence> + <xsd:element name="cancelURL" type="xsd:string" minOccurs="0"/> + <xsd:element name="successURL" type="xsd:string" minOccurs="0"/> + <xsd:element name="failureURL" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="overridePaymentMethod" type="xsd:string" minOccurs="0"/> + <xsd:element name="paymentOptionID" type="xsd:string" minOccurs="0" /> + <xsd:element name="transactionTimeout" type="xsd:string" minOccurs="0" /> + <xsd:element name="orderRequestID" type="xsd:string" minOccurs="0" /> + <xsd:element name="billingAgreementID" type="xsd:string" minOccurs="0" /> + <xsd:element name="mandateID" type="xsd:string" minOccurs="0" /> + <xsd:element name="dateCollect" type="xsd:string" minOccurs="0" /> + <xsd:element name="preapprovalToken" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <!-- End of apAuthService --> + + <xsd:complexType name="APCheckOutDetailsService"> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <!-- End of apCheckoutDetailsService --> + <xsd:complexType name="APTransactionDetailsService"> + <xsd:sequence> + <xsd:element name="transactionDetailsRequestID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <!-- APConfirmPurchaseService --> + <xsd:complexType name="APConfirmPurchaseService"> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <!-- End of APConfirmPurchaseService --> + <xsd:complexType name="APSessionsService"> + <xsd:sequence> + <xsd:element name="cancelURL" type="xsd:string" minOccurs="0"/> + <xsd:element name="successURL" type="xsd:string" minOccurs="0"/> + <xsd:element name="failureURL" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="overridePaymentMethod" type="xsd:string" minOccurs="0"/> + <xsd:element name="paymentOptionID" type="xsd:string" minOccurs="0"/> + <xsd:element name="sessionsType" type="xsd:string" minOccurs="0"/> + <xsd:element name="sessionsRequestID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <!-- APUIStyle --> + <xsd:complexType name="APUI"> + <xsd:sequence> + <xsd:element name="colorBorder" type="xsd:string" minOccurs="0"/> + <xsd:element name="colorBorderSelected" type="xsd:string" minOccurs="0"/> + <xsd:element name="colorButton" type="xsd:string" minOccurs="0"/> + <xsd:element name="colorButtonText" type="xsd:string" minOccurs="0"/> + <xsd:element name="colorCheckbox" type="xsd:string" minOccurs="0"/> + <xsd:element name="colorCheckboxCheckMark" type="xsd:string" minOccurs="0"/> + <xsd:element name="colorHeader" type="xsd:string" minOccurs="0"/> + <xsd:element name="colorLink" type="xsd:string" minOccurs="0"/> + <xsd:element name="colorText" type="xsd:string" minOccurs="0"/> + <xsd:element name="borderRadius" type="xsd:string" minOccurs="0"/> + <xsd:element name="theme" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <!-- End of APUIStyle --> + <!--PayPalGetTxnDetails--> + <xsd:complexType name="PayPalGetTxnDetailsService"> + <xsd:sequence> + <xsd:element name="transactionID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <!-- End of PayPalGetTxnDetails --> + <!--PayPalTransactionSearch--> + <xsd:complexType name="PayPalTransactionSearchService"> + <xsd:sequence> + <xsd:element name="startDate" type="xsd:string" minOccurs="0"/> + <xsd:element name="endDate" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalCustomerEmail" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalReceiptId" type="xsd:string" minOccurs="0"/> + <xsd:element name="transactionID" type="xsd:string" minOccurs="0"/> + <xsd:element name="invoiceNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="grandTotalAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="currency" type="xsd:string" minOccurs="0"/> + <xsd:element name="paymentStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerSalutation" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerFirstname" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerMiddlename" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerLastname" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerSuffix" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <!-- End of PayPalTransactionSearch --> + <!-- Credit card recipient data --> + <xsd:complexType name="Recipient"> + <xsd:sequence> + <xsd:element name="dateOfBirth" type="xsd:string" minOccurs="0"/> + <xsd:element name="postalCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="accountID" type="xsd:string" minOccurs="0"/> + <xsd:element name="lastName" type="xsd:string" minOccurs="0"/> + <xsd:element name="name" type="xsd:string" minOccurs="0"/> + <xsd:element name="billingAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="billingCurrency" type="xsd:string" minOccurs="0"/> + <xsd:element name="billingConversionRate" type="tns:amount" minOccurs="0"/> + + <xsd:element name="firstName" type="xsd:string" minOccurs="0"/> + <xsd:element name="middleInitial" type="xsd:string" minOccurs="0"/> + <xsd:element name="address" type="xsd:string" minOccurs="0"/> + <xsd:element name="city" type="xsd:string" minOccurs="0"/> + <xsd:element name="state" type="xsd:string" minOccurs="0"/> + <xsd:element name="country" type="xsd:string" minOccurs="0"/> + <xsd:element name="phoneNumber" type="xsd:string" minOccurs="0"/> + + + </xsd:sequence> + </xsd:complexType> + <!-- End of Credit card recipient data --> + <xsd:complexType name="Sender"> + <xsd:sequence> + <xsd:element name="referenceNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="sourceOfFunds" type="xsd:string" minOccurs="0"/> + <xsd:element name="name" type="xsd:string" minOccurs="0"/> + <xsd:element name="address" type="xsd:string" minOccurs="0"/> + <xsd:element name="city" type="xsd:string" minOccurs="0"/> + <xsd:element name="state" type="xsd:string" minOccurs="0"/> + <xsd:element name="postalCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="country" type="xsd:string" minOccurs="0"/> + <xsd:element name="accountNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="dateOfBirth" type="xsd:string" minOccurs="0"/> + + <xsd:element name="firstName" type="xsd:string" minOccurs="0"/> + <xsd:element name="middleInitial" type="xsd:string" minOccurs="0"/> + <xsd:element name="lastName" type="xsd:string" minOccurs="0"/> + <xsd:element name="phoneNumber" type="xsd:string" minOccurs="0"/> + + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="CCCheckStatusService"> + <xsd:sequence> + <xsd:element name="authRequestID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="RequestMessage"> + <xsd:sequence> + <xsd:element name="merchantID" type="xsd:string" + minOccurs="0" /> + <xsd:element name="merchantReferenceCode" type="xsd:string" + minOccurs="0" /> + <xsd:element name="debtIndicator" type="tns:boolean" + minOccurs="0" /> + <xsd:element name="clientLibrary" type="xsd:string" + minOccurs="0" /> + <xsd:element name="clientLibraryVersion" type="xsd:string" + minOccurs="0" /> + <xsd:element name="clientEnvironment" type="xsd:string" + minOccurs="0" /> + <xsd:element name="clientSecurityLibraryVersion" + type="xsd:string" minOccurs="0" /> + <xsd:element name="clientApplication" type="xsd:string" + minOccurs="0" /> + <xsd:element name="clientApplicationVersion" + type="xsd:string" minOccurs="0" /> + <xsd:element name="clientApplicationUser" type="xsd:string" + minOccurs="0" /> + <xsd:element name="routingCode" type="xsd:string" + minOccurs="0" /> + <xsd:element name="comments" type="xsd:string" + minOccurs="0" /> + <xsd:element name="returnURL" type="xsd:string" + minOccurs="0" /> + <xsd:element name="invoiceHeader" type="tns:InvoiceHeader" + minOccurs="0" /> + <xsd:element name="paymentScheme" type="xsd:string" minOccurs="0"/> + <xsd:element name="mandateID" type="xsd:string" minOccurs="0"/> + <xsd:element name="aggregatorMerchantIdentifier" type="xsd:string" minOccurs="0"/> + <xsd:element name="customerID" type="xsd:string" minOccurs="0"/> + <xsd:element name="customerFirstName" type="xsd:string" minOccurs="0"/> + <xsd:element name="customerLastName" type="xsd:string" minOccurs="0"/> + <xsd:element name="billTo" type="tns:BillTo" minOccurs="0" /> + <xsd:element name="shipTo" type="tns:ShipTo" minOccurs="0" /> + <xsd:element name="personalID" type="tns:PersonalID" minOccurs="0" /> + <xsd:element name="shipFrom" type="tns:ShipFrom" + minOccurs="0" /> + <xsd:element name="item" type="tns:Item" minOccurs="0" + maxOccurs="1000" /> + <xsd:element name="purchaseTotals" type="tns:PurchaseTotals" + minOccurs="0" /> + <xsd:element name="fundingTotals" type="tns:FundingTotals" + minOccurs="0" /> + <xsd:element name="dcc" type="tns:DCC" minOccurs="0" /> + <xsd:element name="pos" type="tns:Pos" minOccurs="0" /> + <xsd:element name="pin" type="tns:Pin" minOccurs="0" /> + <xsd:element name="encryptedPayment" type="tns:EncryptedPayment" minOccurs="0" /> + <xsd:element name="installment" type="tns:Installment" + minOccurs="0" /> + <xsd:element name="card" type="tns:Card" minOccurs="0" /> + <xsd:element name="category" type="tns:Category" minOccurs="0" /> + <xsd:element name="check" type="tns:Check" minOccurs="0" /> + <xsd:element name="bml" type="tns:BML" minOccurs="0" /> + <xsd:element name="gecc" type="tns:GECC" minOccurs="0" /> + <xsd:element name="ucaf" type="tns:UCAF" minOccurs="0" /> + <xsd:element name="fundTransfer" type="tns:FundTransfer" + minOccurs="0" /> + <xsd:element name="bankInfo" type="tns:BankInfo" + minOccurs="0" /> + <xsd:element name="subscription" type="tns:Subscription" + minOccurs="0" /> + <xsd:element name="recurringSubscriptionInfo" + type="tns:RecurringSubscriptionInfo" minOccurs="0" /> + <xsd:element name="tokenSource" + type="tns:TokenSource" minOccurs="0" /> + <xsd:element name="decisionManager" + type="tns:DecisionManager" minOccurs="0" /> + <xsd:element name="otherTax" type="tns:OtherTax" + minOccurs="0" /> + <xsd:element name="paypal" type="tns:PayPal" minOccurs="0" /> + <xsd:element name="merchantDefinedData" + type="tns:MerchantDefinedData" minOccurs="0" /> + <xsd:element name="merchantSecureData" + type="tns:MerchantSecureData" minOccurs="0" /> + <xsd:element name="jpo" type="tns:JPO" minOccurs="0" /> + <xsd:element name="orderRequestToken" type="xsd:string" + minOccurs="0" /> + <xsd:element name="linkToRequest" type="xsd:string" + minOccurs="0" /> + <xsd:element name="serviceFee" type="tns:ServiceFee" minOccurs="0" /> + <xsd:element name="giftCard" type="tns:GiftCard" minOccurs="0" /> + <xsd:element name="ccAuthService" type="tns:CCAuthService" + minOccurs="0" /> + <xsd:element name="octService" type="tns:OCTService" + minOccurs="0" /> + <xsd:element name="ecAVSService" type="tns:ECAVSService" + minOccurs="0" /> + + <xsd:element name="giftCardActivationService" type="tns:GiftCardActivationService" + minOccurs="0" /> + <xsd:element name="giftCardBalanceInquiryService" type="tns:GiftCardBalanceInquiryService" + minOccurs="0" /> + <xsd:element name="giftCardRedemptionService" type="tns:GiftCardRedemptionService" + minOccurs="0" /> + <xsd:element name="giftCardVoidService" type="tns:GiftCardVoidService" + minOccurs="0" /> + <xsd:element name="giftCardReversalService" type="tns:GiftCardReversalService" + minOccurs="0" /> + <xsd:element name="verificationService" type="tns:VerificationService" minOccurs="0" /> + <xsd:element name="ccSaleService" type="tns:CCSaleService" minOccurs="0" /> + + <xsd:element name="ccSaleCreditService" type="tns:CCSaleCreditService" minOccurs="0" /> + + <xsd:element name="ccSaleReversalService" type="tns:CCSaleReversalService" minOccurs="0" /> + <xsd:element name="ccIncrementalAuthService" type="tns:CCIncrementalAuthService" minOccurs="0" /> + <xsd:element name="ccCaptureService" + type="tns:CCCaptureService" minOccurs="0" /> + <xsd:element name="ccCreditService" + type="tns:CCCreditService" minOccurs="0" /> + <xsd:element name="ccAuthReversalService" + type="tns:CCAuthReversalService" minOccurs="0" /> + <xsd:element name="ccAutoAuthReversalService" + type="tns:CCAutoAuthReversalService" minOccurs="0" /> + <xsd:element name="ccDCCService" type="tns:CCDCCService" + minOccurs="0" /> + <xsd:element name="serviceFeeCalculateService" type="tns:ServiceFeeCalculateService" + minOccurs="0" /> + <xsd:element name="ecDebitService" type="tns:ECDebitService" + minOccurs="0" /> + <xsd:element name="ecCreditService" + type="tns:ECCreditService" minOccurs="0" /> + <xsd:element name="ecAuthenticateService" + type="tns:ECAuthenticateService" minOccurs="0" /> + <xsd:element name="payerAuthEnrollService" + type="tns:PayerAuthEnrollService" minOccurs="0" /> + <xsd:element name="payerAuthValidateService" + type="tns:PayerAuthValidateService" minOccurs="0" /> + <xsd:element name="taxService" type="tns:TaxService" + minOccurs="0" /> + <xsd:element name="dmeService" type="tns:DMEService" + minOccurs="0" /> + <xsd:element name="afsService" type="tns:AFSService" + minOccurs="0" /> + <xsd:element name="davService" type="tns:DAVService" + minOccurs="0" /> + <xsd:element name="exportService" type="tns:ExportService" + minOccurs="0" /> + <xsd:element name="fxRatesService" type="tns:FXRatesService" + minOccurs="0" /> + <xsd:element name="bankTransferService" + type="tns:BankTransferService" minOccurs="0" /> + <xsd:element name="bankTransferRefundService" + type="tns:BankTransferRefundService" minOccurs="0" /> + <xsd:element name="bankTransferRealTimeService" + type="tns:BankTransferRealTimeService" minOccurs="0" /> + <xsd:element name="directDebitMandateService" + type="tns:DirectDebitMandateService" minOccurs="0" /> + <xsd:element name="directDebitService" + type="tns:DirectDebitService" minOccurs="0" /> + <xsd:element name="directDebitRefundService" + type="tns:DirectDebitRefundService" minOccurs="0" /> + <xsd:element name="directDebitValidateService" + type="tns:DirectDebitValidateService" minOccurs="0" /> + <xsd:element name="deviceFingerprintData" + type="tns:DeviceFingerprintData" minOccurs="0" maxOccurs="10" /> + <xsd:element name="paySubscriptionCreateService" + type="tns:PaySubscriptionCreateService" minOccurs="0" /> + <xsd:element name="paySubscriptionUpdateService" + type="tns:PaySubscriptionUpdateService" minOccurs="0" /> + <xsd:element name="paySubscriptionEventUpdateService" + type="tns:PaySubscriptionEventUpdateService" minOccurs="0" /> + <xsd:element name="paySubscriptionRetrieveService" + type="tns:PaySubscriptionRetrieveService" minOccurs="0" /> + <xsd:element name="paySubscriptionDeleteService" + type="tns:PaySubscriptionDeleteService" minOccurs="0" /> + <xsd:element name="payPalPaymentService" + type="tns:PayPalPaymentService" minOccurs="0" /> + <xsd:element name="payPalCreditService" + type="tns:PayPalCreditService" minOccurs="0" /> + <xsd:element name="voidService" type="tns:VoidService" + minOccurs="0" /> + <xsd:element name="businessRules" type="tns:BusinessRules" + minOccurs="0" /> + <xsd:element name="pinlessDebitService" + type="tns:PinlessDebitService" minOccurs="0" /> + <xsd:element name="pinlessDebitValidateService" + type="tns:PinlessDebitValidateService" minOccurs="0" /> + <xsd:element name="pinlessDebitReversalService" + type="tns:PinlessDebitReversalService" minOccurs="0" /> + <xsd:element name="batch" type="tns:Batch" minOccurs="0" /> + <xsd:element name="airlineData" type="tns:AirlineData" + minOccurs="0" /> + <xsd:element name="ancillaryData" type="tns:AncillaryData" + minOccurs="0" /> + <xsd:element name="lodgingData" type="tns:LodgingData" minOccurs="0" /> + <xsd:element name="payPalButtonCreateService" + type="tns:PayPalButtonCreateService" minOccurs="0" /> + <xsd:element name="payPalPreapprovedPaymentService" + type="tns:PayPalPreapprovedPaymentService" minOccurs="0" /> + <xsd:element name="payPalPreapprovedUpdateService" + type="tns:PayPalPreapprovedUpdateService" minOccurs="0" /> + <xsd:element name="riskUpdateService" + type="tns:RiskUpdateService" minOccurs="0" /> + <xsd:element name="fraudUpdateService" + type="tns:FraudUpdateService" minOccurs="0" /> + <xsd:element name="caseManagementActionService" + type="tns:CaseManagementActionService" minOccurs="0" /> + <xsd:element name="reserved" type="tns:RequestReserved" + minOccurs="0" maxOccurs="999" /> + <xsd:element name="deviceFingerprintID" type="xsd:string" + minOccurs="0" /> + <xsd:element name="deviceFingerprintRaw" type="tns:boolean" + minOccurs="0" /> + <xsd:element name="deviceFingerprintHash" type="xsd:string" + minOccurs="0" /> + <xsd:element name="payPalRefundService" + type="tns:PayPalRefundService" minOccurs="0" /> + <xsd:element name="payPalAuthReversalService" + type="tns:PayPalAuthReversalService" minOccurs="0" /> + <xsd:element name="payPalDoCaptureService" + type="tns:PayPalDoCaptureService" minOccurs="0" /> + <xsd:element name="payPalEcDoPaymentService" + type="tns:PayPalEcDoPaymentService" minOccurs="0" /> + <xsd:element name="payPalEcGetDetailsService" + type="tns:PayPalEcGetDetailsService" minOccurs="0" /> + <xsd:element name="payPalEcSetService" + type="tns:PayPalEcSetService" minOccurs="0" /> + <xsd:element name="payPalEcOrderSetupService" + type="tns:PayPalEcOrderSetupService" minOccurs="0" /> + <xsd:element name="payPalAuthorizationService" + type="tns:PayPalAuthorizationService" minOccurs="0" /> + <xsd:element name="payPalUpdateAgreementService" + type="tns:PayPalUpdateAgreementService" minOccurs="0" /> + <xsd:element name="payPalCreateAgreementService" + type="tns:PayPalCreateAgreementService" minOccurs="0" /> + <xsd:element name="payPalDoRefTransactionService" + type="tns:PayPalDoRefTransactionService" minOccurs="0" /> + <xsd:element name="chinaPaymentService" + type="tns:ChinaPaymentService" minOccurs="0" /> + <xsd:element name="chinaRefundService" + type="tns:ChinaRefundService" minOccurs="0" /> + <xsd:element name="boletoPaymentService" + type="tns:BoletoPaymentService" minOccurs="0" /> + <xsd:element name="apPaymentType" type="xsd:string" + minOccurs="0"/> + <xsd:element name="apInitiateService" + type="tns:APInitiateService" minOccurs="0" /> + <xsd:element name="apCheckStatusService" + type="tns:APCheckStatusService" minOccurs="0" /> + <xsd:element name="ignoreCardExpiration" type="tns:boolean" + minOccurs="0" /> + <xsd:element name="reportGroup" type="xsd:string" + minOccurs="0" /> + <xsd:element name="processorID" type="xsd:string" + minOccurs="0" /> + <xsd:element name="thirdPartyCertificationNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="transactionLocalDateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="solutionProviderTransactionID" type="xsd:string" minOccurs="0" /> + <xsd:element name="surchargeAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="surchargeSign" type="xsd:string" minOccurs="0"/> + <xsd:element name="pinDataEncryptedPIN" type="xsd:string" minOccurs="0"/> + <xsd:element name="pinDataKeySerialNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="pinDataPinBlockEncodingFormat" type="xsd:integer" minOccurs="0"/> + <xsd:element name="cashbackAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="pinDebitPurchaseService" type="tns:PinDebitPurchaseService" minOccurs="0"/> + <xsd:element name="pinDebitCreditService" type="tns:PinDebitCreditService" minOccurs="0"/> + <xsd:element name="pinDebitReversalService" type="tns:PinDebitReversalService" minOccurs="0"/> + <xsd:element name="ap" type="tns:AP" minOccurs="0" /> + <xsd:element name="apAuthService" type="tns:APAuthService" minOccurs="0" /> + <xsd:element name="apAuthReversalService" type="tns:APAuthReversalService" minOccurs="0" /> + <xsd:element name="apCaptureService" type="tns:APCaptureService" minOccurs="0" /> + <xsd:element name="apOptionsService" type="tns:APOptionsService" minOccurs="0" /> + <xsd:element name="apRefundService" type="tns:APRefundService" minOccurs="0" /> + <xsd:element name="apSaleService" type="tns:APSaleService" minOccurs="0" /> + <xsd:element name="apCheckoutDetailsService" type="tns:APCheckOutDetailsService" minOccurs="0" /> + <xsd:element name="apSessionsService" type="tns:APSessionsService" minOccurs="0" /> + <xsd:element name="apUI" type="tns:APUI" minOccurs="0" /> + <xsd:element name="apTransactionDetailsService" type="tns:APTransactionDetailsService" minOccurs="0" /> + <xsd:element name="apConfirmPurchaseService" type="tns:APConfirmPurchaseService" minOccurs="0" /> + <xsd:element name="payPalGetTxnDetailsService" type="tns:PayPalGetTxnDetailsService" minOccurs="0" /> + <xsd:element name="payPalTransactionSearchService" type="tns:PayPalTransactionSearchService" minOccurs="0" /> + <xsd:element name="ccDCCUpdateService" type="tns:CCDCCUpdateService" minOccurs="0"/> + <xsd:element name="emvRequest" type="tns:EmvRequest" minOccurs="0" /> + <xsd:element name="merchantTransactionIdentifier" type="xsd:string" minOccurs="0" /> + <xsd:element name="hostedDataCreateService" type="tns:HostedDataCreateService" minOccurs="0"/> + <xsd:element name="hostedDataRetrieveService" type="tns:HostedDataRetrieveService" minOccurs="0"/> + <xsd:element name="merchantCategoryCode" type="xsd:string" minOccurs="0" /> + <xsd:element name="merchantCategoryCodeDomestic" type="xsd:string" minOccurs="0" /> + <xsd:element name="salesSlipNumber" type="xsd:string" minOccurs="0" /> + <xsd:element name="merchandiseCode" type="xsd:string" minOccurs="0" /> + <xsd:element name="merchandiseDescription" type="xsd:string" minOccurs="0" /> + <xsd:element name="paymentInitiationChannel" type="xsd:string" minOccurs="0" /> + <xsd:element name="extendedCreditTotalCount" type="xsd:string" minOccurs="0" /> + <xsd:element name="authIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="paymentNetworkToken" type="tns:PaymentNetworkToken" minOccurs="0"/> + <xsd:element name="recipient" type="tns:Recipient" minOccurs="0"/> + <xsd:element name="sender" type="tns:Sender" minOccurs="0"/> + <xsd:element name="autoRentalData" type="tns:AutoRentalData" minOccurs="0" /> + <xsd:element name="paymentSolution" type="xsd:string" minOccurs="0" /> + <xsd:element name="vc" type="tns:VC" minOccurs="0" /> + <xsd:element name="decryptVisaCheckoutDataService" type="tns:DecryptVisaCheckoutDataService" minOccurs="0" /> + <xsd:element name="taxManagementIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="promotionGroup" type="tns:PromotionGroup" minOccurs="0" maxOccurs="100"/> + <xsd:element name="wallet" type="tns:Wallet" minOccurs="0" /> + <xsd:element name="aft" type="tns:Aft" minOccurs="0" /> + <xsd:element name="balanceInquiry" type="tns:boolean" minOccurs="0" /> + <xsd:element name="prenoteTransaction" type="tns:boolean" minOccurs="0"/> + <xsd:element name="encryptPaymentDataService" type="tns:EncryptPaymentDataService" minOccurs="0"/> + <xsd:element name="nationalNetDomesticData" type="xsd:string" minOccurs="0"/> + <xsd:element name="subsequentAuth" type="xsd:string" minOccurs="0"/> + <xsd:element name="subsequentAuthOriginalAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="binLookupService" type="tns:BinLookupService" minOccurs="0" /> + <xsd:element name="verificationCode" type="xsd:string" minOccurs="0" /> + <xsd:element name="mobileNumber" type="xsd:string" minOccurs="0" /> + <xsd:element name="issuer" type="tns:issuer" minOccurs="0" /> + <xsd:element name="partnerSolutionID" type="xsd:string" minOccurs="0" /> + <xsd:element name="developerID" type="xsd:string" minOccurs="0" /> + <xsd:element name="getVisaCheckoutDataService" type="tns:GETVisaCheckoutDataService" minOccurs="0" /> + <xsd:element name="customerSignatureImage" type="xsd:string" minOccurs="0"/> + <xsd:element name="transactionMetadataService" type="tns:TransactionMetadataService" minOccurs="0" /> + <xsd:element name="subsequentAuthFirst" type="xsd:string" minOccurs="0"/> + <xsd:element name="subsequentAuthReason" type="xsd:string" minOccurs="0"/> + <xsd:element name="subsequentAuthTransactionID" type="xsd:string" minOccurs="0"/> + <xsd:element name="subsequentAuthStoredCredential" type="xsd:string" minOccurs="0"/> + <xsd:element name="loan" type="tns:Loan" minOccurs="0" /> + <xsd:element name="eligibilityInquiry" type="xsd:string" minOccurs="0" /> + <xsd:element name="redemptionInquiry" type="xsd:string" minOccurs="0" /> + <xsd:element name="feeProgramIndicator" type="xsd:string" minOccurs="0" /> + <xsd:element name="apOrderService" type="tns:APOrderService" minOccurs="0" /> + <xsd:element name="apCancelService" type="tns:APCancelService" minOccurs="0" /> + <xsd:element name="apBillingAgreementService" type="tns:APBillingAgreementService" minOccurs="0" /> + <xsd:element name="note_toPayee" type="xsd:string" minOccurs="0" /> + <xsd:element name="note_toPayer" type="xsd:string" minOccurs="0" /> + <xsd:element name="clientMetadataID" type="xsd:string" minOccurs="0" /> + + <xsd:element name="partnerSDKversion" type="xsd:string" minOccurs="0" /> + <xsd:element name="partnerOriginalTransactionID" type="xsd:string" minOccurs="0" /> + <xsd:element name="cardTypeSelectionIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="apCreateMandateService" type="tns:APCreateMandateService" minOccurs="0" /> + <xsd:element name="apMandateStatusService" type="tns:APMandateStatusService" minOccurs="0" /> + <xsd:element name="apUpdateMandateService" type="tns:APUpdateMandateService" minOccurs="0" /> + <xsd:element name="apImportMandateService" type="tns:APImportMandateService" minOccurs="0" /> + <xsd:element name="apRevokeMandateService" type="tns:APRevokeMandateService" minOccurs="0" /> + <xsd:element name="billPaymentType" type="xsd:string" minOccurs="0" /> + <xsd:element name="postdatedTransaction" type="tns:PostdatedTransaction" minOccurs="0" /> + <xsd:element name="getMasterpassDataService" type="tns:GetMasterpassDataService" minOccurs="0" /> + <xsd:element name="ccCheckStatusService" type="tns:CCCheckStatusService" + minOccurs="0" /> + <xsd:element name="mPOS" type="tns:mPOS" minOccurs="0" /> + </xsd:sequence> + + </xsd:complexType> + + <!-- added for Visa Checkout --> + <xsd:complexType name="VC"> + <xsd:sequence> + <xsd:element name="orderID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="DecryptVisaCheckoutDataService"> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + + <xsd:complexType name="DCC"> + <xsd:sequence> + <xsd:element name="dccIndicator" type="xsd:integer" minOccurs="0"/> + <xsd:element name="referenceNumber" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="Promotion"> + <xsd:sequence> + <xsd:element name="discountedAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="type" type="xsd:string" minOccurs="0"/> + <xsd:element name="code" type="xsd:string" minOccurs="0"/> + <xsd:element name="receiptData" type="xsd:string" minOccurs="0"/> + <xsd:element name="discountApplied" type="tns:amount" minOccurs="0"/> + <xsd:element name="description" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="PromotionGroup"> + <xsd:sequence> + <xsd:element name="subtotalAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="taxRate" type="tns:amount" minOccurs="0"/> + <xsd:element name="prohibitDiscount" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="id" type="xsd:integer" use="optional"/> + </xsd:complexType> + + <xsd:complexType name="PromotionGroupReply"> + <xsd:sequence> + <xsd:element name="discountApplied" type="tns:amount" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="id" type="xsd:integer" use="optional"/> + </xsd:complexType> + + <xsd:complexType name="CCAuthReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="authorizationCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="avsCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="avsCodeRaw" type="xsd:string" minOccurs="0"/> + <xsd:element name="cvCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="cvCodeRaw" type="xsd:string" minOccurs="0"/> + <xsd:element name="personalIDCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="authorizedDateTime" type="tns:dateTime" minOccurs="0"/> + <!-- dateTime --> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="bmlAccountNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="authFactorCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="fundingTotals" type="tns:FundingTotals" minOccurs="0"/> + <xsd:element name="fxQuoteID" type="xsd:string" minOccurs="0"/> + <xsd:element name="fxQuoteRate" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="fxQuoteType" type="xsd:string" minOccurs="0"/> + <xsd:element name="fxQuoteExpirationDateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="authRecord" type="xsd:string" minOccurs="0"/> + <xsd:element name="merchantAdviceCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="merchantAdviceCodeRaw" type="xsd:string" minOccurs="0"/> + <xsd:element name="cavvResponseCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="cavvResponseCodeRaw" type="xsd:string" minOccurs="0"/> + <xsd:element name="authenticationXID" type="xsd:string" minOccurs="0"/> + <xsd:element name="authorizationXID" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorCardType" type="xsd:string" minOccurs="0"/> + <xsd:element name="accountBalance" type="tns:amount" minOccurs="0"/> + <xsd:element name="forwardCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="enhancedDataEnabled" type="xsd:string" minOccurs="0"/> + <xsd:element name="referralResponseNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="subResponseCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="approvedAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="creditLine" type="xsd:string" minOccurs="0"/> + <xsd:element name="approvedTerms" type="xsd:string" minOccurs="0"/> + <xsd:element name="paymentNetworkTransactionID" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardCategory" type="xsd:string" minOccurs="0"/> + <xsd:element name="ownerMerchantID" type="xsd:string" minOccurs="0"/> + <xsd:element name="requestAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="requestCurrency" type="xsd:string" minOccurs="0"/> + <xsd:element name="accountBalanceCurrency" type="xsd:string" minOccurs="0"/> + <xsd:element name="accountBalanceSign" type="xsd:string" minOccurs="0"/> + <xsd:element name="amountType" type="xsd:string" minOccurs="0"/> + <xsd:element name="accountType" type="xsd:string" minOccurs="0"/> + <xsd:element name="affluenceIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="evEmail" type="xsd:string" minOccurs="0"/> + <xsd:element name="evPhoneNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="evPostalCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="evName" type="xsd:string" minOccurs="0"/> + <xsd:element name="evStreet" type="xsd:string" minOccurs="0"/> + <xsd:element name="evEmailRaw" type="xsd:string" minOccurs="0"/> + <xsd:element name="evPhoneNumberRaw" type="xsd:string" minOccurs="0"/> + <xsd:element name="evPostalCodeRaw" type="xsd:string" minOccurs="0"/> + <xsd:element name="evNameRaw" type="xsd:string" minOccurs="0"/> + <xsd:element name="evStreetRaw" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardGroup" type="xsd:string" minOccurs="0"/> + <xsd:element name="posData" type="xsd:string" minOccurs="0"/> + <xsd:element name="transactionID" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardIssuerCountry" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardRegulated" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardCommercial" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardPrepaid" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardPayroll" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardHealthcare" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardSignatureDebit" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardPINlessDebit" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardLevel3Eligible" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorTransactionID" type="xsd:string" minOccurs="0"/> + <xsd:element name="providerReasonCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="providerReasonDescription" type="xsd:string" minOccurs="0"/> + <xsd:element name="providerPassThroughData" type="xsd:string" minOccurs="0"/> + <xsd:element name="providerCVNResponseCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="providerAVSResponseCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="providerAcquirerBankCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="paymentCardService" type="xsd:string" minOccurs="0"/> + <xsd:element name="paymentCardServiceResult" type="xsd:string" minOccurs="0"/> + <xsd:element name="transactionQualification" type="xsd:string" minOccurs="0"/> + <xsd:element name="transactionIntegrity" type="xsd:string" minOccurs="0"/> + <xsd:element name="emsTransactionRiskScore" type="xsd:string" minOccurs="0" /> + <xsd:element name="reconciliationReferenceNumber" type="xsd:string" minOccurs="0" /> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="OCTReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="requestDateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="approvalCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="paymentNetworkTransactionID" type="xsd:string" minOccurs="0"/> + <xsd:element name="prepaidBalanceCurrency" type="xsd:string" minOccurs="0"/> + <xsd:element name="prepaidBalanceAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorResponseSource" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationIdType" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="VerificationReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer" /> + <xsd:element name="processorTransactionID" type="xsd:string" minOccurs="0" /> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0" /> + <xsd:element name="verifiedDateTime" type="xsd:string" minOccurs="0" /> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0" /> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="CCSaleReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="authorizationCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="avsCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="avsCodeRaw" type="xsd:string" minOccurs="0"/> + <xsd:element name="cvCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="cvCodeRaw" type="xsd:string" minOccurs="0"/> + <xsd:element name="cavvResponseCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="cavvResponseCodeRaw" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardGroup" type="xsd:string" minOccurs="0"/> + <xsd:element name="paymentNetworkTransactionID" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardCategory" type="xsd:string" minOccurs="0"/> + <xsd:element name="accountBalance" type="tns:amount" minOccurs="0"/> + <xsd:element name="authorizedDateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="requestAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="accountBalanceCurrency" type="xsd:string" minOccurs="0"/> + <xsd:element name="accountBalanceSign" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="CCSaleCreditReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="authorizationCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="authorizedDateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="paymentNetworkTransactionID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="CCSaleReversalReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="authorizationCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="requestDateTime" type="tns:dateTime" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="CCIncrementalAuthReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="amount" type="tns:amount" minOccurs="0" /> + <xsd:element name="authorizationCode" type="xsd:string" minOccurs="0" /> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0" /> + <xsd:element name="authorizedDateTime" type="tns:dateTime" minOccurs="0" /> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0" /> + <xsd:element name="paymentNetworkTransactionID" type="xsd:string" minOccurs="0" /> + <xsd:element name="cardCategory" type="xsd:string" minOccurs="0" /> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="CCCaptureReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="requestDateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="fundingTotals" type="tns:FundingTotals" minOccurs="0"/> + <xsd:element name="fxQuoteID" type="xsd:string" minOccurs="0"/> + <xsd:element name="fxQuoteRate" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="fxQuoteType" type="xsd:string" minOccurs="0"/> + <xsd:element name="fxQuoteExpirationDateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="purchasingLevel3Enabled" type="xsd:string" minOccurs="0"/> + <xsd:element name="enhancedDataEnabled" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationReferenceNumber" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="ServiceFeeCalculateReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer" /> + <xsd:element name="amount" type="tns:amount" minOccurs="0" /> + <xsd:element name="requestDateTime" type="tns:dateTime" minOccurs="0" /> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="CCCreditReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="requestDateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="purchasingLevel3Enabled" type="xsd:string" minOccurs="0"/> + <xsd:element name="enhancedDataEnabled" type="xsd:string" minOccurs="0"/> + <xsd:element name="authorizationXID" type="xsd:string" minOccurs="0"/> + <xsd:element name="forwardCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="ownerMerchantID" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationReferenceNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="authorizationCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="paymentNetworkTransactionID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="PinDebitPurchaseReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="authorizationCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="networkCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="transactionID" type="xsd:string" minOccurs="0"/> + <xsd:element name="requestAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="requestCurrency" type="xsd:string" minOccurs="0"/> + <xsd:element name="amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="dateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="accountType" type="xsd:string" minOccurs="0"/> + <xsd:element name="amountType" type="xsd:string" minOccurs="0"/> + <xsd:element name="accountBalance" type="xsd:string" minOccurs="0"/> + <xsd:element name="accountBalanceCurrency" type="xsd:string" minOccurs="0"/> + <xsd:element name="accountBalanceSign" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="PinDebitCreditReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="authorizationCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="networkCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="transactionID" type="xsd:string" minOccurs="0"/> + <xsd:element name="amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="dateTime" type="tns:dateTime" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="PinDebitReversalReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="dateTime" type="tns:dateTime" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="CCAuthReversalReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="authorizationCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="requestDateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="forwardCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorTransactionID" type="xsd:string" minOccurs="0"/> + <xsd:element name="paymentCardService" type="xsd:string" minOccurs="0"/> + <xsd:element name="paymentCardServiceResult" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="CCAutoAuthReversalReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="result" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="ECAVSReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="validationType" type="xsd:string" minOccurs="0"/> + <xsd:element name="primaryStatusCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="secondaryStatusCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="additionalStatusCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="numberOfReturns" type="xsd:string" minOccurs="0"/> + <xsd:element name="lastReturnDate" type="xsd:string" minOccurs="0"/> + <xsd:element name="lastReturnProcessorResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="lastUpdateDate" type="xsd:string" minOccurs="0"/> + <xsd:element name="addedOrClosedDate" type="xsd:string" minOccurs="0"/> + <xsd:element name="previousStatusCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="fcraDisputeCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="scoredAccountProcessorResponse1" type="xsd:string" minOccurs="0"/> + <xsd:element name="scoredAccountProcessorResponse2" type="xsd:string" minOccurs="0"/> + <xsd:element name="scoredAccountProcessorResponse3" type="xsd:string" minOccurs="0"/> + <xsd:element name="scoredAccountProcessorResponse5" type="xsd:string" minOccurs="0"/> + <xsd:element name="customerDataConditionCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="matchBillToFullName" type="xsd:string" minOccurs="0"/> + <xsd:element name="matchBillToPrefix" type="xsd:string" minOccurs="0"/> + <xsd:element name="matchBillToFirstName" type="xsd:string" minOccurs="0"/> + <xsd:element name="matchBillToMiddleName" type="xsd:string" minOccurs="0"/> + <xsd:element name="matchBillToLastName" type="xsd:string" minOccurs="0"/> + <xsd:element name="matchBillToSuffix" type="xsd:string" minOccurs="0"/> + <xsd:element name="matchBillToCompany" type="xsd:string" minOccurs="0"/> + <xsd:element name="matchBillToAddress" type="xsd:string" minOccurs="0"/> + <xsd:element name="matchBillToCity" type="xsd:string" minOccurs="0"/> + <xsd:element name="matchBillToState" type="xsd:string" minOccurs="0"/> + <xsd:element name="matchBillToPostalCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="matchBillToPhoneNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="matchBillToCompanyPhoneNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="matchBillToCompanyTaxID" type="xsd:string" minOccurs="0"/> + <xsd:element name="matchBillToSSN" type="xsd:string" minOccurs="0"/> + <xsd:element name="matchBillToDateOfBirth" type="xsd:string" minOccurs="0"/> + <xsd:element name="matchPersonalIDType" type="xsd:string" minOccurs="0"/> + <xsd:element name="matchPersonalID" type="xsd:string" minOccurs="0"/> + <xsd:element name="matchPersonalIDIssuedBy" type="xsd:string" minOccurs="0"/> + <xsd:element name="overallMatchScore" type="xsd:integer" minOccurs="0"/> + <xsd:element name="calculatedResponse" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="ECDebitReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="settlementMethod" type="xsd:string" minOccurs="0"/> + <xsd:element name="requestDateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="verificationLevel" type="xsd:integer" minOccurs="0"/> + <xsd:element name="processorTransactionID" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="avsCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="avsCodeRaw" type="xsd:string" minOccurs="0"/> + <xsd:element name="verificationCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="verificationCodeRaw" type="xsd:string" minOccurs="0"/> + <xsd:element name="correctedAccountNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="correctedRoutingNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="ownerMerchantID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="ECCreditReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="settlementMethod" type="xsd:string" minOccurs="0"/> + <xsd:element name="requestDateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="processorTransactionID" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="verificationCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="verificationCodeRaw" type="xsd:string" minOccurs="0"/> + <xsd:element name="correctedAccountNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="correctedRoutingNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="ownerMerchantID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="ECAuthenticateReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="requestDateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="checkpointSummary" type="xsd:string" minOccurs="0"/> + <xsd:element name="fraudShieldIndicators" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="PayerAuthEnrollReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="acsURL" type="xsd:string" minOccurs="0"/> + <xsd:element name="authenticationResult" type="xsd:string" minOccurs="0"/> + <xsd:element name="authenticationStatusMessage" type="xsd:string" minOccurs="0"/> + <xsd:element name="cavv" type="xsd:string" minOccurs="0"/> + <xsd:element name="cavvAlgorithm" type="xsd:string" minOccurs="0"/> + <xsd:element name="commerceIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="eci" type="xsd:string" minOccurs="0"/> + <xsd:element name="eciRaw" type="xsd:string" minOccurs="0"/> + <xsd:element name="paReq" type="xsd:string" minOccurs="0"/> + <xsd:element name="proxyPAN" type="xsd:string" minOccurs="0"/> + <xsd:element name="xid" type="xsd:string" minOccurs="0"/> + <xsd:element name="proofXML" type="xsd:string" minOccurs="0"/> + <xsd:element name="ucafAuthenticationData" type="xsd:string" minOccurs="0"/> + <xsd:element name="ucafCollectionIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="paresStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="veresEnrolled" type="xsd:string" minOccurs="0"/> + <xsd:element name="authenticationPath" type="xsd:string" minOccurs="0"/> + <xsd:element name="specificationVersion" type="xsd:string" minOccurs="0"/> + <xsd:element name="authenticationTransactionID" type="xsd:string" minOccurs="0"/> + <xsd:element name="directoryServerTransactionID" type="xsd:string" minOccurs="0"/> + <xsd:element name="challengeRequired" type="tns:boolean" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="PayerAuthValidateReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="authenticationResult" type="xsd:string" minOccurs="0"/> + <xsd:element name="authenticationStatusMessage" type="xsd:string" minOccurs="0"/> + <xsd:element name="cavv" type="xsd:string" minOccurs="0"/> + <xsd:element name="cavvAlgorithm" type="xsd:string" minOccurs="0"/> + <xsd:element name="commerceIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="eci" type="xsd:string" minOccurs="0"/> + <xsd:element name="eciRaw" type="xsd:string" minOccurs="0"/> + <xsd:element name="xid" type="xsd:string" minOccurs="0"/> + <xsd:element name="ucafAuthenticationData" type="xsd:string" minOccurs="0"/> + <xsd:element name="ucafCollectionIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="paresStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="specificationVersion" type="xsd:string" minOccurs="0"/> + <xsd:element name="directoryServerTransactionID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="TaxReplyItem"> + <xsd:sequence> + <xsd:element name="taxableAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="exemptAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="specialTaxAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="cityTaxAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="countyTaxAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="districtTaxAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="stateTaxAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="countryTaxAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="totalTaxAmount" type="tns:amount"/> + <xsd:element name="jurisdiction" type="tns:TaxReplyItemJurisdiction" minOccurs="0" maxOccurs="1000"/> + </xsd:sequence> + <xsd:attribute name="id" type="xsd:integer" use="required"/> + </xsd:complexType> + <xsd:complexType name="TaxReplyItemJurisdiction"> + <xsd:sequence> + <xsd:element name="country" type="xsd:string" minOccurs="0"/> + <xsd:element name="region" type="xsd:string" minOccurs="0"/> + <xsd:element name="type" type="xsd:string" minOccurs="0"/> + <xsd:element name="code" type="xsd:string" minOccurs="0"/> + <xsd:element name="taxable" type="tns:amount" minOccurs="0"/> + <xsd:element name="rate" type="tns:amount" minOccurs="0"/> + <xsd:element name="taxAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="name" type="xsd:string"/> + <xsd:element name="taxName" type="xsd:string"/> + </xsd:sequence> + <xsd:attribute name="jurisId" type="xsd:integer" use="required"/> + </xsd:complexType> + <xsd:complexType name="TaxReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="currency" type="xsd:string" minOccurs="0"/> + <xsd:element name="grandTotalAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="totalTaxableAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="totalExemptAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="totalSpecialTaxAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="totalCityTaxAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="city" type="xsd:string" minOccurs="0"/> + <xsd:element name="totalCountyTaxAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="county" type="xsd:string" minOccurs="0"/> + <xsd:element name="totalDistrictTaxAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="totalStateTaxAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="state" type="xsd:string" minOccurs="0"/> + <xsd:element name="totalCountryTaxAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="totalTaxAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="commitIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="refundIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="postalCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="geocode" type="xsd:string" minOccurs="0"/> + <xsd:element name="item" type="tns:TaxReplyItem" minOccurs="0" maxOccurs="1000"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="DeviceFingerprint"> + <xsd:sequence> + <xsd:element name="cookiesEnabled" type="tns:boolean" minOccurs="0"/> + <xsd:element name="flashEnabled" type="tns:boolean" minOccurs="0"/> + <xsd:element name="hash" type="xsd:string" minOccurs="0"/> + <xsd:element name="imagesEnabled" type="tns:boolean" minOccurs="0"/> + <xsd:element name="javascriptEnabled" type="tns:boolean" minOccurs="0"/> + <xsd:element name="proxyIPAddress" type="xsd:string" minOccurs="0"/> + <xsd:element name="proxyIPAddressActivities" type="xsd:string" minOccurs="0"/> + <xsd:element name="proxyIPAddressAttributes" type="xsd:string" minOccurs="0"/> + <xsd:element name="proxyServerType" type="xsd:string" minOccurs="0"/> + <xsd:element name="trueIPAddress" type="xsd:string" minOccurs="0"/> + <xsd:element name="trueIPAddressActivities" type="xsd:string" minOccurs="0"/> + <xsd:element name="trueIPAddressAttributes" type="xsd:string" minOccurs="0"/> + <xsd:element name="trueIPAddressCity" type="xsd:string" minOccurs="0"/> + <xsd:element name="trueIPAddressState" type="xsd:string" minOccurs="0"/> + <xsd:element name="trueIPAddressCountry" type="xsd:string" minOccurs="0"/> + <xsd:element name="smartID" type="xsd:string" minOccurs="0"/> + <xsd:element name="smartIDConfidenceLevel" type="xsd:string" minOccurs="0"/> + <xsd:element name="screenResolution" type="xsd:string" minOccurs="0"/> + <xsd:element name="browserLanguage" type="xsd:string" minOccurs="0"/> + <xsd:element name="agentType" type="xsd:string" minOccurs="0"/> + <xsd:element name="dateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="profileDuration" type="xsd:integer" minOccurs="0"/> + <xsd:element name="profiledURL" type="xsd:string" minOccurs="0"/> + <xsd:element name="timeOnPage" type="xsd:integer" minOccurs="0"/> + <xsd:element name="deviceMatch" type="xsd:string" minOccurs="0"/> + <xsd:element name="firstEncounter" type="xsd:string" minOccurs="0"/> + <xsd:element name="flashOS" type="xsd:string" minOccurs="0"/> + <xsd:element name="flashVersion" type="xsd:string" minOccurs="0"/> + <xsd:element name="deviceLatitude" type="xsd:string" minOccurs="0"/> + <xsd:element name="deviceLongitude" type="xsd:string" minOccurs="0"/> + <xsd:element name="gpsAccuracy" type="xsd:string" minOccurs="0"/> + <xsd:element name="jbRoot" type="xsd:integer" minOccurs="0"/> + <xsd:element name="jbRootReason" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="AFSReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="afsResult" type="xsd:integer" minOccurs="0"/> + <xsd:element name="hostSeverity" type="xsd:integer" minOccurs="0"/> + <xsd:element name="consumerLocalTime" type="xsd:string" minOccurs="0"/> + <!-- xsd:time --> + <xsd:element name="afsFactorCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="addressInfoCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="hotlistInfoCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="internetInfoCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="phoneInfoCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="suspiciousInfoCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="velocityInfoCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="identityInfoCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="ipCountry" type="xsd:string" minOccurs="0"/> + <xsd:element name="ipState" type="xsd:string" minOccurs="0"/> + <xsd:element name="ipCity" type="xsd:string" minOccurs="0"/> + <xsd:element name="ipRoutingMethod" type="xsd:string" minOccurs="0"/> + <xsd:element name="ipAnonymizerStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="scoreModelUsed" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardBin" type="xsd:string" minOccurs="0"/> + <xsd:element name="binCountry" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardAccountType" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardScheme" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardIssuer" type="xsd:string" minOccurs="0"/> + <xsd:element name="deviceFingerprint" type="tns:DeviceFingerprint" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="DAVReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="addressType" type="xsd:string" minOccurs="0"/> + <xsd:element name="apartmentInfo" type="xsd:string" minOccurs="0"/> + <xsd:element name="barCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="barCodeCheckDigit" type="xsd:string" minOccurs="0"/> + <xsd:element name="careOf" type="xsd:string" minOccurs="0"/> + <xsd:element name="cityInfo" type="xsd:string" minOccurs="0"/> + <xsd:element name="countryInfo" type="xsd:string" minOccurs="0"/> + <xsd:element name="directionalInfo" type="xsd:string" minOccurs="0"/> + <xsd:element name="lvrInfo" type="xsd:string" minOccurs="0"/> + <xsd:element name="matchScore" type="xsd:integer" minOccurs="0"/> + <xsd:element name="standardizedAddress1" type="xsd:string" minOccurs="0"/> + <xsd:element name="standardizedAddress2" type="xsd:string" minOccurs="0"/> + <xsd:element name="standardizedAddress3" type="xsd:string" minOccurs="0"/> + <xsd:element name="standardizedAddress4" type="xsd:string" minOccurs="0"/> + <xsd:element name="standardizedAddressNoApt" type="xsd:string" minOccurs="0"/> + <xsd:element name="standardizedCity" type="xsd:string" minOccurs="0"/> + <xsd:element name="standardizedCounty" type="xsd:string" minOccurs="0"/> + <xsd:element name="standardizedCSP" type="xsd:string" minOccurs="0"/> + <xsd:element name="standardizedState" type="xsd:string" minOccurs="0"/> + <xsd:element name="standardizedPostalCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="standardizedCountry" type="xsd:string" minOccurs="0"/> + <xsd:element name="standardizedISOCountry" type="xsd:string" minOccurs="0"/> + <xsd:element name="stateInfo" type="xsd:string" minOccurs="0"/> + <xsd:element name="streetInfo" type="xsd:string" minOccurs="0"/> + <xsd:element name="suffixInfo" type="xsd:string" minOccurs="0"/> + <xsd:element name="postalCodeInfo" type="xsd:string" minOccurs="0"/> + <xsd:element name="overallInfo" type="xsd:string" minOccurs="0"/> + <xsd:element name="usInfo" type="xsd:string" minOccurs="0"/> + <xsd:element name="caInfo" type="xsd:string" minOccurs="0"/> + <xsd:element name="intlInfo" type="xsd:string" minOccurs="0"/> + <xsd:element name="usErrorInfo" type="xsd:string" minOccurs="0"/> + <xsd:element name="caErrorInfo" type="xsd:string" minOccurs="0"/> + <xsd:element name="intlErrorInfo" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="DeniedPartiesMatch"> + <xsd:sequence> + <xsd:element name="list" type="xsd:string" minOccurs="0"/> + <xsd:element name="name" type="xsd:string" minOccurs="0" maxOccurs="100"/> + <xsd:element name="address" type="xsd:string" minOccurs="0" maxOccurs="100"/> + <xsd:element name="program" type="xsd:string" minOccurs="0" maxOccurs="100"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="ExportReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="ipCountryConfidence" type="xsd:integer" minOccurs="0"/> + <xsd:element name="infoCode" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="FXQuote"> + <xsd:sequence> + <xsd:element name="id" type="xsd:string" minOccurs="0"/> + <xsd:element name="rate" type="xsd:string" minOccurs="0"/> + <xsd:element name="type" type="xsd:string" minOccurs="0"/> + <xsd:element name="expirationDateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="currency" type="xsd:string" minOccurs="0"/> + <xsd:element name="fundingCurrency" type="xsd:string" minOccurs="0"/> + <xsd:element name="receivedDateTime" type="tns:dateTime" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="FXRatesReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="quote" type="tns:FXQuote" minOccurs="0" maxOccurs="999"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="BankTransferReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="accountHolder" type="xsd:string" minOccurs="0"/> + <xsd:element name="accountNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="bankName" type="xsd:string" minOccurs="0"/> + <xsd:element name="bankCity" type="xsd:string" minOccurs="0"/> + <xsd:element name="bankCountry" type="xsd:string" minOccurs="0"/> + <xsd:element name="paymentReference" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="bankSwiftCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="bankSpecialID" type="xsd:string" minOccurs="0"/> + <xsd:element name="requestDateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="iban" type="xsd:string" minOccurs="0"/> + <xsd:element name="bankCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="branchCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationReferenceNumber" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="BankTransferRealTimeReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="formMethod" type="xsd:string" minOccurs="0"/> + <xsd:element name="formAction" type="xsd:string" minOccurs="0"/> + <xsd:element name="requestDateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="paymentReference" type="xsd:string" minOccurs="0"/> + <xsd:element name="amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="reconciliationReferenceNumber" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="DirectDebitMandateReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="mandateID" type="xsd:string" minOccurs="0"/> + <xsd:element name="mandateMaturationDate" type="xsd:string" minOccurs="0"/> + <xsd:element name="requestDateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="BankTransferRefundReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="requestDateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="iban" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationReferenceNumber" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="DirectDebitReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="requestDateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorTransactionID" type="xsd:string" minOccurs="0"/> + <xsd:element name="mandateAuthenticationDate" type="xsd:string" minOccurs="0"/> + <xsd:element name="mandateID" type="xsd:string" minOccurs="0"/> + <xsd:element name="iban" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationReferenceNumber" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="DirectDebitValidateReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="requestDateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="iban" type="xsd:string" minOccurs="0"/> + <xsd:element name="bankSwiftCode" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="DirectDebitRefundReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="requestDateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorTransactionID" type="xsd:string" minOccurs="0"/> + <xsd:element name="iban" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationReferenceNumber" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="PaySubscriptionCreateReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="subscriptionID" type="xsd:string"/> + <xsd:element name="instrumentIdentifierID" type="xsd:string" minOccurs="0"/> + <xsd:element name="instrumentIdentifierStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="instrumentIdentifierNew" type="xsd:string" minOccurs="0"/> + <xsd:element name="instrumentIdentifierSuccessorID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="PaySubscriptionUpdateReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="subscriptionID" type="xsd:string"/> + <xsd:element name="subscriptionIDNew" type="xsd:string" minOccurs="0"/> + <xsd:element name="ownerMerchantID" type="xsd:string" minOccurs="0"/> + <xsd:element name="instrumentIdentifierID" type="xsd:string" minOccurs="0"/> + <xsd:element name="instrumentIdentifierStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="instrumentIdentifierNew" type="xsd:string" minOccurs="0"/> + <xsd:element name="instrumentIdentifierSuccessorID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="PaySubscriptionEventUpdateReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="ownerMerchantID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="PaySubscriptionRetrieveReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="approvalRequired" type="xsd:string" minOccurs="0"/> + <xsd:element name="automaticRenew" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardAccountNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardExpirationMonth" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardExpirationYear" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardIssueNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardStartMonth" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardStartYear" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardType" type="xsd:string" minOccurs="0"/> + <xsd:element name="checkAccountNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="checkAccountType" type="xsd:string" minOccurs="0"/> + <xsd:element name="checkBankTransitNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="checkSecCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="checkAuthenticateID" type="xsd:string" minOccurs="0"/> + <xsd:element name="city" type="xsd:string" minOccurs="0"/> + <xsd:element name="comments" type="xsd:string" minOccurs="0"/> + <xsd:element name="companyName" type="xsd:string" minOccurs="0"/> + <xsd:element name="country" type="xsd:string" minOccurs="0"/> + <xsd:element name="currency" type="xsd:string" minOccurs="0"/> + <xsd:element name="customerAccountID" type="xsd:string" minOccurs="0"/> + <xsd:element name="email" type="xsd:string" minOccurs="0"/> + <xsd:element name="endDate" type="xsd:string" minOccurs="0"/> + <xsd:element name="firstName" type="xsd:string" minOccurs="0"/> + <xsd:element name="frequency" type="xsd:string" minOccurs="0"/> + <xsd:element name="lastName" type="xsd:string" minOccurs="0"/> + <xsd:element name="merchantReferenceCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="paymentMethod" type="xsd:string" minOccurs="0"/> + <xsd:element name="paymentsRemaining" type="xsd:string" minOccurs="0"/> + <xsd:element name="phoneNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="postalCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="recurringAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="setupAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="startDate" type="xsd:string" minOccurs="0"/> + <xsd:element name="state" type="xsd:string" minOccurs="0"/> + <xsd:element name="status" type="xsd:string" minOccurs="0"/> + <xsd:element name="street1" type="xsd:string" minOccurs="0"/> + <xsd:element name="street2" type="xsd:string" minOccurs="0"/> + <xsd:element name="subscriptionID" type="xsd:string" minOccurs="0"/> + <xsd:element name="subscriptionIDNew" type="xsd:string" minOccurs="0"/> + <xsd:element name="title" type="xsd:string" minOccurs="0"/> + <xsd:element name="totalPayments" type="xsd:string" minOccurs="0"/> + <xsd:element name="shipToFirstName" type="xsd:string" minOccurs="0"/> + <xsd:element name="shipToLastName" type="xsd:string" minOccurs="0"/> + <xsd:element name="shipToStreet1" type="xsd:string" minOccurs="0"/> + <xsd:element name="shipToStreet2" type="xsd:string" minOccurs="0"/> + <xsd:element name="shipToCity" type="xsd:string" minOccurs="0"/> + <xsd:element name="shipToState" type="xsd:string" minOccurs="0"/> + <xsd:element name="shipToPostalCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="shipToCompany" type="xsd:string" minOccurs="0"/> + <xsd:element name="shipToCountry" type="xsd:string" minOccurs="0"/> + <xsd:element name="billPayment" type="xsd:string" minOccurs="0"/> + <xsd:element name="merchantDefinedDataField1" type="xsd:string" minOccurs="0"/> + <xsd:element name="merchantDefinedDataField2" type="xsd:string" minOccurs="0"/> + <xsd:element name="merchantDefinedDataField3" type="xsd:string" minOccurs="0"/> + <xsd:element name="merchantDefinedDataField4" type="xsd:string" minOccurs="0"/> + <xsd:element name="merchantSecureDataField1" type="xsd:string" minOccurs="0"/> + <xsd:element name="merchantSecureDataField2" type="xsd:string" minOccurs="0"/> + <xsd:element name="merchantSecureDataField3" type="xsd:string" minOccurs="0"/> + <xsd:element name="merchantSecureDataField4" type="xsd:string" minOccurs="0"/> + <xsd:element name="ownerMerchantID" type="xsd:string" minOccurs="0"/> + <xsd:element name="companyTaxID" type="xsd:string" minOccurs="0"/> + <xsd:element name="driversLicenseNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="driversLicenseState" type="xsd:string" minOccurs="0"/> + <xsd:element name="dateOfBirth" type="xsd:string" minOccurs="0"/> + <xsd:element name="instrumentIdentifierID" type="xsd:string" minOccurs="0"/> + <xsd:element name="instrumentIdentifierStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="instrumentIdentifierSuccessorID" type="xsd:string" minOccurs="0"/> + <xsd:element name="subsequentAuthTransactionID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="PaySubscriptionDeleteReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="subscriptionID" type="xsd:string"/> + <xsd:element name="instrumentIdentifierID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="PayPalPaymentReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="secureData" type="xsd:string" minOccurs="0"/> + <xsd:element name="amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="requestDateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="PayPalCreditReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="requestDateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="VoidReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="requestDateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="currency" type="xsd:string" minOccurs="0"/> + <xsd:element name="reversalSubmitted" type="tns:boolean" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="PinlessDebitReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="authorizationCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="requestDateTime" type="tns:dateTime" minOccurs="0"/> + <!-- dateTime --> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="receiptNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="ownerMerchantID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="PinlessDebitValidateReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="status" type="xsd:string" minOccurs="0"/> + <xsd:element name="requestDateTime" type="tns:dateTime" minOccurs="0"/> + <!-- dateTime --> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="PinlessDebitReversalReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="requestDateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <!-- payPal Upgrade Services --> + <xsd:complexType name="PayPalButtonCreateReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="encryptedFormData" type="xsd:string" minOccurs="0"/> + <xsd:element name="unencryptedFormData" type="xsd:string" minOccurs="0"/> + <xsd:element name="requestDateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="buttonType" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="PayPalPreapprovedPaymentReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="requestDateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerName" type="xsd:string" minOccurs="0"/> + <xsd:element name="transactionType" type="xsd:string" minOccurs="0"/> + <xsd:element name="feeAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerCountry" type="xsd:string" minOccurs="0"/> + <xsd:element name="pendingReason" type="xsd:string" minOccurs="0"/> + <xsd:element name="paymentStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="mpStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="payer" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerID" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerBusiness" type="xsd:string" minOccurs="0"/> + <xsd:element name="transactionID" type="xsd:string" minOccurs="0"/> + <xsd:element name="desc" type="xsd:string" minOccurs="0"/> + <xsd:element name="mpMax" type="xsd:string" minOccurs="0"/> + <xsd:element name="paymentType" type="xsd:string" minOccurs="0"/> + <xsd:element name="paymentDate" type="xsd:string" minOccurs="0"/> + <xsd:element name="paymentGrossAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="settleAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="taxAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="exchangeRate" type="xsd:string" minOccurs="0"/> + <xsd:element name="paymentSourceID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="PayPalPreapprovedUpdateReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="requestDateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerName" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerCountry" type="xsd:string" minOccurs="0"/> + <xsd:element name="mpStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="payer" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerID" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerBusiness" type="xsd:string" minOccurs="0"/> + <xsd:element name="desc" type="xsd:string" minOccurs="0"/> + <xsd:element name="mpMax" type="xsd:string" minOccurs="0"/> + <xsd:element name="paymentSourceID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <!-- PayPalEcSet --> + <xsd:complexType name="PayPalEcSetReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="paypalToken" type="xsd:string" minOccurs="0"/> + <xsd:element name="amount" type="xsd:string" minOccurs="0"/> + <xsd:element name="currency" type="xsd:string" minOccurs="0"/> + <xsd:element name="correlationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="errorCode" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <!-- end of PayPalEcSet --> + <!-- PayPalEcGetDetails --> + <xsd:complexType name="PayPalEcGetDetailsReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="paypalToken" type="xsd:string" minOccurs="0"/> + <xsd:element name="payer" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerId" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerSalutation" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerFirstname" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerMiddlename" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerLastname" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerSuffix" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerCountry" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerBusiness" type="xsd:string" minOccurs="0"/> + <xsd:element name="shipToName" type="xsd:string" minOccurs="0"/> + <xsd:element name="shipToAddress1" type="xsd:string" minOccurs="0"/> + <xsd:element name="shipToAddress2" type="xsd:string" minOccurs="0"/> + <xsd:element name="shipToCity" type="xsd:string" minOccurs="0"/> + <xsd:element name="shipToState" type="xsd:string" minOccurs="0"/> + <xsd:element name="shipToCountry" type="xsd:string" minOccurs="0"/> + <xsd:element name="shipToZip" type="xsd:string" minOccurs="0"/> + <xsd:element name="addressStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerPhone" type="xsd:string" minOccurs="0"/> + <xsd:element name="avsCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="street1" type="xsd:string" minOccurs="0"/> + <xsd:element name="street2" type="xsd:string" minOccurs="0"/> + <xsd:element name="city" type="xsd:string" minOccurs="0"/> + <xsd:element name="state" type="xsd:string" minOccurs="0"/> + <xsd:element name="postalCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="countryCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="countryName" type="xsd:string" minOccurs="0"/> + <xsd:element name="addressID" type="xsd:string" minOccurs="0"/> + <xsd:element name="errorCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="correlationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalBillingAgreementAcceptedStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalTaxAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="item" type="tns:Item" minOccurs="0" maxOccurs="1000" /> + </xsd:sequence> + </xsd:complexType> + <!-- end of PayPalEcGetDetails --> + <!-- PayPalEcDoPayment --> + <xsd:complexType name="PayPalEcDoPaymentReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="paypalToken" type="xsd:string" minOccurs="0"/> + <xsd:element name="transactionId" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalTransactiontype" type="xsd:string" minOccurs="0"/> + <xsd:element name="paymentType" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalOrderTime" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalFeeAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalTaxAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalExchangeRate" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalPaymentStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalPendingReason" type="xsd:string" minOccurs="0"/> + <xsd:element name="orderId" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalReasonCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="amount" type="xsd:string" minOccurs="0"/> + <xsd:element name="currency" type="xsd:string" minOccurs="0"/> + <xsd:element name="correlationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="errorCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalBillingAgreementId" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <!-- end of PayPalEcDoPayment --> + <!-- PayPalDoCapture --> + <xsd:complexType name="PayPalDoCaptureReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="authorizationId" type="xsd:string" minOccurs="0"/> + <xsd:element name="transactionId" type="xsd:string" minOccurs="0"/> + <xsd:element name="parentTransactionId" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalReceiptId" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalTransactiontype" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalPaymentType" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalOrderTime" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalPaymentGrossAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalFeeAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalTaxAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalExchangeRate" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalPaymentStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="amount" type="xsd:string" minOccurs="0"/> + <xsd:element name="currency" type="xsd:string" minOccurs="0"/> + <xsd:element name="correlationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="errorCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalPendingReason" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <!-- end of PayPalDoCapture --> + <!-- PayPalAuthReversal --> + <xsd:complexType name="PayPalAuthReversalReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="authorizationId" type="xsd:string" minOccurs="0"/> + <xsd:element name="correlationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="errorCode" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <!-- end of PayPalAuthReversal --> + <!-- PayPalRefund --> + <xsd:complexType name="PayPalRefundReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="transactionId" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalNetRefundAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalFeeRefundAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalGrossRefundAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="correlationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="errorCode" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <!-- end of PayPalRefund --> + <!-- PayPalEcOrderSetup --> + <xsd:complexType name="PayPalEcOrderSetupReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="paypalToken" type="xsd:string" minOccurs="0"/> + <xsd:element name="transactionId" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalTransactiontype" type="xsd:string" minOccurs="0"/> + <xsd:element name="paymentType" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalOrderTime" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalFeeAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalTaxAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalExchangeRate" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalPaymentStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalPendingReason" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalReasonCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="amount" type="xsd:string" minOccurs="0"/> + <xsd:element name="currency" type="xsd:string" minOccurs="0"/> + <xsd:element name="correlationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="errorCode" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <!-- end of PayPalEcOrderSetup --> + <!-- PayPalAuthorization--> + <xsd:complexType name="PayPalAuthorizationReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="transactionId" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="amount" type="xsd:string" minOccurs="0"/> + <xsd:element name="currency" type="xsd:string" minOccurs="0"/> + <xsd:element name="correlationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="errorCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="protectionEligibility" type="xsd:string" minOccurs="0"/> + <xsd:element name="protectionEligibilityType" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <!-- end of PayPalAuthorization --> + <!-- PayPalUpdateAgreement--> + <xsd:complexType name="PayPalUpdateAgreementReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="paypalBillingAgreementId" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalBillingAgreementDesc" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalBillingAgreementCustom" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalBillingAgreementStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="payer" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerId" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerCountry" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerBusiness" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerSalutation" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerFirstname" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerMiddlename" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerLastname" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerSuffix" type="xsd:string" minOccurs="0"/> + <xsd:element name="addressStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="errorCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="correlationID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <!-- end of PayPalUpdateAgreement--> + <!-- PayPalCreateAgreement--> + <xsd:complexType name="PayPalCreateAgreementReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="paypalBillingAgreementId" type="xsd:string" minOccurs="0"/> + <xsd:element name="errorCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="correlationID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <!-- end of PayPalCreateAgreement--> + <!-- PayPalDoRefTransaction--> + <xsd:complexType name="PayPalDoRefTransactionReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="paypalBillingAgreementId" type="xsd:string" minOccurs="0"/> + <xsd:element name="transactionId" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalTransactionType" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalPaymentType" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalOrderTime" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="currency" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalTaxAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalExchangeRate" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalPaymentStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalPendingReason" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalReasonCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="errorCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="correlationID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <!-- end of PayPalDoRefTransaction--> + <xsd:complexType name="RiskUpdateReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="FraudUpdateReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="CaseManagementActionReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="RuleResultItem"> + <xsd:sequence> + <xsd:element name="name" type="xsd:string" minOccurs="0"/> + <xsd:element name="decision" type="xsd:string" minOccurs="0"/> + <xsd:element name="evaluation" type="xsd:string" minOccurs="0"/> + <xsd:element name="ruleID" type="xsd:integer" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="RuleResultItems"> + <xsd:sequence> + <xsd:element name="ruleResultItem" type="tns:RuleResultItem" minOccurs="0" maxOccurs="1000"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="DecisionReply"> + <xsd:sequence> + <xsd:element name="casePriority" type="xsd:integer" minOccurs="0"/> + <xsd:element name="activeProfileReply" type="tns:ProfileReply" minOccurs="0"/> + <xsd:element name="velocityInfoCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="additionalFields" type="tns:AdditionalFields" minOccurs="0" maxOccurs="1" /> + <xsd:element name="morphingElement" type="tns:MorphingElement" minOccurs="0" maxOccurs="1" /> + <xsd:element name="providerFields" type="tns:ProviderFields" minOccurs="0" maxOccurs="1" /> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="ProviderFields"> + <xsd:sequence> + <xsd:element name="provider" type="tns:Provider" minOccurs="0" maxOccurs="30"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="Provider"> + <xsd:sequence> + <xsd:element name="name" type="xsd:string" minOccurs="1" maxOccurs="1"/> + <xsd:element name="field" type="tns:ProviderField" minOccurs="0" maxOccurs="500"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="ProviderField"> + <xsd:sequence> + <xsd:element name="name" type="xsd:string" minOccurs="1" maxOccurs="1"/> + <xsd:element name="value" type="xsd:string" minOccurs="1" maxOccurs="1"/> + </xsd:sequence> + </xsd:complexType> + <!-- DME --> + <xsd:complexType name="AdditionalFields"> + <xsd:sequence> + <xsd:element name="field" type="tns:Field" minOccurs="0" maxOccurs="3000"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="Field"> + <xsd:sequence> + <xsd:element name="provider" type="xsd:string" minOccurs="1" maxOccurs="1"/> + <xsd:element name="name" type="xsd:string" minOccurs="1" maxOccurs="1"/> + <xsd:element name="value" type="xsd:string" minOccurs="1" maxOccurs="1"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="MorphingElement"> + <xsd:sequence> + <xsd:element name="element" type="tns:Element" minOccurs="0" maxOccurs="1000"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="Element"> + <xsd:sequence> + <xsd:element name="infoCode" type="xsd:string" minOccurs="1" maxOccurs="1"/> + <xsd:element name="fieldName" type="xsd:string" minOccurs="1" maxOccurs="1"/> + <xsd:element name="count" type="xsd:integer" minOccurs="1" maxOccurs="1"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="DMEReply"> + <xsd:sequence> + <xsd:element name="eventType" type="xsd:string" minOccurs="0"/> + <xsd:element name="eventInfo" type="xsd:string" minOccurs="0"/> + <xsd:element name="eventHotlistInfo" type="xsd:string" minOccurs="0"/> + <xsd:element name="eventPolicy" type="xsd:string" minOccurs="0"/> + <xsd:element name="eventVelocityInfoCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="additionalFields" type="tns:AdditionalFields" minOccurs="0" maxOccurs="1" /> + <xsd:element name="morphingElement" type="tns:MorphingElement" minOccurs="0" maxOccurs="1" /> + <xsd:element name="cardBin" type="xsd:string" minOccurs="0"/> + <xsd:element name="binCountry" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardAccountType" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardScheme" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardIssuer" type="xsd:string" minOccurs="0"/> + <xsd:element name="providerFields" type="tns:ProviderFields" minOccurs="0" maxOccurs="1"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="ProfileReply"> + <xsd:sequence> + <xsd:element name="selectedBy" type="xsd:string" minOccurs="0"/> + <xsd:element name="name" type="xsd:string" minOccurs="0"/> + <xsd:element name="destinationQueue" type="xsd:string" minOccurs="0"/> + <xsd:element name="profileScore" type="xsd:string" minOccurs="0"/> + <xsd:element name="rulesTriggered" type="tns:RuleResultItems" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="CCDCCReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="dccSupported" type="tns:boolean" minOccurs="0"/> + <xsd:element name="validHours" type="xsd:string" minOccurs="0"/> + <xsd:element name="marginRatePercentage" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="CCDCCUpdateReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="ChinaPaymentReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="requestDateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="currency" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="formData" type="xsd:string" minOccurs="0"/> + <xsd:element name="verifyFailure" type="xsd:string" minOccurs="0"/> + <xsd:element name="verifyInProcess" type="xsd:string" minOccurs="0"/> + <xsd:element name="verifySuccess" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="ChinaRefundReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="requestDateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="currency" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="BoletoPaymentReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="requestDateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="boletoNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="expirationDate" type="xsd:string" minOccurs="0"/> + <xsd:element name="url" type="xsd:string" minOccurs="0"/> + <xsd:element name="avsCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="avsCodeRaw" type="xsd:string" minOccurs="0"/> + <xsd:element name="barCodeNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="assignor" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="APInitiateReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="merchantURL" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="dateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="signature" type="xsd:string" minOccurs="0"/> + <xsd:element name="publicKey" type="xsd:string" minOccurs="0"/> + <xsd:element name="paymentStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="responseCode" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="APCheckStatusReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="paymentStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorTradeNo" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorTransactionID" type="xsd:string" minOccurs="0"/> + <xsd:element name="ibanSuffix" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="dateTime" type="tns:dateTime" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <!-- Vme Reseller Reply--> + + <xsd:complexType name="SellerProtection"> + <xsd:sequence> + <xsd:element name="eligibility" type="xsd:string" minOccurs="0" /> + <xsd:element name="type" type="xsd:string" minOccurs="0" /> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="APReply"> + <xsd:sequence> + <xsd:element name="orderID" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardGroup" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardType" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardNumberSuffix" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardExpirationMonth" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardExpirationYear" type="xsd:string" minOccurs="0"/> + <xsd:element name="avsCodeRaw" type="xsd:string" minOccurs="0"/> + <xsd:element name="purchaseID" type="xsd:string" minOccurs="0"/> + <xsd:element name="productID" type="xsd:string" minOccurs="0"/> + <xsd:element name="productDescription" type="xsd:string" minOccurs="0"/> + <xsd:element name="shippingAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="handlingAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="shippingHandlingAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="additionalAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="taxAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="subtotalAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="totalPurchaseAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="giftWrapAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="discountAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardNumberPrefix" type="xsd:string" minOccurs="0"/> + <xsd:element name="riskIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="merchantUUID" type="xsd:string" minOccurs="0"/> + <xsd:element name="merchantSiteID" type="xsd:string" minOccurs="0"/> + <xsd:element name="transactionExpirationDate" type="xsd:string" minOccurs="0"/> + <xsd:element name="sellerProtection" type="tns:SellerProtection" minOccurs="0" /> + <xsd:element name="processorFraudDecision" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorFraudDecisionReason" type="xsd:string" minOccurs="0"/> + <xsd:element name="customerID" type="xsd:string" minOccurs="0" /> + <xsd:element name="billingAgreementID" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerID" type="xsd:string" minOccurs="0"/> + <xsd:element name="fundingSource" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <!-- AP Auth Service --> + <xsd:complexType name="APAuthReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="transactionID" type="xsd:string" minOccurs="0"/> + <xsd:element name="status" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="dateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="providerResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="paymentStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="responseCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="authorizationCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="merchantURL" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorTransactionID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <!-- End of AP Auth Service --> + <!-- AP Auth Reversal Service --> + <xsd:complexType name="APAuthReversalReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="transactionID" type="xsd:string" minOccurs="0"/> + <xsd:element name="status" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="dateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="providerResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="paymentStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="responseCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorTransactionID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <!-- End of AP Auth Reversal Service --> + <!-- AP Capture Service --> + <xsd:complexType name="APCaptureReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="processorTransactionID" type="xsd:string" minOccurs="0"/> + <xsd:element name="status" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="dateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="providerResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="paymentStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="responseCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorTransactionFee" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <!-- End of AP Capture Service --> + <!-- AP Options Service --> + <xsd:complexType name="APOptionsReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="responseCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="offset" type="xsd:string" minOccurs="0"/> + <xsd:element name="count" type="xsd:string" minOccurs="0"/> + <xsd:element name="totalCount" type="xsd:string" minOccurs="0"/> + <xsd:element name="option" type="tns:APOptionsOption" minOccurs="0" maxOccurs="250"/> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="APOptionsOption"> + <xsd:sequence> + <xsd:element name="id" type="xsd:string" minOccurs="0" /> + <xsd:element name="name" type="xsd:string" minOccurs="0" /> + </xsd:sequence> + <xsd:attribute name="data" type="xsd:integer" use="optional"/> + </xsd:complexType> + + + <!-- End of Options Service --> + <!-- AP Refund Service --> + <xsd:complexType name="APRefundReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="transactionID" type="xsd:string" minOccurs="0"/> + <xsd:element name="status" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="dateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="returnRef" type="xsd:string" minOccurs="0"/> + <xsd:element name="providerResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorTransactionID" type="xsd:string" minOccurs="0"/> + <xsd:element name="paymentStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="responseCode" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <!-- End of AP Refund Service --> + <!-- AP Sale Service --> + <xsd:complexType name="APSaleReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="paymentStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="responseCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="merchantURL" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorTransactionID" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorTransactionFee" type="xsd:string" minOccurs="0"/> + <xsd:element name="amount" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="exchangeRate" type="xsd:string" minOccurs="0"/> + <xsd:element name="foreignCurrency" type="xsd:string" minOccurs="0"/> + <xsd:element name="foreignAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="discountAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="dateTime" type="tns:dateTime" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <!-- End of AP Sale Service --> + + <!-- AP CheckOutDetailsReply Service --> + <xsd:complexType name="APCheckOutDetailsReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="status" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="dateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="providerResponse" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <!-- End of AP CheckOutDetailsReply Service --> + <xsd:complexType name="APTransactionDetailsReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="transactionID" type="xsd:string" minOccurs="0"/> + <xsd:element name="status" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="dateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="providerResponse" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <!-- AP ConfirmPurchase Service --> + <xsd:complexType name="APConfirmPurchaseReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="status" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="dateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="providerResponse" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <!-- End of AP ConfirmPurchase Service --> + <xsd:complexType name="APSessionsReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="responseCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="merchantURL" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorToken" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorTransactionID" type="xsd:string" minOccurs="0"/> + <xsd:element name="amount" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="status" type="xsd:string" minOccurs="0"/> + <xsd:element name="dateTime" type="tns:dateTime" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="CCCheckStatusReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="paymentStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="authorizationCode" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="ReplyMessage"> + <xsd:sequence> + <xsd:element name="merchantReferenceCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="requestID" type="xsd:string"/> + <xsd:element name="decision" type="xsd:string"/> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="missingField" type="xsd:string" minOccurs="0" maxOccurs="1000"/> + <xsd:element name="invalidField" type="xsd:string" minOccurs="0" maxOccurs="1000"/> + <xsd:element name="requestToken" type="xsd:string"/> + <xsd:element name="purchaseTotals" type="tns:PurchaseTotals" minOccurs="0"/> + <xsd:element name="deniedPartiesMatch" type="tns:DeniedPartiesMatch" minOccurs="0" maxOccurs="100"/> + <xsd:element name="ccAuthReply" type="tns:CCAuthReply" minOccurs="0"/> + <xsd:element name="octReply" type="tns:OCTReply" minOccurs="0"/> + <xsd:element name="verificationReply" type="tns:VerificationReply" minOccurs="0"/> + <xsd:element name="ccSaleReply" type="tns:CCSaleReply" minOccurs="0"/> + <xsd:element name="ccSaleCreditReply" type="tns:CCSaleCreditReply" minOccurs="0"/> + <xsd:element name="ccSaleReversalReply" type="tns:CCSaleReversalReply" minOccurs="0"/> + <xsd:element name="ccIncrementalAuthReply" type="tns:CCIncrementalAuthReply" minOccurs="0"/> + <xsd:element name="serviceFeeCalculateReply" type="tns:ServiceFeeCalculateReply" minOccurs="0"/> + <xsd:element name="ccCaptureReply" type="tns:CCCaptureReply" minOccurs="0"/> + <xsd:element name="ccCreditReply" type="tns:CCCreditReply" minOccurs="0"/> + <xsd:element name="ccAuthReversalReply" type="tns:CCAuthReversalReply" minOccurs="0"/> + <xsd:element name="ccAutoAuthReversalReply" type="tns:CCAutoAuthReversalReply" minOccurs="0"/> + <xsd:element name="ccDCCReply" type="tns:CCDCCReply" minOccurs="0"/> + <xsd:element name="ccDCCUpdateReply" type="tns:CCDCCUpdateReply" minOccurs="0"/> + <xsd:element name="ecDebitReply" type="tns:ECDebitReply" minOccurs="0"/> + <xsd:element name="ecCreditReply" type="tns:ECCreditReply" minOccurs="0"/> + <xsd:element name="ecAuthenticateReply" type="tns:ECAuthenticateReply" minOccurs="0"/> + <xsd:element name="payerAuthEnrollReply" type="tns:PayerAuthEnrollReply" minOccurs="0"/> + <xsd:element name="payerAuthValidateReply" type="tns:PayerAuthValidateReply" minOccurs="0"/> + <xsd:element name="taxReply" type="tns:TaxReply" minOccurs="0"/> + <xsd:element name="encryptedPayment" type="tns:EncryptedPayment" minOccurs="0" /> + <xsd:element name="encryptPaymentDataReply" type="tns:EncryptPaymentDataReply" minOccurs="0"/> + <xsd:element name="dmeReply" type="tns:DMEReply" minOccurs="0"/> + <xsd:element name="afsReply" type="tns:AFSReply" minOccurs="0"/> + <xsd:element name="davReply" type="tns:DAVReply" minOccurs="0"/> + <xsd:element name="exportReply" type="tns:ExportReply" minOccurs="0"/> + <xsd:element name="fxRatesReply" type="tns:FXRatesReply" minOccurs="0"/> + <xsd:element name="bankTransferReply" type="tns:BankTransferReply" minOccurs="0"/> + <xsd:element name="bankTransferRefundReply" type="tns:BankTransferRefundReply" minOccurs="0"/> + <xsd:element name="bankTransferRealTimeReply" type="tns:BankTransferRealTimeReply" minOccurs="0"/> + <xsd:element name="directDebitMandateReply" type="tns:DirectDebitMandateReply" minOccurs="0"/> + <xsd:element name="directDebitReply" type="tns:DirectDebitReply" minOccurs="0"/> + <xsd:element name="directDebitValidateReply" type="tns:DirectDebitValidateReply" minOccurs="0"/> + <xsd:element name="directDebitRefundReply" type="tns:DirectDebitRefundReply" minOccurs="0"/> + <xsd:element name="paySubscriptionCreateReply" type="tns:PaySubscriptionCreateReply" minOccurs="0"/> + <xsd:element name="paySubscriptionUpdateReply" type="tns:PaySubscriptionUpdateReply" minOccurs="0"/> + <xsd:element name="paySubscriptionEventUpdateReply" type="tns:PaySubscriptionEventUpdateReply" minOccurs="0"/> + <xsd:element name="paySubscriptionRetrieveReply" type="tns:PaySubscriptionRetrieveReply" minOccurs="0"/> + <xsd:element name="paySubscriptionDeleteReply" type="tns:PaySubscriptionDeleteReply" minOccurs="0"/> + <xsd:element name="payPalPaymentReply" type="tns:PayPalPaymentReply" minOccurs="0"/> + <xsd:element name="payPalCreditReply" type="tns:PayPalCreditReply" minOccurs="0"/> + <xsd:element name="voidReply" type="tns:VoidReply" minOccurs="0"/> + <xsd:element name="pinlessDebitReply" type="tns:PinlessDebitReply" minOccurs="0"/> + <xsd:element name="pinlessDebitValidateReply" type="tns:PinlessDebitValidateReply" minOccurs="0"/> + <xsd:element name="pinlessDebitReversalReply" type="tns:PinlessDebitReversalReply" minOccurs="0"/> + <xsd:element name="payPalButtonCreateReply" type="tns:PayPalButtonCreateReply" minOccurs="0"/> + <xsd:element name="payPalPreapprovedPaymentReply" type="tns:PayPalPreapprovedPaymentReply" minOccurs="0"/> + <xsd:element name="payPalPreapprovedUpdateReply" type="tns:PayPalPreapprovedUpdateReply" minOccurs="0"/> + <xsd:element name="riskUpdateReply" type="tns:RiskUpdateReply" minOccurs="0"/> + <xsd:element name="fraudUpdateReply" type="tns:FraudUpdateReply" minOccurs="0"/> + <xsd:element name="caseManagementActionReply" type="tns:CaseManagementActionReply" minOccurs="0"/> + <xsd:element name="decisionReply" type="tns:DecisionReply" minOccurs="0"/> + <xsd:element name="payPalRefundReply" type="tns:PayPalRefundReply" minOccurs="0"/> + <xsd:element name="payPalAuthReversalReply" type="tns:PayPalAuthReversalReply" minOccurs="0"/> + <xsd:element name="payPalDoCaptureReply" type="tns:PayPalDoCaptureReply" minOccurs="0"/> + <xsd:element name="payPalEcDoPaymentReply" type="tns:PayPalEcDoPaymentReply" minOccurs="0"/> + <xsd:element name="payPalEcGetDetailsReply" type="tns:PayPalEcGetDetailsReply" minOccurs="0"/> + <xsd:element name="payPalEcSetReply" type="tns:PayPalEcSetReply" minOccurs="0"/> + <xsd:element name="payPalAuthorizationReply" type="tns:PayPalAuthorizationReply" minOccurs="0"/> + <xsd:element name="payPalEcOrderSetupReply" type="tns:PayPalEcOrderSetupReply" minOccurs="0"/> + <xsd:element name="payPalUpdateAgreementReply" type="tns:PayPalUpdateAgreementReply" minOccurs="0"/> + <xsd:element name="payPalCreateAgreementReply" type="tns:PayPalCreateAgreementReply" minOccurs="0"/> + <xsd:element name="payPalDoRefTransactionReply" type="tns:PayPalDoRefTransactionReply" minOccurs="0"/> + <xsd:element name="chinaPaymentReply" type="tns:ChinaPaymentReply" minOccurs="0"/> + <xsd:element name="chinaRefundReply" type="tns:ChinaRefundReply" minOccurs="0"/> + <xsd:element name="boletoPaymentReply" type="tns:BoletoPaymentReply" minOccurs="0"/> + <xsd:element name="pinDebitPurchaseReply" type="tns:PinDebitPurchaseReply" minOccurs="0"/> + <xsd:element name="pinDebitCreditReply" type="tns:PinDebitCreditReply" minOccurs="0"/> + <xsd:element name="pinDebitReversalReply" type="tns:PinDebitReversalReply" minOccurs="0"/> + <xsd:element name="apInitiateReply" type="tns:APInitiateReply" minOccurs="0"/> + <xsd:element name="apCheckStatusReply" type="tns:APCheckStatusReply" minOccurs="0"/> + <xsd:element name="receiptNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="additionalData" type="xsd:string" minOccurs="0"/> + <xsd:element name="solutionProviderTransactionID" type="xsd:string" minOccurs="0"/> + <xsd:element name="apReply" type="tns:APReply" minOccurs="0"/> + <xsd:element name="shipTo" type="tns:ShipTo" minOccurs="0" /> + <xsd:element name="billTo" type="tns:BillTo" minOccurs="0" /> + <xsd:element name="apAuthReply" type="tns:APAuthReply" minOccurs="0"/> + <xsd:element name="apSessionsReply" type="tns:APSessionsReply" minOccurs="0" /> + <xsd:element name="apAuthReversalReply" type="tns:APAuthReversalReply" minOccurs="0"/> + <xsd:element name="apCaptureReply" type="tns:APCaptureReply" minOccurs="0"/> + <xsd:element name="apOptionsReply" type="tns:APOptionsReply" minOccurs="0"/> + <xsd:element name="apRefundReply" type="tns:APRefundReply" minOccurs="0"/> + <xsd:element name="apSaleReply" type="tns:APSaleReply" minOccurs="0"/> + <xsd:element name="apCheckoutDetailsReply" type="tns:APCheckOutDetailsReply" minOccurs="0"/> + <xsd:element name="apTransactionDetailsReply" type="tns:APTransactionDetailsReply" minOccurs="0"/> + <xsd:element name="apConfirmPurchaseReply" type="tns:APConfirmPurchaseReply" minOccurs="0"/> + <xsd:element name="promotion" type="tns:Promotion" minOccurs="0"/> + <xsd:element name="promotionGroup" type="tns:PromotionGroupReply" minOccurs="0" maxOccurs="100"/> + <xsd:element name="payPalGetTxnDetailsReply" type="tns:PayPalGetTxnDetailsReply" minOccurs="0"/> + <xsd:element name="payPalTransactionSearchReply" type="tns:PayPalTransactionSearchReply" minOccurs="0"/> + <xsd:element name="emvReply" type="tns:EmvReply" minOccurs="0" /> + <xsd:element name="originalTransaction" type="tns:OriginalTransaction" minOccurs="0" /> + <xsd:element name="hostedDataCreateReply" type="tns:HostedDataCreateReply" minOccurs="0" /> + <xsd:element name="hostedDataRetrieveReply" type="tns:HostedDataRetrieveReply" minOccurs="0" /> + <xsd:element name="salesSlipNumber" type="xsd:string" minOccurs="0" /> + <xsd:element name="additionalProcessorResponse" type="xsd:string" minOccurs="0" /> + <xsd:element name="jpo" type="tns:JPO" minOccurs="0" /> + <xsd:element name="card" type="tns:Card" minOccurs="0" /> + <xsd:element name="paymentNetworkToken" type="tns:PaymentNetworkToken" minOccurs="0"/> + <xsd:element name="vcReply" type="tns:VCReply" minOccurs="0" /> + <xsd:element name="decryptVisaCheckoutDataReply" type="tns:DecryptVisaCheckoutDataReply" minOccurs="0"/> + <xsd:element name="getVisaCheckoutDataReply" type="tns:GetVisaCheckoutDataReply" minOccurs="0"/> + <xsd:element name="binLookupReply" type="tns:BinLookupReply" minOccurs="0"/> + <xsd:element name="issuerMessage" type="xsd:string" minOccurs="0" /> + <xsd:element name="token" type="tns:Token" minOccurs="0" /> + <xsd:element name="issuer" type="tns:issuer" minOccurs="0" /> + <xsd:element name="recipient" type="tns:Recipient" minOccurs="0"/> + <xsd:element name="feeProgramIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="installment" type="tns:Installment" minOccurs="0" /> + <xsd:element name="paymentAccountReference" type="xsd:string" minOccurs="0"/> + <xsd:element name="authIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="ucaf" type="tns:UCAF" minOccurs="0"/> + <xsd:element name="network" type="tns:Network" minOccurs="0" maxOccurs="100"/> + <xsd:element name="invoiceHeader" type="tns:InvoiceHeader" minOccurs="0" /> + <xsd:element name="apOrderReply" type="tns:APOrderReply" minOccurs="0" /> + <xsd:element name="apCancelReply" type="tns:APCancelReply" minOccurs="0" /> + <xsd:element name="apBillingAgreementReply" type="tns:APBillingAgreementReply" minOccurs="0" /> + <xsd:element name="customerVerificationStatus" type="xsd:string" minOccurs="0" /> + <xsd:element name="personalID" type="tns:PersonalID" minOccurs="0" /> + <xsd:element name="acquirerMerchantNumber" type="xsd:string" minOccurs="0" /> + <xsd:element name="pos" type="tns:Pos" minOccurs="0" /> + <xsd:element name="issuerMessageAction" type="xsd:string" minOccurs="0" /> + <xsd:element name="customerID" type="xsd:string" minOccurs="0" /> + + <xsd:element name="routing" type="tns:Routing" minOccurs="0"/> + <xsd:element name="transactionLocalDateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="apCreateMandateReply" type="tns:APCreateMandateReply" minOccurs="0"/> + <xsd:element name="apMandateStatusReply" type="tns:APMandateStatusReply" minOccurs="0"/> + <xsd:element name="apUpdateMandateReply" type="tns:APUpdateMandateReply" minOccurs="0"/> + <xsd:element name="apImportMandateReply" type="tns:APImportMandateReply" minOccurs="0"/> + <xsd:element name="apRevokeMandateReply" type="tns:APRevokeMandateReply" minOccurs="0"/> + <xsd:element name="getMasterpassDataReply" type="tns:GetMasterpassDataReply" minOccurs="0"/> + <xsd:element name="paymentNetworkMerchantID" type="xsd:string" minOccurs="0"/> + <xsd:element name="wallet" type="tns:Wallet" minOccurs="0" /> + <xsd:element name="cashbackAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="giftCard" type="tns:GiftCard" minOccurs="0" /> + <xsd:element name="giftCardActivationReply" type="tns:GiftCardActivationReply" minOccurs="0"/> + <xsd:element name="giftCardBalanceInquiryReply" type="tns:GiftCardBalanceInquiryReply" minOccurs="0"/> + <xsd:element name="giftCardRedemptionReply" type="tns:GiftCardRedemptionReply" minOccurs="0"/> + <xsd:element name="giftCardVoidReply" type="tns:GiftCardVoidReply" minOccurs="0"/> + <xsd:element name="giftCardReversalReply" type="tns:GiftCardReversalReply" minOccurs="0"/> + <xsd:element name="ccCheckStatusReply" type="tns:CCCheckStatusReply" minOccurs="0"/> + <xsd:element name="ecAVSReply" type="tns:ECAVSReply" minOccurs="0"/> + <xsd:element name="reserved" type="tns:ReplyReserved" minOccurs="0"/> + + <!--ReplyReserved should always be the last element in the xsd, new elements should be added before this--> + </xsd:sequence> + </xsd:complexType> + <xsd:element name="requestMessage" type="tns:RequestMessage"> + </xsd:element> + <xsd:element name="replyMessage" type="tns:ReplyMessage"> + <xsd:unique name="unique-tax-item-id"> + <xsd:selector xpath="tns:taxReplyItem"/> + <xsd:field xpath="@id"/> + </xsd:unique> + </xsd:element> + <xsd:element name="nvpRequest" type="xsd:string"/> + <xsd:element name="nvpReply" type="xsd:string"/> + <!-- used in SOAP faults --> + <xsd:complexType name="FaultDetails"> + <xsd:sequence> + <xsd:element name="requestID" type="xsd:string"/> + </xsd:sequence> + </xsd:complexType> + <xsd:element name="faultDetails" type="tns:FaultDetails"/> + <xsd:complexType name="AirlineData"> + <xsd:sequence> + <xsd:element name="agentCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="agentName" type="xsd:string" minOccurs="0"/> + <xsd:element name="ticketIssuerCity" type="xsd:string" minOccurs="0"/> + <xsd:element name="ticketIssuerState" type="xsd:string" minOccurs="0"/> + <xsd:element name="ticketIssuerPostalCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="ticketIssuerCountry" type="xsd:string" minOccurs="0"/> + <xsd:element name="ticketIssuerAddress" type="xsd:string" minOccurs="0"/> + <xsd:element name="ticketIssuerCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="ticketIssuerName" type="xsd:string" minOccurs="0"/> + <xsd:element name="ticketNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="checkDigit" type="xsd:integer" minOccurs="0"/> + <xsd:element name="restrictedTicketIndicator" type="xsd:integer" minOccurs="0"/> + <xsd:element name="transactionType" type="xsd:string" minOccurs="0"/> + <xsd:element name="extendedPaymentCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="carrierName" type="xsd:string" minOccurs="0"/> + <xsd:element name="passengerName" type="xsd:string" minOccurs="0"/> + <xsd:element name="passenger" type="tns:Passenger" minOccurs="0" maxOccurs="1000"/> + <xsd:element name="customerCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="documentType" type="xsd:string" minOccurs="0"/> + <xsd:element name="documentNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="documentNumberOfParts" type="xsd:string" minOccurs="0"/> + <xsd:element name="invoiceNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="invoiceDate" type="xsd:string" minOccurs="0"/> + <xsd:element name="chargeDetails" type="xsd:string" minOccurs="0"/> + <xsd:element name="bookingReference" type="xsd:string" minOccurs="0"/> + <xsd:element name="totalFee" type="tns:amount" minOccurs="0"/> + <xsd:element name="clearingSequence" type="xsd:string" minOccurs="0"/> + <xsd:element name="clearingCount" type="xsd:integer" minOccurs="0"/> + <xsd:element name="totalClearingAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="leg" type="tns:Leg" minOccurs="0" maxOccurs="1000"/> + <xsd:element name="numberOfPassengers" type="xsd:string" minOccurs="0"/> + <xsd:element name="reservationSystem" type="xsd:string" minOccurs="0"/> + <xsd:element name="processIdentifier" type="xsd:string" minOccurs="0"/> + <xsd:element name="iataNumericCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="ticketIssueDate" type="xsd:string" minOccurs="0"/> + <xsd:element name="electronicTicket" type="xsd:string" minOccurs="0"/> + <xsd:element name="originalTicketNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="purchaseType" type="xsd:string" minOccurs="0"/> + <xsd:element name="creditReasonIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="ticketUpdateIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="planNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="arrivalDate" type="xsd:string" minOccurs="0"/> + <xsd:element name="ticketRestrictionText" type="xsd:string" minOccurs="0"/> + <xsd:element name="exchangeTicketAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="exchangeTicketFee" type="tns:amount" minOccurs="0"/> + <xsd:element name="journeyType" type="xsd:string" minOccurs="0"/> + <xsd:element name="boardingFee" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="Leg"> + <xsd:sequence> + <xsd:element name="carrierCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="flightNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="originatingAirportCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="class" type="xsd:string" minOccurs="0"/> + <xsd:element name="stopoverCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="departureDate" type="xsd:string" minOccurs="0"/> + <xsd:element name="destination" type="xsd:string" minOccurs="0"/> + <xsd:element name="fareBasis" type="xsd:string" minOccurs="0"/> + <xsd:element name="departTax" type="xsd:string" minOccurs="0"/> + <xsd:element name="conjunctionTicket" type="xsd:string" minOccurs="0"/> + <xsd:element name="exchangeTicket" type="xsd:string" minOccurs="0"/> + <xsd:element name="couponNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="departureTime" type="xsd:string" minOccurs="0"/> + <xsd:element name="departureTimeSegment" type="xsd:string" minOccurs="0"/> + <xsd:element name="arrivalTime" type="xsd:string" minOccurs="0"/> + <xsd:element name="arrivalTimeSegment" type="xsd:string" minOccurs="0"/> + <xsd:element name="endorsementsRestrictions" type="xsd:string" minOccurs="0"/> + <xsd:element name="fare" type="xsd:string" minOccurs="0"/> + <xsd:element name="fee" type="xsd:string" minOccurs="0"/> + <xsd:element name="tax" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="id" type="xsd:integer" use="required"/> + </xsd:complexType> + <xsd:complexType name="AncillaryData"> + <xsd:sequence> + <xsd:element name="ticketNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="passengerName" type="xsd:string" minOccurs="0"/> + <xsd:element name="connectedTicketNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="creditReasonIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="service" type="tns:Service" minOccurs="0" maxOccurs="1000"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="Service"> + <xsd:sequence> + <xsd:element name="categoryCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="subcategoryCode" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="id" type="xsd:integer" use="required"/> + </xsd:complexType> + <xsd:complexType name="LodgingData"> + <xsd:sequence> + <xsd:element name="checkInDate" type="xsd:string" minOccurs="0"/> + <xsd:element name="checkOutDate" type="xsd:string" minOccurs="0"/> + <xsd:element name="dailyRoomRate1" type="tns:amount" minOccurs="0"/> + <xsd:element name="dailyRoomRate2" type="tns:amount" minOccurs="0"/> + <xsd:element name="dailyRoomRate3" type="tns:amount" minOccurs="0"/> + <xsd:element name="roomNights1" type="xsd:integer" minOccurs="0"/> + <xsd:element name="roomNights2" type="xsd:integer" minOccurs="0"/> + <xsd:element name="roomNights3" type="xsd:integer" minOccurs="0"/> + <xsd:element name="guestSmokingPreference" type="xsd:string" minOccurs="0"/> + <xsd:element name="numberOfRoomsBooked" type="xsd:integer" minOccurs="0"/> + <xsd:element name="numberOfGuests" type="xsd:integer" minOccurs="0"/> + <xsd:element name="roomBedType" type="xsd:string" minOccurs="0"/> + <xsd:element name="roomTaxElements" type="xsd:string" minOccurs="0"/> + <xsd:element name="roomRateType" type="xsd:string" minOccurs="0"/> + <xsd:element name="guestName" type="xsd:string" minOccurs="0"/> + <xsd:element name="customerServicePhoneNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="corporateClientCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="promotionalCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="additionalCoupon" type="xsd:string" minOccurs="0"/> + <xsd:element name="roomLocation" type="xsd:string" minOccurs="0"/> + <xsd:element name="specialProgramCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="tax" type="tns:amount" minOccurs="0"/> + <xsd:element name="prepaidCost" type="tns:amount" minOccurs="0"/> + <xsd:element name="foodAndBeverageCost" type="tns:amount" minOccurs="0"/> + <xsd:element name="roomTax" type="tns:amount" minOccurs="0"/> + <xsd:element name="adjustmentAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="phoneCost" type="tns:amount" minOccurs="0"/> + <xsd:element name="restaurantCost" type="tns:amount" minOccurs="0"/> + <xsd:element name="roomServiceCost" type="tns:amount" minOccurs="0"/> + <xsd:element name="miniBarCost" type="tns:amount" minOccurs="0"/> + <xsd:element name="laundryCost" type="tns:amount" minOccurs="0"/> + <xsd:element name="miscellaneousCost" type="tns:amount" minOccurs="0"/> + <xsd:element name="giftShopCost" type="tns:amount" minOccurs="0"/> + <xsd:element name="movieCost" type="tns:amount" minOccurs="0"/> + <xsd:element name="healthClubCost" type="tns:amount" minOccurs="0"/> + <xsd:element name="valetParkingCost" type="tns:amount" minOccurs="0"/> + <xsd:element name="cashDisbursementCost" type="tns:amount" minOccurs="0"/> + <xsd:element name="nonRoomCost" type="tns:amount" minOccurs="0"/> + <xsd:element name="businessCenterCost" type="tns:amount" minOccurs="0"/> + <xsd:element name="loungeBarCost" type="tns:amount" minOccurs="0"/> + <xsd:element name="transportationCost" type="tns:amount" minOccurs="0"/> + <xsd:element name="gratuityCost" type="tns:amount" minOccurs="0"/> + <xsd:element name="conferenceRoomCost" type="tns:amount" minOccurs="0"/> + <xsd:element name="audioVisualCost" type="tns:amount" minOccurs="0"/> + <xsd:element name="banquetCost" type="tns:amount" minOccurs="0"/> + <xsd:element name="internetAccessCost" type="tns:amount" minOccurs="0"/> + <xsd:element name="earlyCheckOutCost" type="tns:amount" minOccurs="0"/> + <xsd:element name="nonRoomTax" type="tns:amount" minOccurs="0"/> + <xsd:element name="travelAgencyCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="travelAgencyName" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="Pos"> + <xsd:sequence> + <xsd:element name="entryMode" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardPresent" type="xsd:string" minOccurs="0"/> + <xsd:element name="terminalCapability" type="xsd:string" minOccurs="0"/> + <xsd:element name="trackData" type="xsd:string" minOccurs="0"/> + <xsd:element name="terminalID" type="xsd:string" minOccurs="0"/> + <xsd:element name="terminalType" type="xsd:string" minOccurs="0"/> + <xsd:element name="terminalLocation" type="xsd:string" minOccurs="0"/> + <xsd:element name="transactionSecurity" type="xsd:string" minOccurs="0"/> + <xsd:element name="catLevel" type="xsd:string" minOccurs="0"/> + <xsd:element name="conditionCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="environment" type="xsd:string" minOccurs="0"/> + <xsd:element name="paymentData" type="xsd:string" minOccurs="0"/> + <xsd:element name="deviceReaderData" type="xsd:string" minOccurs="0"/> + <xsd:element name="encryptionAlgorithm" type="xsd:string" minOccurs="0"/> + <xsd:element name="encodingMethod" type="xsd:string" minOccurs="0"/> + <xsd:element name="deviceID" type="xsd:string" minOccurs="0"/> + <xsd:element name="serviceCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="terminalIDAlternate" type="xsd:string" minOccurs="0" /> + <xsd:element name="terminalCompliance" type="xsd:integer" minOccurs="0" /> + <xsd:element name="terminalCardCaptureCapability" type="xsd:string" minOccurs="0" /> + <xsd:element name="terminalOutputCapability" type="xsd:string" minOccurs="0" /> + <xsd:element name="terminalPINcapability" type="xsd:string" minOccurs="0" /> + <xsd:element name="terminalCVMcapabilities_0" type="xsd:string" minOccurs="0" /> + <xsd:element name="terminalCVMcapabilities_1" type="xsd:string" minOccurs="0" /> + <xsd:element name="terminalCVMcapabilities_2" type="xsd:string" minOccurs="0" /> + <xsd:element name="terminalInputCapabilities_0" type="xsd:string" minOccurs="0" /> + <xsd:element name="terminalInputCapabilities_1" type="xsd:string" minOccurs="0" /> + <xsd:element name="terminalInputCapabilities_2" type="xsd:string" minOccurs="0" /> + <xsd:element name="terminalInputCapabilities_3" type="xsd:string" minOccurs="0" /> + <xsd:element name="terminalInputCapabilities_4" type="xsd:string" minOccurs="0" /> + <xsd:element name="terminalInputCapabilities_5" type="xsd:string" minOccurs="0" /> + <xsd:element name="terminalInputCapabilities_6" type="xsd:string" minOccurs="0" /> + <xsd:element name="terminalSerialNumber" type="xsd:string" minOccurs="0" /> + <xsd:element name="storeAndForwardIndicator" type="xsd:string" minOccurs="0" /> + <xsd:element name="panEntryMode" type="xsd:string" minOccurs="0" /> + <xsd:element name="endlessAisleTransactionIndicator" type="tns:boolean" minOccurs="0" /> + <xsd:element name="terminalModel" type="xsd:string" minOccurs="0" /> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="Pin"> + <xsd:sequence> + <xsd:element name="entryCapability" type="xsd:string" minOccurs="0"/> + + + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="EncryptedPayment"> + <xsd:sequence> + <xsd:element name="descriptor" type="xsd:string" minOccurs="0"/> + <xsd:element name="data" type="xsd:string" minOccurs="0"/> + <xsd:element name="encoding" type="xsd:string" minOccurs="0"/> + <xsd:element name="wrappedKey" type="xsd:string" minOccurs="0"/> + <xsd:element name="referenceID" type="xsd:integer" minOccurs="0"/> + <xsd:element name="errorCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="keySerialNumber" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="Installment"> + <xsd:sequence> + <xsd:element name="sequence" type="xsd:string" minOccurs="0"/> + <xsd:element name="totalCount" type="xsd:string" minOccurs="0"/> + <xsd:element name="totalAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="frequency" type="xsd:string" minOccurs="0"/> + <xsd:element name="amount" type="xsd:string" minOccurs="0"/> + <xsd:element name="planType" type="xsd:string" minOccurs="0"/> + + <xsd:element name="firstInstallmentDate" type="xsd:string" minOccurs="0"/> + <xsd:element name="amountFunded" type="xsd:string" minOccurs="0"/> + <xsd:element name="amountRequestedPercentage" type="xsd:string" minOccurs="0"/> + <xsd:element name="expenses" type="xsd:string" minOccurs="0"/> + <xsd:element name="expensesPercentage" type="xsd:string" minOccurs="0"/> + <xsd:element name="fees" type="xsd:string" minOccurs="0"/> + <xsd:element name="feesPercentage" type="xsd:string" minOccurs="0"/> + <xsd:element name="taxes" type="xsd:string" minOccurs="0"/> + <xsd:element name="taxesPercentage" type="xsd:string" minOccurs="0"/> + <xsd:element name="insurance" type="xsd:string" minOccurs="0"/> + <xsd:element name="insurancePercentage" type="xsd:string" minOccurs="0"/> + <xsd:element name="additionalCosts" type="xsd:string" minOccurs="0"/> + <xsd:element name="additionalCostsPercentage" type="xsd:string" minOccurs="0"/> + <xsd:element name="monthlyInterestRate" type="xsd:string" minOccurs="0"/> + <xsd:element name="annualInterestRate" type="xsd:string" minOccurs="0"/> + <xsd:element name="annualFinancingCost" type="xsd:string" minOccurs="0"/> + <xsd:element name="paymentType" type="xsd:string" minOccurs="0"/> + <xsd:element name="invoiceData" type="xsd:string" minOccurs="0"/> + <xsd:element name="downPayment" type="xsd:string" minOccurs="0"/> + <xsd:element name="firstInstallmentAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="minimumTotalCount" type="xsd:string" minOccurs="0"/> + <xsd:element name="maximumTotalCount" type="xsd:string" minOccurs="0"/> + <xsd:element name="gracePeriodDuration" type="xsd:string" minOccurs="0"/> + <xsd:element name="gracePeriodDurationType" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="MDDField"> + <xsd:simpleContent> + <xsd:extension base="xsd:string"> + <xsd:attribute name="id" type="xsd:integer" use="required"/> + </xsd:extension> + </xsd:simpleContent> + </xsd:complexType> + <xsd:complexType name="MerchantDefinedData"> + <xsd:sequence> + <xsd:element name="field1" type="xsd:string" minOccurs="0"/> + <xsd:element name="field2" type="xsd:string" minOccurs="0"/> + <xsd:element name="field3" type="xsd:string" minOccurs="0"/> + <xsd:element name="field4" type="xsd:string" minOccurs="0"/> + <xsd:element name="field5" type="xsd:string" minOccurs="0"/> + <xsd:element name="field6" type="xsd:string" minOccurs="0"/> + <xsd:element name="field7" type="xsd:string" minOccurs="0"/> + <xsd:element name="field8" type="xsd:string" minOccurs="0"/> + <xsd:element name="field9" type="xsd:string" minOccurs="0"/> + <xsd:element name="field10" type="xsd:string" minOccurs="0"/> + <xsd:element name="field11" type="xsd:string" minOccurs="0"/> + <xsd:element name="field12" type="xsd:string" minOccurs="0"/> + <xsd:element name="field13" type="xsd:string" minOccurs="0"/> + <xsd:element name="field14" type="xsd:string" minOccurs="0"/> + <xsd:element name="field15" type="xsd:string" minOccurs="0"/> + <xsd:element name="field16" type="xsd:string" minOccurs="0"/> + <xsd:element name="field17" type="xsd:string" minOccurs="0"/> + <xsd:element name="field18" type="xsd:string" minOccurs="0"/> + <xsd:element name="field19" type="xsd:string" minOccurs="0"/> + <xsd:element name="field20" type="xsd:string" minOccurs="0"/> + <xsd:element name="mddField" type="tns:MDDField" minOccurs="0" maxOccurs="100"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="MerchantSecureData"> + <xsd:sequence> + <xsd:element name="field1" type="xsd:string" minOccurs="0"/> + <xsd:element name="field2" type="xsd:string" minOccurs="0"/> + <xsd:element name="field3" type="xsd:string" minOccurs="0"/> + <xsd:element name="field4" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="ReplyReserved"> + <xsd:sequence> + <xsd:any processContents="skip" minOccurs="0" maxOccurs="999"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="RequestReserved"> + <xsd:sequence> + <xsd:element name="name" type="xsd:string"/> + <xsd:element name="value" type="xsd:string"/> + </xsd:sequence> + </xsd:complexType> + <!-- PayPalGetTxnDetails --> + <xsd:complexType name="PayPalGetTxnDetailsReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="payer" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerId" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerCountry" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerBusiness" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerSalutation" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerFirstname" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerMiddlename" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerLastname" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerSuffix" type="xsd:string" minOccurs="0"/> + <xsd:element name="addressID" type="xsd:string" minOccurs="0"/> + <xsd:element name="addressStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="shipToName" type="xsd:string" minOccurs="0"/> + <xsd:element name="shipToAddress1" type="xsd:string" minOccurs="0"/> + <xsd:element name="shipToAddress2" type="xsd:string" minOccurs="0"/> + <xsd:element name="shipToCity" type="xsd:string" minOccurs="0"/> + <xsd:element name="shipToState" type="xsd:string" minOccurs="0"/> + <xsd:element name="shipToCountry" type="xsd:string" minOccurs="0"/> + <xsd:element name="shipToZip" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerPhone" type="xsd:string" minOccurs="0"/> + <xsd:element name="transactionId" type="xsd:string" minOccurs="0"/> + <xsd:element name="parentTransactionId" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalReceiptId" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalTransactiontype" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalPaymentType" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalOrderTime" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalPaymentGrossAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalFeeAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="currency" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalSettleAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalTaxAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalExchangeRate" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalPaymentStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalPendingReason" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalReasonCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="protectionEligibility" type="xsd:string" minOccurs="0"/> + <xsd:element name="protectionEligibilityType" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalNote" type="xsd:string" minOccurs="0"/> + <xsd:element name="invoiceNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="item" type="tns:Item" minOccurs="0" maxOccurs="1000" /> + <xsd:element name="errorCode" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + + <!-- end of PayPalGetTxnDetails --> + + <!-- PayPalTransactionSearchReply --> + <xsd:complexType name="PayPalTransactionSearchReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="transaction" type="tns:PaypalTransaction" minOccurs="0" maxOccurs="999" /> + <xsd:element name="errorCode" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="PaypalTransaction"> + <xsd:sequence> + <xsd:element name="transactionTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="transactionTimeZone" type="xsd:string" minOccurs="0"/> + <xsd:element name="transactionType" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalPayerOrPayeeEmail" type="xsd:string" minOccurs="0"/> + <xsd:element name="customerDisplayName" type="xsd:string" minOccurs="0"/> + <xsd:element name="transactionID" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalPaymentStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="grandTotalAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="currency" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalFeeAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalNetAmount" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="id" type="xsd:integer" use="optional"/> + </xsd:complexType> + <!-- end of PayPalTransactionSearchReply --> + + <xsd:complexType name="CCDCCUpdateService"> + <xsd:sequence> + <xsd:element name="reason" type="xsd:string" minOccurs="0"/> + <xsd:element name="action" type="xsd:string" minOccurs="0"/> + <xsd:element name="dccRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="captureRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="creditRequestID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <!-- Merchant Descriptor fields for Service Fee. goes into RequestMessage--> + <xsd:complexType name="ServiceFee"> + <xsd:sequence> + <xsd:element name="merchantDescriptor" type="xsd:string" minOccurs="0"/> + <xsd:element name="merchantDescriptorContact" type="xsd:string" minOccurs="0"/> + <xsd:element name="merchantDescriptorState" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <!-- EMV transaction data request/reply start --> + <xsd:complexType name="EmvRequest"> + <xsd:sequence> + <xsd:element name="combinedTags" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardSequenceNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="aidAndDFname" type="xsd:string" minOccurs="0"/> + <xsd:element name="fallback" type="xsd:string" minOccurs="0"/> + <xsd:element name="fallbackCondition" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="EmvReply"> + <xsd:sequence> + <xsd:element name="combinedTags" type="xsd:string" minOccurs="0"/> + <xsd:element name="decryptedRequestTags" type="xsd:string" minOccurs="0"/> + <xsd:element name="chipValidationResults" type="xsd:string" minOccurs="0"/> + <xsd:element name="chipValidationType" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <!-- EMV transaction data request/reply end --> + <!-- Auth Reversal time out merchant intitated --> + <xsd:complexType name="OriginalTransaction"> + <xsd:sequence> + <xsd:element name="amount" type="tns:amount" minOccurs="0" /> + <xsd:element name="reasonCode" type="xsd:string" minOccurs="0" /> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="HostedDataCreateService"> + <xsd:sequence> + <xsd:element name="profileID" type="xsd:string" minOccurs="0"/> + <xsd:element name="paymentMethod" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="HostedDataRetrieveService"> + <xsd:sequence> + <xsd:element name="profileID" type="xsd:string" minOccurs="0"/> + <xsd:element name="tokenValue" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + + <xsd:complexType name="HostedDataCreateReply"> + <xsd:sequence> + <xsd:element name="responseMessage" type="xsd:string" minOccurs="0" /> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="cardAccountNumberToken" type="xsd:string" minOccurs="0" /> + <xsd:element name="customerID" type="xsd:string" minOccurs="0" /> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="HostedDataRetrieveReply"> + <xsd:sequence> + <xsd:element name="responseMessage" type="xsd:string" minOccurs="0" /> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="aggregatorMerchantIdentifier" type="xsd:string" minOccurs="0" /> + <xsd:element name="customerFirstName" type="xsd:string" minOccurs="0" /> + <xsd:element name="customerLastName" type="xsd:string" minOccurs="0" /> + <xsd:element name="customerID" type="xsd:string" minOccurs="0" /> + <xsd:element name="paymentMethod" type="xsd:string" minOccurs="0" /> + <xsd:element name="billToStreet1" type="xsd:string" minOccurs="0"/> + <xsd:element name="billToStreet2" type="xsd:string" minOccurs="0"/> + <xsd:element name="billToEmail" type="xsd:string" minOccurs="0"/> + <xsd:element name="billToState" type="xsd:string" minOccurs="0"/> + <xsd:element name="billToFirstName" type="xsd:string" minOccurs="0"/> + <xsd:element name="billToLastName" type="xsd:string" minOccurs="0"/> + <xsd:element name="billToCity" type="xsd:string" minOccurs="0"/> + <xsd:element name="billToCountry" type="xsd:string" minOccurs="0"/> + <xsd:element name="billToPostalCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardAccountNumber" type="xsd:string" minOccurs="0" /> + <xsd:element name="cardType" type="xsd:string" minOccurs="0" /> + <xsd:element name="cardExpirationMonth" type="xsd:string" minOccurs="0" /> + <xsd:element name="cardExpirationYear" type="xsd:string" minOccurs="0" /> + <xsd:element name="cardIssueNumber" type="xsd:string" minOccurs="0" /> + <xsd:element name="cardStartMonth" type="xsd:string" minOccurs="0" /> + <xsd:element name="cardStartYear" type="xsd:string" minOccurs="0" /> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="AutoRentalData"> + <xsd:sequence> + <xsd:element name="adjustmentCost" type="tns:amount" minOccurs="0" /> + <xsd:element name="adjustmentCode" type="xsd:string" minOccurs="0" /> + <xsd:element name="agreementNumber" type="xsd:string" minOccurs="0" /> + <xsd:element name="classCode" type="xsd:string" minOccurs="0" /> + <xsd:element name="customerServicePhoneNumber" type="xsd:string" minOccurs="0" /> + <xsd:element name="dailyRate" type="tns:amount" minOccurs="0" /> + <xsd:element name="mileageCost" type="tns:amount" minOccurs="0" /> + <xsd:element name="gasCost" type="tns:amount" minOccurs="0" /> + <xsd:element name="insuranceCost" type="tns:amount" minOccurs="0" /> + <xsd:element name="lateReturnCost" type="tns:amount" minOccurs="0" /> + <xsd:element name="maximumFreeMiles" type="xsd:integer" minOccurs="0" /> + <xsd:element name="milesTraveled" type="xsd:integer" minOccurs="0" /> + <xsd:element name="oneWayCost" type="tns:amount" minOccurs="0" /> + <xsd:element name="parkingViolationCost" type="tns:amount" minOccurs="0" /> + <xsd:element name="pickUpCity" type="xsd:string" minOccurs="0" /> + <xsd:element name="pickUpCountry" type="xsd:string" minOccurs="0" /> + <xsd:element name="pickUpDate" type="xsd:string" minOccurs="0" /> + <xsd:element name="pickUpState" type="xsd:string" minOccurs="0" /> + <xsd:element name="pickUpTime" type="xsd:integer" minOccurs="0" /> + <xsd:element name="ratePerMile" type="tns:amount" minOccurs="0" /> + <xsd:element name="renterName" type="xsd:string" minOccurs="0" /> + <xsd:element name="returnCity" type="xsd:string" minOccurs="0" /> + <xsd:element name="returnCountry" type="xsd:string" minOccurs="0" /> + <xsd:element name="returnDate" type="xsd:string" minOccurs="0" /> + <xsd:element name="returnLocationID" type="xsd:string" minOccurs="0" /> + <xsd:element name="returnState" type="xsd:string" minOccurs="0" /> + <xsd:element name="returnTime" type="xsd:integer" minOccurs="0" /> + <xsd:element name="specialProgramCode" type="xsd:string" minOccurs="0" /> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="VCReply"> + <xsd:sequence> + <xsd:element name="creationTimeStamp" type="xsd:string" minOccurs="0" /> + <xsd:element name="alternateShippingAddressCountryCode" type="xsd:string" minOccurs="0" /> + <xsd:element name="alternateShippingAddressPostalCode" type="xsd:string" minOccurs="0" /> + <xsd:element name="vcAccountLoginName" type="xsd:string" minOccurs="0" /> + <xsd:element name="vcAccountFirstName" type="xsd:string" minOccurs="0" /> + <xsd:element name="vcAccountLastName" type="xsd:string" minOccurs="0" /> + <xsd:element name="vcAccountEncryptedID" type="xsd:string" minOccurs="0" /> + <xsd:element name="vcAccountEmail" type="xsd:string" minOccurs="0" /> + <xsd:element name="vcAccountMobilePhoneNumber" type="xsd:string" minOccurs="0" /> + <xsd:element name="merchantReferenceID" type="xsd:string" minOccurs="0" /> + <xsd:element name="subtotalAmount" type="xsd:string" minOccurs="0" /> + <xsd:element name="shippingHandlingAmount" type="xsd:string" minOccurs="0" /> + <xsd:element name="taxAmount" type="xsd:string" minOccurs="0" /> + <xsd:element name="discountAmount" type="xsd:string" minOccurs="0" /> + <xsd:element name="giftWrapAmount" type="xsd:string" minOccurs="0" /> + <xsd:element name="uncategorizedAmount" type="xsd:string" minOccurs="0" /> + <xsd:element name="totalPurchaseAmount" type="xsd:string" minOccurs="0" /> + <xsd:element name="walletReferenceID" type="xsd:string" minOccurs="0" /> + <xsd:element name="promotionCode" type="xsd:string" minOccurs="0" /> + <xsd:element name="paymentInstrumentID" type="xsd:string" minOccurs="0" /> + <xsd:element name="cardVerificationStatus" type="xsd:string" minOccurs="0" /> + <xsd:element name="issuerID" type="xsd:string" minOccurs="0" /> + <xsd:element name="paymentInstrumentNickName" type="xsd:string" minOccurs="0" /> + <xsd:element name="nameOnCard" type="xsd:string" minOccurs="0" /> + <xsd:element name="cardType" type="xsd:string" minOccurs="0" /> + <xsd:element name="cardGroup" type="xsd:string" minOccurs="0" /> + <xsd:element name="cardArt" type="tns:VCCardArt" minOccurs="0" /> + <xsd:element name="riskAdvice" type="xsd:string" minOccurs="0" /> + <xsd:element name="riskScore" type="xsd:string" minOccurs="0" /> + <xsd:element name="riskAdditionalData" type="xsd:string" minOccurs="0" /> + <xsd:element name="avsCodeRaw" type="xsd:string" minOccurs="0" /> + <xsd:element name="cvnCodeRaw" type="xsd:string" minOccurs="0" /> + <xsd:element name="eciRaw" type="xsd:string" minOccurs="0" /> + <xsd:element name="eci" type="xsd:string" minOccurs="0" /> + <xsd:element name="cavv" type="xsd:string" minOccurs="0" /> + <xsd:element name="veresEnrolled" type="xsd:string" minOccurs="0" /> + <xsd:element name="veresTimeStamp" type="xsd:string" minOccurs="0" /> + <xsd:element name="paresStatus" type="xsd:string" minOccurs="0" /> + <xsd:element name="paresTimeStamp" type="xsd:string" minOccurs="0" /> + <xsd:element name="xid" type="xsd:string" minOccurs="0" /> + <xsd:element name="customData" type="tns:VCCustomData" minOccurs="0" /> + <xsd:element name="vcAccountFullName" type="xsd:string" minOccurs="0" /> + <xsd:element name="paymentDescription" type="xsd:string" minOccurs="0" /> + <xsd:element name="billingAddressStreetName" type="xsd:string" minOccurs="0" /> + <xsd:element name="billingAddressAdditionalLocation" type="xsd:string" minOccurs="0" /> + <xsd:element name="billingAddressStreetNumber" type="xsd:string" minOccurs="0" /> + <xsd:element name="expiredCard" type="xsd:string" minOccurs="0" /> + <xsd:element name="cardFirstName" type="xsd:string" minOccurs="0" /> + <xsd:element name="cardLastName" type="xsd:string" minOccurs="0" /> + <xsd:element name="shippingAddressStreetName" type="xsd:string" minOccurs="0" /> + <xsd:element name="shippingAddressAdditionalLocation" type="xsd:string" minOccurs="0" /> + <xsd:element name="shippingAddressStreetNumber" type="xsd:string" minOccurs="0" /> + <xsd:element name="ageOfAccount" type="xsd:string" minOccurs="0" /> + <xsd:element name="newUser" type="xsd:string" minOccurs="0" /> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="VCCardArt"> + <xsd:sequence> + <xsd:element name="fileName" type="xsd:string" minOccurs="0" /> + <xsd:element name="height" type="xsd:string" minOccurs="0" /> + <xsd:element name="width" type="xsd:string" minOccurs="0" /> + </xsd:sequence> + <xsd:attribute name="id" type="xsd:integer" use="optional"/> + </xsd:complexType> + + <xsd:complexType name="VCCustomData"> + <xsd:sequence> + <xsd:element name="name" type="xsd:string" minOccurs="0" /> + <xsd:element name="value" type="xsd:string" minOccurs="0" /> + </xsd:sequence> + <xsd:attribute name="id" type="xsd:integer" use="optional"/> + </xsd:complexType> + + <xsd:complexType name="DecryptVisaCheckoutDataReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="GetVisaCheckoutDataReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="EncryptPaymentDataReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="requestDateTime" type="tns:dateTime" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="BinLookupService"> + <xsd:sequence> + <xsd:element name="mode" type="xsd:string" minOccurs="0" /> + <xsd:element name="networkOrder" type="xsd:string" minOccurs="0" /> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + + <xsd:complexType name="BinLookupReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="requestDateTime" type="tns:dateTime" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="issuer"> + <xsd:sequence> + <xsd:element name="additionalData" type="xsd:string" minOccurs="0"/> + <xsd:element name="name" type="xsd:string" minOccurs="0"/> + <xsd:element name="country" type="xsd:string" minOccurs="0"/> + <xsd:element name="countryNumericCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="phoneNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="responseCode" type="xsd:string" minOccurs="0" /> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="GETVisaCheckoutDataService"> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="TransactionMetadataService"> + <xsd:sequence> + <xsd:element name="authRequestID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="Loan"> + <xsd:sequence> + <xsd:element name="assetType" type="xsd:string" minOccurs="0"/> + <xsd:element name="type" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="APOrderService"> + <xsd:sequence> + <xsd:element name="sessionsRequestID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + + <xsd:complexType name="APOrderReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="amount" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="status" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="dateTime" type="tns:dateTime" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="APCancelService"> + <xsd:sequence> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="orderRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="saleRequestID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + + <xsd:complexType name="APCancelReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="processorTransactionID" type="xsd:string" minOccurs="0"/> + <xsd:element name="status" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="dateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="paymentStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="responseCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="APBillingAgreementService"> + <xsd:sequence> + <xsd:element name="sessionsRequestID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + + <xsd:complexType name="APBillingAgreementReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="amount" type="xsd:string" minOccurs="0"/> + <xsd:element name="status" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="dateTime" type="tns:dateTime" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="Passenger"> + <xsd:sequence> + <xsd:element name="firstName" type="xsd:string" minOccurs="0" maxOccurs="1"/> + <xsd:element name="lastName" type="xsd:string" minOccurs="0" maxOccurs="1"/> + </xsd:sequence> + <xsd:attribute name="id" type="xsd:integer" use="required"/> + </xsd:complexType> + + <xsd:complexType name="PostdatedTransaction"> + <xsd:sequence> + <xsd:element name="guaranteeIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="guaranteeAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="settlementDate" type="xsd:integer" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="APCreateMandateService"> + <xsd:sequence> + <xsd:element name="saleRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="cancelURL" type="xsd:string" minOccurs="0"/> + <xsd:element name="successURL" type="xsd:string" minOccurs="0"/> + <xsd:element name="failureURL" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + + <xsd:complexType name="APCreateMandateReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="mandateID" type="xsd:string"/> + <xsd:element name="status" type="xsd:string"/> + <xsd:element name="merchantURL" type="xsd:string"/> + <xsd:element name="responseCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorTransactionID" type="xsd:string" minOccurs="0"/> + <xsd:element name="riskScore" type="xsd:string" minOccurs="0"/> + <xsd:element name="encodedHTML" type="xsd:string" minOccurs="0"/> + <xsd:element name="encodedPopupHTML" type="xsd:string" minOccurs="0"/> + <xsd:element name="dateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="dateSigned" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="dateCreated" type="tns:dateTime" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="APMandateStatusService"> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + + <xsd:complexType name="APMandateStatusReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="mandateID" type="xsd:string"/> + <xsd:element name="status" type="xsd:string"/> + <xsd:element name="responseCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorTransactionID" type="xsd:string" minOccurs="0"/> + <xsd:element name="dateCreated" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="dateSigned" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="dateRevoked" type="tns:dateTime" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="APUpdateMandateService"> + <xsd:sequence> + <xsd:element name="esign" type="xsd:string" minOccurs="0"/> + <xsd:element name="cancelURL" type="xsd:string" minOccurs="0"/> + <xsd:element name="successURL" type="xsd:string" minOccurs="0"/> + <xsd:element name="failureURL" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + + <xsd:complexType name="GetMasterpassDataService"> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + + <xsd:complexType name="GetMasterpassDataReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="APUpdateMandateReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="mandateID" type="xsd:string"/> + <xsd:element name="status" type="xsd:string"/> + <xsd:element name="merchantURL" type="xsd:string"/> + <xsd:element name="responseCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorTransactionID" type="xsd:string" minOccurs="0"/> + <xsd:element name="riskScore" type="xsd:string" minOccurs="0"/> + <xsd:element name="encodedHTML" type="xsd:string" minOccurs="0"/> + <xsd:element name="encodedPopupHTML" type="xsd:string" minOccurs="0"/> + <xsd:element name="dateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="dateSigned" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="dateCreated" type="tns:dateTime" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="APImportMandateReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="mandateID" type="xsd:string"/> + <xsd:element name="status" type="xsd:string"/> + <xsd:element name="responseCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorTransactionID" type="xsd:string" minOccurs="0"/> + <xsd:element name="dateSigned" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="dateCreated" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="dateTime" type="tns:dateTime" minOccurs="0"/> + + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="APRevokeMandateService"> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + + <xsd:complexType name="APRevokeMandateReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="mandateID" type="xsd:string"/> + <xsd:element name="status" type="xsd:string"/> + <xsd:element name="responseCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorTransactionID" type="xsd:string" minOccurs="0"/> + <xsd:element name="dateSigned" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="dateCreated" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="dateRevoked" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="dateTime" type="tns:dateTime" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="Category"> + <xsd:sequence> + <xsd:element name="affiliate" type="xsd:string" minOccurs="0"/> + <xsd:element name="campaign" type="xsd:string" minOccurs="0"/> + <xsd:element name="group" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="ECAVSService"> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + + + <xsd:complexType name="GiftCardActivationService"> + <xsd:sequence> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="commerceIndicator" type="xsd:string" minOccurs="0"/> + + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + + <xsd:complexType name="GiftCardBalanceInquiryService"> + <xsd:sequence> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="commerceIndicator" type="xsd:string" minOccurs="0"/> + + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + + <xsd:complexType name="GiftCardVoidService"> + + <xsd:attribute name="run" type="tns:boolean" use="required"/> + + </xsd:complexType> + + <xsd:complexType name="GiftCardReversalService"> + + <xsd:attribute name="run" type="tns:boolean" use="required"/> + + </xsd:complexType> + + <xsd:complexType name="GiftCardRedemptionService"> + <xsd:sequence> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="commerceIndicator" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + + <xsd:complexType name="GiftCard"> + <xsd:sequence> + <xsd:element name="originalRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="redemptionType" type="xsd:string" minOccurs="0"/> + <xsd:element name="count" type="xsd:string" minOccurs="0"/> + <xsd:element name="escheatable" type="tns:boolean" minOccurs="0"/> + <xsd:element name="groupID" type="xsd:string" minOccurs="0"/> + <xsd:element name="securityValue" type="xsd:string" minOccurs="0"/> + <xsd:element name="transactionPostingDate" type="xsd:string" minOccurs="0"/> + <xsd:element name="additionalAccountNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="promoCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="balanceCurrency" type="xsd:string" minOccurs="0"/> + <xsd:element name="extendedAccountNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="previousBalance" type="xsd:string" minOccurs="0"/> + <xsd:element name="currentBalance" type="xsd:string" minOccurs="0"/> + <xsd:element name="baseCurrencyPreviousBalance" type="xsd:string" minOccurs="0"/> + <xsd:element name="baseCurrencyCurrentBalance" type="xsd:string" minOccurs="0"/> + <xsd:element name="baseCurrencyCashbackAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="baseCurrency" type="xsd:string" minOccurs="0"/> + <xsd:element name="expirationDate" type="xsd:string" minOccurs="0"/> + <xsd:element name="exchangeRate" type="xsd:string" minOccurs="0"/> + <xsd:element name="bonusAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="discountAmount" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="GiftCardActivationReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="authorizationCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="requestDateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="GiftCardBalanceInquiryReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="authorizationCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="requestDateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="GiftCardRedemptionReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="authorizationCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="requestDateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="GiftCardReversalReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="authorizationCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="requestDateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="GiftCardVoidReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="authorizationCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="requestDeTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="mPOS"> + <xsd:sequence> + <xsd:element name="deviceType" type="xsd:string" minOccurs="0" /> + </xsd:sequence> + </xsd:complexType> +</xsd:schema> + diff --git a/test/schema/cyber_source/CyberSourceTransaction_1.156.xsd b/test/schema/cyber_source/CyberSourceTransaction_1.156.xsd new file mode 100644 index 00000000000..74dc2e7b7a5 --- /dev/null +++ b/test/schema/cyber_source/CyberSourceTransaction_1.156.xsd @@ -0,0 +1,4894 @@ +<?xml version="1.0" encoding="utf-8"?> +<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:tns="urn:schemas-cybersource-com:transaction-data-1.156" targetNamespace="urn:schemas-cybersource-com:transaction-data-1.156" elementFormDefault="qualified" attributeFormDefault="unqualified"> + <xsd:simpleType name="amount"> + <xsd:restriction base="xsd:string"/> + </xsd:simpleType> + <xsd:simpleType name="boolean"> + <xsd:restriction base="xsd:string"/> + </xsd:simpleType> + <xsd:simpleType name="dateTime"> + <xsd:restriction base="xsd:string"/> + </xsd:simpleType> + <xsd:complexType name="Item"> + <xsd:sequence> + <xsd:element name="unitPrice" type="tns:amount" minOccurs="0"/> + <xsd:element name="quantity" type="tns:amount" minOccurs="0"/> + <xsd:element name="productCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="productName" type="xsd:string" minOccurs="0"/> + <xsd:element name="productSKU" type="xsd:string" minOccurs="0"/> + <xsd:element name="productRisk" type="xsd:string" minOccurs="0"/> + <xsd:element name="taxAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="cityOverrideAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="cityOverrideRate" type="tns:amount" minOccurs="0"/> + <xsd:element name="countyOverrideAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="countyOverrideRate" type="tns:amount" minOccurs="0"/> + <xsd:element name="districtOverrideAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="districtOverrideRate" type="tns:amount" minOccurs="0"/> + <xsd:element name="stateOverrideAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="stateOverrideRate" type="tns:amount" minOccurs="0"/> + <xsd:element name="countryOverrideAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="countryOverrideRate" type="tns:amount" minOccurs="0"/> + <xsd:element name="orderAcceptanceCity" type="xsd:string" minOccurs="0"/> + <xsd:element name="orderAcceptanceCounty" type="xsd:string" minOccurs="0"/> + <xsd:element name="orderAcceptanceCountry" type="xsd:string" minOccurs="0"/> + <xsd:element name="orderAcceptanceState" type="xsd:string" minOccurs="0"/> + <xsd:element name="orderAcceptancePostalCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="orderOriginCity" type="xsd:string" minOccurs="0"/> + <xsd:element name="orderOriginCounty" type="xsd:string" minOccurs="0"/> + <xsd:element name="orderOriginCountry" type="xsd:string" minOccurs="0"/> + <xsd:element name="orderOriginState" type="xsd:string" minOccurs="0"/> + <xsd:element name="orderOriginPostalCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="shipFromCity" type="xsd:string" minOccurs="0"/> + <xsd:element name="shipFromCounty" type="xsd:string" minOccurs="0"/> + <xsd:element name="shipFromCountry" type="xsd:string" minOccurs="0"/> + <xsd:element name="shipFromState" type="xsd:string" minOccurs="0"/> + <xsd:element name="shipFromPostalCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="export" type="xsd:string" minOccurs="0"/> + <xsd:element name="noExport" type="xsd:string" minOccurs="0"/> + <xsd:element name="nationalTax" type="tns:amount" minOccurs="0"/> + <xsd:element name="vatRate" type="tns:amount" minOccurs="0"/> + <xsd:element name="sellerRegistration" type="xsd:string" minOccurs="0"/> + <xsd:element name="sellerRegistration0" type="xsd:string" minOccurs="0"/> + <xsd:element name="sellerRegistration1" type="xsd:string" minOccurs="0"/> + <xsd:element name="sellerRegistration2" type="xsd:string" minOccurs="0"/> + <xsd:element name="sellerRegistration3" type="xsd:string" minOccurs="0"/> + <xsd:element name="sellerRegistration4" type="xsd:string" minOccurs="0"/> + <xsd:element name="sellerRegistration5" type="xsd:string" minOccurs="0"/> + <xsd:element name="sellerRegistration6" type="xsd:string" minOccurs="0"/> + <xsd:element name="sellerRegistration7" type="xsd:string" minOccurs="0"/> + <xsd:element name="sellerRegistration8" type="xsd:string" minOccurs="0"/> + <xsd:element name="sellerRegistration9" type="xsd:string" minOccurs="0"/> + <xsd:element name="buyerRegistration" type="xsd:string" minOccurs="0"/> + <xsd:element name="middlemanRegistration" type="xsd:string" minOccurs="0"/> + <xsd:element name="pointOfTitleTransfer" type="xsd:string" minOccurs="0"/> + <xsd:element name="giftCategory" type="tns:boolean" minOccurs="0"/> + <xsd:element name="timeCategory" type="xsd:string" minOccurs="0"/> + <xsd:element name="hostHedge" type="xsd:string" minOccurs="0"/> + <xsd:element name="timeHedge" type="xsd:string" minOccurs="0"/> + <xsd:element name="velocityHedge" type="xsd:string" minOccurs="0"/> + <xsd:element name="nonsensicalHedge" type="xsd:string" minOccurs="0"/> + <xsd:element name="phoneHedge" type="xsd:string" minOccurs="0"/> + <xsd:element name="obscenitiesHedge" type="xsd:string" minOccurs="0"/> + <xsd:element name="unitOfMeasure" type="xsd:string" minOccurs="0"/> + <xsd:element name="taxRate" type="tns:amount" minOccurs="0"/> + <xsd:element name="totalAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="discountAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="discountRate" type="tns:amount" minOccurs="0"/> + <xsd:element name="commodityCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="grossNetIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="taxTypeApplied" type="xsd:string" minOccurs="0"/> + <xsd:element name="discountIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="alternateTaxID" type="xsd:string" minOccurs="0"/> + <xsd:element name="alternateTaxAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="alternateTaxTypeApplied" type="xsd:string" minOccurs="0"/> + <xsd:element name="alternateTaxRate" type="tns:amount" minOccurs="0"/> + <xsd:element name="alternateTaxType" type="xsd:string" minOccurs="0"/> + <xsd:element name="localTax" type="tns:amount" minOccurs="0"/> + <xsd:element name="zeroCostToCustomerIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="passengerFirstName" type="xsd:string" minOccurs="0"/> + <xsd:element name="passengerLastName" type="xsd:string" minOccurs="0"/> + <xsd:element name="passengerID" type="xsd:string" minOccurs="0"/> + <xsd:element name="passengerStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="passengerType" type="xsd:string" minOccurs="0"/> + <xsd:element name="passengerEmail" type="xsd:string" minOccurs="0"/> + <xsd:element name="passengerPhone" type="xsd:string" minOccurs="0"/> + <xsd:element name="passengerNationality" type="xsd:string" minOccurs="0"/> + <xsd:element name="invoiceNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="productDescription" type="xsd:string" minOccurs="0"/> + <xsd:element name="taxStatusIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="discountManagementIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="typeOfSupply" type="xsd:string" minOccurs="0"/> + <xsd:element name="sign" type="xsd:string" minOccurs="0"/> + <xsd:element name="unitTaxAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="weightAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="weightID" type="xsd:string" minOccurs="0"/> + <xsd:element name="weightUnitMeasurement" type="xsd:string" minOccurs="0"/> + + <xsd:element name="otherTax_1_type" type="xsd:string" minOccurs="0"/> + <xsd:element name="otherTax_1_amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="otherTax_1_rate" type="tns:amount" minOccurs="0"/> + <xsd:element name="otherTax_1_statusIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="otherTax_2_type" type="xsd:string" minOccurs="0"/> + <xsd:element name="otherTax_2_amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="otherTax_2_rate" type="tns:amount" minOccurs="0"/> + <xsd:element name="otherTax_2_statusIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="otherTax_3_type" type="xsd:string" minOccurs="0"/> + <xsd:element name="otherTax_3_amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="otherTax_3_rate" type="tns:amount" minOccurs="0"/> + <xsd:element name="otherTax_3_statusIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="otherTax_4_type" type="xsd:string" minOccurs="0"/> + <xsd:element name="otherTax_4_amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="otherTax_4_rate" type="tns:amount" minOccurs="0"/> + <xsd:element name="otherTax_4_statusIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="otherTax_5_type" type="xsd:string" minOccurs="0"/> + <xsd:element name="otherTax_5_amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="otherTax_5_rate" type="tns:amount" minOccurs="0"/> + <xsd:element name="otherTax_5_statusIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="otherTax_6_type" type="xsd:string" minOccurs="0"/> + <xsd:element name="otherTax_6_amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="otherTax_6_rate" type="tns:amount" minOccurs="0"/> + <xsd:element name="otherTax_6_statusIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="otherTax_7_type" type="xsd:string" minOccurs="0"/> + <xsd:element name="otherTax_7_amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="otherTax_7_rate" type="tns:amount" minOccurs="0"/> + <xsd:element name="otherTax_7_statusIndicator" type="xsd:string" minOccurs="0"/> + + + <xsd:element name="referenceData_1_number" type="xsd:string" minOccurs="0"/> + <xsd:element name="referenceData_1_code" type="xsd:string" minOccurs="0"/> + + <xsd:element name="referenceData_2_number" type="xsd:string" minOccurs="0"/> + <xsd:element name="referenceData_2_code" type="xsd:string" minOccurs="0"/> + + <xsd:element name="referenceData_3_number" type="xsd:string" minOccurs="0"/> + <xsd:element name="referenceData_3_code" type="xsd:string" minOccurs="0"/> + + <xsd:element name="referenceData_4_number" type="xsd:string" minOccurs="0"/> + <xsd:element name="referenceData_4_code" type="xsd:string" minOccurs="0"/> + + <xsd:element name="referenceData_5_number" type="xsd:string" minOccurs="0"/> + <xsd:element name="referenceData_5_code" type="xsd:string" minOccurs="0"/> + + <xsd:element name="referenceData_6_number" type="xsd:string" minOccurs="0"/> + <xsd:element name="referenceData_6_code" type="xsd:string" minOccurs="0"/> + + <xsd:element name="referenceData_7_number" type="xsd:string" minOccurs="0"/> + <xsd:element name="referenceData_7_code" type="xsd:string" minOccurs="0"/> + + </xsd:sequence> + <xsd:attribute name="id" type="xsd:integer" use="optional"/> + </xsd:complexType> + + <xsd:complexType name="CCAuthService"> + <xsd:sequence> + <xsd:element name="cavv" type="xsd:string" minOccurs="0"/> + <xsd:element name="cavvAlgorithm" type="xsd:string" minOccurs="0"/> + <xsd:element name="networkTokenCryptogram" type="xsd:string" minOccurs="0"/> + <xsd:element name="paSpecificationVersion" type="xsd:string" minOccurs="0"/> + <xsd:element name="directoryServerTransactionID" type="xsd:string" minOccurs="0"/> + <xsd:element name="commerceIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="eciRaw" type="xsd:string" minOccurs="0"/> + <xsd:element name="xid" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="avsLevel" type="xsd:string" minOccurs="0"/> + <xsd:element name="fxQuoteID" type="xsd:string" minOccurs="0"/> + <xsd:element name="returnAuthRecord" type="tns:boolean" minOccurs="0"/> + <xsd:element name="authType" type="xsd:string" minOccurs="0"/> + <xsd:element name="verbalAuthCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="billPayment" type="tns:boolean" minOccurs="0"/> + <xsd:element name="authenticationXID" type="xsd:string" minOccurs="0"/> + <xsd:element name="authorizationXID" type="xsd:string" minOccurs="0"/> + <xsd:element name="industryDatatype" type="xsd:string" minOccurs="0"/> + <xsd:element name="traceNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="checksumKey" type="xsd:string" minOccurs="0"/> + <xsd:element name="aggregatorID" type="xsd:string" minOccurs="0"/> + <xsd:element name="aggregatorName" type="xsd:string" minOccurs="0"/> + <xsd:element name="splitTenderIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="veresEnrolled" type="xsd:string" minOccurs="0"/> + <xsd:element name="paresStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="partialAuthIndicator" type="tns:boolean" minOccurs="0"/> + <xsd:element name="captureDate" type="xsd:string" minOccurs="0"/> + <xsd:element name="firstRecurringPayment" type="xsd:string" minOccurs="0"/> + <xsd:element name="duration" type="xsd:integer" minOccurs="0"/> + <xsd:element name="overridePaymentMethod" type="xsd:string" minOccurs="0"/> + <xsd:element name="mobileRemotePaymentType" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardholderVerificationMethod" type="xsd:string" minOccurs="0"/> + <xsd:element name="dccRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="overridePaymentDetails" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardholderAuthenticationMethod" type="xsd:string" minOccurs="0"/> + <xsd:element name="leastCostRouting" type="tns:boolean" minOccurs="0"/> + <xsd:element name="verificationType" type="xsd:string" minOccurs="0" /> + <xsd:element name="cryptocurrencyPurchase" type="xsd:string" minOccurs="0"/> + <xsd:element name="lowValueExemptionIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="riskAnalysisExemptionIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="trustedMerchantExemptionIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="secureCorporatePaymentIndicator" type="xsd:string" minOccurs="0"/> + + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="OCTService"> + <xsd:sequence> + <xsd:element name="commerceIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="networkOrder" type="xsd:string" minOccurs="0"/> + <xsd:element name="overridePaymentMethod" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + + <xsd:complexType name="VerificationService"> + <xsd:sequence> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0" /> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required" /> + </xsd:complexType> + + <xsd:complexType name="CCSaleService"> + <xsd:sequence> + <xsd:element name="overridePaymentMethod" type="xsd:string" minOccurs="0"/> + <xsd:element name="commerceIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="partialAuthIndicator" type="tns:boolean" minOccurs="0"/> + <xsd:element name="cavv" type="xsd:string" minOccurs="0"/> + <xsd:element name="xid" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="industryDatatype" type="xsd:string" minOccurs="0"/> + <xsd:element name="networkTokenCryptogram" type="xsd:string" minOccurs="0"/> + <xsd:element name="paSpecificationVersion" type="xsd:string" minOccurs="0"/> + <xsd:element name="directoryServerTransactionID" type="xsd:string" minOccurs="0"/> + <xsd:element name="cryptocurrencyPurchase" type="xsd:string" minOccurs="0"/> + <xsd:element name="lowValueExemptionIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="riskAnalysisExemptionIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="trustedMerchantExemptionIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="secureCorporatePaymentIndicator" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + + <xsd:complexType name="CCSaleCreditService"> + <xsd:sequence> + <xsd:element name="overridePaymentMethod" type="xsd:string" minOccurs="0"/> + <xsd:element name="commerceIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="refundReason" type="xsd:string" minOccurs="0"/> + <xsd:element name="saleRequestID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + + <xsd:complexType name="CCSaleReversalService"> + <xsd:sequence> + <xsd:element name="saleRequestID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + + <xsd:complexType name="CCIncrementalAuthService"> + <xsd:sequence> + <xsd:element name="authRequestID" type="xsd:string" minOccurs="0" /> + <xsd:element name="duration" type="xsd:integer" minOccurs="0" /> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required" /> + </xsd:complexType> + <xsd:complexType name="CCCaptureService"> + <xsd:sequence> + <xsd:element name="authType" type="xsd:string" minOccurs="0"/> + <xsd:element name="verbalAuthCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="authRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="partialPaymentID" type="xsd:string" minOccurs="0"/> + <xsd:element name="purchasingLevel" type="xsd:string" minOccurs="0"/> + <xsd:element name="industryDatatype" type="xsd:string" minOccurs="0"/> + <xsd:element name="authRequestToken" type="xsd:string" minOccurs="0"/> + <xsd:element name="merchantReceiptNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="posData" type="xsd:string" minOccurs="0"/> + <xsd:element name="transactionID" type="xsd:string" minOccurs="0"/> + <xsd:element name="checksumKey" type="xsd:string" minOccurs="0"/> + <xsd:element name="gratuityAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="duration" type="xsd:integer" minOccurs="0"/> + <xsd:element name="dpdeBillingMonth" type="xsd:integer" minOccurs="0"/> + <xsd:element name="sequence" type="xsd:string" minOccurs="0"/> + <xsd:element name="totalCount" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationIDAlternate" type="xsd:string" minOccurs="0"/> + <xsd:element name="aggregatorID" type="xsd:string" minOccurs="0"/> + <xsd:element name="aggregatorName" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="CCCreditService"> + <xsd:sequence> + <xsd:element name="captureRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="partialPaymentID" type="xsd:string" minOccurs="0"/> + <xsd:element name="purchasingLevel" type="xsd:string" minOccurs="0"/> + <xsd:element name="industryDatatype" type="xsd:string" minOccurs="0"/> + <xsd:element name="commerceIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="billPayment" type="tns:boolean" minOccurs="0"/> + <xsd:element name="authorizationXID" type="xsd:string" minOccurs="0"/> + <xsd:element name="occurrenceNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="authCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="captureRequestToken" type="xsd:string" minOccurs="0"/> + <xsd:element name="merchantReceiptNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="checksumKey" type="xsd:string" minOccurs="0"/> + <xsd:element name="aggregatorID" type="xsd:string" minOccurs="0"/> + <xsd:element name="aggregatorName" type="xsd:string" minOccurs="0"/> + <xsd:element name="duration" type="xsd:integer" minOccurs="0"/> + <xsd:element name="dpdeBillingMonth" type="xsd:integer" minOccurs="0"/> + <xsd:element name="reconciliationIDAlternate" type="xsd:string" minOccurs="0"/> + <xsd:element name="refundReason" type="xsd:string" minOccurs="0"/> + <xsd:element name="overridePaymentMethod" type="xsd:string" minOccurs="0"/> + <xsd:element name="overridePaymentDetails" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="CCAuthReversalService"> + <xsd:sequence> + <xsd:element name="authRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="authRequestToken" type="xsd:string" minOccurs="0"/> + <xsd:element name="reversalReason" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="CCAutoAuthReversalService"> + <xsd:sequence> + <xsd:element name="authPaymentServiceData" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="authAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="commerceIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="authRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="billAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="authCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="authType" type="xsd:string" minOccurs="0"/> + <xsd:element name="billPayment" type="tns:boolean" minOccurs="0"/> + <xsd:element name="dateAdded" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="CCDCCService"> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="ServiceFeeCalculateService"> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="ECDebitService"> + <xsd:sequence> + <xsd:element name="paymentMode" type="xsd:integer" minOccurs="0"/> + <xsd:element name="referenceNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="settlementMethod" type="xsd:string" minOccurs="0"/> + <xsd:element name="transactionToken" type="xsd:string" minOccurs="0"/> + <xsd:element name="verificationLevel" type="xsd:integer" minOccurs="0"/> + <xsd:element name="partialPaymentID" type="xsd:string" minOccurs="0"/> + <xsd:element name="commerceIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="debitRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="effectiveDate" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="ECCreditService"> + <xsd:sequence> + <xsd:element name="referenceNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="settlementMethod" type="xsd:string" minOccurs="0"/> + <xsd:element name="transactionToken" type="xsd:string" minOccurs="0"/> + <xsd:element name="debitRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="partialPaymentID" type="xsd:string" minOccurs="0"/> + <xsd:element name="commerceIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="debitRequestToken" type="xsd:string" minOccurs="0"/> + <xsd:element name="effectiveDate" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="ECAuthenticateService"> + <xsd:sequence> + <xsd:element name="referenceNumber" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="PayerAuthEnrollService"> + <xsd:sequence> + <xsd:element name="httpAccept" type="xsd:string" minOccurs="0"/> + <xsd:element name="httpUserAgent" type="xsd:string" minOccurs="0"/> + <xsd:element name="merchantName" type="xsd:string" minOccurs="0"/> + <xsd:element name="merchantURL" type="xsd:string" minOccurs="0"/> + <xsd:element name="purchaseDescription" type="xsd:string" minOccurs="0"/> + <xsd:element name="purchaseTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="countryCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="acquirerBin" type="xsd:string" minOccurs="0"/> + <xsd:element name="loginID" type="xsd:string" minOccurs="0"/> + <xsd:element name="password" type="xsd:string" minOccurs="0"/> + <xsd:element name="merchantID" type="xsd:string" minOccurs="0"/> + <xsd:element name="overridePaymentMethod" type="xsd:string" minOccurs="0"/> + <xsd:element name="mobilePhone" type="xsd:string" minOccurs="0"/> + <xsd:element name="MCC" type="xsd:string" minOccurs="0"/> + <xsd:element name="productCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="referenceID" type="xsd:string" minOccurs="0"/> + <xsd:element name="marketingOptIn" type="tns:boolean" minOccurs="0"/> + <xsd:element name="marketingSource" type="xsd:string" minOccurs="0"/> + <xsd:element name="defaultCard" type="tns:boolean" minOccurs="0"/> + <xsd:element name="shipAddressUsageDate" type="xsd:string" minOccurs="0"/> + <xsd:element name="transactionCountDay" type="xsd:string" minOccurs="0"/> + <xsd:element name="transactionCountYear" type="xsd:string" minOccurs="0"/> + <xsd:element name="addCardAttempts" type="xsd:string" minOccurs="0"/> + <xsd:element name="accountPurchases" type="xsd:string" minOccurs="0"/> + <xsd:element name="fraudActivity" type="tns:boolean" minOccurs="0"/> + <xsd:element name="paymentAccountDate" type="xsd:string" minOccurs="0"/> + <xsd:element name="alternateAuthenticationMethod" type="xsd:string" minOccurs="0"/> + <xsd:element name="alternateAuthenticationDate" type="xsd:string" minOccurs="0"/> + <xsd:element name="alternateAuthenticationData" type="xsd:string" minOccurs="0"/> + <xsd:element name="challengeRequired" type="tns:boolean" minOccurs="0"/> + <xsd:element name="challengeCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="preorder" type="xsd:string" minOccurs="0"/> + <xsd:element name="reorder" type="xsd:string" minOccurs="0"/> + <xsd:element name="preorderDate" type="xsd:string" minOccurs="0"/> + <xsd:element name="giftCardAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="giftCardCurrency" type="xsd:string" minOccurs="0"/> + <xsd:element name="giftCardCount" type="xsd:string" minOccurs="0"/> + <xsd:element name="messageCategory" type="xsd:string" minOccurs="0"/> + <xsd:element name="npaCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="recurringOriginalPurchaseDate" type="xsd:string" minOccurs="0"/> + <xsd:element name="transactionMode" type="xsd:string" minOccurs="0"/> + <xsd:element name="recurringEndDate" type="xsd:string" minOccurs="0"/> + <xsd:element name="recurringFrequency" type="xsd:string" minOccurs="0"/> + <xsd:element name="merchantNewCustomer" type="xsd:string" minOccurs="0"/> + <xsd:element name="customerCCAlias" type="xsd:string" minOccurs="0"/> + <xsd:element name="installmentTotalCount" type="xsd:string" minOccurs="0"/> + <xsd:element name="authenticationTransactionID" type="xsd:string" minOccurs="0"/> + <xsd:element name="httpUserAccept" type="xsd:string" minOccurs="0"/> + <xsd:element name="mobilePhoneDomestic" type="xsd:string" minOccurs="0"/> + <xsd:element name="pareqChannel" type="xsd:string" minOccurs="0"/> + <xsd:element name="shoppingChannel" type="xsd:string" minOccurs="0"/> + <xsd:element name="authenticationChannel" type="xsd:string" minOccurs="0"/> + <xsd:element name="merchantTTPCredential" type="xsd:string" minOccurs="0"/> + <xsd:element name="requestorID" type="xsd:string" minOccurs="0"/> + <xsd:element name="requestorName" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="PayerAuthValidateService"> + <xsd:sequence> + <xsd:element name="signedPARes" type="xsd:string" minOccurs="0"/> + + <xsd:element name="authenticationTransactionID" type="xsd:string" minOccurs="0"/> + + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="TaxService"> + <xsd:sequence> + <xsd:element name="nexus" type="xsd:string" minOccurs="0"/> + <xsd:element name="noNexus" type="xsd:string" minOccurs="0"/> + <xsd:element name="orderAcceptanceCity" type="xsd:string" minOccurs="0"/> + <xsd:element name="orderAcceptanceCounty" type="xsd:string" minOccurs="0"/> + <xsd:element name="orderAcceptanceCountry" type="xsd:string" minOccurs="0"/> + <xsd:element name="orderAcceptanceState" type="xsd:string" minOccurs="0"/> + <xsd:element name="orderAcceptancePostalCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="orderOriginCity" type="xsd:string" minOccurs="0"/> + <xsd:element name="orderOriginCounty" type="xsd:string" minOccurs="0"/> + <xsd:element name="orderOriginCountry" type="xsd:string" minOccurs="0"/> + <xsd:element name="orderOriginState" type="xsd:string" minOccurs="0"/> + <xsd:element name="orderOriginPostalCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="sellerRegistration" type="xsd:string" minOccurs="0"/> + <xsd:element name="sellerRegistration0" type="xsd:string" minOccurs="0"/> + <xsd:element name="sellerRegistration1" type="xsd:string" minOccurs="0"/> + <xsd:element name="sellerRegistration2" type="xsd:string" minOccurs="0"/> + <xsd:element name="sellerRegistration3" type="xsd:string" minOccurs="0"/> + <xsd:element name="sellerRegistration4" type="xsd:string" minOccurs="0"/> + <xsd:element name="sellerRegistration5" type="xsd:string" minOccurs="0"/> + <xsd:element name="sellerRegistration6" type="xsd:string" minOccurs="0"/> + <xsd:element name="sellerRegistration7" type="xsd:string" minOccurs="0"/> + <xsd:element name="sellerRegistration8" type="xsd:string" minOccurs="0"/> + <xsd:element name="sellerRegistration9" type="xsd:string" minOccurs="0"/> + <xsd:element name="buyerRegistration" type="xsd:string" minOccurs="0"/> + <xsd:element name="middlemanRegistration" type="xsd:string" minOccurs="0"/> + <xsd:element name="pointOfTitleTransfer" type="xsd:string" minOccurs="0"/> + <xsd:element name="commitIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="refundIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="dateOverrideReason" type="xsd:string" minOccurs="0"/> + <xsd:element name="reportingDate" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <!-- DME --> + <xsd:complexType name="DMEService"> + <xsd:sequence> + <xsd:element name="eventType" type="xsd:string" minOccurs="0" /> + <xsd:element name="eventPolicy" type="xsd:string" minOccurs="0" /> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required" /> + </xsd:complexType> + <xsd:complexType name="AFSService"> + <xsd:sequence> + <xsd:element name="avsCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="cvCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="disableAVSScoring" type="tns:boolean" minOccurs="0"/> + <xsd:element name="customRiskModel" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="DAVService"> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="ExportService"> + <xsd:sequence> + <xsd:element name="addressOperator" type="xsd:string" minOccurs="0"/> + <xsd:element name="addressWeight" type="xsd:string" minOccurs="0"/> + <xsd:element name="companyWeight" type="xsd:string" minOccurs="0"/> + <xsd:element name="nameWeight" type="xsd:string" minOccurs="0"/> + <xsd:element name="sanctionsLists" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="FXRatesService"> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="BankTransferService"> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="BankTransferRefundService"> + <xsd:sequence> + <xsd:element name="bankTransferRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="bankTransferRealTimeRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="bankTransferRealTimeReconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="bankTransferRequestToken" type="xsd:string" minOccurs="0"/> + <xsd:element name="bankTransferRealTimeRequestToken" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="BankTransferRealTimeService"> + <xsd:sequence> + <xsd:element name="bankTransferRealTimeType" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="DirectDebitMandateService"> + <xsd:sequence> + <xsd:element name="mandateDescriptor" type="xsd:string" minOccurs="0"/> + <xsd:element name="firstDebitDate" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="DirectDebitService"> + <xsd:sequence> + <xsd:element name="dateCollect" type="xsd:string" minOccurs="0"/> + <xsd:element name="directDebitText" type="xsd:string" minOccurs="0"/> + <xsd:element name="authorizationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="transactionType" type="xsd:string" minOccurs="0"/> + <xsd:element name="directDebitType" type="xsd:string" minOccurs="0"/> + <xsd:element name="validateRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="recurringType" type="xsd:string" minOccurs="0"/> + <xsd:element name="mandateID" type="xsd:string" minOccurs="0"/> + <xsd:element name="validateRequestToken" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="mandateAuthenticationDate" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="DirectDebitRefundService"> + <xsd:sequence> + <xsd:element name="directDebitRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="directDebitRequestToken" type="xsd:string" minOccurs="0"/> + <xsd:element name="directDebitType" type="xsd:string" minOccurs="0"/> + <xsd:element name="recurringType" type="xsd:string" minOccurs="0"/> + <xsd:element name="mandateID" type="xsd:string" minOccurs="0"/> + <xsd:element name="mandateAuthenticationDate" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="DirectDebitValidateService"> + <xsd:sequence> + <xsd:element name="directDebitValidateText" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="DeviceFingerprintData"> + <xsd:sequence> + <xsd:element name="data" type="xsd:string" minOccurs="0"/> + <xsd:element name="provider" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="id" type="xsd:integer" use="required"/> + </xsd:complexType> + <xsd:complexType name="PaySubscriptionCreateService"> + <xsd:sequence> + <xsd:element name="paymentRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="paymentRequestToken" type="xsd:string" minOccurs="0"/> + <xsd:element name="disableAutoAuth" type="tns:boolean" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="PaySubscriptionUpdateService"> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="PaySubscriptionEventUpdateService"> + <xsd:sequence> + <xsd:element name="action" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="PaySubscriptionRetrieveService"> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="PaySubscriptionDeleteService"> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="PayPalPaymentService"> + <xsd:sequence> + <xsd:element name="cancelURL" type="xsd:string" minOccurs="0"/> + <xsd:element name="successURL" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="PayPalCreditService"> + <xsd:sequence> + <xsd:element name="payPalPaymentRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="payPalPaymentRequestToken" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <!--PayPalEcSet--> + <xsd:complexType name="PayPalEcSetService"> + <xsd:sequence> + <xsd:element name="paypalReturn" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalCancelReturn" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalMaxamt" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalCustomerEmail" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalDesc" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalReqconfirmshipping" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalNoshipping" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalAddressOverride" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalToken" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalLc" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalPagestyle" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalHdrimg" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalHdrbordercolor" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalHdrbackcolor" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalPayflowcolor" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalEcSetRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalEcSetRequestToken" type="xsd:string" minOccurs="0"/> + <xsd:element name="promoCode0" type="xsd:string" minOccurs="0"/> + <xsd:element name="requestBillingAddress" type="xsd:string" minOccurs="0"/> + <xsd:element name="invoiceNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalBillingType" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalBillingAgreementDesc" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalPaymentType" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalBillingAgreementCustom" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalLogoimg" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <!--PayPalEcGetDetails--> + <xsd:complexType name="PayPalEcGetDetailsService"> + <xsd:sequence> + <xsd:element name="paypalToken" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalEcSetRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalEcSetRequestToken" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <!--PayPalEcDoPayment--> + <xsd:complexType name="PayPalEcDoPaymentService"> + <xsd:sequence> + <xsd:element name="paypalToken" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalPayerId" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalCustomerEmail" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalDesc" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalEcSetRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalEcSetRequestToken" type="xsd:string" minOccurs="0"/> + <xsd:element name="promoCode0" type="xsd:string" minOccurs="0"/> + <xsd:element name="invoiceNumber" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <!--PayPalDoCapture--> + <xsd:complexType name="PayPalDoCaptureService"> + <xsd:sequence> + <xsd:element name="paypalAuthorizationId" type="xsd:string" minOccurs="0"/> + <xsd:element name="completeType" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalEcDoPaymentRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalEcDoPaymentRequestToken" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalAuthorizationRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalAuthorizationRequestToken" type="xsd:string" minOccurs="0"/> + <xsd:element name="invoiceNumber" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <!--PayPalAuthReversal--> + <xsd:complexType name="PayPalAuthReversalService"> + <xsd:sequence> + <xsd:element name="paypalAuthorizationId" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalEcDoPaymentRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalEcDoPaymentRequestToken" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalAuthorizationRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalAuthorizationRequestToken" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalEcOrderSetupRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalEcOrderSetupRequestToken" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <!--PayPalRefund--> + <xsd:complexType name="PayPalRefundService"> + <xsd:sequence> + <xsd:element name="paypalDoCaptureRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalDoCaptureRequestToken" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalCaptureId" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalNote" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <!--PayPalEcOrderSetup--> + <xsd:complexType name="PayPalEcOrderSetupService"> + <xsd:sequence> + <xsd:element name="paypalToken" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalPayerId" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalCustomerEmail" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalDesc" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalEcSetRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalEcSetRequestToken" type="xsd:string" minOccurs="0"/> + <xsd:element name="promoCode0" type="xsd:string" minOccurs="0"/> + <xsd:element name="invoiceNumber" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <!--PayPalAuthorization--> + <xsd:complexType name="PayPalAuthorizationService"> + <xsd:sequence> + <xsd:element name="paypalOrderId" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalEcOrderSetupRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalEcOrderSetupRequestToken" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalDoRefTransactionRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalDoRefTransactionRequestToken" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalCustomerEmail" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <!--PayPalUpdateAgreement--> + <xsd:complexType name="PayPalUpdateAgreementService"> + <xsd:sequence> + <xsd:element name="paypalBillingAgreementId" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalBillingAgreementStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalBillingAgreementDesc" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalBillingAgreementCustom" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <!--PayPalCreateAgreement--> + <xsd:complexType name="PayPalCreateAgreementService"> + <xsd:sequence> + <xsd:element name="paypalToken" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalEcSetRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalEcSetRequestToken" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <!--PayPalDoRefTransaction--> + <xsd:complexType name="PayPalDoRefTransactionService"> + <xsd:sequence> + <xsd:element name="paypalBillingAgreementId" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalPaymentType" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalReqconfirmshipping" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalReturnFmfDetails" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalSoftDescriptor" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalShippingdiscount" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalDesc" type="xsd:string" minOccurs="0"/> + <xsd:element name="invoiceNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalEcNotifyUrl" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="VoidService"> + <xsd:sequence> + <xsd:element name="voidRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="voidRequestToken" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="PinlessDebitService"> + <xsd:sequence> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="commerceIndicator" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="PinlessDebitValidateService"> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="PinlessDebitReversalService"> + <xsd:sequence> + <xsd:element name="pinlessDebitRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="pinlessDebitRequestToken" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + + <!--PinDebitPurchaseService--> + <xsd:complexType name="PinDebitPurchaseService"> + <xsd:sequence> + <xsd:element name="networkOrder" type="xsd:string" minOccurs="0"/> + <xsd:element name="commerceIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="partialAuthIndicator" type="tns:boolean" minOccurs="0"/> + <xsd:element name="overridePaymentMethod" type="xsd:string" minOccurs="0"/> + <xsd:element name="paymentType" type="xsd:string" minOccurs="0"/> + <xsd:element name="ebtCategory" type="xsd:string" minOccurs="0"/> + <xsd:element name="transactionType" type="xsd:string" minOccurs="0"/> + <xsd:element name="ebtVoucherSerialNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="authorizationCode" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <!--End of PinDebitPurchaseService--> + <!--PinDebitCreditService--> + <xsd:complexType name="PinDebitCreditService"> + <xsd:sequence> + <xsd:element name="networkOrder" type="xsd:string" minOccurs="0"/> + <xsd:element name="commerceIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="overridePaymentMethod" type="xsd:string" minOccurs="0"/> + <xsd:element name="paymentType" type="xsd:string" minOccurs="0"/> + <xsd:element name="ebtCategory" type="xsd:string" minOccurs="0"/> + <xsd:element name="transactionType" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <!--End of PinDebitCreditService--> + <!--PinDebitReversalService--> + <xsd:complexType name="PinDebitReversalService"> + <xsd:sequence> + <xsd:element name="pinDebitRequestID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <!--End of PinDebitReversalService--> + + <!--PayPal upgrade services --> + <xsd:complexType name="PayPalButtonCreateService"> + <xsd:sequence> + <xsd:element name="buttonType" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="PayPalPreapprovedPaymentService"> + <xsd:sequence> + <xsd:element name="mpID" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="PayPalPreapprovedUpdateService"> + <xsd:sequence> + <xsd:element name="mpID" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <!-- China Payment --> + <xsd:complexType name="ChinaPaymentService"> + <xsd:sequence> + <xsd:element name="paymentMode" type="xsd:string" minOccurs="0"/> + <xsd:element name="returnURL" type="xsd:string" minOccurs="0"/> + <xsd:element name="pickUpAddress" type="xsd:string" minOccurs="0"/> + <xsd:element name="pickUpPhoneNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="pickUpPostalCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="pickUpName" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <!-- China Refund --> + <xsd:complexType name="ChinaRefundService"> + <xsd:sequence> + <xsd:element name="chinaPaymentRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="chinaPaymentRequestToken" type="xsd:string" minOccurs="0"/> + <xsd:element name="refundReason" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <!--Boleto Payment --> + <xsd:complexType name="BoletoPaymentService"> + <xsd:sequence> + <xsd:element name="instruction" type="xsd:string" minOccurs="0"/> + <xsd:element name="expirationDate" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="PersonalID"> + <xsd:sequence> + <xsd:element name="number" type="xsd:string" minOccurs="0"/> + <xsd:element name="type" type="xsd:string" minOccurs="0"/> + <xsd:element name="name" type="xsd:string" minOccurs="0"/> + <xsd:element name="country" type="xsd:string" minOccurs="0"/> + <xsd:element name="address" type="xsd:string" minOccurs="0"/> + <xsd:element name="issuedBy" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="Routing"> + <xsd:sequence> + <xsd:element name="networkType" type="xsd:string" minOccurs="0"/> + <xsd:element name="networkLabel" type="xsd:string" minOccurs="0"/> + <xsd:element name="signatureCVMRequired" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="Address"> + <xsd:sequence> + <xsd:element name="street1" type="xsd:string" minOccurs="0"/> + <xsd:element name="street2" type="xsd:string" minOccurs="0"/> + <xsd:element name="city" type="xsd:string" minOccurs="0"/> + <xsd:element name="state" type="xsd:string" minOccurs="0"/> + <xsd:element name="postalCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="country" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="APInitiateService"> + <xsd:sequence> + <xsd:element name="returnURL" type="xsd:string" minOccurs="0"/> + <xsd:element name="productName" type="xsd:string" minOccurs="0"/> + <xsd:element name="productDescription" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="bankID" type="xsd:string" minOccurs="0"/> + <xsd:element name="countryCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="escrowAgreement" type="xsd:string" minOccurs="0"/> + <xsd:element name="languageInterface" type="xsd:string" minOccurs="0"/> + <xsd:element name="intent" type="xsd:string" minOccurs="0"/> + <xsd:element name="successURL" type="xsd:string" minOccurs="0"/> + <xsd:element name="cancelURL" type="xsd:string" minOccurs="0"/> + <xsd:element name="failureURL" type="xsd:string" minOccurs="0"/> + <xsd:element name="overridePaymentMethod" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="APCheckStatusService"> + <xsd:sequence> + <xsd:element name="apInitiateRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="checkStatusRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="sessionsRequestID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="RiskUpdateService"> + <xsd:sequence> + <xsd:element name="actionCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="recordID" type="xsd:string" minOccurs="0"/> + <xsd:element name="recordName" type="xsd:string" minOccurs="0"/> + <xsd:element name="negativeAddress" type="tns:Address" minOccurs="0"/> + <xsd:element name="markingReason" type="xsd:string" minOccurs="0"/> + <xsd:element name="markingNotes" type="xsd:string" minOccurs="0"/> + <xsd:element name="markingRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="deviceFingerprintSmartID" type="xsd:string" minOccurs="0"/> + <xsd:element name="deviceFingerprintTrueIPAddress" type="xsd:string" minOccurs="0"/> + <xsd:element name="deviceFingerprintProxyIPAddress" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="FraudUpdateService"> + <xsd:sequence> + <xsd:element name="actionCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="markedData" type="xsd:string" minOccurs="0"/> + <xsd:element name="markingReason" type="xsd:string" minOccurs="0"/> + <xsd:element name="markingNotes" type="xsd:string" minOccurs="0"/> + <xsd:element name="markingRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="markingTransactionDate" type="xsd:string" minOccurs="0"/> + <xsd:element name="markingAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="markingCurrency" type="xsd:string" minOccurs="0"/> + <xsd:element name="markingIndicator" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="CaseManagementActionService"> + <xsd:sequence> + <xsd:element name="actionCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="requestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="comments" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="EncryptPaymentDataService"> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="InvoiceHeader"> + <xsd:sequence> + <xsd:element name="merchantDescriptor" type="xsd:string" minOccurs="0"/> + <xsd:element name="merchantDescriptorContact" type="xsd:string" minOccurs="0"/> + <xsd:element name="merchantDescriptorAlternate" type="xsd:string" minOccurs="0"/> + <xsd:element name="merchantDescriptorStreet" type="xsd:string" minOccurs="0"/> + <xsd:element name="merchantDescriptorCity" type="xsd:string" minOccurs="0"/> + <xsd:element name="merchantDescriptorState" type="xsd:string" minOccurs="0"/> + <xsd:element name="merchantDescriptorPostalCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="merchantDescriptorCountry" type="xsd:string" minOccurs="0"/> + <xsd:element name="isGift" type="tns:boolean" minOccurs="0"/> + <xsd:element name="returnsAccepted" type="tns:boolean" minOccurs="0"/> + <xsd:element name="tenderType" type="xsd:string" minOccurs="0"/> + <xsd:element name="merchantVATRegistrationNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="purchaserOrderDate" type="xsd:string" minOccurs="0"/> + <!-- xsd:date --> + <xsd:element name="purchaserVATRegistrationNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="vatInvoiceReferenceNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="summaryCommodityCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="supplierOrderReference" type="xsd:string" minOccurs="0"/> + <xsd:element name="userPO" type="xsd:string" minOccurs="0"/> + <xsd:element name="costCenter" type="xsd:string" minOccurs="0"/> + <xsd:element name="purchaserCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="taxable" type="tns:boolean" minOccurs="0"/> + <xsd:element name="amexDataTAA1" type="xsd:string" minOccurs="0"/> + <xsd:element name="amexDataTAA2" type="xsd:string" minOccurs="0"/> + <xsd:element name="amexDataTAA3" type="xsd:string" minOccurs="0"/> + <xsd:element name="amexDataTAA4" type="xsd:string" minOccurs="0"/> + <xsd:element name="invoiceDate" type="xsd:string" minOccurs="0"/> + <xsd:element name="totalTaxTypeCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardAcceptorRefNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="authorizedContactName" type="xsd:string" minOccurs="0"/> + <xsd:element name="businessApplicationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="salesOrganizationID" type="xsd:integer" minOccurs="0"/> + <xsd:element name="submerchantID" type="xsd:string" minOccurs="0"/> + <xsd:element name="submerchantName" type="xsd:string" minOccurs="0"/> + <xsd:element name="submerchantStreet" type="xsd:string" minOccurs="0"/> + <xsd:element name="submerchantCity" type="xsd:string" minOccurs="0"/> + <xsd:element name="submerchantPostalCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="submerchantState" type="xsd:string" minOccurs="0"/> + <xsd:element name="submerchantCountry" type="xsd:string" minOccurs="0"/> + <xsd:element name="submerchantEmail" type="xsd:string" minOccurs="0"/> + <xsd:element name="submerchantTelephoneNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="submerchantRegion" type="xsd:string" minOccurs="0"/> + <xsd:element name="submerchantMerchantID" type="xsd:string" minOccurs="0"/> + <xsd:element name="merchantDescriptorCounty" type="xsd:string" minOccurs="0"/> + <xsd:element name="referenceDataCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="referenceDataNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="merchantDescriptorStoreID" type="xsd:string" minOccurs="0"/> + <xsd:element name="clerkID" type="xsd:string" minOccurs="0"/> + <xsd:element name="customData_1" type="xsd:string" minOccurs="0" /> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="BusinessRules"> + <xsd:sequence> + <xsd:element name="ignoreAVSResult" type="tns:boolean" minOccurs="0"/> + <xsd:element name="ignoreCVResult" type="tns:boolean" minOccurs="0"/> + <xsd:element name="ignoreDAVResult" type="tns:boolean" minOccurs="0"/> + <xsd:element name="ignoreExportResult" type="tns:boolean" minOccurs="0"/> + <xsd:element name="ignoreValidateResult" type="tns:boolean" minOccurs="0"/> + <xsd:element name="declineAVSFlags" type="xsd:string" minOccurs="0"/> + <xsd:element name="scoreThreshold" type="xsd:integer" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="BillTo"> + <xsd:sequence> + <xsd:element name="title" type="xsd:string" minOccurs="0"/> + <xsd:element name="firstName" type="xsd:string" minOccurs="0"/> + <xsd:element name="middleName" type="xsd:string" minOccurs="0"/> + <xsd:element name="lastName" type="xsd:string" minOccurs="0"/> + <xsd:element name="suffix" type="xsd:string" minOccurs="0"/> + <xsd:element name="buildingNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="street1" type="xsd:string" minOccurs="0"/> + <xsd:element name="street2" type="xsd:string" minOccurs="0"/> + <xsd:element name="street3" type="xsd:string" minOccurs="0"/> + <xsd:element name="street4" type="xsd:string" minOccurs="0"/> + <xsd:element name="street5" type="xsd:string" minOccurs="0"/> + <xsd:element name="city" type="xsd:string" minOccurs="0"/> + <xsd:element name="district" type="xsd:string" minOccurs="0"/> + <xsd:element name="county" type="xsd:string" minOccurs="0"/> + <xsd:element name="state" type="xsd:string" minOccurs="0"/> + <xsd:element name="postalCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="country" type="xsd:string" minOccurs="0"/> + <xsd:element name="company" type="xsd:string" minOccurs="0"/> + <xsd:element name="companyTaxID" type="xsd:string" minOccurs="0"/> + <xsd:element name="phoneNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="email" type="xsd:string" minOccurs="0"/> + <xsd:element name="ipAddress" type="xsd:string" minOccurs="0"/> + <xsd:element name="customerUserName" type="xsd:string" minOccurs="0"/> + <xsd:element name="customerPassword" type="xsd:string" minOccurs="0"/> + <xsd:element name="ipNetworkAddress" type="xsd:string" minOccurs="0"/> + <xsd:element name="hostname" type="xsd:string" minOccurs="0"/> + <xsd:element name="domainName" type="xsd:string" minOccurs="0"/> + <xsd:element name="dateOfBirth" type="xsd:string" minOccurs="0"/> + <!-- xsd:date --> + <xsd:element name="driversLicenseNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="driversLicenseState" type="xsd:string" minOccurs="0"/> + <xsd:element name="ssn" type="xsd:string" minOccurs="0"/> + <xsd:element name="customerID" type="xsd:string" minOccurs="0"/> + <xsd:element name="httpBrowserType" type="xsd:string" minOccurs="0"/> + <xsd:element name="httpBrowserEmail" type="xsd:string" minOccurs="0"/> + <xsd:element name="httpBrowserCookiesAccepted" type="tns:boolean" minOccurs="0"/> + <xsd:element name="nif" type="xsd:string" minOccurs="0"/> + <xsd:element name="personalID" type="xsd:string" minOccurs="0"/> + <xsd:element name="language" type="xsd:string" minOccurs="0"/> + <xsd:element name="name" type="xsd:string" minOccurs="0"/> + <xsd:element name="gender" type="xsd:string" minOccurs="0"/> + <xsd:element name="merchantTaxID" type="xsd:string" minOccurs="0"/> + + <xsd:element name="passportNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="passportCountry" type="xsd:string" minOccurs="0"/> + <xsd:element name="customerAccountCreateDate" type="xsd:string" minOccurs="0"/> + <xsd:element name="customerAccountChangeDate" type="xsd:string" minOccurs="0"/> + <xsd:element name="customerAccountPasswordChangeDate" type="xsd:string" minOccurs="0"/> + <xsd:element name="pointOfReference" type="tns:boolean" minOccurs="0"/> + <xsd:element name="defaultIndicator" type="tns:boolean" minOccurs="0"/> + + <xsd:element name="companyStreet1" type="xsd:string" minOccurs="0"/> + <xsd:element name="companyStreet2" type="xsd:string" minOccurs="0"/> + <xsd:element name="companyCity" type="xsd:string" minOccurs="0"/> + <xsd:element name="companyCountry" type="xsd:string" minOccurs="0"/> + <xsd:element name="companyState" type="xsd:string" minOccurs="0"/> + <xsd:element name="companyPostalCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="prefix" type="xsd:string" minOccurs="0"/> + <xsd:element name="companyPhoneNumber" type="xsd:string" minOccurs="0"/> + + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="ShipTo"> + <xsd:sequence> + <xsd:element name="title" type="xsd:string" minOccurs="0"/> + <xsd:element name="firstName" type="xsd:string" minOccurs="0"/> + <xsd:element name="middleName" type="xsd:string" minOccurs="0"/> + <xsd:element name="lastName" type="xsd:string" minOccurs="0"/> + <xsd:element name="suffix" type="xsd:string" minOccurs="0"/> + <xsd:element name="street1" type="xsd:string" minOccurs="0"/> + <xsd:element name="street2" type="xsd:string" minOccurs="0"/> + <xsd:element name="street3" type="xsd:string" minOccurs="0"/> + <xsd:element name="street4" type="xsd:string" minOccurs="0"/> + <xsd:element name="street5" type="xsd:string" minOccurs="0"/> + <xsd:element name="city" type="xsd:string" minOccurs="0"/> + <xsd:element name="county" type="xsd:string" minOccurs="0"/> + <xsd:element name="state" type="xsd:string" minOccurs="0"/> + <xsd:element name="buildingNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="district" type="xsd:string" minOccurs="0"/> + <xsd:element name="postalCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="country" type="xsd:string" minOccurs="0"/> + <xsd:element name="company" type="xsd:string" minOccurs="0"/> + <xsd:element name="phoneNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="email" type="xsd:string" minOccurs="0"/> + <xsd:element name="shippingMethod" type="xsd:string" minOccurs="0"/> + <xsd:element name="name" type="xsd:string" minOccurs="0"/> + <xsd:element name="id" type="xsd:string" minOccurs="0"/> + <xsd:element name="addressVerificationStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="notApplicable" type="tns:boolean" minOccurs="0"/> + <xsd:element name="immutable" type="tns:boolean" minOccurs="0"/> + <xsd:element name="destinationCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="pointOfReference" type="tns:boolean" minOccurs="0"/> + <xsd:element name="default" type="tns:boolean" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="ShipFrom"> + <xsd:sequence> + <xsd:element name="title" type="xsd:string" minOccurs="0"/> + <xsd:element name="firstName" type="xsd:string" minOccurs="0"/> + <xsd:element name="middleName" type="xsd:string" minOccurs="0"/> + <xsd:element name="lastName" type="xsd:string" minOccurs="0"/> + <xsd:element name="suffix" type="xsd:string" minOccurs="0"/> + <xsd:element name="street1" type="xsd:string" minOccurs="0"/> + <xsd:element name="street2" type="xsd:string" minOccurs="0"/> + <xsd:element name="street3" type="xsd:string" minOccurs="0"/> + <xsd:element name="street4" type="xsd:string" minOccurs="0"/> + <xsd:element name="city" type="xsd:string" minOccurs="0"/> + <xsd:element name="county" type="xsd:string" minOccurs="0"/> + <xsd:element name="state" type="xsd:string" minOccurs="0"/> + <xsd:element name="postalCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="country" type="xsd:string" minOccurs="0"/> + <xsd:element name="company" type="xsd:string" minOccurs="0"/> + <xsd:element name="phoneNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="email" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="Card"> + <xsd:sequence> + <xsd:element name="fullName" type="xsd:string" minOccurs="0"/> + <xsd:element name="accountNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="expirationMonth" type="xsd:integer" minOccurs="0"/> + <xsd:element name="expirationYear" type="xsd:integer" minOccurs="0"/> + <xsd:element name="cvIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="cvNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardType" type="xsd:string" minOccurs="0"/> + <xsd:element name="issueNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="startMonth" type="xsd:integer" minOccurs="0"/> + <xsd:element name="startYear" type="xsd:integer" minOccurs="0"/> + <xsd:element name="pin" type="xsd:string" minOccurs="0"/> + <xsd:element name="accountEncoderID" type="xsd:string" minOccurs="0"/> + <xsd:element name="bin" type="xsd:string" minOccurs="0"/> + <xsd:element name="encryptedData" type="xsd:string" minOccurs="0"/> + <xsd:element name="suffix" type="xsd:string" minOccurs="0"/> + <xsd:element name="virtual" type="tns:boolean" minOccurs="0"/> + <xsd:element name="prefix" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardTypeName" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardSubType" type="xsd:string" minOccurs="0"/> + <xsd:element name="level2Eligible" type="xsd:string" minOccurs="0"/> + <xsd:element name="level3Eligible" type="xsd:string" minOccurs="0"/> + <xsd:element name="productCategory" type="xsd:string" minOccurs="0"/> + <xsd:element name="crossBorderIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="billingCurrency" type="xsd:string" minOccurs="0"/> + <xsd:element name="billingCurrencyNumericCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="billingCurrencyMinorDigits" type="xsd:string" minOccurs="0"/> + <xsd:element name="octFastFundsIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="octBlockIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="onlineGamblingBlockIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="productName" type="xsd:string" minOccurs="0"/> + <xsd:element name="usage" type="xsd:string" minOccurs="0"/> + <xsd:element name="prepaidReloadable" type="xsd:string" minOccurs="0"/> + <xsd:element name="prepaidType" type="xsd:string" minOccurs="0"/> + <xsd:element name="brands" type="tns:Brands" minOccurs="0" maxOccurs="5"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="Check"> + <xsd:sequence> + <xsd:element name="fullName" type="xsd:string" minOccurs="0"/> + <xsd:element name="accountNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="accountType" type="xsd:string" minOccurs="0"/> + <xsd:element name="bankTransitNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="checkNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="secCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="accountEncoderID" type="xsd:string" minOccurs="0"/> + <xsd:element name="authenticateID" type="xsd:string" minOccurs="0"/> + <xsd:element name="paymentInfo" type="xsd:string" minOccurs="0"/> + <xsd:element name="imageReferenceNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="terminalCity" type="xsd:string" minOccurs="0"/> + <xsd:element name="terminalState" type="xsd:string" minOccurs="0"/> + <xsd:element name="customerPresent" type="xsd:string" minOccurs="0"/> + <xsd:element name="checkTransactionCode" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="BML"> + <xsd:sequence> + <xsd:element name="customerBillingAddressChange" type="tns:boolean" minOccurs="0"/> + <xsd:element name="customerEmailChange" type="tns:boolean" minOccurs="0"/> + <xsd:element name="customerHasCheckingAccount" type="tns:boolean" minOccurs="0"/> + <xsd:element name="customerHasSavingsAccount" type="tns:boolean" minOccurs="0"/> + <xsd:element name="customerPasswordChange" type="tns:boolean" minOccurs="0"/> + <xsd:element name="customerPhoneChange" type="tns:boolean" minOccurs="0"/> + <xsd:element name="customerRegistrationDate" type="xsd:string" minOccurs="0"/> + <!-- xsd:date --> + <xsd:element name="customerTypeFlag" type="xsd:string" minOccurs="0"/> + <xsd:element name="grossHouseholdIncome" type="tns:amount" minOccurs="0"/> + <xsd:element name="householdIncomeCurrency" type="xsd:string" minOccurs="0"/> + <xsd:element name="itemCategory" type="xsd:string" minOccurs="0"/> + <xsd:element name="merchantPromotionCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="preapprovalNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="productDeliveryTypeIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="residenceStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="tcVersion" type="xsd:string" minOccurs="0"/> + <xsd:element name="yearsAtCurrentResidence" type="xsd:integer" minOccurs="0"/> + <xsd:element name="yearsWithCurrentEmployer" type="xsd:integer" minOccurs="0"/> + <xsd:element name="employerStreet1" type="xsd:string" minOccurs="0"/> + <xsd:element name="employerStreet2" type="xsd:string" minOccurs="0"/> + <xsd:element name="employerCity" type="xsd:string" minOccurs="0"/> + <xsd:element name="employerCompanyName" type="xsd:string" minOccurs="0"/> + <xsd:element name="employerCountry" type="xsd:string" minOccurs="0"/> + <xsd:element name="employerPhoneNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="employerPhoneType" type="xsd:string" minOccurs="0"/> + <xsd:element name="employerState" type="xsd:string" minOccurs="0"/> + <xsd:element name="employerPostalCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="shipToPhoneType" type="xsd:string" minOccurs="0"/> + <xsd:element name="billToPhoneType" type="xsd:string" minOccurs="0"/> + <xsd:element name="methodOfPayment" type="xsd:string" minOccurs="0"/> + <xsd:element name="productType" type="xsd:string" minOccurs="0"/> + <xsd:element name="customerAuthenticatedByMerchant" type="xsd:string" minOccurs="0"/> + <xsd:element name="backOfficeIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="shipToEqualsBillToNameIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="shipToEqualsBillToAddressIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="alternateIPAddress" type="xsd:string" minOccurs="0"/> + <xsd:element name="businessLegalName" type="xsd:string" minOccurs="0"/> + <xsd:element name="dbaName" type="xsd:string" minOccurs="0"/> + <xsd:element name="businessAddress1" type="xsd:string" minOccurs="0"/> + <xsd:element name="businessAddress2" type="xsd:string" minOccurs="0"/> + <xsd:element name="businessCity" type="xsd:string" minOccurs="0"/> + <xsd:element name="businessState" type="xsd:string" minOccurs="0"/> + <xsd:element name="businessPostalCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="businessCountry" type="xsd:string" minOccurs="0"/> + <xsd:element name="businessMainPhone" type="xsd:string" minOccurs="0"/> + <xsd:element name="userID" type="xsd:string" minOccurs="0"/> + <xsd:element name="pin" type="xsd:string" minOccurs="0"/> + <xsd:element name="adminLastName" type="xsd:string" minOccurs="0"/> + <xsd:element name="adminFirstName" type="xsd:string" minOccurs="0"/> + <xsd:element name="adminPhone" type="xsd:string" minOccurs="0"/> + <xsd:element name="adminFax" type="xsd:string" minOccurs="0"/> + <xsd:element name="adminEmailAddress" type="xsd:string" minOccurs="0"/> + <xsd:element name="adminTitle" type="xsd:string" minOccurs="0"/> + <xsd:element name="supervisorLastName" type="xsd:string" minOccurs="0"/> + <xsd:element name="supervisorFirstName" type="xsd:string" minOccurs="0"/> + <xsd:element name="supervisorEmailAddress" type="xsd:string" minOccurs="0"/> + <xsd:element name="businessDAndBNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="businessTaxID" type="xsd:string" minOccurs="0"/> + <xsd:element name="businessNAICSCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="businessType" type="xsd:string" minOccurs="0"/> + <xsd:element name="businessYearsInBusiness" type="xsd:string" minOccurs="0"/> + <xsd:element name="businessNumberOfEmployees" type="xsd:string" minOccurs="0"/> + <xsd:element name="businessPONumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="businessLoanType" type="xsd:string" minOccurs="0"/> + <xsd:element name="businessApplicationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="businessProductCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="pgLastName" type="xsd:string" minOccurs="0"/> + <xsd:element name="pgFirstName" type="xsd:string" minOccurs="0"/> + <xsd:element name="pgSSN" type="xsd:string" minOccurs="0"/> + <xsd:element name="pgDateOfBirth" type="xsd:string" minOccurs="0"/> + <xsd:element name="pgAnnualIncome" type="xsd:string" minOccurs="0"/> + <xsd:element name="pgIncomeCurrencyType" type="xsd:string" minOccurs="0"/> + <xsd:element name="pgResidenceStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="pgCheckingAccountIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="pgSavingsAccountIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="pgYearsAtEmployer" type="xsd:string" minOccurs="0"/> + <xsd:element name="pgYearsAtResidence" type="xsd:string" minOccurs="0"/> + <xsd:element name="pgHomeAddress1" type="xsd:string" minOccurs="0"/> + <xsd:element name="pgHomeAddress2" type="xsd:string" minOccurs="0"/> + <xsd:element name="pgHomeCity" type="xsd:string" minOccurs="0"/> + <xsd:element name="pgHomeState" type="xsd:string" minOccurs="0"/> + <xsd:element name="pgHomePostalCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="pgHomeCountry" type="xsd:string" minOccurs="0"/> + <xsd:element name="pgEmailAddress" type="xsd:string" minOccurs="0"/> + <xsd:element name="pgHomePhone" type="xsd:string" minOccurs="0"/> + <xsd:element name="pgTitle" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="OtherTax"> + <xsd:sequence> + <xsd:element name="vatTaxAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="vatTaxRate" type="tns:amount" minOccurs="0"/> + <xsd:element name="vatTaxAmountSign" type="xsd:string" minOccurs="0"/> + <xsd:element name="alternateTaxAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="alternateTaxIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="alternateTaxID" type="xsd:string" minOccurs="0"/> + <xsd:element name="localTaxAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="localTaxIndicator" type="xsd:integer" minOccurs="0"/> + <xsd:element name="nationalTaxAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="nationalTaxIndicator" type="xsd:integer" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="Aft"> + <xsd:sequence> + <xsd:element name="indicator" type="xsd:string" minOccurs="0" /> + <xsd:element name="serviceFee" type="xsd:string" minOccurs="0" /> + <xsd:element name="foreignExchangeFee" type="xsd:string" minOccurs="0" /> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="Wallet"> + <xsd:sequence> + <xsd:element name="type" type="xsd:string" minOccurs="0"/> + <xsd:element name="orderID" type="xsd:string" minOccurs="0"/> + <xsd:element name="merchantReferenceID" type="xsd:string" minOccurs="0"/> + <xsd:element name="userPhone" type="xsd:string" minOccurs="0"/> + <xsd:element name="avv" type="xsd:string" minOccurs="0"/> + <xsd:element name="eciRaw" type="xsd:string" minOccurs="0"/> + <xsd:element name="authenticatonMethod" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardEnrollmentMethod" type="xsd:string" minOccurs="0"/> + <xsd:element name="paresStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="veresEnrolled" type="xsd:string" minOccurs="0"/> + <xsd:element name="xid" type="xsd:string" minOccurs="0"/> + <xsd:element name="totalPurchaseAmount" type="xsd:string" minOccurs="0" /> + <xsd:element name="subtotalAmount" type="xsd:string" minOccurs="0" /> + <xsd:element name="discountAmount" type="xsd:string" minOccurs="0" /> + <xsd:element name="giftWrapAmount" type="xsd:string" minOccurs="0" /> + <xsd:element name="eventType" type="xsd:string" minOccurs="0" /> + <xsd:element name="promotionCode" type="xsd:string" minOccurs="0" /> + </xsd:sequence> + </xsd:complexType> + + + <xsd:complexType name="PurchaseTotals"> + <xsd:sequence> + <xsd:element name="currency" type="xsd:string" minOccurs="0"/> + <xsd:element name="discountAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="discountAmountSign" type="xsd:string" minOccurs="0"/> + <xsd:element name="discountManagementIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="taxAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="dutyAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="dutyAmountSign" type="xsd:string" minOccurs="0"/> + <xsd:element name="grandTotalAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="freightAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="freightAmountSign" type="xsd:string" minOccurs="0"/> + <xsd:element name="foreignAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="foreignCurrency" type="xsd:string" minOccurs="0"/> + <xsd:element name="originalAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="originalCurrency" type="xsd:string" minOccurs="0"/> + <xsd:element name="exchangeRate" type="tns:amount" minOccurs="0"/> + <xsd:element name="exchangeRateTimeStamp" type="xsd:string" minOccurs="0"/> + <xsd:element name="additionalAmountType0" type="xsd:string" minOccurs="0"/> + <xsd:element name="additionalAmount0" type="xsd:string" minOccurs="0"/> + <xsd:element name="additionalAmountType1" type="xsd:string" minOccurs="0"/> + <xsd:element name="additionalAmount1" type="xsd:string" minOccurs="0"/> + <xsd:element name="additionalAmountType2" type="xsd:string" minOccurs="0"/> + <xsd:element name="additionalAmount2" type="xsd:string" minOccurs="0"/> + <xsd:element name="additionalAmountType3" type="xsd:string" minOccurs="0"/> + <xsd:element name="additionalAmount3" type="xsd:string" minOccurs="0"/> + <xsd:element name="additionalAmountType4" type="xsd:string" minOccurs="0"/> + <xsd:element name="additionalAmount4" type="xsd:string" minOccurs="0"/> + <xsd:element name="serviceFeeAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="subtotalAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="shippingAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="handlingAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="shippingHandlingAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="shippingDiscountAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="giftWrapAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="insuranceAmount" type="tns:amount" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="FundingTotals"> + <xsd:sequence> + <xsd:element name="currency" type="xsd:string" minOccurs="0"/> + <xsd:element name="grandTotalAmount" type="tns:amount" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="GECC"> + <xsd:sequence> + <xsd:element name="saleType" type="xsd:string" minOccurs="0"/> + <xsd:element name="planNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="sequenceNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="promotionEndDate" type="xsd:string" minOccurs="0"/> + <xsd:element name="promotionPlan" type="xsd:string" minOccurs="0"/> + <xsd:element name="line" type="xsd:string" minOccurs="0" maxOccurs="7"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="UCAF"> + <xsd:sequence> + <xsd:element name="authenticationData" type="xsd:string" minOccurs="0"/> + <xsd:element name="collectionIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="downgradeReasonCode" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="Network"> + <xsd:all> + <xsd:element name="octDomesticIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="octCrossBorderIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="aftDomesticIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="aftCrossBorderIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="networkID" type="xsd:string" minOccurs="0"/> + <xsd:element name="networkOrder" type="xsd:string" minOccurs="0"/> + </xsd:all> + <xsd:attribute name="id" type="xsd:integer" use="optional"/> + </xsd:complexType> + + <xsd:complexType name="Brands"> + <xsd:all> + <xsd:element name="name" type="xsd:string" minOccurs="0"/> + <xsd:element name="type" type="xsd:string" minOccurs="0"/> + </xsd:all> + <xsd:attribute name="id" type="xsd:integer" use="optional"/> + </xsd:complexType> + + <xsd:complexType name="FundTransfer"> + <xsd:sequence> + <xsd:element name="accountNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="accountName" type="xsd:string" minOccurs="0"/> + <xsd:element name="bankCheckDigit" type="xsd:string" minOccurs="0"/> + <xsd:element name="iban" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="BankInfo"> + <xsd:sequence> + <xsd:element name="bankCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="name" type="xsd:string" minOccurs="0"/> + <xsd:element name="address" type="xsd:string" minOccurs="0"/> + <xsd:element name="city" type="xsd:string" minOccurs="0"/> + <xsd:element name="country" type="xsd:string" minOccurs="0"/> + <xsd:element name="branchCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="swiftCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="sortCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="issuerID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="RecurringSubscriptionInfo"> + <xsd:sequence> + <xsd:element name="subscriptionID" type="xsd:string" minOccurs="0"/> + <xsd:element name="status" type="xsd:string" minOccurs="0"/> + <xsd:element name="amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="numberOfPayments" type="xsd:integer" minOccurs="0"/> + <xsd:element name="numberOfPaymentsToAdd" type="xsd:integer" minOccurs="0"/> + <xsd:element name="automaticRenew" type="tns:boolean" minOccurs="0"/> + <xsd:element name="frequency" type="xsd:string" minOccurs="0"/> + <xsd:element name="startDate" type="xsd:string" minOccurs="0"/> + <xsd:element name="endDate" type="xsd:string" minOccurs="0"/> + <xsd:element name="approvalRequired" type="tns:boolean" minOccurs="0"/> + <xsd:element name="event" type="tns:PaySubscriptionEvent" minOccurs="0"/> + <xsd:element name="billPayment" type="tns:boolean" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="PaySubscriptionEvent"> + <xsd:sequence> + <xsd:element name="amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="approvedBy" type="xsd:string" minOccurs="0"/> + <xsd:element name="number" type="xsd:integer" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="Subscription"> + <xsd:sequence> + <xsd:element name="title" type="xsd:string" minOccurs="0"/> + <xsd:element name="paymentMethod" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="TokenSource"> + <xsd:sequence> + <xsd:element name="transientToken" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="PaymentNetworkToken"> + <xsd:sequence> + <xsd:element name="requestorID" type="xsd:string" minOccurs="0"/> + <xsd:element name="transactionType" type="xsd:string" minOccurs="0"/> + <xsd:element name="assuranceLevel" type="xsd:string" minOccurs="0"/> + <xsd:element name="accountStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="originalCardCategory" type="xsd:string" minOccurs="0"/> + <xsd:element name="deviceTechType" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="DecisionManager"> + <xsd:sequence> + <xsd:element name="enabled" type="tns:boolean" minOccurs="0"/> + <xsd:element name="profile" type="xsd:string" minOccurs="0"/> + <xsd:element name="travelData" type="tns:DecisionManagerTravelData" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="DecisionManagerTravelData"> + <xsd:sequence> + <xsd:element name="leg" type="tns:DecisionManagerTravelLeg" minOccurs="0" maxOccurs="100"/> + <xsd:element name="departureDateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="completeRoute" type="xsd:string" minOccurs="0"/> + <xsd:element name="journeyType" type="xsd:string" minOccurs="0"/> + <xsd:element name="actualFinalDestination" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="DecisionManagerTravelLeg"> + <xsd:sequence> + <xsd:element name="origin" type="xsd:string" minOccurs="0"/> + <xsd:element name="destination" type="xsd:string" minOccurs="0"/> + <xsd:element name="departureDateTime" type="tns:dateTime" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="id" type="xsd:integer" use="optional"/> + </xsd:complexType> + <xsd:complexType name="Batch"> + <xsd:sequence> + <xsd:element name="batchID" type="xsd:string" minOccurs="0"/> + <xsd:element name="recordID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="PayPal"> + <xsd:sequence> + <xsd:any processContents="skip" minOccurs="0" maxOccurs="999"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="JPO"> + <xsd:sequence> + <xsd:element name="paymentMethod" type="xsd:integer" minOccurs="0"/> + <xsd:element name="bonusAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="bonuses" type="xsd:integer" minOccurs="0"/> + <xsd:element name="installments" type="xsd:integer" minOccurs="0"/> + <xsd:element name="firstBillingMonth" type="xsd:integer" minOccurs="0"/> + <xsd:element name="jccaTerminalID" type="xsd:integer" minOccurs="0"/> + <xsd:element name="issuerMessage" type="xsd:integer" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="Token"> + <xsd:sequence> + <xsd:element name="prefix" type="xsd:string" minOccurs="0"/> + <xsd:element name="suffix" type="xsd:string" minOccurs="0"/> + <xsd:element name="expirationMonth" type="xsd:string" minOccurs="0"/> + <xsd:element name="expirationYear" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + + <!-- Vme Reseller Service--> + <xsd:complexType name="AP"> + <xsd:sequence> + <xsd:element name="orderID" type="xsd:string" minOccurs="0"/> + <xsd:element name="pspBarcodeID" type="xsd:string" minOccurs="0"/> + <xsd:element name="customerRepresentativeID" type="xsd:string" minOccurs="0" /> + <xsd:element name="productDescription" type="xsd:string" minOccurs="0" /> + <xsd:element name="settlementCurrency" type="xsd:string" minOccurs="0" /> + <xsd:element name="subtotalAmount" type="xsd:string" minOccurs="0" /> + <xsd:element name="shippingAmount" type="xsd:string" minOccurs="0" /> + <xsd:element name="handlingAmount" type="xsd:string" minOccurs="0" /> + <xsd:element name="shippingHandlingAmount" type="xsd:string" minOccurs="0" /> + <xsd:element name="additionalAmount" type="xsd:string" minOccurs="0" /> + <xsd:element name="taxAmount" type="xsd:string" minOccurs="0" /> + <xsd:element name="giftWrapAmount" type="xsd:string" minOccurs="0" /> + <xsd:element name="discountAmount" type="xsd:string" minOccurs="0" /> + <xsd:element name="purchaseID" type="xsd:string" minOccurs="0" /> + <xsd:element name="productID" type="xsd:string" minOccurs="0" /> + <xsd:element name="device" type="tns:APDevice" minOccurs="0" /> + <xsd:element name="apiKey" type="xsd:string" minOccurs="0" /> + <xsd:element name="insuranceAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="billingAgreementIndicator" type="tns:boolean" minOccurs="0"/> + <xsd:element name="billingAgreementID" type="xsd:string" minOccurs="0" /> + <xsd:element name="billingAgreementDescription" type="xsd:string" minOccurs="0" /> + <xsd:element name="payerID" type="xsd:string" minOccurs="0" /> + <xsd:element name="fundingSource" type="xsd:string" minOccurs="0"/> + <xsd:element name="shippingAddressImmutable" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="APDevice"> + <xsd:sequence> + <xsd:element name="id" type="xsd:string" minOccurs="0" /> + <xsd:element name="type" type="xsd:string" minOccurs="0" /> + <xsd:element name="userAgent" type="xsd:string" minOccurs="0" /> + </xsd:sequence> + </xsd:complexType> + + <!-- apAuthService --> + <xsd:complexType name="APAuthService"> + <xsd:sequence> + <xsd:element name="cancelURL" type="xsd:string" minOccurs="0"/> + <xsd:element name="successURL" type="xsd:string" minOccurs="0"/> + <xsd:element name="failureURL" type="xsd:string" minOccurs="0"/> + <xsd:element name="overridePaymentMethod" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="preapprovalToken" type="xsd:string" minOccurs="0"/> + <xsd:element name="orderRequestID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <!-- End of apAuthService --> + + <!-- Start of AP Import Mandate Service --> + + + <xsd:complexType name="APImportMandateService"> + <xsd:sequence> + <xsd:element name="dateSigned" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + + <!-- End of of AP Import Mandate Service --> + + <!-- apAuthReversalService --> + <xsd:complexType name="APAuthReversalService"> + <xsd:sequence> + <xsd:element name="authRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <!-- End of apAuthReversalService --> + <!-- apCaptureService --> + <xsd:complexType name="APCaptureService"> + <xsd:sequence> + <xsd:element name="authRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="isFinal" type="tns:boolean" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <!-- End of apCaptureService --> + <!-- apOptionsService --> + <xsd:complexType name="APOptionsService"> + <xsd:sequence> + <xsd:element name="limit" type="xsd:string" minOccurs="0"/> + <xsd:element name="offset" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <!-- End of apOptionsService --> + <!-- apRefundService --> + <xsd:complexType name="APRefundService"> + <xsd:sequence> + <xsd:element name="captureRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="refundRequestID" type="xsd:string" minOccurs="0" /> + <xsd:element name="reason" type="xsd:string" minOccurs="0"/> + <xsd:element name="instant" type="xsd:string" minOccurs="0"/> + <xsd:element name="note" type="xsd:string" minOccurs="0"/> + <xsd:element name="apInitiateRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="returnRef" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="saleRequestID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <!-- End of apRefundService --> + <!-- apSaleService --> + <xsd:complexType name="APSaleService"> + <xsd:sequence> + <xsd:element name="cancelURL" type="xsd:string" minOccurs="0"/> + <xsd:element name="successURL" type="xsd:string" minOccurs="0"/> + <xsd:element name="failureURL" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="overridePaymentMethod" type="xsd:string" minOccurs="0"/> + <xsd:element name="paymentOptionID" type="xsd:string" minOccurs="0" /> + <xsd:element name="transactionTimeout" type="xsd:string" minOccurs="0" /> + <xsd:element name="orderRequestID" type="xsd:string" minOccurs="0" /> + <xsd:element name="billingAgreementID" type="xsd:string" minOccurs="0" /> + <xsd:element name="mandateID" type="xsd:string" minOccurs="0" /> + <xsd:element name="dateCollect" type="xsd:string" minOccurs="0" /> + <xsd:element name="preapprovalToken" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <!-- End of apAuthService --> + + <xsd:complexType name="APCheckOutDetailsService"> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <!-- End of apCheckoutDetailsService --> + <xsd:complexType name="APTransactionDetailsService"> + <xsd:sequence> + <xsd:element name="transactionDetailsRequestID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <!-- APConfirmPurchaseService --> + <xsd:complexType name="APConfirmPurchaseService"> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <!-- End of APConfirmPurchaseService --> + <xsd:complexType name="APSessionsService"> + <xsd:sequence> + <xsd:element name="cancelURL" type="xsd:string" minOccurs="0"/> + <xsd:element name="successURL" type="xsd:string" minOccurs="0"/> + <xsd:element name="failureURL" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="overridePaymentMethod" type="xsd:string" minOccurs="0"/> + <xsd:element name="paymentOptionID" type="xsd:string" minOccurs="0"/> + <xsd:element name="sessionsType" type="xsd:string" minOccurs="0"/> + <xsd:element name="sessionsRequestID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <!-- APUIStyle --> + <xsd:complexType name="APUI"> + <xsd:sequence> + <xsd:element name="colorBorder" type="xsd:string" minOccurs="0"/> + <xsd:element name="colorBorderSelected" type="xsd:string" minOccurs="0"/> + <xsd:element name="colorButton" type="xsd:string" minOccurs="0"/> + <xsd:element name="colorButtonText" type="xsd:string" minOccurs="0"/> + <xsd:element name="colorCheckbox" type="xsd:string" minOccurs="0"/> + <xsd:element name="colorCheckboxCheckMark" type="xsd:string" minOccurs="0"/> + <xsd:element name="colorHeader" type="xsd:string" minOccurs="0"/> + <xsd:element name="colorLink" type="xsd:string" minOccurs="0"/> + <xsd:element name="colorText" type="xsd:string" minOccurs="0"/> + <xsd:element name="borderRadius" type="xsd:string" minOccurs="0"/> + <xsd:element name="theme" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <!-- End of APUIStyle --> + <!--PayPalGetTxnDetails--> + <xsd:complexType name="PayPalGetTxnDetailsService"> + <xsd:sequence> + <xsd:element name="transactionID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <!-- End of PayPalGetTxnDetails --> + <!--PayPalTransactionSearch--> + <xsd:complexType name="PayPalTransactionSearchService"> + <xsd:sequence> + <xsd:element name="startDate" type="xsd:string" minOccurs="0"/> + <xsd:element name="endDate" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalCustomerEmail" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalReceiptId" type="xsd:string" minOccurs="0"/> + <xsd:element name="transactionID" type="xsd:string" minOccurs="0"/> + <xsd:element name="invoiceNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="grandTotalAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="currency" type="xsd:string" minOccurs="0"/> + <xsd:element name="paymentStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerSalutation" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerFirstname" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerMiddlename" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerLastname" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerSuffix" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <!-- End of PayPalTransactionSearch --> + <!-- Credit card recipient data --> + <xsd:complexType name="Recipient"> + <xsd:sequence> + <xsd:element name="dateOfBirth" type="xsd:string" minOccurs="0"/> + <xsd:element name="postalCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="accountID" type="xsd:string" minOccurs="0"/> + <xsd:element name="lastName" type="xsd:string" minOccurs="0"/> + <xsd:element name="name" type="xsd:string" minOccurs="0"/> + <xsd:element name="billingAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="billingCurrency" type="xsd:string" minOccurs="0"/> + <xsd:element name="billingConversionRate" type="tns:amount" minOccurs="0"/> + + <xsd:element name="firstName" type="xsd:string" minOccurs="0"/> + <xsd:element name="middleInitial" type="xsd:string" minOccurs="0"/> + <xsd:element name="address" type="xsd:string" minOccurs="0"/> + <xsd:element name="city" type="xsd:string" minOccurs="0"/> + <xsd:element name="state" type="xsd:string" minOccurs="0"/> + <xsd:element name="country" type="xsd:string" minOccurs="0"/> + <xsd:element name="phoneNumber" type="xsd:string" minOccurs="0"/> + + + </xsd:sequence> + </xsd:complexType> + <!-- End of Credit card recipient data --> + <xsd:complexType name="Sender"> + <xsd:sequence> + <xsd:element name="referenceNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="sourceOfFunds" type="xsd:string" minOccurs="0"/> + <xsd:element name="name" type="xsd:string" minOccurs="0"/> + <xsd:element name="address" type="xsd:string" minOccurs="0"/> + <xsd:element name="city" type="xsd:string" minOccurs="0"/> + <xsd:element name="state" type="xsd:string" minOccurs="0"/> + <xsd:element name="postalCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="country" type="xsd:string" minOccurs="0"/> + <xsd:element name="accountNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="dateOfBirth" type="xsd:string" minOccurs="0"/> + + <xsd:element name="firstName" type="xsd:string" minOccurs="0"/> + <xsd:element name="middleInitial" type="xsd:string" minOccurs="0"/> + <xsd:element name="lastName" type="xsd:string" minOccurs="0"/> + <xsd:element name="phoneNumber" type="xsd:string" minOccurs="0"/> + + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="CCCheckStatusService"> + <xsd:sequence> + <xsd:element name="authRequestID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="RequestMessage"> + <xsd:sequence> + <xsd:element name="merchantID" type="xsd:string" + minOccurs="0" /> + <xsd:element name="merchantReferenceCode" type="xsd:string" + minOccurs="0" /> + <xsd:element name="debtIndicator" type="tns:boolean" + minOccurs="0" /> + <xsd:element name="clientLibrary" type="xsd:string" + minOccurs="0" /> + <xsd:element name="clientLibraryVersion" type="xsd:string" + minOccurs="0" /> + <xsd:element name="clientEnvironment" type="xsd:string" + minOccurs="0" /> + <xsd:element name="clientSecurityLibraryVersion" + type="xsd:string" minOccurs="0" /> + <xsd:element name="clientApplication" type="xsd:string" + minOccurs="0" /> + <xsd:element name="clientApplicationVersion" + type="xsd:string" minOccurs="0" /> + <xsd:element name="clientApplicationUser" type="xsd:string" + minOccurs="0" /> + <xsd:element name="routingCode" type="xsd:string" + minOccurs="0" /> + <xsd:element name="comments" type="xsd:string" + minOccurs="0" /> + <xsd:element name="returnURL" type="xsd:string" + minOccurs="0" /> + <xsd:element name="invoiceHeader" type="tns:InvoiceHeader" + minOccurs="0" /> + <xsd:element name="paymentScheme" type="xsd:string" minOccurs="0"/> + <xsd:element name="mandateID" type="xsd:string" minOccurs="0"/> + <xsd:element name="aggregatorMerchantIdentifier" type="xsd:string" minOccurs="0"/> + <xsd:element name="customerID" type="xsd:string" minOccurs="0"/> + <xsd:element name="customerFirstName" type="xsd:string" minOccurs="0"/> + <xsd:element name="customerLastName" type="xsd:string" minOccurs="0"/> + <xsd:element name="billTo" type="tns:BillTo" minOccurs="0" /> + <xsd:element name="shipTo" type="tns:ShipTo" minOccurs="0" /> + <xsd:element name="personalID" type="tns:PersonalID" minOccurs="0" /> + <xsd:element name="shipFrom" type="tns:ShipFrom" + minOccurs="0" /> + <xsd:element name="item" type="tns:Item" minOccurs="0" + maxOccurs="1000" /> + <xsd:element name="purchaseTotals" type="tns:PurchaseTotals" + minOccurs="0" /> + <xsd:element name="fundingTotals" type="tns:FundingTotals" + minOccurs="0" /> + <xsd:element name="dcc" type="tns:DCC" minOccurs="0" /> + <xsd:element name="pos" type="tns:Pos" minOccurs="0" /> + <xsd:element name="pin" type="tns:Pin" minOccurs="0" /> + <xsd:element name="encryptedPayment" type="tns:EncryptedPayment" minOccurs="0" /> + <xsd:element name="installment" type="tns:Installment" + minOccurs="0" /> + <xsd:element name="card" type="tns:Card" minOccurs="0" /> + <xsd:element name="category" type="tns:Category" minOccurs="0" /> + <xsd:element name="check" type="tns:Check" minOccurs="0" /> + <xsd:element name="bml" type="tns:BML" minOccurs="0" /> + <xsd:element name="gecc" type="tns:GECC" minOccurs="0" /> + <xsd:element name="ucaf" type="tns:UCAF" minOccurs="0" /> + <xsd:element name="fundTransfer" type="tns:FundTransfer" + minOccurs="0" /> + <xsd:element name="bankInfo" type="tns:BankInfo" + minOccurs="0" /> + <xsd:element name="subscription" type="tns:Subscription" + minOccurs="0" /> + <xsd:element name="recurringSubscriptionInfo" + type="tns:RecurringSubscriptionInfo" minOccurs="0" /> + <xsd:element name="tokenSource" + type="tns:TokenSource" minOccurs="0" /> + <xsd:element name="decisionManager" + type="tns:DecisionManager" minOccurs="0" /> + <xsd:element name="otherTax" type="tns:OtherTax" + minOccurs="0" /> + <xsd:element name="paypal" type="tns:PayPal" minOccurs="0" /> + <xsd:element name="merchantDefinedData" + type="tns:MerchantDefinedData" minOccurs="0" /> + <xsd:element name="merchantSecureData" + type="tns:MerchantSecureData" minOccurs="0" /> + <xsd:element name="jpo" type="tns:JPO" minOccurs="0" /> + <xsd:element name="orderRequestToken" type="xsd:string" + minOccurs="0" /> + <xsd:element name="linkToRequest" type="xsd:string" + minOccurs="0" /> + <xsd:element name="serviceFee" type="tns:ServiceFee" minOccurs="0" /> + <xsd:element name="giftCard" type="tns:GiftCard" minOccurs="0" /> + <xsd:element name="ccAuthService" type="tns:CCAuthService" + minOccurs="0" /> + <xsd:element name="octService" type="tns:OCTService" + minOccurs="0" /> + <xsd:element name="ecAVSService" type="tns:ECAVSService" + minOccurs="0" /> + + <xsd:element name="giftCardActivationService" type="tns:GiftCardActivationService" + minOccurs="0" /> + <xsd:element name="giftCardBalanceInquiryService" type="tns:GiftCardBalanceInquiryService" + minOccurs="0" /> + <xsd:element name="giftCardRedemptionService" type="tns:GiftCardRedemptionService" + minOccurs="0" /> + <xsd:element name="giftCardVoidService" type="tns:GiftCardVoidService" + minOccurs="0" /> + <xsd:element name="giftCardReversalService" type="tns:GiftCardReversalService" + minOccurs="0" /> + <xsd:element name="verificationService" type="tns:VerificationService" minOccurs="0" /> + <xsd:element name="ccSaleService" type="tns:CCSaleService" minOccurs="0" /> + + <xsd:element name="ccSaleCreditService" type="tns:CCSaleCreditService" minOccurs="0" /> + + <xsd:element name="ccSaleReversalService" type="tns:CCSaleReversalService" minOccurs="0" /> + <xsd:element name="ccIncrementalAuthService" type="tns:CCIncrementalAuthService" minOccurs="0" /> + <xsd:element name="ccCaptureService" + type="tns:CCCaptureService" minOccurs="0" /> + <xsd:element name="ccCreditService" + type="tns:CCCreditService" minOccurs="0" /> + <xsd:element name="ccAuthReversalService" + type="tns:CCAuthReversalService" minOccurs="0" /> + <xsd:element name="ccAutoAuthReversalService" + type="tns:CCAutoAuthReversalService" minOccurs="0" /> + <xsd:element name="ccDCCService" type="tns:CCDCCService" + minOccurs="0" /> + <xsd:element name="serviceFeeCalculateService" type="tns:ServiceFeeCalculateService" + minOccurs="0" /> + <xsd:element name="ecDebitService" type="tns:ECDebitService" + minOccurs="0" /> + <xsd:element name="ecCreditService" + type="tns:ECCreditService" minOccurs="0" /> + <xsd:element name="ecAuthenticateService" + type="tns:ECAuthenticateService" minOccurs="0" /> + <xsd:element name="payerAuthEnrollService" + type="tns:PayerAuthEnrollService" minOccurs="0" /> + <xsd:element name="payerAuthValidateService" + type="tns:PayerAuthValidateService" minOccurs="0" /> + <xsd:element name="taxService" type="tns:TaxService" + minOccurs="0" /> + <xsd:element name="dmeService" type="tns:DMEService" + minOccurs="0" /> + <xsd:element name="afsService" type="tns:AFSService" + minOccurs="0" /> + <xsd:element name="davService" type="tns:DAVService" + minOccurs="0" /> + <xsd:element name="exportService" type="tns:ExportService" + minOccurs="0" /> + <xsd:element name="fxRatesService" type="tns:FXRatesService" + minOccurs="0" /> + <xsd:element name="bankTransferService" + type="tns:BankTransferService" minOccurs="0" /> + <xsd:element name="bankTransferRefundService" + type="tns:BankTransferRefundService" minOccurs="0" /> + <xsd:element name="bankTransferRealTimeService" + type="tns:BankTransferRealTimeService" minOccurs="0" /> + <xsd:element name="directDebitMandateService" + type="tns:DirectDebitMandateService" minOccurs="0" /> + <xsd:element name="directDebitService" + type="tns:DirectDebitService" minOccurs="0" /> + <xsd:element name="directDebitRefundService" + type="tns:DirectDebitRefundService" minOccurs="0" /> + <xsd:element name="directDebitValidateService" + type="tns:DirectDebitValidateService" minOccurs="0" /> + <xsd:element name="deviceFingerprintData" + type="tns:DeviceFingerprintData" minOccurs="0" maxOccurs="10" /> + <xsd:element name="paySubscriptionCreateService" + type="tns:PaySubscriptionCreateService" minOccurs="0" /> + <xsd:element name="paySubscriptionUpdateService" + type="tns:PaySubscriptionUpdateService" minOccurs="0" /> + <xsd:element name="paySubscriptionEventUpdateService" + type="tns:PaySubscriptionEventUpdateService" minOccurs="0" /> + <xsd:element name="paySubscriptionRetrieveService" + type="tns:PaySubscriptionRetrieveService" minOccurs="0" /> + <xsd:element name="paySubscriptionDeleteService" + type="tns:PaySubscriptionDeleteService" minOccurs="0" /> + <xsd:element name="payPalPaymentService" + type="tns:PayPalPaymentService" minOccurs="0" /> + <xsd:element name="payPalCreditService" + type="tns:PayPalCreditService" minOccurs="0" /> + <xsd:element name="voidService" type="tns:VoidService" + minOccurs="0" /> + <xsd:element name="businessRules" type="tns:BusinessRules" + minOccurs="0" /> + <xsd:element name="pinlessDebitService" + type="tns:PinlessDebitService" minOccurs="0" /> + <xsd:element name="pinlessDebitValidateService" + type="tns:PinlessDebitValidateService" minOccurs="0" /> + <xsd:element name="pinlessDebitReversalService" + type="tns:PinlessDebitReversalService" minOccurs="0" /> + <xsd:element name="batch" type="tns:Batch" minOccurs="0" /> + <xsd:element name="airlineData" type="tns:AirlineData" + minOccurs="0" /> + <xsd:element name="ancillaryData" type="tns:AncillaryData" + minOccurs="0" /> + <xsd:element name="lodgingData" type="tns:LodgingData" minOccurs="0" /> + <xsd:element name="payPalButtonCreateService" + type="tns:PayPalButtonCreateService" minOccurs="0" /> + <xsd:element name="payPalPreapprovedPaymentService" + type="tns:PayPalPreapprovedPaymentService" minOccurs="0" /> + <xsd:element name="payPalPreapprovedUpdateService" + type="tns:PayPalPreapprovedUpdateService" minOccurs="0" /> + <xsd:element name="riskUpdateService" + type="tns:RiskUpdateService" minOccurs="0" /> + <xsd:element name="fraudUpdateService" + type="tns:FraudUpdateService" minOccurs="0" /> + <xsd:element name="caseManagementActionService" + type="tns:CaseManagementActionService" minOccurs="0" /> + <xsd:element name="reserved" type="tns:RequestReserved" + minOccurs="0" maxOccurs="999" /> + <xsd:element name="deviceFingerprintID" type="xsd:string" + minOccurs="0" /> + <xsd:element name="deviceFingerprintRaw" type="tns:boolean" + minOccurs="0" /> + <xsd:element name="deviceFingerprintHash" type="xsd:string" + minOccurs="0" /> + <xsd:element name="payPalRefundService" + type="tns:PayPalRefundService" minOccurs="0" /> + <xsd:element name="payPalAuthReversalService" + type="tns:PayPalAuthReversalService" minOccurs="0" /> + <xsd:element name="payPalDoCaptureService" + type="tns:PayPalDoCaptureService" minOccurs="0" /> + <xsd:element name="payPalEcDoPaymentService" + type="tns:PayPalEcDoPaymentService" minOccurs="0" /> + <xsd:element name="payPalEcGetDetailsService" + type="tns:PayPalEcGetDetailsService" minOccurs="0" /> + <xsd:element name="payPalEcSetService" + type="tns:PayPalEcSetService" minOccurs="0" /> + <xsd:element name="payPalEcOrderSetupService" + type="tns:PayPalEcOrderSetupService" minOccurs="0" /> + <xsd:element name="payPalAuthorizationService" + type="tns:PayPalAuthorizationService" minOccurs="0" /> + <xsd:element name="payPalUpdateAgreementService" + type="tns:PayPalUpdateAgreementService" minOccurs="0" /> + <xsd:element name="payPalCreateAgreementService" + type="tns:PayPalCreateAgreementService" minOccurs="0" /> + <xsd:element name="payPalDoRefTransactionService" + type="tns:PayPalDoRefTransactionService" minOccurs="0" /> + <xsd:element name="chinaPaymentService" + type="tns:ChinaPaymentService" minOccurs="0" /> + <xsd:element name="chinaRefundService" + type="tns:ChinaRefundService" minOccurs="0" /> + <xsd:element name="boletoPaymentService" + type="tns:BoletoPaymentService" minOccurs="0" /> + <xsd:element name="apPaymentType" type="xsd:string" + minOccurs="0"/> + <xsd:element name="apInitiateService" + type="tns:APInitiateService" minOccurs="0" /> + <xsd:element name="apCheckStatusService" + type="tns:APCheckStatusService" minOccurs="0" /> + <xsd:element name="ignoreCardExpiration" type="tns:boolean" + minOccurs="0" /> + <xsd:element name="reportGroup" type="xsd:string" + minOccurs="0" /> + <xsd:element name="processorID" type="xsd:string" + minOccurs="0" /> + <xsd:element name="thirdPartyCertificationNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="transactionLocalDateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="solutionProviderTransactionID" type="xsd:string" minOccurs="0" /> + <xsd:element name="surchargeAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="surchargeSign" type="xsd:string" minOccurs="0"/> + <xsd:element name="pinDataEncryptedPIN" type="xsd:string" minOccurs="0"/> + <xsd:element name="pinDataKeySerialNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="pinDataPinBlockEncodingFormat" type="xsd:integer" minOccurs="0"/> + <xsd:element name="cashbackAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="pinDebitPurchaseService" type="tns:PinDebitPurchaseService" minOccurs="0"/> + <xsd:element name="pinDebitCreditService" type="tns:PinDebitCreditService" minOccurs="0"/> + <xsd:element name="pinDebitReversalService" type="tns:PinDebitReversalService" minOccurs="0"/> + <xsd:element name="ap" type="tns:AP" minOccurs="0" /> + <xsd:element name="apAuthService" type="tns:APAuthService" minOccurs="0" /> + <xsd:element name="apAuthReversalService" type="tns:APAuthReversalService" minOccurs="0" /> + <xsd:element name="apCaptureService" type="tns:APCaptureService" minOccurs="0" /> + <xsd:element name="apOptionsService" type="tns:APOptionsService" minOccurs="0" /> + <xsd:element name="apRefundService" type="tns:APRefundService" minOccurs="0" /> + <xsd:element name="apSaleService" type="tns:APSaleService" minOccurs="0" /> + <xsd:element name="apCheckoutDetailsService" type="tns:APCheckOutDetailsService" minOccurs="0" /> + <xsd:element name="apSessionsService" type="tns:APSessionsService" minOccurs="0" /> + <xsd:element name="apUI" type="tns:APUI" minOccurs="0" /> + <xsd:element name="apTransactionDetailsService" type="tns:APTransactionDetailsService" minOccurs="0" /> + <xsd:element name="apConfirmPurchaseService" type="tns:APConfirmPurchaseService" minOccurs="0" /> + <xsd:element name="payPalGetTxnDetailsService" type="tns:PayPalGetTxnDetailsService" minOccurs="0" /> + <xsd:element name="payPalTransactionSearchService" type="tns:PayPalTransactionSearchService" minOccurs="0" /> + <xsd:element name="ccDCCUpdateService" type="tns:CCDCCUpdateService" minOccurs="0"/> + <xsd:element name="emvRequest" type="tns:EmvRequest" minOccurs="0" /> + <xsd:element name="merchantTransactionIdentifier" type="xsd:string" minOccurs="0" /> + <xsd:element name="hostedDataCreateService" type="tns:HostedDataCreateService" minOccurs="0"/> + <xsd:element name="hostedDataRetrieveService" type="tns:HostedDataRetrieveService" minOccurs="0"/> + <xsd:element name="merchantCategoryCode" type="xsd:string" minOccurs="0" /> + <xsd:element name="merchantCategoryCodeDomestic" type="xsd:string" minOccurs="0" /> + <xsd:element name="salesSlipNumber" type="xsd:string" minOccurs="0" /> + <xsd:element name="merchandiseCode" type="xsd:string" minOccurs="0" /> + <xsd:element name="merchandiseDescription" type="xsd:string" minOccurs="0" /> + <xsd:element name="paymentInitiationChannel" type="xsd:string" minOccurs="0" /> + <xsd:element name="extendedCreditTotalCount" type="xsd:string" minOccurs="0" /> + <xsd:element name="authIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="paymentNetworkToken" type="tns:PaymentNetworkToken" minOccurs="0"/> + <xsd:element name="recipient" type="tns:Recipient" minOccurs="0"/> + <xsd:element name="sender" type="tns:Sender" minOccurs="0"/> + <xsd:element name="autoRentalData" type="tns:AutoRentalData" minOccurs="0" /> + <xsd:element name="paymentSolution" type="xsd:string" minOccurs="0" /> + <xsd:element name="vc" type="tns:VC" minOccurs="0" /> + <xsd:element name="decryptVisaCheckoutDataService" type="tns:DecryptVisaCheckoutDataService" minOccurs="0" /> + <xsd:element name="taxManagementIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="promotionGroup" type="tns:PromotionGroup" minOccurs="0" maxOccurs="100"/> + <xsd:element name="wallet" type="tns:Wallet" minOccurs="0" /> + <xsd:element name="aft" type="tns:Aft" minOccurs="0" /> + <xsd:element name="balanceInquiry" type="tns:boolean" minOccurs="0" /> + <xsd:element name="prenoteTransaction" type="tns:boolean" minOccurs="0"/> + <xsd:element name="encryptPaymentDataService" type="tns:EncryptPaymentDataService" minOccurs="0"/> + <xsd:element name="nationalNetDomesticData" type="xsd:string" minOccurs="0"/> + <xsd:element name="subsequentAuth" type="xsd:string" minOccurs="0"/> + <xsd:element name="subsequentAuthOriginalAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="binLookupService" type="tns:BinLookupService" minOccurs="0" /> + <xsd:element name="verificationCode" type="xsd:string" minOccurs="0" /> + <xsd:element name="mobileNumber" type="xsd:string" minOccurs="0" /> + <xsd:element name="issuer" type="tns:issuer" minOccurs="0" /> + <xsd:element name="partnerSolutionID" type="xsd:string" minOccurs="0" /> + <xsd:element name="developerID" type="xsd:string" minOccurs="0" /> + <xsd:element name="getVisaCheckoutDataService" type="tns:GETVisaCheckoutDataService" minOccurs="0" /> + <xsd:element name="customerSignatureImage" type="xsd:string" minOccurs="0"/> + <xsd:element name="transactionMetadataService" type="tns:TransactionMetadataService" minOccurs="0" /> + <xsd:element name="subsequentAuthFirst" type="xsd:string" minOccurs="0"/> + <xsd:element name="subsequentAuthReason" type="xsd:string" minOccurs="0"/> + <xsd:element name="subsequentAuthTransactionID" type="xsd:string" minOccurs="0"/> + <xsd:element name="subsequentAuthStoredCredential" type="xsd:string" minOccurs="0"/> + <xsd:element name="loan" type="tns:Loan" minOccurs="0" /> + <xsd:element name="eligibilityInquiry" type="xsd:string" minOccurs="0" /> + <xsd:element name="redemptionInquiry" type="xsd:string" minOccurs="0" /> + <xsd:element name="feeProgramIndicator" type="xsd:string" minOccurs="0" /> + <xsd:element name="apOrderService" type="tns:APOrderService" minOccurs="0" /> + <xsd:element name="apCancelService" type="tns:APCancelService" minOccurs="0" /> + <xsd:element name="apBillingAgreementService" type="tns:APBillingAgreementService" minOccurs="0" /> + <xsd:element name="note_toPayee" type="xsd:string" minOccurs="0" /> + <xsd:element name="note_toPayer" type="xsd:string" minOccurs="0" /> + <xsd:element name="clientMetadataID" type="xsd:string" minOccurs="0" /> + + <xsd:element name="partnerSDKversion" type="xsd:string" minOccurs="0" /> + <xsd:element name="partnerOriginalTransactionID" type="xsd:string" minOccurs="0" /> + <xsd:element name="cardTypeSelectionIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="apCreateMandateService" type="tns:APCreateMandateService" minOccurs="0" /> + <xsd:element name="apMandateStatusService" type="tns:APMandateStatusService" minOccurs="0" /> + <xsd:element name="apUpdateMandateService" type="tns:APUpdateMandateService" minOccurs="0" /> + <xsd:element name="apImportMandateService" type="tns:APImportMandateService" minOccurs="0" /> + <xsd:element name="apRevokeMandateService" type="tns:APRevokeMandateService" minOccurs="0" /> + <xsd:element name="billPaymentType" type="xsd:string" minOccurs="0" /> + <xsd:element name="postdatedTransaction" type="tns:PostdatedTransaction" minOccurs="0" /> + <xsd:element name="getMasterpassDataService" type="tns:GetMasterpassDataService" minOccurs="0" /> + <xsd:element name="ccCheckStatusService" type="tns:CCCheckStatusService" + minOccurs="0" /> + <xsd:element name="mPOS" type="tns:mPOS" minOccurs="0" /> + </xsd:sequence> + + </xsd:complexType> + + <!-- added for Visa Checkout --> + <xsd:complexType name="VC"> + <xsd:sequence> + <xsd:element name="orderID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="DecryptVisaCheckoutDataService"> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + + <xsd:complexType name="DCC"> + <xsd:sequence> + <xsd:element name="dccIndicator" type="xsd:integer" minOccurs="0"/> + <xsd:element name="referenceNumber" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="Promotion"> + <xsd:sequence> + <xsd:element name="discountedAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="type" type="xsd:string" minOccurs="0"/> + <xsd:element name="code" type="xsd:string" minOccurs="0"/> + <xsd:element name="receiptData" type="xsd:string" minOccurs="0"/> + <xsd:element name="discountApplied" type="tns:amount" minOccurs="0"/> + <xsd:element name="description" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="PromotionGroup"> + <xsd:sequence> + <xsd:element name="subtotalAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="taxRate" type="tns:amount" minOccurs="0"/> + <xsd:element name="prohibitDiscount" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="id" type="xsd:integer" use="optional"/> + </xsd:complexType> + + <xsd:complexType name="PromotionGroupReply"> + <xsd:sequence> + <xsd:element name="discountApplied" type="tns:amount" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="id" type="xsd:integer" use="optional"/> + </xsd:complexType> + + <xsd:complexType name="CCAuthReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="authorizationCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="avsCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="avsCodeRaw" type="xsd:string" minOccurs="0"/> + <xsd:element name="cvCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="cvCodeRaw" type="xsd:string" minOccurs="0"/> + <xsd:element name="personalIDCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="authorizedDateTime" type="tns:dateTime" minOccurs="0"/> + <!-- dateTime --> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="bmlAccountNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="authFactorCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="fundingTotals" type="tns:FundingTotals" minOccurs="0"/> + <xsd:element name="fxQuoteID" type="xsd:string" minOccurs="0"/> + <xsd:element name="fxQuoteRate" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="fxQuoteType" type="xsd:string" minOccurs="0"/> + <xsd:element name="fxQuoteExpirationDateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="authRecord" type="xsd:string" minOccurs="0"/> + <xsd:element name="merchantAdviceCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="merchantAdviceCodeRaw" type="xsd:string" minOccurs="0"/> + <xsd:element name="cavvResponseCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="cavvResponseCodeRaw" type="xsd:string" minOccurs="0"/> + <xsd:element name="authenticationXID" type="xsd:string" minOccurs="0"/> + <xsd:element name="authorizationXID" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorCardType" type="xsd:string" minOccurs="0"/> + <xsd:element name="accountBalance" type="tns:amount" minOccurs="0"/> + <xsd:element name="forwardCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="enhancedDataEnabled" type="xsd:string" minOccurs="0"/> + <xsd:element name="referralResponseNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="subResponseCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="approvedAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="creditLine" type="xsd:string" minOccurs="0"/> + <xsd:element name="approvedTerms" type="xsd:string" minOccurs="0"/> + <xsd:element name="paymentNetworkTransactionID" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardCategory" type="xsd:string" minOccurs="0"/> + <xsd:element name="ownerMerchantID" type="xsd:string" minOccurs="0"/> + <xsd:element name="requestAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="requestCurrency" type="xsd:string" minOccurs="0"/> + <xsd:element name="accountBalanceCurrency" type="xsd:string" minOccurs="0"/> + <xsd:element name="accountBalanceSign" type="xsd:string" minOccurs="0"/> + <xsd:element name="amountType" type="xsd:string" minOccurs="0"/> + <xsd:element name="accountType" type="xsd:string" minOccurs="0"/> + <xsd:element name="affluenceIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="evEmail" type="xsd:string" minOccurs="0"/> + <xsd:element name="evPhoneNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="evPostalCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="evName" type="xsd:string" minOccurs="0"/> + <xsd:element name="evStreet" type="xsd:string" minOccurs="0"/> + <xsd:element name="evEmailRaw" type="xsd:string" minOccurs="0"/> + <xsd:element name="evPhoneNumberRaw" type="xsd:string" minOccurs="0"/> + <xsd:element name="evPostalCodeRaw" type="xsd:string" minOccurs="0"/> + <xsd:element name="evNameRaw" type="xsd:string" minOccurs="0"/> + <xsd:element name="evStreetRaw" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardGroup" type="xsd:string" minOccurs="0"/> + <xsd:element name="posData" type="xsd:string" minOccurs="0"/> + <xsd:element name="transactionID" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardIssuerCountry" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardRegulated" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardCommercial" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardPrepaid" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardPayroll" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardHealthcare" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardSignatureDebit" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardPINlessDebit" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardLevel3Eligible" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorTransactionID" type="xsd:string" minOccurs="0"/> + <xsd:element name="providerReasonCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="providerReasonDescription" type="xsd:string" minOccurs="0"/> + <xsd:element name="providerPassThroughData" type="xsd:string" minOccurs="0"/> + <xsd:element name="providerCVNResponseCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="providerAVSResponseCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="providerAcquirerBankCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="paymentCardService" type="xsd:string" minOccurs="0"/> + <xsd:element name="paymentCardServiceResult" type="xsd:string" minOccurs="0"/> + <xsd:element name="transactionQualification" type="xsd:string" minOccurs="0"/> + <xsd:element name="transactionIntegrity" type="xsd:string" minOccurs="0"/> + <xsd:element name="emsTransactionRiskScore" type="xsd:string" minOccurs="0" /> + <xsd:element name="reconciliationReferenceNumber" type="xsd:string" minOccurs="0" /> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="OCTReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="requestDateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="approvalCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="paymentNetworkTransactionID" type="xsd:string" minOccurs="0"/> + <xsd:element name="prepaidBalanceCurrency" type="xsd:string" minOccurs="0"/> + <xsd:element name="prepaidBalanceAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorResponseSource" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationIdType" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="VerificationReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer" /> + <xsd:element name="processorTransactionID" type="xsd:string" minOccurs="0" /> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0" /> + <xsd:element name="verifiedDateTime" type="xsd:string" minOccurs="0" /> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0" /> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="CCSaleReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="authorizationCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="avsCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="avsCodeRaw" type="xsd:string" minOccurs="0"/> + <xsd:element name="cvCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="cvCodeRaw" type="xsd:string" minOccurs="0"/> + <xsd:element name="cavvResponseCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="cavvResponseCodeRaw" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardGroup" type="xsd:string" minOccurs="0"/> + <xsd:element name="paymentNetworkTransactionID" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardCategory" type="xsd:string" minOccurs="0"/> + <xsd:element name="accountBalance" type="tns:amount" minOccurs="0"/> + <xsd:element name="authorizedDateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="requestAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="accountBalanceCurrency" type="xsd:string" minOccurs="0"/> + <xsd:element name="accountBalanceSign" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="CCSaleCreditReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="authorizationCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="authorizedDateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="paymentNetworkTransactionID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="CCSaleReversalReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="authorizationCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="requestDateTime" type="tns:dateTime" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="CCIncrementalAuthReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="amount" type="tns:amount" minOccurs="0" /> + <xsd:element name="authorizationCode" type="xsd:string" minOccurs="0" /> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0" /> + <xsd:element name="authorizedDateTime" type="tns:dateTime" minOccurs="0" /> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0" /> + <xsd:element name="paymentNetworkTransactionID" type="xsd:string" minOccurs="0" /> + <xsd:element name="cardCategory" type="xsd:string" minOccurs="0" /> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="CCCaptureReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="requestDateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="fundingTotals" type="tns:FundingTotals" minOccurs="0"/> + <xsd:element name="fxQuoteID" type="xsd:string" minOccurs="0"/> + <xsd:element name="fxQuoteRate" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="fxQuoteType" type="xsd:string" minOccurs="0"/> + <xsd:element name="fxQuoteExpirationDateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="purchasingLevel3Enabled" type="xsd:string" minOccurs="0"/> + <xsd:element name="enhancedDataEnabled" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationReferenceNumber" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="ServiceFeeCalculateReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer" /> + <xsd:element name="amount" type="tns:amount" minOccurs="0" /> + <xsd:element name="requestDateTime" type="tns:dateTime" minOccurs="0" /> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="CCCreditReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="requestDateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="purchasingLevel3Enabled" type="xsd:string" minOccurs="0"/> + <xsd:element name="enhancedDataEnabled" type="xsd:string" minOccurs="0"/> + <xsd:element name="authorizationXID" type="xsd:string" minOccurs="0"/> + <xsd:element name="forwardCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="ownerMerchantID" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationReferenceNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="authorizationCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="paymentNetworkTransactionID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="PinDebitPurchaseReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="authorizationCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="networkCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="transactionID" type="xsd:string" minOccurs="0"/> + <xsd:element name="requestAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="requestCurrency" type="xsd:string" minOccurs="0"/> + <xsd:element name="amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="dateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="accountType" type="xsd:string" minOccurs="0"/> + <xsd:element name="amountType" type="xsd:string" minOccurs="0"/> + <xsd:element name="accountBalance" type="xsd:string" minOccurs="0"/> + <xsd:element name="accountBalanceCurrency" type="xsd:string" minOccurs="0"/> + <xsd:element name="accountBalanceSign" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="PinDebitCreditReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="authorizationCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="networkCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="transactionID" type="xsd:string" minOccurs="0"/> + <xsd:element name="amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="dateTime" type="tns:dateTime" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="PinDebitReversalReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="dateTime" type="tns:dateTime" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="CCAuthReversalReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="authorizationCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="requestDateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="forwardCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorTransactionID" type="xsd:string" minOccurs="0"/> + <xsd:element name="paymentCardService" type="xsd:string" minOccurs="0"/> + <xsd:element name="paymentCardServiceResult" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="CCAutoAuthReversalReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="result" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="ECAVSReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="validationType" type="xsd:string" minOccurs="0"/> + <xsd:element name="primaryStatusCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="secondaryStatusCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="additionalStatusCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="numberOfReturns" type="xsd:string" minOccurs="0"/> + <xsd:element name="lastReturnDate" type="xsd:string" minOccurs="0"/> + <xsd:element name="lastReturnProcessorResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="lastUpdateDate" type="xsd:string" minOccurs="0"/> + <xsd:element name="addedOrClosedDate" type="xsd:string" minOccurs="0"/> + <xsd:element name="previousStatusCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="fcraDisputeCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="scoredAccountProcessorResponse1" type="xsd:string" minOccurs="0"/> + <xsd:element name="scoredAccountProcessorResponse2" type="xsd:string" minOccurs="0"/> + <xsd:element name="scoredAccountProcessorResponse3" type="xsd:string" minOccurs="0"/> + <xsd:element name="scoredAccountProcessorResponse5" type="xsd:string" minOccurs="0"/> + <xsd:element name="customerDataConditionCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="matchBillToFullName" type="xsd:string" minOccurs="0"/> + <xsd:element name="matchBillToPrefix" type="xsd:string" minOccurs="0"/> + <xsd:element name="matchBillToFirstName" type="xsd:string" minOccurs="0"/> + <xsd:element name="matchBillToMiddleName" type="xsd:string" minOccurs="0"/> + <xsd:element name="matchBillToLastName" type="xsd:string" minOccurs="0"/> + <xsd:element name="matchBillToSuffix" type="xsd:string" minOccurs="0"/> + <xsd:element name="matchBillToCompany" type="xsd:string" minOccurs="0"/> + <xsd:element name="matchBillToAddress" type="xsd:string" minOccurs="0"/> + <xsd:element name="matchBillToCity" type="xsd:string" minOccurs="0"/> + <xsd:element name="matchBillToState" type="xsd:string" minOccurs="0"/> + <xsd:element name="matchBillToPostalCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="matchBillToPhoneNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="matchBillToCompanyPhoneNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="matchBillToCompanyTaxID" type="xsd:string" minOccurs="0"/> + <xsd:element name="matchBillToSSN" type="xsd:string" minOccurs="0"/> + <xsd:element name="matchBillToDateOfBirth" type="xsd:string" minOccurs="0"/> + <xsd:element name="matchPersonalIDType" type="xsd:string" minOccurs="0"/> + <xsd:element name="matchPersonalID" type="xsd:string" minOccurs="0"/> + <xsd:element name="matchPersonalIDIssuedBy" type="xsd:string" minOccurs="0"/> + <xsd:element name="overallMatchScore" type="xsd:integer" minOccurs="0"/> + <xsd:element name="calculatedResponse" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="ECDebitReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="settlementMethod" type="xsd:string" minOccurs="0"/> + <xsd:element name="requestDateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="verificationLevel" type="xsd:integer" minOccurs="0"/> + <xsd:element name="processorTransactionID" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="avsCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="avsCodeRaw" type="xsd:string" minOccurs="0"/> + <xsd:element name="verificationCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="verificationCodeRaw" type="xsd:string" minOccurs="0"/> + <xsd:element name="correctedAccountNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="correctedRoutingNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="ownerMerchantID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="ECCreditReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="settlementMethod" type="xsd:string" minOccurs="0"/> + <xsd:element name="requestDateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="processorTransactionID" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="verificationCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="verificationCodeRaw" type="xsd:string" minOccurs="0"/> + <xsd:element name="correctedAccountNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="correctedRoutingNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="ownerMerchantID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="ECAuthenticateReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="requestDateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="checkpointSummary" type="xsd:string" minOccurs="0"/> + <xsd:element name="fraudShieldIndicators" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="PayerAuthEnrollReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="acsURL" type="xsd:string" minOccurs="0"/> + <xsd:element name="authenticationResult" type="xsd:string" minOccurs="0"/> + <xsd:element name="authenticationStatusMessage" type="xsd:string" minOccurs="0"/> + <xsd:element name="cavv" type="xsd:string" minOccurs="0"/> + <xsd:element name="cavvAlgorithm" type="xsd:string" minOccurs="0"/> + <xsd:element name="commerceIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="eci" type="xsd:string" minOccurs="0"/> + <xsd:element name="eciRaw" type="xsd:string" minOccurs="0"/> + <xsd:element name="paReq" type="xsd:string" minOccurs="0"/> + <xsd:element name="proxyPAN" type="xsd:string" minOccurs="0"/> + <xsd:element name="xid" type="xsd:string" minOccurs="0"/> + <xsd:element name="proofXML" type="xsd:string" minOccurs="0"/> + <xsd:element name="ucafAuthenticationData" type="xsd:string" minOccurs="0"/> + <xsd:element name="ucafCollectionIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="paresStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="veresEnrolled" type="xsd:string" minOccurs="0"/> + <xsd:element name="authenticationPath" type="xsd:string" minOccurs="0"/> + <xsd:element name="specificationVersion" type="xsd:string" minOccurs="0"/> + <xsd:element name="authenticationTransactionID" type="xsd:string" minOccurs="0"/> + <xsd:element name="directoryServerTransactionID" type="xsd:string" minOccurs="0"/> + <xsd:element name="challengeRequired" type="tns:boolean" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="PayerAuthValidateReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="authenticationResult" type="xsd:string" minOccurs="0"/> + <xsd:element name="authenticationStatusMessage" type="xsd:string" minOccurs="0"/> + <xsd:element name="cavv" type="xsd:string" minOccurs="0"/> + <xsd:element name="cavvAlgorithm" type="xsd:string" minOccurs="0"/> + <xsd:element name="commerceIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="eci" type="xsd:string" minOccurs="0"/> + <xsd:element name="eciRaw" type="xsd:string" minOccurs="0"/> + <xsd:element name="xid" type="xsd:string" minOccurs="0"/> + <xsd:element name="ucafAuthenticationData" type="xsd:string" minOccurs="0"/> + <xsd:element name="ucafCollectionIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="paresStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="specificationVersion" type="xsd:string" minOccurs="0"/> + <xsd:element name="directoryServerTransactionID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="TaxReplyItem"> + <xsd:sequence> + <xsd:element name="taxableAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="exemptAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="specialTaxAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="cityTaxAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="countyTaxAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="districtTaxAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="stateTaxAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="countryTaxAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="totalTaxAmount" type="tns:amount"/> + <xsd:element name="jurisdiction" type="tns:TaxReplyItemJurisdiction" minOccurs="0" maxOccurs="1000"/> + </xsd:sequence> + <xsd:attribute name="id" type="xsd:integer" use="required"/> + </xsd:complexType> + <xsd:complexType name="TaxReplyItemJurisdiction"> + <xsd:sequence> + <xsd:element name="country" type="xsd:string" minOccurs="0"/> + <xsd:element name="region" type="xsd:string" minOccurs="0"/> + <xsd:element name="type" type="xsd:string" minOccurs="0"/> + <xsd:element name="code" type="xsd:string" minOccurs="0"/> + <xsd:element name="taxable" type="tns:amount" minOccurs="0"/> + <xsd:element name="rate" type="tns:amount" minOccurs="0"/> + <xsd:element name="taxAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="name" type="xsd:string"/> + <xsd:element name="taxName" type="xsd:string"/> + </xsd:sequence> + <xsd:attribute name="jurisId" type="xsd:integer" use="required"/> + </xsd:complexType> + <xsd:complexType name="TaxReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="currency" type="xsd:string" minOccurs="0"/> + <xsd:element name="grandTotalAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="totalTaxableAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="totalExemptAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="totalSpecialTaxAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="totalCityTaxAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="city" type="xsd:string" minOccurs="0"/> + <xsd:element name="totalCountyTaxAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="county" type="xsd:string" minOccurs="0"/> + <xsd:element name="totalDistrictTaxAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="totalStateTaxAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="state" type="xsd:string" minOccurs="0"/> + <xsd:element name="totalCountryTaxAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="totalTaxAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="commitIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="refundIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="postalCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="geocode" type="xsd:string" minOccurs="0"/> + <xsd:element name="item" type="tns:TaxReplyItem" minOccurs="0" maxOccurs="1000"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="DeviceFingerprint"> + <xsd:sequence> + <xsd:element name="cookiesEnabled" type="tns:boolean" minOccurs="0"/> + <xsd:element name="flashEnabled" type="tns:boolean" minOccurs="0"/> + <xsd:element name="hash" type="xsd:string" minOccurs="0"/> + <xsd:element name="imagesEnabled" type="tns:boolean" minOccurs="0"/> + <xsd:element name="javascriptEnabled" type="tns:boolean" minOccurs="0"/> + <xsd:element name="proxyIPAddress" type="xsd:string" minOccurs="0"/> + <xsd:element name="proxyIPAddressActivities" type="xsd:string" minOccurs="0"/> + <xsd:element name="proxyIPAddressAttributes" type="xsd:string" minOccurs="0"/> + <xsd:element name="proxyServerType" type="xsd:string" minOccurs="0"/> + <xsd:element name="trueIPAddress" type="xsd:string" minOccurs="0"/> + <xsd:element name="trueIPAddressActivities" type="xsd:string" minOccurs="0"/> + <xsd:element name="trueIPAddressAttributes" type="xsd:string" minOccurs="0"/> + <xsd:element name="trueIPAddressCity" type="xsd:string" minOccurs="0"/> + <xsd:element name="trueIPAddressState" type="xsd:string" minOccurs="0"/> + <xsd:element name="trueIPAddressCountry" type="xsd:string" minOccurs="0"/> + <xsd:element name="smartID" type="xsd:string" minOccurs="0"/> + <xsd:element name="smartIDConfidenceLevel" type="xsd:string" minOccurs="0"/> + <xsd:element name="screenResolution" type="xsd:string" minOccurs="0"/> + <xsd:element name="browserLanguage" type="xsd:string" minOccurs="0"/> + <xsd:element name="agentType" type="xsd:string" minOccurs="0"/> + <xsd:element name="dateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="profileDuration" type="xsd:integer" minOccurs="0"/> + <xsd:element name="profiledURL" type="xsd:string" minOccurs="0"/> + <xsd:element name="timeOnPage" type="xsd:integer" minOccurs="0"/> + <xsd:element name="deviceMatch" type="xsd:string" minOccurs="0"/> + <xsd:element name="firstEncounter" type="xsd:string" minOccurs="0"/> + <xsd:element name="flashOS" type="xsd:string" minOccurs="0"/> + <xsd:element name="flashVersion" type="xsd:string" minOccurs="0"/> + <xsd:element name="deviceLatitude" type="xsd:string" minOccurs="0"/> + <xsd:element name="deviceLongitude" type="xsd:string" minOccurs="0"/> + <xsd:element name="gpsAccuracy" type="xsd:string" minOccurs="0"/> + <xsd:element name="jbRoot" type="xsd:integer" minOccurs="0"/> + <xsd:element name="jbRootReason" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="AFSReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="afsResult" type="xsd:integer" minOccurs="0"/> + <xsd:element name="hostSeverity" type="xsd:integer" minOccurs="0"/> + <xsd:element name="consumerLocalTime" type="xsd:string" minOccurs="0"/> + <!-- xsd:time --> + <xsd:element name="afsFactorCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="addressInfoCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="hotlistInfoCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="internetInfoCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="phoneInfoCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="suspiciousInfoCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="velocityInfoCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="identityInfoCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="ipCountry" type="xsd:string" minOccurs="0"/> + <xsd:element name="ipState" type="xsd:string" minOccurs="0"/> + <xsd:element name="ipCity" type="xsd:string" minOccurs="0"/> + <xsd:element name="ipRoutingMethod" type="xsd:string" minOccurs="0"/> + <xsd:element name="ipAnonymizerStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="ipCarrier" type="xsd:string" minOccurs="0"/> + <xsd:element name="ipOrganization" type="xsd:string" minOccurs="0"/> + <xsd:element name="scoreModelUsed" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardBin" type="xsd:string" minOccurs="0"/> + <xsd:element name="binCountry" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardAccountType" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardScheme" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardIssuer" type="xsd:string" minOccurs="0"/> + <xsd:element name="deviceFingerprint" type="tns:DeviceFingerprint" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="DAVReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="addressType" type="xsd:string" minOccurs="0"/> + <xsd:element name="apartmentInfo" type="xsd:string" minOccurs="0"/> + <xsd:element name="barCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="barCodeCheckDigit" type="xsd:string" minOccurs="0"/> + <xsd:element name="careOf" type="xsd:string" minOccurs="0"/> + <xsd:element name="cityInfo" type="xsd:string" minOccurs="0"/> + <xsd:element name="countryInfo" type="xsd:string" minOccurs="0"/> + <xsd:element name="directionalInfo" type="xsd:string" minOccurs="0"/> + <xsd:element name="lvrInfo" type="xsd:string" minOccurs="0"/> + <xsd:element name="matchScore" type="xsd:integer" minOccurs="0"/> + <xsd:element name="standardizedAddress1" type="xsd:string" minOccurs="0"/> + <xsd:element name="standardizedAddress2" type="xsd:string" minOccurs="0"/> + <xsd:element name="standardizedAddress3" type="xsd:string" minOccurs="0"/> + <xsd:element name="standardizedAddress4" type="xsd:string" minOccurs="0"/> + <xsd:element name="standardizedAddressNoApt" type="xsd:string" minOccurs="0"/> + <xsd:element name="standardizedCity" type="xsd:string" minOccurs="0"/> + <xsd:element name="standardizedCounty" type="xsd:string" minOccurs="0"/> + <xsd:element name="standardizedCSP" type="xsd:string" minOccurs="0"/> + <xsd:element name="standardizedState" type="xsd:string" minOccurs="0"/> + <xsd:element name="standardizedPostalCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="standardizedCountry" type="xsd:string" minOccurs="0"/> + <xsd:element name="standardizedISOCountry" type="xsd:string" minOccurs="0"/> + <xsd:element name="stateInfo" type="xsd:string" minOccurs="0"/> + <xsd:element name="streetInfo" type="xsd:string" minOccurs="0"/> + <xsd:element name="suffixInfo" type="xsd:string" minOccurs="0"/> + <xsd:element name="postalCodeInfo" type="xsd:string" minOccurs="0"/> + <xsd:element name="overallInfo" type="xsd:string" minOccurs="0"/> + <xsd:element name="usInfo" type="xsd:string" minOccurs="0"/> + <xsd:element name="caInfo" type="xsd:string" minOccurs="0"/> + <xsd:element name="intlInfo" type="xsd:string" minOccurs="0"/> + <xsd:element name="usErrorInfo" type="xsd:string" minOccurs="0"/> + <xsd:element name="caErrorInfo" type="xsd:string" minOccurs="0"/> + <xsd:element name="intlErrorInfo" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="DeniedPartiesMatch"> + <xsd:sequence> + <xsd:element name="list" type="xsd:string" minOccurs="0"/> + <xsd:element name="name" type="xsd:string" minOccurs="0" maxOccurs="100"/> + <xsd:element name="address" type="xsd:string" minOccurs="0" maxOccurs="100"/> + <xsd:element name="program" type="xsd:string" minOccurs="0" maxOccurs="100"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="ExportReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="ipCountryConfidence" type="xsd:integer" minOccurs="0"/> + <xsd:element name="infoCode" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="FXQuote"> + <xsd:sequence> + <xsd:element name="id" type="xsd:string" minOccurs="0"/> + <xsd:element name="rate" type="xsd:string" minOccurs="0"/> + <xsd:element name="type" type="xsd:string" minOccurs="0"/> + <xsd:element name="expirationDateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="currency" type="xsd:string" minOccurs="0"/> + <xsd:element name="fundingCurrency" type="xsd:string" minOccurs="0"/> + <xsd:element name="receivedDateTime" type="tns:dateTime" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="FXRatesReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="quote" type="tns:FXQuote" minOccurs="0" maxOccurs="999"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="BankTransferReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="accountHolder" type="xsd:string" minOccurs="0"/> + <xsd:element name="accountNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="bankName" type="xsd:string" minOccurs="0"/> + <xsd:element name="bankCity" type="xsd:string" minOccurs="0"/> + <xsd:element name="bankCountry" type="xsd:string" minOccurs="0"/> + <xsd:element name="paymentReference" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="bankSwiftCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="bankSpecialID" type="xsd:string" minOccurs="0"/> + <xsd:element name="requestDateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="iban" type="xsd:string" minOccurs="0"/> + <xsd:element name="bankCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="branchCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationReferenceNumber" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="BankTransferRealTimeReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="formMethod" type="xsd:string" minOccurs="0"/> + <xsd:element name="formAction" type="xsd:string" minOccurs="0"/> + <xsd:element name="requestDateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="paymentReference" type="xsd:string" minOccurs="0"/> + <xsd:element name="amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="reconciliationReferenceNumber" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="DirectDebitMandateReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="mandateID" type="xsd:string" minOccurs="0"/> + <xsd:element name="mandateMaturationDate" type="xsd:string" minOccurs="0"/> + <xsd:element name="requestDateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="BankTransferRefundReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="requestDateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="iban" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationReferenceNumber" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="DirectDebitReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="requestDateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorTransactionID" type="xsd:string" minOccurs="0"/> + <xsd:element name="mandateAuthenticationDate" type="xsd:string" minOccurs="0"/> + <xsd:element name="mandateID" type="xsd:string" minOccurs="0"/> + <xsd:element name="iban" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationReferenceNumber" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="DirectDebitValidateReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="requestDateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="iban" type="xsd:string" minOccurs="0"/> + <xsd:element name="bankSwiftCode" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="DirectDebitRefundReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="requestDateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorTransactionID" type="xsd:string" minOccurs="0"/> + <xsd:element name="iban" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationReferenceNumber" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="PaySubscriptionCreateReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="subscriptionID" type="xsd:string"/> + <xsd:element name="instrumentIdentifierID" type="xsd:string" minOccurs="0"/> + <xsd:element name="instrumentIdentifierStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="instrumentIdentifierNew" type="xsd:string" minOccurs="0"/> + <xsd:element name="instrumentIdentifierSuccessorID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="PaySubscriptionUpdateReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="subscriptionID" type="xsd:string"/> + <xsd:element name="subscriptionIDNew" type="xsd:string" minOccurs="0"/> + <xsd:element name="ownerMerchantID" type="xsd:string" minOccurs="0"/> + <xsd:element name="instrumentIdentifierID" type="xsd:string" minOccurs="0"/> + <xsd:element name="instrumentIdentifierStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="instrumentIdentifierNew" type="xsd:string" minOccurs="0"/> + <xsd:element name="instrumentIdentifierSuccessorID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="PaySubscriptionEventUpdateReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="ownerMerchantID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="PaySubscriptionRetrieveReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="approvalRequired" type="xsd:string" minOccurs="0"/> + <xsd:element name="automaticRenew" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardAccountNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardExpirationMonth" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardExpirationYear" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardIssueNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardStartMonth" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardStartYear" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardType" type="xsd:string" minOccurs="0"/> + <xsd:element name="checkAccountNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="checkAccountType" type="xsd:string" minOccurs="0"/> + <xsd:element name="checkBankTransitNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="checkSecCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="checkAuthenticateID" type="xsd:string" minOccurs="0"/> + <xsd:element name="city" type="xsd:string" minOccurs="0"/> + <xsd:element name="comments" type="xsd:string" minOccurs="0"/> + <xsd:element name="companyName" type="xsd:string" minOccurs="0"/> + <xsd:element name="country" type="xsd:string" minOccurs="0"/> + <xsd:element name="currency" type="xsd:string" minOccurs="0"/> + <xsd:element name="customerAccountID" type="xsd:string" minOccurs="0"/> + <xsd:element name="email" type="xsd:string" minOccurs="0"/> + <xsd:element name="endDate" type="xsd:string" minOccurs="0"/> + <xsd:element name="firstName" type="xsd:string" minOccurs="0"/> + <xsd:element name="frequency" type="xsd:string" minOccurs="0"/> + <xsd:element name="lastName" type="xsd:string" minOccurs="0"/> + <xsd:element name="merchantReferenceCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="paymentMethod" type="xsd:string" minOccurs="0"/> + <xsd:element name="paymentsRemaining" type="xsd:string" minOccurs="0"/> + <xsd:element name="phoneNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="postalCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="recurringAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="setupAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="startDate" type="xsd:string" minOccurs="0"/> + <xsd:element name="state" type="xsd:string" minOccurs="0"/> + <xsd:element name="status" type="xsd:string" minOccurs="0"/> + <xsd:element name="street1" type="xsd:string" minOccurs="0"/> + <xsd:element name="street2" type="xsd:string" minOccurs="0"/> + <xsd:element name="subscriptionID" type="xsd:string" minOccurs="0"/> + <xsd:element name="subscriptionIDNew" type="xsd:string" minOccurs="0"/> + <xsd:element name="title" type="xsd:string" minOccurs="0"/> + <xsd:element name="totalPayments" type="xsd:string" minOccurs="0"/> + <xsd:element name="shipToFirstName" type="xsd:string" minOccurs="0"/> + <xsd:element name="shipToLastName" type="xsd:string" minOccurs="0"/> + <xsd:element name="shipToStreet1" type="xsd:string" minOccurs="0"/> + <xsd:element name="shipToStreet2" type="xsd:string" minOccurs="0"/> + <xsd:element name="shipToCity" type="xsd:string" minOccurs="0"/> + <xsd:element name="shipToState" type="xsd:string" minOccurs="0"/> + <xsd:element name="shipToPostalCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="shipToCompany" type="xsd:string" minOccurs="0"/> + <xsd:element name="shipToCountry" type="xsd:string" minOccurs="0"/> + <xsd:element name="billPayment" type="xsd:string" minOccurs="0"/> + <xsd:element name="merchantDefinedDataField1" type="xsd:string" minOccurs="0"/> + <xsd:element name="merchantDefinedDataField2" type="xsd:string" minOccurs="0"/> + <xsd:element name="merchantDefinedDataField3" type="xsd:string" minOccurs="0"/> + <xsd:element name="merchantDefinedDataField4" type="xsd:string" minOccurs="0"/> + <xsd:element name="merchantSecureDataField1" type="xsd:string" minOccurs="0"/> + <xsd:element name="merchantSecureDataField2" type="xsd:string" minOccurs="0"/> + <xsd:element name="merchantSecureDataField3" type="xsd:string" minOccurs="0"/> + <xsd:element name="merchantSecureDataField4" type="xsd:string" minOccurs="0"/> + <xsd:element name="ownerMerchantID" type="xsd:string" minOccurs="0"/> + <xsd:element name="companyTaxID" type="xsd:string" minOccurs="0"/> + <xsd:element name="driversLicenseNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="driversLicenseState" type="xsd:string" minOccurs="0"/> + <xsd:element name="dateOfBirth" type="xsd:string" minOccurs="0"/> + <xsd:element name="instrumentIdentifierID" type="xsd:string" minOccurs="0"/> + <xsd:element name="instrumentIdentifierStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="instrumentIdentifierSuccessorID" type="xsd:string" minOccurs="0"/> + <xsd:element name="subsequentAuthTransactionID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="PaySubscriptionDeleteReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="subscriptionID" type="xsd:string"/> + <xsd:element name="instrumentIdentifierID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="PayPalPaymentReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="secureData" type="xsd:string" minOccurs="0"/> + <xsd:element name="amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="requestDateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="PayPalCreditReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="requestDateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="VoidReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="requestDateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="currency" type="xsd:string" minOccurs="0"/> + <xsd:element name="reversalSubmitted" type="tns:boolean" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="PinlessDebitReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="authorizationCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="requestDateTime" type="tns:dateTime" minOccurs="0"/> + <!-- dateTime --> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="receiptNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="ownerMerchantID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="PinlessDebitValidateReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="status" type="xsd:string" minOccurs="0"/> + <xsd:element name="requestDateTime" type="tns:dateTime" minOccurs="0"/> + <!-- dateTime --> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="PinlessDebitReversalReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="requestDateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <!-- payPal Upgrade Services --> + <xsd:complexType name="PayPalButtonCreateReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="encryptedFormData" type="xsd:string" minOccurs="0"/> + <xsd:element name="unencryptedFormData" type="xsd:string" minOccurs="0"/> + <xsd:element name="requestDateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="buttonType" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="PayPalPreapprovedPaymentReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="requestDateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerName" type="xsd:string" minOccurs="0"/> + <xsd:element name="transactionType" type="xsd:string" minOccurs="0"/> + <xsd:element name="feeAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerCountry" type="xsd:string" minOccurs="0"/> + <xsd:element name="pendingReason" type="xsd:string" minOccurs="0"/> + <xsd:element name="paymentStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="mpStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="payer" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerID" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerBusiness" type="xsd:string" minOccurs="0"/> + <xsd:element name="transactionID" type="xsd:string" minOccurs="0"/> + <xsd:element name="desc" type="xsd:string" minOccurs="0"/> + <xsd:element name="mpMax" type="xsd:string" minOccurs="0"/> + <xsd:element name="paymentType" type="xsd:string" minOccurs="0"/> + <xsd:element name="paymentDate" type="xsd:string" minOccurs="0"/> + <xsd:element name="paymentGrossAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="settleAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="taxAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="exchangeRate" type="xsd:string" minOccurs="0"/> + <xsd:element name="paymentSourceID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="PayPalPreapprovedUpdateReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="requestDateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerName" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerCountry" type="xsd:string" minOccurs="0"/> + <xsd:element name="mpStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="payer" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerID" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerBusiness" type="xsd:string" minOccurs="0"/> + <xsd:element name="desc" type="xsd:string" minOccurs="0"/> + <xsd:element name="mpMax" type="xsd:string" minOccurs="0"/> + <xsd:element name="paymentSourceID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <!-- PayPalEcSet --> + <xsd:complexType name="PayPalEcSetReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="paypalToken" type="xsd:string" minOccurs="0"/> + <xsd:element name="amount" type="xsd:string" minOccurs="0"/> + <xsd:element name="currency" type="xsd:string" minOccurs="0"/> + <xsd:element name="correlationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="errorCode" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <!-- end of PayPalEcSet --> + <!-- PayPalEcGetDetails --> + <xsd:complexType name="PayPalEcGetDetailsReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="paypalToken" type="xsd:string" minOccurs="0"/> + <xsd:element name="payer" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerId" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerSalutation" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerFirstname" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerMiddlename" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerLastname" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerSuffix" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerCountry" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerBusiness" type="xsd:string" minOccurs="0"/> + <xsd:element name="shipToName" type="xsd:string" minOccurs="0"/> + <xsd:element name="shipToAddress1" type="xsd:string" minOccurs="0"/> + <xsd:element name="shipToAddress2" type="xsd:string" minOccurs="0"/> + <xsd:element name="shipToCity" type="xsd:string" minOccurs="0"/> + <xsd:element name="shipToState" type="xsd:string" minOccurs="0"/> + <xsd:element name="shipToCountry" type="xsd:string" minOccurs="0"/> + <xsd:element name="shipToZip" type="xsd:string" minOccurs="0"/> + <xsd:element name="addressStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerPhone" type="xsd:string" minOccurs="0"/> + <xsd:element name="avsCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="street1" type="xsd:string" minOccurs="0"/> + <xsd:element name="street2" type="xsd:string" minOccurs="0"/> + <xsd:element name="city" type="xsd:string" minOccurs="0"/> + <xsd:element name="state" type="xsd:string" minOccurs="0"/> + <xsd:element name="postalCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="countryCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="countryName" type="xsd:string" minOccurs="0"/> + <xsd:element name="addressID" type="xsd:string" minOccurs="0"/> + <xsd:element name="errorCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="correlationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalBillingAgreementAcceptedStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalTaxAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="item" type="tns:Item" minOccurs="0" maxOccurs="1000" /> + </xsd:sequence> + </xsd:complexType> + <!-- end of PayPalEcGetDetails --> + <!-- PayPalEcDoPayment --> + <xsd:complexType name="PayPalEcDoPaymentReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="paypalToken" type="xsd:string" minOccurs="0"/> + <xsd:element name="transactionId" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalTransactiontype" type="xsd:string" minOccurs="0"/> + <xsd:element name="paymentType" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalOrderTime" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalFeeAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalTaxAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalExchangeRate" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalPaymentStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalPendingReason" type="xsd:string" minOccurs="0"/> + <xsd:element name="orderId" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalReasonCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="amount" type="xsd:string" minOccurs="0"/> + <xsd:element name="currency" type="xsd:string" minOccurs="0"/> + <xsd:element name="correlationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="errorCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalBillingAgreementId" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <!-- end of PayPalEcDoPayment --> + <!-- PayPalDoCapture --> + <xsd:complexType name="PayPalDoCaptureReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="authorizationId" type="xsd:string" minOccurs="0"/> + <xsd:element name="transactionId" type="xsd:string" minOccurs="0"/> + <xsd:element name="parentTransactionId" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalReceiptId" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalTransactiontype" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalPaymentType" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalOrderTime" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalPaymentGrossAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalFeeAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalTaxAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalExchangeRate" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalPaymentStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="amount" type="xsd:string" minOccurs="0"/> + <xsd:element name="currency" type="xsd:string" minOccurs="0"/> + <xsd:element name="correlationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="errorCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalPendingReason" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <!-- end of PayPalDoCapture --> + <!-- PayPalAuthReversal --> + <xsd:complexType name="PayPalAuthReversalReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="authorizationId" type="xsd:string" minOccurs="0"/> + <xsd:element name="correlationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="errorCode" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <!-- end of PayPalAuthReversal --> + <!-- PayPalRefund --> + <xsd:complexType name="PayPalRefundReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="transactionId" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalNetRefundAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalFeeRefundAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalGrossRefundAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="correlationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="errorCode" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <!-- end of PayPalRefund --> + <!-- PayPalEcOrderSetup --> + <xsd:complexType name="PayPalEcOrderSetupReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="paypalToken" type="xsd:string" minOccurs="0"/> + <xsd:element name="transactionId" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalTransactiontype" type="xsd:string" minOccurs="0"/> + <xsd:element name="paymentType" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalOrderTime" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalFeeAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalTaxAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalExchangeRate" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalPaymentStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalPendingReason" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalReasonCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="amount" type="xsd:string" minOccurs="0"/> + <xsd:element name="currency" type="xsd:string" minOccurs="0"/> + <xsd:element name="correlationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="errorCode" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <!-- end of PayPalEcOrderSetup --> + <!-- PayPalAuthorization--> + <xsd:complexType name="PayPalAuthorizationReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="transactionId" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="amount" type="xsd:string" minOccurs="0"/> + <xsd:element name="currency" type="xsd:string" minOccurs="0"/> + <xsd:element name="correlationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="errorCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="protectionEligibility" type="xsd:string" minOccurs="0"/> + <xsd:element name="protectionEligibilityType" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <!-- end of PayPalAuthorization --> + <!-- PayPalUpdateAgreement--> + <xsd:complexType name="PayPalUpdateAgreementReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="paypalBillingAgreementId" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalBillingAgreementDesc" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalBillingAgreementCustom" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalBillingAgreementStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="payer" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerId" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerCountry" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerBusiness" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerSalutation" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerFirstname" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerMiddlename" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerLastname" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerSuffix" type="xsd:string" minOccurs="0"/> + <xsd:element name="addressStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="errorCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="correlationID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <!-- end of PayPalUpdateAgreement--> + <!-- PayPalCreateAgreement--> + <xsd:complexType name="PayPalCreateAgreementReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="paypalBillingAgreementId" type="xsd:string" minOccurs="0"/> + <xsd:element name="errorCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="correlationID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <!-- end of PayPalCreateAgreement--> + <!-- PayPalDoRefTransaction--> + <xsd:complexType name="PayPalDoRefTransactionReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="paypalBillingAgreementId" type="xsd:string" minOccurs="0"/> + <xsd:element name="transactionId" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalTransactionType" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalPaymentType" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalOrderTime" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="currency" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalTaxAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalExchangeRate" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalPaymentStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalPendingReason" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalReasonCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="errorCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="correlationID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <!-- end of PayPalDoRefTransaction--> + <xsd:complexType name="RiskUpdateReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="FraudUpdateReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="CaseManagementActionReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="RuleResultItem"> + <xsd:sequence> + <xsd:element name="name" type="xsd:string" minOccurs="0"/> + <xsd:element name="decision" type="xsd:string" minOccurs="0"/> + <xsd:element name="evaluation" type="xsd:string" minOccurs="0"/> + <xsd:element name="ruleID" type="xsd:integer" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="RuleResultItems"> + <xsd:sequence> + <xsd:element name="ruleResultItem" type="tns:RuleResultItem" minOccurs="0" maxOccurs="1000"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="DecisionReply"> + <xsd:sequence> + <xsd:element name="casePriority" type="xsd:integer" minOccurs="0"/> + <xsd:element name="activeProfileReply" type="tns:ProfileReply" minOccurs="0"/> + <xsd:element name="velocityInfoCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="additionalFields" type="tns:AdditionalFields" minOccurs="0" maxOccurs="1" /> + <xsd:element name="morphingElement" type="tns:MorphingElement" minOccurs="0" maxOccurs="1" /> + <xsd:element name="providerFields" type="tns:ProviderFields" minOccurs="0" maxOccurs="1" /> + <xsd:element name="travel" type="tns:Travel" minOccurs="0" maxOccurs="1" /> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="ProviderFields"> + <xsd:sequence> + <xsd:element name="provider" type="tns:Provider" minOccurs="0" maxOccurs="30"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="Provider"> + <xsd:sequence> + <xsd:element name="name" type="xsd:string" minOccurs="1" maxOccurs="1"/> + <xsd:element name="field" type="tns:ProviderField" minOccurs="0" maxOccurs="500"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="ProviderField"> + <xsd:sequence> + <xsd:element name="name" type="xsd:string" minOccurs="1" maxOccurs="1"/> + <xsd:element name="value" type="xsd:string" minOccurs="1" maxOccurs="1"/> + </xsd:sequence> + </xsd:complexType> + <!-- DME --> + <xsd:complexType name="AdditionalFields"> + <xsd:sequence> + <xsd:element name="field" type="tns:Field" minOccurs="0" maxOccurs="3000"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="Field"> + <xsd:sequence> + <xsd:element name="provider" type="xsd:string" minOccurs="1" maxOccurs="1"/> + <xsd:element name="name" type="xsd:string" minOccurs="1" maxOccurs="1"/> + <xsd:element name="value" type="xsd:string" minOccurs="1" maxOccurs="1"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="MorphingElement"> + <xsd:sequence> + <xsd:element name="element" type="tns:Element" minOccurs="0" maxOccurs="1000"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="Element"> + <xsd:sequence> + <xsd:element name="infoCode" type="xsd:string" minOccurs="1" maxOccurs="1"/> + <xsd:element name="fieldName" type="xsd:string" minOccurs="1" maxOccurs="1"/> + <xsd:element name="count" type="xsd:integer" minOccurs="1" maxOccurs="1"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="Travel"> + <xsd:sequence> + <xsd:element name="actualFinalDestinationCountry" type="tns:RestrictedString" minOccurs="0" maxOccurs="1"/> + <xsd:element name="actualFinalDestinationCity" type="tns:RestrictedString" minOccurs="0" maxOccurs="1"/> + <xsd:element name="actualFinalDestinationLatitude" type="tns:RestrictedDecimal" minOccurs="0" maxOccurs="1"/> + <xsd:element name="actualFinalDestinationLongitude" type="tns:RestrictedDecimal" minOccurs="0" maxOccurs="1"/> + <xsd:element name="firstDepartureCountry" type="tns:RestrictedString" minOccurs="0" maxOccurs="1"/> + <xsd:element name="firstDepartureCity" type="tns:RestrictedString" minOccurs="0" maxOccurs="1"/> + <xsd:element name="firstDepartureLatitude" type="tns:RestrictedDecimal" minOccurs="0" maxOccurs="1"/> + <xsd:element name="firstDepartureLongitude" type="tns:RestrictedDecimal" minOccurs="0" maxOccurs="1"/> + <xsd:element name="firstDestinationCountry" type="tns:RestrictedString" minOccurs="0" maxOccurs="1"/> + <xsd:element name="firstDestinationCity" type="tns:RestrictedString" minOccurs="0" maxOccurs="1"/> + <xsd:element name="firstDestinationLatitude" type="tns:RestrictedDecimal" minOccurs="0" maxOccurs="1"/> + <xsd:element name="firstDestinationLongitude" type="tns:RestrictedDecimal" minOccurs="0" maxOccurs="1"/> + <xsd:element name="lastDestinationCountry" type="tns:RestrictedString" minOccurs="0" maxOccurs="1"/> + <xsd:element name="lastDestinationCity" type="tns:RestrictedString" minOccurs="0" maxOccurs="1"/> + <xsd:element name="lastDestinationLatitude" type="tns:RestrictedDecimal" minOccurs="0" maxOccurs="1"/> + <xsd:element name="lastDestinationLongitude" type="tns:RestrictedDecimal" minOccurs="0" maxOccurs="1"/> + </xsd:sequence> + </xsd:complexType> + <xsd:simpleType name="RestrictedString"> + <xsd:restriction base="xsd:string"> + <xsd:maxLength value="90"/> + </xsd:restriction> + </xsd:simpleType> + <xsd:simpleType name="RestrictedDecimal"> + <xsd:restriction base="xsd:decimal"> + <xsd:totalDigits value="9"/> + <xsd:fractionDigits value="6"/> + </xsd:restriction> + </xsd:simpleType> + <xsd:complexType name="DMEReply"> + <xsd:sequence> + <xsd:element name="eventType" type="xsd:string" minOccurs="0"/> + <xsd:element name="eventInfo" type="xsd:string" minOccurs="0"/> + <xsd:element name="eventHotlistInfo" type="xsd:string" minOccurs="0"/> + <xsd:element name="eventPolicy" type="xsd:string" minOccurs="0"/> + <xsd:element name="eventVelocityInfoCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="additionalFields" type="tns:AdditionalFields" minOccurs="0" maxOccurs="1" /> + <xsd:element name="morphingElement" type="tns:MorphingElement" minOccurs="0" maxOccurs="1" /> + <xsd:element name="cardBin" type="xsd:string" minOccurs="0"/> + <xsd:element name="binCountry" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardAccountType" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardScheme" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardIssuer" type="xsd:string" minOccurs="0"/> + <xsd:element name="providerFields" type="tns:ProviderFields" minOccurs="0" maxOccurs="1"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="ProfileReply"> + <xsd:sequence> + <xsd:element name="selectedBy" type="xsd:string" minOccurs="0"/> + <xsd:element name="name" type="xsd:string" minOccurs="0"/> + <xsd:element name="destinationQueue" type="xsd:string" minOccurs="0"/> + <xsd:element name="profileScore" type="xsd:string" minOccurs="0"/> + <xsd:element name="rulesTriggered" type="tns:RuleResultItems" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="CCDCCReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="dccSupported" type="tns:boolean" minOccurs="0"/> + <xsd:element name="validHours" type="xsd:string" minOccurs="0"/> + <xsd:element name="marginRatePercentage" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="CCDCCUpdateReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="ChinaPaymentReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="requestDateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="currency" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="formData" type="xsd:string" minOccurs="0"/> + <xsd:element name="verifyFailure" type="xsd:string" minOccurs="0"/> + <xsd:element name="verifyInProcess" type="xsd:string" minOccurs="0"/> + <xsd:element name="verifySuccess" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="ChinaRefundReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="requestDateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="currency" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="BoletoPaymentReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="requestDateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="boletoNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="expirationDate" type="xsd:string" minOccurs="0"/> + <xsd:element name="url" type="xsd:string" minOccurs="0"/> + <xsd:element name="avsCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="avsCodeRaw" type="xsd:string" minOccurs="0"/> + <xsd:element name="barCodeNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="assignor" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="APInitiateReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="merchantURL" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="dateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="signature" type="xsd:string" minOccurs="0"/> + <xsd:element name="publicKey" type="xsd:string" minOccurs="0"/> + <xsd:element name="paymentStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="responseCode" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="APCheckStatusReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="paymentStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorTradeNo" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorTransactionID" type="xsd:string" minOccurs="0"/> + <xsd:element name="ibanSuffix" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="dateTime" type="tns:dateTime" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <!-- Vme Reseller Reply--> + + <xsd:complexType name="SellerProtection"> + <xsd:sequence> + <xsd:element name="eligibility" type="xsd:string" minOccurs="0" /> + <xsd:element name="type" type="xsd:string" minOccurs="0" /> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="APReply"> + <xsd:sequence> + <xsd:element name="orderID" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardGroup" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardType" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardNumberSuffix" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardExpirationMonth" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardExpirationYear" type="xsd:string" minOccurs="0"/> + <xsd:element name="avsCodeRaw" type="xsd:string" minOccurs="0"/> + <xsd:element name="purchaseID" type="xsd:string" minOccurs="0"/> + <xsd:element name="productID" type="xsd:string" minOccurs="0"/> + <xsd:element name="productDescription" type="xsd:string" minOccurs="0"/> + <xsd:element name="shippingAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="handlingAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="shippingHandlingAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="additionalAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="taxAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="subtotalAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="totalPurchaseAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="giftWrapAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="discountAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardNumberPrefix" type="xsd:string" minOccurs="0"/> + <xsd:element name="riskIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="merchantUUID" type="xsd:string" minOccurs="0"/> + <xsd:element name="merchantSiteID" type="xsd:string" minOccurs="0"/> + <xsd:element name="transactionExpirationDate" type="xsd:string" minOccurs="0"/> + <xsd:element name="sellerProtection" type="tns:SellerProtection" minOccurs="0" /> + <xsd:element name="processorFraudDecision" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorFraudDecisionReason" type="xsd:string" minOccurs="0"/> + <xsd:element name="customerID" type="xsd:string" minOccurs="0" /> + <xsd:element name="billingAgreementID" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerID" type="xsd:string" minOccurs="0"/> + <xsd:element name="fundingSource" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <!-- AP Auth Service --> + <xsd:complexType name="APAuthReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="transactionID" type="xsd:string" minOccurs="0"/> + <xsd:element name="status" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="dateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="providerResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="paymentStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="responseCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="authorizationCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="merchantURL" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorTransactionID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <!-- End of AP Auth Service --> + <!-- AP Auth Reversal Service --> + <xsd:complexType name="APAuthReversalReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="transactionID" type="xsd:string" minOccurs="0"/> + <xsd:element name="status" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="dateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="providerResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="paymentStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="responseCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorTransactionID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <!-- End of AP Auth Reversal Service --> + <!-- AP Capture Service --> + <xsd:complexType name="APCaptureReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="processorTransactionID" type="xsd:string" minOccurs="0"/> + <xsd:element name="status" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="dateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="providerResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="paymentStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="responseCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorTransactionFee" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <!-- End of AP Capture Service --> + <!-- AP Options Service --> + <xsd:complexType name="APOptionsReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="responseCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="offset" type="xsd:string" minOccurs="0"/> + <xsd:element name="count" type="xsd:string" minOccurs="0"/> + <xsd:element name="totalCount" type="xsd:string" minOccurs="0"/> + <xsd:element name="option" type="tns:APOptionsOption" minOccurs="0" maxOccurs="250"/> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="APOptionsOption"> + <xsd:sequence> + <xsd:element name="id" type="xsd:string" minOccurs="0" /> + <xsd:element name="name" type="xsd:string" minOccurs="0" /> + </xsd:sequence> + <xsd:attribute name="data" type="xsd:integer" use="optional"/> + </xsd:complexType> + + + <!-- End of Options Service --> + <!-- AP Refund Service --> + <xsd:complexType name="APRefundReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="transactionID" type="xsd:string" minOccurs="0"/> + <xsd:element name="status" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="dateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="returnRef" type="xsd:string" minOccurs="0"/> + <xsd:element name="providerResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorTransactionID" type="xsd:string" minOccurs="0"/> + <xsd:element name="paymentStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="responseCode" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <!-- End of AP Refund Service --> + <!-- AP Sale Service --> + <xsd:complexType name="APSaleReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="paymentStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="responseCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="merchantURL" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorTransactionID" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorTransactionFee" type="xsd:string" minOccurs="0"/> + <xsd:element name="amount" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="exchangeRate" type="xsd:string" minOccurs="0"/> + <xsd:element name="foreignCurrency" type="xsd:string" minOccurs="0"/> + <xsd:element name="foreignAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="discountAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="dateTime" type="tns:dateTime" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <!-- End of AP Sale Service --> + + <!-- AP CheckOutDetailsReply Service --> + <xsd:complexType name="APCheckOutDetailsReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="status" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="dateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="providerResponse" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <!-- End of AP CheckOutDetailsReply Service --> + <xsd:complexType name="APTransactionDetailsReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="transactionID" type="xsd:string" minOccurs="0"/> + <xsd:element name="status" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="dateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="providerResponse" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <!-- AP ConfirmPurchase Service --> + <xsd:complexType name="APConfirmPurchaseReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="status" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="amount" type="tns:amount" minOccurs="0"/> + <xsd:element name="dateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="providerResponse" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <!-- End of AP ConfirmPurchase Service --> + <xsd:complexType name="APSessionsReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="responseCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="merchantURL" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorToken" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorTransactionID" type="xsd:string" minOccurs="0"/> + <xsd:element name="amount" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="status" type="xsd:string" minOccurs="0"/> + <xsd:element name="dateTime" type="tns:dateTime" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="CCCheckStatusReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="paymentStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="authorizationCode" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="ReplyMessage"> + <xsd:sequence> + <xsd:element name="merchantReferenceCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="requestID" type="xsd:string"/> + <xsd:element name="decision" type="xsd:string"/> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="missingField" type="xsd:string" minOccurs="0" maxOccurs="1000"/> + <xsd:element name="invalidField" type="xsd:string" minOccurs="0" maxOccurs="1000"/> + <xsd:element name="requestToken" type="xsd:string"/> + <xsd:element name="purchaseTotals" type="tns:PurchaseTotals" minOccurs="0"/> + <xsd:element name="deniedPartiesMatch" type="tns:DeniedPartiesMatch" minOccurs="0" maxOccurs="100"/> + <xsd:element name="ccAuthReply" type="tns:CCAuthReply" minOccurs="0"/> + <xsd:element name="octReply" type="tns:OCTReply" minOccurs="0"/> + <xsd:element name="verificationReply" type="tns:VerificationReply" minOccurs="0"/> + <xsd:element name="ccSaleReply" type="tns:CCSaleReply" minOccurs="0"/> + <xsd:element name="ccSaleCreditReply" type="tns:CCSaleCreditReply" minOccurs="0"/> + <xsd:element name="ccSaleReversalReply" type="tns:CCSaleReversalReply" minOccurs="0"/> + <xsd:element name="ccIncrementalAuthReply" type="tns:CCIncrementalAuthReply" minOccurs="0"/> + <xsd:element name="serviceFeeCalculateReply" type="tns:ServiceFeeCalculateReply" minOccurs="0"/> + <xsd:element name="ccCaptureReply" type="tns:CCCaptureReply" minOccurs="0"/> + <xsd:element name="ccCreditReply" type="tns:CCCreditReply" minOccurs="0"/> + <xsd:element name="ccAuthReversalReply" type="tns:CCAuthReversalReply" minOccurs="0"/> + <xsd:element name="ccAutoAuthReversalReply" type="tns:CCAutoAuthReversalReply" minOccurs="0"/> + <xsd:element name="ccDCCReply" type="tns:CCDCCReply" minOccurs="0"/> + <xsd:element name="ccDCCUpdateReply" type="tns:CCDCCUpdateReply" minOccurs="0"/> + <xsd:element name="ecDebitReply" type="tns:ECDebitReply" minOccurs="0"/> + <xsd:element name="ecCreditReply" type="tns:ECCreditReply" minOccurs="0"/> + <xsd:element name="ecAuthenticateReply" type="tns:ECAuthenticateReply" minOccurs="0"/> + <xsd:element name="payerAuthEnrollReply" type="tns:PayerAuthEnrollReply" minOccurs="0"/> + <xsd:element name="payerAuthValidateReply" type="tns:PayerAuthValidateReply" minOccurs="0"/> + <xsd:element name="taxReply" type="tns:TaxReply" minOccurs="0"/> + <xsd:element name="encryptedPayment" type="tns:EncryptedPayment" minOccurs="0" /> + <xsd:element name="encryptPaymentDataReply" type="tns:EncryptPaymentDataReply" minOccurs="0"/> + <xsd:element name="dmeReply" type="tns:DMEReply" minOccurs="0"/> + <xsd:element name="afsReply" type="tns:AFSReply" minOccurs="0"/> + <xsd:element name="davReply" type="tns:DAVReply" minOccurs="0"/> + <xsd:element name="exportReply" type="tns:ExportReply" minOccurs="0"/> + <xsd:element name="fxRatesReply" type="tns:FXRatesReply" minOccurs="0"/> + <xsd:element name="bankTransferReply" type="tns:BankTransferReply" minOccurs="0"/> + <xsd:element name="bankTransferRefundReply" type="tns:BankTransferRefundReply" minOccurs="0"/> + <xsd:element name="bankTransferRealTimeReply" type="tns:BankTransferRealTimeReply" minOccurs="0"/> + <xsd:element name="directDebitMandateReply" type="tns:DirectDebitMandateReply" minOccurs="0"/> + <xsd:element name="directDebitReply" type="tns:DirectDebitReply" minOccurs="0"/> + <xsd:element name="directDebitValidateReply" type="tns:DirectDebitValidateReply" minOccurs="0"/> + <xsd:element name="directDebitRefundReply" type="tns:DirectDebitRefundReply" minOccurs="0"/> + <xsd:element name="paySubscriptionCreateReply" type="tns:PaySubscriptionCreateReply" minOccurs="0"/> + <xsd:element name="paySubscriptionUpdateReply" type="tns:PaySubscriptionUpdateReply" minOccurs="0"/> + <xsd:element name="paySubscriptionEventUpdateReply" type="tns:PaySubscriptionEventUpdateReply" minOccurs="0"/> + <xsd:element name="paySubscriptionRetrieveReply" type="tns:PaySubscriptionRetrieveReply" minOccurs="0"/> + <xsd:element name="paySubscriptionDeleteReply" type="tns:PaySubscriptionDeleteReply" minOccurs="0"/> + <xsd:element name="payPalPaymentReply" type="tns:PayPalPaymentReply" minOccurs="0"/> + <xsd:element name="payPalCreditReply" type="tns:PayPalCreditReply" minOccurs="0"/> + <xsd:element name="voidReply" type="tns:VoidReply" minOccurs="0"/> + <xsd:element name="pinlessDebitReply" type="tns:PinlessDebitReply" minOccurs="0"/> + <xsd:element name="pinlessDebitValidateReply" type="tns:PinlessDebitValidateReply" minOccurs="0"/> + <xsd:element name="pinlessDebitReversalReply" type="tns:PinlessDebitReversalReply" minOccurs="0"/> + <xsd:element name="payPalButtonCreateReply" type="tns:PayPalButtonCreateReply" minOccurs="0"/> + <xsd:element name="payPalPreapprovedPaymentReply" type="tns:PayPalPreapprovedPaymentReply" minOccurs="0"/> + <xsd:element name="payPalPreapprovedUpdateReply" type="tns:PayPalPreapprovedUpdateReply" minOccurs="0"/> + <xsd:element name="riskUpdateReply" type="tns:RiskUpdateReply" minOccurs="0"/> + <xsd:element name="fraudUpdateReply" type="tns:FraudUpdateReply" minOccurs="0"/> + <xsd:element name="caseManagementActionReply" type="tns:CaseManagementActionReply" minOccurs="0"/> + <xsd:element name="decisionReply" type="tns:DecisionReply" minOccurs="0"/> + <xsd:element name="payPalRefundReply" type="tns:PayPalRefundReply" minOccurs="0"/> + <xsd:element name="payPalAuthReversalReply" type="tns:PayPalAuthReversalReply" minOccurs="0"/> + <xsd:element name="payPalDoCaptureReply" type="tns:PayPalDoCaptureReply" minOccurs="0"/> + <xsd:element name="payPalEcDoPaymentReply" type="tns:PayPalEcDoPaymentReply" minOccurs="0"/> + <xsd:element name="payPalEcGetDetailsReply" type="tns:PayPalEcGetDetailsReply" minOccurs="0"/> + <xsd:element name="payPalEcSetReply" type="tns:PayPalEcSetReply" minOccurs="0"/> + <xsd:element name="payPalAuthorizationReply" type="tns:PayPalAuthorizationReply" minOccurs="0"/> + <xsd:element name="payPalEcOrderSetupReply" type="tns:PayPalEcOrderSetupReply" minOccurs="0"/> + <xsd:element name="payPalUpdateAgreementReply" type="tns:PayPalUpdateAgreementReply" minOccurs="0"/> + <xsd:element name="payPalCreateAgreementReply" type="tns:PayPalCreateAgreementReply" minOccurs="0"/> + <xsd:element name="payPalDoRefTransactionReply" type="tns:PayPalDoRefTransactionReply" minOccurs="0"/> + <xsd:element name="chinaPaymentReply" type="tns:ChinaPaymentReply" minOccurs="0"/> + <xsd:element name="chinaRefundReply" type="tns:ChinaRefundReply" minOccurs="0"/> + <xsd:element name="boletoPaymentReply" type="tns:BoletoPaymentReply" minOccurs="0"/> + <xsd:element name="pinDebitPurchaseReply" type="tns:PinDebitPurchaseReply" minOccurs="0"/> + <xsd:element name="pinDebitCreditReply" type="tns:PinDebitCreditReply" minOccurs="0"/> + <xsd:element name="pinDebitReversalReply" type="tns:PinDebitReversalReply" minOccurs="0"/> + <xsd:element name="apInitiateReply" type="tns:APInitiateReply" minOccurs="0"/> + <xsd:element name="apCheckStatusReply" type="tns:APCheckStatusReply" minOccurs="0"/> + <xsd:element name="receiptNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="additionalData" type="xsd:string" minOccurs="0"/> + <xsd:element name="solutionProviderTransactionID" type="xsd:string" minOccurs="0"/> + <xsd:element name="apReply" type="tns:APReply" minOccurs="0"/> + <xsd:element name="shipTo" type="tns:ShipTo" minOccurs="0" /> + <xsd:element name="billTo" type="tns:BillTo" minOccurs="0" /> + <xsd:element name="apAuthReply" type="tns:APAuthReply" minOccurs="0"/> + <xsd:element name="apSessionsReply" type="tns:APSessionsReply" minOccurs="0" /> + <xsd:element name="apAuthReversalReply" type="tns:APAuthReversalReply" minOccurs="0"/> + <xsd:element name="apCaptureReply" type="tns:APCaptureReply" minOccurs="0"/> + <xsd:element name="apOptionsReply" type="tns:APOptionsReply" minOccurs="0"/> + <xsd:element name="apRefundReply" type="tns:APRefundReply" minOccurs="0"/> + <xsd:element name="apSaleReply" type="tns:APSaleReply" minOccurs="0"/> + <xsd:element name="apCheckoutDetailsReply" type="tns:APCheckOutDetailsReply" minOccurs="0"/> + <xsd:element name="apTransactionDetailsReply" type="tns:APTransactionDetailsReply" minOccurs="0"/> + <xsd:element name="apConfirmPurchaseReply" type="tns:APConfirmPurchaseReply" minOccurs="0"/> + <xsd:element name="promotion" type="tns:Promotion" minOccurs="0"/> + <xsd:element name="promotionGroup" type="tns:PromotionGroupReply" minOccurs="0" maxOccurs="100"/> + <xsd:element name="payPalGetTxnDetailsReply" type="tns:PayPalGetTxnDetailsReply" minOccurs="0"/> + <xsd:element name="payPalTransactionSearchReply" type="tns:PayPalTransactionSearchReply" minOccurs="0"/> + <xsd:element name="emvReply" type="tns:EmvReply" minOccurs="0" /> + <xsd:element name="originalTransaction" type="tns:OriginalTransaction" minOccurs="0" /> + <xsd:element name="hostedDataCreateReply" type="tns:HostedDataCreateReply" minOccurs="0" /> + <xsd:element name="hostedDataRetrieveReply" type="tns:HostedDataRetrieveReply" minOccurs="0" /> + <xsd:element name="salesSlipNumber" type="xsd:string" minOccurs="0" /> + <xsd:element name="additionalProcessorResponse" type="xsd:string" minOccurs="0" /> + <xsd:element name="jpo" type="tns:JPO" minOccurs="0" /> + <xsd:element name="card" type="tns:Card" minOccurs="0" /> + <xsd:element name="paymentNetworkToken" type="tns:PaymentNetworkToken" minOccurs="0"/> + <xsd:element name="vcReply" type="tns:VCReply" minOccurs="0" /> + <xsd:element name="decryptVisaCheckoutDataReply" type="tns:DecryptVisaCheckoutDataReply" minOccurs="0"/> + <xsd:element name="getVisaCheckoutDataReply" type="tns:GetVisaCheckoutDataReply" minOccurs="0"/> + <xsd:element name="binLookupReply" type="tns:BinLookupReply" minOccurs="0"/> + <xsd:element name="issuerMessage" type="xsd:string" minOccurs="0" /> + <xsd:element name="token" type="tns:Token" minOccurs="0" /> + <xsd:element name="issuer" type="tns:issuer" minOccurs="0" /> + <xsd:element name="recipient" type="tns:Recipient" minOccurs="0"/> + <xsd:element name="feeProgramIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="installment" type="tns:Installment" minOccurs="0" /> + <xsd:element name="paymentAccountReference" type="xsd:string" minOccurs="0"/> + <xsd:element name="authIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="ucaf" type="tns:UCAF" minOccurs="0"/> + <xsd:element name="network" type="tns:Network" minOccurs="0" maxOccurs="100"/> + <xsd:element name="invoiceHeader" type="tns:InvoiceHeader" minOccurs="0" /> + <xsd:element name="apOrderReply" type="tns:APOrderReply" minOccurs="0" /> + <xsd:element name="apCancelReply" type="tns:APCancelReply" minOccurs="0" /> + <xsd:element name="apBillingAgreementReply" type="tns:APBillingAgreementReply" minOccurs="0" /> + <xsd:element name="customerVerificationStatus" type="xsd:string" minOccurs="0" /> + <xsd:element name="personalID" type="tns:PersonalID" minOccurs="0" /> + <xsd:element name="acquirerMerchantNumber" type="xsd:string" minOccurs="0" /> + <xsd:element name="pos" type="tns:Pos" minOccurs="0" /> + <xsd:element name="issuerMessageAction" type="xsd:string" minOccurs="0" /> + <xsd:element name="customerID" type="xsd:string" minOccurs="0" /> + + <xsd:element name="routing" type="tns:Routing" minOccurs="0"/> + <xsd:element name="transactionLocalDateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="apCreateMandateReply" type="tns:APCreateMandateReply" minOccurs="0"/> + <xsd:element name="apMandateStatusReply" type="tns:APMandateStatusReply" minOccurs="0"/> + <xsd:element name="apUpdateMandateReply" type="tns:APUpdateMandateReply" minOccurs="0"/> + <xsd:element name="apImportMandateReply" type="tns:APImportMandateReply" minOccurs="0"/> + <xsd:element name="apRevokeMandateReply" type="tns:APRevokeMandateReply" minOccurs="0"/> + <xsd:element name="getMasterpassDataReply" type="tns:GetMasterpassDataReply" minOccurs="0"/> + <xsd:element name="paymentNetworkMerchantID" type="xsd:string" minOccurs="0"/> + <xsd:element name="wallet" type="tns:Wallet" minOccurs="0" /> + <xsd:element name="cashbackAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="giftCard" type="tns:GiftCard" minOccurs="0" /> + <xsd:element name="giftCardActivationReply" type="tns:GiftCardActivationReply" minOccurs="0"/> + <xsd:element name="giftCardBalanceInquiryReply" type="tns:GiftCardBalanceInquiryReply" minOccurs="0"/> + <xsd:element name="giftCardRedemptionReply" type="tns:GiftCardRedemptionReply" minOccurs="0"/> + <xsd:element name="giftCardVoidReply" type="tns:GiftCardVoidReply" minOccurs="0"/> + <xsd:element name="giftCardReversalReply" type="tns:GiftCardReversalReply" minOccurs="0"/> + <xsd:element name="ccCheckStatusReply" type="tns:CCCheckStatusReply" minOccurs="0"/> + <xsd:element name="ecAVSReply" type="tns:ECAVSReply" minOccurs="0"/> + <xsd:element name="reserved" type="tns:ReplyReserved" minOccurs="0"/> + + <!--ReplyReserved should always be the last element in the xsd, new elements should be added before this--> + </xsd:sequence> + </xsd:complexType> + <xsd:element name="requestMessage" type="tns:RequestMessage"> + </xsd:element> + <xsd:element name="replyMessage" type="tns:ReplyMessage"> + <xsd:unique name="unique-tax-item-id"> + <xsd:selector xpath="tns:taxReplyItem"/> + <xsd:field xpath="@id"/> + </xsd:unique> + </xsd:element> + <xsd:element name="nvpRequest" type="xsd:string"/> + <xsd:element name="nvpReply" type="xsd:string"/> + <!-- used in SOAP faults --> + <xsd:complexType name="FaultDetails"> + <xsd:sequence> + <xsd:element name="requestID" type="xsd:string"/> + </xsd:sequence> + </xsd:complexType> + <xsd:element name="faultDetails" type="tns:FaultDetails"/> + <xsd:complexType name="AirlineData"> + <xsd:sequence> + <xsd:element name="agentCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="agentName" type="xsd:string" minOccurs="0"/> + <xsd:element name="ticketIssuerCity" type="xsd:string" minOccurs="0"/> + <xsd:element name="ticketIssuerState" type="xsd:string" minOccurs="0"/> + <xsd:element name="ticketIssuerPostalCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="ticketIssuerCountry" type="xsd:string" minOccurs="0"/> + <xsd:element name="ticketIssuerAddress" type="xsd:string" minOccurs="0"/> + <xsd:element name="ticketIssuerCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="ticketIssuerName" type="xsd:string" minOccurs="0"/> + <xsd:element name="ticketNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="checkDigit" type="xsd:integer" minOccurs="0"/> + <xsd:element name="restrictedTicketIndicator" type="xsd:integer" minOccurs="0"/> + <xsd:element name="transactionType" type="xsd:string" minOccurs="0"/> + <xsd:element name="extendedPaymentCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="carrierName" type="xsd:string" minOccurs="0"/> + <xsd:element name="passengerName" type="xsd:string" minOccurs="0"/> + <xsd:element name="passenger" type="tns:Passenger" minOccurs="0" maxOccurs="1000"/> + <xsd:element name="customerCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="documentType" type="xsd:string" minOccurs="0"/> + <xsd:element name="documentNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="documentNumberOfParts" type="xsd:string" minOccurs="0"/> + <xsd:element name="invoiceNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="invoiceDate" type="xsd:string" minOccurs="0"/> + <xsd:element name="chargeDetails" type="xsd:string" minOccurs="0"/> + <xsd:element name="bookingReference" type="xsd:string" minOccurs="0"/> + <xsd:element name="totalFee" type="tns:amount" minOccurs="0"/> + <xsd:element name="clearingSequence" type="xsd:string" minOccurs="0"/> + <xsd:element name="clearingCount" type="xsd:integer" minOccurs="0"/> + <xsd:element name="totalClearingAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="leg" type="tns:Leg" minOccurs="0" maxOccurs="1000"/> + <xsd:element name="numberOfPassengers" type="xsd:string" minOccurs="0"/> + <xsd:element name="reservationSystem" type="xsd:string" minOccurs="0"/> + <xsd:element name="processIdentifier" type="xsd:string" minOccurs="0"/> + <xsd:element name="iataNumericCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="ticketIssueDate" type="xsd:string" minOccurs="0"/> + <xsd:element name="electronicTicket" type="xsd:string" minOccurs="0"/> + <xsd:element name="originalTicketNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="purchaseType" type="xsd:string" minOccurs="0"/> + <xsd:element name="creditReasonIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="ticketUpdateIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="planNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="arrivalDate" type="xsd:string" minOccurs="0"/> + <xsd:element name="ticketRestrictionText" type="xsd:string" minOccurs="0"/> + <xsd:element name="exchangeTicketAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="exchangeTicketFee" type="tns:amount" minOccurs="0"/> + <xsd:element name="journeyType" type="xsd:string" minOccurs="0"/> + <xsd:element name="boardingFee" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="Leg"> + <xsd:sequence> + <xsd:element name="carrierCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="flightNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="originatingAirportCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="class" type="xsd:string" minOccurs="0"/> + <xsd:element name="stopoverCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="departureDate" type="xsd:string" minOccurs="0"/> + <xsd:element name="destination" type="xsd:string" minOccurs="0"/> + <xsd:element name="fareBasis" type="xsd:string" minOccurs="0"/> + <xsd:element name="departTax" type="xsd:string" minOccurs="0"/> + <xsd:element name="conjunctionTicket" type="xsd:string" minOccurs="0"/> + <xsd:element name="exchangeTicket" type="xsd:string" minOccurs="0"/> + <xsd:element name="couponNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="departureTime" type="xsd:string" minOccurs="0"/> + <xsd:element name="departureTimeSegment" type="xsd:string" minOccurs="0"/> + <xsd:element name="arrivalTime" type="xsd:string" minOccurs="0"/> + <xsd:element name="arrivalTimeSegment" type="xsd:string" minOccurs="0"/> + <xsd:element name="endorsementsRestrictions" type="xsd:string" minOccurs="0"/> + <xsd:element name="fare" type="xsd:string" minOccurs="0"/> + <xsd:element name="fee" type="xsd:string" minOccurs="0"/> + <xsd:element name="tax" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="id" type="xsd:integer" use="required"/> + </xsd:complexType> + <xsd:complexType name="AncillaryData"> + <xsd:sequence> + <xsd:element name="ticketNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="passengerName" type="xsd:string" minOccurs="0"/> + <xsd:element name="connectedTicketNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="creditReasonIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="service" type="tns:Service" minOccurs="0" maxOccurs="1000"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="Service"> + <xsd:sequence> + <xsd:element name="categoryCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="subcategoryCode" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="id" type="xsd:integer" use="required"/> + </xsd:complexType> + <xsd:complexType name="LodgingData"> + <xsd:sequence> + <xsd:element name="checkInDate" type="xsd:string" minOccurs="0"/> + <xsd:element name="checkOutDate" type="xsd:string" minOccurs="0"/> + <xsd:element name="dailyRoomRate1" type="tns:amount" minOccurs="0"/> + <xsd:element name="dailyRoomRate2" type="tns:amount" minOccurs="0"/> + <xsd:element name="dailyRoomRate3" type="tns:amount" minOccurs="0"/> + <xsd:element name="roomNights1" type="xsd:integer" minOccurs="0"/> + <xsd:element name="roomNights2" type="xsd:integer" minOccurs="0"/> + <xsd:element name="roomNights3" type="xsd:integer" minOccurs="0"/> + <xsd:element name="guestSmokingPreference" type="xsd:string" minOccurs="0"/> + <xsd:element name="numberOfRoomsBooked" type="xsd:integer" minOccurs="0"/> + <xsd:element name="numberOfGuests" type="xsd:integer" minOccurs="0"/> + <xsd:element name="roomBedType" type="xsd:string" minOccurs="0"/> + <xsd:element name="roomTaxElements" type="xsd:string" minOccurs="0"/> + <xsd:element name="roomRateType" type="xsd:string" minOccurs="0"/> + <xsd:element name="guestName" type="xsd:string" minOccurs="0"/> + <xsd:element name="customerServicePhoneNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="corporateClientCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="promotionalCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="additionalCoupon" type="xsd:string" minOccurs="0"/> + <xsd:element name="roomLocation" type="xsd:string" minOccurs="0"/> + <xsd:element name="specialProgramCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="tax" type="tns:amount" minOccurs="0"/> + <xsd:element name="prepaidCost" type="tns:amount" minOccurs="0"/> + <xsd:element name="foodAndBeverageCost" type="tns:amount" minOccurs="0"/> + <xsd:element name="roomTax" type="tns:amount" minOccurs="0"/> + <xsd:element name="adjustmentAmount" type="tns:amount" minOccurs="0"/> + <xsd:element name="phoneCost" type="tns:amount" minOccurs="0"/> + <xsd:element name="restaurantCost" type="tns:amount" minOccurs="0"/> + <xsd:element name="roomServiceCost" type="tns:amount" minOccurs="0"/> + <xsd:element name="miniBarCost" type="tns:amount" minOccurs="0"/> + <xsd:element name="laundryCost" type="tns:amount" minOccurs="0"/> + <xsd:element name="miscellaneousCost" type="tns:amount" minOccurs="0"/> + <xsd:element name="giftShopCost" type="tns:amount" minOccurs="0"/> + <xsd:element name="movieCost" type="tns:amount" minOccurs="0"/> + <xsd:element name="healthClubCost" type="tns:amount" minOccurs="0"/> + <xsd:element name="valetParkingCost" type="tns:amount" minOccurs="0"/> + <xsd:element name="cashDisbursementCost" type="tns:amount" minOccurs="0"/> + <xsd:element name="nonRoomCost" type="tns:amount" minOccurs="0"/> + <xsd:element name="businessCenterCost" type="tns:amount" minOccurs="0"/> + <xsd:element name="loungeBarCost" type="tns:amount" minOccurs="0"/> + <xsd:element name="transportationCost" type="tns:amount" minOccurs="0"/> + <xsd:element name="gratuityCost" type="tns:amount" minOccurs="0"/> + <xsd:element name="conferenceRoomCost" type="tns:amount" minOccurs="0"/> + <xsd:element name="audioVisualCost" type="tns:amount" minOccurs="0"/> + <xsd:element name="banquetCost" type="tns:amount" minOccurs="0"/> + <xsd:element name="internetAccessCost" type="tns:amount" minOccurs="0"/> + <xsd:element name="earlyCheckOutCost" type="tns:amount" minOccurs="0"/> + <xsd:element name="nonRoomTax" type="tns:amount" minOccurs="0"/> + <xsd:element name="travelAgencyCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="travelAgencyName" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="Pos"> + <xsd:sequence> + <xsd:element name="entryMode" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardPresent" type="xsd:string" minOccurs="0"/> + <xsd:element name="terminalCapability" type="xsd:string" minOccurs="0"/> + <xsd:element name="trackData" type="xsd:string" minOccurs="0"/> + <xsd:element name="terminalID" type="xsd:string" minOccurs="0"/> + <xsd:element name="terminalType" type="xsd:string" minOccurs="0"/> + <xsd:element name="terminalLocation" type="xsd:string" minOccurs="0"/> + <xsd:element name="transactionSecurity" type="xsd:string" minOccurs="0"/> + <xsd:element name="catLevel" type="xsd:string" minOccurs="0"/> + <xsd:element name="conditionCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="environment" type="xsd:string" minOccurs="0"/> + <xsd:element name="paymentData" type="xsd:string" minOccurs="0"/> + <xsd:element name="deviceReaderData" type="xsd:string" minOccurs="0"/> + <xsd:element name="encryptionAlgorithm" type="xsd:string" minOccurs="0"/> + <xsd:element name="encodingMethod" type="xsd:string" minOccurs="0"/> + <xsd:element name="deviceID" type="xsd:string" minOccurs="0"/> + <xsd:element name="serviceCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="terminalIDAlternate" type="xsd:string" minOccurs="0" /> + <xsd:element name="terminalCompliance" type="xsd:integer" minOccurs="0" /> + <xsd:element name="terminalCardCaptureCapability" type="xsd:string" minOccurs="0" /> + <xsd:element name="terminalOutputCapability" type="xsd:string" minOccurs="0" /> + <xsd:element name="terminalPINcapability" type="xsd:string" minOccurs="0" /> + <xsd:element name="terminalCVMcapabilities_0" type="xsd:string" minOccurs="0" /> + <xsd:element name="terminalCVMcapabilities_1" type="xsd:string" minOccurs="0" /> + <xsd:element name="terminalCVMcapabilities_2" type="xsd:string" minOccurs="0" /> + <xsd:element name="terminalInputCapabilities_0" type="xsd:string" minOccurs="0" /> + <xsd:element name="terminalInputCapabilities_1" type="xsd:string" minOccurs="0" /> + <xsd:element name="terminalInputCapabilities_2" type="xsd:string" minOccurs="0" /> + <xsd:element name="terminalInputCapabilities_3" type="xsd:string" minOccurs="0" /> + <xsd:element name="terminalInputCapabilities_4" type="xsd:string" minOccurs="0" /> + <xsd:element name="terminalInputCapabilities_5" type="xsd:string" minOccurs="0" /> + <xsd:element name="terminalInputCapabilities_6" type="xsd:string" minOccurs="0" /> + <xsd:element name="terminalSerialNumber" type="xsd:string" minOccurs="0" /> + <xsd:element name="storeAndForwardIndicator" type="xsd:string" minOccurs="0" /> + <xsd:element name="panEntryMode" type="xsd:string" minOccurs="0" /> + <xsd:element name="endlessAisleTransactionIndicator" type="tns:boolean" minOccurs="0" /> + <xsd:element name="terminalModel" type="xsd:string" minOccurs="0" /> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="Pin"> + <xsd:sequence> + <xsd:element name="entryCapability" type="xsd:string" minOccurs="0"/> + + + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="EncryptedPayment"> + <xsd:sequence> + <xsd:element name="descriptor" type="xsd:string" minOccurs="0"/> + <xsd:element name="data" type="xsd:string" minOccurs="0"/> + <xsd:element name="encoding" type="xsd:string" minOccurs="0"/> + <xsd:element name="wrappedKey" type="xsd:string" minOccurs="0"/> + <xsd:element name="referenceID" type="xsd:integer" minOccurs="0"/> + <xsd:element name="errorCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="keySerialNumber" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="Installment"> + <xsd:sequence> + <xsd:element name="sequence" type="xsd:string" minOccurs="0"/> + <xsd:element name="totalCount" type="xsd:string" minOccurs="0"/> + <xsd:element name="totalAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="frequency" type="xsd:string" minOccurs="0"/> + <xsd:element name="amount" type="xsd:string" minOccurs="0"/> + <xsd:element name="planType" type="xsd:string" minOccurs="0"/> + + <xsd:element name="firstInstallmentDate" type="xsd:string" minOccurs="0"/> + <xsd:element name="amountFunded" type="xsd:string" minOccurs="0"/> + <xsd:element name="amountRequestedPercentage" type="xsd:string" minOccurs="0"/> + <xsd:element name="expenses" type="xsd:string" minOccurs="0"/> + <xsd:element name="expensesPercentage" type="xsd:string" minOccurs="0"/> + <xsd:element name="fees" type="xsd:string" minOccurs="0"/> + <xsd:element name="feesPercentage" type="xsd:string" minOccurs="0"/> + <xsd:element name="taxes" type="xsd:string" minOccurs="0"/> + <xsd:element name="taxesPercentage" type="xsd:string" minOccurs="0"/> + <xsd:element name="insurance" type="xsd:string" minOccurs="0"/> + <xsd:element name="insurancePercentage" type="xsd:string" minOccurs="0"/> + <xsd:element name="additionalCosts" type="xsd:string" minOccurs="0"/> + <xsd:element name="additionalCostsPercentage" type="xsd:string" minOccurs="0"/> + <xsd:element name="monthlyInterestRate" type="xsd:string" minOccurs="0"/> + <xsd:element name="annualInterestRate" type="xsd:string" minOccurs="0"/> + <xsd:element name="annualFinancingCost" type="xsd:string" minOccurs="0"/> + <xsd:element name="paymentType" type="xsd:string" minOccurs="0"/> + <xsd:element name="invoiceData" type="xsd:string" minOccurs="0"/> + <xsd:element name="downPayment" type="xsd:string" minOccurs="0"/> + <xsd:element name="firstInstallmentAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="minimumTotalCount" type="xsd:string" minOccurs="0"/> + <xsd:element name="maximumTotalCount" type="xsd:string" minOccurs="0"/> + <xsd:element name="gracePeriodDuration" type="xsd:string" minOccurs="0"/> + <xsd:element name="gracePeriodDurationType" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="MDDField"> + <xsd:simpleContent> + <xsd:extension base="xsd:string"> + <xsd:attribute name="id" type="xsd:integer" use="required"/> + </xsd:extension> + </xsd:simpleContent> + </xsd:complexType> + <xsd:complexType name="MerchantDefinedData"> + <xsd:sequence> + <xsd:element name="field1" type="xsd:string" minOccurs="0"/> + <xsd:element name="field2" type="xsd:string" minOccurs="0"/> + <xsd:element name="field3" type="xsd:string" minOccurs="0"/> + <xsd:element name="field4" type="xsd:string" minOccurs="0"/> + <xsd:element name="field5" type="xsd:string" minOccurs="0"/> + <xsd:element name="field6" type="xsd:string" minOccurs="0"/> + <xsd:element name="field7" type="xsd:string" minOccurs="0"/> + <xsd:element name="field8" type="xsd:string" minOccurs="0"/> + <xsd:element name="field9" type="xsd:string" minOccurs="0"/> + <xsd:element name="field10" type="xsd:string" minOccurs="0"/> + <xsd:element name="field11" type="xsd:string" minOccurs="0"/> + <xsd:element name="field12" type="xsd:string" minOccurs="0"/> + <xsd:element name="field13" type="xsd:string" minOccurs="0"/> + <xsd:element name="field14" type="xsd:string" minOccurs="0"/> + <xsd:element name="field15" type="xsd:string" minOccurs="0"/> + <xsd:element name="field16" type="xsd:string" minOccurs="0"/> + <xsd:element name="field17" type="xsd:string" minOccurs="0"/> + <xsd:element name="field18" type="xsd:string" minOccurs="0"/> + <xsd:element name="field19" type="xsd:string" minOccurs="0"/> + <xsd:element name="field20" type="xsd:string" minOccurs="0"/> + <xsd:element name="mddField" type="tns:MDDField" minOccurs="0" maxOccurs="100"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="MerchantSecureData"> + <xsd:sequence> + <xsd:element name="field1" type="xsd:string" minOccurs="0"/> + <xsd:element name="field2" type="xsd:string" minOccurs="0"/> + <xsd:element name="field3" type="xsd:string" minOccurs="0"/> + <xsd:element name="field4" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="ReplyReserved"> + <xsd:sequence> + <xsd:any processContents="skip" minOccurs="0" maxOccurs="999"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="RequestReserved"> + <xsd:sequence> + <xsd:element name="name" type="xsd:string"/> + <xsd:element name="value" type="xsd:string"/> + </xsd:sequence> + </xsd:complexType> + <!-- PayPalGetTxnDetails --> + <xsd:complexType name="PayPalGetTxnDetailsReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="payer" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerId" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerCountry" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerBusiness" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerSalutation" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerFirstname" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerMiddlename" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerLastname" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerSuffix" type="xsd:string" minOccurs="0"/> + <xsd:element name="addressID" type="xsd:string" minOccurs="0"/> + <xsd:element name="addressStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="shipToName" type="xsd:string" minOccurs="0"/> + <xsd:element name="shipToAddress1" type="xsd:string" minOccurs="0"/> + <xsd:element name="shipToAddress2" type="xsd:string" minOccurs="0"/> + <xsd:element name="shipToCity" type="xsd:string" minOccurs="0"/> + <xsd:element name="shipToState" type="xsd:string" minOccurs="0"/> + <xsd:element name="shipToCountry" type="xsd:string" minOccurs="0"/> + <xsd:element name="shipToZip" type="xsd:string" minOccurs="0"/> + <xsd:element name="payerPhone" type="xsd:string" minOccurs="0"/> + <xsd:element name="transactionId" type="xsd:string" minOccurs="0"/> + <xsd:element name="parentTransactionId" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalReceiptId" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalTransactiontype" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalPaymentType" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalOrderTime" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalPaymentGrossAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalFeeAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="currency" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalSettleAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalTaxAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalExchangeRate" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalPaymentStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalPendingReason" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalReasonCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="protectionEligibility" type="xsd:string" minOccurs="0"/> + <xsd:element name="protectionEligibilityType" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalNote" type="xsd:string" minOccurs="0"/> + <xsd:element name="invoiceNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="item" type="tns:Item" minOccurs="0" maxOccurs="1000" /> + <xsd:element name="errorCode" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + + <!-- end of PayPalGetTxnDetails --> + + <!-- PayPalTransactionSearchReply --> + <xsd:complexType name="PayPalTransactionSearchReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="transaction" type="tns:PaypalTransaction" minOccurs="0" maxOccurs="999" /> + <xsd:element name="errorCode" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="PaypalTransaction"> + <xsd:sequence> + <xsd:element name="transactionTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="transactionTimeZone" type="xsd:string" minOccurs="0"/> + <xsd:element name="transactionType" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalPayerOrPayeeEmail" type="xsd:string" minOccurs="0"/> + <xsd:element name="customerDisplayName" type="xsd:string" minOccurs="0"/> + <xsd:element name="transactionID" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalPaymentStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="grandTotalAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="currency" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalFeeAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="paypalNetAmount" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="id" type="xsd:integer" use="optional"/> + </xsd:complexType> + <!-- end of PayPalTransactionSearchReply --> + + <xsd:complexType name="CCDCCUpdateService"> + <xsd:sequence> + <xsd:element name="reason" type="xsd:string" minOccurs="0"/> + <xsd:element name="action" type="xsd:string" minOccurs="0"/> + <xsd:element name="dccRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="captureRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="creditRequestID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <!-- Merchant Descriptor fields for Service Fee. goes into RequestMessage--> + <xsd:complexType name="ServiceFee"> + <xsd:sequence> + <xsd:element name="merchantDescriptor" type="xsd:string" minOccurs="0"/> + <xsd:element name="merchantDescriptorContact" type="xsd:string" minOccurs="0"/> + <xsd:element name="merchantDescriptorState" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <!-- EMV transaction data request/reply start --> + <xsd:complexType name="EmvRequest"> + <xsd:sequence> + <xsd:element name="combinedTags" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardSequenceNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="aidAndDFname" type="xsd:string" minOccurs="0"/> + <xsd:element name="fallback" type="xsd:string" minOccurs="0"/> + <xsd:element name="fallbackCondition" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="EmvReply"> + <xsd:sequence> + <xsd:element name="combinedTags" type="xsd:string" minOccurs="0"/> + <xsd:element name="decryptedRequestTags" type="xsd:string" minOccurs="0"/> + <xsd:element name="chipValidationResults" type="xsd:string" minOccurs="0"/> + <xsd:element name="chipValidationType" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <!-- EMV transaction data request/reply end --> + <!-- Auth Reversal time out merchant intitated --> + <xsd:complexType name="OriginalTransaction"> + <xsd:sequence> + <xsd:element name="amount" type="tns:amount" minOccurs="0" /> + <xsd:element name="reasonCode" type="xsd:string" minOccurs="0" /> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="HostedDataCreateService"> + <xsd:sequence> + <xsd:element name="profileID" type="xsd:string" minOccurs="0"/> + <xsd:element name="paymentMethod" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="HostedDataRetrieveService"> + <xsd:sequence> + <xsd:element name="profileID" type="xsd:string" minOccurs="0"/> + <xsd:element name="tokenValue" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + + <xsd:complexType name="HostedDataCreateReply"> + <xsd:sequence> + <xsd:element name="responseMessage" type="xsd:string" minOccurs="0" /> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="cardAccountNumberToken" type="xsd:string" minOccurs="0" /> + <xsd:element name="customerID" type="xsd:string" minOccurs="0" /> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="HostedDataRetrieveReply"> + <xsd:sequence> + <xsd:element name="responseMessage" type="xsd:string" minOccurs="0" /> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="aggregatorMerchantIdentifier" type="xsd:string" minOccurs="0" /> + <xsd:element name="customerFirstName" type="xsd:string" minOccurs="0" /> + <xsd:element name="customerLastName" type="xsd:string" minOccurs="0" /> + <xsd:element name="customerID" type="xsd:string" minOccurs="0" /> + <xsd:element name="paymentMethod" type="xsd:string" minOccurs="0" /> + <xsd:element name="billToStreet1" type="xsd:string" minOccurs="0"/> + <xsd:element name="billToStreet2" type="xsd:string" minOccurs="0"/> + <xsd:element name="billToEmail" type="xsd:string" minOccurs="0"/> + <xsd:element name="billToState" type="xsd:string" minOccurs="0"/> + <xsd:element name="billToFirstName" type="xsd:string" minOccurs="0"/> + <xsd:element name="billToLastName" type="xsd:string" minOccurs="0"/> + <xsd:element name="billToCity" type="xsd:string" minOccurs="0"/> + <xsd:element name="billToCountry" type="xsd:string" minOccurs="0"/> + <xsd:element name="billToPostalCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="cardAccountNumber" type="xsd:string" minOccurs="0" /> + <xsd:element name="cardType" type="xsd:string" minOccurs="0" /> + <xsd:element name="cardExpirationMonth" type="xsd:string" minOccurs="0" /> + <xsd:element name="cardExpirationYear" type="xsd:string" minOccurs="0" /> + <xsd:element name="cardIssueNumber" type="xsd:string" minOccurs="0" /> + <xsd:element name="cardStartMonth" type="xsd:string" minOccurs="0" /> + <xsd:element name="cardStartYear" type="xsd:string" minOccurs="0" /> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="AutoRentalData"> + <xsd:sequence> + <xsd:element name="adjustmentCost" type="tns:amount" minOccurs="0" /> + <xsd:element name="adjustmentCode" type="xsd:string" minOccurs="0" /> + <xsd:element name="agreementNumber" type="xsd:string" minOccurs="0" /> + <xsd:element name="classCode" type="xsd:string" minOccurs="0" /> + <xsd:element name="customerServicePhoneNumber" type="xsd:string" minOccurs="0" /> + <xsd:element name="dailyRate" type="tns:amount" minOccurs="0" /> + <xsd:element name="mileageCost" type="tns:amount" minOccurs="0" /> + <xsd:element name="gasCost" type="tns:amount" minOccurs="0" /> + <xsd:element name="insuranceCost" type="tns:amount" minOccurs="0" /> + <xsd:element name="lateReturnCost" type="tns:amount" minOccurs="0" /> + <xsd:element name="maximumFreeMiles" type="xsd:integer" minOccurs="0" /> + <xsd:element name="milesTraveled" type="xsd:integer" minOccurs="0" /> + <xsd:element name="oneWayCost" type="tns:amount" minOccurs="0" /> + <xsd:element name="parkingViolationCost" type="tns:amount" minOccurs="0" /> + <xsd:element name="pickUpCity" type="xsd:string" minOccurs="0" /> + <xsd:element name="pickUpCountry" type="xsd:string" minOccurs="0" /> + <xsd:element name="pickUpDate" type="xsd:string" minOccurs="0" /> + <xsd:element name="pickUpState" type="xsd:string" minOccurs="0" /> + <xsd:element name="pickUpTime" type="xsd:integer" minOccurs="0" /> + <xsd:element name="ratePerMile" type="tns:amount" minOccurs="0" /> + <xsd:element name="renterName" type="xsd:string" minOccurs="0" /> + <xsd:element name="returnCity" type="xsd:string" minOccurs="0" /> + <xsd:element name="returnCountry" type="xsd:string" minOccurs="0" /> + <xsd:element name="returnDate" type="xsd:string" minOccurs="0" /> + <xsd:element name="returnLocationID" type="xsd:string" minOccurs="0" /> + <xsd:element name="returnState" type="xsd:string" minOccurs="0" /> + <xsd:element name="returnTime" type="xsd:integer" minOccurs="0" /> + <xsd:element name="specialProgramCode" type="xsd:string" minOccurs="0" /> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="VCReply"> + <xsd:sequence> + <xsd:element name="creationTimeStamp" type="xsd:string" minOccurs="0" /> + <xsd:element name="alternateShippingAddressCountryCode" type="xsd:string" minOccurs="0" /> + <xsd:element name="alternateShippingAddressPostalCode" type="xsd:string" minOccurs="0" /> + <xsd:element name="vcAccountLoginName" type="xsd:string" minOccurs="0" /> + <xsd:element name="vcAccountFirstName" type="xsd:string" minOccurs="0" /> + <xsd:element name="vcAccountLastName" type="xsd:string" minOccurs="0" /> + <xsd:element name="vcAccountEncryptedID" type="xsd:string" minOccurs="0" /> + <xsd:element name="vcAccountEmail" type="xsd:string" minOccurs="0" /> + <xsd:element name="vcAccountMobilePhoneNumber" type="xsd:string" minOccurs="0" /> + <xsd:element name="merchantReferenceID" type="xsd:string" minOccurs="0" /> + <xsd:element name="subtotalAmount" type="xsd:string" minOccurs="0" /> + <xsd:element name="shippingHandlingAmount" type="xsd:string" minOccurs="0" /> + <xsd:element name="taxAmount" type="xsd:string" minOccurs="0" /> + <xsd:element name="discountAmount" type="xsd:string" minOccurs="0" /> + <xsd:element name="giftWrapAmount" type="xsd:string" minOccurs="0" /> + <xsd:element name="uncategorizedAmount" type="xsd:string" minOccurs="0" /> + <xsd:element name="totalPurchaseAmount" type="xsd:string" minOccurs="0" /> + <xsd:element name="walletReferenceID" type="xsd:string" minOccurs="0" /> + <xsd:element name="promotionCode" type="xsd:string" minOccurs="0" /> + <xsd:element name="paymentInstrumentID" type="xsd:string" minOccurs="0" /> + <xsd:element name="cardVerificationStatus" type="xsd:string" minOccurs="0" /> + <xsd:element name="issuerID" type="xsd:string" minOccurs="0" /> + <xsd:element name="paymentInstrumentNickName" type="xsd:string" minOccurs="0" /> + <xsd:element name="nameOnCard" type="xsd:string" minOccurs="0" /> + <xsd:element name="cardType" type="xsd:string" minOccurs="0" /> + <xsd:element name="cardGroup" type="xsd:string" minOccurs="0" /> + <xsd:element name="cardArt" type="tns:VCCardArt" minOccurs="0" /> + <xsd:element name="riskAdvice" type="xsd:string" minOccurs="0" /> + <xsd:element name="riskScore" type="xsd:string" minOccurs="0" /> + <xsd:element name="riskAdditionalData" type="xsd:string" minOccurs="0" /> + <xsd:element name="avsCodeRaw" type="xsd:string" minOccurs="0" /> + <xsd:element name="cvnCodeRaw" type="xsd:string" minOccurs="0" /> + <xsd:element name="eciRaw" type="xsd:string" minOccurs="0" /> + <xsd:element name="eci" type="xsd:string" minOccurs="0" /> + <xsd:element name="cavv" type="xsd:string" minOccurs="0" /> + <xsd:element name="veresEnrolled" type="xsd:string" minOccurs="0" /> + <xsd:element name="veresTimeStamp" type="xsd:string" minOccurs="0" /> + <xsd:element name="paresStatus" type="xsd:string" minOccurs="0" /> + <xsd:element name="paresTimeStamp" type="xsd:string" minOccurs="0" /> + <xsd:element name="xid" type="xsd:string" minOccurs="0" /> + <xsd:element name="customData" type="tns:VCCustomData" minOccurs="0" /> + <xsd:element name="vcAccountFullName" type="xsd:string" minOccurs="0" /> + <xsd:element name="paymentDescription" type="xsd:string" minOccurs="0" /> + <xsd:element name="billingAddressStreetName" type="xsd:string" minOccurs="0" /> + <xsd:element name="billingAddressAdditionalLocation" type="xsd:string" minOccurs="0" /> + <xsd:element name="billingAddressStreetNumber" type="xsd:string" minOccurs="0" /> + <xsd:element name="expiredCard" type="xsd:string" minOccurs="0" /> + <xsd:element name="cardFirstName" type="xsd:string" minOccurs="0" /> + <xsd:element name="cardLastName" type="xsd:string" minOccurs="0" /> + <xsd:element name="shippingAddressStreetName" type="xsd:string" minOccurs="0" /> + <xsd:element name="shippingAddressAdditionalLocation" type="xsd:string" minOccurs="0" /> + <xsd:element name="shippingAddressStreetNumber" type="xsd:string" minOccurs="0" /> + <xsd:element name="ageOfAccount" type="xsd:string" minOccurs="0" /> + <xsd:element name="newUser" type="xsd:string" minOccurs="0" /> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="VCCardArt"> + <xsd:sequence> + <xsd:element name="fileName" type="xsd:string" minOccurs="0" /> + <xsd:element name="height" type="xsd:string" minOccurs="0" /> + <xsd:element name="width" type="xsd:string" minOccurs="0" /> + </xsd:sequence> + <xsd:attribute name="id" type="xsd:integer" use="optional"/> + </xsd:complexType> + + <xsd:complexType name="VCCustomData"> + <xsd:sequence> + <xsd:element name="name" type="xsd:string" minOccurs="0" /> + <xsd:element name="value" type="xsd:string" minOccurs="0" /> + </xsd:sequence> + <xsd:attribute name="id" type="xsd:integer" use="optional"/> + </xsd:complexType> + + <xsd:complexType name="DecryptVisaCheckoutDataReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="GetVisaCheckoutDataReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="EncryptPaymentDataReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="requestDateTime" type="tns:dateTime" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="BinLookupService"> + <xsd:sequence> + <xsd:element name="mode" type="xsd:string" minOccurs="0" /> + <xsd:element name="networkOrder" type="xsd:string" minOccurs="0" /> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + + <xsd:complexType name="BinLookupReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="requestDateTime" type="tns:dateTime" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="issuer"> + <xsd:sequence> + <xsd:element name="additionalData" type="xsd:string" minOccurs="0"/> + <xsd:element name="name" type="xsd:string" minOccurs="0"/> + <xsd:element name="country" type="xsd:string" minOccurs="0"/> + <xsd:element name="countryNumericCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="phoneNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="responseCode" type="xsd:string" minOccurs="0" /> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="GETVisaCheckoutDataService"> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="TransactionMetadataService"> + <xsd:sequence> + <xsd:element name="authRequestID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + <xsd:complexType name="Loan"> + <xsd:sequence> + <xsd:element name="assetType" type="xsd:string" minOccurs="0"/> + <xsd:element name="type" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="APOrderService"> + <xsd:sequence> + <xsd:element name="sessionsRequestID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + + <xsd:complexType name="APOrderReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="amount" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="status" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="dateTime" type="tns:dateTime" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="APCancelService"> + <xsd:sequence> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="orderRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="saleRequestID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + + <xsd:complexType name="APCancelReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="processorTransactionID" type="xsd:string" minOccurs="0"/> + <xsd:element name="status" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="dateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="paymentStatus" type="xsd:string" minOccurs="0"/> + <xsd:element name="responseCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="APBillingAgreementService"> + <xsd:sequence> + <xsd:element name="sessionsRequestID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + + <xsd:complexType name="APBillingAgreementReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="amount" type="xsd:string" minOccurs="0"/> + <xsd:element name="status" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="dateTime" type="tns:dateTime" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="Passenger"> + <xsd:sequence> + <xsd:element name="firstName" type="xsd:string" minOccurs="0" maxOccurs="1"/> + <xsd:element name="lastName" type="xsd:string" minOccurs="0" maxOccurs="1"/> + </xsd:sequence> + <xsd:attribute name="id" type="xsd:integer" use="required"/> + </xsd:complexType> + + <xsd:complexType name="PostdatedTransaction"> + <xsd:sequence> + <xsd:element name="guaranteeIndicator" type="xsd:string" minOccurs="0"/> + <xsd:element name="guaranteeAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="settlementDate" type="xsd:integer" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="APCreateMandateService"> + <xsd:sequence> + <xsd:element name="saleRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="cancelURL" type="xsd:string" minOccurs="0"/> + <xsd:element name="successURL" type="xsd:string" minOccurs="0"/> + <xsd:element name="failureURL" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + + <xsd:complexType name="APCreateMandateReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="mandateID" type="xsd:string"/> + <xsd:element name="status" type="xsd:string"/> + <xsd:element name="merchantURL" type="xsd:string"/> + <xsd:element name="responseCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorTransactionID" type="xsd:string" minOccurs="0"/> + <xsd:element name="riskScore" type="xsd:string" minOccurs="0"/> + <xsd:element name="encodedHTML" type="xsd:string" minOccurs="0"/> + <xsd:element name="encodedPopupHTML" type="xsd:string" minOccurs="0"/> + <xsd:element name="dateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="dateSigned" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="dateCreated" type="tns:dateTime" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="APMandateStatusService"> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + + <xsd:complexType name="APMandateStatusReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="mandateID" type="xsd:string"/> + <xsd:element name="status" type="xsd:string"/> + <xsd:element name="responseCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorTransactionID" type="xsd:string" minOccurs="0"/> + <xsd:element name="dateCreated" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="dateSigned" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="dateRevoked" type="tns:dateTime" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + <xsd:complexType name="APUpdateMandateService"> + <xsd:sequence> + <xsd:element name="esign" type="xsd:string" minOccurs="0"/> + <xsd:element name="cancelURL" type="xsd:string" minOccurs="0"/> + <xsd:element name="successURL" type="xsd:string" minOccurs="0"/> + <xsd:element name="failureURL" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + + <xsd:complexType name="GetMasterpassDataService"> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + + <xsd:complexType name="GetMasterpassDataReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="APUpdateMandateReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="mandateID" type="xsd:string"/> + <xsd:element name="status" type="xsd:string"/> + <xsd:element name="merchantURL" type="xsd:string"/> + <xsd:element name="responseCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorTransactionID" type="xsd:string" minOccurs="0"/> + <xsd:element name="riskScore" type="xsd:string" minOccurs="0"/> + <xsd:element name="encodedHTML" type="xsd:string" minOccurs="0"/> + <xsd:element name="encodedPopupHTML" type="xsd:string" minOccurs="0"/> + <xsd:element name="dateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="dateSigned" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="dateCreated" type="tns:dateTime" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="APImportMandateReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="mandateID" type="xsd:string"/> + <xsd:element name="status" type="xsd:string"/> + <xsd:element name="responseCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorTransactionID" type="xsd:string" minOccurs="0"/> + <xsd:element name="dateSigned" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="dateCreated" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="dateTime" type="tns:dateTime" minOccurs="0"/> + + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="APRevokeMandateService"> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + + <xsd:complexType name="APRevokeMandateReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="mandateID" type="xsd:string"/> + <xsd:element name="status" type="xsd:string"/> + <xsd:element name="responseCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorTransactionID" type="xsd:string" minOccurs="0"/> + <xsd:element name="dateSigned" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="dateCreated" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="dateRevoked" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="dateTime" type="tns:dateTime" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="Category"> + <xsd:sequence> + <xsd:element name="affiliate" type="xsd:string" minOccurs="0"/> + <xsd:element name="campaign" type="xsd:string" minOccurs="0"/> + <xsd:element name="group" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="ECAVSService"> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + + + <xsd:complexType name="GiftCardActivationService"> + <xsd:sequence> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="commerceIndicator" type="xsd:string" minOccurs="0"/> + + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + + <xsd:complexType name="GiftCardBalanceInquiryService"> + <xsd:sequence> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="commerceIndicator" type="xsd:string" minOccurs="0"/> + + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + + <xsd:complexType name="GiftCardVoidService"> + + <xsd:attribute name="run" type="tns:boolean" use="required"/> + + </xsd:complexType> + + <xsd:complexType name="GiftCardReversalService"> + + <xsd:attribute name="run" type="tns:boolean" use="required"/> + + </xsd:complexType> + + <xsd:complexType name="GiftCardRedemptionService"> + <xsd:sequence> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + <xsd:element name="commerceIndicator" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + <xsd:attribute name="run" type="tns:boolean" use="required"/> + </xsd:complexType> + + <xsd:complexType name="GiftCard"> + <xsd:sequence> + <xsd:element name="originalRequestID" type="xsd:string" minOccurs="0"/> + <xsd:element name="redemptionType" type="xsd:string" minOccurs="0"/> + <xsd:element name="count" type="xsd:string" minOccurs="0"/> + <xsd:element name="escheatable" type="tns:boolean" minOccurs="0"/> + <xsd:element name="groupID" type="xsd:string" minOccurs="0"/> + <xsd:element name="securityValue" type="xsd:string" minOccurs="0"/> + <xsd:element name="transactionPostingDate" type="xsd:string" minOccurs="0"/> + <xsd:element name="additionalAccountNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="promoCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="balanceCurrency" type="xsd:string" minOccurs="0"/> + <xsd:element name="extendedAccountNumber" type="xsd:string" minOccurs="0"/> + <xsd:element name="previousBalance" type="xsd:string" minOccurs="0"/> + <xsd:element name="currentBalance" type="xsd:string" minOccurs="0"/> + <xsd:element name="baseCurrencyPreviousBalance" type="xsd:string" minOccurs="0"/> + <xsd:element name="baseCurrencyCurrentBalance" type="xsd:string" minOccurs="0"/> + <xsd:element name="baseCurrencyCashbackAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="baseCurrency" type="xsd:string" minOccurs="0"/> + <xsd:element name="expirationDate" type="xsd:string" minOccurs="0"/> + <xsd:element name="exchangeRate" type="xsd:string" minOccurs="0"/> + <xsd:element name="bonusAmount" type="xsd:string" minOccurs="0"/> + <xsd:element name="discountAmount" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="GiftCardActivationReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="authorizationCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="requestDateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="GiftCardBalanceInquiryReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="authorizationCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="requestDateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="GiftCardRedemptionReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="authorizationCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="requestDateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="GiftCardReversalReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="authorizationCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="requestDateTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="GiftCardVoidReply"> + <xsd:sequence> + <xsd:element name="reasonCode" type="xsd:integer"/> + <xsd:element name="authorizationCode" type="xsd:string" minOccurs="0"/> + <xsd:element name="processorResponse" type="xsd:string" minOccurs="0"/> + <xsd:element name="requestDeTime" type="tns:dateTime" minOccurs="0"/> + <xsd:element name="reconciliationID" type="xsd:string" minOccurs="0"/> + </xsd:sequence> + </xsd:complexType> + + <xsd:complexType name="mPOS"> + <xsd:sequence> + <xsd:element name="deviceType" type="xsd:string" minOccurs="0" /> + </xsd:sequence> + </xsd:complexType> +</xsd:schema> + diff --git a/test/schema/firstdata_e4/v11.xsd b/test/schema/firstdata_e4/v11.xsd new file mode 100644 index 00000000000..b2bbc2996f6 --- /dev/null +++ b/test/schema/firstdata_e4/v11.xsd @@ -0,0 +1,126 @@ +<?xml version="1.0" encoding="utf-8"?> +<s:schema + elementFormDefault="qualified" + xmlns:s="http://www.w3.org/2001/XMLSchema" + xmlns:s0="http://secure2.e-xact.com/vplug-in/transaction/rpc-enc/encodedTypes" + xmlns="http://secure2.e-xact.com/vplug-in/transaction/rpc-enc/encodedTypes" + targetNamespace="http://secure2.e-xact.com/vplug-in/transaction/rpc-enc/encodedTypes"> + <s:complexType name="SoftDescriptor_Type"> + <s:sequence> + <s:element minOccurs="0" maxOccurs="1" name="DBAName" type="s:string" /> + <s:element minOccurs="0" maxOccurs="1" name="Street" type="s:string" /> + <s:element minOccurs="0" maxOccurs="1" name="City" type="s:string" /> + <s:element minOccurs="0" maxOccurs="1" name="Region" type="s:string" /> + <s:element minOccurs="0" maxOccurs="1" name="PostalCode" type="s:string" /> + <s:element minOccurs="0" maxOccurs="1" name="CountryCode" type="s:string" /> + <s:element minOccurs="0" maxOccurs="1" name="MID" type="s:string" /> + <s:element minOccurs="0" maxOccurs="1" name="MCC" type="s:string" /> + <s:element minOccurs="0" maxOccurs="1" name="MerchantContactInfo" type="s:string" /> + </s:sequence> + </s:complexType> + <s:complexType name="Level3_ShipToAddress_Type"> + <s:sequence> + <s:element minOccurs="0" maxOccurs="1" name="Address1" type="s:string" /> + <s:element minOccurs="0" maxOccurs="1" name="City" type="s:string" /> + <s:element minOccurs="0" maxOccurs="1" name="State" type="s:string" /> + <s:element minOccurs="0" maxOccurs="1" name="Country" type="s:string" /> + <s:element minOccurs="0" maxOccurs="1" name="CustomerNumber" type="s:string" /> + <s:element minOccurs="0" maxOccurs="1" name="Email" type="s:string" /> + <s:element minOccurs="0" maxOccurs="1" name="Phone" type="s:string" /> + <s:element minOccurs="0" maxOccurs="1" name="Name" type="s:string" /> + <s:element minOccurs="0" maxOccurs="1" name="Zip" type="s:string" /> + </s:sequence> + </s:complexType> + <s:complexType name="Level3_LineItem_Type"> + <s:sequence> + <s:element minOccurs="0" maxOccurs="1" name="CommodityCode" type="s:string" /> + <s:element minOccurs="1" maxOccurs="1" name="Description" type="s:string" /> + <s:element minOccurs="0" maxOccurs="1" name="DiscountAmount" type="s:string" /> + <s:element minOccurs="0" maxOccurs="1" name="DiscountIndicator" type="s:boolean" /> + <s:element minOccurs="0" maxOccurs="1" name="GrossNetIndicator" type="s:boolean" /> + <s:element minOccurs="1" maxOccurs="1" name="LineItemTotal" type="s:string" /> + <s:element minOccurs="0" maxOccurs="1" name="ProductCode" type="s:string" /> + <s:element minOccurs="1" maxOccurs="1" name="Quantity" type="s:string" /> + <s:element minOccurs="0" maxOccurs="1" name="TaxAmount" type="s:string" /> + <s:element minOccurs="0" maxOccurs="1" name="TaxRate" type="s:string" /> + <s:element minOccurs="0" maxOccurs="1" name="TaxType" type="s:string" /> + <s:element minOccurs="1" maxOccurs="1" name="UnitCost" type="s:string" /> + <s:element minOccurs="0" maxOccurs="1" name="UnitOfMeasure" type="s:string" /> + </s:sequence> + </s:complexType> + <s:complexType name="Level3_Type"> + <s:sequence> + <s:element minOccurs="0" maxOccurs="1" name="TaxAmount" type="s:string" /> + <s:element minOccurs="0" maxOccurs="1" name="TaxRate" type="s:string" /> + <s:element minOccurs="0" maxOccurs="1" name="AltTaxAmount" type="s:string" /> + <s:element minOccurs="0" maxOccurs="1" name="AltTaxId" type="s:string" /> + <s:element minOccurs="0" maxOccurs="1" name="DutyAmount" type="s:string" /> + <s:element minOccurs="0" maxOccurs="1" name="FreightAmount" type="s:string" /> + <s:element minOccurs="0" maxOccurs="1" name="DiscountAmount" type="s:string" /> + <s:element minOccurs="0" maxOccurs="1" name="ShipFromZip" type="s:string" /> + <s:element minOccurs="1" maxOccurs="1" name="ShipToAddress" type="Level3_ShipToAddress_Type" /> + <s:element minOccurs="1" maxOccurs="98" name="LineItem" type="Level3_LineItem_Type" /> + </s:sequence> + </s:complexType> + <s:complexType name="PaypalTransactionDetails"> + <s:sequence> + <s:element minOccurs="0" maxOccurs="1" name="PayerID" type="s:string" /> + <s:element minOccurs="0" maxOccurs="1" name="GrossAmountCurrencyID" type="s:string" /> + <s:element minOccurs="1" maxOccurs="1" name="Success" type="s:boolean" /> + <s:element minOccurs="0" maxOccurs="1" name="Authorization" type="s:string" /> + <s:element minOccurs="0" maxOccurs="1" name="Message" type="s:string" /> + <s:element minOccurs="0" maxOccurs="1" name="CorrelationID" type="s:string" /> + <s:element minOccurs="0" maxOccurs="1" name="Timestamp" type="s:string" /> + </s:sequence> + </s:complexType> + <s:complexType name="Transaction"> + <s:all> + <s:element minOccurs="1" maxOccurs="1" name="ExactID" type="s:string" /> + <s:element minOccurs="1" maxOccurs="1" name="Password" type="s:string" /> + <s:element minOccurs="1" maxOccurs="1" name="Transaction_Type" type="s:string" /> + <s:element minOccurs="0" maxOccurs="1" name="DollarAmount" type="s:string" /> + <s:element minOccurs="0" maxOccurs="1" name="SurchargeAmount" type="s:string" /> + <s:element minOccurs="0" maxOccurs="1" name="Card_Number" type="s:string" /> + <s:element minOccurs="0" maxOccurs="1" name="Transaction_Tag" type="s:string" /> + <s:element minOccurs="0" maxOccurs="1" name="Track1" type="s:string" /> + <s:element minOccurs="0" maxOccurs="1" name="Track2" type="s:string" /> + <s:element minOccurs="0" maxOccurs="1" name="PAN" type="s:string" /> + <s:element minOccurs="0" maxOccurs="1" name="Authorization_Num" type="s:string" /> + <s:element minOccurs="0" maxOccurs="1" name="Expiry_Date" type="s:string" /> + <s:element minOccurs="0" maxOccurs="1" name="CardHoldersName" type="s:string" /> + <s:element minOccurs="0" maxOccurs="1" name="VerificationStr1" type="s:string" /> + <s:element minOccurs="0" maxOccurs="1" name="VerificationStr2" type="s:string" /> + <s:element minOccurs="0" maxOccurs="1" name="CVD_Presence_Ind" type="s:string" /> + <s:element minOccurs="0" maxOccurs="1" name="ZipCode" type="s:string" /> + <s:element minOccurs="0" maxOccurs="1" name="Tax1Amount" type="s:string" /> + <s:element minOccurs="0" maxOccurs="1" name="Tax1Number" type="s:string" /> + <s:element minOccurs="0" maxOccurs="1" name="Tax2Amount" type="s:string" /> + <s:element minOccurs="0" maxOccurs="1" name="Tax2Number" type="s:string" /> + <s:element minOccurs="0" maxOccurs="1" name="Secure_AuthRequired" type="s:string" /> + <s:element minOccurs="0" maxOccurs="1" name="Secure_AuthResult" type="s:string" /> + <s:element minOccurs="0" maxOccurs="1" name="Ecommerce_Flag" type="s:string" /> + <s:element minOccurs="0" maxOccurs="1" name="XID" type="s:string" /> + <s:element minOccurs="0" maxOccurs="1" name="CAVV" type="s:string" /> + <s:element minOccurs="0" maxOccurs="1" name="CAVV_Algorithm" type="s:string" /> + <s:element minOccurs="0" maxOccurs="1" name="Reference_No" type="s:string" /> + <s:element minOccurs="0" maxOccurs="1" name="Customer_Ref" type="s:string" /> + <s:element minOccurs="0" maxOccurs="1" name="Reference_3" type="s:string" /> + <s:element minOccurs="0" maxOccurs="1" name="Language" type="s:string" /> + <s:element minOccurs="0" maxOccurs="1" name="Client_IP" type="s:string" /> + <s:element minOccurs="0" maxOccurs="1" name="Client_Email" type="s:string" /> + <s:element minOccurs="0" maxOccurs="1" name="User_Name" type="s:string" /> + <s:element minOccurs="0" maxOccurs="1" name="Currency" type="s:string" /> + <s:element minOccurs="0" maxOccurs="1" name="PartialRedemption" type="s:boolean" /> + <s:element minOccurs="0" maxOccurs="1" name="Level3" type="Level3_Type" /> + <s:element minOccurs="0" maxOccurs="1" name="TransarmorToken" type="s:string" /> + <s:element minOccurs="0" maxOccurs="1" name="CardType" type="s:string" /> + <s:element minOccurs="0" maxOccurs="1" name="EAN" type="s:string" /> + <s:element minOccurs="0" maxOccurs="1" name="VirtualCard" type="s:boolean" /> + <s:element minOccurs="0" maxOccurs="1" name="CardCost" type="s:string" /> + <s:element minOccurs="0" maxOccurs="1" name="PaypalResponse" type="PaypalTransactionDetails" /> + <s:element minOccurs="0" maxOccurs="1" name="SoftDescriptor" type="SoftDescriptor_Type" /> + </s:all> + </s:complexType> + + <s:element name="Transaction" type="Transaction" /> +</s:schema> diff --git a/test/schema/firstdata_e4/v27.xsd b/test/schema/firstdata_e4/v27.xsd new file mode 100644 index 00000000000..775018838c6 --- /dev/null +++ b/test/schema/firstdata_e4/v27.xsd @@ -0,0 +1,223 @@ +<?xml version="1.0" encoding="utf-8"?> +<s:schema + elementFormDefault="qualified" + xmlns:s="http://www.w3.org/2001/XMLSchema" + xmlns:s0="http://secure2.e-xact.com/vplug-in/transaction/rpc-enc/encodedTypes" + xmlns="http://secure2.e-xact.com/vplug-in/transaction/rpc-enc/encodedTypes" + targetNamespace="http://secure2.e-xact.com/vplug-in/transaction/rpc-enc/encodedTypes"> + <s:complexType name="SoftDescriptor_Type"> + <s:sequence> + <s:element minOccurs="0" maxOccurs="1" name="DBAName" type="s:string"/> + <s:element minOccurs="0" maxOccurs="1" name="Street" type="s:string"/> + <s:element minOccurs="0" maxOccurs="1" name="City" type="s:string"/> + <s:element minOccurs="0" maxOccurs="1" name="Region" type="s:string"/> + <s:element minOccurs="0" maxOccurs="1" name="PostalCode" type="s:string"/> + <s:element minOccurs="0" maxOccurs="1" name="CountryCode" type="s:string"/> + <s:element minOccurs="0" maxOccurs="1" name="MID" type="s:string"/> + <s:element minOccurs="0" maxOccurs="1" name="MCC" type="s:string"/> + <s:element minOccurs="0" maxOccurs="1" name="MerchantContactInfo" type="s:string"/> + <s:element minOccurs="0" maxOccurs="1" name="MVV_MAID" type="s:string"/> + <s:element minOccurs="0" maxOccurs="1" name="AMEXMerchantPhone" type="s:string"/> + <s:element minOccurs="0" maxOccurs="1" name="AMEXMerchantEmail" type="s:string"/> + <s:element minOccurs="0" maxOccurs="1" name="PFacSubmerchantId" type="s:string"/> + <s:element minOccurs="0" maxOccurs="1" name="PFacName" type="s:string"/> + </s:sequence> + </s:complexType> + <s:complexType name="Level3_ShipToAddress_Type"> + <s:sequence> + <s:element minOccurs="0" maxOccurs="1" name="Address1" type="s:string"/> + <s:element minOccurs="0" maxOccurs="1" name="City" type="s:string"/> + <s:element minOccurs="0" maxOccurs="1" name="State" type="s:string"/> + <s:element minOccurs="0" maxOccurs="1" name="Country" type="s:string"/> + <s:element minOccurs="0" maxOccurs="1" name="CustomerNumber" type="s:string"/> + <s:element minOccurs="0" maxOccurs="1" name="Email" type="s:string"/> + <s:element minOccurs="0" maxOccurs="1" name="Phone" type="s:string"/> + <s:element minOccurs="0" maxOccurs="1" name="Name" type="s:string"/> + <s:element minOccurs="0" maxOccurs="1" name="Zip" type="s:string"/> + </s:sequence> + </s:complexType> + <s:complexType name="Level3_LineItem_Type"> + <s:sequence> + <s:element minOccurs="0" maxOccurs="1" name="CommodityCode" type="s:string"/> + <s:element minOccurs="1" maxOccurs="1" name="Description" type="s:string"/> + <s:element minOccurs="0" maxOccurs="1" name="DiscountAmount" type="s:string"/> + <s:element minOccurs="0" maxOccurs="1" name="DiscountIndicator" type="s:boolean"/> + <s:element minOccurs="0" maxOccurs="1" name="GrossNetIndicator" type="s:boolean"/> + <s:element minOccurs="1" maxOccurs="1" name="LineItemTotal" type="s:string"/> + <s:element minOccurs="0" maxOccurs="1" name="ProductCode" type="s:string"/> + <s:element minOccurs="1" maxOccurs="1" name="Quantity" type="s:string"/> + <s:element minOccurs="0" maxOccurs="1" name="TaxAmount" type="s:string"/> + <s:element minOccurs="0" maxOccurs="1" name="TaxRate" type="s:string"/> + <s:element minOccurs="0" maxOccurs="1" name="TaxType" type="s:string"/> + <s:element minOccurs="1" maxOccurs="1" name="UnitCost" type="s:string"/> + <s:element minOccurs="0" maxOccurs="1" name="UnitOfMeasure" type="s:string"/> + </s:sequence> + </s:complexType> + <s:complexType name="Level3_Type"> + <s:sequence> + <s:element minOccurs="0" maxOccurs="1" name="TaxAmount" type="s:string"/> + <s:element minOccurs="0" maxOccurs="1" name="TaxRate" type="s:string"/> + <s:element minOccurs="0" maxOccurs="1" name="AltTaxAmount" type="s:string"/> + <s:element minOccurs="0" maxOccurs="1" name="AltTaxId" type="s:string"/> + <s:element minOccurs="0" maxOccurs="1" name="DutyAmount" type="s:string"/> + <s:element minOccurs="0" maxOccurs="1" name="FreightAmount" type="s:string"/> + <s:element minOccurs="0" maxOccurs="1" name="DiscountAmount" type="s:string"/> + <s:element minOccurs="0" maxOccurs="1" name="ShipFromZip" type="s:string"/> + <s:element minOccurs="1" maxOccurs="1" name="ShipToAddress" type="Level3_ShipToAddress_Type"/> + <s:element minOccurs="1" maxOccurs="98" name="LineItem" type="Level3_LineItem_Type"/> + </s:sequence> + </s:complexType> + <s:complexType name="PaypalTransactionDetails"> + <s:sequence> + <s:element minOccurs="0" maxOccurs="1" name="PayerID" type="s:string"/> + <s:element minOccurs="0" maxOccurs="1" name="GrossAmountCurrencyID" type="s:string"/> + <s:element minOccurs="1" maxOccurs="1" name="Success" type="s:boolean"/> + <s:element minOccurs="0" maxOccurs="1" name="Authorization" type="s:string"/> + <s:element minOccurs="0" maxOccurs="1" name="Message" type="s:string"/> + <s:element minOccurs="0" maxOccurs="1" name="CorrelationID" type="s:string"/> + <s:element minOccurs="0" maxOccurs="1" name="Timestamp" type="s:string"/> + </s:sequence> + </s:complexType> + <s:complexType name="Address_Type"> + <s:sequence> + <s:element minOccurs="0" maxOccurs="1" name="Address1" type="s:string"/> + <s:element minOccurs="0" maxOccurs="1" name="Address2" type="s:string"/> + <s:element minOccurs="0" maxOccurs="1" name="City" type="s:string"/> + <s:element minOccurs="0" maxOccurs="1" name="State" type="s:string"/> + <s:element minOccurs="0" maxOccurs="1" name="Zip" type="s:string"/> + <s:element minOccurs="0" maxOccurs="1" name="CountryCode" type="s:string"/> + <s:element minOccurs="0" maxOccurs="1" name="PhoneNumber" type="s:string"/> + <s:element minOccurs="0" maxOccurs="1" name="PhoneType" type="s:string"/> + </s:sequence> + </s:complexType> + <s:complexType name="DynamicCurrency"> + <s:sequence> + <s:element minOccurs="1" maxOccurs="1" name="OptedIn" type="s:string"/> + <s:element minOccurs="0" maxOccurs="1" name="RateResponseSignature" type="s:string"/> + </s:sequence> + </s:complexType> + <s:complexType name="DynamicPricing"> + <s:sequence> + <s:element minOccurs="1" maxOccurs="1" name="OptedIn" type="s:string"/> + <s:element minOccurs="0" maxOccurs="1" name="RateResponseSignature" type="s:string"/> + </s:sequence> + </s:complexType> + <s:complexType name="ForeignCurrencyDetails"> + <s:sequence> + <s:element minOccurs="1" maxOccurs="1" name="DCCIndicator" type="s:string"/> + <s:element minOccurs="0" maxOccurs="1" name="ForeignAmount" type="s:string"/> + <s:element minOccurs="0" maxOccurs="1" name="ForeignCurrencyCode" type="s:string"/> + <s:element minOccurs="0" maxOccurs="1" name="ExchangeRate" type="s:string"/> + <s:element minOccurs="0" maxOccurs="1" name="MarginRate" type="s:string"/> + <s:element minOccurs="0" maxOccurs="1" name="RateSource" type="s:string"/> + </s:sequence> + </s:complexType> + <s:complexType name="FraudDetectResult"> + <s:sequence> + <s:element minOccurs="0" maxOccurs="1" name="Score" type="s:integer"/> + <s:element minOccurs="0" maxOccurs="1" name="Recommendation" type="s:string"/> + <s:element minOccurs="0" maxOccurs="5" name="Explanation" type="FraudDetectExplanation_Type"/> + </s:sequence> + </s:complexType> + <s:complexType name="FraudDetectExplanation_Type"> + <s:sequence> + <s:element minOccurs="1" maxOccurs="1" name="type" type="s:integer"/> + <s:element minOccurs="1" maxOccurs="1" name="description" type="s:string"/> + </s:sequence> + </s:complexType> + <s:complexType name="VisaCheckout"> + <s:sequence> + <s:element minOccurs="1" maxOccurs="1" name="CallID" type="s:string"/> + <s:element minOccurs="0" maxOccurs="1" name="PromoCode" type="s:string"/> + </s:sequence> + </s:complexType> + <s:complexType name="MasterPass"> + <s:sequence> + <s:element minOccurs="1" maxOccurs="1" name="TransactionID" type="s:string"/> + <s:element minOccurs="1" maxOccurs="1" name="WalletID" type="s:string"/> + <s:element minOccurs="1" maxOccurs="1" name="Indicator" type="s:string"/> + </s:sequence> + </s:complexType> + <s:complexType name="Transaction"> + <s:all> + <s:element minOccurs="1" maxOccurs="1" name="ExactID" type="s:string"/> + <s:element minOccurs="1" maxOccurs="1" name="Password" type="s:string"/> + <s:element minOccurs="1" maxOccurs="1" name="Transaction_Type" type="s:string"/> + <s:element minOccurs="0" maxOccurs="1" name="DollarAmount" type="s:string"/> + <s:element minOccurs="0" maxOccurs="1" name="SurchargeAmount" type="s:string"/> + <s:element minOccurs="0" maxOccurs="1" name="Card_Number" type="s:string"/> + <s:element minOccurs="0" maxOccurs="1" name="Transaction_Tag" type="s:string"/> + <s:element minOccurs="0" maxOccurs="1" name="SplitTenderID" type="s:string"/> + <s:element minOccurs="0" maxOccurs="1" name="Track1" type="s:string"/> + <s:element minOccurs="0" maxOccurs="1" name="Track2" type="s:string"/> + <s:element minOccurs="0" maxOccurs="1" name="PAN" type="s:string"/> + <s:element minOccurs="0" maxOccurs="1" name="Authorization_Num" type="s:string"/> + <s:element minOccurs="0" maxOccurs="1" name="Expiry_Date" type="s:string"/> + <s:element minOccurs="0" maxOccurs="1" name="CardHoldersName" type="s:string"/> + <s:element minOccurs="0" maxOccurs="1" name="CVDCode" type="s:string"/> + <s:element minOccurs="0" maxOccurs="1" name="CVD_Presence_Ind" type="s:string"/> + <s:element minOccurs="0" maxOccurs="1" name="ZipCode" type="s:string"/> + <s:element minOccurs="0" maxOccurs="1" name="Tax1Amount" type="s:string"/> + <s:element minOccurs="0" maxOccurs="1" name="Tax1Number" type="s:string"/> + <s:element minOccurs="0" maxOccurs="1" name="Tax2Amount" type="s:string"/> + <s:element minOccurs="0" maxOccurs="1" name="Tax2Number" type="s:string"/> + <s:element minOccurs="0" maxOccurs="1" name="Ecommerce_Flag" type="s:string"/> + <s:element minOccurs="0" maxOccurs="1" name="XID" type="s:string"/> + <s:element minOccurs="0" maxOccurs="1" name="CAVV" type="s:string"/> + <s:element minOccurs="0" maxOccurs="1" name="Reference_No" type="s:string"/> + <s:element minOccurs="0" maxOccurs="1" name="Customer_Ref" type="s:string"/> + <s:element minOccurs="0" maxOccurs="1" name="Reference_3" type="s:string"/> + <s:element minOccurs="0" maxOccurs="1" name="Language" type="s:string"/> + <s:element minOccurs="0" maxOccurs="1" name="Client_IP" type="s:string"/> + <s:element minOccurs="0" maxOccurs="1" name="Client_Email" type="s:string"/> + <s:element minOccurs="0" maxOccurs="1" name="User_Name" type="s:string"/> + <s:element minOccurs="0" maxOccurs="1" name="Currency" type="s:string"/> + <s:element minOccurs="0" maxOccurs="1" name="PartialRedemption" type="s:boolean"/> + <s:element minOccurs="0" maxOccurs="1" name="Level3" type="Level3_Type"/> + <s:element minOccurs="0" maxOccurs="1" name="TransarmorToken" type="s:string"/> + <s:element minOccurs="0" maxOccurs="1" name="CardType" type="s:string"/> + <s:element minOccurs="0" maxOccurs="1" name="EAN" type="s:string"/> + <s:element minOccurs="0" maxOccurs="1" name="VirtualCard" type="s:boolean"/> + <s:element minOccurs="0" maxOccurs="1" name="CardCost" type="s:string"/> + <s:element minOccurs="0" maxOccurs="1" name="FraudSuspected" type="s:string"/> + <s:element minOccurs="0" maxOccurs="1" name="PaypalResponse" type="PaypalTransactionDetails"/> + <s:element minOccurs="0" maxOccurs="1" name="SoftDescriptor" type="SoftDescriptor_Type"/> + <s:element minOccurs="0" maxOccurs="1" name="Address" type="Address_Type"/> + <s:element minOccurs="0" maxOccurs="1" name="TPPID" type="s:string"/> + <s:element minOccurs="0" maxOccurs="1" name="SplitShipmentNumber" type="s:string"/> + <s:element minOccurs="0" maxOccurs="1" name="AmexFraud" type="s:string"/> + <s:element minOccurs="0" maxOccurs="1" name="DynamicCurrency" type="DynamicCurrency"/> + <s:element minOccurs="0" maxOccurs="1" name="DynamicPricing" type="DynamicPricing"/> + <s:element minOccurs="0" maxOccurs="1" name="FeeAmount" type="s:string"/> + <s:element minOccurs="0" maxOccurs="1" name="VisaCheckout" type="VisaCheckout"/> + <s:element minOccurs="0" maxOccurs="1" name="PostDate" type="s:string"/> + <s:element minOccurs="0" maxOccurs="1" name="OtherAmount" type="s:string"/> + <s:element minOccurs="0" maxOccurs="1" name="GiftDepositAvailable" type="s:string"/> + <s:element minOccurs="0" maxOccurs="1" name="SpecialPayment" type="s:string"/> + <s:element minOccurs="0" maxOccurs="1" name="WalletProviderID" type="s:string"/> + <s:element minOccurs="0" maxOccurs="1" name="MasterPass" type="MasterPass"/> + <s:element minOccurs="0" maxOccurs="1" name="FraudDetectInAuthTransId" type="s:string"/> + <s:element minOccurs="0" maxOccurs="1" name="SCV" type="s:string"/> + <s:element minOccurs="0" maxOccurs="1" name="StoredCredentials" type="StoredCredentialsType"/> + <s:element minOccurs="0" maxOccurs="1" name="Extra" type="Extra_Type"/> + </s:all> + </s:complexType> + <s:complexType name="StoredCredentialsType"> + <s:sequence> + <s:element minOccurs="0" maxOccurs="1" name="AuthorizationTypeOverride" type="s:string"/> + <s:element minOccurs="0" maxOccurs="1" name="Initiation" type="s:string"/> + <s:element minOccurs="0" maxOccurs="1" name="Indicator" type="s:string"/> + <s:element minOccurs="0" maxOccurs="1" name="Schedule" type="s:string"/> + <s:element minOccurs="0" maxOccurs="1" name="TransactionId" type="s:string"/> + </s:sequence> + </s:complexType> + <s:complexType name="Extra_Type"> + <s:sequence> + <s:element minOccurs="1" maxOccurs="1" name="Field" type="s:string"/> + <s:element minOccurs="1" maxOccurs="1" name="Value" type="s:string"/> + </s:sequence> + </s:complexType> + + <s:element name="Transaction" type="s0:Transaction"/> + +</s:schema> diff --git a/test/schema/orbital/Request_PTI54.xsd b/test/schema/orbital/Request_PTI77.xsd similarity index 81% rename from test/schema/orbital/Request_PTI54.xsd rename to test/schema/orbital/Request_PTI77.xsd index 0a75e3a164d..bcae535f6f5 100755 --- a/test/schema/orbital/Request_PTI54.xsd +++ b/test/schema/orbital/Request_PTI77.xsd @@ -1,951 +1,1093 @@ -<?xml version="1.0" encoding="UTF-8"?> -<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified" attributeFormDefault="unqualified"> - <xs:element name="Request"> - <xs:annotation> - <xs:documentation>Top level element for all XML request transaction types</xs:documentation> - </xs:annotation> - <xs:complexType> - <xs:choice> - <xs:element name="AccountUpdater" type="accountUpdaterType"/> - <xs:element name="Inquiry" type="inquiryType"/> - <xs:element name="NewOrder" type="newOrderType"/> - <xs:element name="EndOfDay" type="endOfDayType"/> - <xs:element name="FlexCache" type="flexCacheType"/> - <xs:element name="Profile" type="profileType"/> - <xs:element name="Reversal" type="reversalType"/> - <xs:element name="MarkForCapture" type="markForCaptureType"/> - <xs:element name="SafetechFraudAnalysis" type="safetechFraudAnalysisType"/> - </xs:choice> - </xs:complexType> - </xs:element> - - <xs:complexType name="baseElementsType"> - <xs:sequence> - <xs:element name="IndustryType" type="valid-industry-types"/> - <xs:element name="CardBrand" type="valid-card-brands" minOccurs="0"/> - <xs:element name="AccountNum" type="xs:string" minOccurs="0"/> - <xs:element name="Exp" type="xs:string" minOccurs="0"/> - <xs:element name="CurrencyCode" type="xs:string" minOccurs="0"/> - <xs:element name="CurrencyExponent" type="xs:string" minOccurs="0"/> - <xs:element name="CardSecValInd" type="xs:string" minOccurs="0"/> - <xs:element name="CardSecVal" type="xs:string" minOccurs="0"/> - <xs:element name="BCRtNum" type="xs:string" minOccurs="0"/> - <xs:element name="CheckDDA" type="xs:string" minOccurs="0"/> - <xs:element name="BankAccountType" type="xs:string" minOccurs="0"/> - <xs:element name="ECPAuthMethod" type="xs:string" minOccurs="0"/> - <xs:element name="BankPmtDelv" type="xs:string" minOccurs="0"/> - <xs:element name="AVSzip" type="xs:string" minOccurs="0"/> - <xs:element name="AVSaddress1" type="xs:string" minOccurs="0"/> - <xs:element name="AVSaddress2" type="xs:string" minOccurs="0"/> - <xs:element name="AVScity" type="xs:string" minOccurs="0"/> - <xs:element name="AVSstate" type="xs:string" minOccurs="0"/> - <xs:element name="AVSphoneNum" type="xs:string" minOccurs="0"/> - <xs:element name="AVSname" type="xs:string" minOccurs="0"/> - <xs:element name="AVScountryCode" type="valid-country-codes" minOccurs="0"/> - <xs:element name="AVSDestzip" type="xs:string" minOccurs="0"/> - <xs:element name="AVSDestaddress1" type="xs:string" minOccurs="0"/> - <xs:element name="AVSDestaddress2" type="xs:string" minOccurs="0"/> - <xs:element name="AVSDestcity" type="xs:string" minOccurs="0"/> - <xs:element name="AVSDeststate" type="xs:string" minOccurs="0"/> - <xs:element name="AVSDestphoneNum" type="xs:string" minOccurs="0"/> - <xs:element name="AVSDestname" type="xs:string" minOccurs="0"/> - <xs:element name="AVSDestcountryCode" type="valid-country-codes" minOccurs="0"/> - <xs:element name="UseCustomerRefNum" type="xs:string" minOccurs="0"/> - <xs:element name="AuthenticationECIInd" type="valid-eci-types" minOccurs="0"/> - <xs:element name="CAVV" type="xs:string" minOccurs="0"/> - <xs:element name="XID" type="xs:string" minOccurs="0"/> - <xs:element name="AAV" type="xs:string" minOccurs="0"/> - <xs:element name="OrderID" type="xs:string"/> - <xs:element name="Amount" type="xs:string" minOccurs="0"/> - <xs:element name="Comments" type="xs:string" minOccurs="0"/> - <xs:element name="EUDDCountryCode" type="xs:string" minOccurs="0"/> - <xs:element name="EUDDBankSortCode" type="xs:string" minOccurs="0"/> - <xs:element name="EUDDRibCode" type="xs:string" minOccurs="0"/> - <xs:element name="BMLCustomerIP" type="xs:string" minOccurs="0"/> - <xs:element name="BMLCustomerEmail" type="xs:string" minOccurs="0"/> - <xs:element name="BMLShippingCost" type="xs:string" minOccurs="0"/> - <xs:element name="BMLTNCVersion" type="xs:string" minOccurs="0"/> - <xs:element name="BMLCustomerRegistrationDate" type="xs:string" minOccurs="0"/> - <xs:element name="BMLCustomerTypeFlag" type="xs:string" minOccurs="0"/> - <xs:element name="BMLItemCategory" type="xs:string" minOccurs="0"/> - <xs:element name="BMLPreapprovalInvitationNum" type="xs:string" minOccurs="0"/> - <xs:element name="BMLMerchantPromotionalCode" type="xs:string" minOccurs="0"/> - <xs:element name="BMLCustomerBirthDate" type="xs:string" minOccurs="0"/> - <xs:element name="BMLCustomerSSN" type="xs:string" minOccurs="0"/> - <xs:element name="BMLCustomerAnnualIncome" type="xs:string" minOccurs="0"/> - <xs:element name="BMLCustomerResidenceStatus" type="xs:string" minOccurs="0"/> - <xs:element name="BMLCustomerCheckingAccount" type="xs:string" minOccurs="0"/> - <xs:element name="BMLCustomerSavingsAccount" type="xs:string" minOccurs="0"/> - <xs:element name="BMLProductDeliveryType" type="xs:string" minOccurs="0"/> - <xs:element name="BillerReferenceNumber" type="xs:string" minOccurs="0"/> - <xs:element name="UseStoredAAVInd" type="xs:string" minOccurs="0"/> - <xs:element name="ECPCheckSerialNumber" type="xs:string" minOccurs="0"/> - <xs:element name="ECPTerminalCity" type="xs:string" minOccurs="0"/> - <xs:element name="ECPTerminalState" type="xs:string" minOccurs="0"/> - <xs:element name="ECPImageReferenceNumber" type="xs:string" minOccurs="0"/> - <xs:element name="CustomerAni" type="xs:string" minOccurs="0"/> - <xs:element name="AVSPhoneType" type="xs:string" minOccurs="0"/> - <xs:element name="AVSDestPhoneType" type="xs:string" minOccurs="0"/> - <xs:element name="CustomerEmail" type="xs:string" minOccurs="0"/> - <xs:element name="CustomerIpAddress" type="xs:string" minOccurs="0"/> - <xs:element name="EmailAddressSubtype" type="xs:string" minOccurs="0"/> - <xs:element name="CustomerBrowserName" type="xs:string" minOccurs="0"/> - <xs:element name="ShippingMethod" type="xs:string" minOccurs="0"/> - </xs:sequence> - </xs:complexType> - - <xs:complexType name="fraudAnalysisType"> - <xs:sequence> - <xs:element name="FraudScoreIndicator" type="xs:string" minOccurs="0"/> - <xs:element name="RulesTrigger" type="xs:string" minOccurs="0"/> - <xs:element name="SafetechMerchantID" type="xs:string" minOccurs="0"/> - <xs:element name="KaptchaSessionID" type="xs:string" minOccurs="0"/> - <xs:element name="WebsiteShortName" type="xs:string" minOccurs="0"/> - <xs:element name="CashValueOfFencibleItems" type="xs:string" minOccurs="0"/> - <xs:element name="CustomerDOB" type="xs:string" minOccurs="0"/> - <xs:element name="CustomerGender" type="xs:string" minOccurs="0"/> - <xs:element name="CustomerDriverLicense" type="xs:string" minOccurs="0"/> - <xs:element name="CustomerID" type="xs:string" minOccurs="0"/> - <xs:element name="CustomerIDCreationTime" type="xs:string" minOccurs="0"/> - <xs:element name="KTTVersionNumber" type="xs:string" minOccurs="0"/> - <xs:element name="KTTDataLength" type="xs:string" minOccurs="0"/> - <xs:element name="KTTDataString" type="xs:string" minOccurs="0"/> - </xs:sequence> - </xs:complexType> - - <xs:complexType name="safetechFraudAnalysisType"> - <xs:sequence> - <xs:element name="OrbitalConnectionUsername" type="xs:string" minOccurs="0"/> - <xs:element name="OrbitalConnectionPassword" type="xs:string" minOccurs="0"/> - <xs:element name="BIN" type="valid-routing-bins"/> - <xs:element name="MerchantID" type="xs:string"/> - <xs:element name="TerminalID" type="terminal-type"/> - <xs:element name="BaseElements" type="baseElementsType" minOccurs="1"/> - <xs:element name="FraudAnalysis" type="fraudAnalysisType" minOccurs="1"/> - </xs:sequence> - </xs:complexType> - - <xs:complexType name="newOrderType"> - <xs:sequence> - <xs:element name="OrbitalConnectionUsername" type="xs:string" minOccurs="0"/> - <xs:element name="OrbitalConnectionPassword" type="xs:string" minOccurs="0"/> - <xs:element name="IndustryType" type="valid-industry-types"/> - <xs:element name="MessageType" type="valid-trans-types"/> - <xs:element name="BIN" type="valid-routing-bins"/> - <xs:element name="MerchantID" type="xs:string"/> - <xs:element name="TerminalID" type="terminal-type"/> - <xs:element name="CardBrand" type="valid-card-brands" minOccurs="0"/> - <xs:element name="AccountNum" type="xs:string" minOccurs="0"/> - <xs:element name="Exp" type="xs:string" minOccurs="0"/> - <xs:element name="CurrencyCode" type="xs:string" minOccurs="0"/> - <xs:element name="CurrencyExponent" type="xs:string" minOccurs="0"/> - <xs:element name="CardSecValInd" type="xs:string" minOccurs="0"/> - <xs:element name="CardSecVal" type="xs:string" minOccurs="0"/> - <xs:element name="DebitCardIssueNum" type="xs:string" minOccurs="0"/> - <xs:element name="DebitCardStartDate" type="xs:string" minOccurs="0"/> - <xs:element name="BCRtNum" type="xs:string" minOccurs="0"/> - <xs:element name="CheckDDA" type="xs:string" minOccurs="0"/> - <xs:element name="BankAccountType" type="xs:string" minOccurs="0"/> - <xs:element name="ECPAuthMethod" type="xs:string" minOccurs="0"/> - <xs:element name="BankPmtDelv" type="xs:string" minOccurs="0"/> - <xs:element name="AVSzip" type="xs:string" minOccurs="0"/> - <xs:element name="AVSaddress1" type="xs:string" minOccurs="0"/> - <xs:element name="AVSaddress2" type="xs:string" minOccurs="0"/> - <xs:element name="AVScity" type="xs:string" minOccurs="0"/> - <xs:element name="AVSstate" type="xs:string" minOccurs="0"/> - <xs:element name="AVSphoneNum" type="xs:string" minOccurs="0"/> - <xs:element name="AVSname" type="xs:string" minOccurs="0"/> - <xs:element name="AVScountryCode" type="valid-country-codes" minOccurs="0"/> - <xs:element name="AVSDestzip" type="xs:string" minOccurs="0"/> - <xs:element name="AVSDestaddress1" type="xs:string" minOccurs="0"/> - <xs:element name="AVSDestaddress2" type="xs:string" minOccurs="0"/> - <xs:element name="AVSDestcity" type="xs:string" minOccurs="0"/> - <xs:element name="AVSDeststate" type="xs:string" minOccurs="0"/> - <xs:element name="AVSDestphoneNum" type="xs:string" minOccurs="0"/> - <xs:element name="AVSDestname" type="xs:string" minOccurs="0"/> - <xs:element name="AVSDestcountryCode" type="valid-country-codes" minOccurs="0"/> - <xs:element name="CustomerProfileFromOrderInd" type="xs:string" minOccurs="0"/> - <xs:element name="CustomerRefNum" type="xs:string" minOccurs="0"/> - <xs:element name="CustomerProfileOrderOverrideInd" type="xs:string" minOccurs="0"/> - <xs:element name="Status" type="xs:string" minOccurs="0"/> - <xs:element name="AuthenticationECIInd" type="valid-eci-types" minOccurs="0"/> - <xs:element name="CAVV" type="xs:string" minOccurs="0"/> - <xs:element name="XID" type="xs:string" minOccurs="0"/> - <xs:element name="PriorAuthID" type="vallid-prior-auth" minOccurs="0"/> - <xs:element name="OrderID" type="xs:string"/> - <xs:element name="Amount" type="xs:string" minOccurs="0"/> - <xs:element name="Comments" type="xs:string" minOccurs="0"/> - <xs:element name="ShippingRef" type="xs:string" minOccurs="0"/> - <xs:element name="TaxInd" type="valid-tax-inds" minOccurs="0"/> - <xs:element name="Tax" type="xs:string" minOccurs="0"/> - <xs:element name="AMEXTranAdvAddn1" type="xs:string" minOccurs="0"/> - <xs:element name="AMEXTranAdvAddn2" type="xs:string" minOccurs="0"/> - <xs:element name="AMEXTranAdvAddn3" type="xs:string" minOccurs="0"/> - <xs:element name="AMEXTranAdvAddn4" type="xs:string" minOccurs="0"/> - <xs:element name="AAV" type="xs:string" minOccurs="0"/> - <xs:element name="SDMerchantName" type="xs:string" minOccurs="0"/> - <xs:element name="SDProductDescription" type="xs:string" minOccurs="0"/> - <xs:element name="SDMerchantCity" type="xs:string" minOccurs="0"/> - <xs:element name="SDMerchantPhone" type="xs:string" minOccurs="0"/> - <xs:element name="SDMerchantURL" type="xs:string" minOccurs="0"/> - <xs:element name="SDMerchantEmail" type="xs:string" minOccurs="0"/> - <xs:element name="RecurringInd" type="recurring-ind-types" minOccurs="0"/> - <xs:element name="EUDDCountryCode" type="xs:string" minOccurs="0"/> - <xs:element name="EUDDBankSortCode" type="xs:string" minOccurs="0"/> - <xs:element name="EUDDRibCode" type="xs:string" minOccurs="0"/> - <xs:element name="BMLCustomerIP" type="xs:string" minOccurs="0"/> - <xs:element name="BMLCustomerEmail" type="xs:string" minOccurs="0"/> - <xs:element name="BMLShippingCost" type="xs:string" minOccurs="0"/> - <xs:element name="BMLTNCVersion" type="xs:string" minOccurs="0"/> - <xs:element name="BMLCustomerRegistrationDate" type="xs:string" minOccurs="0"/> - <xs:element name="BMLCustomerTypeFlag" type="xs:string" minOccurs="0"/> - <xs:element name="BMLItemCategory" type="xs:string" minOccurs="0"/> - <xs:element name="BMLPreapprovalInvitationNum" type="xs:string" minOccurs="0"/> - <xs:element name="BMLMerchantPromotionalCode" type="xs:string" minOccurs="0"/> - <xs:element name="BMLCustomerBirthDate" type="xs:string" minOccurs="0"/> - <xs:element name="BMLCustomerSSN" type="xs:string" minOccurs="0"/> - <xs:element name="BMLCustomerAnnualIncome" type="xs:string" minOccurs="0"/> - <xs:element name="BMLCustomerResidenceStatus" type="xs:string" minOccurs="0"/> - <xs:element name="BMLCustomerCheckingAccount" type="xs:string" minOccurs="0"/> - <xs:element name="BMLCustomerSavingsAccount" type="xs:string" minOccurs="0"/> - <xs:element name="BMLProductDeliveryType" type="xs:string" minOccurs="0"/> - <xs:element name="BillerReferenceNumber" type="xs:string" minOccurs="0"/> - <xs:element name="MBType" type="xs:string" minOccurs="0"/> - <xs:element name="MBOrderIdGenerationMethod" type="xs:string" minOccurs="0"/> - <xs:element name="MBRecurringStartDate" type="xs:string" minOccurs="0"/> - <xs:element name="MBRecurringEndDate" type="xs:string" minOccurs="0"/> - <xs:element name="MBRecurringNoEndDateFlag" type="xs:string" minOccurs="0"/> - <xs:element name="MBRecurringMaxBillings" type="xs:string" minOccurs="0"/> - <xs:element name="MBRecurringFrequency" type="xs:string" minOccurs="0"/> - <xs:element name="MBDeferredBillDate" type="xs:string" minOccurs="0"/> - <xs:element name="MBMicroPaymentMaxDollarValue" type="xs:string" minOccurs="0"/> - <xs:element name="MBMicroPaymentMaxBillingDays" type="xs:string" minOccurs="0"/> - <xs:element name="MBMicroPaymentMaxTransactions" type="xs:string" minOccurs="0"/> - <xs:element name="TxRefNum" type="xs:string" minOccurs="0"/> - <xs:element name="PCOrderNum" type="xs:string" minOccurs="0"/> - <xs:element name="PCDestZip" type="xs:string" minOccurs="0"/> - <xs:element name="PCDestName" type="xs:string" minOccurs="0"/> - <xs:element name="PCDestAddress1" type="xs:string" minOccurs="0"/> - <xs:element name="PCDestAddress2" type="xs:string" minOccurs="0"/> - <xs:element name="PCDestCity" type="xs:string" minOccurs="0"/> - <xs:element name="PCDestState" type="xs:string" minOccurs="0"/> - <xs:element name="PC3FreightAmt" type="xs:string" minOccurs="0"/> - <xs:element name="PC3DutyAmt" type="xs:string" minOccurs="0"/> - <xs:element name="PC3DestCountryCd" type="xs:string" minOccurs="0"/> - <xs:element name="PC3ShipFromZip" type="xs:string" minOccurs="0"/> - <xs:element name="PC3DiscAmt" type="xs:string" minOccurs="0"/> - <xs:element name="PC3VATtaxAmt" type="xs:string" minOccurs="0"/> - <xs:element name="PC3VATtaxRate" type="xs:string" minOccurs="0"/> - <xs:element name="PC3AltTaxInd" type="xs:string" minOccurs="0"/> - <xs:element name="PC3AltTaxAmt" type="xs:string" minOccurs="0"/> - <xs:element name="PC3LineItemCount" type="xs:string" minOccurs="0"/> - <xs:element name="PC3LineItemArray" type="PC3LineItemArray" minOccurs="0"/> - <xs:element name="PartialAuthInd" type="xs:string" minOccurs="0"/> - <xs:element name="AccountUpdaterEligibility" type="xs:string" minOccurs="0"/> - <xs:element name="UseStoredAAVInd" type="xs:string" minOccurs="0"/> - <xs:element name="ECPActionCode" type="xs:string" minOccurs="0"/> - <xs:element name="ECPCheckSerialNumber" type="xs:string" minOccurs="0"/> - <xs:element name="ECPTerminalCity" type="xs:string" minOccurs="0"/> - <xs:element name="ECPTerminalState" type="xs:string" minOccurs="0"/> - <xs:element name="ECPImageReferenceNumber" type="xs:string" minOccurs="0"/> - <xs:element name="CustomerAni" type="xs:string" minOccurs="0"/> - <xs:element name="AVSPhoneType" type="xs:string" minOccurs="0"/> - <xs:element name="AVSDestPhoneType" type="xs:string" minOccurs="0"/> - <xs:element name="CustomerEmail" type="xs:string" minOccurs="0"/> - <xs:element name="CustomerIpAddress" type="xs:string" minOccurs="0"/> - <xs:element name="EmailAddressSubtype" type="xs:string" minOccurs="0"/> - <xs:element name="CustomerBrowserName" type="xs:string" minOccurs="0"/> - <xs:element name="ShippingMethod" type="xs:string" minOccurs="0"/> - <xs:element name="FraudAnalysis" type="fraudAnalysisType" minOccurs="0"/> - </xs:sequence> - </xs:complexType> - <xs:complexType name="PC3LineItemArray"> - <xs:sequence> - <xs:element name="PC3LineItem" type="PC3LineItemType" maxOccurs="unbounded"/> - </xs:sequence> - </xs:complexType> - <xs:complexType name="PC3LineItemType"> - <xs:sequence> - <xs:element name="PC3DtlIndex" type="xs:string" minOccurs="0"/> - <xs:element name="PC3DtlDesc" type="xs:string" minOccurs="0"/> - <xs:element name="PC3DtlProdCd" type="xs:string" minOccurs="0"/> - <xs:element name="PC3DtlQty" type="xs:string" minOccurs="0"/> - <xs:element name="PC3DtlUOM" type="xs:string" minOccurs="0"/> - <xs:element name="PC3DtlTaxAmt" type="xs:string" minOccurs="0"/> - <xs:element name="PC3DtlTaxRate" type="xs:string" minOccurs="0"/> - <xs:element name="PC3Dtllinetot" type="xs:string" minOccurs="0"/> - <xs:element name="PC3DtlDisc" type="xs:string" minOccurs="0"/> - <xs:element name="PC3DtlCommCd" type="xs:string" minOccurs="0"/> - <xs:element name="PC3DtlUnitCost" type="xs:string" minOccurs="0"/> - <xs:element name="PC3DtlGrossNet" type="xs:string" minOccurs="0"/> - <xs:element name="PC3DtlTaxType" type="xs:string" minOccurs="0"/> - <xs:element name="PC3DtlDiscInd" type="xs:string" minOccurs="0"/> - <xs:element name="PC3DtlDebitInd" type="xs:string" minOccurs="0"/> - </xs:sequence> - </xs:complexType> - <xs:complexType name="markForCaptureType"> - <xs:sequence> - <xs:element name="OrbitalConnectionUsername" type="xs:string" minOccurs="0"/> - <xs:element name="OrbitalConnectionPassword" type="xs:string" minOccurs="0"/> - <xs:element name="OrderID" type="xs:string"/> - <xs:element name="Amount" type="xs:string" minOccurs="0"/> - <xs:element name="TaxInd" type="valid-tax-inds" minOccurs="0"/> - <xs:element name="Tax" type="xs:string" minOccurs="0"/> - <xs:element name="BIN" type="valid-routing-bins"/> - <xs:element name="MerchantID" type="xs:string"/> - <xs:element name="TerminalID" type="terminal-type"/> - <xs:element name="TxRefNum" type="xs:string"/> - <xs:element name="PCOrderNum" type="xs:string" minOccurs="0"/> - <xs:element name="PCDestZip" type="xs:string" minOccurs="0"/> - <xs:element name="PCDestName" type="xs:string" minOccurs="0"/> - <xs:element name="PCDestAddress1" type="xs:string" minOccurs="0"/> - <xs:element name="PCDestAddress2" type="xs:string" minOccurs="0"/> - <xs:element name="PCDestCity" type="xs:string" minOccurs="0"/> - <xs:element name="PCDestState" type="xs:string" minOccurs="0"/> - <xs:element name="AMEXTranAdvAddn1" type="xs:string" minOccurs="0"/> - <xs:element name="AMEXTranAdvAddn2" type="xs:string" minOccurs="0"/> - <xs:element name="AMEXTranAdvAddn3" type="xs:string" minOccurs="0"/> - <xs:element name="AMEXTranAdvAddn4" type="xs:string" minOccurs="0"/> - <xs:element name="PC3FreightAmt" type="xs:string" minOccurs="0"/> - <xs:element name="PC3DutyAmt" type="xs:string" minOccurs="0"/> - <xs:element name="PC3DestCountryCd" type="xs:string" minOccurs="0"/> - <xs:element name="PC3ShipFromZip" type="xs:string" minOccurs="0"/> - <xs:element name="PC3DiscAmt" type="xs:string" minOccurs="0"/> - <xs:element name="PC3VATtaxAmt" type="xs:string" minOccurs="0"/> - <xs:element name="PC3VATtaxRate" type="xs:string" minOccurs="0"/> - <xs:element name="PC3AltTaxInd" type="xs:string" minOccurs="0"/> - <xs:element name="PC3AltTaxID" type="xs:string" minOccurs="0"/> - <xs:element name="PC3AltTaxAmt" type="xs:string" minOccurs="0"/> - <xs:element name="PC3LineItemCount" type="xs:string" minOccurs="0"/> - <xs:element name="PC3LineItemArray" type="PC3LineItemArray" minOccurs="0"/> - </xs:sequence> - </xs:complexType> - <xs:complexType name="reversalType"> - <xs:sequence> - <xs:element name="OrbitalConnectionUsername" type="xs:string" minOccurs="0"/> - <xs:element name="OrbitalConnectionPassword" type="xs:string" minOccurs="0"/> - <xs:element name="TxRefNum" type="xs:string"/> - <xs:element name="TxRefIdx" type="xs:string" minOccurs="0"/> - <xs:element name="AdjustedAmt" type="xs:string" minOccurs="0"/> - <xs:element name="OrderID" type="xs:string"/> - <xs:element name="BIN" type="valid-routing-bins"/> - <xs:element name="MerchantID" type="xs:string"/> - <xs:element name="TerminalID" type="terminal-type"/> - <xs:element name="ReversalRetryNumber" type="xs:string" minOccurs="0"/> - <xs:element name="OnlineReversalInd" type="xs:string" minOccurs="0"/> - </xs:sequence> - </xs:complexType> - <xs:complexType name="endOfDayType"> - <xs:sequence> - <xs:element name="OrbitalConnectionUsername" type="xs:string" minOccurs="0"/> - <xs:element name="OrbitalConnectionPassword" type="xs:string" minOccurs="0"/> - <xs:element name="BIN" type="valid-routing-bins"/> - <xs:element name="MerchantID" type="xs:string"/> - <xs:element name="TerminalID" type="terminal-type"/> - <xs:element ref="SettleRejectHoldingBin" minOccurs="0"/> - </xs:sequence> - </xs:complexType> - <xs:complexType name="inquiryType"> - <xs:sequence> - <xs:element name="OrbitalConnectionUsername" type="xs:string" minOccurs="0"/> - <xs:element name="OrbitalConnectionPassword" type="xs:string" minOccurs="0"/> - <xs:element name="BIN" type="valid-routing-bins"/> - <xs:element name="MerchantID" type="xs:string"/> - <xs:element name="TerminalID" type="terminal-type"/> - <xs:element name="OrderID" type="xs:string" minOccurs="0"/> - <xs:element name="InquiryRetryNumber" type="xs:string"/> - </xs:sequence> - </xs:complexType> - <xs:complexType name="accountUpdaterType"> - <xs:sequence> - <xs:element name="OrbitalConnectionUsername" type="xs:string" minOccurs="0"/> - <xs:element name="OrbitalConnectionPassword" type="xs:string" minOccurs="0"/> - <xs:element name="CustomerBin" type="valid-routing-bins"/> - <xs:element name="CustomerMerchantID" type="xs:string"/> - <xs:element name="CustomerRefNum" type="xs:string" /> - <xs:element name="CustomerProfileAction" type="xs:string" minOccurs="0"/> - <xs:element name="ScheduledDate" type="xs:string" minOccurs="0"/> - </xs:sequence> - </xs:complexType> - <xs:complexType name="profileType"> - <xs:sequence> - <xs:element name="OrbitalConnectionUsername" type="xs:string" minOccurs="0"/> - <xs:element name="OrbitalConnectionPassword" type="xs:string" minOccurs="0"/> - <xs:element name="CustomerBin" type="valid-routing-bins"/> - <xs:element name="CustomerMerchantID" type="xs:string"/> - <xs:element name="CustomerName" type="xs:string" minOccurs="0"/> - <xs:element name="CustomerRefNum" type="xs:string" minOccurs="0"/> - <xs:element name="CustomerAddress1" type="xs:string" minOccurs="0"/> - <xs:element name="CustomerAddress2" type="xs:string" minOccurs="0"/> - <xs:element name="CustomerCity" type="xs:string" minOccurs="0"/> - <xs:element name="CustomerState" type="xs:string" minOccurs="0"/> - <xs:element name="CustomerZIP" type="xs:string" minOccurs="0"/> - <xs:element name="CustomerEmail" type="xs:string" minOccurs="0"/> - <xs:element name="CustomerPhone" type="xs:string" minOccurs="0"/> - <xs:element name="CustomerCountryCode" type="xs:string" minOccurs="0"/> - <xs:element name="CustomerProfileAction" type="profile-action-types"/> - <xs:element name="CustomerProfileOrderOverrideInd" type="valid-profileOrderOverideInds" minOccurs="0"/> - <xs:element name="CustomerProfileFromOrderInd" type="valid-profileFromOrderInd" minOccurs="0"/> - <xs:element name="OrderDefaultDescription" type="xs:string" minOccurs="0"/> - <xs:element name="OrderDefaultAmount" type="xs:string" minOccurs="0"/> - <xs:element name="CustomerAccountType" type="valid-customer-account-types" minOccurs="0"/> - <xs:element name="Status" type="xs:string" minOccurs="0"/> - <xs:element name="CCAccountNum" type="xs:string" minOccurs="0"/> - <xs:element name="CCExpireDate" type="xs:string" minOccurs="0"/> - <xs:element name="ECPAccountDDA" type="xs:string" minOccurs="0"/> - <xs:element name="ECPAccountType" type="xs:string" minOccurs="0"/> - <xs:element name="ECPAccountRT" type="xs:string" minOccurs="0"/> - <xs:element name="ECPBankPmtDlv" type="xs:string" minOccurs="0"/> - <xs:element name="MBType" type="xs:string" minOccurs="0"/> - <xs:element name="MBOrderIdGenerationMethod" type="xs:string" minOccurs="0"/> - <xs:element name="MBRecurringStartDate" type="xs:string" minOccurs="0"/> - <xs:element name="MBRecurringEndDate" type="xs:string" minOccurs="0"/> - <xs:element name="MBRecurringNoEndDateFlag" type="xs:string" minOccurs="0"/> - <xs:element name="MBRecurringMaxBillings" type="xs:string" minOccurs="0"/> - <xs:element name="MBRecurringFrequency" type="xs:string" minOccurs="0"/> - <xs:element name="MBDeferredBillDate" type="xs:string" minOccurs="0"/> - <xs:element name="MBMicroPaymentMaxDollarValue" type="xs:string" minOccurs="0"/> - <xs:element name="MBMicroPaymentMaxBillingDays" type="xs:string" minOccurs="0"/> - <xs:element name="MBMicroPaymentMaxTransactions" type="xs:string" minOccurs="0"/> - <xs:element name="MBCancelDate" type="xs:string" minOccurs="0"/> - <xs:element name="MBRestoreBillingDate" type="xs:string" minOccurs="0"/> - <xs:element name="MBRemoveFlag" type="xs:string" minOccurs="0"/> - <xs:element name="EUDDCountryCode" type="xs:string" minOccurs="0"/> - <xs:element name="EUDDBankSortCode" type="xs:string" minOccurs="0"/> - <xs:element name="EUDDRibCode" type="xs:string" minOccurs="0"/> - <xs:element name="SDMerchantName" type="xs:string" minOccurs="0"/> - <xs:element name="SDProductDescription" type="xs:string" minOccurs="0"/> - <xs:element name="SDMerchantCity" type="xs:string" minOccurs="0"/> - <xs:element name="SDMerchantPhone" type="xs:string" minOccurs="0"/> - <xs:element name="SDMerchantURL" type="xs:string" minOccurs="0"/> - <xs:element name="SDMerchantEmail" type="xs:string" minOccurs="0"/> - <xs:element name="BillerReferenceNumber" type="xs:string" minOccurs="0"/> - <xs:element name="AccountUpdaterEligibility" type="xs:string" minOccurs="0"/> - </xs:sequence> - </xs:complexType> - <xs:complexType name="flexCacheType"> - <xs:sequence> - <xs:element name="OrbitalConnectionUsername" type="xs:string" minOccurs="0"/> - <xs:element name="OrbitalConnectionPassword" type="xs:string" minOccurs="0"/> - <xs:element name="BIN" type="valid-routing-bins"/> - <xs:element name="MerchantID" type="xs:string"/> - <xs:element name="TerminalID" type="terminal-type"/> - <xs:element name="AccountNum" type="xs:string" minOccurs="0"/> - <xs:element name="OrderID" type="xs:string"/> - <xs:element name="Amount" type="xs:string" minOccurs="0"/> - <xs:element name="CardSecVal" type="xs:string" minOccurs="0"/> - <xs:element name="Comments" type="xs:string" minOccurs="0"/> - <xs:element name="ShippingRef" type="xs:string" minOccurs="0"/> - <xs:element name="IndustryType" type="valid-industry-types"/> - <xs:element name="FlexAutoAuthInd" type="yes-or-no"/> - <xs:element name="FlexPartialRedemptionInd" type="yes-or-no"/> - <xs:element name="FlexAction" type="flex-action-types"/> - <xs:element name="StartAccountNum" type="xs:string" minOccurs="0"/> - <xs:element name="ActivationCount" type="xs:string" minOccurs="0"/> - <xs:element name="TxRefNum" type="xs:string" minOccurs="0"/> - <xs:element name="FlexEmployeeNumber" type="xs:string" minOccurs="0"/> - <xs:element name="PriorAuthID" type="vallid-prior-auth" minOccurs="0"/> - <xs:element name="AVSzip" type="xs:string" minOccurs="0"/> - <xs:element name="AVSaddress1" type="xs:string" minOccurs="0"/> - <xs:element name="AVSaddress2" type="xs:string" minOccurs="0"/> - <xs:element name="AVScity" type="xs:string" minOccurs="0"/> - <xs:element name="AVSstate" type="xs:string" minOccurs="0"/> - <xs:element name="AVSphoneNum" type="xs:string" minOccurs="0"/> - <xs:element name="AVSname" type="xs:string" minOccurs="0"/> - <xs:element name="AVScountryCode" type="valid-country-codes" minOccurs="0"/> - <xs:element name="AVSDestzip" type="xs:string" minOccurs="0"/> - <xs:element name="AVSDestaddress1" type="xs:string" minOccurs="0"/> - <xs:element name="AVSDestaddress2" type="xs:string" minOccurs="0"/> - <xs:element name="AVSDestcity" type="xs:string" minOccurs="0"/> - <xs:element name="AVSDeststate" type="xs:string" minOccurs="0"/> - <xs:element name="AVSDestphoneNum" type="xs:string" minOccurs="0"/> - <xs:element name="AVSDestname" type="xs:string" minOccurs="0"/> - <xs:element name="AVSDestcountryCode" type="valid-country-codes" minOccurs="0"/> - <xs:element name="CustomerAni" type="xs:string" minOccurs="0"/> - <xs:element name="AVSPhoneType" type="xs:string" minOccurs="0"/> - <xs:element name="AVSDestPhoneType" type="xs:string" minOccurs="0"/> - <xs:element name="CustomerEmail" type="xs:string" minOccurs="0"/> - <xs:element name="CustomerIpAddress" type="xs:string" minOccurs="0"/> - <xs:element name="EmailAddressSubtype" type="xs:string" minOccurs="0"/> - <xs:element name="CustomerBrowserName" type="xs:string" minOccurs="0"/> - <xs:element name="ShippingMethod" type="xs:string" minOccurs="0"/> - <xs:element name="FraudAnalysis" type="fraudAnalysisType" minOccurs="0"/> - </xs:sequence> - </xs:complexType> - <xs:simpleType name="terminal-type"> - <xs:restriction base="xs:string"> - <xs:maxLength value="3"/> - <xs:minLength value="1"/> - </xs:restriction> - </xs:simpleType> - <xs:simpleType name="valid-trans-types"> - <xs:annotation> - <xs:documentation>New order Transaction Types</xs:documentation> - </xs:annotation> - <xs:restriction base="xs:string"> - <xs:maxLength value="20"/> - <xs:enumeration value="A"> - <xs:annotation> - <xs:documentation>Auth Only No Capture</xs:documentation> - </xs:annotation> - </xs:enumeration> - <xs:enumeration value="AC"> - <xs:annotation> - <xs:documentation>Auth and Capture</xs:documentation> - </xs:annotation> - </xs:enumeration> - <xs:enumeration value="F"> - <xs:annotation> - <xs:documentation>Force Auth No Capture and no online authorization</xs:documentation> - </xs:annotation> - </xs:enumeration> - <xs:enumeration value="FR"> - <xs:annotation> - <xs:documentation>Force Auth No Capture and no online authorization</xs:documentation> - </xs:annotation> - </xs:enumeration> - <xs:enumeration value="FC"> - <xs:annotation> - <xs:documentation>Force Auth and Capture no online authorization</xs:documentation> - </xs:annotation> - </xs:enumeration> - <xs:enumeration value="R"> - <xs:annotation> - <xs:documentation>Refund and Capture no online authorization</xs:documentation> - </xs:annotation> - </xs:enumeration> - </xs:restriction> - </xs:simpleType> - <xs:simpleType name="valid-industry-types"> - <xs:annotation> - <xs:documentation>New order Industry Types</xs:documentation> - </xs:annotation> - <xs:restriction base="xs:string"> - <xs:maxLength value="20"/> - <xs:enumeration value="EC"> - <xs:annotation> - <xs:documentation>Ecommerce transaction</xs:documentation> - </xs:annotation> - </xs:enumeration> - <xs:enumeration value="RC"> - <xs:annotation> - <xs:documentation>Recurring Payment transaction</xs:documentation> - </xs:annotation> - </xs:enumeration> - <xs:enumeration value="MO"> - <xs:annotation> - <xs:documentation>Mail Order Telephone Order transaction</xs:documentation> - </xs:annotation> - </xs:enumeration> - <xs:enumeration value="IV"> - <xs:annotation> - <xs:documentation>Interactive Voice Response</xs:documentation> - </xs:annotation> - </xs:enumeration> - <xs:enumeration value="IN"> - <xs:annotation> - <xs:documentation>Interactive Voice Response</xs:documentation> - </xs:annotation> - </xs:enumeration> - </xs:restriction> - </xs:simpleType> - <xs:simpleType name="valid-tax-inds"> - <xs:union> - <xs:simpleType> - <xs:restriction base="xs:string"> - <xs:maxLength value="1"/> - <xs:enumeration value="0"> - <xs:annotation> - <xs:documentation>Tax not provided</xs:documentation> - </xs:annotation> - </xs:enumeration> - <xs:enumeration value="1"> - <xs:annotation> - <xs:documentation>Tax included</xs:documentation> - </xs:annotation> - </xs:enumeration> - <xs:enumeration value="2"> - <xs:annotation> - <xs:documentation>Non-taxable transaction</xs:documentation> - </xs:annotation> - </xs:enumeration> - </xs:restriction> - </xs:simpleType> - <xs:simpleType> - <xs:restriction base="xs:string"> - <xs:enumeration value=""/> - </xs:restriction> - </xs:simpleType> - </xs:union> - </xs:simpleType> - <xs:simpleType name="valid-routing-bins"> - <xs:restriction base="xs:string"> - <xs:maxLength value="6"/> - <xs:enumeration value="000001"> - <xs:annotation> - <xs:documentation>Stratus</xs:documentation> - </xs:annotation> - </xs:enumeration> - <xs:enumeration value="000002"> - <xs:annotation> - <xs:documentation>Tandam</xs:documentation> - </xs:annotation> - </xs:enumeration> - </xs:restriction> - </xs:simpleType> - <xs:simpleType name="valid-profileOrderOverideInds"> - <xs:union> - <xs:simpleType> - <xs:restriction base="xs:string"> - <xs:maxLength value="2"/> - <xs:enumeration value="NO"> - <xs:annotation> - <xs:documentation>No mapping to order data</xs:documentation> - </xs:annotation> - </xs:enumeration> - <xs:enumeration value="OI"> - <xs:annotation> - <xs:documentation>Use customer reference for OrderID</xs:documentation> - </xs:annotation> - </xs:enumeration> - <xs:enumeration value="OA"> - <xs:annotation> - <xs:documentation>Use customer reference for both Order Id and Order Description</xs:documentation> - </xs:annotation> - </xs:enumeration> - <xs:enumeration value="OD"> - <xs:annotation> - <xs:documentation>Use customer reference for Order Description</xs:documentation> - </xs:annotation> - </xs:enumeration> - </xs:restriction> - </xs:simpleType> - <xs:simpleType> - <xs:restriction base="xs:string"> - <xs:enumeration value=""/> - </xs:restriction> - </xs:simpleType> - </xs:union> - </xs:simpleType> - <xs:simpleType name="valid-profileFromOrderInd"> - <xs:union> - <xs:simpleType> - <xs:restriction base="xs:string"> - <xs:maxLength value="1"/> - <xs:enumeration value="A"> - <xs:annotation> - <xs:documentation>Auto Generate the CustomerRefNum</xs:documentation> - </xs:annotation> - </xs:enumeration> - <xs:enumeration value="O"> - <xs:annotation> - <xs:documentation>Use OrderID as the CustomerRefNum</xs:documentation> - </xs:annotation> - </xs:enumeration> - <xs:enumeration value="S"> - <xs:annotation> - <xs:documentation>Use CustomerRefNum Element</xs:documentation> - </xs:annotation> - </xs:enumeration> - <xs:enumeration value="D"> - <xs:annotation> - <xs:documentation>Use the description as the CustomerRefNum</xs:documentation> - </xs:annotation> - </xs:enumeration> - <xs:enumeration value="I"> - <xs:annotation> - <xs:documentation>Ignore. We will Ignore this entry if it's passed in the XML</xs:documentation> - </xs:annotation> - </xs:enumeration> - </xs:restriction> - </xs:simpleType> - <xs:simpleType> - <xs:restriction base="xs:string"> - <xs:enumeration value=""/> - </xs:restriction> - </xs:simpleType> - </xs:union> - </xs:simpleType> - <xs:simpleType name="valid-card-brands"> - <xs:union> - <xs:simpleType> - <xs:restriction base="xs:string"> - <xs:maxLength value="2"/> - <xs:enumeration value="AX"> - <xs:annotation> - <xs:documentation>American Express</xs:documentation> - </xs:annotation> - </xs:enumeration> - <xs:enumeration value="CB"> - <xs:annotation> - <xs:documentation>Carte Blanche</xs:documentation> - </xs:annotation> - </xs:enumeration> - <xs:enumeration value="DC"> - <xs:annotation> - <xs:documentation>Diners Club</xs:documentation> - </xs:annotation> - </xs:enumeration> - <xs:enumeration value="DI"> - <xs:annotation> - <xs:documentation>Discover</xs:documentation> - </xs:annotation> - </xs:enumeration> - <xs:enumeration value="GC"> - <xs:annotation> - <xs:documentation>GE Twinpay Credit</xs:documentation> - </xs:annotation> - </xs:enumeration> - <xs:enumeration value="GE"> - <xs:annotation> - <xs:documentation>GECC Private Label Credit</xs:documentation> - </xs:annotation> - </xs:enumeration> - <xs:enumeration value="JC"> - <xs:annotation> - <xs:documentation>JCB</xs:documentation> - </xs:annotation> - </xs:enumeration> - <xs:enumeration value="MC"> - <xs:annotation> - <xs:documentation>Mastercard</xs:documentation> - </xs:annotation> - </xs:enumeration> - <xs:enumeration value="VI"> - <xs:annotation> - <xs:documentation>Visa</xs:documentation> - </xs:annotation> - </xs:enumeration> - <xs:enumeration value="GD"> - <xs:annotation> - <xs:documentation>GE Twinpay Debit</xs:documentation> - </xs:annotation> - </xs:enumeration> - <xs:enumeration value="SW"> - <xs:annotation> - <xs:documentation>Switch / Solo</xs:documentation> - </xs:annotation> - </xs:enumeration> - <xs:enumeration value="EC"> - <xs:annotation> - <xs:documentation>Electronic Check</xs:documentation> - </xs:annotation> - </xs:enumeration> - <xs:enumeration value="FC"> - <xs:annotation> - <xs:documentation>Flex Cache</xs:documentation> - </xs:annotation> - </xs:enumeration> - <xs:enumeration value="ED"> - <xs:annotation> - <xs:documentation>European Direct Debit </xs:documentation> - </xs:annotation> - </xs:enumeration> - <xs:enumeration value="BL"> - <xs:annotation> - <xs:documentation>Bill Me Later </xs:documentation> - </xs:annotation> - </xs:enumeration> - <xs:enumeration value="DP"> - <xs:annotation> - <xs:documentation>PINLess Debit </xs:documentation> - </xs:annotation> - </xs:enumeration> - <xs:enumeration value="IM"> - <xs:annotation> - <xs:documentation>International Maestro</xs:documentation> - </xs:annotation> - </xs:enumeration> - </xs:restriction> - </xs:simpleType> - <xs:simpleType> - <xs:restriction base="xs:string"> - <xs:enumeration value=""/> - </xs:restriction> - </xs:simpleType> - </xs:union> - </xs:simpleType> - <xs:simpleType name="valid-customer-account-types"> - <xs:union> - <xs:simpleType> - <xs:restriction base="xs:string"> - <xs:maxLength value="2"/> - <xs:enumeration value="CC"> - <xs:annotation> - <xs:documentation>Credit Card</xs:documentation> - </xs:annotation> - </xs:enumeration> - <xs:enumeration value="SW"> - <xs:annotation> - <xs:documentation>Swith/Solo</xs:documentation> - </xs:annotation> - </xs:enumeration> - <xs:enumeration value="EC"> - <xs:annotation> - <xs:documentation>Electronic Check</xs:documentation> - </xs:annotation> - </xs:enumeration> - <xs:enumeration value="DP"> - <xs:annotation> - <xs:documentation>PINLess Debit</xs:documentation> - </xs:annotation> - </xs:enumeration> - <xs:enumeration value="ED"> - <xs:annotation> - <xs:documentation>European Direct Debit</xs:documentation> - </xs:annotation> - </xs:enumeration> - <xs:enumeration value="IM"> - <xs:annotation> - <xs:documentation>International Maestro</xs:documentation> - </xs:annotation> - </xs:enumeration> - </xs:restriction> - </xs:simpleType> - <xs:simpleType> - <xs:restriction base="xs:string"> - <xs:enumeration value=""/> - </xs:restriction> - </xs:simpleType> - </xs:union> - </xs:simpleType> - <xs:simpleType name="valid-country-codes"> - <xs:union> - <xs:simpleType> - <xs:restriction base="xs:string"> - <xs:maxLength value="2"/> - <xs:enumeration value="US"> - <xs:annotation> - <xs:documentation>United States</xs:documentation> - </xs:annotation> - </xs:enumeration> - <xs:enumeration value="CA"> - <xs:annotation> - <xs:documentation>Canada</xs:documentation> - </xs:annotation> - </xs:enumeration> - <xs:enumeration value="GB"> - <xs:annotation> - <xs:documentation>Germany</xs:documentation> - </xs:annotation> - </xs:enumeration> - <xs:enumeration value="UK"> - <xs:annotation> - <xs:documentation>Great Britain</xs:documentation> - </xs:annotation> - </xs:enumeration> - </xs:restriction> - </xs:simpleType> - <xs:simpleType> - <xs:restriction base="xs:string"> - <xs:enumeration value=""/> - </xs:restriction> - </xs:simpleType> - </xs:union> - </xs:simpleType> - <xs:simpleType name="yes-or-no"> - <xs:restriction base="xs:string"> - <xs:maxLength value="1"/> - <xs:enumeration value="Y"> - <xs:annotation> - <xs:documentation>Yes</xs:documentation> - </xs:annotation> - </xs:enumeration> - <xs:enumeration value="y"> - <xs:annotation> - <xs:documentation>Yes</xs:documentation> - </xs:annotation> - </xs:enumeration> - <xs:enumeration value="N"> - <xs:annotation> - <xs:documentation>No</xs:documentation> - </xs:annotation> - </xs:enumeration> - <xs:enumeration value="n"> - <xs:annotation> - <xs:documentation>No</xs:documentation> - </xs:annotation> - </xs:enumeration> - </xs:restriction> - </xs:simpleType> - <xs:simpleType name="flex-action-types"> - <xs:restriction base="xs:string"> - <xs:maxLength value="30"/> - <xs:pattern value="([Bb][Aa][Ll][Aa][Nn][Cc][Ee][Ii][Nn][Qq][Uu][Ii][Rr][Yy])"/> - <xs:pattern value="([Aa][Dd][Dd][Vv][Aa][Ll][Uu][Ee])"/> - <xs:pattern value="([Rr][Ee][Ff][Uu][Nn][Dd])"/> - <xs:pattern value="([Aa][Uu][Tt][Hh])"/> - <xs:pattern value="([Aa][Cc][Tt][Ii][Vv][Aa][Tt][Ee])"/> - <xs:pattern value="([Dd][Ee][Aa][Cc][Tt][Ii][Vv][Aa][Tt][Ee])"/> - <xs:pattern value="([Rr][Ee][Aa][Cc][Tt][Ii][Vv][Aa][Tt][Ee])"/> - <xs:pattern value="([Rr][Ee][Dd][Ee][Mm][Pp][Tt][Ii][Oo][Nn][Cc][Oo][Mm][Pp][Ll][Ee][Tt][Ii][Oo][Nn])"/> - <xs:pattern value="([Rr][Ee][Dd][Ee][Mm][Pp][Tt][Ii][Oo][Nn])"/> - <xs:pattern value="([Vv][Oo][Ii][Dd])"/> - </xs:restriction> - </xs:simpleType> - <xs:simpleType name="valid-eci-types"> - <xs:union> - <xs:simpleType> - <xs:restriction base="xs:string"> - <xs:maxLength value="1"/> - <xs:enumeration value="5"> - <xs:annotation> - <xs:documentation>Authenticated</xs:documentation> - </xs:annotation> - </xs:enumeration> - <xs:enumeration value="6"> - <xs:annotation> - <xs:documentation>Attempted</xs:documentation> - </xs:annotation> - </xs:enumeration> - </xs:restriction> - </xs:simpleType> - <xs:simpleType> - <xs:restriction base="xs:string"> - <xs:enumeration value=""/> - </xs:restriction> - </xs:simpleType> - </xs:union> - </xs:simpleType> - <xs:simpleType name="recurring-ind-types"> - <xs:union> - <xs:simpleType> - <xs:restriction base="xs:string"> - <xs:maxLength value="2"/> - <xs:enumeration value="RF"> - <xs:annotation> - <xs:documentation>First Recurring Transaction</xs:documentation> - </xs:annotation> - </xs:enumeration> - <xs:enumeration value="RS"> - <xs:annotation> - <xs:documentation>Subsequent Recurring Transactions</xs:documentation> - </xs:annotation> - </xs:enumeration> - </xs:restriction> - </xs:simpleType> - <xs:simpleType> - <xs:restriction base="xs:string"> - <xs:enumeration value=""/> - </xs:restriction> - </xs:simpleType> - </xs:union> - </xs:simpleType> - <xs:element name="SettleRejectHoldingBin"> - <xs:complexType/> - </xs:element> - <xs:simpleType name="profile-action-types"> - <xs:restriction base="xs:string"> - <xs:maxLength value="1"/> - <xs:enumeration value="C"/> - <xs:enumeration value="R"/> - <xs:enumeration value="U"/> - <xs:enumeration value="D"/> - </xs:restriction> - </xs:simpleType> - <xs:simpleType name="vallid-prior-auth"> - <xs:restriction base="xs:string"> - <xs:maxLength value="6"/> - </xs:restriction> - </xs:simpleType> -</xs:schema> +<?xml version="1.0" encoding="UTF-8"?> +<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified" attributeFormDefault="unqualified"> + <xs:element name="Request"> + <xs:annotation> + <xs:documentation>Top level element for all XML request transaction types</xs:documentation> + </xs:annotation> + <xs:complexType> + <xs:choice> + <xs:element name="AccountUpdater" type="accountUpdaterType"/> + <xs:element name="Inquiry" type="inquiryType"/> + <xs:element name="NewOrder" type="newOrderType"/> + <xs:element name="EndOfDay" type="endOfDayType"/> + <xs:element name="FlexCache" type="flexCacheType"/> + <xs:element name="Profile" type="profileType"/> + <xs:element name="Reversal" type="reversalType"/> + <xs:element name="MarkForCapture" type="markForCaptureType"/> + <xs:element name="SafetechFraudAnalysis" type="safetechFraudAnalysisType"/> + </xs:choice> + </xs:complexType> + </xs:element> + + <xs:complexType name="baseElementsType"> + <xs:sequence> + <xs:element name="IndustryType" type="valid-industry-types"/> + <xs:element name="CardBrand" type="valid-card-brands" minOccurs="0"/> + <xs:element name="AccountNum" type="xs:string" minOccurs="0"/> + <xs:element name="Exp" type="xs:string" minOccurs="0"/> + <xs:element name="CurrencyCode" type="xs:string" minOccurs="0"/> + <xs:element name="CurrencyExponent" type="xs:string" minOccurs="0"/> + <xs:element name="CardSecValInd" type="xs:string" minOccurs="0"/> + <xs:element name="CardSecVal" type="xs:string" minOccurs="0"/> + <xs:element name="BCRtNum" type="xs:string" minOccurs="0"/> + <xs:element name="CheckDDA" type="xs:string" minOccurs="0"/> + <xs:element name="BankAccountType" type="xs:string" minOccurs="0"/> + <xs:element name="ECPAuthMethod" type="xs:string" minOccurs="0"/> + <xs:element name="BankPmtDelv" type="xs:string" minOccurs="0"/> + <xs:element name="AVSzip" type="xs:string" minOccurs="0"/> + <xs:element name="AVSaddress1" type="xs:string" minOccurs="0"/> + <xs:element name="AVSaddress2" type="xs:string" minOccurs="0"/> + <xs:element name="AVScity" type="xs:string" minOccurs="0"/> + <xs:element name="AVSstate" type="xs:string" minOccurs="0"/> + <xs:element name="AVSphoneNum" type="xs:string" minOccurs="0"/> + <xs:element name="AVSname" type="xs:string" minOccurs="0"/> + <xs:element name="AVScountryCode" type="xs:string" minOccurs="0"/> + <xs:element name="AVSDestzip" type="xs:string" minOccurs="0"/> + <xs:element name="AVSDestaddress1" type="xs:string" minOccurs="0"/> + <xs:element name="AVSDestaddress2" type="xs:string" minOccurs="0"/> + <xs:element name="AVSDestcity" type="xs:string" minOccurs="0"/> + <xs:element name="AVSDeststate" type="xs:string" minOccurs="0"/> + <xs:element name="AVSDestphoneNum" type="xs:string" minOccurs="0"/> + <xs:element name="AVSDestname" type="xs:string" minOccurs="0"/> + <xs:element name="AVSDestcountryCode" type="xs:string" minOccurs="0"/> + <xs:element name="UseCustomerRefNum" type="xs:string" minOccurs="0"/> + <xs:element name="AuthenticationECIInd" type="valid-eci-types" minOccurs="0"/> + <xs:element name="CAVV" type="xs:string" minOccurs="0"/> + <xs:element name="XID" type="xs:string" minOccurs="0"/> + <xs:element name="AAV" type="xs:string" minOccurs="0"/> + <xs:element name="OrderID" type="xs:string"/> + <xs:element name="Amount" type="xs:string" minOccurs="0"/> + <xs:element name="Comments" type="xs:string" minOccurs="0"/> + <xs:element name="EUDDCountryCode" type="xs:string" minOccurs="0"/> + <xs:element name="EUDDBankSortCode" type="xs:string" minOccurs="0"/> + <xs:element name="EUDDRibCode" type="xs:string" minOccurs="0"/> + <xs:element name="BMLCustomerIP" type="xs:string" minOccurs="0"/> + <xs:element name="BMLCustomerEmail" type="xs:string" minOccurs="0"/> + <xs:element name="BMLShippingCost" type="xs:string" minOccurs="0"/> + <xs:element name="BMLTNCVersion" type="xs:string" minOccurs="0"/> + <xs:element name="BMLCustomerRegistrationDate" type="xs:string" minOccurs="0"/> + <xs:element name="BMLCustomerTypeFlag" type="xs:string" minOccurs="0"/> + <xs:element name="BMLItemCategory" type="xs:string" minOccurs="0"/> + <xs:element name="BMLPreapprovalInvitationNum" type="xs:string" minOccurs="0"/> + <xs:element name="BMLMerchantPromotionalCode" type="xs:string" minOccurs="0"/> + <xs:element name="BMLCustomerBirthDate" type="xs:string" minOccurs="0"/> + <xs:element name="BMLCustomerSSN" type="xs:string" minOccurs="0"/> + <xs:element name="BMLCustomerAnnualIncome" type="xs:string" minOccurs="0"/> + <xs:element name="BMLCustomerResidenceStatus" type="xs:string" minOccurs="0"/> + <xs:element name="BMLCustomerCheckingAccount" type="xs:string" minOccurs="0"/> + <xs:element name="BMLCustomerSavingsAccount" type="xs:string" minOccurs="0"/> + <xs:element name="BMLProductDeliveryType" type="xs:string" minOccurs="0"/> + <xs:element name="BillerReferenceNumber" type="xs:string" minOccurs="0"/> + <xs:element name="UseStoredAAVInd" type="xs:string" minOccurs="0"/> + <xs:element name="ECPCheckSerialNumber" type="xs:string" minOccurs="0"/> + <xs:element name="ECPTerminalCity" type="xs:string" minOccurs="0"/> + <xs:element name="ECPTerminalState" type="xs:string" minOccurs="0"/> + <xs:element name="ECPImageReferenceNumber" type="xs:string" minOccurs="0"/> + <xs:element name="CustomerAni" type="xs:string" minOccurs="0"/> + <xs:element name="AVSPhoneType" type="xs:string" minOccurs="0"/> + <xs:element name="AVSDestPhoneType" type="xs:string" minOccurs="0"/> + <xs:element name="CustomerEmail" type="xs:string" minOccurs="0"/> + <xs:element name="CustomerIpAddress" type="xs:string" minOccurs="0"/> + <xs:element name="EmailAddressSubtype" type="xs:string" minOccurs="0"/> + <xs:element name="CustomerBrowserName" type="xs:string" minOccurs="0"/> + <xs:element name="ShippingMethod" type="xs:string" minOccurs="0"/> + <xs:element name="EUDDBankBranchCode" type="xs:string" minOccurs="0"/> + <xs:element name="EUDDIBAN" type="xs:string" minOccurs="0"/> + <xs:element name="EUDDBIC" type="xs:string" minOccurs="0"/> + <xs:element name="DWWalletID" type="xs:string" minOccurs="0"/> + <xs:element name="DWSLI" type="xs:string" minOccurs="0"/> + <xs:element name="DWIncentiveInd" type="xs:string" minOccurs="0"/> + <xs:element name="DigitalWalletType" type="xs:string" minOccurs="0"/> + </xs:sequence> + </xs:complexType> + + <xs:complexType name="softMerchantDescriptorsType"> + <xs:sequence> + <xs:element name="SMDDBA" type="xs:string" minOccurs="0"/> + <xs:element name="SMDMerchantID" type="xs:string" minOccurs="0"/> + <xs:element name="SMDContactInfo" type="xs:string" minOccurs="0"/> + <xs:element name="SMDStreet" type="xs:string" minOccurs="0"/> + <xs:element name="SMDCity" type="xs:string" minOccurs="0"/> + <xs:element name="SMDRegion" type="xs:string" minOccurs="0"/> + <xs:element name="SMDPostalCode" type="xs:string" minOccurs="0"/> + <xs:element name="SMDCountryCode" type="xs:string" minOccurs="0"/> + <xs:element name="SMDMCC" type="xs:string" minOccurs="0"/> + <xs:element name="SMDEmail" type="xs:string" minOccurs="0"/> + <xs:element name="SMDPhoneNumber" type="xs:string" minOccurs="0"/> + </xs:sequence> + </xs:complexType> + + + <xs:complexType name="fraudAnalysisType"> + <xs:sequence> + <xs:element name="FraudScoreIndicator" type="xs:string" minOccurs="0"/> + <xs:element name="RulesTrigger" type="xs:string" minOccurs="0"/> + <xs:element name="SafetechMerchantID" type="xs:string" minOccurs="0"/> + <xs:element name="KaptchaSessionID" type="xs:string" minOccurs="0"/> + <xs:element name="WebsiteShortName" type="xs:string" minOccurs="0"/> + <xs:element name="CashValueOfFencibleItems" type="xs:string" minOccurs="0"/> + <xs:element name="CustomerDOB" type="xs:string" minOccurs="0"/> + <xs:element name="CustomerGender" type="xs:string" minOccurs="0"/> + <xs:element name="CustomerDriverLicense" type="xs:string" minOccurs="0"/> + <xs:element name="CustomerID" type="xs:string" minOccurs="0"/> + <xs:element name="CustomerIDCreationTime" type="xs:string" minOccurs="0"/> + <xs:element name="KTTVersionNumber" type="xs:string" minOccurs="0"/> + <xs:element name="KTTDataLength" type="xs:string" minOccurs="0"/> + <xs:element name="KTTDataString" type="xs:string" minOccurs="0"/> + <xs:element name="PinlessDebitTxnType" type="xs:string" minOccurs="0"/> + <xs:element name="PinlessDebitMerchantUrl" type="xs:string" minOccurs="0"/> + </xs:sequence> + </xs:complexType> + + <xs:complexType name="safetechFraudAnalysisType"> + <xs:sequence> + <xs:element name="OrbitalConnectionUsername" type="xs:string" minOccurs="0"/> + <xs:element name="OrbitalConnectionPassword" type="xs:string" minOccurs="0"/> + <xs:element name="BIN" type="valid-routing-bins"/> + <xs:element name="MerchantID" type="xs:string"/> + <xs:element name="TerminalID" type="terminal-type"/> + <xs:element name="BaseElements" type="baseElementsType" minOccurs="1"/> + <xs:element name="FraudAnalysis" type="fraudAnalysisType" minOccurs="1"/> + </xs:sequence> + </xs:complexType> + + <xs:complexType name="newOrderType"> + <xs:sequence> + <xs:element name="OrbitalConnectionUsername" type="xs:string" minOccurs="0"/> + <xs:element name="OrbitalConnectionPassword" type="xs:string" minOccurs="0"/> + <xs:element name="IndustryType" type="valid-industry-types"/> + <xs:element name="MessageType" type="valid-trans-types"/> + <xs:element name="BIN" type="valid-routing-bins"/> + <xs:element name="MerchantID" type="xs:string"/> + <xs:element name="TerminalID" type="terminal-type"/> + <xs:element name="CardBrand" type="valid-card-brands" minOccurs="0"/> + <xs:element name="AccountNum" type="xs:string" minOccurs="0"/> + <xs:element name="Exp" type="xs:string" minOccurs="0"/> + <xs:element name="CurrencyCode" type="xs:string" minOccurs="0"/> + <xs:element name="CurrencyExponent" type="xs:string" minOccurs="0"/> + <xs:element name="CardSecValInd" type="xs:string" minOccurs="0"/> + <xs:element name="CardSecVal" type="xs:string" minOccurs="0"/> + <xs:element name="DebitCardIssueNum" type="xs:string" minOccurs="0"/> + <xs:element name="DebitCardStartDate" type="xs:string" minOccurs="0"/> + <xs:element name="BCRtNum" type="xs:string" minOccurs="0"/> + <xs:element name="CheckDDA" type="xs:string" minOccurs="0"/> + <xs:element name="BankAccountType" type="xs:string" minOccurs="0"/> + <xs:element name="ECPAuthMethod" type="xs:string" minOccurs="0"/> + <xs:element name="BankPmtDelv" type="xs:string" minOccurs="0"/> + <xs:element name="AVSzip" type="xs:string" minOccurs="0"/> + <xs:element name="AVSaddress1" type="xs:string" minOccurs="0"/> + <xs:element name="AVSaddress2" type="xs:string" minOccurs="0"/> + <xs:element name="AVScity" type="xs:string" minOccurs="0"/> + <xs:element name="AVSstate" type="xs:string" minOccurs="0"/> + <xs:element name="AVSphoneNum" type="xs:string" minOccurs="0"/> + <xs:element name="AVSname" type="xs:string" minOccurs="0"/> + <xs:element name="AVScountryCode" type="xs:string" minOccurs="0"/> + <xs:element name="AVSDestzip" type="xs:string" minOccurs="0"/> + <xs:element name="AVSDestaddress1" type="xs:string" minOccurs="0"/> + <xs:element name="AVSDestaddress2" type="xs:string" minOccurs="0"/> + <xs:element name="AVSDestcity" type="xs:string" minOccurs="0"/> + <xs:element name="AVSDeststate" type="xs:string" minOccurs="0"/> + <xs:element name="AVSDestphoneNum" type="xs:string" minOccurs="0"/> + <xs:element name="AVSDestname" type="xs:string" minOccurs="0"/> + <xs:element name="AVSDestcountryCode" type="xs:string" minOccurs="0"/> + <xs:element name="CustomerProfileFromOrderInd" type="xs:string" minOccurs="0"/> + <xs:element name="CustomerRefNum" type="xs:string" minOccurs="0"/> + <xs:element name="CustomerProfileOrderOverrideInd" type="xs:string" minOccurs="0"/> + <xs:element name="Status" type="xs:string" minOccurs="0"/> + <xs:element name="AuthenticationECIInd" type="valid-eci-types" minOccurs="0"/> + <xs:element name="CAVV" type="xs:string" minOccurs="0"/> + <xs:element name="XID" type="xs:string" minOccurs="0"/> + <xs:element name="PriorAuthID" type="vallid-prior-auth" minOccurs="0"/> + <xs:element name="OrderID" type="xs:string"/> + <xs:element name="Amount" type="xs:string" minOccurs="0"/> + <xs:element name="Comments" type="xs:string" minOccurs="0"/> + <xs:element name="ShippingRef" type="xs:string" minOccurs="0"/> + <xs:element name="TaxInd" type="valid-tax-inds" minOccurs="0"/> + <xs:element name="Tax" type="xs:string" minOccurs="0"/> + <xs:element name="AMEXTranAdvAddn1" type="xs:string" minOccurs="0"/> + <xs:element name="AMEXTranAdvAddn2" type="xs:string" minOccurs="0"/> + <xs:element name="AMEXTranAdvAddn3" type="xs:string" minOccurs="0"/> + <xs:element name="AMEXTranAdvAddn4" type="xs:string" minOccurs="0"/> + <xs:element name="AAV" type="xs:string" minOccurs="0"/> + <xs:element name="SDMerchantName" type="xs:string" minOccurs="0"/> + <xs:element name="SDProductDescription" type="xs:string" minOccurs="0"/> + <xs:element name="SDMerchantCity" type="xs:string" minOccurs="0"/> + <xs:element name="SDMerchantPhone" type="xs:string" minOccurs="0"/> + <xs:element name="SDMerchantURL" type="xs:string" minOccurs="0"/> + <xs:element name="SDMerchantEmail" type="xs:string" minOccurs="0"/> + <xs:element name="RecurringInd" type="recurring-ind-types" minOccurs="0"/> + <xs:element name="EUDDCountryCode" type="xs:string" minOccurs="0"/> + <xs:element name="EUDDBankSortCode" type="xs:string" minOccurs="0"/> + <xs:element name="EUDDRibCode" type="xs:string" minOccurs="0"/> + <xs:element name="BMLCustomerIP" type="xs:string" minOccurs="0"/> + <xs:element name="BMLCustomerEmail" type="xs:string" minOccurs="0"/> + <xs:element name="BMLShippingCost" type="xs:string" minOccurs="0"/> + <xs:element name="BMLTNCVersion" type="xs:string" minOccurs="0"/> + <xs:element name="BMLCustomerRegistrationDate" type="xs:string" minOccurs="0"/> + <xs:element name="BMLCustomerTypeFlag" type="xs:string" minOccurs="0"/> + <xs:element name="BMLItemCategory" type="xs:string" minOccurs="0"/> + <xs:element name="BMLPreapprovalInvitationNum" type="xs:string" minOccurs="0"/> + <xs:element name="BMLMerchantPromotionalCode" type="xs:string" minOccurs="0"/> + <xs:element name="BMLCustomerBirthDate" type="xs:string" minOccurs="0"/> + <xs:element name="BMLCustomerSSN" type="xs:string" minOccurs="0"/> + <xs:element name="BMLCustomerAnnualIncome" type="xs:string" minOccurs="0"/> + <xs:element name="BMLCustomerResidenceStatus" type="xs:string" minOccurs="0"/> + <xs:element name="BMLCustomerCheckingAccount" type="xs:string" minOccurs="0"/> + <xs:element name="BMLCustomerSavingsAccount" type="xs:string" minOccurs="0"/> + <xs:element name="BMLProductDeliveryType" type="xs:string" minOccurs="0"/> + <xs:element name="BillerReferenceNumber" type="xs:string" minOccurs="0"/> + <xs:element name="MBType" type="xs:string" minOccurs="0"/> + <xs:element name="MBOrderIdGenerationMethod" type="xs:string" minOccurs="0"/> + <xs:element name="MBRecurringStartDate" type="xs:string" minOccurs="0"/> + <xs:element name="MBRecurringEndDate" type="xs:string" minOccurs="0"/> + <xs:element name="MBRecurringNoEndDateFlag" type="xs:string" minOccurs="0"/> + <xs:element name="MBRecurringMaxBillings" type="xs:string" minOccurs="0"/> + <xs:element name="MBRecurringFrequency" type="xs:string" minOccurs="0"/> + <xs:element name="MBDeferredBillDate" type="xs:string" minOccurs="0"/> + <xs:element name="MBMicroPaymentMaxDollarValue" type="xs:string" minOccurs="0"/> + <xs:element name="MBMicroPaymentMaxBillingDays" type="xs:string" minOccurs="0"/> + <xs:element name="MBMicroPaymentMaxTransactions" type="xs:string" minOccurs="0"/> + <xs:element name="TxRefNum" type="xs:string" minOccurs="0"/> + <xs:element name="PCOrderNum" type="xs:string" minOccurs="0"/> + <xs:element name="PCDestZip" type="xs:string" minOccurs="0"/> + <xs:element name="PCDestName" type="xs:string" minOccurs="0"/> + <xs:element name="PCDestAddress1" type="xs:string" minOccurs="0"/> + <xs:element name="PCDestAddress2" type="xs:string" minOccurs="0"/> + <xs:element name="PCDestCity" type="xs:string" minOccurs="0"/> + <xs:element name="PCDestState" type="xs:string" minOccurs="0"/> + <xs:element name="PC3FreightAmt" type="xs:string" minOccurs="0"/> + <xs:element name="PC3DutyAmt" type="xs:string" minOccurs="0"/> + <xs:element name="PC3DestCountryCd" type="xs:string" minOccurs="0"/> + <xs:element name="PC3ShipFromZip" type="xs:string" minOccurs="0"/> + <xs:element name="PC3DiscAmt" type="xs:string" minOccurs="0"/> + <xs:element name="PC3VATtaxAmt" type="xs:string" minOccurs="0"/> + <xs:element name="PC3VATtaxRate" type="xs:string" minOccurs="0"/> + <xs:element name="PC3AltTaxInd" type="xs:string" minOccurs="0"/> + <xs:element name="PC3AltTaxAmt" type="xs:string" minOccurs="0"/> + <xs:element name="PC3LineItemCount" type="xs:string" minOccurs="0"/> + <xs:element name="PC3LineItemArray" type="PC3LineItemArray" minOccurs="0"/> + <xs:element name="PartialAuthInd" type="xs:string" minOccurs="0"/> + <xs:element name="AccountUpdaterEligibility" type="xs:string" minOccurs="0"/> + <xs:element name="UseStoredAAVInd" type="xs:string" minOccurs="0"/> + <xs:element name="ECPActionCode" type="xs:string" minOccurs="0"/> + <xs:element name="ECPCheckSerialNumber" type="xs:string" minOccurs="0"/> + <xs:element name="ECPTerminalCity" type="xs:string" minOccurs="0"/> + <xs:element name="ECPTerminalState" type="xs:string" minOccurs="0"/> + <xs:element name="ECPImageReferenceNumber" type="xs:string" minOccurs="0"/> + <xs:element name="CustomerAni" type="xs:string" minOccurs="0"/> + <xs:element name="AVSPhoneType" type="xs:string" minOccurs="0"/> + <xs:element name="AVSDestPhoneType" type="xs:string" minOccurs="0"/> + <xs:element name="CustomerEmail" type="xs:string" minOccurs="0"/> + <xs:element name="CustomerIpAddress" type="xs:string" minOccurs="0"/> + <xs:element name="EmailAddressSubtype" type="xs:string" minOccurs="0"/> + <xs:element name="CustomerBrowserName" type="xs:string" minOccurs="0"/> + <xs:element name="ShippingMethod" type="xs:string" minOccurs="0"/> + <xs:element name="FraudAnalysis" type="fraudAnalysisType" minOccurs="0"/> + <xs:element name="SoftMerchantDescriptors" type="softMerchantDescriptorsType" minOccurs="0"/> + <xs:element name="CardIndicators" type="xs:string" minOccurs="0"/> + <xs:element name="EUDDBankBranchCode" type="xs:string" minOccurs="0"/> + <xs:element name="EUDDIBAN" type="xs:string" minOccurs="0"/> + <xs:element name="EUDDBIC" type="xs:string" minOccurs="0"/> + <xs:element name="EUDDMandateSignatureDate" type="xs:string" minOccurs="0"/> + <xs:element name="EUDDMandateID" type="xs:string" minOccurs="0"/> + <xs:element name="EUDDMandateType" type="xs:string" minOccurs="0"/> + <xs:element name="PaymentInd" type="xs:string" minOccurs="0"/> + <xs:element name="TxnSurchargeAmt" type="xs:string" minOccurs="0"/> + <xs:element name="PaymentActionInd" type="xs:string" minOccurs="0"/> + <xs:element name="DPANInd" type="xs:string" minOccurs="0"/> + <xs:element name="AEVV" type="xs:string" minOccurs="0"/> + <xs:element name="DWWalletID" type="xs:string" minOccurs="0"/> + <xs:element name="DWSLI" type="xs:string" minOccurs="0"/> + <xs:element name="DWIncentiveInd" type="xs:string" minOccurs="0"/> + <xs:element name="DigitalWalletType" type="xs:string" minOccurs="0"/> + <xs:element name="PRBirthDate" type="xs:string" minOccurs="0"/> + <xs:element name="PRMaskedAccountNumber" type="xs:string" minOccurs="0"/> + <xs:element name="PRPartialPostalCode" type="xs:string" minOccurs="0"/> + <xs:element name="PRLastName" type="xs:string" minOccurs="0"/> + <xs:element name="TokenRequestorID" type="xs:string" minOccurs="0"/> + <xs:element name="PCardRequestorName" type="xs:string" minOccurs="0"/> + <xs:element name="PCardLocalTaxRate" type="xs:string" minOccurs="0"/> + <xs:element name="PCardNationalTax" type="xs:string" minOccurs="0"/> + <xs:element name="PCardPstTaxRegNumber" type="xs:string" minOccurs="0"/> + <xs:element name="PCardCustomerVatRegNumber" type="xs:string" minOccurs="0"/> + <xs:element name="PCardMerchantVatRegNumber" type="xs:string" minOccurs="0"/> + <xs:element name="PCardTotalTaxAmount" type="xs:string" minOccurs="0"/> + <xs:element name="PCardDtlTaxAmount1Ind" type="xs:string" minOccurs="0"/> + <xs:element name="PCardDtlTaxAmount1" type="xs:string" minOccurs="0"/> + <xs:element name="PCardDtlTaxAmount2Ind" type="xs:string" minOccurs="0"/> + <xs:element name="PCardDtlTaxAmount2" type="xs:string" minOccurs="0"/> + <xs:element name="DigitalTokenCryptogram" type="xs:string" minOccurs="0"/> + <xs:element name="PCardNationalTaxRate" type="xs:string" minOccurs="0"/> + <xs:element name="PCardLocalTaxAmount" type="xs:string" minOccurs="0"/> + <xs:element name="EWSFirstName" type="xs:string" minOccurs="0"/> + <xs:element name="EWSMiddleName" type="xs:string" minOccurs="0"/> + <xs:element name="EWSLastName" type="xs:string" minOccurs="0"/> + <xs:element name="EWSBusinessName" type="xs:string" minOccurs="0"/> + <xs:element name="EWSAddressLine1" type="xs:string" minOccurs="0"/> + <xs:element name="EWSAddressLine2" type="xs:string" minOccurs="0"/> + <xs:element name="EWSCity" type="xs:string" minOccurs="0"/> + <xs:element name="EWSState" type="xs:string" minOccurs="0"/> + <xs:element name="EWSZip" type="xs:string" minOccurs="0"/> + <xs:element name="EWSPhoneType" type="xs:string" minOccurs="0"/> + <xs:element name="EWSPhoneNumber" type="xs:string" minOccurs="0"/> + <xs:element name="EWSCheckSerialNumber" type="xs:string" minOccurs="0"/> + <xs:element name="EWSSSNTIN" type="xs:string" minOccurs="0"/> + <xs:element name="EWSDOB" type="xs:string" minOccurs="0"/> + <xs:element name="EWSIDType" type="xs:string" minOccurs="0"/> + <xs:element name="EWSIDNumber" type="xs:string" minOccurs="0"/> + <xs:element name="EWSIDState" type="xs:string" minOccurs="0"/> + <xs:element name="ECPSameDayInd" type="xs:string" minOccurs="0"/> + <xs:element name="ECPReDepositFreq" type="xs:string" minOccurs="0"/> + <xs:element name="ECPReDepositInd" type="xs:string" minOccurs="0"/> + <xs:element name="FXOptoutInd" type="xs:string" minOccurs="0"/> + <xs:element name="FXRateHandlingInd" type="xs:string" minOccurs="0"/> + <xs:element name="FXRateID" type="xs:string" minOccurs="0"/> + <xs:element name="FXExchangeRate" type="xs:string" minOccurs="0"/> + <xs:element name="FXPresentmentCurrency" type="xs:string" minOccurs="0"/> + <xs:element name="FXSettlementCurrency" type="xs:string" minOccurs="0"/> + <xs:element name="MITMsgType" type="xs:string" minOccurs="0"/> + <xs:element name="MITStoredCredentialInd" type="xs:string" minOccurs="0"/> + <xs:element name="MITSubmittedTransactionID" type="xs:string" minOccurs="0"/> + <xs:element name="PinlessDebitTxnType" type="xs:string" minOccurs="0"/> + <xs:element name="PinlessDebitMerchantUrl" type="xs:string" minOccurs="0"/> + <xs:element name="RtauOptOutInd" type="xs:string" minOccurs="0"/> + <xs:element name="PymtBrandProgramCode" type="xs:string" minOccurs="0"/> + </xs:sequence> + </xs:complexType> + <xs:complexType name="PC3LineItemArray"> + <xs:sequence> + <xs:element name="PC3LineItem" type="PC3LineItemType" maxOccurs="unbounded"/> + </xs:sequence> + </xs:complexType> + <xs:complexType name="PC3LineItemType"> + <xs:sequence> + <xs:element name="PC3DtlIndex" type="xs:string" minOccurs="0"/> + <xs:element name="PC3DtlDesc" type="xs:string" minOccurs="0"/> + <xs:element name="PC3DtlProdCd" type="xs:string" minOccurs="0"/> + <xs:element name="PC3DtlQty" type="xs:string" minOccurs="0"/> + <xs:element name="PC3DtlUOM" type="xs:string" minOccurs="0"/> + <xs:element name="PC3DtlTaxAmt" type="xs:string" minOccurs="0"/> + <xs:element name="PC3DtlTaxRate" type="xs:string" minOccurs="0"/> + <xs:element name="PC3Dtllinetot" type="xs:string" minOccurs="0"/> + <xs:element name="PC3DtlDisc" type="xs:string" minOccurs="0"/> + <xs:element name="PC3DtlCommCd" type="xs:string" minOccurs="0"/> + <xs:element name="PC3DtlUnitCost" type="xs:string" minOccurs="0"/> + <xs:element name="PC3DtlGrossNet" type="xs:string" minOccurs="0"/> + <xs:element name="PC3DtlTaxType" type="xs:string" minOccurs="0"/> + <xs:element name="PC3DtlDiscInd" type="xs:string" minOccurs="0"/> + <xs:element name="PC3DtlDebitInd" type="xs:string" minOccurs="0"/> + <xs:element name="PC3DtlDiscountRate" type="xs:string" minOccurs="0"/> + </xs:sequence> + </xs:complexType> + <xs:complexType name="markForCaptureType"> + <xs:sequence> + <xs:element name="OrbitalConnectionUsername" type="xs:string" minOccurs="0"/> + <xs:element name="OrbitalConnectionPassword" type="xs:string" minOccurs="0"/> + <xs:element name="OrderID" type="xs:string"/> + <xs:element name="Amount" type="xs:string" minOccurs="0"/> + <xs:element name="TaxInd" type="valid-tax-inds" minOccurs="0"/> + <xs:element name="Tax" type="xs:string" minOccurs="0"/> + <xs:element name="BIN" type="valid-routing-bins"/> + <xs:element name="MerchantID" type="xs:string"/> + <xs:element name="TerminalID" type="terminal-type"/> + <xs:element name="TxRefNum" type="xs:string"/> + <xs:element name="PCOrderNum" type="xs:string" minOccurs="0"/> + <xs:element name="PCDestZip" type="xs:string" minOccurs="0"/> + <xs:element name="PCDestName" type="xs:string" minOccurs="0"/> + <xs:element name="PCDestAddress1" type="xs:string" minOccurs="0"/> + <xs:element name="PCDestAddress2" type="xs:string" minOccurs="0"/> + <xs:element name="PCDestCity" type="xs:string" minOccurs="0"/> + <xs:element name="PCDestState" type="xs:string" minOccurs="0"/> + <xs:element name="AMEXTranAdvAddn1" type="xs:string" minOccurs="0"/> + <xs:element name="AMEXTranAdvAddn2" type="xs:string" minOccurs="0"/> + <xs:element name="AMEXTranAdvAddn3" type="xs:string" minOccurs="0"/> + <xs:element name="AMEXTranAdvAddn4" type="xs:string" minOccurs="0"/> + <xs:element name="PC3FreightAmt" type="xs:string" minOccurs="0"/> + <xs:element name="PC3DutyAmt" type="xs:string" minOccurs="0"/> + <xs:element name="PC3DestCountryCd" type="xs:string" minOccurs="0"/> + <xs:element name="PC3ShipFromZip" type="xs:string" minOccurs="0"/> + <xs:element name="PC3DiscAmt" type="xs:string" minOccurs="0"/> + <xs:element name="PC3VATtaxAmt" type="xs:string" minOccurs="0"/> + <xs:element name="PC3VATtaxRate" type="xs:string" minOccurs="0"/> + <xs:element name="PC3AltTaxInd" type="xs:string" minOccurs="0"/> + <xs:element name="PC3AltTaxID" type="xs:string" minOccurs="0"/> + <xs:element name="PC3AltTaxAmt" type="xs:string" minOccurs="0"/> + <xs:element name="PC3LineItemCount" type="xs:string" minOccurs="0"/> + <xs:element name="PC3LineItemArray" type="PC3LineItemArray" minOccurs="0"/> + <xs:element name="PCardRequestorName" type="xs:string" minOccurs="0"/> + <xs:element name="PCardLocalTaxRate" type="xs:string" minOccurs="0"/> + <xs:element name="PCardNationalTax" type="xs:string" minOccurs="0"/> + <xs:element name="PCardPstTaxRegNumber" type="xs:string" minOccurs="0"/> + <xs:element name="PCardCustomerVatRegNumber" type="xs:string" minOccurs="0"/> + <xs:element name="PCardMerchantVatRegNumber" type="xs:string" minOccurs="0"/> + <xs:element name="PCardTotalTaxAmount" type="xs:string" minOccurs="0"/> + <xs:element name="PCardDtlTaxAmount1Ind" type="xs:string" minOccurs="0"/> + <xs:element name="PCardDtlTaxAmount1" type="xs:string" minOccurs="0"/> + <xs:element name="PCardDtlTaxAmount2Ind" type="xs:string" minOccurs="0"/> + <xs:element name="PCardDtlTaxAmount2" type="xs:string" minOccurs="0"/> + <xs:element name="PinlessDebitTotalShpmnt" type="xs:string" minOccurs="0"/> + </xs:sequence> + </xs:complexType> + <xs:complexType name="reversalType"> + <xs:sequence> + <xs:element name="OrbitalConnectionUsername" type="xs:string" minOccurs="0"/> + <xs:element name="OrbitalConnectionPassword" type="xs:string" minOccurs="0"/> + <xs:element name="TxRefNum" type="xs:string"/> + <xs:element name="TxRefIdx" type="xs:string" minOccurs="0"/> + <xs:element name="AdjustedAmt" type="xs:string" minOccurs="0"/> + <xs:element name="OrderID" type="xs:string"/> + <xs:element name="BIN" type="valid-routing-bins"/> + <xs:element name="MerchantID" type="xs:string"/> + <xs:element name="TerminalID" type="terminal-type"/> + <xs:element name="ReversalRetryNumber" type="xs:string" minOccurs="0"/> + <xs:element name="OnlineReversalInd" type="xs:string" minOccurs="0"/> + </xs:sequence> + </xs:complexType> + <xs:complexType name="endOfDayType"> + <xs:sequence> + <xs:element name="OrbitalConnectionUsername" type="xs:string" minOccurs="0"/> + <xs:element name="OrbitalConnectionPassword" type="xs:string" minOccurs="0"/> + <xs:element name="BIN" type="valid-routing-bins"/> + <xs:element name="MerchantID" type="xs:string"/> + <xs:element name="TerminalID" type="terminal-type"/> + <xs:element ref="SettleRejectHoldingBin" minOccurs="0"/> + </xs:sequence> + </xs:complexType> + <xs:complexType name="inquiryType"> + <xs:sequence> + <xs:element name="OrbitalConnectionUsername" type="xs:string" minOccurs="0"/> + <xs:element name="OrbitalConnectionPassword" type="xs:string" minOccurs="0"/> + <xs:element name="BIN" type="valid-routing-bins"/> + <xs:element name="MerchantID" type="xs:string"/> + <xs:element name="TerminalID" type="terminal-type"/> + <xs:element name="OrderID" type="xs:string" minOccurs="0"/> + <xs:element name="InquiryRetryNumber" type="xs:string"/> + </xs:sequence> + </xs:complexType> + <xs:complexType name="accountUpdaterType"> + <xs:sequence> + <xs:element name="OrbitalConnectionUsername" type="xs:string" minOccurs="0"/> + <xs:element name="OrbitalConnectionPassword" type="xs:string" minOccurs="0"/> + <xs:element name="CustomerBin" type="valid-routing-bins"/> + <xs:element name="CustomerMerchantID" type="xs:string"/> + <xs:element name="CustomerRefNum" type="xs:string" /> + <xs:element name="CustomerProfileAction" type="xs:string" minOccurs="0"/> + <xs:element name="ScheduledDate" type="xs:string" minOccurs="0"/> + </xs:sequence> + </xs:complexType> + <xs:complexType name="profileType"> + <xs:sequence> + <xs:element name="OrbitalConnectionUsername" type="xs:string" minOccurs="0"/> + <xs:element name="OrbitalConnectionPassword" type="xs:string" minOccurs="0"/> + <xs:element name="CustomerBin" type="valid-routing-bins"/> + <xs:element name="CustomerMerchantID" type="xs:string"/> + <xs:element name="CustomerName" type="xs:string" minOccurs="0"/> + <xs:element name="CustomerRefNum" type="xs:string" minOccurs="0"/> + <xs:element name="CustomerAddress1" type="xs:string" minOccurs="0"/> + <xs:element name="CustomerAddress2" type="xs:string" minOccurs="0"/> + <xs:element name="CustomerCity" type="xs:string" minOccurs="0"/> + <xs:element name="CustomerState" type="xs:string" minOccurs="0"/> + <xs:element name="CustomerZIP" type="xs:string" minOccurs="0"/> + <xs:element name="CustomerEmail" type="xs:string" minOccurs="0"/> + <xs:element name="CustomerPhone" type="xs:string" minOccurs="0"/> + <xs:element name="CustomerCountryCode" type="xs:string" minOccurs="0"/> + <xs:element name="CustomerProfileAction" type="profile-action-types"/> + <xs:element name="CustomerProfileOrderOverrideInd" type="valid-profileOrderOverideInds" minOccurs="0"/> + <xs:element name="CustomerProfileFromOrderInd" type="valid-profileFromOrderInd" minOccurs="0"/> + <xs:element name="OrderDefaultDescription" type="xs:string" minOccurs="0"/> + <xs:element name="OrderDefaultAmount" type="xs:string" minOccurs="0"/> + <xs:element name="CustomerAccountType" type="valid-customer-account-types" minOccurs="0"/> + <xs:element name="Status" type="xs:string" minOccurs="0"/> + <xs:element name="CCAccountNum" type="xs:string" minOccurs="0"/> + <xs:element name="CCExpireDate" type="xs:string" minOccurs="0"/> + <xs:element name="ECPAccountDDA" type="xs:string" minOccurs="0"/> + <xs:element name="ECPAccountType" type="xs:string" minOccurs="0"/> + <xs:element name="ECPAccountRT" type="xs:string" minOccurs="0"/> + <xs:element name="ECPBankPmtDlv" type="xs:string" minOccurs="0"/> + <xs:element name="SwitchSoloStartDate" type="xs:string" minOccurs="0"/> + <xs:element name="SwitchSoloIssueNum" type="xs:string" minOccurs="0"/> + <xs:element name="MBType" type="xs:string" minOccurs="0"/> + <xs:element name="MBOrderIdGenerationMethod" type="xs:string" minOccurs="0"/> + <xs:element name="MBRecurringStartDate" type="xs:string" minOccurs="0"/> + <xs:element name="MBRecurringEndDate" type="xs:string" minOccurs="0"/> + <xs:element name="MBRecurringNoEndDateFlag" type="xs:string" minOccurs="0"/> + <xs:element name="MBRecurringMaxBillings" type="xs:string" minOccurs="0"/> + <xs:element name="MBRecurringFrequency" type="xs:string" minOccurs="0"/> + <xs:element name="MBDeferredBillDate" type="xs:string" minOccurs="0"/> + <xs:element name="MBMicroPaymentMaxDollarValue" type="xs:string" minOccurs="0"/> + <xs:element name="MBMicroPaymentMaxBillingDays" type="xs:string" minOccurs="0"/> + <xs:element name="MBMicroPaymentMaxTransactions" type="xs:string" minOccurs="0"/> + <xs:element name="MBCancelDate" type="xs:string" minOccurs="0"/> + <xs:element name="MBRestoreBillingDate" type="xs:string" minOccurs="0"/> + <xs:element name="MBRemoveFlag" type="xs:string" minOccurs="0"/> + <xs:element name="EUDDCountryCode" type="xs:string" minOccurs="0"/> + <xs:element name="EUDDBankSortCode" type="xs:string" minOccurs="0"/> + <xs:element name="EUDDRibCode" type="xs:string" minOccurs="0"/> + <xs:element name="SDMerchantName" type="xs:string" minOccurs="0"/> + <xs:element name="SDProductDescription" type="xs:string" minOccurs="0"/> + <xs:element name="SDMerchantCity" type="xs:string" minOccurs="0"/> + <xs:element name="SDMerchantPhone" type="xs:string" minOccurs="0"/> + <xs:element name="SDMerchantURL" type="xs:string" minOccurs="0"/> + <xs:element name="SDMerchantEmail" type="xs:string" minOccurs="0"/> + <xs:element name="BillerReferenceNumber" type="xs:string" minOccurs="0"/> + <xs:element name="AccountUpdaterEligibility" type="xs:string" minOccurs="0"/> + <xs:element name="EUDDBankBranchCode" type="xs:string" minOccurs="0"/> + <xs:element name="EUDDIBAN" type="xs:string" minOccurs="0"/> + <xs:element name="EUDDBIC" type="xs:string" minOccurs="0"/> + <xs:element name="EUDDMandateSignatureDate" type="xs:string" minOccurs="0"/> + <xs:element name="EUDDMandateID" type="xs:string" minOccurs="0"/> + <xs:element name="EUDDMandateType" type="xs:string" minOccurs="0"/> + <xs:element name="DPANInd" type="xs:string" minOccurs="0"/> + <xs:element name="TokenRequestorID" type="xs:string" minOccurs="0"/> + <xs:element name="MITMsgType" type="xs:string" minOccurs="0"/> + <xs:element name="MITSubmittedTransactionID" type="xs:string" minOccurs="0"/> + <xs:element name="PinlessDebitTxnType" type="xs:string" minOccurs="0"/> + <xs:element name="PinlessDebitMerchantUrl" type="xs:string" minOccurs="0"/> + </xs:sequence> + </xs:complexType> + <xs:complexType name="flexCacheType"> + <xs:sequence> + <xs:element name="OrbitalConnectionUsername" type="xs:string" minOccurs="0"/> + <xs:element name="OrbitalConnectionPassword" type="xs:string" minOccurs="0"/> + <xs:element name="BIN" type="valid-routing-bins"/> + <xs:element name="MerchantID" type="xs:string"/> + <xs:element name="TerminalID" type="terminal-type"/> + <xs:element name="AccountNum" type="xs:string" minOccurs="0"/> + <xs:element name="OrderID" type="xs:string"/> + <xs:element name="Amount" type="xs:string" minOccurs="0"/> + <xs:element name="CardSecVal" type="xs:string" minOccurs="0"/> + <xs:element name="Comments" type="xs:string" minOccurs="0"/> + <xs:element name="ShippingRef" type="xs:string" minOccurs="0"/> + <xs:element name="IndustryType" type="valid-industry-types"/> + <xs:element name="FlexAutoAuthInd" type="yes-or-no"/> + <xs:element name="FlexPartialRedemptionInd" type="yes-or-no"/> + <xs:element name="FlexAction" type="flex-action-types"/> + <xs:element name="StartAccountNum" type="xs:string" minOccurs="0"/> + <xs:element name="ActivationCount" type="xs:string" minOccurs="0"/> + <xs:element name="TxRefNum" type="xs:string" minOccurs="0"/> + <xs:element name="FlexEmployeeNumber" type="xs:string" minOccurs="0"/> + <xs:element name="PriorAuthID" type="vallid-prior-auth" minOccurs="0"/> + <xs:element name="AVSzip" type="xs:string" minOccurs="0"/> + <xs:element name="AVSaddress1" type="xs:string" minOccurs="0"/> + <xs:element name="AVSaddress2" type="xs:string" minOccurs="0"/> + <xs:element name="AVScity" type="xs:string" minOccurs="0"/> + <xs:element name="AVSstate" type="xs:string" minOccurs="0"/> + <xs:element name="AVSphoneNum" type="xs:string" minOccurs="0"/> + <xs:element name="AVSname" type="xs:string" minOccurs="0"/> + <xs:element name="AVScountryCode" type="xs:string" minOccurs="0"/> + <xs:element name="AVSDestzip" type="xs:string" minOccurs="0"/> + <xs:element name="AVSDestaddress1" type="xs:string" minOccurs="0"/> + <xs:element name="AVSDestaddress2" type="xs:string" minOccurs="0"/> + <xs:element name="AVSDestcity" type="xs:string" minOccurs="0"/> + <xs:element name="AVSDeststate" type="xs:string" minOccurs="0"/> + <xs:element name="AVSDestphoneNum" type="xs:string" minOccurs="0"/> + <xs:element name="AVSDestname" type="xs:string" minOccurs="0"/> + <xs:element name="AVSDestcountryCode" type="xs:string" minOccurs="0"/> + <xs:element name="CustomerAni" type="xs:string" minOccurs="0"/> + <xs:element name="AVSPhoneType" type="xs:string" minOccurs="0"/> + <xs:element name="AVSDestPhoneType" type="xs:string" minOccurs="0"/> + <xs:element name="CustomerEmail" type="xs:string" minOccurs="0"/> + <xs:element name="CustomerIpAddress" type="xs:string" minOccurs="0"/> + <xs:element name="EmailAddressSubtype" type="xs:string" minOccurs="0"/> + <xs:element name="CustomerBrowserName" type="xs:string" minOccurs="0"/> + <xs:element name="ShippingMethod" type="xs:string" minOccurs="0"/> + <xs:element name="FraudAnalysis" type="fraudAnalysisType" minOccurs="0"/> + </xs:sequence> + </xs:complexType> + <xs:simpleType name="terminal-type"> + <xs:restriction base="xs:string"> + <xs:maxLength value="3"/> + <xs:minLength value="1"/> + </xs:restriction> + </xs:simpleType> + <xs:simpleType name="valid-trans-types"> + <xs:annotation> + <xs:documentation>New order Transaction Types</xs:documentation> + </xs:annotation> + <xs:restriction base="xs:string"> + <xs:maxLength value="20"/> + <xs:enumeration value="A"> + <xs:annotation> + <xs:documentation>Auth Only No Capture</xs:documentation> + </xs:annotation> + </xs:enumeration> + <xs:enumeration value="AC"> + <xs:annotation> + <xs:documentation>Auth and Capture</xs:documentation> + </xs:annotation> + </xs:enumeration> + <xs:enumeration value="F"> + <xs:annotation> + <xs:documentation>Force Auth No Capture and no online authorization</xs:documentation> + </xs:annotation> + </xs:enumeration> + <xs:enumeration value="FR"> + <xs:annotation> + <xs:documentation>Force Auth No Capture and no online authorization</xs:documentation> + </xs:annotation> + </xs:enumeration> + <xs:enumeration value="FC"> + <xs:annotation> + <xs:documentation>Force Auth and Capture no online authorization</xs:documentation> + </xs:annotation> + </xs:enumeration> + <xs:enumeration value="R"> + <xs:annotation> + <xs:documentation>Refund and Capture no online authorization</xs:documentation> + </xs:annotation> + </xs:enumeration> + </xs:restriction> + </xs:simpleType> + <xs:simpleType name="valid-industry-types"> + <xs:annotation> + <xs:documentation>New order Industry Types</xs:documentation> + </xs:annotation> + <xs:restriction base="xs:string"> + <xs:maxLength value="20"/> + <xs:enumeration value="EC"> + <xs:annotation> + <xs:documentation>Ecommerce transaction</xs:documentation> + </xs:annotation> + </xs:enumeration> + <xs:enumeration value="RC"> + <xs:annotation> + <xs:documentation>Recurring Payment transaction</xs:documentation> + </xs:annotation> + </xs:enumeration> + <xs:enumeration value="MO"> + <xs:annotation> + <xs:documentation>Mail Order Telephone Order transaction</xs:documentation> + </xs:annotation> + </xs:enumeration> + <xs:enumeration value="IV"> + <xs:annotation> + <xs:documentation>Interactive Voice Response</xs:documentation> + </xs:annotation> + </xs:enumeration> + <xs:enumeration value="IN"> + <xs:annotation> + <xs:documentation>Interactive Voice Response</xs:documentation> + </xs:annotation> + </xs:enumeration> + </xs:restriction> + </xs:simpleType> + <xs:simpleType name="valid-tax-inds"> + <xs:union> + <xs:simpleType> + <xs:restriction base="xs:string"> + <xs:maxLength value="1"/> + <xs:enumeration value="0"> + <xs:annotation> + <xs:documentation>Tax not provided</xs:documentation> + </xs:annotation> + </xs:enumeration> + <xs:enumeration value="1"> + <xs:annotation> + <xs:documentation>Tax included</xs:documentation> + </xs:annotation> + </xs:enumeration> + <xs:enumeration value="2"> + <xs:annotation> + <xs:documentation>Non-taxable transaction</xs:documentation> + </xs:annotation> + </xs:enumeration> + </xs:restriction> + </xs:simpleType> + <xs:simpleType> + <xs:restriction base="xs:string"> + <xs:enumeration value=""/> + </xs:restriction> + </xs:simpleType> + </xs:union> + </xs:simpleType> + <xs:simpleType name="valid-routing-bins"> + <xs:restriction base="xs:string"> + <xs:maxLength value="6"/> + <xs:enumeration value="000001"> + <xs:annotation> + <xs:documentation>Stratus</xs:documentation> + </xs:annotation> + </xs:enumeration> + <xs:enumeration value="000002"> + <xs:annotation> + <xs:documentation>Tandam</xs:documentation> + </xs:annotation> + </xs:enumeration> + </xs:restriction> + </xs:simpleType> + <xs:simpleType name="valid-profileOrderOverideInds"> + <xs:union> + <xs:simpleType> + <xs:restriction base="xs:string"> + <xs:maxLength value="2"/> + <xs:enumeration value="NO"> + <xs:annotation> + <xs:documentation>No mapping to order data</xs:documentation> + </xs:annotation> + </xs:enumeration> + <xs:enumeration value="OI"> + <xs:annotation> + <xs:documentation>Use customer reference for OrderID</xs:documentation> + </xs:annotation> + </xs:enumeration> + <xs:enumeration value="OA"> + <xs:annotation> + <xs:documentation>Use customer reference for both Order Id and Order Description</xs:documentation> + </xs:annotation> + </xs:enumeration> + <xs:enumeration value="OD"> + <xs:annotation> + <xs:documentation>Use customer reference for Order Description</xs:documentation> + </xs:annotation> + </xs:enumeration> + </xs:restriction> + </xs:simpleType> + <xs:simpleType> + <xs:restriction base="xs:string"> + <xs:enumeration value=""/> + </xs:restriction> + </xs:simpleType> + </xs:union> + </xs:simpleType> + <xs:simpleType name="valid-profileFromOrderInd"> + <xs:union> + <xs:simpleType> + <xs:restriction base="xs:string"> + <xs:maxLength value="1"/> + <xs:enumeration value="A"> + <xs:annotation> + <xs:documentation>Auto Generate the CustomerRefNum</xs:documentation> + </xs:annotation> + </xs:enumeration> + <xs:enumeration value="O"> + <xs:annotation> + <xs:documentation>Use OrderID as the CustomerRefNum</xs:documentation> + </xs:annotation> + </xs:enumeration> + <xs:enumeration value="S"> + <xs:annotation> + <xs:documentation>Use CustomerRefNum Element</xs:documentation> + </xs:annotation> + </xs:enumeration> + <xs:enumeration value="D"> + <xs:annotation> + <xs:documentation>Use the description as the CustomerRefNum</xs:documentation> + </xs:annotation> + </xs:enumeration> + <xs:enumeration value="I"> + <xs:annotation> + <xs:documentation>Ignore. We will Ignore this entry if it's passed in the XML</xs:documentation> + </xs:annotation> + </xs:enumeration> + </xs:restriction> + </xs:simpleType> + <xs:simpleType> + <xs:restriction base="xs:string"> + <xs:enumeration value=""/> + </xs:restriction> + </xs:simpleType> + </xs:union> + </xs:simpleType> + <xs:simpleType name="valid-card-brands"> + <xs:union> + <xs:simpleType> + <xs:restriction base="xs:string"> + <xs:maxLength value="2"/> + <xs:enumeration value="AX"> + <xs:annotation> + <xs:documentation>American Express</xs:documentation> + </xs:annotation> + </xs:enumeration> + <xs:enumeration value="CB"> + <xs:annotation> + <xs:documentation>Carte Blanche</xs:documentation> + </xs:annotation> + </xs:enumeration> + <xs:enumeration value="DC"> + <xs:annotation> + <xs:documentation>Diners Club</xs:documentation> + </xs:annotation> + </xs:enumeration> + <xs:enumeration value="DI"> + <xs:annotation> + <xs:documentation>Discover</xs:documentation> + </xs:annotation> + </xs:enumeration> + <xs:enumeration value="GC"> + <xs:annotation> + <xs:documentation>GE Twinpay Credit</xs:documentation> + </xs:annotation> + </xs:enumeration> + <xs:enumeration value="GE"> + <xs:annotation> + <xs:documentation>GECC Private Label Credit</xs:documentation> + </xs:annotation> + </xs:enumeration> + <xs:enumeration value="JC"> + <xs:annotation> + <xs:documentation>JCB</xs:documentation> + </xs:annotation> + </xs:enumeration> + <xs:enumeration value="MC"> + <xs:annotation> + <xs:documentation>Mastercard</xs:documentation> + </xs:annotation> + </xs:enumeration> + <xs:enumeration value="VI"> + <xs:annotation> + <xs:documentation>Visa</xs:documentation> + </xs:annotation> + </xs:enumeration> + <xs:enumeration value="GD"> + <xs:annotation> + <xs:documentation>GE Twinpay Debit</xs:documentation> + </xs:annotation> + </xs:enumeration> + <xs:enumeration value="SW"> + <xs:annotation> + <xs:documentation>Switch / Solo</xs:documentation> + </xs:annotation> + </xs:enumeration> + <xs:enumeration value="EC"> + <xs:annotation> + <xs:documentation>Electronic Check</xs:documentation> + </xs:annotation> + </xs:enumeration> + <xs:enumeration value="FC"> + <xs:annotation> + <xs:documentation>Flex Cache</xs:documentation> + </xs:annotation> + </xs:enumeration> + <xs:enumeration value="ED"> + <xs:annotation> + <xs:documentation>European Direct Debit </xs:documentation> + </xs:annotation> + </xs:enumeration> + <xs:enumeration value="BL"> + <xs:annotation> + <xs:documentation>Bill Me Later </xs:documentation> + </xs:annotation> + </xs:enumeration> + <xs:enumeration value="DP"> + <xs:annotation> + <xs:documentation>PINLess Debit </xs:documentation> + </xs:annotation> + </xs:enumeration> + <xs:enumeration value="IM"> + <xs:annotation> + <xs:documentation>International Maestro</xs:documentation> + </xs:annotation> + </xs:enumeration> + <xs:enumeration value="CZ"> + <xs:annotation> + <xs:documentation>ChaseNet Credit</xs:documentation> + </xs:annotation> + </xs:enumeration> + <xs:enumeration value="CR"> + <xs:annotation> + <xs:documentation>ChaseNet Signature Debit</xs:documentation> + </xs:annotation> + </xs:enumeration> + </xs:restriction> + </xs:simpleType> + <xs:simpleType> + <xs:restriction base="xs:string"> + <xs:enumeration value=""/> + </xs:restriction> + </xs:simpleType> + </xs:union> + </xs:simpleType> + <xs:simpleType name="valid-customer-account-types"> + <xs:union> + <xs:simpleType> + <xs:restriction base="xs:string"> + <xs:maxLength value="2"/> + <xs:enumeration value="CC"> + <xs:annotation> + <xs:documentation>Credit Card</xs:documentation> + </xs:annotation> + </xs:enumeration> + <xs:enumeration value="SW"> + <xs:annotation> + <xs:documentation>Swith/Solo</xs:documentation> + </xs:annotation> + </xs:enumeration> + <xs:enumeration value="EC"> + <xs:annotation> + <xs:documentation>Electronic Check</xs:documentation> + </xs:annotation> + </xs:enumeration> + <xs:enumeration value="DP"> + <xs:annotation> + <xs:documentation>PINLess Debit</xs:documentation> + </xs:annotation> + </xs:enumeration> + <xs:enumeration value="ED"> + <xs:annotation> + <xs:documentation>European Direct Debit</xs:documentation> + </xs:annotation> + </xs:enumeration> + <xs:enumeration value="IM"> + <xs:annotation> + <xs:documentation>International Maestro</xs:documentation> + </xs:annotation> + </xs:enumeration> + <xs:enumeration value="CZ"> + <xs:annotation> + <xs:documentation>International Maestro</xs:documentation> + </xs:annotation> + </xs:enumeration> + <xs:enumeration value="CR"> + <xs:annotation> + <xs:documentation>International Maestro</xs:documentation> + </xs:annotation> + </xs:enumeration> + <xs:enumeration value="AA"> + <xs:annotation> + <xs:documentation>International Maestro</xs:documentation> + </xs:annotation> + </xs:enumeration> + </xs:restriction> + </xs:simpleType> + <xs:simpleType> + <xs:restriction base="xs:string"> + <xs:enumeration value=""/> + </xs:restriction> + </xs:simpleType> + </xs:union> + </xs:simpleType> + <xs:simpleType name="valid-country-codes"> + <xs:union> + <xs:simpleType> + <xs:restriction base="xs:string"> + <xs:maxLength value="2"/> + <xs:enumeration value="US"> + <xs:annotation> + <xs:documentation>United States</xs:documentation> + </xs:annotation> + </xs:enumeration> + <xs:enumeration value="CA"> + <xs:annotation> + <xs:documentation>Canada</xs:documentation> + </xs:annotation> + </xs:enumeration> + <xs:enumeration value="GB"> + <xs:annotation> + <xs:documentation>Germany</xs:documentation> + </xs:annotation> + </xs:enumeration> + <xs:enumeration value="UK"> + <xs:annotation> + <xs:documentation>Great Britain</xs:documentation> + </xs:annotation> + </xs:enumeration> + </xs:restriction> + </xs:simpleType> + <xs:simpleType> + <xs:restriction base="xs:string"> + <xs:enumeration value=""/> + </xs:restriction> + </xs:simpleType> + </xs:union> + </xs:simpleType> + <xs:simpleType name="yes-or-no"> + <xs:restriction base="xs:string"> + <xs:maxLength value="1"/> + <xs:enumeration value="Y"> + <xs:annotation> + <xs:documentation>Yes</xs:documentation> + </xs:annotation> + </xs:enumeration> + <xs:enumeration value="y"> + <xs:annotation> + <xs:documentation>Yes</xs:documentation> + </xs:annotation> + </xs:enumeration> + <xs:enumeration value="N"> + <xs:annotation> + <xs:documentation>No</xs:documentation> + </xs:annotation> + </xs:enumeration> + <xs:enumeration value="n"> + <xs:annotation> + <xs:documentation>No</xs:documentation> + </xs:annotation> + </xs:enumeration> + </xs:restriction> + </xs:simpleType> + <xs:simpleType name="flex-action-types"> + <xs:restriction base="xs:string"> + <xs:maxLength value="30"/> + <xs:pattern value="([Bb][Aa][Ll][Aa][Nn][Cc][Ee][Ii][Nn][Qq][Uu][Ii][Rr][Yy])"/> + <xs:pattern value="([Aa][Dd][Dd][Vv][Aa][Ll][Uu][Ee])"/> + <xs:pattern value="([Rr][Ee][Ff][Uu][Nn][Dd])"/> + <xs:pattern value="([Aa][Uu][Tt][Hh])"/> + <xs:pattern value="([Aa][Cc][Tt][Ii][Vv][Aa][Tt][Ee])"/> + <xs:pattern value="([Dd][Ee][Aa][Cc][Tt][Ii][Vv][Aa][Tt][Ee])"/> + <xs:pattern value="([Rr][Ee][Aa][Cc][Tt][Ii][Vv][Aa][Tt][Ee])"/> + <xs:pattern value="([Rr][Ee][Dd][Ee][Mm][Pp][Tt][Ii][Oo][Nn][Cc][Oo][Mm][Pp][Ll][Ee][Tt][Ii][Oo][Nn])"/> + <xs:pattern value="([Rr][Ee][Dd][Ee][Mm][Pp][Tt][Ii][Oo][Nn])"/> + <xs:pattern value="([Vv][Oo][Ii][Dd])"/> + </xs:restriction> + </xs:simpleType> + <xs:simpleType name="valid-eci-types"> + <xs:union> + <xs:simpleType> + <xs:restriction base="xs:string"> + <xs:maxLength value="2"/> + </xs:restriction> + </xs:simpleType> + </xs:union> + </xs:simpleType> + <xs:simpleType name="recurring-ind-types"> + <xs:union> + <xs:simpleType> + <xs:restriction base="xs:string"> + <xs:maxLength value="2"/> + <xs:enumeration value="RF"> + <xs:annotation> + <xs:documentation>First Recurring Transaction</xs:documentation> + </xs:annotation> + </xs:enumeration> + <xs:enumeration value="RS"> + <xs:annotation> + <xs:documentation>Subsequent Recurring Transactions</xs:documentation> + </xs:annotation> + </xs:enumeration> + <xs:enumeration value="IF"> + <xs:annotation> + <xs:documentation>First Installment Transaction</xs:documentation> + </xs:annotation> + </xs:enumeration> + <xs:enumeration value="IS"> + <xs:annotation> + <xs:documentation>Subsequent Installment Transactions</xs:documentation> + </xs:annotation> + </xs:enumeration> + </xs:restriction> + </xs:simpleType> + <xs:simpleType> + <xs:restriction base="xs:string"> + <xs:enumeration value=""/> + </xs:restriction> + </xs:simpleType> + </xs:union> + </xs:simpleType> + <xs:element name="SettleRejectHoldingBin"> + <xs:complexType/> + </xs:element> + <xs:simpleType name="profile-action-types"> + <xs:restriction base="xs:string"> + <xs:maxLength value="1"/> + <xs:enumeration value="C"/> + <xs:enumeration value="R"/> + <xs:enumeration value="U"/> + <xs:enumeration value="D"/> + </xs:restriction> + </xs:simpleType> + <xs:simpleType name="vallid-prior-auth"> + <xs:restriction base="xs:string"> + <xs:maxLength value="6"/> + </xs:restriction> + </xs:simpleType> +</xs:schema> diff --git a/test/schema/vindicia/Vindicia.xsd b/test/schema/vindicia/Vindicia.xsd deleted file mode 100644 index cd642358c7c..00000000000 --- a/test/schema/vindicia/Vindicia.xsd +++ /dev/null @@ -1,1005 +0,0 @@ - <xsd:schema targetNamespace="http://soap.vindicia.com/v3_6/Vindicia" xmlns="http://schemas.xmlsoap.org/wsdl/" xmlns:enc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:tns="http://soap.vindicia.com/v3_6/Vindicia" xmlns:vin="http://soap.vindicia.com/v3_6/Vindicia" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsdl="http://soap.vindicia.com/v3_6/Vindicia" > - <xsd:import namespace="http://schemas.xmlsoap.org/soap/encoding/" /> - <xsd:complexType name="Account"> - <xsd:sequence> - <xsd:element minOccurs="0" name="VID" type="xsd:string" /> - <xsd:element minOccurs="0" name="merchantAccountId" type="xsd:string" /> - <xsd:element minOccurs="0" name="emailAddress" type="xsd:string" /> - <xsd:element minOccurs="0" name="emailTypePreference" type="tns:EmailPreference" /> - <xsd:element minOccurs="0" name="preferredLanguage" type="xsd:string" /> - <xsd:element minOccurs="0" name="warnBeforeAutobilling" type="xsd:boolean" /> - <xsd:element minOccurs="0" name="company" type="xsd:string" /> - <xsd:element minOccurs="0" name="name" type="xsd:string" /> - <xsd:element minOccurs="0" name="shippingAddress" type="tns:Address" /> - <xsd:element maxOccurs="unbounded" minOccurs="0" name="paymentMethods" type="vin:PaymentMethod" /> - <xsd:element maxOccurs="unbounded" minOccurs="0" name="nameValues" type="vin:NameValuePair" /> - <xsd:element maxOccurs="unbounded" minOccurs="0" name="taxExemptions" type="vin:TaxExemption" /> - <xsd:element maxOccurs="unbounded" minOccurs="0" name="tokenBalances" type="vin:TokenAmount" /> - <xsd:element minOccurs="0" name="credit" type="vin:Credit" /> - </xsd:sequence> - </xsd:complexType> - <xsd:complexType name="ActivityTypeArg"> - <xsd:sequence> - <xsd:element minOccurs="0" name="loginArgs" type="tns:ActivityLogin" /> - <xsd:element minOccurs="0" name="logoutArgs" type="tns:ActivityLogout" /> - <xsd:element minOccurs="0" name="uriviewArgs" type="tns:ActivityURIView" /> - <xsd:element minOccurs="0" name="phoneArgs" type="tns:ActivityPhoneContact" /> - <xsd:element minOccurs="0" name="emailArgs" type="tns:ActivityEmailContact" /> - <xsd:element minOccurs="0" name="fulfillmentArgs" type="tns:ActivityFulfillment" /> - <xsd:element minOccurs="0" name="usageArgs" type="tns:ActivityUsage" /> - <xsd:element minOccurs="0" name="namedvalueArgs" type="tns:ActivityNamedValue" /> - <xsd:element minOccurs="0" name="cancellationArgs" type="tns:ActivityCancellation" /> - <xsd:element minOccurs="0" name="noteArgs" type="tns:ActivityNote" /> - </xsd:sequence> - </xsd:complexType> - <xsd:complexType name="ActivityLogin"> - <xsd:sequence> - <xsd:element minOccurs="0" name="ip" type="xsd:string" /> - </xsd:sequence> - </xsd:complexType> - <xsd:complexType name="ActivityLogout"> - <xsd:sequence> - <xsd:element minOccurs="0" name="ip" type="xsd:string" /> - </xsd:sequence> - </xsd:complexType> - <xsd:complexType name="ActivityUsage"> - <xsd:sequence> - <xsd:element minOccurs="0" name="description" type="xsd:string" /> - <xsd:element minOccurs="0" name="total" type="xsd:int" /> - <xsd:element minOccurs="0" name="lastDay" type="xsd:int" /> - <xsd:element minOccurs="0" name="lastWeek" type="xsd:int" /> - <xsd:element minOccurs="0" name="lastMonth" type="xsd:int" /> - <xsd:element minOccurs="0" name="lastYear" type="xsd:int" /> - <xsd:element minOccurs="0" name="lastUsageDate" type="xsd:dateTime" /> - </xsd:sequence> - </xsd:complexType> - <xsd:complexType name="ActivityNamedValue"> - <xsd:sequence> - <xsd:element minOccurs="1" name="type" type="xsd:string" /> - <xsd:element minOccurs="1" name="name" type="xsd:string" /> - <xsd:element minOccurs="1" name="value" type="xsd:string" /> - </xsd:sequence> - </xsd:complexType> - <xsd:complexType name="ActivityPhoneContact"> - <xsd:sequence> - <xsd:element minOccurs="0" name="srcPhoneNumber" type="xsd:string" /> - <xsd:element minOccurs="0" name="destPhoneNumber" type="xsd:string" /> - <xsd:element minOccurs="0" name="cidPhoneNumber" type="xsd:string" /> - <xsd:element minOccurs="0" name="aniPhoneNumber" type="xsd:string" /> - <xsd:element minOccurs="0" name="durationSeconds" type="xsd:int" /> - <xsd:element minOccurs="0" name="note" type="xsd:string" /> - <xsd:element minOccurs="1" name="type" type="tns:ActivityCallType" /> - </xsd:sequence> - </xsd:complexType> - <xsd:complexType name="ActivityEmailContact"> - <xsd:sequence> - <xsd:element minOccurs="0" name="srcEmail" type="xsd:string" /> - <xsd:element minOccurs="0" name="destEmail" type="xsd:string" /> - <xsd:element minOccurs="0" name="note" type="xsd:string" /> - </xsd:sequence> - </xsd:complexType> - <xsd:complexType name="ActivityFulfillment"> - <xsd:sequence> - <xsd:element minOccurs="0" name="merchantTransactionId" type="xsd:string" /> - <xsd:element minOccurs="0" name="shipper" type="xsd:string" /> - <xsd:element minOccurs="0" name="trackingString" type="xsd:string" /> - <xsd:element minOccurs="0" name="shippingAddress" type="tns:Address" /> - <xsd:element minOccurs="0" name="delivered" type="xsd:boolean" /> - <xsd:element minOccurs="0" name="receivedTs" type="xsd:dateTime" /> - <xsd:element minOccurs="0" name="receiptName" type="xsd:string" /> - </xsd:sequence> - </xsd:complexType> - <xsd:complexType name="ActivityURIView"> - <xsd:sequence> - <xsd:element minOccurs="0" name="ip" type="xsd:string" /> - <xsd:element minOccurs="1" name="uri" type="xsd:anyURI" /> - <xsd:element minOccurs="0" name="size" type="xsd:int" /> - <xsd:element minOccurs="0" name="bytesTransferred" type="xsd:int" /> - <xsd:element minOccurs="0" name="transferTime" type="xsd:int" /> - <xsd:element minOccurs="1" name="description" type="xsd:string" /> - </xsd:sequence> - </xsd:complexType> - <xsd:complexType name="ActivityCancellation"> - <xsd:sequence> - <xsd:element minOccurs="0" name="reason" type="xsd:string" /> - <xsd:element minOccurs="0" name="confirmationCode" type="xsd:int" /> - <xsd:element minOccurs="1" name="initiator" type="tns:ActivityCancelInitType" /> - </xsd:sequence> - </xsd:complexType> - <xsd:complexType name="ActivityNote"> - <xsd:sequence> - <xsd:element minOccurs="0" name="note" type="xsd:string" /> - </xsd:sequence> - </xsd:complexType> - <xsd:complexType name="Activity"> - <xsd:sequence> - <xsd:element minOccurs="0" name="account" type="tns:Account" /> - <xsd:element minOccurs="1" name="activityType" type="tns:ActivityType" /> - <xsd:element minOccurs="0" name="activityArgs" type="tns:ActivityTypeArg" /> - <xsd:element minOccurs="1" name="timestamp" type="xsd:dateTime" /> - </xsd:sequence> - </xsd:complexType> - <xsd:complexType name="Address"> - <xsd:sequence> - <xsd:element minOccurs="0" name="VID" type="xsd:string" /> - <xsd:element minOccurs="0" name="name" type="xsd:string" /> - <xsd:element minOccurs="0" name="addr1" type="xsd:string" /> - <xsd:element minOccurs="0" name="addr2" type="xsd:string" /> - <xsd:element minOccurs="0" name="addr3" type="xsd:string" /> - <xsd:element minOccurs="0" name="city" type="xsd:string" /> - <xsd:element minOccurs="0" name="county" type="xsd:string" /> - <xsd:element minOccurs="0" name="district" type="xsd:string" /> - <xsd:element minOccurs="0" name="postalCode" type="xsd:string" /> - <xsd:element minOccurs="0" name="country" type="xsd:string" /> - <xsd:element minOccurs="0" name="phone" type="xsd:string" /> - <xsd:element minOccurs="0" name="fax" type="xsd:string" /> - <xsd:element minOccurs="0" name="latitude" type="xsd:decimal" /> - <xsd:element minOccurs="0" name="longitude" type="xsd:decimal" /> - </xsd:sequence> - </xsd:complexType> - <xsd:complexType name="Authentication"> - <xsd:sequence> - <xsd:element minOccurs="1" name="version" type="xsd:string" /> - <xsd:element minOccurs="1" name="login" type="xsd:string" /> - <xsd:element minOccurs="1" name="password" type="xsd:string" /> - <xsd:element minOccurs="0" name="evid" type="xsd:string" /> - <xsd:element minOccurs="0" name="userAgent" type="xsd:string" /> - </xsd:sequence> - </xsd:complexType> - <xsd:complexType name="AutoBill"> - <xsd:sequence> - <xsd:element minOccurs="0" name="VID" type="xsd:string" /> - <xsd:element minOccurs="0" name="merchantAutoBillId" type="xsd:string" /> - <xsd:element minOccurs="0" name="account" type="tns:Account" /> - <xsd:element minOccurs="0" name="product" type="tns:Product" /> - <xsd:element minOccurs="0" name="billingPlan" type="tns:BillingPlan" /> - <xsd:element minOccurs="0" name="paymentMethod" type="tns:PaymentMethod" /> - <xsd:element minOccurs="0" name="currency" type="xsd:string" /> - <xsd:element minOccurs="0" name="customerAutoBillName" type="xsd:string" /> - <xsd:element minOccurs="0" name="status" type="tns:AutoBillStatus" /> - <xsd:element minOccurs="0" name="startTimestamp" type="xsd:dateTime" /> - <xsd:element minOccurs="0" name="endTimestamp" type="xsd:dateTime" /> - <xsd:element minOccurs="0" name="sourceIp" type="xsd:string" /> - <xsd:element minOccurs="0" name="billingStatementIdentifier" type="xsd:string" /> - <xsd:element minOccurs="0" name="billingDay" type="xsd:int" /> - <xsd:element minOccurs="0" name="minimumCommitment" type="xsd:int" /> - <xsd:element minOccurs="0" name="merchantAffiliateId" type="xsd:string" /> - <xsd:element minOccurs="0" name="merchantAffiliateSubId" type="xsd:string" /> - <xsd:element minOccurs="0" name="warnOnExpiration" type="xsd:boolean" /> - <xsd:element maxOccurs="unbounded" minOccurs="0" name="futureRebills" type="vin:Transaction" /> - <xsd:element maxOccurs="unbounded" minOccurs="0" name="nameValues" type="vin:NameValuePair" /> - <xsd:element minOccurs="0" name="credit" type="vin:Credit" /> - <xsd:element minOccurs="0" name="upgradedFromVid" type="xsd:string" /> - <xsd:element minOccurs="0" name="upgradedToVid" type="xsd:string" /> - </xsd:sequence> - </xsd:complexType> - <xsd:complexType name="AutoBillUpgradeHistoryStep"> - <xsd:sequence> - <xsd:element minOccurs="0" name="vid" type="xsd:string" /> - <xsd:element minOccurs="0" name="startTimestamp" type="xsd:dateTime" /> - <xsd:element minOccurs="0" name="endTimestamp" type="xsd:dateTime" /> - </xsd:sequence> - </xsd:complexType> - <xsd:complexType name="BillingPlanPrice"> - <xsd:sequence> - <xsd:element minOccurs="0" name="amount" type="xsd:decimal" /> - <xsd:element minOccurs="0" name="currency" type="xsd:string" /> - <xsd:element minOccurs="0" name="priceListName" type="xsd:string" /> - <xsd:element minOccurs="0" name="tokenAmount" type="tns:TokenAmount" /> - </xsd:sequence> - </xsd:complexType> - <xsd:complexType name="BillingPlanPeriod"> - <xsd:sequence> - <xsd:element minOccurs="0" name="type" type="tns:BillingPeriodType" /> - <xsd:element minOccurs="0" name="quantity" type="xsd:int" /> - <xsd:element minOccurs="0" name="cycles" type="xsd:int" /> - <xsd:element maxOccurs="unbounded" minOccurs="0" name="prices" type="vin:BillingPlanPrice" /> - <xsd:element minOccurs="0" name="expireWarningDays" type="xsd:int" /> - <xsd:element minOccurs="0" name="doNotNotifyFirstBill" type="xsd:boolean" /> - </xsd:sequence> - </xsd:complexType> - <xsd:complexType name="BillingPlan"> - <xsd:sequence> - <xsd:element minOccurs="0" name="VID" type="xsd:string" /> - <xsd:element minOccurs="0" name="merchantBillingPlanId" type="xsd:string" /> - <xsd:element minOccurs="0" name="description" type="xsd:string" /> - <xsd:element minOccurs="0" name="status" type="tns:BillingPlanStatus" /> - <xsd:element maxOccurs="unbounded" minOccurs="0" name="periods" type="vin:BillingPlanPeriod" /> - <xsd:element minOccurs="0" name="prenotifyDays" type="xsd:int" /> - <xsd:element minOccurs="0" name="endOfLifeTimestamp" type="xsd:dateTime" /> - <xsd:element maxOccurs="unbounded" minOccurs="0" name="nameValues" type="vin:NameValuePair" /> - <xsd:element maxOccurs="unbounded" minOccurs="0" name="merchantEntitlementIds" type="vin:MerchantEntitlementId" /> - <xsd:element minOccurs="0" name="billingStatementIdentifier" type="xsd:string" /> - </xsd:sequence> - </xsd:complexType> - <xsd:complexType name="Chargeback"> - <xsd:sequence> - <xsd:element minOccurs="0" name="VID" type="xsd:string" /> - <xsd:element minOccurs="1" name="amount" type="xsd:decimal" /> - <xsd:element minOccurs="0" name="currency" type="xsd:string" /> - <xsd:element minOccurs="0" name="presentmentAmount" type="xsd:decimal" /> - <xsd:element minOccurs="0" name="presentmentCurrency" type="xsd:string" /> - <xsd:element minOccurs="0" name="divisionNumber" type="xsd:string" /> - <xsd:element minOccurs="1" name="reasonCode" type="xsd:string" /> - <xsd:element minOccurs="0" name="caseNumber" type="xsd:string" /> - <xsd:element minOccurs="0" name="referenceNumber" type="xsd:string" /> - <xsd:element minOccurs="0" name="merchantNumber" type="xsd:string" /> - <xsd:element minOccurs="0" name="merchantTransactionId" type="xsd:string" /> - <xsd:element minOccurs="0" name="merchantTransactionTimestamp" type="xsd:dateTime" /> - <xsd:element minOccurs="1" name="processorReceivedTimestamp" type="xsd:dateTime" /> - <xsd:element minOccurs="1" name="postedTimestamp" type="xsd:dateTime" /> - <xsd:element minOccurs="0" name="statusChangedTimestamp" type="xsd:dateTime" /> - <xsd:element minOccurs="1" name="status" type="tns:ChargebackStatus" /> - <xsd:element minOccurs="0" name="merchantUserId" type="xsd:string" /> - <xsd:element minOccurs="0" name="note" type="xsd:string" /> - <xsd:element maxOccurs="unbounded" minOccurs="0" name="nameValues" type="vin:NameValuePair" /> - </xsd:sequence> - </xsd:complexType> - <xsd:complexType name="TimeInterval"> - <xsd:sequence> - <xsd:element minOccurs="0" name="type" type="tns:TimeIntervalType" /> - <xsd:element minOccurs="0" name="amount" type="xsd:int" /> - </xsd:sequence> - </xsd:complexType> - <xsd:complexType name="CurrencyAmount"> - <xsd:sequence> - <xsd:element minOccurs="0" name="currency" type="xsd:string" /> - <xsd:element minOccurs="1" name="amount" type="xsd:decimal" /> - </xsd:sequence> - </xsd:complexType> - <xsd:complexType name="Credit"> - <xsd:sequence> - <xsd:element maxOccurs="unbounded" minOccurs="0" name="tokenAmounts" type="vin:TokenAmount" /> - <xsd:element maxOccurs="unbounded" minOccurs="0" name="timeIntervals" type="vin:TimeInterval" /> - <xsd:element maxOccurs="unbounded" minOccurs="0" name="currencyAmounts" type="vin:CurrencyAmount" /> - </xsd:sequence> - </xsd:complexType> - <xsd:complexType name="CreditEventLog"> - <xsd:sequence> - <xsd:element minOccurs="0" name="credit" type="tns:Credit" /> - <xsd:element minOccurs="0" name="timeStamp" type="xsd:dateTime" /> - <xsd:element minOccurs="0" name="type" type="tns:CreditEventType" /> - <xsd:element minOccurs="0" name="note" type="xsd:string" /> - </xsd:sequence> - </xsd:complexType> - <xsd:complexType name="Diagnostic"> - <xsd:sequence> - <xsd:element minOccurs="0" name="a_field" type="xsd:string" /> - </xsd:sequence> - </xsd:complexType> - <xsd:complexType name="GiftCardStatus"> - <xsd:sequence> - <xsd:element minOccurs="0" name="status" type="tns:GiftCardStatusType" /> - <xsd:element minOccurs="0" name="timestamp" type="xsd:dateTime" /> - <xsd:element minOccurs="0" name="providerResponseCode" type="xsd:string" /> - <xsd:element minOccurs="0" name="providerResponseMsg" type="xsd:string" /> - <xsd:element maxOccurs="unbounded" minOccurs="0" name="nameValues" type="vin:NameValuePair" /> - </xsd:sequence> - </xsd:complexType> - <xsd:complexType name="GiftCard"> - <xsd:sequence> - <xsd:element minOccurs="0" name="sku" type="xsd:string" /> - <xsd:element minOccurs="0" name="pin" type="xsd:string" /> - <xsd:element minOccurs="0" name="lastDigits" type="xsd:string" /> - <xsd:element minOccurs="0" name="pinLength" type="xsd:int" /> - <xsd:element minOccurs="0" name="hashType" type="vin:HashType" /> - <xsd:element minOccurs="0" name="pinHash" type="xsd:string" /> - <xsd:element minOccurs="0" name="paymentProvider" type="xsd:string" /> - <xsd:element minOccurs="0" name="status" type="tns:GiftCardStatus" /> - <xsd:element minOccurs="0" name="VID" type="xsd:string" /> - <xsd:element minOccurs="0" name="product" type="tns:Product" /> - </xsd:sequence> - </xsd:complexType> - <xsd:complexType name="ElectronicSignature"> - <xsd:sequence> - <xsd:element minOccurs="0" name="VID" type="xsd:string" /> - <xsd:element minOccurs="1" name="uri" type="xsd:anyURI" /> - <xsd:element minOccurs="0" name="sourceIp" type="xsd:string" /> - <xsd:element minOccurs="1" name="timestamp" type="xsd:dateTime" /> - <xsd:element minOccurs="1" name="account" type="tns:Account" /> - <xsd:element minOccurs="1" name="transaction" type="tns:Transaction" /> - <xsd:element minOccurs="0" name="description" type="xsd:string" /> - </xsd:sequence> - </xsd:complexType> - <xsd:complexType name="EmailTemplate"> - <xsd:sequence> - <xsd:element minOccurs="0" name="vid" type="xsd:string" /> - <xsd:element minOccurs="1" name="product" type="tns:Product" /> - <xsd:element minOccurs="0" name="templateType" type="vin:EmailTemplateType" /> - <xsd:element minOccurs="0" name="version" type="xsd:string" /> - <xsd:element minOccurs="0" name="from" type="xsd:string" /> - <xsd:element minOccurs="0" name="replyTo" type="xsd:string" /> - <xsd:element minOccurs="0" name="htmlMessage" type="xsd:string" /> - <xsd:element minOccurs="0" name="textMessage" type="xsd:string" /> - </xsd:sequence> - </xsd:complexType> - <xsd:complexType name="Entitlement"> - <xsd:sequence> - <xsd:element minOccurs="0" name="merchantEntitlementId" type="xsd:string" /> - <xsd:element minOccurs="0" name="account" type="vin:Account" /> - <xsd:element minOccurs="0" name="startTimestamp" type="xsd:dateTime" /> - <xsd:element minOccurs="0" name="endTimestamp" type="xsd:dateTime" /> - <xsd:element minOccurs="0" name="active" type="xsd:boolean" /> - </xsd:sequence> - </xsd:complexType> - <xsd:complexType name="MetricStatistics"> - <xsd:sequence> - <xsd:element minOccurs="1" name="type" type="vin:MetricStatusType" /> - <xsd:element minOccurs="1" name="numTransactions" type="xsd:int" /> - <xsd:element minOccurs="1" name="minMs" type="xsd:long" /> - <xsd:element minOccurs="1" name="avgMs" type="xsd:long" /> - <xsd:element minOccurs="1" name="maxMs" type="xsd:long" /> - </xsd:sequence> - </xsd:complexType> - <xsd:complexType name="CreditCard"> - <xsd:sequence> - <xsd:element minOccurs="0" name="account" type="xsd:string" /> - <xsd:element minOccurs="0" name="bin" type="xsd:string" /> - <xsd:element minOccurs="0" name="lastDigits" type="xsd:string" /> - <xsd:element minOccurs="0" name="accountLength" type="xsd:int" /> - <xsd:element minOccurs="0" name="hashType" type="vin:HashType" /> - <xsd:element minOccurs="0" name="accountHash" type="xsd:string" /> - <xsd:element minOccurs="0" name="expirationDate" type="xsd:string" /> - </xsd:sequence> - </xsd:complexType> - <xsd:complexType name="ECP"> - <xsd:sequence> - <xsd:element minOccurs="0" name="account" type="xsd:string" /> - <xsd:element minOccurs="0" name="hashType" type="vin:HashType" /> - <xsd:element minOccurs="0" name="accountHash" type="xsd:string" /> - <xsd:element minOccurs="0" name="routingNumber" type="xsd:string" /> - <xsd:element minOccurs="0" name="accountType" type="tns:AccountType" /> - <xsd:element minOccurs="0" name="lastDigits" type="xsd:string" /> - <xsd:element minOccurs="0" name="accountLength" type="xsd:int" /> - <xsd:element minOccurs="0" name="allowedTransactionType" type="tns:ECPTransactionType" /> - </xsd:sequence> - </xsd:complexType> - <xsd:complexType name="DirectDebit"> - <xsd:sequence> - <xsd:element minOccurs="0" name="account" type="xsd:string" /> - <xsd:element minOccurs="0" name="lastDigits" type="xsd:string" /> - <xsd:element minOccurs="0" name="accountLength" type="xsd:int" /> - <xsd:element minOccurs="0" name="hashType" type="vin:HashType" /> - <xsd:element minOccurs="0" name="accountHash" type="xsd:string" /> - <xsd:element minOccurs="0" name="countryCode" type="xsd:string" /> - <xsd:element minOccurs="0" name="bankSortCode" type="xsd:string" /> - <xsd:element minOccurs="0" name="ribCode" type="xsd:string" /> - </xsd:sequence> - </xsd:complexType> - <xsd:complexType name="PayPal"> - <xsd:sequence> - <xsd:element minOccurs="0" name="paypalEmail" type="xsd:string" /> - <xsd:element minOccurs="0" name="payerId" type="xsd:string" /> - <xsd:element minOccurs="0" name="returnUrl" type="xsd:string" /> - <xsd:element minOccurs="0" name="cancelUrl" type="xsd:string" /> - </xsd:sequence> - </xsd:complexType> - <xsd:complexType name="Boleto"> - <xsd:sequence> - <xsd:element minOccurs="0" name="fiscalNumber" type="xsd:string" /> - </xsd:sequence> - </xsd:complexType> - <xsd:complexType name="PaymentMethod"> - <xsd:sequence> - <xsd:element minOccurs="0" name="VID" type="xsd:string" /> - <xsd:element minOccurs="0" name="type" type="tns:PaymentMethodType" /> - <xsd:element minOccurs="0" name="creditCard" type="tns:CreditCard" /> - <xsd:element minOccurs="0" name="ecp" type="tns:ECP" /> - <xsd:element minOccurs="0" name="directDebit" type="tns:DirectDebit" /> - <xsd:element minOccurs="0" name="paypal" type="tns:PayPal" /> - <xsd:element minOccurs="0" name="boleto" type="tns:Boleto" /> - <xsd:element minOccurs="0" name="token" type="tns:Token" /> - <xsd:element maxOccurs="unbounded" minOccurs="0" name="nameValues" type="vin:NameValuePair" /> - <xsd:element minOccurs="0" name="accountHolderName" type="xsd:string" /> - <xsd:element minOccurs="0" name="billingAddress" type="tns:Address" /> - <xsd:element minOccurs="0" name="customerSpecifiedType" type="xsd:string" /> - <xsd:element minOccurs="0" name="customerDescription" type="xsd:string" /> - <xsd:element minOccurs="0" name="merchantPaymentMethodId" type="xsd:string" /> - <xsd:element minOccurs="0" name="currency" type="xsd:string" /> - <xsd:element minOccurs="0" name="sortOrder" type="xsd:int" /> - <xsd:element minOccurs="0" name="active" type="xsd:boolean" /> - </xsd:sequence> - </xsd:complexType> - <xsd:complexType name="PaymentProvider"> - <xsd:sequence> - <xsd:element minOccurs="0" name="VID" type="xsd:string" /> - <xsd:element minOccurs="0" name="description" type="xsd:string" /> - <xsd:element minOccurs="0" name="paymentType" type="tns:PaymentMethodType" /> - <xsd:element minOccurs="0" name="account" type="xsd:string" /> - </xsd:sequence> - </xsd:complexType> - <xsd:complexType name="MerchantEntitlementId"> - <xsd:sequence> - <xsd:element minOccurs="1" name="id" type="xsd:string" /> - <xsd:element minOccurs="0" name="description" type="xsd:string" /> - </xsd:sequence> - </xsd:complexType> - <xsd:complexType name="NameValuePair"> - <xsd:sequence> - <xsd:element minOccurs="1" name="name" type="xsd:string" /> - <xsd:element minOccurs="1" name="value" type="xsd:string" /> - </xsd:sequence> - </xsd:complexType> - <xsd:complexType name="Product"> - <xsd:sequence> - <xsd:element minOccurs="0" name="VID" type="xsd:string" /> - <xsd:element minOccurs="0" name="merchantProductId" type="xsd:string" /> - <xsd:element minOccurs="0" name="description" type="xsd:string" /> - <xsd:element minOccurs="0" name="status" type="tns:ProductStatus" /> - <xsd:element minOccurs="0" name="taxClassification" type="tns:TaxClassification" /> - <xsd:element minOccurs="0" name="defaultBillingPlan" type="tns:BillingPlan" /> - <xsd:element minOccurs="0" name="endOfLifeTimestamp" type="xsd:dateTime" /> - <xsd:element maxOccurs="unbounded" minOccurs="0" name="nameValues" type="vin:NameValuePair" /> - <xsd:element maxOccurs="unbounded" minOccurs="0" name="merchantEntitlementIds" type="vin:MerchantEntitlementId" /> - <xsd:element minOccurs="0" name="billingStatementIdentifier" type="xsd:string" /> - <xsd:element minOccurs="0" name="creditGranted" type="tns:Credit" /> - </xsd:sequence> - </xsd:complexType> - <xsd:complexType name="Refund"> - <xsd:sequence> - <xsd:element minOccurs="0" name="VID" type="xsd:string" /> - <xsd:element minOccurs="0" name="merchantRefundId" type="xsd:string" /> - <xsd:element minOccurs="0" name="transaction" type="tns:Transaction" /> - <xsd:element minOccurs="1" name="amount" type="xsd:decimal" /> - <xsd:element minOccurs="0" name="currency" type="xsd:string" /> - <xsd:element minOccurs="0" name="timestamp" type="xsd:dateTime" /> - <xsd:element minOccurs="0" name="referenceString" type="xsd:string" /> - <xsd:element minOccurs="0" name="note" type="xsd:string" /> - <xsd:element minOccurs="0" name="tokenAction" type="tns:RefundTokenAction" /> - <xsd:element minOccurs="0" name="credit" type="vin:Credit" /> - </xsd:sequence> - </xsd:complexType> - <xsd:complexType name="Return"> - <xsd:sequence> - <xsd:element minOccurs="1" name="returnCode" type="tns:ReturnCode" /> - <xsd:element minOccurs="0" name="returnString" type="xsd:string" /> - <xsd:element minOccurs="0" name="soapId" type="xsd:string" /> - </xsd:sequence> - </xsd:complexType> - <xsd:complexType name="TaxExemption"> - <xsd:sequence> - <xsd:element minOccurs="0" name="region" type="vin:TaxRegion" /> - <xsd:element minOccurs="0" name="exemptionId" type="xsd:string" /> - <xsd:element minOccurs="1" name="active" type="xsd:boolean" /> - </xsd:sequence> - </xsd:complexType> - <xsd:complexType name="SalesTax"> - <xsd:sequence> - <xsd:element minOccurs="1" name="description" type="xsd:string" /> - <xsd:element minOccurs="1" name="tax" type="xsd:decimal" /> - </xsd:sequence> - </xsd:complexType> - <xsd:complexType name="Token"> - <xsd:sequence> - <xsd:element minOccurs="0" name="VID" type="xsd:string" /> - <xsd:element minOccurs="0" name="merchantTokenId" type="xsd:string" /> - <xsd:element minOccurs="0" name="description" type="xsd:string" /> - </xsd:sequence> - </xsd:complexType> - <xsd:complexType name="TokenAmount"> - <xsd:sequence> - <xsd:element minOccurs="1" name="token" type="tns:Token" /> - <xsd:element minOccurs="1" name="amount" type="xsd:int" /> - </xsd:sequence> - </xsd:complexType> - <xsd:complexType name="TokenTransaction"> - <xsd:sequence> - <xsd:element minOccurs="1" name="account" type="tns:Account" /> - <xsd:element minOccurs="1" name="tokenAmount" type="tns:TokenAmount" /> - <xsd:element minOccurs="0" name="description" type="xsd:string" /> - <xsd:element minOccurs="1" name="authTimestamp" type="xsd:dateTime" /> - <xsd:element minOccurs="0" name="clearedTimestamp" type="xsd:dateTime" /> - </xsd:sequence> - </xsd:complexType> - <xsd:complexType name="CaptureResult"> - <xsd:sequence> - <xsd:element minOccurs="1" name="returnCode" type="xsd:int" /> - <xsd:element minOccurs="1" name="merchantTransactionId" type="xsd:string" /> - <xsd:element minOccurs="0" name="originalMerchantTransactionId" type="xsd:string" /> - </xsd:sequence> - </xsd:complexType> - <xsd:complexType name="CancelResult"> - <xsd:sequence> - <xsd:element minOccurs="1" name="returnCode" type="xsd:int" /> - <xsd:element minOccurs="1" name="merchantTransactionId" type="xsd:string" /> - </xsd:sequence> - </xsd:complexType> - <xsd:complexType name="ScoreCode"> - <xsd:sequence> - <xsd:element minOccurs="0" name="description" type="xsd:string" /> - <xsd:element minOccurs="0" name="id" type="xsd:string" /> - </xsd:sequence> - </xsd:complexType> - <xsd:complexType name="TransactionItem"> - <xsd:sequence> - <xsd:element minOccurs="0" name="sku" type="xsd:string" /> - <xsd:element minOccurs="1" name="name" type="xsd:string" /> - <xsd:element minOccurs="1" name="price" type="xsd:decimal" /> - <xsd:element minOccurs="1" name="quantity" type="xsd:int" /> - <xsd:element minOccurs="0" name="taxClassification" type="tns:TaxClassification" /> - <xsd:element maxOccurs="unbounded" minOccurs="0" name="tokens" type="vin:TokenAmount" /> - </xsd:sequence> - </xsd:complexType> - <xsd:complexType name="TransactionStatus"> - <xsd:sequence> - <xsd:element minOccurs="1" name="status" type="tns:TransactionStatusType" /> - <xsd:element minOccurs="1" name="timestamp" type="xsd:dateTime" /> - <xsd:element minOccurs="1" name="paymentMethodType" type="tns:PaymentMethodType" /> - <xsd:element minOccurs="0" name="creditCardStatus" type="tns:TransactionStatusCreditCard" /> - <xsd:element minOccurs="0" name="ecpStatus" type="tns:TransactionStatusECP" /> - <xsd:element minOccurs="0" name="boletoStatus" type="tns:TransactionStatusBoleto" /> - <xsd:element minOccurs="0" name="payPalStatus" type="tns:TransactionStatusPayPal" /> - <xsd:element minOccurs="0" name="directDebitStatus" type="tns:TransactionStatusDirectDebit" /> - <xsd:element minOccurs="0" name="vinAVS" type="tns:AVSMatchType" /> - </xsd:sequence> - </xsd:complexType> - <xsd:complexType name="TransactionStatusCreditCard"> - <xsd:sequence> - <xsd:element minOccurs="1" name="authCode" type="xsd:string" /> - <xsd:element minOccurs="0" name="avsCode" type="xsd:string" /> - <xsd:element minOccurs="0" name="cvnCode" type="xsd:string" /> - </xsd:sequence> - </xsd:complexType> - <xsd:complexType name="TransactionStatusECP"> - <xsd:sequence> - <xsd:element minOccurs="0" name="authCode" type="xsd:string" /> - </xsd:sequence> - </xsd:complexType> - <xsd:complexType name="TransactionStatusBoleto"> - <xsd:sequence> - <xsd:element minOccurs="0" name="uri" type="xsd:string" /> - </xsd:sequence> - </xsd:complexType> - <xsd:complexType name="TransactionStatusPayPal"> - <xsd:sequence> - <xsd:element minOccurs="0" name="token" type="xsd:string" /> - <xsd:element minOccurs="0" name="paypalEmail" type="xsd:string" /> - <xsd:element minOccurs="0" name="payerId" type="xsd:string" /> - <xsd:element minOccurs="0" name="authCode" type="xsd:string" /> - <xsd:element minOccurs="0" name="redirectUrl" type="xsd:string" /> - </xsd:sequence> - </xsd:complexType> - <xsd:complexType name="TransactionStatusDirectDebit"> - <xsd:sequence> - <xsd:element minOccurs="0" name="authCode" type="xsd:string" /> - </xsd:sequence> - </xsd:complexType> - <xsd:complexType name="Transaction"> - <xsd:sequence> - <xsd:element minOccurs="0" name="VID" type="xsd:string" /> - <xsd:element minOccurs="0" name="amount" type="xsd:decimal" /> - <xsd:element minOccurs="0" name="currency" type="xsd:string" /> - <xsd:element minOccurs="0" name="divisionNumber" type="xsd:string" /> - <xsd:element minOccurs="0" name="merchantTransactionId" type="xsd:string" /> - <xsd:element minOccurs="0" name="previousMerchantTransactionId" type="xsd:string" /> - <xsd:element minOccurs="0" name="timestamp" type="xsd:dateTime" /> - <xsd:element minOccurs="0" name="account" type="tns:Account" /> - <xsd:element minOccurs="0" name="sourcePaymentMethod" type="tns:PaymentMethod" /> - <xsd:element minOccurs="0" name="destPaymentMethod" type="tns:PaymentMethod" /> - <xsd:element minOccurs="0" name="ecpTransactionType" type="tns:ECPTransactionType" /> - <xsd:element maxOccurs="unbounded" minOccurs="0" name="statusLog" type="vin:TransactionStatus" /> - <xsd:element minOccurs="0" name="paymentProcessor" type="xsd:string" /> - <xsd:element minOccurs="0" name="sourcePhoneNumber" type="xsd:string" /> - <xsd:element minOccurs="0" name="shippingAddress" type="tns:Address" /> - <xsd:element maxOccurs="unbounded" minOccurs="0" name="nameValues" type="vin:NameValuePair" /> - <xsd:element maxOccurs="unbounded" minOccurs="0" name="transactionItems" type="vin:TransactionItem" /> - <xsd:element minOccurs="0" name="merchantAffiliateId" type="xsd:string" /> - <xsd:element minOccurs="0" name="merchantAffiliateSubId" type="xsd:string" /> - <xsd:element minOccurs="0" name="userAgent" type="xsd:string" /> - <xsd:element minOccurs="0" name="note" type="xsd:string" /> - <xsd:element minOccurs="0" name="preferredNotificationLanguage" type="xsd:string" /> - <xsd:element minOccurs="0" name="sourceMacAddress" type="xsd:string" /> - <xsd:element minOccurs="0" name="sourceIp" type="xsd:string" /> - <xsd:element minOccurs="0" name="billingStatementIdentifier" type="xsd:string" /> - <xsd:element maxOccurs="unbounded" minOccurs="0" name="taxExemptions" type="vin:TaxExemption" /> - <xsd:element minOccurs="0" name="salesTaxAddress" type="tns:Address" /> - <xsd:element minOccurs="0" name="verificationCode" type="xsd:string" /> - <xsd:element minOccurs="0" name="billingPeriodStart" type="xsd:dateTime" /> - <xsd:element minOccurs="0" name="billingPeriodEnd" type="xsd:dateTime" /> - </xsd:sequence> - </xsd:complexType> - <xsd:complexType name="WebSession"> - <xsd:sequence> - <xsd:element minOccurs="0" name="VID" type="xsd:string" /> - <xsd:element minOccurs="0" name="method" type="xsd:string" /> - <xsd:element minOccurs="0" name="version" type="xsd:string" /> - <xsd:element minOccurs="0" name="returnURL" type="xsd:string" /> - <xsd:element minOccurs="0" name="errorURL" type="xsd:string" /> - <xsd:element minOccurs="0" name="ipAddress" type="xsd:string" /> - <xsd:element minOccurs="0" name="expireTime" type="xsd:dateTime" /> - <xsd:element maxOccurs="unbounded" minOccurs="0" name="nameValues" type="vin:NameValuePair" /> - <xsd:element maxOccurs="unbounded" minOccurs="0" name="postValues" type="vin:NameValuePair" /> - <xsd:element maxOccurs="unbounded" minOccurs="0" name="methodParamValues" type="vin:NameValuePair" /> - <xsd:element maxOccurs="unbounded" minOccurs="0" name="privateFormValues" type="vin:NameValuePair" /> - <xsd:element minOccurs="0" name="apiReturn" type="tns:Return" /> - <xsd:element minOccurs="0" name="apiReturnValues" type="tns:WebSessionMethodReturn" /> - </xsd:sequence> - </xsd:complexType> - <xsd:complexType name="WebSessionMethodReturn"> - <xsd:sequence> - <xsd:element minOccurs="0" name="method" type="xsd:string" /> - <xsd:element minOccurs="0" name="paymentMethodValidate" type="tns:PaymentMethodValidate" /> - <xsd:element minOccurs="0" name="paymentMethodUpdate" type="tns:PaymentMethodUpdate" /> - <xsd:element minOccurs="0" name="transactionAuth" type="tns:TransactionAuth" /> - <xsd:element minOccurs="0" name="autoBillUpdate" type="tns:AutoBillUpdate" /> - <xsd:element minOccurs="0" name="transactionAuthCapture" type="tns:TransactionAuthCapture" /> - <xsd:element minOccurs="0" name="accountUpdatePaymentMethod" type="tns:AccountUpdatePaymentMethod" /> - </xsd:sequence> - </xsd:complexType> - <xsd:complexType name="PaymentMethodValidate"> - <xsd:sequence> - <xsd:element minOccurs="0" name="authStatus" type="vin:TransactionStatus" /> - <xsd:element minOccurs="0" name="validated" type="xsd:boolean" /> - <xsd:element minOccurs="0" name="score" type="xsd:int" /> - <xsd:element maxOccurs="unbounded" minOccurs="0" name="scoreCodes" type="vin:ScoreCode" /> - </xsd:sequence> - </xsd:complexType> - <xsd:complexType name="PaymentMethodUpdate"> - <xsd:sequence> - <xsd:element minOccurs="0" name="paymentMethod" type="vin:PaymentMethod" /> - <xsd:element minOccurs="0" name="created" type="xsd:boolean" /> - <xsd:element minOccurs="0" name="validated" type="xsd:boolean" /> - <xsd:element minOccurs="0" name="score" type="xsd:int" /> - <xsd:element maxOccurs="unbounded" minOccurs="0" name="scoreCodes" type="vin:ScoreCode" /> - <xsd:element minOccurs="0" name="authStatus" type="vin:TransactionStatus" /> - </xsd:sequence> - </xsd:complexType> - <xsd:complexType name="TransactionAuth"> - <xsd:sequence> - <xsd:element minOccurs="0" name="transaction" type="vin:Transaction" /> - <xsd:element minOccurs="0" name="score" type="xsd:int" /> - <xsd:element maxOccurs="unbounded" minOccurs="0" name="scoreCodes" type="vin:ScoreCode" /> - </xsd:sequence> - </xsd:complexType> - <xsd:complexType name="AutoBillUpdate"> - <xsd:sequence> - <xsd:element minOccurs="0" name="autobill" type="vin:AutoBill" /> - <xsd:element minOccurs="0" name="created" type="xsd:boolean" /> - <xsd:element minOccurs="0" name="authStatus" type="vin:TransactionStatus" /> - <xsd:element minOccurs="0" name="firstBillDate" type="xsd:date" /> - <xsd:element minOccurs="0" name="firstBillAmount" type="xsd:decimal" /> - <xsd:element minOccurs="0" name="firstBillingCurrency" type="xsd:string" /> - <xsd:element minOccurs="0" name="score" type="xsd:int" /> - <xsd:element maxOccurs="unbounded" minOccurs="0" name="scoreCodes" type="vin:ScoreCode" /> - </xsd:sequence> - </xsd:complexType> - <xsd:complexType name="TransactionAuthCapture"> - <xsd:sequence> - <xsd:element minOccurs="0" name="transaction" type="vin:Transaction" /> - </xsd:sequence> - </xsd:complexType> - <xsd:complexType name="AccountUpdatePaymentMethod"> - <xsd:sequence> - <xsd:element minOccurs="0" name="account" type="vin:Account" /> - <xsd:element minOccurs="0" name="validated" type="xsd:boolean" /> - </xsd:sequence> - </xsd:complexType> - <xsd:simpleType name="EmailPreference"> - <xsd:restriction base="xsd:string"> - <xsd:enumeration value="plaintext" /> - <xsd:enumeration value="html" /> - <xsd:enumeration value="multipart" /> - </xsd:restriction> - </xsd:simpleType> - <xsd:simpleType name="PaymentUpdateBehavior"> - <xsd:restriction base="xsd:string"> - <xsd:enumeration value="Update" /> - <xsd:enumeration value="Validate" /> - <xsd:enumeration value="CatchUp" /> - </xsd:restriction> - </xsd:simpleType> - <xsd:simpleType name="AccountPayerReplacementBehavior"> - <xsd:restriction base="xsd:string"> - <xsd:enumeration value="ReplaceOnAllAutoBills" /> - <xsd:enumeration value="ReplaceOnlyFutureAutoBills" /> - </xsd:restriction> - </xsd:simpleType> - <xsd:simpleType name="ActivityCancelInitType"> - <xsd:restriction base="xsd:string"> - <xsd:enumeration value="Merchant" /> - <xsd:enumeration value="Customer" /> - <xsd:enumeration value="Chargeback" /> - </xsd:restriction> - </xsd:simpleType> - <xsd:simpleType name="ActivityType"> - <xsd:restriction base="xsd:string"> - <xsd:enumeration value="Login" /> - <xsd:enumeration value="Logout" /> - <xsd:enumeration value="URIView" /> - <xsd:enumeration value="Phone" /> - <xsd:enumeration value="Email" /> - <xsd:enumeration value="Fulfillment" /> - <xsd:enumeration value="Usage" /> - <xsd:enumeration value="NamedValue" /> - <xsd:enumeration value="Cancellation" /> - <xsd:enumeration value="Note" /> - </xsd:restriction> - </xsd:simpleType> - <xsd:simpleType name="ActivityCallType"> - <xsd:restriction base="xsd:string"> - <xsd:enumeration value="FromMerchantToCustomer" /> - <xsd:enumeration value="FromMerchantToOther" /> - <xsd:enumeration value="FromCustomerToMerchant" /> - <xsd:enumeration value="FromCustomerToOther" /> - <xsd:enumeration value="FromOtherToCustomer" /> - <xsd:enumeration value="FromOtherToMerchant" /> - </xsd:restriction> - </xsd:simpleType> - <xsd:simpleType name="AddressType"> - <xsd:restriction base="xsd:string"> - <xsd:enumeration value="Shipping" /> - <xsd:enumeration value="Billing" /> - </xsd:restriction> - </xsd:simpleType> - <xsd:simpleType name="AutoBillStatus"> - <xsd:restriction base="xsd:string"> - <xsd:enumeration value="Active" /> - <xsd:enumeration value="Suspended" /> - <xsd:enumeration value="Cancelled" /> - <xsd:enumeration value="Upgraded" /> - <xsd:enumeration value="PendingCustomerAction" /> - </xsd:restriction> - </xsd:simpleType> - <xsd:simpleType name="DuplicateBehavior"> - <xsd:restriction base="xsd:string"> - <xsd:enumeration value="Duplicate" /> - <xsd:enumeration value="SucceedIgnore" /> - <xsd:enumeration value="Fail" /> - </xsd:restriction> - </xsd:simpleType> - <xsd:simpleType name="BillingPeriodType"> - <xsd:restriction base="xsd:string"> - <xsd:enumeration value="Day" /> - <xsd:enumeration value="Week" /> - <xsd:enumeration value="Month" /> - <xsd:enumeration value="Year" /> - </xsd:restriction> - </xsd:simpleType> - <xsd:simpleType name="UpgradeNegativeNet"> - <xsd:restriction base="xsd:string"> - <xsd:enumeration value="CREDIT" /> - <xsd:enumeration value="REFUND" /> - </xsd:restriction> - </xsd:simpleType> - <xsd:simpleType name="UpgradeProrate"> - <xsd:restriction base="xsd:string"> - <xsd:enumeration value="LINEAR" /> - </xsd:restriction> - </xsd:simpleType> - <xsd:simpleType name="BillingPlanStatus"> - <xsd:restriction base="xsd:string"> - <xsd:enumeration value="Active" /> - <xsd:enumeration value="Suspended" /> - </xsd:restriction> - </xsd:simpleType> - <xsd:simpleType name="ChargebackStatus"> - <xsd:restriction base="xsd:string"> - <xsd:enumeration value="New" /> - <xsd:enumeration value="Retrieval" /> - <xsd:enumeration value="Responded" /> - <xsd:enumeration value="Legitimate" /> - <xsd:enumeration value="Challenged" /> - <xsd:enumeration value="Represented" /> - <xsd:enumeration value="Won" /> - <xsd:enumeration value="Lost" /> - <xsd:enumeration value="CollectionsNew" /> - <xsd:enumeration value="CollectionsWon" /> - <xsd:enumeration value="CollectionsLost" /> - <xsd:enumeration value="Expired" /> - <xsd:enumeration value="Pass" /> - <xsd:enumeration value="Incomplete" /> - <xsd:enumeration value="NewSecondChargeback" /> - <xsd:enumeration value="Duplicate" /> - </xsd:restriction> - </xsd:simpleType> - <xsd:simpleType name="TimeIntervalType"> - <xsd:restriction base="xsd:string"> - <xsd:enumeration value="Day" /> - <xsd:enumeration value="Week" /> - <xsd:enumeration value="Month" /> - <xsd:enumeration value="Year" /> - </xsd:restriction> - </xsd:simpleType> - <xsd:simpleType name="CreditEventType"> - <xsd:restriction base="xsd:string"> - <xsd:enumeration value="GiftCardRedemption" /> - <xsd:enumeration value="GiftCardReversal" /> - <xsd:enumeration value="GiftCardStatusInquiry" /> - <xsd:enumeration value="Grant" /> - <xsd:enumeration value="Revocation" /> - <xsd:enumeration value="Consumption" /> - <xsd:enumeration value="Refund" /> - </xsd:restriction> - </xsd:simpleType> - <xsd:simpleType name="GiftCardActivityType"> - <xsd:restriction base="xsd:string"> - <xsd:enumeration value="Redeem" /> - <xsd:enumeration value="Reverse" /> - </xsd:restriction> - </xsd:simpleType> - <xsd:simpleType name="GiftCardStatusType"> - <xsd:restriction base="xsd:string"> - <xsd:enumeration value="Unknown" /> - <xsd:enumeration value="Active" /> - <xsd:enumeration value="RedemptionPending" /> - <xsd:enumeration value="Redeemed" /> - <xsd:enumeration value="Deactive" /> - <xsd:enumeration value="Suspended" /> - </xsd:restriction> - </xsd:simpleType> - <xsd:simpleType name="EmailTemplateType"> - <xsd:restriction base="xsd:string"> - <xsd:enumeration value="PreBilling" /> - <xsd:enumeration value="PreBillingNoPaymentMethod" /> - <xsd:enumeration value="Success" /> - <xsd:enumeration value="FailureSoftFail" /> - <xsd:enumeration value="FailureHardFail" /> - <xsd:enumeration value="FailureNoPaymentMethod" /> - <xsd:enumeration value="BillingDelay" /> - <xsd:enumeration value="BillingDelayNoPaymentMethod" /> - <xsd:enumeration value="Cancellation" /> - </xsd:restriction> - </xsd:simpleType> - <xsd:simpleType name="MetricStatusType"> - <xsd:restriction base="xsd:string"> - <xsd:enumeration value="Completed" /> - <xsd:enumeration value="TimedOut" /> - </xsd:restriction> - </xsd:simpleType> - <xsd:simpleType name="PaymentMethodType"> - <xsd:restriction base="xsd:string"> - <xsd:enumeration value="CreditCard" /> - <xsd:enumeration value="PayPal" /> - <xsd:enumeration value="ECP" /> - <xsd:enumeration value="DirectDebit" /> - <xsd:enumeration value="Token" /> - <xsd:enumeration value="Boleto" /> - </xsd:restriction> - </xsd:simpleType> - <xsd:simpleType name="HashType"> - <xsd:restriction base="xsd:string"> - <xsd:enumeration value="sha1" /> - <xsd:enumeration value="md5" /> - </xsd:restriction> - </xsd:simpleType> - <xsd:simpleType name="AccountType"> - <xsd:restriction base="xsd:string"> - <xsd:enumeration value="ConsumerChecking" /> - <xsd:enumeration value="ConsumerSavings" /> - <xsd:enumeration value="CorporateChecking" /> - </xsd:restriction> - </xsd:simpleType> - <xsd:simpleType name="ECPTransactionType"> - <xsd:restriction base="xsd:string"> - <xsd:enumeration value="All" /> - <xsd:enumeration value="Inbound" /> - <xsd:enumeration value="Outbound" /> - <xsd:enumeration value="InboundOutbound" /> - <xsd:enumeration value="Transfer" /> - <xsd:enumeration value="NA" /> - </xsd:restriction> - </xsd:simpleType> - <xsd:simpleType name="ProductStatus"> - <xsd:restriction base="xsd:string"> - <xsd:enumeration value="Active" /> - <xsd:enumeration value="Suspended" /> - </xsd:restriction> - </xsd:simpleType> - <xsd:simpleType name="TaxClassification"> - <xsd:restriction base="xsd:string"> - <xsd:enumeration value="PhysicalGoods" /> - <xsd:enumeration value="DownloadableExecutableSoftware" /> - <xsd:enumeration value="DownloadableElectronicData" /> - <xsd:enumeration value="Service" /> - <xsd:enumeration value="TaxExempt" /> - <xsd:enumeration value="OtherTaxable" /> - </xsd:restriction> - </xsd:simpleType> - <xsd:simpleType name="RefundTokenAction"> - <xsd:restriction base="xsd:string"> - <xsd:enumeration value="None" /> - <xsd:enumeration value="CancelZeroBalance" /> - <xsd:enumeration value="CancelNegativeBalance" /> - </xsd:restriction> - </xsd:simpleType> - <xsd:simpleType name="ReturnCode"> - <xsd:restriction base="xsd:int"> - <xsd:enumeration value="200" /> - <xsd:enumeration value="201" /> - <xsd:enumeration value="202" /> - <xsd:enumeration value="203" /> - <xsd:enumeration value="204" /> - <xsd:enumeration value="205" /> - <xsd:enumeration value="206" /> - <xsd:enumeration value="300" /> - <xsd:enumeration value="301" /> - <xsd:enumeration value="302" /> - <xsd:enumeration value="303" /> - <xsd:enumeration value="304" /> - <xsd:enumeration value="305" /> - <xsd:enumeration value="306" /> - <xsd:enumeration value="307" /> - <xsd:enumeration value="400" /> - <xsd:enumeration value="401" /> - <xsd:enumeration value="402" /> - <xsd:enumeration value="403" /> - <xsd:enumeration value="404" /> - <xsd:enumeration value="405" /> - <xsd:enumeration value="406" /> - <xsd:enumeration value="407" /> - <xsd:enumeration value="408" /> - <xsd:enumeration value="409" /> - <xsd:enumeration value="410" /> - <xsd:enumeration value="411" /> - <xsd:enumeration value="412" /> - <xsd:enumeration value="413" /> - <xsd:enumeration value="414" /> - <xsd:enumeration value="415" /> - <xsd:enumeration value="416" /> - <xsd:enumeration value="417" /> - <xsd:enumeration value="500" /> - <xsd:enumeration value="501" /> - <xsd:enumeration value="502" /> - <xsd:enumeration value="503" /> - <xsd:enumeration value="504" /> - <xsd:enumeration value="505" /> - </xsd:restriction> - </xsd:simpleType> - <xsd:simpleType name="TaxRegion"> - <xsd:restriction base="xsd:string"> - <xsd:enumeration value="AT" /> - <xsd:enumeration value="BE" /> - <xsd:enumeration value="BG" /> - <xsd:enumeration value="CY" /> - <xsd:enumeration value="CZ" /> - <xsd:enumeration value="DE" /> - <xsd:enumeration value="DK" /> - <xsd:enumeration value="EE" /> - <xsd:enumeration value="EL" /> - <xsd:enumeration value="ES" /> - <xsd:enumeration value="FI" /> - <xsd:enumeration value="FR" /> - <xsd:enumeration value="GB" /> - <xsd:enumeration value="HU" /> - <xsd:enumeration value="IE" /> - <xsd:enumeration value="IT" /> - <xsd:enumeration value="LT" /> - <xsd:enumeration value="LU" /> - <xsd:enumeration value="LV" /> - <xsd:enumeration value="MT" /> - <xsd:enumeration value="NL" /> - <xsd:enumeration value="PL" /> - <xsd:enumeration value="PT" /> - <xsd:enumeration value="RO" /> - <xsd:enumeration value="SE" /> - <xsd:enumeration value="SI" /> - <xsd:enumeration value="SK" /> - <xsd:enumeration value="CH" /> - <xsd:enumeration value="CA" /> - <xsd:enumeration value="US" /> - </xsd:restriction> - </xsd:simpleType> - <xsd:simpleType name="TransactionStatusType"> - <xsd:restriction base="xsd:string"> - <xsd:enumeration value="New" /> - <xsd:enumeration value="AuthorizationPending" /> - <xsd:enumeration value="AuthorizedPending" /> - <xsd:enumeration value="Authorized" /> - <xsd:enumeration value="AuthorizedForValidation" /> - <xsd:enumeration value="Cancelled" /> - <xsd:enumeration value="Captured" /> - <xsd:enumeration value="Settled" /> - <xsd:enumeration value="Refunded" /> - <xsd:enumeration value="Pending" /> - </xsd:restriction> - </xsd:simpleType> - <xsd:simpleType name="AVSMatchType"> - <xsd:restriction base="xsd:string"> - <xsd:enumeration value="FullMatch" /> - <xsd:enumeration value="PartialMatch" /> - <xsd:enumeration value="NoMatch" /> - <xsd:enumeration value="IssuerError" /> - <xsd:enumeration value="NoOpinion" /> - </xsd:restriction> - </xsd:simpleType> -</xsd:schema> \ No newline at end of file diff --git a/test/support/mercury_helper.rb b/test/support/mercury_helper.rb index e52ad7bb800..a8afafa25e5 100644 --- a/test/support/mercury_helper.rb +++ b/test/support/mercury_helper.rb @@ -2,43 +2,43 @@ module MercuryHelper module BatchClosing def close_batch xml = Builder::XmlMarkup.new - xml.tag! "TStream" do - xml.tag! "Admin" do + xml.tag! 'TStream' do + xml.tag! 'Admin' do xml.tag! 'MerchantID', @options[:login] - xml.tag! 'TranCode', "BatchSummary" + xml.tag! 'TranCode', 'BatchSummary' end end xml = xml.target! - response = commit("BatchSummary", xml) + response = commit('BatchSummary', xml) xml = Builder::XmlMarkup.new - xml.tag! "TStream" do - xml.tag! "Admin" do + xml.tag! 'TStream' do + xml.tag! 'Admin' do xml.tag! 'MerchantID', @options[:login] - xml.tag! 'OperatorID', response.params["operator_id"] - xml.tag! 'TranCode', "BatchClose" - xml.tag! 'BatchNo', response.params["batch_no"] - xml.tag! 'BatchItemCount', response.params["batch_item_count"] - xml.tag! 'NetBatchTotal', response.params["net_batch_total"] - xml.tag! 'CreditPurchaseCount', response.params["credit_purchase_count"] - xml.tag! 'CreditPurchaseAmount', response.params["credit_purchase_amount"] - xml.tag! 'CreditReturnCount', response.params["credit_return_count"] - xml.tag! 'CreditReturnAmount', response.params["credit_return_amount"] - xml.tag! 'DebitPurchaseCount', response.params["debit_purchase_count"] - xml.tag! 'DebitPurchaseAmount', response.params["debit_purchase_amount"] - xml.tag! 'DebitReturnCount', response.params["debit_return_count"] - xml.tag! 'DebitReturnAmount', response.params["debit_return_amount"] + xml.tag! 'OperatorID', response.params['operator_id'] + xml.tag! 'TranCode', 'BatchClose' + xml.tag! 'BatchNo', response.params['batch_no'] + xml.tag! 'BatchItemCount', response.params['batch_item_count'] + xml.tag! 'NetBatchTotal', response.params['net_batch_total'] + xml.tag! 'CreditPurchaseCount', response.params['credit_purchase_count'] + xml.tag! 'CreditPurchaseAmount', response.params['credit_purchase_amount'] + xml.tag! 'CreditReturnCount', response.params['credit_return_count'] + xml.tag! 'CreditReturnAmount', response.params['credit_return_amount'] + xml.tag! 'DebitPurchaseCount', response.params['debit_purchase_count'] + xml.tag! 'DebitPurchaseAmount', response.params['debit_purchase_amount'] + xml.tag! 'DebitReturnCount', response.params['debit_return_count'] + xml.tag! 'DebitReturnAmount', response.params['debit_return_amount'] end end xml = xml.target! - commit("BatchClose", xml) + commit('BatchClose', xml) end def hashify_xml!(xml, response) super doc = REXML::Document.new(xml) - doc.elements.each("//BatchSummary/*") do |node| + doc.elements.each('//BatchSummary/*') do |node| response[node.name.underscore.to_sym] = node.text end end diff --git a/test/test_helper.rb b/test/test_helper.rb index 6b37b224c42..361857a3afb 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -1,22 +1,10 @@ -#!/usr/bin/env ruby $:.unshift File.expand_path('../../lib', __FILE__) -begin - require 'rubygems' - require 'bundler' - Bundler.setup -rescue LoadError => e - puts "Error loading bundler (#{e.message}): \"gem install bundler\" for bundler support." -end +require 'bundler/setup' require 'test/unit' -require 'money' -require 'mocha/version' -if(Mocha::VERSION.split(".")[1].to_i < 12) - require 'mocha' -else - require 'mocha/setup' -end +require 'mocha/test_unit' + require 'yaml' require 'json' require 'active_merchant' @@ -24,27 +12,7 @@ require 'active_support/core_ext/integer/time' require 'active_support/core_ext/numeric/time' - -begin - require 'active_support/core_ext/time/acts_like' -rescue LoadError -end - -begin - gem 'actionpack' -rescue LoadError - raise StandardError, "The view tests need ActionPack installed as gem to run" -end - -require 'action_controller' -require "action_view/template" -begin - require 'active_support/core_ext/module/deprecation' - require 'action_dispatch/testing/test_process' -rescue LoadError - require 'action_controller/test_process' -end -require 'active_merchant/billing/integrations/action_view_helper' +require 'active_support/core_ext/time/acts_like' ActiveMerchant::Billing::Base.mode = :test @@ -61,10 +29,9 @@ class SimpleTestGateway < ActiveMerchant::Billing::Gateway class SubclassGateway < SimpleTestGateway end - module ActiveMerchant module Assertions - AssertionClass = RUBY_VERSION > '1.9' ? MiniTest::Assertion : Test::Unit::AssertionFailedError + AssertionClass = defined?(Minitest) ? MiniTest::Assertion : Test::Unit::AssertionFailedError def assert_field(field, value) clean_backtrace do @@ -72,7 +39,7 @@ def assert_field(field, value) end end - # Allows the testing of you to check for negative assertions: + # Allows testing of negative assertions: # # # Instead of # assert !something_that_is_false @@ -91,57 +58,80 @@ def assert_false(boolean, message = nil) end end - # A handy little assertion to check for a successful response: + # An assertion of a successful response: # # # Instead of - # assert_success response + # assert response.success? # # # DRY that up with # assert_success response # # A message will automatically show the inspection of the response # object if things go afoul. - def assert_success(response) + def assert_success(response, message=nil) clean_backtrace do - assert response.success?, "Response failed: #{response.inspect}" + assert response.success?, build_message(nil, "#{message + "\n" if message}Response expected to succeed: <?>", response) end end # The negative of +assert_success+ - def assert_failure(response) + def assert_failure(response, message=nil) clean_backtrace do - assert_false response.success?, "Response expected to fail: #{response.inspect}" + assert !response.success?, build_message(nil, "#{message + "\n" if message}Response expected to fail: <?>", response) end end - def assert_valid(validateable) + def assert_valid(model, message=nil) + errors = model.validate + clean_backtrace do - assert validateable.valid?, "Expected to be valid" + assert_equal({}, errors, (message || 'Expected to be valid')) end + + errors end - def assert_not_valid(validateable) + def assert_not_valid(model) + errors = model.validate + clean_backtrace do - assert_false validateable.valid?, "Expected to not be valid" + assert_not_equal({}, errors, 'Expected to not be valid') end + + errors + end + + def assert_deprecation_warning(message=nil) + ActiveMerchant.expects(:deprecated).with(message || anything) + yield end - def assert_deprecation_warning(message, target) - target.expects(:deprecated).with(message) + def refute(value, message = nil) + assert(!value, message) + end + + def silence_deprecation_warnings + ActiveMerchant.stubs(:deprecated) yield end - def assert_no_deprecation_warning(target) - target.expects(:deprecated).never + def assert_no_deprecation_warning + ActiveMerchant.expects(:deprecated).never yield end + def assert_scrubbed(unexpected_value, transcript) + regexp = (Regexp === unexpected_value ? unexpected_value : Regexp.new(Regexp.quote(unexpected_value.to_s))) + refute_match regexp, transcript, 'Expected the value to be scrubbed out of the transcript' + end + private + def clean_backtrace(&block) yield rescue AssertionClass => e path = File.expand_path(__FILE__) - raise AssertionClass, e.message, e.backtrace.reject { |line| File.expand_path(line) =~ /#{path}/ } + raise AssertionClass, e.message, (e.backtrace.reject { |line| File.expand_path(line) =~ /#{path}/ }) end end @@ -151,20 +141,53 @@ module Fixtures DEFAULT_CREDENTIALS = File.join(File.dirname(__FILE__), 'fixtures.yml') unless defined?(DEFAULT_CREDENTIALS) private + + def default_expiration_date + @default_expiration_date ||= Date.new((Time.now.year + 1), 9, 30) + end + + def formatted_expiration_date(credit_card) + credit_card.expiry_date.expiration.strftime('%Y-%m') + end + def credit_card(number = '4242424242424242', options = {}) defaults = { :number => number, - :month => 9, - :year => Time.now.year + 1, + :month => default_expiration_date.month, + :year => default_expiration_date.year, :first_name => 'Longbob', :last_name => 'Longsen', - :verification_value => '123', + :verification_value => options[:verification_value] || '123', :brand => 'visa' }.update(options) Billing::CreditCard.new(defaults) end + def credit_card_with_track_data(number = '4242424242424242', options = {}) + exp_date = default_expiration_date.strftime('%y%m') + + defaults = { + :track_data => "%B#{number}^LONGSEN/L. ^#{exp_date}1200000000000000**123******?", + }.update(options) + + Billing::CreditCard.new(defaults) + end + + def network_tokenization_credit_card(number = '4242424242424242', options = {}) + defaults = { + :number => number, + :month => default_expiration_date.month, + :year => default_expiration_date.year, + :first_name => 'Longbob', + :last_name => 'Longsen', + :verification_value => '123', + :brand => 'visa' + }.update(options) + + Billing::NetworkTokenizationCreditCard.new(defaults) + end + def check(options = {}) defaults = { :name => 'Jim Smith', @@ -179,21 +202,73 @@ def check(options = {}) Billing::Check.new(defaults) end + def apple_pay_payment_token(options = {}) + # apple_pay_json_raw should contain the JSON serialization of the object described here + # https://developer.apple.com/library/IOs//documentation/PassKit/Reference/PaymentTokenJSON/PaymentTokenJSON.htm + apple_pay_json_raw = '{"version":"EC_v1","data":"","signature":""}' + defaults = { + payment_data: ActiveSupport::JSON.decode(apple_pay_json_raw), + payment_instrument_name: 'Visa 2424', + payment_network: 'Visa', + transaction_identifier: 'uniqueidentifier123' + }.update(options) + + ActiveMerchant::Billing::ApplePayPaymentToken.new(defaults[:payment_data], + payment_instrument_name: defaults[:payment_instrument_name], + payment_network: defaults[:payment_network], + transaction_identifier: defaults[:transaction_identifier] + ) + end + def address(options = {}) { - :name => 'Jim Smith', - :address1 => '1234 My Street', - :address2 => 'Apt 1', - :company => 'Widgets Inc', - :city => 'Ottawa', - :state => 'ON', - :zip => 'K1C2N6', - :country => 'CA', - :phone => '(555)555-5555', - :fax => '(555)555-6666' + name: 'Jim Smith', + address1: '456 My Street', + address2: 'Apt 1', + company: 'Widgets Inc', + city: 'Ottawa', + state: 'ON', + zip: 'K1C2N6', + country: 'CA', + phone: '(555)555-5555', + fax: '(555)555-6666' }.update(options) end + def statement_address(options = {}) + { + address1: '456 My Street', + address2: 'Apt 1', + city: 'Ottawa', + state: 'ON', + zip: 'K1C2N6' + }.update(options) + end + + def stored_credential(*args, **options) + id = options.delete(:id) || options.delete(:network_transaction_id) + + stored_credential = { + network_transaction_id: id, + initial_transaction: false + } + + stored_credential[:initial_transaction] = true if args.include?(:initial) + + stored_credential[:reason_type] = 'recurring' if args.include?(:recurring) + stored_credential[:reason_type] = 'unscheduled' if args.include?(:unscheduled) + stored_credential[:reason_type] = 'installment' if args.include?(:installment) + + stored_credential[:initiator] = 'cardholder' if args.include?(:cardholder) + stored_credential[:initiator] = 'merchant' if args.include?(:merchant) + + stored_credential + end + + def generate_unique_id + SecureRandom.hex(16) + end + def all_fixtures @@fixtures ||= load_fixtures end @@ -206,8 +281,8 @@ def fixtures(key) def load_fixtures [DEFAULT_CREDENTIALS, LOCAL_CREDENTIALS].inject({}) do |credentials, file_name| - if File.exists?(file_name) - yaml_data = YAML.load(File.read(file_name)) + if File.exist?(file_name) + yaml_data = YAML.safe_load(File.read(file_name), [], [], true) credentials.merge!(symbolize_keys(yaml_data)) end credentials @@ -218,7 +293,7 @@ def symbolize_keys(hash) return unless hash.is_a?(Hash) hash.symbolize_keys! - hash.each{|k,v| symbolize_keys(v)} + hash.each { |k, v| symbolize_keys(v) } end end end @@ -226,12 +301,28 @@ def symbolize_keys(hash) Test::Unit::TestCase.class_eval do include ActiveMerchant::Billing include ActiveMerchant::Assertions - include ActiveMerchant::Utils include ActiveMerchant::Fixtures + + def capture_transcript(gateway) + transcript = '' + gateway.class.wiredump_device = transcript + + yield + + transcript + end + + def dump_transcript_and_fail(gateway, amount, credit_card, params) + transcript = capture_transcript(gateway) do + gateway.purchase(amount, credit_card, params) + end + + File.open('transcript.log', 'w') { |f| f.write(transcript) } + assert false, 'A purchase transcript has been written to transcript.log for you to test scrubbing with.' + end end module ActionViewHelperTestHelper - def self.included(base) base.send(:include, ActiveMerchant::Billing::Integrations::ActionViewHelper) base.send(:include, ActionView::Helpers::FormHelper) @@ -255,7 +346,29 @@ def url_for(options, *parameters_for_method_reference) end protected + def protect_against_forgery? false end end + +class MockResponse + attr_reader :code, :body, :message + attr_accessor :headers + + def self.succeeded(body, message='') + MockResponse.new(200, body, message) + end + + def self.failed(body, http_status_code=422, message='') + MockResponse.new(http_status_code, body, message) + end + + def initialize(code, body, message='', headers={}) + @code, @body, @message, @headers = code, body, message, headers + end + + def [](header) + @headers[header] + end +end diff --git a/test/unit/apple_pay_payment_token_test.rb b/test/unit/apple_pay_payment_token_test.rb new file mode 100644 index 00000000000..b5a40f9dd45 --- /dev/null +++ b/test/unit/apple_pay_payment_token_test.rb @@ -0,0 +1,11 @@ +require 'test_helper' + +class ApplePayPaymentTokenTest < Test::Unit::TestCase + def setup + @token = ActiveMerchant::Billing::ApplePayPaymentToken.new(payment_data: {}) + end + + def test_type + assert_equal 'apple_pay', @token.type + end +end diff --git a/test/unit/avs_result_test.rb b/test/unit/avs_result_test.rb index 97dd7a20ded..f3b960647b7 100644 --- a/test/unit/avs_result_test.rb +++ b/test/unit/avs_result_test.rb @@ -2,9 +2,9 @@ class AVSResultTest < Test::Unit::TestCase def test_nil - result = AVSResult.new(nil) + AVSResult.new(nil) end - + def test_no_match result = AVSResult.new(:code => 'N') assert_equal 'N', result.code @@ -12,7 +12,7 @@ def test_no_match assert_equal 'N', result.postal_match assert_equal AVSResult.messages['N'], result.message end - + def test_only_street_match result = AVSResult.new(:code => 'A') assert_equal 'A', result.code @@ -20,7 +20,7 @@ def test_only_street_match assert_equal 'N', result.postal_match assert_equal AVSResult.messages['A'], result.message end - + def test_only_postal_match result = AVSResult.new(:code => 'W') assert_equal 'W', result.code @@ -28,32 +28,32 @@ def test_only_postal_match assert_equal 'Y', result.postal_match assert_equal AVSResult.messages['W'], result.message end - + def test_nil_data result = AVSResult.new(:code => nil) assert_nil result.code assert_nil result.message end - + def test_empty_data result = AVSResult.new(:code => '') assert_nil result.code assert_nil result.message end - + def test_to_hash avs_data = AVSResult.new(:code => 'X').to_hash assert_equal 'X', avs_data['code'] assert_equal AVSResult.messages['X'], avs_data['message'] end - + def test_street_match avs_data = AVSResult.new(:street_match => 'Y') assert_equal 'Y', avs_data.street_match end - + def test_postal_match avs_data = AVSResult.new(:postal_match => 'Y') assert_equal 'Y', avs_data.postal_match end -end \ No newline at end of file +end diff --git a/test/unit/base_test.rb b/test/unit/base_test.rb index 2ec4f4721ac..6e5ac461ab7 100644 --- a/test/unit/base_test.rb +++ b/test/unit/base_test.rb @@ -4,11 +4,11 @@ class BaseTest < Test::Unit::TestCase def setup ActiveMerchant::Billing::Base.mode = :test end - + def teardown ActiveMerchant::Billing::Base.mode = :test end - + def test_should_return_a_new_gateway_specified_by_symbol_name assert_equal BogusGateway, Base.gateway(:bogus) assert_equal MonerisGateway, Base.gateway(:moneris) @@ -18,52 +18,51 @@ def test_should_return_a_new_gateway_specified_by_symbol_name assert_equal LinkpointGateway, Base.gateway(:linkpoint) end - def test_should_raise_when_invalid_gateway_is_passed - assert_raise NameError do - Base.gateway(:nil) + def test_should_raise_when_nil_gateway_is_passed + e = assert_raise ArgumentError do + Base.gateway(nil) end + assert_equal 'A gateway provider must be specified', e.message + end - assert_raise NameError do + def test_should_raise_when_empty_gateway_is_passed + e = assert_raise ArgumentError do Base.gateway('') end + assert_equal 'A gateway provider must be specified', e.message + end - assert_raise NameError do + def test_should_raise_when_invalid_gateway_symbol_is_passed + e = assert_raise ArgumentError do Base.gateway(:hotdog) end + assert_equal 'The specified gateway is not valid (hotdog)', e.message end - def test_should_return_an_integration_by_name - chronopay = Base.integration(:chronopay) - - assert_equal Integrations::Chronopay, chronopay - assert_instance_of Integrations::Chronopay::Notification, chronopay.notification('name=cody') + def test_should_raise_when_invalid_gateway_string_is_passed + e = assert_raise ArgumentError do + Base.gateway('hotdog') + end + assert_equal 'The specified gateway is not valid (hotdog)', e.message end def test_should_set_modes Base.mode = :test assert_equal :test, Base.mode - assert_equal :test, Base.gateway_mode - assert_equal :test, Base.integration_mode Base.mode = :production assert_equal :production, Base.mode - assert_equal :production, Base.gateway_mode - assert_equal :production, Base.integration_mode - Base.mode = :development - Base.gateway_mode = :test - Base.integration_mode = :staging + assert_deprecation_warning(Base::GATEWAY_MODE_DEPRECATION_MESSAGE) { Base.gateway_mode = :development } + assert_deprecation_warning(Base::GATEWAY_MODE_DEPRECATION_MESSAGE) { assert_equal :development, Base.gateway_mode } assert_equal :development, Base.mode - assert_equal :test, Base.gateway_mode - assert_equal :staging, Base.integration_mode end - + def test_should_identify_if_test_mode - Base.gateway_mode = :test + Base.mode = :test assert Base.test? - - Base.gateway_mode = :production + + Base.mode = :production assert_false Base.test? end - end diff --git a/test/unit/check_test.rb b/test/unit/check_test.rb index 877a77b3889..e5908553ab9 100644 --- a/test/unit/check_test.rb +++ b/test/unit/check_test.rb @@ -4,85 +4,78 @@ class CheckTest < Test::Unit::TestCase VALID_ABA = '111000025' INVALID_ABA = '999999999' MALFORMED_ABA = 'I like fish' - + ACCOUNT_NUMBER = '123456789012' - + def test_validation - c = Check.new - assert !c.valid? - assert !c.errors.empty? + assert_not_valid Check.new end - + def test_first_name_last_name check = Check.new(:name => 'Fred Bloggs') assert_equal 'Fred', check.first_name assert_equal 'Bloggs', check.last_name assert_equal 'Fred Bloggs', check.name end - + def test_nil_name check = Check.new(:name => nil) assert_nil check.first_name assert_nil check.last_name - assert_equal "", check.name + assert_equal '', check.name end - + def test_valid - c = Check.new(:name => 'Fred Bloggs', - :routing_number => VALID_ABA, - :account_number => ACCOUNT_NUMBER, - :account_holder_type => 'personal', - :account_type => 'checking') - assert c.valid? + assert_valid Check.new( + :name => 'Fred Bloggs', + :routing_number => VALID_ABA, + :account_number => ACCOUNT_NUMBER, + :account_holder_type => 'personal', + :account_type => 'checking' + ) + end + + def test_credit_card? + assert !check.credit_card? end - + def test_invalid_routing_number - c = Check.new(:routing_number => INVALID_ABA) - assert !c.valid? - assert_equal c.errors.on(:routing_number), "is invalid" + errors = assert_not_valid Check.new(:routing_number => INVALID_ABA) + assert_equal ['is invalid'], errors[:routing_number] end - + def test_malformed_routing_number - c = Check.new(:routing_number => MALFORMED_ABA) - assert !c.valid? - assert_equal c.errors.on(:routing_number), "is invalid" + errors = assert_not_valid Check.new(:routing_number => MALFORMED_ABA) + assert_equal ['is invalid'], errors[:routing_number] end - + def test_account_holder_type c = Check.new c.account_holder_type = 'business' - c.valid? - assert !c.errors.on(:account_holder_type) - + assert !c.validate[:account_holder_type] + c.account_holder_type = 'personal' - c.valid? - assert !c.errors.on(:account_holder_type) - + assert !c.validate[:account_holder_type] + c.account_holder_type = 'pleasure' - c.valid? - assert_equal c.errors.on(:account_holder_type), 'must be personal or business' - + assert_equal ['must be personal or business'], c.validate[:account_holder_type] + c.account_holder_type = nil - c.valid? - assert !c.errors.on(:account_holder_type) + assert !c.validate[:account_holder_type] end - + def test_account_type c = Check.new c.account_type = 'checking' - c.valid? - assert !c.errors.on(:account_type) - + assert !c.validate[:account_type] + c.account_type = 'savings' - c.valid? - assert !c.errors.on(:account_type) - + assert !c.validate[:account_type] + c.account_type = 'moo' - c.valid? - assert_equal c.errors.on(:account_type), "must be checking or savings" - + assert_equal ['must be checking or savings'], c.validate[:account_type] + c.account_type = nil - c.valid? - assert !c.errors.on(:account_type) + assert !c.validate[:account_type] end end diff --git a/test/unit/connection_test.rb b/test/unit/connection_test.rb new file mode 100644 index 00000000000..ac8c29a4f1d --- /dev/null +++ b/test/unit/connection_test.rb @@ -0,0 +1,248 @@ +require 'test_helper' + +class ConnectionTest < Test::Unit::TestCase + + def setup + @ok = stub(:code => 200, :message => 'OK', :body => 'success') + + @endpoint = 'https://example.com/tx.php' + @connection = ActiveMerchant::Connection.new(@endpoint) + @connection.logger = stub(:info => nil, :debug => nil, :error => nil) + end + + def test_connection_endpoint_parses_string_to_uri + assert_equal URI.parse(@endpoint), @connection.endpoint + end + + def test_connection_endpoint_accepts_uri + endpoint = URI.parse(@endpoint) + connection = ActiveMerchant::Connection.new(endpoint) + assert_equal endpoint, connection.endpoint + end + + def test_connection_endpoint_raises_uri_error + assert_raises URI::InvalidURIError do + ActiveMerchant::Connection.new('not a URI') + end + end + + def test_connection_passes_env_proxy_by_default + spy = Net::HTTP.new('example.com', 443) + Net::HTTP.expects(:new).with('example.com', 443, :ENV, nil).returns(spy) + spy.expects(:start).returns(true) + spy.expects(:get).with('/tx.php', {'connection' => 'close'}).returns(@ok) + @connection.request(:get, nil, {}) + end + + def test_connection_does_pass_requested_proxy + @connection.proxy_address = 'proxy.example.com' + @connection.proxy_port = 8080 + spy = Net::HTTP.new('example.com', 443) + Net::HTTP.expects(:new).with('example.com', 443, 'proxy.example.com', 8080).returns(spy) + spy.expects(:start).returns(true) + spy.expects(:get).with('/tx.php', {'connection' => 'close'}).returns(@ok) + @connection.request(:get, nil, {}) + end + + def test_connection_does_not_mutate_headers_argument + headers = { 'Content-Type' => 'text/xml' }.freeze + Net::HTTP.any_instance.expects(:get).with('/tx.php', headers.merge({'connection' => 'close'})).returns(@ok) + Net::HTTP.any_instance.expects(:start).returns(true) + @connection.request(:get, nil, headers) + assert_equal({ 'Content-Type' => 'text/xml' }, headers) + end + + def test_successful_get_request + @connection.logger.expects(:info).twice + Net::HTTP.any_instance.expects(:get).with('/tx.php', {'connection' => 'close'}).returns(@ok) + Net::HTTP.any_instance.expects(:start).returns(true) + response = @connection.request(:get, nil, {}) + assert_equal 'success', response.body + end + + def test_successful_post_request + Net::HTTP.any_instance.expects(:post).with('/tx.php', 'data', ActiveMerchant::Connection::RUBY_184_POST_HEADERS.merge({'connection' => 'close'})).returns(@ok) + Net::HTTP.any_instance.expects(:start).returns(true) + response = @connection.request(:post, 'data', {}) + assert_equal 'success', response.body + end + + def test_successful_put_request + Net::HTTP.any_instance.expects(:put).with('/tx.php', 'data', {'connection' => 'close'}).returns(@ok) + Net::HTTP.any_instance.expects(:start).returns(true) + response = @connection.request(:put, 'data', {}) + assert_equal 'success', response.body + end + + def test_successful_delete_request + Net::HTTP.any_instance.expects(:delete).with('/tx.php', {'connection' => 'close'}).returns(@ok) + Net::HTTP.any_instance.expects(:start).returns(true) + response = @connection.request(:delete, nil, {}) + assert_equal 'success', response.body + end + + def test_successful_delete_with_body_request + Net::HTTP.any_instance.expects(:request).at_most(3).returns(@ok) + Net::HTTP.any_instance.expects(:start).returns(true) + response = @connection.request(:delete, 'data', {}) + assert_equal 'success', response.body + end + + def test_get_raises_argument_error_if_passed_data + assert_raises(ArgumentError) do + Net::HTTP.any_instance.expects(:start).returns(true) + @connection.request(:get, 'data', {}) + end + end + + def test_request_raises_when_request_method_not_supported + assert_raises(ArgumentError) do + Net::HTTP.any_instance.expects(:start).returns(true) + @connection.request(:head, nil, {}) + end + end + + def test_override_max_retries + refute_equal 1, @connection.max_retries + @connection.max_retries = 1 + assert_equal 1, @connection.max_retries + end + + def test_override_ssl_version + refute_equal :SSLv3, @connection.ssl_version + @connection.ssl_version = :SSLv3 + assert_equal :SSLv3, @connection.ssl_version + end + + def test_override_min_version + omit_if Net::HTTP.instance_methods.exclude?(:min_version=) + + refute_equal :TLS1_2, @connection.min_version + @connection.min_version = :TLS1_2 + assert_equal :TLS1_2, @connection.min_version + end + + def test_override_max_version + omit_if Net::HTTP.instance_methods.exclude?(:min_version=) + + refute_equal :TLS1_2, @connection.max_version + @connection.max_version = :TLS1_2 + assert_equal :TLS1_2, @connection.max_version + end + + def test_default_read_timeout + assert_equal ActiveMerchant::Connection::READ_TIMEOUT, @connection.read_timeout + end + + def test_override_read_timeout + @connection.read_timeout = 20 + assert_equal 20, @connection.read_timeout + end + + def test_default_open_timeout + @connection.open_timeout = 20 + assert_equal 20, @connection.open_timeout + end + + def test_default_verify_peer + assert_equal ActiveMerchant::Connection::VERIFY_PEER, @connection.verify_peer + end + + def test_override_verify_peer + @connection.verify_peer = false + assert_equal false, @connection.verify_peer + end + + def test_default_ca_file_exists + assert File.exist?(ActiveMerchant::Connection::CA_FILE) + end + + def test_default_ca_file + assert_equal ActiveMerchant::Connection::CA_FILE, @connection.ca_file + assert_equal ActiveMerchant::Connection::CA_FILE, @connection.send(:http).ca_file + end + + def test_override_ca_file + @connection.ca_file = '/bogus' + assert_equal '/bogus', @connection.ca_file + assert_equal '/bogus', @connection.send(:http).ca_file + end + + def test_default_ca_path + assert_equal ActiveMerchant::Connection::CA_PATH, @connection.ca_path + assert_equal ActiveMerchant::Connection::CA_PATH, @connection.send(:http).ca_path + end + + def test_override_ca_path + @connection.ca_path = '/bogus' + assert_equal '/bogus', @connection.ca_path + assert_equal '/bogus', @connection.send(:http).ca_path + end + + def test_unrecoverable_exception + @connection.logger.expects(:info).once + Net::HTTP.any_instance.expects(:start).raises(EOFError) + + assert_raises(ActiveMerchant::ConnectionError) do + @connection.request(:post, '') + end + end + + def test_failure_then_success_with_recoverable_exception + @connection.logger.expects(:info).never + Net::HTTP.any_instance.expects(:start).times(2).raises(Errno::ECONNREFUSED).then.returns(true) + Net::HTTP.any_instance.expects(:post).returns(@ok) + + @connection.request(:post, '') + end + + def test_failure_limit_reached + @connection.logger.expects(:info).once + Net::HTTP.any_instance.expects(:start).times(ActiveMerchant::Connection::MAX_RETRIES).raises(Errno::ECONNREFUSED) + + assert_raises(ActiveMerchant::ConnectionError) do + @connection.request(:post, '') + end + end + + def test_failure_then_success_with_retry_safe_enabled + Net::HTTP.any_instance.expects(:start).times(2).raises(EOFError).then.returns(@ok) + Net::HTTP.any_instance.expects(:post).returns(@ok) + + @connection.retry_safe = true + + @connection.request(:post, '') + end + + def test_mixture_of_failures_with_retry_safe_enabled + Net::HTTP.any_instance. + expects(:start). + times(3). + raises(Errno::ECONNRESET). + raises(Errno::ECONNREFUSED). + raises(EOFError) + + @connection.retry_safe = true + + assert_raises(ActiveMerchant::ConnectionError) do + @connection.request(:post, '') + end + end + + def test_failure_with_ssl_certificate + @connection.logger.expects(:error).once + Net::HTTP.any_instance.expects(:start).raises(OpenSSL::X509::CertificateError) + + assert_raises(ActiveMerchant::ClientCertificateError) do + @connection.request(:post, '') + end + end + + def test_wiredump_service_raises_on_frozen_object + transcript = ''.freeze + assert_raise ArgumentError, "can't wiredump to frozen object" do + @connection.wiredump_device = transcript + end + end + +end diff --git a/test/unit/country_code_test.rb b/test/unit/country_code_test.rb new file mode 100644 index 00000000000..12ee3ae4ab4 --- /dev/null +++ b/test/unit/country_code_test.rb @@ -0,0 +1,31 @@ +require 'test_helper' + +class CountryCodeTest < Test::Unit::TestCase + def test_alpha2_country_code + code = ActiveMerchant::CountryCode.new('CA') + assert_equal 'CA', code.value + assert_equal 'CA', code.to_s + assert_equal :alpha2, code.format + end + + def test_lower_alpha2_country_code + code = ActiveMerchant::CountryCode.new('ca') + assert_equal 'CA', code.value + assert_equal 'CA', code.to_s + assert_equal :alpha2, code.format + end + + def test_alpha3_country_code + code = ActiveMerchant::CountryCode.new('CAN') + assert_equal :alpha3, code.format + end + + def test_numeric_code + code = ActiveMerchant::CountryCode.new('004') + assert_equal :numeric, code.format + end + + def test_invalid_code_format + assert_raises(ActiveMerchant::CountryCodeFormatError) { ActiveMerchant::CountryCode.new('Canada') } + end +end diff --git a/test/unit/country_test.rb b/test/unit/country_test.rb new file mode 100644 index 00000000000..6251e6fa095 --- /dev/null +++ b/test/unit/country_test.rb @@ -0,0 +1,93 @@ +require 'test_helper' + +class CountryTest < Test::Unit::TestCase + def test_country_from_hash + country = ActiveMerchant::Country.new(:name => 'Canada', :alpha2 => 'CA', :alpha3 => 'CAN', :numeric => '124') + assert_equal 'CA', country.code(:alpha2).value + assert_equal 'CAN', country.code(:alpha3).value + assert_equal '124', country.code(:numeric).value + assert_equal 'Canada', country.to_s + end + + def test_country_for_alpha2_code + country = ActiveMerchant::Country.find('CA') + assert_equal 'CA', country.code(:alpha2).value + assert_equal 'CAN', country.code(:alpha3).value + assert_equal '124', country.code(:numeric).value + assert_equal 'Canada', country.to_s + end + + def test_country_for_alpha3_code + country = ActiveMerchant::Country.find('CAN') + assert_equal 'Canada', country.to_s + end + + def test_country_for_numeric_code + country = ActiveMerchant::Country.find('124') + assert_equal 'Canada', country.to_s + end + + def test_find_country_by_name + country = ActiveMerchant::Country.find('Canada') + assert_equal 'Canada', country.to_s + end + + def test_find_country_by_lowercase_name + country = ActiveMerchant::Country.find('bosnia and herzegovina') + assert_equal 'Bosnia and Herzegovina', country.to_s + end + + def test_find_unknown_country_name + assert_raises(ActiveMerchant::InvalidCountryCodeError) do + ActiveMerchant::Country.find('Asskickistan') + end + end + + def test_find_australia + country = ActiveMerchant::Country.find('AU') + assert_equal 'AU', country.code(:alpha2).value + + country = ActiveMerchant::Country.find('Australia') + assert_equal 'AU', country.code(:alpha2).value + end + + def test_find_united_kingdom + country = ActiveMerchant::Country.find('GB') + assert_equal 'GB', country.code(:alpha2).value + + country = ActiveMerchant::Country.find('United Kingdom') + assert_equal 'GB', country.code(:alpha2).value + end + + def test_find_romania + country = ActiveMerchant::Country.find('ROM') + assert_equal 'RO', country.code(:alpha2).value + + country = ActiveMerchant::Country.find('ROU') + assert_equal 'RO', country.code(:alpha2).value + + country = ActiveMerchant::Country.find('Romania') + assert_equal 'ROU', country.code(:alpha3).value + + country = ActiveMerchant::Country.find('Romania') + assert_not_equal 'ROM', country.code(:alpha3).value + end + + def test_raise_on_nil_name + assert_raises(ActiveMerchant::InvalidCountryCodeError) do + ActiveMerchant::Country.find(nil) + end + end + + def test_country_names_are_alphabetized + country_names = ActiveMerchant::Country::COUNTRIES.map { |each| each[:name] } + assert_equal(country_names.sort, country_names) + end + + def test_comparisons + assert_equal ActiveMerchant::Country.find('GB'), ActiveMerchant::Country.find('GB') + assert_not_equal ActiveMerchant::Country.find('GB'), ActiveMerchant::Country.find('CA') + assert_not_equal Object.new, ActiveMerchant::Country.find('GB') + assert_not_equal ActiveMerchant::Country.find('GB'), Object.new + end +end diff --git a/test/unit/credit_card_methods_test.rb b/test/unit/credit_card_methods_test.rb index 4d3b9a7150e..400dc07c3a7 100644 --- a/test/unit/credit_card_methods_test.rb +++ b/test/unit/credit_card_methods_test.rb @@ -2,39 +2,40 @@ class CreditCardMethodsTest < Test::Unit::TestCase include ActiveMerchant::Billing::CreditCardMethods - + class CreditCard - include ActiveMerchant::Billing::CreditCardMethods + include ActiveMerchant::Billing::CreditCardMethods end - + def maestro_card_numbers %w[ - 5000000000000000 5099999999999999 5600000000000000 - 5899999999999999 6000000000000000 6999999999999999 - 6761999999999999 6763000000000000 5038999999999999 + 5612590000000000 5817500000000000 5818000000000000 + 6390000000000000 6390700000000000 6390990000000000 + 6761999999999999 6763000000000000 6799999999999999 ] end - + def non_maestro_card_numbers %w[ - 4999999999999999 5100000000000000 5599999999999999 - 5900000000000000 5999999999999999 7000000000000000 + 4999999999999999 5100000000000000 5599999999999999 + 5612709999999999 5817520000000000 5818019999999999 + 5912600000000000 6000009999999999 7000000000000000 ] end - + def test_should_be_able_to_identify_valid_expiry_months assert_false valid_month?(-1) assert_false valid_month?(13) assert_false valid_month?(nil) assert_false valid_month?('') - + 1.upto(12) { |m| assert valid_month?(m) } end def test_should_be_able_to_identify_valid_expiry_years assert_false valid_expiry_year?(-1) assert_false valid_expiry_year?(Time.now.year + 21) - + 0.upto(20) { |n| assert valid_expiry_year?(Time.now.year + n) } end @@ -42,29 +43,40 @@ def test_should_be_able_to_identify_valid_start_years assert valid_start_year?(1988) assert valid_start_year?(2007) assert valid_start_year?(3000) - + assert_false valid_start_year?(1987) end - + def test_valid_start_year_can_handle_strings - assert valid_start_year?("2009") + assert valid_start_year?('2009') end - + def test_valid_month_can_handle_strings - assert valid_month?("1") + assert valid_month?('1') end - + def test_valid_expiry_year_can_handle_strings year = Time.now.year + 1 assert valid_expiry_year?(year.to_s) end - + + def test_should_validate_card_verification_value + assert valid_card_verification_value?(123, 'visa') + assert valid_card_verification_value?('123', 'visa') + assert valid_card_verification_value?(1234, 'american_express') + assert valid_card_verification_value?('1234', 'american_express') + assert_false valid_card_verification_value?(12, 'visa') + assert_false valid_card_verification_value?(1234, 'visa') + assert_false valid_card_verification_value?(123, 'american_express') + assert_false valid_card_verification_value?(12345, 'american_express') + end + def test_should_be_able_to_identify_valid_issue_numbers assert valid_issue_number?(1) assert valid_issue_number?(10) assert valid_issue_number?('12') assert valid_issue_number?(0) - + assert_false valid_issue_number?(-1) assert_false valid_issue_number?(123) assert_false valid_issue_number?('CAT') @@ -73,123 +85,193 @@ def test_should_be_able_to_identify_valid_issue_numbers def test_should_ensure_brand_from_credit_card_class_is_not_frozen assert_false CreditCard.brand?('4242424242424242').frozen? end - + def test_should_be_dankort_card_brand assert_equal 'dankort', CreditCard.brand?('5019717010103742') end - + def test_should_detect_visa_dankort_as_visa assert_equal 'visa', CreditCard.brand?('4571100000000000') end - + def test_should_detect_electron_dk_as_visa assert_equal 'visa', CreditCard.brand?('4175001000000000') end - + def test_should_detect_diners_club assert_equal 'diners_club', CreditCard.brand?('36148010000000') end - + def test_should_detect_diners_club_dk assert_equal 'diners_club', CreditCard.brand?('30401000000000') end - + def test_should_detect_maestro_dk_as_maestro assert_equal 'maestro', CreditCard.brand?('6769271000000000') end - + def test_should_detect_maestro_cards - assert_equal 'maestro', CreditCard.brand?('5020100000000000') - + assert_equal 'maestro', CreditCard.brand?('675675000000000') + maestro_card_numbers.each { |number| assert_equal 'maestro', CreditCard.brand?(number) } non_maestro_card_numbers.each { |number| assert_not_equal 'maestro', CreditCard.brand?(number) } end - + def test_should_detect_mastercard - assert_equal 'master', CreditCard.brand?('6771890000000000') + assert_equal 'master', CreditCard.brand?('2720890000000000') assert_equal 'master', CreditCard.brand?('5413031000000000') end - + def test_should_detect_forbrugsforeningen assert_equal 'forbrugsforeningen', CreditCard.brand?('6007221000000000') end - - def test_should_detect_laser_card - # 16 digits - assert_equal 'laser', CreditCard.brand?('6304985028090561') - - # 18 digits - assert_equal 'laser', CreditCard.brand?('630498502809056151') - - # 19 digits - assert_equal 'laser', CreditCard.brand?('6304985028090561515') - - # 17 digits - assert_not_equal 'laser', CreditCard.brand?('63049850280905615') - - # 15 digits - assert_not_equal 'laser', CreditCard.brand?('630498502809056') - - # Alternate format - assert_equal 'laser', CreditCard.brand?('6706950000000000000') - - # Alternate format (16 digits) - assert_equal 'laser', CreditCard.brand?('6706123456789012') - - # New format (16 digits) - assert_equal 'laser', CreditCard.brand?('6709123456789012') - - # Ulster bank (Ireland) with 12 digits - assert_equal 'laser', CreditCard.brand?('677117111234') - end - + + def test_should_detect_sodexo_card + assert_equal 'sodexo', CreditCard.brand?('6060694495764400') + end + + def test_should_detect_vr_card + assert_equal 'vr', CreditCard.brand?('6370364495764400') + end + + def test_should_detect_elo_card + assert_equal 'elo', CreditCard.brand?('5090510000000000') + assert_equal 'elo', CreditCard.brand?('5067530000000000') + assert_equal 'elo', CreditCard.brand?('6509550000000000') + end + + def test_should_detect_alelo_card + assert_equal 'alelo', CreditCard.brand?('5067490000000010') + assert_equal 'alelo', CreditCard.brand?('5067700000000028') + assert_equal 'alelo', CreditCard.brand?('5067600000000036') + assert_equal 'alelo', CreditCard.brand?('5067600000000044') + end + + def test_should_detect_naranja_card + assert_equal 'naranja', CreditCard.brand?('5895627823453005') + assert_equal 'naranja', CreditCard.brand?('5895620000000002') + assert_equal 'naranja', CreditCard.brand?('5895626746595650') + end + + # Alelo BINs beginning with the digit 4 overlap with Visa's range of valid card numbers. + # We intentionally misidentify these cards as Visa, which works because transactions with + # such cards will run on Visa rails. + def test_should_detect_alelo_number_beginning_with_4_as_visa + assert_equal 'visa', CreditCard.brand?('4025880000000010') + assert_equal 'visa', CreditCard.brand?('4025880000000028') + assert_equal 'visa', CreditCard.brand?('4025880000000036') + assert_equal 'visa', CreditCard.brand?('4025880000000044') + end + + def test_should_detect_cabal_card + assert_equal 'cabal', CreditCard.brand?('6044009000000000') + assert_equal 'cabal', CreditCard.brand?('5896575500000000') + assert_equal 'cabal', CreditCard.brand?('6035224400000000') + end + def test_should_detect_when_an_argument_brand_does_not_match_calculated_brand assert CreditCard.matching_brand?('4175001000000000', 'visa') assert_false CreditCard.matching_brand?('4175001000000000', 'master') end - + def test_detecting_full_range_of_maestro_card_numbers - maestro = '50000000000' - + maestro = '63900000000' + assert_equal 11, maestro.length assert_not_equal 'maestro', CreditCard.brand?(maestro) - + while maestro.length < 19 maestro << '0' - assert_equal 'maestro', CreditCard.brand?(maestro) + assert_equal 'maestro', CreditCard.brand?(maestro), "Failed for bin #{maestro}" end - + assert_equal 19, maestro.length - + maestro << '0' assert_not_equal 'maestro', CreditCard.brand?(maestro) end - + def test_matching_discover_card assert_equal 'discover', CreditCard.brand?('6011000000000000') assert_equal 'discover', CreditCard.brand?('6500000000000000') assert_equal 'discover', CreditCard.brand?('6221260000000000') assert_equal 'discover', CreditCard.brand?('6450000000000000') - + assert_not_equal 'discover', CreditCard.brand?('6010000000000000') assert_not_equal 'discover', CreditCard.brand?('6600000000000000') end - + + def test_matching_invalid_card + assert_nil CreditCard.brand?('XXXXXXXXXXXX0000') + assert_false CreditCard.valid_number?('XXXXXXXXXXXX0000') + assert_false CreditCard.valid_number?(nil) + end + + def test_matching_valid_naranja + number = '5895627823453005' + assert_equal 'naranja', CreditCard.brand?(number) + assert CreditCard.valid_number?(number) + end + def test_16_digit_maestro_uk number = '6759000000000000' assert_equal 16, number.length - assert_equal 'switch', CreditCard.brand?(number) + assert_equal 'maestro', CreditCard.brand?(number) end - + def test_18_digit_maestro_uk number = '675900000000000000' assert_equal 18, number.length - assert_equal 'switch', CreditCard.brand?(number) + assert_equal 'maestro', CreditCard.brand?(number) end - + def test_19_digit_maestro_uk number = '6759000000000000000' assert_equal 19, number.length - assert_equal 'switch', CreditCard.brand?(number) + assert_equal 'maestro', CreditCard.brand?(number) + end + + def test_carnet_cards + numbers = [ + '5062280000000000', + '6046220312312312', + '6393889871239871', + '5022751231231231' + ] + numbers.each do |num| + assert_equal 16, num.length + assert_equal 'carnet', CreditCard.brand?(num) + end + end + + def test_electron_cards + # return the card number so assert failures are easy to isolate + electron_test = Proc.new do |card_number| + electron = CreditCard.electron?(card_number) + card_number if electron + end + + CreditCard::ELECTRON_RANGES.each do |range| + range.map { |leader| "#{leader}0000000000" }.each do |card_number| + assert_equal card_number, electron_test.call(card_number) + end + end + + # nil check + assert_false electron_test.call(nil) + + # Visa range + assert_false electron_test.call('4245180000000000') + assert_false electron_test.call('4918810000000000') + + # 19 PAN length + assert electron_test.call('4249620000000000000') + + # 20 PAN length + assert_false electron_test.call('42496200000000000') + end + + def test_credit_card? + assert credit_card.credit_card? end end diff --git a/test/unit/credit_card_test.rb b/test/unit/credit_card_test.rb index 4ff2ccdba27..d31748f17bb 100644 --- a/test/unit/credit_card_test.rb +++ b/test/unit/credit_card_test.rb @@ -3,8 +3,8 @@ class CreditCardTest < Test::Unit::TestCase def setup CreditCard.require_verification_value = false - @visa = credit_card("4779139500118580", :brand => "visa") - @solo = credit_card("676700000000000000", :brand => "solo", :issue_number => '01') + @visa = credit_card('4779139500118580', brand: 'visa') + @maestro = credit_card('676700000000000000', brand: 'maestro', verification_value: '') end def teardown @@ -14,11 +14,11 @@ def teardown def test_constructor_should_properly_assign_values c = credit_card - assert_equal "4242424242424242", c.number + assert_equal '4242424242424242', c.number assert_equal 9, c.month assert_equal Time.now.year + 1, c.year - assert_equal "Longbob Longsen", c.name - assert_equal "visa", c.brand + assert_equal 'Longbob Longsen', c.name + assert_equal 'visa', c.brand assert_valid c end @@ -26,33 +26,26 @@ def test_new_credit_card_should_not_be_valid c = CreditCard.new assert_not_valid c - assert_false c.errors.empty? end def test_should_be_a_valid_visa_card assert_valid @visa - assert @visa.errors.empty? end - def test_should_be_a_valid_solo_card - assert_valid @solo - assert @solo.errors.empty? + def test_should_be_a_valid_maestro_card + assert_valid @maestro end - def test_cards_with_empty_names_should_not_be_valid - @visa.first_name = '' - @visa.last_name = '' - - assert_not_valid @visa - assert_false @visa.errors.empty? + def test_should_be_a_valid_maestro_card_when_require_cvv_is_true + CreditCard.require_verification_value = true + assert_valid @maestro end - def test_should_be_able_to_access_errors_indifferently + def test_cards_with_empty_names_should_not_be_valid @visa.first_name = '' + @visa.last_name = '' assert_not_valid @visa - assert @visa.errors.on(:first_name) - assert @visa.errors.on("first_name") end def test_should_be_able_to_liberate_a_bogus_card @@ -67,45 +60,59 @@ def test_should_be_able_to_identify_invalid_card_numbers @visa.number = nil assert_not_valid @visa - @visa.number = "11112222333344ff" - assert_not_valid @visa - assert_false @visa.errors.on(:type) - assert_false @visa.errors.on(:brand) - assert @visa.errors.on(:number) + @visa.number = '11112222333344ff' + errors = assert_not_valid @visa + assert !errors[:type] + assert !errors[:brand] + assert errors[:number] - @visa.number = "111122223333444" - assert_not_valid @visa - assert_false @visa.errors.on(:type) - assert_false @visa.errors.on(:brand) - assert @visa.errors.on(:number) + @visa.number = '111122223333444' + errors = assert_not_valid @visa + assert !errors[:type] + assert !errors[:brand] + assert errors[:number] - @visa.number = "11112222333344444" - assert_not_valid @visa - assert_false @visa.errors.on(:type) - assert_false @visa.errors.on(:brand) - assert @visa.errors.on(:number) + @visa.number = '11112222333344444' + errors = assert_not_valid @visa + assert !errors[:type] + assert !errors[:brand] + assert errors[:number] end def test_should_have_errors_with_invalid_card_brand_for_otherwise_correct_number @visa.brand = 'master' - assert_not_valid @visa - assert_not_equal @visa.errors.on(:number), @visa.errors.on(:brand) + errors = assert_not_valid @visa + assert !errors[:number] + assert errors[:brand] + assert_equal ['does not match the card number'], errors[:brand] end def test_should_be_invalid_when_brand_cannot_be_detected - @visa.number = nil @visa.brand = nil - assert_not_valid @visa - assert !@visa.errors.on(:brand) - assert !@visa.errors.on(:type) - assert @visa.errors.on(:number) - assert_equal 'is required', @visa.errors.on(:number) + @visa.number = nil + errors = assert_not_valid @visa + assert !errors[:brand] + assert !errors[:type] + assert errors[:number] + assert_equal ['is required'], errors[:number] + + @visa.number = '11112222333344ff' + errors = assert_not_valid @visa + assert !errors[:type] + assert !errors[:brand] + assert errors[:number] + + @visa.number = '11112222333344444' + errors = assert_not_valid @visa + assert !errors[:brand] + assert !errors[:type] + assert errors[:number] end def test_should_be_a_valid_card_number - @visa.number = "4242424242424242" + @visa.number = '4242424242424242' assert_valid @visa end @@ -120,36 +127,36 @@ def test_should_require_a_valid_card_month def test_should_not_be_valid_with_empty_month @visa.month = '' - assert_not_valid @visa - assert_equal 'is required', @visa.errors.on('month') + errors = assert_not_valid @visa + assert_equal ['is required'], errors[:month] end def test_should_not_be_valid_for_edge_month_cases @visa.month = 13 @visa.year = Time.now.year - assert_not_valid @visa - assert @visa.errors.on('month') + errors = assert_not_valid @visa + assert errors[:month] @visa.month = 0 @visa.year = Time.now.year - assert_not_valid @visa - assert @visa.errors.on('month') + errors = assert_not_valid @visa + assert errors[:month] end def test_should_be_invalid_with_empty_year @visa.year = '' - assert_not_valid @visa - assert_equal 'is required', @visa.errors.on('year') + errors = assert_not_valid @visa + assert_equal ['is required'], errors[:year] end def test_should_not_be_valid_for_edge_year_cases @visa.year = Time.now.year - 1 - assert_not_valid @visa - assert @visa.errors.on('year') + errors = assert_not_valid @visa + assert errors[:year] @visa.year = Time.now.year + 21 - assert_not_valid @visa - assert @visa.errors.on('year') + errors = assert_not_valid @visa + assert errors[:year] end def test_should_be_a_valid_future_year @@ -159,15 +166,10 @@ def test_should_be_a_valid_future_year def test_expired_card_should_have_one_error_on_year @visa.year = Time.now.year - 1 - assert_not_valid @visa - assert_equal 1, @visa.errors['year'].length - assert /expired/ =~ @visa.errors['year'].first - end - - def test_should_be_valid_with_start_month_and_year_as_string - @solo.start_month = '2' - @solo.start_year = '2007' - assert_valid @solo + errors = assert_not_valid(@visa) + assert_not_nil errors[:year] + assert_equal 1, errors[:year].size + assert_match(/expired/, errors[:year].first) end def test_should_identify_wrong_card_brand @@ -190,6 +192,8 @@ def test_should_display_number def test_should_correctly_identify_card_brand assert_equal 'visa', CreditCard.brand?('4242424242424242') assert_equal 'american_express', CreditCard.brand?('341111111111111') + assert_equal 'master', CreditCard.brand?('5105105105105100') + (222100..272099).each { |bin| assert_equal 'master', CreditCard.brand?(bin.to_s + '1111111111'), "Failed with BIN #{bin}" } assert_nil CreditCard.brand?('') end @@ -203,66 +207,68 @@ def test_should_not_be_valid_when_requiring_a_verification_value card = credit_card('4242424242424242', :verification_value => nil) assert_not_valid card + card.verification_value = '1234' + errors = assert_not_valid card + assert_equal errors[:verification_value], ['should be 3 digits'] + card.verification_value = '123' assert_valid card - end - - def test_should_require_valid_start_date_for_solo_or_switch - @solo.start_month = nil - @solo.start_year = nil - @solo.issue_number = nil - assert_not_valid @solo - assert @solo.errors.on('start_month') - assert @solo.errors.on('start_year') - assert @solo.errors.on('issue_number') + card = credit_card('341111111111111', :verification_value => '123', :brand => 'american_express') + errors = assert_not_valid card + assert_equal errors[:verification_value], ['should be 4 digits'] - @solo.start_month = 2 - @solo.start_year = 2007 - assert_valid @solo + card.verification_value = '1234' + assert_valid card end - def test_should_require_a_valid_issue_number_for_solo_or_switch - @solo.start_month = nil - @solo.start_year = 2005 - @solo.issue_number = nil + def test_should_be_valid_when_not_requiring_a_verification_value + CreditCard.require_verification_value = true + card = credit_card('4242424242424242', :verification_value => nil, :require_verification_value => false) + assert_valid card - assert_not_valid @solo - assert @solo.errors.on('start_month') - assert_equal "cannot be empty", @solo.errors.on('issue_number') + card.verification_value = '1234' + errors = assert_not_valid card + assert_equal errors[:verification_value], ['should be 3 digits'] - @solo.issue_number = 3 - assert_valid @solo + card.verification_value = '123' + assert_valid card end - def test_should_require_a_validate_non_empty_issue_number_for_solo_or_switch - @solo.issue_number = "invalid" - - assert_not_valid @solo - assert_equal "is invalid", @solo.errors.on('issue_number') - - @solo.issue_number = 3 - assert_valid @solo + def test_bogus_cards_are_not_valid_without_verification_value + CreditCard.require_verification_value = true + card = credit_card('1', brand: 'bogus', verification_value: nil) + assert_not_valid card end def test_should_return_last_four_digits_of_card_number - ccn = CreditCard.new(:number => "4779139500118580") - assert_equal "8580", ccn.last_digits + ccn = CreditCard.new(:number => '4779139500118580') + assert_equal '8580', ccn.last_digits end def test_bogus_last_digits - ccn = CreditCard.new(:number => "1") - assert_equal "1", ccn.last_digits + ccn = CreditCard.new(:number => '1') + assert_equal '1', ccn.last_digits + end + + def test_should_return_empty_string_for_first_digits_of_nil_card_number + ccn = CreditCard.new + assert_equal '', ccn.first_digits end - + + def test_should_return_empty_string_for_last_digits_of_nil_card_number + ccn = CreditCard.new + assert_equal '', ccn.last_digits + end + def test_should_return_first_four_digits_of_card_number - ccn = CreditCard.new(:number => "4779139500118580") - assert_equal "477913", ccn.first_digits + ccn = CreditCard.new(:number => '4779139500118580') + assert_equal '477913', ccn.first_digits end - + def test_should_return_first_bogus_digit_of_card_number - ccn = CreditCard.new(:number => "1") - assert_equal "1", ccn.first_digits + ccn = CreditCard.new(:number => '1') + assert_equal '1', ccn.first_digits end def test_should_be_true_when_credit_card_has_a_first_name @@ -292,25 +298,42 @@ def test_should_test_for_a_full_name def test_should_handle_full_name_when_first_or_last_is_missing c = CreditCard.new(:first_name => 'James') assert c.name? - assert_equal "James", c.name + assert_equal 'James', c.name c = CreditCard.new(:last_name => 'Herdman') assert c.name? - assert_equal "Herdman", c.name + assert_equal 'Herdman', c.name end def test_should_assign_a_full_name - c = CreditCard.new :name => "James Herdman" - assert_equal "James", c.first_name - assert_equal "Herdman", c.last_name + c = CreditCard.new :name => 'James Herdman' + assert_equal 'James', c.first_name + assert_equal 'Herdman', c.last_name - c = CreditCard.new :name => "Rocket J. Squirrel" - assert_equal "Rocket J.", c.first_name - assert_equal "Squirrel", c.last_name + c = CreditCard.new :name => 'Rocket J. Squirrel' + assert_equal 'Rocket J.', c.first_name + assert_equal 'Squirrel', c.last_name - c = CreditCard.new :name => "Twiggy" - assert_equal "", c.first_name - assert_equal "Twiggy", c.last_name + c = CreditCard.new :name => 'Twiggy' + assert_equal '', c.first_name + assert_equal 'Twiggy', c.last_name + assert_equal 'Twiggy', c.name + end + + def test_should_remove_trailing_whitespace_on_name + c = CreditCard.new(:last_name => 'Herdman') + assert_equal 'Herdman', c.name + + c = CreditCard.new(:last_name => 'Herdman', first_name: '') + assert_equal 'Herdman', c.name + end + + def test_should_remove_leading_whitespace_on_name + c = CreditCard.new(:first_name => 'James') + assert_equal 'James', c.name + + c = CreditCard.new(:last_name => '', first_name: 'James') + assert_equal 'James', c.name end # The following is a regression for a bug that raised an exception when @@ -328,31 +351,28 @@ def test_validate_new_card def test_create_and_validate_credit_card_from_brand credit_card = CreditCard.new(:brand => CreditCard.brand?('4242424242424242')) assert_nothing_raised do - credit_card.valid? + credit_card.validate end end def test_autodetection_of_credit_card_brand credit_card = CreditCard.new(:number => '4242424242424242') - credit_card.valid? assert_equal 'visa', credit_card.brand end def test_card_brand_should_not_be_autodetected_when_provided credit_card = CreditCard.new(:number => '4242424242424242', :brand => 'master') - credit_card.valid? assert_equal 'master', credit_card.brand end def test_detecting_bogus_card credit_card = CreditCard.new(:number => '1') - credit_card.valid? assert_equal 'bogus', credit_card.brand end def test_validating_bogus_card credit_card = credit_card('1', :brand => nil) - assert credit_card.valid? + assert_valid credit_card end def test_mask_number @@ -361,22 +381,61 @@ def test_mask_number def test_strip_non_digit_characters card = credit_card('4242-4242 %%%%%%4242......4242') - assert card.valid? - assert_equal "4242424242424242", card.number + assert_valid card + assert_equal '4242424242424242', card.number end - def test_before_validate_handles_blank_number + def test_validate_handles_blank_number card = credit_card(nil) - assert !card.valid? - assert_equal "", card.number + assert_not_valid card + assert_nil card.number end - + + def test_rails_methods_are_deprecated + card = credit_card + warning = %(Implicit inclusion of Rails-specific functionality is deprecated. Explicitly require "active_merchant/billing/rails" if you need it.) + assert_deprecation_warning(warning) do + card.valid? + end + + assert_deprecation_warning(warning) do + card.errors + end + end + def test_brand_is_aliased_as_type - assert_deprecation_warning("CreditCard#type is deprecated and will be removed from a future release of ActiveMerchant. Please use CreditCard#brand instead.", CreditCard) do + assert_deprecation_warning('CreditCard#type is deprecated and will be removed from a future release of ActiveMerchant. Please use CreditCard#brand instead.') do assert_equal @visa.type, @visa.brand end - assert_deprecation_warning("CreditCard#type is deprecated and will be removed from a future release of ActiveMerchant. Please use CreditCard#brand instead.", CreditCard) do - assert_equal @solo.type, @solo.brand + assert_deprecation_warning('CreditCard#type is deprecated and will be removed from a future release of ActiveMerchant. Please use CreditCard#brand instead.') do + assert_equal @maestro.type, @maestro.brand end end + + def test_month_and_year_are_immediately_converted_to_integers + card = CreditCard.new + + card.month = '1' + assert_equal 1, card.month + card.year = '1' + assert_equal 1, card.year + + card.month = '' + assert_nil card.month + card.year = '' + assert_nil card.year + + card.month = nil + assert_nil card.month + card.year = nil + assert_nil card.year + end + + def test_should_report_as_emv_if_icc_data_present + assert CreditCard.new(icc_data: 'E480').emv? + end + + def test_should_not_report_as_emv_if_icc_data_not_present + refute CreditCard.new.emv? + end end diff --git a/test/unit/cvv_result_test.rb b/test/unit/cvv_result_test.rb index 12ed48d1e35..282fc97c8a6 100644 --- a/test/unit/cvv_result_test.rb +++ b/test/unit/cvv_result_test.rb @@ -6,28 +6,28 @@ def test_nil_data assert_nil result.code assert_nil result.message end - + def test_blank_data result = CVVResult.new('') assert_nil result.code assert_nil result.message end - + def test_successful_match result = CVVResult.new('M') assert_equal 'M', result.code assert_equal CVVResult.messages['M'], result.message end - + def test_failed_match result = CVVResult.new('N') assert_equal 'N', result.code assert_equal CVVResult.messages['N'], result.message end - + def test_to_hash result = CVVResult.new('M').to_hash assert_equal 'M', result['code'] assert_equal CVVResult.messages['M'], result['message'] end -end \ No newline at end of file +end diff --git a/test/unit/expiry_date_test.rb b/test/unit/expiry_date_test.rb index 9be4b731afc..07a44b26c15 100644 --- a/test/unit/expiry_date_test.rb +++ b/test/unit/expiry_date_test.rb @@ -6,27 +6,27 @@ def test_should_be_expired date = CreditCard::ExpiryDate.new(last_month.month, last_month.year) assert date.expired? end - + def test_today_should_not_be_expired today = Time.now.utc date = CreditCard::ExpiryDate.new(today.month, today.year) assert_false date.expired? end - + def test_dates_in_the_future_should_not_be_expired next_month = 1.month.from_now date = CreditCard::ExpiryDate.new(next_month.month, next_month.year) assert_false date.expired? end - + def test_invalid_date expiry = CreditCard::ExpiryDate.new(13, 2009) assert_equal Time.at(0).utc, expiry.expiration end - + def test_month_and_year_coerced_to_integer - expiry = CreditCard::ExpiryDate.new("13", "2009") + expiry = CreditCard::ExpiryDate.new('13', '2009') assert_equal 13, expiry.month assert_equal 2009, expiry.year end -end \ No newline at end of file +end diff --git a/test/unit/fixtures_test.rb b/test/unit/fixtures_test.rb new file mode 100644 index 00000000000..b72720d928c --- /dev/null +++ b/test/unit/fixtures_test.rb @@ -0,0 +1,11 @@ +require 'test_helper' + +class FixturesTest < Test::Unit::TestCase + def test_sort + keys = YAML.safe_load(File.read(ActiveMerchant::Fixtures::DEFAULT_CREDENTIALS), [], [], true).keys + assert_equal( + keys, + keys.sort + ) + end +end diff --git a/test/unit/gateways/adyen_test.rb b/test/unit/gateways/adyen_test.rb new file mode 100644 index 00000000000..976cedc73b5 --- /dev/null +++ b/test/unit/gateways/adyen_test.rb @@ -0,0 +1,860 @@ +require 'test_helper' + +class AdyenTest < Test::Unit::TestCase + include CommStub + + def setup + @gateway = AdyenGateway.new( + username: 'ws@adyenmerchant.com', + password: 'password', + merchant_account: 'merchantAccount' + ) + + @credit_card = credit_card('4111111111111111', + :month => 8, + :year => 2018, + :first_name => 'Test', + :last_name => 'Card', + :verification_value => '737', + :brand => 'visa' + ) + + @elo_credit_card = credit_card('5066 9911 1111 1118', + :month => 10, + :year => 2020, + :first_name => 'John', + :last_name => 'Smith', + :verification_value => '737', + :brand => 'elo' + ) + + @three_ds_enrolled_card = credit_card('4212345678901237', brand: :visa) + + @apple_pay_card = network_tokenization_credit_card('4111111111111111', + :payment_cryptogram => 'YwAAAAAABaYcCMX/OhNRQAAAAAA=', + :month => '08', + :year => '2018', + :source => :apple_pay, + :verification_value => nil + ) + + @amount = 100 + + @options = { + billing_address: address(), + shopper_reference: 'John Smith', + order_id: '345123', + installments: 2, + stored_credential: {reason_type: 'unscheduled'} + } + + @normalized_initial_stored_credential = { + stored_credential: { + initial_transaction: true, + reason_type: 'unscheduled' + } + } + + @normalized_stored_credential = { + stored_credential: { + initial_transaction: false, + reason_type: 'recurring' + } + } + + @normalized_3ds_2_options = { + reference: '345123', + shopper_email: 'john.smith@test.com', + shopper_ip: '77.110.174.153', + shopper_reference: 'John Smith', + billing_address: address(), + order_id: '123', + stored_credential: {reason_type: 'unscheduled'}, + three_ds_2: { + channel: 'browser', + browser_info: { + accept_header: 'unknown', + depth: 100, + java: false, + language: 'US', + height: 1000, + width: 500, + timezone: '-120', + user_agent: 'unknown' + } + } + } + end + + # Subdomains are only valid for production gateways, so the test_url check must be manually bypassed for this test to pass. + # def test_subdomain_specification + # gateway = AdyenGateway.new( + # username: 'ws@adyenmerchant.com', + # password: 'password', + # merchant_account: 'merchantAccount', + # subdomain: '123-subdomain' + # ) + # + # response = stub_comms(gateway) do + # gateway.authorize(@amount, @credit_card, @options) + # end.check_request do |endpoint, data, headers| + # assert_match("https://123-subdomain-pal-live.adyenpayments.com/pal/servlet/Payment/v18/authorise", endpoint) + # end.respond_with(successful_authorize_response) + # + # assert response + # assert_success response + # end + + def test_successful_authorize + @gateway.expects(:ssl_post).returns(successful_authorize_response) + + response = @gateway.authorize(@amount, @credit_card, @options) + assert_success response + + assert_equal '#7914775043909934#', response.authorization + assert_equal 'R', response.avs_result['code'] + assert_equal 'M', response.cvv_result['code'] + assert response.test? + end + + def test_successful_authorize_with_3ds + @gateway.expects(:ssl_post).returns(successful_authorize_with_3ds_response) + + response = @gateway.authorize(@amount, @three_ds_enrolled_card, @options.merge(execute_threed: true)) + assert response.test? + refute response.authorization.blank? + assert_equal '#8835440446784145#', response.authorization + assert_equal response.params['resultCode'], 'RedirectShopper' + refute response.params['issuerUrl'].blank? + refute response.params['md'].blank? + refute response.params['paRequest'].blank? + end + + def test_adds_3ds1_standalone_fields + eci = '05' + cavv = '3q2+78r+ur7erb7vyv66vv\/\/\/\/8=' + cavv_algorithm = '1' + xid = 'ODUzNTYzOTcwODU5NzY3Qw==' + directory_response_status = 'C' + authentication_response_status = 'Y' + options_with_3ds1_standalone = @options.merge( + three_d_secure: { + eci: eci, + cavv: cavv, + cavv_algorithm: cavv_algorithm, + xid: xid, + directory_response_status: directory_response_status, + authentication_response_status: authentication_response_status + } + ) + stub_comms do + @gateway.authorize(@amount, @credit_card, options_with_3ds1_standalone) + end.check_request do |endpoint, data, headers| + assert_equal eci, JSON.parse(data)['mpiData']['eci'] + assert_equal cavv, JSON.parse(data)['mpiData']['cavv'] + assert_equal cavv_algorithm, JSON.parse(data)['mpiData']['cavvAlgorithm'] + assert_equal xid, JSON.parse(data)['mpiData']['xid'] + assert_equal directory_response_status, JSON.parse(data)['mpiData']['directoryResponse'] + assert_equal authentication_response_status, JSON.parse(data)['mpiData']['authenticationResponse'] + end.respond_with(successful_authorize_response) + end + + def test_adds_3ds2_standalone_fields + version = '2.1.0' + eci = '02' + cavv = 'jJ81HADVRtXfCBATEp01CJUAAAA=' + ds_transaction_id = '97267598-FAE6-48F2-8083-C23433990FBC' + directory_response_status = 'C' + authentication_response_status = 'Y' + options_with_3ds2_standalone = @options.merge( + three_d_secure: { + version: version, + eci: eci, + cavv: cavv, + ds_transaction_id: ds_transaction_id, + directory_response_status: directory_response_status, + authentication_response_status: authentication_response_status + } + ) + stub_comms do + @gateway.authorize(@amount, @credit_card, options_with_3ds2_standalone) + end.check_request do |endpoint, data, headers| + assert_equal version, JSON.parse(data)['mpiData']['threeDSVersion'] + assert_equal eci, JSON.parse(data)['mpiData']['eci'] + assert_equal cavv, JSON.parse(data)['mpiData']['cavv'] + assert_equal ds_transaction_id, JSON.parse(data)['mpiData']['dsTransID'] + assert_equal directory_response_status, JSON.parse(data)['mpiData']['directoryResponse'] + assert_equal authentication_response_status, JSON.parse(data)['mpiData']['authenticationResponse'] + end.respond_with(successful_authorize_response) + end + + def test_failed_authorize + @gateway.expects(:ssl_post).returns(failed_authorize_response) + + response = @gateway.authorize(@amount, @credit_card, @options) + assert_equal 'Expired Card', response.message + assert_failure response + end + + def test_successful_capture + @gateway.expects(:ssl_post).returns(successful_capture_response) + response = @gateway.capture(@amount, '7914775043909934') + assert_equal '7914775043909934#8814775564188305#', response.authorization + assert_success response + assert response.test? + end + + def test_failed_capture + @gateway.expects(:ssl_post).returns(failed_capture_response) + response = @gateway.capture(nil, '') + assert_nil response.authorization + assert_equal 'Original pspReference required for this operation', response.message + assert_failure response + end + + def test_successful_purchase + response = stub_comms do + @gateway.purchase(@amount, @credit_card, @options) + end.respond_with(successful_authorize_response, successful_capture_response) + assert_success response + assert_equal '7914775043909934#8814775564188305#', response.authorization + assert response.test? + end + + def test_successful_purchase_with_elo_card + response = stub_comms do + @gateway.purchase(@amount, @elo_credit_card, @options) + end.respond_with(successful_authorize_with_elo_response, successful_capture_with_elo_repsonse) + assert_success response + assert_equal '8835511210681145#8835511210689965#', response.authorization + assert response.test? + end + + def test_successful_maestro_purchase + response = stub_comms do + @gateway.purchase(@amount, @credit_card, @options.merge({selected_brand: 'maestro', overwrite_brand: 'true'})) + end.check_request do |endpoint, data, headers| + if endpoint =~ /authorise/ + assert_match(/"overwriteBrand":true/, data) + assert_match(/"selectedBrand":"maestro"/, data) + end + end.respond_with(successful_authorize_response, successful_capture_response) + assert_success response + assert_equal '7914775043909934#8814775564188305#', response.authorization + assert response.test? + end + + def test_3ds_2_fields_sent + stub_comms do + @gateway.authorize(@amount, @credit_card, @normalized_3ds_2_options) + end.check_request do |endpoint, data, headers| + data = JSON.parse(data) + assert_equal 'browser', data['threeDS2RequestData']['deviceChannel'] + assert_equal 'unknown', data['browserInfo']['acceptHeader'] + assert_equal 100, data['browserInfo']['colorDepth'] + assert_equal false, data['browserInfo']['javaEnabled'] + assert_equal 'US', data['browserInfo']['language'] + assert_equal 1000, data['browserInfo']['screenHeight'] + assert_equal 500, data['browserInfo']['screenWidth'] + assert_equal '-120', data['browserInfo']['timeZoneOffset'] + assert_equal 'unknown', data['browserInfo']['userAgent'] + end.respond_with(successful_authorize_response) + end + + def test_installments_sent + stub_comms do + @gateway.authorize(@amount, @credit_card, @options) + end.check_request do |endpoint, data, headers| + assert_equal 2, JSON.parse(data)['installments']['value'] + end.respond_with(successful_authorize_response) + end + + def test_custom_routing_sent + stub_comms do + @gateway.authorize(@amount, @credit_card, @options.merge({custom_routing_flag: 'abcdefg'})) + end.check_request do |endpoint, data, headers| + assert_equal 'abcdefg', JSON.parse(data)['additionalData']['customRoutingFlag'] + end.respond_with(successful_authorize_response) + end + + def test_update_shopper_statement_and_industry_usage_sent + stub_comms do + @gateway.adjust(@amount, '123', @options.merge({update_shopper_statement: 'statement note', industry_usage: 'DelayedCharge'})) + end.check_request do |endpoint, data, headers| + assert_equal 'statement note', JSON.parse(data)['additionalData']['updateShopperStatement'] + assert_equal 'DelayedCharge', JSON.parse(data)['additionalData']['industryUsage'] + end.respond_with(successful_adjust_response) + end + + def test_risk_data_sent + stub_comms do + @gateway.authorize(@amount, @credit_card, @options.merge({risk_data: {'operatingSystem' => 'HAL9000'}})) + end.check_request do |endpoint, data, headers| + assert_equal 'HAL9000', JSON.parse(data)['additionalData']['riskdata.operatingSystem'] + end.respond_with(successful_authorize_response) + end + + def test_risk_data_complex_data + stub_comms do + risk_data = { + 'deliveryMethod' => 'express', + 'basket.item.productTitle' => 'Blue T Shirt', + 'promotions.promotion.promotionName' => 'Big Sale promotion' + } + @gateway.authorize(@amount, @credit_card, @options.merge({risk_data: risk_data})) + end.check_request do |endpoint, data, headers| + parsed = JSON.parse(data) + assert_equal 'express', parsed['additionalData']['riskdata.deliveryMethod'] + assert_equal 'Blue T Shirt', parsed['additionalData']['riskdata.basket.item.productTitle'] + assert_equal 'Big Sale promotion', parsed['additionalData']['riskdata.promotions.promotion.promotionName'] + end.respond_with(successful_authorize_response) + end + + def test_successful_authorize_with_normalized_stored_credentials + @credit_card.verification_value = nil + stub_comms do + @gateway.authorize(50, @credit_card, @options.merge(@normalized_stored_credential)) + end.check_request do |endpoint, data, headers| + assert_match(/"shopperInteraction":"ContAuth"/, data) + assert_match(/"recurringProcessingModel":"Subscription"/, data) + end.respond_with(successful_authorize_response) + end + + def test_successful_initial_authorize_with_normalized_stored_credentials + stub_comms do + @gateway.authorize(50, @credit_card, @options.merge(@normalized_initial_stored_credential)) + end.check_request do |endpoint, data, headers| + assert_match(/"shopperInteraction":"Ecommerce"/, data) + assert_match(/"recurringProcessingModel":"CardOnFile"/, data) + end.respond_with(successful_authorize_response) + end + + def test_nonfractional_currency_handling + stub_comms do + @gateway.authorize(200, @credit_card, @options.merge(currency: 'JPY')) + end.check_request do |endpoint, data, headers| + assert_match(/"amount\":{\"value\":\"2\",\"currency\":\"JPY\"}/, data) + end.respond_with(successful_authorize_response) + + stub_comms do + @gateway.authorize(200, @credit_card, @options.merge(currency: 'CLP')) + end.check_request do |endpoint, data, headers| + assert_match(/"amount\":{\"value\":\"200\",\"currency\":\"CLP\"}/, data) + end.respond_with(successful_authorize_response) + end + + def test_failed_purchase + @gateway.expects(:ssl_post).returns(failed_purchase_response) + + response = @gateway.purchase(@amount, credit_card('400111'), @options) + assert_failure response + + assert_equal AdyenGateway::STANDARD_ERROR_CODE[:incorrect_number], response.error_code + end + + def test_successful_refund + @gateway.expects(:ssl_post).returns(successful_refund_response) + response = @gateway.refund(@amount, '7914775043909934') + assert_equal '7914775043909934#8514775559925128#', response.authorization + assert_equal '[refund-received]', response.message + assert response.test? + end + + def test_successful_refund_with_compound_psp_reference + @gateway.expects(:ssl_post).returns(successful_refund_response) + response = @gateway.refund(@amount, '7914775043909934#8514775559000000') + assert_equal '7914775043909934#8514775559925128#', response.authorization + assert_equal '[refund-received]', response.message + assert response.test? + end + + def test_failed_refund + @gateway.expects(:ssl_post).returns(failed_refund_response) + response = @gateway.refund(@amount, '') + assert_nil response.authorization + assert_equal 'Original pspReference required for this operation', response.message + assert_failure response + end + + def test_successful_void + @gateway.expects(:ssl_post).returns(successful_void_response) + response = @gateway.void('7914775043909934') + assert_equal '7914775043909934#8614775821628806#', response.authorization + assert_equal '[cancel-received]', response.message + assert response.test? + end + + def test_failed_void + @gateway.expects(:ssl_post).returns(failed_void_response) + response = @gateway.void('') + assert_equal 'Original pspReference required for this operation', response.message + assert_failure response + end + + def test_successful_adjust + @gateway.expects(:ssl_post).returns(successful_adjust_response) + response = @gateway.adjust(200, '8835544088660594') + assert_equal '8835544088660594#8835544088660594#', response.authorization + assert_equal '[adjustAuthorisation-received]', response.message + end + + def test_failed_adjust + @gateway.expects(:ssl_post).returns(failed_adjust_response) + response = @gateway.adjust(200, '') + assert_equal 'Original pspReference required for this operation', response.message + assert_failure response + end + + def test_successful_synchronous_adjust + @gateway.expects(:ssl_post).returns(successful_synchronous_adjust_response) + response = @gateway.adjust(200, '8835544088660594') + assert_equal '8835544088660594#8835574118820108#', response.authorization + assert_equal 'Authorised', response.message + end + + def test_failed_synchronous_adjust + @gateway.expects(:ssl_post).returns(failed_synchronous_adjust_response) + response = @gateway.adjust(200, '8835544088660594') + assert_equal 'Refused', response.message + assert_failure response + end + + def test_successful_store + response = stub_comms do + @gateway.store(@credit_card, @options) + end.check_request do |endpoint, data, headers| + assert_equal 'CardOnFile', JSON.parse(data)['recurringProcessingModel'] + end.respond_with(successful_store_response) + assert_success response + assert_equal '#8835205392522157#8315202663743702', response.authorization + end + + def test_failed_store + @gateway.expects(:ssl_post).returns(failed_store_response) + response = @gateway.store(@credit_card, @options) + assert_failure response + assert_equal 'Refused', response.message + end + + def test_successful_verify + response = stub_comms do + @gateway.verify(@credit_card, @options) + end.respond_with(successful_verify_response) + assert_success response + assert_equal '#7914776426645103#', response.authorization + assert_equal 'Authorised', response.message + assert response.test? + end + + def test_failed_verify + response = stub_comms do + @gateway.verify(@credit_card, @options) + end.respond_with(failed_verify_response) + assert_failure response + assert_equal '#7914776433387947#', response.authorization + assert_equal 'Refused', response.message + assert response.test? + end + + def test_failed_avs_check_returns_refusal_reason_raw + @gateway.expects(:ssl_post).returns(failed_authorize_avs_response) + + response = @gateway.authorize(@amount, @credit_card, @options) + assert_failure response + assert_equal 'Refused | 05 : Do not honor', response.message + end + + def test_scrub + assert @gateway.supports_scrubbing? + assert_equal @gateway.scrub(pre_scrubbed), post_scrubbed + end + + def test_scrub_network_tokenization_card + assert @gateway.supports_scrubbing? + assert_equal @gateway.scrub(pre_scrubbed), post_scrubbed + end + + def test_add_address + post = {:card => {:billingAddress => {}}} + @options[:billing_address].delete(:address1) + @options[:billing_address].delete(:address2) + @options[:billing_address].delete(:state) + @gateway.send(:add_address, post, @options) + assert_equal 'N/A', post[:billingAddress][:street] + assert_equal 'N/A', post[:billingAddress][:houseNumberOrName] + assert_equal 'N/A', post[:billingAddress][:stateOrProvince] + assert_equal @options[:billing_address][:zip], post[:billingAddress][:postalCode] + assert_equal @options[:billing_address][:city], post[:billingAddress][:city] + assert_equal @options[:billing_address][:country], post[:billingAddress][:country] + end + + def test_authorize_with_network_tokenization_credit_card_no_name + @apple_pay_card.first_name = nil + @apple_pay_card.last_name = nil + response = stub_comms do + @gateway.authorize(@amount, @apple_pay_card, @options) + end.check_request do |endpoint, data, headers| + assert_equal 'Not Provided', JSON.parse(data)['card']['holderName'] + end.respond_with(successful_authorize_response) + assert_success response + end + + def test_authorize_with_network_tokenization_credit_card + response = stub_comms do + @gateway.authorize(@amount, @apple_pay_card, @options) + end.check_request do |endpoint, data, headers| + parsed = JSON.parse(data) + assert_equal 'YwAAAAAABaYcCMX/OhNRQAAAAAA=', parsed['mpiData']['cavv'] + assert_equal '07', parsed['mpiData']['eci'] + assert_equal 'applepay', parsed['additionalData']['paymentdatasource.type'] + end.respond_with(successful_authorize_response) + assert_success response + end + + def test_extended_avs_response + response = stub_comms do + @gateway.verify(@credit_card, @options) + end.respond_with(extended_avs_response) + assert_equal 'Card member\'s name, billing address, and billing postal code match.', response.avs_result['message'] + end + + def test_optional_idempotency_key_header + options = @options.merge(:idempotency_key => 'test123') + response = stub_comms do + @gateway.authorize(@amount, @credit_card, options) + end.check_request do |endpoint, data, headers| + assert headers['Idempotency-Key'] + end.respond_with(successful_authorize_response) + assert_success response + end + + private + + def pre_scrubbed + <<-PRE_SCRUBBED + opening connection to pal-test.adyen.com:443... + opened + starting SSL for pal-test.adyen.com:443... + SSL established + <- "POST /pal/servlet/Payment/v18/authorise HTTP/1.1\r\nContent-Type: application/json\r\nAuthorization: Basic d3NfMTYzMjQ1QENvbXBhbnkuRGFuaWVsYmFra2Vybmw6eXU0aD50ZlxIVEdydSU1PDhxYTVMTkxVUw==\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: pal-test.adyen.com\r\nContent-Length: 308\r\n\r\n" + <- "{\"merchantAccount\":\"DanielbakkernlNL\",\"reference\":\"345123\",\"amount\":{\"value\":\"100\",\"currency\":\"USD\"},\"card\":{\"expiryMonth\":8,\"expiryYear\":2018,\"holderName\":\"John Smith\",\"number\":\"4111111111111111\",\"cvc\":\"737\"},\"shopperEmail\":\"john.smith@test.com\",\"shopperIP\":\"77.110.174.153\",\"shopperReference\":\"John Smith\"}" + -> "HTTP/1.1 200 OK\r\n" + -> "Date: Thu, 27 Oct 2016 11:37:13 GMT\r\n" + -> "Server: Apache\r\n" + -> "Set-Cookie: JSESSIONID=C0D66C19173B3491D862B8FDBFD72FD7.test3e; Path=/pal/; Secure; HttpOnly\r\n" + -> "pspReference: 8514775682339577\r\n" + -> "Connection: close\r\n" + -> "Transfer-Encoding: chunked\r\n" + -> "Content-Type: application/json;charset=utf-8\r\n" + -> "\r\n" + -> "50\r\n" + reading 80 bytes... + -> "" + -> "{\"pspReference\":\"8514775682339577\",\"resultCode\":\"Authorised\",\"authCode\":\"31845\"}" + read 80 bytes + reading 2 bytes... + -> "" + -> "\r\n" + read 2 bytes + -> "0\r\n" + -> "\r\n" + Conn close + PRE_SCRUBBED + end + + def post_scrubbed + <<-POST_SCRUBBED + opening connection to pal-test.adyen.com:443... + opened + starting SSL for pal-test.adyen.com:443... + SSL established + <- "POST /pal/servlet/Payment/v18/authorise HTTP/1.1\r\nContent-Type: application/json\r\nAuthorization: Basic [FILTERED]==\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: pal-test.adyen.com\r\nContent-Length: 308\r\n\r\n" + <- "{\"merchantAccount\":\"DanielbakkernlNL\",\"reference\":\"345123\",\"amount\":{\"value\":\"100\",\"currency\":\"USD\"},\"card\":{\"expiryMonth\":8,\"expiryYear\":2018,\"holderName\":\"John Smith\",\"number\":\"[FILTERED]\",\"cvc\":\"[FILTERED]\"},\"shopperEmail\":\"john.smith@test.com\",\"shopperIP\":\"77.110.174.153\",\"shopperReference\":\"John Smith\"}" + -> "HTTP/1.1 200 OK\r\n" + -> "Date: Thu, 27 Oct 2016 11:37:13 GMT\r\n" + -> "Server: Apache\r\n" + -> "Set-Cookie: JSESSIONID=C0D66C19173B3491D862B8FDBFD72FD7.test3e; Path=/pal/; Secure; HttpOnly\r\n" + -> "pspReference: 8514775682339577\r\n" + -> "Connection: close\r\n" + -> "Transfer-Encoding: chunked\r\n" + -> "Content-Type: application/json;charset=utf-8\r\n" + -> "\r\n" + -> "50\r\n" + reading 80 bytes... + -> "" + -> "{\"pspReference\":\"8514775682339577\",\"resultCode\":\"Authorised\",\"authCode\":\"31845\"}" + read 80 bytes + reading 2 bytes... + -> "" + -> "\r\n" + read 2 bytes + -> "0\r\n" + -> "\r\n" + Conn close + POST_SCRUBBED + end + + def pre_scrubbed_network_tokenization_card + <<-PRE_SCRUBBED + opening connection to pal-test.adyen.com:443... + opened + starting SSL for pal-test.adyen.com:443... + SSL established + I, [2018-06-18T11:53:47.394267 #25363] INFO -- : [ActiveMerchant::Billing::AdyenGateway] connection_ssl_version=TLSv1.2 connection_ssl_cipher=ECDHE-RSA-AES128-GCM-SHA256 + D, [2018-06-18T11:53:47.394346 #25363] DEBUG -- : {"merchantAccount":"SpreedlyCOM294","reference":"123","amount":{"value":"100","currency":"USD"},"mpiData":{"authenticationResponse":"Y","cavv":"YwAAAAAABaYcCMX/OhNRQAAAAAA=","directoryResponse":"Y","eci":"07"},"card":{"expiryMonth":8,"expiryYear":2018,"holderName":"Longbob Longsen","number":"4111111111111111","billingAddress":{"street":"456 My Street","houseNumberOrName":"Apt 1","postalCode":"K1C2N6","city":"Ottawa","stateOrProvince":"ON","country":"CA"}},"shopperEmail":"john.smith@test.com","shopperIP":"77.110.174.153","shopperReference":"John Smith","selectedBrand":"applepay","shopperInteraction":"Ecommerce"} + <- "POST /pal/servlet/Payment/v18/authorise HTTP/1.1\r\nContent-Type: application/json\r\nAuthorization: Basic d3NAQ29tcGFueS5TcHJlZWRseTQ3MTo3c3d6U0p2R1VWViUvP3Q0Uy9bOVtoc0hF\r\nConnection: close\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nHost: pal-test.adyen.com\r\nContent-Length: 618\r\n\r\n" + <- "{\"merchantAccount\":\"SpreedlyCOM294\",\"reference\":\"123\",\"amount\":{\"value\":\"100\",\"currency\":\"USD\"},\"mpiData\":{\"authenticationResponse\":\"Y\",\"cavv\":\"YwAAAAAABaYcCMX/OhNRQAAAAAA=\",\"directoryResponse\":\"Y\",\"eci\":\"07\"},\"card\":{\"expiryMonth\":8,\"expiryYear\":2018,\"holderName\":\"Longbob Longsen\",\"number\":\"4111111111111111\",\"billingAddress\":{\"street\":\"456 My Street\",\"houseNumberOrName\":\"Apt 1\",\"postalCode\":\"K1C2N6\",\"city\":\"Ottawa\",\"stateOrProvince\":\"ON\",\"country\":\"CA\"}},\"shopperEmail\":\"john.smith@test.com\",\"shopperIP\":\"77.110.174.153\",\"shopperReference\":\"John Smith\",\"selectedBrand\":\"applepay\",\"shopperInteraction\":\"Ecommerce\"}" + -> "HTTP/1.1 200 OK\r\n" + -> "Date: Mon, 18 Jun 2018 15:53:47 GMT\r\n" + -> "Server: Apache\r\n" + -> "Set-Cookie: JSESSIONID=06EE78291B761A33ED9E21E46BA54649.test104e; Path=/pal; Secure; HttpOnly\r\n" + -> "pspReference: 8835293372276408\r\n" + -> "Connection: close\r\n" + -> "Transfer-Encoding: chunked\r\n" + -> "Content-Type: application/json;charset=utf-8\r\n" + -> "\r\n" + -> "50\r\n" + reading 80 bytes... + -> "" + -> "{\"pspReference\":\"8835293372276408\",\"resultCode\":\"Authorised\",\"authCode\":\"26056\"}" + read 80 bytes + reading 2 bytes... + -> "" + -> "\r\n" + read 2 bytes + -> "0\r\n" + -> "\r\n" + Conn close + PRE_SCRUBBED + end + + def post_scrubbed_network_tokenization_card + <<-POST_SCRUBBED + opening connection to pal-test.adyen.com:443... + opened + starting SSL for pal-test.adyen.com:443... + SSL established + I, [2018-06-18T11:53:47.394267 #25363] INFO -- : [ActiveMerchant::Billing::AdyenGateway] connection_ssl_version=TLSv1.2 connection_ssl_cipher=ECDHE-RSA-AES128-GCM-SHA256 + D, [2018-06-18T11:53:47.394346 #25363] DEBUG -- : {"merchantAccount":"SpreedlyCOM294","reference":"123","amount":{"value":"100","currency":"USD"},"mpiData":{"authenticationResponse":"Y","cavv":"[FILTERED]","directoryResponse":"Y","eci":"07"},"card":{"expiryMonth":8,"expiryYear":2018,"holderName":"Longbob Longsen","number":"[FILTERED]","billingAddress":{"street":"456 My Street","houseNumberOrName":"Apt 1","postalCode":"K1C2N6","city":"Ottawa","stateOrProvince":"ON","country":"CA"}},"shopperEmail":"john.smith@test.com","shopperIP":"77.110.174.153","shopperReference":"John Smith","selectedBrand":"applepay","shopperInteraction":"Ecommerce"} + <- "POST /pal/servlet/Payment/v18/authorise HTTP/1.1\r\nContent-Type: application/json\r\nAuthorization: Basic [FILTERED]\r\nConnection: close\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nHost: pal-test.adyen.com\r\nContent-Length: 618\r\n\r\n" + <- "{\"merchantAccount\":\"SpreedlyCOM294\",\"reference\":\"123\",\"amount\":{\"value\":\"100\",\"currency\":\"USD\"},\"mpiData\":{\"authenticationResponse\":\"Y\",\"cavv\":\"[FILTERED]\",\"directoryResponse\":\"Y\",\"eci\":\"07\"},\"card\":{\"expiryMonth\":8,\"expiryYear\":2018,\"holderName\":\"Longbob Longsen\",\"number\":\"[FILTERED]\",\"billingAddress\":{\"street\":\"456 My Street\",\"houseNumberOrName\":\"Apt 1\",\"postalCode\":\"K1C2N6\",\"city\":\"Ottawa\",\"stateOrProvince\":\"ON\",\"country\":\"CA\"}},\"shopperEmail\":\"john.smith@test.com\",\"shopperIP\":\"77.110.174.153\",\"shopperReference\":\"John Smith\",\"selectedBrand\":\"applepay\",\"shopperInteraction\":\"Ecommerce\"}" + -> "HTTP/1.1 200 OK\r\n" + -> "Date: Mon, 18 Jun 2018 15:53:47 GMT\r\n" + -> "Server: Apache\r\n" + -> "Set-Cookie: JSESSIONID=06EE78291B761A33ED9E21E46BA54649.test104e; Path=/pal; Secure; HttpOnly\r\n" + -> "pspReference: 8835293372276408\r\n" + -> "Connection: close\r\n" + -> "Transfer-Encoding: chunked\r\n" + -> "Content-Type: application/json;charset=utf-8\r\n" + -> "\r\n" + -> "50\r\n" + reading 80 bytes... + -> "" + -> "{\"pspReference\":\"8835293372276408\",\"resultCode\":\"Authorised\",\"authCode\":\"26056\"}" + read 80 bytes + reading 2 bytes... + -> "" + -> "\r\n" + read 2 bytes + -> "0\r\n" + -> "\r\n" + Conn close + POST_SCRUBBED + end + + def failed_purchase_response + <<-RESPONSE + { + "status": 422, + "errorCode": "101", + "message": "Invalid card number", + "errorType": "validation", + "pspReference": "8514775645144049" + } + RESPONSE + end + + def successful_authorize_with_elo_response + <<-RESPONSE + { + "pspReference":"8835511210681145", + "resultCode":"Authorised", + "authCode":"98696" + } + RESPONSE + end + + def successful_capture_with_elo_repsonse + <<-RESPONSE + { + "pspReference":"8835511210689965", + "response":"[capture-received]" + } + RESPONSE + end + + def successful_authorize_response + <<-RESPONSE + { + "additionalData": { + "cvcResult": "1 Matches", + "avsResult": "0 Unknown", + "cvcResultRaw": "M" + }, + "pspReference":"7914775043909934", + "resultCode":"Authorised", + "authCode":"50055" + } + RESPONSE + end + + def successful_authorize_with_3ds_response + '{"pspReference":"8835440446784145","resultCode":"RedirectShopper","issuerUrl":"https:\\/\\/test.adyen.com\\/hpp\\/3d\\/validate.shtml","md":"djIhcWk3MUhlVFlyQ1h2UC9NWmhpVm10Zz09IfIxi5eDMZgG72AUXy7PEU86esY68wr2cunaFo5VRyNPuWg3ZSvEIFuielSuoYol5WhjCH+R6EJTjVqY8eCTt+0wiqHd5btd82NstIc8idJuvg5OCu2j8dYo0Pg7nYxW\\/2vXV9Wy\\/RYvwR8tFfyZVC\\/U2028JuWtP2WxrBTqJ6nV2mDoX2chqMRSmX8xrL6VgiLoEfzCC\\/c+14r77+whHP0Mz96IGFf4BIA2Qo8wi2vrTlccH\\/zkLb5hevvV6QH3s9h0\\/JibcUrpoXH6M903ulGuikTr8oqVjEB9w8\\/WlUuxukHmqqXqAeOPA6gScehs6SpRm45PLpLysCfUricEIDhpPN1QCjjgw8+qVf3Ja1SzwfjCVocU","paRequest":"eNpVUctuwjAQ\\/BXaD2Dt4JCHFkspqVQOBChwriJnBanIAyepoF9fG5LS+jQz612PZ3F31ETxllSnSeKSmiY90CjPZs+h709cIZgQU88XXLjPEtfRO50lfpFu8qqUfMzGDsJATbtWx7RsJabq\\/LJIJHcmwp0i9BQL0otY7qhp10URqXOXa9IIdxnLtCC5jz6i+VO4rY2v7HSdr5ZOIBBuNVRVV7b6Kn3BEAaCnT7JY9vWIUDTt41VVSDYAsLD1bqzqDGDLnkmV\\/HhO9lt2DLesORTiSR+ZckmsmeGYG9glrYkHcZ97jB35PCQe6HrI9x0TAvrQO638cgkYRz1Atb2nehOuC38FdBEralUwy8GhnSpq5LMDRPpL0Z4mJ6\\/2WBVa7ISzj1azw+YQZ6N+FawU3ITCg9YcBtjCYJthX570G\\/ZoH\\/b\\/wFlSqpp"}' + end + + def failed_authorize_response + <<-RESPONSE + { + "pspReference": "8514775559925128", + "refusalReason": "Expired Card", + "resultCode": "Refused" + } + RESPONSE + end + + def successful_capture_response + <<-RESPONSE + { + "pspReference": "8814775564188305", + "response": "[capture-received]" + } + RESPONSE + end + + def failed_capture_response + <<-RESPONSE + { + "status": 422, + "errorCode": "167", + "message": "Original pspReference required for this operation", + "errorType": "validation" + } + RESPONSE + end + + def successful_refund_response + <<-RESPONSE + { + "pspReference": "8514775559925128", + "response": "[refund-received]" + } + RESPONSE + end + + def failed_refund_response + <<-RESPONSE + { + "status":422, + "errorCode":"167", + "message":"Original pspReference required for this operation", + "errorType":"validation" + } + RESPONSE + end + + def successful_void_response + <<-RESPONSE + { + "pspReference":"8614775821628806", + "response":"[cancel-received]" + } + RESPONSE + end + + def failed_void_response + <<-RESPONSE + { + "status":422, + "errorCode":"167", + "message":"Original pspReference required for this operation", + "errorType":"validation" + } + RESPONSE + end + + def successful_adjust_response + <<-RESPONSE + { + "pspReference": "8835544088660594", + "response": "[adjustAuthorisation-received]" + } + RESPONSE + end + + def failed_adjust_response + <<-RESPONSE + { + "status":422, + "errorCode":"167", + "message":"Original pspReference required for this operation", + "errorType":"validation" + } + RESPONSE + end + + def successful_synchronous_adjust_response + <<-RESPONSE + {\"additionalData\":{\"authCode\":\"70125\",\"adjustAuthorisationData\":\"BQABAQA9NtGnJAkLXKqW1C+VUeCNMzDf4WwzLFBiuQ8iaA2Yvflz41t0cYxtA7XVzG2pzlJPMnkSK75k3eByNS0\\/m0\\/N2+NnnKv\\/9rYPn8Pjq1jc7CapczdqZNl8P9FwqtIa4Kdeq7ZBNeGalx9oH4reutlFggzWCr+4eYXMRqMgQNI2Bu5XvwkqBbXwbDL05CuNPjjEwO64YrCpVBLrxk4vlW4fvCLFR0u8O68C+Y4swmsPDvGUxWpRgwNVqXsTmvt9z8hlej21BErL8fPEy+fJP4Zab8oyfcLrv9FJkHZq03cyzJpOzqX458Ctn9sIwBawXzNEFN5bCt6eT1rgp0yuHeMGEGwrjNl8rijez7Rd\\/vy1WUYAAMfmZFuJMQ73l1+Hkr0VlHv6crlyP\\/FVTY\\/XIUiGMqa1yM08Zu\\/Gur5N7lU8qnMi2WO9QPyHmmdlfo7+AGsrKrzV4wY\\/wISg0pcv8PypBWVq\\/hYoCqlHsGUuIiyGLIW7A8LtG6\\/JqAA9t\\/0EdnQVz0k06IEEYnBzkQoY8Qv3cVszgPQukGstBraB47gQdVDp9vmuQjMstt8Te56SDRxtfcu0z4nQIURVSkJJNj8RYfwXH9OUbz3Vd2vwoR3lCJFTCKIeW8sidNVB3xAZnddBVQ3P\\/QxPnrrRdCcnoWSGoEOBBIxgF00XwNxJ4P7Xj1bB7oq3M7k99dgPnSdZIjyvG6BWKnCQcGyVRB0yOaYBaOCmN66EgWfXoJR5BA4Jo6gnWnESWV62iUC8OCzmis1VagfaBn0A9vWNcqKFkUr\\/68s3w8ixLJFy+WdpAS\\/flzC3bJbvy9YR9nESKAP40XiNGz9iBROCfPI2bSOvdFf831RdTxWaE+ewAC3w9GsgEKAXxzWsVeSODWRZQA0TEVOfX8SaNVa5w3EXLDsRVnmKgUH8yQnEJQBGhDJXg1sEbowE07CzzdAY5Mc=\",\"refusalReasonRaw\":\"AUTHORISED\"},\"pspReference\":\"8835574118820108\",\"response\":\"Authorised\"} + RESPONSE + end + + def failed_synchronous_adjust_response + <<-RESPONSE + {\"additionalData\":{\"authCode\":\"90745\",\"refusalReasonRaw\":\"2\"},\"pspReference\":\"8835574120337117\",\"response\":\"Refused\"} + RESPONSE + end + + def successful_verify_response + <<-RESPONSE + { + "pspReference":"7914776426645103", + "resultCode":"Authorised", + "authCode":"31265" + } + RESPONSE + end + + def failed_verify_response + <<-RESPONSE + { + "pspReference":"7914776433387947", + "refusalReason":"Refused", + "resultCode":"Refused" + } + RESPONSE + end + + def failed_authorize_avs_response + <<-RESPONSE + {\"additionalData\":{\"cvcResult\":\"0 Unknown\",\"fraudResultType\":\"GREEN\",\"avsResult\":\"3 AVS unavailable\",\"fraudManualReview\":\"false\",\"avsResultRaw\":\"U\",\"refusalReasonRaw\":\"05 : Do not honor\",\"authorisationMid\":\"494619000001174\",\"acquirerCode\":\"AdyenVisa_BR_494619\",\"acquirerReference\":\"802320302458\",\"acquirerAccountCode\":\"AdyenVisa_BR_Cabify\"},\"fraudResult\":{\"accountScore\":0,\"results\":[{\"FraudCheckResult\":{\"accountScore\":0,\"checkId\":46,\"name\":\"DistinctCountryUsageByShopper\"}}]},\"pspReference\":\"1715167376763498\",\"refusalReason\":\"Refused\",\"resultCode\":\"Refused\"} + RESPONSE + end + + def successful_store_response + <<-RESPONSE + {"additionalData":{"recurring.recurringDetailReference":"8315202663743702","recurring.shopperReference":"John Smith"},"pspReference":"8835205392522157","resultCode":"Authorised","authCode":"94571"} + RESPONSE + end + + def failed_store_response + <<-RESPONSE + {"pspReference":"8835205393394754","refusalReason":"Refused","resultCode":"Refused"} + RESPONSE + end + + def extended_avs_response + <<-RESPONSE + {\"additionalData\":{\"cvcResult\":\"1 Matches\",\"cvcResultRaw\":\"Y\",\"avsResult\":\"20 Name, address and zip match\",\"avsResultRaw\":\"M\"}} + RESPONSE + end +end diff --git a/test/unit/gateways/allied_wallet_test.rb b/test/unit/gateways/allied_wallet_test.rb new file mode 100644 index 00000000000..fb291c7c4b4 --- /dev/null +++ b/test/unit/gateways/allied_wallet_test.rb @@ -0,0 +1,354 @@ +require 'test_helper' + +class AlliedWalletTest < Test::Unit::TestCase + include CommStub + + def setup + @gateway = AlliedWalletGateway.new( + site_id: '1234', + merchant_id: '1234', + token: 'token' + ) + + @credit_card = credit_card + @amount = 100 + end + + def test_successful_purchase + response = stub_comms do + @gateway.purchase(@amount, @credit_card) + end.respond_with(successful_purchase_response) + + assert_success response + + assert_equal '123456', response.authorization + assert response.test? + end + + def test_failed_purchase + response = stub_comms do + @gateway.purchase(@amount, @credit_card) + end.respond_with(failed_purchase_response) + + assert_failure response + assert_equal 'Declined', response.message + assert response.test? + end + + def test_successful_authorize_and_capture + response = stub_comms do + @gateway.authorize(@amount, @credit_card) + end.respond_with(successful_authorize_response) + + assert_success response + assert_equal '123456', response.authorization + + capture = stub_comms do + @gateway.capture(@amount, response.authorization) + end.respond_with(successful_capture_response) + + assert_success capture + end + + def test_failed_authorize + response = stub_comms do + @gateway.authorize(@amount, @credit_card) + end.respond_with(failed_authorize_response) + + assert_failure response + assert_equal 'Declined', response.message + assert response.test? + end + + def test_failed_capture + response = stub_comms do + @gateway.capture(100, '') + end.respond_with(failed_capture_response) + + assert_failure response + end + + def test_successful_void + response = stub_comms do + @gateway.purchase(@amount, @credit_card) + end.respond_with(successful_authorize_response) + + assert_success response + assert_equal '123456', response.authorization + + void = stub_comms do + @gateway.void(response.authorization) + end.check_request do |endpoint, data, headers| + assert_match(/123456/, data) + end.respond_with(successful_void_response) + + assert_success void + end + + def test_failed_void + response = stub_comms do + @gateway.void('5d53a33d960c46d00f5dc061947d998c') + end.check_request do |endpoint, data, headers| + assert_match(/5d53a33d960c46d00f5dc061947d998c/, data) + end.respond_with(failed_void_response) + + assert_failure response + end + + def test_successful_refund + response = stub_comms do + @gateway.purchase(@amount, @credit_card) + end.respond_with(successful_purchase_response) + + assert_success response + assert_equal '123456', response.authorization + + refund = stub_comms do + @gateway.refund(@amount, response.authorization) + end.check_request do |endpoint, data, headers| + assert_match(/123456/, data) + end.respond_with(successful_refund_response) + + assert_success refund + end + + def test_failed_refund + response = stub_comms do + @gateway.refund(nil, '') + end.respond_with(failed_refund_response) + + assert_failure response + end + + def test_successful_verify + response = stub_comms do + @gateway.verify(@credit_card) + end.respond_with(successful_authorize_response, failed_void_response) + assert_success response + assert_equal 'Succeeded', response.message + end + + def test_failed_verify + response = stub_comms do + @gateway.verify(@credit_card) + end.respond_with(failed_authorize_response, successful_void_response) + assert_failure response + assert_equal 'Declined', response.message + end + + def test_empty_response_fails + response = stub_comms do + @gateway.purchase(@amount, @credit_card) + end.respond_with(empty_purchase_response) + + assert_failure response + assert_equal 'Error', response.message + end + + def test_invalid_json + response = stub_comms do + @gateway.purchase(@amount, @credit_card) + end.respond_with(invalid_json_response) + + assert_failure response + assert_match %r{Unparsable response}, response.message + end + + def test_transcript_scrubbing + assert_equal scrubbed_transcript, @gateway.scrub(transcript) + end + + def test_nil_cvv_transcript_scrubbing + assert_equal nil_cvv_scrubbed_transcript, @gateway.scrub(nil_cvv_transcript) + end + + def test_empty_string_cvv_transcript_scrubbing + assert_equal empty_string_cvv_scrubbed_transcript, @gateway.scrub(empty_string_cvv_transcript) + end + + private + + def successful_purchase_response + %( + { + "id": "123456", + "message": "Success", + "state": "Sale", + "status": "Successful" + } + ) + end + + def failed_purchase_response + %( + { + "id": "123456", + "message": "Declined", + "state": "Sale", + "status": "Declined" + } + ) + end + + def successful_authorize_response + %( + { + "id": "123456", + "message": "Success", + "state": "Authorize", + "status": "Successful" + } + ) + end + + def failed_authorize_response + %( + { + "id": "123456", + "message": "Declined", + "state": "Authorize", + "status": "Declined" + } + ) + end + + def successful_capture_response + %( + { + "id": "123456", + "message": "Successful", + "state": "Capture", + "status": "Successful" + } + ) + end + + def failed_capture_response + %( + { + "id": "123456", + "message": "Declined", + "state": "Capture", + "status": "Declined" + } + ) + end + + def successful_void_response + %( + { + "id": "123456", + "message": "Success", + "state": "Void", + "status": "Successful" + } + ) + end + + def failed_void_response + %( + { + "id": "123456", + "message": "Error", + "state": "Void", + "status": "Error" + } + ) + end + + def successful_refund_response + %( + { + "id": "123456", + "message": "Success", + "state": "Refund", + "status": "Successful" + } + ) + end + + def failed_refund_response + %( + { + "id": "123456", + "message": "Error", + "state": "Refund", + "status": "Error" + } + ) + end + + def empty_purchase_response + %( + { + "id": "123456", + "message": "Error", + "state": "Purchase", + "status": "Error" + } + ) + end + + def invalid_json_response + %( + { + "id": "123456", + ) + end + + def transcript + %( + <- "POST /merchants/10090/SALEtransactions HTTP/1.1\r\nContent-Type: application/json\r\nAuthorization: Bearer AAEAAHwXaLTYs2APKJW_4TpTDwCw_h9oDx9rA58FR78AFuYCX82Izes1nz9qGBXELGUN_EukKcP5T78Th5guDz4Rw5dQ4Gf0suKw7pz9vWrqa1NpZhrD9Lj9T-SFtOJgfodiwVaBBeSgbJLKA7MOzC9q2dv91HBNP69DygL1oX2L2mtt8fWlKSWhQmtG040E1I43jTueX3L3L9YA7iO6pIwO7CGybE5LnjkQ65KB2K4oYKfXRZosF77hgMJIh-KprFy9cYY3EjfupHeLon9im1BGafrda2N5wj_A_LvdMzfLAD1l1dgj82KlvM_gAzNJ4S19gAicRo9zIbsq36Apt-8jFjS0AQAAAAEAAA9Zr_lVLKMmmtKSo6T_9ulzMCRbYs798EpFD2wMlkb1NCQtA65VrNcM20Ka2FjNQfwOcSMWqDl9zFQhPyFl-npsG1Ww2oyyavA6HSe1HLRLtE_1hNBAlTBPQnLJ6hBf8eR_NTiVa-aQdV2l92-eSwCS59CzrOYGGCY1pLdNMDr_r66kg9l-l94154kRoMBRQSCqZV9iM9M-f3adLJqG6Q79zz1oJpGrH-Zv1kuv8eLaJJNOEFYARb0JbnAC5G1l9-aqxGvBrNkd4sAJIe23XrRx2XJCBIABxuGSQ1xJBTINVlXBXq1mvvd8B1uiYiDNia3c_vIGuSGIjZE0VbUN3oJppfCt1joGdePeUaC2Pyb2vuUN00EBEOaD9RF8IBWMLVJaF9cW2OewDOfBQg94MuOKLdXB_IisRx1ed25VQDVyv0f0CxmkAidvoDN0vvRIJZJr-bgBuL5FZM7gETAeYeiGlh7-Mf2Hzgy7236YNxcC9OnWFEcKEU50nlqog1bJnk8wJgoJWNqG0NUEK4DUzYqknmZ98qQv6rYrg5V-Hey-jAQp_KNf3h-vFHVZdP26Yg\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: api.alliedwallet.com\r\nContent-Length: 464\r\n\r\n" + <- "{\"siteId\":\"10118\",\"amount\":\"1.00\",\"trackingId\":\"82b5f6217fa19daa426e226a231d330a\",\"currency\":\"USD\",\"nameOnCard\":\"Longbob Longsen\",\"cardNumber\":\"4242424242424242\",\"cVVCode\":\"123\",\"expirationYear\":\"2016\",\"expirationMonth\":\"09\",\"email\":\"jim_smith@example.com\",\"iPAddress\":\"127.0.0.1\",\"firstName\":\"Jim\",\"lastName\":\"Smith\",\"addressLine1\":\"456 My Street\",\"addressLine2\":\"Apt 1\",\"city\":\"Ottawa\",\"state\":\"ON\",\"countryId\":\"CA\",\"postalCode\":\"K1C2N6\",\"phone\":\"(555)555-5555\"}" + ) + end + + def scrubbed_transcript + %( + <- "POST /merchants/10090/SALEtransactions HTTP/1.1\r\nContent-Type: application/json\r\nAuthorization: Bearer [FILTERED]\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: api.alliedwallet.com\r\nContent-Length: 464\r\n\r\n" + <- "{\"siteId\":\"10118\",\"amount\":\"1.00\",\"trackingId\":\"82b5f6217fa19daa426e226a231d330a\",\"currency\":\"USD\",\"nameOnCard\":\"Longbob Longsen\",\"cardNumber\":\"[FILTERED]\",\"cVVCode\":\"[FILTERED]\",\"expirationYear\":\"2016\",\"expirationMonth\":\"09\",\"email\":\"jim_smith@example.com\",\"iPAddress\":\"127.0.0.1\",\"firstName\":\"Jim\",\"lastName\":\"Smith\",\"addressLine1\":\"456 My Street\",\"addressLine2\":\"Apt 1\",\"city\":\"Ottawa\",\"state\":\"ON\",\"countryId\":\"CA\",\"postalCode\":\"K1C2N6\",\"phone\":\"(555)555-5555\"}" + ) + end + + def nil_cvv_transcript + %( + <- "POST /merchants/10090/SALEtransactions HTTP/1.1\r\nContent-Type: application/json\r\nAuthorization: Bearer AAEAAHwXaLTYs2APKJW_4TpTDwCw_h9oDx9rA58FR78AFuYCX82Izes1nz9qGBXELGUN_EukKcP5T78Th5guDz4Rw5dQ4Gf0suKw7pz9vWrqa1NpZhrD9Lj9T-SFtOJgfodiwVaBBeSgbJLKA7MOzC9q2dv91HBNP69DygL1oX2L2mtt8fWlKSWhQmtG040E1I43jTueX3L3L9YA7iO6pIwO7CGybE5LnjkQ65KB2K4oYKfXRZosF77hgMJIh-KprFy9cYY3EjfupHeLon9im1BGafrda2N5wj_A_LvdMzfLAD1l1dgj82KlvM_gAzNJ4S19gAicRo9zIbsq36Apt-8jFjS0AQAAAAEAAA9Zr_lVLKMmmtKSo6T_9ulzMCRbYs798EpFD2wMlkb1NCQtA65VrNcM20Ka2FjNQfwOcSMWqDl9zFQhPyFl-npsG1Ww2oyyavA6HSe1HLRLtE_1hNBAlTBPQnLJ6hBf8eR_NTiVa-aQdV2l92-eSwCS59CzrOYGGCY1pLdNMDr_r66kg9l-l94154kRoMBRQSCqZV9iM9M-f3adLJqG6Q79zz1oJpGrH-Zv1kuv8eLaJJNOEFYARb0JbnAC5G1l9-aqxGvBrNkd4sAJIe23XrRx2XJCBIABxuGSQ1xJBTINVlXBXq1mvvd8B1uiYiDNia3c_vIGuSGIjZE0VbUN3oJppfCt1joGdePeUaC2Pyb2vuUN00EBEOaD9RF8IBWMLVJaF9cW2OewDOfBQg94MuOKLdXB_IisRx1ed25VQDVyv0f0CxmkAidvoDN0vvRIJZJr-bgBuL5FZM7gETAeYeiGlh7-Mf2Hzgy7236YNxcC9OnWFEcKEU50nlqog1bJnk8wJgoJWNqG0NUEK4DUzYqknmZ98qQv6rYrg5V-Hey-jAQp_KNf3h-vFHVZdP26Yg\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: api.alliedwallet.com\r\nContent-Length: 464\r\n\r\n" + <- "{\"siteId\":\"10118\",\"amount\":\"1.00\",\"trackingId\":\"82b5f6217fa19daa426e226a231d330a\",\"currency\":\"USD\",\"nameOnCard\":\"Longbob Longsen\",\"cardNumber\":\"4242424242424242\",\"cVVCode\":null,\"expirationYear\":\"2016\",\"expirationMonth\":\"09\",\"email\":\"jim_smith@example.com\",\"iPAddress\":\"127.0.0.1\",\"firstName\":\"Jim\",\"lastName\":\"Smith\",\"addressLine1\":\"456 My Street\",\"addressLine2\":\"Apt 1\",\"city\":\"Ottawa\",\"state\":\"ON\",\"countryId\":\"CA\",\"postalCode\":\"K1C2N6\",\"phone\":\"(555)555-5555\"}" + ) + end + + def nil_cvv_scrubbed_transcript + %( + <- "POST /merchants/10090/SALEtransactions HTTP/1.1\r\nContent-Type: application/json\r\nAuthorization: Bearer [FILTERED]\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: api.alliedwallet.com\r\nContent-Length: 464\r\n\r\n" + <- "{\"siteId\":\"10118\",\"amount\":\"1.00\",\"trackingId\":\"82b5f6217fa19daa426e226a231d330a\",\"currency\":\"USD\",\"nameOnCard\":\"Longbob Longsen\",\"cardNumber\":\"[FILTERED]\",\"cVVCode\":[BLANK],\"expirationYear\":\"2016\",\"expirationMonth\":\"09\",\"email\":\"jim_smith@example.com\",\"iPAddress\":\"127.0.0.1\",\"firstName\":\"Jim\",\"lastName\":\"Smith\",\"addressLine1\":\"456 My Street\",\"addressLine2\":\"Apt 1\",\"city\":\"Ottawa\",\"state\":\"ON\",\"countryId\":\"CA\",\"postalCode\":\"K1C2N6\",\"phone\":\"(555)555-5555\"}" + ) + end + + def empty_string_cvv_transcript + %( + <- "POST /merchants/10090/SALEtransactions HTTP/1.1\r\nContent-Type: application/json\r\nAuthorization: Bearer AAEAAHwXaLTYs2APKJW_4TpTDwCw_h9oDx9rA58FR78AFuYCX82Izes1nz9qGBXELGUN_EukKcP5T78Th5guDz4Rw5dQ4Gf0suKw7pz9vWrqa1NpZhrD9Lj9T-SFtOJgfodiwVaBBeSgbJLKA7MOzC9q2dv91HBNP69DygL1oX2L2mtt8fWlKSWhQmtG040E1I43jTueX3L3L9YA7iO6pIwO7CGybE5LnjkQ65KB2K4oYKfXRZosF77hgMJIh-KprFy9cYY3EjfupHeLon9im1BGafrda2N5wj_A_LvdMzfLAD1l1dgj82KlvM_gAzNJ4S19gAicRo9zIbsq36Apt-8jFjS0AQAAAAEAAA9Zr_lVLKMmmtKSo6T_9ulzMCRbYs798EpFD2wMlkb1NCQtA65VrNcM20Ka2FjNQfwOcSMWqDl9zFQhPyFl-npsG1Ww2oyyavA6HSe1HLRLtE_1hNBAlTBPQnLJ6hBf8eR_NTiVa-aQdV2l92-eSwCS59CzrOYGGCY1pLdNMDr_r66kg9l-l94154kRoMBRQSCqZV9iM9M-f3adLJqG6Q79zz1oJpGrH-Zv1kuv8eLaJJNOEFYARb0JbnAC5G1l9-aqxGvBrNkd4sAJIe23XrRx2XJCBIABxuGSQ1xJBTINVlXBXq1mvvd8B1uiYiDNia3c_vIGuSGIjZE0VbUN3oJppfCt1joGdePeUaC2Pyb2vuUN00EBEOaD9RF8IBWMLVJaF9cW2OewDOfBQg94MuOKLdXB_IisRx1ed25VQDVyv0f0CxmkAidvoDN0vvRIJZJr-bgBuL5FZM7gETAeYeiGlh7-Mf2Hzgy7236YNxcC9OnWFEcKEU50nlqog1bJnk8wJgoJWNqG0NUEK4DUzYqknmZ98qQv6rYrg5V-Hey-jAQp_KNf3h-vFHVZdP26Yg\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: api.alliedwallet.com\r\nContent-Length: 464\r\n\r\n" + <- "{\"siteId\":\"10118\",\"amount\":\"1.00\",\"trackingId\":\"82b5f6217fa19daa426e226a231d330a\",\"currency\":\"USD\",\"nameOnCard\":\"Longbob Longsen\",\"cardNumber\":\"4242424242424242\",\"cVVCode\":\"\",\"expirationYear\":\"2016\",\"expirationMonth\":\"09\",\"email\":\"jim_smith@example.com\",\"iPAddress\":\"127.0.0.1\",\"firstName\":\"Jim\",\"lastName\":\"Smith\",\"addressLine1\":\"456 My Street\",\"addressLine2\":\"Apt 1\",\"city\":\"Ottawa\",\"state\":\"ON\",\"countryId\":\"CA\",\"postalCode\":\"K1C2N6\",\"phone\":\"(555)555-5555\"}" + ) + end + + def empty_string_cvv_scrubbed_transcript + %( + <- "POST /merchants/10090/SALEtransactions HTTP/1.1\r\nContent-Type: application/json\r\nAuthorization: Bearer [FILTERED]\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: api.alliedwallet.com\r\nContent-Length: 464\r\n\r\n" + <- "{\"siteId\":\"10118\",\"amount\":\"1.00\",\"trackingId\":\"82b5f6217fa19daa426e226a231d330a\",\"currency\":\"USD\",\"nameOnCard\":\"Longbob Longsen\",\"cardNumber\":\"[FILTERED]\",\"cVVCode\":\"[BLANK]\",\"expirationYear\":\"2016\",\"expirationMonth\":\"09\",\"email\":\"jim_smith@example.com\",\"iPAddress\":\"127.0.0.1\",\"firstName\":\"Jim\",\"lastName\":\"Smith\",\"addressLine1\":\"456 My Street\",\"addressLine2\":\"Apt 1\",\"city\":\"Ottawa\",\"state\":\"ON\",\"countryId\":\"CA\",\"postalCode\":\"K1C2N6\",\"phone\":\"(555)555-5555\"}" + ) + end + + def whitespace_string_cvv_transcript + %( + <- "POST /merchants/10090/SALEtransactions HTTP/1.1\r\nContent-Type: application/json\r\nAuthorization: Bearer AAEAAHwXaLTYs2APKJW_4TpTDwCw_h9oDx9rA58FR78AFuYCX82Izes1nz9qGBXELGUN_EukKcP5T78Th5guDz4Rw5dQ4Gf0suKw7pz9vWrqa1NpZhrD9Lj9T-SFtOJgfodiwVaBBeSgbJLKA7MOzC9q2dv91HBNP69DygL1oX2L2mtt8fWlKSWhQmtG040E1I43jTueX3L3L9YA7iO6pIwO7CGybE5LnjkQ65KB2K4oYKfXRZosF77hgMJIh-KprFy9cYY3EjfupHeLon9im1BGafrda2N5wj_A_LvdMzfLAD1l1dgj82KlvM_gAzNJ4S19gAicRo9zIbsq36Apt-8jFjS0AQAAAAEAAA9Zr_lVLKMmmtKSo6T_9ulzMCRbYs798EpFD2wMlkb1NCQtA65VrNcM20Ka2FjNQfwOcSMWqDl9zFQhPyFl-npsG1Ww2oyyavA6HSe1HLRLtE_1hNBAlTBPQnLJ6hBf8eR_NTiVa-aQdV2l92-eSwCS59CzrOYGGCY1pLdNMDr_r66kg9l-l94154kRoMBRQSCqZV9iM9M-f3adLJqG6Q79zz1oJpGrH-Zv1kuv8eLaJJNOEFYARb0JbnAC5G1l9-aqxGvBrNkd4sAJIe23XrRx2XJCBIABxuGSQ1xJBTINVlXBXq1mvvd8B1uiYiDNia3c_vIGuSGIjZE0VbUN3oJppfCt1joGdePeUaC2Pyb2vuUN00EBEOaD9RF8IBWMLVJaF9cW2OewDOfBQg94MuOKLdXB_IisRx1ed25VQDVyv0f0CxmkAidvoDN0vvRIJZJr-bgBuL5FZM7gETAeYeiGlh7-Mf2Hzgy7236YNxcC9OnWFEcKEU50nlqog1bJnk8wJgoJWNqG0NUEK4DUzYqknmZ98qQv6rYrg5V-Hey-jAQp_KNf3h-vFHVZdP26Yg\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: api.alliedwallet.com\r\nContent-Length: 464\r\n\r\n" + <- "{\"siteId\":\"10118\",\"amount\":\"1.00\",\"trackingId\":\"82b5f6217fa19daa426e226a231d330a\",\"currency\":\"USD\",\"nameOnCard\":\"Longbob Longsen\",\"cardNumber\":\"4242424242424242\",\"cVVCode\":\" \",\"expirationYear\":\"2016\",\"expirationMonth\":\"09\",\"email\":\"jim_smith@example.com\",\"iPAddress\":\"127.0.0.1\",\"firstName\":\"Jim\",\"lastName\":\"Smith\",\"addressLine1\":\"456 My Street\",\"addressLine2\":\"Apt 1\",\"city\":\"Ottawa\",\"state\":\"ON\",\"countryId\":\"CA\",\"postalCode\":\"K1C2N6\",\"phone\":\"(555)555-5555\"}" + ) + end + + def whitespace_string_cvv_scrubbed_transcript + %( + <- "POST /merchants/10090/SALEtransactions HTTP/1.1\r\nContent-Type: application/json\r\nAuthorization: Bearer [FILTERED]\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: api.alliedwallet.com\r\nContent-Length: 464\r\n\r\n" + <- "{\"siteId\":\"10118\",\"amount\":\"1.00\",\"trackingId\":\"82b5f6217fa19daa426e226a231d330a\",\"currency\":\"USD\",\"nameOnCard\":\"Longbob Longsen\",\"cardNumber\":\"[FILTERED]\",\"cVVCode\":\"[BLANK]\",\"expirationYear\":\"2016\",\"expirationMonth\":\"09\",\"email\":\"jim_smith@example.com\",\"iPAddress\":\"127.0.0.1\",\"firstName\":\"Jim\",\"lastName\":\"Smith\",\"addressLine1\":\"456 My Street\",\"addressLine2\":\"Apt 1\",\"city\":\"Ottawa\",\"state\":\"ON\",\"countryId\":\"CA\",\"postalCode\":\"K1C2N6\",\"phone\":\"(555)555-5555\"}" + ) + end +end diff --git a/test/unit/gateways/authorize_net_arb_test.rb b/test/unit/gateways/authorize_net_arb_test.rb new file mode 100644 index 00000000000..5c18ff468ff --- /dev/null +++ b/test/unit/gateways/authorize_net_arb_test.rb @@ -0,0 +1,141 @@ +require 'test_helper' + +class AuthorizeNetArbTest < Test::Unit::TestCase + include CommStub + + def setup + ActiveMerchant.expects(:deprecated).with('ARB functionality in ActiveMerchant is deprecated and will be removed in a future version. Please contact the ActiveMerchant maintainers if you have an interest in taking ownership of a separate gem that continues support for it.') + @gateway = AuthorizeNetArbGateway.new( + :login => 'X', + :password => 'Y' + ) + @amount = 100 + @credit_card = credit_card + @subscription_id = '100748' + @subscription_status = 'active' + end + + def test_successful_recurring + @gateway.expects(:ssl_post).returns(successful_recurring_response) + + response = @gateway.recurring(@amount, @credit_card, + :billing_address => address.merge(:first_name => 'Jim', :last_name => 'Smith'), + :interval => { + :length => 10, + :unit => :days + }, + :duration => { + :start_date => Time.now.strftime('%Y-%m-%d'), + :occurrences => 30 + } + ) + + assert_instance_of Response, response + assert response.success? + assert response.test? + assert_equal @subscription_id, response.authorization + end + + def test_successful_update_recurring + @gateway.expects(:ssl_post).returns(successful_update_recurring_response) + + response = @gateway.update_recurring(:subscription_id => @subscription_id, :amount => @amount * 2) + + assert_instance_of Response, response + assert response.success? + assert response.test? + assert_equal @subscription_id, response.authorization + end + + def test_successful_cancel_recurring + @gateway.expects(:ssl_post).returns(successful_cancel_recurring_response) + + response = @gateway.cancel_recurring(@subscription_id) + + assert_instance_of Response, response + assert response.success? + assert response.test? + assert_equal @subscription_id, response.authorization + end + + def test_successful_status_recurring + @gateway.expects(:ssl_post).returns(successful_status_recurring_response) + + response = @gateway.status_recurring(@subscription_id) + assert_instance_of Response, response + assert response.success? + assert response.test? + assert_equal @subscription_status, response.params['status'] + end + + def test_expdate_formatting + assert_equal '2009-09', @gateway.send(:expdate, credit_card('4111111111111111', :month => '9', :year => '2009')) + assert_equal '2013-11', @gateway.send(:expdate, credit_card('4111111111111111', :month => '11', :year => '2013')) + end + + private + + def successful_recurring_response + <<-XML +<ARBCreateSubscriptionResponse xmlns="AnetApi/xml/v1/schema/AnetApiSchema.xsd"> + <refId>Sample</refId> + <messages> + <resultCode>Ok</resultCode> + <message> + <code>I00001</code> + <text>Successful.</text> + </message> + </messages> + <subscriptionId>#{@subscription_id}</subscriptionId> +</ARBCreateSubscriptionResponse> + XML + end + + def successful_update_recurring_response + <<-XML +<ARBUpdateSubscriptionResponse xmlns="AnetApi/xml/v1/schema/AnetApiSchema.xsd"> + <refId>Sample</refId> + <messages> + <resultCode>Ok</resultCode> + <message> + <code>I00001</code> + <text>Successful.</text> + </message> + </messages> + <subscriptionId>#{@subscription_id}</subscriptionId> +</ARBUpdateSubscriptionResponse> + XML + end + + def successful_cancel_recurring_response + <<-XML +<ARBCancelSubscriptionResponse xmlns="AnetApi/xml/v1/schema/AnetApiSchema.xsd"> + <refId>Sample</refId> + <messages> + <resultCode>Ok</resultCode> + <message> + <code>I00001</code> + <text>Successful.</text> + </message> + </messages> + <subscriptionId>#{@subscription_id}</subscriptionId> +</ARBCancelSubscriptionResponse> + XML + end + + def successful_status_recurring_response + <<-XML +<ARBGetSubscriptionStatusResponse xmlns="AnetApi/xml/v1/schema/AnetApiSchema.xsd"> + <refId>Sample</refId> + <messages> + <resultCode>Ok</resultCode> + <message> + <code>I00001</code> + <text>Successful.</text> + </message> + </messages> + <Status>#{@subscription_status}</Status> +</ARBGetSubscriptionStatusResponse> + XML + end +end diff --git a/test/unit/gateways/authorize_net_cim_test.rb b/test/unit/gateways/authorize_net_cim_test.rb index e138e383824..1d9bd462192 100644 --- a/test/unit/gateways/authorize_net_cim_test.rb +++ b/test/unit/gateways/authorize_net_cim_test.rb @@ -1,6 +1,8 @@ require 'test_helper' class AuthorizeNetCimTest < Test::Unit::TestCase + include CommStub + def setup @gateway = AuthorizeNetCimGateway.new( :login => 'X', @@ -44,8 +46,8 @@ def setup end def test_expdate_formatting - assert_equal '2009-09', @gateway.send(:expdate, credit_card('4111111111111111', :month => "9", :year => "2009")) - assert_equal '2013-11', @gateway.send(:expdate, credit_card('4111111111111111', :month => "11", :year => "2013")) + assert_equal '2009-09', @gateway.send(:expdate, credit_card('4111111111111111', :month => '9', :year => '2009')) + assert_equal '2013-11', @gateway.send(:expdate, credit_card('4111111111111111', :month => '11', :year => '2013')) assert_equal 'XXXX', @gateway.send(:expdate, credit_card('XXXX1234', :month => nil, :year => nil)) end @@ -56,7 +58,7 @@ def test_should_create_customer_profile_request assert_instance_of Response, response assert_success response assert_equal @customer_profile_id, response.authorization - assert_equal "Successful.", response.message + assert_equal 'Successful.', response.message end def test_should_create_customer_payment_profile_request @@ -74,7 +76,7 @@ def test_should_create_customer_payment_profile_request assert_instance_of Response, response assert_success response assert_equal @customer_payment_profile_id, response.params['customer_payment_profile_id'] - assert_equal "This output is only present if the ValidationMode input parameter is passed with a value of testMode or liveMode", response.params['validation_direct_response'] + assert_equal 'This output is only present if the ValidationMode input parameter is passed with a value of testMode or liveMode', response.params['validation_direct_response'] end def test_should_create_customer_shipping_address_request @@ -260,7 +262,7 @@ def test_should_create_customer_profile_transaction_auth_capture_request_for_ver assert_equal response.authorization, response.params['direct_response']['transaction_id'] assert_equal 'This transaction has been approved.', response.params['direct_response']['message'] assert_equal 'auth_capture', response.params['direct_response']['transaction_type'] - assert_equal 'CSYM0K', approval_code = response.params['direct_response']['approval_code'] + assert_equal 'CSYM0K', response.params['direct_response']['approval_code'] assert_equal '2163585627', response.params['direct_response']['transaction_id'] assert_equal '1', response.params['direct_response']['response_code'] @@ -366,12 +368,14 @@ def test_should_get_customer_payment_profile_request assert response = @gateway.get_customer_payment_profile( :customer_profile_id => @customer_profile_id, - :customer_payment_profile_id => @customer_payment_profile_id + :customer_payment_profile_id => @customer_payment_profile_id, + :unmask_expiration_date => true ) assert_instance_of Response, response assert_success response assert_nil response.authorization assert_equal @customer_payment_profile_id, response.params['profile']['payment_profiles']['customer_payment_profile_id'] + assert_equal formatted_expiration_date(@credit_card), response.params['profile']['payment_profiles']['payment']['credit_card']['expiration_date'] end def test_should_get_customer_shipping_address_request @@ -415,6 +419,30 @@ def test_should_update_customer_payment_profile_request assert_nil response.authorization end + def test_should_update_customer_payment_profile_request_with_last_four_digits + last_four_credit_card = ActiveMerchant::Billing::CreditCard.new(:number => '4242') # Credit card with only last four digits + + response = stub_comms do + @gateway.update_customer_payment_profile( + :customer_profile_id => @customer_profile_id, + :payment_profile => { + :customer_payment_profile_id => @customer_payment_profile_id, + :bill_to => address(:address1 => '345 Avenue B', + :address2 => 'Apt 101'), + :payment => { + :credit_card => last_four_credit_card + } + } + ) + end.check_request do |endpoint, data, headers| + assert_match %r{<cardNumber>XXXX4242</cardNumber>}, data + end.respond_with(successful_update_customer_payment_profile_response) + + assert_instance_of Response, response + assert_success response + assert_nil response.authorization + end + def test_should_update_customer_shipping_address_request @gateway.expects(:ssl_post).returns(successful_update_customer_shipping_address_response) @@ -481,6 +509,8 @@ def test_should_create_customer_profile_transaction_auth_capture_and_then_refund # http://www.modernbill.com/support/manual/old/v4/adminhelp/english/Configuration/Payment_Settings/Gateway_API/AuthorizeNet/Module_Authorize.net.htm assert_failure response assert_equal 'The referenced transaction does not meet the criteria for issuing a credit.', response.params['direct_response']['message'] + assert_equal 'The transaction was unsuccessful.', response.message + assert_equal 'E00027', response.error_code return response end @@ -545,8 +575,8 @@ def test_should_create_customer_profile_transaction_for_refund_request assert response = @gateway.create_customer_profile_transaction_for_refund( :transaction => { :trans_id => 1, - :amount => "1.00", - :credit_card_number_masked => "XXXX1234" + :amount => '1.00', + :credit_card_number_masked => 'XXXX1234' } ) assert_instance_of Response, response @@ -555,6 +585,57 @@ def test_should_create_customer_profile_transaction_for_refund_request assert_equal 'This transaction has been approved.', response.params['direct_response']['message'] end + def test_should_create_customer_profile_transaction_passing_recurring_flag + response = stub_comms do + @gateway.create_customer_profile_transaction( + :transaction => { + :customer_profile_id => @customer_profile_id, + :customer_payment_profile_id => @customer_payment_profile_id, + :type => :auth_capture, + :order => { + :invoice_number => '1234', + :description => 'Test Order Description', + :purchase_order_number => '4321' + }, + :amount => @amount, + :card_code => '123', + :recurring_billing => true + } + ) + end.check_request do |endpoint, data, headers| + assert_match %r{<recurringBilling>true</recurringBilling>}, data + end.respond_with(successful_create_customer_profile_transaction_response(:auth_capture)) + + assert_instance_of Response, response + assert_success response + assert_equal 'M', response.params['direct_response']['card_code'] # M => match + assert_equal response.authorization, response.params['direct_response']['transaction_id'] + assert_equal 'This transaction has been approved.', response.params['direct_response']['message'] + end + + def test_full_or_masked_card_number + assert_equal nil, @gateway.send(:full_or_masked_card_number, nil) + assert_equal '', @gateway.send(:full_or_masked_card_number, '') + assert_equal '4242424242424242', @gateway.send(:full_or_masked_card_number, @credit_card.number) + assert_equal 'XXXX1234', @gateway.send(:full_or_masked_card_number, '1234') + end + + def test_multiple_errors_when_creating_customer_profile + @gateway.expects(:ssl_post).returns(unsuccessful_create_customer_profile_transaction_response_with_multiple_errors(:refund)) + assert response = @gateway.create_customer_profile_transaction( + :transaction => { + :type => :refund, + :amount => 1, + + :customer_profile_id => @customer_profile_id, + :customer_payment_profile_id => @customer_payment_profile_id, + :trans_id => 1 + } + ) + assert_equal 'The transaction was unsuccessful.', response.message + assert_equal 'E00027', response.error_code + end + private def get_auth_only_response @@ -573,7 +654,7 @@ def get_auth_only_response assert_nil response.authorization assert_equal 'This transaction has been approved.', response.params['direct_response']['message'] assert_equal 'auth_only', response.params['direct_response']['transaction_type'] - assert_equal 'Gw4NGI', approval_code = response.params['direct_response']['approval_code'] + assert_equal 'Gw4NGI', response.params['direct_response']['approval_code'] return response end @@ -993,4 +1074,26 @@ def unsuccessful_create_customer_profile_transaction_response(transaction_type) XML end + def unsuccessful_create_customer_profile_transaction_response_with_multiple_errors(transaction_type) + <<-XML + <?xml version="1.0" encoding="utf-8"?> + <createCustomerProfileTransactionResponse + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xmlns:xsd="http://www.w3.org/2001/XMLSchema" + xmlns="AnetApi/xml/v1/schema/AnetApiSchema.xsd"> + <messages> + <resultCode>Error</resultCode> + <message> + <code>E00027</code> + <text>The transaction was unsuccessful.</text> + </message> + <message> + <code>E00001</code> + <text>An error occurred during processing. Please try again.</text> + </message> + </messages> + <directResponse>#{UNSUCCESSUL_DIRECT_RESPONSE[transaction_type]}</directResponse> + </createCustomerProfileTransactionResponse> + XML + end end diff --git a/test/unit/gateways/authorize_net_test.rb b/test/unit/gateways/authorize_net_test.rb index db70bb2abfe..9c444ad6b3a 100644 --- a/test/unit/gateways/authorize_net_test.rb +++ b/test/unit/gateways/authorize_net_test.rb @@ -3,213 +3,906 @@ class AuthorizeNetTest < Test::Unit::TestCase include CommStub + BAD_TRACK_DATA = '%B378282246310005LONGSONLONGBOB1705101130504392?' + TRACK1_DATA = '%B378282246310005^LONGSON/LONGBOB^1705101130504392?' + TRACK2_DATA = ';4111111111111111=1803101000020000831?' + def setup @gateway = AuthorizeNetGateway.new( - :login => 'X', - :password => 'Y' + login: 'X', + password: 'Y' ) + @amount = 100 @credit_card = credit_card - @subscription_id = '100748' - @subscription_status = 'active' @check = check + @apple_pay_payment_token = ActiveMerchant::Billing::ApplePayPaymentToken.new( + {data: 'encoded_payment_data'}, + payment_instrument_name: 'SomeBank Visa', + payment_network: 'Visa', + transaction_identifier: 'transaction123' + ) + + @options = { + order_id: '1', + billing_address: address, + description: 'Store Purchase' + } + + @level_3_options = { + ship_from_address: { + zip: 'origin27701', + country: 'originUS' + }, + summary_commodity_code: 'CODE' + } + + @additional_options = { + line_items: [ + { + item_id: '1', + name: 'mug', + description: 'coffee', + quantity: '100', + unit_price: '10' + }, + { + item_id: '2', + name: 'vase', + description: 'floral', + quantity: '200', + unit_price: '20' + } + ] + } + + @level_3_line_item_options = { + line_items: [ + { + item_id: '1', + name: 'mug', + description: 'coffee', + quantity: '100', + unit_price: '10', + unit_of_measure: 'yards', + total_amount: '1000', + product_code: 'coupon' + } + ] + } + end + + def test_add_swipe_data_with_bad_data + @credit_card.track_data = BAD_TRACK_DATA + stub_comms do + @gateway.purchase(@amount, @credit_card) + end.check_request do |endpoint, data, headers| + parse(data) do |doc| + assert_nil doc.at_xpath('//track1') + assert_nil doc.at_xpath('//track2') + assert_equal '1.00', doc.at_xpath('//transactionRequest/amount').content + end + end.respond_with(successful_purchase_response) + end + + def test_add_swipe_data_with_track_1 + @credit_card.track_data = TRACK1_DATA + stub_comms do + @gateway.purchase(@amount, @credit_card) + end.check_request do |endpoint, data, headers| + parse(data) do |doc| + assert_equal '%B378282246310005^LONGSON/LONGBOB^1705101130504392?', doc.at_xpath('//track1').content + assert_nil doc.at_xpath('//track2') + assert_equal '1.00', doc.at_xpath('//transactionRequest/amount').content + end + end.respond_with(successful_purchase_response) + end + + def test_add_swipe_data_with_track_2 + @credit_card.track_data = TRACK2_DATA + stub_comms do + @gateway.purchase(@amount, @credit_card) + end.check_request do |endpoint, data, headers| + parse(data) do |doc| + assert_nil doc.at_xpath('//track1') + assert_equal ';4111111111111111=1803101000020000831?', doc.at_xpath('//track2').content + assert_equal '1.00', doc.at_xpath('//transactionRequest/amount').content + end + end.respond_with(successful_purchase_response) + end + + def test_retail_market_type_device_type_included_in_swipe_transactions_with_valid_track_data + [BAD_TRACK_DATA, nil].each do |track| + @credit_card.track_data = track + stub_comms do + @gateway.purchase(@amount, @credit_card) + end.check_request do |endpoint, data, headers| + parse(data) do |doc| + assert_nil doc.at_xpath('//retail') + end + end.respond_with(successful_purchase_response) + end + + [TRACK1_DATA, TRACK2_DATA].each do |track| + @credit_card.track_data = track + stub_comms do + @gateway.purchase(@amount, @credit_card) + end.check_request do |endpoint, data, headers| + parse(data) do |doc| + assert_not_nil doc.at_xpath('//retail') + assert_equal '2', doc.at_xpath('//retail/marketType').content + assert_equal '7', doc.at_xpath('//retail/deviceType').content + end + end.respond_with(successful_purchase_response) + end + end + + def test_device_type_used_from_options_if_included_with_valid_track_data + [TRACK1_DATA, TRACK2_DATA].each do |track| + @credit_card.track_data = track + stub_comms do + @gateway.purchase(@amount, @credit_card, {device_type: 1}) + end.check_request do |endpoint, data, headers| + parse(data) do |doc| + assert_not_nil doc.at_xpath('//retail') + assert_equal '2', doc.at_xpath('//retail/marketType').content + assert_equal '1', doc.at_xpath('//retail/deviceType').content + end + end.respond_with(successful_purchase_response) + end + end + + def test_market_type_not_included_for_apple_pay_or_echeck + [@check, @apple_pay_payment_token].each do |payment| + stub_comms do + @gateway.purchase(@amount, payment) + end.check_request do |endpoint, data, headers| + parse(data) do |doc| + assert_nil doc.at_xpath('//retail') + end + end.respond_with(successful_purchase_response) + end + end + + def test_moto_market_type_included_when_card_is_entered_manually + @credit_card.manual_entry = true + stub_comms do + @gateway.purchase(@amount, @credit_card) + end.check_request do |endpoint, data, headers| + parse(data) do |doc| + assert_not_nil doc.at_xpath('//retail') + assert_equal '1', doc.at_xpath('//retail/marketType').content + end + end.respond_with(successful_purchase_response) + end + + def test_market_type_can_be_specified + stub_comms do + @gateway.purchase(@amount, @credit_card, market_type: 0) + end.check_request do |endpoint, data, headers| + parse(data) do |doc| + assert_equal '0', doc.at_xpath('//retail/marketType').content + end + end.respond_with(successful_purchase_response) end def test_successful_echeck_authorization response = stub_comms do @gateway.authorize(@amount, @check) end.check_request do |endpoint, data, headers| - assert_match(/x_method=ECHECK/, data) - assert_match(/x_bank_aba_code=244183602/, data) - assert_match(/x_bank_acct_num=15378535/, data) - assert_match(/x_bank_name=Bank\+of\+Elbonia/, data) - assert_match(/x_bank_acct_name=Jim\+Smith/, data) - assert_match(/x_echeck_type=WEB/, data) - assert_match(/x_bank_check_number=1/, data) - assert_match(/x_recurring_billing=FALSE/, data) - end.respond_with(successful_authorization_response) + parse(data) do |doc| + assert_not_nil doc.at_xpath('//payment/bankAccount') + assert_equal '244183602', doc.at_xpath('//routingNumber').content + assert_equal '15378535', doc.at_xpath('//accountNumber').content + assert_equal 'Bank of Elbonia', doc.at_xpath('//bankName').content + assert_equal 'Jim Smith', doc.at_xpath('//nameOnAccount').content + assert_equal '1', doc.at_xpath('//checkNumber').content + assert_equal '1.00', doc.at_xpath('//transactionRequest/amount').content + end + end.respond_with(successful_authorize_response) assert response assert_instance_of Response, response assert_success response - assert_equal '508141794', response.authorization + assert_equal '508141794', response.authorization.split('#')[0] end def test_successful_echeck_purchase response = stub_comms do @gateway.purchase(@amount, @check) end.check_request do |endpoint, data, headers| - assert_match(/x_method=ECHECK/, data) - assert_match(/x_bank_aba_code=244183602/, data) - assert_match(/x_bank_acct_num=15378535/, data) - assert_match(/x_bank_name=Bank\+of\+Elbonia/, data) - assert_match(/x_bank_acct_name=Jim\+Smith/, data) - assert_match(/x_echeck_type=WEB/, data) - assert_match(/x_bank_check_number=1/, data) - assert_match(/x_recurring_billing=FALSE/, data) + parse(data) do |doc| + assert_not_nil doc.at_xpath('//payment/bankAccount') + assert_equal '244183602', doc.at_xpath('//routingNumber').content + assert_equal '15378535', doc.at_xpath('//accountNumber').content + assert_equal 'Bank of Elbonia', doc.at_xpath('//bankName').content + assert_equal 'Jim Smith', doc.at_xpath('//nameOnAccount').content + assert_equal '1', doc.at_xpath('//checkNumber').content + assert_equal '1.00', doc.at_xpath('//transactionRequest/amount').content + end end.respond_with(successful_purchase_response) assert response assert_instance_of Response, response assert_success response - assert_equal '508141795', response.authorization + assert_equal '508141795', response.authorization.split('#')[0] end def test_echeck_passing_recurring_flag response = stub_comms do - @gateway.purchase(@amount, @check, :recurring => true) + @gateway.purchase(@amount, @check, recurring: true) end.check_request do |endpoint, data, headers| - assert_match(/x_recurring_billing=TRUE/, data) + assert_equal settings_from_doc(parse(data))['recurringBilling'], 'true' end.respond_with(successful_purchase_response) assert_success response end def test_failed_echeck_authorization - @gateway.expects(:ssl_post).returns(failed_authorization_response) + @gateway.expects(:ssl_post).returns(failed_authorize_response) - assert response = @gateway.authorize(@amount, @check) - assert_instance_of Response, response + response = @gateway.authorize(@amount, @check) assert_failure response - assert_equal '508141794', response.authorization end - def test_successful_authorization - @gateway.expects(:ssl_post).returns(successful_authorization_response) + def test_successful_apple_pay_authorization + response = stub_comms do + @gateway.authorize(@amount, @apple_pay_payment_token) + end.check_request do |endpoint, data, headers| + parse(data) do |doc| + assert_equal @gateway.class::APPLE_PAY_DATA_DESCRIPTOR, doc.at_xpath('//opaqueData/dataDescriptor').content + assert_equal Base64.strict_encode64(@apple_pay_payment_token.payment_data.to_json), doc.at_xpath('//opaqueData/dataValue').content + end + end.respond_with(successful_authorize_response) + + assert response + assert_instance_of Response, response + assert_success response + assert_equal '508141794', response.authorization.split('#')[0] + end - assert response = @gateway.authorize(@amount, @credit_card) + def test_successful_apple_pay_purchase + response = stub_comms do + @gateway.purchase(@amount, @apple_pay_payment_token) + end.check_request do |endpoint, data, headers| + parse(data) do |doc| + assert_equal @gateway.class::APPLE_PAY_DATA_DESCRIPTOR, doc.at_xpath('//opaqueData/dataDescriptor').content + assert_equal Base64.strict_encode64(@apple_pay_payment_token.payment_data.to_json), doc.at_xpath('//opaqueData/dataValue').content + end + end.respond_with(successful_purchase_response) + + assert response assert_instance_of Response, response assert_success response - assert_equal '508141794', response.authorization + assert_equal '508141795', response.authorization.split('#')[0] + end + + def test_successful_authorization + @gateway.expects(:ssl_post).returns(successful_authorize_response) + + response = @gateway.authorize(@amount, @credit_card) + assert_success response + + assert_equal 'M', response.cvv_result['code'] + assert_equal 'CVV matches', response.cvv_result['message'] + assert_equal 'I00001', response.params['full_response_code'] + + assert_equal '508141794', response.authorization.split('#')[0] + assert response.test? end def test_successful_purchase @gateway.expects(:ssl_post).returns(successful_purchase_response) - assert response = @gateway.purchase(@amount, @credit_card) - assert_instance_of Response, response + response = @gateway.purchase(@amount, @credit_card) + assert_success response + + assert_equal '508141795', response.authorization.split('#')[0] + assert response.test? + assert_equal 'Y', response.avs_result['code'] + assert response.avs_result['street_match'] + assert response.avs_result['postal_match'] + assert_equal 'Street address and 5-digit postal code match.', response.avs_result['message'] + assert_equal 'P', response.cvv_result['code'] + assert_equal 'CVV not processed', response.cvv_result['message'] + end + + def test_successful_purchase_with_utf_character + stub_comms do + @gateway.purchase(@amount, credit_card('4000100011112224', last_name: 'Wåhlin')) + end.check_request do |endpoint, data, headers| + assert_match(/Wåhlin/, data) + end.respond_with(successful_purchase_response) + end + + def test_passes_partial_auth + stub_comms do + @gateway.purchase(@amount, credit_card, disable_partial_auth: true) + end.check_request do |endpoint, data, headers| + assert_match(/<settingName>allowPartialAuth<\/settingName>/, data) + assert_match(/<settingValue>false<\/settingValue>/, data) + end.respond_with(successful_purchase_response) + end + + def test_passes_email_customer + stub_comms do + @gateway.purchase(@amount, credit_card, email_customer: true) + end.check_request do |endpoint, data, headers| + assert_match(/<settingName>emailCustomer<\/settingName>/, data) + assert_match(/<settingValue>true<\/settingValue>/, data) + end.respond_with(successful_purchase_response) + + stub_comms do + @gateway.purchase(@amount, credit_card, email_customer: false) + end.check_request do |endpoint, data, headers| + assert_match(/<settingName>emailCustomer<\/settingName>/, data) + assert_match(/<settingValue>false<\/settingValue>/, data) + end.respond_with(successful_purchase_response) + end + + def test_passes_header_email_receipt + stub_comms do + @gateway.purchase(@amount, credit_card, header_email_receipt: 'yet another field') + end.check_request do |endpoint, data, headers| + assert_match(/<settingName>headerEmailReceipt<\/settingName>/, data) + assert_match(/<settingValue>yet another field<\/settingValue>/, data) + end.respond_with(successful_purchase_response) + end + + def test_passes_level_3_options + stub_comms do + @gateway.purchase(@amount, credit_card, @options.merge(@level_3_options)) + end.check_request do |endpoint, data, headers| + assert_match(/<order>/, data) + assert_match(/<summaryCommodityCode>#{@level_3_options[:summary_commodity_code]}<\/summaryCommodityCode>/, data) + assert_match(/<\/order>/, data) + assert_match(/<shipFrom>/, data) + assert_match(/<zip>#{@level_3_options[:ship_from_address][:zip]}<\/zip>/, data) + assert_match(/<country>#{@level_3_options[:ship_from_address][:country]}<\/country>/, data) + assert_match(/<\/shipFrom>/, data) + end.respond_with(successful_purchase_response) + end + + def test_passes_line_items + stub_comms do + @gateway.purchase(@amount, credit_card, @options.merge(@additional_options)) + end.check_request do |endpoint, data, headers| + assert_match(/<lineItems>/, data) + assert_match(/<lineItem>/, data) + assert_match(/<itemId>#{@additional_options[:line_items][0][:item_id]}<\/itemId>/, data) + assert_match(/<name>#{@additional_options[:line_items][0][:name]}<\/name>/, data) + assert_match(/<description>#{@additional_options[:line_items][0][:description]}<\/description>/, data) + assert_match(/<quantity>#{@additional_options[:line_items][0][:quantity]}<\/quantity>/, data) + assert_match(/<unitPrice>#{@additional_options[:line_items][0][:unit_price]}<\/unitPrice>/, data) + assert_match(/<\/lineItem>/, data) + assert_match(/<itemId>#{@additional_options[:line_items][1][:item_id]}<\/itemId>/, data) + assert_match(/<name>#{@additional_options[:line_items][1][:name]}<\/name>/, data) + assert_match(/<description>#{@additional_options[:line_items][1][:description]}<\/description>/, data) + assert_match(/<quantity>#{@additional_options[:line_items][1][:quantity]}<\/quantity>/, data) + assert_match(/<unitPrice>#{@additional_options[:line_items][1][:unit_price]}<\/unitPrice>/, data) + assert_match(/<\/lineItems>/, data) + end.respond_with(successful_purchase_response) + end + + def test_passes_level_3_line_items + stub_comms do + @gateway.purchase(@amount, credit_card, @options.merge(@level_3_line_item_options)) + end.check_request do |endpoint, data, headers| + assert_match(/<lineItems>/, data) + assert_match(/<lineItem>/, data) + assert_match(/<itemId>#{@level_3_line_item_options[:line_items][0][:item_id]}<\/itemId>/, data) + assert_match(/<name>#{@level_3_line_item_options[:line_items][0][:name]}<\/name>/, data) + assert_match(/<description>#{@level_3_line_item_options[:line_items][0][:description]}<\/description>/, data) + assert_match(/<quantity>#{@level_3_line_item_options[:line_items][0][:quantity]}<\/quantity>/, data) + assert_match(/<unitPrice>#{@level_3_line_item_options[:line_items][0][:unit_price]}<\/unitPrice>/, data) + assert_match(/<unitOfMeasure>#{@level_3_line_item_options[:line_items][0][:unit_of_measure]}<\/unitOfMeasure>/, data) + assert_match(/<totalAmount>#{@level_3_line_item_options[:line_items][0][:total_amount]}<\/totalAmount>/, data) + assert_match(/<productCode>#{@level_3_line_item_options[:line_items][0][:product_code]}<\/productCode>/, data) + assert_match(/<\/lineItems>/, data) + end.respond_with(successful_purchase_response) + end + + def test_failed_purchase + @gateway.expects(:ssl_post).returns(failed_purchase_response) + + response = @gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_equal 'incorrect_number', response.error_code + end + + def test_live_gateway_cannot_use_test_mode_on_auth_dot_net_server + test_gateway = AuthorizeNetGateway.new( + login: 'X', + password: 'Y', + test: true + ) + test_gateway.stubs(:ssl_post).returns(successful_purchase_response_test_mode) + + response = test_gateway.purchase(@amount, @credit_card) + assert_success response + + real_gateway = AuthorizeNetGateway.new( + login: 'X', + password: 'Y', + test: false + ) + real_gateway.stubs(:ssl_post).returns(successful_purchase_response_test_mode) + response = real_gateway.purchase(@amount, @credit_card) + assert_failure response + assert_equal 'Using a live Authorize.net account in Test Mode is not permitted.', response.message + end + + def test_successful_purchase_using_stored_card + @gateway.expects(:ssl_post).returns(successful_store_response) + store = @gateway.store(@credit_card, @options) + assert_success store + + @gateway.expects(:ssl_post).returns(successful_purchase_using_stored_card_response) + + response = @gateway.purchase(@amount, store.authorization) + assert_success response + + assert_equal '2235700270#XXXX2224#cim_purchase', response.authorization + assert_equal 'Y', response.avs_result['code'] + assert response.avs_result['street_match'] + assert response.avs_result['postal_match'] + assert_equal 'Street address and 5-digit postal code match.', response.avs_result['message'] + end + + def test_successful_purchase_using_stored_card_and_custom_delimiter + @gateway.expects(:ssl_post).returns(successful_store_response) + store = @gateway.store(@credit_card, @options) + assert_success store + + @gateway.expects(:ssl_post).returns(successful_purchase_using_stored_card_response_with_pipe_delimiter) + + response = @gateway.purchase(@amount, store.authorization, {delimiter: '|', description: 'description, with, commas'}) assert_success response - assert_equal '508141795', response.authorization + + assert_equal '2235700270#XXXX2224#cim_purchase', response.authorization + assert_equal 'Y', response.avs_result['code'] + assert response.avs_result['street_match'] + assert response.avs_result['postal_match'] + assert_equal 'Street address and 5-digit postal code match.', response.avs_result['message'] + assert_equal 'description, with, commas', response.params['order_description'] + end + + def test_failed_purchase_using_stored_card + @gateway.expects(:ssl_post).returns(successful_store_response) + store = @gateway.store(@credit_card, @options) + assert_success store + + @gateway.expects(:ssl_post).returns(failed_purchase_using_stored_card_response) + + response = @gateway.purchase(@amount, store.authorization) + assert_failure response + assert_equal 'The credit card number is invalid.', response.message + assert_equal '6', response.params['response_reason_code'] end - def test_failed_authorization - @gateway.expects(:ssl_post).returns(failed_authorization_response) + def test_failed_authorize + @gateway.expects(:ssl_post).returns(failed_authorize_response) - assert response = @gateway.authorize(@amount, @credit_card) + response = @gateway.authorize(@amount, @credit_card, @options) + assert_failure response + assert_equal 'incorrect_number', response.error_code + end + + def test_successful_authorize_and_capture_using_stored_card + @gateway.expects(:ssl_post).returns(successful_store_response) + store = @gateway.store(@credit_card, @options) + + @gateway.expects(:ssl_post).returns(successful_authorize_using_stored_card_response) + auth = @gateway.authorize(@amount, store.authorization) + assert_success auth + assert_equal 'This transaction has been approved.', auth.message + + @gateway.expects(:ssl_post).returns(successful_capture_using_stored_card_response) + + capture = @gateway.capture(@amount, auth.authorization) + assert_success capture + assert_equal 'This transaction has been approved.', capture.message + end + + def test_failed_authorize_using_stored_card + @gateway.expects(:ssl_post).returns(successful_store_response) + store = @gateway.store(@credit_card, @options) + + @gateway.expects(:ssl_post).returns(failed_authorize_using_stored_card_response) + response = @gateway.authorize(@amount, store.authorization) + assert_failure response + assert_equal 'The credit card number is invalid.', response.message + assert_equal '6', response.params['response_reason_code'] + end + + def test_successful_capture + @gateway.expects(:ssl_post).returns(successful_capture_response) + + capture = @gateway.capture(@amount, '2214269051#XXXX1234', @options) + assert_success capture + assert_equal nil, capture.error_code + end + + def test_failed_capture + @gateway.expects(:ssl_post).returns(failed_capture_response) + + assert capture = @gateway.capture(@amount, '2214269051#XXXX1234') + assert_failure capture + end + + def test_failed_already_actioned_capture + @gateway.expects(:ssl_post).returns(already_actioned_capture_response) + + response = @gateway.capture(50, '123456789') assert_instance_of Response, response assert_failure response - assert_equal '508141794', response.authorization end - def test_add_address_outsite_north_america - result = {} + def test_failed_capture_using_stored_card + @gateway.expects(:ssl_post).returns(successful_store_response) + store = @gateway.store(@credit_card, @options) - @gateway.send(:add_address, result, :billing_address => {:address1 => '164 Waverley Street', :country => 'DE', :state => ''} ) + @gateway.expects(:ssl_post).returns(successful_authorize_using_stored_card_response) + auth = @gateway.authorize(@amount, store.authorization) - assert_equal ["address", "city", "company", "country", "phone", "state", "zip"], result.stringify_keys.keys.sort - assert_equal 'n/a', result[:state] - assert_equal '164 Waverley Street', result[:address] - assert_equal 'DE', result[:country] + @gateway.expects(:ssl_post).returns(failed_capture_using_stored_card_response) + capture = @gateway.capture(@amount, auth.authorization) + assert_failure capture + assert_match(/The amount requested for settlement cannot be greater/, capture.message) end - def test_add_address - result = {} + def test_successful_void + @gateway.expects(:ssl_post).returns(successful_void_response) - @gateway.send(:add_address, result, :billing_address => {:address1 => '164 Waverley Street', :country => 'US', :state => 'CO'} ) + assert void = @gateway.void('') + assert_success void + end - assert_equal ["address", "city", "company", "country", "phone", "state", "zip"], result.stringify_keys.keys.sort - assert_equal 'CO', result[:state] - assert_equal '164 Waverley Street', result[:address] - assert_equal 'US', result[:country] + def test_failed_void + @gateway.expects(:ssl_post).returns(failed_void_response) + response = @gateway.void('') + assert_failure response end - def test_add_invoice - result = {} - @gateway.send(:add_invoice, result, :order_id => '#1001') - assert_equal '#1001', result[:invoice_num] + def test_successful_void_using_stored_card + @gateway.expects(:ssl_post).returns(successful_store_response) + store = @gateway.store(@credit_card, @options) + + @gateway.expects(:ssl_post).returns(successful_authorize_using_stored_card_response) + auth = @gateway.authorize(@amount, store.authorization) + + @gateway.expects(:ssl_post).returns(successful_void_using_stored_card_response) + void = @gateway.void(auth.authorization) + assert_success void + assert_equal 'This transaction has been approved.', void.message end - def test_add_description - result = {} - @gateway.send(:add_invoice, result, :description => 'My Purchase is great') - assert_equal 'My Purchase is great', result[:description] + def test_failed_void_using_stored_card + @gateway.expects(:ssl_post).returns(successful_store_response) + store = @gateway.store(@credit_card, @options) + + @gateway.expects(:ssl_post).returns(failed_authorize_using_stored_card_response) + auth = @gateway.authorize(@amount, store.authorization) + + @gateway.expects(:ssl_post).returns(failed_void_using_stored_card_response) + void = @gateway.void(auth.authorization) + assert_failure void + assert_equal 'This transaction has already been voided.', void.message end - def test_add_duplicate_window_without_duplicate_window - result = {} - @gateway.class.duplicate_window = nil - @gateway.send(:add_duplicate_window, result) - assert_nil result[:duplicate_window] + def test_successful_verify + response = stub_comms do + @gateway.verify(@credit_card) + end.respond_with(successful_authorize_response, successful_void_response) + assert_success response end - def test_add_duplicate_window_with_duplicate_window - result = {} - @gateway.class.duplicate_window = 0 - @gateway.send(:add_duplicate_window, result) - assert_equal 0, result[:duplicate_window] + def test_successful_verify_failed_void + response = stub_comms do + @gateway.verify(@credit_card, @options) + end.respond_with(successful_authorize_response, failed_void_response) + assert_success response + assert_match %r{This transaction has been approved}, response.message end - def test_purchase_is_valid_csv - params = { :amount => '1.01' } + def test_unsuccessful_verify + response = stub_comms do + @gateway.verify(@credit_card, @options) + end.respond_with(failed_authorize_response, successful_void_response) + assert_failure response + assert_not_nil response.message + end + + def test_failed_refund_using_stored_card + @gateway.expects(:ssl_post).returns(successful_store_response) + store = @gateway.store(@credit_card, @options) + assert_success store + + @gateway.expects(:ssl_post).returns(successful_purchase_using_stored_card_response) - @gateway.send(:add_creditcard, params, @credit_card) + purchase = @gateway.purchase(@amount, store.authorization) + assert_success purchase - assert data = @gateway.send(:post_data, 'AUTH_ONLY', params) - assert_equal post_data_fixture.size, data.size + @gateway.expects(:ssl_post).returns(failed_refund_using_stored_card_response) + refund = @gateway.refund(@amount, purchase.authorization) + assert_failure refund + assert_equal 'The record cannot be found', refund.message end - def test_purchase_meets_minimum_requirements - params = { - :amount => "1.01", - } + def test_failed_refund_due_to_unsettled_payment + @gateway.expects(:ssl_post).returns(failed_refund_for_unsettled_payment_response) + @gateway.expects(:void).never - @gateway.send(:add_creditcard, params, @credit_card) + @gateway.refund(36.40, '2214269051#XXXX1234') + end - assert data = @gateway.send(:post_data, 'AUTH_ONLY', params) - minimum_requirements.each do |key| - assert_not_nil(data =~ /x_#{key}=/) - end + def test_failed_full_refund_due_to_unsettled_payment_forces_void + @gateway.expects(:ssl_post).returns(failed_refund_for_unsettled_payment_response) + @gateway.expects(:void).once + + @gateway.refund(36.40, '2214269051#XXXX1234', force_full_refund_if_unsettled: true) + end + + def test_failed_full_refund_returns_failed_response_if_reason_code_is_not_unsettled_error + @gateway.expects(:ssl_post).returns(failed_refund_response) + @gateway.expects(:void).never + + response = @gateway.refund(36.40, '2214269051#XXXX1234', force_full_refund_if_unsettled: true) + assert response.present? + assert_failure response + end + + def test_successful_store + @gateway.expects(:ssl_post).returns(successful_store_response) + + store = @gateway.store(@credit_card, @options) + assert_success store + assert_equal 'Successful', store.message + assert_equal '35959426', store.params['customer_profile_id'] + assert_equal '32506918', store.params['customer_payment_profile_id'] + end + + def test_failed_store + @gateway.expects(:ssl_post).returns(failed_store_response) + + store = @gateway.store(@credit_card, @options) + assert_failure store + assert_match(/The field length is invalid/, store.message) + assert_equal('15', store.params['message_code']) + end + + def test_successful_unstore + response = stub_comms do + @gateway.unstore('35959426#32506918#cim_store') + end.check_request do |endpoint, data, headers| + doc = parse(data) + assert_equal '35959426', doc.at_xpath('//deleteCustomerProfileRequest/customerProfileId').content + end.respond_with(successful_unstore_response) + + assert_success response + assert_equal 'Successful', response.message + end + + def test_failed_unstore + @gateway.expects(:ssl_post).returns(failed_unstore_response) + + unstore = @gateway.unstore('35959426#32506918#cim_store') + assert_failure unstore + assert_match(/The record cannot be found/, unstore.message) + assert_equal('40', unstore.params['message_code']) + end + + def test_successful_store_new_payment_profile + @gateway.expects(:ssl_post).returns(successful_store_new_payment_profile_response) + + store = @gateway.store(@credit_card, @options) + assert_success store + assert_equal 'Successful', store.message + assert_equal '38392170', store.params['customer_profile_id'] + assert_equal '34896759', store.params['customer_payment_profile_id'] + end + + def test_failed_store_new_payment_profile + @gateway.expects(:ssl_post).returns(failed_store_new_payment_profile_response) + + store = @gateway.store(@credit_card, @options) + assert_failure store + assert_equal 'A duplicate customer payment profile already exists', store.message + assert_equal '38392767', store.params['customer_profile_id'] + assert_equal '34897359', store.params['customer_payment_profile_id'] end - def test_action_included_in_params - @gateway.expects(:ssl_post).returns(successful_purchase_response) + def test_address + stub_comms do + @gateway.authorize(@amount, @credit_card, billing_address: {address1: '164 Waverley Street', country: 'US', state: 'CO', phone: '(555)555-5555', fax: '(555)555-4444'}) + end.check_request do |endpoint, data, headers| + parse(data) do |doc| + assert_equal 'CO', doc.at_xpath('//billTo/state').content, data + assert_equal '164 Waverley Street', doc.at_xpath('//billTo/address').content, data + assert_equal 'US', doc.at_xpath('//billTo/country').content, data + assert_equal '(555)555-5555', doc.at_xpath('//billTo/phoneNumber').content + assert_equal '(555)555-4444', doc.at_xpath('//billTo/faxNumber').content + end + end.respond_with(successful_authorize_response) + end - response = @gateway.capture(50, '123456789') - assert_equal('PRIOR_AUTH_CAPTURE', response.params['action'] ) + def test_address_with_empty_billing_address + stub_comms do + @gateway.authorize(@amount, @credit_card) + end.check_request do |endpoint, data, headers| + parse(data) do |doc| + assert_equal '', doc.at_xpath('//billTo/address').content, data + assert_equal '', doc.at_xpath('//billTo/city').content, data + assert_equal 'n/a', doc.at_xpath('//billTo/state').content, data + assert_equal '', doc.at_xpath('//billTo/zip').content, data + assert_equal '', doc.at_xpath('//billTo/country').content, data + end + end.respond_with(successful_authorize_response) end - def test_authorization_code_included_in_params - @gateway.expects(:ssl_post).returns(successful_purchase_response) + def test_address_with_address2_present + stub_comms do + @gateway.authorize(@amount, @credit_card, billing_address: {address1: '164 Waverley Street', address2: 'Apt 1234', country: 'US', state: 'CO', phone: '(555)555-5555', fax: '(555)555-4444'}) + end.check_request do |endpoint, data, headers| + parse(data) do |doc| + assert_equal 'CO', doc.at_xpath('//billTo/state').content, data + assert_equal '164 Waverley Street Apt 1234', doc.at_xpath('//billTo/address').content, data + assert_equal 'US', doc.at_xpath('//billTo/country').content, data + assert_equal '(555)555-5555', doc.at_xpath('//billTo/phoneNumber').content + assert_equal '(555)555-4444', doc.at_xpath('//billTo/faxNumber').content + end + end.respond_with(successful_authorize_response) + end - response = @gateway.capture(50, '123456789') - assert_equal('d1GENk', response.params['authorization_code'] ) + def test_address_north_america_with_defaults + stub_comms do + @gateway.authorize(@amount, @credit_card, billing_address: {address1: '164 Waverley Street', country: 'US'}) + end.check_request do |endpoint, data, headers| + parse(data) do |doc| + assert_equal 'NC', doc.at_xpath('//billTo/state').content, data + assert_equal '164 Waverley Street', doc.at_xpath('//billTo/address').content, data + assert_equal 'US', doc.at_xpath('//billTo/country').content, data + end + end.respond_with(successful_authorize_response) + end + + def test_address_outsite_north_america + stub_comms do + @gateway.authorize(@amount, @credit_card, billing_address: {address1: '164 Waverley Street', country: 'DE'}) + end.check_request do |endpoint, data, headers| + parse(data) do |doc| + assert_equal 'n/a', doc.at_xpath('//billTo/state').content, data + assert_equal '164 Waverley Street', doc.at_xpath('//billTo/address').content, data + assert_equal 'DE', doc.at_xpath('//billTo/country').content, data + end + end.respond_with(successful_authorize_response) + end + + def test_address_outsite_north_america_with_address2_present + stub_comms do + @gateway.authorize(@amount, @credit_card, billing_address: {address1: '164 Waverley Street', address2: 'Apt 1234', country: 'DE'}) + end.check_request do |endpoint, data, headers| + parse(data) do |doc| + assert_equal 'n/a', doc.at_xpath('//billTo/state').content, data + assert_equal '164 Waverley Street Apt 1234', doc.at_xpath('//billTo/address').content, data + assert_equal 'DE', doc.at_xpath('//billTo/country').content, data + end + end.respond_with(successful_authorize_response) + end + + def test_duplicate_window + stub_comms do + @gateway.purchase(@amount, @credit_card, duplicate_window: 0) + end.check_request do |endpoint, data, headers| + assert_equal settings_from_doc(parse(data))['duplicateWindow'], '0' + end.respond_with(successful_purchase_response) + end + + def test_duplicate_window_class_attribute_deprecated + @gateway.class.duplicate_window = 0 + assert_deprecation_warning('Using the duplicate_window class_attribute is deprecated. Use the transaction options hash instead.') do + stub_comms do + @gateway.purchase(@amount, @credit_card) + end.respond_with(successful_purchase_response) + end + ensure + @gateway.class.duplicate_window = nil + end + + def test_add_cardholder_authentication_value + stub_comms do + @gateway.purchase(@amount, @credit_card, cardholder_authentication_value: 'E0Mvq8AAABEiMwARIjNEVWZ3iJk=', authentication_indicator: '2') + end.check_request do |endpoint, data, headers| + parse(data) do |doc| + assert_equal 'E0Mvq8AAABEiMwARIjNEVWZ3iJk=', doc.at_xpath('//cardholderAuthentication/cardholderAuthenticationValue').content + assert_equal '2', doc.at_xpath('//cardholderAuthentication/authenticationIndicator').content + assert_equal '1.00', doc.at_xpath('//transactionRequest/amount').content + end + end.respond_with(successful_purchase_response) + end + + def test_alternative_three_d_secure_options + three_d_secure_opts = { cavv: 'E0Mvq8AAABEiMwARIjNEVWZ3iJk=', eci: '2' } + stub_comms do + @gateway.purchase(@amount, @credit_card, three_d_secure: three_d_secure_opts) + end.check_request do |endpoint, data, headers| + parse(data) do |doc| + assert_equal 'E0Mvq8AAABEiMwARIjNEVWZ3iJk=', doc.at_xpath('//cardholderAuthentication/cardholderAuthenticationValue').content + assert_equal '2', doc.at_xpath('//cardholderAuthentication/authenticationIndicator').content + assert_equal '1.00', doc.at_xpath('//transactionRequest/amount').content + end + end.respond_with(successful_purchase_response) + end + + def test_prioritize_authentication_value_params + three_d_secure_opts = { cavv: 'fake', eci: 'fake' } + stub_comms do + @gateway.purchase( + @amount, + @credit_card, + cardholder_authentication_value: 'E0Mvq8AAABEiMwARIjNEVWZ3iJk=', + authentication_indicator: '2', + three_d_secure: three_d_secure_opts + ) + end.check_request do |endpoint, data, headers| + parse(data) do |doc| + assert_equal 'E0Mvq8AAABEiMwARIjNEVWZ3iJk=', doc.at_xpath('//cardholderAuthentication/cardholderAuthenticationValue').content + assert_equal '2', doc.at_xpath('//cardholderAuthentication/authenticationIndicator').content + assert_equal '1.00', doc.at_xpath('//transactionRequest/amount').content + end + end.respond_with(successful_purchase_response) end def test_capture_passing_extra_info response = stub_comms do - @gateway.capture(50, '123456789', :description => "Yo", :order_id => "Sweetness") + @gateway.capture(50, '123456789', description: 'Yo', order_id: 'Sweetness') end.check_request do |endpoint, data, headers| - assert_match(/x_description=Yo/, data) - assert_match(/x_invoice_num=Sweetness/, data) + parse(data) do |doc| + assert_not_nil doc.at_xpath('//order/description'), data + assert_equal 'Yo', doc.at_xpath('//order/description').content, data + assert_equal 'Sweetness', doc.at_xpath('//order/invoiceNumber').content, data + assert_equal '0.50', doc.at_xpath('//transactionRequest/amount').content + end end.respond_with(successful_capture_response) assert_success response end def test_successful_refund - @gateway.expects(:ssl_post).returns(successful_purchase_response) - assert response = @gateway.refund(@amount, '123456789', :card_number => @credit_card.number) + @gateway.expects(:ssl_post).returns(successful_refund_response) + + assert refund = @gateway.refund(36.40, '2214269051#XXXX1234') + assert_success refund + assert_equal 'This transaction has been approved', refund.message + assert_equal '2214602071#2224#refund', refund.authorization + end + + def test_successful_bank_refund + response = stub_comms do + @gateway.refund(50, '12345667', account_type: 'checking', routing_number: '123450987', account_number: '12345667', first_name: 'Louise', last_name: 'Belcher') + end.check_request do |endpoint, data, headers| + parse(data) do |doc| + assert_equal 'checking', doc.at_xpath('//transactionRequest/payment/bankAccount/accountType').content + assert_equal '123450987', doc.at_xpath('//transactionRequest/payment/bankAccount/routingNumber').content + assert_equal '12345667', doc.at_xpath('//transactionRequest/payment/bankAccount/accountNumber').content + assert_equal 'Louise Belcher', doc.at_xpath('//transactionRequest/payment/bankAccount/nameOnAccount').content + end + end.respond_with(successful_refund_response) assert_success response - assert_equal 'This transaction has been approved', response.message end def test_refund_passing_extra_info response = stub_comms do - @gateway.refund(50, '123456789', :card_number => @credit_card.number, :first_name => "Bob", :last_name => "Smith", :zip => "12345") + @gateway.refund(50, '123456789', card_number: @credit_card.number, first_name: 'Bob', last_name: 'Smith', zip: '12345', order_id: '1', description: 'Refund for order 1') end.check_request do |endpoint, data, headers| - assert_match(/x_first_name=Bob/, data) - assert_match(/x_last_name=Smith/, data) - assert_match(/x_zip=12345/, data) + parse(data) do |doc| + assert_equal 'Bob', doc.at_xpath('//billTo/firstName').content, data + assert_equal 'Smith', doc.at_xpath('//billTo/lastName').content, data + assert_equal '12345', doc.at_xpath('//billTo/zip').content, data + assert_equal '0.50', doc.at_xpath('//transactionRequest/amount').content + assert_equal '1', doc.at_xpath('//transactionRequest/order/invoiceNumber').content + assert_equal 'Refund for order 1', doc.at_xpath('//transactionRequest/order/description').content + end end.respond_with(successful_purchase_response) assert_success response end @@ -217,41 +910,52 @@ def test_refund_passing_extra_info def test_failed_refund @gateway.expects(:ssl_post).returns(failed_refund_response) - assert response = @gateway.refund(@amount, '123456789', :card_number => @credit_card.number) - assert_failure response - assert_equal 'The referenced transaction does not meet the criteria for issuing a credit', response.message + refund = @gateway.refund(nil, '') + assert_failure refund + assert_equal 'The sum of credits against the referenced transaction would exceed original debit amount', refund.message + assert_equal '0#2224#refund', refund.authorization end - def test_deprecated_credit - @gateway.expects(:ssl_post).returns(successful_purchase_response) - assert_deprecation_warning(Gateway::CREDIT_DEPRECATION_MESSAGE, @gateway) do - assert response = @gateway.credit(@amount, '123456789', :card_number => @credit_card.number) - assert_success response - assert_equal 'This transaction has been approved', response.message - end + def test_successful_credit + @gateway.expects(:ssl_post).returns(successful_credit_response) + + response = @gateway.credit(@amount, @credit_card) + assert_success response + + assert_equal '2230004436', response.authorization.split('#')[0] + assert_equal 'This transaction has been approved', response.message + end + + def test_failed_credit + @gateway.expects(:ssl_post).returns(failed_credit_response) + + response = @gateway.credit(@amount, @credit_card) + assert_failure response + assert_equal 'The credit card number is invalid', response.message end def test_supported_countries - assert_equal ['US', 'CA', 'GB'], AuthorizeNetGateway.supported_countries + assert_equal 4, (['US', 'CA', 'AU', 'VA'] & AuthorizeNetGateway.supported_countries).size end def test_supported_card_types - assert_equal [:visa, :master, :american_express, :discover, :diners_club, :jcb], AuthorizeNetGateway.supported_cardtypes + assert_equal [:visa, :master, :american_express, :discover, :diners_club, :jcb, :maestro], AuthorizeNetGateway.supported_cardtypes end def test_failure_without_response_reason_text - assert_nothing_raised do - assert_equal '', @gateway.send(:message_from, {}) - end + response = stub_comms do + @gateway.purchase(@amount, @credit_card) + end.respond_with(no_message_response) + assert_equal '', response.message end def test_response_under_review_by_fraud_service @gateway.expects(:ssl_post).returns(fraud_review_response) response = @gateway.purchase(@amount, @credit_card) - assert_failure response + assert_success response assert response.fraud_review? - assert_equal "Thank you! For security reasons your order is currently being reviewed", response.message + assert_equal 'Thank you! For security reasons your order is currently being reviewed', response.message end def test_avs_result @@ -259,6 +963,15 @@ def test_avs_result response = @gateway.purchase(@amount, @credit_card) assert_equal 'X', response.avs_result['code'] + assert_equal 'Y', response.avs_result['street_match'] + assert_equal 'Y', response.avs_result['postal_match'] + + @gateway.expects(:ssl_post).returns(address_not_provided_avs_response) + + response = @gateway.purchase(@amount, @credit_card) + assert_equal 'I', response.avs_result['code'] + assert_equal nil, response.avs_result['street_match'] + assert_equal nil, response.avs_result['postal_match'] end def test_cvv_result @@ -268,223 +981,1482 @@ def test_cvv_result assert_equal 'M', response.cvv_result['code'] end - def test_message_from - @gateway.class_eval { - public :message_from - } - result = { - :response_code => 2, - :card_code => 'N', - :avs_result_code => 'A', - :response_reason_code => '27', - :response_reason_text => 'Failure.', - } - assert_equal "No Match", @gateway.message_from(result) + def test_message + response = stub_comms do + @gateway.purchase(@amount, @credit_card) + end.respond_with(no_match_cvv_response) + assert_equal 'CVV does not match', response.message + + response = stub_comms do + @gateway.purchase(@amount, @credit_card) + end.respond_with(no_match_avs_response) + assert_equal 'Street address matches, but postal code does not match.', response.message - result[:card_code] = 'M' - assert_equal "Street address matches, but 5-digit and 9-digit postal code do not match.", @gateway.message_from(result) + response = stub_comms do + @gateway.purchase(@amount, @credit_card) + end.respond_with(failed_purchase_response) + assert_equal 'The credit card number is invalid', response.message + end - result[:response_reason_code] = '22' - assert_equal "Failure", @gateway.message_from(result) + def test_solution_id_is_added_to_post_data_parameters + @gateway.class.application_id = 'A1000000' + stub_comms do + @gateway.authorize(@amount, @credit_card) + end.check_request do |endpoint, data, headers| + doc = parse(data) + assert_equal 'A1000000', fields_from_doc(doc)['x_solution_id'], data + assert_equal '1.00', doc.at_xpath('//transactionRequest/amount').content + end.respond_with(successful_authorize_response) + ensure + @gateway.class.application_id = nil end - # ARB Unit Tests + def test_alternate_currency + @gateway.expects(:ssl_post).returns(successful_purchase_response) - def test_successful_recurring - @gateway.expects(:ssl_post).returns(successful_recurring_response) + response = @gateway.purchase(@amount, @credit_card, currency: 'GBP') + assert_success response + end - response = @gateway.recurring(@amount, @credit_card, - :billing_address => address.merge(:first_name => 'Jim', :last_name => 'Smith'), - :interval => { - :length => 10, - :unit => :days - }, - :duration => { - :start_date => Time.now.strftime("%Y-%m-%d"), - :occurrences => 30 - } - ) + def assert_no_has_customer_id(data) + assert_no_match %r{x_cust_id}, data + end - assert_instance_of Response, response - assert response.success? - assert response.test? - assert_equal @subscription_id, response.authorization + def test_include_cust_id_for_numeric_values + stub_comms do + @gateway.purchase(@amount, @credit_card, customer: '123') + end.check_request do |endpoint, data, headers| + parse(data) do |doc| + assert_not_nil doc.at_xpath('//customer/id'), data + assert_equal '123', doc.at_xpath('//customer/id').content, data + assert_equal '1.00', doc.at_xpath('//transactionRequest/amount').content + end + end.respond_with(successful_authorize_response) end - def test_successful_update_recurring - @gateway.expects(:ssl_post).returns(successful_update_recurring_response) + def test_include_cust_id_for_word_character_values + stub_comms do + @gateway.purchase(@amount, @credit_card, customer: '4840_TT') + end.check_request do |endpoint, data, headers| + parse(data) do |doc| + assert_not_nil doc.at_xpath('//customer/id'), data + assert_equal '4840_TT', doc.at_xpath('//customer/id').content, data + assert_equal '1.00', doc.at_xpath('//transactionRequest/amount').content + end + end.respond_with(successful_authorize_response) + end - response = @gateway.update_recurring(:subscription_id => @subscription_id, :amount => @amount * 2) + def test_dont_include_cust_id_for_email_addresses + stub_comms do + @gateway.purchase(@amount, @credit_card, customer: 'bob@test.com') + end.check_request do |endpoint, data, headers| + doc = parse(data) + assert !doc.at_xpath('//customer/id'), data + assert_equal '1.00', doc.at_xpath('//transactionRequest/amount').content + end.respond_with(successful_authorize_response) + end - assert_instance_of Response, response - assert response.success? - assert response.test? - assert_equal @subscription_id, response.authorization + def test_dont_include_cust_id_for_phone_numbers + stub_comms do + @gateway.purchase(@amount, @credit_card, customer: '111-123-1231') + end.check_request do |endpoint, data, headers| + doc = parse(data) + assert !doc.at_xpath('//customer/id'), data + assert_equal '1.00', doc.at_xpath('//transactionRequest/amount').content + end.respond_with(successful_authorize_response) end - def test_successful_cancel_recurring - @gateway.expects(:ssl_post).returns(successful_cancel_recurring_response) + def test_includes_shipping_name_when_different_from_billing_name + card = credit_card('4242424242424242', + first_name: 'billing', + last_name: 'name') - response = @gateway.cancel_recurring(@subscription_id) + options = { + order_id: 'a' * 21, + billing_address: address(name: 'billing name'), + shipping_address: address(name: 'shipping lastname') + } - assert_instance_of Response, response - assert response.success? - assert response.test? - assert_equal @subscription_id, response.authorization + stub_comms do + @gateway.purchase(@amount, card, options) + end.check_request do |endpoint, data, headers| + parse(data) do |doc| + assert_equal 'billing', doc.at_xpath('//billTo/firstName').text + assert_equal 'name', doc.at_xpath('//billTo/lastName').text + assert_equal 'shipping', doc.at_xpath('//shipTo/firstName').text + assert_equal 'lastname', doc.at_xpath('//shipTo/lastName').text + end + end.respond_with(successful_purchase_response) end - def test_successful_status_recurring - @gateway.expects(:ssl_post).returns(successful_status_recurring_response) + def test_includes_shipping_name_when_passed_as_options + card = credit_card('4242424242424242', + first_name: 'billing', + last_name: 'name') + + shipping_address = address(first_name: 'shipping', last_name: 'lastname') + shipping_address.delete(:name) + options = { + order_id: 'a' * 21, + billing_address: address(name: 'billing name'), + shipping_address: shipping_address + } - response = @gateway.status_recurring(@subscription_id) - assert_instance_of Response, response - assert response.success? - assert response.test? - assert_equal @subscription_status, response.params['status'] + stub_comms do + @gateway.purchase(@amount, card, options) + end.check_request do |endpoint, data, headers| + parse(data) do |doc| + assert_equal 'billing', doc.at_xpath('//billTo/firstName').text + assert_equal 'name', doc.at_xpath('//billTo/lastName').text + assert_equal 'shipping', doc.at_xpath('//shipTo/firstName').text + assert_equal 'lastname', doc.at_xpath('//shipTo/lastName').text + end + end.respond_with(successful_purchase_response) end - def test_expdate_formatting - assert_equal '2009-09', @gateway.send(:arb_expdate, credit_card('4111111111111111', :month => "9", :year => "2009")) - assert_equal '2013-11', @gateway.send(:arb_expdate, credit_card('4111111111111111', :month => "11", :year => "2013")) + def test_truncation + card = credit_card('4242424242424242', + first_name: 'a' * 51, + last_name: 'a' * 51 + ) + + options = { + order_id: 'a' * 21, + description: 'a' * 256, + billing_address: address( + company: 'a' * 51, + address1: 'a' * 61, + city: 'a' * 41, + state: 'a' * 41, + zip: 'a' * 21, + country: 'a' * 61 + ), + shipping_address: address( + name: ['a' * 51, 'a' * 51].join(' '), + company: 'a' * 51, + address1: 'a' * 61, + city: 'a' * 41, + state: 'a' * 41, + zip: 'a' * 21, + country: 'a' * 61 + ) + } + + stub_comms do + @gateway.purchase(@amount, card, options) + end.check_request do |endpoint, data, headers| + assert_truncated(data, 20, '//refId') + assert_truncated(data, 255, '//description') + assert_address_truncated(data, 50, 'firstName') + assert_address_truncated(data, 50, 'lastName') + assert_address_truncated(data, 50, 'company') + assert_address_truncated(data, 60, 'address') + assert_address_truncated(data, 40, 'city') + assert_address_truncated(data, 40, 'state') + assert_address_truncated(data, 20, 'zip') + assert_address_truncated(data, 60, 'country') + end.respond_with(successful_purchase_response) end - def test_solution_id_is_added_to_post_data_parameters - assert !@gateway.send(:post_data, 'AUTH_ONLY').include?("x_solution_ID=A1000000") - ActiveMerchant::Billing::AuthorizeNetGateway.application_id = 'A1000000' - assert @gateway.send(:post_data, 'AUTH_ONLY').include?("x_solution_ID=A1000000") - ensure - ActiveMerchant::Billing::AuthorizeNetGateway.application_id = nil + def test_invalid_cvv + invalid_cvvs = ['47', '12345', ''] + invalid_cvvs.each do |cvv| + card = credit_card(@credit_card.number, { verification_value: cvv }) + stub_comms do + @gateway.purchase(@amount, card) + end.check_request do |endpoint, data, headers| + parse(data) { |doc| assert_nil doc.at_xpath('//cardCode') } + end.respond_with(successful_purchase_response) + end end - def test_bad_currency - @gateway.expects(:ssl_post).returns(bad_currency_response) + def test_card_number_truncation + card = credit_card(@credit_card.number + '0123456789') + stub_comms do + @gateway.purchase(@amount, card) + end.check_request do |endpoint, data, headers| + parse(data) do |doc| + assert_equal @credit_card.number, doc.at_xpath('//cardNumber').text + end + end.respond_with(successful_purchase_response) + end - response = @gateway.purchase(@amount, @credit_card, {:currency => "XYZ"}) - assert_failure response - assert_equal 'The supplied currency code is either invalid, not supported, not allowed for this merchant or doesn\'t have an exchange rate', response.message + def test_scrub + assert_equal @gateway.scrub(pre_scrubbed), post_scrubbed end - def test_alternate_currency - @gateway.expects(:ssl_post).returns(successful_purchase_response) + def test_supports_scrubbing? + assert @gateway.supports_scrubbing? + end + + def test_successful_apple_pay_authorization_with_network_tokenization + credit_card = network_tokenization_credit_card('4242424242424242', + :payment_cryptogram => '111111111100cryptogram' + ) - response = @gateway.purchase(@amount, @credit_card, {:currency => "GBP"}) + response = stub_comms do + @gateway.authorize(@amount, credit_card) + end.check_request do |endpoint, data, headers| + parse(data) do |doc| + assert_equal credit_card.payment_cryptogram, doc.at_xpath('//creditCard/cryptogram').content + assert_equal credit_card.number, doc.at_xpath('//creditCard/cardNumber').content + end + end.respond_with(successful_authorize_response) + + assert response + assert_instance_of Response, response assert_success response + assert_equal '508141794', response.authorization.split('#')[0] end - def test_include_cust_id_for_numeric_values - stub_comms do - @gateway.purchase(@amount, @credit_card, {:customer => "123"}) - end.check_request do |method, data| - assert data =~ /x_cust_id=123/ - end.respond_with(successful_authorization_response) + def test_failed_apple_pay_authorization_with_network_tokenization_not_supported + credit_card = network_tokenization_credit_card('4242424242424242', + :payment_cryptogram => '111111111100cryptogram' + ) + + response = stub_comms do + @gateway.authorize(@amount, credit_card) + end.check_request do |endpoint, data, headers| + parse(data) do |doc| + assert_equal credit_card.payment_cryptogram, doc.at_xpath('//creditCard/cryptogram').content + assert_equal credit_card.number, doc.at_xpath('//creditCard/cardNumber').content + end + end.respond_with(network_tokenization_not_supported_response) + + assert_equal Gateway::STANDARD_ERROR_CODE[:unsupported_feature], response.error_code + end + + def test_supports_network_tokenization_true + response = stub_comms do + @gateway.supports_network_tokenization? + end.check_request do |endpoint, data, headers| + parse(data) do |doc| + assert_equal 'authOnlyTransaction', doc.at_xpath('//transactionType').content + assert_equal '0.01', doc.at_xpath('//amount').content + assert_equal 'EHuWW9PiBkWvqE5juRwDzAUFBAk=', doc.at_xpath('//creditCard/cryptogram').content + assert_equal '4111111111111111', doc.at_xpath('//creditCard/cardNumber').content + end + end.respond_with(successful_authorize_response) + + assert_instance_of TrueClass, response end - def test_dont_include_cust_id_for_non_numeric_values - stub_comms do - @gateway.purchase(@amount, @credit_card, {:customer => "bob@test.com"}) - end.check_request do |method, data| - assert data !~ /x_cust_id/ - end.respond_with(successful_authorization_response) + def test_supports_network_tokenization_false + response = stub_comms do + @gateway.supports_network_tokenization? + end.check_request do |endpoint, data, headers| + parse(data) do |doc| + assert_equal 'authOnlyTransaction', doc.at_xpath('//transactionType').content + assert_equal '0.01', doc.at_xpath('//amount').content + assert_equal 'EHuWW9PiBkWvqE5juRwDzAUFBAk=', doc.at_xpath('//creditCard/cryptogram').content + assert_equal '4111111111111111', doc.at_xpath('//creditCard/cardNumber').content + end + end.respond_with(network_tokenization_not_supported_response) + + assert_instance_of FalseClass, response + end + + def test_verify_good_credentials + @gateway.expects(:ssl_post).returns(credentials_are_legit_response) + assert @gateway.verify_credentials + end + + def test_verify_bad_credentials + @gateway.expects(:ssl_post).returns(credentials_are_bogus_response) + assert !@gateway.verify_credentials end private - def post_data_fixture - 'x_encap_char=%24&x_card_num=4242424242424242&x_exp_date=0806&x_card_code=123&x_type=AUTH_ONLY&x_first_name=Longbob&x_version=3.1&x_login=X&x_last_name=Longsen&x_tran_key=Y&x_relay_response=FALSE&x_delim_data=TRUE&x_delim_char=%2C&x_amount=1.01' + + def pre_scrubbed + <<-PRE_SCRUBBED + opening connection to apitest.authorize.net:443... + opened + starting SSL for apitest.authorize.net:443... + SSL established + <- "POST /xml/v1/request.api HTTP/1.1\r\nContent-Type: text/xml\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: apitest.authorize.net\r\nContent-Length: 1306\r\n\r\n" + <- "<?xml version=\"1.0\"?>\n<createTransactionRequest xmlns=\"AnetApi/xml/v1/schema/AnetApiSchema.xsd\">\n<merchantAuthentication>\n<name>5KP3u95bQpv</name>\n<transactionKey>4Ktq966gC55GAX7S</transactionKey>\n</merchantAuthentication>\n<refId>1</refId>\n<transactionRequest>\n<transactionType>authCaptureTransaction</transactionType>\n<amount>1.00</amount>\n<payment>\n<creditCard>\n<cardNumber>4000100011112224</cardNumber>\n<expirationDate>09/2016</expirationDate>\n<cardCode>123</cardCode>\n<cryptogram>EHuWW9PiBkWvqE5juRwDzAUFBAk=</cryptogram>\n</creditCard>\n</payment>\n<order>\n<invoiceNumber>1</invoiceNumber>\n<description>Store Purchase</description>\n</order>\n<customer/>\n<billTo>\n<firstName>Longbob</firstName>\n<lastName>Longsen</lastName>\n<company>Widgets Inc</company>\n<address>1234 My Street</address>\n<city>Ottawa</city>\n<state>ON</state>\n<zip>K1C2N6</zip>\n<country>CA</country>\n<phoneNumber>(555)555-5555</phoneNumber>\n<faxNumber>(555)555-6666</faxNumber>\n</billTo>\n<cardholderAuthentication>\n<authenticationIndicator/>\n<cardholderAuthenticationValue/>\n</cardholderAuthentication>\n<transactionSettings>\n<setting>\n<settingName>duplicateWindow</settingName>\n<settingValue>0</settingValue>\n</setting>\n</transactionSettings>\n<userFields>\n<userField>\n<name>x_currency_code</name>\n<value>USD</value>\n</userField>\n</userFields>\n<trackData>\n<track1>123456</track1>\n<track2>78910</track2></trackData>\n</transactionRequest>\n</createTransactionRequest>\n" + -> "HTTP/1.1 200 OK\r\n" + -> "Cache-Control: private\r\n" + -> "Content-Length: 973\r\n" + -> "Content-Type: application/xml; charset=utf-8\r\n" + -> "Server: Microsoft-IIS/7.5\r\n" + -> "X-AspNet-Version: 2.0.50727\r\n" + -> "X-Powered-By: ASP.NET\r\n" + -> "Access-Control-Allow-Origin: *\r\n" + -> "Access-Control-Allow-Methods: GET,POST,OPTIONS\r\n" + -> "Access-Control-Allow-Headers: x-requested-with,cache-control,content-type,origin,method\r\n" + -> "Date: Mon, 26 Jan 2015 16:29:30 GMT\r\n" + -> "Connection: close\r\n" + -> "\r\n" + reading 973 bytes... + -> "\xEF\xBB\xBF<?xml version=\"1.0\" encoding=\"utf-8\"?><createTransactionResponse xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns=\"AnetApi/xml/v1/schema/AnetApiSchema.xsd\"><refId>1</refId><messages><resultCode>Ok</resultCode><message><code>I00001</code><text>Successful.</text></message></messages><transactionResponse><responseCode>1</responseCode><authCode>H6K4BU</authCode><avsResultCode>Y</avsResultCode><cvvResultCode>P</cvvResultCode><cavvResultCode>2</cavvResultCode><transId>2227534280</transId><refTransID /><transHash>FE7A5BA8F209227CE1EC4B07C4A1BB81</transHash><testRequest>0</testRequest><accountNumber>XXXX2224</accountNumber><accountType>Visa</accountType><messages><message><code>1</code><description>This transaction has been approved.</description></message></messages><userFields><userField><name>x_currency_code</name><value>USD</value></userField></userFields></transactionResponse></createTransactionResponse>" + read 973 bytes + Conn close + PRE_SCRUBBED end - def minimum_requirements - %w(version delim_data relay_response login tran_key amount card_num exp_date type) + def post_scrubbed + <<-PRE_SCRUBBED + opening connection to apitest.authorize.net:443... + opened + starting SSL for apitest.authorize.net:443... + SSL established + <- "POST /xml/v1/request.api HTTP/1.1\r\nContent-Type: text/xml\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: apitest.authorize.net\r\nContent-Length: 1306\r\n\r\n" + <- "<?xml version=\"1.0\"?>\n<createTransactionRequest xmlns=\"AnetApi/xml/v1/schema/AnetApiSchema.xsd\">\n<merchantAuthentication>\n<name>5KP3u95bQpv</name>\n<transactionKey>[FILTERED]</transactionKey>\n</merchantAuthentication>\n<refId>1</refId>\n<transactionRequest>\n<transactionType>authCaptureTransaction</transactionType>\n<amount>1.00</amount>\n<payment>\n<creditCard>\n<cardNumber>[FILTERED]</cardNumber>\n<expirationDate>09/2016</expirationDate>\n<cardCode>[FILTERED]</cardCode>\n<cryptogram>[FILTERED]</cryptogram>\n</creditCard>\n</payment>\n<order>\n<invoiceNumber>1</invoiceNumber>\n<description>Store Purchase</description>\n</order>\n<customer/>\n<billTo>\n<firstName>Longbob</firstName>\n<lastName>Longsen</lastName>\n<company>Widgets Inc</company>\n<address>1234 My Street</address>\n<city>Ottawa</city>\n<state>ON</state>\n<zip>K1C2N6</zip>\n<country>CA</country>\n<phoneNumber>(555)555-5555</phoneNumber>\n<faxNumber>(555)555-6666</faxNumber>\n</billTo>\n<cardholderAuthentication>\n<authenticationIndicator/>\n<cardholderAuthenticationValue/>\n</cardholderAuthentication>\n<transactionSettings>\n<setting>\n<settingName>duplicateWindow</settingName>\n<settingValue>0</settingValue>\n</setting>\n</transactionSettings>\n<userFields>\n<userField>\n<name>x_currency_code</name>\n<value>USD</value>\n</userField>\n</userFields>\n<trackData>\n<track1>[FILTERED]</track1>\n<track2>[FILTERED]</track2></trackData>\n</transactionRequest>\n</createTransactionRequest>\n" + -> "HTTP/1.1 200 OK\r\n" + -> "Cache-Control: private\r\n" + -> "Content-Length: 973\r\n" + -> "Content-Type: application/xml; charset=utf-8\r\n" + -> "Server: Microsoft-IIS/7.5\r\n" + -> "X-AspNet-Version: 2.0.50727\r\n" + -> "X-Powered-By: ASP.NET\r\n" + -> "Access-Control-Allow-Origin: *\r\n" + -> "Access-Control-Allow-Methods: GET,POST,OPTIONS\r\n" + -> "Access-Control-Allow-Headers: x-requested-with,cache-control,content-type,origin,method\r\n" + -> "Date: Mon, 26 Jan 2015 16:29:30 GMT\r\n" + -> "Connection: close\r\n" + -> "\r\n" + reading 973 bytes... + -> "\xEF\xBB\xBF<?xml version=\"1.0\" encoding=\"utf-8\"?><createTransactionResponse xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns=\"AnetApi/xml/v1/schema/AnetApiSchema.xsd\"><refId>1</refId><messages><resultCode>Ok</resultCode><message><code>I00001</code><text>Successful.</text></message></messages><transactionResponse><responseCode>1</responseCode><authCode>H6K4BU</authCode><avsResultCode>Y</avsResultCode><cvvResultCode>P</cvvResultCode><cavvResultCode>2</cavvResultCode><transId>2227534280</transId><refTransID /><transHash>FE7A5BA8F209227CE1EC4B07C4A1BB81</transHash><testRequest>0</testRequest><accountNumber>[FILTERED]</accountNumber><accountType>Visa</accountType><messages><message><code>1</code><description>This transaction has been approved.</description></message></messages><userFields><userField><name>x_currency_code</name><value>USD</value></userField></userFields></transactionResponse></createTransactionResponse>" + read 973 bytes + Conn close + PRE_SCRUBBED end - def failed_refund_response - '$3$,$2$,$54$,$The referenced transaction does not meet the criteria for issuing a credit.$,$$,$P$,$0$,$$,$$,$1.00$,$CC$,$credit$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$39265D8BA0CDD4F045B5F4129B2AAA01$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$' + def parse(data) + Nokogiri::XML(data).tap do |doc| + doc.remove_namespaces! + yield(doc) if block_given? + end + end + + def fields_from_doc(doc) + assert_not_nil doc.at_xpath('//userFields/userField/name') + doc.xpath('//userFields/userField').inject({}) do |hash, element| + hash[element.at_xpath('name').content] = element.at_xpath('value').content + hash + end + end + + def settings_from_doc(doc) + assert_not_nil doc.at_xpath('//transactionSettings/setting/settingName') + doc.xpath('//transactionSettings/setting').inject({}) do |hash, element| + hash[element.at_xpath('settingName').content] = element.at_xpath('settingValue').content + hash + end + end + + def assert_truncated(data, expected_size, field) + assert_equal ('a' * expected_size), parse(data).at_xpath(field).text, data end - def successful_authorization_response - '$1$,$1$,$1$,$This transaction has been approved.$,$advE7f$,$Y$,$508141794$,$5b3fe66005f3da0ebe51$,$$,$1.00$,$CC$,$auth_only$,$$,$Longbob$,$Longsen$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$2860A297E0FE804BCB9EF8738599645C$,$P$,$2$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$' + def assert_address_truncated(data, expected_size, field) + assert_truncated(data, expected_size, "//billTo/#{field}") + assert_truncated(data, expected_size, "//shipTo/#{field}") end def successful_purchase_response - '$1$,$1$,$1$,$This transaction has been approved.$,$d1GENk$,$Y$,$508141795$,$32968c18334f16525227$,$Store purchase$,$1.00$,$CC$,$auth_capture$,$$,$Longbob$,$Longsen$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$269862C030129C1173727CC10B1935ED$,$P$,$2$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$' + <<-eos + <?xml version="1.0" encoding="utf-8"?> + <createTransactionResponse + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" + xmlns="AnetApi/xml/v1/schema/AnetApiSchema.xsd"> + <refId>1</refId> + <messages> + <resultCode>Ok</resultCode> + <message> + <code>I00001</code> + <text>Successful.</text> + </message> + </messages> + <transactionResponse> + <responseCode>1</responseCode> + <authCode>GSOFTZ</authCode> + <avsResultCode>Y</avsResultCode> + <cvvResultCode>P</cvvResultCode> + <cavvResultCode>2</cavvResultCode> + <transId>508141795</transId> + <refTransID/> + <transHash>655D049EE60E1766C9C28EB47CFAA389</transHash> + <testRequest>0</testRequest> + <accountNumber>XXXX2224</accountNumber> + <accountType>Visa</accountType> + <messages> + <message> + <code>1</code> + <description>This transaction has been approved.</description> + </message> + </messages> + </transactionResponse> + </createTransactionResponse> + eos + end + + def fraud_review_response + <<-eos + <?xml version="1.0" encoding="utf-8"?> + <createTransactionResponse + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" + xmlns="AnetApi/xml/v1/schema/AnetApiSchema.xsd"> + <refId>1</refId> + <messages> + <resultCode>Ok</resultCode> + <message> + <code>I00001</code> + <text>Successful.</text> + </message> + </messages> + <transactionResponse> + <responseCode>4</responseCode> + <authCode>GSOFTZ</authCode> + <avsResultCode>X</avsResultCode> + <cvvResultCode>M</cvvResultCode> + <cavvResultCode>2</cavvResultCode> + <transId>508141795</transId> + <refTransID/> + <transHash>655D049EE60E1766C9C28EB47CFAA389</transHash> + <testRequest>0</testRequest> + <accountNumber>XXXX2224</accountNumber> + <accountType>Visa</accountType> + <messages> + <message> + <code>1</code> + <description>Thank you! For security reasons your order is currently being reviewed</description> + </message> + </messages> + </transactionResponse> + </createTransactionResponse> + eos + end + + def address_not_provided_avs_response + <<-eos + <?xml version="1.0" encoding="utf-8"?> + <createTransactionResponse + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" + xmlns="AnetApi/xml/v1/schema/AnetApiSchema.xsd"> + <refId>1</refId> + <messages> + <resultCode>Ok</resultCode> + <message> + <code>I00001</code> + <text>Successful.</text> + </message> + </messages> + <transactionResponse> + <responseCode>4</responseCode> + <authCode>GSOFTZ</authCode> + <avsResultCode>B</avsResultCode> + <cvvResultCode>M</cvvResultCode> + <cavvResultCode>2</cavvResultCode> + <transId>508141795</transId> + <refTransID/> + <transHash>655D049EE60E1766C9C28EB47CFAA389</transHash> + <testRequest>0</testRequest> + <accountNumber>XXXX2224</accountNumber> + <accountType>Visa</accountType> + <messages> + <message> + <code>1</code> + <description>Thank you! For security reasons your order is currently being reviewed</description> + </message> + </messages> + </transactionResponse> + </createTransactionResponse> + eos + end + + def no_match_cvv_response + <<-eos + <?xml version="1.0" encoding="utf-8"?> + <createTransactionResponse + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" + xmlns="AnetApi/xml/v1/schema/AnetApiSchema.xsd"> + <refId>1</refId> + <messages> + <resultCode>Error</resultCode> + <message> + <code>E00027</code> + <text>The transaction was unsuccessful.</text> + </message> + </messages> + <transactionResponse> + <responseCode>2</responseCode> + <authCode>GSOFTZ</authCode> + <avsResultCode>A</avsResultCode> + <cvvResultCode>N</cvvResultCode> + <cavvResultCode>2</cavvResultCode> + <transId>508141795</transId> + <refTransID/> + <transHash>655D049EE60E1766C9C28EB47CFAA389</transHash> + <testRequest>0</testRequest> + <accountNumber>XXXX2224</accountNumber> + <accountType>Visa</accountType> + <messages> + <message> + <code>1</code> + <description>Thank you! For security reasons your order is currently being reviewed</description> + </message> + </messages> + </transactionResponse> + </createTransactionResponse> + eos + end + + def no_match_avs_response + <<-eos + <?xml version="1.0" encoding="utf-8"?> + <createTransactionResponse + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" + xmlns="AnetApi/xml/v1/schema/AnetApiSchema.xsd"> + <refId>1</refId> + <messages> + <resultCode>Error</resultCode> + <message> + <code>E00027</code> + <text>The transaction was unsuccessful.</text> + </message> + </messages> + <transactionResponse> + <responseCode>2</responseCode> + <authCode>GSOFTZ</authCode> + <avsResultCode>A</avsResultCode> + <cvvResultCode>M</cvvResultCode> + <cavvResultCode>2</cavvResultCode> + <transId>508141795</transId> + <refTransID/> + <transHash>655D049EE60E1766C9C28EB47CFAA389</transHash> + <testRequest>0</testRequest> + <accountNumber>XXXX2224</accountNumber> + <accountType>Visa</accountType> + <errors> + <error> + <errorCode>27</errorCode> + <errorText>The transaction cannot be found.</errorText> + </error> + </errors> + </transactionResponse> + </createTransactionResponse> + eos + end + + def failed_purchase_response + <<-eos + <?xml version="1.0" encoding="utf-8"?> + <createTransactionResponse xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="AnetApi/xml/v1/schema/AnetApiSchema.xsd"> + <refId>1234567</refId> + <messages> + <resultCode>Error</resultCode> + <message> + <code>E00027</code> + <text>The transaction was unsuccessful.</text> + </message> + </messages> + <transactionResponse> + <responseCode>3</responseCode> + <authCode/> + <avsResultCode>P</avsResultCode> + <cvvResultCode/> + <cavvResultCode/> + <transId>0</transId> + <refTransID/> + <transHash>7F9A0CB845632DCA5833D2F30ED02677</transHash> + <testRequest>0</testRequest> + <accountNumber>XXXX0001</accountNumber> + <accountType/> + <errors> + <error> + <errorCode>6</errorCode> + <errorText>The credit card number is invalid.</errorText> + </error> + </errors> + <userFields> + <userField> + <name>MerchantDefinedFieldName1</name> + <value>MerchantDefinedFieldValue1</value> + </userField> + <userField> + <name>favorite_color</name> + <value>blue</value> + </userField> + </userFields> + </transactionResponse> + </createTransactionResponse> + eos + end + + def no_message_response + <<-eos + <?xml version="1.0" encoding="utf-8"?> + <createTransactionResponse xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="AnetApi/xml/v1/schema/AnetApiSchema.xsd"> + <refId>1234567</refId> + <messages> + <resultCode>Error</resultCode> + </messages> + <transactionResponse> + <responseCode>3</responseCode> + <authCode/> + <avsResultCode>P</avsResultCode> + <cvvResultCode/> + <cavvResultCode/> + <transId>0</transId> + <refTransID/> + <transHash>7F9A0CB845632DCA5833D2F30ED02677</transHash> + <testRequest>0</testRequest> + <accountNumber>XXXX0001</accountNumber> + <accountType/> + <userFields> + <userField> + <name>MerchantDefinedFieldName1</name> + <value>MerchantDefinedFieldValue1</value> + </userField> + <userField> + <name>favorite_color</name> + <value>blue</value> + </userField> + </userFields> + </transactionResponse> + </createTransactionResponse> + eos + end + + def successful_purchase_response_test_mode + <<-eos + <?xml version="1.0" encoding="utf-8"?> + <createTransactionResponse + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" + xmlns="AnetApi/xml/v1/schema/AnetApiSchema.xsd"> + <refId>1</refId> + <messages> + <resultCode>Ok</resultCode> + <message> + <code>I00001</code> + <text>Successful.</text> + </message> + </messages> + <transactionResponse> + <responseCode>1</responseCode> + <authCode>GSOFTZ</authCode> + <avsResultCode>Y</avsResultCode> + <cvvResultCode>P</cvvResultCode> + <cavvResultCode>2</cavvResultCode> + <transId>508141795</transId> + <refTransID/> + <transHash>655D049EE60E1766C9C28EB47CFAA389</transHash> + <testRequest>1</testRequest> + <accountNumber>XXXX2224</accountNumber> + <accountType>Visa</accountType> + <messages> + <message> + <code>1</code> + <description>This transaction has been approved.</description> + </message> + </messages> + </transactionResponse> + </createTransactionResponse> + eos + end + + def successful_authorize_response + <<-eos + <?xml version="1.0" encoding="utf-8"?> + <createTransactionResponse xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="AnetApi/xml/v1/schema/AnetApiSchema.xsd"> + <refId>123456</refId> + <messages> + <resultCode>Ok</resultCode> + <message> + <code>I00001</code> + <text>Successful.</text> + </message> + </messages> + <transactionResponse> + <responseCode>1</responseCode> + <authCode>A88MS0</authCode> + <avsResultCode>Y</avsResultCode> + <cvvResultCode>M</cvvResultCode> + <cavvResultCode>2</cavvResultCode> + <transId>508141794</transId> + <refTransID/> + <transHash>D0EFF3F32E5ABD14A7CE6ADF32736D57</transHash> + <testRequest>0</testRequest> + <accountNumber>XXXX0015</accountNumber> + <accountType>MasterCard</accountType> + <messages> + <message> + <code>1</code> + <description>This transaction has been approved.</description> + </message> + </messages> + <userFields> + <userField> + <name>MerchantDefinedFieldName1</name> + <value>MerchantDefinedFieldValue1</value> + </userField> + <userField> + <name>favorite_color</name> + <value>blue</value> + </userField> + </userFields> + </transactionResponse> + </createTransactionResponse> + eos + end + + def failed_authorize_response + <<-eos + <?xml version="1.0" encoding="utf-8"?> + <createTransactionResponse xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="AnetApi/xml/v1/schema/AnetApiSchema.xsd"> + <refId>123456</refId> + <messages> + <resultCode>Error</resultCode> + <message> + <code>E00027</code> + <text>The transaction was unsuccessful.</text> + </message> + </messages> + <transactionResponse> + <responseCode>3</responseCode> + <authCode/> + <avsResultCode>P</avsResultCode> + <cvvResultCode/> + <cavvResultCode/> + <transId>0</transId> + <refTransID/> + <transHash>DA56E64108957174C5AE9BE466914741</transHash> + <testRequest>0</testRequest> + <accountNumber>XXXX0001</accountNumber> + <accountType/> + <errors> + <error> + <errorCode>6</errorCode> + <errorText>The credit card number is invalid.</errorText> + </error> + </errors> + <userFields> + <userField> + <name>MerchantDefinedFieldName1</name> + <value>MerchantDefinedFieldValue1</value> + </userField> + <userField> + <name>favorite_color</name> + <value>blue</value> + </userField> + </userFields> + </transactionResponse> + </createTransactionResponse> + eos end def successful_capture_response - '$1$,$1$,$1$,$This transaction has been approved.$,$d1GENk$,$Y$,$508141795$,$32968c18334f16525227$,$Store purchase$,$1.00$,$CC$,$auth_capture$,$$,$Longbob$,$Longsen$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$269862C030129C1173727CC10B1935ED$,$P$,$2$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$' + <<-eos + <?xml version="1.0" encoding="utf-8"?> + <createTransactionResponse xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema xmlns=AnetApi/xml/v1/schema/AnetApiSchema.xsd"> + <refId/> + <messages> + <resultCode>Ok</resultCode> + <message> + <code>I00001</code> + <text>Successful.</text> + </message> + </messages> + <transactionResponse> + <responseCode>1</responseCode> + <authCode>UTDVHP</authCode> + <avsResultCode>P</avsResultCode> + <cvvResultCode/> + <cavvResultCode/> + <transId>2214675515</transId> + <refTransID>2214675515</refTransID> + <transHash>6D739029E129D87F6CEFE3B3864F6D61</transHash> + <testRequest>0</testRequest> + <accountNumber>XXXX2224</accountNumber> + <accountType>Visa</accountType> + <messages> + <message> + <code>1</code> + <description>This transaction has been approved.</description> + </message> + </messages> + </transactionResponse> + </createTransactionResponse> + eos end - def failed_authorization_response - '$2$,$1$,$1$,$This transaction was declined.$,$advE7f$,$Y$,$508141794$,$5b3fe66005f3da0ebe51$,$$,$1.00$,$CC$,$auth_only$,$$,$Longbob$,$Longsen$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$2860A297E0FE804BCB9EF8738599645C$,$P$,$2$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$' + def already_actioned_capture_response + <<-eos + <?xml version="1.0" encoding="utf-8"?> + <createTransactionResponse xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema xmlns=AnetApi/xml/v1/schema/AnetApiSchema.xsd"> + <refId/> + <messages> + <resultCode>Ok</resultCode> + <message> + <code>I00001</code> + <text>This transaction has already been captured.</text> + </message> + </messages> + <transactionResponse> + <responseCode>1</responseCode> + <authCode>UTDVHP</authCode> + <avsResultCode>P</avsResultCode> + <cvvResultCode/> + <cavvResultCode/> + <transId>2214675515</transId> + <refTransID>2214675515</refTransID> + <transHash>6D739029E129D87F6CEFE3B3864F6D61</transHash> + <testRequest>0</testRequest> + <accountNumber>XXXX2224</accountNumber> + <accountType>Visa</accountType> + <messages> + <message> + <code>311</code> + <description>This transaction has already been captured.</description> + </message> + </messages> + </transactionResponse> + </createTransactionResponse> + eos end - def fraud_review_response - "$4$,$$,$253$,$Thank you! For security reasons your order is currently being reviewed.$,$$,$X$,$0$,$$,$$,$1.00$,$$,$auth_capture$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$207BCBBF78E85CF174C87AE286B472D2$,$M$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$" - end - - def bad_currency_response - "$3$,$1$,$39$,$The supplied currency code is either invalid, not supported, not allowed for this merchant or doesn't have an exchange rate.$,$$,$P$,$0$,$$,$$,$1.00$,$$,$auth_capture$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$207BCBBF78E85CF174C87AE286B472D2$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$" - end - - def successful_recurring_response - <<-XML -<ARBCreateSubscriptionResponse xmlns="AnetApi/xml/v1/schema/AnetApiSchema.xsd"> - <refId>Sample</refId> - <messages> - <resultCode>Ok</resultCode> - <message> - <code>I00001</code> - <text>Successful.</text> - </message> - </messages> - <subscriptionId>#{@subscription_id}</subscriptionId> -</ARBCreateSubscriptionResponse> - XML - end - - def successful_update_recurring_response - <<-XML -<ARBUpdateSubscriptionResponse xmlns="AnetApi/xml/v1/schema/AnetApiSchema.xsd"> - <refId>Sample</refId> - <messages> - <resultCode>Ok</resultCode> - <message> - <code>I00001</code> - <text>Successful.</text> - </message> - </messages> - <subscriptionId>#{@subscription_id}</subscriptionId> -</ARBUpdateSubscriptionResponse> - XML - end - - def successful_cancel_recurring_response - <<-XML -<ARBCancelSubscriptionResponse xmlns="AnetApi/xml/v1/schema/AnetApiSchema.xsd"> - <refId>Sample</refId> - <messages> - <resultCode>Ok</resultCode> - <message> - <code>I00001</code> - <text>Successful.</text> - </message> - </messages> - <subscriptionId>#{@subscription_id}</subscriptionId> -</ARBCancelSubscriptionResponse> - XML - end - - def successful_status_recurring_response - <<-XML -<ARBGetSubscriptionStatusResponse xmlns="AnetApi/xml/v1/schema/AnetApiSchema.xsd"> - <refId>Sample</refId> - <messages> - <resultCode>Ok</resultCode> - <message> - <code>I00001</code> - <text>Successful.</text> - </message> - </messages> - <Status>#{@subscription_status}</Status> -</ARBGetSubscriptionStatusResponse> - XML + def failed_capture_response + <<-eos + <createTransactionResponse xmlns:xsi= + http://www.w3.org/2001/XMLSchema-instance xmlns:xsd=http://www.w3.org/2001/XMLSchema xmlns=AnetApi/xml/v1/schema/AnetApiSchema.xsd><refId/><messages> + <resultCode>Error</resultCode> + <message> + <code>E00027</code> + <text>The transaction was unsuccessful.</text> + </message> + </messages><transactionResponse> + <responseCode>3</responseCode> + <authCode/> + <avsResultCode>P</avsResultCode> + <cvvResultCode/> + <cavvResultCode/> + <transId>0</transId> + <refTransID>23124</refTransID> + <transHash>D99CC43D1B34F0DAB7F430F8F8B3249A</transHash> + <testRequest>0</testRequest> + <accountNumber/> + <accountType/> + <errors> + <error> + <errorCode>16</errorCode> + <errorText>The transaction cannot be found.</errorText> + </error> + </errors> + <shipTo/> + </transactionResponse> + </createTransactionResponse> + eos + end + + def successful_refund_response + <<-eos + <createTransactionResponse xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="AnetApi/xml/v1/schema/AnetApiSchema.xsd"> + <messages> + <resultCode>Ok</resultCode> + <message> + <code>I00001</code> + <text>Successful.</text> + </message> + </messages> + <transactionResponse> + <responseCode>1</responseCode> + <authCode/> + <avsResultCode>P</avsResultCode> + <cvvResultCode/> + <cavvResultCode/> + <transId>2214602071</transId> + <refTransID>2214269051</refTransID> + <transHash>A3E5982FB6789092985F2D618196A268</transHash> + <testRequest>0</testRequest> + <accountNumber>XXXX2224</accountNumber> + <accountType>Visa</accountType> + <messages> + <message> + <code>1</code> + <description>This transaction has been approved.</description> + </message> + </messages> + </transactionResponse> + </createTransactionResponse> + eos + end + + def failed_refund_response + <<-eos + <createTransactionResponse xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="AnetApi/xml/v1/schema/AnetApiSchema.xsd"> + <messages> + <resultCode>Error</resultCode> + <message> + <code>E00027</code> + <text>The transaction was unsuccessful.</text> + </message> + </messages> + <transactionResponse> + <responseCode>3</responseCode> + <authCode/> + <avsResultCode>P</avsResultCode> + <cvvResultCode/> + <cavvResultCode/> + <transId>0</transId> + <refTransID>2214269051</refTransID> + <transHash>63E03F4968F0874E1B41FCD79DD54717</transHash> + <testRequest>0</testRequest> + <accountNumber>XXXX2224</accountNumber> + <accountType>Visa</accountType> + <errors> + <error> + <errorCode>55</errorCode> + <errorText>The sum of credits against the referenced transaction would exceed original debit amount.</errorText> + </error> + </errors> + </transactionResponse> + </createTransactionResponse> + eos + end + + def successful_void_response + <<-eos + <?xml version="1.0" encoding="utf-8"?> + <createTransactionResponse xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="AnetApi/xml/v1/schema/AnetApiSchema.xsd"> + <messages> + <resultCode>Ok</resultCode> + <message> + <code>I00001</code> + <text>Successful.</text> + </message> + </messages> + <transactionResponse> + <responseCode>1</responseCode> + <authCode>GYEB3</authCode> + <avsResultCode>P</avsResultCode> + <cvvResultCode/> + <cavvResultCode/> + <transId>2213755822</transId> + <refTransID>2213755822</refTransID> + <transHash>3383BBB85FF98057D61B2D9B9A2DA79F</transHash> + <testRequest>0</testRequest> + <accountNumber>XXXX0015</accountNumber> + <accountType>MasterCard</accountType> + <messages> + <message> + <code>1</code> + <description>This transaction has been approved.</description> + </message> + </messages> + </transactionResponse> + </createTransactionResponse> + eos + end + + def failed_void_response + <<-eos + <?xml version="1.0" encoding="utf-8"?> + <createTransactionResponse xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="AnetApi/xml/v1/schema/AnetApiSchema.xsd"> + <messages> + <resultCode>Error</resultCode> + <message> + <code>E00027</code> + <text>The transaction was unsuccessful.</text> + </message> + </messages> + <transactionResponse> + <responseCode>3</responseCode> + <authCode/> + <avsResultCode>P</avsResultCode> + <cvvResultCode/> + <cavvResultCode/> + <transId>0</transId> + <refTransID>2213755821</refTransID> + <transHash>39DC95085A313FEF7278C40EA8A66B16</transHash> + <testRequest>0</testRequest> + <accountNumber/> + <accountType/> + <errors> + <error> + <errorCode>16</errorCode> + <errorText>The transaction cannot be found.</errorText> + </error> + </errors> + <shipTo/> + </transactionResponse> + </createTransactionResponse> + eos + end + + def successful_credit_response + <<-eos + <?xml version="1.0" encoding="UTF-8"?> + <createTransactionResponse xmlns="AnetApi/xml/v1/schema/AnetApiSchema.xsd" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <refId>1</refId> + <messages> + <resultCode>Ok</resultCode> + <message> + <code>I00001</code> + <text>Successful.</text> + </message> + </messages> + <transactionResponse> + <responseCode>1</responseCode> + <authCode /> + <avsResultCode>P</avsResultCode> + <cvvResultCode /> + <cavvResultCode /> + <transId>2230004436</transId> + <refTransID /> + <transHash>BF2ADA32B70495EE035C6A5ADC635047</transHash> + <testRequest>0</testRequest> + <accountNumber>XXXX2224</accountNumber> + <accountType>Visa</accountType> + <messages> + <message> + <code>1</code> + <description>This transaction has been approved.</description> + </message> + </messages> + <userFields> + <userField> + <name>x_currency_code</name> + <value>USD</value> + </userField> + </userFields> + </transactionResponse> + </createTransactionResponse> + eos + end + + def failed_credit_response + <<-eos + <?xml version="1.0" encoding="UTF-8"?> + <createTransactionResponse xmlns="AnetApi/xml/v1/schema/AnetApiSchema.xsd" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <refId>1</refId> + <messages> + <resultCode>Error</resultCode> + <message> + <code>E00027</code> + <text>The transaction was unsuccessful.</text> + </message> + </messages> + <transactionResponse> + <responseCode>3</responseCode> + <authCode /> + <avsResultCode>P</avsResultCode> + <cvvResultCode /> + <cavvResultCode /> + <transId>0</transId> + <refTransID /> + <transHash>0FFA5F1B4CA8DC9643BC117DAFB45770</transHash> + <testRequest>0</testRequest> + <accountNumber>XXXX1222</accountNumber> + <accountType /> + <errors> + <error> + <errorCode>6</errorCode> + <errorText>The credit card number is invalid.</errorText> + </error> + </errors> + <userFields> + <userField> + <name>x_currency_code</name> + <value>USD</value> + </userField> + </userFields> + </transactionResponse> + </createTransactionResponse> + eos + end + + def successful_store_response + <<-eos + <?xml version="1.0" encoding="UTF-8"?> + <createCustomerProfileResponse xmlns="AnetApi/xml/v1/schema/AnetApiSchema.xsd" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <messages> + <resultCode>Ok</resultCode> + <message> + <code>I00001</code> + <text>Successful.</text> + </message> + </messages> + <customerProfileId>35959426</customerProfileId> + <customerPaymentProfileIdList> + <numericString>32506918</numericString> + </customerPaymentProfileIdList> + <customerShippingAddressIdList /> + <validationDirectResponseList /> + </createCustomerProfileResponse> + eos + end + + def failed_store_response + <<-eos + <?xml version="1.0" encoding="UTF-8"?> + <createCustomerProfileResponse xmlns="AnetApi/xml/v1/schema/AnetApiSchema.xsd" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <messages> + <resultCode>Error</resultCode> + <message> + <code>E00015</code> + <text>The field length is invalid for Card Number.</text> + </message> + </messages> + <customerPaymentProfileIdList /> + <customerShippingAddressIdList /> + <validationDirectResponseList /> + </createCustomerProfileResponse> + eos + end + + def successful_unstore_response + <<-eos + <?xml version="1.0" encoding="utf-8"?> + <deleteCustomerProfileResponse xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="AnetApi/xml/v1/schema/AnetApiSchema.xsd"> + <messages> + <resultCode>Ok</resultCode> + <message> + <code>I00001</code> + <text>Successful.</text> + </message> + </messages> + </deleteCustomerProfileResponse> + eos + end + + def failed_unstore_response + <<-eos + <?xml version="1.0" encoding="utf-8"?> + <deleteCustomerProfileResponse xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="AnetApi/xml/v1/schema/AnetApiSchema.xsd"> + <messages> + <resultCode>Error</resultCode> + <message> + <code>E00040</code> + <text>The record cannot be found.</text> + </message> + </messages> + </deleteCustomerProfileResponse> + eos + end + + def successful_store_new_payment_profile_response + <<-eos + <?xml version="1.0" encoding="UTF-8"?> + <createCustomerPaymentProfileResponse xmlns="AnetApi/xml/v1/schema/AnetApiSchema.xsd" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <messages> + <resultCode>Ok</resultCode> + <message> + <code>I00001</code> + <text>Successful.</text> + </message> + </messages> + <customerProfileId>38392170</customerProfileId> + <customerPaymentProfileId>34896759</customerPaymentProfileId> + </createCustomerPaymentProfileResponse> + eos + end + + def failed_store_new_payment_profile_response + <<-eos + <?xml version="1.0" encoding="UTF-8"?> + <createCustomerPaymentProfileResponse xmlns="AnetApi/xml/v1/schema/AnetApiSchema.xsd" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <messages> + <resultCode>Error</resultCode> + <message> + <code>E00039</code> + <text>A duplicate customer payment profile already exists.</text> + </message> + </messages> + <customerProfileId>38392767</customerProfileId> + <customerPaymentProfileId>34897359</customerPaymentProfileId> + </createCustomerPaymentProfileResponse> + eos + end + + def successful_purchase_using_stored_card_response + <<-eos + <?xml version="1.0" encoding="UTF-8"?> + <createCustomerProfileTransactionResponse xmlns="AnetApi/xml/v1/schema/AnetApiSchema.xsd" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <refId>1</refId> + <messages> + <resultCode>Ok</resultCode> + <message> + <code>I00001</code> + <text>Successful.</text> + </message> + </messages> + <directResponse>1,1,1,This transaction has been approved.,8HUT72,Y,2235700270,1,Store Purchase,1.01,CC,auth_capture,e385c780422f4bd182c4,Longbob,Longsen,,,,n/a,,,,,,,,,,,,,,,,,,,4A20EEAF89018FF075899DDB332E9D35,,2,,,,,,,,,,,XXXX2224,Visa,,,,,,,,,,,,,,,,</directResponse> + </createCustomerProfileTransactionResponse> + eos + end + + def successful_purchase_using_stored_card_response_with_pipe_delimiter + <<-eos + <?xml version="1.0" encoding="UTF-8"?> + <createCustomerProfileTransactionResponse xmlns="AnetApi/xml/v1/schema/AnetApiSchema.xsd" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <refId>1</refId> + <messages> + <resultCode>Ok</resultCode> + <message> + <code>I00001</code> + <text>Successful.</text> + </message> + </messages> + <directResponse>1|1|1|This transaction has been approved.|8HUT72|Y|2235700270|1|description, with, commas|1.01|CC|auth_capture|e385c780422f4bd182c4|Longbob|Longsen||||n/a|||||||||||||||||||4A20EEAF89018FF075899DDB332E9D35||2|||||||||||XXXX2224|Visa||||||||||||||||</directResponse> + </createCustomerProfileTransactionResponse> + eos + end + + def failed_purchase_using_stored_card_response + <<-eos + <?xml version="1.0" encoding="UTF-8"?> + <createCustomerProfileTransactionResponse xmlns="AnetApi/xml/v1/schema/AnetApiSchema.xsd" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <refId>1</refId> + <messages> + <resultCode>Error</resultCode> + <message> + <code>E00027</code> + <text>The credit card number is invalid.</text> + </message> + </messages> + <directResponse>3,1,6,The credit card number is invalid.,,P,0,1,Store Purchase,1.01,CC,auth_capture,2da01d7b583c706106e2,Longbob,Longsen,,,,n/a,,,,,,,,,,,,,,,,,,,13BA28EEA3593C13E2E3BC109D16E5D2,,,,,,,,,,,,,XXXX1222,,,,,,,,,,,,,,,,,</directResponse> + </createCustomerProfileTransactionResponse> + eos + end + + def successful_authorize_using_stored_card_response + <<-eos + <?xml version="1.0" encoding="UTF-8"?> + <createCustomerProfileTransactionResponse xmlns="AnetApi/xml/v1/schema/AnetApiSchema.xsd" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <refId>1</refId> + <messages> + <resultCode>Ok</resultCode> + <message> + <code>I00001</code> + <text>Successful.</text> + </message> + </messages> + <directResponse>1,1,1,This transaction has been approved.,GGHQ5R,Y,2235700640,1,Store Purchase,1.01,CC,auth_only,0bde9d39f8eb9443f2af,Longbob,Longsen,,,,n/a,,,,,,,,,,,,,,,,,,,E47E5CA4F1239B00D39A7F8C147215D3,,2,,,,,,,,,,,XXXX2224,Visa,,,,,,,,,,,,,,,,</directResponse> + </createCustomerProfileTransactionResponse> + eos + end + + def failed_authorize_using_stored_card_response + <<-eos + <?xml version="1.0" encoding="UTF-8"?> + <createCustomerProfileTransactionResponse xmlns="AnetApi/xml/v1/schema/AnetApiSchema.xsd" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <refId>1</refId> + <messages> + <resultCode>Error</resultCode> + <message> + <code>E00027</code> + <text>The credit card number is invalid.</text> + </message> + </messages> + <directResponse>3,1,6,The credit card number is invalid.,,P,0,1,Store Purchase,1.01,CC,auth_only,f632442ce056f9f82ee9,Longbob,Longsen,,,,n/a,,,,,,,,,,,,,,,,,,,13BA28EEA3593C13E2E3BC109D16E5D2,,,,,,,,,,,,,XXXX1222,,,,,,,,,,,,,,,,,</directResponse> + </createCustomerProfileTransactionResponse> + eos + end + + def successful_capture_using_stored_card_response + <<-eos + <?xml version="1.0" encoding="UTF-8"?> + <createCustomerProfileTransactionResponse xmlns="AnetApi/xml/v1/schema/AnetApiSchema.xsd" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <refId /> + <messages> + <resultCode>Ok</resultCode> + <message> + <code>I00001</code> + <text>Successful.</text> + </message> + </messages> + <directResponse>1,1,1,This transaction has been approved.,GGHQ5R,P,2235700640,1,,1.01,CC,prior_auth_capture,0bde9d39f8eb9443f2af,,,,,,,,,,,,,,,,,,,,,,,,,E47E5CA4F1239B00D39A7F8C147215D3,,,,,,,,,,,,,XXXX2224,Visa,,,,,,,,,,,,,,,,</directResponse> + </createCustomerProfileTransactionResponse> + eos + end + + def failed_capture_using_stored_card_response + <<-eos + <?xml version="1.0" encoding="UTF-8"?> + <createCustomerProfileTransactionResponse xmlns="AnetApi/xml/v1/schema/AnetApiSchema.xsd" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <refId /> + <messages> + <resultCode>Error</resultCode> + <message> + <code>E00027</code> + <text>The amount requested for settlement cannot be greater than the original amount authorized.</text> + </message> + </messages> + <directResponse>3,2,47,The amount requested for settlement cannot be greater than the original amount authorized.,,P,0,,,41.01,CC,prior_auth_capture,,,,,,,,,,,,,,,,,,,,,,,,,,8A556B125A1DA070AF5A84B798B7FBF7,,,,,,,,,,,,,,Visa,,,,,,,,,,,,,,,,</directResponse> + </createCustomerProfileTransactionResponse> + eos + end + + def failed_refund_using_stored_card_response + <<-eos + <?xml version="1.0" encoding="UTF-8"?> + <createCustomerProfileTransactionResponse xmlns="AnetApi/xml/v1/schema/AnetApiSchema.xsd" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <refId>1</refId> + <messages> + <resultCode>Error</resultCode> + <message> + <code>E00040</code> + <text>The record cannot be found.</text> + </message> + </messages> + </createCustomerProfileTransactionResponse> + eos + end + + def successful_void_using_stored_card_response + <<-eos + <?xml version="1.0" encoding="UTF-8"?> + <createCustomerProfileTransactionResponse xmlns="AnetApi/xml/v1/schema/AnetApiSchema.xsd" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <refId /> + <messages> + <resultCode>Ok</resultCode> + <message> + <code>I00001</code> + <text>Successful.</text> + </message> + </messages> + <directResponse>1,1,1,This transaction has been approved.,3R9YE2,P,2235701141,1,,0.00,CC,void,becdb509b35a32c30e97,,,,,,,,,,,,,,,,,,,,,,,,,C3C4B846B9D5A37D14462C2BF5B924FD,,,,,,,,,,,,,XXXX2224,Visa,,,,,,,,,,,,,,,,</directResponse> + </createCustomerProfileTransactionResponse> + eos + end + + def failed_void_using_stored_card_response + <<-eos + <?xml version="1.0" encoding="UTF-8"?> + <createCustomerProfileTransactionResponse xmlns="AnetApi/xml/v1/schema/AnetApiSchema.xsd" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <refId /> + <messages> + <resultCode>Ok</resultCode> + <message> + <code>I00001</code> + <text>Successful.</text> + </message> + </messages> + <directResponse>1,1,310,This transaction has already been voided.,,P,0,,,0.00,CC,void,,,,,,,,,,,,,,,,,,,,,,,,,,FD9FAE70BEF461997A6C15D7D597658D,,,,,,,,,,,,,,Visa,,,,,,,,,,,,,,,,</directResponse> + </createCustomerProfileTransactionResponse> + eos + end + + def network_tokenization_not_supported_response + <<-eos + <?xml version="1.0" encoding="utf-8"?> + <createTransactionResponse xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="AnetApi/xml/v1/schema/AnetApiSchema.xsd"> + <refId>123456</refId> + <messages> + <resultCode>Error</resultCode> + <message> + <code>E00155</code> + <text>This processor does not support this method of submitting payment data</text> + </message> + </messages> + <transactionResponse> + <responseCode>3</responseCode> + <authCode/> + <avsResultCode>P</avsResultCode> + <cvvResultCode/> + <cavvResultCode/> + <transId>0</transId> + <refTransID/> + <transHash>DA56E64108957174C5AE9BE466914741</transHash> + <testRequest>0</testRequest> + <accountNumber>XXXX0001</accountNumber> + <accountType/> + <errors> + <error> + <errorCode>155</errorCode> + <errorText>This processor does not support this method of submitting payment data</errorText> + </error> + </errors> + <userFields> + <userField> + <name>MerchantDefinedFieldName1</name> + <value>MerchantDefinedFieldValue1</value> + </userField> + <userField> + <name>favorite_color</name> + <value>blue</value> + </userField> + </userFields> + </transactionResponse> + </createTransactionResponse> + eos + end + + def credentials_are_legit_response + <<-eos + <?xml version="1.0" encoding="UTF-8"?> + <authenticateTestResponse xmlns="AnetApi/xml/v1/schema/AnetApiSchema.xsd" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <messages> + <resultCode>Ok</resultCode> + <message> + <code>I00001</code> + <text>Successful.</text> + </message> + </messages> + </authenticateTestResponse> + eos + end + + def credentials_are_bogus_response + <<-eos + <?xml version="1.0" encoding="UTF-8"?> + <authenticateTestResponse xmlns="AnetApi/xml/v1/schema/AnetApiSchema.xsd" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <messages> + <resultCode>Error</resultCode> + <message> + <code>E00007</code> + <text>User authentication failed due to invalid authentication values.</text> + </message> + </messages> + </authenticateTestResponse> + eos + end + + def failed_refund_for_unsettled_payment_response + <<-eos + <?xml version="1.0" encoding="utf-8"?> + <createTransactionResponse xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="AnetApi/xml/v1/schema/AnetApiSchema.xsd"> + <messages> + <resultCode>Error</resultCode> + <message> + <code>E00027</code> + <text>The transaction was unsuccessful.</text> + </message> + </messages> + <transactionResponse> + <responseCode>3</responseCode> + <authCode/> + <avsResultCode>P</avsResultCode> + <cvvResultCode/> + <cavvResultCode/> + <transId>0</transId> + <refTransID/> + <transHash/> + <testRequest>0</testRequest> + <accountNumber>XXXX0001</accountNumber> + <accountType>Visa</accountType> + <errors> + <error> + <errorCode>54</errorCode> + <errorText>The referenced transaction does not meet the criteria for issuing a credit.</errorText> + </error> + </errors> + <userFields/> + <transHashSha2/> + </transactionResponse> + </createTransactionResponse> + eos end end diff --git a/test/unit/gateways/axcessms_test.rb b/test/unit/gateways/axcessms_test.rb new file mode 100644 index 00000000000..e87a092498b --- /dev/null +++ b/test/unit/gateways/axcessms_test.rb @@ -0,0 +1,440 @@ +require 'test_helper' + +class AxcessmsTest < Test::Unit::TestCase + include CommStub + + TEST_AUTHORIZATION = '8a8294494830a3bb01483174f1827b9a' + TEST_PURCHASE = '8a82944a4830c4810148350aeeec5e58' + + def setup + @gateway = AxcessmsGateway.new(fixtures(:axcessms)) + + @amount = 1500 + @credit_card = credit_card('4200000000000000', month: 05, year: 2022) + @declined_card = credit_card('4444444444444444', month: 05, year: 2022) + @mode = 'CONNECTOR_TEST' + + @options = { + order_id: generate_unique_id, + email: 'customer@example.com', + description: "Order Number #{Time.now.to_f.divmod(2473)[1]}", + ip: '0.0.0.0', + mode: @mode, + billing_address: { + :address1 => '10 Marklar St', + :address2 => 'Musselburgh', + :city => 'Dunedin', + :zip => '9013', + :state => 'Otago', + :country => 'NZ' + } + } + end + + def test_successful_authorize + @gateway.expects(:ssl_post).returns(successful_authorize_response) + response = @gateway.authorize(@amount, @credit_card, @options) + assert_success response + assert response.test? + assert_equal TEST_AUTHORIZATION, response.authorization + end + + def test_successful_purchase + @gateway.expects(:ssl_post).returns(successful_purchase_response) + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal TEST_PURCHASE, response.authorization + end + + def test_successful_purchase_with_minimal_options + @gateway.expects(:ssl_post).returns(successful_purchase_response) + response = @gateway.purchase(@amount, @credit_card, billing_address: address) + assert_success response + assert_equal TEST_PURCHASE, response.authorization + end + + def test_successful_reference_purchase + @gateway.expects(:ssl_post).returns(successful_purchase_response) + response = @gateway.purchase(@amount, '12345', @options) + assert_success response + assert_equal TEST_PURCHASE, response.authorization + end + + def test_successful_reference_authorize + @gateway.expects(:ssl_post).returns(successful_authorize_response) + response = @gateway.authorize(@amount, '12345', @options) + assert_success response + assert_equal TEST_AUTHORIZATION, response.authorization + end + + def test_successful_capture + @gateway.expects(:ssl_post).returns(successful_capture_response) + response = @gateway.capture(@amount, TEST_AUTHORIZATION, @options) + assert_success response + assert response.params['reference_id'], TEST_AUTHORIZATION + end + + def test_successful_refund + @gateway.expects(:ssl_post).returns(successful_refund_response) + response = @gateway.refund(@amount - 30, TEST_PURCHASE, @options) + assert_success response + assert response.params['reference_id'], TEST_PURCHASE + end + + def test_successful_void + @gateway.expects(:ssl_post).returns(successful_void_response) + response = @gateway.void(TEST_AUTHORIZATION, @options) + assert_success response + assert response.params['reference_id'], TEST_AUTHORIZATION + end + + def test_unauthorized_capture + @gateway.expects(:ssl_post).returns(failed_capture_response) + response = @gateway.capture(@amount, 'authorization', @options) + assert_failure response + assert_equal 'Reference Error - capture needs at least one successful transaction of type (PA)', response.message + end + + def test_failed_refund + @gateway.expects(:ssl_post).returns(failed_refund_response) + response = @gateway.refund(@amount - 30, 'authorization', @options) + assert_failure response + assert_equal 'Configuration Validation - Invalid payment data. You are not configured for this currency or sub type (country or brand)', response.message + end + + def test_failed_void + @gateway.expects(:ssl_post).returns(failed_void_response) + response = @gateway.refund(@amount - 30, 'authorization', @options) + assert_failure response + assert_equal 'Reference Error - reversal needs at least one successful transaction of type (CP or DB or RB or PA)', response.message + end + + def test_authorize_using_reference_sets_proper_elements + stub_comms do + @gateway.authorize(@amount, 'MY_AUTHORIZE_VALUE', @options) + end.check_request do |endpoint, body, headers| + assert_xpath_text(body, '//ReferenceID', 'MY_AUTHORIZE_VALUE') + assert_no_match(/<Account>/, body) + end.respond_with(successful_authorize_response) + end + + def test_purchase_using_reference_sets_proper_elements + stub_comms do + @gateway.purchase(@amount, 'MY_AUTHORIZE_VALUE', @options) + end.check_request do |endpoint, body, headers| + assert_xpath_text(body, '//ReferenceID', 'MY_AUTHORIZE_VALUE') + assert_no_match(/<Account>/, body) + end.respond_with(successful_authorize_response) + end + + def test_setting_mode_sets_proper_element + stub_comms do + @gateway.purchase(@amount, 'MY_AUTHORIZE_VALUE', {mode: 'CRAZY_TEST_MODE'}) + end.check_request do |endpoint, body, headers| + assert_xpath_text(body, '//Transaction/@mode', 'CRAZY_TEST_MODE') + end.respond_with(successful_authorize_response) + end + + def test_defaults_to_integrator_test + stub_comms do + @gateway.purchase(@amount, 'MY_AUTHORIZE_VALUE', {}) + end.check_request do |endpoint, body, headers| + assert_xpath_text(body, '//Transaction/@mode', 'INTEGRATOR_TEST') + end.respond_with(successful_authorize_response) + end + + def test_successful_verify + response = stub_comms do + @gateway.verify(@credit_card, @options) + end.respond_with(successful_authorize_response, successful_void_response) + assert_success response + end + + def test_successful_verify_with_failed_void + response = stub_comms do + @gateway.verify(@credit_card, @options) + end.respond_with(successful_authorize_response, failed_void_response) + assert_success response + end + + def test_failed_verify + response = stub_comms do + @gateway.verify(@credit_card, @options) + end.respond_with(failed_authorize_response, successful_void_response) + assert_failure response + end + + private + + def assert_xpath_text(xml, xpath, expected_text) + xml = CGI.unescape(xml.gsub('load=', '')) + root = REXML::Document.new(xml).root + element = REXML::XPath.first(root, xpath) + actual_text = xpath.include?('@') ? element.value : element.text + assert_equal expected_text, actual_text, %{Expected to find the text "#{expected_text}" within the XML element with path "#{xpath}", but instead found the text "#{actual_text}" in the following XML:\n#{xml}} + end + + def successful_authorize_response + <<-XML + <?xml version="1.0" encoding="UTF-8"?> + <Response version="1.0"> + <Transaction mode="INTEGRATOR_TEST" channel="8a82941745bc8b070145bc8d545a0019" response="SYNC"> + <Identification> + <ShortID>7159.9714.3714</ShortID> + <UniqueID>8a8294494830a3bb01483174f1827b9a</UniqueID> + <TransactionID>64ac051ce71ede9fbf6d9d9f17dd43db</TransactionID> + </Identification> + <Payment code="CC.PA"> + <Clearing> + <Amount>10.00</Amount> + <Currency>GBP</Currency> + <Descriptor>7159.9714.3714 Recurring </Descriptor> + <FxRate>1.0</FxRate> + <FxSource>INTERN</FxSource> + <FxDate>2014-09-01 13:43:40</FxDate> + </Clearing> + </Payment> + <Processing code="CC.PA.90.00"> + <Timestamp>2014-09-01 13:43:40</Timestamp> + <Result>ACK</Result> + <Status code="90">NEW</Status> + <Reason code="00">Successful Processing</Reason> + <Return code="000.100.110">Request successfully processed in 'Merchant in Connector Test Mode'</Return> + <Risk score="100" /> + </Processing> + </Transaction> + </Response> + XML + end + + def successful_purchase_response + <<-XML + <?xml version="1.0" encoding="UTF-8"?> + <Response version="1.0"> + <Transaction mode="INTEGRATOR_TEST" channel="8a82941745bc8b070145bc8d545a0019" response="SYNC"> + <Identification> + <ShortID>5120.3652.8802</ShortID> + <UniqueID>8a82944a4830c4810148350aeeec5e58</UniqueID> + <TransactionID>38a96c022cafb4ef2e308c119ae34452</TransactionID> + </Identification> + <Payment code="CC.DB"> + <Clearing> + <Amount>10.00</Amount> + <Currency>GBP</Currency> + <Descriptor>5120.3652.8802 Recurring </Descriptor> + <FxRate>1.0</FxRate> + <FxSource>INTERN</FxSource> + <FxDate>2014-09-02 06:26:22</FxDate> + </Clearing> + </Payment> + <Processing code="CC.DB.90.00"> + <Timestamp>2014-09-02 06:26:22</Timestamp> + <Result>ACK</Result> + <Status code="90">NEW</Status> + <Reason code="00">Successful Processing</Reason> + <Return code="000.100.110">Request successfully processed in 'Merchant in Integrator Test Mode'</Return> + <Risk score="100" /> + </Processing> + </Transaction> + </Response> + XML + end + + def successful_capture_response + <<-XML + <?xml version="1.0" encoding="UTF-8"?> + <Response version="1.0"> + <Transaction mode="INTEGRATOR_TEST" channel="8a82941745bc8b070145bc8d545a0019" response="SYNC"> + <Identification> + <ShortID>9318.6958.1986</ShortID> + <UniqueID>8a8294494830a3bb0148351b6546087a</UniqueID> + <TransactionID>2a45729f95aec98e263461e124593a50</TransactionID> + <ReferenceID>8a8294494830a3bb0148351b5c9d086e</ReferenceID> + </Identification> + <Payment code="CC.CP"> + <Clearing> + <Amount>10.00</Amount> + <Currency>GBP</Currency> + <Descriptor>9318.6958.1986 Recurring</Descriptor> + <FxRate>1.0</FxRate> + <FxSource>INTERN</FxSource> + <FxDate>2014-09-02 06:44:21</FxDate> + </Clearing> + </Payment> + <Processing code="CC.CP.90.00"> + <Timestamp>2014-09-02 06:44:21</Timestamp> + <Result>ACK</Result> + <Status code="90">NEW</Status> + <Reason code="00">Successful Processing</Reason> + <Return code="000.100.110">Request successfully processed in 'Merchant in Integrator Test Mode'</Return> + <Risk score="0" /> + </Processing> + </Transaction> + </Response> + XML + end + + def successful_refund_response + <<-XML + <?xml version="1.0" encoding="UTF-8"?> + <Response version="1.0"> + <Transaction mode="INTEGRATOR_TEST" channel="8a82941745bc8b070145bc8d545a0019" response="SYNC"> + <Identification> + <ShortID>8073.1553.0402</ShortID> + <UniqueID>8a82944a4830c4810148350aeeec5e58</UniqueID> + <TransactionID>94e8c527be2ffb1c120188b4c9bec521</TransactionID> + <ReferenceID>8a82944a4830c4810148352533ef630f</ReferenceID> + </Identification> + <Payment code="CC.RF"> + <Clearing> + <Amount>10.00</Amount> + <Currency>GBP</Currency> + <Descriptor>8073.1553.0402 Recurring</Descriptor> + <FxRate>1.0</FxRate> + <FxSource>INTERN</FxSource> + <FxDate>2014-09-02 06:55:06</FxDate> + </Clearing> + </Payment> + <Processing code="CC.RF.90.00"> + <Timestamp>2014-09-02 06:55:06</Timestamp> + <Result>ACK</Result> + <Status code="90">NEW</Status> + <Reason code="00">Successful Processing</Reason> + <Return code="000.100.110">Request successfully processed in 'Merchant in Integrator Test Mode'</Return> + </Processing> + </Transaction> + </Response> + XML + end + + def successful_void_response + <<-XML + <?xml version="1.0" encoding="UTF-8"?> + <Response version="1.0"> + <Transaction mode="INTEGRATOR_TEST" channel="8a82941745bc8b070145bc8d545a0019" response="SYNC"> + <Identification> + <ShortID>7729.5579.2034</ShortID> + <UniqueID>8a8294494830a3bb01483528826e0adf</UniqueID> + <TransactionID>c3238be8ddc9dc269766add6d3d1b48f</TransactionID> + <ReferenceID>8a82944a4830c481014835287b136390</ReferenceID> + </Identification> + <Payment code="CC.RV"> + <Clearing> + <Amount>10.00</Amount> + <Currency>GBP</Currency> + <Descriptor>7729.5579.2034 Recurring</Descriptor> + <FxRate>1.0</FxRate> + <FxSource>INTERN</FxSource> + <FxDate>2014-09-02 06:58:40</FxDate> + </Clearing> + </Payment> + <Processing code="CC.RV.90.00"> + <Timestamp>2014-09-02 06:58:40</Timestamp> + <Result>ACK</Result> + <Status code="90">NEW</Status> + <Reason code="00">Successful Processing</Reason> + <Return code="000.100.110">Request successfully processed in 'Merchant in Integrator Test Mode'</Return> + <Risk score="0" /> + </Processing> + </Transaction> + </Response> + XML + end + + def failed_authorize_response + <<-XML + <?xml version="1.0" encoding="UTF-8"?> + <Response version="1.0"> + <Transaction mode="CONNECTOR_TEST" channel="8a82941745bc8b070145bc8d545a0019" response="SYNC"> + <Identification> + <ShortID>4272.9698.1666</ShortID> + <UniqueID>8a82944a4bc0dc29014bc2c9aaff5d30</UniqueID> + <TransactionID>72229d5b6311fddfaf5203c9af083cfa</TransactionID> + </Identification> + <Payment code="CC.PA" /> + <Processing code="CC.PA.70.40"> + <Timestamp>2015-02-25 22:09:31</Timestamp> + <Result>NOK</Result> + <Status code="70">REJECTED_VALIDATION</Status> + <Reason code="40">Account Validation</Reason> + <Return code="100.100.101">invalid creditcard, bank account number or bank name</Return> + </Processing> + </Transaction> + </Response> + XML + end + + def failed_capture_response + <<-XML + <?xml version="1.0" encoding="UTF-8"?> + <Response version="1.0"> + <Transaction mode="INTEGRATOR_TEST" channel="8a82941745bc8b070145bc8d545a0019" response="SYNC"> + <Identification> + <ShortID>6526.9670.7746</ShortID> + <UniqueID>8a8294494830a3bb0148352d46c90bdf</UniqueID> + <TransactionID>0c99fae829450624ba6151bed0957f05</TransactionID> + <ReferenceID>authorization</ReferenceID> + </Identification> + <Payment code="CC.CP" /> + <Processing code="CC.CP.70.30"> + <Timestamp>2014-09-02 07:03:52</Timestamp> + <Result>NOK</Result> + <Status code="70">REJECTED_VALIDATION</Status> + <Reason code="30">Reference Error</Reason> + <Return code="700.400.510">capture needs at least one successful transaction of type (PA)</Return> + <Risk score="0" /> + </Processing> + </Transaction> + </Response> + XML + end + + def failed_refund_response + <<-XML + <?xml version="1.0" encoding="UTF-8"?> + <Response version="1.0"> + <Transaction mode="INTEGRATOR_TEST" channel="8a82941745bc8b070145bc8d545a0019" response="SYNC"> + <Identification> + <ShortID>3864.0873.5394</ShortID> + <UniqueID>8a8294494830a3bb0148353097580ca5</UniqueID> + <TransactionID>2016aa53f62f498a6452ff0be1fbd060</TransactionID> + <ReferenceID>authorization</ReferenceID> + </Identification> + <Payment code="CC.RF" /> + <Processing code="CC.RF.70.10"> + <Timestamp>2014-09-02 07:07:30</Timestamp> + <Result>NOK</Result> + <Status code="70">REJECTED_VALIDATION</Status> + <Reason code="10">Configuration Validation</Reason> + <Return code="600.200.500">Invalid payment data. You are not configured for this currency or sub type (country or brand)</Return> + </Processing> + </Transaction> + </Response> + XML + end + + def failed_void_response + <<-XML + <?xml version="1.0" encoding="UTF-8"?> + <Response version="1.0"> + <Transaction mode="INTEGRATOR_TEST" channel="8a82941745bc8b070145bc8d545a0019" response="SYNC"> + <Identification> + <ShortID>8395.2778.5122</ShortID> + <UniqueID>8a82944a4830c4810148353115b96501</UniqueID> + <TransactionID>abe0ea6036cbf36b1bed45031b3263a7</TransactionID> + <ReferenceID>authorization</ReferenceID> + </Identification> + <Payment code="CC.RV" /> + <Processing code="CC.RV.70.30"> + <Timestamp>2014-09-02 07:08:02</Timestamp> + <Result>NOK</Result> + <Status code="70">REJECTED_VALIDATION</Status> + <Reason code="30">Reference Error</Reason> + <Return code="700.400.530">reversal needs at least one successful transaction of type (CP or DB or RB or PA)</Return> + <Risk score="0" /> + </Processing> + </Transaction> + </Response> + XML + end +end diff --git a/test/unit/gateways/balanced_test.rb b/test/unit/gateways/balanced_test.rb index 174d12998ff..68e98b4f0d4 100644 --- a/test/unit/gateways/balanced_test.rb +++ b/test/unit/gateways/balanced_test.rb @@ -4,684 +4,866 @@ class BalancedTest < Test::Unit::TestCase include CommStub def setup - @marketplace_uri = '/v1/marketplaces/TEST-MP73SaFdpQePv9dOaG5wXOGO' - - marketplace_uris = { - 'uri' => @marketplace_uri, - 'holds_uri' => @marketplace_uri + '/holds', - 'debits_uri' => @marketplace_uri + '/debits', - 'cards_uri' => @marketplace_uri + '/cards', - 'accounts_uri' => @marketplace_uri + '/accounts', - 'refunds_uri' => @marketplace_uri + '/refunds', - } - @gateway = BalancedGateway.new( - :login => 'e1c5ad38d1c711e1b36c026ba7e239a9', - :marketplace => marketplace_uris + login: 'e1c5ad38d1c711e1b36c026ba7e239a9' ) - @credit_card = credit_card @amount = 100 + @credit_card = credit_card('4111111111111111') @options = { - :email => 'john.buyer@example.org', - :billing_address => address, - :description => 'Shopify Purchase' + email: 'john.buyer@example.org', + billing_address: address, + description: 'Shopify Purchase' } end def test_successful_purchase - @gateway.expects(:ssl_request).times(4).returns( - successful_account_response - ).then.returns( - successful_card_response + @gateway.expects(:ssl_request).times(2).returns( + cards_response ).then.returns( - successful_account_response - ).then.returns( - successful_purchase_response + debits_response ) - assert response = @gateway.purchase(@amount, @credit_card, @options) - assert_instance_of Response, response assert_success response - assert_equal '/v1/marketplaces/TEST-MP73SaFdpQePv9dOaG5wXOGO/debits/WD2x6vLS7RzHYEcdymqRyNAO', response.authorization - assert response.test? + assert_equal 'Success', response.message + assert_equal @amount, response.params['debits'][0]['amount'] end - def test_successful_purchase_with_existing_account - @gateway.expects(:ssl_request).times(4).returns( - failed_account_response - ).then.returns( - successful_card_response - ).then.returns( - successful_account_response - ).then.returns( - successful_purchase_response - ) + def test_successful_purchase_with_outside_token + response = stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, '/cards/CCVOX2d7Ar6Ze5TOxHsebeH', @options) + end.check_request do |method, endpoint, data, headers| + assert_equal('https://api.balancedpayments.com/cards/CCVOX2d7Ar6Ze5TOxHsebeH/debits', endpoint) + end.respond_with(debits_response) - assert response = @gateway.purchase(@amount, @credit_card, @options) - assert_instance_of Response, response assert_success response - assert_equal '/v1/marketplaces/TEST-MP73SaFdpQePv9dOaG5wXOGO/debits/WD2x6vLS7RzHYEcdymqRyNAO', response.authorization - assert response.test? + assert_equal 'Success', response.message + assert_equal @amount, response.params['debits'][0]['amount'] end - def test_successful_purchase_with_existing_account_uri - @gateway.expects(:ssl_request).times(3).returns( - successful_card_response - ).then.returns( - successful_account_response + def test_invalid_card + @gateway.expects(:ssl_request).times(2).returns( + cards_response ).then.returns( - successful_purchase_response + declined_response ) - options = @options.clone - options[:account_uri] = '/v1/marketplaces/TEST-MP73SaFdpQePv9dOaG5wXOGO/accounts/AC58ZKWuGoyQEnDy9ENGRGSq' - assert response = @gateway.purchase(@amount, @credit_card, options) - assert_instance_of Response, response - assert_success response - assert_equal '/v1/marketplaces/TEST-MP73SaFdpQePv9dOaG5wXOGO/debits/WD2x6vLS7RzHYEcdymqRyNAO', response.authorization - assert response.test? + assert response = @gateway.purchase(@amount, credit_card('4222222222222220'), @options) + assert_failure response + assert_match %r{Customer call bank}i, response.message end - def test_successful_purchase_with_existing_card - @gateway.expects(:ssl_request).times(1).returns( - successful_purchase_response + def test_invalid_email + @gateway.expects(:ssl_request).returns( + bad_email_response ) - options = @options.clone - credit_card = '/v1/marketplaces/TEST-MP73SaFdpQePv9dOaG5wXOGO/cards -/CC6r6kLUcxW3MxG3AmZoiuTf' - options[:account_uri] = '/v1/marketplaces/TEST-MP73SaFdpQePv9dOaG5wXOGO/accounts/AC58ZKWuGoyQEnDy9ENGRGSq' - assert response = @gateway.purchase(@amount, credit_card, options) - assert_instance_of Response, response - assert_success response - assert_equal '/v1/marketplaces/TEST-MP73SaFdpQePv9dOaG5wXOGO/debits/WD2x6vLS7RzHYEcdymqRyNAO', response.authorization - assert response.test? - end - - def test_bad_email - @gateway.stubs(:ssl_request).returns(failed_account_response_bad_email).then.returns(successful_card_response) - - assert response = @gateway.purchase(@amount, @credit_card, @options) + assert response = @gateway.purchase(@amount, @credit_card, @options.merge(email: 'invalid_email')) assert_failure response - assert response.test? - assert_match /must be a valid email address/, response.message + assert_match %r{Invalid field.*email}i, response.message end def test_unsuccessful_purchase - @gateway.expects(:ssl_request).times(4).returns( - successful_account_response - ).then.returns( - successful_card_response + @gateway.expects(:ssl_request).times(2).returns( + cards_response ).then.returns( - successful_account_response - ).then.returns( - failed_purchase_response + account_frozen_response ) - assert response = @gateway.purchase(@amount, @credit_card, @options) + assert response = @gateway.purchase(@amount, credit_card('4444444444444448'), @options) assert_failure response - assert response.test? + assert_match %r{Account Frozen}i, response.message end - def test_successful_authorization - @gateway.expects(:ssl_request).times(4).returns( - successful_account_response + def test_passing_appears_on_statement + @gateway.expects(:ssl_request).times(2).returns( + cards_response ).then.returns( - successful_card_response - ).then.returns(successful_account_response).then.returns( - successful_hold_response + appears_on_response ) - assert response = @gateway.authorize(@amount, @credit_card, @options) - assert_instance_of Response, response + options = @options.merge(appears_on_statement_as: 'Homer Electric') + assert response = @gateway.purchase(@amount, @credit_card, options) + assert_success response - assert_equal '/v1/marketplaces/TEST-MP73SaFdpQePv9dOaG5wXOGO/holds/HL7dYMhpVBcqAYqxLF5mZtQ5', response.authorization - assert response.test? + assert_equal 'BAL*Homer Electric', response.params['debits'][0]['appears_on_statement_as'] end - def test_unsuccessful_authorization - @gateway.expects(:ssl_request).times(4).returns( - successful_account_response - ).then.returns( - successful_card_response + def test_authorize_and_capture + @gateway.expects(:ssl_request).times(3).returns( + cards_response ).then.returns( - successful_account_response + holds_response ).then.returns( - failed_purchase_response + authorized_debits_response ) - assert response = @gateway.authorize(@amount, @credit_card, @options) - assert_instance_of Response, response - assert_failure response - assert response.test? - end + amount = @amount + assert auth = @gateway.authorize(amount, @credit_card, @options) + assert_success auth + assert_equal 'Success', auth.message - def test_successful_authorization_capture - @gateway.expects(:ssl_request).times(1).returns( - successful_purchase_response - ) - hold_uri = '/v1/marketplaces/TEST-MP73SaFdpQePv9dOaG5wXOGO/holds/HL7dYMhpVBcqAYqxLF5mZtQ5' - assert response = @gateway.capture(nil, hold_uri) # captures the full amount - assert_instance_of Response, response - assert_success response - assert_equal '/v1/marketplaces/TEST-MP73SaFdpQePv9dOaG5wXOGO/debits/WD2x6vLS7RzHYEcdymqRyNAO', response.authorization - assert response.test? + assert capture = @gateway.capture(amount, auth.authorization) + assert_success capture + assert_equal amount, capture.params['debits'][0]['amount'] end - def test_successful_authorization_capture_with_on_behalf_of_uri - @gateway.expects(:ssl_request).times(1).returns( - successful_purchase_response + def test_authorize_and_capture_partial + @gateway.expects(:ssl_request).times(3).returns( + cards_response + ).then.returns( + holds_response + ).then.returns( + authorized_partial_debits_response ) - hold_uri = '/v1/marketplaces/TEST-MP73SaFdpQePv9dOaG5wXOGO/holds/HL7dYMhpVBcqAYqxLF5mZtQ5' - on_behalf_of_uri = '/v1/marketplaces/TEST-MP73SaFdpQePv9dOaG5wXOGO/accounts/AC73SN17anKkjk6Y1sVe2uaq' - - response = stub_comms do - @gateway.capture(nil, hold_uri, :on_behalf_of_uri => on_behalf_of_uri) - end.check_request do |endpoint, data, headers| - assert_match(/on_behalf_of_uri=\/v1\/marketplaces\/TEST-MP73SaFdpQePv9dOaG5wXOGO\/accounts\/AC73SN17anKkjk6Y1sVe2uaq/, data) - end.respond_with(successful_purchase_response) + amount = @amount + assert auth = @gateway.authorize(amount, @credit_card, @options) + assert_success auth + assert_equal 'Success', auth.message - assert_instance_of Response, response - assert_success response - assert_equal '/v1/marketplaces/TEST-MP73SaFdpQePv9dOaG5wXOGO/debits/WD2x6vLS7RzHYEcdymqRyNAO', response.authorization - assert response.test? + assert capture = @gateway.capture(amount / 2, auth.authorization) + assert_success capture + assert_equal amount / 2, capture.params['debits'][0]['amount'] end - def test_unsuccessful_authorization_capture - @gateway.expects(:ssl_request).times(1).returns( - failed_purchase_response + def test_failed_capture + @gateway.expects(:ssl_request).returns( + method_not_allowed_response ) - hold_uri = '/v1/marketplaces/TEST-MP73SaFdpQePv9dOaG5wXOGO/holds/HL7dYMhpVBcqAYqxLF5mZtQ5' - assert response = @gateway.capture(0, hold_uri) - assert_instance_of Response, response + + assert response = @gateway.capture(@amount, 'bogus') assert_failure response - assert response.test? end - def test_void_authorization_success - @gateway.expects(:ssl_request).times(1).returns( - void_hold_response + def test_void_authorization + @gateway.expects(:ssl_request).times(3).returns( + cards_response + ).then.returns( + holds_response + ).then.returns( + voided_hold_response ) - hold_uri = '/v1/marketplaces/TEST-MP73SaFdpQePv9dOaG5wXOGO/holds/HL7dYMhpVBcqAYqxLF5mZtQ5' - response = @gateway.void(hold_uri) - assert_instance_of Response, response - assert_success response - assert_equal hold_uri, response.authorization - assert response.test? - end - def test_void_authorization_failure - @gateway.expects(:ssl_request).times(1).returns( - void_hold_response_failure - ) - hold_uri = '/v1/marketplaces/TEST-MP73SaFdpQePv9dOaG5wXOGO/holds/HL7dYMhpVBcqAYqxLF5mZtQ5' - assert response = @gateway.void(hold_uri) - assert_instance_of Response, response - assert_failure response - assert response.test? + amount = @amount + assert auth = @gateway.authorize(amount, @credit_card, @options) + assert_success auth + number = auth.params['card_holds'][0]['href'] + assert void = @gateway.void(number) + assert_success void + assert void.params['card_holds'][0]['voided_at'] end def test_refund_purchase - @gateway.expects(:ssl_request).times(1).returns( - successful_refund_response + @gateway.expects(:ssl_request).times(3).returns( + cards_response + ).then.returns( + debits_response + ).then.returns( + refunds_response ) - debit_uri = '/v1/marketplaces/TEST-MP6IEymJ6ynwnSoqJQnUTacN/debits/WD2Nkre6GkWAV1A52YgLWEkh' - refund_uri = '/v1/marketplaces/TEST-MP6IEymJ6ynwnSoqJQnUTacN/refunds/RF3GhhG5I3AgrjjXsdkRFQDA' - assert refund = @gateway.refund(@amount, debit_uri) - assert_instance_of Response, refund + assert debit = @gateway.purchase(@amount, @credit_card, @options) + assert_success debit + + assert refund = @gateway.refund(@amount, debit.authorization) assert_success refund - assert refund.test? - assert_equal refund.authorization, refund_uri + assert_equal @amount, refund.params['refunds'][0]['amount'] end - def test_refund_purchase_failure - @gateway.expects(:ssl_request).times(1).returns( - failed_refund_response + def test_refund_partial_purchase + @gateway.expects(:ssl_request).times(3).returns( + cards_response + ).then.returns( + debits_response + ).then.returns( + partial_refunds_response ) - debit_uri = '/v1/marketplaces/TEST-MP6IEymJ6ynwnSoqJQnUTacN/debits/WD2Nkre6GkWAV1A52YgLWEkh' - assert refund = @gateway.refund(@amount, debit_uri) - assert_instance_of Response, refund - assert_failure refund - assert refund.test? - end - - def test_deprecated_refund_purchase - assert_deprecation_warning("Calling the refund method without an amount parameter is deprecated and will be removed in a future version.", @gateway) do - @gateway.expects(:ssl_request).times(1).returns( - successful_refund_response - ) - - debit_uri = '/v1/marketplaces/TEST-MP6IEymJ6ynwnSoqJQnUTacN/debits/WD2Nkre6GkWAV1A52YgLWEkh' - refund_uri = '/v1/marketplaces/TEST-MP6IEymJ6ynwnSoqJQnUTacN/refunds/RF3GhhG5I3AgrjjXsdkRFQDA' - assert refund = @gateway.refund(debit_uri) - assert_instance_of Response, refund - assert_success refund - assert refund.test? - assert_equal refund.authorization, refund_uri - end + assert debit = @gateway.purchase(@amount, @credit_card, @options) + assert_success debit + + assert refund = @gateway.refund(@amount / 2, debit.authorization) + assert_success refund + assert_equal @amount / 2, refund.params['refunds'][0]['amount'] end - def test_refund_with_nil_debit_uri - @gateway.expects(:ssl_request).times(1).returns( - failed_refund_response + def test_refund_pending_status + @gateway.expects(:ssl_request).times(3).returns( + cards_response + ).then.returns( + debits_response + ).then.returns( + refunds_pending_response ) - assert refund = @gateway.refund(nil, nil) - assert_instance_of Response, refund - assert_failure refund + assert debit = @gateway.purchase(@amount, @credit_card, @options) + assert_success debit + + assert refund = @gateway.refund(@amount, debit.authorization) + assert_success refund + assert_equal 'pending', refund.params['refunds'][0]['status'] + assert_equal @amount, refund.params['refunds'][0]['amount'] end def test_store - @gateway.expects(:ssl_request).times(3).returns( - successful_account_response # create account - ).then.returns( - successful_card_response # create card - ).then.returns( - successful_account_response # associate card to account + @gateway.expects(:ssl_request).returns( + cards_response ) - card_uri = '/v1/marketplaces/TEST-MP73SaFdpQePv9dOaG5wXOGO/cards/CC6r6kLUcxW3MxG3AmZoiuTf' + new_email_address = '%d@example.org' % Time.now assert response = @gateway.store(@credit_card, { - :email=>'john.buyer@example.org' + email: new_email_address }) - assert_instance_of String, response - assert_equal card_uri, response + assert_instance_of String, response.authorization + end + + def test_successful_purchase_with_legacy_outside_token + legacy_outside_token = '/v1/marketplaces/MP6oR9hHNlu2BLVsRRoQL3Gg/cards/CC7m1Mtqk6rVJo5tcD1qitAC' + + response = stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, legacy_outside_token, @options) + end.check_request do |method, endpoint, data, headers| + assert_equal('https://api.balancedpayments.com/cards/CC7m1Mtqk6rVJo5tcD1qitAC/debits', endpoint) + end.respond_with(debits_response) + + assert_success response + assert_equal 'Success', response.message + assert_equal @amount, response.params['debits'][0]['amount'] end - def test_ensure_does_not_respond_to_credit - assert !@gateway.respond_to?(:credit) + def test_capturing_legacy_authorizations + v1_authorization = '/v1/marketplaces/TEST-MP73SaFdpQePv9dOaG5wXOGO/holds/HL7dYMhpVBcqAYqxLF5mZtQ5' + v11_authorization = '/card_holds/HL7dYMhpVBcqAYqxLF5mZtQ5/debits||/card_holds/HL7dYMhpVBcqAYqxLF5mZtQ5' + + [v1_authorization, v11_authorization].each do |authorization| + stub_comms(@gateway, :ssl_request) do + @gateway.capture(@amount, authorization) + end.check_request do |method, endpoint, data, headers| + assert_equal('https://api.balancedpayments.com/card_holds/HL7dYMhpVBcqAYqxLF5mZtQ5/debits', endpoint) + end.respond_with(authorized_debits_response) + end end - def test_ensure_does_not_respond_to_unstore - assert !@gateway.respond_to?(:unstore) + def test_voiding_legacy_authorizations + v1_authorization = '/v1/marketplaces/TEST-MP73SaFdpQePv9dOaG5wXOGO/holds/HL7dYMhpVBcqAYqxLF5mZtQ5' + v11_authorization = '/card_holds/HL7dYMhpVBcqAYqxLF5mZtQ5/debits||/card_holds/HL7dYMhpVBcqAYqxLF5mZtQ5' + + [v1_authorization, v11_authorization].each do |authorization| + stub_comms(@gateway, :ssl_request) do + @gateway.void(authorization) + end.check_request do |method, endpoint, data, headers| + assert_equal :put, method + assert_equal('https://api.balancedpayments.com/card_holds/HL7dYMhpVBcqAYqxLF5mZtQ5', endpoint) + assert_match %r{\bis_void=true\b}, data + end.respond_with(voided_hold_response) + end + end + + def test_refunding_legacy_purchases + v1_authorization = '/v1/marketplaces/TEST-MP73SaFdpQePv9dOaG5wXOGO/debits/WD2x6vLS7RzHYEcdymqRyNAO' + v11_authorization = '|/debits/WD2x6vLS7RzHYEcdymqRyNAO/refunds|' + + [v1_authorization, v11_authorization].each do |authorization| + stub_comms(@gateway, :ssl_request) do + @gateway.refund(nil, authorization) + end.check_request do |method, endpoint, data, headers| + assert_equal('https://api.balancedpayments.com/debits/WD2x6vLS7RzHYEcdymqRyNAO/refunds', endpoint) + end.respond_with(refunds_response) + end + end + + def test_passing_address + a = address + response = stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, @credit_card, address: a) + end.check_request do |method, endpoint, data, headers| + next if endpoint =~ /debits/ + clean = proc { |s| Regexp.escape(CGI.escape(s)) } + assert_match(%r{address\[line1\]=#{clean[a[:address1]]}}, data) + assert_match(%r{address\[line2\]=#{clean[a[:address2]]}}, data) + assert_match(%r{address\[city\]=#{clean[a[:city]]}}, data) + assert_match(%r{address\[state\]=#{clean[a[:state]]}}, data) + assert_match(%r{address\[postal_code\]=#{clean[a[:zip]]}}, data) + assert_match(%r{address\[country_code\]=#{clean[a[:country]]}}, data) + end.respond_with(cards_response, debits_response) + + assert_success response + end + + def test_passing_address_without_zip + response = stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, @credit_card, address: address(zip: nil)) + end.check_request do |method, endpoint, data, headers| + next if endpoint =~ /debits/ + assert_no_match(%r{address}, data) + end.respond_with(cards_response, debits_response) + + assert_success response + end + + def test_passing_address_with_blank_zip + response = stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, @credit_card, address: address(zip: ' ')) + end.check_request do |method, endpoint, data, headers| + next if endpoint =~ /debits/ + assert_no_match(%r{address}, data) + end.respond_with(cards_response, debits_response) + + assert_success response end private + def invalid_login_response + %( +{ + "errors": [ + { + "status": "Unauthorized", + "category_code": "authentication-required", + "description": "<p>The server could not verify that you are authorized to access the URL requested. You either supplied the wrong credentials (e.g. a bad password), or your browser doesn't understand how to supply the credentials required.</p><p>In case you are allowed to request the document, please check your user-id and password and try again.</p> Your request id is OHM45edd6b0db7511e39cf202b12035401b.", + "status_code": 401, + "category_type": "permission", + "request_id": "OHM45edd6b0db7511e39cf202b12035401b" + } + ] +} + ) + end + def marketplace_response <<-RESPONSE { - "first_uri": "/v1/marketplaces?limit=10&offset=0", - "items": [ + "meta": { + "last": "/marketplaces?limit=10&offset=0", + "next": null, + "href": "/marketplaces?limit=10&offset=0", + "limit": 10, + "offset": 0, + "previous": null, + "total": 1, + "first": "/marketplaces?limit=10&offset=0" + }, + "marketplaces": [ { - "in_escrow": 5200, - "support_phone_number": "+16505551234", + "in_escrow": 47202, "domain_url": "example.com", "name": "Test Marketplace", - "transactions_uri": "/v1/marketplaces/TEST-MP73SaFdpQePv9dOaG5wXOGO/transactions", - "support_email_address": "support@example.com", - "uri": "/v1/marketplaces/TEST-MP73SaFdpQePv9dOaG5wXOGO", - "bank_accounts_uri": "/v1/marketplaces/TEST-MP73SaFdpQePv9dOaG5wXOGO/bank_accounts", - "owner_account": { - "holds_uri": "/v1/marketplaces/TEST-MP73SaFdpQePv9dOaG5wXOGO/accounts/AC73SN17anKkjk6Y1sVe2uaq/holds", - "name": "William Henry Cavendish III", - "roles": [ - "merchant", - "buyer" - ], - "created_at": "2012-07-19T17:33:51.977484Z", - "uri": "/v1/marketplaces/TEST-MP73SaFdpQePv9dOaG5wXOGO/accounts/AC73SN17anKkjk6Y1sVe2uaq", - "bank_accounts_uri": "/v1/marketplaces/TEST-MP73SaFdpQePv9dOaG5wXOGO/accounts/AC73SN17anKkjk6Y1sVe2uaq/bank_accounts", - "refunds_uri": "/v1/marketplaces/TEST-MP73SaFdpQePv9dOaG5wXOGO/accounts/AC73SN17anKkjk6Y1sVe2uaq/refunds", - "meta": {}, - "debits_uri": "/v1/marketplaces/TEST-MP73SaFdpQePv9dOaG5wXOGO/accounts/AC73SN17anKkjk6Y1sVe2uaq/debits", - "transactions_uri": "/v1/marketplaces/TEST-MP73SaFdpQePv9dOaG5wXOGO/accounts/AC73SN17anKkjk6Y1sVe2uaq/transactions", - "email_address": "whc@example.org", - "id": "AC73SN17anKkjk6Y1sVe2uaq", - "credits_uri": "/v1/marketplaces/TEST-MP73SaFdpQePv9dOaG5wXOGO/accounts/AC73SN17anKkjk6Y1sVe2uaq/credits", - "cards_uri": "/v1/marketplaces/TEST-MP73SaFdpQePv9dOaG5wXOGO/accounts/AC73SN17anKkjk6Y1sVe2uaq/cards" + "links": { + "owner_customer": "AC73SN17anKkjk6Y1sVe2uaq" }, - "refunds_uri": "/v1/marketplaces/TEST-MP73SaFdpQePv9dOaG5wXOGO/refunds", + "href": "/marketplaces/TEST-MP73SaFdpQePv9dOaG5wXOGO", + "created_at": "2012-07-19T17:33:51.974238Z", + "support_email_address": "support@example.com", + "updated_at": "2012-07-19T17:33:52.848042Z", + "support_phone_number": "+16505551234", + "production": false, "meta": {}, - "debits_uri": "/v1/marketplaces/TEST-MP73SaFdpQePv9dOaG5wXOGO/debits", - "holds_uri": "/v1/marketplaces/TEST-MP73SaFdpQePv9dOaG5wXOGO/holds", - "accounts_uri": "/v1/marketplaces/TEST-MP73SaFdpQePv9dOaG5wXOGO/accounts", - "id": "TEST-MP73SaFdpQePv9dOaG5wXOGO", - "credits_uri": "/v1/marketplaces/TEST-MP73SaFdpQePv9dOaG5wXOGO/credits", - "cards_uri": "/v1/marketplaces/TEST-MP73SaFdpQePv9dOaG5wXOGO/cards" + "unsettled_fees": 0, + "id": "TEST-MP73SaFdpQePv9dOaG5wXOGO" } ], - "previous_uri": null, - "uri": "/v1/marketplaces?limit=10&offset=0", - "limit": 10, - "offset": 0, - "total": 1, - "next_uri": null, - "last_uri": "/v1/marketplaces?limit=10&offset=0" + "links": { + "marketplaces.debits": "/debits", + "marketplaces.reversals": "/reversals", + "marketplaces.customers": "/customers", + "marketplaces.credits": "/credits", + "marketplaces.cards": "/cards", + "marketplaces.card_holds": "/card_holds", + "marketplaces.refunds": "/refunds", + "marketplaces.owner_customer": "/customers/{marketplaces.owner_customer}", + "marketplaces.transactions": "/transactions", + "marketplaces.bank_accounts": "/bank_accounts", + "marketplaces.callbacks": "/callbacks", + "marketplaces.events": "/events" + } } - RESPONSE +RESPONSE end - # raw responses from gateway here - def successful_account_response + def cards_response <<-RESPONSE { - "holds_uri": "/v1/marketplaces/TEST-MP73SaFdpQePv9dOaG5wXOGO/accounts/AC5quPICW5qEHXac1KnjKGYu/holds", - "name": null, - "roles": [ - "buyer" - ], - "created_at": "2012-06-08T02:00:18.233961Z", - "uri": "/v1/marketplaces/TEST-MP73SaFdpQePv9dOaG5wXOGO/accounts/AC5quPICW5qEHXac1KnjKGYu", - "bank_accounts_uri": "/v1/marketplaces/TEST-MP73SaFdpQePv9dOaG5wXOGO/accounts/AC5quPICW5qEHXac1KnjKGYu/bank_accounts", - "refunds_uri": "/v1/marketplaces/TEST-MP73SaFdpQePv9dOaG5wXOGO/accounts/AC5quPICW5qEHXac1KnjKGYu/refunds", - "meta": {}, - "debits_uri": "/v1/marketplaces/TEST-MP73SaFdpQePv9dOaG5wXOGO/accounts/AC5quPICW5qEHXac1KnjKGYu/debits", - "transactions_uri": "/v1/marketplaces/TEST-MP73SaFdpQePv9dOaG5wXOGO/accounts/AC5quPICW5qEHXac1KnjKGYu/transactions", - "email_address": "will@example.org", - "id": "AC5quPICW5qEHXac1KnjKGYu", - "credits_uri": "/v1/marketplaces/TEST-MP73SaFdpQePv9dOaG5wXOGO/accounts/AC5quPICW5qEHXac1KnjKGYu/credits", - "cards_uri": "/v1/marketplaces/TEST-MP73SaFdpQePv9dOaG5wXOGO/accounts/AC5quPICW5qEHXac1KnjKGYu/cards" + "cards": [ + { + "cvv_match": null, + "links": { + "customer": null + }, + "name": "Longbob Longsen", + "expiration_year": 2015, + "avs_street_match": null, + "is_verified": true, + "created_at": "2014-02-06T23:19:27.146436Z", + "cvv_result": null, + "brand": "Visa", + "number": "xxxxxxxxxxxx1111", + "updated_at": "2014-02-06T23:19:27.146441Z", + "id": "CCXfdppSxXOGzaMUHp9EQyI", + "expiration_month": 9, + "cvv": null, + "meta": {}, + "href": "/cards/CCXfdppSxXOGzaMUHp9EQyI", + "address": { + "city": null, + "line2": null, + "line1": null, + "state": null, + "postal_code": null, + "country_code": null + }, + "fingerprint": "e0928a7fe2233bf6697413f663b3d94114358e6ac027fcd58ceba0bb37f05039", + "avs_postal_match": null, + "avs_result": null + } + ], + "links": { + "cards.card_holds": "/cards/{cards.id}/card_holds", + "cards.customer": "/customers/{cards.customer}", + "cards.debits": "/cards/{cards.id}/debits" + } } - RESPONSE +RESPONSE end - def failed_account_response + def debits_response <<-RESPONSE { - "status": "Conflict", - "category_code": "duplicate-email-address", - "additional": null, - "status_code": 409, - "category_type": "logical", - "extras": { - "account_uri": "/v1/marketplaces/TEST-MP73SaFdpQePv9dOaG5wXOGO/accounts/AC58ZKWuGoyQEnDy9ENGRGSq" - }, - "request_id": "OHMc3f6135cd1fd11e19f1e026ba7e5e72e", - "description": "Account with email address 'john.buyer@example.org' already exists. Your request id is OHMc3f6135cd1fd11e19f1e026ba7e5e72e." + "debits": [ + { + "status": "succeeded", + "description": "Shopify Purchase", + "links": { + "customer": null, + "source": "CCXfdppSxXOGzaMUHp9EQyI", + "order": null, + "dispute": null + }, + "updated_at": "2014-02-06T23:19:29.690815Z", + "created_at": "2014-02-06T23:19:28.709143Z", + "transaction_number": "W250-112-1883", + "failure_reason": null, + "currency": "USD", + "amount": 100, + "failure_reason_code": null, + "meta": {}, + "href": "/debits/WDYZhc3mWCkxvOwIokeUz6M", + "appears_on_statement_as": "BAL*example.com", + "id": "WDYZhc3mWCkxvOwIokeUz6M" + } + ], + "links": { + "debits.customer": "/customers/{debits.customer}", + "debits.dispute": "/disputes/{debits.dispute}", + "debits.source": "/resources/{debits.source}", + "debits.order": "/orders/{debits.order}", + "debits.refunds": "/debits/{debits.id}/refunds", + "debits.events": "/debits/{debits.id}/events" + } } - RESPONSE +RESPONSE end - def failed_account_response_bad_email + def authorized_debits_response <<-RESPONSE { - "status": "Bad Request", - "category_code": "request", - "additional": null, - "status_code": 400, - "category_type": "request", - "extras": { - "email_address": "invalid_email must be a valid email address as specified by RFC-2822" - }, - "request_id": "OHM417b4e7ad9e411e2893c026ba7c1aba6", - "description": "Invalid field [email_address] - invalid_email must be a valid email address as specified by RFC-2822 Your request id is OHM417b4e7ad9e411e2893c026ba7c1aba6." + "debits": [ + { + "status": "succeeded", + "description": "Shopify Purchase", + "links": { + "customer": null, + "source": "CC2uKKcUhaSFRrl2mnGPSbDO", + "order": null, + "dispute": null + }, + "updated_at": "2014-02-06T23:19:29.690815Z", + "created_at": "2014-02-06T23:19:28.709143Z", + "transaction_number": "W250-112-1883", + "failure_reason": null, + "currency": "USD", + "amount": 100, + "failure_reason_code": null, + "meta": {}, + "href": "/debits/WDYZhc3mWCkxvOwIokeUz6M", + "appears_on_statement_as": "BAL*example.com", + "id": "WDYZhc3mWCkxvOwIokeUz6M" + } + ], + "links": { + "debits.customer": "/customers/{debits.customer}", + "debits.dispute": "/disputes/{debits.dispute}", + "debits.source": "/resources/{debits.source}", + "debits.order": "/orders/{debits.order}", + "debits.refunds": "/debits/{debits.id}/refunds", + "debits.events": "/debits/{debits.id}/events" + } } - RESPONSE +RESPONSE end - def successful_card_response + def authorized_partial_debits_response <<-RESPONSE +{ + "debits": [ { - "card_type": "visa", - "account": null, - "country_code": "USA", - "expiration_year": 2013, - "created_at": "2012-07-19T23:31:12.334686Z", - "brand": "Visa", - "uri": "/v1/marketplaces/TEST-MP73SaFdpQePv9dOaG5wXOGO/cards/CC6r6kLUcxW3MxG3AmZoiuTf", - "expiration_month": 9, - "is_valid": true, - "meta": {}, - "last_four": 4242, - "postal_code": "K1C2N6", - "id": "CC6r6kLUcxW3MxG3AmZoiuTf", - "street_address": "1234 My Street Apt 1", - "name": "Longbob Longsen" + "status": "succeeded", + "description": "Shopify Purchase", + "links": { + "customer": null, + "source": "CC2uKKcUhaSFRrl2mnGPSbDO", + "order": null, + "dispute": null + }, + "updated_at": "2014-02-06T23:19:29.690815Z", + "created_at": "2014-02-06T23:19:28.709143Z", + "transaction_number": "W250-112-1883", + "failure_reason": null, + "currency": "USD", + "amount": 50, + "failure_reason_code": null, + "meta": {}, + "href": "/debits/WDYZhc3mWCkxvOwIokeUz6M", + "appears_on_statement_as": "BAL*example.com", + "id": "WDYZhc3mWCkxvOwIokeUz6M" } - RESPONSE + ], + "links": { + "debits.customer": "/customers/{debits.customer}", + "debits.dispute": "/disputes/{debits.dispute}", + "debits.source": "/resources/{debits.source}", + "debits.order": "/orders/{debits.order}", + "debits.refunds": "/debits/{debits.id}/refunds", + "debits.events": "/debits/{debits.id}/events" + } +} +RESPONSE end - def void_hold_response_failure + def declined_response <<-RESPONSE { - "status": "Conflict", - "category_code": "cannot-void-authorization", - "additional": null, - "status_code": 409, - "category_type": "logical", - "extras": {}, - "request_id": "OHMe8da23e0d20511e1af4e026ba7e5e72e", - "description": "Hold already captured or voided. Your request id is OHMe8da23e0d20511e1af4e026ba7e5e72e." + "errors": [ + { + "status": "Payment Required", + "category_code": "card-declined", + "additional": "Customer call bank", + "status_code": 402, + "category_type": "banking", + "extras": {}, + "request_id": "OHMc8d80eb4903011e390c002a1fe53e539", + "description": "R530: Customer call bank. Your request id is OHMc8d80eb4903011e390c002a1fe53e539." + } + ] +} +RESPONSE + end + + def bad_email_response + <<-'RESPONSE' +{ + "errors": [ + { + "status": "Bad Request", + "category_code": "request", + "additional": null, + "status_code": 400, + "category_type": "request", + "extras": { + "email": "\"invalid_email\" must be a valid email address as specified by RFC-2822" + }, + "request_id": "OHM9107a4bc903111e390c002a1fe53e539", + "description": "Invalid field [email] - \"invalid_email\" must be a valid email address as specified by RFC-2822 Your request id is OHM9107a4bc903111e390c002a1fe53e539." + } + ] } - RESPONSE +RESPONSE end - def void_hold_response + def account_frozen_response <<-RESPONSE { - "account": { - "holds_uri": "/v1/marketplaces/TEST-MP73SaFdpQePv9dOaG5wXOGO/accounts/AC5quPICW5qEHXac1KnjKGYu/holds", - "name": null, - "roles": [ - "buyer" - ], - "created_at": "2012-06-08T02:00:18.233961Z", - "uri": "/v1/marketplaces/TEST-MP73SaFdpQePv9dOaG5wXOGO/accounts/AC5quPICW5qEHXac1KnjKGYu", - "bank_accounts_uri": "/v1/marketplaces/TEST-MP73SaFdpQePv9dOaG5wXOGO/accounts/AC5quPICW5qEHXac1KnjKGYu/bank_accounts", - "refunds_uri": "/v1/marketplaces/TEST-MP73SaFdpQePv9dOaG5wXOGO/accounts/AC5quPICW5qEHXac1KnjKGYu/refunds", - "meta": {}, - "debits_uri": "/v1/marketplaces/TEST-MP73SaFdpQePv9dOaG5wXOGO/accounts/AC5quPICW5qEHXac1KnjKGYu/debits", - "transactions_uri": "/v1/marketplaces/TEST-MP73SaFdpQePv9dOaG5wXOGO/accounts/AC5quPICW5qEHXac1KnjKGYu/transactions", - "email_address": "will@example.org", - "id": "AC5quPICW5qEHXac1KnjKGYu", - "credits_uri": "/v1/marketplaces/TEST-MP73SaFdpQePv9dOaG5wXOGO/accounts/AC5quPICW5qEHXac1KnjKGYu/credits", - "cards_uri": "/v1/marketplaces/TEST-MP73SaFdpQePv9dOaG5wXOGO/accounts/AC5quPICW5qEHXac1KnjKGYu/cards" - }, - "fee": 35, - "description": null, - "amount": 200, - "created_at": "2012-06-08T02:01:57.356366Z", - "uri": "/v1/marketplaces/TEST-MP73SaFdpQePv9dOaG5wXOGO/holds/HL7dYMhpVBcqAYqxLF5mZtQ5", - "expires_at": "2012-06-15T02:01:57.316184Z", - "source": { - "expiration_month": 12, - "name": null, - "expiration_year": 2020, - "brand": "MasterCard", - "uri": "/v1/marketplaces/TEST-MP73SaFdpQePv9dOaG5wXOGO/accounts/AC5quPICW5qEHXac1KnjKGYu/cards/CCZCMxRRAvPt2K4uU460EFX", - "id": "CCZCMxRRAvPt2K4uU460EFX", - "card_type": "mastercard", - "is_valid": true, - "last_four": 5100, - "created_at": "2012-06-08T01:56:13.845267Z" + "errors": [ + { + "status": "Payment Required", + "category_code": "card-declined", + "additional": "Account Frozen", + "status_code": 402, + "category_type": "banking", + "extras": {}, + "request_id": "OHMec50b6be903c11e387cb026ba7cac9da", + "description": "R758: Account Frozen. Your request id is OHMec50b6be903c11e387cb026ba7cac9da." + } + ], + "links": { + "debits.customer": "/customers/{debits.customer}", + "debits.dispute": "/disputes/{debits.dispute}", + "debits.source": "/resources/{debits.source}", + "debits.order": "/orders/{debits.order}", + "debits.refunds": "/debits/{debits.id}/refunds", + "debits.events": "/debits/{debits.id}/events" }, - "is_void": true, - "meta": {}, - "debit": null, - "id": "HL7dYMhpVBcqAYqxLF5mZtQ5" + "debits": [ + { + "status": "failed", + "description": "Shopify Purchase", + "links": { + "customer": null, + "source": "CC7a41DYIaSSyGoau6rZ8VcG", + "order": null, + "dispute": null + }, + "updated_at": "2014-02-07T21:15:10.107464Z", + "created_at": "2014-02-07T21:15:09.206335Z", + "transaction_number": "W202-883-1157", + "failure_reason": "R758: Account Frozen.", + "currency": "USD", + "amount": 100, + "failure_reason_code": "card-declined", + "meta": {}, + "href": "/debits/WD7cjQ5gizGWMDWbxDndgm7w", + "appears_on_statement_as": "BAL*example.com", + "id": "WD7cjQ5gizGWMDWbxDndgm7w" + } + ] } +RESPONSE + end - RESPONSE + def appears_on_response + <<-RESPONSE +{ + "debits": [ + { + "status": "succeeded", + "description": "Shopify Purchase", + "links": { + "customer": null, + "source": "CC4SKo0WY3lhfWc6CgMyPo34", + "order": null, + "dispute": null + }, + "updated_at": "2014-02-07T21:20:13.950392Z", + "created_at": "2014-02-07T21:20:12.737821Z", + "transaction_number": "W337-477-3752", + "failure_reason": null, + "currency": "USD", + "amount": 100, + "failure_reason_code": null, + "meta": {}, + "href": "/debits/WD4UDDm6iqtYMEd21UBaa50H", + "appears_on_statement_as": "BAL*Homer Electric", + "id": "WD4UDDm6iqtYMEd21UBaa50H" + } + ], + "links": { + "debits.customer": "/customers/{debits.customer}", + "debits.dispute": "/disputes/{debits.dispute}", + "debits.source": "/resources/{debits.source}", + "debits.order": "/orders/{debits.order}", + "debits.refunds": "/debits/{debits.id}/refunds", + "debits.events": "/debits/{debits.id}/events" + } +} +RESPONSE end - def successful_hold_response + def holds_response <<-RESPONSE { - "account": { - "holds_uri": "/v1/marketplaces/TEST-MP73SaFdpQePv9dOaG5wXOGO/accounts/AC5quPICW5qEHXac1KnjKGYu/holds", - "name": null, - "roles": [ - "buyer" - ], - "created_at": "2012-06-08T02:00:18.233961Z", - "uri": "/v1/marketplaces/TEST-MP73SaFdpQePv9dOaG5wXOGO/accounts/AC5quPICW5qEHXac1KnjKGYu", - "bank_accounts_uri": "/v1/marketplaces/TEST-MP73SaFdpQePv9dOaG5wXOGO/accounts/AC5quPICW5qEHXac1KnjKGYu/bank_accounts", - "refunds_uri": "/v1/marketplaces/TEST-MP73SaFdpQePv9dOaG5wXOGO/accounts/AC5quPICW5qEHXac1KnjKGYu/refunds", - "meta": {}, - "debits_uri": "/v1/marketplaces/TEST-MP73SaFdpQePv9dOaG5wXOGO/accounts/AC5quPICW5qEHXac1KnjKGYu/debits", - "transactions_uri": "/v1/marketplaces/TEST-MP73SaFdpQePv9dOaG5wXOGO/accounts/AC5quPICW5qEHXac1KnjKGYu/transactions", - "email_address": "will@example.org", - "id": "AC5quPICW5qEHXac1KnjKGYu", - "credits_uri": "/v1/marketplaces/TEST-MP73SaFdpQePv9dOaG5wXOGO/accounts/AC5quPICW5qEHXac1KnjKGYu/credits", - "cards_uri": "/v1/marketplaces/TEST-MP73SaFdpQePv9dOaG5wXOGO/accounts/AC5quPICW5qEHXac1KnjKGYu/cards" - }, - "fee": 35, - "description": null, - "amount": 200, - "created_at": "2012-06-08T02:01:57.356366Z", - "uri": "/v1/marketplaces/TEST-MP73SaFdpQePv9dOaG5wXOGO/holds/HL7dYMhpVBcqAYqxLF5mZtQ5", - "expires_at": "2012-06-15T02:01:57.316184Z", - "source": { - "expiration_month": 12, - "name": null, - "expiration_year": 2020, - "brand": "MasterCard", - "uri": "/v1/marketplaces/TEST-MP73SaFdpQePv9dOaG5wXOGO/accounts/AC5quPICW5qEHXac1KnjKGYu/cards/CCZCMxRRAvPt2K4uU460EFX", - "id": "CCZCMxRRAvPt2K4uU460EFX", - "card_type": "mastercard", - "is_valid": true, - "last_four": 5100, - "created_at": "2012-06-08T01:56:13.845267Z" - }, - "is_void": false, - "meta": {}, - "debit": null, - "id": "HL7dYMhpVBcqAYqxLF5mZtQ5" + "card_holds": [ + { + "status": "succeeded", + "description": "Shopify Purchase", + "links": { + "card": "CC2uKKcUhaSFRrl2mnGPSbDO", + "debit": null + }, + "updated_at": "2014-02-07T21:46:39.678439Z", + "created_at": "2014-02-07T21:46:39.303526Z", + "transaction_number": "HL343-028-3032", + "expires_at": "2014-02-14T21:46:39.532363Z", + "failure_reason": null, + "currency": "USD", + "amount": 100, + "meta": {}, + "href": "/card_holds/HL2wPXf6ByqkLMiWGab7QRsq", + "failure_reason_code": null, + "voided_at": null, + "id": "HL2wPXf6ByqkLMiWGab7QRsq" + } + ], + "links": { + "card_holds.events": "/card_holds/{card_holds.id}/events", + "card_holds.card": "/resources/{card_holds.card}", + "card_holds.debits": "/card_holds/{card_holds.id}/debits", + "card_holds.debit": "/debits/{card_holds.debit}" + } } +RESPONSE + end - RESPONSE + def method_not_allowed_response + <<-RESPONSE +{ + "errors": [ + { + "status": "Method Not Allowed", + "category_code": "method-not-allowed", + "description": "Your request id is OHMfaf5570a904211e3bcab026ba7f8ec28.", + "status_code": 405, + "category_type": "request", + "request_id": "OHMfaf5570a904211e3bcab026ba7f8ec28" + } + ] +} +RESPONSE end - def successful_purchase_response + def unauthorized_response <<-RESPONSE { - "account": { - "holds_uri": "/v1/marketplaces/TEST-MP73SaFdpQePv9dOaG5wXOGO/accounts/AC5quPICW5qEHXac1KnjKGYu/holds", - "name": null, - "roles": [ - "buyer" - ], - "created_at": "2012-06-08T02:00:18.233961Z", - "uri": "/v1/marketplaces/TEST-MP73SaFdpQePv9dOaG5wXOGO/accounts/AC5quPICW5qEHXac1KnjKGYu", - "bank_accounts_uri": "/v1/marketplaces/TEST-MP73SaFdpQePv9dOaG5wXOGO/accounts/AC5quPICW5qEHXac1KnjKGYu/bank_accounts", - "refunds_uri": "/v1/marketplaces/TEST-MP73SaFdpQePv9dOaG5wXOGO/accounts/AC5quPICW5qEHXac1KnjKGYu/refunds", - "meta": {}, - "debits_uri": "/v1/marketplaces/TEST-MP73SaFdpQePv9dOaG5wXOGO/accounts/AC5quPICW5qEHXac1KnjKGYu/debits", - "transactions_uri": "/v1/marketplaces/TEST-MP73SaFdpQePv9dOaG5wXOGO/accounts/AC5quPICW5qEHXac1KnjKGYu/transactions", - "email_address": "will@example.org", - "id": "AC5quPICW5qEHXac1KnjKGYu", - "credits_uri": "/v1/marketplaces/TEST-MP73SaFdpQePv9dOaG5wXOGO/accounts/AC5quPICW5qEHXac1KnjKGYu/credits", - "cards_uri": "/v1/marketplaces/TEST-MP73SaFdpQePv9dOaG5wXOGO/accounts/AC5quPICW5qEHXac1KnjKGYu/cards" - }, - "fee": 5, - "description": null, - "refunds_uri": "/v1/marketplaces/TEST-MP73SaFdpQePv9dOaG5wXOGO/debits/WD2x6vLS7RzHYEcdymqRyNAO/refunds", - "amount": 150, - "created_at": "2012-06-08T02:11:57.737829Z", - "uri": "/v1/marketplaces/TEST-MP73SaFdpQePv9dOaG5wXOGO/debits/WD2x6vLS7RzHYEcdymqRyNAO", - "source": { - "expiration_month": 12, - "name": null, - "expiration_year": 2020, - "brand": "MasterCard", - "uri": "/v1/marketplaces/TEST-MP73SaFdpQePv9dOaG5wXOGO/accounts/AC5quPICW5qEHXac1KnjKGYu/cards/CCZCMxRRAvPt2K4uU460EFX", - "id": "CCZCMxRRAvPt2K4uU460EFX", - "card_type": "mastercard", - "is_valid": true, - "last_four": 5100, - "created_at": "2012-06-08T01:56:13.845267Z" - }, - "transaction_number": "W615-916-0468", - "meta": {}, - "appears_on_statement_as": "example.com", - "hold": { - "fee": 35, - "description": null, - "created_at": "2012-06-08T02:01:57.356366Z", - "is_void": false, - "expires_at": "2012-06-15T02:01:57.316184Z", - "uri": "/v1/marketplaces/TEST-MP73SaFdpQePv9dOaG5wXOGO/holds/HL7dYMhpVBcqAYqxLF5mZtQ5", - "amount": 200, - "meta": {}, - "account_uri": "/v1/marketplaces/TEST-MP73SaFdpQePv9dOaG5wXOGO/accounts/AC5quPICW5qEHXac1KnjKGYu", - "source_uri": "/v1/marketplaces/TEST-MP73SaFdpQePv9dOaG5wXOGO/accounts/AC5quPICW5qEHXac1KnjKGYu/cards/CCZCMxRRAvPt2K4uU460EFX", - "id": "HL7dYMhpVBcqAYqxLF5mZtQ5" - }, - "id": "WD2x6vLS7RzHYEcdymqRyNAO", - "available_at": "2012-06-08T02:11:57.670850Z" + "errors": [ + { + "status": "Unauthorized", + "category_code": "authentication-required", + "description": "<p>The server could not verify that you are authorized to access the URL requested. You either supplied the wrong credentials (e.g. a bad password), or your browser doesn't understand how to supply the credentials required.</p><p>In case you are allowed to request the document, please check your user-id and password and try again.</p> Your request id is OHM56702560904311e3988c026ba7cd33d0.", + "status_code": 401, + "category_type": "permission", + "request_id": "OHM56702560904311e3988c026ba7cd33d0" + } + ] } - RESPONSE +RESPONSE end - def failed_purchase_response + def voided_hold_response <<-RESPONSE { - "status":"Bad Request", - "category_code": "request", - "additional": null, - "status_code": 400, - "category_type": "request", - "extras": {}, - "request_id": "OHM7ba062c4d1ee11e1a63d026ba7e5e72e", - "description": "Invalid field [amount] - 0 must be >= 50 Your request id is OHM7ba062c4d1ee11e1a63d026ba7e5e72e." + "card_holds": [ + { + "status": "succeeded", + "description": "Shopify Purchase", + "links": { + "card": "CC52ACcRnrG5eupOERKK4OAq", + "debit": null + }, + "updated_at": "2014-02-07T22:10:28.923304Z", + "created_at": "2014-02-07T22:10:27.904233Z", + "transaction_number": "HL728-165-8425", + "expires_at": "2014-02-14T22:10:28.045745Z", + "failure_reason": null, + "currency": "USD", + "amount": 100, + "meta": {}, + "href": "/card_holds/HL54qindwhlErSujLo5IcP5J", + "failure_reason_code": null, + "voided_at": "2014-02-07T22:10:28.923308Z", + "id": "HL54qindwhlErSujLo5IcP5J" + } + ], + "links": { + "card_holds.events": "/card_holds/{card_holds.id}/events", + "card_holds.card": "/resources/{card_holds.card}", + "card_holds.debits": "/card_holds/{card_holds.id}/debits", + "card_holds.debit": "/debits/{card_holds.debit}" + } } - RESPONSE +RESPONSE end - def successful_refund_response + def refunds_response <<-RESPONSE { - "account": { - "holds_uri": "/v1/marketplaces/TEST-MP6IEymJ6ynwnSoqJQnUTacN/accounts/AC5quPICW5qEHXac1KnjKGYu/holds", - "name": null, - "roles": [ - "buyer" - ], - "created_at": "2012-06-08T02:00:18.233961Z", - "uri": "/v1/marketplaces/TEST-MP6IEymJ6ynwnSoqJQnUTacN/accounts/AC5quPICW5qEHXac1KnjKGYu", - "bank_accounts_uri": "/v1/marketplaces/TEST-MP6IEymJ6ynwnSoqJQnUTacN/accounts/AC5quPICW5qEHXac1KnjKGYu/bank_accounts", - "refunds_uri": "/v1/marketplaces/TEST-MP6IEymJ6ynwnSoqJQnUTacN/accounts/AC5quPICW5qEHXac1KnjKGYu/refunds", - "meta": {}, - "debits_uri": "/v1/marketplaces/TEST-MP6IEymJ6ynwnSoqJQnUTacN/accounts/AC5quPICW5qEHXac1KnjKGYu/debits", - "transactions_uri": "/v1/marketplaces/TEST-MP6IEymJ6ynwnSoqJQnUTacN/accounts/AC5quPICW5qEHXac1KnjKGYu/transactions", - "email_address": "will@example.org", - "id": "AC5quPICW5qEHXac1KnjKGYu", - "credits_uri": "/v1/marketplaces/TEST-MP6IEymJ6ynwnSoqJQnUTacN/accounts/AC5quPICW5qEHXac1KnjKGYu/credits", - "cards_uri": "/v1/marketplaces/TEST-MP6IEymJ6ynwnSoqJQnUTacN/accounts/AC5quPICW5qEHXac1KnjKGYu/cards" + "links": { + "refunds.dispute": "/disputes/{refunds.dispute}", + "refunds.events": "/refunds/{refunds.id}/events", + "refunds.debit": "/debits/{refunds.debit}", + "refunds.order": "/orders/{refunds.order}" }, - "fee": -5, - "description": null, - "amount": 150, - "created_at": "2012-07-20T19:16:56.921554Z", - "uri": "/v1/marketplaces/TEST-MP6IEymJ6ynwnSoqJQnUTacN/refunds/RF3GhhG5I3AgrjjXsdkRFQDA", - "transaction_number": "RF589-096-4953", - "meta": {}, - "debit": { - "hold_uri": "/v1/marketplaces/TEST-MP6IEymJ6ynwnSoqJQnUTacN/holds/HL2N2aMGzHdZ5xRocCqiLxKp", - "fee": 5, - "description": null, - "source_uri": "/v1/marketplaces/TEST-MP6IEymJ6ynwnSoqJQnUTacN/accounts/AC5quPICW5qEHXac1KnjKGYu/cards/CC6gGmoZ21ApTyng82GY6RsZ", - "created_at": "2012-07-20T19:16:08.077620Z", - "transaction_number": "W543-770-7869", - "uri": "/v1/marketplaces/TEST-MP6IEymJ6ynwnSoqJQnUTacN/debits/WD2Nkre6GkWAV1A52YgLWEkh", - "refunds_uri": "/v1/marketplaces/TEST-MP6IEymJ6ynwnSoqJQnUTacN/debits/WD2Nkre6GkWAV1A52YgLWEkh/refunds", - "amount": 150, - "meta": {}, - "appears_on_statement_as": "example.com", - "id": "WD2Nkre6GkWAV1A52YgLWEkh", - "available_at": "2012-07-20T19:16:07.990758Z" + "refunds": [ + { + "status": "succeeded", + "description": "Shopify Purchase", + "links": { + "debit": "WDAtJcbjh3EJLW0tp7CUxAk", + "order": null, + "dispute": null + }, + "href": "/refunds/RFJ4N00zLaQFrfBkC8cbN68", + "created_at": "2014-02-07T22:35:06.424855Z", + "transaction_number": "RF424-240-3258", + "updated_at": "2014-02-07T22:35:07.655276Z", + "currency": "USD", + "amount": 100, + "meta": {}, + "id": "RFJ4N00zLaQFrfBkC8cbN68" + } + ] +} +RESPONSE + end + + def partial_refunds_response + <<-RESPONSE +{ + "links": { + "refunds.dispute": "/disputes/{refunds.dispute}", + "refunds.events": "/refunds/{refunds.id}/events", + "refunds.debit": "/debits/{refunds.debit}", + "refunds.order": "/orders/{refunds.order}" }, - "appears_on_statement_as": "example.com", - "id": "RF3GhhG5I3AgrjjXsdkRFQDA" + "refunds": [ + { + "status": "succeeded", + "description": "Shopify Purchase", + "links": { + "debit": "WDAtJcbjh3EJLW0tp7CUxAk", + "order": null, + "dispute": null + }, + "href": "/refunds/RFJ4N00zLaQFrfBkC8cbN68", + "created_at": "2014-02-07T22:35:06.424855Z", + "transaction_number": "RF424-240-3258", + "updated_at": "2014-02-07T22:35:07.655276Z", + "currency": "USD", + "amount": 50, + "meta": {}, + "id": "RFJ4N00zLaQFrfBkC8cbN68" + } + ] } - RESPONSE +RESPONSE end - def failed_refund_response + def refunds_pending_response <<-RESPONSE { - "status": "Bad Request", - "category_code": "request", - "additional": null, - "status_code": 400, - "category_type": "request", - "extras": {}, - "request_id": "OHM6b91d56ed29f11e18991026ba7e239a9", - "description": "Invalid field [amount] - 170 must be <= 150 Your request id is OHM6b91d56ed29f11e18991026ba7e239a9." + "links": { + "refunds.dispute": "/disputes/{refunds.dispute}", + "refunds.events": "/refunds/{refunds.id}/events", + "refunds.debit": "/debits/{refunds.debit}", + "refunds.order": "/orders/{refunds.order}" + }, + "refunds": [ + { + "status": "pending", + "description": null, + "links": { + "debit": "WD7AT5AGKI0jccoElAEEqiuL", + "order": null, + "dispute": null + }, + "href": "/refunds/RF46a5p6ZVMK4qVIeCJ8u2LE", + "created_at": "2014-05-22T20:20:32.956467Z", + "transaction_number": "RF485-302-2551", + "updated_at": "2014-05-22T20:20:35.991553Z", + "currency": "USD", + "amount": 100, + "meta": {}, + "id": "RF46a5p6ZVMK4qVIeCJ8u2LE" + } + ] } - RESPONSE +RESPONSE end end diff --git a/test/unit/gateways/bambora_apac_test.rb b/test/unit/gateways/bambora_apac_test.rb new file mode 100644 index 00000000000..3b4e62c2977 --- /dev/null +++ b/test/unit/gateways/bambora_apac_test.rb @@ -0,0 +1,232 @@ +require 'test_helper' + +class BamboraApacTest < Test::Unit::TestCase + include CommStub + + def setup + @gateway = BamboraApacGateway.new( + username: 'username', + password: 'password' + ) + + @amount = 100 + @credit_card = credit_card + end + + def test_successful_purchase + response = stub_comms do + @gateway.purchase(@amount, @credit_card, order_id: 1) + end.check_request do |endpoint, data, headers| + assert_match(%r{<SubmitSinglePayment }, data) + assert_match(%r{<UserName>username<}, data) + assert_match(%r{<Password>password<}, data) + assert_match(%r{<CustRef>1<}, data) + assert_match(%r{<Amount>100<}, data) + assert_match(%r{<TrnType>1<}, data) + assert_match(%r{<CardNumber>#{@credit_card.number}<}, data) + assert_match(%r{<ExpM>#{"%02d" % @credit_card.month}<}, data) + assert_match(%r{<ExpY>#{@credit_card.year}<}, data) + assert_match(%r{<CVN>#{@credit_card.verification_value}<}, data) + assert_match(%r{<CardHolderName>#{@credit_card.name}<}, data) + end.respond_with(successful_purchase_response) + + assert_success response + assert_equal '89435577', response.authorization + end + + def test_failed_purchase + response = stub_comms do + @gateway.purchase(@amount, @credit_card) + end.respond_with(failed_purchase_response) + + assert_failure response + assert_equal 'Do Not Honour', response.message + assert_equal Gateway::STANDARD_ERROR_CODE[:card_declined], response.error_code + assert_equal '', response.authorization + end + + def test_successful_authorize + response = stub_comms do + @gateway.authorize(@amount, @credit_card, order_id: 1) + end.check_request do |endpoint, data, headers| + assert_match(%r{<SubmitSinglePayment }, data) + assert_match(%r{<CustRef>1<}, data) + assert_match(%r{<TrnType>2<}, data) + end.respond_with(successful_authorize_response) + + assert_success response + assert_equal '89435583', response.authorization + end + + def test_successful_capture + response = stub_comms do + @gateway.capture(@amount, 'receipt') + end.check_request do |endpoint, data, headers| + assert_match(%r{<SubmitSingleCapture }, data) + assert_match(%r{<Receipt>receipt<}, data) + assert_match(%r{<Amount>100<}, data) + end.respond_with(successful_capture_response) + + assert_success response + end + + def test_successful_refund + response = stub_comms do + @gateway.refund(@amount, 'receipt') + end.check_request do |endpoint, data, headers| + assert_match(%r{<SubmitSingleRefund }, data) + assert_match(%r{<Receipt>receipt<}, data) + assert_match(%r{<Amount>100<}, data) + end.respond_with(successful_refund_response) + + assert_success response + end + + def test_successful_void + response = stub_comms do + @gateway.void(@amount, 'receipt') + end.check_request do |endpoint, data, headers| + assert_match(%r{<SubmitSingleVoid }, data) + end.respond_with(successful_void_response) + + assert_success response + end + + def test_scrub + assert @gateway.supports_scrubbing? + assert_equal @gateway.scrub(pre_scrubbed), post_scrubbed + end + + private + + def pre_scrubbed + <<-'PRE_SCRUBBED' +opening connection to demo.ippayments.com.au:443... +opened +starting SSL for demo.ippayments.com.au:443... +SSL established +<- "POST /interface/api/dts.asmx HTTP/1.1\r\nContent-Type: text/xml; charset=utf-8\r\nSoapaction: http://www.ippayments.com.au/interface/api/dts/SubmitSinglePayment\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: demo.ippayments.com.au\r\nContent-Length: 822\r\n\r\n" +<- "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<soap:Envelope xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">\n <soap:Body>\n <SubmitSinglePayment xmlns=\"http://www.ippayments.com.au/interface/api/dts\">\n <trnXML>\n <![CDATA[<Transaction>\n <CustRef>1</CustRef>\n <Amount/>\n <TrnType>1</TrnType>\n <CreditCard Registered=\"False\">\n <CardNumber>4005550000000001</CardNumber>\n <ExpM>09</ExpM>\n <ExpY>2015</ExpY>\n <CVN>123</CVN>\n <CardHolderName>Longbob Longsen</CardHolderName>\n </CreditCard>\n <Security>\n <UserName>nmi.api</UserName>\n <Password>qwerty123</Password>\n </Security>\n <TrnSource/>\n</Transaction>\n]]>\n </trnXML>\n </SubmitSinglePayment>\n </soap:Body>\n</soap:Envelope>\n" +-> "HTTP/1.1 200 OK\r\n" +-> "Server: Microsoft-IIS/6.0\r\n" +-> "X-Robots-Tag: noindex\r\n" +-> "X-Powered-By: ASP.NET\r\n" +-> "Cache-Control: private, max-age=0\r\n" +-> "Content-Type: text/xml; charset=utf-8\r\n" +-> "Content-Length: 767\r\n" +-> "Date: Fri, 19 Dec 2014 19:55:13 GMT\r\n" +-> "Connection: close\r\n" +-> "\r\n" +reading 767 bytes... +-> "<?xml version=\"1.0\" encoding=\"utf-8\"?><soap:Envelope xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"><soap:Body><SubmitSinglePaymentResponse xmlns=\"http://www.ippayments.com.au/interface/api/dts\"><SubmitSinglePaymentResult>&lt;Response&gt;\r\n\t&lt;ResponseCode&gt;1&lt;/ResponseCode&gt;\r\n\t&lt;Timestamp&gt;20-Dec-2014 06:55:17&lt;/Timestamp&gt;\r\n\t&lt;Receipt&gt;&lt;/Receipt&gt;\r\n\t&lt;SettlementDate&gt;&lt;/SettlementDate&gt;\r\n\t&lt;DeclinedCode&gt;183&lt;/DeclinedCode&gt;\r\n\t&lt;DeclinedMessage&gt;Exception parsing transaction XML&lt;/DeclinedMessage&gt;\r\n&lt;/Response&gt;\r\n</SubmitSinglePaymentResult></SubmitSinglePaymentResponse></soap:Body></soap:Envelope>" +read 767 bytes +Conn close + PRE_SCRUBBED + end + + def post_scrubbed + <<-'POST_SCRUBBED' +opening connection to demo.ippayments.com.au:443... +opened +starting SSL for demo.ippayments.com.au:443... +SSL established +<- "POST /interface/api/dts.asmx HTTP/1.1\r\nContent-Type: text/xml; charset=utf-8\r\nSoapaction: http://www.ippayments.com.au/interface/api/dts/SubmitSinglePayment\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: demo.ippayments.com.au\r\nContent-Length: 822\r\n\r\n" +<- "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<soap:Envelope xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">\n <soap:Body>\n <SubmitSinglePayment xmlns=\"http://www.ippayments.com.au/interface/api/dts\">\n <trnXML>\n <![CDATA[<Transaction>\n <CustRef>1</CustRef>\n <Amount/>\n <TrnType>1</TrnType>\n <CreditCard Registered=\"False\">\n <CardNumber>[FILTERED]</CardNumber>\n <ExpM>09</ExpM>\n <ExpY>2015</ExpY>\n <CVN>[FILTERED]</CVN>\n <CardHolderName>Longbob Longsen</CardHolderName>\n </CreditCard>\n <Security>\n <UserName>nmi.api</UserName>\n <Password>[FILTERED]</Password>\n </Security>\n <TrnSource/>\n</Transaction>\n]]>\n </trnXML>\n </SubmitSinglePayment>\n </soap:Body>\n</soap:Envelope>\n" +-> "HTTP/1.1 200 OK\r\n" +-> "Server: Microsoft-IIS/6.0\r\n" +-> "X-Robots-Tag: noindex\r\n" +-> "X-Powered-By: ASP.NET\r\n" +-> "Cache-Control: private, max-age=0\r\n" +-> "Content-Type: text/xml; charset=utf-8\r\n" +-> "Content-Length: 767\r\n" +-> "Date: Fri, 19 Dec 2014 19:55:13 GMT\r\n" +-> "Connection: close\r\n" +-> "\r\n" +reading 767 bytes... +-> "<?xml version=\"1.0\" encoding=\"utf-8\"?><soap:Envelope xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"><soap:Body><SubmitSinglePaymentResponse xmlns=\"http://www.ippayments.com.au/interface/api/dts\"><SubmitSinglePaymentResult>&lt;Response&gt;\r\n\t&lt;ResponseCode&gt;1&lt;/ResponseCode&gt;\r\n\t&lt;Timestamp&gt;20-Dec-2014 06:55:17&lt;/Timestamp&gt;\r\n\t&lt;Receipt&gt;&lt;/Receipt&gt;\r\n\t&lt;SettlementDate&gt;&lt;/SettlementDate&gt;\r\n\t&lt;DeclinedCode&gt;183&lt;/DeclinedCode&gt;\r\n\t&lt;DeclinedMessage&gt;Exception parsing transaction XML&lt;/DeclinedMessage&gt;\r\n&lt;/Response&gt;\r\n</SubmitSinglePaymentResult></SubmitSinglePaymentResponse></soap:Body></soap:Envelope>" +read 767 bytes +Conn close + POST_SCRUBBED + end + + def successful_purchase_response + <<-XML +<?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><soap:Body><SubmitSinglePaymentResponse xmlns="http://www.ippayments.com.au/interface/api/dts"><SubmitSinglePaymentResult>&lt;Response&gt; + &lt;ResponseCode&gt;0&lt;/ResponseCode&gt; + &lt;Timestamp&gt;20-Dec-2014 04:07:39&lt;/Timestamp&gt; + &lt;Receipt&gt;89435577&lt;/Receipt&gt; + &lt;SettlementDate&gt;22-Dec-2014&lt;/SettlementDate&gt; + &lt;DeclinedCode&gt;&lt;/DeclinedCode&gt; + &lt;DeclinedMessage&gt;&lt;/DeclinedMessage&gt; +&lt;/Response&gt; +</SubmitSinglePaymentResult></SubmitSinglePaymentResponse></soap:Body></soap:Envelope> + XML + end + + def failed_purchase_response + <<-XML +<?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><soap:Body><SubmitSinglePaymentResponse xmlns="http://www.ippayments.com.au/interface/api/dts"><SubmitSinglePaymentResult>&lt;Response&gt; + &lt;ResponseCode&gt;1&lt;/ResponseCode&gt; + &lt;Timestamp&gt;20-Dec-2014 04:14:56&lt;/Timestamp&gt; + &lt;Receipt&gt;&lt;/Receipt&gt; + &lt;SettlementDate&gt;22-Dec-2014&lt;/SettlementDate&gt; + &lt;DeclinedCode&gt;05&lt;/DeclinedCode&gt; + &lt;DeclinedMessage&gt;Do Not Honour&lt;/DeclinedMessage&gt; +&lt;/Response&gt; +</SubmitSinglePaymentResult></SubmitSinglePaymentResponse></soap:Body></soap:Envelope> + XML + end + + def successful_authorize_response + <<-XML +<?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><soap:Body><SubmitSinglePaymentResponse xmlns="http://www.ippayments.com.au/interface/api/dts"><SubmitSinglePaymentResult>&lt;Response&gt; + &lt;ResponseCode&gt;0&lt;/ResponseCode&gt; + &lt;Timestamp&gt;20-Dec-2014 04:18:13&lt;/Timestamp&gt; + &lt;Receipt&gt;89435583&lt;/Receipt&gt; + &lt;SettlementDate&gt;22-Dec-2014&lt;/SettlementDate&gt; + &lt;DeclinedCode&gt;&lt;/DeclinedCode&gt; + &lt;DeclinedMessage&gt;&lt;/DeclinedMessage&gt; +&lt;/Response&gt; +</SubmitSinglePaymentResult></SubmitSinglePaymentResponse></soap:Body></soap:Envelope> + XML + end + + def successful_capture_response + <<-XML +<?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><soap:Body><SubmitSingleCaptureResponse xmlns="http://www.ippayments.com.au/interface/api/dts"><SubmitSingleCaptureResult>&lt;Response&gt; + &lt;ResponseCode&gt;0&lt;/ResponseCode&gt; + &lt;Timestamp&gt;20-Dec-2014 04:18:15&lt;/Timestamp&gt; + &lt;Receipt&gt;89435584&lt;/Receipt&gt; + &lt;SettlementDate&gt;22-Dec-2014&lt;/SettlementDate&gt; + &lt;DeclinedCode&gt;&lt;/DeclinedCode&gt; + &lt;DeclinedMessage&gt;&lt;/DeclinedMessage&gt; +&lt;/Response&gt; +</SubmitSingleCaptureResult></SubmitSingleCaptureResponse></soap:Body></soap:Envelope> + XML + end + + def successful_refund_response + <<-XML +<?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><soap:Body><SubmitSingleRefundResponse xmlns="http://www.ippayments.com.au/interface/api/dts"><SubmitSingleRefundResult>&lt;Response&gt; + &lt;ResponseCode&gt;0&lt;/ResponseCode&gt; + &lt;Timestamp&gt;20-Dec-2014 04:24:51&lt;/Timestamp&gt; + &lt;Receipt&gt;89435596&lt;/Receipt&gt; + &lt;SettlementDate&gt;22-Dec-2014&lt;/SettlementDate&gt; + &lt;DeclinedCode&gt;&lt;/DeclinedCode&gt; + &lt;DeclinedMessage&gt;&lt;/DeclinedMessage&gt; +&lt;/Response&gt; +</SubmitSingleRefundResult></SubmitSingleRefundResponse></soap:Body></soap:Envelope> + XML + end + + def successful_void_response + <<-XML +<?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><soap:Body><SubmitSingleVoidResponse xmlns="http://www.ippayments.com.au/interface/api/dts"><SubmitSingleVoidResult>&lt;Response&gt; + &lt;ResponseCode&gt;0&lt;/ResponseCode&gt; + &lt;DeclinedCode&gt;&lt;/DeclinedCode&gt; + &lt;DeclinedMessage&gt;&lt;/DeclinedMessage&gt; + &lt;/Response&gt; + </SubmitSingleVoidResult></SubmitSingleVoidResponse></soap:Body></soap:Envelope> + XML + end +end diff --git a/test/unit/gateways/bank_frick_test.rb b/test/unit/gateways/bank_frick_test.rb new file mode 100644 index 00000000000..4c49abc68c5 --- /dev/null +++ b/test/unit/gateways/bank_frick_test.rb @@ -0,0 +1,385 @@ +require 'test_helper' + +class BankFrickTest < Test::Unit::TestCase + def setup + @gateway = BankFrickGateway.new( + sender: 'sender-uuid', + channel: 'channel-uuid', + userid: 'user-uuid', + userpwd: 'password' + ) + + @credit_card = credit_card + @amount = 100 + + @options = { + order_id: '1', + billing_address: address, + description: 'Store Purchase' + } + end + + def test_successful_purchase + @gateway.expects(:ssl_post).returns(successful_purchase_response) + + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal '8a82944948642b6401486524e8637d97', response.authorization + assert response.test? + assert_match %r{Transaction succeeded}, response.message + end + + def test_failed_purchase + @gateway.expects(:ssl_post).returns(failed_purchase_response) + + response = @gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_match %r{account or user is blacklisted}, response.message + end + + def test_successful_authorize + @gateway.expects(:ssl_post).returns(successful_authorize_response) + + response = @gateway.authorize(@amount, @credit_card, @options) + assert_success response + assert_match %r{Transaction succeeded}, response.message + assert response.authorization + end + + def test_failed_authorize + @gateway.expects(:ssl_post).returns(failed_authorize_response) + + response = @gateway.authorize(@amount, @credit_card, @options) + assert_failure response + assert_equal 'account or user is blacklisted', response.message + end + + def test_successful_capture + @gateway.expects(:ssl_post).returns(successful_capture_response) + + assert response = @gateway.capture(@amount, '8a82944948642b6401486524e8637d97') + assert_success response + assert_match %r{Transaction succeeded}, response.message + end + + def test_failed_capture + @gateway.expects(:ssl_post).returns(failed_capture_response) + + response = @gateway.capture(nil, '') + assert_failure response + end + + def test_successful_refund + @gateway.expects(:ssl_post).returns(successful_refund_response) + + assert refund = @gateway.refund(@amount, '8a82944948642b6401486524e8637d97') + assert_success refund + end + + def test_failed_refund + @gateway.expects(:ssl_post).returns(failed_refund_response) + + response = @gateway.refund(nil, '') + assert_failure response + end + + def test_successful_void + @gateway.expects(:ssl_post).returns(successful_void_response) + + assert void = @gateway.void('8a82944948642b6401486524e8637d97') + assert_success void + end + + def test_failed_void + @gateway.expects(:ssl_post).returns(failed_void_response) + + response = @gateway.void('') + assert_failure response + end + + def test_successful_verify + @gateway.expects(:ssl_post).twice.returns(successful_void_response) + + response = @gateway.verify(@credit_card, @options) + assert_success response + assert_match %r{Transaction succeeded}, response.message + end + + def test_failed_verify + @gateway.expects(:ssl_post).returns(failed_authorize_response) + + response = @gateway.verify(@credit_card, @options) + assert_failure response + end + + private + + def successful_purchase_response + %( +<Response version="1.0"> + <Transaction mode="LIVE" channel="8a829417480cfaf601481b94906b220c" response="SYNC"> + <Identification> + <ShortID>4884.5929.7442</ShortID> + <UniqueID>8a82944948642b6401486524e8637d97</UniqueID> + </Identification> + <Payment code="CC.DB"> + <Clearing> + <Amount>1.00</Amount> + <Currency>EUR</Currency> + <Descriptor>4884.5929.7442 Payment Principals cards Store Purchase</Descriptor> + <FxRate>1.0</FxRate> + <FxSource>INTERN</FxSource> + <FxDate>2014-09-11 14:36:30</FxDate> + </Clearing> + </Payment> + <Processing code="CC.DB.90.00"> + <Timestamp>2014-09-11 14:36:30</Timestamp> + <Result>ACK</Result> + <Status code="90">NEW</Status> + <Reason code="00">Successful Processing</Reason> + <Return code="000.000.000">Transaction succeeded</Return> + <Risk score="0" /> + </Processing> + </Transaction> +</Response> + ) + end + + def failed_purchase_response + %( +<Response version="1.0"> + <Transaction mode="LIVE" channel="8a829417480cfaf601481b94906b220c" response="SYNC"> + <Identification> + <ShortID>6709.9540.7522</ShortID> + <UniqueID>8a82944a486446ed0148652bb89567dc</UniqueID> + </Identification> + <Payment code="CC.DB" /> + <Processing code="CC.DB.65.50"> + <Timestamp>2014-09-11 14:43:57</Timestamp> + <Result>NOK</Result> + <Status code="65">REJECTED_RISK</Status> + <Reason code="50">Blacklist Validation</Reason> + <Return code="800.300.101">account or user is blacklisted</Return> + <Risk score="-100" /> + </Processing> + </Transaction> +</Response> + ) + end + + def successful_authorize_response + %( +<Response version="1.0"> + <Transaction mode="LIVE" channel="8a829417480cfaf601481b94906b220c" response="SYNC"> + <Identification> + <ShortID>3231.0305.6546</ShortID> + <UniqueID>8a82944a486446ed0148652d337268c1</UniqueID> + </Identification> + <Payment code="CC.PA"> + <Clearing> + <Amount>1.00</Amount> + <Currency>EUR</Currency> + <Descriptor>3231.0305.6546 Payment Principals cards Store Purchase</Descriptor> + <FxRate>1.0</FxRate> + <FxSource>INTERN</FxSource> + <FxDate>2014-09-11 14:45:34</FxDate> + </Clearing> + </Payment> + <Processing code="CC.PA.90.00"> + <Timestamp>2014-09-11 14:45:34</Timestamp> + <Result>ACK</Result> + <Status code="90">NEW</Status> + <Reason code="00">Successful Processing</Reason> + <Return code="000.000.000">Transaction succeeded</Return> + <Risk score="0" /> + </Processing> + </Transaction> +</Response> + ) + end + + def failed_authorize_response + %( +<Response version="1.0"> + <Transaction mode="LIVE" channel="8a829417480cfaf601481b94906b220c" response="SYNC"> + <Identification> + <ShortID>1244.6081.9106</ShortID> + <UniqueID>8a82944948642b640148652ee86202fe</UniqueID> + </Identification> + <Payment code="CC.PA" /> + <Processing code="CC.PA.65.50"> + <Timestamp>2014-09-11 14:47:26</Timestamp> + <Result>NOK</Result> + <Status code="65">REJECTED_RISK</Status> + <Reason code="50">Blacklist Validation</Reason> + <Return code="800.300.101">account or user is blacklisted</Return> + <Risk score="-100" /> + </Processing> + </Transaction> +</Response> + ) + end + + def successful_capture_response + %( +<Response version="1.0"> + <Transaction mode="LIVE" channel="8a829417480cfaf601481b94906b220c" response="SYNC"> + <Identification> + <ShortID>5603.9999.9650</ShortID> + <UniqueID>8a82944a486446ed014865309b3c6c64</UniqueID> + <ReferenceID>8a82944948642b6401486530960d04ad</ReferenceID> + </Identification> + <Payment code="CC.CP"> + <Clearing> + <Amount>1.00</Amount> + <Currency>EUR</Currency> + <Descriptor>5603.9999.9650 Payment Principals cards Store Purchase</Descriptor> + <FxRate>1.0</FxRate> + <FxSource>INTERN</FxSource> + <FxDate>2014-09-11 14:49:17</FxDate> + </Clearing> + </Payment> + <Processing code="CC.CP.90.00"> + <Timestamp>2014-09-11 14:49:17</Timestamp> + <Result>ACK</Result> + <Status code="90">NEW</Status> + <Reason code="00">Successful Processing</Reason> + <Return code="000.000.000">Transaction succeeded</Return> + <Risk score="0" /> + </Processing> + </Transaction> +</Response> + ) + end + + def failed_capture_response + %( +<Response version="1.0"> + <Transaction mode="LIVE" channel="8a829417480cfaf601481b94906b220c" response="SYNC"> + <Identification> + <ShortID>3456.5163.4850</ShortID> + <UniqueID>8a82944a486446ed01486532dcf66e22</UniqueID> + <ReferenceID /> + </Identification> + <Payment code="CC.CP" /> + <Processing code="CC.CP.70.35"> + <Timestamp>2014-09-11 14:51:45</Timestamp> + <Result>NOK</Result> + <Status code="70">REJECTED_VALIDATION</Status> + <Reason code="35">Amount Error</Reason> + <Return code="100.550.300">request contains no amount or too low amount</Return> + </Processing> + </Transaction> +</Response> + ) + end + + def successful_refund_response + %( +<Response version="1.0"> + <Transaction mode="LIVE" channel="8a829417480cfaf601481b94906b220c" response="SYNC"> + <Identification> + <ShortID>5437.5700.1378</ShortID> + <UniqueID>8a82944948642b64014865347aef07eb</UniqueID> + <ReferenceID>8a82944a486446ed01486534758f6f85</ReferenceID> + </Identification> + <Payment code="CC.RF"> + <Clearing> + <Amount>1.00</Amount> + <Currency>EUR</Currency> + <Descriptor>5437.5700.1378 Payment Principals cards Store Purchase</Descriptor> + <FxRate>1.0</FxRate> + <FxSource>INTERN</FxSource> + <FxDate>2014-09-11 14:53:31</FxDate> + </Clearing> + </Payment> + <Processing code="CC.RF.90.00"> + <Timestamp>2014-09-11 14:53:31</Timestamp> + <Result>ACK</Result> + <Status code="90">NEW</Status> + <Reason code="00">Successful Processing</Reason> + <Return code="000.000.000">Transaction succeeded</Return> + <Risk score="0" /> + </Processing> + </Transaction> +</Response> + ) + end + + def failed_refund_response + %( +<Response version="1.0"> + <Transaction mode="LIVE" channel="8a829417480cfaf601481b94906b220c" response="SYNC"> + <Identification> + <ShortID>6248.2450.9090</ShortID> + <UniqueID>8a82944a486446ed0148653681ea713b</UniqueID> + <ReferenceID /> + </Identification> + <Payment code="CC.RF" /> + <Processing code="CC.RF.70.35"> + <Timestamp>2014-09-11 14:55:44</Timestamp> + <Result>NOK</Result> + <Status code="70">REJECTED_VALIDATION</Status> + <Reason code="35">Amount Error</Reason> + <Return code="100.550.300">request contains no amount or too low amount</Return> + </Processing> + </Transaction> +</Response> + ) + end + + def successful_void_response + %( +<Response version="1.0"> + <Transaction mode="LIVE" channel="8a829417480cfaf601481b94906b220c" response="SYNC"> + <Identification> + <ShortID>7305.8807.8754</ShortID> + <UniqueID>8a82944a486446ed014865398749724c</UniqueID> + <ReferenceID>8a82944a486446ed014865398203723d</ReferenceID> + </Identification> + <Payment code="CC.RV"> + <Clearing> + <Amount>1.00</Amount> + <Currency>EUR</Currency> + <Descriptor>7305.8807.8754 Payment Principals cards Store Purchase</Descriptor> + <FxRate>1.0</FxRate> + <FxSource>INTERN</FxSource> + <FxDate>2014-09-11 14:59:02</FxDate> + </Clearing> + </Payment> + <Processing code="CC.RV.90.00"> + <Timestamp>2014-09-11 14:59:02</Timestamp> + <Result>ACK</Result> + <Status code="90">NEW</Status> + <Reason code="00">Successful Processing</Reason> + <Return code="000.000.000">Transaction succeeded</Return> + <Risk score="0" /> + </Processing> + </Transaction> +</Response> + ) + end + + def failed_void_response + %( +<Response version="1.0"> + <Transaction mode="LIVE" channel="8a829417480cfaf601481b94906b220c" response="SYNC"> + <Identification> + <ShortID>4192.0294.9794</ShortID> + <UniqueID>8a82944948642b640148653bc3370c7e</UniqueID> + <ReferenceID /> + </Identification> + <Payment code="CC.RV" /> + <Processing code="CC.RV.70.30"> + <Timestamp>2014-09-11 15:01:28</Timestamp> + <Result>NOK</Result> + <Status code="70">REJECTED_VALIDATION</Status> + <Reason code="30">Reference Error</Reason> + <Return code="700.400.530">reversal needs at least one successful transaction of type (CP or DB or RB or PA)</Return> + <Risk score="0" /> + </Processing> + </Transaction> +</Response> + ) + end +end diff --git a/test/unit/gateways/banwire_test.rb b/test/unit/gateways/banwire_test.rb index 27761f56035..faa341c1f97 100644 --- a/test/unit/gateways/banwire_test.rb +++ b/test/unit/gateways/banwire_test.rb @@ -9,9 +9,9 @@ def setup :currency => 'MXN') @credit_card = credit_card('5204164299999999', - :month => 11, - :year => 2012, - :verification_value => '999') + :month => 11, + :year => 2012, + :verification_value => '999') @amount = 100 @options = { @@ -22,10 +22,10 @@ def setup } @amex_credit_card = credit_card('375932134599999', - :month => 3, - :year => 2017, - :first_name => "Banwire", - :last_name => "Test Card") + :month => 3, + :year => 2017, + :first_name => 'Banwire', + :last_name => 'Test Card') @amex_options = { :order_id => '2', :email => 'test@email.com', @@ -58,10 +58,10 @@ def test_invalid_json assert response = @gateway.purchase(@amount, @credit_card, @options) assert_failure response - assert_match /Invalid response received from the Banwire API/, response.message + assert_match %r{Invalid response received from the Banwire API}, response.message end - #American Express requires address and zipcode + # American Express requires address and zipcode def test_successful_amex_purchase response = stub_comms do @gateway.purchase(@amount, @amex_credit_card, @amex_options) @@ -75,16 +75,20 @@ def test_successful_amex_purchase assert response.test? end - #American Express requires address and zipcode + # American Express requires address and zipcode def test_unsuccessful_amex_request @gateway.expects(:ssl_post).returns(failed_purchase_amex_response) assert response = @gateway.purchase(@amount, @amex_credit_card, @amex_options) - assert_match /requeridos para pagos con AMEX/, response.message + assert_match %r{requeridos para pagos con AMEX}, response.message assert_failure response assert response.test? end + def test_transcript_scrubbing + assert_equal scrubbed_transcript, @gateway.scrub(transcript) + end + private def failed_purchase_response @@ -116,4 +120,12 @@ def invalid_json_response {"user":"desarrollo" RESPONSE end + + def transcript + %(user=desarrollo&phone=%28555%29555-5555&mail=unspecified%40email.com&reference=d833147668945a09fc72168bfa53c53c&concept=&card_num=5204164299999999&card_name=Longbob+Longsen&card_type=mastercard&card_exp=09%2F16&card_ccv2=999) + end + + def scrubbed_transcript + %(user=desarrollo&phone=%28555%29555-5555&mail=unspecified%40email.com&reference=d833147668945a09fc72168bfa53c53c&concept=&card_num=[FILTERED]&card_name=Longbob+Longsen&card_type=mastercard&card_exp=09%2F16&card_ccv2=[FILTERED]) + end end diff --git a/test/unit/gateways/barclaycard_smartpay_test.rb b/test/unit/gateways/barclaycard_smartpay_test.rb new file mode 100644 index 00000000000..87ad0da5c0b --- /dev/null +++ b/test/unit/gateways/barclaycard_smartpay_test.rb @@ -0,0 +1,601 @@ +require 'test_helper' + +class BarclaycardSmartpayTest < Test::Unit::TestCase + include CommStub + + def setup + @gateway = BarclaycardSmartpayGateway.new( + company: 'company', + merchant: 'merchant', + password: 'password' + ) + + @credit_card = credit_card + @three_ds_enrolled_card = credit_card('4212345678901237', brand: :visa) + @amount = 100 + + @options = { + order_id: '1', + billing_address: address, + description: 'Store Purchase' + } + + @options_with_alternate_address = { + order_id: '1', + billing_address: { + name: 'PU JOI SO', + address1: '新北市店溪路3579號139樓', + company: 'Widgets Inc', + city: '新北市', + zip: '231509', + country: 'TW', + phone: '(555)555-5555', + fax: '(555)555-6666' + }, + email: 'pujoi@so.com', + customer: 'PU JOI SO', + description: 'Store Purchase' + } + + @options_with_house_number_and_street = { + order_id: '1', + street: 'Top Level Drive', + house_number: '1000', + billing_address: address, + description: 'Store Purchase' + } + + @options_with_shipping_house_number_and_shipping_street = { + order_id: '1', + street: 'Top Level Drive', + house_number: '1000', + billing_address: address, + shipping_house_number: '999', + shipping_street: 'Downtown Loop', + shipping_address: { + name: 'PU JOI SO', + address1: '新北市店溪路3579號139樓', + company: 'Widgets Inc', + city: '新北市', + zip: '231509', + country: 'TW', + phone: '(555)555-5555', + fax: '(555)555-6666' + }, + description: 'Store Purchase' + } + + @options_with_credit_fields = { + order_id: '1', + billing_address: { + name: 'Jim Smith', + address1: '100 Street', + company: 'Widgets Inc', + city: 'Ottawa', + state: 'ON', + zip: 'K1C2N6', + country: 'CA', + phone: '(555)555-5555', + fax: '(555)555-6666'}, + email: 'long@bob.com', + customer: 'Longbob Longsen', + description: 'Store Purchase', + date_of_birth: '1990-10-11', + entity_type: 'NaturalPerson', + nationality: 'US', + shopper_name: { + firstName: 'Longbob', + lastName: 'Longsen', + gender: 'MALE' + } + } + + @avs_address = @options.clone + @avs_address.update(billing_address: { + name: 'Jim Smith', + street: 'Test AVS result', + houseNumberOrName: '2', + city: 'Cupertino', + state: 'CA', + zip: '95014', + country: 'US' + }) + + @normalized_3ds_2_options = { + reference: '345123', + shopper_email: 'john.smith@test.com', + shopper_ip: '77.110.174.153', + shopper_reference: 'John Smith', + billing_address: address(), + order_id: '123', + stored_credential: {reason_type: 'unscheduled'}, + three_ds_2: { + channel: 'browser', + browser_info: { + accept_header: 'unknown', + depth: 100, + java: false, + language: 'US', + height: 1000, + width: 500, + timezone: '-120', + user_agent: 'unknown' + }, + notification_url: 'https://example.com/notification' + } + } + end + + def test_successful_purchase + response = stub_comms do + @gateway.purchase(@amount, @credit_card, @options) + end.respond_with(successful_authorize_response, successful_capture_response) + + assert_success response + assert_equal '7914002629995504#8814002632606717', response.authorization + assert response.test? + end + + def test_successful_authorize_with_alternate_address + response = stub_comms do + @gateway.authorize(@amount, @credit_card, @options_with_alternate_address) + end.check_request do |endpoint, data, headers| + assert_match(/billingAddress.houseNumberOrName=%E6%96%B0%E5%8C%97%E5%B8%82%E5%BA%97%E6%BA%AA%E8%B7%AF3579%E8%99%9F139%E6%A8%93/, data) + assert_match(/billingAddress.street=Not\+Provided/, data) + end.respond_with(successful_authorize_response) + + assert_success response + assert_equal '7914002629995504', response.authorization + assert response.test? + end + + def test_successful_authorize_with_house_number_and_street + response = stub_comms do + @gateway.authorize(@amount, + @credit_card, + @options_with_house_number_and_street) + end.check_request do |endpoint, data, headers| + assert_match(/billingAddress.street=Top\+Level\+Drive/, data) + assert_match(/billingAddress.houseNumberOrName=1000/, data) + end.respond_with(successful_authorize_response) + + assert response + assert_success response + assert_equal '7914002629995504', response.authorization + end + + def test_successful_authorize_with_shipping_house_number_and_street + response = stub_comms do + @gateway.authorize(@amount, + @credit_card, + @options_with_shipping_house_number_and_shipping_street) + end.check_request do |endpoint, data, headers| + assert_match(/billingAddress.street=Top\+Level\+Drive/, data) + assert_match(/billingAddress.houseNumberOrName=1000/, data) + assert_match(/deliveryAddress.street=Downtown\+Loop/, data) + assert_match(/deliveryAddress.houseNumberOrName=999/, data) + end.respond_with(successful_authorize_response) + + assert response + assert_success response + assert_equal '7914002629995504', response.authorization + end + + def test_successful_authorize_with_extra_options + shopper_interaction = 'ContAuth' + shopper_statement = 'One-year premium subscription' + device_fingerprint = 'abcde123' + + response = stub_comms do + @gateway.authorize( + @amount, + @credit_card, + @options.merge( + shopper_interaction: shopper_interaction, + device_fingerprint: device_fingerprint, + shopper_statement: shopper_statement + ) + ) + end.check_request do |endpoint, data, headers| + assert_match(/shopperInteraction=#{shopper_interaction}/, data) + assert_match(/shopperStatement=#{Regexp.quote(CGI.escape(shopper_statement))}/, data) + assert_match(/deviceFingerprint=#{device_fingerprint}/, data) + end.respond_with(successful_authorize_response) + + assert_success response + end + + def test_successful_authorize + @gateway.stubs(:ssl_post).returns(successful_authorize_response) + + response = @gateway.authorize(@amount, @credit_card, @options) + assert_success response + assert_equal '7914002629995504', response.authorization + assert response.test? + end + + def test_successful_authorize_with_3ds + @gateway.stubs(:ssl_post).returns(successful_authorize_with_3ds_response) + + response = @gateway.authorize(@amount, @three_ds_enrolled_card, @options) + + assert_equal '8815161318854998', response.authorization + refute response.params['issuerUrl'].blank? + refute response.params['md'].blank? + refute response.params['paRequest'].blank? + assert response.test? + end + + def test_successful_authorize_with_3ds2_browser_client_data + @gateway.stubs(:ssl_post).returns(successful_authorize_with_3ds2_response) + + assert response = @gateway.authorize(@amount, @three_ds_enrolled_card, @normalized_3ds_2_options) + assert response.test? + assert_equal '8815609737078177', response.authorization + assert_equal response.params['resultCode'], 'IdentifyShopper' + refute response.params['additionalData']['threeds2.threeDS2Token'].blank? + refute response.params['additionalData']['threeds2.threeDSServerTransID'].blank? + refute response.params['additionalData']['threeds2.threeDSMethodURL'].blank? + end + + def test_failed_authorize + @gateway.stubs(:ssl_post).returns(failed_authorize_response) + + response = @gateway.authorize(@amount, @credit_card, @options) + assert_failure response + assert response.test? + end + + def test_successful_capture + @gateway.stubs(:ssl_post).returns(successful_capture_response) + + response = @gateway.capture(@amount, '7914002629995504', @options) + assert_success response + assert_equal '7914002629995504#8814002632606717', response.authorization + assert response.test? + end + + def test_failed_capture + @gateway.stubs(:ssl_post).raises(ActiveMerchant::ResponseError.new(stub(:code => '422', :body => failed_capture_response))) + + response = @gateway.capture(@amount, '0000000000000000', @options) + assert_failure response + assert_equal('167: Original pspReference required for this operation', response.message) + assert response.test? + end + + def test_legacy_capture_psp_reference_passed_for_refund + response = stub_comms do + @gateway.refund(@amount, '8814002632606717', @options) + end.check_request do |endpoint, data, headers| + assert_match(/originalReference=8814002632606717/, data) + end.respond_with(successful_refund_response) + + assert_success response + assert response.test? + end + + def test_successful_refund + response = stub_comms do + @gateway.refund(@amount, '7914002629995504#8814002632606717', @options) + end.check_request do |endpoint, data, headers| + assert_match(/originalReference=7914002629995504&/, data) + assert_no_match(/8814002632606717/, data) + end.respond_with(successful_refund_response) + + assert_success response + assert response.test? + end + + def test_failed_refund + @gateway.stubs(:ssl_post).raises(ActiveMerchant::ResponseError.new(stub(:code => '422', :body => failed_refund_response))) + + response = @gateway.refund(@amount, '0000000000000000', @options) + assert_failure response + assert_equal('137: Invalid amount specified', response.message) + assert response.test? + end + + def test_successful_credit + @gateway.expects(:ssl_post).returns(successful_credit_response) + + response = @gateway.credit(@amount, @credit_card, @options) + assert_success response + end + + def test_failed_credit + @gateway.expects(:ssl_post).returns(failed_credit_response) + + response = @gateway.credit(nil, @credit_card, @options) + assert_failure response + end + + def test_credit_contains_all_fields + response = stub_comms do + @gateway.credit(@amount, @credit_card, @options_with_credit_fields) + end.check_request do |endpoint, data, headers| + assert_match(%r{/refundWithData}, endpoint) + assert_match(/dateOfBirth=1990-10-11&/, data) + assert_match(/entityType=NaturalPerson&/, data) + assert_match(/nationality=US&/, data) + assert_match(/shopperName.firstName=Longbob&/, data) + end.respond_with(successful_credit_response) + + assert_success response + assert response.test? + end + + def test_successful_third_party_payout + response = stub_comms do + @gateway.credit(@amount, @credit_card, @options_with_credit_fields.merge({third_party_payout: true})) + end.check_request do |endpoint, data, headers| + if /storeDetailAndSubmitThirdParty/ =~ endpoint + assert_match(%r{/storeDetailAndSubmitThirdParty}, endpoint) + assert_match(/dateOfBirth=1990-10-11&/, data) + assert_match(/entityType=NaturalPerson&/, data) + assert_match(/nationality=US&/, data) + assert_match(/shopperName.firstName=Longbob&/, data) + assert_match(/recurring\.contract=PAYOUT/, data) + else + assert_match(/originalReference=/, data) + end + end.respond_with(successful_payout_store_response, successful_payout_confirm_response) + + assert_success response + assert response.test? + end + + def test_successful_void + @gateway.expects(:ssl_post).returns(successful_void_response) + + response = @gateway.void('7914002629995504', @options) + assert_success response + assert response.test? + end + + def test_successful_verify + @gateway.expects(:ssl_post).returns(successful_authorize_response) + + response = @gateway.verify(@credit_card, @options) + assert_success response + end + + def test_unsuccessful_verify + @gateway.expects(:ssl_post).returns(failed_authorize_response) + + response = @gateway.verify(@credit_card, @options) + assert_failure response + assert_equal 'Refused', response.message + end + + def test_authorize_nonfractional_currency + response = stub_comms do + @gateway.authorize(@amount, @credit_card, @options.merge(currency: 'JPY')) + end.check_request do |endpoint, data, headers| + assert_match(/amount.value=1/, data) + assert_match(/amount.currency=JPY/, data) + end.respond_with(successful_authorize_response) + + assert_success response + end + + def test_authorize_three_decimal_currency + response = stub_comms do + @gateway.authorize(@amount, @credit_card, @options.merge(currency: 'OMR')) + end.check_request do |endpoint, data, headers| + assert_match(/amount.value=100/, data) + assert_match(/amount.currency=OMR/, data) + end.respond_with(successful_authorize_response) + + assert_success response + end + + def test_successful_store + @gateway.expects(:ssl_post).returns(successful_store_response) + + response = @gateway.store(@credit_card, @options) + assert_success response + end + + def test_failed_store + @gateway.stubs(:ssl_post).raises(ActiveMerchant::ResponseError.new(stub(:code => '422', :body => failed_store_response))) + + response = @gateway.store(@credit_card, @options) + assert_failure response + assert response.test? + end + + def test_avs_result + @gateway.expects(:ssl_post).returns(failed_avs_response) + + response = @gateway.authorize(@amount, @credit_card, @avs_address) + assert_failure response + assert_equal 'N', response.avs_result['code'] + assert response.test? + end + + def test_transcript_scrubbing + assert_equal scrubbed_transcript, @gateway.scrub(transcript) + end + + def test_proper_error_response_handling + response = stub_comms do + @gateway.purchase(@amount, @credit_card, @options) + end.respond_with(configuration_error_response) + + message = "#{response.params['errorCode']}: #{response.params['message']}" + assert_equal('905: Payment details are not supported', message) + + response2 = stub_comms do + @gateway.purchase(@amount, @credit_card, @options) + end.respond_with(validation_error_response) + + message2 = "#{response2.params['errorCode']}: #{response2.params['message']}" + assert_equal('702: Internal error', message2) + end + + private + + def successful_authorize_response + 'pspReference=7914002629995504&authCode=56469&resultCode=Authorised' + end + + def successful_authorize_with_3ds_response + 'pspReference=8815161318854998&resultCode=RedirectShopper&issuerUrl=https%3A%2F%2Ftest.adyen.com%2Fhpp%2F3d%2Fvalidate.shtml&md=WIFa2sF3CuPyN53Txjt3U%2F%2BDuCsddzywiY5NLgEAdUAXPksHUzXL5E%2BsfvdpolkGWR8b1oh%2FNA3jNaUP9UCgfjhXqRslGFy9OGqcZ1ITMz54HHm%2FlsCKN9bTftKnYA4F7GqvOgcIIrinUZjbMvW9doGifwzSqYLo6ASOm6bARL5n7cIFV8IWtA2yPlO%2FztKSTRJt1glN4s8sMcpE57z4soWKMuycbdXdpp6d4ZRSa%2F1TPF0MnJF0zNaSAAkw9JpXqGMOz5sFF2Smpc38HXJzM%2FV%2B1mmoDhhWmXXOb5YQ0QSCS7DXKIcr8ZtuGuGmFp0QOfZiO41%2B2I2N7VhONVx8xSn%2BLu4m6vaDIg5qsnd9saxaWwbJpl9okKm6pB2MJap9ScuBCcvI496BPCrjQ2LHxvDWhk6M3Exemtv942NQIGlsiPaW0KXoC2dQvBsxWh0K&paRequest=eNpVUtuOgjAQ%2FRXj%2B1KKoIWMTVgxWR%2B8RNkPaMpEycrFUlb8%2B20B190%2BnXPm0pnTQnpRiMkJZauQwxabRpxxkmfLacQYDeiczihjgR%2BGbMrhEB%2FxxuEbVZNXJaeO63hAntSUK3kRpeYg5O19s%2BPUm%2FnBHMhIoUC1SXiKjT4URSxvba5QARlkKEWB%2FFSbgbLr41QIpXFVFUB6HWTVllo9OPNMwyeBVl35Reu6iQi53%2B9OM5Y7sipMVqmF1G9tA8QmAnlNeGgtakzjLs%2F4Pjl3u3TtbdNtZzDdJV%2FBPu7PEojNgExo5J5LmUvpfELDyPcjPwDS6yAKOxFffx4nxhXXrDwIUNt74oFQG%2FgrgLFdYSkfPFwws9WTAXZ1VaLJMPb%2BYiCvoVcf1mSpjW%2B%2BN9i8YKFr0MLa3Qdsl9yYREM37NtYAsSWkvElyfjiBv37CT9ySbE1' + end + + def successful_authorize_with_3ds2_response + 'additionalData.threeds2.threeDS2Token=BQABAQB9sBAzFS%2BrvT1fuY78N4P5BA5DO6s9Y6jCIzvMcH%2Bk5%2B0ms8dRPEZZhO8CYx%2Fa5NCl8r4vyJj0nI0HZ9CBl%2FQLxtGLYfVu6sNxZc9xZry%2Bm24pBGTtHsd4vunorPNPAGlYWHBXtf4h0Sj9Qy0bzlau7a%2Feayi1cpjbfV%2B8Eqw%2FAod1B80heU8sX2DKm5SHlR4o0qTu0WQUSJfKRxjdJ1AntgAxjYo3uFUlU%2FyhNpdRiAxgauLImbllfQTGVTcYBQXsY9FSakfAZRW1kT7bNMraCvRUpp4o1Z5ZezJxPcksfCEzFVPyJYcTvcV4odQK4tT6imRLRvG1OgUVNzNAuDBnEJtFOC%2BE5YwAwfKuloCqB9oAAOzL5ZHXOXPASY2ehJ3RaCZjqj5vmAX8L9GY35FV8q49skYZpzIvlMICWjErI2ayKMCiXHFDE54f2GJEhVRKpY9s506740UGQc0%2FMgbKyLyqtU%2BRG30BwA9bSt3NQKchm9xoOL7U%2Bzm6OIeikmw94TBq%2BmBN7SdQi%2BK2W4yfMkqFsl7hc7HHBa%2BOc6At7wxxdxCLg6wksQmDxElXeQfFkWvoBuR96fIHaXILnVHKjWcTbeulXBhVPA5Y47MLEtZL3G8k%2BzKTFUCW7O0MN2WxUoMBT8foan1%2B9QhZejEqiamreIs56PLQkJvhigyRQmiqwnVjXiFOv%2FEcWn0Z6IM2TnAfw3Kd2KwZ9JaePLtZ2Ck7%2FUEsdt1Kj2HYeE86WM4PESystER5oBT12xWXvbp8CEA7Mulmpd3bkiMl5IVRoSBL5pl4qZd1CrnG%2FeuvtXYTsN%2FdA%2BIcWwiLiXpmSwqaRB8DfChwouuNMAAkfKhQ6b3vLAToc3o%2B3Xa1QetsK8GI1pmjkoZRvLd2xfGhVe%2FmCl23wzQsAicwB9ZXXMgWbaS2OwdwsISQGOmsWrajzp7%2FvR0T4aHqJlrFvKnc9BrWEWbDi8g%2BDFZ2E2ifhFYSYhrHVA7yOIIDdTQnH3CIzaevxUAnbIyFsxrhy8USdP6R6CdJZ%2Bg0rIJ5%2FeZ5P8JjDiYJWi5FDJwy%2BNP9PQIFFim6psbELCtnAaW1m7pU1FeNwjYUGIdVD2f%2BVYJe4cWHPCaWAAsARNXTzjrfUEq%2BpEYDcs%2FLyTB8f69qSrmTSDGsCETsNNy27LY%2BtodGDKsxtW35jIqoV8l2Dra3wucman8nIZp3VTNtNvZDCqWetLXxBbFVZN6ecuoMPwhER5MBFUrkkXCSSFBK%2FNGp%2FXaEDP6A2hmUKvXikL3F9S7MIKQCUYC%2FI7K4DFYFBjTBzN4%3D&additionalData.threeds2.threeDSServerTransID=efbf9d05-5e6b-4659-a64e-f1dfa5d846c4&additionalData.threeds2.threeDSMethodURL=https%3A%2F%2Fpal-test.adyen.com%2Fthreeds2simulator%2Facs%2FstartMethod.shtml&pspReference=8815609737078177&resultCode=IdentifyShopper' + end + + def failed_authorize_response + 'pspReference=7914002630895750&refusalReason=Refused&resultCode=Refused' + end + + def successful_capture_response + 'pspReference=8814002632606717&response=%5Bcapture-received%5D' + end + + def failed_capture_response + 'errorType=validation&errorCode=167&message=Original+pspReference+required+for+this+operation&status=422' + end + + def successful_refund_response + 'pspReference=8814002634988063&response=%5Brefund-received%5D' + end + + def failed_refund_response + 'errorType=validation&errorCode=137&message=Invalid+amount+specified&status=422' + end + + def successful_credit_response + 'fraudResult.accountScore=70&fraudResult.results.0.accountScore=20&fraudResult.results.0.checkId=2&fraudResult.results.0.name=CardChunkUsage&fraudResult.results.1.accountScore=25&fraudResult.results.1.checkId=4&fraudResult.results.1.name=HolderNameUsage&fraudResult.results.2.accountScore=25&fraudResult.results.2.checkId=8&fraudResult.results.2.name=ShopperEmailUsage&fraudResult.results.3.accountScore=0&fraudResult.results.3.checkId=1&fraudResult.results.3.name=PaymentDetailRefCheck&fraudResult.results.4.accountScore=0&fraudResult.results.4.checkId=13&fraudResult.results.4.name=IssuerRefCheck&fraudResult.results.5.accountScore=0&fraudResult.results.5.checkId=15&fraudResult.results.5.name=IssuingCountryReferral&fraudResult.results.6.accountScore=0&fraudResult.results.6.checkId=26&fraudResult.results.6.name=ShopperEmailRefCheck&fraudResult.results.7.accountScore=0&fraudResult.results.7.checkId=27&fraudResult.results.7.name=PmOwnerRefCheck&fraudResult.results.8.accountScore=0&fraudResult.results.8.checkId=56&fraudResult.results.8.name=ShopperReferenceTrustCheck&fraudResult.results.9.accountScore=0&fraudResult.results.9.checkId=10&fraudResult.results.9.name=HolderNameContainsNumber&fraudResult.results.10.accountScore=0&fraudResult.results.10.checkId=11&fraudResult.results.10.name=HolderNameIsOneWord&fraudResult.results.11.accountScore=0&fraudResult.results.11.checkId=21&fraudResult.results.11.name=EmailDomainValidation&pspReference=8514743049239955&resultCode=Received' + end + + def successful_payout_store_response + 'pspReference=8815391117417347&resultCode=%5Bpayout-submit-received%5D' + end + + def successful_payout_confirm_response + 'pspReference=8815391117421182&response=%5Bpayout-confirm-received%5D' + end + + def failed_credit_response + 'errorType=validation&errorCode=137&message=Invalid+amount+specified&status=422' + end + + def successful_void_response + 'pspReference=7914002636728161&response=%5Bcancel-received%5D' + end + + def successful_store_response + 'alias=H167852639363479&aliasType=Default&pspReference=8614540938336754&rechargeReference=8314540938334240&recurringDetailReference=8414540862673349&result=Success' + end + + def failed_store_response + 'errorType=validation&errorCode=129&message=Expiry+Date+Invalid&status=422' + end + + def failed_avs_response + 'additionalData.liabilityShift=false&additionalData.authCode=3115&additionalData.avsResult=2+Neither+postal+code+nor+address+match&additionalData.cardHolderName=Longbob+Longsen&additionalData.threeDOffered=false&additionalData.refusalReasonRaw=AUTHORISED&additionalData.issuerCountry=US&additionalData.cvcResult=1+Matches&additionalData.avsResultRaw=2&additionalData.threeDAuthenticated=false&additionalData.cvcResultRaw=1&additionalData.acquirerCode=SmartPayTestPmmAcquirer&additionalData.acquirerReference=7F50RDN2L06&fraudResult.accountScore=170&fraudResult.results.0.accountScore=20&fraudResult.results.0.checkId=2&fraudResult.results.0.name=CardChunkUsage&fraudResult.results.1.accountScore=25&fraudResult.results.1.checkId=4&fraudResult.results.1.name=HolderNameUsage&fraudResult.results.2.accountScore=25&fraudResult.results.2.checkId=8&fraudResult.results.2.name=ShopperEmailUsage&fraudResult.results.3.accountScore=0&fraudResult.results.3.checkId=1&fraudResult.results.3.name=PaymentDetailRefCheck&fraudResult.results.4.accountScore=0&fraudResult.results.4.checkId=13&fraudResult.results.4.name=IssuerRefCheck&fraudResult.results.5.accountScore=0&fraudResult.results.5.checkId=15&fraudResult.results.5.name=IssuingCountryReferral&fraudResult.results.6.accountScore=0&fraudResult.results.6.checkId=26&fraudResult.results.6.name=ShopperEmailRefCheck&fraudResult.results.7.accountScore=0&fraudResult.results.7.checkId=27&fraudResult.results.7.name=PmOwnerRefCheck&fraudResult.results.8.accountScore=0&fraudResult.results.8.checkId=10&fraudResult.results.8.name=HolderNameContainsNumber&fraudResult.results.9.accountScore=0&fraudResult.results.9.checkId=11&fraudResult.results.9.name=HolderNameIsOneWord&fraudResult.results.10.accountScore=0&fraudResult.results.10.checkId=21&fraudResult.results.10.name=EmailDomainValidation&fraudResult.results.11.accountScore=100&fraudResult.results.11.checkId=20&fraudResult.results.11.name=AVSAuthResultCheck&fraudResult.results.12.accountScore=0&fraudResult.results.12.checkId=25&fraudResult.results.12.name=CVCAuthResultCheck&pspReference=8814591938804745&refusalReason=FRAUD-CANCELLED&resultCode=Cancelled&authCode=3115' + end + + def validation_error_response + 'errorType=validation&errorCode=702&message=Internal+error&status=500' + end + + def configuration_error_response + 'errorType=configuration&errorCode=905&message=Payment+details+are+not+supported&pspReference=4315391674762857&status=500' + end + + def transcript + %( + opening connection to pal-test.barclaycardsmartpay.com:443... + opened + starting SSL for pal-test.barclaycardsmartpay.com:443... + SSL established + <- "POST /pal/servlet/Payment/v12/authorise HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded; charset=utf-8\r\nAuthorization: Basic d3NAQ29tcGFueS5QbHVzNTAwQ1k6UVpiWWd3Z2pDejNiZEdiNEhqYXk=\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: pal-test.barclaycardsmartpay.com\r\nContent-Length: 466\r\n\r\n" + <- "merchantAccount=Plus500CYEcom&reference=1&shopperEmail=long%40bob.com&shopperReference=Longbob+Longsen&amount.currency=EUR&amount.value=100&card.cvc=737&card.expiryMonth=06&card.expiryYear=2016&card.holderName=Longbob+Longsen&card.number=4111111111111111&billingAddress.city=Ottawa&billingAddress.street=My+Street+Apt&billingAddress.houseNumberOrName=456+1&billingAddress.postalCode=K1C2N6&billingAddress.stateOrProvince=ON&billingAddress.country=CA&action=authorise" + -> "HTTP/1.1 200 OK\r\n" + -> "Date: Thu, 28 Jan 2016 21:32:16 GMT\r\n" + -> "Server: Apache\r\n" + -> "Set-Cookie: JSESSIONID=69398C80F6B1CBB04AA98B1D1895898B.test4e; Path=/pal/; Secure; HttpOnly\r\n" + -> "pspReference: 8614540167365201\r\n" + -> "Content-Length: 66\r\n" + -> "Connection: close\r\n" + -> "Content-Type: application/x-www-form-urlencoded;charset=utf-8\r\n" + -> "\r\n" + reading 66 bytes... + -> "" + -> "pspReference=8614540167365201&resultCode=Authorised&authCode=33683" + read 66 bytes + Conn close + opening connection to pal-test.barclaycardsmartpay.com:443... + opened + starting SSL for pal-test.barclaycardsmartpay.com:443... + SSL established + <- "POST /pal/servlet/Payment/v12/capture HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded; charset=utf-8\r\nAuthorization: Basic d3NAQ29tcGFueS5QbHVzNTAwQ1k6UVpiWWd3Z2pDejNiZEdiNEhqYXk=\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: pal-test.barclaycardsmartpay.com\r\nContent-Length: 140\r\n\r\n" + <- "merchantAccount=Plus500CYEcom&originalReference=8614540167365201&modificationAmount.currency=EUR&modificationAmount.value=100&action=capture" + -> "HTTP/1.1 200 OK\r\n" + -> "Date: Thu, 28 Jan 2016 21:32:18 GMT\r\n" + -> "Server: Apache\r\n" + -> "Set-Cookie: JSESSIONID=951837A566ED97C5869AA7C9DF91B608.test104e; Path=/pal/; Secure; HttpOnly\r\n" + -> "pspReference: 7914540167387121\r\n" + -> "Content-Length: 61\r\n" + -> "Connection: close\r\n" + -> "Content-Type: application/x-www-form-urlencoded;charset=utf-8\r\n" + -> "\r\n" + reading 61 bytes... + -> "" + -> "pspReference=7914540167387121&response=%5Bcapture-received%5D" + read 61 bytes + Conn close + ) + end + + def scrubbed_transcript + %( + opening connection to pal-test.barclaycardsmartpay.com:443... + opened + starting SSL for pal-test.barclaycardsmartpay.com:443... + SSL established + <- "POST /pal/servlet/Payment/v12/authorise HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded; charset=utf-8\r\nAuthorization: Basic [FILTERED]Accept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: pal-test.barclaycardsmartpay.com\r\nContent-Length: 466\r\n\r\n" + <- "merchantAccount=Plus500CYEcom&reference=1&shopperEmail=long%40bob.com&shopperReference=Longbob+Longsen&amount.currency=EUR&amount.value=100&card.cvc=[FILTERED]&card.expiryMonth=06&card.expiryYear=2016&card.holderName=Longbob+Longsen&card.number=[FILTERED]&billingAddress.city=Ottawa&billingAddress.street=My+Street+Apt&billingAddress.houseNumberOrName=456+1&billingAddress.postalCode=K1C2N6&billingAddress.stateOrProvince=ON&billingAddress.country=CA&action=authorise" + -> "HTTP/1.1 200 OK\r\n" + -> "Date: Thu, 28 Jan 2016 21:32:16 GMT\r\n" + -> "Server: Apache\r\n" + -> "Set-Cookie: JSESSIONID=69398C80F6B1CBB04AA98B1D1895898B.test4e; Path=/pal/; Secure; HttpOnly\r\n" + -> "pspReference: 8614540167365201\r\n" + -> "Content-Length: 66\r\n" + -> "Connection: close\r\n" + -> "Content-Type: application/x-www-form-urlencoded;charset=utf-8\r\n" + -> "\r\n" + reading 66 bytes... + -> "" + -> "pspReference=8614540167365201&resultCode=Authorised&authCode=33683" + read 66 bytes + Conn close + opening connection to pal-test.barclaycardsmartpay.com:443... + opened + starting SSL for pal-test.barclaycardsmartpay.com:443... + SSL established + <- "POST /pal/servlet/Payment/v12/capture HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded; charset=utf-8\r\nAuthorization: Basic [FILTERED]Accept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: pal-test.barclaycardsmartpay.com\r\nContent-Length: 140\r\n\r\n" + <- "merchantAccount=Plus500CYEcom&originalReference=8614540167365201&modificationAmount.currency=EUR&modificationAmount.value=100&action=capture" + -> "HTTP/1.1 200 OK\r\n" + -> "Date: Thu, 28 Jan 2016 21:32:18 GMT\r\n" + -> "Server: Apache\r\n" + -> "Set-Cookie: JSESSIONID=951837A566ED97C5869AA7C9DF91B608.test104e; Path=/pal/; Secure; HttpOnly\r\n" + -> "pspReference: 7914540167387121\r\n" + -> "Content-Length: 61\r\n" + -> "Connection: close\r\n" + -> "Content-Type: application/x-www-form-urlencoded;charset=utf-8\r\n" + -> "\r\n" + reading 61 bytes... + -> "" + -> "pspReference=7914540167387121&response=%5Bcapture-received%5D" + read 61 bytes + Conn close + ) + end + +end diff --git a/test/unit/gateways/barclays_epdq_extra_plus_test.rb b/test/unit/gateways/barclays_epdq_extra_plus_test.rb index 9fe11102190..330bdf13d4d 100644 --- a/test/unit/gateways/barclays_epdq_extra_plus_test.rb +++ b/test/unit/gateways/barclays_epdq_extra_plus_test.rb @@ -9,9 +9,10 @@ def setup :signature_encryptor => 'sha512' } @gateway = BarclaysEpdqExtraPlusGateway.new(@credentials) @credit_card = credit_card + @mastercard = credit_card('5399999999999999', :brand => 'mastercard') @amount = 100 - @identification = "3014726" - @billing_id = "myalias" + @identification = '3014726' + @billing_id = 'myalias' @options = { :order_id => '1', :billing_address => address, @@ -30,7 +31,7 @@ def setup @parameters_d3d = { 'FLAG3D' => 'Y', 'WIN3DS' => 'MAINW', - 'HTTP_ACCEPT' => "*/*" + 'HTTP_ACCEPT' => '*/*' } end @@ -92,6 +93,7 @@ def test_successful_purchase_with_3dsecure def test_successful_authorize @gateway.expects(:add_pair).at_least(1) @gateway.expects(:add_pair).with(anything, 'ECI', '7') + @gateway.expects(:add_pair).with(anything, 'Operation', 'RES') @gateway.expects(:ssl_post).returns(successful_purchase_response) assert response = @gateway.authorize(@amount, @credit_card, @options) assert_success response @@ -99,6 +101,16 @@ def test_successful_authorize assert response.test? end + def test_successful_authorize_with_mastercard + @gateway.expects(:add_pair).at_least(1) + @gateway.expects(:add_pair).with(anything, 'Operation', 'PAU') + @gateway.expects(:ssl_post).returns(successful_purchase_response) + assert response = @gateway.authorize(@amount, @mastercard, @options) + assert_success response + assert_equal '3014726;PAU', response.authorization + assert response.test? + end + def test_successful_authorize_with_custom_eci @gateway.expects(:add_pair).at_least(1) @gateway.expects(:add_pair).with(anything, 'ECI', '4') @@ -121,7 +133,7 @@ def test_successful_authorize_with_3dsecure def test_successful_capture @gateway.expects(:ssl_post).returns(successful_capture_response) - assert response = @gateway.capture(@amount, "3048326") + assert response = @gateway.capture(@amount, '3048326') assert_success response assert_equal '3048326;SAL', response.authorization assert response.test? @@ -129,7 +141,7 @@ def test_successful_capture def test_successful_capture_with_action_option @gateway.expects(:ssl_post).returns(successful_capture_response) - assert response = @gateway.capture(@amount, "3048326", :action => 'SAS') + assert response = @gateway.capture(@amount, '3048326', :action => 'SAS') assert_success response assert_equal '3048326;SAS', response.authorization assert response.test? @@ -137,7 +149,7 @@ def test_successful_capture_with_action_option def test_successful_void @gateway.expects(:ssl_post).returns(successful_void_response) - assert response = @gateway.void("3048606") + assert response = @gateway.void('3048606') assert_success response assert_equal '3048606;DES', response.authorization assert response.test? @@ -145,8 +157,8 @@ def test_successful_void def test_deprecated_credit @gateway.expects(:ssl_post).returns(successful_referenced_credit_response) - assert_deprecation_warning(Gateway::CREDIT_DEPRECATION_MESSAGE, @gateway) do - assert response = @gateway.credit(@amount, "3049652;SAL") + assert_deprecation_warning(Gateway::CREDIT_DEPRECATION_MESSAGE) do + assert response = @gateway.credit(@amount, '3049652;SAL') assert_success response assert_equal '3049652;RFD', response.authorization assert response.test? @@ -157,13 +169,13 @@ def test_successful_unreferenced_credit @gateway.expects(:ssl_post).returns(successful_unreferenced_credit_response) assert response = @gateway.credit(@amount, @credit_card) assert_success response - assert_equal "3049654;RFD", response.authorization + assert_equal '3049654;RFD', response.authorization assert response.test? end def test_successful_refund @gateway.expects(:ssl_post).returns(successful_referenced_credit_response) - assert response = @gateway.refund(@amount, "3049652") + assert response = @gateway.refund(@amount, '3049652') assert_success response assert_equal '3049652;RFD', response.authorization assert response.test? @@ -184,7 +196,7 @@ def test_deprecated_store_option @gateway.expects(:add_pair).at_least(1) @gateway.expects(:add_pair).with(anything, 'ECI', '7') @gateway.expects(:ssl_post).times(2).returns(successful_purchase_response) - assert_deprecation_warning(BarclaysEpdqExtraPlusGateway::OGONE_STORE_OPTION_DEPRECATION_MESSAGE, @gateway) do + assert_deprecation_warning(BarclaysEpdqExtraPlusGateway::OGONE_STORE_OPTION_DEPRECATION_MESSAGE) do assert response = @gateway.store(@credit_card, :store => @billing_id) assert_success response assert_equal '3014726;RES', response.authorization @@ -205,7 +217,7 @@ def test_create_readable_error_message_upon_failure assert_failure response assert response.test? - assert_equal "Unknown order", response.message + assert_equal 'Unknown order', response.message end def test_supported_countries @@ -282,31 +294,31 @@ def test_test_mode def test_format_error_message_with_slash_separator @gateway.expects(:ssl_post).returns('<ncresponse NCERRORPLUS="unknown order/1/i/67.192.100.64" STATUS="0" />') assert response = @gateway.purchase(@amount, @credit_card, @options) - assert_equal "Unknown order", response.message + assert_equal 'Unknown order', response.message end def test_format_error_message_with_pipe_separator @gateway.expects(:ssl_post).returns('<ncresponse NCERRORPLUS=" no card no|no exp date|no brand" STATUS="0" />') assert response = @gateway.purchase(@amount, @credit_card, @options) - assert_equal "No card no, no exp date, no brand", response.message + assert_equal 'No card no, no exp date, no brand', response.message end def test_format_error_message_with_no_separator @gateway.expects(:ssl_post).returns('<ncresponse NCERRORPLUS=" unknown order " STATUS="0" />') assert response = @gateway.purchase(@amount, @credit_card, @options) - assert_equal "Unknown order", response.message + assert_equal 'Unknown order', response.message end def test_without_signature gateway = BarclaysEpdqExtraPlusGateway.new(@credentials.merge(:signature => nil, :signature_encryptor => nil)) gateway.expects(:ssl_post).returns(successful_purchase_response) - assert_deprecation_warning(BarclaysEpdqExtraPlusGateway::OGONE_NO_SIGNATURE_DEPRECATION_MESSAGE, gateway) do + assert_deprecation_warning(BarclaysEpdqExtraPlusGateway::OGONE_NO_SIGNATURE_DEPRECATION_MESSAGE) do gateway.purchase(@amount, @credit_card, @options) end - gateway = BarclaysEpdqExtraPlusGateway.new(@credentials.merge(:signature => nil, :signature_encryptor => "none")) + gateway = BarclaysEpdqExtraPlusGateway.new(@credentials.merge(:signature => nil, :signature_encryptor => 'none')) gateway.expects(:ssl_post).returns(successful_purchase_response) - assert_no_deprecation_warning(@gateway) do + assert_no_deprecation_warning do gateway.purchase(@amount, @credit_card, @options) end end @@ -314,7 +326,7 @@ def test_without_signature def test_signature_for_accounts_created_before_10_may_20101 gateway = BarclaysEpdqExtraPlusGateway.new(@credentials.merge(:signature_encryptor => nil)) assert signature = gateway.send(:add_signature, @parameters) - assert_equal Digest::SHA1.hexdigest("1100EUR4111111111111111MrPSPIDRES2mynicesig").upcase, signature + assert_equal Digest::SHA1.hexdigest('1100EUR4111111111111111MrPSPIDRES2mynicesig').upcase, signature end def test_signature_for_accounts_with_signature_encryptor_to_sha1 @@ -348,13 +360,13 @@ def test_3dsecure_win_3ds_option gateway = BarclaysEpdqExtraPlusGateway.new(@credentials) gateway.send(:add_d3d, post, { :win_3ds => :pop_up }) - assert 'POPUP', post["WIN3DS"] + assert 'POPUP', post['WIN3DS'] gateway.send(:add_d3d, post, { :win_3ds => :pop_ix }) - assert 'POPIX', post["WIN3DS"] + assert 'POPIX', post['WIN3DS'] gateway.send(:add_d3d, post, { :win_3ds => :invalid }) - assert 'MAINW', post["WIN3DS"] + assert 'MAINW', post['WIN3DS'] end def test_3dsecure_additional_options @@ -362,8 +374,8 @@ def test_3dsecure_additional_options gateway = BarclaysEpdqExtraPlusGateway.new(@credentials) gateway.send(:add_d3d, post, { - :http_accept => "text/html", - :http_user_agent => "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0)", + :http_accept => 'text/html', + :http_user_agent => 'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0)', :accept_url => 'https://accept_url', :decline_url => 'https://decline_url', :exception_url => 'https://exception_url', @@ -371,8 +383,8 @@ def test_3dsecure_additional_options :complus => 'com_plus', :language => 'fr_FR' }) - assert 'HTTP_ACCEPT', "text/html" - assert 'HTTP_USER_AGENT', "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0)" + assert 'HTTP_ACCEPT', 'text/html' + assert 'HTTP_USER_AGENT', 'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0)' assert 'ACCEPTURL', 'https://accept_url' assert 'DECLINEURL', 'https://decline_url' assert 'EXCEPTIONURL', 'https://exception_url' @@ -395,19 +407,23 @@ def test_response_params_is_hash assert_instance_of Hash, response.params end + def test_transcript_scrubbing + assert_equal scrubbed_transcript, @gateway.scrub(transcript) + end + private def string_to_digest - "ALIAS=2mynicesigAMOUNT=100mynicesigCARDNO=4111111111111111mynicesig"+ - "CN=Client NamemynicesigCURRENCY=EURmynicesigOPERATION=RESmynicesig"+ - "ORDERID=1mynicesigPSPID=MrPSPIDmynicesig" + 'ALIAS=2mynicesigAMOUNT=100mynicesigCARDNO=4111111111111111mynicesig'\ + 'CN=Client NamemynicesigCURRENCY=EURmynicesigOPERATION=RESmynicesig'\ + 'ORDERID=1mynicesigPSPID=MrPSPIDmynicesig' end def d3d_string_to_digest - "ALIAS=2mynicesigAMOUNT=100mynicesigCARDNO=4111111111111111mynicesig"+ - "CN=Client NamemynicesigCURRENCY=EURmynicesigFLAG3D=Ymynicesig"+ - "HTTP_ACCEPT=*/*mynicesigOPERATION=RESmynicesigORDERID=1mynicesig"+ - "PSPID=MrPSPIDmynicesigWIN3DS=MAINWmynicesig" + 'ALIAS=2mynicesigAMOUNT=100mynicesigCARDNO=4111111111111111mynicesig'\ + 'CN=Client NamemynicesigCURRENCY=EURmynicesigFLAG3D=Ymynicesig'\ + 'HTTP_ACCEPT=*/*mynicesigOPERATION=RESmynicesigORDERID=1mynicesig'\ + 'PSPID=MrPSPIDmynicesigWIN3DS=MAINWmynicesig' end def successful_authorize_response @@ -678,4 +694,42 @@ def test_failed_authorization_due_to_unknown_order_number </ncresponse> END end + + def transcript + <<-TRANSCRIPT + CARDNO=4000100011112224&CN=Longbob+Longsen&COM=Store+Purchase&CVC=123&ECI=7&ED=0914&Operation=SAL&OwnerZip=K1C2N6&Owneraddress=1234+My+Street&PSPID=epdq1004895&PSWD=test&SHASign=0798F0F333C1867CC2B22D77E6452F8CAEFE9888&USERID=spreedly&amount=100&currency=GBP&orderID=b15d2f92e3ddee1a14b1b4b92cae9c&ownercty=CA&ownertelno=%28555%29555-5555&ownertown=Ottawa + <?xml version="1.0"?><ncresponse + orderID="b15d2f92e3ddee1a14b1b4b92cae9c" + PAYID="22489229" + NCSTATUS="0" + NCERROR="0" + ACCEPTANCE="test123" + STATUS="9" + amount="1" + currency="GBP" + PM="CreditCard" + BRAND="VISA" + NCERRORPLUS="!"> + </ncresponse> + TRANSCRIPT + end + + def scrubbed_transcript + <<-SCRUBBED_TRANSCRIPT + CARDNO=[FILTERED]&CN=Longbob+Longsen&COM=Store+Purchase&CVC=[FILTERED]&ECI=7&ED=0914&Operation=SAL&OwnerZip=K1C2N6&Owneraddress=1234+My+Street&PSPID=epdq1004895&PSWD=[FILTERED]&SHASign=0798F0F333C1867CC2B22D77E6452F8CAEFE9888&USERID=spreedly&amount=100&currency=GBP&orderID=b15d2f92e3ddee1a14b1b4b92cae9c&ownercty=CA&ownertelno=%28555%29555-5555&ownertown=Ottawa + <?xml version="1.0"?><ncresponse + orderID="b15d2f92e3ddee1a14b1b4b92cae9c" + PAYID="22489229" + NCSTATUS="0" + NCERROR="0" + ACCEPTANCE="test123" + STATUS="9" + amount="1" + currency="GBP" + PM="CreditCard" + BRAND="VISA" + NCERRORPLUS="!"> + </ncresponse> + SCRUBBED_TRANSCRIPT + end end diff --git a/test/unit/gateways/barclays_epdq_test.rb b/test/unit/gateways/barclays_epdq_test.rb deleted file mode 100644 index 52529397d1c..00000000000 --- a/test/unit/gateways/barclays_epdq_test.rb +++ /dev/null @@ -1,450 +0,0 @@ -require 'test_helper' - -class BarclaysEpdqTest < Test::Unit::TestCase - def setup - @gateway = BarclaysEpdqGateway.new( - :login => 'login', - :password => 'password', - :client_id => 'client_id' - ) - - @credit_card = credit_card - @amount = 100 - - @options = { - :billing_address => address - } - end - - def test_successful_purchase - @gateway.expects(:ssl_post).returns(successful_purchase_response) - - assert response = @gateway.purchase(@amount, @credit_card, @options) - assert_success response - - # Replace with authorization number from the successful response - assert_equal "150127237", response.authorization - assert response.test? - end - - def test_failed_purchase - @gateway.expects(:ssl_post).returns(failed_purchase_response) - - assert response = @gateway.purchase(@amount, @credit_card, @options) - assert_failure response - assert response.test? - end - - def test_deprecated_credit - @gateway.expects(:ssl_post).with(anything, regexp_matches(/>asdfasdf</)).returns(successful_credit_response) - assert_deprecation_warning(Gateway::CREDIT_DEPRECATION_MESSAGE, @gateway) do - assert_success @gateway.credit(@amount, "asdfasdf:jklljkll") - end - end - - def test_credit - @gateway.expects(:ssl_post).with(anything, regexp_matches(/#{@credit_card.number}/)).returns(successful_credit_response) - assert response = @gateway.credit(@amount, @credit_card) - assert_success response - end - - def test_refund - @gateway.expects(:ssl_post).with(anything, regexp_matches(/>asdfasdf</)).returns(successful_credit_response) - assert response = @gateway.refund(@amount, "asdfasdf:jklljkll") - assert_success response - end - - def test_handling_incorrectly_encoded_message - @gateway.expects(:ssl_post).returns(incorrectly_encoded_response) - - assert_nothing_raised { @gateway.purchase(@amount, @credit_card, @options) } - end - - private - - def successful_purchase_response - %(<?xml version="1.0" encoding="UTF-8"?> -<EngineDocList> - <DocVersion DataType="String">1.0</DocVersion> - <EngineDoc> - <ContentType DataType="String">OrderFormDoc</ContentType> - <DocumentId DataType="String">4d45da6a-5e10-3000-002b-00144ff2e45c</DocumentId> - <Instructions> - <Pipeline DataType="String">Payment</Pipeline> - - </Instructions> - <MessageList> - <MaxSev DataType="S32">3</MaxSev> - <Message> - <AdvisedAction DataType="S32">32</AdvisedAction> - <Audience DataType="String">Merchant</Audience> - <Component DataType="String">CcxBarclaysGbpAuth</Component> - <ContextId DataType="String">PaymentNormErrors</ContextId> - <DataState DataType="S32">3</DataState> - <FileLine DataType="S32">121</FileLine> - <FileName DataType="String">CcxBarclaysAuthResponseRedirector.cpp</FileName> - <FileTime DataType="String">10:41:43May 26 2009</FileTime> - <ResourceId DataType="S32">1</ResourceId> - <Sev DataType="S32">3</Sev> - <Text DataType="String">Approved.</Text> - - </Message> - - </MessageList> - <OrderFormDoc> - <Consumer> - <BillTo> - <Location> - <Address> - <City DataType="String">Ottawa</City> - <Country DataType="String"></Country> - <PostalCode DataType="String">K1C2N6</PostalCode> - <StateProv DataType="String">ON</StateProv> - <Street1 DataType="String">1234 My Street</Street1> - <Street2 DataType="String">Apt 1</Street2> - - </Address> - <Id DataType="String">4d45da6a-5e12-3000-002b-00144ff2e45c</Id> - - </Location> - - </BillTo> - <PaymentMech> - <CreditCard> - <Cvv2Indicator DataType="String">1</Cvv2Indicator> - <Cvv2Val DataType="String">123</Cvv2Val> - <Expires DataType="ExpirationDate">09/12</Expires> - <Number DataType="String">4715320629000001</Number> - <Type DataType="S32">1</Type> - - </CreditCard> - <Type DataType="String">CreditCard</Type> - - </PaymentMech> - - </Consumer> - <DateTime DataType="DateTime">1296599280954</DateTime> - <FraudInfo> - <FraudResult DataType="String">None</FraudResult> - <FraudResultCode DataType="S32">0</FraudResultCode> - <OrderScore DataType="Numeric" Precision="0">0</OrderScore> - <StrategyList> - <Strategy> - <FraudAction DataType="String">None</FraudAction> - <StrategyId DataType="S32">1</StrategyId> - <StrategyName DataType="String">My Rules</StrategyName> - <StrategyOwnerId DataType="S32">2974</StrategyOwnerId> - <StrategyScore DataType="Numeric" Precision="0">0</StrategyScore> - - </Strategy> - - </StrategyList> - <TotalScore DataType="Numeric" Precision="0">0</TotalScore> - - </FraudInfo> - <GroupId DataType="String">150127237</GroupId> - <Id DataType="String">150127237</Id> - <Mode DataType="String">P</Mode> - <Transaction> - <AuthCode DataType="String">442130</AuthCode> - <CardProcRequest> - <TerminalId DataType="String">90003750</TerminalId> - - </CardProcRequest> - <CardProcResp> - <AvsDisplay DataType="String">YY</AvsDisplay> - <AvsRespCode DataType="String">EX</AvsRespCode> - <CcErrCode DataType="S32">1</CcErrCode> - <CcReturnMsg DataType="String">Approved.</CcReturnMsg> - <Cvv2Resp DataType="String">2</Cvv2Resp> - <ProcAvsRespCode DataType="String">22</ProcAvsRespCode> - <ProcReturnCode DataType="String">00</ProcReturnCode> - <ProcReturnMsg DataType="String">AUTH CODE:442130</ProcReturnMsg> - <Status DataType="String">1</Status> - - </CardProcResp> - <CardholderPresentCode DataType="S32">7</CardholderPresentCode> - <CurrentTotals> - <Totals> - <Total DataType="Money" Currency="826">3900</Total> - - </Totals> - - </CurrentTotals> - <Id DataType="String">4d45da6a-5e11-3000-002b-00144ff2e45c</Id> - <InputEnvironment DataType="S32">4</InputEnvironment> - <SecurityIndicator DataType="S32">7</SecurityIndicator> - <TerminalInputCapability DataType="S32">1</TerminalInputCapability> - <Type DataType="String">Auth</Type> - - </Transaction> - - </OrderFormDoc> - <User> - <Alias DataType="String">2974</Alias> - <ClientId DataType="S32">2974</ClientId> - <EffectiveAlias DataType="String">2974</EffectiveAlias> - <EffectiveClientId DataType="S32">2974</EffectiveClientId> - <Name DataType="String">spreedlytesting</Name> - <Password DataType="String">XXXXXXX</Password> - - </User> - - </EngineDoc> - <TimeIn DataType="DateTime">1296599280948</TimeIn> - <TimeOut DataType="DateTime">1296599283885</TimeOut> - -</EngineDocList> -) - end - - def failed_purchase_response - %(<?xml version="1.0" encoding="UTF-8"?> -<EngineDocList> - <DocVersion DataType="String">1.0</DocVersion> - <EngineDoc> - <ContentType DataType="String">OrderFormDoc</ContentType> - <DocumentId DataType="String">4d45da6a-5d6b-3000-002b-00144ff2e45c</DocumentId> - <Instructions> - <Pipeline DataType="String">Payment</Pipeline> - - </Instructions> - <MessageList> - <MaxSev DataType="S32">3</MaxSev> - <Message> - <AdvisedAction DataType="S32">32</AdvisedAction> - <Audience DataType="String">Merchant</Audience> - <Component DataType="String">CcxBarclaysGbpAuth</Component> - <ContextId DataType="String">PaymentNormErrors</ContextId> - <DataState DataType="S32">3</DataState> - <FileLine DataType="S32">121</FileLine> - <FileName DataType="String">CcxBarclaysAuthResponseRedirector.cpp</FileName> - <FileTime DataType="String">10:41:43May 26 2009</FileTime> - <ResourceId DataType="S32">50</ResourceId> - <Sev DataType="S32">3</Sev> - <Text DataType="String">Declined (General).</Text> - - </Message> - - </MessageList> - <OrderFormDoc> - <Consumer> - <BillTo> - <Location> - <Address> - <City DataType="String">Ottawa</City> - <Country DataType="String"></Country> - <PostalCode DataType="String">K1C2N6</PostalCode> - <StateProv DataType="String">ON</StateProv> - <Street1 DataType="String">1234 My Street</Street1> - <Street2 DataType="String">Apt 1</Street2> - - </Address> - <Id DataType="String">4d45da6a-5d6d-3000-002b-00144ff2e45c</Id> - - </Location> - - </BillTo> - <PaymentMech> - <CreditCard> - <Cvv2Indicator DataType="String">1</Cvv2Indicator> - <Cvv2Val DataType="String">123</Cvv2Val> - <Expires DataType="ExpirationDate">09/12</Expires> - <Number DataType="String">4715320629000027</Number> - <Type DataType="S32">1</Type> - - </CreditCard> - <Type DataType="String">CreditCard</Type> - - </PaymentMech> - - </Consumer> - <DateTime DataType="DateTime">1296598178436</DateTime> - <FraudInfo> - <FraudResult DataType="String">None</FraudResult> - <FraudResultCode DataType="S32">0</FraudResultCode> - <OrderScore DataType="Numeric" Precision="0">0</OrderScore> - <StrategyList> - <Strategy> - <FraudAction DataType="String">None</FraudAction> - <StrategyId DataType="S32">1</StrategyId> - <StrategyName DataType="String">My Rules</StrategyName> - <StrategyOwnerId DataType="S32">2974</StrategyOwnerId> - <StrategyScore DataType="Numeric" Precision="0">0</StrategyScore> - - </Strategy> - - </StrategyList> - <TotalScore DataType="Numeric" Precision="0">0</TotalScore> - - </FraudInfo> - <GroupId DataType="String">22394792</GroupId> - <Id DataType="String">22394792</Id> - <Mode DataType="String">P</Mode> - <Transaction> - <CardProcRequest> - <TerminalId DataType="String">90003745</TerminalId> - - </CardProcRequest> - <CardProcResp> - <AvsDisplay DataType="String">NY</AvsDisplay> - <AvsRespCode DataType="String">B5</AvsRespCode> - <CcErrCode DataType="S32">50</CcErrCode> - <CcReturnMsg DataType="String">Declined (General).</CcReturnMsg> - <Cvv2Resp DataType="String">2</Cvv2Resp> - <ProcAvsRespCode DataType="String">24</ProcAvsRespCode> - <ProcReturnCode DataType="String">05</ProcReturnCode> - <ProcReturnMsg DataType="String">NOT AUTHORISED</ProcReturnMsg> - <Status DataType="String">1</Status> - - </CardProcResp> - <CardholderPresentCode DataType="S32">7</CardholderPresentCode> - <CurrentTotals> - <Totals> - <Total DataType="Money" Currency="826">4205</Total> - - </Totals> - - </CurrentTotals> - <Id DataType="String">4d45da6a-5d6c-3000-002b-00144ff2e45c</Id> - <InputEnvironment DataType="S32">4</InputEnvironment> - <SecurityIndicator DataType="S32">7</SecurityIndicator> - <TerminalInputCapability DataType="S32">1</TerminalInputCapability> - <Type DataType="String">Auth</Type> - - </Transaction> - - </OrderFormDoc> - <User> - <Alias DataType="String">2974</Alias> - <ClientId DataType="S32">2974</ClientId> - <EffectiveAlias DataType="String">2974</EffectiveAlias> - <EffectiveClientId DataType="S32">2974</EffectiveClientId> - <Name DataType="String">login</Name> - <Password DataType="String">XXXXXXX</Password> - - </User> - - </EngineDoc> - <TimeIn DataType="DateTime">1296598178430</TimeIn> - <TimeOut DataType="DateTime">1296598179756</TimeOut> - -</EngineDocList> -) - end - - def successful_credit_response - %(<?xml version="1.0" encoding="UTF-8"?> -<EngineDocList> - <DocVersion DataType="String">1.0</DocVersion> - <EngineDoc> - <ContentType DataType="String">OrderFormDoc</ContentType> - <DocumentId DataType="String">4d45da6a-8bcd-3000-002b-00144ff2e45c</DocumentId> - <Instructions> - <Pipeline DataType="String">Payment</Pipeline> - - </Instructions> - <MessageList> - - </MessageList> - <OrderFormDoc> - <Consumer> - <BillTo> - <Location> - <Address> - <City DataType="String">Ottawa</City> - <PostalCode DataType="String">K1C2N6</PostalCode> - <StateProv DataType="String">ON</StateProv> - <Street1 DataType="String">1234 My Street</Street1> - <Street2 DataType="String">Apt 1</Street2> - - </Address> - <Id DataType="String">4d45da6a-8bcc-3000-002b-00144ff2e45c</Id> - - </Location> - - </BillTo> - <PaymentMech> - <CreditCard> - <ExchangeType DataType="S32">1</ExchangeType> - <Expires DataType="ExpirationDate">09/12</Expires> - <Number DataType="String">4715320629000001</Number> - - </CreditCard> - <Type DataType="String">CreditCard</Type> - - </PaymentMech> - - </Consumer> - <DateTime DataType="DateTime">1296679499967</DateTime> - <FraudInfo> - <FraudResult DataType="String">None</FraudResult> - <FraudResultCode DataType="S32">0</FraudResultCode> - <OrderScore DataType="Numeric" Precision="0">0</OrderScore> - <StrategyList> - <Strategy> - <FraudAction DataType="String">None</FraudAction> - <StrategyId DataType="S32">1</StrategyId> - <StrategyName DataType="String">My Rules</StrategyName> - <StrategyOwnerId DataType="S32">2974</StrategyOwnerId> - <StrategyScore DataType="Numeric" Precision="0">0</StrategyScore> - - </Strategy> - - </StrategyList> - <TotalScore DataType="Numeric" Precision="0">0</TotalScore> - - </FraudInfo> - <GroupId DataType="String">b92b5bff09d05d771c17e6b6b30531ed</GroupId> - <Id DataType="String">b92b5bff09d05d771c17e6b6b30531ed</Id> - <Mode DataType="String">P</Mode> - <Transaction> - <CardProcResp> - <CcErrCode DataType="S32">1</CcErrCode> - <CcReturnMsg DataType="String">Approved.</CcReturnMsg> - <ProcReturnCode DataType="String">1</ProcReturnCode> - <ProcReturnMsg DataType="String">Approved</ProcReturnMsg> - <Status DataType="String">1</Status> - - </CardProcResp> - <CardholderPresentCode DataType="S32">7</CardholderPresentCode> - <ChargeTypeCode DataType="String">S</ChargeTypeCode> - <CurrentTotals> - <Totals> - <Total DataType="Money" Currency="826">3900</Total> - - </Totals> - - </CurrentTotals> - <Id DataType="String">4d45da6a-8bce-3000-002b-00144ff2e45c</Id> - <InputEnvironment DataType="S32">4</InputEnvironment> - <SecurityIndicator DataType="S32">7</SecurityIndicator> - <TerminalInputCapability DataType="S32">1</TerminalInputCapability> - <Type DataType="String">Credit</Type> - - </Transaction> - - </OrderFormDoc> - <User> - <Alias DataType="String">2974</Alias> - <ClientId DataType="S32">2974</ClientId> - <EffectiveAlias DataType="String">2974</EffectiveAlias> - <EffectiveClientId DataType="S32">2974</EffectiveClientId> - <Name DataType="String">spreedlytesting</Name> - <Password DataType="String">XXXXXXX</Password> - - </User> - - </EngineDoc> - <TimeIn DataType="DateTime">1296679499961</TimeIn> - <TimeOut DataType="DateTime">1296679500312</TimeOut> - -</EngineDocList> - -) - end - - def incorrectly_encoded_response - successful_purchase_response.gsub("Ottawa", "\xD6ttawa") - end -end diff --git a/test/unit/gateways/be2bill_test.rb b/test/unit/gateways/be2bill_test.rb new file mode 100644 index 00000000000..d83f42e42b5 --- /dev/null +++ b/test/unit/gateways/be2bill_test.rb @@ -0,0 +1,51 @@ +require 'test_helper' + +class Be2billTest < Test::Unit::TestCase + def setup + @gateway = Be2billGateway.new( + :login => 'login', + :password => 'password' + ) + + @credit_card = credit_card + @amount = 100 + + @options = { + :order_id => '1', + :billing_address => address, + :description => 'Store Purchase' + } + end + + def test_successful_purchase + @gateway.expects(:ssl_post).returns(successful_purchase_response) + + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_instance_of Response, response + assert_success response + + # Replace with authorization number from the successful response + assert_equal 'A189063', response.authorization + assert response.test? + end + + def test_unsuccessful_request + @gateway.expects(:ssl_post).returns(failed_purchase_response) + + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert response.test? + end + + private + + # Place raw successful response from gateway here + def successful_purchase_response + {'OPERATIONTYPE'=>'payment', 'TRANSACTIONID'=>'A189063', 'EXECCODE'=>'0000', 'MESSAGE'=>'The transaction has been accepted.', 'ALIAS'=>'A189063', 'DESCRIPTOR'=>'RENTABILITEST'}.to_json + end + + # Place raw failed response from gateway here + def failed_purchase_response + {'OPERATIONTYPE'=>'payment', 'TRANSACTIONID'=>'A189063', 'EXECCODE'=>'1001', 'MESSAGE'=>"The parameter \"CARDCODE\" is missing.\n", 'DESCRIPTOR'=>'RENTABILITEST'}.to_json + end +end diff --git a/test/unit/gateways/beanstream_interac_test.rb b/test/unit/gateways/beanstream_interac_test.rb index d7659f76326..a0a7bac3862 100644 --- a/test/unit/gateways/beanstream_interac_test.rb +++ b/test/unit/gateways/beanstream_interac_test.rb @@ -8,44 +8,44 @@ def setup ) @amount = 100 - - @options = { + + @options = { :order_id => '1', :billing_address => address, :description => 'Store Purchase' } end - + def test_successful_purchase @gateway.expects(:ssl_post).returns(successful_purchase_response) response = @gateway.purchase(@amount, @options) assert_success response - assert_equal "R", response.params["responseType"] - assert response.params["pageContents"] - assert_equal response.params["pageContents"], response.redirect + assert_equal 'R', response.params['responseType'] + assert response.params['pageContents'] + assert_equal response.params['pageContents'], response.redirect end - + def test_successful_confirmation @gateway.expects(:ssl_post).returns(successful_confirmation_response) response = @gateway.confirm(successful_return_from_interac_online) assert response.success? - assert_equal "Approved", response.message - assert_equal "10000029;5.00;P", response.authorization + assert_equal 'Approved', response.message + assert_equal '10000029;5.00;P', response.authorization end private - + def successful_purchase_response - "responseType=R&pageContents=%3CHTML%3E%3CHEAD%3E%3C%2FHEAD%3E%3CBODY%3E%3CFORM%20action%3D%22https%3A%2F%2Fpayments%2Ebeanstream%2Ecom%2FiOnlineEmulator%2Fgateway%2Easp%22%20method%3DPOST%20id%3DfrmIOnline%20name%3DfrmIOnline%3E%3Cinput%20type%3D%22hidden%22%20name%3D%22IDEBIT%5FMERCHNUM%22%20%20value%3D%2210010162199999%22%3E%3Cinput%20type%3D%22hidden%22%20name%3D%22IDEBIT%5FAMOUNT%22%20%20value%3D%221500%22%3E%3Cinput%20type%3D%22hidden%22%20name%3D%22IDEBIT%5FTERMID%22%20value%3D%2262199999%22%3E%3Cinput%20type%3D%22hidden%22%20name%3D%22IDEBIT%5FCURRENCY%22%20value%3D%22CAD%22%3E%3Cinput%20type%3D%22hidden%22%20name%3D%22IDEBIT%5FINVOICE%22%20value%3D%221be7db7a129b07ac5f7e%22%3E%3Cinput%20type%3D%22hidden%22%20name%3D%22IDEBIT%5FMERCHDATA%22%20value%3D%226CE36AF7%2D5013%2D4B94%2DB740153714A41962%22%3E%3Cinput%20type%3D%22hidden%22%20name%3D%22IDEBIT%5FFUNDEDURL%22%20value%3D%22https%3A%2F%2Fwww%2Ebeanstream%2Ecom%2Fscripts%2Fprocess%5Ftransaction%5Fauth%2Easp%3F%26funded%3D1%22%3E%3Cinput%20type%3D%22hidden%22%20name%3D%22IDEBIT%5FNOTFUNDEDURL%22%20value%3D%22https%3A%2F%2Fwww%2Ebeanstream%2Ecom%2Fscripts%2Fprocess%5Ftransaction%5Fauth%2Easp%3F%26funded%3D0%22%3E%3Cinput%20type%3D%22hidden%22%20name%3D%22merchant%5Fname%22%20value%3D%22Cody%20Fauser%22%3E%3Cinput%20type%3D%22hidden%22%20name%3D%22referHost%22%20value%3D%22https%3A%2F%2Fwww%2Ebeanstream%2Ecom%2Fscripts%2Fprocess%5Ftransaction%2Easp%22%3E%3Cinput%20type%3D%22hidden%22%20name%3D%22referHost2%22%20value%3D%22https%3A%2F%2Fwww%2Ecatnrose%2Ecom%2Fioxml%2Easp%22%3E%3Cinput%20type%3D%22hidden%22%20name%3D%22referHost3%22%20value%3D%22%22%3E%3Cinput%20type%3D%22hidden%22%20name%3D%22IDEBIT%5FMERCHLANG%22%20value%3D%22en%22%3E%3Cinput%20type%3D%22hidden%22%20name%3D%22IDEBIT%5FVERSION%22%20value%3D%221%22%3E%3C%2FFORM%3E%3CSCRIPT%20language%3D%22JavaScript%22%3Edocument%2EfrmIOnline%2Esubmit%28%29%3B%3C%2FSCRIPT%3E%3C%2FBODY%3E%3C%2FHTML%3E" + 'responseType=R&pageContents=%3CHTML%3E%3CHEAD%3E%3C%2FHEAD%3E%3CBODY%3E%3CFORM%20action%3D%22https%3A%2F%2Fpayments%2Ebeanstream%2Ecom%2FiOnlineEmulator%2Fgateway%2Easp%22%20method%3DPOST%20id%3DfrmIOnline%20name%3DfrmIOnline%3E%3Cinput%20type%3D%22hidden%22%20name%3D%22IDEBIT%5FMERCHNUM%22%20%20value%3D%2210010162199999%22%3E%3Cinput%20type%3D%22hidden%22%20name%3D%22IDEBIT%5FAMOUNT%22%20%20value%3D%221500%22%3E%3Cinput%20type%3D%22hidden%22%20name%3D%22IDEBIT%5FTERMID%22%20value%3D%2262199999%22%3E%3Cinput%20type%3D%22hidden%22%20name%3D%22IDEBIT%5FCURRENCY%22%20value%3D%22CAD%22%3E%3Cinput%20type%3D%22hidden%22%20name%3D%22IDEBIT%5FINVOICE%22%20value%3D%221be7db7a129b07ac5f7e%22%3E%3Cinput%20type%3D%22hidden%22%20name%3D%22IDEBIT%5FMERCHDATA%22%20value%3D%226CE36AF7%2D5013%2D4B94%2DB740153714A41962%22%3E%3Cinput%20type%3D%22hidden%22%20name%3D%22IDEBIT%5FFUNDEDURL%22%20value%3D%22https%3A%2F%2Fwww%2Ebeanstream%2Ecom%2Fscripts%2Fprocess%5Ftransaction%5Fauth%2Easp%3F%26funded%3D1%22%3E%3Cinput%20type%3D%22hidden%22%20name%3D%22IDEBIT%5FNOTFUNDEDURL%22%20value%3D%22https%3A%2F%2Fwww%2Ebeanstream%2Ecom%2Fscripts%2Fprocess%5Ftransaction%5Fauth%2Easp%3F%26funded%3D0%22%3E%3Cinput%20type%3D%22hidden%22%20name%3D%22merchant%5Fname%22%20value%3D%22Cody%20Fauser%22%3E%3Cinput%20type%3D%22hidden%22%20name%3D%22referHost%22%20value%3D%22https%3A%2F%2Fwww%2Ebeanstream%2Ecom%2Fscripts%2Fprocess%5Ftransaction%2Easp%22%3E%3Cinput%20type%3D%22hidden%22%20name%3D%22referHost2%22%20value%3D%22https%3A%2F%2Fwww%2Ecatnrose%2Ecom%2Fioxml%2Easp%22%3E%3Cinput%20type%3D%22hidden%22%20name%3D%22referHost3%22%20value%3D%22%22%3E%3Cinput%20type%3D%22hidden%22%20name%3D%22IDEBIT%5FMERCHLANG%22%20value%3D%22en%22%3E%3Cinput%20type%3D%22hidden%22%20name%3D%22IDEBIT%5FVERSION%22%20value%3D%221%22%3E%3C%2FFORM%3E%3CSCRIPT%20language%3D%22JavaScript%22%3Edocument%2EfrmIOnline%2Esubmit%28%29%3B%3C%2FSCRIPT%3E%3C%2FBODY%3E%3C%2FHTML%3E' end - + def successful_return_from_interac_online - "bank_choice=1&merchant_name=Billing+Boss+IO+SB&confirmValue=&headerText=&IDEBIT_MERCHDATA=C4B50A48-6E11-4C21-A31EF4A602BC0099&IDEBIT_INVOICE=18face21593b59c7bb7e&IDEBIT_AMOUNT=1500&IDEBIT_FUNDEDURL=http%3A%2F%2Febay.massapparel.com%3A8000%2Finterac%2Ffunded%3Ffunded%3D1&IDEBIT_NOTFUNDEDURL=http%3A%2F%2Febay.massapparel.com%3A8000%2Finterac%2Fnotfunded%3Ffunded%3D0&IDEBIT_ISSLANG=en&IDEBIT_TRACK2=3728024906540591214%3D12010123456789XYZ&IDEBIT_ISSCONF=CONF%23TEST&IDEBIT_ISSNAME=TestBank1&IDEBIT_VERSION=1&accountType=Chequing" + 'bank_choice=1&merchant_name=Billing+Boss+IO+SB&confirmValue=&headerText=&IDEBIT_MERCHDATA=C4B50A48-6E11-4C21-A31EF4A602BC0099&IDEBIT_INVOICE=18face21593b59c7bb7e&IDEBIT_AMOUNT=1500&IDEBIT_FUNDEDURL=http%3A%2F%2Febay.massapparel.com%3A8000%2Finterac%2Ffunded%3Ffunded%3D1&IDEBIT_NOTFUNDEDURL=http%3A%2F%2Febay.massapparel.com%3A8000%2Finterac%2Fnotfunded%3Ffunded%3D0&IDEBIT_ISSLANG=en&IDEBIT_TRACK2=3728024906540591214%3D12010123456789XYZ&IDEBIT_ISSCONF=CONF%23TEST&IDEBIT_ISSNAME=TestBank1&IDEBIT_VERSION=1&accountType=Chequing' end - + def successful_confirmation_response - "trnApproved=1&trnId=10000029&messageId=1&messageText=Approved&trnOrderNumber=f29d2406b49b239b6dfb5db1f642b2&authCode=TEST&errorType=N&errorFields=&responseType=T&trnAmount=5%2E00&trnDate=6%2F8%2F2008+3%3A17%3A12+PM&avsProcessed=0&avsId=0&avsResult=0&avsAddrMatch=0&avsPostalMatch=0&avsMessage=Address+Verification+not+performed+for+this+transaction%2E&trnType=P&paymentMethod=IO&ioConfCode=CONF%23TEST&ioInstName=TestBank1&ref1=reference+one&ref2=&ref3=&ref4=&ref5=" + 'trnApproved=1&trnId=10000029&messageId=1&messageText=Approved&trnOrderNumber=f29d2406b49b239b6dfb5db1f642b2&authCode=TEST&errorType=N&errorFields=&responseType=T&trnAmount=5%2E00&trnDate=6%2F8%2F2008+3%3A17%3A12+PM&avsProcessed=0&avsId=0&avsResult=0&avsAddrMatch=0&avsPostalMatch=0&avsMessage=Address+Verification+not+performed+for+this+transaction%2E&trnType=P&paymentMethod=IO&ioConfCode=CONF%23TEST&ioInstName=TestBank1&ref1=reference+one&ref2=&ref3=&ref4=&ref5=' end end diff --git a/test/unit/gateways/beanstream_test.rb b/test/unit/gateways/beanstream_test.rb index 16db9ce0f52..c07adf22c0e 100644 --- a/test/unit/gateways/beanstream_test.rb +++ b/test/unit/gateways/beanstream_test.rb @@ -1,17 +1,30 @@ require 'test_helper' class BeanstreamTest < Test::Unit::TestCase + include CommStub + def setup Base.mode = :test @gateway = BeanstreamGateway.new( :login => 'merchant id', :user => 'username', - :password => 'password' + :password => 'password', + :api_key => 'api_key' ) @credit_card = credit_card + @decrypted_credit_card = ActiveMerchant::Billing::NetworkTokenizationCreditCard.new( + month: '01', + year: '2012', + brand: 'visa', + number: '4030000010001234', + payment_cryptogram: 'cryptogram goes here', + eci: 'an ECI value', + transaction_id: 'transaction ID' + ) + @check = check( :institution_number => '001', :transit_number => '26729' @@ -45,13 +58,36 @@ def setup end def test_successful_purchase - @gateway.expects(:ssl_post).returns(successful_purchase_response) + response = stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, @decrypted_credit_card, @options) + end.check_request do |method, endpoint, data, headers| + refute_match(/recurringPayment=true/, data) + end.respond_with(successful_purchase_response) - assert response = @gateway.purchase(@amount, @credit_card, @options) assert_success response assert_equal '10000028;15.00;P', response.authorization end + def test_successful_purchase_with_recurring + response = stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, @decrypted_credit_card, @options.merge(recurring: true)) + end.check_request do |method, endpoint, data, headers| + assert_match(/recurringPayment=1/, data) + end.respond_with(successful_purchase_response) + + assert_success response + end + + def test_successful_authorize_with_recurring + response = stub_comms(@gateway, :ssl_request) do + @gateway.authorize(@amount, @decrypted_credit_card, @options.merge(recurring: true)) + end.check_request do |method, endpoint, data, headers| + assert_match(/recurringPayment=1/, data) + end.respond_with(successful_purchase_response) + + assert_success response + end + def test_successful_test_request_in_production_environment Base.mode = :production @gateway.expects(:ssl_post).returns(successful_test_purchase_response) @@ -103,13 +139,28 @@ def test_successful_purchase_with_check def test_successful_purchase_with_vault @gateway.expects(:ssl_post).returns(successful_purchase_response) - vault = rand(100000)+10001 + vault = rand(10001..110000) assert response = @gateway.purchase(@amount, vault, @options) assert_success response assert_equal '10000028;15.00;P', response.authorization end + def test_successful_verify + response = stub_comms do + @gateway.verify(@credit_card) + end.respond_with(successful_authorize_response, unsuccessful_void_response) + assert_success response + assert_equal 'Approved', response.message + end + + def test_failed_verify + response = stub_comms do + @gateway.verify(@credit_card) + end.respond_with(unsuccessful_authorize_response, successful_void_response) + assert_failure response + assert_equal 'DECLINE', response.message + end # Testing Non-American countries @@ -140,7 +191,9 @@ def test_brazilian_address_sets_state_and_zip_to_the_required_dummy_values def test_successful_recurring @gateway.expects(:ssl_post).returns(successful_recurring_response) - assert response = @gateway.recurring(@amount, @credit_card, @recurring_options) + response = assert_deprecation_warning(Gateway::RECURRING_DEPRECATION_MESSAGE) do + @gateway.recurring(@amount, @credit_card, @recurring_options) + end assert_success response assert_equal 'Approved', response.message end @@ -148,56 +201,142 @@ def test_successful_recurring def test_successful_update_recurring @gateway.expects(:ssl_post).returns(successful_recurring_response) - assert response = @gateway.recurring(@amount, @credit_card, @recurring_options) + response = assert_deprecation_warning(Gateway::RECURRING_DEPRECATION_MESSAGE) do + @gateway.recurring(@amount, @credit_card, @recurring_options) + end assert_success response assert_equal 'Approved', response.message @gateway.expects(:ssl_post).returns(successful_update_recurring_response) - assert response = @gateway.update_recurring(@amount, @credit_card, @recurring_options.merge(:account_id => response.params["rbAccountId"])) + response = assert_deprecation_warning(Gateway::RECURRING_DEPRECATION_MESSAGE) do + @gateway.update_recurring(@amount, @credit_card, @recurring_options.merge(:account_id => response.params['rbAccountId'])) + end assert_success response - assert_equal "Request successful", response.message + assert_equal 'Request successful', response.message end def test_successful_cancel_recurring @gateway.expects(:ssl_post).returns(successful_recurring_response) - assert response = @gateway.recurring(@amount, @credit_card, @recurring_options) + response = assert_deprecation_warning(Gateway::RECURRING_DEPRECATION_MESSAGE) do + @gateway.recurring(@amount, @credit_card, @recurring_options) + end assert_success response assert_equal 'Approved', response.message @gateway.expects(:ssl_post).returns(successful_cancel_recurring_response) - assert response = @gateway.cancel_recurring(:account_id => response.params["rbAccountId"]) + response = assert_deprecation_warning(Gateway::RECURRING_DEPRECATION_MESSAGE) do + @gateway.cancel_recurring(:account_id => response.params['rbAccountId']) + end assert_success response - assert_equal "Request successful", response.message + assert_equal 'Request successful', response.message end def test_ip_is_being_sent @gateway.expects(:ssl_post).with do |url, data| - data =~ /customerIP=123\.123\.123\.123/ + data =~ /customerIp=123\.123\.123\.123/ end.returns(successful_purchase_response) - @options[:ip] = "123.123.123.123" - assert response = @gateway.purchase(@amount, @credit_card, @options) + @options[:ip] = '123.123.123.123' + @gateway.purchase(@amount, @credit_card, @options) + end + + def test_includes_network_tokenization_fields + response = stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, @decrypted_credit_card, @options) + end.check_request do |method, endpoint, data, headers| + assert_match(/3DSecureXID/, data) + assert_match(/3DSecureECI/, data) + assert_match(/3DSecureCAVV/, data) + end.respond_with(successful_purchase_response) + + assert_success response + end + + def test_defaults_state_and_zip_with_country + address = { country: 'AF' } + @options[:billing_address] = address + @options[:shipping_address] = address + response = stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, @decrypted_credit_card, @options) + end.check_request do |method, endpoint, data, headers| + assert_match(/ordProvince=--/, data) + assert_match(/ordPostalCode=000000/, data) + assert_match(/shipProvince=--/, data) + assert_match(/shipPostalCode=000000/, data) + end.respond_with(successful_purchase_response) + + assert_success response + end + + def test_no_state_and_zip_default_with_missing_country + address = { } + @options[:billing_address] = address + @options[:shipping_address] = address + response = stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, @decrypted_credit_card, @options) + end.check_request do |method, endpoint, data, headers| + assert_no_match(/ordProvince=--/, data) + assert_no_match(/ordPostalCode=000000/, data) + assert_no_match(/shipProvince=--/, data) + assert_no_match(/shipPostalCode=000000/, data) + end.respond_with(successful_purchase_response) + + assert_success response + end + + def test_sends_email_without_addresses + @options[:billing_address] = nil + @options[:shipping_address] = nil + @options[:shipping_email] = 'ship@mail.com' + response = stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, @decrypted_credit_card, @options) + end.check_request do |method, endpoint, data, headers| + assert_match(/ordEmailAddress=xiaobozzz%40example.com/, data) + assert_match(/shipEmailAddress=ship%40mail.com/, data) + end.respond_with(successful_purchase_response) + + assert_success response + end + + def test_transcript_scrubbing + assert_equal scrubbed_transcript, @gateway.scrub(transcript) end private def successful_purchase_response - "cvdId=1&trnType=P&trnApproved=1&trnId=10000028&messageId=1&messageText=Approved&trnOrderNumber=df5e88232a61dc1d0058a20d5b5c0e&authCode=TEST&errorType=N&errorFields=&responseType=T&trnAmount=15%2E00&trnDate=6%2F5%2F2008+5%3A26%3A53+AM&avsProcessed=0&avsId=0&avsResult=0&avsAddrMatch=0&avsPostalMatch=0&avsMessage=Address+Verification+not+performed+f" + 'cvdId=1&trnType=P&trnApproved=1&trnId=10000028&messageId=1&messageText=Approved&trnOrderNumber=df5e88232a61dc1d0058a20d5b5c0e&authCode=TEST&errorType=N&errorFields=&responseType=T&trnAmount=15%2E00&trnDate=6%2F5%2F2008+5%3A26%3A53+AM&avsProcessed=0&avsId=0&avsResult=0&avsAddrMatch=0&avsPostalMatch=0&avsMessage=Address+Verification+not+performed+f' end def successful_test_purchase_response - "merchant_id=100200000&trnId=11011067&authCode=TEST&trnApproved=1&avsId=M&cvdId=1&messageId=1&messageText=Approved&trnOrderNumber=1234" + 'merchant_id=100200000&trnId=11011067&authCode=TEST&trnApproved=1&avsId=M&cvdId=1&messageId=1&messageText=Approved&trnOrderNumber=1234' end def unsuccessful_purchase_response - "merchant_id=100200000&trnId=11011069&authCode=&trnApproved=0&avsId=0&cvdId=6&messageId=16&messageText=Duplicate+transaction&trnOrderNumber=1234" + 'merchant_id=100200000&trnId=11011069&authCode=&trnApproved=0&avsId=0&cvdId=6&messageId=16&messageText=Duplicate+transaction&trnOrderNumber=1234' end def successful_check_purchase_response - "trnApproved=1&trnId=10000072&messageId=1&messageText=Approved&trnOrderNumber=5d9f511363a0f35d37de53b4d74f5b&authCode=&errorType=N&errorFields=&responseType=T&trnAmount=15%2E00&trnDate=6%2F4%2F2008+6%3A33%3A55+PM&avsProcessed=0&avsId=0&avsResult=0&avsAddrMatch=0&avsPostalMatch=0&avsMessage=Address+Verification+not+performed+for+this+transaction%2E&trnType=D&paymentMethod=EFT&ref1=reference+one&ref2=&ref3=&ref4=&ref5=" + 'trnApproved=1&trnId=10000072&messageId=1&messageText=Approved&trnOrderNumber=5d9f511363a0f35d37de53b4d74f5b&authCode=&errorType=N&errorFields=&responseType=T&trnAmount=15%2E00&trnDate=6%2F4%2F2008+6%3A33%3A55+PM&avsProcessed=0&avsId=0&avsResult=0&avsAddrMatch=0&avsPostalMatch=0&avsMessage=Address+Verification+not+performed+for+this+transaction%2E&trnType=D&paymentMethod=EFT&ref1=reference+one&ref2=&ref3=&ref4=&ref5=' + end + + def successful_authorize_response + 'trnApproved=1&trnId=10100560&messageId=1&messageText=Approved&trnOrderNumber=0b936c13208677aaa00be509c541e7&authCode=TEST&errorType=N&errorFields=&responseType=T&trnAmount=15%2E00&trnDate=9%2F9%2F2015+10%3A10%3A28+AM&avsProcessed=1&avsId=N&avsResult=0&avsAddrMatch=0&avsPostalMatch=0&avsMessage=Street+address+and+Postal%2FZIP+do+not+match%2E&cvdId=1&cardType=VI&trnType=PA&paymentMethod=CC&ref1=reference+one&ref2=&ref3=&ref4=&ref5=' + end + + def unsuccessful_authorize_response + 'trnApproved=0&trnId=10100561&messageId=7&messageText=DECLINE&trnOrderNumber=7eef9f25c6f123572f193bee4a1aa0&authCode=&errorType=N&errorFields=&responseType=T&trnAmount=15%2E00&trnDate=9%2F9%2F2015+10%3A11%3A22+AM&avsProcessed=1&avsId=N&avsResult=0&avsAddrMatch=0&avsPostalMatch=0&avsMessage=Street+address+and+Postal%2FZIP+do+not+match%2E&cvdId=2&cardType=AM&trnType=PA&paymentMethod=CC&ref1=reference+one&ref2=&ref3=&ref4=&ref5=' + end + + def successful_void_response + 'trnApproved=1&trnId=10100563&messageId=1&messageText=Approved&trnOrderNumber=6ca476d1a29da81a5f2d5d2c92ddeb&authCode=TEST&errorType=N&errorFields=&responseType=T&trnAmount=15%2E00&trnDate=9%2F9%2F2015+10%3A13%3A12+AM&avsProcessed=0&avsId=U&avsResult=0&avsAddrMatch=0&avsPostalMatch=0&avsMessage=Address+information+is+unavailable%2E&cvdId=2&cardType=VI&trnType=VP&paymentMethod=CC&ref1=reference+one&ref2=&ref3=&ref4=&ref5=' + end + + def unsuccessful_void_response + 'trnApproved=0&trnId=0&messageId=0&messageText=%3CLI%3EAdjustment+id+must+be+less+than+8+characters%3Cbr%3E&trnOrderNumber=&authCode=&errorType=U&errorFields=adjId&responseType=T&trnAmount=&trnDate=9%2F9%2F2015+10%3A15%3A20+AM&avsProcessed=0&avsId=0&avsResult=0&avsAddrMatch=0&avsPostalMatch=0&avsMessage=Address+Verification+not+performed+for+this+transaction%2E&cardType=&trnType=VP&paymentMethod=CC&ref1=&ref2=&ref3=&ref4=&ref5=' end def brazilian_address_params_without_zip_and_state @@ -213,16 +352,23 @@ def next_year end def successful_recurring_response - "trnApproved=1&trnId=10000072&messageId=1&messageText=Approved&trnOrderNumber=5d9f511363a0f35d37de53b4d74f5b&authCode=&errorType=N&errorFields=&responseType=T&trnAmount=15%2E00&trnDate=6%2F4%2F2008+6%3A33%3A55+PM&avsProcessed=0&avsId=0&avsResult=0&avsAddrMatch=0&avsPostalMatch=0&avsMessage=Address+Verification+not+performed+for+this+transaction%2E&trnType=D&paymentMethod=EFT&ref1=reference+one&ref2=&ref3=&ref4=&ref5=" + 'trnApproved=1&trnId=10000072&messageId=1&messageText=Approved&trnOrderNumber=5d9f511363a0f35d37de53b4d74f5b&authCode=&errorType=N&errorFields=&responseType=T&trnAmount=15%2E00&trnDate=6%2F4%2F2008+6%3A33%3A55+PM&avsProcessed=0&avsId=0&avsResult=0&avsAddrMatch=0&avsPostalMatch=0&avsMessage=Address+Verification+not+performed+for+this+transaction%2E&trnType=D&paymentMethod=EFT&ref1=reference+one&ref2=&ref3=&ref4=&ref5=' end def successful_update_recurring_response - "<response><code>1</code><message>Request successful</message></response>" + '<response><code>1</code><message>Request successful</message></response>' end def successful_cancel_recurring_response - "<response><code>1</code><message>Request successful</message></response>" + '<response><code>1</code><message>Request successful</message></response>' end -end + def transcript + 'ref1=reference+one&trnCardOwner=Longbob+Longsen&trnCardNumber=4030000010001234&trnExpMonth=09&trnExpYear=16&trnCardCvd=123&ordName=xiaobo+zzz&ordEmailAddress=xiaobozzz%40example.com&username=awesomesauce&password=sp00nz%21%21' + end + def scrubbed_transcript + 'ref1=reference+one&trnCardOwner=Longbob+Longsen&trnCardNumber=[FILTERED]&trnExpMonth=09&trnExpYear=16&trnCardCvd=[FILTERED]&ordName=xiaobo+zzz&ordEmailAddress=xiaobozzz%40example.com&username=awesomesauce&password=[FILTERED]' + end + +end diff --git a/test/unit/gateways/blue_pay_test.rb b/test/unit/gateways/blue_pay_test.rb index 57075fe66c9..bf07a3eac76 100644 --- a/test/unit/gateways/blue_pay_test.rb +++ b/test/unit/gateways/blue_pay_test.rb @@ -1,302 +1,334 @@ -require 'test_helper' - -RSP = { - :approved_auth => "AUTH_CODE=XCADZ&PAYMENT_ACCOUNT_MASK=xxxxxxxxxxxx4242&CARD_TYPE=VISA&TRANS_TYPE=AUTH&REBID=&STATUS=1&AVS=_&TRANS_ID=100134203758&CVV2=_&MESSAGE=Approved%20Auth", - :approved_capture => "AUTH_CODE=CHTHX&PAYMENT_ACCOUNT_MASK=xxxxxxxxxxxx4242&CARD_TYPE=VISA&TRANS_TYPE=CAPTURE&REBID=&STATUS=1&AVS=_&TRANS_ID=100134203760&CVV2=_&MESSAGE=Approved%20Capture", - :approved_void => "AUTH_CODE=KTMHB&PAYMENT_ACCOUNT_MASK=xxxxxxxxxxxx4242&CARD_TYPE=VISA&TRANS_TYPE=VOID&REBID=&STATUS=1&AVS=_&TRANS_ID=100134203763&CVV2=_&MESSAGE=Approved%20Void", - :declined => "TRANS_ID=100000000150&STATUS=0&AVS=0&CVV2=7&MESSAGE=Declined&REBID=", - :approved_purchase => "AUTH_CODE=GYRUY&PAYMENT_ACCOUNT_MASK=xxxxxxxxxxxx4242&CARD_TYPE=VISA&TRANS_TYPE=SALE&REBID=&STATUS=1&AVS=_&TRANS_ID=100134203767&CVV2=_&MESSAGE=Approved%20Sale" -} - - -class BluePayTest < Test::Unit::TestCase - include CommStub - - def setup - @gateway = BluePayGateway.new( - :login => 'X', - :password => 'Y' - ) - @amount = 100 - @credit_card = credit_card - @rebill_id = '100096219669' - @rebill_status = 'active' - end - - def test_successful_authorization - #@gateway.expects(:ssl_post).returns(successful_authorization_response) - @gateway.expects(:ssl_post).returns(RSP[:approved_auth]) - assert response = @gateway.authorize(@amount, @credit_card) - assert_instance_of Response, response - assert_success response - assert_equal '100134203758', response.authorization - end - - def test_successful_purchase - @gateway.expects(:ssl_post).returns(RSP[:approved_purchase]) - - assert response = @gateway.purchase(@amount, @credit_card) - assert_instance_of Response, response - assert_success response - assert_equal '100134203767', response.authorization - end - - def test_failed_authorization - @gateway.expects(:ssl_post).returns(RSP[:declined]) - - assert response = @gateway.authorize(@amount, @credit_card) - assert_instance_of Response, response - assert_failure response - assert_equal '100000000150', response.authorization - end - - def test_add_address_outsite_north_america - result = {} - - @gateway.send(:add_address, result, :billing_address => {:address1 => '123 Test St.', :address2 => '5F', :city => 'Testville', :company => 'Test Company', :country => 'DE', :state => ''} ) - assert_equal ["ADDR1", "ADDR2", "CITY", "COMPANY_NAME", "COUNTRY", "PHONE", "STATE", "ZIP"], result.stringify_keys.keys.sort - assert_equal 'n/a', result[:STATE] - assert_equal '123 Test St.', result[:ADDR1] - assert_equal 'DE', result[:COUNTRY] - end - - def test_add_address - result = {} - - @gateway.send(:add_address, result, :billing_address => {:address1 => '123 Test St.', :address2 => '5F', :city => 'Testville', :company => 'Test Company', :country => 'US', :state => 'AK'} ) - - assert_equal ["ADDR1", "ADDR2", "CITY", "COMPANY_NAME", "COUNTRY", "PHONE", "STATE", "ZIP"], result.stringify_keys.keys.sort - assert_equal 'AK', result[:STATE] - assert_equal '123 Test St.', result[:ADDR1] - assert_equal 'US', result[:COUNTRY] - - end - - def test_name_comes_from_payment_method - result = {} - - @gateway.send(:add_creditcard, result, @credit_card) - @gateway.send(:add_address, result, :billing_address => {:address1 => '123 Test St.', :address2 => '5F', :city => 'Testville', :company => 'Test Company', :country => 'US', :state => 'AK'} ) - - assert_equal @credit_card.first_name, result[:NAME1] - assert_equal @credit_card.last_name, result[:NAME2] - end - - def test_add_invoice - result = {} - @gateway.send(:add_invoice, result, :order_id => '#1001') - assert_equal '#1001', result[:invoice_num] - end - - def test_add_description - result = {} - @gateway.send(:add_invoice, result, :description => 'My Purchase is great') - assert_equal 'My Purchase is great', result[:description] - end - - def test_purchase_meets_minimum_requirements - params = { - :amount => "1.01", - } - - @gateway.send(:add_creditcard, params, @credit_card) - - assert data = @gateway.send(:post_data, 'AUTH_ONLY', params) - minimum_requirements.each do |key| - assert_not_nil(data =~ /#{key}=/) - end - end - - def test_successful_refund - @gateway.expects(:ssl_post).returns(successful_refund_response) - assert response = @gateway.refund(@amount, '100134230412', :card_number => @credit_card.number) - assert_success response - assert_equal 'This transaction has been approved', response.message - end - - def test_refund_passing_extra_info - response = stub_comms do - @gateway.refund(50, '123456789', :card_number => @credit_card.number, :first_name => "Bob", :last_name => "Smith", :zip => "12345") - end.check_request do |endpoint, data, headers| - assert_match(/NAME1=Bob/, data) - assert_match(/NAME2=Smith/, data) - assert_match(/ZIP=12345/, data) - end.respond_with(successful_purchase_response) - assert_success response - end - - def test_failed_refund - @gateway.expects(:ssl_post).returns(failed_refund_response) - assert response = @gateway.refund(@amount, '123456789', :card_number => @credit_card.number) - assert_failure response - assert_equal 'The referenced transaction does not meet the criteria for issuing a credit', response.message - end - - def test_deprecated_credit - @gateway.expects(:ssl_post).returns(successful_purchase_response) - assert_deprecation_warning("credit should only be used to credit a payment method", @gateway) do - assert response = @gateway.credit(@amount, '123456789', :card_number => @credit_card.number) - assert_success response - assert_equal 'This transaction has been approved', response.message - end - end - - def test_supported_countries - assert_equal ['US'], BluePayGateway.supported_countries - end - - def test_supported_card_types - assert_equal [:visa, :master, :american_express, :discover, :diners_club, :jcb], BluePayGateway.supported_cardtypes - end - - def test_parser_extracts_exactly_the_keys_in_gateway_response - assert_nothing_raised do - response = @gateway.send(:parse, "NEW_IMPORTANT_FIELD=value_on_fire") - assert_equal response.params.keys, ['NEW_IMPORTANT_FIELD'] - assert_equal response.params['NEW_IMPORTANT_FIELD'], "value_on_fire" - end - end - - def test_failure_without_response_reason_text - assert_nothing_raised do - assert_equal '', @gateway.send(:parse, "MESSAGE=").message - end - end - - def test_response_under_review_by_fraud_service - @gateway.expects(:ssl_post).returns(fraud_review_response) - - response = @gateway.purchase(@amount, @credit_card) - assert_failure response - assert response.fraud_review? - assert_equal "Thank you! For security reasons your order is currently being reviewed", response.message - end - - def test_avs_result - @gateway.expects(:ssl_post).returns(fraud_review_response) - - response = @gateway.purchase(@amount, @credit_card) - assert_equal 'X', response.avs_result['code'] - end - - def test_cvv_result - @gateway.expects(:ssl_post).returns(fraud_review_response) - - response = @gateway.purchase(@amount, @credit_card) - assert_equal 'M', response.cvv_result['code'] - end - - def test_message_from - - def get_msg(query) - @gateway.send(:parse, query).message - end - assert_equal "No Match", get_msg('STATUS=2&CVV2=N&AVS=A&MESSAGE=FAILURE') - assert_equal "Street address matches, but 5-digit and 9-digit postal code do not match.", - get_msg('STATUS=2&CVV2=M&AVS=A&MESSAGE=FAILURE') - end - - # Recurring Billing Unit Tests - def test_successful_recurring - @gateway.expects(:ssl_post).returns(successful_recurring_response) - - response = @gateway.recurring(@amount, @credit_card, - :billing_address => address.merge(:first_name => 'Jim', :last_name => 'Smith'), - :rebill_start_date => '1 MONTH', - :rebill_expression => '14 DAYS', - :rebill_cycles => '24', - :rebill_amount => @amount * 4 - ) - - assert_instance_of Response, response - assert response.success? - assert response.test? - assert_equal @rebill_id, response.authorization - end - - def test_successful_update_recurring - @gateway.expects(:ssl_post).returns(successful_update_recurring_response) - - response = @gateway.update_recurring(:rebill_id => @rebill_id, :rebill_amount => @amount * 2) - - assert_instance_of Response, response - assert response.success? - assert response.test? - assert_equal @rebill_id, response.authorization - end - - def test_successful_cancel_recurring - @gateway.expects(:ssl_post).returns(successful_cancel_recurring_response) - - response = @gateway.cancel_recurring(@rebill_id) - - assert_instance_of Response, response - assert response.success? - assert response.test? - assert_equal @rebill_id, response.authorization - end - - def test_successful_status_recurring - @gateway.expects(:ssl_post).returns(successful_status_recurring_response) - - response = @gateway.status_recurring(@rebill_id) - assert_instance_of Response, response - assert response.success? - assert response.test? - assert_equal @rebill_status, response.params['status'] - end - - def test_solution_id_is_added_to_post_data_parameters - assert !@gateway.send(:post_data, 'AUTH_ONLY').include?("solution_ID=A1000000") - ActiveMerchant::Billing::BluePayGateway.application_id = 'A1000000' - assert @gateway.send(:post_data, 'AUTH_ONLY').include?("solution_ID=A1000000") - ensure - ActiveMerchant::Billing::BluePayGateway.application_id = nil - end - - private - - def minimum_requirements - %w(version delim_data relay_response login tran_key amount card_num exp_date type) - end - - def successful_refund_response - "AUTH_CODE=GFOCD&PAYMENT_ACCOUNT_MASK=xxxxxxxxxxxx4242&CARD_TYPE=VISA&TRANS_TYPE=CREDIT&REBID=&STATUS=1&AVS=_&TRANS_ID=100134230412&CVV2=_&MESSAGE=Approved%20Credit" - end - - def failed_refund_response - 'AUTH_CODE=&PAYMENT_ACCOUNT_MASK=xxxxxxxxxxxx4242&CARD_TYPE=VISA&TRANS_TYPE=CREDIT&REBID=&STATUS=0&AVS=_&TRANS_ID=100134229326&CVV2=_&MESSAGE=The%20referenced%20transaction%20does%20not%20meet%20the%20criteria%20for%20issuing%20a%20credit' - end - - def successful_authorization_response - "AUTH_CODE=RSWUC&PAYMENT_ACCOUNT_MASK=xxxxxxxxxxxx4242&CARD_TYPE=VISA&TRANS_TYPE=AUTH&REBID=&STATUS=1&AVS=_&TRANS_ID=100134229528&CVV2=_&MESSAGE=Approved%20Auth" - end - - def successful_purchase_response - "AUTH_CODE=KHJMY&PAYMENT_ACCOUNT_MASK=xxxxxxxxxxxx4242&CARD_TYPE=VISA&TRANS_TYPE=SALE&REBID=&STATUS=1&AVS=_&TRANS_ID=100134229668&CVV2=_&MESSAGE=Approved%20Sale" - end - - def failed_authorization_response - "AUTH_CODE=&PAYMENT_ACCOUNT_MASK=xxxxxxxxxxxx4242&CARD_TYPE=VISA&TRANS_TYPE=AUTH&REBID=&STATUS=0&AVS=_&TRANS_ID=100134229728&CVV2=_&MESSAGE=Declined%20Auth" - end - - def fraud_review_response - "AUTH_CODE=&PAYMENT_ACCOUNT_MASK=xxxxxxxxxxxx4242&CARD_TYPE=VISA&TRANS_TYPE=AUTH&REBID=&STATUS=0&AVS=X&TRANS_ID=100134229728&CVV2=M&MESSAGE=Thank%20you!%20For%20security%20reasons%20your%20order%20is%20currently%20being%20reviewed" - end - - def successful_recurring_response - 'last_date=2012-04-13%2009%3A49%3A27&usual_date=2012-04-13%2000%3A00%3A00&template_id=100096219668&status=active&account_id=100096218902&rebill_id=100096219669&reb_amount=2.00&creation_date=2012-04-13%2009%3A49%3A19&sched_expr=1%20DAY&next_date=2012-04-13%2000%3A00%3A00&next_amount=&user_id=100096218903&cycles_remain=4' - end - - def successful_update_recurring_response - 'last_date=2012-04-13%2009%3A49%3A27&usual_date=2012-04-13%2000%3A00%3A00&template_id=100096219668&status=active&account_id=100096218902&rebill_id=100096219669&reb_amount=2.00&creation_date=2012-04-13%2009%3A49%3A19&sched_expr=1%20DAY&next_date=2012-04-13%2000%3A00%3A00&next_amount=&user_id=100096218903&cycles_remain=4' - end - - def successful_cancel_recurring_response - 'last_date=2012-04-13%2009%3A49%3A27&usual_date=2012-04-13%2000%3A00%3A00&template_id=100096219668&status=stopped&account_id=100096218902&rebill_id=100096219669&reb_amount=2.00&creation_date=2012-04-13%2009%3A49%3A19&sched_expr=1%20DAY&next_date=2012-04-13%2000%3A00%3A00&next_amount=&user_id=100096218903&cycles_remain=4' - end - - def successful_status_recurring_response - 'last_date=2012-04-13%2009%3A49%3A27&usual_date=2012-04-13%2000%3A00%3A00&template_id=100096219668&status=active&account_id=100096218902&rebill_id=100096219669&reb_amount=2.00&creation_date=2012-04-13%2009%3A49%3A19&sched_expr=1%20DAY&next_date=2012-04-13%2000%3A00%3A00&next_amount=&user_id=100096218903&cycles_remain=4' - end -end +require 'test_helper' + +RSP = { + :approved_auth => 'AUTH_CODE=XCADZ&PAYMENT_ACCOUNT_MASK=xxxxxxxxxxxx4242&CARD_TYPE=VISA&TRANS_TYPE=AUTH&REBID=&STATUS=1&AVS=_&TRANS_ID=100134203758&CVV2=_&MESSAGE=Approved%20Auth', + :approved_capture => 'AUTH_CODE=CHTHX&PAYMENT_ACCOUNT_MASK=xxxxxxxxxxxx4242&CARD_TYPE=VISA&TRANS_TYPE=CAPTURE&REBID=&STATUS=1&AVS=_&TRANS_ID=100134203760&CVV2=_&MESSAGE=Approved%20Capture', + :approved_void => 'AUTH_CODE=KTMHB&PAYMENT_ACCOUNT_MASK=xxxxxxxxxxxx4242&CARD_TYPE=VISA&TRANS_TYPE=VOID&REBID=&STATUS=1&AVS=_&TRANS_ID=100134203763&CVV2=_&MESSAGE=Approved%20Void', + :declined => 'TRANS_ID=100000000150&STATUS=0&AVS=0&CVV2=7&MESSAGE=Declined&REBID=', + :approved_purchase => 'AUTH_CODE=GYRUY&PAYMENT_ACCOUNT_MASK=xxxxxxxxxxxx4242&CARD_TYPE=VISA&TRANS_TYPE=SALE&REBID=&STATUS=1&AVS=_&TRANS_ID=100134203767&CVV2=_&MESSAGE=Approved%20Sale' +} + +class BluePayTest < Test::Unit::TestCase + include CommStub + + def setup + @gateway = BluePayGateway.new( + :login => 'X', + :password => 'Y' + ) + @amount = 100 + @credit_card = credit_card + @rebill_id = '100096219669' + @rebill_status = 'active' + @options = {ip: '192.168.0.1'} + end + + def test_successful_authorization + response = stub_comms do + @gateway.authorize(@amount, @credit_card, @options) + end.check_request do |endpoint, data, headers| + assert_match(/CUSTOMER_IP=192.168.0.1/, data) + end.respond_with(RSP[:approved_auth]) + + assert response + assert_instance_of Response, response + assert_success response + assert_equal '100134203758', response.authorization + end + + def test_successful_purchase + response = stub_comms do + @gateway.purchase(@amount, @credit_card, @options) + end.check_request do |endpoint, data, headers| + assert_match(/CUSTOMER_IP=192.168.0.1/, data) + end.respond_with(RSP[:approved_purchase]) + + assert response + assert_instance_of Response, response + assert_success response + assert_equal '100134203767', response.authorization + end + + def test_failed_authorization + response = stub_comms do + @gateway.authorize(@amount, @credit_card, @options) + end.check_request do |endpoint, data, headers| + assert_match(/CUSTOMER_IP=192.168.0.1/, data) + end.respond_with(RSP[:declined]) + + assert response + assert_instance_of Response, response + assert_failure response + assert_equal '100000000150', response.authorization + end + + def test_add_address_outsite_north_america + result = {} + + @gateway.send(:add_address, result, :billing_address => {:address1 => '123 Test St.', :address2 => '5F', :city => 'Testville', :company => 'Test Company', :country => 'DE', :state => ''}) + assert_equal ['ADDR1', 'ADDR2', 'CITY', 'COMPANY_NAME', 'COUNTRY', 'PHONE', 'STATE', 'ZIP'], result.stringify_keys.keys.sort + assert_equal 'n/a', result[:STATE] + assert_equal '123 Test St.', result[:ADDR1] + assert_equal 'DE', result[:COUNTRY] + end + + def test_add_address + result = {} + + @gateway.send(:add_address, result, :billing_address => {:address1 => '123 Test St.', :address2 => '5F', :city => 'Testville', :company => 'Test Company', :country => 'US', :state => 'AK'}) + + assert_equal ['ADDR1', 'ADDR2', 'CITY', 'COMPANY_NAME', 'COUNTRY', 'PHONE', 'STATE', 'ZIP'], result.stringify_keys.keys.sort + assert_equal 'AK', result[:STATE] + assert_equal '123 Test St.', result[:ADDR1] + assert_equal 'US', result[:COUNTRY] + end + + def test_name_comes_from_payment_method + result = {} + + @gateway.send(:add_creditcard, result, @credit_card) + @gateway.send(:add_address, result, :billing_address => {:address1 => '123 Test St.', :address2 => '5F', :city => 'Testville', :company => 'Test Company', :country => 'US', :state => 'AK'}) + + assert_equal @credit_card.first_name, result[:NAME1] + assert_equal @credit_card.last_name, result[:NAME2] + end + + def test_add_invoice + result = {} + @gateway.send(:add_invoice, result, :order_id => '#1001') + assert_equal '#1001', result[:invoice_num] + end + + def test_add_description + result = {} + @gateway.send(:add_invoice, result, :description => 'My Purchase is great') + assert_equal 'My Purchase is great', result[:description] + end + + def test_purchase_meets_minimum_requirements + params = { + :amount => '1.01', + } + + @gateway.send(:add_creditcard, params, @credit_card) + + assert data = @gateway.send(:post_data, 'AUTH_ONLY', params) + minimum_requirements.each do |key| + assert_not_nil(data =~ /#{key}=/) + end + end + + def test_successful_refund + response = stub_comms do + @gateway.refund(@amount, '100134230412', @options.merge({:card_number => @credit_card.number})) + end.check_request do |endpoint, data, headers| + assert_match(/CUSTOMER_IP=192\.168\.0\.1/, data) + end.respond_with(successful_refund_response) + + assert response + assert_success response + assert_equal 'This transaction has been approved', response.message + end + + def test_refund_passing_extra_info + response = stub_comms do + @gateway.refund(50, '123456789', @options.merge({:card_number => @credit_card.number, :first_name => 'Bob', :last_name => 'Smith', :zip => '12345'})) + end.check_request do |endpoint, data, headers| + assert_match(/NAME1=Bob/, data) + assert_match(/NAME2=Smith/, data) + assert_match(/ZIP=12345/, data) + assert_match(/CUSTOMER_IP=192\.168\.0\.1/, data) + end.respond_with(successful_purchase_response) + + assert_success response + end + + def test_failed_refund + response = stub_comms do + @gateway.refund(@amount, '123456789', @options.merge({:card_number => @credit_card.number})) + end.check_request do |endpoint, data, headers| + assert_match(/CUSTOMER_IP=192\.168\.0\.1/, data) + end.respond_with(failed_refund_response) + + assert response + assert_failure response + assert_equal 'The referenced transaction does not meet the criteria for issuing a credit', response.message + end + + def test_deprecated_credit + @gateway.expects(:ssl_post).returns(successful_purchase_response) + assert_deprecation_warning('credit should only be used to credit a payment method') do + response = stub_comms do + @gateway.credit(@amount, '123456789', @options.merge({:card_number => @credit_card.number})) + end.check_request do |endpoint, data, headers| + assert_match(/CUSTOMER_IP=192\.168\.0\.1/, data) + end.respond_with(failed_refund_response) + + assert response + assert_success response + assert_equal 'This transaction has been approved', response.message + end + end + + def test_supported_countries + assert_equal ['US', 'CA'], BluePayGateway.supported_countries + end + + def test_supported_card_types + assert_equal [:visa, :master, :american_express, :discover, :diners_club, :jcb], BluePayGateway.supported_cardtypes + end + + def test_parser_extracts_exactly_the_keys_in_gateway_response + assert_nothing_raised do + response = @gateway.send(:parse, 'NEW_IMPORTANT_FIELD=value_on_fire') + assert_equal response.params.keys, ['NEW_IMPORTANT_FIELD'] + assert_equal response.params['NEW_IMPORTANT_FIELD'], 'value_on_fire' + end + end + + def test_failure_without_response_reason_text + assert_nothing_raised do + assert_equal '', @gateway.send(:parse, 'MESSAGE=').message + end + end + + def test_avs_result + @gateway.expects(:ssl_post).returns(successful_authorization_response) + + response = @gateway.purchase(@amount, @credit_card) + assert_equal '_', response.avs_result['code'] + end + + def test_cvv_result + @gateway.expects(:ssl_post).returns(successful_authorization_response) + + response = @gateway.purchase(@amount, @credit_card) + assert_equal '_', response.cvv_result['code'] + end + + def test_message_from + assert_equal 'CVV does not match', @gateway.send(:parse, 'STATUS=2&CVV2=N&AVS=A&MESSAGE=FAILURE').message + assert_equal 'Street address matches, but postal code does not match.', + @gateway.send(:parse, 'STATUS=2&CVV2=M&AVS=A&MESSAGE=FAILURE').message + end + + # Recurring Billing Unit Tests + def test_successful_recurring + @gateway.expects(:ssl_post).returns(successful_recurring_response) + + response = assert_deprecation_warning(Gateway::RECURRING_DEPRECATION_MESSAGE) do + @gateway.recurring(@amount, @credit_card, + :billing_address => address.merge(:first_name => 'Jim', :last_name => 'Smith'), + :rebill_start_date => '1 MONTH', + :rebill_expression => '14 DAYS', + :rebill_cycles => '24', + :rebill_amount => @amount * 4 + ) + end + + assert_instance_of Response, response + assert response.success? + assert response.test? + assert_equal @rebill_id, response.authorization + end + + def test_successful_update_recurring + @gateway.expects(:ssl_post).returns(successful_update_recurring_response) + + response = assert_deprecation_warning(Gateway::RECURRING_DEPRECATION_MESSAGE) do + @gateway.update_recurring(:rebill_id => @rebill_id, :rebill_amount => @amount * 2) + end + + assert_instance_of Response, response + assert response.success? + assert response.test? + assert_equal @rebill_id, response.authorization + end + + def test_successful_cancel_recurring + @gateway.expects(:ssl_post).returns(successful_cancel_recurring_response) + + response = assert_deprecation_warning(Gateway::RECURRING_DEPRECATION_MESSAGE) do + @gateway.cancel_recurring(@rebill_id) + end + + assert_instance_of Response, response + assert response.success? + assert response.test? + assert_equal @rebill_id, response.authorization + end + + def test_successful_status_recurring + @gateway.expects(:ssl_post).returns(successful_status_recurring_response) + + response = assert_deprecation_warning(Gateway::RECURRING_DEPRECATION_MESSAGE) do + @gateway.status_recurring(@rebill_id) + end + assert_instance_of Response, response + assert response.success? + assert response.test? + assert_equal @rebill_status, response.params['status'] + end + + def test_solution_id_is_added_to_post_data_parameters + assert !@gateway.send(:post_data, 'AUTH_ONLY').include?('solution_ID=A1000000') + ActiveMerchant::Billing::BluePayGateway.application_id = 'A1000000' + assert @gateway.send(:post_data, 'AUTH_ONLY').include?('solution_ID=A1000000') + ensure + ActiveMerchant::Billing::BluePayGateway.application_id = nil + end + + def test_transcript_scrubbing + assert_equal scrubbed_transcript, @gateway.scrub(transcript) + end + + private + + def minimum_requirements + %w(version delim_data relay_response login tran_key amount card_num exp_date type) + end + + def successful_refund_response + 'AUTH_CODE=GFOCD&PAYMENT_ACCOUNT_MASK=xxxxxxxxxxxx4242&CARD_TYPE=VISA&TRANS_TYPE=CREDIT&REBID=&STATUS=1&AVS=_&TRANS_ID=100134230412&CVV2=_&MESSAGE=Approved%20Credit' + end + + def failed_refund_response + 'AUTH_CODE=&PAYMENT_ACCOUNT_MASK=xxxxxxxxxxxx4242&CARD_TYPE=VISA&TRANS_TYPE=CREDIT&REBID=&STATUS=0&AVS=_&TRANS_ID=100134229326&CVV2=_&MESSAGE=The%20referenced%20transaction%20does%20not%20meet%20the%20criteria%20for%20issuing%20a%20credit' + end + + def successful_authorization_response + 'AUTH_CODE=RSWUC&PAYMENT_ACCOUNT_MASK=xxxxxxxxxxxx4242&CARD_TYPE=VISA&TRANS_TYPE=AUTH&REBID=&STATUS=1&AVS=_&TRANS_ID=100134229528&CVV2=_&MESSAGE=Approved%20Auth' + end + + def successful_purchase_response + 'AUTH_CODE=KHJMY&PAYMENT_ACCOUNT_MASK=xxxxxxxxxxxx4242&CARD_TYPE=VISA&TRANS_TYPE=SALE&REBID=&STATUS=1&AVS=_&TRANS_ID=100134229668&CVV2=_&MESSAGE=Approved%20Sale' + end + + def failed_authorization_response + 'AUTH_CODE=&PAYMENT_ACCOUNT_MASK=xxxxxxxxxxxx4242&CARD_TYPE=VISA&TRANS_TYPE=AUTH&REBID=&STATUS=0&AVS=_&TRANS_ID=100134229728&CVV2=_&MESSAGE=Declined%20Auth' + end + + def successful_recurring_response + 'last_date=2012-04-13%2009%3A49%3A27&usual_date=2012-04-13%2000%3A00%3A00&template_id=100096219668&status=active&account_id=100096218902&rebill_id=100096219669&reb_amount=2.00&creation_date=2012-04-13%2009%3A49%3A19&sched_expr=1%20DAY&next_date=2012-04-13%2000%3A00%3A00&next_amount=&user_id=100096218903&cycles_remain=4' + end + + def successful_update_recurring_response + 'last_date=2012-04-13%2009%3A49%3A27&usual_date=2012-04-13%2000%3A00%3A00&template_id=100096219668&status=active&account_id=100096218902&rebill_id=100096219669&reb_amount=2.00&creation_date=2012-04-13%2009%3A49%3A19&sched_expr=1%20DAY&next_date=2012-04-13%2000%3A00%3A00&next_amount=&user_id=100096218903&cycles_remain=4' + end + + def successful_cancel_recurring_response + 'last_date=2012-04-13%2009%3A49%3A27&usual_date=2012-04-13%2000%3A00%3A00&template_id=100096219668&status=stopped&account_id=100096218902&rebill_id=100096219669&reb_amount=2.00&creation_date=2012-04-13%2009%3A49%3A19&sched_expr=1%20DAY&next_date=2012-04-13%2000%3A00%3A00&next_amount=&user_id=100096218903&cycles_remain=4' + end + + def successful_status_recurring_response + 'last_date=2012-04-13%2009%3A49%3A27&usual_date=2012-04-13%2000%3A00%3A00&template_id=100096219668&status=active&account_id=100096218902&rebill_id=100096219669&reb_amount=2.00&creation_date=2012-04-13%2009%3A49%3A19&sched_expr=1%20DAY&next_date=2012-04-13%2000%3A00%3A00&next_amount=&user_id=100096218903&cycles_remain=4' + end + + def transcript + 'card_num=4111111111111111&exp_date=1212&MASTER_ID=&PAYMENT_TYPE=CREDIT&PAYMENT_ACCOUNT=4242424242424242&CARD_CVV2=123&CARD_EXPIRE=0916&NAME1=Longbob&NAME2=Longsen&ORDER_ID=78c40687dd55dbdc140df777b0e8ece3&INVOICE_ID=&invoice_num=78c40687dd55dbdc140df777b0e8ece3&EMAIL=&CUSTOM_ID=&DUPLICATE_OVERRIDE=&TRANS_TYPE=SALE&AMOUNT=1.00&MODE=TEST&ACCOUNT_ID=100096218902&TAMPER_PROOF_SEAL=55624458ce3e15fa8e33e6f2d784bbcb' + end + + def scrubbed_transcript + 'card_num=[FILTERED]&exp_date=1212&MASTER_ID=&PAYMENT_TYPE=CREDIT&PAYMENT_ACCOUNT=[FILTERED]&CARD_CVV2=[FILTERED]&CARD_EXPIRE=0916&NAME1=Longbob&NAME2=Longsen&ORDER_ID=78c40687dd55dbdc140df777b0e8ece3&INVOICE_ID=&invoice_num=78c40687dd55dbdc140df777b0e8ece3&EMAIL=&CUSTOM_ID=&DUPLICATE_OVERRIDE=&TRANS_TYPE=SALE&AMOUNT=1.00&MODE=TEST&ACCOUNT_ID=100096218902&TAMPER_PROOF_SEAL=[FILTERED]' + end +end diff --git a/test/unit/gateways/blue_snap_test.rb b/test/unit/gateways/blue_snap_test.rb new file mode 100644 index 00000000000..541cc05a65d --- /dev/null +++ b/test/unit/gateways/blue_snap_test.rb @@ -0,0 +1,948 @@ +# coding: utf-8 + +require 'test_helper' + +class BlueSnapTest < Test::Unit::TestCase + include CommStub + + def setup + @gateway = BlueSnapGateway.new(api_username: 'login', api_password: 'password') + @credit_card = credit_card + @check = check + @amount = 100 + @options = { order_id: '1', personal_identification_number: 'CNPJ' } + @options_3ds2 = @options.merge( + three_d_secure: { + eci: '05', + cavv: 'AAABAWFlmQAAAABjRWWZEEFgFz+A', + xid: 'MGpHWm5ZWVpKclo0aUk0VmltVDA=', + ds_transaction_id: 'jhg34-sdgds87-sdg87-sdfg7', + version: '2.2.0' + } + ) + @valid_check_options = { + billing_address: { + address1: '123 Street', + address2: 'Apt 1', + city: 'Happy City', + state: 'CA', + zip: '94901' + }, + authorized_by_shopper: true + } + end + + def test_successful_purchase + @gateway.expects(:raw_ssl_request).returns(successful_purchase_response) + + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal '1012082839', response.authorization + end + + def test_successful_purchase_with_unused_state_code + unrecognized_state_code_options = { + billing_address: { + city: 'Dresden', + state: 'Sachsen', + country: 'DE', + zip: '01069' + } + } + + @gateway.expects(:raw_ssl_request).returns(successful_stateless_purchase_response) + + response = @gateway.purchase(@amount, @credit_card, unrecognized_state_code_options) + assert_success response + assert_equal '1021645629', response.authorization + assert_not_includes(response.params, 'state') + end + + def test_successful_echeck_purchase + @gateway.expects(:raw_ssl_request).returns(successful_echeck_purchase_response) + + response = @gateway.purchase(@amount, @check, @options.merge(@valid_check_options)) + assert_success response + assert_equal '1019803029', response.authorization + end + + def test_successful_purchase_with_3ds_auth + response = stub_comms(@gateway, :raw_ssl_request) do + @gateway.purchase(@amount, @credit_card, @options_3ds2) + end.check_request do |method, url, data| + assert_match(/<three-d-secure>/, data) + assert_match(/<eci>#{Regexp.quote(@options_3ds2[:three_d_secure][:eci])}<\/eci>/, data) + assert_match(/<cavv>#{Regexp.quote(@options_3ds2[:three_d_secure][:cavv])}<\/cavv>/, data) + assert_match(/<xid>#{Regexp.quote(@options_3ds2[:three_d_secure][:xid])}<\/xid>/, data) + assert_match(/<three-d-secure-version>#{Regexp.quote(@options_3ds2[:three_d_secure][:version])}<\/three-d-secure-version>/, data) + assert_match(/<ds-transaction-id>#{Regexp.quote(@options_3ds2[:three_d_secure][:ds_transaction_id])}<\/ds-transaction-id>/, data) + end.respond_with(successful_purchase_with_3ds_auth_response) + + assert_success response + assert_equal '1024951831', response.authorization + assert_equal '019082915501456', response.params['original-network-transaction-id'] + assert_equal '019082915501456', response.params['network-transaction-id'] + end + + def test_does_not_send_3ds_auth_when_empty + stub_comms(@gateway, :raw_ssl_request) do + @gateway.purchase(@amount, @credit_card, @options) + end.check_request do |method, url, data| + assert_not_match(/<three-d-secure>/, data) + assert_not_match(/<eci>/, data) + assert_not_match(/<cavv>/, data) + assert_not_match(/<xid>/, data) + assert_not_match(/<three-d-secure-version>/, data) + assert_not_match(/<ds-transaction-id>/, data) + end.respond_with(successful_purchase_response) + end + + def test_failed_purchase + @gateway.expects(:raw_ssl_request).returns(failed_purchase_response) + + response = @gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_equal '14002', response.error_code + end + + def test_failed_echeck_purchase + @gateway.expects(:raw_ssl_request).returns(failed_echeck_purchase_response) + + response = @gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_equal '16004', response.error_code + end + + def test_successful_authorize + response = stub_comms(@gateway, :raw_ssl_request) do + @gateway.authorize(@amount, @credit_card, @options) + end.check_request do |type, endpoint, data, headers| + assert_match '<store-card>false</store-card>', data + assert_match '<personal-identification-number>CNPJ</personal-identification-number>', data + end.respond_with(successful_authorize_response) + assert_success response + assert_equal '1012082893', response.authorization + end + + def test_successful_authorize_with_3ds_auth + response = stub_comms(@gateway, :raw_ssl_request) do + @gateway.authorize(@amount, @credit_card, @options_3ds2) + end.check_request do |type, endpoint, data, headers| + assert_match(/<three-d-secure>/, data) + assert_match(/<eci>#{Regexp.quote(@options_3ds2[:three_d_secure][:eci])}<\/eci>/, data) + assert_match(/<cavv>#{Regexp.quote(@options_3ds2[:three_d_secure][:cavv])}<\/cavv>/, data) + assert_match(/<xid>#{Regexp.quote(@options_3ds2[:three_d_secure][:xid])}<\/xid>/, data) + assert_match(/<three-d-secure-version>#{Regexp.quote(@options_3ds2[:three_d_secure][:version])}<\/three-d-secure-version>/, data) + assert_match(/<ds-transaction-id>#{Regexp.quote(@options_3ds2[:three_d_secure][:ds_transaction_id])}<\/ds-transaction-id>/, data) + end.respond_with(successful_authorize_with_3ds_auth_response) + + assert_success response + assert_equal '1024951833', response.authorization + assert_equal 'MCC8929120829', response.params['original-network-transaction-id'] + assert_equal 'MCC8929120829', response.params['network-transaction-id'] + end + + def test_failed_authorize + @gateway.expects(:raw_ssl_request).returns(failed_authorize_response) + + response = @gateway.authorize(@amount, @credit_card, @options) + assert_failure response + assert_equal '14002', response.error_code + end + + def test_successful_capture + response = stub_comms(@gateway, :raw_ssl_request) do + @gateway.capture(@amount, @credit_card, @options) + end.check_request do |method, url, data| + assert_not_match(/<amount>1.00<\/amount>/, data) + assert_not_match(/<currency>USD<\/currency>/, data) + end.respond_with(successful_capture_response) + + assert_success response + assert_equal '1012082881', response.authorization + end + + def test_successful_partial_capture + response = stub_comms(@gateway, :raw_ssl_request) do + @gateway.capture(@amount, @credit_card, @options.merge(include_capture_amount: true)) + end.check_request do |method, url, data| + assert_match(/<amount>1.00<\/amount>/, data) + assert_match(/<currency>USD<\/currency>/, data) + end.respond_with(successful_capture_response) + + assert_success response + assert_equal '1012082881', response.authorization + end + + def test_failed_capture + @gateway.expects(:raw_ssl_request).returns(failed_capture_response) + + response = @gateway.capture(@amount, 'Authorization') + assert_failure response + assert_equal '20008', response.error_code + end + + def test_successful_refund + @gateway.expects(:raw_ssl_request).returns(successful_refund_response) + + response = @gateway.refund(@amount, 'Authorization') + assert_success response + assert_equal '1012082907', response.authorization + end + + def test_failed_refund + @gateway.expects(:raw_ssl_request).returns(failed_refund_response) + + response = @gateway.refund(@amount, 'Authorization') + assert_failure response + assert_equal '20008', response.error_code + end + + def test_successful_void + @gateway.expects(:raw_ssl_request).returns(successful_void_response) + + response = @gateway.void('Authorization') + assert_success response + assert_equal '1012082919', response.authorization + end + + def test_failed_void + @gateway.expects(:raw_ssl_request).returns(failed_void_response) + + response = @gateway.void('Authorization') + assert_failure response + assert_equal '20008', response.error_code + end + + def test_successful_verify + @gateway.expects(:raw_ssl_request).returns(successful_verify_response) + + response = @gateway.verify(@credit_card, @options) + assert_success response + assert_equal '1012082929', response.authorization + end + + def test_failed_verify + @gateway.expects(:raw_ssl_request).returns(failed_verify_response) + + response = @gateway.verify(@credit_card, @options) + assert_failure response + assert_equal '14002', response.error_code + end + + def test_successful_store + @gateway.expects(:raw_ssl_request).returns(successful_store_response) + + response = @gateway.store(@credit_card, @options) + assert_success response + assert_equal '20936441', response.authorization + end + + def test_successful_echeck_store + @gateway.expects(:raw_ssl_request).returns(successful_echeck_store_response) + + response = @gateway.store(@check, @options) + assert_success response + assert_equal '23844081|check', response.authorization + end + + def test_failed_store + @gateway.expects(:raw_ssl_request).returns(failed_store_response) + + response = @gateway.store(@credit_card, @options) + assert_failure response + assert_equal '14002', response.error_code + end + + def test_failed_echeck_store + @gateway.expects(:raw_ssl_request).returns(failed_echeck_store_response) + + response = @gateway.store(@check, @options) + assert_failure response + assert_equal '10001', response.error_code + end + + def test_currency_added_correctly + stub_comms(@gateway, :raw_ssl_request) do + @gateway.purchase(@amount, @credit_card, @options.merge(currency: 'CAD')) + end.check_request do |method, url, data| + assert_match(/<currency>CAD<\/currency>/, data) + end.respond_with(successful_purchase_response) + end + + def test_verify_good_credentials + @gateway.expects(:raw_ssl_request).returns(credentials_are_legit_response) + assert @gateway.verify_credentials + end + + def test_verify_bad_credentials + @gateway.expects(:raw_ssl_request).returns(credentials_are_bogus_response) + assert !@gateway.verify_credentials + end + + def test_failed_forbidden_response + @gateway.expects(:raw_ssl_request).returns(forbidden_response) + + response = @gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_equal '<xml>You are not authorized to perform this request due to inappropriate role permissions.</xml>', response.message + end + + def test_does_not_send_level_3_when_empty + response = stub_comms(@gateway, :raw_ssl_request) do + @gateway.purchase(@amount, @credit_card, @options) + end.check_request do |type, endpoint, data, headers| + assert_not_match(/level-3-data/, data) + end.respond_with(successful_purchase_response) + assert_success response + end + + def test_scrub + assert @gateway.supports_scrubbing? + assert_equal @gateway.scrub(pre_scrubbed), post_scrubbed + end + + def test_echeck_scrub + assert @gateway.supports_scrubbing? + assert_equal @gateway.scrub(pre_scrubbed_echeck), post_scrubbed_echeck + end + + private + + def pre_scrubbed + %q{ + opening connection to sandbox.bluesnap.com:443... + starting SSL for sandbox.bluesnap.com:443... + <- "POST /services/2/transactions HTTP/1.1\r\nContent-Type: application/xml\r\nAuthorization: Basic QVBJXzE0NjExNzM3MTY2NTc2NzM0MDQyMzpuZll3VHg4ZkZBdkpxQlhjeHF3Qzg=\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: sandbox.bluesnap.com\r\nContent-Length: 683\r\n\r\n" + <- "<card-transaction xmlns=\"http://ws.plimus.com\">\n <card-transaction-type>AUTH_CAPTURE</card-transaction-type>\n <recurring-transaction>RECURRING</recurring-transaction>\n <amount>1.00</amount>\n <currency>USD</currency>\n <card-holder-info>\n <first-name>Longbob</first-name>\n <last-name>Longsen</last-name>\n <country>CA</country>\n <state>ON</state>\n <city>Ottawa</city>\n <zip>K1C2N6</zip>\n </card-holder-info>\n <transaction-fraud-info/>\n <credit-card>\n <card-number>4263982640269299</card-number>\n <security-code>123</security-code>\n <expiration-month>9</expiration-month>\n <expiration-year>2017</expiration-year>\n </credit-card>\n</card-transaction>" + -> "HTTP/1.1 200 OK\r\n" + -> "Set-Cookie: JSESSIONID=156258FCEC747EFAEA6FE909FDF0004A; Path=/services/; Secure; HttpOnly\r\n" + -> "Content-Encoding: gzip\r\n" + -> "\x1F\x8B\b\x00\x00\x00\x00\x00\x00\x03mS]\x8F\xDA0\x10|\xCF\xAF@\xA9\xD47c\xA0\x1F:Z\xE3\x13\xCD\xD1\x16\xF5\xC4U\x81\xF4\xB52\xB1\xE1,%v\xE4u\xB8K" + Conn close + } + end + + def pre_scrubbed_echeck + %q{ + opening connection to sandbox.bluesnap.com:443... + opened + starting SSL for sandbox.bluesnap.com:443... + SSL established + <- "POST /services/2/alt-transactions HTTP/1.1\r\nContent-Type: application/xml\r\nAuthorization: Basic QVBJXzE0NjExNzM3MTY2NTc2NzM0MDQyMzpuZll3VHg4ZkZBdkpxQlhjeHF3Qzg=\r\nConnection: close\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nHost: sandbox.bluesnap.com\r\nContent-Length: 973\r\n\r\n" + <- "<alt-transaction xmlns=\"http://ws.plimus.com\">\n <amount>1.00</amount>\n <currency>USD</currency>\n <payer-info>\n <first-name>Jim</first-name>\n <last-name>Smith</last-name>\n <state>CA</state>\n <city>Happy City</city>\n <zip>94901</zip>\n <company-name>Jim Smith</company-name>\n </payer-info>\n <ecp-transaction>\n <account-number>15378535</account-number>\n <routing-number>244183602</routing-number>\n <account-type>CORPORATE_CHECKING</account-type>\n </ecp-transaction>\n <authorized-by-shopper>true</authorized-by-shopper>\n <transaction-fraud-info/>\n </alt-transaction>" + -> "HTTP/1.1 200 200\r\n" + -> "Set-Cookie: JSESSIONID=65D503B9785EA6641D4757EA568A6532; Path=/services; Secure; HttpOnly\r\n" + -> "Connection: close\r\n" + } + end + + def post_scrubbed + %q{ + opening connection to sandbox.bluesnap.com:443... + starting SSL for sandbox.bluesnap.com:443... + <- "POST /services/2/transactions HTTP/1.1\r\nContent-Type: application/xml\r\nAuthorization: Basic [FILTERED]=\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: sandbox.bluesnap.com\r\nContent-Length: 683\r\n\r\n" + <- "<card-transaction xmlns=\"http://ws.plimus.com\">\n <card-transaction-type>AUTH_CAPTURE</card-transaction-type>\n <recurring-transaction>RECURRING</recurring-transaction>\n <amount>1.00</amount>\n <currency>USD</currency>\n <card-holder-info>\n <first-name>Longbob</first-name>\n <last-name>Longsen</last-name>\n <country>CA</country>\n <state>ON</state>\n <city>Ottawa</city>\n <zip>K1C2N6</zip>\n </card-holder-info>\n <transaction-fraud-info/>\n <credit-card>\n <card-number>[FILTERED]</card-number>\n <security-code>[FILTERED]</security-code>\n <expiration-month>9</expiration-month>\n <expiration-year>2017</expiration-year>\n </credit-card>\n</card-transaction>" + -> "HTTP/1.1 200 OK\r\n" + -> "Set-Cookie: JSESSIONID=156258FCEC747EFAEA6FE909FDF0004A; Path=/services/; Secure; HttpOnly\r\n" + -> "Content-Encoding: gzip\r\n" + -> "\x1F\x8B\b\x00\x00\x00\x00\x00\x00\x03mS]\x8F\xDA0\x10|\xCF\xAF@\xA9\xD47c\xA0\x1F:Z\xE3\x13\xCD\xD1\x16\xF5\xC4U\x81\xF4\xB52\xB1\xE1,%v\xE4u\xB8K" + Conn close + } + end + + def post_scrubbed_echeck + %q{ + opening connection to sandbox.bluesnap.com:443... + opened + starting SSL for sandbox.bluesnap.com:443... + SSL established + <- "POST /services/2/alt-transactions HTTP/1.1\r\nContent-Type: application/xml\r\nAuthorization: Basic [FILTERED]=\r\nConnection: close\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nHost: sandbox.bluesnap.com\r\nContent-Length: 973\r\n\r\n" + <- "<alt-transaction xmlns=\"http://ws.plimus.com\">\n <amount>1.00</amount>\n <currency>USD</currency>\n <payer-info>\n <first-name>Jim</first-name>\n <last-name>Smith</last-name>\n <state>CA</state>\n <city>Happy City</city>\n <zip>94901</zip>\n <company-name>Jim Smith</company-name>\n </payer-info>\n <ecp-transaction>\n <account-number>[FILTERED]</account-number>\n <routing-number>[FILTERED]</routing-number>\n <account-type>CORPORATE_CHECKING</account-type>\n </ecp-transaction>\n <authorized-by-shopper>true</authorized-by-shopper>\n <transaction-fraud-info/>\n </alt-transaction>" + -> "HTTP/1.1 200 200\r\n" + -> "Set-Cookie: JSESSIONID=65D503B9785EA6641D4757EA568A6532; Path=/services; Secure; HttpOnly\r\n" + -> "Connection: close\r\n" + } + end + + def successful_purchase_response + MockResponse.succeeded <<-XML + <?xml version="1.0" encoding="UTF-8"?> + <card-transaction xmlns="http://ws.plimus.com"> + <card-transaction-type>AUTH_CAPTURE</card-transaction-type> + <transaction-id>1012082839</transaction-id> + <recurring-transaction>ECOMMERCE</recurring-transaction> + <soft-descriptor>BLS*Spreedly</soft-descriptor> + <amount>1.00</amount> + <currency>USD</currency> + <card-holder-info> + <first-name>Longbob</first-name> + <last-name>Longsen</last-name> + <country>CA</country> + <state>ON</state> + <city>Ottawa</city> + <zip>K1C2N6</zip> + <personal-identification-number>CNPJ</personal-identification-number> + </card-holder-info> + <credit-card> + <card-last-four-digits>9299</card-last-four-digits> + <card-type>VISA</card-type> + <card-sub-type>CREDIT</card-sub-type> + </credit-card> + <processing-info> + <processing-status>success</processing-status> + <cvv-response-code>ND</cvv-response-code> + <avs-response-code-zip>U</avs-response-code-zip> + <avs-response-code-address>U</avs-response-code-address> + <avs-response-code-name>U</avs-response-code-name> + </processing-info> + </card-transaction> + XML + end + + def successful_purchase_with_3ds_auth_response + MockResponse.succeeded <<-XML + <?xml version="1.0" encoding="UTF-8" standalone="yes"?> + <card-transaction xmlns="http://ws.plimus.com"> + <card-transaction-type>AUTH_CAPTURE</card-transaction-type> + <transaction-id>1024951831</transaction-id> + <recurring-transaction>ECOMMERCE</recurring-transaction> + <soft-descriptor>BLS&#x2a;Spreedly</soft-descriptor> + <amount>1.00</amount> + <usd-amount>1.00</usd-amount> + <currency>USD</currency> + <avs-response-code>N</avs-response-code> + <card-holder-info> + <first-name>Longbob</first-name> + <last-name>Longsen</last-name> + <country>CA</country> + <state>ON</state> + <city>Ottawa</city> + <zip>K1C2N6</zip> + </card-holder-info> + <vaulted-shopper-id>25105083</vaulted-shopper-id> + <credit-card> + <card-last-four-digits>1091</card-last-four-digits> + <card-type>VISA</card-type> + <card-sub-type>CREDIT</card-sub-type> + <bin-category>CONSUMER</bin-category> + <card-regulated>N</card-regulated> + <issuing-country-code>us</issuing-country-code> + </credit-card> + <network-transaction-info> + <original-network-transaction-id>019082915501456</original-network-transaction-id> + <network-transaction-id>019082915501456</network-transaction-id> + </network-transaction-info> + <processing-info> + <processing-status>success</processing-status> + <cvv-response-code>NR</cvv-response-code> + <avs-response-code-zip>N</avs-response-code-zip> + <avs-response-code-address>N</avs-response-code-address> + <avs-response-code-name>U</avs-response-code-name> + <network-transaction-id>019082915501456</network-transaction-id> + </processing-info> + </card-transaction> + XML + end + + def successful_echeck_purchase_response + MockResponse.succeeded <<-XML + <?xml version="1.0" encoding="UTF-8"?> + <alt-transaction xmlns="http://ws.plimus.com"> + <transaction-id>1019803029</transaction-id> + <amount>1.00</amount> + <currency>USD</currency> + <payer-info> + <first-name>Jim</first-name> + <last-name>Smith</last-name> + <state>CA</state> + <city>Happy City</city> + <zip>94901</zip> + <company-name>Jim Smith</company-name> + </payer-info> + <ecp-transaction> + <account-number>15378535</account-number> + <routing-number>244183602</routing-number> + <account-type>CORPORATE_CHECKING</account-type> + </ecp-transaction> + <processing-info> + <processing-status>PENDING</processing-status> + </processing-info> + </alt-transaction> + XML + end + + def successful_stateless_purchase_response + MockResponse.succeeded <<-XML + <?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?> + <card-transaction xmlns=\"http://ws.plimus.com\"> + <card-transaction-type>AUTH_CAPTURE</card-transaction-type> + <transaction-id>1021645629</transaction-id> + <recurring-transaction>ECOMMERCE</recurring-transaction> + <soft-descriptor>BLS&#x2a;Spreedly</soft-descriptor> + <amount>1.00</amount> + <usd-amount>1.00</usd-amount> + <currency>USD</currency> + <card-holder-info> + <first-name>Longbob</first-name> + <last-name>Longsen</last-name> + <country>DE</country> + <city>Dresden</city> + <zip>01069</zip> + </card-holder-info> + <vaulted-shopper-id>24449087</vaulted-shopper-id> + <credit-card> + <card-last-four-digits>9299</card-last-four-digits> + <card-type>VISA</card-type> + <card-sub-type>CREDIT</card-sub-type> + <card-category>PLATINUM</card-category> + <bin-category>CONSUMER</bin-category> + <card-regulated>N</card-regulated> + <issuing-bank>ALLIED IRISH BANKS PLC</issuing-bank> + <issuing-country-code>ie</issuing-country-code> + </credit-card> + <processing-info> + <processing-status>success</processing-status> + <cvv-response-code>ND</cvv-response-code> + <avs-response-code-zip>U</avs-response-code-zip> + <avs-response-code-address>U</avs-response-code-address> + <avs-response-code-name>U</avs-response-code-name> + </processing-info> + </card-transaction> + XML + end + + def failed_purchase_response + body = <<-XML + <?xml version="1.0" encoding="UTF-8"?> + <messages xmlns="http://ws.plimus.com"> + <message> + <error-name>INCORRECT_INFORMATION</error-name> + <code>14002</code> + <description>Transaction failed because of payment processing failure.: 430285 - Authorization has failed for this transaction. Please try again or contact your bank for assistance</description> + </message> + </messages> + XML + + MockResponse.failed(body, 400) + end + + def failed_echeck_purchase_response + body = <<-XML + <?xml version="1.0" encoding="UTF-8" standalone="yes"?> + <messages xmlns="http://ws.plimus.com"> + <message> + <error-name>PAYMENT_NOT_AUTHORIZED_BY_SHOPPER</error-name> + <code>16004</code> + <description>The payment was not authorized by shopper. Missing/Invalid 'authorized-by-shopper' element.</description> + </message> + </messages> + XML + + MockResponse.failed(body, 400) + end + + def successful_authorize_response + MockResponse.succeeded <<-XML + <?xml version="1.0" encoding="UTF-8"?> + <card-transaction xmlns="http://ws.plimus.com"> + <card-transaction-type>AUTH_ONLY</card-transaction-type> + <transaction-id>1012082893</transaction-id> + <recurring-transaction>ECOMMERCE</recurring-transaction> + <soft-descriptor>BLS*Spreedly</soft-descriptor> + <amount>1.00</amount> + <currency>USD</currency> + <card-holder-info> + <first-name>Longbob</first-name> + <last-name>Longsen</last-name> + <country>CA</country> + <state>ON</state> + <city>Ottawa</city> + <zip>K1C2N6</zip> + <personal-identification-number>CNPJ</personal-identification-number> + </card-holder-info> + <credit-card> + <card-last-four-digits>9299</card-last-four-digits> + <card-type>VISA</card-type> + <card-sub-type>CREDIT</card-sub-type> + </credit-card> + <processing-info> + <processing-status>success</processing-status> + <cvv-response-code>ND</cvv-response-code> + <avs-response-code-zip>U</avs-response-code-zip> + <avs-response-code-address>U</avs-response-code-address> + <avs-response-code-name>U</avs-response-code-name> + </processing-info> + </card-transaction> + XML + end + + def successful_authorize_with_3ds_auth_response + MockResponse.succeeded <<-XML + <?xml version="1.0" encoding="UTF-8" standalone="yes"?> + <card-transaction xmlns="http://ws.plimus.com"> + <card-transaction-type>AUTH_ONLY</card-transaction-type> + <transaction-id>1024951833</transaction-id> + <recurring-transaction>ECOMMERCE</recurring-transaction> + <soft-descriptor>BLS&#x2a;Spreedly</soft-descriptor> + <amount>1.00</amount> + <usd-amount>1.00</usd-amount> + <currency>USD</currency> + <avs-response-code>S</avs-response-code> + <card-holder-info> + <first-name>Longbob</first-name> + <last-name>Longsen</last-name> + <country>CA</country> + <state>ON</state> + <city>Ottawa</city> + <zip>K1C2N6</zip> + </card-holder-info> + <vaulted-shopper-id>25105085</vaulted-shopper-id> + <credit-card> + <card-last-four-digits>1096</card-last-four-digits> + <card-type>MASTERCARD</card-type> + <card-sub-type>CREDIT</card-sub-type> + <card-category>STANDARD</card-category> + <bin-category>CONSUMER</bin-category> + <card-regulated>N</card-regulated> + <issuing-bank>PUBLIC BANK BERHAD</issuing-bank> + <issuing-country-code>my</issuing-country-code> + </credit-card> + <network-transaction-info> + <original-network-transaction-id>MCC8929120829</original-network-transaction-id> + <network-transaction-id>MCC8929120829</network-transaction-id> + </network-transaction-info> + <processing-info> + <processing-status>success</processing-status> + <cvv-response-code>NC</cvv-response-code> + <avs-response-code-zip>U</avs-response-code-zip> + <avs-response-code-address>U</avs-response-code-address> + <avs-response-code-name>U</avs-response-code-name> + <network-transaction-id>MCC8929120829</network-transaction-id> + </processing-info> + </card-transaction> + XML + end + + def failed_authorize_response + body = <<-XML + <?xml version="1.0" encoding="UTF-8"?> + <messages xmlns="http://ws.plimus.com"> + <message> + <error-name>INCORRECT_INFORMATION</error-name> + <code>14002</code> + <description>Transaction failed because of payment processing failure.: 430285 - Authorization has failed for this transaction. Please try again or contact your bank for assistance</description> + </message> + </messages> + XML + MockResponse.failed(body, 400) + end + + def successful_capture_response + MockResponse.succeeded <<-XML + <?xml version="1.0" encoding="UTF-8"?> + <card-transaction xmlns="http://ws.plimus.com"> + <card-transaction-type>CAPTURE</card-transaction-type> + <transaction-id>1012082881</transaction-id> + <recurring-transaction>ECOMMERCE</recurring-transaction> + <soft-descriptor>BLS*Spreedly</soft-descriptor> + <amount>1.00</amount> + <currency>USD</currency> + <card-holder-info> + <first-name>Longbob</first-name> + <last-name>Longsen</last-name> + <country>ca</country> + <state>ON</state> + <city>Ottawa</city> + <zip>K1C2N6</zip> + <personal-identification-number>CNPJ</personal-identification-number> + </card-holder-info> + <credit-card> + <card-last-four-digits>9299</card-last-four-digits> + <card-type>VISA</card-type> + <card-sub-type>CREDIT</card-sub-type> + </credit-card> + <processing-info> + <processing-status>SUCCESS</processing-status> + <cvv-response-code>ND</cvv-response-code> + <avs-response-code-zip>U</avs-response-code-zip> + <avs-response-code-address>U</avs-response-code-address> + <avs-response-code-name>U</avs-response-code-name> + </processing-info> + </card-transaction> + XML + end + + def failed_capture_response + body = <<-XML + <?xml version="1.0" encoding="UTF-8"?> + <messages xmlns="http://ws.plimus.com"> + <message> + <error-name>TRANSACTION_ID_REQUIRED</error-name> + <code>20008</code> + <description>Transaction operation cannot be completed due to missing transaction ID parameter.</description> + </message> + </messages> + XML + MockResponse.failed(body, 400) + end + + def successful_refund_response + MockResponse.succeeded <<-XML + <?xml version="1.0" encoding="UTF-8"?> + <card-transaction xmlns="http://ws.plimus.com"> + <card-transaction-type>REFUND</card-transaction-type> + <transaction-id>1012082907</transaction-id> + <recurring-transaction>ECOMMERCE</recurring-transaction> + <soft-descriptor>BLS*Spreedly</soft-descriptor> + <amount>1.00</amount> + <currency>USD</currency> + <card-holder-info> + <first-name>Longbob</first-name> + <last-name>Longsen</last-name> + <country>ca</country> + <state>ON</state> + <city>Ottawa</city> + <zip>K1C2N6</zip> + <personal-identification-number>CNPJ</personal-identification-number> + </card-holder-info> + <credit-card> + <card-last-four-digits>9299</card-last-four-digits> + <card-type>VISA</card-type> + <card-sub-type>CREDIT</card-sub-type> + </credit-card> + <processing-info> + <processing-status>SUCCESS</processing-status> + <cvv-response-code>ND</cvv-response-code> + <avs-response-code-zip>U</avs-response-code-zip> + <avs-response-code-address>U</avs-response-code-address> + <avs-response-code-name>U</avs-response-code-name> + </processing-info> + </card-transaction> + XML + end + + def failed_refund_response + body = <<-XML + <?xml version="1.0" encoding="UTF-8"?> + <messages xmlns="http://ws.plimus.com"> + <message> + <error-name>TRANSACTION_ID_REQUIRED</error-name> + <code>20008</code> + <description>Transaction operation cannot be completed due to missing transaction ID parameter.</description> + </message> + </messages> + XML + MockResponse.failed(body, 400) + end + + def successful_void_response + MockResponse.succeeded <<-XML + <?xml version="1.0" encoding="UTF-8"?> + <card-transaction xmlns="http://ws.plimus.com"> + <card-transaction-type>AUTH_REVERSAL</card-transaction-type> + <transaction-id>1012082919</transaction-id> + <recurring-transaction>ECOMMERCE</recurring-transaction> + <soft-descriptor>BLS*Spreedly</soft-descriptor> + <amount>1.00</amount> + <currency>USD</currency> + <card-holder-info> + <first-name>Longbob</first-name> + <last-name>Longsen</last-name> + <country>ca</country> + <state>ON</state> + <city>Ottawa</city> + <zip>K1C2N6</zip> + <personal-identification-number>CNPJ</personal-identification-number> + </card-holder-info> + <credit-card> + <card-last-four-digits>9299</card-last-four-digits> + <card-type>VISA</card-type> + <card-sub-type>CREDIT</card-sub-type> + </credit-card> + <processing-info> + <processing-status>SUCCESS</processing-status> + <cvv-response-code>ND</cvv-response-code> + <avs-response-code-zip>U</avs-response-code-zip> + <avs-response-code-address>U</avs-response-code-address> + <avs-response-code-name>U</avs-response-code-name> + </processing-info> + </card-transaction> + XML + end + + def failed_void_response + body = <<-XML + <?xml version="1.0" encoding="UTF-8"?> + <messages xmlns="http://ws.plimus.com"> + <message> + <error-name>TRANSACTION_ID_REQUIRED</error-name> + <code>20008</code> + <description>Transaction operation cannot be completed due to missing transaction ID parameter.</description> + </message> + </messages> + XML + MockResponse.failed(body, 400) + end + + def successful_verify_response + MockResponse.succeeded <<-XML + <?xml version="1.0" encoding="UTF-8"?> + <card-transaction xmlns="http://ws.plimus.com"> + <card-transaction-type>AUTH_ONLY</card-transaction-type> + <transaction-id>1012082929</transaction-id> + <recurring-transaction>ECOMMERCE</recurring-transaction> + <soft-descriptor>Spreedly</soft-descriptor> + <amount>0.00</amount> + <currency>USD</currency> + <card-holder-info> + <first-name>Longbob</first-name> + <last-name>Longsen</last-name> + <country>CA</country> + <state>ON</state> + <city>Ottawa</city> + <zip>K1C2N6</zip> + <personal-identification-number>CNPJ</personal-identification-number> + </card-holder-info> + <credit-card> + <card-last-four-digits>9299</card-last-four-digits> + <card-type>VISA</card-type> + <card-sub-type>CREDIT</card-sub-type> + </credit-card> + <processing-info> + <processing-status>success</processing-status> + <cvv-response-code>ND</cvv-response-code> + <avs-response-code-zip>U</avs-response-code-zip> + <avs-response-code-address>U</avs-response-code-address> + <avs-response-code-name>U</avs-response-code-name> + </processing-info> + </card-transaction> + XML + end + + def failed_verify_response + body = <<-XML + <?xml version="1.0" encoding="UTF-8"?> + <messages xmlns="http://ws.plimus.com"> + <message> + <error-name>INCORRECT_INFORMATION</error-name> + <code>14002</code> + <description>Transaction failed because of payment processing failure.: 430285 - Authorization has failed for this transaction. Please try again or contact your bank for assistance</description> + </message> + </messages> + XML + MockResponse.failed(body, 400) + end + + def successful_store_response + response = MockResponse.succeeded <<-XML + <?xml version="1.0" encoding="UTF-8"?> + <vaulted-shopper xmlns="http://ws.plimus.com"> + <first-name>Longbob</first-name> + <last-name>Longsen</last-name> + <country>ca</country> + <state>ON</state> + <city>Ottawa</city> + <zip>K1C2N6</zip> + <personal-identification-number>CNPJ</personal-identification-number> + <shopper-currency>USD</shopper-currency> + <payment-sources> + <credit-card-info> + <billing-contact-info> + <first-name>Longbob</first-name> + <last-name>Longsen</last-name> + <city /> + </billing-contact-info> + <credit-card> + <card-last-four-digits>9299</card-last-four-digits> + <card-type>VISA</card-type> + <card-sub-type>CREDIT</card-sub-type> + </credit-card> + <processing-info> + <cvv-response-code>ND</cvv-response-code> + <avs-response-code-zip>U</avs-response-code-zip> + <avs-response-code-address>U</avs-response-code-address> + <avs-response-code-name>U</avs-response-code-name> + </processing-info> + </credit-card-info> + </payment-sources> + </vaulted-shopper> + XML + + response.headers = { 'content-location' => 'https://sandbox.bluesnap.com/services/2/vaulted-shoppers/20936441' } + response + end + + def successful_echeck_store_response + response = MockResponse.succeeded <<-XML + <?xml version="1.0" encoding="UTF-8" standalone="yes"?> + <vaulted-shopper xmlns="http://ws.plimus.com"> + <vaulted-shopper-id>23844081</vaulted-shopper-id> + <first-name>Jim</first-name> + <last-name>Smith</last-name> + <city>Happy City</city> + <zip>94901</zip> + <company-name>Jim Smith</company-name> + <shopper-currency>USD</shopper-currency> + <payment-sources> + <ecp-info> + <billing-contact-info> + <first-name>Jim</first-name> + <last-name>Smith</last-name> + <city></city> + <company-name>Jim Smith</company-name> + </billing-contact-info> + <ecp> + <account-number>15378535</account-number> + <routing-number>244183602</routing-number> + <account-type>CORPORATE_CHECKING</account-type> + </ecp> + </ecp-info> + </payment-sources> + </vaulted-shopper> + XML + + response.headers = { 'content-location' => 'https://sandbox.bluesnap.com/services/2/vaulted-shoppers/23844081' } + response + end + + def failed_store_response + body = <<-XML + <?xml version="1.0" encoding="UTF-8"?> + <messages xmlns="http://ws.plimus.com"> + <message> + <error-name>INCORRECT_INFORMATION</error-name> + <code>14002</code> + <description>Transaction failed because of payment processing failure.: 430285 - Authorization has failed for this transaction. Please try again or contact your bank for assistance</description> + </message> + </messages> + XML + MockResponse.failed(body, 400) + end + + def failed_echeck_store_response + body = <<-XML + <?xml version="1.0" encoding="UTF-8" standalone="yes"?> + <messages xmlns="http://ws.plimus.com"> + <message> + <error-name>VALIDATION_GENERAL_FAILURE</error-name> + <code>10001</code> + <description>ECP data validity check failed</description> + </message> + </messages> + XML + MockResponse.failed(body, 400) + end + + def forbidden_response + MockResponse.new(403, '<xml>You are not authorized to perform this request due to inappropriate role permissions.</xml>') + end + + def credentials_are_legit_response + MockResponse.new(400, '<xml>Server Error</xml>') + end + + def credentials_are_bogus_response + MockResponse.new(401, %{<!DOCTYPE html><html lang="en"><head><title>HTTP Status 401 – Unauthorized</title><style type="text/css">h1 {font-family:Tahoma,Arial,sans-serif;color:white;background-color:#525D76;font-size:22px;} h2 {font-family:Tahoma,Arial,sans-serif;color:white;background-color:#525D76;font-size:16px;} h3 {font-family:Tahoma,Arial,sans-serif;color:white;background-color:#525D76;font-size:14px;} body {font-family:Tahoma,Arial,sans-serif;color:black;background-color:white;} b {font-family:Tahoma,Arial,sans-serif;color:white;background-color:#525D76;} p {font-family:Tahoma,Arial,sans-serif;background:white;color:black;font-size:12px;} a {color:black;} a.name {color:black;} .line {height:1px;background-color:#525D76;border:none;}</style></head><body><h1>HTTP Status 401 – Unauthorized</h1><hr class="line" /><p><b>Type</b> Status Report</p><p><b>Message</b> Bad credentials</p><p><b>Description</b> The request has not been applied because it lacks valid authentication credentials for the target resource.</p><hr class="line" /><h3>Apache Tomcat Version X</h3></body></html>}) + end +end diff --git a/test/unit/gateways/bogus_test.rb b/test/unit/gateways/bogus_test.rb index 87b6148d724..e6978d828e1 100644 --- a/test/unit/gateways/bogus_test.rb +++ b/test/unit/gateways/bogus_test.rb @@ -1,8 +1,10 @@ require 'test_helper' class BogusTest < Test::Unit::TestCase - SUCCESS_PLACEHOLDER = '4444333322221111' - FAILURE_PLACEHOLDER = '4444333311112222' + CC_SUCCESS_PLACEHOLDER = '4444333322221111' + CC_FAILURE_PLACEHOLDER = '4444333311112222' + CHECK_SUCCESS_PLACEHOLDER = '111111111111' + CHECK_FAILURE_PLACEHOLDER = '222222222222' def setup @gateway = BogusGateway.new( @@ -10,84 +12,103 @@ def setup :password => 'bogus' ) - @creditcard = credit_card(SUCCESS_PLACEHOLDER) + @creditcard = credit_card(CC_SUCCESS_PLACEHOLDER) - @response = ActiveMerchant::Billing::Response.new(true, "Transaction successful", :transid => BogusGateway::AUTHORIZATION) + @response = ActiveMerchant::Billing::Response.new(true, 'Transaction successful', :transid => BogusGateway::AUTHORIZATION) end def test_authorize - assert @gateway.authorize(1000, credit_card(SUCCESS_PLACEHOLDER)).success? - assert !@gateway.authorize(1000, credit_card(FAILURE_PLACEHOLDER)).success? - assert_raises(ActiveMerchant::Billing::Error) do + assert @gateway.authorize(1000, credit_card(CC_SUCCESS_PLACEHOLDER)).success? + response = @gateway.authorize(1000, credit_card(CC_FAILURE_PLACEHOLDER)) + refute response.success? + assert_equal Gateway::STANDARD_ERROR_CODE[:processing_error], response.error_code + e = assert_raises(ActiveMerchant::Billing::Error) do @gateway.authorize(1000, credit_card('123')) end + assert_equal('Bogus Gateway: Use CreditCard number ending in 1 for success, 2 for exception and anything else for error', e.message) end - def test_purchase - assert @gateway.purchase(1000, credit_card(SUCCESS_PLACEHOLDER)).success? - assert !@gateway.purchase(1000, credit_card(FAILURE_PLACEHOLDER)).success? - assert_raises(ActiveMerchant::Billing::Error) do - @gateway.purchase(1000, credit_card('123')) - end + def test_authorize_using_credit_card_token + token = @gateway.store(credit_card(CC_SUCCESS_PLACEHOLDER)).authorization + assert @gateway.authorize(1000, token).success? end - def test_recurring - assert @gateway.recurring(1000, credit_card(SUCCESS_PLACEHOLDER)).success? - assert !@gateway.recurring(1000, credit_card(FAILURE_PLACEHOLDER)).success? - assert_raises(ActiveMerchant::Billing::Error) do - @gateway.recurring(1000, credit_card('123')) + def test_purchase + assert @gateway.purchase(1000, credit_card(CC_SUCCESS_PLACEHOLDER)).success? + response = @gateway.purchase(1000, credit_card(CC_FAILURE_PLACEHOLDER)) + refute response.success? + assert_equal Gateway::STANDARD_ERROR_CODE[:processing_error], response.error_code + e = assert_raises(ActiveMerchant::Billing::Error) do + @gateway.purchase(1000, credit_card('123')) end + assert_equal('Bogus Gateway: Use CreditCard number ending in 1 for success, 2 for exception and anything else for error', e.message) end def test_capture assert @gateway.capture(1000, '1337').success? - assert @gateway.capture(1000, @response.params["transid"]).success? - assert !@gateway.capture(1000, FAILURE_PLACEHOLDER).success? + assert @gateway.capture(1000, @response.params['transid']).success? + response = @gateway.capture(1000, CC_FAILURE_PLACEHOLDER) + refute response.success? + assert_equal Gateway::STANDARD_ERROR_CODE[:processing_error], response.error_code assert_raises(ActiveMerchant::Billing::Error) do - @gateway.capture(1000, SUCCESS_PLACEHOLDER) + @gateway.capture(1000, CC_SUCCESS_PLACEHOLDER) end end def test_credit - assert @gateway.credit(1000, credit_card(SUCCESS_PLACEHOLDER)).success? - assert !@gateway.credit(1000, credit_card(FAILURE_PLACEHOLDER)).success? - assert_raises(ActiveMerchant::Billing::Error) do + assert @gateway.credit(1000, credit_card(CC_SUCCESS_PLACEHOLDER)).success? + response = @gateway.credit(1000, credit_card(CC_FAILURE_PLACEHOLDER)) + refute response.success? + assert_equal Gateway::STANDARD_ERROR_CODE[:processing_error], response.error_code + e = assert_raises(ActiveMerchant::Billing::Error) do @gateway.credit(1000, credit_card('123')) end + assert_equal('Bogus Gateway: Use CreditCard number ending in 1 for success, 2 for exception and anything else for error', e.message) end def test_refund assert @gateway.refund(1000, '1337').success? - assert @gateway.refund(1000, @response.params["transid"]).success? - assert !@gateway.refund(1000, FAILURE_PLACEHOLDER).success? + assert @gateway.refund(1000, @response.params['transid']).success? + response = @gateway.refund(1000, CC_FAILURE_PLACEHOLDER) + refute response.success? + assert_equal Gateway::STANDARD_ERROR_CODE[:processing_error], response.error_code assert_raises(ActiveMerchant::Billing::Error) do - @gateway.refund(1000, SUCCESS_PLACEHOLDER) + @gateway.refund(1000, CC_SUCCESS_PLACEHOLDER) end end def test_credit_uses_refund options = {:foo => :bar} @gateway.expects(:refund).with(1000, '1337', options) - assert_deprecation_warning(Gateway::CREDIT_DEPRECATION_MESSAGE, @gateway) do + assert_deprecation_warning(Gateway::CREDIT_DEPRECATION_MESSAGE) do @gateway.credit(1000, '1337', options) end end def test_void assert @gateway.void('1337').success? - assert @gateway.void(@response.params["transid"]).success? - assert !@gateway.void(FAILURE_PLACEHOLDER).success? + assert @gateway.void(@response.params['transid']).success? + response = @gateway.void(CC_FAILURE_PLACEHOLDER) + refute response.success? + assert_equal Gateway::STANDARD_ERROR_CODE[:processing_error], response.error_code assert_raises(ActiveMerchant::Billing::Error) do - @gateway.void(SUCCESS_PLACEHOLDER) + @gateway.void(CC_SUCCESS_PLACEHOLDER) end end def test_store - @gateway.store(@creditcard) + assert @gateway.store(credit_card(CC_SUCCESS_PLACEHOLDER)).success? + response = @gateway.store(credit_card(CC_FAILURE_PLACEHOLDER)) + refute response.success? + assert_equal Gateway::STANDARD_ERROR_CODE[:processing_error], response.error_code + e = assert_raises(ActiveMerchant::Billing::Error) do + @gateway.store(credit_card('123')) + end + assert_equal('Bogus Gateway: Use CreditCard number ending in 1 for success, 2 for exception and anything else for error', e.message) end def test_unstore - @gateway.unstore(SUCCESS_PLACEHOLDER) + @gateway.unstore(CC_SUCCESS_PLACEHOLDER) end def test_store_then_purchase @@ -96,10 +117,80 @@ def test_store_then_purchase end def test_supported_countries - assert_equal ['US'], BogusGateway.supported_countries + assert_equal [], BogusGateway.supported_countries end def test_supported_card_types assert_equal [:bogus], BogusGateway.supported_cardtypes end + + def test_authorize_with_check + assert @gateway.authorize(1000, check(:account_number => CHECK_SUCCESS_PLACEHOLDER, :number => nil)).success? + assert !@gateway.authorize(1000, check(:account_number => CHECK_FAILURE_PLACEHOLDER, :number => nil)).success? + e = assert_raises(ActiveMerchant::Billing::Error) do + @gateway.authorize(1000, check(:account_number => '123', :number => nil)) + end + assert_equal('Bogus Gateway: Use bank account number ending in 1 for success, 2 for exception and anything else for error', e.message) + end + + def test_purchase_with_check + # use account number if number isn't given + assert @gateway.purchase(1000, check(:account_number => CHECK_SUCCESS_PLACEHOLDER, :number => nil)).success? + assert !@gateway.purchase(1000, check(:account_number => CHECK_FAILURE_PLACEHOLDER, :number => nil)).success? + # give priority to number over account_number if given + assert !@gateway.purchase(1000, check(:account_number => CHECK_SUCCESS_PLACEHOLDER, :number => CHECK_FAILURE_PLACEHOLDER)).success? + assert @gateway.purchase(1000, check(:account_number => CHECK_FAILURE_PLACEHOLDER, :number => CHECK_SUCCESS_PLACEHOLDER)).success? + e = assert_raises(ActiveMerchant::Billing::Error) do + @gateway.purchase(1000, check(:account_number => '123', :number => nil)) + end + assert_equal('Bogus Gateway: Use bank account number ending in 1 for success, 2 for exception and anything else for error', e.message) + end + + def test_store_with_check + assert @gateway.store(check(:account_number => CHECK_SUCCESS_PLACEHOLDER, :number => nil)).success? + assert !@gateway.store(check(:account_number => CHECK_FAILURE_PLACEHOLDER, :number => nil)).success? + e = assert_raises(ActiveMerchant::Billing::Error) do + @gateway.store(check(:account_number => '123', :number => nil)) + end + assert_equal('Bogus Gateway: Use bank account number ending in 1 for success, 2 for exception and anything else for error', e.message) + end + + def test_credit_with_check + assert @gateway.credit(1000, check(:account_number => CHECK_SUCCESS_PLACEHOLDER, :number => nil)).success? + assert !@gateway.credit(1000, check(:account_number => CHECK_FAILURE_PLACEHOLDER, :number => nil)).success? + e = assert_raises(ActiveMerchant::Billing::Error) do + @gateway.credit(1000, check(:account_number => '123', :number => nil)) + end + assert_equal('Bogus Gateway: Use bank account number ending in 1 for success, 2 for exception and anything else for error', e.message) + end + + def test_store_then_purchase_with_check + reference = @gateway.store(check(:account_number => CHECK_SUCCESS_PLACEHOLDER, :number => nil)) + assert @gateway.purchase(1000, reference.authorization).success? + end + + def test_authorize_emv + approve_response = @gateway.authorize(1000, credit_card('123', {icc_data: 'DEADBEEF'})) + assert approve_response.success? + assert_equal '8A023030', approve_response.emv_authorization + decline_response = @gateway.authorize(1005, credit_card('123', {icc_data: 'DEADBEEF'})) + refute decline_response.success? + assert_equal Gateway::STANDARD_ERROR_CODE[:processing_error], decline_response.error_code + assert_equal '8A023035', decline_response.emv_authorization + e = assert_raises(ActiveMerchant::Billing::Error) do + @gateway.authorize(1001, credit_card('123', {icc_data: 'DEADBEEF'})) + end + assert_equal('Bogus Gateway: Use amount ending in 00 for success, 05 for failure and anything else for exception', e.message) + end + + def test_purchase_emv + assert @gateway.purchase(1000, credit_card('123', {icc_data: 'DEADBEEF'})).success? + response = @gateway.purchase(1005, credit_card('123', {icc_data: 'DEADBEEF'})) + refute response.success? + assert_equal Gateway::STANDARD_ERROR_CODE[:processing_error], response.error_code + e = assert_raises(ActiveMerchant::Billing::Error) do + @gateway.purchase(1001, credit_card('123', {icc_data: 'DEADBEEF'})) + end + assert_equal('Bogus Gateway: Use amount ending in 00 for success, 05 for failure and anything else for exception', e.message) + end end diff --git a/test/unit/gateways/borgun_test.rb b/test/unit/gateways/borgun_test.rb new file mode 100644 index 00000000000..bd38587664a --- /dev/null +++ b/test/unit/gateways/borgun_test.rb @@ -0,0 +1,313 @@ +require 'test_helper' + +class BorgunTest < Test::Unit::TestCase + include CommStub + + def setup + @gateway = BorgunGateway.new( + processor: '118', + merchant_id: '118', + username: 'dude', + password: 'secret' + ) + + @credit_card = credit_card + @amount = 100 + + @options = { + order_id: '1', + billing_address: address, + description: 'Store Purchase', + terminal_id: '3' + } + end + + def test_successful_purchase + @gateway.expects(:ssl_post).returns(successful_purchase_response) + + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + + assert_equal '140216103700|11|15|WC0000000001|123456|1|000000012300|978', response.authorization + assert response.test? + end + + def test_failed_purchase + @gateway.expects(:ssl_post).returns(failed_purchase_response) + + response = @gateway.purchase(@amount, @credit_card, @options) + assert_failure response + end + + def test_authorize_and_capture + response = stub_comms do + @gateway.authorize(@amount, @credit_card) + end.respond_with(successful_authorize_response) + + assert_success response + assert_equal '140601083732|11|18|WC0000000001|123456|5|000000012300|978', response.authorization + + capture = stub_comms do + @gateway.capture(@amount, response.authorization) + end.check_request do |endpoint, data, headers| + assert_match(/140601083732/, data) + end.respond_with(successful_capture_response) + + assert_success capture + end + + def test_refund + response = stub_comms do + @gateway.purchase(@amount, @credit_card) + end.respond_with(successful_purchase_response) + + assert_success response + assert_equal '140216103700|11|15|WC0000000001|123456|1|000000012300|978', response.authorization + + refund = stub_comms do + @gateway.refund(@amount, response.authorization) + end.check_request do |endpoint, data, headers| + assert_match(/140216103700/, data) + end.respond_with(successful_refund_response) + + assert_success refund + end + + def test_void + response = stub_comms do + @gateway.purchase(@amount, @credit_card) + end.respond_with(successful_purchase_response) + + assert_success response + assert_equal '140216103700|11|15|WC0000000001|123456|1|000000012300|978', response.authorization + + refund = stub_comms do + @gateway.void(response.authorization) + end.check_request do |endpoint, data, headers| + assert_match(/140216103700/, data) + end.respond_with(successful_void_response) + + assert_success refund + end + + def test_passing_cvv + stub_comms do + @gateway.purchase(@amount, @credit_card) + end.check_request do |endpoint, data, headers| + assert_match(/#{@credit_card.verification_value}/, data) + end.respond_with(successful_purchase_response) + end + + def test_passing_terminal_id + stub_comms do + @gateway.purchase(@amount, @credit_card, { terminal_id: '3' }) + end.check_request do |endpoint, data, headers| + assert_match(/TerminalID&gt;3/, data) + end.respond_with(successful_purchase_response) + end + + def test_transcript_scrubbing + assert_equal scrubbed_transcript, @gateway.scrub(transcript) + end + + private + + def successful_purchase_response + %( + <?xml version="1.0" encoding="UTF-8"?> + <SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <SOAP-ENV:Header xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"></SOAP-ENV:Header><SOAP-ENV:Body> + <ser-root:getAuthorizationOutput xmlns:ser-root="http://Borgun/Heimir/pub/ws/Authorization" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <getAuthResXml>&lt;?xml version="1.0" encoding="iso-8859-1"?&gt; + &lt;getAuthorizationReply&gt; + &lt;Version&gt;1000&lt;/Version&gt; + &lt;Processor&gt;118&lt;/Processor&gt; + &lt;MerchantID&gt;118&lt;/MerchantID&gt; + &lt;TerminalID&gt;1&lt;/TerminalID&gt; + &lt;TransType&gt;1&lt;/TransType&gt; + &lt;TrAmount&gt;000000012300&lt;/TrAmount&gt; + &lt;TrCurrency&gt;978&lt;/TrCurrency&gt; + &lt;DateAndTime&gt;140216103700&lt;/DateAndTime&gt; + &lt;PAN&gt;4507280000053760&lt;/PAN&gt; + &lt;RRN&gt;WC0000000001&lt;/RRN&gt; + &lt;Transaction&gt;15&lt;/Transaction&gt; + &lt;Batch&gt;11&lt;/Batch&gt; + &lt;CardAccId&gt;9256684&lt;/CardAccId&gt; + &lt;CardAccName&gt;Spreedly\Armuli 30\Reykjavik\108\\IS&lt;/CardAccName&gt; + &lt;AuthCode&gt;123456&lt;/AuthCode&gt; + &lt;ActionCode&gt;000&lt;/ActionCode&gt; + &lt;StoreTerminal&gt;00010001&lt;/StoreTerminal&gt; + &lt;CardType&gt;Visa&lt;/CardType&gt; + &lt;/getAuthorizationReply&gt;</getAuthResXml> + </ser-root:getAuthorizationOutput></SOAP-ENV:Body> + </SOAP-ENV:Envelope> + ) + end + + def successful_authorize_response + %( + <?xml version="1.0" encoding="UTF-8"?> + <SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <SOAP-ENV:Header xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"></SOAP-ENV:Header><SOAP-ENV:Body> + <ser-root:getAuthorizationOutput xmlns:ser-root="http://Borgun/Heimir/pub/ws/Authorization" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <getAuthResXml>&lt;?xml version="1.0" encoding="utf-8"?&gt; + &lt;getAuthorizationReply&gt; + &lt;Version&gt;1000&lt;/Version&gt; + &lt;Processor&gt;118&lt;/Processor&gt; + &lt;MerchantID&gt;118&lt;/MerchantID&gt; + &lt;TerminalID&gt;1&lt;/TerminalID&gt; + &lt;TransType&gt;5&lt;/TransType&gt; + &lt;TrAmount&gt;000000012300&lt;/TrAmount&gt; + &lt;TrCurrency&gt;978&lt;/TrCurrency&gt; + &lt;DateAndTime&gt;140601083732&lt;/DateAndTime&gt; + &lt;PAN&gt;4507280000053760&lt;/PAN&gt; + &lt;RRN&gt;WC0000000001&lt;/RRN&gt; + &lt;Transaction&gt;18&lt;/Transaction&gt; + &lt;Batch&gt;11&lt;/Batch&gt; + &lt;CardAccId&gt;9256684&lt;/CardAccId&gt; + &lt;CardAccName&gt;Spreedly\Armuli 30\Reykjavik\108\\IS&lt;/CardAccName&gt; + &lt;AuthCode&gt;123456&lt;/AuthCode&gt; + &lt;ActionCode&gt;000&lt;/ActionCode&gt; + &lt;StoreTerminal&gt;00010001&lt;/StoreTerminal&gt; + &lt;CardType&gt;Visa&lt;/CardType&gt; + &lt;/getAuthorizationReply&gt;</getAuthResXml> + </ser-root:getAuthorizationOutput></SOAP-ENV:Body> + </SOAP-ENV:Envelope> + ) + end + + def successful_capture_response + %( + <?xml version="1.0" encoding="UTF-8"?> + <SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <SOAP-ENV:Header xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"></SOAP-ENV:Header><SOAP-ENV:Body> + <ser-root:getAuthorizationOutput xmlns:ser-root="http://Borgun/Heimir/pub/ws/Authorization" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <getAuthResXml>&lt;?xml version="1.0" encoding="utf-8"?&gt; + &lt;getAuthorizationReply&gt; + &lt;Version&gt;1000&lt;/Version&gt; + &lt;Processor&gt;118&lt;/Processor&gt; + &lt;MerchantID&gt;118&lt;/MerchantID&gt; + &lt;TerminalID&gt;1&lt;/TerminalID&gt; + &lt;TransType&gt;1&lt;/TransType&gt; + &lt;TrAmount&gt;100000&lt;/TrAmount&gt; + &lt;TrCurrency&gt;352&lt;/TrCurrency&gt; + &lt;DateAndTime&gt;140501083700&lt;/DateAndTime&gt; + &lt;PAN&gt;5587402000012011&lt;/PAN&gt; + &lt;RRN&gt;WC0000000001&lt;/RRN&gt; + &lt;Transaction&gt;57&lt;/Transaction&gt; + &lt;Batch&gt;11&lt;/Batch&gt; + &lt;CardAccId/&gt; + &lt;CardAccName&gt;Spreedly\Armuli 30\Reykjavik\108\\IS&lt;/CardAccName&gt; + &lt;AuthCode&gt;048454&lt;/AuthCode&gt; + &lt;ActionCode&gt;000&lt;/ActionCode&gt; + &lt;StoreTerminal&gt;00010001&lt;/StoreTerminal&gt; + &lt;CardType&gt;MasterCard&lt;/CardType&gt; + &lt;/getAuthorizationReply&gt;</getAuthResXml> + </ser-root:getAuthorizationOutput></SOAP-ENV:Body> + </SOAP-ENV:Envelope> + ) + end + + def successful_refund_response + %( + <?xml version="1.0" encoding="UTF-8"?> + <SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <SOAP-ENV:Header xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"></SOAP-ENV:Header><SOAP-ENV:Body> + <ser-root:getAuthorizationOutput xmlns:ser-root="http://Borgun/Heimir/pub/ws/Authorization" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <getAuthResXml>&lt;?xml version="1.0" encoding="iso-8859-1"?&gt; + &lt;getAuthorizationReply&gt; + &lt;Version&gt;1000&lt;/Version&gt; + &lt;Processor&gt;118&lt;/Processor&gt; + &lt;MerchantID&gt;118&lt;/MerchantID&gt; + &lt;TerminalID&gt;1&lt;/TerminalID&gt; + &lt;TransType&gt;3&lt;/TransType&gt; + &lt;TrAmount&gt;100000&lt;/TrAmount&gt; + &lt;TrCurrency&gt;352&lt;/TrCurrency&gt; + &lt;DateAndTime&gt;140216103701&lt;/DateAndTime&gt; + &lt;PAN&gt;5587402000012011&lt;/PAN&gt; + &lt;RRN&gt;WC0000000001&lt;/RRN&gt; + &lt;Transaction&gt;54&lt;/Transaction&gt; + &lt;Batch&gt;11&lt;/Batch&gt; + &lt;CardAccId&gt;9256684&lt;/CardAccId&gt; + &lt;CardAccName&gt;Spreedly\Armuli 30\Reykjavik\108\\IS&lt;/CardAccName&gt; + &lt;AuthCode&gt;048443&lt;/AuthCode&gt; + &lt;ActionCode&gt;000&lt;/ActionCode&gt; + &lt;StoreTerminal&gt;00010001&lt;/StoreTerminal&gt; + &lt;CardType&gt;MasterCard&lt;/CardType&gt; + &lt;/getAuthorizationReply&gt;</getAuthResXml> + </ser-root:getAuthorizationOutput></SOAP-ENV:Body> + </SOAP-ENV:Envelope> + ) + end + + def failed_purchase_response + %( + <?xml version="1.0" encoding="UTF-8"?> + <SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <SOAP-ENV:Header xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"></SOAP-ENV:Header><SOAP-ENV:Body> + <ser-root:getAuthorizationOutput xmlns:ser-root="http://Borgun/Heimir/pub/ws/Authorization" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <getAuthResXml>&lt;?xml version="1.0" encoding="iso-8859-1"?&gt; + &lt;getAuthorizationReply&gt; + &lt;TrAmount&gt;000000012300&lt;/TrAmount&gt; + &lt;TrCurrency&gt;978&lt;/TrCurrency&gt; + &lt;DateAndTime&gt;140216103700&lt;/DateAndTime&gt; + &lt;PAN&gt;6799999999593&lt;/PAN&gt; + &lt;RRN&gt;WC0000000001&lt;/RRN&gt; + &lt;CardAccId&gt;9256684&lt;/CardAccId&gt; + &lt;CardAccName&gt;Spreedly\Armuli 30\Reykjavik\108\\IS&lt;/CardAccName&gt; + &lt;AuthCode&gt;123456&lt;/AuthCode&gt; + &lt;ActionCode&gt;111&lt;/ActionCode&gt; + &lt;StoreTerminal&gt;00010001&lt;/StoreTerminal&gt; + &lt;CardType&gt;MasterCard&lt;/CardType&gt; + &lt;/getAuthorizationReply&gt;</getAuthResXml> + </ser-root:getAuthorizationOutput></SOAP-ENV:Body> + </SOAP-ENV:Envelope> + ) + end + + def successful_void_response + %( + <?xml version="1.0" encoding="UTF-8"?> + <SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <SOAP-ENV:Header xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"></SOAP-ENV:Header><SOAP-ENV:Body> + <ser-root:cancelAuthorizationResponse xmlns:ser-root="http://Borgun/Heimir/pub/ws/Authorization" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <cancelAuthResXml>&lt;?xml version="1.0" encoding="iso-8859-1"?&gt; + &lt;cancelAuthorizationReply&gt; + &lt;Version&gt;1000&lt;/Version&gt; + &lt;Processor&gt;118&lt;/Processor&gt; + &lt;MerchantID&gt;118&lt;/MerchantID&gt; + &lt;TerminalID&gt;1&lt;/TerminalID&gt; + &lt;TransType&gt;5&lt;/TransType&gt; + &lt;TrAmount&gt;000000012300&lt;/TrAmount&gt; + &lt;TrCurrency&gt;978&lt;/TrCurrency&gt; + &lt;DateAndTime&gt;140501083806&lt;/DateAndTime&gt; + &lt;PAN&gt;5587402000012011&lt;/PAN&gt; + &lt;RRN&gt;WC0000000001&lt;/RRN&gt; + &lt;Transaction&gt;184&lt;/Transaction&gt; + &lt;Batch&gt;11&lt;/Batch&gt; + &lt;CardAccName&gt;Spreedly\Armuli 30\Reykjavik\108\\352&lt;/CardAccName&gt; + &lt;AuthCode&gt;057457&lt;/AuthCode&gt; + &lt;ActionCode&gt;000&lt;/ActionCode&gt; + &lt;StoreTerminal&gt;00010001&lt;/StoreTerminal&gt; + &lt;/cancelAuthorizationReply&gt;</cancelAuthResXml> + </ser-root:cancelAuthorizationResponse></SOAP-ENV:Body> + </SOAP-ENV:Envelope> + ) + end + + def transcript + <<-PRE_SCRUBBED + <- "POST /ws/Heimir.pub.ws:Authorization HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAuthorization: Basic yyyyyyyyyyyyyyyyyyyyyyyyyy==\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: gateway01.borgun.is\r\nContent-Length: 1220\r\n\r\n" + <- " <soapenv:Envelope xmlns:soapenv=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:aut=\"http://Borgun/Heimir/pub/ws/Authorization\">\n <soapenv:Header/>\n <soapenv:Body>\n <aut:getAuthorizationInput>\n <getAuthReqXml>\n &lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;\n&lt;getAuthorization&gt;\n &lt;TransType&gt;1&lt;/TransType&gt;\n &lt;TrAmount&gt;12600&lt;/TrAmount&gt;\n &lt;TrCurrency&gt;978&lt;/TrCurrency&gt;\n &lt;PAN&gt;4111111111111111&lt;/PAN&gt;\n &lt;ExpDate&gt;1705&lt;/ExpDate&gt;\n &lt;CVC2&gt;123&lt;/CVC2&gt;\n &lt;DateAndTime&gt;141110215924&lt;/DateAndTime&gt;\n &lt;RRN&gt;AMRCNT158463&lt;/RRN&gt;\n &lt;Version&gt;1000&lt;/Version&gt;\n &lt;Processor&gt;938&lt;/Processor&gt;\n &lt;MerchantID&gt;938&lt;/MerchantID&gt;\n &lt;TerminalID&gt;1&lt;/TerminalID&gt;\n&lt;/getAuthorization&gt;\n\n </getAuthReqXml>\n </aut:getAuthorizationInput>\n </soapenv:Body>\n </soapenv:Envelope>\n" + -> "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<SOAP-ENV:Envelope xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:SOAP-ENC=\"http://schemas.xmlsoap.org/soap/encoding/\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">\n<SOAP-ENV:Header xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\"></SOAP-ENV:Header><SOAP-ENV:Body>\n<ser-root:getAuthorizationOutput xmlns:ser-root=\"http://Borgun/Heimir/pub/ws/Authorization\">\n <getAuthResXml>&lt;?xml version=\"1.0\" encoding=\"iso-8859-1\"?&gt;\n&lt;getAuthorizationReply&gt;\n &lt;Version&gt;1000&lt;/Version&gt;\n &lt;Processor&gt;938&lt;/Processor&gt;\n &lt;MerchantID&gt;938&lt;/MerchantID&gt;\n &lt;TerminalID&gt;1&lt;/TerminalID&gt;\n &lt;TransType&gt;1&lt;/TransType&gt;\n &lt;TrAmount&gt;000000012600&lt;/TrAmount&gt;\n &lt;TrCurrency&gt;978&lt;/TrCurrency&gt;\n &lt;DateAndTime&gt;141110215924&lt;/DateAndTime&gt;\n &lt;PAN&gt;4111111111111111&lt;/PAN&gt;\n &lt;RRN&gt;AMRCNT158463&lt;/RRN&gt;\n &lt;Transaction&gt;22&lt;/Transaction&gt;\n &lt;Batch&gt;263&lt;/Batch&gt;\n &lt;CVCResult&gt;M&lt;/CVCResult&gt;\n &lt;CardAccId&gt;9858674&lt;/CardAccId&gt;\n &lt;CardAccName&gt;Longbob+Longsen\xC3\xADk\\101\\\\IS&lt;/CardAccName&gt;\n &lt;AuthCode&gt;114031&lt;/AuthCode&gt;\n &lt;ActionCode&gt;000&lt;/ActionCode&gt;\n &lt;StoreTerminal&gt;00010001&lt;/StoreTerminal&gt;\n &lt;CardType&gt;Visa&lt;/CardType&gt;\n&lt;/getAuthorizationReply&gt;</getAuthResXml>\n</ser-root:getAuthorizationOutput></SOAP-ENV:Body>\n</SOAP-ENV:Envelope>\n" + PRE_SCRUBBED + end + + def scrubbed_transcript + <<-POST_SCRUBBED + <- "POST /ws/Heimir.pub.ws:Authorization HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAuthorization: Basic [FILTERED]\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: gateway01.borgun.is\r\nContent-Length: 1220\r\n\r\n" + <- " <soapenv:Envelope xmlns:soapenv=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:aut=\"http://Borgun/Heimir/pub/ws/Authorization\">\n <soapenv:Header/>\n <soapenv:Body>\n <aut:getAuthorizationInput>\n <getAuthReqXml>\n &lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;\n&lt;getAuthorization&gt;\n &lt;TransType&gt;1&lt;/TransType&gt;\n &lt;TrAmount&gt;12600&lt;/TrAmount&gt;\n &lt;TrCurrency&gt;978&lt;/TrCurrency&gt;\n &lt;PAN&gt;[FILTERED]&lt;/PAN&gt;\n &lt;ExpDate&gt;1705&lt;/ExpDate&gt;\n &lt;CVC2&gt;[FILTERED]&lt;/CVC2&gt;\n &lt;DateAndTime&gt;141110215924&lt;/DateAndTime&gt;\n &lt;RRN&gt;AMRCNT158463&lt;/RRN&gt;\n &lt;Version&gt;1000&lt;/Version&gt;\n &lt;Processor&gt;938&lt;/Processor&gt;\n &lt;MerchantID&gt;938&lt;/MerchantID&gt;\n &lt;TerminalID&gt;1&lt;/TerminalID&gt;\n&lt;/getAuthorization&gt;\n\n </getAuthReqXml>\n </aut:getAuthorizationInput>\n </soapenv:Body>\n </soapenv:Envelope>\n" + -> "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<SOAP-ENV:Envelope xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:SOAP-ENC=\"http://schemas.xmlsoap.org/soap/encoding/\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">\n<SOAP-ENV:Header xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\"></SOAP-ENV:Header><SOAP-ENV:Body>\n<ser-root:getAuthorizationOutput xmlns:ser-root=\"http://Borgun/Heimir/pub/ws/Authorization\">\n <getAuthResXml>&lt;?xml version=\"1.0\" encoding=\"iso-8859-1\"?&gt;\n&lt;getAuthorizationReply&gt;\n &lt;Version&gt;1000&lt;/Version&gt;\n &lt;Processor&gt;938&lt;/Processor&gt;\n &lt;MerchantID&gt;938&lt;/MerchantID&gt;\n &lt;TerminalID&gt;1&lt;/TerminalID&gt;\n &lt;TransType&gt;1&lt;/TransType&gt;\n &lt;TrAmount&gt;000000012600&lt;/TrAmount&gt;\n &lt;TrCurrency&gt;978&lt;/TrCurrency&gt;\n &lt;DateAndTime&gt;141110215924&lt;/DateAndTime&gt;\n &lt;PAN&gt;[FILTERED]&lt;/PAN&gt;\n &lt;RRN&gt;AMRCNT158463&lt;/RRN&gt;\n &lt;Transaction&gt;22&lt;/Transaction&gt;\n &lt;Batch&gt;263&lt;/Batch&gt;\n &lt;CVCResult&gt;M&lt;/CVCResult&gt;\n &lt;CardAccId&gt;9858674&lt;/CardAccId&gt;\n &lt;CardAccName&gt;Longbob+Longsen\xC3\xADk\\101\\\\IS&lt;/CardAccName&gt;\n &lt;AuthCode&gt;114031&lt;/AuthCode&gt;\n &lt;ActionCode&gt;000&lt;/ActionCode&gt;\n &lt;StoreTerminal&gt;00010001&lt;/StoreTerminal&gt;\n &lt;CardType&gt;Visa&lt;/CardType&gt;\n&lt;/getAuthorizationReply&gt;</getAuthResXml>\n</ser-root:getAuthorizationOutput></SOAP-ENV:Body>\n</SOAP-ENV:Envelope>\n" + POST_SCRUBBED + end +end diff --git a/test/unit/gateways/bpoint_test.rb b/test/unit/gateways/bpoint_test.rb new file mode 100644 index 00000000000..3ee09b8c0e1 --- /dev/null +++ b/test/unit/gateways/bpoint_test.rb @@ -0,0 +1,446 @@ +require 'test_helper' + +class BpointTest < Test::Unit::TestCase + include CommStub + + def setup + @gateway = BpointGateway.new( + username: '', + password: '', + merchant_number: '' + ) + + @credit_card = credit_card + @amount = 100 + + @options = { + order_id: '1', + billing_address: address, + description: 'Store Purchase' + } + end + + def test_successful_store + @gateway.expects(:ssl_post).returns(successful_store_response) + response = @gateway.store(@credit_card) + assert_success response + end + + def test_failed_store + @gateway.expects(:ssl_post).returns(failed_store_response) + response = @gateway.store(@credit_card) + assert_failure response + end + + def test_successful_purchase + @gateway.expects(:ssl_post).returns(successful_purchase_response) + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + + assert_equal '218990188', response.authorization + assert response.test? + end + + def test_failed_purchase + @gateway.expects(:ssl_post).returns(failed_purchase_response) + + response = @gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_equal 'Declined', response.message + end + + def test_successful_authorize + @gateway.expects(:ssl_post).returns(successful_authorize_response) + response = @gateway.authorize(@amount, @credit_card, @options) + assert_success response + assert_equal '219388558', response.authorization + end + + def test_failed_authorize + @gateway.expects(:ssl_post).returns(failed_authorize_response) + response = @gateway.authorize(@amount, @credit_card, @options) + assert_failure response + end + + def test_successful_capture + @gateway.expects(:ssl_post).returns(successful_capture_response) + response = @gateway.capture(@amount, '') + assert_success response + end + + def test_failed_capture + @gateway.expects(:ssl_post).returns(failed_capture_response) + response = @gateway.capture(@amount, '') + assert_failure response + end + + def test_successful_refund + @gateway.expects(:ssl_post).returns(successful_refund_response) + response = @gateway.capture(@amount, '') + assert_success response + end + + def test_failed_refund + @gateway.expects(:ssl_post).returns(failed_capture_response) + response = @gateway.refund(@amount, '') + assert_failure response + end + + def test_successful_void + @gateway.expects(:ssl_post).returns(successful_void_response) + response = @gateway.void(@amount, '') + assert_success response + end + + def test_failed_void + @gateway.expects(:ssl_post).returns(failed_void_response) + response = @gateway.void(@amount, '') + assert_failure response + end + + def test_successful_verify + @gateway.expects(:ssl_post).times(2).returns(successful_verify_response) + response = @gateway.verify(@credit_card) + assert_success response + end + + def test_successful_verify_with_failed_void + response = stub_comms do + @gateway.verify(@credit_card) + end.respond_with(successful_authorize_response, failed_void_response) + assert_success response + end + + def test_failed_verify + @gateway.expects(:ssl_post).returns(failed_verify_response) + response = @gateway.verify(@credit_card) + assert_failure response + end + + def test_scrub + assert @gateway.supports_scrubbing? + assert_equal @gateway.scrub(pre_scrubbed), post_scrubbed + end + + def test_passing_biller_code + stub_comms do + @gateway.authorize(@amount, @credit_card, { biller_code: '1234' }) + end.check_request do |endpoint, data, headers| + assert_match(%r(<BillerCode>1234</BillerCode>)m, data) + end.respond_with(successful_authorize_response) + end + + def test_passing_reference_and_crn + stub_comms do + @gateway.authorize(@amount, @credit_card, @options.merge({ crn1: 'ref' })) + end.check_request do |endpoint, data, headers| + assert_match(%r(<MerchantReference>1</MerchantReference>)m, data) + assert_match(%r(<CRN1>ref</CRN1>)m, data) + end.respond_with(successful_authorize_response) + end + + private + + def pre_scrubbed + <<-PRE_SCRUBBED + <- "POST /evolve/service_1_4_4.asmx HTTP/1.1\r\nContent-Type: application/soap+xml; charset=utf-8\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: www.bpoint.com.au\r\nContent-Length: 843\r\n\r\n" + <- "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<soap12:Envelope xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:soap12=\"http://www.w3.org/2003/05/soap-envelope\">\n <soap12:Body>\n <ProcessPayment xmlns=\"urn:Eve_1_4_4\">\n <username>waysact</username>\n <password>O5dIyDv148</password>\n <merchantNumber>DEMONSTRATION731</merchantNumber>\n <txnReq>\n <PaymentType>PAYMENT</PaymentType>\n <TxnType>WEB_SHOP</TxnType>\n <BillerCode/>\n <MerchantReference/>\n <CRN1/>\n <CRN2/>\n <CRN3/>\n <Amount>100</Amount>\n <CardNumber>4987654321098769</CardNumber>\n <ExpiryDate>9900</ExpiryDate>\n <CVC>123</CVC>\n <OriginalTransactionNumber/>\n </txnReq>\n </ProcessPayment>\n </soap12:Body>\n</soap12:Envelope>\n" + -> "<?xml version=\"1.0\" encoding=\"utf-8\"?><soap:Envelope xmlns:soap=\"http://www.w3.org/2003/05/soap-envelope\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"><soap:Body><ProcessPaymentResponse xmlns=\"urn:Eve_1_4_4\"><ProcessPaymentResult><ResponseCode>0</ResponseCode><AcquirerResponseCode>00</AcquirerResponseCode><AuthorisationResult>Approved</AuthorisationResult><TransactionNumber>219617445</TransactionNumber><ReceiptNumber>53559987445</ReceiptNumber><AuthoriseId>122025580862</AuthoriseId><SettlementDate>20150513</SettlementDate><MaskedCardNumber>498765...769</MaskedCardNumber><CardType>VC</CardType></ProcessPaymentResult><response><ResponseCode>SUCCESS</ResponseCode></response></ProcessPaymentResponse></soap:Body></soap:Envelope>" + PRE_SCRUBBED + end + + def post_scrubbed + <<-POST_SCRUBBED + <- "POST /evolve/service_1_4_4.asmx HTTP/1.1\r\nContent-Type: application/soap+xml; charset=utf-8\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: www.bpoint.com.au\r\nContent-Length: 843\r\n\r\n" + <- "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<soap12:Envelope xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:soap12=\"http://www.w3.org/2003/05/soap-envelope\">\n <soap12:Body>\n <ProcessPayment xmlns=\"urn:Eve_1_4_4\">\n <username>waysact</username>\n <password>[FILTERED]</password>\n <merchantNumber>DEMONSTRATION731</merchantNumber>\n <txnReq>\n <PaymentType>PAYMENT</PaymentType>\n <TxnType>WEB_SHOP</TxnType>\n <BillerCode/>\n <MerchantReference/>\n <CRN1/>\n <CRN2/>\n <CRN3/>\n <Amount>100</Amount>\n <CardNumber>[FILTERED]</CardNumber>\n <ExpiryDate>9900</ExpiryDate>\n <CVC>[FILTERED]</CVC>\n <OriginalTransactionNumber/>\n </txnReq>\n </ProcessPayment>\n </soap12:Body>\n</soap12:Envelope>\n" + -> "<?xml version=\"1.0\" encoding=\"utf-8\"?><soap:Envelope xmlns:soap=\"http://www.w3.org/2003/05/soap-envelope\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"><soap:Body><ProcessPaymentResponse xmlns=\"urn:Eve_1_4_4\"><ProcessPaymentResult><ResponseCode>0</ResponseCode><AcquirerResponseCode>00</AcquirerResponseCode><AuthorisationResult>Approved</AuthorisationResult><TransactionNumber>219617445</TransactionNumber><ReceiptNumber>53559987445</ReceiptNumber><AuthoriseId>122025580862</AuthoriseId><SettlementDate>20150513</SettlementDate><MaskedCardNumber>498765...769</MaskedCardNumber><CardType>VC</CardType></ProcessPaymentResult><response><ResponseCode>SUCCESS</ResponseCode></response></ProcessPaymentResponse></soap:Body></soap:Envelope>" + POST_SCRUBBED + end + + def successful_purchase_response + %( + <?xml version="1.0" encoding="UTF-8"?> + <soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <soap:Body> + <ProcessPaymentResponse xmlns="urn:Eve_1_4_4"> + <ProcessPaymentResult> + <ResponseCode>0</ResponseCode> + <AcquirerResponseCode>00</AcquirerResponseCode> + <AuthorisationResult>Approved</AuthorisationResult> + <TransactionNumber>218990188</TransactionNumber> + <ReceiptNumber>53440560188</ReceiptNumber> + <AuthoriseId>081017039863</AuthoriseId> + <SettlementDate>20150509</SettlementDate> + <MaskedCardNumber>498765...769</MaskedCardNumber> + <CardType>VC</CardType> + </ProcessPaymentResult> + <response> + <ResponseCode>SUCCESS</ResponseCode> + </response> + </ProcessPaymentResponse> + </soap:Body> + </soap:Envelope> + ) + end + + def failed_purchase_response + %( + <?xml version="1.0" encoding="UTF-8"?> + <soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <soap:Body> + <ProcessPaymentResponse xmlns="urn:Eve_1_4_4"> + <ProcessPaymentResult> + <ResponseCode>2</ResponseCode> + <AcquirerResponseCode>01</AcquirerResponseCode> + <AuthorisationResult>Declined</AuthorisationResult> + <TransactionNumber>219013928</TransactionNumber> + <ReceiptNumber>53452203928</ReceiptNumber> + <AuthoriseId /> + <SettlementDate>20150509</SettlementDate> + <MaskedCardNumber>498765...769</MaskedCardNumber> + <CardType>VC</CardType> + </ProcessPaymentResult> + <response> + <ResponseCode>SUCCESS</ResponseCode> + </response> + </ProcessPaymentResponse> + </soap:Body> + </soap:Envelope> + ) + end + + def successful_authorize_response + %( + <?xml version="1.0" encoding="UTF-8"?> + <soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <soap:Body> + <ProcessPaymentResponse xmlns="urn:Eve_1_4_4"> + <ProcessPaymentResult> + <ResponseCode>0</ResponseCode> + <AcquirerResponseCode>00</AcquirerResponseCode> + <AuthorisationResult>Approved</AuthorisationResult> + <TransactionNumber>219388558</TransactionNumber> + <ReceiptNumber>53530098558</ReceiptNumber> + <AuthoriseId>111751554356</AuthoriseId> + <SettlementDate>20150512</SettlementDate> + <MaskedCardNumber>498765...769</MaskedCardNumber> + <CardType>VC</CardType> + </ProcessPaymentResult> + <response> + <ResponseCode>SUCCESS</ResponseCode> + </response> + </ProcessPaymentResponse> + </soap:Body> + </soap:Envelope> + ) + end + alias_method :successful_verify_response, :successful_authorize_response + + def failed_authorize_response + %( + <?xml version="1.0" encoding="UTF-8"?> + <soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <soap:Body> + <ProcessPaymentResponse xmlns="urn:Eve_1_4_4"> + <ProcessPaymentResult> + <ResponseCode>2</ResponseCode> + <AcquirerResponseCode>01</AcquirerResponseCode> + <AuthorisationResult>Declined</AuthorisationResult> + <TransactionNumber>219389176</TransactionNumber> + <ReceiptNumber>53530629176</ReceiptNumber> + <AuthoriseId /> + <SettlementDate>20150512</SettlementDate> + <MaskedCardNumber>498765...769</MaskedCardNumber> + <CardType>VC</CardType> + </ProcessPaymentResult> + <response> + <ResponseCode>SUCCESS</ResponseCode> + </response> + </ProcessPaymentResponse> + </soap:Body> + </soap:Envelope> + ) + end + alias_method :failed_verify_response, :failed_authorize_response + + def successful_capture_response + %( + <?xml version="1.0" encoding="UTF-8"?> + <soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <soap:Body> + <ProcessPaymentResponse xmlns="urn:Eve_1_4_4"> + <ProcessPaymentResult> + <ResponseCode>0</ResponseCode> + <AcquirerResponseCode>00</AcquirerResponseCode> + <AuthorisationResult>Approved</AuthorisationResult> + <TransactionNumber>219389381</TransactionNumber> + <ReceiptNumber>53530769381</ReceiptNumber> + <AuthoriseId>111827122671</AuthoriseId> + <SettlementDate>20150512</SettlementDate> + <MaskedCardNumber>498765...769</MaskedCardNumber> + <CardType>VC</CardType> + </ProcessPaymentResult> + <response> + <ResponseCode>SUCCESS</ResponseCode> + </response> + </ProcessPaymentResponse> + </soap:Body> + </soap:Envelope> + ) + end + + def failed_capture_response + %( + <?xml version="1.0" encoding="UTF-8"?> + <soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <soap:Body> + <ProcessPaymentResponse xmlns="urn:Eve_1_4_4"> + <ProcessPaymentResult> + <ResponseCode>PT_R1</ResponseCode> + <AuthorisationResult>Original transaction not found</AuthorisationResult> + <TransactionNumber>219389566</TransactionNumber> + <ReceiptNumber>53530899566</ReceiptNumber> + <CardType /> + </ProcessPaymentResult> + <response> + <ResponseCode>SUCCESS</ResponseCode> + </response> + </ProcessPaymentResponse> + </soap:Body> + </soap:Envelope> + ) + end + + def successful_refund_response + %( + <?xml version="1.0" encoding="UTF-8"?> + <soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <soap:Body> + <ProcessPaymentResponse xmlns="urn:Eve_1_4_4"> + <ProcessPaymentResult> + <ResponseCode>0</ResponseCode> + <AcquirerResponseCode>00</AcquirerResponseCode> + <AuthorisationResult>Approved</AuthorisationResult> + <TransactionNumber>219391527</TransactionNumber> + <ReceiptNumber>53532101527</ReceiptNumber> + <AuthoriseId>111939009260</AuthoriseId> + <SettlementDate>20150512</SettlementDate> + <MaskedCardNumber>498765...769</MaskedCardNumber> + <CardType>VC</CardType> + </ProcessPaymentResult> + <response> + <ResponseCode>SUCCESS</ResponseCode> + </response> + </ProcessPaymentResponse> + </soap:Body> + </soap:Envelope> + ) + end + + def failed_refund_response + %( + <?xml version="1.0" encoding="UTF-8"?> + <soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <soap:Body> + <ProcessPaymentResponse xmlns="urn:Eve_1_4_4"> + <ProcessPaymentResult> + <ResponseCode>PT_R1</ResponseCode> + <AuthorisationResult>Original transaction not found</AuthorisationResult> + <TransactionNumber>219395831</TransactionNumber> + <ReceiptNumber>53533405831</ReceiptNumber> + <CardType /> + </ProcessPaymentResult> + <response> + <ResponseCode>SUCCESS</ResponseCode> + </response> + </ProcessPaymentResponse> + </soap:Body> + </soap:Envelope> + ) + end + + def successful_void_response + %( + <?xml version="1.0" encoding="UTF-8"?> + <soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <soap:Body> + <ProcessPaymentResponse xmlns="urn:Eve_1_4_4"> + <ProcessPaymentResult> + <ResponseCode>0</ResponseCode> + <AcquirerResponseCode>00</AcquirerResponseCode> + <AuthorisationResult>Approved</AuthorisationResult> + <TransactionNumber>219397643</TransactionNumber> + <ReceiptNumber>53533757643</ReceiptNumber> + <AuthoriseId>112107050623</AuthoriseId> + <SettlementDate>20150512</SettlementDate> + <MaskedCardNumber>498765...769</MaskedCardNumber> + <CardType>VC</CardType> + </ProcessPaymentResult> + <response> + <ResponseCode>SUCCESS</ResponseCode> + </response> + </ProcessPaymentResponse> + </soap:Body> + </soap:Envelope> + ) + end + + def failed_void_response + %( + <?xml version="1.0" encoding="UTF-8"?> + <soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <soap:Body> + <ProcessPaymentResponse xmlns="urn:Eve_1_4_4"> + <ProcessPaymentResult> + <ResponseCode>PT_R1</ResponseCode> + <AuthorisationResult>Original transaction not found</AuthorisationResult> + <TransactionNumber>219397820</TransactionNumber> + <ReceiptNumber>53533887820</ReceiptNumber> + <CardType /> + </ProcessPaymentResult> + <response> + <ResponseCode>SUCCESS</ResponseCode> + </response> + </ProcessPaymentResponse> + </soap:Body> + </soap:Envelope> + ) + end + + def successful_store_response + %( + <?xml version="1.0" encoding="UTF-8"?> + <soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <soap:Body> + <AddTokenResponse xmlns="urn:Eve_1_4_4"> + <AddTokenResult> + <Token>5999992142370790</Token> + <MaskedCardNumber>498765...769</MaskedCardNumber> + <CardType>VC</CardType> + </AddTokenResult> + <response> + <ResponseCode>SUCCESS</ResponseCode> + </response> + </AddTokenResponse> + </soap:Body> + </soap:Envelope> + ) + end + + def failed_store_response + %( + <?xml version="1.0" encoding="UTF-8"?> + <soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <soap:Body> + <AddTokenResponse xmlns="urn:Eve_1_4_4"> + <AddTokenResult /> + <response> + <ResponseCode>ERROR</ResponseCode> + <ResponseMessage>invalid card number: invalid length</ResponseMessage> + </response> + </AddTokenResponse> + </soap:Body> + </soap:Envelope> + ) + end +end diff --git a/test/unit/gateways/braintree_blue_test.rb b/test/unit/gateways/braintree_blue_test.rb index c855bacc57f..727fbd054d8 100644 --- a/test/unit/gateways/braintree_blue_test.rb +++ b/test/unit/gateways/braintree_blue_test.rb @@ -2,40 +2,133 @@ class BraintreeBlueTest < Test::Unit::TestCase def setup + @old_verbose, $VERBOSE = $VERBOSE, false + @gateway = BraintreeBlueGateway.new( :merchant_id => 'test', :public_key => 'test', - :private_key => 'test' + :private_key => 'test', + :test => true ) + + @internal_gateway = @gateway.instance_variable_get(:@braintree_gateway) + end + + def teardown + $VERBOSE = @old_verbose end def test_refund_legacy_method_signature - Braintree::Transaction.expects(:refund). + Braintree::TransactionGateway.any_instance.expects(:refund). with('transaction_id', nil). - returns(braintree_result(:id => "refund_transaction_id")) + returns(braintree_result(:id => 'refund_transaction_id')) response = @gateway.refund('transaction_id', :test => true) - assert_equal "refund_transaction_id", response.authorization + assert_equal 'refund_transaction_id', response.authorization end def test_refund_method_signature - Braintree::Transaction.expects(:refund). + Braintree::TransactionGateway.any_instance.expects(:refund). with('transaction_id', '10.00'). - returns(braintree_result(:id => "refund_transaction_id")) + returns(braintree_result(:id => 'refund_transaction_id')) response = @gateway.refund(1000, 'transaction_id', :test => true) - assert_equal "refund_transaction_id", response.authorization + assert_equal 'refund_transaction_id', response.authorization + end + + def test_transaction_uses_customer_id_by_default + Braintree::TransactionGateway.any_instance.expects(:sale). + with(has_entries(:customer_id => 'present')). + returns(braintree_result) + + assert response = @gateway.purchase(10, 'present', {}) + assert_instance_of Response, response + assert_success response + end + + def test_transaction_uses_payment_method_token_when_option + Braintree::TransactionGateway.any_instance.expects(:sale). + with(has_entries(:payment_method_token => 'present')). + returns(braintree_result) + + assert response = @gateway.purchase(10, 'present', { payment_method_token: true }) + assert_instance_of Response, response + assert_success response + end + + def test_transaction_uses_payment_method_nonce_when_option + Braintree::TransactionGateway.any_instance.expects(:sale). + with(has_entries(:payment_method_nonce => 'present')). + returns(braintree_result) + + assert response = @gateway.purchase(10, 'present', { payment_method_nonce: true }) + assert_instance_of Response, response + assert_success response + end + + def test_authorize_transaction + Braintree::TransactionGateway.any_instance.expects(:sale). + returns(braintree_result) + + response = @gateway.authorize(100, credit_card('41111111111111111111')) + + assert_equal 'transaction_id', response.authorization + assert_equal true, response.test + end + + def test_purchase_transaction + Braintree::TransactionGateway.any_instance.expects(:sale). + returns(braintree_result) + + response = @gateway.purchase(100, credit_card('41111111111111111111')) + + assert_equal 'transaction_id', response.authorization + assert_equal true, response.test + end + + def test_capture_transaction + Braintree::TransactionGateway.any_instance.expects(:submit_for_settlement). + returns(braintree_result(:id => 'capture_transaction_id')) + + response = @gateway.capture(100, 'transaction_id') + + assert_equal 'capture_transaction_id', response.authorization + assert_equal true, response.test + end + + def test_refund_transaction + Braintree::TransactionGateway.any_instance.expects(:refund). + returns(braintree_result(:id => 'refund_transaction_id')) + + response = @gateway.refund(1000, 'transaction_id') + assert_equal 'refund_transaction_id', response.authorization + assert_equal true, response.test end def test_void_transaction - Braintree::Transaction.expects(:void). + Braintree::TransactionGateway.any_instance.expects(:void). with('transaction_id'). - returns(braintree_result(:id => "void_transaction_id")) + returns(braintree_result(:id => 'void_transaction_id')) - response = @gateway.void('transaction_id', :test => true) - assert_equal "void_transaction_id", response.authorization + response = @gateway.void('transaction_id') + assert_equal 'void_transaction_id', response.authorization + assert_equal true, response.test + end + + def test_verify_good_credentials + Braintree::TransactionGateway.any_instance.expects(:find). + with('non_existent_token'). + raises(Braintree::NotFoundError) + assert @gateway.verify_credentials + end + + def test_verify_bad_credentials + Braintree::TransactionGateway.any_instance.expects(:find). + with('non_existent_token'). + raises(Braintree::AuthenticationError) + assert !@gateway.verify_credentials end def test_user_agent_includes_activemerchant_version - assert Braintree::Configuration.instantiate.user_agent.include?("(ActiveMerchant #{ActiveMerchant::VERSION})") + assert @internal_gateway.config.user_agent.include?("(ActiveMerchant #{ActiveMerchant::VERSION})") end def test_merchant_account_id_present_when_provided_on_gateway_initialization @@ -46,11 +139,11 @@ def test_merchant_account_id_present_when_provided_on_gateway_initialization :private_key => 'test' ) - Braintree::Transaction.expects(:sale). - with(has_entries(:merchant_account_id => "present")). + Braintree::TransactionGateway.any_instance.expects(:sale). + with(has_entries(:merchant_account_id => 'present')). returns(braintree_result) - @gateway.authorize(100, credit_card("41111111111111111111")) + @gateway.authorize(100, credit_card('41111111111111111111')) end def test_merchant_account_id_on_transaction_takes_precedence @@ -61,88 +154,193 @@ def test_merchant_account_id_on_transaction_takes_precedence :private_key => 'test' ) - Braintree::Transaction.expects(:sale). - with(has_entries(:merchant_account_id => "account_on_transaction")). + Braintree::TransactionGateway.any_instance.expects(:sale). + with(has_entries(:merchant_account_id => 'account_on_transaction')). returns(braintree_result) - @gateway.authorize(100, credit_card("41111111111111111111"), :merchant_account_id => "account_on_transaction") + @gateway.authorize(100, credit_card('41111111111111111111'), :merchant_account_id => 'account_on_transaction') end def test_merchant_account_id_present_when_provided - Braintree::Transaction.expects(:sale). - with(has_entries(:merchant_account_id => "present")). + Braintree::TransactionGateway.any_instance.expects(:sale). + with(has_entries(:merchant_account_id => 'present')). returns(braintree_result) - @gateway.authorize(100, credit_card("41111111111111111111"), :merchant_account_id => "present") + @gateway.authorize(100, credit_card('41111111111111111111'), :merchant_account_id => 'present') + end + + def test_service_fee_amount_can_be_specified + Braintree::TransactionGateway.any_instance.expects(:sale). + with(has_entries(:service_fee_amount => '2.31')). + returns(braintree_result) + + @gateway.authorize(100, credit_card('41111111111111111111'), :service_fee_amount => '2.31') + end + + def test_hold_in_escrow_can_be_specified + Braintree::TransactionGateway.any_instance.expects(:sale).with do |params| + (params[:options][:hold_in_escrow] == true) + end.returns(braintree_result) + + @gateway.authorize(100, credit_card('41111111111111111111'), :hold_in_escrow => true) end def test_merchant_account_id_absent_if_not_provided - Braintree::Transaction.expects(:sale).with do |params| + Braintree::TransactionGateway.any_instance.expects(:sale).with do |params| not params.has_key?(:merchant_account_id) end.returns(braintree_result) - @gateway.authorize(100, credit_card("41111111111111111111")) + @gateway.authorize(100, credit_card('41111111111111111111')) + end + + def test_verification_merchant_account_id_exists_when_verify_card_and_merchant_account_id + gateway = BraintreeBlueGateway.new( + :merchant_id => 'merchant_id', + :merchant_account_id => 'merchant_account_id', + :public_key => 'public_key', + :private_key => 'private_key' + ) + customer = stub( + :credit_cards => [stub_everything], + :email => 'email', + :phone => '321-654-0987', + :first_name => 'John', + :last_name => 'Smith' + ) + customer.stubs(:id).returns('123') + result = Braintree::SuccessfulResult.new(:customer => customer) + + Braintree::CustomerGateway.any_instance.expects(:create).with do |params| + params[:credit_card][:options][:verification_merchant_account_id] == 'merchant_account_id' + end.returns(result) + + gateway.store(credit_card('41111111111111111111'), :verify_card => true) + end + + def test_merchant_account_id_can_be_set_by_options + gateway = BraintreeBlueGateway.new( + :merchant_id => 'merchant_id', + :merchant_account_id => 'merchant_account_id', + :public_key => 'public_key', + :private_key => 'private_key' + ) + customer = stub( + :credit_cards => [stub_everything], + :email => 'email', + :phone => '321-654-0987', + :first_name => 'John', + :last_name => 'Smith' + ) + customer.stubs(:id).returns('123') + result = Braintree::SuccessfulResult.new(:customer => customer) + Braintree::CustomerGateway.any_instance.expects(:create).with do |params| + params[:credit_card][:options][:verification_merchant_account_id] == 'value_from_options' + end.returns(result) + + gateway.store(credit_card('41111111111111111111'), :verify_card => true, :verification_merchant_account_id => 'value_from_options') end def test_store_with_verify_card_true - customer = mock( - :credit_cards => [], + customer = stub( + :credit_cards => [stub_everything], :email => 'email', + :phone => '321-654-0987', :first_name => 'John', :last_name => 'Smith' ) customer.stubs(:id).returns('123') result = Braintree::SuccessfulResult.new(:customer => customer) - Braintree::Customer.expects(:create).with do |params| + Braintree::CustomerGateway.any_instance.expects(:create).with do |params| params[:credit_card][:options].has_key?(:verify_card) assert_equal true, params[:credit_card][:options][:verify_card] + assert_equal 'Longbob Longsen', params[:credit_card][:cardholder_name] params end.returns(result) - response = @gateway.store(credit_card("41111111111111111111"), :verify_card => true) - assert_equal "123", response.params["customer_vault_id"] - assert_equal response.params["customer_vault_id"], response.authorization + response = @gateway.store(credit_card('41111111111111111111'), :verify_card => true) + assert_equal '123', response.params['customer_vault_id'] + assert_equal response.params['customer_vault_id'], response.authorization + end + + def test_passes_email + customer = stub( + :credit_cards => [stub_everything], + :email => 'bob@example.com', + :phone => '321-654-0987', + :first_name => 'John', + :last_name => 'Smith', + id: '123' + ) + result = Braintree::SuccessfulResult.new(:customer => customer) + Braintree::CustomerGateway.any_instance.expects(:create).with do |params| + assert_equal 'bob@example.com', params[:email] + params + end.returns(result) + + response = @gateway.store(credit_card('41111111111111111111'), :email => 'bob@example.com') + assert_success response + end + + def test_scrubs_invalid_email + customer = stub( + :credit_cards => [stub_everything], + :email => nil, + :phone => '321-654-0987', + :first_name => 'John', + :last_name => 'Smith', + :id => '123' + ) + result = Braintree::SuccessfulResult.new(:customer => customer) + Braintree::CustomerGateway.any_instance.expects(:create).with do |params| + assert_equal nil, params[:email] + params + end.returns(result) + + response = @gateway.store(credit_card('41111111111111111111'), :email => 'bogus') + assert_success response end def test_store_with_verify_card_false - customer = mock( - :credit_cards => [], + customer = stub( + :credit_cards => [stub_everything], :email => 'email', + :phone => '321-654-0987', :first_name => 'John', :last_name => 'Smith' ) customer.stubs(:id).returns('123') result = Braintree::SuccessfulResult.new(:customer => customer) - Braintree::Customer.expects(:create).with do |params| + Braintree::CustomerGateway.any_instance.expects(:create).with do |params| params[:credit_card][:options].has_key?(:verify_card) assert_equal false, params[:credit_card][:options][:verify_card] params end.returns(result) - response = @gateway.store(credit_card("41111111111111111111"), :verify_card => false) - assert_equal "123", response.params["customer_vault_id"] - assert_equal response.params["customer_vault_id"], response.authorization + response = @gateway.store(credit_card('41111111111111111111'), :verify_card => false) + assert_equal '123', response.params['customer_vault_id'] + assert_equal response.params['customer_vault_id'], response.authorization end def test_store_with_billing_address_options customer_attributes = { - :credit_cards => [], + :credit_cards => [stub_everything], :email => 'email', + :phone => '321-654-0987', :first_name => 'John', :last_name => 'Smith' } billing_address = { - :address1 => "1 E Main St", - :address2 => "Suite 403", - :city => "Chicago", - :state => "Illinois", - :zip => "60622", - :country_name => "US" + :address1 => '1 E Main St', + :address2 => 'Suite 403', + :city => 'Chicago', + :state => 'Illinois', + :zip => '60622', + :country_name => 'US' } - customer = mock(customer_attributes) + customer = stub(customer_attributes) customer.stubs(:id).returns('123') result = Braintree::SuccessfulResult.new(:customer => customer) - Braintree::Customer.expects(:create).with do |params| + Braintree::CustomerGateway.any_instance.expects(:create).with do |params| assert_not_nil params[:credit_card][:billing_address] [:street_address, :extended_address, :locality, :region, :postal_code, :country_name].each do |billing_attribute| params[:credit_card][:billing_address].has_key?(billing_attribute) if params[:billing_address] @@ -150,37 +348,196 @@ def test_store_with_billing_address_options params end.returns(result) - @gateway.store(credit_card("41111111111111111111"), :billing_address => billing_address) + @gateway.store(credit_card('41111111111111111111'), :billing_address => billing_address) + end + + def test_store_with_phone_only_billing_address_option + customer_attributes = { + :credit_cards => [stub_everything], + :email => 'email', + :first_name => 'John', + :last_name => 'Smith', + :phone => '123-456-7890' + } + billing_address = { + :phone => '123-456-7890' + } + customer = stub(customer_attributes) + customer.stubs(:id).returns('123') + result = Braintree::SuccessfulResult.new(:customer => customer) + Braintree::CustomerGateway.any_instance.expects(:create).with do |params| + assert_nil params[:credit_card][:billing_address] + params + end.returns(result) + + @gateway.store(credit_card('41111111111111111111'), :billing_address => billing_address) + end + + def test_store_with_nil_billing_address_options + customer_attributes = { + :credit_cards => [stub_everything], + :email => 'email', + :first_name => 'John', + :last_name => 'Smith', + :phone => '123-456-7890' + } + billing_address = { + :name => 'John Smith', + :phone => '123-456-7890', + :company => nil, + :address1 => nil, + :address2 => '', + :city => nil, + :state => nil, + :zip => nil, + :country_name => nil + } + customer = stub(customer_attributes) + customer.stubs(:id).returns('123') + result = Braintree::SuccessfulResult.new(:customer => customer) + Braintree::CustomerGateway.any_instance.expects(:create).with do |params| + assert_nil params[:credit_card][:billing_address] + params + end.returns(result) + + @gateway.store(credit_card('41111111111111111111'), :billing_address => billing_address) + end + + def test_store_with_credit_card_token + customer = stub( + :email => 'email', + :phone => '321-654-0987', + :first_name => 'John', + :last_name => 'Smith' + ) + customer.stubs(:id).returns('123') + + braintree_credit_card = stub_everything(token: 'cctoken') + customer.stubs(:credit_cards).returns([braintree_credit_card]) + + result = Braintree::SuccessfulResult.new(:customer => customer) + Braintree::CustomerGateway.any_instance.expects(:create).with do |params| + assert_equal 'cctoken', params[:credit_card][:token] + params + end.returns(result) + + response = @gateway.store(credit_card('41111111111111111111'), :credit_card_token => 'cctoken') + assert_success response + assert_equal 'cctoken', response.params['braintree_customer']['credit_cards'][0]['token'] + assert_equal 'cctoken', response.params['credit_card_token'] + end + + def test_store_with_customer_id + customer = stub( + :email => 'email', + :phone => '321-654-0987', + :first_name => 'John', + :last_name => 'Smith', + :credit_cards => [stub_everything] + ) + customer.stubs(:id).returns('customerid') + + result = Braintree::SuccessfulResult.new(:customer => customer) + Braintree::CustomerGateway.any_instance.expects(:find). + with('customerid'). + raises(Braintree::NotFoundError) + Braintree::CustomerGateway.any_instance.expects(:create).with do |params| + assert_equal 'customerid', params[:id] + params + end.returns(result) + + response = @gateway.store(credit_card('41111111111111111111'), :customer => 'customerid') + assert_success response + assert_equal 'customerid', response.params['braintree_customer']['id'] + end + + def test_store_with_existing_customer_id + credit_card = stub( + customer_id: 'customerid', + token: 'cctoken' + ) + + result = Braintree::SuccessfulResult.new(credit_card: credit_card) + Braintree::CustomerGateway.any_instance.expects(:find).with('customerid') + Braintree::CreditCardGateway.any_instance.expects(:create).with do |params| + assert_equal 'customerid', params[:customer_id] + assert_equal '41111111111111111111', params[:number] + assert_equal 'Longbob Longsen', params[:cardholder_name] + params + end.returns(result) + + response = @gateway.store(credit_card('41111111111111111111'), customer: 'customerid') + assert_success response + assert_nil response.params['braintree_customer'] + assert_equal 'customerid', response.params['customer_vault_id'] + assert_equal 'cctoken', response.params['credit_card_token'] + end + + def test_store_with_existing_customer_id_and_nil_billing_address_options + credit_card = stub( + customer_id: 'customerid', + token: 'cctoken' + ) + options = { + :customer => 'customerid', + :billing_address => { + :name => 'John Smith', + :phone => '123-456-7890', + :company => nil, + :address1 => nil, + :address2 => nil, + :city => nil, + :state => nil, + :zip => nil, + :country_name => nil + } + } + + result = Braintree::SuccessfulResult.new(credit_card: credit_card) + Braintree::CustomerGateway.any_instance.expects(:find).with('customerid') + Braintree::CreditCardGateway.any_instance.expects(:create).with do |params| + assert_equal 'customerid', params[:customer_id] + assert_equal '41111111111111111111', params[:number] + assert_equal 'Longbob Longsen', params[:cardholder_name] + params + end.returns(result) + + response = @gateway.store(credit_card('41111111111111111111'), options) + assert_success response + assert_nil response.params['braintree_customer'] + assert_equal 'customerid', response.params['customer_vault_id'] + assert_equal 'cctoken', response.params['credit_card_token'] end def test_update_with_cvv - stored_credit_card = mock(:token => "token", :default? => true) + stored_credit_card = mock(:token => 'token', :default? => true) customer = mock(:credit_cards => [stored_credit_card], :id => '123') - Braintree::Customer.stubs(:find).with('vault_id').returns(customer) + Braintree::CustomerGateway.any_instance.stubs(:find).with('vault_id').returns(customer) BraintreeBlueGateway.any_instance.stubs(:customer_hash) result = Braintree::SuccessfulResult.new(:customer => customer) - Braintree::Customer.expects(:update).with do |vault, params| - assert_equal "567", params[:credit_card][:cvv] + Braintree::CustomerGateway.any_instance.expects(:update).with do |vault, params| + assert_equal '567', params[:credit_card][:cvv] + assert_equal 'Longbob Longsen', params[:credit_card][:cardholder_name] [vault, params] end.returns(result) - @gateway.update('vault_id', credit_card("41111111111111111111", :verification_value => "567")) + @gateway.update('vault_id', credit_card('41111111111111111111', :verification_value => '567')) end def test_update_with_verify_card_true - stored_credit_card = mock(:token => "token", :default? => true) - customer = mock(:credit_cards => [stored_credit_card], :id => '123') - Braintree::Customer.stubs(:find).with('vault_id').returns(customer) + stored_credit_card = stub(:token => 'token', :default? => true) + customer = stub(:credit_cards => [stored_credit_card], :id => '123') + Braintree::CustomerGateway.any_instance.stubs(:find).with('vault_id').returns(customer) BraintreeBlueGateway.any_instance.stubs(:customer_hash) result = Braintree::SuccessfulResult.new(:customer => customer) - Braintree::Customer.expects(:update).with do |vault, params| + Braintree::CustomerGateway.any_instance.expects(:update).with do |vault, params| assert_equal true, params[:credit_card][:options][:verify_card] [vault, params] end.returns(result) - @gateway.update('vault_id', credit_card("41111111111111111111"), :verify_card => true) + @gateway.update('vault_id', credit_card('41111111111111111111'), :verify_card => true) end def test_merge_credit_card_options_ignores_bad_option @@ -201,11 +558,11 @@ def test_merge_credit_card_options_handles_nil_credit_card def test_merge_credit_card_options_handles_billing_address billing_address = { - :address1 => "1 E Main St", - :city => "Chicago", - :state => "Illinois", - :zip => "60622", - :country => "US" + :address1 => '1 E Main St', + :city => 'Chicago', + :state => 'Illinois', + :zip => '60622', + :country => 'US' } params = {:first_name => 'John'} options = {:billing_address => billing_address} @@ -213,13 +570,14 @@ def test_merge_credit_card_options_handles_billing_address :first_name => 'John', :credit_card => { :billing_address => { - :street_address => "1 E Main St", + :street_address => '1 E Main St', :extended_address => nil, :company => nil, - :locality => "Chicago", - :region => "Illinois", - :postal_code => "60622", - :country_code_alpha2 => "US" + :locality => 'Chicago', + :region => 'Illinois', + :postal_code => '60622', + :country_code_alpha2 => 'US', + :country_code_alpha3 => 'USA' }, :options => {} } @@ -240,30 +598,96 @@ def test_merge_credit_card_options_only_includes_billing_address_when_present end def test_address_country_handling - Braintree::Transaction.expects(:sale).with do |params| - (params[:billing][:country_code_alpha2] == "US") + Braintree::TransactionGateway.any_instance.expects(:sale).with do |params| + (params[:billing][:country_code_alpha2] == 'US') end.returns(braintree_result) - @gateway.purchase(100, credit_card("41111111111111111111"), :billing_address => {:country => "US"}) + @gateway.purchase(100, credit_card('41111111111111111111'), :billing_address => {:country => 'US'}) - Braintree::Transaction.expects(:sale).with do |params| - (params[:billing][:country_code_alpha2] == "US") + Braintree::TransactionGateway.any_instance.expects(:sale).with do |params| + (params[:billing][:country_code_alpha2] == 'US') end.returns(braintree_result) - @gateway.purchase(100, credit_card("41111111111111111111"), :billing_address => {:country_code_alpha2 => "US"}) + @gateway.purchase(100, credit_card('41111111111111111111'), :billing_address => {:country_code_alpha2 => 'US'}) - Braintree::Transaction.expects(:sale).with do |params| - (params[:billing][:country_name] == "United States of America") + Braintree::TransactionGateway.any_instance.expects(:sale).with do |params| + (params[:billing][:country_name] == 'United States of America') end.returns(braintree_result) - @gateway.purchase(100, credit_card("41111111111111111111"), :billing_address => {:country_name => "United States of America"}) + @gateway.purchase(100, credit_card('41111111111111111111'), :billing_address => {:country_name => 'United States of America'}) - Braintree::Transaction.expects(:sale).with do |params| - (params[:billing][:country_code_alpha3] == "USA") + Braintree::TransactionGateway.any_instance.expects(:sale).with do |params| + (params[:billing][:country_code_alpha3] == 'USA') end.returns(braintree_result) - @gateway.purchase(100, credit_card("41111111111111111111"), :billing_address => {:country_code_alpha3 => "USA"}) + @gateway.purchase(100, credit_card('41111111111111111111'), :billing_address => {:country_code_alpha3 => 'USA'}) - Braintree::Transaction.expects(:sale).with do |params| + Braintree::TransactionGateway.any_instance.expects(:sale).with do |params| (params[:billing][:country_code_numeric] == 840) end.returns(braintree_result) - @gateway.purchase(100, credit_card("41111111111111111111"), :billing_address => {:country_code_numeric => 840}) + @gateway.purchase(100, credit_card('41111111111111111111'), :billing_address => {:country_code_numeric => 840}) + end + + def test_address_zip_handling + Braintree::TransactionGateway.any_instance.expects(:sale).with do |params| + (params[:billing][:postal_code] == '12345') + end.returns(braintree_result) + @gateway.purchase(100, credit_card('41111111111111111111'), :billing_address => {:zip => '12345'}) + + Braintree::TransactionGateway.any_instance.expects(:sale).with do |params| + (params[:billing][:postal_code] == nil) + end.returns(braintree_result) + @gateway.purchase(100, credit_card('41111111111111111111'), :billing_address => {:zip => '1234567890'}) + end + + def test_cardholder_name_passing_with_card + Braintree::TransactionGateway.any_instance.expects(:sale).with do |params| + (params[:credit_card][:cardholder_name] == 'Longbob Longsen') + end.returns(braintree_result) + @gateway.purchase(100, credit_card('41111111111111111111'), :customer => {:first_name => 'Longbob', :last_name => 'Longsen'}) + end + + def test_three_d_secure_pass_thru_handling_version_1 + Braintree::TransactionGateway. + any_instance. + expects(:sale). + with(has_entries(three_d_secure_pass_thru: { + cavv: 'cavv', + eci_flag: 'eci', + xid: 'xid', + })). + returns(braintree_result) + + @gateway.purchase(100, credit_card('41111111111111111111'), three_d_secure: {cavv: 'cavv', eci: 'eci', xid: 'xid'}) + end + + def test_three_d_secure_pass_thru_handling_version_2 + Braintree::TransactionGateway. + any_instance. + expects(:sale). + with(has_entries(three_d_secure_pass_thru: has_entries( + three_d_secure_version: '2.0', + cavv: 'cavv', + eci_flag: 'eci', + ds_transaction_id: 'trans_id', + cavv_algorithm: 'algorithm', + directory_response: 'directory', + authentication_response: 'auth' + ))). + returns(braintree_result) + + @gateway.purchase(100, credit_card('41111111111111111111'), three_d_secure: {version: '2.0', cavv: 'cavv', eci: 'eci', ds_transaction_id: 'trans_id', cavv_algorithm: 'algorithm', directory_response_status: 'directory', authentication_response_status: 'auth'}) + end + + def test_three_d_secure_pass_thru_some_fields + Braintree::TransactionGateway. + any_instance. + expects(:sale). + with(has_entries(three_d_secure_pass_thru: has_entries( + three_d_secure_version: '2.0', + cavv: 'cavv', + eci_flag: 'eci', + ds_transaction_id: 'trans_id' + ))). + returns(braintree_result) + + @gateway.purchase(100, credit_card('41111111111111111111'), three_d_secure: {version: '2.0', cavv: 'cavv', eci: 'eci', ds_transaction_id: 'trans_id'}) end def test_passes_recurring_flag @@ -274,43 +698,71 @@ def test_passes_recurring_flag :private_key => 'test' ) - Braintree::Transaction.expects(:sale). + Braintree::TransactionGateway.any_instance.expects(:sale). with(has_entries(:recurring => true)). returns(braintree_result) - @gateway.purchase(100, credit_card("41111111111111111111"), :recurring => true) + @gateway.purchase(100, credit_card('41111111111111111111'), :recurring => true) - Braintree::Transaction.expects(:sale). + Braintree::TransactionGateway.any_instance.expects(:sale). with(Not(has_entries(:recurring => true))). returns(braintree_result) - @gateway.purchase(100, credit_card("41111111111111111111")) + @gateway.purchase(100, credit_card('41111111111111111111')) + end + + def test_passes_transaction_source + Braintree::TransactionGateway.any_instance.expects(:sale).with do |params| + (params[:transaction_source] == 'recurring') + (params[:recurring] == nil) + end.returns(braintree_result) + @gateway.purchase(100, credit_card('41111111111111111111'), :transaction_source => 'recurring', :recurring => true) + end + + def test_passes_skip_avs + Braintree::TransactionGateway.any_instance.expects(:sale).with do |params| + (params[:options][:skip_avs] == true) + end.returns(braintree_result(:avs_postal_code_response_code => 'B', :avs_street_address_response_code => 'B')) + + response = @gateway.purchase(100, credit_card('41111111111111111111'), :skip_avs => true) + assert_equal 'B', response.avs_result['code'] + end + + def test_passes_skip_cvv + Braintree::TransactionGateway.any_instance.expects(:sale).with do |params| + (params[:options][:skip_cvv] == true) + end.returns(braintree_result(:cvv_response_code => 'B')) + + response = @gateway.purchase(100, credit_card('41111111111111111111'), :skip_cvv => true) + assert_equal 'B', response.cvv_result['code'] end def test_configured_logger_has_a_default # The default is actually provided by the Braintree gem, but we # assert its presence in order to show ActiveMerchant need not # configure a logger - assert Braintree::Configuration.logger.is_a?(Logger) + assert @internal_gateway.config.logger.is_a?(Logger) end def test_configured_logger_has_a_default_log_level_defined_by_active_merchant - assert_equal Logger::WARN, Braintree::Configuration.logger.level + assert_equal Logger::WARN, @internal_gateway.config.logger.level end - def test_configured_logger_respects_any_custom_log_level_set_without_overwriting_it + def test_default_logger_sets_warn_level_without_overwriting_global with_braintree_configuration_restoration do assert Braintree::Configuration.logger.level != Logger::DEBUG Braintree::Configuration.logger.level = Logger::DEBUG - # Re-instatiate a gateway to show it doesn't affect the log level - BraintreeBlueGateway.new( + # Re-instantiate a gateway to show it doesn't touch the global + gateway = BraintreeBlueGateway.new( :merchant_id => 'test', :public_key => 'test', :private_key => 'test' ) + internal_gateway = gateway.instance_variable_get(:@braintree_gateway) - assert_equal Logger::WARN, Braintree::Configuration.logger.level + assert_equal Logger::WARN, internal_gateway.config.logger.level + assert_equal Logger::DEBUG, Braintree::Configuration.logger.level end end @@ -321,21 +773,369 @@ def test_that_setting_a_wiredump_device_on_the_gateway_sets_the_braintree_logger assert_not_equal logger, Braintree::Configuration.logger - BraintreeBlueGateway.new( + gateway = BraintreeBlueGateway.new( :merchant_id => 'test', :public_key => 'test', :private_key => 'test' ) + internal_gateway = gateway.instance_variable_get(:@braintree_gateway) - assert_equal logger, Braintree::Configuration.logger - assert_equal Logger::DEBUG, Braintree::Configuration.logger.level + assert_equal logger, internal_gateway.config.logger + assert_equal Logger::DEBUG, internal_gateway.config.logger.level end end + def test_solution_id_is_added_to_create_transaction_parameters + assert_nil @gateway.send(:create_transaction_parameters, 100, credit_card('41111111111111111111'), {})[:channel] + ActiveMerchant::Billing::BraintreeBlueGateway.application_id = 'ABC123' + assert_equal @gateway.send(:create_transaction_parameters, 100, credit_card('41111111111111111111'), {})[:channel], 'ABC123' + + gateway = BraintreeBlueGateway.new(:merchant_id => 'test', :public_key => 'test', :private_key => 'test', channel: 'overidden-channel') + assert_equal gateway.send(:create_transaction_parameters, 100, credit_card('41111111111111111111'), {})[:channel], 'overidden-channel' + ensure + ActiveMerchant::Billing::BraintreeBlueGateway.application_id = nil + end + + def test_successful_purchase_with_descriptor + Braintree::TransactionGateway.any_instance.expects(:sale).with do |params| + (params[:descriptor][:name] == 'wow*productname') && + (params[:descriptor][:phone] == '4443331112') && + (params[:descriptor][:url] == 'wow.com') + end.returns(braintree_result) + @gateway.purchase(100, credit_card('41111111111111111111'), descriptor_name: 'wow*productname', descriptor_phone: '4443331112', descriptor_url: 'wow.com') + end + + def test_successful_purchase_with_device_data + Braintree::TransactionGateway.any_instance.expects(:sale).with do |params| + (params[:device_data] == 'device data string') + end.returns(braintree_result({risk_data: {id: 123456, decision: 'Decline', device_data_captured: true, fraud_service_provider: 'kount'}})) + + response = @gateway.purchase(100, credit_card('41111111111111111111'), device_data: 'device data string') + + assert transaction = response.params['braintree_transaction'] + assert transaction['risk_data'] + assert_equal 123456, transaction['risk_data']['id'] + assert_equal 'Decline', transaction['risk_data']['decision'] + assert_equal true, transaction['risk_data']['device_data_captured'] + assert_equal 'kount', transaction['risk_data']['fraud_service_provider'] + end + + def test_apple_pay_card + Braintree::TransactionGateway.any_instance.expects(:sale). + with( + :amount => '1.00', + :order_id => '1', + :customer => {:id => nil, :email => nil, :phone => nil, + :first_name => 'Longbob', :last_name => 'Longsen'}, + :options => {:store_in_vault => false, :submit_for_settlement => nil, :hold_in_escrow => nil}, + :custom_fields => nil, + :apple_pay_card => { + :number => '4111111111111111', + :expiration_month => '09', + :expiration_year => (Time.now.year + 1).to_s, + :cardholder_name => 'Longbob Longsen', + :cryptogram => '111111111100cryptogram', + :eci_indicator => '05' + } + ). + returns(braintree_result(:id => 'transaction_id')) + + credit_card = network_tokenization_credit_card('4111111111111111', + :brand => 'visa', + :transaction_id => '123', + :eci => '05', + :payment_cryptogram => '111111111100cryptogram' + ) + + response = @gateway.authorize(100, credit_card, :test => true, :order_id => '1') + assert_equal 'transaction_id', response.authorization + end + + def test_android_pay_card + Braintree::TransactionGateway.any_instance.expects(:sale). + with( + :amount => '1.00', + :order_id => '1', + :customer => {:id => nil, :email => nil, :phone => nil, + :first_name => 'Longbob', :last_name => 'Longsen'}, + :options => {:store_in_vault => false, :submit_for_settlement => nil, :hold_in_escrow => nil}, + :custom_fields => nil, + :android_pay_card => { + :number => '4111111111111111', + :expiration_month => '09', + :expiration_year => (Time.now.year + 1).to_s, + :cryptogram => '111111111100cryptogram', + :google_transaction_id => '1234567890', + :source_card_type => 'visa', + :source_card_last_four => '1111', + :eci_indicator => '05' + } + ). + returns(braintree_result(:id => 'transaction_id')) + + credit_card = network_tokenization_credit_card('4111111111111111', + :brand => 'visa', + :eci => '05', + :payment_cryptogram => '111111111100cryptogram', + :source => :android_pay, + :transaction_id => '1234567890' + ) + + response = @gateway.authorize(100, credit_card, :test => true, :order_id => '1') + assert_equal 'transaction_id', response.authorization + end + + def test_google_pay_card + Braintree::TransactionGateway.any_instance.expects(:sale). + with( + :amount => '1.00', + :order_id => '1', + :customer => {:id => nil, :email => nil, :phone => nil, + :first_name => 'Longbob', :last_name => 'Longsen'}, + :options => {:store_in_vault => false, :submit_for_settlement => nil, :hold_in_escrow => nil}, + :custom_fields => nil, + :android_pay_card => { + :number => '4111111111111111', + :expiration_month => '09', + :expiration_year => (Time.now.year + 1).to_s, + :cryptogram => '111111111100cryptogram', + :google_transaction_id => '1234567890', + :source_card_type => 'visa', + :source_card_last_four => '1111', + :eci_indicator => '05' + } + ). + returns(braintree_result(:id => 'transaction_id')) + + credit_card = network_tokenization_credit_card('4111111111111111', + :brand => 'visa', + :eci => '05', + :payment_cryptogram => '111111111100cryptogram', + :source => :google_pay, + :transaction_id => '1234567890' + ) + + response = @gateway.authorize(100, credit_card, :test => true, :order_id => '1') + assert_equal 'transaction_id', response.authorization + end + + def test_supports_network_tokenization + assert_instance_of TrueClass, @gateway.supports_network_tokenization? + end + + def test_unsuccessful_transaction_returns_id_when_available + Braintree::TransactionGateway.any_instance.expects(:sale).returns(braintree_error_result(transaction: {id: 'transaction_id'})) + assert response = @gateway.purchase(100, credit_card('41111111111111111111')) + refute response.success? + assert response.authorization.present? + end + + def test_unsuccessful_transaction_returns_message_when_available + Braintree::TransactionGateway.any_instance. + expects(:sale). + returns(braintree_error_result(message: 'Some error message')) + assert response = @gateway.purchase(100, credit_card('41111111111111111111')) + refute response.success? + assert_equal response.message, 'Some error message' + end + + def test_refund_unsettled_payment + Braintree::TransactionGateway.any_instance. + expects(:refund). + returns(braintree_error_result(message: 'Cannot refund a transaction unless it is settled. (91506)')) + + Braintree::TransactionGateway.any_instance. + expects(:void). + never + + response = @gateway.refund(1.00, 'transaction_id') + refute response.success? + end + + def test_refund_unsettled_payment_forces_void_on_full_refund + Braintree::TransactionGateway.any_instance. + expects(:refund). + returns(braintree_error_result(message: 'Cannot refund a transaction unless it is settled. (91506)')) + + Braintree::TransactionGateway.any_instance. + expects(:void). + returns(braintree_result) + + response = @gateway.refund(1.00, 'transaction_id', force_full_refund_if_unsettled: true) + assert response.success? + end + + def test_stored_credential_recurring_cit_initial + Braintree::TransactionGateway.any_instance.expects(:sale).with( + standard_purchase_params.merge( + { + :external_vault => { + :status => 'will_vault'}, + :transaction_source => '' + }) + ).returns(braintree_result) + + @gateway.purchase(100, credit_card('41111111111111111111'), {test: true, order_id: '1', stored_credential: stored_credential(:cardholder, :recurring, :initial)}) + end + + def test_stored_credential_recurring_cit_used + Braintree::TransactionGateway.any_instance.expects(:sale).with( + standard_purchase_params.merge( + { + :external_vault => { + :status => 'vaulted', + :previous_network_transaction_id => '123ABC'}, + :transaction_source => '' + }) + ).returns(braintree_result) + + @gateway.purchase(100, credit_card('41111111111111111111'), {test: true, order_id: '1', stored_credential: stored_credential(:cardholder, :recurring, id: '123ABC')}) + end + + def test_stored_credential_recurring_mit_initial + Braintree::TransactionGateway.any_instance.expects(:sale).with( + standard_purchase_params.merge( + { + :external_vault => { + :status => 'will_vault'}, + :transaction_source => 'recurring' + }) + ).returns(braintree_result) + + @gateway.purchase(100, credit_card('41111111111111111111'), {test: true, order_id: '1', stored_credential: stored_credential(:merchant, :recurring, :initial)}) + end + + def test_stored_credential_recurring_mit_used + Braintree::TransactionGateway.any_instance.expects(:sale).with( + standard_purchase_params.merge( + { + :external_vault => { + :status => 'vaulted', + :previous_network_transaction_id => '123ABC'}, + :transaction_source => 'recurring' + }) + ).returns(braintree_result) + + @gateway.purchase(100, credit_card('41111111111111111111'), {test: true, order_id: '1', stored_credential: stored_credential(:merchant, :recurring, id: '123ABC')}) + end + + def test_stored_credential_installment_cit_initial + Braintree::TransactionGateway.any_instance.expects(:sale).with( + standard_purchase_params.merge( + { + :external_vault => { + :status => 'will_vault'}, + :transaction_source => '' + }) + ).returns(braintree_result) + + @gateway.purchase(100, credit_card('41111111111111111111'), {test: true, order_id: '1', stored_credential: stored_credential(:cardholder, :installment, :initial)}) + end + + def test_stored_credential_installment_cit_used + Braintree::TransactionGateway.any_instance.expects(:sale).with( + standard_purchase_params.merge( + { + :external_vault => { + :status => 'vaulted', + :previous_network_transaction_id => '123ABC'}, + :transaction_source => '' + }) + ).returns(braintree_result) + + @gateway.purchase(100, credit_card('41111111111111111111'), {test: true, order_id: '1', stored_credential: stored_credential(:cardholder, :installment, id: '123ABC')}) + end + + def test_stored_credential_installment_mit_initial + Braintree::TransactionGateway.any_instance.expects(:sale).with( + standard_purchase_params.merge( + { + :external_vault => { + :status => 'will_vault'}, + :transaction_source => 'recurring' + }) + ).returns(braintree_result) + + @gateway.purchase(100, credit_card('41111111111111111111'), {test: true, order_id: '1', stored_credential: stored_credential(:merchant, :installment, :initial)}) + end + + def test_stored_credential_installment_mit_used + Braintree::TransactionGateway.any_instance.expects(:sale).with( + standard_purchase_params.merge( + { + :external_vault => { + :status => 'vaulted', + :previous_network_transaction_id => '123ABC'}, + :transaction_source => 'recurring' + }) + ).returns(braintree_result) + + @gateway.purchase(100, credit_card('41111111111111111111'), {test: true, order_id: '1', stored_credential: stored_credential(:merchant, :installment, id: '123ABC')}) + end + + def test_stored_credential_unscheduled_cit_initial + Braintree::TransactionGateway.any_instance.expects(:sale).with( + standard_purchase_params.merge( + { + :external_vault => { + :status => 'will_vault'}, + :transaction_source => '' + }) + ).returns(braintree_result) + + @gateway.purchase(100, credit_card('41111111111111111111'), {test: true, order_id: '1', stored_credential: stored_credential(:cardholder, :unscheduled, :initial)}) + end + + def test_stored_credential_unscheduled_cit_used + Braintree::TransactionGateway.any_instance.expects(:sale).with( + standard_purchase_params.merge( + { + :external_vault => { + :status => 'vaulted', + :previous_network_transaction_id => '123ABC'}, + :transaction_source => '' + }) + ).returns(braintree_result) + + @gateway.purchase(100, credit_card('41111111111111111111'), {test: true, order_id: '1', stored_credential: stored_credential(:cardholder, :unscheduled, id: '123ABC')}) + end + + def test_stored_credential_unscheduled_mit_initial + Braintree::TransactionGateway.any_instance.expects(:sale).with( + standard_purchase_params.merge( + { + :external_vault => { + :status => 'will_vault'}, + :transaction_source => 'unscheduled' + }) + ).returns(braintree_result) + + @gateway.purchase(100, credit_card('41111111111111111111'), {test: true, order_id: '1', stored_credential: stored_credential(:merchant, :unscheduled, :initial)}) + end + + def test_stored_credential_unscheduled_mit_used + Braintree::TransactionGateway.any_instance.expects(:sale).with( + standard_purchase_params.merge( + { + :external_vault => { + :status => 'vaulted', + :previous_network_transaction_id => '123ABC' + }, + :transaction_source => 'unscheduled' + }) + ).returns(braintree_result) + + @gateway.purchase(100, credit_card('41111111111111111111'), {test: true, order_id: '1', stored_credential: stored_credential(:merchant, :unscheduled, id: '123ABC')}) + end + private def braintree_result(options = {}) - Braintree::SuccessfulResult.new(:transaction => Braintree::Transaction._new(nil, {:id => "transaction_id"}.merge(options))) + Braintree::SuccessfulResult.new(:transaction => Braintree::Transaction._new(nil, {:id => 'transaction_id'}.merge(options))) + end + + def braintree_error_result(options = {}) + Braintree::ErrorResult.new(@internal_gateway, {errors: {}}.merge(options)) end def with_braintree_configuration_restoration(&block) @@ -350,4 +1150,22 @@ def with_braintree_configuration_restoration(&block) # Reset the Braintree logger Braintree::Configuration.logger = nil end + + def standard_purchase_params + { + :amount => '1.00', + :order_id => '1', + :customer => {:id => nil, :email => nil, :phone => nil, + :first_name => 'Longbob', :last_name => 'Longsen'}, + :options => {:store_in_vault => false, :submit_for_settlement => true, :hold_in_escrow => nil}, + :custom_fields => nil, + :credit_card => { + :number => '41111111111111111111', + :cvv => '123', + :expiration_month => '09', + :expiration_year => '2020', + :cardholder_name => 'Longbob Longsen', + } + } + end end diff --git a/test/unit/gateways/braintree_orange_test.rb b/test/unit/gateways/braintree_orange_test.rb index 4c956706caf..ee4149137bb 100644 --- a/test/unit/gateways/braintree_orange_test.rb +++ b/test/unit/gateways/braintree_orange_test.rb @@ -1,6 +1,8 @@ require 'test_helper' class BraintreeOrangeTest < Test::Unit::TestCase + include CommStub + def setup @gateway = BraintreeOrangeGateway.new( :login => 'LOGIN', @@ -22,21 +24,31 @@ def test_successful_purchase assert_equal '510695343', response.authorization end + def test_fractional_amounts + response = stub_comms do + @gateway.purchase(100, @credit_card, @options.merge(currency: 'JPY')) + end.check_request do |endpoint, data, headers| + refute_match(/amount=1.00/, data) + end.respond_with(successful_purchase_response) + + assert_success response + end + def test_successful_store @gateway.expects(:ssl_post).returns(successful_store_response) assert response = @gateway.store(@credit_card, @options) assert_instance_of Response, response assert_success response - assert_equal "853162645", response.authorization - assert_equal response.authorization, response.params["customer_vault_id"] + assert_equal '853162645', response.authorization + assert_equal response.authorization, response.params['customer_vault_id'] end def test_add_processor result = {} - @gateway.send(:add_processor, result, {:processor => 'ccprocessorb'} ) - assert_equal ["processor_id"], result.stringify_keys.keys.sort + @gateway.send(:add_processor, result, {:processor => 'ccprocessorb'}) + assert_equal ['processor_id'], result.stringify_keys.keys.sort assert_equal 'ccprocessorb', result[:processor_id] end @@ -48,39 +60,62 @@ def test_failed_purchase assert_failure response end + def test_successful_verify + response = stub_comms do + @gateway.verify(@credit_card) + end.respond_with(successful_authorization_response, successful_void_response) + assert_success response + end + + def test_successful_verify_with_failed_void + response = stub_comms do + @gateway.verify(@credit_card, @options) + end.respond_with(successful_authorization_response, failed_void_response) + assert_success response + assert_match %r{This transaction has been approved}, response.message + end + + def test_unsuccessful_verify + response = stub_comms do + @gateway.verify(@credit_card, @options) + end.respond_with(failed_authorization_response, successful_void_response) + assert_failure response + assert_match %r{Invalid Credit Card Number}, response.message + end + def test_add_address result = {} - @gateway.send(:add_address, result, {:address1 => '164 Waverley Street', :country => 'US', :state => 'CO'} ) - assert_equal ["address1", "city", "company", "country", "phone", "state", "zip"], result.stringify_keys.keys.sort - assert_equal 'CO', result["state"] - assert_equal '164 Waverley Street', result["address1"] - assert_equal 'US', result["country"] + @gateway.send(:add_address, result, {:address1 => '164 Waverley Street', :country => 'US', :state => 'CO'}) + assert_equal ['address1', 'city', 'company', 'country', 'phone', 'state', 'zip'], result.stringify_keys.keys.sort + assert_equal 'CO', result['state'] + assert_equal '164 Waverley Street', result['address1'] + assert_equal 'US', result['country'] end def test_add_shipping_address result = {} - @gateway.send(:add_address, result, {:address1 => '164 Waverley Street', :country => 'US', :state => 'CO'},"shipping" ) - assert_equal ["shipping_address1", "shipping_city", "shipping_company", "shipping_country", "shipping_phone", "shipping_state", "shipping_zip"], result.stringify_keys.keys.sort - assert_equal 'CO', result["shipping_state"] - assert_equal '164 Waverley Street', result["shipping_address1"] - assert_equal 'US', result["shipping_country"] + @gateway.send(:add_address, result, {:address1 => '164 Waverley Street', :country => 'US', :state => 'CO'}, 'shipping') + assert_equal ['shipping_address1', 'shipping_city', 'shipping_company', 'shipping_country', 'shipping_phone', 'shipping_state', 'shipping_zip'], result.stringify_keys.keys.sort + assert_equal 'CO', result['shipping_state'] + assert_equal '164 Waverley Street', result['shipping_address1'] + assert_equal 'US', result['shipping_country'] end def test_adding_store_adds_vault_id_flag result = {} @gateway.send(:add_creditcard, result, @credit_card, :store => true) - assert_equal ["ccexp", "ccnumber", "customer_vault", "cvv", "firstname", "lastname"], result.stringify_keys.keys.sort + assert_equal ['ccexp', 'ccnumber', 'customer_vault', 'cvv', 'firstname', 'lastname'], result.stringify_keys.keys.sort assert_equal 'add_customer', result[:customer_vault] end def test_blank_store_doesnt_add_vault_flag result = {} - @gateway.send(:add_creditcard, result, @credit_card, {} ) - assert_equal ["ccexp", "ccnumber", "cvv", "firstname", "lastname"], result.stringify_keys.keys.sort + @gateway.send(:add_creditcard, result, @credit_card, {}) + assert_equal ['ccexp', 'ccnumber', 'cvv', 'firstname', 'lastname'], result.stringify_keys.keys.sort assert_nil result[:customer_vault] end @@ -115,6 +150,18 @@ def test_cvv_result assert_equal 'N', response.cvv_result['code'] end + def test_add_eci + @gateway.expects(:commit).with { |_, _, parameters| !parameters.has_key?(:billing_method) } + @gateway.purchase(@amount, @credit_card, {}) + + @gateway.expects(:commit).with { |_, _, parameters| parameters[:billing_method] == 'recurring' } + @gateway.purchase(@amount, @credit_card, {:eci => 'recurring'}) + end + + def test_transcript_scrubbing + assert_equal scrubbed_transcript, @gateway.scrub(transcript) + end + private def successful_purchase_response @@ -125,7 +172,31 @@ def failed_purchase_response 'response=2&responsetext=DECLINE&authcode=&transactionid=510695919&avsresponse=N&cvvresponse=N&orderid=50357660b0b3ef16f72a3d3b83c46983&type=sale&response_code=200' end + def successful_authorization_response + 'response=1&responsetext=SUCCESS&authcode=123456&transactionid=2313367000&avsresponse=N&cvvresponse=N&orderid=fb5fa6d66bf82a6ea48e425e5f79095c&type=auth&response_code=100' + end + + def failed_authorization_response + 'response=3&responsetext=Invalid Credit Card Number REFID:127210770&authcode=&transactionid=&avsresponse=&cvvresponse=&orderid=0cfae165b48be9467b26dcb920bf05d6&type=auth&response_code=300' + end + + def successful_void_response + 'response=1&responsetext=Transaction Void Successful&authcode=123456&transactionid=2313367000&avsresponse=&cvvresponse=&orderid=fb5fa6d66bf82a6ea48e425e5f79095c&type=void&response_code=100' + end + + def failed_void_response + 'response=3&responsetext=Only transactions pending settlement can be voided REFID:127210798&authcode=&transactionid=2313369860&avsresponse=&cvvresponse=&orderid=&type=void&response_code=300' + end + def successful_store_response - "response=1&responsetext=Customer Added&authcode=&transactionid=&avsresponse=&cvvresponse=&orderid=&type=&response_code=100&merchant_defined_field_6=&merchant_defined_field_7=&customer_vault_id=853162645" + 'response=1&responsetext=Customer Added&authcode=&transactionid=&avsresponse=&cvvresponse=&orderid=&type=&response_code=100&merchant_defined_field_6=&merchant_defined_field_7=&customer_vault_id=853162645' + end + + def transcript + 'username=demo&password=password&type=sale&orderid=8267b7f890aac7699f6ebc93c7c94d96&ccnumber=4111111111111111&cvv=123&ccexp=0916&firstname=Longbob&lastname=Longsen&address1=456+My+Street&address2=Apt+1&company=Widgets+Inc&phone=%28555%29555-5555&zip=K1C2N6&city=Ottawa&country=CA&state=ON&currency=USD&tax=&amount=77.70' + end + + def scrubbed_transcript + 'username=demo&password=password&type=sale&orderid=8267b7f890aac7699f6ebc93c7c94d96&ccnumber=[FILTERED]&cvv=[FILTERED]&ccexp=0916&firstname=Longbob&lastname=Longsen&address1=456+My+Street&address2=Apt+1&company=Widgets+Inc&phone=%28555%29555-5555&zip=K1C2N6&city=Ottawa&country=CA&state=ON&currency=USD&tax=&amount=77.70' end end diff --git a/test/unit/gateways/braintree_test.rb b/test/unit/gateways/braintree_test.rb index ba1b24e76a2..7cff9f6512f 100644 --- a/test/unit/gateways/braintree_test.rb +++ b/test/unit/gateways/braintree_test.rb @@ -20,18 +20,18 @@ def test_new_with_merchant_id_creates_braintree_blue end def test_should_have_display_name_of_just_braintree - assert_equal "Braintree", BraintreeGateway.display_name + assert_equal 'Braintree', BraintreeGateway.display_name end def test_should_have_homepage_url - assert_equal "http://www.braintreepaymentsolutions.com", BraintreeGateway.homepage_url + assert_equal 'http://www.braintreepaymentsolutions.com', BraintreeGateway.homepage_url end def test_should_have_supported_credit_card_types - assert_equal [:visa, :master, :american_express, :discover, :jcb, :diners_club], BraintreeGateway.supported_cardtypes + assert_equal [:visa, :master, :american_express, :discover, :jcb, :diners_club, :maestro], BraintreeGateway.supported_cardtypes end def test_should_have_default_currency - assert_equal "USD", BraintreeGateway.default_currency + assert_equal 'USD', BraintreeGateway.default_currency end end diff --git a/test/unit/gateways/bridge_pay_test.rb b/test/unit/gateways/bridge_pay_test.rb new file mode 100644 index 00000000000..ce60e3e05f9 --- /dev/null +++ b/test/unit/gateways/bridge_pay_test.rb @@ -0,0 +1,363 @@ +require 'test_helper' + +class BridgePayTest < Test::Unit::TestCase + include CommStub + + def setup + Base.mode = :test + + @gateway = BridgePayGateway.new( + user_name: 'login', + password: 'password' + ) + + @credit_card = credit_card + @check = check + @amount = 100 + @options = {} + end + + def test_successful_purchase + @gateway.expects(:ssl_post).returns(successful_purchase_response) + + response = @gateway.purchase(@amount, @credit_card) + assert_success response + + assert_equal 'OK9757|837495', response.authorization + assert response.test? + end + + def test_failed_purchase + @gateway.expects(:ssl_post).returns(failed_purchase_response) + + response = @gateway.purchase(@amount, @credit_card) + assert_failure response + assert_equal 'Duplicate Transaction', response.message + end + + def test_successful_purchase_with_echeck + @gateway.expects(:ssl_post).returns(successful_purchase_with_echeck_response) + + response = @gateway.purchase(@amount, @check) + assert_success response + + assert_equal 'OK6269|1316661', response.authorization + assert response.test? + end + + def test_failed_purchase_with_bad_echeck + @gateway.expects(:ssl_post).returns(failed_purchase_response) + + @check.account_type = nil + response = @gateway.purchase(@amount, @check) + assert_failure response + end + + def test_authorize_and_capture + response = stub_comms do + @gateway.authorize(@amount, @credit_card) + end.respond_with(successful_authorize_response) + + assert_success response + assert_equal 'OK2657|838662', response.authorization + + capture = stub_comms do + @gateway.capture(@amount, response.authorization) + end.check_request do |endpoint, data, headers| + assert_match(/OK2657/, data) + end.respond_with(successful_capture_response) + + assert_success capture + end + + def test_refund + response = stub_comms do + @gateway.purchase(@amount, @credit_card) + end.respond_with(successful_purchase_response) + + assert_success response + assert_equal 'OK9757|837495', response.authorization + + refund = stub_comms do + @gateway.refund(@amount, response.authorization) + end.check_request do |endpoint, data, headers| + assert_match(/OK9757/, data) + end.respond_with(successful_refund_response) + + assert_success refund + end + + def test_void + response = stub_comms do + @gateway.purchase(@amount, @credit_card) + end.respond_with(successful_purchase_response) + + assert_success response + assert_equal 'OK9757|837495', response.authorization + + refund = stub_comms do + @gateway.void(response.authorization) + end.check_request do |endpoint, data, headers| + assert_match(/OK9757/, data) + end.respond_with(successful_refund_response) + + assert_success refund + end + + def test_store_and_purchase_with_token + store = stub_comms do + @gateway.store(@credit_card) + end.respond_with(successful_store_response) + + assert_success store + assert_equal 'Success', store.message + + purchase = stub_comms do + @gateway.purchase(@amount, store.authorization) + end.respond_with(successful_purchase_response) + + assert_success purchase + assert_equal 'Approved', purchase.message + end + + def test_passing_cvv + stub_comms do + @gateway.purchase(@amount, @credit_card) + end.check_request do |endpoint, data, headers| + assert_match(/#{@credit_card.verification_value}/, data) + end.respond_with(successful_purchase_response) + end + + def test_passing_billing_address + stub_comms do + @gateway.purchase(@amount, @credit_card, :billing_address => address) + end.check_request do |endpoint, data, headers| + assert_match(/Street=456\+My\+Street/, data) + assert_match(/Zip=K1C2N6/, data) + end.respond_with(successful_purchase_response) + end + + def test_successful_verify + response = stub_comms do + @gateway.verify(@credit_card, @options) + end.respond_with(successful_authorize_response, successful_void_response) + assert_success response + assert_equal 'OK2657', response.params['authcode'] + end + + def test_successful_verify_with_failed_void + response = stub_comms do + @gateway.verify(@credit_card, @options) + end.respond_with(successful_authorize_response, failed_void_response) + assert_success response + assert_equal 'Approved', response.message + end + + def test_unsuccessful_verify + response = stub_comms do + @gateway.verify(@credit_card, @options) + end.respond_with(failed_authorize_response, successful_void_response) + assert_failure response + assert_equal 'Invalid Account Number', response.message + end + + def test_scrub + assert @gateway.supports_scrubbing? + assert_equal @gateway.scrub(pre_scrubbed), post_scrubbed + end + + def test_echeck_scrub + assert @gateway.supports_scrubbing? + assert_equal @gateway.scrub(echeck_pre_scrubbed), echeck_post_scrubbed + end + + private + + def successful_purchase_response + %( + <Response xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://TPISoft.com/SmartPayments/"> + <Result>0</Result> + <RespMSG>Approved</RespMSG> + <Message>APPROVAL</Message> + <AuthCode>OK9757</AuthCode> + <PNRef>837495</PNRef> + <HostCode>837495</HostCode> + <GetAVSResult>Z</GetAVSResult> + <GetAVSResultTXT>5 Zip Match No Address Match</GetAVSResultTXT> + <GetStreetMatchTXT>No Match</GetStreetMatchTXT> + <GetZipMatchTXT>Match</GetZipMatchTXT> + <GetCVResult>P</GetCVResult> + <GetCVResultTXT>Service Not Available</GetCVResultTXT> + <GetCommercialCard>False</GetCommercialCard> + <ExtData>InvNum=1,CardType=VISA</ExtData> + </Response> + ) + end + + def failed_purchase_response + %( + <Response xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://TPISoft.com/SmartPayments/"> + <Result>110</Result> + <RespMSG>Duplicate Transaction</RespMSG> + <Message>Duplicate transaction</Message> + <PNRef>837614</PNRef> + <HostCode>837613</HostCode> + <GetGetOrigResult>OK2538</GetGetOrigResult> + <GetCommercialCard>False</GetCommercialCard> + <ExtData>InvNum=1,CardType=VISA</ExtData> + </Response> + ) + end + + def successful_purchase_with_echeck_response + %( + <Response xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://TPISoft.com/SmartPayments/"> + <Result>0</Result> + <RespMSG>Approved</RespMSG> + <Message>APPROVAL</Message> + <AuthCode>OK6269</AuthCode> + <PNRef>1316661</PNRef> + <GetCommercialCard>False</GetCommercialCard> + </Response> + ) + end + + def successful_authorize_response + %( + <Response xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://TPISoft.com/SmartPayments/"> + <Result>0</Result> + <RespMSG>Approved</RespMSG> + <Message>APPROVAL</Message> + <AuthCode>OK2657</AuthCode> + <PNRef>838662</PNRef> + <HostCode>838662</HostCode> + <GetAVSResult>Z</GetAVSResult> + <GetAVSResultTXT>5 Zip Match No Address Match</GetAVSResultTXT> + <GetStreetMatchTXT>No Match</GetStreetMatchTXT> + <GetZipMatchTXT>Match</GetZipMatchTXT> + <GetCVResult>P</GetCVResult> + <GetCVResultTXT>Service Not Available</GetCVResultTXT> + <GetCommercialCard>False</GetCommercialCard> + <ExtData>InvNum=ebd7cd3348d4789e2cabf31e5914ef24,CardType=VISA</ExtData> + </Response> + ) + end + + def failed_authorize_response + %( + <Response xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://TPISoft.com/SmartPayments/"> + <Result>23</Result> + <RespMSG>Invalid Account Number</RespMSG> + <ExtData>InvNum=73c21272e01d0716d3a3262d8faf5bea,CardType=VISA</ExtData> + </Response> + ) + end + + def successful_capture_response + %( + <Response xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://TPISoft.com/SmartPayments/"> + <Result>0</Result> + <RespMSG>Approved</RespMSG> + <Message>APPROVAL</Message> + <AuthCode>OK2667</AuthCode> + <PNRef>838665</PNRef> + <GetCommercialCard>False</GetCommercialCard> + </Response> + ) + end + + def failed_capture_response + %( + <Response xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://TPISoft.com/SmartPayments/"> + <Result>1000</Result> + <RespMSG>Error - Unknown Card Type : </RespMSG> + </Response> + ) + end + + def successful_refund_response + %( + <Response xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://TPISoft.com/SmartPayments/"> + <Result>0</Result> + <RespMSG>Approved</RespMSG> + <Message>APPROVAL</Message> + <AuthCode>868686</AuthCode> + <PNRef>838669</PNRef> + <HostCode>838669</HostCode> + <GetCommercialCard>False</GetCommercialCard> + </Response> + ) + end + + def failed_refund_response + %( + <Response xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://TPISoft.com/SmartPayments/"> + <Result>19</Result> + <RespMSG>Original Transaction ID Not Found</RespMSG> + <Message>Original PNRef is required.</Message> + </Response> + ) + end + + def successful_void_response + %( + <Response xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://TPISoft.com/SmartPayments/"> + <Result>0</Result> + <RespMSG>Approved</RespMSG> + <Message>APPROVAL</Message> + <AuthCode>OK2707</AuthCode> + <PNRef>838671</PNRef> + <GetCommercialCard>False</GetCommercialCard> + </Response> + ) + end + + def failed_void_response + %( + <Response xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://TPISoft.com/SmartPayments/"> + <Result>19</Result> + <RespMSG>Original Transaction ID Not Found</RespMSG> + <Message>Original PNRef is required.</Message> + </Response> + ) + end + + def successful_store_response + %( + <CardVaultResponse xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://TPISoft.com/SmartPayments/"> + <Result>0</Result> + <Message>Success</Message> + <Token>4005552646800019</Token> + <CustomerPaymentInfoKey>128962</CustomerPaymentInfoKey> + <ExpDate>0916</ExpDate> + <NameOnCard>Longbob Longsen</NameOnCard> + <Street /> + <Zip /> + </CardVaultResponse> + ) + end + + def pre_scrubbed + %( + <- "Amount=1.00&PNRef=&InvNum=1914676616596fbf4c467c02facb81d1&CardNum=4000300011100000&ExpDate=0915&MagData=&NameOnCard=Longbob+Longsen&Zip=K1C2N6&Street=1234+My+Street&CVNum=123&ExtData=%3CForce%3ET%3C%2FForce%3E&UserName=Spre3676&Password=H3392nc5&TransType=Auth" + ) + end + + def post_scrubbed + %( + <- "Amount=1.00&PNRef=&InvNum=1914676616596fbf4c467c02facb81d1&CardNum=[FILTERED]&ExpDate=0915&MagData=&NameOnCard=Longbob+Longsen&Zip=K1C2N6&Street=1234+My+Street&CVNum=[FILTERED]&ExtData=%3CForce%3ET%3C%2FForce%3E&UserName=Spre3676&Password=[FILTERED]&TransType=Auth" + ) + end + + def echeck_pre_scrubbed + %( + <- UserName=Spre3676&Password=H3392nc5&TransType=Sale&Amount=1.00&PNRef=&InvNum=b3ca834652da047353eb96433b2ab7d8&CardNum=&ExpDate=&MagData=&NameOnCard=&Zip=K1C2N6&Street=456+My+Street&CVNum=&ExtData=%3CForce%3ET%3C%2FForce%3E&CheckNum=1001&TransitNum=490000018&AccountNum=1234567890&NameOnCheck=John+Doe&MICR=" + ) + end + + def echeck_post_scrubbed + %( + <- UserName=Spre3676&Password=[FILTERED]&TransType=Sale&Amount=1.00&PNRef=&InvNum=b3ca834652da047353eb96433b2ab7d8&CardNum=[FILTERED]&ExpDate=&MagData=&NameOnCard=&Zip=K1C2N6&Street=456+My+Street&CVNum=[FILTERED]&ExtData=%3CForce%3ET%3C%2FForce%3E&CheckNum=1001&TransitNum=[FILTERED]&AccountNum=[FILTERED]&NameOnCheck=John+Doe&MICR=" + ) + end +end diff --git a/test/unit/gateways/cams_test.rb b/test/unit/gateways/cams_test.rb new file mode 100644 index 00000000000..23102e41c39 --- /dev/null +++ b/test/unit/gateways/cams_test.rb @@ -0,0 +1,209 @@ +require 'test_helper' + +class CamsTest < Test::Unit::TestCase + def setup + @gateway = CamsGateway.new( + username: 'testintegrationc', + password: 'password9' + ) + + @credit_card = credit_card('4111111111111111', :month => 5, :year => 10) + @bad_credit_card = credit_card('4242424245555555', :month => 5, :year => 10) + @amount = 100 + + @options = { + order_id: Time.now.to_s, + billing_address: address, + description: 'Store Purchase' + } + end + + def test_successful_purchase + @gateway.expects(:ssl_post).returns(successful_purchase_response) + + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + + assert_equal '2654605773#54321', response.authorization + end + + def test_failed_purchase + @gateway.expects(:ssl_post).returns(failed_purchase_response) + + response = @gateway.purchase(@amount, @bad_credit_card, @options) + assert_failure response + assert_equal Gateway::STANDARD_ERROR_CODE[:card_declined], response.error_code + end + + def test_successful_authorize + @gateway.expects(:ssl_post).returns(successful_authorize_response) + + response = @gateway.authorize(@amount, @credit_card, @options) + assert_success response + end + + def test_failed_authorize + @gateway.expects(:ssl_post).returns(successful_authorize_response) + + response = @gateway.authorize(@amount, @bad_credit_card, @options) + assert_success response + end + + def test_successful_capture + authorization = '12345678#54321' + @gateway.expects(:ssl_post).returns(successful_capture_response) + + assert capture = @gateway.capture(@amount, authorization) + assert_success capture + end + + def test_failed_capture + @gateway.expects(:ssl_post).returns(failed_capture_response) + + response = @gateway.capture(nil, '') + assert_failure response + end + + def test_successful_refund + authorization = '12345678#54321' + @gateway.expects(:ssl_post).returns(successful_refund_response) + + assert refund = @gateway.refund(nil, authorization) + assert_success refund + end + + def test_failed_refund + @gateway.expects(:ssl_post).returns(failed_refund_response) + + response = @gateway.refund(nil, '') + assert_failure response + end + + def test_successful_void + authorization = '12345678#54321' + @gateway.expects(:ssl_post).returns(successful_void_response) + + assert void = @gateway.void(authorization) + assert_success void + end + + def test_failed_void + @gateway.expects(:ssl_post).returns(failed_void_response) + + response = @gateway.void('') + assert_failure response + end + + def test_successful_verify + @gateway.expects(:ssl_post).returns(successful_verify_response) + + assert verify = @gateway.verify(@credit_card, @options) + assert_success verify + end + + def test_failed_verify + @gateway.expects(:ssl_post).returns(failed_verify_response) + + assert verify = @gateway.verify(@bad_credit_card, @options) + assert_failure verify + end + + def test_scrub + assert @gateway.supports_scrubbing? + assert_equal @gateway.scrub(pre_scrubbed), post_scrubbed + end + + private + + def pre_scrubbed + <<-PRE_SCRUBBED +opening connection to secure.centralams.com:443... +opened +starting SSL for secure.centralams.com:443... +SSL established +<- "POST /gw/api/transact.php HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: secure.centralams.com\r\nContent-Length: 249\r\n\r\n" +<- "amount=1.03&currency=USD&ccnumber=4111111111111111&ccexp=0916&firstname=Longbob&lastname=Longsen&address1=1234 My Street&address2=Apt 1&city=Ottawa&state=ON&zip=K1C2N6&country=US&phone=(555)555-5555&type=&password=password9&username=testintegrationc" +-> "HTTP/1.1 200 OK\r\n" +-> "Date: Tue, 21 Apr 2015 23:27:05 GMT\r\n" +-> "Server: Apache\r\n" +-> "Content-Length: 132\r\n" +-> "Connection: close\r\n" +-> "Content-Type: text/html; charset=UTF-8\r\n" +-> "\r\n" +reading 132 bytes... +-> "response=1&responsetext=SUCCESS&authcode=123456&transactionid=2654605773&avsresponse=N&cvvresponse=&orderid=&type=&response_code=100" +read 132 bytes +Conn close + PRE_SCRUBBED + end + + def post_scrubbed + <<-POST_SCRUBBED +opening connection to secure.centralams.com:443... +opened +starting SSL for secure.centralams.com:443... +SSL established +<- "POST /gw/api/transact.php HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: secure.centralams.com\r\nContent-Length: 249\r\n\r\n" +<- "amount=1.03&currency=USD&ccnumber=[FILTERED]&ccexp=0916&firstname=Longbob&lastname=Longsen&address1=1234 My Street&address2=Apt 1&city=Ottawa&state=ON&zip=K1C2N6&country=US&phone=(555)555-5555&type=&password=[FILTERED]&username=testintegrationc" +-> "HTTP/1.1 200 OK\r\n" +-> "Date: Tue, 21 Apr 2015 23:27:05 GMT\r\n" +-> "Server: Apache\r\n" +-> "Content-Length: 132\r\n" +-> "Connection: close\r\n" +-> "Content-Type: text/html; charset=UTF-8\r\n" +-> "\r\n" +reading 132 bytes... +-> "response=1&responsetext=SUCCESS&authcode=123456&transactionid=2654605773&avsresponse=N&cvvresponse=&orderid=&type=&response_code=100" +read 132 bytes +Conn close + POST_SCRUBBED + end + + def successful_purchase_response + %(response=1&responsetext=SUCCESS&authcode=54321&transactionid=2654605773&avsresponse=N&cvvresponse=&orderid=&type=&response_code=100) + end + + def failed_purchase_response + %(response=3&responsetext=Invalid Credit Card Number REFID:3154273850&authcode=&transactionid=&avsresponse=&cvvresponse=&orderid=&type=&response_code=300) + end + + def successful_authorize_response + %(response=1&responsetext=SUCCESS&authcode=123456&transactionid=2655819372&avsresponse=N&cvvresponse=N&orderid=&type=auth&response_code=100) + end + + def failed_authorize_response + %(response=3&responsetext=Invalid Credit Card Number REFID:3154292176&authcode=&transactionid=&avsresponse=&cvvresponse=&orderid=&type=auth&response_code=300) + end + + def successful_capture_response + %(response=1&responsetext=SUCCESS&authcode=123456&transactionid=2655840929&avsresponse=&cvvresponse=&orderid=&type=capture&response_code=100) + end + + def failed_capture_response + %(response=3&responsetext=Invalid Transaction ID / Object ID specified: REFID:3154293596&authcode=&transactionid=&avsresponse=&cvvresponse=&orderid=&type=capture&response_code=300) + end + + def successful_refund_response + %(response=1&responsetext=SUCCESS&authcode=&transactionid=2655841010&avsresponse=&cvvresponse=&orderid=&type=refund&response_code=100) + end + + def failed_refund_response + %(response=3&responsetext=Invalid Transaction ID / Object ID specified: REFID:3154293755&authcode=&transactionid=&avsresponse=&cvvresponse=&orderid=&type=refund&response_code=300) + end + + def successful_void_response + %(response=1&responsetext=Transaction Void Successful&authcode=123456&transactionid=2655848058&avsresponse=&cvvresponse=&orderid=&type=void&response_code=100) + end + + def failed_void_response + %(response=3&responsetext=Invalid Transaction ID / Object ID specified: REFID:3154293864&authcode=&transactionid=&avsresponse=&cvvresponse=&orderid=&type=void&response_code=300) + end + + def successful_verify_response + %(response=1&responsetext=&authcode=&transactionid=2656803675&avsresponse=&cvvresponse=&orderid=&type=verify&response_code=100) + end + + def failed_verify_response + %(response=3&responsetext=Invalid Credit Card Number REFID:3154354764&authcode=&transactionid=&avsresponse=&cvvresponse=&orderid=&type=verify&response_code=300) + end +end diff --git a/test/unit/gateways/card_connect_test.rb b/test/unit/gateways/card_connect_test.rb new file mode 100644 index 00000000000..c4736f57616 --- /dev/null +++ b/test/unit/gateways/card_connect_test.rb @@ -0,0 +1,335 @@ +require 'test_helper' + +class CardConnectTest < Test::Unit::TestCase + include CommStub + def setup + @gateway = CardConnectGateway.new(username: 'username', password: 'password', merchant_id: 'merchand_id') + @credit_card = credit_card('4788250000121443') + @declined_card = credit_card('4387751111111053') + @amount = 100 + @check = check(routing_number: '053000196') + + @options = { + order_id: '1', + billing_address: address, + description: 'Store Purchase' + } + end + + def test_incorrect_domain + assert_raise(ArgumentError) { + CardConnectGateway.new(username: 'username', password: 'password', merchant_id: 'merchand_id', domain: 'www.google.com') + } + end + + def test_successful_purchase + @gateway.expects(:ssl_request).returns(successful_purchase_response) + + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + + assert_equal '363652261392', response.authorization + assert response.test? + end + + def test_successful_purchase_with_echeck + @gateway.expects(:ssl_request).returns(successful_echeck_purchase_response) + + response = @gateway.purchase(@amount, @check, @options) + assert_success response + + assert_equal '010136262668', response.authorization + assert response.test? + end + + def test_failed_purchase + @gateway.expects(:ssl_request).returns(failed_purchase_response) + + response = @gateway.purchase(@amount, @declined_card, @options) + assert_failure response + assert_equal Gateway::STANDARD_ERROR_CODE[:card_declined], response.error_code + end + + def test_failed_purchase_with_echeck + @gateway.expects(:ssl_request).returns(failed_echeck_purchase_response) + + response = @gateway.purchase(@amount, check(routing_number: '23433'), @options) + assert_failure response + assert_equal Gateway::STANDARD_ERROR_CODE[:card_declined], response.error_code + end + + def test_successful_authorize + @gateway.expects(:ssl_request).returns(successful_authorize_response) + + response = @gateway.authorize(@amount, @credit_card, @options) + assert_success response + + assert_equal '363168161558', response.authorization + assert response.test? + end + + def test_failed_authorize + @gateway.expects(:ssl_request).returns(failed_authorize_response) + + response = @gateway.authorize(@amount, @declined_card, @options) + assert_failure response + assert_equal Gateway::STANDARD_ERROR_CODE[:card_declined], response.error_code + end + + def test_successful_capture + @gateway.expects(:ssl_request).returns(successful_capture_response) + + response = @gateway.capture(@amount, '363168161558', @options) + assert_success response + + assert_equal '363168161558', response.authorization + assert response.test? + end + + def test_failed_capture + @gateway.expects(:ssl_request).returns(failed_capture_response) + + response = @gateway.capture(@amount, '23221', @options) + assert_failure response + + assert_equal '23221', response.authorization + assert response.test? + + assert_equal Gateway::STANDARD_ERROR_CODE[:processing_error], response.error_code + end + + def test_successful_refund + @gateway.expects(:ssl_request).returns(successful_refund_response) + + response = @gateway.refund(@amount, '36366126178', @options) + assert_success response + + assert_equal '363661261786', response.authorization + assert response.test? + end + + def test_failed_refund + @gateway.expects(:ssl_request).returns(failed_refund_response) + + response = @gateway.refund(@amount, '23221', @options) + assert_failure response + + assert_equal '23221', response.authorization + assert response.test? + + assert_equal Gateway::STANDARD_ERROR_CODE[:processing_error], response.error_code + end + + def test_successful_void + @gateway.expects(:ssl_request).returns(successful_void_response) + + response = @gateway.void('363750268295') + assert_success response + + assert_equal '363664261982', response.authorization + assert response.test? + end + + def test_failed_void + @gateway.expects(:ssl_request).returns(failed_void_response) + + response = @gateway.void('23221') + assert_failure response + + assert response.test? + + assert_equal Gateway::STANDARD_ERROR_CODE[:processing_error], response.error_code + end + + def test_successful_verify + @gateway.expects(:ssl_request).returns(successful_verify_response) + + response = @gateway.verify(@credit_card, @options) + assert_success response + + assert_equal '363272166977', response.authorization + assert response.test? + end + + def test_successful_verify_with_failed_void + response = stub_comms(@gateway, :ssl_request) do + @gateway.verify(@credit_card, @options) + end.respond_with(successful_authorize_response, failed_void_response) + assert_success response + end + + def test_failed_verify + @gateway.expects(:ssl_request).returns(failed_verify_response) + + response = @gateway.verify(@declined_card, @options) + assert_failure response + assert_equal 'Insufficient funds', response.message + end + + def test_successful_store + @gateway.expects(:ssl_request).returns(successful_store_response) + response = @gateway.store(@credit_card, @options) + + assert_success response + assert_equal 'Profile Saved', response.message + assert_equal '1|16700875781344019340', response.authorization + end + + def test_failed_store + @gateway.expects(:ssl_request).returns(failed_store_response) + response = @gateway.store(@credit_card, @options) + + assert_failure response + end + + def test_successful_unstore + stub_comms(@gateway, :ssl_request) do + @gateway.unstore('1|16700875781344019340') + end.check_request do |verb, url, data, headers| + assert_equal :delete, verb + assert_match %r{16700875781344019340/1}, url + end.respond_with(successful_unstore_response) + end + + def test_failed_unstore + end + + def test_scrub + assert @gateway.supports_scrubbing? + assert_equal @gateway.scrub(pre_scrubbed), post_scrubbed + end + + def test_frontendid_is_added_to_post_data_parameters + @gateway.class.application_id = 'my_app' + stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, @credit_card, @options) + end.check_request do |_, _, body| + assert_equal 'my_app', JSON.parse(body)['frontendid'] + end.respond_with(successful_purchase_response) + ensure + @gateway.class.application_id = nil + end + + private + + def pre_scrubbed + %q( + opening connection to fts.cardconnect.com:6443... + opened + starting SSL for fts.cardconnect.com:6443... + SSL established + <- "PUT /cardconnect/rest/auth HTTP/1.1\r\nAuthorization: Basic dGVzdGluZzp0ZXN0aW5nMTIz\r\nContent-Type: application/json\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: fts.cardconnect.com:6443\r\nContent-Length: 298\r\n\r\n" + <- "{\"orderid\":null,\"ecomind\":\"E\",\"amount\":\"1.00\",\"name\":\"Longbob Longsen\",\"account\":\"4000100011112224\",\"expiry\":\"0918\",\"cvv2\":\"123\",\"currency\":\"USD\",\"address\":\"456 My Street\",\"city\":\"Ottawa\",\"region\":\"ON\",\"country\":\"CA\",\"postal\":\"K1C2N6\",\"phone\":\"(555)555-5555\",\"capture\":\"Y\",\"merchid\":\"496160873888\"}" + -> "HTTP/1.1 200 OK\r\n" + -> "X-FRAME-OPTIONS: DENY\r\n" + -> "Content-Type: application/json\r\n" + -> "Content-Length: 281\r\n" + -> "Date: Fri, 29 Dec 2017 23:51:22 GMT\r\n" + -> "Server: CardConnect\r\n" + -> "Connection: close\r\n" + -> "Set-Cookie: BIGipServerphu-smb-vip_8080=!3EyEfCvmvK/UDgCOaMq7McVUJtfXHaj0/1BWyxbacLNntp1E0Upt2onAMTKRSSu6r6mZaKuZm7N9ais=; path=/; Httponly; Secure\r\n" + -> "\r\n" + reading 281 bytes... + -> "{\"amount\":\"1.00\",\"resptext\":\"Approval\",\"commcard\":\" C \",\"cvvresp\":\"M\",\"batchid\":\"1900941444\",\"avsresp\":\" \",\"respcode\":\"00\",\"merchid\":\"496160873888\",\"token\":\"9405701444882224\",\"authcode\":\"PPS568\",\"respproc\":\"FNOR\",\"retref\":\"363743267882\",\"respstat\":\"A\",\"account\":\"9405701444882224\"}" + read 281 bytes + Conn close + ) + end + + def post_scrubbed + %q( + opening connection to fts.cardconnect.com:6443... + opened + starting SSL for fts.cardconnect.com:6443... + SSL established + <- "PUT /cardconnect/rest/auth HTTP/1.1\r\nAuthorization: Basic [FILTERED]\r\nContent-Type: application/json\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: fts.cardconnect.com:6443\r\nContent-Length: 298\r\n\r\n" + <- "{\"orderid\":null,\"ecomind\":\"E\",\"amount\":\"1.00\",\"name\":\"Longbob Longsen\",\"account\":\"[FILTERED]\",\"expiry\":\"0918\",\"cvv2\":\"[FILTERED]\",\"currency\":\"USD\",\"address\":\"456 My Street\",\"city\":\"Ottawa\",\"region\":\"ON\",\"country\":\"CA\",\"postal\":\"K1C2N6\",\"phone\":\"(555)555-5555\",\"capture\":\"Y\",\"merchid\":\"[FILTERED]\"}" + -> "HTTP/1.1 200 OK\r\n" + -> "X-FRAME-OPTIONS: DENY\r\n" + -> "Content-Type: application/json\r\n" + -> "Content-Length: 281\r\n" + -> "Date: Fri, 29 Dec 2017 23:51:22 GMT\r\n" + -> "Server: CardConnect\r\n" + -> "Connection: close\r\n" + -> "Set-Cookie: BIGipServerphu-smb-vip_8080=!3EyEfCvmvK/UDgCOaMq7McVUJtfXHaj0/1BWyxbacLNntp1E0Upt2onAMTKRSSu6r6mZaKuZm7N9ais=; path=/; Httponly; Secure\r\n" + -> "\r\n" + reading 281 bytes... + -> "{\"amount\":\"1.00\",\"resptext\":\"Approval\",\"commcard\":\" C \",\"cvvresp\":\"M\",\"batchid\":\"1900941444\",\"avsresp\":\" \",\"respcode\":\"00\",\"merchid\":\"[FILTERED]\",\"token\":\"[FILTERED]\",\"authcode\":\"PPS568\",\"respproc\":\"FNOR\",\"retref\":\"363743267882\",\"respstat\":\"A\",\"account\":\"[FILTERED]\"}" + read 281 bytes + Conn close + ) + end + + def successful_purchase_response + '{"amount":"1.00","resptext":"Approval","commcard":" C ","cvvresp":"M","batchid":"1900941444","avsresp":" ","respcode":"00","merchid":"496160873888","token":"9405701444882224","authcode":"PPS500","respproc":"FNOR","retref":"363652261392","respstat":"A","account":"9405701444882224"}' + end + + def successful_echeck_purchase_response + '{"amount":"1.00","resptext":"Success","cvvresp":"U","batchid":"1900940633","avsresp":"U","respcode":"00","merchid":"542041","token":"9051769384108535","authcode":"GF7PBR","respproc":"PSTR","retref":"010136262668","respstat":"A","account":"9051769384108535"}' + end + + def failed_echeck_purchase_response + '{"respproc":"PPS","amount":"0.00","resptext":"Invalid card","cardproc":"PSTR","retref":"010108164081","respstat":"C","respcode":"11","account":"9235405400368535","merchid":"542041","token":"9235405400368535"}' + end + + def failed_purchase_response + '{"respproc":"FNOR","amount":"0.00","resptext":"Insufficient funds","cardproc":"FNOR","commcard":" C ","retref":"005533134378","respstat":"C","respcode":"NU","account":"9435885049491053","merchid":"496160873888","token":"9435885049491053"}' + end + + def successful_authorize_response + '{"amount":"1.00","resptext":"Approval","commcard":" C ","cvvresp":"M","avsresp":" ","respcode":"00","merchid":"496160873888","token":"9405701444882224","authcode":"PPS454","respproc":"FNOR","retref":"363168161558","respstat":"A","account":"9405701444882224"}' + end + + def failed_authorize_response + '{"respproc":"FNOR","amount":"0.00","resptext":"Insufficient funds","cardproc":"FNOR","commcard":" C ","retref":"005737235263","respstat":"C","respcode":"NU","account":"9435885049491053","merchid":"496160873888","token":"9435885049491053"}' + end + + def successful_capture_response + '{"respproc":"FNOR","amount":"1.00","resptext":"Approval","setlstat":"Queued for Capture","commcard":" C ","retref":"363168161558","respstat":"A","respcode":"00","batchid":"1900941444","account":"9405701444882224","merchid":"496160873888","token":"9405701444882224"}' + end + + def failed_capture_response + '{"respproc":"PPS","resptext":"Txn not found","retref":"23221","respstat":"C","respcode":"29","batchid":"-1","account":""}' + end + + def successful_refund_response + '{"respproc":"PPS","amount":"1.00","resptext":"Approval","retref":"363661261786","respstat":"A","respcode":"00","merchid":"496160873888"}' + end + + def failed_refund_response + '{"respproc":"PPS","resptext":"Txn not found","retref":"23221","respcode":"29","respstat":"C"}' + end + + def successful_void_response + '{"authcode":"REVERS","respproc":"FNOR","amount":"0.00","resptext":"Approval","currency":"USD","retref":"363664261982","respstat":"A","respcode":"00","merchid":"496160873888"}' + end + + def failed_void_response + '{"respproc":"PPS","resptext":"Txn not found","retref":"23221","respcode":"29","respstat":"C"}' + end + + def successful_verify_response + '{"amount":"0.00","resptext":"Approval","commcard":" C ","cvvresp":"M","avsresp":" ","respcode":"00","merchid":"496160873888","token":"9405701444882224","authcode":"PPS585","respproc":"FNOR","retref":"363272166977","respstat":"A","account":"9405701444882224"}' + end + + def failed_verify_response + '{"respproc":"FNOR","amount":"0.00","resptext":"Insufficient funds","cardproc":"FNOR","commcard":" C ","retref":"005101240599","respstat":"C","respcode":"NU","account":"9435885049491053","merchid":"496160873888","token":"9435885049491053"}' + end + + def successful_store_response + '{"country":"CA","gsacard":"N","address":"456 My Street Apt 1","resptext":"Profile Saved","city":"Ottawa","acctid":"1","respcode":"09","defaultacct":"Y","accttype":"VISA","token":"9477709629051443","respproc":"PPS","phone":"(555)555-555","profileid":"16700875781344019340","name":"Longbob Longsen","auoptout":"N","postal":"K1C2N6","expiry":"0919","region":"ON","respstat":"A"}' + end + + def successful_unstore_response + '{"respproc":"PPS","resptext":"Profile Deleted","respstat":"A","respcode":"08"}' + end + + def failed_store_response + # Best-guess based on documentation + '{"country":"CA","gsacard":"N","address":"456 My Street Apt 1","resptext":"Profile Saved","city":"Ottawa","acctid":"1","respcode":"09","defaultacct":"Y","accttype":"VISA","token":"9477709629051443","respproc":"PPS","phone":"(555)555-555","profileid":"16700875781344019340","name":"Longbob Longsen","auoptout":"N","postal":"K1C2N6","expiry":"0919","region":"ON","respstat":"C"}' + end + + def failed_unstore_response + '{"respproc":"PPS","resptext":"Profile not found","respstat":"C","respcode":"96"}' + end +end diff --git a/test/unit/gateways/card_save_test.rb b/test/unit/gateways/card_save_test.rb index 8d1eca51247..bb19495a1f1 100644 --- a/test/unit/gateways/card_save_test.rb +++ b/test/unit/gateways/card_save_test.rb @@ -2,7 +2,7 @@ class CardSaveTest < Test::Unit::TestCase def setup - Base.gateway_mode = :test + Base.mode = :test @gateway = CardSaveGateway.new(:login => 'login', :password => 'password') @credit_card = credit_card @amount = 100 @@ -59,7 +59,7 @@ def test_successful_authorize def test_successful_capture @gateway.expects(:ssl_post).returns(capture_successful) - assert response = @gateway.capture(1111, "1;110706124418747501702211;702211") + assert response = @gateway.capture(1111, '1;110706124418747501702211;702211') assert_success response assert_equal('110706124418747501702211', response.authorization) assert response.test? diff --git a/test/unit/gateways/card_stream_modern_test.rb b/test/unit/gateways/card_stream_modern_test.rb deleted file mode 100644 index 6f68e4e73d3..00000000000 --- a/test/unit/gateways/card_stream_modern_test.rb +++ /dev/null @@ -1,109 +0,0 @@ -require 'test_helper' - -class CardStreamModernTest < Test::Unit::TestCase - def setup - @gateway = CardStreamModernGateway.new( - :login => 'login' - ) - - @visacreditcard = credit_card('4929421234600821', - :month => '12', - :year => '2014', - :verification_value => '356', - :brand => :visa - ) - - @visacredit_options = { - :billing_address => { - :address1 => "Flat 6, Primrose Rise", - :address2 => "347 Lavender Road", - :city => "", - :state => "Northampton", - :zip => 'NN17 8YG ' - }, - :order_id => generate_unique_id, - :description => 'AM test purchase' - } - end - - def test_successful_visacreditcard_authorization - @gateway.expects(:ssl_post).returns(successful_authorization_response) - - assert responseAuthorization = @gateway.authorize(142, @visacreditcard, @visacredit_options) - assert_equal 'APPROVED', responseAuthorization.message - assert_success responseAuthorization - assert responseAuthorization.test? - assert !responseAuthorization.authorization.blank? - end - - def test_successful_visacreditcard_capture - @gateway.expects(:ssl_post).returns(successful_capture_response) - - assert responseCapture = @gateway.capture(142, 'authorization', @visacredit_options) - assert_equal 'APPROVED', responseCapture.message - assert_success responseCapture - assert responseCapture.test? - end - - def test_successful_visacreditcard_refund - @gateway.expects(:ssl_post).returns(successful_refund_response) - - assert responseRefund = @gateway.refund(142, "authorization", @visacredit_options) - assert_equal 'APPROVED', responseRefund.message - assert_success responseRefund - assert responseRefund.test? - end - - def test_successful_visacreditcard_cancellation - @gateway.expects(:ssl_post).returns(successful_cancellation_response) - - assert responseRefund = @gateway.void("authorization", @visacredit_options) - assert_equal 'APPROVED', responseRefund.message - assert_success responseRefund - assert responseRefund.test? - end - - def test_successful_visacreditcard_purchase - @gateway.expects(:ssl_post).returns(successful_purchase_response) - - assert responseRefund = @gateway.purchase(142, @visacreditcard, @visacredit_options) - assert_equal 'APPROVED', responseRefund.message - assert_success responseRefund - assert responseRefund.test? - end - - def test_declined_mastercard_purchase - @gateway.expects(:ssl_post).returns(failed_purchase_card_declined_response) - - assert response = @gateway.purchase(10000, @visacreditcard, @visacredit_options) - assert_equal 'CARD DECLINED', response.message - assert_failure response - assert response.test? - end - - private - - def successful_authorization_response - "merchantID=0000000&threeDSEnabled=Y&merchantDescription=General+test+account+with+AVS%2FCV2+checking&amount=142&currencyCode=826&transactionUnique=fadc4985c51fc55ca349c45a79136ade&orderRef=AM+test+purchase&customerName=Longbob+Longsen&customerAddress=Flat+6%2C+Primrose+Rise+347+Lavender+Road&customerPostCode=NN17+8YG+&action=PREAUTH&type=1&countryCode=826&merchantAlias=0000992&remoteAddress=80.229.33.63&responseCode=0&responseMessage=AUTHCODE%3AD24577&xref=13021914RV01VR56LR16FNF&threeDSEnrolled=U&threeDSXID=00000000000004717472&transactionID=4717472&transactionPreviousID=0&timestamp=2013-02-19+14%3A02%3A19&amountReceived=142&avscv2ResponseCode=222100&avscv2ResponseMessage=ALL+MATCH&avscv2AuthEntity=merchant+host&cv2Check=matched&addressCheck=matched&postcodeCheck=matched&cardNumberMask=%2A%2A%2A%2A%2A%2A%2A%2A%2A%2A%2A%2A0821&cardTypeCode=VC&cardType=Visa+Credit&threeDSErrorCode=-1&threeDSErrorDescription=Error+while+attempting+to+send+the+request+to%3A+https%3A%2F%2F3dstest.universalpaymentgateway.com%3A4343%2FAPI%0A%0DPlease+make+sure+that+ActiveMerchant+server+is+running+and+the+URL+is+valid.+ERROR_INTERNET_CANNOT_CONNECT%3A+The+attempt+to+connect+to+the+server+failed.&threeDSMerchantPref=PROCEED&threeDSVETimestamp=2013-02-19+14%3A01%3A56&currencyExponent=2&responseStatus=0&merchantName=CARDSTREAM+TEST&merchantID2=100001" - end - - def successful_capture_response - "merchantID=0000000&threeDSEnabled=Y&merchantDescription=General+test+account+with+AVS%2FCV2+checking&xref=13021914XW02YJ20MQ37RMT&amount=142&currencyCode=826&action=SALE&type=1&countryCode=826&merchantAlias=0000992&remoteAddress=80.229.33.63&responseCode=0&responseMessage=AUTHCODE%3A39657X&customerName=Longbob+Longsen&customerAddress=Flat+6%2C+Primrose+Rise+347+Lavender+Road&customerPostCode=NN17+8YG+&transactionUnique=fadc4985c51fc55ca349c45a79136ade&orderRef=AM+test+purchase&amountReceived=142&avscv2ResponseCode=422100&avscv2ResponseMessage=ALL+MATCH&avscv2AuthEntity=merchant+host&cv2Check=not+matched&addressCheck=matched&postcodeCheck=matched&threeDSXID=00000000000004717475&threeDSEnrolled=U&threeDSErrorCode=-1&threeDSErrorDescription=Error+while+attempting+to+send+the+request+to%3A+https%3A%2F%2F3dstest.universalpaymentgateway.com%3A4343%2FAPI%0A%0DPlease+make+sure+that+ActiveMerchant+server+is+running+and+the+URL+is+valid.+ERROR_INTERNET_CANNOT_CONNECT%3A+The+attempt+to+connect+to+the+server+failed.&threeDSMerchantPref=PROCEED&threeDSVETimestamp=2013-02-19+14%3A02%3A20&cardTypeCode=VC&cardNumberMask=%2A%2A%2A%2A%2A%2A%2A%2A%2A%2A%2A%2A0821&transactionID=4717475&transactionPreviousID=4717472&timestamp=2013-02-19+14%3A02%3A44&cardType=Visa+Credit&currencyExponent=2&responseStatus=0&merchantName=CARDSTREAM+TEST&merchantID2=100001" - end - - def successful_refund_response - "merchantID=0000000&threeDSEnabled=Y&merchantDescription=General+test+account+with+AVS%2FCV2+checking&xref=13021914NT06BM21GJ15VJH&amount=142&currencyCode=826&action=REFUND&type=1&countryCode=826&merchantAlias=0000992&remoteAddress=80.229.33.63&responseCode=0&responseMessage=REFUNDACCEPTED&customerName=Longbob+Longsen&customerAddress=Flat+6%2C+Primrose+Rise+347+Lavender+Road&customerPostCode=NN17+8YG+&transactionUnique=c7981d78d217cf3cfda6559921e31c4a&orderRef=AM+test+purchase&amountReceived=142&avscv2ResponseCode=222100&avscv2ResponseMessage=ALL+MATCH&avscv2AuthEntity=merchant+host&cv2Check=matched&addressCheck=matched&postcodeCheck=matched&threeDSXID=00000000000004717488&threeDSEnrolled=U&threeDSErrorCode=-1&threeDSErrorDescription=Error+while+attempting+to+send+the+request+to%3A+https%3A%2F%2F3dstest.universalpaymentgateway.com%3A4343%2FAPI%0A%0DPlease+make+sure+that+ActiveMerchant+server+is+running+and+the+URL+is+valid.+ERROR_INTERNET_CANNOT_CONNECT%3A+The+attempt+to+connect+to+the+server+failed.&threeDSMerchantPref=PROCEED&threeDSVETimestamp=2013-02-19+14%3A05%3A58&cardTypeCode=VC&cardNumberMask=%2A%2A%2A%2A%2A%2A%2A%2A%2A%2A%2A%2A0821&threeDSRequired=N&transactionID=4717490&transactionPreviousID=4717488&timestamp=2013-02-19+14%3A06%3A21&cardType=Visa+Credit&currencyExponent=2&responseStatus=0&merchantName=CARDSTREAM+TEST&merchantID2=100001" - end - - def successful_purchase_response - "merchantID=0000000&threeDSEnabled=Y&merchantDescription=General+test+account+with+AVS%2FCV2+checking&amount=142&currencyCode=826&transactionUnique=27a594210e27846c8e9102647f210586&orderRef=AM+test+purchase&customerName=Longbob+Longsen&customerAddress=Flat+6%2C+Primrose+Rise+347+Lavender+Road&customerPostCode=NN17+8YG+&action=SALE&type=1&countryCode=826&merchantAlias=0000992&remoteAddress=80.229.33.63&responseCode=0&responseMessage=AUTHCODE%3A635959&xref=13021914LS06DW22NW22LVJ&threeDSEnrolled=U&threeDSXID=00000000000004717491&transactionID=4717491&transactionPreviousID=0&timestamp=2013-02-19+14%3A06%3A44&amountReceived=142&avscv2ResponseCode=222100&avscv2ResponseMessage=ALL+MATCH&avscv2AuthEntity=merchant+host&cv2Check=matched&addressCheck=matched&postcodeCheck=matched&cardNumberMask=%2A%2A%2A%2A%2A%2A%2A%2A%2A%2A%2A%2A0821&cardTypeCode=VC&cardType=Visa+Credit&threeDSErrorCode=-1&threeDSErrorDescription=Error+while+attempting+to+send+the+request+to%3A+https%3A%2F%2F3dstest.universalpaymentgateway.com%3A4343%2FAPI%0A%0DPlease+make+sure+that+ActiveMerchant+server+is+running+and+the+URL+is+valid.+ERROR_INTERNET_CANNOT_CONNECT%3A+The+attempt+to+connect+to+the+server+failed.&threeDSMerchantPref=PROCEED&threeDSVETimestamp=2013-02-19+14%3A06%3A22&currencyExponent=2&responseStatus=0&merchantName=CARDSTREAM+TEST&merchantID2=100001" - end - - def successful_cancellation_response - "merchantID=0000992&threeDSEnabled=Y&merchantDescription=General+test+account+with+AVS%2FCV2+checking&xref=13021918BK14KR25PZ82GHH&action=REFUND&type=1&countryCode=826&merchantAlias=0000992&remoteAddress=80.229.33.63&responseCode=0&responseMessage=REFUNDACCEPTED&amount=284&currencyCode=826&customerName=Longbob+Longsen&customerAddress=Flat+6%2C+Primrose+Rise+347+Lavender+Road&customerPostCode=NN17+8YG+&transactionUnique=86935375de4da6e1dfd63e255976d812&orderRef=AM+test+purchase&amountReceived=284&avscv2ResponseCode=222100&avscv2ResponseMessage=ALL+MATCH&avscv2AuthEntity=merchant+host&cv2Check=matched&addressCheck=matched&postcodeCheck=matched&threeDSXID=00000000000004718188&threeDSEnrolled=U&threeDSErrorCode=-1&threeDSErrorDescription=Error+while+attempting+to+send+the+request+to%3A+https%3A%2F%2F3dstest.universalpaymentgateway.com%3A4343%2FAPI%0A%0DPlease+make+sure+that+ActiveMerchant+server+is+running+and+the+URL+is+valid.+ERROR_INTERNET_CANNOT_CONNECT%3A+The+attempt+to+connect+to+the+server+failed.&threeDSMerchantPref=PROCEED&threeDSVETimestamp=2013-02-19+18%3A14%3A02&cardTypeCode=VC&cardNumberMask=%2A%2A%2A%2A%2A%2A%2A%2A%2A%2A%2A%2A0821&threeDSRequired=N&transactionID=4718190&transactionPreviousID=4718188&timestamp=2013-02-19+18%3A14%3A26&cardType=Visa+Credit&currencyExponent=2&responseStatus=0&merchantName=CARDSTREAM+TEST&merchantID2=100001" - end - - def failed_purchase_card_declined_response - "merchantID=0000000&threeDSEnabled=Y&merchantDescription=General+test+account+with+AVS%2FCV2+checking&amount=10000&currencyCode=826&transactionUnique=7385df1d9c5484142bb6be1e932cd2df&orderRef=AM+test+purchase&customerName=Longbob+Longsen&customerAddress=25+The+Larches+&customerPostCode=LE10+2RT&action=SALE&type=1&countryCode=826&merchantAlias=0000992&remoteAddress=80.229.33.63&responseCode=5&responseMessage=CARD+DECLINED&xref=13021914RQ07HK55HG29KPH&threeDSEnrolled=U&threeDSXID=00000000000004717495&transactionID=4717495&transactionPreviousID=0&timestamp=2013-02-19+14%3A08%3A18&amountReceived=0&cardNumberMask=%2A%2A%2A%2A%2A%2A%2A%2A%2A%2A%2A%2A0191&cardTypeCode=MC&cardType=Mastercard&threeDSErrorCode=-1&threeDSErrorDescription=Error+while+attempting+to+send+the+request+to%3A+https%3A%2F%2F3dstest.universalpaymentgateway.com%3A4343%2FAPI%0A%0DPlease+make+sure+that+ActiveMerchant+server+is+running+and+the+URL+is+valid.+ERROR_INTERNET_CANNOT_CONNECT%3A+The+attempt+to+connect+to+the+server+failed.&threeDSMerchantPref=PROCEED&threeDSVETimestamp=2013-02-19+14%3A07%3A55&currencyExponent=2&responseStatus=1&merchantName=CARDSTREAM+TEST&merchantID2=100001" - end -end diff --git a/test/unit/gateways/card_stream_test.rb b/test/unit/gateways/card_stream_test.rb index 9f29184a2d6..e7f225346de 100644 --- a/test/unit/gateways/card_stream_test.rb +++ b/test/unit/gateways/card_stream_test.rb @@ -1,90 +1,355 @@ require 'test_helper' class CardStreamTest < Test::Unit::TestCase + include CommStub + def setup @gateway = CardStreamGateway.new( - :login => 'LOGIN', - :password => 'PASSWORD' + :login => 'login', + :shared_secret => 'secret' + ) + + @visacreditcard = credit_card('4929421234600821', + :month => '12', + :year => '2014', + :verification_value => '356', + :brand => :visa + ) + + @visacredit_options = { + :billing_address => { + :address1 => 'Flat 6, Primrose Rise', + :address2 => '347 Lavender Road', + :city => '', + :state => 'Northampton', + :zip => 'NN17 8YG ' + }, + :order_id => generate_unique_id, + :description => 'AM test purchase' + } + + @visacredit_descriptor_options = { + :billing_address => { + :address1 => 'Flat 6, Primrose Rise', + :address2 => '347 Lavender Road', + :city => '', + :state => 'Northampton', + :zip => 'NN17 8YG ' + }, + :merchant_name => 'merchant', + :dynamic_descriptor => 'product' + } + + @declined_card = credit_card('4000300011112220', + :month => '9', + :year => '2014' ) - - @amount = 100 - @credit_card = credit_card('4242424242424242') - @options = { :order_id => '1', :billing_address => address } end - - def test_successful_purchase + + def test_successful_visacreditcard_authorization + @gateway.expects(:ssl_post).returns(successful_authorization_response) + + assert responseAuthorization = @gateway.authorize(142, @visacreditcard, @visacredit_options) + assert_equal 'APPROVED', responseAuthorization.message + assert_success responseAuthorization + assert responseAuthorization.test? + assert !responseAuthorization.authorization.blank? + end + + def test_successful_avs_and_cvv_results + @gateway.expects(:ssl_post).returns(successful_authorization_response) + + assert response = @gateway.authorize(142, @visacreditcard, @visacredit_options) + assert_success response + assert response.avs_result + assert_equal 'M', response.avs_result['code'] + assert_equal 'Street address and postal code match.', response.avs_result['message'] + assert_equal 'Y', response.avs_result['street_match'] + assert_equal 'Y', response.avs_result['postal_match'] + + assert response.cvv_result + assert_equal 'M', response.cvv_result['code'] + end + + def test_successful_visacreditcard_capture + @gateway.expects(:ssl_post).returns(successful_capture_response) + + assert responseCapture = @gateway.capture(142, 'authorization', @visacredit_options) + assert_equal 'APPROVED', responseCapture.message + assert_success responseCapture + assert responseCapture.test? + end + + def test_successful_refund + @gateway.expects(:ssl_post).returns(successful_refund_response) + + assert responseRefund = @gateway.refund(142, 'authorization', @visacredit_options) + assert_equal 'APPROVED', responseRefund.message + assert_success responseRefund + assert responseRefund.test? + end + + def test_successful_refund_due_to_unsettled_payment_forces_void + refund = stub_comms do + @gateway.refund(142, 'authorization', @visacredit_options.merge(force_full_refund_if_unsettled: true)) + end.respond_with(failed_refund_for_unsettled_payment_response, successful_void_response) + + assert refund + assert_success refund + assert_equal 'APPROVED', refund.message + assert refund.test? + end + + def test_failed_refund_due_to_unsettled_payment + @gateway.expects(:ssl_post).returns(failed_refund_for_unsettled_payment_response) + + assert refund = @gateway.refund(142, 'authorization', @visacredit_options) + assert_equal 'Can not REFUND this SALE transaction', refund.message + assert_failure refund + assert refund.test? + end + + def test_successful_visacreditcard_void + @gateway.expects(:ssl_post).returns(successful_void_response) + + assert responseRefund = @gateway.void('authorization', @visacredit_options) + assert_equal 'APPROVED', responseRefund.message + assert_success responseRefund + assert responseRefund.test? + end + + def test_successful_visacreditcard_purchase @gateway.expects(:ssl_post).returns(successful_purchase_response) - - assert response = @gateway.purchase(@amount, @credit_card, @options) - assert_instance_of Response, response + + assert responseRefund = @gateway.purchase(142, @visacreditcard, @visacredit_options) + assert_equal 'APPROVED', responseRefund.message + assert_success responseRefund + assert responseRefund.test? + end + + def test_successful_visacreditcard_purchase_with_descriptors + stub_comms do + @gateway.purchase(284, @visacreditcard, @visacredit_descriptor_options) + end.check_request do |endpoint, data, headers| + assert_match(/statementNarrative1=merchant/, data) + assert_match(/statementNarrative2=product/, data) + end.respond_with(successful_purchase_response_with_descriptors) + end + + def test_successful_visacreditcard_purchase_with_default_ip + stub_comms do + @gateway.purchase(284, @visacreditcard, @visacredit_options) + end.check_request do |endpoint, data, headers| + assert_match(/remoteAddress=1\.1\.1\.1/, data) + end.respond_with(successful_purchase_response_with_descriptors) + end + + def test_successful_visacreditcard_purchase_with_default_country + stub_comms do + @gateway.purchase(284, @visacreditcard, @visacredit_options.delete(:billing_address)) + end.check_request do |endpoint, data, headers| + assert_match(/customerCountryCode=GB/, data) + end.respond_with(successful_purchase_response) + end + + def test_successful_visacreditcard_purchase_via_reference + @gateway.expects(:ssl_post).returns(successful_reference_purchase_response) + + assert responsePurchase = @gateway.purchase(142, 'authorization', @visacredit_options) + assert_equal 'APPROVED', responsePurchase.message + assert_success responsePurchase + assert responsePurchase.test? + end + + def test_failed_visacreditcard_purchase_via_reference + @gateway.expects(:ssl_post).returns(failed_reference_purchase_response) + + assert responsePurchase = @gateway.purchase(142, 'authorization', @visacredit_options) + assert_equal 'DB ERROR', responsePurchase.message + assert_failure responsePurchase + assert responsePurchase.test? + end + + def test_declined_mastercard_purchase + @gateway.expects(:ssl_post).returns(failed_purchase_card_declined_response) + + assert response = @gateway.purchase(10000, @visacreditcard, @visacredit_options) + assert_equal 'CARD DECLINED', response.message + assert_failure response + assert response.test? + end + + def test_successful_verify + response = stub_comms do + @gateway.verify(@visacreditcard, @visacredit_options) + end.respond_with(successful_authorization_response, failed_void_response) assert_success response - assert_equal '08010706065208191057', response.authorization - end - - def test_failed_purchase - @gateway.expects(:ssl_post).returns(failed_purchase_response) - - assert response = @gateway.purchase(@amount, @credit_card, @options) - assert_instance_of Response, response + assert_equal 'APPROVED', response.message + end + + def test_failed_verify + response = stub_comms do + @gateway.verify(@declined_card, @visacredit_options) + end.respond_with(failed_authorization_response, successful_void_response) assert_failure response + assert_equal 'CARD DECLINED', response.message + end + + def test_purchase_options + # Default + purchase = stub_comms do + @gateway.purchase(142, @visacreditcard, @visacredit_options) + end.check_request do |endpoint, data, headers| + assert_match(/type=1/, data) + end.respond_with(successful_purchase_response) + + assert_success purchase + + purchase = stub_comms do + @gateway.purchase(142, @visacreditcard, @visacredit_options.merge(type: 2)) + end.check_request do |endpoint, data, headers| + assert_match(/type=2/, data) + end.respond_with(successful_purchase_response) + + assert_success purchase + + purchase = stub_comms do + @gateway.purchase(142, @visacreditcard, @visacredit_options.merge(currency: 'PEN')) + end.check_request do |endpoint, data, headers| + assert_match(/currencyCode=604/, data) + end.respond_with(successful_purchase_response) + + assert_success purchase end - - def test_successful_avs_result + + def test_successful_purchase_without_street_address @gateway.expects(:ssl_post).returns(successful_purchase_response) - - response = @gateway.purchase(@amount, @credit_card, @options) - assert_equal 'Y', response.avs_result['street_match'] - assert_equal 'Y', response.avs_result['postal_match'] + assert response = @gateway.purchase(142, @visacreditcard, billing_address: {state: 'Northampton'}) + assert_equal 'APPROVED', response.message end - - def test_failed_avs_result - @gateway.expects(:ssl_post).returns(successful_purchase_failed_avs_cvv_response) - - response = @gateway.purchase(@amount, @credit_card, @options) - assert_equal 'N', response.cvv_result['code'] - assert_equal 'N', response.avs_result['street_match'] - assert_equal 'N', response.avs_result['postal_match'] - end - - def test_cvv_result + + def test_successful_purchase_without_any_address @gateway.expects(:ssl_post).returns(successful_purchase_response) - - response = @gateway.purchase(@amount, @credit_card, @options) - assert_equal 'M', response.cvv_result['code'] + assert response = @gateway.purchase(142, @visacreditcard) + assert_equal 'APPROVED', response.message end - - def test_supported_countries - assert_equal ['GB'], CardStreamGateway.supported_countries - end - - def test_supported_card_types - assert_equal [:visa, :master, :american_express, :diners_club, :discover, :jcb, :maestro, :solo, :switch], CardStreamGateway.supported_cardtypes - end - - def test_default_currency - params = {} - - @gateway.send(:add_amount, params, 1000, {}) - assert_equal '826', params[:CurrencyCode] - end - - def test_override_currency - params = {} - - @gateway.send(:add_amount, params, 1000, :currency => 'USD') - assert_equal '840', params[:CurrencyCode] - end - + + def test_hmac_signature_added_to_post + post_params = "action=SALE&amount=10000&captureDelay=0&cardCVV=356&cardExpiryMonth=12&cardExpiryYear=14&cardNumber=4929421234600821&countryCode=GB&currencyCode=826&customerAddress=Flat+6%2C+Primrose+Rise+347+Lavender+Road&customerCountryCode=GB&customerName=Longbob+Longsen&customerPostCode=NN17+8YG+&merchantID=login&orderRef=AM+test+purchase&remoteAddress=1.1.1.1&threeDSRequired=N&transactionUnique=#{@visacredit_options[:order_id]}&type=1" + expected_signature = Digest::SHA512.hexdigest("#{post_params}#{@gateway.options[:shared_secret]}") + + @gateway.expects(:ssl_post).with do |url, data| + data.include?("signature=#{expected_signature}") + end.returns(successful_authorization_response) + + @gateway.purchase(10000, @visacreditcard, @visacredit_options) + end + + def test_3ds_response + purchase = stub_comms do + @gateway.purchase(142, @visacreditcard, @visacredit_options.merge(threeds_required: true)) + end.check_request do |endpoint, data, headers| + assert_match(/threeDSRequired=Y/, data) + end.respond_with(successful_purchase_response_with_3dsecure) + + assert_failure purchase # 3DS required means purchase not _yet_ successful + assert_equal 'UDNLRVk6eHJlZj0xNTA4MDYxNVJaMThSSjE1Uko2NFlWWg==', purchase.params['threeDSMD'] + assert_equal "eJxVUttuwjAM/ZWKD2iaQrnJjVQGA7TBGFSTeMxSC8rohTRd2d8vKe0YD5F8jh37\r\n+CQQHiXidIeilMhghUXBD2jFkd/xPNHtCz747EaCdhhsgi1eGHyjLOIsZdR2bBdI\r\nC/VVKY48VQy4uEyWa+YN6LDbA9JASFAup2zk9Tx3pOkbhJQnyHa5FhGdf6wQC2UF\r\nQmRlqizdvc5CDeUPG7p9IC2AUp7ZUam8GBNSVZUtuIwKJZEntsgSAsQUALnr2pQm\r\nKnTDaxyx1TSoHs/BW5/2zlsofCCmAiKukLkO9Zyh07dob0yHYzoAUvPAE6OEzScb\r\ni7q2o9U2DORmUHAD1DWZ/wxoqyWmot2nRYDXPEtRV+gLfzFEWAgWrCxlrMmbFbQG\r\nQwO57/S0MM4LpU1dxM/hrJx9zU8f6/3WuZxGL6/vle+bt6gLzKhYe6h3u80yAIhp\r\nQZpnJs1X0NHDF/kFvj+6mg==", purchase.params['threeDSPaReq'] + assert_equal 'https://dropit.3dsecure.net:9443/PIT/ACS', purchase.params['threeDSACSURL'] + end + + def test_deprecated_3ds_required + assert_deprecation_warning(CardStreamGateway::THREEDSECURE_REQUIRED_DEPRECATION_MESSAGE) do + @gateway = CardStreamGateway.new( + :login => 'login', + :shared_secret => 'secret', + :threeDSRequired => true + ) + end + stub_comms do + @gateway.purchase(142, @visacreditcard, @visacredit_options) + end.check_request do |endpoint, data, headers| + assert_match(/threeDSRequired=Y/, data) + end.respond_with(successful_purchase_response) + end + + def test_default_3dsecure_required + stub_comms do + @gateway.purchase(142, @visacreditcard, @visacredit_options) + end.check_request do |endpoint, data, headers| + assert_match(/threeDSRequired=N/, data) + end.respond_with(successful_purchase_response) + end + + def test_transcript_scrubbing + assert_equal scrubbed_transcript, @gateway.scrub(transcript) + end + private + + def successful_authorization_response + 'merchantID=0000000&threeDSEnabled=Y&merchantDescription=General+test+account+with+AVS%2FCV2+checking&amount=142&currencyCode=826&transactionUnique=fadc4985c51fc55ca349c45a79136ade&orderRef=AM+test+purchase&customerName=Longbob+Longsen&customerAddress=Flat+6%2C+Primrose+Rise+347+Lavender+Road&customerPostCode=NN17+8YG+&action=PREAUTH&type=1&countryCode=826&merchantAlias=0000992&remoteAddress=80.229.33.63&responseCode=0&responseMessage=AUTHCODE%3AD24577&xref=13021914RV01VR56LR16FNF&threeDSEnrolled=U&threeDSXID=00000000000004717472&transactionID=4717472&transactionPreviousID=0&timestamp=2013-02-19+14%3A02%3A19&amountReceived=142&avscv2ResponseCode=222100&avscv2ResponseMessage=ALL+MATCH&avscv2AuthEntity=merchant+host&cv2Check=matched&addressCheck=2&postcodeCheck=2&cardNumberMask=%2A%2A%2A%2A%2A%2A%2A%2A%2A%2A%2A%2A0821&cardTypeCode=VC&cardType=Visa+Credit&threeDSErrorCode=-1&threeDSErrorDescription=Error+while+attempting+to+send+the+request+to%3A+https%3A%2F%2F3dstest.universalpaymentgateway.com%3A4343%2FAPI%0A%0DPlease+make+sure+that+ActiveMerchant+server+is+running+and+the+URL+is+valid.+ERROR_INTERNET_CANNOT_CONNECT%3A+The+attempt+to+connect+to+the+server+failed.&threeDSMerchantPref=PROCEED&threeDSVETimestamp=2013-02-19+14%3A01%3A56&currencyExponent=2&responseStatus=0&merchantName=CARDSTREAM+TEST&merchantID2=100001' + end + + def successful_capture_response + 'merchantID=0000000&threeDSEnabled=Y&merchantDescription=General+test+account+with+AVS%2FCV2+checking&xref=13021914XW02YJ20MQ37RMT&amount=142&currencyCode=826&action=SALE&type=1&countryCode=826&merchantAlias=0000992&remoteAddress=80.229.33.63&responseCode=0&responseMessage=AUTHCODE%3A39657X&customerName=Longbob+Longsen&customerAddress=Flat+6%2C+Primrose+Rise+347+Lavender+Road&customerPostCode=NN17+8YG+&transactionUnique=fadc4985c51fc55ca349c45a79136ade&orderRef=AM+test+purchase&amountReceived=142&avscv2ResponseCode=422100&avscv2ResponseMessage=ALL+MATCH&avscv2AuthEntity=merchant+host&cv2Check=not+matched&addressCheck=matched&postcodeCheck=matched&threeDSXID=00000000000004717475&threeDSEnrolled=U&threeDSErrorCode=-1&threeDSErrorDescription=Error+while+attempting+to+send+the+request+to%3A+https%3A%2F%2F3dstest.universalpaymentgateway.com%3A4343%2FAPI%0A%0DPlease+make+sure+that+ActiveMerchant+server+is+running+and+the+URL+is+valid.+ERROR_INTERNET_CANNOT_CONNECT%3A+The+attempt+to+connect+to+the+server+failed.&threeDSMerchantPref=PROCEED&threeDSVETimestamp=2013-02-19+14%3A02%3A20&cardTypeCode=VC&cardNumberMask=%2A%2A%2A%2A%2A%2A%2A%2A%2A%2A%2A%2A0821&transactionID=4717475&transactionPreviousID=4717472&timestamp=2013-02-19+14%3A02%3A44&cardType=Visa+Credit&currencyExponent=2&responseStatus=0&merchantName=CARDSTREAM+TEST&merchantID2=100001' + end + + def successful_refund_response + 'merchantID=0000000&threeDSEnabled=Y&merchantDescription=General+test+account+with+AVS%2FCV2+checking&xref=13021914NT06BM21GJ15VJH&amount=142&currencyCode=826&action=REFUND&type=1&countryCode=826&merchantAlias=0000992&remoteAddress=80.229.33.63&responseCode=0&responseMessage=REFUNDACCEPTED&customerName=Longbob+Longsen&customerAddress=Flat+6%2C+Primrose+Rise+347+Lavender+Road&customerPostCode=NN17+8YG+&transactionUnique=c7981d78d217cf3cfda6559921e31c4a&orderRef=AM+test+purchase&amountReceived=142&avscv2ResponseCode=222100&avscv2ResponseMessage=ALL+MATCH&avscv2AuthEntity=merchant+host&cv2Check=matched&addressCheck=matched&postcodeCheck=matched&threeDSXID=00000000000004717488&threeDSEnrolled=U&threeDSErrorCode=-1&threeDSErrorDescription=Error+while+attempting+to+send+the+request+to%3A+https%3A%2F%2F3dstest.universalpaymentgateway.com%3A4343%2FAPI%0A%0DPlease+make+sure+that+ActiveMerchant+server+is+running+and+the+URL+is+valid.+ERROR_INTERNET_CANNOT_CONNECT%3A+The+attempt+to+connect+to+the+server+failed.&threeDSMerchantPref=PROCEED&threeDSVETimestamp=2013-02-19+14%3A05%3A58&cardTypeCode=VC&cardNumberMask=%2A%2A%2A%2A%2A%2A%2A%2A%2A%2A%2A%2A0821&threeDSRequired=N&transactionID=4717490&transactionPreviousID=4717488&timestamp=2013-02-19+14%3A06%3A21&cardType=Visa+Credit&currencyExponent=2&responseStatus=0&merchantName=CARDSTREAM+TEST&merchantID2=100001' + end + + def failed_refund_for_unsettled_payment_response + 'responseCode=65541&responseMessage=Can+not+REFUND+this+SALE+transaction&responseStatus=2&xref=18032714RP14KM21FX11YHT&amount=142&currencyCode=826&remoteAddress=1.1.1.1&countryCode=GB&merchantID=103191&action=REFUND_SALE&requestID=5aba43ad1481e&state=finished&requestMerchantID=103191&processMerchantID=103191&transactionID=25814232&timestamp=2018-03-27+14%3A14%3A21&vcsResponseCode=0&vcsResponseMessage=Success+-+no+velocity+check+rules+applied&signature=b56640b215510a04ebfaa095b63705cda08cca318a7ccb2b2b48caec75adc187d9cae5082eb1dc71d258813ee9d879721e48af04966a489171f435bfa67b6d92' + end + + def successful_void_response + 'merchantID=103191&threeDSEnabled=Y&avscv2CheckEnabled=N&eReceiptsEnabled=N&transactionID=11132605&xref=16050316NZ51LT53FP70BMZ&state=canceled&captureDelay=-1&remoteAddress=107.15.253.186&action=CANCEL%3ASALE&type=1&currencyCode=826&countryCode=826&amount=284&orderRef=AM+test+purchase&transactionUnique=2240526ccaa7d41af63a94aab843e683&cardTypeCode=VC&cardNumberMask=492942%2A%2A%2A%2A%2A%2A0821&cardExpiryDate=1214&cardExpiryMonth=12&cardExpiryYear=14&customerName=Longbob+Longsen&customerAddress=Flat+6%2C+Primrose+Rise+347+Lavender+Road&customerPostcode=NN17+8YG&eReceiptsStoreID=1&customerReceiptsRequired=N&cv2Check=matched&addressCheck=matched&postcodeCheck=matched&avscv2ResponseCode=222100&avscv2ResponseMessage=ALL+MATCH&avscv2AuthEntity=merchant+host&addressCheckPref=not+known%2Cnot+checked%2Cmatched%2Cnot+matched%2Cpartially+matched&postcodeCheckPref=not+known%2Cnot+checked%2Cmatched%2Cnot+matched%2Cpartially+matched&threeDSRequired=N&threeDSCheckPref=authenticated&authorisationCode=084609&amountReceived=0&responseCode=0&responseMessage=Transaction+cancelled.&cardCVVMandatory=N&responseStatus=0&timestamp=2016-05-03+16%3A51%3A54&cardType=Visa+Credit&cardScheme=Visa+&cardSchemeCode=VC&cardIssuer=BARCLAYS+BANK+PLC&cardIssuerCountry=United+Kingdom&cardIssuerCountryCode=GBR&signature=73cf5ec5470f6a1b3abce4e2a4b64adc2da0cb7103e8d362b40ab101d2ff339961a593869f289f7660a286e8c92c22fd5dec4330cf7e7e0ca8651cd8c0a8d966' + end + def successful_purchase_response - 'VPResponseCode=00&VPCrossReference=08010706065208191057&VPMessage=AUTHCODE:08191&VPTransactionUnique=c3871e2d005b924bf81565537caba82d&VPOrderDesc=Store purchase&VPBillingCountry=826&VPCardName=Longbob Longsen&VPBillingPostCode=LE10 2RT&VPAmountRecieved=100&VPAVSCV2ResponseCode=222100&VPCV2ResultMessage=CV2 Matched&VPAVSResultMessage=Postcode Matched&VPAVSAddressMessage=Address Numeric Matched&VPCardType=MC&VPBillingAddress=25 The Larches, Narborough, Leicester&VPReturnPoint=0090' + 'merchantID=0000000&threeDSEnabled=Y&merchantDescription=General+test+account+with+AVS%2FCV2+checking&amount=142&currencyCode=826&transactionUnique=27a594210e27846c8e9102647f210586&orderRef=AM+test+purchase&customerName=Longbob+Longsen&customerAddress=Flat+6%2C+Primrose+Rise+347+Lavender+Road&customerPostCode=NN17+8YG+&action=SALE&type=1&countryCode=826&merchantAlias=0000992&remoteAddress=80.229.33.63&responseCode=0&responseMessage=AUTHCODE%3A635959&xref=13021914LS06DW22NW22LVJ&threeDSEnrolled=U&threeDSXID=00000000000004717491&transactionID=4717491&transactionPreviousID=0&timestamp=2013-02-19+14%3A06%3A44&amountReceived=142&avscv2ResponseCode=222100&avscv2ResponseMessage=ALL+MATCH&avscv2AuthEntity=merchant+host&cv2Check=matched&addressCheck=matched&postcodeCheck=matched&cardNumberMask=%2A%2A%2A%2A%2A%2A%2A%2A%2A%2A%2A%2A0821&cardTypeCode=VC&cardType=Visa+Credit&threeDSErrorCode=-1&threeDSErrorDescription=Error+while+attempting+to+send+the+request+to%3A+https%3A%2F%2F3dstest.universalpaymentgateway.com%3A4343%2FAPI%0A%0DPlease+make+sure+that+ActiveMerchant+server+is+running+and+the+URL+is+valid.+ERROR_INTERNET_CANNOT_CONNECT%3A+The+attempt+to+connect+to+the+server+failed.&threeDSMerchantPref=PROCEED&threeDSVETimestamp=2013-02-19+14%3A06%3A22&currencyExponent=2&responseStatus=0&merchantName=CARDSTREAM+TEST&merchantID2=100001' end - - def successful_purchase_failed_avs_cvv_response - 'VPResponseCode=00&VPCrossReference=08010706065208191057&VPMessage=AUTHCODE:08191&VPTransactionUnique=c3871e2d005b924bf81565537caba82d&VPOrderDesc=Store purchase&VPBillingCountry=826&VPCardName=Longbob Longsen&VPBillingPostCode=LE10 2RT&VPAmountRecieved=100&VPAVSCV2ResponseCode=444100&VPCV2ResultMessage=CV2 Matched&VPAVSResultMessage=Postcode Matched&VPAVSAddressMessage=Address Numeric Matched&VPCardType=MC&VPBillingAddress=25 The Larches, Narborough, Leicester&VPReturnPoint=0090' + + def successful_purchase_response_with_3dsecure + 'responseCode=65802&responseMessage=3DS+AUTHENTICATION+REQUIRED&responseStatus=2&merchantID=103191&threeDSEnabled=Y&threeDSCheckPref=not+known%2Cnot+checked%2Cauthenticated%2Cnot+authenticated%2Cattempted+authentication&avscv2CheckEnabled=N&cv2CheckPref=not+known%2Cnot+checked%2Cmatched%2Cnot+matched%2Cpartially+matched&addressCheckPref=not+known%2Cnot+checked%2Cmatched%2Cnot+matched%2Cpartially+matched&postcodeCheckPref=not+known%2Cnot+checked%2Cmatched%2Cnot+matched%2Cpartially+matched&cardCVVMandatory=Y&customerID=1749&eReceiptsEnabled=N&eReceiptsStoreID=1&amount=1202&currencyCode=826&transactionUnique=42e13d06ce4d5f5e3eb4868d29baa8bb&orderRef=AM+test+purchase&threeDSRequired=Y&customerName=Longbob+Longsen&customerAddress=25+The+Larches&customerPostCode=LE10+2RT&action=SALE&type=1&countryCode=826&customerPostcode=LE10+2RT&customerReceiptsRequired=N&state=finished&remoteAddress=45.37.180.92&requestMerchantID=103191&processMerchantID=103191&xref=15080615RZ18RJ15RJ64YVZ&cardExpiryDate=1220&threeDSXID=MDAwMDAwMDAwMDAwMDg5NjY0OTc%3D&threeDSEnrolled=Y&transactionID=8966497&cardNumberMask=%2A%2A%2A%2A%2A%2A%2A%2A%2A%2A%2A%2A1112&cardType=Visa+Credit&cardTypeCode=VC&cardScheme=Visa+&cardSchemeCode=VC&cardIssuer=Unknown&cardIssuerCountry=Unknown&cardIssuerCountryCode=XXX&threeDSPaReq=eJxVUttuwjAM%2FZWKD2iaQrnJjVQGA7TBGFSTeMxSC8rohTRd2d8vKe0YD5F8jh37%0D%0A%2BCQQHiXidIeilMhghUXBD2jFkd%2FxPNHtCz747EaCdhhsgi1eGHyjLOIsZdR2bBdI%0D%0AC%2FVVKY48VQy4uEyWa%2BYN6LDbA9JASFAup2zk9Tx3pOkbhJQnyHa5FhGdf6wQC2UF%0D%0AQmRlqizdvc5CDeUPG7p9IC2AUp7ZUam8GBNSVZUtuIwKJZEntsgSAsQUALnr2pQm%0D%0AKnTDaxyx1TSoHs%2FBW5%2F2zlsofCCmAiKukLkO9Zyh07dob0yHYzoAUvPAE6OEzScb%0D%0Ai7q2o9U2DORmUHAD1DWZ%2FwxoqyWmot2nRYDXPEtRV%2BgLfzFEWAgWrCxlrMmbFbQG%0D%0AQwO57%2FS0MM4LpU1dxM%2FhrJx9zU8f6%2F3WuZxGL6%2Fvle%2Bbt6gLzKhYe6h3u80yAIhp%0D%0AQZpnJs1X0NHDF%2FkFvj%2B6mg%3D%3D&threeDSACSURL=https%3A%2F%2Fdropit.3dsecure.net%3A9443%2FPIT%2FACS&threeDSVETimestamp=2015-08-06+15%3A18%3A15&threeDSCheck=not+checked&vcsResponseCode=0&vcsResponseMessage=Success+-+no+velocity+check+rules+applied&currencyExponent=2&threeDSMD=UDNLRVk6eHJlZj0xNTA4MDYxNVJaMThSSjE1Uko2NFlWWg%3D%3D&timestamp=2015-08-06+15%3A18%3A17&threeDSResponseCode=65802&threeDSResponseMessage=3DS+AUTHENTICATION+REQUIRED&signature=8551e3f1c77b6cfa78e154d99ffb05fdeabbae48a7ce723a3464047731ad98a1c4bfe0b7dfdf46de7ff3dab66b3e2e365025fc9ff3a74d86ae4378c8cc985d88' + end + + def successful_purchase_response_with_descriptors + 'merchantID=103191&threeDSEnabled=Y&threeDSCheckPref=authenticated&avscv2CheckEnabled=N&cv2CheckPref=not+known%2Cnot+checked%2Cmatched%2Cnot+matched%2Cpartially+matched&addressCheckPref=not+known%2Cnot+checked%2Cmatched%2Cnot+matched%2Cpartially+matched&postcodeCheckPref=not+known%2Cnot+checked%2Cmatched%2Cnot+matched%2Cpartially+matched&cardCVVMandatory=Y&customerReceiptsRequired=N&eReceiptsEnabled=N&eReceiptsStoreID=1&captureDelay=0&amount=284&currencyCode=826&statementNarrative1=merchant&statementNarrative2=product&type=1&threeDSRequired=N&customerName=Longbob+Longsen&cardExpiryMonth=12&cardExpiryYear=14&customerAddress=Flat+6%2C+Primrose+Rise+347+Lavender+Road&customerPostCode=NN17+8YG&countryCode=826&action=SALE&customerPostcode=NN17+8YG&requestID=586aae8406720&responseCode=0&responseMessage=AUTHCODE%3A684787&state=captured&requestMerchantID=103191&processMerchantID=103191&xref=17010219YV48VW21QH25TKS&paymentMethod=card&cardExpiryDate=1214&authorisationCode=684787&transactionID=13843073&responseStatus=0&timestamp=2017-01-02+19%3A48%3A21&amountReceived=284&avscv2ResponseCode=222100&avscv2ResponseMessage=ALL+MATCH&avscv2AuthEntity=merchant+host&cv2Check=matched&addressCheck=matched&postcodeCheck=matched&cardNumberMask=492942%2A%2A%2A%2A%2A%2A0821&cardType=Visa+Credit&cardTypeCode=VC&cardScheme=Visa+&cardSchemeCode=VC&cardIssuer=BARCLAYS+BANK+PLC&cardIssuerCountry=United+Kingdom&cardIssuerCountryCode=GBR&vcsResponseCode=0&vcsResponseMessage=Success+-+no+velocity+check+rules+applied&currencyExponent=2&signature=d47c9253d2d9ff6e9464a782b798b3fa699f6ed085eb03d5f87c4cf1f0994efab0c3145236df2163ea2b48fc0232bed25626b7ac331f0c98473ef4b551099eef' end - - def failed_purchase_response - 'VPResponseCode=05&VPCrossReference=NoCrossReference&VPMessage=CARD DECLINED&VPTransactionUnique=d966e18a2983faff3715a541983792e0&VPOrderDesc=Store purchase&VPBillingCountry=826&VPCardName=Longbob Longsen&VPBillingPostCode=LE10 2RT&VPAmountRecieved=NA&VPAVSCV2ResponseCode=222100&VPCV2ResultMessage=CV2 Matched&VPAVSResultMessage=Postcode Matched&VPAVSAddressMessage=Address Numeric Matched&VPCardType=MC&VPBillingAddress=25 The Larches, Narborough, Leicester&VPReturnPoint=0090' + + def failed_purchase_card_declined_response + 'merchantID=0000000&threeDSEnabled=Y&merchantDescription=General+test+account+with+AVS%2FCV2+checking&amount=10000&currencyCode=826&transactionUnique=7385df1d9c5484142bb6be1e932cd2df&orderRef=AM+test+purchase&customerName=Longbob+Longsen&customerAddress=25+The+Larches+&customerPostCode=LE10+2RT&action=SALE&type=1&countryCode=826&merchantAlias=0000992&remoteAddress=80.229.33.63&responseCode=5&responseMessage=CARD+DECLINED&xref=13021914RQ07HK55HG29KPH&threeDSEnrolled=U&threeDSXID=00000000000004717495&transactionID=4717495&transactionPreviousID=0&timestamp=2013-02-19+14%3A08%3A18&amountReceived=0&cardNumberMask=%2A%2A%2A%2A%2A%2A%2A%2A%2A%2A%2A%2A0191&cardTypeCode=MC&cardType=Mastercard&threeDSErrorCode=-1&threeDSErrorDescription=Error+while+attempting+to+send+the+request+to%3A+https%3A%2F%2F3dstest.universalpaymentgateway.com%3A4343%2FAPI%0A%0DPlease+make+sure+that+ActiveMerchant+server+is+running+and+the+URL+is+valid.+ERROR_INTERNET_CANNOT_CONNECT%3A+The+attempt+to+connect+to+the+server+failed.&threeDSMerchantPref=PROCEED&threeDSVETimestamp=2013-02-19+14%3A07%3A55&currencyExponent=2&responseStatus=1&merchantName=CARDSTREAM+TEST&merchantID2=100001' + end + + def failed_authorization_response + 'merchantID=0000000&threeDSEnabled=Y&merchantDescription=General+test+account+with+AVS%2FCV2+checking&amount=10000&currencyCode=826&transactionUnique=7385df1d9c5484142bb6be1e932cd2df&orderRef=AM+test+purchase&customerName=Longbob+Longsen&customerAddress=25+The+Larches+&customerPostCode=LE10+2RT&action=SALE&type=1&countryCode=826&merchantAlias=0000992&remoteAddress=80.229.33.63&responseCode=5&responseMessage=CARD+DECLINED&xref=13021914RQ07HK55HG29KPH&threeDSEnrolled=U&threeDSXID=00000000000004717495&transactionID=4717495&transactionPreviousID=0&timestamp=2013-02-19+14%3A08%3A18&amountReceived=0&cardNumberMask=%2A%2A%2A%2A%2A%2A%2A%2A%2A%2A%2A%2A0191&cardTypeCode=MC&cardType=Mastercard&threeDSErrorCode=-1&threeDSErrorDescription=Error+while+attempting+to+send+the+request+to%3A+https%3A%2F%2F3dstest.universalpaymentgateway.com%3A4343%2FAPI%0A%0DPlease+make+sure+that+ActiveMerchant+server+is+running+and+the+URL+is+valid.+ERROR_INTERNET_CANNOT_CONNECT%3A+The+attempt+to+connect+to+the+server+failed.&threeDSMerchantPref=PROCEED&threeDSVETimestamp=2013-02-19+14%3A07%3A55&currencyExponent=2&responseStatus=1&merchantName=CARDSTREAM+TEST&merchantID2=100001' + end + + def failed_void_response + 'responseCode=65548&responseMessage=DB+ERROR&responseStatus=2&xref=16050317BX02HT10NH49ZRS&merchantID=103191&action=CANCEL&state=finished&remoteAddress=107.15.253.186&requestMerchantID=103191&processMerchantID=103191&timestamp=2016-05-03+17%3A02%3A10&signature=73d3ea59f106208fdf3b0fd1bc09a21c8bb66bbf481a7b6769959b1b3b2cbc18fbce434f2ea7f648a4dbdbf180003b0cc77036410be7f246e827ace0ed459d9b' + end + + def successful_reference_purchase_response + 'merchantID=103191&threeDSEnabled=Y&threeDSCheckPref=authenticated&avscv2CheckEnabled=N&addressCheckPref=not+known%2Cnot+checked%2Cmatched%2Cnot+matched%2Cpartially+matched&postcodeCheckPref=not+known%2Cnot+checked%2Cmatched%2Cnot+matched%2Cpartially+matched&customerReceiptsRequired=N&eReceiptsEnabled=N&eReceiptsStoreID=1&amount=142&currencyCode=826&transactionUnique=1f21b6d2fdf1f13378705707bc7e24bd&orderRef=AM+test+purchase&type=1&threeDSRequired=N&countryCode=826&action=SALE&responseCode=0&responseMessage=AUTHCODE%3A300382&state=captured&remoteAddress=107.15.253.186&requestMerchantID=103191&processMerchantID=103191&cardNumberMask=492942%2A%2A%2A%2A%2A%2A0821&cardExpiryMonth=12&cardExpiryYear=14&customerName=Longbob+Longsen&customerAddress=Flat+6%2C+Primrose+Rise+347+Lavender+Road&customerPostcode=NN17+8YG&previousID=10886746&cardCVVMandatory=N&xref=16040515PS17RG20GT73SBF&cardExpiryDate=1214&authorisationCode=300382&transactionID=10886747&responseStatus=0&timestamp=2016-04-05+15%3A17%3A20&amountReceived=142&avscv2ResponseCode=422100&avscv2ResponseMessage=ADDRESS+MATCH+ONLY&avscv2AuthEntity=merchant+host&cv2Check=not+matched&addressCheck=matched&postcodeCheck=matched&cardType=Visa+Credit&cardTypeCode=VC&cardScheme=Visa+&cardSchemeCode=VC&cardIssuer=BARCLAYS+BANK+PLC&cardIssuerCountry=United+Kingdom&cardIssuerCountryCode=GBR&vcsResponseCode=0&vcsResponseMessage=Success+-+no+velocity+check+rules+applied&currencyExponent=2&signature=9e13f5ffd9cf94215225ab60f80915879a242dcf8c30c7f39a0265acf0f3f7186cbea656d53fcce249e54f522050efd32e677726fc7d5aa1f7b6ee746e040956' + end + + def failed_reference_purchase_response + 'responseCode=65548&responseMessage=DB+ERROR&responseStatus=2&amount=142&currencyCode=826&transactionUnique=740290de68c10d18acabe9fb0a52bd92&orderRef=AM+test+purchase&type=1&threeDSRequired=N&xref=16040515NM21GM43SF71MMR&countryCode=GB&merchantID=103191&action=SALE&state=finished&remoteAddress=107.15.253.186&requestMerchantID=103191&processMerchantID=103191&transactionID=10886787&timestamp=2016-04-05+15%3A21%3A43&vcsResponseCode=0&vcsResponseMessage=Success+-+no+velocity+check+rules+applied&signature=311f2bfd94c3807fae4fbeff13801c37d81c3e1082c5d3c5893281f18d7152b96420fa7b45285eb692b09b576b3b85d894ee35ff9e31cd06ace54019b806550f' + end + + def transcript + <<-eos + POST /direct/ HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: gateway.cardstream.com\r\nContent-Length: 501\r\n\r\n" + amount=&currencyCode=826&transactionUnique=a017ca2ac0569188517ad8368c36a06d&orderRef=AM+test+purchase&customerName=Longbob+Longsen&cardNumber=4929421234600821&cardExpiryMonth=12&cardExpiryYear=14&cardCVV=356&customerAddress=Flat+6%2C+Primrose+Rise+347+Lavender+Road&customerPostCode=NN17+8YG+&merchantID=102922&action=SALE&type=1&countryCode=GB&threeDSRequired=N&signature=970b3fe099a85c9922a79af46c2cb798616b9fbd044a921ac5eb46cd1907a5e89b8c720aae59c7eb1d81a59563f209d5db51aa3c270838199f2bfdcbe2c1149d + eos + end + + def scrubbed_transcript + <<-eos + POST /direct/ HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: gateway.cardstream.com\r\nContent-Length: 501\r\n\r\n" + amount=&currencyCode=826&transactionUnique=a017ca2ac0569188517ad8368c36a06d&orderRef=AM+test+purchase&customerName=Longbob+Longsen&cardNumber=[FILTERED]&cardExpiryMonth=12&cardExpiryYear=14&cardCVV=[FILTERED]&customerAddress=Flat+6%2C+Primrose+Rise+347+Lavender+Road&customerPostCode=NN17+8YG+&merchantID=102922&action=SALE&type=1&countryCode=GB&threeDSRequired=N&signature=970b3fe099a85c9922a79af46c2cb798616b9fbd044a921ac5eb46cd1907a5e89b8c720aae59c7eb1d81a59563f209d5db51aa3c270838199f2bfdcbe2c1149d + eos end -end \ No newline at end of file +end diff --git a/test/unit/gateways/cardknox_test.rb b/test/unit/gateways/cardknox_test.rb new file mode 100644 index 00000000000..bc10d5e0e70 --- /dev/null +++ b/test/unit/gateways/cardknox_test.rb @@ -0,0 +1,273 @@ +require 'test_helper' + +class CardknoxTest < Test::Unit::TestCase + include CommStub + + def setup + @gateway = CardknoxGateway.new(api_key: 'api_key') + @credit_card = credit_card('4242424242424242') + @options = { + billing_address: address, + shipping_address: address + } + @amount = 100 + end + + def test_successful_purchase_passing_extra_info + response = stub_comms do + @gateway.purchase(@amount, @credit_card, @options.merge(order_id: '1337', description: 'socool')) + end.check_request do |endpoint, data, headers| + assert_match(/xOrderID=1337/, data) + assert_match(/xDescription=socool/, data) + end.respond_with(successful_purchase_response) + assert_success response + end + + def test_avs_result + @gateway.expects(:ssl_post).returns(successful_purchase_response) + + response = @gateway.purchase(@amount, @credit_card, @options) + assert_equal 'YYY', response.avs_result['code'] + end + + def test_cvv_result + @gateway.expects(:ssl_post).returns(successful_purchase_response) + + response = @gateway.purchase(@amount, @credit_card, @options) + assert_equal 'M', response.cvv_result['code'] + end + + def test_add_track_data_with_creditcard + @credit_card.track_data = 'data' + + @gateway.expects(:ssl_post).with do |_, body| + body.include?('xMagstripe=data') + end.returns(successful_purchase_response) + + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + end + + def test_add_track_data_with_empty_data + ['', nil].each do |data| + @credit_card.track_data = data + + @gateway.expects(:ssl_post).with do |_, body| + refute body.include? 'xMagstripe=' + body + end.returns(successful_purchase_response) + + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + end + end + + def test_manual_entry_is_properly_indicated_on_purchase + @credit_card.manual_entry = true + response = stub_comms do + @gateway.purchase(@amount, @credit_card, @options) + end.check_request do |endpoint, data, headers| + assert_match %r{xCardNum=4242424242424242}, data + assert_match %r{xCardPresent=true}, data + end.respond_with(successful_purchase_response) + + assert_success response + end + + def test_ip_is_being_sent # failed + @gateway.expects(:ssl_post).with do |url, data| + data =~ /xIP=123.123.123.123/ + end.returns(successful_purchase_response) + + @options[:ip] = '123.123.123.123' + @gateway.purchase(@amount, @credit_card, @options) + end + + def test_successful_credit_card_purchase + @gateway.expects(:ssl_post).returns(successful_purchase_response) + + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal '16268727;f0b84a5b181749b4b0d77f6291a753ab;credit_card', response.authorization + end + + def test_failed_credit_card_purchase + @gateway.expects(:ssl_post).returns(failed_purchase_response) + + response = @gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_equal 'Invalid CVV', response.message + end + + def test_successful_authorize + @gateway.expects(:ssl_post).returns(successful_authorize_response) + + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + assert_equal 'Success', auth.message + assert_equal '15312316;62d4fd9aebd240659d68ffaa156d1788;credit_card', auth.authorization + end + + def test_failed_authorize + @gateway.expects(:ssl_post).returns(failed_authorize_response) + + response = @gateway.authorize(@amount, @credit_card, @options) + assert_failure response + assert_equal 'Invalid CVV', response.message + end + + def test_successful_capture + @gateway.expects(:ssl_post).twice.returns(successful_authorize_response).then.returns(successful_capture_response) + + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + assert capture = @gateway.capture(@amount, auth.authorization) + assert_success capture + assert_equal 'Success', capture.message + assert_equal '15312316;f9097326fb1c4976a6da75ccb872f28a;credit_card', capture.authorization + end + + def test_failed_capture + @gateway.expects(:ssl_post).returns(failed_capture_response) + + assert capture = @gateway.capture(@amount-1, '') + assert_failure capture + assert_equal 'Original transaction not specified', capture.message + end + + def test_successful_check_refund_void + @gateway.expects(:ssl_post).returns(successful_check_refund_void) + assert void = @gateway.void('16274604;;check', @options) + assert_success void + assert_equal 'Success', void.message + assert_equal '16276884;;check', void.authorization + end + + def test_failed_refund + @gateway.expects(:ssl_post).returns(failed_credit_card_refund_response) + + response = @gateway.refund(@amount, '') + assert_failure response + assert_equal 'UNSUPPORTED CARD TYPE', response.message + assert_equal '15308026;;credit_card', response.authorization + end + + def test_successful_credit_card_void + @gateway.expects(:ssl_post).returns(successful_void_response) + + assert void = @gateway.void('15308171;;credit_card') + assert_success void + assert_equal 'Success', void.message + assert_equal '15308171;;credit_card', void.authorization + end + + def test_failed_void + @gateway.expects(:ssl_post).returns(failed_void_response) + + response = @gateway.void('') + assert_failure response + assert_equal 'Original transaction not specified', response.message + end + + def test_successful_verify + @gateway.expects(:ssl_post).twice.returns(successful_verify_response) + + response = @gateway.verify(@credit_card, @options) + assert_success response + assert_match %r{Success}, response.message; + end + + def test_successful_verify_with_failed_void + @gateway.expects(:ssl_post).twice.returns(successful_authorize_response).then.returns(failed_void_response) + response = @gateway.verify(@credit_card, @options) + assert_success response + assert_match %r{Success}, response.message; + end + + def test_failed_verify + @gateway.expects(:ssl_post).returns(failed_verify_response) + + response = @gateway.verify(@credit_card, @options) + assert_failure response + assert_match %r{Invalid CVV}, response.message + end + + def test_scrub + assert @gateway.supports_scrubbing? + assert_equal @gateway.scrub(pre_scrubbed), post_scrubbed + end + + private + + def purchase_request + 'xKey=api_key&xVersion=4.5.4&xSoftwareName=Active+Merchant&xSoftwareVersion=1.52.0&xCommand=cc%3Asale&xAmount=1.00&xCardNum=4242424242424242&xCVV=123&xExp=0916&xName=Longbob+Longsen&xBillFirstName=Longbob&xBillLastName=Longsen&xBillCompany=Widgets+Inc&xBillStreet=456+My+Street&xBillStreet2=Apt+1&xBillCity=Ottawa&xBillState=ON&xBillZip=K1C2N6&xBillCountry=CA&xBillPhone=%28555%29555-5555&xShipFirstName=Longbob&xShipLastName=Longsen&xShipCompany=Widgets+Inc&xShipStreet=456+My+Street&xShipStreet2=Apt+1&xShipCity=Ottawa&xShipState=ON&xShipZip=K1C2N6&xShipCountry=CA&xShipPhone=%28555%29555-5555&xStreet=456+My+Street&xZip=K1C2N6' + end + + def successful_purchase_response # passed avs and cvv + 'xResult=A&xStatus=Approved&xError=&xRefNum=16268727&xAuthCode=230809&xBatch=347&xAvsResultCode=YYY&xAvsResult=Address%3a+Match+%26+5+Digit+Zip%3a+Match&xCvvResultCode=M&xCvvResult=Match&xAuthAmount=4.38&xToken=f0b84a5b181749b4b0d77f6291a753ab&xMaskedCardNumber=4xxxxxxxxxxx2224&xName=Longbob+Longsen' + end + + def failed_purchase_response + 'xResult=D&xStatus=Declined&xError=Invalid+CVV&xRefNum=15307128&xAuthCode=&xBatch=&xAvsResultCode=NNN&xAvsResult=Address%3a+No+Match+%26+5+Digit+Zip%3a+No+Match&xCvvResultCode=N&xCvvResult=No+Match&xToken=8138747e41894071a353318541d2ee8c' + end + + def successful_authorize_response + 'xResult=A&xStatus=Approved&xError=&xRefNum=15312316&xAuthCode=630421&xBatch=&xAvsResultCode=NNN&xAvsResult=Address%3a+No+Match+%26+5+Digit+Zip%3a+No+Match&xCvvResultCode=N&xCvvResult=No+Match&xAuthAmount=2.06&xToken=62d4fd9aebd240659d68ffaa156d1788&xMaskedCardNumber=4xxxxxxxxxxx2224&xName=Longbob+Longsen' + end + + def failed_authorize_response + 'xResult=D&xStatus=Declined&xError=Invalid+CVV&xRefNum=15307290&xAuthCode=&xBatch=&xAvsResultCode=NNN&xAvsResult=Address%3a+No+Match+%26+5+Digit+Zip%3a+No+Match&xCvvResultCode=N&xCvvResult=No+Match&xToken=065751a8a28e468a8d79cb98c04cf350' + end + + def successful_capture_response + 'xResult=A&xStatus=Approved&xError=&xRefNum=15312316&xRefNumCurrent=15312319&xAuthCode=&xBatch=321&xAvsResultCode=&xAvsResult=Unmapped+AVS+response&xCvvResultCode=&xCvvResult=No+CVV+data+available&xAuthAmount=2.06&xToken=f9097326fb1c4976a6da75ccb872f28a&xMaskedCardNumber=4xxxxxxxxxxx2224&xName=Longbob+Longsen' + end + + def failed_capture_response + 'xResult=E&xStatus=Error&xAuthCode=000000&xError=Original+transaction+not+specified&xRefNum=15307619&xErrorCode=00000' + end + + def successful_credit_card_refund_response + end + + def failed_credit_card_refund_response + 'xResult=D&xStatus=Declined&xError=UNSUPPORTED+CARD+TYPE&xRefNum=15308026&xAuthCode=&xBatch=&xAvsResultCode=&xAvsResult=Unmapped+AVS+response&xCvvResultCode=&xCvvResult=No+CVV+data+available' + end + + def successful_check_refund_response + 'xResult=A&xStatus=Approved&xError=&xRefNum=16274604' + end + + def failed_check_refund_response + 'xResult=D&xStatus=Declined&xError=&xRefNum=16274604' + end + + def successful_void_response + 'xResult=A&xStatus=Approved&xError=&xRefNum=15308171&xRefNumCurrent=15308172&xAuthCode=912013&xBatch=&xAvsResultCode=&xAvsResult=Unmapped+AVS+response&xCvvResultCode=&xCvvResult=No+CVV+data+available&xAuthAmount=2.33&xMaskedCardNumber=4xxxxxxxxxxx2224&xName=Longbob+Longsen' + end + + def failed_void_response + 'xResult=E&xStatus=Error&xAuthCode=000000&xError=Original+transaction+not+specified&xRefNum=15308297&xErrorCode=00000' + end + + def successful_check_refund_void + 'xResult=A&xStatus=Approved&xError=&xRefNum=16276884' + end + + def successful_verify_response + 'xResult=A&xStatus=Approved&xError=&xRefNum=15314566&xAuthCode=608755&xBatch=&xAvsResultCode=NNN&xAvsResult=Address%3a+No+Match+%26+5+Digit+Zip%3a+No+Match&xCvvResultCode=N&xCvvResult=No+Match&xAuthAmount=1.00&xToken=09dc51aceb98440fbf0847cad2941d45&xMaskedCardNumber=4xxxxxxxxxxx2224&xName=Longbob+Longsen' + end + + def failed_verify_response + 'xResult=D&xStatus=Declined&xError=Invalid+CVV&xRefNum=15310681&xAuthCode=&xBatch=&xAvsResultCode=NNN&xAvsResult=Address%3a+No+Match+%26+5+Digit+Zip%3a+No+Match&xCvvResultCode=N&xCvvResult=No+Match&xToken=748df69e22f142d4aab296328d4f2653' + end + + def pre_scrubbed + 'xKey=api_key&xVersion=4.5.4&xSoftwareName=Active+Merchant&xSoftwareVersion=1.52.0&xCommand=cc%3Asale&xAmount=1.00&xCardNum=4242424242424242&xCVV=123&xExp=0916&xName=Longbob+Longsen&xBillFirstName=Longbob&xBillLastName=Longsen&xBillCompany=Widgets+Inc&xBillStreet=456+My+Street&xBillStreet2=Apt+1&xBillCity=Ottawa&xBillState=ON&xBillZip=K1C2N6&xBillCountry=CA&xBillPhone=%28555%29555-5555&xShipFirstName=Longbob&xShipLastName=Longsen&xShipCompany=Widgets+Inc&xShipStreet=456+My+Street&xShipStreet2=Apt+1&xShipCity=Ottawa&xShipState=ON&xShipZip=K1C2N6&xShipCountry=CA&xShipPhone=%28555%29555-5555&xStreet=456+My+Street&xZip=K1C2N6' + end + + def post_scrubbed + 'xKey=[FILTERED]&xVersion=4.5.4&xSoftwareName=Active+Merchant&xSoftwareVersion=1.52.0&xCommand=cc%3Asale&xAmount=1.00&xCardNum=[FILTERED]&xCVV=[FILTERED]&xExp=0916&xName=Longbob+Longsen&xBillFirstName=Longbob&xBillLastName=Longsen&xBillCompany=Widgets+Inc&xBillStreet=456+My+Street&xBillStreet2=Apt+1&xBillCity=Ottawa&xBillState=ON&xBillZip=K1C2N6&xBillCountry=CA&xBillPhone=%28555%29555-5555&xShipFirstName=Longbob&xShipLastName=Longsen&xShipCompany=Widgets+Inc&xShipStreet=456+My+Street&xShipStreet2=Apt+1&xShipCity=Ottawa&xShipState=ON&xShipZip=K1C2N6&xShipCountry=CA&xShipPhone=%28555%29555-5555&xStreet=456+My+Street&xZip=K1C2N6' + end +end diff --git a/test/unit/gateways/cardprocess_test.rb b/test/unit/gateways/cardprocess_test.rb new file mode 100644 index 00000000000..5377cd16c73 --- /dev/null +++ b/test/unit/gateways/cardprocess_test.rb @@ -0,0 +1,280 @@ +require 'test_helper' + +class CardprocessTest < Test::Unit::TestCase + def setup + @gateway = CardprocessGateway.new(user_id: 'login', password: 'password', entity_id: '123') + @credit_card = credit_card + @amount = 100 + + @options = { + order_id: '1', + billing_address: address, + description: 'Store Purchase' + } + end + + def test_successful_purchase + @gateway.expects(:ssl_post).returns(successful_purchase_response) + + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + + assert_equal '8a8294495fe8084a016002dd17c163fd', response.authorization + assert_equal 'DB', response.params['paymentType'] + assert response.test? + end + + def test_failed_purchase + @gateway.expects(:ssl_post).returns(failed_purchase_response) + + response = @gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_equal Gateway::STANDARD_ERROR_CODE[:incorrect_number], response.error_code + end + + def test_successful_authorize + @gateway.expects(:ssl_post).returns(successful_authorize_response) + + response = @gateway.authorize(@amount, @credit_card, @options) + assert_success response + + assert_equal '8a82944a5fe82704016002caa42c14f8', response.authorization + assert_equal 'PA', response.params['paymentType'] + assert response.test? + end + + def test_failed_authorize + @gateway.expects(:ssl_post).returns(failed_authorize_response) + + response = @gateway.authorize(@amount, @credit_card, @options) + assert_failure response + assert_equal 'PA', response.params['paymentType'] + assert response.test? + end + + def test_successful_capture + @gateway.expects(:ssl_post).returns(successful_capture_response) + + response = @gateway.capture(@amount, '123', @options) + assert_success response + + assert_equal '8a82944a5fe82704016002caa7cd1513', response.authorization + assert_equal 'CP', response.params['paymentType'] + assert response.test? + end + + def test_failed_capture + @gateway.expects(:ssl_post).returns(failed_capture_response) + + response = @gateway.capture(@amount, '123', @options) + assert_failure response + assert response.test? + end + + def test_successful_refund + @gateway.expects(:ssl_post).returns(successful_refund_response) + + response = @gateway.refund(@amount, '123', @options) + assert_success response + + assert_equal '8a82944a5fe82704016002cc88f61731', response.authorization + assert_equal 'RF', response.params['paymentType'] + assert response.test? + end + + def test_failed_refund + @gateway.expects(:ssl_post).returns(failed_refund_response) + + response = @gateway.refund(@amount, '123', @options) + assert_failure response + end + + def test_successful_void + @gateway.expects(:ssl_post).returns(successful_void_response) + + response = @gateway.void('123') + assert_success response + + assert_equal '8a8294495fe8084a016002cc489446d6', response.authorization + assert_equal 'RV', response.params['paymentType'] + assert response.test? + end + + def test_failed_void + @gateway.expects(:ssl_post).returns(failed_void_response) + + response = @gateway.void('123') + assert_failure response + end + + def test_successful_verify + @gateway.stubs(:ssl_post).returns(successful_authorize_response, successful_capture_response) + + response = @gateway.verify(@credit_card, @options) + assert_success response + assert response.test? + end + + def test_successful_verify_with_failed_void + @gateway.stubs(:ssl_post).returns(successful_authorize_response, failed_void_response) + + response = @gateway.verify(@credit_card, @options) + assert_failure response + assert response.test? + end + + def test_failed_verify + @gateway.expects(:ssl_post).returns(failed_authorize_response) + + response = @gateway.verify(@credit_card, @options) + assert_failure response + assert response.test? + end + + def test_general_credit + @gateway.expects(:ssl_post).returns(successful_credit_response) + + response = @gateway.credit(@amount, @credit_card, @options) + assert_success response + + assert_equal '8a8294495fe8084a01600332a83d4899', response.authorization + assert_equal 'CD', response.params['paymentType'] + assert response.test? + end + + def test_scrub + assert @gateway.supports_scrubbing? + assert_equal @gateway.scrub(pre_scrubbed), post_scrubbed + end + + def test_error_code_parsing + codes = { + '000.000.000' => nil, + '100.100.101' => :incorrect_number, + '100.100.303' => :expired_card, + '100.100.305' => :invalid_expiry_date, + '100.100.600' => :invalid_cvc, + '200.222.222' => :config_error, + '700.777.777' => :config_error, + '800.800.101' => :card_declined, + '800.800.102' => :incorrect_address, + '800.800.302' => :incorrect_address, + '800.800.150' => :card_declined, + '800.100.151' => :invalid_number, + '800.100.152' => :card_declined, + '800.100.153' => :incorrect_cvc, + '800.100.154' => :card_declined, + '800.800.202' => :invalid_zip + } + codes.each_pair do |code, key| + response = {'result' => {'code' => code}} + assert_equal Gateway::STANDARD_ERROR_CODE[key], @gateway.send(:error_code_from, response), "expecting #{code} => #{key}" + end + end + + private + + def pre_scrubbed + %q( +opening connection to test.vr-pay-ecommerce.de:443... +opened +starting SSL for test.vr-pay-ecommerce.de:443... +SSL established +<- "POST /v1/payments HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: test.vr-pay-ecommerce.de\r\nContent-Length: 447\r\n\r\n" +<- "amount=1.00&currency=EUR&paymentBrand=VISA&card.number=4200000000000000&card.holder=Longbob+Longsen&card.expiryMonth=09&card.expiryYear=2018&card.cvv=123&billing.street1=456+My+Street&billing.street2=Apt+1&billing.city=Ottawa&billing.state=ON&billing.postcode=K1C2N6&billing.country=CA&authentication.userId=8a8294174e735d0c014e78beb6c5154f&authentication.password=cTZjAm9c87&authentication.entityId=8a8294174e735d0c014e78beb6b9154b&paymentType=DB" +-> "HTTP/1.1 200 OK\r\n" +-> "Date: Mon, 27 Nov 2017 21:40:52 GMT\r\n" +-> "Server: Apache-Coyote/1.1\r\n" +-> "Strict-Transport-Security: max-age=63072000; includeSubdomains; preload\r\n" +-> "X-Content-Type-Options: nosniff\r\n" +-> "X-XSS-Protection: 1; mode=block\r\n" +-> "Access-Control-Allow-Origin: *\r\n" +-> "Access-Control-Allow-Credentials: true\r\n" +-> "X-Application-WAF-Action: allow\r\n" +-> "Content-Type: application/json;charset=UTF-8\r\n" +-> "Content-Length: 725\r\n" +-> "X-Content-Type-Options: nosniff\r\n" +-> "Connection: close\r\n" +-> "\r\n" +reading 725 bytes... +-> "" +-> "{\"id\":\"8a82944a5fe82704015fff6cf5e572b4\",\"paymentType\":\"DB\",\"paymentBrand\":\"VISA\",\"amount\":\"1.00\",\"currency\":\"EUR\",\"descriptor\":\"5901.3583.3250 OPP_Channel \",\"result\":{\"code\":\"000.100.110\",\"description\":\"Request successfully processed in 'Merchant in Integrator Test Mode'\"},\"card\":{\"bin\":\"420000\",\"last4Digits\":\"0000\",\"holder\":\"Longbob Longsen\",\"expiryMonth\":\"09\",\"expiryYear\":\"2018\"},\"billing\":{\"street1\":\"456 My Street\",\"street2\":\"Apt 1\",\"city\":\"Ottawa\",\"state\":\"ON\",\"postcode\":\"K1C2N6\",\"country\":\"CA\"},\"risk\":{\"score\":\"100\"},\"buildNumber\":\"a89317e58e01406de09ff75de6c962f2365f66e9@2017-11-27 15:38:09 +0000\",\"timestamp\":\"2017-11-27 21:40:51+0000\",\"ndc\":\"8a8294174e735d0c014e78beb6b9154b_24979bbf8c3a424cbd25e59860bb5417\"}" +read 725 bytes +Conn close + ) + end + + def post_scrubbed + %q( +opening connection to test.vr-pay-ecommerce.de:443... +opened +starting SSL for test.vr-pay-ecommerce.de:443... +SSL established +<- "POST /v1/payments HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: test.vr-pay-ecommerce.de\r\nContent-Length: 447\r\n\r\n" +<- "amount=1.00&currency=EUR&paymentBrand=VISA&card.number=[FILTERED]&card.holder=Longbob+Longsen&card.expiryMonth=09&card.expiryYear=2018&card.cvv=[FILTERED]&billing.street1=456+My+Street&billing.street2=Apt+1&billing.city=Ottawa&billing.state=ON&billing.postcode=K1C2N6&billing.country=CA&authentication.userId=[FILTERED]&authentication.password=[FILTERED]&authentication.entityId=[FILTERED]&paymentType=DB" +-> "HTTP/1.1 200 OK\r\n" +-> "Date: Mon, 27 Nov 2017 21:40:52 GMT\r\n" +-> "Server: Apache-Coyote/1.1\r\n" +-> "Strict-Transport-Security: max-age=63072000; includeSubdomains; preload\r\n" +-> "X-Content-Type-Options: nosniff\r\n" +-> "X-XSS-Protection: 1; mode=block\r\n" +-> "Access-Control-Allow-Origin: *\r\n" +-> "Access-Control-Allow-Credentials: true\r\n" +-> "X-Application-WAF-Action: allow\r\n" +-> "Content-Type: application/json;charset=UTF-8\r\n" +-> "Content-Length: 725\r\n" +-> "X-Content-Type-Options: nosniff\r\n" +-> "Connection: close\r\n" +-> "\r\n" +reading 725 bytes... +-> "" +-> "{\"id\":\"8a82944a5fe82704015fff6cf5e572b4\",\"paymentType\":\"DB\",\"paymentBrand\":\"VISA\",\"amount\":\"1.00\",\"currency\":\"EUR\",\"descriptor\":\"5901.3583.3250 OPP_Channel \",\"result\":{\"code\":\"000.100.110\",\"description\":\"Request successfully processed in 'Merchant in Integrator Test Mode'\"},\"card\":{\"bin\":\"420000\",\"last4Digits\":\"0000\",\"holder\":\"Longbob Longsen\",\"expiryMonth\":\"09\",\"expiryYear\":\"2018\"},\"billing\":{\"street1\":\"456 My Street\",\"street2\":\"Apt 1\",\"city\":\"Ottawa\",\"state\":\"ON\",\"postcode\":\"K1C2N6\",\"country\":\"CA\"},\"risk\":{\"score\":\"100\"},\"buildNumber\":\"a89317e58e01406de09ff75de6c962f2365f66e9@2017-11-27 15:38:09 +0000\",\"timestamp\":\"2017-11-27 21:40:51+0000\",\"ndc\":\"8a8294174e735d0c014e78beb6b9154b_24979bbf8c3a424cbd25e59860bb5417\"}" +read 725 bytes +Conn close + ) + end + + def successful_purchase_response + "{\"id\":\"8a8294495fe8084a016002dd17c163fd\",\"paymentType\":\"DB\",\"paymentBrand\":\"VISA\",\"amount\":\"1.00\",\"currency\":\"EUR\",\"descriptor\":\"0177.7272.0802 OPP_Channel \",\"result\":{\"code\":\"000.100.110\",\"description\":\"Request successfully processed in 'Merchant in Integrator Test Mode'\"},\"card\":{\"bin\":\"420000\",\"last4Digits\":\"0000\",\"holder\":\"Longbob Longsen\",\"expiryMonth\":\"09\",\"expiryYear\":\"2018\"},\"billing\":{\"street1\":\"456 My Street\",\"street2\":\"Apt 1\",\"city\":\"Ottawa\",\"state\":\"ON\",\"postcode\":\"K1C2N6\",\"country\":\"CA\"},\"risk\":{\"score\":\"100\"},\"buildNumber\":\"a89317e58e01406de09ff75de6c962f2365f66e9@2017-11-27 15:38:09 +0000\",\"timestamp\":\"2017-11-28 13:42:12+0000\",\"ndc\":\"8a8294174e735d0c014e78beb6b9154b_ed9f9af4170942719cf6e3446f823f45\"}" + end + + def failed_purchase_response + '{"id":"8a82944a5fe827040160030a3308412d","paymentType":"DB","paymentBrand":"VISA","result":{"code":"100.100.101","description":"invalid creditcard, bank account number or bank name"},"card":{"bin":"420000","last4Digits":"0001","holder":"Longbob Longsen","expiryMonth":"09","expiryYear":"2018"},"billing":{"street1":"456 My Street","street2":"Apt 1","city":"Ottawa","state":"ON","postcode":"K1C2N6","country":"CA"},"buildNumber":"a89317e58e01406de09ff75de6c962f2365f66e9@2017-11-27 15:38:09 +0000","timestamp":"2017-11-28 14:31:28+0000","ndc":"8a8294174e735d0c014e78beb6b9154b_43d463600f8d429ea6ac09cf25fd9f24"}' + end + + def successful_authorize_response + "{\"id\":\"8a82944a5fe82704016002caa42c14f8\",\"paymentType\":\"PA\",\"paymentBrand\":\"VISA\",\"amount\":\"1.00\",\"currency\":\"EUR\",\"descriptor\":\"8374.4038.5698 OPP_Channel \",\"result\":{\"code\":\"000.100.110\",\"description\":\"Request successfully processed in 'Merchant in Integrator Test Mode'\"},\"card\":{\"bin\":\"420000\",\"last4Digits\":\"0000\",\"holder\":\"Longbob Longsen\",\"expiryMonth\":\"09\",\"expiryYear\":\"2018\"},\"billing\":{\"street1\":\"456 My Street\",\"street2\":\"Apt 1\",\"city\":\"Ottawa\",\"state\":\"ON\",\"postcode\":\"K1C2N6\",\"country\":\"CA\"},\"risk\":{\"score\":\"100\"},\"buildNumber\":\"a89317e58e01406de09ff75de6c962f2365f66e9@2017-11-27 15:38:09 +0000\",\"timestamp\":\"2017-11-28 13:22:03+0000\",\"ndc\":\"8a8294174e735d0c014e78beb6b9154b_fc59650eafc84da29ce10e7171e71a34\"}" + end + + def failed_authorize_response + '{"paymentType":"PA","paymentBrand":"VISA","result":{"code":"800.100.151","description":"transaction declined (invalid card)"},"card":{"bin":"420000","last4Digits":"0000","holder":"Longbob Longsen","expiryMonth":"09","expiryYear":"2018"},"billing":{"street1":"456 My Street","street2":"Apt 1","city":"Ottawa","state":"ON","postcode":"K1C2N6","country":"CA"},"customParameters":{"forceResultCode":"800.100.151"},"buildNumber":"a89317e58e01406de09ff75de6c962f2365f66e9@2017-11-27 15:38:09 +0000","timestamp":"2017-11-28 13:50:31+0000","ndc":"8a8294174e735d0c014e78beb6b9154b_1077c67bc41048ff887da9ab9ee8b89d"}' + end + + def successful_capture_response + "{\"id\":\"8a82944a5fe82704016002caa7cd1513\",\"paymentType\":\"CP\",\"amount\":\"1.00\",\"currency\":\"EUR\",\"descriptor\":\"4938.4300.2018 OPP_Channel\",\"result\":{\"code\":\"000.100.110\",\"description\":\"Request successfully processed in 'Merchant in Integrator Test Mode'\"},\"risk\":{\"score\":\"0\"},\"buildNumber\":\"a89317e58e01406de09ff75de6c962f2365f66e9@2017-11-27 15:38:09 +0000\",\"timestamp\":\"2017-11-28 13:22:03+0000\",\"ndc\":\"8a8294174e735d0c014e78beb6b9154b_fb956c7668a04e18af61519693a1d114\"}" + end + + def failed_capture_response + '{"id":"8a82944a5fe8270401600313d3965bca","paymentType":"CP","result":{"code":"700.400.510","description":"capture needs at least one successful transaction of type (PA)"},"risk":{"score":"0"},"buildNumber":"a89317e58e01406de09ff75de6c962f2365f66e9@2017-11-27 15:38:09 +0000","timestamp":"2017-11-28 14:41:59+0000","ndc":"8a8294174e735d0c014e78beb6b9154b_8abfd898a6cd406f94181d9096607aea"}' + end + + def successful_refund_response + "{\"id\":\"8a82944a5fe82704016002cc88f61731\",\"paymentType\":\"RF\",\"amount\":\"1.00\",\"currency\":\"EUR\",\"descriptor\":\"3478.1411.3954 OPP_Channel\",\"result\":{\"code\":\"000.100.110\",\"description\":\"Request successfully processed in 'Merchant in Integrator Test Mode'\"},\"buildNumber\":\"a89317e58e01406de09ff75de6c962f2365f66e9@2017-11-27 15:38:09 +0000\",\"timestamp\":\"2017-11-28 13:24:07+0000\",\"ndc\":\"8a8294174e735d0c014e78beb6b9154b_99293f4fffe64105870e77b8e18c0c02\"}" + end + + def failed_refund_response + '{"result":{"code":"200.300.404","description":"invalid or missing parameter","parameterErrors":[{"name":"paymentType","value":"RF","message":"must be one of [PA, DB, CD, PA.CP]"},{"name":"paymentBrand","value":null,"message":"card properties must be set"}]},"buildNumber":"a89317e58e01406de09ff75de6c962f2365f66e9@2017-11-27 15:38:09 +0000","timestamp":"2017-11-28 14:33:31+0000","ndc":"8a8294174e735d0c014e78beb6b9154b_febee8f6863b4392b064b23602f3f382"}' + end + + def successful_void_response + "{\"id\":\"8a8294495fe8084a016002cc489446d6\",\"paymentType\":\"RV\",\"amount\":\"1.00\",\"currency\":\"EUR\",\"descriptor\":\"9673.6314.6402 OPP_Channel\",\"result\":{\"code\":\"000.100.110\",\"description\":\"Request successfully processed in 'Merchant in Integrator Test Mode'\"},\"risk\":{\"score\":\"0\"},\"buildNumber\":\"a89317e58e01406de09ff75de6c962f2365f66e9@2017-11-27 15:38:09 +0000\",\"timestamp\":\"2017-11-28 13:23:50+0000\",\"ndc\":\"8a8294174e735d0c014e78beb6b9154b_744a5416960a420da01edc3b3daf6c6f\"}" + end + + def failed_void_response + '{"result":{"code":"200.300.404","description":"invalid or missing parameter","parameterErrors":[{"name":"paymentBrand","value":null,"message":"card properties must be set"},{"name":"paymentType","value":"RV","message":"must be one of [PA, DB, CD, PA.CP]"},{"name":"amount","value":null,"message":"may not be empty"},{"name":"currency","value":null,"message":"may not be empty"}]},"buildNumber":"a89317e58e01406de09ff75de6c962f2365f66e9@2017-11-27 15:38:09 +0000","timestamp":"2017-11-28 14:34:50+0000","ndc":"8a8294174e735d0c014e78beb6b9154b_4a909e0b99214eb9b155b46a2c67df30"}' + end + + def successful_credit_response + "{\"id\":\"8a8294495fe8084a01600332a83d4899\",\"paymentType\":\"CD\",\"paymentBrand\":\"VISA\",\"amount\":\"1.00\",\"currency\":\"EUR\",\"descriptor\":\"2299.3739.4338 OPP_Channel \",\"result\":{\"code\":\"000.100.110\",\"description\":\"Request successfully processed in 'Merchant in Integrator Test Mode'\"},\"card\":{\"bin\":\"420000\",\"last4Digits\":\"0000\",\"holder\":\"Longbob Longsen\",\"expiryMonth\":\"09\",\"expiryYear\":\"2018\"},\"billing\":{\"street1\":\"456 My Street\",\"street2\":\"Apt 1\",\"city\":\"Ottawa\",\"state\":\"ON\",\"postcode\":\"K1C2N6\",\"country\":\"CA\"},\"risk\":{\"score\":\"100\"},\"buildNumber\":\"a89317e58e01406de09ff75de6c962f2365f66e9@2017-11-27 15:38:09 +0000\",\"timestamp\":\"2017-11-28 15:15:39+0000\",\"ndc\":\"8a8294174e735d0c014e78beb6b9154b_691783d2e7834e6eb8ca011f4fee1b74\"}" + end +end diff --git a/test/unit/gateways/cashnet_test.rb b/test/unit/gateways/cashnet_test.rb new file mode 100644 index 00000000000..844702eac9a --- /dev/null +++ b/test/unit/gateways/cashnet_test.rb @@ -0,0 +1,304 @@ +require 'test_helper' + +class Cashnet < Test::Unit::TestCase + include CommStub + + def setup + @gateway = CashnetGateway.new( + merchant: 'X', + operator: 'X', + password: 'test123', + merchant_gateway_name: 'X' + ) + @amount = 100 + @credit_card = credit_card + end + + def test_successful_purchase + @gateway.expects(:ssl_post).returns(successful_purchase_response) + assert response = @gateway.purchase(@amount, @credit_card) + assert_instance_of Response, response + assert_success response + assert_equal '1234', response.authorization + end + + def test_failed_purchase + @gateway.expects(:ssl_post).returns(failed_purchase_response) + + assert response = @gateway.purchase(@amount, @credit_card) + assert_instance_of Response, response + assert_failure response + assert_equal 'Invalid expiration date, no expiration date provided', response.message + assert_equal '', response.authorization + end + + def test_successful_refund + @gateway.expects(:ssl_post).returns(successful_purchase_response) + assert response = @gateway.refund(@amount, @credit_card) + assert_instance_of Response, response + assert_success response + assert_equal '1234', response.authorization + end + + def test_failed_refund + @gateway.expects(:ssl_post).returns(failed_refund_response) + + assert response = @gateway.refund(@amount, @credit_card) + assert_instance_of Response, response + assert_failure response + assert_equal 'Refund amounts should be expressed as positive amounts', response.message + assert_equal '', response.authorization + end + + def test_supported_countries + assert_equal ['US'], CashnetGateway.supported_countries + end + + def test_supported_card_types + assert_equal [:visa, :master, :american_express, :discover, :diners_club, :jcb], CashnetGateway.supported_cardtypes + end + + def test_add_invoice + result = {} + @gateway.send(:add_invoice, result, order_id: '#1001') + assert_equal '#1001', result[:order_number] + end + + def test_add_creditcard + result = {} + @gateway.send(:add_creditcard, result, @credit_card) + assert_equal @credit_card.number, result[:cardno] + assert_equal @credit_card.verification_value, result[:cid] + assert_equal expected_expiration_date, result[:expdate] + assert_equal 'Longbob Longsen', result[:card_name_g] + end + + def test_add_address + result = {} + + @gateway.send(:add_address, result, billing_address: {address1: '123 Test St.', address2: '5F', city: 'Testville', zip: '12345', state: 'AK'}) + + assert_equal ['addr_g', 'city_g', 'state_g', 'zip_g'], result.stringify_keys.keys.sort + assert_equal '123 Test St.,5F', result[:addr_g] + assert_equal 'Testville', result[:city_g] + assert_equal 'AK', result[:state_g] + assert_equal '12345', result[:zip_g] + end + + def test_add_customer_data + result = {} + @gateway.send(:add_customer_data, result, email: 'test@test.com') + assert_equal 'test@test.com', result[:email_g] + end + + def test_action_meets_minimum_requirements + params = { + amount: '1.01', + } + + @gateway.send(:add_creditcard, params, @credit_card) + @gateway.send(:add_invoice, params, {}) + + assert data = @gateway.send(:post_data, 'SALE', params) + minimum_requirements.each do |key| + assert_not_nil(data =~ /#{key}=/) + end + end + + def test_successful_purchase_with_fname_and_lname + stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, @credit_card, {}) + end.check_request do |method, endpoint, data, headers| + assert_match(/fname=Longbob/, data) + assert_match(/lname=Longsen/, data) + end.respond_with(successful_purchase_response) + end + + def test_invalid_response + response = stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, @credit_card) + end.respond_with(invalid_response) + + assert_failure response + assert_match %r{Unparsable response received}, response.message + end + + def test_passes_custcode_from_credentials + gateway = CashnetGateway.new(merchant: 'X', operator: 'X', password: 'test123', merchant_gateway_name: 'X', custcode: 'TheCustCode') + stub_comms(gateway, :ssl_request) do + gateway.purchase(@amount, @credit_card, {}) + end.check_request do |method, endpoint, data, headers| + assert_match(/custcode=TheCustCode/, data) + end.respond_with(successful_purchase_response) + end + + def test_allows_custcode_override + gateway = CashnetGateway.new(merchant: 'X', operator: 'X', password: 'test123', merchant_gateway_name: 'X', custcode: 'TheCustCode') + stub_comms(gateway, :ssl_request) do + gateway.purchase(@amount, @credit_card, custcode: 'OveriddenCustCode') + end.check_request do |method, endpoint, data, headers| + assert_match(/custcode=OveriddenCustCode/, data) + end.respond_with(successful_purchase_response) + end + + def test_scrub + assert @gateway.supports_scrubbing? + assert_equal @gateway.scrub(pre_scrubbed), post_scrubbed + end + + private + + def expected_expiration_date + '%02d%02d' % [@credit_card.month, @credit_card.year.to_s[2..4]] + end + + def minimum_requirements + %w(command merchant operator station password amount custcode itemcode) + end + + def successful_refund_response + '<cngateway>result=0&respmessage=Success&tx=1234</cngateway>' + end + + def failed_refund_response + '<cngateway>result=305&respmessage=Failed</cngateway>' + end + + def successful_purchase_response + '<cngateway>result=0&respmessage=Success&tx=1234</cngateway>' + end + + def failed_purchase_response + '<cngateway>result=7&respmessage=Failed</cngateway>' + end + + def invalid_response + 'A String without a cngateway tag' + end + + def pre_scrubbed + <<-TRANSCRIPT +opening connection to train.cashnet.com:443... +opened +starting SSL for train.cashnet.com:443... +SSL established +<- "POST /givecorpsgateway HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: train.cashnet.com\r\nContent-Length: 364\r\n\r\n" +<- "command=SALE&merchant=GiveCorpGW&operator=givecorp&password=14givecorps&station=WEB&custcode=ActiveMerchant%2F1.76.0&cardno=5454545454545454&cid=123&expdate=1215&card_name_g=Longbob+Longsen&fname=Longbob&lname=Longsen&order_number=c440ec8493f215d21c8a993ceae30129&itemcode=FEE&addr_g=456+My+Street%2CApt+1&city_g=Ottawa&state_g=ON&zip_g=K1C2N6&email_g=&amount=1.00" +-> "HTTP/1.1 302 Found\r\n" +-> "Date: Wed, 03 Jan 2018 17:03:35 GMT\r\n" +-> "Content-Type: text/html; charset=utf-8\r\n" +-> "Transfer-Encoding: chunked\r\n" +-> "Connection: close\r\n" +-> "Set-Cookie: AWSALB=5ISjTg8Mez7jS1kEnzY4j5NkQ5bdlwDDNmfzTyEMBmILpb0Tn3k58pUQTGHBj3NUpciP0uqQs7FaAb42YZvt35ndLERGJA0dPQ03iCfrqbneQ+Wm5BhDzMGo5GUT; Expires=Wed, 10 Jan 2018 17:03:35 GMT; Path=/\r\n" +-> "Set-Cookie: AWSALB=bVhwwfJ2D6cI5zB3eapqNStEzF5yX1pXhaJGUBUCa+DZhEgn/TZGxznxIOYB9qKqzkPF4lq/zxWg/tuMBTiY4JGLRjayyhizvHnj2smrnNvr2DLQN7ZjLSh51BzM; Expires=Wed, 10 Jan 2018 17:03:35 GMT; Path=/\r\n" +-> "Cache-Control: private\r\n" +-> "Location: https://train.cashnet.com/cashneti/Gateway/htmlgw.aspx?client=EMARKETVENDOR_DEMO&command=SALE&merchant=GiveCorpGW&operator=givecorp&password=14givecorps&station=WEB&custcode=ActiveMerchant%2f1.76.0&cardno=5454545454545454&cid=123&expdate=1215&card_name_g=Longbob+Longsen&fname=Longbob&lname=Longsen&order_number=c440ec8493f215d21c8a993ceae30129&itemcode=FEE&addr_g=456+My+Street%2cApt+1&city_g=Ottawa&state_g=ON&zip_g=K1C2N6&email_g=&amount=1.00\r\n" +-> "Set-Cookie: ASP.NET_SessionId=; path=/; HttpOnly\r\n" +-> "P3P: CP=\"NOI DSP COR NID NOR\"\r\n" +-> "Set-Cookie: BNI_persistence=0000000000000000000000004d79da0a00005000; Path=/\r\n" +-> "Strict-Transport-Security: max-age=31536000\r\n" +-> "\r\n" +-> "282\r\n" +reading 642 bytes... +-> "<html><head><title>Object moved</title></head><body>\r\n<h2>Object moved to <a href=\"https://train.cashnet.com/cashneti/Gateway/htmlgw.aspx?client=EMARKETVENDOR_DEMO&amp;command=SALE&amp;merchant=GiveCorpGW&amp;operator=givecorp&amp;password=14givecorps&amp;station=WEB&amp;custcode=ActiveMerchant%2f1.76.0&amp;cardno=5454545454545454&amp;cid=123&amp;expdate=1215&amp;card_name_g=Longbob+Longsen&amp;fname=Longbob&amp;lname=Longsen&amp;order_number=c440ec8493f215d21c8a993ceae30129&amp;itemcode=FEE&amp;addr_g=456+My+Street%2cApt+1&amp;city_g=Ottawa&amp;state_g=ON&amp;zip_g=K1C2N6&amp;email_g=&amp;amount=1.00\">here</a>.</h2>\r\n</body></html>\r\n" +read 642 bytes +reading 2 bytes... +-> "\r\n" +read 2 bytes +-> "0\r\n" +-> "\r\n" +Conn close +opening connection to train.cashnet.com:443... +opened +starting SSL for train.cashnet.com:443... +SSL established +<- "GET /cashneti/Gateway/htmlgw.aspx?client=EMARKETVENDOR_DEMO&command=SALE&merchant=GiveCorpGW&operator=givecorp&password=14givecorps&station=WEB&custcode=ActiveMerchant%2f1.76.0&cardno=5454545454545454&cid=123&expdate=1215&card_name_g=Longbob+Longsen&fname=Longbob&lname=Longsen&order_number=c440ec8493f215d21c8a993ceae30129&itemcode=FEE&addr_g=456+My+Street%2cApt+1&city_g=Ottawa&state_g=ON&zip_g=K1C2N6&email_g=&amount=1.00 HTTP/1.1\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: train.cashnet.com\r\n\r\n" +-> "HTTP/1.1 200 OK\r\n" +-> "Date: Wed, 03 Jan 2018 17:03:35 GMT\r\n" +-> "Content-Type: text/html; charset=utf-8\r\n" +-> "Transfer-Encoding: chunked\r\n" +-> "Connection: close\r\n" +-> "Set-Cookie: AWSALB=lFPwFYRnXJHRNmE6NCRAIfHtQadwx4bYJoT5xeAL5AuAXPcm1vYWx5F/s5FBr3GcungifktpWlwIgAmWS29K7YRXTCjk4xmcAnhXS86fpVUVQt4ECwPH2xdv8tf2; Expires=Wed, 10 Jan 2018 17:03:35 GMT; Path=/\r\n" +-> "Set-Cookie: AWSALB=mEfysFNBclo1/9+tTuI/XtHrmVkD89Fh6tAJ3Gl0u2EuLCYTW5VwEq+fVqYG1fEkN02dbhKSkIdM22QvyT6cRccDaUBsYAnOKjg2JlVShJlf+li5tfbrsUDk14jG; Expires=Wed, 10 Jan 2018 17:03:35 GMT; Path=/\r\n" +-> "Cache-Control: private\r\n" +-> "Set-Cookie: ASP.NET_SessionId=3ocslggtk4cdz54unbdnm25o; path=/; HttpOnly\r\n" +-> "P3P: CP=\"NOI DSP COR NID NOR\"\r\n" +-> "Set-Cookie: BNI_persistence=0000000000000000000000004d79da0a00005000; Path=/\r\n" +-> "Strict-Transport-Security: max-age=31536000\r\n" +-> "\r\n" +-> "3a\r\n" +reading 58 bytes... +-> "<cngateway>result=0&tx=77972&busdate=7/25/2017</cngateway>" +read 58 bytes +reading 2 bytes... +-> "\r\n" +read 2 bytes +-> "0\r\n" +-> "\r\n" +Conn close +TRANSCRIPT + end + + def post_scrubbed + <<-SCRUBBED +opening connection to train.cashnet.com:443... +opened +starting SSL for train.cashnet.com:443... +SSL established +<- "POST /givecorpsgateway HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: train.cashnet.com\r\nContent-Length: 364\r\n\r\n" +<- "command=SALE&merchant=GiveCorpGW&operator=givecorp&password=[FILTERED]&station=WEB&custcode=ActiveMerchant%2F1.76.0&cardno=[FILTERED]&cid=[FILTERED]&expdate=1215&card_name_g=Longbob+Longsen&fname=Longbob&lname=Longsen&order_number=c440ec8493f215d21c8a993ceae30129&itemcode=FEE&addr_g=456+My+Street%2CApt+1&city_g=Ottawa&state_g=ON&zip_g=K1C2N6&email_g=&amount=1.00" +-> "HTTP/1.1 302 Found\r\n" +-> "Date: Wed, 03 Jan 2018 17:03:35 GMT\r\n" +-> "Content-Type: text/html; charset=utf-8\r\n" +-> "Transfer-Encoding: chunked\r\n" +-> "Connection: close\r\n" +-> "Set-Cookie: AWSALB=5ISjTg8Mez7jS1kEnzY4j5NkQ5bdlwDDNmfzTyEMBmILpb0Tn3k58pUQTGHBj3NUpciP0uqQs7FaAb42YZvt35ndLERGJA0dPQ03iCfrqbneQ+Wm5BhDzMGo5GUT; Expires=Wed, 10 Jan 2018 17:03:35 GMT; Path=/\r\n" +-> "Set-Cookie: AWSALB=bVhwwfJ2D6cI5zB3eapqNStEzF5yX1pXhaJGUBUCa+DZhEgn/TZGxznxIOYB9qKqzkPF4lq/zxWg/tuMBTiY4JGLRjayyhizvHnj2smrnNvr2DLQN7ZjLSh51BzM; Expires=Wed, 10 Jan 2018 17:03:35 GMT; Path=/\r\n" +-> "Cache-Control: private\r\n" +-> "Location: https://train.cashnet.com/cashneti/Gateway/htmlgw.aspx?client=EMARKETVENDOR_DEMO&command=SALE&merchant=GiveCorpGW&operator=givecorp&password=[FILTERED]&station=WEB&custcode=ActiveMerchant%2f1.76.0&cardno=[FILTERED]&cid=[FILTERED]&expdate=1215&card_name_g=Longbob+Longsen&fname=Longbob&lname=Longsen&order_number=c440ec8493f215d21c8a993ceae30129&itemcode=FEE&addr_g=456+My+Street%2cApt+1&city_g=Ottawa&state_g=ON&zip_g=K1C2N6&email_g=&amount=1.00\r\n" +-> "Set-Cookie: ASP.NET_SessionId=; path=/; HttpOnly\r\n" +-> "P3P: CP=\"NOI DSP COR NID NOR\"\r\n" +-> "Set-Cookie: BNI_persistence=0000000000000000000000004d79da0a00005000; Path=/\r\n" +-> "Strict-Transport-Security: max-age=31536000\r\n" +-> "\r\n" +-> "282\r\n" +reading 642 bytes... +-> "<html><head><title>Object moved</title></head><body>\r\n<h2>Object moved to <a href=\"https://train.cashnet.com/cashneti/Gateway/htmlgw.aspx?client=EMARKETVENDOR_DEMO&amp;command=SALE&amp;merchant=GiveCorpGW&amp;operator=givecorp&amp;password=[FILTERED]&amp;station=WEB&amp;custcode=ActiveMerchant%2f1.76.0&amp;cardno=[FILTERED]&amp;cid=[FILTERED]&amp;expdate=1215&amp;card_name_g=Longbob+Longsen&amp;fname=Longbob&amp;lname=Longsen&amp;order_number=c440ec8493f215d21c8a993ceae30129&amp;itemcode=FEE&amp;addr_g=456+My+Street%2cApt+1&amp;city_g=Ottawa&amp;state_g=ON&amp;zip_g=K1C2N6&amp;email_g=&amp;amount=1.00\">here</a>.</h2>\r\n</body></html>\r\n" +read 642 bytes +reading 2 bytes... +-> "\r\n" +read 2 bytes +-> "0\r\n" +-> "\r\n" +Conn close +opening connection to train.cashnet.com:443... +opened +starting SSL for train.cashnet.com:443... +SSL established +<- "GET /cashneti/Gateway/htmlgw.aspx?client=EMARKETVENDOR_DEMO&command=SALE&merchant=GiveCorpGW&operator=givecorp&password=[FILTERED]&station=WEB&custcode=ActiveMerchant%2f1.76.0&cardno=[FILTERED]&cid=[FILTERED]&expdate=1215&card_name_g=Longbob+Longsen&fname=Longbob&lname=Longsen&order_number=c440ec8493f215d21c8a993ceae30129&itemcode=FEE&addr_g=456+My+Street%2cApt+1&city_g=Ottawa&state_g=ON&zip_g=K1C2N6&email_g=&amount=1.00 HTTP/1.1\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: train.cashnet.com\r\n\r\n" +-> "HTTP/1.1 200 OK\r\n" +-> "Date: Wed, 03 Jan 2018 17:03:35 GMT\r\n" +-> "Content-Type: text/html; charset=utf-8\r\n" +-> "Transfer-Encoding: chunked\r\n" +-> "Connection: close\r\n" +-> "Set-Cookie: AWSALB=lFPwFYRnXJHRNmE6NCRAIfHtQadwx4bYJoT5xeAL5AuAXPcm1vYWx5F/s5FBr3GcungifktpWlwIgAmWS29K7YRXTCjk4xmcAnhXS86fpVUVQt4ECwPH2xdv8tf2; Expires=Wed, 10 Jan 2018 17:03:35 GMT; Path=/\r\n" +-> "Set-Cookie: AWSALB=mEfysFNBclo1/9+tTuI/XtHrmVkD89Fh6tAJ3Gl0u2EuLCYTW5VwEq+fVqYG1fEkN02dbhKSkIdM22QvyT6cRccDaUBsYAnOKjg2JlVShJlf+li5tfbrsUDk14jG; Expires=Wed, 10 Jan 2018 17:03:35 GMT; Path=/\r\n" +-> "Cache-Control: private\r\n" +-> "Set-Cookie: ASP.NET_SessionId=3ocslggtk4cdz54unbdnm25o; path=/; HttpOnly\r\n" +-> "P3P: CP=\"NOI DSP COR NID NOR\"\r\n" +-> "Set-Cookie: BNI_persistence=0000000000000000000000004d79da0a00005000; Path=/\r\n" +-> "Strict-Transport-Security: max-age=31536000\r\n" +-> "\r\n" +-> "3a\r\n" +reading 58 bytes... +-> "<cngateway>result=0&tx=77972&busdate=7/25/2017</cngateway>" +read 58 bytes +reading 2 bytes... +-> "\r\n" +read 2 bytes +-> "0\r\n" +-> "\r\n" +Conn close +SCRUBBED + end +end diff --git a/test/unit/gateways/cecabank_test.rb b/test/unit/gateways/cecabank_test.rb new file mode 100644 index 00000000000..87ad7e20ab5 --- /dev/null +++ b/test/unit/gateways/cecabank_test.rb @@ -0,0 +1,154 @@ +require 'test_helper' + +class CecabankTest < Test::Unit::TestCase + include CommStub + + def setup + @gateway = CecabankGateway.new( + :merchant_id => '12345678', + :acquirer_bin => '12345678', + :terminal_id => '00000003', + :key => 'enc_key' + ) + + @credit_card = credit_card + @amount = 100 + + @options = { + :order_id => '1', + :description => 'Store Purchase' + } + end + + def test_successful_purchase + @gateway.expects(:ssl_post).returns(successful_purchase_response) + + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_instance_of Response, response + assert_success response + assert_equal '12345678901234567890|202215722', response.authorization + assert response.test? + end + + def test_invalid_xml_response_handling + @gateway.expects(:ssl_post).returns(invalid_xml_purchase_response) + + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_instance_of Response, response + assert_failure response + assert_match(/Unable to parse the response/, response.message) + assert_match(/No close tag for/, response.params['error_message']) + end + + def test_expiration_date_sent_correctly + stub_comms do + @gateway.purchase(@amount, credit_card('4242424242424242', month: 1, year: 2014), @options) + end.check_request do |endpoint, data, headers| + assert_match(/Caducidad=201401&/, data, 'Expected expiration date format is yyyymm') + end.respond_with(successful_purchase_response) + end + + def test_unsuccessful_request + @gateway.expects(:ssl_post).returns(failed_purchase_response) + + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_match(/Formato CVV2\/CVC2 no valido/, response.message) + assert response.test? + end + + def test_successful_refund_request + @gateway.expects(:ssl_post).returns(successful_refund_response) + + assert response = @gateway.refund(@amount, 'reference', @options) + assert_instance_of Response, response + assert_success response + assert response.test? + end + + def test_unsuccessful_refund_request + @gateway.expects(:ssl_post).returns(failed_refund_response) + + assert response = @gateway.refund(@amount, 'reference', @options) + assert_failure response + assert response.test? + end + + def test_transcript_scrubbing + assert_equal scrubbed_transcript, @gateway.scrub(transcript) + end + + private + + def successful_purchase_response + <<-RESPONSE +<?xml version="1.0" encoding="ISO-8859-1" ?> +<TRANSACCION valor="OK" numeroOperacion="202215722" fecha="22/01/2014 13:15:32"> + <OPERACION tipo="000"> + <importe> 171.00 Euros</importe> + <descripcion><![CDATA[blah blah blah]]></descripcion> + <numeroAutorizacion>101000</numeroAutorizacion> + <referencia>12345678901234567890</referencia> + <pan>##PAN##</pan> + </OPERACION> +</TRANSACCION> + RESPONSE + end + + def failed_purchase_response + <<-RESPONSE +<?xml version="1.0" encoding="ISO-8859-1" ?> +<TRANSACCION valor="ERROR" numeroOperacion="1390410672" fecha="22/01/2014 18:11:12"> + <ERROR> + <codigo>27</codigo> + <descripcion><![CDATA[ERROR. Formato CVV2/CVC2 no valido.]]></descripcion> + </ERROR> +</TRANSACCION> + RESPONSE + end + + def invalid_xml_purchase_response + <<-RESPONSE +<br> +<TRANSACCION valor="OK" numeroOperacion="202215722" fecha="22/01/2014 13:15:32"> +Invalid unparsable xml in the response + RESPONSE + end + + def successful_refund_response + <<-RESPONSE +<?xml version="1.0" encoding="ISO-8859-1" ?> +<TRANSACCION valor="OK" numeroOperacion="1390414594" fecha="##FECHA##" > + <OPERACION tipo="900"> + <importe> 1.00 Euros</importe> + </OPERACION> +</TRANSACCION> + RESPONSE + end + + def failed_refund_response + <<-RESPONSE +<?xml version="1.0" encoding="ISO-8859-1" ?> +<TRANSACCION valor="ERROR" numeroOperacion="1390414596" fecha="##FECHA##"> + <ERROR> + <codigo>15</codigo> + <descripcion><![CDATA[ERROR. Operaci&oacute;n inexistente <1403>]]></descripcion> + </ERROR> +</TRANSACCION> + RESPONSE + end + + def transcript + <<-TRANSCRIPT + Num_operacion=0aa49d22f66af2c07163226dca82ddb8&Idioma=XML&Pago_soportado=SSL&URL_OK=NONE&URL_NOK=NONE&Importe=100&TipoMoneda=978&PAN=5540500001000004&Caducidad=201412&CVV2=989&Pago_elegido=SSL&Cifrado=SHA1&Firma=dcef9a490380a972f8ee4d801d416115402e0c94&Exponente=2&MerchantID=331009926&AcquirerBIN=0000522577&TerminalID=00000003 + Num_operacion=0aa49d22f66af2c07163226dca82ddb8&Idioma=XML&Pago_soportado=SSL&URL_OK=NONE&URL_NOK=NONE&Importe=100&TipoMoneda=978&PAN=5540500001000004&Caducidad=201412&CVV2=989&Pago_elegido=SSL&Cifrado=SHA1&Firma=dcef9a490380a972f8ee4d801d416115402e0c94&Exponente=2&MerchantID=331009926&AcquirerBIN=0000522577&TerminalID=00000003" + TRANSCRIPT + end + + def scrubbed_transcript + <<-SCRUBBED_TRANSCRIPT + Num_operacion=0aa49d22f66af2c07163226dca82ddb8&Idioma=XML&Pago_soportado=SSL&URL_OK=NONE&URL_NOK=NONE&Importe=100&TipoMoneda=978&PAN=[FILTERED]&Caducidad=201412&CVV2=[FILTERED]&Pago_elegido=SSL&Cifrado=SHA1&Firma=dcef9a490380a972f8ee4d801d416115402e0c94&Exponente=2&MerchantID=331009926&AcquirerBIN=0000522577&TerminalID=00000003 + Num_operacion=0aa49d22f66af2c07163226dca82ddb8&Idioma=XML&Pago_soportado=SSL&URL_OK=NONE&URL_NOK=NONE&Importe=100&TipoMoneda=978&PAN=[FILTERED]&Caducidad=201412&CVV2=[FILTERED]&Pago_elegido=SSL&Cifrado=SHA1&Firma=dcef9a490380a972f8ee4d801d416115402e0c94&Exponente=2&MerchantID=331009926&AcquirerBIN=0000522577&TerminalID=00000003" + SCRUBBED_TRANSCRIPT + end +end diff --git a/test/unit/gateways/cenpos_test.rb b/test/unit/gateways/cenpos_test.rb new file mode 100644 index 00000000000..950701578bd --- /dev/null +++ b/test/unit/gateways/cenpos_test.rb @@ -0,0 +1,366 @@ +require 'test_helper' + +class CenposTest < Test::Unit::TestCase + include CommStub + + def setup + @gateway = CenposGateway.new( + :merchant_id => 'merchant_id', + :password => 'password', + :user_id => 'user_id' + ) + + @credit_card = credit_card + @amount = 100 + end + + def test_successful_purchase + response = stub_comms do + @gateway.purchase(@amount, @credit_card) + end.respond_with(successful_purchase_response) + + assert_success response + + assert_equal '1609995363|4242|1.00', response.authorization + assert response.test? + end + + def test_successful_purchase_cvv_result + response = stub_comms do + @gateway.purchase(@amount, @credit_card) + end.respond_with(successful_purchase_response) + + cvv_result = response.cvv_result + assert_equal 'M', cvv_result['code'] + end + + def test_successful_purchase_avs_result + response = stub_comms do + @gateway.purchase(@amount, @credit_card) + end.respond_with(successful_purchase_response) + + avs_result = response.avs_result + assert_equal 'D', avs_result['code'] + end + + def test_failed_purchase + response = stub_comms do + @gateway.purchase(@amount, @credit_card) + end.respond_with(failed_purchase_response) + + assert_failure response + assert_equal 'Decline transaction', response.message + assert_equal Gateway::STANDARD_ERROR_CODE[:card_declined], response.error_code + assert response.test? + end + + def test_missing_cvv_result + response = stub_comms do + @gateway.purchase(@amount, @credit_card) + end.respond_with(failed_purchase_response) + + cvv_result = response.cvv_result + assert_equal nil, cvv_result['code'] + end + + def test_failed_purchase_avs_result + response = stub_comms do + @gateway.purchase(@amount, @credit_card) + end.respond_with(failed_purchase_response) + + avs_result = response.avs_result + assert_equal nil, avs_result['code'] + end + + def test_unmatched_cvv_result + response = stub_comms do + @gateway.purchase(@amount, @credit_card) + end.respond_with(cvv_no_match_response) + + cvv_result = response.cvv_result + assert_equal 'N', cvv_result['code'] + end + + def test_avs_result_unmatched_zip + response = stub_comms do + @gateway.purchase(@amount, @credit_card) + end.respond_with(avs_zip_no_match_response) + + assert_equal 'B', response.avs_result['code'] + end + + def test_avs_result_unmatched_address + response = stub_comms do + @gateway.purchase(@amount, @credit_card) + end.respond_with(avs_billing_no_match_response) + + assert_equal 'P', response.avs_result['code'] + end + + def test_avs_result_unmatched_address_and_zip + response = stub_comms do + @gateway.purchase(@amount, @credit_card) + end.respond_with(avs_billing_and_zip_no_match_response) + + assert_equal 'C', response.avs_result['code'] + end + + def test_successful_authorize_and_capture + response = stub_comms do + @gateway.authorize(@amount, @credit_card) + end.respond_with(successful_authorize_response) + + assert_success response + assert_equal '1760035844|4242|1.00', response.authorization + + capture = stub_comms do + @gateway.capture(@amount, response.authorization) + end.check_request do |endpoint, data, headers| + assert_match(/1760035844/, data) + end.respond_with(successful_capture_response) + + assert_success capture + end + + def test_failed_authorize + response = stub_comms do + @gateway.authorize(@amount, @credit_card) + end.respond_with(failed_authorize_response) + + assert_failure response + assert_equal 'Decline transaction', response.message + assert_equal Gateway::STANDARD_ERROR_CODE[:card_declined], response.error_code + assert response.test? + end + + def test_failed_capture + response = stub_comms do + @gateway.capture(100, '') + end.respond_with(failed_capture_response) + + assert_failure response + end + + def test_successful_void + response = stub_comms do + @gateway.purchase(@amount, @credit_card) + end.respond_with(successful_authorize_response) + + assert_success response + assert_equal '1760035844|4242|1.00', response.authorization + + void = stub_comms do + @gateway.void(response.authorization) + end.check_request do |endpoint, data, headers| + assert_match(/1760035844/, data) + end.respond_with(successful_void_response) + + assert_success void + end + + def test_failed_void + response = stub_comms do + @gateway.void('1758584451|4242|1.00') + end.check_request do |endpoint, data, headers| + assert_match(/1758584451/, data) + end.respond_with(failed_void_response) + + assert_failure response + end + + def test_successful_refund + response = stub_comms do + @gateway.purchase(@amount, @credit_card) + end.respond_with(successful_purchase_response) + + assert_success response + assert_equal '1609995363|4242|1.00', response.authorization + + refund = stub_comms do + @gateway.refund(@amount, response.authorization) + end.check_request do |endpoint, data, headers| + assert_match(/1609995363/, data) + end.respond_with(successful_refund_response) + + assert_success refund + end + + def test_failed_refund + response = stub_comms do + @gateway.refund(nil, '') + end.respond_with(failed_refund_response) + + assert_failure response + end + + def test_successful_credit + response = stub_comms do + @gateway.credit(@amount, @credit_card) + end.respond_with(successful_credit_response) + + assert_success response + + assert_equal '1609996211|4242|1.00', response.authorization + assert response.test? + end + + def test_failed_credit + response = stub_comms do + @gateway.credit(@amount, @credit_card) + end.respond_with(failed_credit_response) + + assert_failure response + assert_equal 'Invalid card number', response.message + assert_equal Gateway::STANDARD_ERROR_CODE[:invalid_number], response.error_code + assert response.test? + end + + def test_successful_verify + response = stub_comms do + @gateway.verify(@credit_card) + end.respond_with(successful_authorize_response, failed_void_response) + assert_success response + assert_equal 'Succeeded', response.message + end + + def test_failed_verify + response = stub_comms do + @gateway.verify(@credit_card) + end.respond_with(failed_authorize_response, successful_void_response) + assert_failure response + assert_equal 'Decline transaction', response.message + assert_equal Gateway::STANDARD_ERROR_CODE[:card_declined], response.error_code + end + + def test_empty_response_fails + response = stub_comms do + @gateway.purchase(@amount, @credit_card) + end.respond_with(empty_purchase_response) + + assert_failure response + assert_equal 'Unable to read error message', response.message + end + + def test_transcript_scrubbing + assert_equal scrubbed_transcript, @gateway.scrub(transcript) + end + + private + + def successful_purchase_response + %( + <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"><s:Body><ProcessCreditCardResponse xmlns="http://tempuri.org/"><ProcessCreditCardResult i:type="a:ProcessRecurringSaleResponse" xmlns:a="http://schemas.datacontract.org/2004/07/Acriter.ABI.CenPOS.EPayment.VirtualTerminal.v6.Common" xmlns:i="http://www.w3.org/2001/XMLSchema-instance"><Message xmlns="http://schemas.datacontract.org/2004/07/Acriter.ABI.CenPOS.EPayment.VirtualTerminal.Common">Approved</Message><Result xmlns="http://schemas.datacontract.org/2004/07/Acriter.ABI.CenPOS.EPayment.VirtualTerminal.Common">0</Result><a:AccountBalanceAmount i:nil="true"/><a:Amount>2.32</a:Amount><a:AutorizationNumber>TAS977</a:AutorizationNumber><a:CardType>VISA</a:CardType><a:Discount>0</a:Discount><a:DiscountAmount>0</a:DiscountAmount><a:EmvData i:nil="true"/><a:OriginalAmount>2.32</a:OriginalAmount><a:ParameterValidationResultList><a:ParameterValidationResult><a:Name>CVV</a:Name><a:Result> Present;Match (M)</a:Result></a:ParameterValidationResult><a:ParameterValidationResult><a:Name>Billing Address</a:Name><a:Result> Present;Match (N)</a:Result></a:ParameterValidationResult><a:ParameterValidationResult><a:Name>Zip Code</a:Name><a:Result> Present;Match (N)</a:Result></a:ParameterValidationResult></a:ParameterValidationResultList><a:PartiallyAuthorizedAmount i:nil="true"/><a:ReferenceNumber>1609995363</a:ReferenceNumber><a:Surcharge>0</a:Surcharge><a:SurchargeAmount>0</a:SurchargeAmount><a:TraceNumber>513519510699</a:TraceNumber><a:ProtectedCardNumber i:nil="true"/><a:RecurringSaleTokenId i:nil="true"/></ProcessCreditCardResult></ProcessCreditCardResponse></s:Body></s:Envelope> + ) + end + + def failed_purchase_response + %( + <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"><s:Body><ProcessCreditCardResponse xmlns="http://tempuri.org/"><ProcessCreditCardResult i:type="a:ProcessRecurringSaleResponse" xmlns:a="http://schemas.datacontract.org/2004/07/Acriter.ABI.CenPOS.EPayment.VirtualTerminal.v6.Common" xmlns:i="http://www.w3.org/2001/XMLSchema-instance"><Message xmlns="http://schemas.datacontract.org/2004/07/Acriter.ABI.CenPOS.EPayment.VirtualTerminal.Common">Decline transaction</Message><Result xmlns="http://schemas.datacontract.org/2004/07/Acriter.ABI.CenPOS.EPayment.VirtualTerminal.Common">1</Result><a:AccountBalanceAmount i:nil="true"/><a:Amount>30.85</a:Amount><a:AutorizationNumber>N7</a:AutorizationNumber><a:CardType>VISA</a:CardType><a:Discount>0</a:Discount><a:DiscountAmount>0</a:DiscountAmount><a:EmvData i:nil="true"/><a:OriginalAmount>30.85</a:OriginalAmount><a:ParameterValidationResultList/><a:PartiallyAuthorizedAmount i:nil="true"/><a:ReferenceNumber>1609995417</a:ReferenceNumber><a:Surcharge>0</a:Surcharge><a:SurchargeAmount>0</a:SurchargeAmount><a:TraceNumber>513519510836</a:TraceNumber><a:ProtectedCardNumber i:nil="true"/><a:RecurringSaleTokenId i:nil="true"/></ProcessCreditCardResult></ProcessCreditCardResponse></s:Body></s:Envelope> + ) + end + + def successful_authorize_response + %( + <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"><s:Body><ProcessCreditCardResponse xmlns="http://tempuri.org/"><ProcessCreditCardResult i:type="a:ProcessRecurringSaleResponse" xmlns:a="http://schemas.datacontract.org/2004/07/Acriter.ABI.CenPOS.EPayment.VirtualTerminal.v6.Common" xmlns:i="http://www.w3.org/2001/XMLSchema-instance"><Message xmlns="http://schemas.datacontract.org/2004/07/Acriter.ABI.CenPOS.EPayment.VirtualTerminal.Common">Approved</Message><Result xmlns="http://schemas.datacontract.org/2004/07/Acriter.ABI.CenPOS.EPayment.VirtualTerminal.Common">0</Result><a:AccountBalanceAmount i:nil="true"/><a:Amount>16.56</a:Amount><a:AutorizationNumber>TAS788</a:AutorizationNumber><a:CardType>VISA</a:CardType><a:Discount>0</a:Discount><a:DiscountAmount>0</a:DiscountAmount><a:EmvData i:nil="true"/><a:OriginalAmount>16.56</a:OriginalAmount><a:ParameterValidationResultList><a:ParameterValidationResult><a:Name>CVV</a:Name><a:Result> Present;Match (M)</a:Result></a:ParameterValidationResult><a:ParameterValidationResult><a:Name>Billing Address</a:Name><a:Result> Present;Match (N)</a:Result></a:ParameterValidationResult><a:ParameterValidationResult><a:Name>Zip Code</a:Name><a:Result> Present;Match (N)</a:Result></a:ParameterValidationResult></a:ParameterValidationResultList><a:PartiallyAuthorizedAmount i:nil="true"/><a:ReferenceNumber>1760035844</a:ReferenceNumber><a:Surcharge>0</a:Surcharge><a:SurchargeAmount>0</a:SurchargeAmount><a:TraceNumber>513520500594</a:TraceNumber><a:ProtectedCardNumber i:nil="true"/><a:RecurringSaleTokenId i:nil="true"/></ProcessCreditCardResult></ProcessCreditCardResponse></s:Body></s:Envelope> + ) + end + + def failed_authorize_response + %( + <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"><s:Body><ProcessCreditCardResponse xmlns="http://tempuri.org/"><ProcessCreditCardResult i:type="a:ProcessRecurringSaleResponse" xmlns:a="http://schemas.datacontract.org/2004/07/Acriter.ABI.CenPOS.EPayment.VirtualTerminal.v6.Common" xmlns:i="http://www.w3.org/2001/XMLSchema-instance"><Message xmlns="http://schemas.datacontract.org/2004/07/Acriter.ABI.CenPOS.EPayment.VirtualTerminal.Common">Decline transaction</Message><Result xmlns="http://schemas.datacontract.org/2004/07/Acriter.ABI.CenPOS.EPayment.VirtualTerminal.Common">1</Result><a:AccountBalanceAmount i:nil="true"/><a:Amount>73.51</a:Amount><a:AutorizationNumber>N7</a:AutorizationNumber><a:CardType>VISA</a:CardType><a:Discount>0</a:Discount><a:DiscountAmount>0</a:DiscountAmount><a:EmvData i:nil="true"/><a:OriginalAmount>73.51</a:OriginalAmount><a:ParameterValidationResultList/><a:PartiallyAuthorizedAmount i:nil="true"/><a:ReferenceNumber>1609995953</a:ReferenceNumber><a:Surcharge>0</a:Surcharge><a:SurchargeAmount>0</a:SurchargeAmount><a:TraceNumber>513520500786</a:TraceNumber><a:ProtectedCardNumber i:nil="true"/><a:RecurringSaleTokenId i:nil="true"/></ProcessCreditCardResult></ProcessCreditCardResponse></s:Body></s:Envelope> + ) + end + + def successful_capture_response + %( + <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"><s:Body><ProcessCreditCardResponse xmlns="http://tempuri.org/"><ProcessCreditCardResult i:type="a:ProcessRecurringSaleResponse" xmlns:a="http://schemas.datacontract.org/2004/07/Acriter.ABI.CenPOS.EPayment.VirtualTerminal.v6.Common" xmlns:i="http://www.w3.org/2001/XMLSchema-instance"><Message xmlns="http://schemas.datacontract.org/2004/07/Acriter.ABI.CenPOS.EPayment.VirtualTerminal.Common">Approved</Message><Result xmlns="http://schemas.datacontract.org/2004/07/Acriter.ABI.CenPOS.EPayment.VirtualTerminal.Common">0</Result><a:AccountBalanceAmount i:nil="true"/><a:Amount>81.47</a:Amount><a:AutorizationNumber>TAS821</a:AutorizationNumber><a:CardType>VISA</a:CardType><a:Discount>0</a:Discount><a:DiscountAmount>0</a:DiscountAmount><a:EmvData i:nil="true"/><a:OriginalAmount>81.47</a:OriginalAmount><a:ParameterValidationResultList><a:ParameterValidationResult><a:Name>Billing Address</a:Name><a:Result> Present;Match (N)</a:Result></a:ParameterValidationResult><a:ParameterValidationResult><a:Name>Zip Code</a:Name><a:Result> Present;Match (N)</a:Result></a:ParameterValidationResult><a:ParameterValidationResult><a:Name>CVV</a:Name><a:Result> Present;Match (M)</a:Result></a:ParameterValidationResult></a:ParameterValidationResultList><a:PartiallyAuthorizedAmount i:nil="true"/><a:ReferenceNumber>1609995899</a:ReferenceNumber><a:Surcharge>0</a:Surcharge><a:SurchargeAmount>0</a:SurchargeAmount><a:TraceNumber i:nil="true"/><a:ProtectedCardNumber i:nil="true"/><a:RecurringSaleTokenId i:nil="true"/></ProcessCreditCardResult></ProcessCreditCardResponse></s:Body></s:Envelope> + ) + end + + def failed_capture_response + %( + <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"><s:Body><ProcessCreditCardResponse xmlns="http://tempuri.org/"><ProcessCreditCardResult i:type="a:ProcessRecurringSaleResponse" xmlns:a="http://schemas.datacontract.org/2004/07/Acriter.ABI.CenPOS.EPayment.VirtualTerminal.v6.Common" xmlns:i="http://www.w3.org/2001/XMLSchema-instance"><Message xmlns="http://schemas.datacontract.org/2004/07/Acriter.ABI.CenPOS.EPayment.VirtualTerminal.Common">Duplicated transaction</Message><Result xmlns="http://schemas.datacontract.org/2004/07/Acriter.ABI.CenPOS.EPayment.VirtualTerminal.Common">2</Result><a:AccountBalanceAmount i:nil="true"/><a:Amount>65.51</a:Amount><a:AutorizationNumber i:nil="true"/><a:CardType>VISA</a:CardType><a:Discount>0</a:Discount><a:DiscountAmount>0</a:DiscountAmount><a:EmvData i:nil="true"/><a:OriginalAmount>65.51</a:OriginalAmount><a:ParameterValidationResultList/><a:PartiallyAuthorizedAmount i:nil="true"/><a:ReferenceNumber>1609996127</a:ReferenceNumber><a:Surcharge>0</a:Surcharge><a:SurchargeAmount>0</a:SurchargeAmount><a:TraceNumber i:nil="true"/><a:ProtectedCardNumber i:nil="true"/><a:RecurringSaleTokenId i:nil="true"/></ProcessCreditCardResult></ProcessCreditCardResponse></s:Body></s:Envelope> + ) + end + + def successful_void_response + %( + <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"><s:Body><ProcessCreditCardResponse xmlns="http://tempuri.org/"><ProcessCreditCardResult i:type="a:ProcessRecurringSaleResponse" xmlns:a="http://schemas.datacontract.org/2004/07/Acriter.ABI.CenPOS.EPayment.VirtualTerminal.v6.Common" xmlns:i="http://www.w3.org/2001/XMLSchema-instance"><Message xmlns="http://schemas.datacontract.org/2004/07/Acriter.ABI.CenPOS.EPayment.VirtualTerminal.Common">Approved</Message><Result xmlns="http://schemas.datacontract.org/2004/07/Acriter.ABI.CenPOS.EPayment.VirtualTerminal.Common">0</Result><a:AccountBalanceAmount i:nil="true"/><a:Amount>3.98</a:Amount><a:AutorizationNumber>TAS048</a:AutorizationNumber><a:CardType>VISA</a:CardType><a:Discount>0</a:Discount><a:DiscountAmount>0</a:DiscountAmount><a:EmvData i:nil="true"/><a:OriginalAmount>3.98</a:OriginalAmount><a:ParameterValidationResultList/><a:PartiallyAuthorizedAmount i:nil="true"/><a:ReferenceNumber>1760036131</a:ReferenceNumber><a:Surcharge>0</a:Surcharge><a:SurchargeAmount>0</a:SurchargeAmount><a:TraceNumber>513520512917</a:TraceNumber><a:ProtectedCardNumber i:nil="true"/><a:RecurringSaleTokenId i:nil="true"/></ProcessCreditCardResult></ProcessCreditCardResponse></s:Body></s:Envelope> + ) + end + + def failed_void_response + %( + <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"><s:Body><ProcessCreditCardResponse xmlns="http://tempuri.org/"><ProcessCreditCardResult i:type="a:ProcessRecurringSaleResponse" xmlns:a="http://schemas.datacontract.org/2004/07/Acriter.ABI.CenPOS.EPayment.VirtualTerminal.v6.Common" xmlns:i="http://www.w3.org/2001/XMLSchema-instance"><Message xmlns="http://schemas.datacontract.org/2004/07/Acriter.ABI.CenPOS.EPayment.VirtualTerminal.Common">Original Transaction not found</Message><Result xmlns="http://schemas.datacontract.org/2004/07/Acriter.ABI.CenPOS.EPayment.VirtualTerminal.Common">7</Result><a:AccountBalanceAmount i:nil="true"/><a:Amount>88.01</a:Amount><a:AutorizationNumber i:nil="true"/><a:CardType>Credit</a:CardType><a:Discount>0</a:Discount><a:DiscountAmount>0</a:DiscountAmount><a:EmvData i:nil="true"/><a:OriginalAmount>88.01</a:OriginalAmount><a:ParameterValidationResultList/><a:PartiallyAuthorizedAmount i:nil="true"/><a:ReferenceNumber>1760036213</a:ReferenceNumber><a:Surcharge>0</a:Surcharge><a:SurchargeAmount>0</a:SurchargeAmount><a:TraceNumber i:nil="true"/><a:ProtectedCardNumber i:nil="true"/><a:RecurringSaleTokenId i:nil="true"/></ProcessCreditCardResult></ProcessCreditCardResponse></s:Body></s:Envelope> + ) + end + + def successful_refund_response + %( + <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"><s:Body><ProcessCreditCardResponse xmlns="http://tempuri.org/"><ProcessCreditCardResult i:type="a:ProcessRecurringSaleResponse" xmlns:a="http://schemas.datacontract.org/2004/07/Acriter.ABI.CenPOS.EPayment.VirtualTerminal.v6.Common" xmlns:i="http://www.w3.org/2001/XMLSchema-instance"><Message xmlns="http://schemas.datacontract.org/2004/07/Acriter.ABI.CenPOS.EPayment.VirtualTerminal.Common">Approved</Message><Result xmlns="http://schemas.datacontract.org/2004/07/Acriter.ABI.CenPOS.EPayment.VirtualTerminal.Common">0</Result><a:AccountBalanceAmount i:nil="true"/><a:Amount>95.10</a:Amount><a:AutorizationNumber i:nil="true"/><a:CardType>VISA</a:CardType><a:Discount>0</a:Discount><a:DiscountAmount>0</a:DiscountAmount><a:EmvData i:nil="true"/><a:OriginalAmount>95.10</a:OriginalAmount><a:ParameterValidationResultList/><a:PartiallyAuthorizedAmount i:nil="true"/><a:ReferenceNumber>1760036098</a:ReferenceNumber><a:Surcharge>0</a:Surcharge><a:SurchargeAmount>0</a:SurchargeAmount><a:TraceNumber i:nil="true"/><a:ProtectedCardNumber i:nil="true"/><a:RecurringSaleTokenId i:nil="true"/></ProcessCreditCardResult></ProcessCreditCardResponse></s:Body></s:Envelope> + ) + end + + def failed_refund_response + %( + <s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\"><s:Body><s:Fault><faultcode xmlns:a=\"http://schemas.microsoft.com/net/2005/12/windowscommunicationfoundation/dispatcher\">a:DeserializationFailed</faultcode><faultstring xml:lang=\"en-US\">The formatter threw an exception while trying to deserialize the message: There was an error while trying to deserialize parameter http://tempuri.org/:request. The InnerException message was 'There was an error deserializing the object of type Acriter.ABI.CenPOS.Client.VirtualTerminal.v6.Common.Requests.ProcessCardRequest. The value '' cannot be parsed as the type 'decimal'.'. Please see InnerException for more details.</faultstring><detail><ExceptionDetail xmlns=\"http://schemas.datacontract.org/2004/07/System.ServiceModel\" xmlns:i=\"http://www.w3.org/2001/XMLSchema-instance\"><HelpLink i:nil=\"true\"/><InnerException><HelpLink i:nil=\"true\"/><InnerException><HelpLink i:nil=\"true\"/><InnerException><HelpLink i:nil=\"true\"/><InnerException i:nil=\"true\"/><Message>Input string was not in a correct format.</Message><StackTrace> at System.Number.StringToNumber(String str, NumberStyles options, NumberBuffer&amp; number, NumberFormatInfo info, Boolean parseDecimal)&#xD;\n at System.Number.ParseDecimal(String value, NumberStyles options, NumberFormatInfo numfmt)&#xD;\n at System.Xml.XmlConvert.ToDecimal(String s)&#xD;\n at System.Xml.XmlConverter.ToDecimal(String value)</StackTrace><Type>System.FormatException</Type></InnerException><Message>The value '' cannot be parsed as the type 'decimal'.</Message><StackTrace> at System.Xml.XmlConverter.ToDecimal(String value)&#xD;\n at System.Xml.XmlDictionaryReader.ReadElementContentAsDecimal()&#xD;\n at System.Runtime.Serialization.XmlReaderDelegator.ReadElementContentAsDecimal()&#xD;\n at ReadProcessCardRequestFromXml(XmlReaderDelegator , XmlObjectSerializerReadContext , XmlDictionaryString[] , XmlDictionaryString[] )&#xD;\n at System.Runtime.Serialization.ClassDataContract.ReadXmlValue(XmlReaderDelegator xmlReader, XmlObjectSerializerReadContext context)&#xD; + ) + end + + def successful_credit_response + %( + <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"><s:Body><ProcessCreditCardResponse xmlns="http://tempuri.org/"><ProcessCreditCardResult i:type="a:ProcessRecurringSaleResponse" xmlns:a="http://schemas.datacontract.org/2004/07/Acriter.ABI.CenPOS.EPayment.VirtualTerminal.v6.Common" xmlns:i="http://www.w3.org/2001/XMLSchema-instance"><Message xmlns="http://schemas.datacontract.org/2004/07/Acriter.ABI.CenPOS.EPayment.VirtualTerminal.Common">Approved</Message><Result xmlns="http://schemas.datacontract.org/2004/07/Acriter.ABI.CenPOS.EPayment.VirtualTerminal.Common">0</Result><a:AccountBalanceAmount i:nil="true"/><a:Amount>91.13</a:Amount><a:AutorizationNumber i:nil="true"/><a:CardType>VISA</a:CardType><a:Discount>0</a:Discount><a:DiscountAmount>0</a:DiscountAmount><a:EmvData i:nil="true"/><a:OriginalAmount>91.13</a:OriginalAmount><a:ParameterValidationResultList/><a:PartiallyAuthorizedAmount i:nil="true"/><a:ReferenceNumber>1609996211</a:ReferenceNumber><a:Surcharge>0</a:Surcharge><a:SurchargeAmount>0</a:SurchargeAmount><a:TraceNumber i:nil="true"/><a:ProtectedCardNumber i:nil="true"/><a:RecurringSaleTokenId i:nil="true"/></ProcessCreditCardResult></ProcessCreditCardResponse></s:Body></s:Envelope> + ) + end + + def failed_credit_response + %( + <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"><s:Body><ProcessCreditCardResponse xmlns="http://tempuri.org/"><ProcessCreditCardResult i:type="a:ProcessRecurringSaleResponse" xmlns:a="http://schemas.datacontract.org/2004/07/Acriter.ABI.CenPOS.EPayment.VirtualTerminal.v6.Common" xmlns:i="http://www.w3.org/2001/XMLSchema-instance"><Message xmlns="http://schemas.datacontract.org/2004/07/Acriter.ABI.CenPOS.EPayment.VirtualTerminal.Common">Invalid card number</Message><Result xmlns="http://schemas.datacontract.org/2004/07/Acriter.ABI.CenPOS.EPayment.VirtualTerminal.Common">211</Result><a:AccountBalanceAmount i:nil="true"/><a:Amount>44.65</a:Amount><a:AutorizationNumber i:nil="true"/><a:CardType>MASTERCARD</a:CardType><a:Discount>0</a:Discount><a:DiscountAmount>0</a:DiscountAmount><a:EmvData i:nil="true"/><a:OriginalAmount>44.65</a:OriginalAmount><a:ParameterValidationResultList/><a:PartiallyAuthorizedAmount i:nil="true"/><a:ReferenceNumber>1760036040</a:ReferenceNumber><a:Surcharge>0</a:Surcharge><a:SurchargeAmount>0</a:SurchargeAmount><a:TraceNumber i:nil="true"/><a:ProtectedCardNumber i:nil="true"/><a:RecurringSaleTokenId i:nil="true"/></ProcessCreditCardResult></ProcessCreditCardResponse></s:Body></s:Envelope> + ) + end + + def cvv_no_match_response + %( + <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"> <s:Body> <ProcessCreditCardResponse xmlns="http://tempuri.org/"> <ProcessCreditCardResult xmlns:a="http://schemas.datacontract.org/2004/07/Acriter.ABI.CenPOS.EPayment.VirtualTerminal.v6.Common" xmlns:i="http://www.w3.org/2001/XMLSchema-instance" i:type="a:ProcessRecurringSaleResponse"> <Message xmlns="http://schemas.datacontract.org/2004/07/Acriter.ABI.CenPOS.EPayment.VirtualTerminal.Common">Approved</Message> <Result xmlns="http://schemas.datacontract.org/2004/07/Acriter.ABI.CenPOS.EPayment.VirtualTerminal.Common">0</Result> <a:AccountBalanceAmount i:nil="true" /> <a:Amount>96.19</a:Amount> <a:AutorizationNumber>TAS922</a:AutorizationNumber> <a:CardType>VISA</a:CardType> <a:Discount>0</a:Discount> <a:DiscountAmount>0</a:DiscountAmount> <a:EmvData i:nil="true" /> <a:OriginalAmount>96.19</a:OriginalAmount> <a:ParameterValidationResultList> <a:ParameterValidationResult> <a:Name>CVV</a:Name> <a:Result>Not Present;No Match (M)</a:Result> </a:ParameterValidationResult> <a:ParameterValidationResult> <a:Name>Billing Address</a:Name> <a:Result>Present;Match (N)</a:Result> </a:ParameterValidationResult> <a:ParameterValidationResult> <a:Name>Zip Code</a:Name> <a:Result>Present;Match (N)</a:Result> </a:ParameterValidationResult> </a:ParameterValidationResultList> <a:PartiallyAuthorizedAmount i:nil="true" /> <a:ReferenceNumber>1761450083</a:ReferenceNumber> <a:Surcharge>0</a:Surcharge> <a:SurchargeAmount>0</a:SurchargeAmount> <a:TraceNumber>520417500008</a:TraceNumber> <a:ProtectedCardNumber i:nil="true" /> <a:RecurringSaleTokenId i:nil="true" /> </ProcessCreditCardResult> </ProcessCreditCardResponse> </s:Body> </s:Envelope> + ) + end + + def avs_billing_no_match_response + %( + <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"> <s:Body> <ProcessCreditCardResponse xmlns="http://tempuri.org/"> <ProcessCreditCardResult xmlns:a="http://schemas.datacontract.org/2004/07/Acriter.ABI.CenPOS.EPayment.VirtualTerminal.v6.Common" xmlns:i="http://www.w3.org/2001/XMLSchema-instance" i:type="a:ProcessRecurringSaleResponse"> <Message xmlns="http://schemas.datacontract.org/2004/07/Acriter.ABI.CenPOS.EPayment.VirtualTerminal.Common">Approved</Message> <Result xmlns="http://schemas.datacontract.org/2004/07/Acriter.ABI.CenPOS.EPayment.VirtualTerminal.Common">0</Result> <a:AccountBalanceAmount i:nil="true" /> <a:Amount>96.19</a:Amount> <a:AutorizationNumber>TAS922</a:AutorizationNumber> <a:CardType>VISA</a:CardType> <a:Discount>0</a:Discount> <a:DiscountAmount>0</a:DiscountAmount> <a:EmvData i:nil="true" /> <a:OriginalAmount>96.19</a:OriginalAmount> <a:ParameterValidationResultList> <a:ParameterValidationResult> <a:Name>CVV</a:Name> <a:Result>Present;Match (M)</a:Result> </a:ParameterValidationResult> <a:ParameterValidationResult> <a:Name>Billing Address</a:Name> <a:Result>Not Present;No Match (N)</a:Result> </a:ParameterValidationResult> <a:ParameterValidationResult> <a:Name>Zip Code</a:Name> <a:Result>Present;Match (N)</a:Result> </a:ParameterValidationResult> </a:ParameterValidationResultList> <a:PartiallyAuthorizedAmount i:nil="true" /> <a:ReferenceNumber>1761450083</a:ReferenceNumber> <a:Surcharge>0</a:Surcharge> <a:SurchargeAmount>0</a:SurchargeAmount> <a:TraceNumber>520417500008</a:TraceNumber> <a:ProtectedCardNumber i:nil="true" /> <a:RecurringSaleTokenId i:nil="true" /> </ProcessCreditCardResult> </ProcessCreditCardResponse> </s:Body> </s:Envelope> + ) + end + + def avs_zip_no_match_response + %( + <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"> <s:Body> <ProcessCreditCardResponse xmlns="http://tempuri.org/"> <ProcessCreditCardResult xmlns:a="http://schemas.datacontract.org/2004/07/Acriter.ABI.CenPOS.EPayment.VirtualTerminal.v6.Common" xmlns:i="http://www.w3.org/2001/XMLSchema-instance" i:type="a:ProcessRecurringSaleResponse"> <Message xmlns="http://schemas.datacontract.org/2004/07/Acriter.ABI.CenPOS.EPayment.VirtualTerminal.Common">Approved</Message> <Result xmlns="http://schemas.datacontract.org/2004/07/Acriter.ABI.CenPOS.EPayment.VirtualTerminal.Common">0</Result> <a:AccountBalanceAmount i:nil="true" /> <a:Amount>96.19</a:Amount> <a:AutorizationNumber>TAS922</a:AutorizationNumber> <a:CardType>VISA</a:CardType> <a:Discount>0</a:Discount> <a:DiscountAmount>0</a:DiscountAmount> <a:EmvData i:nil="true" /> <a:OriginalAmount>96.19</a:OriginalAmount> <a:ParameterValidationResultList> <a:ParameterValidationResult> <a:Name>CVV</a:Name> <a:Result>Present;Match (M)</a:Result> </a:ParameterValidationResult> <a:ParameterValidationResult> <a:Name>Billing Address</a:Name> <a:Result>Present;Match (N)</a:Result> </a:ParameterValidationResult> <a:ParameterValidationResult> <a:Name>Zip Code</a:Name> <a:Result>Not Present;No Match (N)</a:Result> </a:ParameterValidationResult> </a:ParameterValidationResultList> <a:PartiallyAuthorizedAmount i:nil="true" /> <a:ReferenceNumber>1761450083</a:ReferenceNumber> <a:Surcharge>0</a:Surcharge> <a:SurchargeAmount>0</a:SurchargeAmount> <a:TraceNumber>520417500008</a:TraceNumber> <a:ProtectedCardNumber i:nil="true" /> <a:RecurringSaleTokenId i:nil="true" /> </ProcessCreditCardResult> </ProcessCreditCardResponse> </s:Body> </s:Envelope> + ) + end + + def avs_billing_and_zip_no_match_response + %( + <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"> <s:Body> <ProcessCreditCardResponse xmlns="http://tempuri.org/"> <ProcessCreditCardResult xmlns:a="http://schemas.datacontract.org/2004/07/Acriter.ABI.CenPOS.EPayment.VirtualTerminal.v6.Common" xmlns:i="http://www.w3.org/2001/XMLSchema-instance" i:type="a:ProcessRecurringSaleResponse"> <Message xmlns="http://schemas.datacontract.org/2004/07/Acriter.ABI.CenPOS.EPayment.VirtualTerminal.Common">Approved</Message> <Result xmlns="http://schemas.datacontract.org/2004/07/Acriter.ABI.CenPOS.EPayment.VirtualTerminal.Common">0</Result> <a:AccountBalanceAmount i:nil="true" /> <a:Amount>96.19</a:Amount> <a:AutorizationNumber>TAS922</a:AutorizationNumber> <a:CardType>VISA</a:CardType> <a:Discount>0</a:Discount> <a:DiscountAmount>0</a:DiscountAmount> <a:EmvData i:nil="true" /> <a:OriginalAmount>96.19</a:OriginalAmount> <a:ParameterValidationResultList> <a:ParameterValidationResult> <a:Name>CVV</a:Name> <a:Result>Present;Match (M)</a:Result> </a:ParameterValidationResult> <a:ParameterValidationResult> <a:Name>Billing Address</a:Name> <a:Result>Not Present;No Match (N)</a:Result> </a:ParameterValidationResult> <a:ParameterValidationResult> <a:Name>Zip Code</a:Name> <a:Result>Not Present;No Match (N)</a:Result> </a:ParameterValidationResult> </a:ParameterValidationResultList> <a:PartiallyAuthorizedAmount i:nil="true" /> <a:ReferenceNumber>1761450083</a:ReferenceNumber> <a:Surcharge>0</a:Surcharge> <a:SurchargeAmount>0</a:SurchargeAmount> <a:TraceNumber>520417500008</a:TraceNumber> <a:ProtectedCardNumber i:nil="true" /> <a:RecurringSaleTokenId i:nil="true" /> </ProcessCreditCardResult> </ProcessCreditCardResponse> </s:Body> </s:Envelope> + ) + end + + def empty_purchase_response + %( + ) + end + + def transcript + %( + <- "POST /6/transact.asmx HTTP/1.1\r\nContent-Type: text/xml;charset=UTF-8\r\nAccept-Encoding: gzip,deflate\r\nSoapaction: http://tempuri.org/Transactional/ProcessCard\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: ww3.cenpos.net\r\nContent-Length: 1272\r\n\r\n" + <- "<soapenv:Envelope xmlns:soapenv=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:tem=\"http://tempuri.org/\" xmlns:acr=\"http://schemas.datacontract.org/2004/07/Acriter.ABI.CenPOS.EPayment.VirtualTerminal.Common\" xmlns:acr1=\"http://schemas.datacontract.org/2004/07/Acriter.ABI.CenPOS.EPayment.VirtualTerminal.v6.Common\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">\n<soapenv:Header/>\n <soapenv:Body>\n <tem:ProcessCard>\n <tem:request>\n <acr:MerchantId>12722385</acr:MerchantId>\n<acr:Password>101010101010</acr:Password>\n<acr:UserId>Webpay</acr:UserId>\n<acr1:Amount>25</acr1:Amount>\n<acr1:CardExpirationDate>0218</acr1:CardExpirationDate>\n<acr1:CardLastFourDigits>1111</acr1:CardLastFourDigits>\n<acr1:CardNumber>4111111111111111</acr1:CardNumber>\n<acr1:CardVerificationNumber>999</acr1:CardVerificationNumber>\n<acr1:CustomerBillingAddress>1234 My Street</acr1:CustomerBillingAddress>\n<acr1:CustomerCode>1231</acr1:CustomerCode>\n<acr1:CustomerEmailAddress/>\n<acr1:CustomerZipCode>K1C2N6</acr1:CustomerZipCode>\n<acr1:InvoiceNumber>612944</acr1:InvoiceNumber>\n<acr1:NameOnCard>Longbob Longsen</acr1:NameOnCard>\n<acr1:TransactionType>Sale</acr1:TransactionType>\n\n </tem:request>\n </tem:ProcessCard>\n </soapenv:Body>\n</soapenv:Envelope>\n" + -> "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\"><s:Body><ProcessCardResponse xmlns=\"http://tempuri.org/\"><ProcessCardResult xmlns:a=\"http://schemas.datacontract.org/2004/07/Acriter.ABI.CenPOS.EPayment.VirtualTerminal.v6.Common\" xmlns:i=\"http://www.w3.org/2001/XMLSchema-instance\"><Message xmlns=\"http://schemas.datacontract.org/2004/07/Acriter.ABI.CenPOS.EPayment.VirtualTerminal.Common\">Duplicated transaction</Message><Result xmlns=\"http://schemas.datacontract.org/2004/07/Acriter.ABI.CenPOS.EPayment.VirtualTerminal.Common\">2</Result><a:AccountBalanceAmount i:nil=\"true\"/><a:Amount>25</a:Amount><a:AutorizationNumber i:nil=\"true\"/><a:CardType>VISA</a:CardType><a:Discount>0</a:Discount><a:DiscountAmount>0</a:DiscountAmount><a:EmvData i:nil=\"true\"/><a:OriginalAmount>25</a:OriginalAmount><a:ParameterValidationResultList/><a:PartiallyAuthorizedAmount i:nil=\"true\"/><a:ReferenceNumber>1608482770</a:ReferenceNumber><a:Surcharge>0</a:Surcharge><a:SurchargeAmount>0</a:SurchargeAmount><a:TraceNumber i:nil=\"true\"/></ProcessCardResult></ProcessCardResponse></s:Body></s:Envelope>" + ) + end + + def scrubbed_transcript + %( + <- "POST /6/transact.asmx HTTP/1.1\r\nContent-Type: text/xml;charset=UTF-8\r\nAccept-Encoding: gzip,deflate\r\nSoapaction: http://tempuri.org/Transactional/ProcessCard\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: ww3.cenpos.net\r\nContent-Length: 1272\r\n\r\n" + <- "<soapenv:Envelope xmlns:soapenv=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:tem=\"http://tempuri.org/\" xmlns:acr=\"http://schemas.datacontract.org/2004/07/Acriter.ABI.CenPOS.EPayment.VirtualTerminal.Common\" xmlns:acr1=\"http://schemas.datacontract.org/2004/07/Acriter.ABI.CenPOS.EPayment.VirtualTerminal.v6.Common\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">\n<soapenv:Header/>\n <soapenv:Body>\n <tem:ProcessCard>\n <tem:request>\n <acr:MerchantId>12722385</acr:MerchantId>\n<acr:Password>[FILTERED]</acr:Password>\n<acr:UserId>Webpay</acr:UserId>\n<acr1:Amount>25</acr1:Amount>\n<acr1:CardExpirationDate>0218</acr1:CardExpirationDate>\n<acr1:CardLastFourDigits>1111</acr1:CardLastFourDigits>\n<acr1:CardNumber>[FILTERED]</acr1:CardNumber>\n<acr1:CardVerificationNumber>[FILTERED]</acr1:CardVerificationNumber>\n<acr1:CustomerBillingAddress>1234 My Street</acr1:CustomerBillingAddress>\n<acr1:CustomerCode>1231</acr1:CustomerCode>\n<acr1:CustomerEmailAddress/>\n<acr1:CustomerZipCode>K1C2N6</acr1:CustomerZipCode>\n<acr1:InvoiceNumber>612944</acr1:InvoiceNumber>\n<acr1:NameOnCard>Longbob Longsen</acr1:NameOnCard>\n<acr1:TransactionType>Sale</acr1:TransactionType>\n\n </tem:request>\n </tem:ProcessCard>\n </soapenv:Body>\n</soapenv:Envelope>\n" + -> "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\"><s:Body><ProcessCardResponse xmlns=\"http://tempuri.org/\"><ProcessCardResult xmlns:a=\"http://schemas.datacontract.org/2004/07/Acriter.ABI.CenPOS.EPayment.VirtualTerminal.v6.Common\" xmlns:i=\"http://www.w3.org/2001/XMLSchema-instance\"><Message xmlns=\"http://schemas.datacontract.org/2004/07/Acriter.ABI.CenPOS.EPayment.VirtualTerminal.Common\">Duplicated transaction</Message><Result xmlns=\"http://schemas.datacontract.org/2004/07/Acriter.ABI.CenPOS.EPayment.VirtualTerminal.Common\">2</Result><a:AccountBalanceAmount i:nil=\"true\"/><a:Amount>25</a:Amount><a:AutorizationNumber i:nil=\"true\"/><a:CardType>VISA</a:CardType><a:Discount>0</a:Discount><a:DiscountAmount>0</a:DiscountAmount><a:EmvData i:nil=\"true\"/><a:OriginalAmount>25</a:OriginalAmount><a:ParameterValidationResultList/><a:PartiallyAuthorizedAmount i:nil=\"true\"/><a:ReferenceNumber>1608482770</a:ReferenceNumber><a:Surcharge>0</a:Surcharge><a:SurchargeAmount>0</a:SurchargeAmount><a:TraceNumber i:nil=\"true\"/></ProcessCardResult></ProcessCardResponse></s:Body></s:Envelope>" + ) + end +end diff --git a/test/unit/gateways/certo_direct_test.rb b/test/unit/gateways/certo_direct_test.rb deleted file mode 100644 index b1114e33765..00000000000 --- a/test/unit/gateways/certo_direct_test.rb +++ /dev/null @@ -1,198 +0,0 @@ -require 'test_helper' - -class CertoDirectTest < Test::Unit::TestCase - include CommStub - - def setup - @gateway = CertoDirectGateway.new( - :login => 'X', - :password => 'Y' - ) - @amount = 100 - @credit_card = credit_card - @options = { - :billing_address => { - :address1 => 'Infinite Loop 1', - :country => 'US', - :state => 'TX', - :city => 'Gotham', - :zip => '23456', - :phone => '+1-132-12345678', - - }, - :email => 'john.doe@example.com', - :currency => 'USD', - :ip => '127.0.0.1', - :description => 'Test of ActiveMerchant' - } - end - - def test_successful_purchase - @gateway.expects(:ssl_post).returns(successful_purchase_response) - - assert response = @gateway.purchase(@amount, @credit_card, @options) - assert_instance_of Response, response - assert_success response - assert_equal '364926', response.authorization - end - - def test_failed_authorization - http_response = mock() - http_response.stubs(:code).returns('403') - http_response.stubs(:body).returns(failed_authorization_response) - response_error = ::ActiveMerchant::ResponseError.new(http_response) - @gateway.expects(:ssl_post).raises(response_error) - - assert response = @gateway.purchase(@amount, @credit_card, @options) - assert_instance_of Response, response - assert_failure response - assert_equal 'Authentication was failed', response.message - assert_equal nil, response.authorization - end - - def test_communication_error - http_response = mock() - http_response.stubs(:code).returns('408') - response_error = ::ActiveMerchant::ResponseError.new(http_response) - @gateway.expects(:ssl_post).raises(response_error) - - assert_raise(ActiveMerchant::ResponseError) do - @gateway.purchase(@amount, @credit_card, @options) - end - end - - def test_declined_purchase - @gateway.expects(:ssl_post).returns(declined_purchase_response) - - assert response = @gateway.purchase(@amount, @credit_card, @options) - assert_instance_of Response, response - assert_failure response - assert_equal '364971', response.authorization - end - - - private - - def successful_purchase_response - <<-XML -<transaction> - <amount type="decimal">1.0</amount> - <created_at type="datetime">2011-11-01T12:35:45+02:00</created_at> - <id type="integer">367646</id> - <state>completed</state> - <type>Sale</type> - <signature>3d01d5834daeeb360a45a6336326eb2a03c27d7b</signature> - <response> - <message>Transaction was successfully processed</message> - <status>success</status> - <provider> - <code>00</code> - <message>Approved (test mode)</message> - <billing_descriptor>test desc136</billing_descriptor> - <authorization_code>a0080z</authorization_code> - <rrn>130510079645</rrn> - </provider> - </response> - <order> - <authorized_amount type="decimal">0.0</authorized_amount> - <id type="integer">364926</id> - <payment_method_type>CreditCard</payment_method_type> - <settled_amount type="decimal">1.0</settled_amount> - <state>paid</state> - <test type="boolean">true</test> - <payment_method> - <brand>TestCard</brand> - <pan_strip>7000-00XX-XXXX-0005</pan_strip> - </payment_method> - <tracking_params type="array"/> - <billing_address> - <address>Infinite Loop 1</address> - <city>Gotham</city> - <country>US</country> - <first_name>Longbob</first_name> - <last_name>Longsen</last_name> - <phone>+1-132-12345678</phone> - <state>TX</state> - <zip>23456</zip> - </billing_address> - <details> - <amount type="decimal">1.0</amount> - <currency>USD</currency> - <description>CertoConnect order #</description> - <discount type="decimal" nil="true"></discount> - <email>john.doe@example.com</email> - <ip>127.0.0.1</ip> - <items type="array"/> - </details> - <antifraud_responses type="array"/> - </order> -</transaction> -XML - end - - def failed_authorization_response - <<-XML -<response> - <errors type="array"> - <error>Authentication was failed</error> - </errors> -</response> -XML - end - - def declined_purchase_response - <<-XML -<transaction> - <amount type="decimal">1.0</amount> - <created_at type="datetime">2011-11-01T13:52:18+02:00</created_at> - <id type="integer">367691</id> - <state>completed</state> - <type>Sale</type> - <signature>4c62abd58d59856a1ac465b03822e9e3937423ff</signature> - <response> - <message>Transaction was declined</message> - <status>fail</status> - <provider> - <code>D3</code> - <message>Declined (test mode)</message> - <authorization_code>a0458z</authorization_code> - <rrn>130511089540</rrn> - </provider> - </response> - <order> - <authorized_amount type="decimal">0.0</authorized_amount> - <id type="integer">364971</id> - <payment_method_type>CreditCard</payment_method_type> - <settled_amount type="decimal">0.0</settled_amount> - <state>declined</state> - <test type="boolean">true</test> - <payment_method> - <brand>TestCard</brand> - <pan_strip>7000-00XX-XXXX-0005</pan_strip> - </payment_method> - <tracking_params type="array"/> - <billing_address> - <address>Infinite Loop 1</address> - <city>Gotham</city> - <country>US</country> - <first_name>Longbob</first_name> - <last_name>Longsen</last_name> - <phone>+1-132-12345678</phone> - <state>TX</state> - <zip>23456</zip> - </billing_address> - <details> - <amount type="decimal">1.0</amount> - <currency>USD</currency> - <description>CertoConnect order #</description> - <discount type="decimal" nil="true"></discount> - <email>john.doe@example.com</email> - <ip>127.0.0.1</ip> - <items type="array"/> - </details> - <antifraud_responses type="array"/> - </order> -</transaction> -XML - end -end diff --git a/test/unit/gateways/checkout_test.rb b/test/unit/gateways/checkout_test.rb new file mode 100644 index 00000000000..ab70056694e --- /dev/null +++ b/test/unit/gateways/checkout_test.rb @@ -0,0 +1,220 @@ +require 'test_helper' + +class CheckoutTest < Test::Unit::TestCase + include CommStub + + def setup + @gateway = ActiveMerchant::Billing::CheckoutGateway.new( + :merchant_id => 'SBMTEST', # Merchant Code + :password => 'Password1!' # Processing Password + ) + @options = { + order_id: generate_unique_id + } + end + + def test_successful_purchase + @gateway.expects(:ssl_post).returns(successful_purchase_response) + + assert response = @gateway.purchase(100, credit_card, @options) + assert_success response + + assert_equal 'Successful', response.message + assert response.test? + end + + def test_successful_authorize + @gateway.expects(:ssl_post).returns(successful_authorize_response) + + assert response = @gateway.authorize(100, credit_card, @options) + assert_success response + + assert_equal 'Successful', response.message + assert response.test? + end + + def test_successful_capture + @gateway.expects(:ssl_post).returns(successful_capture_response) + + assert capture = @gateway.capture(100, '36919371|9c38d0506da258e216fa072197faaf37|1|CAD|100', @options) + assert_success capture + + assert_equal 'Successful', capture.message + assert capture.test? + end + + def test_unsuccessful_authorize + @gateway.expects(:ssl_post).returns(failed_authorize_response) + + assert response = @gateway.authorize(100, credit_card, @options) + assert_failure response + assert_equal 'Not Successful', response.message + assert response.test? + end + + def test_unsuccessful_capture + @gateway.expects(:ssl_post).returns(failed_capture_response) + + assert response = @gateway.capture(100, '||||', @options) + assert_failure response + assert_equal 'EGP00173', response.params['error_code_tag'] + assert response.test? + end + + def test_unsuccessful_purchase + @gateway.expects(:ssl_post).returns(failed_purchase_response) + + assert response = @gateway.purchase(100, credit_card, @options) + assert_failure response + assert_equal 'Not Successful', response.message + assert response.test? + end + + def test_passes_correct_currency + stub_comms do + @gateway.purchase(100, credit_card, @options.merge( + currency: 'EUR' + )) + end.check_request do |endpoint, data, headers| + assert_match(/<bill_currencycode>EUR<\/bill_currencycode>/, data) + end.respond_with(successful_purchase_response) + end + + def test_passes_descriptors + stub_comms do + @gateway.purchase(100, credit_card, @options.merge( + descriptor_name: 'ZahName', + descriptor_city: 'Oakland' + )) + end.check_request do |endpoint, data, headers| + assert_match(/<descriptor_name>ZahName<\/descriptor_name>/, data) + assert_match(/<descriptor_city>Oakland<\/descriptor_city>/, data) + end.respond_with(successful_purchase_response) + end + + def test_successful_void + @options['orderid'] = '9c38d0506da258e216fa072197faaf37' + void = stub_comms(@gateway, :ssl_request) do + @gateway.void('36919371|9c38d0506da258e216fa072197faaf37|1|CAD|100', @options) + end.check_request do |method, endpoint, data, headers| + # Should only be one pair of track id tags. + assert_equal 2, data.scan(/trackid/).count + end.respond_with(successful_void_response) + + assert void + assert_success void + + assert_equal 'Successful', void.message + assert void.test? + end + + def test_unsuccessful_void + @gateway.expects(:ssl_post).returns(failed_void_response) + + assert void = @gateway.void('36919371|9c38d0506da258e216fa072197faaf37|1|CAD|100', @options) + assert_failure void + end + + def test_successful_refund + @gateway.expects(:ssl_post).returns(successful_refund_response) + + assert refund = @gateway.refund(100, '36919371|9c38d0506da258e216fa072197faaf37|1|CAD|100', @options) + assert_success refund + + assert_equal 'Successful', refund.message + assert refund.test? + end + + def test_unsuccessful_refund + @gateway.expects(:ssl_post).returns(failed_refund_response) + + assert refund = @gateway.refund(100, '36919371|9c38d0506da258e216fa072197faaf37|1|CAD|100', @options) + assert_failure refund + end + + def test_successful_verify + response = stub_comms(@gateway, :ssl_request) do + @gateway.verify(credit_card, @options) + end.respond_with(successful_authorize_response, successful_void_response) + assert_success response + assert_equal '33024417', response.params['tranid'] + end + + def test_successful_verify_with_failed_void + response = stub_comms(@gateway, :ssl_request) do + @gateway.verify(credit_card, @options) + end.respond_with(successful_authorize_response, failed_void_response) + assert_success response + assert_equal 'Successful', response.message + end + + def test_unsuccessful_verify + response = stub_comms(@gateway, :ssl_request) do + @gateway.verify(credit_card, @options) + end.respond_with(failed_authorize_response, successful_void_response) + assert_failure response + assert_equal 'Not Successful', response.message + end + + private + + def failed_purchase_response + <<-RESPONSE + <?xml version="1.0" encoding="UTF-8"?><response type="valid" service="token"><result>Not Successful</result><responsecode>5</responsecode><recommendedaction>Cardholder must call his bank before re-attempting this transaction or try another card</recommendedaction><issuerinfo><name>STATE BANK OF MAURITIUS, LTD.</name><cardbrand>VISA</cardbrand><country>MAURITIUS</country></issuerinfo><CVV2response>X</CVV2response><AVSresponse>0</AVSresponse><tranid>33025003</tranid><authcode>000000</authcode><trackid>Test Shopify - 1003</trackid><merchantid>SBMTEST</merchantid></response> + RESPONSE + end + + def successful_purchase_response + <<-RESPONSE + <?xml version="1.0" encoding="UTF-8"?><response type="valid" service="token"><result>Successful</result><responsecode>0</responsecode><CVV2response>X</CVV2response><AVSresponse>S</AVSresponse><tranid>33024417</tranid><authcode>429259</authcode><trackid>Test Shopify - 1003</trackid><merchantid>SBMTEST</merchantid><customer_token>ec0db513-1727-4554-a74f-67297a1db499</customer_token></response> + RESPONSE + end + + def failed_authorize_response + <<-RESPONSE + <?xml version="1.0" encoding="UTF-8"?><response type="valid" service="token"><result>Not Successful</result><responsecode>5</responsecode><recommendedaction>Cardholder must call his bank before re-attempting this transaction or try another card</recommendedaction><issuerinfo><name>STATE BANK OF MAURITIUS, LTD.</name><cardbrand>VISA</cardbrand><country>MAURITIUS</country></issuerinfo><CVV2response>X</CVV2response><AVSresponse>0</AVSresponse><tranid>33025003</tranid><authcode>000000</authcode><trackid>Test Shopify - 1003</trackid><merchantid>SBMTEST</merchantid></response> + RESPONSE + end + + def successful_authorize_response + <<-RESPONSE + <?xml version="1.0" encoding="UTF-8"?><response type="valid" service="token"><result>Successful</result><responsecode>0</responsecode><CVV2response>X</CVV2response><AVSresponse>S</AVSresponse><tranid>33024417</tranid><authcode>429259</authcode><trackid>Test Shopify - 1003</trackid><merchantid>SBMTEST</merchantid><customer_token>ec0db513-1727-4554-a74f-67297a1db499</customer_token></response> + RESPONSE + end + + def failed_capture_response + <<-RESPONSE + <?xml version="1.0" encoding="UTF-8"?><response type="error"><error_code_tag>EGP00173</error_code_tag><error_text>EGP00173-Currency Code mismatch</error_text></response> + RESPONSE + end + + def successful_capture_response + <<-RESPONSE + <?xml version="1.0" encoding="UTF-8"?><response type="valid" service="token"><result>Successful</result><responsecode>0</responsecode><CVV2response>X</CVV2response><AVSresponse>S</AVSresponse><tranid>33024417</tranid><authcode>429259</authcode><trackid>Test Shopify - 1003</trackid><merchantid>SBMTEST</merchantid><customer_token>ec0db513-1727-4554-a74f-67297a1db499</customer_token></response> + RESPONSE + end + + def successful_void_response + <<-RESPONSE + <?xml version=\"1.0\" encoding=\"UTF-8\"?><response type=\"valid\"><result>Successful</result><responsecode>0</responsecode><tranid>36919479</tranid><authcode>447338</authcode><trackid>dd7bd9e2c8d79eb16c88a29fdfe846fe</trackid><merchantid>SBMTEST</merchantid></response> + RESPONSE + end + + def failed_void_response + <<-RESPONSE + <?xml version=\"1.0\" encoding=\"UTF-8\"?><response type=\"error\"><error_code_tag>EGP00165</error_code_tag><error_text>EGP00165-Invalid Track ID data</error_text></response> + RESPONSE + end + + def successful_refund_response + <<-RESPONSE + <?xml version=\"1.0\" encoding=\"UTF-8\"?><response type=\"valid\"><result>Successful</result><responsecode>0</responsecode><tranid>36919603</tranid><authcode>454744</authcode><trackid>91654e4413a1a1c0a7f4f84880984872</trackid><merchantid>SBMTEST</merchantid></response> + RESPONSE + end + + def failed_refund_response + <<-RESPONSE + <?xml version=\"1.0\" encoding=\"UTF-8\"?><response type=\"error\"><error_code_tag>EGP00165</error_code_tag><error_text>EGP00165-Invalid Track ID data</error_text></response> + RESPONSE + end +end diff --git a/test/unit/gateways/checkout_v2_test.rb b/test/unit/gateways/checkout_v2_test.rb new file mode 100644 index 00000000000..0855d53da5b --- /dev/null +++ b/test/unit/gateways/checkout_v2_test.rb @@ -0,0 +1,512 @@ +require 'test_helper' + +class CheckoutV2Test < Test::Unit::TestCase + include CommStub + + def setup + @gateway = CheckoutV2Gateway.new( + secret_key: '1111111111111' + ) + + @credit_card = credit_card + @amount = 100 + end + + def test_successful_purchase + response = stub_comms do + @gateway.purchase(@amount, @credit_card) + end.respond_with(successful_purchase_response) + + assert_success response + assert_equal 'pay_fj3xswqe3emuxckocjx6td73ni', response.authorization + assert response.test? + end + + def test_successful_purchase_includes_avs_result + response = stub_comms do + @gateway.purchase(@amount, @credit_card) + end.respond_with(successful_purchase_response) + + assert_equal 'S', response.avs_result['code'] + assert_equal 'U.S.-issuing bank does not support AVS.', response.avs_result['message'] + assert_equal 'X', response.avs_result['postal_match'] + assert_equal 'X', response.avs_result['street_match'] + end + + def test_successful_purchase_includes_cvv_result + response = stub_comms do + @gateway.purchase(@amount, @credit_card) + end.respond_with(successful_purchase_response) + + assert_equal 'Y', response.cvv_result['code'] + end + + def test_successful_authorize_includes_avs_result + response = stub_comms do + @gateway.authorize(@amount, @credit_card) + end.respond_with(successful_authorize_response) + + assert_equal 'S', response.avs_result['code'] + assert_equal 'U.S.-issuing bank does not support AVS.', response.avs_result['message'] + assert_equal 'X', response.avs_result['postal_match'] + assert_equal 'X', response.avs_result['street_match'] + end + + def test_successful_authorize_includes_cvv_result + response = stub_comms do + @gateway.authorize(@amount, @credit_card) + end.respond_with(successful_authorize_response) + + assert_equal 'Y', response.cvv_result['code'] + end + + def test_purchase_with_additional_fields + response = stub_comms do + @gateway.purchase(@amount, @credit_card, {descriptor_city: 'london', descriptor_name: 'sherlock'}) + end.check_request do |endpoint, data, headers| + assert_match(/"billing_descriptor\":{\"name\":\"sherlock\",\"city\":\"london\"}/, data) + end.respond_with(successful_purchase_response) + + assert_success response + end + + def test_failed_purchase + response = stub_comms do + @gateway.purchase(@amount, @credit_card) + end.respond_with(failed_purchase_response) + assert_failure response + assert_equal Gateway::STANDARD_ERROR_CODE[:invalid_number], response.error_code + end + + def test_successful_authorize_and_capture + response = stub_comms do + @gateway.authorize(@amount, @credit_card) + end.respond_with(successful_authorize_response) + + assert_success response + assert_equal 'pay_fj3xswqe3emuxckocjx6td73ni', response.authorization + + capture = stub_comms do + @gateway.capture(@amount, response.authorization) + end.respond_with(successful_capture_response) + + assert_success capture + end + + def test_successful_authorize_and_capture_with_additional_options + response = stub_comms do + options = { + card_on_file: true, + transaction_indicator: 2, + previous_charge_id: 'pay_123' + } + @gateway.authorize(@amount, @credit_card, options) + end.check_request do |endpoint, data, headers| + assert_match(%r{"stored":"true"}, data) + assert_match(%r{"payment_type":"Recurring"}, data) + assert_match(%r{"previous_payment_id":"pay_123"}, data) + end.respond_with(successful_authorize_response) + + assert_success response + assert_equal 'pay_fj3xswqe3emuxckocjx6td73ni', response.authorization + + capture = stub_comms do + @gateway.capture(@amount, response.authorization) + end.respond_with(successful_capture_response) + + assert_success capture + end + + def test_3ds_passed + response = stub_comms do + options = { + execute_threed: true, + callback_url: 'https://www.example.com' + } + @gateway.authorize(@amount, @credit_card, options) + end.check_request do |endpoint, data, headers| + assert_match(%r{"success_url"}, data) + assert_match(%r{"failure_url"}, data) + end.respond_with(successful_authorize_response) + + assert_success response + end + + def test_successful_verify_payment + response = stub_comms(@gateway, :ssl_request) do + @gateway.verify_payment('testValue') + end.respond_with(successful_verify_payment_response) + + assert_success response + end + + def test_failed_verify_payment + response = stub_comms(@gateway, :ssl_request) do + @gateway.verify_payment('testValue') + end.respond_with(failed_verify_payment_response) + + assert_failure response + end + + def test_successful_authorize_and_capture_with_3ds + response = stub_comms do + options = { + execute_threed: true, + three_d_secure: { + version: '1.0.2', + eci: '05', + cryptogram: '1234', + xid: '1234' + } + } + @gateway.authorize(@amount, @credit_card, options) + end.respond_with(successful_authorize_response) + + assert_success response + assert_equal 'pay_fj3xswqe3emuxckocjx6td73ni', response.authorization + + capture = stub_comms do + @gateway.capture(@amount, response.authorization) + end.respond_with(successful_capture_response) + + assert_success capture + end + + def test_successful_authorize_and_capture_with_3ds2 + response = stub_comms do + options = { + execute_threed: true, + three_d_secure: { + version: '2.0.0', + eci: '05', + cryptogram: '1234', + ds_transaction_id: '1234' + } + } + @gateway.authorize(@amount, @credit_card, options) + end.respond_with(successful_authorize_response) + + assert_success response + assert_equal 'pay_fj3xswqe3emuxckocjx6td73ni', response.authorization + + capture = stub_comms do + @gateway.capture(@amount, response.authorization) + end.respond_with(successful_capture_response) + + assert_success capture + end + + def test_failed_authorize + response = stub_comms do + @gateway.authorize(@amount, @credit_card) + end.respond_with(failed_authorize_response) + + assert_failure response + assert_equal 'Invalid Card Number', response.message + assert response.test? + end + + def test_failed_capture + response = stub_comms do + @gateway.capture(100, '') + end.respond_with(failed_capture_response) + + assert_failure response + end + + def test_successful_void + response = stub_comms do + @gateway.authorize(@amount, @credit_card) + end.respond_with(successful_authorize_response) + + assert_success response + assert_equal 'pay_fj3xswqe3emuxckocjx6td73ni', response.authorization + + void = stub_comms do + @gateway.void(response.authorization) + end.respond_with(successful_void_response) + + assert_success void + end + + def test_failed_void + response = stub_comms do + @gateway.void('5d53a33d960c46d00f5dc061947d998c') + end.respond_with(failed_void_response) + + assert_failure response + end + + def test_successful_refund + response = stub_comms do + @gateway.purchase(@amount, @credit_card) + end.respond_with(successful_purchase_response) + + assert_success response + assert_equal 'pay_fj3xswqe3emuxckocjx6td73ni', response.authorization + + refund = stub_comms do + @gateway.refund(@amount, response.authorization) + end.respond_with(successful_refund_response) + + assert_success refund + end + + def test_failed_refund + response = stub_comms do + @gateway.refund(nil, '') + end.respond_with(failed_refund_response) + + assert_failure response + end + + def test_successful_verify + response = stub_comms do + @gateway.verify(@credit_card) + end.respond_with(successful_authorize_response, failed_void_response) + assert_success response + assert_equal 'Succeeded', response.message + end + + def test_failed_verify + response = stub_comms do + @gateway.verify(@credit_card) + end.respond_with(failed_authorize_response, successful_void_response) + assert_failure response + assert_equal 'Invalid Card Number', response.message + end + + def test_transcript_scrubbing + assert_equal post_scrubbed, @gateway.scrub(pre_scrubbed) + end + + def test_invalid_json + response = stub_comms do + @gateway.purchase(@amount, @credit_card) + end.respond_with(invalid_json_response) + + assert_failure response + assert_match %r{Unable to read error message}, response.message + end + + def test_error_code_returned + response = stub_comms do + @gateway.purchase(@amount, @credit_card) + end.respond_with(error_code_response) + + assert_failure response + assert_match(/request_invalid: card_expired/, response.error_code) + end + + def test_supported_countries + assert_equal ['AD', 'AE', 'AT', 'BE', 'BG', 'CH', 'CY', 'CZ', 'DE', 'DK', 'EE', 'ES', 'FO', 'FI', 'FR', 'GB', 'GI', 'GL', 'GR', 'HR', 'HU', 'IE', 'IS', 'IL', 'IT', 'LI', 'LT', 'LU', 'LV', 'MC', 'MT', 'NL', 'NO', 'PL', 'PT', 'RO', 'SE', 'SI', 'SM', 'SK', 'SJ', 'TR', 'VA'], @gateway.supported_countries + end + + private + + def pre_scrubbed + %q( + <- "POST /payments HTTP/1.1\r\nContent-Type: application/json;charset=UTF-8\r\nAuthorization: sk_test_ab12301d-e432-4ea7-97d1-569809518aaf\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: api.checkout.com\r\nContent-Length: 346\r\n\r\n" + <- "{\"capture\":false,\"amount\":\"200\",\"reference\":\"1\",\"currency\":\"USD\",\"source\":{\"type\":\"card\",\"name\":\"Longbob Longsen\",\"number\":\"4242424242424242\",\"cvv\":\"100\",\"expiry_year\":\"2025\" + ) + end + + def post_scrubbed + %q( + <- "POST /payments HTTP/1.1\r\nContent-Type: application/json;charset=UTF-8\r\nAuthorization: [FILTERED]\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: api.checkout.com\r\nContent-Length: 346\r\n\r\n" + <- "{\"capture\":false,\"amount\":\"200\",\"reference\":\"1\",\"currency\":\"USD\",\"source\":{\"type\":\"card\",\"name\":\"Longbob Longsen\",\"number\":\"[FILTERED]\",\"cvv\":\"[FILTERED]\",\"expiry_year\":\"2025\" + ) + end + + def successful_purchase_response + %( + { + "id":"pay_fj3xswqe3emuxckocjx6td73ni", + "amount":200, + "currency":"USD", + "reference":"1", + "response_summary": "Approved", + "response_code":"10000", + "customer": { + "id": "cus_zvnv7gsblfjuxppycd7bx4erue", + "email": "longbob.longsen@example.com", + "name": "Sarah Mitchell" + }, + "source": { + "cvv_check":"Y", + "avs_check":"S" + } + } + ) + end + + def failed_purchase_response + %( + { + "id":"pay_awjzhfj776gulbp2nuslj4agbu", + "amount":200, + "currency":"USD", + "reference":"1", + "response_summary": "Invalid Card Number", + "response_code":"20014", + "customer": { + "id": "cus_zvnv7gsblfjuxppycd7bx4erue", + "email": "longbob.longsen@example.com", + "name": "Sarah Mitchell" + }, + "source": { + "cvvCheck":"Y", + "avsCheck":"S" + } + } + ) + end + + def successful_authorize_response + %( + { + "id": "pay_fj3xswqe3emuxckocjx6td73ni", + "action_id": "act_fj3xswqe3emuxckocjx6td73ni", + "amount": 200, + "currency": "USD", + "approved": true, + "status": "Authorized", + "auth_code": "858188", + "eci": "05", + "scheme_id": "638284745624527", + "response_code": "10000", + "response_summary": "Approved", + "risk": { + "flagged": false + }, + "source": { + "id": "src_nq6m5dqvxmsunhtzf7adymbq3i", + "type": "card", + "expiry_month": 8, + "expiry_year": 2025, + "name": "Sarah Mitchell", + "scheme": "Visa", + "last4": "4242", + "fingerprint": "5CD3B9CB15338683110959D165562D23084E1FF564F420FE9A990DF0BCD093FC", + "bin": "424242", + "card_type": "Credit", + "card_category": "Consumer", + "issuer": "JPMORGAN CHASE BANK NA", + "issuer_country": "US", + "product_id": "A", + "product_type": "Visa Traditional", + "avs_check": "S", + "cvv_check": "Y" + }, + "customer": { + "id": "cus_ssxcidkqvfde7lfn5n7xzmgv2a", + "email": "longbob.longsen@example.com", + "name": "Sarah Mitchell" + }, + "processed_on": "2019-03-24T10:14:32Z", + "reference": "ORD-5023-4E89", + "_links": { + "self": { + "href": "https://api.sandbox.checkout.com/payments/pay_fj3xswqe3emuxckocjx6td73ni" + }, + "actions": { + "href": "https://api.sandbox.checkout.com/payments/pay_fj3xswqe3emuxckocjx6td73ni/actions" + }, + "capture": { + "href": "https://api.sandbox.checkout.com/payments/pay_fj3xswqe3emuxckocjx6td73ni/captures" + }, + "void": { + "href": "https://api.sandbox.checkout.com/payments/pay_fj3xswqe3emuxckocjx6td73ni/voids" + } + } + } + ) + end + + def failed_authorize_response + %( + { + "id":"pay_awjzhfj776gulbp2nuslj4agbu", + "amount":200, + "currency":"USD", + "reference":"1", + "customer": { + "id": "cus_zvnv7gsblfjuxppycd7bx4erue", + "email": "longbob.longsen@example.com", + "name": "Sarah Mitchell" + }, + "response_summary": "Invalid Card Number", + "response_code":"20014" + } + ) + end + + def successful_capture_response + %( + { + "action_id": "act_2f56bhkau5dubequbv5aa6w4qi", + "reference": "1" + } + ) + end + + def failed_capture_response + %( + ) + end + + def successful_refund_response + %( + { + "action_id": "act_2f56bhkau5dubequbv5aa6w4qi", + "reference": "1" + } + ) + end + + def failed_refund_response + %( + ) + end + + def successful_void_response + %( + { + "action_id": "act_2f56bhkau5dubequbv5aa6w4qi", + "reference": "1" + } + ) + end + + def failed_void_response + %( + ) + end + + def invalid_json_response + %( + { + "id": "pay_123", + ) + end + + def error_code_response + %( + { + "request_id": "e5a3ce6f-a4e9-4445-9ec7-e5975e9a6213","error_type": "request_invalid","error_codes": ["card_expired"] + } + ) + end + + def successful_verify_payment_response + %( + {"id":"pay_tkvif5mf54eerhd3ysuawfcnt4","requested_on":"2019-08-14T18:13:54Z","source":{"id":"src_lot2ch4ygk3ehi4fugxmk7r2di","type":"card","expiry_month":12,"expiry_year":2020,"name":"Jane Doe","scheme":"Visa","last4":"0907","fingerprint":"E4048195442B0059D73FD47F6E1961A02CD085B0B34B7703CE4A93750DB5A0A1","bin":"457382","avs_check":"S","cvv_check":"Y"},"amount":100,"currency":"USD","payment_type":"Regular","reference":"Dvy8EMaEphrMWolKsLVHcUqPsyx","status":"Authorized","approved":true,"3ds":{"downgraded":false,"enrolled":"Y","authentication_response":"Y","cryptogram":"ce49b5c1-5d3c-4864-bd16-2a8c","xid":"95202312-f034-48b4-b9b2-54254a2b49fb","version":"2.1.0"},"risk":{"flagged":false},"customer":{"id":"cus_zt5pspdtkypuvifj7g6roy7p6y","name":"Jane Doe"},"billing_descriptor":{"name":"","city":"London"},"payment_ip":"127.0.0.1","metadata":{"Udf5":"ActiveMerchant"},"eci":"05","scheme_id":"638284745624527","actions":[{"id":"act_tkvif5mf54eerhd3ysuawfcnt4","type":"Authorization","response_code":"10000","response_summary":"Approved"}],"_links":{"self":{"href":"https://api.sandbox.checkout.com/payments/pay_tkvif5mf54eerhd3ysuawfcnt4"},"actions":{"href":"https://api.sandbox.checkout.com/payments/pay_tkvif5mf54eerhd3ysuawfcnt4/actions"},"capture":{"href":"https://api.sandbox.checkout.com/payments/pay_tkvif5mf54eerhd3ysuawfcnt4/captures"},"void":{"href":"https://api.sandbox.checkout.com/payments/pay_tkvif5mf54eerhd3ysuawfcnt4/voids"}}} + ) + end + + def failed_verify_payment_response + %( + {"id":"pay_xrwmaqlar73uhjtyoghc7bspa4","requested_on":"2019-08-14T18:32:50Z","source":{"type":"card","expiry_month":12,"expiry_year":2020,"name":"Jane Doe","scheme":"Visa","last4":"7863","fingerprint":"DC20145B78E242C561A892B83CB64471729D7A5063E5A5B341035713B8FDEC92","bin":"453962"},"amount":100,"currency":"USD","payment_type":"Regular","reference":"EuyOZtgt8KI4tolEH8lqxCclWqz","status":"Declined","approved":false,"3ds":{"downgraded":false,"enrolled":"Y","version":"2.1.0"},"risk":{"flagged":false},"customer":{"id":"cus_bb4b7eu35sde7o33fq2xchv7oq","name":"Jane Doe"},"payment_ip":"127.0.0.1","metadata":{"Udf5":"ActiveMerchant"},"_links":{"self":{"href":"https://api.sandbox.checkout.com/payments/pay_xrwmaqlar73uhjtyoghc7bspa4"},"actions":{"href":"https://api.sandbox.checkout.com/payments/pay_xrwmaqlar73uhjtyoghc7bspa4/actions"}}} + ) + end +end diff --git a/test/unit/gateways/citrus_pay_test.rb b/test/unit/gateways/citrus_pay_test.rb new file mode 100644 index 00000000000..40b126e3782 --- /dev/null +++ b/test/unit/gateways/citrus_pay_test.rb @@ -0,0 +1,256 @@ +require 'test_helper' + +class CitrusPayTest < Test::Unit::TestCase + include CommStub + def setup + @gateway = CitrusPayGateway.new( + userid: 'userid', + password: 'password' + ) + + @credit_card = credit_card + @amount = 100 + + @options = { + order_id: '1', + billing_address: address, + description: 'Store Purchase' + } + end + + def test_successful_purchase + @gateway.expects(:ssl_request).twice.returns(successful_authorize_response).then.returns(successful_capture_response) + + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + + assert_equal '2a79d859-8b23-4dd0-b319-201fe2373c50|ce61e06e-8c92-4a0f-a491-6eb473d883dd', response.authorization + assert response.test? + end + + def test_failed_purchase + response = stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, @credit_card) + end.respond_with(failed_purchase_response) + + assert_failure response + assert_equal 'FAILURE - DECLINED', response.message + assert response.test? + end + + def test_authorize_and_capture + response = stub_comms(@gateway, :ssl_request) do + @gateway.authorize(@amount, @credit_card) + end.respond_with(successful_authorize_response) + + assert_success response + assert_equal '91debbeb-d88f-42e9-a6ce-9b62c99d656b|f3d100a7-18d9-4609-aabc-8a710ad0e210', response.authorization + + capture = stub_comms(@gateway, :ssl_request) do + @gateway.capture(@amount, response.authorization) + end.check_request do |method, endpoint, data, headers| + assert_match(/f3d100a7-18d9-4609-aabc-8a710ad0e210/, data) + end.respond_with(successful_capture_response) + + assert_success capture + end + + def test_refund + response = stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, @credit_card) + end.respond_with(successful_capture_response) + + assert_success response + assert_equal '2a79d859-8b23-4dd0-b319-201fe2373c50|ce61e06e-8c92-4a0f-a491-6eb473d883dd', response.authorization + + refund = stub_comms(@gateway, :ssl_request) do + @gateway.refund(@amount, response.authorization) + end.check_request do |method, endpoint, data, headers| + assert_match(/ce61e06e-8c92-4a0f-a491-6eb473d883dd/, data) + end.respond_with(successful_refund_response) + + assert_success refund + end + + def test_void + response = stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, @credit_card) + end.respond_with(successful_capture_response) + + assert_success response + assert_equal '2a79d859-8b23-4dd0-b319-201fe2373c50|ce61e06e-8c92-4a0f-a491-6eb473d883dd', response.authorization + + void = stub_comms(@gateway, :ssl_request) do + @gateway.void(response.authorization) + end.check_request do |method, endpoint, data, headers| + assert_match(/ce61e06e-8c92-4a0f-a491-6eb473d883dd/, data) + end.respond_with(successful_void_response) + + assert_success void + end + + def test_passing_alpha3_country_code + stub_comms(@gateway, :ssl_request) do + @gateway.authorize(@amount, @credit_card, :billing_address => {country: 'US'}) + end.check_request do |method, endpoint, data, headers| + assert_match(/USA/, data) + end.respond_with(successful_authorize_response) + end + + def test_non_existent_country + stub_comms(@gateway, :ssl_request) do + @gateway.authorize(@amount, @credit_card, :billing_address => {country: 'Blah'}) + end.check_request do |method, endpoint, data, headers| + assert_match(/"country":null/, data) + end.respond_with(successful_authorize_response) + end + + def test_passing_cvv + stub_comms(@gateway, :ssl_request) do + @gateway.authorize(@amount, @credit_card) + end.check_request do |method, endpoint, data, headers| + assert_match(/#{@credit_card.verification_value}/, data) + end.respond_with(successful_authorize_response) + end + + def test_passing_billing_address + stub_comms(@gateway, :ssl_request) do + @gateway.authorize(@amount, @credit_card, :billing_address => address) + end.check_request do |method, endpoint, data, headers| + parsed = JSON.parse(data) + assert_equal('456 My Street', parsed['billing']['address']['street']) + assert_equal('K1C2N6', parsed['billing']['address']['postcodeZip']) + end.respond_with(successful_authorize_response) + end + + def test_passing_shipping_name + stub_comms(@gateway, :ssl_request) do + @gateway.authorize(@amount, @credit_card, :shipping_address => address) + end.check_request do |method, endpoint, data, headers| + parsed = JSON.parse(data) + assert_equal('Jim', parsed['shipping']['firstName']) + assert_equal('Smith', parsed['shipping']['lastName']) + end.respond_with(successful_authorize_response) + end + + def test_successful_verify + response = stub_comms(@gateway, :ssl_request) do + @gateway.verify(@credit_card, @options) + end.respond_with(successful_authorize_response, successful_void_response) + assert_success response + assert_equal '91debbeb-d88f-42e9-a6ce-9b62c99d656b', response.params['order']['id'] + end + + def test_successful_verify_with_failed_void + response = stub_comms(@gateway, :ssl_request) do + @gateway.verify(@credit_card, @options) + end.respond_with(successful_authorize_response, failed_void_response) + assert_success response + assert_equal 'Succeeded', response.message + end + + def test_unsuccessful_verify + response = stub_comms(@gateway, :ssl_request) do + @gateway.verify(@credit_card, @options) + end.respond_with(failed_authorize_response, successful_void_response) + assert_failure response + assert_equal 'FAILURE - DECLINED', response.message + end + + def test_north_america_region_url + @gateway = TnsGateway.new( + userid: 'userid', + password: 'password', + region: 'north_america' + ) + + response = stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, @credit_card, @options) + end.check_request do |method, endpoint, data, headers| + assert_match(/secure.na.tnspayments.com/, endpoint) + end.respond_with(successful_capture_response) + + assert_success response + end + + def test_asia_pacific_region_url + @gateway = TnsGateway.new( + userid: 'userid', + password: 'password', + region: 'asia_pacific' + ) + + response = stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, @credit_card, @options) + end.check_request do |method, endpoint, data, headers| + assert_match(/secure.ap.tnspayments.com/, endpoint) + end.respond_with(successful_capture_response) + + assert_success response + end + + def test_scrub + assert @gateway.supports_scrubbing? + assert_equal @gateway.scrub(pre_scrubbed), post_scrubbed + end + + private + + def pre_scrubbed + %q[ + D, {"provided":{"card":{"expiry":{"year":"17","month":"09"},"number":"5123456789012346","securityCode":"123"}},"type":"CARD"} + <- transaction/1 HTTP/1.1\r\nAuthorization: Basic bWVyY2hhbnQuVEVTVFNQUkVFRExZMDE6M2YzNGZlNTAzMzRmYmU2Y2JlMDRjMjgzNDExYTU4NjA=\r\nContent-Type: + <- {\"order\":{\"amount\":\"1.00\",\"currency\":\"USD\"},\"sourceOfFunds\":{\"provided\":{\"card\":{\"expiry\":{\"year\":\"17\",\"month\":\"09\"},\"number\":\"5123456789012346\",\"securityCode\":\"123\"}},\"type\":\"CARD\"} + ] + end + + def post_scrubbed + %q[ + D, {"provided":{"card":{"expiry":{"year":"17","month":"09"},"number":"[FILTERED]","securityCode":"[FILTERED]"}},"type":"CARD"} + <- transaction/1 HTTP/1.1\r\nAuthorization: Basic [FILTERED]Content-Type: + <- {\"order\":{\"amount\":\"1.00\",\"currency\":\"USD\"},\"sourceOfFunds\":{\"provided\":{\"card\":{\"expiry\":{\"year\":\"17\",\"month\":\"09\"},\"number\":\"[FILTERED]\",\"securityCode\":\"[FILTERED]\"}},\"type\":\"CARD\"} + ] + end + + def successful_authorize_response + %( + {"billing":{"address":{"city":"Ottawa","country":"USA","postcodeZip":"K1C2N6","stateProvince":"ON","street":"456 My Street, Apt 1"},"phone":"(555)555-5555"},"gatewayEntryPoint":"WEB_SERVICES_API","merchant":"TESTSPREEDLY01","order":{"amount":1.00,"creationTime":"2014-10-16T17:23:56.444Z","currency":"USD","id":"91debbeb-d88f-42e9-a6ce-9b62c99d656b","status":"CAPTURED","totalAuthorizedAmount":1.00,"totalCapturedAmount":1.00,"totalRefundedAmount":0.00},"response":{"acquirerCode":"0","acquirerMessage":"Transaction is approved","cardSecurityCode":{"acquirerCode":"N","gatewayCode":"NO_MATCH"},"gatewayCode":"APPROVED","risk":{"gatewayCode":"ACCEPTED","review":{"decision":"NOT_REQUIRED"},"rule":[{"data":"512345","name":"MERCHANT_BIN_RANGE","recommendation":"NO_ACTION","type":"MERCHANT_RULE"},{"data":"N","name":"MERCHANT_CSC","recommendation":"NO_ACTION","type":"MERCHANT_RULE"},{"name":"SUSPECT_CARD_LIST","recommendation":"NO_ACTION","type":"MERCHANT_RULE"},{"name":"TRUSTED_CARD_LIST","recommendation":"NO_ACTION","type":"MERCHANT_RULE"},{"data":"512345","name":"MSO_BIN_RANGE","recommendation":"NO_ACTION","type":"MSO_RULE"},{"data":"N","name":"MSO_CSC","recommendation":"NO_ACTION","type":"MSO_RULE"}]}},"result":"SUCCESS","sourceOfFunds":{"provided":{"card":{"brand":"MASTERCARD","expiry":{"month":"9","year":"15"},"fundingMethod":"CREDIT","number":"512345xxxxxx2346","scheme":"MASTERCARD"}},"type":"CARD"},"timeOfRecord":"2014-10-16T17:23:57.083Z","transaction":{"acquirer":{"batch":1,"id":"PAYMENTECH_TAMPA","merchantId":"1234678"},"amount":1.00,"authorizationCode":"005163","currency":"USD","frequency":"SINGLE","id":"f3d100a7-18d9-4609-aabc-8a710ad0e210","receipt":"428917000180","reference":"1","source":"INTERNET","terminal":"002","type":"CAPTURE"},"version":"22"} + ) + end + + def successful_capture_response + %( + {"billing":{"address":{"city":"Ottawa","country":"USA","postcodeZip":"K1C2N6","stateProvince":"ON","street":"456 My Street, Apt 1"},"phone":"(555)555-5555"},"gatewayEntryPoint":"WEB_SERVICES_API","merchant":"TESTSPREEDLY01","order":{"amount":1.00,"creationTime":"2014-10-16T17:28:32.999Z","currency":"USD","id":"2a79d859-8b23-4dd0-b319-201fe2373c50","status":"CAPTURED","totalAuthorizedAmount":1.00,"totalCapturedAmount":1.00,"totalRefundedAmount":0.00},"response":{"acquirerCode":"0","acquirerMessage":"Transaction is approved","cardSecurityCode":{"acquirerCode":"N","gatewayCode":"NO_MATCH"},"gatewayCode":"APPROVED","risk":{"gatewayCode":"ACCEPTED","review":{"decision":"NOT_REQUIRED"},"rule":[{"data":"512345","name":"MERCHANT_BIN_RANGE","recommendation":"NO_ACTION","type":"MERCHANT_RULE"},{"data":"N","name":"MERCHANT_CSC","recommendation":"NO_ACTION","type":"MERCHANT_RULE"},{"name":"SUSPECT_CARD_LIST","recommendation":"NO_ACTION","type":"MERCHANT_RULE"},{"name":"TRUSTED_CARD_LIST","recommendation":"NO_ACTION","type":"MERCHANT_RULE"},{"data":"512345","name":"MSO_BIN_RANGE","recommendation":"NO_ACTION","type":"MSO_RULE"},{"data":"N","name":"MSO_CSC","recommendation":"NO_ACTION","type":"MSO_RULE"}]}},"result":"SUCCESS","sourceOfFunds":{"provided":{"card":{"brand":"MASTERCARD","expiry":{"month":"9","year":"15"},"fundingMethod":"CREDIT","number":"512345xxxxxx2346","scheme":"MASTERCARD"}},"type":"CARD"},"timeOfRecord":"2014-10-16T17:28:33.685Z","transaction":{"acquirer":{"batch":1,"id":"PAYMENTECH_TAMPA","merchantId":"1234678"},"amount":1.00,"authorizationCode":"005202","currency":"USD","frequency":"SINGLE","id":"ce61e06e-8c92-4a0f-a491-6eb473d883dd","receipt":"428917000182","reference":"1","source":"INTERNET","terminal":"002","type":"CAPTURE"},"version":"22"} + ) + end + + def failed_purchase_response + %( + {"billing":{"address":{"city":"Ottawa","country":"USA","postcodeZip":"K1C2N6","stateProvince":"ON","street":"456 My Street, Apt 1"},"phone":"(555)555-5555"},"gatewayEntryPoint":"WEB_SERVICES_API","merchant":"TESTSPREEDLY01","order":{"amount":1.00,"creationTime":"2014-10-16T18:25:46.095Z","currency":"USD","id":"fb21987d-a646-48f1-aa6a-07028a74a956","status":"FAILED","totalAuthorizedAmount":0.00,"totalCapturedAmount":0.00,"totalRefundedAmount":0.00},"response":{"cardSecurityCode":{"acquirerCode":"N","gatewayCode":"NO_MATCH"},"gatewayCode":"DECLINED","risk":{"gatewayCode":"ACCEPTED","review":{"decision":"NOT_REQUIRED"},"rule":[{"data":"400030","name":"MERCHANT_BIN_RANGE","recommendation":"NO_ACTION","type":"MERCHANT_RULE"},{"name":"SUSPECT_CARD_LIST","recommendation":"NO_ACTION","type":"MERCHANT_RULE"},{"name":"TRUSTED_CARD_LIST","recommendation":"NO_ACTION","type":"MERCHANT_RULE"},{"data":"400030","name":"MSO_BIN_RANGE","recommendation":"NO_ACTION","type":"MSO_RULE"}]}},"result":"FAILURE","sourceOfFunds":{"provided":{"card":{"brand":"VISA","expiry":{"month":"9","year":"15"},"fundingMethod":"CREDIT","number":"400030xxxxxx2220","scheme":"VISA"}},"type":"CARD"},"timeOfRecord":"2014-10-16T18:25:46.095Z","transaction":{"acquirer":{"batch":1,"id":"PAYMENTECH_TAMPA","merchantId":"1234678"},"amount":1.00,"currency":"USD","frequency":"SINGLE","id":"1","receipt":"428918000183","source":"INTERNET","terminal":"002","type":"AUTHORIZATION"},"version":"22"} + ) + end + + def successful_refund_response + %( + {"billing":{"address":{"city":"Ottawa","country":"USA","postcodeZip":"K1C2N6","stateProvince":"ON","street":"456 My Street, Apt 1"},"phone":"(555)555-5555"},"gatewayEntryPoint":"WEB_SERVICES_API","merchant":"TESTSPREEDLY01","order":{"amount":1.00,"creationTime":"2014-10-16T18:49:35.969Z","currency":"USD","id":"98619fc2-3f30-4f3a-9199-84e435dfa498","status":"REFUNDED","totalAuthorizedAmount":1.00,"totalCapturedAmount":1.00,"totalRefundedAmount":1.00},"response":{"acquirerCode":"0","acquirerMessage":"Transaction is approved","cardSecurityCode":{"acquirerCode":"N","gatewayCode":"NO_MATCH"},"gatewayCode":"APPROVED","risk":{"gatewayCode":"ACCEPTED","review":{"decision":"NOT_REQUIRED"},"rule":[{"data":"512345","name":"MERCHANT_BIN_RANGE","recommendation":"NO_ACTION","type":"MERCHANT_RULE"},{"data":"N","name":"MERCHANT_CSC","recommendation":"NO_ACTION","type":"MERCHANT_RULE"},{"name":"SUSPECT_CARD_LIST","recommendation":"NO_ACTION","type":"MERCHANT_RULE"},{"name":"TRUSTED_CARD_LIST","recommendation":"NO_ACTION","type":"MERCHANT_RULE"},{"data":"512345","name":"MSO_BIN_RANGE","recommendation":"NO_ACTION","type":"MSO_RULE"},{"data":"N","name":"MSO_CSC","recommendation":"NO_ACTION","type":"MSO_RULE"}]}},"result":"SUCCESS","sourceOfFunds":{"provided":{"card":{"brand":"MASTERCARD","expiry":{"month":"9","year":"15"},"fundingMethod":"CREDIT","number":"512345xxxxxx2346","scheme":"MASTERCARD"}},"type":"CARD"},"timeOfRecord":"2014-10-16T18:49:37.417Z","transaction":{"acquirer":{"batch":1,"id":"PAYMENTECH_TAMPA","merchantId":"1234678"},"amount":1.00,"currency":"USD","frequency":"SINGLE","id":"9f8a3bc9-9a00-40a7-98ea-8113fa53c018","receipt":"428918000186","reference":"1","source":"INTERNET","terminal":"002","type":"REFUND"},"version":"22"} + ) + end + + def successful_void_response + %( + {"billing":{"address":{"city":"Ottawa","country":"USA","postcodeZip":"K1C2N6","stateProvince":"ON","street":"456 My Street, Apt 1"},"phone":"(555)555-5555"},"gatewayEntryPoint":"WEB_SERVICES_API","merchant":"TESTSPREEDLY01","order":{"amount":1.00,"creationTime":"2014-10-16T18:57:00.277Z","currency":"USD","id":"fb1125bd-b169-48a2-878d-18831639ec08","status":"CANCELLED","totalAuthorizedAmount":0.00,"totalCapturedAmount":0.00,"totalRefundedAmount":0.00},"response":{"acquirerCode":"000","acquirerMessage":"Approved","cardSecurityCode":{"acquirerCode":"N","gatewayCode":"NO_MATCH"},"gatewayCode":"APPROVED","risk":{"gatewayCode":"ACCEPTED","review":{"decision":"NOT_REQUIRED"},"rule":[{"data":"512345","name":"MERCHANT_BIN_RANGE","recommendation":"NO_ACTION","type":"MERCHANT_RULE"},{"data":"N","name":"MERCHANT_CSC","recommendation":"NO_ACTION","type":"MERCHANT_RULE"},{"name":"SUSPECT_CARD_LIST","recommendation":"NO_ACTION","type":"MERCHANT_RULE"},{"name":"TRUSTED_CARD_LIST","recommendation":"NO_ACTION","type":"MERCHANT_RULE"},{"data":"512345","name":"MSO_BIN_RANGE","recommendation":"NO_ACTION","type":"MSO_RULE"},{"data":"N","name":"MSO_CSC","recommendation":"NO_ACTION","type":"MSO_RULE"}]}},"result":"SUCCESS","sourceOfFunds":{"provided":{"card":{"brand":"MASTERCARD","expiry":{"month":"9","year":"15"},"fundingMethod":"CREDIT","number":"512345xxxxxx2346","scheme":"MASTERCARD"}},"type":"CARD"},"timeOfRecord":"2014-10-16T18:57:01.132Z","transaction":{"acquirer":{"batch":1,"id":"PAYMENTECH_TAMPA","merchantId":"1234678"},"amount":1.00,"currency":"USD","frequency":"SINGLE","id":"e648c580-9edf-4baa-b05e-5eeadab3f86e","receipt":"428918000188","source":"INTERNET","terminal":"002","type":"VOID_AUTHORIZATION"},"version":"22"} + ) + end + + def failed_authorize_response + %( + {"billing":{"address":{"city":"Ottawa","country":"USA","postcodeZip":"K1C2N6","stateProvince":"ON","street":"456 My Street, Apt 1"},"phone":"(555)555-5555"},"gatewayEntryPoint":"WEB_SERVICES_API","merchant":"TESTSPREEDLY01","order":{"amount":1.00,"creationTime":"2014-10-17T12:44:15.432Z","currency":"USD","id":"1a0abf5e-3d1f-4c1b-bd0f-94a64aa9304c","status":"FAILED","totalAuthorizedAmount":0.00,"totalCapturedAmount":0.00,"totalRefundedAmount":0.00},"response":{"cardSecurityCode":{"acquirerCode":"N","gatewayCode":"NO_MATCH"},"gatewayCode":"DECLINED","risk":{"gatewayCode":"ACCEPTED","review":{"decision":"NOT_REQUIRED"},"rule":[{"data":"400030","name":"MERCHANT_BIN_RANGE","recommendation":"NO_ACTION","type":"MERCHANT_RULE"},{"name":"SUSPECT_CARD_LIST","recommendation":"NO_ACTION","type":"MERCHANT_RULE"},{"name":"TRUSTED_CARD_LIST","recommendation":"NO_ACTION","type":"MERCHANT_RULE"},{"data":"400030","name":"MSO_BIN_RANGE","recommendation":"NO_ACTION","type":"MSO_RULE"}]}},"result":"FAILURE","sourceOfFunds":{"provided":{"card":{"brand":"VISA","expiry":{"month":"9","year":"15"},"fundingMethod":"CREDIT","number":"400030xxxxxx2220","scheme":"VISA"}},"type":"CARD"},"timeOfRecord":"2014-10-17T12:44:15.432Z","transaction":{"acquirer":{"batch":1,"id":"PAYMENTECH_TAMPA","merchantId":"1234678"},"amount":1.00,"currency":"USD","frequency":"SINGLE","id":"1","receipt":"429012000210","source":"INTERNET","terminal":"002","type":"AUTHORIZATION"},"version":"22"} + ) + end + + def failed_void_response + %( + {\"error\":{\"cause\":\"INVALID_REQUEST\",\"explanation\":\"Value 'VOID' is invalid. There is no transaction to void.\",\"field\":\"apiOperation\",\"validationType\":\"INVALID\"},\"result\":\"ERROR\"} + ) + end +end diff --git a/test/unit/gateways/clearhaus_test.rb b/test/unit/gateways/clearhaus_test.rb new file mode 100644 index 00000000000..b877de05e4f --- /dev/null +++ b/test/unit/gateways/clearhaus_test.rb @@ -0,0 +1,474 @@ +require 'test_helper' + +class ClearhausTest < Test::Unit::TestCase + include CommStub + + def setup + @gateway = ClearhausGateway.new(api_key: 'test_key') + @credit_card = credit_card + @amount = 100 + + @options = { + order_id: '1', + billing_address: address, + description: 'Store Purchase' + } + + @test_signing_key = '7e51b92e-ca7e-48e3-8a96-7d66cf1f2da2' + end + + def test_successful_purchase + @gateway.expects(:ssl_post).times(2).returns( + successful_authorize_response + ).then.returns( + successful_capture_response + ) + + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + + assert_equal '84412a34-fa29-4369-a098-0165a80e8fda', response.authorization + assert response.test? + end + + def test_failed_purchase + @gateway.expects(:ssl_post).returns(failed_purchase_response) + + response = @gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_equal 40000, response.error_code + assert_equal ClearhausGateway::ACTION_CODE_MESSAGES[40000], response.message + end + + def test_successful_authorize + @gateway.expects(:ssl_post).times(1).returns(successful_authorize_response) + + response = @gateway.authorize(@amount, @credit_card, @options) + assert_success response + + assert_equal '84412a34-fa29-4369-a098-0165a80e8fda', response.authorization + assert response.test? + end + + def test_successful_authorize_with_threed + stub_comms do + response = @gateway.authorize(@amount, @credit_card, @options.merge(pares: '123')) + assert_success response + assert response.test? + end.check_request do |endpoint, data, headers| + expr = { card: { pares: '123' } }.to_query + assert_match expr, data + end.respond_with(successful_authorize_response) + end + + def test_additional_params + stub_comms do + response = @gateway.authorize(@amount, @credit_card, @options.merge(order_id: '123', text_on_statement: 'test')) + assert_success response + assert response.test? + end.check_request do |endpoint, data, headers| + order_expr = { reference: '123'}.to_query + tos_expr = { text_on_statement: 'test'}.to_query + + assert_match order_expr, data + assert_match tos_expr, data + end.respond_with(successful_authorize_response) + end + + def test_successful_authorize_with_card + stub_comms do + response = @gateway.authorize(@amount, '4110', @options) + assert_success response + + assert_equal '84412a34-fa29-4369-a098-0165a80e8fda', response.authorization + assert response.test? + end.check_request do |endpoint, data, headers| + assert_match %r{/cards/4110/authorizations}, endpoint + end.respond_with(successful_authorize_response) + end + + def test_failed_authorize + @gateway.expects(:ssl_post).returns(failed_authorize_response) + + response = @gateway.authorize(@amount, @credit_card, @options) + assert_failure response + + assert_equal 40000, response.error_code + assert_equal ClearhausGateway::ACTION_CODE_MESSAGES[40000], response.message + end + + def test_successful_capture + @gateway.expects(:ssl_post).returns(successful_authorize_response) + auth_response = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth_response + + @gateway.expects(:ssl_post).returns(successful_capture_response) + response = @gateway.capture(@amount, auth_response.authorization, @options) + assert_success response + + assert_equal '84412a34-fa29-4369-a098-0165a80e8fda', response.authorization, "It's acutally the id of the original auth" + assert response.test? + end + + def test_failed_capture + @gateway.expects(:ssl_post).returns(failed_capture_response) + + response = @gateway.capture(@amount, @credit_card, @options) + assert_failure response + + assert_equal 40000, response.error_code + assert_equal ClearhausGateway::ACTION_CODE_MESSAGES[40000], response.message + end + + def test_successful_refund + @gateway.expects(:ssl_post).returns(successful_refund_response) + + response = @gateway.refund(@amount, @credit_card, @options) + assert_success response + + assert_equal 'f04c0872-47ce-4683-8d8c-e154221bba14', response.authorization + assert response.test? + end + + def test_failed_refund + @gateway.expects(:ssl_post).returns(failed_refund_response) + + response = @gateway.refund(@amount, @credit_card, @options) + assert_failure response + + assert_equal 40000, response.error_code + assert_equal ClearhausGateway::ACTION_CODE_MESSAGES[40000], response.message + end + + def test_successful_void + @gateway.expects(:ssl_post).returns(successful_void_response) + + response = @gateway.void(@credit_card, @options) + assert_success response + + assert_equal '77d08c40-cfa9-42e3-993d-795f772b70a4', response.authorization + assert response.test? + end + + def test_failed_void + @gateway.expects(:ssl_post).returns(failed_void_response) + + response = @gateway.void(@credit_card, @options) + assert_failure response + + assert_equal 40000, response.error_code + assert_equal ClearhausGateway::ACTION_CODE_MESSAGES[40000], response.message + end + + def test_successful_verify + @gateway.expects(:ssl_post).times(2).returns( + successful_authorize_response + ).then.returns( + successful_void_response + ) + + response = @gateway.verify(@credit_card, @options) + assert_success response + + assert_equal '84412a34-fa29-4369-a098-0165a80e8fda', response.authorization + assert response.test? + end + + def test_successful_verify_with_failed_void + @gateway.expects(:ssl_post).times(2).returns( + successful_authorize_response + ).then.returns( + failed_void_response + ) + + response = @gateway.verify(@credit_card, @options) + assert_success(response) + end + + def test_failed_verify + @gateway.expects(:ssl_post).times(1).returns(failed_authorize_response) + + response = @gateway.verify(@credit_card, @options) + + assert_equal 40000, response.error_code + assert_equal ClearhausGateway::ACTION_CODE_MESSAGES[40000], response.message + end + + def test_successful_store + @gateway.expects(:ssl_post).returns(successful_store_response) + + response = @gateway.store(@credit_card, @options) + assert_success response + + assert_equal '58dabba0-e9ea-4133-8c38-bfa1028c1ed2', response.authorization + assert response.test? + end + + def test_failed_store + @gateway.expects(:ssl_post).returns(failed_store_response) + + response = @gateway.store(@credit_card, @options) + assert_failure response + end + + def test_signing_request + gateway = ClearhausGateway.new(api_key: 'test_key', signing_key: @test_signing_key, private_key: test_private_key) + card = credit_card('4111111111111111', month: '06', year: '2018', verification_value: '123') + options = { currency: 'EUR', ip: '1.1.1.1' } + + stub_comms gateway, :ssl_request do + response = gateway.authorize(2050, card, options) + assert_success response + + assert_equal '84412a34-fa29-4369-a098-0165a80e8fda', response.authorization + assert response.test? + end.check_request do |method, endpoint, data, headers| + assert headers['Signature'] + assert_match %r{7e51b92e-ca7e-48e3-8a96-7d66cf1f2da2 RS256-hex}, headers['Signature'] + assert_match %r{25f8283c3cc43911d7$}, headers['Signature'] + end.respond_with(successful_authorize_response) + end + + def test_cleans_whitespace_from_private_key + private_key_with_whitespace = " #{test_private_key} " + gateway = ClearhausGateway.new(api_key: 'test_key', signing_key: @test_signing_key, private_key: private_key_with_whitespace) + card = credit_card('4111111111111111', month: '06', year: '2018', verification_value: '123') + options = { currency: 'EUR', ip: '1.1.1.1' } + + stub_comms gateway, :ssl_request do + response = gateway.authorize(2050, card, options) + assert_success response + + assert_equal '84412a34-fa29-4369-a098-0165a80e8fda', response.authorization + assert response.test? + end.check_request do |method, endpoint, data, headers| + assert headers['Signature'] + assert_match %r{7e51b92e-ca7e-48e3-8a96-7d66cf1f2da2 RS256-hex}, headers['Signature'] + assert_match %r{25f8283c3cc43911d7$}, headers['Signature'] + end.respond_with(successful_authorize_response) + end + + def test_unsuccessful_signing_request_with_invalid_key + gateway = ClearhausGateway.new(api_key: 'test_key', signing_key: @test_signing_key, private_key: 'foo') + + # stub actual network access, but this shouldn't be reached + gateway.stubs(:ssl_post).returns(nil) + + card = credit_card('4111111111111111', month: '06', year: '2018', verification_value: '123') + options = { currency: 'EUR', ip: '1.1.1.1' } + + response = gateway.authorize(2050, card, options) + assert_failure response + end + + def test_scrub + assert @gateway.supports_scrubbing? + assert_equal @gateway.scrub(pre_scrubbed), post_scrubbed + end + + private + + def pre_scrubbed + %q( +opening connection to gateway.test.clearhaus.com:443... +opened +starting SSL for gateway.test.clearhaus.com:443... +SSL established +<- "POST /authorizations HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAuthorization: Basic NTI2Y2Y1NjQtMTE5Yy00YmI2LTljZjgtMDAxNWVhYzdlNGY2Og==\r\nUser-Agent: Clearhaus ActiveMerchantBindings/1.54.0\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nConnection: close\r\nHost: gateway.test.clearhaus.com\r\nContent-Length: 128\r\n\r\n" +<- "amount=100&card%5Bcsc%5D=123&card%5Bexpire_month%5D=09&card%5Bexpire_year%5D=2016&card%5Bpan%5D=4111111111111111&currency=EUR" +-> "HTTP/1.1 201 Created\r\n" +-> "Content-Type: application/vnd.clearhaus-gateway.hal+json; version=0.9.0; charset=utf-8\r\n" +-> "Date: Wed, 28 Oct 2015 18:56:11 GMT\r\n" +-> "Server: nginx/1.6.2\r\n" +-> "Status: 201 Created\r\n" +-> "X-Content-Type-Options: nosniff\r\n" +-> "Content-Length: 901\r\n" +-> "Connection: Close\r\n" +-> "\r\n" +reading 901 bytes... +-> "{\"id\":\"efb04d12-4bb6-41c0-b030-45ff105641b0\",\"status\":{\"code\":20000},\"processed_at\":\"2015-10-28T18:56:10+00:00\",\"amount\":100,\"currency\":\"EUR\",\"recurring\":false,\"threed_secure\":false,\"_embedded\":{\"card\":{\"id\":\"27127636-0748-4df5-97fe-e58a0c29b618\",\"scheme\":\"visa\",\"last4\":\"1111\",\"_links\":{\"self\":{\"href\":\"/cards/27127636-0748-4df5-97fe-e58a0c29b618\"},\"authorizations\":{\"href\":\"/cards/27127636-0748-4df5-97fe-e58a0c29b618/authorizations\"},\"credits\":{\"href\":\"/cards/27127636-0748-4df5-97fe-e58a0c29b618/credits\"}}}},\"_links\":{\"self\":{\"href\":\"/authorizations/efb04d12-4bb6-41c0-b030-45ff105641b0\"},\"card\":{\"href\":\"/cards/27127636-0748-4df5-97fe-e58a0c29b618\"},\"captures\":{\"href\":\"/authorizations/efb04d12-4bb6-41c0-b030-45ff105641b0/captures\"},\"voids\":{\"href\":\"/authorizations/efb04d12-4bb6-41c0-b030-45ff105641b0/voids\"},\"refunds\":{\"href\":\"/authorizations/efb04d12-4bb6-41c0-b030-45ff105641b0/refunds\"}}}" +read 901 bytes +Conn close +opening connection to gateway.test.clearhaus.com:443... +opened +starting SSL for gateway.test.clearhaus.com:443... +SSL established +<- "POST /authorizations/efb04d12-4bb6-41c0-b030-45ff105641b0/captures HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAuthorization: Basic NTI2Y2Y1NjQtMTE5Yy00YmI2LTljZjgtMDAxNWVhYzdlNGY2Og==\r\nUser-Agent: Clearhaus ActiveMerchantBindings/1.54.0\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nConnection: close\r\nHost: gateway.test.clearhaus.com\r\nContent-Length: 23\r\n\r\n" +<- "amount=100&currency=EUR" +-> "HTTP/1.1 201 Created\r\n" +-> "Content-Type: application/vnd.clearhaus-gateway.hal+json; version=0.9.0; charset=utf-8\r\n" +-> "Date: Wed, 28 Oct 2015 18:56:12 GMT\r\n" +-> "Server: nginx/1.6.2\r\n" +-> "Status: 201 Created\r\n" +-> "X-Content-Type-Options: nosniff\r\n" +-> "Content-Length: 363\r\n" +-> "Connection: Close\r\n" +-> "\r\n" +reading 363 bytes... +-> "{\"id\":\"802988cf-fb01-4430-963a-735ddc6b87f4\",\"status\":{\"code\":20000},\"processed_at\":\"2015-10-28T18:56:12+00:00\",\"amount\":100,\"_links\":{\"self\":{\"href\":\"/captures/802988cf-fb01-4430-963a-735ddc6b87f4\"},\"authorization\":{\"href\":\"/authorizations/efb04d12-4bb6-41c0-b030-45ff105641b0\"},\"refunds\":{\"href\":\"/authorizations/efb04d12-4bb6-41c0-b030-45ff105641b0/refunds\"}}}" +read 363 bytes +Conn close + ) + end + + def post_scrubbed + %q( +opening connection to gateway.test.clearhaus.com:443... +opened +starting SSL for gateway.test.clearhaus.com:443... +SSL established +<- "POST /authorizations HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAuthorization: Basic [FILTERED]\r\nUser-Agent: Clearhaus ActiveMerchantBindings/1.54.0\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nConnection: close\r\nHost: gateway.test.clearhaus.com\r\nContent-Length: 128\r\n\r\n" +<- "amount=100&card%5Bcsc%5D=[FILTERED]&card%5Bexpire_month%5D=09&card%5Bexpire_year%5D=2016&card%5Bpan%5D=[FILTERED]&currency=EUR" +-> "HTTP/1.1 201 Created\r\n" +-> "Content-Type: application/vnd.clearhaus-gateway.hal+json; version=0.9.0; charset=utf-8\r\n" +-> "Date: Wed, 28 Oct 2015 18:56:11 GMT\r\n" +-> "Server: nginx/1.6.2\r\n" +-> "Status: 201 Created\r\n" +-> "X-Content-Type-Options: nosniff\r\n" +-> "Content-Length: 901\r\n" +-> "Connection: Close\r\n" +-> "\r\n" +reading 901 bytes... +-> "{\"id\":\"efb04d12-4bb6-41c0-b030-45ff105641b0\",\"status\":{\"code\":20000},\"processed_at\":\"2015-10-28T18:56:10+00:00\",\"amount\":100,\"currency\":\"EUR\",\"recurring\":false,\"threed_secure\":false,\"_embedded\":{\"card\":{\"id\":\"27127636-0748-4df5-97fe-e58a0c29b618\",\"scheme\":\"visa\",\"last4\":\"1111\",\"_links\":{\"self\":{\"href\":\"/cards/27127636-0748-4df5-97fe-e58a0c29b618\"},\"authorizations\":{\"href\":\"/cards/27127636-0748-4df5-97fe-e58a0c29b618/authorizations\"},\"credits\":{\"href\":\"/cards/27127636-0748-4df5-97fe-e58a0c29b618/credits\"}}}},\"_links\":{\"self\":{\"href\":\"/authorizations/efb04d12-4bb6-41c0-b030-45ff105641b0\"},\"card\":{\"href\":\"/cards/27127636-0748-4df5-97fe-e58a0c29b618\"},\"captures\":{\"href\":\"/authorizations/efb04d12-4bb6-41c0-b030-45ff105641b0/captures\"},\"voids\":{\"href\":\"/authorizations/efb04d12-4bb6-41c0-b030-45ff105641b0/voids\"},\"refunds\":{\"href\":\"/authorizations/efb04d12-4bb6-41c0-b030-45ff105641b0/refunds\"}}}" +read 901 bytes +Conn close +opening connection to gateway.test.clearhaus.com:443... +opened +starting SSL for gateway.test.clearhaus.com:443... +SSL established +<- "POST /authorizations/efb04d12-4bb6-41c0-b030-45ff105641b0/captures HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAuthorization: Basic [FILTERED]\r\nUser-Agent: Clearhaus ActiveMerchantBindings/1.54.0\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nConnection: close\r\nHost: gateway.test.clearhaus.com\r\nContent-Length: 23\r\n\r\n" +<- "amount=100&currency=EUR" +-> "HTTP/1.1 201 Created\r\n" +-> "Content-Type: application/vnd.clearhaus-gateway.hal+json; version=0.9.0; charset=utf-8\r\n" +-> "Date: Wed, 28 Oct 2015 18:56:12 GMT\r\n" +-> "Server: nginx/1.6.2\r\n" +-> "Status: 201 Created\r\n" +-> "X-Content-Type-Options: nosniff\r\n" +-> "Content-Length: 363\r\n" +-> "Connection: Close\r\n" +-> "\r\n" +reading 363 bytes... +-> "{\"id\":\"802988cf-fb01-4430-963a-735ddc6b87f4\",\"status\":{\"code\":20000},\"processed_at\":\"2015-10-28T18:56:12+00:00\",\"amount\":100,\"_links\":{\"self\":{\"href\":\"/captures/802988cf-fb01-4430-963a-735ddc6b87f4\"},\"authorization\":{\"href\":\"/authorizations/efb04d12-4bb6-41c0-b030-45ff105641b0\"},\"refunds\":{\"href\":\"/authorizations/efb04d12-4bb6-41c0-b030-45ff105641b0/refunds\"}}}" +read 363 bytes +Conn close + ) + end + + def test_private_key + "-----BEGIN RSA PRIVATE KEY-----\nMIIBOwIBAAJBALYK0zmwuYkH3YWcFNLLddx5cwDxEY7Gi1xITuQqRrU4yD3uSw+J\nWYKknb4Tbndb6iEHY+e6gIGD+49TojnNeIUCAwEAAQJARyuYRRe4kcBHdPL+mSL+\nY0IAGkAlUyKAXYXPghidKD/v/oLrFaZWALGM2clv6UoYYpPnInSgbcud4sTcfeUm\nQQIhAN2JZ2qv0WGcbIopBpwpQ5jDxMGVkmkVVUEWWABGF8+pAiEA0lySxTELZm8b\nGx9UEDRghN+Qv/OuIKFldu1Ba4f8W30CIQCaQFIBtunTTVdF28r+cLzgYW9eWwbW\npEP4TdZ4WlW6AQIhAMDCTUdeUpjxlH/87BXROORozAXocBW8bvJUI486U5ctAiAd\nInviQqJd1KTGRDmWIGrE5YACVmW2JSszD9t5VKxkAA==\n-----END RSA PRIVATE KEY-----" + end + + def failed_purchase_response + failed_ch_response + end + + def successful_authorize_response + { + 'id' => '84412a34-fa29-4369-a098-0165a80e8fda', + 'status' => { + 'code' => 20000 + }, + 'processed_at' => '2014-07-09T09:53:41+00:00', + '_links' => { + 'captures' => { 'href' => '/authorizations/84412a34-fa29-4369-a098-0165a80e8fda/captures' } + } + }.to_json + end + + def failed_authorize_response + failed_ch_response + end + + def successful_capture_response + { + 'id' => 'd8e92a70-3030-4d4d-8ad2-684b230c1bed', + 'status' => { + 'code' => 20000 + }, + 'processed_at' => '2014-07-09T11:47:28+00:00', + 'amount' => 1000, + '_links' => { + 'authorization' => { + 'href' => '/authorizations/84412a34-fa29-4369-a098-0165a80e8fda' + }, + 'refunds' => { + 'href' => '/authorizations/84412a34-fa29-4369-a098-0165a80e8fda/refunds' + } + } + }.to_json + end + + def failed_capture_response + failed_ch_response + end + + def successful_refund_response + { + 'id' => 'f04c0872-47ce-4683-8d8c-e154221bba14', + 'status' => { + 'code' => 20000 + }, + 'processed_at' => '2014-07-09T11:57:58+00:00', + 'amount' => 500, + '_links' => { + 'authorization' => { 'href' => '/authorizations/84412a34-fa29-4369-a098-0165a80e8fda' } + } + }.to_json + end + + def failed_refund_response + failed_ch_response + end + + def successful_void_response + { + 'id' => '77d08c40-cfa9-42e3-993d-795f772b70a4', + 'status' => { + 'code' => 20000 + }, + 'processed_at' => '2015-08-21T16:44:48+00:00', + '_links' => { + 'self' => { + 'href' => '/authorizations/77d08c40-cfa9-42e3-993d-795f772b70a4' + }, + 'card' => { + 'href' => '/cards/27127636-0748-4df5-97fe-e58a0c29b618' + }, + 'captures' => { + 'href' => '/authorizations/77d08c40-cfa9-42e3-993d-795f772b70a4/captures' + }, + 'voids' => { 'href' => '/authorizations/77d08c40-cfa9-42e3-993d-795f772b70a4/voids'}, + 'refunds' => { 'href' => '/authorizations/77d08c40-cfa9-42e3-993d-795f772b70a4/refunds'} + } + } + end + + def failed_void_response + failed_ch_response + end + + def successful_store_response + { + 'id' => '58dabba0-e9ea-4133-8c38-bfa1028c1ed2', + 'status' => { + 'code'=> 20000 + }, + 'processed_at' => '2014-07-09T12:14:31+00:00', + 'last4' => '0004', + 'scheme' => 'mastercard', + '_links' => { + 'authorizations' => { 'href' => '/cards/58dabba0-e9ea-4133-8c38-bfa1028c1ed2/authorizations' }, + 'credits'=> { 'href' => '/cards/58dabba0-e9ea-4133-8c38-bfa1028c1ed2/credits' } + } + }.to_json + end + + def failed_store_response + failed_ch_response + end + + def failed_ch_response + { 'status' => { 'code' => 40000, 'message' => 'General input error' }}.to_json + end + +end diff --git a/test/unit/gateways/commercegate_test.rb b/test/unit/gateways/commercegate_test.rb new file mode 100644 index 00000000000..eeb2f2e13ea --- /dev/null +++ b/test/unit/gateways/commercegate_test.rb @@ -0,0 +1,129 @@ +require 'test_helper' + +class CommercegateTest < Test::Unit::TestCase + def setup + @gateway = CommercegateGateway.new( + login: 'usrID', + password: 'usrPass', + site_id: '123', + offer_id: '321' + ) + + @credit_card = credit_card + + @amount = 1000 + + @options = { + address: address + } + end + + def test_successful_authorize + @gateway.expects(:ssl_post).returns(successful_authorize_response) + assert response = @gateway.authorize(@amount, @credit_card, @options) + assert_instance_of Response, response + assert_success response + assert_equal '100130291387', response.authorization + assert_equal 'U', response.avs_result['code'] + assert_equal 'S', response.cvv_result['code'] + end + + def test_successful_capture + @gateway.expects(:ssl_post).returns(successful_capture_response) + assert response = @gateway.capture(@amount, '100130291387', @options) + assert_instance_of Response, response + assert_success response + assert_equal '100130291402', response.authorization + assert_equal '10.00', response.params['amount'] + assert_equal 'EUR', response.params['currencyCode'] + end + + def test_successful_purchase + @gateway.expects(:ssl_post).returns(successful_purchase_response) + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_instance_of Response, response + assert_success response + assert_equal '100130291412', response.authorization + assert_equal 'U', response.avs_result['code'] + assert_equal 'S', response.cvv_result['code'] + assert_equal 'rdkhkRXjPVCXf5jU2Zz5NCcXBihGuaNz', response.params['token'] + end + + def test_successful_refund + @gateway.expects(:ssl_post).returns(successful_refund_response) + assert response = @gateway.refund(@amount, '100130291387', @options) + assert_instance_of Response, response + assert_success response + assert_equal '100130291425', response.authorization + assert_equal '10.00', response.params['amount'] + assert_equal 'EUR', response.params['currencyCode'] + end + + def test_successful_void + @gateway.expects(:ssl_post).returns(successful_void_response) + assert response = @gateway.void('100130291412', @options) + assert_instance_of Response, response + assert_success response + assert_equal '100130425094', response.authorization + assert_equal '10.00', response.params['amount'] + assert_equal 'EUR', response.params['currencyCode'] + end + + def test_unsuccessful_authorize + @gateway.expects(:ssl_post).returns(failed_authorize_response_invalid_country) + assert response = @gateway.authorize(@amount, @credit_card, @options) + assert_instance_of Response, response + assert_failure response + assert_equal '-103', response.params['returnCode'] + end + + def test_unsuccessful_capture_empty_trans_id + @gateway.expects(:ssl_post).returns(failed_request_response) + assert response = @gateway.capture(@amount, '', @options) + assert_instance_of Response, response + assert_failure response + assert_equal '-125', response.params['returnCode'] + end + + def test_unsuccessful_capture_trans_id_not_found + @gateway.expects(:ssl_post).returns(failed_capture_response_invalid_trans_id) + assert response = @gateway.capture(@amount, '', @options) + assert_instance_of Response, response + assert_failure response + assert_equal '-121', response.params['returnCode'] + end + + private + + def failed_request_response + 'returnCode=-125&returnText=Invalid+operation' + end + + def successful_purchase_response + 'action=SALE&returnCode=0&returnText=Success&authCode=040404&avsCode=U&cvvCode=S&amount=10.00&currencyCode=EUR&transID=100130291412&token=rdkhkRXjPVCXf5jU2Zz5NCcXBihGuaNz' + end + + def successful_authorize_response + 'action=AUTH&returnCode=0&returnText=Success&authCode=726293&avsCode=U&cvvCode=S&amount=10.00&currencyCode=EUR&transID=100130291387&token=Hf4lDYcKdJsdX92WJ2CpNlEUdh05utsI' + end + + def failed_authorize_response_invalid_country + 'action=AUTH&returnCode=-103&returnText=Invalid+country' + end + + def successful_capture_response + 'action=CAPTURE&returnCode=0&returnText=Success&amount=10.00&currencyCode=EUR&transID=100130291402' + end + + def failed_capture_response_invalid_trans_id + 'action=CAPTURE&returnCode=-121&returnText=Previous+transaction+not+found' + end + + def successful_refund_response + 'action=REFUND&returnCode=0&returnText=Success&amount=10.00&currencyCode=EUR&transID=100130291425' + end + + def successful_void_response + 'action=VOID_AUTH&returnCode=0&returnText=Success&amount=10.00&currencyCode=EUR&transID=100130425094' + end +end diff --git a/test/unit/gateways/conekta_test.rb b/test/unit/gateways/conekta_test.rb new file mode 100644 index 00000000000..215a50b1ba3 --- /dev/null +++ b/test/unit/gateways/conekta_test.rb @@ -0,0 +1,415 @@ +require 'test_helper' + +class ConektaTest < Test::Unit::TestCase + include CommStub + + def setup + @gateway = ConektaGateway.new(:key => 'key_eYvWV7gSDkNYXsmr') + + @amount = 300 + + @credit_card = ActiveMerchant::Billing::CreditCard.new( + :number => '4242424242424242', + :verification_value => '183', + :month => '01', + :year => '2018', + :first_name => 'Mario F.', + :last_name => 'Moreno Reyes' + ) + + @declined_card = ActiveMerchant::Billing::CreditCard.new( + :number => '4000000000000002', + :verification_value => '183', + :month => '01', + :year => '2018', + :first_name => 'Mario F.', + :last_name => 'Moreno Reyes' + ) + + @options = { + :device_fingerprint => '41l9l92hjco6cuekf0c7dq68v4', + :description => 'Blue clip', + :success_url => 'https://www.example.com/success', + :failure_url => 'https://www.example.com/failure', + :address1 => 'Rio Missisipi #123', + :address2 => 'Paris', + :city => 'Guerrero', + :country => 'Mexico', + :zip => '5555', + :customer => 'Mario Reyes', + :phone => '12345678', + :carrier => 'Estafeta' + } + end + + def test_successful_tokenized_purchase + @gateway.expects(:ssl_request).returns(successful_purchase_response) + assert response = @gateway.purchase(@amount, 'tok_xxxxxxxxxxxxxxxx', @options) + assert_instance_of Response, response + assert_success response + assert_equal nil, response.message + assert response.test? + end + + def test_successful_purchase + @gateway.expects(:ssl_request).returns(successful_purchase_response) + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_instance_of Response, response + assert_success response + assert_equal nil, response.message + assert response.test? + end + + def test_unsuccessful_purchase + @gateway.expects(:ssl_request).returns(failed_purchase_response) + assert response = @gateway.purchase(@amount, @declined_card, @options) + assert_failure response + assert response.test? + end + + def test_successful_purchase_with_installments + response = stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, @credit_card, @options.merge({monthly_installments: '3'})) + end.check_request do |method, endpoint, data, headers| + assert_match %r{monthly_installments=3}, data + end.respond_with(successful_purchase_response) + + assert_success response + assert response.test? + end + + def test_unsuccessful_refund + @gateway.expects(:ssl_request).returns(failed_refund_response) + assert response = @gateway.refund(@amount, '1', @options) + assert_failure response + assert response.test? + end + + def test_successful_authorize + @gateway.expects(:ssl_request).returns(successful_authorize_response) + assert response = @gateway.authorize(@amount, @credit_card, @options) + assert_success response + assert_instance_of Response, response + assert_equal nil, response.message + assert response.test? + end + + def test_successful_void + @gateway.expects(:ssl_request).returns(successful_authorize_response) + assert response = @gateway.authorize(@amount, @credit_card, @options) + assert_success response + assert_instance_of Response, response + assert_equal nil, response.message + assert response.test? + + @gateway.expects(:ssl_request).returns(successful_void_response) + assert response = @gateway.void(successful_authorize_response['id']) + assert_success response + assert_instance_of Response, response + assert_equal nil, response.message + assert response.test? + end + + def test_unsuccessful_void + @gateway.expects(:ssl_request).returns(successful_authorize_response) + assert response = @gateway.authorize(@amount, @credit_card, @options) + assert_success response + assert_instance_of Response, response + assert_equal nil, response.message + assert response.test? + + @gateway.expects(:ssl_request).returns(failed_void_response) + assert response = @gateway.void(successful_authorize_response['id']) + assert_failure response + assert response.test? + end + + def test_unsuccessful_authorize + @gateway.expects(:ssl_request).returns(failed_authorize_response) + assert response = @gateway.authorize(@amount, @declined_card, @options) + assert_failure response + assert response.test? + end + + def test_unsuccessful_capture + @gateway.expects(:ssl_request).returns(failed_purchase_response) + assert response = @gateway.capture(@amount, '1', @options) + assert_failure response + assert response.test? + end + + def test_invalid_key + gateway = ConektaGateway.new(:key => 'invalid_token') + gateway.expects(:ssl_request).returns(failed_login_response) + assert response = gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert response.test? + end + + def test_adds_application_and_meta_headers + application = { + name: 'app', + version: '1.0', + url: 'https://example.com' + } + + response = stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, 'tok_xxxxxxxxxxxxxxxx', @options.merge(application: application, meta: {its_so_meta: 'even this acronym'})) + end.check_request do |method, endpoint, data, headers| + assert_match(/\"application\"/, headers['X-Conekta-Client-User-Agent']) + assert_match(/\"name\":\"app\"/, headers['X-Conekta-Client-User-Agent']) + assert_match(/\"version\":\"1.0\"/, headers['X-Conekta-Client-User-Agent']) + assert_match(/\"url\":\"https:\/\/example.com\"/, headers['X-Conekta-Client-User-Agent']) + assert_match(/\"its_so_meta\":\"even this acronym\"/, headers['X-Conekta-Client-User-Metadata']) + end.respond_with(successful_purchase_response) + + assert_success response + end + + def test_transcript_scrubbing + assert_equal scrubbed_transcript, @gateway.scrub(transcript) + end + + private + + def successful_purchase_response + { + 'id' => '521b859fcfc26c0f180002d9', + 'livemode' => false, + 'created_at' => 1377535391, + 'status' => 'pre_authorized', + 'currency' => 'MXN', + 'description' => 'Blue clip', + 'reference_id' => nil, + 'failure_code' => nil, + 'failure_message' => nil, + 'object' => 'charge', + 'amount' => 300, + 'processed_at' => nil, + 'fee' => 348, + 'card' => { + 'name' => 'Mario Reyes', + 'exp_month' => '01', + 'exp_year' => '18', + 'street2' => 'Paris', + 'street3' => 'nil', + 'city' => 'Guerrero', + 'zip' => '5555', + 'country' => 'Mexico', + 'brand' => 'VISA', + 'last4' => '1111', + 'object' => 'card', + 'fraud_response' => '3d_secure_required', + 'redirect_form' => { + 'url' => 'https => //eps.banorte.com/secure3d/Solucion3DSecure.htm', + 'action' => 'POST', + 'attributes' => { + 'MerchantId' => '7376961', + 'MerchantName' => 'GRUPO CONEKTAME', + 'MerchantCity' => 'EstadodeMexico', + 'Cert3D' => '03', + 'ClientId' => '60518', + 'Name' => '7376962', + 'Password' => 'fgt563j', + 'TransType' => 'PreAuth', + 'Mode' => 'Y', + 'E1' => 'qKNKjndV9emCxuKE1G4z', + 'E2' => '521b859fcfc26c0f180002d9', + 'E3' => 'Y', + 'ResponsePath' => 'https => //eps.banorte.com/RespuestaCC.jsp', + 'CardType' => 'VISA', + 'Card' => '4111111111111111', + 'Cvv2Indicator' => '1', + 'Cvv2Val' => '183', + 'Expires' => '01/18', + 'Total' => '3.0', + 'ForwardPath' => 'http => //localhost => 3000/charges/banorte_3d_secure_response', + 'auth_token' => 'qKNKjndV9emCxuKE1G4z' + } + } + } + }.to_json + end + + def failed_purchase_response + { + 'message' => 'The card was declined', + 'type' => 'invalid_parameter_error', + 'param' => '' + }.to_json + end + + def failed_bank_purchase_response + { + 'message' => 'The minimum purchase is 15 MXN pesos for bank transfer payments', + 'type' => 'invalid_parameter_error', + 'param' => '' + }.to_json + end + + def failed_refund_response + { + 'object' => 'error', + 'type' => 200, + 'message' => 'The charge does not exist or it is not suitable for this operation' + }.to_json + end + + def failed_void_response + { + 'object' => 'error', + 'type' => 200, + 'message' => 'The charge does not exist or it is not suitable for this operation' + }.to_json + end + + def successful_authorize_response + { + 'id' => '521b859fcfc26c0f180002d9', + 'livemode' => false, + 'created_at' => 1377535391, + 'status' => 'pre_authorized', + 'currency' => 'MXN', + 'description' => 'Blue clip', + 'reference_id' => nil, + 'failure_code' => nil, + 'failure_message' => nil, + 'object' => 'charge', + 'amount' => 300, + 'processed_at' => nil, + 'fee' => 348, + 'card' => { + 'name' => 'Mario Reyes', + 'exp_month' => '01', + 'exp_year' => '18', + 'street2' => 'Paris', + 'street3' => 'nil', + 'city' => 'Guerrero', + 'zip' => '5555', + 'country' => 'Mexico', + 'brand' => 'VISA', + 'last4' => '1111', + 'object' => 'card', + 'fraud_response' => '3d_secure_required', + 'redirect_form' => { + 'url' => 'https => //eps.banorte.com/secure3d/Solucion3DSecure.htm', + 'action' => 'POST', + 'attributes' => { + 'MerchantId' => '7376961', + 'MerchantName' => 'GRUPO CONEKTAME', + 'MerchantCity' => 'EstadodeMexico', + 'Cert3D' => '03', + 'ClientId' => '60518', + 'Name' => '7376962', + 'Password' => 'fgt563j', + 'TransType' => 'PreAuth', + 'Mode' => 'Y', + 'E1' => 'qKNKjndV9emCxuKE1G4z', + 'E2' => '521b859fcfc26c0f180002d9', + 'E3' => 'Y', + 'ResponsePath' => 'https => //eps.banorte.com/RespuestaCC.jsp', + 'CardType' => 'VISA', + 'Card' => '4111111111111111', + 'Cvv2Indicator' => '1', + 'Cvv2Val' => '183', + 'Expires' => '01/18', + 'Total' => '3.0', + 'ForwardPath' => 'http => //localhost => 3000/charges/banorte_3d_secure_response', + 'auth_token' => 'qKNKjndV9emCxuKE1G4z' + } + } + } + }.to_json + end + + def successful_void_response + { + 'id' => '521b859fcfc26c0f180002d9', + 'livemode' => false, + 'created_at' => 1377535391, + 'status' => 'voided', + 'currency' => 'MXN', + 'description' => 'Blue clip', + 'reference_id' => nil, + 'failure_code' => nil, + 'failure_message' => nil, + 'object' => 'charge', + 'amount' => 300, + 'processed_at' => nil, + 'fee' => 348, + 'card' => { + 'name' => 'Mario Reyes', + 'exp_month' => '01', + 'exp_year' => '18', + 'street2' => 'Paris', + 'street3' => 'nil', + 'city' => 'Guerrero', + 'zip' => '5555', + 'country' => 'Mexico', + 'brand' => 'VISA', + 'last4' => '1111', + 'object' => 'card', + 'fraud_response' => '3d_secure_required', + 'redirect_form' => { + 'url' => 'https => //eps.banorte.com/secure3d/Solucion3DSecure.htm', + 'action' => 'POST', + 'attributes' => { + 'MerchantId' => '7376961', + 'MerchantName' => 'GRUPO CONEKTAME', + 'MerchantCity' => 'EstadodeMexico', + 'Cert3D' => '03', + 'ClientId' => '60518', + 'Name' => '7376962', + 'Password' => 'fgt563j', + 'TransType' => 'PreAuth', + 'Mode' => 'Y', + 'E1' => 'qKNKjndV9emCxuKE1G4z', + 'E2' => '521b859fcfc26c0f180002d9', + 'E3' => 'Y', + 'ResponsePath' => 'https => //eps.banorte.com/RespuestaCC.jsp', + 'CardType' => 'VISA', + 'Card' => '4111111111111111', + 'Cvv2Indicator' => '1', + 'Cvv2Val' => '183', + 'Expires' => '01/18', + 'Total' => '3.0', + 'ForwardPath' => 'http => //localhost => 3000/charges/banorte_3d_secure_response', + 'auth_token' => 'qKNKjndV9emCxuKE1G4z' + } + } + } + }.to_json + end + + def failed_authorize_response + { + 'message' => 'The card was declined', + 'type' => 'invalid_parameter_error', + 'param' => '' + }.to_json + end + + def failed_capture_response + { + 'object' => 'error', + 'type' => 200, + 'message' => 'The charge does not exist or it is not suitable for this operation' + }.to_json + end + + def failed_login_response + { + 'object' => 'error', + 'type' => 'authentication_error', + 'message' => 'Unrecognized authentication token' + }.to_json + end + + def transcript + 'card%5Baddress%5D%5Bzip%5D=5555&card%5Bcvc%5D=183&card%5Bexp_month%5D=01&card%5Bexp_year%5D=18&card%5Bname%5D=Mario+F.+Moreno+Reyes&card%5Bnumber%5D=4242424242424242&currency=mxn&description=Blue+clip&details%5Bbilling_address%5D%5Bcity%5D=Guerrero' + end + + def scrubbed_transcript + 'card%5Baddress%5D%5Bzip%5D=5555&card%5Bcvc%5D=[FILTERED]&card%5Bexp_month%5D=01&card%5Bexp_year%5D=18&card%5Bname%5D=Mario+F.+Moreno+Reyes&card%5Bnumber%5D=[FILTERED]&currency=mxn&description=Blue+clip&details%5Bbilling_address%5D%5Bcity%5D=Guerrero' + end +end diff --git a/test/unit/gateways/creditcall_test.rb b/test/unit/gateways/creditcall_test.rb new file mode 100644 index 00000000000..146606ff742 --- /dev/null +++ b/test/unit/gateways/creditcall_test.rb @@ -0,0 +1,233 @@ +require 'test_helper' + +class CreditcallTest < Test::Unit::TestCase + include CommStub + + def setup + @gateway = CreditcallGateway.new(terminal_id: 'login', transaction_key: 'password') + @credit_card = credit_card + @amount = 100 + + @options = { + order_id: '1', + billing_address: address, + description: 'Store Purchase' + } + end + + def test_successful_purchase + @gateway.expects(:ssl_post).times(2).returns(successful_authorize_response, successful_capture_response) + + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert response.test? + end + + def test_failed_purchase + @gateway.expects(:ssl_post).returns(failed_purchase_response) + + response = @gateway.purchase(@amount, @credit_card, @options) + assert_failure response + end + + def test_successful_authorize + @gateway.expects(:ssl_post).returns(successful_authorize_response) + + response = @gateway.authorize(@amount, @credit_card, @options) + assert_success response + + assert_equal '86dd2c0f-b742-e511-b302-00505692354f', response.authorization + end + + def test_failed_authorize + @gateway.expects(:ssl_post).returns(failed_authorize_response) + + response = @gateway.authorize(@amount, @credit_card, @options) + assert_failure response + end + + def test_successful_capture + @gateway.expects(:ssl_post).returns(successful_capture_response) + + response = @gateway.capture(@amount, 'bc8e3abe-b842-e511-b302-00505692354f', @options) + assert_success response + end + + def test_failed_capture + @gateway.expects(:ssl_post).returns(failed_capture_response) + + response = @gateway.capture(@amount, '', @options) + assert_failure response + end + + def test_successful_refund + @gateway.expects(:ssl_post).returns(successful_refund_response) + + response = @gateway.refund(@amount, '77e55712-ba42-e511-b302-00505692354f', @options) + assert_success response + end + + def test_failed_refund + @gateway.expects(:ssl_post).returns(failed_refund_response) + + response = @gateway.refund(@amount, '', @options) + assert_failure response + end + + def test_successful_void + @gateway.expects(:ssl_post).returns(successful_void_response) + + response = @gateway.void('e5b1b672-ba42-e511-b302-00505692354f', @options) + assert_success response + end + + def test_failed_void + @gateway.expects(:ssl_post).returns(failed_void_response) + + response = @gateway.void('', @options) + assert_failure response + end + + def test_successful_verify + @gateway.expects(:ssl_post).times(2).returns(successful_authorize_response, successful_void_response) + + response = @gateway.verify(@credit_card, @options) + assert_success response + end + + def test_successful_verify_with_failed_void + @gateway.expects(:ssl_post).times(2).returns(successful_authorize_response, failed_void_response) + + response = @gateway.verify(@credit_card, @options) + assert_success response + end + + def test_failed_verify + @gateway.expects(:ssl_post).returns(failed_authorize_response) + + response = @gateway.verify(@credit_card, @options) + assert_failure response + end + + def test_verification_value_sent + stub_comms do + @gateway.authorize(@amount, @credit_card, @options) + end.check_request do |endpoint, data, headers| + assert_match(%r(<CSC>123</CSC>)m, data) + end.respond_with(successful_authorize_response) + end + + def test_verification_value_not_sent + @credit_card.verification_value = ' ' + stub_comms do + @gateway.authorize(@amount, @credit_card, @options) + end.check_request do |endpoint, data, headers| + assert_no_match(/CSC/, data) + end.respond_with(successful_authorize_response) + end + + def test_options_add_avs_additional_verification_fields + stub_comms do + @gateway.authorize(@amount, @credit_card, @options) + end.check_request do |endpoint, data, headers| + assert_no_match(/AdditionalVerification/, data) + end.respond_with(successful_authorize_response) + + stub_comms do + @gateway.authorize(@amount, @credit_card, @options.merge(verify_zip: 'false', verify_address: 'false')) + end.check_request do |endpoint, data, headers| + assert_no_match(/AdditionalVerification/, data) + end.respond_with(successful_authorize_response) + + stub_comms do + @gateway.authorize(@amount, @credit_card, @options.merge(verify_zip: 'true', verify_address: 'true')) + end.check_request do |endpoint, data, headers| + assert_match(/<AdditionalVerification>\n <Zip>K1C2N6<\/Zip>\n <Address>/, data) + end.respond_with(successful_authorize_response) + + stub_comms do + @gateway.authorize(@amount, @credit_card, @options.merge(verify_zip: 'true', verify_address: 'false')) + end.check_request do |endpoint, data, headers| + assert_match(/ <AdditionalVerification>\n <Zip>K1C2N6<\/Zip>\n <\/AdditionalVerification>\n/, data) + end.respond_with(successful_authorize_response) + end + + def test_scrub + assert @gateway.supports_scrubbing? + assert_equal @gateway.scrub(pre_scrubbed), post_scrubbed + end + + private + + def pre_scrubbed + %q( + <?xml version=\"1.0\"?>\n<Request type=\"CardEaseXML\" version=\"1.0.0\">\n <TransactionDetails>\n <MessageType>Auth</MessageType>\n <Amount unit=\"Minor\">100</Amount>\n </TransactionDetails>\n <TerminalDetails>\n <TerminalID>99961426</TerminalID>\n <TransactionKey>9drdRU9wJ65SNRw3</TransactionKey>\n <Software version=\"SoftwareVersion\">SoftwareName</Software>\n </TerminalDetails>\n <CardDetails>\n <Manual type=\"cnp\">\n <PAN>4000100011112224</PAN>\n <ExpiryDate format=\"yyMM\">1609</ExpiryDate>\n <CSC>123</CSC>\n </Manual>\n </CardDetails>\n</Request>\n + ) + end + + def post_scrubbed + %q( + <?xml version=\"1.0\"?>\n<Request type=\"CardEaseXML\" version=\"1.0.0\">\n <TransactionDetails>\n <MessageType>Auth</MessageType>\n <Amount unit=\"Minor\">100</Amount>\n </TransactionDetails>\n <TerminalDetails>\n <TerminalID>99961426</TerminalID>\n <TransactionKey>[FILTERED]</TransactionKey>\n <Software version=\"SoftwareVersion\">SoftwareName</Software>\n </TerminalDetails>\n <CardDetails>\n <Manual type=\"cnp\">\n <PAN>[FILTERED]</PAN>\n <ExpiryDate format=\"yyMM\">1609</ExpiryDate>\n <CSC>[FILTERED]</CSC>\n </Manual>\n </CardDetails>\n</Request>\n + ) + end + + def successful_purchase_response + %( + <?xml version=\"1.0\" encoding=\"utf-8\"?><Response type=\"CardEaseXML\" version=\"1.0.0\"><TransactionDetails><CardEaseReference>0999da90-b342-e511-b302-00505692354f</CardEaseReference><LocalDateTime format=\"yyyyMMddHHmmss\">20150814143753</LocalDateTime><UTC format=\"yyyyMMddHHmmss\">20150814183753</UTC></TransactionDetails><Result><LocalResult>0</LocalResult><AuthorisationEntity>Unknown</AuthorisationEntity></Result><CardDetails><CardReference>c2c5fa63-3dd1-da11-8531-01422187e37</CardReference><CardHash>8CtuNPQnryhFt6amPWtp6PLZYXI=</CardHash><PAN>341111xxxxx1002</PAN><ExpiryDate format=”yyMM”>2012</ExpiryDate><CardScheme><Description>AMEX</Description></CardScheme></CardDetails></Response> + ) + end + + def failed_purchase_response + %( + <?xml version=\"1.0\" encoding=\"utf-8\"?><Response type=\"CardEaseXML\" version=\"1.0.0\"><TransactionDetails><CardEaseReference>55ec5427-b642-e511-b302-00505692354f</CardEaseReference><LocalDateTime format=\"yyyyMMddHHmmss\">20150814145622</LocalDateTime><UTC format=\"yyyyMMddHHmmss\">20150814185622</UTC></TransactionDetails><Result><LocalResult>1</LocalResult><AuthorisationEntity>Unknown</AuthorisationEntity><Errors><Error code=\"1001\">ExpiredCard</Error></Errors></Result><CardDetails><CardReference>2468f656-9242-e511-ab11-002219649f24</CardReference><CardHash>WSknCJJIvbog334uwCNWUKE9ZbI=</CardHash><PAN>xxxxxxxxxxxx2220</PAN><ExpiryDate format=\"yyMM\">1609</ExpiryDate><CardScheme><Description>VISA</Description></CardScheme></CardDetails></Response>" + ) + end + + def successful_authorize_response + %( + <?xml version=\"1.0\" encoding=\"utf-8\"?><Response type=\"CardEaseXML\" version=\"1.0.0\"><TransactionDetails><CardEaseReference>86dd2c0f-b742-e511-b302-00505692354f</CardEaseReference><LocalDateTime format=\"yyyyMMddHHmmss\">20150814150251</LocalDateTime><UTC format=\"yyyyMMddHHmmss\">20150814190251</UTC></TransactionDetails><Result><LocalResult>0</LocalResult><AuthCode>5ECC4B</AuthCode><AuthorisationEntity>Unknown</AuthorisationEntity><AmountOnlineApproved unit=\"major\">1.00</AmountOnlineApproved></Result><CardDetails><CardReference>ed79bf2f-6d3c-e511-ab11-002219649f24</CardReference><CardHash>YO5CqO1QQbHbMaFRuSV2vVq7uqQ=</CardHash><PAN>xxxxxxxxxxxx2224</PAN><ExpiryDate format=\"yyMM\">1609</ExpiryDate><CardScheme><Description>VISA</Description></CardScheme><ICC type=\"EMV\"><ICCTag tagid=\"0x9F02\">000000000100</ICCTag></ICC></CardDetails></Response> + ) + end + + def failed_authorize_response + %( + <?xml version=\"1.0\" encoding=\"utf-8\"?><Response type=\"CardEaseXML\" version=\"1.0.0\"><TransactionDetails><CardEaseReference>877c147c-b742-e511-b302-00505692354f</CardEaseReference><LocalDateTime format=\"yyyyMMddHHmmss\">20150814150556</LocalDateTime><UTC format=\"yyyyMMddHHmmss\">20150814190556</UTC></TransactionDetails><Result><LocalResult>1</LocalResult><AuthorisationEntity>Unknown</AuthorisationEntity><Errors><Error code=\"1001\">ExpiredCard</Error></Errors></Result><CardDetails><CardReference>2468f656-9242-e511-ab11-002219649f24</CardReference><CardHash>WSknCJJIvbog334uwCNWUKE9ZbI=</CardHash><PAN>xxxxxxxxxxxx2220</PAN><ExpiryDate format=\"yyMM\">1609</ExpiryDate><CardScheme><Description>VISA</Description></CardScheme></CardDetails></Response> + ) + end + + def successful_capture_response + %( + <?xml version=\"1.0\" encoding=\"utf-8\"?><Response type=\"CardEaseXML\" version=\"1.0.0\"><TransactionDetails><CardEaseReference>607c64ca-b742-e511-b302-00505692354f</CardEaseReference><LocalDateTime format=\"yyyyMMddHHmmss\">20150814150811</LocalDateTime><UTC format=\"yyyyMMddHHmmss\">20150814190811</UTC></TransactionDetails><Result><LocalResult>0</LocalResult><AuthorisationEntity>Unknown</AuthorisationEntity></Result></Response> + ) + end + + def failed_capture_response + %( + <?xml version=\"1.0\" encoding=\"utf-8\"?><Response type=\"CardEaseXML\" version=\"1.0.0\"><TransactionDetails><CardEaseReference>c4e68fe7-b742-e511-b302-00505692354f</CardEaseReference><LocalDateTime format=\"yyyyMMddHHmmss\">20150814150903</LocalDateTime><UTC format=\"yyyyMMddHHmmss\">20150814190903</UTC></TransactionDetails><Result><LocalResult>1</LocalResult><AuthorisationEntity>Unknown</AuthorisationEntity><Errors><Error code=\"2101\">CardEaseReferenceInvalid</Error></Errors></Result></Response> + ) + end + + def successful_refund_response + %( + <?xml version=\"1.0\" encoding=\"utf-8\"?><Response type=\"CardEaseXML\" version=\"1.0.0\"><TransactionDetails><CardEaseReference>c8c758b9-b942-e511-b302-00505692354f</CardEaseReference><LocalDateTime format=\"yyyyMMddHHmmss\">20150814152200</LocalDateTime><UTC format=\"yyyyMMddHHmmss\">20150814192200</UTC></TransactionDetails><Result><LocalResult>0</LocalResult><AuthCode>AE9111</AuthCode><AuthorisationEntity>Unknown</AuthorisationEntity></Result><CardDetails><CardReference>ed79bf2f-6d3c-e511-ab11-002219649f24</CardReference><CardHash>zLVjUTo7jcI8Z+8hKKd5nqqn4a0=</CardHash><PAN>400010xxxxxx2224</PAN><ExpiryDate format=\"yyMM\">1609</ExpiryDate></CardDetails></Response> + ) + end + + def failed_refund_response + %( + <?xml version=\"1.0\" encoding=\"utf-8\"?><Response type=\"CardEaseXML\" version=\"1.0.0\"><TransactionDetails><CardEaseReference>0732d424-ba42-e511-b302-00505692354f</CardEaseReference><LocalDateTime format=\"yyyyMMddHHmmss\">20150814152505</LocalDateTime><UTC format=\"yyyyMMddHHmmss\">20150814192505</UTC></TransactionDetails><Result><LocalResult>1</LocalResult><AuthorisationEntity>Unknown</AuthorisationEntity><Errors><Error code=\"2101\">CardEaseReferenceInvalid</Error></Errors></Result></Response> + ) + end + + def successful_void_response + %( + <?xml version=\"1.0\" encoding=\"utf-8\"?><Response type=\"CardEaseXML\" version=\"1.0.0\"><TransactionDetails><CardEaseReference>170c0e63-ba42-e511-b302-00505692354f</CardEaseReference><LocalDateTime format=\"yyyyMMddHHmmss\">20150814152646</LocalDateTime><UTC format=\"yyyyMMddHHmmss\">20150814192646</UTC></TransactionDetails><Result><LocalResult>0</LocalResult><AuthorisationEntity>Unknown</AuthorisationEntity></Result></Response> + ) + end + + def failed_void_response + %( + <?xml version=\"1.0\" encoding=\"utf-8\"?><Response type=\"CardEaseXML\" version=\"1.0.0\"><TransactionDetails><CardEaseReference>e5b1b672-ba42-e511-b302-00505692354f</CardEaseReference><LocalDateTime format=\"yyyyMMddHHmmss\">20150814152716</LocalDateTime><UTC format=\"yyyyMMddHHmmss\">20150814192716</UTC></TransactionDetails><Result><LocalResult>1</LocalResult><AuthorisationEntity>Unknown</AuthorisationEntity><Errors><Error code=\"2101\">CardEaseReferenceInvalid</Error></Errors></Result></Response> + ) + end +end diff --git a/test/unit/gateways/credorax_test.rb b/test/unit/gateways/credorax_test.rb new file mode 100644 index 00000000000..f353c0297fb --- /dev/null +++ b/test/unit/gateways/credorax_test.rb @@ -0,0 +1,375 @@ +require 'test_helper' + +class CredoraxTest < Test::Unit::TestCase + include CommStub + + def setup + @gateway = CredoraxGateway.new(merchant_id: 'login', cipher_key: 'password') + @credit_card = credit_card + @amount = 100 + + @options = { + billing_address: address, + description: 'Store Purchase' + } + end + + def test_successful_purchase + response = stub_comms do + @gateway.purchase(@amount, @credit_card) + end.check_request do |endpoint, data, headers| + assert_no_match(/i8=sample-eci%3Asample-cavv%3Asample-xid/, data) + end.respond_with(successful_purchase_response) + + assert_success response + + assert_equal '8a82944a5351570601535955efeb513c;006596;02617cf5f02ccaed239b6521748298c5;purchase', response.authorization + assert response.test? + end + + def test_failed_purchase + response = stub_comms do + @gateway.purchase(@amount, @credit_card) + end.respond_with(failed_purchase_response) + + assert_failure response + assert_equal 'Transaction not allowed for cardholder', response.message + assert response.test? + end + + def test_successful_authorize_and_capture + response = stub_comms do + @gateway.authorize(@amount, @credit_card) + end.respond_with(successful_authorize_response) + + assert_success response + assert_equal '8a829449535154bc0153595952a2517a;006597;90f7449d555f7bed0a2c5d780475f0bf;authorize', response.authorization + + capture = stub_comms do + @gateway.capture(@amount, response.authorization) + end.check_request do |endpoint, data, headers| + assert_match(/8a829449535154bc0153595952a2517a/, data) + end.respond_with(successful_capture_response) + + assert_success capture + end + + def test_failed_authorize + response = stub_comms do + @gateway.authorize(@amount, @credit_card) + end.respond_with(failed_authorize_response) + + assert_failure response + assert_equal 'Transaction not allowed for cardholder', response.message + assert response.test? + end + + def test_failed_capture + response = stub_comms do + @gateway.capture(100, '') + end.respond_with(failed_capture_response) + + assert_failure response + end + + def test_successful_void + response = stub_comms do + @gateway.purchase(@amount, @credit_card) + end.respond_with(successful_authorize_response) + + assert_success response + assert_equal '8a829449535154bc0153595952a2517a;006597;90f7449d555f7bed0a2c5d780475f0bf;purchase', response.authorization + + void = stub_comms do + @gateway.void(response.authorization) + end.check_request do |endpoint, data, headers| + assert_match(/8a829449535154bc0153595952a2517a/, data) + end.respond_with(successful_void_response) + + assert_success void + end + + def test_failed_void + response = stub_comms do + @gateway.void('5d53a33d960c46d00f5dc061947d998c') + end.check_request do |endpoint, data, headers| + assert_match(/5d53a33d960c46d00f5dc061947d998c/, data) + end.respond_with(failed_void_response) + + assert_failure response + end + + def test_successful_refund + response = stub_comms do + @gateway.purchase(@amount, @credit_card) + end.respond_with(successful_purchase_response) + + assert_success response + assert_equal '8a82944a5351570601535955efeb513c;006596;02617cf5f02ccaed239b6521748298c5;purchase', response.authorization + + refund = stub_comms do + @gateway.refund(@amount, response.authorization) + end.check_request do |endpoint, data, headers| + assert_match(/8a82944a5351570601535955efeb513c/, data) + end.respond_with(successful_refund_response) + + assert_success refund + end + + def test_failed_refund + response = stub_comms do + @gateway.refund(nil, '') + end.respond_with(failed_refund_response) + + assert_failure response + end + + def test_successful_credit + response = stub_comms do + @gateway.credit(@amount, @credit_card) + end.respond_with(successful_credit_response) + + assert_success response + + assert_equal '8a82944a53515706015359604c135301;;868f8b942fae639d28e27e8933d575d4;credit', response.authorization + assert response.test? + end + + def test_failed_credit + response = stub_comms do + @gateway.credit(@amount, @credit_card) + end.respond_with(failed_credit_response) + + assert_failure response + assert_equal 'Transaction not allowed for cardholder', response.message + assert response.test? + end + + def test_successful_verify + response = stub_comms do + @gateway.verify(@credit_card) + end.respond_with(successful_authorize_response, failed_void_response) + assert_success response + assert_equal 'Succeeded', response.message + end + + def test_failed_verify + response = stub_comms do + @gateway.verify(@credit_card) + end.respond_with(failed_authorize_response, successful_void_response) + assert_failure response + assert_equal 'Transaction not allowed for cardholder', response.message + end + + def test_empty_response_fails + response = stub_comms do + @gateway.purchase(@amount, @credit_card) + end.respond_with(empty_purchase_response) + + assert_failure response + assert_equal 'Unable to read error message', response.message + end + + def test_transcript_scrubbing + assert_equal scrubbed_transcript, @gateway.scrub(transcript) + end + + def test_adds_3d_secure_fields + options_with_3ds = @options.merge({eci: 'sample-eci', cavv: 'sample-cavv', xid: 'sample-xid'}) + + response = stub_comms do + @gateway.purchase(@amount, @credit_card, options_with_3ds) + end.check_request do |endpoint, data, headers| + assert_match(/i8=sample-eci%3Asample-cavv%3Asample-xid/, data) + end.respond_with(successful_purchase_response) + + assert_success response + + assert_equal '8a82944a5351570601535955efeb513c;006596;02617cf5f02ccaed239b6521748298c5;purchase', response.authorization + assert response.test? + end + + def test_defaults_3d_secure_cavv_field_to_none_if_not_present + options_with_3ds = @options.merge({eci: 'sample-eci', xid: 'sample-xid'}) + + response = stub_comms do + @gateway.purchase(@amount, @credit_card, options_with_3ds) + end.check_request do |endpoint, data, headers| + assert_match(/i8=sample-eci%3Anone%3Asample-xid/, data) + end.respond_with(successful_purchase_response) + + assert_success response + + assert_equal '8a82944a5351570601535955efeb513c;006596;02617cf5f02ccaed239b6521748298c5;purchase', response.authorization + assert response.test? + end + + def test_adds_3ds2_fields_via_normalized_hash + version = '2.0' + eci = '05' + cavv = '637574652070757070792026206b697474656e73' + ds_transaction_id = '97267598-FAE6-48F2-8083-C23433990FBC' + options_with_normalized_3ds = @options.merge( + three_d_secure: { + version: version, + eci: eci, + cavv: cavv, + ds_transaction_id: ds_transaction_id + } + ) + + stub_comms do + @gateway.purchase(@amount, @credit_card, options_with_normalized_3ds) + end.check_request do |endpoint, data, headers| + assert_match(/i8=#{eci}%3A#{cavv}%3Anone/, data) + assert_match(/3ds_version=#{version}/, data) + assert_match(/3ds_dstrxid=#{ds_transaction_id}/, data) + end.respond_with(successful_purchase_response) + end + + def test_adds_default_cavv_when_omitted_from_normalized_hash + version = '2.0' + eci = '05' + ds_transaction_id = '97267598-FAE6-48F2-8083-C23433990FBC' + options_with_normalized_3ds = @options.merge( + three_d_secure: { + version: version, + eci: eci, + ds_transaction_id: ds_transaction_id + } + ) + + stub_comms do + @gateway.purchase(@amount, @credit_card, options_with_normalized_3ds) + end.check_request do |endpoint, data, headers| + assert_match(/i8=#{eci}%3Anone%3Anone/, data) + assert_match(/3ds_version=#{version}/, data) + assert_match(/3ds_dstrxid=#{ds_transaction_id}/, data) + end.respond_with(successful_purchase_response) + end + + def test_adds_a9_field + options_with_3ds = @options.merge({transaction_type: '8'}) + + stub_comms do + @gateway.purchase(@amount, @credit_card, options_with_3ds) + end.check_request do |endpoint, data, headers| + assert_match(/a9=8/, data) + end.respond_with(successful_purchase_response) + end + + def test_adds_submerchant_id + @options[:submerchant_id] = '12345' + stub_comms do + @gateway.purchase(@amount, @credit_card, @options) + end.check_request do |endpoint, data, headers| + assert_match(/h3=12345/, data) + end.respond_with(successful_purchase_response) + end + + def test_supports_billing_descriptor + @options[:billing_descriptor] = 'abcdefghijkl' + stub_comms do + @gateway.purchase(@amount, @credit_card, @options) + end.check_request do |endpoint, data, headers| + assert_match(/i2=abcdefghijkl/, data) + end.respond_with(successful_purchase_response) + end + + private + + def successful_purchase_response + 'M=SPREE978&O=1&T=03%2F09%2F2016+03%3A05%3A16&V=413&a1=02617cf5f02ccaed239b6521748298c5&a2=2&a4=100&a9=6&z1=8a82944a5351570601535955efeb513c&z13=606944188282&z14=U&z15=100&z2=0&z3=Transaction+has+been+executed+successfully.&z4=006596&z5=0&z6=00&z9=X&K=057e123af2fba5a37b4df76a7cb5cfb6' + end + + def failed_purchase_response + 'M=SPREE978&O=1&T=03%2F09%2F2016+03%3A05%3A47&V=413&a1=92176aca194ceafdb4a679389b77f207&a2=2&a4=100&a9=6&z1=8a82944a535157060153595668fd5162&z13=606944188283&z15=100&z2=05&z3=Transaction+has+been+declined.&z5=0&z6=57&K=2d44820a5a907ff820f928696e460ce1' + end + + def successful_authorize_response + 'M=SPREE978&O=2&T=03%2F09%2F2016+03%3A08%3A58&V=413&a1=90f7449d555f7bed0a2c5d780475f0bf&a2=2&a4=100&a9=6&z1=8a829449535154bc0153595952a2517a&z13=606944188284&z14=U&z15=100&z2=0&z3=Transaction+has+been+executed+successfully.&z4=006597&z5=0&z6=00&z9=X&K=00effd2c80ab7ecd45b499c0bbea3d20' + end + + def failed_authorize_response + 'M=SPREE978&O=2&T=03%2F09%2F2016+03%3A10%3A02&V=413&a1=9bd85e23639ffcd5206f8e7fe4e3d365&a2=2&a4=100&a9=6&z1=8a829449535154bc0153595a4bb051ac&z13=606944188285&z15=100&z2=05&z3=Transaction+has+been+declined.&z5=0&z6=57&K=2fe3ee6b975d1e4ba542c1e7549056f6' + end + + def successful_capture_response + 'M=SPREE978&O=3&T=03%2F09%2F2016+03%3A09%3A03&V=413&a1=2a349969e0ed61fb0db59fc9f32d2fb3&a2=2&a4=100&g2=8a829449535154bc0153595952a2517a&g3=006597&g4=90f7449d555f7bed0a2c5d780475f0bf&z1=8a82944a535157060153595966ba51f9&z13=606944188284&z15=100&z2=0&z3=Transaction+has+been+executed+successfully.&z4=006597&z5=0&z6=00&K=4ad979199490a8d000302735220edfa6' + end + + def failed_capture_response + 'M=SPREE978&O=3&T=03%2F09%2F2016+03%3A10%3A33&V=413&a1=eed7c896e1355dc4007c0c8df44d5852&a2=2&a4=100&a5=EUR&b1=-&z1=1A-1&z2=-9&z3=2.+At+least+one+of+input+parameters+is+malformed.%3A+Parameter+%5Bg4%5D+cannot+be+empty.&K=8d1d8f2f9feeb7909aa3e6c428903d57' + end + + def successful_void_response + 'M=SPREE978&O=4&T=03%2F09%2F2016+03%3A11%3A11&V=413&a1=&a2=2&a4=100&g2=8a82944a535157060153595b484a524d&g3=006598&g4=0d600bf50198059dbe61979f8c28aab2&z1=8a829449535154bc0153595b57c351d2&z13=606944188287&z15=100&z2=0&z3=Transaction+has+been+executed+successfully.&z4=006598&z5=0&z6=00&K=e643b9e88b35fd69d5421b59c611a6c9' + end + + def failed_void_response + 'M=SPREE978&O=4&T=03%2F09%2F2016+03%3A11%3A37&V=413&a1=-&a2=2&a4=-&a5=-&b1=-&z1=1A-1&z2=-9&z3=2.+At+least+one+of+input+parameters+is+malformed.%3A+Parameter+%5Bg4%5D+cannot+be+empty.&K=1e6683cd7b1d01712f12ce7bfc9a5ad2' + end + + def successful_refund_response + 'M=SPREE978&O=5&T=03%2F09%2F2016+03%3A15%3A32&V=413&a1=b449bb41af3eb09fd483e7629eb2266f&a2=2&a4=100&g2=8a82944a535157060153595f3ea352c2&g3=006600&g4=78141b277cfadba072a0bcb90745faef&z1=8a82944a535157060153595f553a52de&z13=606944188288&z15=100&z2=0&z3=Transaction+has+been+executed+successfully.&z4=006600&z5=0&z6=00&K=bfdfd8b0dcee974c07c3c85cfea753fe' + end + + def failed_refund_response + 'M=SPREE978&O=5&T=03%2F09%2F2016+03%3A16%3A06&V=413&a1=c2b481deffe0e27bdef1439655260092&a2=2&a4=-&a5=EUR&b1=-&z1=1A-1&z2=-9&z3=2.+At+least+one+of+input+parameters+is+malformed.%3A+Parameter+%5Bg4%5D+cannot+be+empty.&K=c2f6112b40c61859d03684ac8e422766' + end + + def successful_credit_response + 'M=SPREE978&O=6&T=03%2F09%2F2016+03%3A16%3A35&V=413&a1=868f8b942fae639d28e27e8933d575d4&a2=2&a4=100&z1=8a82944a53515706015359604c135301&z13=606944188289&z15=100&z2=0&z3=Transaction+has+been+executed+successfully.&z5=0&z6=00&K=51ba24f6ef3aa161f86e53c34c9616ac' + end + + def failed_credit_response + 'M=SPREE978&O=6&T=03%2F09%2F2016+03%3A16%3A59&V=413&a1=ff28246cfc811b1c686a52d08d075d9c&a2=2&a4=100&z1=8a829449535154bc01535960a962524f&z13=606944188290&z15=100&z2=05&z3=Transaction+has+been+declined.&z5=0&z6=57&K=cf34816d5c25dc007ef3525505c4c610' + end + + def empty_purchase_response + %( + ) + end + + def transcript + %( + opening connection to intconsole.credorax.com:443... + opened + starting SSL for intconsole.credorax.com:443... + SSL established + <- "POST /intenv/service/gateway HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: intconsole.credorax.com\r\nContent-Length: 264\r\n\r\n" + <- "a4=100&a1=335ebb08c489e6d361108a7eb7d8b92a&a5=EUR&c1=Longbob+Longsen&b2=1&b1=5223450000000007&b5=090&b4=25&b3=12&d1=127.0.0.1&c3=unspecified%40example.com&c5=456+My+StreetApt+1&c7=Ottawa&c10=K1C2N6&c2=+555+555-5555&M=SPREE978&O=1&K=ef26476215cee15664e75d979d33935b" + -> "HTTP/1.1 200 OK\r\n" + -> "Date: Wed, 09 Mar 2016 03:03:00 GMT\r\n" + -> "Content-Type: application/x-www-form-urlencoded\r\n" + -> "Content-Length: 283\r\n" + -> "Connection: close\r\n" + -> "\r\n" + reading 283 bytes... + -> "M=SPREE978&O=1&T=03%2F09%2F2016+03%3A03%3A01&V=413&a1=335ebb08c489e6d361108a7eb7d8b92a&a2=2&a4=100&a9=6&z1=8a829449535154bc01535953dd235043&z13=606944188276&z14=U&z15=100&z2=0&z3=Transaction+has+been+executed+successfully.&z4=006592&z5=0&z6=00&z9=X&K=4061e16f39915297827af1586635015a" + read 283 bytes + Conn close + ) + end + + def scrubbed_transcript + %( + opening connection to intconsole.credorax.com:443... + opened + starting SSL for intconsole.credorax.com:443... + SSL established + <- "POST /intenv/service/gateway HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: intconsole.credorax.com\r\nContent-Length: 264\r\n\r\n" + <- "a4=100&a1=335ebb08c489e6d361108a7eb7d8b92a&a5=EUR&c1=Longbob+Longsen&b2=1&b1=[FILTERED]&b5=[FILTERED]&b4=25&b3=12&d1=127.0.0.1&c3=unspecified%40example.com&c5=456+My+StreetApt+1&c7=Ottawa&c10=K1C2N6&c2=+555+555-5555&M=SPREE978&O=1&K=ef26476215cee15664e75d979d33935b" + -> "HTTP/1.1 200 OK\r\n" + -> "Date: Wed, 09 Mar 2016 03:03:00 GMT\r\n" + -> "Content-Type: application/x-www-form-urlencoded\r\n" + -> "Content-Length: 283\r\n" + -> "Connection: close\r\n" + -> "\r\n" + reading 283 bytes... + -> "M=SPREE978&O=1&T=03%2F09%2F2016+03%3A03%3A01&V=413&a1=335ebb08c489e6d361108a7eb7d8b92a&a2=2&a4=100&a9=6&z1=8a829449535154bc01535953dd235043&z13=606944188276&z14=U&z15=100&z2=0&z3=Transaction+has+been+executed+successfully.&z4=006592&z5=0&z6=00&z9=X&K=4061e16f39915297827af1586635015a" + read 283 bytes + Conn close + ) + end +end diff --git a/test/unit/gateways/ct_payment_test.rb b/test/unit/gateways/ct_payment_test.rb new file mode 100644 index 00000000000..98da844e37f --- /dev/null +++ b/test/unit/gateways/ct_payment_test.rb @@ -0,0 +1,302 @@ +require 'test_helper' + +class CtPaymentTest < Test::Unit::TestCase + def setup + @gateway = CtPaymentGateway.new(api_key: 'api_key', company_number: 'company number', merchant_number: 'merchant_number') + @credit_card = credit_card + @amount = 100 + + @options = { + order_id: '1', + billing_address: address, + description: 'Store Purchase' + } + end + + def test_successful_purchase + @gateway.expects(:ssl_post).twice.returns(successful_purchase_response, successful_ack_response) + + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + + assert_equal '000007708972;443752 ;021efc336262;;', response.authorization + assert response.test? + end + + def test_failed_purchase + @gateway.expects(:ssl_post).returns(failed_purchase_response) + + response = @gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_equal Gateway::STANDARD_ERROR_CODE[:card_declined], response.error_code + end + + def test_successful_authorize + @gateway.expects(:ssl_post).twice.returns(successful_authorize_response, successful_ack_response) + + response = @gateway.authorize(@amount, @credit_card, @options) + assert_success response + + assert_equal '000007708990;448572 ;0e7ebe0a804f;;', response.authorization + assert response.test? + end + + def test_failed_authorize + @gateway.expects(:ssl_post).returns(failed_authorize_response) + + response = @gateway.authorize(@amount, @credit_card, @options) + assert_failure response + assert_equal Gateway::STANDARD_ERROR_CODE[:card_declined], response.error_code + end + + def test_successful_capture + @gateway.expects(:ssl_post).twice.returns(successful_capture_response, successful_ack_response) + + response = @gateway.capture(@amount, '000007708990;448572 ;0e7ebe0a804f;', @options) + assert_success response + + assert_equal '000007708991; ;0636aca3dd8e;;', response.authorization + assert response.test? + end + + def test_failed_capture + @gateway.expects(:ssl_post).returns(failed_capture_response) + + response = @gateway.capture(@amount, '0123456789asd;0123456789asdf;12345678', @options) + assert_failure response + assert_equal Gateway::STANDARD_ERROR_CODE[:incorrect_number], response.error_code + end + + def test_successful_refund + @gateway.expects(:ssl_post).twice.returns(successful_refund_response, successful_ack_response) + + response = @gateway.refund(@amount, '000007708990;448572 ;0e7ebe0a804f;', @options) + assert_success response + + assert_equal '000007709004; ;0a08f144b6ea;;', response.authorization + assert response.test? + end + + def test_failed_refund + @gateway.expects(:ssl_post).returns(failed_refund_response) + + response = @gateway.capture(@amount, '0123456789asd;0123456789asdf;12345678', @options) + assert_failure response + assert_equal Gateway::STANDARD_ERROR_CODE[:incorrect_number], response.error_code + end + + def test_successful_void + @gateway.expects(:ssl_post).returns(successful_void_response) + + response = @gateway.void('000007708990;448572 ;0e7ebe0a804f;', @options) + assert_success response + + assert_equal '000007709013; ;0de38871ce96;;', response.authorization + assert response.test? + end + + def test_failed_void + @gateway.expects(:ssl_post).returns(failed_void_response) + + response = @gateway.void('0123456789asd;0123456789asdf;12345678') + assert_failure response + assert_equal Gateway::STANDARD_ERROR_CODE[:incorrect_number], response.error_code + end + + def test_successful_verify + @gateway.expects(:ssl_post).twice.returns(successful_verify_response, successful_ack_response) + + response = @gateway.verify(@credit_card, @options) + assert_success response + + assert_equal '000007709025; ;0b882fe35f69;;', response.authorization + assert response.test? + end + + def test_failed_verify + @gateway.expects(:ssl_post).returns(failed_verify_response) + + response = @gateway.verify(@credit_card, @options) + assert_failure response + assert_equal Gateway::STANDARD_ERROR_CODE[:card_declined], response.error_code + end + + def test_successful_credit + @gateway.expects(:ssl_post).twice.returns(successful_credit_response, successful_ack_response) + + response = @gateway.credit(@amount, @credit_card, @options) + assert_success response + + assert_equal '000007709063; ;054902f2ded0;;', response.authorization + assert response.test? + end + + def test_scrub + assert @gateway.supports_scrubbing? + assert_equal @gateway.scrub(pre_scrubbed), post_scrubbed + end + + private + + def pre_scrubbed + %q( + opening connection to test.ctpaiement.ca:443... + opened + starting SSL for test.ctpaiement.ca:443... + SSL established + <- "POST /v1/purchase HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nConnection: close\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nHost: test.ctpaiement.ca\r\nContent-Length: 528\r\n\r\n" + <- "auth-api-key=R46SNTJ42UCJ3264182Y0T087YHBA50RTK&payload=TWVyY2hhbnRUZXJtaW5hbE51bWJlcj0gICAgICZBbW91bnQ9MDAwMDAwMDAxMDAmT3BlcmF0b3JJRD0wMDAwMDAwMCZDdXJyZW5jeUNvZGU9VVNEJkludm9pY2VOdW1iZXI9MDYzZmI1MmMyOTc2JklucHV0VHlwZT1JJkxhbmd1YWdlQ29kZT1FJkNhcmRUeXBlPVYmQ2FyZE51bWJlcj00NTAxMTYxMTA3MjE3MjE0JkV4cGlyYXRpb25EYXRlPTA3MjAmQ2FyZEhvbGRlckFkZHJlc3M9NDU2IE15IFN0cmVldE90dGF3YSAgICAgICAgICAgICAgICAgIE9OJkNhcmRIb2xkZXJQb3N0YWxDb2RlPSAgIEsxQzJONiZDdXN0b21lck51bWJlcj0wMDAwMDAwMCZDb21wYW55TnVtYmVyPTAwNTg5Jk1lcmNoYW50TnVtYmVyPTUzNDAwMDMw" + -> "HTTP/1.1 200 200\r\n" + -> "Date: Fri, 29 Jun 2018 18:21:07 GMT\r\n" + -> "Server: Apache\r\n" + -> "Strict-Transport-Security: max-age=31536000; includeSubDomains; preload\r\n" + -> "Connection: close\r\n" + -> "Transfer-Encoding: chunked\r\n" + -> "Content-Type: application/json;charset=ISO-8859-1\r\n" + -> "\r\n" + -> "480\r\n" + reading 1152 bytes... + -> "{\"returnCode\":\" 00\",\"errorDescription\":null,\"authorizationNumber\":\"448186 \",\"referenceNumber\":\" \",\"transactionNumber\":\"000007709037\",\"batchNumber\":\"0001\",\"terminalNumber\":\"13366\",\"serverNumber\":\"0001\",\"timeStamp\":\"20180629-14210749\",\"trxCode\":\"00\",\"merchantNumber\":\"53400030\",\"amount\":\"00000000100\",\"invoiceNumber\":\"063fb52c2976\",\"trxType\":\"C\",\"cardType\":\"V\",\"cardNumber\":\"450116XXXXXX7214 \",\"expirationDate\":\"0720\",\"bankTerminalNumber\":\"53400188\",\"trxDate\":\"06292018\",\"trxTime\":\"142107\",\"accountType\":\"0\",\"trxMethod\":\"T@1\",\"languageCode\":\"E\",\"sequenceNumber\":\"000000000028\",\"receiptDisp\":\" APPROVED-THANK YOU \",\"terminalDisp\":\"APPROVED \",\"operatorId\":\"00000000\",\"surchargeAmount\":\"\",\"companyNumber\":\"00589\",\"secureID\":\"\",\"cvv2Cvc2Status\":\" \",\"iopIssuerConfirmationNumber\":null,\"iopIssuerName\":null,\"avsStatus\":null,\"holderName\":null,\"threeDSStatus\":null,\"emvLabel\":null,\"emvAID\":null,\"emvTVR\":null,\"emvTSI\":null,\"emvTC\":null,\"demoMode\":null,\"terminalInvoiceNumber\":null,\"cashbackAmount\":null,\"tipAmount\":null,\"taxAmount\":null,\"cvmResults\":null,\"token\":null,\"customerNumber\":null,\"email\":\"\"}" + read 1152 bytes + reading 2 bytes... + -> "\r\n" + read 2 bytes + -> "0\r\n" + -> "\r\n" + Conn close + opening connection to test.ctpaiement.ca:443... + opened + starting SSL for test.ctpaiement.ca:443... + SSL established + <- "POST /v1/ack HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nConnection: close\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nHost: test.ctpaiement.ca\r\nContent-Length: 156\r\n\r\n" + <- "auth-api-key=R46SNTJ42UCJ3264182Y0T087YHBA50RTK&payload=VHJhbnNhY3Rpb25OdW1iZXI9MDAwMDA3NzA5MDM3JkNvbXBhbnlOdW1iZXI9MDA1ODkmTWVyY2hhbnROdW1iZXI9NTM0MDAwMzA=" + -> "HTTP/1.1 200 200\r\n" + -> "Date: Fri, 29 Jun 2018 18:21:08 GMT\r\n" + -> "Server: Apache\r\n" + -> "Strict-Transport-Security: max-age=31536000; includeSubDomains; preload\r\n" + -> "Connection: close\r\n" + -> "Transfer-Encoding: chunked\r\n" + -> "Content-Type: application/json;charset=ISO-8859-1\r\n" + -> "\r\n" + -> "15\r\n" + reading 21 bytes... + -> "{\"returnCode\":\"true\"}" + read 21 bytes + reading 2 bytes... + -> "\r\n" + read 2 bytes + -> "0\r\n" + -> "\r\n" + Conn close + ) + end + + def post_scrubbed + %q( + opening connection to test.ctpaiement.ca:443... + opened + starting SSL for test.ctpaiement.ca:443... + SSL established + <- "POST /v1/purchase HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nConnection: close\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nHost: test.ctpaiement.ca\r\nContent-Length: 528\r\n\r\n" + <- "auth-api-key=[FILTERED]&payload=[FILTERED]" + -> "HTTP/1.1 200 200\r\n" + -> "Date: Fri, 29 Jun 2018 18:21:07 GMT\r\n" + -> "Server: Apache\r\n" + -> "Strict-Transport-Security: max-age=31536000; includeSubDomains; preload\r\n" + -> "Connection: close\r\n" + -> "Transfer-Encoding: chunked\r\n" + -> "Content-Type: application/json;charset=ISO-8859-1\r\n" + -> "\r\n" + -> "480\r\n" + reading 1152 bytes... + -> "{\"returnCode\":\" 00\",\"errorDescription\":null,\"authorizationNumber\":\"448186 \",\"referenceNumber\":\" \",\"transactionNumber\":\"000007709037\",\"batchNumber\":\"0001\",\"terminalNumber\":\"13366\",\"serverNumber\":\"0001\",\"timeStamp\":\"20180629-14210749\",\"trxCode\":\"00\",\"merchantNumber\":\"53400030\",\"amount\":\"00000000100\",\"invoiceNumber\":\"063fb52c2976\",\"trxType\":\"C\",\"cardType\":\"V\",\"cardNumber\":\"450116XXXXXX7214 \",\"expirationDate\":\"0720\",\"bankTerminalNumber\":\"53400188\",\"trxDate\":\"06292018\",\"trxTime\":\"142107\",\"accountType\":\"0\",\"trxMethod\":\"T@1\",\"languageCode\":\"E\",\"sequenceNumber\":\"000000000028\",\"receiptDisp\":\" APPROVED-THANK YOU \",\"terminalDisp\":\"APPROVED \",\"operatorId\":\"00000000\",\"surchargeAmount\":\"\",\"companyNumber\":\"00589\",\"secureID\":\"\",\"cvv2Cvc2Status\":\" \",\"iopIssuerConfirmationNumber\":null,\"iopIssuerName\":null,\"avsStatus\":null,\"holderName\":null,\"threeDSStatus\":null,\"emvLabel\":null,\"emvAID\":null,\"emvTVR\":null,\"emvTSI\":null,\"emvTC\":null,\"demoMode\":null,\"terminalInvoiceNumber\":null,\"cashbackAmount\":null,\"tipAmount\":null,\"taxAmount\":null,\"cvmResults\":null,\"token\":null,\"customerNumber\":null,\"email\":\"\"}" + read 1152 bytes + reading 2 bytes... + -> "\r\n" + read 2 bytes + -> "0\r\n" + -> "\r\n" + Conn close + opening connection to test.ctpaiement.ca:443... + opened + starting SSL for test.ctpaiement.ca:443... + SSL established + <- "POST /v1/ack HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nConnection: close\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nHost: test.ctpaiement.ca\r\nContent-Length: 156\r\n\r\n" + <- "auth-api-key=[FILTERED]&payload=[FILTERED]" + -> "HTTP/1.1 200 200\r\n" + -> "Date: Fri, 29 Jun 2018 18:21:08 GMT\r\n" + -> "Server: Apache\r\n" + -> "Strict-Transport-Security: max-age=31536000; includeSubDomains; preload\r\n" + -> "Connection: close\r\n" + -> "Transfer-Encoding: chunked\r\n" + -> "Content-Type: application/json;charset=ISO-8859-1\r\n" + -> "\r\n" + -> "15\r\n" + reading 21 bytes... + -> "{\"returnCode\":\"true\"}" + read 21 bytes + reading 2 bytes... + -> "\r\n" + read 2 bytes + -> "0\r\n" + -> "\r\n" + Conn close + ) + end + + def successful_purchase_response + '{"returnCode":" 00","errorDescription":null,"authorizationNumber":"443752 ","referenceNumber":" ","transactionNumber":"000007708972","batchNumber":"0001","terminalNumber":"13366","serverNumber":"0001","timeStamp":"20180629-12110905","trxCode":"00","merchantNumber":"53400030","amount":"00000000100","invoiceNumber":"021efc336262","trxType":"C","cardType":"V","cardNumber":"450116XXXXXX7214 ","expirationDate":"0720","bankTerminalNumber":"53400188","trxDate":"06292018","trxTime":"121109","accountType":"0","trxMethod":"T@1","languageCode":"E","sequenceNumber":"000000000008","receiptDisp":" APPROVED-THANK YOU ","terminalDisp":"APPROVED ","operatorId":"00000000","surchargeAmount":"","companyNumber":"00589","secureID":"","cvv2Cvc2Status":" ","iopIssuerConfirmationNumber":null,"iopIssuerName":null,"avsStatus":null,"holderName":null,"threeDSStatus":null,"emvLabel":null,"emvAID":null,"emvTVR":null,"emvTSI":null,"emvTC":null,"demoMode":null,"terminalInvoiceNumber":null,"cashbackAmount":null,"tipAmount":null,"taxAmount":null,"cvmResults":null,"token":null,"customerNumber":null,"email":""}' + end + + def successful_ack_response + '{"returnCode":"true"}' + end + + def failed_purchase_response + '{"returnCode":" 05","errorDescription":"Transaction declined","authorizationNumber":" ","referenceNumber":" ","transactionNumber":"000007708975","batchNumber":"0000","terminalNumber":"13366","serverNumber":"0001","timeStamp":"20180629-12272768","trxCode":"00","merchantNumber":"53400030","amount":"00000000100","invoiceNumber":"098096b31937","trxType":"C","cardType":"V","cardNumber":"450224XXXXXX1718 ","expirationDate":"0919","bankTerminalNumber":"53400188","trxDate":"06292018","trxTime":"122727","accountType":"0","trxMethod":"T@1","languageCode":"E","sequenceNumber":"000000000009","receiptDisp":" TRANSACTION NOT APPROVED ","terminalDisp":"05-DECLINE ","operatorId":"00000000","surchargeAmount":"","companyNumber":"00589","secureID":"","cvv2Cvc2Status":" ","iopIssuerConfirmationNumber":null,"iopIssuerName":null,"avsStatus":null,"holderName":null,"threeDSStatus":null,"emvLabel":null,"emvAID":null,"emvTVR":null,"emvTSI":null,"emvTC":null,"demoMode":null,"terminalInvoiceNumber":null,"cashbackAmount":null,"tipAmount":null,"taxAmount":null,"cvmResults":null,"token":null,"customerNumber":null,"email":""}' + end + + def successful_authorize_response + '{"returnCode":" 00","errorDescription":null,"authorizationNumber":"448572 ","referenceNumber":" ","transactionNumber":"000007708990","batchNumber":"0001","terminalNumber":"13367","serverNumber":"0001","timeStamp":"20180629-12501747","trxCode":"01","merchantNumber":"53400030","amount":"00000000100","invoiceNumber":"0e7ebe0a804f","trxType":"C","cardType":"V","cardNumber":"450116XXXXXX7214 ","expirationDate":"0720","bankTerminalNumber":"53400189","trxDate":"06292018","trxTime":"125017","accountType":"0","trxMethod":"T@1","languageCode":"E","sequenceNumber":"000000000014","receiptDisp":" APPROVED-THANK YOU ","terminalDisp":"APPROVED ","operatorId":"00000000","surchargeAmount":"","companyNumber":"00589","secureID":"","cvv2Cvc2Status":" ","iopIssuerConfirmationNumber":null,"iopIssuerName":null,"avsStatus":null,"holderName":null,"threeDSStatus":null,"emvLabel":null,"emvAID":null,"emvTVR":null,"emvTSI":null,"emvTC":null,"demoMode":null,"terminalInvoiceNumber":null,"cashbackAmount":null,"tipAmount":null,"taxAmount":null,"cvmResults":null,"token":null,"customerNumber":null,"email":""}' + end + + def failed_authorize_response + '{"returnCode":" 05","errorDescription":"Transaction declined","authorizationNumber":" ","referenceNumber":" ","transactionNumber":"000007708993","batchNumber":"0000","terminalNumber":"13367","serverNumber":"0001","timeStamp":"20180629-13072751","trxCode":"01","merchantNumber":"53400030","amount":"00000000100","invoiceNumber":"02ec22cbb5db","trxType":"C","cardType":"V","cardNumber":"450224XXXXXX1718 ","expirationDate":"0919","bankTerminalNumber":"53400189","trxDate":"06292018","trxTime":"130727","accountType":"0","trxMethod":"T@1","languageCode":"E","sequenceNumber":"000000000015","receiptDisp":" TRANSACTION NOT APPROVED ","terminalDisp":"05-DECLINE ","operatorId":"00000000","surchargeAmount":"","companyNumber":"00589","secureID":"","cvv2Cvc2Status":" ","iopIssuerConfirmationNumber":null,"iopIssuerName":null,"avsStatus":null,"holderName":null,"threeDSStatus":null,"emvLabel":null,"emvAID":null,"emvTVR":null,"emvTSI":null,"emvTC":null,"demoMode":null,"terminalInvoiceNumber":null,"cashbackAmount":null,"tipAmount":null,"taxAmount":null,"cvmResults":null,"token":null,"customerNumber":null,"email":""}' + end + + def successful_capture_response + '{"returnCode":" 00","errorDescription":null,"authorizationNumber":" ","referenceNumber":" ","transactionNumber":"000007708991","batchNumber":"0001","terminalNumber":"13366","serverNumber":"0001","timeStamp":"20180629-12501869","trxCode":"02","merchantNumber":"53400030","amount":"00000000100","invoiceNumber":"0636aca3dd8e","trxType":"C","cardType":"V","cardNumber":"450116XXXXXX7214 ","expirationDate":"0720","bankTerminalNumber":"53400188","trxDate":"06292018","trxTime":"125018","accountType":"0","trxMethod":"T@1","languageCode":"E","sequenceNumber":"000000000015","receiptDisp":" APPROVED-THANK YOU ","terminalDisp":"APPROVED ","operatorId":"00000000","surchargeAmount":"","companyNumber":"00589","secureID":"","cvv2Cvc2Status":" ","iopIssuerConfirmationNumber":null,"iopIssuerName":null,"avsStatus":null,"holderName":null,"threeDSStatus":null,"emvLabel":null,"emvAID":null,"emvTVR":null,"emvTSI":null,"emvTC":null,"demoMode":null,"terminalInvoiceNumber":null,"cashbackAmount":null,"tipAmount":null,"taxAmount":null,"cvmResults":null,"token":null,"customerNumber":null,"email":""}' + end + + def failed_capture_response + '{"returnCode":"9068","errorDescription":"The original transaction number does not match any actual transaction","authorizationNumber":" ","referenceNumber":" ","transactionNumber":"000007708999","batchNumber":" ","terminalNumber":" ","serverNumber":" ","timeStamp":"20180629-13224441","trxCode":" ","merchantNumber":" ","amount":"00000000000","invoiceNumber":" ","trxType":" ","cardType":" ","cardNumber":" ","expirationDate":" ","bankTerminalNumber":" ","trxDate":" ","trxTime":" ","accountType":" ","trxMethod":" ","languageCode":" ","sequenceNumber":" ","receiptDisp":" OPERATION NON COMPLETEE ","terminalDisp":"9068: Contactez support.","operatorId":" ","surchargeAmount":" ","companyNumber":" ","secureID":"","cvv2Cvc2Status":" ","iopIssuerConfirmationNumber":null,"iopIssuerName":null,"avsStatus":null,"holderName":null,"threeDSStatus":null,"emvLabel":null,"emvAID":null,"emvTVR":null,"emvTSI":null,"emvTC":null,"demoMode":null,"terminalInvoiceNumber":null,"cashbackAmount":null,"tipAmount":null,"taxAmount":null,"cvmResults":null,"token":null,"customerNumber":null,"email":""}' + end + + def successful_refund_response + ' {"returnCode":" 00","errorDescription":null,"authorizationNumber":" ","referenceNumber":" ","transactionNumber":"000007709004","batchNumber":"0001","terminalNumber":"13367","serverNumber":"0001","timeStamp":"20180629-13294388","trxCode":"03","merchantNumber":"53400030","amount":"00000000100","invoiceNumber":"0a08f144b6ea","trxType":"C","cardType":"V","cardNumber":"450116XXXXXX7214 ","expirationDate":"0720","bankTerminalNumber":"53400189","trxDate":"06292018","trxTime":"132944","accountType":"0","trxMethod":"T@1","languageCode":"E","sequenceNumber":"000000000019","receiptDisp":" APPROVED-THANK YOU ","terminalDisp":"APPROVED ","operatorId":"00000000","surchargeAmount":"","companyNumber":"00589","secureID":"","cvv2Cvc2Status":" ","iopIssuerConfirmationNumber":null,"iopIssuerName":null,"avsStatus":null,"holderName":null,"threeDSStatus":null,"emvLabel":null,"emvAID":null,"emvTVR":null,"emvTSI":null,"emvTC":null,"demoMode":null,"terminalInvoiceNumber":null,"cashbackAmount":null,"tipAmount":null,"taxAmount":null,"cvmResults":null,"token":null,"customerNumber":null,"email":""}' + end + + def failed_refund_response + '{"returnCode":"9068","errorDescription":"The original transaction number does not match any actual transaction","authorizationNumber":" ","referenceNumber":" ","transactionNumber":"000007709009","batchNumber":" ","terminalNumber":" ","serverNumber":" ","timeStamp":"20180629-13402119","trxCode":" ","merchantNumber":" ","amount":"00000000000","invoiceNumber":" ","trxType":" ","cardType":" ","cardNumber":" ","expirationDate":" ","bankTerminalNumber":" ","trxDate":" ","trxTime":" ","accountType":" ","trxMethod":" ","languageCode":" ","sequenceNumber":" ","receiptDisp":" OPERATION NON COMPLETEE ","terminalDisp":"9068: Contactez support.","operatorId":" ","surchargeAmount":" ","companyNumber":" ","secureID":"","cvv2Cvc2Status":" ","iopIssuerConfirmationNumber":null,"iopIssuerName":null,"avsStatus":null,"holderName":null,"threeDSStatus":null,"emvLabel":null,"emvAID":null,"emvTVR":null,"emvTSI":null,"emvTC":null,"demoMode":null,"terminalInvoiceNumber":null,"cashbackAmount":null,"tipAmount":null,"taxAmount":null,"cvmResults":null,"token":null,"customerNumber":null,"email":""}' + end + + def successful_void_response + '{"returnCode":" 00","errorDescription":null,"authorizationNumber":" ","referenceNumber":" ","transactionNumber":"000007709013","batchNumber":"0001","terminalNumber":"13367","serverNumber":"0001","timeStamp":"20180629-13451840","trxCode":"04","merchantNumber":"53400030","amount":"00000000100","invoiceNumber":"0de38871ce96","trxType":"C","cardType":"V","cardNumber":"450116XXXXXX7214","expirationDate":"0720","bankTerminalNumber":"53400189","trxDate":"06292018","trxTime":"134518","accountType":"0","trxMethod":"T@1","languageCode":"E","sequenceNumber":"000000000023","receiptDisp":" APPROVED-THANK YOU ","terminalDisp":"APPROVED ","operatorId":"00000000","surchargeAmount":" ","companyNumber":" ","secureID":"","cvv2Cvc2Status":" ","iopIssuerConfirmationNumber":null,"iopIssuerName":null,"avsStatus":null,"holderName":null,"threeDSStatus":null,"emvLabel":null,"emvAID":null,"emvTVR":null,"emvTSI":null,"emvTC":null,"demoMode":null,"terminalInvoiceNumber":null,"cashbackAmount":null,"tipAmount":null,"taxAmount":null,"cvmResults":null,"token":null,"customerNumber":null,"email":""}' + end + + def failed_void_response + '{"returnCode":"9068","errorDescription":"The original transaction number does not match any actual transaction","authorizationNumber":" ","referenceNumber":" ","transactionNumber":"0000000000-1","batchNumber":" ","terminalNumber":" ","serverNumber":" ","timeStamp":"20180629-13520693","trxCode":" ","merchantNumber":" ","amount":"00000000000","invoiceNumber":" ","trxType":" ","cardType":" ","cardNumber":" ","expirationDate":" ","bankTerminalNumber":" ","trxDate":" ","trxTime":" ","accountType":" ","trxMethod":" ","languageCode":" ","sequenceNumber":" ","receiptDisp":" OPERATION NOT COMPLETED ","terminalDisp":"9068: Contact support. ","operatorId":" ","surchargeAmount":" ","companyNumber":" ","secureID":"","cvv2Cvc2Status":" ","iopIssuerConfirmationNumber":null,"iopIssuerName":null,"avsStatus":null,"holderName":null,"threeDSStatus":null,"emvLabel":null,"emvAID":null,"emvTVR":null,"emvTSI":null,"emvTC":null,"demoMode":null,"terminalInvoiceNumber":null,"cashbackAmount":null,"tipAmount":null,"taxAmount":null,"cvmResults":null,"token":null,"customerNumber":null,"email":""}' + end + + def successful_verify_response + '{"returnCode":" 00","errorDescription":null,"authorizationNumber":" ","referenceNumber":" ","transactionNumber":"000007709025","batchNumber":"0001","terminalNumber":"13367","serverNumber":"0001","timeStamp":"20180629-14023575","trxCode":"08","merchantNumber":"53400030","amount":"00000000000","invoiceNumber":"0b882fe35f69","trxType":"C","cardType":"V","cardNumber":"450116XXXXXX7214 ","expirationDate":"0720","bankTerminalNumber":"53400189","trxDate":"06292018","trxTime":"140236","accountType":"0","trxMethod":"T@1","languageCode":"E","sequenceNumber":"000000000025","receiptDisp":" APPROVED-THANK YOU ","terminalDisp":"APPROVED ","operatorId":"00000000","surchargeAmount":"","companyNumber":"00589","secureID":"","cvv2Cvc2Status":" ","iopIssuerConfirmationNumber":null,"iopIssuerName":null,"avsStatus":null,"holderName":null,"threeDSStatus":null,"emvLabel":null,"emvAID":null,"emvTVR":null,"emvTSI":null,"emvTC":null,"demoMode":null,"terminalInvoiceNumber":null,"cashbackAmount":null,"tipAmount":null,"taxAmount":null,"cvmResults":null,"token":null,"customerNumber":null,"email":""}' + end + + def failed_verify_response + '{"returnCode":" 05","errorDescription":"Transaction declined","authorizationNumber":" ","referenceNumber":" ","transactionNumber":"000007709029","batchNumber":"0000","terminalNumber":"13367","serverNumber":"0001","timeStamp":"20180629-14104707","trxCode":"08","merchantNumber":"53400030","amount":"00000000000","invoiceNumber":"0c0054d2bb7a","trxType":"C","cardType":"V","cardNumber":"450224XXXXXX1718 ","expirationDate":"0919","bankTerminalNumber":"53400189","trxDate":"06292018","trxTime":"141047","accountType":"0","trxMethod":"T@1","languageCode":"E","sequenceNumber":"000000000026","receiptDisp":" TRANSACTION NOT APPROVED ","terminalDisp":"05-DECLINE ","operatorId":"00000000","surchargeAmount":"","companyNumber":"00589","secureID":"","cvv2Cvc2Status":" ","iopIssuerConfirmationNumber":null,"iopIssuerName":null,"avsStatus":null,"holderName":null,"threeDSStatus":null,"emvLabel":null,"emvAID":null,"emvTVR":null,"emvTSI":null,"emvTC":null,"demoMode":null,"terminalInvoiceNumber":null,"cashbackAmount":null,"tipAmount":null,"taxAmount":null,"cvmResults":null,"token":null,"customerNumber":null,"email":""}' + end + + def successful_credit_response + '{"returnCode":" 00","errorDescription":null,"authorizationNumber":" ","referenceNumber":" ","transactionNumber":"000007709063","batchNumber":"0001","terminalNumber":"13366","serverNumber":"0001","timeStamp":"20180629-14420931","trxCode":"03","merchantNumber":"53400030","amount":"00000000100","invoiceNumber":"054902f2ded0","trxType":"C","cardType":"V","cardNumber":"450116XXXXXX7214 ","expirationDate":"0720","bankTerminalNumber":"53400188","trxDate":"06292018","trxTime":"144209","accountType":"0","trxMethod":"T@1","languageCode":"E","sequenceNumber":"000000000032","receiptDisp":" APPROVED-THANK YOU ","terminalDisp":"APPROVED ","operatorId":"00000000","surchargeAmount":"","companyNumber":"00589","secureID":"","cvv2Cvc2Status":" ","iopIssuerConfirmationNumber":null,"iopIssuerName":null,"avsStatus":null,"holderName":null,"threeDSStatus":null,"emvLabel":null,"emvAID":null,"emvTVR":null,"emvTSI":null,"emvTC":null,"demoMode":null,"terminalInvoiceNumber":null,"cashbackAmount":null,"tipAmount":null,"taxAmount":null,"cvmResults":null,"token":null,"customerNumber":null,"email":""}' + end +end diff --git a/test/unit/gateways/culqi_test.rb b/test/unit/gateways/culqi_test.rb new file mode 100644 index 00000000000..0cd74341733 --- /dev/null +++ b/test/unit/gateways/culqi_test.rb @@ -0,0 +1,441 @@ +require 'test_helper' + +class CulqiTest < Test::Unit::TestCase + def setup + @gateway = CulqiGateway.new(merchant_id: 'merchant', terminal_id: 'terminal', secret_key: 'password') + + @amount = 1000 + @credit_card = credit_card('4111111111111111') + + @options = { + order_id: generate_unique_id, + billing_address: address + } + end + + def test_successful_purchase + @gateway.expects(:ssl_post).returns(successful_purchase_response) + + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_match %r{Approved}, response.message + assert response.test? + end + + def test_failed_purchase + @gateway.expects(:ssl_post).returns(failed_purchase_response) + + response = @gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_match %r{Failed}, response.message + end + + def test_successful_authorize_and_capture + @gateway.expects(:ssl_post).returns(successful_authorize_response) + + response = @gateway.authorize(@amount, @credit_card, @options) + assert_success response + assert_match %r{Approved}, response.message + assert_match %r(^\d+$), response.authorization + + @gateway.expects(:ssl_post).returns(successful_capture_response) + + capture = @gateway.capture(@amount, response.authorization) + assert_success capture + assert_match %r{Transaction has been successfully captured}, capture.message + end + + def test_failed_authorize + @gateway.expects(:ssl_post).returns(failed_authorize_response) + + response = @gateway.authorize(@amount, @credit_card, @options) + assert_failure response + assert_match %r{Failed}, response.message + end + + def test_failed_capture + @gateway.expects(:ssl_post).returns(failed_capture_response) + + response = @gateway.capture(@amount, '0') + assert_failure response + assert_match %r{Transaction not found}, response.message + end + + def test_successful_refund + @gateway.expects(:ssl_post).returns(successful_authorize_response) + auth = @gateway.authorize(@amount, @credit_card, @options) + + @gateway.expects(:ssl_post).returns(successful_capture_response) + capture = @gateway.capture(@amount, auth.authorization) + + @gateway.expects(:ssl_post).returns(successful_refund_response) + refund = @gateway.refund(@amount, capture.authorization) + assert_success refund + assert_match %r{reversed}, refund.message + end + + def test_failed_refund + @gateway.expects(:ssl_post).returns(failed_refund_response) + + response = @gateway.refund(@amount, '0') + assert_failure response + assert_match %r{Transaction not found}, response.message + end + + def test_successful_void + @gateway.expects(:ssl_post).returns(successful_authorize_response) + + response = @gateway.authorize(@amount, @credit_card, @options) + assert_success response + + @gateway.expects(:ssl_post).returns(successful_void_response) + + void = @gateway.void(response.authorization, @options) + assert_success void + assert_match %r{cancelled}, void.message + end + + def test_failed_void + @gateway.expects(:ssl_post).returns(failed_void_response) + + response = @gateway.void('0', @options) + assert_failure response + assert_match %r{Transaction not found}, response.message + end + + def test_successful_verify + @gateway.expects(:ssl_post).returns(successful_void_response) + @gateway.expects(:ssl_post).returns(successful_authorize_response) + + response = @gateway.verify(@credit_card, @options) + assert_success response + assert_match %r{Approved}, response.message + end + + def test_successful_verify_with_failed_void + @gateway.expects(:ssl_post).returns(failed_void_response) + @gateway.expects(:ssl_post).returns(successful_authorize_response) + + response = @gateway.verify(@credit_card, @options) + assert_success response + assert_match %r{Approved}, response.message + end + + def test_failed_verify + @gateway.expects(:ssl_post).returns(failed_authorize_response) + + response = @gateway.verify(@credit_card, @options) + assert_failure response + assert_match %r{Failed}, response.message + end + + def test_successful_store_and_purchase + @gateway.expects(:ssl_post).returns(successful_store_response) + + response = @gateway.store(@credit_card, @options.merge(partner_id: fixtures(:culqi)[:partner_id])) + assert_success response + assert_match %r{Card tokenized successfully}, response.message + + @gateway.expects(:ssl_post).returns(successful_purchase_response) + + purchase = @gateway.purchase(@amount, response.authorization, @options.merge(cvv: @credit_card.verification_value)) + assert_success purchase + end + + def test_failed_store + @gateway.expects(:ssl_post).returns(failed_store_response) + + response = @gateway.store(@credit_card, @options.merge(partner_id: fixtures(:culqi)[:partner_id])) + assert_failure response + assert_match %r{Card already tokenized for same merchant}, response.message + end + + def test_scrub + assert @gateway.supports_scrubbing? + assert_equal @gateway.scrub(pre_scrubbed), post_scrubbed + end + + private + + def pre_scrubbed + %q( + opening connection to staging.paymentz.com:443... + opened + starting SSL for staging.paymentz.com:443... + SSL established + <- "POST /transaction/SingleCallGenericServlet HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded;charset=UTF-8\r\nAccept: application/json\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: staging.paymentz.com\r\nContent-Length: 442\r\n\r\n" + <- "toid=10838&totype=Culqi&terminalid=470&amount=10.00&description=f595d9cc63612484a9314f1141e9de75&redirecturl=http%3A%2F%2Fwww.example.com&language=ENG&cardnumber=4000100011112224&cvv=123&expiry_month=09&expiry_year=2017&firstname=Longbob&lastname=Longsen&emailaddr=unspecified%40example.com&street=456+My+Street+Apt+1&city=Ottawa&state=ON&countrycode=CA&zip=K1C2N6&telno=%28555%29555-5555&telnocc=051&checksum=741d5843d64b750ccd749eb8b17be33c" + -> "HTTP/1.1 200 OK\r\n" + -> "Date: Tue, 15 Nov 2016 17:43:01 GMT\r\n" + -> "Server: staging\r\n" + -> "X-Frame-Options: SAMEORIGIN\r\n" + -> "Cache-Control: no-store, no-cache, must-revalidate\r\n" + -> "Pragma: no-cache\r\n" + -> "Expires: Wed, 31 Dec 1969 23:59:59 GMT\r\n" + -> "Content-Type: text/html;charset=UTF-8\r\n" + -> "Content-Length: 510\r\n" + -> "Set-Cookie: JSESSIONID=98659561D241CA110A65798B34A3150E; Path=/transaction/; HttpOnly;Secure;\r\n" + -> "Connection: close\r\n" + -> "\r\n" + reading 510 bytes... + -> "" + -> "{\"fraudscore\":\"\",\"statusdescription\":\"Approved---Your Transaction is successful\",\"billingdiscriptor\":\"Test_Transaction\",\"rulestriggered\":\"\",\"orderid\":\"f595d9cc63612484a9314f1141e9de75\",\"authamount\":\"10.00\",\"resultdescription\":\"\",\"cardissuer\":\"\",\"eci\":\"\",\"validationdescription\":\"\",\"banktransid\":\"\",\"cvvresult\":\"\",\"ecidescription\":\"\",\"token\":\"\",\"authcode\":\"\",\"cardsource\":\"\",\"cardcountrycode\":\"\",\"banktransdate\":\"\",\"checksum\":\"7d65440154044eaa7185d5492c2edd3f\",\"resultcode\":\"\",\"trackingid\":\"37859\",\"status\":\"Y\"}" + read 510 bytes + Conn close + ) + end + + def post_scrubbed + %q( + opening connection to staging.paymentz.com:443... + opened + starting SSL for staging.paymentz.com:443... + SSL established + <- "POST /transaction/SingleCallGenericServlet HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded;charset=UTF-8\r\nAccept: application/json\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: staging.paymentz.com\r\nContent-Length: 442\r\n\r\n" + <- "toid=10838&totype=Culqi&terminalid=470&amount=10.00&description=f595d9cc63612484a9314f1141e9de75&redirecturl=http%3A%2F%2Fwww.example.com&language=ENG&cardnumber=[FILTERED]&cvv=[FILTERED]&expiry_month=09&expiry_year=2017&firstname=Longbob&lastname=Longsen&emailaddr=unspecified%40example.com&street=456+My+Street+Apt+1&city=Ottawa&state=ON&countrycode=CA&zip=K1C2N6&telno=%28555%29555-5555&telnocc=051&checksum=741d5843d64b750ccd749eb8b17be33c" + -> "HTTP/1.1 200 OK\r\n" + -> "Date: Tue, 15 Nov 2016 17:43:01 GMT\r\n" + -> "Server: staging\r\n" + -> "X-Frame-Options: SAMEORIGIN\r\n" + -> "Cache-Control: no-store, no-cache, must-revalidate\r\n" + -> "Pragma: no-cache\r\n" + -> "Expires: Wed, 31 Dec 1969 23:59:59 GMT\r\n" + -> "Content-Type: text/html;charset=UTF-8\r\n" + -> "Content-Length: 510\r\n" + -> "Set-Cookie: JSESSIONID=98659561D241CA110A65798B34A3150E; Path=/transaction/; HttpOnly;Secure;\r\n" + -> "Connection: close\r\n" + -> "\r\n" + reading 510 bytes... + -> "" + -> "{\"fraudscore\":\"\",\"statusdescription\":\"Approved---Your Transaction is successful\",\"billingdiscriptor\":\"Test_Transaction\",\"rulestriggered\":\"\",\"orderid\":\"f595d9cc63612484a9314f1141e9de75\",\"authamount\":\"10.00\",\"resultdescription\":\"\",\"cardissuer\":\"\",\"eci\":\"\",\"validationdescription\":\"\",\"banktransid\":\"\",\"cvvresult\":\"\",\"ecidescription\":\"\",\"token\":\"\",\"authcode\":\"\",\"cardsource\":\"\",\"cardcountrycode\":\"\",\"banktransdate\":\"\",\"checksum\":\"7d65440154044eaa7185d5492c2edd3f\",\"resultcode\":\"\",\"trackingid\":\"37859\",\"status\":\"Y\"}" + read 510 bytes + Conn close + ) + end + + def successful_purchase_response + %( + { + "fraudscore": "", + "statusdescription": "Approved---Your Transaction is successful", + "billingdiscriptor": "Test_Transaction", + "rulestriggered": "", + "orderid": "3ee62ef1b32401e9e9ae1db3c3bbdd2c", + "authamount": "10.00", + "resultdescription": "", + "cardissuer": "", + "eci": "", + "validationdescription": "", + "banktransid": "", + "cvvresult": "", + "ecidescription": "", + "token": "ccIJiW3R6eiiWIQSDsCOPuA47MZEfWNS", + "authcode": "", + "cardsource": "", + "cardcountrycode": "", + "banktransdate": "", + "checksum": "3fc8f97d8918cf533006dc89c71e7bd2", + "resultcode": "", + "trackingid": "39539", + "status": "Y" + } + ) + end + + def failed_purchase_response + %( + { + "fraudscore": "", + "statusdescription": "Failed--(Card expired )", + "billingdiscriptor": " ", + "rulestriggered": "", + "orderid": "3a8dab9a1082008519c96cb4170e170e", + "authamount": "10.00", + "resultdescription": "", + "cardissuer": "", + "eci": "", + "validationdescription": "", + "banktransid": "", + "cvvresult": "", + "ecidescription": "", + "token": "", + "authcode": "", + "cardsource": "", + "cardcountrycode": "", + "banktransdate": "", + "checksum": "880492e540f4128680f614b9488fe702", + "resultcode": "", + "trackingid": "39540", + "status": "N" + } + ) + end + + def successful_authorize_response + %( + { + "fraudscore": "", + "statusdescription": "Approved---Your Transaction is successful", + "billingdiscriptor": "Test_Transaction", + "rulestriggered": "", + "orderid": "f2868a3fcd2f656ff2e57758df218d4e", + "authamount": "10.00", + "resultdescription": "", + "cardissuer": "", + "eci": "", + "validationdescription": "", + "banktransid": "", + "cvvresult": "", + "ecidescription": "", + "token": "ccIJiW3R6eiiWIQSDsCOPuA47MZEfWNS", + "authcode": "", + "cardsource": "", + "cardcountrycode": "", + "banktransdate": "", + "checksum": "2ac8857b8821b1b07f268ec60d6ddb4d", + "resultcode": "", + "trackingid": "39541", + "status": "Y" + } + ) + end + + def failed_authorize_response + %( + { + "fraudscore": "", + "statusdescription": "Failed--(Card expired )", + "billingdiscriptor": " ", + "rulestriggered": "", + "orderid": "cffc3df7cd594844d8f3659336fc7ed9", + "authamount": "10.00", + "resultdescription": "", + "cardissuer": "", + "eci": "", + "validationdescription": "", + "banktransid": "", + "cvvresult": "", + "ecidescription": "", + "token": "", + "authcode": "", + "cardsource": "", + "cardcountrycode": "", + "banktransdate": "", + "checksum": "2c62a41893a8a6aed54ba19994c26455", + "resultcode": "", + "trackingid": "39542", + "status": "N" + } + ) + end + + def successful_capture_response + %( + { + "amount": "10.00", + "statusdescription": "Transaction has been successfully captured", + "resultdescription": "", + "lote": "", + "newchecksum": "e64824e56e6fbf6cfc569241cb613e03", + "bankstatus": "", + "resultcode": "", + "trackingid": "39541", + "status": "Y" + } + ) + end + + def failed_capture_response + %( + { + "amount": "10.00", + "statusdescription": "Transaction not found", + "resultdescription": "", + "lote": "", + "newchecksum": "fa03a809a37f6f3453b2bba25776b280", + "bankstatus": "", + "resultcode": "", + "trackingid": "0", + "status": "N" + } + ) + end + + def successful_refund_response + %( + { + "statusDescription": "Transaction has been successfully reversed", + "checksum": "6c9a9383627f4527e2476939df445af6", + "refundamount": "10.00", + "trackingid": "39543", + "status": "Y" + } + ) + end + + def failed_refund_response + %( + { + "statusDescription": "Transaction not found", + "checksum": "fa03a809a37f6f3453b2bba25776b280", + "refundamount": "10.00", + "trackingid": "0", + "status": "N" + } + ) + end + + def successful_void_response + %( + { + "statusdescription": "Transaction has been successfully cancelled", + "orderid": "31adcdb52b8e20197e98b2b0ed91725a", + "resultdescription": "", + "newchecksum": "e68065b4cb141382aca9ff56fa057f95", + "bankstatus": "", + "resultcode": "", + "status": "Y", + "trackingid": "39544" + } + ) + end + + def failed_void_response + %( + { + "statusdescription": "10080_Transaction not found from provided tracking ID.", + "orderid": "8501c8205a1b133c41270771a8404334", + "resultdescription": "", + "newchecksum": "f599cfcc42950ae4be90414560a723de", + "bankstatus": "", + "resultcode": "", + "status": "N", + "trackingid": "0" + } + ) + end + + def successful_store_response + %( + { + "statusdescription": "Card tokenized successfully", + "checksum": "232df0e95a26a0efd2321cedbd1b6113", + "days": "90", + "status": "Y", + "token": "3hYBliyBBTL23joK1m1uul7GDT0Mph75" + } + ) + end + + def failed_store_response + %( + { + "statusdescription": "Card already tokenized for same merchant", + "checksum": "d8ae2ac457a0df3d1970dda78a260d5a", + "days": "", + "status": "N", + "token": "" + } + ) + end +end diff --git a/test/unit/gateways/cyber_source_test.rb b/test/unit/gateways/cyber_source_test.rb index 48be1113d18..c71a31aed9b 100644 --- a/test/unit/gateways/cyber_source_test.rb +++ b/test/unit/gateways/cyber_source_test.rb @@ -1,8 +1,11 @@ require 'test_helper' +require 'nokogiri' class CyberSourceTest < Test::Unit::TestCase + include CommStub + def setup - Base.gateway_mode = :test + Base.mode = :test @gateway = CyberSourceGateway.new( :login => 'l', @@ -10,55 +13,48 @@ def setup ) @amount = 100 + @customer_ip = '127.0.0.1' @credit_card = credit_card('4111111111111111', :brand => 'visa') + @elo_credit_card = credit_card('5067310000000010', :brand => 'elo') @declined_card = credit_card('801111111111111', :brand => 'visa') @check = check() - @options = { :billing_address => { - :address1 => '1234 My Street', - :address2 => 'Apt 1', - :company => 'Widgets Inc', - :city => 'Ottawa', - :state => 'ON', - :zip => 'K1C2N6', - :country => 'Canada', - :phone => '(555)555-5555' - }, - - :email => 'someguy1232@fakeemail.net', - :order_id => '1000', - :line_items => [ - { - :declared_value => @amount, - :quantity => 2, - :code => 'default', - :description => 'Giant Walrus', - :sku => 'WA323232323232323' - }, - { - :declared_value => @amount, - :quantity => 2, - :description => 'Marble Snowcone', - :sku => 'FAKE1232132113123' - } - ], - :currency => 'USD' + @options = { + :ip => @customer_ip, + :order_id => '1000', + :line_items => [ + { + :declared_value => @amount, + :quantity => 2, + :code => 'default', + :description => 'Giant Walrus', + :sku => 'WA323232323232323' + }, + { + :declared_value => @amount, + :quantity => 2, + :description => 'Marble Snowcone', + :sku => 'FAKE1232132113123' + } + ], + :currency => 'USD' } @subscription_options = { :order_id => generate_unique_id, - :email => 'someguy1232@fakeemail.net', :credit_card => @credit_card, :setup_fee => 100, - :billing_address => address, :subscription => { - :frequency => "weekly", + :frequency => 'weekly', :start_date => Date.today.next_week, :occurrences => 4, :automatic_renew => true, :amount => 100 } } + + @issuer_additional_data = 'PR25000000000011111111111112222222sk111111111111111111111111111' + + '1111111115555555222233101abcdefghijkl7777777777777777777777777promotionCde' end def test_successful_credit_card_purchase @@ -67,17 +63,68 @@ def test_successful_credit_card_purchase assert response = @gateway.purchase(@amount, @credit_card, @options) assert_equal 'Successful transaction', response.message assert_success response - assert_equal "#{@options[:order_id]};#{response.params['requestID']};#{response.params['requestToken']}", response.authorization + assert_equal "#{@options[:order_id]};#{response.params['requestID']};#{response.params['requestToken']};purchase;100;USD;", response.authorization assert response.test? end + def test_successful_credit_card_purchase_with_elo + @gateway.expects(:ssl_post).returns(successful_purchase_response) + + assert response = @gateway.purchase(@amount, @elo_credit_card, @options) + assert_equal 'Successful transaction', response.message + assert_success response + assert_equal "#{@options[:order_id]};#{response.params['requestID']};#{response.params['requestToken']};purchase;100;USD;", response.authorization + assert response.test? + end + + def test_purchase_includes_customer_ip + customer_ip_regexp = /<ipAddress>#{@customer_ip}<\// + @gateway.expects(:ssl_post). + with(anything, regexp_matches(customer_ip_regexp), anything). + returns('') + @gateway.expects(:parse).returns({}) + @gateway.purchase(@amount, @credit_card, @options) + end + + def test_purchase_includes_issuer_additional_data + stub_comms do + @gateway.purchase(100, @credit_card, order_id: '1', issuer_additional_data: @issuer_additional_data) + end.check_request do |endpoint, data, headers| + assert_match(/<issuer>\s+<additionalData>#{@issuer_additional_data}<\/additionalData>\s+<\/issuer>/m, data) + end.respond_with(successful_purchase_response) + end + + def test_purchase_includes_mdd_fields + stub_comms do + @gateway.purchase(100, @credit_card, order_id: '1', mdd_field_2: 'CustomValue2', mdd_field_3: 'CustomValue3') + end.check_request do |endpoint, data, headers| + assert_match(/field2>CustomValue2.*field3>CustomValue3</m, data) + end.respond_with(successful_purchase_response) + end + + def test_authorize_includes_issuer_additional_data + stub_comms do + @gateway.authorize(100, @credit_card, order_id: '1', issuer_additional_data: @issuer_additional_data) + end.check_request do |endpoint, data, headers| + assert_match(/<issuer>\s+<additionalData>#{@issuer_additional_data}<\/additionalData>\s+<\/issuer>/m, data) + end.respond_with(successful_authorization_response) + end + + def test_authorize_includes_mdd_fields + stub_comms do + @gateway.authorize(100, @credit_card, order_id: '1', mdd_field_2: 'CustomValue2', mdd_field_3: 'CustomValue3') + end.check_request do |endpoint, data, headers| + assert_match(/field2>CustomValue2.*field3>CustomValue3</m, data) + end.respond_with(successful_authorization_response) + end + def test_successful_check_purchase @gateway.expects(:ssl_post).returns(successful_purchase_response) assert response = @gateway.purchase(@amount, @check, @options) assert_equal 'Successful transaction', response.message assert_success response - assert_equal "#{@options[:order_id]};#{response.params['requestID']};#{response.params['requestToken']}", response.authorization + assert_equal "#{@options[:order_id]};#{response.params['requestID']};#{response.params['requestToken']};purchase;100;USD;", response.authorization assert response.test? end @@ -87,15 +134,70 @@ def test_successful_pinless_debit_card_purchase assert response = @gateway.purchase(@amount, @credit_card, @options.merge(:pinless_debit_card => true)) assert_equal 'Successful transaction', response.message assert_success response - assert_equal "#{@options[:order_id]};#{response.params['requestID']};#{response.params['requestToken']}", response.authorization + assert_equal "#{@options[:order_id]};#{response.params['requestID']};#{response.params['requestToken']};purchase;100;USD;", response.authorization assert response.test? end + def test_successful_credit_cart_purchase_single_request_ignore_avs + @gateway.expects(:ssl_post).with do |host, request_body| + assert_match %r'<ignoreAVSResult>true</ignoreAVSResult>', request_body + assert_not_match %r'<ignoreCVResult>', request_body + true + end.returns(successful_purchase_response) + + assert response = @gateway.purchase(@amount, @credit_card, @options.merge( + ignore_avs: true + )) + assert_success response + end + + def test_successful_credit_cart_purchase_single_request_without_ignore_avs + @gateway.expects(:ssl_post).with do |host, request_body| + assert_not_match %r'<ignoreAVSResult>', request_body + assert_not_match %r'<ignoreCVResult>', request_body + true + end.returns(successful_purchase_response) + + # globally ignored AVS for gateway instance: + @gateway.options[:ignore_avs] = true + + assert response = @gateway.purchase(@amount, @credit_card, @options.merge( + ignore_avs: false + )) + assert_success response + end + + def test_successful_credit_cart_purchase_single_request_ignore_ccv + @gateway.expects(:ssl_post).with do |host, request_body| + assert_not_match %r'<ignoreAVSResult>', request_body + assert_match %r'<ignoreCVResult>true</ignoreCVResult>', request_body + true + end.returns(successful_purchase_response) + + assert response = @gateway.purchase(@amount, @credit_card, @options.merge( + ignore_cvv: true + )) + assert_success response + end + + def test_successful_credit_cart_purchase_single_request_without_ignore_ccv + @gateway.expects(:ssl_post).with do |host, request_body| + assert_not_match %r'<ignoreAVSResult>', request_body + assert_not_match %r'<ignoreCVResult>', request_body + true + end.returns(successful_purchase_response) + + assert response = @gateway.purchase(@amount, @credit_card, @options.merge( + ignore_cvv: false + )) + assert_success response + end + def test_successful_reference_purchase @gateway.stubs(:ssl_post).returns(successful_create_subscription_response, successful_purchase_response) assert_success(response = @gateway.store(@credit_card, @subscription_options)) - assert_success(response_reference_purchase = @gateway.purchase(@amount, response.authorization, @options)) + assert_success(@gateway.purchase(@amount, response.authorization, @options)) assert response.test? end @@ -103,6 +205,17 @@ def test_unsuccessful_authorization @gateway.expects(:ssl_post).returns(unsuccessful_authorization_response) assert response = @gateway.purchase(@amount, @credit_card, @options) + refute_equal 'Successful transaction', response.message + assert_instance_of Response, response + assert_failure response + end + + def test_unsuccessful_authorization_with_reply + @gateway.expects(:ssl_post).returns(unsuccessful_authorization_response_with_reply) + + assert response = @gateway.purchase(@amount, @credit_card, @options) + refute_equal 'Successful transaction', response.message + assert_equal '481', response.params['reasonCode'] assert_instance_of Response, response assert_failure response end @@ -115,6 +228,14 @@ def test_successful_auth_request assert response.test? end + def test_successful_auth_with_elo_request + @gateway.stubs(:ssl_post).returns(successful_authorization_response) + assert response = @gateway.authorize(@amount, @elo_credit_card, @options) + assert_equal Response, response.class + assert response.success? + assert response.test? + end + def test_successful_credit_card_tax_request @gateway.stubs(:ssl_post).returns(successful_tax_response) assert response = @gateway.calculate_tax(@credit_card, @options) @@ -133,6 +254,16 @@ def test_successful_credit_card_capture_request assert response_capture.test? end + def test_successful_credit_card_capture_with_elo_request + @gateway.stubs(:ssl_post).returns(successful_authorization_response, successful_capture_response) + assert response = @gateway.authorize(@amount, @elo_credit_card, @options) + assert response.success? + assert response.test? + assert response_capture = @gateway.capture(@amount, response.authorization) + assert response_capture.success? + assert response_capture.test? + end + def test_successful_credit_card_purchase_request @gateway.stubs(:ssl_post).returns(successful_capture_response) assert response = @gateway.purchase(@amount, @credit_card, @options) @@ -140,23 +271,22 @@ def test_successful_credit_card_purchase_request assert response.test? end - def test_successful_check_purchase_request + def test_successful_credit_card_purchase_with_elo_request @gateway.stubs(:ssl_post).returns(successful_capture_response) - assert response = @gateway.purchase(@amount, @check, @options) + assert response = @gateway.purchase(@amount, @elo_credit_card, @options) assert response.success? assert response.test? end - def test_requires_error_on_purchase_without_order_id - assert_raise(ArgumentError){ @gateway.purchase(@amount, @credit_card, @options.delete_if{|key, val| key == :order_id}) } - end - - def test_requires_error_on_authorization_without_order_id - assert_raise(ArgumentError){ @gateway.purchase(@amount, @credit_card, @options.delete_if{|key, val| key == :order_id}) } + def test_successful_check_purchase_request + @gateway.stubs(:ssl_post).returns(successful_capture_response) + assert response = @gateway.purchase(@amount, @check, @options) + assert response.success? + assert response.test? end def test_requires_error_on_tax_calculation_without_line_items - assert_raise(ArgumentError){ @gateway.calculate_tax(@credit_card, @options.delete_if{|key, val| key == :line_items})} + assert_raise(ArgumentError) { @gateway.calculate_tax(@credit_card, @options.delete_if { |key, val| key == :line_items }) } end def test_default_currency @@ -218,11 +348,24 @@ def test_successful_refund_request @gateway.stubs(:ssl_post).returns(successful_capture_response, successful_refund_response) assert_success(response = @gateway.purchase(@amount, @credit_card, @options)) - assert_success(response_refund = @gateway.refund(@amount, response.authorization)) + assert_success(@gateway.refund(@amount, response.authorization)) + end + + def test_successful_refund_with_elo_request + @gateway.stubs(:ssl_post).returns(successful_capture_response, successful_refund_response) + assert_success(response = @gateway.purchase(@amount, @elo_credit_card, @options)) + + assert_success(@gateway.refund(@amount, response.authorization)) + end + + def test_successful_credit_to_card_request + @gateway.stubs(:ssl_post).returns(successful_card_credit_response) + + assert_success(@gateway.credit(@amount, @credit_card, @options)) end - def test_successful_credit_request - @gateway.stubs(:ssl_post).returns(successful_create_subscription_response, successful_credit_response) + def test_successful_credit_to_subscription_request + @gateway.stubs(:ssl_post).returns(successful_create_subscription_response, successful_subscription_credit_response) assert response = @gateway.store(@credit_card, @subscription_options) assert response.success? @@ -230,22 +373,314 @@ def test_successful_credit_request assert_success(@gateway.credit(@amount, response.authorization, @options)) end - def test_successful_auth_reversal_request - @gateway.stubs(:ssl_post).returns(successful_authorization_response) + def test_successful_void_capture_request + @gateway.stubs(:ssl_post).returns(successful_capture_response, successful_auth_reversal_response) + assert response_capture = @gateway.capture(@amount, '1846925324700976124593') + assert response_capture.success? + assert response_capture.test? + assert response_auth_reversal = @gateway.void(response_capture.authorization, @options) + assert response_auth_reversal.success? + end + + def test_successful_void_authorization_request + @gateway.stubs(:ssl_post).returns(successful_authorization_response, successful_void_response) assert response = @gateway.authorize(@amount, @credit_card, @options) assert response.success? - assert_success(@gateway.auth_reversal(@amount, response.authorization, @options)) + assert response.test? + assert response_void = @gateway.void(response.authorization, @options) + assert response_void.success? + end + + def test_successful_void_authorization_with_elo_request + @gateway.stubs(:ssl_post).returns(successful_authorization_response, successful_void_response) + assert response = @gateway.authorize(@amount, @elo_credit_card, @options) + assert response.success? + assert response.test? + assert response_void = @gateway.void(response.authorization, @options) + assert response_void.success? end def test_validate_pinless_debit_card_request @gateway.stubs(:ssl_post).returns(successful_validate_pinless_debit_card) assert response = @gateway.validate_pinless_debit_card(@credit_card, @options) assert response.success? - assert_success(@gateway.auth_reversal(@amount, response.authorization, @options)) + assert_success(@gateway.void(response.authorization, @options)) + end + + def test_validate_add_subscription_amount + stub_comms do + @gateway.store(@credit_card, @subscription_options) + end.check_request do |endpoint, data, headers| + assert_match %r(<amount>1.00<\/amount>), data + end.respond_with(successful_update_subscription_response) + end + + def test_successful_verify + response = stub_comms(@gateway, :ssl_request) do + @gateway.verify(@credit_card, @options) + end.respond_with(successful_authorization_response) + assert_success response + end + + def test_successful_verify_with_elo + response = stub_comms(@gateway, :ssl_request) do + @gateway.verify(@elo_credit_card, @options) + end.respond_with(successful_authorization_response) + assert_success response + end + + def test_unsuccessful_verify + response = stub_comms(@gateway, :ssl_request) do + @gateway.verify(@credit_card, @options) + end.respond_with(unsuccessful_authorization_response) + assert_failure response + assert_equal 'Invalid account number', response.message + end + + def test_successful_auth_with_network_tokenization_for_visa + credit_card = network_tokenization_credit_card('4111111111111111', + :brand => 'visa', + :transaction_id => '123', + :eci => '05', + :payment_cryptogram => '111111111100cryptogram' + ) + + response = stub_comms do + @gateway.authorize(@amount, credit_card, @options) + end.check_request do |_endpoint, body, _headers| + assert_xml_valid_to_xsd(body) + assert_match %r'<ccAuthService run=\"true\">\n <cavv>111111111100cryptogram</cavv>\n <commerceIndicator>vbv</commerceIndicator>\n <xid>111111111100cryptogram</xid>\n</ccAuthService>\n<paymentNetworkToken>\n <transactionType>1</transactionType>\n</paymentNetworkToken>', body + end.respond_with(successful_purchase_response) + + assert_success response + end + + def test_successful_purchase_with_network_tokenization_for_visa + credit_card = network_tokenization_credit_card('4111111111111111', + :brand => 'visa', + :transaction_id => '123', + :eci => '05', + :payment_cryptogram => '111111111100cryptogram' + ) + + response = stub_comms do + @gateway.purchase(@amount, credit_card, @options) + end.check_request do |_endpoint, body, _headers| + assert_xml_valid_to_xsd(body) + assert_match %r'<ccAuthService run="true">.+?<ccCaptureService run="true"/>'m, body + end.respond_with(successful_purchase_response) + + assert_success response + end + + def test_successful_auth_with_network_tokenization_for_mastercard + @gateway.expects(:ssl_post).with do |host, request_body| + assert_xml_valid_to_xsd(request_body) + assert_match %r'<ucaf>\n <authenticationData>111111111100cryptogram</authenticationData>\n <collectionIndicator>2</collectionIndicator>\n</ucaf>\n<ccAuthService run=\"true\">\n <commerceIndicator>spa</commerceIndicator>\n</ccAuthService>\n<paymentNetworkToken>\n <transactionType>1</transactionType>\n</paymentNetworkToken>', request_body + true + end.returns(successful_purchase_response) + + credit_card = network_tokenization_credit_card('5555555555554444', + :brand => 'master', + :transaction_id => '123', + :eci => '05', + :payment_cryptogram => '111111111100cryptogram' + ) + + assert response = @gateway.authorize(@amount, credit_card, @options) + assert_success response + end + + def test_successful_auth_with_network_tokenization_for_amex + @gateway.expects(:ssl_post).with do |host, request_body| + assert_xml_valid_to_xsd(request_body) + assert_match %r'<ccAuthService run=\"true\">\n <cavv>MTExMTExMTExMTAwY3J5cHRvZ3I=\n</cavv>\n <commerceIndicator>aesk</commerceIndicator>\n <xid>YW0=\n</xid>\n</ccAuthService>\n<paymentNetworkToken>\n <transactionType>1</transactionType>\n</paymentNetworkToken>', request_body + true + end.returns(successful_purchase_response) + + credit_card = network_tokenization_credit_card('378282246310005', + :brand => 'american_express', + :transaction_id => '123', + :eci => '05', + :payment_cryptogram => Base64.encode64('111111111100cryptogram') + ) + + assert response = @gateway.authorize(@amount, credit_card, @options) + assert_success response + end + + def test_successful_auth_first_unscheduled_stored_cred + @gateway.stubs(:ssl_post).returns(successful_authorization_response) + @options[:stored_credential] = { + :initiator => 'cardholder', + :reason_type => 'unscheduled', + :initial_transaction => true, + :network_transaction_id => '' + } + assert response = @gateway.authorize(@amount, @credit_card, @options) + assert_equal Response, response.class + assert response.success? + assert response.test? + end + + def test_successful_auth_subsequent_unscheduled_stored_cred + @gateway.stubs(:ssl_post).returns(successful_authorization_response) + @options[:stored_credential] = { + :initiator => 'merchant', + :reason_type => 'unscheduled', + :initial_transaction => false, + :network_transaction_id => '016150703802094' + } + assert response = @gateway.authorize(@amount, @credit_card, @options) + assert_equal Response, response.class + assert response.success? + assert response.test? + end + + def test_successful_auth_first_recurring_stored_cred + @gateway.stubs(:ssl_post).returns(successful_authorization_response) + @options[:stored_credential] = { + :initiator => 'cardholder', + :reason_type => 'recurring', + :initial_transaction => true, + :network_transaction_id => '' + } + assert response = @gateway.authorize(@amount, @credit_card, @options) + assert_equal Response, response.class + assert response.success? + assert response.test? + end + + def test_successful_auth_subsequent_recurring_stored_cred + @gateway.stubs(:ssl_post).returns(successful_authorization_response) + @options[:stored_credential] = { + :initiator => 'merchant', + :reason_type => 'recurring', + :initial_transaction => false, + :network_transaction_id => '016150703802094' + } + assert response = @gateway.authorize(@amount, @credit_card, @options) + assert_equal Response, response.class + assert response.success? + assert response.test? + end + + def test_nonfractional_currency_handling + @gateway.expects(:ssl_post).with do |host, request_body| + assert_match %r(<grandTotalAmount>1</grandTotalAmount>), request_body + assert_match %r(<currency>JPY</currency>), request_body + true + end.returns(successful_nonfractional_authorization_response) + + assert response = @gateway.authorize(100, @credit_card, @options.merge(currency: 'JPY')) + assert_success response + end + + def test_malformed_xml_handling + @gateway.expects(:ssl_post).returns(malformed_xml_response) + + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_match %r(Missing end tag for), response.message + assert response.test? + end + + def test_3ds_enroll_response + purchase = stub_comms do + @gateway.purchase(@amount, @credit_card, @options.merge(payer_auth_enroll_service: true)) + end.check_request do |endpoint, data, headers| + assert_match(/\<payerAuthEnrollService run=\"true\"\/\>/, data) + end.respond_with(threedeesecure_purchase_response) + + assert_failure purchase + assert_equal 'YTJycDdLR3RIVnpmMXNFejJyazA=', purchase.params['xid'] + assert_equal 'eNpVUe9PwjAQ/d6/ghA/r2tBYMvRBEUFFEKQEP1Yu1Om7gfdJoy/3nZsgk2a3Lveu757B+utRhw/oyo0CphjlskPbIXBsC25TvuPD/lkc3xn2d2R6y+3LWA5WuFOwA/qLExiwRzX4UAbSEwLrbYyzgVItbuZLkS353HWA1pDAhHq6Vgw3ule9/pAT5BALCMUqnwznZJCKwRaZQiopIhzXYpB1wXaAAKF/hbbPE8zn9L9fu9cUB2VREBtAQF6FrQsbJSZOQ9hIF7Xs1KNg6dVZzXdxGk0f1nc4+eslMfREKitIBDIHAV3WZ+Z2+Ku3/F8bjRXeQIysmrEFeOOa0yoIYHUfjQ6Icbt02XGTFRojbFqRmoQATykSYymxlD+YjPDWfntxBqrcusg8wbmWGcrXNFD4w3z2IkfVkZRy6H13mi9YhP9W/0vhyyqPw==', purchase.params['paReq'] + assert_equal 'https://0eafstag.cardinalcommerce.com/EAFService/jsp/v1/redirect', purchase.params['acsURL'] + end + + def test_3ds_validate_response + validation = stub_comms do + @gateway.purchase(@amount, @credit_card, @options.merge(payer_auth_validate_service: true, pares: 'ABC123')) + end.check_request do |endpoint, data, headers| + assert_match(/\<payerAuthValidateService run=\"true\"\>/, data) + assert_match(/\<signedPARes\>ABC123\<\/signedPARes\>/, data) + end.respond_with(successful_threedeesecure_validate_response) + + assert_success validation + end + + def test_scrub + assert_equal @gateway.scrub(pre_scrubbed), post_scrubbed + end + + def test_supports_scrubbing? + assert @gateway.supports_scrubbing? + end + + def test_supports_network_tokenization + assert_instance_of TrueClass, @gateway.supports_network_tokenization? + end + + def test_does_not_throw_on_invalid_xml + raw_response = mock + raw_response.expects(:body).returns(invalid_xml_response) + exception = ActiveMerchant::ResponseError.new(raw_response) + @gateway.expects(:ssl_post).raises(exception) + + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_failure response end private + def pre_scrubbed + <<-PRE_SCRUBBED + opening connection to ics2wstest.ic3.com:443... + opened + starting SSL for ics2wstest.ic3.com:443... + SSL established + <- "POST /commerce/1.x/transactionProcessor HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: ics2wstest.ic3.com\r\nContent-Length: 2459\r\n\r\n" + <- "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\">\n <s:Header>\n <wsse:Security s:mustUnderstand=\"1\" xmlns:wsse=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd\">\n <wsse:UsernameToken>\n <wsse:Username>test</wsse:Username>\n <wsse:Password Type=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText\">DT3MZm8t8BsDZC9ZoKl592lvlRbQCcEXmEcYlh3gZObo6zTLQdf2m5klbqXlTq31iTJ5/Ctl/Z5LFE60GFnWGR8Cn5GeXuToZNbMHAvZKZ3sw9tC3Hf4U3Dj8XS2EI4OBvA1jcw38hd3VEm0ZZCAQEDZCC+AnM2ya9417zqynYjwgSyPOfh6CfMlSJKTgxQJLot7jFxYNvM/s9yBZoh37wJZUXdZ9Bf/CH6O3tKzafbyfn5rK25+GeYN9koih4O8c+PLQepzj5miiR7bikFzgEnsVs6LaZdLM8Sx/XVXk+60h02lg/a6KdS3kmUvnTGOihg5JUnl2JucBpH/P4aQYZ==</wsse:Password>\n </wsse:UsernameToken>\n </wsse:Security>\n </s:Header>\n <s:Body xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\">\n <requestMessage xmlns=\"urn:schemas-cybersource-com:transaction-data-1.109\">\n <merchantID>test</merchantID>\n <merchantReferenceCode>734dda9bb6446f2f2638ab7faf34682f</merchantReferenceCode>\n <clientLibrary>Ruby Active Merchant</clientLibrary>\n <clientLibraryVersion>1.50.0</clientLibraryVersion>\n <clientEnvironment>x86_64-darwin14.0</clientEnvironment>\n<billTo>\n <firstName>Longbob</firstName>\n <lastName>Longsen</lastName>\n <street1>456 My Street</street1>\n <street2>Apt 1</street2>\n <city>Ottawa</city>\n <state>NC</state>\n <postalCode>K1C2N6</postalCode>\n <country>US</country>\n <company>Widgets Inc</company>\n <phoneNumber>(555)555-5555</phoneNumber>\n <email>someguy1232@fakeemail.net</email>\n</billTo>\n<shipTo>\n <firstName>Longbob</firstName>\n <lastName>Longsen</lastName>\n <street1/>\n <city/>\n <state/>\n <postalCode/>\n <country/>\n <email>someguy1232@fakeemail.net</email>\n</shipTo>\n<purchaseTotals>\n <currency>USD</currency>\n <grandTotalAmount>1.00</grandTotalAmount>\n</purchaseTotals>\n<card>\n <accountNumber>4111111111111111</accountNumber>\n <expirationMonth>09</expirationMonth>\n <expirationYear>2016</expirationYear>\n <cvNumber>123</cvNumber>\n <cardType>001</cardType>\n</card>\n<ccAuthService run=\"true\"/>\n<ccCaptureService run=\"true\"/>\n<businessRules>\n <ignoreAVSResult>true</ignoreAVSResult>\n <ignoreCVResult>true</ignoreCVResult>\n</businessRules>\n </requestMessage>\n </s:Body>\n</s:Envelope>\n" + -> "HTTP/1.1 200 OK\r\n" + -> "Server: Apache-Coyote/1.1\r\n" + -> "X-OPNET-Transaction-Trace: pid=18901,requestid=08985faa-d84a-4200-af8a-1d0a4d50f391\r\n" + -> "Set-Cookie: _op_aixPageId=a_233cede6-657e-481e-977d-a4a886dafd37; Path=/\r\n" + -> "Content-Type: text/xml\r\n" + -> "Content-Length: 1572\r\n" + -> "Date: Fri, 05 Jun 2015 13:01:57 GMT\r\n" + -> "Connection: close\r\n" + -> "\r\n" + reading 1572 bytes... + -> "<?xml version=\"1.0\" encoding=\"utf-8\"?><soap:Envelope xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">\n<soap:Header>\n<wsse:Security xmlns:wsse=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd\"><wsu:Timestamp xmlns:wsu=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd\" wsu:Id=\"Timestamp-513448318\"><wsu:Created>2015-06-05T13:01:57.974Z</wsu:Created></wsu:Timestamp></wsse:Security></soap:Header><soap:Body><c:replyMessage xmlns:c=\"urn:schemas-cybersource-com:transaction-data-1.109\"><c:merchantReferenceCode>734dda9bb6446f2f2638ab7faf34682f</c:merchantReferenceCode><c:requestID>4335093172165000001515</c:requestID><c:decision>ACCEPT</c:decision><c:reasonCode>100</c:reasonCode><c:requestToken>Ahj//wSR1gMBn41YRu/WIkGLlo3asGzCbBky4VOjHT9/xXHSYBT9/xXHSbSA+RQkhk0ky3SA3+mwMCcjrAYDPxqwjd+sKWXL</c:requestToken><c:purchaseTotals><c:currency>USD</c:currency></c:purchaseTotals><c:ccAuthReply><c:reasonCode>100</c:reasonCode><c:amount>1.00</c:amount><c:authorizationCode>888888</c:authorizationCode><c:avsCode>X</c:avsCode><c:avsCodeRaw>I1</c:avsCodeRaw><c:cvCode/><c:authorizedDateTime>2015-06-05T13:01:57Z</c:authorizedDateTime><c:processorResponse>100</c:processorResponse><c:reconciliationID>19475060MAIKBSQG</c:reconciliationID></c:ccAuthReply><c:ccCaptureReply><c:reasonCode>100</c:reasonCode><c:requestDateTime>2015-06-05T13:01:57Z</c:requestDateTime><c:amount>1.00</c:amount><c:reconciliationID>19475060MAIKBSQG</c:reconciliationID></c:ccCaptureReply></c:replyMessage></soap:Body></soap:Envelope>" + read 1572 bytes + Conn close + PRE_SCRUBBED + end + + def post_scrubbed + <<-POST_SCRUBBED + opening connection to ics2wstest.ic3.com:443... + opened + starting SSL for ics2wstest.ic3.com:443... + SSL established + <- "POST /commerce/1.x/transactionProcessor HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: ics2wstest.ic3.com\r\nContent-Length: 2459\r\n\r\n" + <- "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\">\n <s:Header>\n <wsse:Security s:mustUnderstand=\"1\" xmlns:wsse=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd\">\n <wsse:UsernameToken>\n <wsse:Username>test</wsse:Username>\n <wsse:Password Type=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText\">[FILTERED]</wsse:Password>\n </wsse:UsernameToken>\n </wsse:Security>\n </s:Header>\n <s:Body xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\">\n <requestMessage xmlns=\"urn:schemas-cybersource-com:transaction-data-1.109\">\n <merchantID>test</merchantID>\n <merchantReferenceCode>734dda9bb6446f2f2638ab7faf34682f</merchantReferenceCode>\n <clientLibrary>Ruby Active Merchant</clientLibrary>\n <clientLibraryVersion>1.50.0</clientLibraryVersion>\n <clientEnvironment>x86_64-darwin14.0</clientEnvironment>\n<billTo>\n <firstName>Longbob</firstName>\n <lastName>Longsen</lastName>\n <street1>456 My Street</street1>\n <street2>Apt 1</street2>\n <city>Ottawa</city>\n <state>NC</state>\n <postalCode>K1C2N6</postalCode>\n <country>US</country>\n <company>Widgets Inc</company>\n <phoneNumber>(555)555-5555</phoneNumber>\n <email>someguy1232@fakeemail.net</email>\n</billTo>\n<shipTo>\n <firstName>Longbob</firstName>\n <lastName>Longsen</lastName>\n <street1/>\n <city/>\n <state/>\n <postalCode/>\n <country/>\n <email>someguy1232@fakeemail.net</email>\n</shipTo>\n<purchaseTotals>\n <currency>USD</currency>\n <grandTotalAmount>1.00</grandTotalAmount>\n</purchaseTotals>\n<card>\n <accountNumber>[FILTERED]</accountNumber>\n <expirationMonth>09</expirationMonth>\n <expirationYear>2016</expirationYear>\n <cvNumber>[FILTERED]</cvNumber>\n <cardType>001</cardType>\n</card>\n<ccAuthService run=\"true\"/>\n<ccCaptureService run=\"true\"/>\n<businessRules>\n <ignoreAVSResult>true</ignoreAVSResult>\n <ignoreCVResult>true</ignoreCVResult>\n</businessRules>\n </requestMessage>\n </s:Body>\n</s:Envelope>\n" + -> "HTTP/1.1 200 OK\r\n" + -> "Server: Apache-Coyote/1.1\r\n" + -> "X-OPNET-Transaction-Trace: pid=18901,requestid=08985faa-d84a-4200-af8a-1d0a4d50f391\r\n" + -> "Set-Cookie: _op_aixPageId=a_233cede6-657e-481e-977d-a4a886dafd37; Path=/\r\n" + -> "Content-Type: text/xml\r\n" + -> "Content-Length: 1572\r\n" + -> "Date: Fri, 05 Jun 2015 13:01:57 GMT\r\n" + -> "Connection: close\r\n" + -> "\r\n" + reading 1572 bytes... + -> "<?xml version=\"1.0\" encoding=\"utf-8\"?><soap:Envelope xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">\n<soap:Header>\n<wsse:Security xmlns:wsse=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd\"><wsu:Timestamp xmlns:wsu=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd\" wsu:Id=\"Timestamp-513448318\"><wsu:Created>2015-06-05T13:01:57.974Z</wsu:Created></wsu:Timestamp></wsse:Security></soap:Header><soap:Body><c:replyMessage xmlns:c=\"urn:schemas-cybersource-com:transaction-data-1.109\"><c:merchantReferenceCode>734dda9bb6446f2f2638ab7faf34682f</c:merchantReferenceCode><c:requestID>4335093172165000001515</c:requestID><c:decision>ACCEPT</c:decision><c:reasonCode>100</c:reasonCode><c:requestToken>Ahj//wSR1gMBn41YRu/WIkGLlo3asGzCbBky4VOjHT9/xXHSYBT9/xXHSbSA+RQkhk0ky3SA3+mwMCcjrAYDPxqwjd+sKWXL</c:requestToken><c:purchaseTotals><c:currency>USD</c:currency></c:purchaseTotals><c:ccAuthReply><c:reasonCode>100</c:reasonCode><c:amount>1.00</c:amount><c:authorizationCode>888888</c:authorizationCode><c:avsCode>X</c:avsCode><c:avsCodeRaw>I1</c:avsCodeRaw><c:cvCode/><c:authorizedDateTime>2015-06-05T13:01:57Z</c:authorizedDateTime><c:processorResponse>100</c:processorResponse><c:reconciliationID>19475060MAIKBSQG</c:reconciliationID></c:ccAuthReply><c:ccCaptureReply><c:reasonCode>100</c:reasonCode><c:requestDateTime>2015-06-05T13:01:57Z</c:requestDateTime><c:amount>1.00</c:amount><c:reconciliationID>19475060MAIKBSQG</c:reconciliationID></c:ccCaptureReply></c:replyMessage></soap:Body></soap:Envelope>" + read 1572 bytes + Conn close + POST_SCRUBBED + end + def successful_purchase_response <<-XML <?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> @@ -270,6 +705,64 @@ def unsuccessful_authorization_response XML end + def unsuccessful_authorization_response_with_reply + <<-XML + <?xml version="1.0" encoding="utf-8"?> + <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> + <soap:Header> + <wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"> + <wsu:Timestamp xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" wsu:Id="Timestamp-5307043"> + <wsu:Created>2017-05-10T01:15:14.835Z</wsu:Created> + </wsu:Timestamp></wsse:Security> + </soap:Header> + <soap:Body> + <c:replyMessage xmlns:c="urn:schemas-cybersource-com:transaction-data-1.121"> + <c:merchantReferenceCode>TEST11111111111</c:merchantReferenceCode> + <c:requestID>1841784762620176127166</c:requestID> + <c:decision>REJECT</c:decision> + <c:reasonCode>481</c:reasonCode> + <c:requestToken>AMYJY9fl62i+vx2OEQYAx9zv/9UBZAAA5h5D</c:requestToken> + <c:purchaseTotals> + <c:currency>USD</c:currency> + </c:purchaseTotals> + <c:ccAuthReply> + <c:reasonCode>100</c:reasonCode> + <c:amount>1186.43</c:amount> + <c:authorizationCode>123456</c:authorizationCode> + <c:avsCode>N</c:avsCode> + <c:avsCodeRaw>N</c:avsCodeRaw> + <c:cvCode>M</c:cvCode> + <c:cvCodeRaw>M</c:cvCodeRaw> + <c:authorizedDateTime>2017-05-10T01:15:14Z</c:authorizedDateTime> + <c:processorResponse>00</c:processorResponse> + <c:reconciliationID>013445773WW7EWMB0RYI9</c:reconciliationID> + </c:ccAuthReply> + <c:afsReply> + <c:reasonCode>100</c:reasonCode> + <c:afsResult>96</c:afsResult> + <c:hostSeverity>1</c:hostSeverity> + <c:consumerLocalTime>20:15:14</c:consumerLocalTime> + <c:afsFactorCode>C^H</c:afsFactorCode> + <c:internetInfoCode>MM-IPBST</c:internetInfoCode> + <c:suspiciousInfoCode>MUL-EM</c:suspiciousInfoCode> + <c:velocityInfoCode>VEL-ADDR^VEL-CC^VEL-NAME</c:velocityInfoCode> + <c:ipCountry>us</c:ipCountry> + <c:ipState>nv</c:ipState><c:ipCity>las vegas</c:ipCity> + <c:ipRoutingMethod>fixed</c:ipRoutingMethod> + <c:scoreModelUsed>default</c:scoreModelUsed> + <c:cardBin>540510</c:cardBin> + <c:binCountry>US</c:binCountry> + <c:cardAccountType>PURCHASING</c:cardAccountType> + <c:cardScheme>MASTERCARD CREDIT</c:cardScheme> + <c:cardIssuer>werewrewrew.</c:cardIssuer> + </c:afsReply> + <c:decisionReply><c:casePriority>3</c:casePriority><c:activeProfileReply/></c:decisionReply> + </c:replyMessage> + </soap:Body> + </soap:Envelope> + XML + end + def successful_tax_response <<-XML <?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> @@ -316,7 +809,13 @@ def successful_refund_response XML end - def successful_credit_response + def successful_card_credit_response + <<-XML +<?xml version=\"1.0\" encoding=\"utf-8\"?><soap:Envelope xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">\n<soap:Header>\n<wsse:Security xmlns:wsse=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd\"><wsu:Timestamp xmlns:wsu=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd\" wsu:Id=\"Timestamp-1360351593\"><wsu:Created>2019-05-16T20:25:05.234Z</wsu:Created></wsu:Timestamp></wsse:Security></soap:Header><soap:Body><c:replyMessage xmlns:c=\"urn:schemas-cybersource-com:transaction-data-1.153\"><c:merchantReferenceCode>329b25a4540e05c731a4fb16112e4c72</c:merchantReferenceCode><c:requestID>5580383051126990804008</c:requestID><c:decision>ACCEPT</c:decision><c:reasonCode>100</c:reasonCode><c:requestToken>Ahj/7wSTLoNfMt0KyZQoGxDdm1ctGjlmo0/RdCA4BUafouhAdpAfJHYQyaSZbpAdvSeAnJl0GvmW6FZMoUAA/SE0</c:requestToken><c:purchaseTotals><c:currency>USD</c:currency></c:purchaseTotals><c:ccCreditReply><c:reasonCode>100</c:reasonCode><c:requestDateTime>2019-05-16T20:25:05Z</c:requestDateTime><c:amount>1.00</c:amount><c:reconciliationID>73594493</c:reconciliationID></c:ccCreditReply><c:acquirerMerchantNumber>000123456789012</c:acquirerMerchantNumber><c:pos><c:terminalID>01234567</c:terminalID></c:pos></c:replyMessage></soap:Body></soap:Envelope> + XML + end + + def successful_subscription_credit_response <<-XML <?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> <soap:Header> @@ -339,4 +838,65 @@ def successful_validate_pinless_debit_card <wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"><wsu:Timestamp xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" wsu:Id="Timestamp-190204278"><wsu:Created>2013-05-13T13:52:57.159Z</wsu:Created></wsu:Timestamp></wsse:Security></soap:Header><soap:Body><c:replyMessage xmlns:c="urn:schemas-cybersource-com:transaction-data-1.69"><c:merchantReferenceCode>6427013</c:merchantReferenceCode><c:requestID>3684531771310176056442</c:requestID><c:decision>ACCEPT</c:decision><c:reasonCode>100</c:reasonCode><c:requestToken>AhijbwSRj3pM2QqPs2j0Ip+xoJXIsAMPYZNJMq6PSbs5ATAA6z42</c:requestToken><c:pinlessDebitValidateReply><c:reasonCode>100</c:reasonCode><c:requestDateTime>2013-05-13T13:52:57Z</c:requestDateTime><c:status>Y</c:status></c:pinlessDebitValidateReply></c:replyMessage></soap:Body></soap:Envelope> XML end + + def successful_auth_reversal_response + <<-XML +<?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> +<soap:Header> +<wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"><wsu:Timestamp xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" wsu:Id="Timestamp-1818361101"><wsu:Created>2016-07-25T21:10:31.506Z</wsu:Created></wsu:Timestamp></wsse:Security></soap:Header><soap:Body><c:replyMessage xmlns:c="urn:schemas-cybersource-com:transaction-data-1.121"><c:merchantReferenceCode>296805293329eea14917a8d04c63a0c4</c:merchantReferenceCode><c:requestID>4694810311256262804010</c:requestID><c:decision>ACCEPT</c:decision><c:reasonCode>100</c:reasonCode><c:requestToken>Ahj//wSR/QMpn9U9RwRUIkG7Nm4cMm7KVRrS4tppCS5TonESgFLhgHRTp0gPkYP4ZNJMt0gO3pPFAnI/oGUyy27D1uIA+xVK</c:requestToken><c:purchaseTotals><c:currency>USD</c:currency></c:purchaseTotals><c:ccAuthReversalReply><c:reasonCode>100</c:reasonCode><c:amount>1.00</c:amount><c:processorResponse>100</c:processorResponse><c:requestDateTime>2016-07-25T21:10:31Z</c:requestDateTime></c:ccAuthReversalReply></c:replyMessage></soap:Body></soap:Envelope> + XML + end + + def successful_void_response + <<-XML +<?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> +<soap:Header> +<wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"><wsu:Timestamp xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" wsu:Id="Timestamp-753384332"><wsu:Created>2016-07-25T20:50:50.583Z</wsu:Created></wsu:Timestamp></wsse:Security></soap:Header><soap:Body><c:replyMessage xmlns:c="urn:schemas-cybersource-com:transaction-data-1.121"><c:merchantReferenceCode>bb3b1bb530192c9dd20f121686c91c40</c:merchantReferenceCode><c:requestID>4694798504476543904007</c:requestID><c:decision>ACCEPT</c:decision><c:reasonCode>100</c:reasonCode><c:requestToken>Ahj//wSR/QLVu2z/GtIOIkG7Nm4bNW7KPRrRY0mvYS4YB0I7QFLgkgkAA0gAwfwyaSZbpAdvSeeBOR/QLVqII/qE+QAA3yVt</c:requestToken><c:purchaseTotals><c:currency>USD</c:currency></c:purchaseTotals><c:voidReply><c:reasonCode>100</c:reasonCode><c:requestDateTime>2016-07-25T20:50:50Z</c:requestDateTime><c:amount>1.00</c:amount><c:currency>usd</c:currency></c:voidReply></c:replyMessage></soap:Body></soap:Envelope> + XML + end + + def successful_nonfractional_authorization_response + <<-XML +<?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> +<soap:Header> +<wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"><wsu:Timestamp xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" wsu:Id="Timestamp-32551101"><wsu:Created>2007-07-12T18:31:53.838Z</wsu:Created></wsu:Timestamp></wsse:Security></soap:Header><soap:Body><c:replyMessage xmlns:c="urn:schemas-cybersource-com:transaction-data-1.26"><c:merchantReferenceCode>TEST11111111111</c:merchantReferenceCode><c:requestID>1842651133440156177166</c:requestID><c:decision>ACCEPT</c:decision><c:reasonCode>100</c:reasonCode><c:requestToken>AP4JY+Or4xRonEAOERAyMzQzOTEzMEM0MFZaNUZCBgDH3fgJ8AEGAMfd+AnwAwzRpAAA7RT/</c:requestToken><c:purchaseTotals><c:currency>JPY</c:currency></c:purchaseTotals><c:ccAuthReply><c:reasonCode>100</c:reasonCode><c:amount>1</c:amount><c:authorizationCode>004542</c:authorizationCode><c:avsCode>A</c:avsCode><c:avsCodeRaw>I7</c:avsCodeRaw><c:authorizedDateTime>2007-07-12T18:31:53Z</c:authorizedDateTime><c:processorResponse>100</c:processorResponse><c:reconciliationID>23439130C40VZ2FB</c:reconciliationID></c:ccAuthReply></c:replyMessage></soap:Body></soap:Envelope> + XML + end + + def malformed_xml_response + <<-XML +<?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> +<soap:Header> +<wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"><wsu:Timestamp xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" wsu:Id="Timestamp-2636690"><wsu:Created>2008-01-15T21:42:03.343Z</wsu:Created></wsu:Timestamp></wsse:Security></soap:Header><soap:Body><c:replyMessage xmlns:c="urn:schemas-cybersource-com:transaction-data-1.26"><c:merchantReferenceCode>b0a6cf9aa07f1a8495f89c364bbd6a9a</c:merchantReferenceCode><c:requestID>2004333231260008401927</c:requestID><c:decision>ACCEPT</c:decision><c:reasonCode>100</c:reasonCode><c:requestToken>Afvvj7Ke2Fmsbq0wHFE2sM6R4GAptYZ0jwPSA+R9PhkyhFTb0KRjoE4+ynthZrG6tMBwjAtT</c:requestToken><c:purchaseTotals><c:currency>USD</c:currency></c:purchaseTotals><c:ccAuthReply><c:reasonCode>100</c:reasonCode><c:amount>1.00</c:amount><c:authorizationCode>123456</c:authorizationCode><c:avsCode>Y</c:avsCode><c:avsCodeRaw>Y</c:avsCodeRaw><c:cvCode>M</c:cvCode><c:cvCodeRaw>M</c:cvCodeRaw><c:authorizedDateTime>2008-01-15T21:42:03Z</c:authorizedDateTime><c:processorResponse>00</c:processorResponse><c:authFactorCode>U</c:authFactorCode><p></c:ccAuthReply></c:replyMessage></soap:Body></soap:Envelope> + XML + end + + def threedeesecure_purchase_response + <<-XML +<?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> +<soap:Header> +<wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"><wsu:Timestamp xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" wsu:Id="Timestamp-1347906680"><wsu:Created>2017-10-17T20:39:27.392Z</wsu:Created></wsu:Timestamp></wsse:Security></soap:Header><soap:Body><c:replyMessage xmlns:c="urn:schemas-cybersource-com:transaction-data-1.121"><c:merchantReferenceCode>1a5ba4804da54b384c6e8a2d8057ea99</c:merchantReferenceCode><c:requestID>5082727663166909004012</c:requestID><c:decision>REJECT</c:decision><c:reasonCode>475</c:reasonCode><c:requestToken>AhjzbwSTE4kEGDR65zjsGwFLjtwzsJ0gXLJx6Xb0ky3SA7ek8AYA/A17</c:requestToken><c:payerAuthEnrollReply><c:reasonCode>475</c:reasonCode><c:acsURL>https://0eafstag.cardinalcommerce.com/EAFService/jsp/v1/redirect</c:acsURL><c:paReq>eNpVUe9PwjAQ/d6/ghA/r2tBYMvRBEUFFEKQEP1Yu1Om7gfdJoy/3nZsgk2a3Lveu757B+utRhw/oyo0CphjlskPbIXBsC25TvuPD/lkc3xn2d2R6y+3LWA5WuFOwA/qLExiwRzX4UAbSEwLrbYyzgVItbuZLkS353HWA1pDAhHq6Vgw3ule9/pAT5BALCMUqnwznZJCKwRaZQiopIhzXYpB1wXaAAKF/hbbPE8zn9L9fu9cUB2VREBtAQF6FrQsbJSZOQ9hIF7Xs1KNg6dVZzXdxGk0f1nc4+eslMfREKitIBDIHAV3WZ+Z2+Ku3/F8bjRXeQIysmrEFeOOa0yoIYHUfjQ6Icbt02XGTFRojbFqRmoQATykSYymxlD+YjPDWfntxBqrcusg8wbmWGcrXNFD4w3z2IkfVkZRy6H13mi9YhP9W/0vhyyqPw==</c:paReq><c:proxyPAN>1198888</c:proxyPAN><c:xid>YTJycDdLR3RIVnpmMXNFejJyazA=</c:xid><c:proofXML>&lt;AuthProof&gt;&lt;Time&gt;2017 Oct 17 20:39:27&lt;/Time&gt;&lt;DSUrl&gt;https://csrtestcustomer34.cardinalcommerce.com/merchantacsfrontend/vereq.jsp?acqid=CYBS&lt;/DSUrl&gt;&lt;VEReqProof&gt;&lt;Message id="a2rp7KGtHVzf1sEz2rk0"&gt;&lt;VEReq&gt;&lt;version&gt;1.0.2&lt;/version&gt;&lt;pan&gt;XXXXXXXXXXXX0002&lt;/pan&gt;&lt;Merchant&gt;&lt;acqBIN&gt;469216&lt;/acqBIN&gt;&lt;merID&gt;1234567&lt;/merID&gt;&lt;/Merchant&gt;&lt;Browser&gt;&lt;deviceCategory&gt;0&lt;/deviceCategory&gt;&lt;/Browser&gt;&lt;/VEReq&gt;&lt;/Message&gt;&lt;/VEReqProof&gt;&lt;VEResProof&gt;&lt;Message id="a2rp7KGtHVzf1sEz2rk0"&gt;&lt;VERes&gt;&lt;version&gt;1.0.2&lt;/version&gt;&lt;CH&gt;&lt;enrolled&gt;Y&lt;/enrolled&gt;&lt;acctID&gt;1198888&lt;/acctID&gt;&lt;/CH&gt;&lt;url&gt;https://testcustomer34.cardinalcommerce.com/merchantacsfrontend/pareq.jsp?vaa=b&amp;amp;gold=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA&lt;/url&gt;&lt;protocol&gt;ThreeDSecure&lt;/protocol&gt;&lt;/VERes&gt;&lt;/Message&gt;&lt;/VEResProof&gt;&lt;/AuthProof&gt;</c:proofXML><c:veresEnrolled>Y</c:veresEnrolled><c:authenticationPath>ENROLLED</c:authenticationPath></c:payerAuthEnrollReply></c:replyMessage></soap:Body></soap:Envelope> + XML + end + + def successful_threedeesecure_validate_response + <<-XML +<?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> +<soap:Header> +<wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"><wsu:Timestamp xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" wsu:Id="Timestamp-635495097"><wsu:Created>2018-05-01T14:28:36.773Z</wsu:Created></wsu:Timestamp></wsse:Security></soap:Header><soap:Body><c:replyMessage xmlns:c="urn:schemas-cybersource-com:transaction-data-1.121"><c:merchantReferenceCode>23751b5aeb076ea5940c5b656284bf6a</c:merchantReferenceCode><c:requestID>5251849164756591904009</c:requestID><c:decision>ACCEPT</c:decision><c:reasonCode>100</c:reasonCode><c:requestToken>Ahj//wSTHLQMXdtQnQUJGxDds0bNnDRoo0+VcdXMBUafKuOrnpAuWT9zDJpJlukB29J4YBpMctAxd21CdBQkwQ3g</c:requestToken><c:purchaseTotals><c:currency>USD</c:currency></c:purchaseTotals><c:ccAuthReply><c:reasonCode>100</c:reasonCode><c:amount>12.02</c:amount><c:authorizationCode>831000</c:authorizationCode><c:avsCode>Y</c:avsCode><c:avsCodeRaw>Y</c:avsCodeRaw><c:authorizedDateTime>2018-05-01T14:28:36Z</c:authorizedDateTime><c:processorResponse>00</c:processorResponse><c:reconciliationID>ZLIU5GM27GBP</c:reconciliationID><c:authRecord>0110322000000E10000200000000000000120205011428360272225A4C495535474D32374742503833313030303030000159004400103232415050524F56414C0022313457303136313530373033383032303934473036340006564943524120</c:authRecord></c:ccAuthReply><c:ccCaptureReply><c:reasonCode>100</c:reasonCode><c:requestDateTime>2018-05-01T14:28:36Z</c:requestDateTime><c:amount>12.02</c:amount><c:reconciliationID>76466844</c:reconciliationID></c:ccCaptureReply><c:payerAuthValidateReply><c:reasonCode>100</c:reasonCode><c:authenticationResult>0</c:authenticationResult><c:authenticationStatusMessage>Success</c:authenticationStatusMessage><c:cavv>AAABAWFlmQAAAABjRWWZEEFgFz+=</c:cavv><c:cavvAlgorithm>2</c:cavvAlgorithm><c:commerceIndicator>vbv</c:commerceIndicator><c:eci>05</c:eci><c:eciRaw>05</c:eciRaw><c:xid>S2R4eGtHbEZqbnozeGhBRHJ6QzA=</c:xid><c:paresStatus>Y</c:paresStatus></c:payerAuthValidateReply></c:replyMessage></soap:Body></soap:Envelope> + XML + end + + def invalid_xml_response + "What's all this then, govna?</p>" + end + + def assert_xml_valid_to_xsd(data, root_element = '//s:Body/*') + schema_file = File.open("#{File.dirname(__FILE__)}/../../schema/cyber_source/CyberSourceTransaction_#{CyberSourceGateway::TEST_XSD_VERSION}.xsd") + doc = Nokogiri::XML(data) + root = Nokogiri::XML(doc.xpath(root_element).to_s) + xsd = Nokogiri::XML::Schema(schema_file) + errors = xsd.validate(root) + assert_empty errors, "XSD validation errors in the following XML:\n#{root}" + end end diff --git a/test/unit/gateways/d_local_test.rb b/test/unit/gateways/d_local_test.rb new file mode 100644 index 00000000000..bb4d1df6aac --- /dev/null +++ b/test/unit/gateways/d_local_test.rb @@ -0,0 +1,245 @@ +require 'test_helper' + +class DLocalTest < Test::Unit::TestCase + include CommStub + + def setup + @gateway = DLocalGateway.new(login: 'login', trans_key: 'password', secret_key: 'shhhhh_key') + @credit_card = credit_card + @amount = 100 + + @options = { + order_id: '1', + billing_address: address + } + end + + def test_successful_purchase + @gateway.expects(:ssl_post).returns(successful_purchase_response) + + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + + assert_equal 'D-15104-05b0ec0c-5a1e-470a-b342-eb5f20758ef7', response.authorization + assert response.test? + end + + def test_failed_purchase + @gateway.expects(:ssl_post).returns(failed_purchase_response) + + response = @gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_equal '300', response.error_code + end + + def test_successful_authorize + @gateway.expects(:ssl_post).returns(successful_authorize_response) + + response = @gateway.authorize(@amount, @credit_card, @options) + assert_success response + + assert_equal 'D-15104-be03e883-3e6b-497d-840e-54c8b6209bc3', response.authorization + end + + def test_successful_authorize_without_address + @gateway.expects(:ssl_post).returns(successful_authorize_response) + + response = @gateway.authorize(@amount, @credit_card, @options.delete(:billing_address)) + assert_success response + + assert_equal 'D-15104-be03e883-3e6b-497d-840e-54c8b6209bc3', response.authorization + end + + def test_passing_country_as_string + stub_comms(@gateway, :ssl_request) do + @gateway.authorize(@amount, @credit_card, @options) + end.check_request do |method, endpoint, data, headers| + assert_match(/"country\":\"CA\"/, data) + end.respond_with(successful_authorize_response) + end + + def test_failed_authorize + @gateway.expects(:ssl_post).returns(failed_authorize_response) + + response = @gateway.authorize(@amount, @credit_card, @options) + assert_failure response + assert_equal '309', response.error_code + end + + def test_successful_capture + @gateway.expects(:ssl_post).returns(successful_capture_response) + + response = @gateway.capture(@amount, 'D-15104-be03e883-3e6b-497d-840e-54c8b6209bc3', @options) + assert_success response + + assert_equal 'D-15104-5a914b68-afb8-44f8-a849-8cf09ab6c246', response.authorization + end + + def test_failed_capture + @gateway.expects(:ssl_post).returns(failed_capture_response) + + response = @gateway.capture(@amount, 'D-15104-be03e883-3e6b-497d-840e-54c8b6209bc3', @options) + assert_failure response + assert_equal '4000', response.error_code + end + + def test_successful_refund + @gateway.expects(:ssl_post).returns(successful_refund_response) + + response = @gateway.refund(@amount, 'D-15104-be03e883-3e6b-497d-840e-54c8b6209bc3', @options) + assert_success response + + assert_equal 'REF-15104-a9cc29e5-1895-4cec-94bd-aa16c3b92570', response.authorization + end + + def test_pending_refund + @gateway.expects(:ssl_post).returns(pending_refund_response) + + response = @gateway.refund(@amount, 'D-15104-be03e883-3e6b-497d-840e-54c8b6209bc3', @options) + assert_success response + + assert_equal 'REF-15104-a9cc29e5-1895-4cec-94bd-aa16c3b92570', response.authorization + end + + def test_failed_refund + @gateway.expects(:ssl_post).returns(failed_refund_response) + + response = @gateway.refund(@amount, 'D-15104-be03e883-3e6b-497d-840e-54c8b6209bc3', @options) + assert_failure response + assert_equal '5007', response.error_code + end + + def test_successful_void + @gateway.expects(:ssl_post).returns(successful_void_response) + + response = @gateway.void('D-15104-be03e883-3e6b-497d-840e-54c8b6209bc3', @options) + assert_success response + + assert_equal 'D-15104-c147279d-14ab-4537-8ba6-e3e1cde0f8d2', response.authorization + end + + def test_failed_void + @gateway.expects(:ssl_post).returns(failed_void_response) + + response = @gateway.void('D-15104-be03e883-3e6b-497d-840e-54c8b6209bc3', @options) + assert_failure response + + assert_equal '5002', response.error_code + end + + def test_successful_verify + @gateway.expects(:ssl_request).times(2).returns(successful_authorize_response, successful_void_response) + + response = @gateway.verify(@credit_card, @options) + assert_success response + + assert_equal 'D-15104-be03e883-3e6b-497d-840e-54c8b6209bc3', response.authorization + end + + def test_successful_verify_with_failed_void + @gateway.expects(:ssl_request).times(2).returns(successful_authorize_response, failed_void_response) + + response = @gateway.verify(@credit_card, @options) + assert_success response + + assert_equal 'D-15104-be03e883-3e6b-497d-840e-54c8b6209bc3', response.authorization + end + + def test_failed_verify + @gateway.expects(:ssl_post).returns(failed_authorize_response) + + response = @gateway.verify(@credit_card, @options) + assert_failure response + assert_equal '309', response.error_code + end + + def test_scrub + assert @gateway.supports_scrubbing? + assert_equal @gateway.scrub(pre_scrubbed), post_scrubbed + end + + private + + def pre_scrubbed + %q( + <- "POST /secure_payments/ HTTP/1.1\r\nContent-Type: application/json\r\nX-Date: 2018-12-04T18:24:21Z\r\nX-Login: aeaf9bbfa1\r\nX-Trans-Key: 9de3769b7e\r\nAuthorization: V2-HMAC-SHA256, Signature: d58d0e87a59af50ff974dfeea176c067354682aa74a8ac115912576d4214a776\r\nConnection: close\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nHost: sandbox.dlocal.com\r\nContent-Length: 441\r\n\r\n" + <- "{\"amount\":\"1.00\",\"currency\":\"BRL\",\"payment_method_id\":\"CARD\",\"payment_method_type\":\"CARD\",\"payment_method_flow\":\"DIRECT\",\"country\":\"BR\",\"payer\":{\"name\":\"Longbob Longsen\",\"phone\":\"(555)555-5555\",\"document\":\"42243309114\",\"address\":null},\"card\":{\"holder_name\":\"Longbob Longsen\",\"expiration_month\":9,\"expiration_year\":2019,\"number\":\"4111111111111111\",\"cvv\":\"123\",\"capture\":true},\"order_id\":\"62595c5db10fdf7b5d5bb3a16d130992\",\"description\":\"200\"}" + -> "HTTP/1.1 200 OK\r\n" + -> "Server: Reblaze Secure Web Gateway\r\n" + -> "Date: Tue, 04 Dec 2018 18:24:22 GMT\r\n" + -> "Content-Type: application/json;charset=utf-8\r\n" + -> "Content-Length: 565\r\n" + -> "Strict-Transport-Security: max-age=31536000; includeSubDomains\r\n" + -> "Via: 1.1 google\r\n" + -> "Alt-Svc: clear\r\n" + -> "Connection: close\r\n" + -> "\r\n" + reading 565 bytes... + -> "{\"id\":\"D-15104-9f5246d5-34e2-4f63-9d29-380ab1567ec9\",\"amount\":1.00,\"currency\":\"BRL\",\"payment_method_id\":\"CARD\",\"payment_method_type\":\"CARD\",\"payment_method_flow\":\"DIRECT\",\"country\":\"BR\",\"card\":{\"holder_name\":\"Longbob Longsen\",\"expiration_month\":9,\"expiration_year\":2019,\"brand\":\"VI\",\"last4\":\"1111\",\"card_id\":\"CV-434cb5d1-aece-4878-8ce2-24f887fc7ff5\"},\"created_date\":\"2018-12-04T18:24:21.000+0000\",\"approved_date\":\"2018-12-04T18:24:22.000+0000\",\"status\":\"PAID\",\"status_detail\":\"The payment was paid\",\"status_code\":\"200\",\"order_id\":\"62595c5db10fdf7b5d5bb3a16d130992\"}" + ) + end + + def post_scrubbed + %q( + <- "POST /secure_payments/ HTTP/1.1\r\nContent-Type: application/json\r\nX-Date: 2018-12-04T18:24:21Z\r\nX-Login: aeaf9bbfa1\r\nX-Trans-Key: [FILTERED]\r\nAuthorization: V2-HMAC-SHA256, Signature: d58d0e87a59af50ff974dfeea176c067354682aa74a8ac115912576d4214a776\r\nConnection: close\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nHost: sandbox.dlocal.com\r\nContent-Length: 441\r\n\r\n" + <- "{\"amount\":\"1.00\",\"currency\":\"BRL\",\"payment_method_id\":\"CARD\",\"payment_method_type\":\"CARD\",\"payment_method_flow\":\"DIRECT\",\"country\":\"BR\",\"payer\":{\"name\":\"Longbob Longsen\",\"phone\":\"(555)555-5555\",\"document\":\"42243309114\",\"address\":null},\"card\":{\"holder_name\":\"Longbob Longsen\",\"expiration_month\":9,\"expiration_year\":2019,\"number\":\"[FILTERED]\",\"cvv\":\"[FILTERED]\",\"capture\":true},\"order_id\":\"62595c5db10fdf7b5d5bb3a16d130992\",\"description\":\"200\"}" + -> "HTTP/1.1 200 OK\r\n" + -> "Server: Reblaze Secure Web Gateway\r\n" + -> "Date: Tue, 04 Dec 2018 18:24:22 GMT\r\n" + -> "Content-Type: application/json;charset=utf-8\r\n" + -> "Content-Length: 565\r\n" + -> "Strict-Transport-Security: max-age=31536000; includeSubDomains\r\n" + -> "Via: 1.1 google\r\n" + -> "Alt-Svc: clear\r\n" + -> "Connection: close\r\n" + -> "\r\n" + reading 565 bytes... + -> "{\"id\":\"D-15104-9f5246d5-34e2-4f63-9d29-380ab1567ec9\",\"amount\":1.00,\"currency\":\"BRL\",\"payment_method_id\":\"CARD\",\"payment_method_type\":\"CARD\",\"payment_method_flow\":\"DIRECT\",\"country\":\"BR\",\"card\":{\"holder_name\":\"Longbob Longsen\",\"expiration_month\":9,\"expiration_year\":2019,\"brand\":\"VI\",\"last4\":\"1111\",\"card_id\":\"CV-434cb5d1-aece-4878-8ce2-24f887fc7ff5\"},\"created_date\":\"2018-12-04T18:24:21.000+0000\",\"approved_date\":\"2018-12-04T18:24:22.000+0000\",\"status\":\"PAID\",\"status_detail\":\"The payment was paid\",\"status_code\":\"200\",\"order_id\":\"62595c5db10fdf7b5d5bb3a16d130992\"}" + ) + end + + def successful_purchase_response + '{"id":"D-15104-05b0ec0c-5a1e-470a-b342-eb5f20758ef7","amount":1.00,"currency":"BRL","payment_method_id":"CARD","payment_method_type":"CARD","payment_method_flow":"DIRECT","country":"BR","card":{"holder_name":"Longbob Longsen","expiration_month":9,"expiration_year":2019,"brand":"VI","last4":"1111","card_id":"CV-993903e4-0b33-48fd-8d9b-99fd6c3f0d1a"},"created_date":"2018-12-06T20:20:41.000+0000","approved_date":"2018-12-06T20:20:42.000+0000","status":"PAID","status_detail":"The payment was paid","status_code":"200","order_id":"15940ef43d39331bc64f31341f8ccd93"}' + end + + def failed_purchase_response + '{"id":"D-15104-c3027e67-21f8-4308-8c94-06c44ffcea67","amount":1.00,"currency":"BRL","payment_method_id":"CARD","payment_method_type":"CARD","payment_method_flow":"DIRECT","country":"BR","card":{"holder_name":"Longbob Longsen","expiration_month":9,"expiration_year":2019,"brand":"VI","last4":"1111","card_id":"CV-529b0bb1-8b8a-42f4-b5e4-d358ffb2c978"},"created_date":"2018-12-06T20:22:40.000+0000","status":"REJECTED","status_detail":"The payment was rejected.","status_code":"300","order_id":"7aa5cd3200f287fbac51dcee32184260"}' + end + + def successful_authorize_response + '{"id":"D-15104-be03e883-3e6b-497d-840e-54c8b6209bc3","amount":1.00,"currency":"BRL","payment_method_id":"CARD","payment_method_type":"CARD","payment_method_flow":"DIRECT","country":"BR","card":{"holder_name":"Longbob Longsen","expiration_month":9,"expiration_year":2019,"brand":"VI","last4":"1111","card_id":"CV-ecd897ac-5361-45a1-a407-aaab044ce87e"},"created_date":"2018-12-06T20:24:46.000+0000","approved_date":"2018-12-06T20:24:46.000+0000","status":"AUTHORIZED","status_detail":"The payment was authorized","status_code":"600","order_id":"5694b51b79df484578158d7790b4aacf"}' + end + + def failed_authorize_response + '{"id":"D-15104-e6ed3df3-1380-46c6-92d4-29f0f567f799","amount":1.00,"currency":"BRL","payment_method_id":"CARD","payment_method_type":"CARD","payment_method_flow":"DIRECT","country":"BR","card":{"holder_name":"Longbob Longsen","expiration_month":9,"expiration_year":2019,"brand":"VI","last4":"1111","card_id":"CV-a6326a1d-b706-4e89-9dff-091d73d85b26"},"created_date":"2018-12-06T20:26:57.000+0000","status":"REJECTED","status_detail":"Card expired.","status_code":"309","order_id":"8ecd3101ba7a9a2d6ccb6465d33ff10d"}' + end + + def successful_capture_response + '{"id":"D-15104-5a914b68-afb8-44f8-a849-8cf09ab6c246","amount":1.00,"currency":"BRL","payment_method_id":"VI","payment_method_type":"CARD","payment_method_flow":"DIRECT","country":"BR","created_date":"2018-12-06T20:26:17.000+0000","approved_date":"2018-12-06T20:26:18.000+0000","status":"PAID","status_detail":"The payment was paid","status_code":"200","order_id":"f8276e468120faf3e7252e33ac5f9a73"}' + end + + def failed_capture_response + '{"code":4000,"message":"Payment not found"}' + end + + def successful_refund_response + '{"id":"REF-15104-a9cc29e5-1895-4cec-94bd-aa16c3b92570","payment_id":"D-15104-f9e16b85-5fc8-40f0-a4d8-4e73a892594f","status":"SUCCESS","currency":"BRL","created_date":"2018-12-06T20:28:37.000+0000","amount":1.00,"status_code":200,"status_detail":"The refund was paid","notification_url":"http://example.com","amount_refunded":1.00,"id_payment":"D-15104-f9e16b85-5fc8-40f0-a4d8-4e73a892594f"}' + end + + # I can't invoke a pending response and there is no example in docs, so this response is speculative + def pending_refund_response + '{"id":"REF-15104-a9cc29e5-1895-4cec-94bd-aa16c3b92570","payment_id":"D-15104-f9e16b85-5fc8-40f0-a4d8-4e73a892594f","status":"PENDING","currency":"BRL","created_date":"2018-12-06T20:28:37.000+0000","amount":1.00,"status_code":100,"status_detail":"The refund is pending","notification_url":"http://example.com","amount_refunded":1.00,"id_payment":"D-15104-f9e16b85-5fc8-40f0-a4d8-4e73a892594f"}' + end + + def failed_refund_response + '{"code":5007,"message":"Amount exceeded"}' + end + + def successful_void_response + '{"id":"D-15104-c147279d-14ab-4537-8ba6-e3e1cde0f8d2","amount":1.00,"currency":"BRL","payment_method_id":"VI","payment_method_type":"CARD","payment_method_flow":"DIRECT","country":"BR","created_date":"2018-12-06T20:38:01.000+0000","approved_date":"2018-12-06T20:38:01.000+0000","status":"CANCELLED","status_detail":"The payment was cancelled","status_code":"400","order_id":"46d8978863be935d892cfa3e992f65f3"}' + end + + def failed_void_response + '{"code":5002,"message":"Invalid transaction status"}' + end +end diff --git a/test/unit/gateways/data_cash_test.rb b/test/unit/gateways/data_cash_test.rb index dd999474fd6..2fb5ac40d10 100644 --- a/test/unit/gateways/data_cash_test.rb +++ b/test/unit/gateways/data_cash_test.rb @@ -1,9 +1,6 @@ require 'test_helper' class DataCashTest < Test::Unit::TestCase - # 100 Cents - AMOUNT = 100 - def setup @gateway = DataCashGateway.new( :login => 'LOGIN', @@ -11,8 +8,10 @@ def setup ) @credit_card = credit_card('4242424242424242') - - @address = { + + @amount = 100 + + @address = { :name => 'Mark McBride', :address1 => 'Flat 12/3', :address2 => '45 Main Road', @@ -22,16 +21,16 @@ def setup :zip => 'A987AA', :phone => '(555)555-5555' } - + @options = { :order_id => generate_unique_id, :billing_address => @address } end - + def test_successful_purchase @gateway.expects(:ssl_post).returns(successful_purchase_response) - + response = @gateway.purchase(@amount, @credit_card, @options) assert_instance_of Response, response assert_success response @@ -48,64 +47,64 @@ def test_credit def test_deprecated_credit @gateway.expects(:ssl_post).with(anything, regexp_matches(/<method>txn_refund<\/method>/)).returns(successful_purchase_response) - assert_deprecation_warning(Gateway::CREDIT_DEPRECATION_MESSAGE, @gateway) do - @gateway.credit(@amount, "transaction_id", @options) + assert_deprecation_warning(Gateway::CREDIT_DEPRECATION_MESSAGE) do + @gateway.credit(@amount, 'transaction_id', @options) end end def test_refund @gateway.expects(:ssl_post).with(anything, regexp_matches(/<method>txn_refund<\/method>/)).returns(successful_purchase_response) - @gateway.refund(@amount, "transaction_id", @options) + @gateway.refund(@amount, 'transaction_id', @options) end def test_unsuccessful_purchase @gateway.expects(:ssl_post).returns(failed_purchase_response) - + response = @gateway.purchase(@amount, @credit_card, @options) assert_instance_of Response, response assert_failure response assert response.test? assert_equal 'DECLINED', response.message end - + def test_error_response @gateway.expects(:ssl_post).returns(failed_purchase_response) - + response = @gateway.purchase(@amount, @credit_card, @options) assert_instance_of Response, response assert_failure response assert response.test? assert_equal 'DECLINED', response.message end - + def test_supported_countries assert_equal ['GB'], DataCashGateway.supported_countries end - + def test_supported_card_types - assert_equal [ :visa, :master, :american_express, :discover, :diners_club, :jcb, :maestro, :switch, :solo, :laser ], DataCashGateway.supported_cardtypes + assert_equal [ :visa, :master, :american_express, :discover, :diners_club, :jcb, :maestro ], DataCashGateway.supported_cardtypes end - + def test_purchase_with_missing_order_id_option - assert_raise(ArgumentError){ @gateway.purchase(100, @credit_card, {}) } + assert_raise(ArgumentError) { @gateway.purchase(100, @credit_card, {}) } end - + def test_authorize_with_missing_order_id_option - assert_raise(ArgumentError){ @gateway.authorize(100, @credit_card, {}) } + assert_raise(ArgumentError) { @gateway.authorize(100, @credit_card, {}) } end - + def test_purchase_does_not_raise_exception_with_missing_billing_address @gateway.expects(:ssl_post).returns(successful_purchase_response) assert @gateway.authorize(100, @credit_card, {:order_id => generate_unique_id }).is_a?(ActiveMerchant::Billing::Response) end - + def test_continuous_authority_purchase_with_missing_continuous_authority_reference assert_raise(ArgumentError) do - @gateway.authorize(100, "a;b;", @options) + @gateway.authorize(100, 'a;b;', @options) end end - + def test_successful_continuous_authority_purchase @gateway.expects(:ssl_post).returns(successful_purchase_using_continuous_authority_response) @@ -115,8 +114,15 @@ def test_successful_continuous_authority_purchase assert response.test? assert_equal 'ACCEPTED', response.message end - + + def test_capture_method_is_ecomm + @gateway.expects(:ssl_post).with(anything, regexp_matches(/<capturemethod>ecomm<\/capturemethod>/)).returns(successful_purchase_response) + response = @gateway.purchase(100, @credit_card, @options) + assert_success response + end + private + def failed_purchase_response <<-XML <Response> @@ -134,7 +140,7 @@ def failed_purchase_response </Response> XML end - + def successful_purchase_response <<-XML <Response> @@ -182,5 +188,70 @@ def successful_purchase_using_continuous_authority_response <time>1363364966</time> </Response> XML - end + end + + def test_transcript_scrubbing + assert @gateway.supports_scrubbing? + assert_equal @gateway.scrub(pre_scrub), post_scrub + end + + def pre_scrub + <<-RAW +opening connection to testserver.datacash.com:443... +opened +starting SSL for testserver.datacash.com:443... +SSL established +<- "POST /Transaction HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: testserver.datacash.com\r\nContent-Length: 1067\r\n\r\n" +<- "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Request version=\"2\">\n <Authentication>\n <client>99626800</client>\n <password>9YM3DjUa6</password>\n </Authentication>\n <Transaction>\n <CardTxn>\n <method>auth</method>\n <Card>\n <pan>4539792100000003</pan>\n <expirydate>03/20</expirydate>\n <Cv2Avs>\n <cv2>444</cv2>\n <ExtendedPolicy>\n <cv2_policy notprovided=\"reject\" notchecked=\"reject\" matched=\"accept\" notmatched=\"reject\" partialmatch=\"reject\"/>\n <postcode_policy notprovided=\"accept\" notchecked=\"accept\" matched=\"accept\" notmatched=\"reject\" partialmatch=\"accept\"/>\n <address_policy notprovided=\"accept\" notchecked=\"accept\" matched=\"accept\" notmatched=\"reject\" partialmatch=\"accept\"/>\n </ExtendedPolicy>\n </Cv2Avs>\n </Card>\n </CardTxn>\n <TxnDetails>\n <merchantreference>d36e05ce3604313963854fca858d11</merchantreference>\n <amount currency=\"GBP\">1.98</amount>\n <capturemethod>ecomm</capturemethod>\n </TxnDetails>\n </Transaction>\n</Request>\n" +-> "HTTP/1.1 200 OK\r\n" +-> "Date: Wed, 03 Jan 2018 21:24:38 GMT\r\n" +-> "Server: Apache\r\n" +-> "Connection: close\r\n" +-> "Transfer-Encoding: chunked\r\n" +-> "Content-Type: text/plain; charset=iso-8859-1\r\n" +-> "\r\n" +-> "559\r\n" +reading 1369 bytes... +-> "" +-> "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Response version='2'>\n <CardTxn>\n <Cv2Avs>\n <address_policy matched='accept' notchecked='accept' notmatched='reject' notprovided='accept' partialmatch='accept'></address_policy>\n <address_result numeric='1'>notchecked</address_result>\n <cv2_policy matched='accept' notchecked='reject' notmatched='reject' notprovided='reject' partialmatch='reject'></cv2_policy>\n <cv2_result numeric='2'>matched</cv2_result>\n <cv2avs_status>ACCEPTED</cv2avs_status>\n <postcode_policy matched='accept' notchecked='accept' notmatched='reject' notprovided='accept' partialmatch='accept'></postcode_policy>\n <postcode_result numeric='1'>notchecked</postcode_result>\n </Cv2Avs>\n <authcode>698899</authcode>\n <card_scheme>VISA Debit</card_scheme>\n <country>United Kingdom</country>\n <issuer>Barclays Bank PLC</issuer>\n <response_code>00</response_code>\n <response_code_text>Approved or completed successfully</response_code_text>\n <token>D4B1B0558173CAE56E87293F9E9E899C8002F7B6</token>\n </CardTxn>\n <acquirer>RBS</acquirer>\n <datacash_reference>4700204504678897</datacash_reference>\n <merchantreference>d36e05ce3604313963854fca858d11</merchantreference>\n <mid>99626800</mid>\n <mode>TEST</mode>\n <reason>ACCEPTED</reason>\n <status>1</status>\n <time>1515014679</time>\n</Response>\n\n" +read 1369 bytes +reading 2 bytes... +-> "" +-> "\r\n" +read 2 bytes +-> "0\r\n" +-> "\r\n" +Conn close + RAW + end + + def post_scrub + <<-SCRUBBED +opening connection to testserver.datacash.com:443... +opened +starting SSL for testserver.datacash.com:443... +SSL established +<- "POST /Transaction HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: testserver.datacash.com\r\nContent-Length: 1067\r\n\r\n" +<- "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Request version=\"2\">\n <Authentication>\n <client>99626800</client>\n <password>[FILTERED]</password>\n </Authentication>\n <Transaction>\n <CardTxn>\n <method>auth</method>\n <Card>\n <pan>[FILTERED]</pan>\n <expirydate>03/20</expirydate>\n <Cv2Avs>\n <cv2>[FILTERED]</cv2>\n <ExtendedPolicy>\n <cv2_policy notprovided=\"reject\" notchecked=\"reject\" matched=\"accept\" notmatched=\"reject\" partialmatch=\"reject\"/>\n <postcode_policy notprovided=\"accept\" notchecked=\"accept\" matched=\"accept\" notmatched=\"reject\" partialmatch=\"accept\"/>\n <address_policy notprovided=\"accept\" notchecked=\"accept\" matched=\"accept\" notmatched=\"reject\" partialmatch=\"accept\"/>\n </ExtendedPolicy>\n </Cv2Avs>\n </Card>\n </CardTxn>\n <TxnDetails>\n <merchantreference>d36e05ce3604313963854fca858d11</merchantreference>\n <amount currency=\"GBP\">1.98</amount>\n <capturemethod>ecomm</capturemethod>\n </TxnDetails>\n </Transaction>\n</Request>\n" +-> "HTTP/1.1 200 OK\r\n" +-> "Date: Wed, 03 Jan 2018 21:24:38 GMT\r\n" +-> "Server: Apache\r\n" +-> "Connection: close\r\n" +-> "Transfer-Encoding: chunked\r\n" +-> "Content-Type: text/plain; charset=iso-8859-1\r\n" +-> "\r\n" +-> "559\r\n" +reading 1369 bytes... +-> "" +-> "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Response version='2'>\n <CardTxn>\n <Cv2Avs>\n <address_policy matched='accept' notchecked='accept' notmatched='reject' notprovided='accept' partialmatch='accept'></address_policy>\n <address_result numeric='1'>notchecked</address_result>\n <cv2_policy matched='accept' notchecked='reject' notmatched='reject' notprovided='reject' partialmatch='reject'></cv2_policy>\n <cv2_result numeric='2'>matched</cv2_result>\n <cv2avs_status>ACCEPTED</cv2avs_status>\n <postcode_policy matched='accept' notchecked='accept' notmatched='reject' notprovided='accept' partialmatch='accept'></postcode_policy>\n <postcode_result numeric='1'>notchecked</postcode_result>\n </Cv2Avs>\n <authcode>698899</authcode>\n <card_scheme>VISA Debit</card_scheme>\n <country>United Kingdom</country>\n <issuer>Barclays Bank PLC</issuer>\n <response_code>00</response_code>\n <response_code_text>Approved or completed successfully</response_code_text>\n <token>D4B1B0558173CAE56E87293F9E9E899C8002F7B6</token>\n </CardTxn>\n <acquirer>RBS</acquirer>\n <datacash_reference>4700204504678897</datacash_reference>\n <merchantreference>d36e05ce3604313963854fca858d11</merchantreference>\n <mid>99626800</mid>\n <mode>TEST</mode>\n <reason>ACCEPTED</reason>\n <status>1</status>\n <time>1515014679</time>\n</Response>\n\n" +read 1369 bytes +reading 2 bytes... +-> "" +-> "\r\n" +read 2 bytes +-> "0\r\n" +-> "\r\n" +Conn close +SCRUBBED + end end diff --git a/test/unit/gateways/decidir_test.rb b/test/unit/gateways/decidir_test.rb new file mode 100644 index 00000000000..f9aebae75f5 --- /dev/null +++ b/test/unit/gateways/decidir_test.rb @@ -0,0 +1,371 @@ +require 'test_helper' + +class DecidirTest < Test::Unit::TestCase + include CommStub + + def setup + @gateway_for_purchase = DecidirGateway.new(api_key: 'api_key') + @gateway_for_auth = DecidirGateway.new(api_key: 'api_key', preauth_mode: true) + @credit_card = credit_card + @amount = 100 + + @options = { + order_id: '1', + billing_address: address, + description: 'Store Purchase' + } + end + + def test_successful_purchase + @gateway_for_purchase.expects(:ssl_request).returns(successful_purchase_response) + + response = @gateway_for_purchase.purchase(@amount, @credit_card, @options) + assert_success response + + assert_equal 7719132, response.authorization + assert_equal 'approved', response.message + assert response.test? + end + + def test_successful_purchase_with_options + options = { + ip: '127.0.0.1', + email: 'joe@example.com', + card_holder_door_number: '1234', + card_holder_birthday: '01011980', + card_holder_identification_type: 'dni', + card_holder_identification_number: '123456', + installments: 12 + } + + response = stub_comms(@gateway_for_purchase, :ssl_request) do + @gateway_for_purchase.purchase(@amount, @credit_card, @options.merge(options)) + end.check_request do |method, endpoint, data, headers| + assert data =~ /card_holder_door_number/, '1234' + assert data =~ /card_holder_birthday/, '01011980' + assert data =~ /type/, 'dni' + assert data =~ /number/, '123456' + end.respond_with(successful_purchase_response) + + assert_equal 7719132, response.authorization + assert_equal 'approved', response.message + assert response.test? + end + + def test_failed_purchase + @gateway_for_purchase.expects(:ssl_request).returns(failed_purchase_response) + + response = @gateway_for_purchase.purchase(@amount, @credit_card, @options) + assert_failure response + assert_equal 'TARJETA INVALIDA', response.message + assert_match Gateway::STANDARD_ERROR_CODE[:invalid_number], response.error_code + end + + def test_failed_purchase_with_invalid_field + @gateway_for_purchase.expects(:ssl_request).returns(failed_purchase_with_invalid_field_response) + + response = @gateway_for_purchase.purchase(@amount, @credit_card, @options.merge(installments: -1)) + assert_failure response + assert_equal 'invalid_param: installments', response.message + assert_match 'invalid_request_error', response.error_code + end + + def test_failed_purchase_with_preauth_mode + assert_raise(ArgumentError) do + @gateway_for_auth.purchase(@amount, @credit_card, @options) + end + end + + def test_successful_authorize + @gateway_for_auth.expects(:ssl_request).returns(successful_authorize_response) + + response = @gateway_for_auth.authorize(@amount, @credit_card, @options) + assert_success response + + assert_equal 7720214, response.authorization + assert_equal 'pre_approved', response.message + assert response.test? + end + + def test_failed_authorize + @gateway_for_auth.expects(:ssl_request).returns(failed_authorize_response) + + response = @gateway_for_auth.authorize(@amount, @credit_card, @options) + assert_failure response + + assert_equal 7719358, response.authorization + assert_equal 'TARJETA INVALIDA', response.message + assert response.test? + end + + def test_failed_authorize_without_preauth_mode + assert_raise(ArgumentError) do + @gateway_for_purchase.authorize(@amount, @credit_card, @options) + end + end + + def test_successful_capture + @gateway_for_auth.expects(:ssl_request).returns(successful_capture_response) + + response = @gateway_for_auth.capture(@amount, 7720214) + assert_success response + + assert_equal 7720214, response.authorization + assert_equal 'approved', response.message + assert response.test? + end + + def test_failed_partial_capture + @gateway_for_auth.expects(:ssl_request).returns(failed_partial_capture_response) + + response = @gateway_for_auth.capture(@amount, '') + assert_failure response + + assert_nil response.authorization + assert_equal 'amount: Amount out of ranges: 100 - 100', response.message + assert_equal 'invalid_request_error', response.error_code + assert response.test? + end + + def test_failed_capture + @gateway_for_auth.expects(:ssl_request).returns(failed_capture_response) + + response = @gateway_for_auth.capture(@amount, '') + assert_failure response + + assert_equal '', response.authorization + assert_equal 'not_found_error', response.message + assert response.test? + end + + def test_failed_capture_without_preauth_mode + assert_raise(ArgumentError) do + @gateway_for_purchase.capture(@amount, @credit_card, @options) + end + end + + def test_successful_refund + @gateway_for_purchase.expects(:ssl_request).returns(successful_refund_response) + + response = @gateway_for_purchase.refund(@amount, 81931, @options) + assert_success response + + assert_equal 81931, response.authorization + assert_equal 'approved', response.message + assert response.test? + end + + def test_partial_refund + @gateway_for_purchase.expects(:ssl_request).returns(partial_refund_response) + + response = @gateway_for_purchase.refund(@amount-1, 81932, @options) + assert_success response + + assert_equal 81932, response.authorization + assert_equal 'approved', response.message + assert response.test? + end + + def test_failed_refund + @gateway_for_purchase.expects(:ssl_request).returns(failed_refund_response) + + response = @gateway_for_purchase.refund(@amount, '') + assert_failure response + + assert_equal '', response.authorization + assert_equal 'not_found_error', response.message + assert response.test? + end + + def test_successful_void + @gateway_for_auth.expects(:ssl_request).returns(successful_void_response) + + response = @gateway_for_auth.void(@amount, '') + assert_success response + + assert_equal 82814, response.authorization + assert_equal 'approved', response.message + assert response.test? + end + + def test_failed_void + @gateway_for_auth.expects(:ssl_request).returns(failed_void_response) + + response = @gateway_for_auth.void('') + assert_failure response + + assert_equal '', response.authorization + assert_equal 'not_found_error', response.message + assert response.test? + end + + def test_successful_verify + @gateway_for_auth.expects(:ssl_request).at_most(3).returns(successful_void_response) + + response = @gateway_for_auth.verify(@credit_card, @options) + assert_success response + + assert_equal 'approved', response.message + assert response.test? + end + + def test_successful_verify_with_failed_void + @gateway_for_auth.expects(:ssl_request).at_most(3).returns(failed_void_response) + + response = @gateway_for_auth.verify(@credit_card, @options) + assert_failure response + + assert_equal 'not_found_error', response.message + assert response.test? + end + + def test_failed_verify + @gateway_for_auth.expects(:ssl_request).at_most(2).returns(failed_authorize_response) + + response = @gateway_for_auth.verify(@credit_card, @options) + assert_failure response + + assert_equal 'TARJETA INVALIDA', response.message + assert response.test? + end + + def test_failed_verify_for_without_preauth_mode + assert_raise(ArgumentError) do + @gateway_for_purchase.verify(@amount, @credit_card, @options) + end + end + + def test_scrub + assert @gateway_for_purchase.supports_scrubbing? + assert_equal @gateway_for_purchase.scrub(pre_scrubbed), post_scrubbed + end + + private + + def pre_scrubbed + %q( + opening connection to developers.decidir.com:443... + opened + starting SSL for developers.decidir.com:443... + SSL established + <- "POST /api/v2/payments HTTP/1.1\r\nContent-Type: application/json\r\nApikey: 5df6b5764c3f4822aecdc82d56f26b9d\r\nCache-Control: no-cache\r\nConnection: close\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nHost: developers.decidir.com\r\nContent-Length: 414\r\n\r\n" + <- "{\"site_transaction_id\":\"d5972b68-87d5-46fd-8d3d-b2512902b9af\",\"payment_method_id\":1,\"bin\":\"450799\",\"payment_type\":\"single\",\"installments\":1,\"description\":\"Store Purchase\",\"sub_payments\":[],\"amount\":100,\"currency\":\"ARS\",\"card_data\":{\"card_number\":\"4507990000004905\",\"card_expiration_month\":\"09\",\"card_expiration_year\":\"20\",\"security_code\":\"123\",\"card_holder_name\":\"Longbob Longsen\",\"card_holder_identification\":{}}}" + -> "HTTP/1.1 201 Created\r\n" + -> "Date: Mon, 24 Jun 2019 18:38:42 GMT\r\n" + -> "Content-Type: application/json\r\n" + -> "Content-Length: 659\r\n" + -> "Connection: close\r\n" + -> "Access-Control-Allow-Origin: *\r\n" + -> "X-Kong-Upstream-Latency: 159\r\n" + -> "X-Kong-Proxy-Latency: 0\r\n" + -> "Via: kong/0.8.3\r\n" + -> "\r\n" + reading 659 bytes... + -> "{\"id\":7721017,\"site_transaction_id\":\"d5972b68-87d5-46fd-8d3d-b2512902b9af\",\"payment_method_id\":1,\"card_brand\":\"Visa\",\"amount\":100,\"currency\":\"ars\",\"status\":\"approved\",\"status_details\":{\"ticket\":\"7297\",\"card_authorization_code\":\"153842\",\"address_validation_code\":\"VTE0011\",\"error\":null},\"date\":\"2019-06-24T15:38Z\",\"customer\":null,\"bin\":\"450799\",\"installments\":1,\"first_installment_expiration_date\":null,\"payment_type\":\"single\",\"sub_payments\":[],\"site_id\":\"99999999\",\"fraud_detection\":null,\"aggregate_data\":null,\"establishment_name\":null,\"spv\":null,\"confirmed\":null,\"pan\":\"345425f15b2c7c4584e0044357b6394d7e\",\"customer_token\":null,\"card_data\":\"/tokens/7721017\"}" + read 659 bytes + Conn close + ) + end + + def post_scrubbed + %q( + opening connection to developers.decidir.com:443... + opened + starting SSL for developers.decidir.com:443... + SSL established + <- "POST /api/v2/payments HTTP/1.1\r\nContent-Type: application/json\r\nApikey: [FILTERED]\r\nCache-Control: no-cache\r\nConnection: close\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nHost: developers.decidir.com\r\nContent-Length: 414\r\n\r\n" + <- "{\"site_transaction_id\":\"d5972b68-87d5-46fd-8d3d-b2512902b9af\",\"payment_method_id\":1,\"bin\":\"450799\",\"payment_type\":\"single\",\"installments\":1,\"description\":\"Store Purchase\",\"sub_payments\":[],\"amount\":100,\"currency\":\"ARS\",\"card_data\":{\"card_number\":\"[FILTERED]\",\"card_expiration_month\":\"09\",\"card_expiration_year\":\"20\",\"security_code\":\"[FILTERED]\",\"card_holder_name\":\"Longbob Longsen\",\"card_holder_identification\":{}}}" + -> "HTTP/1.1 201 Created\r\n" + -> "Date: Mon, 24 Jun 2019 18:38:42 GMT\r\n" + -> "Content-Type: application/json\r\n" + -> "Content-Length: 659\r\n" + -> "Connection: close\r\n" + -> "Access-Control-Allow-Origin: *\r\n" + -> "X-Kong-Upstream-Latency: 159\r\n" + -> "X-Kong-Proxy-Latency: 0\r\n" + -> "Via: kong/0.8.3\r\n" + -> "\r\n" + reading 659 bytes... + -> "{\"id\":7721017,\"site_transaction_id\":\"d5972b68-87d5-46fd-8d3d-b2512902b9af\",\"payment_method_id\":1,\"card_brand\":\"Visa\",\"amount\":100,\"currency\":\"ars\",\"status\":\"approved\",\"status_details\":{\"ticket\":\"7297\",\"card_authorization_code\":\"153842\",\"address_validation_code\":\"VTE0011\",\"error\":null},\"date\":\"2019-06-24T15:38Z\",\"customer\":null,\"bin\":\"450799\",\"installments\":1,\"first_installment_expiration_date\":null,\"payment_type\":\"single\",\"sub_payments\":[],\"site_id\":\"99999999\",\"fraud_detection\":null,\"aggregate_data\":null,\"establishment_name\":null,\"spv\":null,\"confirmed\":null,\"pan\":\"345425f15b2c7c4584e0044357b6394d7e\",\"customer_token\":null,\"card_data\":\"/tokens/7721017\"}" + read 659 bytes + Conn close + ) + end + + def successful_purchase_response + %( + {"id":7719132,"site_transaction_id":"ebcb2db7-7aab-4f33-a7d1-6617a5749fce","payment_method_id":1,"card_brand":"Visa","amount":100,"currency":"ars","status":"approved","status_details":{"ticket":"7156","card_authorization_code":"174838","address_validation_code":"VTE0011","error":null},"date":"2019-06-21T17:48Z","customer":null,"bin":"450799","installments":1,"first_installment_expiration_date":null,"payment_type":"single","sub_payments":[],"site_id":"99999999","fraud_detection":null,"aggregate_data":null,"establishment_name":null,"spv":null,"confirmed":null,"pan":"345425f15b2c7c4584e0044357b6394d7e","customer_token":null,"card_data":"/tokens/7719132"} + ) + end + + def failed_purchase_response + %( + {"id":7719351,"site_transaction_id":"73e3ed66-37b1-4c97-8f69-f9cb96422383","payment_method_id":1,"card_brand":"Visa","amount":100,"currency":"ars","status":"rejected","status_details":{"ticket":"7162","card_authorization_code":"","address_validation_code":null,"error":{"type":"invalid_number","reason":{"id":14,"description":"TARJETA INVALIDA","additional_description":""}}},"date":"2019-06-21T17:57Z","customer":null,"bin":"400030","installments":1,"first_installment_expiration_date":null,"payment_type":"single","sub_payments":[],"site_id":"99999999","fraud_detection":null,"aggregate_data":null,"establishment_name":null,"spv":null,"confirmed":null,"pan":"11b076fbc8fa6a55783b2f5d03f6938d8a","customer_token":null,"card_data":"/tokens/7719351"} + ) + end + + def failed_purchase_with_invalid_field_response + %( + {\"error_type\":\"invalid_request_error\",\"validation_errors\":[{\"code\":\"invalid_param\",\"param\":\"installments\"}]} ) + end + + def successful_authorize_response + %( + {"id":7720214,"site_transaction_id":"0fcedc95-4fbc-4299-80dc-f77e9dd7f525","payment_method_id":1,"card_brand":"Visa","amount":100,"currency":"ars","status":"pre_approved","status_details":{"ticket":"8187","card_authorization_code":"180548","address_validation_code":"VTE0011","error":null},"date":"2019-06-21T18:05Z","customer":null,"bin":"450799","installments":1,"first_installment_expiration_date":null,"payment_type":"single","sub_payments":[],"site_id":"99999997","fraud_detection":null,"aggregate_data":null,"establishment_name":null,"spv":null,"confirmed":null,"pan":"345425f15b2c7c4584e0044357b6394d7e","customer_token":null,"card_data":"/tokens/7720214"} + ) + end + + def failed_authorize_response + %( + {"id":7719358,"site_transaction_id":"ff1c12c1-fb6d-4c1a-bc20-2e77d4322c61","payment_method_id":1,"card_brand":"Visa","amount":100,"currency":"ars","status":"rejected","status_details":{"ticket":"8189","card_authorization_code":"","address_validation_code":null,"error":{"type":"invalid_number","reason":{"id":14,"description":"TARJETA INVALIDA","additional_description":""}}},"date":"2019-06-21T18:07Z","customer":null,"bin":"400030","installments":1,"first_installment_expiration_date":null,"payment_type":"single","sub_payments":[],"site_id":"99999997","fraud_detection":null,"aggregate_data":null,"establishment_name":null,"spv":null,"confirmed":null,"pan":"11b076fbc8fa6a55783b2f5d03f6938d8a","customer_token":null,"card_data":"/tokens/7719358"} + ) + end + + def successful_capture_response + %( + {"id":7720214,"site_transaction_id":"0fcedc95-4fbc-4299-80dc-f77e9dd7f525","payment_method_id":1,"card_brand":"Visa","amount":100,"currency":"ars","status":"approved","status_details":{"ticket":"8187","card_authorization_code":"180548","address_validation_code":"VTE0011","error":null},"date":"2019-06-21T18:05Z","customer":null,"bin":"450799","installments":1,"first_installment_expiration_date":null,"payment_type":"single","sub_payments":[],"site_id":"99999997","fraud_detection":null,"aggregate_data":null,"establishment_name":null,"spv":null,"confirmed":{"id":78436,"origin_amount":100,"date":"2019-06-21T03:00Z"},"pan":"345425f15b2c7c4584e0044357b6394d7e","customer_token":null,"card_data":"/tokens/7720214"} + ) + end + + def failed_partial_capture_response + %( + {"error_type":"invalid_request_error","validation_errors":[{"code":"amount","param":"Amount out of ranges: 100 - 100"}]} + ) + end + + def failed_capture_response + %( + {"error_type":"not_found_error","entity_name":"","id":""} + ) + end + + def successful_refund_response + %( + {"id":81931,"amount":100,"sub_payments":null,"error":null,"status":"approved"} + ) + end + + def partial_refund_response + %( + {"id":81932,"amount":99,"sub_payments":null,"error":null,"status":"approved"} + ) + end + + def failed_refund_response + %( + {"error_type":"not_found_error","entity_name":"","id":""} + ) + end + + def successful_void_response + %( + {"id":82814,"amount":100,"sub_payments":null,"error":null,"status":"approved"} + ) + end + + def failed_void_response + %( + {"error_type":"not_found_error","entity_name":"","id":""} + ) + end +end diff --git a/test/unit/gateways/dibs_test.rb b/test/unit/gateways/dibs_test.rb new file mode 100644 index 00000000000..af127ba0be8 --- /dev/null +++ b/test/unit/gateways/dibs_test.rb @@ -0,0 +1,255 @@ +require 'test_helper' + +class DibsTest < Test::Unit::TestCase + include CommStub + + def setup + @gateway = DibsGateway.new( + merchant_id: 'merchantId', + secret_key: 'secretKey' + ) + + @credit_card = credit_card + @amount = 100 + end + + def test_successful_purchase + response = stub_comms do + @gateway.purchase(@amount, @credit_card) + end.respond_with(successful_authorize_response, successful_capture_response) + + assert_success response + + assert_equal '1066662996', response.authorization + assert response.test? + end + + def test_failed_purchase_due_to_failed_capture + response = stub_comms do + @gateway.purchase(@amount, @credit_card) + end.respond_with(successful_authorize_response, failed_capture_response) + + assert_failure response + assert_equal 'DECLINE: 1', response.message + assert response.test? + end + + def test_failed_purchase_due_to_failed_auth + response = stub_comms do + @gateway.purchase(@amount, @credit_card) + end.respond_with(failed_authorize_response) + + assert_failure response + assert_equal 'DECLINE: REJECTED_BY_ACQUIRER', response.message + assert response.test? + end + + def test_successful_authorize + response = stub_comms do + @gateway.authorize(@amount, @credit_card) + end.respond_with(successful_authorize_response) + assert_success response + assert_equal '1066662996', response.authorization + end + + def test_successful_authorize_and_capture + response = stub_comms do + @gateway.authorize(@amount, @credit_card) + end.respond_with(successful_authorize_response) + + assert_success response + assert_equal '1066662996', response.authorization + + capture = stub_comms do + @gateway.capture(@amount, response.authorization) + end.check_request do |endpoint, data, headers| + assert_match(/1066662996/, data) + end.respond_with(successful_capture_response) + + assert_success capture + end + + def test_failed_authorize + response = stub_comms do + @gateway.authorize(@amount, @credit_card) + end.respond_with(failed_authorize_response) + + assert_failure response + assert_equal 'DECLINE: REJECTED_BY_ACQUIRER', response.message + assert response.test? + end + + def test_failed_capture + response = stub_comms do + @gateway.capture(100, '') + end.respond_with(failed_capture_response) + + assert_failure response + end + + def test_successful_void + response = stub_comms do + @gateway.purchase(@amount, @credit_card) + end.respond_with(successful_authorize_response) + + assert_success response + assert_equal '1066662996', response.authorization + + void = stub_comms do + @gateway.void(response.authorization) + end.check_request do |endpoint, data, headers| + assert_match(/1066662996/, data) + end.respond_with(successful_void_response) + + assert_success void + end + + def test_failed_void + response = stub_comms do + @gateway.void('5d53a33d960c46d00f5dc061947d998c') + end.check_request do |endpoint, data, headers| + assert_match(/5d53a33d960c46d00f5dc061947d998c/, data) + end.respond_with(failed_void_response) + + assert_failure response + end + + def test_successful_refund + response = stub_comms do + @gateway.purchase(@amount, @credit_card) + end.respond_with(successful_authorize_response, successful_capture_response) + + assert_success response + assert_equal '1066662996', response.authorization + + refund = stub_comms do + @gateway.refund(@amount, response.authorization) + end.check_request do |endpoint, data, headers| + assert_match(/1066662996/, data) + end.respond_with(successful_refund_response) + + assert_success refund + end + + def test_failed_refund + response = stub_comms do + @gateway.refund(nil, '') + end.respond_with(failed_refund_response) + + assert_failure response + end + + def test_successful_verify + response = stub_comms do + @gateway.verify(@credit_card) + end.respond_with(successful_authorize_response, failed_void_response) + assert_success response + assert_equal 'Succeeded', response.message + end + + def test_failed_verify + response = stub_comms do + @gateway.verify(@credit_card) + end.respond_with(failed_authorize_response, successful_void_response) + assert_failure response + assert_equal 'DECLINE: REJECTED_BY_ACQUIRER', response.message + end + + def test_successful_store + response = stub_comms do + @gateway.store(@credit_card) + end.respond_with(successful_store_response) + + assert_success response + + assert_equal 'Succeeded', response.message + assert response.test? + end + + def test_failed_store + response = stub_comms do + @gateway.store(@credit_card) + end.respond_with(failed_store_response) + + assert_failure response + assert_equal 'DECLINE: REJECTED_BY_ACQUIRER', response.message + assert response.test? + end + + def test_transcript_scrubbing + assert_equal scrubbed_transcript, @gateway.send(:scrub, transcript) + end + + def test_invalid_json + response = stub_comms do + @gateway.purchase(@amount, @credit_card) + end.respond_with(invalid_json_response) + + assert_failure response + assert_match %r{Invalid JSON response}, response.message + end + + private + + def successful_authorize_response + %({\"transactionId\":\"1066662996\",\"status\":\"ACCEPT\",\"acquirer\":\"TEST\"}) + end + + def failed_authorize_response + %({\"status\":\"DECLINE\",\"declineReason\":\"REJECTED_BY_ACQUIRER\"}) + end + + def successful_capture_response + %({\"status\":\"ACCEPT\"}) + end + + def failed_capture_response + %({\"status\":\"DECLINE\",\"declineReason\":\"1\"}) + end + + def successful_void_response + %({\"status\":\"ACCEPT\"}) + end + + def failed_void_response + %({\"status\":\"ERROR\",\"declineReason\":\"Validation error at field: transactionId - Parameter length should not be less than 1 characters\"}) + end + + def successful_refund_response + %({\"status\":\"ACCEPT\"}) + end + + def failed_refund_response + %({\"status\":\"ERROR\",\"declineReason\":\"Validation error at field: transactionId - Parameter length should not be less than 1 characters\"}) + end + + def successful_store_response + %({\"ticketId\":\"1070103439\",\"status\":\"ACCEPT\",\"acquirer\":\"TEST\"}) + end + + def failed_store_response + %({\"status\":\"DECLINE\",\"declineReason\":\"REJECTED_BY_ACQUIRER\"}) + end + + def invalid_json_response + '{' + end + + def transcript + %( + <- "POST /merchant/v1/JSON/Transaction/AuthorizeCard HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: api.dibspayment.com\r\nContent-Length: 293\r\n\r\n" + <- "request={\"amount\":100,\"orderId\":\"a1f3d8c03f1490750812085ea21852f1\",\"currency\":\"840\",\"cardNumber\":\"4711100000000000\",\"cvc\":\"684\",\"expYear\":\"24\",\"expMonth\":\"6\",\"test\":true,\"clientIp\":\"45.37.180.92\",\"merchantId\":\"90196871\",\"MAC\":\"4ffe83a971fc96075a9fbaae1e9bbdcbfdf8842365f381d6151162dd59e3875f\"}" + <- "POST /merchant/v1/JSON/Transaction/CaptureTransaction HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: api.dibspayment.com\r\nContent-Length: 148\r\n\r\n" + <- "request={\"amount\":100,\"transactionId\":\"1066783460\",\"merchantId\":\"90196871\",\"MAC\":\"5bc0307d55a4f146cfb9d97c42e9bb7b8112c93d4cd8349d38aa5f0360a45e08\"}" + ) + end + + def scrubbed_transcript + %( + <- "POST /merchant/v1/JSON/Transaction/AuthorizeCard HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: api.dibspayment.com\r\nContent-Length: 293\r\n\r\n" + <- "request={\"amount\":100,\"orderId\":\"a1f3d8c03f1490750812085ea21852f1\",\"currency\":\"840\",\"cardNumber\":\"[FILTERED]\",\"cvc\":\"[FILTERED]\",\"expYear\":\"24\",\"expMonth\":\"6\",\"test\":true,\"clientIp\":\"45.37.180.92\",\"merchantId\":\"90196871\",\"MAC\":\"4ffe83a971fc96075a9fbaae1e9bbdcbfdf8842365f381d6151162dd59e3875f\"}" + <- "POST /merchant/v1/JSON/Transaction/CaptureTransaction HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: api.dibspayment.com\r\nContent-Length: 148\r\n\r\n" + <- "request={\"amount\":100,\"transactionId\":\"1066783460\",\"merchantId\":\"90196871\",\"MAC\":\"5bc0307d55a4f146cfb9d97c42e9bb7b8112c93d4cd8349d38aa5f0360a45e08\"}" + ) + end +end diff --git a/test/unit/gateways/digitzs_test.rb b/test/unit/gateways/digitzs_test.rb new file mode 100644 index 00000000000..99b57ddc432 --- /dev/null +++ b/test/unit/gateways/digitzs_test.rb @@ -0,0 +1,269 @@ +require 'test_helper' + +class DigitzsTest < Test::Unit::TestCase + include CommStub + + def setup + @gateway = DigitzsGateway.new(api_key: 'api_key', app_key: 'app_key') + @credit_card = credit_card + @amount = 100 + + @options = { + merchant_id: 'spreedly-susanswidg-32268973-2091076-148408385', + order_id: '1', + billing_address: address, + description: 'Store Purchase' + } + + @options_with_split = { + merchant_id: 'spreedly-susanswidg-32268973-2091076-148408385', + billing_address: address, + description: 'Split Purchase', + payment_type: 'card_split', + split_amount: 100, + split_merchant_id: 'spreedly-susanswidg-32270590-2095203-148657924' + } + end + + def test_successful_purchase + @gateway.expects(:ssl_post).times(2).returns(successful_app_token_response, successful_purchase_response) + + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + + assert_equal 'spreedly-susanswidg-32268973-2091076-148408385-124-148606421', response.authorization + assert response.test? + end + + def test_successful_card_split_purchase + response = stub_comms do + @gateway.purchase(@amount, @credit_card, @options_with_split) + end.check_request do |endpoint, data, headers| + if data =~ /"cardSplit"/ + assert_match(%r(split), data) + assert_match(%r("merchantId":"spreedly-susanswidg-32270590-2095203-148657924"), data) + end + end.respond_with(successful_app_token_response, successful_purchase_response) + assert_success response + + assert_equal 'spreedly-susanswidg-32268973-2091076-148408385-124-148606421', response.authorization + end + + def test_successful_token_split_purchase + response = stub_comms do + @gateway.purchase(@amount, @credit_card, @options_with_split) + end.check_request do |endpoint, data, headers| + if data =~ /"tokenSplit"/ + assert_match(%r(split), data) + assert_match(%r("merchantId":"spreedly-susanswidg-32270590-2095203-148657924"), data) + end + end.respond_with(successful_app_token_response, successful_purchase_response) + assert_success response + + assert_equal 'spreedly-susanswidg-32268973-2091076-148408385-124-148606421', response.authorization + end + + def test_failed_purchase + @gateway.expects(:ssl_post).times(2).returns(successful_app_token_response, failed_purchase_response) + + response = @gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_equal '58', response.error_code + end + + def test_successful_refund + @gateway.expects(:ssl_post).times(2).returns(successful_app_token_response, successful_refund_response) + + response = @gateway.refund(@amount, 'authorization', @options) + assert_success response + + assert_equal 'spreedly-susanswidg-32268973-2091076-148408385-127-148606617', response.authorization + assert response.test? + end + + def test_failed_refund + @gateway.expects(:ssl_post).times(2).returns(successful_app_token_response, failed_refund_response) + + response = @gateway.refund(@amount, '', @options) + assert_failure response + + assert_equal nil, response.authorization + assert response.test? + end + + def test_successful_store + @gateway.expects(:ssl_post).times(3).returns(successful_app_token_response, successful_create_customer_response, successful_token_response) + + response = @gateway.store(@credit_card, @options) + assert_success response + assert_equal 'spreedly-susanswidg-32268973-2091076-148408385-2894006614343495-148710226|c0302d83-a694-4bec-9086-d1886b9eefd9-148710226', response.authorization + end + + def test_successful_store_creates_new_customer + @gateway.expects(:ssl_get).returns(customer_id_exists_response) + @gateway.expects(:ssl_post).times(3).returns(successful_app_token_response, successful_create_customer_response, successful_token_response) + + assert response = @gateway.store(@credit_card, @options.merge({customer_id: 'pre_existing_customer_id'})) + assert_success response + assert_equal 'spreedly-susanswidg-32268973-2091076-148408385-2894006614343495-148710226|c0302d83-a694-4bec-9086-d1886b9eefd9-148710226', response.authorization + end + + def test_scrub + assert @gateway.supports_scrubbing? + assert_equal @gateway.scrub(pre_scrubbed), post_scrubbed + end + + private + + def pre_scrubbed + %q( +opening connection to beta.digitzsapi.com:443... +opened +starting SSL for beta.digitzsapi.com:443... +SSL established +<- "POST /sandbox/auth/token HTTP/1.1\r\nContent-Type: application/json\r\nX-Api-Key: 0HhRdOU2AsWVEu3gRIKi2UpMMmj8Fj48qggBYTo4\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: beta.digitzsapi.com\r\nContent-Length: 115\r\n\r\n" +<- "{\"data\":{\"attributes\":{\"appKey\":\"tcwtTux8SPZYO44Gf0UHZH74Z1HSutqCxmIV2PFj2jRc9Poroh3Z3R1BBQNRQ98Q\"},\"type\":\"auth\"}}" +-> "HTTP/1.1 201 Created\r\n" +-> "Content-Type: application/json\r\n" +-> "Content-Length: 434\r\n" +-> "Connection: close\r\n" +-> "Date: Fri, 27 Jan 2017 20:47:32 GMT\r\n" +-> "Content-Location: https://beta.digitzsapi.com/sandbox/auth/token\r\n" +-> "x-amzn-RequestId: d3637ff0-e4d1-11e6-a393-3dbd03385fb7\r\n" +-> "X-Amzn-Trace-Id: Root=1-588bb1e4-49acd61c62e319bc67e443d8\r\n" +-> "Via: 1.1 344c0192a2becdfa5c3c6b927653ff8b.cloudfront.net (CloudFront), 1.1 986a2cb4ab6fb48c9a4379a4e9d691c4.cloudfront.net (CloudFront)\r\n" +-> "X-Cache: Miss from cloudfront\r\n" +-> "X-Amz-Cf-Id: NfmaknL15LfaGNXlXtc2mhwFwpzNHMbNExCfsMxORdRF7t3bbc77vA==\r\n" +-> "\r\n" +reading 434 bytes... +-> "{\"links\":{\"self\":\"https://beta.digitzsapi.com/sandbox/auth/token\"},\"data\":{\"type\":\"auth\",\"id\":\"0HhRdOU2AsWVEu3gRIKi2UpMMmj8Fj48qggBYTo4\",\"attributes\":{\"appToken\":\"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJwYXJ0bmVySWQiOiJzcHJlZWRseS0xNDgyMzAxOTEiLCJwYXJ0bmVyUHJlZml4Ijoic3ByZWVkbHkiLCJwcm9wYXlUaWVyIjoiU2V0TGlzdGVyIiwicHJvcGF5TWNjIjoiNTk5OSIsImlhdCI6MTQ4NTU1MDA1MiwiZXhwIjoxNDg1NTUzNjUyfQ.P2gunlNF56IKbAKpnRci7vLgUK0Yd7K1PGPzTtYP3Nc\"}}}" +read 434 bytes +Conn close +opening connection to beta.digitzsapi.com:443... +opened +starting SSL for beta.digitzsapi.com:443... +SSL established +<- "POST /sandbox/payments HTTP/1.1\r\nContent-Type: application/json\r\nX-Api-Key: 0HhRdOU2AsWVEu3gRIKi2UpMMmj8Fj48qggBYTo4\r\nAuthorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJwYXJ0bmVySWQiOiJzcHJlZWRseS0xNDgyMzAxOTEiLCJwYXJ0bmVyUHJlZml4Ijoic3ByZWVkbHkiLCJwcm9wYXlUaWVyIjoiU2V0TGlzdGVyIiwicHJvcGF5TWNjIjoiNTk5OSIsImlhdCI6MTQ4NTU1MDA1MiwiZXhwIjoxNDg1NTUzNjUyfQ.P2gunlNF56IKbAKpnRci7vLgUK0Yd7K1PGPzTtYP3Nc\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: beta.digitzsapi.com\r\nContent-Length: 430\r\n\r\n" +<- "{\"data\":{\"attributes\":{\"paymentType\":\"card\",\"merchantId\":\"spreedly-susanswidg-32268973-2091076-148408385\",\"card\":{\"holder\":\"Longbob Longsen\",\"number\":\"4747474747474747\",\"expiry\":\"0918\",\"code\":\"999\"},\"transaction\":{\"amount\":\"200\",\"currency\":\"USD\",\"invoice\":\"91bbccdd926ab8effc53bc7be094bd2b\"},\"billingAddress\":{\"line1\":\"456 My Street\",\"line2\":\"Apt 1\",\"city\":\"Ottawa\",\"state\":\"ON\",\"zip\":\"K1C2N6\",\"country\":\"US\"}},\"type\":\"payments\"}}" +-> "HTTP/1.1 201 Created\r\n" +-> "Content-Type: application/json\r\n" +-> "Content-Length: 504\r\n" +-> "Connection: close\r\n" +-> "Date: Fri, 27 Jan 2017 20:47:35 GMT\r\n" +-> "Content-Location: https://beta.digitzsapi.com/sandbox/payments/spreedly-susanswidg-32268973-2091076-148408385-88-148555005\r\n" +-> "x-amzn-RequestId: d3dcf5b4-e4d1-11e6-9d9a-59db6e3f8bc6\r\n" +-> "X-Amzn-Trace-Id: Root=1-588bb1e5-5c6481e9f44a8bd604900914\r\n" +-> "Via: 1.1 b06057d522f80c65400aebb1c06a2d72.cloudfront.net (CloudFront), 1.1 e6cb8f0dccd39d6bf4fcef2d892671bf.cloudfront.net (CloudFront)\r\n" +-> "X-Cache: Miss from cloudfront\r\n" +-> "X-Amz-Cf-Id: Q62cc8eH9XbSUl9No6Mp_xPS10ld0GQ8XN_S5uT4RdxkvUUA97a2kg==\r\n" +-> "\r\n" +reading 504 bytes... +-> "{\"links\":{\"self\":\"https://beta.digitzsapi.com/sandbox/payments/spreedly-susanswidg-32268973-2091076-148408385-88-148555005\"},\"data\":{\"type\":\"payments\",\"id\":\"spreedly-susanswidg-32268973-2091076-148408385-88-148555005\",\"attributes\":{\"paymentType\":\"card\",\"transaction\":{\"code\":\"0\",\"message\":\"Success\",\"amount\":\"200\",\"invoice\":\"91bbccdd926ab8effc53bc7be094bd2b\",\"currency\":\"USD\",\"authCode\":\"A11111\",\"avsResult\":\"T\",\"codeResult\":\"M\",\"gross\":\"200\",\"net\":\"169\",\"grossMinusNet\":\"31\",\"fee\":\"25\",\"rate\":\"2.90\"}}}}" +read 504 bytes +Conn close + ) + end + + def post_scrubbed + %q( +opening connection to beta.digitzsapi.com:443... +opened +starting SSL for beta.digitzsapi.com:443... +SSL established +<- "POST /sandbox/auth/token HTTP/1.1\r\nContent-Type: application/json\r\nX-Api-Key: [FILTERED]\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: beta.digitzsapi.com\r\nContent-Length: 115\r\n\r\n" +<- "{\"data\":{\"attributes\":{\"appKey\":\"[FILTERED] +-> "HTTP/1.1 201 Created\r\n" +-> "Content-Type: application/json\r\n" +-> "Content-Length: 434\r\n" +-> "Connection: close\r\n" +-> "Date: Fri, 27 Jan 2017 20:47:32 GMT\r\n" +-> "Content-Location: https://beta.digitzsapi.com/sandbox/auth/token\r\n" +-> "x-amzn-RequestId: d3637ff0-e4d1-11e6-a393-3dbd03385fb7\r\n" +-> "X-Amzn-Trace-Id: Root=1-588bb1e4-49acd61c62e319bc67e443d8\r\n" +-> "Via: 1.1 344c0192a2becdfa5c3c6b927653ff8b.cloudfront.net (CloudFront), 1.1 986a2cb4ab6fb48c9a4379a4e9d691c4.cloudfront.net (CloudFront)\r\n" +-> "X-Cache: Miss from cloudfront\r\n" +-> "X-Amz-Cf-Id: NfmaknL15LfaGNXlXtc2mhwFwpzNHMbNExCfsMxORdRF7t3bbc77vA==\r\n" +-> "\r\n" +reading 434 bytes... +-> "{\"links\":{\"self\":\"https://beta.digitzsapi.com/sandbox/auth/token\"},\"data\":{\"type\":\"auth\",\"id\":\"[FILTERED] +read 434 bytes +Conn close +opening connection to beta.digitzsapi.com:443... +opened +starting SSL for beta.digitzsapi.com:443... +SSL established +<- "POST /sandbox/payments HTTP/1.1\r\nContent-Type: application/json\r\nX-Api-Key: [FILTERED]\r\nAuthorization: Bearer [FILTERED] +<- "{\"data\":{\"attributes\":{\"paymentType\":\"card\",\"merchantId\":\"spreedly-susanswidg-32268973-2091076-148408385\",\"card\":{\"holder\":\"Longbob Longsen\",\"number\":\"[FILTERED]\",\"expiry\":\"0918\",\"code\":\"[FILTERED]\"},\"transaction\":{\"amount\":\"200\",\"currency\":\"USD\",\"invoice\":\"91bbccdd926ab8effc53bc7be094bd2b\"},\"billingAddress\":{\"line1\":\"456 My Street\",\"line2\":\"Apt 1\",\"city\":\"Ottawa\",\"state\":\"ON\",\"zip\":\"K1C2N6\",\"country\":\"US\"}},\"type\":\"payments\"}}" +-> "HTTP/1.1 201 Created\r\n" +-> "Content-Type: application/json\r\n" +-> "Content-Length: 504\r\n" +-> "Connection: close\r\n" +-> "Date: Fri, 27 Jan 2017 20:47:35 GMT\r\n" +-> "Content-Location: https://beta.digitzsapi.com/sandbox/payments/spreedly-susanswidg-32268973-2091076-148408385-88-148555005\r\n" +-> "x-amzn-RequestId: d3dcf5b4-e4d1-11e6-9d9a-59db6e3f8bc6\r\n" +-> "X-Amzn-Trace-Id: Root=1-588bb1e5-5c6481e9f44a8bd604900914\r\n" +-> "Via: 1.1 b06057d522f80c65400aebb1c06a2d72.cloudfront.net (CloudFront), 1.1 e6cb8f0dccd39d6bf4fcef2d892671bf.cloudfront.net (CloudFront)\r\n" +-> "X-Cache: Miss from cloudfront\r\n" +-> "X-Amz-Cf-Id: Q62cc8eH9XbSUl9No6Mp_xPS10ld0GQ8XN_S5uT4RdxkvUUA97a2kg==\r\n" +-> "\r\n" +reading 504 bytes... +-> "{\"links\":{\"self\":\"https://beta.digitzsapi.com/sandbox/payments/spreedly-susanswidg-32268973-2091076-148408385-88-148555005\"},\"data\":{\"type\":\"payments\",\"id\":\"[FILTERED] +read 504 bytes +Conn close + ) + end + + def successful_app_token_response + %( + {\"links\":{\"self\":\"https://beta.digitzsapi.com/sandbox/auth/token\"},\"data\":{\"type\":\"auth\",\"id\":\"0HhRdOU2AsWVEu3gRIKi2UpMMmj8Fj48qggBYTo4\",\"attributes\":{\"appToken\":\"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJwYXJ0bmVySWQiOiJzcHJlZWRseS0xNDgyMzAxOTEiLCJwYXJ0bmVyUHJlZml4Ijoic3ByZWVkbHkiLCJwcm9wYXlUaWVyIjoiU2V0TGlzdGVyIiwicHJvcGF5TWNjIjoiNTk5OSIsImlhdCI6MTQ4NjA2NDIxNiwiZXhwIjoxNDg2MDY3ODE2fQ.MaLR3ijeMuIGSHnNYINVILwa9hpxahd4U4Q44HW4jFQ\"}}} + ) + end + + def successful_purchase_response + %( + {\"links\":{\"self\":\"https://beta.digitzsapi.com/sandbox/payments/spreedly-susanswidg-32268973-2091076-148408385-124-148606421\"},\"data\":{\"type\":\"payments\",\"id\":\"spreedly-susanswidg-32268973-2091076-148408385-124-148606421\",\"attributes\":{\"paymentType\":\"card\",\"transaction\":{\"code\":\"0\",\"message\":\"Success\",\"amount\":\"200\",\"invoice\":\"42d7b1d026becf29e2005bae84e8935b\",\"currency\":\"USD\",\"authCode\":\"A11111\",\"avsResult\":\"T\",\"codeResult\":\"M\",\"gross\":\"200\",\"net\":\"169\",\"grossMinusNet\":\"31\",\"fee\":\"25\",\"rate\":\"2.90\"}}}} + ) + end + + def successful_split_purchase_response + %( + {\"links\":{\"self\":\"https://beta.digitzsapi.com/sandbox/payments/spreedly-susanswidg-32268973-2091076-148408385-153-148658575\"},\"data\":{\"type\":\"payments\",\"id\":\"spreedly-susanswidg-32268973-2091076-148408385-153-148658575\",\"attributes\":{\"paymentType\":\"cardSplit\",\"transaction\":{\"code\":\"0\",\"message\":\"Success\",\"amount\":\"500\",\"invoice\":\"88ec8adf6c86762684ae54820423acc8\",\"currency\":\"USD\",\"authCode\":\"A11111\",\"avsResult\":\"T\",\"codeResult\":\"M\"},\"split\":{\"merchantId\":\"spreedly-susanswidg-32270590-2095203-148657924\",\"amount\":\"100\",\"splitId\":\"spreedly-susanswidg-32270590-2095203-148657924-2-148658575\"}}}} + ) + end + + def failed_purchase_response + %( + {\"meta\":{},\"errors\":[{\"status\":\"400\",\"source\":{\"pointer\":\"/payments\"},\"title\":\"Bad Request\",\"detail\":\"Partner error: Credit card declined (transaction element shows reason for decline)\",\"code\":\"58\",\"meta\":{\"debug\":{\"message\":\"Include debug info with support request.\",\"resource\":\"/payments POST\",\"log\":\"2017/02/02/[23]eb325f3ca78b4f7eb2178a0d1e635a0e\",\"request\":\"73c22dc3-e980-11e6-9390-69c24d5ed1f4\"},\"transaction\":{\"code\":\"51\",\"message\":\"Insufficient funds\",\"invoice\":\"3d1f247d9112349e3db252f9f3327047\",\"authCode\":\"A11111\",\"avsResult\":\"T\"}}}]} + ) + end + + def successful_refund_response + %( + {\"links\":{\"self\":\"https://beta.digitzsapi.com/sandbox/payments/spreedly-susanswidg-32268973-2091076-148408385-127-148606617\"},\"data\":{\"type\":\"payments\",\"id\":\"spreedly-susanswidg-32268973-2091076-148408385-127-148606617\",\"attributes\":{\"paymentType\":\"cardRefund\",\"transaction\":{\"code\":\"0\",\"message\":\"Success\",\"amount\":\"200\",\"invoice\":\"f87139e53b5273c12bc32d4be6fff9a8\",\"currency\":\"USD\"}}}} + ) + end + + def failed_refund_response + %( + {\"meta\":{},\"errors\":[{\"status\":\"400\",\"source\":{\"pointer\":\"/data/attributes/originalTransaction/id\"},\"title\":\"Bad Request\",\"detail\":\"\\\"id\\\" is not allowed to be empty\"}]} + ) + end + + def successful_create_customer_response + %( + {\"links\":{\"self\":\"https://beta.digitzsapi.com/sandbox/customers/spreedly-susanswidg-32268973-2091076-148408385-2894006614343495-148710226\"},\"data\":{\"type\":\"customers\",\"id\":\"spreedly-susanswidg-32268973-2091076-148408385-2894006614343495-148710226\",\"attributes\":{\"name\":\"Longbob Longsen\",\"externalId\":\"2b942bae49e9297f60428ee841f30724\"}}} + ) + end + + def successful_token_response + %( + {\"links\":{\"self\":\"https://beta.digitzsapi.com/sandbox/tokens/c0302d83-a694-4bec-9086-d1886b9eefd9-148710226\"},\"data\":{\"type\":\"tokens\",\"id\":\"c0302d83-a694-4bec-9086-d1886b9eefd9-148710226\",\"attributes\":{\"label\":\"Credit Card\",\"customerId\":\"spreedly-susanswidg-32268973-2091076-148408385-2894006614343495-148710226\"}}} + ) + end + + def customer_id_exists_response + %( + {\"links\":{\"self\":\"https://beta.digitzsapi.com/sandbox/customers/spreedly-susanswidg-32268973-2091076-148408385-5980208887457495-148700575\"},\"data\":{\"id\":\"spreedly-susanswidg-32268973-2091076-148408385-5980208887457495-148700575\",\"attributes\":{\"merchantId\":\"spreedly-susanswidg-32268973-2091076-148408385\",\"created\":\"2017-02-13T17:09:12.724Z\",\"name\":\"Jon Doe\",\"externalId\":\"123456\"},\"type\":\"customers\"}} + ) + end +end diff --git a/test/unit/gateways/ebanx_test.rb b/test/unit/gateways/ebanx_test.rb new file mode 100644 index 00000000000..c5e93bc8e13 --- /dev/null +++ b/test/unit/gateways/ebanx_test.rb @@ -0,0 +1,247 @@ +require 'test_helper' + +class EbanxTest < Test::Unit::TestCase + def setup + @gateway = EbanxGateway.new(integration_key: 'key') + @credit_card = credit_card + @amount = 100 + + @options = { + order_id: '1', + billing_address: address, + description: 'Store Purchase' + } + end + + def test_successful_purchase + @gateway.expects(:ssl_request).returns(successful_purchase_response) + + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + + assert_equal '592db57ad6933455efbb62a48d1dfa091dd7cd092109db99', response.authorization + assert response.test? + end + + def test_failed_purchase + @gateway.expects(:ssl_request).returns(failed_purchase_response) + + response = @gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_equal 'NOK', response.error_code + end + + def test_successful_authorize + @gateway.expects(:ssl_request).returns(successful_authorize_response) + + response = @gateway.authorize(@amount, @credit_card, @options) + assert_success response + + assert_equal '592dc02dbe421478a132bf5c2ecfe52c86ac01b454ae799b', response.authorization + assert response.test? + end + + def test_failed_authorize + @gateway.expects(:ssl_request).returns(failed_authorize_response) + + response = @gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_equal 'NOK', response.error_code + end + + def test_successful_capture + @gateway.expects(:ssl_request).returns(successful_capture_response) + + response = @gateway.capture(@amount, 'authorization', @options) + assert_success response + + assert_equal 'Sandbox - Test credit card, transaction captured', response.message + assert response.test? + end + + def test_failed_capture + @gateway.expects(:ssl_request).returns(failed_capture_response) + + response = @gateway.capture(@amount, '', @options) + assert_failure response + assert_equal 'BP-CAP-1', response.error_code + end + + def test_successful_refund + @gateway.expects(:ssl_request).returns(successful_refund_response) + + response = @gateway.refund(@amount, 'authorization', @options) + assert_success response + + assert_equal '59306246f2a0c5f327a15dd6492687e197aca7eda179da08', response.authorization + assert response.test? + end + + def test_failed_refund + @gateway.expects(:ssl_request).returns(failed_refund_response) + + response = @gateway.refund(@amount, '', @options) + assert_failure response + assert_equal 'BP-REF-CAN-2', response.error_code + end + + def test_successful_void + @gateway.expects(:ssl_request).returns(successful_void_response) + + response = @gateway.void('authorization', @options) + assert_success response + + assert_equal '5930629dde0899dc53b3557ea9887aa8f3d264a91d115d40', response.authorization + assert response.test? + end + + def test_failed_void + @gateway.expects(:ssl_request).returns(failed_void_response) + + response = @gateway.void('', @options) + assert_failure response + assert_equal 'BP-CAN-1', response.error_code + end + + def test_successful_verify + @gateway.expects(:ssl_request).times(2).returns(successful_authorize_response, successful_void_response) + + response = @gateway.verify(@credit_card, @options) + assert_success response + assert_equal nil, response.error_code + end + + def test_successful_verify_with_failed_void + @gateway.expects(:ssl_request).times(2).returns(successful_authorize_response, failed_void_response) + + response = @gateway.verify(@credit_card, @options) + assert_success response + assert_equal nil, response.error_code + end + + def test_failed_verify + @gateway.expects(:ssl_request).returns(failed_authorize_response) + + response = @gateway.verify(@credit_card, @options) + assert_failure response + assert_equal 'NOK', response.error_code + end + + def test_successful_store_and_purchase + @gateway.expects(:ssl_request).returns(successful_store_response) + + store = @gateway.store(@credit_card, @options) + assert_success store + assert_equal 'a61a7c98535718801395991b5112f888d359c2d632e2c3bb8afe75aa23f3334d7fd8dc57d7721f8162503773063de59ee85901b5714a92338c6d9c0352aee78c|visa', store.authorization + + @gateway.expects(:ssl_request).returns(successful_purchase_with_stored_card_response) + + response = @gateway.purchase(@amount, store.authorization, @options) + assert_success response + end + + def test_error_response_with_invalid_creds + @gateway.expects(:ssl_request).returns(invalid_cred_response) + + response = @gateway.store(@credit_card, @options) + assert_failure response + assert_equal 'Invalid integration key', response.message + end + + def test_scrub + assert @gateway.supports_scrubbing? + assert_equal @gateway.scrub(pre_scrubbed), post_scrubbed + end + + private + + def pre_scrubbed + %q( + request_body={\"integration_key\":\"1231000\",\"operation\":\"request\",\"payment\":{\"amount_total\":\"1.00\",\"currency_code\":\"USD\",\"merchant_payment_code\":\"2bed75b060e936834e354d944aeaa892\",\"name\":\"Longbob Longsen\",\"email\":\"unspecified@example.com\",\"document\":\"853.513.468-93\",\"payment_type_code\":\"visa\",\"creditcard\":{\"card_number\":\"4111111111111111\",\"card_name\":\"Longbob Longsen\",\"card_due_date\":\"9/2018\",\"card_cvv\":\"123\"},\"address\":\"Rua E\",\"street_number\":\"1040\",\"city\":\"Maracana\u{fa}\",\"state\":\"CE\",\"zipcode\":\"61919-230\",\"country\":\"BR\",\"phone_number\":\"(555)555-5555\"}} + ) + end + + def post_scrubbed + %q( + request_body={\"integration_key\":\"[FILTERED]\",\"operation\":\"request\",\"payment\":{\"amount_total\":\"1.00\",\"currency_code\":\"USD\",\"merchant_payment_code\":\"2bed75b060e936834e354d944aeaa892\",\"name\":\"Longbob Longsen\",\"email\":\"unspecified@example.com\",\"document\":\"853.513.468-93\",\"payment_type_code\":\"visa\",\"creditcard\":{\"card_number\":\"[FILTERED]\",\"card_name\":\"Longbob Longsen\",\"card_due_date\":\"9/2018\",\"card_cvv\":\"[FILTERED]\"},\"address\":\"Rua E\",\"street_number\":\"1040\",\"city\":\"Maracana\u{fa}\",\"state\":\"CE\",\"zipcode\":\"61919-230\",\"country\":\"BR\",\"phone_number\":\"(555)555-5555\"}} + ) + end + + def successful_purchase_response + %( + {"payment":{"hash":"592db57ad6933455efbb62a48d1dfa091dd7cd092109db99","pin":"081043552","merchant_payment_code":"ca2251ed6ac582162b17d77dfd7fb98a","order_number":null,"status":"CO","status_date":"2017-05-30 15:10:01","open_date":"2017-05-30 15:10:01","confirm_date":"2017-05-30 15:10:01","transfer_date":null,"amount_br":"3.31","amount_ext":"1.00","amount_iof":"0.01","currency_rate":"3.3000","currency_ext":"USD","due_date":"2017-06-02","instalments":"1","payment_type_code":"visa","transaction_status":{"acquirer":"EBANX","code":"OK","description":"Sandbox - Test credit card, transaction captured"},"pre_approved":true,"capture_available":false,"customer":{"document":"85351346893","email":"unspecified@example.com","name":"LONGBOB LONGSEN","birth_date":null}},"status":"SUCCESS"} + ) + end + + def failed_purchase_response + %( + {"payment":{"hash":"592dd2f17965cd3d7a17e71a3fe943b8363c72d60caffacc","pin":"655998606","merchant_payment_code":"e71e467805aef9064599bc5a76e98e23","order_number":null,"status":"CA","status_date":"2017-05-30 17:15:45","open_date":"2017-05-30 17:15:45","confirm_date":null,"transfer_date":null,"amount_br":"3.31","amount_ext":"1.00","amount_iof":"0.01","currency_rate":"3.3000","currency_ext":"USD","due_date":"2017-06-02","instalments":"1","payment_type_code":"visa","transaction_status":{"acquirer":"EBANX","code":"NOK","description":"Sandbox - Test credit card, transaction declined reason insufficientFunds"},"pre_approved":false,"capture_available":false,"customer":{"document":"85351346893","email":"unspecified@example.com","name":"LONGBOB LONGSEN","birth_date":null}},"status":"SUCCESS"} + ) + end + + def successful_authorize_response + %( + {"payment":{"hash":"592dc02dbe421478a132bf5c2ecfe52c86ac01b454ae799b","pin":"296389224","merchant_payment_code":"8e5c943c3c93adbed8d8a7347ca333fe","order_number":null,"status":"PE","status_date":null,"open_date":"2017-05-30 15:55:40","confirm_date":null,"transfer_date":null,"amount_br":"3.31","amount_ext":"1.00","amount_iof":"0.01","currency_rate":"3.3000","currency_ext":"USD","due_date":"2017-06-02","instalments":"1","payment_type_code":"visa","transaction_status":{"acquirer":"EBANX","code":"OK","description":"Sandbox - Test credit card, transaction will be approved"},"pre_approved":true,"capture_available":true,"customer":{"document":"85351346893","email":"unspecified@example.com","name":"LONGBOB LONGSEN","birth_date":null}},"status":"SUCCESS"} + ) + end + + def failed_authorize_response + %( + {"payment":{"hash":"592dd2146d5b8a27924daaa0f0248d8c582cb2ce6b67495e","pin":"467618452","merchant_payment_code":"7883bdbbdfa961ce753247fbeb4ff99d","order_number":null,"status":"CA","status_date":"2017-05-30 17:12:03","open_date":"2017-05-30 17:12:03","confirm_date":null,"transfer_date":null,"amount_br":"3.31","amount_ext":"1.00","amount_iof":"0.01","currency_rate":"3.3000","currency_ext":"USD","due_date":"2017-06-02","instalments":"1","payment_type_code":"visa","transaction_status":{"acquirer":"EBANX","code":"NOK","description":"Sandbox - Test credit card, transaction declined reason insufficientFunds"},"pre_approved":false,"capture_available":false,"customer":{"document":"85351346893","email":"unspecified@example.com","name":"LONGBOB LONGSEN","birth_date":null}},"status":"SUCCESS"} + ) + end + + def successful_capture_response + %( + {"payment":{"hash":"592dd65824427e4f5f50564c118f399869637bfb30d54f5b","pin":"081043654","merchant_payment_code":"8424e3000d64d056fbd58639957dc1c4","order_number":null,"status":"CO","status_date":"2017-05-30 17:30:16","open_date":"2017-05-30 17:30:15","confirm_date":"2017-05-30 17:30:16","transfer_date":null,"amount_br":"3.31","amount_ext":"1.00","amount_iof":"0.01","currency_rate":"3.3000","currency_ext":"USD","due_date":"2017-06-02","instalments":"1","payment_type_code":"visa","transaction_status":{"acquirer":"EBANX","code":"OK","description":"Sandbox - Test credit card, transaction captured"},"pre_approved":true,"capture_available":false,"customer":{"document":"85351346893","email":"unspecified@example.com","name":"LONGBOB LONGSEN","birth_date":null}},"status":"SUCCESS"} + ) + end + + def failed_capture_response + %( + {"status":"ERROR","status_code":"BP-CAP-1","status_message":"Parameters hash or merchant_payment_code not informed"} + ) + end + + def successful_refund_response + %( + {"payment":{"hash":"59306246f2a0c5f327a15dd6492687e197aca7eda179da08","pin":"446189033","merchant_payment_code":"b5e1f7298f8fa645e8a903fbdc5ce44a","order_number":null,"status":"CO","status_date":"2017-06-01 15:51:49","open_date":"2017-06-01 15:51:49","confirm_date":"2017-06-01 15:51:49","transfer_date":null,"amount_br":"3.31","amount_ext":"1.00","amount_iof":"0.01","currency_rate":"3.3000","currency_ext":"USD","due_date":"2017-06-04","instalments":"1","payment_type_code":"visa","transaction_status":{"acquirer":"EBANX","code":"OK","description":"Sandbox - Test credit card, transaction captured"},"pre_approved":true,"capture_available":false,"refunds":[{"id":"20739","merchant_refund_code":null,"status":"RE","request_date":"2017-06-01 15:51:50","pending_date":null,"confirm_date":null,"cancel_date":null,"amount_ext":"1.00","description":"full refund"}],"customer":{"document":"85351346893","email":"unspecified@example.com","name":"LONGBOB LONGSEN","birth_date":null}},"refund":{"id":"20739","merchant_refund_code":null,"status":"RE","request_date":"2017-06-01 15:51:50","pending_date":null,"confirm_date":null,"cancel_date":null,"amount_ext":"1.00","description":"full refund"},"operation":"refund","status":"SUCCESS"} + ) + end + + def failed_refund_response + %( + {"status":"ERROR","status_code":"BP-REF-CAN-2","status_message":"Payment not found with this hash: "} + ) + end + + def successful_void_response + %( + {"payment":{"hash":"5930629dde0899dc53b3557ea9887aa8f3d264a91d115d40","pin":"465556618","merchant_payment_code":"8b97c49aecffbb309dadd08c87ccbdd0","order_number":null,"status":"CA","status_date":"2017-06-01 15:53:18","open_date":"2017-06-01 15:53:17","confirm_date":null,"transfer_date":null,"amount_br":"3.31","amount_ext":"1.00","amount_iof":"0.01","currency_rate":"3.3000","currency_ext":"USD","due_date":"2017-06-04","instalments":"1","payment_type_code":"visa","transaction_status":{"acquirer":"EBANX","code":"NOK","description":"Sandbox - Test credit card, transaction cancelled"},"pre_approved":false,"capture_available":false,"customer":{"document":"85351346893","email":"unspecified@example.com","name":"LONGBOB LONGSEN","birth_date":null}},"operation":"cancel","status":"SUCCESS"} + ) + end + + def failed_void_response + %( + {"status":"ERROR","status_code":"BP-CAN-1","status_message":"Parameter hash not informed"} + ) + end + + def successful_store_response + %( + {"status":"SUCCESS","payment_type_code":"visa","token":"a61a7c98535718801395991b5112f888d359c2d632e2c3bb8afe75aa23f3334d7fd8dc57d7721f8162503773063de59ee85901b5714a92338c6d9c0352aee78c","masked_card_number":"411111xxxxxx1111"} + ) + end + + def successful_purchase_with_stored_card_response + %( + {"payment":{"hash":"59d3e2955021c5e2b180e1ea9670e2d9675c15453a2ab346","pin":"252076123","merchant_payment_code":"a942f8a68836e888fa8e8af1e8ca4bf2","order_number":null,"status":"CO","status_date":"2017-10-03 19:18:45","open_date":"2017-10-03 19:18:44","confirm_date":"2017-10-03 19:18:45","transfer_date":null,"amount_br":"3.31","amount_ext":"1.00","amount_iof":"0.01","currency_rate":"3.3000","currency_ext":"USD","due_date":"2017-10-06","instalments":"1","payment_type_code":"visa","details":{"billing_descriptor":""},"transaction_status":{"acquirer":"EBANX","code":"OK","description":"Accepted"},"pre_approved":true,"capture_available":false,"customer":{"document":"85351346893","email":"unspecified@example.com","name":"NOT PROVIDED","birth_date":null}},"status":"SUCCESS"} + ) + end + + def invalid_cred_response + %( + {"status":"ERROR","status_code":"DA-1","status_message":"Invalid integration key"} + ) + end +end diff --git a/test/unit/gateways/efsnet_test.rb b/test/unit/gateways/efsnet_test.rb index f5a7fce1826..e437e2aa422 100644 --- a/test/unit/gateways/efsnet_test.rb +++ b/test/unit/gateways/efsnet_test.rb @@ -9,25 +9,24 @@ def setup ) @credit_card = credit_card('4242424242424242') - @amount = 100 + @amount = 100 @options = { :order_id => 1, :billing_address => address } end - + def test_successful_purchase @gateway.expects(:ssl_post).returns(successful_purchase_response) - + assert response = @gateway.purchase(@amount, @credit_card, @options) assert_instance_of Response, response assert_success response assert response.test? assert_equal '100018347764;1.00', response.authorization assert_equal 'Approved', response.message - end def test_unsuccessful_purchase @gateway.expects(:ssl_post).returns(unsuccessful_purchase_response) - + assert response = @gateway.purchase(@amount, @credit_card, @options) assert_instance_of Response, response assert_failure response @@ -36,62 +35,63 @@ def test_unsuccessful_purchase end def test_credit - @gateway.expects(:ssl_post).with(anything, regexp_matches(/AccountNumber>#{@credit_card.number}<\/AccountNumber/), anything).returns("") + @gateway.expects(:ssl_post).with(anything, regexp_matches(/AccountNumber>#{@credit_card.number}<\/AccountNumber/), anything).returns('') @gateway.credit(@amount, @credit_card, :order_id => 5) end def test_deprecated_credit - @gateway.expects(:ssl_post).with(anything, regexp_matches(/<OriginalTransactionID>transaction_id<\/OriginalTransactionID>/), anything).returns("") - assert_deprecation_warning(Gateway::CREDIT_DEPRECATION_MESSAGE, @gateway) do - @gateway.credit(@amount, "transaction_id", :order_id => 5) + @gateway.expects(:ssl_post).with(anything, regexp_matches(/<OriginalTransactionID>transaction_id<\/OriginalTransactionID>/), anything).returns('') + assert_deprecation_warning(Gateway::CREDIT_DEPRECATION_MESSAGE) do + @gateway.credit(@amount, 'transaction_id', :order_id => 5) end end def test_refund - @gateway.expects(:ssl_post).with(anything, regexp_matches(/<OriginalTransactionID>transaction_id<\/OriginalTransactionID>/), anything).returns("") - @gateway.refund(@amount, "transaction_id", :order_id => 5) + @gateway.expects(:ssl_post).with(anything, regexp_matches(/<OriginalTransactionID>transaction_id<\/OriginalTransactionID>/), anything).returns('') + @gateway.refund(@amount, 'transaction_id', :order_id => 5) end def test_authorize_is_valid_xml params = { - :order_id => "order1", - :transaction_amount => "1.01", - :account_number => "4242424242424242", - :expiration_month => "12", - :expiration_year => "2029", + :order_id => 'order1', + :transaction_amount => '1.01', + :account_number => '4242424242424242', + :expiration_month => '12', + :expiration_year => '2029', } - + assert data = @gateway.send(:post_data, :credit_card_authorize, params) assert REXML::Document.new(data) end def test_settle_is_valid_xml params = { - :order_id => "order1", - :transaction_amount => "1.01", - :original_transaction_amount => "1.01", - :original_transaction_id => "1", + :order_id => 'order1', + :transaction_amount => '1.01', + :original_transaction_amount => '1.01', + :original_transaction_id => '1', } - + assert data = @gateway.send(:post_data, :credit_card_settle, params) assert REXML::Document.new(data) end - + def test_avs_result @gateway.expects(:ssl_post).returns(successful_purchase_response) - + response = @gateway.purchase(@amount, @credit_card, @options) assert_equal 'N', response.avs_result['code'] end - + def test_cvv_result @gateway.expects(:ssl_post).returns(successful_purchase_response) - + response = @gateway.purchase(@amount, @credit_card, @options) assert_equal 'M', response.cvv_result['code'] end - + private + def successful_purchase_response <<-XML <?xml version="1.0"?> @@ -114,7 +114,7 @@ def successful_purchase_response </Reply> XML end - + def unsuccessful_purchase_response <<-XML <?xml version="1.0"?> diff --git a/test/unit/gateways/elavon_test.rb b/test/unit/gateways/elavon_test.rb index b4c0054ca23..038f8200f39 100644 --- a/test/unit/gateways/elavon_test.rb +++ b/test/unit/gateways/elavon_test.rb @@ -1,6 +1,8 @@ require 'test_helper' class ElavonTest < Test::Unit::TestCase + include CommStub + def setup @gateway = ElavonGateway.new( :login => 'login', @@ -8,6 +10,13 @@ def setup :password => 'password' ) + @multi_currency_gateway = ElavonGateway.new( + :login => 'login', + :user => 'user', + :password => 'password', + :multi_currency => true + ) + @credit_card = credit_card @amount = 100 @@ -35,7 +44,7 @@ def test_successful_authorization assert_success response assert_equal '123456;00000000-0000-0000-0000-00000000000', response.authorization - assert_equal "APPROVED", response.message + assert_equal 'APPROVED', response.message assert response.test? end @@ -47,6 +56,101 @@ def test_failed_authorization assert_failure response end + def test_successful_capture + @gateway.expects(:ssl_post).returns(successful_capture_response) + authorization = '123456;00000000-0000-0000-0000-00000000000' + + assert response = @gateway.capture(@amount, authorization, :credit_card => @credit_card) + assert_instance_of Response, response + assert_success response + + assert_equal '123456;00000000-0000-0000-0000-00000000000', response.authorization + assert_equal 'APPROVAL', response.message + assert response.test? + end + + def test_successful_capture_with_auth_code + @gateway.expects(:ssl_post).returns(successful_capture_response) + authorization = '123456;00000000-0000-0000-0000-00000000000' + + assert response = @gateway.capture(@amount, authorization) + assert_instance_of Response, response + assert_success response + + assert_equal '123456;00000000-0000-0000-0000-00000000000', response.authorization + assert_equal 'APPROVAL', response.message + assert response.test? + end + + def test_successful_capture_with_additional_options + authorization = '123456;00000000-0000-0000-0000-00000000000' + response = stub_comms do + @gateway.capture(@amount, authorization, :test_mode => true, :partial_shipment_flag => true) + end.check_request do |endpoint, data, headers| + assert_match(/ssl_transaction_type=CCCOMPLETE/, data) + assert_match(/ssl_test_mode=TRUE/, data) + assert_match(/ssl_partial_shipment_flag=Y/, data) + end.respond_with(successful_capture_response) + + assert_instance_of Response, response + assert_success response + + assert_equal '123456;00000000-0000-0000-0000-00000000000', response.authorization + assert_equal 'APPROVAL', response.message + assert response.test? + end + + def test_successful_purchase_with_ip + response = stub_comms do + @gateway.purchase(@amount, @credit_card, @options.merge(ip: '203.0.113.0')) + end.check_request do |_endpoint, data, _headers| + parsed = CGI.parse(data) + assert_equal ['203.0.113.0'], parsed['ssl_cardholder_ip'] + end.respond_with(successful_purchase_response) + + assert_success response + end + + def test_successful_authorization_with_ip + response = stub_comms do + @gateway.authorize(@amount, @credit_card, @options.merge(ip: '203.0.113.0')) + end.check_request do |_endpoint, data, _headers| + parsed = CGI.parse(data) + assert_equal ['203.0.113.0'], parsed['ssl_cardholder_ip'] + end.respond_with(successful_authorization_response) + + assert_success response + end + + def test_successful_purchase_with_multi_currency + response = stub_comms(@multi_currency_gateway) do + @multi_currency_gateway.purchase(@amount, @credit_card, @options.merge(currency: 'EUR')) + end.check_request do |endpoint, data, headers| + assert_match(/ssl_transaction_currency=EUR/, data) + end.respond_with(successful_purchase_with_multi_currency_response) + + assert_success response + end + + def test_successful_purchase_without_multi_currency + response = stub_comms do + @gateway.purchase(@amount, @credit_card, @options.merge(currency: 'EUR', multi_currency: false)) + end.check_request do |endpoint, data, headers| + assert_no_match(/ssl_transaction_currency=EUR/, data) + end.respond_with(successful_purchase_response) + + assert_success response + end + + def test_failed_capture + @gateway.expects(:ssl_post).returns(failed_authorization_response) + authorization = '123456INVALID;00000000-0000-0000-0000-00000000000' + + assert response = @gateway.capture(@amount, authorization, :credit_card => @credit_card) + assert_instance_of Response, response + assert_failure response + end + def test_unsuccessful_purchase @gateway.expects(:ssl_post).returns(failed_purchase_response) @@ -87,6 +191,29 @@ def test_unsuccessful_refund assert_equal 'The refund amount exceeds the original transaction amount.', response.message end + def test_successful_verify + response = stub_comms do + @gateway.verify(@credit_card) + end.respond_with(successful_authorization_response, successful_void_response) + assert_success response + end + + def test_successful_verify_failed_void + response = stub_comms do + @gateway.verify(@credit_card, @options) + end.respond_with(successful_authorization_response, failed_void_response) + assert_success response + assert_equal 'APPROVED', response.message + end + + def test_unsuccessful_verify + response = stub_comms do + @gateway.verify(@credit_card, @options) + end.respond_with(failed_authorization_response, successful_void_response) + assert_failure response + assert_equal 'The Credit Card Number supplied in the authorization request appears to be invalid.', response.message + end + def test_invalid_login @gateway.expects(:ssl_post).returns(invalid_login_response) @@ -97,10 +224,6 @@ def test_invalid_login assert_failure response end - def test_supported_countries - assert_equal ['US', 'CA'], ElavonGateway.supported_countries - end - def test_supported_card_types assert_equal [:visa, :master, :american_express, :discover], ElavonGateway.supported_cardtypes end @@ -117,7 +240,75 @@ def test_cvv_result assert_equal 'P', response.cvv_result['code'] end + def test_successful_store + @gateway.expects(:ssl_post).returns(successful_store_response) + + assert response = @gateway.store(@credit_card, @options) + assert_success response + assert_equal '7595301425001111', response.params['token'] + assert response.test? + end + + def test_failed_store + @gateway.expects(:ssl_post).returns(failed_store_response) + + assert response = @gateway.store(@credit_card, @options) + assert_instance_of Response, response + assert_failure response + end + + def test_successful_update + @gateway.expects(:ssl_post).returns(successful_update_response) + token = '7595301425001111' + assert response = @gateway.update(token, @credit_card, @options) + assert_success response + assert response.test? + end + + def test_failed_update + @gateway.expects(:ssl_post).returns(failed_update_response) + token = '7595301425001111' + assert response = @gateway.update(token, @credit_card, @options) + assert_instance_of Response, response + assert_failure response + end + + def test_stripping_non_word_characters_from_zip + bad_zip = '99577-0727' + stripped_zip = '995770727' + + @options[:billing_address][:zip] = bad_zip + + @gateway.expects(:commit).with(anything, anything, has_entries(:avs_zip => stripped_zip), anything) + + @gateway.purchase(@amount, @credit_card, @options) + end + + def test_zip_codes_with_letters_are_left_intact + @options[:billing_address][:zip] = '.K1%Z_5E3-' + + @gateway.expects(:commit).with(anything, anything, has_entries(:avs_zip => 'K1Z5E3'), anything) + + @gateway.purchase(@amount, @credit_card, @options) + end + + def test_custom_fields_in_request + stub_comms do + @gateway.purchase(@amount, @credit_card, @options.merge(:customer_number => '123', :custom_fields => {:a_key => 'a value'})) + end.check_request do |endpoint, data, headers| + assert_match(/customer_number=123/, data) + assert_match(/a_key/, data) + refute_match(/ssl_a_key/, data) + end.respond_with(successful_purchase_response) + end + + def test_transcript_scrubbing + assert @gateway.supports_scrubbing? + assert_equal @gateway.scrub(pre_scrub), post_scrub + end + private + def successful_purchase_response "ssl_card_number=42********4242 ssl_exp_date=0910 @@ -134,6 +325,23 @@ def successful_purchase_response ssl_txn_time=08/07/2009 09:54:18 PM" end + def successful_purchase_with_multi_currency_response + "ssl_card_number=42********4242 + ssl_exp_date=0910 + ssl_amount=1.00 + ssl_invoice_number= + ssl_description=Test Transaction + ssl_result=0 + ssl_result_message=APPROVED + ssl_txn_id=00000000-0000-0000-0000-00000000000 + ssl_approval_code=123456 + ssl_cvv2_response=P + ssl_avs_response=X + ssl_account_balance=0.00 + ssl_transaction_currency=EUR + ssl_txn_time=08/07/2009 09:54:18 PM" + end + def successful_refund_response "ssl_card_number=42*****2222 ssl_exp_date= @@ -208,7 +416,7 @@ def failed_void_response end def invalid_login_response - <<-RESPONSE + <<-RESPONSE ssl_result=7000\r ssl_result_message=The VirtualMerchant ID and/or User ID supplied in the authorization request is invalid.\r RESPONSE @@ -235,4 +443,146 @@ def failed_authorization_response errorName=Credit Card Number Invalid errorMessage=The Credit Card Number supplied in the authorization request appears to be invalid." end + + def successful_capture_response + "ssl_card_number=42********4242 + ssl_exp_date=0910 + ssl_amount=1.00 + ssl_customer_code= + ssl_salestax= + ssl_invoice_number= + ssl_result=0 + ssl_result_message=APPROVAL + ssl_txn_id=00000000-0000-0000-0000-00000000000 + ssl_approval_code=123456 + ssl_cvv2_response=P + ssl_avs_response=X + ssl_account_balance=0.00 + ssl_txn_time=08/07/2009 09:56:11 PM" + end + + def failed_capture_response + "errorCode=5040 + errorName=Invalid Transaction ID + errorMessage=The transaction ID is invalid for this transaction type" + end + + def successful_store_response + "ssl_transaction_type=CCGETTOKEN + ssl_result=0 + ssl_token=7595301425001111 + ssl_card_number=41**********1111 + ssl_token_response=SUCCESS + ssl_add_token_response=Card Updated + vu_aamc_id=" + end + + def failed_store_response + "errorCode=5000 + errorName=Credit Card Number Invalid + errorMessage=The Credit Card Number supplied in the authorization request appears to be invalid." + end + + def successful_update_response + "ssl_token=7595301425001111 + ssl_card_type=VISA + ssl_card_number=************1111 + ssl_exp_date=1015 + ssl_company= + ssl_customer_id= + ssl_first_name=John + ssl_last_name=Doe + ssl_avs_address= + ssl_address2= + ssl_avs_zip= + ssl_city= + ssl_state= + ssl_country= + ssl_phone= + ssl_email= + ssl_description= + ssl_user_id=webpage + ssl_token_response=SUCCESS + ssl_result=0" + end + + def failed_update_response + "errorCode=5000 + errorName=Credit Card Number Invalid + errorMessage=The Credit Card Number supplied in the authorization request appears to be invalid." + end + + def pre_scrub + %q{ +opening connection to api.demo.convergepay.com:443... +opened +starting SSL for api.demo.convergepay.com:443... +SSL established +<- "POST /VirtualMerchantDemo/process.do HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: api.demo.convergepay.com\r\nContent-Length: 616\r\n\r\n" +<- "ssl_merchant_id=000127&ssl_pin=IERAOBEE5V0D6Q3Q6R51TG89XAIVGEQ3LGLKMKCKCVQBGGGAU7FN627GPA54P5HR&ssl_show_form=false&ssl_result_format=ASCII&ssl_user_id=ssltest&ssl_invoice_number=&ssl_description=Test+Transaction&ssl_card_number=4124939999999990&ssl_exp_date=0919&ssl_cvv2cvc2=123&ssl_cvv2cvc2_indicator=1&ssl_first_name=Longbob&ssl_last_name=Longsen&ssl_avs_address=456+My+Street&ssl_address2=Apt+1&ssl_avs_zip=K1C2N6&ssl_city=Ottawa&ssl_state=ON&ssl_company=Widgets+Inc&ssl_phone=%28555%29555-5555&ssl_country=CA&ssl_email=paul%40domain.com&ssl_cardholder_ip=203.0.113.0&ssl_amount=1.00&ssl_transaction_type=CCSALE" +-> "HTTP/1.1 200 OK\r\n" +-> "Date: Wed, 03 Jan 2018 21:40:26 GMT\r\n" +-> "Pragma: no-cache\r\n" +-> "Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0\r\n" +-> "Expires: 0\r\n" +-> "Content-Disposition: inline; filename=response.txt\r\n" +-> "AuthApproved: true\r\n" +-> "AuthResponse: AA\r\n" +-> "Set-Cookie: JSESSIONID=00007wKfJV3-JFME8QiC_RCDjuI:14j4qkv92; HTTPOnly; Path=/; Secure\r\n" +-> "Set-Cookie: JSESSIONID=0000uW6woWZ84eAJunhFLfJz8hS:14j4qkv92; HTTPOnly; Path=/; Secure\r\n" +-> "Connection: close\r\n" +-> "Content-Type: text/plain\r\n" +-> "Content-Language: en-US\r\n" +-> "Content-Encoding: gzip\r\n" +-> "Transfer-Encoding: chunked\r\n" +-> "\r\n" +-> "1A5 \r\n" +reading 421 bytes... +-> "\x1F\x8B\b\x00\x00\x00\x00\x00\x00\x03MR\xEFk\xDB0\x10\xFD\xDE\xBF\xC2\x1F\xB7\x81[\xC9\xB1\x1D\xBB \x98\x7FtP\xD66!+\xDBGs\xB1o\x99\xC0\x96\x84%{q\xFF\xFA\xC9R\x12f\x10\xDC\xBDw\xBEw\xEF8\xAD\xFB\xA6\x85\xB1k\xC44\x1Cqd1\xFDr\xFB\xF2<'w\xDA\x16\xE0Y5\x1D\x18d$\xA7\xB9C`\x90\x930\x8C\xDE\x13_\xA1\xA1Gm\xE0\xCC\\\xC6\xC5,y\x8B\xD7\x9E\x0E\x130\xA0\x8FV9\x1Fu\xA8`4\xD3\x88\xBE\xFB\xDD;WM\xE1;\xFBJ9\xA8\x1E\r\x97\xE2Rp\x05A,\xEC\x17\xEFNht\xF0,Z\x87\xFF\xE6\xA36^\xE6E\x8A\xD3Q\x1E\x1D\xDC\xC3\xFF\xA8F\xE1P\x98u\x03]7\xA2\xD6,N\xD2\xE0u\t~\x98\x11\xD1x\xD63\x11+\x94\t\xA8W\xE5fa;c\xE0/\xB8\xDC\x9A\xB5\x03\xED\xDEn\xDD>\xB8b\xDFi\x15\xBD\xA5\xBE~u1.\xAC*\\\xAA\xFEH\x81\xECS\x92$\x9F\xED\v\xEDK\x1C\x8E\x03\xF0\x9E)\x98\xFA\xAF\x9D\xB4\xB1\xB8\xB7\xF6\x1Cc\a\x98z\xC3\xFCz}\xD2\fv(8!+\xF6\xFB\xC3\xEEg\xF1\xE28s\x16\r\xEF\x18\xD9\x10J\xB3\x82&!\xA9\xD3:O\xF3*|\x8A\xEA2\x8C\xB34\t\xB3o\xDB$,\xD3\xA2,\xB3tC\xB7E\xE9\xFE\x04\xA5F9\xC3:l\x87,\xDEnI\x1C9\xA2\x9D\xE7h\xD5TR\xE8\xCB\xD6W\x8B7\xE4\xE2\xBAu&\x9B#\xF4 Z{\x1C\xD7cX'2\xDCn\x9C\xD0\a\xB2y\x88\b\xCD\x02\x12?\xC6\xE41\xDA\x06\xFBW/\xB1\xDE\x9CY\x14\xB2\xEA\xF0T?\xBFW\xC5\xA1\xFE\aC\x85\x1DS\x8C\x02\x00\x00" +read 421 bytes +reading 2 bytes... +-> "\r\n" +read 2 bytes +-> "0\r\n" +-> "\r\n" +Conn close +}} + end + + def post_scrub + %q{ +opening connection to api.demo.convergepay.com:443... +opened +starting SSL for api.demo.convergepay.com:443... +SSL established +<- "POST /VirtualMerchantDemo/process.do HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: api.demo.convergepay.com\r\nContent-Length: 616\r\n\r\n" +<- "ssl_merchant_id=000127&ssl_pin=[FILTERED]&ssl_show_form=false&ssl_result_format=ASCII&ssl_user_id=ssltest&ssl_invoice_number=&ssl_description=Test+Transaction&ssl_card_number=[FILTERED]&ssl_exp_date=0919&ssl_cvv2cvc2=[FILTERED]&ssl_cvv2cvc2_indicator=1&ssl_first_name=Longbob&ssl_last_name=Longsen&ssl_avs_address=456+My+Street&ssl_address2=Apt+1&ssl_avs_zip=K1C2N6&ssl_city=Ottawa&ssl_state=ON&ssl_company=Widgets+Inc&ssl_phone=%28555%29555-5555&ssl_country=CA&ssl_email=paul%40domain.com&ssl_cardholder_ip=203.0.113.0&ssl_amount=1.00&ssl_transaction_type=CCSALE" +-> "HTTP/1.1 200 OK\r\n" +-> "Date: Wed, 03 Jan 2018 21:40:26 GMT\r\n" +-> "Pragma: no-cache\r\n" +-> "Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0\r\n" +-> "Expires: 0\r\n" +-> "Content-Disposition: inline; filename=response.txt\r\n" +-> "AuthApproved: true\r\n" +-> "AuthResponse: AA\r\n" +-> "Set-Cookie: JSESSIONID=00007wKfJV3-JFME8QiC_RCDjuI:14j4qkv92; HTTPOnly; Path=/; Secure\r\n" +-> "Set-Cookie: JSESSIONID=0000uW6woWZ84eAJunhFLfJz8hS:14j4qkv92; HTTPOnly; Path=/; Secure\r\n" +-> "Connection: close\r\n" +-> "Content-Type: text/plain\r\n" +-> "Content-Language: en-US\r\n" +-> "Content-Encoding: gzip\r\n" +-> "Transfer-Encoding: chunked\r\n" +-> "\r\n" +-> "1A5 \r\n" +reading 421 bytes... +-> "\x1F\x8B\b\x00\x00\x00\x00\x00\x00\x03MR\xEFk\xDB0\x10\xFD\xDE\xBF\xC2\x1F\xB7\x81[\xC9\xB1\x1D\xBB \x98\x7FtP\xD66!+\xDBGs\xB1o\x99\xC0\x96\x84%{q\xFF\xFA\xC9R\x12f\x10\xDC\xBDw\xBEw\xEF8\xAD\xFB\xA6\x85\xB1k\xC44\x1Cqd1\xFDr\xFB\xF2<'w\xDA\x16\xE0Y5\x1D\x18d$\xA7\xB9C`\x90\x930\x8C\xDE\x13_\xA1\xA1Gm\xE0\xCC\\\xC6\xC5,y\x8B\xD7\x9E\x0E\x130\xA0\x8FV9\x1Fu\xA8`4\xD3\x88\xBE\xFB\xDD;WM\xE1;\xFBJ9\xA8\x1E\r\x97\xE2Rp\x05A,\xEC\x17\xEFNht\xF0,Z\x87\xFF\xE6\xA36^\xE6E\x8A\xD3Q\x1E\x1D\xDC\xC3\xFF\xA8F\xE1P\x98u\x03]7\xA2\xD6,N\xD2\xE0u\t~\x98\x11\xD1x\xD63\x11+\x94\t\xA8W\xE5fa;c\xE0/\xB8\xDC\x9A\xB5\x03\xED\xDEn\xDD>\xB8b\xDFi\x15\xBD\xA5\xBE~u1.\xAC*\\\xAA\xFEH\x81\xECS\x92$\x9F\xED\v\xEDK\x1C\x8E\x03\xF0\x9E)\x98\xFA\xAF\x9D\xB4\xB1\xB8\xB7\xF6\x1Cc\a\x98z\xC3\xFCz}\xD2\fv(8!+\xF6\xFB\xC3\xEEg\xF1\xE28s\x16\r\xEF\x18\xD9\x10J\xB3\x82&!\xA9\xD3:O\xF3*|\x8A\xEA2\x8C\xB34\t\xB3o\xDB$,\xD3\xA2,\xB3tC\xB7E\xE9\xFE\x04\xA5F9\xC3:l\x87,\xDEnI\x1C9\xA2\x9D\xE7h\xD5TR\xE8\xCB\xD6W\x8B7\xE4\xE2\xBAu&\x9B#\xF4 Z{\x1C\xD7cX'2\xDCn\x9C\xD0\a\xB2y\x88\b\xCD\x02\x12?\xC6\xE41\xDA\x06\xFBW/\xB1\xDE\x9CY\x14\xB2\xEA\xF0T?\xBFW\xC5\xA1\xFE\aC\x85\x1DS\x8C\x02\x00\x00" +read 421 bytes +reading 2 bytes... +-> "\r\n" +read 2 bytes +-> "0\r\n" +-> "\r\n" +Conn close +}} + end end diff --git a/test/unit/gateways/element_test.rb b/test/unit/gateways/element_test.rb new file mode 100644 index 00000000000..253cfcdccbf --- /dev/null +++ b/test/unit/gateways/element_test.rb @@ -0,0 +1,267 @@ +require 'test_helper' + +class ElementTest < Test::Unit::TestCase + def setup + @gateway = ElementGateway.new(account_id: '', account_token: '', application_id: '', acceptor_id: '', application_name: '', application_version: '') + @credit_card = credit_card + @check = check + @amount = 100 + + @options = { + order_id: '1', + billing_address: address, + description: 'Store Purchase' + } + end + + def test_successful_purchase + @gateway.expects(:ssl_post).returns(successful_purchase_response) + + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + + assert_equal '2005831886|100', response.authorization + end + + def test_failed_purchase + @gateway.expects(:ssl_post).returns(failed_purchase_response) + + response = @gateway.purchase(@amount, @credit_card, @options) + assert_failure response + end + + def test_successful_purchase_with_echeck + @gateway.expects(:ssl_post).returns(successful_purchase_with_echeck_response) + + response = @gateway.purchase(@amount, @check, @options) + assert_success response + + assert_equal '2005838412|100', response.authorization + end + + def test_failed_purchase_with_echeck + @gateway.expects(:ssl_post).returns(failed_purchase_with_echeck_response) + + response = @gateway.purchase(@amount, @check, @options) + assert_failure response + end + + def test_successful_purchase_with_payment_account_token + @gateway.expects(:ssl_post).returns(successful_purchase_with_payment_account_token_response) + + response = @gateway.purchase(@amount, 'payment-account-token-id', @options) + assert_success response + + assert_equal '2005838405|100', response.authorization + end + + def test_failed_purchase_with_payment_account_token + @gateway.expects(:ssl_post).returns(failed_purchase_with_payment_account_token_response) + + response = @gateway.purchase(@amount, 'bad-payment-account-token-id', @options) + assert_failure response + end + + def test_successful_authorize + @gateway.expects(:ssl_post).returns(successful_authorize_response) + + response = @gateway.authorize(@amount, @credit_card, @options) + assert_success response + assert_equal 'Approved', response.message + assert_equal '2005832533|100', response.authorization + end + + def test_failed_authorize + @gateway.expects(:ssl_post).returns(failed_authorize_response) + + response = @gateway.authorize(@amount, @credit_card, @options) + assert_failure response + assert_equal 'Declined', response.message + end + + def test_successful_capture + @gateway.expects(:ssl_post).returns(successful_capture_response) + + response = @gateway.capture(@amount, 'trans-id') + assert_success response + assert_equal 'Success', response.message + end + + def test_failed_capture + @gateway.expects(:ssl_post).returns(failed_capture_response) + + response = @gateway.capture(@amount, 'bad-trans-id') + assert_failure response + assert_equal 'TransactionID required', response.message + end + + def test_successful_refund + @gateway.expects(:ssl_post).returns(successful_refund_response) + + response = @gateway.refund(@amount, 'trans-id') + assert_success response + assert_equal 'Approved', response.message + end + + def test_failed_refund + @gateway.expects(:ssl_post).returns(failed_refund_response) + + response = @gateway.refund(@amount, 'bad-trans-id') + assert_failure response + assert_equal 'TransactionID required', response.message + end + + def test_successful_void + @gateway.expects(:ssl_post).returns(successful_void_response) + + response = @gateway.void('trans-id') + assert_success response + assert_equal 'Success', response.message + end + + def test_failed_void + @gateway.expects(:ssl_post).returns(failed_void_response) + + response = @gateway.void('bad-trans-id') + assert_failure response + assert_equal 'TransactionAmount required', response.message + end + + def test_successful_verify + @gateway.expects(:ssl_post).times(2).returns(successful_authorize_response, successful_void_response) + + response = @gateway.verify(@credit_card, @options) + assert_success response + end + + def test_successful_verify_with_failed_void + @gateway.expects(:ssl_post).times(2).returns(successful_authorize_response, failed_void_response) + + response = @gateway.verify(@credit_card, @options) + assert_success response + end + + def test_failed_verify + @gateway.expects(:ssl_post).returns(failed_authorize_response) + + response = @gateway.verify(@credit_card, @options) + assert_failure response + end + + def test_handles_error_response + @gateway.expects(:ssl_post).returns(error_response) + + response = @gateway.purchase(@amount, @credit_card, @options) + assert_equal response.message, 'TargetNamespace required' + assert_failure response + end + + def test_scrub + assert @gateway.supports_scrubbing? + assert_equal @gateway.scrub(pre_scrubbed), post_scrubbed + end + + private + + def pre_scrubbed + <<-XML +<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<soap:Envelope xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">\n <soap:Body>\n <CreditCardSale xmlns=\"https://transaction.elementexpress.com\">\n <credentials>\n <AccountID>1013963</AccountID>\n <AccountToken>683EED8A1A357EB91575A168E74482A74836FD72B1AD11B41B29B473CA9D65B9FE067701</AccountToken>\n <AcceptorID>3928907</AcceptorID>\n </credentials>\n <application>\n <ApplicationID>5211</ApplicationID>\n <ApplicationName>Spreedly</ApplicationName>\n <ApplicationVersion>1</ApplicationVersion>\n </application>\n <card>\n <CardNumber>4000100011112224</CardNumber>\n <ExpirationMonth>09</ExpirationMonth>\n <ExpirationYear>16</ExpirationYear>\n <CardholderName>Longbob Longsen</CardholderName>\n <CVV>123</CVV>\n </card>\n <transaction>\n <TransactionAmount>1.00</TransactionAmount>\n <MarketCode>Default</MarketCode>\n </transaction>\n <terminal>\n <TerminalID>01</TerminalID>\n <CardPresentCode>UseDefault</CardPresentCode>\n <CardholderPresentCode>UseDefault</CardholderPresentCode>\n <CardInputCode>UseDefault</CardInputCode>\n <CVVPresenceCode>UseDefault</CVVPresenceCode>\n <TerminalCapabilityCode>UseDefault</TerminalCapabilityCode>\n <TerminalEnvironmentCode>UseDefault</TerminalEnvironmentCode>\n <MotoECICode>UseDefault</MotoECICode>\n </terminal>\n <address>\n <BillingAddress1>456 My Street</BillingAddress1>\n <BillingAddress2>Apt 1</BillingAddress2>\n <BillingCity>Ottawa</BillingCity>\n <BillingState>ON</BillingState>\n <BillingZipcode>K1C2N6</BillingZipcode>\n </address>\n </CreditCardSale>\n </soap:Body>\n</soap:Envelope>\n + XML + end + + def post_scrubbed + <<-XML +<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<soap:Envelope xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">\n <soap:Body>\n <CreditCardSale xmlns=\"https://transaction.elementexpress.com\">\n <credentials>\n <AccountID>1013963</AccountID>\n <AccountToken>[FILTERED]</AccountToken>\n <AcceptorID>3928907</AcceptorID>\n </credentials>\n <application>\n <ApplicationID>5211</ApplicationID>\n <ApplicationName>Spreedly</ApplicationName>\n <ApplicationVersion>1</ApplicationVersion>\n </application>\n <card>\n <CardNumber>[FILTERED]</CardNumber>\n <ExpirationMonth>09</ExpirationMonth>\n <ExpirationYear>16</ExpirationYear>\n <CardholderName>Longbob Longsen</CardholderName>\n <CVV>[FILTERED]</CVV>\n </card>\n <transaction>\n <TransactionAmount>1.00</TransactionAmount>\n <MarketCode>Default</MarketCode>\n </transaction>\n <terminal>\n <TerminalID>01</TerminalID>\n <CardPresentCode>UseDefault</CardPresentCode>\n <CardholderPresentCode>UseDefault</CardholderPresentCode>\n <CardInputCode>UseDefault</CardInputCode>\n <CVVPresenceCode>UseDefault</CVVPresenceCode>\n <TerminalCapabilityCode>UseDefault</TerminalCapabilityCode>\n <TerminalEnvironmentCode>UseDefault</TerminalEnvironmentCode>\n <MotoECICode>UseDefault</MotoECICode>\n </terminal>\n <address>\n <BillingAddress1>456 My Street</BillingAddress1>\n <BillingAddress2>Apt 1</BillingAddress2>\n <BillingCity>Ottawa</BillingCity>\n <BillingState>ON</BillingState>\n <BillingZipcode>K1C2N6</BillingZipcode>\n </address>\n </CreditCardSale>\n </soap:Body>\n</soap:Envelope>\n + XML + end + + def error_response + <<-XML +<Response xmlns='https://transaction.elementexpress.com'><Response><ExpressResponseCode>103</ExpressResponseCode><ExpressResponseMessage>TargetNamespace required</ExpressResponseMessage></Response></Response> + XML + end + + def successful_purchase_response + <<-XML +<?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><soap:Body><CreditCardSaleResponse xmlns="https://transaction.elementexpress.com"><response><ExpressResponseCode>0</ExpressResponseCode><ExpressResponseMessage>Approved</ExpressResponseMessage><ExpressTransactionDate>20151201</ExpressTransactionDate><ExpressTransactionTime>104518</ExpressTransactionTime><ExpressTransactionTimezone>UTC-05:00</ExpressTransactionTimezone><HostResponseCode>000</HostResponseCode><HostResponseMessage>AP</HostResponseMessage><Credentials /><Batch><BatchCloseType>Regular</BatchCloseType><BatchQueryType>Totals</BatchQueryType><HostBatchID>1</HostBatchID><HostItemID>96</HostItemID><HostBatchAmount>2962.00</HostBatchAmount><BatchGroupingCode>FullBatch</BatchGroupingCode><BatchIndexCode>Current</BatchIndexCode></Batch><Card><EncryptedFormat>Default</EncryptedFormat><AVSResponseCode>N</AVSResponseCode><CVVResponseCode>M</CVVResponseCode><CardLogo>Visa</CardLogo></Card><Transaction><TransactionID>2005831886</TransactionID><ApprovalNumber>000045</ApprovalNumber><ReversalType>System</ReversalType><MarketCode>Default</MarketCode><AcquirerData>aVb001234567810425c0425d5e00</AcquirerData><BillPaymentFlag>False</BillPaymentFlag><DuplicateCheckDisableFlag>False</DuplicateCheckDisableFlag><DuplicateOverrideFlag>False</DuplicateOverrideFlag><RecurringFlag>False</RecurringFlag><ProcessorName>NULL_PROCESSOR_TEST</ProcessorName><TransactionStatus>Approved</TransactionStatus><TransactionStatusCode>1</TransactionStatusCode><PartialApprovedFlag>False</PartialApprovedFlag><ApprovedAmount>1.00</ApprovedAmount><EMVEncryptionFormat>Default</EMVEncryptionFormat><ReversalReason>Unknown</ReversalReason></Transaction><PaymentAccount><PaymentAccountType>CreditCard</PaymentAccountType><PASSUpdaterBatchStatus>Null</PASSUpdaterBatchStatus><PASSUpdaterOption>Null</PASSUpdaterOption></PaymentAccount><Address><BillingAddress1>456 My Street</BillingAddress1><BillingZipcode>K1C2N6</BillingZipcode></Address><ScheduledTask><RunFrequency>OneTimeFuture</RunFrequency><RunUntilCancelFlag>False</RunUntilCancelFlag><ScheduledTaskStatus>Active</ScheduledTaskStatus></ScheduledTask><DemandDepositAccount><DDAAccountType>Checking</DDAAccountType><CheckType>Personal</CheckType></DemandDepositAccount><TransactionSetup><TransactionSetupMethod>Null</TransactionSetupMethod><Device>Null</Device><Embedded>False</Embedded><CVVRequired>False</CVVRequired><AutoReturn>False</AutoReturn><DeviceInputCode>NotUsed</DeviceInputCode></TransactionSetup><Terminal><TerminalType>Unknown</TerminalType><CardPresentCode>UseDefault</CardPresentCode><CardholderPresentCode>UseDefault</CardholderPresentCode><CardInputCode>UseDefault</CardInputCode><CVVPresenceCode>UseDefault</CVVPresenceCode><TerminalCapabilityCode>UseDefault</TerminalCapabilityCode><TerminalEnvironmentCode>UseDefault</TerminalEnvironmentCode><MotoECICode>UseDefault</MotoECICode><CVVResponseType>Regular</CVVResponseType><ConsentCode>NotUsed</ConsentCode><TerminalEncryptionFormat>Default</TerminalEncryptionFormat></Terminal><AutoRental><AutoRentalVehicleClassCode>Unused</AutoRentalVehicleClassCode><AutoRentalDistanceUnit>Unused</AutoRentalDistanceUnit><AutoRentalAuditAdjustmentCode>NoAdjustments</AutoRentalAuditAdjustmentCode></AutoRental><Healthcare><HealthcareFlag>False</HealthcareFlag><HealthcareFirstAccountType>NotSpecified</HealthcareFirstAccountType><HealthcareFirstAmountType>LedgerBalance</HealthcareFirstAmountType><HealthcareFirstAmountSign>Positive</HealthcareFirstAmountSign><HealthcareSecondAccountType>NotSpecified</HealthcareSecondAccountType><HealthcareSecondAmountType>LedgerBalance</HealthcareSecondAmountType><HealthcareSecondAmountSign>Positive</HealthcareSecondAmountSign><HealthcareThirdAccountType>NotSpecified</HealthcareThirdAccountType><HealthcareThirdAmountType>LedgerBalance</HealthcareThirdAmountType><HealthcareThirdAmountSign>Positive</HealthcareThirdAmountSign><HealthcareFourthAccountType>NotSpecified</HealthcareFourthAccountType><HealthcareFourthAmountType>LedgerBalance</HealthcareFourthAmountType><HealthcareFourthAmountSign>Positive</HealthcareFourthAmountSign></Healthcare><Lodging><LodgingPrestigiousPropertyCode>NonParticipant</LodgingPrestigiousPropertyCode><LodgingSpecialProgramCode>Default</LodgingSpecialProgramCode><LodgingChargeType>Default</LodgingChargeType></Lodging><BIN /><EnhancedBIN /><Token><TokenProvider>Null</TokenProvider></Token></response></CreditCardSaleResponse></soap:Body></soap:Envelope> + XML + end + + def successful_purchase_with_echeck_response + <<-XML +<?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><soap:Body><CheckSaleResponse xmlns="https://transaction.elementexpress.com"><response><ExpressResponseCode>0</ExpressResponseCode><ExpressResponseMessage>Success</ExpressResponseMessage><ExpressTransactionDate>20151202</ExpressTransactionDate><ExpressTransactionTime>090320</ExpressTransactionTime><ExpressTransactionTimezone>UTC-05:00</ExpressTransactionTimezone><HostResponseCode>0</HostResponseCode><HostResponseMessage>Transaction Processed</HostResponseMessage><Credentials /><Batch><BatchCloseType>Regular</BatchCloseType><BatchQueryType>Totals</BatchQueryType><BatchGroupingCode>FullBatch</BatchGroupingCode><BatchIndexCode>Current</BatchIndexCode></Batch><Card><EncryptedFormat>Default</EncryptedFormat></Card><Transaction><TransactionID>2005838412</TransactionID><ReferenceNumber>347520966b3df3e93051b5dc85c355a54e3012c2</ReferenceNumber><ReversalType>System</ReversalType><MarketCode>Default</MarketCode><BillPaymentFlag>False</BillPaymentFlag><DuplicateCheckDisableFlag>False</DuplicateCheckDisableFlag><DuplicateOverrideFlag>False</DuplicateOverrideFlag><RecurringFlag>False</RecurringFlag><ProcessorName>NULL_PROCESSOR_TEST</ProcessorName><TransactionStatus>Pending</TransactionStatus><TransactionStatusCode>10</TransactionStatusCode><PartialApprovedFlag>False</PartialApprovedFlag><EMVEncryptionFormat>Default</EMVEncryptionFormat><ReversalReason>Unknown</ReversalReason></Transaction><PaymentAccount><PaymentAccountType>CreditCard</PaymentAccountType><PASSUpdaterBatchStatus>Null</PASSUpdaterBatchStatus><PASSUpdaterOption>Null</PASSUpdaterOption></PaymentAccount><Address><BillingAddress1>456 My Street</BillingAddress1><BillingZipcode>K1C2N6</BillingZipcode></Address><ScheduledTask><RunFrequency>OneTimeFuture</RunFrequency><RunUntilCancelFlag>False</RunUntilCancelFlag><ScheduledTaskStatus>Active</ScheduledTaskStatus></ScheduledTask><DemandDepositAccount><DDAAccountType>Checking</DDAAccountType><CheckType>Personal</CheckType></DemandDepositAccount><TransactionSetup><TransactionSetupMethod>Null</TransactionSetupMethod><Device>Null</Device><Embedded>False</Embedded><CVVRequired>False</CVVRequired><AutoReturn>False</AutoReturn><DeviceInputCode>NotUsed</DeviceInputCode></TransactionSetup><Terminal><TerminalType>Unknown</TerminalType><CardPresentCode>UseDefault</CardPresentCode><CardholderPresentCode>UseDefault</CardholderPresentCode><CardInputCode>UseDefault</CardInputCode><CVVPresenceCode>UseDefault</CVVPresenceCode><TerminalCapabilityCode>UseDefault</TerminalCapabilityCode><TerminalEnvironmentCode>UseDefault</TerminalEnvironmentCode><MotoECICode>UseDefault</MotoECICode><CVVResponseType>Regular</CVVResponseType><ConsentCode>NotUsed</ConsentCode><TerminalEncryptionFormat>Default</TerminalEncryptionFormat></Terminal><AutoRental><AutoRentalVehicleClassCode>Unused</AutoRentalVehicleClassCode><AutoRentalDistanceUnit>Unused</AutoRentalDistanceUnit><AutoRentalAuditAdjustmentCode>NoAdjustments</AutoRentalAuditAdjustmentCode></AutoRental><Healthcare><HealthcareFlag>False</HealthcareFlag><HealthcareFirstAccountType>NotSpecified</HealthcareFirstAccountType><HealthcareFirstAmountType>LedgerBalance</HealthcareFirstAmountType><HealthcareFirstAmountSign>Positive</HealthcareFirstAmountSign><HealthcareSecondAccountType>NotSpecified</HealthcareSecondAccountType><HealthcareSecondAmountType>LedgerBalance</HealthcareSecondAmountType><HealthcareSecondAmountSign>Positive</HealthcareSecondAmountSign><HealthcareThirdAccountType>NotSpecified</HealthcareThirdAccountType><HealthcareThirdAmountType>LedgerBalance</HealthcareThirdAmountType><HealthcareThirdAmountSign>Positive</HealthcareThirdAmountSign><HealthcareFourthAccountType>NotSpecified</HealthcareFourthAccountType><HealthcareFourthAmountType>LedgerBalance</HealthcareFourthAmountType><HealthcareFourthAmountSign>Positive</HealthcareFourthAmountSign></Healthcare><Lodging><LodgingPrestigiousPropertyCode>NonParticipant</LodgingPrestigiousPropertyCode><LodgingSpecialProgramCode>Default</LodgingSpecialProgramCode><LodgingChargeType>Default</LodgingChargeType></Lodging><BIN /><EnhancedBIN /><Token><TokenProvider>Null</TokenProvider></Token></response></CheckSaleResponse></soap:Body></soap:Envelope> + XML + end + + def successful_purchase_with_payment_account_token_response + <<-XML +<?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><soap:Body><CreditCardSaleResponse xmlns="https://transaction.elementexpress.com"><response><ExpressResponseCode>0</ExpressResponseCode><ExpressResponseMessage>Approved</ExpressResponseMessage><ExpressTransactionDate>20151202</ExpressTransactionDate><ExpressTransactionTime>090144</ExpressTransactionTime><ExpressTransactionTimezone>UTC-05:00</ExpressTransactionTimezone><HostResponseCode>000</HostResponseCode><HostResponseMessage>AP</HostResponseMessage><Credentials /><Batch><BatchCloseType>Regular</BatchCloseType><BatchQueryType>Totals</BatchQueryType><HostBatchID>1</HostBatchID><HostItemID>155</HostItemID><HostBatchAmount>2995.00</HostBatchAmount><BatchGroupingCode>FullBatch</BatchGroupingCode><BatchIndexCode>Current</BatchIndexCode></Batch><Card><EncryptedFormat>Default</EncryptedFormat><AVSResponseCode>N</AVSResponseCode><CardLogo>Visa</CardLogo></Card><Transaction><TransactionID>2005838405</TransactionID><ApprovalNumber>000001</ApprovalNumber><ReferenceNumber>c0d498aa3c2c07169d13a989a7af91af5bc4e6a0</ReferenceNumber><ReversalType>System</ReversalType><MarketCode>Default</MarketCode><AcquirerData>aVb001234567810425c0425d5e00</AcquirerData><BillPaymentFlag>False</BillPaymentFlag><DuplicateCheckDisableFlag>False</DuplicateCheckDisableFlag><DuplicateOverrideFlag>False</DuplicateOverrideFlag><RecurringFlag>False</RecurringFlag><ProcessorName>NULL_PROCESSOR_TEST</ProcessorName><TransactionStatus>Approved</TransactionStatus><TransactionStatusCode>1</TransactionStatusCode><PartialApprovedFlag>False</PartialApprovedFlag><ApprovedAmount>1.00</ApprovedAmount><EMVEncryptionFormat>Default</EMVEncryptionFormat><ReversalReason>Unknown</ReversalReason></Transaction><PaymentAccount><PaymentAccountID>C875D86C-5913-487D-822E-76B27E2C2A4E</PaymentAccountID><PaymentAccountType>CreditCard</PaymentAccountType><PaymentAccountReferenceNumber>147b0b90f74faac13afb618fdabee3a4e75bf03b</PaymentAccountReferenceNumber><PASSUpdaterBatchStatus>Null</PASSUpdaterBatchStatus><PASSUpdaterOption>Null</PASSUpdaterOption></PaymentAccount><Address><BillingAddress1>456 My Street</BillingAddress1><BillingZipcode>K1C2N6</BillingZipcode></Address><ScheduledTask><RunFrequency>OneTimeFuture</RunFrequency><RunUntilCancelFlag>False</RunUntilCancelFlag><ScheduledTaskStatus>Active</ScheduledTaskStatus></ScheduledTask><DemandDepositAccount><DDAAccountType>Checking</DDAAccountType><CheckType>Personal</CheckType></DemandDepositAccount><TransactionSetup><TransactionSetupMethod>Null</TransactionSetupMethod><Device>Null</Device><Embedded>False</Embedded><CVVRequired>False</CVVRequired><AutoReturn>False</AutoReturn><DeviceInputCode>NotUsed</DeviceInputCode></TransactionSetup><Terminal><TerminalType>Unknown</TerminalType><CardPresentCode>UseDefault</CardPresentCode><CardholderPresentCode>UseDefault</CardholderPresentCode><CardInputCode>UseDefault</CardInputCode><CVVPresenceCode>UseDefault</CVVPresenceCode><TerminalCapabilityCode>UseDefault</TerminalCapabilityCode><TerminalEnvironmentCode>UseDefault</TerminalEnvironmentCode><MotoECICode>UseDefault</MotoECICode><CVVResponseType>Regular</CVVResponseType><ConsentCode>NotUsed</ConsentCode><TerminalEncryptionFormat>Default</TerminalEncryptionFormat></Terminal><AutoRental><AutoRentalVehicleClassCode>Unused</AutoRentalVehicleClassCode><AutoRentalDistanceUnit>Unused</AutoRentalDistanceUnit><AutoRentalAuditAdjustmentCode>NoAdjustments</AutoRentalAuditAdjustmentCode></AutoRental><Healthcare><HealthcareFlag>False</HealthcareFlag><HealthcareFirstAccountType>NotSpecified</HealthcareFirstAccountType><HealthcareFirstAmountType>LedgerBalance</HealthcareFirstAmountType><HealthcareFirstAmountSign>Positive</HealthcareFirstAmountSign><HealthcareSecondAccountType>NotSpecified</HealthcareSecondAccountType><HealthcareSecondAmountType>LedgerBalance</HealthcareSecondAmountType><HealthcareSecondAmountSign>Positive</HealthcareSecondAmountSign><HealthcareThirdAccountType>NotSpecified</HealthcareThirdAccountType><HealthcareThirdAmountType>LedgerBalance</HealthcareThirdAmountType><HealthcareThirdAmountSign>Positive</HealthcareThirdAmountSign><HealthcareFourthAccountType>NotSpecified</HealthcareFourthAccountType><HealthcareFourthAmountType>LedgerBalance</HealthcareFourthAmountType><HealthcareFourthAmountSign>Positive</HealthcareFourthAmountSign></Healthcare><Lodging><LodgingPrestigiousPropertyCode>NonParticipant</LodgingPrestigiousPropertyCode><LodgingSpecialProgramCode>Default</LodgingSpecialProgramCode><LodgingChargeType>Default</LodgingChargeType></Lodging><BIN /><EnhancedBIN /><Token><TokenProvider>Null</TokenProvider></Token></response></CreditCardSaleResponse></soap:Body></soap:Envelope> + XML + end + + def failed_purchase_with_echeck_response + <<-XML +<?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><soap:Body><CreditCardSaleResponse xmlns="https://transaction.elementexpress.com"><response><ExpressResponseCode>101</ExpressResponseCode><ExpressResponseMessage>CardNumber Required</ExpressResponseMessage><ExpressTransactionDate>20151202</ExpressTransactionDate><ExpressTransactionTime>090342</ExpressTransactionTime><ExpressTransactionTimezone>UTC-05:00</ExpressTransactionTimezone><Credentials /><Batch><BatchCloseType>Regular</BatchCloseType><BatchQueryType>Totals</BatchQueryType><HostBatchID>1</HostBatchID><BatchGroupingCode>FullBatch</BatchGroupingCode><BatchIndexCode>Current</BatchIndexCode></Batch><Card><EncryptedFormat>Default</EncryptedFormat></Card><Transaction><ReferenceNumber>8fe3b762a2a4344d938c32be31f36e354fb28ee3</ReferenceNumber><ReversalType>System</ReversalType><MarketCode>Default</MarketCode><BillPaymentFlag>False</BillPaymentFlag><DuplicateCheckDisableFlag>False</DuplicateCheckDisableFlag><DuplicateOverrideFlag>False</DuplicateOverrideFlag><RecurringFlag>False</RecurringFlag><ProcessorName>NULL_PROCESSOR_TEST</ProcessorName><PartialApprovedFlag>False</PartialApprovedFlag><EMVEncryptionFormat>Default</EMVEncryptionFormat><ReversalReason>Unknown</ReversalReason></Transaction><PaymentAccount><PaymentAccountType>CreditCard</PaymentAccountType><PASSUpdaterBatchStatus>Null</PASSUpdaterBatchStatus><PASSUpdaterOption>Null</PASSUpdaterOption></PaymentAccount><Address /><ScheduledTask><RunFrequency>OneTimeFuture</RunFrequency><RunUntilCancelFlag>False</RunUntilCancelFlag><ScheduledTaskStatus>Active</ScheduledTaskStatus></ScheduledTask><DemandDepositAccount><DDAAccountType>Checking</DDAAccountType><CheckType>Personal</CheckType></DemandDepositAccount><TransactionSetup><TransactionSetupMethod>Null</TransactionSetupMethod><Device>Null</Device><Embedded>False</Embedded><CVVRequired>False</CVVRequired><AutoReturn>False</AutoReturn><DeviceInputCode>NotUsed</DeviceInputCode></TransactionSetup><Terminal><TerminalType>Unknown</TerminalType><CardPresentCode>UseDefault</CardPresentCode><CardholderPresentCode>UseDefault</CardholderPresentCode><CardInputCode>UseDefault</CardInputCode><CVVPresenceCode>UseDefault</CVVPresenceCode><TerminalCapabilityCode>UseDefault</TerminalCapabilityCode><TerminalEnvironmentCode>UseDefault</TerminalEnvironmentCode><MotoECICode>UseDefault</MotoECICode><CVVResponseType>Regular</CVVResponseType><ConsentCode>NotUsed</ConsentCode><TerminalEncryptionFormat>Default</TerminalEncryptionFormat></Terminal><AutoRental><AutoRentalVehicleClassCode>Unused</AutoRentalVehicleClassCode><AutoRentalDistanceUnit>Unused</AutoRentalDistanceUnit><AutoRentalAuditAdjustmentCode>NoAdjustments</AutoRentalAuditAdjustmentCode></AutoRental><Healthcare><HealthcareFlag>False</HealthcareFlag><HealthcareFirstAccountType>NotSpecified</HealthcareFirstAccountType><HealthcareFirstAmountType>LedgerBalance</HealthcareFirstAmountType><HealthcareFirstAmountSign>Positive</HealthcareFirstAmountSign><HealthcareSecondAccountType>NotSpecified</HealthcareSecondAccountType><HealthcareSecondAmountType>LedgerBalance</HealthcareSecondAmountType><HealthcareSecondAmountSign>Positive</HealthcareSecondAmountSign><HealthcareThirdAccountType>NotSpecified</HealthcareThirdAccountType><HealthcareThirdAmountType>LedgerBalance</HealthcareThirdAmountType><HealthcareThirdAmountSign>Positive</HealthcareThirdAmountSign><HealthcareFourthAccountType>NotSpecified</HealthcareFourthAccountType><HealthcareFourthAmountType>LedgerBalance</HealthcareFourthAmountType><HealthcareFourthAmountSign>Positive</HealthcareFourthAmountSign></Healthcare><Lodging><LodgingPrestigiousPropertyCode>NonParticipant</LodgingPrestigiousPropertyCode><LodgingSpecialProgramCode>Default</LodgingSpecialProgramCode><LodgingChargeType>Default</LodgingChargeType></Lodging><BIN /><EnhancedBIN /><Token><TokenProvider>Null</TokenProvider></Token></response></CreditCardSaleResponse></soap:Body></soap:Envelope> + XML + end + + def failed_purchase_with_payment_account_token_response + <<-XML +<?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><soap:Body><CreditCardSaleResponse xmlns="https://transaction.elementexpress.com"><response><ExpressResponseCode>103</ExpressResponseCode><ExpressResponseMessage>PAYMENT ACCOUNT NOT FOUND</ExpressResponseMessage><ExpressTransactionDate>20151202</ExpressTransactionDate><ExpressTransactionTime>090245</ExpressTransactionTime><ExpressTransactionTimezone>UTC-05:00</ExpressTransactionTimezone><Credentials /><Batch><BatchCloseType>Regular</BatchCloseType><BatchQueryType>Totals</BatchQueryType><HostBatchID>1</HostBatchID><BatchGroupingCode>FullBatch</BatchGroupingCode><BatchIndexCode>Current</BatchIndexCode></Batch><Card><EncryptedFormat>Default</EncryptedFormat></Card><Transaction><ReferenceNumber>564bd4943761a37bdbb3f201faa56faa091781b5</ReferenceNumber><ReversalType>System</ReversalType><MarketCode>Default</MarketCode><BillPaymentFlag>False</BillPaymentFlag><DuplicateCheckDisableFlag>False</DuplicateCheckDisableFlag><DuplicateOverrideFlag>False</DuplicateOverrideFlag><RecurringFlag>False</RecurringFlag><ProcessorName>NULL_PROCESSOR_TEST</ProcessorName><PartialApprovedFlag>False</PartialApprovedFlag><EMVEncryptionFormat>Default</EMVEncryptionFormat><ReversalReason>Unknown</ReversalReason></Transaction><PaymentAccount><PaymentAccountID>asdf</PaymentAccountID><PaymentAccountType>CreditCard</PaymentAccountType><PASSUpdaterBatchStatus>Null</PASSUpdaterBatchStatus><PASSUpdaterOption>Null</PASSUpdaterOption></PaymentAccount><Address /><ScheduledTask><RunFrequency>OneTimeFuture</RunFrequency><RunUntilCancelFlag>False</RunUntilCancelFlag><ScheduledTaskStatus>Active</ScheduledTaskStatus></ScheduledTask><DemandDepositAccount><DDAAccountType>Checking</DDAAccountType><CheckType>Personal</CheckType></DemandDepositAccount><TransactionSetup><TransactionSetupMethod>Null</TransactionSetupMethod><Device>Null</Device><Embedded>False</Embedded><CVVRequired>False</CVVRequired><AutoReturn>False</AutoReturn><DeviceInputCode>NotUsed</DeviceInputCode></TransactionSetup><Terminal><TerminalType>Unknown</TerminalType><CardPresentCode>UseDefault</CardPresentCode><CardholderPresentCode>UseDefault</CardholderPresentCode><CardInputCode>UseDefault</CardInputCode><CVVPresenceCode>UseDefault</CVVPresenceCode><TerminalCapabilityCode>UseDefault</TerminalCapabilityCode><TerminalEnvironmentCode>UseDefault</TerminalEnvironmentCode><MotoECICode>UseDefault</MotoECICode><CVVResponseType>Regular</CVVResponseType><ConsentCode>NotUsed</ConsentCode><TerminalEncryptionFormat>Default</TerminalEncryptionFormat></Terminal><AutoRental><AutoRentalVehicleClassCode>Unused</AutoRentalVehicleClassCode><AutoRentalDistanceUnit>Unused</AutoRentalDistanceUnit><AutoRentalAuditAdjustmentCode>NoAdjustments</AutoRentalAuditAdjustmentCode></AutoRental><Healthcare><HealthcareFlag>False</HealthcareFlag><HealthcareFirstAccountType>NotSpecified</HealthcareFirstAccountType><HealthcareFirstAmountType>LedgerBalance</HealthcareFirstAmountType><HealthcareFirstAmountSign>Positive</HealthcareFirstAmountSign><HealthcareSecondAccountType>NotSpecified</HealthcareSecondAccountType><HealthcareSecondAmountType>LedgerBalance</HealthcareSecondAmountType><HealthcareSecondAmountSign>Positive</HealthcareSecondAmountSign><HealthcareThirdAccountType>NotSpecified</HealthcareThirdAccountType><HealthcareThirdAmountType>LedgerBalance</HealthcareThirdAmountType><HealthcareThirdAmountSign>Positive</HealthcareThirdAmountSign><HealthcareFourthAccountType>NotSpecified</HealthcareFourthAccountType><HealthcareFourthAmountType>LedgerBalance</HealthcareFourthAmountType><HealthcareFourthAmountSign>Positive</HealthcareFourthAmountSign></Healthcare><Lodging><LodgingPrestigiousPropertyCode>NonParticipant</LodgingPrestigiousPropertyCode><LodgingSpecialProgramCode>Default</LodgingSpecialProgramCode><LodgingChargeType>Default</LodgingChargeType></Lodging><BIN /><EnhancedBIN /><Token><TokenProvider>Null</TokenProvider></Token></response></CreditCardSaleResponse></soap:Body></soap:Envelope> + XML + end + + def failed_purchase_response + <<-XML +<?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><soap:Body><CreditCardSaleResponse xmlns="https://transaction.elementexpress.com"><response><ExpressResponseCode>20</ExpressResponseCode><ExpressResponseMessage>Declined</ExpressResponseMessage><ExpressTransactionDate>20151201</ExpressTransactionDate><ExpressTransactionTime>104817</ExpressTransactionTime><ExpressTransactionTimezone>UTC-05:00</ExpressTransactionTimezone><HostResponseCode>007</HostResponseCode><HostResponseMessage>DECLINED</HostResponseMessage><Credentials /><Batch><BatchCloseType>Regular</BatchCloseType><BatchQueryType>Totals</BatchQueryType><HostBatchID>1</HostBatchID><BatchGroupingCode>FullBatch</BatchGroupingCode><BatchIndexCode>Current</BatchIndexCode></Batch><Card><EncryptedFormat>Default</EncryptedFormat><AVSResponseCode>N</AVSResponseCode><CVVResponseCode>M</CVVResponseCode><CardLogo>Visa</CardLogo></Card><Transaction><TransactionID>2005831909</TransactionID><ReversalType>System</ReversalType><MarketCode>Default</MarketCode><AcquirerData>aVb001234567810425c0425d5e00</AcquirerData><BillPaymentFlag>False</BillPaymentFlag><DuplicateCheckDisableFlag>False</DuplicateCheckDisableFlag><DuplicateOverrideFlag>False</DuplicateOverrideFlag><RecurringFlag>False</RecurringFlag><ProcessorName>NULL_PROCESSOR_TEST</ProcessorName><TransactionStatus>Declined</TransactionStatus><TransactionStatusCode>2</TransactionStatusCode><PartialApprovedFlag>False</PartialApprovedFlag><EMVEncryptionFormat>Default</EMVEncryptionFormat><ReversalReason>Unknown</ReversalReason></Transaction><PaymentAccount><PaymentAccountType>CreditCard</PaymentAccountType><PASSUpdaterBatchStatus>Null</PASSUpdaterBatchStatus><PASSUpdaterOption>Null</PASSUpdaterOption></PaymentAccount><Address><BillingAddress1>456 My Street</BillingAddress1><BillingZipcode>K1C2N6</BillingZipcode></Address><ScheduledTask><RunFrequency>OneTimeFuture</RunFrequency><RunUntilCancelFlag>False</RunUntilCancelFlag><ScheduledTaskStatus>Active</ScheduledTaskStatus></ScheduledTask><DemandDepositAccount><DDAAccountType>Checking</DDAAccountType><CheckType>Personal</CheckType></DemandDepositAccount><TransactionSetup><TransactionSetupMethod>Null</TransactionSetupMethod><Device>Null</Device><Embedded>False</Embedded><CVVRequired>False</CVVRequired><AutoReturn>False</AutoReturn><DeviceInputCode>NotUsed</DeviceInputCode></TransactionSetup><Terminal><TerminalType>Unknown</TerminalType><CardPresentCode>UseDefault</CardPresentCode><CardholderPresentCode>UseDefault</CardholderPresentCode><CardInputCode>UseDefault</CardInputCode><CVVPresenceCode>UseDefault</CVVPresenceCode><TerminalCapabilityCode>UseDefault</TerminalCapabilityCode><TerminalEnvironmentCode>UseDefault</TerminalEnvironmentCode><MotoECICode>UseDefault</MotoECICode><CVVResponseType>Regular</CVVResponseType><ConsentCode>NotUsed</ConsentCode><TerminalEncryptionFormat>Default</TerminalEncryptionFormat></Terminal><AutoRental><AutoRentalVehicleClassCode>Unused</AutoRentalVehicleClassCode><AutoRentalDistanceUnit>Unused</AutoRentalDistanceUnit><AutoRentalAuditAdjustmentCode>NoAdjustments</AutoRentalAuditAdjustmentCode></AutoRental><Healthcare><HealthcareFlag>False</HealthcareFlag><HealthcareFirstAccountType>NotSpecified</HealthcareFirstAccountType><HealthcareFirstAmountType>LedgerBalance</HealthcareFirstAmountType><HealthcareFirstAmountSign>Positive</HealthcareFirstAmountSign><HealthcareSecondAccountType>NotSpecified</HealthcareSecondAccountType><HealthcareSecondAmountType>LedgerBalance</HealthcareSecondAmountType><HealthcareSecondAmountSign>Positive</HealthcareSecondAmountSign><HealthcareThirdAccountType>NotSpecified</HealthcareThirdAccountType><HealthcareThirdAmountType>LedgerBalance</HealthcareThirdAmountType><HealthcareThirdAmountSign>Positive</HealthcareThirdAmountSign><HealthcareFourthAccountType>NotSpecified</HealthcareFourthAccountType><HealthcareFourthAmountType>LedgerBalance</HealthcareFourthAmountType><HealthcareFourthAmountSign>Positive</HealthcareFourthAmountSign></Healthcare><Lodging><LodgingPrestigiousPropertyCode>NonParticipant</LodgingPrestigiousPropertyCode><LodgingSpecialProgramCode>Default</LodgingSpecialProgramCode><LodgingChargeType>Default</LodgingChargeType></Lodging><BIN /><EnhancedBIN /><Token><TokenProvider>Null</TokenProvider></Token></response></CreditCardSaleResponse></soap:Body></soap:Envelope> + XML + end + + def successful_authorize_response + <<-XML +<?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><soap:Body><CreditCardAuthorizationResponse xmlns="https://transaction.elementexpress.com"><response><ExpressResponseCode>0</ExpressResponseCode><ExpressResponseMessage>Approved</ExpressResponseMessage><ExpressTransactionDate>20151201</ExpressTransactionDate><ExpressTransactionTime>120220</ExpressTransactionTime><ExpressTransactionTimezone>UTC-05:00</ExpressTransactionTimezone><HostResponseCode>000</HostResponseCode><HostResponseMessage>AP</HostResponseMessage><Credentials /><Batch><BatchCloseType>Regular</BatchCloseType><BatchQueryType>Totals</BatchQueryType><HostBatchID>1</HostBatchID><BatchGroupingCode>FullBatch</BatchGroupingCode><BatchIndexCode>Current</BatchIndexCode></Batch><Card><EncryptedFormat>Default</EncryptedFormat><AVSResponseCode>N</AVSResponseCode><CVVResponseCode>M</CVVResponseCode><CardLogo>Visa</CardLogo></Card><Transaction><TransactionID>2005832533</TransactionID><ApprovalNumber>000002</ApprovalNumber><ReversalType>System</ReversalType><MarketCode>Default</MarketCode><AcquirerData>aVb001234567810425c0425d5e00</AcquirerData><BillPaymentFlag>False</BillPaymentFlag><DuplicateCheckDisableFlag>False</DuplicateCheckDisableFlag><DuplicateOverrideFlag>False</DuplicateOverrideFlag><RecurringFlag>False</RecurringFlag><ProcessorName>NULL_PROCESSOR_TEST</ProcessorName><TransactionStatus>Authorized</TransactionStatus><TransactionStatusCode>5</TransactionStatusCode><PartialApprovedFlag>False</PartialApprovedFlag><ApprovedAmount>1.00</ApprovedAmount><EMVEncryptionFormat>Default</EMVEncryptionFormat><ReversalReason>Unknown</ReversalReason></Transaction><PaymentAccount><PaymentAccountType>CreditCard</PaymentAccountType><PASSUpdaterBatchStatus>Null</PASSUpdaterBatchStatus><PASSUpdaterOption>Null</PASSUpdaterOption></PaymentAccount><Address><BillingAddress1>456 My Street</BillingAddress1><BillingZipcode>K1C2N6</BillingZipcode></Address><ScheduledTask><RunFrequency>OneTimeFuture</RunFrequency><RunUntilCancelFlag>False</RunUntilCancelFlag><ScheduledTaskStatus>Active</ScheduledTaskStatus></ScheduledTask><DemandDepositAccount><DDAAccountType>Checking</DDAAccountType><CheckType>Personal</CheckType></DemandDepositAccount><TransactionSetup><TransactionSetupMethod>Null</TransactionSetupMethod><Device>Null</Device><Embedded>False</Embedded><CVVRequired>False</CVVRequired><AutoReturn>False</AutoReturn><DeviceInputCode>NotUsed</DeviceInputCode></TransactionSetup><Terminal><TerminalType>Unknown</TerminalType><CardPresentCode>UseDefault</CardPresentCode><CardholderPresentCode>UseDefault</CardholderPresentCode><CardInputCode>UseDefault</CardInputCode><CVVPresenceCode>UseDefault</CVVPresenceCode><TerminalCapabilityCode>UseDefault</TerminalCapabilityCode><TerminalEnvironmentCode>UseDefault</TerminalEnvironmentCode><MotoECICode>UseDefault</MotoECICode><CVVResponseType>Regular</CVVResponseType><ConsentCode>NotUsed</ConsentCode><TerminalEncryptionFormat>Default</TerminalEncryptionFormat></Terminal><AutoRental><AutoRentalVehicleClassCode>Unused</AutoRentalVehicleClassCode><AutoRentalDistanceUnit>Unused</AutoRentalDistanceUnit><AutoRentalAuditAdjustmentCode>NoAdjustments</AutoRentalAuditAdjustmentCode></AutoRental><Healthcare><HealthcareFlag>False</HealthcareFlag><HealthcareFirstAccountType>NotSpecified</HealthcareFirstAccountType><HealthcareFirstAmountType>LedgerBalance</HealthcareFirstAmountType><HealthcareFirstAmountSign>Positive</HealthcareFirstAmountSign><HealthcareSecondAccountType>NotSpecified</HealthcareSecondAccountType><HealthcareSecondAmountType>LedgerBalance</HealthcareSecondAmountType><HealthcareSecondAmountSign>Positive</HealthcareSecondAmountSign><HealthcareThirdAccountType>NotSpecified</HealthcareThirdAccountType><HealthcareThirdAmountType>LedgerBalance</HealthcareThirdAmountType><HealthcareThirdAmountSign>Positive</HealthcareThirdAmountSign><HealthcareFourthAccountType>NotSpecified</HealthcareFourthAccountType><HealthcareFourthAmountType>LedgerBalance</HealthcareFourthAmountType><HealthcareFourthAmountSign>Positive</HealthcareFourthAmountSign></Healthcare><Lodging><LodgingPrestigiousPropertyCode>NonParticipant</LodgingPrestigiousPropertyCode><LodgingSpecialProgramCode>Default</LodgingSpecialProgramCode><LodgingChargeType>Default</LodgingChargeType></Lodging><BIN /><EnhancedBIN /><Token><TokenProvider>Null</TokenProvider></Token></response></CreditCardAuthorizationResponse></soap:Body></soap:Envelope> + XML + end + + def failed_authorize_response + <<-XML +<?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><soap:Body><CreditCardAuthorizationResponse xmlns="https://transaction.elementexpress.com"><response><ExpressResponseCode>20</ExpressResponseCode><ExpressResponseMessage>Declined</ExpressResponseMessage><ExpressTransactionDate>20151201</ExpressTransactionDate><ExpressTransactionTime>120315</ExpressTransactionTime><ExpressTransactionTimezone>UTC-05:00</ExpressTransactionTimezone><HostResponseCode>007</HostResponseCode><HostResponseMessage>DECLINED</HostResponseMessage><Credentials /><Batch><BatchCloseType>Regular</BatchCloseType><BatchQueryType>Totals</BatchQueryType><HostBatchID>1</HostBatchID><BatchGroupingCode>FullBatch</BatchGroupingCode><BatchIndexCode>Current</BatchIndexCode></Batch><Card><EncryptedFormat>Default</EncryptedFormat><AVSResponseCode>N</AVSResponseCode><CVVResponseCode>M</CVVResponseCode><CardLogo>Visa</CardLogo></Card><Transaction><TransactionID>2005832537</TransactionID><ReversalType>System</ReversalType><MarketCode>Default</MarketCode><AcquirerData>aVb001234567810425c0425d5e00</AcquirerData><BillPaymentFlag>False</BillPaymentFlag><DuplicateCheckDisableFlag>False</DuplicateCheckDisableFlag><DuplicateOverrideFlag>False</DuplicateOverrideFlag><RecurringFlag>False</RecurringFlag><ProcessorName>NULL_PROCESSOR_TEST</ProcessorName><TransactionStatus>Declined</TransactionStatus><TransactionStatusCode>2</TransactionStatusCode><PartialApprovedFlag>False</PartialApprovedFlag><EMVEncryptionFormat>Default</EMVEncryptionFormat><ReversalReason>Unknown</ReversalReason></Transaction><PaymentAccount><PaymentAccountType>CreditCard</PaymentAccountType><PASSUpdaterBatchStatus>Null</PASSUpdaterBatchStatus><PASSUpdaterOption>Null</PASSUpdaterOption></PaymentAccount><Address><BillingAddress1>456 My Street</BillingAddress1><BillingZipcode>K1C2N6</BillingZipcode></Address><ScheduledTask><RunFrequency>OneTimeFuture</RunFrequency><RunUntilCancelFlag>False</RunUntilCancelFlag><ScheduledTaskStatus>Active</ScheduledTaskStatus></ScheduledTask><DemandDepositAccount><DDAAccountType>Checking</DDAAccountType><CheckType>Personal</CheckType></DemandDepositAccount><TransactionSetup><TransactionSetupMethod>Null</TransactionSetupMethod><Device>Null</Device><Embedded>False</Embedded><CVVRequired>False</CVVRequired><AutoReturn>False</AutoReturn><DeviceInputCode>NotUsed</DeviceInputCode></TransactionSetup><Terminal><TerminalType>Unknown</TerminalType><CardPresentCode>UseDefault</CardPresentCode><CardholderPresentCode>UseDefault</CardholderPresentCode><CardInputCode>UseDefault</CardInputCode><CVVPresenceCode>UseDefault</CVVPresenceCode><TerminalCapabilityCode>UseDefault</TerminalCapabilityCode><TerminalEnvironmentCode>UseDefault</TerminalEnvironmentCode><MotoECICode>UseDefault</MotoECICode><CVVResponseType>Regular</CVVResponseType><ConsentCode>NotUsed</ConsentCode><TerminalEncryptionFormat>Default</TerminalEncryptionFormat></Terminal><AutoRental><AutoRentalVehicleClassCode>Unused</AutoRentalVehicleClassCode><AutoRentalDistanceUnit>Unused</AutoRentalDistanceUnit><AutoRentalAuditAdjustmentCode>NoAdjustments</AutoRentalAuditAdjustmentCode></AutoRental><Healthcare><HealthcareFlag>False</HealthcareFlag><HealthcareFirstAccountType>NotSpecified</HealthcareFirstAccountType><HealthcareFirstAmountType>LedgerBalance</HealthcareFirstAmountType><HealthcareFirstAmountSign>Positive</HealthcareFirstAmountSign><HealthcareSecondAccountType>NotSpecified</HealthcareSecondAccountType><HealthcareSecondAmountType>LedgerBalance</HealthcareSecondAmountType><HealthcareSecondAmountSign>Positive</HealthcareSecondAmountSign><HealthcareThirdAccountType>NotSpecified</HealthcareThirdAccountType><HealthcareThirdAmountType>LedgerBalance</HealthcareThirdAmountType><HealthcareThirdAmountSign>Positive</HealthcareThirdAmountSign><HealthcareFourthAccountType>NotSpecified</HealthcareFourthAccountType><HealthcareFourthAmountType>LedgerBalance</HealthcareFourthAmountType><HealthcareFourthAmountSign>Positive</HealthcareFourthAmountSign></Healthcare><Lodging><LodgingPrestigiousPropertyCode>NonParticipant</LodgingPrestigiousPropertyCode><LodgingSpecialProgramCode>Default</LodgingSpecialProgramCode><LodgingChargeType>Default</LodgingChargeType></Lodging><BIN /><EnhancedBIN /><Token><TokenProvider>Null</TokenProvider></Token></response></CreditCardAuthorizationResponse></soap:Body></soap:Envelope> + XML + end + + def successful_capture_response + <<-XML +<?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><soap:Body><CreditCardAuthorizationCompletionResponse xmlns="https://transaction.elementexpress.com"><response><ExpressResponseCode>0</ExpressResponseCode><ExpressResponseMessage>Success</ExpressResponseMessage><ExpressTransactionDate>20151201</ExpressTransactionDate><ExpressTransactionTime>120222</ExpressTransactionTime><ExpressTransactionTimezone>UTC-05:00</ExpressTransactionTimezone><HostResponseCode>000</HostResponseCode><HostResponseMessage>AP</HostResponseMessage><Credentials /><Batch><BatchCloseType>Regular</BatchCloseType><BatchQueryType>Totals</BatchQueryType><HostBatchID>1</HostBatchID><HostItemID>97</HostItemID><HostBatchAmount>2963.00</HostBatchAmount><BatchGroupingCode>FullBatch</BatchGroupingCode><BatchIndexCode>Current</BatchIndexCode></Batch><Card><EncryptedFormat>Default</EncryptedFormat><CardLogo>Visa</CardLogo></Card><Transaction><TransactionID>2005832535</TransactionID><ApprovalNumber>000002</ApprovalNumber><ReversalType>System</ReversalType><MarketCode>Default</MarketCode><AcquirerData>aVb001234567810425c0425d5e00</AcquirerData><BillPaymentFlag>False</BillPaymentFlag><DuplicateCheckDisableFlag>False</DuplicateCheckDisableFlag><DuplicateOverrideFlag>False</DuplicateOverrideFlag><RecurringFlag>False</RecurringFlag><ProcessorName>NULL_PROCESSOR_TEST</ProcessorName><TransactionStatus>Approved</TransactionStatus><TransactionStatusCode>1</TransactionStatusCode><PartialApprovedFlag>False</PartialApprovedFlag><EMVEncryptionFormat>Default</EMVEncryptionFormat><ReversalReason>Unknown</ReversalReason></Transaction><PaymentAccount><PaymentAccountType>CreditCard</PaymentAccountType><PASSUpdaterBatchStatus>Null</PASSUpdaterBatchStatus><PASSUpdaterOption>Null</PASSUpdaterOption></PaymentAccount><Address /><ScheduledTask><RunFrequency>OneTimeFuture</RunFrequency><RunUntilCancelFlag>False</RunUntilCancelFlag><ScheduledTaskStatus>Active</ScheduledTaskStatus></ScheduledTask><DemandDepositAccount><DDAAccountType>Checking</DDAAccountType><CheckType>Personal</CheckType></DemandDepositAccount><TransactionSetup><TransactionSetupMethod>Null</TransactionSetupMethod><Device>Null</Device><Embedded>False</Embedded><CVVRequired>False</CVVRequired><AutoReturn>False</AutoReturn><DeviceInputCode>NotUsed</DeviceInputCode></TransactionSetup><Terminal><TerminalType>Unknown</TerminalType><CardPresentCode>UseDefault</CardPresentCode><CardholderPresentCode>UseDefault</CardholderPresentCode><CardInputCode>UseDefault</CardInputCode><CVVPresenceCode>UseDefault</CVVPresenceCode><TerminalCapabilityCode>UseDefault</TerminalCapabilityCode><TerminalEnvironmentCode>UseDefault</TerminalEnvironmentCode><MotoECICode>UseDefault</MotoECICode><CVVResponseType>Regular</CVVResponseType><ConsentCode>NotUsed</ConsentCode><TerminalEncryptionFormat>Default</TerminalEncryptionFormat></Terminal><AutoRental><AutoRentalVehicleClassCode>Unused</AutoRentalVehicleClassCode><AutoRentalDistanceUnit>Unused</AutoRentalDistanceUnit><AutoRentalAuditAdjustmentCode>NoAdjustments</AutoRentalAuditAdjustmentCode></AutoRental><Healthcare><HealthcareFlag>False</HealthcareFlag><HealthcareFirstAccountType>NotSpecified</HealthcareFirstAccountType><HealthcareFirstAmountType>LedgerBalance</HealthcareFirstAmountType><HealthcareFirstAmountSign>Positive</HealthcareFirstAmountSign><HealthcareSecondAccountType>NotSpecified</HealthcareSecondAccountType><HealthcareSecondAmountType>LedgerBalance</HealthcareSecondAmountType><HealthcareSecondAmountSign>Positive</HealthcareSecondAmountSign><HealthcareThirdAccountType>NotSpecified</HealthcareThirdAccountType><HealthcareThirdAmountType>LedgerBalance</HealthcareThirdAmountType><HealthcareThirdAmountSign>Positive</HealthcareThirdAmountSign><HealthcareFourthAccountType>NotSpecified</HealthcareFourthAccountType><HealthcareFourthAmountType>LedgerBalance</HealthcareFourthAmountType><HealthcareFourthAmountSign>Positive</HealthcareFourthAmountSign></Healthcare><Lodging><LodgingPrestigiousPropertyCode>NonParticipant</LodgingPrestigiousPropertyCode><LodgingSpecialProgramCode>Default</LodgingSpecialProgramCode><LodgingChargeType>Default</LodgingChargeType></Lodging><BIN /><EnhancedBIN /><Token><TokenProvider>Null</TokenProvider></Token></response></CreditCardAuthorizationCompletionResponse></soap:Body></soap:Envelope> + XML + end + + def failed_capture_response + <<-XML +<?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><soap:Body><CreditCardAuthorizationCompletionResponse xmlns="https://transaction.elementexpress.com"><response><ExpressResponseCode>101</ExpressResponseCode><ExpressResponseMessage>TransactionID required</ExpressResponseMessage><ExpressTransactionTimezone>UTC-05:00</ExpressTransactionTimezone><Credentials /><Batch><BatchCloseType>Regular</BatchCloseType><BatchQueryType>Totals</BatchQueryType><BatchGroupingCode>FullBatch</BatchGroupingCode><BatchIndexCode>Current</BatchIndexCode></Batch><Card><EncryptedFormat>Default</EncryptedFormat></Card><Transaction><ReversalType>System</ReversalType><MarketCode>Default</MarketCode><BillPaymentFlag>False</BillPaymentFlag><DuplicateCheckDisableFlag>False</DuplicateCheckDisableFlag><DuplicateOverrideFlag>False</DuplicateOverrideFlag><RecurringFlag>False</RecurringFlag><PartialApprovedFlag>False</PartialApprovedFlag><EMVEncryptionFormat>Default</EMVEncryptionFormat><ReversalReason>Unknown</ReversalReason></Transaction><PaymentAccount><PaymentAccountType>CreditCard</PaymentAccountType><PASSUpdaterBatchStatus>Null</PASSUpdaterBatchStatus><PASSUpdaterOption>Null</PASSUpdaterOption></PaymentAccount><Address /><ScheduledTask><RunFrequency>OneTimeFuture</RunFrequency><RunUntilCancelFlag>False</RunUntilCancelFlag><ScheduledTaskStatus>Active</ScheduledTaskStatus></ScheduledTask><DemandDepositAccount><DDAAccountType>Checking</DDAAccountType><CheckType>Personal</CheckType></DemandDepositAccount><TransactionSetup><TransactionSetupMethod>Null</TransactionSetupMethod><Device>Null</Device><Embedded>False</Embedded><CVVRequired>False</CVVRequired><AutoReturn>False</AutoReturn><DeviceInputCode>NotUsed</DeviceInputCode></TransactionSetup><Terminal><TerminalType>Unknown</TerminalType><CardPresentCode>UseDefault</CardPresentCode><CardholderPresentCode>UseDefault</CardholderPresentCode><CardInputCode>UseDefault</CardInputCode><CVVPresenceCode>UseDefault</CVVPresenceCode><TerminalCapabilityCode>UseDefault</TerminalCapabilityCode><TerminalEnvironmentCode>UseDefault</TerminalEnvironmentCode><MotoECICode>UseDefault</MotoECICode><CVVResponseType>Regular</CVVResponseType><ConsentCode>NotUsed</ConsentCode><TerminalEncryptionFormat>Default</TerminalEncryptionFormat></Terminal><AutoRental><AutoRentalVehicleClassCode>Unused</AutoRentalVehicleClassCode><AutoRentalDistanceUnit>Unused</AutoRentalDistanceUnit><AutoRentalAuditAdjustmentCode>NoAdjustments</AutoRentalAuditAdjustmentCode></AutoRental><Healthcare><HealthcareFlag>False</HealthcareFlag><HealthcareFirstAccountType>NotSpecified</HealthcareFirstAccountType><HealthcareFirstAmountType>LedgerBalance</HealthcareFirstAmountType><HealthcareFirstAmountSign>Positive</HealthcareFirstAmountSign><HealthcareSecondAccountType>NotSpecified</HealthcareSecondAccountType><HealthcareSecondAmountType>LedgerBalance</HealthcareSecondAmountType><HealthcareSecondAmountSign>Positive</HealthcareSecondAmountSign><HealthcareThirdAccountType>NotSpecified</HealthcareThirdAccountType><HealthcareThirdAmountType>LedgerBalance</HealthcareThirdAmountType><HealthcareThirdAmountSign>Positive</HealthcareThirdAmountSign><HealthcareFourthAccountType>NotSpecified</HealthcareFourthAccountType><HealthcareFourthAmountType>LedgerBalance</HealthcareFourthAmountType><HealthcareFourthAmountSign>Positive</HealthcareFourthAmountSign></Healthcare><Lodging><LodgingPrestigiousPropertyCode>NonParticipant</LodgingPrestigiousPropertyCode><LodgingSpecialProgramCode>Default</LodgingSpecialProgramCode><LodgingChargeType>Default</LodgingChargeType></Lodging><BIN /><EnhancedBIN /><Token><TokenProvider>Null</TokenProvider></Token></response></CreditCardAuthorizationCompletionResponse></soap:Body></soap:Envelope> + XML + end + + def successful_refund_response + <<-XML +<?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><soap:Body><CreditCardReturnResponse xmlns="https://transaction.elementexpress.com"><response><ExpressResponseCode>0</ExpressResponseCode><ExpressResponseMessage>Approved</ExpressResponseMessage><ExpressTransactionDate>20151201</ExpressTransactionDate><ExpressTransactionTime>120437</ExpressTransactionTime><ExpressTransactionTimezone>UTC-05:00</ExpressTransactionTimezone><HostResponseCode>000</HostResponseCode><HostResponseMessage>AP</HostResponseMessage><Credentials /><Batch><BatchCloseType>Regular</BatchCloseType><BatchQueryType>Totals</BatchQueryType><HostBatchID>1</HostBatchID><HostItemID>99</HostItemID><HostBatchAmount>2963.00</HostBatchAmount><BatchGroupingCode>FullBatch</BatchGroupingCode><BatchIndexCode>Current</BatchIndexCode></Batch><Card><EncryptedFormat>Default</EncryptedFormat><CardLogo>Visa</CardLogo></Card><Transaction><TransactionID>2005832540</TransactionID><ApprovalNumber>000004</ApprovalNumber><ReversalType>System</ReversalType><MarketCode>Default</MarketCode><BillPaymentFlag>False</BillPaymentFlag><DuplicateCheckDisableFlag>False</DuplicateCheckDisableFlag><DuplicateOverrideFlag>False</DuplicateOverrideFlag><RecurringFlag>False</RecurringFlag><ProcessorName>NULL_PROCESSOR_TEST</ProcessorName><TransactionStatus>Approved</TransactionStatus><TransactionStatusCode>1</TransactionStatusCode><PartialApprovedFlag>False</PartialApprovedFlag><EMVEncryptionFormat>Default</EMVEncryptionFormat><ReversalReason>Unknown</ReversalReason></Transaction><PaymentAccount><PaymentAccountType>CreditCard</PaymentAccountType><PASSUpdaterBatchStatus>Null</PASSUpdaterBatchStatus><PASSUpdaterOption>Null</PASSUpdaterOption></PaymentAccount><Address><BillingAddress1>456 My Street</BillingAddress1><BillingZipcode>K1C2N6</BillingZipcode></Address><ScheduledTask><RunFrequency>OneTimeFuture</RunFrequency><RunUntilCancelFlag>False</RunUntilCancelFlag><ScheduledTaskStatus>Active</ScheduledTaskStatus></ScheduledTask><DemandDepositAccount><DDAAccountType>Checking</DDAAccountType><CheckType>Personal</CheckType></DemandDepositAccount><TransactionSetup><TransactionSetupMethod>Null</TransactionSetupMethod><Device>Null</Device><Embedded>False</Embedded><CVVRequired>False</CVVRequired><AutoReturn>False</AutoReturn><DeviceInputCode>NotUsed</DeviceInputCode></TransactionSetup><Terminal><TerminalType>Unknown</TerminalType><CardPresentCode>UseDefault</CardPresentCode><CardholderPresentCode>UseDefault</CardholderPresentCode><CardInputCode>UseDefault</CardInputCode><CVVPresenceCode>UseDefault</CVVPresenceCode><TerminalCapabilityCode>UseDefault</TerminalCapabilityCode><TerminalEnvironmentCode>UseDefault</TerminalEnvironmentCode><MotoECICode>UseDefault</MotoECICode><CVVResponseType>Regular</CVVResponseType><ConsentCode>NotUsed</ConsentCode><TerminalEncryptionFormat>Default</TerminalEncryptionFormat></Terminal><AutoRental><AutoRentalVehicleClassCode>Unused</AutoRentalVehicleClassCode><AutoRentalDistanceUnit>Unused</AutoRentalDistanceUnit><AutoRentalAuditAdjustmentCode>NoAdjustments</AutoRentalAuditAdjustmentCode></AutoRental><Healthcare><HealthcareFlag>False</HealthcareFlag><HealthcareFirstAccountType>NotSpecified</HealthcareFirstAccountType><HealthcareFirstAmountType>LedgerBalance</HealthcareFirstAmountType><HealthcareFirstAmountSign>Positive</HealthcareFirstAmountSign><HealthcareSecondAccountType>NotSpecified</HealthcareSecondAccountType><HealthcareSecondAmountType>LedgerBalance</HealthcareSecondAmountType><HealthcareSecondAmountSign>Positive</HealthcareSecondAmountSign><HealthcareThirdAccountType>NotSpecified</HealthcareThirdAccountType><HealthcareThirdAmountType>LedgerBalance</HealthcareThirdAmountType><HealthcareThirdAmountSign>Positive</HealthcareThirdAmountSign><HealthcareFourthAccountType>NotSpecified</HealthcareFourthAccountType><HealthcareFourthAmountType>LedgerBalance</HealthcareFourthAmountType><HealthcareFourthAmountSign>Positive</HealthcareFourthAmountSign></Healthcare><Lodging><LodgingPrestigiousPropertyCode>NonParticipant</LodgingPrestigiousPropertyCode><LodgingSpecialProgramCode>Default</LodgingSpecialProgramCode><LodgingChargeType>Default</LodgingChargeType></Lodging><BIN /><EnhancedBIN /><Token><TokenProvider>Null</TokenProvider></Token></response></CreditCardReturnResponse></soap:Body></soap:Envelope> + XML + end + + def failed_refund_response + <<-XML +<?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><soap:Body><CreditCardReturnResponse xmlns="https://transaction.elementexpress.com"><response><ExpressResponseCode>101</ExpressResponseCode><ExpressResponseMessage>TransactionID required</ExpressResponseMessage><ExpressTransactionTimezone>UTC-05:00</ExpressTransactionTimezone><Credentials /><Batch><BatchCloseType>Regular</BatchCloseType><BatchQueryType>Totals</BatchQueryType><BatchGroupingCode>FullBatch</BatchGroupingCode><BatchIndexCode>Current</BatchIndexCode></Batch><Card><EncryptedFormat>Default</EncryptedFormat></Card><Transaction><ReversalType>System</ReversalType><MarketCode>Default</MarketCode><BillPaymentFlag>False</BillPaymentFlag><DuplicateCheckDisableFlag>False</DuplicateCheckDisableFlag><DuplicateOverrideFlag>False</DuplicateOverrideFlag><RecurringFlag>False</RecurringFlag><PartialApprovedFlag>False</PartialApprovedFlag><EMVEncryptionFormat>Default</EMVEncryptionFormat><ReversalReason>Unknown</ReversalReason></Transaction><PaymentAccount><PaymentAccountType>CreditCard</PaymentAccountType><PASSUpdaterBatchStatus>Null</PASSUpdaterBatchStatus><PASSUpdaterOption>Null</PASSUpdaterOption></PaymentAccount><Address /><ScheduledTask><RunFrequency>OneTimeFuture</RunFrequency><RunUntilCancelFlag>False</RunUntilCancelFlag><ScheduledTaskStatus>Active</ScheduledTaskStatus></ScheduledTask><DemandDepositAccount><DDAAccountType>Checking</DDAAccountType><CheckType>Personal</CheckType></DemandDepositAccount><TransactionSetup><TransactionSetupMethod>Null</TransactionSetupMethod><Device>Null</Device><Embedded>False</Embedded><CVVRequired>False</CVVRequired><AutoReturn>False</AutoReturn><DeviceInputCode>NotUsed</DeviceInputCode></TransactionSetup><Terminal><TerminalType>Unknown</TerminalType><CardPresentCode>UseDefault</CardPresentCode><CardholderPresentCode>UseDefault</CardholderPresentCode><CardInputCode>UseDefault</CardInputCode><CVVPresenceCode>UseDefault</CVVPresenceCode><TerminalCapabilityCode>UseDefault</TerminalCapabilityCode><TerminalEnvironmentCode>UseDefault</TerminalEnvironmentCode><MotoECICode>UseDefault</MotoECICode><CVVResponseType>Regular</CVVResponseType><ConsentCode>NotUsed</ConsentCode><TerminalEncryptionFormat>Default</TerminalEncryptionFormat></Terminal><AutoRental><AutoRentalVehicleClassCode>Unused</AutoRentalVehicleClassCode><AutoRentalDistanceUnit>Unused</AutoRentalDistanceUnit><AutoRentalAuditAdjustmentCode>NoAdjustments</AutoRentalAuditAdjustmentCode></AutoRental><Healthcare><HealthcareFlag>False</HealthcareFlag><HealthcareFirstAccountType>NotSpecified</HealthcareFirstAccountType><HealthcareFirstAmountType>LedgerBalance</HealthcareFirstAmountType><HealthcareFirstAmountSign>Positive</HealthcareFirstAmountSign><HealthcareSecondAccountType>NotSpecified</HealthcareSecondAccountType><HealthcareSecondAmountType>LedgerBalance</HealthcareSecondAmountType><HealthcareSecondAmountSign>Positive</HealthcareSecondAmountSign><HealthcareThirdAccountType>NotSpecified</HealthcareThirdAccountType><HealthcareThirdAmountType>LedgerBalance</HealthcareThirdAmountType><HealthcareThirdAmountSign>Positive</HealthcareThirdAmountSign><HealthcareFourthAccountType>NotSpecified</HealthcareFourthAccountType><HealthcareFourthAmountType>LedgerBalance</HealthcareFourthAmountType><HealthcareFourthAmountSign>Positive</HealthcareFourthAmountSign></Healthcare><Lodging><LodgingPrestigiousPropertyCode>NonParticipant</LodgingPrestigiousPropertyCode><LodgingSpecialProgramCode>Default</LodgingSpecialProgramCode><LodgingChargeType>Default</LodgingChargeType></Lodging><BIN /><EnhancedBIN /><Token><TokenProvider>Null</TokenProvider></Token></response></CreditCardReturnResponse></soap:Body></soap:Envelope> + XML + end + + def successful_void_response + <<-XML +<?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><soap:Body><CreditCardReversalResponse xmlns="https://transaction.elementexpress.com"><response><ExpressResponseCode>0</ExpressResponseCode><ExpressResponseMessage>Success</ExpressResponseMessage><ExpressTransactionDate>20151201</ExpressTransactionDate><ExpressTransactionTime>120516</ExpressTransactionTime><ExpressTransactionTimezone>UTC-05:00</ExpressTransactionTimezone><HostResponseCode>006</HostResponseCode><HostResponseMessage>REVERSED</HostResponseMessage><Credentials /><Batch><BatchCloseType>Regular</BatchCloseType><BatchQueryType>Totals</BatchQueryType><BatchGroupingCode>FullBatch</BatchGroupingCode><BatchIndexCode>Current</BatchIndexCode></Batch><Card><EncryptedFormat>Default</EncryptedFormat><CardLogo>Visa</CardLogo></Card><Transaction><TransactionID>2005832551</TransactionID><ApprovalNumber>000005</ApprovalNumber><ReversalType>System</ReversalType><MarketCode>Default</MarketCode><AcquirerData>aVb001234567810425c0425d5e00</AcquirerData><BillPaymentFlag>False</BillPaymentFlag><DuplicateCheckDisableFlag>False</DuplicateCheckDisableFlag><DuplicateOverrideFlag>False</DuplicateOverrideFlag><RecurringFlag>False</RecurringFlag><ProcessorName>NULL_PROCESSOR_TEST</ProcessorName><TransactionStatus>Success</TransactionStatus><TransactionStatusCode>8</TransactionStatusCode><PartialApprovedFlag>False</PartialApprovedFlag><EMVEncryptionFormat>Default</EMVEncryptionFormat><ReversalReason>Unknown</ReversalReason></Transaction><PaymentAccount><PaymentAccountType>CreditCard</PaymentAccountType><PASSUpdaterBatchStatus>Null</PASSUpdaterBatchStatus><PASSUpdaterOption>Null</PASSUpdaterOption></PaymentAccount><Address /><ScheduledTask><RunFrequency>OneTimeFuture</RunFrequency><RunUntilCancelFlag>False</RunUntilCancelFlag><ScheduledTaskStatus>Active</ScheduledTaskStatus></ScheduledTask><DemandDepositAccount><DDAAccountType>Checking</DDAAccountType><CheckType>Personal</CheckType></DemandDepositAccount><TransactionSetup><TransactionSetupMethod>Null</TransactionSetupMethod><Device>Null</Device><Embedded>False</Embedded><CVVRequired>False</CVVRequired><AutoReturn>False</AutoReturn><DeviceInputCode>NotUsed</DeviceInputCode></TransactionSetup><Terminal><TerminalType>Unknown</TerminalType><CardPresentCode>UseDefault</CardPresentCode><CardholderPresentCode>UseDefault</CardholderPresentCode><CardInputCode>UseDefault</CardInputCode><CVVPresenceCode>UseDefault</CVVPresenceCode><TerminalCapabilityCode>UseDefault</TerminalCapabilityCode><TerminalEnvironmentCode>UseDefault</TerminalEnvironmentCode><MotoECICode>UseDefault</MotoECICode><CVVResponseType>Regular</CVVResponseType><ConsentCode>NotUsed</ConsentCode><TerminalEncryptionFormat>Default</TerminalEncryptionFormat></Terminal><AutoRental><AutoRentalVehicleClassCode>Unused</AutoRentalVehicleClassCode><AutoRentalDistanceUnit>Unused</AutoRentalDistanceUnit><AutoRentalAuditAdjustmentCode>NoAdjustments</AutoRentalAuditAdjustmentCode></AutoRental><Healthcare><HealthcareFlag>False</HealthcareFlag><HealthcareFirstAccountType>NotSpecified</HealthcareFirstAccountType><HealthcareFirstAmountType>LedgerBalance</HealthcareFirstAmountType><HealthcareFirstAmountSign>Positive</HealthcareFirstAmountSign><HealthcareSecondAccountType>NotSpecified</HealthcareSecondAccountType><HealthcareSecondAmountType>LedgerBalance</HealthcareSecondAmountType><HealthcareSecondAmountSign>Positive</HealthcareSecondAmountSign><HealthcareThirdAccountType>NotSpecified</HealthcareThirdAccountType><HealthcareThirdAmountType>LedgerBalance</HealthcareThirdAmountType><HealthcareThirdAmountSign>Positive</HealthcareThirdAmountSign><HealthcareFourthAccountType>NotSpecified</HealthcareFourthAccountType><HealthcareFourthAmountType>LedgerBalance</HealthcareFourthAmountType><HealthcareFourthAmountSign>Positive</HealthcareFourthAmountSign></Healthcare><Lodging><LodgingPrestigiousPropertyCode>NonParticipant</LodgingPrestigiousPropertyCode><LodgingSpecialProgramCode>Default</LodgingSpecialProgramCode><LodgingChargeType>Default</LodgingChargeType></Lodging><BIN /><EnhancedBIN /><Token><TokenProvider>Null</TokenProvider></Token></response></CreditCardReversalResponse></soap:Body></soap:Envelope> + XML + end + + def failed_void_response + <<-XML +<?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><soap:Body><CreditCardReversalResponse xmlns="https://transaction.elementexpress.com"><response><ExpressResponseCode>101</ExpressResponseCode><ExpressResponseMessage>TransactionAmount required</ExpressResponseMessage><ExpressTransactionTimezone>UTC-05:00</ExpressTransactionTimezone><Credentials /><Batch><BatchCloseType>Regular</BatchCloseType><BatchQueryType>Totals</BatchQueryType><BatchGroupingCode>FullBatch</BatchGroupingCode><BatchIndexCode>Current</BatchIndexCode></Batch><Card><EncryptedFormat>Default</EncryptedFormat></Card><Transaction><ReversalType>System</ReversalType><MarketCode>Default</MarketCode><BillPaymentFlag>False</BillPaymentFlag><DuplicateCheckDisableFlag>False</DuplicateCheckDisableFlag><DuplicateOverrideFlag>False</DuplicateOverrideFlag><RecurringFlag>False</RecurringFlag><PartialApprovedFlag>False</PartialApprovedFlag><EMVEncryptionFormat>Default</EMVEncryptionFormat><ReversalReason>Unknown</ReversalReason></Transaction><PaymentAccount><PaymentAccountType>CreditCard</PaymentAccountType><PASSUpdaterBatchStatus>Null</PASSUpdaterBatchStatus><PASSUpdaterOption>Null</PASSUpdaterOption></PaymentAccount><Address /><ScheduledTask><RunFrequency>OneTimeFuture</RunFrequency><RunUntilCancelFlag>False</RunUntilCancelFlag><ScheduledTaskStatus>Active</ScheduledTaskStatus></ScheduledTask><DemandDepositAccount><DDAAccountType>Checking</DDAAccountType><CheckType>Personal</CheckType></DemandDepositAccount><TransactionSetup><TransactionSetupMethod>Null</TransactionSetupMethod><Device>Null</Device><Embedded>False</Embedded><CVVRequired>False</CVVRequired><AutoReturn>False</AutoReturn><DeviceInputCode>NotUsed</DeviceInputCode></TransactionSetup><Terminal><TerminalType>Unknown</TerminalType><CardPresentCode>UseDefault</CardPresentCode><CardholderPresentCode>UseDefault</CardholderPresentCode><CardInputCode>UseDefault</CardInputCode><CVVPresenceCode>UseDefault</CVVPresenceCode><TerminalCapabilityCode>UseDefault</TerminalCapabilityCode><TerminalEnvironmentCode>UseDefault</TerminalEnvironmentCode><MotoECICode>UseDefault</MotoECICode><CVVResponseType>Regular</CVVResponseType><ConsentCode>NotUsed</ConsentCode><TerminalEncryptionFormat>Default</TerminalEncryptionFormat></Terminal><AutoRental><AutoRentalVehicleClassCode>Unused</AutoRentalVehicleClassCode><AutoRentalDistanceUnit>Unused</AutoRentalDistanceUnit><AutoRentalAuditAdjustmentCode>NoAdjustments</AutoRentalAuditAdjustmentCode></AutoRental><Healthcare><HealthcareFlag>False</HealthcareFlag><HealthcareFirstAccountType>NotSpecified</HealthcareFirstAccountType><HealthcareFirstAmountType>LedgerBalance</HealthcareFirstAmountType><HealthcareFirstAmountSign>Positive</HealthcareFirstAmountSign><HealthcareSecondAccountType>NotSpecified</HealthcareSecondAccountType><HealthcareSecondAmountType>LedgerBalance</HealthcareSecondAmountType><HealthcareSecondAmountSign>Positive</HealthcareSecondAmountSign><HealthcareThirdAccountType>NotSpecified</HealthcareThirdAccountType><HealthcareThirdAmountType>LedgerBalance</HealthcareThirdAmountType><HealthcareThirdAmountSign>Positive</HealthcareThirdAmountSign><HealthcareFourthAccountType>NotSpecified</HealthcareFourthAccountType><HealthcareFourthAmountType>LedgerBalance</HealthcareFourthAmountType><HealthcareFourthAmountSign>Positive</HealthcareFourthAmountSign></Healthcare><Lodging><LodgingPrestigiousPropertyCode>NonParticipant</LodgingPrestigiousPropertyCode><LodgingSpecialProgramCode>Default</LodgingSpecialProgramCode><LodgingChargeType>Default</LodgingChargeType></Lodging><BIN /><EnhancedBIN /><Token><TokenProvider>Null</TokenProvider></Token></response></CreditCardReversalResponse></soap:Body></soap:Envelope> + XML + end +end diff --git a/test/unit/gateways/epay_test.rb b/test/unit/gateways/epay_test.rb index ceb3a5f3431..59becd05499 100644 --- a/test/unit/gateways/epay_test.rb +++ b/test/unit/gateways/epay_test.rb @@ -2,7 +2,7 @@ class EpayTest < Test::Unit::TestCase def setup - Base.gateway_mode = :test + Base.mode = :test @gateway = EpayGateway.new( :login => '10100111001', @@ -26,11 +26,20 @@ def test_failed_purchase assert response = @gateway.authorize(100, @credit_card) assert_failure response assert_equal 'The payment was declined. Try again in a moment or try with another credit card.', - response.message + response.message + end + + def test_invalid_characters_in_response + @gateway.expects(:raw_ssl_request).returns(invalid_authorize_response_with_invalid_characters) + + assert response = @gateway.authorize(100, @credit_card) + assert_failure response + assert_equal 'The payment was declined of unknown reasons. For more information contact the bank. E.g. try with another credit card.<br />Denied - Call your bank for information', + response.message end def test_failed_response_on_purchase - @gateway.expects(:raw_ssl_request).returns(Net::HTTPBadRequest.new(1.0, 400,'Bad Request')) + @gateway.expects(:raw_ssl_request).returns(Net::HTTPBadRequest.new(1.0, 400, 'Bad Request')) assert response = @gateway.authorize(100, @credit_card) assert_equal 400, response.params['response_code'] @@ -88,7 +97,7 @@ def test_failed_refund def test_deprecated_credit @gateway.expects(:soap_post).returns(REXML::Document.new(valid_refund_response)) - assert_deprecation_warning(Gateway::CREDIT_DEPRECATION_MESSAGE, @gateway) do + assert_deprecation_warning(Gateway::CREDIT_DEPRECATION_MESSAGE) do assert_success @gateway.credit(100, '123') end end @@ -96,15 +105,19 @@ def test_deprecated_credit def test_authorize_sends_order_number @gateway.expects(:raw_ssl_request).with(anything, anything, regexp_matches(/orderid=1234/), anything).returns(valid_authorize_response) - assert response = @gateway.authorize(100, '123', :order_id => '#1234') + @gateway.authorize(100, '123', :order_id => '#1234') end - + def test_purchase_sends_order_number @gateway.expects(:raw_ssl_request).with(anything, anything, regexp_matches(/orderid=1234/), anything).returns(valid_authorize_response) - assert response = @gateway.purchase(100, '123', :order_id => '#1234') + @gateway.purchase(100, '123', :order_id => '#1234') + end + + def test_transcript_scrubbing + assert_equal scrubbed_transcript, @gateway.scrub(transcript) end - + private def valid_authorize_response @@ -115,6 +128,10 @@ def invalid_authorize_response { 'Location' => 'https://ssl.ditonlinebetalingssystem.dk/auth/default.aspx?decline=1&error=102&errortext=The payment was declined. Try again in a moment or try with another credit card.' } end + def invalid_authorize_response_with_invalid_characters + { 'Location' => 'https://ssl.ditonlinebetalingssystem.dk/auth/default.aspx?decline=1&error=209&errortext=The payment was declined of unknown reasons. For more information contact the bank. E.g. try with another credit card.<br />Denied - Call your bank for information' } + end + def valid_capture_response '<?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><soap:Body><captureResponse xmlns="https://ssl.ditonlinebetalingssystem.dk/remote/payment"><captureResult>true</captureResult><pbsResponse>0</pbsResponse><epayresponse>-1</epayresponse></captureResponse></soap:Body></soap:Envelope>' end @@ -138,4 +155,22 @@ def valid_refund_response def invalid_refund_response '<?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><soap:Body><creditResponse xmlns="https://ssl.ditonlinebetalingssystem.dk/remote/payment"><creditResult>false</creditResult><pbsresponse>-1</pbsresponse><epayresponse>-1008</epayresponse></creditResponse></soap:Body></soap:Envelope>' end + + def transcript + <<-TRANSCRIPT + amount=100&currency=208&cardno=3333333333333000&cvc=123&expmonth=9&expyear=2012&orderid=0001&instantcapture=1&language=2&cms=activemerchant&accepturl=https%3A%2F%2Fssl.ditonlinebetalingssystem.dk%2Fauth%2Fdefault.aspx%3Faccept%3D1&declineurl=https%3A%2F%2Fssl.ditonlinebetalingssystem.dk%2Fauth%2Fdefault.aspx%3Fdecline%3D1&merchantnumber=8886945 + <html><head><title>Object moved</title></head><body> + <h2>Object moved to <a href="https://ssl.ditonlinebetalingssystem.dk/auth/default.aspx?accept=1&amp;tid=6620099&amp;orderid=0001&amp;amount=100&amp;cur=208&amp;date=20110905&amp;time=1745&amp;cardnopostfix=3000&amp;tcardno=333333XXXXXX3000&amp;cardid=3&amp;transfee=0">here</a>.</h2> + </body></html> + TRANSCRIPT + end + + def scrubbed_transcript + <<-SCRUBBED_TRANSCRIPT + amount=100&currency=208&cardno=[FILTERED]&cvc=[FILTERED]&expmonth=9&expyear=2012&orderid=0001&instantcapture=1&language=2&cms=activemerchant&accepturl=https%3A%2F%2Fssl.ditonlinebetalingssystem.dk%2Fauth%2Fdefault.aspx%3Faccept%3D1&declineurl=https%3A%2F%2Fssl.ditonlinebetalingssystem.dk%2Fauth%2Fdefault.aspx%3Fdecline%3D1&merchantnumber=8886945 + <html><head><title>Object moved</title></head><body> + <h2>Object moved to <a href="https://ssl.ditonlinebetalingssystem.dk/auth/default.aspx?accept=1&amp;tid=6620099&amp;orderid=0001&amp;amount=100&amp;cur=208&amp;date=20110905&amp;time=1745&amp;cardnopostfix=3000&amp;tcardno=333333XXXXXX3000&amp;cardid=3&amp;transfee=0">here</a>.</h2> + </body></html> + SCRUBBED_TRANSCRIPT + end end diff --git a/test/unit/gateways/evo_ca_test.rb b/test/unit/gateways/evo_ca_test.rb index e699e1462a1..f2fe062e658 100644 --- a/test/unit/gateways/evo_ca_test.rb +++ b/test/unit/gateways/evo_ca_test.rb @@ -99,7 +99,7 @@ def test_successful_refund def test_add_address result = {} - @gateway.send(:add_address, result, :address => {:address1 => '123 Main Street', :country => 'CA', :state => 'BC'} ) + @gateway.send(:add_address, result, :address => {:address1 => '123 Main Street', :country => 'CA', :state => 'BC'}) assert_equal %w{address1 address2 city company country firstname lastname phone state zip}, result.stringify_keys.keys.sort assert_equal 'BC', result[:state] assert_equal '123 Main Street', result[:address1] @@ -109,7 +109,7 @@ def test_add_address def test_add_shipping_address result = {} - @gateway.send(:add_address, result, :shipping_address => {:address1 => '123 Main Street', :country => 'CA', :state => 'BC'} ) + @gateway.send(:add_address, result, :shipping_address => {:address1 => '123 Main Street', :country => 'CA', :state => 'BC'}) assert_equal %w{shipping_address1 shipping_address2 shipping_city shipping_company shipping_country shipping_firstname shipping_lastname shipping_state shipping_zip}, result.stringify_keys.keys.sort assert_equal 'BC', result[:shipping_state] assert_equal '123 Main Street', result[:shipping_address1] diff --git a/test/unit/gateways/eway_managed_test.rb b/test/unit/gateways/eway_managed_test.rb index 3b9ec9b099a..02666e0bc71 100644 --- a/test/unit/gateways/eway_managed_test.rb +++ b/test/unit/gateways/eway_managed_test.rb @@ -2,7 +2,7 @@ class EwayManagedTest < Test::Unit::TestCase def setup - Base.gateway_mode = :test + Base.mode = :test @gateway = EwayManagedGateway.new(:username => 'username', :login => 'login', :password => 'password') @@ -14,22 +14,23 @@ def setup @amount = 100 - @options = { :billing_address => { - :address1 => '1234 My Street', - :address2 => 'Apt 1', - :company => 'Widgets Inc', - :city => 'Ottawa', - :state => 'ON', - :zip => 'K1C2N6', - :country => 'au', - :title => 'Mr.', - :phone => '(555)555-5555' - }, - :email => 'someguy1232@fakeemail.net', - :order_id => '1000', - :customer => 'mycustomerref', - :description => 'My Description', - :invoice => 'invoice-4567' + @options = { + :billing_address => { + :address1 => '1234 My Street', + :address2 => 'Apt 1', + :company => 'Widgets Inc', + :city => 'Ottawa', + :state => 'ON', + :zip => 'K1C2N6', + :country => 'au', + :title => 'Mr.', + :phone => '(555)555-5555' + }, + :email => 'someguy1232@fakeemail.net', + :order_id => '1000', + :customer => 'mycustomerref', + :description => 'My Description', + :invoice => 'invoice-4567' } end @@ -76,10 +77,10 @@ def test_successful_purchase assert response = @gateway.purchase(@amount, @valid_customer_id, @options) assert_instance_of EwayManagedGateway::EwayResponse, response - assert_equal "00,Transaction Approved(Test Gateway)", response.message + assert_equal '00,Transaction Approved(Test Gateway)', response.message assert_success response - assert_equal "123456", response.authorization - assert_equal "123456", response.params['transaction_number'] + assert_equal '123456', response.authorization + assert_equal '123456', response.params['transaction_number'] assert response.test? end @@ -125,7 +126,6 @@ def test_purchase_invoice_reference_comes_from_order_id_or_invoice request_hash['Envelope']['Body']['ProcessPayment']['invoiceReference'] == 'order_id' }.returns(successful_purchase_response) @gateway.purchase(@amount, @valid_customer_id, options) - end def test_invalid_customer_id @@ -141,9 +141,9 @@ def test_successful_store assert response = @gateway.store(@credit_card, @options) assert_instance_of EwayManagedGateway::EwayResponse, response - assert_equal "OK", response.message + assert_equal 'OK', response.message assert_success response - assert_equal "1234567", response.token + assert_equal '1234567', response.token assert response.test? end @@ -230,7 +230,7 @@ def test_sucessful_update assert response = @gateway.store(@credit_card, @options) assert_instance_of EwayManagedGateway::EwayResponse, response - assert_equal "OK", response.message + assert_equal 'OK', response.message assert_success response assert response.test? end @@ -240,7 +240,7 @@ def test_successful_retrieve assert response = @gateway.retrieve(@valid_customer_id) assert_instance_of EwayManagedGateway::EwayResponse, response - assert_equal "OK", response.message + assert_equal 'OK', response.message assert_success response assert response.test? end @@ -372,9 +372,9 @@ def expected_store_request XML end - # Documented here: https://www.eway.com.au/gateway/ManagedPaymentService/managedCreditCardPayment.asmx?op=CreateCustomer - def expected_purchase_request - <<-XML + # Documented here: https://www.eway.com.au/gateway/ManagedPaymentService/managedCreditCardPayment.asmx?op=CreateCustomer + def expected_purchase_request + <<-XML <?xml version="1.0" encoding="utf-8"?> <soap12:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap12="http://www.w3.org/2003/05/soap-envelope"> <soap12:Header> @@ -393,10 +393,10 @@ def expected_purchase_request </ProcessPayment> </soap12:Body> </soap12:Envelope> - XML - end + XML + end - # Documented here: https://www.eway.com.au/gateway/ManagedPaymentService/managedCreditCardPayment.asmx?op=QueryCustomer + # Documented here: https://www.eway.com.au/gateway/ManagedPaymentService/managedCreditCardPayment.asmx?op=QueryCustomer def expected_retrieve_request <<-XML <?xml version="1.0" encoding="utf-8"?> @@ -416,5 +416,4 @@ def expected_retrieve_request </soap12:Envelope> XML end - end diff --git a/test/unit/gateways/eway_rapid_test.rb b/test/unit/gateways/eway_rapid_test.rb index dbf73f359a1..790b839228e 100644 --- a/test/unit/gateways/eway_rapid_test.rb +++ b/test/unit/gateways/eway_rapid_test.rb @@ -1,395 +1,1278 @@ -require "test_helper" +require 'test_helper' class EwayRapidTest < Test::Unit::TestCase include CommStub def setup + ActiveMerchant::Billing::EwayRapidGateway.partner_id = nil @gateway = EwayRapidGateway.new( - :login => "login", - :password => "password" + :login => 'login', + :password => 'password' ) @credit_card = credit_card @amount = 100 + + @address = { + name: 'John Smith', + title: 'Test Title', + company: 'Test Company, Inc.', + address1: '14701 Test Road', + address2: 'Test Unit 100', + city: 'TestCity', + state: 'NC', + zip: '27517', + country: 'USA', + phone: '555-555-1000', + fax: '555-555-2000' + } + + @shipping_address = { + name: 'John Smith', + title: 'Test Title', + company: 'Test Company, Inc.', + address1: '14701 Test Road', + address2: 'Test Unit 100', + city: 'TestCity', + state: 'NC', + zip: '27517', + country: 'USA', + phone_number: '555-555-1000', + fax: '555-555-2000' + } + + @email = 'john.smith@example.com' end - def test_purchase_calls_sub_methods - request = sequence("request") - @gateway.expects(:setup_purchase).with(@amount, {:order_id => 1, :redirect_url => "http://example.com/"}).returns(Response.new(true, "Success", {"formactionurl" => "url"}, :authorization => "auth1")).in_sequence(request) - @gateway.expects(:run_purchase).with("auth1", @credit_card, "url").returns(Response.new(true, "Success", {}, :authorization => "auth2")).in_sequence(request) - @gateway.expects(:status).with("auth2").returns(Response.new(true, "Success", {})).in_sequence(request) + def test_successful_purchase + response = stub_comms do + @gateway.purchase(@amount, @credit_card) + end.respond_with(successful_purchase_response) - response = @gateway.purchase(@amount, @credit_card, :order_id => 1) assert_success response + assert_equal 'Transaction Approved Successful', response.message + assert_equal 10440187, response.authorization + assert response.test? + end + + def test_purchase_passes_customer_data_from_payment_method_when_no_address_is_provided + stub_comms do + @gateway.purchase(@amount, @credit_card, { email: @email }) + end.check_request do |endpoint, data, headers| + assert_customer_data_passed( + data, + @credit_card.first_name, + @credit_card.last_name, + @email + ) + end.respond_with(successful_purchase_response) + end + + def test_purchase_passes_customer_data_from_billing_address + stub_comms do + @gateway.purchase(@amount, @credit_card, { billing_address: @address, email: @email }) + end.check_request do |endpoint, data, headers| + assert_customer_data_passed( + data, + @address[:name].split[0], + @address[:name].split[1], + @email, + @address + ) + end.respond_with(successful_purchase_response) end - def test_successful_setup_purchase + def test_purchase_passes_customer_data_from_address + stub_comms do + @gateway.purchase(@amount, @credit_card, { address: @address, email: @email }) + end.check_request do |endpoint, data, headers| + assert_customer_data_passed( + data, + @address[:name].split[0], + @address[:name].split[1], + @email, + @address + ) + end.respond_with(successful_purchase_response) + end + + def test_purchase_passes_shipping_data + stub_comms do + @gateway.purchase(@amount, @credit_card, { shipping_address: @shipping_address, email: @email }) + end.check_request do |endpoint, data, headers| + assert_shipping_data_passed(data, @shipping_address, @email) + end.respond_with(successful_purchase_response) + end + + def test_localized_currency + stub_comms do + @gateway.purchase(100, @credit_card, :currency => 'CAD') + end.check_request do |endpoint, data, headers| + assert_match '"TotalAmount":"100"', data + end.respond_with(successful_purchase_response) + + stub_comms do + @gateway.purchase(100, @credit_card, :currency => 'JPY') + end.check_request do |endpoint, data, headers| + assert_match '"TotalAmount":"1"', data + end.respond_with(successful_purchase_response) + end + + def test_failed_purchase response = stub_comms do - @gateway.setup_purchase(@amount, :redirect_url => "http://bogus") - end.respond_with(successful_setup_purchase_response) + @gateway.purchase(-100, @credit_card) + end.respond_with(failed_purchase_response) - assert_success response - assert_equal "Succeeded", response.message - assert_equal( - "60CF3xWrFUQeDCEsJcA8zNHaspAT3CKpe-0DiqWjTYA3RZw1xhw2LU-BFCNYbr7eJt8KFaxCxmzYh9WDAYX8yIuYexTq0tC8i2kOt0dm0EV-mjxYEQ2YeHP2dazkSc7j58OiT", - response.authorization - ) - assert_equal "https://secure-au.sandbox.ewaypayments.com/Process", response.form_url + assert_failure response + assert_equal 'Invalid Payment TotalAmount', response.message + assert_nil response.authorization assert response.test? end - def test_failed_setup_purchase + def test_failed_purchase_without_message response = stub_comms do - @gateway.setup_purchase(@amount, :redirect_url => "http://bogus") - end.respond_with(failed_setup_purchase_response) + @gateway.purchase(-100, @credit_card) + end.respond_with(failed_purchase_response_without_message) assert_failure response - assert_equal "V6047", response.message + assert_equal 'Do Not Honour', response.message assert_nil response.authorization assert response.test? end - def test_setup_purchase_with_all_options + def test_failed_purchase_with_multiple_messages response = stub_comms do - @gateway.setup_purchase(200, - :redirect_url => "http://awesomesauce.com", - :ip => "0.0.0.0", - :request_method => "CustomRequest", - :application_id => "Woohoo", - :description => "Description", - :order_id => "orderid1", - :currency => "INR", - :email => "jim@example.com", + @gateway.purchase(-100, @credit_card) + end.respond_with(failed_purchase_response_multiple_messages) + + assert_failure response + assert_equal 'Invalid Customer Phone,Invalid ShippingAddress Phone', response.message + assert_nil response.authorization + assert response.test? + end + + def test_purchase_with_all_options + response = stub_comms do + @gateway.purchase(200, @credit_card, + :transaction_type => 'CustomTransactionType', + :redirect_url => 'http://awesomesauce.com', + :ip => '0.0.0.0', + :application_id => 'Woohoo', + :partner_id => 'SomePartner', + :description => 'The Really Long Description More Than Sixty Four Characters Gets Truncated', + :order_id => 'orderid1', + :invoice => 'I1234', + :currency => 'INR', + :email => 'jim@example.com', :billing_address => { - :title => "Mr.", - :name => "Jim Awesome Smith", - :company => "Awesome Co", - :address1 => "1234 My Street", - :address2 => "Apt 1", - :city => "Ottawa", - :state => "ON", - :zip => "K1C2N6", - :country => "CA", - :phone => "(555)555-5555", - :fax => "(555)555-6666" + :title => 'Mr.', + :name => 'Jim Awesome Smith', + :company => 'Awesome Co', + :address1 => '1234 My Street', + :address2 => 'Apt 1', + :city => 'Ottawa', + :state => 'ON', + :zip => 'K1C2N6', + :country => 'CA', + :phone => '(555)555-5555', + :fax => '(555)555-6666' }, :shipping_address => { - :title => "Ms.", - :name => "Baker", - :company => "Elsewhere Inc.", - :address1 => "4321 Their St.", - :address2 => "Apt 2", - :city => "Chicago", - :state => "IL", - :zip => "60625", - :country => "US", - :phone => "1115555555", - :fax => "1115556666" + :title => 'Ms.', + :name => 'Baker', + :company => 'Elsewhere Inc.', + :address1 => '4321 Their St.', + :address2 => 'Apt 2', + :city => 'Chicago', + :state => 'IL', + :zip => '60625', + :country => 'US', + :phone => '1115555555', + :fax => '1115556666' } ) end.check_request do |endpoint, data, headers| - assert_no_match(%r{#{@credit_card.number}}, data) - - assert_match(%r{RedirectUrl>http://awesomesauce.com<}, data) - assert_match(%r{CustomerIP>0.0.0.0<}, data) - assert_match(%r{Method>CustomRequest<}, data) - assert_match(%r{DeviceID>Woohoo<}, data) - - assert_match(%r{TotalAmount>200<}, data) - assert_match(%r{InvoiceDescription>Description<}, data) - assert_match(%r{InvoiceReference>orderid1<}, data) - assert_match(%r{CurrencyCode>INR<}, data) - - assert_match(%r{Title>Mr.<}, data) - assert_match(%r{FirstName>Jim<}, data) - assert_match(%r{LastName>Awesome Smith<}, data) - assert_match(%r{CompanyName>Awesome Co<}, data) - assert_match(%r{Street1>1234 My Street<}, data) - assert_match(%r{Street2>Apt 1<}, data) - assert_match(%r{City>Ottawa<}, data) - assert_match(%r{State>ON<}, data) - assert_match(%r{PostalCode>K1C2N6<}, data) - assert_match(%r{Country>ca<}, data) - assert_match(%r{Phone>\(555\)555-5555<}, data) - assert_match(%r{Fax>\(555\)555-6666<}, data) - assert_match(%r{Email>jim@example\.com<}, data) - - assert_match(%r{Title>Ms.<}, data) - assert_match(%r{LastName>Baker<}, data) + assert_match(%r{"TransactionType":"CustomTransactionType"}, data) + assert_match(%r{"RedirectUrl":"http://awesomesauce.com"}, data) + assert_match(%r{"CustomerIP":"0.0.0.0"}, data) + assert_match(%r{"DeviceID":"Woohoo"}, data) + assert_match(%r{"PartnerID":"SomePartner"}, data) + + assert_match(%r{"TotalAmount":"200"}, data) + assert_match(%r{"InvoiceDescription":"The Really Long Description More Than Sixty Four Characters Gets"}, data) + assert_match(%r{"InvoiceReference":"orderid1"}, data) + assert_match(%r{"InvoiceNumber":"I1234"}, data) + assert_match(%r{"CurrencyCode":"INR"}, data) + + assert_match(%r{"Title":"Mr."}, data) + assert_match(%r{"FirstName":"Jim Awesome"}, data) + assert_match(%r{"LastName":"Smith"}, data) + assert_match(%r{"CompanyName":"Awesome Co"}, data) + assert_match(%r{"Street1":"1234 My Street"}, data) + assert_match(%r{"Street2":"Apt 1"}, data) + assert_match(%r{"City":"Ottawa"}, data) + assert_match(%r{"State":"ON"}, data) + assert_match(%r{"PostalCode":"K1C2N6"}, data) + assert_match(%r{"Country":"ca"}, data) + assert_match(%r{"Phone":"\(555\)555-5555"}, data) + assert_match(%r{"Fax":"\(555\)555-6666"}, data) + assert_match(%r{"Email":"jim@example\.com"}, data) + + assert_match(%r{"Title":"Ms."}, data) + assert_match(%r{"LastName":"Baker"}, data) assert_no_match(%r{Elsewhere Inc.}, data) - assert_match(%r{Street1>4321 Their St.<}, data) - assert_match(%r{Street2>Apt 2<}, data) - assert_match(%r{City>Chicago<}, data) - assert_match(%r{State>IL<}, data) - assert_match(%r{PostalCode>60625<}, data) - assert_match(%r{Country>us<}, data) - assert_match(%r{Phone>1115555555<}, data) - assert_match(%r{Fax>1115556666<}, data) - assert_match(%r{Email>(\s+)?<}, data) - end.respond_with(successful_setup_purchase_response) + assert_match(%r{"Street1":"4321 Their St."}, data) + assert_match(%r{"Street2":"Apt 2"}, data) + assert_match(%r{"City":"Chicago"}, data) + assert_match(%r{"State":"IL"}, data) + assert_match(%r{"PostalCode":"60625"}, data) + assert_match(%r{"Country":"us"}, data) + assert_match(%r{"Phone":"1115555555"}, data) + assert_match(%r{"Fax":"1115556666"}, data) + assert_match(%r{"Email":"jim@example\.com"}, data) + end.respond_with(successful_purchase_response) assert_success response - assert_equal( - "60CF3xWrFUQeDCEsJcA8zNHaspAT3CKpe-0DiqWjTYA3RZw1xhw2LU-BFCNYbr7eJt8KFaxCxmzYh9WDAYX8yIuYexTq0tC8i2kOt0dm0EV-mjxYEQ2YeHP2dazkSc7j58OiT", - response.authorization - ) + assert_equal 10440187, response.authorization assert response.test? end - def test_successful_run_purchase - request_sequence = sequence("request") - @gateway.expects(:ssl_request).returns(successful_setup_purchase_response).in_sequence(request_sequence) - @gateway.expects(:raw_ssl_request).with( - :post, - "https://secure-au.sandbox.ewaypayments.com/Process", - all_of( - regexp_matches(%r{EWAY_ACCESSCODE=60CF3xWrFUQeDCEsJcA8zNHaspAT3CKpe-0DiqWjTYA3RZw1xhw2LU-BFCNYbr7eJt8KFaxCxmzYh9WDAYX8yIuYexTq0tC8i2kOt0dm0EV-mjxYEQ2YeHP2dazkSc7j58OiT}), - regexp_matches(%r{EWAY_CARDNAME=Longbob\+Longsen}), - regexp_matches(%r{EWAY_CARDNUMBER=#{@credit_card.number}}), - regexp_matches(%r{EWAY_CARDEXPIRYMONTH=#{@credit_card.month}}), - regexp_matches(%r{EWAY_CARDEXPIRYYEAR=#{@credit_card.year}}), - regexp_matches(%r{EWAY_CARDCVN=#{@credit_card.verification_value}}) - ), - anything - ).returns(successful_run_purchase_response).in_sequence(request_sequence) - @gateway.expects(:ssl_request).returns(successful_status_response).in_sequence(request_sequence) - - response = @gateway.purchase(@amount, @credit_card, :order_id => 1) + def test_partner_id_class_attribute + ActiveMerchant::Billing::EwayRapidGateway.partner_id = 'SomePartner' + stub_comms do + @gateway.purchase(200, @credit_card) + end.check_request do |endpoint, data, headers| + assert_match(%r{"PartnerID":"SomePartner"}, data) + end.respond_with(successful_purchase_response) + end + + def test_partner_id_params_overrides_class_attribute + ActiveMerchant::Billing::EwayRapidGateway.partner_id = 'SomePartner' + stub_comms do + @gateway.purchase(200, @credit_card, partner_id: 'OtherPartner') + end.check_request do |endpoint, data, headers| + assert_match(%r{"PartnerID":"OtherPartner"}, data) + end.respond_with(successful_purchase_response) + end + + def test_partner_id_is_omitted_when_not_set + stub_comms do + @gateway.purchase(200, @credit_card) + end.check_request do |endpoint, data, headers| + assert_no_match(%r{"PartnerID":}, data) + end.respond_with(successful_purchase_response) + end + + def test_partner_id_truncates_to_50_characters + partner_string = 'EWay Rapid PartnerID is capped at 50 characters and will truncate if it is too long.' + stub_comms do + @gateway.purchase(200, @credit_card, partner_id: partner_string) + end.check_request do |endpoint, data, headers| + assert_match(%r{"PartnerID":"#{partner_string.slice(0, 50)}"}, data) + end.respond_with(successful_purchase_response) + end + + def test_successful_authorize + response = stub_comms do + @gateway.authorize(@amount, @credit_card) + end.respond_with(successful_authorize_response) + assert_success response - assert_equal "Transaction Approved", response.message - assert_equal( - "60CF3sfH7-yvAsUAHrdIiGppPrQW7v7DMAXxKkaKwyrIUoqvUvK44XbK9G9HNbngIz_iwQpfmPT_duMgh2G0pXCX8i4z1RAmMHpUQwa6VrghV3Bx9rh_tojjym7LC_fE-eR97", - response.authorization - ) + assert_equal 'Transaction Approved Successful', response.message + assert_equal 10774952, response.authorization + end + + def test_authorize_passes_customer_data_from_payment_method_when_no_address_is_provided + stub_comms do + @gateway.authorize(@amount, @credit_card, { email: @email }) + end.check_request do |endpoint, data, headers| + assert_customer_data_passed( + data, + @credit_card.first_name, + @credit_card.last_name, + @email + ) + end.respond_with(successful_authorize_response) + end + + def test_authorize_passes_customer_data_from_billing_address + stub_comms do + @gateway.authorize(@amount, @credit_card, { billing_address: @address, email: @email }) + end.check_request do |endpoint, data, headers| + assert_customer_data_passed( + data, + @address[:name].split[0], + @address[:name].split[1], + @email, + @address + ) + end.respond_with(successful_authorize_response) + end + + def test_authorize_passes_customer_data_from_address + stub_comms do + @gateway.authorize(@amount, @credit_card, { address: @address, email: @email }) + end.check_request do |endpoint, data, headers| + assert_customer_data_passed( + data, + @address[:name].split[0], + @address[:name].split[1], + @email, + @address + ) + end.respond_with(successful_authorize_response) + end + + def test_authorize_passes_shipping_data + stub_comms do + @gateway.authorize(@amount, @credit_card, { shipping_address: @shipping_address, email: @email }) + end.check_request do |endpoint, data, headers| + assert_shipping_data_passed(data, @shipping_address, @email) + end.respond_with(successful_authorize_response) + end + + def test_successful_capture + response = stub_comms do + @gateway.capture(nil, 'auth') + end.respond_with(successful_capture_response) + + assert_success response + assert_equal '982541', response.message + assert_equal 10774953, response.authorization + end + + def test_failed_authorize + response = stub_comms do + @gateway.authorize(@amount, @credit_card) + end.respond_with(failed_authorize_response) + + assert_failure response + assert_equal 'Invalid Payment TotalAmount', response.message + assert_nil response.authorization + end + + def test_failed_capture + response = stub_comms do + @gateway.capture(@amount, 'auth') + end.respond_with(failed_capture_response) + + assert_failure response + assert_equal 'Invalid Auth Transaction ID for Capture/Void', response.message + assert_equal 0, response.authorization + end + + def test_successful_void + response = stub_comms do + @gateway.void('auth') + end.respond_with(successful_void_response) + + assert_success response + assert_equal '878060', response.message + assert_equal 10775041, response.authorization + end + + def test_failed_void + response = stub_comms do + @gateway.void(@amount, 'auth') + end.respond_with(failed_void_response) + + assert_failure response + assert_equal 'Invalid Auth Transaction ID for Capture/Void', response.message + assert_equal 0, response.authorization + end + + def test_successful_store + response = stub_comms do + @gateway.store(@credit_card, :billing_address => { + :title => 'Mr.', + :name => 'Jim Awesome Smith', + :company => 'Awesome Co', + :address1 => '1234 My Street', + :address2 => 'Apt 1', + :city => 'Ottawa', + :state => 'ON', + :zip => 'K1C2N6', + :country => 'CA', + :phone => '(555)555-5555', + :fax => '(555)555-6666' + }) + end.check_request do |endpoint, data, headers| + assert_match '"Method":"CreateTokenCustomer"', data + end.respond_with(successful_store_response) + + assert_success response + assert_equal 'Transaction Approved Successful', response.message + assert_equal 917224224772, response.authorization assert response.test? end - def test_failed_run_purchase - request_sequence = sequence("request") - @gateway.expects(:ssl_request).returns(successful_setup_purchase_response).in_sequence(request_sequence) - @gateway.expects(:raw_ssl_request).returns(failed_run_purchase_response).in_sequence(request_sequence) + def test_store_passes_customer_data_from_billing_address + stub_comms do + @gateway.store(@credit_card, { billing_address: @address, email: @email }) + end.check_request do |endpoint, data, headers| + assert_customer_data_passed( + data, + @address[:name].split[0], + @address[:name].split[1], + @email, + @address + ) + end.respond_with(successful_store_response) + end + + def test_store_passes_shipping_data + stub_comms do + @gateway.store( + @credit_card, + { shipping_address: @shipping_address, billing_address: @address, email: @email } + ) + end.check_request do |endpoint, data, headers| + assert_shipping_data_passed(data, @shipping_address, @email) + end.respond_with(successful_store_response) + end + + def test_failed_store + response = stub_comms do + @gateway.store(@credit_card, :billing_address => {}) + end.respond_with(failed_store_response) - response = @gateway.purchase(@amount, @credit_card, :order_id => 1) assert_failure response - assert_match %r{Not Found}, response.message + assert_equal 'Customer CountryCode Required', response.message assert_nil response.authorization assert response.test? end - def test_successful_status + def test_successful_update response = stub_comms do - @gateway.status("thetransauth") + @gateway.update('faketoken', nil) end.check_request do |endpoint, data, headers| - assert_match(%r{thetransauth}, data) - end.respond_with(successful_status_response) + assert_match '"Method":"UpdateTokenCustomer"', data + end.respond_with(successful_update_response) assert_success response - assert_equal( - "60CF3sfH7-yvAsUAHrdIiGppPrQW7v7DMAXxKkaKwyrIUoqvUvK44XbK9G9HNbngIz_iwQpfmPT_duMgh2G0pXCX8i4z1RAmMHpUQwa6VrghV3Bx9rh_tojjym7LC_fE-eR97", - response.authorization - ) + assert_equal 'Transaction Approved Successful', response.message + assert_equal 916161208398, response.authorization + assert response.test? + end + + def test_update_passes_customer_data_from_payment_method_when_no_address_is_provided + stub_comms do + @gateway.update('token', @credit_card, { email: @email }) + end.check_request do |endpoint, data, headers| + assert_customer_data_passed( + data, + @credit_card.first_name, + @credit_card.last_name, + @email + ) + end.respond_with(successful_update_response) + end + + def test_update_passes_customer_data_from_billing_address + stub_comms do + @gateway.update('token', @credit_card, { billing_address: @address, email: @email }) + end.check_request do |endpoint, data, headers| + assert_customer_data_passed( + data, + @address[:name].split[0], + @address[:name].split[1], + @email, + @address + ) + end.respond_with(successful_update_response) + end + + def test_update_passes_customer_data_from_address + stub_comms do + @gateway.update('token', @credit_card, { address: @address, email: @email }) + end.check_request do |endpoint, data, headers| + assert_customer_data_passed( + data, + @address[:name].split[0], + @address[:name].split[1], + @email, + @address + ) + end.respond_with(successful_update_response) + end + + def test_update_passes_shipping_data + stub_comms do + @gateway.update('token', @credit_card, { shipping_address: @shipping_address, email: @email }) + end.check_request do |endpoint, data, headers| + assert_shipping_data_passed(data, @shipping_address, @email) + end.respond_with(successful_update_response) + end + + def test_successful_refund + response = stub_comms do + @gateway.refund(@amount, '1234567') + end.check_request do |endpoint, data, headers| + assert_match %r{Transaction\/1234567\/Refund$}, endpoint + json = JSON.parse(data) + assert_equal '100', json['Refund']['TotalAmount'] + assert_equal '1234567', json['Refund']['TransactionID'] + end.respond_with(successful_refund_response) + + assert_success response + assert_equal 'Transaction Approved Successful', response.message + assert_equal 10488258, response.authorization assert response.test? - assert_equal "Transaction Approved", response.message - assert_equal "orderid1", response.params["invoicereference"] end - def test_failed_status + def test_failed_refund response = stub_comms do - @gateway.status("thetransauth") - end.respond_with(failed_status_response) + @gateway.refund(@amount, '1234567') + end.respond_with(failed_refund_response) assert_failure response - assert_equal( - "A1001WfAHR_QP8daLnG6fQLcadzuCBJbpIp-zsUL6FkQgUyY2MXwVA0etYvflPe_rDBiuOMV-BfTSGDKt7uU3E2bLUhsD1rrXwGT9BTPcOOH_Vh9jHDSn2inqk8udwQIRcxuc", - response.authorization - ) + assert_equal 'System Error', response.message + assert_nil response.authorization assert response.test? - assert_equal "Do Not Honour", response.message - assert_equal "1", response.params["invoicereference"] end - def test_store_calls_sub_methods - options = { - :order_id => 1, - :billing_address => { - :name => "Jim Awesome Smith", - } - } - @gateway.expects(:purchase).with(0, @credit_card, options.merge(:request_method => "CreateTokenCustomer")) + def test_successful_stored_card_purchase + response = stub_comms do + @gateway.purchase(100, 'the_customer_token', transaction_type: 'MOTO') + end.check_request do |endpoint, data, headers| + assert_match '"Method":"TokenPayment"', data + assert_match '"TransactionType":"MOTO"', data + end.respond_with(successful_store_purchase_response) - @gateway.store(@credit_card, options) + assert_success response + assert_equal 'Transaction Approved Successful', response.message + assert_equal 10440234, response.authorization + assert response.test? end def test_verification_results response = stub_comms do - @gateway.status("thetransauth") - end.respond_with(successful_status_response(:verification_status => "Valid")) + @gateway.purchase(100, @credit_card) + end.respond_with(successful_purchase_response(:verification_status => 'Valid')) assert_success response - assert_equal "M", response.cvv_result["code"] - assert_equal "M", response.avs_result["code"] + assert_equal 'M', response.cvv_result['code'] + assert_equal 'M', response.avs_result['code'] response = stub_comms do - @gateway.status("thetransauth") - end.respond_with(successful_status_response(:verification_status => "Invalid")) + @gateway.purchase(100, @credit_card) + end.respond_with(successful_purchase_response(:verification_status => 'Invalid')) assert_success response - assert_equal "N", response.cvv_result["code"] - assert_equal "N", response.avs_result["code"] + assert_equal 'N', response.cvv_result['code'] + assert_equal 'N', response.avs_result['code'] response = stub_comms do - @gateway.status("thetransauth") - end.respond_with(successful_status_response(:verification_status => "Unchecked")) + @gateway.purchase(100, @credit_card) + end.respond_with(successful_purchase_response(:verification_status => 'Unchecked')) assert_success response - assert_equal "P", response.cvv_result["code"] - assert_equal "I", response.avs_result["code"] + assert_equal 'P', response.cvv_result['code'] + assert_equal 'I', response.avs_result['code'] + end + + def test_transcript_scrubbing + assert_equal scrubbed_transcript, @gateway.scrub(transcript) end private - def successful_setup_purchase_response + def assert_customer_data_passed(data, first_name, last_name, email, address = nil) + parsed_data = JSON.parse(data) + customer = parsed_data['Customer'] + + assert_equal customer['FirstName'], first_name + assert_equal customer['LastName'], last_name + assert_equal customer['Email'], email + + if address + assert_equal customer['Title'], address[:title] + assert_equal customer['CompanyName'], address[:company] + assert_equal customer['Street1'], address[:address1] + assert_equal customer['Street2'], address[:address2] + assert_equal customer['City'], address[:city] + assert_equal customer['State'], address[:state] + assert_equal customer['PostalCode'], address[:zip] + assert_equal customer['Country'], address[:country].downcase + assert_equal customer['Phone'], address[:phone] + assert_equal customer['Fax'], address[:fax] + end + end + + def assert_shipping_data_passed(data, address, email) + parsed_data = JSON.parse(data) + shipping = parsed_data['ShippingAddress'] + + assert_equal shipping['FirstName'], address[:name].split[0] + assert_equal shipping['LastName'], address[:name].split[1] + assert_equal shipping['Title'], address[:title] + assert_equal shipping['Street1'], address[:address1] + assert_equal shipping['Street2'], address[:address2] + assert_equal shipping['City'], address[:city] + assert_equal shipping['State'], address[:state] + assert_equal shipping['PostalCode'], address[:zip] + assert_equal shipping['Country'], address[:country].downcase + assert_equal shipping['Phone'], address[:phone_number] + assert_equal shipping['Fax'], address[:fax] + assert_equal shipping['Email'], email + end + + def successful_purchase_response(options = {}) + verification_status = options[:verification_status] || 0 + verification_status = %Q{"#{verification_status}"} if verification_status.is_a? String %( - <CreateAccessCodeResponse> - <AccessCode>60CF3xWrFUQeDCEsJcA8zNHaspAT3CKpe-0DiqWjTYA3RZw1xhw2LU-BFCNYbr7eJt8KFaxCxmzYh9WDAYX8yIuYexTq0tC8i2kOt0dm0EV-mjxYEQ2YeHP2dazkSc7j58OiT</AccessCode> - <Customer> - <TokenCustomerID p3:nil=\"true\" xmlns:p3=\"http://www.w3.org/2001/XMLSchema-instance\" /> - <Reference /> - <Title /> - <FirstName /> - <LastName /> - <CompanyName /> - <JobDescription /> - <Street1 /> - <Street2 /> - <City /> - <State /> - <PostalCode /> - <Country /> - <Email /> - <Phone /> - <Mobile /> - <Comments /> - <Fax /> - <Url /> - <CardNumber /> - <CardStartMonth /> - <CardStartYear /> - <CardIssueNumber /> - <CardName /> - <CardExpiryMonth /> - <CardExpiryYear /> - <IsActive>false</IsActive> - </Customer> - <Payment> - <TotalAmount>100</TotalAmount> - <InvoiceDescription>Store Purchase</InvoiceDescription> - <InvoiceReference>1</InvoiceReference> - <CurrencyCode>AUD</CurrencyCode> - </Payment> - <FormActionURL>https://secure-au.sandbox.ewaypayments.com/Process</FormActionURL> - </CreateAccessCodeResponse> + { + "AuthorisationCode": "763051", + "ResponseCode": "00", + "ResponseMessage": "A2000", + "TransactionID": 10440187, + "TransactionStatus": true, + "TransactionType": "Purchase", + "BeagleScore": 0, + "Verification": { + "CVN": #{verification_status}, + "Address": #{verification_status}, + "Email": #{verification_status}, + "Mobile": #{verification_status}, + "Phone": #{verification_status} + }, + "Customer": { + "CardDetails": { + "Number": "444433XXXXXX1111", + "Name": "Longbob Longsen", + "ExpiryMonth": "09", + "ExpiryYear": "14", + "StartMonth": null, + "StartYear": null, + "IssueNumber": null + }, + "TokenCustomerID": null, + "Reference": "", + "Title": "Mr.", + "FirstName": "Jim", + "LastName": "Smith", + "CompanyName": "Widgets Inc", + "JobDescription": "", + "Street1": "1234 My Street", + "Street2": "Apt 1", + "City": "Ottawa", + "State": "ON", + "PostalCode": "K1C2N6", + "Country": "ca", + "Email": "", + "Phone": "(555)555-5555", + "Mobile": "", + "Comments": "", + "Fax": "(555)555-6666", + "Url": "" + }, + "Payment": { + "TotalAmount": 100, + "InvoiceNumber": "1", + "InvoiceDescription": "Store Purchase", + "InvoiceReference": "1", + "CurrencyCode": "AUD" + }, + "Errors": null + } ) end - def failed_setup_purchase_response + def failed_purchase_response_without_message %( - <CreateAccessCodeResponse> - <Errors>V6047</Errors> - <Customer> - <TokenCustomerID p3:nil="true" xmlns:p3="http://www.w3.org/2001/XMLSchema-instance" /> - <IsActive>false</IsActive> - </Customer> - <Payment> - <TotalAmount>100</TotalAmount> - <CurrencyCode>AUD</CurrencyCode> - </Payment> - </CreateAccessCodeResponse> + { + "AuthorisationCode": null, + "ResponseCode": "05", + "TransactionID": null, + "TransactionStatus": null, + "TransactionType": "Purchase", + "BeagleScore": null, + "Verification": null, + "Customer": { + } + } ) end - def successful_status_response(options={}) - verification_status = (options[:verification_status] || "Unchecked") + def failed_purchase_response_multiple_messages %( - <GetAccessCodeResultResponse> - <AccessCode>60CF3sfH7-yvAsUAHrdIiGppPrQW7v7DMAXxKkaKwyrIUoqvUvK44XbK9G9HNbngIz_iwQpfmPT_duMgh2G0pXCX8i4z1RAmMHpUQwa6VrghV3Bx9rh_tojjym7LC_fE-eR97</AccessCode> - <AuthorisationCode>957199</AuthorisationCode> - <ResponseCode>00</ResponseCode> - <ResponseMessage>A2000</ResponseMessage> - <InvoiceNumber /> - <InvoiceReference>orderid1</InvoiceReference> - <TotalAmount>100</TotalAmount> - <TransactionID>9942726</TransactionID> - <TransactionStatus>true</TransactionStatus> - <TokenCustomerID p2:nil=\"true\" xmlns:p2=\"http://www.w3.org/2001/XMLSchema-instance\" /> - <BeagleScore>0</BeagleScore> - <Options /> - <Verification> - <CVN>#{verification_status}</CVN> - <Address>#{verification_status}</Address> - <Email>#{verification_status}</Email> - <Mobile>#{verification_status}</Mobile> - <Phone>#{verification_status}</Phone> - </Verification> - </GetAccessCodeResultResponse> + { + "AuthorisationCode": null, + "ResponseCode": null, + "ResponseMessage": "V6070,V6083", + "TransactionID": null, + "TransactionStatus": null, + "TransactionType": "Purchase", + "BeagleScore": null, + "Verification": null, + "Customer": { + } + } ) end - def failed_status_response + def failed_purchase_response %( - <GetAccessCodeResultResponse> - <AccessCode>A1001WfAHR_QP8daLnG6fQLcadzuCBJbpIp-zsUL6FkQgUyY2MXwVA0etYvflPe_rDBiuOMV-BfTSGDKt7uU3E2bLUhsD1rrXwGT9BTPcOOH_Vh9jHDSn2inqk8udwQIRcxuc</AccessCode> - <AuthorisationCode /> - <ResponseCode>05</ResponseCode> - <ResponseMessage>D4405</ResponseMessage> - <InvoiceNumber /> - <InvoiceReference>1</InvoiceReference> - <TotalAmount>105</TotalAmount> - <TransactionID>9942743</TransactionID> - <TransactionStatus>false</TransactionStatus> - <TokenCustomerID p2:nil=\"true\" xmlns:p2=\"http://www.w3.org/2001/XMLSchema-instance\" /> - <BeagleScore>0</BeagleScore> - <Options /> - <Verification> - <CVN>Unchecked</CVN> - <Address>Unchecked</Address> - <Email>Unchecked</Email> - <Mobile>Unchecked</Mobile> - <Phone>Unchecked</Phone> - </Verification> - </GetAccessCodeResultResponse> + { + "AuthorisationCode": null, + "ResponseCode": null, + "ResponseMessage": null, + "TransactionID": null, + "TransactionStatus": null, + "TransactionType": "Purchase", + "BeagleScore": null, + "Verification": null, + "Customer": { + "CardDetails": { + "Number": "444433XXXXXX1111", + "Name": "Longbob Longsen", + "ExpiryMonth": "09", + "ExpiryYear": "2014", + "StartMonth": null, + "StartYear": null, + "IssueNumber": null + }, + "TokenCustomerID": null, + "Reference": null, + "Title": "Mr.", + "FirstName": "Jim", + "LastName": "Smith", + "CompanyName": "Widgets Inc", + "JobDescription": null, + "Street1": "1234 My Street", + "Street2": "Apt 1", + "City": "Ottawa", + "State": "ON", + "PostalCode": "K1C2N6", + "Country": "ca", + "Email": null, + "Phone": "(555)555-5555", + "Mobile": null, + "Comments": null, + "Fax": "(555)555-6666", + "Url": null + }, + "Payment": { + "TotalAmount": -100, + "InvoiceNumber": "1", + "InvoiceDescription": "Store Purchase", + "InvoiceReference": "1", + "CurrencyCode": "AUD" + }, + "Errors": "V6011" + } ) end - class MockResponse - attr_reader :code, :body - def initialize(code, body, headers={}) - @code, @body, @headers = code, body, headers - end + def successful_authorize_response + %( + { + "AuthorisationCode": "805851", + "ResponseCode": "00", + "ResponseMessage": "A2000", + "TransactionID": 10774952, + "TransactionStatus": true, + "TransactionType": "Purchase", + "BeagleScore": 0, + "Verification": { + "CVN": 0, + "Address": 0, + "Email": 0, + "Mobile": 0, + "Phone": 0 + }, + "Customer": { + "CardDetails": { + "Number": "444433XXXXXX1111", + "Name": "Longbob Longsen", + "ExpiryMonth": "09", + "ExpiryYear": "15", + "StartMonth": null, + "StartYear": null, + "IssueNumber": null + }, + "TokenCustomerID": null, + "Reference": "", + "Title": "Mr.", + "FirstName": "Jim", + "LastName": "Smith", + "CompanyName": "Widgets Inc", + "JobDescription": "", + "Street1": "1234 My Street", + "Street2": "Apt 1", + "City": "Ottawa", + "State": "ON", + "PostalCode": "K1C2N6", + "Country": "ca", + "Email": "", + "Phone": "(555)555-5555", + "Mobile": "", + "Comments": "", + "Fax": "(555)555-6666", + "Url": "" + }, + "Payment": { + "TotalAmount":100, + "InvoiceNumber": "1", + "InvoiceDescription": "Store Purchase", + "InvoiceReference": "1", + "CurrencyCode": "AUD" + }, + "Errors": null + } + ) + end - def [](header) - @headers[header] - end + def failed_authorize_response + %( + { + "AuthorisationCode": null, + "ResponseCode": null, + "ResponseMessage": null, + "TransactionID": null, + "TransactionStatus": null, + "TransactionType": "Purchase", + "BeagleScore": null, + "Verification": null, + "Customer": { + "CardDetails": { + "Number": "444433XXXXXX1111", + "Name": "Longbob Longsen", + "ExpiryMonth": "09", + "ExpiryYear": "2015", + "StartMonth": null, + "StartYear": null, + "IssueNumber": null + }, + "TokenCustomerID": null, + "Reference": null, + "Title": "Mr.", + "FirstName": "Jim", + "LastName": "Smith", + "CompanyName": "Widgets Inc", + "JobDescription": null, + "Street1": "1234 My Street", + "Street2": "Apt 1", + "City": "Ottawa", + "State": "ON", + "PostalCode": "K1C2N6", + "Country": "ca", + "Email": null, + "Phone": "(555)555-5555", + "Mobile": null, + "Comments": null, + "Fax": "(555)555-6666", + "Url": null + }, + "Payment": { + "TotalAmount": -100, + "InvoiceNumber": "1", + "InvoiceDescription": "Store Purchase", + "InvoiceReference": "1", + "CurrencyCode": "AUD" + }, + "Errors": "V6011" + } + ) end - def successful_run_purchase_response - MockResponse.new( - 302, - %( - <html><head><title>Object moved</title></head><body> - <h2>Object moved to <a href="http://example.com/?AccessCode=60CF3xWrFUQeDCEsJcA8zNHaspAT3CKpe-0DiqWjTYA3RZw1xhw2LU-BFCNYbr7eJt8KFaxCxmzYh9WDAYX8yIuYexTq0tC8i2kOt0dm0EV-mjxYEQ2YeHP2dazkSc7j58OiT">here</a>.</h2> - </body></html> - ), - "Location" => "http://example.com/?AccessCode=60CF3xWrFUQeDCEsJcA8zNHaspAT3CKpe-0DiqWjTYA3RZw1xhw2LU-BFCNYbr7eJt8KFaxCxmzYh9WDAYX8yIuYexTq0tC8i2kOt0dm0EV-mjxYEQ2YeHP2dazkSc7j58OiT" + def successful_capture_response + %( + { + "ResponseCode": "982541", + "ResponseMessage": "982541", + "TransactionID": 10774953, + "TransactionStatus": true, + "Errors": null + } ) end - def failed_run_purchase_response - MockResponse.new( - 200, - %( - {"Message":"Not Found","Errors":null} - ) + def failed_capture_response + %( + { + "ResponseCode": null, + "ResponseMessage": null + ,"TransactionID": 0 + ,"TransactionStatus": false, + "Errors": "V6134" + } + ) + end + + def successful_void_response + %( + { + "ResponseCode": "878060", + "ResponseMessage": "878060", + "TransactionID": 10775041, + "TransactionStatus": true, + "Errors": null + } + ) + end + + def failed_void_response + %( + { + "ResponseCode": null, + "ResponseMessage": null, + "TransactionID": 0, + "TransactionStatus": false, + "Errors": "V6134" + } ) end + + def successful_store_response + %( + { + "AuthorisationCode": null, + "ResponseCode": "00", + "ResponseMessage": "A2000", + "TransactionID": null, + "TransactionStatus": false, + "TransactionType": "Purchase", + "BeagleScore": null, + "Verification": { + "CVN": 0, + "Address": 0, + "Email": 0, + "Mobile": 0, + "Phone": 0 + }, + "Customer": { + "CardDetails": { + "Number": "444433XXXXXX1111", + "Name": "Longbob Longsen", + "ExpiryMonth": "09", + "ExpiryYear": "14", + "StartMonth": null, + "StartYear": null, + "IssueNumber": null + }, + "TokenCustomerID": 917224224772, + "Reference": "", + "Title": "Dr.", + "FirstName": "Jim", + "LastName": "Smith", + "CompanyName": "Widgets Inc", + "JobDescription": "", + "Street1": "1234 My Street", + "Street2": "Apt 1", + "City": "Ottawa", + "State": "ON", + "PostalCode": "K1C2N6", + "Country": "ca", + "Email": "", + "Phone": "(555)555-5555", + "Mobile": "", + "Comments": "", + "Fax": "(555)555-6666", + "Url": "" + }, + "Payment": { + "TotalAmount": 0, + "InvoiceNumber": "1", + "InvoiceDescription": "Store Purchase", + "InvoiceReference": "1", + "CurrencyCode": "AUD" + }, + "Errors": null + } + ) + end + + def failed_store_response + %( + { + "AuthorisationCode": null, + "ResponseCode": null, + "ResponseMessage": null, + "TransactionID": null, + "TransactionStatus": null, + "TransactionType": "Purchase", + "BeagleScore": null, + "Verification": null, + "Customer": { + "CardDetails": { + "Number": "444433XXXXXX1111", + "Name": "Longbob Longsen", + "ExpiryMonth": "09", + "ExpiryYear": "2014", + "StartMonth": null, + "StartYear": null, + "IssueNumber": null + }, + "TokenCustomerID": null, + "Reference": null, + "Title": "Mr.", + "FirstName": "Jim", + "LastName": "Smith", + "CompanyName": "Widgets Inc", + "JobDescription": null, + "Street1": "1234 My Street", + "Street2": "Apt 1", + "City": "Ottawa", + "State": "ON", + "PostalCode": "K1C2N6", + "Country": null, + "Email": null, + "Phone": "(555)555-5555", + "Mobile": null, + "Comments": null, + "Fax": "(555)555-6666", + "Url": null + }, + "Payment": { + "TotalAmount": 0, + "InvoiceNumber": "1", + "InvoiceDescription": "Store Purchase", + "InvoiceReference": "1", + "CurrencyCode": "AUD" + }, + "Errors": "V6044" + } + ) + end + + def successful_update_response + %( + { + "AuthorisationCode": null, + "ResponseCode": "00", + "ResponseMessage": "A2000", + "TransactionID": null, + "TransactionStatus": false, + "TransactionType": "Purchase", + "BeagleScore": null, + "Verification": { + "CVN": 0, + "Address": 0, + "Email": 0, + "Mobile": 0, + "Phone": 0 + }, + "Customer": { + "CardDetails": { + "Number": "444433XXXXXX1111", + "Name": "Longbob Longsen", + "ExpiryMonth": "09", + "ExpiryYear": "14", + "StartMonth": null, + "StartYear": null, + "IssueNumber": null + }, + "TokenCustomerID": 916161208398, + "Reference": "", + "Title": "Dr.", + "FirstName": "Jim", + "LastName": "Smith", + "CompanyName": "Widgets Inc", + "JobDescription": "", + "Street1": "1234 My Street", + "Street2": "Apt 1", + "City": "Ottawa", + "State": "ON", + "PostalCode": "K1C2N6", + "Country": "ca", + "Email": "", + "Phone": "(555)555-5555", + "Mobile": "", + "Comments": "", + "Fax": "(555)555-6666", + "Url": "" + }, + "Payment": { + "TotalAmount": 0, + "InvoiceNumber": "1", + "InvoiceDescription": "Store Purchase", + "InvoiceReference": "1", + "CurrencyCode": "AUD" + }, + "Errors": null + } + ) + end + + def successful_refund_response + %( + { + "AuthorisationCode": "457313", + "ResponseCode": null, + "ResponseMessage": "A2000", + "TransactionID": 10488258, + "TransactionStatus": true, + "Verification": null, + "Customer": { + "CardDetails": { + "Number": null, + "Name": null, + "ExpiryMonth": null, + "ExpiryYear": null, + "StartMonth": null, + "StartYear": null, + "IssueNumber": null + }, + "TokenCustomerID": null, + "Reference": null, + "Title": null, + "FirstName": "Jim", + "LastName": "Smith", + "CompanyName": "Widgets Inc", + "JobDescription": null, + "Street1": "1234 My Street", + "Street2": "Apt 1", + "City": "Ottawa", + "State": "ON", + "PostalCode": "K1C2N6", + "Country": "ca", + "Email": null, + "Phone": "(555)555-5555", + "Mobile": null, + "Comments": null, + "Fax": "(555)555-6666", + "Url": null + }, + "Refund": { + "TransactionID": null, + "TotalAmount": 0, + "InvoiceNumber": null, + "InvoiceDescription": null, + "InvoiceReference": null, + "CurrencyCode": null + }, + "Errors": null + } + ) + end + + def failed_refund_response + %( + { + "AuthorisationCode": null, + "ResponseCode": null, + "ResponseMessage": null, + "TransactionID": null, + "TransactionStatus": false, + "Verification": null, + "Customer": { + "CardDetails": { + "Number": null, + "Name": null, + "ExpiryMonth": null, + "ExpiryYear": null, + "StartMonth": null, + "StartYear": null, + "IssueNumber": null + }, + "TokenCustomerID": null, + "Reference": null, + "Title": null, + "FirstName": "Jim", + "LastName": "Smith", + "CompanyName": "Widgets Inc", + "JobDescription": null, + "Street1": "1234 My Street", + "Street2": "Apt 1", + "City": "Ottawa", + "State": "ON", + "PostalCode": "K1C2N6", + "Country": "ca", + "Email": null, + "Phone": "(555)555-5555", + "Mobile": null, + "Comments": null, + "Fax": "(555)555-6666", + "Url": null + }, + "Refund": { + "TransactionID": null, + "TotalAmount": 0, + "InvoiceNumber": null, + "InvoiceDescription": null, + "InvoiceReference": null, + "CurrencyCode": null + }, + "Errors": "S5000" + } + ) + end + + def successful_store_purchase_response + %( + { + "AuthorisationCode": "232671", + "ResponseCode": "00", + "ResponseMessage": "A2000", + "TransactionID": 10440234, + "TransactionStatus": true, + "TransactionType": "MOTO", + "BeagleScore": 0, + "Verification": { + "CVN": 0, + "Address": 0, + "Email": 0, + "Mobile": 0, + "Phone": 0 + }, + "Customer": { + "CardDetails": { + "Number": "444433XXXXXX1111", + "Name": "Longbob Longsen", + "ExpiryMonth": "09", + "ExpiryYear": "14", + "StartMonth": "", + "StartYear": "", + "IssueNumber": "" + }, + "TokenCustomerID": 912056757740, + "Reference": "", + "Title": "Mr.", + "FirstName": "Jim", + "LastName": "Smith", + "CompanyName": "Widgets Inc", + "JobDescription": "", + "Street1": "1234 My Street, Apt 1", + "Street2": "", + "City": "Ottawa", + "State": "ON", + "PostalCode": "K1C2N6", + "Country": "ca", + "Email": "", + "Phone": "(555)555-5555", + "Mobile": "", + "Comments": "", + "Fax": "(555)555-6666", + "Url": "" + }, + "Payment": { + "TotalAmount": 100, + "InvoiceNumber": "", + "InvoiceDescription": "", + "InvoiceReference": "", + "CurrencyCode": "AUD" + }, + "Errors": null + } + ) + end + + def transcript + <<-TRANSCRIPT + "CardDetails":{"Name":"Longbob Longsen","Number":"4444333322221111","ExpiryMonth":"09","ExpiryYear":"2015","CVN":"123"}},"ShippingAddress" + \"CardDetails\":{\"Name\":\"Longbob Longsen\",\"Number\":\"4444333322221111\",\"ExpiryMonth\":\"09\",\"ExpiryYear\":\"2015\",\"CVN\":\"123\"}},\"ShippingAddress\" + {\"CardDetails\":{\"Number\":\"444433XXXXXX1111\",\"Name\":\"Longbob Longsen\",\"ExpiryMonth\":\"09\",\"ExpiryYear\":\"15\" + "Verification":{"CVN":0,"Address":0,"Email":0,"Mobile":0,"Phone":0},"Customer":{"CardDetails":{"Number":"444433XXXXXX1111","Name":"Longbob Longsen","ExpiryMonth":"09" + TRANSCRIPT + end + + def scrubbed_transcript + <<-SCRUBBED_TRANSCRIPT + "CardDetails":{"Name":"Longbob Longsen","Number":"[FILTERED]","ExpiryMonth":"09","ExpiryYear":"2015","CVN":"[FILTERED]"}},"ShippingAddress" + \"CardDetails\":{\"Name\":\"Longbob Longsen\",\"Number\":\"[FILTERED]\",\"ExpiryMonth\":\"09\",\"ExpiryYear\":\"2015\",\"CVN\":\"[FILTERED]\"}},\"ShippingAddress\" + {\"CardDetails\":{\"Number\":\"[FILTERED]\",\"Name\":\"Longbob Longsen\",\"ExpiryMonth\":\"09\",\"ExpiryYear\":\"15\" + "Verification":{"CVN":[FILTERED],"Address":0,"Email":0,"Mobile":0,"Phone":0},"Customer":{"CardDetails":{"Number":"[FILTERED]","Name":"Longbob Longsen","ExpiryMonth":"09" + SCRUBBED_TRANSCRIPT + end end diff --git a/test/unit/gateways/eway_test.rb b/test/unit/gateways/eway_test.rb index f498ef05421..5c1059163e5 100644 --- a/test/unit/gateways/eway_test.rb +++ b/test/unit/gateways/eway_test.rb @@ -6,6 +6,8 @@ def setup :login => '87654321' ) + @amount = 100 + @credit_card = credit_card('4646464646464646') @options = { @@ -33,7 +35,7 @@ def test_purchase_without_billing_address def test_successful_purchase @gateway.expects(:ssl_post).returns(successful_purchase_response) - assert response = @gateway.purchase(@amount, @credit_card, @options) + response = @gateway.purchase(@amount, @credit_card, @options) assert_instance_of Response, response assert_success response assert_equal '11292', response.authorization @@ -42,7 +44,7 @@ def test_successful_purchase def test_failed_purchase @gateway.expects(:ssl_post).returns(failed_purchase_response) - assert response = @gateway.purchase(@amount, @credit_card, @options) + response = @gateway.purchase(@amount, @credit_card, @options) assert_instance_of Response, response assert_failure response end @@ -52,7 +54,7 @@ def test_successful_refund assert purchase = @gateway.purchase(@amount, @credit_card, @options) @gateway.expects(:ssl_post).returns(successful_refund_response) - assert response = @gateway.refund(40, purchase.authorization) + response = @gateway.refund(40, purchase.authorization) assert_success response assert_equal '40', response.params['ewayreturnamount'] end @@ -62,17 +64,17 @@ def test_failed_refund purchase = @gateway.purchase(@amount, @credit_card, @options) @gateway.expects(:ssl_post).returns(failed_refund_response) - assert response = @gateway.refund(200, purchase.authorization) + response = @gateway.refund(200, purchase.authorization) assert_failure response assert_equal '200', response.params['ewayreturnamount'] end def test_amount_style - assert_equal '1034', @gateway.send(:amount, 1034) + assert_equal '1034', @gateway.send(:amount, 1034) - assert_raise(ArgumentError) do - @gateway.send(:amount, '10.34') - end + assert_raise(ArgumentError) do + @gateway.send(:amount, '10.34') + end end def test_ensure_does_not_respond_to_authorize @@ -90,7 +92,12 @@ def test_add_address assert_equal @options[:billing_address][:zip], post[:CustomerPostcode] end + def test_transcript_scrubbing + assert_equal scrubbed_transcript, @gateway.scrub(transcript) + end + private + def successful_purchase_response <<-XML <?xml version="1.0"?> @@ -154,4 +161,26 @@ def failed_refund_response </ewayResponse> XML end + + def transcript + <<-TRANSCRIPT + D, [2012-11-14T16:05:08.673367 #78717] DEBUG -- : <ewaygateway><ewayCardNumber>4444333322221111</ewayCardNumber><ewayCardExpiryMonth>09</ewayCardExpiryMonth><ewayCardExpiryYear>13</ewayCardExpiryYear><ewayCustomerFirstName>Longbob</ewayCustomerFirstName><ewayCustomerLastName>Longsen</ewayCustomerLastName><ewayCardHoldersName>Longbob Longsen</ewayCardHoldersName><ewayCVN>123</ewayCVN><ewayCustomerAddress>47 Bobway, Bobville, WA, AU</ewayCustomerAddress><ewayCustomerPostcode>2000</ewayCustomerPostcode><ewayCustomerEmail>bob@testbob.com</ewayCustomerEmail><ewayCustomerInvoiceRef>1230123</ewayCustomerInvoiceRef><ewayCustomerInvoiceDescription>purchased items</ewayCustomerInvoiceDescription><ewayTrxnNumber/><ewayOption1/><ewayOption2/><ewayOption3/><ewayTotalAmount>100</ewayTotalAmount><ewayCustomerID>87654321</ewayCustomerID></ewaygateway> + opening connection to www.eway.com.au... + <- "<ewaygateway><ewayCardNumber>4444333322221111</ewayCardNumber><ewayCardExpiryMonth>09</ewayCardExpiryMonth><ewayCardExpiryYear>13</ewayCardExpiryYear><ewayCustomerFirstName>Longbob</ewayCustomerFirstName><ewayCustomerLastName>Longsen</ewayCustomerLastName><ewayCardHoldersName>Longbob Longsen</ewayCardHoldersName><ewayCVN>123</ewayCVN><ewayCustomerAddress>47 Bobway, Bobville, WA, AU</ewayCustomerAddress><ewayCustomerPostcode>2000</ewayCustomerPostcode><ewayCustomerEmail>bob@testbob.com</ewayCustomerEmail><ewayCustomerInvoiceRef>1230123</ewayCustomerInvoiceRef><ewayCustomerInvoiceDescription>purchased items</ewayCustomerInvoiceDescription><ewayTrxnNumber/><ewayOption1/><ewayOption2/><ewayOption3/><ewayTotalAmount>100</ewayTotalAmount><ewayCustomerID>87654321</ewayCustomerID></ewaygateway>" + -> "<ewayResponse><ewayTrxnStatus>True</ewayTrxnStatus><ewayTrxnNumber>10584</ewayTrxnNumber><ewayTrxnReference/><ewayTrxnOption1/><ewayTrxnOption2/><ewayTrxnOption3/><ewayAuthCode>123456</ewayAuthCode><ewayReturnAmount>100</ewayReturnAmount><ewayTrxnError>00,Transaction Approved(Test CVN Gateway)</ewayTrxnError></ewayResponse>\r\n" + read 327 bytes + D, [2012-11-14T16:05:10.597502 #78717] DEBUG -- : <ewayResponse><ewayTrxnStatus>True</ewayTrxnStatus><ewayTrxnNumber>10584</ewayTrxnNumber><ewayTrxnReference/><ewayTrxnOption1/><ewayTrxnOption2/><ewayTrxnOption3/><ewayAuthCode>123456</ewayAuthCode><ewayReturnAmount>100</ewayReturnAmount><ewayTrxnError>00,Transaction Approved(Test CVN Gateway)</ewayTrxnError></ewayResponse> + TRANSCRIPT + end + + def scrubbed_transcript + <<-SCRUBBED_TRANSCRIPT + D, [2012-11-14T16:05:08.673367 #78717] DEBUG -- : <ewaygateway><ewayCardNumber>[FILTERED]</ewayCardNumber><ewayCardExpiryMonth>09</ewayCardExpiryMonth><ewayCardExpiryYear>13</ewayCardExpiryYear><ewayCustomerFirstName>Longbob</ewayCustomerFirstName><ewayCustomerLastName>Longsen</ewayCustomerLastName><ewayCardHoldersName>Longbob Longsen</ewayCardHoldersName><ewayCVN>[FILTERED]</ewayCVN><ewayCustomerAddress>47 Bobway, Bobville, WA, AU</ewayCustomerAddress><ewayCustomerPostcode>2000</ewayCustomerPostcode><ewayCustomerEmail>bob@testbob.com</ewayCustomerEmail><ewayCustomerInvoiceRef>1230123</ewayCustomerInvoiceRef><ewayCustomerInvoiceDescription>purchased items</ewayCustomerInvoiceDescription><ewayTrxnNumber/><ewayOption1/><ewayOption2/><ewayOption3/><ewayTotalAmount>100</ewayTotalAmount><ewayCustomerID>87654321</ewayCustomerID></ewaygateway> + opening connection to www.eway.com.au... + <- "<ewaygateway><ewayCardNumber>[FILTERED]</ewayCardNumber><ewayCardExpiryMonth>09</ewayCardExpiryMonth><ewayCardExpiryYear>13</ewayCardExpiryYear><ewayCustomerFirstName>Longbob</ewayCustomerFirstName><ewayCustomerLastName>Longsen</ewayCustomerLastName><ewayCardHoldersName>Longbob Longsen</ewayCardHoldersName><ewayCVN>[FILTERED]</ewayCVN><ewayCustomerAddress>47 Bobway, Bobville, WA, AU</ewayCustomerAddress><ewayCustomerPostcode>2000</ewayCustomerPostcode><ewayCustomerEmail>bob@testbob.com</ewayCustomerEmail><ewayCustomerInvoiceRef>1230123</ewayCustomerInvoiceRef><ewayCustomerInvoiceDescription>purchased items</ewayCustomerInvoiceDescription><ewayTrxnNumber/><ewayOption1/><ewayOption2/><ewayOption3/><ewayTotalAmount>100</ewayTotalAmount><ewayCustomerID>87654321</ewayCustomerID></ewaygateway>" + -> "<ewayResponse><ewayTrxnStatus>True</ewayTrxnStatus><ewayTrxnNumber>10584</ewayTrxnNumber><ewayTrxnReference/><ewayTrxnOption1/><ewayTrxnOption2/><ewayTrxnOption3/><ewayAuthCode>123456</ewayAuthCode><ewayReturnAmount>100</ewayReturnAmount><ewayTrxnError>00,Transaction Approved(Test CVN Gateway)</ewayTrxnError></ewayResponse>\r\n" + read 327 bytes + D, [2012-11-14T16:05:10.597502 #78717] DEBUG -- : <ewayResponse><ewayTrxnStatus>True</ewayTrxnStatus><ewayTrxnNumber>10584</ewayTrxnNumber><ewayTrxnReference/><ewayTrxnOption1/><ewayTrxnOption2/><ewayTrxnOption3/><ewayAuthCode>123456</ewayAuthCode><ewayReturnAmount>100</ewayReturnAmount><ewayTrxnError>00,Transaction Approved(Test CVN Gateway)</ewayTrxnError></ewayResponse> + SCRUBBED_TRANSCRIPT + end end diff --git a/test/unit/gateways/exact_test.rb b/test/unit/gateways/exact_test.rb index b4d5c7e0511..664f3d27ebf 100644 --- a/test/unit/gateways/exact_test.rb +++ b/test/unit/gateways/exact_test.rb @@ -2,18 +2,18 @@ class ExactTest < Test::Unit::TestCase def setup - @gateway = ExactGateway.new( :login => "A00427-01", - :password => "testus" ) + @gateway = ExactGateway.new(:login => 'A00427-01', + :password => 'testus') @credit_card = credit_card @amount = 100 - @options = { + @options = { :order_id => '1', :billing_address => address, :description => 'Store Purchase' } end - + def test_successful_purchase @gateway.expects(:ssl_post).returns(successful_purchase_response) assert response = @gateway.purchase(@amount, @credit_card, @options) @@ -21,40 +21,39 @@ def test_successful_purchase assert_equal 'ET1700;106625152', response.authorization assert response.test? assert_equal 'Transaction Normal - VER UNAVAILABLE', response.message - - ExactGateway::SENSITIVE_FIELDS.each{ |f| assert !response.params.has_key?(f.to_s) } + + ExactGateway::SENSITIVE_FIELDS.each { |f| assert !response.params.has_key?(f.to_s) } end - + def test_successful_refund @gateway.expects(:ssl_post).returns(successful_refund_response) - assert response = @gateway.refund(@amount, "123") + assert response = @gateway.refund(@amount, '123') assert_success response end def test_deprecated_credit @gateway.expects(:ssl_post).returns(successful_refund_response) - assert_deprecation_warning(Gateway::CREDIT_DEPRECATION_MESSAGE, @gateway) do - assert response = @gateway.credit(@amount, "123") + assert_deprecation_warning(Gateway::CREDIT_DEPRECATION_MESSAGE) do + assert response = @gateway.credit(@amount, '123') assert_success response end end - + def test_failed_purchase @gateway.expects(:ssl_post).returns(failed_purchase_response) - + assert response = @gateway.purchase(@amount, @credit_card, @options) assert_instance_of Response, response assert_failure response end - def test_expdate - assert_equal( "%02d%s" % [ @credit_card.month, - @credit_card.year.to_s[-2..-1] ], - @gateway.send(:expdate, @credit_card) ) + assert_equal('%02d%s' % [ @credit_card.month, + @credit_card.year.to_s[-2..-1] ], + @gateway.send(:expdate, @credit_card)) end - + def test_soap_fault @gateway.expects(:ssl_post).returns(soap_fault_response) assert response = @gateway.purchase(100, @credit_card, {}) @@ -62,92 +61,93 @@ def test_soap_fault assert response.test? assert_equal 'Unable to handle request without a valid action parameter. Please supply a valid soap action.', response.message end - + def test_supported_countries assert_equal ['CA', 'US'], ExactGateway.supported_countries end - + def test_supported_card_types assert_equal [:visa, :master, :american_express, :jcb, :discover], ExactGateway.supported_cardtypes end - + def test_avs_result @gateway.expects(:ssl_post).returns(successful_purchase_response) - + response = @gateway.purchase(@amount, @credit_card) assert_equal 'U', response.avs_result['code'] end - + def test_cvv_result @gateway.expects(:ssl_post).returns(successful_purchase_response) - + response = @gateway.purchase(@amount, @credit_card) assert_equal 'M', response.cvv_result['code'] end - - + private + def successful_purchase_response <<-RESPONSE -<?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:tns="http://secure2.e-xact.com/vplug-in/transaction/rpc-enc/" xmlns:types="http://secure2.e-xact.com/vplug-in/transaction/rpc-enc/encodedTypes" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><soap:Body soap:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><q1:SendAndCommitResponse xmlns:q1="http://secure2.e-xact.com/vplug-in/transaction/rpc-enc/Response"><SendAndCommitResult href="#id1" /></q1:SendAndCommitResponse><types:TransactionResult id="id1" xsi:type="types:TransactionResult"><ExactID xsi:type="xsd:string">A00427-01</ExactID><Password xsi:type="xsd:string">#######</Password><Transaction_Type xsi:type="xsd:string">00</Transaction_Type><DollarAmount xsi:type="xsd:string">1</DollarAmount><SurchargeAmount xsi:type="xsd:string">0</SurchargeAmount><Card_Number xsi:type="xsd:string">4242424242424242</Card_Number><Transaction_Tag xsi:type="xsd:string">106625152</Transaction_Tag><Authorization_Num xsi:type="xsd:string">ET1700</Authorization_Num><Expiry_Date xsi:type="xsd:string">0909</Expiry_Date><CardHoldersName xsi:type="xsd:string">Longbob Longsen</CardHoldersName><VerificationStr2 xsi:type="xsd:string">123</VerificationStr2><CVD_Presence_Ind xsi:type="xsd:string">1</CVD_Presence_Ind><Secure_AuthRequired xsi:type="xsd:string">0</Secure_AuthRequired><Secure_AuthResult xsi:type="xsd:string">0</Secure_AuthResult><Ecommerce_Flag xsi:type="xsd:string">0</Ecommerce_Flag><CAVV_Algorithm xsi:type="xsd:string">0</CAVV_Algorithm><Reference_No xsi:type="xsd:string">1</Reference_No><Reference_3 xsi:type="xsd:string">Store Purchase</Reference_3><Language xsi:type="xsd:string">0</Language><LogonMessage xsi:type="xsd:string">Processed by: -E-xact Transaction Gateway :- Version 8.4.0 B19b -Copyright 2006 -{34:2652}</LogonMessage><Error_Number xsi:type="xsd:string">0</Error_Number><Error_Description xsi:type="xsd:string" /><Transaction_Error xsi:type="xsd:boolean">false</Transaction_Error><Transaction_Approved xsi:type="xsd:boolean">true</Transaction_Approved><EXact_Resp_Code xsi:type="xsd:string">00</EXact_Resp_Code><EXact_Message xsi:type="xsd:string">Transaction Normal</EXact_Message><Bank_Resp_Code xsi:type="xsd:string">00</Bank_Resp_Code><Bank_Message xsi:type="xsd:string">VER UNAVAILABLE </Bank_Message><SequenceNo xsi:type="xsd:string">377</SequenceNo><AVS xsi:type="xsd:string">U</AVS><CVV2 xsi:type="xsd:string">M</CVV2><Retrieval_Ref_No xsi:type="xsd:string">200801181700</Retrieval_Ref_No><MerchantName xsi:type="xsd:string">E-xact ConnectionShop</MerchantName><MerchantAddress xsi:type="xsd:string">Suite 400 - 1152 Mainland St.</MerchantAddress><MerchantCity xsi:type="xsd:string">Vancouver</MerchantCity><MerchantProvince xsi:type="xsd:string">BC</MerchantProvince><MerchantCountry xsi:type="xsd:string">Canada</MerchantCountry><MerchantPostal xsi:type="xsd:string">V6B 4X2</MerchantPostal><MerchantURL xsi:type="xsd:string">www.e-xact.com</MerchantURL><CTR xsi:type="xsd:string">========== TRANSACTION RECORD ========= - -E-xact ConnectionShop -Suite 400 - 1152 Mainland St. -Vancouver, BC V6B 4X2 -www.e-xact.com - -TYPE: Purchase - -ACCT: Visa $1.00 USD - -CARD NUMBER : ############4242 -TRANS. REF. : 1 -CARD HOLDER : Longbob Longsen -EXPIRY DATE : xx/xx -DATE/TIME : 18 Jan 08 14:17:00 -REFERENCE # : 5999 377 M -AUTHOR.# : ET1700 - - Approved - Thank You 00 - -SIGNATURE - - - -_______________________________________ +<?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:tns="http://secure2.e-xact.com/vplug-in/transaction/rpc-enc/" xmlns:types="http://secure2.e-xact.com/vplug-in/transaction/rpc-enc/encodedTypes" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><soap:Body soap:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><q1:SendAndCommitResponse xmlns:q1="http://secure2.e-xact.com/vplug-in/transaction/rpc-enc/Response"><SendAndCommitResult href="#id1" /></q1:SendAndCommitResponse><types:TransactionResult id="id1" xsi:type="types:TransactionResult"><ExactID xsi:type="xsd:string">A00427-01</ExactID><Password xsi:type="xsd:string">#######</Password><Transaction_Type xsi:type="xsd:string">00</Transaction_Type><DollarAmount xsi:type="xsd:string">1</DollarAmount><SurchargeAmount xsi:type="xsd:string">0</SurchargeAmount><Card_Number xsi:type="xsd:string">4242424242424242</Card_Number><Transaction_Tag xsi:type="xsd:string">106625152</Transaction_Tag><Authorization_Num xsi:type="xsd:string">ET1700</Authorization_Num><Expiry_Date xsi:type="xsd:string">0909</Expiry_Date><CardHoldersName xsi:type="xsd:string">Longbob Longsen</CardHoldersName><VerificationStr2 xsi:type="xsd:string">123</VerificationStr2><CVD_Presence_Ind xsi:type="xsd:string">1</CVD_Presence_Ind><Secure_AuthRequired xsi:type="xsd:string">0</Secure_AuthRequired><Secure_AuthResult xsi:type="xsd:string">0</Secure_AuthResult><Ecommerce_Flag xsi:type="xsd:string">0</Ecommerce_Flag><CAVV_Algorithm xsi:type="xsd:string">0</CAVV_Algorithm><Reference_No xsi:type="xsd:string">1</Reference_No><Reference_3 xsi:type="xsd:string">Store Purchase</Reference_3><Language xsi:type="xsd:string">0</Language><LogonMessage xsi:type="xsd:string">Processed by: +E-xact Transaction Gateway :- Version 8.4.0 B19b +Copyright 2006 +{34:2652}</LogonMessage><Error_Number xsi:type="xsd:string">0</Error_Number><Error_Description xsi:type="xsd:string" /><Transaction_Error xsi:type="xsd:boolean">false</Transaction_Error><Transaction_Approved xsi:type="xsd:boolean">true</Transaction_Approved><EXact_Resp_Code xsi:type="xsd:string">00</EXact_Resp_Code><EXact_Message xsi:type="xsd:string">Transaction Normal</EXact_Message><Bank_Resp_Code xsi:type="xsd:string">00</Bank_Resp_Code><Bank_Message xsi:type="xsd:string">VER UNAVAILABLE </Bank_Message><SequenceNo xsi:type="xsd:string">377</SequenceNo><AVS xsi:type="xsd:string">U</AVS><CVV2 xsi:type="xsd:string">M</CVV2><Retrieval_Ref_No xsi:type="xsd:string">200801181700</Retrieval_Ref_No><MerchantName xsi:type="xsd:string">E-xact ConnectionShop</MerchantName><MerchantAddress xsi:type="xsd:string">Suite 400 - 1152 Mainland St.</MerchantAddress><MerchantCity xsi:type="xsd:string">Vancouver</MerchantCity><MerchantProvince xsi:type="xsd:string">BC</MerchantProvince><MerchantCountry xsi:type="xsd:string">Canada</MerchantCountry><MerchantPostal xsi:type="xsd:string">V6B 4X2</MerchantPostal><MerchantURL xsi:type="xsd:string">www.e-xact.com</MerchantURL><CTR xsi:type="xsd:string">========== TRANSACTION RECORD ========= + +E-xact ConnectionShop +Suite 400 - 1152 Mainland St. +Vancouver, BC V6B 4X2 +www.e-xact.com + +TYPE: Purchase + +ACCT: Visa $1.00 USD + +CARD NUMBER : ############4242 +TRANS. REF. : 1 +CARD HOLDER : Longbob Longsen +EXPIRY DATE : xx/xx +DATE/TIME : 18 Jan 08 14:17:00 +REFERENCE # : 5999 377 M +AUTHOR.# : ET1700 + + Approved - Thank You 00 + +SIGNATURE + + + +_______________________________________ </CTR></types:TransactionResult></soap:Body></soap:Envelope> RESPONSE end + def successful_refund_response <<-RESPONSE -<?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:tns="http://secure2.e-xact.com/vplug-in/transaction/rpc-enc/" xmlns:types="http://secure2.e-xact.com/vplug-in/transaction/rpc-enc/encodedTypes" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><soap:Body soap:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><q1:SendAndCommitResponse xmlns:q1="http://secure2.e-xact.com/vplug-in/transaction/rpc-enc/Response"><SendAndCommitResult href="#id1" /></q1:SendAndCommitResponse><types:TransactionResult id="id1" xsi:type="types:TransactionResult"><ExactID xsi:type="xsd:string">A00427-01</ExactID><Password xsi:type="xsd:string">#######</Password><Transaction_Type xsi:type="xsd:string">00</Transaction_Type><DollarAmount xsi:type="xsd:string">1</DollarAmount><SurchargeAmount xsi:type="xsd:string">0</SurchargeAmount><Card_Number xsi:type="xsd:string">4242424242424242</Card_Number><Transaction_Tag xsi:type="xsd:string">106625152</Transaction_Tag><Authorization_Num xsi:type="xsd:string">ET1700</Authorization_Num><Expiry_Date xsi:type="xsd:string">0909</Expiry_Date><CardHoldersName xsi:type="xsd:string">Longbob Longsen</CardHoldersName><VerificationStr2 xsi:type="xsd:string">123</VerificationStr2><CVD_Presence_Ind xsi:type="xsd:string">1</CVD_Presence_Ind><Secure_AuthRequired xsi:type="xsd:string">0</Secure_AuthRequired><Secure_AuthResult xsi:type="xsd:string">0</Secure_AuthResult><Ecommerce_Flag xsi:type="xsd:string">0</Ecommerce_Flag><CAVV_Algorithm xsi:type="xsd:string">0</CAVV_Algorithm><Reference_No xsi:type="xsd:string">1</Reference_No><Reference_3 xsi:type="xsd:string">Store Purchase</Reference_3><Language xsi:type="xsd:string">0</Language><LogonMessage xsi:type="xsd:string">Processed by: -E-xact Transaction Gateway :- Version 8.4.0 B19b -Copyright 2006 -{34:2652}</LogonMessage><Error_Number xsi:type="xsd:string">0</Error_Number><Error_Description xsi:type="xsd:string" /><Transaction_Error xsi:type="xsd:boolean">false</Transaction_Error><Transaction_Approved xsi:type="xsd:boolean">true</Transaction_Approved><EXact_Resp_Code xsi:type="xsd:string">00</EXact_Resp_Code><EXact_Message xsi:type="xsd:string">Transaction Normal</EXact_Message><Bank_Resp_Code xsi:type="xsd:string">00</Bank_Resp_Code><Bank_Message xsi:type="xsd:string">VER UNAVAILABLE </Bank_Message><SequenceNo xsi:type="xsd:string">377</SequenceNo><AVS xsi:type="xsd:string">U</AVS><CVV2 xsi:type="xsd:string">M</CVV2><Retrieval_Ref_No xsi:type="xsd:string">200801181700</Retrieval_Ref_No><MerchantName xsi:type="xsd:string">E-xact ConnectionShop</MerchantName><MerchantAddress xsi:type="xsd:string">Suite 400 - 1152 Mainland St.</MerchantAddress><MerchantCity xsi:type="xsd:string">Vancouver</MerchantCity><MerchantProvince xsi:type="xsd:string">BC</MerchantProvince><MerchantCountry xsi:type="xsd:string">Canada</MerchantCountry><MerchantPostal xsi:type="xsd:string">V6B 4X2</MerchantPostal><MerchantURL xsi:type="xsd:string">www.e-xact.com</MerchantURL><CTR xsi:type="xsd:string">========== TRANSACTION RECORD ========= - -E-xact ConnectionShop -Suite 400 - 1152 Mainland St. -Vancouver, BC V6B 4X2 -www.e-xact.com - -TYPE: Refund - -ACCT: Visa $1.00 USD - -CARD NUMBER : ############4242 -TRANS. REF. : 1 -CARD HOLDER : Longbob Longsen -EXPIRY DATE : xx/xx -DATE/TIME : 18 Jan 08 14:17:00 -REFERENCE # : 5999 377 M - - Approved - Thank You 00 - -SIGNATURE +<?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:tns="http://secure2.e-xact.com/vplug-in/transaction/rpc-enc/" xmlns:types="http://secure2.e-xact.com/vplug-in/transaction/rpc-enc/encodedTypes" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><soap:Body soap:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><q1:SendAndCommitResponse xmlns:q1="http://secure2.e-xact.com/vplug-in/transaction/rpc-enc/Response"><SendAndCommitResult href="#id1" /></q1:SendAndCommitResponse><types:TransactionResult id="id1" xsi:type="types:TransactionResult"><ExactID xsi:type="xsd:string">A00427-01</ExactID><Password xsi:type="xsd:string">#######</Password><Transaction_Type xsi:type="xsd:string">00</Transaction_Type><DollarAmount xsi:type="xsd:string">1</DollarAmount><SurchargeAmount xsi:type="xsd:string">0</SurchargeAmount><Card_Number xsi:type="xsd:string">4242424242424242</Card_Number><Transaction_Tag xsi:type="xsd:string">106625152</Transaction_Tag><Authorization_Num xsi:type="xsd:string">ET1700</Authorization_Num><Expiry_Date xsi:type="xsd:string">0909</Expiry_Date><CardHoldersName xsi:type="xsd:string">Longbob Longsen</CardHoldersName><VerificationStr2 xsi:type="xsd:string">123</VerificationStr2><CVD_Presence_Ind xsi:type="xsd:string">1</CVD_Presence_Ind><Secure_AuthRequired xsi:type="xsd:string">0</Secure_AuthRequired><Secure_AuthResult xsi:type="xsd:string">0</Secure_AuthResult><Ecommerce_Flag xsi:type="xsd:string">0</Ecommerce_Flag><CAVV_Algorithm xsi:type="xsd:string">0</CAVV_Algorithm><Reference_No xsi:type="xsd:string">1</Reference_No><Reference_3 xsi:type="xsd:string">Store Purchase</Reference_3><Language xsi:type="xsd:string">0</Language><LogonMessage xsi:type="xsd:string">Processed by: +E-xact Transaction Gateway :- Version 8.4.0 B19b +Copyright 2006 +{34:2652}</LogonMessage><Error_Number xsi:type="xsd:string">0</Error_Number><Error_Description xsi:type="xsd:string" /><Transaction_Error xsi:type="xsd:boolean">false</Transaction_Error><Transaction_Approved xsi:type="xsd:boolean">true</Transaction_Approved><EXact_Resp_Code xsi:type="xsd:string">00</EXact_Resp_Code><EXact_Message xsi:type="xsd:string">Transaction Normal</EXact_Message><Bank_Resp_Code xsi:type="xsd:string">00</Bank_Resp_Code><Bank_Message xsi:type="xsd:string">VER UNAVAILABLE </Bank_Message><SequenceNo xsi:type="xsd:string">377</SequenceNo><AVS xsi:type="xsd:string">U</AVS><CVV2 xsi:type="xsd:string">M</CVV2><Retrieval_Ref_No xsi:type="xsd:string">200801181700</Retrieval_Ref_No><MerchantName xsi:type="xsd:string">E-xact ConnectionShop</MerchantName><MerchantAddress xsi:type="xsd:string">Suite 400 - 1152 Mainland St.</MerchantAddress><MerchantCity xsi:type="xsd:string">Vancouver</MerchantCity><MerchantProvince xsi:type="xsd:string">BC</MerchantProvince><MerchantCountry xsi:type="xsd:string">Canada</MerchantCountry><MerchantPostal xsi:type="xsd:string">V6B 4X2</MerchantPostal><MerchantURL xsi:type="xsd:string">www.e-xact.com</MerchantURL><CTR xsi:type="xsd:string">========== TRANSACTION RECORD ========= + +E-xact ConnectionShop +Suite 400 - 1152 Mainland St. +Vancouver, BC V6B 4X2 +www.e-xact.com + +TYPE: Refund + +ACCT: Visa $1.00 USD + +CARD NUMBER : ############4242 +TRANS. REF. : 1 +CARD HOLDER : Longbob Longsen +EXPIRY DATE : xx/xx +DATE/TIME : 18 Jan 08 14:17:00 +REFERENCE # : 5999 377 M + + Approved - Thank You 00 + +SIGNATURE Please retain this copy for your records. @@ -157,35 +157,35 @@ def successful_refund_response </CTR></types:TransactionResult></soap:Body></soap:Envelope> RESPONSE end - + def failed_purchase_response <<-RESPONSE -<?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:tns="http://secure2.e-xact.com/vplug-in/transaction/rpc-enc/" xmlns:types="http://secure2.e-xact.com/vplug-in/transaction/rpc-enc/encodedTypes" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><soap:Body soap:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><q1:SendAndCommitResponse xmlns:q1="http://secure2.e-xact.com/vplug-in/transaction/rpc-enc/Response"><SendAndCommitResult href="#id1" /></q1:SendAndCommitResponse><types:TransactionResult id="id1" xsi:type="types:TransactionResult"><ExactID xsi:type="xsd:string">A00427-01</ExactID><Password xsi:type="xsd:string">#######</Password><Transaction_Type xsi:type="xsd:string">00</Transaction_Type><DollarAmount xsi:type="xsd:string">5013</DollarAmount><SurchargeAmount xsi:type="xsd:string">0</SurchargeAmount><Card_Number xsi:type="xsd:string">4111111111111111</Card_Number><Transaction_Tag xsi:type="xsd:string">106624668</Transaction_Tag><Expiry_Date xsi:type="xsd:string">0909</Expiry_Date><CardHoldersName xsi:type="xsd:string">Longbob Longsen</CardHoldersName><VerificationStr2 xsi:type="xsd:string">123</VerificationStr2><CVD_Presence_Ind xsi:type="xsd:string">1</CVD_Presence_Ind><Secure_AuthRequired xsi:type="xsd:string">0</Secure_AuthRequired><Secure_AuthResult xsi:type="xsd:string">0</Secure_AuthResult><Ecommerce_Flag xsi:type="xsd:string">0</Ecommerce_Flag><CAVV_Algorithm xsi:type="xsd:string">0</CAVV_Algorithm><Reference_No xsi:type="xsd:string">1</Reference_No><Reference_3 xsi:type="xsd:string">Store Purchase</Reference_3><Language xsi:type="xsd:string">0</Language><LogonMessage xsi:type="xsd:string">Processed by: -E-xact Transaction Gateway :- Version 8.4.0 B19b -Copyright 2006 -{34:2652}</LogonMessage><Error_Number xsi:type="xsd:string">0</Error_Number><Error_Description xsi:type="xsd:string" /><Transaction_Error xsi:type="xsd:boolean">false</Transaction_Error><Transaction_Approved xsi:type="xsd:boolean">false</Transaction_Approved><EXact_Resp_Code xsi:type="xsd:string">00</EXact_Resp_Code><EXact_Message xsi:type="xsd:string">Transaction Normal</EXact_Message><Bank_Resp_Code xsi:type="xsd:string">13</Bank_Resp_Code><Bank_Message xsi:type="xsd:string">AMOUNT ERR</Bank_Message><SequenceNo xsi:type="xsd:string">376</SequenceNo><AVS xsi:type="xsd:string">U</AVS><CVV2 xsi:type="xsd:string">M</CVV2><MerchantName xsi:type="xsd:string">E-xact ConnectionShop</MerchantName><MerchantAddress xsi:type="xsd:string">Suite 400 - 1152 Mainland St.</MerchantAddress><MerchantCity xsi:type="xsd:string">Vancouver</MerchantCity><MerchantProvince xsi:type="xsd:string">BC</MerchantProvince><MerchantCountry xsi:type="xsd:string">Canada</MerchantCountry><MerchantPostal xsi:type="xsd:string">V6B 4X2</MerchantPostal><MerchantURL xsi:type="xsd:string">www.e-xact.com</MerchantURL><CTR xsi:type="xsd:string">========== TRANSACTION RECORD ========= - -E-xact ConnectionShop -Suite 400 - 1152 Mainland St. -Vancouver, BC V6B 4X2 -www.e-xact.com - -TYPE: Purchase - -ACCT: Visa $5,013.00 USD - -CARD NUMBER : ############1111 -TRANS. REF. : 1 -CARD HOLDER : Longbob Longsen -EXPIRY DATE : xx/xx -DATE/TIME : 18 Jan 08 14:11:09 -REFERENCE # : 5999 376 M -AUTHOR.# : - - Transaction not Approved 13 - - -</CTR></types:TransactionResult></soap:Body></soap:Envelope> +<?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:tns="http://secure2.e-xact.com/vplug-in/transaction/rpc-enc/" xmlns:types="http://secure2.e-xact.com/vplug-in/transaction/rpc-enc/encodedTypes" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><soap:Body soap:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><q1:SendAndCommitResponse xmlns:q1="http://secure2.e-xact.com/vplug-in/transaction/rpc-enc/Response"><SendAndCommitResult href="#id1" /></q1:SendAndCommitResponse><types:TransactionResult id="id1" xsi:type="types:TransactionResult"><ExactID xsi:type="xsd:string">A00427-01</ExactID><Password xsi:type="xsd:string">#######</Password><Transaction_Type xsi:type="xsd:string">00</Transaction_Type><DollarAmount xsi:type="xsd:string">5013</DollarAmount><SurchargeAmount xsi:type="xsd:string">0</SurchargeAmount><Card_Number xsi:type="xsd:string">4111111111111111</Card_Number><Transaction_Tag xsi:type="xsd:string">106624668</Transaction_Tag><Expiry_Date xsi:type="xsd:string">0909</Expiry_Date><CardHoldersName xsi:type="xsd:string">Longbob Longsen</CardHoldersName><VerificationStr2 xsi:type="xsd:string">123</VerificationStr2><CVD_Presence_Ind xsi:type="xsd:string">1</CVD_Presence_Ind><Secure_AuthRequired xsi:type="xsd:string">0</Secure_AuthRequired><Secure_AuthResult xsi:type="xsd:string">0</Secure_AuthResult><Ecommerce_Flag xsi:type="xsd:string">0</Ecommerce_Flag><CAVV_Algorithm xsi:type="xsd:string">0</CAVV_Algorithm><Reference_No xsi:type="xsd:string">1</Reference_No><Reference_3 xsi:type="xsd:string">Store Purchase</Reference_3><Language xsi:type="xsd:string">0</Language><LogonMessage xsi:type="xsd:string">Processed by: +E-xact Transaction Gateway :- Version 8.4.0 B19b +Copyright 2006 +{34:2652}</LogonMessage><Error_Number xsi:type="xsd:string">0</Error_Number><Error_Description xsi:type="xsd:string" /><Transaction_Error xsi:type="xsd:boolean">false</Transaction_Error><Transaction_Approved xsi:type="xsd:boolean">false</Transaction_Approved><EXact_Resp_Code xsi:type="xsd:string">00</EXact_Resp_Code><EXact_Message xsi:type="xsd:string">Transaction Normal</EXact_Message><Bank_Resp_Code xsi:type="xsd:string">13</Bank_Resp_Code><Bank_Message xsi:type="xsd:string">AMOUNT ERR</Bank_Message><SequenceNo xsi:type="xsd:string">376</SequenceNo><AVS xsi:type="xsd:string">U</AVS><CVV2 xsi:type="xsd:string">M</CVV2><MerchantName xsi:type="xsd:string">E-xact ConnectionShop</MerchantName><MerchantAddress xsi:type="xsd:string">Suite 400 - 1152 Mainland St.</MerchantAddress><MerchantCity xsi:type="xsd:string">Vancouver</MerchantCity><MerchantProvince xsi:type="xsd:string">BC</MerchantProvince><MerchantCountry xsi:type="xsd:string">Canada</MerchantCountry><MerchantPostal xsi:type="xsd:string">V6B 4X2</MerchantPostal><MerchantURL xsi:type="xsd:string">www.e-xact.com</MerchantURL><CTR xsi:type="xsd:string">========== TRANSACTION RECORD ========= + +E-xact ConnectionShop +Suite 400 - 1152 Mainland St. +Vancouver, BC V6B 4X2 +www.e-xact.com + +TYPE: Purchase + +ACCT: Visa $5,013.00 USD + +CARD NUMBER : ############1111 +TRANS. REF. : 1 +CARD HOLDER : Longbob Longsen +EXPIRY DATE : xx/xx +DATE/TIME : 18 Jan 08 14:11:09 +REFERENCE # : 5999 376 M +AUTHOR.# : + + Transaction not Approved 13 + + +</CTR></types:TransactionResult></soap:Body></soap:Envelope> RESPONSE end diff --git a/test/unit/gateways/ezic_test.rb b/test/unit/gateways/ezic_test.rb new file mode 100644 index 00000000000..5fa29b4004b --- /dev/null +++ b/test/unit/gateways/ezic_test.rb @@ -0,0 +1,170 @@ +require 'test_helper' + +class EzicTest < Test::Unit::TestCase + include CommStub + + def setup + @gateway = EzicGateway.new(account_id: 'TheID') + @credit_card = credit_card + @amount = 100 + + @options = { + order_id: '1', + billing_address: address, + description: 'Store Purchase' + } + end + + def test_successful_purchase + @gateway.expects(:ssl_post).returns(successful_purchase_response) + + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal '120741089764', response.authorization + assert response.test? + assert_equal 'Street address and 9-digit postal code match.', response.avs_result['message'] + assert_equal 'CVV matches', response.cvv_result['message'] + end + + def test_failed_purchase + @gateway.expects(:ssl_post).returns(failed_purchase_response) + + response = @gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_equal 'TEST DECLINED', response.message + end + + def test_successful_authorize + @gateway.expects(:ssl_post).returns(successful_authorize_response) + + response = @gateway.authorize(@amount, @credit_card, @options) + assert_success response + assert_equal '120762306743', response.authorization + end + + def test_failed_authorize + @gateway.expects(:ssl_post).returns(failed_authorize_response) + + response = @gateway.authorize(@amount, @credit_card, @options) + assert_failure response + assert_equal 'TEST DECLINED', response.message + end + + def test_successful_capture + @gateway.expects(:ssl_post).returns(successful_capture_response) + + response = @gateway.capture(@amount, '123312') + assert_success response + assert_equal '120762306743', response.authorization + end + + def test_failed_capture + @gateway.expects(:raw_ssl_request).returns(failed_capture_response) + + response = @gateway.capture(@amount, '2131212') + assert_failure response + assert_equal '20105: Settlement amount cannot exceed authorized amount', response.message + end + + def test_successful_refund + @gateway.expects(:ssl_post).returns(successful_refund_response) + + response = @gateway.refund(@amount, '32432423', @options) + assert_success response + assert_equal '120421340652', response.authorization + end + + def test_failed_refund + @gateway.expects(:raw_ssl_request).returns(failed_refund_response) + + response = @gateway.refund(@amount, '5511231') + assert_failure response + assert_equal '20183: Amount of refunds exceed original sale', response.message + end + + def test_failed_void + @gateway.expects(:raw_ssl_request).returns(failed_void_response) + + response = @gateway.void('5511231') + assert_failure response + assert_equal 'Processor/Network Error', response.message + end + + def test_successful_verify + response = stub_comms(@gateway, :raw_ssl_request) do + @gateway.verify(@credit_card, @options) + end.respond_with(successful_authorize_raw_response, failed_void_response) + assert_success response + assert_equal '120762306743', response.authorization + end + + def test_failed_verify + response = stub_comms do + @gateway.verify(@credit_card, @options) + end.respond_with(failed_authorize_response, failed_void_response) + assert_failure response + assert_equal 'TEST DECLINED', response.message + end + + def test_scrub + assert @gateway.supports_scrubbing? + assert_equal @gateway.scrub(pre_scrubbed), post_scrubbed + end + + private + + def pre_scrubbed + <<-PRE_SCRUBBED + <- "account_id=120536457270&amount=1.00&description=Store+Purchase&pay_type=C&card_number=4000100011112224&card_cvv2=123&card_expire=0916&bill_name2=Smith&bill_name1=Jim&bill_street=1234+My+Street&bill_city=Ottawa&bill_state=ON&bill_zip=K1C2N6&bill_country=CA&cust_phone=%28555%29555-5555&tran_type=S" + -> "avs_code=X&cvv2_code=M&status_code=1&processor=TEST&auth_code=999999&settle_amount=1.00&settle_currency=USD&trans_id=120477042083&auth_msg=TEST+APPROVED&auth_date=2015-04-22+15:20:05" + PRE_SCRUBBED + end + + def post_scrubbed + <<-POST_SCRUBBED + <- "account_id=120536457270&amount=1.00&description=Store+Purchase&pay_type=C&card_number=[FILTERED]&card_cvv2=[FILTERED]&card_expire=0916&bill_name2=Smith&bill_name1=Jim&bill_street=1234+My+Street&bill_city=Ottawa&bill_state=ON&bill_zip=K1C2N6&bill_country=CA&cust_phone=%28555%29555-5555&tran_type=S" + -> "avs_code=X&cvv2_code=M&status_code=1&processor=TEST&auth_code=999999&settle_amount=1.00&settle_currency=USD&trans_id=120477042083&auth_msg=TEST+APPROVED&auth_date=2015-04-22+15:20:05" + POST_SCRUBBED + end + + def successful_purchase_response + 'avs_code=X&cvv2_code=M&status_code=1&processor=TEST&auth_code=999999&settle_amount=1.00&settle_currency=USD&trans_id=120741089764&auth_msg=TEST+APPROVED&auth_date=2015-04-23+15:27:28' + end + + def failed_purchase_response + 'avs_code=Y&cvv2_code=M&status_code=0&processor=TEST&settle_currency=USD&settle_amount=190.88&trans_id=120740287652&auth_msg=TEST+DECLINED&auth_date=2015-04-23+15:31:30' + end + + def successful_authorize_response + 'avs_code=X&cvv2_code=M&status_code=T&processor=TEST&auth_code=999999&settle_amount=1.00&settle_currency=USD&trans_id=120762306743&auth_msg=TEST+APPROVED&ticket_code=XXXXXXXXXXXXXXX&auth_date=2015-04-23+17:24:37' + end + + def failed_authorize_response + 'avs_code=Y&cvv2_code=M&status_code=0&processor=TEST&auth_code=999999&settle_currency=USD&settle_amount=190.88&trans_id=120761061862&auth_msg=TEST+DECLINED&ticket_code=XXXXXXXXXXXXXXX&auth_date=2015-04-23+17:25:35' + end + + def successful_capture_response + 'avs_code=X&cvv2_code=M&status_code=1&auth_code=999999&trans_id=120762306743&auth_msg=TEST+CAPTURED&ticket_code=XXXXXXXXXXXXXXX&auth_date=2015-04-23+17:24:37' + end + + def failed_capture_response + MockResponse.failed('', 611, '20105: Settlement amount cannot exceed authorized amount') + end + + def successful_refund_response + 'status_code=1&processor=TEST&auth_code=RRRRRR&settle_amount=-1.00&settle_currency=USD&trans_id=120421340652&auth_msg=TEST+RETURNED&auth_date=2015-04-23+18:26:02' + end + + def failed_refund_response + MockResponse.failed('', 611, '20183: Amount of refunds exceed original sale') + end + + def failed_void_response + MockResponse.failed('', 611, 'Processor/Network Error') + end + + def successful_authorize_raw_response + MockResponse.succeeded(successful_authorize_response) + end + +end diff --git a/test/unit/gateways/fat_zebra_test.rb b/test/unit/gateways/fat_zebra_test.rb index 5c7d968b200..2bd96c1c19f 100644 --- a/test/unit/gateways/fat_zebra_test.rb +++ b/test/unit/gateways/fat_zebra_test.rb @@ -1,6 +1,8 @@ require 'test_helper' class FatZebraTest < Test::Unit::TestCase + include CommStub + def setup @gateway = FatZebraGateway.new( :username => 'TEST', @@ -23,7 +25,19 @@ def test_successful_purchase assert response = @gateway.purchase(@amount, @credit_card, @options) assert_success response - assert_equal '001-P-12345AA', response.authorization + assert_equal '001-P-12345AA|purchases', response.authorization + assert response.test? + end + + def test_successful_purchase_with_metadata + @gateway.expects(:ssl_request).with { |method, url, body, headers| + body.match '"metadata":{"foo":"bar"}' + }.returns(successful_purchase_response_with_metadata) + + assert response = @gateway.purchase(@amount, @credit_card, @options.merge(:metadata => { 'foo' => 'bar' })) + assert_success response + + assert_equal '001-P-12345AA|purchases', response.authorization assert response.test? end @@ -32,10 +46,10 @@ def test_successful_purchase_with_token body.match '"card_token":"e1q7dbj2"' }.returns(successful_purchase_response) - assert response = @gateway.purchase(@amount, "e1q7dbj2", @options) + assert response = @gateway.purchase(@amount, 'e1q7dbj2', @options) assert_success response - assert_equal '001-P-12345AA', response.authorization + assert_equal '001-P-12345AA|purchases', response.authorization assert response.test? end @@ -44,10 +58,66 @@ def test_successful_purchase_with_token_string body.match '"card_token":"e1q7dbj2"' }.returns(successful_purchase_response) - assert response = @gateway.purchase(@amount, "e1q7dbj2", @options) + assert response = @gateway.purchase(@amount, 'e1q7dbj2', @options) + assert_success response + + assert_equal '001-P-12345AA|purchases', response.authorization + assert response.test? + end + + def test_successful_multi_currency_purchase + @gateway.expects(:ssl_request).with { |method, url, body, headers| + body.match '"currency":"USD"' + }.returns(successful_purchase_response) + + assert response = @gateway.purchase(@amount, 'e1q7dbj2', @options.merge(:currency => 'USD')) + assert_success response + + assert_equal '001-P-12345AA|purchases', response.authorization + assert response.test? + end + + def test_successful_purchase_with_recurring_flag + stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, @credit_card, @options.merge(recurring: true)) + end.check_request do |method, endpoint, data, headers| + assert_match(%r("extra":{"ecm":"32"}), data) + end.respond_with(successful_purchase_response) + end + + def test_successful_purchase_with_descriptor + @gateway.expects(:ssl_request).with { |method, url, body, headers| + json = JSON.parse(body) + json['extra']['name'] == 'Merchant' && json['extra']['location'] == 'Location' + }.returns(successful_purchase_response) + + assert response = @gateway.purchase(@amount, 'e1q7dbj2', @options.merge(:merchant => 'Merchant', :merchant_location => 'Location')) + assert_success response + + assert_equal '001-P-12345AA|purchases', response.authorization + assert response.test? + end + + def test_successful_authorization + @gateway.expects(:ssl_request).with { |method, url, body, headers| + body.match '"capture":false' + }.returns(successful_purchase_response) + + assert response = @gateway.authorize(@amount, 'e1q7dbj2', @options) assert_success response - assert_equal '001-P-12345AA', response.authorization + assert_equal '001-P-12345AA|purchases', response.authorization + assert response.test? + end + + def test_successful_capture + @gateway.expects(:ssl_request).with { |method, url, body, headers| + url =~ %r[purchases/e1q7dbj2/capture\z] + }.returns(successful_purchase_response) + + response = @gateway.capture(@amount, 'e1q7dbj2', @options) + assert_success response + assert_equal '001-P-12345AA|purchases', response.authorization assert response.test? end @@ -57,7 +127,7 @@ def test_unsuccessful_request assert response = @gateway.purchase(@amount, @credit_card, @options) assert_failure response assert response.test? - assert_match /Invalid Card Number/, response.message + assert_match %r{Invalid Card Number}, response.message end def test_declined_purchase @@ -66,14 +136,14 @@ def test_declined_purchase assert response = @gateway.purchase(@amount, @credit_card, @options) assert_failure response assert response.test? - assert_match /Card Declined/, response.message + assert_match %r{Card Declined}, response.message end def test_parse_error - @gateway.expects(:ssl_request).returns("{") # Some invalid JSON + @gateway.expects(:ssl_request).returns('{') # Some invalid JSON assert response = @gateway.purchase(@amount, @credit_card, @options) assert_failure response - assert_match /Invalid JSON response/, response.message + assert_match %r{Invalid JSON response}, response.message end def test_request_error @@ -81,7 +151,7 @@ def test_request_error assert response = @gateway.purchase(@amount, @credit_card, @options) assert_failure response - assert_match /Card Number is required/, response.message + assert_match %r{Card Number is required}, response.message end def test_successful_tokenization @@ -89,7 +159,7 @@ def test_successful_tokenization assert response = @gateway.store(@credit_card) assert_success response - assert_equal "e1q7dbj2", response.authorization + assert_equal 'e1q7dbj2|credit_cards', response.authorization end def test_unsuccessful_tokenization @@ -102,37 +172,143 @@ def test_unsuccessful_tokenization def test_successful_refund @gateway.expects(:ssl_request).returns(successful_refund_response) - assert response = @gateway.refund(100, "TEST", "Test refund") + assert response = @gateway.refund(100, 'TEST') assert_success response - assert_equal '003-R-7MNIUMY6', response.authorization + assert_equal '003-R-7MNIUMY6|refunds', response.authorization assert response.test? end def test_unsuccessful_refund @gateway.expects(:ssl_request).returns(unsuccessful_refund_response) - assert response = @gateway.refund(100, "TEST", "Test refund") + assert response = @gateway.refund(100, 'TEST') assert_failure response assert response.test? end + def test_scrub + assert @gateway.supports_scrubbing? + assert_equal @gateway.scrub(pre_scrubbed), post_scrubbed + end + private + def pre_scrubbed + <<-'PRE_SCRUBBED' +opening connection to gateway.sandbox.fatzebra.com.au:443... +opened +starting SSL for gateway.sandbox.fatzebra.com.au:443... +SSL established +<- "POST /v1.0/credit_cards HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAuthorization: Basic VEVTVDpURVNU\r\nUser-Agent: Fat Zebra v1.0/ActiveMerchant 1.56.0\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nConnection: close\r\nHost: gateway.sandbox.fatzebra.com.au\r\nContent-Length: 93\r\n\r\n" +<- "{\"card_number\":\"5123456789012346\",\"card_expiry\":\"5/2017\",\"cvv\":\"111\",\"card_holder\":\"Foo Bar\"}" +-> "HTTP/1.1 200 OK\r\n" +-> "Content-Type: application/json; charset=utf-8\r\n" +-> "Connection: close\r\n" +-> "Status: 200 OK\r\n" +-> "Cache-control: no-store\r\n" +-> "Pragma: no-cache\r\n" +-> "X-Request-Id: 3BA78272_F214_AC10001D_01BB_566A58EC_222F1D_49F4\r\n" +-> "X-Runtime: 0.142463\r\n" +-> "Date: Fri, 11 Dec 2015 05:02:36 GMT\r\n" +-> "X-Rack-Cache: invalidate, pass\r\n" +-> "X-Sandbox: true\r\n" +-> "X-Backend-Server: app-3\r\n" +-> "\r\n" +reading all... +-> "{\"successful\":true,\"response\":{\"token\":\"nkk9rhwu\",\"card_holder\":\"Foo Bar\",\"card_number\":\"512345XXXXXX2346\",\"card_expiry\":\"2017-05-31T23:59:59+10:00\",\"authorized\":true,\"transaction_count\":0},\"errors\":[],\"test\":true}" +read 214 bytes +Conn close + PRE_SCRUBBED + end + + def post_scrubbed + <<-'POST_SCRUBBED' +opening connection to gateway.sandbox.fatzebra.com.au:443... +opened +starting SSL for gateway.sandbox.fatzebra.com.au:443... +SSL established +<- "POST /v1.0/credit_cards HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAuthorization: Basic [FILTERED]\r\nUser-Agent: Fat Zebra v1.0/ActiveMerchant 1.56.0\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nConnection: close\r\nHost: gateway.sandbox.fatzebra.com.au\r\nContent-Length: 93\r\n\r\n" +<- "{\"card_number\":\"[FILTERED]\",\"card_expiry\":\"5/2017\",\"cvv\":\"[FILTERED]\",\"card_holder\":\"Foo Bar\"}" +-> "HTTP/1.1 200 OK\r\n" +-> "Content-Type: application/json; charset=utf-8\r\n" +-> "Connection: close\r\n" +-> "Status: 200 OK\r\n" +-> "Cache-control: no-store\r\n" +-> "Pragma: no-cache\r\n" +-> "X-Request-Id: 3BA78272_F214_AC10001D_01BB_566A58EC_222F1D_49F4\r\n" +-> "X-Runtime: 0.142463\r\n" +-> "Date: Fri, 11 Dec 2015 05:02:36 GMT\r\n" +-> "X-Rack-Cache: invalidate, pass\r\n" +-> "X-Sandbox: true\r\n" +-> "X-Backend-Server: app-3\r\n" +-> "\r\n" +reading all... +-> "{\"successful\":true,\"response\":{\"token\":\"nkk9rhwu\",\"card_holder\":\"Foo Bar\",\"card_number\":\"[FILTERED]\",\"card_expiry\":\"2017-05-31T23:59:59+10:00\",\"authorized\":true,\"transaction_count\":0},\"errors\":[],\"test\":true}" +read 214 bytes +Conn close + POST_SCRUBBED + end + # Place raw successful response from gateway here def successful_purchase_response { :successful => true, :response => { - :authorization => "55355", - :id => "001-P-12345AA", - :card_number => "XXXXXXXXXXXX1111", - :card_holder => "John Smith", - :card_expiry => "10/2011", - :card_token => "a1bhj98j", + :authorization => 55355, + :id => '001-P-12345AA', + :card_number => 'XXXXXXXXXXXX1111', + :card_holder => 'John Smith', + :card_expiry => '10/2011', + :card_token => 'a1bhj98j', :amount => 349, + :decimal_amount => 3.49, :successful => true, - :reference => "ABC123", - :message => "Approved", + :message => 'Approved', + :reference => 'ABC123', + :currency => 'AUD', + :transaction_id => '001-P-12345AA', + :settlement_date => '2011-07-01', + :transaction_date => '2011-07-01T12:00:00+11:00', + :response_code => '08', + :captured => true, + :captured_amount => 349, + :rrn => '000000000000', + :cvv_match => 'U', + :metadata => { + }, + }, + :test => true, + :errors => [] + }.to_json + end + + def successful_purchase_response_with_metadata + { + :successful => true, + :response => { + :authorization => 55355, + :id => '001-P-12345AA', + :card_number => 'XXXXXXXXXXXX1111', + :card_holder => 'John Smith', + :card_expiry => '2011-10-31', + :card_token => 'a1bhj98j', + :amount => 349, + :decimal_amount => 3.49, + :successful => true, + :message => 'Approved', + :reference => 'ABC123', + :currency => 'AUD', + :transaction_id => '001-P-12345AA', + :settlement_date => '2011-07-01', + :transaction_date => '2011-07-01T12:00:00+11:00', + :response_code => '08', + :captured => true, + :captured_amount => 349, + :rrn => '000000000000', + :cvv_match => 'U', + :metadata => { + 'foo' => 'bar', + }, }, :test => true, :errors => [] @@ -143,15 +319,28 @@ def declined_purchase_response { :successful => true, :response => { - :authorization_id => nil, - :id => nil, - :card_number => "XXXXXXXXXXXX1111", - :card_holder => "John Smith", - :card_expiry => "10/2011", + :authorization => 0, + :id => '001-P-12345AB', + :card_number => 'XXXXXXXXXXXX1111', + :card_holder => 'John Smith', + :card_expiry => '10/2011', :amount => 100, :authorized => false, - :reference => "ABC123", - :message => "Card Declined - check with issuer", + :reference => 'ABC123', + :decimal_amount => 1.0, + :successful => false, + :message => 'Card Declined - check with issuer', + :currency => 'AUD', + :transaction_id => '001-P-12345AB', + :settlement_date => nil, + :transaction_date => '2011-07-01T12:00:00+11:00', + :response_code => '01', + :captured => false, + :captured_amount => 0, + :rrn => '000000000001', + :cvv_match => 'U', + :metadata => { + } }, :test => true, :errors => [] @@ -162,20 +351,28 @@ def successful_refund_response { :successful => true, :response => { - :authorization => "1339973263", - :id => "003-R-7MNIUMY6", - :amount => -10, - :refunded => "Approved", - :message => "08 Approved", - :card_holder => "Harry Smith", - :card_number => "XXXXXXXXXXXX4444", - :card_expiry => "2013-05-31", - :card_type => "MasterCard", - :transaction_id => "003-R-7MNIUMY6", - :successful => true + :authorization => 1339973263, + :id => '003-R-7MNIUMY6', + :amount => 10, + :refunded => 'Approved', + :message => 'Approved', + :card_holder => 'Harry Smith', + :card_number => 'XXXXXXXXXXXX4444', + :card_expiry => '2013-05-31', + :card_type => 'MasterCard', + :transaction_id => '003-R-7MNIUMY6', + :reference => '18280', + :currency => 'USD', + :successful => true, + :transaction_date => '2013-07-01T12:00:00+11:00', + :response_code => '08', + :settlement_date => '2013-07-01', + :metadata => { + }, + :standalone => false, + :rrn => '000000000002', }, :errors => [ - ], :test => true }.to_json @@ -190,10 +387,10 @@ def unsuccessful_refund_response :amount => nil, :refunded => nil, :message => nil, - :card_holder => "Matthew Savage", - :card_number => "XXXXXXXXXXXX4444", - :card_expiry => "2013-05-31", - :card_type => "MasterCard", + :card_holder => 'Matthew Savage', + :card_number => 'XXXXXXXXXXXX4444', + :card_expiry => '2013-05-31', + :card_type => 'MasterCard', :transaction_id => nil, :successful => false }, @@ -208,10 +405,10 @@ def successful_tokenize_response { :successful => true, :response => { - :token => "e1q7dbj2", - :card_holder => "Bob Smith", - :card_number => "XXXXXXXXXXXX2346", - :card_expiry => "2013-05-31T23:59:59+10:00", + :token => 'e1q7dbj2', + :card_holder => 'Bob Smith', + :card_number => 'XXXXXXXXXXXX2346', + :card_expiry => '2013-05-31T23:59:59+10:00', :authorized => true, :transaction_count => 0 }, @@ -225,8 +422,8 @@ def failed_tokenize_response :successful => false, :response => { :token => nil, - :card_holder => "Bob ", - :card_number => "512345XXXXXX2346", + :card_holder => 'Bob ', + :card_number => '512345XXXXXX2346', :card_expiry => nil, :authorized => false, :transaction_count => 10 @@ -244,7 +441,7 @@ def failed_purchase_response :successful => false, :response => {}, :test => true, - :errors => ["Invalid Card Number"] + :errors => ['Invalid Card Number'] }.to_json end @@ -253,7 +450,7 @@ def missing_data_response :successful => false, :response => {}, :test => true, - :errors => ["Card Number is required"] + :errors => ['Card Number is required'] }.to_json end end diff --git a/test/unit/gateways/federated_canada_test.rb b/test/unit/gateways/federated_canada_test.rb index bd5c2415124..d3ca5956ae8 100644 --- a/test/unit/gateways/federated_canada_test.rb +++ b/test/unit/gateways/federated_canada_test.rb @@ -11,34 +11,33 @@ def setup @credit_card.verification_value = '999' @amount = 100 - @options = { + @options = { :order_id => '1', :billing_address => address, :description => 'Store Purchase' } end - + def test_successful_authorization @gateway.expects(:ssl_post).returns(successful_authorization_response) - options = {:billing_address => {:address1 => '888', :address2 => "apt 13", :country => 'CA', :state => 'SK', :city => "Big Beaver", :zip => "77777"}} + options = {:billing_address => {:address1 => '888', :address2 => 'apt 13', :country => 'CA', :state => 'SK', :city => 'Big Beaver', :zip => '77777'}} assert response = @gateway.authorize(@amount, @credit_card, options) assert_instance_of Response, response assert_success response assert_equal '1355694937', response.authorization assert_equal 'auth', response.params['type'] end - - + def test_successful_purchase @gateway.expects(:ssl_post).returns(successful_purchase_response) assert response = @gateway.purchase(@amount, @credit_card, @options) assert_instance_of Response, response assert_success response assert_equal '1346648416', response.authorization - assert_equal 'sale', response.params['type'] + assert_equal 'sale', response.params['type'] assert response.test? end - + def test_unsuccessful_request @gateway.expects(:ssl_post).returns(failed_purchase_response) assert response = @gateway.purchase(@amount, @credit_card, @options) @@ -48,21 +47,21 @@ def test_unsuccessful_request def test_add_address result = {} - @gateway.send(:add_address, result, :billing_address => {:address1 => '123 Happy Town Road', :address2 => "apt 13", :country => 'CA', :state => 'SK', :phone => '1234567890'} ) - assert_equal ["address1", "address2", "city", "company", "country", "phone", "state", "zip"], result.stringify_keys.keys.sort + @gateway.send(:add_address, result, :billing_address => {:address1 => '123 Happy Town Road', :address2 => 'apt 13', :country => 'CA', :state => 'SK', :phone => '1234567890'}) + assert_equal ['address1', 'address2', 'city', 'company', 'country', 'phone', 'state', 'zip'], result.stringify_keys.keys.sort assert_equal 'SK', result[:state] assert_equal '123 Happy Town Road', result[:address1] - assert_equal 'apt 13', result[:address2] + assert_equal 'apt 13', result[:address2] assert_equal 'CA', result[:country] end def test_add_invoice result = {} - @gateway.send(:add_invoice, result, :order_id => '#1001', :description => "This is a great order") + @gateway.send(:add_invoice, result, :order_id => '#1001', :description => 'This is a great order') assert_equal '#1001', result[:orderid] assert_equal 'This is a great order', result[:orderdescription] end - + def test_purchase_is_valid_csv params = {:amount => @amount} @gateway.send(:add_creditcard, params, @credit_card) @@ -70,8 +69,7 @@ def test_purchase_is_valid_csv assert data = @gateway.send(:post_data, 'auth', params) assert_equal post_data_fixture.size, data.size end - - + def test_purchase_meets_minimum_requirements params = {:amount => @amount} @gateway.send(:add_creditcard, params, @credit_card) @@ -80,10 +78,10 @@ def test_purchase_meets_minimum_requirements assert_not_nil(data.include?(key)) end end - + def test_expdate_formatting - assert_equal '0909', @gateway.send(:expdate, credit_card('4111111111111111', :month => "9", :year => "2009")) - assert_equal '0711', @gateway.send(:expdate, credit_card('4111111111111111', :month => "7", :year => "2011")) + assert_equal '0909', @gateway.send(:expdate, credit_card('4111111111111111', :month => '9', :year => '2009')) + assert_equal '0711', @gateway.send(:expdate, credit_card('4111111111111111', :month => '7', :year => '2011')) end def test_supported_countries @@ -105,7 +103,7 @@ def test_cvv_result response = @gateway.purchase(@amount, @credit_card) assert_equal 'M', response.cvv_result['code'] end - + def test_amount assert_equal '1.00', @gateway.send(:amount, 100) assert_equal '10.00', @gateway.send(:amount, 1000) @@ -113,29 +111,29 @@ def test_amount @gateway.send(:amount, '10.00') end end - + private - + def post_data_fixture - "password=password&type=auth&ccnumber=4111111111111111&username=demo&ccexp=1111&amount=100&cvv=999" + 'password=password&type=auth&ccnumber=4111111111111111&username=demo&ccexp=1111&amount=100&cvv=999' end - + def minimum_requirements %w{type username password amount ccnumber ccexp} end - + # Raw successful authorization response def successful_authorization_response - "response=1&responsetext=SUCCESS&authcode=123456&transactionid=1355694937&avsresponse=Y&cvvresponse=M&orderid=&type=auth&response_code=100" + 'response=1&responsetext=SUCCESS&authcode=123456&transactionid=1355694937&avsresponse=Y&cvvresponse=M&orderid=&type=auth&response_code=100' end # Raw successful purchase response def successful_purchase_response - "response=1&responsetext=SUCCESS&authcode=123456&transactionid=1346648416&avsresponse=N&cvvresponse=N&orderid=&type=sale&response_code=100" + 'response=1&responsetext=SUCCESS&authcode=123456&transactionid=1346648416&avsresponse=N&cvvresponse=N&orderid=&type=sale&response_code=100' end - + # Raw failed sale response def failed_purchase_response - "response=2&responsetext=DECLINE&authcode=&transactionid=1346648595&avsresponse=N&cvvresponse=N&orderid=&type=sale&response_code=200" + 'response=2&responsetext=DECLINE&authcode=&transactionid=1346648595&avsresponse=N&cvvresponse=N&orderid=&type=sale&response_code=200' end end diff --git a/test/unit/gateways/finansbank_test.rb b/test/unit/gateways/finansbank_test.rb index dbd8ac1391c..9cb280aac30 100644 --- a/test/unit/gateways/finansbank_test.rb +++ b/test/unit/gateways/finansbank_test.rb @@ -1,11 +1,10 @@ +# encoding: utf-8 + require 'test_helper' class FinansbankTest < Test::Unit::TestCase def setup - if RUBY_VERSION < '1.9' && $KCODE == "NONE" - @original_kcode = $KCODE - $KCODE = 'u' - end + @original_kcode = nil @gateway = FinansbankGateway.new( :login => 'login', @@ -33,8 +32,6 @@ def test_successful_purchase assert response = @gateway.purchase(@amount, @credit_card, @options) assert_instance_of Response, response assert_success response - - # Replace with authorization number from the successful response assert_equal '1', response.authorization assert response.test? end @@ -45,8 +42,6 @@ def test_successful_authorize_capture assert response = @gateway.purchase(@amount, @credit_card, @options) assert_instance_of Response, response assert_success response - - # Replace with authorization number from the successful response assert_equal '1', response.authorization @gateway.expects(:ssl_post).returns(successful_capture_response) @@ -76,6 +71,54 @@ def test_unsuccessful_request assert response.test? end + def test_successful_void + @gateway.expects(:ssl_post).returns(successful_void_response) + + assert response = @gateway.void(@options[:order_id]) + assert_success response + assert response.test? + end + + def test_failed_void + @gateway.expects(:ssl_post).returns(failed_void_response) + + assert response = @gateway.void(@options[:order_id]) + assert_failure response + assert response.test? + end + + def test_successful_refund + @gateway.expects(:ssl_post).returns(success_refund_response) + + assert response = @gateway.refund(5 * 100, @options[:order_id]) + assert_success response + assert response.test? + end + + def test_failed_refund + @gateway.expects(:ssl_post).returns(failed_refund_response) + + assert response = @gateway.refund(5 * 100, @options[:order_id]) + assert_failure response + assert response.test? + end + + def test_successful_credit + @gateway.expects(:ssl_post).returns(success_credit_response) + + assert response = @gateway.credit(5 * 100, @credit_card) + assert_success response + assert response.test? + end + + def test_failed_credit + @gateway.expects(:ssl_post).returns(failed_credit_response) + + assert response = @gateway.credit(5 * 100, @credit_card) + assert_failure response + assert response.test? + end + private def successful_purchase_response @@ -170,6 +213,136 @@ def capture_without_authorize_response <ERRORCODE>CORE-2115</ERRORCODE> <NUMCODE>992115</NUMCODE> </Extra> +</CC5Response> + EOF + end + + def successful_void_response + <<-EOF +<CC5Response> + <OrderId>1</OrderId> + <GroupId>1</GroupId> + <Response>Approved</Response> + <AuthCode>794573</AuthCode> + <HostRefNum>402310197597</HostRefNum> + <ProcReturnCode>00</ProcReturnCode> + <TransId>14023KVGD18549</TransId> + <ErrMsg></ErrMsg> + <Extra> + <SETTLEID>1363</SETTLEID> + <TRXDATE>20140123 10:21:05</TRXDATE> + <ERRORCODE></ERRORCODE> + <NUMCODE>00</NUMCODE> + </Extra> +</CC5Response> + EOF + end + + def failed_void_response + <<-EOF +<CC5Response> + <OrderId></OrderId> + <GroupId></GroupId> + <Response>Error</Response> + <AuthCode></AuthCode> + <HostRefNum></HostRefNum> + <ProcReturnCode>99</ProcReturnCode> + <TransId>14023KvNI18702</TransId> + <ErrMsg>İptal edilmeye uygun satış işlemi bulunamadı.</ErrMsg> + <Extra> + <SETTLEID></SETTLEID> + <TRXDATE>20140123 10:47:13</TRXDATE> + <ERRORCODE>CORE-2008</ERRORCODE> + <NUMCODE>992008</NUMCODE> + </Extra> +</CC5Response> + EOF + end + + def success_refund_response + <<-EOF +<CC5Response> + <OrderId>1</OrderId> + <GroupId>1</GroupId> + <Response>Approved</Response> + <AuthCode>811778</AuthCode> + <HostRefNum>402410197809</HostRefNum> + <ProcReturnCode>00</ProcReturnCode> + <TransId>14024KACE13836</TransId> + <ErrMsg></ErrMsg> + <Extra> + <SETTLEID>1364</SETTLEID> + <TRXDATE>20140124 10:00:02</TRXDATE> + <ERRORCODE></ERRORCODE> + <PARAPUANTRL>000000001634</PARAPUANTRL> + <PARAPUAN>000000001634</PARAPUAN> + <NUMCODE>00</NUMCODE> + <CAVVRESULTCODE>3</CAVVRESULTCODE> + </Extra> +</CC5Response> + EOF + end + + def failed_refund_response + <<-EOF +<CC5Response> + <OrderId></OrderId> + <GroupId></GroupId> + <Response>Error</Response> + <AuthCode></AuthCode> + <HostRefNum></HostRefNum> + <ProcReturnCode>99</ProcReturnCode> + <TransId>14024KEwH13882</TransId> + <ErrMsg>Iade yapilamaz, siparis gunsonuna girmemis.</ErrMsg> + <Extra> + <SETTLEID></SETTLEID> + <TRXDATE>20140124 10:04:48</TRXDATE> + <ERRORCODE>CORE-2508</ERRORCODE> + <NUMCODE>992508</NUMCODE> + </Extra> +</CC5Response> + EOF + end + + def success_credit_response + <<-EOF +<CC5Response> + <OrderId>ORDER-14024KUGB13953</OrderId> + <GroupId>ORDER-14024KUGB13953</GroupId> + <Response>Approved</Response> + <AuthCode>718160</AuthCode> + <HostRefNum>402410197818</HostRefNum> + <ProcReturnCode>00</ProcReturnCode> + <TransId>14024KUGD13955</TransId> + <ErrMsg></ErrMsg> + <Extra> + <SETTLEID>1364</SETTLEID> + <TRXDATE>20140124 10:20:06</TRXDATE> + <ERRORCODE></ERRORCODE> + <NUMCODE>00</NUMCODE> + <CAVVRESULTCODE>3</CAVVRESULTCODE> + </Extra> +</CC5Response> + EOF + end + + def failed_credit_response + <<-EOF +<CC5Response> + <OrderId></OrderId> + <GroupId></GroupId> + <Response>Error</Response> + <AuthCode></AuthCode> + <HostRefNum></HostRefNum> + <ProcReturnCode>99</ProcReturnCode> + <TransId>14024KUtG13966</TransId> + <ErrMsg>Kredi karti numarasi gecerli formatta degil.</ErrMsg> + <Extra> + <SETTLEID></SETTLEID> + <TRXDATE>20140124 10:20:45</TRXDATE> + <ERRORCODE>CORE-2012</ERRORCODE> + <NUMCODE>992012</NUMCODE> + </Extra> </CC5Response> EOF end diff --git a/test/unit/gateways/first_giving_test.rb b/test/unit/gateways/first_giving_test.rb new file mode 100644 index 00000000000..1e9b7f0345b --- /dev/null +++ b/test/unit/gateways/first_giving_test.rb @@ -0,0 +1,104 @@ +require 'test_helper' + +class FirstGivingTest < Test::Unit::TestCase + def setup + @gateway = FirstGivingGateway.new( + application_key: 'application_key', + security_token: 'security_token', + charity_id: 'charity_id' + ) + + @credit_card = credit_card + @amount = 100 + + @options = {} + end + + def test_successful_purchase + @gateway.expects(:ssl_post).returns(successful_purchase_response) + + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + + assert_equal 'a-c71f5e0a25f96e48a3dc54', response.authorization + assert_equal 'Success', response.message + end + + def test_unsuccessful_purchase + @gateway.expects(:ssl_post).returns(failed_purchase_response) + + response = @gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_equal 'Unfortunately, we were unable to perform credit card number validation. The credit card number validator responded with the following message ccNumber failed data validation for the following reasons : creditcardChecksum: 4457010000000000 seems to contain an invalid checksum.', response.message + end + + def test_successful_refund + @gateway.expects(:ssl_get).returns(successful_refund_response) + + response = @gateway.refund(@amount, @options) + assert_success response + assert_equal 'a-a09bf64559e5824eb925f5', response.authorization + end + + def test_unsuccessful_refund + @gateway.expects(:ssl_get).returns(failed_refund_response) + + response = @gateway.refund(@amount, @options) + assert_failure response + assert_equal 'Bad JG_APPLICATIONKEY and JG_SECURITYTOKEN.', response.message + end + + private + + # Place raw successful response from gateway here + def successful_purchase_response + %( + <?xml version="1.0" encoding="utf-8"?> + <firstGivingDonationApi xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <firstGivingResponse acknowledgement="Success"> + <transactionId>a-c71f5e0a25f96e48a3dc54</transactionId> + <donationId>0</donationId> + </firstGivingResponse> + </firstGivingDonationApi> + ) + end + + # Place raw failed response from gateway here + def failed_purchase_response + %( + <?xml version="1.0" encoding="utf-8"?> + <firstGivingDonationApi xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <firstGivingResponse + acknowledgement="Failed" + friendlyErrorMessage="Unfortunately, we were unable to perform credit card number validation. The credit card number validator responded with the following message ccNumber failed data validation for the following reasons : creditcardChecksum: 4457010000000000 seems to contain an invalid checksum." + verboseErrorMessage="ccNumber failed data validation for the following reasons : creditcardChecksum: 4457010000000000 seems to contain an invalid checksum" + errorTarget="ccNumber" /> + </firstGivingDonationApi> + ) + end + + # TODO: Place raw successful response from gateway here + def successful_refund_response + %( + <?xml version="1.0" encoding="utf-8"?> + <firstGivingDonationApi xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <firstGivingResponse acknowledgement="Success"> + <transactionId>a-a09bf64559e5824eb925f5</transactionId> + <donationId>0</donationId> + </firstGivingResponse> + </firstGivingDonationApi> + ) + end + + # TODO: Place raw failed response from gateway here + def failed_refund_response + %( + <?xml version="1.0" encoding="utf-8"?> + <firstGivingDonationApi xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <firstGivingResponse + acknowledgement="Failed" + verboseErrorMessage="Bad JG_APPLICATIONKEY and JG_SECURITYTOKEN." /> + </firstGivingDonationApi> + ) + end +end diff --git a/test/unit/gateways/first_pay_test.rb b/test/unit/gateways/first_pay_test.rb index 35030bceb1b..b761e884938 100644 --- a/test/unit/gateways/first_pay_test.rb +++ b/test/unit/gateways/first_pay_test.rb @@ -1,131 +1,441 @@ require 'test_helper' class FirstPayTest < Test::Unit::TestCase + include CommStub + def setup - @gateway = FirstPayGateway.new(:login => 'login', :password => 'password') - + @gateway = FirstPayGateway.new( + transaction_center_id: 1234, + gateway_id: 'a91c38c3-7d7f-4d29-acc7-927b4dca0dbe' + ) + @credit_card = credit_card @amount = 100 - - @options = { - :order_id => '1', - :billing_address => address, - :description => 'Store Purchase', - :ip => '127.0.0.1', - :email => 'test@test.com' + + @options = { + order_id: SecureRandom.hex(24), + billing_address: address } end - + def test_successful_purchase - @gateway.expects(:ssl_post).returns(successful_purchase_response) - - assert response = @gateway.purchase(@amount, @credit_card, @options) + response = stub_comms do + @gateway.purchase(@amount, @credit_card, @options) + end.check_request do |endpoint, data, headers| + assert_match(/<FIELD KEY="transaction_center_id">1234<\/FIELD>/, data) + assert_match(/<FIELD KEY="gateway_id">a91c38c3-7d7f-4d29-acc7-927b4dca0dbe<\/FIELD>/, data) + assert_match(/<FIELD KEY="operation_type">sale<\/FIELD>/, data) + assert_match(/<FIELD KEY="order_id">#{@options[:order_id]}<\/FIELD>/, data) + assert_match(/<FIELD KEY="total">1.00<\/FIELD>/, data) + assert_match(/<FIELD KEY="card_name">#{@credit_card.brand}<\/FIELD>/, data) + assert_match(/<FIELD KEY="card_number">#{@credit_card.number}<\/FIELD>/, data) + assert_match(/<FIELD KEY="card_exp">#{@gateway.expdate(@credit_card)}<\/FIELD>/, data) + assert_match(/<FIELD KEY="cvv2">#{@credit_card.verification_value}<\/FIELD>/, data) + assert_match(/<FIELD KEY="owner_name">#{@options[:billing_address][:name]}<\/FIELD>/, data) + assert_match(/<FIELD KEY="owner_street">#{@options[:billing_address][:address1]}<\/FIELD>/, data) + assert_match(/<FIELD KEY="owner_street2">#{@options[:billing_address][:address2]}<\/FIELD>/, data) + assert_match(/<FIELD KEY="owner_city">#{@options[:billing_address][:city]}<\/FIELD>/, data) + assert_match(/<FIELD KEY="owner_state">#{@options[:billing_address][:state]}<\/FIELD>/, data) + assert_match(/<FIELD KEY="owner_zip">#{@options[:billing_address][:zip]}<\/FIELD>/, data) + assert_match(/<FIELD KEY="owner_country">#{@options[:billing_address][:country]}<\/FIELD>/, data) + assert_match(/<FIELD KEY="owner_phone">/, data) # The () in phone num seems to break this? + end.respond_with(successful_purchase_response) + + assert response + assert_instance_of Response, response assert_success response - - # Replace with authorization number from the successful response - assert_equal '57097598', response.authorization - assert response.test? + assert_equal '47913', response.authorization + assert_equal 'Approved', response.message end def test_failed_purchase - @gateway.expects(:ssl_post).returns(failed_purchase_response) - - assert response = @gateway.purchase(@amount, @credit_card, @options) + @gateway.stubs(:ssl_post).returns(failed_purchase_response) + response = @gateway.purchase(@amount, @credit_card, @options) + + assert response + assert_instance_of Response, response + assert_failure response + assert_equal '47915', response.authorization + assert_equal 'Declined', response.message + end + + def test_successful_authorize + response = stub_comms do + @gateway.authorize(@amount, @credit_card, @options) + end.check_request do |endpoint, data, headers| + assert_match(/<FIELD KEY="transaction_center_id">1234<\/FIELD>/, data) + assert_match(/<FIELD KEY="gateway_id">a91c38c3-7d7f-4d29-acc7-927b4dca0dbe<\/FIELD>/, data) + assert_match(/<FIELD KEY="operation_type">auth<\/FIELD>/, data) + assert_match(/<FIELD KEY="order_id">#{@options[:order_id]}<\/FIELD>/, data) + assert_match(/<FIELD KEY="total">1.00<\/FIELD>/, data) + assert_match(/<FIELD KEY="card_name">#{@credit_card.brand}<\/FIELD>/, data) + assert_match(/<FIELD KEY="card_number">#{@credit_card.number}<\/FIELD>/, data) + assert_match(/<FIELD KEY="card_exp">#{@gateway.expdate(@credit_card)}<\/FIELD>/, data) + assert_match(/<FIELD KEY="cvv2">#{@credit_card.verification_value}<\/FIELD>/, data) + assert_match(/<FIELD KEY="owner_name">#{@options[:billing_address][:name]}<\/FIELD>/, data) + assert_match(/<FIELD KEY="owner_street">#{@options[:billing_address][:address1]}<\/FIELD>/, data) + assert_match(/<FIELD KEY="owner_street2">#{@options[:billing_address][:address2]}<\/FIELD>/, data) + assert_match(/<FIELD KEY="owner_city">#{@options[:billing_address][:city]}<\/FIELD>/, data) + assert_match(/<FIELD KEY="owner_state">#{@options[:billing_address][:state]}<\/FIELD>/, data) + assert_match(/<FIELD KEY="owner_zip">#{@options[:billing_address][:zip]}<\/FIELD>/, data) + assert_match(/<FIELD KEY="owner_country">#{@options[:billing_address][:country]}<\/FIELD>/, data) + assert_match(/<FIELD KEY="owner_phone">/, data) # The () in phone num seems to break this? + end.respond_with(successful_authorize_response) + + assert response + assert_instance_of Response, response + assert_success response + assert_equal '47920', response.authorization + assert_equal 'Approved', response.message + end + + def test_failed_authorize + @gateway.stubs(:ssl_post).returns(failed_authorize_response) + response = @gateway.authorize(@amount, @credit_card, @options) + + assert_failure response + assert_equal '47924', response.authorization + assert_equal 'Declined', response.message + end + + def test_successful_capture + response = stub_comms do + @gateway.capture(@amount, '47920') + end.check_request do |endpoint, data, headers| + assert_match(/<FIELD KEY="transaction_center_id">1234<\/FIELD>/, data) + assert_match(/<FIELD KEY="gateway_id">a91c38c3-7d7f-4d29-acc7-927b4dca0dbe<\/FIELD>/, data) + assert_match(/<FIELD KEY="operation_type">settle<\/FIELD>/, data) + assert_match(/<FIELD KEY="total_number_transactions">1<\/FIELD>/, data) + assert_match(/<FIELD KEY="reference_number1">47920<\/FIELD>/, data) + assert_match(/<FIELD KEY="settle_amount1">1.00<\/FIELD>/, data) + end.respond_with(successful_capture_response) + + assert response + assert_instance_of Response, response + assert_success response + assert_equal '47920', response.authorization + assert_equal 'Approved', response.message + end + + def test_failed_capture + @gateway.stubs(:ssl_post).returns(failed_capture_response) + response = @gateway.capture(@amount, '47920') + assert_failure response - assert_equal('DECLINE', response.message) - assert response.test? - end - - def test_error_on_purchase - @gateway.expects(:ssl_post).returns(error_response) - - assert response = @gateway.purchase(@amount, @credit_card, @options) - assert ! response.success? - assert_equal('704-MISSING BASIC DATA TYPE:card, exp, zip, addr, member, amount', response.message) - assert response.test? - end - + assert_equal '47920', response.authorization + assert response.message.include?('Settle failed') + end + def test_successful_refund - @gateway.expects(:ssl_post).returns(successful_refund_response) - @options[:credit_card] = @credit_card - - assert_success(response = @gateway.refund(@amount, '123456', @options)) - - assert_equal '53147613', response.authorization - end - + response = stub_comms do + @gateway.refund(@amount, '47925') + end.check_request do |endpoint, data, headers| + assert_match(/<FIELD KEY="transaction_center_id">1234<\/FIELD>/, data) + assert_match(/<FIELD KEY="gateway_id">a91c38c3-7d7f-4d29-acc7-927b4dca0dbe<\/FIELD>/, data) + assert_match(/<FIELD KEY="operation_type">credit<\/FIELD>/, data) + assert_match(/<FIELD KEY="total_number_transactions">1<\/FIELD>/, data) + assert_match(/<FIELD KEY="reference_number1">47925<\/FIELD>/, data) + assert_match(/<FIELD KEY="credit_amount1">1.00<\/FIELD>/, data) + end.respond_with(successful_refund_response) + + assert response + assert_instance_of Response, response + assert_success response + assert_equal '47925', response.authorization + assert_equal 'Accepted', response.message + end + def test_failed_refund - @options[:credit_card] = @credit_card - @gateway.expects(:ssl_post).returns(failed_refund_response) - - assert response = @gateway.refund(@amount, '123456', @options) + @gateway.stubs(:ssl_post).returns(failed_refund_response) + response = @gateway.capture(@amount, '47925') + assert_failure response - assert_equal('PARENT TRANSACTION NOT FOUND', response.message) - assert response.test? - end - - def test_failed_unlinked_refund - assert_raise ArgumentError do - @gateway.refund(@amount, "asdf") - end - end - - def test_deprecated_credit - @gateway.expects(:ssl_post).returns(successful_refund_response) - @options[:credit_card] = @credit_card - assert_deprecation_warning(Gateway::CREDIT_DEPRECATION_MESSAGE, @gateway) do - assert_success(response = @gateway.credit(@amount, '123456', @options)) - end - end - + assert_equal '47925', response.authorization + assert response.message.include?('Credit failed') + end + def test_successful_void - @gateway.expects(:ssl_post).returns(successful_void_response) - @options[:transactionid] = '123456' - - assert response = @gateway.void(@amount, @credit_card, @options) + response = stub_comms do + @gateway.void('47934') + end.check_request do |endpoint, data, headers| + assert_match(/<FIELD KEY="transaction_center_id">1234<\/FIELD>/, data) + assert_match(/<FIELD KEY="gateway_id">a91c38c3-7d7f-4d29-acc7-927b4dca0dbe<\/FIELD>/, data) + assert_match(/<FIELD KEY="operation_type">void<\/FIELD>/, data) + assert_match(/<FIELD KEY="total_number_transactions">1<\/FIELD>/, data) + assert_match(/<FIELD KEY="reference_number1">47934<\/FIELD>/, data) + end.respond_with(successful_void_response) + + assert response + assert_instance_of Response, response assert_success response - - assert_equal '53147623', response.authorization - assert response.test? + assert_equal '47934', response.authorization + assert_equal 'Approved', response.message end - + def test_failed_void - @gateway.expects(:ssl_post).returns(failed_void_response) - - assert response = @gateway.void(@amount, @credit_card, @options) + @gateway.stubs(:ssl_post).returns(failed_void_response) + response = @gateway.void('1') + + assert_failure response + assert_equal '1', response.authorization + assert response.message.include?('Void failed') + end + + def test_recurring_payments + @options[:recurring] = 1 + @options[:recurring_start_date] = '01/01/1900' + @options[:recurring_end_date] = '02/02/1901' + @options[:recurring_type] = 'monthly' + response = stub_comms do + @gateway.purchase(@amount, @credit_card, @options) + end.check_request do |endpoint, data, headers| + assert_match(%r{<FIELD KEY="recurring">1</FIELD>}, data) + assert_match(%r{<FIELD KEY="recurring_start_date">01/01/1900</FIELD>}, data) + assert_match(%r{<FIELD KEY="recurring_end_date">02/02/1901</FIELD>}, data) + assert_match(%r{<FIELD KEY="recurring_type">monthly</FIELD>}, data) + end.respond_with(successful_purchase_response) + + assert response + assert_success response + end + + def test_error_message + @gateway.stubs(:ssl_post).returns(failed_login_response) + response = @gateway.void('1') + assert_failure response - assert_equal('PARENT TRANSACTION NOT FOUND', response.message) - assert response.test? + assert response.error_code.include?('Merchant: 1234 has encountered error #DTO-200-TC.') + end + + def test_scrub + assert @gateway.supports_scrubbing? + assert_equal @gateway.scrub(pre_scrubbed), post_scrubbed end - - + private - + def successful_purchase_response - "CAPTURED:056708:NA:X:Jun 02 2009:241:NLS:NLS:NLS:57097598:9999:NA:NA:NA:NA:NA:NA:NA" + %(<RESPONSE> + <FIELDS> + <FIELD KEY="status">1</FIELD> + <FIELD KEY="auth_code">DEMO48</FIELD> + <FIELD KEY="auth_response">APPROVED</FIELD> + <FIELD KEY="avs_code">Z</FIELD> + <FIELD KEY="cvv2_code"> </FIELD> + <FIELD KEY="order_id">#{@options[:order_id]}</FIELD> + <FIELD KEY="reference_number">47913</FIELD> + <FIELD KEY="error" /> + <FIELD KEY="available_balance" /> + <FIELD KEY="is_partial">0</FIELD> + <FIELD KEY="partial_amount">0</FIELD> + <FIELD KEY="partial_id" /> + <FIELD KEY="original_full_amount" /> + <FIELD KEY="outstanding_balance">0</FIELD> + </FIELDS> +</RESPONSE>) end - + def failed_purchase_response - "NOT CAPTURED:DECLINE:NA:NA:Dec 11 2003:278654:NLS:NLS:NLS:53147611:200312111612:NA:NA:NA:NA:NA:NA" + %(<RESPONSE> + <FIELDS> + <FIELD KEY="status">2</FIELD> + <FIELD KEY="auth_code" /> + <FIELD KEY="auth_response">Declined</FIELD> + <FIELD KEY="avs_code"> </FIELD> + <FIELD KEY="cvv2_code"> </FIELD> + <FIELD KEY="order_id">#{@options[:order_id]}</FIELD> + <FIELD KEY="reference_number">47915</FIELD> + <FIELD KEY="error" /> + <FIELD KEY="available_balance" /> + <FIELD KEY="is_partial">0</FIELD> + <FIELD KEY="partial_amount">0</FIELD> + <FIELD KEY="partial_id" /> + <FIELD KEY="original_full_amount" /> + <FIELD KEY="outstanding_balance">0</FIELD> + </FIELDS> +</RESPONSE>) + end + + def successful_authorize_response + %(<RESPONSE> + <FIELDS> + <FIELD KEY="status">1</FIELD> + <FIELD KEY="auth_code">DEMO80</FIELD> + <FIELD KEY="auth_response">APPROVED</FIELD> + <FIELD KEY="avs_code">Z</FIELD> + <FIELD KEY="cvv2_code"> </FIELD> + <FIELD KEY="order_id">#{@options[:order_id]}</FIELD> + <FIELD KEY="reference_number">47920</FIELD> + <FIELD KEY="error" /> + <FIELD KEY="available_balance" /> + <FIELD KEY="is_partial">0</FIELD> + <FIELD KEY="partial_amount">0</FIELD> + <FIELD KEY="partial_id" /> + <FIELD KEY="original_full_amount" /> + <FIELD KEY="outstanding_balance">0</FIELD> + </FIELDS> +</RESPONSE>) + end + + def failed_authorize_response + %(<RESPONSE> + <FIELDS> + <FIELD KEY="status">2</FIELD> + <FIELD KEY="auth_code" /> + <FIELD KEY="auth_response">Declined</FIELD> + <FIELD KEY="avs_code"> </FIELD> + <FIELD KEY="cvv2_code"> </FIELD> + <FIELD KEY="order_id">#{@options[:order_id]}</FIELD> + <FIELD KEY="reference_number">47924</FIELD> + <FIELD KEY="error" /> + <FIELD KEY="available_balance" /> + <FIELD KEY="is_partial">0</FIELD> + <FIELD KEY="partial_amount">0</FIELD> + <FIELD KEY="partial_id" /> + <FIELD KEY="original_full_amount" /> + <FIELD KEY="outstanding_balance">0</FIELD> + </FIELDS> +</RESPONSE>) + end + + def successful_capture_response + %(<RESPONSE> + <FIELDS> + <FIELD KEY="total_transactions_settled">1</FIELD> + <FIELD KEY="total_amount_settled">1</FIELD> + <FIELD KEY="status1">1</FIELD> + <FIELD KEY="response1">APPROVED</FIELD> + <FIELD KEY="reference_number1">47920</FIELD> + <FIELD KEY="batch_number1">20140623</FIELD> + <FIELD KEY="settle_amount1">1.00</FIELD> + <FIELD KEY="error1" /> + </FIELDS> +</RESPONSE>) + end + + def failed_capture_response + %(<RESPONSE> + <FIELDS> + <FIELD KEY="total_transactions_settled">0</FIELD> + <FIELD KEY="total_amount_settled">0</FIELD> + <FIELD KEY="status1">2</FIELD> + <FIELD KEY="response1">Settle Failed. Transaction cannot be settled. Auth not found. Make sure the settlement amount does not exceed the original auth amount and that is was authorized less then 30 days ago.</FIELD> + <FIELD KEY="reference_number1">47920</FIELD> + <FIELD KEY="batch_number1" /> + <FIELD KEY="settle_amount1">1.00</FIELD> + <FIELD KEY="error1" /> + </FIELDS> +</RESPONSE>) end - + def successful_refund_response - "CAPTURED:945101216:199641568:NA:Dec 11 2003:278655:NLS:NLS:NLS:53147613:200312111613:NA:NA:NA:NA:NA" + %(<RESPONSE> + <FIELDS> + <FIELD KEY="total_transactions_credited">1</FIELD> + <FIELD KEY="status1">1</FIELD> + <FIELD KEY="response1">ACCEPTED</FIELD> + <FIELD KEY="reference_number1">47925</FIELD> + <FIELD KEY="credit_amount1">1.00</FIELD> + <FIELD KEY="error1" /> + </FIELDS> +</RESPONSE>) end - + def failed_refund_response - "NOT CAPTURED:PARENT TRANSACTION NOT FOUND:NA:NA:Dec 11 2003:278614:NLS:NLS:NLS:53147499:200311251526:NA:NA:NA:NA:NA" + %(<RESPONSE> + <FIELDS> + <FIELD KEY="total_transactions_credited">0</FIELD> + <FIELD KEY="status1">2</FIELD> + <FIELD KEY="response1">Credit Failed. Transaction cannot be credited.</FIELD> + <FIELD KEY="reference_number1">47925</FIELD> + <FIELD KEY="credit_amount1">1.00</FIELD> + <FIELD KEY="error1" /> + </FIELDS> +</RESPONSE>) end - + def successful_void_response - "CAPTURED:000000:NA:Y:Dec 11 2003:278659:NLS:NLS:NLS:53147623:200312111628:NA:NA:NA:NA:NA" + %(<RESPONSE> + <FIELDS> + <FIELD KEY="total_transactions_voided">1</FIELD> + <FIELD KEY="status1">1</FIELD> + <FIELD KEY="response1">APPROVED</FIELD> + <FIELD KEY="reference_number1">47934</FIELD> + <FIELD KEY="error1" /> + </FIELDS> +</RESPONSE>) end - + def failed_void_response - "NOT CAPTURED:PARENT TRANSACTION NOT FOUND:NA:NA:Dec 11 2003:278644:NLS:NLS:NLS:53147562:200311251526:NA:NA:NA:NA:NA" + %(<RESPONSE> + <FIELDS> + <FIELD KEY="total_transactions_voided">0</FIELD> + <FIELD KEY="status1">2</FIELD> + <FIELD KEY="response1">Void Failed. Transaction cannot be voided.</FIELD> + <FIELD KEY="reference_number1">1</FIELD> + <FIELD KEY="error1" /> + </FIELDS> +</RESPONSE>) + end + + def failed_login_response + %(<RESPONSE> + <FIELDS> + <FIELD KEY="status">0</FIELD> + <FIELD KEY="auth_code"></FIELD> + <FIELD KEY="auth_response"></FIELD> + <FIELD KEY="avs_code"></FIELD> + <FIELD KEY="cvv2_code"></FIELD> + <FIELD KEY="reference_number"></FIELD> + <FIELD KEY="order_id">a0d2560dda18631ce325c07dcbda2a9880fd17fb344fd233</FIELD> + <FIELD KEY="error">Merchant: 1234 has encountered error #DTO-200-TC. Please call 888-638-7867 if you feel this is in error.</FIELD> + </FIELDS> +</RESPONSE>) + end + + def pre_scrubbed + %(<RESPONSE> + <FIELDS> + <FIELD KEY="order_id">77b61bfe08510e00852f2f20011e7952d80f9a4be17d27cf</FIELD> + <FIELD KEY="total">1.00</FIELD><FIELD KEY="card_name">visa</FIELD> + <FIELD KEY="card_number">4111111111111111</FIELD> + <FIELD KEY="card_exp">0919</FIELD> + <FIELD KEY="cvv2">789</FIELD> + <FIELD KEY="owner_name">Jim Smith</FIELD> + <FIELD KEY="owner_street">456 My Street</FIELD> + <FIELD KEY="owner_street2">Apt 1</FIELD> + <FIELD KEY="owner_city">Ottawa</FIELD> + <FIELD KEY="owner_state">ON</FIELD> + <FIELD KEY="owner_zip">K1C2N6</FIELD> + <FIELD KEY="owner_country">CA</FIELD> + <FIELD KEY="owner_phone">(555)555-5555</FIELD> + <FIELD KEY="transaction_center_id">1264</FIELD> + <FIELD KEY="gateway_id">a91c38c3-7d7f-4d29-acc7-927b4dca0dbe</FIELD> + <FIELD KEY="operation_type">sale</FIELD> + </FIELDS> +</RESPONSE>) end - - def error_response - '!ERROR! 704-MISSING BASIC DATA TYPE:card, exp, zip, addr, member, amount' + + def post_scrubbed + %(<RESPONSE> + <FIELDS> + <FIELD KEY=\"order_id\">77b61bfe08510e00852f2f20011e7952d80f9a4be17d27cf</FIELD> + <FIELD KEY=\"total\">1.00</FIELD><FIELD KEY=\"card_name\">visa</FIELD> + <FIELD KEY=\"card_number[FILTERED]</FIELD> + <FIELD KEY=\"card_exp\">0919</FIELD> + <FIELD KEY=\"cvv2[FILTERED]</FIELD> + <FIELD KEY=\"owner_name\">Jim Smith</FIELD> + <FIELD KEY=\"owner_street\">456 My Street</FIELD> + <FIELD KEY=\"owner_street2\">Apt 1</FIELD> + <FIELD KEY=\"owner_city\">Ottawa</FIELD> + <FIELD KEY=\"owner_state\">ON</FIELD> + <FIELD KEY=\"owner_zip\">K1C2N6</FIELD> + <FIELD KEY=\"owner_country\">CA</FIELD> + <FIELD KEY=\"owner_phone\">(555)555-5555</FIELD> + <FIELD KEY=\"transaction_center_id\">1264</FIELD> + <FIELD KEY=\"gateway_id[FILTERED]</FIELD> + <FIELD KEY=\"operation_type\">sale</FIELD> + </FIELDS> +</RESPONSE>) end end diff --git a/test/unit/gateways/firstdata_e4_test.rb b/test/unit/gateways/firstdata_e4_test.rb old mode 100644 new mode 100755 index c6d387f3a4c..e4aa2511096 --- a/test/unit/gateways/firstdata_e4_test.rb +++ b/test/unit/gateways/firstdata_e4_test.rb @@ -1,4 +1,5 @@ require 'test_helper' +require 'nokogiri' require 'yaml' class FirstdataE4Test < Test::Unit::TestCase @@ -6,8 +7,8 @@ class FirstdataE4Test < Test::Unit::TestCase def setup @gateway = FirstdataE4Gateway.new( - :login => "A00427-01", - :password => "testus" + :login => 'A00427-01', + :password => 'testus' ) @credit_card = credit_card @@ -17,7 +18,16 @@ def setup :billing_address => address, :description => 'Store Purchase' } - @authorization = "ET1700;106625152;4738" + @authorization = 'ET1700;106625152;4738' + end + + def test_invalid_credentials + @gateway.expects(:ssl_post).raises(bad_credentials_response) + assert response = @gateway.store(@credit_card, {}) + assert_failure response + assert response.test? + assert_equal '', response.authorization + assert_equal 'Unauthorized Request. Bad or missing credentials.', response.message end def test_successful_purchase @@ -28,7 +38,20 @@ def test_successful_purchase assert response.test? assert_equal 'Transaction Normal - Approved', response.message - ExactGateway::SENSITIVE_FIELDS.each{|f| assert !response.params.has_key?(f.to_s)} + FirstdataE4Gateway::SENSITIVE_FIELDS.each { |f| assert !response.params.has_key?(f.to_s) } + end + + def test_successful_purchase_with_specified_currency + options_with_specified_currency = @options.merge({currency: 'GBP'}) + @gateway.expects(:ssl_post).returns(successful_purchase_with_specified_currency_response) + assert response = @gateway.purchase(@amount, @credit_card, options_with_specified_currency) + assert_success response + assert_equal 'ET1700;106625152;4738', response.authorization + assert response.test? + assert_equal 'Transaction Normal - Approved', response.message + assert_equal 'GBP', response.params['currency'] + + FirstdataE4Gateway::SENSITIVE_FIELDS.each { |f| assert !response.params.has_key?(f.to_s) } end def test_successful_purchase_with_token @@ -37,6 +60,15 @@ def test_successful_purchase_with_token assert_success response end + def test_successful_purchase_with_specified_currency_and_token + options_with_specified_currency = @options.merge({currency: 'GBP'}) + @gateway.expects(:ssl_post).returns(successful_purchase_with_specified_currency_response) + assert response = @gateway.purchase(@amount, '8938737759041111;visa;Longbob;Longsen;9;2014', + options_with_specified_currency) + assert_success response + assert_equal 'GBP', response.params['currency'] + end + def test_successful_void @gateway.expects(:ssl_post).returns(successful_void_response) assert response = @gateway.void(@authorization, @options) @@ -49,12 +81,20 @@ def test_successful_refund assert_success response end + def test_successful_refund_with_specified_currency + options_with_specified_currency = @options.merge({currency: 'GBP'}) + @gateway.expects(:ssl_post).returns(successful_refund_with_specified_currency_response) + assert response = @gateway.refund(@amount, @authorization, options_with_specified_currency) + assert_success response + assert_equal 'GBP', response.params['currency'] + end + def test_successful_store @gateway.expects(:ssl_post).returns(successful_purchase_response) assert response = @gateway.store(@credit_card, @options) assert_success response assert_equal '8938737759041111', response.params['transarmor_token'] - assert_equal '8938737759041111;visa;Longbob;Longsen;9;2014', response.authorization + assert_equal "8938737759041111;visa;Longbob;Longsen;9;#{@credit_card.year}", response.authorization end def test_failed_store_without_transarmor_support @@ -70,11 +110,19 @@ def test_failed_purchase assert response = @gateway.purchase(@amount, @credit_card, @options) assert_instance_of Response, response assert_failure response + assert_equal response.error_code, 'invalid_expiry_date' + end + + def test_successful_verify + response = stub_comms do + @gateway.verify(@credit_card) + end.respond_with(successful_verify_response) + assert_success response end def test_expdate assert_equal( - "%02d%s" % [@credit_card.month, @credit_card.year.to_s[-2..-1]], + '%02d%s' % [@credit_card.month, @credit_card.year.to_s[-2..-1]], @gateway.send(:expdate, @credit_card) ) end @@ -88,11 +136,11 @@ def test_no_transaction end def test_supported_countries - assert_equal ['CA', 'US'], ExactGateway.supported_countries + assert_equal ['CA', 'US'], FirstdataE4Gateway.supported_countries end - def test_supported_card_types - assert_equal [:visa, :master, :american_express, :jcb, :discover], ExactGateway.supported_cardtypes + def test_supported_cardtypes + assert_equal [:visa, :master, :american_express, :jcb, :discover], FirstdataE4Gateway.supported_cardtypes end def test_avs_result @@ -110,15 +158,241 @@ def test_cvv_result end def test_requests_include_verification_string - stub_comms(:ssl_post) do + stub_comms do + @gateway.purchase(@amount, @credit_card, @options) + end.check_request do |endpoint, data, headers| + assert_match '<VerificationStr1>456 My Street|K1C2N6|Ottawa|ON|CA</VerificationStr1>', data + end.respond_with(successful_purchase_response) + end + + def test_tax_fields_are_sent + stub_comms do + @gateway.purchase(@amount, @credit_card, @options.merge(tax1_amount: 830, tax1_number: 'Br59a')) + end.check_request do |endpoint, data, headers| + assert_match '<Tax1Amount>830', data + assert_match '<Tax1Number>Br59a', data + end.respond_with(successful_purchase_response) + end + + def test_customer_ref_is_sent + stub_comms do + @gateway.purchase(@amount, @credit_card, @options.merge(customer: '932')) + end.check_request do |endpoint, data, headers| + assert_match '<Customer_Ref>932', data + end.respond_with(successful_purchase_response) + end + + def test_eci_default_value + stub_comms do @gateway.purchase(@amount, @credit_card, @options) end.check_request do |endpoint, data, headers| - assert_match "<VerificationStr1>1234 My Street|K1C2N6|Ottawa|ON|CA</VerificationStr1>", data + assert_match '<Ecommerce_Flag>07</Ecommerce_Flag>', data + end.respond_with(successful_purchase_response) + end + + def test_eci_numeric_padding + @credit_card = network_tokenization_credit_card + @credit_card.eci = '5' + + stub_comms do + @gateway.purchase(@amount, @credit_card, @options) + end.check_request do |endpoint, data, headers| + assert_match '<Ecommerce_Flag>05</Ecommerce_Flag>', data + end.respond_with(successful_purchase_response) + + @credit_card = network_tokenization_credit_card + @credit_card.eci = 5 + + stub_comms do + @gateway.purchase(@amount, @credit_card, @options) + end.check_request do |endpoint, data, headers| + assert_match '<Ecommerce_Flag>05</Ecommerce_Flag>', data + end.respond_with(successful_purchase_response) + end + + def test_eci_option_value + stub_comms do + @gateway.purchase(@amount, @credit_card, @options.merge(eci: '05')) + end.check_request do |endpoint, data, headers| + assert_match '<Ecommerce_Flag>05</Ecommerce_Flag>', data + end.respond_with(successful_purchase_response) + end + + def test_network_tokenization_requests_with_amex + stub_comms do + credit_card = network_tokenization_credit_card( + '378282246310005', + brand: 'american_express', + transaction_id: '123', + eci: '05', + payment_cryptogram: 'whatever_the_cryptogram_of_at_least_20_characters_is' + ) + + @gateway.purchase(@amount, credit_card, @options) + end.check_request do |_, data, _| + assert_match '<Ecommerce_Flag>05</Ecommerce_Flag>', data + assert_match "<XID>mrLdtHIWq2nLXq7IrA==\n</XID>", data + assert_match "<CAVV>whateverthecryptogramofatlc=\n</CAVV>", data + assert_xml_valid_to_wsdl(data) + end.respond_with(successful_purchase_response) + end + + def test_network_tokenization_requests_with_discover + stub_comms do + credit_card = network_tokenization_credit_card( + '6011111111111117', + brand: 'discover', + transaction_id: '123', + eci: '05', + payment_cryptogram: 'whatever_the_cryptogram_is' + ) + + @gateway.purchase(@amount, credit_card, @options) + end.check_request do |_, data, _| + assert_match '<Ecommerce_Flag>04</Ecommerce_Flag>', data + assert_match '<XID>123</XID>', data + assert_match '<CAVV>whatever_the_cryptogram_is</CAVV>', data + assert_xml_valid_to_wsdl(data) + end.respond_with(successful_purchase_response) + end + + def test_network_tokenization_requests_with_other_brands + %w(visa mastercard other).each do |brand| + stub_comms do + credit_card = network_tokenization_credit_card( + '378282246310005', + brand: brand, + transaction_id: '123', + eci: '05', + payment_cryptogram: 'whatever_the_cryptogram_is' + ) + + @gateway.purchase(@amount, credit_card, @options) + end.check_request do |_, data, _| + assert_match '<Ecommerce_Flag>05</Ecommerce_Flag>', data + assert_match '<XID>123</XID>', data + assert_match '<CAVV>whatever_the_cryptogram_is</CAVV>', data + assert_xml_valid_to_wsdl(data) + end.respond_with(successful_purchase_response) + end + end + + def test_requests_include_card_authentication_data + authentication_hash = { + eci: '06', + cavv: 'SAMPLECAVV', + xid: 'SAMPLEXID' + } + options_with_authentication_data = @options.merge(authentication_hash) + + stub_comms do + @gateway.purchase(@amount, @credit_card, options_with_authentication_data) + end.check_request do |endpoint, data, headers| + assert_match '<Ecommerce_Flag>06</Ecommerce_Flag>', data + assert_match '<CAVV>SAMPLECAVV</CAVV>', data + assert_match '<XID>SAMPLEXID</XID>', data + assert_xml_valid_to_wsdl(data) + end.respond_with(successful_purchase_response) + end + + def test_card_type + assert_equal 'Visa', @gateway.send(:card_type, 'visa') + assert_equal 'Mastercard', @gateway.send(:card_type, 'master') + assert_equal 'Mastercard', @gateway.send(:card_type, 'mastercard') + assert_equal 'American Express', @gateway.send(:card_type, 'american_express') + assert_equal 'JCB', @gateway.send(:card_type, 'jcb') + assert_equal 'Discover', @gateway.send(:card_type, 'discover') + end + + def test_add_swipe_data_with_creditcard + @credit_card.track_data = 'Track Data' + + stub_comms do + @gateway.purchase(@amount, @credit_card) + end.check_request do |endpoint, data, headers| + assert_match '<Track1>Track Data</Track1>', data + assert_match '<Ecommerce_Flag>R</Ecommerce_Flag>', data end.respond_with(successful_purchase_response) end + def test_transcript_scrubbing + assert @gateway.supports_scrubbing? + assert_equal @gateway.scrub(pre_scrub), post_scrub + end + + def test_supports_network_tokenization + assert_instance_of TrueClass, @gateway.supports_network_tokenization? + end + private + def assert_xml_valid_to_wsdl(data) + xsd = Nokogiri::XML::Schema(File.open("#{File.dirname(__FILE__)}/../../schema/firstdata_e4/v11.xsd")) + doc = Nokogiri::XML(data) + errors = xsd.validate(doc) + assert_empty errors, "XSD validation errors in the following XML:\n#{doc}" + end + + def pre_scrub + <<-PRE_SCRUBBED + opening connection to api.demo.globalgatewaye4.firstdata.com:443... + opened + starting SSL for api.demo.globalgatewaye4.firstdata.com:443... + SSL established + <- "POST /transaction/v11 HTTP/1.1\r\nContent-Type: application/xml\r\nAccepts: application/xml\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: api.demo.globalgatewaye4.firstdata.com\r\nContent-Length: 593\r\n\r\n" + <- "<?xml version=\"1.0\" encoding=\"UTF-8\"?><Transaction><ExactID>REDACTED</ExactID><Password>REDACTED</Password><Transaction_Type>00</Transaction_Type><DollarAmount>1.00</DollarAmount><Card_Number>4242424242424242</Card_Number><Expiry_Date>0916</Expiry_Date><CardHoldersName>Longbob Longsen</CardHoldersName><CardType>Visa</CardType><VerificationStr1>1234 My Street|K1C2N6|Ottawa|ON|CA</VerificationStr1><CVD_Presence_Ind>1</CVD_Presence_Ind><VerificationStr2>123</VerificationStr2><Reference_No>1</Reference_No><Reference_3>Store Purchase</Reference_3><CAVV>lol</CAVV><XID/><Ecommerce_Flag/></Transaction>" + -> "HTTP/1.1 201 Created\r\n" + -> "Cache-Control: max-age=0, private, must-revalidate\r\n" + -> "Content-Type: application/xml; charset=utf-8\r\n" + -> "Date: Mon, 26 Jan 2015 17:11:44 GMT\r\n" + -> "ETag: \"0a9a542fbfc55846e0ebe471b5725fe8\"\r\n" + -> "Location: https://api.demo.globalgatewaye4.firstdata.com/transaction/v11/42930941\r\n" + -> "Server: Apache\r\n" + -> "Status: 201\r\n" + -> "X-Rack-Cache: invalidate, pass\r\n" + -> "X-Request-Id: e0fabf89d7d7272cab4d4d743327d036\r\n" + -> "X-UA-Compatible: IE=Edge,chrome=1\r\n" + -> "Content-Length: 2872\r\n" + -> "Connection: Close\r\n" + -> "\r\n" + reading 2872 bytes... + -> "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<TransactionResult>\n <ExactID>AD2327-05</ExactID>\n <Password></Password>\n <Transaction_Type>00</Transaction_Type>\n <DollarAmount>1.0</DollarAmount>\n <SurchargeAmount></SurchargeAmount>\n <Card_Number>############4242</Card_Number>\n <Transaction_Tag>42930941</Transaction_Tag>\n <Track1></Track1>\n <Track2></Track2>\n <PAN></PAN>\n <Authorization_Num>ET151682</Authorization_Num>\n <Expiry_Date>0916</Expiry_Date>\n <CardHoldersName>Longbob Longsen</CardHoldersName>\n <VerificationStr1>1234 My Street|K1C2N6|Ottawa|ON|CA</VerificationStr1>\n <VerificationStr2>123</VerificationStr2>\n <CVD_Presence_Ind>0</CVD_Presence_Ind>\n <ZipCode></ZipCode>\n <Tax1Amount></Tax1Amount>\n <Tax1Number></Tax1Number>\n <Tax2Amount></Tax2Amount>\n <Tax2Number></Tax2Number>\n <Secure_AuthRequired></Secure_AuthRequired>\n <Secure_AuthResult></Secure_AuthResult>\n <Ecommerce_Flag></Ecommerce_Flag>\n <XID></XID>\n <CAVV>lol</CAVV>\n <CAVV_Algorithm></CAVV_Algorithm>\n <Reference_No>1</Reference_No>\n <Customer_Ref></Customer_Ref>\n <Reference_3>Store Purchase</Reference_3>\n <Language></Language>\n <Client_IP>216.191.105.146</Client_IP>\n <Client_Email></Client_Email>\n <Transaction_Error>false</Transaction_Error>\n <Transaction_Approved>true</Transaction_Approved>\n <EXact_Resp_Code>00</EXact_Resp_Code>\n <EXact_Message>Transaction Normal</EXact_Message>\n <Bank_Resp_Code>100</Bank_Resp_Code>\n <Bank_Message>Approved</Bank_Message>\n <Bank_Resp_Code_2></Bank_Resp_Code_2>\n <SequenceNo>106826</SequenceNo>\n <AVS>1</AVS>\n <CVV2>M</CVV2>\n <Retrieval_Ref_No>0025564</Retrieval_Ref_No>\n <CAVV_Response></CAVV_Response>\n <Currency>USD</Currency>\n <AmountRequested></AmountRequested>\n <PartialRedemption>false</PartialRedemption>\n <MerchantName>Shopify DEMO0678</MerchantName>\n <MerchantAddress>126 York Street</MerchantAddress>\n <MerchantCity>Ottawa</MerchantCity>\n <MerchantProvince>Alabama</MerchantProvince>\n <MerchantCountry>Canada</MerchantCountry>\n <MerchantPostal>K1N 5T5</MerchantPostal>\n <MerchantURL>www.shopify.com</MerchantURL>\n <TransarmorToken></TransarmorToken>\n <CardType>Visa</CardType>\n <CurrentBalance></CurrentBalance>\n <PreviousBalance></PreviousBalance>\n <EAN></EAN>\n <CardCost></CardCost>\n <VirtualCard>false</VirtualCard>\n <CTR>=========== TRANSACTION RECORD ==========\nShopify DEMO0678\n126 York Street\nOttawa, AL K1N 5T5\nCanada\nwww.shopify.com\n\nTY" + -> "PE: Purchase\n\nACCT: Visa $ 1.00 USD\n\nCARDHOLDER NAME : Longbob Longsen\nCARD NUMBER : ############4242\nDATE/TIME : 26 Jan 15 12:11:44\nREFERENCE # : 106826 M\nAUTHOR. # : ET151682\nTRANS. REF. : 1\n\n Approved - Thank You 100\n\n\nPlease retain this copy for your records.\n\nCardholder will pay above amount to card\nissuer pursuant to cardholder agreement.\n=========================================</CTR>\n</TransactionResult>\n" + read 2872 bytes + Conn close + PRE_SCRUBBED + end + + def post_scrub + <<-POST_SCRUBBED + opening connection to api.demo.globalgatewaye4.firstdata.com:443... + opened + starting SSL for api.demo.globalgatewaye4.firstdata.com:443... + SSL established + <- "POST /transaction/v11 HTTP/1.1\r\nContent-Type: application/xml\r\nAccepts: application/xml\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: api.demo.globalgatewaye4.firstdata.com\r\nContent-Length: 593\r\n\r\n" + <- "<?xml version=\"1.0\" encoding=\"UTF-8\"?><Transaction><ExactID>REDACTED</ExactID><Password>[FILTERED]</Password><Transaction_Type>00</Transaction_Type><DollarAmount>1.00</DollarAmount><Card_Number>[FILTERED]</Card_Number><Expiry_Date>0916</Expiry_Date><CardHoldersName>Longbob Longsen</CardHoldersName><CardType>Visa</CardType><VerificationStr1>1234 My Street|K1C2N6|Ottawa|ON|CA</VerificationStr1><CVD_Presence_Ind>1</CVD_Presence_Ind><VerificationStr2>[FILTERED]</VerificationStr2><Reference_No>1</Reference_No><Reference_3>Store Purchase</Reference_3><CAVV>[FILTERED]</CAVV><XID/><Ecommerce_Flag/></Transaction>" + -> "HTTP/1.1 201 Created\r\n" + -> "Cache-Control: max-age=0, private, must-revalidate\r\n" + -> "Content-Type: application/xml; charset=utf-8\r\n" + -> "Date: Mon, 26 Jan 2015 17:11:44 GMT\r\n" + -> "ETag: \"0a9a542fbfc55846e0ebe471b5725fe8\"\r\n" + -> "Location: https://api.demo.globalgatewaye4.firstdata.com/transaction/v11/42930941\r\n" + -> "Server: Apache\r\n" + -> "Status: 201\r\n" + -> "X-Rack-Cache: invalidate, pass\r\n" + -> "X-Request-Id: e0fabf89d7d7272cab4d4d743327d036\r\n" + -> "X-UA-Compatible: IE=Edge,chrome=1\r\n" + -> "Content-Length: 2872\r\n" + -> "Connection: Close\r\n" + -> "\r\n" + reading 2872 bytes... + -> "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<TransactionResult>\n <ExactID>AD2327-05</ExactID>\n <Password></Password>\n <Transaction_Type>00</Transaction_Type>\n <DollarAmount>1.0</DollarAmount>\n <SurchargeAmount></SurchargeAmount>\n <Card_Number>[FILTERED]</Card_Number>\n <Transaction_Tag>42930941</Transaction_Tag>\n <Track1></Track1>\n <Track2></Track2>\n <PAN></PAN>\n <Authorization_Num>ET151682</Authorization_Num>\n <Expiry_Date>0916</Expiry_Date>\n <CardHoldersName>Longbob Longsen</CardHoldersName>\n <VerificationStr1>1234 My Street|K1C2N6|Ottawa|ON|CA</VerificationStr1>\n <VerificationStr2>[FILTERED]</VerificationStr2>\n <CVD_Presence_Ind>0</CVD_Presence_Ind>\n <ZipCode></ZipCode>\n <Tax1Amount></Tax1Amount>\n <Tax1Number></Tax1Number>\n <Tax2Amount></Tax2Amount>\n <Tax2Number></Tax2Number>\n <Secure_AuthRequired></Secure_AuthRequired>\n <Secure_AuthResult></Secure_AuthResult>\n <Ecommerce_Flag></Ecommerce_Flag>\n <XID></XID>\n <CAVV>[FILTERED]</CAVV>\n <CAVV_Algorithm></CAVV_Algorithm>\n <Reference_No>1</Reference_No>\n <Customer_Ref></Customer_Ref>\n <Reference_3>Store Purchase</Reference_3>\n <Language></Language>\n <Client_IP>216.191.105.146</Client_IP>\n <Client_Email></Client_Email>\n <Transaction_Error>false</Transaction_Error>\n <Transaction_Approved>true</Transaction_Approved>\n <EXact_Resp_Code>00</EXact_Resp_Code>\n <EXact_Message>Transaction Normal</EXact_Message>\n <Bank_Resp_Code>100</Bank_Resp_Code>\n <Bank_Message>Approved</Bank_Message>\n <Bank_Resp_Code_2></Bank_Resp_Code_2>\n <SequenceNo>106826</SequenceNo>\n <AVS>1</AVS>\n <CVV2>M</CVV2>\n <Retrieval_Ref_No>0025564</Retrieval_Ref_No>\n <CAVV_Response></CAVV_Response>\n <Currency>USD</Currency>\n <AmountRequested></AmountRequested>\n <PartialRedemption>false</PartialRedemption>\n <MerchantName>Shopify DEMO0678</MerchantName>\n <MerchantAddress>126 York Street</MerchantAddress>\n <MerchantCity>Ottawa</MerchantCity>\n <MerchantProvince>Alabama</MerchantProvince>\n <MerchantCountry>Canada</MerchantCountry>\n <MerchantPostal>K1N 5T5</MerchantPostal>\n <MerchantURL>www.shopify.com</MerchantURL>\n <TransarmorToken></TransarmorToken>\n <CardType>Visa</CardType>\n <CurrentBalance></CurrentBalance>\n <PreviousBalance></PreviousBalance>\n <EAN></EAN>\n <CardCost></CardCost>\n <VirtualCard>false</VirtualCard>\n <CTR>=========== TRANSACTION RECORD ==========\nShopify DEMO0678\n126 York Street\nOttawa, AL K1N 5T5\nCanada\nwww.shopify.com\n\nTY" + -> "PE: Purchase\n\nACCT: Visa $ 1.00 USD\n\nCARDHOLDER NAME : Longbob Longsen\nCARD NUMBER : ############4242\nDATE/TIME : 26 Jan 15 12:11:44\nREFERENCE # : 106826 M\nAUTHOR. # : ET151682\nTRANS. REF. : 1\n\n Approved - Thank You 100\n\n\nPlease retain this copy for your records.\n\nCardholder will pay above amount to card\nissuer pursuant to cardholder agreement.\n=========================================</CTR>\n</TransactionResult>\n" + read 2872 bytes + Conn close + POST_SCRUBBED + end + def successful_purchase_response <<-RESPONSE <?xml version="1.0" encoding="UTF-8"?> @@ -207,6 +481,96 @@ def successful_purchase_response </TransactionResult> RESPONSE end + + def successful_purchase_with_specified_currency_response + <<-RESPONSE + <?xml version="1.0" encoding="UTF-8"?> + <TransactionResult> + <ExactID>AD1234-56</ExactID> + <Password></Password> + <Transaction_Type>00</Transaction_Type> + <DollarAmount>47.38</DollarAmount> + <SurchargeAmount></SurchargeAmount> + <Card_Number>############1111</Card_Number> + <Transaction_Tag>106625152</Transaction_Tag> + <Track1></Track1> + <Track2></Track2> + <PAN></PAN> + <Authorization_Num>ET1700</Authorization_Num> + <Expiry_Date>0913</Expiry_Date> + <CardHoldersName>Fred Burfle</CardHoldersName> + <VerificationStr1></VerificationStr1> + <VerificationStr2>773</VerificationStr2> + <CVD_Presence_Ind>0</CVD_Presence_Ind> + <ZipCode></ZipCode> + <Tax1Amount></Tax1Amount> + <Tax1Number></Tax1Number> + <Tax2Amount></Tax2Amount> + <Tax2Number></Tax2Number> + <Secure_AuthRequired></Secure_AuthRequired> + <Secure_AuthResult></Secure_AuthResult> + <Ecommerce_Flag></Ecommerce_Flag> + <XID></XID> + <CAVV></CAVV> + <CAVV_Algorithm></CAVV_Algorithm> + <Reference_No>77</Reference_No> + <Customer_Ref></Customer_Ref> + <Reference_3></Reference_3> + <Language></Language> + <Client_IP>1.1.1.10</Client_IP> + <Client_Email></Client_Email> + <Transaction_Error>false</Transaction_Error> + <Transaction_Approved>true</Transaction_Approved> + <EXact_Resp_Code>00</EXact_Resp_Code> + <EXact_Message>Transaction Normal</EXact_Message> + <Bank_Resp_Code>100</Bank_Resp_Code> + <Bank_Message>Approved</Bank_Message> + <Bank_Resp_Code_2></Bank_Resp_Code_2> + <SequenceNo>000040</SequenceNo> + <AVS>U</AVS> + <CVV2>M</CVV2> + <Retrieval_Ref_No>3146117</Retrieval_Ref_No> + <CAVV_Response></CAVV_Response> + <Currency>GBP</Currency> + <AmountRequested></AmountRequested> + <PartialRedemption>false</PartialRedemption> + <MerchantName>Friendly Inc DEMO0983</MerchantName> + <MerchantAddress>123 King St</MerchantAddress> + <MerchantCity>Toronto</MerchantCity> + <MerchantProvince>Ontario</MerchantProvince> + <MerchantCountry>Canada</MerchantCountry> + <MerchantPostal>L7Z 3K8</MerchantPostal> + <MerchantURL></MerchantURL> + <TransarmorToken>8938737759041111</TransarmorToken> + <CTR>=========== TRANSACTION RECORD ========== +Friendly Inc DEMO0983 +123 King St +Toronto, ON L7Z 3K8 +Canada + + +TYPE: Purchase + +ACCT: Visa £ 47.38 GBP + +CARD NUMBER : ############1111 +DATE/TIME : 28 Sep 12 07:54:48 +REFERENCE # : 000040 M +AUTHOR. # : ET120454 +TRANS. REF. : 77 + + Approved - Thank You 100 + + +Please retain this copy for your records. + +Cardholder will pay above amount to card +issuer pursuant to cardholder agreement. +=========================================</CTR> + </TransactionResult> + RESPONSE + end + def successful_purchase_response_without_transarmor <<-RESPONSE <?xml version="1.0" encoding="UTF-8"?> @@ -295,6 +659,7 @@ def successful_purchase_response_without_transarmor </TransactionResult> RESPONSE end + def successful_refund_response <<-RESPONSE <?xml version="1.0" encoding="UTF-8"?> @@ -374,6 +739,92 @@ def successful_refund_response Approved - Thank You 100 +Please retain this copy for your records. + +=========================================</CTR> + </TransactionResult> + RESPONSE + end + + def successful_refund_with_specified_currency_response + <<-RESPONSE + <?xml version="1.0" encoding="UTF-8"?> + <TransactionResult> + <ExactID>AD1234-56</ExactID> + <Password></Password> + <Transaction_Type>34</Transaction_Type> + <DollarAmount>123</DollarAmount> + <SurchargeAmount></SurchargeAmount> + <Card_Number>############1111</Card_Number> + <Transaction_Tag>888</Transaction_Tag> + <Track1></Track1> + <Track2></Track2> + <PAN></PAN> + <Authorization_Num>ET112216</Authorization_Num> + <Expiry_Date>0913</Expiry_Date> + <CardHoldersName>Fred Burfle</CardHoldersName> + <VerificationStr1></VerificationStr1> + <VerificationStr2></VerificationStr2> + <CVD_Presence_Ind>0</CVD_Presence_Ind> + <ZipCode></ZipCode> + <Tax1Amount></Tax1Amount> + <Tax1Number></Tax1Number> + <Tax2Amount></Tax2Amount> + <Tax2Number></Tax2Number> + <Secure_AuthRequired></Secure_AuthRequired> + <Secure_AuthResult></Secure_AuthResult> + <Ecommerce_Flag></Ecommerce_Flag> + <XID></XID> + <CAVV></CAVV> + <CAVV_Algorithm></CAVV_Algorithm> + <Reference_No></Reference_No> + <Customer_Ref></Customer_Ref> + <Reference_3></Reference_3> + <Language></Language> + <Client_IP>1.1.1.10</Client_IP> + <Client_Email></Client_Email> + <Transaction_Error>false</Transaction_Error> + <Transaction_Approved>true</Transaction_Approved> + <EXact_Resp_Code>00</EXact_Resp_Code> + <EXact_Message>Transaction Normal</EXact_Message> + <Bank_Resp_Code>100</Bank_Resp_Code> + <Bank_Message>Approved</Bank_Message> + <Bank_Resp_Code_2></Bank_Resp_Code_2> + <SequenceNo>000041</SequenceNo> + <AVS></AVS> + <CVV2>I</CVV2> + <Retrieval_Ref_No>9176784</Retrieval_Ref_No> + <CAVV_Response></CAVV_Response> + <Currency>GBP</Currency> + <AmountRequested></AmountRequested> + <PartialRedemption>false</PartialRedemption> + <MerchantName>Friendly Inc DEMO0983</MerchantName> + <MerchantAddress>123 King St</MerchantAddress> + <MerchantCity>Toronto</MerchantCity> + <MerchantProvince>Ontario</MerchantProvince> + <MerchantCountry>Canada</MerchantCountry> + <MerchantPostal>L7Z 3K8</MerchantPostal> + <MerchantURL></MerchantURL> + <CTR>=========== TRANSACTION RECORD ========== +Friendly Inc DEMO0983 +123 King St +Toronto, ON L7Z 3K8 +Canada + + +TYPE: Refund + +ACCT: Visa £ 23.69 GBP + +CARD NUMBER : ############1111 +DATE/TIME : 28 Sep 12 08:31:23 +REFERENCE # : 000041 M +AUTHOR. # : ET112216 +TRANS. REF. : + + Approved - Thank You 100 + + Please retain this copy for your records. =========================================</CTR> @@ -464,6 +915,102 @@ def failed_purchase_response RESPONSE end + def successful_verify_response + <<-RESPONSE +<?xml version="1.0" encoding="UTF-8"?> +<TransactionResult> + <ExactID>AD2552-05</ExactID> + <Password></Password> + <Transaction_Type>05</Transaction_Type> + <DollarAmount>0.0</DollarAmount> + <SurchargeAmount></SurchargeAmount> + <Card_Number>############4242</Card_Number> + <Transaction_Tag>25101911</Transaction_Tag> + <Track1></Track1> + <Track2></Track2> + <PAN></PAN> + <Authorization_Num>ET184931</Authorization_Num> + <Expiry_Date>0915</Expiry_Date> + <CardHoldersName>Longbob Longsen</CardHoldersName> + <VerificationStr1>1234 My Street|K1C2N6|Ottawa|ON|CA</VerificationStr1> + <VerificationStr2>123</VerificationStr2> + <CVD_Presence_Ind>0</CVD_Presence_Ind> + <ZipCode></ZipCode> + <Tax1Amount></Tax1Amount> + <Tax1Number></Tax1Number> + <Tax2Amount></Tax2Amount> + <Tax2Number></Tax2Number> + <Secure_AuthRequired></Secure_AuthRequired> + <Secure_AuthResult></Secure_AuthResult> + <Ecommerce_Flag></Ecommerce_Flag> + <XID></XID> + <CAVV></CAVV> + <CAVV_Algorithm></CAVV_Algorithm> + <Reference_No>1</Reference_No> + <Customer_Ref></Customer_Ref> + <Reference_3>Store Purchase</Reference_3> + <Language></Language> + <Client_IP>75.182.123.244</Client_IP> + <Client_Email></Client_Email> + <Transaction_Error>false</Transaction_Error> + <Transaction_Approved>true</Transaction_Approved> + <EXact_Resp_Code>00</EXact_Resp_Code> + <EXact_Message>Transaction Normal</EXact_Message> + <Bank_Resp_Code>100</Bank_Resp_Code> + <Bank_Message>Approved</Bank_Message> + <Bank_Resp_Code_2></Bank_Resp_Code_2> + <SequenceNo>000040</SequenceNo> + <AVS>1</AVS> + <CVV2>M</CVV2> + <Retrieval_Ref_No>7228838</Retrieval_Ref_No> + <CAVV_Response></CAVV_Response> + <Currency>USD</Currency> + <AmountRequested></AmountRequested> + <PartialRedemption>false</PartialRedemption> + <MerchantName>FriendlyInc</MerchantName> + <MerchantAddress>123 Main Street</MerchantAddress> + <MerchantCity>Durham</MerchantCity> + <MerchantProvince>North Carolina</MerchantProvince> + <MerchantCountry>United States</MerchantCountry> + <MerchantPostal>27592</MerchantPostal> + <MerchantURL></MerchantURL> + <TransarmorToken></TransarmorToken> + <CardType>Visa</CardType> + <CurrentBalance></CurrentBalance> + <PreviousBalance></PreviousBalance> + <EAN></EAN> + <CardCost></CardCost> + <VirtualCard>false</VirtualCard> + <CTR>=========== TRANSACTION RECORD ========== +FriendlyInc DEMO0 +123 Main Street +Durham, NC 27592 +United States + + +TYPE: Auth Only + +ACCT: Visa $ 0.00 USD + +CARDHOLDER NAME : Longbob Longsen +CARD NUMBER : ############4242 +DATE/TIME : 04 Jul 14 14:21:52 +REFERENCE # : 000040 M +AUTHOR. # : ET184931 +TRANS. REF. : 1 + + Approved - Thank You 100 + + +Please retain this copy for your records. + +Cardholder will pay above amount to card +issuer pursuant to cardholder agreement. +=========================================</CTR> +</TransactionResult> + RESPONSE + end + def no_transaction_response yamlexcep = <<-RESPONSE --- !ruby/exception:ActiveMerchant::ResponseError @@ -493,7 +1040,44 @@ def no_transaction_response read: true socket: RESPONSE - YAML.load(yamlexcep) + YAML.safe_load(yamlexcep, ['Net::HTTPBadRequest', 'ActiveMerchant::ResponseError']) + end + + def bad_credentials_response + yamlexcep = <<-RESPONSE +--- !ruby/exception:ActiveMerchant::ResponseError +message: +response: !ruby/object:Net::HTTPUnauthorized + code: '401' + message: Authorization Required + body: Unauthorized Request. Bad or missing credentials. + read: true + header: + cache-control: + - no-cache + content-type: + - text/html; charset=utf-8 + date: + - Tue, 30 Dec 2014 23:28:32 GMT + server: + - Apache + status: + - '401' + x-rack-cache: + - invalidate, pass + x-request-id: + - 4157e21cc5620a95ead8d2025b55bdf4 + x-ua-compatible: + - IE=Edge,chrome=1 + content-length: + - '49' + connection: + - Close + body_exist: true + http_version: '1.1' + socket: + RESPONSE + YAML.safe_load(yamlexcep, ['Net::HTTPUnauthorized', 'ActiveMerchant::ResponseError']) end def successful_void_response diff --git a/test/unit/gateways/firstdata_e4_v27_test.rb b/test/unit/gateways/firstdata_e4_v27_test.rb new file mode 100644 index 00000000000..8e4b4371c8a --- /dev/null +++ b/test/unit/gateways/firstdata_e4_v27_test.rb @@ -0,0 +1,1109 @@ +require 'test_helper' +require 'nokogiri' +require 'yaml' + +class FirstdataE4V27Test < Test::Unit::TestCase + include CommStub + + def setup + @gateway = FirstdataE4V27Gateway.new( + :login => 'A00427-01', + :password => 'testus', + :key_id => '12345', + :hmac_key => 'hexkey' + ) + + @credit_card = credit_card + @amount = 100 + @options = { + :order_id => '1', + :billing_address => address, + :description => 'Store Purchase' + } + @authorization = 'ET1700;106625152;4738' + end + + def test_invalid_credentials + @gateway.expects(:ssl_post).raises(bad_credentials_response) + assert response = @gateway.store(@credit_card, {}) + assert_failure response + assert response.test? + assert_equal '', response.authorization + assert_equal 'Unauthorized Request. Bad or missing credentials.', response.message + end + + def test_successful_purchase + @gateway.expects(:ssl_post).returns(successful_purchase_response) + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal 'ET1700;106625152;4738', response.authorization + assert response.test? + assert_equal 'Transaction Normal - Approved', response.message + + FirstdataE4V27Gateway::SENSITIVE_FIELDS.each { |f| assert !response.params.has_key?(f.to_s) } + end + + def test_successful_purchase_with_token + @gateway.expects(:ssl_post).returns(successful_purchase_response) + assert response = @gateway.purchase(@amount, '8938737759041111;visa;Longbob;Longsen;9;2014') + assert_success response + end + + def test_successful_purchase_with_wallet + response = stub_comms do + @gateway.purchase(@amount, @credit_card, @options.merge!({wallet_provider_id: 4})) + end.check_request do |endpoint, data, headers| + assert_match(/WalletProviderID>4</, data) + end.respond_with(successful_purchase_response) + + assert_success response + end + + def test_successful_purchase_with_stored_credentials + stored_credential = { + stored_credential: { + initial_transaction: true, + reason_type: 'unscheduled', + initiator: 'customer' + } + } + response = stub_comms do + @gateway.purchase(@amount, @credit_card, @options.merge(stored_credential)) + end.check_request do |endpoint, data, headers| + assert_match(/Indicator>1</, data) + assert_match(/Schedule>U</, data) + assert_match(/TransactionId>new</, data) + end.respond_with(successful_purchase_response_with_stored_credentials) + + assert_success response + assert_equal '732602247202501', response.params['stored_credentials_transaction_id'] + end + + def test_successful_void + @gateway.expects(:ssl_post).returns(successful_void_response) + assert response = @gateway.void(@authorization, @options) + assert_success response + end + + def test_successful_refund + @gateway.expects(:ssl_post).returns(successful_refund_response) + assert response = @gateway.refund(@amount, @authorization) + assert_success response + end + + def test_successful_store + @gateway.expects(:ssl_post).returns(successful_purchase_response) + assert response = @gateway.store(@credit_card, @options) + assert_success response + assert_equal '8938737759041111', response.params['transarmor_token'] + assert_equal "8938737759041111;visa;Longbob;Longsen;9;#{@credit_card.year}", response.authorization + end + + def test_failed_store_without_transarmor_support + @gateway.expects(:ssl_post).returns(successful_purchase_response_without_transarmor) + assert_raise StandardError do + @gateway.store(@credit_card, @options) + end + end + + def test_failed_purchase + @gateway.expects(:ssl_post).returns(failed_purchase_response) + + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_instance_of Response, response + assert_failure response + assert_equal response.error_code, 'invalid_expiry_date' + end + + def test_successful_verify + response = stub_comms do + @gateway.verify(@credit_card) + end.respond_with(successful_verify_response) + assert_success response + end + + def test_expdate + assert_equal( + '%02d%2s' % [@credit_card.month, @credit_card.year.to_s[-2..-1]], + @gateway.send(:expdate, @credit_card) + ) + end + + def test_no_transaction + @gateway.expects(:ssl_post).raises(no_transaction_response()) + assert response = @gateway.purchase(100, @credit_card, {}) + assert_failure response + assert response.test? + assert_equal 'Malformed request: Transaction Type is missing.', response.message + end + + def test_supported_countries + assert_equal ['CA', 'US'], FirstdataE4V27Gateway.supported_countries + end + + def test_supported_cardtypes + assert_equal [:visa, :master, :american_express, :jcb, :discover], FirstdataE4V27Gateway.supported_cardtypes + end + + def test_avs_result + @gateway.expects(:ssl_post).returns(successful_purchase_response) + + response = @gateway.purchase(@amount, @credit_card) + assert_equal 'U', response.avs_result['code'] + end + + def test_cvv_result + @gateway.expects(:ssl_post).returns(successful_purchase_response) + + response = @gateway.purchase(@amount, @credit_card) + assert_equal 'M', response.cvv_result['code'] + end + + def test_request_includes_address + stub_comms do + @gateway.purchase(@amount, @credit_card, @options) + end.check_request do |endpoint, data, headers| + assert_match '<Address><Address1>456 My Street</Address1><Address2>Apt 1</Address2><City>Ottawa</City><State>ON</State><Zip>K1C2N6</Zip><CountryCode>CA</CountryCode></Address>', data + end.respond_with(successful_purchase_response) + end + + def test_tax_fields_are_sent + stub_comms do + @gateway.purchase(@amount, @credit_card, @options.merge(tax1_amount: 830, tax1_number: 'Br59a')) + end.check_request do |endpoint, data, headers| + assert_match '<Tax1Amount>830', data + assert_match '<Tax1Number>Br59a', data + end.respond_with(successful_purchase_response) + end + + def test_customer_ref_is_sent + stub_comms do + @gateway.purchase(@amount, @credit_card, @options.merge(customer: '932')) + end.check_request do |endpoint, data, headers| + assert_match '<Customer_Ref>932', data + end.respond_with(successful_purchase_response) + end + + def test_eci_default_value + stub_comms do + @gateway.purchase(@amount, @credit_card, @options) + end.check_request do |endpoint, data, headers| + assert_match '<Ecommerce_Flag>07</Ecommerce_Flag>', data + end.respond_with(successful_purchase_response) + end + + def test_eci_numeric_padding + @credit_card = network_tokenization_credit_card + @credit_card.eci = '5' + + stub_comms do + @gateway.purchase(@amount, @credit_card, @options) + end.check_request do |endpoint, data, headers| + assert_match '<Ecommerce_Flag>05</Ecommerce_Flag>', data + end.respond_with(successful_purchase_response) + + @credit_card = network_tokenization_credit_card + @credit_card.eci = 5 + + stub_comms do + @gateway.purchase(@amount, @credit_card, @options) + end.check_request do |endpoint, data, headers| + assert_match '<Ecommerce_Flag>05</Ecommerce_Flag>', data + end.respond_with(successful_purchase_response) + end + + def test_eci_option_value + stub_comms do + @gateway.purchase(@amount, @credit_card, @options.merge(eci: '05')) + end.check_request do |endpoint, data, headers| + assert_match '<Ecommerce_Flag>05</Ecommerce_Flag>', data + end.respond_with(successful_purchase_response) + end + + def test_network_tokenization_requests_with_amex + stub_comms do + credit_card = network_tokenization_credit_card( + '378282246310005', + brand: 'american_express', + transaction_id: '123', + eci: '05', + payment_cryptogram: 'whatever_the_cryptogram_of_at_least_20_characters_is' + ) + + @gateway.purchase(@amount, credit_card, @options) + end.check_request do |_, data, _| + assert_match '<Ecommerce_Flag>05</Ecommerce_Flag>', data + assert_match "<XID>mrLdtHIWq2nLXq7IrA==\n</XID>", data + assert_match "<CAVV>whateverthecryptogramofatlc=\n</CAVV>", data + assert_xml_valid_to_wsdl(data) + end.respond_with(successful_purchase_response) + end + + def test_network_tokenization_requests_with_discover + stub_comms do + credit_card = network_tokenization_credit_card( + '6011111111111117', + brand: 'discover', + transaction_id: '123', + eci: '05', + payment_cryptogram: 'whatever_the_cryptogram_is' + ) + + @gateway.purchase(@amount, credit_card, @options) + end.check_request do |_, data, _| + assert_match '<Ecommerce_Flag>04</Ecommerce_Flag>', data + assert_match '<XID>123</XID>', data + assert_match '<CAVV>whatever_the_cryptogram_is</CAVV>', data + assert_xml_valid_to_wsdl(data) + end.respond_with(successful_purchase_response) + end + + def test_network_tokenization_requests_with_other_brands + %w(visa mastercard other).each do |brand| + stub_comms do + credit_card = network_tokenization_credit_card( + '378282246310005', + brand: brand, + transaction_id: '123', + eci: '05', + payment_cryptogram: 'whatever_the_cryptogram_is' + ) + + @gateway.purchase(@amount, credit_card, @options) + end.check_request do |_, data, _| + assert_match '<Ecommerce_Flag>05</Ecommerce_Flag>', data + assert_match '<XID>123</XID>', data + assert_match '<CAVV>whatever_the_cryptogram_is</CAVV>', data + assert_xml_valid_to_wsdl(data) + end.respond_with(successful_purchase_response) + end + end + + def test_requests_include_card_authentication_data + authentication_hash = { + eci: '06', + cavv: 'SAMPLECAVV', + xid: 'SAMPLEXID' + } + options_with_authentication_data = @options.merge(authentication_hash) + + stub_comms do + @gateway.purchase(@amount, @credit_card, options_with_authentication_data) + end.check_request do |endpoint, data, headers| + assert_match '<Ecommerce_Flag>06</Ecommerce_Flag>', data + assert_match '<CAVV>SAMPLECAVV</CAVV>', data + assert_match '<XID>SAMPLEXID</XID>', data + assert_xml_valid_to_wsdl(data) + end.respond_with(successful_purchase_response) + end + + def test_card_type + assert_equal 'Visa', @gateway.send(:card_type, 'visa') + assert_equal 'Mastercard', @gateway.send(:card_type, 'master') + assert_equal 'American Express', @gateway.send(:card_type, 'american_express') + assert_equal 'JCB', @gateway.send(:card_type, 'jcb') + assert_equal 'Discover', @gateway.send(:card_type, 'discover') + end + + def test_add_swipe_data_with_creditcard + @credit_card.track_data = 'Track Data' + + stub_comms do + @gateway.purchase(@amount, @credit_card) + end.check_request do |endpoint, data, headers| + assert_match '<Track1>Track Data</Track1>', data + assert_match '<Ecommerce_Flag>R</Ecommerce_Flag>', data + end.respond_with(successful_purchase_response) + end + + def test_transcript_scrubbing + assert @gateway.supports_scrubbing? + assert_equal @gateway.scrub(pre_scrub), post_scrub + end + + def test_supports_network_tokenization + assert_instance_of TrueClass, @gateway.supports_network_tokenization? + end + + private + + def assert_xml_valid_to_wsdl(data) + xsd = Nokogiri::XML::Schema(File.open("#{File.dirname(__FILE__)}/../../schema/firstdata_e4/v27.xsd")) + doc = Nokogiri::XML(data) + errors = xsd.validate(doc) + assert_empty errors, "XSD validation errors in the following XML:\n#{doc}" + end + + def pre_scrub + <<-PRE_SCRUBBED + opening connection to api.demo.globalgatewaye4.firstdata.com:443... + opened + starting SSL for api.demo.globalgatewaye4.firstdata.com:443... + SSL established + <- "POST /transaction/v27 HTTP/1.1\r\nContent-Type: application/xml\r\nX-Gge4-Date: 2018-07-03T07:35:34Z\r\nX-Gge4-Content-Sha1: 5335f81daf59c493fe5d4c18910d17eba69558d4\r\nAuthorization: GGE4_API 397439:iwaxRr8f3GQIMSucb+dmDeiwoAk=\r\nAccepts: application/xml\r\nConnection: close\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nHost: api.demo.globalgatewaye4.firstdata.com\r\nContent-Length: 807\r\n\r\n" + <- "<?xml version=\"1.0\" encoding=\"UTF-8\"?><Transaction xmlns=\"http://secure2.e-xact.com/vplug-in/transaction/rpc-enc/encodedTypes\"><ExactID>SD8821-67</ExactID><Password>cBhEc4GENtZ5fnVtpb2qlrhKUDprqtar</Password><Transaction_Type>00</Transaction_Type><DollarAmount>1.00</DollarAmount><Currency>USD</Currency><Card_Number>4242424242424242</Card_Number><Expiry_Date>0919</Expiry_Date><CardHoldersName>Longbob Longsen</CardHoldersName><CardType>Visa</CardType><Ecommerce_Flag>07</Ecommerce_Flag><CVD_Presence_Ind>1</CVD_Presence_Ind><CVDCode>123</CVDCode><CAVV/><XID/><Address><Address1>456 My Street</Address1><Address2>Apt 1</Address2><City>Ottawa</City><State>ON</State><Zip>K1C2N6</Zip><CountryCode>CA</CountryCode></Address><Reference_No>1</Reference_No><Reference_3>Store Purchase</Reference_3></Transaction>" + -> "HTTP/1.1 201 Created\r\n" + -> "Server: nginx\r\n" + -> "Date: Tue, 03 Jul 2018 07:35:34 GMT\r\n" + -> "Content-Type: application/xml; charset=utf-8\r\n" + -> "Transfer-Encoding: chunked\r\n" + -> "Connection: close\r\n" + -> "X-Frame-Options: SAMEORIGIN\r\n" + -> "X-XSS-Protection: 1; mode=block\r\n" + -> "X-Content-Type-Options: nosniff\r\n" + -> "X-GGE4-Date: 2018-07-03T07:35:34Z\r\n" + -> "X-GGE4-CONTENT-SHA1: 87ae8c40ae5afbfd060c7569645f3f6b4045994f\r\n" + -> "Authorization: GGE4_API 397439:dPOI+d2MkNJjBJhHTYUo0ieILw4=\r\n" + -> "Pragma: no-cache\r\n" + -> "Expires: Tue, 03 Jul 2018 06:35:34 GMT\r\n" + -> "Cache-Control: no-store, no-cache\r\n" + -> "X-Request-Id: 0f9ba7k2rd80a2tpch40\r\n" + -> "Location: https://api.demo.globalgatewaye4.firstdata.com/transaction/v27/2264726018\r\n" + -> "Status: 201 Created\r\n" + -> "Strict-Transport-Security: max-age=31536000; includeSubDomains\r\n" + -> "Strict-Transport-Security: max-age=315360000; includeSubdomains\r\n" + -> "\r\n" + -> "2da\r\n" + reading 2944 bytes... + -> "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<TransactionResult>\n <ExactID>SD8821-67</ExactID>\n <Password/>\n <Transaction_Type>00</Transaction_Type>\n <DollarAmount>1.0</DollarAmount>\n <SurchargeAmount/>\n <Card_Number>############4242</Card_Number>\n <Transaction_Tag>2264726018</Transaction_Tag>\n <SplitTenderID/>\n <Track1/>\n <Track2/>\n <PAN/>\n <Authorization_Num>ET121995</Authorization_Num>\n <Expiry_Date>0919</Expiry_Date>\n <CardHoldersName>Longbob Longsen</CardHoldersName>\n <CVD_Presence_Ind>1</CVD_Presence_Ind>\n <ZipCode/>\n <Tax1Amount/>\n <Tax1Number/>\n <Tax2Amount/>\n <Tax2Number/>\n <Secure_AuthRequired/>\n <Secure_AuthResult/>\n <Ecommerce_Flag>7</Ecommerce_Flag>\n <XID/>\n <CAVV/>\n <Reference_No>1</Reference_No>\n <Customer_Ref/>\n <Reference_3>Store Purchase</Reference_3>\n <Language/>\n <Client_IP/>\n <Client_Email/>\n <User_Name/>\n <Transaction_Error>false</Transaction_Error>\n <Transaction_Approved>true</Transaction_Approved>\n <EXact_Resp_Code>00</EXact_Resp_Code>\n <EXact_Message>Transaction Normal</EXact_Message>\n <Bank_Resp_Code>100</Bank_Resp_Code>\n <Bank_Message>Approved</Bank_Message>\n <Bank_Resp_Code_2/>\n <SequenceNo>001157</SequenceNo>\n <AVS>4</AVS>\n <CVV2>M</CVV2>\n <Retrieval_Ref_No>1196543</Retrieval_Ref_No>\n <CAVV_Response/>\n <Currency>USD</Currency>\n <AmountRequested/>\n <PartialRedemption>false</PartialRedemption>\n <MerchantName>Spreedly DEMO0095</MerchantName>\n <MerchantAddress>123 Testing</MerchantAddress>\n <MerchantCity>Durham</MerchantCity>\n <MerchantProvince>North Carolina</MerchantProvince>\n <MerchantCountry>United States</MerchantCountry>\n <MerchantPostal>27701</MerchantPostal>\n <MerchantURL/>\n <TransarmorToken/>\n <CardType>Visa</CardType>\n <CurrentBalance/>\n <PreviousBalance/>\n <EAN/>\n <CardCost/>\n <VirtualCard>false</VirtualCard>\n <CTR>========== TRANSACTION RECORD ==========\nSpreedly DEMO0095\n123 Testing\nDurham, NC 27701\nUnited States\n\n\nTYPE: Purchase\n\nACCT: Visa $ 1.00 USD\n\nCARDHOLDER NAME : Longbob Longsen\nCARD NUMBER : ############4242\nDATE/TIME : 03 Jul 18 03:35:34\nREFERENCE # : 03 001157 M\nAUTHOR. # : ET121995\nTRANS. REF. : 1\n\n Approved - Thank You 100\n\n\nPlease retain this copy for your records.\n\nCardholder will pay above amount to\ncard issuer pursuant to cardholder\nagreement.\n========================================</CTR>\n <FraudSuspected/>\n <Address>\n <Address1>456 My Street</Address1>\n <Address2>Apt 1</Address2>\n <City>Ottawa</City>\n <State>ON</State>\n <Zip>K1C2N6</Zip>\n <CountryCode>CA</CountryCode>\n </Address>\n <CVDCode>123</CVDCode>\n <SplitShipmentNumber/>\n</TransactionResult>\n" + read 2944 bytes + Conn close + PRE_SCRUBBED + end + + def post_scrub + <<-POST_SCRUBBED + opening connection to api.demo.globalgatewaye4.firstdata.com:443... + opened + starting SSL for api.demo.globalgatewaye4.firstdata.com:443... + SSL established + <- "POST /transaction/v27 HTTP/1.1\r\nContent-Type: application/xml\r\nX-Gge4-Date: 2018-07-03T07:35:34Z\r\nX-Gge4-Content-Sha1: 5335f81daf59c493fe5d4c18910d17eba69558d4\r\nAuthorization: GGE4_API 397439:iwaxRr8f3GQIMSucb+dmDeiwoAk=\r\nAccepts: application/xml\r\nConnection: close\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nHost: api.demo.globalgatewaye4.firstdata.com\r\nContent-Length: 807\r\n\r\n" + <- "<?xml version=\"1.0\" encoding=\"UTF-8\"?><Transaction xmlns=\"http://secure2.e-xact.com/vplug-in/transaction/rpc-enc/encodedTypes\"><ExactID>SD8821-67</ExactID><Password>[FILTERED]</Password><Transaction_Type>00</Transaction_Type><DollarAmount>1.00</DollarAmount><Currency>USD</Currency><Card_Number>[FILTERED]</Card_Number><Expiry_Date>0919</Expiry_Date><CardHoldersName>Longbob Longsen</CardHoldersName><CardType>Visa</CardType><Ecommerce_Flag>07</Ecommerce_Flag><CVD_Presence_Ind>1</CVD_Presence_Ind><CVDCode>[FILTERED]</CVDCode><CAVV/><XID/><Address><Address1>456 My Street</Address1><Address2>Apt 1</Address2><City>Ottawa</City><State>ON</State><Zip>K1C2N6</Zip><CountryCode>CA</CountryCode></Address><Reference_No>1</Reference_No><Reference_3>Store Purchase</Reference_3></Transaction>" + -> "HTTP/1.1 201 Created\r\n" + -> "Server: nginx\r\n" + -> "Date: Tue, 03 Jul 2018 07:35:34 GMT\r\n" + -> "Content-Type: application/xml; charset=utf-8\r\n" + -> "Transfer-Encoding: chunked\r\n" + -> "Connection: close\r\n" + -> "X-Frame-Options: SAMEORIGIN\r\n" + -> "X-XSS-Protection: 1; mode=block\r\n" + -> "X-Content-Type-Options: nosniff\r\n" + -> "X-GGE4-Date: 2018-07-03T07:35:34Z\r\n" + -> "X-GGE4-CONTENT-SHA1: 87ae8c40ae5afbfd060c7569645f3f6b4045994f\r\n" + -> "Authorization: GGE4_API 397439:dPOI+d2MkNJjBJhHTYUo0ieILw4=\r\n" + -> "Pragma: no-cache\r\n" + -> "Expires: Tue, 03 Jul 2018 06:35:34 GMT\r\n" + -> "Cache-Control: no-store, no-cache\r\n" + -> "X-Request-Id: 0f9ba7k2rd80a2tpch40\r\n" + -> "Location: https://api.demo.globalgatewaye4.firstdata.com/transaction/v27/2264726018\r\n" + -> "Status: 201 Created\r\n" + -> "Strict-Transport-Security: max-age=31536000; includeSubDomains\r\n" + -> "Strict-Transport-Security: max-age=315360000; includeSubdomains\r\n" + -> "\r\n" + -> "2da\r\n" + reading 2944 bytes... + -> "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<TransactionResult>\n <ExactID>SD8821-67</ExactID>\n <Password/>\n <Transaction_Type>00</Transaction_Type>\n <DollarAmount>1.0</DollarAmount>\n <SurchargeAmount/>\n <Card_Number>[FILTERED]</Card_Number>\n <Transaction_Tag>2264726018</Transaction_Tag>\n <SplitTenderID/>\n <Track1/>\n <Track2/>\n <PAN/>\n <Authorization_Num>ET121995</Authorization_Num>\n <Expiry_Date>0919</Expiry_Date>\n <CardHoldersName>Longbob Longsen</CardHoldersName>\n <CVD_Presence_Ind>1</CVD_Presence_Ind>\n <ZipCode/>\n <Tax1Amount/>\n <Tax1Number/>\n <Tax2Amount/>\n <Tax2Number/>\n <Secure_AuthRequired/>\n <Secure_AuthResult/>\n <Ecommerce_Flag>7</Ecommerce_Flag>\n <XID/>\n <CAVV/>\n <Reference_No>1</Reference_No>\n <Customer_Ref/>\n <Reference_3>Store Purchase</Reference_3>\n <Language/>\n <Client_IP/>\n <Client_Email/>\n <User_Name/>\n <Transaction_Error>false</Transaction_Error>\n <Transaction_Approved>true</Transaction_Approved>\n <EXact_Resp_Code>00</EXact_Resp_Code>\n <EXact_Message>Transaction Normal</EXact_Message>\n <Bank_Resp_Code>100</Bank_Resp_Code>\n <Bank_Message>Approved</Bank_Message>\n <Bank_Resp_Code_2/>\n <SequenceNo>001157</SequenceNo>\n <AVS>4</AVS>\n <CVV2>M</CVV2>\n <Retrieval_Ref_No>1196543</Retrieval_Ref_No>\n <CAVV_Response/>\n <Currency>USD</Currency>\n <AmountRequested/>\n <PartialRedemption>false</PartialRedemption>\n <MerchantName>Spreedly DEMO0095</MerchantName>\n <MerchantAddress>123 Testing</MerchantAddress>\n <MerchantCity>Durham</MerchantCity>\n <MerchantProvince>North Carolina</MerchantProvince>\n <MerchantCountry>United States</MerchantCountry>\n <MerchantPostal>27701</MerchantPostal>\n <MerchantURL/>\n <TransarmorToken/>\n <CardType>Visa</CardType>\n <CurrentBalance/>\n <PreviousBalance/>\n <EAN/>\n <CardCost/>\n <VirtualCard>false</VirtualCard>\n <CTR>========== TRANSACTION RECORD ==========\nSpreedly DEMO0095\n123 Testing\nDurham, NC 27701\nUnited States\n\n\nTYPE: Purchase\n\nACCT: Visa $ 1.00 USD\n\nCARDHOLDER NAME : Longbob Longsen\nCARD NUMBER : [FILTERED]\nDATE/TIME : 03 Jul 18 03:35:34\nREFERENCE # : 03 001157 M\nAUTHOR. # : ET121995\nTRANS. REF. : 1\n\n Approved - Thank You 100\n\n\nPlease retain this copy for your records.\n\nCardholder will pay above amount to\ncard issuer pursuant to cardholder\nagreement.\n========================================</CTR>\n <FraudSuspected/>\n <Address>\n <Address1>456 My Street</Address1>\n <Address2>Apt 1</Address2>\n <City>Ottawa</City>\n <State>ON</State>\n <Zip>K1C2N6</Zip>\n <CountryCode>CA</CountryCode>\n </Address>\n <CVDCode>[FILTERED]</CVDCode>\n <SplitShipmentNumber/>\n</TransactionResult>\n" + read 2944 bytes + Conn close + POST_SCRUBBED + end + + def successful_purchase_response + <<-RESPONSE + <?xml version="1.0" encoding="UTF-8"?> + <TransactionResult> + <ExactID>AD1234-56</ExactID> + <Password></Password> + <Transaction_Type>00</Transaction_Type> + <DollarAmount>47.38</DollarAmount> + <SurchargeAmount></SurchargeAmount> + <Card_Number>############1111</Card_Number> + <Transaction_Tag>106625152</Transaction_Tag> + <Track1></Track1> + <Track2></Track2> + <PAN></PAN> + <Authorization_Num>ET1700</Authorization_Num> + <Expiry_Date>0913</Expiry_Date> + <CardHoldersName>Fred Burfle</CardHoldersName> + <CVD_Presence_Ind>0</CVD_Presence_Ind> + <ZipCode></ZipCode> + <Tax1Amount></Tax1Amount> + <Tax1Number></Tax1Number> + <Tax2Amount></Tax2Amount> + <Tax2Number></Tax2Number> + <Secure_AuthRequired></Secure_AuthRequired> + <Secure_AuthResult></Secure_AuthResult> + <Ecommerce_Flag></Ecommerce_Flag> + <XID></XID> + <CAVV></CAVV> + <CAVV_Algorithm></CAVV_Algorithm> + <Reference_No>77</Reference_No> + <Customer_Ref></Customer_Ref> + <Reference_3></Reference_3> + <Language></Language> + <Client_IP>1.1.1.10</Client_IP> + <Client_Email></Client_Email> + <Transaction_Error>false</Transaction_Error> + <Transaction_Approved>true</Transaction_Approved> + <EXact_Resp_Code>00</EXact_Resp_Code> + <EXact_Message>Transaction Normal</EXact_Message> + <Bank_Resp_Code>100</Bank_Resp_Code> + <Bank_Message>Approved</Bank_Message> + <Bank_Resp_Code_2></Bank_Resp_Code_2> + <SequenceNo>000040</SequenceNo> + <AVS>U</AVS> + <CVV2>M</CVV2> + <Retrieval_Ref_No>3146117</Retrieval_Ref_No> + <CAVV_Response></CAVV_Response> + <Currency>USD</Currency> + <AmountRequested></AmountRequested> + <PartialRedemption>false</PartialRedemption> + <MerchantName>Friendly Inc DEMO0983</MerchantName> + <MerchantAddress>123 King St</MerchantAddress> + <MerchantCity>Toronto</MerchantCity> + <MerchantProvince>Ontario</MerchantProvince> + <MerchantCountry>Canada</MerchantCountry> + <MerchantPostal>L7Z 3K8</MerchantPostal> + <MerchantURL></MerchantURL> + <TransarmorToken>8938737759041111</TransarmorToken> + <CTR>=========== TRANSACTION RECORD ========== +Friendly Inc DEMO0983 +123 King St +Toronto, ON L7Z 3K8 +Canada + + +TYPE: Purchase + +ACCT: Visa $ 47.38 USD + +CARD NUMBER : ############1111 +DATE/TIME : 28 Sep 12 07:54:48 +REFERENCE # : 000040 M +AUTHOR. # : ET120454 +TRANS. REF. : 77 + + Approved - Thank You 100 + + +Please retain this copy for your records. + +Cardholder will pay above amount to card +issuer pursuant to cardholder agreement. +=========================================</CTR> + <Address> + <Address1>456 My Street</Address1> + <Address2>Apt 1</Address2> + <City>Ottawa</City> + <State>ON</State> + <Zip>K1C2N6</Zip> + <CountryCode>CA</CountryCode> + </Address> + </TransactionResult> + RESPONSE + end + + def successful_purchase_response_with_stored_credentials + <<-RESPONSE + <?xml version="1.0" encoding="UTF-8"?> + <TransactionResult> + <ExactID>AD1234-56</ExactID> + <Password></Password> + <Transaction_Type>00</Transaction_Type> + <DollarAmount>47.38</DollarAmount> + <SurchargeAmount></SurchargeAmount> + <Card_Number>############1111</Card_Number> + <Transaction_Tag>106625152</Transaction_Tag> + <Track1></Track1> + <Track2></Track2> + <PAN></PAN> + <Authorization_Num>ET1700</Authorization_Num> + <Expiry_Date>0913</Expiry_Date> + <CardHoldersName>Fred Burfle</CardHoldersName> + <CVD_Presence_Ind>0</CVD_Presence_Ind> + <ZipCode></ZipCode> + <Tax1Amount></Tax1Amount> + <Tax1Number></Tax1Number> + <Tax2Amount></Tax2Amount> + <Tax2Number></Tax2Number> + <Secure_AuthRequired></Secure_AuthRequired> + <Secure_AuthResult></Secure_AuthResult> + <Ecommerce_Flag></Ecommerce_Flag> + <XID></XID> + <CAVV></CAVV> + <CAVV_Algorithm></CAVV_Algorithm> + <Reference_No>77</Reference_No> + <Customer_Ref></Customer_Ref> + <Reference_3></Reference_3> + <Language></Language> + <Client_IP>1.1.1.10</Client_IP> + <Client_Email></Client_Email> + <Transaction_Error>false</Transaction_Error> + <Transaction_Approved>true</Transaction_Approved> + <EXact_Resp_Code>00</EXact_Resp_Code> + <EXact_Message>Transaction Normal</EXact_Message> + <Bank_Resp_Code>100</Bank_Resp_Code> + <Bank_Message>Approved</Bank_Message> + <Bank_Resp_Code_2></Bank_Resp_Code_2> + <SequenceNo>000040</SequenceNo> + <AVS>U</AVS> + <CVV2>M</CVV2> + <Retrieval_Ref_No>3146117</Retrieval_Ref_No> + <CAVV_Response></CAVV_Response> + <Currency>USD</Currency> + <AmountRequested></AmountRequested> + <PartialRedemption>false</PartialRedemption> + <MerchantName>Friendly Inc DEMO0983</MerchantName> + <MerchantAddress>123 King St</MerchantAddress> + <MerchantCity>Toronto</MerchantCity> + <MerchantProvince>Ontario</MerchantProvince> + <MerchantCountry>Canada</MerchantCountry> + <MerchantPostal>L7Z 3K8</MerchantPostal> + <MerchantURL></MerchantURL> + <TransarmorToken>8938737759041111</TransarmorToken> + <CTR>=========== TRANSACTION RECORD ========== +Friendly Inc DEMO0983 +123 King St +Toronto, ON L7Z 3K8 +Canada + + +TYPE: Purchase + +ACCT: Visa $ 47.38 USD + +CARD NUMBER : ############1111 +DATE/TIME : 28 Sep 12 07:54:48 +REFERENCE # : 000040 M +AUTHOR. # : ET120454 +TRANS. REF. : 77 + + Approved - Thank You 100 + + +Please retain this copy for your records. + +Cardholder will pay above amount to card +issuer pursuant to cardholder agreement. +=========================================</CTR> + <Address> + <Address1>456 My Street</Address1> + <Address2>Apt 1</Address2> + <City>Ottawa</City> + <State>ON</State> + <Zip>K1C2N6</Zip> + <CountryCode>CA</CountryCode> + </Address> + <StoredCredentials> + <Indicator>1</Indicator> + <Schedule>U</Schedule> + <TransactionId>732602247202501</TransactionId> + </StoredCredentials> + </TransactionResult> + RESPONSE + end + + def successful_purchase_response_without_transarmor + <<-RESPONSE + <?xml version="1.0" encoding="UTF-8"?> + <TransactionResult> + <ExactID>AD1234-56</ExactID> + <Password></Password> + <Transaction_Type>00</Transaction_Type> + <DollarAmount>47.38</DollarAmount> + <SurchargeAmount></SurchargeAmount> + <Card_Number>############1111</Card_Number> + <Transaction_Tag>106625152</Transaction_Tag> + <Track1></Track1> + <Track2></Track2> + <PAN></PAN> + <Authorization_Num>ET1700</Authorization_Num> + <Expiry_Date>0913</Expiry_Date> + <CardHoldersName>Fred Burfle</CardHoldersName> + <CVD_Presence_Ind>0</CVD_Presence_Ind> + <ZipCode></ZipCode> + <Tax1Amount></Tax1Amount> + <Tax1Number></Tax1Number> + <Tax2Amount></Tax2Amount> + <Tax2Number></Tax2Number> + <Secure_AuthRequired></Secure_AuthRequired> + <Secure_AuthResult></Secure_AuthResult> + <Ecommerce_Flag></Ecommerce_Flag> + <XID></XID> + <CAVV></CAVV> + <CAVV_Algorithm></CAVV_Algorithm> + <Reference_No>77</Reference_No> + <Customer_Ref></Customer_Ref> + <Reference_3></Reference_3> + <Language></Language> + <Client_IP>1.1.1.10</Client_IP> + <Client_Email></Client_Email> + <Transaction_Error>false</Transaction_Error> + <Transaction_Approved>true</Transaction_Approved> + <EXact_Resp_Code>00</EXact_Resp_Code> + <EXact_Message>Transaction Normal</EXact_Message> + <Bank_Resp_Code>100</Bank_Resp_Code> + <Bank_Message>Approved</Bank_Message> + <Bank_Resp_Code_2></Bank_Resp_Code_2> + <SequenceNo>000040</SequenceNo> + <AVS>U</AVS> + <CVV2>M</CVV2> + <Retrieval_Ref_No>3146117</Retrieval_Ref_No> + <CAVV_Response></CAVV_Response> + <Currency>USD</Currency> + <AmountRequested></AmountRequested> + <PartialRedemption>false</PartialRedemption> + <MerchantName>Friendly Inc DEMO0983</MerchantName> + <MerchantAddress>123 King St</MerchantAddress> + <MerchantCity>Toronto</MerchantCity> + <MerchantProvince>Ontario</MerchantProvince> + <MerchantCountry>Canada</MerchantCountry> + <MerchantPostal>L7Z 3K8</MerchantPostal> + <MerchantURL></MerchantURL> + <TransarmorToken></TransarmorToken> + <CTR>=========== TRANSACTION RECORD ========== +Friendly Inc DEMO0983 +123 King St +Toronto, ON L7Z 3K8 +Canada + + +TYPE: Purchase + +ACCT: Visa $ 47.38 USD + +CARD NUMBER : ############1111 +DATE/TIME : 28 Sep 12 07:54:48 +REFERENCE # : 000040 M +AUTHOR. # : ET120454 +TRANS. REF. : 77 + + Approved - Thank You 100 + + +Please retain this copy for your records. + +Cardholder will pay above amount to card +issuer pursuant to cardholder agreement. +=========================================</CTR> + </TransactionResult> + RESPONSE + end + + def successful_refund_response + <<-RESPONSE + <?xml version="1.0" encoding="UTF-8"?> + <TransactionResult> + <ExactID>AD1234-56</ExactID> + <Password></Password> + <Transaction_Type>34</Transaction_Type> + <DollarAmount>123</DollarAmount> + <SurchargeAmount></SurchargeAmount> + <Card_Number>############1111</Card_Number> + <Transaction_Tag>888</Transaction_Tag> + <Track1></Track1> + <Track2></Track2> + <PAN></PAN> + <Authorization_Num>ET112216</Authorization_Num> + <Expiry_Date>0913</Expiry_Date> + <CardHoldersName>Fred Burfle</CardHoldersName> + <CVD_Presence_Ind>0</CVD_Presence_Ind> + <ZipCode></ZipCode> + <Tax1Amount></Tax1Amount> + <Tax1Number></Tax1Number> + <Tax2Amount></Tax2Amount> + <Tax2Number></Tax2Number> + <Secure_AuthRequired></Secure_AuthRequired> + <Secure_AuthResult></Secure_AuthResult> + <Ecommerce_Flag></Ecommerce_Flag> + <XID></XID> + <CAVV></CAVV> + <CAVV_Algorithm></CAVV_Algorithm> + <Reference_No></Reference_No> + <Customer_Ref></Customer_Ref> + <Reference_3></Reference_3> + <Language></Language> + <Client_IP>1.1.1.10</Client_IP> + <Client_Email></Client_Email> + <Transaction_Error>false</Transaction_Error> + <Transaction_Approved>true</Transaction_Approved> + <EXact_Resp_Code>00</EXact_Resp_Code> + <EXact_Message>Transaction Normal</EXact_Message> + <Bank_Resp_Code>100</Bank_Resp_Code> + <Bank_Message>Approved</Bank_Message> + <Bank_Resp_Code_2></Bank_Resp_Code_2> + <SequenceNo>000041</SequenceNo> + <AVS></AVS> + <CVV2>I</CVV2> + <Retrieval_Ref_No>9176784</Retrieval_Ref_No> + <CAVV_Response></CAVV_Response> + <Currency>USD</Currency> + <AmountRequested></AmountRequested> + <PartialRedemption>false</PartialRedemption> + <MerchantName>Friendly Inc DEMO0983</MerchantName> + <MerchantAddress>123 King St</MerchantAddress> + <MerchantCity>Toronto</MerchantCity> + <MerchantProvince>Ontario</MerchantProvince> + <MerchantCountry>Canada</MerchantCountry> + <MerchantPostal>L7Z 3K8</MerchantPostal> + <MerchantURL></MerchantURL> + <CTR>=========== TRANSACTION RECORD ========== +Friendly Inc DEMO0983 +123 King St +Toronto, ON L7Z 3K8 +Canada + + +TYPE: Refund + +ACCT: Visa $ 23.69 USD + +CARD NUMBER : ############1111 +DATE/TIME : 28 Sep 12 08:31:23 +REFERENCE # : 000041 M +AUTHOR. # : ET112216 +TRANS. REF. : + + Approved - Thank You 100 + + +Please retain this copy for your records. + +=========================================</CTR> + </TransactionResult> + RESPONSE + end + + def failed_purchase_response + <<-RESPONSE + <?xml version="1.0" encoding="UTF-8"?> + <TransactionResult> + <ExactID>AD1234-56</ExactID> + <Password></Password> + <Transaction_Type>00</Transaction_Type> + <DollarAmount>5013.0</DollarAmount> + <SurchargeAmount></SurchargeAmount> + <Card_Number>############1111</Card_Number> + <Transaction_Tag>555555</Transaction_Tag> + <Track1></Track1> + <Track2></Track2> + <PAN></PAN> + <Authorization_Num></Authorization_Num> + <Expiry_Date>0911</Expiry_Date> + <CardHoldersName>Fred Burfle</CardHoldersName> + <CVD_Presence_Ind>0</CVD_Presence_Ind> + <ZipCode></ZipCode> + <Tax1Amount></Tax1Amount> + <Tax1Number></Tax1Number> + <Tax2Amount></Tax2Amount> + <Tax2Number></Tax2Number> + <Secure_AuthRequired></Secure_AuthRequired> + <Secure_AuthResult></Secure_AuthResult> + <Ecommerce_Flag></Ecommerce_Flag> + <XID></XID> + <CAVV></CAVV> + <CAVV_Algorithm></CAVV_Algorithm> + <Reference_No>77</Reference_No> + <Customer_Ref></Customer_Ref> + <Reference_3></Reference_3> + <Language></Language> + <Client_IP>1.1.1.10</Client_IP> + <Client_Email></Client_Email> + <LogonMessage></LogonMessage> + <Error_Number>0</Error_Number> + <Error_Description> </Error_Description> + <Transaction_Error>false</Transaction_Error> + <Transaction_Approved>false</Transaction_Approved> + <EXact_Resp_Code>00</EXact_Resp_Code> + <EXact_Message>Transaction Normal</EXact_Message> + <Bank_Resp_Code>605</Bank_Resp_Code> + <Bank_Message>Invalid Expiration Date</Bank_Message> + <Bank_Resp_Code_2></Bank_Resp_Code_2> + <SequenceNo>000033</SequenceNo> + <AVS></AVS> + <CVV2></CVV2> + <Retrieval_Ref_No></Retrieval_Ref_No> + <CAVV_Response></CAVV_Response> + <Currency>USD</Currency> + <AmountRequested></AmountRequested> + <PartialRedemption>false</PartialRedemption> + <MerchantName>Friendly Inc DEMO0983</MerchantName> + <MerchantAddress>123 King St</MerchantAddress> + <MerchantCity>Toronto</MerchantCity> + <MerchantProvince>Ontario</MerchantProvince> + <MerchantCountry>Canada</MerchantCountry> + <MerchantPostal>L7Z 3K8</MerchantPostal> + <MerchantURL></MerchantURL> + <CTR>=========== TRANSACTION RECORD ========== +Friendly Inc DEMO0983 +123 King St +Toronto, ON L7Z 3K8 +Canada + + +TYPE: Purchase +ACCT: Visa $ 5,013.00 USD +CARD NUMBER : ############1111 +DATE/TIME : 25 Sep 12 07:27:00 +REFERENCE # : 000033 M +AUTHOR. # : +TRANS. REF. : 77 +Transaction not approved 605 +Please retain this copy for your records. +=========================================</CTR> + </TransactionResult> + RESPONSE + end + + def successful_verify_response + <<-RESPONSE +<?xml version="1.0" encoding="UTF-8"?> +<TransactionResult> + <ExactID>AD2552-05</ExactID> + <Password></Password> + <Transaction_Type>05</Transaction_Type> + <DollarAmount>0.0</DollarAmount> + <SurchargeAmount></SurchargeAmount> + <Card_Number>############4242</Card_Number> + <Transaction_Tag>25101911</Transaction_Tag> + <Track1></Track1> + <Track2></Track2> + <PAN></PAN> + <Authorization_Num>ET184931</Authorization_Num> + <Expiry_Date>0915</Expiry_Date> + <CardHoldersName>Longbob Longsen</CardHoldersName> + <CVD_Presence_Ind>0</CVD_Presence_Ind> + <ZipCode></ZipCode> + <Tax1Amount></Tax1Amount> + <Tax1Number></Tax1Number> + <Tax2Amount></Tax2Amount> + <Tax2Number></Tax2Number> + <Secure_AuthRequired></Secure_AuthRequired> + <Secure_AuthResult></Secure_AuthResult> + <Ecommerce_Flag></Ecommerce_Flag> + <XID></XID> + <CAVV></CAVV> + <CAVV_Algorithm></CAVV_Algorithm> + <Reference_No>1</Reference_No> + <Customer_Ref></Customer_Ref> + <Reference_3>Store Purchase</Reference_3> + <Language></Language> + <Client_IP>75.182.123.244</Client_IP> + <Client_Email></Client_Email> + <Transaction_Error>false</Transaction_Error> + <Transaction_Approved>true</Transaction_Approved> + <EXact_Resp_Code>00</EXact_Resp_Code> + <EXact_Message>Transaction Normal</EXact_Message> + <Bank_Resp_Code>100</Bank_Resp_Code> + <Bank_Message>Approved</Bank_Message> + <Bank_Resp_Code_2></Bank_Resp_Code_2> + <SequenceNo>000040</SequenceNo> + <AVS>1</AVS> + <CVV2>M</CVV2> + <Retrieval_Ref_No>7228838</Retrieval_Ref_No> + <CAVV_Response></CAVV_Response> + <Currency>USD</Currency> + <AmountRequested></AmountRequested> + <PartialRedemption>false</PartialRedemption> + <MerchantName>FriendlyInc</MerchantName> + <MerchantAddress>123 Main Street</MerchantAddress> + <MerchantCity>Durham</MerchantCity> + <MerchantProvince>North Carolina</MerchantProvince> + <MerchantCountry>United States</MerchantCountry> + <MerchantPostal>27592</MerchantPostal> + <MerchantURL></MerchantURL> + <TransarmorToken></TransarmorToken> + <CardType>Visa</CardType> + <CurrentBalance></CurrentBalance> + <PreviousBalance></PreviousBalance> + <EAN></EAN> + <CardCost></CardCost> + <VirtualCard>false</VirtualCard> + <CTR>=========== TRANSACTION RECORD ========== +FriendlyInc DEMO0 +123 Main Street +Durham, NC 27592 +United States + + +TYPE: Auth Only + +ACCT: Visa $ 0.00 USD + +CARDHOLDER NAME : Longbob Longsen +CARD NUMBER : ############4242 +DATE/TIME : 04 Jul 14 14:21:52 +REFERENCE # : 000040 M +AUTHOR. # : ET184931 +TRANS. REF. : 1 + + Approved - Thank You 100 + + +Please retain this copy for your records. + +Cardholder will pay above amount to card +issuer pursuant to cardholder agreement. +=========================================</CTR> +</TransactionResult> + RESPONSE + end + + def no_transaction_response + yamlexcep = <<-RESPONSE +--- !ruby/exception:ActiveMerchant::ResponseError +message: Failed with 400 Bad Request +message: +response: !ruby/object:Net::HTTPBadRequest + body: "Malformed request: Transaction Type is missing." + body_exist: true + code: "400" + header: + connection: + - Close + content-type: + - text/html; charset=utf-8 + server: + - Apache + date: + - Fri, 28 Sep 2012 18:21:37 GMT + content-length: + - "47" + status: + - "400" + cache-control: + - no-cache + http_version: "1.1" + message: Bad Request + read: true + socket: + RESPONSE + YAML.safe_load(yamlexcep, ['Net::HTTPBadRequest', 'ActiveMerchant::ResponseError']) + end + + def bad_credentials_response + yamlexcep = <<-RESPONSE +--- !ruby/exception:ActiveMerchant::ResponseError +message: +response: !ruby/object:Net::HTTPUnauthorized + code: '401' + message: Authorization Required + body: Unauthorized Request. Bad or missing credentials. + read: true + header: + cache-control: + - no-cache + content-type: + - text/html; charset=utf-8 + date: + - Tue, 30 Dec 2014 23:28:32 GMT + server: + - Apache + status: + - '401' + x-rack-cache: + - invalidate, pass + x-request-id: + - 4157e21cc5620a95ead8d2025b55bdf4 + x-ua-compatible: + - IE=Edge,chrome=1 + content-length: + - '49' + connection: + - Close + body_exist: true + http_version: '1.1' + socket: + RESPONSE + YAML.safe_load(yamlexcep, ['Net::HTTPUnauthorized', 'ActiveMerchant::ResponseError']) + end + + def successful_void_response + <<-RESPONSE + <?xml version="1.0" encoding="UTF-8"?> + <TransactionResult> + <ExactID>AD1234-56</ExactID> + <Password></Password> + <Transaction_Type>33</Transaction_Type> + <DollarAmount>11.45</DollarAmount> + <SurchargeAmount></SurchargeAmount> + <Card_Number>############1111</Card_Number> + <Transaction_Tag>987123</Transaction_Tag> + <Track1></Track1> + <Track2></Track2> + <PAN></PAN> + <Authorization_Num>ET112112</Authorization_Num> + <Expiry_Date>0913</Expiry_Date> + <CardHoldersName>Fred Burfle</CardHoldersName> + <CVD_Presence_Ind>0</CVD_Presence_Ind> + <ZipCode></ZipCode> + <Tax1Amount></Tax1Amount> + <Tax1Number></Tax1Number> + <Tax2Amount></Tax2Amount> + <Tax2Number></Tax2Number> + <Secure_AuthRequired></Secure_AuthRequired> + <Secure_AuthResult></Secure_AuthResult> + <Ecommerce_Flag></Ecommerce_Flag> + <XID></XID> + <CAVV></CAVV> + <CAVV_Algorithm></CAVV_Algorithm> + <Reference_No></Reference_No> + <Customer_Ref></Customer_Ref> + <Reference_3></Reference_3> + <Language></Language> + <Client_IP>1.1.1.10</Client_IP> + <Client_Email></Client_Email> + <LogonMessage></LogonMessage> + <Error_Number>0</Error_Number> + <Error_Description> </Error_Description> + <Transaction_Error>false</Transaction_Error> + <Transaction_Approved>true</Transaction_Approved> + <EXact_Resp_Code>00</EXact_Resp_Code> + <EXact_Message>Transaction Normal</EXact_Message> + <Bank_Resp_Code>100</Bank_Resp_Code> + <Bank_Message>Approved</Bank_Message> + <Bank_Resp_Code_2></Bank_Resp_Code_2> + <SequenceNo>000166</SequenceNo> + <AVS></AVS> + <CVV2>I</CVV2> + <Retrieval_Ref_No>2046743</Retrieval_Ref_No> + <CAVV_Response></CAVV_Response> + <Currency>USD</Currency> + <AmountRequested></AmountRequested> + <PartialRedemption>false</PartialRedemption> + <MerchantName>FreshBooks DEMO0785</MerchantName> + <MerchantAddress>35 Golden Ave</MerchantAddress> + <MerchantCity>Toronto</MerchantCity> + <MerchantProvince>Ontario</MerchantProvince> + <MerchantCountry>Canada</MerchantCountry> + <MerchantPostal>M6R 2J5</MerchantPostal> + <MerchantURL></MerchantURL> +<CTR>=========== TRANSACTION RECORD ========== +FreshBooks DEMO0785 +35 Golden Ave +Toronto, ON M6R 2J5 +Canada + + +TYPE: Void + +ACCT: Visa $ 47.38 USD + +CARD NUMBER : ############1111 +DATE/TIME : 15 Nov 12 08:20:36 +REFERENCE # : 000166 M +AUTHOR. # : ET112112 +TRANS. REF. : + +Approved - Thank You 100 + + +Please retain this copy for your records. + +Cardholder will pay above amount to card +issuer pursuant to cardholder agreement. +=========================================</CTR> + </TransactionResult> +RESPONSE + end +end diff --git a/test/unit/gateways/flo2cash_simple_test.rb b/test/unit/gateways/flo2cash_simple_test.rb new file mode 100644 index 00000000000..627f6782e29 --- /dev/null +++ b/test/unit/gateways/flo2cash_simple_test.rb @@ -0,0 +1,101 @@ +require 'test_helper' + +class Flo2cashSimpleTest < Test::Unit::TestCase + include CommStub + + def setup + Base.mode = :test + + @gateway = Flo2cashSimpleGateway.new( + :username => 'username', + :password => 'password', + :account_id => 'account_id' + ) + + @credit_card = credit_card + @amount = 100 + end + + def test_successful_purchase + response = stub_comms do + @gateway.purchase(@amount, @credit_card, order_id: 'boom') + end.check_request do |endpoint, data, headers| + assert_match(%r{<Reference>boom</Reference>}, data) + end.respond_with(successful_purchase_response) + + assert_success response + + assert_equal 'P150200005007600', response.authorization + assert response.test? + end + + def test_failed_purchase + response = stub_comms do + @gateway.purchase(@amount, @credit_card) + end.respond_with(failed_purchase_response) + + assert_failure response + assert_equal 'Transaction Declined - Bank Error', response.message + assert_equal Gateway::STANDARD_ERROR_CODE[:processing_error], response.error_code + assert response.test? + end + + def test_successful_refund + response = stub_comms do + @gateway.purchase(@amount, @credit_card) + end.respond_with(successful_purchase_response) + + assert_success response + assert_equal 'P150200005007600', response.authorization + + refund = stub_comms do + @gateway.refund(@amount, response.authorization) + end.check_request do |endpoint, data, headers| + assert_match(/P150200005007600/, data) + end.respond_with(successful_refund_response) + + assert_success refund + end + + def test_empty_response_fails + response = stub_comms do + @gateway.purchase(@amount, @credit_card) + end.respond_with(empty_purchase_response) + + assert_failure response + assert_equal 'Unable to read error message', response.message + end + + def test_transcript_scrubbing + transcript = @gateway.scrub(successful_purchase_response) + + assert_scrubbed(@credit_card.number, transcript) + assert_scrubbed(@credit_card.verification_value, transcript) + assert_scrubbed(@gateway.options[:password], transcript) + end + + private + + def successful_purchase_response + %( + <?xml version=\"1.0\" encoding=\"utf-8\"?><soap:Envelope xmlns:soap=\"http://www.w3.org/2003/05/soap-envelope\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"><soap:Body><ProcessPurchaseResponse xmlns=\"http://www.flo2cash.co.nz/webservices/paymentwebservice\"><transactionresult><TransactionId>P150200005007600</TransactionId><OriginalTransactionId /><Type>PURCHASE</Type><AccountId>621366</AccountId><Status>SUCCESSFUL</Status><ReceiptNumber>25002185</ReceiptNumber><AuthCode>088682</AuthCode><Amount>1.00</Amount><Reference /><Particular>Store Purchase</Particular><Message>Transaction Successful</Message><BlockedReason /><CardStored>false</CardStored><CardToken /></transactionresult></ProcessPurchaseResponse></soap:Body></soap:Envelope> + ) + end + + def failed_purchase_response + %( + <?xml version=\"1.0\" encoding=\"utf-8\"?><soap:Envelope xmlns:soap=\"http://www.w3.org/2003/05/soap-envelope\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"><soap:Body><ProcessPurchaseResponse xmlns=\"http://www.flo2cash.co.nz/webservices/paymentwebservice\"><transactionresult><TransactionId>P150200005007599</TransactionId><OriginalTransactionId /><Type>PURCHASE</Type><AccountId>621366</AccountId><Status>FAILED</Status><ReceiptNumber>0</ReceiptNumber><AuthCode /><Amount>1.00</Amount><Reference /><Particular>Store Purchase</Particular><Message>Transaction Declined - Bank Error</Message><BlockedReason /><CardStored>false</CardStored><CardToken /></transactionresult></ProcessPurchaseResponse></soap:Body></soap:Envelope> + ) + end + + def successful_refund_response + %( + <?xml version=\"1.0\" encoding=\"utf-8\"?><soap:Envelope xmlns:soap=\"http://www.w3.org/2003/05/soap-envelope\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"><soap:Body><ProcessRefundResponse xmlns=\"http://www.flo2cash.co.nz/webservices/paymentwebservice\"><transactionresult><TransactionId>P150200005007602</TransactionId><OriginalTransactionId>P150200005007601</OriginalTransactionId><Type>REFUND</Type><AccountId>621366</AccountId><Status>SUCCESSFUL</Status><ReceiptNumber>25002187</ReceiptNumber><AuthCode>039335</AuthCode><Amount>1.00</Amount><Reference /><Particular /><Message>Transaction Successful</Message><BlockedReason /><CardStored>false</CardStored><CardToken /></transactionresult></ProcessRefundResponse></soap:Body></soap:Envelope> + ) + end + + def empty_purchase_response + %( + ) + end +end diff --git a/test/unit/gateways/flo2cash_test.rb b/test/unit/gateways/flo2cash_test.rb new file mode 100644 index 00000000000..4564114db64 --- /dev/null +++ b/test/unit/gateways/flo2cash_test.rb @@ -0,0 +1,135 @@ +require 'test_helper' + +class Flo2cashTest < Test::Unit::TestCase + include CommStub + + def setup + Base.mode = :test + + @gateway = Flo2cashGateway.new( + :username => 'username', + :password => 'password', + :account_id => 'account_id' + ) + + @credit_card = credit_card + @amount = 100 + end + + def test_successful_purchase + response = stub_comms do + @gateway.purchase(@amount, @credit_card, order_id: 'boom') + end.check_request do |endpoint, data, headers| + assert_match(%r{<Reference>boom</Reference>}, data) + end.respond_with(successful_authorize_response, successful_capture_response) + + assert_success response + + assert_equal 'P150100005006789', response.authorization + assert response.test? + end + + def test_failed_purchase + response = stub_comms do + @gateway.purchase(@amount, @credit_card) + end.respond_with(failed_authorize_response) + + assert_failure response + assert_equal 'Transaction Declined - Bank Error', response.message + assert_equal Gateway::STANDARD_ERROR_CODE[:processing_error], response.responses.first.error_code + assert response.test? + end + + def test_successful_authorize_and_capture + response = stub_comms do + @gateway.authorize(@amount, @credit_card) + end.respond_with(successful_authorize_response) + + assert_success response + assert_equal 'P150100005006789', response.authorization + + capture = stub_comms do + @gateway.capture(@amount, response.authorization) + end.check_request do |endpoint, data, headers| + assert_match(/P150100005006789/, data) + end.respond_with(successful_capture_response) + + assert_success capture + end + + def test_failed_authorize + response = stub_comms do + @gateway.authorize(@amount, @credit_card) + end.respond_with(failed_authorize_response) + + assert_failure response + assert_equal 'Transaction Declined - Bank Error', response.message + assert_equal Gateway::STANDARD_ERROR_CODE[:processing_error], response.error_code + assert response.test? + end + + def test_successful_refund + response = stub_comms do + @gateway.purchase(@amount, @credit_card) + end.respond_with(successful_authorize_response, successful_capture_response) + + assert_success response + assert_equal 'P150100005006789', response.authorization + + refund = stub_comms do + @gateway.refund(@amount, response.authorization) + end.check_request do |endpoint, data, headers| + assert_match(/P150100005006789/, data) + end.respond_with(successful_refund_response) + + assert_success refund + end + + def test_empty_response_fails + response = stub_comms do + @gateway.purchase(@amount, @credit_card) + end.respond_with(empty_purchase_response) + + assert_failure response + assert_equal 'Unable to read error message', response.message + end + + def test_transcript_scrubbing + transcript = @gateway.scrub(successful_authorize_response) + + assert_scrubbed(@credit_card.number, transcript) + assert_scrubbed(@credit_card.verification_value, transcript) + assert_scrubbed(@gateway.options[:password], transcript) + end + + private + + def successful_authorize_response + %( + <?xml version=\"1.0\" encoding=\"utf-8\"?><soap:Envelope xmlns:soap=\"http://www.w3.org/2003/05/soap-envelope\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"><soap:Body><ProcessAuthoriseResponse xmlns=\"http://www.flo2cash.co.nz/webservices/paymentwebservice\"><transactionresult><TransactionId>P150100005006789</TransactionId><OriginalTransactionId /><Type>AUTHORISATION</Type><AccountId>621409</AccountId><Status>SUCCESSFUL</Status><ReceiptNumber>25001371</ReceiptNumber><AuthCode>017265</AuthCode><Amount>100</Amount><Reference /><Particular>Store Purchase</Particular><Message>Transaction Successful</Message><BlockedReason /><CardStored>false</CardStored><CardToken /></transactionresult></ProcessAuthoriseResponse></soap:Body></soap:Envelope> + ) + end + + def failed_authorize_response + %( + <?xml version=\"1.0\" encoding=\"utf-8\"?><soap:Envelope xmlns:soap=\"http://www.w3.org/2003/05/soap-envelope\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"><soap:Body><ProcessAuthoriseResponse xmlns=\"http://www.flo2cash.co.nz/webservices/paymentwebservice\"><transactionresult><TransactionId>P150100005006794</TransactionId><OriginalTransactionId /><Type>AUTHORISATION</Type><AccountId>621409</AccountId><Status>FAILED</Status><ReceiptNumber>0</ReceiptNumber><AuthCode /><Amount>100</Amount><Reference /><Particular>Store Purchase</Particular><Message>Transaction Declined - Bank Error</Message><BlockedReason /><CardStored>false</CardStored><CardToken /></transactionresult></ProcessAuthoriseResponse></soap:Body></soap:Envelope> + ) + end + + def successful_capture_response + %( + <?xml version=\"1.0\" encoding=\"utf-8\"?><soap:Envelope xmlns:soap=\"http://www.w3.org/2003/05/soap-envelope\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"><soap:Body><ProcessCaptureResponse xmlns=\"http://www.flo2cash.co.nz/webservices/paymentwebservice\"><transactionresult><TransactionId>P150100005006790</TransactionId><OriginalTransactionId>P150100005006789</OriginalTransactionId><Type>CAPTURE</Type><AccountId>621409</AccountId><Status>SUCCESSFUL</Status><ReceiptNumber>25001372</ReceiptNumber><AuthCode>007524</AuthCode><Amount>100</Amount><Reference /><Particular /><Message>Transaction Successful</Message><BlockedReason /><CardStored>false</CardStored><CardToken /></transactionresult></ProcessCaptureResponse></soap:Body></soap:Envelope> + ) + end + + def successful_refund_response + %( + <?xml version=\"1.0\" encoding=\"utf-8\"?><soap:Envelope xmlns:soap=\"http://www.w3.org/2003/05/soap-envelope\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"><soap:Body><ProcessRefundResponse xmlns=\"http://www.flo2cash.co.nz/webservices/paymentwebservice\"><transactionresult><TransactionId>P150100005006770</TransactionId><OriginalTransactionId>P150100005006769</OriginalTransactionId><Type>REFUND</Type><AccountId>621366</AccountId><Status>SUCCESSFUL</Status><ReceiptNumber>25001352</ReceiptNumber><AuthCode>039241</AuthCode><Amount>100</Amount><Reference /><Particular /><Message>Transaction Successful</Message><BlockedReason /><CardStored>false</CardStored><CardToken /></transactionresult></ProcessRefundResponse></soap:Body></soap:Envelope> + ) + end + + def empty_purchase_response + %( + ) + end +end diff --git a/test/unit/gateways/forte_test.rb b/test/unit/gateways/forte_test.rb new file mode 100644 index 00000000000..c7ca564cc65 --- /dev/null +++ b/test/unit/gateways/forte_test.rb @@ -0,0 +1,616 @@ +require 'test_helper' + +class ForteTest < Test::Unit::TestCase + include CommStub + + def setup + @gateway = ForteGateway.new(location_id: 'location_id', account_id: 'account_id', api_key: 'api_key', secret: 'secret') + @credit_card = credit_card + @check = check + @amount = 100 + + @options = { + order_id: '1', + billing_address: address, + description: 'Store Purchase' + } + end + + def test_successful_purchase + response = stub_comms(@gateway, :raw_ssl_request) do + @gateway.purchase(@amount, @credit_card, @options) + end.respond_with(MockedResponse.new(successful_purchase_response)) + assert_success response + + assert_equal 'trn_bb7687a7-3d3a-40c2-8fa9-90727a814249#123456', response.authorization + assert response.test? + end + + def test_purchase_passes_options + options = { order_id: '1' } + @gateway.expects(:commit).with(anything, has_entries(:order_number => '1')) + + stub_comms(@gateway, :raw_ssl_request) do + @gateway.purchase(@amount, @credit_card, options) + end.respond_with(MockedResponse.new(successful_purchase_response)) + end + + def test_failed_purchase + response = stub_comms(@gateway, :raw_ssl_request) do + @gateway.purchase(@amount, @credit_card, @options) + end.respond_with(MockedResponse.new(failed_purchase_response)) + assert_failure response + assert_equal 'INVALID TRN', response.message + end + + def test_successful_purchase_with_echeck + response = stub_comms(@gateway, :raw_ssl_request) do + @gateway.purchase(@amount, @check, @options) + end.respond_with(MockedResponse.new(successful_echeck_purchase_response)) + assert_success response + + assert_equal 'trn_bb7687a7-3d3a-40c2-8fa9-90727a814249#123456', response.authorization + assert response.test? + end + + def test_failed_purchase_with_echeck + response = stub_comms(@gateway, :raw_ssl_request) do + @gateway.purchase(@amount, @credit_card, @options) + end.respond_with(MockedResponse.new(failed_echeck_purchase_response)) + assert_failure response + assert_equal 'INVALID CREDIT CARD NUMBER', response.message + end + + def test_successful_authorize + response = stub_comms(@gateway, :raw_ssl_request) do + @gateway.authorize(@amount, @credit_card, @options) + end.respond_with(MockedResponse.new(successful_authorize_response)) + assert_success response + end + + def test_failed_authorize + response = stub_comms(@gateway, :raw_ssl_request) do + @gateway.authorize(@amount, @credit_card, @options) + end.respond_with(MockedResponse.new(failed_authorize_response)) + assert_failure response + assert_equal 'INVALID CREDIT CARD NUMBER', response.message + end + + def test_successful_capture + response = stub_comms(@gateway, :raw_ssl_request) do + @gateway.capture(@amount, 'authcode') + end.respond_with(MockedResponse.new(successful_capture_response)) + assert_success response + end + + def test_failed_capture + response = stub_comms(@gateway, :raw_ssl_request) do + @gateway.capture(@amount, 'authcode') + end.respond_with(MockedResponse.new(failed_capture_response)) + assert_failure response + end + + def test_successful_credit + response = stub_comms(@gateway, :raw_ssl_request) do + @gateway.credit(@amount, @credit_card, @options) + end.respond_with(MockedResponse.new(successful_credit_response)) + assert_success response + end + + def test_failed_credit + response = stub_comms(@gateway, :raw_ssl_request) do + @gateway.credit(@amount, @credit_card, @options) + end.respond_with(MockedResponse.new(failed_credit_response)) + assert_failure response + end + + def test_successful_void + response = stub_comms(@gateway, :raw_ssl_request) do + @gateway.void('authcode') + end.respond_with(MockedResponse.new(successful_credit_response)) + assert_success response + end + + def test_failed_void + response = stub_comms(@gateway, :raw_ssl_request) do + @gateway.void('authcode') + end.respond_with(MockedResponse.new(failed_credit_response)) + assert_failure response + end + + def test_successful_verify + response = stub_comms(@gateway, :raw_ssl_request) do + @gateway.verify(@credit_card, @options) + end.respond_with(MockedResponse.new(successful_authorize_response), MockedResponse.new(successful_void_response)) + assert_success response + end + + def test_successful_verify_with_failed_void + response = stub_comms(@gateway, :raw_ssl_request) do + @gateway.verify(@credit_card, @options) + end.respond_with(MockedResponse.new(successful_authorize_response), MockedResponse.new(failed_void_response)) + assert_success response + end + + def test_failed_verify + response = stub_comms(@gateway, :raw_ssl_request) do + @gateway.verify(@credit_card, @options) + end.respond_with(MockedResponse.new(failed_authorize_response)) + assert_failure response + end + + def test_successful_refund + response = stub_comms(@gateway, :raw_ssl_request) do + @gateway.refund(@amount, 'authcode') + end.respond_with(MockedResponse.new(successful_refund_response)) + assert_success response + end + + def test_failed_refund + response = stub_comms(@gateway, :raw_ssl_request) do + @gateway.refund(@amount, 'authcode') + end.respond_with(MockedResponse.new(failed_refund_response)) + assert_failure response + end + + def test_handles_improper_padding + @gateway = ForteGateway.new(location_id: ' improperly-padded ', account_id: ' account_id ', api_key: 'api_key', secret: 'secret') + response = stub_comms(@gateway, :raw_ssl_request) do + @gateway.purchase(@amount, @credit_card, @options) + end.check_request do |type, url, parameters, headers| + URI.parse(url) + end.respond_with(MockedResponse.new(successful_purchase_response)) + assert_success response + end + + def test_scrub + assert @gateway.supports_scrubbing? + assert_equal @gateway.scrub(pre_scrubbed), post_scrubbed + end + + private + + class MockedResponse + attr :code, :body + def initialize(body, code = 200) + @code = code + @body = body + end + end + + def pre_scrubbed + %q( +<- "POST /api/v2/accounts/act_300111/locations/loc_176008/transactions/ HTTP/1.1\r\nContent-Type: application/json\r\nAuthorization: Basic ZjA4N2E5MGYwMGYwYWU1NzA1MGM5MzdlZDM4MTVjOWY6ZDc5M2Q2NDA2NGUzMTEzYTc0ZmE3MjAzNWNmYzNhMWQ=\r\nX-Forte-Auth-Account-Id: act_300111\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: sandbox.forte.net\r\nContent-Length: 471\r\n\r\n" +<- "{\"authorization_amount\":\"1.00\",\"card\":{\"card_type\":\"visa\",\"name_on_card\":\"Longbob Longsen\",\"account_number\":\"4000100011112224\",\"expire_month\":9,\"expire_year\":2016,\"card_verification_value\":\"123\"},\"billing_address\":{\"first_name\":\"Jim\",\"last_name\":\"Smith\",\"address_line1\":\"456 My Street\",\"address_line2\":\"Apt 1\",\"address_country\":\"CA\",\"address_zip\":\"K1C2N6\",\"address_state\":\"ON\",\"address_city\":\"Ottawa\"},\"action\":\"sale\",\"account_id\":\"act_300111\",\"location_id\":\"loc_176008\"}" + ) + end + + def post_scrubbed + %q( +<- "POST /api/v2/accounts/act_300111/locations/loc_176008/transactions/ HTTP/1.1\r\nContent-Type: application/json\r\nAuthorization: Basic [FILTERED]=\r\nX-Forte-Auth-Account-Id: act_300111\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: sandbox.forte.net\r\nContent-Length: 471\r\n\r\n" +<- "{\"authorization_amount\":\"1.00\",\"card\":{\"card_type\":\"visa\",\"name_on_card\":\"Longbob Longsen\",\"account_number[FILTERED]\",\"expire_month\":9,\"expire_year\":2016,\"card_verification_value[FILTERED]\"},\"billing_address\":{\"first_name\":\"Jim\",\"last_name\":\"Smith\",\"address_line1\":\"456 My Street\",\"address_line2\":\"Apt 1\",\"address_country\":\"CA\",\"address_zip\":\"K1C2N6\",\"address_state\":\"ON\",\"address_city\":\"Ottawa\"},\"action\":\"sale\",\"account_id\":\"act_300111\",\"location_id\":\"loc_176008\"}" + ) + end + + def successful_purchase_response + ' + { + "transaction_id":"trn_bb7687a7-3d3a-40c2-8fa9-90727a814249", + "account_id":"act_300111", + "location_id":"loc_176008", + "action":"sale", + "authorization_amount":1.0, + "authorization_code":"123456", + "billing_address":{ + "first_name":"Jim", + "last_name":"Smith" + }, + "card": { + "name_on_card":"Longbob Longsen", + "masked_account_number":"****2224", + "expire_month":9, + "expire_year":2016, + "card_verification_value":"***", + "card_type":"visa" + }, + "response": { + "authorization_code":"123456", + "avs_result":"Y", + "cvv_code":"M", + "environment":"sandbox", + "response_type":"A", + "response_code":"A01", + "response_desc":"TEST APPROVAL" + }, + "links": { + "self":"https://sandbox.forte.net/API/v2/transactions/trn_bb7687a7-3d3a-40c2-8fa9-90727a814249", + "settlements":"https://sandbox.forte.net/API/v2/transactions/trn_bb7687a7-3d3a-40c2-8fa9-90727a814249/settlements" + } + } + ' + end + + def failed_purchase_response + ' + { + "transaction_id":"trn_e9ea64c4-5c2c-43dd-9138-f2661b59947c", + "account_id":"act_300111", + "location_id":"loc_176008", + "action":"sale", + "authorization_amount":1.0, + "billing_address": { + "first_name":"Jim", + "last_name":"Smith" + }, + "card": { + "name_on_card":"Longbob Longsen", + "masked_account_number":"****1111", + "expire_month":9, + "expire_year":2016, + "card_verification_value":"***", + "card_type":"visa" + }, + "response": { + "environment":"sandbox", + "response_type":"D", + "response_code":"U20", + "response_desc": "INVALID TRN" + }, + "links": { + "self":"https://sandbox.forte.net/API/v2/transactions/trn_e9ea64c4-5c2c-43dd-9138-f2661b59947c", + "settlements":"https://sandbox.forte.net/API/v2/transactions/trn_e9ea64c4-5c2c-43dd-9138-f2661b59947c/settlements" + } + } + ' + end + + def successful_echeck_purchase_response + ' + { + "transaction_id":"trn_bb7687a7-3d3a-40c2-8fa9-90727a814249", + "account_id":"act_300111", + "location_id":"loc_176008", + "action":"sale", + "authorization_amount":1.0, + "authorization_code":"123456", + "billing_address":{ + "first_name":"Jim", + "last_name":"Smith" + }, + "echeck": { + "account_holder": "Jim Smith", + "masked_account_number":"****8535", + "routing_number":"244183602", + "account_type":"checking", + "check_number":"1" + }, + "echeck": { + "name_on_card":"Longbob Longsen", + "masked_account_number":"****2224", + "expire_month":9, + "expire_year":2016, + "card_verification_value":"***", + "card_type":"visa" + }, + "response": { + "authorization_code":"123456", + "avs_result":"Y", + "cvv_code":"M", + "environment":"sandbox", + "response_type":"A", + "response_code":"A01", + "response_desc":"TEST APPROVAL" + }, + "links": { + "self":"https://sandbox.forte.net/API/v2/transactions/trn_bb7687a7-3d3a-40c2-8fa9-90727a814249", + "settlements":"https://sandbox.forte.net/API/v2/transactions/trn_bb7687a7-3d3a-40c2-8fa9-90727a814249/settlements" + } + } + ' + end + + def failed_echeck_purchase_response + ' + { + "transaction_id":"trn_bb7687a7-3d3a-40c2-8fa9-90727a814249", + "account_id":"act_300111", + "location_id":"loc_176008", + "action":"sale", + "authorization_amount":1.0, + "authorization_code":"123456", + "billing_address":{ + "first_name":"Jim", + "last_name":"Smith" + }, + "echeck": { + "account_holder": "Jim Smith", + "masked_account_number":"****8535", + "routing_number":"244183602", + "account_type":"checking", + "check_number":"1" + }, + "echeck": { + "name_on_card":"Longbob Longsen", + "masked_account_number":"****2224", + "expire_month":9, + "expire_year":2016, + "card_verification_value":"***", + "card_type":"visa" + }, + "response": { + "environment":"sandbox", + "response_type":"D", + "response_code":"U19", + "response_desc":"INVALID CREDIT CARD NUMBER" + }, + "links": { + "self":"https://sandbox.forte.net/API/v2/transactions/trn_bb7687a7-3d3a-40c2-8fa9-90727a814249", + "settlements":"https://sandbox.forte.net/API/v2/transactions/trn_bb7687a7-3d3a-40c2-8fa9-90727a814249/settlements" + } + } + ' + end + + def successful_authorize_response + ' + { + "transaction_id":"trn_527fdc8a-d3d0-4680-badc-bfa784c63c13", + "account_id":"act_300111", + "location_id":"loc_176008", + "action":"authorize", + "authorization_amount":1.0, + "authorization_code":"123456", + "billing_address": { + "first_name":"Jim", + "last_name":"Smith" + }, + "card": { + "name_on_card":"Longbob Longsen", + "masked_account_number":"****2224", + "expire_month":9, + "expire_year":2016, + "card_verification_value":"***", + "card_type":"visa" + }, + "response": { + "authorization_code":"123456", + "avs_result":"Y", + "cvv_code":"M", + "environment":"sandbox", + "response_type":"A", + "response_code":"A01", + "response_desc":"TEST APPROVAL" + }, + "links":{ + "self":"https://sandbox.forte.net/API/v2/transactions/trn_527fdc8a-d3d0-4680-badc-bfa784c63c13", + "settlements":"https://sandbox.forte.net/API/v2/transactions/trn_527fdc8a-d3d0-4680-badc-bfa784c63c13/settlements" + } + } + ' + end + + def failed_authorize_response + ' + { + "transaction_id":"trn_7c045645-98b3-4c8a-88d6-e8d686884564", + "account_id":"act_300111", + "location_id":"loc_176008", + "action":"authorize", + "authorization_amount":19.85, + "billing_address": { + "first_name":"Jim", + "last_name":"Smith" + }, + "card": { + "name_on_card":"Longbob Longsen", + "masked_account_number":"****1111", + "expire_month":9, + "expire_year":2016, + "card_verification_value":"***", + "card_type":"visa" + }, + "response":{ + "environment":"sandbox", + "response_type":"D", + "response_code":"U20", + "response_desc":"INVALID CREDIT CARD NUMBER" + }, + "links": { + "self":"https://sandbox.forte.net/API/v2/transactions/trn_7c045645-98b3-4c8a-88d6-e8d686884564", + "settlements":"https://sandbox.forte.net/API/v2/transactions/trn_7c045645-98b3-4c8a-88d6-e8d686884564/settlements" + } + } + ' + end + + def successful_capture_response + ' + { + "transaction_id":"trn_94a04a97-c847-4420-820b-fb153a1f0f64", + "account_id":"act_300111", + "location_id":"loc_176008", + "original_transaction_id":"trn_e5e3b23d-3e13-44d4-bce1-4b9aaa466a5d", + "action":"capture", + "authorization_code":"13844235", + "response": { + "authorization_code":"13844235", + "environment":"sandbox", + "response_type":"A", + "response_code":"A01", + "response_desc":"APPROVED" + }, + "links": { + "self":"https://sandbox.forte.net/API/v2/transactions/trn_94a04a97-c847-4420-820b-fb153a1f0f64", + "settlements":"https://sandbox.forte.net/API/v2/transactions/trn_94a04a97-c847-4420-820b-fb153a1f0f64/settlements" + } + } + ' + end + + def failed_capture_response + ' + { + "account_id":"act_300111", + "location_id":"loc_176008", + "action":"capture", + "authorization_code":"", + "response":{ + "environment":"sandbox", + "response_desc":"The field transaction_id is required." + } + } + ' + end + + def successful_credit_response + ' + { + "transaction_id":"trn_357b284e-1dde-42ba-b0a5-5f66e08c7d9f", + "account_id":"act_300111", + "location_id":"loc_176008", + "action":"disburse", + "authorization_amount":1.0, + "authorization_code":"123456", + "billing_address": { + "first_name":"Jim", + "last_name":"Smith" + }, + "card": { + "name_on_card":"Longbob Longsen", + "masked_account_number":"****2224", + "expire_month":9, + "expire_year":2016, + "card_verification_value":"***", + "card_type":"visa" + }, + "response": { + "authorization_code":"123456", + "avs_result":"Y", + "cvv_code":"M", + "environment":"sandbox", + "response_type":"A", + "response_code":"A01", + "response_desc":"TEST APPROVAL" + }, + "links": { + "self":"https://sandbox.forte.net/API/v2/transactions/trn_357b284e-1dde-42ba-b0a5-5f66e08c7d9f", + "settlements":"https://sandbox.forte.net/API/v2/transactions/trn_357b284e-1dde-42ba-b0a5-5f66e08c7d9f/settlements" + } + } + ' + end + + def failed_credit_response + ' + { + "transaction_id":"trn_ce70ce9a-6265-4892-9a83-5825cb869ed5", + "account_id":"act_300111", + "location_id":"loc_176008", + "action":"disburse", + "authorization_amount":1.0, + "billing_address": { + "first_name":"Jim", + "last_name":"Smith" + }, + "response": { + "environment":"sandbox", + "response_type":"E", + "response_code":"F01", + "response_desc":"MANDITORY FIELD MISSING:card.card_type,MANDITORY FIELD MISSING:card.account_number,MANDITORY FIELD MISSING:card.expire_year,MANDITORY FIELD MISSING:card.expire_month" + }, + "links": { + "self":"https://sandbox.forte.net/API/v2/transactions/trn_ce70ce9a-6265-4892-9a83-5825cb869ed5", + "settlements":"https://sandbox.forte.net/API/v2/transactions/trn_ce70ce9a-6265-4892-9a83-5825cb869ed5/settlements" + } + } + ' + end + + def successful_void_response + ' + { + "transaction_id":"trn_6c9d049e-1971-45fb-a4da-a0c35c4ed274", + "account_id":"act_300111", + "location_id":"loc_176008", + "action":"void", + "authorization_code":"13802096", + "response": { + "authorization_code":"13802096", + "environment":"sandbox", + "response_type":"A", + "response_code":"A01", + "response_desc":"APPROVED" + }, + "links": { + "self":"https://sandbox.forte.net/API/v2/transactions/trn_6c9d049e-1971-45fb-a4da-a0c35c4ed274", + "settlements":"https://sandbox.forte.net/API/v2/transactions/trn_6c9d049e-1971-45fb-a4da-a0c35c4ed274/settlements" + } + } + ' + end + + def failed_void_response + ' + { + "account_id":"act_300111", + "location_id":"loc_176008", + "action":"void", + "authorization_code":"", + "response": { + "environment":"sandbox", + "response_desc":"The field transaction_id is required." + } + } + ' + end + + def successful_refund_response + <<-SUCCESS + { + "transaction_id": "trn_6ad08872-a8c9-44a9-baca-670c31de98a1", + "location_id": "loc_176008", + "original_transaction_id": "trn_cf645bab-72cc-41d5-a9d2-376845333008", + "order_number": "1", + "action": "disburse", + "authorization_amount": 1, + "authorization_code": "123456", + "entered_by": "f087a90f00f0ae57050c937ed3815c9f", + "billing_address": { + "first_name": "Jim", + "last_name": "Smith", + "physical_address": { + "street_line1": "456 My Street", + "street_line2": "Apt 1", + "locality": "Ottawa", + "region": "ON", + "postal_code": "K1C2N6" + } + }, + "response": { + "environment": "sandbox", + "response_type": "A", + "response_code": "A01", + "response_desc": "TEST APPROVAL", + "authorization_code": "123456", + "avs_result": "Y", + "cvv_code": "M" + } + } + SUCCESS + end + + def failed_refund_response + <<-FAILED + { + "location_id": "loc_176008", + "action": "reverse", + "authorization_amount": 1, + "entered_by": "f087a90f00f0ae57050c937ed3815c9f", + "response": { + "environment": "sandbox", + "response_desc": "Error[1]: The field authorization_code is required when performing a reverse action. Error[2]: The field original_transaction_id is required when performing a reverse action." + } + } + FAILED + end +end diff --git a/test/unit/gateways/garanti_test.rb b/test/unit/gateways/garanti_test.rb index d70c4922068..46e7ce79e87 100644 --- a/test/unit/gateways/garanti_test.rb +++ b/test/unit/gateways/garanti_test.rb @@ -4,16 +4,13 @@ class GarantiTest < Test::Unit::TestCase def setup - if RUBY_VERSION < '1.9' && $KCODE == "NONE" - @original_kcode = $KCODE - $KCODE = 'u' - end + @original_kcode = nil - Base.gateway_mode = :test + Base.mode = :test @gateway = GarantiGateway.new(:login => 'a', :password => 'b', :terminal_id => 'c', :merchant_id => 'd') @credit_card = credit_card(4242424242424242) - @amount = 1000 #1000 cents, 10$ + @amount = 1000 # 1000 cents, 10$ @options = { :order_id => 'db4af18c5222503d845180350fbda516', @@ -50,20 +47,30 @@ def test_character_normalization if ActiveSupport::Inflector.method(:transliterate).arity == -2 assert_equal 'ABCCDEFGGHIIJKLMNOOPRSSTUUVYZ', @gateway.send(:normalize, 'ABCÇDEFGĞHIİJKLMNOÖPRSŞTUÜVYZ') assert_equal 'abccdefgghiijklmnooprsstuuvyz', @gateway.send(:normalize, 'abcçdefgğhıijklmnoöprsştuüvyz') - elsif RUBY_VERSION >= '1.9' + else assert_equal 'ABCDEFGHIJKLMNOPRSTUVYZ', @gateway.send(:normalize, 'ABCÇDEFGĞHIİJKLMNOÖPRSŞTUÜVYZ') assert_equal 'abcdefghijklmnoprstuvyz', @gateway.send(:normalize, 'abcçdefgğhıijklmnoöprsştuüvyz') - else - assert_equal 'ABCCDEFGGHIIJKLMNOOPRSSTUUVYZ', @gateway.send(:normalize, 'ABCÇDEFGĞHIİJKLMNOÖPRSŞTUÜVYZ') - assert_equal 'abccdefgghijklmnooprsstuuvyz', @gateway.send(:normalize, 'abcçdefgğhıijklmnoöprsştuüvyz') end end def test_nil_normalization assert_nil @gateway.send(:normalize, nil) end - - + + def test_strip_invalid_xml_chars + xml = <<EOF + <response> + <element>Parse the First & but not this &tilde; &x002a;</element> + </response> +EOF + parsed_xml = @gateway.send(:strip_invalid_xml_chars, xml) + + assert REXML::Document.new(parsed_xml) + assert_raise(REXML::ParseException) do + REXML::Document.new(xml) + end + end + private # Place raw successful response from gateway here @@ -90,7 +97,7 @@ def successful_purchase_response <SequenceNum>000008</SequenceNum> <ProvDate>20101218 08:56:39</ProvDate> <CardNumberMasked></CardNumberMasked> - <CardHolderName></CardHolderName> + <CardHolderName>Company Name & Another Name</CardHolderName> <HostMsgList></HostMsgList> <RewardInqResult> <RewardList></RewardList> diff --git a/test/unit/gateways/gateway_test.rb b/test/unit/gateways/gateway_test.rb index 2bef0185402..1be0dd4931c 100644 --- a/test/unit/gateways/gateway_test.rb +++ b/test/unit/gateways/gateway_test.rb @@ -1,12 +1,36 @@ require 'test_helper' class GatewayTest < Test::Unit::TestCase + def setup + @gateway = Gateway.new + end + + def teardown + Gateway.money_format = :dollars + end + def test_should_detect_if_a_card_is_supported Gateway.supported_cardtypes = [:visa, :bogus] - assert [:visa, :bogus].all? { |supported_cardtype| Gateway.supports?(supported_cardtype) } + assert([:visa, :bogus].all? { |supported_cardtype| Gateway.supports?(supported_cardtype) }) Gateway.supported_cardtypes = [] - assert_false [:visa, :bogus].all? { |invalid_cardtype| Gateway.supports?(invalid_cardtype) } + assert_false([:visa, :bogus].all? { |invalid_cardtype| Gateway.supports?(invalid_cardtype) }) + end + + def test_should_validate_supported_countries + assert_raise(ActiveMerchant::InvalidCountryCodeError) do + Gateway.supported_countries = %w(us uk sg) + end + + all_country_codes = ActiveMerchant::Country::COUNTRIES.collect do |country| + [country[:alpha2], country[:alpha3]] + end.flatten + + assert_nothing_raised do + Gateway.supported_countries = all_country_codes + assert Gateway.supported_countries == all_country_codes, + 'List of supported countries not properly set' + end end def test_should_gateway_uses_ssl_strict_checking_by_default @@ -14,35 +38,135 @@ def test_should_gateway_uses_ssl_strict_checking_by_default end def test_should_be_able_to_look_for_test_mode - Base.gateway_mode = :test - assert Gateway.new.test? + Base.mode = :test + assert @gateway.test? - Base.gateway_mode = :production - assert_false Gateway.new.test? + Base.mode = :production + assert_false @gateway.test? end def test_amount_style - assert_equal '10.34', Gateway.new.send(:amount, 1034) + assert_equal '10.34', @gateway.send(:amount, 1034) - assert_raise(ArgumentError) do - Gateway.new.send(:amount, '10.34') - end + assert_raise(ArgumentError) do + @gateway.send(:amount, '10.34') + end end def test_card_brand - credit_card = stub(:brand => "visa") - assert_equal "visa", Gateway.card_brand(credit_card) + credit_card = stub(:brand => 'visa') + assert_equal 'visa', Gateway.card_brand(credit_card) end def test_card_brand_using_type - credit_card = stub(:type => "String") - assert_equal "string", Gateway.card_brand(credit_card) + credit_card = stub(:type => 'String') + assert_equal 'string', Gateway.card_brand(credit_card) end def test_setting_application_id_outside_the_class_definition assert_equal SimpleTestGateway.application_id, SubclassGateway.application_id - SimpleTestGateway.application_id = "New Application ID" + SimpleTestGateway.application_id = 'New Application ID' assert_equal SimpleTestGateway.application_id, SubclassGateway.application_id end + + def test_localized_amount_should_not_modify_for_fractional_currencies + Gateway.money_format = :dollars + assert_equal '1.00', @gateway.send(:localized_amount, 100, 'CAD') + assert_equal '12.34', @gateway.send(:localized_amount, 1234, 'USD') + + Gateway.money_format = :cents + assert_equal '100', @gateway.send(:localized_amount, 100, 'CAD') + assert_equal '1234', @gateway.send(:localized_amount, 1234, 'USD') + end + + def test_localized_amount_should_ignore_money_format_for_non_fractional_currencies + Gateway.money_format = :dollars + assert_equal '1', @gateway.send(:localized_amount, 100, 'JPY') + assert_equal '12', @gateway.send(:localized_amount, 1234, 'ISK') + + Gateway.money_format = :cents + assert_equal '1', @gateway.send(:localized_amount, 100, 'JPY') + assert_equal '12', @gateway.send(:localized_amount, 1234, 'ISK') + end + + def test_localized_amount_returns_three_decimal_places_for_three_decimal_currencies + @gateway.currencies_with_three_decimal_places = %w(BHD KWD OMR RSD TND) + + Gateway.money_format = :dollars + assert_equal '0.100', @gateway.send(:localized_amount, 100, 'OMR') + assert_equal '1.234', @gateway.send(:localized_amount, 1234, 'BHD') + + Gateway.money_format = :cents + assert_equal '100', @gateway.send(:localized_amount, 100, 'OMR') + assert_equal '1234', @gateway.send(:localized_amount, 1234, 'BHD') + end + + def test_split_names + assert_equal ['Longbob', 'Longsen'], @gateway.send(:split_names, 'Longbob Longsen') + end + + def test_split_names_with_single_name + assert_equal ['', 'Prince'], @gateway.send(:split_names, 'Prince') + end + + def test_split_names_with_empty_names + assert_equal [nil, nil], @gateway.send(:split_names, '') + assert_equal [nil, nil], @gateway.send(:split_names, nil) + assert_equal [nil, nil], @gateway.send(:split_names, ' ') + end + + def test_supports_scrubbing? + gateway = Gateway.new + refute gateway.supports_scrubbing? + end + + def test_should_not_allow_scrubbing_if_unsupported + gateway = Gateway.new + refute gateway.supports_scrubbing? + + assert_raise(RuntimeError) do + gateway.scrub('hi') + end + end + + def test_strip_invalid_xml_chars + xml = <<EOF + <response> + <element>Parse the First & but not this &tilde; &x002a;</element> + </response> +EOF + parsed_xml = @gateway.send(:strip_invalid_xml_chars, xml) + + assert REXML::Document.new(parsed_xml) + assert_raise(REXML::ParseException) do + REXML::Document.new(xml) + end + end + + def test_add_field_to_post_if_present + order_id = 'abc123' + + post = { } + options = { order_id: order_id, do_not_add: 24 } + + @gateway.add_field_to_post_if_present(post, options, :order_id) + + assert_equal post[:order_id], order_id + assert_false post.key?(:do_not_add) + end + + def test_add_fields_to_post_if_present + order_id = 'abc123' + transaction_number = 500 + + post = { } + options = { order_id: order_id, transaction_number: transaction_number, do_not_add: 24 } + + @gateway.add_fields_to_post_if_present(post, options, [:order_id, :transaction_number]) + + assert_equal post[:order_id], order_id + assert_equal post[:transaction_number], transaction_number + assert_false post.key?(:do_not_add) + end end diff --git a/test/unit/gateways/global_collect_test.rb b/test/unit/gateways/global_collect_test.rb new file mode 100644 index 00000000000..fd1d5bff1fc --- /dev/null +++ b/test/unit/gateways/global_collect_test.rb @@ -0,0 +1,444 @@ +require 'test_helper' + +class GlobalCollectTest < Test::Unit::TestCase + include CommStub + + def setup + @gateway = GlobalCollectGateway.new(merchant_id: '1234', + api_key_id: '39u4193urng12', + secret_api_key: '109H/288H*50Y18W4/0G8571F245KA=') + + @credit_card = credit_card('4567350000427977') + @declined_card = credit_card('5424180279791732') + @accepted_amount = 4005 + @rejected_amount = 2997 + @options = { + billing_address: address, + description: 'Store Purchase' + } + end + + def test_successful_authorize_and_capture + response = stub_comms do + @gateway.authorize(@accepted_amount, @credit_card, @options) + end.respond_with(successful_authorize_response) + + assert_success response + assert_equal '000000142800000000920000100001', response.authorization + + capture = stub_comms do + @gateway.capture(@accepted_amount, response.authorization) + end.check_request do |endpoint, data, headers| + assert_match(/000000142800000000920000100001/, endpoint) + end.respond_with(successful_capture_response) + + assert_success capture + end + + def test_purchase_does_not_run_capture_if_authorize_auto_captured + response = stub_comms do + @gateway.purchase(@accepted_amount, @credit_card, @options) + end.respond_with(successful_capture_response) + + assert_success response + assert_equal 'CAPTURE_REQUESTED', response.params['payment']['status'] + assert_equal 1, response.responses.size + end + + def test_authorize_with_pre_authorization_flag + response = stub_comms do + @gateway.authorize(@accepted_amount, @credit_card, @options.merge(pre_authorization: true)) + end.check_request do |endpoint, data, headers| + assert_match(/PRE_AUTHORIZATION/, data) + end.respond_with(successful_authorize_response) + + assert_success response + end + + def test_authorize_without_pre_authorization_flag + response = stub_comms do + @gateway.authorize(@accepted_amount, @credit_card, @options) + end.check_request do |endpoint, data, headers| + assert_match(/FINAL_AUTHORIZATION/, data) + end.respond_with(successful_authorize_response) + + assert_success response + end + + def test_successful_authorization_with_extra_options + options = @options.merge( + { + customer: '123987', + email: 'example@example.com', + order_id: '123', + ip: '127.0.0.1', + fraud_fields: + { + 'website' => 'www.example.com', + 'giftMessage' => 'Happy Day!' + } + } + ) + + response = stub_comms do + @gateway.authorize(@accepted_amount, @credit_card, options) + end.check_request do |endpoint, data, headers| + assert_match %r("fraudFields":{"website":"www.example.com","giftMessage":"Happy Day!","customerIpAddress":"127.0.0.1"}), data + assert_match %r("merchantReference":"123"), data + assert_match %r("customer":{"personalInformation":{"name":{"firstName":"Longbob","surname":"Longsen"}},"merchantCustomerId":"123987","contactDetails":{"emailAddress":"example@example.com","phoneNumber":"\(555\)555-5555"},"billingAddress":{"street":"456 My Street","additionalInfo":"Apt 1","zip":"K1C2N6","city":"Ottawa","state":"ON","countryCode":"CA"}}}), data + end.respond_with(successful_authorize_response) + + assert_success response + end + + def test_trucates_first_name_to_15_chars + credit_card = credit_card('4567350000427977', { first_name: 'thisisaverylongfirstname' }) + + response = stub_comms do + @gateway.authorize(@accepted_amount, credit_card, @options) + end.check_request do |endpoint, data, headers| + assert_match(/thisisaverylong/, data) + end.respond_with(successful_authorize_response) + + assert_success response + assert_equal '000000142800000000920000100001', response.authorization + end + + def test_handles_blank_names + credit_card = credit_card('4567350000427977', { first_name: nil, last_name: nil}) + + response = stub_comms do + @gateway.authorize(@accepted_amount, credit_card, @options) + end.respond_with(successful_authorize_response) + + assert_success response + end + + def test_failed_authorize + response = stub_comms do + @gateway.authorize(@rejected_amount, @declined_card, @options) + end.respond_with(failed_authorize_response) + + assert_failure response + assert_equal 'Not authorised', response.message + end + + def test_failed_capture + response = stub_comms do + @gateway.capture(100, '', @options) + end.respond_with(failed_capture_response) + + assert_failure response + end + + def test_successful_void + response = stub_comms do + @gateway.purchase(@accepted_amount, @credit_card, @options) + end.respond_with(successful_capture_response) + + assert_success response + assert_equal '000000142800000000920000100001', response.authorization + + void = stub_comms do + @gateway.void(response.authorization) + end.check_request do |endpoint, data, headers| + assert_match(/000000142800000000920000100001/, endpoint) + end.respond_with(successful_void_response) + + assert_success void + end + + def test_failed_void + response = stub_comms do + @gateway.void('5d53a33d960c46d00f5dc061947d998c') + end.check_request do |endpoint, data, headers| + assert_match(/5d53a33d960c46d00f5dc061947d998c/, endpoint) + end.respond_with(failed_void_response) + + assert_failure response + end + + def test_successful_verify + response = stub_comms do + @gateway.verify(@credit_card, @options) + end.respond_with(successful_verify_response) + assert_equal '000000142800000000920000100001', response.authorization + + assert_success response + end + + def test_failed_verify + response = stub_comms do + @gateway.verify(@credit_card, @options) + end.respond_with(failed_verify_response) + assert_equal 'cee09c50-5d9d-41b8-b740-8c7bf06d2c66', response.authorization + + assert_failure response + end + + def test_successful_refund + response = stub_comms do + @gateway.authorize(@accepted_amount, @credit_card, @options) + end.respond_with(successful_authorize_response) + + assert_equal '000000142800000000920000100001', response.authorization + + capture = stub_comms do + @gateway.capture(@accepted_amount, response.authorization) + end.respond_with(successful_capture_response) + + refund = stub_comms do + @gateway.refund(@accepted_amount, capture.authorization) + end.check_request do |endpoint, data, headers| + assert_match(/000000142800000000920000100001/, endpoint) + end.respond_with(successful_refund_response) + + assert_success refund + end + + def test_refund_passes_currency_code + stub_comms do + @gateway.refund(@accepted_amount, '000000142800000000920000100001', {currency: 'COP'}) + end.check_request do |endpoint, data, headers| + assert_match(/"currencyCode\":\"COP\"/, data) + end.respond_with(failed_refund_response) + end + + def test_failed_refund + response = stub_comms do + @gateway.refund(nil, '') + end.respond_with(failed_refund_response) + + assert_failure response + end + + def test_rejected_refund + response = stub_comms do + @gateway.refund(@accepted_amount, '000000142800000000920000100001') + end.respond_with(rejected_refund_response) + + assert_failure response + assert_equal '1850', response.error_code + assert_equal 'Status: REJECTED', response.message + end + + def test_invalid_raw_response + response = stub_comms do + @gateway.purchase(@accepted_amount, @credit_card, @options) + end.respond_with(invalid_json_response) + + assert_failure response + assert_match %r{^Invalid response received from the Ingenico ePayments}, response.message + end + + def test_scrub + assert @gateway.supports_scrubbing? + assert_equal @gateway.scrub(pre_scrubbed), post_scrubbed + end + + def test_scrub_invalid_response + response = stub_comms do + @gateway.purchase(@accepted_amount, @credit_card, @options) + end.respond_with(invalid_json_plus_card_data).message + + assert_equal @gateway.scrub(response), scrubbed_invalid_json_plus + end + + private + + def pre_scrubbed + %q( + opening connection to api-sandbox.globalcollect.com:443... + opened + starting SSL for api-sandbox.globalcollect.com:443... + SSL established + <- "POST //v1/1428/payments HTTP/1.1\r\nContent-Type: application/json\r\nAuthorization: GCS v1HMAC:96f16a41890565d0:Bqv5QtSXi+SdqXUyoBBeXUDlRvi5DzSm49zWuJTLX9s=\r\nDate: Tue, 15 Mar 2016 14:32:13 GMT\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: api-sandbox.globalcollect.com\r\nContent-Length: 560\r\n\r\n" + <- "{\"order\":{\"amountOfMoney\":{\"amount\":\"100\",\"currencyCode\":\"USD\"},\"customer\":{\"merchantCustomerId\":null,\"personalInformation\":{\"name\":{\"firstName\":null,\"surname\":null}},\"billingAddress\":{\"street\":\"456 My Street\",\"additionalInfo\":\"Apt 1\",\"zip\":\"K1C2N6\",\"city\":\"Ottawa\",\"state\":\"ON\",\"countryCode\":\"CA\"}},\"contactDetails\":{\"emailAddress\":null}},\"cardPaymentMethodSpecificInput\":{\"paymentProductId\":\"1\",\"skipAuthentication\":\"true\",\"skipFraudService\":\"true\",\"card\":{\"cvv\":\"123\",\"cardNumber\":\"4567350000427977\",\"expiryDate\":\"0917\",\"cardholderName\":\"Longbob Longsen\"}}}" + -> "HTTP/1.1 201 Created\r\n" + -> "Date: Tue, 15 Mar 2016 18:32:14 GMT\r\n" + -> "Server: Apache/2.4.16 (Unix) OpenSSL/1.0.1p\r\n" + -> "Location: https://api-sandbox.globalcollect.com:443/v1/1428/payments/000000142800000000300000100001\r\n" + -> "X-Powered-By: Servlet/3.0 JSP/2.2\r\n" + -> "Connection: close\r\n" + -> "Transfer-Encoding: chunked\r\n" + -> "Content-Type: application/json\r\n" + -> "\r\n" + -> "457\r\n" + reading 1111 bytes... + -> "{\n \"creationOutput\" : {\n \"additionalReference\" : \"00000014280000000030\",\n \"externalReference\" : \"000000142800000000300000100001\"\n },\n \"payment\" : {\n \"id\" : \"000000142800000000300000100001\",\n \"paymentOutput\" : {\n \"amountOfMoney\" : {\n \"amount\" : 100,\n \"currencyCode\" : \"USD\"\n },\n \"references\" : {\n \"paymentReference\" : \"0\"\n },\n \"paymentMethod\" : \"card\",\n \"cardPaymentMethodSpecificOutput\" : {\n \"paymentProductId\" : 1,\n \"authorisationCode\" : \"OK1131\",\n \"card\" : {\n \"cardNumber\" : \"************7977\",\n \"expiryDate\" : \"0917\"\n },\n \"fraudResults\" : {\n \"fraudServiceResult\" : \"no-advice\",\n \"avsResult\" : \"0\",\n \"cvvResult\" : \"0\"\n }\n }\n },\n \"status\" : \"PENDING_APPROVAL\",\n \"statusOutput\" : {\n \"isCancellable\" : true,\n \"statusCode\" : 600,\n \"statusCodeChangeDateTime\" : \"20160315193214\",\n \"isAuthorized\" : true\n }\n }\n}" + read 1111 bytes + reading 2 bytes... + -> "" + -> "\r\n" + read 2 bytes + -> "0\r\n" + -> "\r\n" + Conn close + opening connection to api-sandbox.globalcollect.com:443... + opened + starting SSL for api-sandbox.globalcollect.com:443... + SSL established + <- "POST //v1/1428/payments/000000142800000000300000100001/approve HTTP/1.1\r\nContent-Type: application/json\r\nAuthorization: GCS v1HMAC:96f16a41890565d0:9GxB1mGvy8b2nXktFhxm9ppJVfcNrTNl7Szp/xiUXNc=\r\nDate: Tue, 15 Mar 2016 14:32:13 GMT\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: api-sandbox.globalcollect.com\r\nContent-Length: 208\r\n\r\n" + <- "{\"order\":{\"amountOfMoney\":{\"amount\":\"100\",\"currencyCode\":\"USD\"},\"customer\":{\"merchantCustomerId\":null,\"personalInformation\":{\"name\":{\"firstName\":null,\"surname\":null}}},\"contactDetails\":{\"emailAddress\":null}}}" + -> "HTTP/1.1 200 OK\r\n" + -> "Date: Tue, 15 Mar 2016 18:32:15 GMT\r\n" + -> "Server: Apache/2.4.16 (Unix) OpenSSL/1.0.1p\r\n" + -> "X-Powered-By: Servlet/3.0 JSP/2.2\r\n" + -> "Connection: close\r\n" + -> "Transfer-Encoding: chunked\r\n" + -> "Content-Type: application/json\r\n" + -> "\r\n" + -> "3c7\r\n" + reading 967 bytes... + -> "{\n \"payment\" : {\n \"id\" : \"000000142800000000300000100001\",\n \"paymentOutput\" : {\n \"amountOfMoney\" : {\n \"amount\" : 100,\n \"currencyCode\" : \"USD\"\n },\n \"references\" : {\n \"paymentReference\" : \"0\"\n },\n \"paymentMethod\" : \"card\",\n \"cardPaymentMethodSpecificOutput\" : {\n \"paymentProductId\" : 1,\n \"authorisationCode\" : \"OK1131\",\n \"card\" : {\n \"cardNumber\" : \"************7977\",\n \"expiryDate\" : \"0917\"\n },\n \"fraudResults\" : {\n \"fraudServiceResult\" : \"no-advice\",\n \"avsResult\" : \"0\",\n \"cvvResult\" : \"0\"\n }\n }\n },\n \"status\" : \"CAPTURE_REQUESTED\",\n \"statusOutput\" : {\n \"isCancellable\" : true,\n \"statusCode\" : 800,\n \"statusCodeChangeDateTime\" : \"20160315193215\",\n \"isAuthorized\" : true\n }\n }\n}" + read 967 bytes + reading 2 bytes... + -> "" + -> "\r\n" + read 2 bytes + -> "0\r\n" + -> "\r\n" + Conn close + ) + end + + def post_scrubbed + %q( + opening connection to api-sandbox.globalcollect.com:443... + opened + starting SSL for api-sandbox.globalcollect.com:443... + SSL established + <- "POST //v1/1428/payments HTTP/1.1\r\nContent-Type: application/json\r\nAuthorization: [FILTERED]\r\nDate: Tue, 15 Mar 2016 14:32:13 GMT\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: api-sandbox.globalcollect.com\r\nContent-Length: 560\r\n\r\n" + <- "{\"order\":{\"amountOfMoney\":{\"amount\":\"100\",\"currencyCode\":\"USD\"},\"customer\":{\"merchantCustomerId\":null,\"personalInformation\":{\"name\":{\"firstName\":null,\"surname\":null}},\"billingAddress\":{\"street\":\"456 My Street\",\"additionalInfo\":\"Apt 1\",\"zip\":\"K1C2N6\",\"city\":\"Ottawa\",\"state\":\"ON\",\"countryCode\":\"CA\"}},\"contactDetails\":{\"emailAddress\":null}},\"cardPaymentMethodSpecificInput\":{\"paymentProductId\":\"1\",\"skipAuthentication\":\"true\",\"skipFraudService\":\"true\",\"card\":{\"cvv\":\"[FILTERED]\",\"cardNumber\":\"[FILTERED]\",\"expiryDate\":\"0917\",\"cardholderName\":\"Longbob Longsen\"}}}" + -> "HTTP/1.1 201 Created\r\n" + -> "Date: Tue, 15 Mar 2016 18:32:14 GMT\r\n" + -> "Server: Apache/2.4.16 (Unix) OpenSSL/1.0.1p\r\n" + -> "Location: https://api-sandbox.globalcollect.com:443/v1/1428/payments/000000142800000000300000100001\r\n" + -> "X-Powered-By: Servlet/3.0 JSP/2.2\r\n" + -> "Connection: close\r\n" + -> "Transfer-Encoding: chunked\r\n" + -> "Content-Type: application/json\r\n" + -> "\r\n" + -> "457\r\n" + reading 1111 bytes... + -> "{\n \"creationOutput\" : {\n \"additionalReference\" : \"00000014280000000030\",\n \"externalReference\" : \"000000142800000000300000100001\"\n },\n \"payment\" : {\n \"id\" : \"000000142800000000300000100001\",\n \"paymentOutput\" : {\n \"amountOfMoney\" : {\n \"amount\" : 100,\n \"currencyCode\" : \"USD\"\n },\n \"references\" : {\n \"paymentReference\" : \"0\"\n },\n \"paymentMethod\" : \"card\",\n \"cardPaymentMethodSpecificOutput\" : {\n \"paymentProductId\" : 1,\n \"authorisationCode\" : \"OK1131\",\n \"card\" : {\n \"cardNumber\" : \"************7977\",\n \"expiryDate\" : \"0917\"\n },\n \"fraudResults\" : {\n \"fraudServiceResult\" : \"no-advice\",\n \"avsResult\" : \"0\",\n \"cvvResult\" : \"0\"\n }\n }\n },\n \"status\" : \"PENDING_APPROVAL\",\n \"statusOutput\" : {\n \"isCancellable\" : true,\n \"statusCode\" : 600,\n \"statusCodeChangeDateTime\" : \"20160315193214\",\n \"isAuthorized\" : true\n }\n }\n}" + read 1111 bytes + reading 2 bytes... + -> "" + -> "\r\n" + read 2 bytes + -> "0\r\n" + -> "\r\n" + Conn close + opening connection to api-sandbox.globalcollect.com:443... + opened + starting SSL for api-sandbox.globalcollect.com:443... + SSL established + <- "POST //v1/1428/payments/000000142800000000300000100001/approve HTTP/1.1\r\nContent-Type: application/json\r\nAuthorization: [FILTERED]\r\nDate: Tue, 15 Mar 2016 14:32:13 GMT\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: api-sandbox.globalcollect.com\r\nContent-Length: 208\r\n\r\n" + <- "{\"order\":{\"amountOfMoney\":{\"amount\":\"100\",\"currencyCode\":\"USD\"},\"customer\":{\"merchantCustomerId\":null,\"personalInformation\":{\"name\":{\"firstName\":null,\"surname\":null}}},\"contactDetails\":{\"emailAddress\":null}}}" + -> "HTTP/1.1 200 OK\r\n" + -> "Date: Tue, 15 Mar 2016 18:32:15 GMT\r\n" + -> "Server: Apache/2.4.16 (Unix) OpenSSL/1.0.1p\r\n" + -> "X-Powered-By: Servlet/3.0 JSP/2.2\r\n" + -> "Connection: close\r\n" + -> "Transfer-Encoding: chunked\r\n" + -> "Content-Type: application/json\r\n" + -> "\r\n" + -> "3c7\r\n" + reading 967 bytes... + -> "{\n \"payment\" : {\n \"id\" : \"000000142800000000300000100001\",\n \"paymentOutput\" : {\n \"amountOfMoney\" : {\n \"amount\" : 100,\n \"currencyCode\" : \"USD\"\n },\n \"references\" : {\n \"paymentReference\" : \"0\"\n },\n \"paymentMethod\" : \"card\",\n \"cardPaymentMethodSpecificOutput\" : {\n \"paymentProductId\" : 1,\n \"authorisationCode\" : \"OK1131\",\n \"card\" : {\n \"cardNumber\" : \"************7977\",\n \"expiryDate\" : \"0917\"\n },\n \"fraudResults\" : {\n \"fraudServiceResult\" : \"no-advice\",\n \"avsResult\" : \"0\",\n \"cvvResult\" : \"0\"\n }\n }\n },\n \"status\" : \"CAPTURE_REQUESTED\",\n \"statusOutput\" : {\n \"isCancellable\" : true,\n \"statusCode\" : 800,\n \"statusCodeChangeDateTime\" : \"20160315193215\",\n \"isAuthorized\" : true\n }\n }\n}" + read 967 bytes + reading 2 bytes... + -> "" + -> "\r\n" + read 2 bytes + -> "0\r\n" + -> "\r\n" + Conn close + ) + end + + def successful_authorize_response + %({\n \"creationOutput\" : {\n \"additionalReference\" : \"00000014280000000092\",\n \"externalReference\" : \"000000142800000000920000100001\"\n },\n \"payment\" : {\n \"id\" : \"000000142800000000920000100001\",\n \"paymentOutput\" : {\n \"amountOfMoney\" : {\n \"amount\" : 100,\n \"currencyCode\" : \"USD\"\n },\n \"references\" : {\n \"paymentReference\" : \"0\"\n },\n \"paymentMethod\" : \"card\",\n \"cardPaymentMethodSpecificOutput\" : {\n \"paymentProductId\" : 1,\n \"authorisationCode\" : \"OK1131\",\n \"card\" : {\n \"cardNumber\" : \"************7977\",\n \"expiryDate\" : \"0917\"\n },\n \"fraudResults\" : {\n \"fraudServiceResult\" : \"no-advice\",\n \"avsResult\" : \"0\",\n \"cvvResult\" : \"0\"\n }\n }\n },\n \"status\" : \"PENDING_APPROVAL\",\n \"statusOutput\" : {\n \"isCancellable\" : true,\n \"statusCode\" : 600,\n \"statusCodeChangeDateTime\" : \"20160316205952\",\n \"isAuthorized\" : true\n }\n }\n}) + end + + def failed_authorize_response + %({\n \"errorId\" : \"460ec7ed-f8be-4bd7-bf09-a4cbe07f774e\",\n \"errors\" : [ {\n \"code\" : \"430330\",\n \"message\" : \"Not authorised\"\n } ],\n \"paymentResult\" : {\n \"creationOutput\" : {\n \"additionalReference\" : \"00000014280000000064\",\n \"externalReference\" : \"000000142800000000640000100001\"\n },\n \"payment\" : {\n \"id\" : \"000000142800000000640000100001\",\n \"paymentOutput\" : {\n \"amountOfMoney\" : {\n \"amount\" : 100,\n \"currencyCode\" : \"USD\"\n },\n \"references\" : {\n \"paymentReference\" : \"0\"\n },\n \"paymentMethod\" : \"card\",\n \"cardPaymentMethodSpecificOutput\" : {\n \"paymentProductId\" : 1\n }\n },\n \"status\" : \"REJECTED\",\n \"statusOutput\" : {\n \"errors\" : [ {\n \"code\" : \"430330\",\n \"requestId\" : \"55635\",\n \"message\" : \"Not authorised\"\n } ],\n \"isCancellable\" : false,\n \"statusCode\" : 100,\n \"statusCodeChangeDateTime\" : \"20160316154235\",\n \"isAuthorized\" : false\n }\n }\n }\n}) + end + + def successful_capture_response + %({\n \"payment\" : {\n \"id\" : \"000000142800000000920000100001\",\n \"paymentOutput\" : {\n \"amountOfMoney\" : {\n \"amount\" : 100,\n \"currencyCode\" : \"USD\"\n },\n \"references\" : {\n \"paymentReference\" : \"0\"\n },\n \"paymentMethod\" : \"card\",\n \"cardPaymentMethodSpecificOutput\" : {\n \"paymentProductId\" : 1,\n \"authorisationCode\" : \"OK1131\",\n \"card\" : {\n \"cardNumber\" : \"************7977\",\n \"expiryDate\" : \"0917\"\n },\n \"fraudResults\" : {\n \"fraudServiceResult\" : \"no-advice\",\n \"avsResult\" : \"0\",\n \"cvvResult\" : \"0\"\n }\n }\n },\n \"status\" : \"CAPTURE_REQUESTED\",\n \"statusOutput\" : {\n \"isCancellable\" : true,\n \"statusCode\" : 800,\n \"statusCodeChangeDateTime\" : \"20160317191047\",\n \"isAuthorized\" : true\n }\n }\n}) + end + + def failed_capture_response + %({\n \"errorId\" : \"6a3ffb94-e1ed-41bc-b9fb-4a8759b3fed7\",\n \"errors\" : [ {\n \"code\" : \"1002\",\n \"propertyName\" : \"paymentId\",\n \"message\" : \"INVALID_PAYMENT_ID\"\n } ]\n}) + end + + def successful_refund_response + %({\n \"id\" : \"000000142800000000920000100001\",\n \"refundOutput\" : {\n \"amountOfMoney\" : {\n \"amount\" : 4005,\n \"currencyCode\" : \"USD\"\n },\n \"references\" : {\n \"paymentReference\" : \"0\"\n },\n \"paymentMethod\" : \"card\",\n \"cardRefundMethodSpecificOutput\" : {\n }\n },\n \"status\" : \"REFUND_REQUESTED\",\n \"statusOutput\" : {\n \"isCancellable\" : true,\n \"statusCode\" : 800,\n \"statusCodeChangeDateTime\" : \"20160317215704\"\n }\n}) + end + + def failed_refund_response + %({\n \"errorId\" : \"1bd31e6a-39dd-4214-941a-088a320e0286\",\n \"errors\" : [ {\n \"code\" : \"1002\",\n \"propertyName\" : \"paymentId\",\n \"message\" : \"INVALID_PAYMENT_ID\"\n } ]\n}) + end + + def rejected_refund_response + %({\n \"id\" : \"00000022184000047564000-100001\",\n \"refundOutput\" : {\n \"amountOfMoney\" : {\n \"amount\" : 627000,\n \"currencyCode\" : \"COP\"\n },\n \"references\" : {\n \"merchantReference\" : \"17091GTgZmcC\",\n \"paymentReference\" : \"0\"\n },\n \"paymentMethod\" : \"card\",\n \"cardRefundMethodSpecificOutput\" : {\n }\n },\n \"status\" : \"REJECTED\",\n \"statusOutput\" : {\n \"isCancellable\" : false,\n \"statusCategory\" : \"UNSUCCESSFUL\",\n \"statusCode\" : 1850,\n \"statusCodeChangeDateTime\" : \"20170313230631\"\n }\n}) + end + + def successful_void_response + %({\n \"payment\" : {\n \"id\" : \"000000142800000000920000100001\",\n \"paymentOutput\" : {\n \"amountOfMoney\" : {\n \"amount\" : 100,\n \"currencyCode\" : \"USD\"\n },\n \"references\" : {\n \"paymentReference\" : \"0\"\n },\n \"paymentMethod\" : \"card\",\n \"cardPaymentMethodSpecificOutput\" : {\n \"paymentProductId\" : 1,\n \"authorisationCode\" : \"OK1131\",\n \"card\" : {\n \"cardNumber\" : \"************7977\",\n \"expiryDate\" : \"0917\"\n },\n \"fraudResults\" : {\n \"fraudServiceResult\" : \"no-advice\",\n \"avsResult\" : \"0\",\n \"cvvResult\" : \"0\"\n }\n }\n },\n \"status\" : \"CANCELLED\",\n \"statusOutput\" : {\n \"isCancellable\" : false,\n \"statusCode\" : 99999,\n \"statusCodeChangeDateTime\" : \"20160317191526\"\n }\n }\n}) + end + + def failed_void_response + %({\n \"errorId\" : \"9e38736e-15f3-4d6b-8517-aad3029619b9\",\n \"errors\" : [ {\n \"code\" : \"1002\",\n \"propertyName\" : \"paymentId\",\n \"message\" : \"INVALID_PAYMENT_ID\"\n } ]\n}) + end + + def successful_verify_response + %({\n \"payment\" : {\n \"id\" : \"000000142800000000920000100001\",\n \"paymentOutput\" : {\n \"amountOfMoney\" : {\n \"amount\" : 100,\n \"currencyCode\" : \"USD\"\n },\n \"references\" : {\n \"paymentReference\" : \"0\"\n },\n \"paymentMethod\" : \"card\",\n \"cardPaymentMethodSpecificOutput\" : {\n \"paymentProductId\" : 1,\n \"authorisationCode\" : \"OK1131\",\n \"card\" : {\n \"cardNumber\" : \"************7977\",\n \"expiryDate\" : \"0917\"\n },\n \"fraudResults\" : {\n \"fraudServiceResult\" : \"no-advice\",\n \"avsResult\" : \"0\",\n \"cvvResult\" : \"0\"\n }\n }\n },\n \"status\" : \"CANCELLED\",\n \"statusOutput\" : {\n \"isCancellable\" : false,\n \"statusCode\" : 99999,\n \"statusCodeChangeDateTime\" : \"20160318170240\"\n }\n }\n}) + end + + def failed_verify_response + %({\n \"errorId\" : \"cee09c50-5d9d-41b8-b740-8c7bf06d2c66\",\n \"errors\" : [ {\n \"code\" : \"430330\",\n \"message\" : \"Not authorised\"\n } ],\n \"paymentResult\" : {\n \"creationOutput\" : {\n \"additionalReference\" : \"00000014280000000134\",\n \"externalReference\" : \"000000142800000000920000100001\"\n },\n \"payment\" : {\n \"id\" : \"000000142800000000920000100001\",\n \"paymentOutput\" : {\n \"amountOfMoney\" : {\n \"amount\" : 100,\n \"currencyCode\" : \"USD\"\n },\n \"references\" : {\n \"paymentReference\" : \"0\"\n },\n \"paymentMethod\" : \"card\",\n \"cardPaymentMethodSpecificOutput\" : {\n \"paymentProductId\" : 1\n }\n },\n \"status\" : \"REJECTED\",\n \"statusOutput\" : {\n \"errors\" : [ {\n \"code\" : \"430330\",\n \"requestId\" : \"64357\",\n \"message\" : \"Not authorised\"\n } ],\n \"isCancellable\" : false,\n \"statusCode\" : 100,\n \"statusCodeChangeDateTime\" : \"20160318170253\",\n \"isAuthorized\" : false\n }\n }\n }\n}) + end + + def invalid_json_response + '<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN"> + <html><head> + <title>502 Proxy Error</title> + </head><body> + <h1>Proxy Error</h1> + <p>The proxy server received an invalid + response from an upstream server.<br /> + The proxy server could not handle the request <em><a href="/v1/9040/payments">POST&nbsp;/v1/9040/payments</a></em>.<p> + Reason: <strong>Error reading from remote server</strong></p></p> + </body></html>' + end + + def invalid_json_plus_card_data + %q(<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN"> + <html><head> + <title>502 Proxy Error</title> + </head></html> + opening connection to api-sandbox.globalcollect.com:443... + opened + starting SSL for api-sandbox.globalcollect.com:443... + SSL established + <- "POST //v1/1428/payments HTTP/1.1\r\nContent-Type: application/json\r\nAuthorization: GCS v1HMAC:96f16a41890565d0:Bqv5QtSXi+SdqXUyoBBeXUDlRvi5DzSm49zWuJTLX9s=\r\nDate: Tue, 15 Mar 2016 14:32:13 GMT\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: api-sandbox.globalcollect.com\r\nContent-Length: 560\r\n\r\n" + <- "{\"order\":{\"amountOfMoney\":{\"amount\":\"100\",\"currencyCode\":\"USD\"},\"customer\":{\"merchantCustomerId\":null,\"personalInformation\":{\"name\":{\"firstName\":null,\"surname\":null}},\"billingAddress\":{\"street\":\"456 My Street\",\"additionalInfo\":\"Apt 1\",\"zip\":\"K1C2N6\",\"city\":\"Ottawa\",\"state\":\"ON\",\"countryCode\":\"CA\"}},\"contactDetails\":{\"emailAddress\":null}},\"cardPaymentMethodSpecificInput\":{\"paymentProductId\":\"1\",\"skipAuthentication\":\"true\",\"skipFraudService\":\"true\",\"card\":{\"cvv\":\"123\",\"cardNumber\":\"4567350000427977\",\"expiryDate\":\"0917\",\"cardholderName\":\"Longbob Longsen\"}}}" + -> "HTTP/1.1 201 Created\r\n" + -> "Date: Tue, 15 Mar 2016 18:32:14 GMT\r\n" + -> "Server: Apache/2.4.16 (Unix) OpenSSL/1.0.1p\r\n" + -> "Location: https://api-sandbox.globalcollect.com:443/v1/1428/payments/000000142800000000300000100001\r\n" + -> "X-Powered-By: Servlet/3.0 JSP/2.2\r\n" + -> "Connection: close\r\n" + -> "Transfer-Encoding: chunked\r\n" + -> "Content-Type: application/json\r\n" + -> "\r\n" + -> "457\r\n") + end + + def scrubbed_invalid_json_plus + 'Invalid response received from the Ingenico ePayments (formerly GlobalCollect) API. Please contact Ingenico ePayments if you continue to receive this message. (The raw response returned by the API was "<!DOCTYPE HTML PUBLIC \\"-//IETF//DTD HTML 2.0//EN\\">\\n <html><head>\\n <title>502 Proxy Error</title>\\n </head></html>\\n opening connection to api-sandbox.globalcollect.com:443...\\n opened\\n starting SSL for api-sandbox.globalcollect.com:443...\\n SSL established\\n <- \\"POST //v1/1428/payments HTTP/1.1\\\\r\\\\nContent-Type: application/json\\\\r\\\\nAuthorization: [FILTERED]\\\\r\\\\nDate: Tue, 15 Mar 2016 14:32:13 GMT\\\\r\\\\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\\\\r\\\\nAccept: */*\\\\r\\\\nUser-Agent: Ruby\\\\r\\\\nConnection: close\\\\r\\\\nHost: api-sandbox.globalcollect.com\\\\r\\\\nContent-Length: 560\\\\r\\\\n\\\\r\\\\n\\"\\n <- \\"{\\\\\\"order\\\\\\":{\\\\\\"amountOfMoney\\\\\\":{\\\\\\"amount\\\\\\":\\\\\\"100\\\\\\",\\\\\\"currencyCode\\\\\\":\\\\\\"USD\\\\\\"},\\\\\\"customer\\\\\\":{\\\\\\"merchantCustomerId\\\\\\":null,\\\\\\"personalInformation\\\\\\":{\\\\\\"name\\\\\\":{\\\\\\"firstName\\\\\\":null,\\\\\\"surname\\\\\\":null}},\\\\\\"billingAddress\\\\\\":{\\\\\\"street\\\\\\":\\\\\\"456 My Street\\\\\\",\\\\\\"additionalInfo\\\\\\":\\\\\\"Apt 1\\\\\\",\\\\\\"zip\\\\\\":\\\\\\"K1C2N6\\\\\\",\\\\\\"city\\\\\\":\\\\\\"Ottawa\\\\\\",\\\\\\"state\\\\\\":\\\\\\"ON\\\\\\",\\\\\\"countryCode\\\\\\":\\\\\\"CA\\\\\\"}},\\\\\\"contactDetails\\\\\\":{\\\\\\"emailAddress\\\\\\":null}},\\\\\\"cardPaymentMethodSpecificInput\\\\\\":{\\\\\\"paymentProductId\\\\\\":\\\\\\"1\\\\\\",\\\\\\"skipAuthentication\\\\\\":\\\\\\"true\\\\\\",\\\\\\"skipFraudService\\\\\\":\\\\\\"true\\\\\\",\\\\\\"card\\\\\\":{\\\\\\"cvv\\\\\\":\\\\\\"[FILTERED]\\\\\\",\\\\\\"cardNumber\\\\\\":\\\\\\"[FILTERED]\\\\\\",\\\\\\"expiryDate\\\\\\":\\\\\\"0917\\\\\\",\\\\\\"cardholderName\\\\\\":\\\\\\"Longbob Longsen\\\\\\"}}}\\"\\n -> \\"HTTP/1.1 201 Created\\\\r\\\\n\\"\\n -> \\"Date: Tue, 15 Mar 2016 18:32:14 GMT\\\\r\\\\n\\"\\n -> \\"Server: Apache/2.4.16 (Unix) OpenSSL/1.0.1p\\\\r\\\\n\\"\\n -> \\"Location: https://api-sandbox.globalcollect.com:443/v1/1428/payments/000000142800000000300000100001\\\\r\\\\n\\"\\n -> \\"X-Powered-By: Servlet/3.0 JSP/2.2\\\\r\\\\n\\"\\n -> \\"Connection: close\\\\r\\\\n\\"\\n -> \\"Transfer-Encoding: chunked\\\\r\\\\n\\"\\n -> \\"Content-Type: application/json\\\\r\\\\n\\"\\n -> \\"\\\\r\\\\n\\"\\n -> \\"457\\\\r\\\\n\\"")' + end +end diff --git a/test/unit/gateways/global_transport_test.rb b/test/unit/gateways/global_transport_test.rb new file mode 100644 index 00000000000..560c8033025 --- /dev/null +++ b/test/unit/gateways/global_transport_test.rb @@ -0,0 +1,476 @@ +require 'test_helper' + +class GlobalTransportTest < Test::Unit::TestCase + include CommStub + + def setup + Base.mode = :test + @gateway = GlobalTransportGateway.new(global_user_name: 'login', global_password: 'password', term_type: 'ABC') + + @options = { + order_id: '1', + billing_address: address, + } + end + + def test_successful_purchase + @gateway.expects(:ssl_post).returns(successful_purchase_response) + + response = @gateway.purchase(100, credit_card, @options) + assert_success response + assert_equal '3648838', response.authorization + assert response.test? + assert_equal 'CVV matches', response.cvv_result['message'] + assert_equal 'Street address and postal code do not match. For American Express: Card member\'s name, street address and postal code do not match.', response.avs_result['message'] + end + + def test_failed_purchase + @gateway.expects(:ssl_post).returns(failed_purchase_response) + + response = @gateway.purchase(100, credit_card, @options) + assert_failure response + end + + def test_successful_partial_purchase + @gateway.expects(:ssl_post).returns(successful_partial_purchase_response) + + response = @gateway.purchase(200, credit_card, @options) + assert_success response + assert_equal '8869188', response.authorization + assert_equal 'Partial Approval', response.message + assert_equal '3.54', response.params['balance_due'] + assert_equal '20.00', response.params['approved_amount'] + end + + def test_successful_authorize_and_capture + response = stub_comms do + @gateway.authorize(100, credit_card) + end.respond_with(successful_authorize_response) + + assert_success response + assert_equal '3648890', response.authorization + + capture = stub_comms do + @gateway.capture(100, response.authorization) + end.check_request do |endpoint, data, headers| + assert_match(/PNRef=3648890/, data) + end.respond_with(successful_capture_response) + + assert_success capture + end + + def test_successful_partial_authorize_and_capture + response = stub_comms do + @gateway.authorize(200, credit_card, @options) + end.respond_with(successful_partial_authorize_response) + + assert_success response + assert_equal '8869269', response.authorization + assert_equal 'Partial Approval', response.message + + capture = stub_comms do + @gateway.capture(150, response.authorization) + end.check_request do |endpoint, data, headers| + assert_match(/PNRef=8869269/, data) + end.respond_with(successful_partial_capture_response) + + assert_success capture + end + + def test_failed_authorize + @gateway.expects(:ssl_post).returns(failed_authorize_response) + + response = @gateway.authorize(100, credit_card, @options) + assert_failure response + end + + def test_failed_capture + capture = stub_comms do + @gateway.capture(100, 'Authorization') + end.respond_with(failed_capture_response) + + assert_failure capture + assert_match(/less than or equal/, capture.message) + end + + def test_successful_refund + response = stub_comms do + @gateway.purchase(100, credit_card) + end.respond_with(successful_purchase_response) + + assert_success response + assert_equal '3648838', response.authorization + + refund = stub_comms do + @gateway.refund(100, response.authorization) + end.check_request do |endpoint, data, headers| + assert_match(/PNRef=3648838/, data) + end.respond_with(successful_refund_response) + + assert_success refund + end + + def test_failed_refund + refund = stub_comms do + @gateway.refund(100, 'PurchaseAuth') + end.respond_with(failed_refund_response) + + assert_failure refund + end + + def test_successful_void + response = stub_comms do + @gateway.purchase(100, credit_card) + end.respond_with(successful_purchase_response) + + assert_success response + + void = stub_comms do + @gateway.void(response.authorization) + end.check_request do |endpoint, data, headers| + assert_match(/PNRef=3648838/, data) + end.respond_with(successful_void_response) + + assert_success void + end + + def test_failed_void + void = stub_comms do + @gateway.void('PurchaseAuth') + end.respond_with(failed_void_response) + + assert_failure void + assert_equal 'Invalid PNRef', void.message + end + + def test_successful_verify + @gateway.expects(:ssl_post).returns(successful_verify_response) + + response = @gateway.verify(credit_card, @options) + assert_success response + assert_equal '3649156', response.authorization + assert response.test? + end + + def test_failed_verify + @gateway.expects(:ssl_post).returns(failed_verify_response) + + response = @gateway.verify(credit_card, @options) + assert_failure response + end + + def test_truncation + stub_comms do + @gateway.purchase(100, credit_card, order_id: 'a' * 17) + end.check_request do |endpoint, data, headers| + assert_match(/&InvNum=a{16}&/, data) + end.respond_with(successful_purchase_response) + end + + def test_transcript_scrubbing + assert @gateway.supports_scrubbing? + assert_equal @gateway.scrub(pre_scrub), post_scrub + end + + private + + def successful_purchase_response + %( + <Response xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="GlobalPayments"> + <Result>0</Result> + <RespMSG>Approved</RespMSG> + <Message>AP</Message> + <AuthCode>VI0100</AuthCode> + <PNRef>3648838</PNRef> + <HostCode>0010</HostCode> + <GetAVSResult>N</GetAVSResult> + <GetAVSResultTXT>No Match</GetAVSResultTXT> + <GetStreetMatchTXT>No Match</GetStreetMatchTXT> + <GetZipMatchTXT>No Match</GetZipMatchTXT> + <GetCVResult>M</GetCVResult> + <GetCVResultTXT>Match</GetCVResultTXT> + <GetCommercialCard>False</GetCommercialCard> + <ExtData>InvNum=1,CardType=Visa,BatchNum=0003&lt;BatchNum&gt;0003&lt;/BatchNum&gt;&lt;ReceiptData&gt;&lt;MID&gt;332518545311149&lt;/MID&gt;&lt;Trans_Id&gt;014258077000462&lt;/Trans_Id&gt;&lt;Val_Code&gt;ABCA&lt;/Val_Code&gt;&lt;/ReceiptData&gt;</ExtData> + </Response> + ) + end + + def failed_purchase_response + %( + <Response xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="GlobalPayments"> + <Result>12</Result> + <RespMSG>Declined</RespMSG> + <Message>DECLINE</Message> + <PNRef>3648889</PNRef> + <GetAVSResult>N</GetAVSResult> + <GetAVSResultTXT>No Match</GetAVSResultTXT> + <GetStreetMatchTXT>No Match</GetStreetMatchTXT> + <GetZipMatchTXT>No Match</GetZipMatchTXT> + <GetCVResult>M</GetCVResult> + <GetCVResultTXT>Match</GetCVResultTXT> + <GetCommercialCard>False</GetCommercialCard> + <ExtData>InvNum=1,CardType=Visa&lt;ReceiptData&gt;&lt;MID&gt;332518545311149&lt;/MID&gt;&lt;Trans_Id&gt;014258078002543&lt;/Trans_Id&gt;&lt;Val_Code&gt;ABAD&lt;/Val_Code&gt;&lt;/ReceiptData&gt;&lt;ApprovedAmount&gt;0.00&lt;/ApprovedAmount&gt;&lt;BalanceDue&gt;14.00&lt;/BalanceDue&gt;</ExtData> + </Response> + ) + end + + def successful_partial_purchase_response + %( + <Response xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="GlobalPayments"> + <Result>200</Result> + <RespMSG>Partial Approval</RespMSG> + <Message>PARTIAL AP</Message> + <AuthCode>VI2000</AuthCode> + <PNRef>8869188</PNRef> + <HostCode>0004</HostCode> + <GetAVSResult>N</GetAVSResult> + <GetAVSResultTXT>No Match</GetAVSResultTXT> + <GetStreetMatchTXT>No Match</GetStreetMatchTXT> + <GetZipMatchTXT>No Match</GetZipMatchTXT> + <GetCVResult>M</GetCVResult> + <GetCVResultTXT>Match</GetCVResultTXT> + <GetCommercialCard>False</GetCommercialCard> + <ExtData>InvNum=1,CardType=Visa,BatchNum=0005&lt;BatchNum&gt;0005&lt;/BatchNum&gt;&lt;ReceiptData&gt;&lt;MID&gt;332518545311149&lt;/MID&gt;&lt;Trans_Id&gt;017198190587855&lt;/Trans_Id&gt;&lt;Val_Code&gt;AABC&lt;/Val_Code&gt;&lt;/ReceiptData&gt;&lt;ApprovedAmount&gt;20.00&lt;/ApprovedAmount&gt;&lt;BalanceDue&gt;3.54&lt;/BalanceDue&gt;</ExtData> + <AcqRefData>aWb017198190587855cAABCd5e10fJj470993170717112415k0057840C000000002354lA m000005</AcqRefData> + </Response> + ) + end + + def successful_partial_authorize_response + %( + <Response xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="GlobalPayments"> + <Result>200</Result> + <RespMSG>Partial Approval</RespMSG> + <Message>PARTIAL AP</Message> + <AuthCode>VI2000</AuthCode> + <PNRef>8869269</PNRef> + <GetAVSResult>N</GetAVSResult> + <GetAVSResultTXT>No Match</GetAVSResultTXT> + <GetStreetMatchTXT>No Match</GetStreetMatchTXT> + <GetZipMatchTXT>No Match</GetZipMatchTXT> + <GetCVResult>M</GetCVResult> + <GetCVResultTXT>Match</GetCVResultTXT> + <GetCommercialCard>False</GetCommercialCard> + <ExtData>InvNum=1,CardType=Visa&lt;ReceiptData&gt;&lt;MID&gt;332518545311149&lt;/MID&gt;&lt;Trans_Id&gt;017198190582649&lt;/Trans_Id&gt;&lt;Val_Code&gt;AABC&lt;/Val_Code&gt;&lt;/ReceiptData&gt;&lt;ApprovedAmount&gt;20.00&lt;/ApprovedAmount&gt;&lt;BalanceDue&gt;3.54&lt;/BalanceDue&gt;</ExtData> + <AcqRefData>aWb017198190582649cAABCd5e10fJj471048170717124409k0057840C000000002354lA m000005</AcqRefData> + </Response> + ) + end + + def successful_partial_capture_response + %( + <Response xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="GlobalPayments"> + <Result>0</Result> + <RespMSG>Approved</RespMSG> + <Message>AP</Message> + <AuthCode>VI2000</AuthCode> + <PNRef>8869275</PNRef> + <HostCode>0034</HostCode> + <GetCVResultTXT>Service Not Requested</GetCVResultTXT> + <GetCommercialCard>False</GetCommercialCard> + <ExtData>InvNum=1,CardType=Visa,BatchNum=0005&lt;ExtReceiptData&gt;&lt;AccountNumber&gt;************1111&lt;/AccountNumber&gt;&lt;Issuer&gt;Visa&lt;/Issuer&gt;&lt;Amount&gt;20.00&lt;/Amount&gt;&lt;AuthAmount&gt;20.00&lt;/AuthAmount&gt;&lt;TicketNumber&gt;1&lt;/TicketNumber&gt;&lt;EntryMode&gt;Manual CNP&lt;/EntryMode&gt;&lt;/ExtReceiptData&gt;&lt;BatchNum&gt;0005&lt;/BatchNum&gt;&lt;ReceiptData&gt;&lt;MID&gt;332518545311149&lt;/MID&gt;&lt;Trans_Id&gt;017198190583609&lt;/Trans_Id&gt;&lt;Val_Code&gt;AABC&lt;/Val_Code&gt;&lt;/ReceiptData&gt;</ExtData> + <AcqRefData>aWb017198190583609cAABCd5e10fJj471054170717130009lA m000005</AcqRefData> + </Response> + ) + end + + def successful_authorize_response + %( + <Response xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="GlobalPayments"> + <Result>0</Result> + <RespMSG>Approved</RespMSG> + <Message>AP</Message> + <AuthCode>VI0100</AuthCode> + <PNRef>3648890</PNRef> + <GetAVSResult>N</GetAVSResult> + <GetAVSResultTXT>No Match</GetAVSResultTXT> + <GetStreetMatchTXT>No Match</GetStreetMatchTXT> + <GetZipMatchTXT>No Match</GetZipMatchTXT> + <GetCVResult>M</GetCVResult> + <GetCVResultTXT>Match</GetCVResultTXT> + <GetCommercialCard>False</GetCommercialCard> + <ExtData>InvNum=1,CardType=Visa&lt;ReceiptData&gt;&lt;MID&gt;332518545311149&lt;/MID&gt;&lt;Trans_Id&gt;014258077002729&lt;/Trans_Id&gt;&lt;Val_Code&gt;ABCA&lt;/Val_Code&gt;&lt;/ReceiptData&gt;</ExtData> + </Response> + ) + end + + def failed_authorize_response + %( + <Response xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="GlobalPayments"> + <Result>12</Result> + <RespMSG>Declined</RespMSG> + <Message>DECLINE</Message> + <PNRef>3648893</PNRef> + <GetAVSResult>N</GetAVSResult> + <GetAVSResultTXT>No Match</GetAVSResultTXT> + <GetStreetMatchTXT>No Match</GetStreetMatchTXT> + <GetZipMatchTXT>No Match</GetZipMatchTXT> + <GetCVResult>M</GetCVResult> + <GetCVResultTXT>Match</GetCVResultTXT> + <GetCommercialCard>False</GetCommercialCard> + <ExtData>InvNum=1,CardType=Visa&lt;ReceiptData&gt;&lt;MID&gt;332518545311149&lt;/MID&gt;&lt;Trans_Id&gt;014258078002835&lt;/Trans_Id&gt;&lt;Val_Code&gt;ABAD&lt;/Val_Code&gt;&lt;/ReceiptData&gt;&lt;ApprovedAmount&gt;0.00&lt;/ApprovedAmount&gt;&lt;BalanceDue&gt;14.00&lt;/BalanceDue&gt;</ExtData> + </Response> + ) + end + + def successful_capture_response + %( + <Response xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="GlobalPayments"> + <Result>0</Result> + <RespMSG>Approved</RespMSG> + <Message>AP</Message> + <AuthCode>VI0100</AuthCode> + <PNRef>3648928</PNRef> + <HostCode>0028</HostCode> + <GetCVResultTXT>Service Not Requested</GetCVResultTXT> + <GetCommercialCard>False</GetCommercialCard> + <ExtData>InvNum=1,CardType=Visa,BatchNum=0003&lt;ExtReceiptData&gt;&lt;AccountNumber&gt;************6781&lt;/AccountNumber&gt;&lt;Issuer&gt;Visa&lt;/Issuer&gt;&lt;Amount&gt;1.00&lt;/Amount&gt;&lt;AuthAmount&gt;1.00&lt;/AuthAmount&gt;&lt;TicketNumber&gt;1&lt;/TicketNumber&gt;&lt;EntryMode&gt;Manual CNP&lt;/EntryMode&gt;&lt;/ExtReceiptData&gt;&lt;BatchNum&gt;0003&lt;/BatchNum&gt;&lt;ReceiptData&gt;&lt;MID&gt;332518545311149&lt;/MID&gt;&lt;Trans_Id&gt;014258077003984&lt;/Trans_Id&gt;&lt;Val_Code&gt;ABCA&lt;/Val_Code&gt;&lt;/ReceiptData&gt;</ExtData> + </Response> + ) + end + + def failed_capture_response + %( + <Response xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="GlobalPayments"> + <Result>118</Result> + <RespMSG>The amount of a Pre-Auth Complete (Capture) must be less than or equal to the original amount authorized. Please retry.</RespMSG> + </Response> + ) + end + + def successful_refund_response + %( + <Response xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="GlobalPayments"> + <Result>0</Result> + <RespMSG>Approved</RespMSG> + <Message>AP</Message> + <AuthCode>C04153</AuthCode> + <PNRef>3649149</PNRef> + <HostCode>0130</HostCode> + <GetCVResultTXT>Service Not Requested</GetCVResultTXT> + <GetCommercialCard>False</GetCommercialCard> + <ExtData>InvNum=1,CardType=Visa,BatchNum=0003&lt;ExtReceiptData&gt;&lt;AccountNumber&gt;************8903&lt;/AccountNumber&gt;&lt;Issuer&gt;Visa&lt;/Issuer&gt;&lt;Amount&gt;5.00&lt;/Amount&gt;&lt;AuthAmount&gt;5.00&lt;/AuthAmount&gt;&lt;TicketNumber&gt;1&lt;/TicketNumber&gt;&lt;EntryMode&gt;Manual CNP&lt;/EntryMode&gt;&lt;/ExtReceiptData&gt;&lt;BatchNum&gt;0003&lt;/BatchNum&gt;&lt;ReceiptData&gt;&lt;MID&gt;332518545311149&lt;/MID&gt;&lt;/ReceiptData&gt;</ExtData> + </Response> + ) + end + + def failed_refund_response + %( + <Response xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="GlobalPayments"> + <Result>113</Result> + <RespMSG>Requested Refund Exceeds Available Refund Amount</RespMSG> + </Response> + ) + end + + def successful_void_response + %( + <Response xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="GlobalPayments"> + <Result>0</Result> + <RespMSG>Approved</RespMSG> + <Message>REVERSED</Message> + <AuthCode>VOIDED</AuthCode> + <PNRef>3649152</PNRef> + <GetCVResultTXT>Service Not Requested</GetCVResultTXT> + <GetCommercialCard>False</GetCommercialCard> + <ExtData>InvNum=1,CardType=Visa&lt;ExtReceiptData&gt;&lt;AccountNumber&gt;************8903&lt;/AccountNumber&gt;&lt;Issuer&gt;Visa&lt;/Issuer&gt;&lt;Amount&gt;5.00&lt;/Amount&gt;&lt;AuthAmount&gt;5.00&lt;/AuthAmount&gt;&lt;TicketNumber&gt;1&lt;/TicketNumber&gt;&lt;EntryMode&gt;Manual CNP&lt;/EntryMode&gt;&lt;/ExtReceiptData&gt;&lt;ReceiptData&gt;&lt;MID&gt;332518545311149&lt;/MID&gt;&lt;/ReceiptData&gt;</ExtData> + </Response> + ) + end + + def failed_void_response + %( + <Response xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="GlobalPayments"> + <Result>26</Result> + <RespMSG>Invalid PNRef</RespMSG> + </Response> + ) + end + + def successful_verify_response + %( + <Response xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="GlobalPayments"> + <Result>0</Result> + <RespMSG>Approved</RespMSG> + <Message>AP</Message> + <AuthCode>VI0000</AuthCode> + <PNRef>3649156</PNRef> + <GetAVSResult>N</GetAVSResult> + <GetAVSResultTXT>No Match</GetAVSResultTXT> + <GetStreetMatchTXT>No Match</GetStreetMatchTXT> + <GetZipMatchTXT>No Match</GetZipMatchTXT> + <GetCVResult>M</GetCVResult> + <GetCVResultTXT>Match</GetCVResultTXT> + <GetCommercialCard>False</GetCommercialCard> + <ExtData>CardType=Visa&lt;ReceiptData&gt;&lt;MID&gt;332518545311149&lt;/MID&gt;&lt;Trans_Id&gt;014258000006274&lt;/Trans_Id&gt;&lt;Val_Code&gt;CDCD&lt;/Val_Code&gt;&lt;/ReceiptData&gt;</ExtData> + </Response> + ) + end + + def failed_verify_response + %( + <Response xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="GlobalPayments"> + <Result>23</Result> + <RespMSG>Invalid Account Number</RespMSG> + </Response> + ) + end + + def pre_scrub + %q{ +opening connection to certapia.globalpay.com:443... +opened +starting SSL for certapia.globalpay.com:443... +SSL established +<- "POST /GlobalPay/transact.asmx/ProcessCreditCard HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: certapia.globalpay.com\r\nContent-Length: 253\r\n\r\n" +<- "CardNum=4003002345678903&ExpDate=0919&NameOnCard=Longbob+Longsen&Amount=&PNRef=&Zip=K1C2N6&Street=456+My+Street&CVNum=123&MagData=&InvNum=1&ExtData=%3CTermType%3E1BJ%3C%2FTermType%3E&GlobalUserName=spre930948&GlobalPassword=AoaeYX2n3Y7wfr&TransType=Sale" +-> "HTTP/1.1 200 OK\r\n" +-> "Date: Mon, 08 Jan 2018 16:00:33 GMT\r\n" +-> "Content-Type: text/xml; charset=utf-8\r\n" +-> "Content-Length: 559\r\n" +-> "Connection: close\r\n" +-> "Cache-Control: private, no-store, max-age=0\r\n" +-> "Pragma: no-cache\r\n" +-> "X-Frame-Options: SAMEORIGIN\r\n" +-> "Set-Cookie: ASP.NET_SessionId=tawdjune2xixlighniqxcvkm; path=/; HttpOnly\r\n" +-> "X-AspNet-Version: 4.0.30319\r\n" +-> "X-Powered-By: ASP.NET\r\n" +-> "Set-Cookie: TS012462a7=01cf83bd2f409aa8ee6c4cacb355788019f1a8ff3010306e6b8fe1c42e01745058ecc78aeff78d5071c2b7c56c186a470efd7c78f1; Path=/; Secure\r\n" +-> "Set-Cookie: TS012462a7_28=013b80ac89ca00c8b688533fc64e6f7b3fa3424b483ef82651a9f9a1c184ec131cc099732b39bf84f703f9f0754d2a12a53fe3d537; Path=/; Secure\r\n" +-> "\r\n" +reading 559 bytes... +-> "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n<Response xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns=\"GlobalPayments\">\r\n <Result>12</Result>\r\n <RespMSG>Declined</RespMSG>\r\n <Message>INVLD AMOUNT</Message>\r\n <PNRef>9169188</PNRef>\r\n <GetCVResultTXT>Service Not Requested</GetCVResultTXT>\r\n <GetCommercialCard>False</GetCommercialCard>\r\n <ExtData>InvNum=1,CardType=Visa&lt;ReceiptData&gt;&lt;MID&gt;332518545311149&lt;/MID&gt;&lt;/ReceiptData&gt;</ExtData>\r\n <AcqRefData>aY</AcqRefData>\r\n</Response>" +read 559 bytes +Conn close + } + end + + def post_scrub + %q{ +opening connection to certapia.globalpay.com:443... +opened +starting SSL for certapia.globalpay.com:443... +SSL established +<- "POST /GlobalPay/transact.asmx/ProcessCreditCard HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: certapia.globalpay.com\r\nContent-Length: 253\r\n\r\n" +<- "CardNum=[FILTERED]&ExpDate=0919&NameOnCard=Longbob+Longsen&Amount=&PNRef=&Zip=K1C2N6&Street=456+My+Street&CVNum=[FILTERED]&MagData=&InvNum=1&ExtData=%3CTermType%3E1BJ%3C%2FTermType%3E&GlobalUserName=spre930948&GlobalPassword=[FILTERED]&TransType=Sale" +-> "HTTP/1.1 200 OK\r\n" +-> "Date: Mon, 08 Jan 2018 16:00:33 GMT\r\n" +-> "Content-Type: text/xml; charset=utf-8\r\n" +-> "Content-Length: 559\r\n" +-> "Connection: close\r\n" +-> "Cache-Control: private, no-store, max-age=0\r\n" +-> "Pragma: no-cache\r\n" +-> "X-Frame-Options: SAMEORIGIN\r\n" +-> "Set-Cookie: ASP.NET_SessionId=tawdjune2xixlighniqxcvkm; path=/; HttpOnly\r\n" +-> "X-AspNet-Version: 4.0.30319\r\n" +-> "X-Powered-By: ASP.NET\r\n" +-> "Set-Cookie: TS012462a7=01cf83bd2f409aa8ee6c4cacb355788019f1a8ff3010306e6b8fe1c42e01745058ecc78aeff78d5071c2b7c56c186a470efd7c78f1; Path=/; Secure\r\n" +-> "Set-Cookie: TS012462a7_28=013b80ac89ca00c8b688533fc64e6f7b3fa3424b483ef82651a9f9a1c184ec131cc099732b39bf84f703f9f0754d2a12a53fe3d537; Path=/; Secure\r\n" +-> "\r\n" +reading 559 bytes... +-> "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n<Response xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns=\"GlobalPayments\">\r\n <Result>12</Result>\r\n <RespMSG>Declined</RespMSG>\r\n <Message>INVLD AMOUNT</Message>\r\n <PNRef>9169188</PNRef>\r\n <GetCVResultTXT>Service Not Requested</GetCVResultTXT>\r\n <GetCommercialCard>False</GetCommercialCard>\r\n <ExtData>InvNum=1,CardType=Visa&lt;ReceiptData&gt;&lt;MID&gt;332518545311149&lt;/MID&gt;&lt;/ReceiptData&gt;</ExtData>\r\n <AcqRefData>aY</AcqRefData>\r\n</Response>" +read 559 bytes +Conn close + } + end +end diff --git a/test/unit/gateways/hdfc_test.rb b/test/unit/gateways/hdfc_test.rb index 34775cd159e..9913adedf19 100644 --- a/test/unit/gateways/hdfc_test.rb +++ b/test/unit/gateways/hdfc_test.rb @@ -22,7 +22,7 @@ def test_successful_purchase assert_success response - assert_equal "849768440022761|Longbob Longsen", response.authorization + assert_equal '849768440022761|Longbob Longsen', response.authorization assert response.test? end @@ -32,8 +32,8 @@ def test_failed_purchase end.respond_with(failed_purchase_response) assert_failure response - assert_equal "Invalid Brand.", response.message - assert_equal "GW00160", response.params["error_code_tag"] + assert_equal 'Invalid Brand.', response.message + assert_equal 'GW00160', response.params['error_code_tag'] assert response.test? end @@ -43,7 +43,7 @@ def test_authorize_and_capture end.respond_with(successful_authorize_response) assert_success response - assert_equal "2441955352022771|Longbob Longsen", response.authorization + assert_equal '2441955352022771|Longbob Longsen', response.authorization capture = stub_comms do @gateway.capture(@amount, response.authorization) @@ -60,7 +60,7 @@ def test_refund end.respond_with(successful_purchase_response) assert_success response - assert_equal "849768440022761|Longbob Longsen", response.authorization + assert_equal '849768440022761|Longbob Longsen', response.authorization refund = stub_comms do @gateway.refund(@amount, response.authorization) @@ -81,21 +81,21 @@ def test_passing_cvv def test_passing_currency stub_comms do - @gateway.purchase(@amount, @credit_card, :currency => "INR") + @gateway.purchase(@amount, @credit_card, :currency => 'INR') end.check_request do |endpoint, data, headers| assert_match(/currencycode>356</, data) end.respond_with(successful_purchase_response) end def test_passing_invalid_currency - assert_raise(ArgumentError, %r(unsupported currency)i) do - @gateway.purchase(@amount, @credit_card, :currency => "AOA") + assert_raise(ArgumentError, 'Unsupported currency for HDFC: AOA') do + @gateway.purchase(@amount, @credit_card, :currency => 'AOA') end end def test_passing_order_id stub_comms do - @gateway.purchase(@amount, @credit_card, :order_id => "932823723") + @gateway.purchase(@amount, @credit_card, :order_id => '932823723') end.check_request do |endpoint, data, headers| assert_match(/932823723/, data) end.respond_with(successful_purchase_response) @@ -103,7 +103,7 @@ def test_passing_order_id def test_passing_description stub_comms do - @gateway.purchase(@amount, @credit_card, :description => "Awesome Services By Us") + @gateway.purchase(@amount, @credit_card, :description => 'Awesome Services By Us') end.check_request do |endpoint, data, headers| assert_match(/Awesome Services By Us/, data) end.respond_with(successful_purchase_response) @@ -111,7 +111,7 @@ def test_passing_description def test_escaping stub_comms do - @gateway.purchase(@amount, @credit_card, :order_id => "a" * 41, :description => "This has 'Hack Characters' ~`!\#$%^=+|\\:'\",;<>{}[]() and non-Hack Characters -_@.") + @gateway.purchase(@amount, @credit_card, :order_id => 'a' * 41, :description => "This has 'Hack Characters' ~`!\#$%^=+|\\:'\",;<>{}[]() and non-Hack Characters -_@.") end.check_request do |endpoint, data, headers| assert_match(/>This has Hack Characters and non-Hack Characters -_@.</, data) assert_match(/>#{"a" * 40}</, data) @@ -122,7 +122,7 @@ def test_passing_billing_address stub_comms do @gateway.purchase(@amount, @credit_card, :billing_address => address) end.check_request do |endpoint, data, headers| - assert_match(/udf4>Jim Smith\nWidgets Inc\n1234 My Street\nApt 1\nOttawa ON K1C2N6\nCA/, data) + assert_match(/udf4>Jim Smith\nWidgets Inc\n456 My Street\nApt 1\nOttawa ON K1C2N6\nCA/, data) end.respond_with(successful_purchase_response) end @@ -156,7 +156,7 @@ def test_empty_response_fails end.respond_with(empty_purchase_response) assert_failure response - assert_equal "Unable to read error message", response.message + assert_equal 'Unable to read error message', response.message end def test_handling_bad_xml @@ -178,8 +178,8 @@ def test_handling_bad_xml )) assert_success response - assert_equal "&", response.params["unescaped"] - assert_equal "&\"'<>", response.params["escaped"] + assert_equal '&', response.params['unescaped'] + assert_equal "&\"'<>", response.params['escaped'] end private diff --git a/test/unit/gateways/hps_test.rb b/test/unit/gateways/hps_test.rb new file mode 100644 index 00000000000..ef046791860 --- /dev/null +++ b/test/unit/gateways/hps_test.rb @@ -0,0 +1,985 @@ +require 'test_helper' + +class HpsTest < Test::Unit::TestCase + include CommStub + + def setup + @gateway = HpsGateway.new({:secret_api_key => '12'}) + + @credit_card = credit_card + @amount = 100 + + @options = { + order_id: '1', + billing_address: address, + description: 'Store Purchase' + } + end + + def test_successful_purchase + @gateway.expects(:ssl_post).returns(successful_charge_response) + + response = @gateway.purchase(@amount, @credit_card, @options) + assert_instance_of Response, response + assert_success response + end + + def test_successful_purchase_no_address + @gateway.expects(:ssl_post).returns(successful_charge_response) + + options = { + order_id: '1', + description: 'Store Purchase' + } + response = @gateway.purchase(@amount, @credit_card, options) + assert_instance_of Response, response + assert_success response + end + + def test_failed_purchase + @gateway.expects(:ssl_post).returns(failed_charge_response) + + response = @gateway.purchase(10.34, @credit_card, @options) + assert_instance_of Response, response + assert_failure response + end + + def test_successful_authorize + @gateway.expects(:ssl_post).returns(successful_authorize_response) + + response = @gateway.authorize(@amount, @credit_card, @options) + assert_instance_of Response, response + assert_success response + end + + def test_successful_authorize_no_address + @gateway.expects(:ssl_post).returns(successful_charge_response) + + options = { + order_id: '1', + description: 'Store Authorize' + } + response = @gateway.authorize(@amount, @credit_card, options) + assert_instance_of Response, response + assert_success response + end + + def test_failed_authorize + @gateway.expects(:ssl_post).returns(failed_authorize_response) + + response = @gateway.authorize(10.34, @credit_card, @options) + assert_instance_of Response, response + assert_failure response + end + + def test_successful_capture + @gateway.expects(:ssl_post).returns(successful_capture_response) + + capture_response = @gateway.capture(@amount, 16072899) + assert_instance_of Response, capture_response + assert_success capture_response + end + + def test_failed_capture + @gateway.expects(:ssl_post).returns(failed_capture_response) + + capture_response = @gateway.capture(@amount, 216072899) + assert_instance_of Response, capture_response + assert_failure capture_response + assert_equal 'Transaction rejected because the referenced original transaction is invalid. Subject \'216072899\'. Original transaction not found.', capture_response.message + end + + def test_successful_refund + @gateway.expects(:ssl_post).returns(successful_refund_response) + + refund = @gateway.refund(@amount, 'transaction_id') + assert_instance_of Response, refund + assert_success refund + assert_equal '0', refund.params['GatewayRspCode'] + end + + def test_failed_refund + @gateway.expects(:ssl_post).returns(failed_refund_response) + + refund = @gateway.refund(@amount, '169054') + assert_instance_of Response, refund + assert_failure refund + end + + def test_successful_void + @gateway.expects(:ssl_post).returns(successful_void_response) + + void = @gateway.void('169054') + assert_instance_of Response, void + assert_success void + end + + def test_failed_void + @gateway.expects(:ssl_post).returns(failed_refund_response) + + void = @gateway.void('169054') + assert_instance_of Response, void + assert_failure void + end + + def test_successful_purchase_with_swipe_no_encryption + @gateway.expects(:ssl_post).returns(successful_swipe_purchase_response) + + @credit_card.track_data = '%B547888879888877776?;5473500000000014=25121019999888877776?' + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal 'Success', response.message + end + + def test_failed_purchase_with_swipe_bad_track_data + @gateway.expects(:ssl_post).returns(failed_swipe_purchase_response) + + @credit_card.track_data = '%B547888879888877776?;?' + response = @gateway.purchase(@amount, @credit_card, @options) + + assert_failure response + assert_equal 'Transaction was rejected because the track data could not be read.', response.message + end + + def test_successful_purchase_with_swipe_encryption_type_01 + @gateway.expects(:ssl_post).returns(successful_swipe_purchase_response) + + @options[:encryption_type] = '01' + @credit_card.track_data = '&lt;E1052711%B5473501000000014^MC TEST CARD^251200000000000000000000000000000000?|GVEY/MKaKXuqqjKRRueIdCHPPoj1gMccgNOtHC41ymz7bIvyJJVdD3LW8BbwvwoenI+|+++++++C4cI2zjMp|11;5473501000000014=25120000000000000000?|8XqYkQGMdGeiIsgM0pzdCbEGUDP|+++++++C4cI2zjMp|00|||/wECAQECAoFGAgEH2wYcShV78RZwb3NAc2VjdXJlZXhjaGFuZ2UubmV0PX50qfj4dt0lu9oFBESQQNkpoxEVpCW3ZKmoIV3T93zphPS3XKP4+DiVlM8VIOOmAuRrpzxNi0TN/DWXWSjUC8m/PI2dACGdl/hVJ/imfqIs68wYDnp8j0ZfgvM26MlnDbTVRrSx68Nzj2QAgpBCHcaBb/FZm9T7pfMr2Mlh2YcAt6gGG1i2bJgiEJn8IiSDX5M2ybzqRT86PCbKle/XCTwFFe1X|&gt;' + response = @gateway.purchase(@amount, @credit_card, @options) + + assert_success response + assert_equal 'Success', response.message + end + + def test_successful_purchase_with_swipe_encryption_type_02 + @gateway.expects(:ssl_post).returns(successful_swipe_purchase_response) + + @options[:encryption_type] = '02' + @options[:encrypted_track_number] = 2 + @options[:ktb] = '/wECAQECAoFGAgEH3QgVTDT6jRZwb3NAc2VjdXJlZXhjaGFuZ2UubmV0Nkt08KRSPigRYcr1HVgjRFEvtUBy+VcCKlOGA3871r3SOkqDvH2+30insdLHmhTLCc4sC2IhlobvWnutAfylKk2GLspH/pfEnVKPvBv0hBnF4413+QIRlAuGX6+qZjna2aMl0kIsjEY4N6qoVq2j5/e5I+41+a2pbm61blv2PEMAmyuCcAbN3/At/1kRZNwN6LSUg9VmJO83kOglWBe1CbdFtncq' + @credit_card.track_data = '7SV2BK6ESQPrq01iig27E74SxMg' + response = @gateway.purchase(@amount, @credit_card, @options) + + assert_success response + assert_equal 'Success', response.message + end + + def test_successful_verify + @gateway.expects(:ssl_post).returns(successful_verify_response) + + response = @gateway.verify(@credit_card, @options) + assert_success response + assert_equal 'Success', response.message + end + + def test_failed_verify + @gateway.expects(:ssl_post).returns(failed_verify_response) + @credit_card.number = 12345 + + response = @gateway.verify(@credit_card, @options) + + assert_failure response + assert_equal 'The card number is not a valid credit card number.', response.message + end + + def test_test_returns_true + gateway = HpsGateway.new(fixtures(:hps)) + assert_equal true, gateway.send(:test?) + end + + def test_test_returns_false + assert_false @gateway.send(:test?) + end + + def test_transcript_scrubbing + assert @gateway.supports_scrubbing? + assert_equal @gateway.scrub(pre_scrub), post_scrub + end + + def test_successful_purchase_with_apple_pay_raw_cryptogram_with_eci + @gateway.expects(:ssl_post).returns(successful_charge_response) + + credit_card = network_tokenization_credit_card('4242424242424242', + payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=', + verification_value: nil, + eci: '05', + source: :apple_pay + ) + assert response = @gateway.purchase(@amount, credit_card, @options) + assert_success response + assert_equal 'Success', response.message + end + + def test_failed_purchase_with_apple_pay_raw_cryptogram_with_eci + @gateway.expects(:ssl_post).returns(failed_charge_response_decline) + + credit_card = network_tokenization_credit_card('4242424242424242', + payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=', + verification_value: nil, + eci: '05', + source: :apple_pay + ) + assert response = @gateway.purchase(@amount, credit_card, @options) + assert_failure response + assert_equal 'The card was declined.', response.message + end + + def test_successful_purchase_with_apple_pay_raw_cryptogram_without_eci + @gateway.expects(:ssl_post).returns(successful_charge_response) + + credit_card = network_tokenization_credit_card('4242424242424242', + payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=', + verification_value: nil, + source: :apple_pay + ) + assert response = @gateway.purchase(@amount, credit_card, @options) + assert_success response + assert_equal 'Success', response.message + end + + def test_failed_purchase_with_apple_pay_raw_cryptogram_without_eci + @gateway.expects(:ssl_post).returns(failed_charge_response_decline) + + credit_card = network_tokenization_credit_card('4242424242424242', + payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=', + verification_value: nil, + source: :apple_pay + ) + assert response = @gateway.purchase(@amount, credit_card, @options) + assert_failure response + assert_equal 'The card was declined.', response.message + end + + def test_successful_auth_with_apple_pay_raw_cryptogram_with_eci + @gateway.expects(:ssl_post).returns(successful_authorize_response) + + credit_card = network_tokenization_credit_card('4242424242424242', + payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=', + verification_value: nil, + eci: '05', + source: :apple_pay + ) + assert response = @gateway.authorize(@amount, credit_card, @options) + assert_success response + assert_equal 'Success', response.message + end + + def test_failed_auth_with_apple_pay_raw_cryptogram_with_eci + @gateway.expects(:ssl_post).returns(failed_authorize_response_decline) + + credit_card = network_tokenization_credit_card('4242424242424242', + payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=', + verification_value: nil, + eci: '05', + source: :apple_pay + ) + assert response = @gateway.authorize(@amount, credit_card, @options) + assert_failure response + assert_equal 'The card was declined.', response.message + end + + def test_successful_auth_with_apple_pay_raw_cryptogram_without_eci + @gateway.expects(:ssl_post).returns(successful_authorize_response) + + credit_card = network_tokenization_credit_card('4242424242424242', + payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=', + verification_value: nil, + source: :apple_pay + ) + assert response = @gateway.authorize(@amount, credit_card, @options) + assert_success response + assert_equal 'Success', response.message + end + + def test_failed_auth_with_apple_pay_raw_cryptogram_without_eci + @gateway.expects(:ssl_post).returns(failed_authorize_response_decline) + + credit_card = network_tokenization_credit_card('4242424242424242', + payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=', + verification_value: nil, + source: :apple_pay + ) + assert response = @gateway.authorize(@amount, credit_card, @options) + assert_failure response + assert_equal 'The card was declined.', response.message + end + + def test_three_d_secure_visa + @credit_card.number = '4012002000060016' + @credit_card.brand = 'visa' + + options = { + :three_d_secure => { + :cavv => 'EHuWW9PiBkWvqE5juRwDzAUFBAk=', + :eci => '05', + :xid => 'TTBCSkVTa1ZpbDI1bjRxbGk5ODE=' + } + } + + response = stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, @credit_card, options) + end.check_request do |method, endpoint, data, headers| + assert_match(/<hps:SecureECommerce>(.*)<\/hps:SecureECommerce>/, data) + assert_match(/<hps:PaymentDataSource>Visa 3DSecure<\/hps:PaymentDataSource>/, data) + assert_match(/<hps:TypeOfPaymentData>3DSecure<\/hps:TypeOfPaymentData>/, data) + assert_match(/<hps:PaymentData>#{options[:three_d_secure][:cavv]}<\/hps:PaymentData>/, data) + assert_match(/<hps:ECommerceIndicator>5<\/hps:ECommerceIndicator>/, data) + assert_match(/<hps:XID>#{options[:three_d_secure][:xid]}<\/hps:XID>/, data) + end.respond_with(successful_charge_response) + + assert_success response + assert_equal 'Success', response.message + end + + def test_three_d_secure_mastercard + @credit_card.number = '5473500000000014' + @credit_card.brand = 'master' + + options = { + :three_d_secure => { + :cavv => 'EHuWW9PiBkWvqE5juRwDzAUFBAk=', + :eci => '05', + :xid => 'TTBCSkVTa1ZpbDI1bjRxbGk5ODE=' + } + } + + response = stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, @credit_card, options) + end.check_request do |method, endpoint, data, headers| + assert_match(/<hps:SecureECommerce>(.*)<\/hps:SecureECommerce>/, data) + assert_match(/<hps:PaymentDataSource>MasterCard 3DSecure<\/hps:PaymentDataSource>/, data) + assert_match(/<hps:TypeOfPaymentData>3DSecure<\/hps:TypeOfPaymentData>/, data) + assert_match(/<hps:PaymentData>#{options[:three_d_secure][:cavv]}<\/hps:PaymentData>/, data) + assert_match(/<hps:ECommerceIndicator>5<\/hps:ECommerceIndicator>/, data) + assert_match(/<hps:XID>#{options[:three_d_secure][:xid]}<\/hps:XID>/, data) + end.respond_with(successful_charge_response) + + assert_success response + assert_equal 'Success', response.message + end + + def test_three_d_secure_discover + @credit_card.number = '6011000990156527' + @credit_card.brand = 'discover' + + options = { + :three_d_secure => { + :cavv => 'EHuWW9PiBkWvqE5juRwDzAUFBAk=', + :eci => '5', + :xid => 'TTBCSkVTa1ZpbDI1bjRxbGk5ODE=' + } + } + + response = stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, @credit_card, options) + end.check_request do |method, endpoint, data, headers| + assert_match(/<hps:SecureECommerce>(.*)<\/hps:SecureECommerce>/, data) + assert_match(/<hps:PaymentDataSource>Discover 3DSecure<\/hps:PaymentDataSource>/, data) + assert_match(/<hps:TypeOfPaymentData>3DSecure<\/hps:TypeOfPaymentData>/, data) + assert_match(/<hps:PaymentData>#{options[:three_d_secure][:cavv]}<\/hps:PaymentData>/, data) + assert_match(/<hps:ECommerceIndicator>5<\/hps:ECommerceIndicator>/, data) + assert_match(/<hps:XID>#{options[:three_d_secure][:xid]}<\/hps:XID>/, data) + end.respond_with(successful_charge_response) + + assert_success response + assert_equal 'Success', response.message + end + + def test_three_d_secure_amex + @credit_card.number = '372700699251018' + @credit_card.brand = 'american_express' + + options = { + :three_d_secure => { + :cavv => 'EHuWW9PiBkWvqE5juRwDzAUFBAk=', + :eci => '05', + :xid => 'TTBCSkVTa1ZpbDI1bjRxbGk5ODE=' + } + } + + response = stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, @credit_card, options) + end.check_request do |method, endpoint, data, headers| + assert_match(/<hps:SecureECommerce>(.*)<\/hps:SecureECommerce>/, data) + assert_match(/<hps:PaymentDataSource>AMEX 3DSecure<\/hps:PaymentDataSource>/, data) + assert_match(/<hps:TypeOfPaymentData>3DSecure<\/hps:TypeOfPaymentData>/, data) + assert_match(/<hps:PaymentData>#{options[:three_d_secure][:cavv]}<\/hps:PaymentData>/, data) + assert_match(/<hps:ECommerceIndicator>5<\/hps:ECommerceIndicator>/, data) + assert_match(/<hps:XID>#{options[:three_d_secure][:xid]}<\/hps:XID>/, data) + end.respond_with(successful_charge_response) + + assert_success response + assert_equal 'Success', response.message + end + + def test_three_d_secure_jcb + @credit_card.number = '372700699251018' + @credit_card.brand = 'jcb' + + options = { + :three_d_secure => { + :cavv => 'EHuWW9PiBkWvqE5juRwDzAUFBAk=', + :eci => '5', + :xid => 'TTBCSkVTa1ZpbDI1bjRxbGk5ODE=' + } + } + + response = stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, @credit_card, options) + end.check_request do |method, endpoint, data, headers| + refute_match(/<hps:SecureECommerce>(.*)<\/hps:SecureECommerce>/, data) + refute_match(/<hps:PaymentDataSource>(.*)<\/hps:PaymentDataSource>/, data) + refute_match(/<hps:TypeOfPaymentData>3DSecure<\/hps:TypeOfPaymentData>/, data) + refute_match(/<hps:PaymentData>#{options[:three_d_secure][:cavv]}<\/hps:PaymentData>/, data) + refute_match(/<hps:ECommerceIndicator>5<\/hps:ECommerceIndicator>/, data) + refute_match(/<hps:XID>#{options[:three_d_secure][:xid]}<\/hps:XID>/, data) + end.respond_with(successful_charge_response) + + assert_success response + assert_equal 'Success', response.message + end + + private + + def successful_charge_response + <<-RESPONSE +<?xml version="1.0" encoding="UTF-8"?> +<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <soap:Body> + <PosResponse xmlns="http://Hps.Exchange.PosGateway" rootUrl="https://posgateway.cert.secureexchange.net/Hps.Exchange.PosGateway"> + <Ver1.0> + <Header> + <LicenseId>95878</LicenseId> + <SiteId>95881</SiteId> + <DeviceId>2409000</DeviceId> + <GatewayTxnId>15927453</GatewayTxnId> + <GatewayRspCode>0</GatewayRspCode> + <GatewayRspMsg>Success</GatewayRspMsg> + <RspDT>2014-03-14T15:40:25.4686202</RspDT> + </Header> + <Transaction> + <CreditSale> + <RspCode>00</RspCode> + <RspText>APPROVAL</RspText> + <AuthCode>36987A</AuthCode> + <AVSRsltCode>0</AVSRsltCode> + <CVVRsltCode>M</CVVRsltCode> + <RefNbr>407313649105</RefNbr> + <AVSResultCodeAction>ACCEPT</AVSResultCodeAction> + <CVVResultCodeAction>ACCEPT</CVVResultCodeAction> + <CardType>Visa</CardType> + <AVSRsltText>AVS Not Requested.</AVSRsltText> + <CVVRsltText>Match.</CVVRsltText> + </CreditSale> + </Transaction> + </Ver1.0> + </PosResponse> + </soap:Body> +</soap:Envelope> + RESPONSE + end + + def failed_charge_response + <<-RESPONSE +<?xml version="1.0" encoding="UTF-8"?> +<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <soap:Body> + <PosResponse xmlns="http://Hps.Exchange.PosGateway" rootUrl="https://posgateway.cert.secureexchange.net/Hps.Exchange.PosGateway"> + <Ver1.0> + <Header> + <LicenseId>21229</LicenseId> + <SiteId>21232</SiteId> + <DeviceId>1525997</DeviceId> + <GatewayTxnId>16099851</GatewayTxnId> + <GatewayRspCode>0</GatewayRspCode> + <GatewayRspMsg>Success</GatewayRspMsg> + <RspDT>2014-03-17T13:01:55.851307</RspDT> + </Header> + <Transaction> + <CreditSale> + <RspCode>02</RspCode> + <RspText>CALL</RspText> + <AuthCode /> + <AVSRsltCode>0</AVSRsltCode> + <RefNbr>407613674802</RefNbr> + <CardType>Visa</CardType> + <AVSRsltText>AVS Not Requested.</AVSRsltText> + </CreditSale> + </Transaction> + </Ver1.0> + </PosResponse> + </soap:Body> +</soap:Envelope> + RESPONSE + end + + def failed_charge_response_decline + <<-RESPONSE +<?xml version="1.0" encoding="UTF-8"?> +<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <soap:Body> + <PosResponse xmlns="http://Hps.Exchange.PosGateway" rootUrl="https://posgateway.cert.secureexchange.net/Hps.Exchange.PosGateway"> + <Ver1.0> + <Header> + <LicenseId>21229</LicenseId> + <SiteId>21232</SiteId> + <DeviceId>1525997</DeviceId> + <GatewayTxnId>16099851</GatewayTxnId> + <GatewayRspCode>0</GatewayRspCode> + <GatewayRspMsg>Success</GatewayRspMsg> + <RspDT>2014-03-17T13:01:55.851307</RspDT> + </Header> + <Transaction> + <CreditSale> + <RspCode>05</RspCode> + <RspText>DECLINE</RspText> + <AuthCode /> + <AVSRsltCode>0</AVSRsltCode> + <RefNbr>407613674802</RefNbr> + <CardType>Visa</CardType> + <AVSRsltText>AVS Not Requested.</AVSRsltText> + </CreditSale> + </Transaction> + </Ver1.0> + </PosResponse> + </soap:Body> +</soap:Envelope> + RESPONSE + end + + def successful_authorize_response + <<-RESPONSE +<?xml version="1.0" encoding="UTF-8"?> +<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <soap:Body> + <PosResponse xmlns="http://Hps.Exchange.PosGateway" rootUrl="https://posgateway.cert.secureexchange.net/Hps.Exchange.PosGateway"> + <Ver1.0> + <Header> + <LicenseId>21229</LicenseId> + <SiteId>21232</SiteId> + <DeviceId>1525997</DeviceId> + <GatewayTxnId>16072891</GatewayTxnId> + <GatewayRspCode>0</GatewayRspCode> + <GatewayRspMsg>Success</GatewayRspMsg> + <RspDT>2014-03-17T13:05:34.5819712</RspDT> + </Header> + <Transaction> + <CreditAuth> + <RspCode>00</RspCode> + <RspText>APPROVAL</RspText> + <AuthCode>43204A</AuthCode> + <AVSRsltCode>0</AVSRsltCode> + <CVVRsltCode>M</CVVRsltCode> + <RefNbr>407613674895</RefNbr> + <AVSResultCodeAction>ACCEPT</AVSResultCodeAction> + <CVVResultCodeAction>ACCEPT</CVVResultCodeAction> + <CardType>Visa</CardType> + <AVSRsltText>AVS Not Requested.</AVSRsltText> + <CVVRsltText>Match.</CVVRsltText> + </CreditAuth> + </Transaction> + </Ver1.0> + </PosResponse> + </soap:Body> +</soap:Envelope> + RESPONSE + end + + def failed_authorize_response + <<-RESPONSE +<?xml version="1.0" encoding="UTF-8"?> +<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <soap:Body> + <PosResponse xmlns="http://Hps.Exchange.PosGateway" rootUrl="https://posgateway.cert.secureexchange.net/Hps.Exchange.PosGateway"> + <Ver1.0> + <Header> + <LicenseId>21229</LicenseId> + <SiteId>21232</SiteId> + <DeviceId>1525997</DeviceId> + <GatewayTxnId>16088893</GatewayTxnId> + <GatewayRspCode>0</GatewayRspCode> + <GatewayRspMsg>Success</GatewayRspMsg> + <RspDT>2014-03-17T13:06:45.449707</RspDT> + </Header> + <Transaction> + <CreditAuth> + <RspCode>54</RspCode> + <RspText>EXPIRED CARD</RspText> + <AuthCode /> + <AVSRsltCode>0</AVSRsltCode> + <RefNbr>407613674811</RefNbr> + <CardType>Visa</CardType> + <AVSRsltText>AVS Not Requested.</AVSRsltText> + </CreditAuth> + </Transaction> + </Ver1.0> + </PosResponse> + </soap:Body> +</soap:Envelope> + RESPONSE + end + + def failed_authorize_response_decline + <<-RESPONSE +<?xml version="1.0" encoding="UTF-8"?> +<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <soap:Body> + <PosResponse xmlns="http://Hps.Exchange.PosGateway" rootUrl="https://posgateway.cert.secureexchange.net/Hps.Exchange.PosGateway"> + <Ver1.0> + <Header> + <LicenseId>21229</LicenseId> + <SiteId>21232</SiteId> + <DeviceId>1525997</DeviceId> + <GatewayTxnId>16088893</GatewayTxnId> + <GatewayRspCode>0</GatewayRspCode> + <GatewayRspMsg>Success</GatewayRspMsg> + <RspDT>2014-03-17T13:06:45.449707</RspDT> + </Header> + <Transaction> + <CreditAuth> + <RspCode>05</RspCode> + <RspText>DECLINE</RspText> + <AuthCode /> + <AVSRsltCode>0</AVSRsltCode> + <RefNbr>407613674811</RefNbr> + <CardType>Visa</CardType> + <AVSRsltText>AVS Not Requested.</AVSRsltText> + </CreditAuth> + </Transaction> + </Ver1.0> + </PosResponse> + </soap:Body> +</soap:Envelope> + RESPONSE + end + + def successful_capture_response + <<-RESPONSE +<?xml version="1.0" encoding="utf-8"?> +<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> + <soap:Body> + <PosResponse rootUrl="https://posgateway.cert.secureexchange.net/Hps.Exchange.PosGateway" xmlns="http://Hps.Exchange.PosGateway"> + <Ver1.0> + <Header> + <LicenseId>21229</LicenseId> + <SiteId>21232</SiteId> + <DeviceId>1525997</DeviceId> + <GatewayTxnId>17213037</GatewayTxnId> + <GatewayRspCode>0</GatewayRspCode> + <GatewayRspMsg>Success</GatewayRspMsg> + <RspDT>2014-05-16T14:45:48.9906929</RspDT> + </Header> + <Transaction> + <CreditAddToBatch /> + </Transaction> + </Ver1.0> + </PosResponse> + </soap:Body> +</soap:Envelope> + RESPONSE + end + + def failed_capture_response + <<-Response +<?xml version="1.0" encoding="UTF-8"?> +<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <soap:Body> + <PosResponse xmlns="http://Hps.Exchange.PosGateway" rootUrl="https://posgateway.cert.secureexchange.net/Hps.Exchange.PosGateway"> + <Ver1.0> + <Header> + <LicenseId>21229</LicenseId> + <SiteId>21232</SiteId> + <DeviceId>1525997</DeviceId> + <GatewayTxnId>16104055</GatewayTxnId> + <GatewayRspCode>3</GatewayRspCode> + <GatewayRspMsg>Transaction rejected because the referenced original transaction is invalid. Subject '216072899'. Original transaction not found.</GatewayRspMsg> + <RspDT>2014-03-17T14:20:32.355307</RspDT> + </Header> + </Ver1.0> + </PosResponse> + </soap:Body> +</soap:Envelope> + Response + end + + def successful_refund_response + <<-RESPONSE +<?xml version="1.0" encoding="UTF-8"?> +<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <soap:Body> + <PosResponse xmlns="http://Hps.Exchange.PosGateway" rootUrl="https://posgateway.cert.secureexchange.net/Hps.Exchange.PosGateway"> + <Ver1.0> + <Header> + <LicenseId>21229</LicenseId> + <SiteId>21232</SiteId> + <DeviceId>1525997</DeviceId> + <SiteTrace /> + <GatewayTxnId>16092738</GatewayTxnId> + <GatewayRspCode>0</GatewayRspCode> + <GatewayRspMsg>Success</GatewayRspMsg> + <RspDT>2014-03-17T13:31:42.0231712</RspDT> + </Header> + <Transaction> + <CreditReturn /> + </Transaction> + </Ver1.0> + </PosResponse> + </soap:Body> +</soap:Envelope> + RESPONSE + end + + def failed_refund_response + <<-RESPONSE +<?xml version="1.0" encoding="UTF-8"?> +<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <soap:Body> + <PosResponse xmlns="http://Hps.Exchange.PosGateway" rootUrl="https://posgateway.cert.secureexchange.net/Hps.Exchange.PosGateway"> + <Ver1.0> + <Header> + <LicenseId>21229</LicenseId> + <SiteId>21232</SiteId> + <DeviceId>1525997</DeviceId> + <SiteTrace /> + <GatewayTxnId>16092766</GatewayTxnId> + <GatewayRspCode>3</GatewayRspCode> + <GatewayRspMsg>Transaction rejected because the referenced original transaction is invalid.</GatewayRspMsg> + <RspDT>2014-03-17T13:48:55.3203712</RspDT> + </Header> + </Ver1.0> + </PosResponse> + </soap:Body> +</soap:Envelope> + RESPONSE + end + + def successful_void_response + <<-RESPONSE +<?xml version="1.0" encoding="UTF-8"?> +<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <soap:Body> + <PosResponse xmlns="http://Hps.Exchange.PosGateway" rootUrl="https://posgateway.cert.secureexchange.net/Hps.Exchange.PosGateway"> + <Ver1.0> + <Header> + <LicenseId>21229</LicenseId> + <SiteId>21232</SiteId> + <DeviceId>1525997</DeviceId> + <GatewayTxnId>16092767</GatewayTxnId> + <GatewayRspCode>0</GatewayRspCode> + <GatewayRspMsg>Success</GatewayRspMsg> + <RspDT>2014-03-17T13:53:43.6863712</RspDT> + </Header> + <Transaction> + <CreditVoid /> + </Transaction> + </Ver1.0> + </PosResponse> + </soap:Body> +</soap:Envelope> + RESPONSE + end + + def failed_void_response + <<-RESPONSE +<?xml version="1.0" encoding="UTF-8"?> +<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <soap:Body> + <PosResponse xmlns="http://Hps.Exchange.PosGateway" rootUrl="https://posgateway.cert.secureexchange.net/Hps.Exchange.PosGateway"> + <Ver1.0> + <Header> + <LicenseId>21229</LicenseId> + <SiteId>21232</SiteId> + <DeviceId>1525997</DeviceId> + <GatewayTxnId>16103858</GatewayTxnId> + <GatewayRspCode>3</GatewayRspCode> + <GatewayRspMsg>Transaction rejected because the referenced original transaction is invalid. Subject '169054'. Original transaction not found.</GatewayRspMsg> + <RspDT>2014-03-17T13:55:56.8947712</RspDT> + </Header> + </Ver1.0> + </PosResponse> + </soap:Body> +</soap:Envelope> + RESPONSE + end + + def successful_swipe_purchase_response + <<-RESPONSE +<?xml version="1.0" encoding="UTF-8"?> +<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <soap:Body> + <PosResponse xmlns="http://Hps.Exchange.PosGateway" rootUrl="https://posgateway.cert.secureexchange.net/Hps.Exchange.PosGateway"> + <Ver1.0> + <Header> + <LicenseId>95878</LicenseId> + <SiteId>95881</SiteId> + <DeviceId>2409000</DeviceId> + <GatewayTxnId>17596558</GatewayTxnId> + <GatewayRspCode>0</GatewayRspCode> + <GatewayRspMsg>Success</GatewayRspMsg> + <RspDT>2014-05-26T10:27:30.4211513</RspDT> + </Header> + <Transaction> + <CreditSale> + <RspCode>00</RspCode> + <RspText>APPROVAL</RspText> + <AuthCode>037677</AuthCode> + <AVSRsltCode>0</AVSRsltCode> + <RefNbr>414614470800</RefNbr> + <AVSResultCodeAction>ACCEPT</AVSResultCodeAction> + <CardType>MC</CardType> + <AVSRsltText>AVS Not Requested.</AVSRsltText> + </CreditSale> + </Transaction> + </Ver1.0> + </PosResponse> + </soap:Body> +</soap:Envelope> + RESPONSE + end + + def failed_swipe_purchase_response + <<-RESPONSE +<?xml version="1.0" encoding="UTF-8"?> +<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <soap:Body> + <PosResponse xmlns="http://Hps.Exchange.PosGateway" rootUrl="https://posgateway.cert.secureexchange.net/Hps.Exchange.PosGateway"> + <Ver1.0> + <Header> + <LicenseId>95878</LicenseId> + <SiteId>95881</SiteId> + <DeviceId>2409000</DeviceId> + <GatewayTxnId>17602711</GatewayTxnId> + <GatewayRspCode>8</GatewayRspCode> + <GatewayRspMsg>Transaction was rejected because the track data could not be read.</GatewayRspMsg> + <RspDT>2014-05-26T10:42:44.5031513</RspDT> + </Header> + </Ver1.0> + </PosResponse> + </soap:Body> +</soap:Envelope> + RESPONSE + end + + def successful_verify_response + <<-RESPONSE +<?xml version="1.0" encoding="UTF-8"?> +<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <soap:Body> + <PosResponse xmlns="http://Hps.Exchange.PosGateway" rootUrl="https://posgateway.cert.secureexchange.net/Hps.Exchange.PosGateway"> + <Ver1.0> + <Header> + <LicenseId>95878</LicenseId> + <SiteId>95881</SiteId> + <DeviceId>2409000</DeviceId> + <SiteTrace /> + <GatewayTxnId>20153225</GatewayTxnId> + <GatewayRspCode>0</GatewayRspCode> + <GatewayRspMsg>Success</GatewayRspMsg> + <RspDT>2014-09-04T14:43:49.6015895</RspDT> + </Header> + <Transaction> + <CreditAccountVerify> + <RspCode>85</RspCode> + <RspText>CARD OK</RspText> + <AuthCode>65557A</AuthCode> + <AVSRsltCode>0</AVSRsltCode> + <CVVRsltCode>M</CVVRsltCode> + <RefNbr>424715929580</RefNbr> + <CardType>Visa</CardType> + <AVSRsltText>AVS Not Requested.</AVSRsltText> + <CVVRsltText>Match.</CVVRsltText> + </CreditAccountVerify> + </Transaction> + </Ver1.0> + </PosResponse> + </soap:Body> +</soap:Envelope> + RESPONSE + end + + def failed_verify_response + <<-RESPONSE +<?xml version="1.0" encoding="UTF-8"?> +<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <soap:Body> + <PosResponse xmlns="http://Hps.Exchange.PosGateway" rootUrl="https://posgateway.cert.secureexchange.net/Hps.Exchange.PosGateway"> + <Ver1.0> + <Header> + <LicenseId>95878</LicenseId> + <SiteId>95881</SiteId> + <DeviceId>2409000</DeviceId> + <SiteTrace /> + <GatewayTxnId>20155097</GatewayTxnId> + <GatewayRspCode>14</GatewayRspCode> + <GatewayRspMsg>Transaction rejected because the manually entered card number is invalid.</GatewayRspMsg> + <RspDT>2014-09-04T15:42:47.983634</RspDT> + </Header> + </Ver1.0> + </PosResponse> + </soap:Body> +</soap:Envelope> + RESPONSE + end + + def pre_scrub + %q{ +opening connection to posgateway.cert.secureexchange.net:443... +opened +starting SSL for posgateway.cert.secureexchange.net:443... +SSL established +<- "POST /Hps.Exchange.PosGateway/PosGatewayService.asmx?wsdl HTTP/1.1\r\nContent-Type: text/xml\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: posgateway.cert.secureexchange.net\r\nContent-Length: 1295\r\n\r\n" +<- "<?xml version=\"1.0\" encoding=\"UTF-8\"?><SOAP:Envelope xmlns:SOAP=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:hps=\"http://Hps.Exchange.PosGateway\"><SOAP:Body><hps:PosRequest><hps:Ver1.0><hps:Header><hps:SecretAPIKey>skapi_cert_MYl2AQAowiQAbLp5JesGKh7QFkcizOP2jcX9BrEMqQ</hps:SecretAPIKey></hps:Header><hps:Transaction><hps:CreditSale><hps:Block1><hps:Amt>1.00</hps:Amt><hps:AllowDup>Y</hps:AllowDup><hps:CardHolderData><hps:CardHolderFirstName>Longbob</hps:CardHolderFirstName><hps:CardHolderLastName>Longsen</hps:CardHolderLastName><hps:CardHolderAddr>456 My Street</hps:CardHolderAddr><hps:CardHolderCity>Ottawa</hps:CardHolderCity><hps:CardHolderState>ON</hps:CardHolderState><hps:CardHolderZip>K1C2N6</hps:CardHolderZip></hps:CardHolderData><hps:AdditionalTxnFields><hps:Description>Store Purchase</hps:Description><hps:InvoiceNbr>1</hps:InvoiceNbr></hps:AdditionalTxnFields><hps:CardData><hps:ManualEntry><hps:CardNbr>4000100011112224</hps:CardNbr><hps:ExpMonth>9</hps:ExpMonth><hps:ExpYear>2019</hps:ExpYear><hps:CVV2>123</hps:CVV2><hps:CardPresent>N</hps:CardPresent><hps:ReaderPresent>N</hps:ReaderPresent></hps:ManualEntry><hps:TokenRequest>N</hps:TokenRequest></hps:CardData><hps:SecureECommerce><hps:PaymentDataSource>ApplePay</hps:PaymentDataSource><hps:TypeOfPaymentData>3DSecure</hps:TypeOfPaymentData><hps:PaymentData>EHuWW9PiBkWvqE5juRwDzAUFBAk</hps:PaymentData><hps:ECommerceIndicator>5</hps:ECommerceIndicator><hps:XID>abc123</hps:XID</hps:SecureECommerce></hps:Block1></hps:CreditSale></hps:Transaction></hps:Ver1.0></hps:PosRequest></SOAP:Body></SOAP:Envelope>" +-> "HTTP/1.1 200 OK\r\n" +-> "Cache-Control: private, max-age=0\r\n" +-> "Content-Type: text/xml; charset=utf-8\r\n" +-> "Server: Microsoft-IIS/7.5\r\n" +-> "X-dynaTrace: PT=266421;PA=-1324159421;SP=Gateway Cert;PS=1926692524\r\n" +-> "dynaTrace: PT=266421;PA=-1324159421;SP=Gateway Cert;PS=1926692524\r\n" +-> "X-AspNet-Version: 4.0.30319\r\n" +-> "X-Powered-By: ASP.NET\r\n" +-> "X-Frame-Options: DENY\r\n" +-> "X-Content-Type-Options: nosniff\r\n" +-> "Date: Mon, 08 Jan 2018 16:28:18 GMT\r\n" +-> "Connection: close\r\n" +-> "Content-Length: 1067\r\n" +-> "\r\n" +reading 1067 bytes... +-> "<?xml version=\"1.0\" encoding=\"utf-8\"?><soap:Envelope xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"><soap:Body><PosResponse rootUrl=\"https://posgateway.cert.secureexchange.net/Hps.Exchange.PosGateway\" xmlns=\"http://Hps.Exchange.PosGateway\"><Ver1.0><Header><LicenseId>95878</LicenseId><SiteId>95881</SiteId><DeviceId>2409000</DeviceId><GatewayTxnId>1035967766</GatewayTxnId><GatewayRspCode>0</GatewayRspCode><GatewayRspMsg>Success</GatewayRspMsg><RspDT>2018-01-08T10:28:18.5555936</RspDT></Header><Transaction><CreditSale><RspCode>00</RspCode><RspText>APPROVAL</RspText><AuthCode>64349A</AuthCode><AVSRsltCode>0</AVSRsltCode><CVVRsltCode>M</CVVRsltCode><RefNbr>800818231451</RefNbr><AVSResultCodeActi" +-> "on>ACCEPT</AVSResultCodeAction><CVVResultCodeAction>ACCEPT</CVVResultCodeAction><CardType>Visa</CardType><AVSRsltText>AVS Not Requested.</AVSRsltText><CVVRsltText>Match.</CVVRsltText></CreditSale></Transaction></Ver1.0></PosResponse></soap:Body></soap:Envelope>" +read 1067 bytes +Conn close + } + end + + def post_scrub + %q{ +opening connection to posgateway.cert.secureexchange.net:443... +opened +starting SSL for posgateway.cert.secureexchange.net:443... +SSL established +<- "POST /Hps.Exchange.PosGateway/PosGatewayService.asmx?wsdl HTTP/1.1\r\nContent-Type: text/xml\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: posgateway.cert.secureexchange.net\r\nContent-Length: 1295\r\n\r\n" +<- "<?xml version=\"1.0\" encoding=\"UTF-8\"?><SOAP:Envelope xmlns:SOAP=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:hps=\"http://Hps.Exchange.PosGateway\"><SOAP:Body><hps:PosRequest><hps:Ver1.0><hps:Header><hps:SecretAPIKey>[FILTERED]</hps:SecretAPIKey></hps:Header><hps:Transaction><hps:CreditSale><hps:Block1><hps:Amt>1.00</hps:Amt><hps:AllowDup>Y</hps:AllowDup><hps:CardHolderData><hps:CardHolderFirstName>Longbob</hps:CardHolderFirstName><hps:CardHolderLastName>Longsen</hps:CardHolderLastName><hps:CardHolderAddr>456 My Street</hps:CardHolderAddr><hps:CardHolderCity>Ottawa</hps:CardHolderCity><hps:CardHolderState>ON</hps:CardHolderState><hps:CardHolderZip>K1C2N6</hps:CardHolderZip></hps:CardHolderData><hps:AdditionalTxnFields><hps:Description>Store Purchase</hps:Description><hps:InvoiceNbr>1</hps:InvoiceNbr></hps:AdditionalTxnFields><hps:CardData><hps:ManualEntry><hps:CardNbr>[FILTERED]</hps:CardNbr><hps:ExpMonth>9</hps:ExpMonth><hps:ExpYear>2019</hps:ExpYear><hps:CVV2>[FILTERED]</hps:CVV2><hps:CardPresent>N</hps:CardPresent><hps:ReaderPresent>N</hps:ReaderPresent></hps:ManualEntry><hps:TokenRequest>N</hps:TokenRequest></hps:CardData><hps:SecureECommerce><hps:PaymentDataSource>ApplePay</hps:PaymentDataSource><hps:TypeOfPaymentData>3DSecure</hps:TypeOfPaymentData><hps:PaymentData>[FILTERED]</hps:PaymentData><hps:ECommerceIndicator>5</hps:ECommerceIndicator><hps:XID>abc123</hps:XID</hps:SecureECommerce></hps:Block1></hps:CreditSale></hps:Transaction></hps:Ver1.0></hps:PosRequest></SOAP:Body></SOAP:Envelope>" +-> "HTTP/1.1 200 OK\r\n" +-> "Cache-Control: private, max-age=0\r\n" +-> "Content-Type: text/xml; charset=utf-8\r\n" +-> "Server: Microsoft-IIS/7.5\r\n" +-> "X-dynaTrace: PT=266421;PA=-1324159421;SP=Gateway Cert;PS=1926692524\r\n" +-> "dynaTrace: PT=266421;PA=-1324159421;SP=Gateway Cert;PS=1926692524\r\n" +-> "X-AspNet-Version: 4.0.30319\r\n" +-> "X-Powered-By: ASP.NET\r\n" +-> "X-Frame-Options: DENY\r\n" +-> "X-Content-Type-Options: nosniff\r\n" +-> "Date: Mon, 08 Jan 2018 16:28:18 GMT\r\n" +-> "Connection: close\r\n" +-> "Content-Length: 1067\r\n" +-> "\r\n" +reading 1067 bytes... +-> "<?xml version=\"1.0\" encoding=\"utf-8\"?><soap:Envelope xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"><soap:Body><PosResponse rootUrl=\"https://posgateway.cert.secureexchange.net/Hps.Exchange.PosGateway\" xmlns=\"http://Hps.Exchange.PosGateway\"><Ver1.0><Header><LicenseId>95878</LicenseId><SiteId>95881</SiteId><DeviceId>2409000</DeviceId><GatewayTxnId>1035967766</GatewayTxnId><GatewayRspCode>0</GatewayRspCode><GatewayRspMsg>Success</GatewayRspMsg><RspDT>2018-01-08T10:28:18.5555936</RspDT></Header><Transaction><CreditSale><RspCode>00</RspCode><RspText>APPROVAL</RspText><AuthCode>64349A</AuthCode><AVSRsltCode>0</AVSRsltCode><CVVRsltCode>M</CVVRsltCode><RefNbr>800818231451</RefNbr><AVSResultCodeActi" +-> "on>ACCEPT</AVSResultCodeAction><CVVResultCodeAction>ACCEPT</CVVResultCodeAction><CardType>Visa</CardType><AVSRsltText>AVS Not Requested.</AVSRsltText><CVVRsltText>Match.</CVVRsltText></CreditSale></Transaction></Ver1.0></PosResponse></soap:Body></soap:Envelope>" +read 1067 bytes +Conn close + } + end + +end diff --git a/test/unit/gateways/iats_payments_test.rb b/test/unit/gateways/iats_payments_test.rb index 078b566dd00..ac94ec90912 100644 --- a/test/unit/gateways/iats_payments_test.rb +++ b/test/unit/gateways/iats_payments_test.rb @@ -5,24 +5,591 @@ class IatsPaymentsTest < Test::Unit::TestCase def setup @gateway = IatsPaymentsGateway.new( - :login => 'X', - :password => 'Y' + :agent_code => 'login', + :password => 'password', + :region => 'uk' ) @amount = 100 @credit_card = credit_card + @check = check + @options = { + :ip => '71.65.249.145', + :order_id => generate_unique_id, + :billing_address => address, + :description => 'Store purchase' + } end def test_successful_purchase - @gateway.expects(:ssl_post).returns(successful_purchase_response) + response = stub_comms do + @gateway.purchase(@amount, @credit_card, @options) + end.check_request do |endpoint, data, headers| + assert_match(/<agentCode>login<\/agentCode>/, data) + assert_match(/<password>password<\/password>/, data) + assert_match(/<customerIPAddress>#{@options[:ip]}<\/customerIPAddress>/, data) + assert_match(/<invoiceNum>#{@options[:order_id]}<\/invoiceNum>/, data) + assert_match(/<creditCardNum>#{@credit_card.number}<\/creditCardNum>/, data) + assert_match(/<creditCardExpiry>0#{@credit_card.month}\/#{@credit_card.year.to_s[-2..-1]}<\/creditCardExpiry>/, data) + assert_match(/<cvv2>#{@credit_card.verification_value}<\/cvv2>/, data) + assert_match(/<mop>VISA<\/mop>/, data) + assert_match(/<firstName>#{@credit_card.first_name}<\/firstName>/, data) + assert_match(/<lastName>#{@credit_card.last_name}<\/lastName>/, data) + assert_match(/<address>#{@options[:billing_address][:address1]}<\/address>/, data) + assert_match(/<city>#{@options[:billing_address][:city]}<\/city>/, data) + assert_match(/<state>#{@options[:billing_address][:state]}<\/state>/, data) + assert_match(/<zipCode>#{@options[:billing_address][:zip]}<\/zipCode>/, data) + assert_match(/<total>1.00<\/total>/, data) + assert_match(/<comment>#{@options[:description]}<\/comment>/, data) + assert_equal endpoint, 'https://www.uk.iatspayments.com/NetGate/ProcessLink.asmx?op=ProcessCreditCardV1' + assert_equal headers['Content-Type'], 'application/soap+xml; charset=utf-8' + end.respond_with(successful_purchase_response) - assert response = @gateway.purchase(@amount, @credit_card) + assert response assert_instance_of Response, response assert_success response - assert_equal '508141795', response.authorization + assert_equal 'A6DE6F24', response.authorization + assert_equal 'Success', response.message + end + + def test_failed_purchase + response = stub_comms do + @gateway.purchase(@amount, @credit_card, @options) + end.respond_with(failed_purchase_response) + + assert response + assert_failure response + assert_equal 'A6DE6F24', response.authorization + assert response.message.include?('REJECT') + end + + def test_successful_check_purchase + response = stub_comms do + @gateway.purchase(@amount, @check, @options) + end.check_request do |endpoint, data, headers| + assert_match(/<ProcessACHEFTV1/, data) + assert_match(/<agentCode>login<\/agentCode>/, data) + assert_match(/<password>password<\/password>/, data) + assert_match(/<customerIPAddress>#{@options[:ip]}<\/customerIPAddress>/, data) + assert_match(/<invoiceNum>#{@options[:order_id]}<\/invoiceNum>/, data) + assert_match(/<accountNum>#{@check.routing_number}#{@check.account_number}<\/accountNum>/, data) + assert_match(/<accountType>CHECKING<\/accountType>/, data) + assert_match(/<firstName>#{@check.first_name}<\/firstName>/, data) + assert_match(/<lastName>#{@check.last_name}<\/lastName>/, data) + assert_match(/<address>#{@options[:billing_address][:address1]}<\/address>/, data) + assert_match(/<city>#{@options[:billing_address][:city]}<\/city>/, data) + assert_match(/<state>#{@options[:billing_address][:state]}<\/state>/, data) + assert_match(/<zipCode>#{@options[:billing_address][:zip]}<\/zipCode>/, data) + assert_match(/<total>1.00<\/total>/, data) + assert_match(/<comment>#{@options[:description]}<\/comment>/, data) + assert_equal endpoint, 'https://www.uk.iatspayments.com/NetGate/ProcessLink.asmx?op=ProcessACHEFTV1' + assert_equal headers['Content-Type'], 'application/soap+xml; charset=utf-8' + end.respond_with(successful_check_purchase_response) + + assert response + assert_instance_of Response, response + assert_success response + assert_equal 'A7F8B8B3|check', response.authorization + assert_equal 'Success', response.message + end + + def test_failed_check_purchase + response = stub_comms do + @gateway.purchase(@amount, @check, @options) + end.respond_with(failed_check_purchase_response) + + assert response + assert_instance_of Response, response + assert_failure response + assert_nil response.authorization + assert_equal 'REJECT: 40', response.message + end + + def test_successful_refund + response = stub_comms do + @gateway.refund(@amount, '1234', @options) + end.check_request do |endpoint, data, headers| + assert_match(/<transactionId>1234<\/transactionId>/, data) + assert_match(/<total>-1.00<\/total>/, data) + end.respond_with(successful_refund_response) + + assert response + assert_success response + assert_equal 'A6DEA654', response.authorization + end + + def test_successful_check_refund + response = stub_comms do + @gateway.refund(@amount, 'ref|check', @options) + end.check_request do |endpoint, data, headers| + assert_match(/<ProcessACHEFTRefundWithTransactionIdV1/, data) + assert_match(/<agentCode>login<\/agentCode>/, data) + assert_match(/<password>password<\/password>/, data) + assert_match(/<customerIPAddress>#{@options[:ip]}<\/customerIPAddress>/, data) + assert_match(/<total>-1.00<\/total>/, data) + assert_match(/<comment>#{@options[:description]}<\/comment>/, data) + assert_equal endpoint, 'https://www.uk.iatspayments.com/NetGate/ProcessLink.asmx?op=ProcessACHEFTRefundWithTransactionIdV1' + assert_equal headers['Content-Type'], 'application/soap+xml; charset=utf-8' + end.respond_with(successful_check_refund_response) + + assert response + assert_instance_of Response, response + assert_success response + assert_equal 'A7F8B8B3', response.authorization + assert_equal 'Success', response.message + end + + def test_failed_check_refund + response = stub_comms do + @gateway.refund(@amount, 'ref|check', @options) + end.respond_with(failed_check_refund_response) + + assert response + assert_instance_of Response, response + assert_failure response + assert_nil response.authorization + assert_equal 'REJECT: 39', response.message + end + + def test_failed_refund + response = stub_comms do + @gateway.refund(@amount, '1234', @options) + end.check_request do |endpoint, data, headers| + assert_match(/<transactionId>1234<\/transactionId>/, data) + assert_match(/<total>-1.00<\/total>/, data) + end.respond_with(failed_refund_response) + + assert response + assert_failure response + assert_equal 'A6DEA654', response.authorization + end + + def test_successful_store + response = stub_comms do + @gateway.store(@credit_card, @options) + end.check_request do |endpoint, data, headers| + assert_match(/beginDate/, data) + assert_match(/endDate/, data) + assert_match(%r{<creditCardNum>#{@credit_card.number}</creditCardNum>}, data) + assert_match(%r{<amount>0</amount>}, data) + assert_match(%r{<recurring>false</recurring>}, data) + end.respond_with(successful_store_response) + + assert response + assert_success response + assert_equal 'A12181132', response.authorization + assert_equal 'Success', response.message + end + + def test_failed_store + response = stub_comms do + @gateway.store(@credit_card, @options) + end.respond_with(failed_store_response) + + assert response + assert_failure response + assert_match(/Invalid credit card number/, response.message) + end + + def test_successful_unstore + response = stub_comms do + @gateway.unstore('TheAuthorization', @options) + end.check_request do |endpoint, data, headers| + assert_match(%r{<customerCode>TheAuthorization</customerCode>}, data) + end.respond_with(successful_unstore_response) + + assert response + assert_success response + assert_equal 'Success', response.message + end + + def test_deprecated_options + assert_deprecation_warning("The 'login' option is deprecated in favor of 'agent_code' and will be removed in a future version.") do + @gateway = IatsPaymentsGateway.new( + :login => 'login', + :password => 'password' + ) + end + + response = stub_comms do + @gateway.purchase(@amount, @credit_card, @options) + end.check_request do |endpoint, data, headers| + assert_match(/<agentCode>login<\/agentCode>/, data) + assert_match(/<password>password<\/password>/, data) + assert_equal endpoint, 'https://www.iatspayments.com/NetGate/ProcessLink.asmx?op=ProcessCreditCardV1' + end.respond_with(successful_purchase_response) + + assert_success response + end + + def test_region_urls + @gateway = IatsPaymentsGateway.new( + :agent_code => 'code', + :password => 'password', + :region => 'na' # North america + ) + + response = stub_comms do + @gateway.purchase(@amount, @credit_card, @options) + end.check_request do |endpoint, data, headers| + assert_equal endpoint, 'https://www.iatspayments.com/NetGate/ProcessLink.asmx?op=ProcessCreditCardV1' + end.respond_with(successful_purchase_response) + + assert_success response + end + + def test_supported_countries + @gateway.supported_countries.each do |country_code| + assert ActiveMerchant::Country.find(country_code), "Supported country code #{country_code} is invalid. Please use a value explicitly listed in ActiveMerchant::Country class." + end + end + + def test_failed_connection + response = stub_comms do + @gateway.purchase(@amount, @credit_card, @options) + end.respond_with(failed_connection_response) + + assert response + assert_failure response + assert_match(/Server Error/, response.message) + end + + def test_scrub + assert_equal @gateway.scrub(pre_scrub), post_scrub + end + + def test_supports_scrubbing? + assert @gateway.supports_scrubbing? end private + def successful_purchase_response - '1,1,1,This transaction has been approved.,d1GENk,Y,508141795,32968c18334f16525227,Store purchase,1.00,CC,auth_capture,,Longbob,Longsen,,,,,,,,,,,,,,,,,,,,,,,269862C030129C1173727CC10B1935ED,P,2,,,,,,,,,,,,,,,,,,,,,,,,,,,,' + <<-XML +<?xml version="1.0" encoding="utf-8"?> +<soap12:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap12="http://www.w3.org/2003/05/soap-envelope"> + <soap12:Body> + <ProcessCreditCardV1Response xmlns="https://www.iatspayments.com/NetGate/"> + <ProcessCreditCardV1Result> + <IATSRESPONSE> + <STATUS>Success</STATUS> + <ERRORS /> + <PROCESSRESULT> + <AUTHORIZATIONRESULT> OK</AUTHORIZATIONRESULT> + <CUSTOMERCODE /> + <SETTLEMENTBATCHDATE> 04/22/2014</SETTLEMENTBATCHDATE> + <SETTLEMENTDATE> 04/23/2014</SETTLEMENTDATE> + <TRANSACTIONID>A6DE6F24</TRANSACTIONID> + </PROCESSRESULT> + </IATSRESPONSE> + </ProcessCreditCardV1Result> + </ProcessCreditCardV1Response> + </soap12:Body> +</soap12:Envelope> + XML + end + + def failed_purchase_response + <<-XML +<?xml version="1.0" encoding="utf-8"?> +<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> + <soap:Body> + <ProcessCreditCardV1Response xmlns="https://www.iatspayments.com/NetGate/"> + <ProcessCreditCardV1Result> + <IATSRESPONSE xmlns=""> + <STATUS>Success</STATUS> + <ERRORS /> + <PROCESSRESULT> + <AUTHORIZATIONRESULT> REJECT: 15</AUTHORIZATIONRESULT> + <CUSTOMERCODE /> + <SETTLEMENTBATCHDATE> 04/22/2014</SETTLEMENTBATCHDATE> + <SETTLEMENTDATE> 04/23/2014</SETTLEMENTDATE> + <TRANSACTIONID>A6DE6F24</TRANSACTIONID> + </PROCESSRESULT> + </IATSRESPONSE> + </ProcessCreditCardV1Result> + </ProcessCreditCardV1Response> + </soap:Body> +</soap:Envelope> + XML + end + + def successful_check_purchase_response + <<-XML +<?xml version="1.0" encoding="utf-8"?> +<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> + <soap:Body> + <ProcessACHEFTV1Response xmlns="https://www.iatspayments.com/NetGate/"> + <ProcessACHEFTV1Result> + <IATSRESPONSE xmlns=""> + <STATUS>Success</STATUS> + <ERRORS /> + <PROCESSRESULT> + <AUTHORIZATIONRESULT> OK: 555555</AUTHORIZATIONRESULT> + <CUSTOMERCODE /> + <TRANSACTIONID>A7F8B8B3</TRANSACTIONID> + </PROCESSRESULT> + </IATSRESPONSE> + </ProcessACHEFTV1Result> + </ProcessACHEFTV1Response> + </soap:Body> +</soap:Envelope> + XML + end + + def failed_check_purchase_response + <<-XML +<?xml version="1.0" encoding="utf-8"?> +<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> + <soap:Body> + <ProcessACHEFTV1Response xmlns="https://www.iatspayments.com/NetGate/"> + <ProcessACHEFTV1Result> + <IATSRESPONSE xmlns=""> + <STATUS>Success</STATUS> + <ERRORS /> + <PROCESSRESULT> + <AUTHORIZATIONRESULT> REJECT: 40</AUTHORIZATIONRESULT> + <CUSTOMERCODE /> + <TRANSACTIONID /> + </PROCESSRESULT> + </IATSRESPONSE> + </ProcessACHEFTV1Result> + </ProcessACHEFTV1Response> + </soap:Body> +</soap:Envelope> + XML + end + + def successful_refund_response + <<-XML +<?xml version="1.0" encoding="utf-8"?> +<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> + <soap:Body> + <ProcessCreditCardV1Response xmlns="https://www.iatspayments.com/NetGate/"> + <ProcessCreditCardV1Result> + <IATSRESPONSE xmlns=""> + <STATUS>Success</STATUS> + <ERRORS /> + <PROCESSRESULT> + <AUTHORIZATIONRESULT> OK: 678594: </AUTHORIZATIONRESULT> + <CUSTOMERCODE /> + <SETTLEMENTBATCHDATE> 04/22/2014 </SETTLEMENTBATCHDATE> + <SETTLEMENTDATE> 04/23/2014 </SETTLEMENTDATE> + <TRANSACTIONID>A6DEA654</TRANSACTIONID> + </PROCESSRESULT> + </IATSRESPONSE> + </ProcessCreditCardV1Result> + </ProcessCreditCardV1Response> + </soap:Body> +</soap:Envelope> + XML + end + + def successful_check_refund_response + <<-XML +<?xml version="1.0" encoding="utf-8"?> +<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> + <soap:Body> + <ProcessACHEFTRefundWithTransactionIdV1Response xmlns="https://www.iatspayments.com/NetGate/"> + <ProcessACHEFTRefundWithTransactionIdV1Result> + <IATSRESPONSE xmlns=""> + <STATUS>Success</STATUS> + <ERRORS /> + <PROCESSRESULT> + <AUTHORIZATIONRESULT> OK: 555555</AUTHORIZATIONRESULT> + <CUSTOMERCODE /> + <TRANSACTIONID>A7F8B8B3</TRANSACTIONID> + </PROCESSRESULT> + </IATSRESPONSE> + </ProcessACHEFTRefundWithTransactionIdV1Result> + </ProcessACHEFTRefundWithTransactionIdV1Response> + </soap:Body> +</soap:Envelope> + XML + end + + def failed_check_refund_response + <<-XML +<?xml version="1.0" encoding="utf-8"?> +<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> + <soap:Body> + <ProcessACHEFTRefundWithTransactionIdV1Response xmlns="https://www.iatspayments.com/NetGate/"> + <ProcessACHEFTRefundWithTransactionIdV1Result> + <IATSRESPONSE xmlns=""> + <STATUS>Success</STATUS> + <ERRORS /> + <PROCESSRESULT> + <AUTHORIZATIONRESULT> REJECT: 39</AUTHORIZATIONRESULT> + <CUSTOMERCODE /> + <SETTLEMENTBATCHDATE> 06/11/2015</SETTLEMENTBATCHDATE> + <SETTLEMENTDATE> 06/12/2015</SETTLEMENTDATE> + <TRANSACTIONID></TRANSACTIONID> + </PROCESSRESULT> + </IATSRESPONSE> + </ProcessACHEFTRefundWithTransactionIdV1Result> + </ProcessACHEFTRefundWithTransactionIdV1Response> + </soap:Body> +</soap:Envelope> + XML + end + + def failed_refund_response + <<-XML +<?xml version="1.0" encoding="utf-8"?> +<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> + <soap:Body> + <ProcessCreditCardV1Response xmlns="https://www.iatspayments.com/NetGate/"> + <ProcessCreditCardV1Result> + <IATSRESPONSE xmlns=""> + <STATUS>Success</STATUS> + <ERRORS /> + <PROCESSRESULT> + <AUTHORIZATIONRESULT> REJECT: 15 </AUTHORIZATIONRESULT> + <CUSTOMERCODE /> + <SETTLEMENTBATCHDATE> 04/22/2014 </SETTLEMENTBATCHDATE> + <SETTLEMENTDATE> 04/23/2014 </SETTLEMENTDATE> + <TRANSACTIONID>A6DEA654</TRANSACTIONID> + </PROCESSRESULT> + </IATSRESPONSE> + </ProcessCreditCardV1Result> + </ProcessCreditCardV1Response> + </soap:Body> +</soap:Envelope> + XML + end + + def successful_store_response + <<-XML + <?xml version="1.0" encoding="UTF-8"?> + <soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <soap:Body> + <CreateCreditCardCustomerCodeV1Response xmlns="https://www.iatspayments.com/NetGate/"> + <CreateCreditCardCustomerCodeV1Result> + <IATSRESPONSE xmlns=""> + <STATUS>Success</STATUS> + <ERRORS /> + <PROCESSRESULT> + <AUTHORIZATIONRESULT>OK</AUTHORIZATIONRESULT> + <CUSTOMERCODE>A12181132</CUSTOMERCODE> + </PROCESSRESULT> + </IATSRESPONSE> + </CreateCreditCardCustomerCodeV1Result> + </CreateCreditCardCustomerCodeV1Response> + </soap:Body> + </soap:Envelope> + XML + end + + def failed_store_response + <<-XML + <?xml version="1.0" encoding="UTF-8"?> + <soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <soap:Body> + <CreateCreditCardCustomerCodeV1Response xmlns="https://www.iatspayments.com/NetGate/"> + <CreateCreditCardCustomerCodeV1Result> + <IATSRESPONSE xmlns=""> + <STATUS>Success</STATUS> + <ERRORS /> + <PROCESSRESULT> + <AUTHORIZATIONRESULT>0Error:Invalid credit card number</AUTHORIZATIONRESULT> + <CUSTOMERCODE /> + </PROCESSRESULT> + </IATSRESPONSE> + </CreateCreditCardCustomerCodeV1Result> + </CreateCreditCardCustomerCodeV1Response> + </soap:Body> + </soap:Envelope> + XML + end + + def successful_unstore_response + <<-XML + <?xml version="1.0" encoding="UTF-8"?> + <soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <soap:Body> + <DeleteCustomerCodeV1Response xmlns="https://www.iatspayments.com/NetGate/"> + <DeleteCustomerCodeV1Result> + <IATSRESPONSE xmlns=""> + <STATUS>Success</STATUS> + <ERRORS /> + <PROCESSRESULT> + <AUTHORIZATIONRESULT>OK</AUTHORIZATIONRESULT> + <CUSTOMERCODE>"A12181132" is deleted</CUSTOMERCODE> + </PROCESSRESULT> + </IATSRESPONSE> + </DeleteCustomerCodeV1Result> + </DeleteCustomerCodeV1Response> + </soap:Body> + </soap:Envelope> + XML + end + + def failed_connection_response + <<-XML + <?xml version="1.0" encoding="UTF-8"?> + <soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <soap:Body> + <ProcessCreditCardV1Response xmlns="https://www.iatspayments.com/NetGate/"> + <ProcessCreditCardV1Result> + <IATSRESPONSE xmlns=""> + <STATUS>Failure</STATUS> + <ERRORS>Server Error</ERRORS> + <PROCESSRESULT> + </PROCESSRESULT> + </IATSRESPONSE> + </ProcessCreditCardV1Result> + </ProcessCreditCardV1Response> + </soap:Body> + </soap:Envelope> + XML + end + + def pre_scrub + <<-XML + opening connection to www.iatspayments.com:443... + opened + starting SSL for www.iatspayments.com:443... + SSL established + <- "POST /NetGate/ProcessLink.asmx?op=ProcessCreditCardV1 HTTP/1.1\r\nContent-Type: application/soap+xml; charset=utf-8\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: www.iatspayments.com\r\nContent-Length: 779\r\n\r\n" + <- "<?xml version=\"1.0\" encoding=\"utf-8\"?><soap12:Envelope xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:soap12=\"http://www.w3.org/2003/05/soap-envelope\"><soap12:Body><ProcessCreditCardV1 xmlns=\"https://www.iatspayments.com/NetGate/\"><agentCode>TEST88</agentCode><password>TEST88</password><invoiceNum>63b5dd7098e8e3a9ff9a6f0992fdb6d5</invoiceNum><total>1.00</total><firstName>Longbob</firstName><lastName>Longsen</lastName><creditCardNum>4222222222222220</creditCardNum><creditCardExpiry>09/17</creditCardExpiry><cvv2>123</cvv2><mop>VISA</mop><address>456 My Street</address><city>Ottawa</city><state>ON</state><zipCode>K1C2N6</zipCode><comment>Store purchase</comment></ProcessCreditCardV1></soap12:Body></soap12:Envelope>" + -> "HTTP/1.1 200 OK\r\n" + -> "Cache-Control: private, max-age=0\r\n" + -> "Content-Type: application/soap+xml; charset=utf-8\r\n" + -> "X-AspNet-Version: 4.0.30319\r\n" + -> "X-Powered-By: ASP.NET\r\n" + -> "Date: Thu, 29 Sep 2016 05:41:04 GMT\r\n" + -> "Content-Length: 719\r\n" + -> "Connection: close\r\n" + -> "Via: 1.1 sjc1-10\r\n" + -> "\r\n" + reading 719 bytes... + -> "<?xml version=\"1.0\" encoding=\"utf-8\"?><soap:Envelope xmlns:soap=\"http://www.w3.org/2003/05/soap-envelope\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"><soap:Body><ProcessCreditCardV1Response xmlns=\"https://www.iatspayments.com/NetGate/\"><ProcessCreditCardV1Result><IATSRESPONSE xmlns=\"\"><STATUS>Success</STATUS><ERRORS /><PROCESSRESULT><AUTHORIZATIONRESULT> OK: 678594:\n</AUTHORIZATIONRESULT><CUSTOMERCODE /><SETTLEMENTBATCHDATE> 09/28/2016\n</SETTLEMENTBATCHDATE><SETTLEMENTDATE> 09/29/2016\n</SETTLEMENTDATE><TRANSACTIONID>A92E3B72\n</TRANSACTIONID></PROCESSRESULT></IATSRESPONSE></ProcessCreditCardV1Result></ProcessCreditCardV1Response></soap:Body></soap:Envelope>" + read 719 bytes + Conn close + XML + end + + def post_scrub + <<-XML + opening connection to www.iatspayments.com:443... + opened + starting SSL for www.iatspayments.com:443... + SSL established + <- "POST /NetGate/ProcessLink.asmx?op=ProcessCreditCardV1 HTTP/1.1\r\nContent-Type: application/soap+xml; charset=utf-8\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: www.iatspayments.com\r\nContent-Length: 779\r\n\r\n" + <- "<?xml version=\"1.0\" encoding=\"utf-8\"?><soap12:Envelope xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:soap12=\"http://www.w3.org/2003/05/soap-envelope\"><soap12:Body><ProcessCreditCardV1 xmlns=\"https://www.iatspayments.com/NetGate/\"><agentCode>[FILTERED]</agentCode><password>[FILTERED]</password><invoiceNum>63b5dd7098e8e3a9ff9a6f0992fdb6d5</invoiceNum><total>1.00</total><firstName>Longbob</firstName><lastName>Longsen</lastName><creditCardNum>[FILTERED]</creditCardNum><creditCardExpiry>09/17</creditCardExpiry><cvv2>[FILTERED]</cvv2><mop>VISA</mop><address>456 My Street</address><city>Ottawa</city><state>ON</state><zipCode>K1C2N6</zipCode><comment>Store purchase</comment></ProcessCreditCardV1></soap12:Body></soap12:Envelope>" + -> "HTTP/1.1 200 OK\r\n" + -> "Cache-Control: private, max-age=0\r\n" + -> "Content-Type: application/soap+xml; charset=utf-8\r\n" + -> "X-AspNet-Version: 4.0.30319\r\n" + -> "X-Powered-By: ASP.NET\r\n" + -> "Date: Thu, 29 Sep 2016 05:41:04 GMT\r\n" + -> "Content-Length: 719\r\n" + -> "Connection: close\r\n" + -> "Via: 1.1 sjc1-10\r\n" + -> "\r\n" + reading 719 bytes... + -> "<?xml version=\"1.0\" encoding=\"utf-8\"?><soap:Envelope xmlns:soap=\"http://www.w3.org/2003/05/soap-envelope\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"><soap:Body><ProcessCreditCardV1Response xmlns=\"https://www.iatspayments.com/NetGate/\"><ProcessCreditCardV1Result><IATSRESPONSE xmlns=\"\"><STATUS>Success</STATUS><ERRORS /><PROCESSRESULT><AUTHORIZATIONRESULT> OK: 678594:\n</AUTHORIZATIONRESULT><CUSTOMERCODE /><SETTLEMENTBATCHDATE> 09/28/2016\n</SETTLEMENTBATCHDATE><SETTLEMENTDATE> 09/29/2016\n</SETTLEMENTDATE><TRANSACTIONID>A92E3B72\n</TRANSACTIONID></PROCESSRESULT></IATSRESPONSE></ProcessCreditCardV1Result></ProcessCreditCardV1Response></soap:Body></soap:Envelope>" + read 719 bytes + Conn close + XML end end diff --git a/test/unit/gateways/ideal_rabobank_test.rb b/test/unit/gateways/ideal_rabobank_test.rb deleted file mode 100644 index fe777decea0..00000000000 --- a/test/unit/gateways/ideal_rabobank_test.rb +++ /dev/null @@ -1,320 +0,0 @@ -# coding: UTF-8 - -require 'test_helper' - -class IdealRabobankTest < Test::Unit::TestCase - - DEFAULT_IDEAL_OPTIONS = { - :login => '123456789', - :pem => 'PEM', - :password => 'PASSWORD' - } - - def setup - @gateway = IdealRabobankGateway.new(DEFAULT_IDEAL_OPTIONS) - - # stub security methods, so we can run tests without PEM files - @stubbed_time_stamp = '2007-07-02T10:03:18.000Z' - @gateway.stubs(:create_fingerprint).returns('TOKEN') - @gateway.stubs(:sign_message).returns('TOKEN_CODE') - @gateway.stubs(:create_time_stamp).returns(@stubbed_time_stamp) - - @transaction_options = { - :issuer_id => '0001', - :expiration_period => 'PT10M', - :return_url => 'http://www.return.url', - :order_id => '1234567890123456', - :currency => 'EUR', - :description => 'A description', - :entrance_code => '1234' - } - end - - def test_build_transaction_request - request = @gateway.send(:build_transaction_request, 100, @transaction_options) - - xml_request = REXML::Document.new(request) - - assert_ideal_message xml_request, 'AcquirerTrxReq' - assert_merchant_elements xml_request - - assert_equal @transaction_options[:issuer_id], xml_request.root.elements['Issuer/issuerID'].text, 'Should map to an issuerID element.' - assert_equal @transaction_options[:return_url], xml_request.root.elements['Merchant/merchantReturnURL'].text, 'Should map to a merchantReturnURL element.' - assert_equal @transaction_options[:order_id], xml_request.root.elements['Transaction/purchaseID'].text, 'Should map to a purchaseID element.' - assert_equal '100', xml_request.root.elements['Transaction/amount'].text, 'Should map to an amount element.' - assert_equal @transaction_options[:currency], xml_request.root.elements['Transaction/currency'].text, 'Should map to a currency element.' - assert_equal @transaction_options[:expiration_period], xml_request.root.elements['Transaction/expirationPeriod'].text, 'Should map to an expirationPeriod element.' - assert_equal 'nl', xml_request.root.elements['Transaction/language'].text, 'Should map to a language element.' - assert_equal @transaction_options[:description], xml_request.root.elements['Transaction/description'].text, 'Should map to a description element.' - assert_equal @transaction_options[:entrance_code], xml_request.root.elements['Transaction/entranceCode'].text, 'Should map to an entranceCode element.' - end - - def test_build_status_request - request = @gateway.send(:build_status_request, :transaction_id => '1234') - xml_request = REXML::Document.new(request) - - assert_ideal_message xml_request, 'AcquirerStatusReq' - assert_merchant_elements xml_request - - assert_equal '1234', xml_request.root.elements['Transaction/transactionID'].text, 'Should map to a transactionID element.' - end - - def test_build_directory_request - request = @gateway.send(:build_directory_request) - xml_request = REXML::Document.new(request) - - assert_ideal_message xml_request, 'DirectoryReq' - assert_merchant_elements xml_request - end - - def assert_ideal_message xml_request, message_name - assert_equal '1.0', xml_request.version, "Should be version 1.0 of the xml specification" - assert_equal 'UTF-8', xml_request.encoding, "Should be UTF-8 encoding" - assert_equal 'http://www.idealdesk.com/Message', xml_request.root.namespace, "Should have a valid namespace" - assert_equal message_name, xml_request.root.name, "Root should match messagename" - assert_equal '1.1.0', xml_request.root.attribute('version', nil).value, "Should have a ideal version number" - assert_equal @stubbed_time_stamp, xml_request.root.elements['createDateTimeStamp'].text, 'Should have a time stamp.' - end - - def assert_merchant_elements xml_request - assert_equal DEFAULT_IDEAL_OPTIONS[:login], xml_request.root.elements['Merchant/merchantID'].text, 'Should map to an merchantID element.' - assert_equal '0', xml_request.root.elements['Merchant/subID'].text, 'Should map to an subID element.' - assert_equal 'SHA1_RSA', xml_request.root.elements['Merchant/authentication'].text, 'Should map to an authentication element.' - assert_equal 'TOKEN', xml_request.root.elements['Merchant/token'].text, 'Should map to a token element.' - assert_equal 'TOKEN_CODE', xml_request.root.elements['Merchant/tokenCode'].text, 'Should map to a tokenCode element.' - end - - # test incoming messages - - def test_setup_purchase_successful - @gateway.expects(:ssl_post).returns(successful_transaction_response) - response = @gateway.setup_purchase(100, @transaction_options) - assert_success response - transaction = response.transaction - assert_equal '0050000002797923', transaction['transactionID'], 'Should map to transaction_id' - assert_equal '9459897270157938', transaction['purchaseID'], 'Should map to purchase_id' - assert_equal '0050', response.params['AcquirerTrxRes']['Acquirer']['acquirerID'], 'Should map to acquirer_id' - assert_equal 'https://issuer.url/action?trxid=0050000002797923', response.service_url, "Response should have an issuer url" - end - - def test_error_response - @gateway.expects(:ssl_post).returns(failed_transaction_response) - response = @gateway.setup_purchase(100, @transaction_options) - assert_failure response - assert_equal 'ErrorRes', response.message, 'Should return error response' - error = response.error - assert_equal "BR1210", error['errorCode'], "Should return an error code" - assert_equal "Field generating error: Parameter \'25.99\' is not a natural(or \'-\') format", error['errorDetail'], "Should return an error detail" - assert_equal "Value contains non-permitted character", error['errorMessage'], "Should return an error message" - assert_equal "Betalen met iDEAL is nu niet mogelijk. Probeer het later nogmaals of betaal op een andere manier.", error['consumerMessage'], "Should return consumer message" - end - - def test_capture - @gateway.expects(:ssl_post).returns(successful_status_response) - @gateway.expects(:verify_message).returns(true) - - response = @gateway.capture('0050000002807474') - assert_success response - transaction = response.transaction - assert_equal '0050000002807474', transaction['transactionID'], 'Should map to transaction_id' - assert_equal 'C M Bröcker-Meijer en M Bröcker', transaction['consumerName'] - assert_equal 'P001234567', transaction['consumerAccountNumber'] - assert_equal 'DEN HAAG', transaction['consumerCity'] - assert_equal 'Success', transaction['status'], 'Should map to status' - end - - # make sure the gateway does not crash if issuer 'forgets' consumerAcountNumber - def test_capture_with_missing_account_number - @gateway.expects(:ssl_post).returns(successful_status_response_with_missing_fields) - @gateway.expects(:verify_message).returns(true) - - response = @gateway.capture('0050000002807474') - assert_success response - transaction = response.transaction - assert_equal '0050000002807474', transaction['transactionID'], 'Should map to transaction_id' - assert_nil transaction['consumerAccountNumber'] - assert_equal 'Success', transaction['status'], 'Should map to status' - end - - def test_payment_cancelled - @gateway.expects(:ssl_post).returns(cancelled_status_response) - @gateway.expects(:verify_message).returns(true) - - response = @gateway.capture('0050000002807474') - assert_failure response - transaction = response.transaction - assert_equal '0050000002807474', transaction['transactionID'], 'Should map to transaction_id' - assert_equal 'Cancelled', transaction['status'], 'Should map to status' - end - - def test_issuers_multiple - @gateway.expects(:ssl_post).returns(directory_request_response) - response = @gateway.issuers - assert_success response - list = response.issuer_list - assert_equal 4, list.size, "Should return multiple issuers" - assert_equal '0031', list[0]['issuerID'], "Should return an issuerID" - end - - def test_issuers_one_issuer - @gateway.expects(:ssl_post).returns(directory_request_response_one_issuer) - response = @gateway.issuers - assert_success response - list = response.issuer_list - assert_equal 1, list.size, "Should return one issuer" - assert_equal '0031', list[0]['issuerID'], "Should return an issuerID" - end - - def successful_transaction_response - <<-RESPONSE -<?xml version='1.0' encoding='UTF-8'?> -<AcquirerTrxRes version='1.1.0' xmlns='http://www.idealdesk.com/Message'> - <createDateTimeStamp>2007-07-02T10:03:18.000Z</createDateTimeStamp> - <Acquirer> - <acquirerID>0050</acquirerID> - </Acquirer> - <Issuer> - <issuerAuthenticationURL>https://issuer.url/action?trxid=0050000002797923</issuerAuthenticationURL> - </Issuer> - <Transaction> - <transactionID>0050000002797923</transactionID> - <purchaseID>9459897270157938</purchaseID> - </Transaction> -</AcquirerTrxRes> - RESPONSE - end - - def failed_transaction_response - <<-RESPONSE -<?xml version='1.0' encoding='UTF-8'?> - <ErrorRes version='1.1.0' xmlns='http://www.idealdesk.com/Message'> - <createDateTimeStamp>2007-07-02T10:03:18.000Z</createDateTimeStamp> - <Error> - <errorCode>BR1210</errorCode> - <errorMessage>Value contains non-permitted character</errorMessage> - <errorDetail>Field generating error: Parameter &apos;25.99&apos; is not a natural(or &apos;-&apos;) format</errorDetail> - <consumerMessage>Betalen met iDEAL is nu niet mogelijk. Probeer het later nogmaals of betaal op een andere manier.</consumerMessage> - </Error> -</ErrorRes> - RESPONSE - end - - def successful_status_response - <<-RESPONSE -?xml version='1.0' encoding='UTF-8'?> -<AcquirerStatusRes version='1.1.0' xmlns='http://www.idealdesk.com/Message'> - <createDateTimeStamp>2007-07-02T10:03:18.000Z</createDateTimeStamp> -<Acquirer> - <acquirerID>0050</acquirerID> -</Acquirer> -<Transaction> - <transactionID>0050000002807474</transactionID> - <status>Success</status> - <consumerName>C M Bröcker-Meijer en M Bröcker</consumerName> - <consumerAccountNumber>P001234567</consumerAccountNumber> - <consumerCity>DEN HAAG</consumerCity> -</Transaction> -<Signature> - <signatureValue>LONGSTRING</signatureValue> - <fingerprint>FINGERPRINT</fingerprint> -</Signature> -</AcquirerStatusRes> - RESPONSE - end - - def successful_status_response_with_missing_fields - <<-RESPONSE -?xml version='1.0' encoding='UTF-8'?> -<AcquirerStatusRes version='1.1.0' xmlns='http://www.idealdesk.com/Message'> - <createDateTimeStamp>2007-07-02T10:03:18.000Z</createDateTimeStamp> -<Acquirer> - <acquirerID>0050</acquirerID> -</Acquirer> -<Transaction> - <transactionID>0050000002807474</transactionID> - <status>Success</status> -</Transaction> -<Signature> - <signatureValue>LONGSTRING</signatureValue> - <fingerprint>FINGERPRINT</fingerprint> -</Signature> -</AcquirerStatusRes> - RESPONSE - end - - def cancelled_status_response - <<-RESPONSE -<?xml version='1.0' encoding='UTF-8'?> -<AcquirerStatusRes version='1.1.0' xmlns='http://www.idealdesk.com/Message'> - <createDateTimeStamp>2007-07-02T10:03:18.000Z</createDateTimeStamp> -<Acquirer> - <acquirerID>0050</acquirerID> -</Acquirer> -<Transaction> - <transactionID>0050000002807474</transactionID> - <status>Cancelled</status> -</Transaction> -<Signature> - <signatureValue>LONGSTRING</signatureValue> - <fingerprint>FINGERPRINT</fingerprint> -</Signature> -</AcquirerStatusRes> - RESPONSE - end - - def directory_request_response - <<-RESPONSE -<?xml version='1.0' encoding='UTF-8'?> -<DirectoryRes version='1.1.0' xmlns='http://www.idealdesk.com/Message'> -<createDateTimeStamp>2007-07-02T10:03:18.000Z</createDateTimeStamp> -<Acquirer> - <acquirerID>0050</acquirerID> -</Acquirer> -<Directory> - <directoryDateTimeStamp>2007-07-02T10:03:18.000Z</directoryDateTimeStamp> - <Issuer> - <issuerID>0031</issuerID> - <issuerName>ABN Amro Bank</issuerName> - <issuerList>Short</issuerList> - </Issuer> - <Issuer> - <issuerID>0721</issuerID> - <issuerName>Postbank</issuerName> - <issuerList>Short</issuerList> - </Issuer> - <Issuer> - <issuerID>0021</issuerID> - <issuerName>Rabobank</issuerName> - <issuerList>Short</issuerList> - </Issuer> - <Issuer> - <issuerID>0751</issuerID> - <issuerName>SNS Bank</issuerName> - <issuerList>Short</issuerList> - </Issuer> -</Directory> -</DirectoryRes> - RESPONSE - end - - def directory_request_response_one_issuer - <<-RESPONSE -<?xml version='1.0' encoding='UTF-8'?> -<DirectoryRes version='1.1.0' xmlns='http://www.idealdesk.com/Message'> -<createDateTimeStamp>2007-07-02T10:03:18.000Z</createDateTimeStamp> -<Acquirer> - <acquirerID>0050</acquirerID> -</Acquirer> -<Directory> - <directoryDateTimeStamp>2007-07-02T10:03:18.000Z</directoryDateTimeStamp> - <Issuer> - <issuerID>0031</issuerID> - <issuerName>ABN Amro Bank</issuerName> - <issuerList>Short</issuerList> - </Issuer> -</Directory> -</DirectoryRes> - RESPONSE - end - -end diff --git a/test/unit/gateways/in_context_paypal_express_test.rb b/test/unit/gateways/in_context_paypal_express_test.rb new file mode 100644 index 00000000000..4e92dc3032a --- /dev/null +++ b/test/unit/gateways/in_context_paypal_express_test.rb @@ -0,0 +1,42 @@ +require 'test_helper' + +class InContextPaypalExpressTest < Test::Unit::TestCase + TEST_REDIRECT_URL = 'https://www.sandbox.paypal.com/checkoutnow?token=1234567890' + LIVE_REDIRECT_URL = 'https://www.paypal.com/checkoutnow?token=1234567890' + TEST_REDIRECT_URL_WITHOUT_REVIEW = 'https://www.sandbox.paypal.com/checkoutnow?token=1234567890&useraction=commit' + LIVE_REDIRECT_URL_WITHOUT_REVIEW = 'https://www.paypal.com/checkoutnow?token=1234567890&useraction=commit' + + def setup + @gateway = InContextPaypalExpressGateway.new( + :login => 'cody', + :password => 'test', + :pem => 'PEM' + ) + + Base.mode = :test + end + + def teardown + Base.mode = :test + end + + def test_live_redirect_url + Base.mode = :production + assert_equal LIVE_REDIRECT_URL, @gateway.redirect_url_for('1234567890') + end + + def test_test_redirect_url + assert_equal :test, Base.mode + assert_equal TEST_REDIRECT_URL, @gateway.redirect_url_for('1234567890') + end + + def test_live_redirect_url_without_review + Base.mode = :production + assert_equal LIVE_REDIRECT_URL_WITHOUT_REVIEW, @gateway.redirect_url_for('1234567890', review: false) + end + + def test_test_redirect_url_without_review + assert_equal :test, Base.mode + assert_equal TEST_REDIRECT_URL_WITHOUT_REVIEW, @gateway.redirect_url_for('1234567890', review: false) + end +end diff --git a/test/unit/gateways/inspire_test.rb b/test/unit/gateways/inspire_test.rb index 116b870f9b1..b92b13ef34c 100644 --- a/test/unit/gateways/inspire_test.rb +++ b/test/unit/gateways/inspire_test.rb @@ -1,22 +1,21 @@ require 'test_helper' -class InspiregatewayTest < Test::Unit::TestCase - +class InspireTest < Test::Unit::TestCase + include CommStub + def setup @gateway = InspireGateway.new( :login => 'LOGIN', :password => 'PASSWORD' ) - @credit_card = credit_card('4242424242424242', - :brand => 'visa' - ) + @credit_card = credit_card('4242424242424242') @amount = 100 @options = { :billing_address => address } end - + def test_successful_purchase @gateway.expects(:ssl_post).returns(successful_purchase_response) - + assert response = @gateway.authorize(@amount, @credit_card, @options) assert_instance_of Response, response assert_success response @@ -25,23 +24,49 @@ def test_successful_purchase def test_failed_purchase @gateway.expects(:ssl_post).returns(failed_purchase_response) - + assert response = @gateway.authorize(@amount, @credit_card, @options) assert_instance_of Response, response assert_failure response end - + + def test_successful_refund + response = stub_comms do + @gateway.refund(nil, 'identifier') + end.check_request do |_, data, _| + assert_match %r{identifier}, data + assert_no_match %r{amount}, data + end.respond_with(successful_refund_response) + assert_success response + end + + def test_partial_refund + response = stub_comms do + @gateway.refund(100, 'identifier') + end.check_request do |_, data, _| + assert_match %r{identifier}, data + assert_match %r{amount}, data + end.respond_with(successful_refund_response) + assert_success response + end + + def test_failed_refund + response = stub_comms do + @gateway.refund(nil, 'identifier') + end.respond_with(failed_refund_response) + assert_failure response + end + def test_add_address result = {} - - @gateway.send(:add_address, result, nil, :billing_address => {:address1 => '164 Waverley Street', :country => 'US', :state => 'CO'} ) - assert_equal ["address1", "city", "company", "country", "phone", "state", "zip"], result.stringify_keys.keys.sort + + @gateway.send(:add_address, result, nil, :billing_address => {:address1 => '164 Waverley Street', :country => 'US', :state => 'CO'}) + assert_equal ['address1', 'city', 'company', 'country', 'phone', 'state', 'zip'], result.stringify_keys.keys.sort assert_equal 'CO', result[:state] assert_equal '164 Waverley Street', result[:address1] assert_equal 'US', result[:country] - end - + def test_supported_countries assert_equal ['US'], InspireGateway.supported_countries end @@ -49,23 +74,23 @@ def test_supported_countries def test_supported_card_types assert_equal [:visa, :master, :american_express], InspireGateway.supported_cardtypes end - + def test_adding_store_adds_vault_id_flag result = {} - + @gateway.send(:add_creditcard, result, @credit_card, :store => true) - assert_equal ["ccexp", "ccnumber", "customer_vault", "cvv", "firstname", "lastname"], result.stringify_keys.keys.sort + assert_equal ['ccexp', 'ccnumber', 'customer_vault', 'cvv', 'firstname', 'lastname'], result.stringify_keys.keys.sort assert_equal 'add_customer', result[:customer_vault] end - + def test_blank_store_doesnt_add_vault_flag result = {} - - @gateway.send(:add_creditcard, result, @credit_card, {} ) - assert_equal ["ccexp", "ccnumber", "cvv", "firstname", "lastname"], result.stringify_keys.keys.sort + + @gateway.send(:add_creditcard, result, @credit_card, {}) + assert_equal ['ccexp', 'ccnumber', 'cvv', 'firstname', 'lastname'], result.stringify_keys.keys.sort assert_nil result[:customer_vault] end - + def test_accept_check post = {} check = Check.new(:name => 'Fred Bloggs', @@ -76,42 +101,42 @@ def test_accept_check @gateway.send(:add_check, post, check) assert_equal %w[account_holder_type account_type checkaba checkaccount checkname payment], post.stringify_keys.keys.sort end - + def test_funding_source assert_equal :check, @gateway.send(:determine_funding_source, Check.new) assert_equal :credit_card, @gateway.send(:determine_funding_source, @credit_card) assert_equal :vault, @gateway.send(:determine_funding_source, '12345') end - + def test_avs_result @gateway.expects(:ssl_post).returns(successful_purchase_response) - + response = @gateway.purchase(@amount, @credit_card) assert_equal 'N', response.avs_result['code'] end - + def test_cvv_result @gateway.expects(:ssl_post).returns(successful_purchase_response) - + response = @gateway.purchase(@amount, @credit_card) assert_equal 'N', response.cvv_result['code'] end - - def test_gateway_should_be_available_as_brain_tree - gateway = InspireGateway.new(:login => 'l', :password => 'p') - gateway.expects(:ssl_post).returns(successful_purchase_response) - response = gateway.purchase(@amount, @credit_card) - assert_success response - - end private + def successful_purchase_response 'response=1&responsetext=SUCCESS&authcode=123456&transactionid=510695343&avsresponse=N&cvvresponse=N&orderid=ea1e0d50dcc8cfc6e4b55650c592097e&type=sale&response_code=100' end - + def failed_purchase_response 'response=2&responsetext=DECLINE&authcode=&transactionid=510695919&avsresponse=N&cvvresponse=N&orderid=50357660b0b3ef16f72a3d3b83c46983&type=sale&response_code=200' end -end + def successful_refund_response + 'response=1&responsetext=SUCCESS&authcode=&transactionid=2594884528&avsresponse=&cvvresponse=&orderid=&type=refund&response_code=100' + end + + def failed_refund_response + 'response=3&responsetext=Invalid Transaction ID specified REFID:3150951931&authcode=&transactionid=&avsresponse=&cvvresponse=&orderid=&type=refund&response_code=300' + end +end diff --git a/test/unit/gateways/instapay_test.rb b/test/unit/gateways/instapay_test.rb old mode 100755 new mode 100644 index 1e698357c52..d70c9c52f72 --- a/test/unit/gateways/instapay_test.rb +++ b/test/unit/gateways/instapay_test.rb @@ -11,9 +11,9 @@ def test_successful_purchase @gateway.expects(:ssl_post).returns(successful_purchase_response) assert response = @gateway.purchase(@amount, @credit_card) - assert_instance_of Response, response + assert_instance_of Response, response assert_success response - assert_equal "118583850", response.authorization + assert_equal '118583850', response.authorization end def test_unsuccessful_purchase @@ -29,9 +29,9 @@ def test_successful_auth @gateway.expects(:ssl_post).returns(successful_purchase_response) assert response = @gateway.authorize(@amount, @credit_card) - assert_instance_of Response, response + assert_instance_of Response, response assert_success response - assert_equal "118583850", response.authorization + assert_equal '118583850', response.authorization end def test_unsuccessful_auth @@ -42,33 +42,33 @@ def test_unsuccessful_auth assert_failure response assert_nil response.authorization end - + def test_avs_result @gateway.expects(:ssl_post).returns(successful_purchase_response) - + response = @gateway.purchase(@amount, @credit_card) assert_equal 'X', response.avs_result['code'] end - + def test_cvv_result @gateway.expects(:ssl_post).returns(successful_purchase_response) - + response = @gateway.purchase(@amount, @credit_card) assert_equal 'M', response.cvv_result['code'] end - + def test_successful_capture @gateway.expects(:ssl_post).returns(successful_capture_response) - - response = @gateway.capture(100, "123456") + + response = @gateway.capture(100, '123456') assert_equal InstapayGateway::SUCCESS_MESSAGE, response.message end - + def test_failed_capture @gateway.expects(:ssl_post).returns(failed_capture_response) - - response = @gateway.capture(100, "123456") - assert_equal "Post amount exceeds Auth amount", response.message + + response = @gateway.capture(100, '123456') + assert_equal 'Post amount exceeds Auth amount', response.message end private @@ -82,7 +82,7 @@ def successful_purchase_response def failed_purchase_response "<html><body><plaintext>\r\nDeclined=DECLINED:0720930009:CVV2 MISMATCH:N7\r\nhistoryid=118583848\r\norderid=92886713\r\nACCOUNTNUMBER=************2220\r\nDeclined=DECLINED:0720930009:CVV2 MISMATCH:N7\r\nhistoryid=118583848\r\norderid=92886713\r\nrcode=0720930009\r\nReason=DECLINED:0720930009:CVV2 MISMATCH:N7\r\nrecurid=0\r\nresult=0\r\nStatus=Declined\r\ntransid=80410586\r\n" end - + def successful_auth_response "<html><body><plaintext>\r\nAccepted=AUTH:TEST:::118585994:::\r\nhistoryid=118585994\r\norderid=92888143\r\nAccepted=AUTH:TEST:::118585994:::\r\nACCOUNTNUMBER=************5454\r\nauthcode=TEST\r\nAuthNo=AUTH:TEST:::118585994:::\r\nhistoryid=118585994\r\norderid=92888143\r\nrecurid=0\r\nrefcode=118585994-TEST\r\nresult=1\r\nStatus=Accepted\r\ntransid=0\r\n" end @@ -90,13 +90,12 @@ def successful_auth_response def failed_auth_response "<html><body><plaintext>\r\nDeclined=DECLINED:0720930009:CVV2 MISMATCH:N7\r\nhistoryid=118585991\r\norderid=92888142\r\nACCOUNTNUMBER=************2220\r\nDeclined=DECLINED:0720930009:CVV2 MISMATCH:N7\r\nhistoryid=118585991\r\norderid=92888142\r\nrcode=0720930009\r\nReason=DECLINED:0720930009:CVV2 MISMATCH:N7\r\nrecurid=0\r\nresult=0\r\nStatus=Declined\r\ntransid=80412271\r\n" end - + def successful_capture_response "<html><body><plaintext>\r\nAccepted=AVSAUTH:TEST:::121609962::::DUPLICATE\r\nhistoryid=121609962\r\norderid=95009583\r\nAccepted=AVSAUTH:TEST:::121609962::::DUPLICATE\r\nACCOUNTNUMBER=************5454\r\nauthcode=TEST\r\nAuthNo=AVSAUTH:TEST:::121609962::::DUPLICATE\r\nDUPLICATE=1\r\nhistoryid=121609962\r\norderid=95009583\r\nrecurid=0\r\nrefcode=121609962-TEST\r\nresult=1\r\nStatus=Accepted\r\ntransid=0\r\n" end - + def failed_capture_response "<html><body><plaintext>\r\nDeclined=DECLINED:1101450002:Post amount exceeds Auth amount:\r\nhistoryid=\r\norderid=\r\nDeclined=DECLINED:1101450002:Post amount exceeds Auth amount:\r\nrcode=1101450002\r\nReason=DECLINED:1101450002:Post amount exceeds Auth amount:\r\nresult=0\r\nStatus=Declined\r\ntransid=0\r\n" end end - diff --git a/test/unit/gateways/ipp_test.rb b/test/unit/gateways/ipp_test.rb new file mode 100644 index 00000000000..40b8035b37b --- /dev/null +++ b/test/unit/gateways/ipp_test.rb @@ -0,0 +1,211 @@ +require 'test_helper' + +class IppTest < Test::Unit::TestCase + include CommStub + + def setup + @gateway = IppGateway.new( + username: 'username', + password: 'password' + ) + + @amount = 100 + @credit_card = credit_card + end + + def test_successful_purchase + response = stub_comms do + @gateway.purchase(@amount, @credit_card, order_id: 1) + end.check_request do |endpoint, data, headers| + assert_match(%r{<SubmitSinglePayment }, data) + assert_match(%r{<UserName>username<}, data) + assert_match(%r{<Password>password<}, data) + assert_match(%r{<CustRef>1<}, data) + assert_match(%r{<Amount>100<}, data) + assert_match(%r{<TrnType>1<}, data) + assert_match(%r{<CardNumber>#{@credit_card.number}<}, data) + assert_match(%r{<ExpM>#{"%02d" % @credit_card.month}<}, data) + assert_match(%r{<ExpY>#{@credit_card.year}<}, data) + assert_match(%r{<CVN>#{@credit_card.verification_value}<}, data) + assert_match(%r{<CardHolderName>#{@credit_card.name}<}, data) + end.respond_with(successful_purchase_response) + + assert_success response + assert_equal '89435577', response.authorization + end + + def test_failed_purchase + response = stub_comms do + @gateway.purchase(@amount, @credit_card) + end.respond_with(failed_purchase_response) + + assert_failure response + assert_equal 'Do Not Honour', response.message + assert_equal Gateway::STANDARD_ERROR_CODE[:card_declined], response.error_code + assert_equal '', response.authorization + end + + def test_successful_authorize + response = stub_comms do + @gateway.authorize(@amount, @credit_card, order_id: 1) + end.check_request do |endpoint, data, headers| + assert_match(%r{<SubmitSinglePayment }, data) + assert_match(%r{<CustRef>1<}, data) + assert_match(%r{<TrnType>2<}, data) + end.respond_with(successful_authorize_response) + + assert_success response + assert_equal '89435583', response.authorization + end + + def test_successful_capture + response = stub_comms do + @gateway.capture(@amount, 'receipt') + end.check_request do |endpoint, data, headers| + assert_match(%r{<SubmitSingleCapture }, data) + assert_match(%r{<Receipt>receipt<}, data) + assert_match(%r{<Amount>100<}, data) + end.respond_with(successful_capture_response) + + assert_success response + end + + def test_successful_refund + response = stub_comms do + @gateway.refund(@amount, 'receipt') + end.check_request do |endpoint, data, headers| + assert_match(%r{<SubmitSingleRefund }, data) + assert_match(%r{<Receipt>receipt<}, data) + assert_match(%r{<Amount>100<}, data) + end.respond_with(successful_refund_response) + + assert_success response + end + + def test_scrub + assert @gateway.supports_scrubbing? + assert_equal @gateway.scrub(pre_scrubbed), post_scrubbed + end + + private + + def pre_scrubbed + <<-'PRE_SCRUBBED' +opening connection to demo.ippayments.com.au:443... +opened +starting SSL for demo.ippayments.com.au:443... +SSL established +<- "POST /interface/api/dts.asmx HTTP/1.1\r\nContent-Type: text/xml; charset=utf-8\r\nSoapaction: http://www.ippayments.com.au/interface/api/dts/SubmitSinglePayment\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: demo.ippayments.com.au\r\nContent-Length: 822\r\n\r\n" +<- "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<soap:Envelope xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">\n <soap:Body>\n <SubmitSinglePayment xmlns=\"http://www.ippayments.com.au/interface/api/dts\">\n <trnXML>\n <![CDATA[<Transaction>\n <CustRef>1</CustRef>\n <Amount/>\n <TrnType>1</TrnType>\n <CreditCard Registered=\"False\">\n <CardNumber>4005550000000001</CardNumber>\n <ExpM>09</ExpM>\n <ExpY>2015</ExpY>\n <CVN>123</CVN>\n <CardHolderName>Longbob Longsen</CardHolderName>\n </CreditCard>\n <Security>\n <UserName>nmi.api</UserName>\n <Password>qwerty123</Password>\n </Security>\n <TrnSource/>\n</Transaction>\n]]>\n </trnXML>\n </SubmitSinglePayment>\n </soap:Body>\n</soap:Envelope>\n" +-> "HTTP/1.1 200 OK\r\n" +-> "Server: Microsoft-IIS/6.0\r\n" +-> "X-Robots-Tag: noindex\r\n" +-> "X-Powered-By: ASP.NET\r\n" +-> "Cache-Control: private, max-age=0\r\n" +-> "Content-Type: text/xml; charset=utf-8\r\n" +-> "Content-Length: 767\r\n" +-> "Date: Fri, 19 Dec 2014 19:55:13 GMT\r\n" +-> "Connection: close\r\n" +-> "\r\n" +reading 767 bytes... +-> "<?xml version=\"1.0\" encoding=\"utf-8\"?><soap:Envelope xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"><soap:Body><SubmitSinglePaymentResponse xmlns=\"http://www.ippayments.com.au/interface/api/dts\"><SubmitSinglePaymentResult>&lt;Response&gt;\r\n\t&lt;ResponseCode&gt;1&lt;/ResponseCode&gt;\r\n\t&lt;Timestamp&gt;20-Dec-2014 06:55:17&lt;/Timestamp&gt;\r\n\t&lt;Receipt&gt;&lt;/Receipt&gt;\r\n\t&lt;SettlementDate&gt;&lt;/SettlementDate&gt;\r\n\t&lt;DeclinedCode&gt;183&lt;/DeclinedCode&gt;\r\n\t&lt;DeclinedMessage&gt;Exception parsing transaction XML&lt;/DeclinedMessage&gt;\r\n&lt;/Response&gt;\r\n</SubmitSinglePaymentResult></SubmitSinglePaymentResponse></soap:Body></soap:Envelope>" +read 767 bytes +Conn close + PRE_SCRUBBED + end + + def post_scrubbed + <<-'POST_SCRUBBED' +opening connection to demo.ippayments.com.au:443... +opened +starting SSL for demo.ippayments.com.au:443... +SSL established +<- "POST /interface/api/dts.asmx HTTP/1.1\r\nContent-Type: text/xml; charset=utf-8\r\nSoapaction: http://www.ippayments.com.au/interface/api/dts/SubmitSinglePayment\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: demo.ippayments.com.au\r\nContent-Length: 822\r\n\r\n" +<- "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<soap:Envelope xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">\n <soap:Body>\n <SubmitSinglePayment xmlns=\"http://www.ippayments.com.au/interface/api/dts\">\n <trnXML>\n <![CDATA[<Transaction>\n <CustRef>1</CustRef>\n <Amount/>\n <TrnType>1</TrnType>\n <CreditCard Registered=\"False\">\n <CardNumber>[FILTERED]</CardNumber>\n <ExpM>09</ExpM>\n <ExpY>2015</ExpY>\n <CVN>[FILTERED]</CVN>\n <CardHolderName>Longbob Longsen</CardHolderName>\n </CreditCard>\n <Security>\n <UserName>nmi.api</UserName>\n <Password>[FILTERED]</Password>\n </Security>\n <TrnSource/>\n</Transaction>\n]]>\n </trnXML>\n </SubmitSinglePayment>\n </soap:Body>\n</soap:Envelope>\n" +-> "HTTP/1.1 200 OK\r\n" +-> "Server: Microsoft-IIS/6.0\r\n" +-> "X-Robots-Tag: noindex\r\n" +-> "X-Powered-By: ASP.NET\r\n" +-> "Cache-Control: private, max-age=0\r\n" +-> "Content-Type: text/xml; charset=utf-8\r\n" +-> "Content-Length: 767\r\n" +-> "Date: Fri, 19 Dec 2014 19:55:13 GMT\r\n" +-> "Connection: close\r\n" +-> "\r\n" +reading 767 bytes... +-> "<?xml version=\"1.0\" encoding=\"utf-8\"?><soap:Envelope xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"><soap:Body><SubmitSinglePaymentResponse xmlns=\"http://www.ippayments.com.au/interface/api/dts\"><SubmitSinglePaymentResult>&lt;Response&gt;\r\n\t&lt;ResponseCode&gt;1&lt;/ResponseCode&gt;\r\n\t&lt;Timestamp&gt;20-Dec-2014 06:55:17&lt;/Timestamp&gt;\r\n\t&lt;Receipt&gt;&lt;/Receipt&gt;\r\n\t&lt;SettlementDate&gt;&lt;/SettlementDate&gt;\r\n\t&lt;DeclinedCode&gt;183&lt;/DeclinedCode&gt;\r\n\t&lt;DeclinedMessage&gt;Exception parsing transaction XML&lt;/DeclinedMessage&gt;\r\n&lt;/Response&gt;\r\n</SubmitSinglePaymentResult></SubmitSinglePaymentResponse></soap:Body></soap:Envelope>" +read 767 bytes +Conn close + POST_SCRUBBED + end + + def successful_purchase_response + <<-XML +<?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><soap:Body><SubmitSinglePaymentResponse xmlns="http://www.ippayments.com.au/interface/api/dts"><SubmitSinglePaymentResult>&lt;Response&gt; + &lt;ResponseCode&gt;0&lt;/ResponseCode&gt; + &lt;Timestamp&gt;20-Dec-2014 04:07:39&lt;/Timestamp&gt; + &lt;Receipt&gt;89435577&lt;/Receipt&gt; + &lt;SettlementDate&gt;22-Dec-2014&lt;/SettlementDate&gt; + &lt;DeclinedCode&gt;&lt;/DeclinedCode&gt; + &lt;DeclinedMessage&gt;&lt;/DeclinedMessage&gt; +&lt;/Response&gt; +</SubmitSinglePaymentResult></SubmitSinglePaymentResponse></soap:Body></soap:Envelope> + XML + end + + def failed_purchase_response + <<-XML +<?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><soap:Body><SubmitSinglePaymentResponse xmlns="http://www.ippayments.com.au/interface/api/dts"><SubmitSinglePaymentResult>&lt;Response&gt; + &lt;ResponseCode&gt;1&lt;/ResponseCode&gt; + &lt;Timestamp&gt;20-Dec-2014 04:14:56&lt;/Timestamp&gt; + &lt;Receipt&gt;&lt;/Receipt&gt; + &lt;SettlementDate&gt;22-Dec-2014&lt;/SettlementDate&gt; + &lt;DeclinedCode&gt;05&lt;/DeclinedCode&gt; + &lt;DeclinedMessage&gt;Do Not Honour&lt;/DeclinedMessage&gt; +&lt;/Response&gt; +</SubmitSinglePaymentResult></SubmitSinglePaymentResponse></soap:Body></soap:Envelope> + XML + end + + def successful_authorize_response + <<-XML +<?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><soap:Body><SubmitSinglePaymentResponse xmlns="http://www.ippayments.com.au/interface/api/dts"><SubmitSinglePaymentResult>&lt;Response&gt; + &lt;ResponseCode&gt;0&lt;/ResponseCode&gt; + &lt;Timestamp&gt;20-Dec-2014 04:18:13&lt;/Timestamp&gt; + &lt;Receipt&gt;89435583&lt;/Receipt&gt; + &lt;SettlementDate&gt;22-Dec-2014&lt;/SettlementDate&gt; + &lt;DeclinedCode&gt;&lt;/DeclinedCode&gt; + &lt;DeclinedMessage&gt;&lt;/DeclinedMessage&gt; +&lt;/Response&gt; +</SubmitSinglePaymentResult></SubmitSinglePaymentResponse></soap:Body></soap:Envelope> + XML + end + + def successful_capture_response + <<-XML +<?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><soap:Body><SubmitSingleCaptureResponse xmlns="http://www.ippayments.com.au/interface/api/dts"><SubmitSingleCaptureResult>&lt;Response&gt; + &lt;ResponseCode&gt;0&lt;/ResponseCode&gt; + &lt;Timestamp&gt;20-Dec-2014 04:18:15&lt;/Timestamp&gt; + &lt;Receipt&gt;89435584&lt;/Receipt&gt; + &lt;SettlementDate&gt;22-Dec-2014&lt;/SettlementDate&gt; + &lt;DeclinedCode&gt;&lt;/DeclinedCode&gt; + &lt;DeclinedMessage&gt;&lt;/DeclinedMessage&gt; +&lt;/Response&gt; +</SubmitSingleCaptureResult></SubmitSingleCaptureResponse></soap:Body></soap:Envelope> + XML + end + + def successful_refund_response + <<-XML +<?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><soap:Body><SubmitSingleRefundResponse xmlns="http://www.ippayments.com.au/interface/api/dts"><SubmitSingleRefundResult>&lt;Response&gt; + &lt;ResponseCode&gt;0&lt;/ResponseCode&gt; + &lt;Timestamp&gt;20-Dec-2014 04:24:51&lt;/Timestamp&gt; + &lt;Receipt&gt;89435596&lt;/Receipt&gt; + &lt;SettlementDate&gt;22-Dec-2014&lt;/SettlementDate&gt; + &lt;DeclinedCode&gt;&lt;/DeclinedCode&gt; + &lt;DeclinedMessage&gt;&lt;/DeclinedMessage&gt; +&lt;/Response&gt; +</SubmitSingleRefundResult></SubmitSingleRefundResponse></soap:Body></soap:Envelope> + XML + end +end diff --git a/test/unit/gateways/iridium_test.rb b/test/unit/gateways/iridium_test.rb index b988623eae6..055afb50653 100644 --- a/test/unit/gateways/iridium_test.rb +++ b/test/unit/gateways/iridium_test.rb @@ -2,26 +2,26 @@ class IridiumTest < Test::Unit::TestCase def setup - Base.gateway_mode = :test + Base.mode = :test @gateway = IridiumGateway.new(:login => 'login', :password => 'password') @credit_card = credit_card @amount = 100 - - @options = { + + @options = { :order_id => '1', :billing_address => address, :description => 'Store Purchase' } end - + def test_successful_purchase @gateway.expects(:ssl_post).returns(successful_purchase_response) - + assert response = @gateway.purchase(@amount, @credit_card, @options) assert_success response - + # Replace with authorization number from the successful response assert_equal '1;100115170338509401960011;960011', response.authorization assert response.test? @@ -29,89 +29,92 @@ def test_successful_purchase def test_unsuccessful_request @gateway.expects(:ssl_post).returns(failed_purchase_response) - + assert response = @gateway.purchase(@amount, @credit_card, @options) assert_failure response assert_nil response.authorization assert response.test? end - def test_successful_authorize @gateway.expects(:ssl_post).returns(successful_authorize_response) - + assert response = @gateway.authorize(@amount, @credit_card, @options) assert_success response - + assert_equal('1;100115172046327701460093;460093', response.authorization) assert response.test? end - + def test_successful_capture @gateway.expects(:ssl_post).returns(successful_capture_response) - - assert response = @gateway.capture(1111, "1;100115172046327701460093;460093") + + assert response = @gateway.capture(1111, '1;100115172046327701460093;460093') assert_success response - + assert_equal('100115172047506301812526', response.authorization) assert response.test? end def test_successful_deprecated_credit @gateway.expects(:ssl_post).returns(successful_credit_response) - assert_deprecation_warning(Gateway::CREDIT_DEPRECATION_MESSAGE, @gateway) do + assert_deprecation_warning(Gateway::CREDIT_DEPRECATION_MESSAGE) do assert response = @gateway.credit(@amount, '123456789') assert_success response assert_equal 'Refund successful', response.message end end - + def test_successful_refund @gateway.expects(:ssl_post).returns(successful_credit_response) assert response = @gateway.refund(@amount, '123456789') assert_success response assert_equal 'Refund successful', response.message end - + def test_failed_refund @gateway.expects(:ssl_post).returns(failed_credit_response) - + assert response = @gateway.refund(@amount, '123456789') assert_failure response assert_equal 'Amount exceeds that available for refund [17]', response.message end - + def test_default_currency @gateway.expects(:ssl_post).with(anything, regexp_matches(/CurrencyCode="978"/), anything).returns(successful_purchase_response) assert_success @gateway.purchase(@amount, @credit_card, @options) end - + def test_override_currency @gateway.expects(:ssl_post). with(anything, all_of(regexp_matches(/Amount="400"/), regexp_matches(/CurrencyCode="484"/)), anything). returns(successful_purchase_response) assert_success @gateway.purchase(400, @credit_card, @options.merge(:currency => 'MXN')) end - + def test_do_not_depend_on_expiry_date_class @gateway.stubs(:ssl_post).returns(successful_purchase_response) @credit_card.expects(:expiry_date).never - + @gateway.purchase(@amount, @credit_card, @options) end - + def test_use_ducktyping_for_credit_card @gateway.expects(:ssl_post).returns(successful_purchase_response) - credit_card = stub(:number => '4242424242424242', :verification_value => '123', :name => "Hans Tester", :year => 2012, :month => 1) - + credit_card = stub(:number => '4242424242424242', :verification_value => '123', :name => 'Hans Tester', :year => 2012, :month => 1) + assert_nothing_raised do assert_success @gateway.purchase(@amount, credit_card, @options) end end - + + def test_transcript_scrubbing + assert_equal post_scrubbed, @gateway.scrub(pre_scrubbed) + end + private - + # Place raw successful response from gateway here def successful_purchase_response %(<?xml version="1.0" encoding="utf-8"?> @@ -137,7 +140,7 @@ def successful_purchase_response </soap:Body> </soap:Envelope>) end - + # Place raw failed response from gateway here def failed_purchase_response %(<?xml version="1.0" encoding="utf-8"?> @@ -162,7 +165,7 @@ def failed_purchase_response </soap:Body> </soap:Envelope>) end - + def successful_authorize_response %(<?xml version="1.0" encoding="utf-8"?> <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> @@ -187,7 +190,7 @@ def successful_authorize_response </soap:Body> </soap:Envelope>) end - + def successful_capture_response %(<?xml version="1.0" encoding="utf-8"?> <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> @@ -208,7 +211,7 @@ def successful_capture_response </soap:Body> </soap:Envelope>) end - + def successful_credit_response %(<?xml version="1.0" encoding="utf-8"?> <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> @@ -229,7 +232,7 @@ def successful_credit_response </soap:Body> </soap:Envelope>) end - + def failed_credit_response %(<?xml version="1.0" encoding="utf-8"?> <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> @@ -250,4 +253,93 @@ def failed_credit_response </soap:Body> </soap:Envelope>) end + + def pre_scrubbed + <<-PRE_SCRUBBED + <?xml version="1.0" encoding="utf-8"?> + <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <soap:Body> + <CardDetailsTransaction xmlns="https://www.thepaymentgateway.net/"> + <PaymentMessage> + <MerchantAuthentication MerchantID="Flamen-3258723" Password="7C03L5ON49"/> + <TransactionDetails Amount="100" CurrencyCode="978"> + <MessageDetails TransactionType="SALE"/> + <OrderID>c7073e2a2d18b19b8dadcd0a5da3f7e1</OrderID> + <TransactionControl> + <ThreeDSecureOverridePolicy>FALSE</ThreeDSecureOverridePolicy> + <EchoAVSCheckResult>TRUE</EchoAVSCheckResult> + <EchoCV2CheckResult>TRUE</EchoCV2CheckResult> + </TransactionControl> + </TransactionDetails> + <CardDetails> + <CardName>Longbob Longsen</CardName> + <CV2>452</CV2> + <CardNumber>4976000000003436</CardNumber> + <ExpiryDate Month="09" Year="12"/> + </CardDetails> + <CustomerDetails> + <BillingAddress> + <Address1>32 Edward Street</Address1> + <Address2>Camborne</Address2> + <City>Ottawa</City> + <State>Cornwall</State> + <PostCode>TR14&#160;8PA</PostCode> + <CountryCode>826</CountryCode> + </BillingAddress> + <PhoneNumber>(555)555-5555</PhoneNumber> + <EmailAddress></EmailAddress> + <CustomerIPAddress>127.0.0.1</CustomerIPAddress> + </CustomerDetails> + </PaymentMessage> + </CardDetailsTransaction> + </soap:Body> + </soap:Envelope> + <?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><soap:Body><CardDetailsTransactionResponse xmlns="https://www.thepaymentgateway.net/"><CardDetailsTransactionResult AuthorisationAttempted="True"><StatusCode>0</StatusCode><Message>AuthCode: 608724</Message></CardDetailsTransactionResult><TransactionOutputData CrossReference="110428221508160201608724"><AuthCode>608724</AuthCode><AddressNumericCheckResult>PASSED</AddressNumericCheckResult><PostCodeCheckResult>PASSED</PostCodeCheckResult><CV2CheckResult>PASSED</CV2CheckResult><GatewayEntryPoints><GatewayEntryPoint EntryPointURL="https://gw1.iridiumcorp.net/" Metric="100" /><GatewayEntryPoint EntryPointURL="https://gw2.iridiumcorp.net/" Metric="200" /><GatewayEntryPoint EntryPointURL="https://gw3.iridiumcorp.net/" Metric="300" /></GatewayEntryPoints></TransactionOutputData></CardDetailsTransactionResponse></soap:Body></soap:Envelope> + PRE_SCRUBBED + end + + def post_scrubbed + <<-POST_SCRUBBED + <?xml version="1.0" encoding="utf-8"?> + <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <soap:Body> + <CardDetailsTransaction xmlns="https://www.thepaymentgateway.net/"> + <PaymentMessage> + <MerchantAuthentication MerchantID="Flamen-3258723" Password="7C03L5ON49"/> + <TransactionDetails Amount="100" CurrencyCode="978"> + <MessageDetails TransactionType="SALE"/> + <OrderID>c7073e2a2d18b19b8dadcd0a5da3f7e1</OrderID> + <TransactionControl> + <ThreeDSecureOverridePolicy>FALSE</ThreeDSecureOverridePolicy> + <EchoAVSCheckResult>TRUE</EchoAVSCheckResult> + <EchoCV2CheckResult>TRUE</EchoCV2CheckResult> + </TransactionControl> + </TransactionDetails> + <CardDetails> + <CardName>Longbob Longsen</CardName> + <CV2>[FILTERED]</CV2> + <CardNumber>[FILTERED]</CardNumber> + <ExpiryDate Month="09" Year="12"/> + </CardDetails> + <CustomerDetails> + <BillingAddress> + <Address1>32 Edward Street</Address1> + <Address2>Camborne</Address2> + <City>Ottawa</City> + <State>Cornwall</State> + <PostCode>TR14&#160;8PA</PostCode> + <CountryCode>826</CountryCode> + </BillingAddress> + <PhoneNumber>(555)555-5555</PhoneNumber> + <EmailAddress></EmailAddress> + <CustomerIPAddress>127.0.0.1</CustomerIPAddress> + </CustomerDetails> + </PaymentMessage> + </CardDetailsTransaction> + </soap:Body> + </soap:Envelope> + <?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><soap:Body><CardDetailsTransactionResponse xmlns="https://www.thepaymentgateway.net/"><CardDetailsTransactionResult AuthorisationAttempted="True"><StatusCode>0</StatusCode><Message>AuthCode: 608724</Message></CardDetailsTransactionResult><TransactionOutputData CrossReference="110428221508160201608724"><AuthCode>608724</AuthCode><AddressNumericCheckResult>PASSED</AddressNumericCheckResult><PostCodeCheckResult>PASSED</PostCodeCheckResult><CV2CheckResult>PASSED</CV2CheckResult><GatewayEntryPoints><GatewayEntryPoint EntryPointURL="https://gw1.iridiumcorp.net/" Metric="100" /><GatewayEntryPoint EntryPointURL="https://gw2.iridiumcorp.net/" Metric="200" /><GatewayEntryPoint EntryPointURL="https://gw3.iridiumcorp.net/" Metric="300" /></GatewayEntryPoints></TransactionOutputData></CardDetailsTransactionResponse></soap:Body></soap:Envelope> + POST_SCRUBBED + end + end diff --git a/test/unit/gateways/itransact_test.rb b/test/unit/gateways/itransact_test.rb index e1fe37ef8e3..5f43bfc702f 100644 --- a/test/unit/gateways/itransact_test.rb +++ b/test/unit/gateways/itransact_test.rb @@ -11,8 +11,8 @@ def setup @credit_card = credit_card @check = check @amount = 1014 # = $10.14 - - @options = { + + @options = { :email => 'name@domain.com', :order_id => '1', :billing_address => address, @@ -20,14 +20,14 @@ def setup :email_text => ['line1', 'line2', 'line3'] } end - + def test_successful_card_purchase @gateway.expects(:ssl_post).returns(successful_card_purchase_response) - + assert response = @gateway.purchase(@amount, @credit_card, @options) assert_instance_of Response, response assert_success response - + assert_equal '9999999999', response.authorization assert response.test? end @@ -45,19 +45,19 @@ def test_successful_check_purchase def test_unsuccessful_card_request @gateway.expects(:ssl_post).returns(failed_purchase_response) - + assert response = @gateway.purchase(@amount, @credit_card, @options) assert_failure response assert response.test? end private - + def successful_card_purchase_response "<?xml version=\"1.0\" standalone=\"yes\"?> <GatewayInterface><TransactionResponse><TransactionResult><Status>ok</Status><ErrorCategory></ErrorCategory><ErrorMessage></ErrorMessage><WarningMessage></WarningMessage><AuthCode></AuthCode><AVSCategory></AVSCategory><AVSResponse></AVSResponse><CVV2Response></CVV2Response><TimeStamp>20081216141214</TimeStamp><TestMode>TRUE</TestMode><Total>1.0</Total><XID>9999999999</XID><CustomerData><BillingAddress><Address1>1234 My Street</Address1><City>Ottawa</City><FirstName>Longbob</FirstName><LastName>Longsen</LastName><State>ON</State><Zip>K1C2N6</Zip><Country>CA</Country><Phone>(555)555-5555</Phone></BillingAddress><ShippingAddress><Address1></Address1><City></City><FirstName></FirstName><LastName></LastName><State></State><Zip></Zip><Country></Country><Phone></Phone></ShippingAddress></CustomerData></TransactionResult></TransactionResponse></GatewayInterface>" end - + def failed_purchase_response '<?xml version="1.0" standalone="yes"?> <GatewayInterface><TransactionResponse><TransactionResult><Status>FAILED</Status><ErrorCategory>REQUEST_FORMAT</ErrorCategory><ErrorMessage>Form does not contain xml parameter</ErrorMessage><AuthCode></AuthCode><AVSCategory></AVSCategory><AVSResponse></AVSResponse><CVV2Response></CVV2Response><TimeStamp></TimeStamp><TestMode></TestMode><Total></Total><XID></XID><CustomerData><BillingAddress><Address1 /><City></City><FirstName></FirstName><LastName></LastName><State></State><Zip></Zip><Country></Country><Phone></Phone></BillingAddress><ShippingAddress><Address1></Address1><City></City><FirstName></FirstName><LastName></LastName><State></State><Zip></Zip><Country></Country><Phone></Phone></ShippingAddress></CustomerData></TransactionResult></TransactionResponse></GatewayInterface>' diff --git a/test/unit/gateways/iveri_test.rb b/test/unit/gateways/iveri_test.rb new file mode 100644 index 00000000000..1269cd1ee53 --- /dev/null +++ b/test/unit/gateways/iveri_test.rb @@ -0,0 +1,553 @@ +require 'test_helper' + +class IveriTest < Test::Unit::TestCase + def setup + @gateway = IveriGateway.new(app_id: '123', cert_id: '321') + @credit_card = credit_card('4242424242424242') + @amount = 100 + + @options = { + order_id: generate_unique_id, + billing_address: address, + description: 'Store Purchase', + currency: 'ZAR' + } + end + + def test_successful_purchase + @gateway.expects(:ssl_post).returns(successful_purchase_response) + + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal '{F0568958-D10B-4093-A3BF-663168B06140}|{5CEF96FD-960E-4EA5-811F-D02CE6E36A96}|48b63446223ce91451fc3c1641a9ec03', response.authorization + assert response.test? + end + + def test_failed_purchase + @gateway.expects(:ssl_post).returns(failed_purchase_response) + + response = @gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_equal '4', response.error_code + end + + def test_successful_authorize + @gateway.expects(:ssl_post).returns(successful_authorize_response) + + response = @gateway.authorize(@amount, @credit_card, @options) + assert_success response + assert_equal '{B90D7CDB-C8E8-4477-BDF2-695F28137874}|{EF0DC64E-2D00-4B6C-BDA0-2AD265391317}|23b4125c3b8e2777bffee52e196a863b', response.authorization + assert response.test? + end + + def test_failed_authorize + @gateway.expects(:ssl_post).returns(failed_authorize_response) + + response = @gateway.authorize(@amount, @credit_card, @options) + assert_failure response + assert_equal '4', response.error_code + end + + def test_successful_capture + @gateway.expects(:ssl_post).returns(successful_capture_response) + + response = @gateway.capture(@amount, '{B90D7CDB-C8E8-4477-BDF2-695F28137874}|{EF0DC64E-2D00-4B6C-BDA0-2AD265391317}|23b4125c3b8e2777bffee52e196a863b') + assert_success response + assert_equal '{7C91245F-607D-44AE-8958-C26E447BAEB7}|{EF0DC64E-2D00-4B6C-BDA0-2AD265391317}|23b4125c3b8e2777bffee52e196a863b', response.authorization + assert response.test? + end + + def test_failed_capture + @gateway.expects(:ssl_post).returns(failed_capture_response) + + response = @gateway.capture(@amount, '', @options) + assert_failure response + assert_equal '14', response.error_code + end + + def test_successful_refund + @gateway.expects(:ssl_post).returns(successful_refund_response) + + response = @gateway.refund(@amount, '{33C8274D-6811-409A-BF86-661F24084A2F}|{D50DB1B4-B6EC-4AF1-AFF7-71C2AA4A957B}|5be2c040bd46b7eebc70274659779acf') + assert_success response + assert_equal '{097C55B5-D020-40AD-8949-F9F5E4102F1D}|{D50DB1B4-B6EC-4AF1-AFF7-71C2AA4A957B}|5be2c040bd46b7eebc70274659779acf', response.authorization + assert response.test? + end + + def test_failed_refund + @gateway.expects(:ssl_post).returns(failed_refund_response) + + response = @gateway.refund(@amount, '', @options) + assert_failure response + assert_equal '255', response.error_code + end + + def test_successful_void + @gateway.expects(:ssl_post).returns(successful_void_response) + + response = @gateway.void('{230390C8-4A9E-4426-BDD3-15D072F135FE}|{3CC6E6A8-13E0-41A6-AB1E-71BE1AEEAE58}|1435f1a008137cd8508bf43751e07495') + assert_success response + assert_equal '{0A1A3FFF-C2A3-4B91-85FD-10D1C25B765B}||', response.authorization + assert response.test? + end + + def test_failed_void + @gateway.expects(:ssl_post).returns(failed_void_response) + + response = @gateway.void('', @options) + assert_failure response + assert_equal '255', response.error_code + end + + def test_successful_verify + @gateway.expects(:ssl_post).returns(successful_verify_response) + + response = @gateway.verify(@credit_card, @options) + assert_success response + assert_equal '{F4337D04-B526-4A7E-A400-2A6DEADDCF57}|{5D5F8BF7-2D9D-42C3-AF32-08C5E62CD45E}|c0006d1d739905afc9e70beaf4194ea3', response.authorization + assert response.test? + end + + def test_failed_verify + @gateway.expects(:ssl_post).returns(failed_verify_response) + + response = @gateway.verify(credit_card('2121212121212121'), @options) + assert_failure response + assert_equal '4', response.error_code + end + + def test_successful_verify_credentials + @gateway.expects(:ssl_post).returns(successful_verify_credentials_response) + assert @gateway.verify_credentials + end + + def test_failed_verify_credentials + @gateway.expects(:ssl_post).returns(failed_verify_credentials_response) + assert !@gateway.verify_credentials + end + + def test_scrub + assert @gateway.supports_scrubbing? + assert_equal @gateway.scrub(pre_scrubbed), post_scrubbed + end + + private + + def pre_scrubbed + %q( +opening connection to portal.nedsecure.co.za:443... +opened +starting SSL for portal.nedsecure.co.za:443... +SSL established +<- "POST /iVeriWebService/Service.asmx HTTP/1.1\r\nContent-Type: text/xml; charset=utf-8\r\nContent-Length: 1016\r\nSoapaction: http://iveri.com/Execute\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: portal.nedsecure.co.za\r\n\r\n" +<- "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<soap:Envelope xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">\n <soap:Body>\n <Execute xmlns=\"http://iveri.com/\">\n <validateRequest>true</validateRequest>\n <protocol>V_XML</protocol>\n <protocolVersion>2.0</protocolVersion>\n <request>&lt;V_XML Version=\"2.0\" CertificateID=\"CB69E68D-C7E7-46B9-9B7A-025DCABAD6EF\" Direction=\"Request\"&gt;\n &lt;Transaction ApplicationID=\"D10A603D-4ADE-405B-93F1-826DFC0181E8\" Command=\"Debit\" Mode=\"Test\"&gt;\n &lt;Amount&gt;100&lt;/Amount&gt;\n &lt;Currency&gt;ZAR&lt;/Currency&gt;\n &lt;ExpiryDate&gt;092018&lt;/ExpiryDate&gt;\n &lt;MerchantReference&gt;b3ceea8b93d5611cbde7d162baef1245&lt;/MerchantReference&gt;\n &lt;CardSecurityCode&gt;123&lt;/CardSecurityCode&gt;\n &lt;PAN&gt;4242424242424242&lt;/PAN&gt;\n &lt;/Transaction&gt;\n&lt;/V_XML&gt;</request>\n </Execute>\n </soap:Body>\n</soap:Envelope>\n" +-> "HTTP/1.1 200 OK\r\n" +-> "Cache-Control: private, max-age=0\r\n" +-> "Content-Type: text/xml; charset=utf-8\r\n" +-> "Server: Microsoft-IIS/8.0\r\n" +-> "X-AspNet-Version: 4.0.30319\r\n" +-> "X-Powered-By: ASP.NET\r\n" +-> "Date: Wed, 12 Apr 2017 19:46:44 GMT\r\n" +-> "Connection: close\r\n" +-> "Content-Length: 2377\r\n" +-> "\r\n" +reading 2377 bytes... +-> "<?xml version=\"1.0\" encoding=\"utf-8\"?><soap:Envelope xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"><soap:Body><ExecuteResponse xmlns=\"http://iveri.com/\"><ExecuteResult>&lt;V_XML Version=\"2.0\" Direction=\"Response\"&gt;\r\n &lt;Transaction ApplicationID=\"{D10A603D-4ADE-405B-93F1-826DFC0181E8}\" Command=\"Debit\" Mode=\"Test\" RequestID=\"{5485B5EA-2661-4436-BAA9-CD6DD546FA0D}\"&gt;\r\n &lt;Result Status=\"0\" AppServer=\"105IVERIAPPPR02\" DBServer=\"105iveridbpr01\" Gateway=\"Nedbank\" AcquirerCode=\"00\" /&gt;\r\n &lt;Amount&gt;100&lt;/Amount&gt;\r\n &lt;AuthorisationCode&gt;115205&lt;/AuthorisationCode&gt;\r\n &lt;Currency&gt;ZAR&lt;/Currency&gt;\r\n &lt;ExpiryDate&gt;092018&lt;/ExpiryDate&gt;\r\n &lt;MerchantReference&gt;b3ceea8b93d5611cbde7d162baef1245&lt;/MerchantReference&gt;\r\n &lt;Terminal&gt;Default&lt;/Terminal&gt;\r\n &lt;TransactionIndex&gt;{10418186-FE90-44F9-AB7A-FEC11C9027F8}&lt;/TransactionIndex&gt;\r\n &lt;MerchantName&gt;iVeri Payment Technology&lt;/MerchantName&gt;\r\n &lt;MerchantUSN&gt;7771777&lt;/MerchantUSN&gt;\r\n &lt;Acquirer&gt;NBPostilionBICISONBSouthAfrica&lt;/Acquirer&gt;\r\n &lt;AcquirerReference&gt;70412:04077382&lt;/AcquirerReference&gt;\r\n &lt;AcquirerDate&gt;20170412&lt;/AcquirerDate&gt;\r\n &lt;AcquirerTime&gt;214645&lt;/AcquirerTime&gt;\r\n &lt;DisplayAmount&gt;R 1.00&lt;/DisplayAmount&gt;\r\n &lt;BIN&gt;4&lt;/BIN&gt;\r\n &lt;Association&gt;VISA&lt;/Association&gt;\r\n &lt;CardType&gt;Unknown CardType&lt;/CardType&gt;\r\n &lt;Issuer&gt;Unknown&lt;/Issuer&gt;\r\n &lt;Jurisdiction&gt;International&lt;/Jurisdiction&gt;\r\n &lt;PANMode&gt;Keyed,CVV&lt;/PANMode&gt;\r\n &lt;ReconReference&gt;04077382&lt;/ReconReference&gt;\r\n &lt;CardHolderPresence&gt;CardNotPresent&lt;/CardHolderPresence&gt;\r\n &lt;MerchantAddress&gt;MERCHANT ADDRESS&lt;/MerchantAddress&gt;\r\n &lt;MerchantCity&gt;Sandton&lt;/MerchantCity&gt;\r\n &lt;MerchantCountryCode&gt;ZA&lt;/MerchantCountryCode&gt;\r\n &lt;MerchantCountry&gt;South Africa&lt;/MerchantCountry&gt;\r\n &lt;DistributorName&gt;Nedbank&lt;/DistributorName&gt;\r\n &lt;CCNumber&gt;4242........4242&lt;/CCNumber&gt;\r\n &lt;PAN&gt;[4242........4242]&lt;/PAN&gt;\r\n &lt;/Transaction&gt;\r\n&lt;/V_XML&gt;</ExecuteResult></ExecuteResponse></soap:Body></soap:Envelope>" +read 2377 bytes +Conn close +) + end + + def post_scrubbed + %q( +opening connection to portal.nedsecure.co.za:443... +opened +starting SSL for portal.nedsecure.co.za:443... +SSL established +<- "POST /iVeriWebService/Service.asmx HTTP/1.1\r\nContent-Type: text/xml; charset=utf-8\r\nContent-Length: 1016\r\nSoapaction: http://iveri.com/Execute\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: portal.nedsecure.co.za\r\n\r\n" +<- "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<soap:Envelope xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">\n <soap:Body>\n <Execute xmlns=\"http://iveri.com/\">\n <validateRequest>true</validateRequest>\n <protocol>V_XML</protocol>\n <protocolVersion>2.0</protocolVersion>\n <request>&lt;V_XML Version=\"2.0\" CertificateID=\"[FILTERED]\" Direction=\"Request\"&gt;\n &lt;Transaction ApplicationID=\"D10A603D-4ADE-405B-93F1-826DFC0181E8\" Command=\"Debit\" Mode=\"Test\"&gt;\n &lt;Amount&gt;100&lt;/Amount&gt;\n &lt;Currency&gt;ZAR&lt;/Currency&gt;\n &lt;ExpiryDate&gt;092018&lt;/ExpiryDate&gt;\n &lt;MerchantReference&gt;b3ceea8b93d5611cbde7d162baef1245&lt;/MerchantReference&gt;\n &lt;CardSecurityCode&gt;[FILTERED]&lt;/CardSecurityCode&gt;\n &lt;PAN&gt;[FILTERED]&lt;/PAN&gt;\n &lt;/Transaction&gt;\n&lt;/V_XML&gt;</request>\n </Execute>\n </soap:Body>\n</soap:Envelope>\n" +-> "HTTP/1.1 200 OK\r\n" +-> "Cache-Control: private, max-age=0\r\n" +-> "Content-Type: text/xml; charset=utf-8\r\n" +-> "Server: Microsoft-IIS/8.0\r\n" +-> "X-AspNet-Version: 4.0.30319\r\n" +-> "X-Powered-By: ASP.NET\r\n" +-> "Date: Wed, 12 Apr 2017 19:46:44 GMT\r\n" +-> "Connection: close\r\n" +-> "Content-Length: 2377\r\n" +-> "\r\n" +reading 2377 bytes... +-> "<?xml version=\"1.0\" encoding=\"utf-8\"?><soap:Envelope xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"><soap:Body><ExecuteResponse xmlns=\"http://iveri.com/\"><ExecuteResult>&lt;V_XML Version=\"2.0\" Direction=\"Response\"&gt;\r\n &lt;Transaction ApplicationID=\"{D10A603D-4ADE-405B-93F1-826DFC0181E8}\" Command=\"Debit\" Mode=\"Test\" RequestID=\"{5485B5EA-2661-4436-BAA9-CD6DD546FA0D}\"&gt;\r\n &lt;Result Status=\"0\" AppServer=\"105IVERIAPPPR02\" DBServer=\"105iveridbpr01\" Gateway=\"Nedbank\" AcquirerCode=\"00\" /&gt;\r\n &lt;Amount&gt;100&lt;/Amount&gt;\r\n &lt;AuthorisationCode&gt;115205&lt;/AuthorisationCode&gt;\r\n &lt;Currency&gt;ZAR&lt;/Currency&gt;\r\n &lt;ExpiryDate&gt;092018&lt;/ExpiryDate&gt;\r\n &lt;MerchantReference&gt;b3ceea8b93d5611cbde7d162baef1245&lt;/MerchantReference&gt;\r\n &lt;Terminal&gt;Default&lt;/Terminal&gt;\r\n &lt;TransactionIndex&gt;{10418186-FE90-44F9-AB7A-FEC11C9027F8}&lt;/TransactionIndex&gt;\r\n &lt;MerchantName&gt;iVeri Payment Technology&lt;/MerchantName&gt;\r\n &lt;MerchantUSN&gt;7771777&lt;/MerchantUSN&gt;\r\n &lt;Acquirer&gt;NBPostilionBICISONBSouthAfrica&lt;/Acquirer&gt;\r\n &lt;AcquirerReference&gt;70412:04077382&lt;/AcquirerReference&gt;\r\n &lt;AcquirerDate&gt;20170412&lt;/AcquirerDate&gt;\r\n &lt;AcquirerTime&gt;214645&lt;/AcquirerTime&gt;\r\n &lt;DisplayAmount&gt;R 1.00&lt;/DisplayAmount&gt;\r\n &lt;BIN&gt;4&lt;/BIN&gt;\r\n &lt;Association&gt;VISA&lt;/Association&gt;\r\n &lt;CardType&gt;Unknown CardType&lt;/CardType&gt;\r\n &lt;Issuer&gt;Unknown&lt;/Issuer&gt;\r\n &lt;Jurisdiction&gt;International&lt;/Jurisdiction&gt;\r\n &lt;PANMode&gt;Keyed,CVV&lt;/PANMode&gt;\r\n &lt;ReconReference&gt;04077382&lt;/ReconReference&gt;\r\n &lt;CardHolderPresence&gt;CardNotPresent&lt;/CardHolderPresence&gt;\r\n &lt;MerchantAddress&gt;MERCHANT ADDRESS&lt;/MerchantAddress&gt;\r\n &lt;MerchantCity&gt;Sandton&lt;/MerchantCity&gt;\r\n &lt;MerchantCountryCode&gt;ZA&lt;/MerchantCountryCode&gt;\r\n &lt;MerchantCountry&gt;South Africa&lt;/MerchantCountry&gt;\r\n &lt;DistributorName&gt;Nedbank&lt;/DistributorName&gt;\r\n &lt;CCNumber&gt;4242........4242&lt;/CCNumber&gt;\r\n &lt;PAN&gt;[FILTERED]&lt;/PAN&gt;\r\n &lt;/Transaction&gt;\r\n&lt;/V_XML&gt;</ExecuteResult></ExecuteResponse></soap:Body></soap:Envelope>" +read 2377 bytes +Conn close +) + end + + def successful_purchase_response + <<-XML +<?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><soap:Body><ExecuteResponse xmlns="http://iveri.com/"><ExecuteResult>&lt;V_XML Version="2.0" Direction="Response"&gt; +&lt;Transaction ApplicationID="{D10A603D-4ADE-405B-93F1-826DFC0181E8}" Command="Debit" Mode="Test" RequestID="{F0568958-D10B-4093-A3BF-663168B06140}"&gt; + &lt;Result Status="0" AppServer="105IVERIAPPPR01" DBServer="105IVERIDBPR01" Gateway="Nedbank" AcquirerCode="00" /&gt; + &lt;Amount&gt;100&lt;/Amount&gt; + &lt;AuthorisationCode&gt;537473&lt;/AuthorisationCode&gt; + &lt;Currency&gt;ZAR&lt;/Currency&gt; + &lt;ExpiryDate&gt;092018&lt;/ExpiryDate&gt; + &lt;MerchantReference&gt;48b63446223ce91451fc3c1641a9ec03&lt;/MerchantReference&gt; + &lt;Terminal&gt;Default&lt;/Terminal&gt; + &lt;TransactionIndex&gt;{5CEF96FD-960E-4EA5-811F-D02CE6E36A96}&lt;/TransactionIndex&gt; + &lt;MerchantName&gt;iVeri Payment Technology&lt;/MerchantName&gt; + &lt;MerchantUSN&gt;7771777&lt;/MerchantUSN&gt; + &lt;Acquirer&gt;NBPostilionBICISONBSouthAfrica&lt;/Acquirer&gt; + &lt;AcquirerReference&gt;70417:04077982&lt;/AcquirerReference&gt; + &lt;AcquirerDate&gt;20170417&lt;/AcquirerDate&gt; + &lt;AcquirerTime&gt;190433&lt;/AcquirerTime&gt; + &lt;DisplayAmount&gt;R 1.00&lt;/DisplayAmount&gt; + &lt;BIN&gt;4&lt;/BIN&gt; + &lt;Association&gt;VISA&lt;/Association&gt; + &lt;CardType&gt;Unknown CardType&lt;/CardType&gt; + &lt;Issuer&gt;Unknown&lt;/Issuer&gt; + &lt;Jurisdiction&gt;International&lt;/Jurisdiction&gt; + &lt;PANMode&gt;Keyed,CVV&lt;/PANMode&gt; + &lt;ReconReference&gt;04077982&lt;/ReconReference&gt; + &lt;CardHolderPresence&gt;CardNotPresent&lt;/CardHolderPresence&gt; + &lt;MerchantAddress&gt;MERCHANT ADDRESS&lt;/MerchantAddress&gt; + &lt;MerchantCity&gt;Sandton&lt;/MerchantCity&gt; + &lt;MerchantCountryCode&gt;ZA&lt;/MerchantCountryCode&gt; + &lt;MerchantCountry&gt;South Africa&lt;/MerchantCountry&gt; + &lt;DistributorName&gt;Nedbank&lt;/DistributorName&gt; + &lt;CCNumber&gt;4242........4242&lt;/CCNumber&gt; + &lt;PAN&gt;4242........4242&lt;/PAN&gt; +&lt;/Transaction&gt; +&lt;/V_XML&gt;</ExecuteResult></ExecuteResponse></soap:Body></soap:Envelope> + XML + end + + def failed_purchase_response + <<-XML +<?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><soap:Body><ExecuteResponse xmlns="http://iveri.com/"><ExecuteResult>&lt;V_XML Version="2.0" Direction="Response"&gt; + &lt;Transaction ApplicationID="{D10A603D-4ADE-405B-93F1-826DFC0181E8}" Command="Debit" Mode="Test" RequestID="{B14C3834-72B9-4ACA-B362-B3C9EC96E8C0}"&gt; + &lt;Result Status="-1" Code="4" Description="Denied" Source="NBPostilionBICISONBSouthAfrica" AppServer="105IVERIAPPPR01" DBServer="105IVERIDBPR01" Gateway="Nedbank" AcquirerCode="05" AcquirerDescription="Do not Honour" /&gt; + &lt;Amount&gt;100&lt;/Amount&gt; + &lt;Currency&gt;ZAR&lt;/Currency&gt; + &lt;ExpiryDate&gt;092018&lt;/ExpiryDate&gt; + &lt;MerchantReference&gt;435a5d60b5fe874840c34e2e0504626b&lt;/MerchantReference&gt; + &lt;Terminal&gt;Default&lt;/Terminal&gt; + &lt;TransactionIndex&gt;{B35872A9-39C7-4DB8-9774-A5E34FFA519E}&lt;/TransactionIndex&gt; + &lt;MerchantName&gt;iVeri Payment Technology&lt;/MerchantName&gt; + &lt;MerchantUSN&gt;7771777&lt;/MerchantUSN&gt; + &lt;Acquirer&gt;NBPostilionBICISONBSouthAfrica&lt;/Acquirer&gt; + &lt;AcquirerReference&gt;70417:04077988&lt;/AcquirerReference&gt; + &lt;AcquirerDate&gt;20170417&lt;/AcquirerDate&gt; + &lt;AcquirerTime&gt;192038&lt;/AcquirerTime&gt; + &lt;DisplayAmount&gt;R 1.00&lt;/DisplayAmount&gt; + &lt;BIN&gt;2&lt;/BIN&gt; + &lt;Association&gt;Unknown Association&lt;/Association&gt; + &lt;CardType&gt;Unknown CardType&lt;/CardType&gt; + &lt;Issuer&gt;Unknown&lt;/Issuer&gt; + &lt;Jurisdiction&gt;Local&lt;/Jurisdiction&gt; + &lt;PANMode&gt;Keyed,CVV&lt;/PANMode&gt; + &lt;ReconReference&gt;04077988&lt;/ReconReference&gt; + &lt;CardHolderPresence&gt;CardNotPresent&lt;/CardHolderPresence&gt; + &lt;MerchantAddress&gt;MERCHANT ADDRESS&lt;/MerchantAddress&gt; + &lt;MerchantCity&gt;Sandton&lt;/MerchantCity&gt; + &lt;MerchantCountryCode&gt;ZA&lt;/MerchantCountryCode&gt; + &lt;MerchantCountry&gt;South Africa&lt;/MerchantCountry&gt; + &lt;DistributorName&gt;Nedbank&lt;/DistributorName&gt; + &lt;CCNumber&gt;2121........2121&lt;/CCNumber&gt; + &lt;PAN&gt;2121........2121&lt;/PAN&gt; + &lt;/Transaction&gt; +&lt;/V_XML&gt;</ExecuteResult></ExecuteResponse></soap:Body></soap:Envelope> + XML + end + + def successful_authorize_response + <<-XML +<?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><soap:Body><ExecuteResponse xmlns="http://iveri.com/"><ExecuteResult>&lt;V_XML Version="2.0" Direction="Response"&gt; + &lt;Transaction ApplicationID="{D10A603D-4ADE-405B-93F1-826DFC0181E8}" Command="Authorisation" Mode="Test" RequestID="{B90D7CDB-C8E8-4477-BDF2-695F28137874}"&gt; + &lt;Result Status="0" AppServer="105IVERIAPPPR01" DBServer="105IVERIDBPR01" Gateway="Nedbank" AcquirerCode="00" /&gt; + &lt;Amount&gt;100&lt;/Amount&gt; + &lt;AuthorisationCode&gt;541267&lt;/AuthorisationCode&gt; + &lt;Currency&gt;ZAR&lt;/Currency&gt; + &lt;ExpiryDate&gt;092018&lt;/ExpiryDate&gt; + &lt;MerchantReference&gt;23b4125c3b8e2777bffee52e196a863b&lt;/MerchantReference&gt; + &lt;Terminal&gt;Default&lt;/Terminal&gt; + &lt;TransactionIndex&gt;{EF0DC64E-2D00-4B6C-BDA0-2AD265391317}&lt;/TransactionIndex&gt; + &lt;MerchantName&gt;iVeri Payment Technology&lt;/MerchantName&gt; + &lt;MerchantUSN&gt;7771777&lt;/MerchantUSN&gt; + &lt;Acquirer&gt;NBPostilionBICISONBSouthAfrica&lt;/Acquirer&gt; + &lt;AcquirerReference&gt;70417:04078057&lt;/AcquirerReference&gt; + &lt;AcquirerDate&gt;20170417&lt;/AcquirerDate&gt; + &lt;AcquirerTime&gt;200747&lt;/AcquirerTime&gt; + &lt;DisplayAmount&gt;R 1.00&lt;/DisplayAmount&gt; + &lt;BIN&gt;4&lt;/BIN&gt; + &lt;Association&gt;VISA&lt;/Association&gt; + &lt;CardType&gt;Unknown CardType&lt;/CardType&gt; + &lt;Issuer&gt;Unknown&lt;/Issuer&gt; + &lt;Jurisdiction&gt;International&lt;/Jurisdiction&gt; + &lt;PANMode&gt;Keyed,CVV&lt;/PANMode&gt; + &lt;ReconReference&gt;04078057&lt;/ReconReference&gt; + &lt;CardHolderPresence&gt;CardNotPresent&lt;/CardHolderPresence&gt; + &lt;MerchantAddress&gt;MERCHANT ADDRESS&lt;/MerchantAddress&gt; + &lt;MerchantCity&gt;Sandton&lt;/MerchantCity&gt; + &lt;MerchantCountryCode&gt;ZA&lt;/MerchantCountryCode&gt; + &lt;MerchantCountry&gt;South Africa&lt;/MerchantCountry&gt; + &lt;DistributorName&gt;Nedbank&lt;/DistributorName&gt; + &lt;CCNumber&gt;4242........4242&lt;/CCNumber&gt; + &lt;PAN&gt;4242........4242&lt;/PAN&gt; + &lt;/Transaction&gt; +&lt;/V_XML&gt;</ExecuteResult></ExecuteResponse></soap:Body></soap:Envelope> + XML + end + + def failed_authorize_response + <<-XML +<?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><soap:Body><ExecuteResponse xmlns="http://iveri.com/"><ExecuteResult>&lt;V_XML Version="2.0" Direction="Response"&gt; + &lt;Transaction ApplicationID="{D10A603D-4ADE-405B-93F1-826DFC0181E8}" Command="Authorisation" Mode="Test" RequestID="{3A1A29BE-288F-4FEE-8C15-B3BB8A207544}"&gt; + &lt;Result Status="-1" Code="4" Description="Denied" Source="NBPostilionBICISONBSouthAfrica" AppServer="105IVERIAPPPR01" DBServer="105IVERIDBPR01" Gateway="Nedbank" AcquirerCode="05" AcquirerDescription="Do not Honour" /&gt; + &lt;Amount&gt;100&lt;/Amount&gt; + &lt;Currency&gt;ZAR&lt;/Currency&gt; + &lt;ExpiryDate&gt;092018&lt;/ExpiryDate&gt; + &lt;MerchantReference&gt;3d12442ea042e78fd33057b7b50c76f7&lt;/MerchantReference&gt; + &lt;Terminal&gt;Default&lt;/Terminal&gt; + &lt;TransactionIndex&gt;{8AC33FB1-0D2E-42C7-A0DB-CF8B20279825}&lt;/TransactionIndex&gt; + &lt;MerchantName&gt;iVeri Payment Technology&lt;/MerchantName&gt; + &lt;MerchantUSN&gt;7771777&lt;/MerchantUSN&gt; + &lt;Acquirer&gt;NBPostilionBICISONBSouthAfrica&lt;/Acquirer&gt; + &lt;AcquirerReference&gt;70417:04078062&lt;/AcquirerReference&gt; + &lt;AcquirerDate&gt;20170417&lt;/AcquirerDate&gt; + &lt;AcquirerTime&gt;202648&lt;/AcquirerTime&gt; + &lt;DisplayAmount&gt;R 1.00&lt;/DisplayAmount&gt; + &lt;BIN&gt;2&lt;/BIN&gt; + &lt;Association&gt;Unknown Association&lt;/Association&gt; + &lt;CardType&gt;Unknown CardType&lt;/CardType&gt; + &lt;Issuer&gt;Unknown&lt;/Issuer&gt; + &lt;Jurisdiction&gt;Local&lt;/Jurisdiction&gt; + &lt;PANMode&gt;Keyed,CVV&lt;/PANMode&gt; + &lt;ReconReference&gt;04078062&lt;/ReconReference&gt; + &lt;CardHolderPresence&gt;CardNotPresent&lt;/CardHolderPresence&gt; + &lt;MerchantAddress&gt;MERCHANT ADDRESS&lt;/MerchantAddress&gt; + &lt;MerchantCity&gt;Sandton&lt;/MerchantCity&gt; + &lt;MerchantCountryCode&gt;ZA&lt;/MerchantCountryCode&gt; + &lt;MerchantCountry&gt;South Africa&lt;/MerchantCountry&gt; + &lt;DistributorName&gt;Nedbank&lt;/DistributorName&gt; + &lt;CCNumber&gt;2121........2121&lt;/CCNumber&gt; + &lt;PAN&gt;2121........2121&lt;/PAN&gt; + &lt;/Transaction&gt; +&lt;/V_XML&gt;</ExecuteResult></ExecuteResponse></soap:Body></soap:Envelope> + XML + end + + def successful_capture_response + <<-XML +<?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><soap:Body><ExecuteResponse xmlns="http://iveri.com/"><ExecuteResult>&lt;V_XML Version="2.0" Direction="Response"&gt; + &lt;Transaction ApplicationID="{D10A603D-4ADE-405B-93F1-826DFC0181E8}" Command="Debit" Mode="Test" RequestID="{7C91245F-607D-44AE-8958-C26E447BAEB7}"&gt; + &lt;Result Status="0" AppServer="105IVERIAPPPR02" DBServer="105iveridbpr01" Gateway="Nedbank" AcquirerCode="00" /&gt; + &lt;Amount&gt;100&lt;/Amount&gt; + &lt;AuthorisationCode&gt;541268&lt;/AuthorisationCode&gt; + &lt;Currency&gt;ZAR&lt;/Currency&gt; + &lt;ExpiryDate&gt;092018&lt;/ExpiryDate&gt; + &lt;MerchantReference&gt;23b4125c3b8e2777bffee52e196a863b&lt;/MerchantReference&gt; + &lt;Terminal&gt;Default&lt;/Terminal&gt; + &lt;TransactionIndex&gt;{EF0DC64E-2D00-4B6C-BDA0-2AD265391317}&lt;/TransactionIndex&gt; + &lt;MerchantName&gt;iVeri Payment Technology&lt;/MerchantName&gt; + &lt;MerchantUSN&gt;7771777&lt;/MerchantUSN&gt; + &lt;Acquirer&gt;NBPostilionBICISONBSouthAfrica&lt;/Acquirer&gt; + &lt;AcquirerReference&gt;70417:04078057&lt;/AcquirerReference&gt; + &lt;AcquirerDate&gt;20170417&lt;/AcquirerDate&gt; + &lt;AcquirerTime&gt;200748&lt;/AcquirerTime&gt; + &lt;DisplayAmount&gt;R 1.00&lt;/DisplayAmount&gt; + &lt;BIN&gt;4&lt;/BIN&gt; + &lt;Association&gt;VISA&lt;/Association&gt; + &lt;CardType&gt;Unknown CardType&lt;/CardType&gt; + &lt;Issuer&gt;Unknown&lt;/Issuer&gt; + &lt;Jurisdiction&gt;International&lt;/Jurisdiction&gt; + &lt;PANMode&gt;Keyed,CVV&lt;/PANMode&gt; + &lt;ReconReference&gt;04078057&lt;/ReconReference&gt; + &lt;CardHolderPresence&gt;CardNotPresent&lt;/CardHolderPresence&gt; + &lt;MerchantAddress&gt;MERCHANT ADDRESS&lt;/MerchantAddress&gt; + &lt;MerchantCity&gt;Sandton&lt;/MerchantCity&gt; + &lt;MerchantCountryCode&gt;ZA&lt;/MerchantCountryCode&gt; + &lt;MerchantCountry&gt;South Africa&lt;/MerchantCountry&gt; + &lt;DistributorName&gt;Nedbank&lt;/DistributorName&gt; + &lt;CCNumber&gt;4242........4242&lt;/CCNumber&gt; + &lt;PAN&gt;4242........4242&lt;/PAN&gt; + &lt;/Transaction&gt; +&lt;/V_XML&gt;</ExecuteResult></ExecuteResponse></soap:Body></soap:Envelope> + XML + end + + def failed_capture_response + <<-XML +<?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><soap:Body><ExecuteResponse xmlns="http://iveri.com/"><ExecuteResult>&lt;V_XML Version="2.0" Direction="Response"&gt; + &lt;Transaction ApplicationID="{D10A603D-4ADE-405B-93F1-826DFC0181E8}" Command="Debit" Mode="Test" RequestID="{9DAAA002-0EF9-46DC-A440-8DCD9E78B36F}"&gt; + &lt;Result Status="-1" Code="14" Description="Missing PAN" Source="NBPostilionBICISONBSouthAfricaTestProvider" AppServer="105IVERIAPPPR02" DBServer="105iveridbpr01" Gateway="Nedbank" AcquirerCode="" AcquirerDescription="" /&gt; + &lt;/Transaction&gt; +&lt;/V_XML&gt;</ExecuteResult></ExecuteResponse></soap:Body></soap:Envelope> + XML + end + + def successful_refund_response + <<-XML +<?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><soap:Body><ExecuteResponse xmlns="http://iveri.com/"><ExecuteResult>&lt;V_XML Version="2.0" Direction="Response"&gt; + &lt;Transaction ApplicationID="{D10A603D-4ADE-405B-93F1-826DFC0181E8}" Command="Credit" Mode="Test" RequestID="{097C55B5-D020-40AD-8949-F9F5E4102F1D}"&gt; + &lt;Result Status="0" AppServer="105IVERIAPPPR02" DBServer="105iveridbpr01" Gateway="Nedbank" AcquirerCode="00" /&gt; + &lt;Amount&gt;100&lt;/Amount&gt; + &lt;AuthorisationCode&gt;541996&lt;/AuthorisationCode&gt; + &lt;Currency&gt;ZAR&lt;/Currency&gt; + &lt;ExpiryDate&gt;092018&lt;/ExpiryDate&gt; + &lt;MerchantReference&gt;5be2c040bd46b7eebc70274659779acf&lt;/MerchantReference&gt; + &lt;Terminal&gt;Default&lt;/Terminal&gt; + &lt;TransactionIndex&gt;{D50DB1B4-B6EC-4AF1-AFF7-71C2AA4A957B}&lt;/TransactionIndex&gt; + &lt;MerchantName&gt;iVeri Payment Technology&lt;/MerchantName&gt; + &lt;MerchantUSN&gt;7771777&lt;/MerchantUSN&gt; + &lt;Acquirer&gt;NBPostilionBICISONBSouthAfrica&lt;/Acquirer&gt; + &lt;AcquirerReference&gt;70417:04078059&lt;/AcquirerReference&gt; + &lt;AcquirerDate&gt;20170417&lt;/AcquirerDate&gt; + &lt;AcquirerTime&gt;201956&lt;/AcquirerTime&gt; + &lt;DisplayAmount&gt;R 1.00&lt;/DisplayAmount&gt; + &lt;BIN&gt;4&lt;/BIN&gt; + &lt;Association&gt;VISA&lt;/Association&gt; + &lt;CardType&gt;Unknown CardType&lt;/CardType&gt; + &lt;Issuer&gt;Unknown&lt;/Issuer&gt; + &lt;Jurisdiction&gt;International&lt;/Jurisdiction&gt; + &lt;PANMode /&gt; + &lt;ReconReference&gt;04078059&lt;/ReconReference&gt; + &lt;CardHolderPresence&gt;CardNotPresent&lt;/CardHolderPresence&gt; + &lt;MerchantAddress&gt;MERCHANT ADDRESS&lt;/MerchantAddress&gt; + &lt;MerchantCity&gt;Sandton&lt;/MerchantCity&gt; + &lt;MerchantCountryCode&gt;ZA&lt;/MerchantCountryCode&gt; + &lt;MerchantCountry&gt;South Africa&lt;/MerchantCountry&gt; + &lt;DistributorName&gt;Nedbank&lt;/DistributorName&gt; + &lt;CCNumber&gt;4242........4242&lt;/CCNumber&gt; + &lt;PAN&gt;4242........4242&lt;/PAN&gt; + &lt;/Transaction&gt; +&lt;/V_XML&gt;</ExecuteResult></ExecuteResponse></soap:Body></soap:Envelope> + XML + end + + def failed_refund_response + <<-XML +<?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><soap:Body><ExecuteResponse xmlns="http://iveri.com/"><ExecuteResult>&lt;V_XML Version="2.0" Direction="Response"&gt; + &lt;Transaction ApplicationID="{D10A603D-4ADE-405B-93F1-826DFC0181E8}" Command="Credit" Mode="Test" RequestID="{5097A60A-A112-42F1-9490-FA17A859E7A3}"&gt; + &lt;Result Status="-1" Code="255" Description="Credit is not supported for ApplicationID (D10A603D-4ADE-405B-93F1-826DFC0181E8)" Source="PortalService" AppServer="105IVERIAPPPR02" DBServer="105iveridbpr01" Gateway="Nedbank" AcquirerCode="" AcquirerDescription="" /&gt; + &lt;/Transaction&gt; +&lt;/V_XML&gt;</ExecuteResult></ExecuteResponse></soap:Body></soap:Envelope> + XML + end + + def successful_void_response + <<-XML +<?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><soap:Body><ExecuteResponse xmlns="http://iveri.com/"><ExecuteResult>&lt;V_XML Version="2.0" Direction="Response"&gt; + &lt;Transaction ApplicationID="{D10A603D-4ADE-405B-93F1-826DFC0181E8}" Command="Void" Mode="Test" RequestID="{0A1A3FFF-C2A3-4B91-85FD-10D1C25B765B}"&gt; + &lt;Result Status="0" AppServer="105IVERIAPPPR02" DBServer="105iveridbpr01" Gateway="Nedbank" /&gt; + &lt;OriginalRequestID&gt;{230390C8-4A9E-4426-BDD3-15D072F135FE}&lt;/OriginalRequestID&gt; + &lt;/Transaction&gt; +&lt;/V_XML&gt;</ExecuteResult></ExecuteResponse></soap:Body></soap:Envelope> + XML + end + + def failed_void_response + <<-XML +<?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><soap:Body><ExecuteResponse xmlns="http://iveri.com/"><ExecuteResult>&lt;V_XML Version="2.0" Direction="Response"&gt; + &lt;Transaction ApplicationID="{D10A603D-4ADE-405B-93F1-826DFC0181E8}" Command="Void" Mode="Test" RequestID="{AE97CCE4-0631-4F08-AB47-9C2698ABEC75}"&gt; + &lt;Result Status="-1" Code="255" Description="Missing OriginalMerchantTrace" Source="NBPostilionBICISONBSouthAfricaTestProvider" AppServer="105IVERIAPPPR01" DBServer="105IVERIDBPR01" Gateway="Nedbank" AcquirerCode="" AcquirerDescription="" /&gt; + &lt;/Transaction&gt; +&lt;/V_XML&gt;</ExecuteResult></ExecuteResponse></soap:Body></soap:Envelope> + XML + end + + def successful_verify_response + <<-XML +<?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><soap:Body><ExecuteResponse xmlns="http://iveri.com/"><ExecuteResult>&lt;V_XML Version="2.0" Direction="Response"&gt; + &lt;Transaction ApplicationID="{D10A603D-4ADE-405B-93F1-826DFC0181E8}" Command="Authorisation" Mode="Test" RequestID="{F4337D04-B526-4A7E-A400-2A6DEADDCF57}"&gt; + &lt;Result Status="0" AppServer="105IVERIAPPPR01" DBServer="105IVERIDBPR01" Gateway="Nedbank" AcquirerCode="00" /&gt; + &lt;Amount&gt;0&lt;/Amount&gt; + &lt;AuthorisationCode&gt;613755&lt;/AuthorisationCode&gt; + &lt;Currency&gt;ZAR&lt;/Currency&gt; + &lt;ExpiryDate&gt;092018&lt;/ExpiryDate&gt; + &lt;MerchantReference&gt;c0006d1d739905afc9e70beaf4194ea3&lt;/MerchantReference&gt; + &lt;Terminal&gt;Default&lt;/Terminal&gt; + &lt;TransactionIndex&gt;{5D5F8BF7-2D9D-42C3-AF32-08C5E62CD45E}&lt;/TransactionIndex&gt; + &lt;MerchantName&gt;iVeri Payment Technology&lt;/MerchantName&gt; + &lt;MerchantUSN&gt;7771777&lt;/MerchantUSN&gt; + &lt;Acquirer&gt;NBPostilionBICISONBSouthAfrica&lt;/Acquirer&gt; + &lt;AcquirerReference&gt;70418:04078335&lt;/AcquirerReference&gt; + &lt;AcquirerDate&gt;20170418&lt;/AcquirerDate&gt; + &lt;AcquirerTime&gt;161555&lt;/AcquirerTime&gt; + &lt;DisplayAmount&gt;R 0.00&lt;/DisplayAmount&gt; + &lt;BIN&gt;4&lt;/BIN&gt; + &lt;Association&gt;VISA&lt;/Association&gt; + &lt;CardType&gt;Unknown CardType&lt;/CardType&gt; + &lt;Issuer&gt;Unknown&lt;/Issuer&gt; + &lt;Jurisdiction&gt;International&lt;/Jurisdiction&gt; + &lt;PANMode&gt;Keyed,CVV&lt;/PANMode&gt; + &lt;ReconReference&gt;04078335&lt;/ReconReference&gt; + &lt;CardHolderPresence&gt;CardNotPresent&lt;/CardHolderPresence&gt; + &lt;MerchantAddress&gt;MERCHANT ADDRESS&lt;/MerchantAddress&gt; + &lt;MerchantCity&gt;Sandton&lt;/MerchantCity&gt; + &lt;MerchantCountryCode&gt;ZA&lt;/MerchantCountryCode&gt; + &lt;MerchantCountry&gt;South Africa&lt;/MerchantCountry&gt; + &lt;DistributorName&gt;Nedbank&lt;/DistributorName&gt; + &lt;CCNumber&gt;4242........4242&lt;/CCNumber&gt; + &lt;PAN&gt;4242........4242&lt;/PAN&gt; + &lt;/Transaction&gt; +&lt;/V_XML&gt;</ExecuteResult></ExecuteResponse></soap:Body></soap:Envelope> + XML + end + + def failed_verify_response + <<-XML +<?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><soap:Body><ExecuteResponse xmlns="http://iveri.com/"><ExecuteResult>&lt;V_XML Version="2.0" Direction="Response"&gt; + &lt;Transaction ApplicationID="{D10A603D-4ADE-405B-93F1-826DFC0181E8}" Command="Authorisation" Mode="Test" RequestID="{A700FAE2-2A76-407D-A540-B41668E2B703}"&gt; + &lt;Result Status="-1" Code="4" Description="Denied" Source="NBPostilionBICISONBSouthAfrica" AppServer="105IVERIAPPPR02" DBServer="105iveridbpr01" Gateway="Nedbank" AcquirerCode="05" AcquirerDescription="Do not Honour" /&gt; + &lt;Amount&gt;0&lt;/Amount&gt; + &lt;Currency&gt;ZAR&lt;/Currency&gt; + &lt;ExpiryDate&gt;092018&lt;/ExpiryDate&gt; + &lt;MerchantReference&gt;e955afb03f224284b09ad6ae7e9b4683&lt;/MerchantReference&gt; + &lt;Terminal&gt;Default&lt;/Terminal&gt; + &lt;TransactionIndex&gt;{2A378547-AEA4-48E1-8A3E-29F9BBEA954D}&lt;/TransactionIndex&gt; + &lt;MerchantName&gt;iVeri Payment Technology&lt;/MerchantName&gt; + &lt;MerchantUSN&gt;7771777&lt;/MerchantUSN&gt; + &lt;Acquirer&gt;NBPostilionBICISONBSouthAfrica&lt;/Acquirer&gt; + &lt;AcquirerReference&gt;70418:04078337&lt;/AcquirerReference&gt; + &lt;AcquirerDate&gt;20170418&lt;/AcquirerDate&gt; + &lt;AcquirerTime&gt;161716&lt;/AcquirerTime&gt; + &lt;DisplayAmount&gt;R 0.00&lt;/DisplayAmount&gt; + &lt;BIN&gt;2&lt;/BIN&gt; + &lt;Association&gt;Unknown Association&lt;/Association&gt; + &lt;CardType&gt;Unknown CardType&lt;/CardType&gt; + &lt;Issuer&gt;Unknown&lt;/Issuer&gt; + &lt;Jurisdiction&gt;Local&lt;/Jurisdiction&gt; + &lt;PANMode&gt;Keyed,CVV&lt;/PANMode&gt; + &lt;ReconReference&gt;04078337&lt;/ReconReference&gt; + &lt;CardHolderPresence&gt;CardNotPresent&lt;/CardHolderPresence&gt; + &lt;MerchantAddress&gt;MERCHANT ADDRESS&lt;/MerchantAddress&gt; + &lt;MerchantCity&gt;Sandton&lt;/MerchantCity&gt; + &lt;MerchantCountryCode&gt;ZA&lt;/MerchantCountryCode&gt; + &lt;MerchantCountry&gt;South Africa&lt;/MerchantCountry&gt; + &lt;DistributorName&gt;Nedbank&lt;/DistributorName&gt; + &lt;CCNumber&gt;2121........2121&lt;/CCNumber&gt; + &lt;PAN&gt;2121........2121&lt;/PAN&gt; + &lt;/Transaction&gt; +&lt;/V_XML&gt;</ExecuteResult></ExecuteResponse></soap:Body></soap:Envelope> + XML + end + + def successful_verify_credentials_response + <<-XML +<?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><soap:Body><ExecuteResponse xmlns="http://iveri.com/"><ExecuteResult>&lt;V_XML Version="2.0" Direction="Response"&gt; + &lt;Transaction ApplicationID="{D10A603D-4ADE-405B-93F1-826DFC0181E8}" Command="Void" Mode="Test" RequestID="{5ED922D0-92AD-40DF-9019-320591A4BA59}"&gt; + &lt;Result Status="-1" Code="255" Description="Missing OriginalMerchantTrace" Source="NBPostilionBICISONBSouthAfricaTestProvider" AppServer="105IVERIAPPPR01" DBServer="105IVERIDBPR01" Gateway="Nedbank" AcquirerCode="" AcquirerDescription="" /&gt; + &lt;/Transaction&gt; +&lt;/V_XML&gt;</ExecuteResult></ExecuteResponse></soap:Body></soap:Envelope> + XML + end + + def failed_verify_credentials_response + <<-XML +<?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><soap:Body><ExecuteResponse xmlns="http://iveri.com/"><ExecuteResult>&lt;V_XML Version="2.0" Direction="Response"&gt; + &lt;Result Status="-1" Code="255" Description="The ApplicationID {11111111-1111-1111-1111-111111111111} is not valid for the current CertificateID {11111111-1111-1111-1111-111111111111}" Source="RequestHandler" RequestID="{EE6E5B39-63AD-402C-8331-F25082AD8564}" AppServer="105IVERIAPPPR01" DBServer="105IVERIDBPR01" Gateway="Nedbank" AcquirerCode="" AcquirerDescription="" /&gt; +&lt;/V_XML&gt;</ExecuteResult></ExecuteResponse></soap:Body></soap:Envelope> + XML + end +end diff --git a/test/unit/gateways/jetpay_test.rb b/test/unit/gateways/jetpay_test.rb index 5cc0915bcb3..91069432c3e 100644 --- a/test/unit/gateways/jetpay_test.rb +++ b/test/unit/gateways/jetpay_test.rb @@ -4,15 +4,14 @@ class JetpayTest < Test::Unit::TestCase include ActiveMerchant::Billing def setup - Base.gateway_mode = :test + Base.mode = :test @gateway = JetpayGateway.new(:login => 'login') - + @credit_card = credit_card @amount = 100 - + @options = { - :order_id => '1', :billing_address => address(:country => 'US'), :shipping_address => address(:country => 'US'), :email => 'test@test.com', @@ -21,135 +20,144 @@ def setup :tax => 7 } end - + def test_successful_purchase @gateway.expects(:ssl_post).returns(successful_purchase_response) - + assert response = @gateway.purchase(@amount, @credit_card, @options) assert_success response - - assert_equal '707a4f1750d8dc03bd;TEST47;100', response.authorization - assert_equal('TEST47', response.params["approval"]) + + assert_equal '8afa688fd002821362;TEST97;100;KKLIHOJKKNKKHJKONJHOLHOL', response.authorization + assert_equal('TEST97', response.params['approval']) assert response.test? end def test_unsuccessful_request @gateway.expects(:ssl_post).returns(failed_purchase_response) - + assert response = @gateway.purchase(@amount, @credit_card, @options) assert_failure response - assert_equal('7605f7c5d6e8f74deb;;100', response.authorization) + assert_equal('7605f7c5d6e8f74deb;;100;', response.authorization) assert response.test? end - + def test_successful_authorize @gateway.expects(:ssl_post).returns(successful_authorize_response) - + assert response = @gateway.authorize(@amount, @credit_card, @options) assert_success response - - assert_equal('010327153017T10018;502F6B;100', response.authorization) - assert_equal('502F6B', response.params["approval"]) + + assert_equal('cbf902091334a0b1aa;TEST01;100;KKLIHOJKKNKKHJKONOHCLOIO', response.authorization) + assert_equal('TEST01', response.params['approval']) assert response.test? end - + def test_successful_capture @gateway.expects(:ssl_post).returns(successful_capture_response) - - assert response = @gateway.capture(1111, "010327153017T10018;502F7B;1111") + + assert response = @gateway.capture(1111, '010327153017T10018;502F7B;1111') assert_success response - - assert_equal('010327153017T10018;502F6B;1111', response.authorization) - assert_equal('502F6B', response.params["approval"]) + + assert_equal('010327153017T10018;502F6B;1111;', response.authorization) + assert_equal('502F6B', response.params['approval']) assert response.test? end - + def test_successful_void - # no need for csv - card = credit_card('4242424242424242', :verification_value => nil) - @gateway.expects(:ssl_post).returns(successful_void_response) - + assert response = @gateway.void('010327153x17T10418;502F7B;500') assert_success response - - assert_equal('010327153x17T10418;502F7B;500', response.authorization) - assert_equal('502F7B', response.params["approval"]) + + assert_equal('010327153x17T10418;502F7B;500;', response.authorization) + assert_equal('502F7B', response.params['approval']) assert response.test? end - + def test_successful_credit # no need for csv card = credit_card('4242424242424242', :verification_value => nil) @gateway.expects(:ssl_post).returns(successful_credit_response) - + # linked credit assert response = @gateway.refund(9900, '010327153017T10017') assert_success response - - assert_equal('010327153017T10017;002F6B;9900', response.authorization) + + assert_equal('010327153017T10017;002F6B;9900;', response.authorization) assert_equal('002F6B', response.params['approval']) assert response.test? - + # unlinked credit @gateway.expects(:ssl_post).returns(successful_credit_response) - + assert response = @gateway.credit(9900, card) - assert_success response + assert_success response end def test_deprecated_credit @gateway.expects(:ssl_post).returns(successful_credit_response) - assert_deprecation_warning(Gateway::CREDIT_DEPRECATION_MESSAGE, @gateway) do + assert_deprecation_warning(Gateway::CREDIT_DEPRECATION_MESSAGE) do assert response = @gateway.credit(9900, '010327153017T10017') assert_success response end end - + def test_successful_refund @gateway.expects(:ssl_post).returns(successful_credit_response) - + # linked credit assert response = @gateway.refund(9900, '010327153017T10017') assert_success response - - assert_equal('010327153017T10017;002F6B;9900', response.authorization) + + assert_equal('010327153017T10017;002F6B;9900;', response.authorization) assert_equal('002F6B', response.params['approval']) assert response.test? end def test_avs_result @gateway.expects(:ssl_post).returns(successful_purchase_response) - + response = @gateway.purchase(@amount, @credit_card) assert_equal 'Y', response.avs_result['code'] end - + def test_cvv_result @gateway.expects(:ssl_post).returns(successful_purchase_response) - + response = @gateway.purchase(@amount, @credit_card) assert_equal 'P', response.cvv_result['code'] end - - + + def test_transcript_scrubbing + assert_equal scrubbed_transcript, @gateway.scrub(transcript) + end + + def test_purchase_sends_order_origin + @gateway.expects(:ssl_post).with(anything, regexp_matches(/<Origin>RECURRING<\/Origin>/)).returns(successful_purchase_response) + + @gateway.purchase(@amount, @credit_card, {:origin => 'RECURRING'}) + end + private + def successful_purchase_response <<-EOF - <JetPayResponse><TransactionID>707a4f1750d8dc03bd</TransactionID> + <JetPayResponse> + <TransactionID>8afa688fd002821362</TransactionID> <ActionCode>000</ActionCode> - <Approval>TEST47</Approval> + <Approval>TEST97</Approval> <CVV2>P</CVV2> <ResponseText>APPROVED</ResponseText> + <Token>KKLIHOJKKNKKHJKONJHOLHOL</Token> <AddressMatch>Y</AddressMatch> <ZipMatch>Y</ZipMatch> <AVS>Y</AVS> </JetPayResponse> EOF end - + def failed_purchase_response <<-EOF <JetPayResponse> @@ -159,18 +167,23 @@ def failed_purchase_response </JetPayResponse> EOF end - + def successful_authorize_response <<-EOF <JetPayResponse> - <TransactionID>010327153017T10018</TransactionID> + <TransactionID>cbf902091334a0b1aa</TransactionID> <ActionCode>000</ActionCode> - <Approval>502F6B</Approval> + <Approval>TEST01</Approval> + <CVV2>P</CVV2> <ResponseText>APPROVED</ResponseText> + <Token>KKLIHOJKKNKKHJKONOHCLOIO</Token> + <AddressMatch>Y</AddressMatch> + <ZipMatch>Y</ZipMatch> + <AVS>Y</AVS> </JetPayResponse> EOF end - + def successful_capture_response <<-EOF <JetPayResponse> @@ -181,7 +194,7 @@ def successful_capture_response </JetPayResponse> EOF end - + def successful_void_response <<-EOF <JetPayResponse> @@ -192,7 +205,7 @@ def successful_void_response </JetPayResponse> EOF end - + def successful_credit_response <<-EOF <JetPayResponse> @@ -203,4 +216,30 @@ def successful_credit_response </JetPayResponse> EOF end + + def transcript + <<-EOF + <TerminalID>TESTTERMINAL</TerminalID> + <TransactionType>SALE</TransactionType> + <TransactionID>e23c963a1247fd7aad</TransactionID> + <CardNum>4000300020001000</CardNum> + <CardExpMonth>09</CardExpMonth> + <CardExpYear>16</CardExpYear> + <CardName>Longbob Longsen</CardName> + <CVV2>123</CVV2> + EOF + end + + def scrubbed_transcript + <<-EOF + <TerminalID>TESTTERMINAL</TerminalID> + <TransactionType>SALE</TransactionType> + <TransactionID>e23c963a1247fd7aad</TransactionID> + <CardNum>[FILTERED]</CardNum> + <CardExpMonth>09</CardExpMonth> + <CardExpYear>16</CardExpYear> + <CardName>Longbob Longsen</CardName> + <CVV2>[FILTERED]</CVV2> + EOF + end end diff --git a/test/unit/gateways/jetpay_v2_test.rb b/test/unit/gateways/jetpay_v2_test.rb new file mode 100644 index 00000000000..8129bf5eb11 --- /dev/null +++ b/test/unit/gateways/jetpay_v2_test.rb @@ -0,0 +1,308 @@ +require 'test_helper' + +class JetpayV2Test < Test::Unit::TestCase + + def setup + @gateway = JetpayV2Gateway.new(:login => 'login') + + @credit_card = credit_card + @amount = 100 + + @options = { + :device => 'spreedly', + :application => 'spreedly', + :developer_id => 'GenkID', + :billing_address => address(:country => 'US'), + :shipping_address => address(:country => 'US'), + :email => 'test@test.com', + :ip => '127.0.0.1', + :order_id => '12345' + } + end + + def test_successful_purchase + @gateway.expects(:ssl_post).returns(successful_purchase_response) + + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + + assert_equal '8afa688fd002821362;TEST97;100;KKLIHOJKKNKKHJKONJHOLHOL', response.authorization + assert_equal('TEST97', response.params['approval']) + assert response.test? + end + + def test_failed_purchase + @gateway.expects(:ssl_post).returns(failed_purchase_response) + + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_equal('7605f7c5d6e8f74deb;;100;', response.authorization) + assert response.test? + end + + def test_successful_authorize + @gateway.expects(:ssl_post).returns(successful_authorize_response) + + assert response = @gateway.authorize(@amount, @credit_card, @options) + assert_success response + + assert_equal('cbf902091334a0b1aa;TEST01;100;KKLIHOJKKNKKHJKONOHCLOIO', response.authorization) + assert_equal('TEST01', response.params['approval']) + assert response.test? + end + + def test_successful_capture + @gateway.expects(:ssl_post).returns(successful_capture_response) + + assert response = @gateway.capture(1111, '010327153017T10018;502F7B;1111', @options) + assert_success response + + assert_equal('010327153017T10018;502F6B;1111;', response.authorization) + assert_equal('502F6B', response.params['approval']) + assert response.test? + end + + def test_failed_capture + @gateway.expects(:ssl_post).returns(failed_capture_response) + + assert response = @gateway.capture(@amount, '7605f7c5d6e8f74deb', @options) + assert_failure response + assert_equal 'Transaction Not Found.', response.message + end + + def test_successful_void + @gateway.expects(:ssl_post).returns(successful_void_response) + + assert response = @gateway.void('010327153x17T10418;502F7B;500', @options) + assert_success response + + assert_equal('010327153x17T10418;502F7B;500;', response.authorization) + assert_equal('502F7B', response.params['approval']) + assert response.test? + end + + def test_failed_void + @gateway.expects(:ssl_post).returns(failed_void_response) + + assert void = @gateway.void('bogus', @options) + assert_failure void + end + + def test_successful_credit + card = credit_card('4242424242424242', :verification_value => nil) + + @gateway.expects(:ssl_post).returns(successful_credit_response) + + assert response = @gateway.credit(@amount, card, @options) + assert_success response + end + + def test_failed_credit + card = credit_card('2424242424242424', :verification_value => nil) + + @gateway.expects(:ssl_post).returns(failed_credit_response) + + assert credit = @gateway.credit(@amount, card, @options) + assert_failure credit + assert_match %r{Invalid card format}, credit.message + end + + def test_successful_refund + @gateway.expects(:ssl_post).returns(successful_credit_response) + + assert response = @gateway.refund(9900, '010327153017T10017', @options) + assert_success response + + assert_equal('010327153017T10017;002F6B;9900;', response.authorization) + assert_equal('002F6B', response.params['approval']) + assert response.test? + end + + def test_failed_refund + @gateway.expects(:ssl_post).returns(failed_void_response) + + assert refund = @gateway.refund(@amount, 'bogus', @options) + assert_failure refund + end + + def test_successful_verify + @gateway.expects(:ssl_post).returns(successful_authorize_response) + + assert verify = @gateway.verify(@credit_card, @options) + assert_success verify + end + + def test_failed_verify + card = credit_card('2424242424242424', :verification_value => nil) + + @gateway.expects(:ssl_post).returns(failed_credit_response) + + assert verify = @gateway.verify(card, @options) + assert_failure verify + assert_match %r{Invalid card format}, verify.message + end + + def test_avs_result + @gateway.expects(:ssl_post).returns(successful_purchase_response) + + response = @gateway.purchase(@amount, @credit_card, @options) + assert_equal 'D', response.avs_result['code'] + end + + def test_cvv_result + @gateway.expects(:ssl_post).returns(successful_purchase_response) + + response = @gateway.purchase(@amount, @credit_card, @options) + assert_equal 'P', response.cvv_result['code'] + end + + def test_transcript_scrubbing + assert @gateway.supports_scrubbing + assert_equal scrubbed_transcript, @gateway.scrub(transcript) + end + + def test_purchase_sends_additional_options + @gateway.expects(:ssl_post). + with(anything, regexp_matches(/<TaxAmount ExemptInd=\"false\">777<\/TaxAmount>/)). + with(anything, regexp_matches(/<UDField1>Value1<\/UDField1>/)). + with(anything, regexp_matches(/<UDField2>Value2<\/UDField2>/)). + with(anything, regexp_matches(/<UDField3>Value3<\/UDField3>/)). + returns(successful_purchase_response) + + @gateway.purchase(@amount, @credit_card, {:tax => '777', :ud_field_1 => 'Value1', :ud_field_2 => 'Value2', :ud_field_3 => 'Value3'}) + end + + private + + def successful_purchase_response + <<-EOF + <JetPayResponse Version="2.2"> + <TransactionID>8afa688fd002821362</TransactionID> + <ActionCode>000</ActionCode> + <Approval>TEST97</Approval> + <CVV2>P</CVV2> + <ResponseText>APPROVED</ResponseText> + <Token>KKLIHOJKKNKKHJKONJHOLHOL</Token> + <AddressMatch>Y</AddressMatch> + <ZipMatch>Y</ZipMatch> + <AVS>D</AVS> + </JetPayResponse> + EOF + end + + def failed_purchase_response + <<-EOF + <JetPayResponse Version="2.2"> + <TransactionID>7605f7c5d6e8f74deb</TransactionID> + <ActionCode>005</ActionCode> + <ResponseText>DECLINED</ResponseText> + </JetPayResponse> + EOF + end + + def successful_authorize_response + <<-EOF + <JetPayResponse Version="2.2"> + <TransactionID>cbf902091334a0b1aa</TransactionID> + <ActionCode>000</ActionCode> + <Approval>TEST01</Approval> + <CVV2>P</CVV2> + <ResponseText>APPROVED</ResponseText> + <Token>KKLIHOJKKNKKHJKONOHCLOIO</Token> + <AddressMatch>Y</AddressMatch> + <ZipMatch>Y</ZipMatch> + <AVS>D</AVS> + </JetPayResponse> + EOF + end + + def successful_capture_response + <<-EOF + <JetPayResponse Version="2.2"> + <TransactionID>010327153017T10018</TransactionID> + <ActionCode>000</ActionCode> + <Approval>502F6B</Approval> + <ResponseText>APPROVED</ResponseText> + </JetPayResponse> + EOF + end + + def failed_capture_response + <<-EOF + <JetPayResponse Version="2.2"> + <TransactionID>010327153017T10018</TransactionID> + <ActionCode>025</ActionCode> + <Approval>REJECT</Approval> + <ResponseText>ED</ResponseText> + </JetPayResponse> + EOF + end + + def successful_void_response + <<-EOF + <JetPayResponse Version="2.2"> + <TransactionID>010327153x17T10418</TransactionID> + <ActionCode>000</ActionCode> + <Approval>502F7B</Approval> + <ResponseText>VOID PROCESSED</ResponseText> + </JetPayResponse> + EOF + end + + def failed_void_response + <<-EOF + <JetPayResponse Version="2.2"> + <TransactionID>010327153x17T10418</TransactionID> + <ActionCode>900</ActionCode> + <ResponseText>INVALID MESSAGE TYPE</ResponseText> + </JetPayResponse> + EOF + end + + def successful_credit_response + <<-EOF + <JetPayResponse Version="2.2"> + <TransactionID>010327153017T10017</TransactionID> + <ActionCode>000</ActionCode> + <Approval>002F6B</Approval> + <ResponseText>APPROVED</ResponseText> + </JetPayResponse> + EOF + end + + def failed_credit_response + <<-EOF + <JetPayResponse Version="2.2"> + <TransactionID>010327153017T10017</TransactionID> + <ActionCode>912</ActionCode> + <ResponseText>INVALID CARD NUMBER</ResponseText> + </JetPayResponse> + EOF + end + + def transcript + <<-EOF + <TerminalID>TESTMCC3136X</TerminalID> + <TransactionType>SALE</TransactionType> + <TransactionID>e23c963a1247fd7aad</TransactionID> + <CardNum>4000300020001000</CardNum> + <CardExpMonth>09</CardExpMonth> + <CardExpYear>16</CardExpYear> + <CardName>Longbob Longsen</CardName> + <CVV2>123</CVV2> + EOF + end + + def scrubbed_transcript + <<-EOF + <TerminalID>TESTMCC3136X</TerminalID> + <TransactionType>SALE</TransactionType> + <TransactionID>e23c963a1247fd7aad</TransactionID> + <CardNum>[FILTERED]</CardNum> + <CardExpMonth>09</CardExpMonth> + <CardExpYear>16</CardExpYear> + <CardName>Longbob Longsen</CardName> + <CVV2>[FILTERED]</CVV2> + EOF + end +end diff --git a/test/unit/gateways/komoju_test.rb b/test/unit/gateways/komoju_test.rb new file mode 100644 index 00000000000..dc67d18686c --- /dev/null +++ b/test/unit/gateways/komoju_test.rb @@ -0,0 +1,146 @@ +require 'test_helper' + +class KomojuTest < Test::Unit::TestCase + def setup + @gateway = KomojuGateway.new(:login => 'login') + + @credit_card = credit_card + @amount = 100 + + @options = { + :order_id => '1', + :description => 'Store Purchase', + :tax => '10', + :ip => '192.168.0.1', + :email => 'valid@email.com', + :browser_language => 'en', + :browser_user_agent => 'user_agent' + } + end + + def test_successful_credit_card_purchase + successful_response = successful_credit_card_purchase_response + @gateway.expects(:ssl_post).returns(JSON.generate(successful_response)) + + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + + assert_equal successful_response['id'], response.authorization + assert response.test? + end + + def test_failed_purchase + raw_response = mock + raw_response.expects(:body).returns(JSON.generate(failed_purchase_response)) + exception = ActiveMerchant::ResponseError.new(raw_response) + + @gateway.expects(:ssl_post).raises(exception) + + response = @gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_equal 'missing_parameter', response.error_code + assert response.test? + end + + def test_detected_fraud + raw_response = mock + raw_response.expects(:body).returns(JSON.generate(detected_fraud_response)) + exception = ActiveMerchant::ResponseError.new(raw_response) + + @gateway.expects(:ssl_post).raises(exception) + + response = @gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_equal 'fraudulent', response.error_code + assert response.test? + end + + def test_successful_credit_card_refund + successful_response = successful_credit_card_refund_response + @gateway.expects(:ssl_post).returns(JSON.generate(successful_response)) + + response = @gateway.refund(@amount, '7e8c55a54256ce23e387f2838c', @options) + assert_success response + + assert_equal successful_response['id'], response.authorization + assert response.test? + end + + private + + def successful_credit_card_purchase_response + { + 'id' => '7e8c55a54256ce23e387f2838c', + 'resource' => 'payment', + 'status' => 'captured', + 'amount' => 100, + 'tax' => 8, + 'payment_deadline' => nil, + 'payment_details' => { + 'type' => 'credit_card', + 'brand' => 'visa', + 'last_four_digits' => '2220', + 'month' => 9, + 'year' => 2016 + }, + 'payment_method_fee' => 0, + 'total' => 108, + 'currency' => 'JPY', + 'description' => 'Store Purchase', + 'subscription' => nil, + 'captured_at' => '2015-03-20T04:51:48Z', + 'metadata' => { + 'order_id' => '262f2a92-542c-4b4e-a68b-5b6d54a438a8' + }, + 'created_at' => '2015-03-20T04:51:48Z' + } + end + + def successful_credit_card_refund_response + { + 'id' => '7e8c55a54256ce23e387f2838c', + 'resource' => 'payment', + 'status' => 'refunded', + 'amount' => 100, + 'tax' => 8, + 'payment_deadline' => nil, + 'payment_details' => { + 'type' => 'credit_card', + 'brand' => 'visa', + 'last_four_digits' => '2220', + 'month' => 9, + 'year' => 2016 + }, + 'payment_method_fee' => 0, + 'total' => 108, + 'currency' => 'JPY', + 'description' => 'Store Purchase', + 'subscription' => nil, + 'captured_at' => nil, + 'metadata' => { + 'order_id' => '262f2a92-542c-4b4e-a68b-5b6d54a438a8' + }, + 'created_at' => '2015-03-20T04:51:48Z' + } + end + + def failed_purchase_response + { + 'error' => { + 'code' => 'missing_parameter', + 'message' => 'A required parameter (currency) is missing', + 'param' => 'currency' + } + } + end + + def detected_fraud_response + { + 'error' => { + 'code' => 'fraudulent', + 'message' => 'The payment could not be completed.', + 'param' => nil + } + } + end +end diff --git a/test/unit/gateways/kushki_test.rb b/test/unit/gateways/kushki_test.rb new file mode 100644 index 00000000000..861d82f8354 --- /dev/null +++ b/test/unit/gateways/kushki_test.rb @@ -0,0 +1,383 @@ +require 'test_helper' + +class KushkiTest < Test::Unit::TestCase + include CommStub + + def setup + @gateway = KushkiGateway.new(public_merchant_id: '_', private_merchant_id: '_') + @amount = 100 + @credit_card = credit_card + end + + def test_successful_purchase + @gateway.expects(:ssl_post).returns(successful_charge_response) + @gateway.expects(:ssl_post).returns(successful_token_response) + + response = @gateway.purchase(@amount, @credit_card) + assert_success response + assert_equal 'Succeeded', response.message + assert_match %r(^\d+$), response.authorization + assert response.test? + end + + def test_successful_purchase_with_options + options = { + currency: 'USD', + amount: { + subtotal_iva_0: '4.95', + subtotal_iva: '10', + iva: '1.54', + ice: '3.50' + } + } + + amount = 100 * ( + options[:amount][:subtotal_iva_0].to_f + + options[:amount][:subtotal_iva].to_f + + options[:amount][:iva].to_f + + options[:amount][:ice].to_f + ) + + @gateway.expects(:ssl_post).returns(successful_charge_response) + @gateway.expects(:ssl_post).returns(successful_token_response) + + response = @gateway.purchase(amount, @credit_card, options) + assert_success response + assert_equal 'Succeeded', response.message + assert_match %r(^\d+$), response.authorization + assert response.test? + end + + def test_taxes_are_excluded_when_not_provided + options = { + currency: 'COP', + amount: { + subtotal_iva_0: '4.95', + subtotal_iva: '10', + iva: '1.54', + ice: '3.50' + } + } + + amount = 100 * ( + options[:amount][:subtotal_iva_0].to_f + + options[:amount][:subtotal_iva].to_f + + options[:amount][:iva].to_f + + options[:amount][:ice].to_f + ) + + response = stub_comms do + @gateway.purchase(amount, @credit_card, options) + end.check_request do |endpoint, data, headers| + if /charges/ =~ endpoint + assert_no_match %r{extraTaxes}, data + end + end.respond_with(successful_charge_response, successful_token_response) + + assert_success response + end + + def test_partial_taxes_do_not_error + options = { + currency: 'COP', + amount: { + subtotal_iva_0: '4.95', + subtotal_iva: '10', + iva: '1.54', + ice: '3.50', + extra_taxes: { + tasa_aeroportuaria: 0.2, + iac: 0.4 + } + } + } + + amount = 100 * ( + options[:amount][:subtotal_iva_0].to_f + + options[:amount][:subtotal_iva].to_f + + options[:amount][:iva].to_f + + options[:amount][:ice].to_f + ) + + response = stub_comms do + @gateway.purchase(amount, @credit_card, options) + end.check_request do |endpoint, data, headers| + if /charges/ =~ endpoint + assert_match %r{extraTaxes}, data + assert_no_match %r{propina}, data + assert_match %r{iac}, data + end + end.respond_with(successful_charge_response, successful_token_response) + + assert_success response + end + + def test_taxes_are_included_when_provided + options = { + currency: 'COP', + amount: { + subtotal_iva_0: '4.95', + subtotal_iva: '10', + iva: '1.54', + ice: '3.50', + extra_taxes: { + propina: 0.1, + tasa_aeroportuaria: 0.2, + agencia_de_viaje: 0.3, + iac: 0.4 + } + } + } + + amount = 100 * ( + options[:amount][:subtotal_iva_0].to_f + + options[:amount][:subtotal_iva].to_f + + options[:amount][:iva].to_f + + options[:amount][:ice].to_f + ) + + response = stub_comms do + @gateway.purchase(amount, @credit_card, options) + end.check_request do |endpoint, data, headers| + if /charges/ =~ endpoint + assert_match %r{extraTaxes}, data + assert_match %r{propina}, data + end + end.respond_with(successful_charge_response, successful_token_response) + + assert_success response + end + + def test_failed_purchase + options = { + amount: { + subtotal_iva: '200' + } + } + + @gateway.expects(:ssl_post).returns(failed_charge_response) + @gateway.expects(:ssl_post).returns(successful_token_response) + + response = @gateway.purchase(@amount, @credit_card, options) + assert_failure response + assert_equal 'Monto de la transacción es diferente al monto de la venta inicial', response.message + assert_equal '220', response.error_code + end + + def test_successful_refund + @gateway.expects(:ssl_post).returns(successful_charge_response) + @gateway.expects(:ssl_post).returns(successful_token_response) + + purchase = @gateway.purchase(@amount, @credit_card) + assert_success purchase + + @gateway.expects(:ssl_request).returns(successful_refund_response) + + assert refund = @gateway.refund(@amount, purchase.authorization) + assert_success refund + assert_equal 'Succeeded', refund.message + end + + def test_failed_refund + @gateway.expects(:ssl_post).returns(successful_charge_response) + @gateway.expects(:ssl_post).returns(successful_token_response) + + purchase = @gateway.purchase(@amount, @credit_card) + assert_success purchase + + @gateway.expects(:ssl_request).returns(failed_refund_response) + + assert refund = @gateway.refund(@amount, purchase.authorization) + assert_failure refund + assert_equal 'Ticket number inválido', refund.message + assert_equal 'K010', refund.error_code + end + + def test_successful_void + @gateway.expects(:ssl_post).returns(successful_charge_response) + @gateway.expects(:ssl_post).returns(successful_token_response) + + purchase = @gateway.purchase(@amount, @credit_card) + assert_success purchase + + @gateway.expects(:ssl_request).returns(successful_void_response) + + assert void = @gateway.void(purchase.authorization) + assert_success void + assert_equal 'Succeeded', void.message + end + + def test_failed_void + @gateway.expects(:ssl_request).returns(failed_void_response) + + response = @gateway.void('000') + assert_failure response + assert_equal 'Tipo de moneda no válida', response.message + assert_equal '205', response.error_code + end + + def test_scrub + assert @gateway.supports_scrubbing? + assert_equal @gateway.scrub(pre_scrubbed), post_scrubbed + end + + private + + def pre_scrubbed + %q( + opening connection to api-uat.kushkipagos.com:443... + opened + starting SSL for api-uat.kushkipagos.com:443... + SSL established + <- "POST /v1/tokens HTTP/1.1\r\nContent-Type: application/json\r\nPublic-Merchant-Id: 10000001837148605646147925549896\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: api-uat.kushkipagos.com\r\nContent-Length: 166\r\n\r\n" + <- "{\"totalAmount\":1.0,\"currency\":\"USD\",\"isDeferred\":false,\"card\":{\"number\":\"4000100011112224\",\"name\":\"Longbob Longsen\",\"cvv\":\"777\",\"expiryMonth\":\"09\",\"expiryYear\":\"18\"}}" + -> "HTTP/1.1 201 Created\r\n" + -> "Date: Tue, 07 Feb 2017 14:53:00 GMT\r\n" + -> "Server: Apache/2.4.18 (Unix) OpenSSL/1.0.1s Resin/4.0.40\r\n" + -> "Access-Control-Allow-Origin: *\r\n" + -> "Access-Control-Allow-Methods: OPTIONS\r\n" + -> "Access-Control-Max-Age: 1000\r\n" + -> "Access-Control-Allow-Headers: x-requested-with, Content-Type, origin, authorization, accept, client-security-token\r\n" + -> "Content-Length: 44\r\n" + -> "Content-Type: application/json\r\n" + -> "Connection: close\r\n" + -> "\r\n" + reading 44 bytes... + -> "" + -> "{\"token\":\"BeWb3z100000UXANj0018371b8iPZHYq\"}" + read 44 bytes + Conn close + opening connection to api-uat.kushkipagos.com:443... + opened + starting SSL for api-uat.kushkipagos.com:443... + SSL established + <- "POST /v1/charges HTTP/1.1\r\nContent-Type: application/json\r\nPrivate-Merchant-Id: 10000001837138390991147925549896\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: api-uat.kushkipagos.com\r\nContent-Length: 123\r\n\r\n" + <- "{\"token\":\"BeWb3z100000UXANj0018371b8iPZHYq\",\"amount\":{\"currency\":\"USD\",\"subtotalIva\":1.0,\"iva\":0,\"subtotalIva0\":0,\"ice\":0}}" + -> "HTTP/1.1 201 Created\r\n" + -> "Date: Tue, 07 Feb 2017 14:53:02 GMT\r\n" + -> "Server: Apache/2.4.18 (Unix) OpenSSL/1.0.1s Resin/4.0.40\r\n" + -> "Access-Control-Allow-Origin: *\r\n" + -> "Access-Control-Allow-Methods: OPTIONS\r\n" + -> "Access-Control-Max-Age: 1000\r\n" + -> "Access-Control-Allow-Headers: x-requested-with, Content-Type, origin, authorization, accept, client-security-token\r\n" + -> "Content-Length: 37\r\n" + -> "Content-Type: application/json\r\n" + -> "Connection: close\r\n" + -> "\r\n" + reading 37 bytes... + -> "" + -> "{\"ticketNumber\":\"170383559069100036\"}" + read 37 bytes + Conn close + ) + end + + def post_scrubbed + %q( + opening connection to api-uat.kushkipagos.com:443... + opened + starting SSL for api-uat.kushkipagos.com:443... + SSL established + <- "POST /v1/tokens HTTP/1.1\r\nContent-Type: application/json\r\nPublic-Merchant-Id: 10000001837148605646147925549896\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: api-uat.kushkipagos.com\r\nContent-Length: 166\r\n\r\n" + <- "{\"totalAmount\":1.0,\"currency\":\"USD\",\"isDeferred\":false,\"card\":{\"number\":\"[FILTERED]\",\"name\":\"Longbob Longsen\",\"cvv\":\"[FILTERED]\",\"expiryMonth\":\"09\",\"expiryYear\":\"18\"}}" + -> "HTTP/1.1 201 Created\r\n" + -> "Date: Tue, 07 Feb 2017 14:53:00 GMT\r\n" + -> "Server: Apache/2.4.18 (Unix) OpenSSL/1.0.1s Resin/4.0.40\r\n" + -> "Access-Control-Allow-Origin: *\r\n" + -> "Access-Control-Allow-Methods: OPTIONS\r\n" + -> "Access-Control-Max-Age: 1000\r\n" + -> "Access-Control-Allow-Headers: x-requested-with, Content-Type, origin, authorization, accept, client-security-token\r\n" + -> "Content-Length: 44\r\n" + -> "Content-Type: application/json\r\n" + -> "Connection: close\r\n" + -> "\r\n" + reading 44 bytes... + -> "" + -> "{\"token\":\"BeWb3z100000UXANj0018371b8iPZHYq\"}" + read 44 bytes + Conn close + opening connection to api-uat.kushkipagos.com:443... + opened + starting SSL for api-uat.kushkipagos.com:443... + SSL established + <- "POST /v1/charges HTTP/1.1\r\nContent-Type: application/json\r\nPrivate-Merchant-Id: [FILTERED]\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: api-uat.kushkipagos.com\r\nContent-Length: 123\r\n\r\n" + <- "{\"token\":\"BeWb3z100000UXANj0018371b8iPZHYq\",\"amount\":{\"currency\":\"USD\",\"subtotalIva\":1.0,\"iva\":0,\"subtotalIva0\":0,\"ice\":0}}" + -> "HTTP/1.1 201 Created\r\n" + -> "Date: Tue, 07 Feb 2017 14:53:02 GMT\r\n" + -> "Server: Apache/2.4.18 (Unix) OpenSSL/1.0.1s Resin/4.0.40\r\n" + -> "Access-Control-Allow-Origin: *\r\n" + -> "Access-Control-Allow-Methods: OPTIONS\r\n" + -> "Access-Control-Max-Age: 1000\r\n" + -> "Access-Control-Allow-Headers: x-requested-with, Content-Type, origin, authorization, accept, client-security-token\r\n" + -> "Content-Length: 37\r\n" + -> "Content-Type: application/json\r\n" + -> "Connection: close\r\n" + -> "\r\n" + reading 37 bytes... + -> "" + -> "{\"ticketNumber\":\"170383559069100036\"}" + read 37 bytes + Conn close + ) + end + + def successful_token_response + %( + { + "token":"Rcp7Un10000070Jwa5018371WVtD0ECx" + } + ) + end + + def successful_charge_response + %( + { + "ticketNumber":"170384522771700083" + } + ) + end + + def failed_charge_response + %( + { + "code":"220", + "message":"Monto de la transacción es diferente al monto de la venta inicial" + } + ) + end + + def successful_refund_response + %( + { + "code": "K000", + "message": "El reembolso solicitado se realizó con éxito." + } + ) + end + + def failed_refund_response + %( + { + "code": "K010", + "message": "Ticket number inválido" + } + ) + end + + def successful_void_response + %( + { + "ticketNumber":"170384634023500095" + } + ) + end + + def failed_void_response + %( + { + "code":"205", + "message":"Tipo de moneda no válida" + } + ) + end +end diff --git a/test/unit/gateways/latitude19_test.rb b/test/unit/gateways/latitude19_test.rb new file mode 100644 index 00000000000..75f7b75cba8 --- /dev/null +++ b/test/unit/gateways/latitude19_test.rb @@ -0,0 +1,590 @@ +require 'test_helper' + +class Latitude19Test < Test::Unit::TestCase + def setup + @gateway = Latitude19Gateway.new(fixtures(:latitude19)) + + @amount = 100 + @credit_card = credit_card('4000100011112224', verification_value: '747') + @declined_card = credit_card('0000000000000000') + + @options = { + order_id: generate_unique_id, + billing_address: address + } + end + + def test_successful_purchase + @gateway.expects(:ssl_post).returns(successful_purchase_response) + @gateway.expects(:ssl_post).returns(successful_token_response) + @gateway.expects(:ssl_post).returns(successful_session_response) + + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal 'Approved', response.message + assert response.test? + end + + # def test_failed_purchase + # end + + def test_successful_authorize_and_capture + @gateway.expects(:ssl_post).returns(successful_authorize_response) + @gateway.expects(:ssl_post).returns(successful_token_response) + @gateway.expects(:ssl_post).returns(successful_session_response) + + response = @gateway.authorize(@amount, @credit_card, @options) + assert_success response + assert_equal 'Approved', response.message + assert_match %r(^auth\|\w+$), response.authorization + + @gateway.expects(:ssl_post).returns(successful_capture_response) + + capture = @gateway.capture(@amount, response.authorization, @options) + assert_success capture + assert_equal 'Approved', capture.message + end + + # def test_failed_authorize + # end + + def test_failed_capture + @gateway.expects(:ssl_post).returns(failed_capture_response) + + authorization = 'auth' + '|' + SecureRandom.hex(6) + response = @gateway.capture(@amount, authorization, @options) + assert_failure response + assert_equal 'Not submitted', response.message + assert_equal '400', response.error_code + end + + def test_successful_void + @gateway.expects(:ssl_post).returns(successful_authorize_response) + @gateway.expects(:ssl_post).returns(successful_token_response) + @gateway.expects(:ssl_post).returns(successful_session_response) + + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + @gateway.expects(:ssl_post).returns(successful_reversal_response) + + void = @gateway.void(auth.authorization, @options) + assert_success void + assert_equal 'Approved', void.message + + # response = @gateway.authorize(@amount, @credit_card, @options) + # assert_success response + # assert_equal "pgwResponseCodeDescription|Approved|responseText|00 -- APPROVAL|processorResponseCode|00", response.message + + # capture = @gateway.capture(@amount, response.authorization, @options) + # assert_success capture + # assert_equal "pgwResponseCodeDescription|Approved|responseText|00 -- APPROVAL|processorResponseCode|00", capture.message + + # void = @gateway.void(capture.authorization, @options) + # assert_success void + # assert_equal "pgwResponseCodeDescription|Approved|responseText|00 -- APPROVAL|processorResponseCode|00", void.message + + @gateway.expects(:ssl_post).returns(successful_purchase_response) + @gateway.expects(:ssl_post).returns(successful_token_response) + @gateway.expects(:ssl_post).returns(successful_session_response) + + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + + @gateway.expects(:ssl_post).returns(successful_void_response) + + void = @gateway.void(purchase.authorization, @options) + assert_success void + assert_equal 'Approved', void.message + end + + def test_failed_void + @gateway.expects(:ssl_post).returns(successful_authorize_response) + @gateway.expects(:ssl_post).returns(successful_token_response) + @gateway.expects(:ssl_post).returns(successful_session_response) + + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + @gateway.expects(:ssl_post).returns(failed_reversal_response) + + authorization = auth.authorization[0..9] + 'XX' + response = @gateway.void(authorization, @options) + + assert_failure response + assert_equal 'Not submitted', response.message + assert_equal '400', response.error_code + end + + def test_successful_credit + @gateway.expects(:ssl_post).returns(successful_credit_response) + @gateway.expects(:ssl_post).returns(successful_token_response) + @gateway.expects(:ssl_post).returns(successful_session_response) + + response = @gateway.credit(@amount, @credit_card, @options) + assert_success response + assert_equal 'Approved', response.message + end + + # def test_failed_credit + # end + + def test_successful_verify + @gateway.expects(:ssl_post).returns(successful_verify_response) + @gateway.expects(:ssl_post).returns(successful_token_response) + @gateway.expects(:ssl_post).returns(successful_session_response) + + response = @gateway.verify(@credit_card, @options) + assert_success response + assert_equal 'Approved', response.message + end + + # def test_failed_verify + # end + + def test_successful_store_and_purchase + @gateway.expects(:ssl_post).returns(successful_verify_response) + @gateway.expects(:ssl_post).returns(successful_token_response) + @gateway.expects(:ssl_post).returns(successful_session_response) + + store = @gateway.store(@credit_card, @options) + assert_success store + assert_equal 'Approved', store.message + + @gateway.expects(:ssl_post).returns(successful_purchase_response) + + purchase = @gateway.purchase(@amount, store.authorization, @options) + assert_success purchase + assert_equal 'Approved', purchase.message + end + + def test_scrub + assert @gateway.supports_scrubbing? + assert_equal @gateway.scrub(pre_scrubbed), post_scrubbed + end + + private + + def pre_scrubbed + %q( + opening connection to gateway-sb.l19tech.com:443... + opened + starting SSL for gateway-sb.l19tech.com:443... + SSL established + <- "POST /payments/session HTTP/1.1\r\nContent-Type: application/json\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: gateway-sb.l19tech.com\r\nContent-Length: 319\r\n\r\n" + <- "{\"method\":\"getSession\",\"id\":\"c8829a018d77c5ecf4f68f307f6ab640\",\"params\":[{\"pgwAccountNumber\":\"03022016\",\"pgwConfigurationId\":\"380835424362\",\"requestTimeStamp\":\"20160407141623\",\"pgwHMAC\":\"e5a4f078d9cde4e520ffb8b073365deecb43e2f0accc44d26bccdfe47abdf52479fcd15098d9c741b22520d6bbab1f0107a1674a350fe387774896044c831758\"}]}" + -> "HTTP/1.1 200 OK\r\n" + -> "Server: nginx/1.0.15\r\n" + -> "Date: Thu, 07 Apr 2016 14:16:24 GMT\r\n" + -> "Content-Type: text/html; charset=UTF-8\r\n" + -> "Transfer-Encoding: chunked\r\n" + -> "Connection: close\r\n" + -> "Access-Control-Allow-Origin: *\r\n" + -> "Access-Control-Allow-Methods: POST, HEAD, OPTIONS\r\n" + -> "\r\n" + -> "a2\r\n" + reading 162 bytes... + -> "{\"error\": null, \"result\": {\"sessionId\": \"000008HH2RNN2PWBSW20160407141623\", \"version\": \"1.0\", \"lastActionSucceeded\": 1}, \"id\": \"c8829a018d77c5ecf4f68f307f6ab640\"}" + read 162 bytes + reading 2 bytes... + -> "\r\n" + read 2 bytes + -> "0\r\n" + -> "\r\n" + Conn close + opening connection to gateway-sb.l19tech.com:443... + opened + starting SSL for gateway-sb.l19tech.com:443... + SSL established + <- "POST /payments/token HTTP/1.1\r\nContent-Type: application/json\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: gateway-sb.l19tech.com\r\nContent-Length: 153\r\n\r\n" + <- "{\"method\":\"tokenize\",\"id\":\"f077295af9de092b2b1867d89c74fd4d\",\"params\":[{\"sessionId\":\"000008HH2RNN2PWBSW20160407141623\",\"cardNumber\":\"4000100011112224\"}]}" + -> "HTTP/1.1 200 OK\r\n" + -> "Server: nginx/1.0.15\r\n" + -> "Date: Thu, 07 Apr 2016 14:16:26 GMT\r\n" + -> "Content-Type: text/html; charset=UTF-8\r\n" + -> "Transfer-Encoding: chunked\r\n" + -> "Connection: close\r\n" + -> "Access-Control-Allow-Origin: *\r\n" + -> "Access-Control-Allow-Methods: POST, HEAD, OPTIONS\r\n" + -> "\r\n" + -> "94\r\n" + reading 148 bytes... + -> "{\"error\": null, \"result\": {\"version\": \"1.0\", \"lastActionSucceeded\": 1, \"sessionToken\": \"d133b6d9b992443\"}, \"id\": \"f077295af9de092b2b1867d89c74fd4d\"}" + read 148 bytes + reading 2 bytes... + -> "\r\n" + read 2 bytes + -> "0\r\n" + -> "\r\n" + Conn close + opening connection to gateway-sb.l19tech.com:443... + opened + starting SSL for gateway-sb.l19tech.com:443... + SSL established + <- "POST /payments/v1/ HTTP/1.1\r\nContent-Type: application/json\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: gateway-sb.l19tech.com\r\nContent-Length: 621\r\n\r\n" + <- "{\"method\":\"sale\",\"id\":\"db4f39966113020eab9c50ec626be3ce\",\"params\":[{\"sessionToken\":\"d133b6d9b992443\",\"amount\":\"100\",\"orderNumber\":\"6b122930383de3e1e355c48f863e002c\",\"transactionClass\":\"eCommerce\",\"cardExp\":\"09/17\",\"cardType\":\"VI\",\"cvv\":\"123\",\"firstName\":\"Longbob\",\"lastName\":\"Longsen\",\"address1\":\"456 My Street\",\"address2\":\"Apt 1\",\"city\":\"Ottawa\",\"stateProvince\":\"ON\",\"zipPostalCode\":\"K1C2N6\",\"countryCode\":\"CA\",\"pgwAccountNumber\":\"03022016\",\"pgwConfigurationId\":\"380835424362\",\"pgwHMAC\":\"f3ebc73f54474c253bbafb88330f9db6bc3331544140d70a928dd264703653094187b9bde70d339dd680d612291e4981f92dc4e69d7f264d3fcd8a9cb09bca43\"}]}" + -> "HTTP/1.1 200 OK\r\n" + -> "Server: nginx/1.0.15\r\n" + -> "Date: Thu, 07 Apr 2016 14:16:29 GMT\r\n" + -> "Content-Type: text/html; charset=UTF-8\r\n" + -> "Transfer-Encoding: chunked\r\n" + -> "Connection: close\r\n" + -> "Access-Control-Allow-Origin: *\r\n" + -> "Access-Control-Allow-Methods: POST, HEAD, OPTIONS\r\n" + -> "\r\n" + -> "1e0\r\n" + reading 480 bytes... + -> "{\"error\": null, \"result\": {\"accountToken\": \"d133b6d9b992443\", \"responseText\": \"00 -- APPROVAL\", \"cvvResponse\": \"M\", \"cardLevelResponse\": \"A\", \"authCode\": \"AU1C1Q\", \"avsResponse\": \"Y\", \"threeDSecureResponse\": \"\", \"pgwTID\": \"00002KUZ6WY7\", \"last4\": \"2224\", \"cardType\": \"VI\", \"version\": \"1.0\", \"authDate\": \"\", \"processor\": {\"TID\": \"00002KUZ6WY7\", \"orderNumber\": \"\", \"responseCode\": \"00\"}, \"lastActionSucceeded\": 1, \"pgwResponseCode\": \"100\"}, \"id\": \"db4f39966113020eab9c50ec626be3ce\"}" + read 480 bytes + reading 2 bytes... + -> "\r\n" + read 2 bytes + -> "0\r\n" + -> "\r\n" + Conn close + ) + end + + def post_scrubbed + %q( + opening connection to gateway-sb.l19tech.com:443... + opened + starting SSL for gateway-sb.l19tech.com:443... + SSL established + <- "POST /payments/session HTTP/1.1\r\nContent-Type: application/json\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: gateway-sb.l19tech.com\r\nContent-Length: 319\r\n\r\n" + <- "{\"method\":\"getSession\",\"id\":\"c8829a018d77c5ecf4f68f307f6ab640\",\"params\":[{\"pgwAccountNumber\":\"03022016\",\"pgwConfigurationId\":\"380835424362\",\"requestTimeStamp\":\"20160407141623\",\"pgwHMAC\":\"e5a4f078d9cde4e520ffb8b073365deecb43e2f0accc44d26bccdfe47abdf52479fcd15098d9c741b22520d6bbab1f0107a1674a350fe387774896044c831758\"}]}" + -> "HTTP/1.1 200 OK\r\n" + -> "Server: nginx/1.0.15\r\n" + -> "Date: Thu, 07 Apr 2016 14:16:24 GMT\r\n" + -> "Content-Type: text/html; charset=UTF-8\r\n" + -> "Transfer-Encoding: chunked\r\n" + -> "Connection: close\r\n" + -> "Access-Control-Allow-Origin: *\r\n" + -> "Access-Control-Allow-Methods: POST, HEAD, OPTIONS\r\n" + -> "\r\n" + -> "a2\r\n" + reading 162 bytes... + -> "{\"error\": null, \"result\": {\"sessionId\": \"000008HH2RNN2PWBSW20160407141623\", \"version\": \"1.0\", \"lastActionSucceeded\": 1}, \"id\": \"c8829a018d77c5ecf4f68f307f6ab640\"}" + read 162 bytes + reading 2 bytes... + -> "\r\n" + read 2 bytes + -> "0\r\n" + -> "\r\n" + Conn close + opening connection to gateway-sb.l19tech.com:443... + opened + starting SSL for gateway-sb.l19tech.com:443... + SSL established + <- "POST /payments/token HTTP/1.1\r\nContent-Type: application/json\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: gateway-sb.l19tech.com\r\nContent-Length: 153\r\n\r\n" + <- "{\"method\":\"tokenize\",\"id\":\"f077295af9de092b2b1867d89c74fd4d\",\"params\":[{\"sessionId\":\"000008HH2RNN2PWBSW20160407141623\",\"cardNumber\":\"[FILTERED]\"}]}" + -> "HTTP/1.1 200 OK\r\n" + -> "Server: nginx/1.0.15\r\n" + -> "Date: Thu, 07 Apr 2016 14:16:26 GMT\r\n" + -> "Content-Type: text/html; charset=UTF-8\r\n" + -> "Transfer-Encoding: chunked\r\n" + -> "Connection: close\r\n" + -> "Access-Control-Allow-Origin: *\r\n" + -> "Access-Control-Allow-Methods: POST, HEAD, OPTIONS\r\n" + -> "\r\n" + -> "94\r\n" + reading 148 bytes... + -> "{\"error\": null, \"result\": {\"version\": \"1.0\", \"lastActionSucceeded\": 1, \"sessionToken\": \"d133b6d9b992443\"}, \"id\": \"f077295af9de092b2b1867d89c74fd4d\"}" + read 148 bytes + reading 2 bytes... + -> "\r\n" + read 2 bytes + -> "0\r\n" + -> "\r\n" + Conn close + opening connection to gateway-sb.l19tech.com:443... + opened + starting SSL for gateway-sb.l19tech.com:443... + SSL established + <- "POST /payments/v1/ HTTP/1.1\r\nContent-Type: application/json\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: gateway-sb.l19tech.com\r\nContent-Length: 621\r\n\r\n" + <- "{\"method\":\"sale\",\"id\":\"db4f39966113020eab9c50ec626be3ce\",\"params\":[{\"sessionToken\":\"d133b6d9b992443\",\"amount\":\"100\",\"orderNumber\":\"6b122930383de3e1e355c48f863e002c\",\"transactionClass\":\"eCommerce\",\"cardExp\":\"09/17\",\"cardType\":\"VI\",\"cvv\":\"[FILTERED]\",\"firstName\":\"Longbob\",\"lastName\":\"Longsen\",\"address1\":\"456 My Street\",\"address2\":\"Apt 1\",\"city\":\"Ottawa\",\"stateProvince\":\"ON\",\"zipPostalCode\":\"K1C2N6\",\"countryCode\":\"CA\",\"pgwAccountNumber\":\"03022016\",\"pgwConfigurationId\":\"380835424362\",\"pgwHMAC\":\"f3ebc73f54474c253bbafb88330f9db6bc3331544140d70a928dd264703653094187b9bde70d339dd680d612291e4981f92dc4e69d7f264d3fcd8a9cb09bca43\"}]}" + -> "HTTP/1.1 200 OK\r\n" + -> "Server: nginx/1.0.15\r\n" + -> "Date: Thu, 07 Apr 2016 14:16:29 GMT\r\n" + -> "Content-Type: text/html; charset=UTF-8\r\n" + -> "Transfer-Encoding: chunked\r\n" + -> "Connection: close\r\n" + -> "Access-Control-Allow-Origin: *\r\n" + -> "Access-Control-Allow-Methods: POST, HEAD, OPTIONS\r\n" + -> "\r\n" + -> "1e0\r\n" + reading 480 bytes... + -> "{\"error\": null, \"result\": {\"accountToken\": \"d133b6d9b992443\", \"responseText\": \"00 -- APPROVAL\", \"cvvResponse\": \"M\", \"cardLevelResponse\": \"A\", \"authCode\": \"AU1C1Q\", \"avsResponse\": \"Y\", \"threeDSecureResponse\": \"\", \"pgwTID\": \"00002KUZ6WY7\", \"last4\": \"2224\", \"cardType\": \"VI\", \"version\": \"1.0\", \"authDate\": \"\", \"processor\": {\"TID\": \"00002KUZ6WY7\", \"orderNumber\": \"\", \"responseCode\": \"00\"}, \"lastActionSucceeded\": 1, \"pgwResponseCode\": \"100\"}, \"id\": \"db4f39966113020eab9c50ec626be3ce\"}" + read 480 bytes + reading 2 bytes... + -> "\r\n" + read 2 bytes + -> "0\r\n" + -> "\r\n" + Conn close + ) + end + + def successful_session_response + <<-RESPONSE + { + "error": null, + "result": { + "sessionId": "000008HH2RO24UPJ3220160407195951", + "version": "1.0", + "lastActionSucceeded": 1 + }, + "id": "578b19e6ab40636236c5613e9530116a" + } + RESPONSE + end + + def successful_token_response + <<-RESPONSE + { + "error": null, + "result": { + "version": "1.0", + "lastActionSucceeded": 1, + "sessionToken": "cf239e74c6c64ed" + }, + "id": "5f3290299575e1ca1075648de677c69f" + } + RESPONSE + end + + def successful_purchase_response + <<-RESPONSE + { + "error": null, + "result": { + "accountToken": "cf239e74c6c64ed", + "responseText": "00 -- APPROVAL", + "cvvResponse": "M", + "cardLevelResponse": "A", + "authCode": "AUPZ1U", + "avsResponse": "Y", + "threeDSecureResponse": "", + "pgwTID": "00002KUZCJD1", + "last4": "2224", + "cardType": "VI", + "version": "1.0", + "authDate": "", + "pgwResponseCode": "100", + "lastActionSucceeded": 1, + "processor": { + "TID": "00002KUZCJD1", + "orderNumber": "", + "responseCode": "00" + } + }, + "id": "b6bd0e612f7e1090130ed3ca76ded99d" + } + RESPONSE + end + + def failed_purchase_response + end + + def successful_authorize_response + <<-RESPONSE + { + "error": null, + "result": { + "accountToken": "49763642056e4e3", + "responseText": "00 -- APPROVAL", + "cvvResponse": "M", + "cardLevelResponse": "A", + "authCode": "AUNAIA", + "avsResponse": "Y", + "threeDSecureResponse": "", + "pgwTID": "00002KUZH81E", + "last4": "2224", + "cardType": "VI", + "version": "1.0", + "authDate": "", + "processor": { + "TID": "00002KUZH81E", + "orderNumber": "", + "responseCode": "00" + }, + "lastActionSucceeded": 1, + "pgwResponseCode": "100" + }, + "id": "7d8dbf1603f86aab09730c0863eab073" + } + RESPONSE + end + + def failed_authorize_response + end + + def successful_capture_response + <<-RESPONSE + { + "error": null, + "result": { + "accountToken": "49763642056e4e3", + "responseText": "00 -- APPROVAL", + "pgwTID": "00002KUZH81E", + "last4": "2224", + "cardType": "VI", + "version": "1.0", + "processor": { + "TID": "00002KUZH81E", + "orderNumber": "", + "responseCode": "00" + }, + "lastActionSucceeded": 1, + "pgwResponseCode": "100" + }, + "id": "63969448557de48aee071734d8721e94" + } + RESPONSE + end + + def failed_capture_response + <<-RESPONSE + { + "error": null, + "result": { + "accountToken": "", + "responseText": "CNR -- Missing required token.", + "pgwTID": "ba479cac02ae", + "last4": "", + "cardType": "UK", + "version": "1.0", + "processor": { + "TID": "ba479cac02ae", + "orderNumber": "", + "responseCode": "" + }, + "lastActionSucceeded": 0, + "pgwResponseCode": "400" + }, + "id": "bad12bcb3992dc80f1fdee8de7787b17" + } + RESPONSE + end + + def successful_reversal_response + <<-RESPONSE + { + "error": null, + "result": { + "accountToken": "370a4f1ed5bc4e8", + "responseText": "00 -- APPROVAL", + "last4": "2224", + "cardType": "VI", + "version": "1.0", + "processor": { + "TID": "", + "orderNumber": "", + "responseCode": "00" + }, + "lastActionSucceeded": 1, + "pgwResponseCode": "100" + }, + "id": "d56d9cf246c9c829ade1d86aed9e5eff" + } + RESPONSE + end + + def failed_reversal_response + <<-RESPONSE + { + "error": null, + "result": { + "accountToken": "", + "responseText": "CNR -- Missing required token.", + "last4": "", + "cardType": "UK", + "version": "1.0", + "processor": { + "TID": "", + "orderNumber": "", + "responseCode": "" + }, + "lastActionSucceeded": 0, + "pgwResponseCode": "400" + }, + "id": "633cd63f8d97f3e51744afd55ce55023" + } + RESPONSE + end + + def successful_void_response + <<-RESPONSE + { + "error": null, + "result": { + "accountToken": "aaa3a8ac2a974d2", + "responseText": "00 -- APPROVAL", + "last4": "2224", + "cardType": "VI", + "version": "1.0", + "processor": { + "TID": "", + "orderNumber": "", + "responseCode": "00" + }, + "lastActionSucceeded": 1, + "pgwResponseCode": "100" + }, + "id": "1e7808e2e579779fa976a4472a4bc36a" + } + RESPONSE + end + + def failed_void_response + end + + def successful_credit_response + <<-RESPONSE + { + "error": null, + "result": { + "accountToken": "b9e35e6ba0b34f6", + "responseText": "00 -- APPROVAL", + "pgwTID": "00002KUZSGV2", + "last4": "2224", + "cardType": "VI", + "version": "1.0", + "processor": { + "TID": "00002KUZSGV2", + "orderNumber": "", + "responseCode": "00" + }, + "lastActionSucceeded": 1, + "pgwResponseCode": "100" + }, + "id": "7815e15188b44db93e710db1f4744010" + } + RESPONSE + end + + def successful_verify_response + <<-RESPONSE + { + "error": null, + "result": { + "accountToken": "d079e09f2ec14af", + "responseText": "85 -- AVS ACCEPTED", + "cvvResponse": "M", + "avsResponse": "Y", + "last4": "2224", + "cardType": "VI", + "version": "1.0", + "processor": { + "TID": "", + "orderNumber": "", + "responseCode": "85" + }, + "lastActionSucceeded": 1, + "pgwResponseCode": "100" + }, + "id": "f8463871ea905306f7446cf8937b0bfa" + } + RESPONSE + end +end diff --git a/test/unit/gateways/linkpoint_test.rb b/test/unit/gateways/linkpoint_test.rb index 7333d3d2bb6..50639eb460a 100644 --- a/test/unit/gateways/linkpoint_test.rb +++ b/test/unit/gateways/linkpoint_test.rb @@ -3,74 +3,86 @@ class LinkpointTest < Test::Unit::TestCase def setup Base.mode = :test - + @gateway = LinkpointGateway.new( :login => 123123, :pem => 'PEM' ) + @amount = 100 @credit_card = credit_card('4111111111111111') @options = { :order_id => 1000, :billing_address => address } end - + + def test_instantiating_without_credential_raises + assert_raise ArgumentError do + LinkpointGateway.new(login: 123123) + end + end + def test_credit_card_formatting assert_equal '04', @gateway.send(:format_creditcard_expiry_year, 2004) assert_equal '04', @gateway.send(:format_creditcard_expiry_year, '2004') assert_equal '04', @gateway.send(:format_creditcard_expiry_year, 4) assert_equal '04', @gateway.send(:format_creditcard_expiry_year, '04') end - + def test_successful_authorization @gateway.expects(:ssl_post).returns(successful_authorization_response) - + assert response = @gateway.authorize(@amount, @credit_card, @options) assert_instance_of Response, response assert_success response assert_equal '1000', response.authorization end - + def test_successful_capture @gateway.expects(:ssl_post).returns(successful_capture_response) - - assert response = @gateway.capture(@email, '1000', @options) + + assert response = @gateway.capture(@amount, 'token', @options) assert_instance_of Response, response assert_success response assert_equal '1000', response.authorization end - + def test_successful_purchase @gateway.expects(:ssl_post).returns(successful_purchase_response) - + assert response = @gateway.purchase(@amount, @credit_card, @options) assert_instance_of Response, response assert_success response assert_equal '1000', response.authorization end - + def test_failed_purchase @gateway.expects(:ssl_post).returns(failed_purchase_response) - + assert response = @gateway.purchase(@amount, @credit_card, @options) assert_failure response end - + def test_recurring @gateway.expects(:ssl_post).returns(successful_recurring_response) - - assert response = @gateway.recurring(2400, @credit_card, :order_id => 1003, :installments => 12, :startdate => "immediate", :periodicity => :monthly) + + response = assert_deprecation_warning(Gateway::RECURRING_DEPRECATION_MESSAGE) do + @gateway.recurring(2400, @credit_card, :order_id => 1003, :installments => 12, :startdate => 'immediate', :periodicity => :monthly) + end assert_success response end - + def test_amount_style assert_equal '10.34', @gateway.send(:amount, 1034) - + assert_raise(ArgumentError) do @gateway.send(:amount, '10.34') end end def test_purchase_is_valid_xml - parameters = @gateway.send(:parameters, 1000, @credit_card, :ordertype => "SALE", :order_id => 1004, + @gateway.send( + :parameters, 1000, @credit_card, + :ordertype => 'SALE', + :order_id => 1004, :billing_address => { :address1 => '1313 lucky lane', :city => 'Lost Angeles', @@ -84,7 +96,14 @@ def test_purchase_is_valid_xml end def test_recurring_is_valid_xml - parameters = @gateway.send(:parameters, 1000, @credit_card, :ordertype => "SALE", :action => "SUBMIT", :installments => 12, :startdate => "immediate", :periodicity => "monthly", :order_id => 1006, + @gateway.send( + :parameters, 1000, @credit_card, + :ordertype => 'SALE', + :action => 'SUBMIT', + :installments => 12, + :startdate => 'immediate', + :periodicity => 'monthly', + :order_id => 1006, :billing_address => { :address1 => '1313 lucky lane', :city => 'Lost Angeles', @@ -97,17 +116,42 @@ def test_recurring_is_valid_xml end def test_line_items_are_valid_xml - options = {:ordertype => "SALE", :action => "SUBMIT", :installments => 12, :startdate => "immediate", :periodicity => "monthly", :order_id => 1006, + options = { + :ordertype => 'SALE', + :action => 'SUBMIT', + :installments => 12, + :startdate => 'immediate', + :periodicity => 'monthly', + :order_id => 1006, :billing_address => { :address1 => '1313 lucky lane', :city => 'Lost Angeles', :state => 'CA', :zip => '90210' - }, - :line_items => [{:id => '123456', :description => "Logo T-Shirt", :price => - "12.00", :quantity => '1', :options => [{:name => "Color", :value => - "Red"}, {:name => "Size", :value => "XL"}]},{:id => '111', :description => "keychain", :price => "3.00", :quantity => '1'}]} - + }, + :line_items => [ + { + :id => '123456', + :description => 'Logo T-Shirt', + :price => '12.00', + :quantity => '1', + :options => [ + { + :name => 'Color', + :value => 'Red'}, + { + :name => 'Size', + :value => 'XL'} + ] + }, + { + :id => '111', + :description => 'keychain', + :price => '3.00', + :quantity => '1' + } + ] + } assert data = @gateway.send(:post_data, @amount, @credit_card, options) assert REXML::Document.new(data) @@ -115,8 +159,11 @@ def test_line_items_are_valid_xml def test_declined_purchase_is_valid_xml @gateway = LinkpointGateway.new(:login => 123123, :pem => 'PEM') - - parameters = @gateway.send(:parameters, 1000, @credit_card, :ordertype => "SALE", :order_id => 1005, + + @gateway.send( + :parameters, 1000, @credit_card, + :ordertype => 'SALE', + :order_id => 1005, :billing_address => { :address1 => '1313 lucky lane', :city => 'Lost Angeles', @@ -124,74 +171,88 @@ def test_declined_purchase_is_valid_xml :zip => '90210' } ) - + assert data = @gateway.send(:post_data, @amount, @credit_card, @options) assert REXML::Document.new(data) end - + def test_overriding_test_mode - Base.gateway_mode = :production - + Base.mode = :production + gateway = LinkpointGateway.new( :login => 'LOGIN', :pem => 'PEM', :test => true ) - + assert gateway.test? end - + def test_using_production_mode - Base.gateway_mode = :production - + Base.mode = :production + gateway = LinkpointGateway.new( :login => 'LOGIN', :pem => 'PEM' ) - + assert !gateway.test? end - + def test_supported_countries assert_equal ['US'], LinkpointGateway.supported_countries end - + def test_supported_card_types assert_equal [:visa, :master, :american_express, :discover, :jcb, :diners_club], LinkpointGateway.supported_cardtypes end - + def test_avs_result @gateway.expects(:ssl_post).returns(successful_authorization_response) - + response = @gateway.purchase(@amount, @credit_card, @options) assert_equal 'N', response.avs_result['code'] end - + def test_cvv_result @gateway.expects(:ssl_post).returns(successful_authorization_response) - + response = @gateway.purchase(@amount, @credit_card, @options) assert_equal 'M', response.cvv_result['code'] end - + + def test_transcript_scrubbing + assert_equal scrubbed_transcript, @gateway.scrub(transcript) + end + private + def successful_authorization_response '<r_csp>CSI</r_csp><r_time>Sun Jan 6 21:41:31 2008</r_time><r_ref>0004486182</r_ref><r_error/><r_ordernum>1000</r_ordernum><r_message>APPROVED</r_message><r_code>1234560004486182:NNNM:100018312899:</r_code><r_tdate>1199680890</r_tdate><r_score/><r_authresponse/><r_approved>APPROVED</r_approved><r_avs>NNNM</r_avs>' end - + def successful_capture_response '<r_csp>CSI</r_csp><r_time>Wed Dec 2 13:57:19 2009</r_time><r_ref>0009554566</r_ref><r_error></r_error><r_ordernum>1000</r_ordernum><r_message>ACCEPTED</r_message><r_code>0000000009554566: :9554566:</r_code><r_tdate>1259780240</r_tdate><r_score></r_score><r_authresponse></r_authresponse><r_approved>APPROVED</r_approved><r_avs> </r_avs>' end - + def successful_purchase_response - '<r_csp>CSI</r_csp><r_time>Sun Jan 6 21:45:22 2008</r_time><r_ref>0004486195</r_ref><r_error></r_error><r_ordernum>1000</r_ordernum><r_message>APPROVED</r_message><r_code>1234560004486195:NNNM:100018312912:</r_code><r_tdate>1199681121</r_tdate><r_score></r_score><r_authresponse></r_authresponse><r_approved>APPROVED</r_approved><r_avs>NNNM</r_avs>' + '<r_csp>CSI</r_csp><r_time>Sun Jan 6 21:45:22 2008</r_time><r_ref>0004486195</r_ref><r_error></r_error><r_ordernum>1000</r_ordernum><r_message>APPROVED</r_message><r_code>1234560004486195:NNNM:100018312912:</r_code><r_tdate>1199681121</r_tdate><r_score></r_score><r_authresponse></r_authresponse><r_approved>APPROVED</r_approved><r_avs>NNNM</r_avs>' end - + def failed_purchase_response '<r_csp></r_csp><r_time>Sun Jan 6 21:50:51 2008</r_time><r_ref></r_ref><r_error>SGS-002300: Invalid credit card type.</r_error><r_ordernum>2aec6babe076111deb2c94c21181d9fe</r_ordernum><r_message></r_message><r_code></r_code><r_tdate></r_tdate><r_score></r_score><r_authresponse></r_authresponse><r_approved>DECLINED</r_approved><r_avs></r_avs>' end - + def successful_recurring_response '<r_csp>CSI</r_csp><r_time>Sun Jan 6 21:49:00 2008</r_time><r_ref>0004486198</r_ref><r_error></r_error><r_ordernum>2206b7c9a31de5fb077913134011059d</r_ordernum><r_message>APPROVED</r_message><r_code>1234560004486198:NNNM:100018312915:</r_code><r_tdate>1199681339</r_tdate><r_score></r_score><r_authresponse></r_authresponse><r_approved>APPROVED</r_approved><r_avs>NNN</r_avs>' end + + def transcript + '</orderoptions><creditcard><cardnumber>4111111111111111</cardnumber><cardexpmonth>9</cardexpmonth><cardexpyear>16</cardexpyear><cvmvalue>123</cvmvalue><cvmindicator>provided</cvmindicator></creditcard><billing><name>Jim Smith</name>' + end + + def scrubbed_transcript + '</orderoptions><creditcard><cardnumber>[FILTERED]</cardnumber><cardexpmonth>9</cardexpmonth><cardexpyear>16</cardexpyear><cvmvalue>[FILTERED]</cvmvalue><cvmindicator>provided</cvmindicator></creditcard><billing><name>Jim Smith</name>' + end + end diff --git a/test/unit/gateways/litle/litle_card_token_test.rb b/test/unit/gateways/litle/litle_card_token_test.rb deleted file mode 100644 index 7693e262c7b..00000000000 --- a/test/unit/gateways/litle/litle_card_token_test.rb +++ /dev/null @@ -1,135 +0,0 @@ -require 'test_helper' - -class LitleCardTokenTest < Test::Unit::TestCase - def setup - @card_token = LitleGateway::LitleCardToken.new( - :token => '1234567890123456', - :month => 9, - :year => Time.now.year + 1, - :brand => 'visa', - :verification_value => '123' - ) - end - - def test_constructor_should_properly_assign_values - assert_equal '1234567890123456', @card_token.token - assert_equal 9, @card_token.month - assert_equal Time.now.year + 1, @card_token.year - assert_equal 'visa', @card_token.brand - assert_equal 'VI', @card_token.type - assert_equal '123', @card_token.verification_value - assert_valid @card_token - end - - def test_new_card_token_should_not_be_valid - c = LitleGateway::LitleCardToken.new - - assert_not_valid c - assert_false c.errors.empty? - end - - def test_should_be_able_to_access_errors_indifferently - @card_token.token = '' - - assert_not_valid @card_token - assert @card_token.errors.on(:token) - assert @card_token.errors.on('token') - end - - def test_should_be_able_to_identify_invalid_tokens - @card_token.token = nil - assert_not_valid @card_token - - @card_token.token = '11112222333344ff' - assert_not_valid @card_token - assert @card_token.errors.on(:token) - - @card_token.token = '123456' - assert_not_valid @card_token - assert @card_token.errors.on(:token) - - @card_token.token = 'Q11ab222333344444' - assert_not_valid @card_token - assert @card_token.errors.on(:token) - end - - def test_should_have_errors_with_invalid_card_brand_for_otherwise_correct_number - @card_token.brand = 'larry' - - assert_not_valid @card_token - assert @card_token.errors.on(:brand) - assert !@card_token.errors.on(:token) - end - - def test_should_have_blank_type_for_invalid_brand - @card_token.brand = 'larry' - - assert_not_valid @card_token - assert @card_token.errors.on(:brand) - assert @card_token.type.blank? - end - - def test_should_have_blank_type_with_blank_card_brand - @card_token.brand = '' - - assert_valid @card_token - assert @card_token.type.blank? - end - - def test_should_require_a_valid_card_month - @card_token.month = Time.now.utc.month - @card_token.year = Time.now.utc.year - - assert_valid @card_token - end - - def test_should_not_be_valid_with_empty_month_and_valid_year - @card_token.month = '' - - assert_not_valid @card_token - assert_equal 'is not a valid month', @card_token.errors.on('month') - end - - def test_should_not_be_valid_for_edge_month_cases - @card_token.month = 13 - @card_token.year = Time.now.year - assert_not_valid @card_token - assert @card_token.errors.on('month') - - @card_token.month = 0 - @card_token.year = Time.now.year - assert_not_valid @card_token - assert @card_token.errors.on('month') - end - - def test_should_be_invalid_with_valid_month_and_empty_year - @card_token.year = '' - assert_not_valid @card_token - assert_equal 'is not a valid year', @card_token.errors.on('year') - end - - def test_should_not_be_valid_for_edge_year_cases - @card_token.year = 1987 - assert_not_valid @card_token - assert @card_token.errors.on('year') - - @card_token.year = 1977 - assert_not_valid @card_token - assert @card_token.errors.on('year') - end - - def test_should_be_a_valid_future_year - @card_token.year = Time.now.year + 1 - assert_valid @card_token - end - - # The following is a regression for a bug that raised an exception when - # a new credit card was validated - def test_validate_new_card - card_token = LitleGateway::LitleCardToken.new - - assert_nothing_raised do - card_token.validate - end - end -end diff --git a/test/unit/gateways/litle_test.rb b/test/unit/gateways/litle_test.rb old mode 100755 new mode 100644 index e3166870e8f..47b866abef3 --- a/test/unit/gateways/litle_test.rb +++ b/test/unit/gateways/litle_test.rb @@ -1,760 +1,1027 @@ require 'test_helper' class LitleTest < Test::Unit::TestCase + include CommStub def setup - @gateway = LitleGateway.new({:merchant_id=>'101', :user=>'active', :password=>'merchant', :version=>'8.10', :url=>'https://www.testlitle.com/sandbox/communicator/online'}) - @credit_card_options = { - :first_name => 'Steve', - :last_name => 'Smith', - :month => '9', - :year => '2010', - :brand => 'visa', - :number => '4242424242424242', - :verification_value => '969' - } - @billing_address = { - :name => 'Steve Smith', - :company => 'testCompany', - :address1 => '900 random st', - :address2 => 'floor 10', - :city => 'lowell', - :state => 'ma', - :country => 'usa', - :zip => '12345', - :phone => '1234567890' - } - - @shipping_address = { - :name => 'Steve Smith', - :company => '', - :address1 => '500 nnnn st', - :address2 => '', - :city => 'lowell', - :state => 'ma', - :country => 'usa', - :zip => '12345', - :phone => '1234567890' - } - - @response_options = { - 'response' => '000', - 'message' => 'successful', - 'litleTxnId' => '1234', - 'litleToken'=>'1111222233334444' - } - end - - def test_build_purchase_request - # define all inputs - money = 1000 - - creditcard = CreditCard.new(@credit_card_options) - - order_id = '1234' - ip = '192.168.0.1' - customer = '4000' - invoice = '1000' - merchant = 'ABC' - description = 'cool stuff' - email = 'abc@xyz.com' - currency = 'USD' - - options = { - :order_id=>order_id, - :ip=>ip, - :customer=>customer, - :invoice=>invoice, - :merchant=>merchant, - :description=>description, - :email=>email, - :currency=>currency, - :billing_address=>@billing_address, - :shipping_address=>@shipping_address, - :merchant_id=>'101' - } - - hash_from_gateway = @gateway.send(:build_purchase_request, money, creditcard, options) - - assert_equal 1000, hash_from_gateway['amount'] - assert_equal 'VI', hash_from_gateway['card']['type'] - assert_equal '4242424242424242', hash_from_gateway['card']['number'] - assert_equal '0910', hash_from_gateway['card']['expDate'] - assert_equal '969', hash_from_gateway['card']['cardValidationNum'] - #billing address - assert_equal 'Steve Smith', hash_from_gateway['billToAddress']['name'] - assert_equal 'testCompany', hash_from_gateway['billToAddress']['companyName'] - assert_equal '900 random st', hash_from_gateway['billToAddress']['addressLine1'] - assert_equal 'floor 10', hash_from_gateway['billToAddress']['addressLine2'] - assert_equal 'lowell', hash_from_gateway['billToAddress']['city'] - assert_equal 'ma', hash_from_gateway['billToAddress']['state'] - assert_equal '12345', hash_from_gateway['billToAddress']['zip'] - assert_equal 'usa', hash_from_gateway['billToAddress']['country'] - assert_equal 'abc@xyz.com', hash_from_gateway['billToAddress']['email'] - assert_equal '1234567890', hash_from_gateway['billToAddress']['phone'] - #shipping address - assert_equal 'Steve Smith', hash_from_gateway['shipToAddress']['name'] - assert_nil hash_from_gateway['shipToAddress']['company'] - assert_equal '500 nnnn st', hash_from_gateway['shipToAddress']['addressLine1'] - assert_equal '', hash_from_gateway['shipToAddress']['addressLine2'] - assert_equal 'lowell', hash_from_gateway['shipToAddress']['city'] - assert_equal 'ma', hash_from_gateway['shipToAddress']['state'] - assert_equal '12345', hash_from_gateway['shipToAddress']['zip'] - assert_equal 'usa', hash_from_gateway['shipToAddress']['country'] - assert_equal 'abc@xyz.com', hash_from_gateway['shipToAddress']['email'] - assert_equal '1234567890', hash_from_gateway['shipToAddress']['phone'] - - assert_equal '1234', hash_from_gateway['orderId'] - assert_equal '4000', hash_from_gateway['customerId'] - assert_equal 'ABC', hash_from_gateway['reportGroup'] #The option :merchant is used for Litle's Report Group - assert_equal '101', hash_from_gateway['merchantId'] - - assert_equal '1000', hash_from_gateway['enhancedData']['invoiceReferenceNumber'] - assert_equal '192.168.0.1', hash_from_gateway['fraudCheckType']['customerIpAddress'] - assert_equal 'cool stuff', hash_from_gateway['enhancedData']['customerReference'] - end - - def test_build_purchase_request_with_token - # define all inputs - money = 1000 - token = '1234567890123456' - month = 9 - year = Time.now.year + 1 - exp_date = "#{'%02d' % month.to_i}#{year.to_s[2..3]}" - brand = 'visa' - cvv = '123' - options = { - :token => { - :month => month, - :year => year, - :brand => brand, - :verification_value => cvv - } - } - - hash_from_gateway = @gateway.send(:build_purchase_request, money, token, options) - - assert_equal money, hash_from_gateway['amount'] - assert_equal token, hash_from_gateway['token']['litleToken'] - assert_equal exp_date, hash_from_gateway['token']['expDate'] - assert_equal cvv, hash_from_gateway['token']['cardValidationNum'] - assert_equal 'VI', hash_from_gateway['token']['type'] - end - - def test_build_authorize_request_with_token - # define all inputs - money = 1000 - token = '1234567890123456' - month = 9 - year = Time.now.year + 1 - exp_date = "#{'%02d' % month.to_i}#{year.to_s[2..3]}" - options = { - :token => { - :month => month, - :year => year - } - } - - hash_from_gateway = @gateway.send(:build_authorize_request, money, token, options) - - assert_equal money, hash_from_gateway['amount'] - assert_equal token, hash_from_gateway['token']['litleToken'] - assert_equal exp_date, hash_from_gateway['token']['expDate'] - end - - def test_create_hash_money_not_nil - # define all inputs - money = 1000 - - order_id = '1234' - ip = '192.168.0.1' - customer = '4000' - invoice = '1000' - merchant = 'ABC' - description = 'cool stuff' - email = 'abc@xyz.com' - currency = 'USD' - - options = { - :order_id=>order_id, - :ip=>ip, - :customer=>customer, - :invoice=>invoice, - :merchant=>merchant, - :description=>description, - :email=>email, - :currency=>currency, - :billing_address=>@billing_address, - :shipping_address=>@shipping_address, - :merchant_id=>'101' - } - - hashFromGateway = @gateway.send(:create_hash, money, options) - - assert_equal 1000, hashFromGateway['amount'] - #billing address - assert_equal 'Steve Smith', hashFromGateway['billToAddress']['name'] - assert_equal 'testCompany', hashFromGateway['billToAddress']['companyName'] - assert_equal '900 random st', hashFromGateway['billToAddress']['addressLine1'] - assert_equal 'floor 10', hashFromGateway['billToAddress']['addressLine2'] - assert_equal 'lowell', hashFromGateway['billToAddress']['city'] - assert_equal 'ma', hashFromGateway['billToAddress']['state'] - assert_equal '12345', hashFromGateway['billToAddress']['zip'] - assert_equal 'usa', hashFromGateway['billToAddress']['country'] - assert_equal 'abc@xyz.com', hashFromGateway['billToAddress']['email'] - assert_equal '1234567890', hashFromGateway['billToAddress']['phone'] - #shipping address - assert_equal 'Steve Smith', hashFromGateway['shipToAddress']['name'] - assert_nil hashFromGateway['shipToAddress']['company'] - assert_equal '500 nnnn st', hashFromGateway['shipToAddress']['addressLine1'] - assert_equal '', hashFromGateway['shipToAddress']['addressLine2'] - assert_equal 'lowell', hashFromGateway['shipToAddress']['city'] - assert_equal 'ma', hashFromGateway['shipToAddress']['state'] - assert_equal '12345', hashFromGateway['shipToAddress']['zip'] - assert_equal 'usa', hashFromGateway['shipToAddress']['country'] - assert_equal 'abc@xyz.com', hashFromGateway['shipToAddress']['email'] - assert_equal '1234567890', hashFromGateway['shipToAddress']['phone'] - - assert_equal '1234', hashFromGateway['orderId'] - assert_equal '4000', hashFromGateway['customerId'] - assert_equal 'ABC', hashFromGateway['reportGroup'] #The option :merchant is used for Litle's Report Group - assert_equal '101', hashFromGateway['merchantId'] - - assert_equal '1000', hashFromGateway['enhancedData']['invoiceReferenceNumber'] - assert_equal '192.168.0.1', hashFromGateway['fraudCheckType']['customerIpAddress'] - assert_equal 'cool stuff', hashFromGateway['enhancedData']['customerReference'] - end + Base.mode = :test + + @gateway = LitleGateway.new( + login: 'login', + password: 'password', + merchant_id: 'merchant_id' + ) + + @credit_card = credit_card + @decrypted_apple_pay = ActiveMerchant::Billing::NetworkTokenizationCreditCard.new( + { + month: '01', + year: '2012', + brand: 'visa', + number: '44444444400009', + payment_cryptogram: 'BwABBJQ1AgAAAAAgJDUCAAAAAAA=' + }) + @decrypted_android_pay = ActiveMerchant::Billing::NetworkTokenizationCreditCard.new( + { + source: :android_pay, + month: '01', + year: '2021', + brand: 'visa', + number: '4457000300000007', + payment_cryptogram: 'BwABBJQ1AgAAAAAgJDUCAAAAAAA=' + }) + @amount = 100 + @options = {} + @check = check( + name: 'Tom Black', + routing_number: '011075150', + account_number: '4099999992', + account_type: 'checking' + ) + @authorize_check = check( + name: 'John Smith', + routing_number: '011075150', + account_number: '1099999999', + account_type: 'checking' + ) + end + + def test_successful_purchase + response = stub_comms do + @gateway.purchase(@amount, @credit_card) + end.respond_with(successful_purchase_response) - def test_create_hash_with_default_order_source - # define all inputs - money = 1000 - options = { - } + assert_success response - hashFromGateway = @gateway.send(:create_hash, money, options) + assert_equal '100000000000000006;sale;100', response.authorization + assert response.test? + end - assert_equal 'ecommerce', hashFromGateway['orderSource'] - end - - def test_create_hash_with_custom_order_source - # define all inputs - money = 1000 - options = { - :order_source => 'recurring' - } - - hashFromGateway = @gateway.send(:create_hash, money, options) - - assert_equal 'recurring', hashFromGateway['orderSource'] - end + def test_successful_purchase_with_echeck + response = stub_comms do + @gateway.purchase(2004, @check) + end.respond_with(successful_purchase_with_echeck_response) - def test_create_hash_money_nil - # define all inputs - money = nil + assert_success response - hashFromGateway = @gateway.send(:create_hash, money, {}) + assert_equal '621100411297330000;echeckSales;2004', response.authorization + assert response.test? + end - assert_nil hashFromGateway['amount'] + def test_successful_purchase_with_paypage_registration_with_month_year_verification_name + paypage_registration = ActiveMerchant::Billing::LitlePaypageRegistration.new( + 'XkNDRGZDTGZyS2RBSTVCazJNSmdWam5TQ2gyTGhydFh0Mk5qZ0Z3cVp5VlNBN00rcGRZdHF6amFRWEttbVBnYw==', + month: '11', + year: '12', + verification_value: '123', + name: 'Joe Payer' + ) + + response = stub_comms do + @gateway.purchase(@amount, paypage_registration, billing_address: address) + end.check_request do |endpoint, data, headers| + matcher = %r(<paypage>\s*<paypageRegistrationId>XkNDRGZDTGZyS2RBSTVCazJNSmdWam5TQ2gyTGhydFh0Mk5qZ0Z3cVp5VlNBN00rcGRZdHF6amFRWEttbVBnYw==</paypageRegistrationId>\s*<expDate>1112</expDate>\s*<cardValidationNum>123</cardValidationNum>\s*</paypage>) + assert_match(matcher, data) + end.respond_with(successful_purchase_response) + + assert_success response + + assert_equal '100000000000000006;sale;100', response.authorization + assert response.test? end - def test_create_hash_money_empty_string - # define all inputs - money = '' + def test_successful_purchase_with_paypage_registration_without_month_year_verification_name + paypage_registration = ActiveMerchant::Billing::LitlePaypageRegistration.new( + 'ZkNDRGZDTGZyS2RBSTVCazJNSmdWam5TQ2gyTGhydFh0Mk5qZ0Z3cVp5VlNBN00rcGRZdHF6amFRWEttbVBnYw==' + ) - hashFromGateway = @gateway.send(:create_hash, money, {}) + response = stub_comms do + @gateway.purchase(@amount, paypage_registration, billing_address: address) + end.check_request do |endpoint, data, headers| + matcher = %r(<paypage>\s*<paypageRegistrationId>ZkNDRGZDTGZyS2RBSTVCazJNSmdWam5TQ2gyTGhydFh0Mk5qZ0Z3cVp5VlNBN00rcGRZdHF6amFRWEttbVBnYw==</paypageRegistrationId>\s*</paypage>) + assert_match(matcher, data) + end.respond_with(successful_purchase_response) - assert_nil hashFromGateway['amount'] + assert_success response + + assert_equal '100000000000000006;sale;100', response.authorization + assert response.test? end - def test_recognize_ax_and_some_empties - creditcard = CreditCard.new(@credit_card_options.merge(:brand => 'american_express')) - hashFromGateway = @gateway.send(:build_purchase_request, 0, creditcard, {}) - assert_equal 'AX', hashFromGateway['card']['type'] - assert_nil hashFromGateway['billToAddress'] - assert_nil hashFromGateway['shipToAddress'] + def test_purchase_with_processing_type + response = stub_comms do + @gateway.purchase(@amount, @credit_card, processing_type: 'initialCOF') + end.check_request do |_endpoint, data, _headers| + matcher = /\<processingType\>initialCOF\<\/processingType\>/ + assert_match(matcher, data) + end.respond_with(successful_purchase_response) + + assert_success response + assert_equal '100000000000000006;sale;100', response.authorization + assert response.test? end - def test_recognize_di - creditcard = CreditCard.new(@credit_card_options.merge(:brand => 'discover')) - hashFromGateway = @gateway.send(:build_purchase_request, 0, creditcard, {}) - assert_equal 'DI', hashFromGateway['card']['type'] + def test_failed_purchase + response = stub_comms do + @gateway.purchase(@amount, @credit_card) + end.respond_with(failed_purchase_response) + + assert_failure response + assert_equal 'Insufficient Funds', response.message + assert_equal '110', response.params['response'] + assert response.test? end - def test_recognize_mastercard - creditcard = CreditCard.new(@credit_card_options.merge(:brand => 'master')) - hashFromGateway = @gateway.send(:build_purchase_request, 0,creditcard,{}) - assert_equal 'MC', hashFromGateway['card']['type'] + def test_passing_merchant_data + options = @options.merge( + affiliate: 'some-affiliate', + campaign: 'super-awesome-campaign', + merchant_grouping_id: 'brilliant-group' + ) + stub_comms do + @gateway.purchase(@amount, @credit_card, options) + end.check_request do |endpoint, data, headers| + assert_match(%r(<affiliate>some-affiliate</affiliate>), data) + assert_match(%r(<campaign>super-awesome-campaign</campaign>), data) + assert_match(%r(<merchantGroupingId>brilliant-group</merchantGroupingId>), data) + end.respond_with(successful_purchase_response) end - def test_recognize_jcb - creditcard = CreditCard.new(@credit_card_options.merge(:brand => 'jcb')) - hashFromGateway = @gateway.send(:build_purchase_request, 0, creditcard, {}) - assert_equal 'DI', hashFromGateway['card']['type'] + def test_passing_litle_token + stub_comms do + @gateway.purchase(@amount, '121212121212', month: '01', year: '20') + end.check_request do |endpoint, data, headers| + assert_match(%r(<expDate>0120<), data) + end.respond_with(successful_purchase_response) end - def test_recognize_diners - creditcard = CreditCard.new(@credit_card_options.merge(:brand => 'diners_club')) - hashFromGateway = @gateway.send(:build_purchase_request, 0, creditcard, {}) - assert_equal 'DI', hashFromGateway['card']['type'] + def test_passing_name_on_card + stub_comms do + @gateway.purchase(@amount, @credit_card) + end.check_request do |endpoint, data, headers| + assert_match(%r(<billToAddress>\s*<name>Longbob Longsen<), data) + end.respond_with(successful_purchase_response) end - def test_two_digit_month - creditcard = CreditCard.new(@credit_card_options.merge(:month => '11')) - hashFromGateway = @gateway.send(:build_purchase_request, 0, creditcard, {}) - assert_equal '1110', hashFromGateway['card']['expDate'] + def test_passing_order_id + stub_comms do + @gateway.purchase(@amount, @credit_card, order_id: '774488') + end.check_request do |endpoint, data, headers| + assert_match(/774488/, data) + end.respond_with(successful_purchase_response) end - def test_nils_in_both_addresses - creditcard = CreditCard.new(@credit_card_options) + def test_passing_billing_address + stub_comms do + @gateway.purchase(@amount, @credit_card, billing_address: address) + end.check_request do |endpoint, data, headers| + assert_match(/<billToAddress>.*Widgets.*456.*Apt 1.*Otta.*ON.*K1C.*CA.*555-5/m, data) + end.respond_with(successful_purchase_response) + end - hashFromGateway = @gateway.send(:build_purchase_request, 0, creditcard, - {:shipping_address=>{},:billing_address=>{}}) + def test_passing_shipping_address + stub_comms do + @gateway.purchase(@amount, @credit_card, shipping_address: address) + end.check_request do |endpoint, data, headers| + assert_match(/<shipToAddress>.*Widgets.*456.*Apt 1.*Otta.*ON.*K1C.*CA.*555-5/m, data) + end.respond_with(successful_purchase_response) + end + + def test_passing_descriptor + stub_comms do + @gateway.authorize(@amount, @credit_card, { + descriptor_name: 'Name', descriptor_phone: 'Phone' + }) + end.check_request do |endpoint, data, headers| + assert_match(%r(<customBilling>.*<descriptor>Name<)m, data) + assert_match(%r(<customBilling>.*<phone>Phone<)m, data) + end.respond_with(successful_authorize_response) + end - %w(name companyName company addressLine1 addressLine2 city state zip country email phone).each do |att| - #billing address - assert_nil hashFromGateway['billToAddress'][att] - #shipping address - assert_nil hashFromGateway['shipToAddress'][att] - end + def test_passing_debt_repayment + stub_comms do + @gateway.authorize(@amount, @credit_card, { debt_repayment: true }) + end.check_request do |endpoint, data, headers| + assert_match(%r(<debtRepayment>true</debtRepayment>), data) + end.respond_with(successful_authorize_response) + end + + def test_passing_payment_cryptogram + stub_comms do + @gateway.purchase(@amount, @decrypted_apple_pay) + end.check_request do |endpoint, data, headers| + assert_match(/BwABBJQ1AgAAAAAgJDUCAAAAAAA=/, data) + end.respond_with(successful_purchase_response) + end + def test_add_applepay_order_source + stub_comms do + @gateway.purchase(@amount, @decrypted_apple_pay) + end.check_request do |endpoint, data, headers| + assert_match '<orderSource>applepay</orderSource>', data + end.respond_with(successful_purchase_response) end - def test_build_credit_request_identification - hashFromGateway = @gateway.send(:build_credit_request, 1000, '123456789012345678', {}) - assert_equal '123456789012345678', hashFromGateway['litleTxnId'] - assert_equal nil, hashFromGateway['orderSource'] - assert_equal nil, hashFromGateway['orderId'] + def test_add_android_pay_order_source + stub_comms do + @gateway.purchase(@amount, @decrypted_android_pay) + end.check_request do |endpoint, data, headers| + assert_match '<orderSource>androidpay</orderSource>', data + end.respond_with(successful_purchase_response) end - def test_build_credit_request_token - token = '171299999999999' - options = { - :order_id => '1234', - :billing_address => { - :zip => '12345' - }, - :token => { - :month => 11, - :year => 2014 - } - } + def test_successful_authorize_and_capture + response = stub_comms do + @gateway.authorize(@amount, @credit_card) + end.respond_with(successful_authorize_response) - hashFromGateway = @gateway.send(:build_credit_request, 1000, token, options) - assert_equal nil, hashFromGateway['litleTxnId'] - assert_equal 'ecommerce', hashFromGateway['orderSource'] - assert_equal '1234', hashFromGateway['orderId'] - assert_equal token, hashFromGateway['token']['litleToken'] - assert_equal '1114', hashFromGateway['token']['expDate'] - assert_equal '12345', hashFromGateway['billToAddress']['zip'] + assert_success response + + assert_equal '100000000000000001;authorization;100', response.authorization + assert response.test? + + capture = stub_comms do + @gateway.capture(@amount, response.authorization) + end.check_request do |endpoint, data, headers| + assert_match(/100000000000000001/, data) + end.respond_with(successful_capture_response) + + assert_success capture end - def test_currency_USD - creditcard = CreditCard.new(@credit_card_options) - hashFromGateway = @gateway.send(:build_purchase_request, 0, creditcard, {:currency=>'USD',:merchant_id=>'101'}) - assert_equal '101', hashFromGateway['merchantId'] + def test_successful_authorize_with_paypage_registration_with_month_year_verification_name + paypage_registration = ActiveMerchant::Billing::LitlePaypageRegistration.new( + 'XkNDRGZDTGZyS2RBSTVCazJNSmdWam5TQ2gyTGhydFh0Mk5qZ0Z3cVp5VlNBN00rcGRZdHF6amFRWEttbVBnYw==', + month: '11', + year: '12', + verification_value: '123', + name: 'Joe Payer' + ) + + response = stub_comms do + @gateway.authorize(@amount, paypage_registration) + end.check_request do |endpoint, data, headers| + matcher = %r(<paypage>\s*<paypageRegistrationId>XkNDRGZDTGZyS2RBSTVCazJNSmdWam5TQ2gyTGhydFh0Mk5qZ0Z3cVp5VlNBN00rcGRZdHF6amFRWEttbVBnYw==</paypageRegistrationId>\s*<expDate>1112</expDate>\s*<cardValidationNum>123</cardValidationNum>\s*</paypage>) + assert_match(matcher, data) + end.respond_with(successful_authorize_response) + + assert_success response + + assert_equal "100000000000000001;authorization;100", response.authorization + assert response.test? end - def test_currency_DEFAULT - creditcard = CreditCard.new(@credit_card_options) - hashFromGateway = @gateway.send(:build_purchase_request, 0, creditcard, {:merchant_id=>'101'}) - assert_equal '101', hashFromGateway['merchantId'] + def test_successful_authorize_with_paypage_registration_without_month_year_verification_name + paypage_registration = ActiveMerchant::Billing::LitlePaypageRegistration.new( + 'XkNDRGZDTGZyS2RBSTVCazJNSmdWam5TQ2gyTGhydFh0Mk5qZ0Z3cVp5VlNBN00rcGRZdHF6amFRWEttbVBnYw==', + ) + + response = stub_comms do + @gateway.authorize(@amount, paypage_registration) + end.check_request do |endpoint, data, headers| + matcher = %r(<paypage>\s*<paypageRegistrationId>XkNDRGZDTGZyS2RBSTVCazJNSmdWam5TQ2gyTGhydFh0Mk5qZ0Z3cVp5VlNBN00rcGRZdHF6amFRWEttbVBnYw==</paypageRegistrationId>\s*</paypage>) + assert_match(matcher, data) + end.respond_with(successful_authorize_response) + + assert_success response + + assert_equal "100000000000000001;authorization;100", response.authorization + assert response.test? end - def test_currency_EUR - creditcard = CreditCard.new(@credit_card_options) - hashFromGateway = @gateway.send(:build_purchase_request, 0, creditcard, {:currency=>'EUR',:merchant_id=>'102'}) - assert_equal '102', hashFromGateway['merchantId'] + def test_failed_authorize + response = stub_comms do + @gateway.authorize(@amount, @credit_card) + end.respond_with(failed_authorize_response) + + assert_failure response + assert_equal 'Insufficient Funds', response.message + assert_equal '110', response.params['response'] end - def test_auth_pass - authorizationResponseObj = @response_options - retObj = {'response'=>'0','authorizationResponse'=>authorizationResponseObj} - LitleOnline::Communications.expects(:http_post => retObj.to_xml(:root => 'litleOnlineResponse')) + def test_failed_capture + response = stub_comms do + @gateway.capture(@amount, @credit_card) + end.respond_with(failed_capture_response) - creditcard = CreditCard.new(@credit_card_options) - responseFrom = @gateway.authorize(0, creditcard) - assert_equal true, responseFrom.success? - assert_equal 'successful', responseFrom.message - assert_equal '1234;authorization', responseFrom.authorization - assert_equal '1111222233334444', responseFrom.params['litleOnlineResponse']['authorizationResponse']['litleToken'] + assert_failure response + assert_equal 'No transaction found with specified litleTxnId', response.message + assert_equal '360', response.params['response'] end - def test_avs - fraudResult = {'avsResult'=>'01'} - authorizationResponseObj = @response_options.merge('fraudResult' => fraudResult) - retObj = {'response'=>'0','authorizationResponse'=>authorizationResponseObj} - LitleOnline::Communications.expects(:http_post => retObj.to_xml(:root => 'litleOnlineResponse')) + def test_successful_refund + response = stub_comms do + @gateway.purchase(@amount, @credit_card) + end.respond_with(successful_purchase_response) - creditcard = CreditCard.new(@credit_card_options) - responseFrom = @gateway.authorize(0, creditcard) - assert_equal true, responseFrom.success? - assert_equal 'X', responseFrom.avs_result['code'] - assert_equal 'Street address and 9-digit postal code match.', responseFrom.avs_result['message'] - assert_equal 'Y', responseFrom.avs_result['street_match'] - assert_equal 'Y', responseFrom.avs_result['postal_match'] - end - - def test_cvv - fraudResult = {'cardValidationResult'=>'M'} - authorizationResponseObj = @response_options.merge('fraudResult' => fraudResult) - retObj = {'response'=>'0','authorizationResponse'=>authorizationResponseObj} - LitleOnline::Communications.expects(:http_post => retObj.to_xml(:root => 'litleOnlineResponse')) + assert_equal '100000000000000006;sale;100', response.authorization - creditcard = CreditCard.new(@credit_card_options) - responseFrom = @gateway.authorize(0, creditcard) - assert_equal true, responseFrom.success? - assert_equal 'M', responseFrom.cvv_result['code'] - assert_equal 'Match', responseFrom.cvv_result['message'] + refund = stub_comms do + @gateway.refund(@amount, response.authorization) + end.check_request do |endpoint, data, headers| + assert_match(/100000000000000006/, data) + end.respond_with(successful_refund_response) + + assert_success refund end - def test_sale_avs - fraudResult = {'avsResult'=>'10'} - saleResponseObj = @response_options.merge('fraudResult' => fraudResult) - retObj = {'response'=>'0','saleResponse'=>saleResponseObj} - LitleOnline::Communications.expects(:http_post => retObj.to_xml(:root => 'litleOnlineResponse')) - - creditcard = CreditCard.new(@credit_card_options) - responseFrom = @gateway.purchase(0, creditcard) - assert_equal true, responseFrom.success? - assert_equal 'Z', responseFrom.avs_result['code'] - assert_equal 'Street address does not match, but 5-digit postal code matches.', responseFrom.avs_result['message'] - assert_equal 'N', responseFrom.avs_result['street_match'] - assert_equal 'Y', responseFrom.avs_result['postal_match'] - end - - def test_sale_cvv - fraudResult = {'cardValidationResult'=>''} - saleResponseObj = @response_options.merge('fraudResult' => fraudResult) - retObj = {'response'=>'0','saleResponse'=>saleResponseObj} - LitleOnline::Communications.expects(:http_post => retObj.to_xml(:root => 'litleOnlineResponse')) - - creditcard = CreditCard.new(@credit_card_options) - responseFrom = @gateway.purchase(0, creditcard) - assert_equal true, responseFrom.success? - assert_equal 'P', responseFrom.cvv_result['code'] - assert_equal 'Not Processed', responseFrom.cvv_result['message'] - end - - def test_auth_fail - authorizationResponseObj = {'response' => '111', 'message' => 'fail', 'litleTxnId' => '1234', 'litleToken'=>'1111222233334444'} - retObj = {'response'=>'0','authorizationResponse'=>authorizationResponseObj} - LitleOnline::Communications.expects(:http_post => retObj.to_xml(:root => 'litleOnlineResponse')) - - creditcard = CreditCard.new(@credit_card_options) - responseFrom = @gateway.authorize(0, creditcard) - assert_equal false, responseFrom.success? - assert_equal '1234;authorization', responseFrom.authorization - assert_equal '1111222233334444', responseFrom.params['litleOnlineResponse']['authorizationResponse']['litleToken'] + def test_failed_refund + response = stub_comms do + @gateway.refund(@amount, 'SomeAuthorization') + end.respond_with(failed_refund_response) + + assert_failure response + assert_equal 'No transaction found with specified litleTxnId', response.message + assert_equal '360', response.params['response'] end - def test_auth_fail_schema - retObj = {'response'=>'1','message'=>'Error validating xml data against the schema'} - LitleOnline::Communications.expects(:http_post => retObj.to_xml(:root => 'litleOnlineResponse')) - - creditcard = CreditCard.new(@credit_card_options) - responseFrom = @gateway.authorize(0, creditcard) - assert_equal false, responseFrom.success? - assert_equal 'Error validating xml data against the schema', responseFrom.message - end - - def test_purchase_pass - purchaseResponseObj = {'response' => '000', 'message' => 'successful', 'litleTxnId'=>'123456789012345678'} - retObj = {'response'=>'0','saleResponse'=>purchaseResponseObj} - LitleOnline::Communications.expects(:http_post => retObj.to_xml(:root => 'litleOnlineResponse')) - - creditcard = CreditCard.new(@credit_card_options) - responseFrom = @gateway.purchase(0, creditcard) - assert_equal true, responseFrom.success? - assert_equal '123456789012345678;sale', responseFrom.authorization - end - - def test_purchase_pass_with_token - purchaseResponseObj = {'response' => '000', 'message' => 'successful', 'litleTxnId'=>'123456789012345678'} - retObj = {'response'=>'0','saleResponse'=>purchaseResponseObj} - LitleOnline::Communications.expects(:http_post => retObj.to_xml(:root => 'litleOnlineResponse')) - - options = { - :token => { - :month => '11', - :year => '2014' - } - } - - responseFrom = @gateway.purchase(0, '171299999999999', options) - assert_equal true, responseFrom.success? - assert_equal '123456789012345678;sale', responseFrom.authorization - end - - def test_purchase_fail - purchaseResponseObj = {'response' => '111', 'message' => 'fail', 'litleTxnId'=>'123456789012345678'} - retObj = {'response'=>'0','saleResponse'=>purchaseResponseObj} - LitleOnline::Communications.expects(:http_post => retObj.to_xml(:root => 'litleOnlineResponse')) - - creditcard = CreditCard.new(@credit_card_options) - responseFrom = @gateway.purchase(0, creditcard) - assert_equal false, responseFrom.success? - assert_equal '123456789012345678;sale', responseFrom.authorization - end - - def test_purchase_fail_schema - retObj = {'response'=>'1','message'=>'Error validating xml data against the schema'} - LitleOnline::Communications.expects(:http_post => retObj.to_xml(:root => 'litleOnlineResponse')) - - creditcard = CreditCard.new(@credit_card_options) - responseFrom = @gateway.purchase(0, creditcard) - assert_equal false, responseFrom.success? - assert_equal 'Error validating xml data against the schema', responseFrom.message - end - - def test_capture_pass - captureResponseObj = {'response' => '000', 'message' => 'pass', 'litleTxnId'=>'123456789012345678'} - retObj = {'response'=>'0','captureResponse'=>captureResponseObj} - LitleOnline::Communications.expects(:http_post => retObj.to_xml(:root => 'litleOnlineResponse')) - authorization = "1234" - responseFrom = @gateway.capture(0, authorization) - assert_equal true, responseFrom.success? - assert_equal '123456789012345678;capture', responseFrom.authorization - end - - def test_capture_fail - captureResponseObj = {'response' => '111', 'message' => 'fail', 'litleTxnId'=>'123456789012345678'} - retObj = {'response'=>'0','captureResponse'=>captureResponseObj} - LitleOnline::Communications.expects(:http_post => retObj.to_xml(:root => 'litleOnlineResponse')) - authorization = "1234" - responseFrom = @gateway.capture(0, authorization) - assert_equal false, responseFrom.success? - assert_equal '123456789012345678;capture', responseFrom.authorization - end - - def test_capture_fail_schema - retObj = {'response'=>'1','message'=>'Error validating xml data against the schema'} - LitleOnline::Communications.expects(:http_post => retObj.to_xml(:root => 'litleOnlineResponse')) - authorization = '1234' - responseFrom = @gateway.capture(0, authorization) - assert_equal false, responseFrom.success? - assert_equal 'Error validating xml data against the schema', responseFrom.message - end - - def test_void_sale_pass - voidResponseObj = {'response' => '000', 'message' => 'pass', 'litleTxnId'=>'123456789012345678'} - retObj = {'response'=>'0','voidResponse'=>voidResponseObj} - LitleOnline::Communications.expects(:http_post => retObj.to_xml(:root => 'litleOnlineResponse')) - identification = "1234;sale" - responseFrom = @gateway.void(identification) - assert_equal true, responseFrom.success? - assert_equal '123456789012345678;void', responseFrom.authorization - end - - def test_void_sale_fail - voidResponseObj = {'response' => '111', 'message' => 'fail', 'litleTxnId'=>'123456789012345678'} - retObj = {'response'=>'0','voidResponse'=>voidResponseObj} - LitleOnline::Communications.expects(:http_post => retObj.to_xml(:root => 'litleOnlineResponse')) - identification = "1234;sale" - responseFrom = @gateway.void(identification) - assert_equal false, responseFrom.success? - assert_equal '123456789012345678;void', responseFrom.authorization - end - - def test_void_sale_fail_schema - retObj = {'response'=>'1','message'=>'Error validating xml data against the schema'} - LitleOnline::Communications.expects(:http_post => retObj.to_xml(:root => 'litleOnlineResponse')) - identification = "1234;sale" - responseFrom = @gateway.void(identification) - assert_equal false, responseFrom.success? - assert_equal 'Error validating xml data against the schema', responseFrom.message - end - - def test_void_authorization_pass - authReversalResponseObj = {'response' => '000', 'message' => 'pass', 'litleTxnId'=>'123456789012345678'} - retObj = {'response'=>'0','authReversalResponse'=>authReversalResponseObj} - LitleOnline::Communications.expects(:http_post => retObj.to_xml(:root => 'litleOnlineResponse')) - identification = "1234;authorization" - responseFrom = @gateway.void(identification) - assert_equal true, responseFrom.success? - assert_equal '123456789012345678;authReversal', responseFrom.authorization - end - - def test_void_authorization_fail - authReversalResponseObj = {'response' => '111', 'message' => 'fail', 'litleTxnId'=>'123456789012345678'} - retObj = {'response'=>'0','authReversalResponse'=>authReversalResponseObj} - LitleOnline::Communications.expects(:http_post => retObj.to_xml(:root => 'litleOnlineResponse')) - identification = "1234;authorization" - responseFrom = @gateway.void(identification) - assert_equal false, responseFrom.success? - assert_equal '123456789012345678;authReversal', responseFrom.authorization - end - - def test_void_authorization_fail_schema - retObj = {'response'=>'1','message'=>'Error validating xml data against the schema'} - LitleOnline::Communications.expects(:http_post => retObj.to_xml(:root => 'litleOnlineResponse')) - identification = "1234;authorization" - responseFrom = @gateway.void(identification) - assert_equal false, responseFrom.success? - assert_equal 'Error validating xml data against the schema', responseFrom.message - end - - def test_refund_pass - creditResponseObj = {'response' => '000', 'message' => 'pass', 'litleTxnId'=>'123456789012345678'} - retObj = {'response'=>'0','creditResponse'=>creditResponseObj} - LitleOnline::Communications.expects(:http_post => retObj.to_xml(:root => 'litleOnlineResponse')) - identification = "1234;credit" - responseFrom = @gateway.refund(0, identification) - assert_equal true, responseFrom.success? - assert_equal '123456789012345678;credit', responseFrom.authorization - end - - def test_refund_fail - creditResponseObj = {'response' => '111', 'message' => 'fail', 'litleTxnId'=>'123456789012345678'} - retObj = {'response'=>'0','creditResponse'=>creditResponseObj} - LitleOnline::Communications.expects(:http_post => retObj.to_xml(:root => 'litleOnlineResponse')) - identification = "1234;credit" - responseFrom = @gateway.refund(0, identification) - assert_equal false, responseFrom.success? - assert_equal '123456789012345678;credit', responseFrom.authorization - end - - def test_refund_fail_schema - retObj = {'response'=>'1','message'=>'Error validating xml data against the schema'} - LitleOnline::Communications.expects(:http_post => retObj.to_xml(:root => 'litleOnlineResponse')) - identification = '1234;credit' - responseFrom = @gateway.refund(0, identification) - assert_equal false, responseFrom.success? - assert_equal 'Error validating xml data against the schema', responseFrom.message - end - - def test_store_pass1 - storeResponseObj = {'response' => '801', 'message' => 'successful', 'litleToken'=>'1111222233334444', 'litleTxnId'=>nil} - retObj = {'response'=>'0','registerTokenResponse'=>storeResponseObj} - LitleOnline::Communications.expects(:http_post => retObj.to_xml(:root => 'litleOnlineResponse')) - creditcard = CreditCard.new(@credit_card_options) - - responseFrom = @gateway.store(creditcard,{}) - assert_equal true, responseFrom.success? - assert_equal '1111222233334444', responseFrom.params['litleOnlineResponse']['registerTokenResponse']['litleToken'] - end - - def test_store_pass2 - storeResponseObj = {'response' => '802', 'message' => 'already registered', 'litleToken'=>'1111222233334444', 'litleTxnId'=>nil} - retObj = {'response'=>'0','registerTokenResponse'=>storeResponseObj} - LitleOnline::Communications.expects(:http_post => retObj.to_xml(:root => 'litleOnlineResponse')) - creditcard = CreditCard.new(@credit_card_options) - - responseFrom = @gateway.store(creditcard,{}) - assert_equal true, responseFrom.success? - assert_equal '1111222233334444', responseFrom.params['litleOnlineResponse']['registerTokenResponse']['litleToken'] + def test_successful_void_of_authorization + response = stub_comms do + @gateway.authorize(@amount, @credit_card) + end.respond_with(successful_authorize_response) + + assert_success response + assert_equal '100000000000000001;authorization;100', response.authorization + + void = stub_comms do + @gateway.void(response.authorization) + end.check_request do |endpoint, data, headers| + assert_match(/<authReversal.*<litleTxnId>100000000000000001</m, data) + end.respond_with(successful_void_of_auth_response) + + assert_success void end - def test_store_fail - storeResponseObj = {'response' => '803', 'message' => 'fail', 'litleToken'=>'1111222233334444', 'litleTxnId'=>nil} - retObj = {'response'=>'0','registerTokenResponse'=>storeResponseObj} - LitleOnline::Communications.expects(:http_post => retObj.to_xml(:root => 'litleOnlineResponse')) - creditcard = CreditCard.new(@credit_card_options) + def test_successful_void_of_other_things + refund = stub_comms do + @gateway.refund(@amount, 'SomeAuthorization') + end.respond_with(successful_refund_response) + + assert_equal '100000000000000003;credit;', refund.authorization - responseFrom = @gateway.store(creditcard,{}) - assert_equal false, responseFrom.success? - assert_equal '1111222233334444', responseFrom.params['litleOnlineResponse']['registerTokenResponse']['litleToken'] + void = stub_comms do + @gateway.void(refund.authorization) + end.check_request do |endpoint, data, headers| + assert_match(/<void.*<litleTxnId>100000000000000003</m, data) + end.respond_with(successful_void_of_other_things_response) + + assert_success void end - def test_store_fail_schema - retObj = {'response'=>'1','message'=>'Error validating xml data against the schema'} - LitleOnline::Communications.expects(:http_post => retObj.to_xml(:root => 'litleOnlineResponse')) - creditcard = CreditCard.new(@credit_card_options) + def test_failed_void_of_authorization + response = stub_comms do + @gateway.void('123456789012345360;authorization;100') + end.respond_with(failed_void_of_authorization_response) - responseFrom = @gateway.store(creditcard,{}) - assert_equal false, responseFrom.success? - assert_equal 'Error validating xml data against the schema', responseFrom.message + assert_failure response + assert_equal 'No transaction found with specified litleTxnId', response.message + assert_equal '360', response.params['response'] end - def test_store_via_paypage - storeResponseObj = {'response' => '801', 'message' => 'Account number was successfully registered', 'litleToken'=>'1111222233334444', 'litleTxnId'=>nil} - retObj = {'response'=>'0','registerTokenResponse'=>storeResponseObj} - LitleOnline::Communications.expects(:http_post => retObj.to_xml(:root => 'litleOnlineResponse')) - paypage_registration_id = 'eVY1SUYrSTFndWo0U3p0L2RaWjR1blhMVEh2L0pJTWl5bm1LS3QyUDVWOFRpczRYYS9qbndzLzVEb0M5N3RTcQ==' + def test_failed_void_of_other_things + response = stub_comms do + @gateway.void('123456789012345360;credit;100') + end.respond_with(failed_void_of_other_things_response) - responseFrom = @gateway.store(paypage_registration_id,{}) - assert_equal true, responseFrom.success? - assert_equal '1111222233334444', responseFrom.params['litleOnlineResponse']['registerTokenResponse']['litleToken'] + assert_failure response + assert_equal 'No transaction found with specified litleTxnId', response.message + assert_equal '360', response.params['response'] end - def test_in_production_with_test_param_sends_request_to_test_server - begin - ActiveMerchant::Billing::Base.mode = :production - @gateway = LitleGateway.new( - :merchant_id => 'login', - :login => 'login', - :password => 'password', - :test => true - ) - purchaseResponseObj = {'response' => '000', 'message' => 'successful', 'litleTxnId'=>'123456789012345678'} - retObj = {'response'=>'0','saleResponse'=>purchaseResponseObj} - LitleOnline::Communications.expects(:http_post).with(anything,has_entry('url', 'https://www.testlitle.com/sandbox/communicator/online')).returns(retObj.to_xml(:root => 'litleOnlineResponse')) + def test_successful_void_of_echeck + response = stub_comms do + @gateway.void('945032206979933000;echeckSales;2004') + end.respond_with(successful_void_of_echeck_response) - creditcard = CreditCard.new(@credit_card_options) - assert response = @gateway.purchase(@amount, credit_card) - assert_instance_of Response, response - assert_success response - assert response.test?, response.inspect - ensure - ActiveMerchant::Billing::Base.mode = :test - end + assert_success response + assert_equal '986272331806746000;echeckVoid;', response.authorization end - def test_configured_logger_has_a_default - # The default is actually provided by the LittleOnline gem, but we - # assert its presence in order to show ActiveMerchant need not - # configure a logger - assert LitleOnline::Configuration.logger.is_a?(Logger) + def test_successful_store + response = stub_comms do + @gateway.store(@credit_card) + end.check_request do |endpoint, data, headers| + assert_match(/<accountNumber>4242424242424242</, data) + end.respond_with(successful_store_response) + + assert_success response + assert_equal '1111222233330123', response.authorization end - def test_configured_logger_has_a_default_log_level_defined_by_active_merchant - assert_equal Logger::WARN, LitleOnline::Configuration.logger.level + def test_successful_store_with_paypage_registration_id + response = stub_comms do + @gateway.store('cDZJcmd1VjNlYXNaSlRMTGpocVZQY1NNlYE4ZW5UTko4NU9KK3p1L1p1VzE4ZWVPQVlSUHNITG1JN2I0NzlyTg=') + end.respond_with(successful_store_paypage_response) + + assert_success response + assert_equal '1111222233334444', response.authorization end - def test_configured_logger_respects_any_custom_log_level_set_without_overwriting_it - with_litle_configuration_restoration do - assert_equal Logger::WARN, LitleOnline::Configuration.logger.level + def test_failed_store + response = stub_comms do + @gateway.store(@credit_card) + end.respond_with(failed_store_response) - LitleOnline::Configuration.logger.level = Logger::DEBUG + assert_failure response + assert_equal 'Credit card number was invalid', response.message + assert_equal '820', response.params['response'] + end - LitleGateway.new({:merchant_id=>'101', :user=>'active', :password=>'merchant', :version=>'8.10', :url=>'https://www.testlitle.com/sandbox/communicator/online'}) - assert_equal Logger::WARN, LitleOnline::Configuration.logger.level - end + def test_successful_verify + response = stub_comms do + @gateway.verify(@credit_card) + end.respond_with(successful_authorize_response, successful_void_of_auth_response) + assert_success response end - def test_that_setting_a_wiredump_device_on_the_gateway_sets_the_little_logger_upon_instantiation - with_litle_configuration_restoration do - logger = Logger.new(STDOUT) - ActiveMerchant::Billing::LitleGateway.wiredump_device = logger + def test_successful_verify_failed_void + response = stub_comms do + @gateway.verify(@credit_card, @options) + end.respond_with(successful_authorize_response, failed_void_of_authorization_response) + assert_success response + assert_equal 'Approved', response.message + end + + def test_unsuccessful_verify + response = stub_comms do + @gateway.verify(@credit_card, @options) + end.respond_with(failed_authorize_response, successful_void_of_auth_response) + assert_failure response + assert_equal 'Insufficient Funds', response.message + end - # The gem was already configured in the setup method above - assert_not_equal logger, LitleOnline::Configuration.logger + def test_add_swipe_data_with_creditcard + @credit_card.track_data = 'Track Data' - # The initialize call will setup the logger for the gem - LitleGateway.new({:merchant_id=>'101', :user=>'active', :password=>'merchant', :version=>'8.10', :url=>'https://www.testlitle.com/sandbox/communicator/online'}) - assert_equal logger, LitleOnline::Configuration.logger - assert_equal Logger::DEBUG, LitleOnline::Configuration.logger.level - end + stub_comms do + @gateway.purchase(@amount, @credit_card) + end.check_request do |endpoint, data, headers| + assert_match '<track>Track Data</track>', data + assert_match '<orderSource>retail</orderSource>', data + assert_match %r{<pos>.+<\/pos>}m, data + end.respond_with(successful_purchase_response) end - def test_deprecated_credit - @gateway.expects(:refund).returns true - assert_deprecation_warning(Gateway::CREDIT_DEPRECATION_MESSAGE, @gateway) do - assert response = @gateway.credit(0, '123') - end + def test_order_source_with_creditcard_no_track_data + stub_comms do + @gateway.purchase(@amount, @credit_card) + end.check_request do |endpoint, data, headers| + assert_match '<orderSource>ecommerce</orderSource>', data + assert %r{<pos>.+<\/pos>}m !~ data + end.respond_with(successful_purchase_response) + end + + def test_order_source_override + stub_comms do + @gateway.purchase(@amount, @credit_card, order_source: 'recurring') + end.check_request do |endpoint, data, headers| + assert_match '<orderSource>recurring</orderSource>', data + end.respond_with(successful_purchase_response) + end + + def test_unsuccessful_xml_schema_validation + response = stub_comms do + @gateway.store(@credit_card) + end.respond_with(unsuccessful_xml_schema_validation_response) + + assert_failure response + assert_match(/^Error validating xml data against the schema/, response.message) + assert_equal '1', response.params['response'] + end + + def test_stored_credential_cit_card_on_file_initial + options = @options.merge( + stored_credential: { + initial_transaction: true, + reason_type: 'unscheduled', + initiator: 'cardholder', + network_transaction_id: nil + } + ) + + response = stub_comms do + @gateway.authorize(@amount, @credit_card, options) + end.check_request do |endpoint, data, headers| + assert_match(%r(<processingType>initialCOF</processingType>), data) + end.respond_with(successful_authorize_stored_credentials) + + assert_success response + end + + def test_stored_credential_cit_card_on_file_used + options = @options.merge( + stored_credential: { + initial_transaction: false, + reason_type: 'unscheduled', + initiator: 'cardholder', + network_transaction_id: network_transaction_id + } + ) + + response = stub_comms do + @gateway.authorize(@amount, @credit_card, options) + end.check_request do |endpoint, data, headers| + assert_match(%r(<processingType>cardholderInitiatedCOF</processingType>), data) + assert_match(%r(<originalNetworkTransactionId>#{network_transaction_id}</originalNetworkTransactionId>), data) + assert_match(%r(<orderSource>ecommerce</orderSource>), data) + end.respond_with(successful_authorize_stored_credentials) + + assert_success response + end + + def test_stored_credential_cit_cof_doesnt_override_order_source + options = @options.merge( + order_source: '3dsAuthenticated', + xid: 'BwABBJQ1AgAAAAAgJDUCAAAAAAA=', + cavv: 'BwABBJQ1AgAAAAAgJDUCAAAAAAA=', + stored_credential: { + initial_transaction: false, + reason_type: 'unscheduled', + initiator: 'cardholder', + network_transaction_id: network_transaction_id + } + ) + + response = stub_comms do + @gateway.authorize(@amount, @credit_card, options) + end.check_request do |endpoint, data, headers| + assert_match(%r(<processingType>cardholderInitiatedCOF</processingType>), data) + assert_match(%r(<originalNetworkTransactionId>#{network_transaction_id}</originalNetworkTransactionId>), data) + assert_match(%r(<orderSource>3dsAuthenticated</orderSource>), data) + end.respond_with(successful_authorize_stored_credentials) + + assert_success response + end + + def test_stored_credential_mit_card_on_file_initial + options = @options.merge( + stored_credential: { + initial_transaction: true, + reason_type: 'unscheduled', + initiator: 'merchant', + network_transaction_id: nil + } + ) + + response = stub_comms do + @gateway.authorize(@amount, @credit_card, options) + end.check_request do |endpoint, data, headers| + assert_match(%r(<processingType>initialCOF</processingType>), data) + end.respond_with(successful_authorize_stored_credentials) + + assert_success response + end + + def test_stored_credential_mit_card_on_file_used + options = @options.merge( + stored_credential: { + initial_transaction: false, + reason_type: 'unscheduled', + initiator: 'merchant', + network_transaction_id: network_transaction_id + } + ) + + response = stub_comms do + @gateway.authorize(@amount, @credit_card, options) + end.check_request do |endpoint, data, headers| + assert_match(%r(<processingType>merchantInitiatedCOF</processingType>), data) + assert_match(%r(<originalNetworkTransactionId>#{network_transaction_id}</originalNetworkTransactionId>), data) + assert_match(%r(<orderSource>ecommerce</orderSource>), data) + end.respond_with(successful_authorize_stored_credentials) + + assert_success response + end + + def test_stored_credential_installment_initial + options = @options.merge( + stored_credential: { + initial_transaction: true, + reason_type: 'installment', + initiator: 'merchant', + network_transaction_id: nil + } + ) + + response = stub_comms do + @gateway.authorize(@amount, @credit_card, options) + end.check_request do |endpoint, data, headers| + assert_match(%r(<processingType>initialInstallment</processingType>), data) + end.respond_with(successful_authorize_stored_credentials) + + assert_success response + end + + def test_stored_credential_installment_used + options = @options.merge( + stored_credential: { + initial_transaction: false, + reason_type: 'installment', + initiator: 'merchant', + network_transaction_id: network_transaction_id + } + ) + + response = stub_comms do + @gateway.authorize(@amount, @credit_card, options) + end.check_request do |endpoint, data, headers| + assert_match(%r(<originalNetworkTransactionId>#{network_transaction_id}</originalNetworkTransactionId>), data) + assert_match(%r(<orderSource>installment</orderSource>), data) + end.respond_with(successful_authorize_stored_credentials) + + assert_success response + end + + def test_stored_credential_recurring_initial + options = @options.merge( + stored_credential: { + initial_transaction: true, + reason_type: 'recurring', + initiator: 'merchant', + network_transaction_id: nil + } + ) + + response = stub_comms do + @gateway.authorize(@amount, @credit_card, options) + end.check_request do |endpoint, data, headers| + assert_match(%r(<processingType>initialRecurring</processingType>), data) + end.respond_with(successful_authorize_stored_credentials) + + assert_success response + end + + def test_stored_credential_recurring_used + options = @options.merge( + stored_credential: { + initial_transaction: false, + reason_type: 'recurring', + initiator: 'merchant', + network_transaction_id: network_transaction_id + } + ) + + response = stub_comms do + @gateway.authorize(@amount, @credit_card, options) + end.check_request do |endpoint, data, headers| + assert_match(%r(<originalNetworkTransactionId>#{network_transaction_id}</originalNetworkTransactionId>), data) + assert_match(%r(<orderSource>recurring</orderSource>), data) + end.respond_with(successful_authorize_stored_credentials) + + assert_success response + end + + def test_scrub + assert_equal @gateway.scrub(pre_scrub), post_scrub + end + + def test_supports_scrubbing? + assert @gateway.supports_scrubbing? end private - def with_litle_configuration_restoration(&block) - # Remember the wiredump device since we may overwrite it - existing_wiredump_device = ActiveMerchant::Billing::LitleGateway.wiredump_device + def network_transaction_id + '63225578415568556365452427825' + end + + def successful_purchase_response + %( + <litleOnlineResponse version='8.22' response='0' message='Valid Format' xmlns='http://www.litle.com/schema'> + <saleResponse id='1' reportGroup='Default Report Group' customerId=''> + <litleTxnId>100000000000000006</litleTxnId> + <orderId>1</orderId> + <response>000</response> + <responseTime>2014-03-31T11:34:39</responseTime> + <message>Approved</message> + <authCode>11111 </authCode> + <fraudResult> + <avsResult>01</avsResult> + <cardValidationResult>M</cardValidationResult> + </fraudResult> + </saleResponse> + </litleOnlineResponse> + ) + end + + def successful_purchase_with_echeck_response + %( + <litleOnlineResponse version='9.12' response='0' message='Valid Format' xmlns='http://www.litle.com/schema'> + <echeckSalesResponse id='42' reportGroup='Default Report Group' customerId=''> + <litleTxnId>621100411297330000</litleTxnId> + <orderId>42</orderId> + <response>000</response> + <responseTime>2018-01-09T14:02:20</responseTime> + <message>Approved</message> + </echeckSalesResponse> + </litleOnlineResponse> + ) + end + + def successful_authorize_stored_credentials + %( + <litleOnlineResponse xmlns="http://www.litle.com/schema" version="9.14" response="0" message="Valid Format"> + <authorizationResponse id="1" reportGroup="Default Report Group"> + <litleTxnId>991939023768015826</litleTxnId> + <orderId>1</orderId> + <response>000</response> + <message>Approved</message> + <responseTime>2019-02-26T17:45:29.885</responseTime> + <authCode>75045</authCode> + <networkTransactionId>63225578415568556365452427825</networkTransactionId> + </authorizationResponse> + </litleOnlineResponse> + ) + end + + def failed_purchase_response + %( + <litleOnlineResponse version='8.22' response='0' message='Valid Format' xmlns='http://www.litle.com/schema'> + <saleResponse id='6' reportGroup='Default Report Group' customerId=''> + <litleTxnId>600000000000000002</litleTxnId> + <orderId>6</orderId> + <response>110</response> + <responseTime>2014-03-31T11:48:47</responseTime> + <message>Insufficient Funds</message> + <fraudResult> + <avsResult>34</avsResult> + <cardValidationResult>P</cardValidationResult> + </fraudResult> + </saleResponse> + </litleOnlineResponse> + ) + end + + def successful_authorize_response + %( + <litleOnlineResponse version='8.22' response='0' message='Valid Format' xmlns='http://www.litle.com/schema'> + <authorizationResponse id='1' reportGroup='Default Report Group' customerId=''> + <litleTxnId>100000000000000001</litleTxnId> + <orderId>1</orderId> + <response>000</response> + <responseTime>2014-03-31T12:21:56</responseTime> + <message>Approved</message> + <authCode>11111 </authCode> + <fraudResult> + <avsResult>01</avsResult> + <cardValidationResult>M</cardValidationResult> + </fraudResult> + </authorizationResponse> + </litleOnlineResponse> + ) + end + + def failed_authorize_response + %( + <litleOnlineResponse version='8.22' response='0' message='Valid Format' xmlns='http://www.litle.com/schema'> + <authorizationResponse id='6' reportGroup='Default Report Group' customerId=''> + <litleTxnId>600000000000000001</litleTxnId> + <orderId>6</orderId> + <response>110</response> + <responseTime>2014-03-31T12:24:21</responseTime> + <message>Insufficient Funds</message> + <fraudResult> + <avsResult>34</avsResult> + <cardValidationResult>P</cardValidationResult> + </fraudResult> + </authorizationResponse> + </litleOnlineResponse> + ) + end + + def successful_capture_response + %( + <litleOnlineResponse version='8.22' response='0' message='Valid Format' xmlns='http://www.litle.com/schema'> + <captureResponse id='' reportGroup='Default Report Group' customerId=''> + <litleTxnId>100000000000000002</litleTxnId> + <response>000</response> + <responseTime>2014-03-31T12:28:07</responseTime> + <message>Approved</message> + </captureResponse> + </litleOnlineResponse> + ) + end + + def failed_capture_response + %( + <litleOnlineResponse version='8.22' response='0' message='Valid Format' xmlns='http://www.litle.com/schema'> + <captureResponse id='' reportGroup='Default Report Group' customerId=''> + <litleTxnId>304546900824606360</litleTxnId> + <response>360</response> + <responseTime>2014-03-31T12:30:53</responseTime> + <message>No transaction found with specified litleTxnId</message> + </captureResponse> + </litleOnlineResponse> + ) + end + + def successful_refund_response + %( + <litleOnlineResponse version='8.22' response='0' message='Valid Format' xmlns='http://www.litle.com/schema'> + <creditResponse id='' reportGroup='Default Report Group' customerId=''> + <litleTxnId>100000000000000003</litleTxnId> + <response>000</response> + <responseTime>2014-03-31T12:36:50</responseTime> + <message>Approved</message> + </creditResponse> + </litleOnlineResponse> + ) + end - yield + def failed_refund_response + %( + <litleOnlineResponse version='8.22' response='0' message='Valid Format' xmlns='http://www.litle.com/schema'> + <creditResponse id='' reportGroup='Default Report Group' customerId=''> + <litleTxnId>996483567570258360</litleTxnId> + <response>360</response> + <responseTime>2014-03-31T12:42:41</responseTime> + <message>No transaction found with specified litleTxnId</message> + </creditResponse> + </litleOnlineResponse> + ) + end - # Restore the wiredump device - ActiveMerchant::Billing::LitleGateway.wiredump_device = existing_wiredump_device + def successful_void_of_auth_response + %( + <litleOnlineResponse version='8.22' response='0' message='Valid Format' xmlns='http://www.litle.com/schema'> + <authReversalResponse id='' reportGroup='Default Report Group' customerId=''> + <litleTxnId>972619753208653000</litleTxnId> + <orderId>123</orderId> + <response>000</response> + <responseTime>2014-03-31T12:45:44</responseTime> + <message>Approved</message> + </authReversalResponse> + </litleOnlineResponse> + ) + end + + def successful_void_of_other_things_response + %( + <litleOnlineResponse version='8.22' response='0' message='Valid Format' xmlns='http://www.litle.com/schema'> + <voidResponse id='' reportGroup='Default Report Group' customerId=''> + <litleTxnId>100000000000000004</litleTxnId> + <response>000</response> + <responseTime>2014-03-31T12:44:52</responseTime> + <message>Approved</message> + </voidResponse> + </litleOnlineResponse> + ) + end + + def successful_void_of_echeck_response + %( + <litleOnlineResponse version='9.12' response='0' message='Valid Format' xmlns='http://www.litle.com/schema'> + <echeckVoidResponse id='' reportGroup='Default Report Group' customerId=''> + <litleTxnId>986272331806746000</litleTxnId> + <response>000</response> + <responseTime>2018-01-09T14:20:00</responseTime> + <message>Approved</message> + <postDate>2018-01-09</postDate> + </echeckVoidResponse> + </litleOnlineResponse> + ) + end + + def failed_void_of_authorization_response + %( + <litleOnlineResponse version='8.22' response='0' message='Valid Format' xmlns='http://www.litle.com/schema'> + <authReversalResponse id='' reportGroup='Default Report Group' customerId=''> + <litleTxnId>775712323632364360</litleTxnId> + <orderId>123</orderId> + <response>360</response> + <responseTime>2014-03-31T13:03:17</responseTime> + <message>No transaction found with specified litleTxnId</message> + </authReversalResponse> + </litleOnlineResponse> + ) + end + + def failed_void_of_other_things_response + %( + <litleOnlineResponse version='8.22' response='0' message='Valid Format' xmlns='http://www.litle.com/schema'> + <voidResponse id='' reportGroup='Default Report Group' customerId=''> + <litleTxnId>486912375928374360</litleTxnId> + <response>360</response> + <responseTime>2014-03-31T12:55:46</responseTime> + <message>No transaction found with specified litleTxnId</message> + </voidResponse> + </litleOnlineResponse> + ) + end - # Reset the Litle logger - LitleOnline::Configuration.logger = nil + def successful_store_response + %( + <litleOnlineResponse version='8.22' response='0' message='Valid Format' xmlns='http://www.litle.com/schema'> + <registerTokenResponse id='50' reportGroup='Default Report Group' customerId=''> + <litleTxnId>501000000000000001</litleTxnId> + <orderId>50</orderId> + <litleToken>1111222233330123</litleToken> + <response>801</response> + <responseTime>2014-03-31T13:06:41</responseTime> + <message>Account number was successfully registered</message> + <bin>445711</bin> + <type>VI</type> + </registerTokenResponse> + </litleOnlineResponse> + ) end + + def successful_store_paypage_response + %( + <litleOnlineResponse version='8.2' response='0' message='Valid Format' xmlns='http://www.litle.com/schema'> + <registerTokenResponse id='99999' reportGroup='Default Report Group' customerId=''> + <litleTxnId>222358384397377801</litleTxnId> + <orderId>F12345</orderId> + <litleToken>1111222233334444</litleToken> + <response>801</response> + <responseTime>2015-05-20T14:37:22</responseTime> + <message>Account number was successfully registered</message> + </registerTokenResponse> + </litleOnlineResponse> + ) + end + + def failed_store_response + %( + <litleOnlineResponse version='8.22' response='0' message='Valid Format' xmlns='http://www.litle.com/schema'> + <registerTokenResponse id='51' reportGroup='Default Report Group' customerId=''> + <litleTxnId>510000000000000001</litleTxnId> + <orderId>51</orderId> + <response>820</response> + <responseTime>2014-03-31T13:10:51</responseTime> + <message>Credit card number was invalid</message> + </registerTokenResponse> + </litleOnlineResponse> + ) + end + + def unsuccessful_xml_schema_validation_response + %( + <litleOnlineResponse version='8.29' xmlns='http://www.litle.com/schema' + response='1' + message='Error validating xml data against the schema on line 8\nthe length of the value is 10, but the required minimum is 13.'/> + + ) + end + + def pre_scrub + <<-pre_scrub + opening connection to www.testlitle.com:443... + opened + starting SSL for www.testlitle.com:443... + SSL established + <- "POST /sandbox/communicator/online HTTP/1.1\r\nContent-Type: text/xml\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: www.testlitle.com\r\nContent-Length: 406\r\n\r\n" + <- "<litleOnlineRequest xmlns=\"http://www.litle.com/schema\" merchantId=\"101\" version=\"9.4\">\n <authentication>\n <user>ACTIVE</user>\n <password>MERCHANT</password>\n </authentication>\n <registerTokenRequest reportGroup=\"Default Report Group\">\n <orderId/>\n <accountNumber>4242424242424242</accountNumber>\n <cardValidationNum>111</cardValidationNum>\n </registerTokenRequest>\n</litleOnlineRequest>" + -> "HTTP/1.1 200 OK\r\n" + -> "Date: Mon, 16 May 2016 03:07:36 GMT\r\n" + -> "Server: Apache-Coyote/1.1\r\n" + -> "Content-Type: text/xml;charset=utf-8\r\n" + -> "Connection: close\r\n" + -> "Transfer-Encoding: chunked\r\n" + -> "\r\n" + -> "1bf\r\n" + reading 447 bytes... + -> "" + -> "<litleOnlineResponse version='10.1' response='0' message='Valid Format' xmlns='http://www.litle.com/schema'>\n <registerTokenResponse id='' reportGroup='Default Report Group' customerId=''>\n <litleTxnId>185074924759529000</litleTxnId>\n <litleToken>1111222233334444</litleToken>\n <response>000</response>\n <responseTime>2016-05-15T23:07:36</responseTime>\n <message>Approved</message>\n </registerTokenResponse>\n</litleOnlineResponse>" + read 447 bytes + reading 2 bytes... + -> "" + -> "\r\n" + read 2 bytes + -> "0\r\n" + -> "\r\n" + Conn close + pre_scrub + end + + def post_scrub + <<-post_scrub + opening connection to www.testlitle.com:443... + opened + starting SSL for www.testlitle.com:443... + SSL established + <- "POST /sandbox/communicator/online HTTP/1.1\r\nContent-Type: text/xml\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: www.testlitle.com\r\nContent-Length: 406\r\n\r\n" + <- "<litleOnlineRequest xmlns=\"http://www.litle.com/schema\" merchantId=\"101\" version=\"9.4\">\n <authentication>\n <user>[FILTERED]</user>\n <password>[FILTERED]</password>\n </authentication>\n <registerTokenRequest reportGroup=\"Default Report Group\">\n <orderId/>\n <accountNumber>[FILTERED]</accountNumber>\n <cardValidationNum>[FILTERED]</cardValidationNum>\n </registerTokenRequest>\n</litleOnlineRequest>" + -> "HTTP/1.1 200 OK\r\n" + -> "Date: Mon, 16 May 2016 03:07:36 GMT\r\n" + -> "Server: Apache-Coyote/1.1\r\n" + -> "Content-Type: text/xml;charset=utf-8\r\n" + -> "Connection: close\r\n" + -> "Transfer-Encoding: chunked\r\n" + -> "\r\n" + -> "1bf\r\n" + reading 447 bytes... + -> "" + -> "<litleOnlineResponse version='10.1' response='0' message='Valid Format' xmlns='http://www.litle.com/schema'>\n <registerTokenResponse id='' reportGroup='Default Report Group' customerId=''>\n <litleTxnId>185074924759529000</litleTxnId>\n <litleToken>1111222233334444</litleToken>\n <response>000</response>\n <responseTime>2016-05-15T23:07:36</responseTime>\n <message>Approved</message>\n </registerTokenResponse>\n</litleOnlineResponse>" + read 447 bytes + reading 2 bytes... + -> "" + -> "\r\n" + read 2 bytes + -> "0\r\n" + -> "\r\n" + Conn close + post_scrub + end + end diff --git a/test/unit/gateways/maxipago_test.rb b/test/unit/gateways/maxipago_test.rb new file mode 100644 index 00000000000..b437bcda00a --- /dev/null +++ b/test/unit/gateways/maxipago_test.rb @@ -0,0 +1,371 @@ +require 'test_helper' + +class MaxipagoTest < Test::Unit::TestCase + def setup + @gateway = MaxipagoGateway.new( + :login => 'login', + :password => 'password' + ) + + @credit_card = credit_card + @amount = 100 + + @options = { + :order_id => '1', + :billing_address => address, + :description => 'Store Purchase', + :installments => 3 + } + end + + def test_successful_purchase + @gateway.expects(:ssl_post).returns(successful_purchase_response) + + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + + assert_equal '123456789|123456789', response.authorization + end + + def test_failed_purchase + @gateway.expects(:ssl_post).returns(failed_purchase_response) + + response = @gateway.purchase(@amount, @credit_card, @options) + assert_failure response + end + + def test_successful_authorize + @gateway.expects(:ssl_post).returns(successful_authorize_response) + + response = @gateway.authorize(@amount, @credit_card, @options) + assert_success response + + assert_equal 'C0A8013F:014455FCC857:91A0:01A7243E|663921', response.authorization + assert response.test? + end + + def test_failed_authorize + @gateway.expects(:ssl_post).returns(failed_authorize_response) + + response = @gateway.authorize(@amount, @credit_card, @options) + assert_failure response + end + + def test_successful_capture + @gateway.expects(:ssl_post).returns(successful_capture_response) + + response = @gateway.capture(nil, 'authorization', @options) + assert_success response + end + + def test_failed_capture + @gateway.expects(:ssl_post).returns(failed_capture_response) + + response = @gateway.capture(nil, 'bogus', @options) + assert_failure response + end + + def test_successful_void + @gateway.expects(:ssl_post).returns(successful_authorize_response) + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + @gateway.expects(:ssl_post).returns(successful_void_response) + void = @gateway.void(auth.authorization) + assert_success void + assert_equal 'VOIDED', void.params['response_message'] + end + + def test_failed_void + @gateway.expects(:ssl_post).returns(failed_void_response) + response = @gateway.void('NOAUTH|0000000') + assert_failure response + assert_equal 'Unable to validate, original void transaction not found', response.message + end + + def test_successful_refund + @gateway.expects(:ssl_post).returns(successful_purchase_response) + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + + @gateway.expects(:ssl_post).returns(successful_refund_response) + refund = @gateway.refund(@amount, purchase.authorization, @options) + assert_success refund + assert_equal 'CAPTURED', refund.message + end + + def test_failed_refund + @gateway.expects(:ssl_post).returns(successful_purchase_response) + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + + @gateway.expects(:ssl_post).returns(failed_refund_response) + refund_amount = @amount + 10 + refund = @gateway.refund(refund_amount, purchase.authorization, @options) + assert_failure refund + assert_equal 'The Return amount is greater than the amount that can be returned.', refund.message + end + + def test_successful_verify + @gateway.expects(:ssl_post).times(2).returns(successful_authorize_response).then.returns(successful_void_response) + response = @gateway.verify(@credit_card, @options) + assert_success response + assert_equal 'AUTHORIZED', response.message + end + + def test_failed_verify + @gateway.expects(:ssl_post).returns(failed_authorize_response) + response = @gateway.verify(@credit_card, @options) + assert_failure response + assert_equal 'The transaction has an expired credit card.', response.message + end + + def test_scrub + assert @gateway.supports_scrubbing? + assert_equal @gateway.scrub(pre_scrubbed), post_scrubbed + end + + private + + def pre_scrubbed + %( + opening connection to testapi.maxipago.net:443... + opened + starting SSL for testapi.maxipago.net:443... + SSL established + <- "POST /UniversalAPI/postXML HTTP/1.1\r\nContent-Type: text/xml\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: testapi.maxipago.net\r\nContent-Length: 1224\r\n\r\n" + <- "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<transaction-request>\n <version>3.1.1.15</version>\n <verification>\n <merchantId>100</merchantId>\n <merchantKey>21g8u6gh6szw1gywfs165vui</merchantKey>\n </verification>\n <order>\n <sale>\n <processorID>1</processorID>\n <fraudCheck>N</fraudCheck>\n <referenceNum>12345</referenceNum>\n <transactionDetail>\n <payType>\n <creditCard>\n <number>4111111111111111</number>\n <expMonth>9</expMonth>\n <expYear>2017</expYear>\n <cvvNumber>444</cvvNumber>\n </creditCard>\n </payType>\n </transactionDetail>\n <payment>\n <chargeTotal>10.00</chargeTotal>\n <creditInstallment>\n <numberOfInstallments>3</numberOfInstallments>\n <chargeInterest>N</chargeInterest>\n </creditInstallment>\n </payment>\n <billing>\n <name>Longbob Longsen</name>\n <address>456 My Street</address>\n <address2>Apt 1</address2>\n <city>Ottawa</city>\n <state>ON</state>\n <postalcode>K1C2N6</postalcode>\n <country>CA</country>\n <phone>(555)555-5555</phone>\n </billing>\n </sale>\n </order>\n</transaction-request>\n" + -> "HTTP/1.1 200 OK\r\n" + -> "Date: Thu, 09 Jun 2016 14:54:53 GMT\r\n" + -> "Server: Apache\r\n" + -> "Strict-Transport-Security: max-age=31557600; includeSubDomains\r\n" + -> "Content-Length: 628\r\n" + -> "X-Frame-Options: SAMEORIGIN\r\n" + -> "Connection: close\r\n" + -> "Content-Type: text/plain; charset=UTF-8\r\n" + -> "\r\n" + reading 628 bytes... + -> "" + -> "<?xml version=\"1.0\" encoding=\"UTF-8\"?><transaction-response>\n<authCode>123456</authCode>\n<orderID>C0A8013F:015535A8A798:D12E:006BB50C</orderID>\n<referenceNum>12345</referenceNum>\n<transactionID>1410203</transactionID>\n<transactionTimestamp>1465484093</transactionTimestamp>\n<responseCode>0</responseCode>\n<responseMessage>CAPTURED</responseMessage>\n<avsResponseCode>YYY</avsResponseCode>\n<cvvResponseCode>M</cvvResponseCode>\n<processorCode>A</processorCode>\n<processorMessage>APPROVED</processorMessage>\n<errorMessage/>\n<creditCardCountry>US</creditCardCountry>\n<creditCardScheme>Visa</creditCardScheme>\n</transaction-response>\n" + read 628 bytes + Conn close + ) + end + + def post_scrubbed + %( + opening connection to testapi.maxipago.net:443... + opened + starting SSL for testapi.maxipago.net:443... + SSL established + <- "POST /UniversalAPI/postXML HTTP/1.1\r\nContent-Type: text/xml\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: testapi.maxipago.net\r\nContent-Length: 1224\r\n\r\n" + <- "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<transaction-request>\n <version>3.1.1.15</version>\n <verification>\n <merchantId>100</merchantId>\n <merchantKey>[FILTERED]</merchantKey>\n </verification>\n <order>\n <sale>\n <processorID>1</processorID>\n <fraudCheck>N</fraudCheck>\n <referenceNum>12345</referenceNum>\n <transactionDetail>\n <payType>\n <creditCard>\n <number>[FILTERED]</number>\n <expMonth>9</expMonth>\n <expYear>2017</expYear>\n <cvvNumber>[FILTERED]</cvvNumber>\n </creditCard>\n </payType>\n </transactionDetail>\n <payment>\n <chargeTotal>10.00</chargeTotal>\n <creditInstallment>\n <numberOfInstallments>3</numberOfInstallments>\n <chargeInterest>N</chargeInterest>\n </creditInstallment>\n </payment>\n <billing>\n <name>Longbob Longsen</name>\n <address>456 My Street</address>\n <address2>Apt 1</address2>\n <city>Ottawa</city>\n <state>ON</state>\n <postalcode>K1C2N6</postalcode>\n <country>CA</country>\n <phone>(555)555-5555</phone>\n </billing>\n </sale>\n </order>\n</transaction-request>\n" + -> "HTTP/1.1 200 OK\r\n" + -> "Date: Thu, 09 Jun 2016 14:54:53 GMT\r\n" + -> "Server: Apache\r\n" + -> "Strict-Transport-Security: max-age=31557600; includeSubDomains\r\n" + -> "Content-Length: 628\r\n" + -> "X-Frame-Options: SAMEORIGIN\r\n" + -> "Connection: close\r\n" + -> "Content-Type: text/plain; charset=UTF-8\r\n" + -> "\r\n" + reading 628 bytes... + -> "" + -> "<?xml version=\"1.0\" encoding=\"UTF-8\"?><transaction-response>\n<authCode>123456</authCode>\n<orderID>C0A8013F:015535A8A798:D12E:006BB50C</orderID>\n<referenceNum>12345</referenceNum>\n<transactionID>1410203</transactionID>\n<transactionTimestamp>1465484093</transactionTimestamp>\n<responseCode>0</responseCode>\n<responseMessage>CAPTURED</responseMessage>\n<avsResponseCode>YYY</avsResponseCode>\n<cvvResponseCode>M</cvvResponseCode>\n<processorCode>A</processorCode>\n<processorMessage>APPROVED</processorMessage>\n<errorMessage/>\n<creditCardCountry>US</creditCardCountry>\n<creditCardScheme>Visa</creditCardScheme>\n</transaction-response>\n" + read 628 bytes + Conn close + ) + end + + def successful_purchase_response + %( + <transaction-response> + <authCode>555555</authCode> + <orderID>123456789</orderID> + <referenceNum>123456789</referenceNum> + <transactionID>123456789</transactionID> + <transactionTimestamp>123456789</transactionTimestamp> + <responseCode>0</responseCode> + <responseMessage>CAPTURED</responseMessage> + <avsResponseCode/> + <cvvResponseCode/> + <processorCode>0</processorCode> + <processorMessage>APPROVED</processorMessage> + <errorMessage/> + <processorTransactionID>123456789</processorTransactionID> + <processorReferenceNumber>123456789</processorReferenceNumber> + <fraudScore>29</fraudScore> + </transaction-response> + ) + end + + def failed_purchase_response + %( + <transaction-response> + <authCode/> + <orderID>123456789</orderID> + <referenceNum>123456789</referenceNum> + <transactionID>123456789</transactionID> + <transactionTimestamp>123456789</transactionTimestamp> + <responseCode>1</responseCode> + <responseMessage>DECLINED</responseMessage> + <avsResponseCode>NNN</avsResponseCode> + <cvvResponseCode>N</cvvResponseCode> + <processorCode>D</processorCode> + <processorMessage>DECLINED</processorMessage> + <errorMessage/> + </transaction-response> + ) + end + + def successful_authorize_response + %( + <?xml version="1.0" encoding="UTF-8"?> + <transaction-response> + <authCode>123456</authCode> + <orderID>C0A8013F:014455FCC857:91A0:01A7243E</orderID> + <referenceNum>12345</referenceNum> + <transactionID>663921</transactionID> + <transactionTimestamp>1393012206</transactionTimestamp> + <responseCode>0</responseCode> + <responseMessage>AUTHORIZED</responseMessage> + <avsResponseCode>YYY</avsResponseCode> + <cvvResponseCode>M</cvvResponseCode> + <processorCode>A</processorCode> + <processorMessage>APPROVED</processorMessage> + <errorMessage/> + </transaction-response> + ) + end + + def failed_authorize_response + %( + <?xml version="1.0" encoding="UTF-8"?> + <transaction-response> + <authCode/> + <orderID/> + <referenceNum/> + <transactionID/> + <transactionTimestamp>1393012170003</transactionTimestamp> + <responseCode>1024</responseCode> + <responseMessage>INVALID REQUEST</responseMessage> + <avsResponseCode/> + <cvvResponseCode/> + <processorCode/> + <processorMessage/> + <errorMessage>The transaction has an expired credit card.</errorMessage> + </transaction-response> + ) + end + + def successful_capture_response + %( + <?xml version="1.0" encoding="UTF-8"?> + <transaction-response> + <authCode/> + <orderID>C0A8013F:014455FF974D:82CA:01C7717B</orderID> + <referenceNum>12345</referenceNum> + <transactionID>663924</transactionID> + <transactionTimestamp>1393012391</transactionTimestamp> + <responseCode>0</responseCode> + <responseMessage>CAPTURED</responseMessage> + <avsResponseCode/> + <cvvResponseCode/> + <processorCode>A</processorCode> + <processorMessage>APPROVED</processorMessage> + <errorMessage/> + </transaction-response> + ) + end + + def failed_capture_response + %( + <?xml version="1.0" encoding="UTF-8"?> + <transaction-response> + <authCode/> + <orderID/> + <referenceNum/> + <transactionID/> + <transactionTimestamp>1393012277035</transactionTimestamp> + <responseCode>1024</responseCode> + <responseMessage>INVALID REQUEST</responseMessage> + <avsResponseCode/> + <cvvResponseCode/> + <processorCode/> + <processorMessage/> + <errorMessage>Reference Number is a required field.</errorMessage> + </transaction-response> + ) + end + + def successful_void_response + %( + <?xml version="1.0" encoding="UTF-8"?> + <transaction-response> + <authCode/> + <orderID/> + <referenceNum/> + <transactionID>1408584</transactionID> + <transactionTimestamp/> + <responseCode>0</responseCode> + <responseMessage>VOIDED</responseMessage> + <avsResponseCode/> + <cvvResponseCode/> + <processorCode>A</processorCode> + <processorMessage>APPROVED</processorMessage> + <errorMessage/> + </transaction-response> + ) + end + + def failed_void_response + %( + <?xml version="1.0" encoding="UTF-8" standalone="yes"?> + <api-error> + <errorCode>1</errorCode> + <errorMsg><![CDATA[Unable to validate, original void transaction not found]]></errorMsg> + </api-error> + ) + end + + def successful_refund_response + %( + <?xml version="1.0" encoding="UTF-8"?> + <transaction-response> + <authCode/> + <orderID>C0A8013F:015527599F53:FAF8:0155C41C</orderID> + <referenceNum>12345</referenceNum> + <transactionID>1408589</transactionID> + <transactionTimestamp>1465244034</transactionTimestamp> + <responseCode>0</responseCode> + <responseMessage>CAPTURED</responseMessage> + <avsResponseCode/> + <cvvResponseCode/> + <processorCode>A</processorCode> + <processorMessage>APPROVED</processorMessage> + <errorMessage/> + <creditCardScheme>Visa</creditCardScheme> + </transaction-response> + ) + end + + def failed_refund_response + %( + <?xml version="1.0" encoding="UTF-8"?> + <transaction-response> + <authCode/> + <orderID/> + <referenceNum/> + <transactionID/> + <transactionTimestamp>1465244175808</transactionTimestamp> + <responseCode>1024</responseCode> + <responseMessage>INVALID REQUEST</responseMessage> + <avsResponseCode/> + <cvvResponseCode/> + <processorCode/> + <processorMessage/> + <errorMessage>The Return amount is greater than the amount that can be returned.</errorMessage> + </transaction-response> + ) + end +end diff --git a/test/unit/gateways/mercado_pago_test.rb b/test/unit/gateways/mercado_pago_test.rb new file mode 100644 index 00000000000..a0a9a0c3429 --- /dev/null +++ b/test/unit/gateways/mercado_pago_test.rb @@ -0,0 +1,505 @@ +require 'test_helper' + +class MercadoPagoTest < Test::Unit::TestCase + include CommStub + + def setup + @gateway = MercadoPagoGateway.new(access_token: 'access_token') + @credit_card = credit_card + @elo_credit_card = credit_card('5067268650517446', + :month => 10, + :year => 2020, + :first_name => 'John', + :last_name => 'Smith', + :verification_value => '737' + ) + @cabal_credit_card = credit_card('6035227716427021', + :month => 10, + :year => 2020, + :first_name => 'John', + :last_name => 'Smith', + :verification_value => '737' + ) + @amount = 100 + + @options = { + order_id: '1', + billing_address: address, + description: 'Store Purchase' + } + end + + def test_successful_purchase + @gateway.expects(:ssl_post).at_most(2).returns(successful_purchase_response) + + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + + assert_equal '4141491|1.0', response.authorization + assert_equal 'accredited', response.message + assert response.test? + end + + def test_successful_purchase_with_elo + @gateway.expects(:ssl_post).at_most(2).returns(successful_purchase_with_elo_response) + + response = @gateway.purchase(@amount, @elo_credit_card, @options) + assert_success response + + assert_equal '18044843|1.0', response.authorization + assert_equal 'accredited', response.message + assert response.test? + end + + def test_successful_purchase_with_cabal + @gateway.expects(:ssl_post).at_most(2).returns(successful_purchase_with_cabal_response) + + response = @gateway.purchase(@amount, @cabal_credit_card, @options) + assert_success response + + assert_equal '20728968|1.0', response.authorization + assert_equal 'accredited', response.message + assert response.test? + end + + def test_failed_purchase + @gateway.expects(:ssl_post).at_most(2).returns(failed_purchase_response) + + response = @gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_equal 'rejected', response.error_code + assert_equal 'cc_rejected_other_reason', response.message + end + + def test_successful_authorize + @gateway.expects(:ssl_post).at_most(2).returns(successful_authorize_response) + + response = @gateway.authorize(@amount, @credit_card, @options) + assert_success response + + assert_equal '4261941|1.0', response.authorization + assert_equal 'pending_capture', response.message + assert response.test? + end + + def test_successful_authorize_with_elo + @gateway.expects(:ssl_post).at_most(2).returns(successful_authorize_with_elo_response) + + response = @gateway.authorize(@amount, @elo_credit_card, @options) + assert_success response + + assert_equal '18044850|1.0', response.authorization + assert_equal 'pending_capture', response.message + assert response.test? + end + + def test_successful_authorize_with_cabal + @gateway.expects(:ssl_post).at_most(2).returns(successful_authorize_with_cabal_response) + + response = @gateway.authorize(@amount, @cabal_credit_card, @options) + assert_success response + + assert_equal '20729288|1.0', response.authorization + assert_equal 'pending_capture', response.message + assert response.test? + end + + def test_failed_authorize + @gateway.expects(:ssl_post).at_most(2).returns(failed_authorize_response) + + response = @gateway.authorize(@amount, @credit_card, @options) + assert_failure response + assert_equal 'rejected', response.error_code + assert_equal 'cc_rejected_other_reason', response.message + end + + def test_successful_capture + @gateway.expects(:ssl_request).returns(successful_capture_response) + + response = @gateway.capture(@amount, 'authorization|amount') + assert_success response + + assert_equal '4261941|1.0', response.authorization + assert_equal 'accredited', response.message + assert response.test? + end + + def test_successful_capture_with_elo + @gateway.expects(:ssl_request).returns(successful_capture_with_elo_response) + + response = @gateway.capture(@amount, 'authorization|amount') + assert_success response + + assert_equal '18044850|1.0', response.authorization + assert_equal 'accredited', response.message + assert response.test? + end + + def test_successful_capture_with_cabal + @gateway.expects(:ssl_request).returns(successful_capture_with_cabal_response) + + response = @gateway.capture(@amount, 'authorization|amount') + assert_success response + + assert_equal '20729288|1.0', response.authorization + assert_equal 'accredited', response.message + assert response.test? + end + + def test_failed_capture + @gateway.expects(:ssl_request).returns(failed_capture_response) + + response = @gateway.capture(@amount, '') + assert_failure response + + assert_equal '|1.0', response.authorization + assert_equal 'Method not allowed', response.message + assert response.test? + end + + def test_successful_refund + @gateway.expects(:ssl_post).returns(successful_refund_response) + + response = @gateway.refund(@amount, 'authorization|1.0', @options) + assert_success response + end + + def test_failed_refund + @gateway.expects(:ssl_post).returns(failed_refund_response) + + response = @gateway.refund(@amount, '', @options) + assert_failure response + assert_equal nil, response.error_code + end + + def test_successful_void + @gateway.expects(:ssl_request).returns(successful_void_response) + + response = @gateway.void('authorization|amount') + assert_success response + + assert_equal '4261966|', response.authorization + assert_equal 'by_collector', response.message + assert response.test? + end + + def test_failed_void + @gateway.expects(:ssl_request).returns(failed_void_response) + + response = @gateway.void('') + assert_failure response + + assert_equal '|', response.authorization + assert_equal 'Method not allowed', response.message + assert response.test? + end + + def test_successful_verify + @gateway.expects(:ssl_request).at_most(3).returns(successful_void_response) + + response = @gateway.verify(@credit_card, @options) + assert_success response + + assert_equal 'by_collector', response.message + assert response.test? + end + + def test_successful_verify_with_failed_void + @gateway.expects(:ssl_request).at_most(3).returns(failed_void_response) + + response = @gateway.verify(@credit_card, @options) + assert_failure response + + assert_equal 'Method not allowed', response.message + assert response.test? + end + + def test_failed_verify + @gateway.expects(:ssl_request).at_most(2).returns(failed_authorize_response) + + response = @gateway.verify(@credit_card, @options) + assert_failure response + + assert_equal 'cc_rejected_other_reason', response.message + assert response.test? + end + + def test_scrub + assert @gateway.supports_scrubbing? + assert_equal @gateway.scrub(pre_scrubbed), post_scrubbed + end + + def test_does_not_send_brand + credit_card = credit_card('378282246310005', brand: 'american_express') + + response = stub_comms do + @gateway.purchase(@amount, credit_card, @options) + end.check_request do |endpoint, data, headers| + if endpoint =~ /payments/ + assert_not_match(%r("payment_method_id":"amex"), data) + end + end.respond_with(successful_purchase_response) + + assert_success response + assert_equal '4141491|1.0', response.authorization + end + + def test_sends_payment_method_id + credit_card = credit_card('30569309025904') + + response = stub_comms do + @gateway.purchase(@amount, credit_card, @options.merge(payment_method_id: 'diners')) + end.check_request do |endpoint, data, headers| + if endpoint =~ /payments/ + assert_match(%r("payment_method_id":"diners"), data) + end + end.respond_with(successful_purchase_response) + + assert_success response + assert_equal '4141491|1.0', response.authorization + end + + def test_includes_deviceid_header + @options[:device_id] = '1a2b3c' + @gateway.expects(:ssl_post).with(anything, anything, {'Content-Type' => 'application/json'}).returns(successful_purchase_response) + @gateway.expects(:ssl_post).with(anything, anything, {'Content-Type' => 'application/json', 'X-Device-Session-ID' => '1a2b3c'}).returns(successful_purchase_response) + + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + end + + def test_includes_additional_data + @options[:additional_info] = {'foo' => 'bar', 'baz' => 'quux'} + response = stub_comms do + @gateway.purchase(@amount, @credit_card, @options) + end.check_request do |endpoint, data, headers| + if data =~ /payment_method_id/ + assert_match(/"foo":"bar"/, data) + assert_match(/"baz":"quux"/, data) + end + end.respond_with(successful_purchase_response) + + assert_success response + end + + def test_includes_issuer_id + response = stub_comms do + @gateway.purchase(@amount, @credit_card, @options.merge(issuer_id: '1a2b3c4d')) + end.check_request do |endpoint, data, headers| + if endpoint =~ /payments/ + assert_match(%r("issuer_id":"1a2b3c4d"), data) + end + end.respond_with(successful_purchase_response) + + assert_success response + assert_equal '4141491|1.0', response.authorization + end + + private + + def pre_scrubbed + %q( + opening connection to api.mercadopago.com:443... + opened + starting SSL for api.mercadopago.com:443... + SSL established + <- "POST /v1/card_tokens?access_token=TEST-8527269031909288-071213-0fc96cb7cd3633189bfbe29f63722700__LB_LA__-263489584 HTTP/1.1\r\nContent-Type: application/json\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: api.mercadopago.com\r\nContent-Length: 140\r\n\r\n" + <- "{\"card_number\":\"4509953566233704\",\"security_code\":\"123\",\"expiration_month\":9,\"expiration_year\":2018,\"cardholder\":{\"name\":\"Longbob Longsen\"}}" + -> "HTTP/1.1 201 Created\r\n" + -> "Access-Control-Allow-Origin: *\r\n" + -> "X-Request-Id: eb7a95a0-dccb-4580-9a69-534f6faf0bd6\r\n" + -> "Content-Type: application/json;charset=utf-8\r\n" + -> "Date: Thu, 13 Jul 2017 17:37:58 GMT\r\n" + -> "Connection: close\r\n" + -> "X-XSS-Protection: 1; mode=block\r\n" + -> "X-Content-Type-Options: nosniff\r\n" + -> "Strict-Transport-Security: max-age=16070400\r\n" + -> "Set-Cookie: TS016da221=0119b547a2244bba3789910575ac019d7d44d644026217ca433918a8c8fd9ff83de9d4b3c095adc76ee58870b56cd33041797db9e2; Path=/; Secure; HTTPOnly\r\n" + -> "Vary: Accept-Encoding, User-Agent\r\n" + -> "Content-Encoding: gzip\r\n" + -> "Transfer-Encoding: chunked\r\n" + -> "\r\n" + Conn close + opening connection to api.mercadopago.com:443... + opened + starting SSL for api.mercadopago.com:443... + SSL established + <- "POST /v1/payments?access_token=TEST-8527269031909288-071213-0fc96cb7cd3633189bfbe29f63722700__LB_LA__-263489584 HTTP/1.1\r\nContent-Type: application/json\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: api.mercadopago.com\r\nContent-Length: 395\r\n\r\n" + <- "{\"transaction_amount\":5.0,\"description\":\"Store Purchase\",\"installments\":1,\"order\":{\"type\":\"mercadopago\",\"id\":2554731505684667137},\"token\":\"02ed9760103508d54361da8741a22a9e\",\"payment_method_id\":\"visa\",\"additional_info\":{\"payer\":{\"address\":{\"zip_code\":\"K1C2N6\",\"street_number\":\"456\",\"street_name\":\"My Street\"}}},\"payer\":{\"email\":\"user+br@example.com\",\"first_name\":\"Longbob\",\"last_name\":\"Longsen\"}}" + -> "HTTP/1.1 201 Created\r\n" + -> "Date: Thu, 13 Jul 2017 17:37:59 GMT\r\n" + -> "Content-Type: application/json;charset=UTF-8\r\n" + -> "Connection: close\r\n" + -> "X-Response-Status: approved/accredited\r\n" + -> "X-Caller-Id: 263489584\r\n" + -> "Vary: Accept,Accept-Encoding, User-Agent\r\n" + -> "Cache-Control: max-age=0\r\n" + -> "ETag: 1deee4b03ae344416c5863ac0d92c13e\r\n" + -> "X-Content-Type-Options: nosniff\r\n" + -> "X-Frame-Options: DENY\r\n" + -> "X-Request-Id: ccb324d1-8365-42dd-8e9a-734488220777\r\n" + -> "X-XSS-Protection: 1; mode=block\r\n" + -> "Strict-Transport-Security: max-age=15724800\r\n" + -> "Access-Control-Allow-Origin: *\r\n" + -> "Access-Control-Allow-Headers: Content-Type\r\n" + -> "Access-Control-Allow-Methods: PUT, GET, POST, DELETE, OPTIONS\r\n" + -> "Access-Control-Max-Age: 86400\r\n" + -> "Set-Cookie: TS016da221=0119b547a287375accd901052e4871cecbc881599be32e9bcb508701e62cabee4424801a25969778d1c93e2c57c2fd0a8a934c9817; Path=/; Secure; HTTPOnly\r\n" + -> "Content-Encoding: gzip\r\n" + -> "Transfer-Encoding: chunked\r\n" + -> "\r\n" + Conn close + ) + end + + def post_scrubbed + %q( + opening connection to api.mercadopago.com:443... + opened + starting SSL for api.mercadopago.com:443... + SSL established + <- "POST /v1/card_tokens?access_token=[FILTERED] HTTP/1.1\r\nContent-Type: application/json\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: api.mercadopago.com\r\nContent-Length: 140\r\n\r\n" + <- "{\"card_number\":\"[FILTERED]\",\"security_code\":\"[FILTERED]\",\"expiration_month\":9,\"expiration_year\":2018,\"cardholder\":{\"name\":\"Longbob Longsen\"}}" + -> "HTTP/1.1 201 Created\r\n" + -> "Access-Control-Allow-Origin: *\r\n" + -> "X-Request-Id: eb7a95a0-dccb-4580-9a69-534f6faf0bd6\r\n" + -> "Content-Type: application/json;charset=utf-8\r\n" + -> "Date: Thu, 13 Jul 2017 17:37:58 GMT\r\n" + -> "Connection: close\r\n" + -> "X-XSS-Protection: 1; mode=block\r\n" + -> "X-Content-Type-Options: nosniff\r\n" + -> "Strict-Transport-Security: max-age=16070400\r\n" + -> "Set-Cookie: TS016da221=0119b547a2244bba3789910575ac019d7d44d644026217ca433918a8c8fd9ff83de9d4b3c095adc76ee58870b56cd33041797db9e2; Path=/; Secure; HTTPOnly\r\n" + -> "Vary: Accept-Encoding, User-Agent\r\n" + -> "Content-Encoding: gzip\r\n" + -> "Transfer-Encoding: chunked\r\n" + -> "\r\n" + Conn close + opening connection to api.mercadopago.com:443... + opened + starting SSL for api.mercadopago.com:443... + SSL established + <- "POST /v1/payments?access_token=[FILTERED] HTTP/1.1\r\nContent-Type: application/json\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: api.mercadopago.com\r\nContent-Length: 395\r\n\r\n" + <- "{\"transaction_amount\":5.0,\"description\":\"Store Purchase\",\"installments\":1,\"order\":{\"type\":\"mercadopago\",\"id\":2554731505684667137},\"token\":\"02ed9760103508d54361da8741a22a9e\",\"payment_method_id\":\"visa\",\"additional_info\":{\"payer\":{\"address\":{\"zip_code\":\"K1C2N6\",\"street_number\":\"456\",\"street_name\":\"My Street\"}}},\"payer\":{\"email\":\"user+br@example.com\",\"first_name\":\"Longbob\",\"last_name\":\"Longsen\"}}" + -> "HTTP/1.1 201 Created\r\n" + -> "Date: Thu, 13 Jul 2017 17:37:59 GMT\r\n" + -> "Content-Type: application/json;charset=UTF-8\r\n" + -> "Connection: close\r\n" + -> "X-Response-Status: approved/accredited\r\n" + -> "X-Caller-Id: 263489584\r\n" + -> "Vary: Accept,Accept-Encoding, User-Agent\r\n" + -> "Cache-Control: max-age=0\r\n" + -> "ETag: 1deee4b03ae344416c5863ac0d92c13e\r\n" + -> "X-Content-Type-Options: nosniff\r\n" + -> "X-Frame-Options: DENY\r\n" + -> "X-Request-Id: ccb324d1-8365-42dd-8e9a-734488220777\r\n" + -> "X-XSS-Protection: 1; mode=block\r\n" + -> "Strict-Transport-Security: max-age=15724800\r\n" + -> "Access-Control-Allow-Origin: *\r\n" + -> "Access-Control-Allow-Headers: Content-Type\r\n" + -> "Access-Control-Allow-Methods: PUT, GET, POST, DELETE, OPTIONS\r\n" + -> "Access-Control-Max-Age: 86400\r\n" + -> "Set-Cookie: TS016da221=0119b547a287375accd901052e4871cecbc881599be32e9bcb508701e62cabee4424801a25969778d1c93e2c57c2fd0a8a934c9817; Path=/; Secure; HTTPOnly\r\n" + -> "Content-Encoding: gzip\r\n" + -> "Transfer-Encoding: chunked\r\n" + -> "\r\n" + Conn close + ) + end + + def successful_purchase_response + %( + {"id":4141491,"date_created":"2017-07-06T09:49:35.000-04:00","date_approved":"2017-07-06T09:49:35.000-04:00","date_last_updated":"2017-07-06T09:49:35.000-04:00","date_of_expiration":null,"money_release_date":"2017-07-18T09:49:35.000-04:00","operation_type":"regular_payment","issuer_id":"166","payment_method_id":"visa","payment_type_id":"credit_card","status":"approved","status_detail":"accredited","currency_id":"MXN","description":"Store Purchase","live_mode":false,"sponsor_id":null,"authorization_code":null,"related_exchange_rate":null,"collector_id":261735089,"payer":{"type":"guest","id":null,"email":"user@example.com","identification":{"type":null,"number":null},"phone":{"area_code":null,"number":null,"extension":""},"first_name":"First User","last_name":"User","entity_type":null},"metadata":{},"additional_info":{"payer":{"address":{"zip_code":"K1C2N6","street_name":"My Street","street_number":"456"}}},"order":{"type":"mercadopago","id":"2326513804447055222"},"external_reference":null,"transaction_amount":5,"transaction_amount_refunded":0,"coupon_amount":0,"differential_pricing_id":null,"deduction_schema":null,"transaction_details":{"net_received_amount":0.14,"total_paid_amount":5,"overpaid_amount":0,"external_resource_url":null,"installment_amount":5,"financial_institution":null,"payment_method_reference_id":null,"payable_deferral_period":null,"acquirer_reference":null},"fee_details":[{"type":"mercadopago_fee","amount":4.86,"fee_payer":"collector"}],"captured":true,"binary_mode":false,"call_for_authorize_id":null,"statement_descriptor":"WWW.MERCADOPAGO.COM","installments":1,"card":{"id":null,"first_six_digits":"450995","last_four_digits":"3704","expiration_month":9,"expiration_year":2018,"date_created":"2017-07-06T09:49:35.000-04:00","date_last_updated":"2017-07-06T09:49:35.000-04:00","cardholder":{"name":"Longbob Longsen","identification":{"number":null,"type":null}}},"notification_url":null,"refunds":[],"processing_mode":null,"merchant_account_id":null,"acquirer":null,"merchant_number":null} + ) + end + + def successful_purchase_with_elo_response + %( + {"id":18044843,"date_created":"2019-03-04T09:39:21.000-04:00","date_approved":"2019-03-04T09:39:22.000-04:00","date_last_updated":"2019-03-04T09:39:22.000-04:00","date_of_expiration":null,"money_release_date":"2019-03-18T09:39:22.000-04:00","operation_type":"regular_payment","issuer_id":"687","payment_method_id":"elo","payment_type_id":"credit_card","status":"approved","status_detail":"accredited","currency_id":"BRL","description":"Store Purchase","live_mode":false,"sponsor_id":null,"authorization_code":null,"money_release_schema":null,"taxes_amount":0,"counter_currency":null,"shipping_amount":0,"pos_id":null,"store_id":null,"collector_id":263489584,"payer":{"first_name":"Test","last_name":"Test","email":"test_user_80507629@testuser.com","identification":{"number":"32659430","type":"DNI"},"phone":{"area_code":"01","number":"1111-1111","extension":""},"type":"registered","entity_type":null,"id":"412997143"},"metadata":{},"additional_info":{"payer":{"address":{"zip_code":"K1C2N6","street_name":"456 My Street Apt 1"}},"shipments":{"receiver_address":{"zip_code":"K1C2N6","street_name":"456 My Street Apt 1"}}},"order":{},"external_reference":"6cc551da28909403939d024cd067a65f","transaction_amount":5,"transaction_amount_refunded":0,"coupon_amount":0,"differential_pricing_id":null,"deduction_schema":null,"transaction_details":{"net_received_amount":4.75,"total_paid_amount":5,"overpaid_amount":0,"external_resource_url":null,"installment_amount":5,"financial_institution":null,"payment_method_reference_id":null,"payable_deferral_period":null,"acquirer_reference":null},"fee_details":[{"type":"mercadopago_fee","amount":0.25,"fee_payer":"collector"}],"captured":true,"binary_mode":true,"call_for_authorize_id":null,"statement_descriptor":"MERCADOPAGO","installments":1,"card":{"id":null,"first_six_digits":"506726","last_four_digits":"7446","expiration_month":10,"expiration_year":2020,"date_created":"2019-03-04T09:39:21.000-04:00","date_last_updated":"2019-03-04T09:39:21.000-04:00","cardholder":{"name":"John Smith","identification":{"number":null,"type":null}}},"notification_url":null,"refunds":[],"processing_mode":"aggregator","merchant_account_id":null,"acquirer":null,"merchant_number":null,"acquirer_reconciliation":[]} + ) + end + + def successful_purchase_with_cabal_response + %( + {"id":20728968,"date_created":"2019-08-06T15:38:12.000-04:00","date_approved":"2019-08-06T15:38:12.000-04:00","date_last_updated":"2019-08-06T15:38:12.000-04:00","date_of_expiration":null,"money_release_date":"2019-08-20T15:38:12.000-04:00","operation_type":"regular_payment","issuer_id":"688","payment_method_id":"cabal","payment_type_id":"credit_card","status":"approved","status_detail":"accredited","currency_id":"ARS","description":"Store Purchase","live_mode":false,"sponsor_id":null,"authorization_code":null,"money_release_schema":null,"taxes_amount":0,"counter_currency":null,"shipping_amount":0,"pos_id":null,"store_id":null,"collector_id":388534608,"payer":{"first_name":"Test","last_name":"Test","email":"test_user_80507629@testuser.com","identification":{"number":"32659430","type":"DNI"},"phone":{"area_code":"01","number":"1111-1111","extension":""},"type":"registered","entity_type":null,"id":"388571255"},"metadata":{},"additional_info":{"payer":{"address":{"zip_code":"K1C2N6","street_name":"456 My Street Apt 1"}},"shipments":{"receiver_address":{"zip_code":"K1C2N6","street_name":"456 My Street Apt 1"}}},"order":{},"external_reference":"ab86ce493d1cc1e447877720843812e9","transaction_amount":5,"transaction_amount_refunded":0,"coupon_amount":0,"differential_pricing_id":null,"deduction_schema":null,"transaction_details":{"payment_method_reference_id":null,"net_received_amount":4.79,"total_paid_amount":5,"overpaid_amount":0,"external_resource_url":null,"installment_amount":5,"financial_institution":null,"payable_deferral_period":null,"acquirer_reference":null},"fee_details":[{"type":"mercadopago_fee","amount":0.21,"fee_payer":"collector"}],"captured":true,"binary_mode":true,"call_for_authorize_id":null,"statement_descriptor":"SPREEDLYMLA","installments":1,"card":{"id":null,"first_six_digits":"603522","last_four_digits":"7021","expiration_month":10,"expiration_year":2020,"date_created":"2019-08-06T15:38:12.000-04:00","date_last_updated":"2019-08-06T15:38:12.000-04:00","cardholder":{"name":"John Smith","identification":{"number":null,"type":null}}},"notification_url":null,"refunds":[],"processing_mode":"aggregator","merchant_account_id":null,"acquirer":null,"merchant_number":null,"acquirer_reconciliation":[]} + ) + end + + def failed_purchase_response + %( + {"id":4142297,"date_created":"2017-07-06T10:13:32.000-04:00","date_approved":null,"date_last_updated":"2017-07-06T10:13:32.000-04:00","date_of_expiration":null,"money_release_date":null,"operation_type":"regular_payment","issuer_id":"166","payment_method_id":"visa","payment_type_id":"credit_card","status":"rejected","status_detail":"cc_rejected_other_reason","currency_id":"MXN","description":"Store Purchase","live_mode":false,"sponsor_id":null,"authorization_code":null,"related_exchange_rate":null,"collector_id":261735089,"payer":{"type":"guest","id":null,"email":"user@example.com","identification":{"type":null,"number":null},"phone":{"area_code":null,"number":null,"extension":""},"first_name":"First User","last_name":"User","entity_type":null},"metadata":{},"additional_info":{"payer":{"address":{"zip_code":"K1C2N6","street_name":"My Street","street_number":"456"}}},"order":{"type":"mercadopago","id":"830943860538524456"},"external_reference":null,"transaction_amount":5,"transaction_amount_refunded":0,"coupon_amount":0,"differential_pricing_id":null,"deduction_schema":null,"transaction_details":{"net_received_amount":0,"total_paid_amount":5,"overpaid_amount":0,"external_resource_url":null,"installment_amount":5,"financial_institution":null,"payment_method_reference_id":null,"payable_deferral_period":null,"acquirer_reference":null},"fee_details":[],"captured":true,"binary_mode":false,"call_for_authorize_id":null,"statement_descriptor":"WWW.MERCADOPAGO.COM","installments":1,"card":{"id":null,"first_six_digits":"400030","last_four_digits":"2220","expiration_month":9,"expiration_year":2018,"date_created":"2017-07-06T10:13:32.000-04:00","date_last_updated":"2017-07-06T10:13:32.000-04:00","cardholder":{"name":"Longbob Longsen","identification":{"number":null,"type":null}}},"notification_url":null,"refunds":[],"processing_mode":null,"merchant_account_id":null,"acquirer":null,"merchant_number":null} + ) + end + + def successful_authorize_response + %( + {"id":4261941,"date_created":"2017-07-13T14:24:46.000-04:00","date_approved":null,"date_last_updated":"2017-07-13T14:24:46.000-04:00","date_of_expiration":null,"money_release_date":null,"operation_type":"regular_payment","issuer_id":"25","payment_method_id":"visa","payment_type_id":"credit_card","status":"authorized","status_detail":"pending_capture","currency_id":"BRL","description":"Store Purchase","live_mode":false,"sponsor_id":null,"authorization_code":null,"related_exchange_rate":null,"collector_id":263489584,"payer":{"type":"guest","id":null,"email":"user+br@example.com","identification":{"type":null,"number":null},"phone":{"area_code":null,"number":null,"extension":null},"first_name":null,"last_name":null,"entity_type":null},"metadata":{},"additional_info":{"payer":{"address":{"zip_code":"K1C2N6","street_name":"My Street","street_number":"456"}}},"order":{"type":"mercadopago","id":"2294029672081601730"},"external_reference":null,"transaction_amount":5,"transaction_amount_refunded":0,"coupon_amount":0,"differential_pricing_id":null,"deduction_schema":null,"transaction_details":{"net_received_amount":0,"total_paid_amount":5,"overpaid_amount":0,"external_resource_url":null,"installment_amount":5,"financial_institution":null,"payment_method_reference_id":null,"payable_deferral_period":null,"acquirer_reference":null},"fee_details":[],"captured":false,"binary_mode":false,"call_for_authorize_id":null,"statement_descriptor":"WWW.MERCADOPAGO.COM","installments":1,"card":{"id":null,"first_six_digits":"450995","last_four_digits":"3704","expiration_month":9,"expiration_year":2018,"date_created":"2017-07-13T14:24:46.000-04:00","date_last_updated":"2017-07-13T14:24:46.000-04:00","cardholder":{"name":"Longbob Longsen","identification":{"number":null,"type":null}}},"notification_url":null,"refunds":[],"processing_mode":"aggregator","merchant_account_id":null,"acquirer":null,"merchant_number":null} + ) + end + + def successful_authorize_with_elo_response + %( + {"id":18044850,"date_created":"2019-03-04T09:44:37.000-04:00","date_approved":null,"date_last_updated":"2019-03-04T09:44:40.000-04:00","date_of_expiration":null,"money_release_date":null,"operation_type":"regular_payment","issuer_id":"687","payment_method_id":"elo","payment_type_id":"credit_card","status":"authorized","status_detail":"pending_capture","currency_id":"BRL","description":"Store Purchase","live_mode":false,"sponsor_id":null,"authorization_code":null,"money_release_schema":null,"taxes_amount":0,"counter_currency":null,"shipping_amount":0,"pos_id":null,"store_id":null,"collector_id":263489584,"payer":{"first_name":"Test","last_name":"Test","email":"test_user_80507629@testuser.com","identification":{"number":"32659430","type":"DNI"},"phone":{"area_code":"01","number":"1111-1111","extension":""},"type":"registered","entity_type":null,"id":"413001901"},"metadata":{},"additional_info":{"payer":{"address":{"zip_code":"K1C2N6","street_name":"456 My Street Apt 1"}},"shipments":{"receiver_address":{"zip_code":"K1C2N6","street_name":"456 My Street Apt 1"}}},"order":{},"external_reference":"24cd4658b9ea6dbf164f9fb9f67e5e78","transaction_amount":5,"transaction_amount_refunded":0,"coupon_amount":0,"differential_pricing_id":null,"deduction_schema":null,"transaction_details":{"net_received_amount":0,"total_paid_amount":5,"overpaid_amount":0,"external_resource_url":null,"installment_amount":5,"financial_institution":null,"payment_method_reference_id":null,"payable_deferral_period":null,"acquirer_reference":null},"fee_details":[],"captured":false,"binary_mode":true,"call_for_authorize_id":null,"statement_descriptor":"MERCADOPAGO","installments":1,"card":{"id":null,"first_six_digits":"506726","last_four_digits":"7446","expiration_month":10,"expiration_year":2020,"date_created":"2019-03-04T09:44:37.000-04:00","date_last_updated":"2019-03-04T09:44:37.000-04:00","cardholder":{"name":"John Smith","identification":{"number":null,"type":null}}},"notification_url":null,"refunds":[],"processing_mode":"aggregator","merchant_account_id":null,"acquirer":null,"merchant_number":null,"acquirer_reconciliation":[]} + ) + end + + def successful_authorize_with_cabal_response + %( + {"id":20729288,"date_created":"2019-08-06T15:57:47.000-04:00","date_approved":null,"date_last_updated":"2019-08-06T15:57:49.000-04:00","date_of_expiration":null,"money_release_date":null,"operation_type":"regular_payment","issuer_id":"688","payment_method_id":"cabal","payment_type_id":"credit_card","status":"authorized","status_detail":"pending_capture","currency_id":"ARS","description":"Store Purchase","live_mode":false,"sponsor_id":null,"authorization_code":null,"money_release_schema":null,"taxes_amount":0,"counter_currency":null,"shipping_amount":0,"pos_id":null,"store_id":null,"collector_id":388534608,"payer":{"first_name":"Test","last_name":"Test","email":"test_user_80507629@testuser.com","identification":{"number":"32659430","type":"DNI"},"phone":{"area_code":"01","number":"1111-1111","extension":""},"type":"registered","entity_type":null,"id":"388571255"},"metadata":{},"additional_info":{"payer":{"address":{"zip_code":"K1C2N6","street_name":"456 My Street Apt 1"}},"shipments":{"receiver_address":{"zip_code":"K1C2N6","street_name":"456 My Street Apt 1"}}},"order":{},"external_reference":"f70cb796271176441a5077012ff2af2a","transaction_amount":5,"transaction_amount_refunded":0,"coupon_amount":0,"differential_pricing_id":null,"deduction_schema":null,"transaction_details":{"payment_method_reference_id":null,"net_received_amount":0,"total_paid_amount":5,"overpaid_amount":0,"external_resource_url":null,"installment_amount":5,"financial_institution":null,"payable_deferral_period":null,"acquirer_reference":null},"fee_details":[],"captured":false,"binary_mode":true,"call_for_authorize_id":null,"statement_descriptor":"SPREEDLYMLA","installments":1,"card":{"id":null,"first_six_digits":"603522","last_four_digits":"7021","expiration_month":10,"expiration_year":2020,"date_created":"2019-08-06T15:57:47.000-04:00","date_last_updated":"2019-08-06T15:57:47.000-04:00","cardholder":{"name":"John Smith","identification":{"number":null,"type":null}}},"notification_url":null,"refunds":[],"processing_mode":"aggregator","merchant_account_id":null,"acquirer":null,"merchant_number":null,"acquirer_reconciliation":[]} + ) + end + + def failed_authorize_response + %( + {"id":4261953,"date_created":"2017-07-13T14:25:33.000-04:00","date_approved":null,"date_last_updated":"2017-07-13T14:25:33.000-04:00","date_of_expiration":null,"money_release_date":null,"operation_type":"regular_payment","issuer_id":"25","payment_method_id":"visa","payment_type_id":"credit_card","status":"rejected","status_detail":"cc_rejected_other_reason","currency_id":"BRL","description":"Store Purchase","live_mode":false,"sponsor_id":null,"authorization_code":null,"related_exchange_rate":null,"collector_id":263489584,"payer":{"type":"guest","id":null,"email":"user+br@example.com","identification":{"type":null,"number":null},"phone":{"area_code":null,"number":null,"extension":null},"first_name":null,"last_name":null,"entity_type":null},"metadata":{},"additional_info":{"payer":{"address":{"zip_code":"K1C2N6","street_name":"My Street","street_number":"456"}}},"order":{"type":"mercadopago","id":"7528376941458928221"},"external_reference":null,"transaction_amount":5,"transaction_amount_refunded":0,"coupon_amount":0,"differential_pricing_id":null,"deduction_schema":null,"transaction_details":{"net_received_amount":0,"total_paid_amount":5,"overpaid_amount":0,"external_resource_url":null,"installment_amount":5,"financial_institution":null,"payment_method_reference_id":null,"payable_deferral_period":null,"acquirer_reference":null},"fee_details":[],"captured":false,"binary_mode":false,"call_for_authorize_id":null,"statement_descriptor":"WWW.MERCADOPAGO.COM","installments":1,"card":{"id":null,"first_six_digits":"400030","last_four_digits":"2220","expiration_month":9,"expiration_year":2018,"date_created":"2017-07-13T14:25:33.000-04:00","date_last_updated":"2017-07-13T14:25:33.000-04:00","cardholder":{"name":"Longbob Longsen","identification":{"number":null,"type":null}}},"notification_url":null,"refunds":[],"processing_mode":"aggregator","merchant_account_id":null,"acquirer":null,"merchant_number":null} + ) + end + + def successful_capture_response + %( + {"id":4261941,"date_created":"2017-07-13T14:24:46.000-04:00","date_approved":"2017-07-13T14:24:47.000-04:00","date_last_updated":"2017-07-13T14:24:47.000-04:00","date_of_expiration":null,"money_release_date":"2017-07-27T14:24:47.000-04:00","operation_type":"regular_payment","issuer_id":"25","payment_method_id":"visa","payment_type_id":"credit_card","status":"approved","status_detail":"accredited","currency_id":"BRL","description":"Store Purchase","live_mode":false,"sponsor_id":null,"authorization_code":null,"related_exchange_rate":null,"collector_id":263489584,"payer":{"type":"guest","id":null,"email":"user+br@example.com","identification":{"type":null,"number":null},"phone":{"area_code":null,"number":null,"extension":null},"first_name":null,"last_name":null,"entity_type":null},"metadata":{},"additional_info":{"payer":{"address":{"zip_code":"K1C2N6","street_name":"My Street","street_number":"456"}}},"order":{"type":"mercadopago","id":"2294029672081601730"},"external_reference":null,"transaction_amount":5,"transaction_amount_refunded":0,"coupon_amount":0,"differential_pricing_id":null,"deduction_schema":null,"transaction_details":{"net_received_amount":4.75,"total_paid_amount":5,"overpaid_amount":0,"external_resource_url":null,"installment_amount":5,"financial_institution":null,"payment_method_reference_id":null,"payable_deferral_period":null,"acquirer_reference":null},"fee_details":[{"type":"mercadopago_fee","amount":0.25,"fee_payer":"collector"}],"captured":true,"binary_mode":false,"call_for_authorize_id":null,"statement_descriptor":"WWW.MERCADOPAGO.COM","installments":1,"card":{"id":null,"first_six_digits":"450995","last_four_digits":"3704","expiration_month":9,"expiration_year":2018,"date_created":"2017-07-13T14:24:46.000-04:00","date_last_updated":"2017-07-13T14:24:46.000-04:00","cardholder":{"name":"Longbob Longsen","identification":{"number":null,"type":null}}},"notification_url":null,"refunds":[],"processing_mode":"aggregator","merchant_account_id":null,"acquirer":null,"merchant_number":null} + ) + end + + def successful_capture_with_elo_response + %( + {"id":18044850,"date_created":"2019-03-04T09:44:37.000-04:00","date_approved":"2019-03-04T09:44:41.000-04:00","date_last_updated":"2019-03-04T09:44:41.000-04:00","date_of_expiration":null,"money_release_date":"2019-03-18T09:44:41.000-04:00","operation_type":"regular_payment","issuer_id":"687","payment_method_id":"elo","payment_type_id":"credit_card","status":"approved","status_detail":"accredited","currency_id":"BRL","description":"Store Purchase","live_mode":false,"sponsor_id":null,"authorization_code":null,"money_release_schema":null,"taxes_amount":0,"counter_currency":null,"shipping_amount":0,"pos_id":null,"store_id":null,"collector_id":263489584,"payer":{"first_name":"Test","last_name":"Test","email":"test_user_80507629@testuser.com","identification":{"number":"32659430","type":"DNI"},"phone":{"area_code":"01","number":"1111-1111","extension":""},"type":"registered","entity_type":null,"id":"413001901"},"metadata":{},"additional_info":{"payer":{"address":{"zip_code":"K1C2N6","street_name":"456 My Street Apt 1"}},"shipments":{"receiver_address":{"zip_code":"K1C2N6","street_name":"456 My Street Apt 1"}}},"order":{},"external_reference":"24cd4658b9ea6dbf164f9fb9f67e5e78","transaction_amount":5,"transaction_amount_refunded":0,"coupon_amount":0,"differential_pricing_id":null,"deduction_schema":null,"transaction_details":{"net_received_amount":4.75,"total_paid_amount":5,"overpaid_amount":0,"external_resource_url":null,"installment_amount":5,"financial_institution":null,"payment_method_reference_id":null,"payable_deferral_period":null,"acquirer_reference":null},"fee_details":[{"type":"mercadopago_fee","amount":0.25,"fee_payer":"collector"}],"captured":true,"binary_mode":true,"call_for_authorize_id":null,"statement_descriptor":"MERCADOPAGO","installments":1,"card":{"id":null,"first_six_digits":"506726","last_four_digits":"7446","expiration_month":10,"expiration_year":2020,"date_created":"2019-03-04T09:44:37.000-04:00","date_last_updated":"2019-03-04T09:44:37.000-04:00","cardholder":{"name":"John Smith","identification":{"number":null,"type":null}}},"notification_url":null,"refunds":[],"processing_mode":"aggregator","merchant_account_id":null,"acquirer":null,"merchant_number":null,"acquirer_reconciliation":[]} + ) + end + + def successful_capture_with_cabal_response + %( + {"id":20729288,"date_created":"2019-08-06T15:57:47.000-04:00","date_approved":"2019-08-06T15:57:49.000-04:00","date_last_updated":"2019-08-06T15:57:49.000-04:00","date_of_expiration":null,"money_release_date":"2019-08-20T15:57:49.000-04:00","operation_type":"regular_payment","issuer_id":"688","payment_method_id":"cabal","payment_type_id":"credit_card","status":"approved","status_detail":"accredited","currency_id":"ARS","description":"Store Purchase","live_mode":false,"sponsor_id":null,"authorization_code":null,"money_release_schema":null,"taxes_amount":0,"counter_currency":null,"shipping_amount":0,"pos_id":null,"store_id":null,"collector_id":388534608,"payer":{"first_name":"Test","last_name":"Test","email":"test_user_80507629@testuser.com","identification":{"number":"32659430","type":"DNI"},"phone":{"area_code":"01","number":"1111-1111","extension":""},"type":"registered","entity_type":null,"id":"388571255"},"metadata":{},"additional_info":{"payer":{"address":{"zip_code":"K1C2N6","street_name":"456 My Street Apt 1"}},"shipments":{"receiver_address":{"zip_code":"K1C2N6","street_name":"456 My Street Apt 1"}}},"order":{},"external_reference":"f70cb796271176441a5077012ff2af2a","transaction_amount":5,"transaction_amount_refunded":0,"coupon_amount":0,"differential_pricing_id":null,"deduction_schema":null,"transaction_details":{"payment_method_reference_id":null,"net_received_amount":4.79,"total_paid_amount":5,"overpaid_amount":0,"external_resource_url":null,"installment_amount":5,"financial_institution":null,"payable_deferral_period":null,"acquirer_reference":null},"fee_details":[{"type":"mercadopago_fee","amount":0.21,"fee_payer":"collector"}],"captured":true,"binary_mode":true,"call_for_authorize_id":null,"statement_descriptor":"SPREEDLYMLA","installments":1,"card":{"id":null,"first_six_digits":"603522","last_four_digits":"7021","expiration_month":10,"expiration_year":2020,"date_created":"2019-08-06T15:57:47.000-04:00","date_last_updated":"2019-08-06T15:57:47.000-04:00","cardholder":{"name":"John Smith","identification":{"number":null,"type":null}}},"notification_url":null,"refunds":[],"processing_mode":"aggregator","merchant_account_id":null,"acquirer":null,"merchant_number":null,"acquirer_reconciliation":[]} + ) + end + + def failed_capture_response + %( + {"message":"Method not allowed","error":"method_not_allowed","status":405,"cause":[{"code":"Method not allowed","description":"Method not allowed","data":null}]} + ) + end + + def successful_refund_response + %( + {"id":4247757,"payment_id":4247751,"amount":5,"metadata":{},"source":{"id":"261735089","name":"Spreedly Integrations","type":"collector"},"date_created":"2017-07-12T14:45:08.752-04:00","unique_sequence_number":null} + ) + end + + def failed_refund_response + %( + {"message":"Resource /payments/refunds/ not found.","error":"not_found","status":404,"cause":[]} + ) + end + + def successful_void_response + %( + {"id":4261966,"date_created":"2017-07-13T14:26:56.000-04:00","date_approved":null,"date_last_updated":"2017-07-13T14:26:57.000-04:00","date_of_expiration":null,"money_release_date":null,"operation_type":"regular_payment","issuer_id":"25","payment_method_id":"visa","payment_type_id":"credit_card","status":"cancelled","status_detail":"by_collector","currency_id":"BRL","description":"Store Purchase","live_mode":false,"sponsor_id":null,"authorization_code":null,"related_exchange_rate":null,"collector_id":263489584,"payer":{"type":"guest","id":null,"email":"user+br@example.com","identification":{"type":null,"number":null},"phone":{"area_code":null,"number":null,"extension":null},"first_name":null,"last_name":null,"entity_type":null},"metadata":{},"additional_info":{"payer":{"address":{"zip_code":"K1C2N6","street_name":"My Street","street_number":"456"}}},"order":{"type":"mercadopago","id":"6688620487994029432"},"external_reference":null,"transaction_amount":5,"transaction_amount_refunded":0,"coupon_amount":0,"differential_pricing_id":null,"deduction_schema":null,"transaction_details":{"net_received_amount":0,"total_paid_amount":5,"overpaid_amount":0,"external_resource_url":null,"installment_amount":5,"financial_institution":null,"payment_method_reference_id":null,"payable_deferral_period":null,"acquirer_reference":null},"fee_details":[],"captured":false,"binary_mode":false,"call_for_authorize_id":null,"statement_descriptor":"WWW.MERCADOPAGO.COM","installments":1,"card":{"id":null,"first_six_digits":"450995","last_four_digits":"3704","expiration_month":9,"expiration_year":2018,"date_created":"2017-07-13T14:26:56.000-04:00","date_last_updated":"2017-07-13T14:26:56.000-04:00","cardholder":{"name":"Longbob Longsen","identification":{"number":null,"type":null}}},"notification_url":null,"refunds":[],"processing_mode":"aggregator","merchant_account_id":null,"acquirer":null,"merchant_number":null} + ) + end + + def failed_void_response + %( + {"message":"Method not allowed","error":"method_not_allowed","status":405,"cause":[{"code":"Method not allowed","description":"Method not allowed","data":null}]} + ) + end +end diff --git a/test/unit/gateways/merchant_e_solutions_test.rb b/test/unit/gateways/merchant_e_solutions_test.rb index e9954318ef2..ca891477a85 100644 --- a/test/unit/gateways/merchant_e_solutions_test.rb +++ b/test/unit/gateways/merchant_e_solutions_test.rb @@ -1,8 +1,10 @@ require 'test_helper' class MerchantESolutionsTest < Test::Unit::TestCase + include CommStub + def setup - Base.gateway_mode = :test + Base.mode = :test @gateway = MerchantESolutionsGateway.new( :login => 'login', @@ -35,6 +37,19 @@ def test_unsuccessful_purchase assert response.test? end + def test_purchase_with_long_order_id_truncates_id + options = {order_id: 'thisislongerthan17characters'} + @gateway.expects(:ssl_post).with( + anything, + all_of( + includes('invoice_number=thisislongerthan1') + ) + ).returns(successful_purchase_response) + assert response = @gateway.purchase(@amount, @credit_card, options) + assert_success response + assert_equal 'This transaction has been approved', response.message + end + def test_authorization @gateway.expects(:ssl_post).returns(successful_authorization_response) assert response = @gateway.authorize(@amount, @credit_card, @options) @@ -88,42 +103,60 @@ def test_unstore def test_successful_avs_check @gateway.expects(:ssl_post).returns(successful_purchase_response + '&avs_result=Y') assert response = @gateway.purchase(@amount, @credit_card, @options) - assert_equal response.avs_result['code'], "Y" - assert_equal response.avs_result['message'], "Street address and 5-digit postal code match." - assert_equal response.avs_result['street_match'], "Y" - assert_equal response.avs_result['postal_match'], "Y" + assert_equal response.avs_result['code'], 'Y' + assert_equal response.avs_result['message'], 'Street address and 5-digit postal code match.' + assert_equal response.avs_result['street_match'], 'Y' + assert_equal response.avs_result['postal_match'], 'Y' end def test_unsuccessful_avs_check_with_bad_street_address @gateway.expects(:ssl_post).returns(successful_purchase_response + '&avs_result=Z') assert response = @gateway.purchase(@amount, @credit_card, @options) - assert_equal response.avs_result['code'], "Z" - assert_equal response.avs_result['message'], "Street address does not match, but 5-digit postal code matches." - assert_equal response.avs_result['street_match'], "N" - assert_equal response.avs_result['postal_match'], "Y" + assert_equal response.avs_result['code'], 'Z' + assert_equal response.avs_result['message'], 'Street address does not match, but 5-digit postal code matches.' + assert_equal response.avs_result['street_match'], 'N' + assert_equal response.avs_result['postal_match'], 'Y' end def test_unsuccessful_avs_check_with_bad_zip @gateway.expects(:ssl_post).returns(successful_purchase_response + '&avs_result=A') assert response = @gateway.purchase(@amount, @credit_card, @options) - assert_equal response.avs_result['code'], "A" - assert_equal response.avs_result['message'], "Street address matches, but 5-digit and 9-digit postal code do not match." - assert_equal response.avs_result['street_match'], "Y" - assert_equal response.avs_result['postal_match'], "N" + assert_equal response.avs_result['code'], 'A' + assert_equal response.avs_result['message'], 'Street address matches, but postal code does not match.' + assert_equal response.avs_result['street_match'], 'Y' + assert_equal response.avs_result['postal_match'], 'N' end def test_successful_cvv_check @gateway.expects(:ssl_post).returns(successful_purchase_response + '&cvv2_result=M') assert response = @gateway.purchase(@amount, @credit_card, @options) - assert_equal response.cvv_result['code'], "M" - assert_equal response.cvv_result['message'], "Match" + assert_equal response.cvv_result['code'], 'M' + assert_equal response.cvv_result['message'], 'CVV matches' end def test_unsuccessful_cvv_check @gateway.expects(:ssl_post).returns(failed_purchase_response + '&cvv2_result=N') assert response = @gateway.purchase(@amount, @credit_card, @options) - assert_equal response.cvv_result['code'], "N" - assert_equal response.cvv_result['message'], "No Match" + assert_equal response.cvv_result['code'], 'N' + assert_equal response.cvv_result['message'], 'CVV does not match' + end + + def test_visa_3dsecure_params_submitted + stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, @credit_card, @options.merge({:xid => '1', :cavv => '2'})) + end.check_request do |method, endpoint, data, headers| + assert_match(/xid=1/, data) + assert_match(/cavv=2/, data) + end.respond_with(successful_purchase_response) + end + + def test_mastercard_3dsecure_params_submitted + stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, @credit_card, @options.merge({:ucaf_collection_ind => '1', :ucaf_auth_data => '2'})) + end.check_request do |method, endpoint, data, headers| + assert_match(/ucaf_collection_ind=1/, data) + assert_match(/ucaf_auth_data=2/, data) + end.respond_with(successful_purchase_response) end def test_supported_countries @@ -134,6 +167,11 @@ def test_supported_card_types assert_equal [:visa, :master, :american_express, :discover, :jcb], MerchantESolutionsGateway.supported_cardtypes end + def test_scrub + assert @gateway.supports_scrubbing? + assert_equal @gateway.scrub(pre_scrubbed), post_scrubbed + end + private def successful_purchase_response @@ -167,4 +205,18 @@ def successful_unstore_response def failed_purchase_response 'transaction_id=error&error_code=101&auth_response_text=Invalid%20I%20or%20Key%20Incomplete%20Request' end + + def pre_scrubbed + <<-TRANSCRIPT + "profile_id=94100010518900000029&profile_key=YvKeIpxLxpJoKRKkJjMOpqmGkwUCBBEO&transaction_type=D&invoice_number=123&card_number=4111111111111111&cvv2=123&card_exp_date=0919&cardholder_street_address=123%2BState%2BStreet&cardholder_zip=55555&transaction_amount=1.00" + "transaction_id=3dfdc828adf032d589111ff45a7087fc&error_code=000&auth_response_text=Exact Match&avs_result=Y&cvv2_result=M&auth_code=T4797H" + TRANSCRIPT + end + + def post_scrubbed + <<-TRANSCRIPT + "profile_id=94100010518900000029&profile_key=[FILTERED]&transaction_type=D&invoice_number=123&card_number=[FILTERED]&cvv2=[FILTERED]&card_exp_date=0919&cardholder_street_address=123%2BState%2BStreet&cardholder_zip=55555&transaction_amount=1.00" + "transaction_id=3dfdc828adf032d589111ff45a7087fc&error_code=000&auth_response_text=Exact Match&avs_result=Y&cvv2_result=M&auth_code=T4797H" + TRANSCRIPT + end end diff --git a/test/unit/gateways/merchant_one_test.rb b/test/unit/gateways/merchant_one_test.rb index 839fc8ff8c5..16e3b879473 100644 --- a/test/unit/gateways/merchant_one_test.rb +++ b/test/unit/gateways/merchant_one_test.rb @@ -27,7 +27,7 @@ def test_successful_purchase assert response = @gateway.purchase(@amount, @credit_card, @options) assert_instance_of Response, response assert_success response - assert_equal "281719471", response.authorization + assert_equal '281719471', response.authorization assert response.test?, response.test.to_s end @@ -60,10 +60,10 @@ def test_unsuccessful_request private def successful_purchase_response - "response=1&responsetext=SUCCESS&authcode=123456&transactionid=281719471&avsresponse=&cvvresponse=M&orderid=&type=sale&response_code=100" + 'response=1&responsetext=SUCCESS&authcode=123456&transactionid=281719471&avsresponse=&cvvresponse=M&orderid=&type=sale&response_code=100' end def failed_purchase_response - "response=3&responsetext=DECLINE&authcode=123456&transactionid=281719471&avsresponse=&cvvresponse=M&orderid=&type=sale&response_code=300" + 'response=3&responsetext=DECLINE&authcode=123456&transactionid=281719471&avsresponse=&cvvresponse=M&orderid=&type=sale&response_code=300' end end diff --git a/test/unit/gateways/merchant_partners_test.rb b/test/unit/gateways/merchant_partners_test.rb new file mode 100644 index 00000000000..3307fa37d61 --- /dev/null +++ b/test/unit/gateways/merchant_partners_test.rb @@ -0,0 +1,791 @@ +require 'test_helper' +require 'nokogiri' + +class MerchantPartnersTest < Test::Unit::TestCase + include CommStub + + def setup + @gateway = MerchantPartnersGateway.new( + account_id: 'TEST0', + merchant_pin: '1234567890' + ) + + @credit_card = credit_card + @amount = 100 + + @request_root = '/interface_driver/trans_catalog/transaction/inputs' + end + + def test_successful_purchase + response = stub_comms do + @gateway.purchase(@amount, @credit_card) + end.check_request do |endpoint, data, headers| + parse(data) do |doc| + assert_not_nil root = doc.at_xpath(@request_root) + assert_equal @gateway.options[:account_id], root.at_xpath('//acctid').content + assert_equal @gateway.options[:merchant_pin], root.at_xpath('//merchantpin').content + assert_equal @credit_card.name, root.at_xpath('//ccname').content + assert_equal @credit_card.number, root.at_xpath('//ccnum').content + assert_equal @credit_card.verification_value, root.at_xpath('//cvv2').content + assert_equal '2', root.at_xpath('//service').content + assert_equal '1.00', root.at_xpath('//amount').content + end + end.respond_with(successful_purchase_response) + + assert_success response + assert response.test? + assert_equal '398182213', response.authorization + end + + def test_failed_purchase + response = stub_comms do + @gateway.purchase(@amount, @credit_card) + end.respond_with(failed_purchase_response) + + assert_failure response + assert_equal 'Invalid account number', response.message + assert response.params['result'].start_with?('DECLINED') + assert response.test? + end + + def test_successful_authorize_and_capture + response = stub_comms do + @gateway.authorize(@amount, @credit_card) + end.check_request do |endpoint, data, headers| + parse(data) do |doc| + assert_not_nil root = doc.at_xpath(@request_root) + assert_equal @gateway.options[:account_id], root.at_xpath('//acctid').content + assert_equal @gateway.options[:merchant_pin], root.at_xpath('//merchantpin').content + assert_equal @credit_card.name, root.at_xpath('//ccname').content + assert_equal @credit_card.number, root.at_xpath('//ccnum').content + assert_equal @credit_card.verification_value, root.at_xpath('//cvv2').content + assert_equal '1', root.at_xpath('//service').content + assert_equal '1.00', root.at_xpath('//amount').content + end + end.respond_with(successful_authorize_response) + + assert_success response + assert response.test? + assert_equal '398047747', response.authorization + + capture = stub_comms do + @gateway.capture(@amount, response.authorization) + end.check_request do |endpoint, data, headers| + parse(data) do |doc| + assert_not_nil root = doc.at_xpath(@request_root) + assert_equal @gateway.options[:account_id], root.at_xpath('//acctid').content + assert_equal @gateway.options[:merchant_pin], root.at_xpath('//merchantpin').content + assert_equal response.authorization, root.at_xpath('//historykeyid').content + assert_equal '3', root.at_xpath('//service').content + assert_equal '1.00', root.at_xpath('//amount').content + end + end.respond_with(successful_capture_response) + + assert_success capture + assert capture.test? + assert_equal '398044113', capture.authorization + end + + def test_failed_authorize + response = stub_comms do + @gateway.authorize(@amount, @credit_card) + end.respond_with(failed_authorize_response) + + assert_failure response + assert_equal 'Invalid account number', response.message + assert response.params['result'].start_with?('DECLINED') + assert response.test? + end + + def test_failed_capture + response = stub_comms do + @gateway.capture(100, '') + end.respond_with(failed_capture_response) + + assert_failure response + end + + def test_successful_void + response = stub_comms do + @gateway.authorize(@amount, @credit_card) + end.respond_with(successful_authorize_response) + + assert_success response + assert_equal '398047747', response.authorization + + void = stub_comms do + @gateway.void(response.authorization) + end.check_request do |endpoint, data, headers| + parse(data) do |doc| + assert_not_nil root = doc.at_xpath(@request_root) + assert_equal @gateway.options[:account_id], root.at_xpath('//acctid').content + assert_equal @gateway.options[:merchant_pin], root.at_xpath('//merchantpin').content + assert_equal response.authorization, root.at_xpath('//historykeyid').content + assert_equal '5', root.at_xpath('//service').content + end + end.respond_with(successful_void_response) + + assert_success void + end + + def test_failed_void + response = stub_comms do + @gateway.void('5d53a33d960c46d00f5dc061947d998c') + end.respond_with(failed_void_response) + + assert_failure response + end + + def test_successful_refund + response = stub_comms do + @gateway.purchase(@amount, @credit_card) + end.respond_with(successful_purchase_response) + + assert_success response + + refund = stub_comms do + @gateway.refund(@amount, response.authorization) + end.check_request do |endpoint, data, headers| + parse(data) do |doc| + assert_not_nil root = doc.at_xpath(@request_root) + assert_equal @gateway.options[:account_id], root.at_xpath('//acctid').content + assert_equal @gateway.options[:merchant_pin], root.at_xpath('//merchantpin').content + assert_equal response.authorization, root.at_xpath('//historykeyid').content + assert_equal '4', root.at_xpath('//service').content + assert_equal '1.00', root.at_xpath('//amount').content + end + end.respond_with(successful_refund_response) + + assert_success refund + end + + def test_failed_refund + response = stub_comms do + @gateway.refund(nil, '') + end.respond_with(failed_refund_response) + + assert_failure response + end + + def test_successful_credit + response = stub_comms do + @gateway.credit(@amount, @credit_card) + end.check_request do |endpoint, data, headers| + parse(data) do |doc| + assert_not_nil root = doc.at_xpath(@request_root) + assert_equal @gateway.options[:account_id], root.at_xpath('//acctid').content + assert_equal @gateway.options[:merchant_pin], root.at_xpath('//merchantpin').content + assert_equal @credit_card.name, root.at_xpath('//ccname').content + assert_equal @credit_card.number, root.at_xpath('//ccnum').content + assert_equal @credit_card.verification_value, root.at_xpath('//cvv2').content + assert_equal '6', root.at_xpath('//service').content + assert_equal '1.00', root.at_xpath('//amount').content + end + end.respond_with(successful_credit_response) + + assert_success response + assert response.test? + end + + def test_failed_credit + response = stub_comms do + @gateway.credit(@amount, @credit_card) + end.respond_with(failed_credit_response) + + assert_failure response + assert_equal 'Invalid account number', response.message + assert response.params['result'].start_with?('DECLINED') + assert response.test? + end + + def test_successful_verify + response = stub_comms do + @gateway.verify(@credit_card) + end.respond_with(successful_authorize_response, failed_void_response) + assert_success response + assert_equal 'Succeeded', response.message + end + + def test_failed_verify + response = stub_comms do + @gateway.verify(@credit_card) + end.respond_with(failed_authorize_response, successful_void_response) + assert_failure response + assert_equal 'Invalid account number', response.message + end + + def test_successful_store + response = stub_comms do + @gateway.store(@credit_card) + end.check_request do |endpoint, data, headers| + parse(data) do |doc| + assert_not_nil root = doc.at_xpath(@request_root) + assert_equal @gateway.options[:account_id], root.at_xpath('//acctid').content + assert_equal @gateway.options[:merchant_pin], root.at_xpath('//merchantpin').content + assert_equal @credit_card.name, root.at_xpath('//ccname').content + assert_equal @credit_card.number, root.at_xpath('//ccnum').content + assert_equal @credit_card.verification_value, root.at_xpath('//cvv2').content + assert_equal '7', root.at_xpath('//service').content + end + end.respond_with(successful_store_response) + + assert_success response + assert_equal 'Succeeded', response.message + assert_equal '17522090|6781', response.authorization + assert response.test? + end + + def test_successful_stored_purchase + response = stub_comms do + @gateway.store(@credit_card) + end.respond_with(successful_store_response) + + assert_success response + assert_equal 'Succeeded', response.message + assert_equal '17522090|6781', response.authorization + assert response.test? + + purchase = stub_comms do + @gateway.purchase(@amount, response.authorization) + end.check_request do |endpoint, data, headers| + parse(data) do |doc| + assert_not_nil root = doc.at_xpath(@request_root) + assert_equal @gateway.options[:account_id], root.at_xpath('//acctid').content + assert_equal @gateway.options[:merchant_pin], root.at_xpath('//merchantpin').content + assert_equal response.params['userprofileid'], root.at_xpath('//userprofileid').content + assert_equal response.params['last4digits'], root.at_xpath('//last4digits').content + assert_equal '8', root.at_xpath('//service').content + assert_equal '1.00', root.at_xpath('//amount').content + end + end.respond_with(successful_purchase_response) + + assert_success purchase + assert purchase.test? + end + + def test_successful_stored_credit + response = stub_comms do + @gateway.store(@credit_card) + end.respond_with(successful_store_response) + + assert_success response + assert_equal 'Succeeded', response.message + assert_equal '17522090|6781', response.authorization + assert response.test? + + credit = stub_comms do + @gateway.credit(@amount, response.authorization) + end.check_request do |endpoint, data, headers| + parse(data) do |doc| + assert_not_nil root = doc.at_xpath(@request_root) + assert_equal @gateway.options[:account_id], root.at_xpath('//acctid').content + assert_equal @gateway.options[:merchant_pin], root.at_xpath('//merchantpin').content + assert_equal response.params['userprofileid'], root.at_xpath('//userprofileid').content + assert_equal response.params['last4digits'], root.at_xpath('//last4digits').content + assert_equal '13', root.at_xpath('//service').content + assert_equal '1.00', root.at_xpath('//amount').content + end + end.respond_with(successful_purchase_response) + + assert_success credit + assert credit.test? + end + + def test_failed_store + response = stub_comms do + @gateway.store(@credit_card) + end.respond_with(failed_store_response) + + assert_failure response + assert_equal 'Live Transactions Not Allowed', response.message + assert response.params['result'].start_with?('DECLINED') + assert response.test? + end + + def test_transcript_scrubbing + assert_equal scrubbed_transcript, @gateway.scrub(transcript) + end + + private + + def parse(data) + Nokogiri::XML(data).tap do |doc| + doc.remove_namespaces! + yield(doc) if block_given? + end + end + + def successful_purchase_response + %(<?xml version="1.0"?><interface_driver> +<trans_catalog> +<transaction> +<outputs> +<status>Approved</status> +<accountname>Longbob Longsen</accountname> +<result>AVSSALE:TEST:::398182213:N::U</result> +<authcode>TEST</authcode> +<historyid>398182213</historyid> +<orderid>287915678</orderid> +<refcode>398182213</refcode> +<total>1.0</total> +<merchantordernumber>252e218a23e7b3af6d74b4f371e4a0a8</merchantordernumber> +<last4digits>6781</last4digits> +<avsresult>N</avsresult> +<cvv2result>U</cvv2result> +<duplicate>0</duplicate> +<paytype>Visa</paytype> +</outputs> +</transaction> +</trans_catalog> +</interface_driver> + ) + end + + def failed_purchase_response + %(<?xml version="1.0"?><interface_driver> +<trans_catalog> +<transaction> +<outputs> +<status>Declined</status> +<accountname>Longbob Longsen</accountname> +<result>DECLINED:1102140001:Invalid account number:</result> +<authcode></authcode> +<historyid>398045105</historyid> +<orderid>287819971</orderid> +<refcode>398045105</refcode> +<total>1.0</total> +<merchantordernumber>cf704493db5e5c8ba3f6c91a3fd2105c</merchantordernumber> +<last4digits>6782</last4digits> +<avsresult></avsresult> +<cvv2result></cvv2result> +<duplicate>0</duplicate> +<paytype>Visa</paytype> +</outputs> +</transaction> +</trans_catalog> +</interface_driver> + ) + end + + def successful_authorize_response + %(<?xml version="1.0"?> +<interface_driver> +<trans_catalog> +<transaction> +<outputs> +<status>Approved</status> +<accountname>Longbob Longsen</accountname> +<result>AVSAUTH:TEST:::398047747:N::U</result> +<authcode>TEST</authcode> +<historyid>398047747</historyid> +<orderid>287809633</orderid> +<refcode>398047747</refcode> +<total>1.0</total> +<merchantordernumber>06002c1fd9d98a8101eb70484a033ae2</merchantordernumber> +<last4digits>6781</last4digits> +<avsresult>N</avsresult> +<cvv2result>U</cvv2result> +<duplicate>0</duplicate> +<paytype>Visa</paytype> +</outputs> +</transaction> +</trans_catalog> +</interface_driver> + ) + end + + def failed_authorize_response + %(<?xml version="1.0"?><interface_driver> +<trans_catalog> +<transaction> +<outputs> +<status>Declined</status> +<accountname>Longbob Longsen</accountname> +<result>DECLINED:1102140001:Invalid account number:</result> +<authcode></authcode> +<historyid>398049530</historyid> +<orderid>287811302</orderid> +<refcode>398049530</refcode> +<total>1.0</total> +<merchantordernumber>5dcbd6b4af0ef5b3391aebe2489b83ae</merchantordernumber> +<last4digits>6782</last4digits> +<avsresult></avsresult> +<cvv2result></cvv2result> +<duplicate>0</duplicate> +<paytype>Visa</paytype> +</outputs> +</transaction> +</trans_catalog> +</interface_driver> + ) + end + + def successful_capture_response + %(<?xml version="1.0"?><interface_driver> +<trans_catalog> +<transaction> +<outputs> +<status>Approved</status> +<accountname>Longbob Longsen</accountname> +<result>AVSPOST:TEST:::398044113:::</result> +<authcode>TEST</authcode> +<historyid>398044113</historyid> +<orderid>287810629</orderid> +<refcode>398044113</refcode> +<total>1.0</total> +<merchantordernumber>bcb384e495bcc61b7ecddc74511916b7</merchantordernumber> +<last4digits>6781</last4digits> +<avsresult></avsresult> +<cvv2result></cvv2result> +<duplicate>0</duplicate> +<paytype>Visa</paytype> +</outputs> +</transaction> +</trans_catalog> +</interface_driver> + ) + end + + def failed_capture_response + %(<?xml version="1.0"?><interface_driver> +<trans_catalog> +<transaction> +<outputs> +<status>Declined</status> +<accountname></accountname> +<result>DECLINED:1102010009:Missing account number:</result> +<authcode></authcode> +<historyid>0</historyid> +<orderid>0</orderid> +<refcode>0</refcode> +<total>0.00</total> +<merchantordernumber></merchantordernumber> +<last4digits></last4digits> +<avsresult></avsresult> +<cvv2result></cvv2result> +<duplicate>0</duplicate> +<paytype></paytype> +</outputs> +</transaction> +</trans_catalog> +</interface_driver> + ) + end + + def successful_void_response + %(<?xml version="1.0"?><interface_driver> +<trans_catalog> +<transaction> +<outputs> +<status>Approved</status> +<accountname>Longbob Longsen</accountname> +<result>VOID:TEST:::398050218:::</result> +<authcode>TEST</authcode> +<historyid>398050218</historyid> +<orderid>287820429</orderid> +<refcode>398050218</refcode> +<total>1.0</total> +<merchantordernumber>f170b6e6773fc1e9e840a4561c0562cd</merchantordernumber> +<last4digits>6781</last4digits> +<avsresult></avsresult> +<cvv2result></cvv2result> +<duplicate>0</duplicate> +<paytype>Visa</paytype> +</outputs> +</transaction> +</trans_catalog> +</interface_driver> + ) + end + + def failed_void_response + %(<?xml version="1.0"?><interface_driver> +<trans_catalog> +<transaction> +<outputs> +<status>Declined</status> +<accountname></accountname> +<result>DECLINED:3101810001:Invalid acct type:</result> +<authcode></authcode> +<historyid>0</historyid> +<orderid>0</orderid> +<refcode>0</refcode> +<total>0.00</total> +<merchantordernumber></merchantordernumber> +<last4digits></last4digits> +<avsresult></avsresult> +<cvv2result></cvv2result> +<duplicate>0</duplicate> +<paytype></paytype> +</outputs> +</transaction> +</trans_catalog> +</interface_driver> + ) + end + + def successful_refund_response + %(<?xml version="1.0"?><interface_driver> +<trans_catalog> +<transaction> +<outputs> +<status>Approved</status> +<accountname>Longbob Longsen</accountname> +<result>CREDIT:TEST:::398045843:::</result> +<authcode>TEST</authcode> +<historyid>398045843</historyid> +<orderid>287822169</orderid> +<refcode>398045843</refcode> +<total>1.0</total> +<merchantordernumber>20eda293fcab33cd69fc22d662c08a1a</merchantordernumber> +<last4digits>6781</last4digits> +<avsresult></avsresult> +<cvv2result></cvv2result> +<duplicate>0</duplicate> +<paytype>Visa</paytype> +</outputs> +</transaction> +</trans_catalog> +</interface_driver> + ) + end + + def failed_refund_response + %(<?xml version="1.0"?><interface_driver> +<trans_catalog> +<transaction> +<outputs> +<status>Declined</status> +<accountname></accountname> +<result>DECLINED:1101700009:Missing account number:</result> +<authcode></authcode> +<historyid>0</historyid> +<orderid>0</orderid> +<refcode>0</refcode> +<total>0.00</total> +<merchantordernumber></merchantordernumber> +<last4digits></last4digits> +<avsresult></avsresult> +<cvv2result></cvv2result> +<duplicate>0</duplicate> +<paytype></paytype> +</outputs> +</transaction> +</trans_catalog> +</interface_driver> + ) + end + + def successful_credit_response + %(<?xml version="1.0"?><interface_driver> +<trans_catalog> +<transaction> +<outputs> +<status>Approved</status> +<accountname>Default Name</accountname> +<result>CREDIT:TEST:::398041091:::</result> +<authcode>TEST</authcode> +<historyid>398041091</historyid> +<orderid>287816362</orderid> +<refcode>398041091</refcode> +<total>1.0</total> +<merchantordernumber>8e36e7e5dedd21e3c5a2dc76b886b2e1</merchantordernumber> +<last4digits>6781</last4digits> +<avsresult></avsresult> +<cvv2result></cvv2result> +<duplicate>0</duplicate> +<paytype>Visa</paytype> +</outputs> +</transaction> +</trans_catalog> +</interface_driver> + ) + end + + def failed_credit_response + %(<?xml version="1.0"?><interface_driver> +<trans_catalog> +<transaction> +<outputs> +<status>Declined</status> +<accountname>Default Name</accountname> +<result>DECLINED:1102140001:Invalid account number:</result> +<authcode></authcode> +<historyid>398056981</historyid> +<orderid>287823604</orderid> +<refcode>398056981</refcode> +<total>1.0</total> +<merchantordernumber>18a6d240b6df82cf1fa4638088321473</merchantordernumber> +<last4digits>6782</last4digits> +<avsresult></avsresult> +<cvv2result></cvv2result> +<duplicate>0</duplicate> +<paytype>Visa</paytype> +</outputs> +</transaction> +</trans_catalog> +</interface_driver> + ) + end + + def successful_store_response + %(<?xml version="1.0"?><interface_driver> +<trans_catalog> +<transaction> +<outputs> +<status>Approved</status> +<accountname></accountname> +<result>PROFILEADD:Success:::0:::</result> +<authcode></authcode> +<historyid>0</historyid> +<orderid>0</orderid> +<userprofileid>17522090</userprofileid> +<refcode>0</refcode> +<total>0.00</total> +<merchantordernumber></merchantordernumber> +<last4digits>6781</last4digits> +<avsresult></avsresult> +<cvv2result></cvv2result> +<duplicate>0</duplicate> +<paytype></paytype> +</outputs> +</transaction> +</trans_catalog> +</interface_driver> + ) + end + + def failed_store_response + %(<?xml version="1.0"?><interface_driver> +<trans_catalog> +<transaction> +<outputs> +<status>Declined</status> +<accountname></accountname> +<result>DECLINED:1000390009:Live Transactions Not Allowed:</result> +<authcode></authcode> +<historyid>0</historyid> +<orderid>0</orderid> +<refcode>0</refcode> +<total>0.00</total> +<merchantordernumber></merchantordernumber> +<last4digits></last4digits> +<avsresult></avsresult> +<cvv2result></cvv2result> +<duplicate>0</duplicate> +<paytype></paytype> +</outputs> +</transaction> +</trans_catalog> +</interface_driver> + ) + end + + def transcript + %(<?xml version="1.0" encoding="UTF-8"?> + <interface_driver> + <trans_catalog> + <transaction name="creditcard"> + <inputs> + <amount>1.00</amount> + <merchantordernumber>41f093119352aae74d30478695ace163</merchantordernumber> + <currency>USD</currency> + <ccname>Longbob Longsen</ccname> + <ccnum>4003000123456781</ccnum> + <cvv2>123</cvv2> + <expmon>09</expmon> + <expyear>2016</expyear> + <billaddr1>456 My Street</billaddr1> + <billaddr2>Apt 1</billaddr2> + <billcity>Ottawa</billcity> + <billstate>ON</billstate> + <billcountry>CA</billcountry> + <bilzip>K1C2N6</bilzip> + <phone>(555)555-5555</phone> + <acctid>TEST0</acctid> + <merchantpin>1234567890</merchantpin> + <service>2</service> + </inputs> + </transaction> + </trans_catalog> + </interface_driver> + + <?xml version="1.0"?><interface_driver> + <trans_catalog> + <transaction> + <outputs> + <status>Approved</status> + <accountname>Longbob Longsen</accountname> + <result>AVSSALE:TEST:::398009684:N::U</result> + <authcode>TEST</authcode> + <historyid>398009684</historyid> + <orderid>287780716</orderid> + <refcode>398009684</refcode> + <total>1.0</total> + <merchantordernumber>41f093119352aae74d30478695ace163</merchantordernumber> + <last4digits>6781</last4digits> + <avsresult>N</avsresult> + <cvv2result>U</cvv2result> + <duplicate>0</duplicate> + <paytype>Visa</paytype> + </outputs> + </transaction> + </trans_catalog> + </interface_driver> + ) + end + + def scrubbed_transcript + %(<?xml version="1.0" encoding="UTF-8"?> + <interface_driver> + <trans_catalog> + <transaction name="creditcard"> + <inputs> + <amount>1.00</amount> + <merchantordernumber>41f093119352aae74d30478695ace163</merchantordernumber> + <currency>USD</currency> + <ccname>Longbob Longsen</ccname> + <ccnum>[FILTERED]</ccnum> + <cvv2>[FILTERED]</cvv2> + <expmon>09</expmon> + <expyear>2016</expyear> + <billaddr1>456 My Street</billaddr1> + <billaddr2>Apt 1</billaddr2> + <billcity>Ottawa</billcity> + <billstate>ON</billstate> + <billcountry>CA</billcountry> + <bilzip>K1C2N6</bilzip> + <phone>(555)555-5555</phone> + <acctid>TEST0</acctid> + <merchantpin>[FILTERED]</merchantpin> + <service>2</service> + </inputs> + </transaction> + </trans_catalog> + </interface_driver> + + <?xml version="1.0"?><interface_driver> + <trans_catalog> + <transaction> + <outputs> + <status>Approved</status> + <accountname>Longbob Longsen</accountname> + <result>AVSSALE:TEST:::398009684:N::U</result> + <authcode>TEST</authcode> + <historyid>398009684</historyid> + <orderid>287780716</orderid> + <refcode>398009684</refcode> + <total>1.0</total> + <merchantordernumber>41f093119352aae74d30478695ace163</merchantordernumber> + <last4digits>6781</last4digits> + <avsresult>N</avsresult> + <cvv2result>U</cvv2result> + <duplicate>0</duplicate> + <paytype>Visa</paytype> + </outputs> + </transaction> + </trans_catalog> + </interface_driver> + ) + end +end diff --git a/test/unit/gateways/merchant_ware_test.rb b/test/unit/gateways/merchant_ware_test.rb index 7b38537054e..bfaf52f2462 100644 --- a/test/unit/gateways/merchant_ware_test.rb +++ b/test/unit/gateways/merchant_ware_test.rb @@ -1,6 +1,8 @@ require 'test_helper' class MerchantWareTest < Test::Unit::TestCase + include CommStub + def setup @gateway = MerchantWareGateway.new( :login => 'login', @@ -25,12 +27,12 @@ def test_successful_authorization assert_success response assert_equal '4706382;1', response.authorization - assert_equal "APPROVED", response.message + assert_equal 'APPROVED', response.message assert response.test? end def test_soap_fault_during_authorization - response_500 = stub(:code => "500", :message => "Internal Server Error", :body => fault_authorization_response) + response_500 = stub(:code => '500', :message => 'Internal Server Error', :body => fault_authorization_response) @gateway.expects(:ssl_post).raises(ActiveMerchant::ResponseError.new(response_500)) assert response = @gateway.authorize(@amount, @credit_card, @options) @@ -39,9 +41,9 @@ def test_soap_fault_during_authorization assert response.test? assert_nil response.authorization - assert_equal "Server was unable to process request. ---> strPAN should be at least 13 to at most 19 characters in size. Parameter name: strPAN", response.message - assert_equal response_500.code, response.params["http_code"] - assert_equal response_500.message, response.params["http_message"] + assert_equal 'Server was unable to process request. ---> strPAN should be at least 13 to at most 19 characters in size. Parameter name: strPAN', response.message + assert_equal response_500.code, response.params['http_code'] + assert_equal response_500.message, response.params['http_message'] end def test_failed_authorization @@ -53,43 +55,43 @@ def test_failed_authorization assert response.test? assert_nil response.authorization - assert_equal "transaction type not supported by version", response.message - assert_equal "FAILED", response.params["status"] - assert_equal "1014", response.params["failure_code"] + assert_equal 'transaction type not supported by version', response.message + assert_equal 'FAILED', response.params['status'] + assert_equal '1014', response.params['failure_code'] end def test_credit - @gateway.expects(:ssl_post).with(anything, regexp_matches(/<strPAN>#{@credit_card.number}<\//), anything).returns("") + @gateway.expects(:ssl_post).with(anything, regexp_matches(/<strPAN>#{@credit_card.number}<\//), anything).returns('') @gateway.expects(:parse).returns({}) @gateway.credit(@amount, @credit_card, @options) end def test_deprecated_credit - @gateway.expects(:ssl_post).with(anything, regexp_matches(/<strReferenceCode>transaction_id<\//), anything).returns("") + @gateway.expects(:ssl_post).with(anything, regexp_matches(/<strReferenceCode>transaction_id<\//), anything).returns('') @gateway.expects(:parse).returns({}) - assert_deprecation_warning(Gateway::CREDIT_DEPRECATION_MESSAGE, @gateway) do - @gateway.credit(@amount, "transaction_id", @options) + assert_deprecation_warning(Gateway::CREDIT_DEPRECATION_MESSAGE) do + @gateway.credit(@amount, 'transaction_id', @options) end end def test_refund - @gateway.expects(:ssl_post).with(anything, regexp_matches(/<strReferenceCode>transaction_id<\//), anything).returns("") + @gateway.expects(:ssl_post).with(anything, regexp_matches(/<strReferenceCode>transaction_id<\//), anything).returns('') @gateway.expects(:parse).returns({}) - @gateway.refund(@amount, "transaction_id", @options) + @gateway.refund(@amount, 'transaction_id', @options) end def test_failed_void @gateway.expects(:ssl_post).returns(failed_void_response) - assert response = @gateway.void("1") + assert response = @gateway.void('1') assert_instance_of Response, response assert_failure response assert response.test? assert_nil response.authorization - assert_equal "decline", response.message - assert_equal "DECLINED", response.params["status"] - assert_equal "1012", response.params["failure_code"] + assert_equal 'decline', response.message + assert_equal 'DECLINED', response.params['status'] + assert_equal '1012', response.params['failure_code'] end def test_avs_result @@ -106,6 +108,16 @@ def test_cvv_result assert_equal 'M', response.cvv_result['code'] end + def test_add_swipe_data_with_creditcard + @credit_card.track_data = 'Track Data' + options = {:order_id => '1'} + stub_comms do + @gateway.authorize(@amount, @credit_card, options) + end.check_request do |endpoint, data, headers| + assert_match '<trackData>Track Data</trackData>', data + end.respond_with(successful_authorization_response) + end + private def successful_authorization_response diff --git a/test/unit/gateways/merchant_ware_version_four_test.rb b/test/unit/gateways/merchant_ware_version_four_test.rb index e6ba24b9806..4119f8f63d9 100644 --- a/test/unit/gateways/merchant_ware_version_four_test.rb +++ b/test/unit/gateways/merchant_ware_version_four_test.rb @@ -19,19 +19,19 @@ def setup end def test_successful_authorization - @gateway.expects(:ssl_post).returns(successful_authorization_response) + @gateway.expects(:ssl_post).returns(successful_authorize_response) assert response = @gateway.authorize(@amount, @credit_card, @options) assert_instance_of Response, response assert_success response assert_equal '1236564', response.authorization - assert_equal "APPROVED", response.message + assert_equal 'APPROVED', response.message assert response.test? end def test_soap_fault_during_authorization - response_400 = stub(:code => "400", :message => "Bad Request", :body => fault_authorization_response) + response_400 = stub(:code => '400', :message => 'Bad Request', :body => failed_authorize_response) @gateway.expects(:ssl_post).raises(ActiveMerchant::ResponseError.new(response_400)) assert response = @gateway.authorize(@amount, @credit_card, @options) @@ -40,9 +40,9 @@ def test_soap_fault_during_authorization assert response.test? assert_nil response.authorization - assert_equal "amount cannot be null. Parameter name: amount", response.message - assert_equal response_400.code, response.params["http_code"] - assert_equal response_400.message, response.params["http_message"] + assert_equal 'amount cannot be null. Parameter name: amount', response.message + assert_equal response_400.code, response.params['http_code'] + assert_equal response_400.message, response.params['http_message'] end def test_failed_authorization @@ -54,9 +54,9 @@ def test_failed_authorization assert response.test? assert_nil response.authorization - assert_equal "invalid exp date", response.message - assert_equal "DECLINED", response.params["status"] - assert_equal "1024", response.params["failure_code"] + assert_equal 'invalid exp date', response.message + assert_equal 'DECLINED', response.params['status'] + assert_equal '1024', response.params['failure_code'] end def test_failed_authorization_due_to_invalid_credit_card_number @@ -68,40 +68,40 @@ def test_failed_authorization_due_to_invalid_credit_card_number assert response.test? assert_nil response.authorization - assert_equal "Invalid card number.", response.message - assert_nil response.params["status"] - assert_nil response.params["failure_code"] + assert_equal 'Invalid card number.', response.message + assert_nil response.params['status'] + assert_nil response.params['failure_code'] end def test_refund - @gateway.expects(:ssl_post).with(anything, regexp_matches(/<token>transaction_id<\//), anything).returns("") + @gateway.expects(:ssl_post).with(anything, regexp_matches(/<token>transaction_id<\//), anything).returns('') @gateway.expects(:parse).returns({}) - @gateway.refund(@amount, "transaction_id", @options) + @gateway.refund(@amount, 'transaction_id', @options) end def test_failed_void @gateway.expects(:ssl_post).returns(failed_void_response) - assert response = @gateway.void("1") + assert response = @gateway.void('1') assert_instance_of Response, response assert_failure response assert response.test? assert_nil response.authorization - assert_equal "original transaction id not found", response.message - assert_equal "DECLINED", response.params["status"] - assert_equal "1019", response.params["failure_code"] + assert_equal 'original transaction id not found', response.message + assert_equal 'DECLINED', response.params['status'] + assert_equal '1019', response.params['failure_code'] end def test_avs_result - @gateway.expects(:ssl_post).returns(successful_authorization_response) + @gateway.expects(:ssl_post).returns(successful_authorize_response) response = @gateway.authorize(@amount, @credit_card, @options) assert_equal 'N', response.avs_result['code'] end def test_cvv_result - @gateway.expects(:ssl_post).returns(successful_authorization_response) + @gateway.expects(:ssl_post).returns(successful_authorize_response) response = @gateway.authorize(@amount, @credit_card, @options) assert_equal 'M', response.cvv_result['code'] @@ -115,13 +115,52 @@ def test_successful_purchase_using_prior_transaction assert_success response assert_equal '1236564', response.authorization - assert_equal "APPROVED", response.message + assert_equal 'APPROVED', response.message assert response.test? end + def test_successful_verify + @gateway.expects(:ssl_post).times(2).returns(successful_authorize_response, successful_void_response) + + response = @gateway.verify(@credit_card, @options) + assert_success response + assert_equal 'APPROVED', response.message + end + + def test_successful_verify_with_failed_void + @gateway.expects(:ssl_post).times(2).returns(successful_authorize_response, failed_void_response) + + response = @gateway.verify(@credit_card, @options) + assert_success response + end + + def test_failed_verify + @gateway.expects(:ssl_post).returns(failed_authorize_response) + + response = @gateway.verify(@credit_card, @options) + assert_failure response + end + + def test_scrub + assert @gateway.supports_scrubbing? + assert_equal @gateway.scrub(pre_scrubbed), post_scrubbed + end + private - def successful_authorization_response + def pre_scrubbed + %q( + <?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<soap:Envelope xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">\n <soap:Body>\n <SaleKeyed xmlns=\"http://schemas.merchantwarehouse.com/merchantware/40/Credit/\">\n <merchantName>Test Spreedly PayItSimple</merchantName>\n <merchantSiteId>BK34Z768</merchantSiteId>\n <merchantKey>TCTTS-IDYQV-RDFY1-6DS01-WTPVH</merchantKey>\n <invoiceNumber>14b33b8a</invoiceNumber>\n <amount>10.20</amount>\n <cardNumber>5424180279791732</cardNumber>\n <expirationDate>0916</expirationDate>\n <cardholder>Longbob Longsen</cardholder>\n <cardSecurityCode>123</cardSecurityCode>\n <avsStreetAddress>456 My Street</avsStreetAddress>\n <avsStreetZipCode>K1C2N6</avsStreetZipCode>\n </SaleKeyed>\n </soap:Body>\n</soap:Envelope>\n" + ) + end + + def post_scrubbed + %q( + <?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<soap:Envelope xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">\n <soap:Body>\n <SaleKeyed xmlns=\"http://schemas.merchantwarehouse.com/merchantware/40/Credit/\">\n <merchantName>Test Spreedly PayItSimple</merchantName>\n <merchantSiteId>BK34Z768</merchantSiteId>\n <merchantKey>[FILTERED]</merchantKey>\n <invoiceNumber>14b33b8a</invoiceNumber>\n <amount>10.20</amount>\n <cardNumber>[FILTERED]</cardNumber>\n <expirationDate>0916</expirationDate>\n <cardholder>Longbob Longsen</cardholder>\n <cardSecurityCode>[FILTERED]</cardSecurityCode>\n <avsStreetAddress>456 My Street</avsStreetAddress>\n <avsStreetZipCode>K1C2N6</avsStreetZipCode>\n </SaleKeyed>\n </soap:Body>\n</soap:Envelope>\n" + ) + end + + def successful_authorize_response <<-XML <?xml version="1.0" encoding="utf-8"?> <soap:Envelope @@ -153,7 +192,7 @@ def successful_authorization_response XML end - def fault_authorization_response + def failed_authorize_response <<-XML <?xml version="1.0" encoding="utf-8"?> <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" @@ -213,12 +252,41 @@ def failed_authorization_response XML end + def successful_void_response + <<-XML +<?xml version="1.0" encoding="utf-8"?> +<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> + <soap:Body> + <VoidResponse xmlns="http://schemas.merchantwarehouse.com/merchantware/40/Credit/"> + <VoidResult> + <Amount /> + <ApprovalStatus>APPROVED</ApprovalStatus> + <AuthorizationCode>VOID</AuthorizationCode> + <AvsResponse /> + <Cardholder /> + <CardNumber /> + <CardType>0</CardType> + <CvResponse /> + <EntryMode>0</EntryMode> + <ErrorMessage /> + <ExtraData /> + <InvoiceNumber /> + <Token>266783537</Token> + <TransactionDate>7/9/2015 3:13:51 PM</TransactionDate> + <TransactionType>3</TransactionType> + </VoidResult> + </VoidResponse> + </soap:Body> +</soap:Envelope> + XML + end + def failed_void_response <<-XML <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <soap:Body> - <VoidPreAuthorizationResponse xmlns="http://schemas.merchantwarehouse.com/merchantware/40/Credit/"> - <VoidPreAuthorizationResult> + <VoidResponse xmlns="http://schemas.merchantwarehouse.com/merchantware/40/Credit/"> + <VoidResult> <Amount /> <ApprovalStatus>DECLINED;1019;original transaction id not found</ApprovalStatus> <AuthorizationCode /> @@ -234,8 +302,8 @@ def failed_void_response <Token /> <TransactionDate>5/15/2013 9:37:04 AM</TransactionDate> <TransactionType>3</TransactionType> - </VoidPreAuthorizationResult> - </VoidPreAuthorizationResponse> + </VoidResult> + </VoidResponse> </soap:Body> </soap:Envelope> XML diff --git a/test/unit/gateways/merchant_warrior_test.rb b/test/unit/gateways/merchant_warrior_test.rb index e1495f8b32c..2d718e7da74 100644 --- a/test/unit/gateways/merchant_warrior_test.rb +++ b/test/unit/gateways/merchant_warrior_test.rb @@ -16,14 +16,7 @@ def setup @failure_amount = 10033 @options = { - :address => { - :name => 'Longbob Longsen', - :country => 'AU', - :state => 'Queensland', - :city => 'Brisbane', - :address1 => '123 test st', - :zip => '4000' - }, + :address => address, :transaction_product => 'TestProduct' } end @@ -35,7 +28,7 @@ def test_successful_purchase assert_success response assert_equal 'Transaction approved', response.message assert response.test? - assert_equal "30-98a79008-dae8-11df-9322-0022198101cd", response.authorization + assert_equal '30-98a79008-dae8-11df-9322-0022198101cd', response.authorization end def test_failed_purchase @@ -45,17 +38,17 @@ def test_failed_purchase assert_failure response assert_equal 'Card has expired', response.message assert response.test? - assert_equal "30-69433444-af1-11df-9322-0022198101cd", response.authorization + assert_equal '30-69433444-af1-11df-9322-0022198101cd', response.authorization end def test_successful_refund @gateway.expects(:ssl_post).returns(successful_refund_response) - assert response = @gateway.refund(@success_amount, @transaction_id) + assert response = @gateway.refund(@success_amount, @transaction_id, @options) assert_success response assert_equal 'Transaction approved', response.message assert response.test? - assert_equal "30-d4d19f4-db17-11df-9322-0022198101cd", response.authorization + assert_equal '30-d4d19f4-db17-11df-9322-0022198101cd', response.authorization end def test_failed_refund @@ -69,8 +62,8 @@ def test_failed_refund end def test_successful_store - @credit_card.month = "2" - @credit_card.year = "2005" + @credit_card.month = '2' + @credit_card.year = '2005' store = stub_comms do @gateway.store(@credit_card, @options) @@ -80,8 +73,75 @@ def test_successful_store end.respond_with(successful_store_response) assert_success store - assert_equal "Operation successful", store.message - assert_match "KOCI10023982", store.authorization + assert_equal 'Operation successful', store.message + assert_match 'KOCI10023982', store.authorization + end + + def test_scrub_name + @credit_card.first_name = "Chars; Merchant-Warrior Don't Like" + @credit_card.last_name = '& More. # Here' + @options[:address][:name] = 'Ren & Stimpy' + + stub_comms do + @gateway.purchase(@success_amount, @credit_card, @options) + end.check_request do |endpoint, data, headers| + assert_match(/customerName=Ren\+\+Stimpy/, data) + assert_match(/paymentCardName=Chars\+Merchant-Warrior\+Dont\+Like\+\+More\.\+\+Here/, data) + end.respond_with(successful_purchase_response) + end + + def test_address + @options[:address] = { + name: 'Bat Man', + address1: '123 Main', + city: 'Brooklyn', + state: 'NY', + country: 'US', + zip: '11111', + phone: '555-1212', + email: 'user@aol.com', + ip: '1.2.3.4' + } + + stub_comms do + @gateway.purchase(@success_amount, @credit_card, @options) + end.check_request do |endpoint, data, headers| + assert_match(/customerName=Bat\+Man/, data) + assert_match(/customerCountry=US/, data) + assert_match(/customerState=NY/, data) + assert_match(/customerCity=Brooklyn/, data) + assert_match(/customerAddress=123\+Main/, data) + assert_match(/customerPostCode=11111/, data) + assert_match(/customerIP=1.2.3.4/, data) + assert_match(/customerPhone=555-1212/, data) + assert_match(/customerEmail=user%40aol.com/, data) + end.respond_with(successful_purchase_response) + end + + def test_address_without_state + @options[:address] = { + name: 'Bat Man', + state: nil + } + + stub_comms do + @gateway.purchase(@success_amount, @credit_card, @options) + end.check_request do |endpoint, data, headers| + assert_match(/customerState=N%2FA/, data) + end.respond_with(successful_purchase_response) + end + + def test_orderid_truncated + stub_comms do + @gateway.purchase(@success_amount, @credit_card, order_id: 'ThisIsQuiteALongDescriptionWithLotsOfChars') + end.check_request do |endpoint, data, headers| + assert_match(/transactionProduct=ThisIsQuiteALongDescriptionWithLot&/, data) + end.respond_with(successful_purchase_response) + end + + def test_scrub + assert @gateway.supports_scrubbing? + assert_equal @gateway.scrub(pre_scrubbed), post_scrubbed end private @@ -161,4 +221,12 @@ def successful_store_response </mwResponse> XML end + + def pre_scrubbed + 'transactionAmount=1.00&transactionCurrency=AUD&hash=adb50f6ff360f861e6f525e8daae76b5&transactionProduct=98fc25d40a47f3d24da460c0ca307c&customerName=Longbob+Longsen&customerCountry=AU&customerState=Queensland&customerCity=Brisbane&customerAddress=123+test+st&customerPostCode=4000&customerIP=&customerPhone=&customerEmail=&paymentCardNumber=5123456789012346&paymentCardName=Longbob+Longsen&paymentCardExpiry=0520&paymentCardCSC=123&merchantUUID=51f7da294af8f&apiKey=nooudtd0&method=processCard' + end + + def post_scrubbed + 'transactionAmount=1.00&transactionCurrency=AUD&hash=adb50f6ff360f861e6f525e8daae76b5&transactionProduct=98fc25d40a47f3d24da460c0ca307c&customerName=Longbob+Longsen&customerCountry=AU&customerState=Queensland&customerCity=Brisbane&customerAddress=123+test+st&customerPostCode=4000&customerIP=&customerPhone=&customerEmail=&paymentCardNumber=[FILTERED]&paymentCardName=Longbob+Longsen&paymentCardExpiry=0520&paymentCardCSC=[FILTERED]&merchantUUID=51f7da294af8f&apiKey=[FILTERED]&method=processCard' + end end diff --git a/test/unit/gateways/mercury_test.rb b/test/unit/gateways/mercury_test.rb index 9b34fad0647..e7d298f52b4 100644 --- a/test/unit/gateways/mercury_test.rb +++ b/test/unit/gateways/mercury_test.rb @@ -4,16 +4,16 @@ class MercuryTest < Test::Unit::TestCase include CommStub def setup - Base.gateway_mode = :test + Base.mode = :test @gateway = MercuryGateway.new(fixtures(:mercury)) @amount = 100 - @credit_card = credit_card("5499990123456781", :brand => "master") + @credit_card = credit_card('5499990123456781', :brand => 'master') @declined_card = credit_card('4000300011112220') @options = { - :order_id => '1' + :order_id => 'c111111111.1' } end @@ -21,7 +21,7 @@ def test_successful_purchase response = stub_comms do @gateway.purchase(@amount, @credit_card, @options) end.check_request do |endpoint, data, headers| - assert_match(/InvoiceNo>1</, data) + assert_match(/InvoiceNo>c111111111.1</, data) assert_match(/Frequency>OneTime/, data) assert_match(/RecordNo>RecordNumberRequested/, data) end.respond_with(successful_purchase_response) @@ -33,11 +33,14 @@ def test_successful_purchase assert response.test? end - def test_order_id_must_be_numeric - e = assert_raise(ArgumentError) do - @gateway.purchase(@amount, @credit_card, :order_id => "a") - end - assert_match(/not numeric/, e.message) + def test_successful_purchase_with_allow_partial_auth + response = stub_comms do + @gateway.purchase(@amount, @credit_card, @options.merge(allow_partial_auth: true)) + end.check_request do |endpoint, data, headers| + assert_match(/PartialAuth>Allow</, data) + end.respond_with(successful_purchase_response) + + assert_success response end def test_unsuccessful_request @@ -46,6 +49,7 @@ def test_unsuccessful_request response = @gateway.purchase(@amount, @credit_card, @options) assert_failure response assert response.test? + assert_equal Gateway::STANDARD_ERROR_CODE[:card_declined], response.error_code end def test_successful_refund @@ -59,91 +63,198 @@ def test_successful_refund assert refund.test? end + def test_card_present_with_track_1_data + track_data = '%B4003000123456781^LONGSEN/L. ^15121200000000000000123?' + @credit_card.track_data = track_data + response = stub_comms do + @gateway.purchase(@amount, @credit_card, @options) + end.check_request do |endpoint, data, headers| + assert_match(/<Track1>#{Regexp.escape(track_data)}<\/Track1>/, data) + end.respond_with(successful_purchase_response) + + assert_instance_of Response, response + assert_success response + end + + def test_card_present_with_track_2_data + track_data = ';5413330089010608=2512101097750213?' + stripped_track_data = '5413330089010608=2512101097750213' + @credit_card.track_data = track_data + response = stub_comms do + @gateway.purchase(@amount, @credit_card, @options) + end.check_request do |endpoint, data, headers| + assert_match(/<Track2>#{Regexp.escape(stripped_track_data)}<\/Track2>/, data) + end.respond_with(successful_purchase_response) + + assert_instance_of Response, response + assert_success response + end + + def test_card_present_with_max_length_track_1_data + track_data = '%B373953192351004^CARDUSER/JOHN^200910100000019301000000877000000930001234567?' + stripped_data = 'B373953192351004^CARDUSER/JOHN^200910100000019301000000877000000930001234567' + @credit_card.track_data = track_data + response = stub_comms do + @gateway.purchase(@amount, @credit_card, @options) + end.check_request do |endpoint, data, headers| + assert_match(/<Track1>#{Regexp.escape(stripped_data)}<\/Track1>/, data) + end.respond_with(successful_purchase_response) + + assert_instance_of Response, response + assert_success response + end + + def test_card_present_with_invalid_data + track_data = 'this is not valid track data' + @credit_card.track_data = track_data + response = stub_comms do + @gateway.purchase(@amount, @credit_card, @options) + end.check_request do |endpoint, data, headers| + assert_match(/<Track1>#{Regexp.escape(track_data)}<\/Track1>/, data) + end.respond_with(successful_purchase_response) + + assert_instance_of Response, response + assert_success response + end + + def test_transcript_scrubbing + assert @gateway.supports_scrubbing? + assert_equal @gateway.scrub(pre_scrub), post_scrub + end + private def successful_purchase_response <<-RESPONSE -<?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><soap:Body><CreditTransactionResponse xmlns="http://www.mercurypay.com"><CreditTransactionResult>&lt;?xml version="1.0"?&gt; -&lt;RStream&gt; - &lt;CmdResponse&gt; - &lt;ResponseOrigin&gt;Processor&lt;/ResponseOrigin&gt; - &lt;DSIXReturnCode&gt;000000&lt;/DSIXReturnCode&gt; - &lt;CmdStatus&gt;Approved&lt;/CmdStatus&gt; - &lt;TextResponse&gt;AP*&lt;/TextResponse&gt; - &lt;UserTraceData&gt;&lt;/UserTraceData&gt; - &lt;/CmdResponse&gt; - &lt;TranResponse&gt; - &lt;MerchantID&gt;595901&lt;/MerchantID&gt; - &lt;AcctNo&gt;5499990123456781&lt;/AcctNo&gt; - &lt;ExpDate&gt;0813&lt;/ExpDate&gt; - &lt;CardType&gt;M/C&lt;/CardType&gt; - &lt;TranCode&gt;Sale&lt;/TranCode&gt; - &lt;AuthCode&gt;000011&lt;/AuthCode&gt; - &lt;CaptureStatus&gt;Captured&lt;/CaptureStatus&gt; - &lt;RefNo&gt;0194&lt;/RefNo&gt; - &lt;InvoiceNo&gt;1&lt;/InvoiceNo&gt; - &lt;AVSResult&gt;Y&lt;/AVSResult&gt; - &lt;CVVResult&gt;M&lt;/CVVResult&gt; - &lt;OperatorID&gt;999&lt;/OperatorID&gt; - &lt;Memo&gt;LM Integration (Ruby)&lt;/Memo&gt; - &lt;Amount&gt; - &lt;Purchase&gt;1.00&lt;/Purchase&gt; - &lt;Authorize&gt;1.00&lt;/Authorize&gt; - &lt;/Amount&gt; - &lt;AcqRefData&gt;KbMCC0742510421 &lt;/AcqRefData&gt; - &lt;ProcessData&gt;|17|410100700000&lt;/ProcessData&gt; - &lt;/TranResponse&gt; -&lt;/RStream&gt; +<?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><soap:Body><CreditTransactionResponse xmlns="http://www.mercurypay.com"><CreditTransactionResult><?xml version="1.0"?> +<RStream> + <CmdResponse> + <ResponseOrigin>Processor</ResponseOrigin> + <DSIXReturnCode>000000</DSIXReturnCode> + <CmdStatus>Approved</CmdStatus> + <TextResponse>AP*</TextResponse> + <UserTraceData></UserTraceData> + </CmdResponse> + <TranResponse> + <MerchantID>595901</MerchantID> + <AcctNo>5499990123456781</AcctNo> + <ExpDate>0813</ExpDate> + <CardType>M/C</CardType> + <TranCode>Sale</TranCode> + <AuthCode>000011</AuthCode> + <CaptureStatus>Captured</CaptureStatus> + <RefNo>0194</RefNo> + <InvoiceNo>1</InvoiceNo> + <AVSResult>Y</AVSResult> + <CVVResult>M</CVVResult> + <OperatorID>999</OperatorID> + <Memo>LM Integration (Ruby)</Memo> + <Amount> + <Purchase>1.00</Purchase> + <Authorize>1.00</Authorize> + </Amount> + <AcqRefData>KbMCC0742510421 </AcqRefData> + <ProcessData>|17|410100700000</ProcessData> + </TranResponse> +</RStream> </CreditTransactionResult></CreditTransactionResponse></soap:Body></soap:Envelope> RESPONSE end def failed_purchase_response <<-RESPONSE -<?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><soap:Body><CreditTransactionResponse xmlns="http://www.mercurypay.com"><CreditTransactionResult>&lt;?xml version="1.0"?&gt; -&lt;RStream&gt; - &lt;CmdResponse&gt; - &lt;ResponseOrigin&gt;Server&lt;/ResponseOrigin&gt; - &lt;DSIXReturnCode&gt;004101&lt;/DSIXReturnCode&gt; - &lt;CmdStatus&gt;Error&lt;/CmdStatus&gt; - &lt;TextResponse&gt;No Live Cards on Test Merchant ID Allowed.&lt;/TextResponse&gt; - &lt;UserTraceData&gt;&lt;/UserTraceData&gt; - &lt;/CmdResponse&gt; -&lt;/RStream&gt; +<?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><soap:Body><CreditTransactionResponse xmlns="http://www.mercurypay.com"><CreditTransactionResult><?xml version="1.0"?> +<RStream> + <CmdResponse> + <ResponseOrigin>Server</ResponseOrigin> + <DSIXReturnCode>000000</DSIXReturnCode> + <CmdStatus>Error</CmdStatus> + <TextResponse>No Live Cards on Test Merchant ID Allowed.</TextResponse> + <UserTraceData></UserTraceData> + </CmdResponse> +</RStream> </CreditTransactionResult></CreditTransactionResponse></soap:Body></soap:Envelope> RESPONSE end def successful_refund_response <<-RESPONSE -<?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><soap:Body><CreditTransactionResponse xmlns="http://www.mercurypay.com"><CreditTransactionResult>&lt;?xml version="1.0"?&gt; -&lt;RStream&gt; - &lt;CmdResponse&gt; - &lt;ResponseOrigin&gt;Processor&lt;/ResponseOrigin&gt; - &lt;DSIXReturnCode&gt;000000&lt;/DSIXReturnCode&gt; - &lt;CmdStatus&gt;Approved&lt;/CmdStatus&gt; - &lt;TextResponse&gt;AP&lt;/TextResponse&gt; - &lt;UserTraceData&gt;&lt;/UserTraceData&gt; - &lt;/CmdResponse&gt; - &lt;TranResponse&gt; - &lt;MerchantID&gt;595901&lt;/MerchantID&gt; - &lt;AcctNo&gt;5499990123456781&lt;/AcctNo&gt; - &lt;ExpDate&gt;0813&lt;/ExpDate&gt; - &lt;CardType&gt;M/C&lt;/CardType&gt; - &lt;TranCode&gt;VoidSale&lt;/TranCode&gt; - &lt;AuthCode&gt;VOIDED&lt;/AuthCode&gt; - &lt;CaptureStatus&gt;Captured&lt;/CaptureStatus&gt; - &lt;RefNo&gt;0568&lt;/RefNo&gt; - &lt;InvoiceNo&gt;123&lt;/InvoiceNo&gt; - &lt;OperatorID&gt;999&lt;/OperatorID&gt; - &lt;Amount&gt; - &lt;Purchase&gt;1.00&lt;/Purchase&gt; - &lt;Authorize&gt;1.00&lt;/Authorize&gt; - &lt;/Amount&gt; - &lt;AcqRefData&gt;K&lt;/AcqRefData&gt; - &lt;/TranResponse&gt; -&lt;/RStream&gt; +<?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><soap:Body><CreditTransactionResponse xmlns="http://www.mercurypay.com"><CreditTransactionResult><?xml version="1.0"?> +<RStream> + <CmdResponse> + <ResponseOrigin>Processor</ResponseOrigin> + <DSIXReturnCode>000000</DSIXReturnCode> + <CmdStatus>Approved</CmdStatus> + <TextResponse>AP</TextResponse> + <UserTraceData></UserTraceData> + </CmdResponse> + <TranResponse> + <MerchantID>595901</MerchantID> + <AcctNo>5499990123456781</AcctNo> + <ExpDate>0813</ExpDate> + <CardType>M/C</CardType> + <TranCode>VoidSale</TranCode> + <AuthCode>VOIDED</AuthCode> + <CaptureStatus>Captured</CaptureStatus> + <RefNo>0568</RefNo> + <InvoiceNo>123</InvoiceNo> + <OperatorID>999</OperatorID> + <Amount> + <Purchase>1.00</Purchase> + <Authorize>1.00</Authorize> + </Amount> + <AcqRefData>K</AcqRefData> + </TranResponse> +</RStream> </CreditTransactionResult></CreditTransactionResponse></soap:Body></soap:Envelope> RESPONSE end + + def pre_scrub + %q{ +opening connection to w1.mercurycert.net:443... +opened +starting SSL for w1.mercurycert.net:443... +SSL established +<- "POST /ws/ws.asmx HTTP/1.1\r\nContent-Type: text/xml; charset=utf-8\r\nSoapaction: http://www.mercurypay.com/CreditTransaction\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: w1.mercurycert.net\r\nContent-Length: 823\r\n\r\n" +<- "<?xml version=\"1.0\" encoding=\"UTF-8\"?><soap:Envelope xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"><soap:Body><CreditTransaction xmlns=\"http://www.mercurypay.com\"><tran>\n<![CDATA[\n<TStream><Transaction><TranType>Credit</TranType><TranCode>Sale</TranCode><InvoiceNo>c111111111.1</InvoiceNo><RefNo>c111111111.1</RefNo><Memo>ActiveMerchant</Memo><Frequency>OneTime</Frequency><RecordNo>RecordNumberRequested</RecordNo><MerchantID>089716741701445</MerchantID><Amount><Purchase>1.00</Purchase></Amount><Account><AcctNo>4003000123456781</AcctNo><ExpDate>1218</ExpDate></Account><CardType>VISA</CardType><CVVData>123</CVVData></Transaction></TStream>\n]]>\n</tran><pw>xyz</pw></CreditTransaction></soap:Body></soap:Envelope>" +-> "HTTP/1.1 200 OK\r\n" +-> "Cache-Control: private, max-age=0\r\n" +-> "Content-Type: text/xml; charset=utf-8\r\n" +-> "X-AspNet-Version: 4.0.30319\r\n" +-> "X-Powered-By: ASP.NET\r\n" +-> "Date: Mon, 08 Jan 2018 19:49:31 GMT\r\n" +-> "Connection: close\r\n" +-> "Content-Length: 1648\r\n" +-> "\r\n" +reading 1648 bytes... +-> "<?xml version=\"1.0\" encoding=\"utf-8\"?><soap:Envelope xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"><soap:Body><CreditTransactionResponse xmlns=\"http://www.mercurypay.com\"><CreditTransactionResult><?xml version=\"1.0\"?>\r\n<RStream>\r\n\t<CmdResponse>\r\n\t\t<ResponseOrigin>Processor</ResponseOrigin>\r\n\t\t<DSIXReturnCode>000000</DSIXReturnCode>\r\n\t\t<CmdStatus>Approved</CmdStatus>\r\n\t\t<TextResponse>AP*</TextResponse>\r\n\t\t<UserTraceData></UserTraceData>\r\n\t</CmdResponse>\r\n\t<TranResponse>\r\n\t\t<MerchantID>089716741701445</MerchantID>\r\n\t\t<AcctNo>400300XXXXXX6781</AcctNo>\r\n\t\t<ExpDate>XXXX</ExpDate>\r\n\t\t<CardType>VISA</CardType>\r\n\t\t<TranCode>Sale</TranCode>\r\n\t\t<AuthCode>VI0100</AuthCode>\r\n\t\t<CaptureStatus>Captured</CaptureStatus>\r\n\t\t<RefNo>0001</RefNo>\r\n\t\t<InvoiceNo>C111111111.1</InvoiceNo>\r\n\t\t<CVVResult>U</CVVResult>\r\n\t\t<Memo>ActiveMerchant</Memo>\r\n\t\t<Amount>\r\n\t\t\t<Purchase>1.00</Purchase>\r\n\t\t\t<Authorize>1.00</Authorize>\r\n\t\t</Amount>\r\n\t\t<AcqRefData>KaNb018008177003332cABCAd5e00fJlA m000005</AcqRefData>\r\n\t\t<RecordNo>win4rRFHp8+AV/vstAfKvsUvZ5IH+bHblTktfumnY/EiEgUQFyIQGjMM</RecordNo>\r\n\t\t<ProcessData>|00|600550672000</ProcessData>\r\n\t</TranResponse>\r\n</RStream>\r\n</CreditTransactionResult></CreditTransactionResponse></soap:Body></soap:Envelope>" +read 1648 bytes +Conn close + } + end + + def post_scrub + %q{ +opening connection to w1.mercurycert.net:443... +opened +starting SSL for w1.mercurycert.net:443... +SSL established +<- "POST /ws/ws.asmx HTTP/1.1\r\nContent-Type: text/xml; charset=utf-8\r\nSoapaction: http://www.mercurypay.com/CreditTransaction\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: w1.mercurycert.net\r\nContent-Length: 823\r\n\r\n" +<- "<?xml version=\"1.0\" encoding=\"UTF-8\"?><soap:Envelope xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"><soap:Body><CreditTransaction xmlns=\"http://www.mercurypay.com\"><tran>\n<![CDATA[\n<TStream><Transaction><TranType>Credit</TranType><TranCode>Sale</TranCode><InvoiceNo>c111111111.1</InvoiceNo><RefNo>c111111111.1</RefNo><Memo>ActiveMerchant</Memo><Frequency>OneTime</Frequency><RecordNo>RecordNumberRequested</RecordNo><MerchantID>089716741701445</MerchantID><Amount><Purchase>1.00</Purchase></Amount><Account><AcctNo>[FILTERED]</AcctNo><ExpDate>1218</ExpDate></Account><CardType>VISA</CardType><CVVData>[FILTERED]</CVVData></Transaction></TStream>\n]]>\n</tran><pw>[FILTERED]</pw></CreditTransaction></soap:Body></soap:Envelope>" +-> "HTTP/1.1 200 OK\r\n" +-> "Cache-Control: private, max-age=0\r\n" +-> "Content-Type: text/xml; charset=utf-8\r\n" +-> "X-AspNet-Version: 4.0.30319\r\n" +-> "X-Powered-By: ASP.NET\r\n" +-> "Date: Mon, 08 Jan 2018 19:49:31 GMT\r\n" +-> "Connection: close\r\n" +-> "Content-Length: 1648\r\n" +-> "\r\n" +reading 1648 bytes... +-> "<?xml version=\"1.0\" encoding=\"utf-8\"?><soap:Envelope xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"><soap:Body><CreditTransactionResponse xmlns=\"http://www.mercurypay.com\"><CreditTransactionResult><?xml version=\"1.0\"?>\r\n<RStream>\r\n\t<CmdResponse>\r\n\t\t<ResponseOrigin>Processor</ResponseOrigin>\r\n\t\t<DSIXReturnCode>000000</DSIXReturnCode>\r\n\t\t<CmdStatus>Approved</CmdStatus>\r\n\t\t<TextResponse>AP*</TextResponse>\r\n\t\t<UserTraceData></UserTraceData>\r\n\t</CmdResponse>\r\n\t<TranResponse>\r\n\t\t<MerchantID>089716741701445</MerchantID>\r\n\t\t<AcctNo>[FILTERED]</AcctNo>\r\n\t\t<ExpDate>XXXX</ExpDate>\r\n\t\t<CardType>VISA</CardType>\r\n\t\t<TranCode>Sale</TranCode>\r\n\t\t<AuthCode>VI0100</AuthCode>\r\n\t\t<CaptureStatus>Captured</CaptureStatus>\r\n\t\t<RefNo>0001</RefNo>\r\n\t\t<InvoiceNo>C111111111.1</InvoiceNo>\r\n\t\t<CVVResult>U</CVVResult>\r\n\t\t<Memo>ActiveMerchant</Memo>\r\n\t\t<Amount>\r\n\t\t\t<Purchase>1.00</Purchase>\r\n\t\t\t<Authorize>1.00</Authorize>\r\n\t\t</Amount>\r\n\t\t<AcqRefData>KaNb018008177003332cABCAd5e00fJlA m000005</AcqRefData>\r\n\t\t<RecordNo>win4rRFHp8+AV/vstAfKvsUvZ5IH+bHblTktfumnY/EiEgUQFyIQGjMM</RecordNo>\r\n\t\t<ProcessData>|00|600550672000</ProcessData>\r\n\t</TranResponse>\r\n</RStream>\r\n</CreditTransactionResult></CreditTransactionResponse></soap:Body></soap:Envelope>" +read 1648 bytes +Conn close + } + end end diff --git a/test/unit/gateways/metrics_global_test.rb b/test/unit/gateways/metrics_global_test.rb index 33cba856121..e8e3e7da467 100644 --- a/test/unit/gateways/metrics_global_test.rb +++ b/test/unit/gateways/metrics_global_test.rb @@ -16,52 +16,51 @@ def setup def test_successful_authorization @gateway.expects(:ssl_post).returns(successful_authorization_response) - + assert response = @gateway.authorize(@amount, @credit_card) assert_instance_of Response, response assert_success response assert_equal '508141794', response.authorization end - + def test_successful_purchase @gateway.expects(:ssl_post).returns(successful_purchase_response) - + assert response = @gateway.purchase(@amount, @credit_card) assert_instance_of Response, response assert_success response assert_equal '508141795', response.authorization end - + def test_failed_authorization @gateway.expects(:ssl_post).returns(failed_authorization_response) - + assert response = @gateway.authorize(@amount, @credit_card) assert_instance_of Response, response assert_failure response assert_equal '508141794', response.authorization end - + def test_add_address_outsite_north_america result = {} - - @gateway.send(:add_address, result, :billing_address => {:address1 => '164 Waverley Street', :country => 'DE', :state => ''} ) - - assert_equal ["address", "city", "company", "country", "phone", "state", "zip"], result.stringify_keys.keys.sort + + @gateway.send(:add_address, result, :billing_address => {:address1 => '164 Waverley Street', :country => 'DE', :state => ''}) + + assert_equal ['address', 'city', 'company', 'country', 'phone', 'state', 'zip'], result.stringify_keys.keys.sort assert_equal 'n/a', result[:state] - assert_equal '164 Waverley Street', result[:address] - assert_equal 'DE', result[:country] + assert_equal '164 Waverley Street', result[:address] + assert_equal 'DE', result[:country] end - + def test_add_address result = {} - - @gateway.send(:add_address, result, :billing_address => {:address1 => '164 Waverley Street', :country => 'US', :state => 'CO'} ) - - assert_equal ["address", "city", "company", "country", "phone", "state", "zip"], result.stringify_keys.keys.sort + + @gateway.send(:add_address, result, :billing_address => {:address1 => '164 Waverley Street', :country => 'US', :state => 'CO'}) + + assert_equal ['address', 'city', 'company', 'country', 'phone', 'state', 'zip'], result.stringify_keys.keys.sort assert_equal 'CO', result[:state] assert_equal '164 Waverley Street', result[:address] assert_equal 'US', result[:country] - end def test_add_invoice @@ -69,40 +68,40 @@ def test_add_invoice @gateway.send(:add_invoice, result, :order_id => '#1001') assert_equal '#1001', result[:invoice_num] end - + def test_add_description result = {} @gateway.send(:add_invoice, result, :description => 'My Purchase is great') assert_equal 'My Purchase is great', result[:description] end - + def test_add_duplicate_window_without_duplicate_window result = {} @gateway.class.duplicate_window = nil @gateway.send(:add_duplicate_window, result) assert_nil result[:duplicate_window] end - + def test_add_duplicate_window_with_duplicate_window result = {} @gateway.class.duplicate_window = 0 @gateway.send(:add_duplicate_window, result) assert_equal 0, result[:duplicate_window] end - + def test_purchase_is_valid_csv - params = { :amount => '1.01' } - - @gateway.send(:add_creditcard, params, @credit_card) + params = { :amount => '1.01' } - assert data = @gateway.send(:post_data, 'AUTH_ONLY', params) - assert_equal post_data_fixture.size, data.size - end + @gateway.send(:add_creditcard, params, @credit_card) + + assert data = @gateway.send(:post_data, 'AUTH_ONLY', params) + assert_equal post_data_fixture.size, data.size + end def test_purchase_meets_minimum_requirements - params = { - :amount => "1.01", - } + params = { + :amount => '1.01', + } @gateway.send(:add_creditcard, params, @credit_card) @@ -111,17 +110,17 @@ def test_purchase_meets_minimum_requirements assert_not_nil(data =~ /x_#{key}=/) end end - + def test_successful_refund @gateway.expects(:ssl_post).returns(successful_purchase_response) assert response = @gateway.refund(@amount, '123456789', :card_number => @credit_card.number) assert_success response assert_equal 'This transaction has been approved', response.message end - + def test_refund_passing_extra_info response = stub_comms do - @gateway.refund(50, '123456789', :card_number => @credit_card.number, :first_name => "Bob", :last_name => "Smith", :zip => "12345") + @gateway.refund(50, '123456789', :card_number => @credit_card.number, :first_name => 'Bob', :last_name => 'Smith', :zip => '12345') end.check_request do |endpoint, data, headers| assert_match(/x_first_name=Bob/, data) assert_match(/x_last_name=Smith/, data) @@ -129,10 +128,10 @@ def test_refund_passing_extra_info end.respond_with(successful_purchase_response) assert_success response end - + def test_failed_refund @gateway.expects(:ssl_post).returns(failed_refund_response) - + assert response = @gateway.refund(@amount, '123456789', :card_number => @credit_card.number) assert_failure response assert_equal 'The referenced transaction does not meet the criteria for issuing a credit', response.message @@ -140,46 +139,46 @@ def test_failed_refund def test_deprecated_credit @gateway.expects(:ssl_post).returns(successful_purchase_response) - assert_deprecation_warning(Gateway::CREDIT_DEPRECATION_MESSAGE, @gateway) do + assert_deprecation_warning(Gateway::CREDIT_DEPRECATION_MESSAGE) do assert response = @gateway.credit(@amount, '123456789', :card_number => @credit_card.number) assert_success response assert_equal 'This transaction has been approved', response.message end end - + def test_supported_countries assert_equal ['US'], MetricsGlobalGateway.supported_countries end - + def test_supported_card_types assert_equal [:visa, :master, :american_express, :discover, :diners_club, :jcb], MetricsGlobalGateway.supported_cardtypes end - + def test_failure_without_response_reason_text assert_nothing_raised do assert_equal '', @gateway.send(:message_from, {}) end end - + def test_response_under_review_by_fraud_service @gateway.expects(:ssl_post).returns(fraud_review_response) - + response = @gateway.purchase(@amount, @credit_card) assert_failure response assert response.fraud_review? - assert_equal "Thank you! For security reasons your order is currently being reviewed", response.message + assert_equal 'Thank you! For security reasons your order is currently being reviewed', response.message end - + def test_avs_result @gateway.expects(:ssl_post).returns(fraud_review_response) - + response = @gateway.purchase(@amount, @credit_card) assert_equal 'X', response.avs_result['code'] end - + def test_cvv_result @gateway.expects(:ssl_post).returns(fraud_review_response) - + response = @gateway.purchase(@amount, @credit_card) assert_equal 'M', response.cvv_result['code'] end @@ -195,42 +194,42 @@ def test_message_from :response_reason_code => '27', :response_reason_text => 'Failure.', } - assert_equal "No Match", @gateway.message_from(result) + assert_equal 'CVV does not match', @gateway.message_from(result) result[:card_code] = 'M' - assert_equal "Street address matches, but 5-digit and 9-digit postal code do not match.", @gateway.message_from(result) + assert_equal 'Street address matches, but postal code does not match.', @gateway.message_from(result) result[:response_reason_code] = '22' - assert_equal "Failure", @gateway.message_from(result) + assert_equal 'Failure', @gateway.message_from(result) end - + private def post_data_fixture 'x_encap_char=%24&x_card_num=4242424242424242&x_exp_date=0806&x_card_code=123&x_type=AUTH_ONLY&x_first_name=Longbob&x_version=3.1&x_login=X&x_last_name=Longsen&x_tran_key=Y&x_relay_response=FALSE&x_delim_data=TRUE&x_delim_char=%2C&x_amount=1.01' end - + def minimum_requirements %w(version delim_data relay_response login tran_key amount card_num exp_date type) end - + def failed_refund_response '$3$,$2$,$54$,$The referenced transaction does not meet the criteria for issuing a credit.$,$$,$P$,$0$,$$,$$,$1.00$,$CC$,$credit$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$39265D8BA0CDD4F045B5F4129B2AAA01$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$' end - + def successful_authorization_response '$1$,$1$,$1$,$This transaction has been approved.$,$advE7f$,$Y$,$508141794$,$5b3fe66005f3da0ebe51$,$$,$1.00$,$CC$,$auth_only$,$$,$Longbob$,$Longsen$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$2860A297E0FE804BCB9EF8738599645C$,$P$,$2$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$' end - + def successful_purchase_response '$1$,$1$,$1$,$This transaction has been approved.$,$d1GENk$,$Y$,$508141795$,$32968c18334f16525227$,$Store purchase$,$1.00$,$CC$,$auth_capture$,$$,$Longbob$,$Longsen$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$269862C030129C1173727CC10B1935ED$,$P$,$2$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$' end - + def failed_authorization_response '$2$,$1$,$1$,$This transaction was declined.$,$advE7f$,$Y$,$508141794$,$5b3fe66005f3da0ebe51$,$$,$1.00$,$CC$,$auth_only$,$$,$Longbob$,$Longsen$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$2860A297E0FE804BCB9EF8738599645C$,$P$,$2$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$' end - + def fraud_review_response - "$4$,$$,$253$,$Thank you! For security reasons your order is currently being reviewed.$,$$,$X$,$0$,$$,$$,$1.00$,$$,$auth_capture$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$207BCBBF78E85CF174C87AE286B472D2$,$M$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$" + '$4$,$$,$253$,$Thank you! For security reasons your order is currently being reviewed.$,$$,$X$,$0$,$$,$$,$1.00$,$$,$auth_capture$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$207BCBBF78E85CF174C87AE286B472D2$,$M$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$' end end diff --git a/test/unit/gateways/micropayment_test.rb b/test/unit/gateways/micropayment_test.rb new file mode 100644 index 00000000000..5e63d8f0fa3 --- /dev/null +++ b/test/unit/gateways/micropayment_test.rb @@ -0,0 +1,225 @@ +require 'test_helper' + +class MicropaymentTest < Test::Unit::TestCase + include CommStub + + def setup + @gateway = MicropaymentGateway.new( + access_key: 'key' + ) + + @credit_card = credit_card + @amount = 100 + end + + def test_successful_purchase + response = stub_comms do + @gateway.purchase(@amount, @credit_card) + end.check_request do |endpoint, data, headers| + assert_match(/accessKey=key/, data) + assert_match(/number=#{@credit_card.number}/, data) + assert_match(/cvc2=#{@credit_card.verification_value}/, data) + assert_match(/amount=#{@amount}/, data) + end.respond_with(successful_purchase_response) + + assert_success response + assert_equal 'CCadc2b593ca98bfd730c383582de00faed995b0|www.spreedly.com-IDhm7nyju168', response.authorization + assert response.test? + end + + def test_failed_purchase + response = stub_comms do + @gateway.purchase(@amount, @credit_card) + end.respond_with(failed_purchase_response) + + assert_failure response + assert_equal 'AS stellt falsches Routing fest', response.message + assert response.test? + end + + def test_successful_authorize_and_capture + response = stub_comms do + @gateway.authorize(@amount, @credit_card) + end.check_request do |endpoint, data, headers| + assert_match(/accessKey=key/, data) + assert_match(/number=#{@credit_card.number}/, data) + assert_match(/cvc2=#{@credit_card.verification_value}/, data) + assert_match(/amount=#{@amount}/, data) + end.respond_with(successful_authorize_response) + + assert_success response + assert_equal 'CC747358d9598614c3ba1e9a7b82a28318cd81bc|www.spreedly.com-IDhngtaj81a1', response.authorization + + capture = stub_comms do + @gateway.capture(@amount, response.authorization) + end.check_request do |endpoint, data, headers| + assert_match(/accessKey=key/, data) + assert_match(/transactionId=www.spreedly.com-IDhngtaj81a1/, data) + assert_match(/sessionId=CC747358d9598614c3ba1e9a7b82a28318cd81bc/, data) + assert_match(/amount=#{@amount}/, data) + end.respond_with(successful_capture_response) + + assert_success capture + end + + def test_failed_authorize + response = stub_comms do + @gateway.authorize(@amount, @credit_card) + end.respond_with(failed_authorize_response) + + assert_failure response + assert_equal 'AS stellt falsches Routing fest', response.message + assert response.test? + end + + def test_failed_capture + response = stub_comms do + @gateway.capture(100, '') + end.respond_with(failed_capture_response) + + assert_failure response + end + + def test_successful_void + response = stub_comms do + @gateway.authorize(@amount, @credit_card) + end.respond_with(successful_authorize_response) + + assert_success response + + void = stub_comms do + @gateway.void(response.authorization) + end.check_request do |endpoint, data, headers| + assert_match(/accessKey=key/, data) + assert_match(/transactionId=www.spreedly.com-IDhngtaj81a1/, data) + assert_match(/sessionId=CC747358d9598614c3ba1e9a7b82a28318cd81bc/, data) + end.respond_with(successful_void_response) + + assert_success void + end + + def test_failed_void + response = stub_comms do + @gateway.void('') + end.respond_with(failed_void_response) + + assert_failure response + end + + def test_successful_refund + response = stub_comms do + @gateway.purchase(@amount, @credit_card) + end.respond_with(successful_purchase_response) + + assert_success response + + refund = stub_comms do + @gateway.refund(@amount, response.authorization) + end.check_request do |endpoint, data, headers| + assert_match(/accessKey=key/, data) + assert_match(/transactionId=www.spreedly.com-IDhm7nyju168/, data) + assert_match(/sessionId=CCadc2b593ca98bfd730c383582de00faed995b0/, data) + assert_match(/amount=#{@amount}/, data) + end.respond_with(successful_refund_response) + + assert_success refund + end + + def test_failed_refund + response = stub_comms do + @gateway.refund(nil, '') + end.respond_with(failed_refund_response) + + assert_failure response + end + + def test_successful_verify + response = stub_comms do + @gateway.verify(@credit_card) + end.respond_with(successful_authorize_response, failed_void_response) + assert_success response + assert_equal 'Succeeded', response.message + end + + def test_failed_verify + response = stub_comms do + @gateway.verify(@credit_card) + end.respond_with(failed_authorize_response, successful_void_response) + assert_failure response + assert_equal 'AS stellt falsches Routing fest', response.message + end + + def test_transcript_scrubbing + assert_equal scrubbed_transcript, @gateway.scrub(transcript) + end + + private + + def invalid_login_response + %(error=3000\nerrorMessage=Authorization+failed+-+Reason%3A+accesskey+wrong) + end + + def successful_purchase_response + %(error=0\ncustomerId=e003ddc41a0d6786b70e6a74dcf5febf4d1d8419\nsessionId=CCadc2b593ca98bfd730c383582de00faed995b0\nsessionStatus=SUCCESS\ntransactionStatus=SUCCESS\ntransactionId=www.spreedly.com-IDhm7nyju168\ntransactionCreated=2015-08-13+21%3A56%3A34\ntransactionAuth=1c0b99395e34bf545f610c0a3ca4d987\nccCountry=US\nipCountry=AU\ntransactionResultCode=00\ntransactionResultMessage=Funktion+fehlerfrei+durchgef%C3%BChrt\ndataStorageId=e2d932a845fd6c52ee317e4615cd5550) + end + + def failed_purchase_response + %(error=0\ncustomerId=e1ad848e79c9efd71626bdf186afa09d6cb93e75\nsessionId=CCf25d76fd2b46975c5ce3600690aafab32f3596\nsessionStatus=FAILED\ntransactionStatus=FAILED\ntransactionId=www.spreedly.com-ID7878kjxq8f\ntransactionCreated=2015-08-13+22%3A03%3A43\ntransactionAuth=4e68e239c74c67bd0242f2ba04783a34\nccCountry=US\nipCountry=AU\ntransactionResultCode=ipg92\ntransactionResultMessage=AS+stellt+falsches+Routing+fest\ndataStorageId=e2d932a845fd6c52ee317e4615cd5550) + end + + def successful_authorize_response + %(error=0\ncustomerId=4b527481457abafc15f3c96f5c5b6109f708d9c6\nsessionId=CC747358d9598614c3ba1e9a7b82a28318cd81bc\nsessionStatus=SUCCESS\ntransactionStatus=SUCCESS\ntransactionId=www.spreedly.com-IDhngtaj81a1\ntransactionCreated=2015-08-13+22%3A23%3A39\ntransactionAuth=df5e0d54694e12b48b95ad26e676b70a\nccCountry=US\nipCountry=AU\ntransactionResultCode=00\ntransactionResultMessage=Funktion+fehlerfrei+durchgef%C3%BChrt\ndataStorageId=e2d932a845fd6c52ee317e4615cd5550) + end + + def failed_authorize_response + %(error=0\ncustomerId=98d94168a1ae360e18c78d29a40e44d67a76e9ab\nsessionId=CC4a4396dd378b7470704e2d9d5fb403df1c57c0\nsessionStatus=FAILED\ntransactionStatus=FAILED\ntransactionId=www.spreedly.com-ID7ngsk9bkcq\ntransactionCreated=2015-08-13+22%3A37%3A28\ntransactionAuth=62b6bcd74130aff939d04e9638ae3a9e\nccCountry=US\nipCountry=AU\ntransactionResultCode=ipg92\ntransactionResultMessage=AS+stellt+falsches+Routing+fest\ndataStorageId=e2d932a845fd6c52ee317e4615cd5550) + end + + def successful_capture_response + %(error=0\nsessionStatus=SUCCESS\ntransactionStatus=SUCCESS\ntransactionId=www.spreedly.com-IDhngtaj81a1%231\ntransactionCreated=2015-08-13+22%3A23%3A41\ntransactionAuth=df5e0d54694e12b48b95ad26e676b70a\nccCountry=US\nipCountry=AU\ntransactionResultCode=00\ntransactionResultMessage=Funktion+fehlerfrei+durchgef%C3%BChrt\ndataStorageId=e2d932a845fd6c52ee317e4615cd5550) + end + + def failed_capture_response + %(error=3110\nerrorMessage=%22sessionId%22+with+the+value+%221%22+does+not+exist) + end + + def successful_void_response + %(error=0\nsessionStatus=SUCCESS\ntransactionStatus=SUCCESS\ntransactionId=www.spreedly.com-IDg8za20ugv4%231\ntransactionCreated=2015-08-17+20%3A32%3A36\ntransactionAuth=698f2fb02df442828316c7f50dba7e10\ntransactionResultCode=00\ntransactionResultMessage=Funktion+fehlerfrei+durchgef%C3%BChrt\ndataStorageId=e2d932a845fd6c52ee317e4615cd5550) + end + + def failed_void_response + %(error=3101\nerrorMessage=%22sessionId%22+is+empty) + end + + def successful_refund_response + %(error=0\nsessionStatus=SUCCESS\ntransactionStatus=SUCCESS\ntransactionId=www.spreedly.com-ID7nf7n0yf86%231\ntransactionCreated=2015-08-17+20%3A36%3A45\ntransactionAuth=2dd36e70e5d74c7318ed1e27cd1f1efa\nccCountry=US\nipCountry=AU\ntransactionResultCode=00\ntransactionResultMessage=Funktion+fehlerfrei+durchgef%C3%BChrt\ndataStorageId=e2d932a845fd6c52ee317e4615cd5550) + end + + def failed_refund_response + %(error=3101\nerrorMessage=%22sessionId%22+is+empty) + end + + def successful_credit_response + %() + end + + def failed_credit_response + %() + end + + def successful_store_response + %() + end + + def failed_store_response + %() + end + + def transcript + %(amount=250&currency=EUR&project=sprdly&firstname=Longbob&surname=Longsen&number=4111111111111111&cvc2=666&expiryYear=2016&expiryMonth=09&ip=1.1.1.1&sendMail=false&testMode=1&accessKey=0b4832ca37a31e748c4490b58d743986) + end + + def scrubbed_transcript + %(amount=250&currency=EUR&project=sprdly&firstname=Longbob&surname=Longsen&number=[FILTERED]&cvc2=[FILTERED]&expiryYear=2016&expiryMonth=09&ip=1.1.1.1&sendMail=false&testMode=1&accessKey=[FILTERED]) + end +end diff --git a/test/unit/gateways/migs_test.rb b/test/unit/gateways/migs_test.rb index 644e1bd214a..73ef494ff7b 100644 --- a/test/unit/gateways/migs_test.rb +++ b/test/unit/gateways/migs_test.rb @@ -3,92 +3,175 @@ class MigsTest < Test::Unit::TestCase def setup @gateway = MigsGateway.new( - :login => 'login', - :password => 'password', - :secure_hash => '76AF3392002D202A60D0AB5F9D81653C' + login: 'login', + password: 'password', + secure_hash: '76AF3392002D202A60D0AB5F9D81653C', + advanced_login: 'advlogin', + advanced_password: 'advpass' ) - @credit_card = credit_card @amount = 100 - - @options = { - :order_id => '1', - :billing_address => address, - :description => 'Store Purchase' + @authorization = '2070000742' + @tx_source = 'MOTO' + + @options = { + order_id: '1', + billing_address: address, + description: 'Store Purchase' } end - + def test_successful_purchase @gateway.expects(:ssl_post).returns(successful_purchase_response) - + assert response = @gateway.purchase(@amount, @credit_card, @options) assert_instance_of Response, response assert_success response - + # Replace with authorization number from the successful response assert_equal '123456', response.authorization end def test_unsuccessful_request @gateway.expects(:ssl_post).returns(failed_purchase_response) - + assert response = @gateway.purchase(@amount, @credit_card, @options) assert_instance_of Response, response assert_failure response - + assert_equal '654321', response.authorization end def test_secure_hash params = { - :MerchantId => 'MER123', - :OrderInfo => 'A48cvE28', - :Amount => 2995 + MerchantId: 'MER123', + OrderInfo: 'A48cvE28', + Amount: 2995 } - ordered_values = "#{@gateway.options[:secure_hash]}2995MER123A48cvE28" - + ordered_values = 'vpc_Amount=2995&vpc_MerchantId=MER123&vpc_OrderInfo=A48cvE28' @gateway.send(:add_secure_hash, params) - assert_equal Digest::MD5.hexdigest(ordered_values).upcase, params[:SecureHash] + assert_equal OpenSSL::HMAC.hexdigest('SHA256', [@gateway.options[:secure_hash]].pack('H*'), ordered_values).upcase, params[:SecureHash] end def test_purchase_offsite_response # Below response from instance running remote test - response_params = "vpc_3DSXID=a1B8UcW%2BKYqkSinLQohGmqQd9uY%3D&vpc_3DSenrolled=U&vpc_AVSResultCode=Unsupported&vpc_AcqAVSRespCode=Unsupported&vpc_AcqCSCRespCode=Unsupported&vpc_AcqResponseCode=00&vpc_Amount=100&vpc_AuthorizeId=367739&vpc_BatchNo=20120421&vpc_CSCResultCode=Unsupported&vpc_Card=MC&vpc_Command=pay&vpc_Locale=en&vpc_MerchTxnRef=9&vpc_Merchant=TESTANZTEST3&vpc_Message=Approved&vpc_OrderInfo=1&vpc_ReceiptNo=120421367739&vpc_SecureHash=8794D9478D030B65F3092282E76283F8&vpc_TransactionNo=2000025183&vpc_TxnResponseCode=0&vpc_VerSecurityLevel=06&vpc_VerStatus=U&vpc_VerType=3DS&vpc_Version=1" + response_params = 'vpc_3DSXID=a1B8UcW%2BKYqkSinLQohGmqQd9uY%3D&vpc_3DSenrolled=U&vpc_AVSResultCode=Unsupported&vpc_AcqAVSRespCode=Unsupported&vpc_AcqCSCRespCode=Unsupported&vpc_AcqResponseCode=00&vpc_Amount=100&vpc_AuthorizeId=367739&vpc_BatchNo=20120421&vpc_CSCResultCode=Unsupported&vpc_Card=MC&vpc_Command=pay&vpc_Locale=en&vpc_MerchTxnRef=9&vpc_Merchant=TESTANZTEST3&vpc_Message=Approved&vpc_OrderInfo=1&vpc_ReceiptNo=120421367739&vpc_SecureHash=20DE2CDEBE40D6F24E3ABC5D74081CB5B341CD447530121AD51A9504A923BBD0&vpc_TransactionNo=2000025183&vpc_TxnResponseCode=0&vpc_VerSecurityLevel=06&vpc_VerStatus=U&vpc_VerType=3DS&vpc_Version=1' response_hash = @gateway.send(:parse, response_params) - response_hash.delete(:SecureHash) calculated_hash = @gateway.send(:calculate_secure_hash, response_hash, @gateway.options[:secure_hash]) - assert_equal '8794D9478D030B65F3092282E76283F8', calculated_hash + expected_hash_input = 'vpc_3DSXID=a1B8UcW+KYqkSinLQohGmqQd9uY=&vpc_3DSenrolled=U&vpc_AVSResultCode=Unsupported&vpc_AcqAVSRespCode=Unsupported&vpc_AcqCSCRespCode=Unsupported&vpc_AcqResponseCode=00&vpc_Amount=100&vpc_AuthorizeId=367739&vpc_BatchNo=20120421&vpc_CSCResultCode=Unsupported&vpc_Card=MC&vpc_Command=pay&vpc_Locale=en&vpc_MerchTxnRef=9&vpc_Merchant=TESTANZTEST3&vpc_Message=Approved&vpc_OrderInfo=1&vpc_ReceiptNo=120421367739&vpc_TransactionNo=2000025183&vpc_TxnResponseCode=0&vpc_VerSecurityLevel=06&vpc_VerStatus=U&vpc_VerType=3DS&vpc_Version=1' + assert_equal OpenSSL::HMAC.hexdigest('SHA256', [@gateway.options[:secure_hash]].pack('H*'), expected_hash_input).upcase, calculated_hash response = @gateway.purchase_offsite_response(response_params) assert_success response - tampered_response1 = response_params.gsub('83F8', '93F8') - assert_raise(SecurityError){@gateway.purchase_offsite_response(tampered_response1)} + tampered_response1 = response_params.gsub('20DE', '20DF') + assert_raise(SecurityError) { @gateway.purchase_offsite_response(tampered_response1) } tampered_response2 = response_params.gsub('Locale=en', 'Locale=es') - assert_raise(SecurityError){@gateway.purchase_offsite_response(tampered_response2)} + assert_raise(SecurityError) { @gateway.purchase_offsite_response(tampered_response2) } + end + + def test_scrub + assert @gateway.supports_scrubbing? + assert_equal @gateway.scrub(pre_scrubbed), post_scrubbed + end + + def test_purchase_passes_tx_source + expect_commit_with_tx_source + @gateway.purchase(@amount, @credit_card, @options.merge(tx_source: @tx_source)) + end + + def test_capture_passes_tx_source + expect_commit_with_tx_source + @gateway.capture(@amount, @authorization, @options.merge(tx_source: @tx_source)) + end + + def test_refund_passes_tx_source + expect_commit_with_tx_source + @gateway.refund(@amount, @authorization, @options.merge(tx_source: @tx_source)) + end + + def test_void_passes_tx_source + expect_commit_with_tx_source + @gateway.void(@authorization, @options.merge(tx_source: @tx_source)) end private - + # Place raw successful response from gateway here def successful_purchase_response build_response( - :TxnResponseCode => '0', - :TransactionNo => '123456' + TxnResponseCode: '0', + TransactionNo: '123456' ) end - + # Place raw failed response from gateway here def failed_purchase_response build_response( - :TxnResponseCode => '3', - :TransactionNo => '654321' + TxnResponseCode: '3', + TransactionNo: '654321' ) end - + def build_response(options) - options.collect { |key, value| "vpc_#{key}=#{CGI.escape(value.to_s)}"}.join('&') + options.collect { |key, value| "vpc_#{key}=#{CGI.escape(value.to_s)}" }.join('&') + end + + def expect_commit_with_tx_source + @gateway.expects(:commit).with(has_entries(TxSource: @tx_source)) + end + + def pre_scrubbed + <<-EOS +opening connection to migs.mastercard.com.au:443... +opened +starting SSL for migs.mastercard.com.au:443... +SSL established +<- "POST /vpcdps HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: migs.mastercard.com.au\r\nContent-Length: 354\r\n\r\n" +<- "vpc_Amount=100&vpc_Currency=SAR&vpc_OrderInfo=1&vpc_CardNum=4987654321098769&vpc_CardSecurityCode=123&vpc_CardExp=2105&vpc_Version=1&vpc_Merchant=TESTH-STATION&vpc_AccessCode=F1CE6F32&vpc_Command=pay&vpc_MerchTxnRef=84c1f31ded35dea26ac297fd7ba092da&vpc_SecureHash=CD1B2B8BC325C6C8FC1A041AD6AC90821984277113DF708B16B37809E7B0EC33&vpc_SecureHashType=SHA256&vpc_VerType=3DS&vpc_3DSXID=YzRjZWRjODY4MmY2NGQ3ZTgzNDQ&vpc_VerToken=AAACAgeVABgnAggAQ5UAAAAAAAA&vpc_3DSenrolled=Y&vpc_3DSECI=05&3DSstatus=Y" +-> "HTTP/1.1 200 OK\r\n" +-> "Date: Tue, 13 Feb 2018 19:02:18 GMT\r\n" +-> "Expires: Sun, 15 Jul 1990 00:00:00 GMT\r\n" +-> "Pragma: no-cache\r\n" +-> "Cache-Control: no-cache\r\n" +-> "Content-Length: 595\r\n" +-> "P3P: CP=\"NOI DSP COR CURa ADMa TA1a OUR BUS IND UNI COM NAV INT\"\r\n" +-> "Content-Type: text/plain;charset=iso-8859-1\r\n" +-> "Connection: close\r\n" +-> "Set-Cookie: TS01c4b9ca=01fb8d8de2ba6ffaf7439497dd78d9b3348c82bcf24d4619e65a406161e57276b6b293e77732a293be63bf750213e588797bc86f05; Path=/; Secure; HTTPOnly\r\n" +-> "\r\n" +reading 595 bytes... +-> "vpc_AVSResultCode=Unsupported&vpc_AcqAVSRespCode=Unsupported&vpc_AcqCSCRespCode=Unsupported&vpc_AcqResponseCode=00&vpc_Amount=100&vpc_AuthorizeId=239491&vpc_BatchNo=20180214&vpc_CSCResultCode=Unsupported&vpc_Card=VC&vpc_Command=pay&vpc_Currency=SAR&vpc_Locale=en_SA&vpc_MerchTxnRef=84c1f31ded35dea26ac297fd7ba092da&vpc_Merchant=TESTH-STATION&vpc_Message=Approved&vpc_OrderInfo=1&vpc_ReceiptNo=804506239491&vpc_RiskOverallResult=ACC&vpc_SecureHash=99993E000461810D9F71B1A4FC5EA2D68DF6BA1F7EBA6A9FC544DA035627C03C&vpc_SecureHashType=SHA256&vpc_TransactionNo=372&vpc_TxnResponseCode=0&vpc_Version=1&vpc_VerType=3DS&vpc_3DSXID=YzRjZWRjODY4MmY2NGQ3ZTgzNDQ&vpc_VerToken=AAACAgeVABgnAggAQ5UAAAAAAAA&vpc_3DSenrolled=Y&vpc_3DSECI=05&3DSstatus=Y" +read 595 bytes +Conn close + EOS + end + + def post_scrubbed + <<-EOS +opening connection to migs.mastercard.com.au:443... +opened +starting SSL for migs.mastercard.com.au:443... +SSL established +<- "POST /vpcdps HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: migs.mastercard.com.au\r\nContent-Length: 354\r\n\r\n" +<- "vpc_Amount=100&vpc_Currency=SAR&vpc_OrderInfo=1&vpc_CardNum=[FILTERED]&vpc_CardSecurityCode=[FILTERED]&vpc_CardExp=2105&vpc_Version=1&vpc_Merchant=TESTH-STATION&vpc_AccessCode=[FILTERED]&vpc_Command=pay&vpc_MerchTxnRef=84c1f31ded35dea26ac297fd7ba092da&vpc_SecureHash=CD1B2B8BC325C6C8FC1A041AD6AC90821984277113DF708B16B37809E7B0EC33&vpc_SecureHashType=SHA256&vpc_VerType=3DS&vpc_3DSXID=[FILTERED]&vpc_VerToken=[FILTERED]&vpc_3DSenrolled=Y&vpc_3DSECI=05&3DSstatus=Y" +-> "HTTP/1.1 200 OK\r\n" +-> "Date: Tue, 13 Feb 2018 19:02:18 GMT\r\n" +-> "Expires: Sun, 15 Jul 1990 00:00:00 GMT\r\n" +-> "Pragma: no-cache\r\n" +-> "Cache-Control: no-cache\r\n" +-> "Content-Length: 595\r\n" +-> "P3P: CP=\"NOI DSP COR CURa ADMa TA1a OUR BUS IND UNI COM NAV INT\"\r\n" +-> "Content-Type: text/plain;charset=iso-8859-1\r\n" +-> "Connection: close\r\n" +-> "Set-Cookie: TS01c4b9ca=01fb8d8de2ba6ffaf7439497dd78d9b3348c82bcf24d4619e65a406161e57276b6b293e77732a293be63bf750213e588797bc86f05; Path=/; Secure; HTTPOnly\r\n" +-> "\r\n" +reading 595 bytes... +-> "vpc_AVSResultCode=Unsupported&vpc_AcqAVSRespCode=Unsupported&vpc_AcqCSCRespCode=Unsupported&vpc_AcqResponseCode=00&vpc_Amount=100&vpc_AuthorizeId=239491&vpc_BatchNo=20180214&vpc_CSCResultCode=Unsupported&vpc_Card=VC&vpc_Command=pay&vpc_Currency=SAR&vpc_Locale=en_SA&vpc_MerchTxnRef=84c1f31ded35dea26ac297fd7ba092da&vpc_Merchant=TESTH-STATION&vpc_Message=Approved&vpc_OrderInfo=1&vpc_ReceiptNo=804506239491&vpc_RiskOverallResult=ACC&vpc_SecureHash=99993E000461810D9F71B1A4FC5EA2D68DF6BA1F7EBA6A9FC544DA035627C03C&vpc_SecureHashType=SHA256&vpc_TransactionNo=372&vpc_TxnResponseCode=0&vpc_Version=1&vpc_VerType=3DS&vpc_3DSXID=[FILTERED]&vpc_VerToken=[FILTERED]&vpc_3DSenrolled=Y&vpc_3DSECI=05&3DSstatus=Y" +read 595 bytes +Conn close + EOS end end diff --git a/test/unit/gateways/modern_payments_cim_test.rb b/test/unit/gateways/modern_payments_cim_test.rb index 25b316077c6..0e6aa87a998 100644 --- a/test/unit/gateways/modern_payments_cim_test.rb +++ b/test/unit/gateways/modern_payments_cim_test.rb @@ -3,7 +3,7 @@ class ModernPaymentsCimTest < Test::Unit::TestCase def setup Base.mode = :test - + @gateway = ModernPaymentsCimGateway.new( :login => 'login', :password => 'password' @@ -11,77 +11,77 @@ def setup @credit_card = credit_card @amount = 100 - - @options = { + + @options = { :order_id => '1', :billing_address => address, :description => 'Store Purchase' } end - + def test_create_customer @gateway.expects(:ssl_post).returns(successful_create_customer_response) - + assert response = @gateway.create_customer(@options) assert_instance_of Response, response assert response.test? assert_success response - assert_equal "6677348", response.params["create_customer_result"] + assert_equal '6677348', response.params['create_customer_result'] end - + def test_modify_customer_credit_card @gateway.expects(:ssl_post).returns(successful_modify_customer_credit_card_response) - - assert response = @gateway.modify_customer_credit_card("10001", @credit_card) + + assert response = @gateway.modify_customer_credit_card('10001', @credit_card) assert_instance_of Response, response assert response.test? assert_success response - assert_equal "6677757", response.params["modify_customer_credit_card_result"] + assert_equal '6677757', response.params['modify_customer_credit_card_result'] end - + def test_successful_credit_card_authorization @gateway.expects(:ssl_post).returns(successful_authorization_response) - - assert response = @gateway.authorize_credit_card_payment("10001", @amount) + + assert response = @gateway.authorize_credit_card_payment('10001', @amount) assert_instance_of Response, response assert response.test? - + assert_success response - assert_equal "18713505", response.params["trans_id"] - assert_equal "RESPONSECODE=A\nAUTHCODE=020411\nDECLINEREASON=\nAVSDATA=Z\nTRANSID=C00 17093294", response.params["auth_string"] - assert_equal "Approved", response.params["message_text"] - assert_equal "true", response.params["approved"] - assert_equal "Z", response.params["avs_code"] - assert_equal "020411", response.params["auth_code"] - assert_equal "C00 17093294", response.params["trans_code"] - assert_equal "18713505", response.authorization + assert_equal '18713505', response.params['trans_id'] + assert_equal "RESPONSECODE=A\nAUTHCODE=020411\nDECLINEREASON=\nAVSDATA=Z\nTRANSID=C00 17093294", response.params['auth_string'] + assert_equal 'Approved', response.params['message_text'] + assert_equal 'true', response.params['approved'] + assert_equal 'Z', response.params['avs_code'] + assert_equal '020411', response.params['auth_code'] + assert_equal 'C00 17093294', response.params['trans_code'] + assert_equal '18713505', response.authorization assert_equal ModernPaymentsCimGateway::SUCCESS_MESSAGE, response.message assert_equal 'Z', response.avs_result['code'] end - + def test_unsuccessful_credit_card_authorization @gateway.expects(:ssl_post).returns(unsuccessful_credit_card_authorization_response) - - assert response = @gateway.authorize_credit_card_payment("10001", @amount) + + assert response = @gateway.authorize_credit_card_payment('10001', @amount) assert_instance_of Response, response assert response.test? assert_failure response - assert_equal "999", response.authorization - assert_match /RESPONSECODE=D/, response.params["message_text"] + assert_equal '999', response.authorization + assert_match %r{RESPONSECODE=D}, response.params['message_text'] end - + def test_soap_fault_response @gateway.expects(:ssl_post).returns(soap_fault_response) - + assert response = @gateway.create_customer(@options) assert_instance_of Response, response assert response.test? assert_failure response - assert_equal "soap:Client", response.params["faultcode"] + assert_equal 'soap:Client', response.params['faultcode'] end private - + def successful_create_customer_response <<-XML <?xml version="1.0" encoding="utf-8"?> @@ -94,7 +94,7 @@ def successful_create_customer_response </soap:Envelope> XML end - + def successful_modify_customer_credit_card_response <<-XML <?xml version="1.0" encoding="utf-8"?> @@ -107,7 +107,7 @@ def successful_modify_customer_credit_card_response </soap:Envelope> XML end - + def unsuccessful_credit_card_authorization_response <<-XML <?xml version="1.0" encoding="utf-8"?> @@ -125,10 +125,10 @@ def unsuccessful_credit_card_authorization_response </AuthorizeCreditCardPaymentResult> </AuthorizeCreditCardPaymentResponse> </soap:Body> -</soap:Envelope> +</soap:Envelope> XML end - + def soap_fault_response <<-XML <?xml version="1.0" encoding="utf-8"?> @@ -147,7 +147,7 @@ def soap_fault_response </soap:Envelope> XML end - + def successful_authorization_response <<-XML <?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><soap:Body><AuthorizeCreditCardPaymentResponse xmlns="https://secure.modpay.com/ws/"><AuthorizeCreditCardPaymentResult><transId>18713505</transId><authCode>020411</authCode><avsCode>Z</avsCode><transCode>C00 17093294 @@ -156,7 +156,7 @@ def successful_authorization_response DECLINEREASON= AVSDATA=Z TRANSID=C00 17093294 -</authString><messageText>Approved</messageText><approved>true</approved></AuthorizeCreditCardPaymentResult></AuthorizeCreditCardPaymentResponse></soap:Body></soap:Envelope> +</authString><messageText>Approved</messageText><approved>true</approved></AuthorizeCreditCardPaymentResult></AuthorizeCreditCardPaymentResponse></soap:Body></soap:Envelope> XML end end diff --git a/test/unit/gateways/monei_test.rb b/test/unit/gateways/monei_test.rb new file mode 100755 index 00000000000..79d9a2c2365 --- /dev/null +++ b/test/unit/gateways/monei_test.rb @@ -0,0 +1,436 @@ +require 'test_helper' + +class MoneiTest < Test::Unit::TestCase + include CommStub + + def setup + @gateway = MoneiGateway.new( + fixtures(:monei) + ) + + @credit_card = credit_card + @amount = 100 + + @options = { + order_id: '1', + billing_address: address, + description: 'Store Purchase' + } + end + + def test_successful_purchase + @gateway.expects(:ssl_post).returns(successful_purchase_response) + + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + + assert_equal '8a829449488d79090148996c441551fb', response.authorization + assert response.test? + end + + def test_failed_purchase + @gateway.expects(:ssl_post).returns(failed_purchase_response) + + response = @gateway.purchase(@amount, @credit_card, @options) + assert_failure response + end + + def test_successful_authorize + @gateway.expects(:ssl_post).returns(successful_authorize_response) + + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + end + + def test_failed_authorize + @gateway.expects(:ssl_post).returns(failed_authorize_response) + + response = @gateway.authorize(@amount, @credit_card, @options) + assert_failure response + end + + def test_successful_capture + @gateway.expects(:ssl_post).returns(successful_authorize_response) + + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + @gateway.expects(:ssl_post).returns(successful_capture_response) + + assert capture = @gateway.capture(@amount, auth.authorization) + assert_success capture + end + + def test_failed_capture + @gateway.expects(:ssl_post).returns(failed_capture_response) + + response = @gateway.capture(nil, '') + assert_failure response + end + + def test_successful_refund + @gateway.expects(:ssl_post).returns(successful_purchase_response) + + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + + @gateway.expects(:ssl_post).returns(successful_refund_response) + + assert refund = @gateway.refund(@amount, purchase.authorization) + assert_success refund + end + + def test_failed_refund + @gateway.expects(:ssl_post).returns(failed_refund_response) + + response = @gateway.refund(nil, '') + assert_failure response + end + + def test_successful_void + @gateway.expects(:ssl_post).returns(successful_authorize_response) + + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + @gateway.expects(:ssl_post).returns(successful_void_response) + + assert void = @gateway.void(auth.authorization) + assert_success void + end + + def test_failed_void + @gateway.expects(:ssl_post).returns(failed_void_response) + + response = @gateway.void('') + assert_failure response + end + + def test_successful_verify + response = stub_comms do + @gateway.verify(@credit_card, @options) + end.respond_with(successful_authorize_response, successful_void_response) + assert_success response + end + + def test_successful_verify_with_failed_void + response = stub_comms do + @gateway.verify(@credit_card, @options) + end.respond_with(successful_authorize_response, failed_void_response) + assert_success response + end + + def test_failed_verify + response = stub_comms do + @gateway.verify(@credit_card, @options) + end.respond_with(failed_authorize_response, successful_void_response) + assert_failure response + end + + def test_3ds_request + three_d_secure_options = { + eci: '05', + cavv: 'AAACAgSRBklmQCFgMpEGAAAAAAA=', + xid: 'CAACCVVUlwCXUyhQNlSXAAAAAAA=' + } + options = @options.merge!({ + three_d_secure: three_d_secure_options + }) + stub_comms do + @gateway.purchase(@amount, @credit_card, options) + end.check_request do |endpoint, data, headers| + body = CGI.unescape data + assert_match %r{<Authentication type="3DSecure">}, body + assert_match %r{<ResultIndicator>05</ResultIndicator>}, body + assert_match %r{<Parameter name="VERIFICATION_ID">#{three_d_secure_options[:cavv]}</Parameter>}, body + assert_match %r{<Parameter name="XID">#{three_d_secure_options[:xid]}</Parameter>}, body + end.respond_with(successful_purchase_response) + end + + private + + def successful_purchase_response + return <<-XML +<?xml version="1.0" encoding="UTF-8"?> +<Response version="1.0"> + <Transaction mode="CONNECTOR_TEST" channel="8a82941746287806014698a0e3240575" response="SYNC"> + <Identification> + <ShortID>7621.0198.1858</ShortID> + <UniqueID>8a829449488d79090148996c441551fb</UniqueID> + <TransactionID>1</TransactionID> + </Identification> + <Payment code="CC.DB"> + <Clearing> + <Amount>1.00</Amount> + <Currency>EUR</Currency> + <Descriptor>7621.0198.1858 DEFAULT Store Purchase</Descriptor> + <FxRate>1.0</FxRate> + <FxSource>INTERN</FxSource> + <FxDate>2014-09-21 18:14:42</FxDate> + </Clearing> + </Payment> + <Processing code="CC.DB.90.00"> + <Timestamp>2014-09-21 18:14:42</Timestamp> + <Result>ACK</Result> + <Status code="90">NEW</Status> + <Reason code="00">Successful Processing</Reason> + <Return code="000.100.112">Request successfully processed in 'Merchant in Connector Test Mode'</Return> + <Risk score="0" /> + </Processing> + </Transaction> +</Response> + XML + end + + def failed_purchase_response + <<-XML +<?xml version="1.0" encoding="UTF-8"?> +<Response version="1.0"> + <Transaction mode="CONNECTOR_TEST" channel="8a82943746287806014628a0e3240575" response="SYNC"> + <Identification> + <ShortID>9086.6774.0834</ShortID> + <UniqueID>8a82944a488d36c101489972b0ee6ace</UniqueID> + <TransactionID>1</TransactionID> + </Identification> + <Payment code="CC.DB" /> + <Processing code="CC.DB.70.40"> + <Timestamp>2014-09-21 18:21:43</Timestamp> + <Result>NOK</Result> + <Status code="70">REJECTED_VALIDATION</Status> + <Reason code="40">Account Validation</Reason> + <Return code="100.100.700">invalid cc number/brand combination</Return> + </Processing> + </Transaction> +</Response> + XML + end + + def successful_authorize_response + <<-XML +<?xml version="1.0" encoding="UTF-8"?> +<Response version="1.0"> + <Transaction mode="CONNECTOR_TEST" channel="8a82941746487806014628a0e3240575" response="SYNC"> + <Identification> + <ShortID>6853.2944.1442</ShortID> + <UniqueID>8a82944a488d36c101489976f0cc6b1c</UniqueID> + <TransactionID>1</TransactionID> + </Identification> + <Payment code="CC.PA"> + <Clearing> + <Amount>1.00</Amount> + <Currency>EUR</Currency> + <Descriptor>6853.2944.1442 DEFAULT Store Purchase</Descriptor> + <FxRate>1.0</FxRate> + <FxSource>INTERN</FxSource> + <FxDate>2014-09-21 18:26:22</FxDate> + </Clearing> + </Payment> + <Processing code="CC.PA.90.00"> + <Timestamp>2014-09-21 18:26:22</Timestamp> + <Result>ACK</Result> + <Status code="90">NEW</Status> + <Reason code="00">Successful Processing</Reason> + <Return code="000.100.112">Request successfully processed in 'Merchant in Connector Test Mode'</Return> + <Risk score="0" /> + </Processing> + </Transaction> +</Response> + XML + end + + def failed_authorize_response + <<-XML +<?xml version="1.0" encoding="UTF-8"?> +<Response version="1.0"> + <Transaction mode="CONNECTOR_TEST" channel="8a82941746287806014628a0e3240575" response="SYNC"> + <Identification> + <ShortID>4727.2856.0290</ShortID> + <UniqueID>8a829449488d79090148998943a853f6</UniqueID> + <TransactionID>1</TransactionID> + </Identification> + <Payment code="CC.PA" /> + <Processing code="CC.PA.70.40"> + <Timestamp>2014-09-21 18:46:22</Timestamp> + <Result>NOK</Result> + <Status code="70">REJECTED_VALIDATION</Status> + <Reason code="40">Account Validation</Reason> + <Return code="100.100.700">invalid cc number/brand combination</Return> + </Processing> + </Transaction> +</Response> + XML + end + + def successful_capture_response + <<-XML +<?xml version="1.0" encoding="UTF-8"?> +<Response version="1.0"> + <Transaction mode="CONNECTOR_TEST" channel="8a82941746287806014628a0e3240575" response="SYNC"> + <Identification> + <ShortID>1269.8369.2962</ShortID> + <UniqueID>8a82944a488d36c10148998d9b316cc6</UniqueID> + <TransactionID /> + <ReferenceID>8a829449488d79090148998d97f05439</ReferenceID> + </Identification> + <Payment code="CC.CP"> + <Clearing> + <Amount>1.00</Amount> + <Currency>EUR</Currency> + <Descriptor>1269.8369.2962 DEFAULT Store Purchase</Descriptor> + <FxRate>1.0</FxRate> + <FxSource>INTERN</FxSource> + <FxDate>2014-09-21 18:51:07</FxDate> + </Clearing> + </Payment> + <Processing code="CC.CP.90.00"> + <Timestamp>2014-09-21 18:51:07</Timestamp> + <Result>ACK</Result> + <Status code="90">NEW</Status> + <Reason code="00">Successful Processing</Reason> + <Return code="000.100.112">Request successfully processed in 'Merchant in Connector Test Mode'</Return> + <Risk score="0" /> + </Processing> + </Transaction> +</Response> + XML + end + + def failed_capture_response + <<-XML +<?xml version="1.0" encoding="UTF-8"?> +<Response version="1.0"> + <Transaction mode="CONNECTOR_TEST" channel="8a82941746287806014628a0e3240575" response="SYNC"> + <Identification> + <ShortID>0239.0447.7858</ShortID> + <UniqueID>8a82944a488d36c10148998fc4b66cfc</UniqueID> + <TransactionID /> + <ReferenceID /> + </Identification> + <Payment code="CC.CP" /> + <Processing code="CC.CP.70.20"> + <Timestamp>2014-09-21 18:53:29</Timestamp> + <Result>NOK</Result> + <Status code="70">REJECTED_VALIDATION</Status> + <Reason code="20">Format Error</Reason> + <Return code="200.100.302">invalid Request/Transaction/Payment/Presentation tag (not present or [partially] empty)</Return> + </Processing> + </Transaction> +</Response> + XML + end + + def successful_refund_response + <<-XML +<?xml version="1.0" encoding="UTF-8"?> +<Response version="1.0"> + <Transaction mode="CONNECTOR_TEST" channel="8a82941746287806014628a0e3240575" response="SYNC"> + <Identification> + <ShortID>3009.2986.8450</ShortID> + <UniqueID>8a829449488d790901489992a493546f</UniqueID> + <TransactionID /> + <ReferenceID>8a82944a488d36c101489992a10f6d21</ReferenceID> + </Identification> + <Payment code="CC.RF"> + <Clearing> + <Amount>1.00</Amount> + <Currency>EUR</Currency> + <Descriptor>3009.2986.8450 DEFAULT Store Purchase</Descriptor> + <FxRate>1.0</FxRate> + <FxSource>INTERN</FxSource> + <FxDate>2014-09-21 18:56:37</FxDate> + </Clearing> + </Payment> + <Processing code="CC.RF.90.00"> + <Timestamp>2014-09-21 18:56:37</Timestamp> + <Result>ACK</Result> + <Status code="90">NEW</Status> + <Reason code="00">Successful Processing</Reason> + <Return code="000.100.112">Request successfully processed in 'Merchant in Connector Test Mode'</Return> + </Processing> + </Transaction> +</Response> + XML + end + + def failed_refund_response + <<-XML +<?xml version="1.0" encoding="UTF-8"?> +<Response version="1.0"> + <Transaction mode="CONNECTOR_TEST" channel="8a82941746287806014628a0e3240575" response="SYNC"> + <Identification> + <ShortID>5070.8829.8658</ShortID> + <UniqueID>8a829449488d790901489994b2c65481</UniqueID> + <TransactionID /> + <ReferenceID /> + </Identification> + <Payment code="CC.RF" /> + <Processing code="CC.RF.70.20"> + <Timestamp>2014-09-21 18:58:52</Timestamp> + <Result>NOK</Result> + <Status code="70">REJECTED_VALIDATION</Status> + <Reason code="20">Format Error</Reason> + <Return code="200.100.302">invalid Request/Transaction/Payment/Presentation tag (not present or [partially] empty)</Return> + </Processing> + </Transaction> +</Response> + XML + end + + def successful_void_response + <<-XML +<?xml version="1.0" encoding="UTF-8"?> +<Response version="1.0"> + <Transaction mode="CONNECTOR_TEST" channel="8a82941746287806014628a0e3240575" response="SYNC"> + <Identification> + <ShortID>4587.6991.6578</ShortID> + <UniqueID>8a82944a488d36c1014899957fff6d49</UniqueID> + <TransactionID /> + <ReferenceID>8a829449488d7909014899957cb45486</ReferenceID> + </Identification> + <Payment code="CC.RV"> + <Clearing> + <Amount>1.00</Amount> + <Currency>EUR</Currency> + <Descriptor>4587.6991.6578 DEFAULT Store Purchase</Descriptor> + <FxRate>1.0</FxRate> + <FxSource>INTERN</FxSource> + <FxDate>2014-09-21 18:59:44</FxDate> + </Clearing> + </Payment> + <Processing code="CC.RV.90.00"> + <Timestamp>2014-09-21 18:59:44</Timestamp> + <Result>ACK</Result> + <Status code="90">NEW</Status> + <Reason code="00">Successful Processing</Reason> + <Return code="000.100.112">Request successfully processed in 'Merchant in Connector Test Mode'</Return> + <Risk score="0" /> + </Processing> + </Transaction> +</Response> + XML + end + + def failed_void_response + <<-XML +<Response version="1.0"> + <Transaction mode="CONNECTOR_TEST" channel="8a82941746287806014628a0e3240575" response="SYNC"> + <Identification> + <ShortID>5843.9770.9986</ShortID> + <UniqueID>8a829449488d7909014899965cd354b6</UniqueID> + <TransactionID /> + <ReferenceID /> + </Identification> + <Payment code="CC.RV" /> + <Processing code="CC.RV.70.30"> + <Timestamp>2014-09-21 19:00:41</Timestamp> + <Result>NOK</Result> + <Status code="70">REJECTED_VALIDATION</Status> + <Reason code="30">Reference Error</Reason> + <Return code="700.400.530">reversal needs at least one successful transaction of type (CP or DB or RB or PA)</Return> + <Risk score="0" /> + </Processing> + </Transaction> +</Response> + XML + end +end diff --git a/test/unit/gateways/moneris_test.rb b/test/unit/gateways/moneris_test.rb index 299a8c81a49..f2cd1cd3a77 100644 --- a/test/unit/gateways/moneris_test.rb +++ b/test/unit/gateways/moneris_test.rb @@ -7,29 +7,97 @@ def setup Base.mode = :test @gateway = MonerisGateway.new( - :login => 'store1', + :login => 'store3', :password => 'yesguy' ) @amount = 100 @credit_card = credit_card('4242424242424242') - @options = { :order_id => '1', :customer => '1' } + @options = { :order_id => '1', :customer => '1', :billing_address => address} end def test_default_options assert_equal 7, @gateway.options[:crypt_type] - assert_equal "store1", @gateway.options[:login] - assert_equal "yesguy", @gateway.options[:password] + assert_equal 'store3', @gateway.options[:login] + assert_equal 'yesguy', @gateway.options[:password] end def test_successful_purchase @gateway.expects(:ssl_post).returns(successful_purchase_response) - assert response = @gateway.authorize(100, @credit_card, @options) + assert response = @gateway.purchase(100, @credit_card, @options) assert_success response assert_equal '58-0_3;1026.1', response.authorization end + def test_successful_first_purchase_with_credential_on_file + gateway = MonerisGateway.new( + :login => 'store3', + :password => 'yesguy' + ) + gateway.expects(:ssl_post).returns(successful_first_cof_purchase_response) + assert response = gateway.purchase( + @amount, + @credit_card, + @options.merge( + issuer_id: '', + payment_indicator: 'C', + payment_information: '0' + ) + ) + assert_success response + assert_equal 'Approved', response.message + assert_false response.authorization.blank? + assert_not_empty response.params['issuer_id'] + end + + def test_successful_subsequent_purchase_with_credential_on_file + gateway = MonerisGateway.new( + :login => 'store3', + :password => 'yesguy' + ) + gateway.expects(:ssl_post).returns(successful_first_cof_authorize_response) + assert response = gateway.authorize( + @amount, + @credit_card, + @options.merge( + issuer_id: '', + payment_indicator: 'C', + payment_information: '0' + ) + ) + assert_success response + assert_equal 'Approved', response.message + assert_false response.authorization.blank? + + gateway.expects(:ssl_post).returns(successful_subsequent_cof_purchase_response) + + assert response2 = gateway.purchase( + @amount, + @credit_card, + @options.merge( + order_id: response.authorization, + issuer_id: response.params['issuer_id'], + payment_indicator: 'U', + payment_information: '2' + ) + ) + assert_success response2 + assert_equal 'Approved', response2.message + assert_false response2.authorization.blank? + end + + def test_successful_purchase_with_network_tokenization + @gateway.expects(:ssl_post).returns(successful_purchase_network_tokenization) + @credit_card = network_tokenization_credit_card('4242424242424242', + payment_cryptogram: 'BwABB4JRdgAAAAAAiFF2AAAAAAA=', + verification_value: nil + ) + assert response = @gateway.purchase(100, @credit_card, @options) + assert_success response + assert_equal '101965-0_10;0bbb277b543a17b6781243889a689573', response.authorization + end + def test_failed_purchase @gateway.expects(:ssl_post).returns(failed_purchase_response) @@ -38,67 +106,75 @@ def test_failed_purchase end def test_deprecated_credit - @gateway.expects(:ssl_post).with(anything, regexp_matches(/txn_number>123<\//), anything).returns("") + @gateway.expects(:ssl_post).with(anything, regexp_matches(/txn_number>123<\//), anything).returns('') @gateway.expects(:parse).returns({}) - assert_deprecation_warning(Gateway::CREDIT_DEPRECATION_MESSAGE, @gateway) do - @gateway.credit(@amount, "123;456", @options) + assert_deprecation_warning(Gateway::CREDIT_DEPRECATION_MESSAGE) do + @gateway.credit(@amount, '123;456', @options) end end def test_refund - @gateway.expects(:ssl_post).with(anything, regexp_matches(/txn_number>123<\//), anything).returns("") + @gateway.expects(:ssl_post).with(anything, regexp_matches(/txn_number>123<\//), anything).returns('') @gateway.expects(:parse).returns({}) - @gateway.refund(@amount, "123;456", @options) + @gateway.refund(@amount, '123;456', @options) end def test_amount_style - assert_equal '10.34', @gateway.send(:amount, 1034) + assert_equal '10.34', @gateway.send(:amount, 1034) - assert_raise(ArgumentError) do - @gateway.send(:amount, '10.34') - end + assert_raise(ArgumentError) do + @gateway.send(:amount, '10.34') + end end def test_preauth_is_valid_xml - params = { - :order_id => "order1", - :amount => "1.01", - :pan => "4242424242424242", - :expdate => "0303", - :crypt_type => 7, - } - - assert data = @gateway.send(:post_data, 'preauth', params) - assert REXML::Document.new(data) - assert_equal xml_capture_fixture.size, data.size + params = { + :order_id => 'order1', + :amount => '1.01', + :pan => '4242424242424242', + :expdate => '0303', + :crypt_type => 7, + } + + assert data = @gateway.send(:post_data, 'preauth', params) + assert REXML::Document.new(data) + assert_equal xml_capture_fixture.size, data.size end def test_purchase_is_valid_xml - params = { - :order_id => "order1", - :amount => "1.01", - :pan => "4242424242424242", - :expdate => "0303", - :crypt_type => 7, - } - - assert data = @gateway.send(:post_data, 'purchase', params) - assert REXML::Document.new(data) - assert_equal xml_purchase_fixture.size, data.size + params = { + :order_id => 'order1', + :amount => '1.01', + :pan => '4242424242424242', + :expdate => '0303', + :crypt_type => 7, + } + + assert data = @gateway.send(:post_data, 'purchase', params) + assert REXML::Document.new(data) + assert_equal xml_purchase_fixture.size, data.size end def test_capture_is_valid_xml - params = { - :order_id => "order1", - :amount => "1.01", - :pan => "4242424242424242", - :expdate => "0303", - :crypt_type => 7, - } + params = { + :order_id => 'order1', + :amount => '1.01', + :pan => '4242424242424242', + :expdate => '0303', + :crypt_type => 7, + } + + assert data = @gateway.send(:post_data, 'preauth', params) + assert REXML::Document.new(data) + assert_equal xml_capture_fixture.size, data.size + end - assert data = @gateway.send(:post_data, 'preauth', params) - assert REXML::Document.new(data) - assert_equal xml_capture_fixture.size, data.size + def test_successful_verify + response = stub_comms do + @gateway.verify(@credit_card, @options) + end.respond_with(successful_authorize_response, failed_void_response) + assert_success response + assert_equal 'Approved', response.message end def test_supported_countries @@ -119,9 +195,9 @@ def test_successful_store @gateway.expects(:ssl_post).returns(successful_store_response) assert response = @gateway.store(@credit_card) assert_success response - assert_equal "Successfully registered cc details", response.message - assert response.params["data_key"].present? - @data_key = response.params["data_key"] + assert_equal 'Successfully registered cc details', response.message + assert response.params['data_key'].present? + @data_key = response.params['data_key'] end def test_successful_unstore @@ -129,8 +205,8 @@ def test_successful_unstore test_successful_store assert response = @gateway.unstore(@data_key) assert_success response - assert_equal "Successfully deleted cc details", response.message - assert response.params["data_key"].present? + assert_equal 'Successfully deleted cc details', response.message + assert response.params['data_key'].present? end def test_update @@ -138,8 +214,8 @@ def test_update test_successful_store assert response = @gateway.update(@data_key, @credit_card) assert_success response - assert_equal "Successfully updated cc details", response.message - assert response.params["data_key"].present? + assert_equal 'Successfully updated cc details', response.message + assert response.params['data_key'].present? end def test_successful_purchase_with_vault @@ -147,16 +223,27 @@ def test_successful_purchase_with_vault test_successful_store assert response = @gateway.purchase(100, @data_key, {:order_id => generate_unique_id, :customer => generate_unique_id}) assert_success response - assert_equal "Approved", response.message + assert_equal 'Approved', response.message assert response.authorization.present? end + def test_successful_authorize_with_network_tokenization + @gateway.expects(:ssl_post).returns(successful_authorization_network_tokenization) + @credit_card = network_tokenization_credit_card('4242424242424242', + payment_cryptogram: 'BwABB4JRdgAAAAAAiFF2AAAAAAA=', + verification_value: nil + ) + assert response = @gateway.authorize(100, @credit_card, @options) + assert_success response + assert_equal '109232-0_10;d88d9f5f3472898832c54d6b5572757e', response.authorization + end + def test_successful_authorization_with_vault @gateway.expects(:ssl_post).returns(successful_purchase_response) test_successful_store assert response = @gateway.authorize(100, @data_key, {:order_id => generate_unique_id, :customer => generate_unique_id}) assert_success response - assert_equal "Approved", response.message + assert_equal 'Approved', response.message assert response.authorization.present? end @@ -167,11 +254,11 @@ def test_failed_authorization_with_vault assert_failure response end - def test_gets_sent_when_its_enabled + def test_cvv_enabled_and_provided gateway = MonerisGateway.new(login: 'store1', password: 'yesguy', cvv_enabled: true) - @credit_card.verification_value = "452" - stub_comms do + @credit_card.verification_value = '452' + stub_comms(gateway) do gateway.purchase(@amount, @credit_card, @options) end.check_request do |endpoint, data, headers| assert_match(%r{cvd_indicator>1<}, data) @@ -179,11 +266,11 @@ def test_gets_sent_when_its_enabled end.respond_with(successful_purchase_response) end - def test_no_cvv_specified_when_its_enabled + def test_cvv_enabled_but_not_provided gateway = MonerisGateway.new(login: 'store1', password: 'yesguy', cvv_enabled: true) - @credit_card.verification_value = "" - stub_comms do + @credit_card.verification_value = '' + stub_comms(gateway) do gateway.purchase(@amount, @credit_card, @options) end.check_request do |endpoint, data, headers| assert_match(%r{cvd_indicator>0<}, data) @@ -191,8 +278,8 @@ def test_no_cvv_specified_when_its_enabled end.respond_with(successful_purchase_response) end - def test_passing_cvv_when_not_enabled - @credit_card.verification_value = "452" + def test_cvv_disabled_and_provided + @credit_card.verification_value = '452' stub_comms do @gateway.purchase(@amount, @credit_card, @options) end.check_request do |endpoint, data, headers| @@ -201,8 +288,8 @@ def test_passing_cvv_when_not_enabled end.respond_with(successful_purchase_response) end - def test_no_cvv_specified_when_not_enabled - @credit_card.verification_value = "" + def test_cvv_disabled_but_not_provided + @credit_card.verification_value = '' stub_comms do @gateway.purchase(@amount, @credit_card, @options) end.check_request do |endpoint, data, headers| @@ -211,8 +298,100 @@ def test_no_cvv_specified_when_not_enabled end.respond_with(successful_purchase_response) end + def test_avs_enabled_and_provided + gateway = MonerisGateway.new(login: 'store1', password: 'yesguy', avs_enabled: true) + + billing_address = address(address1: '1234 Anystreet', address2: '') + stub_comms(gateway) do + gateway.purchase(@amount, @credit_card, billing_address: billing_address, order_id: '1') + end.check_request do |endpoint, data, headers| + assert_match(%r{avs_street_number>1234<}, data) + assert_match(%r{avs_street_name>Anystreet<}, data) + assert_match(%r{avs_zipcode>#{billing_address[:zip]}<}, data) + end.respond_with(successful_purchase_response_with_avs_result) + end + + def test_avs_enabled_but_not_provided + gateway = MonerisGateway.new(login: 'store1', password: 'yesguy', avs_enabled: true) + + stub_comms(gateway) do + gateway.purchase(@amount, @credit_card, @options.tap { |x| x.delete(:billing_address) }) + end.check_request do |endpoint, data, headers| + assert_no_match(%r{avs_street_number>}, data) + assert_no_match(%r{avs_street_name>}, data) + assert_no_match(%r{avs_zipcode>}, data) + end.respond_with(successful_purchase_response) + end + + def test_avs_disabled_and_provided + billing_address = address(address1: '1234 Anystreet', address2: '') + stub_comms do + @gateway.purchase(@amount, @credit_card, billing_address: billing_address, order_id: '1') + end.check_request do |endpoint, data, headers| + assert_no_match(%r{avs_street_number>}, data) + assert_no_match(%r{avs_street_name>}, data) + assert_no_match(%r{avs_zipcode>}, data) + end.respond_with(successful_purchase_response_with_avs_result) + end + + def test_avs_disabled_and_not_provided + stub_comms do + @gateway.purchase(@amount, @credit_card, @options.tap { |x| x.delete(:billing_address) }) + end.check_request do |endpoint, data, headers| + assert_no_match(%r{avs_street_number>}, data) + assert_no_match(%r{avs_street_name>}, data) + assert_no_match(%r{avs_zipcode>}, data) + end.respond_with(successful_purchase_response) + end + + def test_avs_result_valid_with_address + @gateway.expects(:ssl_post).returns(successful_purchase_response_with_avs_result) + assert response = @gateway.purchase(100, @credit_card, @options) + assert_equal(response.avs_result, { + 'code' => 'A', + 'message' => 'Street address matches, but postal code does not match.', + 'street_match' => 'Y', + 'postal_match' => 'N' + }) + end + + def test_customer_can_be_specified + stub_comms do + @gateway.purchase(@amount, @credit_card, order_id: '3', customer: 'Joe Jones') + end.check_request do |endpoint, data, headers| + assert_match(%r{cust_id>Joe Jones}, data) + end.respond_with(successful_purchase_response) + end + + def test_customer_not_specified_card_name_used + stub_comms do + @gateway.purchase(@amount, @credit_card, order_id: '3') + end.check_request do |endpoint, data, headers| + assert_match(%r{cust_id>Longbob Longsen}, data) + end.respond_with(successful_purchase_response) + end + + def test_add_swipe_data_with_creditcard + @credit_card.track_data = 'Track Data' + + stub_comms do + @gateway.purchase(@amount, @credit_card, @options) + end.check_request do |endpoint, data, headers| + assert_match '<pos_code>00</pos_code>', data + assert_match '<track2>Track Data</track2>', data + end.respond_with(successful_purchase_response) + end + + def test_scrub + assert_equal @gateway.scrub(pre_scrub), post_scrub + end + + def test_supports_scrubbing? + assert @gateway.supports_scrubbing? + end private + def successful_purchase_response <<-RESPONSE <?xml version="1.0"?> @@ -238,6 +417,209 @@ def successful_purchase_response RESPONSE end + def successful_first_cof_purchase_response + <<-RESPONSE +<?xml version=\"1.0\" standalone=\"yes\"?> +<?xml version=“1.0” standalone=“yes”?> +<response> + <receipt> + <ReceiptId>a33ba7edd448b91ef8d2f85fea614b8d</ReceiptId> + <ReferenceNum>660114080015099160</ReferenceNum> + <ResponseCode>027</ResponseCode> + <ISO>01</ISO> + <AuthCode>822665</AuthCode> + <TransTime>07:43:28</TransTime> + <TransDate>2018-11-11</TransDate> + <TransType>00</TransType> + <Complete>true</Complete> + <Message>APPROVED * =</Message> + <TransAmount>1.00</TransAmount> + <CardType>V</CardType> + <TransID>799655-0_11</TransID> + <TimedOut>false</TimedOut> + <BankTotals>null</BankTotals> + <Ticket>null</Ticket> + <IssuerId>355689484440192</IssuerId> + <IsVisaDebit>false</IsVisaDebit> + </receipt> +</response> + RESPONSE + end + + def successful_first_cof_authorize_response + <<-RESPONSE +<?xml version=\"1.0\" standalone=\"yes\"?> +<response> + <receipt> + <ReceiptId>8dbc28468af2007779bbede7ec1bab6c</ReceiptId> + <ReferenceNum>660109300018229130</ReferenceNum> + <ResponseCode>027</ResponseCode> + <ISO>01</ISO> + <AuthCode>718280</AuthCode> + <TransTime>07:50:53</TransTime> + <TransDate>2018-11-11</TransDate> + <TransType>01</TransType> + <Complete>true</Complete> + <Message>APPROVED * =</Message> + <TransAmount>1.00</TransAmount> + <CardType>V</CardType> + <TransID>830724-0_11</TransID> + <TimedOut>false</TimedOut> + <BankTotals>null</BankTotals> + <Ticket>null</Ticket> + <MessageId>1A8315282537312</MessageId> + <IssuerId>550923784451193</IssuerId> + <IsVisaDebit>false</IsVisaDebit> + </receipt> +</response> + RESPONSE + end + + def successful_subsequent_cof_purchase_response + <<-RESPONSE +<?xml version="1.0" standalone="yes"?> +<response> + <receipt> + <ReceiptId>830724-0_11;8dbc28468af2007779bbede7ec1bab6c</ReceiptId> + <ReferenceNum>660109490014038930</ReferenceNum> + <ResponseCode>027</ResponseCode> + <ISO>01</ISO> + <AuthCode>111234</AuthCode> + <TransTime>07:50:54</TransTime> + <TransDate>2018-11-11</TransDate> + <TransType>00</TransType> + <Complete>true</Complete> + <Message>APPROVED * =</Message> + <TransAmount>1.00</TransAmount> + <CardType>V</CardType> + <TransID>455422-0_11</TransID> + <TimedOut>false</TimedOut> + <BankTotals>null</BankTotals> + <Ticket>null</Ticket> + <IssuerId>762097792112819</IssuerId> + <IsVisaDebit>false</IsVisaDebit> + </receipt> +</response> + RESPONSE + end + + def successful_purchase_network_tokenization + <<-RESPONSE +<?xml version="1.0"?> +<response> + <receipt> + <ReceiptId>0bbb277b543a17b6781243889a689573</ReceiptId> + <ReferenceNum>660110910011133780</ReferenceNum> + <ResponseCode>027</ResponseCode> + <ISO>01</ISO> + <AuthCode>368269</AuthCode> + <TransTime>22:54:10</TransTime> + <TransDate>2015-07-05</TransDate> + <TransType>00</TransType> + <Complete>true</Complete> + <Message>APPROVED * =</Message> + <TransAmount>1.00</TransAmount> + <CardType>V</CardType> + <TransID>101965-0_10</TransID> + <TimedOut>false</TimedOut> + <BankTotals>null</BankTotals> + <Ticket>null</Ticket> + <CorporateCard>false</CorporateCard> + <IsVisaDebit>false</IsVisaDebit> + </receipt> +</response> + + RESPONSE + end + + def successful_authorize_response + <<-RESPONSE + <?xml version="1.0"?> + <response> + <receipt> + <ReceiptId>47986100c3ad69c37ca945f5c54abf1c</ReceiptId> + <ReferenceNum>660144080010396720</ReferenceNum> + <ResponseCode>027</ResponseCode> + <ISO>01</ISO> + <AuthCode>149406</AuthCode> + <TransTime>09:59:15</TransTime> + <TransDate>2016-03-10</TransDate> + <TransType>01</TransType> + <Complete>true</Complete> + <Message>APPROVED * =</Message> + <TransAmount>1.00</TransAmount> + <CardType>V</CardType> + <TransID>51340-0_10</TransID> + <TimedOut>false</TimedOut> + <BankTotals>null</BankTotals> + <Ticket>null</Ticket> + <CorporateCard>false</CorporateCard> + <MessageId>1A6070359555668</MessageId> + <IsVisaDebit>false</IsVisaDebit> + </receipt> + </response> + RESPONSE + end + + def successful_authorization_network_tokenization + <<-RESPONSE +<?xml version="1.0"?> +<response> + <receipt> + <ReceiptId>d88d9f5f3472898832c54d6b5572757e</ReceiptId> + <ReferenceNum>660110910011139740</ReferenceNum> + <ResponseCode>027</ResponseCode> + <ISO>01</ISO> + <AuthCode>873534</AuthCode> + <TransTime>09:31:41</TransTime> + <TransDate>2015-07-09</TransDate> + <TransType>01</TransType> + <Complete>true</Complete> + <Message>APPROVED * =</Message> + <TransAmount>1.00</TransAmount> + <CardType>V</CardType> + <TransID>109232-0_10</TransID> + <TimedOut>false</TimedOut> + <BankTotals>null</BankTotals> + <Ticket>null</Ticket> + <CorporateCard>false</CorporateCard> + <IsVisaDebit>false</IsVisaDebit> + </receipt> +</response> + + RESPONSE + end + + def successful_purchase_response_with_avs_result + <<-RESPONSE +<?xml version="1.0"?> +<response> + <receipt> + <ReceiptId>9c7189ec64b58f541335be1ca6294d09</ReceiptId> + <ReferenceNum>660110910011136190</ReferenceNum> + <ResponseCode>027</ResponseCode> + <ISO>01</ISO> + <AuthCode>115497</AuthCode> + <TransTime>15:20:51</TransTime> + <TransDate>2014-06-18</TransDate> + <TransType>00</TransType> + <Complete>true</Complete><Message>APPROVED * =</Message> + <TransAmount>10.10</TransAmount> + <CardType>V</CardType> + <TransID>491573-0_9</TransID> + <TimedOut>false</TimedOut> + <BankTotals>null</BankTotals> + <Ticket>null</Ticket> + <CorporateCard>false</CorporateCard> + <AvsResultCode>A</AvsResultCode> + <ITDResponse>null</ITDResponse> + <IsVisaDebit>false</IsVisaDebit> + </receipt> +</response> + + RESPONSE + end + def failed_purchase_response <<-RESPONSE <?xml version="1.0"?> @@ -263,7 +645,6 @@ def failed_purchase_response RESPONSE end - def successful_store_response <<-RESPONSE <?xml version="1.0"?> @@ -306,12 +687,94 @@ def successful_update_response RESPONSE end + def failed_void_response + <<-RESPONSE + <?xml version="1.0"?> + <response> + <receipt> + <ReceiptId>null</ReceiptId> + <ReferenceNum>null</ReferenceNum> + <ResponseCode>null</ResponseCode> + <ISO>null</ISO> + <AuthCode>null</AuthCode> + <TransTime>null</TransTime> + <TransDate>null</TransDate> + <TransType>null</TransType> + <Complete>false</Complete> + <Message>No Pre-auth corresponds to the store Id and order Id and transaction Id entered</Message> + <TransAmount>null</TransAmount> + <CardType>null</CardType> + <TransID>null</TransID> + <TimedOut>false</TimedOut> + <BankTotals>null</BankTotals> + <Ticket>null</Ticket> + <IsVisaDebit>false</IsVisaDebit> + </receipt> + </response> + RESPONSE + end def xml_purchase_fixture - '<request><store_id>store1</store_id><api_token>yesguy</api_token><purchase><amount>1.01</amount><pan>4242424242424242</pan><expdate>0303</expdate><crypt_type>7</crypt_type><order_id>order1</order_id></purchase></request>' + '<request><store_id>store1</store_id><api_token>yesguy</api_token><purchase><amount>1.01</amount><pan>4242424242424242</pan><expdate>0303</expdate><crypt_type>7</crypt_type><order_id>order1</order_id></purchase></request>' end def xml_capture_fixture - '<request><store_id>store1</store_id><api_token>yesguy</api_token><preauth><amount>1.01</amount><pan>4242424242424242</pan><expdate>0303</expdate><crypt_type>7</crypt_type><order_id>order1</order_id></preauth></request>' + '<request><store_id>store1</store_id><api_token>yesguy</api_token><preauth><amount>1.01</amount><pan>4242424242424242</pan><expdate>0303</expdate><crypt_type>7</crypt_type><order_id>order1</order_id></preauth></request>' + end + + def pre_scrub + <<-pre_scrub + opening connection to esqa.moneris.com:443... + opened + starting SSL for esqa.moneris.com:443... + SSL established + <- "POST /gateway2/servlet/MpgRequest HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: esqa.moneris.com\r\nContent-Length: 176\r\n\r\n" + <- "<request><store_id>store1</store_id><api_token>yesguy</api_token><res_add_cc><pan>4242424242424242</pan><expdate>1705</expdate><crypt_type>7</crypt_type></res_add_cc></request>" + -> "HTTP/1.1 200 OK\r\n" + -> "Date: Mon, 16 May 2016 02:35:23 GMT\r\n" + -> "Connection: close\r\n" + -> "Content-Type: text/html\r\n" + -> "Set-Cookie: TS011902c9=01649737b1334cfbe6b21538231fb4ad142215050461293f17e2dc76d7821e71c2f25055ea; Path=/\r\n" + -> "Transfer-Encoding: chunked\r\n" + -> "\r\n" + -> "391\r\n" + reading 913 bytes... + -> "<?xml version=\"1.0\"?><response><receipt><DataKey>LAmXQeZwdtzUtz1QI1vF6etR2</DataKey><ReceiptId>null</ReceiptId><ReferenceNum>null</ReferenceNum><ResponseCode>001</ResponseCode><ISO>null</ISO><AuthCode>null</AuthCode><Message>Successfully registered CC details.</Message><TransTime>22:35:23</TransTime><TransDate>2016-05-15</TransDate><TransType>null</TransType><Complete>true</Complete><TransAmount>null</TransAmount><CardType>null</CardType><TransID>null</TransID><TimedOut>false</TimedOut><CorporateCard>null</CorporateCard><RecurSuccess>null</RecurSuccess><AvsResultCode>null</AvsResultCode><CvdResultCode>null</CvdResultCode><ResSuccess>true</ResSuccess><PaymentType>cc</PaymentType><IsVisaDebit>null</IsVisaDebit><ResolveData><cust_id></cust_id><phone></phone><email></email><note></note><crypt_type>7</crypt_type><masked_pan>4242***4242</masked_pan><expdate>1705</expdate></ResolveData></receipt></response>" + read 913 bytes + reading 2 bytes... + -> "\r\n" + read 2 bytes + -> "0\r\n" + -> "\r\n" + Conn close + pre_scrub + end + + def post_scrub + <<-post_scrub + opening connection to esqa.moneris.com:443... + opened + starting SSL for esqa.moneris.com:443... + SSL established + <- "POST /gateway2/servlet/MpgRequest HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: esqa.moneris.com\r\nContent-Length: 176\r\n\r\n" + <- "<request><store_id>[FILTERED]</store_id><api_token>[FILTERED]</api_token><res_add_cc><pan>[FILTERED]</pan><expdate>1705</expdate><crypt_type>7</crypt_type></res_add_cc></request>" + -> "HTTP/1.1 200 OK\r\n" + -> "Date: Mon, 16 May 2016 02:35:23 GMT\r\n" + -> "Connection: close\r\n" + -> "Content-Type: text/html\r\n" + -> "Set-Cookie: TS011902c9=01649737b1334cfbe6b21538231fb4ad142215050461293f17e2dc76d7821e71c2f25055ea; Path=/\r\n" + -> "Transfer-Encoding: chunked\r\n" + -> "\r\n" + -> "391\r\n" + reading 913 bytes... + -> "<?xml version=\"1.0\"?><response><receipt><DataKey>LAmXQeZwdtzUtz1QI1vF6etR2</DataKey><ReceiptId>null</ReceiptId><ReferenceNum>null</ReferenceNum><ResponseCode>001</ResponseCode><ISO>null</ISO><AuthCode>null</AuthCode><Message>Successfully registered CC details.</Message><TransTime>22:35:23</TransTime><TransDate>2016-05-15</TransDate><TransType>null</TransType><Complete>true</Complete><TransAmount>null</TransAmount><CardType>null</CardType><TransID>null</TransID><TimedOut>false</TimedOut><CorporateCard>null</CorporateCard><RecurSuccess>null</RecurSuccess><AvsResultCode>null</AvsResultCode><CvdResultCode>null</CvdResultCode><ResSuccess>true</ResSuccess><PaymentType>cc</PaymentType><IsVisaDebit>null</IsVisaDebit><ResolveData><cust_id></cust_id><phone></phone><email></email><note></note><crypt_type>7</crypt_type><masked_pan>4242***4242</masked_pan><expdate>1705</expdate></ResolveData></receipt></response>" + read 913 bytes + reading 2 bytes... + -> "\r\n" + read 2 bytes + -> "0\r\n" + -> "\r\n" + Conn close + post_scrub end end diff --git a/test/unit/gateways/moneris_us_test.rb b/test/unit/gateways/moneris_us_test.rb index 4c48b955b67..077ad2c8d62 100644 --- a/test/unit/gateways/moneris_us_test.rb +++ b/test/unit/gateways/moneris_us_test.rb @@ -1,6 +1,8 @@ require 'test_helper' class MonerisUsTest < Test::Unit::TestCase + include CommStub + def setup Base.mode = :test @@ -11,13 +13,18 @@ def setup @amount = 100 @credit_card = credit_card('4242424242424242') + @check = check({ + routing_number: '011000015', + account_number: '1234455', + number: 123 + }) @options = { :order_id => '1', :billing_address => address } end def test_default_options assert_equal 7, @gateway.options[:crypt_type] - assert_equal "monusqa002", @gateway.options[:login] - assert_equal "qatoken", @gateway.options[:password] + assert_equal 'monusqa002', @gateway.options[:login] + assert_equal 'qatoken', @gateway.options[:password] end def test_successful_purchase @@ -35,71 +42,101 @@ def test_failed_purchase assert_failure response end + def test_successful_echeck_purchase + @gateway.expects(:ssl_post).returns(successful_echeck_purchase_response) + + assert response = @gateway.authorize(100, @check, @options) + assert_success response + assert_equal '1522-0_25;cb80f38f44af2168fd9033cdf2d0d4c0', response.authorization + end + def test_deprecated_credit - @gateway.expects(:ssl_post).with(anything, regexp_matches(/txn_number>123<\//), anything).returns("") + @gateway.expects(:ssl_post).with(anything, regexp_matches(/txn_number>123<\//), anything).returns('') @gateway.expects(:parse).returns({}) - assert_deprecation_warning(Gateway::CREDIT_DEPRECATION_MESSAGE, @gateway) do - @gateway.credit(@amount, "123;456", @options) + assert_deprecation_warning(Gateway::CREDIT_DEPRECATION_MESSAGE) do + @gateway.credit(@amount, '123;456', @options) end end def test_refund - @gateway.expects(:ssl_post).with(anything, regexp_matches(/txn_number>123<\//), anything).returns("") + @gateway.expects(:ssl_post).with(anything, regexp_matches(/txn_number>123<\//), anything).returns('') @gateway.expects(:parse).returns({}) - @gateway.refund(@amount, "123;456", @options) + @gateway.refund(@amount, '123;456', @options) + end + + def test_successful_verify + response = stub_comms do + @gateway.verify(@credit_card, @options) + end.respond_with(successful_authorize_response, successful_capture_response) + assert_success response + assert_equal '830337-0_25;d315c7a28623dec77dc136450692d2dd', response.authorization + end + + def test_successful_verify_and_failed_void + response = stub_comms do + @gateway.verify(@credit_card, @options) + end.respond_with(successful_authorize_response, failed_capture_response) + assert_success response + assert_equal '830337-0_25;d315c7a28623dec77dc136450692d2dd', response.authorization + assert_equal 'Approved', response.message + end + + def test_failed_verify + response = stub_comms do + @gateway.verify(@credit_card, @options) + end.respond_with(failed_authorize_response, successful_capture_response) + assert_failure response + assert_equal 'Declined', response.message end def test_amount_style - assert_equal '10.34', @gateway.send(:amount, 1034) + assert_equal '10.34', @gateway.send(:amount, 1034) - assert_raise(ArgumentError) do - @gateway.send(:amount, '10.34') - end + assert_raise(ArgumentError) do + @gateway.send(:amount, '10.34') + end end def test_preauth_is_valid_xml + params = { + :order_id => 'order1', + :amount => '1.01', + :pan => '4242424242424242', + :expdate => '0303', + :crypt_type => 7, + } - params = { - :order_id => "order1", - :amount => "1.01", - :pan => "4242424242424242", - :expdate => "0303", - :crypt_type => 7, - } - - assert data = @gateway.send(:post_data, 'us_preauth', params) - assert REXML::Document.new(data) - assert_equal xml_capture_fixture.size, data.size + assert data = @gateway.send(:post_data, 'us_preauth', params) + assert REXML::Document.new(data) + assert_equal xml_capture_fixture.size, data.size end def test_purchase_is_valid_xml + params = { + :order_id => 'order1', + :amount => '1.01', + :pan => '4242424242424242', + :expdate => '0303', + :crypt_type => 7, + } - params = { - :order_id => "order1", - :amount => "1.01", - :pan => "4242424242424242", - :expdate => "0303", - :crypt_type => 7, - } - - assert data = @gateway.send(:post_data, 'us_purchase', params) - assert REXML::Document.new(data) - assert_equal xml_purchase_fixture.size, data.size + assert data = @gateway.send(:post_data, 'us_purchase', params) + assert REXML::Document.new(data) + assert_equal xml_purchase_fixture.size, data.size end def test_capture_is_valid_xml + params = { + :order_id => 'order1', + :amount => '1.01', + :pan => '4242424242424242', + :expdate => '0303', + :crypt_type => 7, + } - params = { - :order_id => "order1", - :amount => "1.01", - :pan => "4242424242424242", - :expdate => "0303", - :crypt_type => 7, - } - - assert data = @gateway.send(:post_data, 'us_preauth', params) - assert REXML::Document.new(data) - assert_equal xml_capture_fixture.size, data.size + assert data = @gateway.send(:post_data, 'us_preauth', params) + assert REXML::Document.new(data) + assert_equal xml_capture_fixture.size, data.size end def test_supported_countries @@ -116,63 +153,497 @@ def test_should_raise_error_if_transaction_param_empty_on_credit_request end end + def test_successful_store + @gateway.expects(:ssl_post).returns(successful_store_response) + assert response = @gateway.store(@credit_card) + assert_success response + assert_equal 'Successfully registered cc details', response.message + assert response.params['data_key'].present? + @data_key = response.params['data_key'] + end + + def test_successful_unstore + @gateway.expects(:ssl_post).returns(successful_unstore_response) + test_successful_store + assert response = @gateway.unstore(@data_key) + assert_success response + assert_equal 'Successfully deleted cc details', response.message + assert response.params['data_key'].present? + end + + def test_update + @gateway.expects(:ssl_post).returns(successful_update_response) + test_successful_store + assert response = @gateway.update(@data_key, @credit_card) + assert_success response + assert_equal 'Successfully updated cc details', response.message + assert response.params['data_key'].present? + end + + def test_successful_purchase_with_vault + @gateway.expects(:ssl_post).returns(successful_purchase_response) + test_successful_store + assert response = @gateway.purchase(100, @data_key, {:order_id => generate_unique_id, :customer => generate_unique_id}) + assert_success response + assert_equal 'Approved', response.message + assert response.authorization.present? + end + + def test_successful_authorization_with_vault + @gateway.expects(:ssl_post).returns(successful_purchase_response) + test_successful_store + assert response = @gateway.authorize(100, @data_key, {:order_id => generate_unique_id, :customer => generate_unique_id}) + assert_success response + assert_equal 'Approved', response.message + assert response.authorization.present? + end + + def test_failed_authorization_with_vault + @gateway.expects(:ssl_post).returns(failed_purchase_response) + test_successful_store + assert response = @gateway.authorize(100, @data_key, @options) + assert_failure response + end + + def test_cvv_enabled_and_provided + gateway = MonerisGateway.new(login: 'store1', password: 'yesguy', cvv_enabled: true) + + @credit_card.verification_value = '452' + stub_comms(gateway) do + gateway.purchase(@amount, @credit_card, @options) + end.check_request do |endpoint, data, headers| + assert_match(%r{cvd_indicator>1<}, data) + assert_match(%r{cvd_value>452<}, data) + end.respond_with(successful_purchase_response) + end + + def test_cvv_enabled_but_not_provided + gateway = MonerisGateway.new(login: 'store1', password: 'yesguy', cvv_enabled: true) + + @credit_card.verification_value = '' + stub_comms(gateway) do + gateway.purchase(@amount, @credit_card, @options) + end.check_request do |endpoint, data, headers| + assert_match(%r{cvd_indicator>0<}, data) + assert_no_match(%r{cvd_value>}, data) + end.respond_with(successful_purchase_response) + end + + def test_cvv_disabled_and_provided + @credit_card.verification_value = '452' + stub_comms do + @gateway.purchase(@amount, @credit_card, @options) + end.check_request do |endpoint, data, headers| + assert_no_match(%r{cvd_value>}, data) + assert_no_match(%r{cvd_indicator>}, data) + end.respond_with(successful_purchase_response) + end + + def test_cvv_disabled_but_not_provided + @credit_card.verification_value = '' + stub_comms do + @gateway.purchase(@amount, @credit_card, @options) + end.check_request do |endpoint, data, headers| + assert_no_match(%r{cvd_value>}, data) + assert_no_match(%r{cvd_indicator>}, data) + end.respond_with(successful_purchase_response) + end + + def test_avs_enabled_and_provided + gateway = MonerisGateway.new(login: 'store1', password: 'yesguy', avs_enabled: true) + + billing_address = address(address1: '1234 Anystreet', address2: '') + stub_comms(gateway) do + gateway.purchase(@amount, @credit_card, billing_address: billing_address, order_id: '1') + end.check_request do |endpoint, data, headers| + assert_match(%r{avs_street_number>1234<}, data) + assert_match(%r{avs_street_name>Anystreet<}, data) + assert_match(%r{avs_zipcode>#{billing_address[:zip]}<}, data) + end.respond_with(successful_purchase_response_with_avs_result) + end + + def test_avs_enabled_but_not_provided + gateway = MonerisGateway.new(login: 'store1', password: 'yesguy', avs_enabled: true) + + stub_comms(gateway) do + gateway.purchase(@amount, @credit_card, @options.tap { |x| x.delete(:billing_address) }) + end.check_request do |endpoint, data, headers| + assert_no_match(%r{avs_street_number>}, data) + assert_no_match(%r{avs_street_name>}, data) + assert_no_match(%r{avs_zipcode>}, data) + end.respond_with(successful_purchase_response) + end + + def test_avs_disabled_and_provided + billing_address = address(address1: '1234 Anystreet', address2: '') + stub_comms do + @gateway.purchase(@amount, @credit_card, billing_address: billing_address, order_id: '1') + end.check_request do |endpoint, data, headers| + assert_no_match(%r{avs_street_number>}, data) + assert_no_match(%r{avs_street_name>}, data) + assert_no_match(%r{avs_zipcode>}, data) + end.respond_with(successful_purchase_response_with_avs_result) + end + + def test_avs_disabled_and_not_provided + stub_comms do + @gateway.purchase(@amount, @credit_card, @options.tap { |x| x.delete(:billing_address) }) + end.check_request do |endpoint, data, headers| + assert_no_match(%r{avs_street_number>}, data) + assert_no_match(%r{avs_street_name>}, data) + assert_no_match(%r{avs_zipcode>}, data) + end.respond_with(successful_purchase_response) + end + + def test_avs_result_valid_with_address + @gateway.expects(:ssl_post).returns(successful_purchase_response_with_avs_result) + assert response = @gateway.purchase(100, @credit_card, @options) + assert_equal(response.avs_result, { + 'code' => 'A', + 'message' => 'Street address matches, but postal code does not match.', + 'street_match' => 'Y', + 'postal_match' => 'N' + }) + end + + def test_customer_can_be_specified + stub_comms do + @gateway.purchase(@amount, @credit_card, order_id: '3', customer: 'Joe Jones') + end.check_request do |endpoint, data, headers| + assert_match(%r{cust_id>Joe Jones}, data) + end.respond_with(successful_purchase_response) + end + + def test_customer_not_specified_card_name_used + stub_comms do + @gateway.purchase(@amount, @credit_card, order_id: '3') + end.check_request do |endpoint, data, headers| + assert_match(%r{cust_id>Longbob Longsen}, data) + end.respond_with(successful_purchase_response) + end + + def test_transcript_scrubbing + assert @gateway.supports_scrubbing? + assert_equal @gateway.scrub(pre_scrub), post_scrub + end + private + def successful_purchase_response <<-RESPONSE -<?xml version="1.0"?> -<response> - <receipt> - <ReceiptId>1026.1</ReceiptId> - <ReferenceNum>661221050010170010</ReferenceNum> - <ResponseCode>027</ResponseCode> - <ISO>01</ISO> - <AuthCode>013511</AuthCode> - <TransTime>18:41:13</TransTime> - <TransDate>2008-01-05</TransDate> - <TransType>00</TransType> - <Complete>true</Complete> - <Message>APPROVED * =</Message> - <TransAmount>1.00</TransAmount> - <CardType>V</CardType> - <TransID>58-0_3</TransID> - <TimedOut>false</TimedOut> - </receipt> -</response> + <?xml version="1.0"?> + <response> + <receipt> + <ReceiptId>1026.1</ReceiptId> + <ReferenceNum>661221050010170010</ReferenceNum> + <ResponseCode>027</ResponseCode> + <ISO>01</ISO> + <AuthCode>013511</AuthCode> + <TransTime>18:41:13</TransTime> + <TransDate>2008-01-05</TransDate> + <TransType>00</TransType> + <Complete>true</Complete> + <Message>APPROVED * =</Message> + <TransAmount>1.00</TransAmount> + <CardType>V</CardType> + <TransID>58-0_3</TransID> + <TimedOut>false</TimedOut> + </receipt> + </response> + RESPONSE + end + def successful_purchase_response_with_avs_result + <<-RESPONSE + <?xml version="1.0"?> + <response> + <receipt> + <ReceiptId>9c7189ec64b58f541335be1ca6294d09</ReceiptId> + <ReferenceNum>660110910011136190</ReferenceNum> + <ResponseCode>027</ResponseCode> + <ISO>01</ISO> + <AuthCode>115497</AuthCode> + <TransTime>15:20:51</TransTime> + <TransDate>2014-06-18</TransDate> + <TransType>00</TransType> + <Complete>true</Complete><Message>APPROVED * =</Message> + <TransAmount>10.10</TransAmount> + <CardType>V</CardType> + <TransID>491573-0_9</TransID> + <TimedOut>false</TimedOut> + <BankTotals>null</BankTotals> + <Ticket>null</Ticket> + <CorporateCard>false</CorporateCard> + <AvsResultCode>A</AvsResultCode> + <ITDResponse>null</ITDResponse> + <IsVisaDebit>false</IsVisaDebit> + </receipt> + </response> RESPONSE end def failed_purchase_response <<-RESPONSE -<?xml version="1.0"?> -<response> - <receipt> - <ReceiptId>1026.1</ReceiptId> - <ReferenceNum>661221050010170010</ReferenceNum> - <ResponseCode>481</ResponseCode> - <ISO>01</ISO> - <AuthCode>013511</AuthCode> - <TransTime>18:41:13</TransTime> - <TransDate>2008-01-05</TransDate> - <TransType>00</TransType> - <Complete>true</Complete> - <Message>DECLINED * =</Message> - <TransAmount>1.00</TransAmount> - <CardType>V</CardType> - <TransID>97-2-0</TransID> - <TimedOut>false</TimedOut> - </receipt> -</response> + <?xml version="1.0"?> + <response> + <receipt> + <ReceiptId>1026.1</ReceiptId> + <ReferenceNum>661221050010170010</ReferenceNum> + <ResponseCode>481</ResponseCode> + <ISO>01</ISO> + <AuthCode>013511</AuthCode> + <TransTime>18:41:13</TransTime> + <TransDate>2008-01-05</TransDate> + <TransType>00</TransType> + <Complete>true</Complete> + <Message>DECLINED * =</Message> + <TransAmount>1.00</TransAmount> + <CardType>V</CardType> + <TransID>97-2-0</TransID> + <TimedOut>false</TimedOut> + </receipt> + </response> + RESPONSE + end + + def successful_authorize_response + <<-RESPONSE + <?xml version="1.0" encoding="UTF-8"?> + <response> + <receipt> + <ReceiptId>d315c7a28623dec77dc136450692d2dd</ReceiptId> + <ReferenceNum>640000030011763320</ReferenceNum> + <ResponseCode>001</ResponseCode> + <ISO>00</ISO> + <AuthCode>372611</AuthCode> + <TransTime>09:08:58</TransTime> + <TransDate>2015-04-21</TransDate> + <TransType>01</TransType> + <Complete>true</Complete> + <Message>APPROVED*</Message> + <TransAmount>1.00</TransAmount> + <CardType>V</CardType> + <TransID>830337-0_25</TransID> + <TimedOut>false</TimedOut> + <BankTotals>null</BankTotals> + <Ticket>null</Ticket> + <CorporateCard>false</CorporateCard> + <CardLevelResult>A</CardLevelResult> + <CavvResultCode /> + </receipt> + </response> + RESPONSE + end + + def failed_authorize_response + <<-RESPONSE + <?xml version="1.0" encoding="UTF-8"?> + <response> + <receipt> + <ReceiptId>1fa06a83bbd1285ccfa1312835d5aa8d</ReceiptId> + <ReferenceNum>640020510015803330</ReferenceNum> + <ResponseCode>481</ResponseCode> + <ISO>05</ISO> + <AuthCode>242724</AuthCode> + <TransTime>09:12:31</TransTime> + <TransDate>2015-04-21</TransDate> + <TransType>01</TransType> + <Complete>true</Complete> + <Message>DECLINED*</Message> + <TransAmount>1.05</TransAmount> + <CardType>V</CardType> + <TransID>118187-0_25</TransID> + <TimedOut>false</TimedOut> + <BankTotals>null</BankTotals> + <Ticket>null</Ticket> + <CorporateCard>false</CorporateCard> + <CardLevelResult>A</CardLevelResult> + <CavvResultCode /> + </receipt> + </response> + RESPONSE + end + + def successful_capture_response + <<-RESPONSE + <?xml version="1.0" encoding="UTF-8"?> + <response> + <receipt> + <ReceiptId>3a7150ceb7026fccc380743ea3f47fdf</ReceiptId> + <ReferenceNum>640000030011763340</ReferenceNum> + <ResponseCode>001</ResponseCode> + <ISO>00</ISO> + <AuthCode>224958</AuthCode> + <TransTime>09:13:45</TransTime> + <TransDate>2015-04-21</TransDate> + <TransType>02</TransType> + <Complete>true</Complete> + <Message>APPROVED*</Message> + <TransAmount>0.00</TransAmount> + <CardType>V</CardType> + <TransID>830339-1_25</TransID> + <TimedOut>false</TimedOut> + <BankTotals>null</BankTotals> + <Ticket>null</Ticket> + <CorporateCard>false</CorporateCard> + <CardLevelResult>A</CardLevelResult> + </receipt> + </response> + RESPONSE + end + + def failed_capture_response + <<-RESPONSE + <?xml version="1.0" encoding="UTF-8"?> + <response> + <receipt> + <ReceiptId>3a7150ceb7026fccc380743ea3f47fdf</ReceiptId> + <ReferenceNum>640000030011763350</ReferenceNum> + <ResponseCode>476</ResponseCode> + <ISO>12</ISO> + <AuthCode>224958</AuthCode> + <TransTime>09:13:46</TransTime> + <TransDate>2015-04-21</TransDate> + <TransType>02</TransType> + <Complete>true</Complete> + <Message>DECLINED*</Message> + <TransAmount>0.00</TransAmount> + <CardType>V</CardType> + <TransID>830340-2_25</TransID> + <TimedOut>false</TimedOut> + <BankTotals>null</BankTotals> + <Ticket>null</Ticket> + <CorporateCard>false</CorporateCard> + </receipt> + </response> + RESPONSE + end + + def successful_store_response + <<-RESPONSE + <?xml version="1.0"?> + <response> + <receipt> + <DataKey>1234567890</DataKey> + <ResponseCode>027</ResponseCode> + <Complete>true</Complete> + <Message>Successfully registered cc details * =</Message> + </receipt> + </response> + RESPONSE + end + def successful_unstore_response + <<-RESPONSE + <?xml version="1.0"?> + <response> + <receipt> + <DataKey>1234567890</DataKey> + <ResponseCode>027</ResponseCode> + <Complete>true</Complete> + <Message>Successfully deleted cc details * =</Message> + </receipt> + </response> RESPONSE end + def successful_update_response + <<-RESPONSE + <?xml version="1.0"?> + <response> + <receipt> + <DataKey>1234567890</DataKey> + <ResponseCode>027</ResponseCode> + <Complete>true</Complete> + <Message>Successfully updated cc details * =</Message> + </receipt> + </response> + RESPONSE + end + + def successful_echeck_purchase_response + <<-RESPONSE + <?xml version="1.0"?> + <response> + <receipt> + <ReceiptId>cb80f38f44af2168fd9033cdf2d0d4c0</ReceiptId> + <ReferenceNum>001000040010015220</ReferenceNum> + <ResponseCode>005</ResponseCode> + <ISO>01</ISO> + <AuthCode></AuthCode> + <TransTime>08:23:37</TransTime> + <TransDate>2018-06-18</TransDate> + <TransType>00</TransType> + <Complete>true</Complete> + <Message>REGISTERED * =</Message> + <TransAmount>1.0</TransAmount> + <CardType>CQ</CardType> + <TransID>1522-0_25</TransID> + <TimedOut>false</TimedOut> + <BankTotals>null</BankTotals> + <Ticket>null</Ticket> + <CorporateCard>false</CorporateCard> + <MessageId>null</MessageId> + <RecurSuccess>true</RecurSuccess> + </receipt> + </response> + RESPONSE + end + def xml_purchase_fixture - '<request><store_id>monusqa002</store_id><api_token>qatoken</api_token><us_purchase><amount>1.01</amount><pan>4242424242424242</pan><expdate>0303</expdate><crypt_type>7</crypt_type><order_id>order1</order_id></us_purchase></request>' + '<request><store_id>monusqa002</store_id><api_token>qatoken</api_token><us_purchase><amount>1.01</amount><pan>4242424242424242</pan><expdate>0303</expdate><crypt_type>7</crypt_type><order_id>order1</order_id></us_purchase></request>' end def xml_capture_fixture - '<request><store_id>monusqa002</store_id><api_token>qatoken</api_token><us_preauth><amount>1.01</amount><pan>4242424242424242</pan><expdate>0303</expdate><crypt_type>7</crypt_type><order_id>order1</order_id></us_preauth></request>' + '<request><store_id>monusqa002</store_id><api_token>qatoken</api_token><us_preauth><amount>1.01</amount><pan>4242424242424242</pan><expdate>0303</expdate><crypt_type>7</crypt_type><order_id>order1</order_id></us_preauth></request>' + end + + def pre_scrub + %q{ +opening connection to esplusqa.moneris.com:443... +opened +starting SSL for esplusqa.moneris.com:443... +SSL established +<- "POST /gateway_us/servlet/MpgRequest HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: esplusqa.moneris.com\r\nContent-Length: 291\r\n\r\n" +<- "<request><store_id>monusqa002</store_id><api_token>qatoken</api_token><us_purchase><order_id>cec9ca34132f0945446589e36fff9cce</order_id><cust_id>Longbob Longsen</cust_id><amount>1.00</amount><pan>4242424242424242</pan><expdate>1909</expdate><crypt_type>7</crypt_type></us_purchase></request>" +-> "HTTP/1.1 200 OK\r\n" +-> "Date: Mon, 08 Jan 2018 19:20:26 GMT\r\n" +-> "Content-Length: 659\r\n" +-> "X-UA-Compatible: IE=Edge\r\n" +-> "Connection: close\r\n" +-> "Content-Type: text/html; charset=UTF-8\r\n" +-> "Set-Cookie: TS01d02998=01649737b16d4ca54c296a0a369f4e549e4191e85d8d022d01468e559975e945b419002a42; Path=/\r\n" +-> "Set-Cookie: TS01d02998_28=01e24a44e55591744bc115f421ddccd549b1655d75bda586c8ea625670efaa4432f67c8b7e06e7af82c70ef3ac4f46d7435664f2ac; Path=/\r\n" +-> "\r\n" +reading 659 bytes... +-> "<?xml version=\"1.0\" standalone=\"yes\"?><response><receipt><ReceiptId>cec9ca34132f0945446589e36fff9cce</ReceiptId><ReferenceNum>640000030013630190</ReferenceNum><ResponseCode>001</ResponseCode><ISO>00</ISO><AuthCode>827125</AuthCode><TransTime>13:20:24</TransTime><TransDate>2018-01-08</TransDate><TransType>00</TransType><Complete>true</Complete><Message>APPROVED*</Message><TransAmount>1.00</TransAmount><CardType>V</CardType><TransID>113295-0_25</TransID><TimedOut>false</TimedOut><BankTotals>null</BankTotals><Ticket>null</Ticket><CorporateCard>false</CorporateCard><CardLevelResult>A</CardLevelResult><CavvResultCode> </CavvResultCode></receipt></response>" +read 659 bytes +Conn close + } + end + + def post_scrub + %q{ +opening connection to esplusqa.moneris.com:443... +opened +starting SSL for esplusqa.moneris.com:443... +SSL established +<- "POST /gateway_us/servlet/MpgRequest HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: esplusqa.moneris.com\r\nContent-Length: 291\r\n\r\n" +<- "<request><store_id>monusqa002</store_id><api_token>[FILTERED]</api_token><us_purchase><order_id>cec9ca34132f0945446589e36fff9cce</order_id><cust_id>Longbob Longsen</cust_id><amount>1.00</amount><pan>[FILTERED]</pan><expdate>1909</expdate><crypt_type>7</crypt_type></us_purchase></request>" +-> "HTTP/1.1 200 OK\r\n" +-> "Date: Mon, 08 Jan 2018 19:20:26 GMT\r\n" +-> "Content-Length: 659\r\n" +-> "X-UA-Compatible: IE=Edge\r\n" +-> "Connection: close\r\n" +-> "Content-Type: text/html; charset=UTF-8\r\n" +-> "Set-Cookie: TS01d02998=01649737b16d4ca54c296a0a369f4e549e4191e85d8d022d01468e559975e945b419002a42; Path=/\r\n" +-> "Set-Cookie: TS01d02998_28=01e24a44e55591744bc115f421ddccd549b1655d75bda586c8ea625670efaa4432f67c8b7e06e7af82c70ef3ac4f46d7435664f2ac; Path=/\r\n" +-> "\r\n" +reading 659 bytes... +-> "<?xml version=\"1.0\" standalone=\"yes\"?><response><receipt><ReceiptId>cec9ca34132f0945446589e36fff9cce</ReceiptId><ReferenceNum>640000030013630190</ReferenceNum><ResponseCode>001</ResponseCode><ISO>00</ISO><AuthCode>827125</AuthCode><TransTime>13:20:24</TransTime><TransDate>2018-01-08</TransDate><TransType>00</TransType><Complete>true</Complete><Message>APPROVED*</Message><TransAmount>1.00</TransAmount><CardType>V</CardType><TransID>113295-0_25</TransID><TimedOut>false</TimedOut><BankTotals>null</BankTotals><Ticket>null</Ticket><CorporateCard>false</CorporateCard><CardLevelResult>A</CardLevelResult><CavvResultCode> </CavvResultCode></receipt></response>" +read 659 bytes +Conn close + } end end diff --git a/test/unit/gateways/money_movers_test.rb b/test/unit/gateways/money_movers_test.rb new file mode 100644 index 00000000000..c5c3b275baf --- /dev/null +++ b/test/unit/gateways/money_movers_test.rb @@ -0,0 +1,133 @@ +require 'test_helper' + +class MoneyMoversTest < Test::Unit::TestCase + def setup + @gateway = MoneyMoversGateway.new( + :login => 'demo', + :password => 'password' + ) + + @credit_card = credit_card('4111111111111111') + @credit_card.verification_value = '999' + @amount = 100 + + @options = { + :order_id => '1', + :billing_address => address, + :description => 'Store Purchase' + } + end + + def test_successful_authorization + @gateway.expects(:ssl_post).returns(successful_authorization_response) + assert response = @gateway.authorize(@amount, @credit_card, @options) + assert_success response + assert_equal '1355694937', response.authorization + assert_equal 'auth', response.params['type'] + end + + def test_successful_purchase + @gateway.expects(:ssl_post).returns(successful_purchase_response) + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal '1346648416', response.authorization + assert_equal 'sale', response.params['type'] + assert response.test? + end + + def test_unsuccessful_request + @gateway.expects(:ssl_post).returns(failed_purchase_response) + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert response.test? + end + + def test_add_address + result = {} + @gateway.send(:add_address, result, :billing_address => {:address1 => '1 Main St.', :address2 => 'apt 13', :country => 'US', :state => 'MI', :phone => '1234567890'}) + assert_equal ['address1', 'address2', 'city', 'company', 'country', 'phone', 'state', 'zip'], result.stringify_keys.keys.sort + assert_equal 'MI', result[:state] + assert_equal '1 Main St.', result[:address1] + assert_equal 'apt 13', result[:address2] + assert_equal 'US', result[:country] + end + + def test_add_invoice + result = {} + @gateway.send(:add_invoice, result, :order_id => '#1001', :description => 'This is a great order') + assert_equal '#1001', result[:orderid] + assert_equal 'This is a great order', result[:orderdescription] + end + + def test_purchase_is_valid_csv + params = {:amount => @amount} + @gateway.send(:add_creditcard, params, @credit_card) + + assert data = @gateway.send(:post_data, 'auth', params) + assert_equal post_data_fixture.size, data.size + end + + def test_purchase_meets_minimum_requirements + params = {:amount => @amount} + @gateway.send(:add_creditcard, params, @credit_card) + assert data = @gateway.send(:post_data, 'auth', params) + minimum_requirements.each do |key| + assert data.include?(key) + end + end + + def test_expdate_formatting + assert_equal '0909', @gateway.send(:expdate, credit_card('4111111111111111', :month => '9', :year => '2009')) + assert_equal '0711', @gateway.send(:expdate, credit_card('4111111111111111', :month => '7', :year => '2011')) + end + + def test_supported_countries + assert_equal ['US'], @gateway.supported_countries + end + + def test_supported_card_types + assert_equal @gateway.supported_cardtypes, [:visa, :master, :american_express, :discover] + end + + def test_avs_result + @gateway.expects(:ssl_post).returns(successful_authorization_response) + response = @gateway.purchase(@amount, @credit_card) + assert_equal 'Y', response.avs_result['code'] + end + + def test_cvv_result + @gateway.expects(:ssl_post).returns(successful_authorization_response) + response = @gateway.purchase(@amount, @credit_card) + assert_equal 'M', response.cvv_result['code'] + end + + def test_amount + assert_equal '1.00', @gateway.send(:amount, 100) + assert_equal '10.00', @gateway.send(:amount, 1000) + assert_raise(ArgumentError) do + @gateway.send(:amount, '10.00') + end + end + + private + + def post_data_fixture + 'password=password&type=auth&ccnumber=4111111111111111&username=demo&ccexp=1111&amount=100&cvv=999' + end + + def minimum_requirements + %w{type username password amount ccnumber ccexp} + end + + def successful_authorization_response + 'response=1&responsetext=SUCCESS&authcode=123456&transactionid=1355694937&avsresponse=Y&cvvresponse=M&orderid=&type=auth&response_code=100' + end + + def successful_purchase_response + 'response=1&responsetext=SUCCESS&authcode=123456&transactionid=1346648416&avsresponse=N&cvvresponse=N&orderid=&type=sale&response_code=100' + end + + def failed_purchase_response + 'response=2&responsetext=DECLINE&authcode=&transactionid=1346648595&avsresponse=N&cvvresponse=N&orderid=&type=sale&response_code=200' + end +end diff --git a/test/unit/gateways/mundipagg_test.rb b/test/unit/gateways/mundipagg_test.rb new file mode 100644 index 00000000000..b6806224f3a --- /dev/null +++ b/test/unit/gateways/mundipagg_test.rb @@ -0,0 +1,902 @@ +require 'test_helper' + +class MundipaggTest < Test::Unit::TestCase + include CommStub + def setup + @credit_card = credit_card + + @alelo_card = credit_card( + '5067700000000028', + { + month: 10, + year: 2032, + first_name: 'John', + last_name: 'Smith', + verification_value: '737', + brand: 'alelo' + } + ) + + @alelo_visa_card = credit_card( + '4025880000000010', + { + month: 10, + year: 2032, + first_name: 'John', + last_name: 'Smith', + verification_value: '737', + brand: 'alelo' + } + ) + + @gateway = MundipaggGateway.new(api_key: 'my_api_key') + @amount = 100 + + @options = { + gateway_affiliation_id: 'abc123', + order_id: '1', + billing_address: address, + description: 'Store Purchase' + } + end + + def test_successful_purchase + test_successful_purchase_with(@credit_card) + end + + def test_successful_purchase_with_alelo_card + test_successful_purchase_with(@alelo_card) + end + + def test_successful_purchase_with_alelo_number_beginning_with_4 + test_successful_purchase_with(@alelo_visa_card) + end + + def test_successful_purchase_with_holder_document + @options[:holder_document] = 'a1b2c3d4' + response = stub_comms do + @gateway.purchase(@amount, @credit_card, @options) + end.check_request do |endpoint, data, headers| + assert_match(/a1b2c3d4/, data) + end.respond_with(successful_purchase_response) + + assert_success response + assert response.test? + end + + def test_billing_not_sent + @options.delete(:billing_address) + stub_comms do + @gateway.purchase(@amount, @credit_card, @options) + end.check_request do |endpoint, data, headers| + refute data['billing_address'] + end.respond_with(successful_purchase_response) + end + + def test_failed_purchase + @gateway.expects(:ssl_post).returns(failed_purchase_response) + + response = @gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_equal Gateway::STANDARD_ERROR_CODE[:processing_error], response.error_code + end + + def test_successful_authorize + @gateway.expects(:ssl_post).returns(successful_authorize_response) + + response = @gateway.authorize(@amount, @credit_card, @options) + assert_success response + + assert_equal 'ch_gm5wrlGMI2Fb0x6K', response.authorization + assert response.test? + end + + def test_successful_authorize_with_partially_missing_address + shipping_address = { + country: 'BR', + address1: 'Foster St.' + } + + @gateway.expects(:ssl_post).returns(successful_authorize_response) + response = @gateway.authorize(@amount, @credit_card, @options.merge(shipping_address: shipping_address)) + assert_success response + + assert_equal 'ch_gm5wrlGMI2Fb0x6K', response.authorization + assert response.test? + end + + def test_failed_authorize + @gateway.expects(:ssl_post).returns(failed_authorize_response) + + response = @gateway.authorize(@amount, @credit_card, @options) + assert_failure response + assert_equal Gateway::STANDARD_ERROR_CODE[:processing_error], response.error_code + end + + def test_successful_capture + @gateway.expects(:ssl_post).returns(successful_capture_response) + + response = @gateway.capture(@amount, @credit_card, @options) + assert_success response + + assert_equal 'ch_gm5wrlGMI2Fb0x6K', response.authorization + assert response.test? + end + + def test_failed_capture + @gateway.expects(:ssl_post).returns(failed_capture_response) + + response = @gateway.capture(@amount, @credit_card, @options) + assert_failure response + assert_equal Gateway::STANDARD_ERROR_CODE[:processing_error], response.error_code + end + + def test_successful_refund + @gateway.expects(:ssl_request).returns(successful_refund_response) + + response = @gateway.refund(@amount, 'K1J5B1YFLE') + assert_success response + + assert_equal 'ch_RbPVPWMH2bcGA50z', response.authorization + assert response.test? + end + + def test_failed_refund + @gateway.expects(:ssl_request).returns(failed_refund_response) + + response = @gateway.refund(@amount, 'abc') + assert_failure response + assert_equal Gateway::STANDARD_ERROR_CODE[:processing_error], response.error_code + end + + def test_successful_void + @gateway.expects(:ssl_request).returns(successful_void_response) + + response = @gateway.void('ch_RbPVPWMH2bcGA50z') + assert_success response + + assert_equal 'ch_RbPVPWMH2bcGA50z', response.authorization + assert response.test? + end + + def test_failed_void + @gateway.expects(:ssl_request).returns(failed_void_response) + + response = @gateway.void('abc') + assert_failure response + assert_equal Gateway::STANDARD_ERROR_CODE[:processing_error], response.error_code + end + + def test_successful_verify + @gateway.expects(:ssl_request).returns(successful_authorize_response) + @gateway.expects(:ssl_post).returns(successful_verify_response) + + response = @gateway.verify(@credit_card, @options) + assert_success response + + assert_equal 'ch_G9rB74PI3uoDMxAo', response.authorization + assert response.test? + end + + def test_successful_verify_with_failed_void + response = stub_comms(@gateway, :ssl_request) do + @gateway.verify(@credit_card, @options) + end.respond_with(successful_authorize_response, failed_void_response) + assert_success response + assert_equal 'Simulator|Transação de simulação autorizada com sucesso', response.message + end + + def test_sucessful_store + @gateway.expects(:ssl_post).times(2).returns(successful_create_customer_response, successful_store_response) + + response = @gateway.store(@credit_card, @options) + assert_success response + + assert_equal 'cus_N70xAX6S65cMnokB|card_51ElNwYSVJFpRe0g', response.authorization + assert response.test? + end + + def test_gateway_id_fallback + gateway = MundipaggGateway.new(api_key: 'my_api_key', gateway_id: 'abc123') + options = { + order_id: '1', + billing_address: address, + description: 'Store Purchase' + } + stub_comms do + gateway.purchase(@amount, @credit_card, options) + end.check_request do |endpoint, data, headers| + assert_match(/"gateway_affiliation_id":"abc123"/, data) + end.respond_with(successful_purchase_response) + end + + def test_scrub + assert @gateway.supports_scrubbing? + assert_equal @gateway.scrub(pre_scrubbed), post_scrubbed + end + + private + + def test_successful_purchase_with(card) + @gateway.expects(:ssl_post).returns(successful_purchase_response) + + response = @gateway.purchase(@amount, card, @options) + assert_success response + + assert_equal 'ch_90Vjq8TrwfP74XJO', response.authorization + assert response.test? + end + + def pre_scrubbed + %q( + opening connection to api.mundipagg.com:443... + opened + starting SSL for api.mundipagg.com:443... + SSL established + <- "POST /core/v1/charges/ HTTP/1.1\r\nContent-Type: application/json\r\nAuthorization: Basic c2tfdGVzdF9keE1WOE51QnZpajZKNVhuOg==\r\nAccept: application/json\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: api.mundipagg.com\r\nContent-Length: 424\r\n\r\n" + <- "{\"amount\":100,\"currency\":\"USD\",\"customer\":{\"email\":null,\"name\":\"Longbob Longsen\"},\"payment\":{\"payment_method\":\"credit_card\",\"credit_card\":{\"card\":{\"number\":\"4000100011112224\",\"holder_name\":\"Longbob Longsen\",\"exp_month\":9,\"exp_year\":2019,\"cvv\":\"123\",\"billing_address\":{\"street\":\"My Street\",\"number\":\"456\",\"compliment\":\"Apt 1\",\"city\":\"Ottawa\",\"state\":\"ON\",\"country\":\"CA\",\"zip_code\":\"K1C2N6\",\"neighborhood\":\"Sesame Street\"}}}}}" + -> "HTTP/1.1 200 OK\r\n" + -> "Date: Thu, 01 Feb 2018 20:23:19 GMT\r\n" + -> "Content-Type: application/json; charset=utf-8\r\n" + -> "Content-Length: 801\r\n" + -> "Connection: close\r\n" + -> "Cache-Control: no-cache\r\n" + -> "Pragma: no-cache\r\n" + -> "Content-Encoding: gzip\r\n" + -> "Expires: -1\r\n" + -> "Set-Cookie: TS01e8e2cd=0118d560cc62281517c87bb3b52c62fba3f9d13acb485adc69cac121833699beb2a66ca4bfe3e2af65dfe2f67542ec36ff8e41db56; Path=/; Domain=.api.mundipagg.com\r\n" + -> "\r\n" + reading 801 bytes... + -> "\x1F\x8B\b\x00\x00\x00\x00\x00\x04\x00\x95T\xCDn\x9C0\x10\xBEW\xEA; \xCEA2,,lnmRU\xD5\xB6I\x9AM\xAA\xAA\x174k\x9B]\xB7`\x13c\xB2\xD9\xA4\xFB4=\xF4A\xF2b\xB5\xCD\xC2\x82H\x95\xE4\x84\x98\xEF\x9B\xF1\xCC7?\x0Fo\xDF8\x8E\xCB\x88{\xEC\xB8x\x9D\xBE\x17\xBF\xD0\x1C>_G??\xDD\x7F\xBF\xF9\x9A\xBBG\x16\xC7\x82P\xC3\xF8\x18$\x97'\xA7\xC1b6\xDF\x03+Pt\x03\xDB\xB4\t\x004\xC1\x81\x8F#\x8FNH\xE6\x85\x10\x11o\x19\xFB\xB1\x87&\xD3I\x12M!\x0E\t\xEC\x1D\xA1\x105W\xDA\xC9G\xA8\xB1\x94\xC0H:6W\nT]\x99\xE8\x86\xD0\xE6SKI9\xDE\x1A\xF3\xF5\xE2\xD4m#l\v\xCAUZP\xB5\x16ME\x92\x12\xA6R\f\x92\xB8\xFDW\xCC\vn\x80\xFC\xC4C\x81\x87\xFC\xAB\x00\x1D\a\x93c\x7F\xF6\xA3\x8D/\xA9.\xEC\xFF\xC4\xA4%\xD6%y\x19\x11\xD7\x95\x12\x05\x95\x9A\xF6`\f\a\xD1\xEB*\xF5O\xCF/6\xE8j\xB3Y\xD0\xF8\xC3\\$\x8D\x8F\xA6p(\xAC\xEE\x9F\x05_-\xC5\xD21\xDF\x8A\xF2\x0E\xA7\x05\xB0\xDC\x10:\v\xA19\xE375\xB5\"f\x90W\xB4E^Z\xD3+\xAA2z\xAE\x05\xA7\xA6=\x0F;c\xD95\xD5\xE6P\xA9TI\xE0\x15`\xC5\x04\x1FUm\xB0\xF4\xAE\xD8\xC2\xE6v\xC3\xC2\xB3\xEB \x96\xF3\xB2\v\xDA\xF3L\xD5\xB6\xA4O\xB6r4}q\xE0\x87\xD3 \x9Eya\x92\x10/DQ\xE6\xC1\x94\xF8\x1E\xC28\xCE\xA2)\xCEhD;\xD7\xD1\xA0\rF\rC\xA9j\xFD`G\xAFj\x8Cie0%\xEBNR\xC6\xB5K\x9E\x9B\xA13\x90\xDF\x05\xC775\x93T\xA6m\xFF*V\xD49(!\xDD\x11\x05\xB2\x8C\xE5\fl\xAD\xED\x9A\x8DY\xAA)1\fga\x18\x8Da^\xD5\x06\x9E\xA0d\x12=\x01C\xAD\xD6]\xF0Y\x10\xC5\xF1lL*t}\xB0\xB2\x94E\x9B\xEE\xEF+\xDB\x89\xC7\xBF\x8F\x7F\x84C\xA8\xD3\xD4\xD1\xFC\xEA\xA0B\xB2{ \xE0`Q8Z!\x1D@\x8C\xE3J\xAA\xA5<\xD4\x86:\x86(\xA9\x84A\x8Fm\x9E\xC0I\xBA\xD7\xBF\xA3\xDA\xAEw3t\xD8\x1DmN\xEF\xE4\xFC'\x8F.\xD4\xEA\x12\xF3\xFB`\x19\xB7N\x9A\x951\xA9\xE7\xB0bw)a+f{\xE4\x86\b!\x1F\xF5HvV3Q\xCB\x1E\b\x82\xB0GYj\x15\xEC\x83\xDFX\x05=\xFBZ\xE4\xA4\xD7\xE5\xFFl\xA9\xD9\xD3\xBB2-\x04WkM\x9B\r\xCD[\n\xE6\xE8%\xEB\x01\x87I4[pK{\xA1\x9E[\xE3\xD9\x8F\x1E\xF9\xB9E\x1E\x90\x97,\xD7\xB7c\x95\x02!\xB2\x99\xF5No\x9B\x92\xA4\xD4\x86\xF9\xB2u\x16\xCD\xCFQ\x0F\xE7u\xB1\xB4\xE7\xCD\r\xA3\xE9\x00\xB9ge\xD7\xFD\xB9\x7F\x12\x9C\raN\xD9j\xBD\x14r-\x9A\x9B\xBD\xA0\x95\xD6\xF3\xA9'0S\xF6\xE2\x9F+\x05\e\x18@F0\xFB\xC0\xF9\xD9\xD0\xC5l\xB9\xB4^'\xEF\x06\x88.\x95\xA6\xFE>\xDF#\xA7+\xEA\xC8\x19&\xD0\xBA\xEC\x0EB\rO\xD2\x9E\xB1\xEBf\xF5\xC5\rzE{\xBAS\xA7;S\n^\xD1\xC16\xB4\xEA\xEA\bm6\xF6\x18\xBF}\xB3\xFB\aD\t\x1C\xBA\xE0\a\x00\x00" + read 801 bytes + Conn close + ) + end + + def post_scrubbed + %q( + opening connection to api.mundipagg.com:443... + opened + starting SSL for api.mundipagg.com:443... + SSL established + <- "POST /core/v1/charges/ HTTP/1.1\r\nContent-Type: application/json\r\nAuthorization: Basic [FILTERED]==\r\nAccept: application/json\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: api.mundipagg.com\r\nContent-Length: 424\r\n\r\n" + <- "{\"amount\":100,\"currency\":\"USD\",\"customer\":{\"email\":null,\"name\":\"Longbob Longsen\"},\"payment\":{\"payment_method\":\"credit_card\",\"credit_card\":{\"card\":{\"number\":\"[FILTERED]\",\"holder_name\":\"Longbob Longsen\",\"exp_month\":9,\"exp_year\":2019,\"cvv\":\"[FILTERED]\",\"billing_address\":{\"street\":\"My Street\",\"number\":\"456\",\"compliment\":\"Apt 1\",\"city\":\"Ottawa\",\"state\":\"ON\",\"country\":\"CA\",\"zip_code\":\"K1C2N6\",\"neighborhood\":\"Sesame Street\"}}}}}" + -> "HTTP/1.1 200 OK\r\n" + -> "Date: Thu, 01 Feb 2018 20:23:19 GMT\r\n" + -> "Content-Type: application/json; charset=utf-8\r\n" + -> "Content-Length: 801\r\n" + -> "Connection: close\r\n" + -> "Cache-Control: no-cache\r\n" + -> "Pragma: no-cache\r\n" + -> "Content-Encoding: gzip\r\n" + -> "Expires: -1\r\n" + -> "Set-Cookie: TS01e8e2cd=0118d560cc62281517c87bb3b52c62fba3f9d13acb485adc69cac121833699beb2a66ca4bfe3e2af65dfe2f67542ec36ff8e41db56; Path=/; Domain=.api.mundipagg.com\r\n" + -> "\r\n" + reading 801 bytes... + -> "\x1F\x8B\b\x00\x00\x00\x00\x00\x04\x00\x95T\xCDn\x9C0\x10\xBEW\xEA; \xCEA2,,lnmRU\xD5\xB6I\x9AM\xAA\xAA\x174k\x9B]\xB7`\x13c\xB2\xD9\xA4\xFB4=\xF4A\xF2b\xB5\xCD\xC2\x82H\x95\xE4\x84\x98\xEF\x9B\xF1\xCC7?\x0Fo\xDF8\x8E\xCB\x88{\xEC\xB8x\x9D\xBE\x17\xBF\xD0\x1C>_G??\xDD\x7F\xBF\xF9\x9A\xBBG\x16\xC7\x82P\xC3\xF8\x18$\x97'\xA7\xC1b6\xDF\x03+Pt\x03\xDB\xB4\t\x004\xC1\x81\x8F#\x8FNH\xE6\x85\x10\x11o\x19\xFB\xB1\x87&\xD3I\x12M!\x0E\t\xEC\x1D\xA1\x105W\xDA\xC9G\xA8\xB1\x94\xC0H:6W\nT]\x99\xE8\x86\xD0\xE6SKI9\xDE\x1A\xF3\xF5\xE2\xD4m#l\v\xCAUZP\xB5\x16ME\x92\x12\xA6R\f\x92\xB8\xFDW\xCC\vn\x80\xFC\xC4C\x81\x87\xFC\xAB\x00\x1D\a\x93c\x7F\xF6\xA3\x8D/\xA9.\xEC\xFF\xC4\xA4%\xD6%y\x19\x11\xD7\x95\x12\x05\x95\x9A\xF6`\f\a\xD1\xEB*\xF5O\xCF/6\xE8j\xB3Y\xD0\xF8\xC3\\$\x8D\x8F\xA6p(\xAC\xEE\x9F\x05_-\xC5\xD21\xDF\x8A\xF2\x0E\xA7\x05\xB0\xDC\x10:\v\xA19\xE375\xB5\"f\x90W\xB4E^Z\xD3+\xAA2z\xAE\x05\xA7\xA6=\x0F;c\xD95\xD5\xE6P\xA9TI\xE0\x15`\xC5\x04\x1FUm\xB0\xF4\xAE\xD8\xC2\xE6v\xC3\xC2\xB3\xEB \x96\xF3\xB2\v\xDA\xF3L\xD5\xB6\xA4O\xB6r4}q\xE0\x87\xD3 \x9Eya\x92\x10/DQ\xE6\xC1\x94\xF8\x1E\xC28\xCE\xA2)\xCEhD;\xD7\xD1\xA0\rF\rC\xA9j\xFD`G\xAFj\x8Cie0%\xEBNR\xC6\xB5K\x9E\x9B\xA13\x90\xDF\x05\xC775\x93T\xA6m\xFF*V\xD49(!\xDD\x11\x05\xB2\x8C\xE5\fl\xAD\xED\x9A\x8DY\xAA)1\fga\x18\x8Da^\xD5\x06\x9E\xA0d\x12=\x01C\xAD\xD6]\xF0Y\x10\xC5\xF1lL*t}\xB0\xB2\x94E\x9B\xEE\xEF+\xDB\x89\xC7\xBF\x8F\x7F\x84C\xA8\xD3\xD4\xD1\xFC\xEA\xA0B\xB2{ \xE0`Q8Z!\x1D@\x8C\xE3J\xAA\xA5<\xD4\x86:\x86(\xA9\x84A\x8Fm\x9E\xC0I\xBA\xD7\xBF\xA3\xDA\xAEw3t\xD8\x1DmN\xEF\xE4\xFC'\x8F.\xD4\xEA\x12\xF3\xFB`\x19\xB7N\x9A\x951\xA9\xE7\xB0bw)a+f{\xE4\x86\b!\x1F\xF5HvV3Q\xCB\x1E\b\x82\xB0GYj\x15\xEC\x83\xDFX\x05=\xFBZ\xE4\xA4\xD7\xE5\xFFl\xA9\xD9\xD3\xBB2-\x04WkM\x9B\r\xCD[\n\xE6\xE8%\xEB\x01\x87I4[pK{\xA1\x9E[\xE3\xD9\x8F\x1E\xF9\xB9E\x1E\x90\x97,\xD7\xB7c\x95\x02!\xB2\x99\xF5No\x9B\x92\xA4\xD4\x86\xF9\xB2u\x16\xCD\xCFQ\x0F\xE7u\xB1\xB4\xE7\xCD\r\xA3\xE9\x00\xB9ge\xD7\xFD\xB9\x7F\x12\x9C\raN\xD9j\xBD\x14r-\x9A\x9B\xBD\xA0\x95\xD6\xF3\xA9'0S\xF6\xE2\x9F+\x05\e\x18@F0\xFB\xC0\xF9\xD9\xD0\xC5l\xB9\xB4^'\xEF\x06\x88.\x95\xA6\xFE>\xDF#\xA7+\xEA\xC8\x19&\xD0\xBA\xEC\x0EB\rO\xD2\x9E\xB1\xEBf\xF5\xC5\rzE{\xBAS\xA7;S\n^\xD1\xC16\xB4\xEA\xEA\bm6\xF6\x18\xBF}\xB3\xFB\aD\t\x1C\xBA\xE0\a\x00\x00" + read 801 bytes + Conn close + ) + end + + def successful_purchase_response + %( + { + "id": "ch_90Vjq8TrwfP74XJO", + "code": "ME0KIN4A0O", + "gateway_id": "162bead8-23a0-4708-b687-078a69a1aa7c", + "amount": 100, + "paid_amount": 100, + "status": "paid", + "currency": "USD", + "payment_method": "credit_card", + "paid_at": "2018-02-01T18:41:05Z", + "created_at": "2018-02-01T18:41:04Z", + "updated_at": "2018-02-01T18:41:04Z", + "customer": { + "id": "cus_VxJX2NmTqyUnXgL9", + "name": "Longbob Longsen", + "email": "", + "delinquent": false, + "created_at": "2018-02-01T18:41:04Z", + "updated_at": "2018-02-01T18:41:04Z", + "phones": {} + }, + "last_transaction": { + "id": "tran_JNzjzadcVZHlG8K2", + "transaction_type": "credit_card", + "gateway_id": "c579c8fa-53d7-41a8-b4cc-a03c712ebbb7", + "amount": 100, + "status": "captured", + "success": true, + "installments": 1, + "acquirer_name": "simulator", + "acquirer_affiliation_code": "", + "acquirer_tid": "198548", + "acquirer_nsu": "866277", + "acquirer_auth_code": "713736", + "acquirer_message": "Simulator|Transação de simulação autorizada com sucesso", + "acquirer_return_code": "0", + "operation_type": "auth_and_capture", + "card": { + "id": "card_pD02Q6WtOTB7a3kE", + "first_six_digits": "400010", + "last_four_digits": "2224", + "brand": "Visa", + "holder_name": "Longbob Longsen", + "exp_month": 9, + "exp_year": 2019, + "status": "active", + "created_at": "2018-02-01T18:41:04Z", + "updated_at": "2018-02-01T18:41:04Z", + "billing_address": { + "street": "My Street", + "number": "456", + "zip_code": "K1C2N6", + "neighborhood": "Sesame Street", + "city": "Ottawa", + "state": "ON", + "country": "CA", + "line_1": "456, My Street, Sesame Street" + }, + "type": "credit" + }, + "created_at": "2018-02-01T18:41:04Z", + "updated_at": "2018-02-01T18:41:04Z", + "gateway_response": { + "code": "201" + } + } + } + ) + end + + def failed_purchase_response + %( + { + "id": "ch_ykXLG3RfVfNE4dZe", + "code": "3W80HGVS0R", + "gateway_id": "79ae6732-1b60-4008-80f5-0d1be8ec41a7", + "amount": 105200, + "status": "failed", + "currency": "USD", + "payment_method": "credit_card", + "created_at": "2018-02-01T18:42:44Z", + "updated_at": "2018-02-01T18:42:45Z", + "customer": { + "id": "cus_0JnywlzI3hV6ZNe2", + "name": "Longbob Longsen", + "email": "", + "delinquent": false, + "created_at": "2018-02-01T18:42:44Z", + "updated_at": "2018-02-01T18:42:44Z", + "phones": {} + }, + "last_transaction": { + "id": "tran_nVx8730IjhOR8PD2", + "transaction_type": "credit_card", + "gateway_id": "f3993413-73a0-4e8d-a7bc-eb3ed198c770", + "amount": 105200, + "status": "not_authorized", + "success": false, + "installments": 1, + "acquirer_name": "simulator", + "acquirer_affiliation_code": "", + "acquirer_message": "Simulator|Transação de simulada negada por falta de crédito, utilizado para realizar simulação de autorização parcial.", + "acquirer_return_code": "92", + "operation_type": "auth_and_capture", + "card": { + "id": "card_VrxnWlrsOHOpzMj5", + "first_six_digits": "400030", + "last_four_digits": "2220", + "brand": "Visa", + "holder_name": "Longbob Longsen", + "exp_month": 9, + "exp_year": 2019, + "status": "active", + "created_at": "2018-02-01T18:42:44Z", + "updated_at": "2018-02-01T18:42:44Z", + "billing_address": { + "street": "My Street", + "number": "456", + "zip_code": "K1C2N6", + "neighborhood": "Sesame Street", + "city": "Ottawa", + "state": "ON", + "country": "CA", + "line_1": "456, My Street, Sesame Street" + }, + "type": "credit" + }, + "created_at": "2018-02-01T18:42:44Z", + "updated_at": "2018-02-01T18:42:44Z", + "gateway_response": { + "code": "201" + } + } + } + ) + end + + def successful_authorize_response + %( + { + "id": "ch_gm5wrlGMI2Fb0x6K", + "code": "K1J5B1YFLE", + "gateway_id": "3b6c0f72-c4b3-48b2-8eb7-2424321a6c93", + "amount": 100, + "status": "pending", + "currency": "USD", + "payment_method": "credit_card", + "created_at": "2018-02-01T16:43:30Z", + "updated_at": "2018-02-01T16:43:30Z", + "customer": { + "id": "cus_bVWYqeTmpu9VYLd9", + "name": "Longbob Longsen", + "email": "", + "delinquent": false, + "created_at": "2018-02-01T16:43:30Z", + "updated_at": "2018-02-01T16:43:30Z", + "phones": {} + }, + "last_transaction": { + "id": "tran_DWJlEApZI9UL2qR9", + "transaction_type": "credit_card", + "gateway_id": "6dae95a7-6b7f-4431-be33-cb3ecf21287a", + "amount": 100, + "status": "authorized_pending_capture", + "success": true, + "installments": 1, + "acquirer_name": "simulator", + "acquirer_affiliation_code": "", + "acquirer_tid": "25970", + "acquirer_nsu": "506128", + "acquirer_auth_code": "523448", + "acquirer_message": "Simulator|Transação de simulação autorizada com sucesso", + "acquirer_return_code": "0", + "operation_type": "auth_only", + "card": { + "id": "card_J26O3K2hvPc2vOQG", + "first_six_digits": "400010", + "last_four_digits": "2224", + "brand": "Visa", + "holder_name": "Longbob Longsen", + "exp_month": 9, + "exp_year": 2019, + "status": "active", + "created_at": "2018-02-01T16:43:30Z", + "updated_at": "2018-02-01T16:43:30Z", + "billing_address": { + "street": "My Street", + "number": "456", + "zip_code": "K1C2N6", + "neighborhood": "Sesame Street", + "city": "Ottawa", + "state": "ON", + "country": "CA", + "line_1": "456, My Street, Sesame Street" + }, + "type": "credit" + }, + "created_at": "2018-02-01T16:43:31Z", + "updated_at": "2018-02-01T16:43:31Z", + "gateway_response": { + "code": "201" + } + } + } + ) + end + + def failed_authorize_response + %( + { + "id": "ch_O4bW13ukwF5XpmLg", + "code": "2KW1C5VSZO", + "gateway_id": "9bf24ea7-e913-44bc-92ca-50491ffcd7a1", + "amount": 105200, + "status": "failed", + "currency": "USD", + "payment_method": "credit_card", + "created_at": "2018-02-01T18:46:06Z", + "updated_at": "2018-02-01T18:46:06Z", + "customer": { + "id": "cus_7VGAGxqI4OUwZ392", + "name": "Longbob Longsen", + "email": "", + "delinquent": false, + "created_at": "2018-02-01T18:46:06Z", + "updated_at": "2018-02-01T18:46:06Z", + "phones": {} + }, + "last_transaction": { + "id": "tran_g0JYdDXcDesqd36E", + "transaction_type": "credit_card", + "gateway_id": "c0896dd6-0d5c-4e8b-9d92-df5a70e3fb76", + "amount": 105200, + "status": "not_authorized", + "success": false, + "installments": 1, + "acquirer_name": "simulator", + "acquirer_affiliation_code": "", + "acquirer_message": "Simulator|Transação de simulada negada por falta de crédito, utilizado para realizar simulação de autorização parcial.", + "acquirer_return_code": "92", + "operation_type": "auth_only", + "card": { + "id": "card_LR0A1vcVbsmY3wzY", + "first_six_digits": "400030", + "last_four_digits": "2220", + "brand": "Visa", + "holder_name": "Longbob Longsen", + "exp_month": 9, + "exp_year": 2019, + "status": "active", + "created_at": "2018-02-01T18:46:06Z", + "updated_at": "2018-02-01T18:46:06Z", + "billing_address": { + "street": "My Street", + "number": "456", + "zip_code": "K1C2N6", + "neighborhood": "Sesame Street", + "city": "Ottawa", + "state": "ON", + "country": "CA", + "line_1": "456, My Street, Sesame Street" + }, + "type": "credit" + }, + "created_at": "2018-02-01T18:46:06Z", + "updated_at": "2018-02-01T18:46:06Z", + "gateway_response": { + "code": "201" + } + } + } + ) + end + + def successful_capture_response + %( + { + "id": "ch_gm5wrlGMI2Fb0x6K", + "code": "ch_gm5wrlGMI2Fb0x6K", + "gateway_id": "3b6c0f72-c4b3-48b2-8eb7-2424321a6c93", + "amount": 100, + "paid_amount": 100, + "status": "paid", + "currency": "USD", + "payment_method": "credit_card", + "paid_at": "2018-02-01T16:43:33Z", + "created_at": "2018-02-01T16:43:30Z", + "updated_at": "2018-02-01T16:43:33Z", + "customer": { + "id": "cus_bVWYqeTmpu9VYLd9", + "name": "Longbob Longsen", + "email": "", + "delinquent": false, + "created_at": "2018-02-01T16:43:30Z", + "updated_at": "2018-02-01T16:43:30Z", + "phones": {} + }, + "last_transaction": { + "id": "tran_wL9APd6cws19WNJ7", + "transaction_type": "credit_card", + "gateway_id": "6dae95a7-6b7f-4431-be33-cb3ecf21287a", + "amount": 100, + "status": "captured", + "success": true, + "installments": 1, + "acquirer_name": "simulator", + "acquirer_affiliation_code": "", + "acquirer_tid": "299257", + "acquirer_nsu": "894685", + "acquirer_auth_code": "523448", + "acquirer_message": "Simulator|Transação de simulação capturada com sucesso", + "acquirer_return_code": "0", + "operation_type": "capture", + "card": { + "id": "card_J26O3K2hvPc2vOQG", + "first_six_digits": "400010", + "last_four_digits": "2224", + "brand": "Visa", + "holder_name": "Longbob Longsen", + "exp_month": 9, + "exp_year": 2019, + "status": "active", + "created_at": "2018-02-01T16:43:30Z", + "updated_at": "2018-02-01T16:43:30Z", + "billing_address": { + "street": "My Street", + "number": "456", + "zip_code": "K1C2N6", + "neighborhood": "Sesame Street", + "city": "Ottawa", + "state": "ON", + "country": "CA", + "line_1": "456, My Street, Sesame Street" + }, + "type": "credit" + }, + "created_at": "2018-02-01T16:43:33Z", + "updated_at": "2018-02-01T16:43:33Z", + "gateway_response": { + "code": "200" + } + } + } + ) + end + + def failed_capture_response + '{"message": "Charge not found."}' + end + + def successful_refund_response + %( + { + "id": "ch_RbPVPWMH2bcGA50z", + "code": "O5L5A4VCRK", + "gateway_id": "d77c6a32-e1c8-42d4-bd1b-e92b36f054f9", + "amount": 100, + "canceled_amount": 100, + "status": "canceled", + "currency": "USD", + "payment_method": "credit_card", + "canceled_at": "2018-02-01T16:34:07Z", + "created_at": "2018-02-01T16:34:07Z", + "updated_at": "2018-02-01T16:34:07Z", + "customer": { + "id": "cus_odYDGxQirlcp693a", + "name": "Longbob Longsen", + "email": "", + "delinquent": false, + "created_at": "2018-02-01T16:34:07Z", + "updated_at": "2018-02-01T16:34:07Z", + "phones": {} + }, + "last_transaction": { + "id": "tran_m1prZBNTgUmZrGzZ", + "transaction_type": "credit_card", + "gateway_id": "23648dca-07dc-4f31-9b24-26aa702dc7e8", + "amount": 100, + "status": "voided", + "success": true, + "acquirer_name": "simulator", + "acquirer_affiliation_code": "", + "acquirer_tid": "489627", + "acquirer_nsu": "174061", + "acquirer_auth_code": "433589", + "acquirer_return_code": "0", + "operation_type": "cancel", + "card": { + "id": "card_8PaGBMOhXwi9Q24z", + "first_six_digits": "400010", + "last_four_digits": "2224", + "brand": "Visa", + "holder_name": "Longbob Longsen", + "exp_month": 9, + "exp_year": 2019, + "status": "active", + "created_at": "2018-02-01T16:34:07Z", + "updated_at": "2018-02-01T16:34:07Z", + "billing_address": { + "street": "My Street", + "number": "456", + "zip_code": "K1C2N6", + "neighborhood": "Sesame Street", + "city": "Ottawa", + "state": "ON", + "country": "CA", + "line_1": "456, My Street, Sesame Street" + }, + "type": "credit" + }, + "created_at": "2018-02-01T16:34:07Z", + "updated_at": "2018-02-01T16:34:07Z", + "gateway_response": { + "code": "200" + } + } + } + ) + end + + def failed_refund_response + '{"message": "Charge not found."}' + end + + def successful_void_response + %( + { + "id": "ch_RbPVPWMH2bcGA50z", + "code": "O5L5A4VCRK", + "gateway_id": "d77c6a32-e1c8-42d4-bd1b-e92b36f054f9", + "amount": 100, + "canceled_amount": 100, + "status": "canceled", + "currency": "USD", + "payment_method": "credit_card", + "canceled_at": "2018-02-01T16:34:07Z", + "created_at": "2018-02-01T16:34:07Z", + "updated_at": "2018-02-01T16:34:07Z", + "customer": { + "id": "cus_odYDGxQirlcp693a", + "name": "Longbob Longsen", + "email": "", + "delinquent": false, + "created_at": "2018-02-01T16:34:07Z", + "updated_at": "2018-02-01T16:34:07Z", + "phones": {} + }, + "last_transaction": { + "id": "tran_m1prZBNTgUmZrGzZ", + "transaction_type": "credit_card", + "gateway_id": "23648dca-07dc-4f31-9b24-26aa702dc7e8", + "amount": 100, + "status": "voided", + "success": true, + "acquirer_name": "simulator", + "acquirer_affiliation_code": "", + "acquirer_tid": "489627", + "acquirer_nsu": "174061", + "acquirer_auth_code": "433589", + "acquirer_return_code": "0", + "operation_type": "cancel", + "card": { + "id": "card_8PaGBMOhXwi9Q24z", + "first_six_digits": "400010", + "last_four_digits": "2224", + "brand": "Visa", + "holder_name": "Longbob Longsen", + "exp_month": 9, + "exp_year": 2019, + "status": "active", + "created_at": "2018-02-01T16:34:07Z", + "updated_at": "2018-02-01T16:34:07Z", + "billing_address": { + "street": "My Street", + "number": "456", + "zip_code": "K1C2N6", + "neighborhood": "Sesame Street", + "city": "Ottawa", + "state": "ON", + "country": "CA", + "line_1": "456, My Street, Sesame Street" + }, + "type": "credit" + }, + "created_at": "2018-02-01T16:34:07Z", + "updated_at": "2018-02-01T16:34:07Z", + "gateway_response": { + "code": "200" + } + } + } + ) + end + + def failed_void_response + '{"message": "Charge not found."}' + end + + def successful_verify_response + %( + { + "id": "ch_G9rB74PI3uoDMxAo", + "code": "8EXXFEBQDK", + "gateway_id": "4b228be6-6795-416a-9238-204020e7fdd1", + "amount": 100, + "canceled_amount": 100, + "status": "canceled", + "currency": "USD", + "payment_method": "credit_card", + "canceled_at": "2018-02-02T14:25:25Z", + "created_at": "2018-02-02T14:25:24Z", + "updated_at": "2018-02-02T14:25:25Z", + "customer": { + "id": "cus_V2GXxeOunSYpEvOD", + "name": "Longbob Longsen", + "email": "", + "delinquent": false, + "created_at": "2018-02-02T14:25:24Z", + "updated_at": "2018-02-02T14:25:24Z", + "phones": {} + }, + "last_transaction": { + "id": "tran_r50yVePSJbuA3dKb", + "transaction_type": "credit_card", + "gateway_id": "2d1c155a-e89d-4972-9ee1-a3b3f56d6ff8", + "amount": 100, + "status": "voided", + "success": true, + "acquirer_name": "simulator", + "acquirer_affiliation_code": "", + "acquirer_tid": "711106", + "acquirer_nsu": "553868", + "acquirer_auth_code": "271719", + "acquirer_return_code": "0", + "operation_type": "cancel", + "card": { + "id": "card_7WraOEps6FpRLQob", + "first_six_digits": "400010", + "last_four_digits": "2224", + "brand": "Visa", + "holder_name": "Longbob Longsen", + "exp_month": 9, + "exp_year": 2019, + "status": "active", + "created_at": "2018-02-02T14:25:24Z", + "updated_at": "2018-02-02T14:25:24Z", + "billing_address": { + "street": "My Street", + "number": "456", + "zip_code": "K1C2N6", + "neighborhood": "Sesame Street", + "city": "Ottawa", + "state": "ON", + "country": "CA", + "line_1": "456, My Street, Sesame Street" + }, + "type": "credit" + }, + "created_at": "2018-02-02T14:25:25Z", + "updated_at": "2018-02-02T14:25:25Z", + "gateway_response": { + "code": "200" + } + } + } + ) + end + + def successful_create_customer_response + %( + { + "id": "cus_NRL1bw3HpAHLbWPQ", + "name": "Sideshow Bob", + "email": "", + "delinquent": false, + "created_at": "2018-02-05T15:03:39Z", + "updated_at": "2018-02-05T15:03:39Z", + "phones": {} + } + ) + end + + def successful_store_response + %( + { + "id": "card_51ElNwYSVJFpRe0g", + "first_six_digits": "400010", + "last_four_digits": "2224", + "brand": "Visa", + "holder_name": "Longbob Longsen", + "exp_month": 9, + "exp_year": 2019, + "status": "active", + "created_at": "2018-02-05T15:45:01Z", + "updated_at": "2018-02-05T15:45:01Z", + "billing_address": { + "street": "My Street", + "number": "456", + "zip_code": "K1C2N6", + "neighborhood": "Sesame Street", + "city": "Ottawa", + "state": "ON", + "country": "CA", + "line_1": "456, My Street, Sesame Street" + }, + "customer": { + "id": "cus_N70xAX6S65cMnokB", + "name": "Bob Belcher", + "email": "", + "delinquent": false, + "created_at": "2018-02-05T15:45:01Z", + "updated_at": "2018-02-05T15:45:01Z", + "phones": {} + }, + "type": "credit" + } + ) + end +end diff --git a/test/unit/gateways/nab_transact_test.rb b/test/unit/gateways/nab_transact_test.rb index 126d9cde318..d02ebadce3a 100644 --- a/test/unit/gateways/nab_transact_test.rb +++ b/test/unit/gateways/nab_transact_test.rb @@ -1,6 +1,8 @@ require 'test_helper' class NabTransactTest < Test::Unit::TestCase + include CommStub + def setup @gateway = NabTransactGateway.new( :login => 'login', @@ -16,11 +18,10 @@ def setup } end - def test_successful_purchase @gateway.expects(:ssl_post).with(&check_transaction_type(:purchase)).returns(successful_purchase_response) - assert response = @gateway.purchase(@amount, @credit_card, @options) + response = @gateway.purchase(@amount, @credit_card, @options) assert_instance_of Response, response assert_success response @@ -28,37 +29,73 @@ def test_successful_purchase assert response.test? end + def test_successful_purchase_with_merchant_descriptor + name, location = 'Active Merchant', 'USA' + + response = assert_metadata(name, location) do + response = @gateway.purchase(@amount, @credit_card, @options.merge(:merchant_name => name, :merchant_location => location)) + end + + assert response + assert_instance_of Response, response + assert_success response + end + def test_successful_authorize @gateway.expects(:ssl_post).with(&check_transaction_type(:authorization)).returns(successful_authorize_response) - assert response = @gateway.authorize(@amount, @credit_card, @options) + response = @gateway.authorize(@amount, @credit_card, @options) assert_equal '009887*test*009887*200', response.authorization assert response.test? end + def test_successful_authorize_with_merchant_descriptor + name, location = 'Active Merchant', 'USA' + + response = assert_metadata(name, location) do + response = @gateway.authorize(@amount, @credit_card, @options.merge(:merchant_name => name, :merchant_location => location)) + end + + assert response + assert_instance_of Response, response + assert_success response + end + def test_successful_capture @gateway.expects(:ssl_post).with(&check_transaction_type(:capture)).returns(successful_purchase_response) - assert response = @gateway.capture(@amount, '009887*test*009887*200') + response = @gateway.capture(@amount, '009887*test*009887*200') assert_equal '009887*test**200', response.authorization assert response.test? end + def test_successful_capture_with_merchant_descriptor + name, location = 'Active Merchant', 'USA' + + response = assert_metadata(name, location) do + response = @gateway.capture(@amount, '009887*test*009887*200', @options.merge(:merchant_name => name, :merchant_location => location)) + end + + assert response + assert_instance_of Response, response + assert_success response + end + def test_unsuccessful_purchase @gateway.expects(:ssl_post).with(&check_transaction_type(:purchase)).returns(failed_purchase_response) - assert response = @gateway.purchase(@amount, @credit_card, @options) + response = @gateway.purchase(@amount, @credit_card, @options) assert_instance_of Response, response assert_failure response assert response.test? - assert_equal "Expired Card", response.message + assert_equal 'Expired Card', response.message end def test_failed_login @gateway.expects(:ssl_post).returns(failed_login_response) - assert response = @gateway.purchase(@amount, @credit_card, @options) + response = @gateway.purchase(@amount, @credit_card, @options) assert_instance_of Response, response assert_failure response - assert_equal "Invalid merchant ID", response.message + assert_equal 'Invalid merchant ID', response.message end def test_supported_countries @@ -71,19 +108,112 @@ def test_supported_card_types def test_successful_refund @gateway.expects(:ssl_post).with(&check_transaction_type(:refund)).returns(successful_refund_response) - assert_success @gateway.refund(@amount, "009887", {:order_id => '1'}) + assert_success @gateway.refund(@amount, '009887', {:order_id => '1'}) + end + + def test_successful_refund_with_merchant_descriptor + name, location = 'Active Merchant', 'USA' + + response = assert_metadata(name, location) do + response = @gateway.refund(@amount, '009887', {:order_id => '1', :merchant_name => name, :merchant_location => location}) + end + + assert response + assert_instance_of Response, response + assert_success response + end + + def test_successful_credit + @gateway.expects(:ssl_post).with(&check_transaction_type(:unmatched_refund)).returns(successful_refund_response) + assert_success @gateway.credit(@amount, @credit_card, {:order_id => '1'}) end def test_failed_refund @gateway.expects(:ssl_post).with(&check_transaction_type(:refund)).returns(failed_refund_response) - assert response = @gateway.refund(@amount, "009887", {:order_id => '1'}) + response = @gateway.refund(@amount, '009887', {:order_id => '1'}) assert_failure response - assert_equal "Only $1.00 available for refund", response.message + assert_equal 'Only $1.00 available for refund', response.message + end + + def test_request_timeout_default + stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, @credit_card, @options) + end.check_request do |method, endpoint, data, headers| + assert_match(/<timeoutValue>60/, data) + end.respond_with(successful_purchase_response) + end + + def test_override_request_timeout + gateway = NabTransactGateway.new(login: 'login', password: 'password', request_timeout: 44) + stub_comms(gateway, :ssl_request) do + gateway.purchase(@amount, @credit_card, @options) + end.check_request do |method, endpoint, data, headers| + assert_match(/<timeoutValue>44/, data) + end.respond_with(successful_purchase_response) + end + + def test_nonfractional_currencies + stub_comms(@gateway, :ssl_request) do + @gateway.authorize(10000, @credit_card, @options.merge(currency: 'JPY')) + end.check_request do |method, endpoint, data, headers| + assert_match(/<amount>100<\/amount>/, data) + end.respond_with(successful_authorize_response) + end + + def test_scrub + assert @gateway.supports_scrubbing? + assert_equal @gateway.scrub(pre_scrubbed), post_scrubbed end private + def pre_scrubbed + <<-'PRE_SCRUBBED' +opening connection to transact.nab.com.au:443... +opened +starting SSL for transact.nab.com.au:443... +SSL established +<- "POST /test/xmlapi/payment HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: transact.nab.com.au\r\nContent-Length: 715\r\n\r\n" +<- "<?xml version=\"1.0\" encoding=\"UTF-8\"?><NABTransactMessage><MessageInfo><messageID>6673348a21d79657983ab247b2483e</messageID><messageTimestamp>20151212075932886818+000</messageTimestamp><timeoutValue>60</timeoutValue><apiVersion>xml-4.2</apiVersion></MessageInfo><MerchantInfo><merchantID>XYZ0010</merchantID><password>abcd1234</password></MerchantInfo><RequestType>Payment</RequestType><Payment><TxnList count=\"1\"><Txn ID=\"1\"><txnType>0</txnType><txnSource>23</txnSource><amount>200</amount><currency>AUD</currency><purchaseOrderNo></purchaseOrderNo><CreditCardInfo><cardNumber>4444333322221111</cardNumber><expiryDate>05/17</expiryDate><cvv>111</cvv></CreditCardInfo></Txn></TxnList></Payment></NABTransactMessage>" +-> "HTTP/1.1 200 OK\r\n" +-> "Date: Sat, 12 Dec 2015 07:59:34 GMT\r\n" +-> "Server: Apache-Coyote/1.1\r\n" +-> "Content-Type: text/xml;charset=ISO-8859-1\r\n" +-> "Content-Length: 920\r\n" +-> "Connection: close\r\n" +-> "\r\n" +reading 920 bytes... +-> "" +-> "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?><NABTransactMessage><MessageInfo><messageID>6673348a21d79657983ab247b2483e</messageID><messageTimestamp>20151212185934964000+660</messageTimestamp><apiVersion>xml-4.2</apiVersion></MessageInfo><RequestType>Payment</RequestType><MerchantInfo><merchantID>XYZ0010</merchantID></MerchantInfo><Status><statusCode>000</statusCode><statusDescription>Normal</statusDescription></Status><Payment><TxnList count=\"1\"><Txn ID=\"1\"><txnType>0</txnType><txnSource>23</txnSource><amount>200</amount><currency>AUD</currency><purchaseOrderNo/><approved>No</approved><responseCode>103</responseCode><responseText>Invalid Purchase Order Number</responseText><settlementDate/><txnID/><authID/><CreditCardInfo><pan>444433...111</pan><expiryDate>05/17</expiryDate><cardType>6</cardType><cardDescription>Visa</cardDescription></CreditCardInfo></Txn></TxnList></Payment></NABTransactMessage>" +read 920 bytes +Conn close + PRE_SCRUBBED + end + + def post_scrubbed + <<-'POST_SCRUBBED' +opening connection to transact.nab.com.au:443... +opened +starting SSL for transact.nab.com.au:443... +SSL established +<- "POST /test/xmlapi/payment HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: transact.nab.com.au\r\nContent-Length: 715\r\n\r\n" +<- "<?xml version=\"1.0\" encoding=\"UTF-8\"?><NABTransactMessage><MessageInfo><messageID>6673348a21d79657983ab247b2483e</messageID><messageTimestamp>20151212075932886818+000</messageTimestamp><timeoutValue>60</timeoutValue><apiVersion>xml-4.2</apiVersion></MessageInfo><MerchantInfo><merchantID>XYZ0010</merchantID><password>[FILTERED]</password></MerchantInfo><RequestType>Payment</RequestType><Payment><TxnList count=\"1\"><Txn ID=\"1\"><txnType>0</txnType><txnSource>23</txnSource><amount>200</amount><currency>AUD</currency><purchaseOrderNo></purchaseOrderNo><CreditCardInfo><cardNumber>[FILTERED]</cardNumber><expiryDate>05/17</expiryDate><cvv>[FILTERED]</cvv></CreditCardInfo></Txn></TxnList></Payment></NABTransactMessage>" +-> "HTTP/1.1 200 OK\r\n" +-> "Date: Sat, 12 Dec 2015 07:59:34 GMT\r\n" +-> "Server: Apache-Coyote/1.1\r\n" +-> "Content-Type: text/xml;charset=ISO-8859-1\r\n" +-> "Content-Length: 920\r\n" +-> "Connection: close\r\n" +-> "\r\n" +reading 920 bytes... +-> "" +-> "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?><NABTransactMessage><MessageInfo><messageID>6673348a21d79657983ab247b2483e</messageID><messageTimestamp>20151212185934964000+660</messageTimestamp><apiVersion>xml-4.2</apiVersion></MessageInfo><RequestType>Payment</RequestType><MerchantInfo><merchantID>XYZ0010</merchantID></MerchantInfo><Status><statusCode>000</statusCode><statusDescription>Normal</statusDescription></Status><Payment><TxnList count=\"1\"><Txn ID=\"1\"><txnType>0</txnType><txnSource>23</txnSource><amount>200</amount><currency>AUD</currency><purchaseOrderNo/><approved>No</approved><responseCode>103</responseCode><responseText>Invalid Purchase Order Number</responseText><settlementDate/><txnID/><authID/><CreditCardInfo><pan>444433...111</pan><expiryDate>05/17</expiryDate><cardType>6</cardType><cardDescription>Visa</cardDescription></CreditCardInfo></Txn></TxnList></Payment></NABTransactMessage>" +read 920 bytes +Conn close + POST_SCRUBBED + end + def check_transaction_type(type) Proc.new do |endpoint, data, headers| request_hash = Hash.from_xml(data) @@ -91,13 +221,27 @@ def check_transaction_type(type) end end + def valid_metadata(name, location) + return <<-XML.gsub(/^\s{4}/, '').gsub(/\n/, '') + <metadata><meta name="ca_name" value="#{name}"/><meta name="ca_location" value="#{location}"/></metadata> + XML + end + + def assert_metadata(name, location, &block) + stub_comms(@gateway, :ssl_request) do + yield + end.check_request do |method, endpoint, data, headers| + metadata_matcher = Regexp.escape(valid_metadata(name, location)) + assert_match %r{#{metadata_matcher}}, data + end.respond_with(successful_purchase_response) + end def failed_login_response '<NABTransactMessage><Status><statusCode>504</statusCode><statusDescription>Invalid merchant ID</statusDescription></Status></NABTransactMessage>' end def successful_purchase_response - <<-XML.gsub(/^\s{4}/,'') + <<-XML.gsub(/^\s{4}/, '') <?xml version="1.0" encoding="UTF-8"?> <NABTransactMessage> <MessageInfo> @@ -140,7 +284,7 @@ def successful_purchase_response end def failed_purchase_response - <<-XML.gsub(/^\s{4}/,'') + <<-XML.gsub(/^\s{4}/, '') <?xml version="1.0" encoding="UTF-8"?> <NABTransactMessage> <MessageInfo> @@ -183,7 +327,7 @@ def failed_purchase_response end def successful_authorize_response - <<-XML.gsub(/^\s{4}/,'') + <<-XML.gsub(/^\s{4}/, '') <?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?> <NABTransactMessage> <MessageInfo> @@ -228,7 +372,7 @@ def successful_authorize_response end def successful_refund_response - <<-XML.gsub(/^\s{4}/,'') + <<-XML.gsub(/^\s{4}/, '') <?xml version="1.0" encoding="UTF-8"?> <NABTransactMessage> <MessageInfo> @@ -271,7 +415,7 @@ def successful_refund_response end def failed_refund_response - <<-XML.gsub(/^\s{4}/,'') + <<-XML.gsub(/^\s{4}/, '') <?xml version="1.0" encoding="UTF-8"?> <NABTransactMessage> <MessageInfo> diff --git a/test/unit/gateways/ncr_secure_pay_test.rb b/test/unit/gateways/ncr_secure_pay_test.rb new file mode 100644 index 00000000000..4fabb3b8779 --- /dev/null +++ b/test/unit/gateways/ncr_secure_pay_test.rb @@ -0,0 +1,437 @@ +require 'test_helper' + +class NcrSecurePayTest < Test::Unit::TestCase + include CommStub + + def setup + @gateway = NcrSecurePayGateway.new(username: 'login', password: 'password') + @credit_card = credit_card + @amount = 100 + + @options = { + order_id: '1', + billing_address: address, + description: 'Store Purchase' + } + end + + def test_successful_purchase + response = stub_comms do + @gateway.purchase(@amount, @credit_card, @options) + end.check_request do |endpoint, data, headers| + assert_match(/\<username\>login\<\/username\>/, data) + assert_match(/\<password\>password\<\/password\>/, data) + assert_match(/\<action\>sale\<\/action\>/, data) + assert_match(/\<amount\>1.00\<\/amount\>/, data) + assert_match(/\<account\>#{@credit_card.number}\<\/account\>/, data) + assert_match(/\<cv\>#{@credit_card.verification_value}\<\/cv\>/, data) + assert_match(/\<comments\>Store Purchase\<\/comments\>/, data) + end.respond_with(successful_purchase_response) + + assert_success response + assert_equal '506897', response.authorization + assert response.test? + end + + def test_failed_purchase + @gateway.expects(:ssl_post).returns(failed_purchase_response) + + response = @gateway.purchase(@amount, @credit_card, @options) + assert_failure response + end + + def test_successful_authorize + response = stub_comms do + @gateway.authorize(@amount, @credit_card, @options) + end.check_request do |endpoint, data, headers| + assert_match(/\<username\>login\<\/username\>/, data) + assert_match(/\<password\>password\<\/password\>/, data) + assert_match(/\<action\>preauth\<\/action\>/, data) + assert_match(/\<amount\>1.00\<\/amount\>/, data) + assert_match(/\<account\>#{@credit_card.number}\<\/account\>/, data) + assert_match(/\<cv\>#{@credit_card.verification_value}\<\/cv\>/, data) + end.respond_with(successful_authorize_response) + + assert_success response + assert_equal '506899', response.authorization + assert response.test? + end + + def test_failed_authorize + @gateway.expects(:ssl_post).returns(failed_authorize_response) + + response = @gateway.authorize(@amount, @credit_card, @options) + assert_failure response + end + + def test_successful_capture + response = stub_comms do + @gateway.capture(@amount, '12345', @options) + end.check_request do |endpoint, data, headers| + assert_match(/\<username\>login\<\/username\>/, data) + assert_match(/\<password\>password\<\/password\>/, data) + assert_match(/\<action\>preauthcomplete\<\/action\>/, data) + assert_match(/\<amount\>1.00\<\/amount\>/, data) + assert_match(/\<ttid\>12345\<\/ttid\>/, data) + end.respond_with(successful_capture_response) + + assert_success response + assert_equal '506901', response.authorization + assert response.test? + end + + def test_failed_capture + @gateway.expects(:ssl_post).returns(failed_capture_response) + + response = @gateway.capture(@amount, 'ref123', @options) + assert_failure response + end + + def test_successful_refund + response = stub_comms do + @gateway.refund(@amount, '12345', @options) + end.check_request do |endpoint, data, headers| + assert_match(/\<username\>login\<\/username\>/, data) + assert_match(/\<password\>password\<\/password\>/, data) + assert_match(/\<action\>credit\<\/action\>/, data) + assert_match(/\<amount\>1.00\<\/amount\>/, data) + assert_match(/\<ttid\>12345\<\/ttid\>/, data) + end.respond_with(successful_refund_response) + + assert_success response + assert_equal '506901', response.authorization + assert response.test? + end + + def test_failed_refund + @gateway.expects(:ssl_post).returns(failed_refund_response) + + response = @gateway.refund(@amount, @credit_card, @options) + assert_failure response + end + + def test_successful_void + response = stub_comms do + @gateway.void('12345', @options) + end.check_request do |endpoint, data, headers| + assert_match(/\<username\>login\<\/username\>/, data) + assert_match(/\<password\>password\<\/password\>/, data) + assert_match(/\<action\>void\<\/action\>/, data) + assert_match(/\<ttid\>12345\<\/ttid\>/, data) + end.respond_with(successful_void_response) + + assert_success response + assert_equal '506905', response.authorization + assert response.test? + end + + def test_failed_void + @gateway.expects(:ssl_post).returns(failed_void_response) + + response = @gateway.void(@credit_card, @options) + assert_failure response + end + + def test_successful_verify + response = stub_comms do + @gateway.verify(@credit_card, @options) + end.respond_with(successful_authorize_response, successful_void_response) + + assert_success response + assert response.test? + end + + def test_successful_verify_with_failed_void + response = stub_comms do + @gateway.verify(@credit_card, @options) + end.respond_with(successful_authorize_response, failed_void_response) + + assert_success response + assert response.test? + end + + def test_failed_verify + response = stub_comms do + @gateway.verify(@credit_card, @options) + end.respond_with(failed_authorize_response, failed_void_response) + + assert_failure response + assert response.test? + end + + def test_scrub + assert @gateway.supports_scrubbing? + assert_equal @gateway.scrub(pre_scrubbed), post_scrubbed + end + + private + + def pre_scrubbed + %q( + opening connection to testbox.monetra.com:8665... + opened + starting SSL for testbox.monetra.com:8665... + SSL established + <- "POST / HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: testbox.monetra.com:8665\r\nContent-Length: 461\r\n\r\n" + <- "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<MonetraTrans>\n <Trans identifier=\"1\">\n <username>test_ecomm:public</username>\n <password>publ1ct3st</password>\n <action>sale</action>\n <amount>1.00</amount>\n <currency>USD</currency>\n <cardholdername>Longbob Longsen</cardholdername>\n <account>4111111111111111</account>\n <cv>123</cv>\n <expdate>0917</expdate>\n <zip>K1C2N6</zip>\n <street>456 My Street</street>\n </Trans>\n</MonetraTrans>\n" + -> "HTTP/1.1 200 OK\r\n" + -> "Server: MHTTP v1.1.0\r\n" + -> "Content-Length: 641\r\n" + -> "Content-Type: application/x-www-form-urlencoded\r\n" + -> "Connection: Close\r\n" + -> "\r\n" + reading 641 bytes... + -> "<MonetraResp>\n\t<DataTransferStatus code=\"SUCCESS\"/>\n\t<Resp identifier=\"1\">\n\t\t<cardholdername>Longbob Longsen</cardholdername>\n\t\t<phard_code>SUCCESS</phard_code>\n\t\t<item>805</item>\n\t\t<account>XXXXXXXXXXXX1111</account>\n\t\t<verbiage>APPROVED</verbiage>\n\t\t<code>AUTH</code>\n\t\t<rcpt_entry_mode>M</rcpt_entry_mode>\n\t\t<cardlevel>VISA_TRADITIONAL</cardlevel>\n\t\t<cardtype>VISA</cardtype>\n\t\t<ttid>506855</ttid>\n\t\t<rcpt_host_ts>010716144811</rcpt_host_ts>\n\t\t<avs>BAD</avs>\n\t\t<msoft_code>INT_SUCCESS</msoft_code>\n\t\t<cv>BAD</cv>\n\t\t<pclevel>0</pclevel>\n\t\t<auth>166322</auth>\n\t\t<timestamp>1452196091</timestamp>\n\t\t<batch>909</batch>\n\t</Resp>\n</MonetraResp>" + read 641 bytes + Conn close + ) + end + + def post_scrubbed + %q( + opening connection to testbox.monetra.com:8665... + opened + starting SSL for testbox.monetra.com:8665... + SSL established + <- "POST / HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: testbox.monetra.com:8665\r\nContent-Length: 461\r\n\r\n" + <- "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<MonetraTrans>\n <Trans identifier=\"1\">\n <username>test_ecomm:public</username>\n <password>[FILTERED]</password>\n <action>sale</action>\n <amount>1.00</amount>\n <currency>USD</currency>\n <cardholdername>Longbob Longsen</cardholdername>\n <account>[FILTERED]</account>\n <cv>[FILTERED]</cv>\n <expdate>0917</expdate>\n <zip>K1C2N6</zip>\n <street>456 My Street</street>\n </Trans>\n</MonetraTrans>\n" + -> "HTTP/1.1 200 OK\r\n" + -> "Server: MHTTP v1.1.0\r\n" + -> "Content-Length: 641\r\n" + -> "Content-Type: application/x-www-form-urlencoded\r\n" + -> "Connection: Close\r\n" + -> "\r\n" + reading 641 bytes... + -> "<MonetraResp>\n\t<DataTransferStatus code=\"SUCCESS\"/>\n\t<Resp identifier=\"1\">\n\t\t<cardholdername>Longbob Longsen</cardholdername>\n\t\t<phard_code>SUCCESS</phard_code>\n\t\t<item>805</item>\n\t\t<account>[FILTERED]</account>\n\t\t<verbiage>APPROVED</verbiage>\n\t\t<code>AUTH</code>\n\t\t<rcpt_entry_mode>M</rcpt_entry_mode>\n\t\t<cardlevel>VISA_TRADITIONAL</cardlevel>\n\t\t<cardtype>VISA</cardtype>\n\t\t<ttid>506855</ttid>\n\t\t<rcpt_host_ts>010716144811</rcpt_host_ts>\n\t\t<avs>BAD</avs>\n\t\t<msoft_code>INT_SUCCESS</msoft_code>\n\t\t<cv>[FILTERED]</cv>\n\t\t<pclevel>0</pclevel>\n\t\t<auth>166322</auth>\n\t\t<timestamp>1452196091</timestamp>\n\t\t<batch>909</batch>\n\t</Resp>\n</MonetraResp>" + read 641 bytes + Conn close + ) + end + + def successful_purchase_response + %( + <MonetraResp> + <DataTransferStatus code="SUCCESS"/> + <Resp identifier="1"> + <pclevel>0</pclevel> + <auth>612997</auth> + <timestamp>1452204696</timestamp> + <batch>909</batch> + <cardholdername>Longbob Longsen</cardholdername> + <phard_code>SUCCESS</phard_code> + <item>833</item> + <account>XXXXXXXXXXXX1111</account> + <verbiage>APPROVED</verbiage> + <code>AUTH</code> + <rcpt_entry_mode>M</rcpt_entry_mode> + <cardlevel>VISA_TRADITIONAL</cardlevel> + <cardtype>VISA</cardtype> + <ttid>506897</ttid> + <rcpt_host_ts>010716171136</rcpt_host_ts> + <avs>BAD</avs> + <msoft_code>INT_SUCCESS</msoft_code> + <cv>BAD</cv> + </Resp> + </MonetraResp> + ) + end + + def failed_purchase_response + %( + <MonetraResp> + <DataTransferStatus code="SUCCESS"/> + <Resp identifier="1"> + <phard_code>GENERICFAIL</phard_code> + <cv>BAD</cv> + <cardholdername>Longbob Longsen</cardholdername> + <rcpt_host_ts>010716171332</rcpt_host_ts> + <code>DENY</code> + <sequenceid>834</sequenceid> + <msoft_code>INT_SUCCESS</msoft_code> + <rcpt_entry_mode>M</rcpt_entry_mode> + <cardtype>VISA</cardtype> + <account>XXXXXXXXXXXX1111</account> + <timestamp>1452204812</timestamp> + <avs>BAD</avs> + <ttid>506898</ttid> + <verbiage>DECLINE</verbiage> + </Resp> + </MonetraResp> + ) + end + + def successful_authorize_response + %( + <MonetraResp> + <DataTransferStatus code="SUCCESS"/> + <Resp identifier="1"> + <rcpt_host_ts>010716171552</rcpt_host_ts> + <avs>BAD</avs> + <code>AUTH</code> + <rcpt_entry_mode>M</rcpt_entry_mode> + <cardholdername>Longbob Longsen</cardholdername> + <pclevel>0</pclevel> + <auth>538752</auth> + <cardtype>VISA</cardtype> + <ttid>506899</ttid> + <verbiage>APPROVED</verbiage> + <account>XXXXXXXXXXXX1111</account> + <msoft_code>INT_SUCCESS</msoft_code> + <cardlevel>VISA_TRADITIONAL</cardlevel> + <timestamp>1452204952</timestamp> + <cv>BAD</cv> + <phard_code>SUCCESS</phard_code> + <item>835</item> + </Resp> + </MonetraResp> + ) + end + + def failed_authorize_response + %( + <MonetraResp> + <DataTransferStatus code="SUCCESS"/> + <Resp identifier="1"> + <timestamp>1452205008</timestamp> + <rcpt_host_ts>010716171648</rcpt_host_ts> + <cardtype>VISA</cardtype> + <avs>BAD</avs> + <cardholdername>Longbob Longsen</cardholdername> + <msoft_code>INT_SUCCESS</msoft_code> + <cv>BAD</cv> + <phard_code>GENERICFAIL</phard_code> + <ttid>506900</ttid> + <code>DENY</code> + <rcpt_entry_mode>M</rcpt_entry_mode> + <sequenceid>836</sequenceid> + <verbiage>DECLINE</verbiage> + <account>XXXXXXXXXXXX1111</account> + </Resp> + </MonetraResp> + ) + end + + def successful_capture_response + %( + <MonetraResp> + <DataTransferStatus code="SUCCESS"/> + <Resp identifier="1"> + <verbiage>SUCCESS</verbiage> + <account>XXXXXXXXXXXX1111</account> + <msoft_code>INT_SUCCESS</msoft_code> + <code>AUTH</code> + <timestamp>1452205042</timestamp> + <pclevel>0</pclevel> + <rcpt_entry_mode>M</rcpt_entry_mode> + <cardtype>VISA</cardtype> + <batch>909</batch> + <ttid>506901</ttid> + <cardholdername>Longbob Longsen</cardholdername> + <rcpt_host_ts>010716171722</rcpt_host_ts> + <phard_code>UNKNOWN</phard_code> + </Resp> + </MonetraResp> + ) + end + + def failed_capture_response + %( + <MonetraResp> + <DataTransferStatus code="SUCCESS"/> + <Resp identifier="1"> + <verbiage>This transaction requires an apprcode ttid or unique ptrannum</verbiage> + <timestamp>1452205210</timestamp> + <phard_code>UNKNOWN</phard_code> + <rcpt_host_ts>010716172010</rcpt_host_ts> + <code>DENY</code> + <msoft_code>DATA_BADTRANS</msoft_code> + <rcpt_entry_mode>M</rcpt_entry_mode> + <cardtype>UNKNOWN</cardtype> + <ttid>506902</ttid> + </Resp> + </MonetraResp> + ) + end + + def successful_refund_response + %( + <MonetraResp> + <DataTransferStatus code="SUCCESS"/> + <Resp identifier="1"> + <verbiage>SUCCESS</verbiage> + <msoft_code>INT_SUCCESS</msoft_code> + <code>AUTH</code> + <timestamp>1452205042</timestamp> + <pclevel>0</pclevel> + <rcpt_entry_mode>M</rcpt_entry_mode> + <batch>909</batch> + <ttid>506901</ttid> + <rcpt_host_ts>010716171722</rcpt_host_ts> + <phard_code>UNKNOWN</phard_code> + </Resp> + </MonetraResp> + ) + end + + def failed_refund_response + %( + <MonetraResp> + <DataTransferStatus code="SUCCESS"/> + <Resp identifier="1"> + <cardtype>VISA</cardtype> + <verbiage>USE VOID OR REVERSAL TO REFUND UNSETTLED TRANSACTIONS</verbiage> + <account>XXXXXXXXXXXX1111</account> + <msoft_code>DATA_INVALIDMOD</msoft_code> + <ttid>506904</ttid> + <code>DENY</code> + <cardholdername>Longbob Longsen</cardholdername> + <timestamp>1452205437</timestamp> + <rcpt_host_ts>010716172357</rcpt_host_ts> + <rcpt_entry_mode>M</rcpt_entry_mode> + <phard_code>UNKNOWN</phard_code> + </Resp> + </MonetraResp> + ) + end + + def successful_void_response + %( + <MonetraResp> + <DataTransferStatus code="SUCCESS"/> + <Resp identifier="1"> + <rcpt_host_ts>010716172453</rcpt_host_ts> + <ttid>506905</ttid> + <verbiage>SUCCESS</verbiage> + <msoft_code>INT_SUCCESS</msoft_code> + <phard_code>UNKNOWN</phard_code> + <cardholdername>Longbob Longsen</cardholdername> + <timestamp>1452205493</timestamp> + <code>AUTH</code> + <batch>909</batch> + <rcpt_entry_mode>M</rcpt_entry_mode> + <cardtype>VISA</cardtype> + <account>XXXXXXXXXXXX1111</account> + </Resp> + </MonetraResp> + ) + end + + def failed_void_response + %( + <MonetraResp> + <DataTransferStatus code="SUCCESS"/> + <Resp identifier="1"> + <rcpt_host_ts>010716172604</rcpt_host_ts> + <verbiage>Must specify ttid or ptrannum</verbiage> + <timestamp>1452205564</timestamp> + <msoft_code>UNKNOWN</msoft_code> + <rcpt_entry_mode>M</rcpt_entry_mode> + <phard_code>UNKNOWN</phard_code> + <ttid>506906</ttid> + <code>DENY</code> + <cardtype>UNKNOWN</cardtype> + </Resp> + </MonetraResp> + ) + end +end diff --git a/test/unit/gateways/net_registry_test.rb b/test/unit/gateways/net_registry_test.rb index 15336010d06..0d6c50e2f63 100644 --- a/test/unit/gateways/net_registry_test.rb +++ b/test/unit/gateways/net_registry_test.rb @@ -14,19 +14,19 @@ def setup :billing_address => address } end - + def test_filtered_fields @gateway.stubs(:ssl_post).returns(successful_purchase_response) response = @gateway.purchase(@amount, @credit_card, @options) - + NetRegistryGateway::FILTERED_PARAMS.each do |param| assert_false response.params.has_key?(param) end end - + def test_successful_purchase @gateway.stubs(:ssl_post).returns(successful_purchase_response) - + response = @gateway.purchase(@amount, @credit_card, @options) assert_success response assert_match '0707161858000000', response.authorization @@ -34,7 +34,7 @@ def test_successful_purchase def test_successful_credit @gateway.stubs(:ssl_post).returns(successful_credit_response) - assert_deprecation_warning(Gateway::CREDIT_DEPRECATION_MESSAGE, @gateway) do + assert_deprecation_warning(Gateway::CREDIT_DEPRECATION_MESSAGE) do response = @gateway.credit(@amount, '0707161858000000', @options) assert_success response end @@ -45,10 +45,10 @@ def test_successful_refund response = @gateway.refund(@amount, '0707161858000000', @options) assert_success response end - + def test_capture_without_credit_card_provided assert_raise(ArgumentError) do - response = @gateway.capture(@amount, '0707161858000000', @options) + @gateway.capture(@amount, '0707161858000000', @options) end end @@ -56,7 +56,7 @@ def test_successful_authorization @gateway.stubs(:ssl_post).returns(successful_authorization_response) response = @gateway.authorize(@amount, @credit_card, @options) assert_success response - assert_match /\A\d{6}\z/, response.authorization + assert_match %r{\A\d{6}\z}, response.authorization assert_equal '000000', response.authorization end @@ -65,7 +65,7 @@ def test_successful_authorization_and_capture response = @gateway.authorize(@amount, @credit_card, @options) assert_success response - assert_match /\A\d{6}\z/, response.authorization + assert_match %r{\A\d{6}\z}, response.authorization response = @gateway.capture(@amount, response.authorization, :credit_card => @credit_card) assert_success response @@ -81,7 +81,7 @@ def test_purchase_with_invalid_credit_card def test_purchase_with_expired_credit_card @gateway.stubs(:ssl_post).returns(purchase_with_expired_credit_card_response) - + response = @gateway.purchase(@amount, @credit_card, @options) assert_failure response assert_equal 'CARD EXPIRED', response.message @@ -89,7 +89,7 @@ def test_purchase_with_expired_credit_card def test_purchase_with_invalid_month @gateway.stubs(:ssl_post).returns(purchase_with_invalid_month_response) - + response = @gateway.purchase(@amount, @credit_card, @options) assert_failure response assert_equal 'Invalid month', response.message @@ -98,22 +98,23 @@ def test_purchase_with_invalid_month def test_bad_login gateway = NetRegistryGateway.new(:login => 'bad-login', :password => 'bad-login') gateway.stubs(:ssl_post).returns(bad_login_response) - + response = gateway.purchase(@amount, @credit_card, @options) assert_failure response assert_equal 'failed', response.params['status'] end private + def successful_purchase_response <<-RESPONSE approved 00015X000000 Transaction No: 00000000 ------------------------ -MERCHANTNAME +MERCHANTNAME LOCATION AU - + MERCH ID 10000000 TERM ID Y0TR00 COUNTRY CODE AU @@ -122,18 +123,18 @@ def successful_purchase_response VISA 411111-111 CREDIT A/C 12/10 - + AUTHORISATION NO: 000000 APPROVED 08 - + PURCHASE $1.00 TOTAL AUD $1.00 - -PLEASE RETAIN AS RECORD - OF PURCHASE - + +PLEASE RETAIN AS RECORD + OF PURCHASE + (SUBJECT TO CARDHOLDER'S - ACCEPTANCE) + ACCEPTANCE) ------------------------ . settlement_date=16/07/07 @@ -161,16 +162,16 @@ def successful_purchase_response result=1 RESPONSE end - + def successful_credit_response <<-RESPONSE approved 00015X000000 Transaction No: 00000000 ------------------------ -MERCHANTNAME +MERCHANTNAME LOCATION AU - + MERCH ID 10000000 TERM ID Y0TR00 COUNTRY CODE AU @@ -179,18 +180,18 @@ def successful_credit_response VISA 411111-111 CREDIT A/C 12/10 - + AUTHORISATION NO: APPROVED 08 - + ** REFUND ** $1.00 TOTAL AUD $1.00 - -PLEASE RETAIN AS RECORD - OF REFUND - + +PLEASE RETAIN AS RECORD + OF REFUND + (SUBJECT TO CARDHOLDER'S - ACCEPTANCE) + ACCEPTANCE) ------------------------ . settlement_date=16/07/07 @@ -218,16 +219,16 @@ def successful_credit_response result=1 RESPONSE end - + def successful_authorization_response <<-RESPONSE approved 00015X000000 Transaction No: 00000000 ------------------------ -MERCHANTNAME +MERCHANTNAME LOCATION AU - + MERCH ID 10000000 TERM ID Y0TR00 COUNTRY CODE AU @@ -236,18 +237,18 @@ def successful_authorization_response VISA 411111-111 CREDIT A/C 12/10 - + AUTHORISATION NO: 000000 APPROVED 08 - + PURCHASE $1.00 TOTAL AUD $1.00 - -PLEASE RETAIN AS RECORD - OF PURCHASE - + +PLEASE RETAIN AS RECORD + OF PURCHASE + (SUBJECT TO CARDHOLDER'S - ACCEPTANCE) + ACCEPTANCE) ------------------------ . settlement_date=17/07/07 @@ -272,19 +273,19 @@ def successful_authorization_response cashout_amount=0 receipt_array=ARRAY(0x836a25c) account_type=CREDIT A/C -result=1 +result=1 RESPONSE end - + def successful_capture_response <<-RESPONSE approved 00015X000000 Transaction No: 00000000 ------------------------ -MERCHANTNAME +MERCHANTNAME LOCATION AU - + MERCH ID 10000000 TERM ID Y0TR00 COUNTRY CODE AU @@ -293,18 +294,18 @@ def successful_capture_response VISA 411111-111 CREDIT A/C 12/10 - + AUTHORISATION NO: 000000 APPROVED 08 - + PURCHASE $1.00 TOTAL AUD $1.00 - -PLEASE RETAIN AS RECORD - OF PURCHASE - + +PLEASE RETAIN AS RECORD + OF PURCHASE + (SUBJECT TO CARDHOLDER'S - ACCEPTANCE) + ACCEPTANCE) ------------------------ . settlement_date=17/07/07 @@ -329,19 +330,19 @@ def successful_capture_response cashout_amount=0 receipt_array=ARRAY(0x8378200) account_type=CREDIT A/C -result=1 +result=1 RESPONSE end - + def purchase_with_invalid_credit_card_response <<-RESPONSE declined 00015X000000 Transaction No: 00000000 ------------------------ -MERCHANTNAME +MERCHANTNAME LOCATION AU - + MERCH ID 10000000 TERM ID Y0TR40 COUNTRY CODE AU @@ -350,15 +351,15 @@ def purchase_with_invalid_credit_card_response VISA 411111-111 CREDIT A/C 12/10 - + AUTHORISATION NO: DECLINED 31 - + PURCHASE $1.00 TOTAL AUD $1.00 - + (SUBJECT TO CARDHOLDER'S - ACCEPTANCE) + ACCEPTANCE) ------------------------ . settlement_date=16/07/07 @@ -383,10 +384,10 @@ def purchase_with_invalid_credit_card_response cashout_amount=0 receipt_array=ARRAY(0x83752d0) account_type=CREDIT A/C -result=0 +result=0 RESPONSE end - + def purchase_with_expired_credit_card_response <<-RESPONSE failed @@ -403,14 +404,14 @@ def purchase_with_expired_credit_card_response result=-1 RESPONSE end - + def purchase_with_invalid_month_response <<-RESPONSE failed Invalid month RESPONSE end - + def bad_login_response <<-RESPONSE failed diff --git a/test/unit/gateways/netaxept_test.rb b/test/unit/gateways/netaxept_test.rb index a4a22940f46..5dddbde3427 100644 --- a/test/unit/gateways/netaxept_test.rb +++ b/test/unit/gateways/netaxept_test.rb @@ -18,7 +18,7 @@ def setup end def test_successful_purchase - s = sequence("request") + s = sequence('request') @gateway.expects(:ssl_get).returns(successful_purchase_response[0]).in_sequence(s) @gateway.expects(:ssl_get).returns(successful_purchase_response[1]).in_sequence(s) @gateway.expects(:ssl_get).returns(successful_purchase_response[2]).in_sequence(s) @@ -32,7 +32,7 @@ def test_successful_purchase end def test_failed_purchase - s = sequence("request") + s = sequence('request') @gateway.expects(:ssl_get).returns(successful_purchase_response[0]).in_sequence(s) @gateway.expects(:ssl_get).returns(successful_purchase_response[1]).in_sequence(s) @gateway.expects(:ssl_get).returns(failed_purchase_response).in_sequence(s) @@ -46,12 +46,12 @@ def test_failed_purchase def test_requires_order_id assert_raise(ArgumentError) do - response = @gateway.purchase(@amount, @credit_card, {}) + @gateway.purchase(@amount, @credit_card, {}) end end def test_handles_currency_with_money - s = sequence("request") + s = sequence('request') @gateway.expects(:ssl_get).with(regexp_matches(/currencyCode=USD/)).returns(successful_purchase_response[0]).in_sequence(s) @gateway.expects(:ssl_get).returns(successful_purchase_response[1]).in_sequence(s) @gateway.expects(:ssl_get).returns(successful_purchase_response[2]).in_sequence(s) @@ -61,7 +61,7 @@ def test_handles_currency_with_money end def test_handles_currency_with_option - s = sequence("request") + s = sequence('request') @gateway.expects(:ssl_get).with(regexp_matches(/currencyCode=USD/)).returns(successful_purchase_response[0]).in_sequence(s) @gateway.expects(:ssl_get).returns(successful_purchase_response[1]).in_sequence(s) @gateway.expects(:ssl_get).returns(successful_purchase_response[2]).in_sequence(s) @@ -80,7 +80,7 @@ def test_handles_setup_transaction_error end def test_handles_query_error - s = sequence("request") + s = sequence('request') @gateway.expects(:ssl_get).returns(successful_purchase_response[0]).in_sequence(s) @gateway.expects(:ssl_get).returns(successful_purchase_response[1]).in_sequence(s) @gateway.expects(:ssl_get).returns(error_purchase_response[2]).in_sequence(s) @@ -93,7 +93,7 @@ def test_handles_query_error def test_url_escape_password @gateway = NetaxeptGateway.new(:login => 'login', :password => '1a=W+Yr2') - s = sequence("request") + s = sequence('request') @gateway.expects(:ssl_get).with(regexp_matches(/token=1a%3DW%2BYr2/)).returns(successful_purchase_response[0]).in_sequence(s) @gateway.expects(:ssl_get).returns(successful_purchase_response[1]).in_sequence(s) @gateway.expects(:ssl_get).returns(successful_purchase_response[2]).in_sequence(s) @@ -103,7 +103,7 @@ def test_url_escape_password end def test_using_credit_card_transaction_service_type - s = sequence("request") + s = sequence('request') @gateway.expects(:ssl_get).with(regexp_matches(/serviceType=M/)).returns(successful_purchase_response[0]).in_sequence(s) @gateway.expects(:ssl_get).returns(successful_purchase_response[1]).in_sequence(s) @gateway.expects(:ssl_get).returns(successful_purchase_response[2]).in_sequence(s) diff --git a/test/unit/gateways/netbanx_test.rb b/test/unit/gateways/netbanx_test.rb new file mode 100644 index 00000000000..a4a424d27f3 --- /dev/null +++ b/test/unit/gateways/netbanx_test.rb @@ -0,0 +1,636 @@ +require 'test_helper' + +class NetbanxTest < Test::Unit::TestCase + def setup + @gateway = NetbanxGateway.new(account_number: '1234567890', api_key: 'foobar') + @credit_card = credit_card + @amount = 100 + + @options = { + order_id: '1', + billing_address: address, + description: 'Store Purchase', + currency: 'CAD' + } + end + + def test_successful_purchase + @gateway.expects(:ssl_request).returns(successful_purchase_response) + + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + + assert_equal '9c13fdfe-77d9-4fef-bfd6-a95132423b99', response.authorization + assert response.test? + end + + def test_failed_purchase + @gateway.expects(:ssl_request).returns(failed_purchase_response) + + response = @gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_equal 'The card has been declined due to insufficient funds.', response.message + assert response.test? + end + + def test_successful_authorize + @gateway.expects(:ssl_request).returns(successful_authorize_response) + + response = @gateway.authorize(@amount, @credit_card, @options) + assert_success response + + assert_equal 'b8c53059-9da3-4054-8caf-3769161a3cdc', response.authorization + assert response.test? + end + + def test_failed_authorize + @gateway.expects(:ssl_request).returns(failed_authorize_response) + + response = @gateway.authorize(@amount, @credit_card, @options) + assert_failure response + + assert_equal 'The card has been declined due to insufficient funds.', response.message + assert response.test? + end + + def test_successful_capture + @gateway.expects(:ssl_request).returns(successful_capture_response) + + response = @gateway.authorize(@amount, '056ff3a9-5274-4452-92ab-0e3b3e591c3b') + assert_success response + + assert_equal '11e0906b-6596-4490-b0e3-825f71a82799', response.authorization + assert_equal 'OK', response.message + assert response.test? + end + + def test_failed_capture + @gateway.expects(:ssl_request).returns(failed_capture_response) + + response = @gateway.authorize(@amount, '056ff3a9-f000-b44r-92ab-0e3b3e591c3b') + assert_failure response + + assert_equal 'The authorization ID included in this settlement request could not be found.', response.message + assert response.test? + end + + def test_successful_refund + @gateway.expects(:ssl_request).returns(successful_capture_response) + + response = @gateway.refund(@amount, '056ff3a9-5274-4452-92ab-0e3b3e591c3b') + assert_success response + + assert_equal '11e0906b-6596-4490-b0e3-825f71a82799', response.authorization + assert_equal 'OK', response.message + assert response.test? + end + + def test_failed_refund + @gateway.expects(:ssl_request).returns(failed_refund_response) + + response = @gateway.refund(@amount, '056ff3a9-f000-b44r-92ab-0e3b3e591c3b') + assert_failure response + + assert_equal 'The settlement you are attempting to refund has not been batched yet. There are no settled funds available to refund.', response.message + assert response.test? + end + + def test_successful_void + @gateway.expects(:ssl_request).returns(successful_void_response) + + response = @gateway.void('16d9eafa-cca0-4916-9408-e83c899924a6') + assert_success response + + assert_equal '64b3f52e-cd0d-474a-8f16-bb9c559d3bca', response.authorization + assert_equal 'OK', response.message + assert response.test? + end + + def test_failed_void + @gateway.expects(:ssl_request).returns(failed_void_response) + + response = @gateway.void('056ff3a9-f000-b44r-92ab-0e3b3e591c3b') + assert_failure response + + assert_equal 'The confirmation number included in this request could not be found.', response.message + assert response.test? + end + + def test_successful_store + @gateway.expects(:ssl_request).returns(successful_store_response) + + options = @options.merge({ locale: 'en_GB' }) + + response = @gateway.store(@credit_card, options) + assert_success response + + assert_equal '2f840ab3-0e71-4387-bad3-4705e6f4b015|e4a3cd5a-56db-4d9b-97d3-fdd9ab3bd0f4|C6gmdUA1xWT8RsC', response.authorization + assert response.test? + end + + def test_successful_purchase_with_token + @gateway.expects(:ssl_request).returns(successful_purchase_with_token_response) + + response = @gateway.purchase(@amount, 'CL0RCSnrkREnfwA', @options) + assert_success response + + assert_equal 'OK', response.message + assert_equal 'bfc9b743-b9b3-4906-b06b-da4e185d93bf', response.authorization + assert response.test? + end + + def test_successful_unstore + @gateway.expects(:ssl_request).twice.returns(successful_unstore_response) + + response = @gateway.unstore('2f840ab3-0e71-4387-bad3-4705e6f4b015|e4a3cd5a-56db-4d9b-97d3-fdd9ab3bd0f4') + assert_success response + assert response.test? + + response = @gateway.unstore('2f840ab3-0e71-4387-bad3-4705e6f4b015') + assert_success response + assert response.test? + end + + def test_scrub + assert @gateway.supports_scrubbing? + assert_equal @gateway.scrub(pre_scrubbed), post_scrubbed + end + + private + + def pre_scrubbed + <<-PRE_SCRUBBED + opening connection to api.test.netbanx.com:443... + opened + starting SSL for api.test.netbanx.com:443... + SSL established + <- "POST /cardpayments/v1/accounts/1234567890/auths HTTP/1.1\r\nContent-Type: application/json\r\nAccept: application/json\r\nAuthorization: Basic b29aNG9zaDhpVGlzZWUwYWVqb2U5cGVlNHRvaDVhYTRPaGdoYWUybGFocGgyT2hyYWU2dGhlZTNQaGVleWVlVzNlaWc5YWVQaWVwaGFpTDU=\r\nUser-Agent: Netbanx-Paysafe v1.0/ActiveMerchant 1.60.0\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nConnection: close\r\nHost: api.test.netbanx.com\r\nContent-Length: 272\r\n\r\n" + <- "{\"amount\":\"100\",\"merchantRefNum\":\"feff07f6aac020790c6d68626be3790b\",\"billingDetails\":{\"street\":\"456 My Street\",\"city\":\"Ottawa\",\"zip\":\"K1C2N6\",\"country\":\"CA\"},\"settleWithAuth\":true,\"card\":{\"cardNum\":\"4530910000012345\",\"cvv\":\"123\",\"cardExpiry\":{\"month\":\"09\",\"year\":\"2017\"}}}" + -> "HTTP/1.1 200 OK\r\n" + -> "Server: WebServer32xS10i3\r\n" + -> "Content-Length: 1190\r\n" + -> "X-ApplicationUid: GUID=fbf46c92-d3e5-496f-98b4-f56380039396\r\n" + -> "X-Powered-By: Servlet/2.5 JSP/2.1\r\n" + -> "Content-Type: application/json\r\n" + -> "Expires: Thu, 04 Aug 2016 08:29:51 GMT\r\n" + -> "Cache-Control: max-age=0, no-cache, no-store\r\n" + -> "Pragma: no-cache\r\n" + -> "Date: Thu, 04 Aug 2016 08:29:51 GMT\r\n" + -> "Connection: close\r\n" + -> "Set-Cookie: JSESSIONID=rRtUrEShVlgJ4WUe2aLrCaaKnSq932fmNvhqpKZn22ytPUhPjgG7!-2137665119; path=/; HttpOnly\r\n" + -> "\r\n" + reading 1190 bytes... + -> "{" + -> "\"links\":[{\"rel\":\"settlement\",\"href\":\"https://api.test.netbanx.com/cardpayments/v1/accounts/1234567890/settlements/fbf46c92-d3e5-496f-98b4-f56380039396\"},{\"rel\":\"self\",\"href\":\"https://api.test.netbanx.com/cardpayments/v1/accounts/1234567890/auths/fbf46c92-d3e5-496f-98b4-f56380039396\"}],\"id\":\"fbf46c92-d3e5-496f-98b4-f56380039396\",\"merchantRefNum\":\"feff07f6aac020790c6d68626be3790b\",\"txnTime\":\"2016-08-04T08:29:51Z\",\"status\":\"COMPLETED\",\"amount\":100,\"settleWithAuth\":true,\"preAuth\":false,\"availableToSettle\":0,\"card\":{\"type\":\"VI\",\"lastDigits\":\"2345\",\"cardExpiry\":{\"month\":9,\"year\":2017}},\"authCode\":\"125492\",\"billingDetails\":{\"street\":\"456 My Street\",\"city\":\"Ottawa\",\"country\":\"CA\",\"zip\":\"K1C2N6\"},\"merchantDescriptor\":{\"dynamicDescriptor\":\"Test\",\"phone\":\"123-1234123\"},\"currencyCode\":\"CAD\",\"avsResponse\":\"MATCH\",\"cvvVerification\":\"MATCH\",\"settlements\":[{\"links\":[{\"rel\":\"self\",\"href\":\"https://api.test.netbanx.com/cardpayments/v1/accounts/1234567890/settlements/fbf46c92-d3e5-496f-98b4-f56380039396\"}],\"id\":\"fbf46c92-d3e5-496f-98b4-f56380039396\",\"merchantRefNum\":\"feff07f6aac020790c6d68626be3790b\",\"txnTime\":\"2016-08-04T08:29:51Z\",\"status\":\"PENDING\",\"amount\":100,\"availableToRefund\":100}]}" + read 1190 bytes + Conn close + PRE_SCRUBBED + end + + def post_scrubbed + <<-POST_SCRUBBED + opening connection to api.test.netbanx.com:443... + opened + starting SSL for api.test.netbanx.com:443... + SSL established + <- "POST /cardpayments/v1/accounts/1234567890/auths HTTP/1.1\r\nContent-Type: application/json\r\nAccept: application/json\r\nAuthorization: Basic [FILTERED]=\r\nUser-Agent: Netbanx-Paysafe v1.0/ActiveMerchant 1.60.0\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nConnection: close\r\nHost: api.test.netbanx.com\r\nContent-Length: 272\r\n\r\n" + <- "{\"amount\":\"100\",\"merchantRefNum\":\"feff07f6aac020790c6d68626be3790b\",\"billingDetails\":{\"street\":\"456 My Street\",\"city\":\"Ottawa\",\"zip\":\"K1C2N6\",\"country\":\"CA\"},\"settleWithAuth\":true,\"card\":{\"cardNum\":\"[FILTERED]\",\"cvv\":\"[FILTERED]\",\"cardExpiry\":{\"month\":\"09\",\"year\":\"2017\"}}}" + -> "HTTP/1.1 200 OK\r\n" + -> "Server: WebServer32xS10i3\r\n" + -> "Content-Length: 1190\r\n" + -> "X-ApplicationUid: GUID=fbf46c92-d3e5-496f-98b4-f56380039396\r\n" + -> "X-Powered-By: Servlet/2.5 JSP/2.1\r\n" + -> "Content-Type: application/json\r\n" + -> "Expires: Thu, 04 Aug 2016 08:29:51 GMT\r\n" + -> "Cache-Control: max-age=0, no-cache, no-store\r\n" + -> "Pragma: no-cache\r\n" + -> "Date: Thu, 04 Aug 2016 08:29:51 GMT\r\n" + -> "Connection: close\r\n" + -> "Set-Cookie: JSESSIONID=rRtUrEShVlgJ4WUe2aLrCaaKnSq932fmNvhqpKZn22ytPUhPjgG7!-2137665119; path=/; HttpOnly\r\n" + -> "\r\n" + reading 1190 bytes... + -> "{" + -> "\"links\":[{\"rel\":\"settlement\",\"href\":\"https://api.test.netbanx.com/cardpayments/v1/accounts/1234567890/settlements/fbf46c92-d3e5-496f-98b4-f56380039396\"},{\"rel\":\"self\",\"href\":\"https://api.test.netbanx.com/cardpayments/v1/accounts/1234567890/auths/fbf46c92-d3e5-496f-98b4-f56380039396\"}],\"id\":\"fbf46c92-d3e5-496f-98b4-f56380039396\",\"merchantRefNum\":\"feff07f6aac020790c6d68626be3790b\",\"txnTime\":\"2016-08-04T08:29:51Z\",\"status\":\"COMPLETED\",\"amount\":100,\"settleWithAuth\":true,\"preAuth\":false,\"availableToSettle\":0,\"card\":{\"type\":\"VI\",\"lastDigits\":\"2345\",\"cardExpiry\":{\"month\":9,\"year\":2017}},\"authCode\":\"125492\",\"billingDetails\":{\"street\":\"456 My Street\",\"city\":\"Ottawa\",\"country\":\"CA\",\"zip\":\"K1C2N6\"},\"merchantDescriptor\":{\"dynamicDescriptor\":\"Test\",\"phone\":\"123-1234123\"},\"currencyCode\":\"CAD\",\"avsResponse\":\"MATCH\",\"cvvVerification\":\"MATCH\",\"settlements\":[{\"links\":[{\"rel\":\"self\",\"href\":\"https://api.test.netbanx.com/cardpayments/v1/accounts/1234567890/settlements/fbf46c92-d3e5-496f-98b4-f56380039396\"}],\"id\":\"fbf46c92-d3e5-496f-98b4-f56380039396\",\"merchantRefNum\":\"feff07f6aac020790c6d68626be3790b\",\"txnTime\":\"2016-08-04T08:29:51Z\",\"status\":\"PENDING\",\"amount\":100,\"availableToRefund\":100}]}" + read 1190 bytes + Conn close + POST_SCRUBBED + end + + def successful_purchase_response + <<-RESPONSE + { + "links": [ + { + "rel": "settlement", + "href": "https://api.test.netbanx.com/cardpayments/v1/accounts/123457890/settlements/9c13fdfe-77d9-4fef-bfd6-a95132423b99" + }, + { + "rel": "self", + "href": "https://api.test.netbanx.com/cardpayments/v1/accounts/123457890/auths/9c13fdfe-77d9-4fef-bfd6-a95132423b99" + } + ], + "id": "9c13fdfe-77d9-4fef-bfd6-a95132423b99", + "merchantRefNum": "2651eb361b1609777a8b9034257c1be9", + "txnTime": "2016-08-04T06:37:15Z", + "status": "COMPLETED", + "amount": 100, + "settleWithAuth": true, + "preAuth": false, + "availableToSettle": 0, + "card": { + "type": "VI", + "lastDigits": "2345", + "cardExpiry": { + "month": 9, + "year": 2017 + } + }, + "authCode": "762449", + "billingDetails": { + "street": "456 My Street", + "street2": "Apt 1", + "city": "Ottawa", + "state": "ON", + "country": "CA", + "zip": "K1C2N6" + }, + "merchantDescriptor": { + "dynamicDescriptor": "Test", + "phone": "123-1234123" + }, + "currencyCode": "CAD", + "avsResponse": "MATCH", + "cvvVerification": "MATCH", + "settlements": [ + { + "links": [ + { + "rel": "self", + "href": "https://api.test.netbanx.com/cardpayments/v1/accounts/123457890/settlements/9c13fdfe-77d9-4fef-bfd6-a95132423b99" + } + ], + "id": "9c13fdfe-77d9-4fef-bfd6-a95132423b99", + "merchantRefNum": "2651eb361b1609777a8b9034257c1be9", + "txnTime": "2016-08-04T06:37:15Z", + "status": "PENDING", + "amount": 100, + "availableToRefund": 100 + } + ] + } + RESPONSE + end + + def failed_purchase_response + <<-RESPONSE + { + "links": [ + { + "rel": "self", + "href": "https://api.test.netbanx.com/cardpayments/v1/accounts/1234567890/auths/a3eeaaa7-9f29-4345-b326-46f92300dd6c" + } + ], + "id": "a3eeaaa7-9f29-4345-b326-46f92300dd6c", + "merchantRefNum": "f37cc5db-d766-477f-b4c2-636ad5664f50", + "error": { + "code": "3022", + "message": "The card has been declined due to insufficient funds.", + "links": [ + { + "rel": "errorinfo", + "href": "https://developer.optimalpayments.com/en/documentation/card-payments-api/error-3022" + } + ] + }, + "riskReasonCode": [ + 1059 + ], + "settleWithAuth": true, + "cvvVerification": "MATCH" + } + RESPONSE + end + + def successful_purchase_with_token_response + <<-RESPONSE + { + "links": [ + { + "rel": "settlement", + "href": "https://api.test.netbanx.com/cardpayments/v1/accounts/1234567890/settlements/bfc9b743-b9b3-4906-b06b-da4e185d93bf" + }, + { + "rel": "self", + "href": "https://api.test.netbanx.com/cardpayments/v1/accounts/1234567890/auths/bfc9b743-b9b3-4906-b06b-da4e185d93bf" + } + ], + "id": "bfc9b743-b9b3-4906-b06b-da4e185d93bf", + "merchantRefNum": "efdf1a69-2380-4c4f-a392-62ce80970b65", + "txnTime": "2016-08-08T06:36:50Z", + "status": "COMPLETED", + "amount": 100, + "settleWithAuth": true, + "preAuth": false, + "availableToSettle": 0, + "card": { + "type": "VI", + "lastDigits": "2345", + "cardExpiry": { + "month": 9, + "year": 2017 + } + }, + "authCode": "101408", + "billingDetails": { + "street": "456 My Street", + "city": "Ottawa", + "country": "CA", + "zip": "K1C2N6" + }, + "merchantDescriptor": { + "dynamicDescriptor": "Test", + "phone": "123-1234123" + }, + "currencyCode": "CAD", + "avsResponse": "MATCH", + "cvvVerification": "NOT_PROCESSED", + "settlements": [ + { + "links": [ + { + "rel": "self", + "href": "https://api.test.netbanx.com/cardpayments/v1/accounts/1234567890/settlements/bfc9b743-b9b3-4906-b06b-da4e185d93bf" + } + ], + "id": "bfc9b743-b9b3-4906-b06b-da4e185d93bf", + "merchantRefNum": "efdf1a69-2380-4c4f-a392-62ce80970b65", + "txnTime": "2016-08-08T06:36:50Z", + "status": "PENDING", + "amount": 100, + "availableToRefund": 100 + } + ] + } + RESPONSE + end + + def successful_authorize_response + <<-RESPONSE + { + "links": [ + { + "rel": "self", + "href": "https://api.test.netbanx.com/cardpayments/v1/accounts/1234567890/auths/b8c53059-9da3-4054-8caf-3769161a3cdc" + } + ], + "id": "b8c53059-9da3-4054-8caf-3769161a3cdc", + "merchantRefNum": "5e6eb079-33ab-44d5-a376-e7bb5bd5c256", + "txnTime": "2016-08-08T06:46:26Z", + "status": "COMPLETED", + "amount": 100, + "settleWithAuth": false, + "availableToSettle": 100, + "card": { + "type": "VI", + "lastDigits": "2345", + "cardExpiry": { + "month": 9, + "year": 2017 + } + }, + "authCode": "143980", + "billingDetails": { + "street": "456 My Street", + "city": "Ottawa", + "country": "CA", + "zip": "K1C2N6" + }, + "merchantDescriptor": { + "dynamicDescriptor": "Test", + "phone": "123-1234123" + }, + "currencyCode": "CAD", + "avsResponse": "MATCH", + "cvvVerification": "MATCH" + } + RESPONSE + end + + def failed_authorize_response + <<-RESPONSE + { + "links": [ + { + "rel": "self", + "href": "https://api.test.netbanx.com/cardpayments/v1/accounts/1234567890/auths/e62d1415-c952-4f09-9e4f-cd733adfca0c" + } + ], + "id": "e62d1415-c952-4f09-9e4f-cd733adfca0c", + "merchantRefNum": "46a3e959-dbec-4b65-8a6f-d80d83e0f2fa", + "error": { + "code": "3022", + "message": "The card has been declined due to insufficient funds.", + "links": [ + { + "rel": "errorinfo", + "href": "https://developer.optimalpayments.com/en/documentation/card-payments-api/error-3022" + } + ] + }, + "riskReasonCode": [ + 1059 + ], + "settleWithAuth": false, + "cvvVerification": "MATCH" + } + RESPONSE + end + + def successful_capture_response + <<-RESPONSE + { + "links": [ + { + "rel": "self", + "href": "https://api.test.netbanx.com/cardpayments/v1/accounts/1234567890/settlements/11e0906b-6596-4490-b0e3-825f71a82799" + } + ], + "id": "11e0906b-6596-4490-b0e3-825f71a82799", + "merchantRefNum": "0e2c8d4d-f03e-4251-9311-955f5c159b90", + "txnTime": "2016-08-08T06:49:05Z", + "status": "PENDING", + "amount": 100, + "availableToRefund": 100 + } + RESPONSE + end + + def failed_capture_response + <<-RESPONSE + { + "links": [ + { + "rel": "self", + "href": "https://api.test.netbanx.com/cardpayments/v1/accounts/1234567890/settlements/4fd1068f-2543-4a38-bb48-2122a1e75c61" + } + ], + "id": "4fd1068f-2543-4a38-bb48-2122a1e75c61", + "merchantRefNum": "012b0c60-925b-4907-8a56-c9c2336b8648", + "error": { + "code": "3201", + "message": "The authorization ID included in this settlement request could not be found.", + "links": [ + { + "rel": "errorinfo", + "href": "https://developer.optimalpayments.com/en/documentation/card-payments-api/error-3201" + } + ] + } + } + RESPONSE + end + + def successful_refund_response + <<-RESPONSE + { + "links": [ + { + "rel": "self", + "href": "https://api.test.netbanx.com/cardpayments/v1/accounts/1234567890/refunds/03a449a3-1709-4ce2-87ba-1e9e8c69eaea" + } + ], + "id": "03a449a3-1709-4ce2-87ba-1e9e8c69eaea", + "merchantRefNum": "6a9608ba-6c81-4810-ae02-3d9a38ac3400", + "txnTime": "2016-08-08T07:42:03Z", + "status": "PENDING", + "amount": 100 + } + RESPONSE + end + + def failed_refund_response + <<-RESPONSE + { + "links": [ + { + "rel": "self", + "href": "https://api.test.netbanx.com/cardpayments/v1/accounts/1234567890/refunds/6cc833f9-9d4d-416a-a5a4-18ed785450a2" + } + ], + "id": "6cc833f9-9d4d-416a-a5a4-18ed785450a2", + "merchantRefNum": "59332fd0-0296-4ae4-b9a4-aa086237b238", + "error": { + "code": "3406", + "message": "The settlement you are attempting to refund has not been batched yet. There are no settled funds available to refund.", + "links": [ + { + "rel": "errorinfo", + "href": "https://developer.optimalpayments.com/en/documentation/card-payments-api/error-3406" + } + ] + } + } + RESPONSE + end + + def successful_void_response + <<-RESPONSE + { + "links": [ + { + "rel": "self", + "href": "https://api.test.netbanx.com/cardpayments/v1/accounts/1234567890/voidauths/64b3f52e-cd0d-474a-8f16-bb9c559d3bca" + } + ], + "id": "64b3f52e-cd0d-474a-8f16-bb9c559d3bca", + "merchantRefNum": "60edbe35-5779-403b-a633-8685bd7acb4c", + "txnTime": "2016-08-08T06:50:32Z", + "status": "COMPLETED", + "amount": 100 + } + RESPONSE + end + + def failed_void_response + <<-RESPONSE + { + "links": [ + { + "rel": "self", + "href": "https://api.test.netbanx.com/cardpayments/v1/accounts/1234567890/voidauths/96d5c070-e402-4d2d-ae55-addeca4fc4ef" + } + ], + "id": "96d5c070-e402-4d2d-ae55-addeca4fc4ef", + "merchantRefNum": "5628cddf-ff53-4b70-95e2-ed4762f60c6e", + "error": { + "code": "3500", + "message": "The confirmation number included in this request could not be found.", + "links": [ + { + "rel": "errorinfo", + "href": "https://developer.optimalpayments.com/en/documentation/card-payments-api/error-3500" + } + ] + } + } + RESPONSE + end + + def successful_store_response + <<-RESPONSE + { + "id": "2f840ab3-0e71-4387-bad3-4705e6f4b015", + "status": "ACTIVE", + "merchantCustomerId": "5e9d1ab0f847d147ffe872a9faf76d98", + "locale": "en_GB", + "paymentToken": "PJzuA8s6c6pSIs4", + "addresses": [], + "cards": [ + { + "status": "ACTIVE", + "id": "e4a3cd5a-56db-4d9b-97d3-fdd9ab3bd0f4", + "cardBin": "453091", + "lastDigits": "2345", + "cardExpiry": { + "year": 2017, + "month": 9 + }, + "holderName": "Longbob Longsen", + "cardType": "VI", + "paymentToken": "C6gmdUA1xWT8RsC", + "defaultCardIndicator": true + } + ] + } + RESPONSE + end + + def successful_unstore_response + <<-RESPONSE + { + "id": "2f840ab3-0e71-4387-bad3-4705e6f4b015", + "status": "ACTIVE", + "merchantCustomerId": "5e9d1ab0f847d147ffe872a9faf76d98", + "locale": "en_GB", + "paymentToken": "PJzuA8s6c6pSIs4", + "addresses": [], + "cards": [ + { + "status": "ACTIVE", + "id": "e4a3cd5a-56db-4d9b-97d3-fdd9ab3bd0f4", + "cardBin": "453091", + "lastDigits": "2345", + "cardExpiry": { + "year": 2017, + "month": 9 + }, + "holderName": "Longbob Longsen", + "cardType": "VI", + "paymentToken": "C6gmdUA1xWT8RsC", + "defaultCardIndicator": true + } + ] + } + RESPONSE + end +end diff --git a/test/unit/gateways/netbilling_test.rb b/test/unit/gateways/netbilling_test.rb index 691cc309a62..724e3037542 100644 --- a/test/unit/gateways/netbilling_test.rb +++ b/test/unit/gateways/netbilling_test.rb @@ -2,7 +2,7 @@ class NetbillingTest < Test::Unit::TestCase include CommStub - + def setup @gateway = NetbillingGateway.new(:login => 'login') @@ -10,10 +10,10 @@ def setup @amount = 100 @options = { :billing_address => address } end - + def test_successful_request @gateway.expects(:ssl_post).returns(successful_purchase_response) - + assert response = @gateway.purchase(@amount, @credit_card, @options) assert_success response assert_equal '110270311543', response.authorization @@ -22,35 +22,35 @@ def test_successful_request def test_unsuccessful_request @gateway.expects(:ssl_post).returns(unsuccessful_purchase_response) - + assert response = @gateway.purchase(@amount, @credit_card, @options) assert_failure response assert response.test? end - + def test_avs_result @gateway.expects(:ssl_post).returns(successful_purchase_response) - + response = @gateway.purchase(@amount, @credit_card, @options) assert_equal 'X', response.avs_result['code'] end - + def test_cvv_result @gateway.expects(:ssl_post).returns(successful_purchase_response) - + response = @gateway.purchase(@amount, @credit_card, @options) assert_equal 'M', response.cvv_result['code'] end - + def test_site_tag_sent_if_provided @gateway = NetbillingGateway.new(:login => 'login', :site_tag => 'dummy-site-tag') - + response = stub_comms do @gateway.purchase(@amount, @credit_card, @options) end.check_request do |endpoint, data, headers| assert_match(/site_tag=dummy-site-tag/, data) end.respond_with(successful_purchase_response) - + assert_success response end @@ -60,16 +60,90 @@ def test_site_tag_not_sent_if_not_provided end.check_request do |endpoint, data, headers| assert_no_match(/site_tag/, data) end.respond_with(successful_purchase_response) - + + assert_success response + end + + def test_successful_repeat_purchase + @gateway.expects(:ssl_post).returns(successful_repeat_purchase_response) + + assert response = @gateway.purchase(@amount, '112281536850', @options) + assert_success response + assert_equal '112232503575', response.authorization + assert response.test? + end + + def test_unsuccessful_repeat_purchase_invalid_trans_id + http_response = mock() + http_response.stubs(:code).returns('611') + http_response.stubs(:body).returns('') + http_response.stubs(:message).returns(unsuccessful_repeat_purchase_invalid_trans_id_response) + response_error = ::ActiveMerchant::ResponseError.new(http_response) + @gateway.expects(:ssl_post).raises(response_error) + + assert response = @gateway.purchase(@amount, '1111', @options) + assert_failure response + assert_match(/no record found/i, response.message) + assert response.test? + end + + def test_successful_store + @gateway.expects(:ssl_post).returns(successful_store_response) + + assert response = @gateway.store(@credit_card, @options) assert_success response + assert response.test? + end + + def test_unsuccessful_store_invalid_billing_info + http_response = mock() + http_response.stubs(:code).returns('699') + http_response.stubs(:body).returns('') + http_response.stubs(:message).returns(unsuccessful_store_invalid_billing_info_response) + response_error = ::ActiveMerchant::ResponseError.new(http_response) + @gateway.expects(:ssl_post).raises(response_error) + + assert response = @gateway.store(@credit_card, @options) + assert_failure response + assert_match(/invalid credit card number/i, response.message) + assert response.test? + end + + def test_transcript_scrubbing + assert_equal scrubbed_transcript, @gateway.scrub(transcript) end private + def successful_purchase_response - "avs_code=X&cvv2_code=M&status_code=1&auth_code=999999&trans_id=110270311543&auth_msg=TEST+APPROVED&auth_date=2008-01-25+16:43:54" + 'avs_code=X&cvv2_code=M&status_code=1&auth_code=999999&trans_id=110270311543&auth_msg=TEST+APPROVED&auth_date=2008-01-25+16:43:54' end - + def unsuccessful_purchase_response - "status_code=0&auth_msg=CARD+EXPIRED&trans_id=110492608613&auth_date=2008-01-25+17:47:44" + 'status_code=0&auth_msg=CARD+EXPIRED&trans_id=110492608613&auth_date=2008-01-25+17:47:44' + end + + def successful_repeat_purchase_response + 'avs_code=X&cvv2_code=M&status_code=1&processor=TEST&auth_code=999999&settle_amount=1.00&settle_currency=USD&trans_id=112232503575&auth_msg=TEST+APPROVED&auth_date=2014-12-29+18:23:40' + end + + def unsuccessful_repeat_purchase_invalid_trans_id_response + 'No Record Found For Specified ID' + end + + def successful_store_response + 'status_code=T&processor=TEST&settle_amount=0.00&settle_currency=USD&trans_id=112235386882&auth_msg=OFFLINE+RECORD&auth_date=2014-12-29+18:23:43' + end + + def unsuccessful_store_invalid_billing_info_response + '20111: Invalid credit card number: 123' + end + + def transcript + 'amount=1.00&description=Internet+purchase&bill_name1=Longbob&bill_name2=Longsen&card_number=4444111111111119&card_expire=0916&card_cvv2=123&bill_street=1600+Amphitheatre+Parkway&cust_phone=650-253-0001&bill_zip=94043&bill_city=Mountain+View&bill_country=US&bill_state=CA&account_id=104901072025&pay_type=C&tran_type=S' + end + + def scrubbed_transcript + 'amount=1.00&description=Internet+purchase&bill_name1=Longbob&bill_name2=Longsen&card_number=[FILTERED]&card_expire=0916&card_cvv2=[FILTERED]&bill_street=1600+Amphitheatre+Parkway&cust_phone=650-253-0001&bill_zip=94043&bill_city=Mountain+View&bill_country=US&bill_state=CA&account_id=104901072025&pay_type=C&tran_type=S' end end diff --git a/test/unit/gateways/netpay_test.rb b/test/unit/gateways/netpay_test.rb index 55ba0dc273f..6b107d2e9f5 100644 --- a/test/unit/gateways/netpay_test.rb +++ b/test/unit/gateways/netpay_test.rb @@ -34,17 +34,17 @@ def test_successful_purchase @gateway.expects(:ssl_post).with( anything, all_of( - includes("StoreId=12345"), - includes("UserName=login"), - includes("Password=password"), - includes("ResourceName=Auth"), - includes("Total=10.00"), + includes('StoreId=12345'), + includes('UserName=login'), + includes('Password=password'), + includes('ResourceName=Auth'), + includes('Total=10.00'), includes("CardNumber=#{@credit_card.number}"), - includes("ExpDate=" + CGI.escape("09/#{@credit_card.year.to_s[-2..-1]}")), + includes('ExpDate=' + CGI.escape("09/#{@credit_card.year.to_s[-2..-1]}")), includes("CustomerName=#{CGI.escape(@credit_card.name)}"), includes("CVV2=#{@credit_card.verification_value}"), includes("Comments=#{CGI.escape(@options[:description])}"), - includes("CurrencyCode=484") + includes('CurrencyCode=484') ) ).returns(successful_response) @@ -76,22 +76,21 @@ def test_unsuccessful_purchase assert response.test? end - def test_successful_authorize @gateway.expects(:ssl_post).with( anything, all_of( - includes("StoreId=12345"), - includes("UserName=login"), - includes("Password=password"), - includes("ResourceName=PreAuth"), - includes("Total=10.00"), + includes('StoreId=12345'), + includes('UserName=login'), + includes('Password=password'), + includes('ResourceName=PreAuth'), + includes('Total=10.00'), includes("CardNumber=#{@credit_card.number}"), - includes("ExpDate=" + CGI.escape("09/#{@credit_card.year.to_s[-2..-1]}")), + includes('ExpDate=' + CGI.escape("09/#{@credit_card.year.to_s[-2..-1]}")), includes("CustomerName=#{CGI.escape(@credit_card.name)}"), includes("CVV2=#{@credit_card.verification_value}"), includes("Comments=#{CGI.escape(@options[:description])}"), - includes("CurrencyCode=484") + includes('CurrencyCode=484') ) ).returns(successful_response) @@ -107,7 +106,7 @@ def test_successful_capture anything, all_of( includes('ResourceName=PostAuth'), - includes("Total=10.00"), + includes('Total=10.00'), includes("OrderId=#{@order_id}") ) ).returns(successful_response) @@ -120,9 +119,9 @@ def test_successful_void anything, all_of( includes('ResourceName=Refund'), - includes("Total=10.00"), + includes('Total=10.00'), includes("OrderId=#{@order_id}"), - includes("CurrencyCode=484") + includes('CurrencyCode=484') ) ).returns(successful_response) assert response = @gateway.void("#{@order_id}|10.00|484") @@ -134,7 +133,7 @@ def test_successful_refund anything, all_of( includes('ResourceName=Credit'), - includes("Total=10.00"), + includes('Total=10.00'), includes("OrderId=#{@order_id}") ) ).returns(successful_response) diff --git a/test/unit/gateways/transnational_test.rb b/test/unit/gateways/network_merchants_test.rb similarity index 68% rename from test/unit/gateways/transnational_test.rb rename to test/unit/gateways/network_merchants_test.rb index 3a4003cf5f3..57fbb837167 100644 --- a/test/unit/gateways/transnational_test.rb +++ b/test/unit/gateways/network_merchants_test.rb @@ -1,13 +1,14 @@ require 'test_helper' -class TransnationalTest < Test::Unit::TestCase +class NetworkMerchantsTest < Test::Unit::TestCase def setup - @gateway = TransnationalGateway.new( + @gateway = NetworkMerchantsGateway.new( :login => 'login', :password => 'password' ) @credit_card = credit_card + @check = check @amount = 100 @options = { @@ -17,6 +18,17 @@ def setup } end + def test_add_swipe_data_with_creditcard + @credit_card.track_data = 'data' + + @gateway.expects(:ssl_post).with do |_, body| + body.include?('track_1=data') + end.returns(successful_purchase_response) + + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + end + def test_successful_purchase @gateway.expects(:ssl_post).returns(successful_purchase_response) @@ -51,7 +63,7 @@ def test_purchase_and_store assert response = @gateway.purchase(@amount, @credit_card, @options.merge(:store => true)) assert_success response assert_equal 'SUCCESS', response.message - assert_equal "1378262091", response.params['customer_vault_id'] + assert_equal '1378262091', response.params['customer_vault_id'] end def test_authorize @@ -85,22 +97,22 @@ def test_void assert response = @gateway.void('1869041506', @options) assert_success response - assert_equal "Transaction Void Successful", response.message + assert_equal 'Transaction Void Successful', response.message end def test_refund @gateway.expects(:ssl_post).returns(successful_refund) - assert response = @gateway.refund(50, "1869041506") + assert response = @gateway.refund(50, '1869041506') assert_success response - assert_equal "SUCCESS", response.message - assert_equal "1869043195", response.authorization + assert_equal 'SUCCESS', response.message + assert_equal '1869043195', response.authorization end def test_store @gateway.expects(:ssl_post).returns(successful_store) - assert store = @gateway.store(@credit_card, @options) + store = @gateway.store(@credit_card, @options) assert_success store assert_equal '1200085822', store.authorization assert_equal '1200085822', store.params['customer_vault_id'] @@ -109,7 +121,7 @@ def test_store def test_store_check @gateway.expects(:ssl_post).returns(successful_store) - assert store = @gateway.store(@check, @options) + store = @gateway.store(@check, @options) assert_success store assert_equal '1200085822', store.authorization assert_equal '1200085822', store.params['customer_vault_id'] @@ -118,8 +130,8 @@ def test_store_check def test_store_failure @gateway.expects(:ssl_post).returns(failed_store) - @credit_card.number = "123" - assert store = @gateway.store(@creditcard, @options) + @credit_card.number = '123' + store = @gateway.store(@credit_card, @options) assert_failure store assert store.message.include?('Billing Information missing') assert_nil store.authorization @@ -131,7 +143,7 @@ def test_unstore assert unstore = @gateway.unstore('1200085822') assert_success unstore - assert_equal "Customer Deleted", unstore.message + assert_equal 'Customer Deleted', unstore.message end def test_purchase_on_stored_card @@ -139,75 +151,92 @@ def test_purchase_on_stored_card assert purchase = @gateway.purchase(@amount, 1200085822, @options) assert_success purchase - assert_equal "SUCCESS", purchase.message + assert_equal 'SUCCESS', purchase.message assert_equal '1869047279', purchase.authorization end def test_invalid_login - gateway = TransnationalGateway.new(:login => '', :password => '') + gateway = NetworkMerchantsGateway.new(:login => '', :password => '') gateway.expects(:ssl_post).returns(failed_login) assert response = gateway.purchase(@amount, @credit_card, @options) assert_failure response assert_equal 'Invalid Username', response.message end + def test_currency_uses_default_when_not_provided + @gateway.expects(:ssl_post).with do |_, body| + body.include?('currency=USD') + end.returns(successful_purchase_response) + + @gateway.purchase(@amount, @credit_card, @options) + end + + def test_provided_currency_overrides_default + @options.update(currency: 'EUR') + @gateway.expects(:ssl_post).with do |_, body| + body.include?('currency=EUR') + end.returns(successful_purchase_response) + + @gateway.purchase(@amount, @credit_card, @options) + end + private # Place raw successful response from gateway here def successful_purchase_response - "response=1&responsetext=SUCCESS&authcode=123456&transactionid=1869031575&avsresponse=N&cvvresponse=N&orderid=1&type=auth&response_code=100&merchant_defined_field_6=&merchant_defined_field_7=&customer_vault_id=" + 'response=1&responsetext=SUCCESS&authcode=123456&transactionid=1869031575&avsresponse=N&cvvresponse=N&orderid=1&type=auth&response_code=100&merchant_defined_field_6=&merchant_defined_field_7=&customer_vault_id=' end # Place raw failed response from gateway here def failed_purchase_response - "response=2&responsetext=DECLINE&authcode=&transactionid=1869031793&avsresponse=N&cvvresponse=N&orderid=1&type=sale&response_code=200&merchant_defined_field_6=&merchant_defined_field_7=&customer_vault_id=" + 'response=2&responsetext=DECLINE&authcode=&transactionid=1869031793&avsresponse=N&cvvresponse=N&orderid=1&type=sale&response_code=200&merchant_defined_field_6=&merchant_defined_field_7=&customer_vault_id=' end def successful_check_purchase - "response=1&responsetext=SUCCESS&authcode=123456&transactionid=1869039732&avsresponse=&cvvresponse=&orderid=1&type=sale&response_code=100&merchant_defined_field_6=&merchant_defined_field_7=&customer_vault_id=" + 'response=1&responsetext=SUCCESS&authcode=123456&transactionid=1869039732&avsresponse=&cvvresponse=&orderid=1&type=sale&response_code=100&merchant_defined_field_6=&merchant_defined_field_7=&customer_vault_id=' end def successful_purchase_and_store - "response=1&responsetext=SUCCESS&authcode=123456&transactionid=1869036881&avsresponse=N&cvvresponse=N&orderid=1&type=sale&response_code=100&merchant_defined_field_6=&merchant_defined_field_7=&customer_vault_id=1378262091" + 'response=1&responsetext=SUCCESS&authcode=123456&transactionid=1869036881&avsresponse=N&cvvresponse=N&orderid=1&type=sale&response_code=100&merchant_defined_field_6=&merchant_defined_field_7=&customer_vault_id=1378262091' end def successful_authorize - "response=1&responsetext=SUCCESS&authcode=123456&transactionid=1869041506&avsresponse=N&cvvresponse=N&orderid=1&type=auth&response_code=100&merchant_defined_field_6=&merchant_defined_field_7=&customer_vault_id=" + 'response=1&responsetext=SUCCESS&authcode=123456&transactionid=1869041506&avsresponse=N&cvvresponse=N&orderid=1&type=auth&response_code=100&merchant_defined_field_6=&merchant_defined_field_7=&customer_vault_id=' end def successful_capture - "response=1&responsetext=SUCCESS&authcode=123456&transactionid=1869041506&avsresponse=&cvvresponse=&orderid=1&type=capture&response_code=100&merchant_defined_field_6=&merchant_defined_field_7=&customer_vault_id=" + 'response=1&responsetext=SUCCESS&authcode=123456&transactionid=1869041506&avsresponse=&cvvresponse=&orderid=1&type=capture&response_code=100&merchant_defined_field_6=&merchant_defined_field_7=&customer_vault_id=' end def failed_capture - "response=3&responsetext=Invalid Transaction ID / Object ID specified: REFID:342421573&authcode=&transactionid=&avsresponse=&cvvresponse=&orderid=&type=capture&response_code=300&merchant_defined_field_6=&merchant_defined_field_7=&customer_vault_id=" + 'response=3&responsetext=Invalid Transaction ID / Object ID specified: REFID:342421573&authcode=&transactionid=&avsresponse=&cvvresponse=&orderid=&type=capture&response_code=300&merchant_defined_field_6=&merchant_defined_field_7=&customer_vault_id=' end def successful_void - "response=1&responsetext=Transaction Void Successful&authcode=123456&transactionid=1869042801&avsresponse=&cvvresponse=&orderid=1&type=void&response_code=100&merchant_defined_field_6=&merchant_defined_field_7=&customer_vault_id=" + 'response=1&responsetext=Transaction Void Successful&authcode=123456&transactionid=1869042801&avsresponse=&cvvresponse=&orderid=1&type=void&response_code=100&merchant_defined_field_6=&merchant_defined_field_7=&customer_vault_id=' end def successful_refund - "response=1&responsetext=SUCCESS&authcode=&transactionid=1869043195&avsresponse=&cvvresponse=&orderid=1&type=refund&response_code=100&merchant_defined_field_6=&merchant_defined_field_7=&customer_vault_id=" + 'response=1&responsetext=SUCCESS&authcode=&transactionid=1869043195&avsresponse=&cvvresponse=&orderid=1&type=refund&response_code=100&merchant_defined_field_6=&merchant_defined_field_7=&customer_vault_id=' end def successful_store - "response=1&responsetext=Customer Added&authcode=&transactionid=&avsresponse=&cvvresponse=&orderid=&type=&response_code=100&merchant_defined_field_6=&merchant_defined_field_7=&customer_vault_id=1200085822" + 'response=1&responsetext=Customer Added&authcode=&transactionid=&avsresponse=&cvvresponse=&orderid=&type=&response_code=100&merchant_defined_field_6=&merchant_defined_field_7=&customer_vault_id=1200085822' end def failed_store - "response=3&responsetext=Billing Information missing REFID:342424380&authcode=&transactionid=&avsresponse=&cvvresponse=&orderid=&type=&response_code=300&merchant_defined_field_6=&merchant_defined_field_7=&customer_vault_id=" + 'response=3&responsetext=Billing Information missing REFID:342424380&authcode=&transactionid=&avsresponse=&cvvresponse=&orderid=&type=&response_code=300&merchant_defined_field_6=&merchant_defined_field_7=&customer_vault_id=' end def successful_unstore - "response=1&responsetext=Customer Deleted&authcode=&transactionid=&avsresponse=&cvvresponse=&orderid=&type=&response_code=100&merchant_defined_field_6=&merchant_defined_field_7=&customer_vault_id=" + 'response=1&responsetext=Customer Deleted&authcode=&transactionid=&avsresponse=&cvvresponse=&orderid=&type=&response_code=100&merchant_defined_field_6=&merchant_defined_field_7=&customer_vault_id=' end def successful_purchase_on_stored_card - "response=1&responsetext=SUCCESS&authcode=123456&transactionid=1869047279&avsresponse=N&cvvresponse=&orderid=1&type=sale&response_code=100&merchant_defined_field_6=&merchant_defined_field_7=&customer_vault_id=1138093627" + 'response=1&responsetext=SUCCESS&authcode=123456&transactionid=1869047279&avsresponse=N&cvvresponse=&orderid=1&type=sale&response_code=100&merchant_defined_field_6=&merchant_defined_field_7=&customer_vault_id=1138093627' end def failed_login - "response=3&responsetext=Invalid Username&authcode=&transactionid=0&avsresponse=&cvvresponse=&orderid=1&type=sale&response_code=300" + 'response=3&responsetext=Invalid Username&authcode=&transactionid=0&avsresponse=&cvvresponse=&orderid=1&type=sale&response_code=300' end end diff --git a/test/unit/gateways/nmi_test.rb b/test/unit/gateways/nmi_test.rb index 999f9607152..72b90edaa32 100644 --- a/test/unit/gateways/nmi_test.rb +++ b/test/unit/gateways/nmi_test.rb @@ -1,29 +1,741 @@ require 'test_helper' -require 'unit/gateways/authorize_net_test' -class NmiTest < AuthorizeNetTest +class NmiTest < Test::Unit::TestCase + include CommStub + def setup - super - @gateway = NmiGateway.new(:login => 'X', :password => 'Y') + @gateway = NmiGateway.new(fixtures(:nmi)) + + @amount = 100 + @credit_card = credit_card + @check = check + + @merchant_defined_fields = { merchant_defined_field_8: 'value8' } + + @transaction_options = { + recurring: true, order_id: '#1001', description: 'AM test', currency: 'GBP', dup_seconds: 15, + customer: '123', tax: 5.25, shipping: 10.51, ponumber: 1002 + } end - def test_credit_card_purchase_no_recurring_flag + def test_successful_purchase response = stub_comms do @gateway.purchase(@amount, @credit_card) end.check_request do |endpoint, data, headers| - assert_no_match(/x_recurring_billing/, data) + assert_match(/username=#{@gateway.options[:login]}/, data) + assert_match(/password=#{@gateway.options[:password]}/, data) + assert_match(/type=sale/, data) + assert_match(/amount=1.00/, data) + assert_match(/payment=creditcard/, data) + assert_match(/ccnumber=#{@credit_card.number}/, data) + assert_match(/cvv=#{@credit_card.verification_value}/, data) + assert_match(/ccexp=#{sprintf("%.2i", @credit_card.month)}#{@credit_card.year.to_s[-2..-1]}/, data) + assert_not_match(/dup_seconds/, data) + end.respond_with(successful_purchase_response) + + assert_success response + assert response.test? + assert_equal '2762757839#creditcard', response.authorization + end + + def test_purchase_with_options + options = @transaction_options.merge(@merchant_defined_fields) + + response = stub_comms do + @gateway.purchase(@amount, @credit_card, options) + end.check_request do |endpoint, data, headers| + test_transaction_options(data) + + assert_match(/merchant_defined_field_8=value8/, data) + end.respond_with(successful_purchase_response) + + assert_success response + end + + def test_failed_purchase + response = stub_comms do + @gateway.purchase(@amount, @credit_card) + end.respond_with(failed_purchase_response) + + assert_failure response + assert response.test? + assert_equal 'DECLINE', response.message + end + + def test_successful_purchase_with_echeck + response = stub_comms do + @gateway.purchase(@amount, @check) + end.check_request do |endpoint, data, headers| + assert_match(/username=#{@gateway.options[:login]}/, data) + assert_match(/password=#{@gateway.options[:password]}/, data) + assert_match(/type=sale/, data) + assert_match(/amount=1.00/, data) + assert_match(/payment=check/, data) + assert_match(/firstname=#{@check.first_name}/, data) + assert_match(/lastname=#{@check.last_name}/, data) + assert_match(/checkname=#{@check.name}/, CGI.unescape(data)) + assert_match(/checkaba=#{@check.routing_number}/, data) + assert_match(/checkaccount=#{@check.account_number}/, data) + assert_match(/account_holder_type=#{@check.account_holder_type}/, data) + assert_match(/account_type=#{@check.account_type}/, data) + assert_match(/sec_code=WEB/, data) + end.respond_with(successful_echeck_purchase_response) + + assert_success response + assert response.test? + assert_equal '2762759808#check', response.authorization + end + + def test_failed_purchase_with_echeck + response = stub_comms do + @gateway.purchase(@amount, @check) + end.respond_with(failed_echeck_purchase_response) + + assert_failure response + assert response.test? + assert_equal 'FAILED', response.message + end + + def test_authorize_with_options + options = @transaction_options.merge(@merchant_defined_fields) + + response = stub_comms do + @gateway.authorize(@amount, @credit_card, options) + end.check_request do |endpoint, data, headers| + test_transaction_options(data) + + assert_match(/merchant_defined_field_8=value8/, data) end.respond_with(successful_purchase_response) assert_success response end - def test_credit_card_purchase_with_recurring_flag + def test_successful_authorize_and_capture response = stub_comms do - @gateway.purchase(@amount, @credit_card, :recurring => true) + @gateway.authorize(@amount, @credit_card) end.check_request do |endpoint, data, headers| - assert_match(/x_recurring_billing=TRUE/, data) + assert_match(/username=#{@gateway.options[:login]}/, data) + assert_match(/password=#{@gateway.options[:password]}/, data) + assert_match(/type=auth/, data) + assert_match(/payment=creditcard/, data) + assert_match(/ccnumber=#{@credit_card.number}/, data) + assert_match(/cvv=#{@credit_card.verification_value}/, data) + assert_match(/ccexp=#{sprintf("%.2i", @credit_card.month)}#{@credit_card.year.to_s[-2..-1]}/, data) + end.respond_with(successful_authorization_response) + + assert_success response + assert_equal '2762787830#creditcard', response.authorization + + capture = stub_comms do + @gateway.capture(@amount, response.authorization) + end.check_request do |endpoint, data, headers| + assert_match(/username=#{@gateway.options[:login]}/, data) + assert_match(/password=#{@gateway.options[:password]}/, data) + assert_match(/type=capture/, data) + assert_match(/amount=1.00/, data) + assert_match(/transactionid=2762787830/, data) + end.respond_with(successful_capture_response) + + assert_success capture + end + + def test_failed_authorize + response = stub_comms do + @gateway.authorize(@amount, @credit_card) + end.respond_with(failed_authorization_response) + + assert_failure response + assert_equal 'DECLINE', response.message + assert response.test? + end + + def test_failed_capture + response = stub_comms do + @gateway.capture(100, '') + end.respond_with(failed_capture_response) + + assert_failure response + end + + def test_successful_void + response = stub_comms do + @gateway.purchase(@amount, @credit_card) end.respond_with(successful_purchase_response) assert_success response + assert_equal '2762757839#creditcard', response.authorization + + void = stub_comms do + @gateway.void(response.authorization) + end.check_request do |endpoint, data, headers| + assert_match(/username=#{@gateway.options[:login]}/, data) + assert_match(/password=#{@gateway.options[:password]}/, data) + assert_match(/type=void/, data) + assert_match(/transactionid=2762757839/, data) + end.respond_with(successful_void_response) + + assert_success void + end + + def test_failed_void + response = stub_comms do + @gateway.void('5d53a33d960c46d00f5dc061947d998c') + end.respond_with(failed_void_response) + + assert_failure response + end + + def test_successful_refund + response = stub_comms do + @gateway.purchase(@amount, @credit_card) + end.respond_with(successful_purchase_response) + + assert_success response + assert_equal '2762757839#creditcard', response.authorization + + refund = stub_comms do + @gateway.refund(@amount, response.authorization) + end.check_request do |endpoint, data, headers| + assert_match(/username=#{@gateway.options[:login]}/, data) + assert_match(/password=#{@gateway.options[:password]}/, data) + assert_match(/type=refund/, data) + assert_match(/amount=1.00/, data) + assert_match(/transactionid=2762757839/, data) + end.respond_with(successful_refund_response) + + assert_success refund + end + + def test_failed_refund + response = stub_comms do + @gateway.refund(nil, '') + end.respond_with(failed_refund_response) + + assert_failure response + end + + def test_successful_credit + response = stub_comms do + @gateway.credit(@amount, @credit_card) + end.check_request do |endpoint, data, headers| + assert_match(/username=#{@gateway.options[:login]}/, data) + assert_match(/password=#{@gateway.options[:password]}/, data) + assert_match(/type=credit/, data) + assert_match(/amount=1.00/, data) + assert_match(/payment=creditcard/, data) + assert_match(/ccnumber=#{@credit_card.number}/, data) + assert_match(/cvv=#{@credit_card.verification_value}/, data) + assert_match(/ccexp=#{sprintf("%.2i", @credit_card.month)}#{@credit_card.year.to_s[-2..-1]}/, data) + end.respond_with(successful_credit_response) + + assert_success response + + assert_equal '2762828010#creditcard', response.authorization + assert response.test? + end + + def test_credit_with_options + response = stub_comms do + @gateway.credit(@amount, @credit_card, @transaction_options) + end.check_request do |endpoint, data, headers| + test_transaction_options(data) + end.respond_with(successful_credit_response) + + assert_success response + end + + def test_failed_credit + response = stub_comms do + @gateway.credit(@amount, @credit_card) + end.respond_with(failed_credit_response) + + assert_failure response + assert response.test? + assert_match 'Invalid Credit Card', response.message + end + + def test_successful_verify + test_verify + end + + def test_verify_with_options + test_verify(@transaction_options) + end + + def test_failed_verify + response = stub_comms do + @gateway.verify(@credit_card) + end.respond_with(failed_validate_response) + + assert_failure response + assert_match 'Invalid Credit Card', response.message + end + + def test_successful_store + response = stub_comms do + @gateway.store(@credit_card) + end.check_request do |endpoint, data, headers| + assert_match(/username=#{@gateway.options[:login]}/, data) + assert_match(/password=#{@gateway.options[:password]}/, data) + assert_match(/customer_vault=add_customer/, data) + assert_match(/payment=creditcard/, data) + assert_match(/ccnumber=#{@credit_card.number}/, data) + assert_match(/cvv=#{@credit_card.verification_value}/, data) + assert_match(/ccexp=#{sprintf("%.2i", @credit_card.month)}#{@credit_card.year.to_s[-2..-1]}/, data) + end.respond_with(successful_store_response) + + assert_success response + assert response.test? + assert_equal 'Succeeded', response.message + assert response.params['customer_vault_id'] + assert response.authorization.include?(response.params['customer_vault_id']) + end + + def test_failed_store + response = stub_comms do + @gateway.store(@credit_card) + end.respond_with(failed_store_response) + + assert_failure response + assert response.test? + assert_match 'Invalid Credit Card', response.message + end + + def test_successful_store_with_echeck + response = stub_comms do + @gateway.store(@check) + end.check_request do |endpoint, data, headers| + assert_match(/username=#{@gateway.options[:login]}/, data) + assert_match(/password=#{@gateway.options[:password]}/, data) + assert_match(/customer_vault=add_customer/, data) + assert_match(/payment=check/, data) + assert_match(/checkname=#{@check.name}/, CGI.unescape(data)) + assert_match(/checkaba=#{@check.routing_number}/, data) + assert_match(/checkaccount=#{@check.account_number}/, data) + assert_match(/account_holder_type=#{@check.account_holder_type}/, data) + assert_match(/account_type=#{@check.account_type}/, data) + assert_match(/sec_code=WEB/, data) + end.respond_with(successful_echeck_store_response) + + assert_success response + assert response.test? + assert_equal 'Succeeded', response.message + assert response.authorization.include?(response.params['customer_vault_id']) + end + + def test_avs_result + response = stub_comms do + @gateway.authorize(@amount, @credit_card) + end.respond_with(successful_authorization_response) + + assert_equal 'N', response.avs_result['code'] + end + + def test_cvv_result + response = stub_comms do + @gateway.authorize(@amount, @credit_card) + end.respond_with(successful_authorization_response) + + assert_equal 'N', response.cvv_result['code'] + end + + def test_transcript_scrubbing + assert_equal scrubbed_transcript, @gateway.scrub(transcript) + end + + def test_includes_cvv_tag + stub_comms do + @gateway.purchase(@amount, @credit_card) + end.check_request do |endpoint, data, headers| + assert_match(%r{cvv}, data) + end.respond_with(successful_purchase_response) + end + + def test_blank_cvv_not_sent + @credit_card.verification_value = nil + stub_comms do + @gateway.purchase(@amount, @credit_card) + end.check_request do |endpoint, data, headers| + assert_no_match(%r{cvv}, data) + end.respond_with(successful_purchase_response) + + @credit_card.verification_value = ' ' + stub_comms do + @gateway.purchase(@amount, @credit_card) + end.check_request do |endpoint, data, headers| + assert_no_match(%r{cvv}, data) + end.respond_with(successful_purchase_response) + end + + def test_supported_countries + assert_equal 1, + (['US'] | NmiGateway.supported_countries).size + end + + def test_supported_card_types + assert_equal [:visa, :master, :american_express, :discover], NmiGateway.supported_cardtypes + end + + def test_duplicate_window_deprecation + assert_deprecation_warning(NmiGateway::DUP_WINDOW_DEPRECATION_MESSAGE) do + NmiGateway.duplicate_window = nil + end + end + + def test_stored_credential_recurring_cit_initial + options = stored_credential_options(:cardholder, :recurring, :initial) + response = stub_comms do + @gateway.authorize(@amount, @credit_card, options) + end.check_request do |endpoint, data, headers| + assert_match(/initiated_by=customer/, data) + assert_match(/stored_credential_indicator=stored/, data) + assert_match(/billing_method=recurring/, data) + refute_match(/initial_transaction_id/, data) + end.respond_with(successful_authorization_response) + + assert_success response + end + + def test_stored_credential_recurring_cit_used + options = stored_credential_options(:cardholder, :recurring, id: 'abc123') + response = stub_comms do + @gateway.authorize(@amount, @credit_card, options) + end.check_request do |endpoint, data, headers| + assert_match(/initiated_by=customer/, data) + assert_match(/stored_credential_indicator=used/, data) + assert_match(/billing_method=recurring/, data) + assert_match(/initial_transaction_id=abc123/, data) + end.respond_with(successful_authorization_response) + + assert_success response + end + + def test_stored_credential_recurring_mit_initial + options = stored_credential_options(:merchant, :recurring, :initial) + response = stub_comms do + @gateway.authorize(@amount, @credit_card, options) + end.check_request do |endpoint, data, headers| + assert_match(/initiated_by=merchant/, data) + assert_match(/stored_credential_indicator=stored/, data) + assert_match(/billing_method=recurring/, data) + refute_match(/initial_transaction_id/, data) + end.respond_with(successful_authorization_response) + + assert_success response + end + + def test_stored_credential_recurring_mit_used + options = stored_credential_options(:merchant, :recurring, id: 'abc123') + response = stub_comms do + @gateway.authorize(@amount, @credit_card, options) + end.check_request do |endpoint, data, headers| + assert_match(/initiated_by=merchant/, data) + assert_match(/stored_credential_indicator=used/, data) + assert_match(/billing_method=recurring/, data) + assert_match(/initial_transaction_id=abc123/, data) + end.respond_with(successful_authorization_response) + + assert_success response + end + + def test_stored_credential_installment_cit_initial + options = stored_credential_options(:cardholder, :installment, :initial) + response = stub_comms do + @gateway.authorize(@amount, @credit_card, options) + end.check_request do |endpoint, data, headers| + assert_match(/initiated_by=customer/, data) + assert_match(/stored_credential_indicator=stored/, data) + assert_match(/billing_method=installment/, data) + refute_match(/initial_transaction_id/, data) + end.respond_with(successful_authorization_response) + + assert_success response + end + + def test_stored_credential_installment_cit_used + options = stored_credential_options(:cardholder, :installment, id: 'abc123') + response = stub_comms do + @gateway.authorize(@amount, @credit_card, options) + end.check_request do |endpoint, data, headers| + assert_match(/initiated_by=customer/, data) + assert_match(/stored_credential_indicator=used/, data) + assert_match(/billing_method=installment/, data) + assert_match(/initial_transaction_id=abc123/, data) + end.respond_with(successful_authorization_response) + + assert_success response + end + + def test_stored_credential_installment_mit_initial + options = stored_credential_options(:merchant, :installment, :initial) + response = stub_comms do + @gateway.authorize(@amount, @credit_card, options) + end.check_request do |endpoint, data, headers| + assert_match(/initiated_by=merchant/, data) + assert_match(/stored_credential_indicator=stored/, data) + assert_match(/billing_method=installment/, data) + refute_match(/initial_transaction_id/, data) + end.respond_with(successful_authorization_response) + + assert_success response + end + + def test_stored_credential_installment_mit_used + options = stored_credential_options(:merchant, :installment, id: 'abc123') + response = stub_comms do + @gateway.authorize(@amount, @credit_card, options) + end.check_request do |endpoint, data, headers| + assert_match(/initiated_by=merchant/, data) + assert_match(/stored_credential_indicator=used/, data) + assert_match(/billing_method=installment/, data) + assert_match(/initial_transaction_id=abc123/, data) + end.respond_with(successful_authorization_response) + + assert_success response + end + + def test_stored_credential_unscheduled_cit_initial + options = stored_credential_options(:cardholder, :unscheduled, :initial) + response = stub_comms do + @gateway.authorize(@amount, @credit_card, options) + end.check_request do |endpoint, data, headers| + assert_match(/initiated_by=customer/, data) + assert_match(/stored_credential_indicator=stored/, data) + refute_match(/billing_method/, data) + refute_match(/initial_transaction_id/, data) + end.respond_with(successful_authorization_response) + + assert_success response + end + + def test_stored_credential_unscheduled_cit_used + options = stored_credential_options(:cardholder, :unscheduled, id: 'abc123') + response = stub_comms do + @gateway.authorize(@amount, @credit_card, options) + end.check_request do |endpoint, data, headers| + assert_match(/initiated_by=customer/, data) + assert_match(/stored_credential_indicator=used/, data) + refute_match(/billing_method/, data) + assert_match(/initial_transaction_id=abc123/, data) + end.respond_with(successful_authorization_response) + + assert_success response + end + + def test_stored_credential_unscheduled_mit_initial + options = stored_credential_options(:merchant, :unscheduled, :initial) + response = stub_comms do + @gateway.authorize(@amount, @credit_card, options) + end.check_request do |endpoint, data, headers| + assert_match(/initiated_by=merchant/, data) + assert_match(/stored_credential_indicator=stored/, data) + refute_match(/billing_method/, data) + refute_match(/initial_transaction_id/, data) + end.respond_with(successful_authorization_response) + + assert_success response + end + + def test_stored_credential_unscheduled_mit_used + options = stored_credential_options(:merchant, :unscheduled, id: 'abc123') + response = stub_comms do + @gateway.authorize(@amount, @credit_card, options) + end.check_request do |endpoint, data, headers| + assert_match(/initiated_by=merchant/, data) + assert_match(/stored_credential_indicator=used/, data) + refute_match(/billing_method/, data) + assert_match(/initial_transaction_id=abc123/, data) + end.respond_with(successful_authorization_response) + + assert_success response + end + + def test_purchase_with_stored_credential + options = stored_credential_options(:merchant, :installment, id: 'abc123') + response = stub_comms do + @gateway.purchase(@amount, @credit_card, options) + end.check_request do |endpoint, data, headers| + assert_match(/initiated_by=merchant/, data) + assert_match(/stored_credential_indicator=used/, data) + assert_match(/billing_method=installment/, data) + assert_match(/initial_transaction_id=abc123/, data) + end.respond_with(successful_authorization_response) + + assert_success response + end + + def test_stored_credential_installment_takes_precedence_over_recurring_option + options = stored_credential_options(:merchant, :installment, id: 'abc123').merge(recurring: true) + response = stub_comms do + @gateway.authorize(@amount, @credit_card, options) + end.check_request do |endpoint, data, headers| + assert_match(/initiated_by=merchant/, data) + assert_match(/stored_credential_indicator=used/, data) + assert_match(/billing_method=installment/, data) + assert_match(/initial_transaction_id=abc123/, data) + end.respond_with(successful_authorization_response) + + assert_success response + end + + def test_stored_credential_unscheduled_takes_precedence_over_recurring_option + options = stored_credential_options(:merchant, :unscheduled, id: 'abc123').merge(recurring: true) + response = stub_comms do + @gateway.authorize(@amount, @credit_card, options) + end.check_request do |endpoint, data, headers| + assert_match(/initiated_by=merchant/, data) + assert_match(/stored_credential_indicator=used/, data) + refute_match(/billing_method/, data) + assert_match(/initial_transaction_id=abc123/, data) + end.respond_with(successful_authorization_response) + + assert_success response + end + + private + + def test_verify(options = {}) + response = stub_comms do + @gateway.verify(@credit_card, options) + end.check_request do |endpoint, data, headers| + assert_match(/username=#{@gateway.options[:login]}/, data) + assert_match(/password=#{@gateway.options[:password]}/, data) + assert_match(/type=validate/, data) + assert_match(/payment=creditcard/, data) + assert_match(/ccnumber=#{@credit_card.number}/, data) + assert_match(/cvv=#{@credit_card.verification_value}/, data) + assert_match(/ccexp=#{sprintf("%.2i", @credit_card.month)}#{@credit_card.year.to_s[-2..-1]}/, + data) + + test_level3_options(data) if options.any? + end.respond_with(successful_validate_response) + + assert_success response + assert_equal 'Succeeded', response.message + end + + def test_level3_options(data) + assert_match(/tax=5.25/, data) + assert_match(/shipping=10.51/, data) + assert_match(/ponumber=1002/, data) + end + + def test_transaction_options(data) + assert_match(/billing_method=recurring/, data) + assert_match(/orderid=#{CGI.escape("#1001")}/, data) + assert_match(/orderdescription=AM\+test/, data) + assert_match(/currency=GBP/, data) + assert_match(/dup_seconds=15/, data) + assert_match(/customer_id=123/, data) + + test_level3_options(data) + end + + def stored_credential_options(*args, id: nil) + { + order_id: '#1001', + description: 'AM test', + currency: 'GBP', + dup_seconds: 15, + customer: '123', + tax: 5.25, + shipping: 10.51, + ponumber: 1002, + stored_credential: stored_credential(*args, id: id) + } + end + + def successful_purchase_response + 'response=1&responsetext=SUCCESS&authcode=123456&transactionid=2762757839&avsresponse=N&cvvresponse=N&orderid=b6c1c57f709cfaa65a5cf5b8532ad181&type=&response_code=100' + end + + def failed_purchase_response + 'response=2&responsetext=DECLINE&authcode=&transactionid=2762766725&avsresponse=N&cvvresponse=N&orderid=f4bd34a5a6089aa822d13352807bdf11&type=&response_code=200' + end + + def successful_echeck_purchase_response + 'response=1&responsetext=SUCCESS&authcode=123456&transactionid=2762759808&avsresponse=&cvvresponse=&orderid=6780868212a4bc8d3d6ffc52d4873587&type=&response_code=100' + end + + def failed_echeck_purchase_response + 'response=2&responsetext=FAILED&authcode=123456&transactionid=2762783009&avsresponse=&cvvresponse=&orderid=8070b75a09d75c3e84e1c17d44bbbf34&type=&response_code=200' + end + + def successful_authorization_response + 'response=1&responsetext=SUCCESS&authcode=123456&transactionid=2762787830&avsresponse=N&cvvresponse=N&orderid=7655856b032e28d2106d724fc26cd04d&type=&response_code=100' + end + + def failed_authorization_response + 'response=2&responsetext=DECLINE&authcode=&transactionid=2762789345&avsresponse=N&cvvresponse=N&orderid=1fe4a8b28a831c6f959d4204158e1ac1&type=&response_code=200' + end + + def successful_capture_response + 'response=1&responsetext=SUCCESS&authcode=123456&transactionid=2762797441&avsresponse=N&cvvresponse=&orderid=&type=&response_code=100' + end + + def failed_capture_response + 'response=2&responsetext=DECLINE&authcode=&transactionid=2762804008&avsresponse=N&cvvresponse=&orderid=&type=&response_code=200' + end + + def successful_void_response + 'response=1&responsetext=Transaction Void Successful&authcode=123456&transactionid=2762811592&avsresponse=&cvvresponse=&orderid=33a327d76cfdb8e98946352607d80eb2&type=void&response_code=100' + end + + def failed_void_response + 'response=3&responsetext=Only transactions pending settlement can be voided REFID:3161855545&authcode=&transactionid=2762816924&avsresponse=&cvvresponse=&orderid=&type=void&response_code=300' + end + + def successful_refund_response + 'response=1&responsetext=SUCCESS&authcode=&transactionid=2762823772&avsresponse=&cvvresponse=&orderid=&type=refund&response_code=100' + end + + def failed_refund_response + 'response=3&responsetext=Invalid Transaction ID specified REFID:3161856100&authcode=&transactionid=&avsresponse=&cvvresponse=&orderid=&type=refund&response_code=300' + end + + def successful_credit_response + 'response=1&responsetext=SUCCESS&authcode=&transactionid=2762828010&avsresponse=&cvvresponse=&orderid=3deb5bbdcba694a09fd7835263ee83ab&type=credit&response_code=100' + end + + def failed_credit_response + 'response=3&responsetext=Invalid Credit Card Number REFID:3162207528&authcode=&transactionid=&avsresponse=&cvvresponse=&orderid=f95e02a07bb77447c8b2001795540771&type=credit&response_code=300' + end + + def successful_validate_response + 'response=1&responsetext=SUCCESS&authcode=&transactionid=2762837000&avsresponse=N&cvvresponse=N&orderid=&type=validate&response_code=100' + end + + def failed_validate_response + 'response=3&responsetext=Invalid Credit Card Number REFID:3162208770&authcode=&transactionid=&avsresponse=&cvvresponse=&orderid=&type=validate&response_code=300' + end + + def successful_store_response + 'response=1&responsetext=Customer Added&authcode=&transactionid=&avsresponse=&cvvresponse=&orderid=bc28d976f4eb7d379c0dffb5a21342ca&type=&response_code=100&customer_vault_id=256806849' + end + + def failed_store_response + 'response=3&responsetext=Invalid Credit Card Number REFID:3162210328&authcode=&transactionid=&avsresponse=&cvvresponse=&orderid=d5efdca79fdc2770fbe56feca8ed5ee6&type=&response_code=300' + end + + def successful_echeck_store_response + 'response=1&responsetext=Customer Added&authcode=&transactionid=&avsresponse=&cvvresponse=&orderid=35b5500a13d23a7e9706fdf3518556b3&type=&response_code=100&customer_vault_id=1910603011' + end + + def transcript + ' + amount=1.00&orderid=c9f2fb356d2a839d315aa6e8d7ed2404&orderdescription=Store+purchase&currency=USD&payment=creditcard&firstname=Longbob&lastname=Longsen&ccnumber=4111111111111111&cvv=917&ccexp=0916&email=&ipaddress=&company=Widgets+Inc&address1=456+My+Street&address2=Apt+1&city=Ottawa&state=ON&country=CA&zip=K1C2N6&phone=%28555%29555-5555&type=sale&username=demo&password=passwordw$thsym%ols + response=1&responsetext=SUCCESS&authcode=123456&transactionid=2767466670&avsresponse=N&cvvresponse=N&orderid=c9f2fb356d2a839d315aa6e8d7ed2404&type=sale&response_code=100 + amount=1.00&orderid=e88df316d8ba3c8c6b98aa93b78facc0&orderdescription=Store+purchase&currency=USD&payment=check&checkname=Jim+Smith&checkaba=123123123&checkaccount=123123123&account_holder_type=personal&account_type=checking&sec_code=WEB&email=&ipaddress=&company=Widgets+Inc&address1=456+My+Street&address2=Apt+1&city=Ottawa&state=ON&country=CA&zip=K1C2N6&phone=%28555%29555-5555&type=sale&username=demo&password=passwordw$thsym%ols + response=1&responsetext=SUCCESS&authcode=123456&transactionid=2767467157&avsresponse=&cvvresponse=&orderid=e88df316d8ba3c8c6b98aa93b78facc0&type=sale&response_code=100 + ' + end + + def scrubbed_transcript + ' + amount=1.00&orderid=c9f2fb356d2a839d315aa6e8d7ed2404&orderdescription=Store+purchase&currency=USD&payment=creditcard&firstname=Longbob&lastname=Longsen&ccnumber=[FILTERED]&cvv=[FILTERED]&ccexp=0916&email=&ipaddress=&company=Widgets+Inc&address1=456+My+Street&address2=Apt+1&city=Ottawa&state=ON&country=CA&zip=K1C2N6&phone=%28555%29555-5555&type=sale&username=demo&password=[FILTERED] + response=1&responsetext=SUCCESS&authcode=123456&transactionid=2767466670&avsresponse=N&cvvresponse=N&orderid=c9f2fb356d2a839d315aa6e8d7ed2404&type=sale&response_code=100 + amount=1.00&orderid=e88df316d8ba3c8c6b98aa93b78facc0&orderdescription=Store+purchase&currency=USD&payment=check&checkname=Jim+Smith&checkaba=[FILTERED]&checkaccount=[FILTERED]&account_holder_type=personal&account_type=checking&sec_code=WEB&email=&ipaddress=&company=Widgets+Inc&address1=456+My+Street&address2=Apt+1&city=Ottawa&state=ON&country=CA&zip=K1C2N6&phone=%28555%29555-5555&type=sale&username=demo&password=[FILTERED] + response=1&responsetext=SUCCESS&authcode=123456&transactionid=2767467157&avsresponse=&cvvresponse=&orderid=e88df316d8ba3c8c6b98aa93b78facc0&type=sale&response_code=100 + ' end end diff --git a/test/unit/gateways/ogone_test.rb b/test/unit/gateways/ogone_test.rb index 09c597a105d..8454a030cfd 100644 --- a/test/unit/gateways/ogone_test.rb +++ b/test/unit/gateways/ogone_test.rb @@ -7,12 +7,15 @@ def setup :user => 'username', :password => 'password', :signature => 'mynicesig', - :signature_encryptor => 'sha512' } + :signature_encryptor => 'sha512', + :timeout => '30' } + @gateway = OgoneGateway.new(@credentials) @credit_card = credit_card + @mastercard = credit_card('5399999999999999', :brand => 'mastercard') @amount = 100 - @identification = "3014726" - @billing_id = "myalias" + @identification = '3014726' + @billing_id = 'myalias' @options = { :order_id => '1', :billing_address => address, @@ -31,7 +34,7 @@ def setup @parameters_d3d = { 'FLAG3D' => 'Y', 'WIN3DS' => 'MAINW', - 'HTTP_ACCEPT' => "*/*" + 'HTTP_ACCEPT' => '*/*' } end @@ -90,6 +93,16 @@ def test_successful_purchase_with_3dsecure assert response.test? end + def test_successful_with_timeout + @gateway.expects(:add_pair).at_least(1) + @gateway.expects(:add_pair).with(anything, 'RTIMEOUT', '30') + @gateway.expects(:ssl_post).returns(successful_purchase_response) + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal '3014726;SAL', response.authorization + assert response.test? + end + def test_successful_authorize @gateway.expects(:add_pair).at_least(1) @gateway.expects(:add_pair).with(anything, 'ECI', '7') @@ -100,6 +113,16 @@ def test_successful_authorize assert response.test? end + def test_successful_authorize_with_mastercard + @gateway.expects(:add_pair).at_least(1) + @gateway.expects(:add_pair).with(anything, 'Operation', 'PAU') + @gateway.expects(:ssl_post).returns(successful_purchase_response) + assert response = @gateway.authorize(@amount, @mastercard, @options) + assert_success response + assert_equal '3014726;PAU', response.authorization + assert response.test? + end + def test_successful_authorize_with_custom_eci @gateway.expects(:add_pair).at_least(1) @gateway.expects(:add_pair).with(anything, 'ECI', '4') @@ -122,7 +145,7 @@ def test_successful_authorize_with_3dsecure def test_successful_capture @gateway.expects(:ssl_post).returns(successful_capture_response) - assert response = @gateway.capture(@amount, "3048326") + assert response = @gateway.capture(@amount, '3048326') assert_success response assert_equal '3048326;SAL', response.authorization assert response.test? @@ -130,7 +153,7 @@ def test_successful_capture def test_successful_capture_with_action_option @gateway.expects(:ssl_post).returns(successful_capture_response) - assert response = @gateway.capture(@amount, "3048326", :action => 'SAS') + assert response = @gateway.capture(@amount, '3048326', :action => 'SAS') assert_success response assert_equal '3048326;SAS', response.authorization assert response.test? @@ -138,7 +161,7 @@ def test_successful_capture_with_action_option def test_successful_void @gateway.expects(:ssl_post).returns(successful_void_response) - assert response = @gateway.void("3048606") + assert response = @gateway.void('3048606') assert_success response assert_equal '3048606;DES', response.authorization assert response.test? @@ -146,8 +169,8 @@ def test_successful_void def test_deprecated_credit @gateway.expects(:ssl_post).returns(successful_referenced_credit_response) - assert_deprecation_warning(Gateway::CREDIT_DEPRECATION_MESSAGE, @gateway) do - assert response = @gateway.credit(@amount, "3049652;SAL") + assert_deprecation_warning(Gateway::CREDIT_DEPRECATION_MESSAGE) do + assert response = @gateway.credit(@amount, '3049652;SAL') assert_success response assert_equal '3049652;RFD', response.authorization assert response.test? @@ -158,34 +181,56 @@ def test_successful_unreferenced_credit @gateway.expects(:ssl_post).returns(successful_unreferenced_credit_response) assert response = @gateway.credit(@amount, @credit_card) assert_success response - assert_equal "3049654;RFD", response.authorization + assert_equal '3049654;RFD', response.authorization assert response.test? end def test_successful_refund @gateway.expects(:ssl_post).returns(successful_referenced_credit_response) - assert response = @gateway.refund(@amount, "3049652") + assert response = @gateway.refund(@amount, '3049652') assert_success response assert_equal '3049652;RFD', response.authorization assert response.test? end + def test_successful_verify + @gateway.expects(:ssl_post).times(2).returns(successful_authorize_response).then.returns(successful_void_response) + assert response = @gateway.verify(@credit_card, @options) + assert_success response + assert_equal 'The transaction was successful', response.message + end + + def test_failed_verify + @gateway.expects(:ssl_post).returns(failed_authorization_response) + assert response = @gateway.verify(@credit_card, @options) + assert_failure response + assert_equal 'Unknown order', response.message + end + def test_successful_store - @gateway.expects(:add_pair).at_least(1) - @gateway.expects(:add_pair).with(anything, 'ECI', '7') - @gateway.expects(:ssl_post).times(2).returns(successful_purchase_response) + @gateway.expects(:authorize).with(1, @credit_card, :billing_id => @billing_id).returns(OgoneResponse.new(true, '', @gateway.send(:parse, successful_purchase_response), :authorization => '3014726;RES')) + @gateway.expects(:void).with('3014726;RES') assert response = @gateway.store(@credit_card, :billing_id => @billing_id) assert_success response assert_equal '3014726;RES', response.authorization - assert_equal '2', response.billing_id - assert response.test? + assert_equal @billing_id, response.billing_id + end + + def test_store_amount_at_gateway_level + gateway = OgoneGateway.new(@credentials.merge(:store_amount => 100)) + gateway.expects(:authorize).with(100, @credit_card, :billing_id => @billing_id).returns(OgoneResponse.new(true, '', gateway.send(:parse, successful_purchase_response_100), :authorization => '3014726;RES')) + gateway.expects(:void).with('3014726;RES') + assert response = gateway.store(@credit_card, :billing_id => @billing_id) + assert_success response + assert_equal '3014726;RES', response.authorization + assert_equal @billing_id, response.billing_id end def test_deprecated_store_option @gateway.expects(:add_pair).at_least(1) @gateway.expects(:add_pair).with(anything, 'ECI', '7') @gateway.expects(:ssl_post).times(2).returns(successful_purchase_response) - assert_deprecation_warning(OgoneGateway::OGONE_STORE_OPTION_DEPRECATION_MESSAGE, @gateway) do + assert_deprecation_warning(OgoneGateway::OGONE_STORE_OPTION_DEPRECATION_MESSAGE) do assert response = @gateway.store(@credit_card, :store => @billing_id) assert_success response assert_equal '3014726;RES', response.authorization @@ -201,12 +246,12 @@ def test_unsuccessful_request end def test_create_readable_error_message_upon_failure - @gateway.expects(:ssl_post).returns(test_failed_authorization_due_to_unknown_order_number) + @gateway.expects(:ssl_post).returns(failed_authorization_response) assert response = @gateway.purchase(@amount, @credit_card, @options) assert_failure response assert response.test? - assert_equal "Unknown order", response.message + assert_equal 'Unknown order', response.message end def test_supported_countries @@ -258,7 +303,7 @@ def test_cvv_result def test_billing_id @gateway.expects(:ssl_post).returns(successful_purchase_response) response = @gateway.purchase(@amount, @credit_card) - assert_equal '2', response.billing_id + assert_equal @billing_id, response.billing_id end def test_order_id @@ -283,31 +328,31 @@ def test_test_mode def test_format_error_message_with_slash_separator @gateway.expects(:ssl_post).returns('<ncresponse NCERRORPLUS="unknown order/1/i/67.192.100.64" STATUS="0" />') assert response = @gateway.purchase(@amount, @credit_card, @options) - assert_equal "Unknown order", response.message + assert_equal 'Unknown order', response.message end def test_format_error_message_with_pipe_separator @gateway.expects(:ssl_post).returns('<ncresponse NCERRORPLUS=" no card no|no exp date|no brand" STATUS="0" />') assert response = @gateway.purchase(@amount, @credit_card, @options) - assert_equal "No card no, no exp date, no brand", response.message + assert_equal 'No card no, no exp date, no brand', response.message end def test_format_error_message_with_no_separator @gateway.expects(:ssl_post).returns('<ncresponse NCERRORPLUS=" unknown order " STATUS="0" />') assert response = @gateway.purchase(@amount, @credit_card, @options) - assert_equal "Unknown order", response.message + assert_equal 'Unknown order', response.message end def test_without_signature gateway = OgoneGateway.new(@credentials.merge(:signature => nil, :signature_encryptor => nil)) gateway.expects(:ssl_post).returns(successful_purchase_response) - assert_deprecation_warning(OgoneGateway::OGONE_NO_SIGNATURE_DEPRECATION_MESSAGE, gateway) do + assert_deprecation_warning(OgoneGateway::OGONE_NO_SIGNATURE_DEPRECATION_MESSAGE) do gateway.purchase(@amount, @credit_card, @options) end - gateway = OgoneGateway.new(@credentials.merge(:signature => nil, :signature_encryptor => "none")) + gateway = OgoneGateway.new(@credentials.merge(:signature => nil, :signature_encryptor => 'none')) gateway.expects(:ssl_post).returns(successful_purchase_response) - assert_no_deprecation_warning(@gateway) do + assert_no_deprecation_warning do gateway.purchase(@amount, @credit_card, @options) end end @@ -315,7 +360,7 @@ def test_without_signature def test_signature_for_accounts_created_before_10_may_20101 gateway = OgoneGateway.new(@credentials.merge(:signature_encryptor => nil)) assert signature = gateway.send(:add_signature, @parameters) - assert_equal Digest::SHA1.hexdigest("1100EUR4111111111111111MrPSPIDRES2mynicesig").upcase, signature + assert_equal Digest::SHA1.hexdigest('1100EUR4111111111111111MrPSPIDRES2mynicesig').upcase, signature end def test_signature_for_accounts_with_signature_encryptor_to_sha1 @@ -349,13 +394,13 @@ def test_3dsecure_win_3ds_option gateway = OgoneGateway.new(@credentials) gateway.send(:add_d3d, post, { :win_3ds => :pop_up }) - assert 'POPUP', post["WIN3DS"] + assert 'POPUP', post['WIN3DS'] gateway.send(:add_d3d, post, { :win_3ds => :pop_ix }) - assert 'POPIX', post["WIN3DS"] + assert 'POPIX', post['WIN3DS'] gateway.send(:add_d3d, post, { :win_3ds => :invalid }) - assert 'MAINW', post["WIN3DS"] + assert 'MAINW', post['WIN3DS'] end def test_3dsecure_additional_options @@ -363,23 +408,27 @@ def test_3dsecure_additional_options gateway = OgoneGateway.new(@credentials) gateway.send(:add_d3d, post, { - :http_accept => "text/html", - :http_user_agent => "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0)", + :http_accept => 'text/html', + :http_user_agent => 'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0)', :accept_url => 'https://accept_url', :decline_url => 'https://decline_url', :exception_url => 'https://exception_url', - :paramsplus => 'params_plus', + :cancel_url => 'https://cancel_url', + :paramvar => 'param_var', + :paramplus => 'param_plus', :complus => 'com_plus', :language => 'fr_FR' }) - assert 'HTTP_ACCEPT', "text/html" - assert 'HTTP_USER_AGENT', "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0)" - assert 'ACCEPTURL', 'https://accept_url' - assert 'DECLINEURL', 'https://decline_url' - assert 'EXCEPTIONURL', 'https://exception_url' - assert 'PARAMSPLUS', 'params_plus' - assert 'COMPLUS', 'com_plus' - assert 'LANGUAGE', 'fr_FR' + assert_equal post['HTTP_ACCEPT'], 'text/html' + assert_equal post['HTTP_USER_AGENT'], 'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0)' + assert_equal post['ACCEPTURL'], 'https://accept_url' + assert_equal post['DECLINEURL'], 'https://decline_url' + assert_equal post['EXCEPTIONURL'], 'https://exception_url' + assert_equal post['CANCELURL'], 'https://cancel_url' + assert_equal post['PARAMPLUS'], 'param_plus' + assert_equal post['PARAMVAR'], 'param_var' + assert_equal post['COMPLUS'], 'com_plus' + assert_equal post['LANGUAGE'], 'fr_FR' end def test_accessing_params_attribute_of_response @@ -396,19 +445,24 @@ def test_response_params_is_hash assert_instance_of Hash, response.params end + def test_transcript_scrubbing + assert @gateway.supports_scrubbing? + assert_equal @gateway.scrub(pre_scrub), post_scrub + end + private def string_to_digest - "ALIAS=2mynicesigAMOUNT=100mynicesigCARDNO=4111111111111111mynicesig"+ - "CN=Client NamemynicesigCURRENCY=EURmynicesigOPERATION=RESmynicesig"+ - "ORDERID=1mynicesigPSPID=MrPSPIDmynicesig" + 'ALIAS=2mynicesigAMOUNT=100mynicesigCARDNO=4111111111111111mynicesig'\ + 'CN=Client NamemynicesigCURRENCY=EURmynicesigOPERATION=RESmynicesig'\ + 'ORDERID=1mynicesigPSPID=MrPSPIDmynicesig' end def d3d_string_to_digest - "ALIAS=2mynicesigAMOUNT=100mynicesigCARDNO=4111111111111111mynicesig"+ - "CN=Client NamemynicesigCURRENCY=EURmynicesigFLAG3D=Ymynicesig"+ - "HTTP_ACCEPT=*/*mynicesigOPERATION=RESmynicesigORDERID=1mynicesig"+ - "PSPID=MrPSPIDmynicesigWIN3DS=MAINWmynicesig" + 'ALIAS=2mynicesigAMOUNT=100mynicesigCARDNO=4111111111111111mynicesig'\ + 'CN=Client NamemynicesigCURRENCY=EURmynicesigFLAG3D=Ymynicesig'\ + 'HTTP_ACCEPT=*/*mynicesigOPERATION=RESmynicesigORDERID=1mynicesig'\ + 'PSPID=MrPSPIDmynicesigWIN3DS=MAINWmynicesig' end def successful_authorize_response @@ -456,7 +510,32 @@ def successful_purchase_response currency="EUR" PM="CreditCard" BRAND="VISA" - ALIAS="2"> + ALIAS="#{@billing_id}"> + </ncresponse> + END + end + + def successful_purchase_response_100 + <<-END + <?xml version="1.0"?><ncresponse + orderID="1233680882919266242708828" + PAYID="3014726" + NCSTATUS="0" + NCERROR="0" + NCERRORPLUS="!" + ACCEPTANCE="test123" + STATUS="5" + IPCTY="99" + CCCTY="99" + ECI="7" + CVCCheck="NO" + AAVCheck="NO" + VC="NO" + amount="100" + currency="EUR" + PM="CreditCard" + BRAND="VISA" + ALIAS="#{@billing_id}"> </ncresponse> END end @@ -660,7 +739,7 @@ def successful_unreferenced_credit_response END end - def test_failed_authorization_due_to_unknown_order_number + def failed_authorization_response <<-END <?xml version="1.0"?> <ncresponse @@ -680,4 +759,52 @@ def test_failed_authorization_due_to_unknown_order_number END end + def pre_scrub + %q{ +opening connection to secure.ogone.com:443... +opened +starting SSL for secure.ogone.com:443... +SSL established +<- "POST /ncol/test/orderdirect.asp HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: secure.ogone.com\r\nContent-Length: 455\r\n\r\n" +<- "CARDNO=4000100011112224&CN=Longbob+Longsen&COM=Store+Purchase&CVC=123&ECI=7&ED=0919&Operation=SAL&OwnerZip=K1C2N6&Owneraddress=456+My+Street&PSPID=spreedlyinc&PSWD=spreedly1test&SHASign=A67038AB141C6E54C51315F993DC83F5C28A9E585C6C8A79346F802E6557C0C8EE233A5FF1352AAD3C6AA5D476CF49F2B0DF512C63BA624F0583B72C1DCABCEF&USERID=spreedlytest&amount=100&currency=EUR&orderID=7de271d36c1c36999a6039d99179b2&ownercty=CA&ownertelno=%28555%29555-5555&ownertown=Ottawa" +-> "HTTP/1.1 200 OK\r\n" +-> "Cache-Control: private, max-age=0\r\n" +-> "Content-Length: 152\r\n" +-> "Content-Type: text/XML; Charset=iso-8859-1\r\n" +-> "Expires: Mon, 08 Jan 2018 18:13:04 GMT\r\n" +-> "Strict-Transport-Security: max-age=31536000;includeSubdomains\r\n" +-> "Date: Mon, 08 Jan 2018 18:14:05 GMT\r\n" +-> "Connection: close\r\n" +-> "\r\n" +reading 152 bytes... +-> "<?xml version=\"1.0\"?><ncresponse\r\norderID=\"7de271d36c1c36999a6039d99179b2\"\r\nPAYID=\"3029762647\"\r\nNCERROR=\"0\"\r\nSTATUS=\"9\"\r\nNCERRORPLUS=\"!\">\r\n</ncresponse>" +read 152 bytes +Conn close + } + end + + def post_scrub + %q{ +opening connection to secure.ogone.com:443... +opened +starting SSL for secure.ogone.com:443... +SSL established +<- "POST /ncol/test/orderdirect.asp HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: secure.ogone.com\r\nContent-Length: 455\r\n\r\n" +<- "CARDNO=[FILTERED]&CN=Longbob+Longsen&COM=Store+Purchase&CVC=[FILTERED]&ECI=7&ED=0919&Operation=SAL&OwnerZip=K1C2N6&Owneraddress=456+My+Street&PSPID=spreedlyinc&PSWD=[FILTERED]&SHASign=A67038AB141C6E54C51315F993DC83F5C28A9E585C6C8A79346F802E6557C0C8EE233A5FF1352AAD3C6AA5D476CF49F2B0DF512C63BA624F0583B72C1DCABCEF&USERID=spreedlytest&amount=100&currency=EUR&orderID=7de271d36c1c36999a6039d99179b2&ownercty=CA&ownertelno=%28555%29555-5555&ownertown=Ottawa" +-> "HTTP/1.1 200 OK\r\n" +-> "Cache-Control: private, max-age=0\r\n" +-> "Content-Length: 152\r\n" +-> "Content-Type: text/XML; Charset=iso-8859-1\r\n" +-> "Expires: Mon, 08 Jan 2018 18:13:04 GMT\r\n" +-> "Strict-Transport-Security: max-age=31536000;includeSubdomains\r\n" +-> "Date: Mon, 08 Jan 2018 18:14:05 GMT\r\n" +-> "Connection: close\r\n" +-> "\r\n" +reading 152 bytes... +-> "<?xml version=\"1.0\"?><ncresponse\r\norderID=\"7de271d36c1c36999a6039d99179b2\"\r\nPAYID=\"3029762647\"\r\nNCERROR=\"0\"\r\nSTATUS=\"9\"\r\nNCERRORPLUS=\"!\">\r\n</ncresponse>" +read 152 bytes +Conn close + } + end + end diff --git a/test/unit/gateways/omise_test.rb b/test/unit/gateways/omise_test.rb new file mode 100644 index 00000000000..5c31855fd64 --- /dev/null +++ b/test/unit/gateways/omise_test.rb @@ -0,0 +1,816 @@ +require 'test_helper' + +class OmiseTest < Test::Unit::TestCase + def setup + @gateway = OmiseGateway.new( + public_key: 'pkey_test_abc', + secret_key: 'skey_test_123' + ) + + @credit_card = credit_card + @amount = 3333 + + @options = { + order_id: '1', + billing_address: address, + description: 'Store Purchase' + } + + @card_token = { + object: 'token', + id: 'tokn_test_4zgf1crg50rdb68xlk5' + }; + end + + def test_supported_countries + assert_equal @gateway.supported_countries, %w( TH JP ) + end + + def test_supported_cardtypes + assert_equal @gateway.supported_cardtypes, [:visa, :master, :jcb] + end + + def test_supports_scrubbing + assert @gateway.supports_scrubbing? + end + + def test_scrub + assert_equal post_scrubbed, @gateway.scrub(pre_scrubbed) + end + + def test_gateway_url + assert_equal 'https://api.omise.co/', OmiseGateway::API_URL + assert_equal 'https://vault.omise.co/', OmiseGateway::VAULT_URL + end + + def test_request_headers + headers = @gateway.send(:headers, { key: 'pkey_test_555' }) + assert_equal 'Basic cGtleV90ZXN0XzU1NTo=', headers['Authorization'] + assert_equal 'application/json;utf-8', headers['Content-Type'] + end + + def test_post_data + post_data = @gateway.send(:post_data, { card: {number: '4242424242424242'} }) + assert_equal '{"card":{"number":"4242424242424242"}}', post_data + end + + def test_parse_response + response = @gateway.send(:parse, successful_purchase_response) + assert(response.key?('object'), 'expect json response has object key') + end + + def test_successful_response + response = @gateway.send(:parse, successful_purchase_response) + success = @gateway.send(:successful?, response) + assert(success, 'expect success to be true') + end + + def test_error_response + response = @gateway.send(:parse, error_response) + success = @gateway.send(:successful?, response) + assert(!success, 'expect success to be false') + end + + def test_error_code_from + response = @gateway.send(:parse, invalid_security_code_response) + error_code = @gateway.send(:error_code_from, response) + assert_equal 'invalid_security_code', error_code + end + + def test_standard_error_code_mapping + invalid_expiration_date = @gateway.send(:parse, invalid_expiration_date_response) + invalid_expiration_date_code = @gateway.send(:standard_error_code_mapping, invalid_expiration_date) + assert_equal 'invalid_expiry_date', invalid_expiration_date_code + end + + def test_invalid_cvc + invalid_security_code = @gateway.send(:parse, invalid_security_code_response) + invalid_cvc_code = @gateway.send(:standard_error_code_mapping, invalid_security_code) + assert_equal 'invalid_cvc', invalid_cvc_code + end + + def test_card_declined + card_declined = @gateway.send(:parse, failed_capture_response) + card_declined_code = @gateway.send(:standard_error_code_mapping, card_declined) + assert_equal 'card_declined', card_declined_code + end + + def test_invalid_number + invalid_number = @gateway.send(:parse, incorrect_number_response) + invalid_number_code = @gateway.send(:standard_error_code_mapping, invalid_number) + assert_equal 'invalid_number', invalid_number_code + end + + def test_invalid_expiry_date + expiration_year = @gateway.send(:parse, invalid_expiration_year_response) + invalid_expiry_date_code = @gateway.send(:standard_error_code_mapping, expiration_year) + assert_equal 'invalid_expiry_date', invalid_expiry_date_code + + expiration_month = @gateway.send(:parse, invalid_expiration_month_response) + invalid_expiry_date_code = @gateway.send(:standard_error_code_mapping, expiration_month) + assert_equal 'invalid_expiry_date', invalid_expiry_date_code + end + + def test_successful_api_request + @gateway.expects(:ssl_request).returns(successful_list_charges_response) + response = @gateway.send(:https_request, :get, 'charges') + assert(!response.empty?) + end + + def test_message_from_response + response = @gateway.send(:parse, error_response) + assert_equal 'failed fraud check', @gateway.send(:message_from, response) + end + + def test_authorization_from_response + response = @gateway.send(:parse, successful_purchase_response) + assert_equal 'chrg_test_4zgf1d2wbstl173k99v', @gateway.send(:authorization_from, response) + end + + def test_add_creditcard + result = {} + @gateway.send(:add_creditcard, result, @credit_card) + assert_equal @credit_card.number, result[:card][:number] + assert_equal @credit_card.verification_value, result[:card][:security_code] + assert_equal 'Longbob Longsen', result[:card][:name] + end + + def test_add_customer_without_card + result = {} + customer_id = 'cust_test_4zjzcgm8kpdt4xdhdw2' + @gateway.send(:add_customer, result, {customer_id: customer_id}) + assert_equal 'cust_test_4zjzcgm8kpdt4xdhdw2', result[:customer] + end + + def test_add_customer_with_card_id + result = {} + customer_id = 'cust_test_4zjzcgm8kpdt4xdhdw2' + result[:card] = 'card_test_4zguktjcxanu3dw171a' + @gateway.send(:add_customer, result, {customer_id: customer_id}) + assert_equal customer_id, result[:customer] + end + + def test_add_amount + result = {} + desc = 'Charge for order 3947' + @gateway.send(:add_amount, result, @amount, {description: desc}) + assert_equal desc, result[:description] + end + + def test_add_amount_with_correct_currency + result = {} + jpy_currency = 'JPY' + @gateway.send(:add_amount, result, @amount, {currency: jpy_currency}) + assert_equal jpy_currency, result[:currency] + end + + def test_commit_transaction + @gateway.expects(:ssl_request).returns(successful_purchase_response) + response = @gateway.send(:commit, :post, 'charges', {}) + assert_equal 'chrg_test_4zgf1d2wbstl173k99v', response.authorization + end + + def test_successful_token_exchange + @gateway.expects(:ssl_request).returns(successful_token_exchange) + token = @gateway.send(:get_token, {}, @credit_card) + assert_equal 'tokn_test_4zgf1crg50rdb68xlk5', token.params['id'] + end + + def test_successful_purchase + @gateway.expects(:ssl_request).twice.returns(successful_token_exchange, successful_purchase_response) + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal 'chrg_test_4zgf1d2wbstl173k99v', response.authorization + assert response.test? + end + + def test_successful_authorize + @gateway.expects(:ssl_request).twice.returns(successful_token_exchange, successful_authorize_response) + response = @gateway.authorize(@amount, @credit_card, @options) + assert_success response + assert_equal 'chrg_test_4zmqak4ccnfut5maxp7', response.authorization + assert response.test? + assert response.params['authorized'] + end + + def test_successful_store + @gateway.expects(:ssl_request).twice.returns(successful_token_exchange, successful_store_response) + response = @gateway.store(@credit_card, @options) + assert_equal 'cust_test_4zkp720zggu4rubgsqb', response.authorization + end + + def test_successful_capture + @gateway.expects(:ssl_request).returns(successful_capture_response) + response = @gateway.send(:capture, @amount, 'chrg_test_4z5goqdwpjebu1gsmqq') + assert_equal 'chrg_test_4z5goqdwpjebu1gsmqq', response.params['id'] + end + + def test_failed_capture + @gateway.expects(:ssl_request).returns(failed_capture_response) + response = @gateway.send(:capture, @amount, 'chrg_test_4z5goqdwpjebu1gsmqq') + assert_equal 'Charge is not authorized', response.message + end + + def test_successful_refund + @gateway.expects(:ssl_request).returns(successful_refund_response) + response = @gateway.send(:refund, @amount, 'chrg_test_4z5goqdwpjebu1gsmqq') + assert_equal 'rfnd_test_4zmbpt1zwdsqtmtffw8', response.params['id'] + end + + def test_successful_partial_refund + @gateway.expects(:ssl_request).returns(successful_partial_refund_response) + response = @gateway.send(:refund, 1000, 'chrg_test_4z5goqdwpjebu1gsmqq') + assert_equal 1000, response.params['amount'] + end + + def test_failed_refund + @gateway.expects(:ssl_request).returns(failed_refund_response) + response = @gateway.send(:refund, 9999999, 'chrg_test_4z5goqdwpjebu1gsmqq') + assert_equal "charge can't be refunded", response.message + end + + private + + def pre_scrubbed + <<-'PRE_SCRUBED' + opening connection to vault.omise.co:443... + opened + starting SSL for vault.omise.co:443... + SSL established + <- "POST /tokens HTTP/1.1\r\nContent-Type: application/json;utf-8\r\nUser-Agent: Omise/v1.0 ActiveMerchantBindings/1.48.0\r\nAuthorization: Basic cGtleV90ZXN0XzR6dDBmc3M4Z3MwejZiNHpsc3E6\r\nAccept-Encoding: utf-8\r\nAccept: */*\r\nConnection: close\r\nHost: vault.omise.co\r\nContent-Length: 129\r\n\r\n" + <- "{\"card\":{\"number\":\"4242424242424242\",\"name\":\"Longbob Longsen\",\"security_code\":\"123\",\"expiration_month\":9,\"expiration_year\":2016}}" + -> "HTTP/1.1 200 OK\r\n" + -> "Cache-Control: no-cache, no-store\r\n" + -> "Content-Type: application/json; charset=utf-8\r\n" + -> "Date: Tue, 28 Apr 2015 11:28:04 GMT\r\n" + -> "Omise-Version: 2015-04-24\r\n" + -> "Server: nginx\r\n" + -> "Status: 200 OK\r\n" + -> "Strict-Transport-Security: max-age=31536000; includeSubDomains\r\n" + -> "Strict-Transport-Security: max-age=31536000; includeSubdomains\r\n" + -> "Vary: Accept-Encoding\r\n" + -> "X-Content-Type-Options: nosniff\r\n" + -> "X-Content-Type-Options: nosniff\r\n" + -> "X-Frame-Options: SAMEORIGIN\r\n" + -> "X-Request-Id: 6fe70604-c96c-425a-bdc1-2c50049be41b\r\n" + -> "X-Runtime: 0.083059\r\n" + -> "X-XSS-Protection: 1; mode=block\r\n" + -> "Content-Length: 680\r\n" + -> "Connection: Close\r\n" + -> "\r\n" + reading 680 bytes... + -> "{\n \"object\": \"token\",\n \"id\": \"tokn_test_4zulil6gzuconeb4mrm\",\n \"livemode\": false,\n \"location\": \"https://vault.omise.co/tokens/tokn_test_4zulil6gzuconeb4mrm\",\n \"used\": false,\n \"card\": {\n \"object\": \"card\",\n \"id\": \"card_test_4zulil6fy04h678xb52\",\n \"livemode\": false,\n \"country\": \"us\",\n \"city\": null,\n \"postal_code\": null,\n \"financing\": \"\",\n \"last_digits\": \"4242\",\n \"brand\": \"Visa\",\n \"expiration_month\": 9,\n \"expiration_year\": 2016,\n \"fingerprint\": \"MYfx1beqiXkgHgJMoH+LzpyuspoeQZoQDmsI1GDSl/A=\",\n \"name\": \"Longbob Longsen\",\n \"security_code_check\": true,\n \"created\": \"2015-04-28T11:30:28Z\"\n },\n \"created\": \"2015-04-28T11:30:28Z\"\n}\n" + read 680 bytes + Conn close + PRE_SCRUBED + end + + def post_scrubbed + <<-'POST_SCRUBBED' + opening connection to vault.omise.co:443... + opened + starting SSL for vault.omise.co:443... + SSL established + <- "POST /tokens HTTP/1.1\r\nContent-Type: application/json;utf-8\r\nUser-Agent: Omise/v1.0 ActiveMerchantBindings/1.48.0\r\nAuthorization: Basic [FILTERED]\r\nAccept-Encoding: utf-8\r\nAccept: */*\r\nConnection: close\r\nHost: vault.omise.co\r\nContent-Length: 129\r\n\r\n" + <- "{\"card\":{\"number\":[FILTERED],\"name\":\"Longbob Longsen\",\"security_code\":[FILTERED],\"expiration_month\":9,\"expiration_year\":2016}}" + -> "HTTP/1.1 200 OK\r\n" + -> "Cache-Control: no-cache, no-store\r\n" + -> "Content-Type: application/json; charset=utf-8\r\n" + -> "Date: Tue, 28 Apr 2015 11:28:04 GMT\r\n" + -> "Omise-Version: 2015-04-24\r\n" + -> "Server: nginx\r\n" + -> "Status: 200 OK\r\n" + -> "Strict-Transport-Security: max-age=31536000; includeSubDomains\r\n" + -> "Strict-Transport-Security: max-age=31536000; includeSubdomains\r\n" + -> "Vary: Accept-Encoding\r\n" + -> "X-Content-Type-Options: nosniff\r\n" + -> "X-Content-Type-Options: nosniff\r\n" + -> "X-Frame-Options: SAMEORIGIN\r\n" + -> "X-Request-Id: 6fe70604-c96c-425a-bdc1-2c50049be41b\r\n" + -> "X-Runtime: 0.083059\r\n" + -> "X-XSS-Protection: 1; mode=block\r\n" + -> "Content-Length: 680\r\n" + -> "Connection: Close\r\n" + -> "\r\n" + reading 680 bytes... + -> "{\n \"object\": \"token\",\n \"id\": \"tokn_test_4zulil6gzuconeb4mrm\",\n \"livemode\": false,\n \"location\": \"https://vault.omise.co/tokens/tokn_test_4zulil6gzuconeb4mrm\",\n \"used\": false,\n \"card\": {\n \"object\": \"card\",\n \"id\": \"card_test_4zulil6fy04h678xb52\",\n \"livemode\": false,\n \"country\": \"us\",\n \"city\": null,\n \"postal_code\": null,\n \"financing\": \"\",\n \"last_digits\": \"4242\",\n \"brand\": \"Visa\",\n \"expiration_month\": 9,\n \"expiration_year\": 2016,\n \"fingerprint\": \"MYfx1beqiXkgHgJMoH+LzpyuspoeQZoQDmsI1GDSl/A=\",\n \"name\": \"Longbob Longsen\",\n \"security_code_check\": true,\n \"created\": \"2015-04-28T11:30:28Z\"\n },\n \"created\": \"2015-04-28T11:30:28Z\"\n}\n" + read 680 bytes + Conn close + POST_SCRUBBED + end + + def error_response + <<-RESPONSE + { + "object": "error", + "location": "https://docs.omise.co/api/errors#failed-fraud-check", + "code": "failed_fraud_check", + "message": "failed fraud check" + } + RESPONSE + end + + def successful_token_exchange + <<-RESPONSE + { + "object": "token", + "id": "tokn_test_4zgf1crg50rdb68xlk5", + "livemode": false, + "location": "https://vault.omise.co/tokens/tokn_test_4zgf1crg50rdb68xlk5", + "used": false, + "card": { + "object": "card", + "id": "card_test_4zgf1crf975xnz6coa7", + "livemode": false, + "country": "us", + "city": "Bangkok", + "postal_code": "10320", + "financing": "", + "last_digits": "4242", + "brand": "Visa", + "expiration_month": 10, + "expiration_year": 2018, + "fingerprint": "mKleiBfwp+PoJWB/ipngANuECUmRKjyxROwFW5IO7TM=", + "name": "Somchai Prasert", + "security_code_check": true, + "created": "2015-03-23T05:25:14Z" + }, + "created": "2015-03-23T05:25:14Z" + } + RESPONSE + end + + def successful_list_charges_response + <<-RESPONSE + { + "object": "list", + "from": "1970-01-01T00:00:00+00:00", + "to": "2015-04-01T03:34:11+00:00", + "offset": 0, + "limit": 20, + "total": 1, + "data": [ + { + "object": "charge", + "id": "chrg_test_4zgukttzllzumc25qvd", + "livemode": false, + "location": "/charges/chrg_test_4zgukttzllzumc25qvd", + "amount": 99, + "currency": "thb", + "description": "Charge for order 3947", + "capture": true, + "authorized": true, + "paid": true, + "transaction": "trxn_test_4zguktuecyuo77xgq38", + "refunded": 0, + "refunds": { + "object": "list", + "from": "1970-01-01T00:00:00+00:00", + "to": "2015-04-01T03:34:11+00:00", + "offset": 0, + "limit": 20, + "total": 0, + "data": [ + + ], + "location": "/charges/chrg_test_4zgukttzllzumc25qvd/refunds" + }, + "failure_code": null, + "failure_message": null, + "card": { + "object": "card", + "id": "card_test_4zguktjcxanu3dw171a", + "livemode": false, + "country": "us", + "city": "Bangkok", + "postal_code": "10320", + "financing": "", + "last_digits": "4242", + "brand": "Visa", + "expiration_month": 2, + "expiration_year": 2017, + "fingerprint": "djVaKigLa0g0b12XdGLV8CAdy45FRrOdVsgmv4oze5I=", + "name": "JOHN DOE", + "security_code_check": true, + "created": "2015-03-24T07:54:32Z" + }, + "customer": null, + "ip": null, + "dispute": null, + "created": "2015-03-24T07:54:33Z" + } + ] + } + RESPONSE + end + + def successful_purchase_response + <<-RESPONSE + { + "object": "charge", + "id": "chrg_test_4zgf1d2wbstl173k99v", + "livemode": false, + "location": "/charges/chrg_test_4zgf1d2wbstl173k99v", + "amount": 100000, + "currency": "thb", + "description": null, + "capture": true, + "authorized": true, + "paid": true, + "transaction": "trxn_test_4zgf1d3f7t9k6gk8hn8", + "refunded": 0, + "refunds": { + "object": "list", + "from": "1970-01-01T00:00:00+00:00", + "to": "2015-03-23T05:25:15+00:00", + "offset": 0, + "limit": 20, + "total": 0, + "data": [ + + ], + "location": "/charges/chrg_test_4zgf1d2wbstl173k99v/refunds" + }, + "failure_code": null, + "failure_message": null, + "card": { + "object": "card", + "id": "card_test_4zgf1crf975xnz6coa7", + "livemode": false, + "location": "/customers/cust_test_4zgf1cv8e71bbwcww1p/cards/card_test_4zgf1crf975xnz6coa7", + "country": "us", + "city": "Bangkok", + "postal_code": "10320", + "financing": "", + "last_digits": "4242", + "brand": "Visa", + "expiration_month": 10, + "expiration_year": 2018, + "fingerprint": "mKleiBfwp+PoJWB/ipngANuECUmRKjyxROwFW5IO7TM=", + "name": "Somchai Prasert", + "security_code_check": true, + "created": "2015-03-23T05:25:14Z" + }, + "customer": "cust_test_4zgf1cv8e71bbwcww1p", + "ip": null, + "dispute": null, + "created": "2015-03-23T05:25:15Z" + } + RESPONSE + end + + def successful_store_response + <<-RESPONSE + { + "object": "customer", + "id": "cust_test_4zkp720zggu4rubgsqb", + "livemode": false, + "location": "/customers/cust_test_4zkp720zggu4rubgsqb", + "default_card": "card_test_4zkp6xeuzurrvacxs2j", + "email": "john.doe@example.com", + "description": "John Doe (id: 30)", + "created": "2015-04-03T04:10:35Z", + "cards": { + "object": "list", + "from": "1970-01-01T00:00:00+00:00", + "to": "2015-04-03T04:10:35+00:00", + "offset": 0, + "limit": 20, + "total": 1, + "data": [ + { + "object": "card", + "id": "card_test_4zkp6xeuzurrvacxs2j", + "livemode": false, + "location": "/customers/cust_test_4zkp720zggu4rubgsqb/cards/card_test_4zkp6xeuzurrvacxs2j", + "country": "us", + "city": "Bangkok", + "postal_code": "10320", + "financing": "", + "last_digits": "4242", + "brand": "Visa", + "expiration_month": 4, + "expiration_year": 2017, + "fingerprint": "djVaKigLa0g0b12XdGLV8CAdy45FRrOdVsgmv4oze5I=", + "name": "JOHN DOE", + "security_code_check": false, + "created": "2015-04-03T04:10:13Z" + } + ], + "location": "/customers/cust_test_4zkp720zggu4rubgsqb/cards" + } + } + RESPONSE + end + + def successful_charge_response + <<-RESPONSE + { + "object": "charge", + "id": "chrg_test_4zmqak4ccnfut5maxp7", + "livemode": false, + "location": "/charges/chrg_test_4zmqak4ccnfut5maxp7", + "amount": 100000, + "currency": "thb", + "description": null, + "capture": false, + "authorized": true, + "paid": true, + "transaction": "trxn_test_4zmqf6njyokta57ljs1", + "refunded": 0, + "refunds": { + "object": "list", + "from": "1970-01-01T00:00:00+00:00", + "to": "2015-04-08T09:11:39+00:00", + "offset": 0, + "limit": 20, + "total": 0, + "data": [ + + ], + "location": "/charges/chrg_test_4zmqak4ccnfut5maxp7/refunds" + }, + "failure_code": null, + "failure_message": null, + "card": { + "object": "card", + "id": "card_test_4zmqaffhmut87bi075q", + "livemode": false, + "country": "us", + "city": "Bangkok", + "postal_code": "10320", + "financing": "", + "last_digits": "4242", + "brand": "Visa", + "expiration_month": 4, + "expiration_year": 2017, + "fingerprint": "djVaKigLa0g0b12XdGLV8CAdy45FRrOdVsgmv4oze5I=", + "name": "JOHN DOE", + "security_code_check": true, + "created": "2015-04-08T08:45:40Z" + }, + "customer": null, + "ip": null, + "dispute": null, + "created": "2015-04-08T08:46:02Z" + } + RESPONSE + end + + def successful_authorize_response + <<-RESPONSE + { + "object": "charge", + "id": "chrg_test_4zmqak4ccnfut5maxp7", + "livemode": false, + "location": "/charges/chrg_test_4zmqak4ccnfut5maxp7", + "amount": 100000, + "currency": "thb", + "description": null, + "capture": false, + "authorized": true, + "paid": false, + "transaction": null, + "refunded": 0, + "refunds": { + "object": "list", + "from": "1970-01-01T00:00:00+00:00", + "to": "2015-04-08T08:46:02+00:00", + "offset": 0, + "limit": 20, + "total": 0, + "data": [ + + ], + "location": "/charges/chrg_test_4zmqak4ccnfut5maxp7/refunds" + }, + "failure_code": null, + "failure_message": null, + "card": { + "object": "card", + "id": "card_test_4zmqaffhmut87bi075q", + "livemode": false, + "country": "us", + "city": "Bangkok", + "postal_code": "10320", + "financing": "", + "last_digits": "4242", + "brand": "Visa", + "expiration_month": 4, + "expiration_year": 2017, + "fingerprint": "djVaKigLa0g0b12XdGLV8CAdy45FRrOdVsgmv4oze5I=", + "name": "JOHN DOE", + "security_code_check": true, + "created": "2015-04-08T08:45:40Z" + }, + "customer": null, + "ip": null, + "dispute": null, + "created": "2015-04-08T08:46:02Z" + } + RESPONSE + end + + def successful_capture_response + <<-RESPONSE + { "object": "charge", + "id": "chrg_test_4z5goqdwpjebu1gsmqq", + "livemode": false, + "location": "/charges/chrg_test_4z5goqdwpjebu1gsmqq", + "amount": 100000, + "currency": "thb", + "description": "Charge for order 3947", + "capture": false, + "authorized": true, + "paid": true, + "transaction": "trxn_test_4z5gp0t3mpfsu28u8jo", + "refunded": 0, + "refunds": { + "object": "list", + "from": "1970-01-01T00:00:00+00:00", + "to": "2015-02-23T05:16:54+00:00", + "offset": 0, + "limit": 20, + "total": 0, + "data": [ + + ], + "location": "/charges/chrg_test_4z5goqdwpjebu1gsmqq/refunds" + }, + "return_uri": "http://www.example.com/orders/3947/complete", + "reference": "paym_4z5goqdw6rblbxztm4c", + "authorize_uri": "https://api.omise.co/payments/paym_4z5goqdw6rblbxztm4c/authorize", + "failure_code": null, + "failure_message": null, + "card": { + "object": "card", + "id": "card_test_4z5gogdycbrium283yk", + "livemode": false, + "country": "us", + "city": "Bangkok", + "postal_code": "10320", + "financing": "", + "last_digits": "4242", + "brand": "Visa", + "expiration_month": 2, + "expiration_year": 2017, + "fingerprint": "umrBpbHRuc8vstbcNEZPbnKkIycR/gvI6ivW9AshKCw=", + "name": "JOHN DOE", + "security_code_check": true, + "created": "2015-02-23T05:15:18Z" + }, + "customer": null, + "ip": null, + "created": "2015-02-23T05:16:05Z" + } + RESPONSE + end + + def successful_refund_response + <<-RESPONSE + { "object": "refund", + "id": "rfnd_test_4zmbpt1zwdsqtmtffw8", + "location": "/charges/chrg_test_4zmbg6gtzz7zhf6rio6/refunds/rfnd_test_4zmbpt1zwdsqtmtffw8", + "amount": 3333, + "currency": "thb", + "charge": "chrg_test_4zmbg6gtzz7zhf6rio6", + "transaction": "trxn_test_4zmbpt23zmi9acu4qzk", + "created": "2015-04-07T07:55:21Z" + } + RESPONSE + end + + def successful_partial_refund_response + <<-RESPONSE + { "object": "refund", + "id": "rfnd_test_4zmbpt1zwdsqtmtffw8", + "location": "/charges/chrg_test_4zmbg6gtzz7zhf6rio6/refunds/rfnd_test_4zmbpt1zwdsqtmtffw8", + "amount": 1000, + "currency": "thb", + "charge": "chrg_test_4zmbg6gtzz7zhf6rio6", + "transaction": "trxn_test_4zmbpt23zmi9acu4qzk", + "created": "2015-04-07T07:55:21Z" + } + RESPONSE + end + + def failed_refund_response + <<-RESPONSE + { "object": "error", + "location": "https://docs.omise.co/api/errors#failed-refund", + "code": "failed_refund", + "message": "charge can't be refunded" + } + RESPONSE + end + + def invalid_expiration_month_response + <<-RESPONSE + { + "object": "error", + "location": "https://docs.omise.co/api/errors#invalid-card", + "code": "invalid_card", + "message": "expiration month is not between 1 and 12 and expiration date is invalid" + } + RESPONSE + end + + def invalid_expiration_year_response + <<-RESPONSE + { + "object": "error", + "location": "https://docs.omise.co/api/errors#invalid-card", + "code": "invalid_card", + "message": "expiration year is invalid" + } + RESPONSE + end + + def invalid_expiration_date_response + <<-RESPONSE + { + "object": "error", + "location": "https://docs.omise.co/api/errors#invalid-card", + "code": "invalid_card", + "message": "expiration month is not between 1 and 12 and expiration date is invalid" + } + RESPONSE + end + + def incorrect_number_response + <<-RESPONSE + { + "object": "error", + "location": "https://docs.omise.co/api/errors#invalid-card", + "code": "invalid_card", + "message": "number is invalid and brand not supported (unknown)" + } + RESPONSE + end + + def invalid_security_code_response + <<-RESPONSE + { + "object": "charge", + "id": "chrg_4zyeviyhhhs7sow8c3k", + "livemode": true, + "location": "/charges/chrg_4zyeviyhhhs7sow8c3k", + "amount": 111, + "currency": "thb", + "description": "activemerchant testing", + "capture": true, + "authorized": false, + "paid": false, + "transaction": null, + "refunded": 0, + "refunds": { + "object": "list", + "from": "1970-01-01T00:00:00+00:00", + "to": "2015-05-08T05:37:50+00:00", + "offset": 0, + "limit": 20, + "total": 0, + "data": [ + + ], + "location": "/charges/chrg_4zyeviyhhhs7sow8c3k/refunds" + }, + "failure_code": "invalid_security_code", + "failure_message": "the security code is invalid", + "card": { + "object": "card", + "id": "card_4zyevhammij8qhn2z59", + "livemode": true, + "country": "th", + "city": "Bangkok", + "postal_code": "11111", + "financing": "", + "last_digits": "1111", + "brand": "Visa", + "expiration_month": 1, + "expiration_year": 2021, + "fingerprint": "7qTrZY+fWNSQ9JTizVeVV7Jph4RBg6qANX3rBMXhpuE=", + "name": "WARACHET SAMTALEE", + "security_code_check": false, + "created": "2015-05-08T05:37:42Z" + }, + "customer": null, + "ip": null, + "dispute": null, + "created": "2015-05-08T05:37:50Z" + } + RESPONSE + end + + def failed_capture_response + <<-RESPONSE + { + "object": "error", + "location": "https://docs.omise.co/api/errors#failed-capture", + "code": "failed_capture", + "message": "Charge is not authorized" + } + RESPONSE + end + +end diff --git a/test/unit/gateways/openpay_test.rb b/test/unit/gateways/openpay_test.rb new file mode 100644 index 00000000000..5c15f77e489 --- /dev/null +++ b/test/unit/gateways/openpay_test.rb @@ -0,0 +1,624 @@ +require 'test_helper' + +class OpenpayTest < Test::Unit::TestCase + include CommStub + + def setup + @gateway = OpenpayGateway.new( + key: 'key', + merchant_id: 'merchant_id' + ) + + @credit_card = credit_card('4111111111111111') + @amount = 100 + @refund_amount = 50 + + @options = { + order_id: '1234567890', + billing_address: address, + description: 'Store Purchase' + } + end + + def test_successful_purchase + @gateway.expects(:ssl_request).returns(successful_purchase_response) + + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_instance_of Response, response + assert_success response + + assert_equal 'tay1mauq3re4iuuk8bm4', response.authorization + assert response.test? + end + + def test_unsuccessful_request + @gateway.expects(:ssl_request).returns(failed_purchase_response) + + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_equal 'The card was declined', response.message + assert response.test? + end + + def test_successful_authorization + @gateway.expects(:ssl_request).returns(successful_authorization_response) + + assert response = @gateway.authorize(@amount, @credit_card, @options) + assert_instance_of Response, response + assert_success response + + assert response.authorization + assert_equal 'in_progress', response.params['status'] + assert response.test? + end + + def test_successful_capture + @gateway.expects(:ssl_request).returns(successful_capture_response) + + assert response = @gateway.capture(@amount, 'tubpycc6gtsk71fu3tsd') + assert_success response + assert_equal 'completed', response.params['status'] + assert response.test? + end + + def test_successful_void + @gateway.expects(:ssl_request).returns(successful_void_response) + authorization = 'tay1mauq3re4iuuk8bm4' + + assert response = @gateway.void(authorization) + assert_instance_of Response, response + assert_success response + + assert_equal authorization, response.authorization + assert_equal 'cancelled', response.params['status'] + assert response.test? + end + + def test_successful_refund + @gateway.expects(:ssl_request).returns(successful_refunded_response) + + assert response = @gateway.refund(@amount, 'tei4hnvyp4agt5ecnbow') + assert_success response + + assert_equal 'tei4hnvyp4agt5ecnbow', response.authorization + assert response.params['refund'] + assert_equal 'completed', response.params['status'] + assert_equal 'completed', response.params['refund']['status'] + assert response.test? + end + + def test_unsuccessful_refund + @gateway.expects(:ssl_request).returns(generic_error_response) + + assert response = @gateway.refund(@refund_amount, 'tei4hnvyp4agt5ecnbow') + assert_failure response + assert !response.authorization + end + + def test_successful_verify + response = stub_comms(@gateway, :ssl_request) do + @gateway.verify(@credit_card) + end.respond_with(successful_authorization_response, successful_void_response) + assert_success response + end + + def test_unsuccessful_verify + response = stub_comms(@gateway, :ssl_request) do + @gateway.verify(@credit_card, @options) + end.respond_with(failed_authorize_response, successful_void_response) + assert_failure response + assert_not_nil response.message + end + + def test_successful_purchase_with_card_id + @gateway.expects(:ssl_request).returns(successful_purchase_response) + + assert response = @gateway.purchase(@amount, {credit_card: 'a2b79p8xmzeyvmolqfja'}, @options) + assert_instance_of Response, response + assert_success response + + assert_equal 'tay1mauq3re4iuuk8bm4', response.authorization + assert response.test? + end + + def test_succesful_store_new_customer_with_card + @gateway.expects(:ssl_request).twice.returns(successful_new_customer, successful_new_card) + @options[:email] = 'john@gmail.com' + @options[:name] = 'John Doe' + + assert response = @gateway.store(@credit_card, @options) + assert_instance_of MultiResponse, response + assert_success response + assert_equal 2, response.responses.size + + customer_response = response.responses[0] + assert_not_nil customer_response.params['id'] + + card_response = response.responses[1] + assert_not_nil card_response.params['id'] + + assert response.test? + end + + def test_successful_store_new_card + @gateway.expects(:ssl_request).returns(successful_new_card) + + assert response = @gateway.store(@credit_card, customer: 'a2b79p8xmzeyvmolqfja') + assert_success response + + assert_equal 'kgipbqixvjg3gbzowl7l', response.authorization + assert response.test? + end + + def test_missing_params_store + @options[:name] = 'John Doe' + + assert_raise(ArgumentError) do + @gateway.store(@credit_card, @options) + end + end + + def test_successful_unstore + @gateway.expects(:ssl_request).returns(nil) + + assert response = @gateway.unstore('a2b79p8xmzeyvmolqfja', 'kgipbqixvjg3gbzowl7l', @options) + assert_success response + + assert_nil response.authorization + assert response.test? + end + + def test_passing_device_session_id + response = stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, @credit_card, device_session_id: 'TheDeviceSessionID') + end.check_request do |method, endpoint, data, headers| + assert_match(%r{"device_session_id":"TheDeviceSessionID"}, data) + end.respond_with(successful_purchase_response) + + assert_success response + end + + def test_passing_payment_installments + response = stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, @credit_card, payments: '6') + end.check_request do |method, endpoint, data, headers| + assert_match(%r{"payments":"6"}, data) + assert_match(%r{"payment_plan":}, data) + end.respond_with(successful_purchase_response) + + assert_success response + end + + def test_transcript_scrubbing + assert_equal scrubbed_transcript, @gateway.scrub(transcript) + end + + def test_nil_cvv_transcript_scrubbing + assert_equal nil_cvv_scrubbed_transcript, @gateway.scrub(nil_cvv_transcript) + end + + def test_empty_string_cvv_transcript_scrubbing + assert_equal empty_string_cvv_scrubbed_transcript, @gateway.scrub(empty_string_cvv_transcript) + end + + def test_whitespace_string_cvv_transcript_scrubbing + assert_equal whitespace_string_cvv_scrubbed_transcript, @gateway.scrub(whitespace_string_cvv_transcript) + end + + private + + def successful_new_card + <<-RESPONSE +{ + "type":"debit", + "brand":"mastercard", + "address":{ + "line1":"Av 5 de Febrero", + "line2":"Roble 207", + "line3":"col carrillo", + "state":"Queretaro", + "city":"Queretaro", + "postal_code":"76900", + "country_code":"MX" + }, + "id":"kgipbqixvjg3gbzowl7l", + "card_number":"1111", + "holder_name":"Juan Perez Ramirez", + "expiration_year":"20", + "expiration_month":"12", + "allows_charges":true, + "allows_payouts":false, + "creation_date":"2013-12-12T17:50:00-06:00", + "bank_name":"DESCONOCIDO", + "bank_code":"000", + "customer_id":"a2b79p8xmzeyvmolqfja" +} + RESPONSE + end + + def successful_new_customer + <<-RESPONSE +{ + "id":"a2b79p8xmzeyvmolqfja", + "name":"Anacleto", + "last_name":"Morones", + "email":"morones.an@elllano.com", + "phone_number":"44209087654", + "status":"active", + "balance":0, + "clabe":"646180109400003235", + "address":{ + "line1":"Camino Real", + "line2":"Col. San Pablo", + "state":"Queretaro", + "city":"Queretaro", + "postal_code":"76000", + "country_code":"MX" + }, + "creation_date":"2013-12-12T16:29:11-06:00" +} + RESPONSE + end + + def successful_refunded_response + <<-RESPONSE +{ + "amount": 1.00, + "authorization": "801585", + "method": "card", + "operation_type": "in", + "transaction_type": "charge", + "card": { + "type": "debit", + "brand": "mastercard", + "address": { + "line1": "1234 My Street", + "line2": "Apt 1", + "line3": null, + "state": "ON", + "city": "Ottawa", + "postal_code": "K1C2N6", + "country_code": "CA" + }, + "card_number": "1111", + "holder_name": "Longbob Longsen", + "expiration_year": "15", + "expiration_month": "09", + "allows_charges": true, + "allows_payouts": false, + "creation_date": "2014-01-20T17:08:43-06:00", + "bank_name": "DESCONOCIDO", + "bank_code": "000", + "customer_id": null + }, + "status": "completed", + "refund": { + "amount": 1.00, + "authorization": "030706", + "method": "card", + "operation_type": "out", + "transaction_type": "refund", + "status": "completed", + "currency": "MXN", + "id": "tspoc4u9msdbnkkhpcmi", + "creation_date": "2014-01-20T17:08:44-06:00", + "description": "Store Purchase", + "error_message": null, + "order_id": null + }, + "currency": "MXN", + "id": "tei4hnvyp4agt5ecnbow", + "creation_date": "2014-01-20T17:08:43-06:00", + "description": "Store Purchase", + "error_message": null, + "order_id": null, + "error_code": null +} + RESPONSE + end + + def successful_capture_response + <<-RESPONSE +{ + "amount": 1.00, + "authorization": "801585", + "method": "card", + "operation_type": "in", + "transaction_type": "charge", + "card": { + "type": "debit", + "brand": "mastercard", + "address": null, + "card_number": "1111", + "holder_name": "Longbob Longsen", + "expiration_year": "15", + "expiration_month": "09", + "allows_charges": true, + "allows_payouts": false, + "creation_date": "2014-01-18T21:01:10-06:00", + "bank_name": "DESCONOCIDO", + "bank_code": "000", + "customer_id": null + }, + "status": "completed", + "currency": "MXN", + "id": "tubpycc6gtsk71fu3tsd", + "creation_date": "2014-01-18T21:01:10-06:00", + "description": "Store Purchase", + "error_message": null, + "order_id": null, + "error_code": null +} + RESPONSE + end + + def successful_authorization_response + <<-RESPONSE +{ + "amount": 1.00, + "authorization": "801585", + "method": "card", + "operation_type": "in", + "transaction_type": "charge", + "card": { + "type": "debit", + "brand": "mastercard", + "address": null, + "card_number": "1111", + "holder_name": "Longbob Longsen", + "expiration_year": "15", + "expiration_month": "09", + "allows_charges": true, + "allows_payouts": false, + "creation_date": "2014-01-18T21:01:10-06:00", + "bank_name": "DESCONOCIDO", + "bank_code": "000", + "customer_id": null + }, + "status": "in_progress", + "currency": "MXN", + "id": "tubpycc6gtsk71fu3tsd", + "creation_date": "2014-01-18T21:01:10-06:00", + "description": "Store Purchase", + "error_message": null, + "order_id": null, + "error_code": null +} + RESPONSE + end + + def successful_purchase_response(status = 'completed') + <<-RESPONSE +{ + "amount": 1.00, + "authorization": "801585", + "method": "card", + "operation_type": "in", + "transaction_type": "charge", + "card": { + "type": "debit", + "brand": "mastercard", + "address": { + "line1": "1234 My Street", + "line2": "Apt 1", + "line3": null, + "state": "ON", + "city": "Ottawa", + "postal_code": "K1C2N6", + "country_code": "CA" + }, + "card_number": "1111", + "holder_name": "Longbob Longsen", + "expiration_year": "15", + "expiration_month": "09", + "allows_charges": true, + "allows_payouts": false, + "creation_date": "2014-01-18T21:49:38-06:00", + "bank_name": "BANCOMER", + "bank_code": "012", + "customer_id": null + }, + "status": "#{status}", + "currency": "MXN", + "id": "tay1mauq3re4iuuk8bm4", + "creation_date": "2014-01-18T21:49:38-06:00", + "description": "Store Purchase", + "error_message": null, + "order_id": null, + "error_code": null +} + RESPONSE + end + + def successful_void_response + successful_purchase_response('cancelled') + end + + def failed_purchase_response + <<-RESPONSE +{ + "category": "gateway", + "description": "The card was declined", + "http_code": 402, + "error_code": 3001, + "request_id": "337cf033-9cd6-4314-a880-c71700e1625f" +} + RESPONSE + end + + def failed_authorize_response + <<-RESPONSE +{ + "category":"gateway", + "description":"The card is not supported on online transactions", + "http_code":412, + "error_code":3008, + "request_id":"a4001ef2-7613-4ec8-a23b-4de45154dbe4" +} + RESPONSE + end + + def generic_error_response + <<-RESPONSE + { + "category": "gateway", + "description": "Generic Error Response", + "http_code": 500, + "error_code": 1001, + "request_id": "b6b8241c-0bbc-4605-8c44-605b17d35aa8" + } + RESPONSE + end + + def transcript + <<-TRANSCRIPT + { + "amount":"1.00", + "method":"card", + "description":"Store Purchase", + "order_id":null, + "device_session_id":null, + "card":{ + "card_number":"4111111111111111", + "expiration_month":"09", + "expiration_year":"16", + "cvv2":"123", + "holder_name":"Longbob Longsen", + } + } + TRANSCRIPT + end + + def scrubbed_transcript + <<-SCRUBBED_TRANSCRIPT + { + "amount":"1.00", + "method":"card", + "description":"Store Purchase", + "order_id":null, + "device_session_id":null, + "card":{ + "card_number":"[FILTERED]", + "expiration_month":"09", + "expiration_year":"16", + "cvv2":"[FILTERED]", + "holder_name":"Longbob Longsen", + } + } + SCRUBBED_TRANSCRIPT + end + + def nil_cvv_transcript + <<-TRANSCRIPT + { + "amount":"1.00", + "method":"card", + "description":"Store Purchase", + "order_id":null, + "device_session_id":null, + "card":{ + "card_number":"4111111111111111", + "expiration_month":"09", + "expiration_year":"16", + "cvv2":null, + "holder_name":"Longbob Longsen", + } + } + TRANSCRIPT + end + + def nil_cvv_scrubbed_transcript + <<-SCRUBBED_TRANSCRIPT + { + "amount":"1.00", + "method":"card", + "description":"Store Purchase", + "order_id":null, + "device_session_id":null, + "card":{ + "card_number":"[FILTERED]", + "expiration_month":"09", + "expiration_year":"16", + "cvv2":[BLANK], + "holder_name":"Longbob Longsen", + } + } + SCRUBBED_TRANSCRIPT + end + + def empty_string_cvv_transcript + <<-TRANSCRIPT + { + "amount":"1.00", + "method":"card", + "description":"Store Purchase", + "order_id":null, + "device_session_id":null, + "card":{ + "card_number":"4111111111111111", + "expiration_month":"09", + "expiration_year":"16", + "cvv2":"", + "holder_name":"Longbob Longsen", + } + } + TRANSCRIPT + end + + def empty_string_cvv_scrubbed_transcript + <<-SCRUBBED_TRANSCRIPT + { + "amount":"1.00", + "method":"card", + "description":"Store Purchase", + "order_id":null, + "device_session_id":null, + "card":{ + "card_number":"[FILTERED]", + "expiration_month":"09", + "expiration_year":"16", + "cvv2":"[BLANK]", + "holder_name":"Longbob Longsen", + } + } + SCRUBBED_TRANSCRIPT + end + + def whitespace_string_cvv_transcript + <<-TRANSCRIPT + { + "amount":"1.00", + "method":"card", + "description":"Store Purchase", + "order_id":null, + "device_session_id":null, + "card":{ + "card_number":"4111111111111111", + "expiration_month":"09", + "expiration_year":"16", + "cvv2":" ", + "holder_name":"Longbob Longsen", + } + } + TRANSCRIPT + end + + def whitespace_string_cvv_scrubbed_transcript + <<-SCRUBBED_TRANSCRIPT + { + "amount":"1.00", + "method":"card", + "description":"Store Purchase", + "order_id":null, + "device_session_id":null, + "card":{ + "card_number":"[FILTERED]", + "expiration_month":"09", + "expiration_year":"16", + "cvv2":"[BLANK]", + "holder_name":"Longbob Longsen", + } + } + SCRUBBED_TRANSCRIPT + end +end diff --git a/test/unit/gateways/opp_test.rb b/test/unit/gateways/opp_test.rb new file mode 100644 index 00000000000..28efb3e2d72 --- /dev/null +++ b/test/unit/gateways/opp_test.rb @@ -0,0 +1,308 @@ +require 'test_helper' + +class OppTest < Test::Unit::TestCase + include CommStub + + def setup + @gateway = OppGateway.new(fixtures(:opp)) + @amount = 100 + + @valid_card = credit_card('4200000000000000', month: 05, year: 2018, verification_value: '123') + @invalid_card = credit_card('4444444444444444', month: 05, year: 2018, verification_value: '123') + + request_type = 'complete' # 'minimal' || 'complete' + time = Time.now.to_i + ip = '101.102.103.104' + @complete_request_options = { + order_id: "Order #{time}", + merchant_transaction_id: "active_merchant_test_complete #{time}", + address: address, + description: 'Store Purchase - Books', + # risk_workflow: true, + # test_mode: 'EXTERNAL' # or 'INTERNAL', valid only for test system + + billing_address: { + name: 'Billy Billing', + address1: 'My Street On the Moon, Apt 42/3.14', + city: 'Istambul', + state: 'IS', + zip: 'H12JK2354', + country: 'TR', + }, + shipping_address: { + name: '', + address1: 'My Street On Upiter, Apt 3.14/2.78', + city: 'Moskau', + state: 'MO', + zip: 'MO2342432', + country: 'RU', + }, + customer: { + merchant_customer_id: "merchantCustomerId #{ip}", + givenname: 'Billy', + surname: 'Billing', + birth_date: '1965-05-01', + phone: '(?!?)555-5555', + mobile: '(?!?)234-23423', + email: 'billy.billing@nosuchdeal.com', + company_name: 'No such deal Ltd.', + identification_doctype: 'PASSPORT', + identification_docid: 'FakeID2342431234123', + ip: ip, + }, + } + + @minimal_request_options = { + order_id: "Order #{time}", + description: 'Store Purchase - Books', + } + + @complete_request_options['customParameters[SHOPPER_test124TestName009]'] = 'customParameters_test' + @complete_request_options['customParameters[SHOPPER_otherCustomerParameter]'] = 'otherCustomerParameter_test' + + @test_success_id = '8a82944a4e008ca9014e1273e0696122' + @test_failure_id = '8a8294494e0078a6014e12b371fb6a8e' + + @options = @minimal_request_options if request_type == 'minimal' + @options = @complete_request_options if request_type == 'complete' + end + + # ****************************************** SUCCESSFUL TESTS ****************************************** + def test_successful_purchase + @gateway.expects(:raw_ssl_request).returns(successful_response('DB', @test_success_id)) + response = @gateway.purchase(@amount, @valid_card, @options) + assert_success response, 'Failed purchase' + assert_equal @test_success_id, response.authorization + assert response.test? + end + + def test_successful_authorize + @gateway.expects(:raw_ssl_request).returns(successful_response('PA', @test_success_id)) + response = @gateway.authorize(@amount, @valid_card, @options) + assert_success response, 'Authorization Failed' + assert_equal @test_success_id, response.authorization + assert response.test? + end + + def test_successful_capture + @gateway.expects(:raw_ssl_request).returns(successful_response('PA', @test_success_id)) + auth = @gateway.authorize(@amount, @valid_card, @options) + assert_success auth, 'Authorization Failed' + assert_equal @test_success_id, auth.authorization + assert auth.test? + @gateway.expects(:raw_ssl_request).returns(successful_response('CP', @test_success_id)) + capt = @gateway.capture(@amount, auth.authorization, @options) + assert_success capt, 'Capture failed' + assert_equal @test_success_id, capt.authorization + assert capt.test? + end + + def test_successful_refund + @gateway.expects(:raw_ssl_request).returns(successful_response('DB', @test_success_id)) + purchase = @gateway.purchase(@amount, @valid_card, @options) + assert_success purchase, 'Purchase failed' + assert purchase.test? + @gateway.expects(:raw_ssl_request).returns(successful_response('RF', @test_success_id)) + refund = @gateway.refund(@amount, purchase.authorization, @options) + assert_success refund, 'Refund failed' + assert_equal @test_success_id, refund.authorization + assert refund.test? + end + + def test_successful_void + @gateway.expects(:raw_ssl_request).returns(successful_response('DB', @test_success_id)) + purchase = @gateway.purchase(@amount, @valid_card, @options) + assert_success purchase, 'Purchase failed' + assert purchase.test? + @gateway.expects(:raw_ssl_request).returns(successful_response('RV', @test_success_id)) + void = @gateway.void(purchase.authorization, @options) + assert_success void, 'Void failed' + assert_equal @test_success_id, void.authorization + assert void.test? + end + + def test_successful_store + @gateway.expects(:raw_ssl_request).returns(successful_store_response(@test_success_id)) + store = @gateway.store(@valid_card) + assert_success store + assert_equal "Request successfully processed in 'Merchant in Integrator Test Mode'", store.message + assert_equal @test_success_id, store.authorization + end + + # ****************************************** FAILURE TESTS ****************************************** + def test_failed_purchase + @gateway.expects(:raw_ssl_request).returns(failed_response('DB', @test_failure_id)) + response = @gateway.purchase(@amount, @invalid_card, @options) + assert_failure response + assert_equal '100.100.101', response.error_code + end + + def test_failed_authorize + @gateway.expects(:raw_ssl_request).returns(failed_response('PA', @test_failure_id)) + response = @gateway.authorize(@amount, @invalid_card, @options) + assert_failure response + assert_equal '100.100.101', response.error_code + end + + def test_failed_capture + @gateway.expects(:raw_ssl_request).returns(failed_response('CP', @test_failure_id)) + response = @gateway.capture(@amount, @invalid_card) + assert_failure response + assert_equal '100.100.101', response.error_code + end + + def test_failed_refund + @gateway.expects(:raw_ssl_request).returns(failed_response('PF', @test_failure_id)) + response = @gateway.refund(@amount, @test_success_id) + assert_failure response + assert_equal '100.100.101', response.error_code + end + + def test_failed_void + @gateway.expects(:raw_ssl_request).returns(failed_response('RV', @test_failure_id)) + response = @gateway.void(@test_success_id, @options) + assert_failure response + assert_equal '100.100.101', response.error_code + end + + def test_failed_store + @gateway.expects(:raw_ssl_request).returns(failed_store_response(@test_failure_id)) + store = @gateway.store(@invalid_card) + assert_failure store + assert_equal '100.100.101', store.error_code + end + + def test_passes_3d_secure_fields + options = @complete_request_options.merge({eci: 'eci', cavv: 'cavv', xid: 'xid'}) + + response = stub_comms(@gateway, :raw_ssl_request) do + @gateway.purchase(@amount, @valid_card, options) + end.check_request do |method, endpoint, data, headers| + assert_match(/threeDSecure.eci=eci/, data) + assert_match(/threeDSecure.verificationId=cavv/, data) + assert_match(/threeDSecure.xid=xid/, data) + end.respond_with(successful_response('DB', @test_success_id)) + + assert_success response + end + + def test_scrub + assert @gateway.supports_scrubbing? + assert_equal @gateway.scrub(pre_scrubbed), post_scrubbed + end + + private + + def pre_scrubbed + 'paymentType=DB&amount=1.00&currency=EUR&paymentBrand=VISA&card.holder=Longbob+Longsen&card.number=4200000000000000&card.expiryMonth=05&card.expiryYear=2018& card.cvv=123&billing.street1=456+My+Street&billing.street2=Apt+1&billing.city=Ottawa&billing.state=ON&billing.postcode=K1C2N6&billing.country=CA&authentication.entityId=8a8294174b7ecb28014b9699220015ca&authentication.password=sy6KJsT8&authentication.userId=8a8294174b7ecb28014b9699220015cc' + end + + def post_scrubbed + 'paymentType=DB&amount=1.00&currency=EUR&paymentBrand=VISA&card.holder=Longbob+Longsen&card.number=[FILTERED]&card.expiryMonth=05&card.expiryYear=2018& card.cvv=[FILTERED]&billing.street1=456+My+Street&billing.street2=Apt+1&billing.city=Ottawa&billing.state=ON&billing.postcode=K1C2N6&billing.country=CA&authentication.entityId=8a8294174b7ecb28014b9699220015ca&authentication.password=[FILTERED]&authentication.userId=8a8294174b7ecb28014b9699220015cc' + end + + def successful_response(type, id) + OppMockResponse.new(200, + JSON.generate({ + 'id' => id, + 'paymentType' => type, + 'paymentBrand' => 'VISA', + 'amount' => '1.00', + 'currency' => 'EUR', + 'descriptor' => '5410.9959.0306 OPP_Channel', + 'result' => { + 'code' => '000.100.110', + 'description' => "Request successfully processed in 'Merchant in Integrator Test Mode'" + }, + 'card' => { + 'bin' => '420000', + 'last4Digits' => '0000', + 'holder' => 'Longbob Longsen', + 'expiryMonth' => '05', + 'expiryYear' => '2018' + }, + 'buildNumber' => '20150618-111601.r185004.opp-tags-20150618_stage', + 'timestamp' => '2015-06-20 19:31:01+0000', + 'ndc' => '8a8294174b7ecb28014b9699220015ca_4453edbc001f405da557c05cb3c3add9' + }) + ) + end + + def successful_store_response(id) + OppMockResponse.new(200, + JSON.generate({ + 'id' => id, + 'result' => { + 'code' => '000.100.110', + 'description' => "Request successfully processed in 'Merchant in Integrator Test Mode'" + }, + 'card' => { + 'bin' => '420000', + 'last4Digits' => '0000', + 'holder' => 'Longbob Longsen', + 'expiryMonth' => '05', + 'expiryYear' => '2018' + }, + 'buildNumber' => '20150618-111601.r185004.opp-tags-20150618_stage', + 'timestamp' => '2015-06-20 19:31:01+0000', + 'ndc' => '8a8294174b7ecb28014b9699220015ca_4453edbc001f405da557c05cb3c3add9' + }) + ) + end + + def failed_response(type, id, code='100.100.101') + OppMockResponse.new(400, + JSON.generate({ + 'id' => id, + 'paymentType' => type, + 'paymentBrand' => 'VISA', + 'result' => { + 'code' => code, + 'description' => 'invalid creditcard, bank account number or bank name' + }, + 'card' => { + 'bin' => '444444', + 'last4Digits' => '4444', + 'holder' => 'Longbob Longsen', + 'expiryMonth' => '05', + 'expiryYear' => '2018' + }, + 'buildNumber' => '20150618-111601.r185004.opp-tags-20150618_stage', + 'timestamp' => '2015-06-20 20:40:26+0000', + 'ndc' => '8a8294174b7ecb28014b9699220015ca_5200332e7d664412a84ed5f4777b3c7d' + }) + ) + end + + def failed_store_response(id, code='100.100.101') + OppMockResponse.new(400, + JSON.generate({ + 'id' => id, + 'result' => { + 'code' => code, + 'description' => 'invalid creditcard, bank account number or bank name' + }, + 'card' => { + 'bin' => '444444', + 'last4Digits' => '4444', + 'holder' => 'Longbob Longsen', + 'expiryMonth' => '05', + 'expiryYear' => '2018' + }, + 'buildNumber' => '20150618-111601.r185004.opp-tags-20150618_stage', + 'timestamp' => '2015-06-20 20:40:26+0000', + 'ndc' => '8a8294174b7ecb28014b9699220015ca_5200332e7d664412a84ed5f4777b3c7d' + }) + ) + end + + class OppMockResponse + attr_reader :code, :body + + def initialize(code, body) + @code = code + @body = body + end + end + +end diff --git a/test/unit/gateways/optimal_payment_test.rb b/test/unit/gateways/optimal_payment_test.rb index 392b3000d06..f8d194e94ad 100644 --- a/test/unit/gateways/optimal_payment_test.rb +++ b/test/unit/gateways/optimal_payment_test.rb @@ -6,11 +6,14 @@ class ActiveMerchant::Billing::OptimalPaymentGateway end class OptimalPaymentTest < Test::Unit::TestCase + include CommStub + def setup @gateway = OptimalPaymentGateway.new( - :login => 'login', - :password => 'password' - ) + :account_number => '12345678', + :store_id => 'login', + :password => 'password' + ) @credit_card = credit_card @amount = 100 @@ -28,6 +31,14 @@ def test_full_request assert_match full_request, @gateway.cc_auth_request(@amount, @options) end + def test_ip_address_is_passed + stub_comms do + @gateway.purchase(@amount, @credit_card, @options.merge(ip: '1.2.3.4')) + end.check_request do |endpoint, data, headers| + assert_match %r{customerIP%3E1.2.3.4%3C}, data + end.respond_with(successful_purchase_response) + end + def test_minimal_request options = { :order_id => '1', @@ -61,7 +72,7 @@ def test_successful_purchase end def test_purchase_from_canada_includes_state_field - @options[:billing_address][:country] = "CA" + @options[:billing_address][:country] = 'CA' @gateway.expects(:ssl_post).with do |url, data| data =~ /state/ && data !~ /region/ end.returns(successful_purchase_response) @@ -70,7 +81,7 @@ def test_purchase_from_canada_includes_state_field end def test_purchase_from_us_includes_state_field - @options[:billing_address][:country] = "US" + @options[:billing_address][:country] = 'US' @gateway.expects(:ssl_post).with do |url, data| data =~ /state/ && data !~ /region/ end.returns(successful_purchase_response) @@ -79,7 +90,7 @@ def test_purchase_from_us_includes_state_field end def test_purchase_from_any_other_country_includes_region_field - @options[:billing_address][:country] = "GB" + @options[:billing_address][:country] = 'GB' @gateway.expects(:ssl_post).with do |url, data| data =~ /region/ && data !~ /state/ end.returns(successful_purchase_response) @@ -88,11 +99,11 @@ def test_purchase_from_any_other_country_includes_region_field end def test_purchase_with_shipping_address - @options[:shipping_address] = {:country => "CA"} + @options[:shipping_address] = {:country => 'CA'} @gateway.expects(:ssl_post).with do |url, data| - xml = data.split("&").detect{|string| string =~ /txnRequest=/}.gsub("txnRequest=","") - doc = Nokogiri::XML.parse(URI.decode(xml)) - doc.xpath('//xmlns:shippingDetails/xmlns:country').first.text == "CA" && doc.to_s.include?('<shippingDetails>') + xml = data.split('&').detect { |string| string =~ /txnRequest=/ }.gsub('txnRequest=', '') + doc = Nokogiri::XML.parse(CGI.unescape(xml)) + doc.xpath('//xmlns:shippingDetails/xmlns:country').first.text == 'CA' && doc.to_s.include?('<shippingDetails>') end.returns(successful_purchase_response) assert @gateway.purchase(@amount, @credit_card, @options) @@ -101,18 +112,49 @@ def test_purchase_with_shipping_address def test_purchase_without_shipping_address @options[:shipping_address] = nil @gateway.expects(:ssl_post).with do |url, data| - xml = data.split("&").detect{|string| string =~ /txnRequest=/}.gsub("txnRequest=","") - doc = Nokogiri::XML.parse(URI.decode(xml)) + xml = data.split('&').detect { |string| string =~ /txnRequest=/ }.gsub('txnRequest=', '') + doc = Nokogiri::XML.parse(CGI.unescape(xml)) doc.to_s.include?('<shippingDetails>') == false end.returns(successful_purchase_response) assert @gateway.purchase(@amount, @credit_card, @options) end + def test_purchase_without_billing_address + @options[:billing_address] = nil + @gateway.expects(:ssl_post).returns(successful_purchase_response) + + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + end + + def test_cvd_fields_pass_correctly + stub_comms do + @gateway.purchase(@amount, @credit_card, @options) + end.check_request do |endpoint, data, headers| + assert_match(/cvdIndicator%3E1%3C\/cvdIndicator%3E%0A%20%20%20%20%3Ccvd%3E123%3C\/cvd/, data) + end.respond_with(successful_purchase_response) + + credit_card = CreditCard.new( + :number => '4242424242424242', + :month => 9, + :year => Time.now.year + 1, + :first_name => 'Longbob', + :last_name => 'Longsen', + :brand => 'visa' + ) + + stub_comms do + @gateway.purchase(@amount, credit_card, @options) + end.check_request do |endpoint, data, headers| + assert_match(/cvdIndicator%3E0%3C\/cvdIndicator%3E%0A%20%20%3C\/card/, data) + end.respond_with(failed_purchase_response) + end + def test_successful_void @gateway.expects(:ssl_post).returns(successful_purchase_response) - assert response = @gateway.void("1234567", @options) + assert response = @gateway.void('1234567', @options) assert_instance_of Response, response assert_success response @@ -130,22 +172,21 @@ def test_unsuccessful_request end def test_in_production_with_test_param_sends_request_to_test_server - begin - ActiveMerchant::Billing::Base.mode = :production - @gateway = OptimalPaymentGateway.new( - :login => 'login', - :password => 'password', - :test => true - ) - @gateway.expects(:ssl_post).with("https://webservices.test.optimalpayments.com/creditcardWS/CreditCardServlet/v1", anything).returns(successful_purchase_response) - - assert response = @gateway.purchase(@amount, @credit_card, @options) - assert_instance_of Response, response - assert_success response - assert response.test? - ensure - ActiveMerchant::Billing::Base.mode = :test - end + ActiveMerchant::Billing::Base.mode = :production + @gateway = OptimalPaymentGateway.new( + :account_number => '12345678', + :store_id => 'login', + :password => 'password', + :test => true + ) + @gateway.expects(:ssl_post).with('https://webservices.test.optimalpayments.com/creditcardWS/CreditCardServlet/v1', anything).returns(successful_purchase_response) + + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_instance_of Response, response + assert_success response + assert response.test? + ensure + ActiveMerchant::Billing::Base.mode = :test end def test_avs_result_in_response @@ -170,12 +211,27 @@ def test_avs_results_not_in_response assert !response.cvv_result['code'] end - def test_email_being_sent - @gateway.expects(:ssl_post).with do |url, data| - data =~ /email%3Eemail%2540example.com%3C\/email/ + def test_deprecated_options + assert_deprecation_warning("The 'account' option is deprecated in favor of 'account_number' and will be removed in a future version.") do + @gateway = OptimalPaymentGateway.new( + :account => '12345678', + :store_id => 'login', + :password => 'password' + ) + end + + assert_deprecation_warning("The 'login' option is deprecated in favor of 'store_id' and will be removed in a future version.") do + @gateway = OptimalPaymentGateway.new( + :account_number => '12345678', + :login => 'login', + :password => 'password' + ) end + end - @gateway.purchase(@amount, @credit_card, @options) + def test_scrub + assert_equal @gateway.scrub(pre_scrubbed), post_scrubbed + assert_equal @gateway.scrub(pre_scrubbed_double_escaped), post_scrubbed_double_escaped end private @@ -184,7 +240,7 @@ def full_request str = <<-XML <ccAuthRequestV1 xmlns> <merchantAccount> - <accountNum/> + <accountNum>12345678</accountNum> <storeID>login</storeID> <storePwd>password</storePwd> </merchantAccount> @@ -204,14 +260,14 @@ def full_request <cardPayMethod>WEB</cardPayMethod> <firstName>Jim</firstName> <lastName>Smith</lastName> - <street>1234+My+Street</street> - <street2>Apt+1</street2> + <street>456 My Street</street> + <street2>Apt 1</street2> <city>Ottawa</city> <state>ON</state> <country>CA</country> <zip>K1C2N6</zip> - <phone>%28555%29555-5555</phone> - <email>email%40example.com</email> + <phone>(555)555-5555</phone> + <email>email@example.com</email> </billingDetails> </ccAuthRequestV1> XML @@ -222,7 +278,7 @@ def minimal_request str = <<-XML <ccAuthRequestV1 xmlns> <merchantAccount> - <accountNum/> + <accountNum>12345678</accountNum> <storeID>login</storeID> <storePwd>password</storePwd> </merchantAccount> @@ -235,6 +291,7 @@ def minimal_request <year>#{Time.now.year + 1}</year> </cardExpiry> <cardType>VI</cardType> + <cvdIndicator>0</cvdIndicator> </card> <billingDetails> <cardPayMethod>WEB</cardPayMethod> @@ -329,4 +386,64 @@ def failed_purchase_response </ccTxnResponseV1> XML end + + def pre_scrubbed + <<-EOS +opening connection to webservices.test.optimalpayments.com:443... +opened +starting SSL for webservices.test.optimalpayments.com:443... +SSL established +<- "POST /creditcardWS/CreditCardServlet/v1 HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: webservices.test.optimalpayments.com\r\nContent-Length: 1616\r\n\r\n" +<- "txnMode=ccPurchase&txnRequest=%3CccAuthRequestV1%20xmlns=%22http://www.optimalpayments.com/creditcard/xmlschema/v1%22%20xmlns:xsi=%22http://www.w3.org/2001/XMLSchema-instance%22%20xsi:schemaLocation=%22http://www.optimalpayments.com/creditcard/xmlschema/v1%22%3E%0A%20%20%3CmerchantAccount%3E%0A%20%20%20%20%3CaccountNum%3E1001134550%3C/accountNum%3E%0A%20%20%20%20%3CstoreID%3Etest%3C/storeID%3E%0A%20%20%20%20%3CstorePwd%3Etest%3C/storePwd%3E%0A%20%20%3C/merchantAccount%3E%0A%20%20%3CmerchantRefNum%3E1%3C/merchantRefNum%3E%0A%20%20%3Camount%3E1.0%3C/amount%3E%0A%20%20%3Ccard%3E%0A%20%20%20%20%3CcardNum%3E4387751111011%3C/cardNum%3E%0A%20%20%20%20%3CcardExpiry%3E%0A%20%20%20%20%20%20%3Cmonth%3E9%3C/month%3E%0A%20%20%20%20%20%20%3Cyear%3E2019%3C/year%3E%0A%20%20%20%20%3C/cardExpiry%3E%0A%20%20%20%20%3CcardType%3EVI%3C/cardType%3E%0A%20%20%20%20%3CcvdIndicator%3E1%3C/cvdIndicator%3E%0A%20%20%20%20%3Ccvd%3E123%3C/cvd%3E%0A%20%20%3C/card%3E%0A%20%20%3CbillingDetails%3E%0A%20%20%20%20%3CcardPayMethod%3EWEB%3C/cardPayMethod%3E%0A%20%20%20%20%3CfirstName%3EJim%3C/firstName%3E%0A%20%20%20%20%3ClastName%3ESmith%3C/lastName%3E%0A%20%20%20%20%3Cstreet%3E456%20My%20Street%3C/street%3E%0A%20%20%20%20%3Cstreet2%3EApt%201%3C/street2%3E%0A%20%20%20%20%3Ccity%3EOttawa%3C/city%3E%0A%20%20%20%20%3Cstate%3EON%3C/state%3E%0A%20%20%20%20%3Ccountry%3ECA%3C/country%3E%0A%20%20%20%20%3Czip%3EK1C2N6%3C/zip%3E%0A%20%20%20%20%3Cphone%3E(555)555-5555%3C/phone%3E%0A%20%20%20%20%3Cemail%3Eemail@example.com%3C/email%3E%0A%20%20%3C/billingDetails%3E%0A%20%20%3CcustomerIP%3E1.2.3.4%3C/customerIP%3E%0A%3C/ccAuthRequestV1%3E%0A" +-> "HTTP/1.1 200 OK\r\n" +-> "Server: WebServer32xS10i3\r\n" +-> "Content-Length: 632\r\n" +-> "X-ApplicationUid: GUID=610a301289c34e8254330b7edc724f5b\r\n" +-> "Content-Type: application/xml\r\n" +-> "Date: Mon, 12 Feb 2018 21:57:42 GMT\r\n" +-> "Connection: close\r\n" +-> "\r\n" +reading 632 bytes... +-> "<" +-> "?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<ccTxnResponseV1 xmlns=\"http://www.optimalpayments.com/creditcard/xmlschema/v1\"><confirmationNumber>498871860</confirmationNumber><decision>ACCEPTED</decision><code>0</code><description>No Error</description><authCode>369231</authCode><avsResponse>X</avsResponse><cvdResponse>M</cvdResponse><detail><tag>InternalResponseCode</tag><value>0</value></detail><detail><tag>SubErrorCode</tag><value>0</value></detail><detail><tag>InternalResponseDescription</tag><value>no_error</value></detail><txnTime>2018-02-12T16:57:42.289-05:00</txnTime><duplicateFound>false</duplicateFound></ccTxnResponseV1>" +read 632 bytes +Conn close + EOS + end + + def post_scrubbed + <<-EOS +opening connection to webservices.test.optimalpayments.com:443... +opened +starting SSL for webservices.test.optimalpayments.com:443... +SSL established +<- "POST /creditcardWS/CreditCardServlet/v1 HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: webservices.test.optimalpayments.com\r\nContent-Length: 1616\r\n\r\n" +<- "txnMode=ccPurchase&txnRequest=%3CccAuthRequestV1%20xmlns=%22http://www.optimalpayments.com/creditcard/xmlschema/v1%22%20xmlns:xsi=%22http://www.w3.org/2001/XMLSchema-instance%22%20xsi:schemaLocation=%22http://www.optimalpayments.com/creditcard/xmlschema/v1%22%3E%0A%20%20%3CmerchantAccount%3E%0A%20%20%20%20%3CaccountNum%3E1001134550%3C/accountNum%3E%0A%20%20%20%20%3CstoreID%3Etest%3C/storeID%3E%0A%20%20%20%20%3CstorePwd%3E[FILTERED]%3C/storePwd%3E%0A%20%20%3C/merchantAccount%3E%0A%20%20%3CmerchantRefNum%3E1%3C/merchantRefNum%3E%0A%20%20%3Camount%3E1.0%3C/amount%3E%0A%20%20%3Ccard%3E%0A%20%20%20%20%3CcardNum%3E[FILTERED]%3C/cardNum%3E%0A%20%20%20%20%3CcardExpiry%3E%0A%20%20%20%20%20%20%3Cmonth%3E9%3C/month%3E%0A%20%20%20%20%20%20%3Cyear%3E2019%3C/year%3E%0A%20%20%20%20%3C/cardExpiry%3E%0A%20%20%20%20%3CcardType%3EVI%3C/cardType%3E%0A%20%20%20%20%3CcvdIndicator%3E1%3C/cvdIndicator%3E%0A%20%20%20%20%3Ccvd%3E[FILTERED]%3C/cvd%3E%0A%20%20%3C/card%3E%0A%20%20%3CbillingDetails%3E%0A%20%20%20%20%3CcardPayMethod%3EWEB%3C/cardPayMethod%3E%0A%20%20%20%20%3CfirstName%3EJim%3C/firstName%3E%0A%20%20%20%20%3ClastName%3ESmith%3C/lastName%3E%0A%20%20%20%20%3Cstreet%3E456%20My%20Street%3C/street%3E%0A%20%20%20%20%3Cstreet2%3EApt%201%3C/street2%3E%0A%20%20%20%20%3Ccity%3EOttawa%3C/city%3E%0A%20%20%20%20%3Cstate%3EON%3C/state%3E%0A%20%20%20%20%3Ccountry%3ECA%3C/country%3E%0A%20%20%20%20%3Czip%3EK1C2N6%3C/zip%3E%0A%20%20%20%20%3Cphone%3E(555)555-5555%3C/phone%3E%0A%20%20%20%20%3Cemail%3Eemail@example.com%3C/email%3E%0A%20%20%3C/billingDetails%3E%0A%20%20%3CcustomerIP%3E1.2.3.4%3C/customerIP%3E%0A%3C/ccAuthRequestV1%3E%0A" +-> "HTTP/1.1 200 OK\r\n" +-> "Server: WebServer32xS10i3\r\n" +-> "Content-Length: 632\r\n" +-> "X-ApplicationUid: GUID=610a301289c34e8254330b7edc724f5b\r\n" +-> "Content-Type: application/xml\r\n" +-> "Date: Mon, 12 Feb 2018 21:57:42 GMT\r\n" +-> "Connection: close\r\n" +-> "\r\n" +reading 632 bytes... +-> "<" +-> "?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<ccTxnResponseV1 xmlns=\"http://www.optimalpayments.com/creditcard/xmlschema/v1\"><confirmationNumber>498871860</confirmationNumber><decision>ACCEPTED</decision><code>0</code><description>No Error</description><authCode>369231</authCode><avsResponse>X</avsResponse><cvdResponse>M</cvdResponse><detail><tag>InternalResponseCode</tag><value>0</value></detail><detail><tag>SubErrorCode</tag><value>0</value></detail><detail><tag>InternalResponseDescription</tag><value>no_error</value></detail><txnTime>2018-02-12T16:57:42.289-05:00</txnTime><duplicateFound>false</duplicateFound></ccTxnResponseV1>" +read 632 bytes +Conn close + EOS + end + + def pre_scrubbed_double_escaped + <<-PRE_SCRUBBED + txnMode=ccPurchase&txnRequest=%3CccAuthRequestV1+xmlns%3D%22http%3A%2F%2Fwww.optimalpayments.com%2Fcreditcard%2Fxmlschema%2Fv1%22+xmlns%3Axsi%3D%22http%3A%2F%2Fwww.w3.org%2F2001%2FXMLSchema-instance%22+xsi%3AschemaLocation%3D%22http%3A%2F%2Fwww.optimalpayments.com%2Fcreditcard%2Fxmlschema%2Fv1%22%3E%0A++%3CmerchantAccount%3E%0A++++%3CaccountNum%3E89986098%3C%2FaccountNum%3E%0A++++%3CstoreID%3Etest%3C%2FstoreID%3E%0A++++%3CstorePwd%3Etest%3C%2FstorePwd%3E%0A++%3C%2FmerchantAccount%3E%0A++%3CmerchantRefNum%3E1%3C%2FmerchantRefNum%3E%0A++%3Camount%3E1.0%3C%2Famount%3E%0A++%3Ccard%3E%0A++++%3CcardNum%3E4387751111011%3C%2FcardNum%3E%0A++++%3CcardExpiry%3E%0A++++++%3Cmonth%3E9%3C%2Fmonth%3E%0A++++++%3Cyear%3E2015%3C%2Fyear%3E%0A++++%3C%2FcardExpiry%3E%0A++++%3CcardType%3EVI%3C%2FcardType%3E%0A++++%3CcvdIndicator%3E1%3C%2FcvdIndicator%3E%0A++++%3Ccvd%3E123%3C%2Fcvd%3E%0A++%3C%2Fcard%3E%0A++%3CbillingDetails%3E%0A++++%3CcardPayMethod%3EWEB%3C%2FcardPayMethod%3E%0A++++%3CfirstName%3EJim%3C%2FfirstName%3E%0A++++%3ClastName%3ESmith%3C%2FlastName%3E%0A++++%3Cstreet%3E1234+My+Street%3C%2Fstreet%3E%0A++++%3Cstreet2%3EApt+1%3C%2Fstreet2%3E%0A++++%3Ccity%3EOttawa%3C%2Fcity%3E%0A++++%3Cstate%3EON%3C%2Fstate%3E%0A++++%3Ccountry%3ECA%3C%2Fcountry%3E%0A++++%3Czip%3EK1C2N6%3C%2Fzip%3E%0A++++%3Cphone%3E%28555%29555-5555%3C%2Fphone%3E%0A++++%3Cemail%3Eemail%40example.com%3C%2Femail%3E%0A++%3C%2FbillingDetails%3E%0A++%3CcustomerIP%3E1.2.3.4%3C%2FcustomerIP%3E%0A%3C%2FccAuthRequestV1%3E%0A + PRE_SCRUBBED + end + + def post_scrubbed_double_escaped + <<-POST_SCRUBBED + txnMode=ccPurchase&txnRequest=%3CccAuthRequestV1+xmlns%3D%22http%3A%2F%2Fwww.optimalpayments.com%2Fcreditcard%2Fxmlschema%2Fv1%22+xmlns%3Axsi%3D%22http%3A%2F%2Fwww.w3.org%2F2001%2FXMLSchema-instance%22+xsi%3AschemaLocation%3D%22http%3A%2F%2Fwww.optimalpayments.com%2Fcreditcard%2Fxmlschema%2Fv1%22%3E%0A++%3CmerchantAccount%3E%0A++++%3CaccountNum%3E89986098%3C%2FaccountNum%3E%0A++++%3CstoreID%3Etest%3C%2FstoreID%3E%0A++++%3CstorePwd%3E[FILTERED]%3C%2FstorePwd%3E%0A++%3C%2FmerchantAccount%3E%0A++%3CmerchantRefNum%3E1%3C%2FmerchantRefNum%3E%0A++%3Camount%3E1.0%3C%2Famount%3E%0A++%3Ccard%3E%0A++++%3CcardNum%3E[FILTERED]%3C%2FcardNum%3E%0A++++%3CcardExpiry%3E%0A++++++%3Cmonth%3E9%3C%2Fmonth%3E%0A++++++%3Cyear%3E2015%3C%2Fyear%3E%0A++++%3C%2FcardExpiry%3E%0A++++%3CcardType%3EVI%3C%2FcardType%3E%0A++++%3CcvdIndicator%3E1%3C%2FcvdIndicator%3E%0A++++%3Ccvd%3E[FILTERED]%3C%2Fcvd%3E%0A++%3C%2Fcard%3E%0A++%3CbillingDetails%3E%0A++++%3CcardPayMethod%3EWEB%3C%2FcardPayMethod%3E%0A++++%3CfirstName%3EJim%3C%2FfirstName%3E%0A++++%3ClastName%3ESmith%3C%2FlastName%3E%0A++++%3Cstreet%3E1234+My+Street%3C%2Fstreet%3E%0A++++%3Cstreet2%3EApt+1%3C%2Fstreet2%3E%0A++++%3Ccity%3EOttawa%3C%2Fcity%3E%0A++++%3Cstate%3EON%3C%2Fstate%3E%0A++++%3Ccountry%3ECA%3C%2Fcountry%3E%0A++++%3Czip%3EK1C2N6%3C%2Fzip%3E%0A++++%3Cphone%3E%28555%29555-5555%3C%2Fphone%3E%0A++++%3Cemail%3Eemail%40example.com%3C%2Femail%3E%0A++%3C%2FbillingDetails%3E%0A++%3CcustomerIP%3E1.2.3.4%3C%2FcustomerIP%3E%0A%3C%2FccAuthRequestV1%3E%0A + POST_SCRUBBED + end end diff --git a/test/unit/gateways/orbital_cvv_result_test.rb b/test/unit/gateways/orbital_cvv_result_test.rb new file mode 100644 index 00000000000..311b15c1a6a --- /dev/null +++ b/test/unit/gateways/orbital_cvv_result_test.rb @@ -0,0 +1,38 @@ +require 'test_helper' + +class OrbitalCVVResultTest < Test::Unit::TestCase + def test_nil_data + result = ActiveMerchant::Billing::OrbitalGateway::CVVResult.new(nil) + assert_equal '', result.code + assert_equal ActiveMerchant::Billing::OrbitalGateway::CVVResult.messages[''], result.message + end + + def test_blank_data + result = ActiveMerchant::Billing::OrbitalGateway::CVVResult.new('') + assert_equal '', result.code + assert_equal ActiveMerchant::Billing::OrbitalGateway::CVVResult.messages[''], result.message + end + + def test_successful_match + result = ActiveMerchant::Billing::OrbitalGateway::CVVResult.new('M') + assert_equal 'M', result.code + assert_equal ActiveMerchant::Billing::OrbitalGateway::CVVResult.messages['M'], result.message + end + + def test_failed_match + result = ActiveMerchant::Billing::OrbitalGateway::CVVResult.new('N') + assert_equal 'N', result.code + assert_equal ActiveMerchant::Billing::OrbitalGateway::CVVResult.messages['N'], result.message + end + + def test_code_upcasing + result = ActiveMerchant::Billing::OrbitalGateway::CVVResult.new('m') + assert_equal 'M', result.code + end + + def test_to_hash + result = ActiveMerchant::Billing::OrbitalGateway::CVVResult.new('M').to_hash + assert_equal 'M', result['code'] + assert_equal ActiveMerchant::Billing::OrbitalGateway::CVVResult.messages['M'], result['message'] + end +end diff --git a/test/unit/gateways/orbital_test.rb b/test/unit/gateways/orbital_test.rb index 667b054ab22..6f594600095 100644 --- a/test/unit/gateways/orbital_test.rb +++ b/test/unit/gateways/orbital_test.rb @@ -1,3 +1,5 @@ +# encoding: utf-8 + require 'test_helper' require 'nokogiri' @@ -10,7 +12,51 @@ def setup :password => 'password', :merchant_id => 'merchant_id' ) - @customer_ref_num = "ABC" + @customer_ref_num = 'ABC' + + @level_2 = { + tax_indicator: '1', + tax: '10', + advice_addendum_1: 'taa1 - test', + advice_addendum_2: 'taa2 - test', + advice_addendum_3: 'taa3 - test', + advice_addendum_4: 'taa4 - test', + purchase_order: '123abc', + name: address[:name], + address1: address[:address1], + address2: address[:address2], + city: address[:city], + state: address[:state], + zip: address[:zip], + } + + @options = { :order_id => '1'} + @options_stored_credentials = { + mit_msg_type: 'MRSB', + mit_stored_credential_ind: 'Y', + mit_submitted_transaction_id: '123456abcdef' + } + @normalized_mit_stored_credential = { + stored_credential: { + initial_transaction: false, + initiator: 'merchant', + reason_type: 'unscheduled', + network_transaction_id: 'abcdefg12345678' + } + } + @normalized_initial_stored_credential = { + stored_credential: { + initial_transaction: true, + initiator: 'customer' + } + } + @three_d_secure_options = { + three_d_secure: { + eci: '5', + xid: 'TESTXID', + cavv: 'TESTCAVV', + } + } end def test_successful_purchase @@ -22,19 +68,128 @@ def test_successful_purchase assert_equal '4A5398CF9B87744GG84A1D30F2F2321C66249416;1', response.authorization end + def test_level_2_data + stub_comms do + @gateway.purchase(50, credit_card, @options.merge(level_2_data: @level_2)) + end.check_request do |endpoint, data, headers| + assert_match %{<TaxInd>#{@level_2[:tax_indicator].to_i}</TaxInd>}, data + assert_match %{<Tax>#{@level_2[:tax].to_i}</Tax>}, data + assert_match %{<AMEXTranAdvAddn1>#{@level_2[:advice_addendum_1]}</AMEXTranAdvAddn1>}, data + assert_match %{<AMEXTranAdvAddn2>#{@level_2[:advice_addendum_2]}</AMEXTranAdvAddn2>}, data + assert_match %{<AMEXTranAdvAddn3>#{@level_2[:advice_addendum_3]}</AMEXTranAdvAddn3>}, data + assert_match %{<AMEXTranAdvAddn4>#{@level_2[:advice_addendum_4]}</AMEXTranAdvAddn4>}, data + assert_match %{<PCOrderNum>#{@level_2[:purchase_order]}</PCOrderNum>}, data + assert_match %{<PCDestZip>#{@level_2[:zip]}</PCDestZip>}, data + assert_match %{<PCDestName>#{@level_2[:name]}</PCDestName>}, data + assert_match %{<PCDestAddress1>#{@level_2[:address1]}</PCDestAddress1>}, data + assert_match %{<PCDestAddress2>#{@level_2[:address2]}</PCDestAddress2>}, data + assert_match %{<PCDestCity>#{@level_2[:city]}</PCDestCity>}, data + assert_match %{<PCDestState>#{@level_2[:state]}</PCDestState>}, data + end.respond_with(successful_purchase_response) + end + + def test_network_tokenization_credit_card_data + stub_comms do + @gateway.purchase(50, network_tokenization_credit_card(nil, eci: '5', transaction_id: 'BwABB4JRdgAAAAAAiFF2AAAAAAA='), @options) + end.check_request do |endpoint, data, headers| + assert_match %{<AuthenticationECIInd>5</AuthenticationECIInd>}, data + assert_match %{<DPANInd>Y</DPANInd>}, data + assert_match %{DigitalTokenCryptogram}, data + assert_match %{XID}, data + end.respond_with(successful_purchase_response) + end + + def test_three_d_secure_data_on_visa_purchase + stub_comms do + @gateway.purchase(50, credit_card, @options.merge(@three_d_secure_options)) + end.check_request do |endpoint, data, headers| + assert_match %{<AuthenticationECIInd>5</AuthenticationECIInd>}, data + assert_match %{<CAVV>TESTCAVV</CAVV>}, data + assert_match %{<XID>TESTXID</XID>}, data + end.respond_with(successful_purchase_response) + end + + def test_three_d_secure_data_on_visa_authorization + stub_comms do + @gateway.authorize(50, credit_card, @options.merge(@three_d_secure_options)) + end.check_request do |endpoint, data, headers| + assert_match %{<AuthenticationECIInd>5</AuthenticationECIInd>}, data + assert_match %{<CAVV>TESTCAVV</CAVV>}, data + assert_match %{<XID>TESTXID</XID>}, data + end.respond_with(successful_purchase_response) + end + + def test_three_d_secure_data_on_master_purchase + stub_comms do + @gateway.purchase(50, credit_card(nil, brand: 'master'), @options.merge(@three_d_secure_options)) + end.check_request do |endpoint, data, headers| + assert_match %{<AuthenticationECIInd>5</AuthenticationECIInd>}, data + assert_match %{<AAV>TESTCAVV</AAV>}, data + end.respond_with(successful_purchase_response) + end + + def test_three_d_secure_data_on_master_authorization + stub_comms do + @gateway.authorize(50, credit_card(nil, brand: 'master'), @options.merge(@three_d_secure_options)) + end.check_request do |endpoint, data, headers| + assert_match %{<AuthenticationECIInd>5</AuthenticationECIInd>}, data + assert_match %{<AAV>TESTCAVV</AAV>}, data + end.respond_with(successful_purchase_response) + end + + def test_three_d_secure_data_on_american_express_purchase + stub_comms do + @gateway.purchase(50, credit_card(nil, brand: 'american_express'), @options.merge(@three_d_secure_options)) + end.check_request do |endpoint, data, headers| + assert_match %{<AuthenticationECIInd>5</AuthenticationECIInd>}, data + assert_match %{<AEVV>TESTCAVV</AEVV>}, data + assert_match %{<PymtBrandProgramCode>ASK</PymtBrandProgramCode>}, data + end.respond_with(successful_purchase_response) + end + + def test_three_d_secure_data_on_american_express_authorization + stub_comms do + @gateway.authorize(50, credit_card(nil, brand: 'american_express'), @options.merge(@three_d_secure_options)) + end.check_request do |endpoint, data, headers| + assert_match %{<AuthenticationECIInd>5</AuthenticationECIInd>}, data + assert_match %{<AEVV>TESTCAVV</AEVV>}, data + assert_match %{<PymtBrandProgramCode>ASK</PymtBrandProgramCode>}, data + end.respond_with(successful_purchase_response) + end + + def test_currency_exponents + stub_comms do + @gateway.purchase(50, credit_card, :order_id => '1') + end.check_request do |endpoint, data, headers| + assert_match %r{<CurrencyExponent>2<\/CurrencyExponent>}, data + end.respond_with(successful_purchase_response) + + stub_comms do + @gateway.purchase(50, credit_card, :order_id => '1', :currency => 'CAD') + end.check_request do |endpoint, data, headers| + assert_match %r{<CurrencyExponent>2<\/CurrencyExponent>}, data + end.respond_with(successful_purchase_response) + + stub_comms do + @gateway.purchase(50, credit_card, :order_id => '1', :currency => 'JPY') + end.check_request do |endpoint, data, headers| + assert_match %r{<CurrencyExponent>0<\/CurrencyExponent>}, data + end.respond_with(successful_purchase_response) + end + def test_unauthenticated_response @gateway.expects(:ssl_post).returns(failed_purchase_response) assert response = @gateway.purchase(101, credit_card, :order_id => '1') assert_instance_of Response, response assert_failure response - assert_equal "AUTH DECLINED 12001", response.message + assert_equal 'AUTH DECLINED 12001', response.message end def test_successful_void @gateway.expects(:ssl_post).returns(successful_void_response) - assert response = @gateway.void("identifier") + assert response = @gateway.void('identifier') assert_instance_of Response, response assert_success response assert_nil response.message @@ -43,8 +198,8 @@ def test_successful_void def test_deprecated_void @gateway.expects(:ssl_post).returns(successful_void_response) - assert_deprecation_warning("Calling the void method with an amount parameter is deprecated and will be removed in a future version.", @gateway) do - assert response = @gateway.void(@amount, "identifier") + assert_deprecation_warning('Calling the void method with an amount parameter is deprecated and will be removed in a future version.') do + assert response = @gateway.void(50, 'identifier') assert_instance_of Response, response assert_success response assert_nil response.message @@ -66,24 +221,39 @@ def test_order_id_as_number def test_order_id_format response = stub_comms do - @gateway.purchase(101, credit_card, :order_id => "#1001.1") + @gateway.purchase(101, credit_card, :order_id => ' #101.23,56 $Hi &thére@Friends') end.check_request do |endpoint, data, headers| - assert_match(/<OrderID>1001-1<\/OrderID>/, data) + assert_match(/<OrderID>101-23,56 \$Hi &amp;thre@Fr<\/OrderID>/, data) end.respond_with(successful_purchase_response) assert_success response end def test_order_id_format_for_capture response = stub_comms do - @gateway.capture(101, "4A5398CF9B87744GG84A1D30F2F2321C66249416;1001.1", :order_id => "#1001.1") + @gateway.capture(101, '4A5398CF9B87744GG84A1D30F2F2321C66249416;1001.1', :order_id => '#1001.1') end.check_request do |endpoint, data, headers| assert_match(/<OrderID>1001-1<\/OrderID>/, data) end.respond_with(successful_purchase_response) assert_success response end + def test_numeric_merchant_id_for_caputre + gateway = ActiveMerchant::Billing::OrbitalGateway.new( + :login => 'login', + :password => 'password', + :merchant_id => 700000123456 + ) + + response = stub_comms(gateway) do + gateway.capture(101, '4A5398CF9B87744GG84A1D30F2F2321C66249416;1', @options) + end.check_request do |endpoint, data, headers| + assert_match(/<MerchantID>700000123456<\/MerchantID>/, data) + end.respond_with(successful_purchase_response) + assert_success response + end + def test_expiry_date - year = (DateTime.now + 1.year).strftime("%y") + year = (DateTime.now + 1.year).strftime('%y') assert_equal "09#{year}", @gateway.send(:expiry_date, credit_card) end @@ -110,8 +280,8 @@ def test_truncates_address def test_truncates_name card = credit_card('4242424242424242', - :first_name => 'John', - :last_name => 'Jacob Jingleheimer Smith-Jones') + :first_name => 'John', + :last_name => 'Jacob Jingleheimer Smith-Jones') response = stub_comms do @gateway.purchase(50, card, :order_id => 1, :billing_address => address) @@ -146,7 +316,7 @@ def test_truncates_phone end def test_truncates_zip - long_zip = '1234567890123' + long_zip = '1234567890123' response = stub_comms do @gateway.purchase(50, credit_card, :order_id => 1, :billing_address => address(:zip => long_zip)) @@ -156,6 +326,99 @@ def test_truncates_zip assert_success response end + def test_address_format + address_with_invalid_chars = address( + :address1 => '456% M|a^in \\S/treet', + :address2 => '|Apt. ^Num\\ber /One%', + :city => 'R^ise o\\f /th%e P|hoenix', + :state => '%O|H\\I/O', + :dest_address1 => '2/21%B |B^aker\\ St.', + :dest_address2 => 'L%u%xury S|u^i\\t/e', + :dest_city => '/Winn/i%p|e^g\\', + :dest_zip => 'A1A 2B2', + :dest_state => '^MB' + ) + + response = stub_comms do + @gateway.purchase(50, credit_card, :order_id => 1, + :billing_address => address_with_invalid_chars) + end.check_request do |endpoint, data, headers| + assert_match(/456 Main Street</, data) + assert_match(/Apt. Number One</, data) + assert_match(/Rise of the Phoenix</, data) + assert_match(/OH</, data) + assert_match(/221B Baker St.</, data) + assert_match(/Luxury Suite</, data) + assert_match(/Winnipeg</, data) + assert_match(/MB</, data) + end.respond_with(successful_purchase_response) + assert_success response + + response = stub_comms do + assert_deprecation_warning do + @gateway.add_customer_profile(credit_card, + :billing_address => address_with_invalid_chars) + end + end.check_request do |endpoint, data, headers| + assert_match(/456 Main Street</, data) + assert_match(/Apt. Number One</, data) + assert_match(/Rise of the Phoenix</, data) + end.respond_with(successful_profile_response) + assert_success response + end + + def test_truncates_by_byte_length + card = credit_card('4242424242424242', + :first_name => 'John', + :last_name => 'Jacob Jingleheimer Smith-Jones') + + long_address = address( + :address1 => '456 Stréêt Name is Really Long', + :address2 => 'Apårtmeñt 123456789012345678901', + :city => '¡Vancouver-by-the-sea!', + :state => 'ßC', + :zip => 'Postäl Cøde', + :dest_name => 'Pierré von Bürgermeister de Queso', + :dest_address1 => '9876 Stréêt Name is Really Long', + :dest_address2 => 'Apårtmeñt 987654321098765432109', + :dest_city => 'Montréal-of-the-south!', + :dest_state => 'Oñtario', + :dest_zip => 'Postäl Zïps' + ) + + response = stub_comms do + @gateway.purchase(50, card, :order_id => 1, + :billing_address => long_address) + end.check_request do |endpoint, data, headers| + assert_match(/456 Stréêt Name is Really Lo</, data) + assert_match(/Apårtmeñt 123456789012345678</, data) + assert_match(/¡Vancouver-by-the-s</, data) + assert_match(/ß</, data) + assert_match(/Postäl C</, data) + assert_match(/Pierré von Bürgermeister de </, data) + assert_match(/9876 Stréêt Name is Really L</, data) + assert_match(/Apårtmeñt 987654321098765432</, data) + assert_match(/Montréal-of-the-sou</, data) + assert_match(/O</, data) + assert_match(/Postäl Z</, data) + end.respond_with(successful_purchase_response) + assert_success response + + response = stub_comms do + assert_deprecation_warning do + @gateway.add_customer_profile(credit_card, + :billing_address => long_address) + end + end.check_request do |endpoint, data, headers| + assert_match(/456 Stréêt Name is Really Lo</, data) + assert_match(/Apårtmeñt 123456789012345678</, data) + assert_match(/¡Vancouver-by-the-s</, data) + assert_match(/ß</, data) + assert_match(/Postäl C</, data) + end.respond_with(successful_profile_response) + assert_success response + end + def test_nil_address_values_should_not_throw_exceptions @gateway.expects(:ssl_post).returns(successful_purchase_response) @@ -175,30 +438,95 @@ def test_nil_address_values_should_not_throw_exceptions end def test_dest_address + billing_address = address( + :dest_zip => '90001', + :dest_address1 => '456 Main St.', + :dest_city => 'Somewhere', + :dest_state => 'CA', + :dest_name => 'Joan Smith', + :dest_phone => '(123) 456-7890', + :dest_country => 'US') + response = stub_comms do - @gateway.purchase(50, credit_card, :order_id => 1, :billing_address => address(:dest_zip => '90001', - :dest_address1 => '123 Main St.', - :dest_city => 'Somewhere', - :dest_state => 'CA', - :dest_name => 'Joan Smith', - :dest_phone => '(123) 456-7890', - :dest_country => 'USA')) + @gateway.purchase(50, credit_card, :order_id => 1, + :billing_address => billing_address) end.check_request do |endpoint, data, headers| assert_match(/<AVSDestzip>90001/, data) - assert_match(/<AVSDestaddress1>123 Main St./, data) + assert_match(/<AVSDestaddress1>456 Main St./, data) assert_match(/<AVSDestaddress2/, data) assert_match(/<AVSDestcity>Somewhere/, data) assert_match(/<AVSDeststate>CA/, data) assert_match(/<AVSDestname>Joan Smith/, data) assert_match(/<AVSDestphoneNum>1234567890/, data) - assert_match(/<AVSDestcountryCode>USA/, data) + assert_match(/<AVSDestcountryCode>US/, data) end.respond_with(successful_purchase_response) assert_success response + + # non-AVS country + response = stub_comms do + @gateway.purchase(50, credit_card, :order_id => 1, + :billing_address => billing_address.merge(:dest_country => 'BR')) + end.check_request do |endpoint, data, headers| + assert_match(/<AVSDestcountryCode></, data) + end.respond_with(successful_purchase_response) + assert_success response + end + + def test_successful_purchase_with_negative_stored_credentials_indicator + stub_comms do + @gateway.purchase(50, credit_card, @options.merge(mit_stored_credential_ind: 'N')) + end.check_request do |endpoint, data, headers| + assert_no_match(/<MITMsgType>/, data) + assert_no_match(/<MITStoredCredentialInd>/, data) + end.respond_with(successful_purchase_response) + end + + def test_successful_purchase_with_stored_credentials + stub_comms do + @gateway.purchase(50, credit_card, @options.merge(@options_stored_credentials)) + end.check_request do |endpoint, data, headers| + assert_match %{<MITMsgType>#{@options_stored_credentials[:mit_msg_type]}</MITMsgType>}, data + assert_match %{<MITStoredCredentialInd>#{@options_stored_credentials[:mit_stored_credential_ind]}</MITStoredCredentialInd>}, data + assert_match %{<MITSubmittedTransactionID>#{@options_stored_credentials[:mit_submitted_transaction_id]}</MITSubmittedTransactionID>}, data + end.respond_with(successful_purchase_response) + end + + def test_successful_purchase_with_normalized_stored_credentials + stub_comms do + @gateway.purchase(50, credit_card, @options.merge(@normalized_mit_stored_credential)) + end.check_request do |endpoint, data, headers| + assert_match %{<MITMsgType>MUSE</MITMsgType>}, data + assert_match %{<MITStoredCredentialInd>Y</MITStoredCredentialInd>}, data + assert_match %{<MITSubmittedTransactionID>abcdefg12345678</MITSubmittedTransactionID>}, data + end.respond_with(successful_purchase_response) + end + + def test_successful_initial_purchase_with_normalized_stored_credentials + stub_comms do + @gateway.purchase(50, credit_card, @options.merge(@normalized_initial_stored_credential)) + end.check_request do |endpoint, data, headers| + assert_match %{<MITMsgType>CSTO</MITMsgType>}, data + assert_match %{<MITStoredCredentialInd>Y</MITStoredCredentialInd>}, data + end.respond_with(successful_purchase_response) + end + + def test_successful_purchase_with_overridden_normalized_stored_credentials + stub_comms do + @gateway.purchase(50, credit_card, @options.merge(@normalized_mit_stored_credential).merge(@options_stored_credentials)) + end.check_request do |endpoint, data, headers| + assert_match %{<MITMsgType>MRSB</MITMsgType>}, data + assert_match %{<MITStoredCredentialInd>Y</MITStoredCredentialInd>}, data + assert_match %{<MITSubmittedTransactionID>123456abcdef</MITSubmittedTransactionID>}, data + end.respond_with(successful_purchase_response) end def test_default_managed_billing response = stub_comms do - @gateway.add_customer_profile(credit_card, :managed_billing => {:start_date => "10-10-2014" }) + assert_deprecation_warning(Gateway::RECURRING_DEPRECATION_MESSAGE) do + assert_deprecation_warning do + @gateway.add_customer_profile(credit_card, :managed_billing => {:start_date => '10-10-2014' }) + end + end end.check_request do |endpoint, data, headers| assert_match(/<MBType>R/, data) assert_match(/<MBOrderIdGenerationMethod>IO/, data) @@ -210,10 +538,16 @@ def test_default_managed_billing def test_managed_billing response = stub_comms do - @gateway.add_customer_profile(credit_card, :managed_billing => {:start_date => "10-10-2014", - :end_date => "10-10-2015", + assert_deprecation_warning(Gateway::RECURRING_DEPRECATION_MESSAGE) do + assert_deprecation_warning do + @gateway.add_customer_profile(credit_card, + :managed_billing => { + :start_date => '10-10-2014', + :end_date => '10-10-2015', :max_dollar_value => 1500, :max_transactions => 12}) + end + end end.check_request do |endpoint, data, headers| assert_match(/<MBType>R/, data) assert_match(/<MBOrderIdGenerationMethod>IO/, data) @@ -230,7 +564,7 @@ def test_dont_send_customer_data_by_default @gateway.purchase(50, credit_card, :order_id => 1) end.check_request do |endpoint, data, headers| assert_no_match(/<CustomerRefNum>K1C2N6/, data) - assert_no_match(/<CustomerProfileFromOrderInd>1234 My Street/, data) + assert_no_match(/<CustomerProfileFromOrderInd>456 My Street/, data) assert_no_match(/<CustomerProfileOrderOverrideInd>Apt 1/, data) end.respond_with(successful_purchase_response) assert_success response @@ -259,8 +593,52 @@ def test_send_customer_data_when_customer_ref_is_provided assert_success response end + def test_dont_send_customer_profile_from_order_ind_for_profile_purchase + @gateway.options[:customer_profiles] = true + response = stub_comms do + @gateway.purchase(50, nil, :order_id => 1, :customer_ref_num => @customer_ref_num) + end.check_request do |endpoint, data, headers| + assert_no_match(/<CustomerProfileFromOrderInd>/, data) + end.respond_with(successful_purchase_response) + assert_success response + end + + def test_dont_send_customer_profile_from_order_ind_for_profile_authorize + @gateway.options[:customer_profiles] = true + response = stub_comms do + @gateway.authorize(50, nil, :order_id => 1, :customer_ref_num => @customer_ref_num) + end.check_request do |endpoint, data, headers| + assert_no_match(/<CustomerProfileFromOrderInd>/, data) + end.respond_with(successful_purchase_response) + assert_success response + end + + def test_currency_code_and_exponent_are_set_for_profile_purchase + @gateway.options[:customer_profiles] = true + response = stub_comms do + @gateway.purchase(50, nil, :order_id => 1, :customer_ref_num => @customer_ref_num) + end.check_request do |endpoint, data, headers| + assert_match(/<CustomerRefNum>ABC/, data) + assert_match(/<CurrencyCode>124/, data) + assert_match(/<CurrencyExponent>2/, data) + end.respond_with(successful_purchase_response) + assert_success response + end + + def test_currency_code_and_exponent_are_set_for_profile_authorizations + @gateway.options[:customer_profiles] = true + response = stub_comms do + @gateway.authorize(50, nil, :order_id => 1, :customer_ref_num => @customer_ref_num) + end.check_request do |endpoint, data, headers| + assert_match(/<CustomerRefNum>ABC/, data) + assert_match(/<CurrencyCode>124/, data) + assert_match(/<CurrencyExponent>2/, data) + end.respond_with(successful_purchase_response) + assert_success response + end + # <AVSzip>K1C2N6</AVSzip> - # <AVSaddress1>1234 My Street</AVSaddress1> + # <AVSaddress1>456 My Street</AVSaddress1> # <AVSaddress2>Apt 1</AVSaddress2> # <AVScity>Ottawa</AVScity> # <AVSstate>ON</AVSstate> @@ -272,7 +650,7 @@ def test_send_address_details_for_united_states @gateway.purchase(50, credit_card, :order_id => 1, :billing_address => address) end.check_request do |endpoint, data, headers| assert_match(/<AVSzip>K1C2N6/, data) - assert_match(/<AVSaddress1>1234 My Street/, data) + assert_match(/<AVSaddress1>456 My Street/, data) assert_match(/<AVSaddress2>Apt 1/, data) assert_match(/<AVScity>Ottawa/, data) assert_match(/<AVSstate>ON/, data) @@ -290,7 +668,7 @@ def test_dont_send_address_details_for_germany @gateway.purchase(50, credit_card, :order_id => 1, :billing_address => address(:country => 'DE')) end.check_request do |endpoint, data, headers| assert_no_match(/<AVSzip>K1C2N6/, data) - assert_no_match(/<AVSaddress1>1234 My Street/, data) + assert_no_match(/<AVSaddress1>456 My Street/, data) assert_no_match(/<AVSaddress2>Apt 1/, data) assert_no_match(/<AVScity>Ottawa/, data) assert_no_match(/<AVSstate>ON/, data) @@ -301,14 +679,30 @@ def test_dont_send_address_details_for_germany assert_success response end + def test_allow_sending_avs_parts_when_no_country_specified + response = stub_comms do + @gateway.purchase(50, credit_card, :order_id => 1, :billing_address => address(:country => nil)) + end.check_request do |endpoint, data, headers| + assert_match(/<AVSzip>K1C2N6/, data) + assert_match(/<AVSaddress1>456 My Street/, data) + assert_match(/<AVSaddress2>Apt 1/, data) + assert_match(/<AVScity>Ottawa/, data) + assert_match(/<AVSstate>ON/, data) + assert_match(/<AVSphoneNum>5555555555/, data) + assert_match(/<AVSname>Longbob Longsen/, data) + assert_match(/<AVScountryCode(\/>|><\/AVScountryCode>)/, data) + end.respond_with(successful_purchase_response) + assert_success response + end + def test_american_requests_adhere_to_xml_schema response = stub_comms do @gateway.purchase(50, credit_card, :order_id => 1, :billing_address => address) end.check_request do |endpoint, data, headers| - schema_file = File.read("#{File.dirname(__FILE__)}/../../schema/orbital/Request_PTI54.xsd") + schema_file = File.read("#{File.dirname(__FILE__)}/../../schema/orbital/Request_PTI77.xsd") doc = Nokogiri::XML(data) xsd = Nokogiri::XML::Schema(schema_file) - assert xsd.valid?(doc), "Request does not adhere to DTD" + assert xsd.valid?(doc), 'Request does not adhere to DTD' end.respond_with(successful_purchase_response) assert_success response end @@ -317,17 +711,19 @@ def test_german_requests_adhere_to_xml_schema response = stub_comms do @gateway.purchase(50, credit_card, :order_id => 1, :billing_address => address(:country => 'DE')) end.check_request do |endpoint, data, headers| - schema_file = File.read("#{File.dirname(__FILE__)}/../../schema/orbital/Request_PTI54.xsd") + schema_file = File.read("#{File.dirname(__FILE__)}/../../schema/orbital/Request_PTI77.xsd") doc = Nokogiri::XML(data) xsd = Nokogiri::XML::Schema(schema_file) - assert xsd.valid?(doc), "Request does not adhere to DTD" + assert xsd.valid?(doc), 'Request does not adhere to DTD' end.respond_with(successful_purchase_response) assert_success response end def test_add_customer_profile response = stub_comms do - @gateway.add_customer_profile(credit_card) + assert_deprecation_warning do + @gateway.add_customer_profile(credit_card) + end end.check_request do |endpoint, data, headers| assert_match(/<CustomerProfileAction>C/, data) assert_match(/<CustomerName>Longbob Longsen/, data) @@ -337,7 +733,9 @@ def test_add_customer_profile def test_add_customer_profile_with_email response = stub_comms do - @gateway.add_customer_profile(credit_card, { :billing_address => { :email => 'xiaobozzz@example.com' } }) + assert_deprecation_warning do + @gateway.add_customer_profile(credit_card, { :billing_address => { :email => 'xiaobozzz@example.com' } }) + end end.check_request do |endpoint, data, headers| assert_match(/<CustomerProfileAction>C/, data) assert_match(/<CustomerEmail>xiaobozzz@example.com/, data) @@ -347,7 +745,9 @@ def test_add_customer_profile_with_email def test_update_customer_profile response = stub_comms do - @gateway.update_customer_profile(credit_card) + assert_deprecation_warning do + @gateway.update_customer_profile(credit_card) + end end.check_request do |endpoint, data, headers| assert_match(/<CustomerProfileAction>U/, data) assert_match(/<CustomerName>Longbob Longsen/, data) @@ -357,7 +757,9 @@ def test_update_customer_profile def test_retrieve_customer_profile response = stub_comms do - @gateway.retrieve_customer_profile(@customer_ref_num) + assert_deprecation_warning do + @gateway.retrieve_customer_profile(@customer_ref_num) + end end.check_request do |endpoint, data, headers| assert_no_match(/<CustomerName>Longbob Longsen/, data) assert_match(/<CustomerProfileAction>R/, data) @@ -368,7 +770,9 @@ def test_retrieve_customer_profile def test_delete_customer_profile response = stub_comms do - @gateway.delete_customer_profile(@customer_ref_num) + assert_deprecation_warning do + @gateway.delete_customer_profile(@customer_ref_num) + end end.check_request do |endpoint, data, headers| assert_no_match(/<CustomerName>Longbob Longsen/, data) assert_match(/<CustomerProfileAction>D/, data) @@ -378,7 +782,7 @@ def test_delete_customer_profile end def test_attempts_seconday_url - @gateway.expects(:ssl_post).with(OrbitalGateway.test_url, anything, anything).raises(ActiveMerchant::ConnectionError) + @gateway.expects(:ssl_post).with(OrbitalGateway.test_url, anything, anything).raises(ActiveMerchant::ConnectionError.new('message', nil)) @gateway.expects(:ssl_post).with(OrbitalGateway.secondary_test_url, anything, anything).returns(successful_purchase_response) response = @gateway.purchase(50, credit_card, :order_id => '1') @@ -408,21 +812,164 @@ def test_retry_logic_not_enabled assert_success response end + ActiveMerchant::Billing::OrbitalGateway::APPROVED.each do |resp_code| + define_method "test_approval_response_code_#{resp_code}" do + @gateway.expects(:ssl_post).returns(successful_purchase_response(resp_code)) + + assert response = @gateway.purchase(50, credit_card, :order_id => '1') + assert_instance_of Response, response + assert_success response + end + end + + def test_account_num_is_removed_from_response + @gateway.expects(:ssl_post).returns(successful_purchase_response) + + response = @gateway.purchase(50, credit_card, :order_id => '1') + assert_instance_of Response, response + assert_success response + assert_nil response.params['account_num'] + end + + def test_cc_account_num_is_removed_from_response + @gateway.expects(:ssl_post).returns(successful_profile_response) + + response = nil + + assert_deprecation_warning do + response = @gateway.add_customer_profile(credit_card, + :billing_address => address) + end + + assert_instance_of Response, response + assert_success response + assert_nil response.params['cc_account_num'] + end + + def test_successful_verify + response = stub_comms do + @gateway.verify(credit_card, @options) + end.respond_with(successful_purchase_response, successful_purchase_response) + assert_success response + assert_equal '4A5398CF9B87744GG84A1D30F2F2321C66249416;1', response.authorization + assert_equal 'Approved', response.message + end + + def test_successful_verify_and_failed_void + response = stub_comms do + @gateway.verify(credit_card, @options) + end.respond_with(successful_purchase_response, failed_purchase_response) + assert_success response + assert_equal '4A5398CF9B87744GG84A1D30F2F2321C66249416;1', response.authorization + assert_equal 'Approved', response.message + end + + def test_failed_verify + response = stub_comms do + @gateway.verify(credit_card, @options) + end.respond_with(failed_purchase_response, failed_purchase_response) + assert_failure response + assert_equal 'AUTH DECLINED 12001', response.message + end + + def test_cvv_indicator_present_for_visas_with_cvvs + stub_comms do + @gateway.purchase(50, credit_card, @options) + end.check_request do |_endpoint, data, _headers| + assert_match %r{<CardSecValInd>1<\/CardSecValInd>}, data + assert_match %r{<CardSecVal>123<\/CardSecVal>}, data + end.respond_with(successful_purchase_response) + end + + def test_cvv_indicator_absent_for_recurring + stub_comms do + @gateway.purchase(50, credit_card(nil, {verification_value: nil}), @options) + end.check_request do |_endpoint, data, _headers| + assert_no_match %r{<CardSecValInd>}, data + assert_no_match %r{<CardSecVal>}, data + end.respond_with(successful_purchase_response) + end + + def test_scrub + assert @gateway.supports_scrubbing? + assert_equal @gateway.scrub(pre_scrubbed), post_scrubbed + end + private - def successful_purchase_response - %q{<?xml version="1.0" encoding="UTF-8"?><Response><NewOrderResp><IndustryType></IndustryType><MessageType>AC</MessageType><MerchantID>700000000000</MerchantID><TerminalID>001</TerminalID><CardBrand>VI</CardBrand><AccountNum>4111111111111111</AccountNum><OrderID>1</OrderID><TxRefNum>4A5398CF9B87744GG84A1D30F2F2321C66249416</TxRefNum><TxRefIdx>1</TxRefIdx><ProcStatus>0</ProcStatus><ApprovalStatus>1</ApprovalStatus><RespCode>00</RespCode><AVSRespCode>H </AVSRespCode><CVV2RespCode>N</CVV2RespCode><AuthCode>091922</AuthCode><RecurringAdviceCd></RecurringAdviceCd><CAVVRespCode></CAVVRespCode><StatusMsg>Approved</StatusMsg><RespMsg></RespMsg><HostRespCode>00</HostRespCode><HostAVSRespCode>Y</HostAVSRespCode><HostCVV2RespCode>N</HostCVV2RespCode><CustomerRefNum></CustomerRefNum><CustomerName></CustomerName><ProfileProcStatus></ProfileProcStatus><CustomerProfileMessage></CustomerProfileMessage><RespTime>144951</RespTime></NewOrderResp></Response>} + def successful_purchase_response(resp_code = '00') + %Q{<?xml version="1.0" encoding="UTF-8"?><Response><NewOrderResp><IndustryType></IndustryType><MessageType>AC</MessageType><MerchantID>700000000000</MerchantID><TerminalID>001</TerminalID><CardBrand>VI</CardBrand><AccountNum>4111111111111111</AccountNum><OrderID>1</OrderID><TxRefNum>4A5398CF9B87744GG84A1D30F2F2321C66249416</TxRefNum><TxRefIdx>1</TxRefIdx><ProcStatus>0</ProcStatus><ApprovalStatus>1</ApprovalStatus><RespCode>#{resp_code}</RespCode><AVSRespCode>H </AVSRespCode><CVV2RespCode>N</CVV2RespCode><AuthCode>091922</AuthCode><RecurringAdviceCd></RecurringAdviceCd><CAVVRespCode></CAVVRespCode><StatusMsg>Approved</StatusMsg><RespMsg></RespMsg><HostRespCode>00</HostRespCode><HostAVSRespCode>Y</HostAVSRespCode><HostCVV2RespCode>N</HostCVV2RespCode><CustomerRefNum></CustomerRefNum><CustomerName></CustomerName><ProfileProcStatus></ProfileProcStatus><CustomerProfileMessage></CustomerProfileMessage><RespTime>144951</RespTime></NewOrderResp></Response>} end def failed_purchase_response - %q{<?xml version="1.0" encoding="UTF-8"?><Response><NewOrderResp><IndustryType></IndustryType><MessageType>AC</MessageType><MerchantID>700000000000</MerchantID><TerminalID>001</TerminalID><CardBrand>VI</CardBrand><AccountNum>4000300011112220</AccountNum><OrderID>1</OrderID><TxRefNum>4A5398CF9B87744GG84A1D30F2F2321C66249416</TxRefNum><TxRefIdx>0</TxRefIdx><ProcStatus>0</ProcStatus><ApprovalStatus>0</ApprovalStatus><RespCode>05</RespCode><AVSRespCode>G </AVSRespCode><CVV2RespCode>N</CVV2RespCode><AuthCode></AuthCode><RecurringAdviceCd></RecurringAdviceCd><CAVVRespCode></CAVVRespCode><StatusMsg>Do Not Honor</StatusMsg><RespMsg>AUTH DECLINED 12001</RespMsg><HostRespCode>05</HostRespCode><HostAVSRespCode>N</HostAVSRespCode><HostCVV2RespCode>N</HostCVV2RespCode><CustomerRefNum></CustomerRefNum><CustomerName></CustomerName><ProfileProcStatus></ProfileProcStatus><CustomerProfileMessage></CustomerProfileMessage><RespTime>150214</RespTime></NewOrderResp></Response>} + '<?xml version="1.0" encoding="UTF-8"?><Response><NewOrderResp><IndustryType></IndustryType><MessageType>AC</MessageType><MerchantID>700000000000</MerchantID><TerminalID>001</TerminalID><CardBrand>VI</CardBrand><AccountNum>4000300011112220</AccountNum><OrderID>1</OrderID><TxRefNum>4A5398CF9B87744GG84A1D30F2F2321C66249416</TxRefNum><TxRefIdx>0</TxRefIdx><ProcStatus>0</ProcStatus><ApprovalStatus>0</ApprovalStatus><RespCode>05</RespCode><AVSRespCode>G </AVSRespCode><CVV2RespCode>N</CVV2RespCode><AuthCode></AuthCode><RecurringAdviceCd></RecurringAdviceCd><CAVVRespCode></CAVVRespCode><StatusMsg>Do Not Honor</StatusMsg><RespMsg>AUTH DECLINED 12001</RespMsg><HostRespCode>05</HostRespCode><HostAVSRespCode>N</HostAVSRespCode><HostCVV2RespCode>N</HostCVV2RespCode><CustomerRefNum></CustomerRefNum><CustomerName></CustomerName><ProfileProcStatus></ProfileProcStatus><CustomerProfileMessage></CustomerProfileMessage><RespTime>150214</RespTime></NewOrderResp></Response>' end def successful_profile_response - %q{<?xml version="1.0" encoding="UTF-8"?><Response><ProfileResp><CustomerBin>000001</CustomerBin><CustomerMerchantID>700000000000</CustomerMerchantID><CustomerName>Longbob Longsen</CustomerName><CustomerRefNum>ABC</CustomerRefNum><CustomerProfileAction>CREATE</CustomerProfileAction><ProfileProcStatus>0</ProfileProcStatus><CustomerProfileMessage>Profile Request Processed</CustomerProfileMessage><CustomerAccountType>CC</CustomerAccountType><Status>A</Status><CCAccountNum>4111111111111111</CCAccountNum><RespTime/></ProfileResp></Response>} + '<?xml version="1.0" encoding="UTF-8"?><Response><ProfileResp><CustomerBin>000001</CustomerBin><CustomerMerchantID>700000000000</CustomerMerchantID><CustomerName>Longbob Longsen</CustomerName><CustomerRefNum>ABC</CustomerRefNum><CustomerProfileAction>CREATE</CustomerProfileAction><ProfileProcStatus>0</ProfileProcStatus><CustomerProfileMessage>Profile Request Processed</CustomerProfileMessage><CustomerAccountType>CC</CustomerAccountType><Status>A</Status><CCAccountNum>4111111111111111</CCAccountNum><RespTime/></ProfileResp></Response>' end def successful_void_response - %q{<?xml version="1.0" encoding="UTF-8"?><Response><ReversalResp><MerchantID>700000208761</MerchantID><TerminalID>001</TerminalID><OrderID>2</OrderID><TxRefNum>50FB1C41FEC9D016FF0BEBAD0884B174AD0853B0</TxRefNum><TxRefIdx>1</TxRefIdx><OutstandingAmt>0</OutstandingAmt><ProcStatus>0</ProcStatus><StatusMsg></StatusMsg><RespTime>01192013172049</RespTime></ReversalResp></Response>} + '<?xml version="1.0" encoding="UTF-8"?><Response><ReversalResp><MerchantID>700000208761</MerchantID><TerminalID>001</TerminalID><OrderID>2</OrderID><TxRefNum>50FB1C41FEC9D016FF0BEBAD0884B174AD0853B0</TxRefNum><TxRefIdx>1</TxRefIdx><OutstandingAmt>0</OutstandingAmt><ProcStatus>0</ProcStatus><StatusMsg></StatusMsg><RespTime>01192013172049</RespTime></ReversalResp></Response>' + end + + def pre_scrubbed + <<-EOS +opening connection to orbitalvar1.paymentech.net:443... +opened +starting SSL for orbitalvar1.paymentech.net:443... +SSL established +<- "POST /authorize HTTP/1.1\r\nContent-Type: application/PTI71\r\nMime-Version: 1.1\r\nContent-Transfer-Encoding: text\r\nRequest-Number: 1\r\nDocument-Type: Request\r\nInterface-Version: Ruby|ActiveMerchant|Proprietary Gateway\r\nContent-Length: 964\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: orbitalvar1.paymentech.net\r\n\r\n" +<- "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Request>\n <NewOrder>\n <OrbitalConnectionUsername>T16WAYSACT</OrbitalConnectionUsername>\n <OrbitalConnectionPassword>zbp8X1ykGZ</OrbitalConnectionPassword>\n <IndustryType>EC</IndustryType>\n <MessageType>AC</MessageType>\n <BIN>000001</BIN>\n <MerchantID>041756</MerchantID>\n <TerminalID>001</TerminalID>\n <AccountNum>4112344112344113</AccountNum>\n <Exp>0917</Exp>\n <CurrencyCode>840</CurrencyCode>\n <CurrencyExponent>2</CurrencyExponent>\n <CardSecValInd>1</CardSecValInd>\n <CardSecVal>123</CardSecVal>\n <AVSzip>K1C2N6</AVSzip>\n <AVSaddress1>456 My Street</AVSaddress1>\n <AVSaddress2>Apt 1</AVSaddress2>\n <AVScity>Ottawa</AVScity>\n <AVSstate>ON</AVSstate>\n <AVSphoneNum>5555555555</AVSphoneNum>\n <AVSname>Longbob Longsen</AVSname>\n <AVScountryCode>CA</AVScountryCode>\n <OrderID>b141cf3ce2a442732e1906</OrderID>\n <Amount>100</Amount>\n </NewOrder>\n</Request>\n" +-> "HTTP/1.1 200 OK\r\n" +-> "Date: Thu, 02 Jun 2016 07:04:44 GMT\r\n" +-> "content-type: text/plain; charset=ISO-8859-1\r\n" +-> "content-length: 1200\r\n" +-> "content-transfer-encoding: text/xml\r\n" +-> "document-type: Response\r\n" +-> "mime-version: 1.0\r\n" +-> "Connection: close\r\n" +-> "\r\n" +reading 1200 bytes... +-> "<?xml version=\"1.0\" encoding=\"UTF-8\"?><Response><NewOrderResp><IndustryType></IndustryType><MessageType>AC</MessageType><MerchantID>041756</MerchantID><TerminalID>001</TerminalID><CardBrand>VI</CardBrand><AccountNum>4112344112344113</AccountNum><OrderID>b141cf3ce2a442732e1906</OrderID><TxRefNum>574FDA8CECFBC3DA073FF74A7E6DE4E0BA87545B</TxRefNum><TxRefIdx>2</TxRefIdx><ProcStatus>0</ProcStatus><ApprovalStatus>1</ApprovalStatus><RespCode>00</RespCode><AVSRespCode>7 </AVSRespCode><CVV2RespCode>M</CVV2RespCode><AuthCode>tst595</AuthCode><RecurringAdviceCd></RecurringAdviceCd><CAVVRespCode></CAVVRespCode><StatusMsg>Approved</StatusMsg><RespMsg></RespMsg><HostRespCode>100</HostRespCode><HostAVSRespCode>IU</HostAVSRespCode><HostCVV2RespCode>M</HostCVV2RespCode><CustomerRefNum></CustomerRefNum><CustomerName></CustomerName><ProfileProcStatus></ProfileProcStatus><CustomerProfileMessage></CustomerProfileMessage><RespTime>030444</RespTime><PartialAuthOccurred></PartialAuthOccurred><RequestedAmount></RequestedAmount><RedeemedAmount></RedeemedAmount><RemainingBalance></RemainingBalance><CountryFraudFilterStatus></CountryFraudFilterStatus><IsoCountryCode></IsoCountryCode></NewOrderResp></Response>" +read 1200 bytes +Conn close + EOS + end + + def post_scrubbed + <<-EOS +opening connection to orbitalvar1.paymentech.net:443... +opened +starting SSL for orbitalvar1.paymentech.net:443... +SSL established +<- "POST /authorize HTTP/1.1\r\nContent-Type: application/PTI71\r\nMime-Version: 1.1\r\nContent-Transfer-Encoding: text\r\nRequest-Number: 1\r\nDocument-Type: Request\r\nInterface-Version: Ruby|ActiveMerchant|Proprietary Gateway\r\nContent-Length: 964\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: orbitalvar1.paymentech.net\r\n\r\n" +<- "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Request>\n <NewOrder>\n <OrbitalConnectionUsername>[FILTERED]</OrbitalConnectionUsername>\n <OrbitalConnectionPassword>[FILTERED]</OrbitalConnectionPassword>\n <IndustryType>EC</IndustryType>\n <MessageType>AC</MessageType>\n <BIN>000001</BIN>\n <MerchantID>[FILTERED]</MerchantID>\n <TerminalID>001</TerminalID>\n <AccountNum>[FILTERED]</AccountNum>\n <Exp>0917</Exp>\n <CurrencyCode>840</CurrencyCode>\n <CurrencyExponent>2</CurrencyExponent>\n <CardSecValInd>1</CardSecValInd>\n <CardSecVal>[FILTERED]</CardSecVal>\n <AVSzip>K1C2N6</AVSzip>\n <AVSaddress1>456 My Street</AVSaddress1>\n <AVSaddress2>Apt 1</AVSaddress2>\n <AVScity>Ottawa</AVScity>\n <AVSstate>ON</AVSstate>\n <AVSphoneNum>5555555555</AVSphoneNum>\n <AVSname>Longbob Longsen</AVSname>\n <AVScountryCode>CA</AVScountryCode>\n <OrderID>b141cf3ce2a442732e1906</OrderID>\n <Amount>100</Amount>\n </NewOrder>\n</Request>\n" +-> "HTTP/1.1 200 OK\r\n" +-> "Date: Thu, 02 Jun 2016 07:04:44 GMT\r\n" +-> "content-type: text/plain; charset=ISO-8859-1\r\n" +-> "content-length: 1200\r\n" +-> "content-transfer-encoding: text/xml\r\n" +-> "document-type: Response\r\n" +-> "mime-version: 1.0\r\n" +-> "Connection: close\r\n" +-> "\r\n" +reading 1200 bytes... +-> "<?xml version=\"1.0\" encoding=\"UTF-8\"?><Response><NewOrderResp><IndustryType></IndustryType><MessageType>AC</MessageType><MerchantID>[FILTERED]</MerchantID><TerminalID>001</TerminalID><CardBrand>VI</CardBrand><AccountNum>[FILTERED]</AccountNum><OrderID>b141cf3ce2a442732e1906</OrderID><TxRefNum>574FDA8CECFBC3DA073FF74A7E6DE4E0BA87545B</TxRefNum><TxRefIdx>2</TxRefIdx><ProcStatus>0</ProcStatus><ApprovalStatus>1</ApprovalStatus><RespCode>00</RespCode><AVSRespCode>7 </AVSRespCode><CVV2RespCode>M</CVV2RespCode><AuthCode>tst595</AuthCode><RecurringAdviceCd></RecurringAdviceCd><CAVVRespCode></CAVVRespCode><StatusMsg>Approved</StatusMsg><RespMsg></RespMsg><HostRespCode>100</HostRespCode><HostAVSRespCode>IU</HostAVSRespCode><HostCVV2RespCode>M</HostCVV2RespCode><CustomerRefNum></CustomerRefNum><CustomerName></CustomerName><ProfileProcStatus></ProfileProcStatus><CustomerProfileMessage></CustomerProfileMessage><RespTime>030444</RespTime><PartialAuthOccurred></PartialAuthOccurred><RequestedAmount></RequestedAmount><RedeemedAmount></RedeemedAmount><RemainingBalance></RemainingBalance><CountryFraudFilterStatus></CountryFraudFilterStatus><IsoCountryCode></IsoCountryCode></NewOrderResp></Response>" +read 1200 bytes +Conn close + EOS + end + + def pre_scrubbed_profile + <<-EOS +<?xml version="1.0" encoding="UTF-8"?><Response><ProfileResp><CustomerBin>000001</CustomerBin><CustomerMerchantID>253997</CustomerMerchantID><CustomerName>LONGBOB LONGSEN</CustomerName><CustomerRefNum>109273631</CustomerRefNum><CustomerProfileAction>CREATE</CustomerProfileAction><ProfileProcStatus>0</ProfileProcStatus><CustomerProfileMessage>Profile Request Processed</CustomerProfileMessage><CustomerAddress1>456 MY STREET</CustomerAddress1><CustomerAddress2>APT 1</CustomerAddress2><CustomerCity>OTTAWA</CustomerCity><CustomerState>ON</CustomerState><CustomerZIP>K1C2N6</CustomerZIP><CustomerEmail></CustomerEmail><CustomerPhone>5555555555</CustomerPhone><CustomerCountryCode>CA</CustomerCountryCode><CustomerProfileOrderOverrideInd>NO</CustomerProfileOrderOverrideInd><OrderDefaultDescription></OrderDefaultDescription><OrderDefaultAmount></OrderDefaultAmount><CustomerAccountType>CC</CustomerAccountType><Status>A</Status><CCAccountNum>4112344112344113</CCAccountNum><CCExpireDate>0919</CCExpireDate><ECPAccountDDA></ECPAccountDDA><ECPAccountType></ECPAccountType><ECPAccountRT></ECPAccountRT><ECPBankPmtDlv></ECPBankPmtDlv><SwitchSoloStartDate></SwitchSoloStartDate><SwitchSoloIssueNum></SwitchSoloIssueNum><RespTime></RespTime></ProfileResp></Response> + EOS + end + + def post_scrubbed_profile + <<-EOS +<?xml version="1.0" encoding="UTF-8"?><Response><ProfileResp><CustomerBin>000001</CustomerBin><CustomerMerchantID>[FILTERED]</CustomerMerchantID><CustomerName>LONGBOB LONGSEN</CustomerName><CustomerRefNum>109273631</CustomerRefNum><CustomerProfileAction>CREATE</CustomerProfileAction><ProfileProcStatus>0</ProfileProcStatus><CustomerProfileMessage>Profile Request Processed</CustomerProfileMessage><CustomerAddress1>456 MY STREET</CustomerAddress1><CustomerAddress2>APT 1</CustomerAddress2><CustomerCity>OTTAWA</CustomerCity><CustomerState>ON</CustomerState><CustomerZIP>K1C2N6</CustomerZIP><CustomerEmail></CustomerEmail><CustomerPhone>5555555555</CustomerPhone><CustomerCountryCode>CA</CustomerCountryCode><CustomerProfileOrderOverrideInd>NO</CustomerProfileOrderOverrideInd><OrderDefaultDescription></OrderDefaultDescription><OrderDefaultAmount></OrderDefaultAmount><CustomerAccountType>CC</CustomerAccountType><Status>A</Status><CCAccountNum>[FILTERED]</CCAccountNum><CCExpireDate>0919</CCExpireDate><ECPAccountDDA></ECPAccountDDA><ECPAccountType></ECPAccountType><ECPAccountRT></ECPAccountRT><ECPBankPmtDlv></ECPBankPmtDlv><SwitchSoloStartDate></SwitchSoloStartDate><SwitchSoloIssueNum></SwitchSoloIssueNum><RespTime></RespTime></ProfileResp></Response> + EOS end end diff --git a/test/unit/gateways/pac_net_raven_test.rb b/test/unit/gateways/pac_net_raven_test.rb new file mode 100644 index 00000000000..79530834e28 --- /dev/null +++ b/test/unit/gateways/pac_net_raven_test.rb @@ -0,0 +1,597 @@ +require 'test_helper' + +class PacNetRavenGatewayTest < Test::Unit::TestCase + def setup + @gateway = PacNetRavenGateway.new( + user: 'user', + secret: 'secret', + prn: 123456 + ) + + @credit_card = credit_card + @amount = 100 + + @options = { + billing_address: address + } + end + + def test_successful_authorization + @gateway.expects(:ssl_post).returns(successful_authorization_response) + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_instance_of Response, response + assert_success response + assert_equal 'This transaction has been approved', response.message + assert_equal '123456789', response.authorization + assert response.test? + end + + def test_invalid_credit_card_authorization + @gateway.expects(:ssl_post).returns(invalid_credit_card_response) + assert response = @gateway.authorize(@amount, @credit_card, @options) + assert_failure response + assert_equal 'Error processing transaction because CardNumber is not between 12 and 19 in length', response.message + end + + def test_expired_credit_card_authorization + @gateway.expects(:ssl_post).returns(expired_credit_card_response) + assert response = @gateway.authorize(@amount, @credit_card, @options) + assert_failure response + assert_equal 'Invalid because the card expiry date is not a date in the future', response.message + end + + def test_declined_authorization + @gateway.expects(:ssl_post).returns(declined_purchese_response) + assert response = @gateway.authorize(@amount, @credit_card, @options) + assert_failure response + assert_equal 'This transaction has been declined', response.message + end + + def test_successful_purchase + @gateway.expects(:ssl_post).returns(successful_purchase_response) + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_instance_of Response, response + assert_success response + assert_equal 'This transaction has been approved', response.message + assert_equal '123456789', response.authorization + assert response.test? + end + + def test_successful_purchase_with_avs_cvv + @gateway.expects(:ssl_post).returns(successful_purchase_response_with_avs_cvv) + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_instance_of Response, response + assert_success response + assert_equal 'This transaction has been approved', response.message + assert_equal '123456789', response.authorization + assert response.test? + assert_equal 'Y', response.avs_result['street_match'] + assert_equal 'Y', response.avs_result['postal_match'] + assert_equal 'Y', response.cvv_result['code'] + end + + def test_successful_purchase_with_failed_avs_cvv + @gateway.expects(:ssl_post).returns(successful_purchase_response_with_failed_avs_cvv) + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_instance_of Response, response + assert_success response + assert_equal 'This transaction has been approved', response.message + assert_equal '123456789', response.authorization + assert response.test? + assert_equal 'N', response.avs_result['street_match'] + assert_equal 'N', response.avs_result['postal_match'] + assert_equal 'N', response.cvv_result['code'] + end + + def test_invalid_credit_card_number_purchese + @gateway.expects(:ssl_post).returns(invalid_credit_card_response) + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_equal 'Error processing transaction because CardNumber is not between 12 and 19 in length', response.message + end + + def test_expired_credit_card_purchese + @gateway.expects(:ssl_post).returns(expired_credit_card_response) + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_equal 'Invalid because the card expiry date is not a date in the future', response.message + end + + def test_declined_purchese + @gateway.expects(:ssl_post).returns(declined_purchese_response) + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_equal 'This transaction has been declined', response.message + end + + def test_successful_capture + @gateway.expects(:ssl_post).returns(successful_capture_response) + assert response = @gateway.capture(@amount, '123456789') + assert_instance_of Response, response + assert_success response + assert_equal 'This transaction has been approved', response.message + assert_equal '123456789', response.authorization + assert response.test? + end + + def test_invalid_preauth_number_capture + @gateway.expects(:ssl_post).returns(invalid_preauth_number_response) + assert response = @gateway.capture(@amount, '') + assert_failure response + assert_equal 'Error processing transaction because the pre-auth number', response.message + end + + def test_insufficient_preauth_amount_capture + @gateway.expects(:ssl_post).returns(insufficient_preauth_amount_response) + assert response = @gateway.capture(200, '123456789') + assert_failure response + assert_equal 'Invalid because the preauthorization amount 100 is insufficient', response.message + end + + def test_successful_refund + @gateway.expects(:ssl_post).returns(successful_refund_response) + assert response = @gateway.refund(@amount, '123456789') + assert_success response + assert_equal 'This transaction has been approved', response.message + end + + def test_amount_greater_than_original_amount_refund + @gateway.expects(:ssl_post).returns(amount_greater_than_original_amount_refund_response) + assert response = @gateway.refund(200, '123456789') + assert_failure response + assert_equal 'Invalid because the payment amount cannot be greater than the original charge', response.message + end + + def test_successful_void + @gateway.expects(:ssl_post).returns(successful_void_response) + assert response = @gateway.void('123456789') + assert_success response + assert_equal 'This transaction has been voided', response.message + end + + def test_failed_void + @gateway.expects(:ssl_post).returns(failed_void_response) + assert response = @gateway.void('123456789') + assert_failure response + assert_equal 'Error processing transaction because the payment may not be voided', response.message + end + + def test_argument_error_prn + exception = assert_raises(ArgumentError) { + PacNetRavenGateway.new(:user => 'user', :secret => 'secret') + } + assert_equal 'Missing required parameter: prn', exception.message + end + + def test_argument_error_user + exception = assert_raises(ArgumentError) { + PacNetRavenGateway.new(:secret => 'secret', :prn => 123456) + } + assert_equal 'Missing required parameter: user', exception.message + end + + def test_argument_error_secret + exception = assert_raises(ArgumentError) { + PacNetRavenGateway.new(:user => 'user', :prn => 123456) + } + assert_equal 'Missing required parameter: secret', exception.message + end + + def test_add_address + result = {} + @gateway.send(:add_address, result, :billing_address => {:address1 => 'Address 1', :address2 => 'Address 2', :zip => 'ZIP'}) + assert_equal ['BillingPostalCode', 'BillingStreetAddressLineFour', 'BillingStreetAddressLineOne'], result.stringify_keys.keys.sort + assert_equal 'ZIP', result['BillingPostalCode'] + assert_equal 'Address 2', result['BillingStreetAddressLineFour'] + assert_equal 'Address 1', result['BillingStreetAddressLineOne'] + end + + def test_add_creditcard + result = {} + @gateway.send(:add_creditcard, result, @credit_card) + assert_equal ['CVV2', 'CardNumber', 'Expiry'], result.stringify_keys.keys.sort + assert_equal @credit_card.number, result['CardNumber'] + assert_equal @gateway.send(:expdate, @credit_card), result['Expiry'] + assert_equal @credit_card.verification_value, result['CVV2'] + end + + def test_add_currency_code_default + result = {} + @gateway.send(:add_currency_code, result, 100, {}) + assert_equal 'USD', result['Currency'] + end + + def test_add_currency_code_from_options + result = {} + @gateway.send(:add_currency_code, result, 100, {currency: 'CAN'}) + assert_equal 'CAN', result['Currency'] + end + + def test_parse + result = @gateway.send(:parse, 'key1=value1&key2=value2') + h = {'key1' => 'value1', 'key2' => 'value2'} + assert_equal h, result + end + + def test_endpoint_for_void + assert_equal 'void', @gateway.send(:endpoint, 'void') + end + + def test_endpoint_for_cc_debit + assert_equal 'submit', @gateway.send(:endpoint, 'cc_debit') + end + + def test_endpoint_for_cc_preauth + assert_equal 'submit', @gateway.send(:endpoint, 'cc_preauth') + end + + def test_endpoint_for_cc_settle + assert_equal 'submit', @gateway.send(:endpoint, 'cc_settle') + end + + def test_endpoint_for_cc_refund + assert_equal 'submit', @gateway.send(:endpoint, 'cc_refund') + end + + def test_success + assert @gateway.send(:success?, { + :action => 'cc_settle', + 'ApprovalCode' => '123456', + 'ErrorCode' => nil, + 'Status' => 'Approved' + }) + + refute @gateway.send(:success?, { + :action => 'cc_settle', + 'ApprovalCode' => nil, + 'ErrorCode' => 'SomeError', + 'Status' => 'SomeError' + }) + + assert @gateway.send(:success?, { + :action => 'cc_debit', + 'ApprovalCode' => '123456', + 'ErrorCode' => nil, + 'Status' => 'Approved' + }) + + refute @gateway.send(:success?, { + :action => 'cc_debit', + 'ApprovalCode' => nil, + 'ErrorCode' => 'SomeError', + 'Status' => 'SomeError' + }) + + assert @gateway.send(:success?, { + :action => 'cc_preauth', + 'ApprovalCode' => '123456', + 'ErrorCode' => nil, + 'Status' => 'Approved' + }) + + refute @gateway.send(:success?, { + :action => 'cc_preauth', + 'ApprovalCode' => nil, + 'ErrorCode' => 'SomeError', + 'Status' => 'SomeError' + }) + + assert @gateway.send(:success?, { + :action => 'cc_refund', + 'ApprovalCode' => '123456', + 'ErrorCode' => nil, + 'Status' => 'Approved' + }) + + refute @gateway.send(:success?, { + :action => 'cc_refund', + 'ApprovalCode' => nil, + 'ErrorCode' => 'SomeError', + 'Status' => 'SomeError' + }) + + assert @gateway.send(:success?, { + :action => 'void', + 'ApprovalCode' => '123456', + 'ErrorCode' => nil, + 'Status' => 'Voided' + }) + + refute @gateway.send(:success?, { + :action => 'void', + 'ApprovalCode' => nil, + 'ErrorCode' => 'SomeError', + 'Status' => 'SomeError' + }) + end + + def test_message_from_approved + assert_equal 'This transaction has been approved', @gateway.send(:message_from, { + 'Status' => 'Approved', + 'Message'=> nil + }) + end + + def test_message_from_declined + assert_equal 'This transaction has been declined', @gateway.send(:message_from, { + 'Status' => 'Declined', + 'Message'=> nil + }) + end + + def test_message_from_voided + assert_equal 'This transaction has been voided', @gateway.send(:message_from, { + 'Status' => 'Voided', + 'Message'=> nil + }) + end + + def test_message_from_status + assert_equal 'This is the message', @gateway.send(:message_from, { + 'Status' => 'SomeStatus', + 'Message'=> 'This is the message' + }) + end + + def test_post_data + @gateway.stubs(:request_id => 'wouykiikdvqbwwxueppby') + @gateway.stubs(:timestamp => '2013-10-08T14:31:54.Z') + + assert_equal "PymtType=cc_preauth&RAPIVersion=2&UserName=user&Timestamp=2013-10-08T14%3A31%3A54.Z&RequestID=wouykiikdvqbwwxueppby&Signature=7794efc8c0d39f0983edc10f778e6143ba13531d&CardNumber=4242424242424242&Expiry=09#{@credit_card.year.to_s[-2..-1]}&CVV2=123&Currency=USD&BillingStreetAddressLineOne=Address+1&BillingStreetAddressLineFour=Address+2&BillingPostalCode=ZIP123", + @gateway.send(:post_data, 'cc_preauth', { + 'CardNumber' => @credit_card.number, + 'Expiry' => @gateway.send(:expdate, @credit_card), + 'CVV2' => @credit_card.verification_value, + 'Currency' => 'USD', + 'BillingStreetAddressLineOne' => 'Address 1', + 'BillingStreetAddressLineFour' => 'Address 2', + 'BillingPostalCode' => 'ZIP123' + }) + end + + def test_signature_for_cc_preauth_action + assert_equal 'd5ff154d6631333c21d0c78975b3bf5d9ccd0ef8', @gateway.send(:signature, 'cc_preauth', { + 'UserName' => 'user', + 'Timestamp' => '2013-10-08T14:31:54.Z', + 'RequestID' => 'wouykiikdvqbwwxueppby', + 'PymtType' => 'cc_preauth' + }, { + 'Amount' => 100, + 'Currency' => 'USD', + 'TrackingNumber' => '123456789' + }) + end + + def test_signature_for_cc_settle_action + assert_equal 'c80cccf6c77438785726b5a447d5aed84738c6d1', @gateway.send(:signature, 'cc_settle', { + 'UserName' => 'user', + 'Timestamp' => '2013-10-08T14:31:54.Z', + 'RequestID' => 'wouykiikdvqbwwxueppby', + 'PymtType' => 'cc_settle' + }, { + 'Amount' => 100, + 'Currency' => 'USD', + 'TrackingNumber' => '123456789' + }) + end + + def test_signature_for_cc_debit_action + assert_equal 'b2a0eb307cfd092152d44b06a49a360feccdb1b9', @gateway.send(:signature, 'cc_debit', { + 'UserName' => 'user', + 'Timestamp' => '2013-10-08T14:31:54.Z', + 'RequestID' => 'wouykiikdvqbwwxueppby', + 'PymtType' => 'cc_debit' + }, { + 'Amount' => 100, + 'Currency' => 'USD', + 'TrackingNumber' => '123456789' + }) + end + + def test_signature_for_cc_refund_action + assert_equal '9b174f1ebf5763e4793a52027645ff5156fca2e3', @gateway.send(:signature, 'cc_refund', { + 'UserName' => 'user', + 'Timestamp' => '2013-10-08T14:31:54.Z', + 'RequestID' => 'wouykiikdvqbwwxueppby', + 'PymtType' => 'cc_refund' + }, { + 'Amount' => 100, + 'Currency' => 'USD', + 'TrackingNumber' => '123456789' + }) + end + + def test_signature_for_void_action + assert_equal '236d4a857ee2e8cfec851be250159367d2c7c52e', @gateway.send(:signature, 'void', { + 'UserName' => 'user', + 'Timestamp' => '2013-10-08T14:31:54.Z', + 'RequestID' => 'wouykiikdvqbwwxueppby' + }, { + 'Amount' => 100, + 'Currency' => 'USD', + 'TrackingNumber' => '123456789' + }) + end + + def test_expdate + @credit_card.year = 2015 + @credit_card.month = 9 + assert_equal '0915', @gateway.send(:expdate, @credit_card) + end + + private + + def failed_void_response + %w( + ApprovalCode= + ErrorCode=error:canNotBeVoided + Message=Error+processing+transaction+because+the+payment+may+not+be+voided + RequestNumber=603758541 + RequestResult=ok + Status=Approved + TrackingNumber=123456789 + ).join('&') + end + + def successful_authorization_response + %w( + ApprovalCode=123456 + ErrorCode= + Message= + RequestNumber=603758541 + RequestResult=ok + Status=Approved + TrackingNumber=123456789 + ).join('&') + end + + def successful_purchase_response + %w( + ApprovalCode=123456 + ErrorCode= + Message= + RequestNumber=603758541 + RequestResult=ok + Status=Approved + TrackingNumber=123456789 + ).join('&') + end + + def successful_purchase_response_with_avs_cvv + %w( + ApprovalCode=123456 + ErrorCode= + Message= + RequestNumber=603758541 + RequestResult=ok + Status=Approved + TrackingNumber=123456789 + AVSAddressResponseCode=avs_address_matched + AVSPostalResponseCode=avs_postal_matched + CVV2ResponseCode=cvv2_matched + ).join('&') + end + + def successful_purchase_response_with_failed_avs_cvv + %w( + ApprovalCode=123456 + ErrorCode= + Message= + RequestNumber=603758541 + RequestResult=ok + Status=Approved + TrackingNumber=123456789 + AVSAddressResponseCode=avs_address_not_matched + AVSPostalResponseCode=avs_postal_not_matched + CVV2ResponseCode=cvv2_not_matched + ).join('&') + end + + def successful_capture_response + %w( + ApprovalCode=123456 + ErrorCode= + Message= + RequestNumber=603758541 + RequestResult=ok + Status=Approved + TrackingNumber=123456789 + ).join('&') + end + + def invalid_preauth_number_response + %w( + ApprovalCode= + ErrorCode=invalid:PreAuthNumber + Message=Error+processing+transaction+because+the+pre-auth+number + RequestNumber=603758541 + RequestResult=ok + Status=Invalid:PreauthNumber + TrackingNumber=123456789 + ).join('&') + end + + def insufficient_preauth_amount_response + %w( + ApprovalCode= + ErrorCode=rejected:PreauthAmountInsufficient + Message=Invalid+because+the+preauthorization+amount+100+is+insufficient + RequestNumber=603758541 + RequestResult=ok + Status=Rejected:PreauthAmountInsufficient + TrackingNumber=123456789 + ).join('&') + end + + def invalid_credit_card_response + %w( + ApprovalCode= + ErrorCode=invalid:cardNumber + Message=Error+processing+transaction+because+CardNumber+is+not+between+12+and+19+in+length + RequestNumber=603758541 + RequestResult=ok + Status=Invalid:CardNumber + TrackingNumber=123456789 + ).join('&') + end + + def expired_credit_card_response + %w( + ApprovalCode= + ErrorCode=invalid:CustomerCardExpiryDate + Message=Invalid+because+the+card+expiry+date+is+not+a+date+in+the+future + RequestNumber=603758541 + RequestResult=ok + Status=Invalid:CustomerCardExpiryDate + TrackingNumber=123456789 + ).join('&') + end + + def declined_purchese_response + %w( + ApprovalCode=123456 + ErrorCode= + Message= + RequestNumber=603758541 + RequestResult=ok + Status=Declined + TrackingNumber=123456789 + ).join('&') + end + + def successful_refund_response + %w( + ApprovalCode=123456 + ErrorCode= + Message= + RequestNumber=603758541 + RequestResult=ok + Status=Approved + TrackingNumber=123456789 + ).join('&') + end + + def amount_greater_than_original_amount_refund_response + %w( + ApprovalCode= + ErrorCode=invalid:RefundAmountGreaterThanOriginalAmount + Message=Invalid+because+the+payment+amount+cannot+be+greater+than+the+original+charge + RequestNumber=603758541 + RequestResult=ok + Status=Invalid:RefundAmountGreaterThanOriginalAmount + TrackingNumber=123456789 + ).join('&') + end + + def successful_void_response + %w( + ApprovalCode=123456 + ErrorCode= + Message= + RequestNumber=603758541 + RequestResult=ok + Status=Voided + TrackingNumber=123456789 + ).join('&') + end +end diff --git a/test/unit/gateways/pagarme_test.rb b/test/unit/gateways/pagarme_test.rb new file mode 100644 index 00000000000..3944a780cd2 --- /dev/null +++ b/test/unit/gateways/pagarme_test.rb @@ -0,0 +1,978 @@ +require 'test_helper' + +class PagarmeTest < Test::Unit::TestCase + def setup + @gateway = PagarmeGateway.new(api_key: 'ak_test_e1QGU2gL98MDCHZxHLJ9sofPUFJ7tH') + + @credit_card = credit_card('4242424242424242', { + first_name: 'Richard', + last_name: 'Deschamps' + }) + + @amount = 1000 + + @options = { + order_id: '1', + ip: '127.0.0.1', + customer: 'Richard Deschamps', + invoice: '1', + merchant: 'Richard\'s', + description: 'Store Purchase', + email: 'suporte@pagar.me', + billing_address: address() + } + end + + def test_successful_purchase + @gateway.expects(:ssl_request).returns(successful_purchase_response) + + response = @gateway.purchase(@amount, @credit_card, @options) + assert_instance_of Response, response + assert_success response + + assert_equal 427312, response.authorization + assert_equal @amount, response.params['amount'] + + assert_equal @credit_card.name, response.params['card']['holder_name'] + assert_equal @credit_card.last_digits, response.params['card']['last_digits'] + assert_equal @credit_card.brand, response.params['card']['brand'] + + assert_equal 'credit_card', response.params['payment_method'] + assert_equal 'paid', response.params['status'] + assert_equal 'Transação aprovada', response.message + assert response.test? + end + + def test_failed_purchase + @gateway.expects(:ssl_request).returns(failed_purchase_response) + + response = @gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_equal Gateway::STANDARD_ERROR_CODE[:card_declined], response.error_code + + assert_equal 'refused', response.params['status'] + assert_equal 'Transação recusada', response.message + end + + def test_successful_authorize + @gateway.expects(:ssl_request).returns(successful_authorize_response) + + response = @gateway.authorize(@amount, @credit_card, @options) + assert_instance_of Response, response + assert_success response + + assert_equal 429356, response.authorization + assert_equal @amount, response.params['amount'] + + assert_equal @credit_card.name, response.params['card']['holder_name'] + assert_equal @credit_card.last_digits, response.params['card']['last_digits'] + assert_equal @credit_card.brand, response.params['card']['brand'] + + assert_equal 'credit_card', response.params['payment_method'] + assert_equal 'authorized', response.params['status'] + assert_equal 'Transação autorizada', response.message + assert response.test? + end + + def test_failed_authorize + @gateway.expects(:ssl_request).returns(failed_authorize_response) + + response = @gateway.authorize(@amount, @credit_card, @options) + assert_failure response + assert_equal Gateway::STANDARD_ERROR_CODE[:card_declined], response.error_code + + assert_equal 'refused', response.params['status'] + assert_equal 'Transação recusada', response.message + end + + def test_successful_capture + @gateway.expects(:ssl_request).returns(successful_capture_response) + + response = @gateway.capture(@amount, 429356, @options) + assert_instance_of Response, response + assert_success response + + assert_equal 429356, response.authorization + assert_equal @amount, response.params['amount'] + + assert_equal @credit_card.name, response.params['card']['holder_name'] + assert_equal @credit_card.last_digits, response.params['card']['last_digits'] + assert_equal @credit_card.brand, response.params['card']['brand'] + + assert_equal 'credit_card', response.params['payment_method'] + assert_equal 'paid', response.params['status'] + assert_equal 'Transação aprovada', response.message + assert response.test? + end + + def test_failed_capture + @gateway.expects(:ssl_request).returns(failed_capture_response) + + response = @gateway.capture(@amount, 429356, @options) + assert_failure response + assert_equal Gateway::STANDARD_ERROR_CODE[:processing_error], response.error_code + + assert_equal 'Transação com status \'captured\' não pode ser capturada.', response.message + end + + def test_successful_refund + @gateway.expects(:ssl_request).returns(successful_refund_response) + + response = @gateway.refund(@amount, 429356, @options) + assert_instance_of Response, response + assert_success response + + assert_equal 429356, response.authorization + + assert_equal 'credit_card', response.params['payment_method'] + assert_equal 'refunded', response.params['status'] + assert_equal 'Transação estornada', response.message + assert response.test? + end + + def test_failed_refund + @gateway.expects(:ssl_request).returns(failed_refund_response) + + response = @gateway.refund(@amount, 429356, @options) + assert_failure response + assert_equal Gateway::STANDARD_ERROR_CODE[:processing_error], response.error_code + + assert_equal 'Transação já estornada', response.message + end + + def test_successful_void + @gateway.expects(:ssl_request).returns(successful_void_response) + + response = @gateway.void(472218, @options) + assert_instance_of Response, response + assert_success response + + assert_equal 472218, response.authorization + + assert_equal 'credit_card', response.params['payment_method'] + assert_equal 'refunded', response.params['status'] + assert_equal 'Transação estornada', response.message + assert response.test? + end + + def test_failed_void + @gateway.expects(:ssl_request).returns(failed_void_response) + + response = @gateway.void(472218, @options) + assert_failure response + assert_equal Gateway::STANDARD_ERROR_CODE[:processing_error], response.error_code + + assert_equal 'Transação já estornada', response.message + end + + def test_successful_verify + s = sequence('request') + @gateway.expects(:ssl_request).returns(successful_verify_response).in_sequence(s) + @gateway.expects(:ssl_request).returns(successful_verify_void_response).in_sequence(s) + + response = @gateway.verify(@credit_card, @options) + assert_instance_of MultiResponse, response + assert_success response + + assert_equal 127, response.params['amount'] + + assert_equal @credit_card.name, response.params['card']['holder_name'] + assert_equal @credit_card.last_digits, response.params['card']['last_digits'] + assert_equal @credit_card.brand, response.params['card']['brand'] + + assert_equal 'credit_card', response.params['payment_method'] + assert_equal 'authorized', response.params['status'] + assert_equal 'Transação autorizada', response.message + + assert_success response.responses[1] + assert_equal 'refunded', response.responses[1].params['status'] + assert_equal 'Transação estornada', response.responses[1].message + + assert response.test? + end + + def test_successful_verify_with_failed_void + s = sequence('request') + @gateway.expects(:ssl_request).returns(successful_verify_response).in_sequence(s) + @gateway.expects(:ssl_request).returns(failed_void_response).in_sequence(s) + + response = @gateway.verify(@credit_card, @options) + assert_instance_of MultiResponse, response + assert_success response + + assert_equal 127, response.params['amount'] + + assert_equal @credit_card.name, response.params['card']['holder_name'] + assert_equal @credit_card.last_digits, response.params['card']['last_digits'] + assert_equal @credit_card.brand, response.params['card']['brand'] + + assert_equal 'credit_card', response.params['payment_method'] + assert_equal 'authorized', response.params['status'] + assert_equal 'Transação autorizada', response.message + + assert_failure response.responses[1] + assert_equal Gateway::STANDARD_ERROR_CODE[:processing_error], response.responses[1].error_code + assert_equal 'Transação já estornada', response.responses[1].message + end + + def test_failed_verify + @gateway.expects(:ssl_request).returns(failed_verify_response) + + response = @gateway.verify(@credit_card, @options) + assert_failure response + assert_equal Gateway::STANDARD_ERROR_CODE[:card_declined], response.error_code + + assert_equal 127, response.params['amount'] + assert_equal 'refused', response.params['status'] + assert_equal 'Transação recusada', response.message + end + + def test_generic_error + @gateway.expects(:ssl_request).returns(failed_error_response) + + response = @gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_equal Gateway::STANDARD_ERROR_CODE[:processing_error], response.error_code + assert_equal 'Internal server error.', response.message + end + + def test_failed_json_body + @gateway.expects(:ssl_request).returns(failed_json_response) + + response = @gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_equal Gateway::STANDARD_ERROR_CODE[:processing_error], response.error_code + + assert response.message.start_with?('Resposta inválida') + end + + def test_scrub + assert @gateway.supports_scrubbing? + assert_equal @gateway.scrub(pre_scrubbed), post_scrubbed + end + + private + + def pre_scrubbed + <<-PRE_SCRUBBED + opening connection to api.pagar.me:443... + opened + starting SSL for api.pagar.me:443... + SSL established + <- "POST /1/transactions HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAuthorization: Basic YWtfdGVzdF9sTmxMNHF3Z0RVQ1VoQk1PWElqRnBSSmdXSkpOZjM6eA==\r\nUser-Agent: Pagar.me/1 ActiveMerchant/1.58.0\r\nAccept-Encoding: deflate\r\nAccept: */*\r\nConnection: close\r\nHost: api.pagar.me\r\nContent-Length: 196\r\n\r\n" + <- "amount=1000&payment_method=credit_card&card_number=4242424242424242&card_holder_name=Richard+Deschamps&card_expiration_date=9%2F2017&card_cvv=123&metadata[description]=ActiveMerchant+Test+Purchase" + -> "HTTP/1.1 200 OK\r\n" + -> "Access-Control-Allow-Credentials: true\r\n" + -> "Access-Control-Allow-Headers: Content-Type, Authorization, X-Requested-With, X-HTTP-Method-Override\r\n" + -> "Access-Control-Allow-Methods: GET,PUT,POST,DELETE\r\n" + -> "Access-Control-Allow-Origin: *\r\n" + -> "Charset: utf-8\r\n" + -> "Content-Type: application/json\r\n" + -> "Date: Wed, 23 Mar 2016 08:17:52 GMT\r\n" + -> "ETag: \"1486888623\"\r\n" + -> "Server: nginx\r\n" + -> "X-Powered-By: Express\r\n" + -> "X-Response-Time: 260ms\r\n" + -> "Content-Length: 1217\r\n" + -> "Connection: Close\r\n" + -> "Set-Cookie: visid_incap_166741=4xzkgPXeQ66jO1Z91iibOjxR8lYAAAAAQUIPAAAAAADBzoQck8nwH8iJzqUkDgR6; expires=Wed, 22 Mar 2017 14:07:58 GMT; path=/; Domain=.pagar.me\r\n" + -> "Set-Cookie: nlbi_166741=XR6HTRng0zFBQUS2W7H6TQAAAADCwTVJNcjRvaX6/996Dj8I; path=/; Domain=.pagar.me\r\n" + -> "Set-Cookie: incap_ses_297_166741=4loNMgeLdj0PSeV/BCgfBDxR8lYAAAAAgi6H8uNqma8hraBXzDFdzQ==; path=/; Domain=.pagar.me\r\n" + -> "X-Iinfo: 6-56899509-56899511 NNNN CT(143 143 0) RT(1458721083333 27) q(0 0 3 -1) r(7 7) U6\r\n" + -> "X-CDN: Incapsula\r\n" + -> "\r\n" + reading 1217 bytes... + -> "{\"object\":\"transaction\",\"status\":\"paid\",\"refuse_reason\":null,\"status_reason\":\"acquirer\",\"acquirer_response_code\":\"00\",\"acquirer_name\":\"development\",\"authorization_code\":\"606507\",\"soft_descriptor\":null,\"tid\":1458721084304,\"nsu\":1458721084304,\"date_created\":\"2016-03-23T08:18:04.162Z\",\"date_updated\":\"2016-03-23T08:18:04.388Z\",\"amount\":1000,\"authorized_amount\":1000,\"paid_amount\":1000,\"refunded_amount\":0,\"installments\":1,\"id\":428211,\"cost\":65,\"card_holder_name\":\"Richard Deschamps\",\"card_last_digits\":\"4242\",\"card_first_digits\":\"424242\",\"card_brand\":\"visa\",\"postback_url\":null,\"payment_method\":\"credit_card\",\"capture_method\":\"ecommerce\",\"antifraud_score\":null,\"boleto_url\":null,\"boleto_barcode\":null,\"boleto_expiration_date\":null,\"referer\":\"api_key\",\"ip\":\"179.191.82.50\",\"subscription_id\":null,\"phone\":null,\"address\":null,\"customer\":null,\"card\":{\"object\":\"card\",\"id\":\"card_cim4ccq3p00q1ju6e4aw4tdon\",\"date_created\":\"2016-03-23T04:19:38.917Z\",\"date_updated\":\"2016-03-23T04:19:39.160Z\",\"brand\":\"visa\",\"holder_name\":\"Richard Deschamps\",\"first_digits\":\"424242\",\"last_digits\":\"4242\",\"country\":\"US\",\"fingerprint\":\"VpmCgO7Ub/rS\",\"valid\":true},\"metadata\":{\"description\":\"ActiveMerchant Test Purchase\"},\"antifraud_metadata\":{}}" + read 1217 bytes + Conn close + PRE_SCRUBBED + end + + def post_scrubbed + <<-POST_SCRUBBED + opening connection to api.pagar.me:443... + opened + starting SSL for api.pagar.me:443... + SSL established + <- "POST /1/transactions HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAuthorization: Basic [FILTERED]==\r\nUser-Agent: Pagar.me/1 ActiveMerchant/1.58.0\r\nAccept-Encoding: deflate\r\nAccept: */*\r\nConnection: close\r\nHost: api.pagar.me\r\nContent-Length: 196\r\n\r\n" + <- "amount=1000&payment_method=credit_card&card_number=[FILTERED]&card_holder_name=Richard+Deschamps&card_expiration_date=9%2F2017&card_cvv=[FILTERED]&metadata[description]=ActiveMerchant+Test+Purchase" + -> "HTTP/1.1 200 OK\r\n" + -> "Access-Control-Allow-Credentials: true\r\n" + -> "Access-Control-Allow-Headers: Content-Type, Authorization, X-Requested-With, X-HTTP-Method-Override\r\n" + -> "Access-Control-Allow-Methods: GET,PUT,POST,DELETE\r\n" + -> "Access-Control-Allow-Origin: *\r\n" + -> "Charset: utf-8\r\n" + -> "Content-Type: application/json\r\n" + -> "Date: Wed, 23 Mar 2016 08:17:52 GMT\r\n" + -> "ETag: \"1486888623\"\r\n" + -> "Server: nginx\r\n" + -> "X-Powered-By: Express\r\n" + -> "X-Response-Time: 260ms\r\n" + -> "Content-Length: 1217\r\n" + -> "Connection: Close\r\n" + -> "Set-Cookie: visid_incap_166741=4xzkgPXeQ66jO1Z91iibOjxR8lYAAAAAQUIPAAAAAADBzoQck8nwH8iJzqUkDgR6; expires=Wed, 22 Mar 2017 14:07:58 GMT; path=/; Domain=.pagar.me\r\n" + -> "Set-Cookie: nlbi_166741=XR6HTRng0zFBQUS2W7H6TQAAAADCwTVJNcjRvaX6/996Dj8I; path=/; Domain=.pagar.me\r\n" + -> "Set-Cookie: incap_ses_297_166741=4loNMgeLdj0PSeV/BCgfBDxR8lYAAAAAgi6H8uNqma8hraBXzDFdzQ==; path=/; Domain=.pagar.me\r\n" + -> "X-Iinfo: 6-56899509-56899511 NNNN CT(143 143 0) RT(1458721083333 27) q(0 0 3 -1) r(7 7) U6\r\n" + -> "X-CDN: Incapsula\r\n" + -> "\r\n" + reading 1217 bytes... + -> "{\"object\":\"transaction\",\"status\":\"paid\",\"refuse_reason\":null,\"status_reason\":\"acquirer\",\"acquirer_response_code\":\"00\",\"acquirer_name\":\"development\",\"authorization_code\":\"606507\",\"soft_descriptor\":null,\"tid\":1458721084304,\"nsu\":1458721084304,\"date_created\":\"2016-03-23T08:18:04.162Z\",\"date_updated\":\"2016-03-23T08:18:04.388Z\",\"amount\":1000,\"authorized_amount\":1000,\"paid_amount\":1000,\"refunded_amount\":0,\"installments\":1,\"id\":428211,\"cost\":65,\"card_holder_name\":\"Richard Deschamps\",\"card_last_digits\":\"4242\",\"card_first_digits\":\"424242\",\"card_brand\":\"visa\",\"postback_url\":null,\"payment_method\":\"credit_card\",\"capture_method\":\"ecommerce\",\"antifraud_score\":null,\"boleto_url\":null,\"boleto_barcode\":null,\"boleto_expiration_date\":null,\"referer\":\"api_key\",\"ip\":\"179.191.82.50\",\"subscription_id\":null,\"phone\":null,\"address\":null,\"customer\":null,\"card\":{\"object\":\"card\",\"id\":\"card_cim4ccq3p00q1ju6e4aw4tdon\",\"date_created\":\"2016-03-23T04:19:38.917Z\",\"date_updated\":\"2016-03-23T04:19:39.160Z\",\"brand\":\"visa\",\"holder_name\":\"Richard Deschamps\",\"first_digits\":\"424242\",\"last_digits\":\"4242\",\"country\":\"US\",\"fingerprint\":\"VpmCgO7Ub/rS\",\"valid\":true},\"metadata\":{\"description\":\"ActiveMerchant Test Purchase\"},\"antifraud_metadata\":{}}" + read 1217 bytes + Conn close + POST_SCRUBBED + end + + def successful_purchase_response + <<-SUCCESS_RESPONSE + { + "acquirer_name": "development", + "acquirer_response_code": "00", + "address": null, + "amount": 1000, + "antifraud_metadata": {}, + "antifraud_score": null, + "authorization_code": "799941", + "authorized_amount": 1000, + "boleto_barcode": null, + "boleto_expiration_date": null, + "boleto_url": null, + "capture_method": "ecommerce", + "card": { + "brand": "visa", + "country": "US", + "date_created": "2015-11-06T03:39:13.000Z", + "date_updated": "2016-03-22T20:40:02.907Z", + "fingerprint": "W8EBIq2PN+qB", + "first_digits": "424242", + "holder_name": "Richard Deschamps", + "id": "card_cign456uw00rsyp6d5qq0og97", + "last_digits": "4242", + "object": "card", + "valid": true + }, + "card_brand": "visa", + "card_first_digits": "424242", + "card_holder_name": "Richard Deschamps", + "card_last_digits": "4242", + "cost": 65, + "customer": null, + "date_created": "2016-03-22T20:40:02.917Z", + "date_updated": "2016-03-22T20:40:03.193Z", + "id": 427312, + "installments": 1, + "ip": "179.191.82.50", + "metadata": {}, + "nsu": 1458679203081, + "object": "transaction", + "paid_amount": 1000, + "payment_method": "credit_card", + "phone": null, + "postback_url": null, + "referer": "api_key", + "refunded_amount": 0, + "refuse_reason": null, + "soft_descriptor": null, + "status": "paid", + "status_reason": "acquirer", + "subscription_id": null, + "tid": 1458679203081 + } + SUCCESS_RESPONSE + end + + def failed_purchase_response + <<-FAILED_RESPONSE + { + "acquirer_name": "development", + "acquirer_response_code": "88", + "address": null, + "amount": 1000, + "antifraud_metadata": {}, + "antifraud_score": null, + "authorization_code": null, + "authorized_amount": 0, + "boleto_barcode": null, + "boleto_expiration_date": null, + "boleto_url": null, + "capture_method": "ecommerce", + "card": { + "brand": "visa", + "country": "US", + "date_created": "2015-11-06T03:39:13.000Z", + "date_updated": "2016-03-22T20:40:02.907Z", + "fingerprint": "W8EBIq2PN+qB", + "first_digits": "424242", + "holder_name": "Richard Deschamps", + "id": "card_cign456uw00rsyp6d5qq0og97", + "last_digits": "4242", + "object": "card", + "valid": true + }, + "card_brand": "visa", + "card_first_digits": "424242", + "card_holder_name": "Richard Deschamps", + "card_last_digits": "4242", + "cost": 0, + "customer": null, + "date_created": "2016-03-23T08:44:29.178Z", + "date_updated": "2016-03-23T08:44:29.716Z", + "id": 428235, + "installments": 1, + "ip": "179.191.82.50", + "metadata": {}, + "nsu": 1458722669610, + "object": "transaction", + "paid_amount": 0, + "payment_method": "credit_card", + "phone": null, + "postback_url": null, + "referer": "api_key", + "refunded_amount": 0, + "refuse_reason": "acquirer", + "soft_descriptor": null, + "status": "refused", + "status_reason": "acquirer", + "subscription_id": null, + "tid": 1458722669610 + } + FAILED_RESPONSE + end + + def successful_authorize_response + <<-SUCCESS_RESPONSE + { + "acquirer_name": "development", + "acquirer_response_code": "00", + "address": null, + "amount": 1000, + "antifraud_metadata": {}, + "antifraud_score": null, + "authorization_code": "231072", + "authorized_amount": 1000, + "boleto_barcode": null, + "boleto_expiration_date": null, + "boleto_url": null, + "capture_method": "ecommerce", + "card": { + "brand": "visa", + "country": "US", + "date_created": "2015-11-06T03:39:13.000Z", + "date_updated": "2016-03-22T20:40:02.907Z", + "fingerprint": "W8EBIq2PN+qB", + "first_digits": "424242", + "holder_name": "Richard Deschamps", + "id": "card_cign456uw00rsyp6d5qq0og97", + "last_digits": "4242", + "object": "card", + "valid": true + }, + "card_brand": "visa", + "card_first_digits": "424242", + "card_holder_name": "Richard Deschamps", + "card_last_digits": "4242", + "cost": 0, + "customer": null, + "date_created": "2016-03-24T18:29:56.523Z", + "date_updated": "2016-03-24T18:29:56.742Z", + "id": 429356, + "installments": 1, + "ip": "179.191.82.50", + "metadata": {}, + "nsu": 1458844196661, + "object": "transaction", + "paid_amount": 0, + "payment_method": "credit_card", + "phone": null, + "postback_url": null, + "referer": "api_key", + "refunded_amount": 0, + "refuse_reason": null, + "soft_descriptor": null, + "status": "authorized", + "status_reason": "acquirer", + "subscription_id": null, + "tid": 1458844196661 + } + SUCCESS_RESPONSE + end + + def failed_authorize_response + <<-FAILED_RESPONSE + { + "acquirer_name": "development", + "acquirer_response_code": "88", + "address": null, + "amount": 1000, + "antifraud_metadata": {}, + "antifraud_score": null, + "authorization_code": null, + "authorized_amount": 0, + "boleto_barcode": null, + "boleto_expiration_date": null, + "boleto_url": null, + "capture_method": "ecommerce", + "card": { + "brand": "visa", + "country": "US", + "date_created": "2015-11-06T03:39:13.000Z", + "date_updated": "2016-03-22T20:40:02.907Z", + "fingerprint": "W8EBIq2PN+qB", + "first_digits": "424242", + "holder_name": "Richard Deschamps", + "id": "card_cign456uw00rsyp6d5qq0og97", + "last_digits": "4242", + "object": "card", + "valid": true + }, + "card_brand": "visa", + "card_first_digits": "424242", + "card_holder_name": "Richard Deschamps", + "card_last_digits": "4242", + "cost": 0, + "customer": null, + "date_created": "2016-03-24T18:54:03.086Z", + "date_updated": "2016-03-24T18:54:03.458Z", + "id": 429402, + "installments": 1, + "ip": "179.191.82.50", + "metadata": {}, + "nsu": 1458845643337, + "object": "transaction", + "paid_amount": 0, + "payment_method": "credit_card", + "phone": null, + "postback_url": null, + "referer": "api_key", + "refunded_amount": 0, + "refuse_reason": "acquirer", + "soft_descriptor": null, + "status": "refused", + "status_reason": "acquirer", + "subscription_id": null, + "tid": 1458845643337 + } + FAILED_RESPONSE + end + + def successful_capture_response + <<-SUCCESS_RESPONSE + { + "acquirer_name": "development", + "acquirer_response_code": "00", + "address": null, + "amount": 1000, + "antifraud_metadata": {}, + "antifraud_score": null, + "authorization_code": "231072", + "authorized_amount": 1000, + "boleto_barcode": null, + "boleto_expiration_date": null, + "boleto_url": null, + "capture_method": "ecommerce", + "card": { + "brand": "visa", + "country": "US", + "date_created": "2015-11-06T03:39:13.000Z", + "date_updated": "2016-03-22T20:40:02.907Z", + "fingerprint": "W8EBIq2PN+qB", + "first_digits": "424242", + "holder_name": "Richard Deschamps", + "id": "card_cign456uw00rsyp6d5qq0og97", + "last_digits": "4242", + "object": "card", + "valid": true + }, + "card_brand": "visa", + "card_first_digits": "424242", + "card_holder_name": "Richard Deschamps", + "card_last_digits": "4242", + "cost": 65, + "customer": null, + "date_created": "2016-03-24T18:29:56.523Z", + "date_updated": "2016-03-24T21:35:14.237Z", + "id": 429356, + "installments": 1, + "ip": "179.191.82.50", + "metadata": {}, + "nsu": "1458844196661", + "object": "transaction", + "paid_amount": 1000, + "payment_method": "credit_card", + "phone": null, + "postback_url": null, + "referer": "api_key", + "refunded_amount": 0, + "refuse_reason": null, + "soft_descriptor": null, + "status": "paid", + "status_reason": "acquirer", + "subscription_id": null, + "tid": "1458844196661" + } + SUCCESS_RESPONSE + end + + def failed_capture_response + <<-FAILED_RESPONSE + { + "errors": [ + { + "message": "Transação com status 'captured' não pode ser capturada.", + "parameter_name": null, + "type": "action_forbidden" + } + ], + "method": "post", + "url": "/transactions/429356/capture" + } + FAILED_RESPONSE + end + + def successful_refund_response + <<-SUCCESS_RESPONSE + { + "acquirer_name": "development", + "acquirer_response_code": "00", + "address": null, + "amount": 1000, + "antifraud_metadata": {}, + "antifraud_score": null, + "authorization_code": "231072", + "authorized_amount": 1000, + "boleto_barcode": null, + "boleto_expiration_date": null, + "boleto_url": null, + "capture_method": "ecommerce", + "card": { + "brand": "visa", + "country": "US", + "date_created": "2015-11-06T03:39:13.000Z", + "date_updated": "2016-03-22T20:40:02.907Z", + "fingerprint": "W8EBIq2PN+qB", + "first_digits": "424242", + "holder_name": "Richard Deschamps", + "id": "card_cign456uw00rsyp6d5qq0og97", + "last_digits": "4242", + "object": "card", + "valid": true + }, + "card_brand": "visa", + "card_first_digits": "424242", + "card_holder_name": "Richard Deschamps", + "card_last_digits": "4242", + "cost": 0, + "customer": null, + "date_created": "2016-03-24T18:29:56.523Z", + "date_updated": "2016-03-29T17:37:20.035Z", + "id": 429356, + "installments": 1, + "ip": "179.191.82.50", + "metadata": {}, + "nsu": "1458844196661", + "object": "transaction", + "paid_amount": 1000, + "payment_method": "credit_card", + "phone": null, + "postback_url": null, + "referer": "api_key", + "refunded_amount": 1000, + "refuse_reason": null, + "soft_descriptor": null, + "status": "refunded", + "status_reason": "acquirer", + "subscription_id": null, + "tid": "1458844196661" + } + SUCCESS_RESPONSE + end + + def failed_refund_response + <<-FAILED_RESPONSE + { + "errors": [ + { + "message": "Transação já estornada", + "parameter_name": null, + "type": "action_forbidden" + } + ], + "method": "post", + "url": "/transactions/429356/refund" + } + FAILED_RESPONSE + end + + def successful_void_response + <<-SUCCESS_RESPONSE + { + "acquirer_name": "pagarme", + "acquirer_response_code": "00", + "address": null, + "amount": 1000, + "antifraud_metadata": {}, + "antifraud_score": null, + "authorization_code": "420653", + "authorized_amount": 1000, + "boleto_barcode": null, + "boleto_expiration_date": null, + "boleto_url": null, + "capture_method": "ecommerce", + "card": { + "brand": "visa", + "country": "US", + "date_created": "2016-04-28T19:04:17.522Z", + "date_updated": "2016-04-28T19:04:17.744Z", + "fingerprint": "W8EBIq2PN+qB", + "first_digits": "424242", + "holder_name": "Richard Deschamps", + "id": "card_cinknt1td006ffo6dr0jjitj4", + "last_digits": "4242", + "object": "card", + "valid": true + }, + "card_brand": "visa", + "card_first_digits": "424242", + "card_holder_name": "Richard Deschamps", + "card_last_digits": "4242", + "cost": 0, + "customer": null, + "date_created": "2016-04-28T19:04:17.528Z", + "date_updated": "2016-04-28T19:07:07.905Z", + "id": 472218, + "installments": 1, + "ip": "179.185.132.108", + "metadata": {}, + "nsu": 472218, + "object": "transaction", + "paid_amount": 0, + "payment_method": "credit_card", + "phone": null, + "postback_url": null, + "referer": "api_key", + "refunded_amount": 1000, + "refuse_reason": null, + "soft_descriptor": null, + "status": "refunded", + "status_reason": "acquirer", + "subscription_id": null, + "tid": 472218 + } + SUCCESS_RESPONSE + end + + def failed_void_response + <<-FAILED_RESPONSE + { + "errors": [ + { + "message": "Transação já estornada", + "parameter_name": null, + "type": "action_forbidden" + } + ], + "method": "post", + "url": "/transactions/472218/refund" + } + FAILED_RESPONSE + end + + def successful_verify_response + <<-SUCCESS_RESPONSE + { + "acquirer_name": "pagarme", + "acquirer_response_code": "00", + "address": null, + "amount": 127, + "antifraud_metadata": {}, + "antifraud_score": null, + "authorization_code": "713869", + "authorized_amount": 127, + "boleto_barcode": null, + "boleto_expiration_date": null, + "boleto_url": null, + "capture_method": "ecommerce", + "card": { + "brand": "visa", + "country": "US", + "date_created": "2016-05-03T22:15:08.888Z", + "date_updated": "2016-05-03T22:15:09.140Z", + "fingerprint": "gulv5VbV4RnS", + "first_digits": "424242", + "holder_name": "Richard Deschamps", + "id": "card_cinrztr2w003sxc6djpqm0toe", + "last_digits": "4242", + "object": "card", + "valid": true + }, + "card_brand": "visa", + "card_first_digits": "424242", + "card_holder_name": "Richard Deschamps", + "card_last_digits": "4242", + "cost": 0, + "customer": null, + "date_created": "2016-05-03T22:15:18.697Z", + "date_updated": "2016-05-03T22:15:18.900Z", + "id": 476135, + "installments": 1, + "ip": "179.191.82.50", + "metadata": {}, + "nsu": 476135, + "object": "transaction", + "paid_amount": 0, + "payment_method": "credit_card", + "phone": null, + "postback_url": null, + "referer": "api_key", + "refunded_amount": 0, + "refuse_reason": null, + "soft_descriptor": null, + "status": "authorized", + "status_reason": "antifraud", + "subscription_id": null, + "tid": 476135 + } + SUCCESS_RESPONSE + end + + def successful_verify_void_response + <<-SUCCESS_RESPONSE + { + "acquirer_name": "pagarme", + "acquirer_response_code": "00", + "address": null, + "amount": 127, + "antifraud_metadata": {}, + "antifraud_score": null, + "authorization_code": "713869", + "authorized_amount": 127, + "boleto_barcode": null, + "boleto_expiration_date": null, + "boleto_url": null, + "capture_method": "ecommerce", + "card": { + "brand": "visa", + "country": "US", + "date_created": "2016-05-03T22:15:08.888Z", + "date_updated": "2016-05-03T22:15:09.140Z", + "fingerprint": "gulv5VbV4RnS", + "first_digits": "424242", + "holder_name": "Richard Deschamps", + "id": "card_cinrztr2w003sxc6djpqm0toe", + "last_digits": "4242", + "object": "card", + "valid": true + }, + "card_brand": "visa", + "card_first_digits": "424242", + "card_holder_name": "Richard Deschamps", + "card_last_digits": "4242", + "cost": 0, + "customer": null, + "date_created": "2016-05-03T22:15:18.697Z", + "date_updated": "2016-05-04T19:48:48.294Z", + "id": 476135, + "installments": 1, + "ip": "179.191.82.50", + "metadata": {}, + "nsu": 476135, + "object": "transaction", + "paid_amount": 0, + "payment_method": "credit_card", + "phone": null, + "postback_url": null, + "referer": "api_key", + "refunded_amount": 127, + "refuse_reason": null, + "soft_descriptor": null, + "status": "refunded", + "status_reason": "acquirer", + "subscription_id": null, + "tid": 476135 + } + SUCCESS_RESPONSE + end + + def failed_verify_response + <<-FAILED_RESPONSE + { + "acquirer_name": "pagarme", + "acquirer_response_code": "88", + "address": null, + "amount": 127, + "antifraud_metadata": {}, + "antifraud_score": null, + "authorization_code": null, + "authorized_amount": 0, + "boleto_barcode": null, + "boleto_expiration_date": null, + "boleto_url": null, + "capture_method": "ecommerce", + "card": { + "brand": "visa", + "country": "US", + "date_created": "2016-05-03T22:15:08.888Z", + "date_updated": "2016-05-03T22:15:09.140Z", + "fingerprint": "gulv5VbV4RnS", + "first_digits": "424242", + "holder_name": "Richard Deschamps", + "id": "card_cinrztr2w003sxc6djpqm0toe", + "last_digits": "4242", + "object": "card", + "valid": true + }, + "card_brand": "visa", + "card_first_digits": "424242", + "card_holder_name": "Richard Deschamps", + "card_last_digits": "4242", + "cost": 0, + "customer": null, + "date_created": "2016-05-03T22:23:26.282Z", + "date_updated": "2016-05-03T22:23:26.505Z", + "id": 476143, + "installments": 1, + "ip": "179.191.82.50", + "metadata": {}, + "nsu": 476143, + "object": "transaction", + "paid_amount": 0, + "payment_method": "credit_card", + "phone": null, + "postback_url": null, + "referer": "api_key", + "refunded_amount": 0, + "refuse_reason": "acquirer", + "soft_descriptor": null, + "status": "refused", + "status_reason": "acquirer", + "subscription_id": null, + "tid": 476143 + } + FAILED_RESPONSE + end + + def failed_error_response + <<-FAILED_RESPONSE + { + "errors": [ + { + "message": "Internal server error.", + "parameter_name": null, + "type": "internal_error" + } + ], + "method": "post", + "url": "/transactions" + } + FAILED_RESPONSE + end + + def failed_json_response + <<-SUCCESS_RESPONSE + { + foo: bar + } + SUCCESS_RESPONSE + end + +end diff --git a/test/unit/gateways/pago_facil_test.rb b/test/unit/gateways/pago_facil_test.rb new file mode 100644 index 00000000000..cb75a847402 --- /dev/null +++ b/test/unit/gateways/pago_facil_test.rb @@ -0,0 +1,239 @@ +require 'test_helper' + +class PagoFacilTest < Test::Unit::TestCase + def setup + @gateway = PagoFacilGateway.new(fixtures(:pago_facil)) + + @credit_card = ActiveMerchant::Billing::CreditCard.new( + number: '4111111111111111', + verification_value: '123', + first_name: 'Juan', + last_name: 'Reyes Garza', + month: 9, + year: Time.now.year + 1 + ) + + @amount = 100 + + @options = { + order_id: '1', + billing_address: { + address1: 'Anatole France 311', + address2: 'Polanco', + city: 'Miguel Hidalgo', + state: 'Distrito Federal', + country: 'Mexico', + zip: '11560', + phone: '5550220910' + }, + email: 'comprador@correo.com', + cellphone: '5550123456' + } + end + + def test_successful_purchase + @gateway.expects(:ssl_post).returns(successful_purchase_response) + + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal '305638', response.authorization + assert_equal 'Transaction has been successful!-Approved', response.message + assert response.test? + end + + def test_successful_purchase_amex + @gateway.expects(:ssl_post).returns(successful_purchase_response_amex) + + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal '305638', response.authorization + assert_equal 'Transaction has been successful!-Approved', response.message + assert response.test? + end + + def test_failed_purchase + @gateway.expects(:ssl_post).returns(failed_purchase_response) + + response = @gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_equal 'Errores en los datos de entrada Validaciones', response.message + end + + def test_invalid_json + @gateway.expects(:ssl_post).returns(invalid_json_response) + + response = @gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_match %r{Invalid response received from the PagoFacil API}, response.message + end + + private + + def successful_purchase_response + {'WebServices_Transacciones'=> + {'transaccion'=> + {'autorizado'=>'1', + 'autorizacion'=>'305638', + 'transaccion'=>'S-PFE12S12I12568', + 'texto'=>'Transaction has been successful!-Approved', + 'mode'=>'R', + 'empresa'=>'Usuario Invitado', + 'TransIni'=>'15:33:18 pm 25/02/2014', + 'TransFin'=>'15:33:27 pm 25/02/2014', + 'param1'=>'', + 'param2'=>'', + 'param3'=>'', + 'param4'=>'', + 'param5'=>'', + 'TipoTC'=>'Visa', + 'data'=> + {'anyoExpiracion'=>'(2) **', + 'apellidos'=>'Reyes Garza', + 'calleyNumero'=>'Anatole France 311', + 'celular'=>'5550123456', + 'colonia'=>'Polanco', + 'cp'=>'11560', + 'cvt'=>'(3) ***', + 'email'=>'comprador@correo.com', + 'estado'=>'Distrito Federal', + 'idPedido'=>'1', + 'idServicio'=>'3', + 'idSucursal'=>'60f961360ca187d533d5adba7d969d6334771370', + 'idUsuario'=>'62ad6f592ecf2faa87ef2437ed85a4d175e73c58', + 'mesExpiracion'=>'(2) **', + 'monto'=>'1.00', + 'municipio'=>'Miguel Hidalgo', + 'nombre'=>'Juan', + 'numeroTarjeta'=>'(16) **** **** ****1111', + 'pais'=>'Mexico', + 'telefono'=>'5550220910', + 'transFechaHora'=>'1393363998', + 'bin'=>'(6) ***1'}, + 'dataVal'=> + {'idSucursal'=>'12', + 'cp'=>'11560', + 'nombre'=>'Juan', + 'apellidos'=>'Reyes Garza', + 'numeroTarjeta'=>'(16) **** **** ****1111', + 'cvt'=>'(3) ***', + 'monto'=>'1.00', + 'mesExpiracion'=>'(2) **', + 'anyoExpiracion'=>'(2) **', + 'idUsuario'=>'14', + 'source'=>'1', + 'idServicio'=>'3', + 'recurrente'=>'0', + 'plan'=>'NOR', + 'diferenciado'=>'00', + 'mensualidades'=>'00', + 'ip'=>'187.162.238.170', + 'httpUserAgent'=>'Ruby', + 'idPedido'=>'1', + 'tipoTarjeta'=>'Visa', + 'hashKeyCC'=>'e5be0afe08f125ec4f6f1251141c60df88d65eae', + 'idEmpresa'=>'12', + 'nombre_comercial'=>'Usuario Invitado', + 'transFechaHora'=>'1393363998', + 'noProcess'=>'', + 'noMail'=>'', + 'notaMail'=>'', + 'settingsTransaction'=> + {'noMontoMes'=>'0.00', + 'noTransaccionesDia'=>'0', + 'minTransaccionTc'=>'5', + 'tiempoDevolucion'=>'30', + 'sendPdfTransCliente'=>'1', + 'noMontoDia'=>'0.00', + 'noTransaccionesMes'=>'0'}, + 'email'=>'comprador@correo.com', + 'telefono'=>'5550220910', + 'celular'=>'5550123456', + 'calleyNumero'=>'Anatole France 311', + 'colonia'=>'Polanco', + 'municipio'=>'Miguel Hidalgo', + 'estado'=>'Distrito Federal', + 'pais'=>'Mexico', + 'idCaja'=>'', + 'paisDetectedIP'=>'MX', + 'qa'=>'1', + 'https'=>'on'}, + 'status'=>'success' + } + } + }.to_json + end + + def failed_purchase_response + {'WebServices_Transacciones'=> + {'transaccion'=> + {'autorizado'=>'0', + 'transaccion'=>'n/a', + 'autorizacion'=>'n/a', + 'texto'=>'Errores en los datos de entrada Validaciones', + 'error'=> + {'numeroTarjeta'=>"'1111111111111111' no es de una institucion permitida"}, + 'empresa'=>'Sin determinar', + 'TransIni'=>'16:10:20 pm 25/02/2014', + 'TransFin'=>'16:10:20 pm 25/02/2014', + 'param1'=>'', + 'param2'=>'', + 'param3'=>'', + 'param4'=>'', + 'param5'=>'', + 'TipoTC'=>'', + 'data'=> + {'anyoExpiracion'=>'(2) **', + 'apellidos'=>'Reyes Garza', + 'calleyNumero'=>'Anatole France 311', + 'celular'=>'5550123456', + 'colonia'=>'Polanco', + 'cp'=>'11560', + 'cvt'=>'(3) ***', + 'email'=>'comprador@correo.com', + 'estado'=>'Distrito Federal', + 'idPedido'=>'1', + 'idServicio'=>'3', + 'idSucursal'=>'60f961360ca187d533d5adba7d969d6334771370', + 'idUsuario'=>'62ad6f592ecf2faa87ef2437ed85a4d175e73c58', + 'mesExpiracion'=>'(2) **', + 'monto'=>'1.00', + 'municipio'=>'Miguel Hidalgo', + 'nombre'=>'Juan', + 'numeroTarjeta'=>'(16) **** **** ****1111', + 'pais'=>'Mexico', + 'telefono'=>'5550220910', + 'transFechaHora'=>'1393366220', + 'bin'=>'(6) ***1'}, + 'dataVal'=> + {'email'=>'comprador@correo.com', + 'telefono'=>'5550220910', + 'celular'=>'5550123456', + 'calleyNumero'=>'Anatole France 311', + 'colonia'=>'Polanco', + 'municipio'=>'Miguel Hidalgo', + 'estado'=>'Distrito Federal', + 'pais'=>'Mexico', + 'idCaja'=>'', + 'numeroTarjeta'=>'', + 'cvt'=>'', + 'anyoExpiracion'=>'', + 'mesExpiracion'=>'', + 'https'=>'on'}, + 'status'=>'success' + } + } + }.to_json + end + + def invalid_json_response + "<b>Notice</b>\n#{failed_purchase_response}" + end + + def successful_purchase_response_amex + response = JSON.parse(successful_purchase_response) + response. + fetch('WebServices_Transacciones'). + fetch('transaccion')['autorizado'] = true + response.to_json + end +end diff --git a/test/unit/gateways/pay_conex_test.rb b/test/unit/gateways/pay_conex_test.rb new file mode 100644 index 00000000000..0269f29396d --- /dev/null +++ b/test/unit/gateways/pay_conex_test.rb @@ -0,0 +1,275 @@ +# coding: utf-8 + +require 'test_helper' + +class PayConexTest < Test::Unit::TestCase + include CommStub + + def setup + @gateway = PayConexGateway.new(account_id: 'account', api_accesskey: 'key') + @credit_card = credit_card + @amount = 100 + + @options = { + order_id: '1', + billing_address: address, + description: 'Store Purchase' + } + end + + def test_successful_purchase + @gateway.expects(:ssl_post).returns(successful_purchase_response) + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal '000000001681', response.authorization + assert response.test? + end + + def test_failed_purchase + @gateway.expects(:ssl_post).returns(failed_purchase_response) + + response = @gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_equal 30002, response.params['error_code'] + end + + def test_successful_authorize_and_capture + @gateway.expects(:ssl_post).returns(successful_authorize_response) + response = @gateway.authorize(@amount, @credit_card, @options) + assert_success response + assert_equal '000000001721', response.authorization + + @gateway.expects(:ssl_post).returns(successful_capture_response) + response = @gateway.capture(@amount, response.authorization) + assert_success response + assert_equal 'CAPTURED', response.message + end + + def test_failed_authorize + @gateway.expects(:ssl_post).returns(failed_authorize_response) + + response = @gateway.authorize(@amount, @credit_card, @options) + assert_failure response + assert_equal 30002, response.params['error_code'] + assert_equal 'DECLINED', response.message + end + + def test_failed_capture + @gateway.expects(:ssl_post).returns(failed_capture_response) + + response = @gateway.capture(@amount, 'Authorization') + assert_failure response + assert_equal 20006, response.params['error_code'] + assert_equal 'Invalid token_id', response.message + end + + def test_successful_refund + @gateway.expects(:ssl_post).returns(successful_refund_response) + response = @gateway.refund(@amount, 'Authorization') + assert_success response + assert_equal '000000001801', response.authorization + assert_equal 'VOID', response.message + end + + def test_failed_refund + @gateway.expects(:ssl_post).returns(failed_refund_response) + + response = @gateway.refund(@amount, 'Authorization') + assert_failure response + assert_equal 20017, response.params['error_code'] + assert_equal 'INVALID REFUND AMOUNT', response.message + end + + def test_successful_void + @gateway.expects(:ssl_post).returns(successful_void_response) + response = @gateway.void('Authorization') + assert_success response + assert_equal '000000001881', response.authorization + assert_equal 'APPROVED', response.message + end + + def test_failed_void + @gateway.expects(:ssl_post).returns(failed_void_response) + + response = @gateway.refund(@amount, 'Authorization') + assert_failure response + assert_equal 20687, response.params['error_code'] + assert_equal 'TRANSACTION ID ALREADY REVERSED', response.message + end + + def test_successful_verify + @gateway.expects(:ssl_post).returns(successful_verify_response) + response = @gateway.verify(@credit_card) + assert_success response + assert_equal '000000001981', response.authorization + assert_equal 'APPROVED', response.message + end + + def test_successful_credit + @gateway.expects(:ssl_post).returns(successful_credit_response) + response = @gateway.credit(@amount, @credit_card) + assert_success response + assert_equal '000000002061', response.authorization + end + + def test_failed_credit + @gateway.expects(:ssl_post).returns(failed_credit_response) + + response = @gateway.authorize(@amount, @credit_card) + assert_failure response + assert_equal '30370', response.params['error_code'] + assert_equal 'CARD DATA UNREADABLE', response.message + end + + def test_successful_store + @gateway.expects(:ssl_post).returns(successful_store_response) + response = @gateway.store(@credit_card) + assert_success response + assert_equal '000000002101', response.authorization + end + + def test_failed_store + @gateway.expects(:ssl_post).returns(failed_store_response) + + response = @gateway.store(@credit_card) + assert_failure response + assert_equal '30370', response.params['error_code'] + assert_equal 'CARD DATA UNREADABLE', response.message + end + + def test_card_present_purchase_passes_track_data + stub_comms do + @gateway.purchase(@amount, credit_card_with_track_data('4000100011112224')) + end.check_request do |endpoint, data, headers| + assert_match(/card_tracks/, data) + end.respond_with(successful_card_present_purchase_response) + end + + def test_successful_purchase_using_token + @gateway.expects(:ssl_post).returns(successful_purchase_using_token_response) + response = @gateway.purchase(@amount, 'TheToken', @options) + assert_success response + assert_equal '000000004561', response.authorization + end + + def test_successful_purchase_using_echeck + @gateway.expects(:ssl_post).returns(successful_purchase_using_echeck_response) + response = @gateway.purchase(@amount, check, @options) + assert_success response + assert_equal '000000007161', response.authorization + end + + def test_failed_purchase_using_echeck + @gateway.expects(:ssl_post).returns(failed_purchase_using_echeck_response) + response = @gateway.purchase(@amount, check, @options) + assert_failure response + assert_equal 'Invalid bank_routing_number', response.message + end + + def test_scrub + assert @gateway.supports_scrubbing? + assert_equal post_scrubbed, @gateway.scrub(pre_scrubbed) + end + + private + + def pre_scrubbed + <<-PRE_SCRUBBED + D, DEBUG -- : account_id=220614968961&api_accesskey=69e9c4dd6b8ab9ab47da4e288df78315&tender_type=CARD&card_number=4000100011112224&card_expiration=0916&card_verification=123&first_name=Longbob&last_name=Longsen&street_address1=1234+My+Street&street_address2=Apt+1&city=Ottawa&state=ON&zip=K1C2N6&country=CA&phone=%28555%29555-5555&transaction_description=Store+Purchase&response_format=JSON&transaction_amount=1.00&email=joe%40example.com&transaction_type=SALE + <- "account_id=220614968961&api_accesskey=69e9c4dd6b8ab9ab47da4e288df78315&tender_type=CARD&card_number=4000100011112224&card_expiration=0916&card_verification=123&first_name=Longbob&last_name=Longsen&street_address1=1234+My+Street&street_address2=Apt+1&city=Ottawa&state=ON&zip=K1C2N6&country=CA&phone=%28555%29555-5555&transaction_description=Store+Purchase&response_format=JSON&transaction_amount=1.00&email=joe%40example.com&transaction_type=SALE" + -> "\x1F\x8B\b\x00\x00\x00\x00\x00\x00\x03" + -> "eP\xCBn\xC20\x10\xFC\x15\xE43Hy\x00\xA2\xB9E-\a$\xA4\"\xA2r\xE8%\xDA8\vXMlw\xED\xA4\xA5U\xFF\xBDv\b\x90\xD2\xBD\xED\xCC\xEChf\xBF\x99%\x90\x06 + -> "\xDFH\x86f<\x02\x00\x00" + D, [2015-03-04T18:13:07.219562 #71589] DEBUG -- : {"transaction_id":"000000002021","tender_type":"CARD","transaction_timestamp":"2015-03-04 17:13:04","card_brand":"VISA","transaction_type":"SALE","last4":"2224","card_expiration":"0916","authorization_code":"CVI292","authorization_message":"APPROVED","request_amount":1,"transaction_amount":1,"first_name":"Longbob","last_name":"Longsen","keyed":true,"swiped":false,"transaction_approved":true,"avs_response":"Z","cvv2_response":"U","transaction_description":"Store Purchase","balance":1,"currency":"USD","error":false,"error_code":0,"error_message":null,"error_msg":null} + {"transaction_id":"000000002021","tender_type":"CARD","transaction_timestamp":"2015-03-04 17:13:04","card_brand":"VISA","transaction_type":"SALE","last4":"2224","card_expiration":"0916","authorization_code":"CVI292","authorization_message":"APPROVED","request_amount":1,"transaction_amount":1,"first_name":"Longbob","last_name":"Longsen","keyed":true,"swiped":false,"transaction_approved":true,"avs_response":"Z","cvv2_response":"U","transaction_description":"Store Purchase","balance":1,"currency":"USD","error":false,"error_code":0,"error_message":null,"error_msg":null} + PRE_SCRUBBED + end + + def post_scrubbed + <<-POST_SCRUBBED + D, DEBUG -- : account_id=220614968961&api_accesskey=[FILTERED]&tender_type=CARD&card_number=[FILTERED]&card_expiration=0916&card_verification=[FILTERED]&first_name=Longbob&last_name=Longsen&street_address1=1234+My+Street&street_address2=Apt+1&city=Ottawa&state=ON&zip=K1C2N6&country=CA&phone=%28555%29555-5555&transaction_description=Store+Purchase&response_format=JSON&transaction_amount=1.00&email=joe%40example.com&transaction_type=SALE + <- \"account_id=220614968961&api_accesskey=[FILTERED]&tender_type=CARD&card_number=[FILTERED]&card_expiration=0916&card_verification=[FILTERED]&first_name=Longbob&last_name=Longsen&street_address1=1234+My+Street&street_address2=Apt+1&city=Ottawa&state=ON&zip=K1C2N6&country=CA&phone=%28555%29555-5555&transaction_description=Store+Purchase&response_format=JSON&transaction_amount=1.00&email=joe%40example.com&transaction_type=SALE\" + -> \"\u001F?\b\u0000\u0000\u0000\u0000\u0000\u0000\u0003\" + -> \"eP?n?0\u0010?\u0015?3Hy\u0000??E-\a$?\"?r?%?8\vXMlw???U??v\b?????hf??%?\u0006 + -> \"?H?f<\u0002\u0000\u0000\" + D, [2015-03-04T18:13:07.219562 #71589] DEBUG -- : {\"transaction_id\":\"000000002021\",\"tender_type\":\"CARD\",\"transaction_timestamp\":\"2015-03-04 17:13:04\",\"card_brand\":\"VISA\",\"transaction_type\":\"SALE\",\"last4\":\"2224\",\"card_expiration\":\"0916\",\"authorization_code\":\"CVI292\",\"authorization_message\":\"APPROVED\",\"request_amount\":1,\"transaction_amount\":1,\"first_name\":\"Longbob\",\"last_name\":\"Longsen\",\"keyed\":true,\"swiped\":false,\"transaction_approved\":true,\"avs_response\":\"Z\",\"cvv2_response\":\"U\",\"transaction_description\":\"Store Purchase\",\"balance\":1,\"currency\":\"USD\",\"error\":false,\"error_code\":0,\"error_message\":null,\"error_msg\":null} + {\"transaction_id\":\"000000002021\",\"tender_type\":\"CARD\",\"transaction_timestamp\":\"2015-03-04 17:13:04\",\"card_brand\":\"VISA\",\"transaction_type\":\"SALE\",\"last4\":\"2224\",\"card_expiration\":\"0916\",\"authorization_code\":\"CVI292\",\"authorization_message\":\"APPROVED\",\"request_amount\":1,\"transaction_amount\":1,\"first_name\":\"Longbob\",\"last_name\":\"Longsen\",\"keyed\":true,\"swiped\":false,\"transaction_approved\":true,\"avs_response\":\"Z\",\"cvv2_response\":\"U\",\"transaction_description\":\"Store Purchase\",\"balance\":1,\"currency\":\"USD\",\"error\":false,\"error_code\":0,\"error_message\":null,\"error_msg\":null} + POST_SCRUBBED + end + + def successful_purchase_response + %({"transaction_id":"000000001681","tender_type":"CARD","transaction_timestamp":"2015-03-04 16:35:52","card_brand":"VISA","transaction_type":"SALE","last4":"2224","card_expiration":"0916","authorization_code":"CVI877","authorization_message":"APPROVED","request_amount":1,"transaction_amount":1,"first_name":"Longbob","last_name":"Longsen","keyed":true,"swiped":false,"transaction_approved":true,"avs_response":"Z","cvv2_response":"U","transaction_description":"Store Purchase","balance":1,"currency":"USD","error":false,"error_code":0,"error_message":null,"error_msg":null}) + end + + def failed_purchase_response + %({"transaction_id":"000000001701","tender_type":"CARD","transaction_timestamp":"2015-03-04 16:38:37","card_brand":"VISA","transaction_type":"SALE","last4":"2224","card_expiration":"0916","authorization_message":"CALL AUTH CENTER","transaction_amount":1.01,"first_name":"Longbob","last_name":"Longsen","keyed":true,"swiped":false,"transaction_approved":false,"transaction_description":"Store Purchase","reason_code":null,"gateway_id":null,"currency":"USD","error":true,"error_code":30002,"error_message":"DECLINED","error_msg":"DECLINED"}) + end + + def successful_authorize_response + %({"transaction_id":"000000001721","tender_type":"CARD","transaction_timestamp":"2015-03-04 16:44:34","card_brand":"VISA","transaction_type":"AUTHORIZATION","last4":"2224","card_expiration":"0916","authorization_code":"CVI986","authorization_message":"APPROVED","request_amount":1,"transaction_amount":1,"first_name":"Longbob","last_name":"Longsen","keyed":true,"swiped":false,"transaction_approved":true,"avs_response":"Z","cvv2_response":"U","transaction_description":"Store Purchase","balance":1,"currency":"USD","error":false,"error_code":0,"error_message":null,"error_msg":null}) + end + + def failed_authorize_response + %({"transaction_id":"000000001741","tender_type":"CARD","transaction_timestamp":"2015-03-04 16:48:29","card_brand":"VISA","transaction_type":"AUTHORIZATION","last4":"2224","card_expiration":"0916","authorization_message":"CALL AUTH CENTER","transaction_amount":1.01,"first_name":"Longbob","last_name":"Longsen","keyed":true,"swiped":false,"transaction_approved":false,"transaction_description":"Store Purchase","reason_code":null,"gateway_id":null,"currency":"USD","error":true,"error_code":30002,"error_message":"DECLINED","error_msg":"DECLINED"}) + end + + def successful_capture_response + %({"transaction_id":"000000001721","tender_type":"CARD","transaction_timestamp":"2015-03-04 16:44:38","card_brand":"VISA","transaction_type":"CAPTURE","last4":"2224","card_expiration":"0916","authorization_message":"CAPTURED","request_amount":1,"transaction_amount":1,"first_name":"Longbob Longsen","keyed":true,"swiped":false,"transaction_approved":true,"transaction_description":"Store Purchase","currency":"USD","error":false,"error_code":0,"error_message":null,"error_msg":null}) + end + + def failed_capture_response + %({"error":true,"error_code":20006,"error_message":"Invalid token_id","error_msg":"Invalid token_id"}) + end + + def successful_refund_response + %({"original_transaction_id":"000000001781","transaction_id":"000000001801","tender_type":"CARD","transaction_timestamp":"2015-03-04 16:52:41","card_brand":"VISA","transaction_type":"REFUND","last4":"2224","card_expiration":"0916","authorization_code":"CVI086","authorization_message":"VOID","request_amount":1,"transaction_amount":1,"first_name":"Longbob Longsen","keyed":true,"swiped":false,"transaction_approved":true,"transaction_description":"Store Purchase","currency":"USD","error":false,"error_code":0,"error_message":null,"error_msg":null}) + end + + def failed_refund_response + %({"error":true,"error_code":20017,"error_message":"INVALID REFUND AMOUNT","error_msg":"INVALID REFUND AMOUNT"}) + end + + def successful_void_response + %({"original_transaction_id":"000000001861","transaction_id":"000000001881","tender_type":"CARD","transaction_timestamp":"2015-03-04 17:02:44","card_brand":"VISA","transaction_type":"REVERSAL","last4":"2224","card_expiration":"0916","authorization_code":"CVI194","authorization_message":"APPROVED","request_amount":1,"transaction_amount":1,"first_name":"Longbob Longsen","keyed":true,"swiped":false,"transaction_approved":true,"transaction_description":"Store Purchase","currency":"USD","error":false,"error_code":0,"error_message":null,"error_msg":null}) + end + + def failed_void_response + %({"error":true,"error_code":20687,"error_message":"TRANSACTION ID ALREADY REVERSED","error_msg":"TRANSACTION ID ALREADY REVERSED"}) + end + + def successful_verify_response + %({"transaction_id":"000000001981","tender_type":"CARD","transaction_timestamp":"2015-03-04 17:09:34","card_brand":"VISA","transaction_type":"AUTHORIZATION","last4":"2224","card_expiration":"0916","authorization_code":"CVI261","authorization_message":"APPROVED","request_amount":0,"transaction_amount":0,"first_name":"Longbob","last_name":"Longsen","keyed":true,"swiped":false,"transaction_approved":true,"avs_response":"Z","cvv2_response":"U","transaction_description":"Store Purchase","currency":"USD","error":false,"error_code":0,"error_message":null,"error_msg":null}) + end + + def successful_credit_response + %({"transaction_id":"000000002061","tender_type":"CARD","transaction_timestamp":"2015-03-04 17:58:03","card_brand":"VISA","transaction_type":"CREDIT","last4":"2224","card_expiration":"0916","authorization_message":"CREDIT","request_amount":1,"transaction_amount":1,"first_name":"Longbob","last_name":"Longsen","keyed":true,"swiped":false,"transaction_approved":true,"transaction_description":"Store Purchase","currency":"USD","error":false,"error_code":0,"error_message":null,"error_msg":null}) + end + + def failed_credit_response + %({"error":true,"error_code":"30370","error_message":"CARD DATA UNREADABLE","error_msg":"CARD DATA UNREADABLE"}) + end + + def successful_store_response + %({"transaction_id":"000000002101","tender_type":"CARD","transaction_timestamp":"2015-03-04 18:01:45","card_brand":"VISA","transaction_type":"STORE","last4":"2224","card_expiration":"0916","request_amount":0,"transaction_amount":0,"first_name":"Longbob","last_name":"Longsen","keyed":true,"swiped":false,"currency":"USD","error":false,"error_code":0,"error_message":null,"error_msg":null}) + end + + def failed_store_response + %({"error":true,"error_code":"30370","error_message":"CARD DATA UNREADABLE","error_msg":"CARD DATA UNREADABLE"}) + end + + def successful_card_present_purchase_response + %({"transaction_id":"000000004441","tender_type":"CARD","transaction_timestamp":"2015-03-05 11:02:54","card_brand":"VISA","transaction_type":"SALE","last4":"2224","card_expiration":"1215","authorization_code":"CVI636","authorization_message":"APPROVED","request_amount":1,"transaction_amount":1,"first_name":"L.","last_name":"LONGSEN","keyed":false,"swiped":true,"transaction_approved":true,"avs_response":"Z","transaction_description":"Store Purchase","balance":1,"currency":"USD","error":false,"error_code":0,"error_message":null,"error_msg":null}) + end + + def successful_purchase_using_token_response + %({"transaction_id":"000000004561","tender_type":"CARD","transaction_timestamp":"2015-03-05 12:18:18","card_brand":"VISA","transaction_type":"STORE","last4":"2224","card_expiration":"0916","request_amount":0,"transaction_amount":0,"first_name":"Longbob","last_name":"Longsen","keyed":true,"swiped":false,"currency":"USD","error":false,"error_code":0,"error_message":null,"error_msg":null}) + end + + def successful_purchase_using_echeck_response + %({"transaction_id":"000000007161","tender_type":"ACH","transaction_timestamp":"2015-03-05 16:05:56","card_brand":"ACH","transaction_type":"SALE","last4":"8535","card_expiration":null,"authorization_code":"ACH","authorization_message":"PENDING","request_amount":1,"transaction_amount":1,"first_name":"Jim","last_name":"Smith","keyed":true,"swiped":false,"transaction_approved":true,"transaction_description":"Store Purchase","currency":"USD","check_number":"1","error":false,"error_code":0,"error_message":null,"error_msg":null}) + end + + def failed_purchase_using_echeck_response + %({"error":true,"error_code":20019,"error_message":"Invalid bank_routing_number","error_msg":"Invalid bank_routing_number"}) + end +end diff --git a/test/unit/gateways/pay_gate_xml_test.rb b/test/unit/gateways/pay_gate_xml_test.rb index 5090127654b..157cd2bd89b 100644 --- a/test/unit/gateways/pay_gate_xml_test.rb +++ b/test/unit/gateways/pay_gate_xml_test.rb @@ -8,9 +8,12 @@ def setup @credit_card = credit_card('4000000000000002') @declined_card = credit_card('4000000000000036') + # May need to generate a unique order id as server responds with duplicate order detected @options = { - :order_id => 'abc123', + :order_id => Time.now.getutc, :billing_address => address, + :email => 'john.doe@example.com', + :ip => '127.0.0.1', :description => 'Store Purchase', } end @@ -27,7 +30,6 @@ def test_successful_authorization assert response.test? end - def test_successful_settlement @gateway.expects(:ssl_post).returns(successful_settlement_response) @@ -38,6 +40,14 @@ def test_successful_settlement assert response.test? end + def test_successful_refund + @gateway.expects(:ssl_post).returns(successful_refund_response) + + assert response = @gateway.refund(@amount, '16996548', @options) + assert_success response + + assert response.test? + end def test_unsuccessful_request @gateway.expects(:ssl_post).returns(failed_authorization_response) @@ -82,5 +92,14 @@ def failed_authorization_response ENDOFXML end + def successful_refund_response + <<-ENDOFXML + <?xml version="1.0" encoding="UTF-8"?> + <!DOCTYPE protocol SYSTEM "https://www.paygate.co.za/payxml/payxml_v4.dtd"> + <protocol ver="4.0" pgid="10011021600" pwd="test" > + <refundrx tid="16996548" cref="abc123" stat="5" sdesc="Received by Paygate" res="990005" rdesc="Request for Refund Received" bno="" /> + </protocol> + ENDOFXML + end end diff --git a/test/unit/gateways/pay_hub_test.rb b/test/unit/gateways/pay_hub_test.rb new file mode 100644 index 00000000000..0226cea3ec0 --- /dev/null +++ b/test/unit/gateways/pay_hub_test.rb @@ -0,0 +1,385 @@ +require 'test_helper' + +class PayHubTest < Test::Unit::TestCase + def setup + @gateway = PayHubGateway.new( + orgid: '123456', + username: 'abc123DEF', + password: 'abc123DEF', + tid: '123' + ) + @credit_card = credit_card + @amount = 200 + @options = { + first_name: 'Garry', + last_name: 'Barry', + phone: '9179328589', + email: 'abcsss@mailinator.com', + billing_address: { + address1: '123 ahappy St.', + address2: 'Abca', + city: 'aHappy City', + state: 'CA', + zip: '94901' + } + } + end + + def test_successful_purchase + @gateway.expects(:ssl_request).returns(successful_purchase_or_authorize_response) + + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert response.test? + assert_equal 'SUCCESS', response.message + end + + def test_successful_purchase_without_options + @gateway.expects(:ssl_request).returns(successful_purchase_or_authorize_response) + + response = @gateway.purchase(@amount, @credit_card) + assert_success response + assert_equal 'SUCCESS', response.message + end + + def test_successful_authorize + @gateway.expects(:ssl_request).returns(successful_purchase_or_authorize_response) + + response = @gateway.authorize(@amount, @credit_card, @options) + assert_success response + assert_equal 'SUCCESS', response.message + end + + def test_successful_capture + @gateway.expects(:ssl_request).returns(successful_capture_response) + + response = @gateway.capture(@amount, 123) + + assert_success response + assert_equal 'TRANSACTION CAPTURED SUCCESSFULLY', response.message + end + + def test_unsuccessful_capture + amount = 200 + @gateway.expects(:ssl_request).returns(failed_capture_response) + + response = @gateway.capture(amount, 123) + assert_failure response + assert_equal 'UNABLE TO CAPTURE', response.message + end + + def test_successful_settled_refund + @gateway.expects(:ssl_request).twice.returns(failed_void_response, successful_refund_response) + + response = @gateway.refund(@amount, 123) + + assert_success response + assert_equal 'SUCCESS', response.message + end + + def test_successful_unsettled_refund + @gateway.expects(:ssl_request).returns(successful_void_response) + + response = @gateway.refund(@amount, 123) + + assert_success response + assert_equal 'SUCCESS', response.message + end + + def test_unsuccessful_refund + @gateway.expects(:ssl_request).twice.returns(failed_void_response, failed_refund_response) + + assert response = @gateway.refund(@amount, 123) + + assert_failure response + assert_equal 'Unable to refund the previous transaction.', response.message + end + + def test_invalid_raw_response + @gateway.expects(:ssl_request).returns(invalid_json_response) + + assert response = @gateway.purchase(@amount, @credit_card, @options) + + assert_failure response + assert_match %r{^Invalid response received from the Payhub API}, response.message + end + + def test_invalid_number + @gateway.expects(:ssl_request).returns(response_for_error_codes('14')) + + response = @gateway.purchase(@amount, @credit_card, @options) + + assert_equal PayHubGateway::STANDARD_ERROR_CODE_MAPPING['14'], response.error_code + end + + def test_invalid_expiry_date + @gateway.expects(:ssl_request).returns(response_for_error_codes('80')) + + response = @gateway.purchase(@amount, @credit_card, @options) + + assert_equal PayHubGateway::STANDARD_ERROR_CODE_MAPPING['80'], response.error_code + end + + def test_invalid_cvc + @gateway.expects(:ssl_request).returns(response_for_error_codes('82')) + + response = @gateway.purchase(@amount, @credit_card, @options) + + assert_equal PayHubGateway::STANDARD_ERROR_CODE_MAPPING['82'], response.error_code + end + + def test_expired_card + @gateway.expects(:ssl_request).returns(response_for_error_codes('82')) + + response = @gateway.purchase(@amount, @credit_card, @options) + + assert_equal PayHubGateway::STANDARD_ERROR_CODE_MAPPING['82'], response.error_code + end + + def test_card_declined + ['05', '61', '62', '65', '93'].each do |error_code| + @gateway.expects(:ssl_request).returns(response_for_error_codes(error_code)) + + response = @gateway.purchase(@amount, @credit_card, @options) + + assert_equal PayHubGateway::STANDARD_ERROR_CODE_MAPPING[error_code], response.error_code + end + end + + def test_call_issuer + ['01', '02'].each do |error_code| + @gateway.expects(:ssl_request).returns(response_for_error_codes(error_code)) + + response = @gateway.purchase(@amount, @credit_card, @options) + + assert_equal PayHubGateway::STANDARD_ERROR_CODE_MAPPING[error_code], response.error_code + end + end + + def test_pickup_card + ['04', '07', '41', '43'].each do |error_code| + @gateway.expects(:ssl_request).returns(response_for_error_codes(error_code)) + + response = @gateway.purchase(@amount, @credit_card, @options) + + assert_equal PayHubGateway::STANDARD_ERROR_CODE_MAPPING[error_code], response.error_code + end + end + + def test_avs_codes + PayHubGateway::AVS_CODE_TRANSLATOR.keys.each do |code| + @gateway.expects(:ssl_request).returns(response_for_avs_codes(code)) + + response = @gateway.purchase(@amount, @credit_card, @options) + + assert_equal code, response.avs_result['code'] + end + end + + def test_cvv_codes + PayHubGateway::CVV_CODE_TRANSLATOR.keys.each do |code| + @gateway.expects(:ssl_request).returns(response_for_cvv_codes(code)) + + response = @gateway.purchase(@amount, @credit_card, @options) + + assert_equal code, response.cvv_result['code'] + assert_equal PayHubGateway::CVV_CODE_TRANSLATOR[code], response.cvv_result['message'] + end + end + + def test_unsuccessful_request + @gateway.expects(:ssl_request).returns(failed_purchase_or_authorize_response) + + @gateway.options.merge!({:mode => 'live', :test => false}) + + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_failure response + refute response.test? + end + + def test_unsuccessful_authorize + @gateway.expects(:ssl_request).returns(failed_purchase_or_authorize_response) + + @gateway.options.merge!({:mode => 'live', :test => false}) + + response = @gateway.authorize(@amount, @credit_card, @options) + assert_failure response + refute response.test? + end + + private + + def failed_void_response + <<-RESPONSE + { + "CARD_TOKEN_NO": "9999000000001705", + "AVS_RESULT_CODE": "N", + "TRANSACTION_ID": "7525", + "CUSTOMER_ID": "136", + "VERIFICATION_RESULT_CODE": "M", + "RESPONSE_CODE": "4073", + "RISK_STATUS_RESPONSE_CODE": "", + "TRANSACTION_DATE_TIME": "110914 101145", + "RISK_STATUS_RESPONSE_TEXT": "", + "APPROVAL_CODE": "TAS725", + "BATCH_ID": "420", + "RESPONSE_TEXT": "Unable to void previous transaction.", + "CIS_NOTE": "" + } + RESPONSE + end + + def failed_refund_response + <<-RESPONSE + { + "CARD_TOKEN_NO": "", + "AVS_RESULT_CODE": "", + "TRANSACTION_ID": "", + "CUSTOMER_ID": "", + "VERIFICATION_RESULT_CODE": "", + "RESPONSE_CODE": "4074", + "RISK_STATUS_RESPONSE_CODE": "", + "TRANSACTION_DATE_TIME": "", + "RISK_STATUS_RESPONSE_TEXT": "", + "APPROVAL_CODE": "", + "BATCH_ID": "", + "RESPONSE_TEXT": "Unable to refund the previous transaction.", + "CIS_NOTE": "" + } + RESPONSE + end + + def response_for_cvv_codes(code) + <<-RESPONSE + { + "RESPONSE_CODE": "00", + "RESPONSE_TEXT": "SUCCESS", + "VERIFICATION_RESULT_CODE": "#{code}" + } + RESPONSE + end + + def response_for_avs_codes(code) + <<-RESPONSE + { + "RESPONSE_CODE": "00", + "RESPONSE_TEXT": "SUCCESS", + "AVS_RESULT_CODE": "#{code}" + } + RESPONSE + end + + def response_for_error_codes(error_code) + <<-RESPONSE + { + "RESPONSE_CODE": "#{error_code}", + "RESPONSE_TEXT": "#{PayHubGateway::STANDARD_ERROR_CODE_MAPPING[error_code]}" + } + RESPONSE + end + + def successful_purchase_or_authorize_response + <<-RESPONSE + { + "CARD_TOKEN_NO": "9999000000001033", + "AVS_RESULT_CODE": "N", + "TRANSACTION_ID": "479", + "CUSTOMER_ID": "18", + "VERIFICATION_RESULT_CODE": "M", + "RESPONSE_CODE": "00", + "RISK_STATUS_RESPONSE_CODE": "", + "TRANSACTION_DATE_TIME": #{Time.now.year + 1}, + "RISK_STATUS_RESPONSE_TEXT": "", + "APPROVAL_CODE": "TAS214", + "BATCH_ID": "97", + "RESPONSE_TEXT": "SUCCESS", + "CIS_NOTE": "" + } + RESPONSE + end + + def failed_purchase_or_authorize_response + <<-RESPONSE + { + "CARD_TOKEN_NO": "", + "AVS_RESULT_CODE": "", + "TRANSACTION_ID": "", + "CUSTOMER_ID": "", + "VERIFICATION_RESULT_CODE": "", + "RESPONSE_CODE": "4008", + "RISK_STATUS_RESPONSE_CODE": "", + "TRANSACTION_DATE_TIME": "", + "RISK_STATUS_RESPONSE_TEXT"=>"", + "APPROVAL_CODE"=>"", + "BATCH_ID"=>"", + "RESPONSE_TEXT"=>"Invalid API Username. Please contact the merchant.", + "CIS_NOTE"=>"" + } + RESPONSE + end + + def successful_void_response + <<-RESPONSE + { + "CARD_TOKEN_NO": "9999000000001705", + "AVS_RESULT_CODE": "0", + "TRANSACTION_ID": "7522", + "CUSTOMER_ID": "136", + "VERIFICATION_RESULT_CODE": "", + "RESPONSE_CODE": "00", + "RISK_STATUS_RESPONSE_CODE": "", + "TRANSACTION_DATE_TIME": "2014-11-07 21:55:34", + "RISK_STATUS_RESPONSE_TEXT": "", + "APPROVAL_CODE": "TAS444", + "BATCH_ID": "416", + "RESPONSE_TEXT": "SUCCESS", + "CIS_NOTE": "" + } + RESPONSE + end + + def successful_refund_response + <<-RESPONSE + { + "CARD_TOKEN_NO": "9999000000001034", + "AVS_RESULT_CODE": "0", + "TRANSACTION_ID": "7523", + "CUSTOMER_ID": "", + "VERIFICATION_RESULT_CODE": "", + "RESPONSE_CODE": "00", + "RISK_STATUS_RESPONSE_CODE": "", + "TRANSACTION_DATE_TIME": "2014-11-07 21:58:35", + "RISK_STATUS_RESPONSE_TEXT": "", + "APPROVAL_CODE": " ", + "BATCH_ID": "417", + "RESPONSE_TEXT": "SUCCESS", + "CIS_NOTE": "" + } + RESPONSE + end + + def successful_capture_response + <<-RESPONSE + { + "BATCH_ID": "428", + "RESPONSE_TEXT": "TRANSACTION CAPTURED SUCCESSFULLY", + "RESPONSE_CODE": "00" + } + RESPONSE + end + + def failed_capture_response + <<-RESPONSE + { + "RESPONSE_TEXT": "UNABLE TO CAPTURE", + "RESPONSE_CODE": "4075" + } + RESPONSE + end + + def invalid_json_response + <<-RESPONSE + "foo" => "bar" + RESPONSE + end +end diff --git a/test/unit/gateways/pay_junction_test.rb b/test/unit/gateways/pay_junction_test.rb index 9f9f8dee039..16a3139be79 100644 --- a/test/unit/gateways/pay_junction_test.rb +++ b/test/unit/gateways/pay_junction_test.rb @@ -2,12 +2,14 @@ require 'test_helper' class PayJunctionTest < Test::Unit::TestCase + include CommStub + def setup - Base.gateway_mode = :test + Base.mode = :test @gateway = PayJunctionGateway.new( - :login => "pj-ql-01", - :password => "pj-ql-01p" + :login => 'pj-ql-01', + :password => 'pj-ql-01p' ) @credit_card = credit_card @@ -17,69 +19,83 @@ def setup } @amount = 100 end - - - def test_detect_test_credentials_when_in_production + + def test_detect_test_credentials_when_in_production Base.mode = :production - + live_gw = PayJunctionGateway.new( - :login => "l", - :password => "p" + :login => 'l', + :password => 'p' ) assert_false live_gw.test? - + test_gw = PayJunctionGateway.new( - :login => "pj-ql-01", - :password => "pj-ql-01p" - ) + :login => 'pj-ql-01', + :password => 'pj-ql-01p' + ) assert test_gw.test? end - + def test_successful_authorization @gateway.expects(:ssl_post).returns(successful_authorization_response) response = @gateway.authorize(@amount, @credit_card, @options) assert_success response assert_equal PayJunctionGateway::SUCCESS_MESSAGE, response.message end - + def test_failed_authorization @gateway.expects(:ssl_post).returns(failed_authorization_response) response = @gateway.authorize(@amount, @credit_card, @options) assert_failure response assert_equal PayJunctionGateway::DECLINE_CODES['FE'], response.message end - + def test_successful_refund @gateway.expects(:ssl_post).returns(successful_refund_response) - response = @gateway.refund(@amount, "123") + response = @gateway.refund(@amount, '123') assert_success response assert_equal PayJunctionGateway::SUCCESS_MESSAGE, response.message end - + def test_successful_deprecated_credit @gateway.expects(:ssl_post).returns(successful_refund_response) - assert_deprecation_warning(Gateway::CREDIT_DEPRECATION_MESSAGE, @gateway) do - response = @gateway.credit(@amount, "123") + assert_deprecation_warning(Gateway::CREDIT_DEPRECATION_MESSAGE) do + response = @gateway.credit(@amount, '123') assert_success response assert_equal PayJunctionGateway::SUCCESS_MESSAGE, response.message end end - + def test_avs_result_not_supported @gateway.expects(:ssl_post).returns(successful_authorization_response) - - response = @gateway.purchase(@amount, @credit_card, @options) + + response = @gateway.purchase(@amount, @credit_card, @options) assert_nil response.avs_result['code'] end - + def test_cvv_result_not_supported @gateway.expects(:ssl_post).returns(successful_authorization_response) - + response = @gateway.purchase(@amount, @credit_card, @options) assert_nil response.cvv_result['code'] end - + + def test_add_creditcard_with_track_data + @credit_card.track_data = 'Tracking data' + stub_comms do + @gateway.authorize(@amount, @credit_card, @options) + end.check_request do |endpoint, data, headers| + assert_match 'dc_track=Tracking+data', data + assert_no_match(/dc_name=/, data) + assert_no_match(/dc_number=/, data) + assert_no_match(/dc_expiration_month=/, data) + assert_no_match(/dc_expiration_year=/, data) + assert_no_match(/dc_verification_number=/, data) + end.respond_with(successful_authorization_response) + end + private + def successful_authorization_response <<-RESPONSE dc_merchant_name=PayJunction - (demo)dc_merchant_address=3 W. Carrillodc_merchant_city=Santa Barbaradc_merchant_state=CAdc_merchant_zip=93101dc_merchant_phone=800-601-0230dc_device_id=1174dc_transaction_date=2007-11-28 19:22:33.791634dc_transaction_action=chargedc_approval_code=TAS193dc_response_code=00dc_response_message=APPROVAL TAS193 dc_transaction_id=3144302dc_posture=holddc_invoice_number=9f76c4e4bd66a36dc5aeb4bd7b3a02fadc_notes=--START QUICK-LINK DEBUG-- @@ -109,7 +125,7 @@ def successful_authorization_response dc_transaction_action=charge dc_approval_code=TAS193 dc_response_code=00 -dc_response_message=APPROVAL TAS193 +dc_response_message=APPROVAL TAS193 dc_transaction_id=3144302 dc_posture=hold dc_invoice_number=9f76c4e4bd66a36dc5aeb4bd7b3a02fa @@ -162,7 +178,7 @@ def successful_refund_response dc_transaction_action=charge dc_approval_code=TAS193 dc_response_code=00 -dc_response_message=APPROVAL TAS193 +dc_response_message=APPROVAL TAS193 dc_transaction_id=3144302 dc_posture=hold dc_invoice_number=9f76c4e4bd66a36dc5aeb4bd7b3a02fa @@ -185,7 +201,7 @@ def successful_refund_response dc_card_name=cody fauserdc_card_brand=VSAdc_card_exp=XX/XXdc_card_number=XXXX-XXXX-XXXX-3344dc_card_address=dc_card_city=dc_card_zipcode=dc_card_state=dc_card_country=dc_base_amount=4.00dc_tax_amount=0.00dc_capture_amount=4.00dc_cashback_amount=0.00dc_shipping_amount=0.00 RESPONSE end - + def failed_authorization_response 'dc_merchant_name=dc_merchant_address=dc_merchant_city=dc_merchant_state=dc_merchant_zip=dc_merchant_phone=dc_device_id=dc_transaction_date=dc_transaction_action=dc_approval_code=dc_response_code=FEdc_response_message=dc_number [Input is invalid. The credit card number is bad.], System [error.System], dc_transaction_id=dc_posture=dc_invoice_number=dc_notes=dc_card_name=dc_card_brand=dc_card_exp=dc_card_number=dc_card_address=dc_card_city=dc_card_zipcode=dc_card_state=dc_card_country=dc_base_amount=dc_tax_amount=dc_capture_amount=dc_cashback_amount=dc_shipping_amount=' end diff --git a/test/unit/gateways/pay_junction_v2_test.rb b/test/unit/gateways/pay_junction_v2_test.rb new file mode 100644 index 00000000000..99a40a808ec --- /dev/null +++ b/test/unit/gateways/pay_junction_v2_test.rb @@ -0,0 +1,673 @@ +require 'test_helper' + +class PayJunctionV2Test < Test::Unit::TestCase + def setup + @gateway = PayJunctionV2Gateway.new(api_login: 'api_login', api_password: 'api_password', api_key: 'api_key') + + @amount = 99 + @credit_card = credit_card('4444333322221111', month: 01, year: 2020, verification_value: 999) + @options = { + order_id: generate_unique_id + } + end + + def test_successful_purchase + @gateway.expects(:ssl_post).returns(successful_purchase_response) + + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + + assert_equal 'Approved', response.message + assert response.test? + end + + def test_failed_purchase + @gateway.expects(:ssl_post).returns(failed_purchase_response) + + amount = 5 + response = @gateway.purchase(amount, @credit_card, @options) + assert_failure response + assert_equal 'Declined (do not honor)', response.message + end + + def test_successful_authorize + @gateway.expects(:ssl_post).returns(successful_authorize_response) + + response = @gateway.authorize(@amount, @credit_card, @options) + assert_success response + + assert_equal 'Approved', response.message + assert response.test? + end + + def test_failed_authorize + @gateway.expects(:ssl_post).returns(failed_authorize_response) + + amount = 10 + response = @gateway.authorize(amount, @credit_card, @options) + assert_failure response + assert_equal 'Declined (restricted)', response.message + end + + def test_successful_capture + @gateway.expects(:ssl_request).returns(successful_capture_response) + @gateway.expects(:ssl_post).returns(successful_authorize_response) + + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + assert capture = @gateway.capture(@amount, auth.authorization) + assert_success capture + assert_equal 'Approved', capture.message + end + + def test_failed_capture + raw_response = mock + raw_response.expects(:body).returns(failed_capture_response) + exception = ActiveMerchant::ResponseError.new(raw_response) + + @gateway.expects(:ssl_request).raises(exception) + + response = @gateway.capture(@amount, 'invalid_authorization') + assert_failure response + assert_equal '404 Not Found|', response.message + end + + def test_successful_refund + @gateway.expects(:ssl_post).returns(successful_refund_response) + @gateway.expects(:ssl_post).returns(successful_purchase_response) + + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + + assert refund = @gateway.refund(@amount, purchase.authorization) + assert_success refund + assert_equal 'Approved', refund.message + end + + def test_failed_refund + raw_response = mock + raw_response.expects(:body).returns(failed_refund_response) + exception = ActiveMerchant::ResponseError.new(raw_response) + + @gateway.expects(:ssl_request).raises(exception) + + response = @gateway.refund(@amount, 'invalid_authorization') + assert_failure response + assert_equal '404 Not Found|', response.message + end + + def test_successful_credit + @gateway.expects(:ssl_post).returns(successful_credit_response) + + response = @gateway.credit(@amount, @credit_card, @options) + assert_success response + assert_equal 'Approved', response.message + end + + def test_failed_credit + raw_response = mock + raw_response.expects(:body).returns(failed_credit_response) + exception = ActiveMerchant::ResponseError.new(raw_response) + + @gateway.expects(:ssl_post).raises(exception) + + amount = 0 + response = @gateway.credit(amount, @credit_card, @options) + assert_failure response + assert_equal 'Amount Base must be greater than 0.|', response.message + end + + def test_successful_void + @gateway.expects(:ssl_request).returns(successful_void_response) + @gateway.expects(:ssl_post).returns(successful_authorize_response) + + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + + assert void = @gateway.void(auth.authorization) + assert_success void + assert_equal 'Approved', void.message + end + + def test_failed_void + raw_response = mock + raw_response.expects(:body).returns(failed_void_response) + exception = ActiveMerchant::ResponseError.new(raw_response) + + @gateway.expects(:ssl_request).raises(exception) + + response = @gateway.void('invalid_authorization') + assert_failure response + assert_equal '404 Not Found|', response.message + end + + def test_successful_verify + @gateway.expects(:ssl_request).returns(successful_void_response) + @gateway.expects(:ssl_post).returns(successful_authorize_response) + + response = @gateway.verify(@credit_card, @options) + assert_success response + assert_equal 'Approved', response.message + end + + def test_successful_verify_with_failed_void + raw_response = mock + raw_response.expects(:body).returns(failed_void_response) + exception = ActiveMerchant::ResponseError.new(raw_response) + + @gateway.expects(:ssl_request).raises(exception) + @gateway.expects(:ssl_post).returns(successful_authorize_response) + + response = @gateway.verify(@credit_card, @options) + assert_success response + assert_equal 'Approved', response.message + end + + def test_failed_verify + raw_response = mock + raw_response.expects(:body).returns(failed_verify_response) + exception = ActiveMerchant::ResponseError.new(raw_response) + + @gateway.expects(:ssl_post).raises(exception) + + credit_card = credit_card('444433332222111') + response = @gateway.verify(credit_card, @options) + assert_failure response + assert_match %r{Card Number is not a valid card number}, response.message + end + + def test_successful_store_and_purchase + @gateway.expects(:ssl_post).returns(successful_purchase_response) + @gateway.expects(:ssl_request).returns(successful_void_response) + @gateway.expects(:ssl_post).returns(successful_authorize_response) + + response = @gateway.store(@credit_card, @options) + assert_success response + assert response.authorization + assert_equal 'Approved', response.message + + response = @gateway.purchase(@amount, response.authorization, @options) + assert_success response + assert_equal 'Approved', response.message + end + + def test_failed_store + raw_response = mock + raw_response.expects(:body).returns(failed_store_response) + exception = ActiveMerchant::ResponseError.new(raw_response) + + @gateway.expects(:ssl_post).raises(exception) + + credit_card = credit_card('444433332222111') + response = @gateway.store(credit_card, @options) + assert_failure response + assert_match %r{Card Number is not a valid card number}, response.message + end + + def test_scrub + assert @gateway.supports_scrubbing? + assert_equal @gateway.scrub(pre_scrubbed), post_scrubbed + end + + private + + def pre_scrubbed + %q{ + opening connection to api.payjunctionlabs.com:443... + opened + starting SSL for api.payjunctionlabs.com:443... + SSL established + <- "POST /transactions/ HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded;charset=UTF-8\r\nAuthorization: Basic cGotcWwtMDE6cGotcWwtMDFw\r\nAccept: application/json\r\nX-Pj-Application-Key: c43e89f9-525c-4968-b299-37f7cb1bb1a2\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: api.payjunctionlabs.com\r\nContent-Length: 135\r\n\r\n" + <- "amountBase=0.99&invoiceNumber=b585872cc9cff42dd46b9ba8d31cc90f&cardNumber=4444333322221111&cardExpMonth=01&cardExpYear=2020&cardCvv=999" + -> "HTTP/1.1 200 OK\r\n" + -> "Date: Fri, 19 Aug 2016 17:59:29 GMT\r\n" + -> "Content-Type: application/json\r\n" + -> "Transfer-Encoding: chunked\r\n" + -> "Connection: close\r\n" + -> "Set-Cookie: __cfduid=dc9cabfd2f0cbbf76295f47b8aff448601471629567; expires=Sat, 19-Aug-17 17:59:27 GMT; path=/; domain=.payjunctionlabs.com; HttpOnly\r\n" + -> "X-Frame-Options: SAMEORIGIN\r\n" + -> "X-Pj-Request-Id: 30af86ae-75fb-4ec0-a704-393df6a11fed\r\n" + -> "Vary: User-Agent\r\n" + -> "Server: cloudflare-nginx\r\n" + -> "CF-RAY: 2d4f7fdbf7262fff-MAA\r\n" + -> "Content-Encoding: gzip\r\n" + -> "\r\n" + -> "19e\r\n" + reading 414 bytes... + -> "\x1F\x8B\b\x00\x00\x00\x00\x00\x00\x03\x94R[O\x830\x14~\xDF\xAF <\xEBF\x99s\xB07tx\x8D:'\x9A\xE8\x8B9\x94\xE2j\x80\x92\xF6\x94D\x97\xFDwC\v\x13M|\xF0\xA5I\xBF[\xCF\xA5\xDB\x91\xE3\xB8(\xA1R@\x91\x8B\xEA2s\x9D\x85\x13\xCC\xE7\xD3\x83\x96\xD1\x92\xB7ww\x83X\xAB\xC5d\x025\x1F\xD7\xF0\xF1\xAE+\xA3. Uc*\xCA\xC9 AMZ\xB7k\xEC\xC8d\xC9+(l*1\x98U\x99\xD4\xD3\x8Bh)\x1E[)\x94BWx\x02\x8A\x19\xCA\e\x87\xE1\x90H\x04B\xF1\x8B\xE1U#8e\xB7\xBAL\x994\\:\vf\xC1\xDC\xA74\xA4y~\xE4g\xD9\xD1q\x1A\xA6\x10dSBi\xE8\xE5\xD6W2\xDC\bS\x91(\x1D?\xC7K\x8B*\x04\xD4\xCA\xD6\x15\xAD\x92\xC7uW\x18\x95\f\x90Y\xB9\xEF\x91\xE3C/8$aB\xE6\x8BY\xB8\x98z/VU\x80\xC2\e\x91\xF1\x9C\xFF%\xF5\xC3N*\x99\xAAEe\xFB\xDC\x8E\x1C\xA7\xED\xB1\xAE\xA5h\xAC\x13\xA5f\a\x16\xA6\"\xEB\xA6\xE1\xB9\x1DT2\xA5\xE0\xCD\xA2Q\xEF\xEA\xB8Z\n\xCA\x94\x12\xF2;\xB9\xCD\xD6\xB8\x11\x92\x7F\xFEN\xDF?\v\xC5i\xFF\xCE\xEA\xCA\xF7\xA2\x95\xFB-h\xD40\xEB\xE7\x94n\xEF\x92\xD7u|\xFF\x18?$\xF1\xD2\xED$\xBB\xBD\x976\xCD\x7F\xBD\xA3\xFE\xDC\xD9\x8D0\xC4\x82\x95\xAC\xC2\xC1\xA8,hz\xC9\xA1Pl\xAFn@\x17C!~\xD4\xAC\xDB\xE6z\xD9\x8F\b(5\xBF\xA9\xE7\x9E.\x1F\xA2\x9EkWx&\xB4\xFDI\x84\x10\xD2\xD6\xB5\e\xED\xBE\x00\x00\x00\xFF\xFF\x03\x00\xD2NT\x17#\x03\x00\x00" + read 414 bytes + reading 2 bytes... + -> "\r\n" + read 2 bytes + -> "0\r\n" + -> "\r\n" + Conn close + } + end + + def post_scrubbed + %q{ + opening connection to api.payjunctionlabs.com:443... + opened + starting SSL for api.payjunctionlabs.com:443... + SSL established + <- "POST /transactions/ HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded;charset=UTF-8\r\nAuthorization: Basic [FILTERED]\r\nAccept: application/json\r\nX-Pj-Application-Key: [FILTERED]\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: api.payjunctionlabs.com\r\nContent-Length: 135\r\n\r\n" + <- "amountBase=0.99&invoiceNumber=b585872cc9cff42dd46b9ba8d31cc90f&cardNumber=[FILTERED]&cardExpMonth=01&cardExpYear=2020&cardCvv=[FILTERED]" + -> "HTTP/1.1 200 OK\r\n" + -> "Date: Fri, 19 Aug 2016 17:59:29 GMT\r\n" + -> "Content-Type: application/json\r\n" + -> "Transfer-Encoding: chunked\r\n" + -> "Connection: close\r\n" + -> "Set-Cookie: __cfduid=dc9cabfd2f0cbbf76295f47b8aff448601471629567; expires=Sat, 19-Aug-17 17:59:27 GMT; path=/; domain=.payjunctionlabs.com; HttpOnly\r\n" + -> "X-Frame-Options: SAMEORIGIN\r\n" + -> "X-Pj-Request-Id: 30af86ae-75fb-4ec0-a704-393df6a11fed\r\n" + -> "Vary: User-Agent\r\n" + -> "Server: cloudflare-nginx\r\n" + -> "CF-RAY: 2d4f7fdbf7262fff-MAA\r\n" + -> "Content-Encoding: gzip\r\n" + -> "\r\n" + -> "19e\r\n" + reading 414 bytes... + -> "\x1F\x8B\b\x00\x00\x00\x00\x00\x00\x03\x94R[O\x830\x14~\xDF\xAF <\xEBF\x99s\xB07tx\x8D:'\x9A\xE8\x8B9\x94\xE2j\x80\x92\xF6\x94D\x97\xFDwC\v\x13M|\xF0\xA5I\xBF[\xCF\xA5\xDB\x91\xE3\xB8(\xA1R@\x91\x8B\xEA2s\x9D\x85\x13\xCC\xE7\xD3\x83\x96\xD1\x92\xB7ww\x83X\xAB\xC5d\x025\x1F\xD7\xF0\xF1\xAE+\xA3. Uc*\xCA\xC9 AMZ\xB7k\xEC\xC8d\xC9+(l*1\x98U\x99\xD4\xD3\x8Bh)\x1E[)\x94BWx\x02\x8A\x19\xCA\e\x87\xE1\x90H\x04B\xF1\x8B\xE1U#8e\xB7\xBAL\x994\\:\vf\xC1\xDC\xA74\xA4y~\xE4g\xD9\xD1q\x1A\xA6\x10dSBi\xE8\xE5\xD6W2\xDC\bS\x91(\x1D?\xC7K\x8B*\x04\xD4\xCA\xD6\x15\xAD\x92\xC7uW\x18\x95\f\x90Y\xB9\xEF\x91\xE3C/8$aB\xE6\x8BY\xB8\x98z/VU\x80\xC2\e\x91\xF1\x9C\xFF%\xF5\xC3N*\x99\xAAEe\xFB\xDC\x8E\x1C\xA7\xED\xB1\xAE\xA5h\xAC\x13\xA5f\a\x16\xA6\"\xEB\xA6\xE1\xB9\x1DT2\xA5\xE0\xCD\xA2Q\xEF\xEA\xB8Z\n\xCA\x94\x12\xF2;\xB9\xCD\xD6\xB8\x11\x92\x7F\xFEN\xDF?\v\xC5i\xFF\xCE\xEA\xCA\xF7\xA2\x95\xFB-h\xD40\xEB\xE7\x94n\xEF\x92\xD7u|\xFF\x18?$\xF1\xD2\xED$\xBB\xBD\x976\xCD\x7F\xBD\xA3\xFE\xDC\xD9\x8D0\xC4\x82\x95\xAC\xC2\xC1\xA8,hz\xC9\xA1Pl\xAFn@\x17C!~\xD4\xAC\xDB\xE6z\xD9\x8F\b(5\xBF\xA9\xE7\x9E.\x1F\xA2\x9EkWx&\xB4\xFDI\x84\x10\xD2\xD6\xB5\e\xED\xBE\x00\x00\x00\xFF\xFF\x03\x00\xD2NT\x17#\x03\x00\x00" + read 414 bytes + reading 2 bytes... + -> "\r\n" + read 2 bytes + -> "0\r\n" + -> "\r\n" + Conn close + } + end + + def successful_purchase_response + %( + { + "transactionId" : 9275, + "uri" : "https://api.payjunctionlabs.com/transactions/9275", + "terminalId" : 1, + "action" : "CHARGE", + "amountBase" : "0.99", + "amountTotal" : "0.99", + "invoiceNumber" : "6ee793bd25121812ab7a230c5845983f", + "method" : "KEYED", + "status" : "CAPTURE", + "created" : "2016-08-22T18:12:10Z", + "lastModified" : "2016-08-22T18:12:09Z", + "response" : { + "approved" : true, + "code" : "00", + "message" : "Approved", + "processor" : { + "authorized" : true, + "approvalCode" : "PJ20AP", + "avs" : { + "status" : "NOT_REQUESTED" + }, + "cvv" : { + "status" : "NOT_REQUESTED" + } + } + }, + "settlement" : { + "settled" : false + }, + "vault" : { + "type" : "CARD", + "accountType" : "VISA", + "lastFour" : "1111" + } + } + ) + end + + def failed_purchase_response + %( + { + "transactionId" : 9277, + "uri" : "https://api.payjunctionlabs.com/transactions/9277", + "terminalId" : 1, + "action" : "CHARGE", + "amountBase" : "0.05", + "amountTotal" : "0.05", + "invoiceNumber" : "99de11a3873042a8e75ba8b35ab71d84", + "method" : "KEYED", + "status" : "DECLINED", + "created" : "2016-08-22T18:15:24Z", + "lastModified" : "2016-08-22T18:15:24Z", + "response" : { + "approved" : false, + "code" : "05", + "message" : "Declined (do not honor)", + "processor" : { + "authorized" : false, + "avs" : { + "status" : "NOT_REQUESTED" + }, + "cvv" : { + "status" : "NOT_REQUESTED" + } + } + }, + "settlement" : { + "settled" : false + }, + "vault" : { + "type" : "CARD", + "accountType" : "VISA", + "lastFour" : "1111" + } + } + ) + end + + def successful_authorize_response + %( + { + "transactionId" : 9275, + "uri" : "https://api.payjunctionlabs.com/transactions/9275", + "terminalId" : 1, + "action" : "CHARGE", + "amountBase" : "0.99", + "amountTotal" : "0.99", + "invoiceNumber" : "6ee793bd25121812ab7a230c5845983f", + "method" : "KEYED", + "status" : "HOLD", + "created" : "2016-08-22T18:12:10Z", + "lastModified" : "2016-08-22T18:12:09Z", + "response" : { + "approved" : true, + "code" : "00", + "message" : "Approved", + "processor" : { + "authorized" : true, + "approvalCode" : "PJ20AP", + "avs" : { + "status" : "NOT_REQUESTED" + }, + "cvv" : { + "status" : "NOT_REQUESTED" + } + } + }, + "settlement" : { + "settled" : false + }, + "vault" : { + "type" : "CARD", + "accountType" : "VISA", + "lastFour" : "1111" + } + } + ) + end + + def failed_authorize_response + %( + { + "transactionId" : 9277, + "uri" : "https://api.payjunctionlabs.com/transactions/9277", + "terminalId" : 1, + "action" : "CHARGE", + "amountBase" : "0.10", + "amountTotal" : "0.10", + "invoiceNumber" : "99de11a3873042a8e75ba8b35ab71d84", + "method" : "KEYED", + "status" : "DECLINED", + "created" : "2016-08-22T18:15:24Z", + "lastModified" : "2016-08-22T18:15:24Z", + "response" : { + "approved" : false, + "code" : "05", + "message" : "Declined (restricted)", + "processor" : { + "authorized" : false, + "avs" : { + "status" : "NOT_REQUESTED" + }, + "cvv" : { + "status" : "NOT_REQUESTED" + } + } + }, + "settlement" : { + "settled" : false + }, + "vault" : { + "type" : "CARD", + "accountType" : "VISA", + "lastFour" : "1111" + } + } + ) + end + + def successful_capture_response + %( + { + "transactionId" : 9281, + "uri" : "https://api.payjunctionlabs.com/transactions/9281", + "terminalId" : 1, + "action" : "CHARGE", + "amountBase" : "0.99", + "amountTotal" : "0.99", + "invoiceNumber" : "d3924cd19b50f5b5f10ae37f6b794dc4", + "method" : "KEYED", + "status" : "CAPTURE", + "created" : "2016-08-22T18:29:23Z", + "lastModified" : "2016-08-22T18:29:24Z", + "response" : { + "approved" : true, + "code" : "00", + "message" : "Approved", + "processor" : { + "authorized" : true, + "approvalCode" : "PJ20AP", + "avs" : { + "status" : "NOT_REQUESTED" + }, + "cvv" : { + "status" : "NOT_REQUESTED" + } + } + }, + "settlement" : { + "settled" : false + }, + "vault" : { + "type" : "CARD", + "accountType" : "VISA", + "lastFour" : "1111" + } + } + ) + end + + def failed_capture_response + %( + { + "errors" : [ { + "message" : "404 Not Found" + } ] + } + ) + end + + def successful_refund_response + %( + { + "transactionId" : 9283, + "uri" : "https://api.payjunctionlabs.com/transactions/9283", + "terminalId" : 1, + "action" : "CHARGE", + "amountBase" : "0.99", + "amountTotal" : "0.99", + "invoiceNumber" : "1d13a750593375c7dc4e8c03987f4990", + "method" : "KEYED", + "status" : "VOID", + "created" : "2016-08-22T18:37:25Z", + "lastModified" : "2016-08-22T18:37:27Z", + "response" : { + "approved" : true, + "code" : "00", + "message" : "Approved", + "processor" : { + "authorized" : true, + "approvalCode" : "PJ20AP", + "avs" : { + "status" : "NOT_REQUESTED" + }, + "cvv" : { + "status" : "NOT_REQUESTED" + } + } + }, + "settlement" : { + "settled" : false + }, + "vault" : { + "type" : "CARD", + "accountType" : "VISA", + "lastFour" : "1111" + } + } + ) + end + + def failed_refund_response + %( + { + "errors" : [ { + "message" : "404 Not Found" + } ] + } + ) + end + + def successful_credit_response + %( + { + "transactionId" : 9285, + "uri" : "https://api.payjunctionlabs.com/transactions/9285", + "terminalId" : 1, + "action" : "REFUND", + "amountBase" : "0.99", + "amountTotal" : "0.99", + "invoiceNumber" : "9f1cec3b0b4375ef866a9ba90b277dcb", + "method" : "KEYED", + "status" : "CAPTURE", + "created" : "2016-08-22T18:42:30Z", + "lastModified" : "2016-08-22T18:42:29Z", + "response" : { + "approved" : true, + "code" : "00", + "message" : "Approved", + "processor" : { + "authorized" : false, + "avs" : { + "status" : "NOT_REQUESTED" + }, + "cvv" : { + "status" : "NOT_REQUESTED" + } + } + }, + "settlement" : { + "settled" : false + }, + "vault" : { + "type" : "CARD", + "accountType" : "VISA", + "lastFour" : "1111" + } + } + ) + end + + def failed_credit_response + %( + { + "errors" : [ { + "message" : "Amount Base must be greater than 0.", + "parameter" : "amountBase", + "type" : "invalid" + } ] + } + ) + end + + def successful_void_response + %( + { + "transactionId" : 9287, + "uri" : "https://api.payjunctionlabs.com/transactions/9287", + "terminalId" : 1, + "action" : "CHARGE", + "amountBase" : "0.99", + "amountTotal" : "0.99", + "invoiceNumber" : "d28be42ad1c89b0d75b1164cc6a0a12d", + "method" : "KEYED", + "status" : "VOID", + "created" : "2016-08-22T18:47:33Z", + "lastModified" : "2016-08-22T18:47:34Z", + "response" : { + "approved" : true, + "code" : "00", + "message" : "Approved", + "processor" : { + "authorized" : true, + "approvalCode" : "PJ20AP", + "avs" : { + "status" : "NOT_REQUESTED" + }, + "cvv" : { + "status" : "NOT_REQUESTED" + } + } + }, + "settlement" : { + "settled" : false + }, + "vault" : { + "type" : "CARD", + "accountType" : "VISA", + "lastFour" : "1111" + } + } + ) + end + + def failed_void_response + %( + { + "errors" : [ { + "message" : "404 Not Found" + } ] + } + ) + end + + def failed_verify_response + %( + { + "errors" : [ { + "message" : "Card Number is not a valid card number.", + "parameter" : "cardNumber", + "type" : "invalid" + } ] + } + ) + end + + def failed_store_response + %( + { + "errors" : [ { + "message" : "Card Number is not a valid card number.", + "parameter" : "cardNumber", + "type" : "invalid" + } ] + } + ) + end +end diff --git a/test/unit/gateways/pay_secure_test.rb b/test/unit/gateways/pay_secure_test.rb index 78ea5b7b56f..a49bf1a60f0 100644 --- a/test/unit/gateways/pay_secure_test.rb +++ b/test/unit/gateways/pay_secure_test.rb @@ -1,7 +1,7 @@ require 'test_helper' class PaySecureTest < Test::Unit::TestCase - + def setup @gateway = PaySecureGateway.new( :login => 'login', @@ -9,14 +9,14 @@ def setup ) @credit_card = credit_card - @options = { + @options = { :order_id => '1000', :billing_address => address, :description => 'Test purchase' } @amount = 100 end - + def test_successful_purchase @gateway.expects(:ssl_post).returns(successful_purchase_response) assert response = @gateway.purchase(@amount, @credit_card, @options) @@ -25,7 +25,7 @@ def test_successful_purchase assert_equal '2778;SimProxy 54041670', response.authorization assert response.test? end - + def test_failed_purchase @gateway.expects(:ssl_post).returns(failure_response) assert response = @gateway.purchase(@amount, @credit_card, @options) @@ -33,22 +33,23 @@ def test_failed_purchase assert_equal "Field value '8f796cb29a1be32af5ce12d4ca7425c2' does not match required format.", response.message assert_failure response end - + def test_avs_result_not_supported @gateway.expects(:ssl_post).returns(successful_purchase_response) - - response = @gateway.purchase(@amount, @credit_card, @options) + + response = @gateway.purchase(@amount, @credit_card, @options) assert_nil response.avs_result['code'] end - + def test_cvv_result_not_supported @gateway.expects(:ssl_post).returns(successful_purchase_response) - + response = @gateway.purchase(@amount, @credit_card, @options) assert_nil response.cvv_result['code'] end - + private + def successful_purchase_response <<-RESPONSE Status: Accepted @@ -60,7 +61,7 @@ def successful_purchase_response TransID: SimProxy 54041670 RESPONSE end - + def failure_response <<-RESPONSE Status: Declined diff --git a/test/unit/gateways/paybox_direct_test.rb b/test/unit/gateways/paybox_direct_test.rb index 60740af2386..e6e29d1ad93 100644 --- a/test/unit/gateways/paybox_direct_test.rb +++ b/test/unit/gateways/paybox_direct_test.rb @@ -10,57 +10,79 @@ def setup ) @credit_card = credit_card('1111222233334444', - :brand => 'visa' - ) + :brand => 'visa' + ) @amount = 100 - - @options = { + + @options = { :order_id => '1', :billing_address => address, :description => 'Store Purchase' } end - + def test_successful_purchase @gateway.expects(:ssl_post).returns(purchase_response) - + assert response = @gateway.purchase(@amount, @credit_card, @options) assert_instance_of Response, response assert_success response - + # Replace with authorization number from the successful response assert_equal response.params['numappel'].to_s + response.params['numtrans'], response.authorization assert_equal 'XXXXXX', response.params['autorisation'] - assert_equal "The transaction was approved", response.message + assert_equal 'The transaction was approved', response.message assert response.test? end + def test_purchase_with_default_currency + @gateway.expects(:ssl_post).with do |_, body| + body.include?('DEVISE=978') + end.returns(purchase_response) + + @gateway.purchase(@amount, @credit_card, @options) + end + + def test_purchase_with_set_currency + @options.update(currency: 'GBP') + + @gateway.expects(:ssl_post).with do |_, body| + body.include?('DEVISE=826') + end.returns(purchase_response) + + @gateway.purchase(@amount, @credit_card, @options) + end + def test_deprecated_credit - @gateway.expects(:ssl_post).with(anything, regexp_matches(/NUMAPPEL=transid/), anything).returns("") + @gateway.expects(:ssl_post).with(anything, regexp_matches(/NUMAPPEL=transid/), anything).returns('') @gateway.expects(:parse).returns({}) - assert_deprecation_warning(Gateway::CREDIT_DEPRECATION_MESSAGE, @gateway) do - @gateway.credit(@amount, "transid", @options) + assert_deprecation_warning(Gateway::CREDIT_DEPRECATION_MESSAGE) do + @gateway.credit(@amount, 'transid', @options) end end - + def test_refund - @gateway.expects(:ssl_post).with(anything, regexp_matches(/NUMAPPEL=transid/), anything).returns("") + @gateway.expects(:ssl_post).with(anything) do |_, body| + body.include?('NUMAPPEL=transid') + body.include?('MONTANT=0000000100&DEVISE=97') + end.returns('') + @gateway.expects(:parse).returns({}) - @gateway.refund(@amount, "transid", @options) + @gateway.refund(@amount, 'transid', @options) end def test_unsuccessful_request @gateway.expects(:ssl_post).returns(failed_purchase_response) - + assert response = @gateway.purchase(@amount, @credit_card, @options) assert_failure response - assert_equal "Demande trait?e avec succ?s ✔漢", response.message + assert_equal 'Demande trait?e avec succ?s ✔漢', response.message assert response.test? end def test_keep_the_card_code_not_considered_fraudulent - @gateway.expects(:ssl_post).returns(purchase_response("00104")) + @gateway.expects(:ssl_post).returns(purchase_response('00104')) assert response = @gateway.purchase(@amount, @credit_card, @options) assert_failure response @@ -68,7 +90,7 @@ def test_keep_the_card_code_not_considered_fraudulent end def test_do_not_honour_code_not_considered_fraudulent - @gateway.expects(:ssl_post).returns(purchase_response("00105")) + @gateway.expects(:ssl_post).returns(purchase_response('00105')) assert response = @gateway.purchase(@amount, @credit_card, @options) assert_failure response @@ -76,7 +98,7 @@ def test_do_not_honour_code_not_considered_fraudulent end def test_card_absent_from_file_code_not_considered_fraudulent - @gateway.expects(:ssl_post).returns(purchase_response("00156")) + @gateway.expects(:ssl_post).returns(purchase_response('00156')) assert response = @gateway.purchase(@amount, @credit_card, @options) assert_failure response @@ -87,11 +109,11 @@ def test_version @gateway.expects(:ssl_post).with(anything, regexp_matches(/VERSION=00103/)).returns(purchase_response) @gateway.purchase(@amount, @credit_card, @options) end - + private - + # Place raw successful response from gateway here - def purchase_response(code="00000") + def purchase_response(code='00000') "NUMTRANS=0720248861&NUMAPPEL=0713790302&NUMQUESTION=0000790217&SITE=1999888&RANG=99&AUTORISATION=XXXXXX&CODEREPONSE=#{code}&COMMENTAIRE=Demande trait?e avec succ?s ✔漢" end diff --git a/test/unit/gateways/payeezy_test.rb b/test/unit/gateways/payeezy_test.rb new file mode 100644 index 00000000000..abc9e1e0131 --- /dev/null +++ b/test/unit/gateways/payeezy_test.rb @@ -0,0 +1,803 @@ +require 'test_helper' +require 'yaml' + +class PayeezyGateway < Test::Unit::TestCase + include CommStub + + def setup + @gateway = PayeezyGateway.new(fixtures(:payeezy)) + + @credit_card = credit_card + @bad_credit_card = credit_card('4111111111111113') + @check = check + @amount = 100 + @options = { + :billing_address => address, + :ta_token => '123' + } + @options_stored_credentials = { + cardbrand_original_transaction_id: 'abc123', + sequence: 'FIRST', + is_scheduled: true, + initiator: 'MERCHANT', + auth_type_override: 'A' + } + @authorization = 'ET1700|106625152|credit_card|4738' + @reversal_id = SecureRandom.random_number(1000000).to_s + end + + def test_invalid_credentials + @gateway.expects(:ssl_post).raises(bad_credentials_response) + + assert response = @gateway.authorize(100, @credit_card, {}) + assert_failure response + assert response.test? + assert response.authorization + assert_equal 'HMAC validation Failure', response.message + end + + def test_invalid_token + @gateway.expects(:ssl_post).raises(invalid_token_response) + + assert response = @gateway.authorize(100, @credit_card, {}) + assert_failure response + assert response.test? + assert response.authorization + assert_equal 'Access denied', response.message + end + + def test_invalid_token_on_integration + @gateway.expects(:ssl_post).raises(invalid_token_response_integration) + + assert response = @gateway.authorize(100, @credit_card, {}) + assert_failure response + assert response.test? + assert response.authorization + assert_equal 'Invalid ApiKey for given resource', response.message + end + + def test_successful_purchase + @gateway.expects(:ssl_post).returns(successful_purchase_response) + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal 'ET114541|55083431|credit_card|1', response.authorization + assert response.test? + assert_equal 'Transaction Normal - Approved', response.message + end + + def test_successful_store + response = stub_comms(@gateway, :ssl_request) do + @gateway.store(@credit_card, @options.merge(js_security_key: 'js-f4c4b54f08d6c44c8cad3ea80bbf92c4f4c4b54f08d6c44c')) + end.respond_with(successful_store_response) + + assert_success response + assert_equal 'Token successfully created.', response.message + assert response.test? + end + + def test_successful_store_and_purchase + response = stub_comms(@gateway, :ssl_request) do + @gateway.store(@credit_card, @options.merge(js_security_key: 'js-f4c4b54f08d6c44c8cad3ea80bbf92c4f4c4b54f08d6c44c')) + end.respond_with(successful_store_response) + + assert_success response + assert_match %r{Token successfully created}, response.message + + @gateway.expects(:ssl_post).returns(successful_purchase_response) + + purchase = @gateway.purchase(@amount, response.authorization, @options) + assert_success purchase + end + + def test_failed_store + response = stub_comms(@gateway, :ssl_request) do + @gateway.store(@bad_credit_card, @options.merge(js_security_key: 'js-f4c4b54f08d6c44c8cad3ea80bbf92c4f4c4b54f08d6c44c')) + end.respond_with(failed_store_response) + + assert_failure response + assert_equal 'The credit card number check failed', response.message + assert response.test? + end + + def test_successful_purchase_with_echeck + @gateway.expects(:ssl_post).returns(successful_purchase_echeck_response) + assert response = @gateway.purchase(@amount, @check, @options) + assert_success response + assert_equal 'ET133078|69864362|tele_check|100', response.authorization + assert response.test? + assert_equal 'Transaction Normal - Approved', response.message + end + + def test_successful_purchase_defaulting_check_number + check_without_number = check(number: nil) + + response = stub_comms do + @gateway.purchase(@amount, check_without_number, @options) + end.check_request do |endpoint, data, headers| + assert_match(/001/, data) + end.respond_with(successful_purchase_echeck_response) + + assert_success response + assert_equal 'ET133078|69864362|tele_check|100', response.authorization + assert response.test? + assert_equal 'Transaction Normal - Approved', response.message + end + + def test_successful_purchase_with_stored_credentials + response = stub_comms do + @gateway.purchase(@amount, @credit_card, @options.merge(@options_stored_credentials)) + end.check_request do |endpoint, data, headers| + assert_match(/stored_credentials/, data) + end.respond_with(successful_purchase_stored_credentials_response) + + assert_success response + assert response.test? + assert_equal 'Transaction Normal - Approved', response.message + end + + def test_failed_purchase + @gateway.expects(:ssl_post).raises(failed_purchase_response) + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_instance_of Response, response + assert_failure response + assert_equal response.error_code, 'card_expired' + end + + def test_successful_authorize + @gateway.expects(:ssl_post).returns(successful_authorize_response) + assert response = @gateway.authorize(@amount, @credit_card, @options) + assert_success response + assert_equal 'ET156862|69601979|credit_card|100', response.authorization + assert response.test? + assert_equal 'Transaction Normal - Approved', response.message + end + + def test_failed_authorize + @gateway.expects(:ssl_post).returns(failed_authorize_response) + assert response = @gateway.authorize(@amount, @credit_card, @options) + assert_instance_of Response, response + assert_failure response + end + + def test_successful_capture + @gateway.expects(:ssl_post).returns(successful_capture_response) + assert response = @gateway.capture(@amount, 'ET156862|69601979|credit_card|100') + assert_success response + assert_equal 'ET176427|69601874|credit_card|100', response.authorization + assert response.test? + assert_equal 'Transaction Normal - Approved', response.message + end + + def test_failed_capture + @gateway.expects(:ssl_post).raises(failed_capture_response) + assert response = @gateway.capture(@amount, '') + assert_instance_of Response, response + assert_failure response + end + + def test_successful_refund + @gateway.expects(:ssl_post).returns(successful_refund_response) + assert response = @gateway.refund(@amount, @authorization) + assert_success response + end + + def test_successful_refund_with_echeck + @gateway.expects(:ssl_post).returns(successful_refund_echeck_response) + assert response = @gateway.refund(@amount, @authorization) + assert_success response + end + + def test_failed_refund + @gateway.expects(:ssl_post).raises(failed_refund_response) + assert response = @gateway.refund(@amount, @authorization) + assert_failure response + end + + def test_successful_void + response = stub_comms do + @gateway.void(@authorization, @options) + end.check_request do |endpoint, data, headers| + json = '{"transaction_type":"void","method":"credit_card","transaction_tag":"106625152","currency_code":"USD","amount":"4738"}' + assert_match json, data + end.respond_with(successful_void_response) + + assert_success response + end + + def test_successful_void_with_reversal_id + stub_comms do + @gateway.void(@authorization, @options.merge(reversal_id: @reversal_id)) + end.check_request do |endpoint, data, headers| + json = "{\"transaction_type\":\"void\",\"method\":\"credit_card\",\"reversal_id\":\"#{@reversal_id}\",\"currency_code\":\"USD\",\"amount\":\"4738\"}" + assert_match json, data + end.respond_with(successful_void_response) + end + + def test_failed_void + @gateway.expects(:ssl_post).raises(failed_void_response) + assert response = @gateway.void(@authorization, @options) + assert_failure response + end + + def test_successful_verify + @gateway.expects(:ssl_post).times(2).returns(successful_authorize_response, successful_void_response) + + response = @gateway.verify(@credit_card, @options) + assert_success response + end + + def test_failed_verify + @gateway.expects(:ssl_post).returns(failed_authorize_response) + + response = @gateway.verify(@credit_card, @options) + assert_failure response + end + + def test_invalid_transaction_tag + @gateway.expects(:ssl_post).raises(failed_capture_response) + + assert response = @gateway.capture(@amount, @authorization) + assert_instance_of Response, response + assert_failure response + assert_equal response.error_code, 'server_error' + assert_equal response.message, 'ProcessedBad Request (69) - Invalid Transaction Tag' + end + + def test_supported_countries + assert_equal ['CA', 'US'].sort, PayeezyGateway.supported_countries.sort + end + + def test_supported_cardtypes + assert_equal [:visa, :master, :american_express, :discover, :jcb, :diners_club], PayeezyGateway.supported_cardtypes + end + + def test_avs_result + @gateway.expects(:ssl_post).returns(successful_purchase_response) + + response = @gateway.purchase(@amount, @credit_card) + assert_equal '4', response.avs_result['code'] + end + + def test_cvv_result + @gateway.expects(:ssl_post).returns(successful_void_response) + + response = @gateway.purchase(@amount, @credit_card) + assert_equal 'I', response.cvv_result['code'] + end + + def test_requests_include_verification_string + stub_comms do + @gateway.purchase(@amount, @credit_card, @options) + end.check_request do |endpoint, data, headers| + json_address = '{"street":"456 My Street","city":"Ottawa","state_province":"ON","zip_postal_code":"K1C2N6","country":"CA"}' + assert_match json_address, data + end.respond_with(successful_purchase_response) + end + + def test_gateway_message_surfaces + @gateway.expects(:ssl_post).returns(below_minimum_response) + assert response = @gateway.authorize(@amount, @credit_card, @options) + assert_failure response + assert_equal 'Below Minimum Sale', response.message + end + + def test_card_type + assert_equal 'Visa', PayeezyGateway::CREDIT_CARD_BRAND['visa'] + assert_equal 'Mastercard', PayeezyGateway::CREDIT_CARD_BRAND['master'] + assert_equal 'American Express', PayeezyGateway::CREDIT_CARD_BRAND['american_express'] + assert_equal 'JCB', PayeezyGateway::CREDIT_CARD_BRAND['jcb'] + assert_equal 'Discover', PayeezyGateway::CREDIT_CARD_BRAND['discover'] + end + + def test_scrub + assert @gateway.supports_scrubbing? + assert_equal @gateway.scrub(pre_scrubbed), post_scrubbed + end + + def test_scrub_store + assert_equal @gateway.scrub(pre_scrubbed_store), post_scrubbed_store + end + + def test_scrub_echeck + assert @gateway.supports_scrubbing? + assert_equal @gateway.scrub(pre_scrubbed_echeck), post_scrubbed_echeck + end + + private + + def pre_scrubbed + <<-TRANSCRIPT + opening connection to api-cert.payeezy.com:443... + opened + starting SSL for api-cert.payeezy.com:443... + SSL established + <- "POST /v1/transactions HTTP/1.1\r\nContent-Type: application/json\r\nApikey: oKB61AAxbN3xwC6gVAH3dp58FmioHSAT\r\nToken: fdoa-a480ce8951daa73262734cf102641994c1e55e7cdf4c02b6\r\nNonce: 5803993876.636232\r\nTimestamp: 1449523748359\r\nAuthorization: NGRlZjJkMWNlMDc5NGI5OTVlYTQxZDRkOGQ4NjRhNmZhNDgwZmIyNTZkMWJhN2M3MDdkNDI0ZWI1OGUwMGExMA==\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: api-cert.payeezy.com\r\nContent-Length: 365\r\n\r\n" + <- "{\"transaction_type\":\"purchase\",\"merchant_ref\":null,\"method\":\"credit_card\",\"credit_card\":{\"type\":\"Visa\",\"cardholder_name\":\"Longbob Longsen\",\"card_number\":\"4242424242424242\",\"exp_date\":\"0916\",\"cvv\":\"123\"},\"billing_address\":{\"street\":\"456 My Street\",\"city\":\"Ottawa\",\"state_province\":\"ON\",\"zip_postal_code\":\"K1C2N6\",\"country\":\"CA\"},\"currency_code\":\"USD\",\"amount\":\"100\"}" + -> "HTTP/1.1 201 Created\r\n" + -> "Access-Control-Allow-Headers: Content-Type, apikey, token\r\n" + -> "Access-Control-Allow-Methods: GET, PUT, POST, DELETE\r\n" + -> "Access-Control-Allow-Origin: http://localhost:8080\r\n" + -> "Access-Control-Max-Age: 3628800\r\n" + -> "Access-Control-Request-Headers: origin, x-requested-with, accept, content-type\r\n" + -> "Content-Language: en-US\r\n" + -> "Content-Type: application/json;charset=UTF-8\r\n" + -> "Date: Mon, 07 Dec 2015 21:29:08 GMT\r\n" + -> "OPTR_CXT: 0100010000e4b64c5c-53c6-4f8b-aab6-b7950e2a40c100000000-0000-0000-0000-000000000000-1 HTTP ;\r\n" + -> "Server: Apigee Router\r\n" + -> "X-Archived-Client-IP: 10.180.205.250\r\n" + -> "X-Backside-Transport: OK OK,OK OK\r\n" + -> "X-Client-IP: 10.180.205.250,54.236.202.5\r\n" + -> "X-Global-Transaction-ID: 74768541\r\n" + -> "X-Powered-By: Servlet/3.0\r\n" + -> "Content-Length: 549\r\n" + -> "Connection: Close\r\n" + -> "\r\n" + reading 549 bytes... + -> "{\"correlation_id\":\"228.1449523748595\",\"transaction_status\":\"approved\",\"validation_status\":\"success\",\"transaction_type\":\"purchase\",\"transaction_id\":\"ET189831\",\"transaction_tag\":\"69607700\",\"method\":\"credit_card\",\"amount\":\"100\",\"currency\":\"USD\",\"avs\":\"4\",\"cvv2\":\"M\",\"token\":{\"token_type\":\"FDToken\",\"token_data\":{\"value\":\"1950935021264242\"}},\"card\":{\"type\":\"Visa\",\"cardholder_name\":\"Longbob Longsen\",\"card_number\":\"4242\",\"exp_date\":\"0916\"},\"bank_resp_code\":\"100\",\"bank_message\":\"Approved\",\"gateway_resp_code\":\"00\",\"gateway_message\":\"Transaction Normal\"}" + read 549 bytes + Conn close + TRANSCRIPT + end + + def post_scrubbed + <<-TRANSCRIPT + opening connection to api-cert.payeezy.com:443... + opened + starting SSL for api-cert.payeezy.com:443... + SSL established + <- "POST /v1/transactions HTTP/1.1\r\nContent-Type: application/json\r\nApikey: [FILTERED]\r\nToken: [FILTERED]\r\nNonce: 5803993876.636232\r\nTimestamp: 1449523748359\r\nAuthorization: NGRlZjJkMWNlMDc5NGI5OTVlYTQxZDRkOGQ4NjRhNmZhNDgwZmIyNTZkMWJhN2M3MDdkNDI0ZWI1OGUwMGExMA==\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: api-cert.payeezy.com\r\nContent-Length: 365\r\n\r\n" + <- "{\"transaction_type\":\"purchase\",\"merchant_ref\":null,\"method\":\"credit_card\",\"credit_card\":{\"type\":\"Visa\",\"cardholder_name\":\"Longbob Longsen\",\"card_number\":\"[FILTERED]\",\"exp_date\":\"0916\",\"cvv\":\"[FILTERED]\"},\"billing_address\":{\"street\":\"456 My Street\",\"city\":\"Ottawa\",\"state_province\":\"ON\",\"zip_postal_code\":\"K1C2N6\",\"country\":\"CA\"},\"currency_code\":\"USD\",\"amount\":\"100\"}" + -> "HTTP/1.1 201 Created\r\n" + -> "Access-Control-Allow-Headers: Content-Type, apikey, token\r\n" + -> "Access-Control-Allow-Methods: GET, PUT, POST, DELETE\r\n" + -> "Access-Control-Allow-Origin: http://localhost:8080\r\n" + -> "Access-Control-Max-Age: 3628800\r\n" + -> "Access-Control-Request-Headers: origin, x-requested-with, accept, content-type\r\n" + -> "Content-Language: en-US\r\n" + -> "Content-Type: application/json;charset=UTF-8\r\n" + -> "Date: Mon, 07 Dec 2015 21:29:08 GMT\r\n" + -> "OPTR_CXT: 0100010000e4b64c5c-53c6-4f8b-aab6-b7950e2a40c100000000-0000-0000-0000-000000000000-1 HTTP ;\r\n" + -> "Server: Apigee Router\r\n" + -> "X-Archived-Client-IP: 10.180.205.250\r\n" + -> "X-Backside-Transport: OK OK,OK OK\r\n" + -> "X-Client-IP: 10.180.205.250,54.236.202.5\r\n" + -> "X-Global-Transaction-ID: 74768541\r\n" + -> "X-Powered-By: Servlet/3.0\r\n" + -> "Content-Length: 549\r\n" + -> "Connection: Close\r\n" + -> "\r\n" + reading 549 bytes... + -> "{\"correlation_id\":\"228.1449523748595\",\"transaction_status\":\"approved\",\"validation_status\":\"success\",\"transaction_type\":\"purchase\",\"transaction_id\":\"ET189831\",\"transaction_tag\":\"69607700\",\"method\":\"credit_card\",\"amount\":\"100\",\"currency\":\"USD\",\"avs\":\"4\",\"cvv2\":\"M\",\"token\":{\"token_type\":\"FDToken\",\"token_data\":{\"value\":\"1950935021264242\"}},\"card\":{\"type\":\"Visa\",\"cardholder_name\":\"Longbob Longsen\",\"card_number\":\"[FILTERED]\",\"exp_date\":\"0916\"},\"bank_resp_code\":\"100\",\"bank_message\":\"Approved\",\"gateway_resp_code\":\"00\",\"gateway_message\":\"Transaction Normal\"}" + read 549 bytes + Conn close + TRANSCRIPT + end + + def pre_scrubbed_echeck + <<-TRANSCRIPT + {\"transaction_type\":\"purchase\",\"merchant_ref\":null,\"method\":\"tele_check\",\"tele_check\":{\"check_number\":\"1\",\"check_type\":\"P\",\"routing_number\":\"244183602\",\"account_number\":\"15378535\",\"accountholder_name\":\"Jim Smith\"},\"billing_address\":{\"street\":\"456 My Street\",\"city\":\"Ottawa\",\"state_province\":\"ON\",\"zip_postal_code\":\"K1C2N6\",\"country\":\"CA\"},\"currency_code\":\"USD\",\"amount\":\"100\"}" + -> "HTTP/1.1 201 Created\r\n" + -> "Access-Control-Allow-Headers: Content-Type, apikey, token\r\n" + -> "Access-Control-Allow-Methods: GET, PUT, POST, DELETE\r\n" + -> "Access-Control-Allow-Origin: http://localhost:8080\r\n" + -> "Access-Control-Max-Age: 3628800\r\n" + -> "Access-Control-Request-Headers: origin, x-requested-with, accept, content-type\r\n" + -> "Content-Language: en-US\r\n" + -> "Content-Type: application/json;charset=UTF-8\r\n" + -> "Date: Wed, 09 Dec 2015 19:33:14 GMT\r\n" + -> "OPTR_CXT: 0100010000094b4179-bed8-4068-b077-d8679a20046f00000000-0000-0000-0000-000000000000-1 HTTP ;\r\n" + -> "Server: Apigee Router\r\n" + -> "X-Archived-Client-IP: 10.180.205.250\r\n" + -> "X-Backside-Transport: OK OK,OK OK\r\n" + -> "X-Client-IP: 10.180.205.250,107.23.55.229\r\n" + -> "X-Global-Transaction-ID: 97138449\r\n" + -> "X-Powered-By: Servlet/3.0\r\n" + -> "Content-Length: 491\r\n" + -> "Connection: Close\r\n" + -> "\r\n" + reading 491 bytes... + -> "{\"correlation_id\":\"228.1449689594381\",\"transaction_status\":\"approved\",\"validation_status\":\"success\",\"transaction_type\":\"purchase\",\"transaction_id\":\"ET196703\",\"transaction_tag\":\"69865571\",\"method\":\"tele_check\",\"amount\":\"100\",\"currency\":\"USD\",\"bank_resp_code\":\"100\",\"bank_message\":\"Approved\",\"gateway_resp_code\":\"00\",\"gateway_message\":\"Transaction Normal\",\"tele_check\":{\"accountholder_name\":\"Jim Smith\",\"check_number\":\"1\",\"check_type\":\"P\",\"account_number\":\"8535\",\"routing_number\":\"244183602\"}} + TRANSCRIPT + end + + def post_scrubbed_echeck + <<-TRANSCRIPT + {\"transaction_type\":\"purchase\",\"merchant_ref\":null,\"method\":\"tele_check\",\"tele_check\":{\"check_number\":\"1\",\"check_type\":\"P\",\"routing_number\":\"[FILTERED]\",\"account_number\":\"[FILTERED]\",\"accountholder_name\":\"Jim Smith\"},\"billing_address\":{\"street\":\"456 My Street\",\"city\":\"Ottawa\",\"state_province\":\"ON\",\"zip_postal_code\":\"K1C2N6\",\"country\":\"CA\"},\"currency_code\":\"USD\",\"amount\":\"100\"}" + -> "HTTP/1.1 201 Created\r\n" + -> "Access-Control-Allow-Headers: Content-Type, apikey, token\r\n" + -> "Access-Control-Allow-Methods: GET, PUT, POST, DELETE\r\n" + -> "Access-Control-Allow-Origin: http://localhost:8080\r\n" + -> "Access-Control-Max-Age: 3628800\r\n" + -> "Access-Control-Request-Headers: origin, x-requested-with, accept, content-type\r\n" + -> "Content-Language: en-US\r\n" + -> "Content-Type: application/json;charset=UTF-8\r\n" + -> "Date: Wed, 09 Dec 2015 19:33:14 GMT\r\n" + -> "OPTR_CXT: 0100010000094b4179-bed8-4068-b077-d8679a20046f00000000-0000-0000-0000-000000000000-1 HTTP ;\r\n" + -> "Server: Apigee Router\r\n" + -> "X-Archived-Client-IP: 10.180.205.250\r\n" + -> "X-Backside-Transport: OK OK,OK OK\r\n" + -> "X-Client-IP: 10.180.205.250,107.23.55.229\r\n" + -> "X-Global-Transaction-ID: 97138449\r\n" + -> "X-Powered-By: Servlet/3.0\r\n" + -> "Content-Length: 491\r\n" + -> "Connection: Close\r\n" + -> "\r\n" + reading 491 bytes... + -> "{\"correlation_id\":\"228.1449689594381\",\"transaction_status\":\"approved\",\"validation_status\":\"success\",\"transaction_type\":\"purchase\",\"transaction_id\":\"ET196703\",\"transaction_tag\":\"69865571\",\"method\":\"tele_check\",\"amount\":\"100\",\"currency\":\"USD\",\"bank_resp_code\":\"100\",\"bank_message\":\"Approved\",\"gateway_resp_code\":\"00\",\"gateway_message\":\"Transaction Normal\",\"tele_check\":{\"accountholder_name\":\"Jim Smith\",\"check_number\":\"1\",\"check_type\":\"P\",\"account_number\":\"[FILTERED]\",\"routing_number\":\"[FILTERED]\"}} + TRANSCRIPT + end + + def pre_scrubbed_store + <<-TRANSCRIPT + opening connection to api-cert.payeezy.com:443... + opened + starting SSL for api-cert.payeezy.com:443... + SSL established + <- "GET /v1/securitytokens?apikey=UyDMTXx6TD9WErF6ynw7xeEfCAn8fcGs&js_security_key=js-f4c4b54f08d6c44c8cad3ea80bbf92c4f4c4b54f08d6c44c&ta_token=120&callback=Payeezy.callback&type=FDToken&credit_card.type=Visa&credit_card.cardholder_name=Longbob+Longsen&credit_card.card_number=4242424242424242&credit_card.exp_date=0919&credit_card.cvv=123 HTTP/1.1\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: api-cert.payeezy.com\r\n\r\n" + -> "HTTP/1.1 200 Success\r\n" + -> "Content-Language: en-US\r\n" + -> "Content-Type: application/json\r\n" + -> "correlation_id: 228.1574930196886\r\n" + -> "Date: Fri, 12 Jan 2018 09:28:22 GMT\r\n" + -> "statuscode: 201\r\n" + -> "X-Archived-Client-IP: 10.180.205.250\r\n" + -> "X-Backside-Transport: OK OK,OK OK\r\n" + -> "X-Client-IP: 10.180.205.250,54.218.45.37\r\n" + -> "X-Global-Transaction-ID: 463881989\r\n" + -> "X-Powered-By: Servlet/3.0\r\n" + -> "Content-Length: 266\r\n" + -> "Connection: Close\r\n" + -> "\r\n" + reading 266 bytes... + -> "\n Payeezy.callback({\n \t\"status\":201,\n \t\"results\":{\"correlation_id\":\"228.1574930196886\",\"status\":\"success\",\"type\":\"FDToken\",\"token\":{\"type\":\"Visa\",\"cardholder_name\":\"Longbob Longsen\",\"exp_date\":\"0919\",\"value\":\"2158545373614242\"}}\n })\n " + read 266 bytes + Conn close + TRANSCRIPT + end + + def post_scrubbed_store + <<-TRANSCRIPT + opening connection to api-cert.payeezy.com:443... + opened + starting SSL for api-cert.payeezy.com:443... + SSL established + <- "GET /v1/securitytokens?apikey=[FILTERED]js_security_key=js-f4c4b54f08d6c44c8cad3ea80bbf92c4f4c4b54f08d6c44c&ta_token=120&callback=Payeezy.callback&type=FDToken&credit_card.type=Visa&credit_card.cardholder_name=Longbob+Longsen&credit_card.card_number=[FILTERED]credit_card.exp_date=0919&credit_card.cvv=[FILTERED] HTTP/1.1\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: api-cert.payeezy.com\r\n\r\n" + -> "HTTP/1.1 200 Success\r\n" + -> "Content-Language: en-US\r\n" + -> "Content-Type: application/json\r\n" + -> "correlation_id: 228.1574930196886\r\n" + -> "Date: Fri, 12 Jan 2018 09:28:22 GMT\r\n" + -> "statuscode: 201\r\n" + -> "X-Archived-Client-IP: 10.180.205.250\r\n" + -> "X-Backside-Transport: OK OK,OK OK\r\n" + -> "X-Client-IP: 10.180.205.250,54.218.45.37\r\n" + -> "X-Global-Transaction-ID: 463881989\r\n" + -> "X-Powered-By: Servlet/3.0\r\n" + -> "Content-Length: 266\r\n" + -> "Connection: Close\r\n" + -> "\r\n" + reading 266 bytes... + -> "\n Payeezy.callback({\n \t\"status\":201,\n \t\"results\":{\"correlation_id\":\"228.1574930196886\",\"status\":\"success\",\"type\":\"FDToken\",\"token\":{\"type\":\"Visa\",\"cardholder_name\":\"Longbob Longsen\",\"exp_date\":\"0919\",\"value\":\"2158545373614242\"}}\n })\n " + read 266 bytes + Conn close + TRANSCRIPT + end + + def successful_purchase_response + <<-RESPONSE + {\"method\":\"credit_card\",\"amount\":\"1\",\"currency\":\"USD\",\"avs\":\"4\",\"card\":{\"type\":\"Visa\",\"cardholder_name\":\"Bobsen 995\",\"card_number\":\"4242\",\"exp_date\":\"0816\"},\"token\":{\"token_type\":\"transarmor\",\"token_data\":{\"value\":\"0152552999534242\"}},\"transaction_status\":\"approved\",\"validation_status\":\"success\",\"transaction_type\":\"purchase\",\"transaction_id\":\"ET114541\",\"transaction_tag\":\"55083431\",\"bank_resp_code\":\"100\",\"bank_message\":\"Approved\",\"gateway_resp_code\":\"00\",\"gateway_message\":\"Transaction Normal\",\"correlation_id\":\"124.1433862672836\"} + RESPONSE + end + + def successful_purchase_stored_credentials_response + '{"correlation_id":"228.4479800174823","transaction_status":"approved","validation_status":"success","transaction_type":"purchase","transaction_id":"ET117353","transaction_tag":"2309866208","method":"credit_card","amount":"100","currency":"USD","avs":"4","cvv2":"M","token":{"token_type":"FDToken","token_data":{"value":"9091469151414242"}},"card":{"type":"Visa","cardholder_name":"Longbob Longsen","card_number":"4242","exp_date":"0919"},"bank_resp_code":"100","bank_message":"Approved","gateway_resp_code":"00","gateway_message":"Transaction Normal","stored_credentials":{"cardbrand_original_transaction_id":"706838021010062"}}' + end + + def successful_purchase_echeck_response + <<-RESPONSE + {\"correlation_id\":\"228.1449688619062\",\"transaction_status\":\"approved\",\"validation_status\":\"success\",\"transaction_type\":\"purchase\",\"transaction_id\":\"ET133078\",\"transaction_tag\":\"69864362\",\"method\":\"tele_check\",\"amount\":\"100\",\"currency\":\"USD\",\"bank_resp_code\":\"100\",\"bank_message\":\"Approved\",\"gateway_resp_code\":\"00\",\"gateway_message\":\"Transaction Normal\",\"tele_check\":{\"accountholder_name\":\"Jim Smith\",\"check_number\":\"1\",\"check_type\":\"P\",\"account_number\":\"8535\",\"routing_number\":\"244183602\"}} + RESPONSE + end + + def successful_store_response + <<-RESPONSE + {\"correlation_id\":\"124.1792879391754\",\"status\":\"success\",\"type\":\"FDToken\",\"token\":{\"type\":\"Visa\",\"cardholder_name\":\"Longbob Longsen\",\"exp_date\":\"0919\",\"value\":\"9045348309244242\"}} + RESPONSE + end + + def failed_store_response + <<-RESPONSE + {\"correlation_id\":\"124.1792940806770\",\"status\":\"failed\",\"Error\":{\"messages\":[{\"code\":\"invalid_card_number\",\"description\":\"The credit card number check failed\"}]},\"type\":\"FDToken\"} + RESPONSE + end + + def failed_purchase_response + yamlexcep = <<-RESPONSE +--- !ruby/exception:ActiveMerchant::ResponseError +response: !ruby/object:Net::HTTPBadRequest + http_version: '1.1' + code: '400' + message: Bad Request + header: + content-language: + - en-US + content-type: + - application/json + date: + - Tue, 09 Jun 2015 15:46:44 GMT + optr_cxt: + - 0100010000eb11d301-785c-449b-b060-6d0b4638d54d00000000-0000-0000-0000-000000000000-1 HTTP ; + x-archived-client-ip: + - 10.174.197.250 + x-backside-transport: + - FAIL FAIL,FAIL FAIL + x-client-ip: + - 10.174.197.250,54.236.202.5 + x-powered-by: + - Servlet/3.0 + content-length: + - '384' + connection: + - Close + body: '{"method":"credit_card","amount":"10000000","currency":"USD","card":{"type":"Visa","cvv":"000","cardholder_name":"Bobsen + 5675","card_number":"4242","exp_date":"0810"},"transaction_status":"Not Processed","validation_status":"failed","transaction_type":"purchase","Error":{"messages":[{"code":"card_expired","description":"The + card has expired"}]},"correlation_id":"124.1433864804381"}' + read: true + uri: + decode_content: true + socket: + body_exist: true +message: + RESPONSE + YAML.safe_load(yamlexcep, ['Net::HTTPBadRequest', 'ActiveMerchant::ResponseError']) + end + + def successful_authorize_response + <<-RESPONSE + {\"correlation_id\":\"228.1449517682800\",\"transaction_status\":\"approved\",\"validation_status\":\"success\",\"transaction_type\":\"authorize\",\"transaction_id\":\"ET156862\",\"transaction_tag\":\"69601979\",\"method\":\"credit_card\",\"amount\":\"100\",\"currency\":\"USD\",\"avs\":\"4\",\"cvv2\":\"M\",\"token\":{\"token_type\":\"FDToken\",\"token_data\":{\"value\":\"1446473518714242\"}},\"card\":{\"type\":\"Visa\",\"cardholder_name\":\"Longbob Longsen\",\"card_number\":\"4242\",\"exp_date\":\"0916\"},\"bank_resp_code\":\"100\",\"bank_message\":\"Approved\",\"gateway_resp_code\":\"00\",\"gateway_message\":\"Transaction Normal\"} + RESPONSE + end + + def failed_authorize_response + <<-RESPONSE + {\"correlation_id\":\"228.1449522605561\",\"transaction_status\":\"declined\",\"validation_status\":\"success\",\"transaction_type\":\"authorize\",\"transaction_tag\":\"69607256\",\"method\":\"credit_card\",\"amount\":\"501300\",\"currency\":\"USD\",\"avs\":\"4\",\"cvv2\":\"M\",\"token\":{\"token_type\":\"FDToken\",\"token_data\":{\"value\":\"0843687226934242\"}},\"card\":{\"type\":\"Visa\",\"cardholder_name\":\"Longbob Longsen\",\"card_number\":\"4242\",\"exp_date\":\"0916\"},\"bank_resp_code\":\"013\",\"bank_message\":\"Transaction not approved\",\"gateway_resp_code\":\"00\",\"gateway_message\":\"Transaction Normal\"} + RESPONSE + end + + def successful_capture_response + <<-RESPONSE + {\"correlation_id\":\"228.1449517473876\",\"transaction_status\":\"approved\",\"validation_status\":\"success\",\"transaction_type\":\"capture\",\"transaction_id\":\"ET176427\",\"transaction_tag\":\"69601874\",\"method\":\"credit_card\",\"amount\":\"100\",\"currency\":\"USD\",\"cvv2\":\"I\",\"token\":{\"token_type\":\"FDToken\",\"token_data\":{\"value\":\"8129044621504242\"}},\"bank_resp_code\":\"100\",\"bank_message\":\"Approved\",\"gateway_resp_code\":\"00\",\"gateway_message\":\"Transaction Normal\"} + RESPONSE + end + + def successful_refund_response + <<-RESPONSE + {\"method\":\"credit_card\",\"amount\":\"1\",\"currency\":\"USD\",\"cvv2\":\"I\",\"token\":{\"token_type\":\"transarmor\",\"token_data\":{\"value\":\"9968749582724242\"}},\"transaction_status\":\"approved\",\"validation_status\":\"success\",\"transaction_type\":\"refund\",\"transaction_id\":\"55084328\",\"transaction_tag\":\"55084328\",\"bank_resp_code\":\"100\",\"bank_message\":\"Approved\",\"gateway_resp_code\":\"00\",\"gateway_message\":\"Transaction Normal\",\"correlation_id\":\"124.1433864648126\"} + RESPONSE + end + + def successful_refund_echeck_response + <<-RESPONSE + {\"correlation_id\":\"228.1449688783287\",\"transaction_status\":\"approved\",\"validation_status\":\"success\",\"transaction_type\":\"refund\",\"transaction_id\":\"69864710\",\"transaction_tag\":\"69864710\",\"method\":\"tele_check\",\"amount\":\"50\",\"currency\":\"USD\",\"bank_resp_code\":\"100\",\"bank_message\":\"Approved\",\"gateway_resp_code\":\"00\",\"gateway_message\":\"Transaction Normal\"} + RESPONSE + end + + def below_minimum_response + <<-RESPONSE + {\"correlation_id\":\"123.1234678982\",\"transaction_status\":\"declined\",\"validation_status\":\"success\",\"transaction_type\":\"authorize\",\"transaction_tag\":\"92384753\",\"method\":\"credit_card\",\"amount\":\"250\",\"currency\":\"USD\",\"card\":{\"type\":\"Mastercard\",\"cardholder_name\":\"Omri Test\",\"card_number\":\"[FILTERED]\",\"exp_date\":\"0123\"},\"gateway_resp_code\":\"36\",\"gateway_message\":\"Below Minimum Sale\"} + RESPONSE + end + + def failed_refund_response + yamlexcep = <<-RESPONSE +--- !ruby/exception:ActiveMerchant::ResponseError +response: !ruby/object:Net::HTTPBadRequest + http_version: '1.1' + code: '400' + message: Bad Request + header: + content-language: + - en-US + content-type: + - application/json + date: + - Tue, 09 Jun 2015 15:46:44 GMT + optr_cxt: + - 0100010000eb11d301-785c-449b-b060-6d0b4638d54d00000000-0000-0000-0000-000000000000-1 HTTP ; + x-archived-client-ip: + - 10.174.197.250 + x-backside-transport: + - FAIL FAIL,FAIL FAIL + x-client-ip: + - 10.174.197.250,54.236.202.5 + x-powered-by: + - Servlet/3.0 + content-length: + - '384' + connection: + - Close + body: '{"correlation_id":"228.1449520714925","Error":{"messages":[{"code":"missing_transaction_tag","description":"The transaction tag is not provided"}]},"transaction_status":"Not Processed","validation_status":"failed","transaction_type":"refund","amount":"50","currency":"USD"}' + read: true + uri: + decode_content: true + socket: + body_exist: true +message: + RESPONSE + YAML.safe_load(yamlexcep, ['Net::HTTPBadRequest', 'ActiveMerchant::ResponseError']) + end + + def successful_void_response + <<-RESPONSE + {\"method\":\"credit_card\",\"amount\":\"1\",\"currency\":\"USD\",\"cvv2\":\"I\",\"token\":{\"token_type\":\"transarmor\",\"token_data\":{\"value\":\"9594258319174242\"}},\"transaction_status\":\"approved\",\"validation_status\":\"success\",\"transaction_type\":\"void\",\"transaction_id\":\"ET196233\",\"transaction_tag\":\"55083674\",\"bank_resp_code\":\"100\",\"bank_message\":\"Approved\",\"gateway_resp_code\":\"00\",\"gateway_message\":\"Transaction Normal\",\"correlation_id\":\"124.1433863576596\"} +RESPONSE + end + + def failed_void_response + yamlexcep = <<-RESPONSE +--- !ruby/exception:ActiveMerchant::ResponseError +response: !ruby/object:Net::HTTPBadRequest + http_version: '1.1' + code: '400' + message: Bad Request + header: + content-language: + - en-US + content-type: + - application/json + date: + - Tue, 09 Jun 2015 15:46:44 GMT + optr_cxt: + - 0100010000eb11d301-785c-449b-b060-6d0b4638d54d00000000-0000-0000-0000-000000000000-1 HTTP ; + x-archived-client-ip: + - 10.174.197.250 + x-backside-transport: + - FAIL FAIL,FAIL FAIL + x-client-ip: + - 10.174.197.250,54.236.202.5 + x-powered-by: + - Servlet/3.0 + content-length: + - '384' + connection: + - Close + body: '{"correlation_id":"228.1449520846984","Error":{"messages":[{"code":"missing_transaction_id","description":"The transaction id is not provided"},{"code":"missing_transaction_tag","description":"The transaction tag is not provided"}]},"transaction_status":"Not Processed","validation_status":"failed","transaction_type":"void","amount":"0","currency":"USD"}' + read: true + uri: + decode_content: true + socket: + body_exist: true +message: + RESPONSE + YAML.safe_load(yamlexcep, ['Net::HTTPBadRequest', 'ActiveMerchant::ResponseError']) + end + + def failed_capture_response + yamlexcep = <<-RESPONSE +--- !ruby/exception:ActiveMerchant::ResponseError +response: !ruby/object:Net::HTTPBadRequest + http_version: '1.1' + code: '400' + message: Bad Request + header: + content-language: + - en-US + content-type: + - application/json + date: + - Tue, 09 Jun 2015 17:33:50 GMT + optr_cxt: + - 0100010000d084138f-24f3-4686-8a51-3c17406a572500000000-0000-0000-0000-000000000000-1 HTTP ; + x-archived-client-ip: + - 10.174.197.250 + x-backside-transport: + - FAIL FAIL,FAIL FAIL + x-client-ip: + - 10.174.197.250,107.23.55.229 + x-powered-by: + - Servlet/3.0 + content-length: + - '190' + connection: + - Close + body: '{"transaction_status":"Not Processed","Error":{"messages":[{"code":"server_error","description":"ProcessedBad + Request (69) - Invalid Transaction Tag"}]},"correlation_id":"124.1433871231542"}' + read: true + uri: + decode_content: true + socket: + body_exist: true +message: + RESPONSE + YAML.safe_load(yamlexcep, ['Net::HTTPBadRequest', 'ActiveMerchant::ResponseError']) + end + + def invalid_token_response + yamlexcep = <<-RESPONSE +--- !ruby/exception:ActiveMerchant::ResponseError +response: !ruby/object:Net::HTTPUnauthorized + http_version: '1.1' + code: '401' + message: Unauthorized + header: + content-language: + - en-US + content-type: + - application/json;charset=utf-8 + date: + - Tue, 23 Jun 2015 15:13:02 GMT + optr_cxt: + - 435543224354-37b2-4369-9cfe-26543635465346346-0000-0000-0000-000000000000-1 HTTP ; + x-archived-client-ip: + - 10.180.205.250 + x-backside-transport: + - FAIL FAIL,FAIL FAIL + x-client-ip: + - 10.180.205.250,107.23.55.229 + x-powered-by: + - Servlet/3.0 + content-length: + - '25' + connection: + - Close + body: '{"error":"Access denied"}' + read: true + uri: + decode_content: true + socket: + body_exist: true +message: + RESPONSE + YAML.safe_load(yamlexcep, ['Net::HTTPUnauthorized', 'ActiveMerchant::ResponseError']) + end + + def invalid_token_response_integration + yamlexcep = <<-RESPONSE +--- !ruby/exception:ActiveMerchant::ResponseError +response: !ruby/object:Net::HTTPUnauthorized + http_version: '1.1' + code: '401' + message: Unauthorized + header: + content-type: + - application/json + content-length: + - '125' + connection: + - Close + body: '{\"fault\":{\"faultstring\":\"Invalid ApiKey for given resource\",\"detail\":{\"errorcode\":\"oauth.v2.InvalidApiKeyForGivenResource\"}}}' + read: true + uri: + decode_content: true + socket: + body_exist: true +message: + RESPONSE + YAML.safe_load(yamlexcep, ['Net::HTTPUnauthorized', 'ActiveMerchant::ResponseError']) + end + + def bad_credentials_response + yamlexcep = <<-RESPONSE +--- !ruby/exception:ActiveMerchant::ResponseError +response: !ruby/object:Net::HTTPForbidden + http_version: '1.1' + code: '403' + message: Forbidden + header: + content-type: + - application/json + content-length: + - '51' + connection: + - Close + body: '{"code":"403", "message":"HMAC validation Failure"}' + read: true + uri: + decode_content: true + socket: + body_exist: true +message: + RESPONSE + YAML.safe_load(yamlexcep, ['Net::HTTPForbidden', 'ActiveMerchant::ResponseError']) + end +end diff --git a/test/unit/gateways/payex_test.rb b/test/unit/gateways/payex_test.rb new file mode 100644 index 00000000000..a567c1a08c1 --- /dev/null +++ b/test/unit/gateways/payex_test.rb @@ -0,0 +1,284 @@ +require 'test_helper' + +class PayexTest < Test::Unit::TestCase + def setup + @gateway = PayexGateway.new( + :account => 'account', + :encryption_key => 'encryption_key' + ) + + @credit_card = credit_card + @amount = 1000 + + @options = { + :order_id => '1234', + } + end + + def test_successful_purchase + @gateway.expects(:ssl_post).times(2).returns(successful_initialize_response, successful_purchase_response) + + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_instance_of MultiResponse, response + assert_success response + + assert_equal '2623681', response.authorization + assert response.test? + end + + def test_failed_purchase + @gateway.expects(:ssl_post).times(2).returns(successful_initialize_response, failed_purchase_response) + + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert response.test? + end + + def test_successful_authorize + @gateway.expects(:ssl_post).times(2).returns(successful_initialize_response, successful_authorize_response) + assert response = @gateway.authorize(@amount, @credit_card, @options) + assert_success response + assert response.authorization + assert_equal 'OK', response.message + assert_equal '2624653', response.authorization + assert response.test? + end + + def test_successful_capture + @gateway.expects(:ssl_post).returns(successful_capture_response) + assert response = @gateway.capture(@amount, 'fakeauth') + assert_success response + assert_equal '2624655', response.authorization + assert response.test? + end + + def test_failed_capture + @gateway.expects(:ssl_post).returns(failed_capture_response) + assert response = @gateway.capture(@amount, '1') + assert_failure response + assert_not_equal 'OK', response.message + assert_not_equal 'RecordNotFound', response.params[:status_errorcode] + assert response.test? + end + + def test_successful_void + @gateway.expects(:ssl_post).returns(successful_void_response) + + assert response = @gateway.void('fakeauth') + assert_success response + assert_equal '2624825', response.authorization + assert response.test? + end + + def test_unsuccessful_void + @gateway.expects(:ssl_post).returns(unsuccessful_void_response) + assert response = @gateway.void('1') + assert_failure response + assert_not_equal 'OK', response.message + assert_match %r{1}, response.message + assert response.test? + end + + def test_successful_refund + @gateway.expects(:ssl_post).returns(successful_refund_response) + assert response = @gateway.refund(@amount - 200, 'fakeauth', order_id: '123') + assert_success response + assert_equal '2624828', response.authorization + assert response.test? + end + + def test_unsuccessful_refund + @gateway.expects(:ssl_post).returns(unsuccessful_refund_response) + assert response = @gateway.refund(@amount, '1', order_id: '123') + assert_failure response + assert_not_equal 'OK', response.message + assert_match %r{1}, response.message + assert response.test? + end + + def test_successful_store + @gateway.expects(:ssl_post).times(3).returns(successful_store_response, successful_initialize_response, successful_purchase_response) + assert response = @gateway.store(@credit_card, @options.merge({merchant_ref: '9876'})) + assert_success response + assert_equal 'OK', response.message + assert_equal 'bcea4ac8d1f44640bff7a8c93caa249c', response.authorization + assert response.test? + end + + def test_successful_unstore + @gateway.expects(:ssl_post).returns(successful_unstore_response) + assert response = @gateway.unstore('fakeauth') + assert_success response + assert_equal 'OK', response.message + assert response.test? + end + + def test_successful_purchase_with_stored_card + @gateway.expects(:ssl_post).returns(successful_autopay_response) + assert response = @gateway.purchase(@amount, 'fakeauth', @options.merge({order_id: '5678'})) + assert_success response + assert_equal 'OK', response.message + assert_equal '2624657', response.authorization + assert response.test? + end + + private + + def successful_initialize_response + '<?xml version="1.0" encoding="utf-8"?> + <soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> + <soap:Body> + <Initialize8Response xmlns="http://external.payex.com/PxOrder/"> + <Initialize8Result>&lt;?xml version="1.0" encoding="utf-8" ?&gt;&lt;payex&gt;&lt;header name="Payex Header v1.0"&gt;&lt;id&gt;09ef982cf2584d58bf4363dacd2ef127&lt;/id&gt;&lt;date&gt;2013-11-06 14:19:23&lt;/date&gt;&lt;/header&gt;&lt;status&gt;&lt;code&gt;OK&lt;/code&gt;&lt;description&gt;OK&lt;/description&gt;&lt;errorCode&gt;OK&lt;/errorCode&gt;&lt;paramName /&gt;&lt;thirdPartyError /&gt;&lt;thirdPartySubError /&gt;&lt;/status&gt;&lt;orderRef&gt;53681e74064a4621b93a6fcceba20c00&lt;/orderRef&gt;&lt;sessionRef&gt;7424a69d355c4cafa853ff49553b786f&lt;/sessionRef&gt;&lt;redirectUrl&gt;https://test-confined.payex.com/PxOrderCC.aspx?orderRef=53681e74064a4621b93a6fcceba20c00&lt;/redirectUrl&gt;&lt;/payex&gt;</Initialize8Result> + </Initialize8Response> + </soap:Body> + </soap:Envelope> + ' + end + + # Place raw successful response from gateway here + def successful_purchase_response + '<?xml version="1.0" encoding="utf-8"?> + <soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> + <soap:Body> + <PurchaseCCResponse xmlns="http://confined.payex.com/PxOrder/"> + <PurchaseCCResult>&lt;?xml version="1.0" encoding="utf-8" ?&gt;&lt;payex&gt;&lt;header name="Payex Header v1.0"&gt;&lt;id&gt;45eca449c8b54b54ac8811d4c26f638d&lt;/id&gt;&lt;date&gt;2013-11-06 14:19:35&lt;/date&gt;&lt;/header&gt;&lt;status&gt;&lt;code&gt;OK&lt;/code&gt;&lt;description&gt;OK&lt;/description&gt;&lt;errorCode&gt;OK&lt;/errorCode&gt;&lt;paramName /&gt;&lt;thirdPartyError /&gt;&lt;/status&gt;&lt;transactionStatus&gt;0&lt;/transactionStatus&gt;&lt;transactionRef&gt;750dc1438350481086abd0438bde0c23&lt;/transactionRef&gt;&lt;transactionNumber&gt;2623681&lt;/transactionNumber&gt;&lt;orderId&gt;1234&lt;/orderId&gt;&lt;productId&gt;4321&lt;/productId&gt;&lt;paymentMethod&gt;VISA&lt;/paymentMethod&gt;&lt;productNumber&gt;4321&lt;/productNumber&gt;&lt;BankHash&gt;00000001-4581-0903-5682-000000000000&lt;/BankHash&gt;&lt;AuthenticatedStatus&gt;None&lt;/AuthenticatedStatus&gt;&lt;AuthenticatedWith&gt;N&lt;/AuthenticatedWith&gt;&lt;/payex&gt;</PurchaseCCResult> + </PurchaseCCResponse> + </soap:Body> + </soap:Envelope> + ' + end + + def failed_purchase_response + '<?xml version="1.0" encoding="utf-8"?> + <soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> + <soap:Body> + <PurchaseCCResponse xmlns="http://confined.payex.com/PxOrder/"> + <PurchaseCCResult>&lt;?xml version="1.0" encoding="utf-8" ?&gt;&lt;payex&gt;&lt;header name="Payex Header v1.0"&gt;&lt;id&gt;131faa4301f74e91bf29e9749ad8f2a6&lt;/id&gt;&lt;date&gt;2013-11-06 14:40:18&lt;/date&gt;&lt;/header&gt;&lt;status&gt;&lt;code&gt;ValidationError_InvalidParameter&lt;/code&gt;&lt;errorCode&gt;ValidationError_InvalidParameter&lt;/errorCode&gt;&lt;description&gt;Invalid parameter:expireDate + Parameter name: expireDate&lt;/description&gt;&lt;paramName&gt;expireDate&lt;/paramName&gt;&lt;thirdPartyError /&gt;&lt;/status&gt;&lt;/payex&gt;</PurchaseCCResult> + </PurchaseCCResponse> + </soap:Body> + </soap:Envelope> + ' + end + + def successful_authorize_response + '<?xml version="1.0" encoding="utf-8"?> + <soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> + <soap:Body> + <PurchaseCCResponse xmlns="http://confined.payex.com/PxOrder/"> + <PurchaseCCResult>&lt;?xml version="1.0" encoding="utf-8" ?&gt;&lt;payex&gt;&lt;header name="Payex Header v1.0"&gt;&lt;id&gt;41ebf6488fdb42a79baf49d2ef9e7dc6&lt;/id&gt;&lt;date&gt;2013-11-06 14:43:01&lt;/date&gt;&lt;/header&gt;&lt;status&gt;&lt;code&gt;OK&lt;/code&gt;&lt;description&gt;OK&lt;/description&gt;&lt;errorCode&gt;OK&lt;/errorCode&gt;&lt;paramName /&gt;&lt;thirdPartyError /&gt;&lt;/status&gt;&lt;transactionStatus&gt;3&lt;/transactionStatus&gt;&lt;transactionRef&gt;89cbf30161f442228cbca16ff7f886d4&lt;/transactionRef&gt;&lt;transactionNumber&gt;2624653&lt;/transactionNumber&gt;&lt;orderId&gt;1234&lt;/orderId&gt;&lt;productId&gt;4321&lt;/productId&gt;&lt;paymentMethod&gt;VISA&lt;/paymentMethod&gt;&lt;productNumber&gt;4321&lt;/productNumber&gt;&lt;BankHash&gt;00000001-4581-0903-5682-000000000000&lt;/BankHash&gt;&lt;AuthenticatedStatus&gt;None&lt;/AuthenticatedStatus&gt;&lt;AuthenticatedWith&gt;N&lt;/AuthenticatedWith&gt;&lt;/payex&gt;</PurchaseCCResult> + </PurchaseCCResponse> + </soap:Body> + </soap:Envelope> + ' + end + + def successful_capture_response + '<?xml version="1.0" encoding="utf-8"?> + <soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> + <soap:Body> + <Capture5Response xmlns="http://external.payex.com/PxOrder/"> + <Capture5Result>&lt;?xml version="1.0" encoding="utf-8" ?&gt;&lt;payex&gt;&lt;header name="Payex Header v1.0"&gt;&lt;id&gt;efe1a4572c9b4ed5a656d648bf7f9207&lt;/id&gt;&lt;date&gt;2013-11-06 14:43:03&lt;/date&gt;&lt;/header&gt;&lt;status&gt;&lt;code&gt;OK&lt;/code&gt;&lt;description&gt;OK&lt;/description&gt;&lt;errorCode&gt;OK&lt;/errorCode&gt;&lt;paramName /&gt;&lt;thirdPartyError /&gt;&lt;thirdPartySubError /&gt;&lt;/status&gt;&lt;transactionStatus&gt;6&lt;/transactionStatus&gt;&lt;transactionNumber&gt;2624655&lt;/transactionNumber&gt;&lt;originalTransactionNumber&gt;2624653&lt;/originalTransactionNumber&gt;&lt;/payex&gt;</Capture5Result> + </Capture5Response> + </soap:Body> + </soap:Envelope> + ' + end + + def failed_capture_response + '<?xml version="1.0" encoding="utf-8"?> + <soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> + <soap:Body> + <Capture5Response xmlns="http://external.payex.com/PxOrder/"> + <Capture5Result>&lt;?xml version="1.0" encoding="utf-8" ?&gt;&lt;payex&gt;&lt;header name="Payex Header v1.0"&gt;&lt;id&gt;19706b063aad458aa9783563a4e5bbff&lt;/id&gt;&lt;date&gt;2013-11-06 14:53:34&lt;/date&gt;&lt;/header&gt;&lt;status&gt;&lt;code&gt;ValidationError_Generic&lt;/code&gt;&lt;description&gt;1&lt;/description&gt;&lt;errorCode&gt;NoRecordFound&lt;/errorCode&gt;&lt;paramName /&gt;&lt;thirdPartyError /&gt;&lt;thirdPartySubError /&gt;&lt;/status&gt;&lt;/payex&gt;</Capture5Result> + </Capture5Response> + </soap:Body> + </soap:Envelope> + ' + end + + def successful_void_response + '<?xml version="1.0" encoding="utf-8"?> + <soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> + <soap:Body> + <Cancel2Response xmlns="http://external.payex.com/PxOrder/"> + <Cancel2Result>&lt;?xml version="1.0" encoding="utf-8" ?&gt;&lt;payex&gt;&lt;header name="Payex Header v1.0"&gt;&lt;id&gt;219b3a4b4ae0478482c75eba06d5a0dd&lt;/id&gt;&lt;date&gt;2013-11-06 14:56:48&lt;/date&gt;&lt;/header&gt;&lt;status&gt;&lt;code&gt;OK&lt;/code&gt;&lt;description&gt;OK&lt;/description&gt;&lt;errorCode&gt;OK&lt;/errorCode&gt;&lt;paramName /&gt;&lt;thirdPartyError /&gt;&lt;thirdPartySubError /&gt;&lt;/status&gt;&lt;transactionStatus&gt;4&lt;/transactionStatus&gt;&lt;transactionNumber&gt;2624825&lt;/transactionNumber&gt;&lt;originalTransactionNumber&gt;2624824&lt;/originalTransactionNumber&gt;&lt;/payex&gt;</Cancel2Result> + </Cancel2Response> + </soap:Body> + </soap:Envelope> + ' + end + + def unsuccessful_void_response + '<?xml version="1.0" encoding="utf-8"?> + <soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> + <soap:Body> + <Cancel2Response xmlns="http://external.payex.com/PxOrder/"> + <Cancel2Result>&lt;?xml version="1.0" encoding="utf-8" ?&gt;&lt;payex&gt;&lt;header name="Payex Header v1.0"&gt;&lt;id&gt;220b395b966a43eb9d0c70109f6dadd3&lt;/id&gt;&lt;date&gt;2013-11-06 15:02:24&lt;/date&gt;&lt;/header&gt;&lt;status&gt;&lt;code&gt;ValidationError_Generic&lt;/code&gt;&lt;description&gt;1&lt;/description&gt;&lt;errorCode&gt;Error_Generic&lt;/errorCode&gt;&lt;paramName /&gt;&lt;thirdPartyError /&gt;&lt;thirdPartySubError /&gt;&lt;/status&gt;&lt;/payex&gt;</Cancel2Result> + </Cancel2Response> + </soap:Body> + </soap:Envelope> + ' + end + + def successful_refund_response + '<?xml version="1.0" encoding="utf-8"?> + <soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> + <soap:Body> + <Credit5Response xmlns="http://external.payex.com/PxOrder/"> + <Credit5Result>&lt;?xml version="1.0" encoding="utf-8" ?&gt;&lt;payex&gt;&lt;header name="Payex Header v1.0"&gt;&lt;id&gt;a1e70daf3ed842ddb72e58a28f5cbc11&lt;/id&gt;&lt;date&gt;2013-11-06 14:57:54&lt;/date&gt;&lt;/header&gt;&lt;status&gt;&lt;code&gt;OK&lt;/code&gt;&lt;description&gt;OK&lt;/description&gt;&lt;errorCode&gt;OK&lt;/errorCode&gt;&lt;paramName /&gt;&lt;thirdPartyError /&gt;&lt;thirdPartySubError /&gt;&lt;/status&gt;&lt;transactionStatus&gt;2&lt;/transactionStatus&gt;&lt;transactionNumber&gt;2624828&lt;/transactionNumber&gt;&lt;originalTransactionNumber&gt;2624827&lt;/originalTransactionNumber&gt;&lt;/payex&gt;</Credit5Result> + </Credit5Response> + </soap:Body> + </soap:Envelope> + ' + end + + def unsuccessful_refund_response + '<?xml version="1.0" encoding="utf-8"?> + <soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> + <soap:Body> + <Credit5Response xmlns="http://external.payex.com/PxOrder/"> + <Credit5Result>&lt;?xml version="1.0" encoding="utf-8" ?&gt;&lt;payex&gt;&lt;header name="Payex Header v1.0"&gt;&lt;id&gt;e82613f6c59d4e08a7163cd91c2b3ce5&lt;/id&gt;&lt;date&gt;2013-11-06 15:02:23&lt;/date&gt;&lt;/header&gt;&lt;status&gt;&lt;code&gt;ValidationError_Generic&lt;/code&gt;&lt;description&gt;1&lt;/description&gt;&lt;errorCode&gt;Error_Generic&lt;/errorCode&gt;&lt;paramName /&gt;&lt;thirdPartyError /&gt;&lt;thirdPartySubError /&gt;&lt;/status&gt;&lt;/payex&gt;</Credit5Result> + </Credit5Response> + </soap:Body> + </soap:Envelope> + ' + end + + def successful_store_response + '<?xml version="1.0" encoding="utf-8"?> + <soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> + <soap:Body> + <CreateAgreement3Response xmlns="http://external.payex.com/PxAgreement/"> + <CreateAgreement3Result>&lt;?xml version="1.0" encoding="utf-8" ?&gt;&lt;payex&gt;&lt;header name="Payex Header v1.0"&gt;&lt;id&gt;3c8063b7fcb64a449b715fe711f0a03f&lt;/id&gt;&lt;date&gt;2013-11-06 14:43:04&lt;/date&gt;&lt;/header&gt;&lt;status&gt;&lt;code&gt;OK&lt;/code&gt;&lt;description&gt;OK&lt;/description&gt;&lt;errorCode&gt;OK&lt;/errorCode&gt;&lt;paramName /&gt;&lt;thirdPartyError /&gt;&lt;thirdPartySubError /&gt;&lt;/status&gt;&lt;agreementRef&gt;bcea4ac8d1f44640bff7a8c93caa249c&lt;/agreementRef&gt;&lt;/payex&gt;</CreateAgreement3Result> + </CreateAgreement3Response> + </soap:Body> + </soap:Envelope> + ' + end + + def successful_unstore_response + '<?xml version="1.0" encoding="utf-8"?> + <soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> + <soap:Body> + <DeleteAgreementResponse xmlns="http://external.payex.com/PxAgreement/"> + <DeleteAgreementResult>&lt;?xml version="1.0" encoding="utf-8" ?&gt;&lt;payex&gt;&lt;header name="Payex Header v1.0"&gt;&lt;id&gt;7935ca4c24c3467cb36c48a270557194&lt;/id&gt;&lt;date&gt;2013-11-06 15:06:54&lt;/date&gt;&lt;/header&gt;&lt;status&gt;&lt;code&gt;OK&lt;/code&gt;&lt;description&gt;OK&lt;/description&gt;&lt;errorCode&gt;OK&lt;/errorCode&gt;&lt;paramName /&gt;&lt;thirdPartyError /&gt;&lt;thirdPartySubError /&gt;&lt;/status&gt;&lt;agreementRef&gt;7e9c6342dc20459691d1abb027a3c8c0&lt;/agreementRef&gt;&lt;/payex&gt;</DeleteAgreementResult> + </DeleteAgreementResponse> + </soap:Body> + </soap:Envelope> + ' + end + + def successful_autopay_response + '<?xml version="1.0" encoding="utf-8"?> + <soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> + <soap:Body> + <AutoPay3Response xmlns="http://external.payex.com/PxAgreement/"> + <AutoPay3Result>&lt;?xml version="1.0" encoding="utf-8" ?&gt;&lt;payex&gt;&lt;header name="Payex Header v1.0"&gt;&lt;id&gt;37a7164c16804af199b7d6aade0aa580&lt;/id&gt;&lt;date&gt;2013-11-06 14:43:09&lt;/date&gt;&lt;/header&gt;&lt;status&gt;&lt;code&gt;OK&lt;/code&gt;&lt;description&gt;OK&lt;/description&gt;&lt;errorCode&gt;OK&lt;/errorCode&gt;&lt;paramName /&gt;&lt;thirdPartyError /&gt;&lt;thirdPartySubError /&gt;&lt;/status&gt;&lt;transactionStatus&gt;3&lt;/transactionStatus&gt;&lt;transactionRef&gt;de2984e302da40b498afe5aced8cea7e&lt;/transactionRef&gt;&lt;transactionNumber&gt;2624657&lt;/transactionNumber&gt;&lt;paymentMethod&gt;VISA&lt;/paymentMethod&gt;&lt;captureTokens&gt;&lt;stan&gt;1337&lt;/stan&gt;&lt;terminalId&gt;666&lt;/terminalId&gt;&lt;transactionTime&gt;11/6/2013 2:43:09 PM&lt;/transactionTime&gt;&lt;/captureTokens&gt;&lt;pending&gt;false&lt;/pending&gt;&lt;/payex&gt;</AutoPay3Result> + </AutoPay3Response> + </soap:Body> + </soap:Envelope> + ' + end +end diff --git a/test/unit/gateways/payflow_express_test.rb b/test/unit/gateways/payflow_express_test.rb index 9fe549f98ca..ef8fab1fcb4 100644 --- a/test/unit/gateways/payflow_express_test.rb +++ b/test/unit/gateways/payflow_express_test.rb @@ -3,17 +3,17 @@ class PayflowExpressTest < Test::Unit::TestCase TEST_REDIRECT_URL = 'https://www.sandbox.paypal.com/cgi-bin/webscr?cmd=_express-checkout&token=1234567890' TEST_REDIRECT_URL_MOBILE = 'https://www.sandbox.paypal.com/cgi-bin/webscr?cmd=_express-checkout-mobile&token=1234567890' - LIVE_REDIRECT_URL = 'https://www.paypal.com/cgibin/webscr?cmd=_express-checkout&token=1234567890' - LIVE_REDIRECT_URL_MOBILE = 'https://www.paypal.com/cgibin/webscr?cmd=_express-checkout-mobile&token=1234567890' - + LIVE_REDIRECT_URL = 'https://www.paypal.com/cgi-bin/webscr?cmd=_express-checkout&token=1234567890' + LIVE_REDIRECT_URL_MOBILE = 'https://www.paypal.com/cgi-bin/webscr?cmd=_express-checkout-mobile&token=1234567890' + TEST_REDIRECT_URL_WITHOUT_REVIEW = "#{TEST_REDIRECT_URL}&useraction=commit" LIVE_REDIRECT_URL_WITHOUT_REVIEW = "#{LIVE_REDIRECT_URL}&useraction=commit" TEST_REDIRECT_URL_MOBILE_WITHOUT_REVIEW = "#{TEST_REDIRECT_URL_MOBILE}&useraction=commit" LIVE_REDIRECT_URL_MOBILE_WITHOUT_REVIEW = "#{LIVE_REDIRECT_URL_MOBILE}&useraction=commit" - + def setup - Base.gateway_mode = :test - + Base.mode = :test + @gateway = PayflowExpressGateway.new( :login => 'LOGIN', :password => 'PASSWORD' @@ -29,61 +29,61 @@ def setup :phone => '(555)555-5555' } end - + def teardown - Base.gateway_mode = :test + Base.mode = :test end - + def test_using_test_mode assert @gateway.test? end - + def test_overriding_test_mode - Base.gateway_mode = :production - + Base.mode = :production + gateway = PayflowExpressGateway.new( :login => 'LOGIN', :password => 'PASSWORD', :test => true ) - + assert gateway.test? end - + def test_using_production_mode - Base.gateway_mode = :production - + Base.mode = :production + gateway = PayflowExpressGateway.new( :login => 'LOGIN', :password => 'PASSWORD' ) - + assert !gateway.test? end - + def test_live_redirect_url - Base.gateway_mode = :production + Base.mode = :production assert_equal LIVE_REDIRECT_URL, @gateway.redirect_url_for('1234567890') assert_equal LIVE_REDIRECT_URL_MOBILE, @gateway.redirect_url_for('1234567890', :mobile => true) end - + def test_test_redirect_url assert_equal TEST_REDIRECT_URL, @gateway.redirect_url_for('1234567890') assert_equal TEST_REDIRECT_URL_MOBILE, @gateway.redirect_url_for('1234567890', :mobile => true) end - + def test_live_redirect_url_without_review - Base.gateway_mode = :production + Base.mode = :production assert_equal LIVE_REDIRECT_URL_WITHOUT_REVIEW, @gateway.redirect_url_for('1234567890', :review => false) assert_equal LIVE_REDIRECT_URL_MOBILE_WITHOUT_REVIEW, @gateway.redirect_url_for('1234567890', :review => false, :mobile => true) end - + def test_test_redirect_url_without_review - assert_equal :test, Base.gateway_mode + assert_equal :test, Base.mode assert_equal TEST_REDIRECT_URL_WITHOUT_REVIEW, @gateway.redirect_url_for('1234567890', :review => false) assert_equal TEST_REDIRECT_URL_MOBILE_WITHOUT_REVIEW, @gateway.redirect_url_for('1234567890', :review => false, :mobile => true) end - + def test_invalid_get_express_details_request @gateway.expects(:ssl_post).returns(invalid_get_express_details_response) response = @gateway.details_for('EC-2OPN7UJGFWK9OYFV') @@ -91,20 +91,20 @@ def test_invalid_get_express_details_request assert response.test? assert_equal 'Field format error: Invalid Token', response.message end - + def test_get_express_details @gateway.expects(:ssl_post).returns(successful_get_express_details_response) response = @gateway.details_for('EC-2OPN7UJGFWK9OYFV') assert_instance_of PayflowExpressResponse, response assert_success response assert response.test? - + assert_equal 'EC-2OPN7UJGFWK9OYFV', response.token assert_equal '12345678901234567', response.payer_id assert_equal 'Buyer1@paypal.com', response.email assert_equal 'Joe Smith', response.full_name assert_equal 'US', response.payer_country - + assert address = response.address assert_equal 'Joe Smith', address['name'] assert_nil address['company'] @@ -114,11 +114,36 @@ def test_get_express_details assert_equal 'CA', address['state'] assert_equal '95100', address['zip'] assert_equal 'US', address['country'] - assert_nil address['phone'] + assert_equal '555-555-5555', address['phone'] + end + + def test_get_express_details_with_ship_to_name + @gateway.expects(:ssl_post).returns(successful_get_express_details_response_with_ship_to_name) + response = @gateway.details_for('EC-2OPN7UJGFWK9OYFV') + assert_instance_of PayflowExpressResponse, response + assert_success response + assert response.test? + + assert_equal 'EC-2OPN7UJGFWK9OYFV', response.token + assert_equal '12345678901234567', response.payer_id + assert_equal 'Buyer1@paypal.com', response.email + assert_equal 'Joe Smith', response.full_name + assert_equal 'US', response.payer_country + + assert address = response.address + assert_equal 'John Joseph', address['name'] + assert_nil address['company'] + assert_equal '111 Main St.', address['address1'] + assert_nil address['address2'] + assert_equal 'San Jose', address['city'] + assert_equal 'CA', address['state'] + assert_equal '95100', address['zip'] + assert_equal 'US', address['country'] + assert_equal '555-555-5555', address['phone'] end def test_get_express_details_with_invalid_xml - @gateway.expects(:ssl_post).returns(successful_get_express_details_response(:street => "Main & Magic")) + @gateway.expects(:ssl_post).returns(successful_get_express_details_response(:street => 'Main & Magic')) response = @gateway.details_for('EC-2OPN7UJGFWK9OYFV') assert_instance_of PayflowExpressResponse, response assert_success response @@ -132,12 +157,12 @@ def test_button_source xml = Builder::XmlMarkup.new @gateway.send(:add_paypal_details, xml, {}) xml_doc = REXML::Document.new(xml.target!) - assert_equal 'ActiveMerchant', REXML::XPath.first(xml_doc, '/PayPal/ButtonSource').text + assert_nil REXML::XPath.first(xml_doc, '/PayPal/ButtonSource') end - + private - - def successful_get_express_details_response(options={:street => "111 Main St."}) + + def successful_get_express_details_response(options={:street => '111 Main St.'}) <<-RESPONSE <XMLPayResponse xmlns='http://www.verisign.com/XMLPay'> <ResponseData> @@ -154,6 +179,7 @@ def successful_get_express_details_response(options={:street => "111 Main St."}) <FeeAmount>0</FeeAmount> <PayerStatus>verified</PayerStatus> <Name>Joe</Name> + <Phone>555-555-5555</Phone> <ShipTo> <Address> <Street>#{options[:street]}</Street> @@ -172,7 +198,45 @@ def successful_get_express_details_response(options={:street => "111 Main St."}) </XMLPayResponse> RESPONSE end - + + def successful_get_express_details_response_with_ship_to_name + <<-RESPONSE +<XMLPayResponse xmlns='http://www.verisign.com/XMLPay'> + <ResponseData> + <Vendor>TEST</Vendor> + <Partner>verisign</Partner> + <TransactionResults> + <TransactionResult> + <Result>0</Result> + <Message>Approved</Message> + <PayPalResult> + <EMail>Buyer1@paypal.com</EMail> + <PayerID>12345678901234567</PayerID> + <Token>EC-2OPN7UJGFWK9OYFV</Token> + <FeeAmount>0</FeeAmount> + <PayerStatus>verified</PayerStatus> + <Name>Joe</Name> + <Phone>555-555-5555</Phone> + <ShipTo> + <Address> + <Street>111 Main St.</Street> + <City>San Jose</City> + <State>CA</State> + <Zip>95100</Zip> + <Country>US</Country> + </Address> + </ShipTo> + <CorrelationID>9c3706997455e</CorrelationID> + </PayPalResult> + <ExtData Name='LASTNAME' Value='Smith'/> + <ExtData Name='SHIPTONAME' Value='John Joseph'/> + </TransactionResult> + </TransactionResults> + </ResponseData> + </XMLPayResponse> + RESPONSE + end + def invalid_get_express_details_response <<-RESPONSE <XMLPayResponse xmlns='http://www.verisign.com/XMLPay'> @@ -186,7 +250,7 @@ def invalid_get_express_details_response </TransactionResult> </TransactionResults> </ResponseData> -</XMLPayResponse> +</XMLPayResponse> RESPONSE end end diff --git a/test/unit/gateways/payflow_express_uk_test.rb b/test/unit/gateways/payflow_express_uk_test.rb index a6082c216cd..60a18a88be8 100644 --- a/test/unit/gateways/payflow_express_uk_test.rb +++ b/test/unit/gateways/payflow_express_uk_test.rb @@ -7,37 +7,63 @@ def setup :password => 'PASSWORD' ) end - + def test_supported_countries assert_equal ['GB'], PayflowExpressUkGateway.supported_countries end - + def test_get_express_details - @gateway.expects(:ssl_post).returns(successful_get_express_details_response) - response = @gateway.details_for('EC-2OPN7UJGFWK9OYFV') - assert_instance_of PayflowExpressResponse, response - assert_success response - assert response.test? + @gateway.expects(:ssl_post).returns(successful_get_express_details_response) + response = @gateway.details_for('EC-2OPN7UJGFWK9OYFV') + assert_instance_of PayflowExpressResponse, response + assert_success response + assert response.test? + + assert_equal 'EC-2OPN7UJGFWK9OYFV', response.token + assert_equal 'LYWCMEN4FA7ZQ', response.payer_id + assert_equal 'paul@test.com', response.email + assert_equal 'paul smith', response.full_name + assert_equal 'GB', response.payer_country + + assert address = response.address + assert_equal 'paul smith', address['name'] + assert_nil address['company'] + assert_equal '10 keyworth avenue', address['address1'] + assert_equal 'grangetown', address['address2'] + assert_equal 'hinterland', address['city'] + assert_equal 'Tyne and Wear', address['state'] + assert_equal 'sr5 2uh', address['zip'] + assert_equal 'GB', address['country'] + assert_nil address['phone'] + end + + def test_get_express_details_with_ship_to_name + @gateway.expects(:ssl_post).returns(successful_get_express_details_response_with_ship_to_name) + response = @gateway.details_for('EC-2OPN7UJGFWK9OYFV') + assert_instance_of PayflowExpressResponse, response + assert_success response + assert response.test? + + assert_equal 'EC-2OPN7UJGFWK9OYFV', response.token + assert_equal 'LYWCMEN4FA7ZQ', response.payer_id + assert_equal 'paul@test.com', response.email + assert_equal 'paul smith', response.full_name + assert_equal 'GB', response.payer_country - assert_equal 'EC-2OPN7UJGFWK9OYFV', response.token - assert_equal 'LYWCMEN4FA7ZQ', response.payer_id - assert_equal 'paul@test.com', response.email - assert_equal 'paul smith', response.full_name - assert_equal 'GB', response.payer_country + assert address = response.address + assert_equal 'John Joseph', address['name'] + assert_nil address['company'] + assert_equal '10 keyworth avenue', address['address1'] + assert_equal 'grangetown', address['address2'] + assert_equal 'hinterland', address['city'] + assert_equal 'Tyne and Wear', address['state'] + assert_equal 'sr5 2uh', address['zip'] + assert_equal 'GB', address['country'] + assert_nil address['phone'] + end - assert address = response.address - assert_equal 'paul smith', address['name'] - assert_nil address['company'] - assert_equal '10 keyworth avenue', address['address1'] - assert_equal 'grangetown', address['address2'] - assert_equal 'hinterland', address['city'] - assert_equal 'Tyne and Wear', address['state'] - assert_equal 'sr5 2uh', address['zip'] - assert_equal 'GB', address['country'] - assert_nil address['phone'] - end - private + def successful_get_express_details_response <<-RESPONSE <?xml version="1.0"?> @@ -73,7 +99,52 @@ def successful_get_express_details_response </PayPalResult> <ExtData Name="LASTNAME" Value="smith"/> <ExtData Name="SHIPTOSTREET2" Value="grangetown"/> - <ExtData Name="SHIPTONAME" Value="paul smith"/> + <ExtData Name="STREET2" Value="ALLAWAY AVENUE"/> + <ExtData Name="COUNTRYCODE" Value="GB"/> + <ExtData Name="ADDRESSSTATUS" Value="Y"/> + </TransactionResult> + </TransactionResults> + </ResponseData> +</XMLPayResponse> + RESPONSE + end + + def successful_get_express_details_response_with_ship_to_name + <<-RESPONSE +<?xml version="1.0"?> +<XMLPayResponse xmlns="http://www.paypal.com/XMLPay"> + <ResponseData> + <Vendor>markcoop</Vendor> + <Partner>paypaluk</Partner> + <TransactionResults> + <TransactionResult> + <Result>0</Result> + <AVSResult> + <StreetMatch>Match</StreetMatch> + <ZipMatch>Match</ZipMatch> + </AVSResult> + <Message>Approved</Message> + <PayPalResult> + <EMail>paul@test.com</EMail> + <PayerID>LYWCMEN4FA7ZQ</PayerID> + <Token>EC-2OPN7UJGFWK9OYFV</Token> + <FeeAmount>0</FeeAmount> + <PayerStatus>unverified</PayerStatus> + <Name>paul</Name> + <ShipTo> + <Address> + <Street>10 keyworth avenue</Street> + <City>hinterland</City> + <State>Tyne and Wear</State> + <Zip>sr5 2uh</Zip> + <Country>GB</Country> + </Address> + </ShipTo> + <CorrelationID>1ea22ef3873ba</CorrelationID> + </PayPalResult> + <ExtData Name="LASTNAME" Value="smith"/> + <ExtData Name="SHIPTOSTREET2" Value="grangetown"/> + <ExtData Name="SHIPTONAME" Value="John Joseph"/> <ExtData Name="STREET2" Value="ALLAWAY AVENUE"/> <ExtData Name="COUNTRYCODE" Value="GB"/> <ExtData Name="ADDRESSSTATUS" Value="Y"/> diff --git a/test/unit/gateways/payflow_test.rb b/test/unit/gateways/payflow_test.rb index 9ee933154d0..6c03e4c18e9 100644 --- a/test/unit/gateways/payflow_test.rb +++ b/test/unit/gateways/payflow_test.rb @@ -1,6 +1,8 @@ require 'test_helper' class PayflowTest < Test::Unit::TestCase + include CommStub + def setup Base.mode = :test @@ -11,46 +13,175 @@ def setup @amount = 100 @credit_card = credit_card('4242424242424242') - @options = { :billing_address => address.merge(:first_name => "Longbob", :last_name => "Longsen") } + @options = { :billing_address => address.merge(:first_name => 'Longbob', :last_name => 'Longsen') } + @check = check(:name => 'Jim Smith') + @l2_json = '{ + "Tender": { + "ACH": { + "AcctType": "C", + "AcctNum": "6355059797", + "ABA": "021000021" + } + } + }' + + @l3_json = '{ + "Invoice": { + "Date": "20190104", + "Level3Invoice": { + "CountyTax": {"Amount": "3.23"} + } + } + }' end def test_successful_authorization @gateway.stubs(:ssl_post).returns(successful_authorization_response) assert response = @gateway.authorize(@amount, @credit_card, @options) - assert_equal "Approved", response.message + assert_equal 'Approved', response.message assert_success response assert response.test? - assert_equal "VUJN1A6E11D9", response.authorization + assert_equal 'VUJN1A6E11D9', response.authorization + refute response.fraud_review? end def test_failed_authorization @gateway.stubs(:ssl_post).returns(failed_authorization_response) assert response = @gateway.authorize(@amount, @credit_card, @options) - assert_equal "Declined", response.message + assert_equal 'Declined', response.message assert_failure response assert response.test? end + def test_authorization_with_three_d_secure_option + response = stub_comms do + @gateway.authorize(@amount, @credit_card, @options.merge(three_d_secure_option)) + end.check_request do |endpoint, data, headers| + assert_three_d_secure REXML::Document.new(data), authorize_buyer_auth_result_path + end.respond_with(successful_authorization_response) + assert_equal 'Approved', response.message + assert_success response + assert response.test? + assert_equal 'VUJN1A6E11D9', response.authorization + refute response.fraud_review? + end + + def test_successful_authorization_with_more_options + options = @options.merge( + { + order_id: '123', + description: 'Description string', + order_desc: 'OrderDesc string', + comment: 'Comment string', + comment2: 'Comment2 string' + } + ) + + response = stub_comms do + @gateway.authorize(@amount, @credit_card, options) + end.check_request do |endpoint, data, headers| + assert_match %r(<InvNum>123</InvNum>), data + assert_match %r(<Description>Description string</Description>), data + assert_match %r(<OrderDesc>OrderDesc string</OrderDesc>), data + assert_match %r(<Comment>Comment string</Comment>), data + assert_match %r(<ExtData Name=\"COMMENT2\" Value=\"Comment2 string\"/>), data + end.respond_with(successful_authorization_response) + assert_equal 'Approved', response.message + assert_success response + assert response.test? + assert_equal 'VUJN1A6E11D9', response.authorization + refute response.fraud_review? + end + + def test_successful_purchase_with_fraud_review + @gateway.stubs(:ssl_post).returns(successful_purchase_with_fraud_review_response) + + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal '126', response.params['result'] + assert response.fraud_review? + end + + def test_successful_purchase_with_three_d_secure_option + response = stub_comms do + @gateway.purchase(@amount, @credit_card, @options.merge(three_d_secure_option)) + end.check_request do |endpoint, data, headers| + assert_three_d_secure REXML::Document.new(data), purchase_buyer_auth_result_path + end.respond_with(successful_purchase_with_fraud_review_response) + assert_success response + assert_equal '126', response.params['result'] + assert response.fraud_review? + end + + def test_successful_purchase_with_level_2_fields + options = @options.merge(level_two_fields: @l2_json) + + response = stub_comms do + @gateway.purchase(@amount, @credit_card, options) + end.check_request do |endpoint, data, headers| + assert_match %r(<AcctNum>6355059797</AcctNum>), data + assert_match %r(<ACH><AcctType>), data.tr("\n ", '') + end.respond_with(successful_l2_response) + assert_equal 'Approved', response.message + assert_success response + assert_equal 'A1ADADCE9B12', response.authorization + refute response.fraud_review? + end + + def test_successful_purchase_with_level_3_fields + options = @options.merge(level_three_fields: @l3_json) + + response = stub_comms do + @gateway.purchase(@amount, @credit_card, options) + end.check_request do |endpoint, data, headers| + assert_match %r(<Date>20190104</Date>), data + assert_match %r(<Amount>3.23</Amount>), data + assert_match %r(<Level3Invoice><CountyTax><Amount>), data.tr("\n ", '') + end.respond_with(successful_l3_response) + assert_equal 'Approved', response.message + assert_success response + assert_equal 'A71AAC3B60A1', response.authorization + refute response.fraud_review? + end + + def test_successful_purchase_with_level_2_3_fields + options = @options.merge(level_two_fields: @l2_json).merge(level_three_fields: @l3_json) + + response = stub_comms do + @gateway.purchase(@amount, @credit_card, options) + end.check_request do |endpoint, data, headers| + assert_match %r(<Date>20190104</Date>), data + assert_match %r(<Amount>3.23</Amount>), data + assert_match %r(<AcctNum>6355059797</AcctNum>), data + assert_match %r(<ACH><AcctType>), data.tr("\n ", '') + assert_match %r(<Level3Invoice><CountyTax><Amount>), data.tr("\n ", '') + end.respond_with(successful_l2_response) + assert_equal 'Approved', response.message + assert_success response + assert_equal 'A1ADADCE9B12', response.authorization + refute response.fraud_review? + end + def test_credit - @gateway.expects(:ssl_post).with(anything, regexp_matches(/<CardNum>#{@credit_card.number}<\//), anything).returns("") + @gateway.expects(:ssl_post).with(anything, regexp_matches(/<CardNum>#{@credit_card.number}<\//), anything).returns('') @gateway.expects(:parse).returns({}) @gateway.credit(@amount, @credit_card, @options) end def test_deprecated_credit - @gateway.expects(:ssl_post).with(anything, regexp_matches(/<PNRef>transaction_id<\//), anything).returns("") + @gateway.expects(:ssl_post).with(anything, regexp_matches(/<PNRef>transaction_id<\//), anything).returns('') @gateway.expects(:parse).returns({}) - assert_deprecation_warning(Gateway::CREDIT_DEPRECATION_MESSAGE, @gateway) do - @gateway.credit(@amount, "transaction_id", @options) + assert_deprecation_warning(Gateway::CREDIT_DEPRECATION_MESSAGE) do + @gateway.credit(@amount, 'transaction_id', @options) end end def test_refund - @gateway.expects(:ssl_post).with(anything, regexp_matches(/<PNRef>transaction_id<\//), anything).returns("") + @gateway.expects(:ssl_post).with(anything, regexp_matches(/<PNRef>transaction_id<\//), anything).returns('') @gateway.expects(:parse).returns({}) - @gateway.refund(@amount, "transaction_id", @options) + @gateway.refund(@amount, 'transaction_id', @options) end def test_avs_result @@ -78,12 +209,24 @@ def test_cvv_result assert_equal 'M', response.cvv_result['code'] end + def test_ach_purchase + @gateway.expects(:ssl_post).with(anything, regexp_matches(/<AcctNum>#{@check.account_number}<\//), anything).returns('') + @gateway.expects(:parse).returns({}) + @gateway.purchase(@amount, @check) + end + + def test_ach_credit + @gateway.expects(:ssl_post).with(anything, regexp_matches(/<AcctNum>#{@check.account_number}<\//), anything).returns('') + @gateway.expects(:parse).returns({}) + @gateway.credit(@amount, @check) + end + def test_using_test_mode assert @gateway.test? end def test_overriding_test_mode - Base.gateway_mode = :production + Base.mode = :production gateway = PayflowGateway.new( :login => 'LOGIN', @@ -95,14 +238,14 @@ def test_overriding_test_mode end def test_using_production_mode - Base.gateway_mode = :production + Base.mode = :production gateway = PayflowGateway.new( :login => 'LOGIN', :password => 'PASSWORD' ) - assert !gateway.test? + refute gateway.test? end def test_partner_class_accessor @@ -140,130 +283,163 @@ def test_default_currency end def test_supported_countries - assert_equal ['US', 'CA', 'SG', 'AU'], PayflowGateway.supported_countries + assert_equal ['US', 'CA', 'NZ', 'AU'], PayflowGateway.supported_countries end def test_supported_card_types assert_equal [:visa, :master, :american_express, :jcb, :discover, :diners_club], PayflowGateway.supported_cardtypes end + def test_successful_verify + response = stub_comms(@gateway) do + @gateway.verify(@credit_card, @options) + end.respond_with(successful_authorization_response) + assert_success response + end + + def test_unsuccessful_verify + response = stub_comms(@gateway) do + @gateway.verify(@credit_card, @options) + end.respond_with(failed_authorization_response) + assert_failure response + assert_equal 'Declined', response.message + end + def test_initial_recurring_transaction_missing_parameters assert_raises ArgumentError do - response = @gateway.recurring(@amount, @credit_card, - :periodicity => :monthly, - :initial_transaction => { } - ) + assert_deprecation_warning(Gateway::RECURRING_DEPRECATION_MESSAGE) do + @gateway.recurring(@amount, @credit_card, + :periodicity => :monthly, + :initial_transaction => { } + ) + end end end def test_initial_purchase_missing_amount assert_raises ArgumentError do - response = @gateway.recurring(@amount, @credit_card, - :periodicity => :monthly, - :initial_transaction => { :amount => :purchase } - ) + assert_deprecation_warning(Gateway::RECURRING_DEPRECATION_MESSAGE) do + @gateway.recurring(@amount, @credit_card, + :periodicity => :monthly, + :initial_transaction => { :amount => :purchase } + ) + end end end def test_recurring_add_action_missing_parameters assert_raises ArgumentError do - response = @gateway.recurring(@amount, @credit_card) + assert_deprecation_warning(Gateway::RECURRING_DEPRECATION_MESSAGE) do + @gateway.recurring(@amount, @credit_card) + end end end def test_recurring_modify_action_missing_parameters assert_raises ArgumentError do - response = @gateway.recurring(@amount, nil) + assert_deprecation_warning(Gateway::RECURRING_DEPRECATION_MESSAGE) do + @gateway.recurring(@amount, nil) + end end end def test_successful_recurring_action @gateway.stubs(:ssl_post).returns(successful_recurring_response) - response = @gateway.recurring(@amount, @credit_card, :periodicity => :monthly) + response = assert_deprecation_warning(Gateway::RECURRING_DEPRECATION_MESSAGE) do + @gateway.recurring(@amount, @credit_card, :periodicity => :monthly) + end assert_instance_of PayflowResponse, response assert_success response assert_equal 'RT0000000009', response.profile_id assert response.test? - assert_equal "R7960E739F80", response.authorization + assert_equal 'R7960E739F80', response.authorization end def test_successful_recurring_modify_action @gateway.stubs(:ssl_post).returns(successful_recurring_response) - response = @gateway.recurring(@amount, nil, :profile_id => "RT0000000009", :periodicity => :monthly) + response = assert_deprecation_warning(Gateway::RECURRING_DEPRECATION_MESSAGE) do + @gateway.recurring(@amount, nil, :profile_id => 'RT0000000009', :periodicity => :monthly) + end assert_instance_of PayflowResponse, response assert_success response assert_equal 'RT0000000009', response.profile_id assert response.test? - assert_equal "R7960E739F80", response.authorization + assert_equal 'R7960E739F80', response.authorization end def test_successful_recurring_modify_action_with_retry_num_days @gateway.stubs(:ssl_post).returns(successful_recurring_response) - response = @gateway.recurring(@amount, nil, :profile_id => "RT0000000009", :retry_num_days => 3, :periodicity => :monthly) + response = assert_deprecation_warning(Gateway::RECURRING_DEPRECATION_MESSAGE) do + @gateway.recurring(@amount, nil, :profile_id => 'RT0000000009', :retry_num_days => 3, :periodicity => :monthly) + end assert_instance_of PayflowResponse, response assert_success response assert_equal 'RT0000000009', response.profile_id assert response.test? - assert_equal "R7960E739F80", response.authorization + assert_equal 'R7960E739F80', response.authorization end def test_falied_recurring_modify_action_with_starting_at_in_the_past @gateway.stubs(:ssl_post).returns(start_date_error_recurring_response) - response = @gateway.recurring(@amount, nil, :profile_id => "RT0000000009", :starting_at => Date.yesterday, :periodicity => :monthly) + response = assert_deprecation_warning(Gateway::RECURRING_DEPRECATION_MESSAGE) do + @gateway.recurring(@amount, nil, :profile_id => 'RT0000000009', :starting_at => Date.yesterday, :periodicity => :monthly) + end assert_instance_of PayflowResponse, response assert_success response assert_equal 'RT0000000009', response.profile_id assert_equal 'Field format error: START or NEXTPAYMENTDATE older than last payment date', response.message assert response.test? - assert_equal "R7960E739F80", response.authorization + assert_equal 'R7960E739F80', response.authorization end def test_falied_recurring_modify_action_with_starting_at_missing_and_changed_periodicity @gateway.stubs(:ssl_post).returns(start_date_missing_recurring_response) - response = @gateway.recurring(@amount, nil, :profile_id => "RT0000000009", :periodicity => :yearly) + response = assert_deprecation_warning(Gateway::RECURRING_DEPRECATION_MESSAGE) do + @gateway.recurring(@amount, nil, :profile_id => 'RT0000000009', :periodicity => :yearly) + end assert_instance_of PayflowResponse, response assert_success response assert_equal 'RT0000000009', response.profile_id assert_equal 'Field format error: START field missing', response.message assert response.test? - assert_equal "R7960E739F80", response.authorization + assert_equal 'R7960E739F80', response.authorization end def test_recurring_profile_payment_history_inquiry @gateway.stubs(:ssl_post).returns(successful_payment_history_recurring_response) - response = @gateway.recurring_inquiry('RT0000000009', :history => true) + response = assert_deprecation_warning(Gateway::RECURRING_DEPRECATION_MESSAGE) do + @gateway.recurring_inquiry('RT0000000009', :history => true) + end assert_equal 1, response.payment_history.size assert_equal '1', response.payment_history.first['payment_num'] assert_equal '7.25', response.payment_history.first['amt'] end def test_recurring_profile_payment_history_inquiry_contains_the_proper_xml - request = @gateway.send( :build_recurring_request, :inquiry, nil, :profile_id => 'RT0000000009', :history => true) + request = @gateway.send(:build_recurring_request, :inquiry, nil, :profile_id => 'RT0000000009', :history => true) assert_match %r(<PaymentHistory>Y</PaymentHistory), request end - def test_format_issue_number + def test_add_credit_card_with_three_d_secure xml = Builder::XmlMarkup.new - credit_card = credit_card("5641820000000005", - :brand => "switch", - :issue_number => 1 + credit_card = credit_card( + '5641820000000005', + :brand => 'maestro' ) - @gateway.send(:add_credit_card, xml, credit_card) - doc = REXML::Document.new(xml.target!) - node = REXML::XPath.first(doc, '/Card/ExtData') - assert_equal '01', node.attributes['Value'] + @gateway.send(:add_credit_card, xml, credit_card, @options.merge(three_d_secure_option)) + assert_three_d_secure REXML::Document.new(xml.target!), '/Card/BuyerAuthResult' end def test_duplicate_response_flag @@ -285,7 +461,7 @@ def test_timeout_is_same_in_header_and_xml assert_equal timeout, headers['X-VPS-Client-Timeout'] xml = @gateway.send(:build_request, 'dummy body') - assert_match /Timeout="#{timeout}"/, xml + assert_match %r{Timeout="#{timeout}"}, xml end def test_name_field_are_included_instead_of_first_and_last @@ -296,7 +472,127 @@ def test_name_field_are_included_instead_of_first_and_last assert_success response end + def test_passed_in_verbosity + assert_nil PayflowGateway.new(:login => 'test', :password => 'test').options[:verbosity] + gateway = PayflowGateway.new(:login => 'test', :password => 'test', :verbosity => 'HIGH') + assert_equal 'HIGH', gateway.options[:verbosity] + @gateway.expects(:ssl_post).returns(verbose_transaction_response) + response = @gateway.purchase(100, @credit_card, @options) + assert_success response + assert_equal '2014-06-25 09:33:41', response.params['transaction_time'] + end + + def test_paypal_nvp_option_sends_header + headers = @gateway.send(:build_headers, 1) + assert_not_include headers, 'PAYPAL-NVP' + + old_use_paypal_nvp = PayflowGateway.use_paypal_nvp + PayflowGateway.use_paypal_nvp = true + headers = @gateway.send(:build_headers, 1) + assert_equal 'Y', headers['PAYPAL-NVP'] + PayflowGateway.use_paypal_nvp = old_use_paypal_nvp + end + + def test_scrub + assert @gateway.supports_scrubbing? + assert_equal @gateway.scrub(pre_scrubbed), post_scrubbed + assert_equal @gateway.scrub(pre_scrubbed_check), post_scrubbed_check + end + private + + def pre_scrubbed + <<-EOS +opening connection to pilot-payflowpro.paypal.com:443... +opened +starting SSL for pilot-payflowpro.paypal.com:443... +SSL established +<- "POST / HTTP/1.1\r\nContent-Type: text/xml\r\nContent-Length: 1017\r\nX-Vps-Client-Timeout: 60\r\nX-Vps-Vit-Integration-Product: ActiveMerchant\r\nX-Vps-Vit-Runtime-Version: 2.1.7\r\nX-Vps-Request-Id: 3b2f9831949b48b4b0b89a33a60f9b0c\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: pilot-payflowpro.paypal.com\r\n\r\n" +<- "<?xml version=\"1.0\" encoding=\"UTF-8\"?><XMLPayRequest Timeout=\"60\" version=\"2.1\" xmlns=\"http://www.paypal.com/XMLPay\"><RequestData><Vendor>spreedlyIntegrations</Vendor><Partner>PayPal</Partner><Transactions><Transaction CustRef=\"codyexample\"><Verbosity>MEDIUM</Verbosity><Sale><PayData><Invoice><EMail>cody@example.com</EMail><BillTo><Name>Jim Smith</Name><EMail>cody@example.com</EMail><Phone>(555)555-5555</Phone><CustCode>codyexample</CustCode><Address><Street>456 My Street</Street><City>Ottawa</City><State>ON</State><Country>CA</Country><Zip>K1C2N6</Zip></Address></BillTo><TotalAmt Currency=\"USD\"/></Invoice><Tender><Card><CardType>MasterCard</CardType><CardNum>5105105105105100</CardNum><ExpDate>201909</ExpDate><NameOnCard>Longbob</NameOnCard><CVNum>123</CVNum><ExtData Name=\"LASTNAME\" Value=\"Longsen\"/></Card></Tender></PayData></Sale></Transaction></Transactions></RequestData><RequestAuth><UserPass><User>spreedlyIntegrations</User><Password>L9DjqEKjXCkU</Password></UserPass></RequestAuth></XMLPayRequest>" +-> "HTTP/1.1 200 OK\r\n" +-> "Connection: close\r\n" +-> "Server: VPS-3.033.00\r\n" +-> "X-VPS-Request-ID: 3b2f9831949b48b4b0b89a33a60f9b0c\r\n" +-> "Date: Thu, 01 Mar 2018 15:42:15 GMT\r\n" +-> "Content-type: text/xml\r\n" +-> "Content-length: 267\r\n" +-> "\r\n" +reading 267 bytes... +-> "<XMLPayResponse xmlns=\"http://www.paypal.com/XMLPay\"><ResponseData><Vendor></Vendor><Partner></Partner><TransactionResults><TransactionResult><Result>4</Result><Message>Invalid amount</Message></TransactionResult></TransactionResults></ResponseData></XMLPayResponse>" +read 267 bytes +Conn close + EOS + end + + def post_scrubbed + <<-EOS +opening connection to pilot-payflowpro.paypal.com:443... +opened +starting SSL for pilot-payflowpro.paypal.com:443... +SSL established +<- "POST / HTTP/1.1\r\nContent-Type: text/xml\r\nContent-Length: 1017\r\nX-Vps-Client-Timeout: 60\r\nX-Vps-Vit-Integration-Product: ActiveMerchant\r\nX-Vps-Vit-Runtime-Version: 2.1.7\r\nX-Vps-Request-Id: 3b2f9831949b48b4b0b89a33a60f9b0c\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: pilot-payflowpro.paypal.com\r\n\r\n" +<- "<?xml version=\"1.0\" encoding=\"UTF-8\"?><XMLPayRequest Timeout=\"60\" version=\"2.1\" xmlns=\"http://www.paypal.com/XMLPay\"><RequestData><Vendor>spreedlyIntegrations</Vendor><Partner>PayPal</Partner><Transactions><Transaction CustRef=\"codyexample\"><Verbosity>MEDIUM</Verbosity><Sale><PayData><Invoice><EMail>cody@example.com</EMail><BillTo><Name>Jim Smith</Name><EMail>cody@example.com</EMail><Phone>(555)555-5555</Phone><CustCode>codyexample</CustCode><Address><Street>456 My Street</Street><City>Ottawa</City><State>ON</State><Country>CA</Country><Zip>K1C2N6</Zip></Address></BillTo><TotalAmt Currency=\"USD\"/></Invoice><Tender><Card><CardType>MasterCard</CardType><CardNum>[FILTERED]</CardNum><ExpDate>201909</ExpDate><NameOnCard>Longbob</NameOnCard><CVNum>[FILTERED]</CVNum><ExtData Name=\"LASTNAME\" Value=\"Longsen\"/></Card></Tender></PayData></Sale></Transaction></Transactions></RequestData><RequestAuth><UserPass><User>spreedlyIntegrations</User><Password>[FILTERED]</Password></UserPass></RequestAuth></XMLPayRequest>" +-> "HTTP/1.1 200 OK\r\n" +-> "Connection: close\r\n" +-> "Server: VPS-3.033.00\r\n" +-> "X-VPS-Request-ID: 3b2f9831949b48b4b0b89a33a60f9b0c\r\n" +-> "Date: Thu, 01 Mar 2018 15:42:15 GMT\r\n" +-> "Content-type: text/xml\r\n" +-> "Content-length: 267\r\n" +-> "\r\n" +reading 267 bytes... +-> "<XMLPayResponse xmlns=\"http://www.paypal.com/XMLPay\"><ResponseData><Vendor></Vendor><Partner></Partner><TransactionResults><TransactionResult><Result>4</Result><Message>Invalid amount</Message></TransactionResult></TransactionResults></ResponseData></XMLPayResponse>" +read 267 bytes +Conn close + EOS + end + + def pre_scrubbed_check + <<-EOS +opening connection to pilot-payflowpro.paypal.com:443... +opened +starting SSL for pilot-payflowpro.paypal.com:443... +SSL established +<- "POST / HTTP/1.1\r\nContent-Type: text/xml\r\nContent-Length: 658\r\nX-Vps-Client-Timeout: 60\r\nX-Vps-Vit-Integration-Product: ActiveMerchant\r\nX-Vps-Vit-Runtime-Version: 2.1.7\r\nX-Vps-Request-Id: 863021e6890a0660238ef22d0a21c5f2\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: pilot-payflowpro.paypal.com\r\n\r\n" +<- "<?xml version=\"1.0\" encoding=\"UTF-8\"?><XMLPayRequest Timeout=\"60\" version=\"2.1\" xmlns=\"http://www.paypal.com/XMLPay\"><RequestData><Vendor>spreedlyIntegrations</Vendor><Partner>PayPal</Partner><Transactions><Transaction CustRef=\"codyexample\"><Verbosity>MEDIUM</Verbosity><Sale><PayData><Invoice><BillTo><Name>Jim Smith</Name></BillTo><TotalAmt Currency=\"USD\"/></Invoice><Tender><ACH><AcctType>C</AcctType><AcctNum>1234567801</AcctNum><ABA>111111118</ABA></ACH></Tender></PayData></Sale></Transaction></Transactions></RequestData><RequestAuth><UserPass><User>spreedlyIntegrations</User><Password>L9DjqEKjXCkU</Password></UserPass></RequestAuth></XMLPayRequest>" +-> "HTTP/1.1 200 OK\r\n" +-> "Connection: close\r\n" +-> "Server: VPS-3.033.00\r\n" +-> "X-VPS-Request-ID: 863021e6890a0660238ef22d0a21c5f2\r\n" +-> "Date: Thu, 01 Mar 2018 15:45:59 GMT\r\n" +-> "Content-type: text/xml\r\n" +-> "Content-length: 267\r\n" +-> "\r\n" +reading 267 bytes... +-> "<XMLPayResponse xmlns=\"http://www.paypal.com/XMLPay\"><ResponseData><Vendor></Vendor><Partner></Partner><TransactionResults><TransactionResult><Result>4</Result><Message>Invalid amount</Message></TransactionResult></TransactionResults></ResponseData></XMLPayResponse>" +read 267 bytes +Conn close + EOS + end + + def post_scrubbed_check + <<-EOS +opening connection to pilot-payflowpro.paypal.com:443... +opened +starting SSL for pilot-payflowpro.paypal.com:443... +SSL established +<- "POST / HTTP/1.1\r\nContent-Type: text/xml\r\nContent-Length: 658\r\nX-Vps-Client-Timeout: 60\r\nX-Vps-Vit-Integration-Product: ActiveMerchant\r\nX-Vps-Vit-Runtime-Version: 2.1.7\r\nX-Vps-Request-Id: 863021e6890a0660238ef22d0a21c5f2\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: pilot-payflowpro.paypal.com\r\n\r\n" +<- "<?xml version=\"1.0\" encoding=\"UTF-8\"?><XMLPayRequest Timeout=\"60\" version=\"2.1\" xmlns=\"http://www.paypal.com/XMLPay\"><RequestData><Vendor>spreedlyIntegrations</Vendor><Partner>PayPal</Partner><Transactions><Transaction CustRef=\"codyexample\"><Verbosity>MEDIUM</Verbosity><Sale><PayData><Invoice><BillTo><Name>Jim Smith</Name></BillTo><TotalAmt Currency=\"USD\"/></Invoice><Tender><ACH><AcctType>C</AcctType><AcctNum>[FILTERED]</AcctNum><ABA>111111118</ABA></ACH></Tender></PayData></Sale></Transaction></Transactions></RequestData><RequestAuth><UserPass><User>spreedlyIntegrations</User><Password>[FILTERED]</Password></UserPass></RequestAuth></XMLPayRequest>" +-> "HTTP/1.1 200 OK\r\n" +-> "Connection: close\r\n" +-> "Server: VPS-3.033.00\r\n" +-> "X-VPS-Request-ID: 863021e6890a0660238ef22d0a21c5f2\r\n" +-> "Date: Thu, 01 Mar 2018 15:45:59 GMT\r\n" +-> "Content-type: text/xml\r\n" +-> "Content-length: 267\r\n" +-> "\r\n" +reading 267 bytes... +-> "<XMLPayResponse xmlns=\"http://www.paypal.com/XMLPay\"><ResponseData><Vendor></Vendor><Partner></Partner><TransactionResults><TransactionResult><Result>4</Result><Message>Invalid amount</Message></TransactionResult></TransactionResults></ResponseData></XMLPayResponse>" +read 267 bytes +Conn close + EOS + end + def successful_recurring_response <<-XML <ResponseData> @@ -311,7 +607,7 @@ def successful_recurring_response end def start_date_error_recurring_response - <<-XML + <<-XML <ResponseData> <Result>0</Result> <Message>Field format error: START or NEXTPAYMENTDATE older than last payment date</Message> @@ -324,7 +620,7 @@ def start_date_error_recurring_response end def start_date_missing_recurring_response - <<-XML + <<-XML <ResponseData> <Result>0</Result> <Message>Field format error: START field missing</Message> @@ -377,6 +673,59 @@ def successful_authorization_response XML end + def successful_l3_response + <<-XML +<ResponseData> + <Vendor>spreedlyIntegrations</Vendor> + <Partner>paypal</Partner> + <TransactionResults> + <TransactionResult> + <Result>0</Result> + <ProcessorResult> + <AVSResult>Z</AVSResult> + <CVResult>M</CVResult> + <HostCode>A</HostCode> + </ProcessorResult> + <FraudPreprocessResult> + <Message>No Rules Triggered</Message> + </FraudPreprocessResult> + <FraudPostprocessResult> + <Message>No Rules Triggered</Message> + </FraudPostprocessResult> + <IAVSResult>N</IAVSResult> + <AVSResult> + <StreetMatch>No Match</StreetMatch> + <ZipMatch>Match</ZipMatch> + </AVSResult> + <CVResult>Match</CVResult> + <Message>Approved</Message> + <PNRef>A71AAC3B60A1</PNRef> + <AuthCode>240PNI</AuthCode> + </TransactionResult> + </TransactionResults> +</ResponseData> + XML + end + + def successful_l2_response + <<-XML +<ResponseData> + <Vendor>spreedlyIntegrations</Vendor> + <Partner>paypal</Partner> + <TransactionResults> + <TransactionResult> + <Result>0</Result> + <ProcessorResult> + <HostCode>A</HostCode> + </ProcessorResult> + <Message>Approved</Message> + <PNRef>A1ADADCE9B12</PNRef> + </TransactionResult> + </TransactionResults> +</ResponseData> + XML + end + def failed_authorization_response <<-XML <ResponseData> @@ -397,6 +746,46 @@ def failed_authorization_response XML end + def successful_purchase_with_fraud_review_response + <<-XML + <XMLPayResponse xmlns="http://www.paypal.com/XMLPay"> + <ResponseData> + <Vendor>spreedly</Vendor> + <Partner>paypal</Partner> + <TransactionResults> + <TransactionResult> + <Result>126</Result> + <ProcessorResult> + <HostCode>A</HostCode> + </ProcessorResult> + <FraudPreprocessResult> + <Message>Review HighRiskBinCheck</Message> + <XMLData> + <triggeredRules> + <rule num="1"> + <ruleId>13</ruleId> + <ruleID>13</ruleID> + <ruleAlias>HighRiskBinCheck</ruleAlias> + <ruleDescription>BIN Risk List Match</ruleDescription> + <action>R</action> + <triggeredMessage>The card number is in a high risk bin list</triggeredMessage> + </rule> + </triggeredRules> + </XMLData> + </FraudPreprocessResult> + <FraudPostprocessResult> + <Message>Review</Message> + </FraudPostprocessResult> + <Message>Under review by Fraud Service</Message> + <PNRef>A71A7B022DC0</PNRef> + <AuthCode>907PNI</AuthCode> + </TransactionResult> + </TransactionResults> + </ResponseData> + </XMLPayResponse> + XML + end + def successful_duplicate_response <<-XML <?xml version="1.0"?> @@ -428,4 +817,84 @@ def successful_duplicate_response </XMLPayResponse> XML end + + def verbose_transaction_response + <<-XML +<?xml version="1.0" encoding="UTF-8"?> +<XMLPayResponse xmlns="http://www.paypal.com/XMLPay"> + <ResponseData> + <Vendor>ActiveMerchant</Vendor> + <Partner>paypal</Partner> + <TransactionResults> + <TransactionResult> + <Result>0</Result> + <ProcessorResult> + <AVSResult>U</AVSResult> + <CVResult>M</CVResult> + <HostCode>A</HostCode> + </ProcessorResult> + <FraudPreprocessResult> + <Message>No Rules Triggered</Message> + </FraudPreprocessResult> + <FraudPostprocessResult> + <Message>No Rules Triggered</Message> + </FraudPostprocessResult> + <IAVSResult>X</IAVSResult> + <AVSResult> + <StreetMatch>Service Not Available</StreetMatch> + <ZipMatch>Service Not Available</ZipMatch> + </AVSResult> + <CVResult>Match</CVResult> + <Message>Approved</Message> + <PNRef>A70A6C93C4C8</PNRef> + <AuthCode>242PNI</AuthCode> + <Amount>1.00</Amount> + <VisaCardLevel>12</VisaCardLevel> + <TransactionTime>2014-06-25 09:33:41</TransactionTime> + <Account>4242</Account> + <ExpirationDate>0714</ExpirationDate> + <CardType>0</CardType> + <PayPalResult> + <FeeAmount>0</FeeAmount> + <Name>Longbob</Name> + <Lastname>Longsen</Lastname> + </PayPalResult> + </TransactionResult> + </TransactionResults> + </ResponseData> +</XMLPayResponse> + XML + end + + def assert_three_d_secure(xml_doc, buyer_auth_result_path) + assert_equal 'Y', REXML::XPath.first(xml_doc, "#{buyer_auth_result_path}/Status").text + assert_equal 'QvDbSAxSiaQs241899E0', REXML::XPath.first(xml_doc, "#{buyer_auth_result_path}/AuthenticationId").text + assert_equal 'pareq block', REXML::XPath.first(xml_doc, "#{buyer_auth_result_path}/PAReq").text + assert_equal 'https://bankacs.bank.com/ascurl', REXML::XPath.first(xml_doc, "#{buyer_auth_result_path}/ACSUrl").text + assert_equal '02', REXML::XPath.first(xml_doc, "#{buyer_auth_result_path}/ECI").text + assert_equal 'jGvQIvG/5UhjAREALGYa6Vu/hto=', REXML::XPath.first(xml_doc, "#{buyer_auth_result_path}/CAVV").text + assert_equal 'UXZEYlNBeFNpYVFzMjQxODk5RTA=', REXML::XPath.first(xml_doc, "#{buyer_auth_result_path}/XID").text + end + + def authorize_buyer_auth_result_path + '/XMLPayRequest/RequestData/Transactions/Transaction/Authorization/PayData/Tender/Card/BuyerAuthResult' + end + + def purchase_buyer_auth_result_path + '/XMLPayRequest/RequestData/Transactions/Transaction/Sale/PayData/Tender/Card/BuyerAuthResult' + end + + def three_d_secure_option + { + :three_d_secure => { + :status => 'Y', + :authentication_id => 'QvDbSAxSiaQs241899E0', + :pareq => 'pareq block', + :acs_url => 'https://bankacs.bank.com/ascurl', + :eci => '02', + :cavv => 'jGvQIvG/5UhjAREALGYa6Vu/hto=', + :xid => 'UXZEYlNBeFNpYVFzMjQxODk5RTA=' + } + } + end end diff --git a/test/unit/gateways/payflow_uk_test.rb b/test/unit/gateways/payflow_uk_test.rb index 53c41fcfd20..9d50c599edd 100644 --- a/test/unit/gateways/payflow_uk_test.rb +++ b/test/unit/gateways/payflow_uk_test.rb @@ -11,20 +11,20 @@ def setup def test_default_currency assert_equal 'GBP', PayflowUkGateway.default_currency end - + def test_express_instance assert_instance_of PayflowExpressUkGateway, @gateway.express end - + def test_default_partner assert_equal 'PayPalUk', PayflowUkGateway.partner end - + def test_supported_countries assert_equal ['GB'], PayflowUkGateway.supported_countries end - + def test_supported_card_types - assert_equal [:visa, :master, :american_express, :discover, :solo, :switch], PayflowUkGateway.supported_cardtypes + assert_equal [:visa, :master, :american_express, :discover], PayflowUkGateway.supported_cardtypes end end diff --git a/test/unit/gateways/payment_express_test.rb b/test/unit/gateways/payment_express_test.rb index fe1e697457f..cf15aa1ad92 100644 --- a/test/unit/gateways/payment_express_test.rb +++ b/test/unit/gateways/payment_express_test.rb @@ -11,7 +11,7 @@ def setup @visa = credit_card - @solo = credit_card("6334900000000005", :brand => "solo", :issue_number => '01') + @solo = credit_card('6334900000000005', :brand => 'maestro') @options = { :order_id => generate_unique_id, @@ -74,9 +74,9 @@ def test_successful_card_store end def test_successful_card_store_with_custom_billing_id - @gateway.expects(:ssl_post).returns(successful_store_response(:billing_id => "my-custom-id")) + @gateway.expects(:ssl_post).returns(successful_store_response(:billing_id => 'my-custom-id')) - assert response = @gateway.store(@visa, :billing_id => "my-custom-id") + assert response = @gateway.store(@visa, :billing_id => 'my-custom-id') assert_success response assert response.test? assert_equal 'my-custom-id', response.token @@ -126,7 +126,7 @@ def test_purchase_using_merchant_specified_billing_id_token end def test_supported_countries - assert_equal %w(AU CA DE ES FR GB HK IE MY NL NZ SG US ZA), PaymentExpressGateway.supported_countries + assert_equal %w(AU FJ GB HK IE MY NZ PG SG US), PaymentExpressGateway.supported_countries end def test_supported_card_types @@ -158,9 +158,9 @@ def test_expect_no_optional_fields_by_default def test_pass_optional_txn_data options = { - :txn_data1 => "Transaction Data 1", - :txn_data2 => "Transaction Data 2", - :txn_data3 => "Transaction Data 3" + :txn_data1 => 'Transaction Data 1', + :txn_data2 => 'Transaction Data 2', + :txn_data3 => 'Transaction Data 3' } perform_each_transaction_type_with_request_body_assertions(options) do |body| @@ -172,12 +172,12 @@ def test_pass_optional_txn_data def test_pass_optional_txn_data_truncated_to_255_chars options = { - :txn_data1 => "Transaction Data 1-01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345-EXTRA", - :txn_data2 => "Transaction Data 2-01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345-EXTRA", - :txn_data3 => "Transaction Data 3-01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345-EXTRA" + :txn_data1 => 'Transaction Data 1-01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345-EXTRA', + :txn_data2 => 'Transaction Data 2-01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345-EXTRA', + :txn_data3 => 'Transaction Data 3-01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345-EXTRA' } - truncated_addendum = "01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345" + truncated_addendum = '01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345' perform_each_transaction_type_with_request_body_assertions(options) do |body| assert_match(/<TxnData1>Transaction Data 1-#{truncated_addendum}<\/TxnData1>/, body) @@ -242,9 +242,17 @@ def test_pass_client_type_as_symbol_for_unknown_type_omits_element end end + def test_pass_ip_as_client_info + options = {:ip => '192.168.0.1'} + + perform_each_transaction_type_with_request_body_assertions(options) do |body| + assert_match(/<ClientInfo>192.168.0.1<\/ClientInfo>/, body) + end + end + def test_purchase_truncates_order_id_to_16_chars stub_comms do - @gateway.purchase(@amount, @visa, {:order_id => "16chars---------EXTRA"}) + @gateway.purchase(@amount, @visa, {:order_id => '16chars---------EXTRA'}) end.check_request do |endpoint, data, headers| assert_match(/<TxnId>16chars---------<\/TxnId>/, data) end.respond_with(successful_authorization_response) @@ -252,7 +260,7 @@ def test_purchase_truncates_order_id_to_16_chars def test_authorize_truncates_order_id_to_16_chars stub_comms do - @gateway.authorize(@amount, @visa, {:order_id => "16chars---------EXTRA"}) + @gateway.authorize(@amount, @visa, {:order_id => '16chars---------EXTRA'}) end.check_request do |endpoint, data, headers| assert_match(/<TxnId>16chars---------<\/TxnId>/, data) end.respond_with(successful_authorization_response) @@ -260,7 +268,7 @@ def test_authorize_truncates_order_id_to_16_chars def test_capture_truncates_order_id_to_16_chars stub_comms do - @gateway.capture(@amount, 'identification', {:order_id => "16chars---------EXTRA"}) + @gateway.capture(@amount, 'identification', {:order_id => '16chars---------EXTRA'}) end.check_request do |endpoint, data, headers| assert_match(/<TxnId>16chars---------<\/TxnId>/, data) end.respond_with(successful_authorization_response) @@ -268,7 +276,7 @@ def test_capture_truncates_order_id_to_16_chars def test_refund_truncates_order_id_to_16_chars stub_comms do - @gateway.refund(@amount, 'identification', {:description => 'refund', :order_id => "16chars---------EXTRA"}) + @gateway.refund(@amount, 'identification', {:description => 'refund', :order_id => '16chars---------EXTRA'}) end.check_request do |endpoint, data, headers| assert_match(/<TxnId>16chars---------<\/TxnId>/, data) end.respond_with(successful_authorization_response) @@ -276,7 +284,7 @@ def test_refund_truncates_order_id_to_16_chars def test_purchase_truncates_description_to_50_chars stub_comms do - @gateway.purchase(@amount, @visa, {:description => "50chars-------------------------------------------EXTRA"}) + @gateway.purchase(@amount, @visa, {:description => '50chars-------------------------------------------EXTRA'}) end.check_request do |endpoint, data, headers| assert_match(/<MerchantReference>50chars-------------------------------------------<\/MerchantReference>/, data) end.respond_with(successful_authorization_response) @@ -284,7 +292,7 @@ def test_purchase_truncates_description_to_50_chars def test_authorize_truncates_description_to_50_chars stub_comms do - @gateway.authorize(@amount, @visa, {:description => "50chars-------------------------------------------EXTRA"}) + @gateway.authorize(@amount, @visa, {:description => '50chars-------------------------------------------EXTRA'}) end.check_request do |endpoint, data, headers| assert_match(/<MerchantReference>50chars-------------------------------------------<\/MerchantReference>/, data) end.respond_with(successful_authorization_response) @@ -292,7 +300,7 @@ def test_authorize_truncates_description_to_50_chars def test_capture_truncates_description_to_50_chars stub_comms do - @gateway.capture(@amount, 'identification', {:description => "50chars-------------------------------------------EXTRA"}) + @gateway.capture(@amount, 'identification', {:description => '50chars-------------------------------------------EXTRA'}) end.check_request do |endpoint, data, headers| assert_match(/<MerchantReference>50chars-------------------------------------------<\/MerchantReference>/, data) end.respond_with(successful_authorization_response) @@ -300,12 +308,16 @@ def test_capture_truncates_description_to_50_chars def test_refund_truncates_description_to_50_chars stub_comms do - @gateway.capture(@amount, 'identification', {:description => "50chars-------------------------------------------EXTRA"}) + @gateway.capture(@amount, 'identification', {:description => '50chars-------------------------------------------EXTRA'}) end.check_request do |endpoint, data, headers| assert_match(/<MerchantReference>50chars-------------------------------------------<\/MerchantReference>/, data) end.respond_with(successful_authorization_response) end + def test_transcript_scrubbing + assert_equal scrubbed_transcript, @gateway.scrub(transcript) + end + private def perform_each_transaction_type_with_request_body_assertions(options = {}) @@ -332,7 +344,7 @@ def perform_each_transaction_type_with_request_body_assertions(options = {}) # refund stub_comms do - @gateway.refund(@amount, 'identification', {:description => "description"}.merge(options)) + @gateway.refund(@amount, 'identification', {:description => 'description'}.merge(options)) end.check_request do |endpoint, data, headers| yield data end.respond_with(successful_authorization_response) @@ -423,4 +435,12 @@ def successful_dps_billing_id_token_purchase_response def successful_billing_id_token_purchase_response %(<Txn><Transaction success="1" reco="00" responsetext="APPROVED"><Authorized>1</Authorized><MerchantReference></MerchantReference><CardName>Visa</CardName><Retry>0</Retry><StatusRequired>0</StatusRequired><AuthCode>030817</AuthCode><Amount>10.00</Amount><CurrencyId>554</CurrencyId><InputCurrencyId>554</InputCurrencyId><InputCurrencyName>NZD</InputCurrencyName><CurrencyRate>1.00</CurrencyRate><CurrencyName>NZD</CurrencyName><CardHolderName>LONGBOB LONGSEN</CardHolderName><DateSettlement>20070323</DateSettlement><TxnType>Purchase</TxnType><CardNumber>424242........42</CardNumber><DateExpiry>0808</DateExpiry><ProductId></ProductId><AcquirerDate>20070323</AcquirerDate><AcquirerTime>030817</AcquirerTime><AcquirerId>9000</AcquirerId><Acquirer>Test</Acquirer><TestMode>1</TestMode><CardId>2</CardId><CardHolderResponseText>APPROVED</CardHolderResponseText><CardHolderHelpText>The Transaction was approved</CardHolderHelpText><CardHolderResponseDescription>The Transaction was approved</CardHolderResponseDescription><MerchantResponseText>APPROVED</MerchantResponseText><MerchantHelpText>The Transaction was approved</MerchantHelpText><MerchantResponseDescription>The Transaction was approved</MerchantResponseDescription><UrlFail></UrlFail><UrlSuccess></UrlSuccess><EnablePostResponse>0</EnablePostResponse><PxPayName></PxPayName><PxPayLogoSrc></PxPayLogoSrc><PxPayUserId></PxPayUserId><PxPayXsl></PxPayXsl><PxPayBgColor></PxPayBgColor><AcquirerPort>9999999999-99999999</AcquirerPort><AcquirerTxnRef>12859</AcquirerTxnRef><GroupAccount>9997</GroupAccount><DpsTxnRef>0000000303ace8db</DpsTxnRef><AllowRetry>0</AllowRetry><DpsBillingId></DpsBillingId><BillingId>TEST1234</BillingId><TransactionId>03ace8db</TransactionId><PxHostId>00000003</PxHostId></Transaction><ReCo>00</ReCo><ResponseText>APPROVED</ResponseText><HelpText>The Transaction was approved</HelpText><Success>1</Success><DpsTxnRef>0000000303ace8db</DpsTxnRef><TxnRef></TxnRef></Txn>) end + + def transcript + %(<Txn><CardHolderName>Longbob Longsen</CardHolderName><CardNumber>4111111111111111</CardNumber><DateExpiry>0916</DateExpiry><Cvc2>123</Cvc2><Cvc2Presence>1</Cvc2Presence><Amount>1.00</Amount><InputCurrency>NZD</InputCurrency><TxnId>59956b468905bde7</TxnId><MerchantReference>Store purchase</MerchantReference><EnableAvsData>1</EnableAvsData><AvsAction>1</AvsAction><AvsStreetAddress>456 My Street</AvsStreetAddress><AvsPostCode>K1C2N6</AvsPostCode><PostUsername>WaysactDev</PostUsername><PostPassword>kvr52dw9</PostPassword><TxnType>Purchase</TxnType></Txn>) + end + + def scrubbed_transcript + %(<Txn><CardHolderName>Longbob Longsen</CardHolderName><CardNumber>[FILTERED]</CardNumber><DateExpiry>0916</DateExpiry><Cvc2>[FILTERED]</Cvc2><Cvc2Presence>1</Cvc2Presence><Amount>1.00</Amount><InputCurrency>NZD</InputCurrency><TxnId>59956b468905bde7</TxnId><MerchantReference>Store purchase</MerchantReference><EnableAvsData>1</EnableAvsData><AvsAction>1</AvsAction><AvsStreetAddress>456 My Street</AvsStreetAddress><AvsPostCode>K1C2N6</AvsPostCode><PostUsername>WaysactDev</PostUsername><PostPassword>[FILTERED]</PostPassword><TxnType>Purchase</TxnType></Txn>) + end end diff --git a/test/unit/gateways/paymentez_test.rb b/test/unit/gateways/paymentez_test.rb new file mode 100644 index 00000000000..3e6df5428e6 --- /dev/null +++ b/test/unit/gateways/paymentez_test.rb @@ -0,0 +1,592 @@ +require 'test_helper' + +class PaymentezTest < Test::Unit::TestCase + include CommStub + + def setup + @gateway = PaymentezGateway.new(application_code: 'foo', app_key: 'bar') + @credit_card = credit_card + @elo_credit_card = credit_card('6362970000457013', + :month => 10, + :year => 2020, + :first_name => 'John', + :last_name => 'Smith', + :verification_value => '737', + :brand => 'elo' + ) + @amount = 100 + + @options = { + order_id: '1', + user_id: '123', + billing_address: address, + description: 'Store Purchase', + email: 'a@b.com' + } + end + + def test_successful_purchase + @gateway.expects(:ssl_post).returns(successful_purchase_response) + + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + + assert_equal 'PR-926', response.authorization + assert response.test? + end + + def test_successful_purchase_with_elo + @gateway.expects(:ssl_post).returns(successful_purchase_with_elo_response) + + response = @gateway.purchase(@amount, @elo_credit_card, @options) + assert_success response + + assert_equal 'CI-14952', response.authorization + assert response.test? + end + + def test_successful_purchase_with_token + @gateway.expects(:ssl_post).returns(successful_purchase_response) + + response = @gateway.purchase(@amount, '123456789012345678901234567890', @options) + assert_success response + + assert_equal 'PR-926', response.authorization + assert response.test? + end + + def test_failed_purchase + @gateway.expects(:ssl_post).returns(failed_purchase_response) + + response = @gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_equal Gateway::STANDARD_ERROR_CODE[:card_declined], response.error_code + end + + def test_expired_card + @gateway.expects(:ssl_post).returns(expired_card_response) + + response = @gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_equal Gateway::STANDARD_ERROR_CODE[:card_declined], response.error_code + assert_equal 'Expired card', response.message + end + + def test_successful_authorize + @gateway.stubs(:ssl_post).returns(successful_authorize_response) + + response = @gateway.authorize(@amount, @credit_card, @options) + assert_success response + assert_equal 'CI-635', response.authorization + assert response.test? + end + + def test_successful_authorize_with_elo + @gateway.stubs(:ssl_post).returns(successful_authorize_with_elo_response) + + response = @gateway.authorize(@amount, @elo_credit_card, @options) + assert_success response + assert_equal 'CI-14953', response.authorization + assert response.test? + end + + def test_successful_authorize_with_token + @gateway.stubs(:ssl_post).returns(successful_authorize_response) + + response = @gateway.authorize(@amount, '123456789012345678901234567890', @options) + assert_success response + assert_equal 'CI-635', response.authorization + assert response.test? + end + + def test_failed_authorize + @gateway.expects(:ssl_post).returns(failed_authorize_response) + + response = @gateway.authorize(@amount, @credit_card, @options) + assert_failure response + assert response.test? + end + + def test_successful_capture + @gateway.expects(:ssl_post).returns(successful_capture_response) + + response = @gateway.capture(nil, '1234', @options) + assert_success response + assert_equal 'CI-635', response.authorization + assert response.test? + end + + def test_successful_capture_with_elo + @gateway.expects(:ssl_post).returns(successful_capture_with_elo_response) + + response = @gateway.capture(nil, '1234', @options) + assert_success response + assert_equal 'CI-14953', response.authorization + assert response.test? + end + + def test_successful_capture_with_amount + @gateway.expects(:ssl_post).returns(successful_capture_response) + + response = @gateway.capture(@amount + 1, '1234', @options) + assert_success response + assert_equal 'CI-635', response.authorization + assert response.test? + end + + def test_failed_capture + @gateway.expects(:ssl_post).returns(failed_capture_response) + + response = @gateway.capture(@amount, '1234', @options) + assert_failure response + assert response.test? + end + + def test_successful_refund + @gateway.expects(:ssl_post).returns(successful_refund_response) + + response = @gateway.refund(nil, '1234', @options) + assert_success response + assert response.test? + end + + def test_partial_refund + response = stub_comms do + @gateway.refund(@amount, '1234', @options) + end.check_request do |_endpoint, data, _headers| + assert_match(/"amount":1.0/, data) + end.respond_with(successful_refund_response) + assert_success response + assert response.test? + end + + def test_failed_refund + @gateway.expects(:ssl_post).returns(failed_refund_response) + + response = @gateway.refund(@amount, '1234', @options) + assert_failure response + assert response.test? + end + + def test_successful_void + @gateway.expects(:ssl_post).returns(successful_void_response) + + response = @gateway.void('1234', @options) + assert_success response + assert response.test? + end + + def test_failed_void + @gateway.expects(:ssl_post).returns(failed_void_response) + + response = @gateway.void('1234', @options) + assert_failure response + end + + def test_simple_store + @gateway.expects(:ssl_post).returns(successful_store_response) + + response = @gateway.store(@credit_card, @options) + assert_success response + assert_equal '14436664108567261211', response.authorization + end + + def test_simple_store_with_elo + @gateway.expects(:ssl_post).returns(successful_store_with_elo_response) + + response = @gateway.store(@elo_credit_card, @options) + assert_success response + assert_equal '15550938907932827845', response.authorization + end + + def test_complex_store + @gateway.stubs(:ssl_post).returns(already_stored_response, successful_unstore_response, successful_store_response) + + response = @gateway.store(@credit_card, @options) + assert_success response + end + + def test_paymentez_crashes_fail + @gateway.stubs(:ssl_post).returns(crash_response) + + response = @gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_equal Gateway::STANDARD_ERROR_CODE[:processing_error], response.error_code + end + + def test_scrub + assert @gateway.supports_scrubbing? + assert_equal @gateway.scrub(pre_scrubbed), post_scrubbed + end + + private + + def pre_scrubbed + %q( +opening connection to ccapi-stg.paymentez.com:443... +opened +starting SSL for ccapi-stg.paymentez.com:443... +SSL established +<- "POST /v2/transaction/debit_cc HTTP/1.1\r\nContent-Type: application/json\r\nAuth-Token: U1BETFktTVgtU0VSVkVSOzE1MTM3MDU5OTc7M8I1MjQ1NT5yMWNlZWU0ZjFlYTdiZDBlOGE1MWIxZjBkYzBjZTMyYjZmN2RmNjE4ZGQ5MmNiODhjMTM5MWIyNg==\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: ccapi-stg.paymentez.com\r\nContent-Length: 264\r\n\r\n" +<- "{\"order\":{\"amount\":1.0,\"vat\":0,\"dev_reference\":\"Testing\",\"description\":\"Store Purchase\"},\"card\":{\"number\":\"4111111111111111\",\"holder_name\":\"Longbob Longsen\",\"expiry_month\":9,\"expiry_year\":2018,\"cvc\":\"123\",\"type\":\"vi\"},\"user\":{\"id\":\"123\",\"email\":\"joe@example.com\"}}" +-> "HTTP/1.1 200 OK\r\n" +-> "Server: nginx/1.12.1\r\n" +-> "Date: Tue, 19 Dec 2017 17:51:42 GMT\r\n" +-> "Content-Type: application/json\r\n" +-> "Content-Length: 402\r\n" +-> "Connection: close\r\n" +-> "Vary: Accept-Language, Cookie\r\n" +-> "Content-Language: es\r\n" +-> "\r\n" +reading 402 bytes... +-> "{\"transaction\": {\"status\": \"success\", \"payment_date\": \"2017-12-19T17:51:39.985\", \"amount\": 1.0, \"authorization_code\": \"123456\", \"installments\": 1, \"dev_reference\": \"Testing\", \"message\": \"Response by mock\", \"carrier_code\": \"00\", \"id\": \"PR-871\", \"status_detail\": 3}, \"card\": {\"bin\": \"411111\", \"expiry_year\": \"2018\", \"expiry_month\": \"9\", \"transaction_reference\": \"PR-871\", \"type\": \"vi\", \"number\": \"1111\"}}" +read 402 bytes +Conn close + ) + end + + def post_scrubbed + %q( +opening connection to ccapi-stg.paymentez.com:443... +opened +starting SSL for ccapi-stg.paymentez.com:443... +SSL established +<- "POST /v2/transaction/debit_cc HTTP/1.1\r\nContent-Type: application/json\r\nAuth-Token: [FILTERED]\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: ccapi-stg.paymentez.com\r\nContent-Length: 264\r\n\r\n" +<- "{\"order\":{\"amount\":1.0,\"vat\":0,\"dev_reference\":\"Testing\",\"description\":\"Store Purchase\"},\"card\":{\"number\":[FILTERED],\"holder_name\":\"Longbob Longsen\",\"expiry_month\":9,\"expiry_year\":2018,\"cvc\":[FILTERED],\"type\":\"vi\"},\"user\":{\"id\":\"123\",\"email\":\"joe@example.com\"}}" +-> "HTTP/1.1 200 OK\r\n" +-> "Server: nginx/1.12.1\r\n" +-> "Date: Tue, 19 Dec 2017 17:51:42 GMT\r\n" +-> "Content-Type: application/json\r\n" +-> "Content-Length: 402\r\n" +-> "Connection: close\r\n" +-> "Vary: Accept-Language, Cookie\r\n" +-> "Content-Language: es\r\n" +-> "\r\n" +reading 402 bytes... +-> "{\"transaction\": {\"status\": \"success\", \"payment_date\": \"2017-12-19T17:51:39.985\", \"amount\": 1.0, \"authorization_code\": \"123456\", \"installments\": 1, \"dev_reference\": \"Testing\", \"message\": \"Response by mock\", \"carrier_code\": \"00\", \"id\": \"PR-871\", \"status_detail\": 3}, \"card\": {\"bin\": \"411111\", \"expiry_year\": \"2018\", \"expiry_month\": \"9\", \"transaction_reference\": \"PR-871\", \"type\": \"vi\", \"number\": \"1111\"}}" +read 402 bytes +Conn close + ) + end + + def successful_purchase_response + ' + { + "transaction": { + "status": "success", + "payment_date": "2017-12-19T20:29:12.715", + "amount": 1, + "authorization_code": "123456", + "installments": 1, + "dev_reference": "Testing", + "message": "Response by mock", + "carrier_code": "00", + "id": "PR-926", + "status_detail": 3 + }, + "card": { + "bin": "411111", + "expiry_year": "2018", + "expiry_month": "9", + "transaction_reference": "PR-926", + "type": "vi", + "number": "1111" + } + } + ' + end + + def successful_purchase_with_elo_response + ' + { + "transaction": { + "status": "success", + "payment_date": "2019-03-06T16:47:13.430", + "amount": 1, + "authorization_code": "TEST00", + "installments": 1, + "dev_reference": "Testing", + "message": "Response by mock", + "carrier_code": null, + "id": "CI-14952", + "status_detail": 3 + }, + "card": { + "bin": "636297", + "expiry_year": "2020", + "expiry_month": "10", + "transaction_reference": "CI-14952", + "type": "el", + "number": "7013", + "origin": "Paymentez" + } + } + ' + end + + def failed_purchase_response + ' + { + "transaction": { + "status": "failure", + "payment_date": null, + "amount": 1, + "authorization_code": null, + "installments": 1, + "dev_reference": "Testing", + "message": "Response by mock", + "carrier_code": "3", + "id": "PR-945", + "status_detail": 9 + }, + "card": { + "bin": "424242", + "expiry_year": "2018", + "expiry_month": "9", + "transaction_reference": "PR-945", + "type": "vi", + "number": "4242" + } + } + ' + end + + def successful_authorize_response + ' + { + "transaction": { + "status": "success", + "payment_date": "2017-12-21T18:04:42", + "amount": 1, + "authorization_code": "487897", + "installments": 1, + "dev_reference": "Testing", + "message": "Operation Successful", + "carrier_code": "4", + "id": "CI-635", + "status_detail": 0 + }, + "card": { + "bin": "411111", + "status": "valid", + "token": "12032069702317830187", + "expiry_year": "2018", + "expiry_month": "9", + "transaction_reference": "CI-635", + "type": "vi", + "number": "1111" + } + } + ' + end + + def successful_authorize_with_elo_response + ' + { + "transaction": { + "status": "success", + "payment_date": "2019-03-06T16:53:36.336", + "amount": 1, + "authorization_code": "TEST00", + "installments": 1, + "dev_reference": "Testing", + "message": "Response by mock", + "carrier_code": null, + "id": "CI-14953", + "status_detail": 0 + }, + "card": { + "bin": "636297", + "status": "", + "token": "", + "expiry_year": "2020", + "expiry_month": "10", + "transaction_reference": "CI-14953", + "type": "el", + "number": "7013", + "origin": "Paymentez" + } + } + ' + end + + def failed_authorize_response + ' + { + "transaction": { + "status": "failure", + "payment_date": null, + "amount": 1.0, + "authorization_code": null, + "installments": 1, + "dev_reference": "Testing", + "message": null, + "carrier_code": "3", + "id": "CI-1223", + "status_detail": 9 + }, + "card": { + "bin": "424242", + "status": null, + "token": "6461587429110733892", + "expiry_year": "2019", + "expiry_month": "9", + "transaction_reference": "CI-1223", + "type": "vi", + "number": "4242", + "origin": "Paymentez" + } + } + ' + end + + def successful_capture_response + ' + { + "transaction": { + "status": "success", + "payment_date": "2017-12-21T18:04:42", + "amount": 1, + "authorization_code": "487897", + "installments": 1, + "dev_reference": "Testing", + "message": "Operation Successful", + "carrier_code": "6", + "id": "CI-635", + "status_detail": 3 + }, + "card": { + "bin": "411111", + "status": "valid", + "token": "12032069702317830187", + "expiry_year": "2018", + "expiry_month": "9", + "transaction_reference": "CI-635", + "type": "vi", + "number": "1111" + } + } + ' + end + + def successful_capture_with_elo_response + ' + { + "transaction": { + "status": "success", + "payment_date": "2019-03-06T16:53:36", + "amount": 1, + "authorization_code": "TEST00", + "installments": 1, + "dev_reference": "Testing", + "message": "Response by mock", + "carrier_code": null, + "id": "CI-14953", + "status_detail": 3 + }, + "card": { + "bin": "636297", + "status": "", + "token": "", + "expiry_year": "2020", + "expiry_month": "10", + "transaction_reference": "CI-14953", + "type": "el", + "number": "7013", + "origin": "Paymentez" + } + } + ' + end + + def failed_capture_response + '{"error": {"type": "Carrier not supported", "help": "", "description": "{}"}}' + end + + def successful_void_response + '{"status": "success", "detail": "Completed"}' + end + + def failed_void_response + '{"error": {"type": "Carrier not supported", "help": "", "description": "{}"}}' + end + + alias_method :successful_refund_response, :successful_void_response + alias_method :failed_refund_response, :failed_void_response + + def already_stored_response + '{"error": {"type": "Card already added: 14436664108567261211", "help": "If you want to update the card, first delete it", "description": "{}"}}' + end + + def successful_unstore_response + '{"message": "card deleted"}' + end + + def successful_store_response + '{"card": {"bin": "411111", "status": "valid", "token": "14436664108567261211", "message": "", "expiry_year": "2018", "expiry_month": "9", "transaction_reference": "PR-959", "type": "vi", "number": "1111"}}' + end + + def successful_store_with_elo_response + '{"card": {"bin": "636297", "status": "valid", "token": "15550938907932827845", "message": "", "expiry_year": "2020", "expiry_month": "10", "transaction_reference": "CI-14956", "type": "el", "number": "7013", "origin": "Paymentez"}}' + end + + def failed_store_response + ' + { + "card": { + "bin": "424242", + "status": "rejected", + "token": "2026849624512750545", + "message": "Not Authorized", + "expiry_year": "2018", + "expiry_month": "9", + "transaction_reference": "CI-606", + "type": "vi", + "number": "4242" + } + } + ' + end + + def expired_card_response + ' + { + "transaction":{ + "status":"failure", + "payment_date":null, + "amount":1.0, + "authorization_code":null, + "installments":1, + "dev_reference":"ci123", + "message":"Expired card", + "carrier_code":"54", + "id":"PR-25", + "status_detail":9 + }, + "card":{ + "bin":"528851", + "expiry_year":"2024", + "expiry_month":"4", + "transaction_reference":"PR-25", + "type":"mc", + "number":"9794", + "origin":"Paymentez" + } + } + ' + end + + def crash_response + ' + <html> + <head> + <title>Internal Server Error</title> + </head> + <body> + <h1><p>Internal Server Error</p></h1> + + </body> + </html> + ' + end +end diff --git a/test/unit/gateways/paymill_test.rb b/test/unit/gateways/paymill_test.rb index 8715a00e151..f44e8b07c30 100644 --- a/test/unit/gateways/paymill_test.rb +++ b/test/unit/gateways/paymill_test.rb @@ -13,17 +13,17 @@ def test_successful_purchase assert response = @gateway.purchase(@amount, @credit_card) assert_success response - assert_equal "tran_c94ba7df2dae8fd55028df41173c;", response.authorization - assert_equal "Transaction approved", response.message + assert_equal 'tran_c94ba7df2dae8fd55028df41173c;', response.authorization + assert_equal 'Operation successful', response.message assert_equal 20000, response.params['data']['response_code'] assert_equal 'pay_b8e6a28fc5e5e1601cdbefbaeb8a', response.params['data']['payment']['id'] assert_equal '5100', response.params['data']['payment']['last4'] - assert_nil response.cvv_result["message"] - assert_nil response.avs_result["message"] + assert_nil response.cvv_result['message'] + assert_nil response.avs_result['message'] assert response.test? end - def test_failed_purchase_with_invalid_credit_card + def test_failed_store_card_attempting_purchase @gateway.expects(:raw_ssl_request).returns(failed_store_response) response = @gateway.purchase(@amount, @credit_card) assert_failure response @@ -31,6 +31,21 @@ def test_failed_purchase_with_invalid_credit_card assert_equal '000.100.201', response.params['transaction']['processing']['return']['code'] end + def test_broken_gateway + @gateway.expects(:raw_ssl_request).returns(broken_gateway_response) + response = @gateway.purchase(@amount, @credit_card) + assert_failure response + assert_equal "File not found.\n", response.message + end + + def test_failed_purchase + @gateway.stubs(:raw_ssl_request).returns(successful_store_response, failed_purchase_response) + response = @gateway.purchase(@amount, @credit_card) + assert_failure response + assert_equal 'Card declined', response.message + assert_equal 50102, response.params['data']['response_code'] + end + def test_invalid_login @gateway.stubs(:raw_ssl_request).returns(successful_store_response, failed_login_response) response = @gateway.purchase(@amount, @credit_card) @@ -38,6 +53,20 @@ def test_invalid_login assert_equal 'Access Denied', response.message end + def test_empty_server_response + @gateway.stubs(:raw_ssl_request).returns(successful_store_response, MockResponse.failed('')) + response = @gateway.purchase(@amount, @credit_card) + assert_failure response + assert_equal "Unable to parse error response: ''", response.message + end + + def test_invalid_server_response + @gateway.stubs(:raw_ssl_request).returns(successful_store_response, MockResponse.failed('not-json')) + response = @gateway.purchase(@amount, @credit_card) + assert_failure response + assert_equal "Unable to parse error response: 'not-json'", response.message + end + def test_invalid_login_on_storing_card @gateway.stubs(:raw_ssl_request).returns(failed_store_invalid_credentials_response, successful_purchase_response) response = @gateway.purchase(@amount, @credit_card) @@ -52,19 +81,40 @@ def test_successful_authorize_and_capture assert_success response assert response.test? - assert_equal "tran_50fb13e10636cf1e59e13018d100;preauth_57c0c87ae3d193f66dc8", response.authorization - assert_equal "Transaction approved", response.message - assert_equal '5100', response.params['data']['payment']['last4'] - assert_equal 10001, response.params['data']['response_code'] - assert_nil response.avs_result["message"] - assert_nil response.cvv_result["message"] + assert_equal 'tran_4c612d5293e26d56d986eb89648c;preauth_fdf916cab73b97c4a139', response.authorization + assert_equal 'Operation successful', response.message + assert_equal '0004', response.params['data']['payment']['last4'] + assert_equal 20000, response.params['data']['response_code'] + assert_nil response.avs_result['message'] + assert_nil response.cvv_result['message'] @gateway.expects(:raw_ssl_request).returns(successful_capture_response) response = @gateway.capture(@amount, response.authorization) assert_success response assert response.test? assert_equal 20000, response.params['data']['response_code'] - assert_equal "Transaction approved", response.message + assert_equal 'Operation successful', response.message + end + + def test_failed_authorize + @gateway.stubs(:raw_ssl_request).returns(successful_store_response, failed_authorize_response) + response = @gateway.authorize(@amount, @credit_card) + assert_failure response + assert_equal 'Card declined', response.message + assert_equal 50102, response.params['data']['response_code'] + end + + def test_successful_authorize_and_void + @gateway.stubs(:raw_ssl_request).returns(successful_store_response, successful_authorize_response) + + assert response = @gateway.authorize(@amount, @credit_card) + assert_success response + + @gateway.expects(:raw_ssl_request).returns(successful_void_response) + response = @gateway.void(response.authorization) + assert_success response + assert response.test? + assert_equal 'Transaction approved.', response.message end def test_failed_capture @@ -88,7 +138,23 @@ def test_successful_refund assert_success refund assert response.test? - assert_equal 'Transaction approved', refund.message + assert_equal 'Operation successful', refund.message + assert_equal 'tran_89c8728e94273510afa99ab64e45', refund.params['data']['transaction']['id'] + assert_equal 'refund_d02807f46181c0919016;', refund.authorization + assert_equal 20000, refund.params['data']['response_code'] + end + + def test_successful_refund_response_with_string_response_code + @gateway.stubs(:raw_ssl_request).returns(successful_store_response, successful_purchase_response) + assert response = @gateway.purchase(@amount, @credit_card) + assert_success response + + @gateway.expects(:raw_ssl_request).returns(successful_refund_response_with_string_response_code) + assert refund = @gateway.refund(@amount, response.authorization) + assert_success refund + assert response.test? + + assert_equal 'Operation successful', refund.message assert_equal 'tran_89c8728e94273510afa99ab64e45', refund.params['data']['transaction']['id'] assert_equal 'refund_d02807f46181c0919016;', refund.authorization assert_equal 20000, refund.params['data']['response_code'] @@ -110,11 +176,25 @@ def test_successful_store assert response = @gateway.store(@credit_card) assert_success response - assert_equal "tok_4f9a571b39bd8d0b4db5", response.authorization + assert_equal 'tok_4f9a571b39bd8d0b4db5', response.authorization assert_equal "Request successfully processed in 'Merchant in Connector Test Mode'", response.message assert response.test? end + def test_store_includes_currency_and_amount + expected_currency = 'USD' + expected_amount = 100 + + @gateway.expects(:raw_ssl_request).with( + :get, + store_endpoint_url(@credit_card, expected_currency, expected_amount), + nil, + {} + ).returns(successful_store_response, successful_purchase_response) + + @gateway.store(@credit_card) + end + def test_failed_store_with_invalid_credit_card @gateway.expects(:raw_ssl_request).returns(failed_store_response) response = @gateway.store(@credit_card) @@ -126,34 +206,43 @@ def test_failed_store_with_invalid_credit_card def test_successful_purchase_with_token @gateway.stubs(:raw_ssl_request).returns(successful_purchase_response) - assert response = @gateway.purchase(@amount, "token") + assert response = @gateway.purchase(@amount, 'token') assert_success response - assert_equal "tran_c94ba7df2dae8fd55028df41173c;", response.authorization - assert_equal "Transaction approved", response.message + assert_equal 'tran_c94ba7df2dae8fd55028df41173c;', response.authorization + assert_equal 'Operation successful', response.message assert_equal 20000, response.params['data']['response_code'] assert_equal 'pay_b8e6a28fc5e5e1601cdbefbaeb8a', response.params['data']['payment']['id'] assert_equal '5100', response.params['data']['payment']['last4'] - assert_nil response.cvv_result["message"] - assert_nil response.avs_result["message"] + assert_nil response.cvv_result['message'] + assert_nil response.avs_result['message'] assert response.test? end def test_successful_authorize_with_token @gateway.stubs(:raw_ssl_request).returns(successful_authorize_response) - assert response = @gateway.authorize(@amount, "token") + assert response = @gateway.authorize(@amount, 'token') assert_success response assert response.test? - assert_equal "tran_50fb13e10636cf1e59e13018d100;preauth_57c0c87ae3d193f66dc8", response.authorization - assert_equal "Transaction approved", response.message - assert_equal '5100', response.params['data']['payment']['last4'] - assert_equal 10001, response.params['data']['response_code'] - assert_nil response.avs_result["message"] - assert_nil response.cvv_result["message"] + assert_equal 'tran_4c612d5293e26d56d986eb89648c;preauth_fdf916cab73b97c4a139', response.authorization + assert_equal 'Operation successful', response.message + assert_equal '0004', response.params['data']['payment']['last4'] + assert_equal 20000, response.params['data']['response_code'] + assert_nil response.avs_result['message'] + assert_nil response.cvv_result['message'] + end + + def test_transcript_scrubbing + assert_equal scrubbed_transcript, @gateway.scrub(transcript) end private + + def store_endpoint_url(credit_card, currency, amount) + "https://test-token.paymill.com?account.holder=#{credit_card.first_name}+#{credit_card.last_name}&account.number=#{credit_card.number}&account.expiry.month=#{'%02d' % credit_card.month}&account.expiry.year=#{credit_card.year}&account.verification=#{credit_card.verification_value}&presentation.amount3D=#{amount}&presentation.currency3D=#{currency}&channel.id=PUBLIC&jsonPFunction=jsonPFunction&transaction.mode=CONNECTOR_TEST" + end + def successful_store_response MockResponse.new 200, %[jsonPFunction({"transaction":{"mode":"CONNECTOR_TEST","channel":"57313835619696ac361dc591bc973626","response":"SYNC","payment":{"code":"CC.DB"},"processing":{"code":"CC.DB.90.00","reason":{"code":"00","message":"Successful Processing"},"result":"ACK","return":{"code":"000.100.112","message":"Request successfully processed in 'Merchant in Connector Test Mode'"},"timestamp":"2013-02-12 21:33:43"},"identification":{"shortId":"1998.1832.1612","uniqueId":"tok_4f9a571b39bd8d0b4db5"}}})] end @@ -219,81 +308,244 @@ def successful_purchase_response JSON end + def failed_purchase_response + MockResponse.failed <<-JSON + { + "data":{ + "id":"tran_a432ce3b113cdd65b48e0d05db88", + "amount":"100", + "origin_amount":100, + "status":"failed", + "description":null, + "livemode":false, + "refunds":null, + "currency":"EUR", + "created_at":1385054845, + "updated_at":1385054845, + "response_code":50102, + "short_id":null, + "is_fraud":false, + "invoices":[ + + ], + "app_id":null, + "fees":[ + + ], + "payment":{ + "id":"pay_8b75b960574031979a880f98", + "type":"creditcard", + "client":"client_a69d8452d530ed20b297", + "card_type":"mastercard", + "country":null, + "expire_month":"5", + "expire_year":"2020", + "card_holder":"", + "last4":"5100", + "created_at":1385054844, + "updated_at":1385054845, + "app_id":null + }, + "client":{ + "id":"client_a69d8452d530ed20b297", + "email":null, + "description":null, + "created_at":1385054845, + "updated_at":1385054845, + "app_id":null, + "payment":[ + + ], + "subscription":null + }, + "preauthorization":null + }, + "mode":"test" + } + JSON + end + def successful_authorize_response MockResponse.succeeded <<-JSON - { "data":{ - "id":"tran_50fb13e10636cf1e59e13018d100", - "amount":"100", - "origin_amount":100, - "status":"preauth", - "description":null, - "livemode":false, - "refunds":null, - "currency":"EUR", - "created_at":1360787311, - "updated_at":1360787311, - "response_code":10001, - "invoices":[ - - ], - "payment":{ - "id":"pay_58e0662ef367027b2356f263e5aa", - "type":"creditcard", - "client":"client_9e4b7b0d61adc9a9e64e", - "card_type":"mastercard", - "country":null, - "expire_month":9, - "expire_year":2014, - "card_holder":null, - "last4":"5100", - "created_at":1360787310, - "updated_at":1360787311 - }, - "client":{ - "id":"client_9e4b7b0d61adc9a9e64e", - "email":null, - "description":null, - "created_at":1360787311, - "updated_at":1360787311, - "payment":[ + { + "data":{ + "id":"tran_4c612d5293e26d56d986eb89648c", + "amount":"100", + "origin_amount":100, + "status":"preauth", + "description":null, + "livemode":false, + "refunds":null, + "currency":"EUR", + "created_at":1385054035, + "updated_at":1385054035, + "response_code":20000, + "short_id":"7357.7357.7357", + "is_fraud":false, + "invoices":[ - ], - "subscription":null + ], + "app_id":null, + "fees":[ + + ], + "payment":{ + "id":"pay_f9ff269434185e0789106758", + "type":"creditcard", + "client":"client_d5179e1b6a8f596b19b9", + "card_type":"mastercard", + "country":null, + "expire_month":"9", + "expire_year":"2014", + "card_holder":"", + "last4":"0004", + "created_at":1385054033, + "updated_at":1385054035, + "app_id":null + }, + "client":{ + "id":"client_d5179e1b6a8f596b19b9", + "email":null, + "description":null, + "created_at":1385054035, + "updated_at":1385054035, + "app_id":null, + "payment":[ + + ], + "subscription":null + }, + "preauthorization":{ + "id":"preauth_fdf916cab73b97c4a139", + "amount":"100", + "currency":"EUR", + "status":"closed", + "livemode":false, + "created_at":1385054035, + "updated_at":1385054035, + "app_id":null, + "payment":{ + "id":"pay_f9ff269434185e0789106758", + "type":"creditcard", + "client":"client_d5179e1b6a8f596b19b9", + "card_type":"mastercard", + "country":null, + "expire_month":"9", + "expire_year":"2014", + "card_holder":"", + "last4":"0004", + "created_at":1385054033, + "updated_at":1385054035, + "app_id":null + }, + "client":{ + "id":"client_d5179e1b6a8f596b19b9", + "email":null, + "description":null, + "created_at":1385054035, + "updated_at":1385054035, + "app_id":null, + "payment":[ + + ], + "subscription":null + } + } }, - "preauthorization":{ - "id":"preauth_57c0c87ae3d193f66dc8", + "mode":"test" + } + JSON + end + + # Paymill returns an HTTP Status code of 200 for an auth failure. + def failed_authorize_response + MockResponse.succeeded <<-JSON + { + "data":{ + "id":"tran_e53189278c7250bfa15c9c580ff2", "amount":"100", - "status":"closed", + "origin_amount":100, + "status":"failed", + "description":null, "livemode":false, - "created_at":1360787311, - "updated_at":1360787311, + "refunds":null, + "currency":"EUR", + "created_at":1385054501, + "updated_at":1385054501, + "response_code":50102, + "short_id":null, + "is_fraud":false, + "invoices":[ + + ], + "app_id":null, + "fees":[ + + ], "payment":{ - "id":"pay_58e0662ef367027b2356f263e5aa", + "id":"pay_7bc2d73764f38040df934995", "type":"creditcard", - "client":"client_9e4b7b0d61adc9a9e64e", + "client":"client_531e6247ff900e734884", "card_type":"mastercard", "country":null, - "expire_month":9, - "expire_year":2014, - "card_holder":null, + "expire_month":"5", + "expire_year":"2020", + "card_holder":"", "last4":"5100", - "created_at":1360787310, - "updated_at":1360787311 - }, - "client":{ - "id":"client_9e4b7b0d61adc9a9e64e", + "created_at":1385054500, + "updated_at":1385054501, + "app_id":null + }, + "client":{ + "id":"client_531e6247ff900e734884", "email":null, "description":null, - "created_at":1360787311, - "updated_at":1360787311, + "created_at":1385054501, + "updated_at":1385054501, + "app_id":null, "payment":[ ], "subscription":null + }, + "preauthorization":{ + "id":"preauth_cfa6a29b4c679efee58b", + "amount":"100", + "currency":"EUR", + "status":"failed", + "livemode":false, + "created_at":1385054501, + "updated_at":1385054501, + "app_id":null, + "payment":{ + "id":"pay_7bc2d73764f38040df934995", + "type":"creditcard", + "client":"client_531e6247ff900e734884", + "card_type":"mastercard", + "country":null, + "expire_month":"5", + "expire_year":"2020", + "card_holder":"", + "last4":"5100", + "created_at":1385054500, + "updated_at":1385054501, + "app_id":null + }, + "client":{ + "id":"client_531e6247ff900e734884", + "email":null, + "description":null, + "created_at":1385054501, + "updated_at":1385054501, + "app_id":null, + "payment":[ + + ], + "subscription":null + } } - } - }, - "mode":"test" + }, + "mode":"test" } JSON end @@ -378,6 +630,15 @@ def successful_capture_response JSON end + def successful_void_response + MockResponse.succeeded <<-JSON + { + "data":[], + "mode":"test" + } + JSON + end + def successful_refund_response MockResponse.succeeded <<-JSON { @@ -437,30 +698,83 @@ def successful_refund_response JSON end + def successful_refund_response_with_string_response_code + MockResponse.succeeded <<-JSON + { + "data":{ + "id":"refund_d02807f46181c0919016", + "amount":"100", + "status":"refunded", + "description":null, + "livemode":false, + "created_at":1360892424, + "updated_at":1360892424, + "response_code":20000, + "transaction":{ + "id":"tran_89c8728e94273510afa99ab64e45", + "amount":"000", + "origin_amount":100, + "status":"refunded", + "description":null, + "livemode":false, + "refunds":null, + "currency":"EUR", + "created_at":1360892424, + "updated_at":1360892424, + "response_code":"20000", + "invoices":[ + + ], + "payment":{ + "id":"pay_e7f0738e00f3cd57ff00c60f9b72", + "type":"creditcard", + "client":"client_17c00b38c5b6fc62c3e6", + "card_type":"mastercard", + "country":null, + "expire_month":9, + "expire_year":2014, + "card_holder":null, + "last4":"5100", + "created_at":1360892423, + "updated_at":1360892424 + }, + "client":{ + "id":"client_17c00b38c5b6fc62c3e6", + "email":null, + "description":null, + "created_at":1360892424, + "updated_at":1360892424, + "payment":[ + "pay_e7f0738e00f3cd57ff00c60f9b72" + ], + "subscription":null + }, + "preauthorization":null + } + }, + "mode":"test" + } + JSON + end + def failed_refund_response MockResponse.new 412, %[{"error":"Amount to high","exception":"refund_amount_to_high"}] end + def broken_gateway_response + MockResponse.new(404, "File not found.\n") + end + def failed_capture_response MockResponse.new 409, %[{"error":"Preauthorization has already been used","exception":"preauthorization_already_used"}] end - class MockResponse - attr_reader :code, :body - def self.succeeded(body) - MockResponse.new(200, body) - end - - def self.failed(body) - MockResponse.new(422, body) - end - - def initialize(code, body, headers={}) - @code, @body, @headers = code, body, headers - end + def transcript + 'connection_uri=https://test-token.paymill.com?account.number=5500000000000004&account.expiry.month=09&account.expiry.year=2016&account.verification=123' + end - def [](header) - @headers[header] - end + def scrubbed_transcript + 'connection_uri=https://test-token.paymill.com?account.number=[FILTERED]&account.expiry.month=09&account.expiry.year=2016&account.verification=[FILTERED]' end + end diff --git a/test/unit/gateways/paypal/paypal_common_api_test.rb b/test/unit/gateways/paypal/paypal_common_api_test.rb index 7428848a955..49751604ab9 100644 --- a/test/unit/gateways/paypal/paypal_common_api_test.rb +++ b/test/unit/gateways/paypal/paypal_common_api_test.rb @@ -6,7 +6,9 @@ class CommonPaypalGateway < ActiveMerchant::Billing::Gateway include ActiveMerchant::Billing::PaypalCommonAPI def currency(code); 'USD'; end + def localized_amount(num, code); num; end + def commit(a, b); end end @@ -21,15 +23,16 @@ def setup :pem => 'PEM' ) - @address = { :address1 => '1234 My Street', - :address2 => 'Apt 1', - :company => 'Widgets Inc', - :city => 'Ottawa', - :state => 'ON', - :zip => 'K1C2N6', - :country => 'Canada', - :phone => '(555)555-5555' - } + @address = { + :address1 => '1234 My Street', + :address2 => 'Apt 1', + :company => 'Widgets Inc', + :city => 'Ottawa', + :state => 'ON', + :zip => 'K1C2N6', + :country => 'Canada', + :phone => '(555)555-5555' + } end def xml_builder @@ -83,7 +86,7 @@ def test_build_request_wrapper_plain def test_build_request_wrapper_with_request_details result = @gateway.send(:build_request_wrapper, 'Action', :request_details => true) do |xml| - xml.tag! 'n2:TransactionID', 'baz' + xml.tag! 'n2:TransactionID', 'baz' end assert_equal 'baz', REXML::XPath.first(REXML::Document.new(result), '//ActionReq/ActionRequest/n2:ActionRequestDetails/n2:TransactionID').text end @@ -115,14 +118,13 @@ def test_balance_cleans_up_currencies_values_like_0 end def test_build_do_authorize_request - request = REXML::Document.new(@gateway.send(:build_do_authorize,123, 100, :currency => 'USD')) + request = REXML::Document.new(@gateway.send(:build_do_authorize, 123, 100, :currency => 'USD')) assert_equal '123', REXML::XPath.first(request, '//DoAuthorizationReq/DoAuthorizationRequest/TransactionID').text assert_equal '1.00', REXML::XPath.first(request, '//DoAuthorizationReq/DoAuthorizationRequest/Amount').text end - def test_build_manage_pending_transaction_status_request - request = REXML::Document.new(@gateway.send(:build_manage_pending_transaction_status,123, 'Accept')) + request = REXML::Document.new(@gateway.send(:build_manage_pending_transaction_status, 123, 'Accept')) assert_equal '123', REXML::XPath.first(request, '//ManagePendingTransactionStatusReq/ManagePendingTransactionStatusRequest/TransactionID').text assert_equal 'Accept', REXML::XPath.first(request, '//ManagePendingTransactionStatusReq/ManagePendingTransactionStatusRequest/Action').text end @@ -134,10 +136,12 @@ def test_transaction_search_requires end def test_build_transaction_search_request - options = {:start_date => DateTime.new(2012, 2, 21, 0), + options = { + :start_date => DateTime.new(2012, 2, 21, 0), :end_date => DateTime.new(2012, 3, 21, 0), :receiver => 'foo@example.com', - :first_name => 'Robert'} + :first_name => 'Robert' + } request = REXML::Document.new(@gateway.send(:build_transaction_search, options)) assert_match %r{^2012-02-21T\d{2}:00:00Z$}, REXML::XPath.first(request, '//TransactionSearchReq/TransactionSearchRequest/StartDate').text assert_match %r{^2012-03-21T\d{2}:00:00Z$}, REXML::XPath.first(request, '//TransactionSearchReq/TransactionSearchRequest/EndDate').text @@ -153,9 +157,9 @@ def test_build_reference_transaction_request def test_build_reference_transaction_gets_ip request = REXML::Document.new(@gateway.send(:build_reference_transaction_request, - 100, - :reference_id => 'id', - :ip => '127.0.0.1')) + 100, + :reference_id => 'id', + :ip => '127.0.0.1')) assert_equal '100', REXML::XPath.first(request, '//n2:PaymentDetails/n2:OrderTotal').text assert_equal 'id', REXML::XPath.first(request, '//DoReferenceTransactionReq/DoReferenceTransactionRequest/n2:DoReferenceTransactionRequestDetails/n2:ReferenceID').text assert_equal '127.0.0.1', REXML::XPath.first(request, '//DoReferenceTransactionReq/DoReferenceTransactionRequest/n2:DoReferenceTransactionRequestDetails/n2:IPAddress').text diff --git a/test/unit/gateways/paypal_digital_goods_test.rb b/test/unit/gateways/paypal_digital_goods_test.rb index b3b35e9cd73..4b433f38843 100644 --- a/test/unit/gateways/paypal_digital_goods_test.rb +++ b/test/unit/gateways/paypal_digital_goods_test.rb @@ -1,122 +1,122 @@ require 'test_helper' class PaypalDigitalGoodsTest < Test::Unit::TestCase - TEST_REDIRECT_URL = 'https://www.sandbox.paypal.com/incontext?token=1234567890&useraction=commit' - LIVE_REDIRECT_URL = 'https://www.paypal.com/incontext?token=1234567890&useraction=commit' - + TEST_REDIRECT_URL = 'https://www.sandbox.paypal.com/incontext?cmd=_express-checkout&token=1234567890&useraction=commit' + MOBILE_TEST_REDIRECT_URL = 'https://www.sandbox.paypal.com/incontext?cmd=_express-checkout-mobile&token=1234567890&useraction=commit' + LIVE_REDIRECT_URL = 'https://www.paypal.com/incontext?cmd=_express-checkout&token=1234567890&useraction=commit' + MOBILE_LIVE_REDIRECT_URL = 'https://www.paypal.com/incontext?cmd=_express-checkout-mobile&token=1234567890&useraction=commit' + def setup @gateway = PaypalDigitalGoodsGateway.new( - :login => 'cody', + :login => 'cody', :password => 'test', :pem => 'PEM' ) - Base.gateway_mode = :test - end - + Base.mode = :test + end + def teardown - Base.gateway_mode = :test - end + Base.mode = :test + end def test_live_redirect_url - Base.gateway_mode = :production + Base.mode = :production assert_equal LIVE_REDIRECT_URL, @gateway.redirect_url_for('1234567890') + assert_equal MOBILE_LIVE_REDIRECT_URL, @gateway.redirect_url_for('1234567890', mobile: true) end def test_test_redirect_url - assert_equal :test, Base.gateway_mode + assert_equal :test, Base.mode assert_equal TEST_REDIRECT_URL, @gateway.redirect_url_for('1234567890') + assert_equal MOBILE_TEST_REDIRECT_URL, @gateway.redirect_url_for('1234567890', mobile: true) end def test_setup_request_invalid_requests - assert_raise ArgumentError do - @gateway.setup_purchase(100, - :ip => "127.0.0.1", - :description => "Test Title", - :return_url => "http://return.url", - :cancel_return_url => "http://cancel.url") - end + assert_raise ArgumentError do + @gateway.setup_purchase(100, + :ip => '127.0.0.1', + :description => 'Test Title', + :return_url => 'http://return.url', + :cancel_return_url => 'http://cancel.url') + end - assert_raise ArgumentError do - @gateway.setup_purchase(100, - :ip => "127.0.0.1", - :description => "Test Title", - :return_url => "http://return.url", - :cancel_return_url => "http://cancel.url", - :items => [ ]) - end + assert_raise ArgumentError do + @gateway.setup_purchase(100, + :ip => '127.0.0.1', + :description => 'Test Title', + :return_url => 'http://return.url', + :cancel_return_url => 'http://cancel.url', + :items => [ ]) + end - assert_raise ArgumentError do - @gateway.setup_purchase(100, - :ip => "127.0.0.1", - :description => "Test Title", - :return_url => "http://return.url", - :cancel_return_url => "http://cancel.url", - :items => [ Hash.new ] ) - end + assert_raise ArgumentError do + @gateway.setup_purchase(100, + :ip => '127.0.0.1', + :description => 'Test Title', + :return_url => 'http://return.url', + :cancel_return_url => 'http://cancel.url', + :items => [ Hash.new ]) + end - assert_raise ArgumentError do - @gateway.setup_purchase(100, - :ip => "127.0.0.1", - :description => "Test Title", - :return_url => "http://return.url", - :cancel_return_url => "http://cancel.url", - :items => [ { :name => "Charge", - :number => "1", - :quantity => "1", - :amount => 100, - :description => "Description", - :category => "Physical" } ] ) - end + assert_raise ArgumentError do + @gateway.setup_purchase(100, + :ip => '127.0.0.1', + :description => 'Test Title', + :return_url => 'http://return.url', + :cancel_return_url => 'http://cancel.url', + :items => [ { :name => 'Charge', + :number => '1', + :quantity => '1', + :amount => 100, + :description => 'Description', + :category => 'Physical' } ]) + end end - def test_build_setup_request_valid @gateway.expects(:ssl_post).returns(successful_setup_response) - + @gateway.setup_purchase(100, - :ip => "127.0.0.1", - :description => "Test Title", - :return_url => "http://return.url", - :cancel_return_url => "http://cancel.url", - :items => [ { :name => "Charge", - :number => "1", - :quantity => "1", + :ip => '127.0.0.1', + :description => 'Test Title', + :return_url => 'http://return.url', + :cancel_return_url => 'http://cancel.url', + :items => [ { :name => 'Charge', + :number => '1', + :quantity => '1', :amount => 100, - :description => "Description", - :category => "Digital" } ] ) - + :description => 'Description', + :category => 'Digital' } ]) end - private def successful_setup_response -"<?xml version=\"1.0\" encoding=\"UTF-8\"?> -<SOAP-ENV:Envelope xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:SOAP-ENC=\"http://schemas.xmlsoap.org/soap/encoding/\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xmlns:cc=\"urn:ebay:apis:CoreComponentTypes\" xmlns:wsu=\"http://schemas.xmlsoap.org/ws/2002/07/utility\" xmlns:saml=\"urn:oasis:names:tc:SAML:1.0:assertion\" xmlns:ds=\"http://www.w3.org/2000/09/xmldsig#\" xmlns:wsse=\"http://schemas.xmlsoap.org/ws/2002/12/secext\" xmlns:ed=\"urn:ebay:apis:EnhancedDataTypes\" xmlns:ebl=\"urn:ebay:apis:eBLBaseComponents\" xmlns:ns=\"urn:ebay:api:PayPalAPI\"> - <SOAP-ENV:Header> - <Security xmlns=\"http://schemas.xmlsoap.org/ws/2002/12/secext\" xsi:type=\"wsse:SecurityType\"></Security> - <RequesterCredentials xmlns=\"urn:ebay:api:PayPalAPI\" xsi:type=\"ebl:CustomSecurityHeaderType\"> - <Credentials xmlns=\"urn:ebay:apis:eBLBaseComponents\" xsi:type=\"ebl:UserIdPasswordType\"> - <Username xsi:type=\"xs:string\"></Username> - <Password xsi:type=\"xs:string\"></Password> - <Signature xsi:type=\"xs:string\">OMGOMGOMGOMGOMG</Signature> - <Subject xsi:type=\"xs:string\"></Subject> - </Credentials> - </RequesterCredentials> - </SOAP-ENV:Header> - <SOAP-ENV:Body id=\"_0\"> - <SetExpressCheckoutResponse xmlns=\"urn:ebay:api:PayPalAPI\"> - <Timestamp xmlns=\"urn:ebay:apis:eBLBaseComponents\">2011-05-19T20:13:30Z</Timestamp> - <Ack xmlns=\"urn:ebay:apis:eBLBaseComponents\">Success</Ack> - <CorrelationID xmlns=\"urn:ebay:apis:eBLBaseComponents\">da0ed6bc90ef1</CorrelationID> - <Version xmlns=\"urn:ebay:apis:eBLBaseComponents\">72</Version> - <Build xmlns=\"urn:ebay:apis:eBLBaseComponents\">1882144</Build> - <Token xsi:type=\"ebl:ExpressCheckoutTokenType\">EC-0XOMGOMGOMG</Token> - </SetExpressCheckoutResponse> - </SOAP-ENV:Body> - </SOAP-ENV:Envelope>" + "<?xml version=\"1.0\" encoding=\"UTF-8\"?> + <SOAP-ENV:Envelope xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:SOAP-ENC=\"http://schemas.xmlsoap.org/soap/encoding/\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xmlns:cc=\"urn:ebay:apis:CoreComponentTypes\" xmlns:wsu=\"http://schemas.xmlsoap.org/ws/2002/07/utility\" xmlns:saml=\"urn:oasis:names:tc:SAML:1.0:assertion\" xmlns:ds=\"http://www.w3.org/2000/09/xmldsig#\" xmlns:wsse=\"http://schemas.xmlsoap.org/ws/2002/12/secext\" xmlns:ed=\"urn:ebay:apis:EnhancedDataTypes\" xmlns:ebl=\"urn:ebay:apis:eBLBaseComponents\" xmlns:ns=\"urn:ebay:api:PayPalAPI\"> + <SOAP-ENV:Header> + <Security xmlns=\"http://schemas.xmlsoap.org/ws/2002/12/secext\" xsi:type=\"wsse:SecurityType\"></Security> + <RequesterCredentials xmlns=\"urn:ebay:api:PayPalAPI\" xsi:type=\"ebl:CustomSecurityHeaderType\"> + <Credentials xmlns=\"urn:ebay:apis:eBLBaseComponents\" xsi:type=\"ebl:UserIdPasswordType\"> + <Username xsi:type=\"xs:string\"></Username> + <Password xsi:type=\"xs:string\"></Password> + <Signature xsi:type=\"xs:string\">OMGOMGOMGOMGOMG</Signature> + <Subject xsi:type=\"xs:string\"></Subject> + </Credentials> + </RequesterCredentials> + </SOAP-ENV:Header> + <SOAP-ENV:Body id=\"_0\"> + <SetExpressCheckoutResponse xmlns=\"urn:ebay:api:PayPalAPI\"> + <Timestamp xmlns=\"urn:ebay:apis:eBLBaseComponents\">2011-05-19T20:13:30Z</Timestamp> + <Ack xmlns=\"urn:ebay:apis:eBLBaseComponents\">Success</Ack> + <CorrelationID xmlns=\"urn:ebay:apis:eBLBaseComponents\">da0ed6bc90ef1</CorrelationID> + <Version xmlns=\"urn:ebay:apis:eBLBaseComponents\">72</Version> + <Build xmlns=\"urn:ebay:apis:eBLBaseComponents\">1882144</Build> + <Token xsi:type=\"ebl:ExpressCheckoutTokenType\">EC-0XOMGOMGOMG</Token> + </SetExpressCheckoutResponse> + </SOAP-ENV:Body> + </SOAP-ENV:Envelope>" end - -end +end diff --git a/test/unit/gateways/paypal_express_test.rb b/test/unit/gateways/paypal_express_test.rb index 3cb7a70c6cc..be0274025f8 100644 --- a/test/unit/gateways/paypal_express_test.rb +++ b/test/unit/gateways/paypal_express_test.rb @@ -4,8 +4,8 @@ class PaypalExpressTest < Test::Unit::TestCase TEST_REDIRECT_URL = 'https://www.sandbox.paypal.com/cgi-bin/webscr?cmd=_express-checkout&token=1234567890' TEST_REDIRECT_URL_MOBILE = 'https://www.sandbox.paypal.com/cgi-bin/webscr?cmd=_express-checkout-mobile&token=1234567890' - LIVE_REDIRECT_URL = 'https://www.paypal.com/cgibin/webscr?cmd=_express-checkout&token=1234567890' - LIVE_REDIRECT_URL_MOBILE = 'https://www.paypal.com/cgibin/webscr?cmd=_express-checkout-mobile&token=1234567890' + LIVE_REDIRECT_URL = 'https://www.paypal.com/cgi-bin/webscr?cmd=_express-checkout&token=1234567890' + LIVE_REDIRECT_URL_MOBILE = 'https://www.paypal.com/cgi-bin/webscr?cmd=_express-checkout-mobile&token=1234567890' TEST_REDIRECT_URL_WITHOUT_REVIEW = "#{TEST_REDIRECT_URL}&useraction=commit" LIVE_REDIRECT_URL_WITHOUT_REVIEW = "#{LIVE_REDIRECT_URL}&useraction=commit" @@ -19,37 +19,38 @@ def setup :pem => 'PEM' ) - @address = { :address1 => '1234 My Street', - :address2 => 'Apt 1', - :company => 'Widgets Inc', - :city => 'Ottawa', - :state => 'ON', - :zip => 'K1C2N6', - :country => 'Canada', - :phone => '(555)555-5555' - } + @address = { + :address1 => '1234 My Street', + :address2 => 'Apt 1', + :company => 'Widgets Inc', + :city => 'Ottawa', + :state => 'ON', + :zip => 'K1C2N6', + :country => 'Canada', + :phone => '(555)555-5555' + } - Base.gateway_mode = :test + Base.mode = :test end def teardown - Base.gateway_mode = :test + Base.mode = :test end def test_live_redirect_url - Base.gateway_mode = :production + Base.mode = :production assert_equal LIVE_REDIRECT_URL, @gateway.redirect_url_for('1234567890') assert_equal LIVE_REDIRECT_URL_MOBILE, @gateway.redirect_url_for('1234567890', :mobile => true) end def test_live_redirect_url_without_review - Base.gateway_mode = :production + Base.mode = :production assert_equal LIVE_REDIRECT_URL_WITHOUT_REVIEW, @gateway.redirect_url_for('1234567890', :review => false) assert_equal LIVE_REDIRECT_URL_MOBILE_WITHOUT_REVIEW, @gateway.redirect_url_for('1234567890', :review => false, :mobile => true) end def test_force_sandbox_redirect_url - Base.gateway_mode = :production + Base.mode = :production gateway = PaypalExpressGateway.new( :login => 'cody', @@ -64,13 +65,13 @@ def test_force_sandbox_redirect_url end def test_test_redirect_url - assert_equal :test, Base.gateway_mode + assert_equal :test, Base.mode assert_equal TEST_REDIRECT_URL, @gateway.redirect_url_for('1234567890') assert_equal TEST_REDIRECT_URL_MOBILE, @gateway.redirect_url_for('1234567890', :mobile => true) end def test_test_redirect_url_without_review - assert_equal :test, Base.gateway_mode + assert_equal :test, Base.mode assert_equal TEST_REDIRECT_URL_WITHOUT_REVIEW, @gateway.redirect_url_for('1234567890', :review => false) assert_equal TEST_REDIRECT_URL_MOBILE_WITHOUT_REVIEW, @gateway.redirect_url_for('1234567890', :review => false, :mobile => true) end @@ -86,6 +87,7 @@ def test_get_express_details assert_equal 'EC-2XE90996XX9870316', response.token assert_equal 'FWRVKNRRZ3WUC', response.payer_id assert_equal 'buyer@jadedpallet.com', response.email + assert_equal 'This is a test note', response.note assert address = response.address assert_equal 'Fred Brooks', address['name'] @@ -103,7 +105,7 @@ def test_get_express_details end def test_express_response_missing_address - response = PaypalExpressResponse.new(true, "ok") + response = PaypalExpressResponse.new(true, 'ok') assert_nil response.address['address1'] end @@ -164,10 +166,24 @@ def test_does_not_include_items_if_not_specified end def test_items_are_included_if_specified_in_build_setup_request - xml = REXML::Document.new(@gateway.send(:build_setup_request, 'SetExpressCheckout', 0, {:currency => 'GBP', :items => [ - {:name => 'item one', :description => 'item one description', :amount => 10000, :number => 1, :quantity => 3}, - {:name => 'item two', :description => 'item two description', :amount => 20000, :number => 2, :quantity => 4} - ]})) + xml = REXML::Document.new(@gateway.send(:build_setup_request, 'SetExpressCheckout', 0, { + :currency => 'GBP', + :items => [ + { + :name => 'item one', + :description => 'item one description', + :amount => 10000, + :number => 1, + :quantity => 3 + }, + { :name => 'item two', + :description => 'item two description', + :amount => 20000, + :number => 2, + :quantity => 4 + } + ] + })) assert_equal 'item one', REXML::XPath.first(xml, '//n2:PaymentDetails/n2:PaymentDetailsItem/n2:Name').text assert_equal 'item one description', REXML::XPath.first(xml, '//n2:PaymentDetails/n2:PaymentDetailsItem/n2:Description').text @@ -191,7 +207,7 @@ def test_does_not_include_callback_url_if_not_specified end def test_callback_url_is_included_if_specified_in_build_setup_request - xml = REXML::Document.new(@gateway.send(:build_setup_request, 'SetExpressCheckout', 0, {:callback_url => "http://example.com/update_callback"})) + xml = REXML::Document.new(@gateway.send(:build_setup_request, 'SetExpressCheckout', 0, {:callback_url => 'http://example.com/update_callback'})) assert_equal 'http://example.com/update_callback', REXML::XPath.first(xml, '//n2:CallbackURL').text end @@ -227,16 +243,22 @@ def test_does_not_include_flatrate_shipping_options_if_not_specified end def test_flatrate_shipping_options_are_included_if_specified_in_build_setup_request - xml = REXML::Document.new(@gateway.send(:build_setup_request, 'SetExpressCheckout', 0, {:currency => 'AUD', :shipping_options => [ - {:default => true, - :name => "first one", + xml = REXML::Document.new(@gateway.send(:build_setup_request, 'SetExpressCheckout', 0, + { + :currency => 'AUD', + :shipping_options => [ + { + :default => true, + :name => 'first one', :amount => 1000 - }, - {:default => false, - :name => "second one", - :amount => 2000 - } - ]})) + }, + { + :default => false, + :name => 'second one', + :amount => 2000 + } + ] + })) assert_equal 'true', REXML::XPath.first(xml, '//n2:FlatRateShippingOptions/n2:ShippingOptionIsDefault').text assert_equal 'first one', REXML::XPath.first(xml, '//n2:FlatRateShippingOptions/n2:ShippingOptionName').text @@ -250,14 +272,18 @@ def test_flatrate_shipping_options_are_included_if_specified_in_build_setup_requ end def test_address_is_included_if_specified - xml = REXML::Document.new(@gateway.send(:build_setup_request, 'Sale', 0, {:currency => 'GBP', :address => { - :name => "John Doe", - :address1 => "123 somewhere", - :city => "Townville", - :country => "Canada", - :zip => "k1l4p2", - :phone => "1231231231" - }})) + xml = REXML::Document.new(@gateway.send(:build_setup_request, 'Sale', 0, + { + :currency => 'GBP', + :address => { + :name => 'John Doe', + :address1 => '123 somewhere', + :city => 'Townville', + :country => 'Canada', + :zip => 'k1l4p2', + :phone => '1231231231' + } + })) assert_equal 'John Doe', REXML::XPath.first(xml, '//n2:PaymentDetails/n2:ShipToAddress/n2:Name').text assert_equal '123 somewhere', REXML::XPath.first(xml, '//n2:PaymentDetails/n2:ShipToAddress/n2:Street1').text @@ -286,10 +312,30 @@ def test_removes_fractional_amounts_with_twd_currency end def test_fractional_discounts_are_correctly_calculated_with_jpy_currency - xml = REXML::Document.new(@gateway.send(:build_setup_request, 'SetExpressCheckout', 14250, { :items => - [{:name => 'item one', :description => 'description', :amount => 15000, :number => 1, :quantity => 1}, - {:name => 'Discount', :description => 'Discount', :amount => -750, :number => 2, :quantity => 1}], - :subtotal => 14250, :currency => 'JPY', :shipping => 0, :handling => 0, :tax => 0 })) + xml = REXML::Document.new(@gateway.send(:build_setup_request, 'SetExpressCheckout', 14250, + { + :items => [ + { + :name => 'item one', + :description => 'description', + :amount => 15000, + :number => 1, + :quantity => 1 + }, + { + :name => 'Discount', + :description => 'Discount', + :amount => -750, + :number => 2, + :quantity => 1 + } + ], + :subtotal => 14250, + :currency => 'JPY', + :shipping => 0, + :handling => 0, + :tax => 0 + })) assert_equal '142', REXML::XPath.first(xml, '//n2:OrderTotal').text assert_equal '142', REXML::XPath.first(xml, '//n2:ItemTotal').text @@ -299,10 +345,30 @@ def test_fractional_discounts_are_correctly_calculated_with_jpy_currency end def test_non_fractional_discounts_are_correctly_calculated_with_jpy_currency - xml = REXML::Document.new(@gateway.send(:build_setup_request, 'SetExpressCheckout', 14300, { :items => - [{:name => 'item one', :description => 'description', :amount => 15000, :number => 1, :quantity => 1}, - {:name => 'Discount', :description => 'Discount', :amount => -700, :number => 2, :quantity => 1}], - :subtotal => 14300, :currency => 'JPY', :shipping => 0, :handling => 0, :tax => 0 })) + xml = REXML::Document.new(@gateway.send(:build_setup_request, 'SetExpressCheckout', 14300, + { + :items => [ + { + :name => 'item one', + :description => 'description', + :amount => 15000, + :number => 1, + :quantity => 1 + }, + { + :name => 'Discount', + :description => 'Discount', + :amount => -700, + :number => 2, + :quantity => 1 + } + ], + :subtotal => 14300, + :currency => 'JPY', + :shipping => 0, + :handling => 0, + :tax => 0 + })) assert_equal '143', REXML::XPath.first(xml, '//n2:OrderTotal').text assert_equal '143', REXML::XPath.first(xml, '//n2:ItemTotal').text @@ -312,10 +378,30 @@ def test_non_fractional_discounts_are_correctly_calculated_with_jpy_currency end def test_fractional_discounts_are_correctly_calculated_with_usd_currency - xml = REXML::Document.new(@gateway.send(:build_setup_request, 'SetExpressCheckout', 14250, { :items => - [{:name => 'item one', :description => 'description', :amount => 15000, :number => 1, :quantity => 1}, - {:name => 'Discount', :description => 'Discount', :amount => -750, :number => 2, :quantity => 1}], - :subtotal => 14250, :currency => 'USD', :shipping => 0, :handling => 0, :tax => 0 })) + xml = REXML::Document.new(@gateway.send(:build_setup_request, 'SetExpressCheckout', 14250, + { + :items => [ + { + :name => 'item one', + :description => 'description', + :amount => 15000, + :number => 1, + :quantity => 1 + }, + { + :name => 'Discount', + :description => 'Discount', + :amount => -750, + :number => 2, + :quantity => 1 + } + ], + :subtotal => 14250, + :currency => 'USD', + :shipping => 0, + :handling => 0, + :tax => 0 + })) assert_equal '142.50', REXML::XPath.first(xml, '//n2:OrderTotal').text assert_equal '142.50', REXML::XPath.first(xml, '//n2:ItemTotal').text @@ -368,11 +454,25 @@ def test_button_source end def test_items_are_included_if_specified_in_build_sale_or_authorization_request - xml = REXML::Document.new(@gateway.send(:build_sale_or_authorization_request, 'Sale', 100, {:items => [ - {:name => 'item one', :description => 'item one description', :amount => 10000, :number => 1, :quantity => 3}, - {:name => 'item two', :description => 'item two description', :amount => 20000, :number => 2, :quantity => 4} - ]})) - + xml = REXML::Document.new(@gateway.send(:build_sale_or_authorization_request, 'Sale', 100, + { + :items => [ + { + :name => 'item one', + :description => 'item one description', + :amount => 10000, + :number => 1, + :quantity => 3 + }, + { + :name => 'item two', + :description => 'item two description', + :amount => 20000, + :number => 2, + :quantity => 4 + } + ] + })) assert_equal 'item one', REXML::XPath.first(xml, '//n2:PaymentDetails/n2:PaymentDetailsItem/n2:Name').text assert_equal 'item one description', REXML::XPath.first(xml, '//n2:PaymentDetails/n2:PaymentDetailsItem/n2:Description').text @@ -389,7 +489,7 @@ def test_items_are_included_if_specified_in_build_sale_or_authorization_request def test_build_create_billing_agreement PaypalExpressGateway.application_id = 'ActiveMerchant_FOO' - xml = REXML::Document.new(@gateway.send(:build_create_billing_agreement_request, "ref_id")) + xml = REXML::Document.new(@gateway.send(:build_create_billing_agreement_request, 'ref_id')) assert_equal 'ref_id', REXML::XPath.first(xml, '//CreateBillingAgreementReq/CreateBillingAgreementRequest/Token').text end @@ -397,44 +497,67 @@ def test_build_create_billing_agreement def test_store @gateway.expects(:ssl_post).returns(successful_create_billing_agreement_response) - response = @gateway.store("ref_id") + response = @gateway.store('ref_id') - assert_equal "Success", response.params['ack'] - assert_equal "Success", response.message - assert_equal "B-3R788221G4476823M", response.params["billing_agreement_id"] + assert_equal 'Success', response.params['ack'] + assert_equal 'Success', response.message + assert_equal 'B-3R788221G4476823M', response.params['billing_agreement_id'] end def test_unstore_successful @gateway.expects(:ssl_post).returns(successful_cancel_billing_agreement_response) - response = @gateway.unstore("B-3RU433629T663020S") + response = @gateway.unstore('B-3RU433629T663020S') assert response.success? - assert_equal "Success", response.params['ack'] - assert_equal "Success", response.message - assert_equal "B-3RU433629T663020S", response.params["billing_agreement_id"] - assert_equal "Canceled", response.params["billing_agreement_status"] + assert_equal 'Success', response.params['ack'] + assert_equal 'Success', response.message + assert_equal 'B-3RU433629T663020S', response.params['billing_agreement_id'] + assert_equal 'Canceled', response.params['billing_agreement_status'] end def test_unstore_failed @gateway.expects(:ssl_post).returns(failed_cancel_billing_agreement_response) - response = @gateway.unstore("B-3RU433629T663020S") + response = @gateway.unstore('B-3RU433629T663020S') assert !response.success? - assert_equal "Failure", response.params['ack'] - assert_equal "Billing Agreement was cancelled", response.message - assert_equal "10201", response.params["error_codes"] + assert_equal 'Failure', response.params['ack'] + assert_equal 'Billing Agreement was cancelled', response.message + assert_equal '10201', response.params['error_codes'] + end + + def test_agreement_details_successful + @gateway.expects(:ssl_post).returns(successful_billing_agreement_details_response) + response = @gateway.agreement_details('B-6VE21702A47915521') + + assert response.success? + assert_equal 'Success', response.params['ack'] + assert_equal 'Success', response.message + assert_equal 'B-6VE21702A47915521', response.params['billing_agreement_id'] + assert_equal 'Active', response.params['billing_agreement_status'] + end + + def test_agreement_details_failure + @gateway.expects(:ssl_post).returns(failure_billing_agreement_details_response) + response = @gateway.agreement_details('bad_reference_id') + + assert !response.success? + assert_equal 'Failure', response.params['ack'] + assert_equal 'Billing Agreement Id or transaction Id is not valid', response.message + assert_equal '11451', response.params['error_codes'] end def test_build_reference_transaction_test PaypalExpressGateway.application_id = 'ActiveMerchant_FOO' - xml = REXML::Document.new(@gateway.send(:build_reference_transaction_request, 'Sale', 2000, { - :reference_id => "ref_id", - :payment_type => 'Any', - :invoice_id => 'invoice_id', - :description => 'Description', - :ip => '127.0.0.1' })) - - assert_equal '72', REXML::XPath.first(xml, '//DoReferenceTransactionReq/DoReferenceTransactionRequest/n2:Version').text + xml = REXML::Document.new(@gateway.send(:build_reference_transaction_request, 'Sale', 2000, + { + :reference_id => 'ref_id', + :payment_type => 'Any', + :invoice_id => 'invoice_id', + :description => 'Description', + :ip => '127.0.0.1' + })) + + assert_equal '124', REXML::XPath.first(xml, '//DoReferenceTransactionReq/DoReferenceTransactionRequest/n2:Version').text assert_equal 'ref_id', REXML::XPath.first(xml, '//DoReferenceTransactionReq/DoReferenceTransactionRequest/n2:DoReferenceTransactionRequestDetails/n2:ReferenceID').text assert_equal 'Sale', REXML::XPath.first(xml, '//DoReferenceTransactionReq/DoReferenceTransactionRequest/n2:DoReferenceTransactionRequestDetails/n2:PaymentAction').text assert_equal 'Any', REXML::XPath.first(xml, '//DoReferenceTransactionReq/DoReferenceTransactionRequest/n2:DoReferenceTransactionRequestDetails/n2:PaymentType').text @@ -445,29 +568,36 @@ def test_build_reference_transaction_test assert_equal '127.0.0.1', REXML::XPath.first(xml, '//DoReferenceTransactionReq/DoReferenceTransactionRequest/n2:DoReferenceTransactionRequestDetails/n2:IPAddress').text end + def test_build_details_billing_agreement_request_test + xml = REXML::Document.new(@gateway.send(:build_details_billing_agreement_request, 'reference_ID')) + assert_equal 'reference_ID', REXML::XPath.first(xml, '//BillAgreementUpdateReq/BAUpdateRequest/ReferenceID').text + assert_nil REXML::XPath.first(xml, '//BillAgreementUpdateReq/BAUpdateRequest/BillingAgreementStatus') + end + def test_authorize_reference_transaction @gateway.expects(:ssl_post).returns(successful_authorize_reference_transaction_response) - response = @gateway.authorize_reference_transaction(2000, { - :reference_id => "ref_id", - :payment_type => 'Any', - :invoice_id => 'invoice_id', - :description => 'Description', - :ip => '127.0.0.1' }) + response = @gateway.authorize_reference_transaction(2000, + { + :reference_id => 'ref_id', + :payment_type => 'Any', + :invoice_id => 'invoice_id', + :description => 'Description', + :ip => '127.0.0.1' }) - assert_equal "Success", response.params['ack'] - assert_equal "Success", response.message - assert_equal "9R43552341412482K", response.authorization + assert_equal 'Success', response.params['ack'] + assert_equal 'Success', response.message + assert_equal '9R43552341412482K', response.authorization end def test_reference_transaction @gateway.expects(:ssl_post).returns(successful_reference_transaction_response) - response = @gateway.reference_transaction(2000, { :reference_id => "ref_id" }) + response = @gateway.reference_transaction(2000, { :reference_id => 'ref_id' }) - assert_equal "Success", response.params['ack'] - assert_equal "Success", response.message - assert_equal "9R43552341412482K", response.authorization + assert_equal 'Success', response.params['ack'] + assert_equal 'Success', response.message + assert_equal '9R43552341412482K', response.authorization end def test_reference_transaction_requires_fields @@ -479,30 +609,30 @@ def test_reference_transaction_requires_fields def test_error_code_for_single_error @gateway.expects(:ssl_post).returns(response_with_error) response = @gateway.setup_authorization(100, - :return_url => 'http://example.com', - :cancel_return_url => 'http://example.com' - ) - assert_equal "10736", response.params['error_codes'] + :return_url => 'http://example.com', + :cancel_return_url => 'http://example.com' + ) + assert_equal '10736', response.params['error_codes'] end def test_ensure_only_unique_error_codes @gateway.expects(:ssl_post).returns(response_with_duplicate_errors) response = @gateway.setup_authorization(100, - :return_url => 'http://example.com', - :cancel_return_url => 'http://example.com' - ) + :return_url => 'http://example.com', + :cancel_return_url => 'http://example.com' + ) - assert_equal "10736" , response.params['error_codes'] + assert_equal '10736', response.params['error_codes'] end def test_error_codes_for_multiple_errors @gateway.expects(:ssl_post).returns(response_with_errors) response = @gateway.setup_authorization(100, - :return_url => 'http://example.com', - :cancel_return_url => 'http://example.com' - ) + :return_url => 'http://example.com', + :cancel_return_url => 'http://example.com' + ) - assert_equal ["10736", "10002"] , response.params['error_codes'].split(',') + assert_equal ['10736', '10002'], response.params['error_codes'].split(',') end def test_allow_guest_checkout @@ -512,6 +642,13 @@ def test_allow_guest_checkout assert_equal 'Billing', REXML::XPath.first(xml, '//n2:LandingPage').text end + def test_paypal_chooses_landing_page + xml = REXML::Document.new(@gateway.send(:build_setup_request, 'SetExpressCheckout', 10, {:allow_guest_checkout => true, :paypal_chooses_landing_page=> true})) + + assert_equal 'Sole', REXML::XPath.first(xml, '//n2:SolutionType').text + assert_nil REXML::XPath.first(xml, '//n2:LandingPage') + end + def test_not_adds_brand_name_if_not_specified xml = REXML::Document.new(@gateway.send(:build_setup_request, 'SetExpressCheckout', 10, {})) @@ -544,6 +681,14 @@ def test_adds_buyer_optin_if_specified assert_equal '0', REXML::XPath.first(do_not_allow_optin_xml, '//n2:BuyerEmailOptInEnable').text end + def test_add_total_type_if_specified + total_type_xml = REXML::Document.new(@gateway.send(:build_setup_request, 'SetExpressCheckout', 10, {:total_type => 'EstimatedTotal'})) + no_total_type_xml = REXML::Document.new(@gateway.send(:build_setup_request, 'SetExpressCheckout', 10, {})) + + assert_equal 'EstimatedTotal', REXML::XPath.first(total_type_xml, '//n2:TotalType').text + assert_nil REXML::XPath.first(no_total_type_xml, '//n2:BuyerEmailOptInEnable') + end + def test_structure_correct all_options_enabled = { :allow_guest_checkout => true, @@ -562,30 +707,43 @@ def test_structure_correct :shipping => 10, :handling => 0, :tax => 5, - :items => [{:name => 'item one', - :number => 'number 1', - :quantity => 3, - :amount => 35, - :description => 'one description', - :url => 'http://example.com/number_1'}], - :address => {:name => 'John Doe', - :address1 => 'Apartment 1', - :address2 => '1 Road St', - :city => 'First City', - :state => 'NSW', - :country => 'AU', - :zip => '2000', - :phone => '555 5555'}, - :callback_url => "http://example.com/update_callback", + :total_type => 'EstimatedTotal', + :items => [ + { + :name => 'item one', + :number => 'number 1', + :quantity => 3, + :amount => 35, + :description => 'one description', + :url => 'http://example.com/number_1' + } + ], + :address => + { + :name => 'John Doe', + :address1 => 'Apartment 1', + :address2 => '1 Road St', + :city => 'First City', + :state => 'NSW', + :country => 'AU', + :zip => '2000', + :phone => '555 5555' + }, + :callback_url => 'http://example.com/update_callback', :callback_timeout => 2, :callback_version => '53.0', - :shipping_options => [{:default => true, - :name => "first one", - :amount => 10}] + :funding_sources => {:source => 'BML'}, + :shipping_options => [ + { + :default => true, + :name => 'first one', + :amount => 10 + } + ] } doc = Nokogiri::XML(@gateway.send(:build_setup_request, 'Sale', 10, all_options_enabled)) - #Strip back to the SetExpressCheckoutRequestDetails element - this is where the base component xsd starts + # Strip back to the SetExpressCheckoutRequestDetails element - this is where the base component xsd starts xml = doc.xpath('//base:SetExpressCheckoutRequestDetails', 'base' => 'urn:ebay:apis:eBLBaseComponents').first sub_doc = Nokogiri::XML::Document.new sub_doc.root = xml @@ -627,9 +785,8 @@ def successful_create_billing_agreement_response RESPONSE end - -def successful_authorize_reference_transaction_response - <<-RESPONSE + def successful_authorize_reference_transaction_response + <<-RESPONSE <?xml version="1.0" encoding="UTF-8"?> <SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:cc="urn:ebay:apis:CoreComponentTypes" xmlns:wsu="http://schemas.xmlsoap.org/ws/2002/07/utility" xmlns:saml="urn:oasis:names:tc:SAML:1.0:assertion" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:wsse="http://schemas.xmlsoap.org/ws/2002/12/secext" xmlns:ed="urn:ebay:apis:EnhancedDataTypes" xmlns:ebl="urn:ebay:apis:eBLBaseComponents" xmlns:ns="urn:ebay:api:PayPalAPI"> <SOAP-ENV:Header> @@ -724,7 +881,6 @@ def successful_reference_transaction_response RESPONSE end - def successful_details_response <<-RESPONSE <?xml version="1.0" encoding="UTF-8"?> @@ -735,6 +891,7 @@ def successful_details_response <Credentials xmlns="urn:ebay:apis:eBLBaseComponents" xsi:type="ebl:UserIdPasswordType"> <Username xsi:type="xs:string"/> <Password xsi:type="xs:string"/> + <Signature xsi:type="xs:string" /> <Subject xsi:type="xs:string"/> </Credentials> </RequesterCredentials> @@ -807,6 +964,7 @@ def successful_details_response <InsuranceTotal xsi:type="cc:BasicAmountType" currencyID="USD">0.00</InsuranceTotal> <ShippingDiscount xsi:type="cc:BasicAmountType" currencyID="USD">0.00</ShippingDiscount> <InsuranceOptionOffered xsi:type="xs:string">false</InsuranceOptionOffered> + <NoteText xsi:type="xs:string">This is a test note</NoteText> <SellerDetails xsi:type="ebl:SellerDetailsType"/> <PaymentRequestID xsi:type="xs:string"/> <OrderURL xsi:type="xs:string"/> @@ -820,6 +978,7 @@ def successful_details_response <ShippingOptionName xsi:type=\"xs:string\">default</ShippingOptionName> </UserSelectedOptions> <CheckoutStatus xsi:type="xs:string">PaymentActionNotInitiated</CheckoutStatus> + <PaymentRequestInfo xsi:type="ebl:PaymentRequestInfoType" /> </GetExpressCheckoutDetailsResponseDetails> </GetExpressCheckoutDetailsResponse> </SOAP-ENV:Body> @@ -904,8 +1063,8 @@ def response_with_error RESPONSE end - def response_with_errors - <<-RESPONSE + def response_with_errors + <<-RESPONSE <?xml version="1.0" encoding="UTF-8"?> <SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:cc="urn:ebay:apis:CoreComponentTypes" xmlns:wsu="http://schemas.xmlsoap.org/ws/2002/07/utility" xmlns:saml="urn:oasis:names:tc:SAML:1.0:assertion" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:market="urn:ebay:apis:Market" xmlns:auction="urn:ebay:apis:Auction" xmlns:sizeship="urn:ebay:api:PayPalAPI/sizeship.xsd" xmlns:ship="urn:ebay:apis:ship" xmlns:skype="urn:ebay:apis:skype" xmlns:wsse="http://schemas.xmlsoap.org/ws/2002/12/secext" xmlns:ebl="urn:ebay:apis:eBLBaseComponents" xmlns:ns="urn:ebay:api:PayPalAPI"> <SOAP-ENV:Header> @@ -941,10 +1100,10 @@ def response_with_errors </SOAP-ENV:Body> </SOAP-ENV:Envelope> RESPONSE - end + end - def response_with_duplicate_errors - <<-RESPONSE + def response_with_duplicate_errors + <<-RESPONSE <?xml version="1.0" encoding="UTF-8"?> <SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:cc="urn:ebay:apis:CoreComponentTypes" xmlns:wsu="http://schemas.xmlsoap.org/ws/2002/07/utility" xmlns:saml="urn:oasis:names:tc:SAML:1.0:assertion" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:market="urn:ebay:apis:Market" xmlns:auction="urn:ebay:apis:Auction" xmlns:sizeship="urn:ebay:api:PayPalAPI/sizeship.xsd" xmlns:ship="urn:ebay:apis:ship" xmlns:skype="urn:ebay:apis:skype" xmlns:wsse="http://schemas.xmlsoap.org/ws/2002/12/secext" xmlns:ebl="urn:ebay:apis:eBLBaseComponents" xmlns:ns="urn:ebay:api:PayPalAPI"> <SOAP-ENV:Header> @@ -980,10 +1139,10 @@ def response_with_duplicate_errors </SOAP-ENV:Body> </SOAP-ENV:Envelope> RESPONSE - end + end - def successful_cancel_billing_agreement_response - <<-RESPONSE + def successful_cancel_billing_agreement_response + <<-RESPONSE <?xml version="1.0" encoding="UTF-8"?><SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:cc="urn:ebay:apis:CoreComponentTypes" xmlns:wsu="http://schemas.xmlsoap.org/ws/2002/07/utility" xmlns:saml="urn:oasis:names:tc:SAML:1.0:assertion" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" @@ -1007,10 +1166,10 @@ def successful_cancel_billing_agreement_response xsi:type="xs:string"></ExternalAddressID><AddressStatus xsi:type="ebl:AddressStatusCodeType">None</AddressStatus></Address></PayerInfo></BAUpdateResponseDetails></BAUpdateResponse></SOAP-ENV:Body></SOAP-ENV:Envelope> RESPONSE - end + end - def failed_cancel_billing_agreement_response - <<-RESPONSE + def failed_cancel_billing_agreement_response + <<-RESPONSE <?xml version="1.0" encoding="UTF-8"?><SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:cc="urn:ebay:apis:CoreComponentTypes" xmlns:wsu="http://schemas.xmlsoap.org/ws/2002/07/utility" xmlns:saml="urn:oasis:names:tc:SAML:1.0:assertion" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" @@ -1033,5 +1192,122 @@ def failed_cancel_billing_agreement_response xsi:type="xs:string"></AddressID><AddressOwner xsi:type="ebl:AddressOwnerCodeType">PayPal</AddressOwner><ExternalAddressID xsi:type="xs:string"></ExternalAddressID><AddressStatus xsi:type="ebl:AddressStatusCodeType">None</AddressStatus></Address></PayerInfo></BAUpdateResponseDetails></BAUpdateResponse></SOAP-ENV:Body></SOAP-ENV:Envelope> RESPONSE - end + end + + def successful_billing_agreement_details_response + <<-RESPONSE + <?xml version="1.0" encoding="UTF-8"?><SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" + xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:cc="urn:ebay:apis:CoreComponentTypes" + xmlns:wsu="http://schemas.xmlsoap.org/ws/2002/07/utility" xmlns:saml="urn:oasis:names:tc:SAML:1.0:assertion" + xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:wsse="http://schemas.xmlsoap.org/ws/2002/12/secext" xmlns:ed="urn:ebay:apis:EnhancedDataTypes" + xmlns:ebl="urn:ebay:apis:eBLBaseComponents" xmlns:ns="urn:ebay:api:PayPalAPI"><SOAP-ENV:Header><Security + xmlns="http://schemas.xmlsoap.org/ws/2002/12/secext" xsi:type="wsse:SecurityType"></Security><RequesterCredentials + xmlns="urn:ebay:api:PayPalAPI" xsi:type="ebl:CustomSecurityHeaderType"><Credentials xmlns="urn:ebay:apis:eBLBaseComponents" + xsi:type="ebl:UserIdPasswordType"><Username xsi:type="xs:string"></Username><Password xsi:type="xs:string"></Password><Signature + xsi:type="xs:string"></Signature><Subject xsi:type="xs:string"></Subject></Credentials></RequesterCredentials></SOAP-ENV:Header><SOAP-ENV:Body id="_0"> + <BAUpdateResponse xmlns="urn:ebay:api:PayPalAPI"><Timestamp xmlns="urn:ebay:apis:eBLBaseComponents">2014-05-08T09:22:03Z</Timestamp> + <Ack xmlns="urn:ebay:apis:eBLBaseComponents">Success</Ack><CorrelationID xmlns="urn:ebay:apis:eBLBaseComponents">f84ed24f5bd6d</CorrelationID> + <Version xmlns="urn:ebay:apis:eBLBaseComponents">72</Version><Build xmlns="urn:ebay:apis:eBLBaseComponents">10918103</Build><BAUpdateResponseDetails + xmlns="urn:ebay:apis:eBLBaseComponents" xsi:type="ebl:BAUpdateResponseDetailsType"> + <BillingAgreementID xsi:type="xs:string">B-6VE21702A47915521</BillingAgreementID><BillingAgreementDescription + xsi:type="xs:string">My active merchant custom description</BillingAgreementDescription> + <BillingAgreementStatus xsi:type="ebl:MerchantPullStatusCodeType">Active</BillingAgreementStatus><PayerInfo xsi:type="ebl:PayerInfoType"><Payer + xsi:type="ebl:EmailAddressType">ivan.rostovsky.xan@gmail.com</Payer><PayerID xsi:type="ebl:UserIDType">SW3AR2WYZ3NJW</PayerID><PayerStatus + xsi:type="ebl:PayPalUserStatusCodeType">verified</PayerStatus><PayerName xsi:type="ebl:PersonNameType"><Salutation + xmlns="urn:ebay:apis:eBLBaseComponents"></Salutation><FirstName xmlns="urn:ebay:apis:eBLBaseComponents">Ivan</FirstName><MiddleName + xmlns="urn:ebay:apis:eBLBaseComponents"></MiddleName><LastName xmlns="urn:ebay:apis:eBLBaseComponents">Rostovsky</LastName> + <Suffix xmlns="urn:ebay:apis:eBLBaseComponents"></Suffix></PayerName><PayerCountry xsi:type="ebl:CountryCodeType">US</PayerCountry> + <PayerBusiness xsi:type="xs:string"></PayerBusiness><Address xsi:type="ebl:AddressType"><Name xsi:type="xs:string"></Name><Street1 + xsi:type="xs:string"></Street1><Street2 xsi:type="xs:string"></Street2><CityName xsi:type="xs:string"></CityName><StateOrProvince + xsi:type="xs:string"></StateOrProvince><CountryName></CountryName><Phone xsi:type="xs:string"></Phone><PostalCode xsi:type="xs:string"> + </PostalCode><AddressID xsi:type="xs:string"></AddressID><AddressOwner xsi:type="ebl:AddressOwnerCodeType">PayPal</AddressOwner> + <ExternalAddressID xsi:type="xs:string"></ExternalAddressID><AddressStatus xsi:type="ebl:AddressStatusCodeType">None</AddressStatus> + </Address></PayerInfo></BAUpdateResponseDetails></BAUpdateResponse></SOAP-ENV:Body></SOAP-ENV:Envelope> + RESPONSE + end + + def failure_billing_agreement_details_response + <<-RESPONSE + <?xml version="1.0" encoding="UTF-8"?><SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" + xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:cc="urn:ebay:apis:CoreComponentTypes" + xmlns:wsu="http://schemas.xmlsoap.org/ws/2002/07/utility" xmlns:saml="urn:oasis:names:tc:SAML:1.0:assertion" + xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:wsse="http://schemas.xmlsoap.org/ws/2002/12/secext" + xmlns:ed="urn:ebay:apis:EnhancedDataTypes" xmlns:ebl="urn:ebay:apis:eBLBaseComponents" + xmlns:ns="urn:ebay:api:PayPalAPI"><SOAP-ENV:Header><Security xmlns="http://schemas.xmlsoap.org/ws/2002/12/secext" + xsi:type="wsse:SecurityType"></Security><RequesterCredentials xmlns="urn:ebay:api:PayPalAPI" xsi:type="ebl:CustomSecurityHeaderType"><Credentials + xmlns="urn:ebay:apis:eBLBaseComponents" xsi:type="ebl:UserIdPasswordType"><Username xsi:type="xs:string"></Username><Password + xsi:type="xs:string"></Password><Signature xsi:type="xs:string"></Signature><Subject xsi:type="xs:string"></Subject></Credentials> + </RequesterCredentials></SOAP-ENV:Header><SOAP-ENV:Body id="_0"><BAUpdateResponse xmlns="urn:ebay:api:PayPalAPI"><Timestamp + xmlns="urn:ebay:apis:eBLBaseComponents">2014-05-08T09:30:49Z</Timestamp><Ack xmlns="urn:ebay:apis:eBLBaseComponents">Failure</Ack> + <CorrelationID xmlns="urn:ebay:apis:eBLBaseComponents">fb481ac974e22</CorrelationID><Errors xmlns="urn:ebay:apis:eBLBaseComponents" + xsi:type="ebl:ErrorType"><ShortMessage xsi:type="xs:string">Billing Agreement Id or transaction Id is not valid</ShortMessage> + <LongMessage xsi:type="xs:string">Billing Agreement Id or transaction Id is not valid</LongMessage><ErrorCode xsi:type="xs:token">11451</ErrorCode> + <SeverityCode xmlns="urn:ebay:apis:eBLBaseComponents">Error</SeverityCode></Errors><Version xmlns="urn:ebay:apis:eBLBaseComponents">72</Version> + <Build xmlns="urn:ebay:apis:eBLBaseComponents">10918103</Build><BAUpdateResponseDetails xmlns="urn:ebay:apis:eBLBaseComponents" + xsi:type="ebl:BAUpdateResponseDetailsType"><PayerInfo xsi:type="ebl:PayerInfoType"><Payer xsi:type="ebl:EmailAddressType"></Payer> + <PayerID xsi:type="ebl:UserIDType"></PayerID><PayerStatus xsi:type="ebl:PayPalUserStatusCodeType">unverified</PayerStatus><PayerName + xsi:type="ebl:PersonNameType"><Salutation xmlns="urn:ebay:apis:eBLBaseComponents"></Salutation><FirstName xmlns="urn:ebay:apis:eBLBaseComponents"> + </FirstName><MiddleName xmlns="urn:ebay:apis:eBLBaseComponents"></MiddleName><LastName xmlns="urn:ebay:apis:eBLBaseComponents"></LastName><Suffix + xmlns="urn:ebay:apis:eBLBaseComponents"></Suffix></PayerName><PayerBusiness xsi:type="xs:string"></PayerBusiness><Address xsi:type="ebl:AddressType"> + <Name xsi:type="xs:string"></Name><Street1 xsi:type="xs:string"></Street1><Street2 xsi:type="xs:string"></Street2><CityName xsi:type="xs:string"> + </CityName><StateOrProvince xsi:type="xs:string"></StateOrProvince><CountryName></CountryName><Phone xsi:type="xs:string"></Phone><PostalCode + xsi:type="xs:string"></PostalCode><AddressID xsi:type="xs:string"></AddressID><AddressOwner xsi:type="ebl:AddressOwnerCodeType">PayPal</AddressOwner> + <ExternalAddressID xsi:type="xs:string"></ExternalAddressID><AddressStatus xsi:type="ebl:AddressStatusCodeType">None</AddressStatus></Address> + </PayerInfo></BAUpdateResponseDetails></BAUpdateResponse></SOAP-ENV:Body></SOAP-ENV:Envelope> + RESPONSE + end + + def pre_scrubbed + <<-TRANSCRIPT +<?xml version=\"1.0\" encoding=\"UTF-8\"?><env:Envelope xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:env=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"><env:Header><RequesterCredentials xmlns=\"urn:ebay:api:PayPalAPI\" xmlns:n1=\"urn:ebay:apis:eBLBaseComponents\" env:mustUnderstand=\"0\"><n1:Credentials><n1:Username>activemerchant-cert-test_api1.example.com</n1:Username><n1:Password>ERDD3JRFU5H5DQXS</n1:Password><n1:Subject/></n1:Credentials></RequesterCredentials></env:Header><env:Body><SetExpressCheckoutReq xmlns=\"urn:ebay:api:PayPalAPI\"> + <SetExpressCheckoutRequest xmlns:n2=\"urn:ebay:apis:eBLBaseComponents\"> + <n2:Version>124</n2:Version> + <n2:SetExpressCheckoutRequestDetails> + <n2:ReturnURL>http://example.com/return</n2:ReturnURL> + <n2:CancelURL>http://example.com/cancel</n2:CancelURL> + <n2:ReqBillingAddress>0</n2:ReqBillingAddress> + <n2:NoShipping>0</n2:NoShipping> + <n2:AddressOverride>0</n2:AddressOverride> + <n2:BuyerEmail>buyer@jadedpallet.com</n2:BuyerEmail> + <n2:PaymentDetails> + <n2:OrderTotal currencyID=\"USD\">5.00</n2:OrderTotal> + <n2:OrderDescription>Stuff that you purchased, yo!</n2:OrderDescription> + <n2:InvoiceID>230000</n2:InvoiceID> + <n2:PaymentAction>Authorization</n2:PaymentAction> + </n2:PaymentDetails> + </n2:SetExpressCheckoutRequestDetails> + </SetExpressCheckoutRequest> +</SetExpressCheckoutReq> +</env:Body></env:Envelope> +<?xml version=\"1.0\" encoding=\"UTF-8\"?><SOAP-ENV:Envelope xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:SOAP-ENC=\"http://schemas.xmlsoap.org/soap/encoding/\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xmlns:cc=\"urn:ebay:apis:CoreComponentTypes\" xmlns:wsu=\"http://schemas.xmlsoap.org/ws/2002/07/utility\" xmlns:saml=\"urn:oasis:names:tc:SAML:1.0:assertion\" xmlns:ds=\"http://www.w3.org/2000/09/xmldsig#\" xmlns:wsse=\"http://schemas.xmlsoap.org/ws/2002/12/secext\" xmlns:ed=\"urn:ebay:apis:EnhancedDataTypes\" xmlns:ebl=\"urn:ebay:apis:eBLBaseComponents\" xmlns:ns=\"urn:ebay:api:PayPalAPI\"><SOAP-ENV:Header><Security xmlns=\"http://schemas.xmlsoap.org/ws/2002/12/secext\" xsi:type=\"wsse:SecurityType\"></Security><RequesterCredentials xmlns=\"urn:ebay:api:PayPalAPI\" xsi:type=\"ebl:CustomSecurityHeaderType\"><Credentials xmlns=\"urn:ebay:apis:eBLBaseComponents\" xsi:type=\"ebl:UserIdPasswordType\"><Username xsi:type=\"xs:string\"></Username><Password xsi:type=\"xs:string\"></Password><Signature xsi:type=\"xs:string\"></Signature><Subject xsi:type=\"xs:string\"></Subject></Credentials></RequesterCredentials></SOAP-ENV:Header><SOAP-ENV:Body id=\"_0\"><SetExpressCheckoutResponse xmlns=\"urn:ebay:api:PayPalAPI\"><Timestamp xmlns=\"urn:ebay:apis:eBLBaseComponents\">2018-05-24T20:23:54Z</Timestamp><Ack xmlns=\"urn:ebay:apis:eBLBaseComponents\">Success</Ack><CorrelationID xmlns=\"urn:ebay:apis:eBLBaseComponents\">b6dd2a043921b</CorrelationID><Version xmlns=\"urn:ebay:apis:eBLBaseComponents\">124</Version><Build xmlns=\"urn:ebay:apis:eBLBaseComponents\">46549960</Build><Token xsi:type=\"ebl:ExpressCheckoutTokenType\">EC-7KR85820NC734104L</Token></SetExpressCheckoutResponse></SOAP-ENV:Body></SOAP-ENV:Envelope> + TRANSCRIPT + end + + def post_scrubbed + <<-TRANSCRIPT +<?xml version=\"1.0\" encoding=\"UTF-8\"?><env:Envelope xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:env=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"><env:Header><RequesterCredentials xmlns=\"urn:ebay:api:PayPalAPI\" xmlns:n1=\"urn:ebay:apis:eBLBaseComponents\" env:mustUnderstand=\"0\"><n1:Credentials><n1:Username>[FILTERED]</n1:Username><n1:Password>[FILTERED]</n1:Password><n1:Subject/></n1:Credentials></RequesterCredentials></env:Header><env:Body><SetExpressCheckoutReq xmlns=\"urn:ebay:api:PayPalAPI\"> + <SetExpressCheckoutRequest xmlns:n2=\"urn:ebay:apis:eBLBaseComponents\"> + <n2:Version>124</n2:Version> + <n2:SetExpressCheckoutRequestDetails> + <n2:ReturnURL>http://example.com/return</n2:ReturnURL> + <n2:CancelURL>http://example.com/cancel</n2:CancelURL> + <n2:ReqBillingAddress>0</n2:ReqBillingAddress> + <n2:NoShipping>0</n2:NoShipping> + <n2:AddressOverride>0</n2:AddressOverride> + <n2:BuyerEmail>buyer@jadedpallet.com</n2:BuyerEmail> + <n2:PaymentDetails> + <n2:OrderTotal currencyID=\"USD\">5.00</n2:OrderTotal> + <n2:OrderDescription>Stuff that you purchased, yo!</n2:OrderDescription> + <n2:InvoiceID>230000</n2:InvoiceID> + <n2:PaymentAction>Authorization</n2:PaymentAction> + </n2:PaymentDetails> + </n2:SetExpressCheckoutRequestDetails> + </SetExpressCheckoutRequest> +</SetExpressCheckoutReq> +</env:Body></env:Envelope> +<?xml version=\"1.0\" encoding=\"UTF-8\"?><SOAP-ENV:Envelope xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:SOAP-ENC=\"http://schemas.xmlsoap.org/soap/encoding/\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xmlns:cc=\"urn:ebay:apis:CoreComponentTypes\" xmlns:wsu=\"http://schemas.xmlsoap.org/ws/2002/07/utility\" xmlns:saml=\"urn:oasis:names:tc:SAML:1.0:assertion\" xmlns:ds=\"http://www.w3.org/2000/09/xmldsig#\" xmlns:wsse=\"http://schemas.xmlsoap.org/ws/2002/12/secext\" xmlns:ed=\"urn:ebay:apis:EnhancedDataTypes\" xmlns:ebl=\"urn:ebay:apis:eBLBaseComponents\" xmlns:ns=\"urn:ebay:api:PayPalAPI\"><SOAP-ENV:Header><Security xmlns=\"http://schemas.xmlsoap.org/ws/2002/12/secext\" xsi:type=\"wsse:SecurityType\"></Security><RequesterCredentials xmlns=\"urn:ebay:api:PayPalAPI\" xsi:type=\"ebl:CustomSecurityHeaderType\"><Credentials xmlns=\"urn:ebay:apis:eBLBaseComponents\" xsi:type=\"ebl:UserIdPasswordType\"><Username xsi:type=\"xs:string\"></Username><Password xsi:type=\"xs:string\"></Password><Signature xsi:type=\"xs:string\"></Signature><Subject xsi:type=\"xs:string\"></Subject></Credentials></RequesterCredentials></SOAP-ENV:Header><SOAP-ENV:Body id=\"_0\"><SetExpressCheckoutResponse xmlns=\"urn:ebay:api:PayPalAPI\"><Timestamp xmlns=\"urn:ebay:apis:eBLBaseComponents\">2018-05-24T20:23:54Z</Timestamp><Ack xmlns=\"urn:ebay:apis:eBLBaseComponents\">Success</Ack><CorrelationID xmlns=\"urn:ebay:apis:eBLBaseComponents\">b6dd2a043921b</CorrelationID><Version xmlns=\"urn:ebay:apis:eBLBaseComponents\">124</Version><Build xmlns=\"urn:ebay:apis:eBLBaseComponents\">46549960</Build><Token xsi:type=\"ebl:ExpressCheckoutTokenType\">EC-7KR85820NC734104L</Token></SetExpressCheckoutResponse></SOAP-ENV:Body></SOAP-ENV:Envelope> + TRANSCRIPT + end end diff --git a/test/unit/gateways/paypal_test.rb b/test/unit/gateways/paypal_test.rb index 52d6d65f703..306e4bf5dd9 100644 --- a/test/unit/gateways/paypal_test.rb +++ b/test/unit/gateways/paypal_test.rb @@ -4,7 +4,6 @@ class PaypalTest < Test::Unit::TestCase include CommStub def setup - Base.mode = :test PaypalGateway.pem_file = nil @amount = 100 @@ -20,47 +19,85 @@ def setup end def test_no_ip_address - assert_raise(ArgumentError){ @gateway.purchase(@amount, @credit_card, :billing_address => address)} + assert_raise(ArgumentError) do + @gateway.purchase(@amount, @credit_card, :billing_address => address) + end end def test_recurring_requires_description @recurring_required_fields.delete(:description) - assert_raise(ArgumentError){ @gateway.recurring(@amount, @credit_card, @options.merge(@recurring_required_fields)) } + assert_raise(ArgumentError) do + assert_deprecation_warning(Gateway::RECURRING_DEPRECATION_MESSAGE) do + @gateway.recurring(@amount, @credit_card, @options.merge(@recurring_required_fields)) + end + end end def test_recurring_requires_start_date @recurring_required_fields.delete(:start_date) - assert_raise(ArgumentError){ @gateway.recurring(@amount, @credit_card, @options.merge(@recurring_required_fields)) } + assert_raise(ArgumentError) do + assert_deprecation_warning(Gateway::RECURRING_DEPRECATION_MESSAGE) do + @gateway.recurring(@amount, @credit_card, @options.merge(@recurring_required_fields)) + end + end end def test_recurring_requires_frequency @recurring_required_fields.delete(:frequency) - assert_raise(ArgumentError){ @gateway.recurring(@amount, @credit_card, @options.merge(@recurring_required_fields)) } + assert_raise(ArgumentError) do + assert_deprecation_warning(Gateway::RECURRING_DEPRECATION_MESSAGE) do + @gateway.recurring(@amount, @credit_card, @options.merge(@recurring_required_fields)) + end + end end def test_recurring_requires_period @recurring_required_fields.delete(:period) - assert_raise(ArgumentError){ @gateway.recurring(@amount, @credit_card, @options.merge(@recurring_required_fields)) } + assert_raise(ArgumentError) do + assert_deprecation_warning(Gateway::RECURRING_DEPRECATION_MESSAGE) do + @gateway.recurring(@amount, @credit_card, @options.merge(@recurring_required_fields)) + end + end end def test_update_recurring_requires_profile_id - assert_raise(ArgumentError){ @gateway.update_recurring(:amount => 100)} + assert_raise(ArgumentError) do + assert_deprecation_warning(Gateway::RECURRING_DEPRECATION_MESSAGE) do + @gateway.update_recurring(:amount => 100) + end + end end def test_cancel_recurring_requires_profile_id - assert_raise(ArgumentError){ @gateway.cancel_recurring(nil, :note => 'Note')} + assert_raise(ArgumentError) do + assert_deprecation_warning(Gateway::RECURRING_DEPRECATION_MESSAGE) do + @gateway.cancel_recurring(nil, :note => 'Note') + end + end end def test_status_recurring_requires_profile_id - assert_raise(ArgumentError){ @gateway.status_recurring(nil, :note => 'Note')} + assert_raise(ArgumentError) do + assert_deprecation_warning(Gateway::RECURRING_DEPRECATION_MESSAGE) do + @gateway.status_recurring(nil) + end + end end def test_suspend_recurring_requires_profile_id - assert_raise(ArgumentError){ @gateway.suspend_recurring(nil, :note => 'Note')} + assert_raise(ArgumentError) do + assert_deprecation_warning(Gateway::RECURRING_DEPRECATION_MESSAGE) do + @gateway.suspend_recurring(nil, :note => 'Note') + end + end end def test_reactivate_recurring_requires_profile_id - assert_raise(ArgumentError){ @gateway.reactivate_recurring(nil, :note => 'Note')} + assert_raise(ArgumentError) do + assert_deprecation_warning(Gateway::RECURRING_DEPRECATION_MESSAGE) do + @gateway.reactivate_recurring(nil, :note => 'Note') + end + end end def test_successful_purchase_with_auth_signature @@ -117,6 +154,15 @@ def test_failed_purchase assert response.test? end + def test_descriptors_passed + stub_comms do + @gateway.purchase(@amount, @credit_card, @options.merge(soft_descriptor: 'Eggcellent', soft_descriptor_city: 'New York')) + end.check_request do |endpoint, data, headers| + assert_match(%r{<n2:SoftDescriptor>Eggcellent}, data) + assert_match(%r{<n2:SoftDescriptorCity>New York}, data) + end.respond_with(successful_purchase_response) + end + def test_reauthorization @gateway.expects(:ssl_post).returns(successful_reauthorization_response) response = @gateway.reauthorize(@amount, '32J876265E528623B') @@ -134,20 +180,20 @@ def test_reauthorization_with_warning end def test_amount_style - assert_equal '10.34', @gateway.send(:amount, 1034) + assert_equal '10.34', @gateway.send(:amount, 1034) - assert_raise(ArgumentError) do - @gateway.send(:amount, '10.34') - end + assert_raise(ArgumentError) do + @gateway.send(:amount, '10.34') + end end def test_paypal_timeout_error @gateway.stubs(:ssl_post).returns(paypal_timeout_error_response) response = @gateway.purchase(@amount, @credit_card, @options) - assert_equal "SOAP-ENV:Server", response.params['faultcode'] - assert_equal "Internal error", response.params['faultstring'] - assert_equal "Timeout processing request", response.params['detail'] - assert_equal "SOAP-ENV:Server: Internal error - Timeout processing request", response.message + assert_equal 'SOAP-ENV:Server', response.params['faultcode'] + assert_equal 'Internal error', response.params['faultstring'] + assert_equal 'Timeout processing request', response.params['detail'] + assert_equal 'SOAP-ENV:Server: Internal error - Timeout processing request', response.message end def test_pem_file_accessor @@ -173,7 +219,7 @@ def test_ensure_options_are_transferred_to_express_instance end def test_supported_countries - assert_equal ['US'], PaypalGateway.supported_countries + assert_equal ['CA', 'NZ', 'GB', 'US'], PaypalGateway.supported_countries end def test_supported_card_types @@ -187,6 +233,32 @@ def test_button_source assert_equal 'ActiveMerchant_DC', REXML::XPath.first(xml, '//n2:ButtonSource').text end + def test_button_source_via_credentials + PaypalGateway.application_id = 'ActiveMerchant_DC' + gateway = PaypalGateway.new( + login: 'cody', + password: 'test', + pem: 'PEM', + button_source: 'WOOHOO' + ) + + xml = REXML::Document.new(gateway.send(:build_sale_or_authorization_request, 'Test', @amount, @credit_card, {})) + assert_equal 'WOOHOO', REXML::XPath.first(xml, '//n2:ButtonSource').text + end + + def test_button_source_via_credentials_with_no_application_id + PaypalGateway.application_id = nil + gateway = PaypalGateway.new( + login: 'cody', + password: 'test', + pem: 'PEM', + button_source: 'WOOHOO' + ) + + xml = REXML::Document.new(gateway.send(:build_sale_or_authorization_request, 'Test', @amount, @credit_card, {})) + assert_equal 'WOOHOO', REXML::XPath.first(xml, '//n2:ButtonSource').text + end + def test_item_total_shipping_handling_and_tax_not_included_unless_all_are_present xml = @gateway.send(:build_sale_or_authorization_request, 'Authorization', @amount, @credit_card, :tax => @amount, @@ -276,8 +348,8 @@ def test_fraud_review response = @gateway.purchase(@amount, @credit_card, @options) assert_success response - assert_equal "SuccessWithWarning", response.params["ack"] - assert_equal "Payment Pending your review in Fraud Management Filters", response.message + assert_equal 'SuccessWithWarning', response.params['ack'] + assert_equal 'Payment Pending your review in Fraud Management Filters', response.message assert response.fraud_review? end @@ -286,7 +358,7 @@ def test_failed_capture_due_to_pending_fraud_review response = @gateway.capture(@amount, 'authorization') assert_failure response - assert_equal "Transaction must be accepted in Fraud Management Filters before capture.", response.message + assert_equal 'Transaction must be accepted in Fraud Management Filters before capture.', response.message end # This occurs when sufficient 3rd party API permissions are not present to make the call for the user @@ -294,8 +366,8 @@ def test_authentication_failed_response @gateway.expects(:ssl_post).returns(authentication_failed_response) response = @gateway.purchase(@amount, @credit_card, @options) assert_failure response - assert_equal "10002", response.params["error_codes"] - assert_equal "You do not have permissions to make this API call", response.message + assert_equal '10002', response.params['error_codes'] + assert_equal 'You do not have permissions to make this API call', response.message end def test_amount_format_for_jpy_currency @@ -306,7 +378,9 @@ def test_amount_format_for_jpy_currency def test_successful_create_profile @gateway.expects(:ssl_post).returns(successful_create_profile_paypal_response) - response = @gateway.recurring(@amount, @credit_card, :description => "some description", :start_date => Time.now, :frequency => 12, :period => 'Month') + response = assert_deprecation_warning(Gateway::RECURRING_DEPRECATION_MESSAGE) do + @gateway.recurring(@amount, @credit_card, :description => 'some description', :start_date => Time.now, :frequency => 12, :period => 'Month') + end assert_instance_of Response, response assert response.success? assert response.test? @@ -316,7 +390,9 @@ def test_successful_create_profile def test_failed_create_profile @gateway.expects(:ssl_post).returns(failed_create_profile_paypal_response) - response = @gateway.recurring(@amount, @credit_card, :description => "some description", :start_date => Time.now, :frequency => 12, :period => 'Month') + response = assert_deprecation_warning(Gateway::RECURRING_DEPRECATION_MESSAGE) do + @gateway.recurring(@amount, @credit_card, :description => 'some description', :start_date => Time.now, :frequency => 12, :period => 'Month') + end assert_instance_of Response, response assert !response.success? assert response.test? @@ -327,42 +403,56 @@ def test_failed_create_profile def test_update_recurring_delegation @gateway.expects(:build_change_profile_request).with('I-G7A2FF8V75JY', :amount => 200) @gateway.stubs(:commit) - @gateway.update_recurring(:profile_id => 'I-G7A2FF8V75JY', :amount => 200) + assert_deprecation_warning(Gateway::RECURRING_DEPRECATION_MESSAGE) do + @gateway.update_recurring(:profile_id => 'I-G7A2FF8V75JY', :amount => 200) + end end def test_update_recurring_response @gateway.expects(:ssl_post).returns(successful_update_recurring_payment_profile_response) - response = @gateway.update_recurring(:profile_id => 'I-G7A2FF8V75JY', :amount => 200) + response = assert_deprecation_warning(Gateway::RECURRING_DEPRECATION_MESSAGE) do + @gateway.update_recurring(:profile_id => 'I-G7A2FF8V75JY', :amount => 200) + end assert response.success? end def test_cancel_recurring_delegation @gateway.expects(:build_manage_profile_request).with('I-G7A2FF8V75JY', 'Cancel', :note => 'A Note').returns(:cancel_request) @gateway.expects(:commit).with('ManageRecurringPaymentsProfileStatus', :cancel_request) - @gateway.cancel_recurring('I-G7A2FF8V75JY', :note => 'A Note') + assert_deprecation_warning(Gateway::RECURRING_DEPRECATION_MESSAGE) do + @gateway.cancel_recurring('I-G7A2FF8V75JY', :note => 'A Note') + end end def test_suspend_recurring_delegation @gateway.expects(:build_manage_profile_request).with('I-G7A2FF8V75JY', 'Suspend', :note => 'A Note').returns(:request) @gateway.expects(:commit).with('ManageRecurringPaymentsProfileStatus', :request) - @gateway.suspend_recurring('I-G7A2FF8V75JY', :note => 'A Note') + assert_deprecation_warning(Gateway::RECURRING_DEPRECATION_MESSAGE) do + @gateway.suspend_recurring('I-G7A2FF8V75JY', :note => 'A Note') + end end def test_reactivate_recurring_delegation @gateway.expects(:build_manage_profile_request).with('I-G7A2FF8V75JY', 'Reactivate', :note => 'A Note').returns(:request) @gateway.expects(:commit).with('ManageRecurringPaymentsProfileStatus', :request) - @gateway.reactivate_recurring('I-G7A2FF8V75JY', :note => 'A Note') + assert_deprecation_warning(Gateway::RECURRING_DEPRECATION_MESSAGE) do + @gateway.reactivate_recurring('I-G7A2FF8V75JY', :note => 'A Note') + end end def test_status_recurring_delegation @gateway.expects(:build_get_profile_details_request).with('I-G7A2FF8V75JY').returns(:request) @gateway.expects(:commit).with('GetRecurringPaymentsProfileDetails', :request) - @gateway.status_recurring('I-G7A2FF8V75JY') + assert_deprecation_warning(Gateway::RECURRING_DEPRECATION_MESSAGE) do + @gateway.status_recurring('I-G7A2FF8V75JY') + end end def test_status_recurring_response - @gateway.expects(:ssl_post).returns(succesful_get_recurring_payments_profile_response) - response = @gateway.status_recurring('I-M1L3RX91DPDD') + @gateway.expects(:ssl_post).returns(successful_get_recurring_payments_profile_response) + response = assert_deprecation_warning(Gateway::RECURRING_DEPRECATION_MESSAGE) do + @gateway.status_recurring('I-M1L3RX91DPDD') + end assert response.success? assert_equal 'I-M1L3RX91DPDD', response.params['profile_id'] end @@ -370,39 +460,215 @@ def test_status_recurring_response def test_bill_outstanding_amoung_delegation @gateway.expects(:build_bill_outstanding_amount).with('I-G7A2FF8V75JY', :amount => 400).returns(:request) @gateway.expects(:commit).with('BillOutstandingAmount', :request) - @gateway.bill_outstanding_amount('I-G7A2FF8V75JY', :amount => 400) + assert_deprecation_warning(Gateway::RECURRING_DEPRECATION_MESSAGE) do + @gateway.bill_outstanding_amount('I-G7A2FF8V75JY', :amount => 400) + end end def test_bill_outstanding_amoung_response @gateway.expects(:ssl_post).returns(successful_bill_outstanding_amount) - response = @gateway.bill_outstanding_amount('I-G7A2FF8V75JY', :amount => 400) + response = assert_deprecation_warning(Gateway::RECURRING_DEPRECATION_MESSAGE) do + @gateway.bill_outstanding_amount('I-G7A2FF8V75JY', :amount => 400) + end assert response.success? end def test_mass_pay_transfer_recipient_types - response = stub_comms do + stub_comms do @gateway.transfer 1000, 'fred@example.com' end.check_request do |endpoint, data, headers| assert_no_match %r{ReceiverType}, data end.respond_with(successful_purchase_response) - response = stub_comms do - @gateway.transfer 1000, 'fred@example.com', :receiver_type => "EmailAddress" + stub_comms do + @gateway.transfer 1000, 'fred@example.com', :receiver_type => 'EmailAddress' end.check_request do |endpoint, data, headers| assert_match %r{<ReceiverType>EmailAddress</ReceiverType>}, data assert_match %r{<ReceiverEmail>fred@example\.com</ReceiverEmail>}, data end.respond_with(successful_purchase_response) - response = stub_comms do - @gateway.transfer 1000, 'fred@example.com', :receiver_type => "UserID" + stub_comms do + @gateway.transfer 1000, 'fred@example.com', :receiver_type => 'UserID' end.check_request do |endpoint, data, headers| assert_match %r{<ReceiverType>UserID</ReceiverType>}, data assert_match %r{<ReceiverID>fred@example\.com</ReceiverID>}, data end.respond_with(successful_purchase_response) end + def test_successful_verify + response = stub_comms do + @gateway.verify(@credit_card, @options) + end.respond_with(successful_zero_dollar_auth_response) + assert_success response + assert_equal 'This card authorization verification is not a payment transaction.', response.message + assert_equal '0.00', response.params['amount'] + end + + def test_failed_verify + response = stub_comms do + @gateway.verify(@credit_card, @options) + end.respond_with(failed_zero_dollar_auth_response) + assert_failure response + assert_match %r{This transaction cannot be processed}, response.message + end + + def test_successful_verify_non_visa_mc + amex_card = credit_card('371449635398431', brand: nil, verification_value: '1234') + response = stub_comms do + @gateway.verify(amex_card, @options) + end.respond_with(successful_one_dollar_auth_response, successful_void_response) + assert_success response + assert_equal 'Success', response.message + assert_equal '1.00', response.params['amount'] + end + + def test_successful_verify_non_visa_mc_failed_void + amex_card = credit_card('371449635398431', brand: nil, verification_value: '1234') + response = stub_comms do + @gateway.verify(amex_card, @options) + end.respond_with(successful_one_dollar_auth_response, failed_void_response) + assert_success response + assert_equal 'Success', response.message + assert_equal '1.00', response.params['amount'] + end + + def test_failed_verify_non_visa_mc + amex_card = credit_card('371449635398431', brand: nil, verification_value: '1234') + response = stub_comms do + @gateway.verify(amex_card, @options) + end.respond_with(failed_one_dollar_auth_response, successful_void_response) + assert_failure response + assert_match %r{This transaction cannot be processed}, response.message + assert_equal '1.00', response.params['amount'] + end + + def test_scrub + assert_equal @gateway.scrub(pre_scrubbed), post_scrubbed + end + + def test_supports_scrubbing? + assert @gateway.supports_scrubbing? + end + + def test_includes_cvv_tag + stub_comms do + @gateway.purchase(@amount, @credit_card, @options) + end.check_request do |endpoint, data, headers| + assert_match(%r{CVV2}, data) + end.respond_with(successful_purchase_response) + end + + def test_blank_cvv_not_sent + @credit_card.verification_value = nil + stub_comms do + @gateway.purchase(@amount, @credit_card, @options) + end.check_request do |endpoint, data, headers| + assert_no_match(%r{CVV2}, data) + end.respond_with(successful_purchase_response) + + @credit_card.verification_value = ' ' + stub_comms do + @gateway.purchase(@amount, @credit_card, @options) + end.check_request do |endpoint, data, headers| + assert_no_match(%r{CVV2}, data) + end.respond_with(successful_purchase_response) + end + + def test_card_declined + ['15005', '10754', '10752', '10759', '10761', '15002', '11084'].each do |error_code| + @gateway.expects(:ssl_request).returns(response_with_error_code(error_code)) + + response = @gateway.purchase(@amount, @credit_card, @options) + assertion_failed_message = "error_code #{error_code} should have been translated to :card_declined" + assert_equal(:card_declined, response.error_code, assertion_failed_message) + end + end + + def test_incorrect_cvc + ['15004'].each do |error_code| + @gateway.expects(:ssl_request).returns(response_with_error_code(error_code)) + + response = @gateway.purchase(@amount, @credit_card, @options) + assertion_failed_message = "error_code #{error_code} should have been translated to :card_declined" + assert_equal(:incorrect_cvc, response.error_code, assertion_failed_message) + end + end + + def test_invalid_cvc + ['10762'].each do |error_code| + @gateway.expects(:ssl_request).returns(response_with_error_code(error_code)) + + response = @gateway.purchase(@amount, @credit_card, @options) + assertion_failed_message = "error_code #{error_code} should have been translated to :card_declined" + assert_equal(:invalid_cvc, response.error_code, assertion_failed_message) + end + end + + def test_error_code_with_no_mapping_returns_standardized_processing_error + @gateway.expects(:ssl_request).returns(response_with_error_code('999999')) + + response = @gateway.purchase(@amount, @credit_card, @options) + assert_equal(:processing_error, response.error_code) + end + + def test_3ds_version_1_request + stub_comms do + @gateway.purchase(@amount, @credit_card, @options.merge(three_d_secure_option)) + end.check_request do |endpoint, data, headers| + assert_match %r{<n2:Version>124</n2:Version>}, data + assert_match %r{<AuthStatus3ds>Y</AuthStatus3ds>}, data + assert_match %r{<Cavv>cavv</Cavv>}, data + assert_match %r{<Eci3ds>eci</Eci3ds>}, data + assert_match %r{<Xid>xid</Xid>}, data + end.respond_with(successful_purchase_response) + end + private + def pre_scrubbed + <<-PRE_SCRUBBED + opening connection to api-3t.sandbox.paypal.com:443... + opened + starting SSL for api-3t.sandbox.paypal.com:443... + SSL established + <- "POST /2.0/ HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: api-3t.sandbox.paypal.com\r\nContent-Length: 2229\r\n\r\n" + <- "<?xml version=\"1.0\" encoding=\"UTF-8\"?><env:Envelope xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:env=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"><env:Header><RequesterCredentials xmlns=\"urn:ebay:api:PayPalAPI\" xmlns:n1=\"urn:ebay:apis:eBLBaseComponents\" env:mustUnderstand=\"0\"><n1:Credentials><n1:Username>activemerchant-test_api1.example.com</n1:Username><n1:Password>HBC6A84QLRWC923A</n1:Password><n1:Subject/><n1:Signature>AFcWxV21C7fd0v3bYYYRCpSSRl31AC-11AKBL8FFO9tjImL311y8a0hx</n1:Signature></n1:Credentials></RequesterCredentials></env:Header><env:Body><DoDirectPaymentReq xmlns=\"urn:ebay:api:PayPalAPI\">\n <DoDirectPaymentRequest xmlns:n2=\"urn:ebay:apis:eBLBaseComponents\">\n <n2:Version>72</n2:Version>\n <n2:DoDirectPaymentRequestDetails>\n <n2:PaymentAction>Sale</n2:PaymentAction>\n <n2:PaymentDetails>\n <n2:OrderTotal currencyID=\"USD\">1.00</n2:OrderTotal>\n <n2:OrderDescription>Stuff that you purchased, yo!</n2:OrderDescription>\n <n2:InvoiceID>70e472b155c61d27fe19555a96d51127</n2:InvoiceID>\n <n2:ButtonSource>ActiveMerchant</n2:ButtonSource>\n </n2:PaymentDetails>\n <n2:CreditCard>\n <n2:CreditCardType>Visa</n2:CreditCardType>\n <n2:CreditCardNumber>4381258770269608</n2:CreditCardNumber>\n <n2:ExpMonth>09</n2:ExpMonth>\n <n2:ExpYear>2015</n2:ExpYear>\n <n2:CVV2>123</n2:CVV2>\n <n2:CardOwner>\n <n2:PayerName>\n <n2:FirstName>Longbob</n2:FirstName>\n <n2:LastName>Longsen</n2:LastName>\n </n2:PayerName>\n <n2:Payer>buyer@jadedpallet.com</n2:Payer>\n <n2:Address>\n <n2:Name>Longbob Longsen</n2:Name>\n <n2:Street1>1234 Penny Lane</n2:Street1>\n <n2:Street2/>\n <n2:CityName>Jonsetown</n2:CityName>\n <n2:StateOrProvince>NC</n2:StateOrProvince>\n <n2:Country>US</n2:Country>\n <n2:PostalCode>23456</n2:PostalCode>\n </n2:Address>\n </n2:CardOwner>\n </n2:CreditCard>\n <n2:IPAddress>10.0.0.1</n2:IPAddress>\n </n2:DoDirectPaymentRequestDetails>\n </DoDirectPaymentRequest>\n</DoDirectPaymentReq>\n</env:Body></env:Envelope>" + -> "HTTP/1.1 200 OK\r\n" + -> "Date: Tue, 02 Dec 2014 18:44:21 GMT\r\n" + -> "Server: Apache\r\n" + -> "Content-Length: 1909\r\n" + -> "Connection: close\r\n" + -> "Content-Type: text/xml; charset=utf-8\r\n" + -> "\r\n" + reading 1909 bytes... + -> "<?xml version=\"1.0\" encoding=\"UTF-8\"?><SOAP-ENV:Envelope xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:SOAP-ENC=\"http://schemas.xmlsoap.org/soap/encoding/\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xmlns:cc=\"urn:ebay:apis:CoreComponentTypes\" xmlns:wsu=\"http://schemas.xmlsoap.org/ws/2002/07/utility\" xmlns:saml=\"urn:oasis:names:tc:SAML:1.0:assertion\" xmlns:ds=\"http://www.w3.org/2000/09/xmldsig#\" xmlns:wsse=\"http://schemas.xmlsoap.org/ws/2002/12/secext\" xmlns:ed=\"urn:ebay:apis:EnhancedDataTypes\" xmlns:ebl=\"urn:ebay:apis:eBLBaseComponents\" xmlns:ns=\"urn:ebay:api:PayPalAPI\"><SOAP-ENV:Header><Security xmlns=\"http://schemas.xmlsoap.org/ws/2002/12/secext\" xsi:type=\"wsse:SecurityType\"></Security><RequesterCredentials xmlns=\"urn:ebay:api:PayPalAPI\" xsi:type=\"ebl:CustomSecurityHeaderType\"><Credentials xmlns=\"urn:ebay:apis:eBLBaseComponents\" xsi:type=\"ebl:UserIdPasswordType\"><Username xsi:type=\"xs:string\"></Username><Password xsi:type=\"xs:string\"></Password><Signature xsi:type=\"xs:string\"></Signature><Subject xsi:type=\"xs:string\"></Subject></Credentials></RequesterCredentials></SOAP-ENV:Header><SOAP-ENV:Body id=\"_0\"><DoDirectPaymentResponse xmlns=\"urn:ebay:api:PayPalAPI\"><Timestamp xmlns=\"urn:ebay:apis:eBLBaseComponents\">2014-12-02T18:44:24Z</Timestamp><Ack xmlns=\"urn:ebay:apis:eBLBaseComponents\">Success</Ack><CorrelationID xmlns=\"urn:ebay:apis:eBLBaseComponents\">28804ee3a8eb7</CorrelationID><Version xmlns=\"urn:ebay:apis:eBLBaseComponents\">72</Version><Build xmlns=\"urn:ebay:apis:eBLBaseComponents\">13597118</Build><Amount xsi:type=\"cc:BasicAmountType\" currencyID=\"USD\">1.00</Amount><AVSCode xsi:type=\"xs:string\">X</AVSCode><CVV2Code xsi:type=\"xs:string\">M</CVV2Code><TransactionID>38L91123G19597918</TransactionID></DoDirectPaymentResponse></SOAP-ENV:Body></SOAP-ENV:Envelope>" + read 1909 bytes + Conn close + PRE_SCRUBBED + end + + def post_scrubbed + <<-POST_SCRUBBED + opening connection to api-3t.sandbox.paypal.com:443... + opened + starting SSL for api-3t.sandbox.paypal.com:443... + SSL established + <- "POST /2.0/ HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: api-3t.sandbox.paypal.com\r\nContent-Length: 2229\r\n\r\n" + <- "<?xml version=\"1.0\" encoding=\"UTF-8\"?><env:Envelope xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:env=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"><env:Header><RequesterCredentials xmlns=\"urn:ebay:api:PayPalAPI\" xmlns:n1=\"urn:ebay:apis:eBLBaseComponents\" env:mustUnderstand=\"0\"><n1:Credentials><n1:Username>[FILTERED]</n1:Username><n1:Password>[FILTERED]</n1:Password><n1:Subject/><n1:Signature>[FILTERED]</n1:Signature></n1:Credentials></RequesterCredentials></env:Header><env:Body><DoDirectPaymentReq xmlns=\"urn:ebay:api:PayPalAPI\">\n <DoDirectPaymentRequest xmlns:n2=\"urn:ebay:apis:eBLBaseComponents\">\n <n2:Version>72</n2:Version>\n <n2:DoDirectPaymentRequestDetails>\n <n2:PaymentAction>Sale</n2:PaymentAction>\n <n2:PaymentDetails>\n <n2:OrderTotal currencyID=\"USD\">1.00</n2:OrderTotal>\n <n2:OrderDescription>Stuff that you purchased, yo!</n2:OrderDescription>\n <n2:InvoiceID>70e472b155c61d27fe19555a96d51127</n2:InvoiceID>\n <n2:ButtonSource>ActiveMerchant</n2:ButtonSource>\n </n2:PaymentDetails>\n <n2:CreditCard>\n <n2:CreditCardType>Visa</n2:CreditCardType>\n <n2:CreditCardNumber>[FILTERED]</n2:CreditCardNumber>\n <n2:ExpMonth>09</n2:ExpMonth>\n <n2:ExpYear>2015</n2:ExpYear>\n <n2:CVV2>[FILTERED]</n2:CVV2>\n <n2:CardOwner>\n <n2:PayerName>\n <n2:FirstName>Longbob</n2:FirstName>\n <n2:LastName>Longsen</n2:LastName>\n </n2:PayerName>\n <n2:Payer>buyer@jadedpallet.com</n2:Payer>\n <n2:Address>\n <n2:Name>Longbob Longsen</n2:Name>\n <n2:Street1>1234 Penny Lane</n2:Street1>\n <n2:Street2/>\n <n2:CityName>Jonsetown</n2:CityName>\n <n2:StateOrProvince>NC</n2:StateOrProvince>\n <n2:Country>US</n2:Country>\n <n2:PostalCode>23456</n2:PostalCode>\n </n2:Address>\n </n2:CardOwner>\n </n2:CreditCard>\n <n2:IPAddress>10.0.0.1</n2:IPAddress>\n </n2:DoDirectPaymentRequestDetails>\n </DoDirectPaymentRequest>\n</DoDirectPaymentReq>\n</env:Body></env:Envelope>" + -> "HTTP/1.1 200 OK\r\n" + -> "Date: Tue, 02 Dec 2014 18:44:21 GMT\r\n" + -> "Server: Apache\r\n" + -> "Content-Length: 1909\r\n" + -> "Connection: close\r\n" + -> "Content-Type: text/xml; charset=utf-8\r\n" + -> "\r\n" + reading 1909 bytes... + -> "<?xml version=\"1.0\" encoding=\"UTF-8\"?><SOAP-ENV:Envelope xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:SOAP-ENC=\"http://schemas.xmlsoap.org/soap/encoding/\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xmlns:cc=\"urn:ebay:apis:CoreComponentTypes\" xmlns:wsu=\"http://schemas.xmlsoap.org/ws/2002/07/utility\" xmlns:saml=\"urn:oasis:names:tc:SAML:1.0:assertion\" xmlns:ds=\"http://www.w3.org/2000/09/xmldsig#\" xmlns:wsse=\"http://schemas.xmlsoap.org/ws/2002/12/secext\" xmlns:ed=\"urn:ebay:apis:EnhancedDataTypes\" xmlns:ebl=\"urn:ebay:apis:eBLBaseComponents\" xmlns:ns=\"urn:ebay:api:PayPalAPI\"><SOAP-ENV:Header><Security xmlns=\"http://schemas.xmlsoap.org/ws/2002/12/secext\" xsi:type=\"wsse:SecurityType\"></Security><RequesterCredentials xmlns=\"urn:ebay:api:PayPalAPI\" xsi:type=\"ebl:CustomSecurityHeaderType\"><Credentials xmlns=\"urn:ebay:apis:eBLBaseComponents\" xsi:type=\"ebl:UserIdPasswordType\"><Username xsi:type=\"xs:string\"></Username><Password xsi:type=\"xs:string\"></Password><Signature xsi:type=\"xs:string\"></Signature><Subject xsi:type=\"xs:string\"></Subject></Credentials></RequesterCredentials></SOAP-ENV:Header><SOAP-ENV:Body id=\"_0\"><DoDirectPaymentResponse xmlns=\"urn:ebay:api:PayPalAPI\"><Timestamp xmlns=\"urn:ebay:apis:eBLBaseComponents\">2014-12-02T18:44:24Z</Timestamp><Ack xmlns=\"urn:ebay:apis:eBLBaseComponents\">Success</Ack><CorrelationID xmlns=\"urn:ebay:apis:eBLBaseComponents\">28804ee3a8eb7</CorrelationID><Version xmlns=\"urn:ebay:apis:eBLBaseComponents\">72</Version><Build xmlns=\"urn:ebay:apis:eBLBaseComponents\">13597118</Build><Amount xsi:type=\"cc:BasicAmountType\" currencyID=\"USD\">1.00</Amount><AVSCode xsi:type=\"xs:string\">X</AVSCode><CVV2Code xsi:type=\"xs:string\">M</CVV2Code><TransactionID>38L91123G19597918</TransactionID></DoDirectPaymentResponse></SOAP-ENV:Body></SOAP-ENV:Envelope>" + read 1909 bytes + Conn close + POST_SCRUBBED + end + def successful_purchase_response <<-RESPONSE <?xml version="1.0" encoding="UTF-8"?> @@ -495,6 +761,244 @@ def failed_purchase_response RESPONSE end + def successful_zero_dollar_auth_response + <<-RESPONSE +<?xml version="1.0" encoding="UTF-8"?> +<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:cc="urn:ebay:apis:CoreComponentTypes" xmlns:wsu="http://schemas.xmlsoap.org/ws/2002/07/utility" xmlns:saml="urn:oasis:names:tc:SAML:1.0:assertion" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:wsse="http://schemas.xmlsoap.org/ws/2002/12/secext" xmlns:ed="urn:ebay:apis:EnhancedDataTypes" xmlns:ebl="urn:ebay:apis:eBLBaseComponents" xmlns:ns="urn:ebay:api:PayPalAPI"> + <SOAP-ENV:Header> + <Security xmlns="http://schemas.xmlsoap.org/ws/2002/12/secext" xsi:type="wsse:SecurityType"></Security> + <RequesterCredentials xmlns="urn:ebay:api:PayPalAPI" xsi:type="ebl:CustomSecurityHeaderType"> + <Credentials xmlns="urn:ebay:apis:eBLBaseComponents" xsi:type="ebl:UserIdPasswordType"> + <Username xsi:type="xs:string"> </Username> + <Password xsi:type="xs:string"></Password> + <Signature xsi:type="xs:string"> </Signature> + <Subject xsi:type="xs:string"> </Subject> + </Credentials> + </RequesterCredentials> + </SOAP-ENV:Header> + <SOAP-ENV:Body id="_0"> + <DoDirectPaymentResponse xmlns="urn:ebay:api:PayPalAPI"> + <Timestamp xmlns="urn:ebay:apis:eBLBaseComponents">2014-06-27T18:14:48Z</Timestamp> + <Ack xmlns="urn:ebay:apis:eBLBaseComponents">SuccessWithWarning</Ack> + <CorrelationID xmlns="urn:ebay:apis:eBLBaseComponents">e33ce283dd3d3</CorrelationID> + <Errors xmlns="urn:ebay:apis:eBLBaseComponents" xsi:type="ebl:ErrorType"> + <ShortMessage xsi:type="xs:string">Credit card verified.</ShortMessage> + <LongMessage xsi:type="xs:string">This card authorization verification is not a payment transaction.</LongMessage> + <ErrorCode xsi:type="xs:token">10574</ErrorCode> + <SeverityCode xmlns="urn:ebay:apis:eBLBaseComponents">Warning</SeverityCode> + </Errors> + <Version xmlns="urn:ebay:apis:eBLBaseComponents">72</Version> + <Build xmlns="urn:ebay:apis:eBLBaseComponents">11660982</Build> + <Amount xsi:type="cc:BasicAmountType" currencyID="USD">0.00</Amount> + <AVSCode xsi:type="xs:string">X</AVSCode><CVV2Code xsi:type="xs:string">M</CVV2Code> + <TransactionID>86D41672SH9764158</TransactionID> + </DoDirectPaymentResponse> + </SOAP-ENV:Body> +</SOAP-ENV:Envelope> + RESPONSE + end + + def failed_zero_dollar_auth_response + <<-RESPONSE +<?xml version="1.0" encoding="UTF-8"?> +<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" xmlns:cc="urn:ebay:apis:CoreComponentTypes" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:ebl="urn:ebay:apis:eBLBaseComponents" xmlns:ed="urn:ebay:apis:EnhancedDataTypes" xmlns:ns="urn:ebay:api:PayPalAPI" xmlns:saml="urn:oasis:names:tc:SAML:1.0:assertion" xmlns:wsse="http://schemas.xmlsoap.org/ws/2002/12/secext" xmlns:wsu="http://schemas.xmlsoap.org/ws/2002/07/utility" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <SOAP-ENV:Header> + <Security xmlns="http://schemas.xmlsoap.org/ws/2002/12/secext" xsi:type="wsse:SecurityType" /> + <RequesterCredentials xmlns="urn:ebay:api:PayPalAPI" xsi:type="ebl:CustomSecurityHeaderType"> + <Credentials xmlns="urn:ebay:apis:eBLBaseComponents" xsi:type="ebl:UserIdPasswordType"> + <Username xsi:type="xs:string" /> + <Password xsi:type="xs:string" /> + <Signature xsi:type="xs:string" /> + <Subject xsi:type="xs:string" /> + </Credentials> + </RequesterCredentials> + </SOAP-ENV:Header> + <SOAP-ENV:Body id="_0"> + <DoDirectPaymentResponse xmlns="urn:ebay:api:PayPalAPI"> + <Timestamp xmlns="urn:ebay:apis:eBLBaseComponents">2014-06-27T18:25:51Z</Timestamp> + <Ack xmlns="urn:ebay:apis:eBLBaseComponents">Failure</Ack> + <CorrelationID xmlns="urn:ebay:apis:eBLBaseComponents">5dda14853a55d</CorrelationID> + <Errors xmlns="urn:ebay:apis:eBLBaseComponents" xsi:type="ebl:ErrorType"> + <ShortMessage xsi:type="xs:string">Invalid Data</ShortMessage> + <LongMessage xsi:type="xs:string">This transaction cannot be processed. Please enter a valid credit card number and type.</LongMessage> + <ErrorCode xsi:type="xs:token">10527</ErrorCode> + <SeverityCode>Error</SeverityCode> + </Errors> + <Version xmlns="urn:ebay:apis:eBLBaseComponents">72</Version> + <Build xmlns="urn:ebay:apis:eBLBaseComponents">11660982</Build> + <Amount xsi:type="cc:BasicAmountType" currencyID="USD">0.00</Amount> + </DoDirectPaymentResponse> + </SOAP-ENV:Body> +</SOAP-ENV:Envelope> + RESPONSE + end + + def successful_one_dollar_auth_response + <<-RESPONSE +<?xml version="1.0" encoding="UTF-8"?> +<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" xmlns:cc="urn:ebay:apis:CoreComponentTypes" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:ebl="urn:ebay:apis:eBLBaseComponents" xmlns:ed="urn:ebay:apis:EnhancedDataTypes" xmlns:ns="urn:ebay:api:PayPalAPI" xmlns:saml="urn:oasis:names:tc:SAML:1.0:assertion" xmlns:wsse="http://schemas.xmlsoap.org/ws/2002/12/secext" xmlns:wsu="http://schemas.xmlsoap.org/ws/2002/07/utility" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <SOAP-ENV:Header> + <Security xmlns="http://schemas.xmlsoap.org/ws/2002/12/secext" xsi:type="wsse:SecurityType" /> + <RequesterCredentials xmlns="urn:ebay:api:PayPalAPI" xsi:type="ebl:CustomSecurityHeaderType"> + <Credentials xmlns="urn:ebay:apis:eBLBaseComponents" xsi:type="ebl:UserIdPasswordType"> + <Username xsi:type="xs:string" /> + <Password xsi:type="xs:string" /> + <Signature xsi:type="xs:string" /> + <Subject xsi:type="xs:string" /> + </Credentials> + </RequesterCredentials> + </SOAP-ENV:Header> + <SOAP-ENV:Body id="_0"> + <DoDirectPaymentResponse xmlns="urn:ebay:api:PayPalAPI"> + <Timestamp xmlns="urn:ebay:apis:eBLBaseComponents">2014-06-27T18:39:40Z</Timestamp> + <Ack xmlns="urn:ebay:apis:eBLBaseComponents">Success</Ack> + <CorrelationID xmlns="urn:ebay:apis:eBLBaseComponents">814bcb1ced3d</CorrelationID> + <Version xmlns="urn:ebay:apis:eBLBaseComponents">72</Version> + <Build xmlns="urn:ebay:apis:eBLBaseComponents">11660982</Build> + <Amount xsi:type="cc:BasicAmountType" currencyID="USD">1.00</Amount> + <AVSCode xsi:type="xs:string">X</AVSCode> + <CVV2Code xsi:type="xs:string">M</CVV2Code> + <TransactionID>521683708W7313256</TransactionID> + </DoDirectPaymentResponse> + </SOAP-ENV:Body> +</SOAP-ENV:Envelope> + RESPONSE + end + + def failed_one_dollar_auth_response + <<-RESPONSE +<?xml version="1.0" encoding="UTF-8"?> +<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" xmlns:cc="urn:ebay:apis:CoreComponentTypes" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:ebl="urn:ebay:apis:eBLBaseComponents" xmlns:ed="urn:ebay:apis:EnhancedDataTypes" xmlns:ns="urn:ebay:api:PayPalAPI" xmlns:saml="urn:oasis:names:tc:SAML:1.0:assertion" xmlns:wsse="http://schemas.xmlsoap.org/ws/2002/12/secext" xmlns:wsu="http://schemas.xmlsoap.org/ws/2002/07/utility" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <SOAP-ENV:Header> + <Security xmlns="http://schemas.xmlsoap.org/ws/2002/12/secext" xsi:type="wsse:SecurityType" /> + <RequesterCredentials xmlns="urn:ebay:api:PayPalAPI" xsi:type="ebl:CustomSecurityHeaderType"> + <Credentials xmlns="urn:ebay:apis:eBLBaseComponents" xsi:type="ebl:UserIdPasswordType"> + <Username xsi:type="xs:string" /> + <Password xsi:type="xs:string" /> + <Signature xsi:type="xs:string" /> + <Subject xsi:type="xs:string" /> + </Credentials> + </RequesterCredentials> + </SOAP-ENV:Header> + <SOAP-ENV:Body id="_0"> + <DoDirectPaymentResponse xmlns="urn:ebay:api:PayPalAPI"> + <Timestamp xmlns="urn:ebay:apis:eBLBaseComponents">2014-06-27T18:47:18Z</Timestamp> + <Ack xmlns="urn:ebay:apis:eBLBaseComponents">Failure</Ack> + <CorrelationID xmlns="urn:ebay:apis:eBLBaseComponents">f3ab2d6fc76e4</CorrelationID> + <Errors xmlns="urn:ebay:apis:eBLBaseComponents" xsi:type="ebl:ErrorType"> + <ShortMessage xsi:type="xs:string">Invalid Data</ShortMessage> + <LongMessage xsi:type="xs:string">This transaction cannot be processed. Please enter a valid credit card number and type.</LongMessage> + <ErrorCode xsi:type="xs:token">10527</ErrorCode> + <SeverityCode>Error</SeverityCode> + </Errors> + <Version xmlns="urn:ebay:apis:eBLBaseComponents">72</Version> + <Build xmlns="urn:ebay:apis:eBLBaseComponents">11660982</Build> + <Amount xsi:type="cc:BasicAmountType" currencyID="USD">1.00</Amount> + </DoDirectPaymentResponse> + </SOAP-ENV:Body> +</SOAP-ENV:Envelope> + RESPONSE + end + + def response_with_error_code(error_code) + <<-RESPONSE + <?xml version="1.0" encoding="UTF-8"?> +<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" xmlns:cc="urn:ebay:apis:CoreComponentTypes" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:ebl="urn:ebay:apis:eBLBaseComponents" xmlns:ed="urn:ebay:apis:EnhancedDataTypes" xmlns:ns="urn:ebay:api:PayPalAPI" xmlns:saml="urn:oasis:names:tc:SAML:1.0:assertion" xmlns:wsse="http://schemas.xmlsoap.org/ws/2002/12/secext" xmlns:wsu="http://schemas.xmlsoap.org/ws/2002/07/utility" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <SOAP-ENV:Header> + <Security xmlns="http://schemas.xmlsoap.org/ws/2002/12/secext" xsi:type="wsse:SecurityType" /> + <RequesterCredentials xmlns="urn:ebay:api:PayPalAPI" xsi:type="ebl:CustomSecurityHeaderType"> + <Credentials xmlns="urn:ebay:apis:eBLBaseComponents" xsi:type="ebl:UserIdPasswordType"> + <Username xsi:type="xs:string" /> + <Password xsi:type="xs:string" /> + <Signature xsi:type="xs:string" /> + <Subject xsi:type="xs:string" /> + </Credentials> + </RequesterCredentials> + </SOAP-ENV:Header> + <SOAP-ENV:Body id="_0"> + <DoDirectPaymentResponse xmlns="urn:ebay:api:PayPalAPI"> + <Timestamp xmlns="urn:ebay:apis:eBLBaseComponents">2014-06-27T18:47:18Z</Timestamp> + <Ack xmlns="urn:ebay:apis:eBLBaseComponents">Failure</Ack> + <CorrelationID xmlns="urn:ebay:apis:eBLBaseComponents">f3ab2d6fc76e4</CorrelationID> + <Errors xmlns="urn:ebay:apis:eBLBaseComponents" xsi:type="ebl:ErrorType"> + <ShortMessage xsi:type="xs:string">Invalid Data</ShortMessage> + <LongMessage xsi:type="xs:string">This transaction cannot be processed. Please enter a valid credit card number and type.</LongMessage> + <ErrorCode xsi:type="xs:token">#{error_code}</ErrorCode> + <SeverityCode>Error</SeverityCode> + </Errors> + <Version xmlns="urn:ebay:apis:eBLBaseComponents">72</Version> + <Build xmlns="urn:ebay:apis:eBLBaseComponents">11660982</Build> + <Amount xsi:type="cc:BasicAmountType" currencyID="USD">1.00</Amount> + </DoDirectPaymentResponse> + </SOAP-ENV:Body> +</SOAP-ENV:Envelope> + RESPONSE + end + + def successful_void_response + <<-RESPONSE +<?xml version="1.0" encoding="UTF-8"?> +<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" xmlns:cc="urn:ebay:apis:CoreComponentTypes" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:ebl="urn:ebay:apis:eBLBaseComponents" xmlns:ed="urn:ebay:apis:EnhancedDataTypes" xmlns:ns="urn:ebay:api:PayPalAPI" xmlns:saml="urn:oasis:names:tc:SAML:1.0:assertion" xmlns:wsse="http://schemas.xmlsoap.org/ws/2002/12/secext" xmlns:wsu="http://schemas.xmlsoap.org/ws/2002/07/utility" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <SOAP-ENV:Header> + <Security xmlns="http://schemas.xmlsoap.org/ws/2002/12/secext" xsi:type="wsse:SecurityType" /> + <RequesterCredentials xmlns="urn:ebay:api:PayPalAPI" xsi:type="ebl:CustomSecurityHeaderType"> + <Credentials xmlns="urn:ebay:apis:eBLBaseComponents" xsi:type="ebl:UserIdPasswordType"> + <Username xsi:type="xs:string" /> + <Password xsi:type="xs:string" /> + <Signature xsi:type="xs:string" /> + <Subject xsi:type="xs:string" /> + </Credentials> + </RequesterCredentials> + </SOAP-ENV:Header> + <SOAP-ENV:Body id="_0"> + <DoVoidResponse xmlns="urn:ebay:api:PayPalAPI"> + <Timestamp xmlns="urn:ebay:apis:eBLBaseComponents">2014-06-27T18:39:41Z</Timestamp> + <Ack xmlns="urn:ebay:apis:eBLBaseComponents">Success</Ack> + <CorrelationID xmlns="urn:ebay:apis:eBLBaseComponents">5c184c86a25bc</CorrelationID> + <Version xmlns="urn:ebay:apis:eBLBaseComponents">72</Version> + <Build xmlns="urn:ebay:apis:eBLBaseComponents">11624049</Build> + <AuthorizationID xsi:type="xs:string">521683708W7313256</AuthorizationID> + </DoVoidResponse> + </SOAP-ENV:Body> +</SOAP-ENV:Envelope> + RESPONSE + end + + def failed_void_response + <<-RESPONSE +<?xml version="1.0" encoding="UTF-8"?> +<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" xmlns:cc="urn:ebay:apis:CoreComponentTypes" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:ebl="urn:ebay:apis:eBLBaseComponents" xmlns:ed="urn:ebay:apis:EnhancedDataTypes" xmlns:ns="urn:ebay:api:PayPalAPI" xmlns:saml="urn:oasis:names:tc:SAML:1.0:assertion" xmlns:wsse="http://schemas.xmlsoap.org/ws/2002/12/secext" xmlns:wsu="http://schemas.xmlsoap.org/ws/2002/07/utility" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <SOAP-ENV:Header> + <Security xmlns="http://schemas.xmlsoap.org/ws/2002/12/secext" xsi:type="wsse:SecurityType" /> + <RequesterCredentials xmlns="urn:ebay:api:PayPalAPI" xsi:type="ebl:CustomSecurityHeaderType"> + <Credentials xmlns="urn:ebay:apis:eBLBaseComponents" xsi:type="ebl:UserIdPasswordType"> + <Username xsi:type="xs:string" /> + <Password xsi:type="xs:string" /> + <Signature xsi:type="xs:string" /> + <Subject xsi:type="xs:string" /> + </Credentials> + </RequesterCredentials> + </SOAP-ENV:Header> + <SOAP-ENV:Body id="_0"> + <DoVoidResponse xmlns="urn:ebay:api:PayPalAPI"> + <Timestamp xmlns="urn:ebay:apis:eBLBaseComponents">2014-06-27T18:50:11Z</Timestamp> + <Ack xmlns="urn:ebay:apis:eBLBaseComponents">Failure</Ack> + <CorrelationID xmlns="urn:ebay:apis:eBLBaseComponents">e99444d222eaf</CorrelationID> + <Errors xmlns="urn:ebay:apis:eBLBaseComponents" xsi:type="ebl:ErrorType"> + <ShortMessage xsi:type="xs:string">Transaction refused because of an invalid argument. See additional error messages for details.</ShortMessage> + <LongMessage xsi:type="xs:string">The transaction id is not valid</LongMessage> + <ErrorCode xsi:type="xs:token">10004</ErrorCode> + <SeverityCode>Error</SeverityCode> + </Errors> + <Version xmlns="urn:ebay:apis:eBLBaseComponents">72</Version> + <Build xmlns="urn:ebay:apis:eBLBaseComponents">11624049</Build> + <AuthorizationID xsi:type="xs:string" /> + </DoVoidResponse> + </SOAP-ENV:Body> +</SOAP-ENV:Envelope> + RESPONSE + end + def paypal_timeout_error_response <<-RESPONSE <?xml version='1.0' encoding='UTF-8'?> @@ -888,7 +1392,6 @@ def successful_details_response RESPONSE end - def successful_update_recurring_payment_profile_response <<-RESPONSE <?xml version=\"1.0\" encoding=\"UTF-8\"?><SOAP-ENV:Envelope xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:SOAP-ENC=\"http://schemas.xmlsoap.org/soap/encoding/\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xmlns:cc=\"urn:ebay:apis:CoreComponentTypes\" xmlns:wsu=\"http://schemas.xmlsoap.org/ws/2002/07/utility\" xmlns:saml=\"urn:oasis:names:tc:SAML:1.0:assertion\" xmlns:ds=\"http://www.w3.org/2000/09/xmldsig#\" xmlns:wsse=\"http://schemas.xmlsoap.org/ws/2002/12/secext\" xmlns:ed=\"urn:ebay:apis:EnhancedDataTypes\" xmlns:ebl=\"urn:ebay:apis:eBLBaseComponents\" xmlns:ns=\"urn:ebay:api:PayPalAPI\"><SOAP-ENV:Header><Security xmlns=\"http://schemas.xmlsoap.org/ws/2002/12/secext\" xsi:type=\"wsse:SecurityType\"></Security><RequesterCredentials xmlns=\"urn:ebay:api:PayPalAPI\" xsi:type=\"ebl:CustomSecurityHeaderType\"><Credentials xmlns=\"urn:ebay:apis:eBLBaseComponents\" xsi:type=\"ebl:UserIdPasswordType\"><Username xsi:type=\"xs:string\"></Username><Password xsi:type=\"xs:string\"></Password><Signature xsi:type=\"xs:string\"></Signature><Subject xsi:type=\"xs:string\"></Subject></Credentials></RequesterCredentials></SOAP-ENV:Header><SOAP-ENV:Body id=\"_0\"> @@ -920,9 +1423,20 @@ def successful_bill_outstanding_amount RESPONSE end - def succesful_get_recurring_payments_profile_response + def successful_get_recurring_payments_profile_response <<-RESPONSE <?xml version=\"1.0\" encoding=\"UTF-8\"?><SOAP-ENV:Envelope xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:SOAP-ENC=\"http://schemas.xmlsoap.org/soap/encoding/\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xmlns:cc=\"urn:ebay:apis:CoreComponentTypes\" xmlns:wsu=\"http://schemas.xmlsoap.org/ws/2002/07/utility\" xmlns:saml=\"urn:oasis:names:tc:SAML:1.0:assertion\" xmlns:ds=\"http://www.w3.org/2000/09/xmldsig#\" xmlns:wsse=\"http://schemas.xmlsoap.org/ws/2002/12/secext\" xmlns:ed=\"urn:ebay:apis:EnhancedDataTypes\" xmlns:ebl=\"urn:ebay:apis:eBLBaseComponents\" xmlns:ns=\"urn:ebay:api:PayPalAPI\"><SOAP-ENV:Header><Security xmlns=\"http://schemas.xmlsoap.org/ws/2002/12/secext\" xsi:type=\"wsse:SecurityType\"></Security><RequesterCredentials xmlns=\"urn:ebay:api:PayPalAPI\" xsi:type=\"ebl:CustomSecurityHeaderType\"><Credentials xmlns=\"urn:ebay:apis:eBLBaseComponents\" xsi:type=\"ebl:UserIdPasswordType\"><Username xsi:type=\"xs:string\"></Username><Password xsi:type=\"xs:string\"></Password><Signature xsi:type=\"xs:string\"></Signature><Subject xsi:type=\"xs:string\"></Subject></Credentials></RequesterCredentials></SOAP-ENV:Header><SOAP-ENV:Body id=\"_0\"><GetRecurringPaymentsProfileDetailsResponse xmlns=\"urn:ebay:api:PayPalAPI\"><Timestamp xmlns=\"urn:ebay:apis:eBLBaseComponents\">2012-03-19T21:34:40Z</Timestamp><Ack xmlns=\"urn:ebay:apis:eBLBaseComponents\">Success</Ack><CorrelationID xmlns=\"urn:ebay:apis:eBLBaseComponents\">6f24b53c49232</CorrelationID><Version xmlns=\"urn:ebay:apis:eBLBaseComponents\">72</Version><Build xmlns=\"urn:ebay:apis:eBLBaseComponents\">2649250</Build><GetRecurringPaymentsProfileDetailsResponseDetails xmlns=\"urn:ebay:apis:eBLBaseComponents\" xsi:type=\"ebl:GetRecurringPaymentsProfileDetailsResponseDetailsType\"><ProfileID xsi:type=\"xs:string\">I-M1L3RX91DPDD</ProfileID><ProfileStatus xsi:type=\"ebl:RecurringPaymentsProfileStatusType\">CancelledProfile</ProfileStatus><Description xsi:type=\"xs:string\">A description</Description><AutoBillOutstandingAmount xsi:type=\"ebl:AutoBillType\">NoAutoBill</AutoBillOutstandingAmount><MaxFailedPayments>0</MaxFailedPayments><RecurringPaymentsProfileDetails xsi:type=\"ebl:RecurringPaymentsProfileDetailsType\"><SubscriberName xsi:type=\"xs:string\">Ryan Bates</SubscriberName><SubscriberShippingAddress xsi:type=\"ebl:AddressType\"><Name xsi:type=\"xs:string\"></Name><Street1 xsi:type=\"xs:string\"></Street1><Street2 xsi:type=\"xs:string\"></Street2><CityName xsi:type=\"xs:string\"></CityName><StateOrProvince xsi:type=\"xs:string\"></StateOrProvince><CountryName></CountryName><Phone xsi:type=\"xs:string\"></Phone><PostalCode xsi:type=\"xs:string\"></PostalCode><AddressID xsi:type=\"xs:string\"></AddressID><AddressOwner xsi:type=\"ebl:AddressOwnerCodeType\">PayPal</AddressOwner><ExternalAddressID xsi:type=\"xs:string\"></ExternalAddressID><AddressStatus xsi:type=\"ebl:AddressStatusCodeType\">Unconfirmed</AddressStatus></SubscriberShippingAddress><BillingStartDate xsi:type=\"xs:dateTime\">2012-03-19T11:00:00Z</BillingStartDate></RecurringPaymentsProfileDetails><CurrentRecurringPaymentsPeriod xsi:type=\"ebl:BillingPeriodDetailsType\"><BillingPeriod xsi:type=\"ebl:BillingPeriodTypeType\">Month</BillingPeriod><BillingFrequency>1</BillingFrequency><TotalBillingCycles>0</TotalBillingCycles><Amount xsi:type=\"cc:BasicAmountType\" currencyID=\"USD\">1.23</Amount><ShippingAmount xsi:type=\"cc:BasicAmountType\" currencyID=\"USD\">0.00</ShippingAmount><TaxAmount xsi:type=\"cc:BasicAmountType\" currencyID=\"USD\">0.00</TaxAmount></CurrentRecurringPaymentsPeriod><RecurringPaymentsSummary xsi:type=\"ebl:RecurringPaymentsSummaryType\"><NumberCyclesCompleted>1</NumberCyclesCompleted><NumberCyclesRemaining>-1</NumberCyclesRemaining><OutstandingBalance xsi:type=\"cc:BasicAmountType\" currencyID=\"USD\">1.23</OutstandingBalance><FailedPaymentCount>1</FailedPaymentCount></RecurringPaymentsSummary><CreditCard xsi:type=\"ebl:CreditCardDetailsType\"><CreditCardType xsi:type=\"ebl:CreditCardTypeType\">Visa</CreditCardType><CreditCardNumber xsi:type=\"xs:string\">3576</CreditCardNumber><ExpMonth>1</ExpMonth><ExpYear>2013</ExpYear><CardOwner xsi:type=\"ebl:PayerInfoType\"><PayerStatus xsi:type=\"ebl:PayPalUserStatusCodeType\">unverified</PayerStatus><PayerName xsi:type=\"ebl:PersonNameType\"><FirstName xmlns=\"urn:ebay:apis:eBLBaseComponents\">Ryan</FirstName><LastName xmlns=\"urn:ebay:apis:eBLBaseComponents\">Bates</LastName></PayerName><Address xsi:type=\"ebl:AddressType\"><AddressOwner xsi:type=\"ebl:AddressOwnerCodeType\">PayPal</AddressOwner><AddressStatus xsi:type=\"ebl:AddressStatusCodeType\">Unconfirmed</AddressStatus></Address></CardOwner><StartMonth>0</StartMonth><StartYear>0</StartYear><ThreeDSecureRequest xsi:type=\"ebl:ThreeDSecureRequestType\"></ThreeDSecureRequest></CreditCard><RegularRecurringPaymentsPeriod xsi:type=\"ebl:BillingPeriodDetailsType\"><BillingPeriod xsi:type=\"ebl:BillingPeriodTypeType\">Month</BillingPeriod><BillingFrequency>1</BillingFrequency><TotalBillingCycles>0</TotalBillingCycles><Amount xsi:type=\"cc:BasicAmountType\" currencyID=\"USD\">1.23</Amount><ShippingAmount xsi:type=\"cc:BasicAmountType\" currencyID=\"USD\">0.00</ShippingAmount><TaxAmount xsi:type=\"cc:BasicAmountType\" currencyID=\"USD\">0.00</TaxAmount></RegularRecurringPaymentsPeriod><TrialAmountPaid xsi:type=\"cc:BasicAmountType\" currencyID=\"USD\">0.00</TrialAmountPaid><RegularAmountPaid xsi:type=\"cc:BasicAmountType\" currencyID=\"USD\">0.00</RegularAmountPaid><AggregateAmount xsi:type=\"cc:BasicAmountType\" currencyID=\"USD\">0.00</AggregateAmount><AggregateOptionalAmount xsi:type=\"cc:BasicAmountType\" currencyID=\"USD\">0.00</AggregateOptionalAmount><FinalPaymentDueDate xsi:type=\"xs:dateTime\">1970-01-01T00:00:00Z</FinalPaymentDueDate></GetRecurringPaymentsProfileDetailsResponseDetails></GetRecurringPaymentsProfileDetailsResponse></SOAP-ENV:Body></SOAP-ENV:Envelope> RESPONSE end + + def three_d_secure_option + { + three_d_secure: { + trans_status: 'Y', + eci: 'eci', + cavv: 'cavv', + xid: 'xid' + } + } + end end diff --git a/test/unit/gateways/payscout_test.rb b/test/unit/gateways/payscout_test.rb new file mode 100644 index 00000000000..f5cb64b3a3f --- /dev/null +++ b/test/unit/gateways/payscout_test.rb @@ -0,0 +1,537 @@ +require 'test_helper' + +class PayscoutTest < Test::Unit::TestCase + def setup + @gateway = PayscoutGateway.new( + :username => 'xxx', + :password => 'xxx' + ) + + @credit_card = credit_card + @amount = 100 + + @options = { + :order_id => '1', + :billing_address => address, + :description => 'Store Purchase' + } + end + + # Purchase + + def test_approved_puschase + @gateway.expects(:ssl_post).returns(approved_purchase_response) + + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_instance_of Response, response + assert_success response + + assert_equal '1234567891', response.authorization + assert_equal 'The transaction has been approved', response.message + assert response.test? + end + + def test_declined_puschase + @gateway.expects(:ssl_post).returns(declined_purchase_response) + + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_instance_of Response, response + assert_failure response + + assert_equal '1234567892', response.authorization + assert_equal 'The transaction has been declined', response.message + assert response.test? + end + + # Authorization + + def test_approved_authorization + @gateway.expects(:ssl_post).returns(approved_authorization_response) + + assert response = @gateway.authorize(@amount, @credit_card, @options) + assert_instance_of Response, response + assert_success response + + assert_equal '1234567890', response.authorization + assert_equal 'The transaction has been approved', response.message + assert response.test? + end + + def test_declined_authorization + @gateway.expects(:ssl_post).returns(declined_authorization_response) + + assert response = @gateway.authorize(@amount, @credit_card, @options) + assert_instance_of Response, response + assert_failure response + + assert_equal '1234567893', response.authorization + assert_equal 'The transaction has been declined', response.message + assert response.test? + end + + # Capture + + def test_approved_capture + @gateway.expects(:ssl_post).returns(approved_capture_response) + + assert response = @gateway.capture(@amount, '1234567894', @options) + assert_instance_of Response, response + assert_success response + + assert_equal '1234567894', response.authorization + assert_equal 'The transaction has been approved', response.message + assert response.test? + end + + def test_invalid_amount_capture + @gateway.expects(:ssl_post).returns(invalid_amount_capture_response) + + assert response = @gateway.capture(@amount, '1234567895', @options) + assert_instance_of Response, response + assert_failure response + + assert_equal '1234567895', response.authorization + assert_equal 'The+specified+amount+of+2.00+exceeds+the+authorization+amount+of+1.00', response.message + assert response.test? + end + + def test_not_found_transaction_id_capture + @gateway.expects(:ssl_post).returns(not_found_transaction_id_capture_response) + + assert capture = @gateway.capture(@amount, '1234567890') + assert_failure capture + assert_match 'Transaction+not+found', capture.message + end + + def test_invalid_transaction_id_capture + @gateway.expects(:ssl_post).returns(invalid_transaction_id_capture_response) + + assert capture = @gateway.capture(@amount, '') + assert_failure capture + assert_match 'Invalid+Transaction+ID', capture.message + end + + # Refund + + def test_approved_refund + @gateway.expects(:ssl_post).returns(approved_refund_response) + + assert refund = @gateway.refund(@amount, '1234567890') + assert_success refund + assert_equal 'The transaction has been approved', refund.message + end + + def test_not_found_transaction_id_refund + @gateway.expects(:ssl_post).returns(not_found_transaction_id_refund_response) + + assert refund = @gateway.refund(@amount, '1234567890') + assert_failure refund + assert_match 'Transaction+not+found', refund.message + end + + def test_invalid_transaction_id_refund + @gateway.expects(:ssl_post).returns(invalid_transaction_id_refund_response) + + assert refund = @gateway.refund(@amount, '') + assert_failure refund + assert_match 'Invalid+Transaction+ID', refund.message + end + + def test_invalid_amount_refund + @gateway.expects(:ssl_post).returns(invalid_amount_refund_response) + + assert refund = @gateway.refund(200, '1234567890') + assert_failure refund + assert_match 'Refund+amount+may+not+exceed+the+transaction+balance', refund.message + end + + # Void + + def test_approved_void_purchase + @gateway.expects(:ssl_post).returns(approved_void_purchase_response) + + assert void = @gateway.void('1234567890') + assert_success void + assert_equal 'The transaction has been approved', void.message + end + + def test_approved_void_authorization + @gateway.expects(:ssl_post).returns(approved_void_authorization_response) + + assert void = @gateway.void('1234567890') + assert_success void + assert_equal 'The transaction has been approved', void.message + end + + def test_invalid_transaction_id_void + @gateway.expects(:ssl_post).returns(invalid_transaction_id_void_response) + + assert void = @gateway.void('') + assert_failure void + assert_match 'Invalid+Transaction+ID', void.message + end + + def test_not_found_transaction_id_void + @gateway.expects(:ssl_post).returns(not_found_transaction_id_void_response) + + assert void = @gateway.void('1234567890') + assert_failure void + assert_match 'Transaction+not+found', void.message + end + + # Methods + + def test_billing_address + post = {} + address = address(email: 'example@example.com') + @gateway.send(:add_address, post, { billing_address: address }) + + assert_equal address[:address1], post[:address1] + assert_equal address[:address2], post[:address2] + assert_equal address[:city], post[:city] + assert_equal address[:state], post[:state] + assert_equal address[:zip], post[:zip] + assert_equal address[:country], post[:country] + assert_equal address[:phone], post[:phone] + assert_equal address[:fax], post[:fax] + assert_equal address[:email], post[:email] + end + + def test_shipping_address + post = {} + address = address(email: 'example@example.com', first_name: 'John', last_name: 'Doe') + @gateway.send(:add_address, post, { shipping_address: address }) + + assert_equal address[:first_name], post[:shipping_firstname] + assert_equal address[:last_name], post[:shipping_lastname] + assert_equal address[:company], post[:shipping_company] + assert_equal address[:address1], post[:shipping_address1] + assert_equal address[:address2], post[:shipping_address2] + assert_equal address[:city], post[:shipping_city] + assert_equal address[:country], post[:shipping_country] + assert_equal address[:state], post[:shipping_state] + assert_equal address[:zip], post[:shipping_zip] + assert_equal address[:email], post[:shipping_email] + end + + def test_add_currency_from_options + post = {} + @gateway.send(:add_currency, post, 100, { currency: 'CAD' }) + + assert_equal 'CAD', post[:currency] + end + + def test_add_currency_from_money + post = {} + @gateway.send(:add_currency, post, 100, {}) + + assert_equal 'USD', post[:currency] + end + + def test_add_invoice + post = {} + options = {description: 'Order Description', order_id: '123'} + @gateway.send(:add_invoice, post, options) + + assert_equal 'Order Description', post[:orderdescription] + assert_equal '123', post[:orderid] + end + + def test_expdate + @credit_card = credit_card + @credit_card.year = 2015 + @credit_card.month = 8 + + assert_equal '0815', @gateway.send(:expdate, @credit_card) + end + + def test_add_creditcard + post = {} + @gateway.send(:add_creditcard, post, @credit_card) + + assert_equal @credit_card.number, post[:ccnumber] + assert_equal @credit_card.verification_value, post[:cvv] + assert_equal @gateway.send(:expdate, @credit_card), post[:ccexp] + assert_equal @credit_card.first_name, post[:firstname] + assert_equal @credit_card.last_name, post[:lastname] + end + + def test_parse + data = @gateway.send(:parse, approved_authorization_response) + + assert data.key?('response') + assert data.key?('responsetext') + assert data.key?('authcode') + assert data.key?('transactionid') + assert data.key?('avsresponse') + assert data.key?('cvvresponse') + assert data.key?('orderid') + assert data.key?('type') + assert data.key?('response_code') + + assert_equal '1', data['response'] + assert_equal 'SUCCESS', data['responsetext'] + assert_equal '123456', data['authcode'] + assert_equal '1234567890', data['transactionid'] + assert_equal 'N', data['avsresponse'] + assert_equal 'M', data['cvvresponse'] + assert_equal '1', data['orderid'] + assert_equal 'auth', data['type'] + assert_equal '100', data['response_code'] + end + + def test_message_from_for_approved_response + assert_equal 'The transaction has been approved', @gateway.send(:message_from, {'response' => '1'}) + end + + def test_message_from_for_declined_response + assert_equal 'The transaction has been declined', @gateway.send(:message_from, {'response' => '2'}) + end + + def test_message_from_for_failed_response + assert_equal 'Error message', @gateway.send(:message_from, {'response' => '3', 'responsetext' => 'Error message'}) + end + + def test_success + assert @gateway.send(:success?, {'response' => '1'}) + refute @gateway.send(:success?, {'response' => '2'}) + refute @gateway.send(:success?, {'response' => '3'}) + end + + def test_post_data + parameters = {param1: 'value1', param2: 'value2'} + result = @gateway.send(:post_data, 'auth', parameters) + + assert_match 'username=xxx', result + assert_match 'password=xxx', result + assert_match 'type=auth', result + assert_match 'param1=value1', result + assert_match 'param2=value2', result + end + + private + + def approved_authorization_response + %w( + response=1 + responsetext=SUCCESS + authcode=123456 + transactionid=1234567890 + avsresponse=N + cvvresponse=M + orderid=1 + type=auth + response_code=100 + ).join('&') + end + + def declined_authorization_response + %w( + response=2 + responsetext=DECLINE + authcode= + transactionid=1234567893 + avsresponse=N + cvvresponse=M + orderid=1 + type=auth + response_code=200 + ).join('&') + end + + def approved_purchase_response + %w( + response=1 + responsetext=SUCCESS + authcode=123456 + transactionid=1234567891 + avsresponse=N + cvvresponse=M + orderid=1 + type=sale + response_code=100 + ).join('&') + end + + def declined_purchase_response + %w( + response=2 + responsetext=DECLINE + authcode= + transactionid=1234567892 + avsresponse=N + cvvresponse=M + orderid=1 + type=sale + response_code=200 + ).join('&') + end + + def approved_capture_response + %w( + response=1 + responsetext=SUCCESS + authcode=123456 + transactionid=1234567894 + avsresponse=N + cvvresponse=M + orderid=1 + type=capture + response_code=100 + ).join('&') + end + + def invalid_amount_capture_response + %w( + response=3 + responsetext=The+specified+amount+of+2.00+exceeds+the+authorization+amount+of+1.00 + authcode= + transactionid=1234567895 + avsresponse=N + cvvresponse=M + orderid=1 + type=capture + response_code=300 + ).join('&') + end + + def not_found_transaction_id_capture_response + %w( + response=3 + responsetext=Transaction+not+found+REFID:4054576 + authcode= + transactionid= + avsresponse= + cvvresponse= + orderid=1 + type=capture + response_code=300 + ).join('&') + end + + def invalid_transaction_id_capture_response + %w( + response=3 + responsetext=Invalid+Transaction+ID+/+Object+ID+specified:++REFID:4054567 + authcode= + transactionid= + avsresponse= + cvvresponse= + orderid=1 + type=capture + response_code=300 + ).join('&') + end + + def approved_refund_response + %w( + response=1 + responsetext=SUCCESS + authcode= + transactionid=1234567896 + avsresponse= + cvvresponse= + orderid=1 + type=refund + response_code=100 + ).join('&') + end + + def not_found_transaction_id_refund_response + %w( + response=3 + responsetext=Transaction+not+found+REFID:4054576 + authcode= + transactionid= + avsresponse= + cvvresponse= + orderid=1 + type=refund + response_code=300 + ).join('&') + end + + def invalid_transaction_id_refund_response + %w( + response=3 + responsetext=Invalid+Transaction+ID+/+Object+ID+specified:++REFID:4054567 + authcode= + transactionid= + avsresponse= + cvvresponse= + orderid=1 + type=refund + response_code=300 + ).join('&') + end + + def invalid_amount_refund_response + %w( + response=3 + responsetext=Refund+amount+may+not+exceed+the+transaction+balance+REFID:4054562 + authcode= + transactionid= + avsresponse= + cvvresponse= + orderid=1 + type=refund + response_code=300 + ).join('&') + end + + def approved_void_purchase_response + %w( + response=1 + responsetext=Transaction+Void+Successful + authcode=123456 + transactionid=1234567896 + avsresponse= + cvvresponse= + orderid=1 + type=void + response_code=100 + ).join('&') + end + + def approved_void_authorization_response + %w( + response=1 + responsetext=Transaction+Void+Successful + authcode=123456 + transactionid=1234567896 + avsresponse= + cvvresponse= + orderid=1 + type=void + response_code=100 + ).join('&') + end + + def invalid_transaction_id_void_response + %w( + response=3 + responsetext=Invalid+Transaction+ID+/+Object+ID+specified:++REFID:4054572 + authcode= + transactionid= + avsresponse= + cvvresponse= + orderid=1 + type=void + response_code=300 + ).join('&') + end + + def not_found_transaction_id_void_response + %w( + response=3 + responsetext=Transaction+not+found+REFID:4054582 + authcode= + transactionid= + avsresponse= + cvvresponse= + orderid=1 + type=void + response_code=300 + ).join('&') + end +end diff --git a/test/unit/gateways/paystation_test.rb b/test/unit/gateways/paystation_test.rb index c2cd579d1a2..c32716c1383 100644 --- a/test/unit/gateways/paystation_test.rb +++ b/test/unit/gateways/paystation_test.rb @@ -1,8 +1,8 @@ require 'test_helper' class PaystationTest < Test::Unit::TestCase + include CommStub def setup - @gateway = PaystationGateway.new( :paystation_id => 'some_id_number', :gateway_id => 'another_id_number' @@ -10,29 +10,29 @@ def setup @credit_card = credit_card @amount = 100 - - @options = { + + @options = { :order_id => '1', :customer => 'Joe Bloggs, Customer ID #56', :description => 'Store Purchase' } end - + def test_successful_purchase @gateway.expects(:ssl_post).returns(successful_purchase_response) - + assert response = @gateway.purchase(@amount, @credit_card, @options) assert_success response - + assert_equal '0008813023-01', response.authorization - - assert_equal 'Store Purchase', response.params["merchant_reference"] + + assert_equal 'Store Purchase', response.params['merchant_reference'] assert response.test? end def test_unsuccessful_request @gateway.expects(:ssl_post).returns(failed_purchase_response) - + assert response = @gateway.purchase(@amount, @credit_card, @options) assert_failure response assert response.test? @@ -40,292 +40,390 @@ def test_unsuccessful_request def test_successful_store @gateway.expects(:ssl_post).returns(successful_store_response) - - assert response = @gateway.store(@credit_card, @options.merge(:token => "justatest1310263135")) + + assert response = @gateway.store(@credit_card, @options.merge(:token => 'justatest1310263135')) assert_success response assert response.test? - - assert_equal "justatest1310263135", response.token + + assert_equal 'justatest1310263135', response.token end - + def test_successful_purchase_from_token @gateway.expects(:ssl_post).returns(successful_stored_purchase_response) - - token = "u09fxli14afpnd6022x0z82317beqe9e2w048l9it8286k6lpvz9x27hdal9bl95" - + + token = 'u09fxli14afpnd6022x0z82317beqe9e2w048l9it8286k6lpvz9x27hdal9bl95' + assert response = @gateway.purchase(@amount, token, @options) assert_success response - + assert_equal '0009062149-01', response.authorization - - assert_equal 'Store Purchase', response.params["merchant_reference"] + + assert_equal 'Store Purchase', response.params['merchant_reference'] assert response.test? end - + def test_successful_authorization @gateway.expects(:ssl_post).returns(successful_authorization_response) - - assert response = @gateway.authorize(@successful_amount, @credit_card, @options) + + assert response = @gateway.authorize(@amount, @credit_card, @options) assert_success response - + assert response.authorization end - + def test_successful_capture @gateway.expects(:ssl_post).returns(successful_capture_response) - - assert response = @gateway.capture(@successful_amount, "0009062250-01", @options.merge(:credit_card_verification => 123)) + + assert response = @gateway.capture(@amount, '0009062250-01', @options.merge(:credit_card_verification => 123)) + assert_success response + end + + def test_successful_refund + response = stub_comms do + @gateway.purchase(@amount, @credit_card, @options) + end.respond_with(successful_purchase_response) + assert_success response + assert_equal '0008813023-01', response.authorization + assert_equal 'Store Purchase', response.params['merchant_reference'] + + refund = stub_comms do + @gateway.refund(@amount, response.authorization, @options) + end.check_request do |endpoint, data, headers| + assert_match(/0008813023-01/, data) + end.respond_with(successful_refund_response) + + assert_success refund + end + + def test_failed_refund + response = stub_comms do + @gateway.refund(nil, '', @options) + end.respond_with(failed_refund_response) + + assert_failure response + end + + def test_successful_verify + @gateway.expects(:ssl_post).returns(successful_authorization_response) + + assert response = @gateway.verify(@credit_card, @options) + assert_success response + end + + def test_scrub + assert @gateway.supports_scrubbing? + assert_equal @gateway.scrub(pre_scrubbed), post_scrubbed end private - - def successful_purchase_response - %(<?xml version="1.0" standalone="yes"?> - <response> - <ec>0</ec> - <em>Transaction successful</em> - <ti>0006713018-01</ti> - <ct>mastercard</ct> - <merchant_ref>Store Purchase</merchant_ref> - <tm>T</tm> - <MerchantSession>1</MerchantSession> - <UsedAcquirerMerchantID>123456</UsedAcquirerMerchantID> - <TransactionID>0008813023-01</TransactionID> - <PurchaseAmount>10000</PurchaseAmount> - <Locale/> - <ReturnReceiptNumber>8813023</ReturnReceiptNumber> - <ShoppingTransactionNumber/> - <AcqResponseCode>00</AcqResponseCode> - <QSIResponseCode>0</QSIResponseCode> - <CSCResultCode/> - <AVSResultCode/> - <TransactionTime>2011-06-22 00:05:52</TransactionTime> - <PaystationErrorCode>0</PaystationErrorCode> - <PaystationErrorMessage>Transaction successful</PaystationErrorMessage> - <MerchantReference>Store Purchase</MerchantReference> - <TransactionMode>T</TransactionMode> - <BatchNumber>0622</BatchNumber> - <AuthorizeID/> - <Cardtype>MC</Cardtype> - <Username>12345</Username> - <RequestIP>192.168.0.1</RequestIP> - <RequestUserAgent/> - <RequestHttpReferrer/> - <PaymentRequestTime>2011-06-22 00:05:52</PaymentRequestTime> - <DigitalOrderTime/> - <DigitalReceiptTime>2011-06-22 00:05:52</DigitalReceiptTime> - <PaystationTransactionID>0008813023-01</PaystationTransactionID> - <IssuerName>unknown</IssuerName> - <IssuerCountry>unknown</IssuerCountry> - </response>) - end - - def failed_purchase_response - %(<?xml version="1.0" standalone="yes"?> - <response> - <ec>5</ec> - <em>Insufficient Funds</em> - <ti>0006713018-01</ti> - <ct>mastercard</ct> - <merchant_ref>Store Purchase</merchant_ref> - <tm>T</tm> - <MerchantSession>1</MerchantSession> - <UsedAcquirerMerchantID>123456</UsedAcquirerMerchantID> - <TransactionID>0008813018-01</TransactionID> - <PurchaseAmount>10051</PurchaseAmount> - <Locale/> - <ReturnReceiptNumber>8813018</ReturnReceiptNumber> - <ShoppingTransactionNumber/> - <AcqResponseCode>51</AcqResponseCode> - <QSIResponseCode>5</QSIResponseCode> - <CSCResultCode/> - <AVSResultCode/> - <TransactionTime>2011-06-22 00:05:46</TransactionTime> - <PaystationErrorCode>5</PaystationErrorCode> - <PaystationErrorMessage>Insufficient Funds</PaystationErrorMessage> - <MerchantReference>Store Purchase</MerchantReference> - <TransactionMode>T</TransactionMode> - <BatchNumber>0622</BatchNumber> - <AuthorizeID/> - <Cardtype>MC</Cardtype> - <Username>123456</Username> - <RequestIP>192.168.0.1</RequestIP> - <RequestUserAgent/> - <RequestHttpReferrer/> - <PaymentRequestTime>2011-06-22 00:05:46</PaymentRequestTime> - <DigitalOrderTime/> - <DigitalReceiptTime>2011-06-22 00:05:46</DigitalReceiptTime> - <PaystationTransactionID>0008813018-01</PaystationTransactionID> - <IssuerName>unknown</IssuerName> - <IssuerCountry>unknown</IssuerCountry> - </response>) - end - - def successful_store_response - %(<?xml version="1.0" standalone="yes"?> - <PaystationFuturePaymentResponse> - <ec>34</ec> - <em>Future Payment Saved Ok</em> - <ti/> - <ct/> - <merchant_ref>Store Purchase</merchant_ref> - <tm>T</tm> - <MerchantSession>3e48fa9a6b0fe36177adf7269db7a3c4</MerchantSession> - <UsedAcquirerMerchantID/> - <TransactionID/> - <PurchaseAmount>0</PurchaseAmount> - <Locale/> - <ReturnReceiptNumber/> - <ShoppingTransactionNumber/> - <AcqResponseCode/> - <QSIResponseCode/> - <CSCResultCode/> - <AVSResultCode/> - <TransactionTime>2011-07-10 13:58:55</TransactionTime> - <PaystationErrorCode>34</PaystationErrorCode> - <PaystationErrorMessage>Future Payment Saved Ok</PaystationErrorMessage> - <MerchantReference>Store Purchase</MerchantReference> - <TransactionMode>T</TransactionMode> - <BatchNumber/> - <AuthorizeID/> - <Cardtype/> - <Username>123456</Username> - <RequestIP>192.168.0.1</RequestIP> - <RequestUserAgent/> - <RequestHttpReferrer/> - <PaymentRequestTime>2011-07-10 13:58:55</PaymentRequestTime> - <DigitalOrderTime/> - <DigitalReceiptTime>2011-07-10 13:58:55</DigitalReceiptTime> - <PaystationTransactionID>0009062177-01</PaystationTransactionID> - <FuturePaymentToken>justatest1310263135</FuturePaymentToken> - <IssuerName>unknown</IssuerName> - <IssuerCountry>unknown</IssuerCountry> - </PaystationFuturePaymentResponse>) - end - - def successful_stored_purchase_response - %(<?xml version="1.0" standalone="yes"?> - <PaystationFuturePaymentResponse> - <ec>0</ec> - <em>Transaction successful</em> - <ti>0006713018-01</ti> - <ct>visa</ct> - <merchant_ref>Store Purchase</merchant_ref> - <tm>T</tm> - <MerchantSession>0fc70a577f19ae63f651f53c7044640a</MerchantSession> - <UsedAcquirerMerchantID>123456</UsedAcquirerMerchantID> - <TransactionID>0009062149-01</TransactionID> - <PurchaseAmount>10000</PurchaseAmount> - <Locale/> - <ReturnReceiptNumber>9062149</ReturnReceiptNumber> - <ShoppingTransactionNumber/> - <AcqResponseCode>00</AcqResponseCode> - <QSIResponseCode>0</QSIResponseCode> - <CSCResultCode/> - <AVSResultCode/> - <TransactionTime>2011-07-10 13:55:00</TransactionTime> - <PaystationErrorCode>0</PaystationErrorCode> - <PaystationErrorMessage>Transaction successful</PaystationErrorMessage> - <MerchantReference>Store Purchase</MerchantReference> - <TransactionMode>T</TransactionMode> - <BatchNumber>0710</BatchNumber> - <AuthorizeID/> - <Cardtype>VC</Cardtype> - <Username>123456</Username> - <RequestIP>192.168.0.1</RequestIP> - <RequestUserAgent/> - <RequestHttpReferrer/> - <PaymentRequestTime>2011-07-10 13:55:00</PaymentRequestTime> - <DigitalOrderTime/> - <DigitalReceiptTime>2011-07-10 13:55:00</DigitalReceiptTime> - <PaystationTransactionID>0009062149-01</PaystationTransactionID> - <FuturePaymentToken>u09fxli14afpnd6022x0z82317beqe9e2w048l9it8286k6lpvz9x27hdal9bl95</FuturePaymentToken> - <IssuerName>unknown</IssuerName> - <IssuerCountry>unknown</IssuerCountry> - </PaystationFuturePaymentResponse>) - end - - def successful_authorization_response - %(<?xml version="1.0" standalone="yes"?> - <response> - <ec>0</ec> - <em>Transaction successful</em> - <ti>0009062250-01</ti> - <ct>visa</ct> - <merchant_ref>Store Purchase</merchant_ref> - <tm>T</tm> - <MerchantSession>b2168af96076522466af4e3d61e5ba0c</MerchantSession> - <UsedAcquirerMerchantID>123456</UsedAcquirerMerchantID> - <TransactionID>0009062250-01</TransactionID> - <PurchaseAmount>10000</PurchaseAmount> - <Locale/> - <ReturnReceiptNumber>9062250</ReturnReceiptNumber> - <ShoppingTransactionNumber/> - <AcqResponseCode>00</AcqResponseCode> - <QSIResponseCode>0</QSIResponseCode> - <CSCResultCode/> - <AVSResultCode/> - <TransactionTime>2011-07-10 14:11:00</TransactionTime> - <PaystationErrorCode>0</PaystationErrorCode> - <PaystationErrorMessage>Transaction successful</PaystationErrorMessage> - <MerchantReference>Store Purchase</MerchantReference> - <TransactionMode>T</TransactionMode> - <BatchNumber>0710</BatchNumber> - <AuthorizeID/> - <Cardtype>VC</Cardtype> - <Username>123456</Username> - <RequestIP>192.168.0.1</RequestIP> - <RequestUserAgent/> - <RequestHttpReferrer/> - <PaymentRequestTime>2011-07-10 14:11:00</PaymentRequestTime> - <DigitalOrderTime/> - <DigitalReceiptTime>2011-07-10 14:11:00</DigitalReceiptTime> - <PaystationTransactionID>0009062250-01</PaystationTransactionID> - <IssuerName>unknown</IssuerName> - <IssuerCountry>unknown</IssuerCountry> - </response>) - end - - def successful_capture_response - %(<?xml version="1.0" standalone="yes"?> - <PaystationCaptureResponse> - <ec>0</ec> - <em>Transaction successful</em> - <ti>0009062289-01</ti> - <ct/> - <merchant_ref>Store Purchase</merchant_ref> - <tm>T</tm> - <MerchantSession>485fdedc81dc83848dd799cd10a869db</MerchantSession> - <UsedAcquirerMerchantID>123456</UsedAcquirerMerchantID> - <TransactionID>0009062289-01</TransactionID> - <CaptureAmount>10000</CaptureAmount> - <Locale/> - <ReturnReceiptNumber>9062289</ReturnReceiptNumber> - <ShoppingTransactionNumber/> - <AcqResponseCode>00</AcqResponseCode> - <QSIResponseCode>0</QSIResponseCode> - <CSCResultCode/> - <AVSResultCode/> - <TransactionTime>2011-07-10 14:17:36</TransactionTime> - <PaystationErrorCode>0</PaystationErrorCode> - <PaystationErrorMessage>Transaction successful</PaystationErrorMessage> - <MerchantReference>Store Purchase</MerchantReference> - <TransactionMode>T</TransactionMode> - <BatchNumber>0710</BatchNumber> - <AuthorizeID/> - <Cardtype/> - <Username>123456</Username> - <RequestIP>192.168.0.1</RequestIP> - <RequestUserAgent/> - <RequestHttpReferrer/> - <PaymentRequestTime>2011-07-10 14:17:36</PaymentRequestTime> - <DigitalOrderTime>2011-07-10 14:17:36</DigitalOrderTime> - <DigitalReceiptTime>2011-07-10 14:17:36</DigitalReceiptTime> - <PaystationTransactionID/> - <RefundedAmount/> - <CapturedAmount>10000</CapturedAmount> - <AuthorisedAmount/> - </PaystationCaptureResponse>) - end + + def successful_purchase_response + %(<?xml version="1.0" standalone="yes"?> + <response> + <ec>0</ec> + <em>Transaction successful</em> + <ti>0006713018-01</ti> + <ct>mastercard</ct> + <merchant_ref>Store Purchase</merchant_ref> + <tm>T</tm> + <MerchantSession>1</MerchantSession> + <UsedAcquirerMerchantID>123456</UsedAcquirerMerchantID> + <TransactionID>0008813023-01</TransactionID> + <PurchaseAmount>10000</PurchaseAmount> + <Locale/> + <ReturnReceiptNumber>8813023</ReturnReceiptNumber> + <ShoppingTransactionNumber/> + <AcqResponseCode>00</AcqResponseCode> + <QSIResponseCode>0</QSIResponseCode> + <CSCResultCode/> + <AVSResultCode/> + <TransactionTime>2011-06-22 00:05:52</TransactionTime> + <PaystationErrorCode>0</PaystationErrorCode> + <PaystationErrorMessage>Transaction successful</PaystationErrorMessage> + <MerchantReference>Store Purchase</MerchantReference> + <TransactionMode>T</TransactionMode> + <BatchNumber>0622</BatchNumber> + <AuthorizeID/> + <Cardtype>MC</Cardtype> + <Username>12345</Username> + <RequestIP>192.168.0.1</RequestIP> + <RequestUserAgent/> + <RequestHttpReferrer/> + <PaymentRequestTime>2011-06-22 00:05:52</PaymentRequestTime> + <DigitalOrderTime/> + <DigitalReceiptTime>2011-06-22 00:05:52</DigitalReceiptTime> + <PaystationTransactionID>0008813023-01</PaystationTransactionID> + <IssuerName>unknown</IssuerName> + <IssuerCountry>unknown</IssuerCountry> + </response>) + end + + def failed_purchase_response + %(<?xml version="1.0" standalone="yes"?> + <response> + <ec>5</ec> + <em>Insufficient Funds</em> + <ti>0006713018-01</ti> + <ct>mastercard</ct> + <merchant_ref>Store Purchase</merchant_ref> + <tm>T</tm> + <MerchantSession>1</MerchantSession> + <UsedAcquirerMerchantID>123456</UsedAcquirerMerchantID> + <TransactionID>0008813018-01</TransactionID> + <PurchaseAmount>10051</PurchaseAmount> + <Locale/> + <ReturnReceiptNumber>8813018</ReturnReceiptNumber> + <ShoppingTransactionNumber/> + <AcqResponseCode>51</AcqResponseCode> + <QSIResponseCode>5</QSIResponseCode> + <CSCResultCode/> + <AVSResultCode/> + <TransactionTime>2011-06-22 00:05:46</TransactionTime> + <PaystationErrorCode>5</PaystationErrorCode> + <PaystationErrorMessage>Insufficient Funds</PaystationErrorMessage> + <MerchantReference>Store Purchase</MerchantReference> + <TransactionMode>T</TransactionMode> + <BatchNumber>0622</BatchNumber> + <AuthorizeID/> + <Cardtype>MC</Cardtype> + <Username>123456</Username> + <RequestIP>192.168.0.1</RequestIP> + <RequestUserAgent/> + <RequestHttpReferrer/> + <PaymentRequestTime>2011-06-22 00:05:46</PaymentRequestTime> + <DigitalOrderTime/> + <DigitalReceiptTime>2011-06-22 00:05:46</DigitalReceiptTime> + <PaystationTransactionID>0008813018-01</PaystationTransactionID> + <IssuerName>unknown</IssuerName> + <IssuerCountry>unknown</IssuerCountry> + </response>) + end + + def successful_store_response + %(<?xml version="1.0" standalone="yes"?> + <PaystationFuturePaymentResponse> + <ec>34</ec> + <em>Future Payment Saved Ok</em> + <ti/> + <ct/> + <merchant_ref>Store Purchase</merchant_ref> + <tm>T</tm> + <MerchantSession>3e48fa9a6b0fe36177adf7269db7a3c4</MerchantSession> + <UsedAcquirerMerchantID/> + <TransactionID/> + <PurchaseAmount>0</PurchaseAmount> + <Locale/> + <ReturnReceiptNumber/> + <ShoppingTransactionNumber/> + <AcqResponseCode/> + <QSIResponseCode/> + <CSCResultCode/> + <AVSResultCode/> + <TransactionTime>2011-07-10 13:58:55</TransactionTime> + <PaystationErrorCode>34</PaystationErrorCode> + <PaystationErrorMessage>Future Payment Saved Ok</PaystationErrorMessage> + <MerchantReference>Store Purchase</MerchantReference> + <TransactionMode>T</TransactionMode> + <BatchNumber/> + <AuthorizeID/> + <Cardtype/> + <Username>123456</Username> + <RequestIP>192.168.0.1</RequestIP> + <RequestUserAgent/> + <RequestHttpReferrer/> + <PaymentRequestTime>2011-07-10 13:58:55</PaymentRequestTime> + <DigitalOrderTime/> + <DigitalReceiptTime>2011-07-10 13:58:55</DigitalReceiptTime> + <PaystationTransactionID>0009062177-01</PaystationTransactionID> + <FuturePaymentToken>justatest1310263135</FuturePaymentToken> + <IssuerName>unknown</IssuerName> + <IssuerCountry>unknown</IssuerCountry> + </PaystationFuturePaymentResponse>) + end + + def successful_stored_purchase_response + %(<?xml version="1.0" standalone="yes"?> + <PaystationFuturePaymentResponse> + <ec>0</ec> + <em>Transaction successful</em> + <ti>0006713018-01</ti> + <ct>visa</ct> + <merchant_ref>Store Purchase</merchant_ref> + <tm>T</tm> + <MerchantSession>0fc70a577f19ae63f651f53c7044640a</MerchantSession> + <UsedAcquirerMerchantID>123456</UsedAcquirerMerchantID> + <TransactionID>0009062149-01</TransactionID> + <PurchaseAmount>10000</PurchaseAmount> + <Locale/> + <ReturnReceiptNumber>9062149</ReturnReceiptNumber> + <ShoppingTransactionNumber/> + <AcqResponseCode>00</AcqResponseCode> + <QSIResponseCode>0</QSIResponseCode> + <CSCResultCode/> + <AVSResultCode/> + <TransactionTime>2011-07-10 13:55:00</TransactionTime> + <PaystationErrorCode>0</PaystationErrorCode> + <PaystationErrorMessage>Transaction successful</PaystationErrorMessage> + <MerchantReference>Store Purchase</MerchantReference> + <TransactionMode>T</TransactionMode> + <BatchNumber>0710</BatchNumber> + <AuthorizeID/> + <Cardtype>VC</Cardtype> + <Username>123456</Username> + <RequestIP>192.168.0.1</RequestIP> + <RequestUserAgent/> + <RequestHttpReferrer/> + <PaymentRequestTime>2011-07-10 13:55:00</PaymentRequestTime> + <DigitalOrderTime/> + <DigitalReceiptTime>2011-07-10 13:55:00</DigitalReceiptTime> + <PaystationTransactionID>0009062149-01</PaystationTransactionID> + <FuturePaymentToken>u09fxli14afpnd6022x0z82317beqe9e2w048l9it8286k6lpvz9x27hdal9bl95</FuturePaymentToken> + <IssuerName>unknown</IssuerName> + <IssuerCountry>unknown</IssuerCountry> + </PaystationFuturePaymentResponse>) + end + + def successful_authorization_response + %(<?xml version="1.0" standalone="yes"?> + <response> + <ec>0</ec> + <em>Transaction successful</em> + <ti>0009062250-01</ti> + <ct>visa</ct> + <merchant_ref>Store Purchase</merchant_ref> + <tm>T</tm> + <MerchantSession>b2168af96076522466af4e3d61e5ba0c</MerchantSession> + <UsedAcquirerMerchantID>123456</UsedAcquirerMerchantID> + <TransactionID>0009062250-01</TransactionID> + <PurchaseAmount>10000</PurchaseAmount> + <Locale/> + <ReturnReceiptNumber>9062250</ReturnReceiptNumber> + <ShoppingTransactionNumber/> + <AcqResponseCode>00</AcqResponseCode> + <QSIResponseCode>0</QSIResponseCode> + <CSCResultCode/> + <AVSResultCode/> + <TransactionTime>2011-07-10 14:11:00</TransactionTime> + <PaystationErrorCode>0</PaystationErrorCode> + <PaystationErrorMessage>Transaction successful</PaystationErrorMessage> + <MerchantReference>Store Purchase</MerchantReference> + <TransactionMode>T</TransactionMode> + <BatchNumber>0710</BatchNumber> + <AuthorizeID/> + <Cardtype>VC</Cardtype> + <Username>123456</Username> + <RequestIP>192.168.0.1</RequestIP> + <RequestUserAgent/> + <RequestHttpReferrer/> + <PaymentRequestTime>2011-07-10 14:11:00</PaymentRequestTime> + <DigitalOrderTime/> + <DigitalReceiptTime>2011-07-10 14:11:00</DigitalReceiptTime> + <PaystationTransactionID>0009062250-01</PaystationTransactionID> + <IssuerName>unknown</IssuerName> + <IssuerCountry>unknown</IssuerCountry> + </response>) + end + + def successful_capture_response + %(<?xml version="1.0" standalone="yes"?> + <PaystationCaptureResponse> + <ec>0</ec> + <em>Transaction successful</em> + <ti>0009062289-01</ti> + <ct/> + <merchant_ref>Store Purchase</merchant_ref> + <tm>T</tm> + <MerchantSession>485fdedc81dc83848dd799cd10a869db</MerchantSession> + <UsedAcquirerMerchantID>123456</UsedAcquirerMerchantID> + <TransactionID>0009062289-01</TransactionID> + <CaptureAmount>10000</CaptureAmount> + <Locale/> + <ReturnReceiptNumber>9062289</ReturnReceiptNumber> + <ShoppingTransactionNumber/> + <AcqResponseCode>00</AcqResponseCode> + <QSIResponseCode>0</QSIResponseCode> + <CSCResultCode/> + <AVSResultCode/> + <TransactionTime>2011-07-10 14:17:36</TransactionTime> + <PaystationErrorCode>0</PaystationErrorCode> + <PaystationErrorMessage>Transaction successful</PaystationErrorMessage> + <MerchantReference>Store Purchase</MerchantReference> + <TransactionMode>T</TransactionMode> + <BatchNumber>0710</BatchNumber> + <AuthorizeID/> + <Cardtype/> + <Username>123456</Username> + <RequestIP>192.168.0.1</RequestIP> + <RequestUserAgent/> + <RequestHttpReferrer/> + <PaymentRequestTime>2011-07-10 14:17:36</PaymentRequestTime> + <DigitalOrderTime>2011-07-10 14:17:36</DigitalOrderTime> + <DigitalReceiptTime>2011-07-10 14:17:36</DigitalReceiptTime> + <PaystationTransactionID/> + <RefundedAmount/> + <CapturedAmount>10000</CapturedAmount> + <AuthorisedAmount/> + </PaystationCaptureResponse>) + end + + def successful_refund_response + %(<?xml version="1.0" standalone="yes"?> + <PaystationRefundResponse> + <ec>0</ec> + <em>Transaction successful</em> + <ti>0008813023-01</ti> + <ct>mastercard</ct> + <merchant_ref>Store Purchase</merchant_ref> + <tm>T</tm> + <MerchantSession>70ceae1b3f069e41ca7f4350a1180cb1</MerchantSession> + <UsedAcquirerMerchantID>924518</UsedAcquirerMerchantID> + <TransactionID>0008813023-01</TransactionID> + <RefundAmount>10000</RefundAmount> + <SurchargeAmount/> + <Locale>en</Locale> + <ReturnReceiptNumber>58160420</ReturnReceiptNumber> + <ShoppingTransactionNumber/> + <AcqResponseCode>00</AcqResponseCode> + <QSIResponseCode>0</QSIResponseCode> + <CSCResultCode/> + <AVSResultCode/> + <TransactionTime>2015-06-25 03:23:24</TransactionTime> + <PaystationErrorCode>0</PaystationErrorCode> + <PaystationErrorMessage>Transaction successful</PaystationErrorMessage> + <PaystationExtendedErrorMessage/> + <MerchantReference>Store Purchase</MerchantReference> + <CardNo>512345XXXXXXX346</CardNo> + <CardExpiry>1305</CardExpiry> + <TransactionProcess>refund</TransactionProcess> + <TransactionMode>T</TransactionMode> + <BatchNumber>0625</BatchNumber> + <AuthorizeID/> + <Cardtype>MC</Cardtype> + <Username>609035</Username> + <RequestIP>173.95.131.239</RequestIP> + <RequestUserAgent>Ruby</RequestUserAgent> + <RequestHttpReferrer/> + <PaymentRequestTime>2015-06-25 03:23:24</PaymentRequestTime> + <DigitalOrderTime>2015-06-25 03:23:24</DigitalOrderTime> + <DigitalReceiptTime/> + <PaystationTransactionID/> + <RefundedAmount>10000</RefundedAmount> + <CapturedAmount/> + </PaystationRefundResponse>) + end + + def failed_refund_response + %(<?xml version="1.0" standalone="yes"?> + <FONT FACE="Arial" SIZE="2"><strong>Error 11:</strong> Not enough input parameters.</FONT>) + end + + def pre_scrubbed + 'pstn_pi=609035&pstn_gi=PUSHPAY&pstn_2p=t&pstn_nr=t&pstn_df=yymm&pstn_ms=a755b9c84a530aee91dc3077f57294b0&pstn_mo=Store+Purchase&pstn_mr=&pstn_am=&pstn_cu=NZD&pstn_cn=5123456789012346&pstn_ct=visa&pstn_ex=1305&pstn_cc=123&pstn_tm=T&paystation=_empty' + end + + def post_scrubbed + 'pstn_pi=609035&pstn_gi=PUSHPAY&pstn_2p=t&pstn_nr=t&pstn_df=yymm&pstn_ms=a755b9c84a530aee91dc3077f57294b0&pstn_mo=Store+Purchase&pstn_mr=&pstn_am=&pstn_cu=NZD&pstn_cn=[FILTERED]&pstn_ct=visa&pstn_ex=1305&pstn_cc=[FILTERED]&pstn_tm=T&paystation=_empty' + end + end diff --git a/test/unit/gateways/payu_in_test.rb b/test/unit/gateways/payu_in_test.rb new file mode 100644 index 00000000000..876bae34162 --- /dev/null +++ b/test/unit/gateways/payu_in_test.rb @@ -0,0 +1,529 @@ +require 'test_helper' + +class PayuInTest < Test::Unit::TestCase + include CommStub + + def setup + @gateway = PayuInGateway.new( + key: 'key', + salt: 'salt' + ) + + @credit_card = credit_card + + @options = { + order_id: '1' + } + end + + def assert_parameter(parameter, expected_value, data, options={}) + assert (data =~ %r{(?:^|&)#{parameter}=([^&]*)(?:&|$)}), "Unable to find #{parameter} in #{data}" + value = CGI.unescape($1 || '') + case expected_value + when Regexp + assert_match expected_value, value, "#{parameter} value does not match expected" + else + assert_equal expected_value.to_s, value, "#{parameter} value does not match expected" + end + if options[:length] + assert_equal options[:length], value.length, "#{parameter} value of #{value} is the wrong length" + end + end + + def test_successful_purchase + response = stub_comms do + @gateway.purchase(100, @credit_card, @options) + end.check_request do |endpoint, data, headers| + assert_equal 'identity', headers['Accept-Encoding'] + case endpoint + when /_payment/ + assert_parameter('amount', '1.00', data) + assert_parameter('txnid', '1', data) + assert_parameter('productinfo', 'Purchase', data) + assert_parameter('surl', 'http://example.com', data) + assert_parameter('furl', 'http://example.com', data) + assert_parameter('pg', 'CC', data) + assert_parameter('firstname', @credit_card.first_name, data) + assert_parameter('bankcode', @credit_card.brand.upcase, data) + assert_parameter('ccnum', @credit_card.number, data) + assert_parameter('ccvv', @credit_card.verification_value, data) + assert_parameter('ccname', @credit_card.name, data) + assert_parameter('ccexpmon', '%02d' % @credit_card.month.to_i, data) + assert_parameter('ccexpyr', @credit_card.year, data) + assert_parameter('email', 'unknown@example.com', data) + assert_parameter('phone', '11111111111', data) + assert_parameter('key', 'key', data) + assert_parameter('txn_s2s_flow', '1', data) + assert_parameter('hash', '5199c0735c21d647f287a2781024743d35fabfd640bc20f2ae7b5277e3d7d06fa315fcdda266cfa64920517944244c632e5f38768481626b22e2b0d70c806d60', data) + when /hdfc_not_enrolled/ + assert_parameter('transactionId', '6e7e62723683934e6c5507675df11bdd86197c5c935878ff72e344205f3c8a1d', data) + assert_parameter('pgId', '8', data) + assert_parameter('eci', '7', data) + assert_parameter('nonEnrolled', '1', data) + assert_parameter('nonDomestic', '0', data) + assert_parameter('bank', 'VISA', data) + assert_parameter('cccat', 'creditcard', data) + assert_parameter('ccnum', '4b5c9002295c6cd8e5289e2f9c312dc737810a747b84e71665cf077c78fe245a', data) + assert_parameter('ccname', '53ab689fdb1b025c7e9c53c6b4a6e27f51e0d627579e7c12af2cb6cbc4944cc0', data) + assert_parameter('ccvv', 'cc8d6cfb6b03f94e2a64b490ae10c261c10747f543b1fba09d7f56f9ef6aac04', data) + assert_parameter('ccexpmon', '5ddf3702e74f473ec89762f6efece025737c2ab999e695cf10496e6fa3946079', data) + assert_parameter('ccexpyr', '5da83563fcaa945063dc4c2094c48e800badf7c8246c9d13b43757fe99d63e6d', data) + assert_parameter('is_seamless', '1', data) + else + flunk "Unknown endpoint #{endpoint}" + end + end.respond_with(successful_purchase_setup_response, successful_purchase_response) + + assert_success response + + assert_equal '403993715512145540', response.authorization + assert_equal 'No Error', response.message + assert response.test? + end + + def test_successful_purchase_with_full_options + response = stub_comms do + @gateway.purchase( + 100, + credit_card('4242424242424242', name: 'Bobby Jimbob', verification_value: '678', month: '4', year: '2015'), + order_id: '99', + description: 'Awesome!', + email: 'jim@example.com', + billing_address: { + name: 'Jim Smith', + address1: '123 Road', + address2: 'Suite 123', + city: 'Somewhere', + state: 'ZZ', + country: 'US', + zip: '12345', + phone: '12223334444' + }, + shipping_address: { + name: 'Joe Bob', + address1: '987 Street', + address2: 'Suite 987', + city: 'Anyplace', + state: 'AA', + country: 'IN', + zip: '98765', + phone: '98887776666' + } + ) + end.check_request do |endpoint, data, headers| + assert_equal 'identity', headers['Accept-Encoding'] + case endpoint + when /_payment/ + assert_parameter('amount', '1.00', data) + assert_parameter('txnid', '99', data) + assert_parameter('productinfo', 'Awesome!', data) + assert_parameter('surl', 'http://example.com', data) + assert_parameter('furl', 'http://example.com', data) + assert_parameter('pg', 'CC', data) + assert_parameter('firstname', 'Bobby', data) + assert_parameter('lastname', 'Jimbob', data) + assert_parameter('bankcode', 'VISA', data) + assert_parameter('ccnum', '4242424242424242', data) + assert_parameter('ccvv', '678', data) + assert_parameter('ccname', 'Bobby Jimbob', data) + assert_parameter('ccexpmon', '04', data) + assert_parameter('ccexpyr', '2015', data) + assert_parameter('email', 'jim@example.com', data) + assert_parameter('phone', '12223334444', data) + assert_parameter('key', 'key', data) + assert_parameter('txn_s2s_flow', '1', data) + assert_parameter('hash', '1ee17ee9615b55fdee4cd92cee4f28bd88e0c7ff16bd7525cb7b0a792728502f71ffba37606b1b77504d1d0b9d520d39cb1829fffd1aa5eef27dfa4c4a887f61', data) + assert_parameter('address1', '123 Road', data) + assert_parameter('address2', 'Suite 123', data) + assert_parameter('city', 'Somewhere', data) + assert_parameter('state', 'ZZ', data) + assert_parameter('country', 'US', data) + assert_parameter('zipcode', '12345', data) + assert_parameter('shipping_firstname', 'Joe', data) + assert_parameter('shipping_lastname', 'Bob', data) + assert_parameter('shipping_address1', '987 Street', data) + assert_parameter('shipping_address2', 'Suite 987', data) + assert_parameter('shipping_city', 'Anyplace', data) + assert_parameter('shipping_state', 'AA', data) + assert_parameter('shipping_country', 'IN', data) + assert_parameter('shipping_zipcode', '98765', data) + assert_parameter('shipping_phone', '98887776666', data) + when /hdfc_not_enrolled/ + assert_parameter('transactionId', '6e7e62723683934e6c5507675df11bdd86197c5c935878ff72e344205f3c8a1d', data) + assert_parameter('pgId', '8', data) + assert_parameter('eci', '7', data) + assert_parameter('nonEnrolled', '1', data) + assert_parameter('nonDomestic', '0', data) + assert_parameter('bank', 'VISA', data) + assert_parameter('cccat', 'creditcard', data) + assert_parameter('ccnum', '4b5c9002295c6cd8e5289e2f9c312dc737810a747b84e71665cf077c78fe245a', data) + assert_parameter('ccname', '53ab689fdb1b025c7e9c53c6b4a6e27f51e0d627579e7c12af2cb6cbc4944cc0', data) + assert_parameter('ccvv', 'cc8d6cfb6b03f94e2a64b490ae10c261c10747f543b1fba09d7f56f9ef6aac04', data) + assert_parameter('ccexpmon', '5ddf3702e74f473ec89762f6efece025737c2ab999e695cf10496e6fa3946079', data) + assert_parameter('ccexpyr', '5da83563fcaa945063dc4c2094c48e800badf7c8246c9d13b43757fe99d63e6d', data) + assert_parameter('is_seamless', '1', data) + else + flunk "Unknown endpoint #{endpoint}" + end + end.respond_with(successful_purchase_setup_response, successful_purchase_response) + + assert_success response + end + + def test_input_constraint_cleanup + response = stub_comms do + @gateway.purchase( + 100, + credit_card( + '4242424242424242', + first_name: ('3' + ('a' * 61)), + last_name: ('3' + ('a' * 21)), + month: '4', + year: '2015' + ), + order_id: ('!@#' + ('a' * 31)), + description: ('a' * 101), + email: ('c' * 51), + billing_address: { + name: 'Jim Smith', + address1: ("!#$%^&'\"()" + 'Aa0@-_/ .' + ('a' * 101)), + address2: ("!#$%^&'\"()" + 'Aa0@-_/ .' + ('a' * 101)), + city: ("!#$%^&'\"()" + 'Aa0@-_/ .' + ('a' * 51)), + state: ("!#$%^&'\"()" + 'Aa0@-_/ .' + ('a' * 51)), + country: ("!#$%^&'\"()" + 'Aa0@-_/ .' + ('a' * 51)), + zip: ('a-' + ('1' * 21)), + phone: ('a-' + ('1' * 51)) + }, + shipping_address: { + name: (('3' + ('a' * 61)) + ' ' + ('3' + ('a' * 21))), + address1: ("!#$%^&'\"()" + 'Aa0@-_/ .' + ('a' * 101)), + address2: ("!#$%^&'\"()" + 'Aa0@-_/ .' + ('a' * 101)), + city: ("!#$%^&'\"()" + 'Aa0@-_/ .' + ('a' * 51)), + state: ("!#$%^&'\"()" + 'Aa0@-_/ .' + ('a' * 51)), + country: ("!#$%^&'\"()" + 'Aa0@-_/ .' + ('a' * 51)), + zip: ('a-' + ('1' * 21)), + phone: ('a-' + ('1' * 51)) + } + ) + end.check_request do |endpoint, data, headers| + case endpoint + when /_payment/ + assert_parameter('txnid', /^a/, data, length: 30) + assert_parameter('productinfo', /^a/, data, length: 100) + assert_parameter('firstname', /^a/, data, length: 60) + assert_parameter('lastname', /^a/, data, length: 20) + assert_parameter('email', /^c/, data, length: 50) + assert_parameter('phone', /^\d/, data, length: 50) + assert_parameter('address1', /^Aa0@-_\/ \.a/, data, length: 100) + assert_parameter('address2', /^Aa0@-_\/ \.a/, data, length: 100) + assert_parameter('city', /^Aa0@-_\/ \.a/, data, length: 50) + assert_parameter('state', /^Aa0@-_\/ \.a/, data, length: 50) + assert_parameter('country', /^Aa0@-_\/ \.a/, data, length: 50) + assert_parameter('zipcode', /^1/, data, length: 20) + assert_parameter('shipping_firstname', /^a/, data, length: 60) + assert_parameter('shipping_lastname', /^a/, data, length: 20) + assert_parameter('shipping_address1', /^Aa0@-_\/ \.a/, data, length: 100) + assert_parameter('shipping_address2', /^Aa0@-_\/ \.a/, data, length: 100) + assert_parameter('shipping_city', /^Aa0@-_\/ \.a/, data, length: 50) + assert_parameter('shipping_state', /^Aa0@-_\/ \.a/, data, length: 50) + assert_parameter('shipping_country', /^Aa0@-_\/ \.a/, data, length: 50) + assert_parameter('shipping_zipcode', /^1/, data, length: 20) + assert_parameter('shipping_phone', /^\d/, data, length: 50) + end + end.respond_with(successful_purchase_setup_response, successful_purchase_response) + + assert_success response + end + + def test_brand_mappings + stub_comms do + @gateway.purchase(100, credit_card('4242424242424242', brand: :visa), @options) + end.check_request do |endpoint, data, _| + case endpoint + when /_payment/ + assert_parameter('bankcode', 'VISA', data) + end + end.respond_with(successful_purchase_response) + + stub_comms do + @gateway.purchase(100, credit_card('4242424242424242', brand: :master), @options) + end.check_request do |endpoint, data, _| + case endpoint + when /_payment/ + assert_parameter('bankcode', 'MAST', data) + end + end.respond_with(successful_purchase_response) + + stub_comms do + @gateway.purchase(100, credit_card('4242424242424242', brand: :american_express), @options) + end.check_request do |endpoint, data, _| + case endpoint + when /_payment/ + assert_parameter('bankcode', 'AMEX', data) + end + end.respond_with(successful_purchase_response) + + stub_comms do + @gateway.purchase(100, credit_card('4242424242424242', brand: :diners_club), @options) + end.check_request do |endpoint, data, _| + case endpoint + when /_payment/ + assert_parameter('bankcode', 'DINR', data) + end + end.respond_with(successful_purchase_response) + + stub_comms do + @gateway.purchase(100, credit_card('4242424242424242', brand: :maestro), @options) + end.check_request do |endpoint, data, _| + case endpoint + when /_payment/ + assert_parameter('bankcode', 'MAES', data) + end + end.respond_with(successful_purchase_response) + end + + def test_failed_purchase + @gateway.expects(:ssl_post).returns(failed_purchase_response) + + response = @gateway.purchase(100, @credit_card, @options) + assert_failure response + assert_equal 'Invalid amount @~@ ExceptionConstant : INVALID_AMOUNT', response.message + end + + def test_successful_refund + response = stub_comms do + @gateway.refund(100, 'abc') + end.check_request do |endpoint, data, headers| + assert_parameter('command', 'cancel_refund_transaction', data) + assert_parameter('var1', 'abc', data) + assert_parameter('var2', /./, data) + assert_parameter('var3', '1.00', data) + assert_parameter('key', 'key', data) + assert_parameter('txn_s2s_flow', '1', data) + assert_parameter('hash', '06ee55774af4e3eee3f946d4079d34efca243453199b0d4a1328f248b93428ed5c6342c6d73010c0b86d19afc04ae7a1c62c68c472cc0811d00a9a10ecf28791', data) + end.respond_with(successful_refund_response) + + assert_success response + assert_equal 'Refund Request Queued', response.message + end + + def test_failed_refund + @gateway.expects(:ssl_post).returns(failed_refund_response) + + response = @gateway.refund(100, 'abc') + assert_failure response + assert_equal 'Invalid payuid', response.message + end + + def test_refund_without_amount + assert_raise ArgumentError do + @gateway.refund(nil, 'abc') + end + end + + def test_3dsecure_cards_fail + @gateway.expects(:ssl_post).returns(threedsecure_enrolled_response) + + response = @gateway.purchase(100, @credit_card, @options) + assert_failure response + assert_equal '3D-secure enrolled cards are not supported.', response.message + end + + def test_scrub + assert @gateway.supports_scrubbing? + assert_equal @gateway.scrub(pre_scrubbed), post_scrubbed + end + + def test_invalid_json + @gateway.expects(:ssl_post).returns(invalid_json_response) + + response = @gateway.purchase(100, @credit_card, @options) + assert_failure response + assert_match %r{html}, response.message + end + + private + + def pre_scrubbed + <<-PRE_SCRUBBED +opening connection to test.payu.in:443... +opened +starting SSL for test.payu.in:443... +SSL established +<- "POST /_payment HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept-Encoding: identity\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: test.payu.in\r\nContent-Length: 460\r\n\r\n" +<- "amount=1.00&txnid=19ceaa9a230d3057dba07b78ad5c7d46&productinfo=Store+Purchase&surl=http%3A%2F%2Fexample.com&furl=http%3A%2F%2Fexample.com&pg=CC&firstname=Longbob&bankcode=VISA&ccnum=5123456789012346&ccvv=123&ccname=Longbob+Longsen&ccexpmon=5&ccexpyr=2017&email=unknown%40example.com&phone=11111111111&key=Gzv04m&txn_s2s_flow=1&hash=a255c1b5107556b7f00b7c5bbebf92392ec4d2c0675253ca20ef459d4259775efbeae039b59357ddd42374d278dedb432f2e9c238acc6358afe9b22cf908fbb3" +-> "HTTP/1.1 200 OK\r\n" +-> "Date: Fri, 08 May 2015 15:41:17 GMT\r\n" +-> "Server: Apache\r\n" +-> "P3P: CP=\"IDC DSP COR ADM DEVi TAIi PSA PSD IVAi IVDi CONi HIS OUR IND CNT\"\r\n" +-> "Set-Cookie: PHPSESSID=ud24vi12os6m7f7g0lpmked4a0; path=/; secure; HttpOnly\r\n" +-> "Expires: Thu, 19 Nov 1981 08:52:00 GMT\r\n" +-> "Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0\r\n" +-> "Pragma: no-cache\r\n" +-> "Vary: Accept-Encoding\r\n" +-> "Content-Length: 691\r\n" +-> "Connection: close\r\n" +-> "Content-Type: text/html; charset=UTF-8\r\n" +-> "\r\n" +reading 691 bytes... +-> "" +-> "{\"status\":\"success\",\"response\":{\"form_post_vars\":{\"transactionId\":\"b84436e889cf6864a9fa2ab267f3f76a766ad6437b017ccb5093e8217996b814\",\"pgId\":\"8\",\"eci\":\"7\",\"nonEnrolled\":1,\"nonDomestic\":0,\"bank\":\"VISA\",\"cccat\":\"creditcard\",\"ccnum\":\"4b5c9002295c6cd8e5289e2f9c312dc737810a747b84e71665cf077c78fe245a\",\"ccname\":\"53ab689fdb1b025c7e9c53c6b4a6e27f51e0d627579e7c12af2cb6cbc4944cc0\",\"ccvv\":\"f31c6a1d6582f44ee1be4a3e1126b9cb08d1e7006f7afe083d7270b00dcb933f\",\"ccexpmon\":\"5ddf3702e74f473ec89762f6efece025737c2ab999e695cf10496e6fa3946079\",\"ccexpyr\":\"5da83563fcaa945063dc4c2094c48e800badf7c8246c9d13b43757fe99d63e6d\",\"is_seamless\":\"1\"},\"post_uri\":\"https:\\/\\/test.payu.in\\/hdfc_not_enrolled\",\"enrolled\":\"0\"}}" +read 691 bytes +Conn close +opening connection to test.payu.in:443... +opened +starting SSL for test.payu.in:443... +SSL established +<- "POST /hdfc_not_enrolled HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept-Encoding: identity\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: test.payu.in\r\nContent-Length: 520\r\n\r\n" +<- "transactionId=b84436e889cf6864a9fa2ab267f3f76a766ad6437b017ccb5093e8217996b814&pgId=8&eci=7&nonEnrolled=1&nonDomestic=0&bank=VISA&cccat=creditcard&ccnum=4b5c9002295c6cd8e5289e2f9c312dc737810a747b84e71665cf077c78fe245a&ccname=53ab689fdb1b025c7e9c53c6b4a6e27f51e0d627579e7c12af2cb6cbc4944cc0&ccvv=f31c6a1d6582f44ee1be4a3e1126b9cb08d1e7006f7afe083d7270b00dcb933f&ccexpmon=5ddf3702e74f473ec89762f6efece025737c2ab999e695cf10496e6fa3946079&ccexpyr=5da83563fcaa945063dc4c2094c48e800badf7c8246c9d13b43757fe99d63e6d&is_seamless=1" +-> "HTTP/1.1 200 OK\r\n" +-> "Date: Fri, 08 May 2015 15:41:27 GMT\r\n" +-> "Server: Apache\r\n" +-> "P3P: CP=\"IDC DSP COR ADM DEVi TAIi PSA PSD IVAi IVDi CONi HIS OUR IND CNT\"\r\n" +-> "Set-Cookie: PHPSESSID=n717g1mr5lvht96ukdobu6m344; path=/; secure; HttpOnly\r\n" +-> "Expires: Thu, 19 Nov 1981 08:52:00 GMT\r\n" +-> "Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0\r\n" +-> "Pragma: no-cache\r\n" +-> "Vary: Accept-Encoding\r\n" +-> "Content-Length: 1012\r\n" +-> "Connection: close\r\n" +-> "Content-Type: text/html; charset=UTF-8\r\n" +-> "\r\n" +reading 1012 bytes... +-> "" +-> "{\"status\":\"success\",\"result\":\"mihpayid=403993715511983692&mode=CC&status=success&key=Gzv04m&txnid=19ceaa9a230d3057dba07b78ad5c7d46&amount=1.00&addedon=2015-05-08+21%3A11%3A17&productinfo=Store+Purchase&firstname=Longbob&lastname=&address1=&address2=&city=&state=&country=&zipcode=&email=unknown%40example.com&phone=11111111111&udf1=&udf2=&udf3=&udf4=&udf5=&udf6=&udf7=&udf8=&udf9=&udf10=&card_token=&card_no=512345XXXXXX2346&field0=&field1=512816420000&field2=999999&field3=6816991112151281&field4=-1&field5=&field6=&field7=&field8=&field9=SUCCESS&PG_TYPE=HDFCPG&error=E000&error_Message=No+Error&net_amount_debit=1&unmappedstatus=success&hash=c0d3e5346c37ddd32bb3b386ed1d0709a612d304180e7a25dcbf047cc1c3a4e9de9940af0179c6169c0038b2a826d7ea4b868fcbc4e435928e8cbd25da3c1e56&bank_ref_no=6816991112151281&bank_ref_num=6816991112151281&bankcode=VISA&surl=http%3A%2F%2Fexample.com&curl=http%3A%2F%2Fexample.com&furl=http%3A%2F%2Fexample.com&card_hash=f25e4f9ea802050c23423966d35adc54046f651f0d9a2b837b49c75f964d1fa7\"}" +read 1012 bytes +Conn close + PRE_SCRUBBED + end + + def post_scrubbed + <<-POST_SCRUBBED +opening connection to test.payu.in:443... +opened +starting SSL for test.payu.in:443... +SSL established +<- "POST /_payment HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept-Encoding: identity\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: test.payu.in\r\nContent-Length: 460\r\n\r\n" +<- "amount=1.00&txnid=19ceaa9a230d3057dba07b78ad5c7d46&productinfo=Store+Purchase&surl=http%3A%2F%2Fexample.com&furl=http%3A%2F%2Fexample.com&pg=CC&firstname=Longbob&bankcode=VISA&ccnum=[FILTERED]&ccvv=[FILTERED]&ccname=Longbob+Longsen&ccexpmon=5&ccexpyr=2017&email=unknown%40example.com&phone=11111111111&key=Gzv04m&txn_s2s_flow=1&hash=a255c1b5107556b7f00b7c5bbebf92392ec4d2c0675253ca20ef459d4259775efbeae039b59357ddd42374d278dedb432f2e9c238acc6358afe9b22cf908fbb3" +-> "HTTP/1.1 200 OK\r\n" +-> "Date: Fri, 08 May 2015 15:41:17 GMT\r\n" +-> "Server: Apache\r\n" +-> "P3P: CP=\"IDC DSP COR ADM DEVi TAIi PSA PSD IVAi IVDi CONi HIS OUR IND CNT\"\r\n" +-> "Set-Cookie: PHPSESSID=ud24vi12os6m7f7g0lpmked4a0; path=/; secure; HttpOnly\r\n" +-> "Expires: Thu, 19 Nov 1981 08:52:00 GMT\r\n" +-> "Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0\r\n" +-> "Pragma: no-cache\r\n" +-> "Vary: Accept-Encoding\r\n" +-> "Content-Length: 691\r\n" +-> "Connection: close\r\n" +-> "Content-Type: text/html; charset=UTF-8\r\n" +-> "\r\n" +reading 691 bytes... +-> "" +-> "{\"status\":\"success\",\"response\":{\"form_post_vars\":{\"transactionId\":\"b84436e889cf6864a9fa2ab267f3f76a766ad6437b017ccb5093e8217996b814\",\"pgId\":\"8\",\"eci\":\"7\",\"nonEnrolled\":1,\"nonDomestic\":0,\"bank\":\"VISA\",\"cccat\":\"creditcard\",\"ccnum\":\"[FILTERED]\",\"ccname\":\"53ab689fdb1b025c7e9c53c6b4a6e27f51e0d627579e7c12af2cb6cbc4944cc0\",\"ccvv\":\"[FILTERED]\",\"ccexpmon\":\"5ddf3702e74f473ec89762f6efece025737c2ab999e695cf10496e6fa3946079\",\"ccexpyr\":\"5da83563fcaa945063dc4c2094c48e800badf7c8246c9d13b43757fe99d63e6d\",\"is_seamless\":\"1\"},\"post_uri\":\"https:\\/\\/test.payu.in\\/hdfc_not_enrolled\",\"enrolled\":\"0\"}}" +read 691 bytes +Conn close +opening connection to test.payu.in:443... +opened +starting SSL for test.payu.in:443... +SSL established +<- "POST /hdfc_not_enrolled HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept-Encoding: identity\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: test.payu.in\r\nContent-Length: 520\r\n\r\n" +<- "transactionId=b84436e889cf6864a9fa2ab267f3f76a766ad6437b017ccb5093e8217996b814&pgId=8&eci=7&nonEnrolled=1&nonDomestic=0&bank=VISA&cccat=creditcard&ccnum=[FILTERED]&ccname=53ab689fdb1b025c7e9c53c6b4a6e27f51e0d627579e7c12af2cb6cbc4944cc0&ccvv=[FILTERED]&ccexpmon=5ddf3702e74f473ec89762f6efece025737c2ab999e695cf10496e6fa3946079&ccexpyr=5da83563fcaa945063dc4c2094c48e800badf7c8246c9d13b43757fe99d63e6d&is_seamless=1" +-> "HTTP/1.1 200 OK\r\n" +-> "Date: Fri, 08 May 2015 15:41:27 GMT\r\n" +-> "Server: Apache\r\n" +-> "P3P: CP=\"IDC DSP COR ADM DEVi TAIi PSA PSD IVAi IVDi CONi HIS OUR IND CNT\"\r\n" +-> "Set-Cookie: PHPSESSID=n717g1mr5lvht96ukdobu6m344; path=/; secure; HttpOnly\r\n" +-> "Expires: Thu, 19 Nov 1981 08:52:00 GMT\r\n" +-> "Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0\r\n" +-> "Pragma: no-cache\r\n" +-> "Vary: Accept-Encoding\r\n" +-> "Content-Length: 1012\r\n" +-> "Connection: close\r\n" +-> "Content-Type: text/html; charset=UTF-8\r\n" +-> "\r\n" +reading 1012 bytes... +-> "" +-> "{\"status\":\"success\",\"result\":\"mihpayid=403993715511983692&mode=CC&status=success&key=Gzv04m&txnid=19ceaa9a230d3057dba07b78ad5c7d46&amount=1.00&addedon=2015-05-08+21%3A11%3A17&productinfo=Store+Purchase&firstname=Longbob&lastname=&address1=&address2=&city=&state=&country=&zipcode=&email=unknown%40example.com&phone=11111111111&udf1=&udf2=&udf3=&udf4=&udf5=&udf6=&udf7=&udf8=&udf9=&udf10=&card_token=&card_no=512345XXXXXX2346&field0=&field1=512816420000&field2=999999&field3=6816991112151281&field4=-1&field5=&field6=&field7=&field8=&field9=SUCCESS&PG_TYPE=HDFCPG&error=E000&error_Message=No+Error&net_amount_debit=1&unmappedstatus=success&hash=c0d3e5346c37ddd32bb3b386ed1d0709a612d304180e7a25dcbf047cc1c3a4e9de9940af0179c6169c0038b2a826d7ea4b868fcbc4e435928e8cbd25da3c1e56&bank_ref_no=6816991112151281&bank_ref_num=6816991112151281&bankcode=VISA&surl=http%3A%2F%2Fexample.com&curl=http%3A%2F%2Fexample.com&furl=http%3A%2F%2Fexample.com&card_hash=[FILTERED]\"}" +read 1012 bytes +Conn close + POST_SCRUBBED + end + + def successful_purchase_setup_response + %({ + "status":"success", + "response":{ + "form_post_vars":{ + "transactionId":"6e7e62723683934e6c5507675df11bdd86197c5c935878ff72e344205f3c8a1d", + "pgId":"8", + "eci":"7", + "nonEnrolled":1, + "nonDomestic":0, + "bank":"VISA", + "cccat":"creditcard", + "ccnum":"4b5c9002295c6cd8e5289e2f9c312dc737810a747b84e71665cf077c78fe245a", + "ccname":"53ab689fdb1b025c7e9c53c6b4a6e27f51e0d627579e7c12af2cb6cbc4944cc0", + "ccvv":"cc8d6cfb6b03f94e2a64b490ae10c261c10747f543b1fba09d7f56f9ef6aac04", + "ccexpmon":"5ddf3702e74f473ec89762f6efece025737c2ab999e695cf10496e6fa3946079", + "ccexpyr":"5da83563fcaa945063dc4c2094c48e800badf7c8246c9d13b43757fe99d63e6d", + "is_seamless":"1" + }, + "post_uri":"https:\/\/test.payu.in\/hdfc_not_enrolled", + "enrolled":"0" + } + }) + end + + def successful_purchase_response + %({ + "status":"success", + "result":"mihpayid=403993715512145540&mode=CC&status=success&key=Gzv04m&txnid=fcb18f5cd3d85d724b65ce2f541b7624&amount=11.00&addedon=2015-05-26+01%3A58%3A17&productinfo=Store+Purchase&firstname=Longbob&lastname=&address1=&address2=&city=&state=&country=&zipcode=&email=unknown%40example.com&phone=11111111111&udf1=&udf2=&udf3=&udf4=&udf5=&udf6=&udf7=&udf8=&udf9=&udf10=&card_token=&card_no=512345XXXXXX2346&field0=&field1=514682003588&field2=999999&field3=1424005580151461&field4=-1&field5=&field6=&field7=&field8=&field9=SUCCESS&PG_TYPE=HDFCPG&error=E000&error_Message=No+Error&net_amount_debit=11&unmappedstatus=success&hash=6768bf85d0046f7d57ab18804e2bc81ffb08d91eb9db9df89834e70a48e959c646ef00988fe7c5a0493741d7ceb7eaa2fa91a4932ce4c89c7ad49471c68f0008&bank_ref_no=1424005580151461&bank_ref_num=1424005580151461&bankcode=VISA&surl=http%3A%2F%2Fexample.com&curl=http%3A%2F%2Fexample.com&furl=http%3A%2F%2Fexample.com&card_hash=515a2cb0f0e6711f6a3d2c4704cc691d212d4dc0e065c7c8d3441a6b5fc23e97" + }) + end + + def failed_purchase_response + %({ + "status":"failed", + "error":"Invalid amount @~@ ExceptionConstant : INVALID_AMOUNT" + }) + end + + def successful_refund_response + %({ + "status":1, + "msg":"Refund Request Queued", + "request_id":"125199106", + "bank_ref_num":null, + "mihpayid":403993715512169368, + "error_code":102 + }) + end + + def failed_refund_response + %({ + "status":0, + "msg":"Invalid payuid", + "mihpayid":"" + }) + end + + def threedsecure_enrolled_response + %({ + "status":"success", + "response":{ + "post_uri":"https:\/\/dropit.3dsecure.net:9443\/PIT\/ACS", + "form_post_vars":{ + "PaReq":"eJxVUl1X4jAQ\/Ss9ffXYNEWg5UzjAQXt6rIg+MFjTWOJC2lIUoT99ZvUsrp5ydzJnHvnzgQuD9uNt2dK80qkPg5C32OCVgUXZeo\/LifnsX9JYLlWjF0vGK0VI\/CTaZ2XzONF6vfC4\/RlEfVfn\/ZmNeZxZ4jjH2WUzF9o6hOYDR\/YjkArQCx\/EAE6Qcuk6DoXhkBOd6NsSi46nah\/AaiFsGUquyZvWnvfznkS2pP0Q0Cf7yDyLSPZKBv98j7YqzfbG+\/eFICaPNCqFkYdSafbA3QCUKsNWRsj9QAh3TiTpWHaBFZNMBPQKuACyfIjP6IyN8zdeVE0LQfvWgJyDIC+PMxqF2mreOAFiScbpqSSi+xelZJn5e4PjeXh5m61SgG5CigsLYlC3A27Ud\/DeBDiAbbzafKQb12r5EFjOzbrtcUgnczwE2DsXr5nwDpRdoUnuycE7CArwWyFFfgXA\/pq+urWrYEaO9BbPlmO6\/Hvm\/fn4ikpp2c7eTafp6lbTFPg2LidIu6FSUPnACBHgdqdo\/ab2Oi\/7\/MXv\/7OxQ==", + "MD":"1094876012351471", + "TermUrl":"https:\/\/test.payu.in\/_hdfc_response.php?txtid=0ce5251d1f1c07bfe1f5878d26db4010521e13b6bd6ace16c43ec591f7f7c9cb&action=hdfc2_3dsresponse" + }, + "enrolled":"1" + } + }) + end + + def invalid_json_response + %(<html>) + end +end diff --git a/test/unit/gateways/payu_latam_test.rb b/test/unit/gateways/payu_latam_test.rb new file mode 100644 index 00000000000..d4ea5d0664e --- /dev/null +++ b/test/unit/gateways/payu_latam_test.rb @@ -0,0 +1,758 @@ +require 'test_helper' + +class PayuLatamTest < Test::Unit::TestCase + include CommStub + + def setup + @gateway = PayuLatamGateway.new(merchant_id: 'merchant_id', account_id: 'account_id', api_login: 'api_login', api_key: 'api_key', payment_country: 'AR') + + @amount = 4000 + @credit_card = credit_card('4097440000000004', verification_value: '444', first_name: 'APPROVED', last_name: '') + @declined_card = credit_card('4097440000000004', verification_value: '333', first_name: 'REJECTED', last_name: '') + @pending_card = credit_card('4097440000000004', verification_value: '222', first_name: 'PENDING', last_name: '') + @no_cvv_visa_card = credit_card('4097440000000004', verification_value: ' ') + @no_cvv_amex_card = credit_card('4097440000000004', verification_value: ' ', brand: 'american_express') + @cabal_credit_card = credit_card('5896570000000004', :verification_value => '123', :first_name => 'APPROVED', :last_name => '', :brand => 'cabal') + + @options = { + dni_number: '5415668464654', + dni_type: 'TI', + merchant_buyer_id: '1', + currency: 'ARS', + order_id: generate_unique_id, + description: 'Active Merchant Transaction', + installments_number: 1, + tax: 0, + tax_return_base: 0, + email: 'username@domain.com', + ip: '127.0.0.1', + device_session_id: 'vghs6tvkcle931686k1900o6e1', + cookie: 'pt1t38347bs6jc9ruv2ecpv7o2', + user_agent: 'Mozilla/5.0 (Windows NT 5.1; rv:18.0) Gecko/20100101 Firefox/18.0', + billing_address: address( + address1: 'Viamonte', + address2: '1366', + city: 'Plata', + state: 'Buenos Aires', + country: 'AR', + zip: '64000', + phone: '7563126' + ) + } + end + + def test_successful_purchase + @gateway.expects(:ssl_post).returns(successful_purchase_response) + + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal 'APPROVED', response.message + assert response.test? + end + + def test_successful_purchase_with_specified_language + stub_comms do + @gateway.purchase(@amount, @credit_card, @options.merge(language: 'es')) + end.check_request do |endpoint, data, headers| + assert_match(/"language":"es"/, data) + end.respond_with(successful_purchase_response) + end + + def test_successful_purchase_with_cabal_card + @gateway.expects(:ssl_post).returns(successful_purchase_with_cabal_response) + + response = @gateway.purchase(@amount, @cabal_credit_card, @options) + assert_success response + assert_equal 'APPROVED', response.message + assert response.test? + end + + def test_failed_purchase + @gateway.expects(:ssl_post).returns(failed_purchase_response) + + response = @gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_equal 'ANTIFRAUD_REJECTED', response.message + assert_equal 'DECLINED', response.params['transactionResponse']['state'] + end + + def test_successful_authorize + @gateway.expects(:ssl_post).returns(successful_authorize_response) + + response = @gateway.authorize(@amount, @credit_card, @options) + assert_success response + assert_equal 'APPROVED', response.message + assert_match %r(^\d+\|(\w|-)+$), response.authorization + end + + def test_successful_authorize_with_specified_language + stub_comms do + @gateway.authorize(@amount, @credit_card, @options.merge(language: 'es')) + end.check_request do |endpoint, data, headers| + assert_match(/"language":"es"/, data) + end.respond_with(successful_purchase_response) + end + + def test_successful_authorize_with_cabal_card + @gateway.expects(:ssl_post).returns(successful_authorize_with_cabal_response) + + response = @gateway.authorize(@amount, @cabal_credit_card, @options) + assert_success response + assert_equal 'APPROVED', response.message + assert_match %r(^\d+\|(\w|-)+$), response.authorization + end + + def test_failed_authorize + @gateway.expects(:ssl_post).returns(pending_authorize_response) + + response = @gateway.authorize(@amount, @pending_card, @options) + assert_failure response + assert_equal 'PENDING_TRANSACTION_REVIEW', response.message + assert_equal 'PENDING', response.params['transactionResponse']['state'] + end + + def test_pending_refund + @gateway.expects(:ssl_post).returns(pending_refund_response) + + response = @gateway.refund(@amount, '7edbaf68-8f3a-4ae7-b9c7-d1e27e314999') + assert_success response + assert_equal 'PENDING', response.params['transactionResponse']['state'] + end + + def test_pending_refund_with_specified_language + stub_comms do + @gateway.refund(@amount, '7edbaf68-8f3a-4ae7-b9c7-d1e27e314999', @options.merge(language: 'es')) + end.check_request do |endpoint, data, headers| + assert_match(/"language":"es"/, data) + end.respond_with(pending_refund_response) + end + + def test_failed_refund + @gateway.expects(:ssl_post).returns(failed_refund_response) + + response = @gateway.refund(@amount, '') + assert_failure response + assert_equal 'property: order.id, message: must not be null property: parentTransactionId, message: must not be null', response.message + end + + def test_successful_void + @gateway.expects(:ssl_post).returns(successful_void_response) + + response = @gateway.void('7edbaf68-8f3a-4ae7-b9c7-d1e27e314999', @options) + assert_success response + assert_equal 'PENDING_REVIEW', response.message + end + + def test_successful_void_with_specified_language + stub_comms do + @gateway.void('7edbaf68-8f3a-4ae7-b9c7-d1e27e314999', @options.merge(language: 'es')) + end.check_request do |endpoint, data, headers| + assert_match(/"language":"es"/, data) + end.respond_with(successful_void_response) + end + + def test_failed_void + @gateway.expects(:ssl_post).returns(failed_void_response) + + response = @gateway.void('') + assert_failure response + assert_equal 'property: order.id, message: must not be null property: parentTransactionId, message: must not be null', response.message + end + + def test_successful_purchase_with_dni_number + stub_comms do + @gateway.purchase(@amount, @credit_card, @options) + end.check_request do |endpoint, data, headers| + assert_match(/"dniNumber":"5415668464654"/, data) + end.respond_with(successful_purchase_response) + end + + def test_successful_purchase_with_merchant_buyer_id + stub_comms do + @gateway.purchase(@amount, @credit_card, @options) + end.check_request do |endpoint, data, headers| + assert_match(/"merchantBuyerId":"1"/, data) + end.respond_with(successful_purchase_response) + end + + def test_verify_good_credentials + @gateway.expects(:ssl_post).returns(credentials_are_legit_response) + assert @gateway.verify_credentials + end + + def test_verify_bad_credentials + @gateway.expects(:ssl_post).returns(credentials_are_bogus_response) + assert !@gateway.verify_credentials + end + + def test_request_using_visa_card_with_no_cvv + @gateway.expects(:ssl_post).with { |url, body, headers| + body.match '"securityCode":"000"' + body.match '"processWithoutCvv2":true' + }.returns(successful_purchase_response) + response = @gateway.purchase(@amount, @no_cvv_visa_card, @options) + assert_success response + assert_equal 'APPROVED', response.message + assert response.test? + end + + def test_request_using_amex_card_with_no_cvv + @gateway.expects(:ssl_post).with { |url, body, headers| + body.match '"securityCode":"0000"' + body.match '"processWithoutCvv2":true' + }.returns(successful_purchase_response) + response = @gateway.purchase(@amount, @no_cvv_amex_card, @options) + assert_success response + assert_equal 'APPROVED', response.message + assert response.test? + end + + def test_request_passes_cvv_option + @gateway.expects(:ssl_post).with { |url, body, headers| + body.match '"securityCode":"777"' + !body.match '"processWithoutCvv2"' + }.returns(successful_purchase_response) + options = @options.merge(cvv: '777') + response = @gateway.purchase(@amount, @no_cvv_visa_card, options) + assert_success response + assert_equal 'APPROVED', response.message + assert response.test? + end + + def test_successful_capture + @gateway.expects(:ssl_post).returns(successful_capture_response) + + response = @gateway.capture(@amount, '4000|authorization', @options) + assert_success response + assert_equal 'APPROVED', response.message + end + + def test_successful_capture_with_specified_language + stub_comms do + @gateway.capture(@amount, '4000|authorization', @options.merge(language: 'es')) + end.check_request do |endpoint, data, headers| + assert_match(/"language":"es"/, data) + end.respond_with(successful_purchase_response) + end + + def test_successful_partial_capture + stub_comms do + @gateway.capture(@amount - 1, '4000|authorization', @options) + end.check_request do |endpoint, data, headers| + assert_equal '39.99', JSON.parse(data)['transaction']['additionalValues']['TX_VALUE']['value'] + end.respond_with(successful_purchase_response) + end + + def test_failed_capture + @gateway.expects(:ssl_post).returns(failed_void_response) + + response = @gateway.capture(@amount, '') + assert_failure response + assert_equal 'property: order.id, message: must not be null property: parentTransactionId, message: must not be null', response.message + end + + def test_partial_buyer_hash_info + options_buyer = { + shipping_address: address( + address1: 'Calle 200', + address2: 'N107', + city: 'Sao Paulo', + state: 'SP', + country: 'BR', + zip: '01019-030', + phone: '(11)756312345' + ), + buyer: { + name: 'Jorge Borges', + dni_number: '5415668464456', + merchant_buyer_id: '1', + email: 'axaxaxas@mlo.org' + } + } + + stub_comms do + @gateway.purchase(@amount, @credit_card, @options.update(options_buyer)) + end.check_request do |endpoint, data, headers| + assert_match(/\"buyer\":{\"fullName\":\"Jorge Borges\",\"dniNumber\":\"5415668464456\",\"dniType\":null,\"merchantBuyerId\":\"1\",\"emailAddress\":\"axaxaxas@mlo.org\",\"contactPhone\":\"7563126\",\"shippingAddress\":{\"street1\":\"Calle 200\",\"street2\":\"N107\",\"city\":\"Sao Paulo\",\"state\":\"SP\",\"country\":\"BR\",\"postalCode\":\"01019-030\",\"phone\":\"\(11\)756312345\"}}/, data) + end.respond_with(successful_purchase_response) + end + + def test_buyer_fields_default_to_payer + stub_comms do + @gateway.purchase(@amount, @credit_card, @options) + end.check_request do |endpoint, data, headers| + assert_match(/\"buyer\":{\"fullName\":\"APPROVED\",\"dniNumber\":\"5415668464654\",\"dniType\":\"TI\",\"merchantBuyerId\":\"1\",\"emailAddress\":\"username@domain.com\",\"contactPhone\":\"7563126\"/, data) + end.respond_with(successful_purchase_response) + end + + def test_brazil_required_fields + gateway = PayuLatamGateway.new(merchant_id: 'merchant_id', account_id: 'account_id', api_login: 'api_login', api_key: 'api_key', payment_country: 'BR') + + options_brazil = { + currency: 'BRL', + billing_address: address( + address1: 'Calle 100', + address2: 'BL4', + city: 'Sao Paulo', + state: 'SP', + country: 'BR', + zip: '09210710', + phone: '(11)756312633' + ), + shipping_address: address( + address1: 'Calle 200', + address2: 'N107', + city: 'Sao Paulo', + state: 'SP', + country: 'BR', + zip: '01019-030', + phone: '(11)756312633' + ), + buyer: { + cnpj: '32593371000110' + } + } + + stub_comms(gateway) do + gateway.purchase(@amount, @credit_card, @options.update(options_brazil)) + end.check_request do |endpoint, data, headers| + assert_match(/\"cnpj\":\"32593371000110\"/, data) + end.respond_with(successful_purchase_response) + end + + def test_colombia_required_fields + gateway = PayuLatamGateway.new(merchant_id: 'merchant_id', account_id: 'account_id', api_login: 'api_login', api_key: 'api_key', payment_country: 'CO') + + options_colombia = { + currency: 'COP', + billing_address: address( + address1: 'Calle 100', + address2: 'BL4', + city: 'Bogota', + state: 'Bogota DC', + country: 'CO', + zip: '09210710', + phone: '(11)756312633' + ), + shipping_address: address( + address1: 'Calle 200', + address2: 'N107', + city: 'Bogota', + state: 'Bogota DC', + country: 'CO', + zip: '01019-030', + phone: '(11)756312633' + ), + tx_tax: '3193', + tx_tax_return_base: '16806' + } + + stub_comms(gateway) do + gateway.purchase(@amount, @credit_card, @options.update(options_colombia)) + end.check_request do |endpoint, data, headers| + assert_match(/\"additionalValues\":{\"TX_VALUE\":{\"value\":\"40.00\",\"currency\":\"COP\"},\"TX_TAX\":{\"value\":0,\"currency\":\"COP\"},\"TX_TAX_RETURN_BASE\":{\"value\":0,\"currency\":\"COP\"}}/, data) + end.respond_with(successful_purchase_response) + end + + def test_mexico_required_fields + gateway = PayuLatamGateway.new(merchant_id: 'merchant_id', account_id: 'account_id', api_login: 'api_login', api_key: 'api_key', payment_country: 'MX') + + options_mexico = { + currency: 'MXN', + billing_address: address( + address1: 'Calle 100', + address2: 'BL4', + city: 'Guadalajara', + state: 'Jalisco', + country: 'MX', + zip: '09210710', + phone: '(11)756312633' + ), + shipping_address: address( + address1: 'Calle 200', + address2: 'N107', + city: 'Guadalajara', + state: 'Jalisco', + country: 'MX', + zip: '01019-030', + phone: '(11)756312633' + ), + birth_date: '1985-05-25' + } + + stub_comms(gateway) do + gateway.purchase(@amount, @credit_card, @options.update(options_mexico)) + end.check_request do |endpoint, data, headers| + assert_match(/\"birthdate\":\"1985-05-25\"/, data) + end.respond_with(successful_purchase_response) + end + + def test_scrub + assert @gateway.supports_scrubbing? + assert_equal @gateway.scrub(pre_scrubbed), post_scrubbed + end + + private + + def pre_scrubbed + %q( + opening connection to sandbox.api.payulatam.com:443... + opened + starting SSL for sandbox.api.payulatam.com:443... + SSL established + <- "POST /payments-api/4.0/service.cgi HTTP/1.1\r\nContent-Type: application/json\r\nAccept: application/json\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: sandbox.api.payulatam.com\r\nContent-Length: 985\r\n\r\n" + <- "{\"test\":true,\"language\":\"en\",\"command\":\"SUBMIT_TRANSACTION\",\"merchant\":{\"apiLogin\":\"pRRXKOl8ikMmt9u\",\"apiKey\":\"4Vj8eK4rloUd272L48hsrarnUA\"},\"transaction\":{\"type\":\"AUTHORIZATION_AND_CAPTURE\",\"order\":{\"accountId\":\"512326\",\"referenceCode\":\"c540ae0d09ce1868070d21f69aa72873\",\"description\":\"unspecified\",\"language\":\"en\",\"buyer\":{\"emailAddress\":\"unspecified@example.com\",\"fullName\":\"APPROVED\",\"shippingAddress\":{\"street1\":\"Calle 93 B 17 \u{2013} 25 Apt 1\",\"city\":\"Panama\",\"state\":\"Panama\",\"country\":\"PA\",\"postalCode\":\"000000\",\"phone\":\"5582254\"}},\"additionalValues\":{\"TX_VALUE\":{\"value\":\"10.00\",\"currency\":\"USD\"}},\"signature\":\"8cf73cd8ac7760922deeb8d2b5a56689\"},\"creditCard\":{\"number\":\"5500000000000004\",\"securityCode\":\"444\",\"expirationDate\":\"2017/09\",\"name\":\"APPROVED\"},\"paymentMethod\":\"MASTERCARD\",\"paymentCountry\":\"PA\",\"payer\":{\"fullName\":\"APPROVED\",\"emailAddress\":\"unspecified@example.com\"},\"ipAddress\":\"127.0.0.1\",\"cookie\":\"n/a\",\"userAgent\":\"n/a\",\"extraParameters\":{\"INSTALLMENTS_NUMBER\":1}}}" + -> "HTTP/1.1 200 OK\r\n" + -> "Content-Type: application/json\r\n" + -> "Transfer-Encoding: chunked\r\n" + -> "Date: Fri, 13 May 2016 22:07:21 GMT\r\n" + -> "Connection: close\r\n" + -> "Server: PayU server\r\n" + -> "\r\n" + -> "21b\r\n" + reading 539 bytes... + -> "" + -> "{\"code\":\"SUCCESS\",\"error\":null,\"transactionResponse\":{\"orderId\":7348886,\"transactionId\":\"90944ffb-7376-46ae-97fd-b1fcb2d602c3\",\"state\":\"DECLINED\",\"paymentNetworkResponseCode\":null,\"paymentNetworkResponseErrorMessage\":null,\"trazabilityCode\":null,\"authorizationCode\":null,\"pendingReason\":null,\"responseCode\":\"INACTIVE_PAYMENT_PROVIDER\",\"errorCode\":null,\"responseMessage\":\"The payment network processor is not available\",\"transactionDate\":null,\"transactionTime\":null,\"operationDate\":null,\"referenceQuestionnaire\":null,\"extraParameters\":null}}" + read 539 bytes + reading 2 bytes... + -> "" + -> "\r\n" + read 2 bytes + -> "0\r\n" + -> "\r\n" + Conn close + ) + end + + def post_scrubbed + %q( + opening connection to sandbox.api.payulatam.com:443... + opened + starting SSL for sandbox.api.payulatam.com:443... + SSL established + <- "POST /payments-api/4.0/service.cgi HTTP/1.1\r\nContent-Type: application/json\r\nAccept: application/json\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: sandbox.api.payulatam.com\r\nContent-Length: 985\r\n\r\n" + <- "{\"test\":true,\"language\":\"en\",\"command\":\"SUBMIT_TRANSACTION\",\"merchant\":{\"apiLogin\":\"pRRXKOl8ikMmt9u\",\"apiKey\":\"[FILTERED]\"},\"transaction\":{\"type\":\"AUTHORIZATION_AND_CAPTURE\",\"order\":{\"accountId\":\"512326\",\"referenceCode\":\"c540ae0d09ce1868070d21f69aa72873\",\"description\":\"unspecified\",\"language\":\"en\",\"buyer\":{\"emailAddress\":\"unspecified@example.com\",\"fullName\":\"APPROVED\",\"shippingAddress\":{\"street1\":\"Calle 93 B 17 \u{2013} 25 Apt 1\",\"city\":\"Panama\",\"state\":\"Panama\",\"country\":\"PA\",\"postalCode\":\"000000\",\"phone\":\"5582254\"}},\"additionalValues\":{\"TX_VALUE\":{\"value\":\"10.00\",\"currency\":\"USD\"}},\"signature\":\"8cf73cd8ac7760922deeb8d2b5a56689\"},\"creditCard\":{\"number\":\"[FILTERED]\",\"securityCode\":\"[FILTERED]\",\"expirationDate\":\"2017/09\",\"name\":\"APPROVED\"},\"paymentMethod\":\"MASTERCARD\",\"paymentCountry\":\"PA\",\"payer\":{\"fullName\":\"APPROVED\",\"emailAddress\":\"unspecified@example.com\"},\"ipAddress\":\"127.0.0.1\",\"cookie\":\"n/a\",\"userAgent\":\"n/a\",\"extraParameters\":{\"INSTALLMENTS_NUMBER\":1}}}" + -> "HTTP/1.1 200 OK\r\n" + -> "Content-Type: application/json\r\n" + -> "Transfer-Encoding: chunked\r\n" + -> "Date: Fri, 13 May 2016 22:07:21 GMT\r\n" + -> "Connection: close\r\n" + -> "Server: PayU server\r\n" + -> "\r\n" + -> "21b\r\n" + reading 539 bytes... + -> "" + -> "{\"code\":\"SUCCESS\",\"error\":null,\"transactionResponse\":{\"orderId\":7348886,\"transactionId\":\"90944ffb-7376-46ae-97fd-b1fcb2d602c3\",\"state\":\"DECLINED\",\"paymentNetworkResponseCode\":null,\"paymentNetworkResponseErrorMessage\":null,\"trazabilityCode\":null,\"authorizationCode\":null,\"pendingReason\":null,\"responseCode\":\"INACTIVE_PAYMENT_PROVIDER\",\"errorCode\":null,\"responseMessage\":\"The payment network processor is not available\",\"transactionDate\":null,\"transactionTime\":null,\"operationDate\":null,\"referenceQuestionnaire\":null,\"extraParameters\":null}}" + read 539 bytes + reading 2 bytes... + -> "" + -> "\r\n" + read 2 bytes + -> "0\r\n" + -> "\r\n" + Conn close + ) + end + + def successful_purchase_response + <<-RESPONSE + { + "code": "SUCCESS", + "error": null, + "transactionResponse": { + "orderId": 3018500, + "transactionId": "b5369274-4b51-4cd3-a634-61db79b3eb9c", + "state": "APPROVED", + "paymentNetworkResponseCode": null, + "paymentNetworkResponseErrorMessage": null, + "trazabilityCode": "00000000", + "authorizationCode": "00000000", + "pendingReason": null, + "responseCode": "APPROVED", + "errorCode": null, + "responseMessage": null, + "transactionDate": null, + "transactionTime": null, + "operationDate": 1393966959622, + "extraParameters": null + } + } + RESPONSE + end + + def successful_purchase_with_cabal_response + <<-RESPONSE + { + "code":"SUCCESS", + "error":null, + "transactionResponse": { + "orderId":846449068, + "transactionId":"34fa1616-f16c-4474-98dc-6163cb05f6d1", + "state":"APPROVED", + "paymentNetworkResponseCode":null, + "paymentNetworkResponseErrorMessage":null, + "trazabilityCode":"00000000", + "authorizationCode":"00000000", + "pendingReason":null, + "responseCode":"APPROVED", + "errorCode":null, + "responseMessage":null, + "transactionDate":null, + "transactionTime":null, + "operationDate":1567524354749, + "referenceQuestionnaire":null, + "extraParameters": { + "PAYMENT_WAY_ID":"28", + "BANK_REFERENCED_CODE":"DEBIT" + }, + "additionalInfo":null + } + } + RESPONSE + end + + def failed_purchase_response + <<-RESPONSE + { + "code": "SUCCESS", + "error": null, + "transactionResponse": { + "orderId": 7354347, + "transactionId": "15b6cec0-9eec-4564-b6b9-c846b868203e", + "state": "DECLINED", + "paymentNetworkResponseCode": null, + "paymentNetworkResponseErrorMessage": null, + "trazabilityCode": null, + "authorizationCode": null, + "pendingReason": null, + "responseCode": "ANTIFRAUD_REJECTED", + "errorCode": null, + "responseMessage": null, + "transactionDate": null, + "transactionTime": null, + "operationDate": null, + "referenceQuestionnaire": null, + "extraParameters": null + } + } + RESPONSE + end + + def successful_authorize_response + <<-RESPONSE + { + "code": "SUCCESS", + "error": null, + "transactionResponse": { + "orderId": 3018500, + "transactionId": "b5369274-4b51-4cd3-a634-61db79b3eb9c", + "state": "APPROVED", + "paymentNetworkResponseCode": null, + "paymentNetworkResponseErrorMessage": null, + "trazabilityCode": "00000000", + "authorizationCode": "00000000", + "pendingReason": null, + "responseCode": "APPROVED", + "errorCode": null, + "responseMessage": null, + "transactionDate": null, + "transactionTime": null, + "operationDate": 1393966959622, + "extraParameters": null + } + } + RESPONSE + end + + def successful_authorize_with_cabal_response + <<-RESPONSE + { + "code":"SUCCESS", + "error":null, + "transactionResponse": { + "orderId":846449155, + "transactionId":"c15e6015-87c2-4db9-9100-894bf5564330", + "state":"APPROVED", + "paymentNetworkResponseCode":null, + "paymentNetworkResponseErrorMessage":null, + "trazabilityCode":"00000000", + "authorizationCode":"00000000", + "pendingReason":null, + "responseCode":"APPROVED", + "errorCode":null, + "responseMessage":null, + "transactionDate":null, + "transactionTime":null, + "operationDate":1567524806987, + "referenceQuestionnaire":null, + "extraParameters": { + "PAYMENT_WAY_ID":"28", + "BANK_REFERENCED_CODE":"DEBIT" + }, + "additionalInfo":null + } + } + + RESPONSE + end + + def pending_authorize_response + <<-RESPONSE + { + "code": "SUCCESS", + "error": null, + "transactionResponse": { + "orderId": 7354347, + "transactionId": "15b6cec0-9eec-4564-b6b9-c846b868203e", + "state": "PENDING", + "paymentNetworkResponseCode": null, + "paymentNetworkResponseErrorMessage": null, + "trazabilityCode": null, + "authorizationCode": null, + "pendingReason": "PENDING_REVIEW", + "responseCode": "PENDING_TRANSACTION_REVIEW", + "errorCode": null, + "responseMessage": null, + "transactionDate": null, + "transactionTime": null, + "operationDate": null, + "referenceQuestionnaire": null, + "extraParameters": null + } + } + RESPONSE + end + + def pending_refund_response + <<-RESPONSE + { + "code": "SUCCESS", + "error": null, + "transactionResponse": + { + "orderId": 924877963, + "transactionId": null, + "state": "PENDING", + "paymentNetworkResponseCode": null, + "paymentNetworkResponseErrorMessage": null, + "trazabilityCode": null, + "authorizationCode": null, + "pendingReason": "PENDING_REVIEW", + "responseCode": null, + "errorCode": null, + "responseMessage": "924877963", + "transactionDate": null, + "transactionTime": null, + "operationDate": null, + "referenceQuestionnaire": null, + "extraParameters": null, + "additionalInfo": null + } + } + RESPONSE + end + + def failed_refund_response + <<-RESPONSE + { + "code":"ERROR", + "error":"property: order.id, message: must not be null property: parentTransactionId, message: must not be null", + "transactionResponse": null + } + RESPONSE + end + + def successful_void_response + <<-RESPONSE + { + "code": "SUCCESS", + "error": null, + "transactionResponse": { + "orderId": 840434914, + "transactionId": "e66fd9aa-f485-4f10-b1d6-be8e9e354b63", + "state": "PENDING", + "paymentNetworkResponseCode": "0", + "paymentNetworkResponseErrorMessage": null, + "trazabilityCode": "49263990", + "authorizationCode": "NPS-011111", + "pendingReason": "PENDING_REVIEW", + "responseCode": null, + "errorCode": null, + "responseMessage": null, + "transactionDate": null, + "transactionTime": null, + "operationDate": 1486655230074, + "referenceQuestionnaire": null, + "extraParameters": null, + "additionalInfo": null + } + } + RESPONSE + end + + def failed_void_response + <<-RESPONSE + { + "code":"ERROR", + "error":"property: order.id, message: must not be null property: parentTransactionId, message: must not be null", + "transactionResponse": null + } + RESPONSE + end + + def successful_capture_response + <<-RESPONSE + { + "code": "SUCCESS", + "error": null, + "transactionResponse": { + "orderId": 272601, + "transactionId": "66c7bff2-c423-42ed-800a-8be11531e7a1", + "state": "APPROVED", + "paymentNetworkResponseCode": null, + "paymentNetworkResponseErrorMessage": null, + "trazabilityCode": "00000000", + "authorizationCode": "00000000", + "pendingReason": null, + "responseCode": "APPROVED", + "errorCode": null, + "responseMessage": null, + "transactionDate": null, + "transactionTime": null, + "operationDate": 1314012754, + "extraParameters": null + } + } + RESPONSE + end + + def failed_capture_response + <<-RESPONSE + { + "code":"ERROR", + "error":"property: order.id, message: must not be null property: parentTransactionId, message: must not be null", + "transactionResponse": null + } + RESPONSE + end + + def credentials_are_legit_response + <<-RESPONSE + { + "code": "SUCCESS", + "error": null, + "paymentMethods": null + } + RESPONSE + end + + def credentials_are_bogus_response + <<-RESPONSE + { + "code": "ERROR", + "error": "Invalid credentials", + "transactionResponse": null + } + RESPONSE + end +end diff --git a/test/unit/gateways/payway_test.rb b/test/unit/gateways/payway_test.rb index 0885b9f3fa5..42a1aa25321 100644 --- a/test/unit/gateways/payway_test.rb +++ b/test/unit/gateways/payway_test.rb @@ -6,7 +6,7 @@ def setup @gateway = PaywayGateway.new( :username => '12341234', :password => 'abcdabcd', - :pem => File.read('gem-public_cert.pem') + :pem => certificate ) @amount = 1000 @@ -50,7 +50,6 @@ def test_succesful_purchase_visa_from_register_user assert_match '0', response.params['summary_code'] assert_match '08', response.params['response_code'] assert_match 'VISA', response.params['card_scheme_name'] - end def test_successful_purchase_master_card @@ -220,35 +219,59 @@ def test_store private - def successful_response_store - "response.responseCode=00" - end + def successful_response_store + 'response.responseCode=00' + end - def successful_response_visa - "response.summaryCode=0&response.responseCode=08&response.cardSchemeName=VISA" - end + def successful_response_visa + 'response.summaryCode=0&response.responseCode=08&response.cardSchemeName=VISA' + end - def successful_response_master_card - "response.summaryCode=0&response.responseCode=08&response.cardSchemeName=MASTERCARD" - end + def successful_response_master_card + 'response.summaryCode=0&response.responseCode=08&response.cardSchemeName=MASTERCARD' + end - def purchase_with_invalid_credit_card_response - "response.summaryCode=1&response.responseCode=14" - end + def purchase_with_invalid_credit_card_response + 'response.summaryCode=1&response.responseCode=14' + end - def purchase_with_expired_credit_card_response - "response.summaryCode=1&response.responseCode=54" - end + def purchase_with_expired_credit_card_response + 'response.summaryCode=1&response.responseCode=54' + end - def purchase_with_invalid_month_response - "response.summaryCode=3&response.responseCode=QA" - end + def purchase_with_invalid_month_response + 'response.summaryCode=3&response.responseCode=QA' + end - def bad_login_response - "response.summaryCode=3&response.responseCode=QH" - end + def bad_login_response + 'response.summaryCode=3&response.responseCode=QH' + end - def bad_merchant_response - "response.summaryCode=3&response.responseCode=QK" - end + def bad_merchant_response + 'response.summaryCode=3&response.responseCode=QK' + end + + def certificate + '------BEGIN CERTIFICATE----- + -MIIDeDCCAmCgAwIBAgIBATANBgkqhkiG9w0BAQUFADBBMRMwEQYDVQQDDApjb2R5 + -ZmF1c2VyMRUwEwYKCZImiZPyLGQBGRYFZ21haWwxEzARBgoJkiaJk/IsZAEZFgNj + -b20wHhcNMTMxMTEzMTk1NjE2WhcNMTQxMTEzMTk1NjE2WjBBMRMwEQYDVQQDDApj + -b2R5ZmF1c2VyMRUwEwYKCZImiZPyLGQBGRYFZ21haWwxEzARBgoJkiaJk/IsZAEZ + -FgNjb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC6T4Iqt5iWvAlU + -iXI6L8UO0URQhIC65X/gJ9hL/x4lwSl/ckVm/R/bPrJGmifT+YooFv824N3y/TIX + -25o/lZtRj1TUZJK4OCb0aVzosQVxBHSe6rLmxO8cItNTMOM9wn3thaITFrTa1DOQ + -O3wqEjvW2L6VMozVfK1MfjL9IGgy0rCnl+2g4Gh4jDDpkLfnMG5CWI6cTCf3C1ye + -ytOpWgi0XpOEy8nQWcFmt/KCQ/kFfzBo4QxqJi54b80842EyvzWT9OB7Oew/CXZG + -F2yIHtiYxonz6N09vvSzq4CvEuisoUFLKZnktndxMEBKwJU3XeSHAbuS7ix40OKO + -WKuI54fHAgMBAAGjezB5MAkGA1UdEwQCMAAwCwYDVR0PBAQDAgSwMB0GA1UdDgQW + -BBR9QQpefI3oDCAxiqJW/3Gg6jI6qjAfBgNVHREEGDAWgRRjb2R5ZmF1c2VyQGdt + -YWlsLmNvbTAfBgNVHRIEGDAWgRRjb2R5ZmF1c2VyQGdtYWlsLmNvbTANBgkqhkiG + -9w0BAQUFAAOCAQEAYJgMj+RlvKSOcks29P76WE+Lexvq3eZBDxxgFHatACdq5Fis + -MUEGiiHeLkR1KRTkvkXCos6CtZVUBVUsftueHmKA7adO2yhrjv4YhPTb/WZxWmQC + -L59lMhnp9UcFJ0H7TkAiU1TvvXewdQPseX8Ayl0zRwD70RfhGh0LfFsKN0JGR4ZS + -yZvtu7hS26h9KwIyo5N3nw7cKSLzT7KsV+s1C+rTjVCb3/JJA9yOe/SCj/Xyc+JW + -ZJB9YPQZG+vWBdDSca3sUMtvFxpLUFwdKF5APSPOVnhbFJ3vSXY1ulP/R6XW9vnw + -6kkQi2fHhU20ugMzp881Eixr+TjC0RvUerLG7g== + ------END CERTIFICATE-----' + end end diff --git a/test/unit/gateways/pin_test.rb b/test/unit/gateways/pin_test.rb index c8edf25f776..1d448d2203b 100644 --- a/test/unit/gateways/pin_test.rb +++ b/test/unit/gateways/pin_test.rb @@ -8,7 +8,7 @@ def setup @amount = 100 @options = { - :email => 'roland@pin.net.au', + :email => 'roland@pinpayments.com', :billing_address => address, :description => 'Store Purchase', :ip => '127.0.0.1' @@ -30,11 +30,11 @@ def test_money_format end def test_url - assert_equal 'https://test-api.pin.net.au/1', PinGateway.test_url + assert_equal 'https://test-api.pinpayments.com/1', PinGateway.test_url end def test_live_url - assert_equal 'https://api.pin.net.au/1', PinGateway.live_url + assert_equal 'https://api.pinpayments.com/1', PinGateway.live_url end def test_supported_countries @@ -42,11 +42,11 @@ def test_supported_countries end def test_supported_cardtypes - assert_equal [:visa, :master], PinGateway.supported_cardtypes + assert_equal [:visa, :master, :american_express], PinGateway.supported_cardtypes end def test_display_name - assert_equal 'Pin', PinGateway.display_name + assert_equal 'Pin Payments', PinGateway.display_name end def test_setup_purchase_parameters @@ -55,8 +55,9 @@ def test_setup_purchase_parameters @gateway.expects(:add_invoice).with(instance_of(Hash), @options) @gateway.expects(:add_creditcard).with(instance_of(Hash), @credit_card) @gateway.expects(:add_address).with(instance_of(Hash), @credit_card, @options) + @gateway.expects(:add_capture).with(instance_of(Hash), @options) - @gateway.stubs(:ssl_post).returns(successful_purchase_response) + @gateway.stubs(:ssl_request).returns(successful_purchase_response) assert_success @gateway.purchase(@amount, @credit_card, @options) end @@ -65,7 +66,7 @@ def test_successful_purchase headers = {} @gateway.stubs(:headers).returns(headers) @gateway.stubs(:post_data).returns(post_data) - @gateway.expects(:ssl_post).with('https://test-api.pin.net.au/1/charges', post_data, headers).returns(successful_purchase_response) + @gateway.expects(:ssl_request).with(:post, 'https://test-api.pinpayments.com/1/charges', post_data, headers).returns(successful_purchase_response) assert response = @gateway.purchase(@amount, @credit_card, @options) assert_success response @@ -75,35 +76,61 @@ def test_successful_purchase end def test_unsuccessful_request - @gateway.expects(:ssl_post).returns(failed_purchase_response) + @gateway.expects(:ssl_request).returns(failed_purchase_response) assert response = @gateway.purchase(@amount, @credit_card, @options) assert_failure response - assert_equal "The current resource was deemed invalid.", response.message + assert_equal 'The current resource was deemed invalid.', response.message assert response.test? end + def test_unparsable_body_of_successful_response + @gateway.stubs(:raw_ssl_request).returns(MockResponse.succeeded('This is not [ JSON')) + + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_match(/Invalid JSON response received/, response.message) + end + + def test_unparsable_body_of_failed_response + @gateway.stubs(:raw_ssl_request).returns(MockResponse.failed('This is not [ JSON')) + + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_match(/Invalid JSON response received/, response.message) + end + def test_successful_store - @gateway.expects(:ssl_post).returns(successful_store_response) + @gateway.expects(:ssl_request).returns(successful_customer_store_response) assert response = @gateway.store(@credit_card, @options) assert_success response - assert_equal 'card_sVOs8D9nANoNgDc38NvKow', response.authorization - assert_equal JSON.parse(successful_store_response), response.params + assert_equal 'card__o8I8GmoXDF0d35LEDZbNQ;cus_05p0n7UFPmcyCNjD8c6HdA', response.authorization + assert_equal JSON.parse(successful_customer_store_response), response.params assert response.test? end def test_unsuccessful_store - @gateway.expects(:ssl_post).returns(failed_store_response) + @gateway.expects(:ssl_request).returns(failed_customer_store_response) assert response = @gateway.store(@credit_card, @options) assert_failure response - assert_equal "The current resource was deemed invalid.", response.message + assert_equal 'The current resource was deemed invalid.', response.message + assert response.test? + end + + def test_successful_update + token = 'cus_05p0n7UFPmcyCNjD8c6HdA' + @gateway.expects(:ssl_request).with(:put, "https://test-api.pinpayments.com/1/customers/#{token}", instance_of(String), instance_of(Hash)).returns(successful_customer_store_response) + assert response = @gateway.update('cus_05p0n7UFPmcyCNjD8c6HdA', @credit_card, @options) + assert_success response + assert_equal 'card__o8I8GmoXDF0d35LEDZbNQ;cus_05p0n7UFPmcyCNjD8c6HdA', response.authorization + assert_equal JSON.parse(successful_customer_store_response), response.params assert response.test? end def test_successful_refund token = 'ch_encBuMDf17qTabmVjDsQlg' - @gateway.expects(:ssl_post).with("https://test-api.pin.net.au/1/charges/#{token}/refunds", {:amount => '100'}.to_json, instance_of(Hash)).returns(successful_refund_response) + @gateway.expects(:ssl_request).with(:post, "https://test-api.pinpayments.com/1/charges/#{token}/refunds", {:amount => '100'}.to_json, instance_of(Hash)).returns(successful_refund_response) assert response = @gateway.refund(100, token) assert_equal 'rf_d2C7M6Mn4z2m3APqarNN6w', response.authorization @@ -113,21 +140,56 @@ def test_successful_refund def test_unsuccessful_refund token = 'ch_encBuMDf17qTabmVjDsQlg' - @gateway.expects(:ssl_post).with("https://test-api.pin.net.au/1/charges/#{token}/refunds", {:amount => '100'}.to_json, instance_of(Hash)).returns(failed_refund_response) + @gateway.expects(:ssl_request).with(:post, "https://test-api.pinpayments.com/1/charges/#{token}/refunds", {:amount => '100'}.to_json, instance_of(Hash)).returns(failed_refund_response) assert response = @gateway.refund(100, token) assert_failure response - assert_equal "The current resource was deemed invalid.", response.message + assert_equal 'The current resource was deemed invalid.', response.message + assert response.test? + end + + def test_successful_authorize + post_data = {} + headers = {} + @gateway.stubs(:headers).returns(headers) + @gateway.stubs(:post_data).returns(post_data) + @gateway.expects(:ssl_request).with(:post, 'https://test-api.pinpayments.com/1/charges', post_data, headers).returns(successful_purchase_response) + + assert response = @gateway.authorize(@amount, @credit_card, @options) + assert_success response + assert_equal 'ch_Kw_JxmVqMeSOQU19_krRdw', response.authorization + assert_equal JSON.parse(successful_purchase_response), response.params + assert response.test? + end + + def test_successful_capture + post_data = {} + headers = {} + token = 'ch_encBuMDf17qTabmVjDsQlg' + @gateway.stubs(:headers).returns(headers) + @gateway.stubs(:post_data).returns(post_data) + @gateway.expects(:ssl_request).with(:put, "https://test-api.pinpayments.com/1/charges/#{token}/capture", post_data, headers).returns(successful_capture_response) + + assert response = @gateway.capture(100, token) + assert_success response + assert_equal token, response.authorization assert response.test? end def test_store_parameters @gateway.expects(:add_creditcard).with(instance_of(Hash), @credit_card) @gateway.expects(:add_address).with(instance_of(Hash), @credit_card, @options) - @gateway.expects(:ssl_post).returns(successful_store_response) + @gateway.expects(:ssl_request).returns(successful_store_response) assert_success @gateway.store(@credit_card, @options) end + def test_update_parameters + @gateway.expects(:add_creditcard).with(instance_of(Hash), @credit_card) + @gateway.expects(:add_address).with(instance_of(Hash), @credit_card, @options) + @gateway.expects(:ssl_request).returns(successful_store_response) + assert_success @gateway.update('cus_XZg1ULpWaROQCOT5PdwLkQ', @credit_card, @options) + end + def test_add_amount @gateway.expects(:amount).with(100).returns('100') post = {} @@ -163,14 +225,14 @@ def test_add_customer_data @gateway.send(:add_customer_data, post, @options) - assert_equal 'roland@pin.net.au', post[:email] + assert_equal 'roland@pinpayments.com', post[:email] assert_equal '127.0.0.1', post[:ip_address] end def test_add_address post = {} - @gateway.send(:add_address, post, @creditcard, @options) + @gateway.send(:add_address, post, @credit_card, @options) assert_equal @options[:billing_address][:address1], post[:card][:address_line1] assert_equal @options[:billing_address][:city], post[:card][:address_city] @@ -194,6 +256,16 @@ def test_add_invoice assert_equal @options[:description], post[:description] end + def test_add_capture + post = {} + + @gateway.send(:add_capture, post, @options) + assert_equal post[:capture], true + + @gateway.send(:add_capture, post, :capture => false) + assert_equal post[:capture], false + end + def test_add_creditcard post = {} @gateway.send(:add_creditcard, post, @credit_card) @@ -202,7 +274,7 @@ def test_add_creditcard assert_equal @credit_card.month, post[:card][:expiry_month] assert_equal @credit_card.year, post[:card][:expiry_year] assert_equal @credit_card.verification_value, post[:card][:cvc] - assert_equal "#{@credit_card.first_name} #{@credit_card.last_name}", post[:card][:name] + assert_equal @credit_card.name, post[:card][:name] end def test_add_creditcard_with_card_token @@ -227,20 +299,23 @@ def test_post_data def test_headers expected_headers = { - "Content-Type" => "application/json", - "Authorization" => "Basic #{Base64.strict_encode64('I_THISISNOTAREALAPIKEY:').strip}" + 'Content-Type' => 'application/json', + 'Authorization' => "Basic #{Base64.strict_encode64('I_THISISNOTAREALAPIKEY:').strip}" } - @gateway.expects(:ssl_post).with(anything, anything, expected_headers).returns(successful_purchase_response) - assert response = @gateway.purchase(@amount, @credit_card, {}) + @gateway.expects(:ssl_request).with(:post, anything, anything, expected_headers).returns(successful_purchase_response) + assert @gateway.purchase(@amount, @credit_card, {}) expected_headers['X-Partner-Key'] = 'MyPartnerKey' expected_headers['X-Safe-Card'] = '1' - @gateway.expects(:ssl_post).with(anything, anything, expected_headers).returns(successful_purchase_response) - assert response = @gateway.purchase(@amount, @credit_card, :partner_key => 'MyPartnerKey', :safe_card => '1') + @gateway.expects(:ssl_request).with(:post, anything, anything, expected_headers).returns(successful_purchase_response) + assert @gateway.purchase(@amount, @credit_card, :partner_key => 'MyPartnerKey', :safe_card => '1') end + def test_transcript_scrubbing + assert_equal scrubbed_transcript, @gateway.scrub(transcript) + end private @@ -252,7 +327,7 @@ def successful_purchase_response "amount":400, "currency":"AUD", "description":"test charge", - "email":"roland@pin.net.au", + "email":"roland@pinpayments.com", "ip_address":"203.192.1.172", "created_at":"2013-01-14T03:00:41Z", "status_message":"Success!", @@ -332,7 +407,7 @@ def successful_customer_store_response '{ "response":{ "token":"cus_05p0n7UFPmcyCNjD8c6HdA", - "email":"roland@pin.net.au", + "email":"roland@pinpayments.com", "created_at":"2013-01-16T03:16:11Z", "card":{ "token":"card__o8I8GmoXDF0d35LEDZbNQ", @@ -389,4 +464,84 @@ def failed_refund_response } }' end + + def successful_capture_response + '{ + "response":{ + "token":"ch_encBuMDf17qTabmVjDsQlg", + "success":true, + "amount":400, + "currency":"AUD", + "description":"test charge", + "email":"roland@pinpayments.com", + "ip_address":"203.192.1.172", + "created_at":"2013-01-14T03:00:41Z", + "status_message":"Success!", + "error_message":null, + "card":{ + "token":"card_0oG1hjachN7g8KsOnWlOcg", + "display_number":"XXXX-XXXX-XXXX-0000", + "scheme":"master", + "address_line1":"42 Sevenoaks St", + "address_line2":null, + "address_city":"Lathlain", + "address_postcode":"6454", + "address_state":"WA", + "address_country":"AU" + }, + "transfer":[ + + ], + "amount_refunded":0, + "total_fees":62, + "merchant_entitlement":338, + "refund_pending":false + } + }' + end + + def transcript + '{ + "amount":"100", + "currency":"AUD", + "email":"roland@pinpayments.com", + "ip_address":"203.59.39.62", + "description":"Store Purchase 1437598192", + "card":{ + "number":"5520000000000000", + "expiry_month":9, + "expiry_year":2017, + "cvc":"123", + "name":"Longbob Longsen", + "address_line1":"456 My Street", + "address_city":"Ottawa", + "address_postcode":"K1C2N6", + "address_state":"ON", + "address_country":"CA" + } + }' + end + + def scrubbed_transcript + '{ + "amount":"100", + "currency":"AUD", + "email":"roland@pinpayments.com", + "ip_address":"203.59.39.62", + "description":"Store Purchase 1437598192", + "card":{ + "number":"[FILTERED]", + "expiry_month":9, + "expiry_year":2017, + "cvc":"[FILTERED]", + "name":"Longbob Longsen", + "address_line1":"456 My Street", + "address_city":"Ottawa", + "address_postcode":"K1C2N6", + "address_state":"ON", + "address_country":"CA" + } + }' + end + end diff --git a/test/unit/gateways/plugnpay_test.rb b/test/unit/gateways/plugnpay_test.rb index ffaf0f26d36..4760eada3f6 100644 --- a/test/unit/gateways/plugnpay_test.rb +++ b/test/unit/gateways/plugnpay_test.rb @@ -3,7 +3,7 @@ class PlugnpayTest < Test::Unit::TestCase def setup - Base.gateway_mode = :test + Base.mode = :test @gateway = PlugnpayGateway.new( :login => 'X', @@ -36,41 +36,41 @@ def test_purchase_error end def test_capture_partial_amount - @gateway.expects(:ssl_post).with(anything, all_of(regexp_matches(/mode=mark/), regexp_matches(/card_amount=0.99/)), anything).returns("") + @gateway.expects(:ssl_post).with(anything, all_of(regexp_matches(/mode=mark/), regexp_matches(/card_amount=0.99/)), anything).returns('') @gateway.expects(:parse).returns({}) @gateway.capture(@amount - 1, @credit_card, @options) end def test_capture_full_amount - @gateway.expects(:ssl_post).with(anything, all_of(regexp_matches(/mode=mark/), regexp_matches(/card_amount=1.00/)), anything).returns("") + @gateway.expects(:ssl_post).with(anything, all_of(regexp_matches(/mode=mark/), regexp_matches(/card_amount=1.00/)), anything).returns('') @gateway.expects(:parse).returns({'auth_msg' => 'Blah blah blah Transaction may not be reauthorized'}, {}) @gateway.capture(@amount, @credit_card, @options) end def test_credit - @gateway.expects(:ssl_post).with(anything, regexp_matches(/card_number=#{@credit_card.number}/), anything).returns("") + @gateway.expects(:ssl_post).with(anything, regexp_matches(/card_number=#{@credit_card.number}/), anything).returns('') @gateway.expects(:parse).returns({}) @gateway.credit(@amount, @credit_card, @options) end def test_deprecated_credit - @gateway.expects(:ssl_post).with(anything, regexp_matches(/orderID=transaction_id/), anything).returns("") + @gateway.expects(:ssl_post).with(anything, regexp_matches(/orderID=transaction_id/), anything).returns('') @gateway.expects(:parse).returns({}) - assert_deprecation_warning(Gateway::CREDIT_DEPRECATION_MESSAGE, @gateway) do - @gateway.credit(@amount, "transaction_id", @options) + assert_deprecation_warning(Gateway::CREDIT_DEPRECATION_MESSAGE) do + @gateway.credit(@amount, 'transaction_id', @options) end end def test_refund - @gateway.expects(:ssl_post).with(anything, regexp_matches(/orderID=transaction_id/), anything).returns("") + @gateway.expects(:ssl_post).with(anything, regexp_matches(/orderID=transaction_id/), anything).returns('') @gateway.expects(:parse).returns({}) - @gateway.refund(@amount, "transaction_id", @options) + @gateway.refund(@amount, 'transaction_id', @options) end def test_add_address_outsite_north_america result = PlugnpayGateway::PlugnpayPostData.new - @gateway.send(:add_addresses, result, :billing_address => {:address1 => '164 Waverley Street', :country => 'DE', :state => 'Dortmund'} ) + @gateway.send(:add_addresses, result, :billing_address => {:address1 => '164 Waverley Street', :country => 'DE', :state => 'Dortmund'}) assert_equal result[:state], 'ZZ' assert_equal result[:province], 'Dortmund' @@ -80,13 +80,12 @@ def test_add_address_outsite_north_america assert_equal result[:card_address1], '164 Waverley Street' assert_equal result[:card_country], 'DE' - end def test_add_address result = PlugnpayGateway::PlugnpayPostData.new - @gateway.send(:add_addresses, result, :billing_address => {:address1 => '164 Waverley Street', :country => 'US', :state => 'CO'} ) + @gateway.send(:add_addresses, result, :billing_address => {:address1 => '164 Waverley Street', :country => 'US', :state => 'CO'}) assert_equal result[:card_state], 'CO' assert_equal result[:card_address1], '164 Waverley Street' @@ -108,6 +107,7 @@ def test_cvv_result end private + def successful_purchase_response "FinalStatus=success&IPaddress=72%2e138%2e32%2e216&MStatus=success&User_Agent=&acct_code3=newcard&address1=1234%20My%20Street&address2=Apt%201&app_level=5&auth_code=TSTAUT&auth_date=20080125&auth_msg=%20&authtype=authpostauth&avs_code=X&card_address1=1234%20My%20Street&card_amount=1%2e00&card_city=Ottawa&card_country=CA&card_name=Longbob%20Longsen&card_state=ON&card_type=VISA&card_zip=K1C2N6&city=Ottawa&convert=underscores&country=CA&currency=usd&cvvresp=M&dontsndmail=yes&easycart=0&merchant=pnpdemo2&merchfraudlev=&mode=auth&orderID=2008012522252119738&phone=555%2d555%2d5555&publisher_email=trash%40plugnpay%2ecom&publisher_name=pnpdemo2&publisher_password=pnpdemo222&resp_code=00&shipinfo=0&shipname=Jim%20Smith&sresp=A&state=ON&success=yes&zip=K1C2N6&a=b\n" end diff --git a/test/unit/gateways/pro_pay_test.rb b/test/unit/gateways/pro_pay_test.rb new file mode 100644 index 00000000000..3064da173bd --- /dev/null +++ b/test/unit/gateways/pro_pay_test.rb @@ -0,0 +1,300 @@ +require 'test_helper' +class ProPayTest < Test::Unit::TestCase + include CommStub + + def setup + @gateway = ProPayGateway.new(cert_str: 'certStr') + @credit_card = credit_card + @amount = 100 + + @options = { + order_id: '1', + billing_address: address, + description: 'Store Purchase' + } + end + + def test_successful_purchase + @gateway.expects(:ssl_post).returns(successful_purchase_response) + + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + + assert_equal '16', response.authorization + assert_equal 'Success', response.message + end + + def test_failed_purchase + @gateway.expects(:ssl_post).returns(failed_purchase_response) + + response = @gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_equal '58', response.error_code + assert_equal 'Credit card declined - Insufficient funds', response.message + end + + def test_successful_authorize + @gateway.expects(:ssl_post).returns(successful_authorize_response) + + response = @gateway.authorize(@amount, @credit_card, @options) + assert_success response + + assert_equal '24', response.authorization + assert_equal 'Success', response.message + end + + def test_failed_authorize + @gateway.expects(:ssl_post).returns(failed_authorize_response) + + response = @gateway.authorize(@amount, @credit_card, @options) + assert_failure response + assert_equal '58', response.error_code + assert_equal 'Credit card declined - Insufficient funds', response.message + end + + def test_successful_capture + @gateway.expects(:ssl_post).returns(successful_capture_response) + + response = @gateway.capture(@amount, 'auth', @options) + assert_success response + + assert_equal '24', response.authorization + assert_equal 'Success', response.message + end + + def test_failed_capture + @gateway.expects(:ssl_post).returns(failed_capture_response) + + response = @gateway.capture(@amount, 'invalid-auth', @options) + assert_failure response + assert_equal '51', response.error_code + assert_equal 'Invalid transNum and/or Unable to act perform actions on transNum due to funding', response.message + end + + def test_successful_refund + @gateway.expects(:ssl_post).returns(successful_refund_response) + + response = @gateway.refund(@amount, 'auth', @options) + assert_success response + end + + def test_failed_refund + @gateway.expects(:ssl_post).returns(failed_refund_response) + + response = @gateway.refund(@amount, 'invalid-auth', @options) + assert_failure response + assert_equal 'Invalid transNum and/or Unable to act perform actions on transNum due to funding', response.message + end + + def test_successful_void + response = stub_comms do + @gateway.void('auth', @options) + end.check_request do |endpoint, data, headers| + assert_match(%r(<transType>07</transType>), data) + end.respond_with(successful_void_response) + + assert_success response + end + + def test_failed_void + response = stub_comms do + @gateway.void('invalid-auth', @options) + end.check_request do |endpoint, data, headers| + assert_match(%r(<transType>07</transType>), data) + end.respond_with(failed_void_response) + + assert_failure response + end + + def test_successful_verify + @gateway.expects(:ssl_post).times(2).returns(successful_authorize_response, successful_void_response) + + response = @gateway.verify(@credit_card, @options) + assert_success response + assert_equal 'Success', response.message + end + + def test_successful_verify_with_failed_void + @gateway.expects(:ssl_post).times(2).returns(successful_authorize_response, failed_void_response) + + response = @gateway.verify(@credit_card, @options) + assert_success response + end + + def test_successful_credit + @gateway.expects(:ssl_post).returns(successful_credit_response) + + response = @gateway.credit(@amount, @credit_card, @options) + assert_success response + + assert_equal '103', response.authorization + assert_equal 'Success', response.message + end + + def test_failed_credit + @gateway.expects(:ssl_post).returns(failed_credit_response) + + response = @gateway.credit(@amount, @credit_card, @options) + assert_failure response + assert_equal 'Invalid ccNum', response.message + end + + def test_failed_verify + @gateway.expects(:ssl_post).returns(failed_authorize_response) + + response = @gateway.verify(@credit_card, @options) + assert_failure response + assert_equal '58', response.error_code + end + + def test_scrub + assert @gateway.supports_scrubbing? + assert_equal @gateway.scrub(pre_scrubbed), post_scrubbed + end + + def test_does_not_send_dashed_zip_code + options = @options.merge(billing_address: address.update(zip: '12345-3456')) + + stub_comms do + @gateway.purchase(@amount, @credit_card, options) + end.check_request do |endpoint, data, headers| + assert_match(/<zip>123453456</, data) + end.respond_with(successful_purchase_response) + end + + private + + def pre_scrubbed + <<-RESPONSE +opening connection to xmltest.propay.com:443... +opened +starting SSL for xmltest.propay.com:443... +SSL established +<- "POST /API/PropayAPI.aspx HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: xmltest.propay.com\r\nContent-Length: 547\r\n\r\n" +<- "<?xml version=\"1.0\"?>\n<XMLRequest>\n <certStr>5ab9cddef2e4911b77e0c4ffb70f03</certStr>\n <class>partner</class>\n <XMLTrans>\n <amount>100</amount>\n <currencyCode>USD</currencyCode>\n <ccNum>4747474747474747</ccNum>\n <expDate>0918</expDate>\n <CVV2>999</CVV2>\n <cardholderName>Longbob Longsen</cardholderName>\n <addr>456 My Street</addr>\n <aptNum>Apt 1</aptNum>\n <city>Ottawa</city>\n <state>ON</state>\n <zip>K1C2N6</zip>\n <accountNum>32287391</accountNum>\n <transType>04</transType>\n </XMLTrans>\n</XMLRequest>\n" +-> "HTTP/1.1 200 OK\r\n" +-> "Cache-Control: max-age=0,no-cache,no-store,must-revalidate\r\n" +-> "Pragma: no-cache\r\n" +-> "Content-Type: text/xml; charset=utf-8\r\n" +-> "Content-Encoding: gzip\r\n" +-> "Expires: Thu, 01 Jan 1970 00:00:00 GMT\r\n" +-> "Vary: Accept-Encoding\r\n" +-> "Server: Microsoft-IIS/7.5\r\n" +-> "Set-Cookie: ASP.NET_SessionId=hn1orxwu31yeoym5fkdhac4o; path=/; secure; HttpOnly\r\n" +-> "Set-Cookie: sessionValidation=1a1d69b6-6e53-408b-b054-602593da00e7; path=/; secure; HttpOnly\r\n" +-> "X-Powered-By: ASP.NET\r\n" +-> "X-Frame-Options: SAMEORIGIN\r\n" +-> "Date: Tue, 25 Apr 2017 19:44:03 GMT\r\n" +-> "Connection: close\r\n" +-> "Content-Length: 343\r\n" +-> "\r\n" +reading 343 bytes... +-> "" +read 343 bytes +Conn close + RESPONSE + end + + def post_scrubbed + <<-POST_SCRUBBED +opening connection to xmltest.propay.com:443... +opened +starting SSL for xmltest.propay.com:443... +SSL established +<- "POST /API/PropayAPI.aspx HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: xmltest.propay.com\r\nContent-Length: 547\r\n\r\n" +<- "<?xml version=\"1.0\"?>\n<XMLRequest>\n <certStr>[FILTERED]</certStr>\n <class>partner</class>\n <XMLTrans>\n <amount>100</amount>\n <currencyCode>USD</currencyCode>\n <ccNum>[FILTERED]</ccNum>\n <expDate>0918</expDate>\n <CVV2>[FILTERED]</CVV2>\n <cardholderName>Longbob Longsen</cardholderName>\n <addr>456 My Street</addr>\n <aptNum>Apt 1</aptNum>\n <city>Ottawa</city>\n <state>ON</state>\n <zip>K1C2N6</zip>\n <accountNum>32287391</accountNum>\n <transType>04</transType>\n </XMLTrans>\n</XMLRequest>\n" +-> "HTTP/1.1 200 OK\r\n" +-> "Cache-Control: max-age=0,no-cache,no-store,must-revalidate\r\n" +-> "Pragma: no-cache\r\n" +-> "Content-Type: text/xml; charset=utf-8\r\n" +-> "Content-Encoding: gzip\r\n" +-> "Expires: Thu, 01 Jan 1970 00:00:00 GMT\r\n" +-> "Vary: Accept-Encoding\r\n" +-> "Server: Microsoft-IIS/7.5\r\n" +-> "Set-Cookie: ASP.NET_SessionId=hn1orxwu31yeoym5fkdhac4o; path=/; secure; HttpOnly\r\n" +-> "Set-Cookie: sessionValidation=1a1d69b6-6e53-408b-b054-602593da00e7; path=/; secure; HttpOnly\r\n" +-> "X-Powered-By: ASP.NET\r\n" +-> "X-Frame-Options: SAMEORIGIN\r\n" +-> "Date: Tue, 25 Apr 2017 19:44:03 GMT\r\n" +-> "Connection: close\r\n" +-> "Content-Length: 343\r\n" +-> "\r\n" +reading 343 bytes... +-> "" +read 343 bytes +Conn close + POST_SCRUBBED + end + + def successful_purchase_response + %( + <?xml version="1.0"?><XMLResponse><XMLTrans><transType>04</transType><status>00</status><accountNum>32287391</accountNum><transNum>16</transNum><authCode>A11111</authCode><AVS>T</AVS><CVV2Resp>M</CVV2Resp><responseCode>0</responseCode><NetAmt>67</NetAmt><GrossAmt>100</GrossAmt><GrossAmtLessNetAmt>33</GrossAmtLessNetAmt><PerTransFee>30</PerTransFee><Rate>3.25</Rate></XMLTrans></XMLResponse> + ) + end + + def failed_purchase_response + %( + <?xml version="1.0"?><XMLResponse><XMLTrans><transType>04</transType><status>58</status><accountNum>32287391</accountNum><transNum>22</transNum><authCode>A11111</authCode><AVS>T</AVS><responseCode>51</responseCode></XMLTrans></XMLResponse> + ) + end + + def successful_authorize_response + %( + <?xml version="1.0"?><XMLResponse><XMLTrans><transType>05</transType><status>00</status><accountNum>32287391</accountNum><transNum>24</transNum><authCode>A11111</authCode><AVS>T</AVS><CVV2Resp>M</CVV2Resp><responseCode>0</responseCode><NetAmt>0</NetAmt><GrossAmt>100</GrossAmt><GrossAmtLessNetAmt>100</GrossAmtLessNetAmt><PerTransFee>0</PerTransFee><Rate>0.00</Rate></XMLTrans></XMLResponse> + ) + end + + def failed_authorize_response + %( + <?xml version="1.0"?><XMLResponse><XMLTrans><transType>05</transType><status>58</status><accountNum>32287391</accountNum><transNum>26</transNum><authCode>A11111</authCode><AVS>T</AVS><responseCode>51</responseCode></XMLTrans></XMLResponse> + ) + end + + def successful_capture_response + %( + <?xml version="1.0"?><XMLResponse><XMLTrans><transType>06</transType><status>00</status><accountNum>32287391</accountNum><transNum>24</transNum><NetAmt>67</NetAmt><GrossAmt>100</GrossAmt><GrossAmtLessNetAmt>33</GrossAmtLessNetAmt><PerTransFee>30</PerTransFee><Rate>3.25</Rate></XMLTrans></XMLResponse> + ) + end + + def failed_capture_response + %( + <?xml version="1.0"?><XMLResponse><XMLTrans><transType>06</transType><status>51</status><accountNum>32287391</accountNum></XMLTrans></XMLResponse> + ) + end + + def successful_refund_response + %( + <?xml version="1.0"?><XMLResponse><XMLTrans><transType>07</transType><status>00</status><accountNum>32287391</accountNum><transNum>5</transNum></XMLTrans></XMLResponse> + ) + end + + def failed_refund_response + %( + <?xml version="1.0"?><XMLResponse><XMLTrans><transType>07</transType><status>51</status><accountNum>32287391</accountNum></XMLTrans></XMLResponse> + ) + end + + def successful_void_response + %( + <?xml version="1.0"?><XMLResponse><XMLTrans><transType>07</transType><status>00</status><accountNum>32287391</accountNum><transNum>44</transNum></XMLTrans></XMLResponse> + ) + end + + def failed_void_response + %( + <?xml version="1.0"?><XMLResponse><XMLTrans><transType>07</transType><status>51</status><accountNum>32287391</accountNum></XMLTrans></XMLResponse> + ) + end + + def successful_credit_response + %( + <?xml version="1.0"?><XMLResponse><XMLTrans><transType>35</transType><status>00</status><accountNum>32287391</accountNum><transNum>103</transNum></XMLTrans></XMLResponse> + ) + end + + def failed_credit_response + %( + <?xml version="1.0"?><XMLResponse><XMLTrans><transType>35</transType><status>48</status><accountNum>32287391</accountNum></XMLTrans></XMLResponse> + ) + end +end diff --git a/test/unit/gateways/psigate_test.rb b/test/unit/gateways/psigate_test.rb index 354ea829ec0..1bcb49689fb 100644 --- a/test/unit/gateways/psigate_test.rb +++ b/test/unit/gateways/psigate_test.rb @@ -9,7 +9,7 @@ def setup @amount = 100 @credit_card = credit_card('4111111111111111') - @options = { :order_id => "1", :billing_address => address } + @options = { :order_id => '1', :billing_address => address } end def test_successful_authorization @@ -38,23 +38,23 @@ def test_failed_purchase end def test_deprecated_credit - @gateway.expects(:ssl_post).with(anything, regexp_matches(/<OrderID>transaction_id<\//), anything).returns("") + @gateway.expects(:ssl_post).with(anything, regexp_matches(/<OrderID>transaction_id<\//), anything).returns('') @gateway.expects(:parse).returns({}) - assert_deprecation_warning(Gateway::CREDIT_DEPRECATION_MESSAGE, @gateway) do - @gateway.credit(@amount, "transaction_id", @options) + assert_deprecation_warning(Gateway::CREDIT_DEPRECATION_MESSAGE) do + @gateway.credit(@amount, 'transaction_id', @options) end end def test_refund - @gateway.expects(:ssl_post).with(anything, regexp_matches(/<OrderID>transaction_id<\//), anything).returns("") + @gateway.expects(:ssl_post).with(anything, regexp_matches(/<OrderID>transaction_id<\//), anything).returns('') @gateway.expects(:parse).returns({}) - @gateway.refund(@amount, "transaction_id", @options) + @gateway.refund(@amount, 'transaction_id', @options) end def test_void - @gateway.expects(:ssl_post).with(anything, regexp_matches(/<OrderID>transaction_id<\//), anything).returns("") + @gateway.expects(:ssl_post).with(anything, regexp_matches(/<OrderID>transaction_id<\//), anything).returns('') @gateway.expects(:parse).returns({}) - @gateway.void("transaction_id;1", @options) + @gateway.void('transaction_id;1', @options) end def test_amount_style @@ -87,6 +87,11 @@ def test_cvv_result assert_equal 'M', response.cvv_result['code'] end + def test_scrub + assert @gateway.supports_scrubbing? + assert_equal @gateway.scrub(pre_scrubbed), post_scrubbed + end + private def successful_authorization_response @@ -186,4 +191,18 @@ def xml_purchase_fixture def xml_capture_fixture '<?xml version="1.0"?><Order><OrderID>1004</OrderID><CardAction>2</CardAction><StoreID>teststore</StoreID><PaymentType>CC</PaymentType><SubTotal>20.00</SubTotal><Passphrase>psigate1234</Passphrase></Order>' end + + def pre_scrubbed + <<-PRE_SCRUBBED + <?xml version='1.0'?><Order><StoreID>teststore</StoreID><Passphrase>psigate1234</Passphrase><OrderID>1b7b4b36bf61e972a9e6a6be8fff15d8</OrderID><Email>jack@example.com</Email><PaymentType>CC</PaymentType><CardAction>0</CardAction><SubTotal>24.00</SubTotal><CardNumber>4242424242424242</CardNumber><CardExpMonth>09</CardExpMonth><CardExpYear>14</CardExpYear><CardIDCode>1</CardIDCode><CardIDNumber>123</CardIDNumber><Bname>Jim Smith</Bname><Baddress1>1234 My Street</Baddress1><Baddress2>Apt 1</Baddress2><Bcity>Ottawa</Bcity><Bprovince>ON</Bprovince><Bpostalcode>K1C2N6</Bpostalcode><Bcountry>CA</Bcountry><Bcompany>Widgets Inc</Bcompany></Order> + <CardNumber>......4242</CardNumber> + PRE_SCRUBBED + end + + def post_scrubbed + <<-POST_SCRUBBED + <?xml version='1.0'?><Order><StoreID>teststore</StoreID><Passphrase>[FILTERED]</Passphrase><OrderID>1b7b4b36bf61e972a9e6a6be8fff15d8</OrderID><Email>jack@example.com</Email><PaymentType>CC</PaymentType><CardAction>0</CardAction><SubTotal>24.00</SubTotal><CardNumber>[FILTERED]</CardNumber><CardExpMonth>09</CardExpMonth><CardExpYear>14</CardExpYear><CardIDCode>1</CardIDCode><CardIDNumber>[FILTERED]</CardIDNumber><Bname>Jim Smith</Bname><Baddress1>1234 My Street</Baddress1><Baddress2>Apt 1</Baddress2><Bcity>Ottawa</Bcity><Bprovince>ON</Bprovince><Bpostalcode>K1C2N6</Bpostalcode><Bcountry>CA</Bcountry><Bcompany>Widgets Inc</Bcompany></Order> + <CardNumber>[FILTERED]</CardNumber> + POST_SCRUBBED + end end diff --git a/test/unit/gateways/psl_card_test.rb b/test/unit/gateways/psl_card_test.rb index 9c6aca35b82..6a971d1815e 100644 --- a/test/unit/gateways/psl_card_test.rb +++ b/test/unit/gateways/psl_card_test.rb @@ -8,14 +8,14 @@ def setup :password => 'PASSWORD' ) - @credit_card = credit_card + @credit_card = credit_card @options = { :billing_address => address, :description => 'Store purchase' } @amount = 100 end - + def test_successful_authorization @gateway.expects(:ssl_post).returns(successful_purchase_response) assert response = @gateway.authorize(@amount, @credit_card, @options) @@ -34,15 +34,15 @@ def test_unsuccessful_request def test_supported_countries assert_equal ['GB'], PslCardGateway.supported_countries end - + def test_supported_card_types - assert_equal [ :visa, :master, :american_express, :diners_club, :jcb, :switch, :solo, :maestro ], PslCardGateway.supported_cardtypes + assert_equal [ :visa, :master, :american_express, :diners_club, :jcb, :maestro ], PslCardGateway.supported_cardtypes end - + def test_avs_result @gateway.expects(:ssl_post).returns(successful_purchase_response) - response = @gateway.purchase(@amount, @credit_card, @options) + response = @gateway.purchase(@amount, @credit_card, @options) assert_equal 'Y', response.avs_result['code'] end @@ -52,13 +52,14 @@ def test_cvv_result response = @gateway.purchase(@amount, @credit_card, @options) assert_equal 'M', response.cvv_result['code'] end - + private + def successful_purchase_response - "ResponseCode=00&Message=AUTHCODE:01256&CrossReference=08012522454901256086&First4=4543&Last4=9982&ExpMonth=12&ExpYear=2010&AVSCV2Check=ALL MATCH&Amount=1000&QAAddress=76 Roseby Avenue Manchester&QAPostcode=M63X 7TH&MerchantName=Merchant Name&QAName=John Smith" + 'ResponseCode=00&Message=AUTHCODE:01256&CrossReference=08012522454901256086&First4=4543&Last4=9982&ExpMonth=12&ExpYear=2010&AVSCV2Check=ALL MATCH&Amount=1000&QAAddress=76 Roseby Avenue Manchester&QAPostcode=M63X 7TH&MerchantName=Merchant Name&QAName=John Smith' end - + def unsuccessful_purchase_response - "ResponseCode=05&Message=CARD DECLINED&QAAddress=The Parkway Larches Approach Hull North Humberside&QAPostcode=HU7 9OP&MerchantName=Merchant Name&QAName=" + 'ResponseCode=05&Message=CARD DECLINED&QAAddress=The Parkway Larches Approach Hull North Humberside&QAPostcode=HU7 9OP&MerchantName=Merchant Name&QAName=' end -end \ No newline at end of file +end diff --git a/test/unit/gateways/qbms_test.rb b/test/unit/gateways/qbms_test.rb index 060ea37b578..894a3e0571d 100644 --- a/test/unit/gateways/qbms_test.rb +++ b/test/unit/gateways/qbms_test.rb @@ -5,8 +5,8 @@ def setup Base.mode = :test @gateway = QbmsGateway.new( - :login => "test", - :ticket => "abc123", + :login => 'test', + :ticket => 'abc123', :pem => 'PEM') @amount = 100 @@ -45,7 +45,7 @@ def test_truncated_address_is_sent with(anything, regexp_matches(/12345 Ridiculously Lengthy Roa\<.*445566778\</), anything). returns(charge_response) - options = { :billing_address => address.update(:address1 => "12345 Ridiculously Lengthy Road Name", :zip => '4455667788') } + options = { :billing_address => address.update(:address1 => '12345 Ridiculously Lengthy Road Name', :zip => '4455667788') } assert response = @gateway.purchase(@amount, @card, options) assert_success response end @@ -61,7 +61,7 @@ def test_partial_address_is_ok def test_successful_void @gateway.expects(:ssl_post).returns(void_response) - assert response = @gateway.void("x") + assert response = @gateway.void('x') assert_instance_of Response, response assert_success response end @@ -69,8 +69,8 @@ def test_successful_void def test_successful_deprecated_credit @gateway.expects(:ssl_post).returns(credit_response) - assert_deprecation_warning(Gateway::CREDIT_DEPRECATION_MESSAGE, @gateway) do - assert response = @gateway.credit(@amount, "x") + assert_deprecation_warning(Gateway::CREDIT_DEPRECATION_MESSAGE) do + assert response = @gateway.credit(@amount, 'x') assert_instance_of Response, response assert_success response end @@ -79,7 +79,7 @@ def test_successful_deprecated_credit def test_successful_refund @gateway.expects(:ssl_post).returns(credit_response) - assert response = @gateway.refund(@amount, "x") + assert response = @gateway.refund(@amount, 'x') assert_instance_of Response, response assert_success response end @@ -90,17 +90,17 @@ def test_avs_result assert_equal 'Y', response.avs_result['street_match'] assert_equal 'Y', response.avs_result['postal_match'] - @gateway.expects(:ssl_post).returns(authorization_response(:avs_street => "Fail")) + @gateway.expects(:ssl_post).returns(authorization_response(:avs_street => 'Fail')) assert response = @gateway.authorize(@amount, @card) assert_equal 'N', response.avs_result['street_match'] assert_equal 'Y', response.avs_result['postal_match'] - @gateway.expects(:ssl_post).returns(authorization_response(:avs_zip => "Fail")) + @gateway.expects(:ssl_post).returns(authorization_response(:avs_zip => 'Fail')) assert response = @gateway.authorize(@amount, @card) assert_equal 'Y', response.avs_result['street_match'] assert_equal 'N', response.avs_result['postal_match'] - @gateway.expects(:ssl_post).returns(authorization_response(:avs_street => "Fail", :avs_zip => "Fail")) + @gateway.expects(:ssl_post).returns(authorization_response(:avs_street => 'Fail', :avs_zip => 'Fail')) assert response = @gateway.authorize(@amount, @card) assert_equal 'N', response.avs_result['street_match'] assert_equal 'N', response.avs_result['postal_match'] @@ -111,11 +111,11 @@ def test_cvv_result assert response = @gateway.authorize(@amount, @card) assert_equal 'M', response.cvv_result['code'] - @gateway.expects(:ssl_post).returns(authorization_response(:card_security_code_match => "Fail")) + @gateway.expects(:ssl_post).returns(authorization_response(:card_security_code_match => 'Fail')) assert response = @gateway.authorize(@amount, @card) assert_equal 'N', response.cvv_result['code'] - @gateway.expects(:ssl_post).returns(authorization_response(:card_security_code_match => "NotAvailable")) + @gateway.expects(:ssl_post).returns(authorization_response(:card_security_code_match => 'NotAvailable')) assert response = @gateway.authorize(@amount, @card) assert_equal 'P', response.cvv_result['code'] end @@ -147,7 +147,7 @@ def test_failed_authorization def test_use_test_url_when_overwriting_with_test_option ActiveMerchant::Billing::Base.mode = :production - @gateway = QbmsGateway.new(:login => "test", :ticket => "abc123", :test => true) + @gateway = QbmsGateway.new(:login => 'test', :ticket => 'abc123', :test => true) @gateway.stubs(:parse).returns({}) @gateway.expects(:ssl_post).with(QbmsGateway.test_url, anything, anything).returns(authorization_response) @gateway.authorize(@amount, @card) @@ -155,10 +155,15 @@ def test_use_test_url_when_overwriting_with_test_option ActiveMerchant::Billing::Base.mode = :test end + def test_scrub + assert @gateway.supports_scrubbing? + assert_equal @gateway.scrub(pre_scrubbed), post_scrubbed + end + # helper methods start here def query_response(opts = {}) - wrap "MerchantAccountQuery", opts, <<-"XML" + wrap 'MerchantAccountQuery', opts, <<-"XML" <ConvenienceFees>0.0</ConvenienceFees> <CreditCardType>Visa</CreditCardType> <CreditCardType>MasterCard</CreditCardType> @@ -168,12 +173,12 @@ def query_response(opts = {}) def authorization_response(opts = {}) opts = { - :avs_street => "Pass", - :avs_zip => "Pass", - :card_security_code_match => "Pass", + :avs_street => 'Pass', + :avs_zip => 'Pass', + :card_security_code_match => 'Pass', }.merge(opts) - wrap "CustomerCreditCardAuth", opts, <<-"XML" + wrap 'CustomerCreditCardAuth', opts, <<-"XML" <CreditCardTransID>1000</CreditCardTransID> <AuthorizationCode>STRTYPE</AuthorizationCode> <AVSStreet>#{opts[:avs_street]}</AVSStreet> @@ -184,7 +189,7 @@ def authorization_response(opts = {}) end def capture_response(opts = {}) - wrap "CustomerCreditCardCapture", opts, <<-"XML" + wrap 'CustomerCreditCardCapture', opts, <<-"XML" <CreditCardTransID>1000</CreditCardTransID> <AuthorizationCode>STRTYPE</AuthorizationCode> <MerchantAccountNumber>STRTYPE</MerchantAccountNumber> @@ -195,12 +200,12 @@ def capture_response(opts = {}) def charge_response(opts = {}) opts = { - :avs_street => "Pass", - :avs_zip => "Pass", - :card_security_code_match => "Pass", + :avs_street => 'Pass', + :avs_zip => 'Pass', + :card_security_code_match => 'Pass', }.merge(opts) - wrap "CustomerCreditCardCharge", opts, <<-"XML" + wrap 'CustomerCreditCardCharge', opts, <<-"XML" <CreditCardTransID>1000</CreditCardTransID> <AuthorizationCode>STRTYPE</AuthorizationCode> <AVSStreet>#{opts[:avs_street]}</AVSStreet> @@ -213,14 +218,14 @@ def charge_response(opts = {}) end def void_response(opts = {}) - wrap "CustomerCreditCardTxnVoid", opts, <<-"XML" + wrap 'CustomerCreditCardTxnVoid', opts, <<-"XML" <CreditCardTransID>1000</CreditCardTransID> <ClientTransID>STRTYPE</ClientTransID> XML end def credit_response(opts = {}) - wrap "CustomerCreditCardTxnVoidOrRefund", opts, <<-"XML" + wrap 'CustomerCreditCardTxnVoidOrRefund', opts, <<-"XML" <CreditCardTransID>1000</CreditCardTransID> <VoidOrRefundTxnType>STRTYPE</VoidOrRefundTxnType> <MerchantAccountNumber>STRTYPE</MerchantAccountNumber> @@ -232,7 +237,7 @@ def credit_response(opts = {}) def wrap(type, opts, xml) opts = { :signon_status_code => 0, - :request_id => "x", + :request_id => 'x', :status_code => 0, }.merge(opts) @@ -254,4 +259,66 @@ def wrap(type, opts, xml) </QBMSXML> XML end + + def pre_scrubbed + <<-PRE_SCRUBBED + <?xml version="1.0" encoding="utf-8"?><?qbmsxml version="4.0"?><QBMSXML><SignonMsgsRq><SignonDesktopRq><ClientDateTime>2012-07-06T11:48:30-07:00</ClientDateTime><ApplicationLogin>subscriptions-test.spreedly.com</ApplicationLogin><ConnectionTicket>TGT-135-0exSkgC_I9tvKAxCwOE$Eg</ConnectionTicket></SignonDesktopRq></SignonMsgsRq><QBMSXMLMsgsRq><CustomerCreditCardChargeRq><TransRequestID>859e649c87f9ac698536</TransRequestID><CreditCardNumber>4111111111111111</CreditCardNumber><ExpirationMonth>9</ExpirationMonth><ExpirationYear>2013</ExpirationYear><IsECommerce>true</IsECommerce><Amount>1.00</Amount><NameOnCard>Longbob Longsen</NameOnCard><CreditCardAddress>1234 My Street</CreditCardAddress><CreditCardPostalCode>K1C2N6</CreditCardPostalCode><CardSecurityCode>123</CardSecurityCode></CustomerCreditCardChargeRq></QBMSXMLMsgsRq></QBMSXML> + <!DOCTYPE QBMSXML PUBLIC "-//INTUIT//DTD QBMSXML QBMS 4.0//EN" "http://webmerchantaccount.ptc.quickbooks.com/dtds/qbmsxml40.dtd"> + <QBMSXML> + <SignonMsgsRs> + <SignonDesktopRs statusCode="0" statusSeverity="INFO"> + <ServerDateTime>2012-07-06T18:48:31</ServerDateTime> + <SessionTicket>V1-110-Q31341600511142d1e4131:133159303</SessionTicket> + </SignonDesktopRs> + </SignonMsgsRs> + <QBMSXMLMsgsRs> + <CustomerCreditCardChargeRs statusCode="0" statusMessage="Status OK" statusSeverity="INFO"> + <CreditCardTransID>YY1002519111</CreditCardTransID> + <AuthorizationCode>135927</AuthorizationCode> + <AVSStreet>Pass</AVSStreet> + <AVSZip>Pass</AVSZip> + <CardSecurityCodeMatch>Pass</CardSecurityCodeMatch> + <MerchantAccountNumber>5247711053184054</MerchantAccountNumber> + <ReconBatchID>420120706 1Q11485247711053184054AUTO04</ReconBatchID> + <PaymentGroupingCode>5</PaymentGroupingCode> + <PaymentStatus>Completed</PaymentStatus> + <TxnAuthorizationTime>2012-07-06T18:48:31</TxnAuthorizationTime> + <TxnAuthorizationStamp>1341600511</TxnAuthorizationStamp> + <ClientTransID>q0b539f2</ClientTransID> + </CustomerCreditCardChargeRs> + </QBMSXMLMsgsRs> + </QBMSXML> + PRE_SCRUBBED + end + + def post_scrubbed + <<-POST_SCRUBBED + <?xml version="1.0" encoding="utf-8"?><?qbmsxml version="4.0"?><QBMSXML><SignonMsgsRq><SignonDesktopRq><ClientDateTime>2012-07-06T11:48:30-07:00</ClientDateTime><ApplicationLogin>subscriptions-test.spreedly.com</ApplicationLogin><ConnectionTicket>[FILTERED]</ConnectionTicket></SignonDesktopRq></SignonMsgsRq><QBMSXMLMsgsRq><CustomerCreditCardChargeRq><TransRequestID>859e649c87f9ac698536</TransRequestID><CreditCardNumber>[FILTERED]</CreditCardNumber><ExpirationMonth>9</ExpirationMonth><ExpirationYear>2013</ExpirationYear><IsECommerce>true</IsECommerce><Amount>1.00</Amount><NameOnCard>Longbob Longsen</NameOnCard><CreditCardAddress>1234 My Street</CreditCardAddress><CreditCardPostalCode>K1C2N6</CreditCardPostalCode><CardSecurityCode>[FILTERED]</CardSecurityCode></CustomerCreditCardChargeRq></QBMSXMLMsgsRq></QBMSXML> + <!DOCTYPE QBMSXML PUBLIC "-//INTUIT//DTD QBMSXML QBMS 4.0//EN" "http://webmerchantaccount.ptc.quickbooks.com/dtds/qbmsxml40.dtd"> + <QBMSXML> + <SignonMsgsRs> + <SignonDesktopRs statusCode="0" statusSeverity="INFO"> + <ServerDateTime>2012-07-06T18:48:31</ServerDateTime> + <SessionTicket>V1-110-Q31341600511142d1e4131:133159303</SessionTicket> + </SignonDesktopRs> + </SignonMsgsRs> + <QBMSXMLMsgsRs> + <CustomerCreditCardChargeRs statusCode="0" statusMessage="Status OK" statusSeverity="INFO"> + <CreditCardTransID>YY1002519111</CreditCardTransID> + <AuthorizationCode>135927</AuthorizationCode> + <AVSStreet>Pass</AVSStreet> + <AVSZip>Pass</AVSZip> + <CardSecurityCodeMatch>Pass</CardSecurityCodeMatch> + <MerchantAccountNumber>5247711053184054</MerchantAccountNumber> + <ReconBatchID>420120706 1Q11485247711053184054AUTO04</ReconBatchID> + <PaymentGroupingCode>5</PaymentGroupingCode> + <PaymentStatus>Completed</PaymentStatus> + <TxnAuthorizationTime>2012-07-06T18:48:31</TxnAuthorizationTime> + <TxnAuthorizationStamp>1341600511</TxnAuthorizationStamp> + <ClientTransID>q0b539f2</ClientTransID> + </CustomerCreditCardChargeRs> + </QBMSXMLMsgsRs> + </QBMSXML> + POST_SCRUBBED + end end diff --git a/test/unit/gateways/quantum_test.rb b/test/unit/gateways/quantum_test.rb index a1d771cd8b0..10bb11ccaf4 100644 --- a/test/unit/gateways/quantum_test.rb +++ b/test/unit/gateways/quantum_test.rb @@ -9,19 +9,19 @@ def setup @credit_card = credit_card @amount = 100 - - @options = { + + @options = { :billing_address => address, :description => 'Store Purchase' } end - + def test_successful_purchase @gateway.expects(:ssl_post).returns(successful_purchase_response) - + assert response = @gateway.purchase(@amount, @credit_card, @options) assert_success response - + # Replace with authorization number from the successful response assert_equal '2983691;2224', response.authorization assert response.test? @@ -29,14 +29,14 @@ def test_successful_purchase def test_unsuccessful_request @gateway.expects(:ssl_post).returns(failed_purchase_response) - + assert response = @gateway.purchase(@amount, @credit_card, @options) assert_failure response assert response.test? end - + private - + # Place raw successful response from gateway here def successful_purchase_response %(<QGWRequest> @@ -74,7 +74,7 @@ def successful_purchase_response </Result> </QGWRequest>) end - + # Place raw failed response from gateway here def failed_purchase_response %(<QGWRequest> diff --git a/test/unit/gateways/quickbooks_test.rb b/test/unit/gateways/quickbooks_test.rb new file mode 100644 index 00000000000..7d59f157ba5 --- /dev/null +++ b/test/unit/gateways/quickbooks_test.rb @@ -0,0 +1,397 @@ +require 'test_helper' + +class QuickBooksTest < Test::Unit::TestCase + include CommStub + + def setup + @gateway = QuickbooksGateway.new( + consumer_key: 'consumer_key', + consumer_secret: 'consumer_secret', + access_token: 'access_token', + token_secret: 'token_secret', + realm: 'realm_ID' + ) + + @credit_card = credit_card + @amount = 100 + + @options = { + order_id: '1', + billing_address: address, + description: 'Store Purchase' + } + + @authorization = 'ECZ7U0SO423E' + end + + def test_successful_purchase + @gateway.expects(:ssl_post).returns(successful_purchase_response) + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + + assert_equal 'EF1IQ9GGXS2D', response.authorization + assert response.test? + end + + def test_failed_purchase + @gateway.expects(:ssl_post).returns(failed_purchase_response) + + response = @gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_equal Gateway::STANDARD_ERROR_CODE[:processing_error], response.error_code + end + + def test_successful_authorize + @gateway.expects(:ssl_post).returns(successful_authorize_response) + response = @gateway.authorize(@amount, @credit_card, @options) + assert_success response + + assert_equal @authorization, response.authorization + assert response.test? + end + + def test_failed_authorize + @gateway.expects(:ssl_post).returns(failed_authorize_response) + + response = @gateway.authorize(@amount, @credit_card, @options) + assert_failure response + assert_equal Gateway::STANDARD_ERROR_CODE[:card_declined], response.error_code + end + + def test_successful_capture + @gateway.expects(:ssl_post).returns(successful_capture_response) + + response = @gateway.capture(@amount, @authorization) + assert_success response + end + + def test_failed_capture + @gateway.expects(:ssl_post).returns(failed_capture_response) + + response = @gateway.capture(@amount, @authorization) + assert_failure response + end + + def test_successful_refund + @gateway.expects(:ssl_post).returns(successful_refund_response) + + response = @gateway.refund(@amount, @authorization) + assert_success response + end + + def test_failed_refund + @gateway.expects(:ssl_post).returns(failed_refund_response) + + response = @gateway.refund(@amount, @authorization) + assert_failure response + end + + def test_successful_verify + response = stub_comms do + @gateway.verify(@credit_card) + end.respond_with(successful_authorize_response) + + assert_success response + end + + def test_failed_verify + response = stub_comms do + @gateway.verify(@credit_card, @options) + end.respond_with(failed_authorize_response) + + assert_failure response + assert_not_nil response.message + end + + def test_scrub + assert @gateway.supports_scrubbing? + assert_equal @gateway.send(:scrub, pre_scrubbed), post_scrubbed + end + + def test_scrub_with_small_json + assert_equal @gateway.scrub(pre_scrubbed_small_json), post_scrubbed_small_json + end + + def test_default_context + stub_comms do + @gateway.purchase(@amount, @credit_card, @options) + end.check_request do |_endpoint, data, _headers| + json = JSON.parse(data) + refute json.fetch('context').fetch('mobile') + assert json.fetch('context').fetch('isEcommerce') + end.respond_with(successful_purchase_response) + end + + private + + def pre_scrubbed_small_json + "intuit.com\\r\\nContent-Length: 258\\r\\n\\r\\n\"\n<- \"{\\\"amount\\\":\\\"34.50\\\",\\\"currency\\\":\\\"USD\\\",\\\"card\\\":{\\\"number\\\":\\\"4111111111111111\\\",\\\"expMonth\\\":\\\"09\\\",\\\"expYear\\\":2016,\\\"cvc\\\":\\\"123\\\",\\\"name\\\":\\\"Bob Bobson\\\",\\\"address\\\":{\\\"streetAddress\\\":null,\\\"city\\\":\\\"Los Santos\\\",\\\"region\\\":\\\"CA\\\",\\\"country\\\":\\\"US\\\",\\\"postalCode\\\":\\\"90210\\\"}},\\\"capture\\\":\\\"true\\\"}\"\n-> \"HTTP/1.1 201 Created\\r\\n\"\n-> \"Date: Tue, 03 Mar 2015 20:00:35 GMT\\r\\n\"\n-> \"Content-Type: " + end + + def post_scrubbed_small_json + "intuit.com\\r\\nContent-Length: 258\\r\\n\\r\\n\"\n<- \"{\\\"amount\\\":\\\"34.50\\\",\\\"currency\\\":\\\"USD\\\",\\\"card\\\":{\\\"number\\\":\\\"[FILTERED]\\\",\\\"expMonth\\\":\\\"09\\\",\\\"expYear\\\":2016,\\\"cvc\\\":\\\"[FILTERED]\\\",\\\"name\\\":\\\"Bob Bobson\\\",\\\"address\\\":{\\\"streetAddress\\\":null,\\\"city\\\":\\\"Los Santos\\\",\\\"region\\\":\\\"CA\\\",\\\"country\\\":\\\"US\\\",\\\"postalCode\\\":\\\"90210\\\"}},\\\"capture\\\":\\\"true\\\"}\"\n-> \"HTTP/1.1 201 Created\\r\\n\"\n-> \"Date: Tue, 03 Mar 2015 20:00:35 GMT\\r\\n\"\n-> \"Content-Type: " + end + + def successful_purchase_response + <<-RESPONSE + { + "created": "2014-11-27T22:09:01Z", + "status": "CAPTURED", + "amount": "20.00", + "currency": "USD", + "card": { + "number": "xxxxxxxxxxxx1111", + "name": "alicks profit", + "address": { + "city": "xxxxxxxx", + "region": "xx", + "country": "xx", + "streetAddress": "xxxxxxxxxxxxx", + "postalCode": "xxxxx" + }, + "expMonth": "01", + "expYear": "2021" + }, + "id": "EF1IQ9GGXS2D", + "authCode": "664472", + "capture": "true" + } + RESPONSE + end + + def failed_purchase_response + <<-RESPONSE + { + "errors":[{ + "code": "PMT-4000", + "type": "invalid_request", + "message": "the request to process this transaction has been declined.", + "detail": "Amount.", + "infoLink": "https://developer.intuit.com/v2/docs?redirectID=PayErrors" + }] + } + RESPONSE + end + + def successful_authorize_response + <<-RESPONSE + { + "created": "2014-11-27T22:17:22Z", + "status": "AUTHORIZED", + "amount": "2000.00", + "currency": "USD", + "card": { + "number": "xxxxxxxxxxxx4242", + "name": "alicks profit", + "address": { + "city": "xxxxxxxx", + "region": "xx", + "country": "xx", + "streetAddress": "xxxxxxxxxxxxx", + "postalCode": "xxxxx" + }, + "expMonth": "01", + "expYear": "2021" + }, + "capture": false, + "id": "ECZ7U0SO423E", + "authCode": "279714" + } + RESPONSE + end + + def failed_authorize_response + <<-RESPONSE + { + "errors":[{ + "code": "PMT-5000", + "type": "invalid_request", + "message": "he request to process this transaction has been declined.", + "detail": "Amount.", + "infoLink": "https://developer.intuit.com/v2/docs?redirectID=PayErrors" + }] + } + RESPONSE + end + + def successful_capture_response + <<-RESPONSE + { + "created": "2014-12-17T22:39:21Z", + "status": "CAPTURED", + "amount": "10.55", + "currency": "USD", + "card": { + "number": "xxxxxxxxxxxx4444", + "cvc": "xxx", + "name": "emulate=0", + "address": { + "city": "xxxxxxxxx", + "region": "xx", + "country": "xx", + "streetAddress": "xxxxxxxxxxxxx", + "postalCode": "xxxxx" + }, + "expMonth": "02", + "expYear": "2020" + }, + "id": "ELFWEU8LS00K", + "authCode": "537265" + } + RESPONSE + end + + def failed_capture_response + <<-RESPONSE + { + "errors":[{ + "code": "PMT-5000", + "type": "invalid_request", + "message": "he request to process this transaction has been declined.", + "detail": "Amount.", + "infoLink": "https://developer.intuit.com/v2/docs?redirectID=PayErrors" + }] + } + RESPONSE + end + + def successful_refund_response + <<-RESPONSE + { + "created": "2014-09-23T01:49:12Z", + "status": "ISSUED", + "amount": "5.00", + "description": "first refund", + "id": "EMU891209421", + "context": { + "tax": "0.00", + "recurring": false, + "deviceInfo": { + "id": "", + "type": "", + "longitude": "", + "latitude": "", + "phoneNumber": "", + "macAddress": "", + "ipAddress": "" + } + } + } + RESPONSE + end + + def failed_refund_response + <<-RESPONSE + { + "errors":[{ + "code": "PMT-5000", + "type": "invalid_request", + "message": "he request to process this transaction has been declined.", + "detail": "Amount.", + "infoLink": "https://developer.intuit.com/v2/docs?redirectID=PayErrors" + }] + } + RESPONSE + end + + def successful_void_response + <<-RESPONSE + { + "created": "2014-09-23T01:49:12Z", + "status": "ISSUED", + "amount": "5.00", + "description": "first refund", + "id": "EMU891209421", + "context": { + "tax": "0.00", + "recurring": false, + "deviceInfo": { + "id": "", + "type": "", + "longitude": "", + "latitude": "", + "phoneNumber": "", + "macAddress": "", + "ipAddress": "" + } + } + } + RESPONSE + end + + def failed_void_response + <<-RESPONSE + { + "errors":[{ + "code": "PMT-5000", + "type": "invalid_request", + "message": "he request to process this transaction has been declined.", + "detail": "Amount.", + "infoLink": "https://developer.intuit.com/v2/docs?redirectID=PayErrors" + }] + } + RESPONSE + end + + def pre_scrubbed + <<-PRE_SCRUBBED + opening connection to sandbox.api.intuit.com:443... + opened + starting SSL for sandbox.api.intuit.com:443... + SSL established + <- "POST /quickbooks/v4/payments/charges HTTP/1.1\r\nContent-Type: application/json\r\nRequest-Id: f8b0ce95a6e5fe249b52b23112443221\r\nAuthorization: OAuth realm=\"1292767175\", oauth_consumer_key=\"qyprdSPSxCNr5XLx0Px6g4h43zRcl6\", oauth_nonce=\"aZgGttabmZeU8ST6OjhUEMYWg7HLoyxZirBLJZVeA\", oauth_signature=\"iltPw94HHT7QCuEPTJ4RnfwY%2FzU%3D\", oauth_signature_method=\"HMAC-SHA1\", oauth_timestamp=\"1418937070\", oauth_token=\"qyprdDJJpRXRsoLDQMqaDk68c4ovXjMMVL2Wzs9RI0VNb52B\", oauth_version=\"1.0\"\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: sandbox.api.intuit.com\r\nContent-Length: 265\r\n\r\n" + <- "{\"amount\":\"1.00\",\"currency\":\"USD\",\"card\":{\"number\":\"4000100011112224\",\"expMonth\":\"09\",\"expYear\":2015,\"cvc\":\"123\",\"name\":\"Longbob Longsen\",\"address\":{\"streetAddress\":\"1234 My Street\",\"city\":\"Ottawa\",\"region\":\"CA\",\"country\":\"US\",\"postalCode\":90210}},\"capture\":\"true\"}" + -> "HTTP/1.1 201 Created\r\n" + -> "Date: Thu, 18 Dec 2014 21:11:11 GMT\r\n" + -> "Content-Type: application/json;charset=utf-8\r\n" + -> "Access-Control-Allow-Origin: *\r\n" + -> "Access-Control-Allow-Methods: DELETE, POST, GET, OPTIONS\r\n" + -> "Access-Control-Allow-Headers: realmid, realm_id, intuit_realm_id, Origin, X-Requested-With, Content-Type, Accept, intuit_tid, intuittid, Authorization, company_id, company-id, intuit_company_id, request_id, request-id\r\n" + -> "intuit_tid: gw-f4c34b4f-54ec-4350-b44f-c46d4b2d003d\r\n" + -> "Connection: close\r\n" + -> "Transfer-Encoding: chunked\r\n" + -> "\r\n" + -> "168\r\n" + reading 360 bytes... + -> "{\"created\":\"2014-12-18T21:11:12Z\",\"status\":\"CAPTURED\",\"amount\":\"1.00\",\"currency\":\"USD\",\"card\":{\"number\":\"xxxxxxxxxxxx2224\",\"cvc\":\"xxx\",\"name\":\"Longbob Longsen\",\"address\":{\"city\":\"xxxxxx\",\"region\":\"xx\",\"country\":\"xx\",\"streetAddress\":\"xxxxxxxxxxxxxx\",\"postalCode\":\"xxxxx\"},\"expMonth\":\"09\",\"expYear\":\"2015\"},\"capture\":true,\"id\":\"EE228DLEWTNE\",\"authCode\":\"586868\"}" + read 360 bytes + reading 2 bytes... + -> "\r\n" + read 2 bytes + -> "0\r\n" + -> "\r\n" + Conn close + PRE_SCRUBBED + end + + def post_scrubbed + <<-POST_SCRUBBED + opening connection to sandbox.api.intuit.com:443... + opened + starting SSL for sandbox.api.intuit.com:443... + SSL established + <- "POST /quickbooks/v4/payments/charges HTTP/1.1\r\nContent-Type: application/json\r\nRequest-Id: f8b0ce95a6e5fe249b52b23112443221\r\nAuthorization: OAuth realm=\"[FILTERED]\", oauth_consumer_key=\"[FILTERED]\", oauth_nonce=\"[FILTERED]\", oauth_signature=\"[FILTERED]\", oauth_signature_method=\"HMAC-SHA1\", oauth_timestamp=\"1418937070\", oauth_token=\"[FILTERED]\", oauth_version=\"1.0\"\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: sandbox.api.intuit.com\r\nContent-Length: 265\r\n\r\n" + <- "{\"amount\":\"1.00\",\"currency\":\"USD\",\"card\":{\"number\":\"[FILTERED]\",\"expMonth\":\"09\",\"expYear\":2015,\"cvc\":\"[FILTERED]\",\"name\":\"Longbob Longsen\",\"address\":{\"streetAddress\":\"1234 My Street\",\"city\":\"Ottawa\",\"region\":\"CA\",\"country\":\"US\",\"postalCode\":90210}},\"capture\":\"true\"}" + -> "HTTP/1.1 201 Created\r\n" + -> "Date: Thu, 18 Dec 2014 21:11:11 GMT\r\n" + -> "Content-Type: application/json;charset=utf-8\r\n" + -> "Access-Control-Allow-Origin: *\r\n" + -> "Access-Control-Allow-Methods: DELETE, POST, GET, OPTIONS\r\n" + -> "Access-Control-Allow-Headers: realmid, realm_id, intuit_realm_id, Origin, X-Requested-With, Content-Type, Accept, intuit_tid, intuittid, Authorization, company_id, company-id, intuit_company_id, request_id, request-id\r\n" + -> "intuit_tid: gw-f4c34b4f-54ec-4350-b44f-c46d4b2d003d\r\n" + -> "Connection: close\r\n" + -> "Transfer-Encoding: chunked\r\n" + -> "\r\n" + -> "168\r\n" + reading 360 bytes... + -> "{\"created\":\"2014-12-18T21:11:12Z\",\"status\":\"CAPTURED\",\"amount\":\"1.00\",\"currency\":\"USD\",\"card\":{\"number\":\"xxxxxxxxxxxx2224\",\"cvc\":\"xxx\",\"name\":\"Longbob Longsen\",\"address\":{\"city\":\"xxxxxx\",\"region\":\"xx\",\"country\":\"xx\",\"streetAddress\":\"xxxxxxxxxxxxxx\",\"postalCode\":\"xxxxx\"},\"expMonth\":\"09\",\"expYear\":\"2015\"},\"capture\":true,\"id\":\"EE228DLEWTNE\",\"authCode\":\"586868\"}" + read 360 bytes + reading 2 bytes... + -> "\r\n" + read 2 bytes + -> "0\r\n" + -> "\r\n" + Conn close + POST_SCRUBBED + end +end diff --git a/test/unit/gateways/quickpay_test.rb b/test/unit/gateways/quickpay_test.rb index d04e84948b0..f9f2e2d28cd 100644 --- a/test/unit/gateways/quickpay_test.rb +++ b/test/unit/gateways/quickpay_test.rb @@ -1,124 +1,21 @@ require 'test_helper' class QuickpayTest < Test::Unit::TestCase - def setup - @gateway = QuickpayGateway.new( - :login => 'LOGIN', - :password => 'PASSWORD' - ) - @credit_card = credit_card('4242424242424242') - @amount = 100 - @options = { :order_id => '1', :billing_address => address } - end - - def test_successful_purchase - @gateway.expects(:ssl_post).returns(successful_authorization_response, successful_capture_response) - - assert response = @gateway.purchase(@amount, @credit_card, @options) - assert_success response - assert_equal '2865261', response.authorization - assert response.test? - end - - def test_successful_authorization - @gateway.expects(:ssl_post).returns(successful_authorization_response) - - assert response = @gateway.authorize(@amount, @credit_card, @options) - assert_success response - assert_equal '2865261', response.authorization - assert response.test? + def test_error_without_login_option + assert_raise ArgumentError do + QuickpayGateway.new + end end - def test_failed_authorization - @gateway.expects(:ssl_post).returns(failed_authorization_response) - - assert response = @gateway.authorize(@amount, @credit_card, @options) - assert_failure response - assert_equal 'Missing/error in card verification data', response.message - assert response.test? - end - - def test_parsing_response_with_errors - @gateway.expects(:ssl_post).returns(error_response) - - response = @gateway.purchase(@amount, @credit_card, @options) - assert_failure response - assert_equal '008', response.params['qpstat'] - assert_equal 'Missing/error in cardnumber, Missing/error in expirationdate, Missing/error in card verification data, Missing/error in amount, Missing/error in ordernum, Missing/error in currency', response.params['qpstatmsg'] - assert_equal 'Missing/error in cardnumber, Missing/error in expirationdate, Missing/error in card verification data, Missing/error in amount, Missing/error in ordernum, Missing/error in currency', response.message - end - - def test_merchant_error - @gateway.expects(:ssl_post).returns(merchant_error) - - response = @gateway.purchase(@amount, @credit_card, @options) - assert_failure response - assert_equal response.message, 'Missing/error in merchant' + def test_v4to7 + gateway = QuickpayGateway.new(:login => 50000000, :password => 'secret') + assert_instance_of QuickpayV4to7Gateway, gateway end - - def test_parsing_successful_response - @gateway.expects(:ssl_post).returns(successful_authorization_response) - - response = @gateway.authorize(@amount, @credit_card, @options) - assert_success response - assert_equal 'OK', response.message - - assert_equal '2865261', response.authorization - assert_equal '000', response.params['qpstat'] - assert_equal '000', response.params['pbsstat'] - assert_equal '2865261', response.params['transaction'] - assert_equal '070425223705', response.params['time'] - assert_equal '104680', response.params['ordernum'] - assert_equal 'cody@example.com', response.params['merchantemail'] - assert_equal 'Visa', response.params['cardtype'] - assert_equal @amount.to_s, response.params['amount'] - assert_equal 'OK', response.params['qpstatmsg'] - assert_equal 'Shopify', response.params['merchant'] - assert_equal '1110', response.params['msgtype'] - assert_equal 'USD', response.params['currency'] - end - - def test_supported_countries - assert_equal ['DK', 'SE'], QuickpayGateway.supported_countries - end - - def test_supported_card_types - assert_equal [ :dankort, :forbrugsforeningen, :visa, :master, :american_express, :diners_club, :jcb, :maestro ], QuickpayGateway.supported_cardtypes + def test_v10 + gateway = QuickpayGateway.new(:login => 100, :api_key => 'APIKEY') + assert_instance_of QuickpayV10Gateway, gateway end - def test_add_testmode_does_not_add_testmode_if_transaction_id_present - post_hash = {:transaction => "12345"} - @gateway.send(:add_testmode, post_hash) - assert_equal nil, post_hash[:testmode] - end - - def test_add_testmode_adds_a_testmode_param_if_transaction_id_not_present - post_hash = {} - @gateway.send(:add_testmode, post_hash) - assert_equal '1', post_hash[:testmode] - end - - private - - def error_response - "<?xml version='1.0' encoding='ISO-8859-1'?><response><qpstat>008</qpstat><qpstatmsg>Missing/error in cardnumber, Missing/error in expirationdate, Missing/error in card verification data, Missing/error in amount, Missing/error in ordernum, Missing/error in currency</qpstatmsg></response>" - end - - def merchant_error - "<?xml version='1.0' encoding='ISO-8859-1'?><response><qpstat>008</qpstat><qpstatmsg>Missing/error in merchant</qpstatmsg></response>" - end - - def successful_authorization_response - "<?xml version='1.0' encoding='ISO-8859-1'?><response><qpstat>000</qpstat><transaction>2865261</transaction><time>070425223705</time><ordernum>104680</ordernum><merchantemail>cody@example.com</merchantemail><pbsstat>000</pbsstat><cardtype>Visa</cardtype><amount>100</amount><qpstatmsg>OK</qpstatmsg><merchant>Shopify</merchant><msgtype>1110</msgtype><currency>USD</currency></response>" - end - - def successful_capture_response - '<?xml version="1.0" encoding="ISO-8859-1"?><response><msgtype>1230</msgtype><amount>100</amount><time>080107061755</time><pbsstat>000</pbsstat><qpstat>000</qpstat><qpstatmsg>OK</qpstatmsg><currency>DKK</currency><ordernum>4820346075804536193</ordernum><transaction>2865261</transaction><merchant>Shopify</merchant><merchantemail>pixels@jadedpixel.com</merchantemail></response>' - end - - def failed_authorization_response - '<?xml version="1.0" encoding="ISO-8859-1"?><response><qpstat>008</qpstat><qpstatmsg>Missing/error in card verification data</qpstatmsg></response>' - end end diff --git a/test/unit/gateways/quickpay_v10_test.rb b/test/unit/gateways/quickpay_v10_test.rb new file mode 100644 index 00000000000..f07f13cc03e --- /dev/null +++ b/test/unit/gateways/quickpay_v10_test.rb @@ -0,0 +1,311 @@ +require 'test_helper' + +class QuickpayV10Test < Test::Unit::TestCase + include CommStub + + def setup + @gateway = QuickpayV10Gateway.new(:api_key => 'APIKEY') + @credit_card = credit_card('4242424242424242') + @amount = 100 + @options = { :order_id => '1', :billing_address => address, :customer_ip => '1.1.1.1' } + end + + def parse(body) + JSON.parse(body) + end + + def test_unsuccessful_payment + @gateway.expects(:ssl_post).returns(failed_payment_response) + response = @gateway.authorize(@amount, @credit_card, @options) + assert response.authorization.blank? + assert_failure response + end + + def test_successful_purchase + stub_comms do + response = @gateway.purchase(@amount, @credit_card, @options) + assert response + assert_success response + assert_equal '1145', response.authorization + assert response.test? + end.check_request do |endpoint, data, headers| + parsed = parse(data) + if parsed['order_id'] + assert_match %r{/payments}, endpoint + elsif !parsed['auto_capture'].nil? + assert_match %r{/payments/\d+/authorize}, endpoint + assert_equal false, parsed['auto_capture'] + else + assert_match %r{/payments/\d+/capture}, endpoint + end + end.respond_with(successful_payment_response, successful_authorization_response) + end + + def test_successful_authorization + stub_comms do + assert response = @gateway.authorize(@amount, @credit_card, @options) + assert_success response + assert_equal '1145', response.authorization + assert response.test? + end.check_request do |endpoint, data, headers| + parsed_data = parse(data) + if parsed_data['order_id'] + assert_match %r{/payments}, endpoint + assert_match '1.1.1.1', @options[:customer_ip] + else + assert_match %r{/payments/\d+/authorize}, endpoint + end + end.respond_with(successful_payment_response, successful_authorization_response) + end + + def test_successful_authorization_with_3ds + options = @options.merge( + three_d_secure: { + cavv: '1234', + eci: '1234', + xid: '1234' + } + ) + stub_comms do + assert response = @gateway.authorize(@amount, @credit_card, options) + assert_success response + assert_equal '1145', response.authorization + assert response.test? + end.check_request do |endpoint, data, headers| + parsed_data = parse(data) + if parsed_data['order_id'] + assert_match %r{/payments}, endpoint + assert_match '1.1.1.1', options[:customer_ip] + else + assert_match %r{/payments/\d+/authorize}, endpoint + end + end.respond_with(successful_payment_response, successful_authorization_response) + end + + def test_successful_void + stub_comms do + assert response = @gateway.void(1145) + assert_success response + assert response.test? + end.check_request do |endpoint, data, headers| + assert_match %r{/payments/1145/cancel}, endpoint + end.respond_with({'id' => 1145}.to_json) + end + + def test_failed_authorization + stub_comms do + assert response = @gateway.authorize(@amount, @credit_card, @options) + assert_failure response + assert_equal 'Validation error', response.message + assert response.test? + end.respond_with(successful_payment_response, failed_authorization_response) + end + + def test_parsing_response_with_errors + stub_comms do + assert response = @gateway.authorize(@amount, @credit_card, @options) + assert_failure response + assert_equal 'is not valid', response.params['errors']['id'][0] + assert response.test? + end.respond_with(successful_payment_response, failed_authorization_response) + end + + def test_successful_store + stub_comms do + assert response = @gateway.store(@credit_card, @options) + assert_success response + assert response.test? + end.check_request do |endpoint, data, headers| + assert_match %r{/card}, endpoint + end.respond_with(successful_store_response, successful_sauthorize_response) + end + + def test_successful_unstore + stub_comms do + assert response = @gateway.unstore('123') + assert_success response + assert response.test? + end.check_request do |endpoint, data, headers| + assert_match %r{/cards/\d+/cancel}, endpoint + end.respond_with({'id' => '123'}.to_json) + end + + def test_successful_verify + response = stub_comms do + @gateway.verify(@credit_card, @options) + end.respond_with(successful_authorization_response) + assert_success response + assert_equal 'OK', response.message + end + + def test_failed_verify + response = stub_comms do + @gateway.verify(@credit_card, @options) + end.respond_with(failed_authorization_response, {'id' => 1145}.to_json) + assert_failure response + assert_equal 'Validation error', response.message + end + + def test_supported_countries + klass = @gateway.class + assert_equal ['DE', 'DK', 'ES', 'FI', 'FR', 'FO', 'GB', 'IS', 'NO', 'SE'], klass.supported_countries + end + + def test_supported_card_types + klass = @gateway.class + assert_equal [:dankort, :forbrugsforeningen, :visa, :master, :american_express, :diners_club, :jcb, :maestro ], klass.supported_cardtypes + end + + def test_successful_capture + @gateway.expects(:ssl_post).returns(successful_capture_response) + assert response = @gateway.capture(100, 1124) + assert_success response + end + + def test_transcript_scrubbing + assert_equal scrubbed_transcript, @gateway.scrub(transcript) + end + + private + + def successful_payment_response + { + 'id' =>1145, + 'order_id' =>'310f59c57a', + 'accepted' =>false, + 'test_mode' =>false, + 'branding_id' =>nil, + 'variables' =>{}, + 'acquirer' =>nil, + 'operations' =>[], + 'metadata' =>{}, + 'created_at' =>'2015-03-30T16:56:17Z', + 'balance' =>0, + 'currency' =>'DKK' + }.to_json + end + + def successful_authorization_response + { + 'id' => 1145, + 'order_id' => '310f59c57a', + 'accepted' => false, + 'test_mode' => true, + 'branding_id' => nil, + 'variables' => {}, + 'acquirer' => 'clearhaus', + 'operations' => [], + 'metadata' => { + 'type' =>'card', + 'brand' =>'quickpay-test-card', + 'last4' =>'0008', + 'exp_month' =>9, + 'exp_year' =>2016, + 'country' =>'DK', + 'is_3d_secure' =>false, + 'customer_ip' =>nil, + 'customer_country' =>nil + }, + 'created_at' => '2015-03-30T16:56:17Z', + 'balance' => 0, + 'currency' => 'DKK' + }.to_json + end + + def successful_capture_response + { + 'id' =>1145, + 'order_id' =>'310f59c57a', + 'accepted' =>true, + 'test_mode' =>true, + 'branding_id' =>nil, + 'variables' =>{}, + 'acquirer' =>'clearhaus', + 'operations' =>[], + 'metadata' =>{'type'=>'card', 'brand'=>'quickpay-test-card', 'last4'=>'0008', 'exp_month'=>9, 'exp_year'=>2016, 'country'=>'DK', 'is_3d_secure'=>false, 'customer_ip'=>nil, 'customer_country'=>nil}, + 'created_at' =>'2015-03-30T16:56:17Z', + 'balance' =>0, + 'currency' =>'DKK' + }.to_json + end + + def succesful_refund_response + { + 'id' =>1145, + 'order_id' =>'310f59c57a', + 'accepted' =>true, + 'test_mode' =>true, + 'branding_id' =>nil, + 'variables' =>{}, + 'acquirer' =>'clearhaus', + 'operations' =>[], + 'metadata'=>{ + 'type' =>'card', + 'brand' =>'quickpay-test-card', + 'last4' =>'0008', + 'exp_month' =>9, + 'exp_year' =>2016, + 'country' =>'DK', + 'is_3d_secure' =>false, + 'customer_ip' =>nil, + 'customer_country' =>nil + }, + 'created_at' =>'2015-03-30T16:56:17Z', + 'balance' =>100, + 'currency' =>'DKK' + }.to_json + end + + def failed_authorization_response + { + 'message' => 'Validation error', + 'errors' => { + 'id' => ['is not valid'] + } + }.to_json + end + + def failed_payment_response + { + 'message' => 'Validation error', + 'errors' => { + 'currency' => ['must be three uppercase letters'] + }, + 'error_code' => nil + }.to_json + end + + def successful_store_response + { + 'id' => 834, + 'order_id' => '310affr' + }.to_json + end + + def successful_sauthorize_response + { + 'id' => 834, + 'order_id' => '310affr' + }.to_json + end + + def expected_expiration_date + '%02d%02d' % [@credit_card.year.to_s[2..4], @credit_card.month] + end + + def transcript + %q( + POST /payments/7488279/authorize?synchronized HTTP/1.1\r\nContent-Type: application/json\r\nAuthorization: Basic OjAzNTA4ZTc3OTFiYTZjOWQwZTY4MzA3MTZlNjUwZjM1YzQzNDJjNGIzNTc2NzIzYWQ1NTZlMjM2Y2E0Yzc3ODg=\r\nUser-Agent: Quickpay-v10 ActiveMerchantBindings/1.52.0\r\nAccept: application/json\r\nAccept-Version: v10\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nConnection: close\r\nHost: api.quickpay.net\r\nContent-Length: 136\r\n\r\n + {\"amount\":\"100\",\"card\":{\"number\":\"1000000000000008\",\"cvd\":\"123\",\"expiration\":\"1609\",\"issued_to\":\"Longbob Longsen\"},\"auto_capture\":false} + D, [2015-08-17T11:44:26.710099 #75027] DEBUG -- : {"amount":"100","card":{"number":"1000000000000008","cvd":"123","expiration":"1609","issued_to":"Longbob Longsen"},"auto_capture":false} + ) + end + + def scrubbed_transcript + %q( + POST /payments/7488279/authorize?synchronized HTTP/1.1\r\nContent-Type: application/json\r\nAuthorization: Basic [FILTERED]=\r\nUser-Agent: Quickpay-v10 ActiveMerchantBindings/1.52.0\r\nAccept: application/json\r\nAccept-Version: v10\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nConnection: close\r\nHost: api.quickpay.net\r\nContent-Length: 136\r\n\r\n + {\"amount\":\"100\",\"card\":{\"number\":\"[FILTERED]\",\"cvd\":\"[FILTERED]\",\"expiration\":\"1609\",\"issued_to\":\"Longbob Longsen\"},\"auto_capture\":false} + D, [2015-08-17T11:44:26.710099 #75027] DEBUG -- : {"amount":"100","card":{"number":"[FILTERED]","cvd":"[FILTERED]","expiration":"1609","issued_to":"Longbob Longsen"},"auto_capture":false} + ) + end +end diff --git a/test/unit/gateways/quickpay_v4to7_test.rb b/test/unit/gateways/quickpay_v4to7_test.rb new file mode 100644 index 00000000000..74e395980ca --- /dev/null +++ b/test/unit/gateways/quickpay_v4to7_test.rb @@ -0,0 +1,232 @@ +require 'test_helper' + +class QuickpayV4to7Test < Test::Unit::TestCase + include CommStub + + def merchant_id + '80000000000' + end + + def setup + @gateway = QuickpayGateway.new( + :login => merchant_id, + :password => 'PASSWORD', + :version => 7 + ) + + @credit_card = credit_card('4242424242424242') + @amount = 100 + @options = { :order_id => '1', :billing_address => address } + end + + def test_successful_purchase + @gateway.expects(:ssl_post).returns(successful_authorization_response, successful_capture_response) + + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal '2865261', response.authorization + assert response.test? + end + + def test_successful_authorization + @gateway.expects(:ssl_post).returns(successful_authorization_response) + + assert response = @gateway.authorize(@amount, @credit_card, @options) + assert_success response + assert_equal '2865261', response.authorization + assert response.test? + end + + def test_successful_store_for_v6 + @gateway = QuickpayGateway.new( + :login => merchant_id, + :password => 'PASSWORD', + :version => 6 + ) + @gateway.expects(:generate_check_hash).returns(mock_md5_hash) + + response = stub_comms do + @gateway.store(@credit_card, {:order_id => 'fa73664073e23597bbdd', :description => 'Storing Card'}) + end.check_request do |endpoint, data, headers| + assert_equal(expected_store_parameters_v6, CGI::parse(data)) + end.respond_with(successful_store_response_v6) + + assert response + assert_instance_of Response, response + assert_success response + assert_equal '80760015', response.authorization + end + + def test_successful_store_for_v7 + @gateway.expects(:generate_check_hash).returns(mock_md5_hash) + + response = stub_comms do + @gateway.store(@credit_card, {:order_id => 'ed7546cb4ceb8f017ea4', :description => 'Storing Card'}) + end.check_request do |endpoint, data, headers| + assert_equal(expected_store_parameters_v7, CGI::parse(data)) + end.respond_with(successful_store_response_v7) + + assert response + assert_instance_of Response, response + assert_success response + assert_equal '80758573', response.authorization + end + + def test_failed_authorization + @gateway.expects(:ssl_post).returns(failed_authorization_response) + + assert response = @gateway.authorize(@amount, @credit_card, @options) + assert_failure response + assert_equal 'Missing/error in card verification data', response.message + assert response.test? + end + + def test_parsing_response_with_errors + @gateway.expects(:ssl_post).returns(error_response) + + response = @gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_equal '008', response.params['qpstat'] + assert_equal 'Missing/error in cardnumber, Missing/error in expirationdate, Missing/error in card verification data, Missing/error in amount, Missing/error in ordernum, Missing/error in currency', response.params['qpstatmsg'] + assert_equal 'Missing/error in cardnumber, Missing/error in expirationdate, Missing/error in card verification data, Missing/error in amount, Missing/error in ordernum, Missing/error in currency', response.message + end + + def test_merchant_error + @gateway.expects(:ssl_post).returns(merchant_error) + + response = @gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_equal response.message, 'Missing/error in merchant' + end + + def test_parsing_successful_response + @gateway.expects(:ssl_post).returns(successful_authorization_response) + + response = @gateway.authorize(@amount, @credit_card, @options) + + assert_success response + assert_equal 'OK', response.message + + assert_equal '2865261', response.authorization + assert_equal '000', response.params['qpstat'] + assert_equal '000', response.params['pbsstat'] + assert_equal '2865261', response.params['transaction'] + assert_equal '070425223705', response.params['time'] + assert_equal '104680', response.params['ordernum'] + assert_equal 'cody@example.com', response.params['merchantemail'] + assert_equal 'Visa', response.params['cardtype'] + assert_equal @amount.to_s, response.params['amount'] + assert_equal 'OK', response.params['qpstatmsg'] + assert_equal 'Shopify', response.params['merchant'] + assert_equal '1110', response.params['msgtype'] + assert_equal 'USD', response.params['currency'] + end + + def test_supported_countries + klass = @gateway.class + assert_equal ['DE', 'DK', 'ES', 'FI', 'FR', 'FO', 'GB', 'IS', 'NO', 'SE'], klass.supported_countries + end + + def test_supported_card_types + klass = @gateway.class + assert_equal [ :dankort, :forbrugsforeningen, :visa, :master, :american_express, :diners_club, :jcb, :maestro ], klass.supported_cardtypes + end + + def test_add_testmode_does_not_add_testmode_if_transaction_id_present + post_hash = {:transaction => '12345'} + @gateway.send(:add_testmode, post_hash) + assert_equal nil, post_hash[:testmode] + end + + def test_add_testmode_adds_a_testmode_param_if_transaction_id_not_present + post_hash = {} + @gateway.send(:add_testmode, post_hash) + assert_equal '1', post_hash[:testmode] + end + + def test_finalize_is_disabled_by_default + stub_comms(@gateway, :ssl_request) do + @gateway.capture(@amount, '12345') + end.check_request do |method, endpoint, data, headers| + assert data =~ /finalize=0/ + end.respond_with(successful_capture_response) + end + + def test_finalize_is_enabled + stub_comms(@gateway, :ssl_request) do + @gateway.capture(@amount, '12345', finalize: true) + end.check_request do |method, endpoint, data, headers| + assert data =~ /finalize=1/ + end.respond_with(successful_capture_response) + end + + private + + def error_response + "<?xml version='1.0' encoding='ISO-8859-1'?><response><qpstat>008</qpstat><qpstatmsg>Missing/error in cardnumber, Missing/error in expirationdate, Missing/error in card verification data, Missing/error in amount, Missing/error in ordernum, Missing/error in currency</qpstatmsg></response>" + end + + def merchant_error + "<?xml version='1.0' encoding='ISO-8859-1'?><response><qpstat>008</qpstat><qpstatmsg>Missing/error in merchant</qpstatmsg></response>" + end + + def successful_authorization_response + "<?xml version='1.0' encoding='ISO-8859-1'?><response><qpstat>000</qpstat><transaction>2865261</transaction><time>070425223705</time><ordernum>104680</ordernum><merchantemail>cody@example.com</merchantemail><pbsstat>000</pbsstat><cardtype>Visa</cardtype><amount>100</amount><qpstatmsg>OK</qpstatmsg><merchant>Shopify</merchant><msgtype>1110</msgtype><currency>USD</currency></response>" + end + + def successful_capture_response + '<?xml version="1.0" encoding="ISO-8859-1"?><response><msgtype>1230</msgtype><amount>100</amount><time>080107061755</time><pbsstat>000</pbsstat><qpstat>000</qpstat><qpstatmsg>OK</qpstatmsg><currency>DKK</currency><ordernum>4820346075804536193</ordernum><transaction>2865261</transaction><merchant>Shopify</merchant><merchantemail>pixels@jadedpixel.com</merchantemail></response>' + end + + def successful_store_response_v6 + '<?xml version="1.0" encoding="UTF-8"?><response><msgtype>subscribe</msgtype><ordernumber>fa73664073e23597bbdd</ordernumber><amount>0</amount><currency>n/a</currency><time>2014-02-26T21:25:47+01:00</time><state>9</state><qpstat>000</qpstat><qpstatmsg>OK</qpstatmsg><chstat>000</chstat><chstatmsg>OK</chstatmsg><merchant>Test Merchant</merchant><merchantemail>merchant@example.com</merchantemail><transaction>80760015</transaction><cardtype>visa</cardtype><cardnumber>XXXXXXXXXXXX4242</cardnumber><cardexpire>1509</cardexpire><splitpayment/><fraudprobability/><fraudremarks/><fraudreport/><md5check>mock_hash</md5check></response>' + end + + def successful_store_response_v7 + '<?xml version="1.0" encoding="UTF-8"?><response><msgtype>subscribe</msgtype><ordernumber>ed7546cb4ceb8f017ea4</ordernumber><amount>0</amount><currency>DKK</currency><time>2014-02-26T21:04:00+01:00</time><state>9</state><qpstat>000</qpstat><qpstatmsg>OK</qpstatmsg><chstat>000</chstat><chstatmsg>OK</chstatmsg><merchant>Test Merchant</merchant><merchantemail>merchant@example.com</merchantemail><transaction>80758573</transaction><cardtype>visa</cardtype><cardnumber>XXXXXXXXXXXX4242</cardnumber><cardexpire>1509</cardexpire><splitpayment/><acquirer>nets</acquirer><fraudprobability/><fraudremarks/><fraudreport/><md5check>mock_hash</md5check></response>' + end + + def failed_authorization_response + '<?xml version="1.0" encoding="ISO-8859-1"?><response><qpstat>008</qpstat><qpstatmsg>Missing/error in card verification data</qpstatmsg></response>' + end + + def expected_store_parameters_v6 + { + 'cardnumber'=>['4242424242424242'], + 'cvd'=>['123'], + 'expirationdate'=>[expected_expiration_date], + 'ordernumber'=>['fa73664073e23597bbdd'], + 'description'=>['Storing Card'], + 'testmode'=>['1'], + 'protocol'=>['6'], + 'msgtype'=>['subscribe'], + 'merchant'=>[merchant_id], + 'md5check'=>[mock_md5_hash] + } + end + + def expected_store_parameters_v7 + { + 'amount'=>['0'], + 'currency'=>['DKK'], + 'cardnumber'=>['4242424242424242'], + 'cvd'=>['123'], + 'expirationdate'=>[expected_expiration_date], + 'ordernumber'=>['ed7546cb4ceb8f017ea4'], + 'description'=>['Storing Card'], + 'testmode'=>['1'], + 'protocol'=>['7'], + 'msgtype'=>['subscribe'], + 'merchant'=>[merchant_id], + 'md5check'=>[mock_md5_hash] + } + end + + def expected_expiration_date + '%02d%02d' % [@credit_card.year.to_s[2..4], @credit_card.month] + end + + def mock_md5_hash + 'mock_hash' + end +end diff --git a/test/unit/gateways/qvalent_test.rb b/test/unit/gateways/qvalent_test.rb new file mode 100644 index 00000000000..6bff79ebb82 --- /dev/null +++ b/test/unit/gateways/qvalent_test.rb @@ -0,0 +1,412 @@ +require 'test_helper' + +class QvalentTest < Test::Unit::TestCase + include CommStub + + def setup + @gateway = QvalentGateway.new( + username: 'username', + password: 'password', + merchant: 'merchant', + pem: 'pem', + pem_password: 'pempassword' + ) + + @credit_card = credit_card + @amount = 100 + end + + def test_successful_purchase + response = stub_comms do + @gateway.purchase(@amount, @credit_card) + end.respond_with(successful_purchase_response) + + assert_success response + + assert_equal '5d53a33d960c46d00f5dc061947d998c', response.authorization + assert_equal 'M', response.cvv_result['code'] + assert response.test? + end + + def test_failed_purchase + response = stub_comms do + @gateway.purchase(@amount, @credit_card) + end.respond_with(failed_purchase_response) + + assert_failure response + assert_equal 'Invalid card number (no such number)', response.message + assert_equal Gateway::STANDARD_ERROR_CODE[:invalid_number], response.error_code + assert response.test? + end + + def test_successful_authorize + response = stub_comms do + @gateway.authorize(@amount, @credit_card) + end.respond_with(successful_authorize_response) + + assert_success response + + assert_equal '21c74c8f08bca415b5373022e6194f74', response.authorization + assert response.test? + end + + def test_failed_authorize + response = stub_comms do + @gateway.authorize(@amount, @credit_card) + end.respond_with(failed_authorize_response) + + assert_failure response + assert_equal 'Expired card', response.message + assert response.test? + end + + def test_successful_capture + response = stub_comms do + @gateway.capture(@amount, 'auth') + end.respond_with(successful_capture_response) + + assert_success response + + assert_equal 'fedf9ea13afa46872592d62e8cdcb0a3', response.authorization + assert response.test? + end + + def test_failed_capture + response = stub_comms do + @gateway.capture(@amount, '') + end.respond_with(failed_capture_response) + + assert_failure response + assert_equal 'Invalid Parameters - order.authId: Required field', response.message + assert response.test? + end + + def test_successful_refund + response = stub_comms do + @gateway.purchase(@amount, @credit_card) + end.respond_with(successful_purchase_response) + + assert_success response + assert_equal '5d53a33d960c46d00f5dc061947d998c', response.authorization + + refund = stub_comms do + @gateway.refund(@amount, response.authorization) + end.check_request do |endpoint, data, headers| + assert_match %r{5d53a33d960c46d00f5dc061947d998c}, data + end.respond_with(successful_refund_response) + + assert_success refund + end + + def test_failed_refund + response = stub_comms do + @gateway.refund(nil, '') + end.respond_with(failed_refund_response) + + assert_failure response + end + + def test_successful_credit + response = stub_comms do + @gateway.credit(@amount, @credit_card) + end.respond_with(successful_credit_response) + + assert_success response + end + + def test_failed_credit + response = stub_comms do + @gateway.credit(@amount, @credit_card) + end.respond_with(failed_credit_response) + + assert_failure response + end + + def test_successful_void + response = stub_comms do + @gateway.void('auth') + end.respond_with(successful_void_response) + + assert_success response + + assert_equal '67686b64b544335815002fd85704c8a1', response.authorization + assert response.test? + end + + def test_failed_void + response = stub_comms do + @gateway.void('') + end.respond_with(failed_void_response) + + assert_failure response + assert_equal 'Invalid Parameters - customer.originalOrderNumber: Required field', response.message + assert response.test? + end + + def test_successful_store + response = stub_comms do + @gateway.store(@credit_card) + end.respond_with(successful_store_response) + + assert_success response + + assert_equal 'RSL-20887450', response.authorization + assert_equal 'Succeeded', response.message + assert response.test? + end + + def test_failed_store + response = stub_comms do + @gateway.store(@credit_card) + end.respond_with(failed_store_response) + + assert_failure response + assert_equal 'Invalid card number (no such number)', response.message + assert_equal Gateway::STANDARD_ERROR_CODE[:invalid_number], response.error_code + assert response.test? + end + + def test_empty_response_fails + response = stub_comms do + @gateway.purchase(@amount, @credit_card) + end.respond_with(empty_purchase_response) + + assert_failure response + assert_equal 'Unable to read error message', response.message + end + + def test_3d_secure_fields + response = stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, @credit_card, {xid: '123', cavv: '456', eci: '5'}) + end.check_request do |method, endpoint, data, headers| + assert_match(/xid=123/, data) + assert_match(/cavv=456/, data) + assert_match(/ECI=5/, data) + end.respond_with(successful_purchase_response) + + assert_success response + end + + def test_stored_credential_fields_initial + response = stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, @credit_card, {stored_credential: {initial_transaction: true, reason_type: 'unscheduled', initiator: 'merchant'}}) + end.check_request do |method, endpoint, data, headers| + assert_match(/posEntryMode=MANUAL/, data) + assert_match(/storedCredentialUsage=INITIAL_STORAGE/, data) + assert_match(/ECI=SSL/, data) + end.respond_with(successful_purchase_response) + + assert_success response + end + + def test_stored_credential_fields_recurring + response = stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, @credit_card, {stored_credential: {reason_type: 'recurring', initiator: 'merchant', network_transaction_id: '7890'}}) + end.check_request do |method, endpoint, data, headers| + assert_match(/posEntryMode=STORED_CREDENTIAL/, data) + assert_match(/storedCredentialUsage=RECURRING/, data) + assert_match(/ECI=REC/, data) + assert_match(/authTraceId=7890/, data) + end.respond_with(successful_purchase_response) + + assert_success response + end + + def test_stored_credential_fields_unscheduled + response = stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, @credit_card, {stored_credential: {reason_type: 'unscheduled', initiator: 'merchant', network_transaction_id: '7890'}}) + end.check_request do |method, endpoint, data, headers| + assert_match(/posEntryMode=STORED_CREDENTIAL/, data) + assert_match(/storedCredentialUsage=UNSCHEDULED/, data) + assert_match(/ECI=MTO/, data) + assert_match(/authTraceId=7890/, data) + end.respond_with(successful_purchase_response) + + assert_success response + end + + def test_stored_credential_fields_cardholder_initiated + response = stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, @credit_card, {stored_credential: {reason_type: 'unscheduled', initiator: 'cardholder', network_transaction_id: '7890'}}) + end.check_request do |method, endpoint, data, headers| + assert_match(/posEntryMode=STORED_CREDENTIAL/, data) + refute_match(/storedCredentialUsage/, data) + assert_match(/ECI=MTO/, data) + assert_match(/authTraceId=7890/, data) + end.respond_with(successful_purchase_response) + + assert_success response + end + + def test_stored_credential_fields_mastercard + @credit_card.brand = 'master' + response = stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, @credit_card, {stored_credential: {reason_type: 'recurring', initiator: 'merchant', network_transaction_id: '7890'}}) + end.check_request do |method, endpoint, data, headers| + assert_match(/posEntryMode=STORED_CREDENTIAL/, data) + refute_match(/storedCredentialUsage/, data) + assert_match(/ECI=REC/, data) + assert_match(/authTraceId=7890/, data) + end.respond_with(successful_purchase_response) + + assert_success response + end + + def test_cvv_result + response = stub_comms do + @gateway.purchase(@amount, @credit_card) + end.respond_with(mapped_cvv_response) + + assert_success response + assert_equal 'D', response.cvv_result['code'] + end + + def test_transcript_scrubbing + assert_equal scrubbed_transcript, @gateway.scrub(transcript) + end + + private + + def successful_purchase_response + %( + response.summaryCode=0\r\nresponse.responseCode=08\r\nresponse.text=Honour with identification\r\nresponse.referenceNo=723907124\r\nresponse.orderNumber=5d53a33d960c46d00f5dc061947d998c\r\nresponse.RRN=723907124 \r\nresponse.settlementDate=20150228\r\nresponse.transactionDate=28-FEB-2015 09:34:15\r\nresponse.cardSchemeName=VISA\r\nresponse.creditGroup=VI/BC/MC\r\nresponse.previousTxn=0\r\nresponse.cvnResponse=M +\r\nresponse.end\r\n + ) + end + + def failed_purchase_response + %( + response.summaryCode=1\r\nresponse.responseCode=14\r\nresponse.text=Invalid card number (no such number)\r\nresponse.referenceNo=723907125\r\nresponse.orderNumber=b6e50802b764df4ca3e25fbd581e13d2\r\nresponse.settlementDate=20150228\r\nresponse.cardSchemeName=VISA\r\nresponse.creditGroup=VI/BC/MC\r\nresponse.previousTxn=0\r\nresponse.end\r\n + ) + end + + def successful_authorize_response + %( + response.summaryCode=0\r\nresponse.responseCode=08\r\nresponse.text=Honour with identification\r\nresponse.referenceNo=731560096\r\nresponse.orderNumber=21c74c8f08bca415b5373022e6194f74\r\nresponse.RRN=731560096 \r\nresponse.settlementDate=20170314\r\nresponse.transactionDate=14-MAR-2017 05:41:44\r\nresponse.cardSchemeName=VISA\r\nresponse.creditGroup=VI/BC/MC\r\nresponse.previousTxn=0\r\nresponse.authId=C3JVDS\r\nresponse.end\r\n + ) + end + + def failed_authorize_response + %( + response.summaryCode=1\r\nresponse.responseCode=54\r\nresponse.text=Expired card\r\nresponse.referenceNo=731560142\r\nresponse.orderNumber=d48cb6104266ed1a51647576d8948c57\r\nresponse.RRN=731560142 \r\nresponse.settlementDate=20170314\r\nresponse.transactionDate=14-MAR-2017 05:45:18\r\nresponse.cardSchemeName=VISA\r\nresponse.creditGroup=VI/BC/MC\r\nresponse.previousTxn=0\r\nresponse.end\r\n + ) + end + + def successful_capture_response + %( + response.summaryCode=0\r\nresponse.responseCode=00\r\nresponse.text=Approved or completed successfully\r\nresponse.referenceNo=731560097\r\nresponse.orderNumber=fedf9ea13afa46872592d62e8cdcb0a3\r\nresponse.RRN=731560097\r\nresponse.settlementDate=20170314\r\nresponse.transactionDate=14-MAR-2017 05:45:52\r\nresponse.cardSchemeName=VISA\r\nresponse.creditGroup=VI/BC/MC\r\nresponse.previousTxn=0\r\nresponse.end\r\n + ) + end + + def failed_capture_response + %( + response.summaryCode=3\r\nresponse.responseCode=QA\r\nresponse.text=Invalid Parameters - order.authId: Required field\r\nresponse.previousTxn=0\r\nresponse.end\r\n + ) + end + + def successful_void_response + %( + response.summaryCode=0\r\nresponse.responseCode=00\r\nresponse.text=Approved or completed successfully\r\nresponse.referenceNo=731560098\r\nresponse.orderNumber=67686b64b544335815002fd85704c8a1\r\nresponse.settlementDate=20170314\r\nresponse.cardSchemeName=VISA\r\nresponse.creditGroup=VI/BC/MC\r\nresponse.previousTxn=0\r\nresponse.end\r\n + ) + end + + def failed_void_response + %( + response.summaryCode=3\r\nresponse.responseCode=QA\r\nresponse.text=Invalid Parameters - customer.originalOrderNumber: Required field\r\nresponse.previousTxn=0\r\nresponse.end\r\n + ) + end + + def successful_refund_response + %( + response.summaryCode=0\r\nresponse.responseCode=08\r\nresponse.text=Honour with identification\r\nresponse.referenceNo=723907127\r\nresponse.orderNumber=f1a65bfe-f95b-4e06-b800-6d3b3a771238\r\nresponse.RRN=723907127 \r\nresponse.settlementDate=20150228\r\nresponse.transactionDate=28-FEB-2015 09:37:20\r\nresponse.cardSchemeName=VISA\r\nresponse.creditGroup=VI/BC/MC\r\nresponse.previousTxn=0\r\nresponse.end\r\n + ) + end + + def failed_refund_response + %( + response.summaryCode=1\r\nresponse.responseCode=14\r\nresponse.text=Invalid card number (no such number) - card.PAN: Required field\r\nresponse.previousTxn=0\r\nresponse.end\r\n + ) + end + + def successful_credit_response + %( + response.summaryCode=0\r\nresponse.responseCode=08\r\nresponse.text=Honour with identification\r\nresponse.referenceNo=732344591\r\nresponse.orderNumber=f365d21f7f5a1a5fe0eb994f144858e2\r\nresponse.RRN=732344591 \r\nresponse.settlementDate=20170817\r\nresponse.transactionDate=17-AUG-2017 01:19:34\r\nresponse.cardSchemeName=VISA\r\nresponse.creditGroup=VI/BC/MC\r\nresponse.previousTxn=0\r\nresponse.traceCode=799500\r\nresponse.end\r\n + ) + end + + def failed_credit_response + %( + response.summaryCode=1\r\nresponse.responseCode=14\r\nresponse.text=Invalid card number (no such number)\r\nresponse.referenceNo=732344705\r\nresponse.orderNumber=3baab91d5642a34292375a8932cde85f\r\nresponse.settlementDate=20170817\r\nresponse.cardSchemeName=VISA\r\nresponse.creditGroup=VI/BC/MC\r\nresponse.previousTxn=0\r\nresponse.end\r\n + ) + end + + def successful_store_response + %( + response.summaryCode=0\r\nresponse.responseCode=00\r\nresponse.text=Approved or completed successfully\r\nresponse.cardSchemeName=VISA\r\nresponse.creditGroup=VI/BC/MC\r\nresponse.accountAlias=400010...224\r\nresponse.preregistrationCode=RSL-20887450\r\nresponse.customerReferenceNumber=RSL-20887450\r\nresponse.previousTxn=0\r\nresponse.end\r\n + ) + end + + def failed_store_response + %( + response.summaryCode=1\r\nresponse.responseCode=14\r\nresponse.text=Invalid card number (no such number)\r\nresponse.cardSchemeName=VISA\r\nresponse.creditGroup=VI/BC/MC\r\nresponse.previousTxn=0\r\nresponse.end\r\n + ) + end + + def empty_purchase_response + %( + ) + end + + def mapped_cvv_response + %( + response.summaryCode=0\r\nresponse.responseCode=08\r\nresponse.text=Honour with identification\r\nresponse.referenceNo=723907124\r\nresponse.orderNumber=5d53a33d960c46d00f5dc061947d998c\r\nresponse.RRN=723907124 \r\nresponse.settlementDate=20150228\r\nresponse.transactionDate=28-FEB-2015 09:34:15\r\nresponse.cardSchemeName=VISA\r\nresponse.creditGroup=VI/BC/MC\r\nresponse.previousTxn=0\r\nresponse.cvnResponse=S +\r\nresponse.end\r\n + ) + end + + def transcript + %( +opening connection to ccapi.client.support.qvalent.com:443... +opened +starting SSL for ccapi.client.support.qvalent.com:443... +SSL established +<- "POST /post/CreditCardAPIReceiver HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: ccapi.client.support.qvalent.com\r\nContent-Length: 321\r\n\r\n" +<- "card.CVN=123&card.PAN=4000100011112224&card.cardHolderName=Longbob+Longsen&card.currency=AUD&card.expiryMonth=09&card.expiryYear=16&customer.merchant=24436057&customer.orderNumber=0de136e8dbc1018ee060bffe2812b52a&customer.password=QRSLTEST&customer.username=QRSL&order.ECI=&order.amount=100&order.type=capture&message.end" +-> "HTTP/1.1 200 OK\r\n" +-> "X-Server-Shutdown: false\r\n" +-> "Content-Type: text/plain;charset=ISO-8859-1\r\n" +-> "Content-Length: 386\r\n" +-> "Date: Fri, 27 Feb 2015 22:00:04 GMT\r\n" +-> "Connection: close\r\n" +-> "Set-Cookie: TSb51a02=6c9aed20dc1a52dc4564c052d36cd28c05d8566e98b85ab254f0e8e4; Path=/\r\n" +-> "\r\n" +reading 386 bytes... +-> "response.summaryCode=0\r\nresponse.responseCode=08\r\nresponse.text=Honour with identification\r\nresponse.referenceNo=723907122\r\nresponse.orderNumber=0de136e8dbc1018ee060bffe2812b52a\r\nresponse.RRN=723907122 \r\nresponse.settlementDate=20150228\r\nresponse.transactionDate=28-FEB-2015 09:00:04\r\nresponse.cardSchemeName=VISA\r\nresponse.creditGroup=VI/BC/MC\r\nresponse.previousTxn=0\r\nresponse.end\r\n" +read 386 bytes +Conn close + ) + end + + def scrubbed_transcript + %( +opening connection to ccapi.client.support.qvalent.com:443... +opened +starting SSL for ccapi.client.support.qvalent.com:443... +SSL established +<- "POST /post/CreditCardAPIReceiver HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: ccapi.client.support.qvalent.com\r\nContent-Length: 321\r\n\r\n" +<- "card.CVN=[FILTERED]&card.PAN=[FILTERED]&card.cardHolderName=Longbob+Longsen&card.currency=AUD&card.expiryMonth=09&card.expiryYear=16&customer.merchant=24436057&customer.orderNumber=0de136e8dbc1018ee060bffe2812b52a&customer.password=[FILTERED]&customer.username=QRSL&order.ECI=&order.amount=100&order.type=capture&message.end" +-> "HTTP/1.1 200 OK\r\n" +-> "X-Server-Shutdown: false\r\n" +-> "Content-Type: text/plain;charset=ISO-8859-1\r\n" +-> "Content-Length: 386\r\n" +-> "Date: Fri, 27 Feb 2015 22:00:04 GMT\r\n" +-> "Connection: close\r\n" +-> "Set-Cookie: TSb51a02=6c9aed20dc1a52dc4564c052d36cd28c05d8566e98b85ab254f0e8e4; Path=/\r\n" +-> "\r\n" +reading 386 bytes... +-> "response.summaryCode=0\r\nresponse.responseCode=08\r\nresponse.text=Honour with identification\r\nresponse.referenceNo=723907122\r\nresponse.orderNumber=0de136e8dbc1018ee060bffe2812b52a\r\nresponse.RRN=723907122 \r\nresponse.settlementDate=20150228\r\nresponse.transactionDate=28-FEB-2015 09:00:04\r\nresponse.cardSchemeName=VISA\r\nresponse.creditGroup=VI/BC/MC\r\nresponse.previousTxn=0\r\nresponse.end\r\n" +read 386 bytes +Conn close + ) + end +end diff --git a/test/unit/gateways/realex_test.rb b/test/unit/gateways/realex_test.rb index f06bab3c29e..a672d03ec73 100644 --- a/test/unit/gateways/realex_test.rb +++ b/test/unit/gateways/realex_test.rb @@ -1,10 +1,10 @@ require 'test_helper' -require 'digest/sha1' class RealexTest < Test::Unit::TestCase class ActiveMerchant::Billing::RealexGateway # For the purposes of testing, lets redefine some protected methods as public. - public :build_purchase_or_authorization_request, :build_refund_request, :build_void_request, :build_capture_request + public :build_purchase_or_authorization_request, :build_refund_request, :build_void_request, + :build_capture_request, :build_verify_request, :build_credit_request end def setup @@ -12,6 +12,7 @@ def setup @password = 'your_secret' @account = 'your_account' @rebate_secret = 'your_rebate_secret' + @refund_secret = 'your_refund_secret' @gateway = RealexGateway.new( :login => @login, @@ -20,8 +21,8 @@ def setup ) @gateway_with_account = RealexGateway.new( - :login => @merchant_id, - :password => @secret, + :login => @login, + :password => @password, :account => 'bill_web_cengal' ) @@ -50,12 +51,49 @@ def setup @amount = 100 end + def test_initialize_sets_refund_and_credit_hashes + refund_secret = 'refund' + rebate_secret = 'rebate' + + gateway = RealexGateway.new( + login: @login, + password: @password, + rebate_secret: rebate_secret, + refund_secret: refund_secret + ) + + assert gateway.options[:refund_hash] == Digest::SHA1.hexdigest(rebate_secret) + assert gateway.options[:credit_hash] == Digest::SHA1.hexdigest(refund_secret) + end + + def test_initialize_with_nil_refund_and_rebate_secrets + gateway = RealexGateway.new( + login: @login, + password: @password, + rebate_secret: nil, + refund_secret: nil + ) + + assert_false gateway.options.key?(:refund_hash) + assert_false gateway.options.key?(:credit_hash) + end + + def test_initialize_without_refund_and_rebate_secrets + gateway = RealexGateway.new( + login: @login, + password: @password + ) + + assert_false gateway.options.key?(:refund_hash) + assert_false gateway.options.key?(:credit_hash) + end + def test_hash gateway = RealexGateway.new( :login => 'thestore', :password => 'mysecret' ) - Time.stubs(:now).returns(Time.parse("2001-04-03 12:32:45")) + Time.stubs(:now).returns(Time.new(2001, 4, 3, 12, 32, 45)) gateway.expects(:ssl_post).with(anything, regexp_matches(/9af7064afd307c9f988e8dfc271f9257f1fc02f6/)).returns(successful_purchase_response) gateway.purchase(29900, credit_card('5105105105105100'), :order_id => 'ORD453-11') end @@ -88,19 +126,22 @@ def test_unsuccessful_refund assert_failure @gateway.refund(@amount, '1234;1234;1234') end - def test_deprecated_credit - @gateway.expects(:ssl_post).returns(successful_refund_response) - assert_deprecation_warning(Gateway::CREDIT_DEPRECATION_MESSAGE, @gateway) do - assert_success @gateway.credit(@amount, '1234;1234;1234') - end + def test_successful_credit + @gateway.expects(:ssl_post).returns(successful_credit_response) + assert_success @gateway.credit(@amount, @credit_card, @options) + end + + def test_unsuccessful_credit + @gateway.expects(:ssl_post).returns(unsuccessful_credit_response) + assert_failure @gateway.credit(@amount, @credit_card, @options) end def test_supported_countries - assert_equal ['IE', 'GB', "FR", "BE", "NL", "LU", "IT"], RealexGateway.supported_countries + assert_equal ['IE', 'GB', 'FR', 'BE', 'NL', 'LU', 'IT', 'US', 'CA', 'ES'], RealexGateway.supported_countries end def test_supported_card_types - assert_equal [ :visa, :master, :american_express, :diners_club, :switch, :solo, :laser ], RealexGateway.supported_cardtypes + assert_equal [ :visa, :master, :american_express, :diners_club ], RealexGateway.supported_cardtypes end def test_avs_result_not_supported @@ -134,14 +175,15 @@ def test_capture_xml <request timestamp="20090824160201" type="settle"> <merchantid>your_merchant_id</merchantid> <account>your_account</account> + <amount>100</amount> <orderid>1</orderid> <pasref>4321</pasref> <authcode>1234</authcode> - <sha1hash>4132600f1dc70333b943fc292bd0ca7d8e722f6e</sha1hash> + <sha1hash>ef0a6c485452f3f94aff336fa90c6c62993056ca</sha1hash> </request> SRC - assert_xml_equal valid_capture_xml, @gateway.build_capture_request('1;4321;1234', {}) + assert_xml_equal valid_capture_xml, @gateway.build_capture_request(@amount, '1;4321;1234', {}) end def test_purchase_xml @@ -193,6 +235,35 @@ def test_void_xml assert_xml_equal valid_void_request_xml, @gateway.build_void_request('1;4321;1234', {}) end + def test_verify_xml + options = { + :order_id => '1' + } + @gateway.expects(:new_timestamp).returns('20181026114304') + + valid_verify_request_xml = <<-SRC +<request timestamp="20181026114304" type="otb"> + <merchantid>your_merchant_id</merchantid> + <account>your_account</account> + <orderid>1</orderid> + <card> + <number>4263971921001307</number> + <expdate>0808</expdate> + <chname>Longbob Longsen</chname> + <type>VISA</type> + <issueno></issueno> + <cvn> + <number></number> + <presind></presind> + </cvn> + </card> + <sha1hash>d53aebf1eaee4c3ff4c30f83f27b80ce99ba5644</sha1hash> +</request> +SRC + + assert_xml_equal valid_verify_request_xml, @gateway.build_verify_request(@credit_card, options) + end + def test_auth_xml options = { :order_id => '1' @@ -242,7 +313,6 @@ def test_refund_xml SRC assert_xml_equal valid_refund_request_xml, @gateway.build_refund_request(@amount, '1;4321;1234', {}) - end def test_refund_with_rebate_secret_xml @@ -265,7 +335,69 @@ def test_refund_with_rebate_secret_xml SRC assert_xml_equal valid_refund_request_xml, gateway.build_refund_request(@amount, '1;4321;1234', {}) + end + + def test_credit_xml + options = { + :order_id => '1' + } + + @gateway.expects(:new_timestamp).returns('20190717161006') + + valid_credit_request_xml = <<-SRC + <request timestamp="20190717161006" type="credit"> + <merchantid>your_merchant_id</merchantid> + <account>your_account</account> + <orderid>1</orderid> + <amount currency="EUR">100</amount> + <card> + <number>4263971921001307</number> + <expdate>0808</expdate> + <chname>Longbob Longsen</chname> + <type>VISA</type> + <issueno></issueno> + <cvn> + <number></number> + <presind></presind> + </cvn> + </card> + <autosettle flag="1"/> + <sha1hash>73ff566dcfc3a73bebf1a2d387316162111f030e</sha1hash> +</request> +SRC + + assert_xml_equal valid_credit_request_xml, @gateway.build_credit_request(@amount, @credit_card, options) + end + + def test_credit_with_refund_secret_xml + gateway = RealexGateway.new(:login => @login, :password => @password, :account => @account, :refund_secret => @refund_secret) + + gateway.expects(:new_timestamp).returns('20190717161006') + + valid_credit_request_xml = <<-SRC +<request timestamp="20190717161006" type="credit"> + <merchantid>your_merchant_id</merchantid> + <account>your_account</account> + <orderid>1</orderid> + <amount currency="EUR">100</amount> + <card> + <number>4263971921001307</number> + <expdate>0808</expdate> + <chname>Longbob Longsen</chname> + <type>VISA</type> + <issueno></issueno> + <cvn> + <number></number> + <presind></presind> + </cvn> + </card> + <refundhash>bbc192c6eac0132a039c23eae8550a22907c6796</refundhash> + <autosettle flag="1"/> + <sha1hash>73ff566dcfc3a73bebf1a2d387316162111f030e</sha1hash> +</request> +SRC + assert_xml_equal valid_credit_request_xml, gateway.build_credit_request(@amount, @credit_card, @options) end def test_auth_with_address @@ -283,11 +415,10 @@ def test_auth_with_address assert_instance_of Response, response assert_success response assert response.test? - end def test_zip_in_shipping_address - @gateway.expects(:ssl_post).with(anything, regexp_matches(/<code>BT28XX<\/code>/)).returns(successful_purchase_response) + @gateway.expects(:ssl_post).with(anything, regexp_matches(/<code>28\|123<\/code>/)).returns(successful_purchase_response) options = { :order_id => '1', @@ -298,7 +429,7 @@ def test_zip_in_shipping_address end def test_zip_in_billing_address - @gateway.expects(:ssl_post).with(anything, regexp_matches(/<code>BT28XX<\/code>/)).returns(successful_purchase_response) + @gateway.expects(:ssl_post).with(anything, regexp_matches(/<code>28\|123<\/code>/)).returns(successful_purchase_response) options = { :order_id => '1', @@ -308,6 +439,115 @@ def test_zip_in_billing_address @gateway.authorize(@amount, @credit_card, options) end + def test_transcript_scrubbing + assert_equal scrubbed_transcript, @gateway.scrub(transcript) + end + + def test_three_d_secure_1 + @gateway.expects(:ssl_post).returns(successful_purchase_response) + + options = { + order_id: '1', + three_d_secure: { + cavv: '1234', + eci: '1234', + xid: '1234', + version: '1.0.2', + } + } + + response = @gateway.authorize(@amount, @credit_card, options) + assert_equal 'M', response.cvv_result['code'] + end + + def test_auth_xml_with_three_d_secure_1 + options = { + order_id: '1', + three_d_secure: { + cavv: '1234', + eci: '1234', + xid: '1234', + version: '1.0.2', + } + } + + @gateway.expects(:new_timestamp).returns('20090824160201') + + valid_auth_request_xml = <<-SRC +<request timestamp="20090824160201" type="auth"> + <merchantid>your_merchant_id</merchantid> + <account>your_account</account> + <orderid>1</orderid> + <amount currency=\"EUR\">100</amount> + <card> + <number>4263971921001307</number> + <expdate>0808</expdate> + <chname>Longbob Longsen</chname> + <type>VISA</type> + <issueno></issueno> + <cvn> + <number></number> + <presind></presind> + </cvn> + </card> + <autosettle flag="0"/> + <sha1hash>3499d7bc8dbacdcfba2286bd74916d026bae630f</sha1hash> + <mpi> + <cavv>1234</cavv> + <xid>1234</xid> + <eci>1234</eci> + <message_version>1.0.2</message_version> + </mpi> +</request> +SRC + + assert_xml_equal valid_auth_request_xml, @gateway.build_purchase_or_authorization_request(:authorization, @amount, @credit_card, options) + end + + def test_auth_xml_with_three_d_secure_2 + options = { + order_id: '1', + three_d_secure: { + cavv: '1234', + eci: '1234', + ds_transaction_id: '1234', + version: '2.1.0', + } + } + + @gateway.expects(:new_timestamp).returns('20090824160201') + + valid_auth_request_xml = <<-SRC +<request timestamp="20090824160201" type="auth"> + <merchantid>your_merchant_id</merchantid> + <account>your_account</account> + <orderid>1</orderid> + <amount currency=\"EUR\">100</amount> + <card> + <number>4263971921001307</number> + <expdate>0808</expdate> + <chname>Longbob Longsen</chname> + <type>VISA</type> + <issueno></issueno> + <cvn> + <number></number> + <presind></presind> + </cvn> + </card> + <autosettle flag="0"/> + <sha1hash>3499d7bc8dbacdcfba2286bd74916d026bae630f</sha1hash> + <mpi> + <authentication_value>1234</authentication_value> + <ds_trans_id>1234</ds_trans_id> + <eci>1234</eci> + <message_version>2.1.0</message_version> + </mpi> +</request> +SRC + + assert_xml_equal valid_auth_request_xml, @gateway.build_purchase_or_authorization_request(:authorization, @amount, @credit_card, options) + end + private def successful_purchase_response @@ -433,6 +673,107 @@ def unsuccessful_refund_response RESPONSE end + def successful_credit_response + <<-RESPONSE + <response timestamp="20190717205030"> + <merchantid>spreedly</merchantid> + <account>internet</account> + <orderid>57a861e97273371e6f1b1737a9bc5710</orderid> + <authcode>005030</authcode> + <result>00</result> + <cvnresult>U</cvnresult> + <avspostcoderesponse>U</avspostcoderesponse> + <avsaddressresponse>U</avsaddressresponse> + <batchid>674655</batchid> + <message>AUTH CODE: 005030</message> + <pasref>15633930303644971</pasref> + <timetaken>0</timetaken> + <authtimetaken>0</authtimetaken> + <cardissuer> + <bank>AIB BANK</bank> + <country>IRELAND</country> + <countrycode>IE</countrycode> + <region>EUR</region> + </cardissuer> + <sha1hash>6d2fc...67814</sha1hash> + </response>" + RESPONSE + end + + def unsuccessful_credit_response + <<-RESPONSE + <response timestamp="20190717210119"> + <result>502</result> + <message>Refund Hash not present.</message> + <orderid>_refund_fd4ea2d10b339011bdba89f580c5b207</orderid> + </response>" + RESPONSE + end + + def transcript + <<-REQUEST + <request timestamp="20150722170750" type="auth"> + <merchantid>your merchant id</merchantid> + <orderid>445472dc5ea848fec1c1720a07d5710b</orderid> + <amount currency="EUR">10000</amount> + <card> + <number>4000126842489127</number> + <expdate>0620</expdate> + <chname>Longbob Longsen</chname> + <type>VISA</type> + <issueno/> + <cvn> + <number>123</number> + <presind>1</presind> + </cvn> + </card> + <autosettle flag="1"/> + <sha1hash>d22109765de91b75e7ad2e5d2fcf8a88235019d9</sha1hash> + <comments> + <comment id="1">Test Realex Purchase</comment> + </comments> + <tssinfo> + <address type="billing"> + <code>90210</code> + <country>US</country> + </address> + </tssinfo> + </request> + REQUEST + end + + def scrubbed_transcript + <<-REQUEST + <request timestamp="20150722170750" type="auth"> + <merchantid>your merchant id</merchantid> + <orderid>445472dc5ea848fec1c1720a07d5710b</orderid> + <amount currency="EUR">10000</amount> + <card> + <number>[FILTERED]</number> + <expdate>0620</expdate> + <chname>Longbob Longsen</chname> + <type>VISA</type> + <issueno/> + <cvn> + <number>[FILTERED]</number> + <presind>1</presind> + </cvn> + </card> + <autosettle flag="1"/> + <sha1hash>d22109765de91b75e7ad2e5d2fcf8a88235019d9</sha1hash> + <comments> + <comment id="1">Test Realex Purchase</comment> + </comments> + <tssinfo> + <address type="billing"> + <code>90210</code> + <country>US</country> + </address> + </tssinfo> + </request> + REQUEST + end + require 'nokogiri' def assert_xml_equal(expected, actual) assert_xml_equal_recursive(Nokogiri::XML(expected).root, Nokogiri::XML(actual).root) @@ -445,6 +786,6 @@ def assert_xml_equal_recursive(a, b) assert_equal a1.name, b1.name assert_equal a1.value, b1.value end - a.children.zip(b.children).all?{|a1, b1| assert_xml_equal_recursive(a1, b1)} + a.children.zip(b.children).all? { |a1, b1| assert_xml_equal_recursive(a1, b1) } end end diff --git a/test/unit/gateways/redsys_sha256_test.rb b/test/unit/gateways/redsys_sha256_test.rb new file mode 100644 index 00000000000..f1fbac3c70f --- /dev/null +++ b/test/unit/gateways/redsys_sha256_test.rb @@ -0,0 +1,389 @@ +require 'test_helper' + +class RedsysSHA256Test < Test::Unit::TestCase + include CommStub + + def setup + Base.mode = :test + @credentials = { + :login => '091952713', + :secret_key => 'QIK77hYl6UFcoCYFKcj+ZjJg8Q6I93Dx', + :signature_algorithm => 'sha256' + } + @gateway = RedsysGateway.new(@credentials) + @credit_card = credit_card('4548812049400004') + @headers = { + 'Content-Type' => 'application/x-www-form-urlencoded' + } + @options = {} + end + + def test_purchase_payload + @credit_card.month = 9 + @credit_card.year = 2017 + @gateway.expects(:ssl_post).with(RedsysGateway.test_url, purchase_request, @headers).returns(successful_purchase_response) + @gateway.purchase(100, @credit_card, :order_id => '144742736014') + end + + def test_purchase_payload_with_credit_card_token + @gateway.expects(:ssl_post).with(RedsysGateway.test_url, purchase_request_with_credit_card_token, @headers).returns(successful_purchase_response) + @gateway.purchase(100, '3126bb8b80a79e66eb1ecc39e305288b60075f86', :order_id => '144742884282') + end + + def test_successful_purchase + @gateway.expects(:ssl_post).returns(successful_purchase_response) + res = @gateway.purchase(100, credit_card, :order_id => '144742736014') + assert_success res + assert_equal 'Transaction Approved', res.message + assert_equal '144742736014|100|978', res.authorization + assert_equal '144742736014', res.params['ds_order'] + end + + # This one is being werid... + def test_successful_purchase_requesting_credit_card_token + @gateway.expects(:ssl_post).returns(successful_purchase_response_with_credit_card_token) + res = @gateway.purchase(100, 'e55e1d0ef338e281baf1d0b5b68be433260ddea0', :order_id => '144742955848') + assert_success res + assert_equal 'Transaction Approved', res.message + assert_equal '144742955848|100|978', res.authorization + assert_equal '144742955848', res.params['ds_order'] + assert_equal 'e55e1d0ef338e281baf1d0b5b68be433260ddea0', res.params['ds_merchant_identifier'] + end + + def test_failed_purchase + @gateway.expects(:ssl_post).returns(failed_purchase_response) + res = @gateway.purchase(100, credit_card, :order_id => '144743314659') + assert_failure res + assert_equal 'SIS0093 ERROR', res.message + end + + def test_purchase_without_order_id + assert_raise ArgumentError do + @gateway.purchase(100, credit_card) + end + end + + def test_error_purchase + @gateway.expects(:ssl_post).returns(error_purchase_response) + res = @gateway.purchase(100, credit_card, :order_id => '123') + assert_failure res + assert_equal 'SIS0051 ERROR', res.message + end + + def test_refund_request + @gateway.expects(:ssl_post).with(RedsysGateway.test_url, refund_request, @headers).returns(successful_refund_response) + @gateway.refund(100, '144743427234') + end + + def test_successful_refund + @gateway.expects(:ssl_post).returns(successful_refund_response) + res = @gateway.refund(100, '1001') + assert_success res + assert_equal 'Refund / Confirmation approved', res.message + assert_equal '144743427234|100|978', res.authorization + assert_equal '144743427234', res.params['ds_order'] + end + + def test_error_refund + @gateway.expects(:ssl_post).returns(error_refund_response) + res = @gateway.refund(100, '1001') + assert_failure res + assert_equal 'SIS0057 ERROR', res.message + end + + # Remaining methods a pretty much the same, so we just test that + # the commit method gets called. + + def test_authorize + @gateway.expects(:ssl_post).with( + anything, + all_of( + includes(CGI.escape('<DS_MERCHANT_TRANSACTIONTYPE>1</DS_MERCHANT_TRANSACTIONTYPE>')), + includes(CGI.escape('<DS_MERCHANT_PAN>4242424242424242</DS_MERCHANT_PAN>')), + includes(CGI.escape('<DS_MERCHANT_AMOUNT>100</DS_MERCHANT_AMOUNT>')) + ), + anything + ).returns(successful_authorize_response) + response = @gateway.authorize(100, credit_card, :order_id => '144743367273') + assert_success response + end + + def test_authorize_without_order_id + assert_raise ArgumentError do + @gateway.authorize(100, credit_card) + end + end + + def test_bad_order_id_format + stub_comms(@gateway, :ssl_request) do + @gateway.authorize(100, credit_card, order_id: 'Una#cce-ptable44Format') + end.check_request do |method, endpoint, data, headers| + assert_match(/MERCHANT_ORDER%3E\d\d\d\dUnaccept%3C/, data) + end.respond_with(successful_authorize_response) + end + + def test_order_id_numeric_start_but_too_long + stub_comms(@gateway, :ssl_request) do + @gateway.authorize(100, credit_card, order_id: '1234ThisIs]FineButTooLong') + end.check_request do |method, endpoint, data, headers| + assert_match(/MERCHANT_ORDER%3E1234ThisIsFi%3C/, data) + end.respond_with(successful_authorize_response) + end + + def test_capture + @gateway.expects(:ssl_post).with( + anything, + all_of( + includes(CGI.escape('<DS_MERCHANT_TRANSACTIONTYPE>2</DS_MERCHANT_TRANSACTIONTYPE>')), + includes(CGI.escape('<DS_MERCHANT_ORDER>144743367273</DS_MERCHANT_ORDER>')), + includes(CGI.escape('<DS_MERCHANT_AMOUNT>100</DS_MERCHANT_AMOUNT>')) + ), + anything + ).returns(successful_capture_response) + @gateway.capture(100, '144743367273') + end + + def test_void + @gateway.expects(:ssl_post).with( + anything, + all_of( + includes(CGI.escape('<DS_MERCHANT_TRANSACTIONTYPE>9</DS_MERCHANT_TRANSACTIONTYPE>')), + includes(CGI.escape('<DS_MERCHANT_ORDER>144743389043</DS_MERCHANT_ORDER>')), + includes(CGI.escape('<DS_MERCHANT_AMOUNT>100</DS_MERCHANT_AMOUNT>')), + includes(CGI.escape('<DS_MERCHANT_CURRENCY>978</DS_MERCHANT_CURRENCY>')) + ), + anything + ).returns(successful_void_response) + @gateway.void('144743389043|100|978') + end + + def test_override_currency + @gateway.expects(:ssl_post).with( + anything, + includes(CGI.escape('<DS_MERCHANT_CURRENCY>840</DS_MERCHANT_CURRENCY>')), + anything + ).returns(successful_purchase_response) + @gateway.authorize(100, credit_card, :order_id => '1001', :currency => 'USD') + end + + def test_successful_verify + @gateway.expects(:ssl_post).times(2).returns(successful_authorize_response).then.returns(successful_void_response) + response = @gateway.verify(credit_card, :order_id => '144743367273') + assert_success response + end + + def test_successful_verify_with_failed_void + @gateway.expects(:ssl_post).times(2).returns(successful_authorize_response).then.returns(failed_void_response) + response = @gateway.verify(credit_card, :order_id => '144743367273') + assert_success response + assert_equal 'Transaction Approved', response.message + end + + def test_unsuccessful_verify + @gateway.expects(:ssl_post).returns(failed_authorize_response) + response = @gateway.verify(credit_card, :order_id => '141278225678') + assert_failure response + assert_equal 'SIS0093 ERROR', response.message + end + + def test_unknown_currency + assert_raise ArgumentError do + @gateway.purchase(100, credit_card, @options.merge(currency: 'HUH WUT')) + end + end + + def test_default_currency + assert_equal 'EUR', RedsysGateway.default_currency + end + + def test_supported_countries + assert_equal ['ES'], RedsysGateway.supported_countries + end + + def test_supported_cardtypes + assert_equal [:visa, :master, :american_express, :jcb, :diners_club], RedsysGateway.supported_cardtypes + end + + def test_using_test_mode + assert @gateway.test? + assert_equal @gateway.send(:url), RedsysGateway.test_url + end + + def test_overriding_options + Base.mode = :production + gw = RedsysGateway.new( + :terminal => 1, + :login => '1234', + :secret_key => '12345', + :test => true + ) + assert gw.test? + assert_equal RedsysGateway.test_url, gw.send(:url) + end + + def test_production_mode + Base.mode = :production + gw = RedsysGateway.new( + :terminal => 1, + :login => '1234', + :secret_key => '12345' + ) + assert !gw.test? + assert_equal RedsysGateway.live_url, gw.send(:url) + end + + def test_transcript_scrubbing + assert_equal post_scrubbed, @gateway.scrub(pre_scrubbed) + end + + def test_failed_transaction_transcript_scrubbing + assert_equal failed_transaction_post_scrubbed, @gateway.scrub(failed_transaction_pre_scrubbed) + end + + def test_nil_cvv_transcript_scrubbing + assert_equal nil_cvv_post_scrubbed, @gateway.scrub(nil_cvv_pre_scrubbed) + end + + def test_empty_string_cvv_transcript_scrubbing + assert_equal empty_string_cvv_post_scrubbed, @gateway.scrub(empty_string_cvv_pre_scrubbed) + end + + def test_whitespace_string_cvv_transcript_scrubbing + assert_equal whitespace_string_cvv_post_scrubbed, @gateway.scrub(whitespace_string_cvv_pre_scrubbed) + end + + private + + def generate_order_id + (Time.now.to_f * 100).to_i.to_s + end + + # Sample response for two main types of operation, + # one with card and another without. + + def purchase_request + 'entrada=%3C%3Fxml+version%3D%221.0%22+encoding%3D%22UTF-8%22%3F%3E%3CREQUEST%3E%3CDATOSENTRADA%3E%3CDS_Version%3E0.1%3C%2FDS_Version%3E%3CDS_MERCHANT_CURRENCY%3E978%3C%2FDS_MERCHANT_CURRENCY%3E%3CDS_MERCHANT_AMOUNT%3E100%3C%2FDS_MERCHANT_AMOUNT%3E%3CDS_MERCHANT_ORDER%3E144742736014%3C%2FDS_MERCHANT_ORDER%3E%3CDS_MERCHANT_TRANSACTIONTYPE%3EA%3C%2FDS_MERCHANT_TRANSACTIONTYPE%3E%3CDS_MERCHANT_PRODUCTDESCRIPTION%2F%3E%3CDS_MERCHANT_TERMINAL%3E1%3C%2FDS_MERCHANT_TERMINAL%3E%3CDS_MERCHANT_MERCHANTCODE%3E091952713%3C%2FDS_MERCHANT_MERCHANTCODE%3E%3CDS_MERCHANT_TITULAR%3ELongbob+Longsen%3C%2FDS_MERCHANT_TITULAR%3E%3CDS_MERCHANT_PAN%3E4548812049400004%3C%2FDS_MERCHANT_PAN%3E%3CDS_MERCHANT_EXPIRYDATE%3E1709%3C%2FDS_MERCHANT_EXPIRYDATE%3E%3CDS_MERCHANT_CVV2%3E123%3C%2FDS_MERCHANT_CVV2%3E%3C%2FDATOSENTRADA%3E%3CDS_SIGNATUREVERSION%3EHMAC_SHA256_V1%3C%2FDS_SIGNATUREVERSION%3E%3CDS_SIGNATURE%3Eq9QH2P%2B4qm8w%2FS85KRPVaepWOrOT2RXlEmyPUce5XRM%3D%3C%2FDS_SIGNATURE%3E%3C%2FREQUEST%3E' + end + + def purchase_request_with_credit_card_token + 'entrada=%3C%3Fxml+version%3D%221.0%22+encoding%3D%22UTF-8%22%3F%3E%3CREQUEST%3E%3CDATOSENTRADA%3E%3CDS_Version%3E0.1%3C%2FDS_Version%3E%3CDS_MERCHANT_CURRENCY%3E978%3C%2FDS_MERCHANT_CURRENCY%3E%3CDS_MERCHANT_AMOUNT%3E100%3C%2FDS_MERCHANT_AMOUNT%3E%3CDS_MERCHANT_ORDER%3E144742884282%3C%2FDS_MERCHANT_ORDER%3E%3CDS_MERCHANT_TRANSACTIONTYPE%3EA%3C%2FDS_MERCHANT_TRANSACTIONTYPE%3E%3CDS_MERCHANT_PRODUCTDESCRIPTION%2F%3E%3CDS_MERCHANT_TERMINAL%3E1%3C%2FDS_MERCHANT_TERMINAL%3E%3CDS_MERCHANT_MERCHANTCODE%3E091952713%3C%2FDS_MERCHANT_MERCHANTCODE%3E%3CDS_MERCHANT_IDENTIFIER%3E3126bb8b80a79e66eb1ecc39e305288b60075f86%3C%2FDS_MERCHANT_IDENTIFIER%3E%3CDS_MERCHANT_DIRECTPAYMENT%3Etrue%3C%2FDS_MERCHANT_DIRECTPAYMENT%3E%3C%2FDATOSENTRADA%3E%3CDS_SIGNATUREVERSION%3EHMAC_SHA256_V1%3C%2FDS_SIGNATUREVERSION%3E%3CDS_SIGNATURE%3EN0tYMrHGf1PmmJ7WIiRONdqbIGmyhaV%2BhP4acTyfJYE%3D%3C%2FDS_SIGNATURE%3E%3C%2FREQUEST%3E' + end + + def successful_purchase_response + "<?xml version='1.0' encoding=\"UTF-8\" ?><RETORNOXML><CODIGO>0</CODIGO><Ds_Version>0.1</Ds_Version><OPERACION><Ds_Amount>100</Ds_Amount><Ds_Currency>978</Ds_Currency><Ds_Order>144742736014</Ds_Order><Ds_Signature>P9OHK0+RjbFkx7Bgd/OVfn9garq3j3eNPig81jP/ziU=</Ds_Signature><Ds_MerchantCode>091952713</Ds_MerchantCode><Ds_Terminal>1</Ds_Terminal><Ds_Response>0000</Ds_Response><Ds_AuthorisationCode>399127</Ds_AuthorisationCode><Ds_TransactionType>A</Ds_TransactionType><Ds_SecurePayment>0</Ds_SecurePayment><Ds_Language>1</Ds_Language><Ds_MerchantData></Ds_MerchantData><Ds_Card_Country>724</Ds_Card_Country></OPERACION></RETORNOXML>" + end + + def successful_purchase_response_with_credit_card_token + "<?xml version='1.0' encoding=\"UTF-8\" ?><RETORNOXML><CODIGO>0</CODIGO><Ds_Version>0.1</Ds_Version><OPERACION><Ds_Amount>100</Ds_Amount><Ds_Currency>978</Ds_Currency><Ds_Order>144742955848</Ds_Order><Ds_Signature>p9LAThJR5eC9QGUtf5ZNKtYTkQ8NAu9YOO3wgJfWP3U=</Ds_Signature><Ds_MerchantCode>091952713</Ds_MerchantCode><Ds_Terminal>1</Ds_Terminal><Ds_Response>0000</Ds_Response><Ds_AuthorisationCode>399366</Ds_AuthorisationCode><Ds_TransactionType>A</Ds_TransactionType><Ds_SecurePayment>0</Ds_SecurePayment><Ds_Language>1</Ds_Language><Ds_Merchant_Identifier>e55e1d0ef338e281baf1d0b5b68be433260ddea0</Ds_Merchant_Identifier><Ds_MerchantData></Ds_MerchantData><Ds_Card_Country>724</Ds_Card_Country></OPERACION></RETORNOXML>\n" + end + + def failed_purchase_response + "<?xml version='1.0' encoding=\"UTF-8\" ?><RETORNOXML><CODIGO>SIS0093</CODIGO><RECIBIDO><?xml version=\"1.0\" encoding=\"UTF-8\"?><REQUEST><DATOSENTRADA><DS_Version>0.1</DS_Version><DS_MERCHANT_CURRENCY>978</DS_MERCHANT_CURRENCY><DS_MERCHANT_AMOUNT>100</DS_MERCHANT_AMOUNT><DS_MERCHANT_ORDER>144743314659</DS_MERCHANT_ORDER><DS_MERCHANT_TRANSACTIONTYPE>A</DS_MERCHANT_TRANSACTIONTYPE><DS_MERCHANT_PRODUCTDESCRIPTION/><DS_MERCHANT_TERMINAL>1</DS_MERCHANT_TERMINAL><DS_MERCHANT_MERCHANTCODE>091952713</DS_MERCHANT_MERCHANTCODE><DS_MERCHANT_TITULAR>Longbob Longsen</DS_MERCHANT_TITULAR><DS_MERCHANT_PAN>4242424242424242</DS_MERCHANT_PAN><DS_MERCHANT_EXPIRYDATE>1609</DS_MERCHANT_EXPIRYDATE><DS_MERCHANT_CVV2>123</DS_MERCHANT_CVV2></DATOSENTRADA><DS_SIGNATUREVERSION>HMAC_SHA256_V1</DS_SIGNATUREVERSION><DS_SIGNATURE>/iV3bMFP657mBtoRgUsW9hI/IQKMTiC9xV5YJiuK4hM=</DS_SIGNATURE></REQUEST></RECIBIDO></RETORNOXML>\n" + end + + def error_purchase_response + "<?xml version='1.0' encoding=\"ISO-8859-1\" ?><RETORNOXML><CODIGO>SIS0051</CODIGO><RECIBIDO><DATOSENTRADA>\n <DS_Version>0.1</DS_Version>\n <DS_MERCHANT_CURRENCY>978</DS_MERCHANT_CURRENCY>\n <DS_MERCHANT_AMOUNT>123</DS_MERCHANT_AMOUNT>\n <DS_MERCHANT_ORDER>1001</DS_MERCHANT_ORDER>\n <DS_MERCHANT_TRANSACTIONTYPE>A</DS_MERCHANT_TRANSACTIONTYPE>\n <DS_MERCHANT_TERMINAL>1</DS_MERCHANT_TERMINAL>\n <DS_MERCHANT_MERCHANTCODE>91952713</DS_MERCHANT_MERCHANTCODE>\n <DS_MERCHANT_MERCHANTSIGNATURE>b5cdaf0f0672be67e6c77f219b63ecbeed1ce525</DS_MERCHANT_MERCHANTSIGNATURE>\n <DS_MERCHANT_TITULAR>Sam Lown</DS_MERCHANT_TITULAR>\n <DS_MERCHANT_PAN>4792587766554414</DS_MERCHANT_PAN>\n <DS_MERCHANT_EXPIRYDATE>1510</DS_MERCHANT_EXPIRYDATE>\n <DS_MERCHANT_CVV2>737</DS_MERCHANT_CVV2>\n</DATOSENTRADA>\n</RECIBIDO></RETORNOXML>\n" + end + + def successful_authorize_response + "<?xml version='1.0' encoding=\"UTF-8\" ?><RETORNOXML><CODIGO>0</CODIGO><Ds_Version>0.1</Ds_Version><OPERACION><Ds_Amount>100</Ds_Amount><Ds_Currency>978</Ds_Currency><Ds_Order>144743367273</Ds_Order><Ds_Signature>29qv8K/6k3P1zyk5F+ZYmMel0uuOzC58kXCgp5rcnhI=</Ds_Signature><Ds_MerchantCode>091952713</Ds_MerchantCode><Ds_Terminal>1</Ds_Terminal><Ds_Response>0000</Ds_Response><Ds_AuthorisationCode>399957</Ds_AuthorisationCode><Ds_TransactionType>1</Ds_TransactionType><Ds_SecurePayment>0</Ds_SecurePayment><Ds_Language>1</Ds_Language><Ds_MerchantData></Ds_MerchantData><Ds_Card_Country>724</Ds_Card_Country></OPERACION></RETORNOXML>\n" + end + + def failed_authorize_response + "<?xml version='1.0' encoding=\"ISO-8859-1\" ?><RETORNOXML><CODIGO>SIS0093</CODIGO><RECIBIDO><DATOSENTRADA>\n <DS_Version>0.1</DS_Version>\n <DS_MERCHANT_CURRENCY>978</DS_MERCHANT_CURRENCY>\n <DS_MERCHANT_AMOUNT>100</DS_MERCHANT_AMOUNT>\n <DS_MERCHANT_ORDER>141278225678</DS_MERCHANT_ORDER>\n <DS_MERCHANT_TRANSACTIONTYPE>1</DS_MERCHANT_TRANSACTIONTYPE>\n <DS_MERCHANT_TERMINAL>1</DS_MERCHANT_TERMINAL>\n <DS_MERCHANT_MERCHANTCODE>91952713</DS_MERCHANT_MERCHANTCODE>\n <DS_MERCHANT_MERCHANTSIGNATURE>1c34699589507802f800b929ea314dc143b0b8a5</DS_MERCHANT_MERCHANTSIGNATURE>\n <DS_MERCHANT_TITULAR>Longbob Longsen</DS_MERCHANT_TITULAR>\n <DS_MERCHANT_PAN>4242424242424242</DS_MERCHANT_PAN>\n <DS_MERCHANT_EXPIRYDATE>1509</DS_MERCHANT_EXPIRYDATE>\n <DS_MERCHANT_CVV2>123</DS_MERCHANT_CVV2>\n</DATOSENTRADA>\n</RECIBIDO></RETORNOXML>" + end + + def refund_request + 'entrada=%3C%3Fxml+version%3D%221.0%22+encoding%3D%22UTF-8%22%3F%3E%3CREQUEST%3E%3CDATOSENTRADA%3E%3CDS_Version%3E0.1%3C%2FDS_Version%3E%3CDS_MERCHANT_CURRENCY%3E978%3C%2FDS_MERCHANT_CURRENCY%3E%3CDS_MERCHANT_AMOUNT%3E100%3C%2FDS_MERCHANT_AMOUNT%3E%3CDS_MERCHANT_ORDER%3E144743427234%3C%2FDS_MERCHANT_ORDER%3E%3CDS_MERCHANT_TRANSACTIONTYPE%3E3%3C%2FDS_MERCHANT_TRANSACTIONTYPE%3E%3CDS_MERCHANT_PRODUCTDESCRIPTION%2F%3E%3CDS_MERCHANT_TERMINAL%3E1%3C%2FDS_MERCHANT_TERMINAL%3E%3CDS_MERCHANT_MERCHANTCODE%3E091952713%3C%2FDS_MERCHANT_MERCHANTCODE%3E%3C%2FDATOSENTRADA%3E%3CDS_SIGNATUREVERSION%3EHMAC_SHA256_V1%3C%2FDS_SIGNATUREVERSION%3E%3CDS_SIGNATURE%3EQhNVtjoee6s%2Bvo%2B5bJVM4esT58bz7zkY1Xe7qjdmxA0%3D%3C%2FDS_SIGNATURE%3E%3C%2FREQUEST%3E' + end + + def successful_refund_response + "<?xml version='1.0' encoding=\"UTF-8\" ?><RETORNOXML><CODIGO>0</CODIGO><Ds_Version>0.1</Ds_Version><OPERACION><Ds_Amount>100</Ds_Amount><Ds_Currency>978</Ds_Currency><Ds_Order>144743427234</Ds_Order><Ds_Signature>Iyc7inddQUGys6zbCZQUteIeR31ZDyQOT4zW+uxjB0M=</Ds_Signature><Ds_MerchantCode>091952713</Ds_MerchantCode><Ds_Terminal>1</Ds_Terminal><Ds_Response>0900</Ds_Response><Ds_AuthorisationCode>400062</Ds_AuthorisationCode><Ds_TransactionType>3</Ds_TransactionType><Ds_SecurePayment>0</Ds_SecurePayment><Ds_Language>1</Ds_Language><Ds_MerchantData></Ds_MerchantData><Ds_Card_Country>724</Ds_Card_Country></OPERACION></RETORNOXML>\n" + end + + def error_refund_response + "<?xml version='1.0' encoding=\"ISO-8859-1\" ?><RETORNOXML><CODIGO>SIS0057</CODIGO><RECIBIDO><DATOSENTRADA>\n <DS_Version>0.1</DS_Version>\n <DS_MERCHANT_CURRENCY>978</DS_MERCHANT_CURRENCY>\n <DS_MERCHANT_AMOUNT>123</DS_MERCHANT_AMOUNT>\n <DS_MERCHANT_ORDER>1001</DS_MERCHANT_ORDER>\n <DS_MERCHANT_TRANSACTIONTYPE>3</DS_MERCHANT_TRANSACTIONTYPE>\n <DS_MERCHANT_TERMINAL>1</DS_MERCHANT_TERMINAL>\n <DS_MERCHANT_MERCHANTCODE>91952713</DS_MERCHANT_MERCHANTCODE>\n <DS_MERCHANT_MERCHANTSIGNATURE>9e12f1607147b4611bfdbff80aa143241c27f935</DS_MERCHANT_MERCHANTSIGNATURE>\n</DATOSENTRADA>\n</RECIBIDO></RETORNOXML>\n" + end + + def successful_void_response + "<?xml version='1.0' encoding=\"UTF-8\" ?><RETORNOXML><CODIGO>0</CODIGO><Ds_Version>0.1</Ds_Version><OPERACION><Ds_Amount>100</Ds_Amount><Ds_Currency>978</Ds_Currency><Ds_Order>144743389043</Ds_Order><Ds_Signature>nqT1A3Kk9BeFrpwwl+n5YyBZ23ufqiEvu7/gzl9xBqM=</Ds_Signature><Ds_MerchantCode>091952713</Ds_MerchantCode><Ds_Terminal>1</Ds_Terminal><Ds_Response>0400</Ds_Response><Ds_AuthorisationCode>400002</Ds_AuthorisationCode><Ds_TransactionType>9</Ds_TransactionType><Ds_SecurePayment>0</Ds_SecurePayment><Ds_Language>1</Ds_Language><Ds_MerchantData></Ds_MerchantData><Ds_Card_Country>724</Ds_Card_Country></OPERACION></RETORNOXML>\n" + end + + def failed_void_response + "<?xml version='1.0' encoding=\"ISO-8859-1\" ?><RETORNOXML><CODIGO>SIS0222</CODIGO><RECIBIDO><DATOSENTRADA>\n <DS_Version>0.1</DS_Version>\n <DS_MERCHANT_CURRENCY>978</DS_MERCHANT_CURRENCY>\n <DS_MERCHANT_AMOUNT>100</DS_MERCHANT_AMOUNT>\n <DS_MERCHANT_ORDER>141278298713</DS_MERCHANT_ORDER>\n <DS_MERCHANT_TRANSACTIONTYPE>9</DS_MERCHANT_TRANSACTIONTYPE>\n <DS_MERCHANT_TERMINAL>1</DS_MERCHANT_TERMINAL>\n <DS_MERCHANT_MERCHANTCODE>91952713</DS_MERCHANT_MERCHANTCODE>\n <DS_MERCHANT_MERCHANTSIGNATURE>ead33f15453316e86dfc51642e400e2467fe71bb</DS_MERCHANT_MERCHANTSIGNATURE>\n</DATOSENTRADA>\n</RECIBIDO></RETORNOXML>" + end + + def successful_capture_response + "<?xml version='1.0' encoding=\"UTF-8\" ?><RETORNOXML><CODIGO>0</CODIGO><Ds_Version>0.1</Ds_Version><OPERACION><Ds_Amount>100</Ds_Amount><Ds_Currency>978</Ds_Currency><Ds_Order>144743367273</Ds_Order><Ds_Signature>mPJiCwWEFf21P44slxLsxqX37DGJRoQyYJUXUhOjXvI=</Ds_Signature><Ds_MerchantCode>091952713</Ds_MerchantCode><Ds_Terminal>1</Ds_Terminal><Ds_Response>0900</Ds_Response><Ds_AuthorisationCode>399957</Ds_AuthorisationCode><Ds_TransactionType>2</Ds_TransactionType><Ds_SecurePayment>0</Ds_SecurePayment><Ds_Language>1</Ds_Language><Ds_MerchantData></Ds_MerchantData><Ds_Card_Country>724</Ds_Card_Country></OPERACION></RETORNOXML>\n" + end + + def pre_scrubbed + <<-PRE_SCRUBBED + entrada=%3CDATOSENTRADA%3E%0A++%3CDS_Version%3E0.1%3C%2FDS_Version%3E%0A++%3CDS_MERCHANT_CURRENCY%3E978%3C%2FDS_MERCHANT_CURRENCY%3E%0A++%3CDS_MERCHANT_AMOUNT%3E100%3C%2FDS_MERCHANT_AMOUNT%3E%0A++%3CDS_MERCHANT_ORDER%3E135214014098%3C%2FDS_MERCHANT_ORDER%3E%0A++%3CDS_MERCHANT_TRANSACTIONTYPE%3EA%3C%2FDS_MERCHANT_TRANSACTIONTYPE%3E%0A++%3CDS_MERCHANT_TERMINAL%3E1%3C%2FDS_MERCHANT_TERMINAL%3E%0A++%3CDS_MERCHANT_MERCHANTCODE%3E91952713%3C%2FDS_MERCHANT_MERCHANTCODE%3E%0A++%3CDS_MERCHANT_MERCHANTSIGNATURE%3E39589b03cdd3c525885cdb3b3761e2fb7a8be9ee%3C%2FDS_MERCHANT_MERCHANTSIGNATURE%3E%0A++%3CDS_MERCHANT_TITULAR%3ELongbob+Longsen%3C%2FDS_MERCHANT_TITULAR%3E%0A++%3CDS_MERCHANT_PAN%3E4548812049400004%3C%2FDS_MERCHANT_PAN%3E%0A++%3CDS_MERCHANT_EXPIRYDATE%3E1309%3C%2FDS_MERCHANT_EXPIRYDATE%3E%0A++%3CDS_MERCHANT_CVV2%3E123%3C%2FDS_MERCHANT_CVV2%3E%0A%3C%2FDATOSENTRADA%3E%0A + <?xml version='1.0' encoding="ISO-8859-1" ?><RETORNOXML><CODIGO>0</CODIGO><Ds_Version>0.1</Ds_Version><OPERACION><Ds_Amount>100</Ds_Amount><Ds_Currency>978</Ds_Currency><Ds_Order>135214014098</Ds_Order><Ds_Signature>97FBF7E648015AC8AFCA107CD67A1F600FBE9611</Ds_Signature><Ds_MerchantCode>91952713</Ds_MerchantCode><Ds_Terminal>1</Ds_Terminal><Ds_Response>0000</Ds_Response><Ds_AuthorisationCode>701841</Ds_AuthorisationCode><Ds_TransactionType>A</Ds_TransactionType><Ds_SecurePayment>0</Ds_SecurePayment><Ds_Language>1</Ds_Language><Ds_MerchantData></Ds_MerchantData><Ds_Card_Country>724</Ds_Card_Country></OPERACION></RETORNOXML> + PRE_SCRUBBED + end + + def post_scrubbed + <<-POST_SCRUBBED + entrada=%3CDATOSENTRADA%3E%0A++%3CDS_Version%3E0.1%3C%2FDS_Version%3E%0A++%3CDS_MERCHANT_CURRENCY%3E978%3C%2FDS_MERCHANT_CURRENCY%3E%0A++%3CDS_MERCHANT_AMOUNT%3E100%3C%2FDS_MERCHANT_AMOUNT%3E%0A++%3CDS_MERCHANT_ORDER%3E135214014098%3C%2FDS_MERCHANT_ORDER%3E%0A++%3CDS_MERCHANT_TRANSACTIONTYPE%3EA%3C%2FDS_MERCHANT_TRANSACTIONTYPE%3E%0A++%3CDS_MERCHANT_TERMINAL%3E1%3C%2FDS_MERCHANT_TERMINAL%3E%0A++%3CDS_MERCHANT_MERCHANTCODE%3E91952713%3C%2FDS_MERCHANT_MERCHANTCODE%3E%0A++%3CDS_MERCHANT_MERCHANTSIGNATURE%3E39589b03cdd3c525885cdb3b3761e2fb7a8be9ee%3C%2FDS_MERCHANT_MERCHANTSIGNATURE%3E%0A++%3CDS_MERCHANT_TITULAR%3ELongbob+Longsen%3C%2FDS_MERCHANT_TITULAR%3E%0A++%3CDS_MERCHANT_PAN%3E[FILTERED]%3C%2FDS_MERCHANT_PAN%3E%0A++%3CDS_MERCHANT_EXPIRYDATE%3E1309%3C%2FDS_MERCHANT_EXPIRYDATE%3E%0A++%3CDS_MERCHANT_CVV2%3E[FILTERED]%3C%2FDS_MERCHANT_CVV2%3E%0A%3C%2FDATOSENTRADA%3E%0A + <?xml version='1.0' encoding="ISO-8859-1" ?><RETORNOXML><CODIGO>0</CODIGO><Ds_Version>0.1</Ds_Version><OPERACION><Ds_Amount>100</Ds_Amount><Ds_Currency>978</Ds_Currency><Ds_Order>135214014098</Ds_Order><Ds_Signature>97FBF7E648015AC8AFCA107CD67A1F600FBE9611</Ds_Signature><Ds_MerchantCode>91952713</Ds_MerchantCode><Ds_Terminal>1</Ds_Terminal><Ds_Response>0000</Ds_Response><Ds_AuthorisationCode>701841</Ds_AuthorisationCode><Ds_TransactionType>A</Ds_TransactionType><Ds_SecurePayment>0</Ds_SecurePayment><Ds_Language>1</Ds_Language><Ds_MerchantData></Ds_MerchantData><Ds_Card_Country>724</Ds_Card_Country></OPERACION></RETORNOXML> + POST_SCRUBBED + end + + def failed_transaction_pre_scrubbed + %q( +POST /sis/operaciones HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: sis-t.redsys.es:25443\r\nContent-Length: 969\r\n\r\n"<- "entrada=%3CDATOSENTRADA%3E%0A++%3CDS_Version%3E0.1%3C%2FDS_Version%3E%0A++%3CDS_MERCHANT_CURRENCY%3E978%3C%2FDS_MERCHANT_CURRENCY%3E%0A++%3CDS_MERCHANT_AMOUNT%3E%3C%2FDS_MERCHANT_AMOUNT%3E%0A++%3CDS_MERCHANT_ORDER%3E144009991943%3C%2FDS_MERCHANT_ORDER%3E%0A++%3CDS_MERCHANT_TRANSACTIONTYPE%3EA%3C%2FDS_MERCHANT_TRANSACTIONTYPE%3E%0A++%3CDS_MERCHANT_PRODUCTDESCRIPTION%3ETest+Description%3C%2FDS_MERCHANT_PRODUCTDESCRIPTION%3E%0A++%3CDS_MERCHANT_TERMINAL%3E1%3C%2FDS_MERCHANT_TERMINAL%3E%0A++%3CDS_MERCHANT_MERCHANTCODE%3E91952713%3C%2FDS_MERCHANT_MERCHANTCODE%3E%0A++%3CDS_MERCHANT_MERCHANTSIGNATURE%3E2bf324cba60dcdd9e2c1bc8de2458a6ed168778f%3C%2FDS_MERCHANT_MERCHANTSIGNATURE%3E%0A++%3CDS_MERCHANT_TITULAR%3ELongbob+Longsen%3C%2FDS_MERCHANT_TITULAR%3E%0A++%3CDS_MERCHANT_PAN%3E4548812049400004%3C%2FDS_MERCHANT_PAN%3E%0A++%3CDS_MERCHANT_EXPIRYDATE%3E1609%3C%2FDS_MERCHANT_EXPIRYDATE%3E%0A++%3CDS_MERCHANT_CVV2%3E123%3C%2FDS_MERCHANT_CVV2%3E%0A%3C%2FDATOSENTRADA%3E%0A +<?xml version='1.0' encoding=\"ISO-8859-1\" ?><RETORNOXML><CODIGO>SIS0018</CODIGO><RECIBIDO><DATOSENTRADA>\n <DS_Version>0.1</DS_Version>\n <DS_MERCHANT_CURRENCY>978</DS_MERCHANT_CURRENCY>\n <DS_MERCHANT_AMOUNT></DS_MERCHANT_AMOUNT>\n <DS_MERCHANT_ORDER>144009991943</DS_MERCHANT_ORDER>\n <DS_MERCHANT_TRANSACTIONTYPE>A</DS_MERCHANT_TRANSACTIONTYPE>\n <DS_MERCHANT_PRODUCTDESCRIPTION>Test Description</DS_MERCHANT_PRODUCTDESCRIPTION>\n <DS_MERCHANT_TERMINAL>1</DS_MERCHANT_TERMINAL>\n <DS_MERCHANT_MERCHANTCODE>91952713</DS_MERCHANT_MERCHANTCODE>\n <DS_MERCHANT_MERCHANTSIGNATURE>2bf324cba60dcdd9e2c1bc8de2458a6ed168778f</DS_MERCHANT_MERCHANTSIGNATURE>\n <DS_MERCHANT_TITULAR>Longbob Longsen</DS_MERCHANT_TITULAR>\n <DS_MERCHANT_PAN>4548812049400004</DS_MERCHANT_PAN>\n <DS_MERCHANT_EXPIRYDATE>1609</DS_MERCHANT_EXPIRYDATE>\n <DS_MERCHANT_CVV2>123</DS_MERCHANT_CVV2>\n</DATOSENTRADA>\n</RECIBIDO></RETORNOXML>\n + ) + end + + def failed_transaction_post_scrubbed + %q( +POST /sis/operaciones HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: sis-t.redsys.es:25443\r\nContent-Length: 969\r\n\r\n"<- "entrada=%3CDATOSENTRADA%3E%0A++%3CDS_Version%3E0.1%3C%2FDS_Version%3E%0A++%3CDS_MERCHANT_CURRENCY%3E978%3C%2FDS_MERCHANT_CURRENCY%3E%0A++%3CDS_MERCHANT_AMOUNT%3E%3C%2FDS_MERCHANT_AMOUNT%3E%0A++%3CDS_MERCHANT_ORDER%3E144009991943%3C%2FDS_MERCHANT_ORDER%3E%0A++%3CDS_MERCHANT_TRANSACTIONTYPE%3EA%3C%2FDS_MERCHANT_TRANSACTIONTYPE%3E%0A++%3CDS_MERCHANT_PRODUCTDESCRIPTION%3ETest+Description%3C%2FDS_MERCHANT_PRODUCTDESCRIPTION%3E%0A++%3CDS_MERCHANT_TERMINAL%3E1%3C%2FDS_MERCHANT_TERMINAL%3E%0A++%3CDS_MERCHANT_MERCHANTCODE%3E91952713%3C%2FDS_MERCHANT_MERCHANTCODE%3E%0A++%3CDS_MERCHANT_MERCHANTSIGNATURE%3E2bf324cba60dcdd9e2c1bc8de2458a6ed168778f%3C%2FDS_MERCHANT_MERCHANTSIGNATURE%3E%0A++%3CDS_MERCHANT_TITULAR%3ELongbob+Longsen%3C%2FDS_MERCHANT_TITULAR%3E%0A++%3CDS_MERCHANT_PAN%3E[FILTERED]%3C%2FDS_MERCHANT_PAN%3E%0A++%3CDS_MERCHANT_EXPIRYDATE%3E1609%3C%2FDS_MERCHANT_EXPIRYDATE%3E%0A++%3CDS_MERCHANT_CVV2%3E[FILTERED]%3C%2FDS_MERCHANT_CVV2%3E%0A%3C%2FDATOSENTRADA%3E%0A +<?xml version='1.0' encoding=\"ISO-8859-1\" ?><RETORNOXML><CODIGO>SIS0018</CODIGO><RECIBIDO><DATOSENTRADA>\n <DS_Version>0.1</DS_Version>\n <DS_MERCHANT_CURRENCY>978</DS_MERCHANT_CURRENCY>\n <DS_MERCHANT_AMOUNT></DS_MERCHANT_AMOUNT>\n <DS_MERCHANT_ORDER>144009991943</DS_MERCHANT_ORDER>\n <DS_MERCHANT_TRANSACTIONTYPE>A</DS_MERCHANT_TRANSACTIONTYPE>\n <DS_MERCHANT_PRODUCTDESCRIPTION>Test Description</DS_MERCHANT_PRODUCTDESCRIPTION>\n <DS_MERCHANT_TERMINAL>1</DS_MERCHANT_TERMINAL>\n <DS_MERCHANT_MERCHANTCODE>91952713</DS_MERCHANT_MERCHANTCODE>\n <DS_MERCHANT_MERCHANTSIGNATURE>2bf324cba60dcdd9e2c1bc8de2458a6ed168778f</DS_MERCHANT_MERCHANTSIGNATURE>\n <DS_MERCHANT_TITULAR>Longbob Longsen</DS_MERCHANT_TITULAR>\n <DS_MERCHANT_PAN>[FILTERED]</DS_MERCHANT_PAN>\n <DS_MERCHANT_EXPIRYDATE>1609</DS_MERCHANT_EXPIRYDATE>\n <DS_MERCHANT_CVV2>[FILTERED]</DS_MERCHANT_CVV2>\n</DATOSENTRADA>\n</RECIBIDO></RETORNOXML>\n + ) + end + + def nil_cvv_pre_scrubbed + <<-PRE_SCRUBBED + entrada=%3CDATOSENTRADA%3E%0A++%3CDS_Version%3E0.1%3C%2FDS_Version%3E%0A++%3CDS_MERCHANT_CURRENCY%3E978%3C%2FDS_MERCHANT_CURRENCY%3E%0A++%3CDS_MERCHANT_AMOUNT%3E100%3C%2FDS_MERCHANT_AMOUNT%3E%0A++%3CDS_MERCHANT_ORDER%3E135214014098%3C%2FDS_MERCHANT_ORDER%3E%0A++%3CDS_MERCHANT_TRANSACTIONTYPE%3EA%3C%2FDS_MERCHANT_TRANSACTIONTYPE%3E%0A++%3CDS_MERCHANT_TERMINAL%3E1%3C%2FDS_MERCHANT_TERMINAL%3E%0A++%3CDS_MERCHANT_MERCHANTCODE%3E91952713%3C%2FDS_MERCHANT_MERCHANTCODE%3E%0A++%3CDS_MERCHANT_MERCHANTSIGNATURE%3E39589b03cdd3c525885cdb3b3761e2fb7a8be9ee%3C%2FDS_MERCHANT_MERCHANTSIGNATURE%3E%0A++%3CDS_MERCHANT_TITULAR%3ELongbob+Longsen%3C%2FDS_MERCHANT_TITULAR%3E%0A++%3CDS_MERCHANT_PAN%3E4548812049400004%3C%2FDS_MERCHANT_PAN%3E%0A++%3CDS_MERCHANT_EXPIRYDATE%3E1309%3C%2FDS_MERCHANT_EXPIRYDATE%3E%0A++%3CDS_MERCHANT_CVV2%2F%3E%0A%3C%2FDATOSENTRADA%3E%0A + <?xml version='1.0' encoding="ISO-8859-1" ?><RETORNOXML><CODIGO>0</CODIGO><Ds_Version>0.1</Ds_Version><OPERACION><Ds_Amount>100</Ds_Amount><Ds_Currency>978</Ds_Currency><Ds_Order>135214014098</Ds_Order><Ds_Signature>97FBF7E648015AC8AFCA107CD67A1F600FBE9611</Ds_Signature><Ds_MerchantCode>91952713</Ds_MerchantCode><Ds_Terminal>1</Ds_Terminal><Ds_Response>0000</Ds_Response><Ds_AuthorisationCode>701841</Ds_AuthorisationCode><Ds_TransactionType>A</Ds_TransactionType><Ds_SecurePayment>0</Ds_SecurePayment><Ds_Language>1</Ds_Language><Ds_MerchantData></Ds_MerchantData><Ds_Card_Country>724</Ds_Card_Country></OPERACION></RETORNOXML> + PRE_SCRUBBED + end + + def nil_cvv_post_scrubbed + <<-POST_SCRUBBED + entrada=%3CDATOSENTRADA%3E%0A++%3CDS_Version%3E0.1%3C%2FDS_Version%3E%0A++%3CDS_MERCHANT_CURRENCY%3E978%3C%2FDS_MERCHANT_CURRENCY%3E%0A++%3CDS_MERCHANT_AMOUNT%3E100%3C%2FDS_MERCHANT_AMOUNT%3E%0A++%3CDS_MERCHANT_ORDER%3E135214014098%3C%2FDS_MERCHANT_ORDER%3E%0A++%3CDS_MERCHANT_TRANSACTIONTYPE%3EA%3C%2FDS_MERCHANT_TRANSACTIONTYPE%3E%0A++%3CDS_MERCHANT_TERMINAL%3E1%3C%2FDS_MERCHANT_TERMINAL%3E%0A++%3CDS_MERCHANT_MERCHANTCODE%3E91952713%3C%2FDS_MERCHANT_MERCHANTCODE%3E%0A++%3CDS_MERCHANT_MERCHANTSIGNATURE%3E39589b03cdd3c525885cdb3b3761e2fb7a8be9ee%3C%2FDS_MERCHANT_MERCHANTSIGNATURE%3E%0A++%3CDS_MERCHANT_TITULAR%3ELongbob+Longsen%3C%2FDS_MERCHANT_TITULAR%3E%0A++%3CDS_MERCHANT_PAN%3E[FILTERED]%3C%2FDS_MERCHANT_PAN%3E%0A++%3CDS_MERCHANT_EXPIRYDATE%3E1309%3C%2FDS_MERCHANT_EXPIRYDATE%3E%0A++%3CDS_MERCHANT_CVV2[BLANK]DATOSENTRADA%3E%0A + <?xml version='1.0' encoding="ISO-8859-1" ?><RETORNOXML><CODIGO>0</CODIGO><Ds_Version>0.1</Ds_Version><OPERACION><Ds_Amount>100</Ds_Amount><Ds_Currency>978</Ds_Currency><Ds_Order>135214014098</Ds_Order><Ds_Signature>97FBF7E648015AC8AFCA107CD67A1F600FBE9611</Ds_Signature><Ds_MerchantCode>91952713</Ds_MerchantCode><Ds_Terminal>1</Ds_Terminal><Ds_Response>0000</Ds_Response><Ds_AuthorisationCode>701841</Ds_AuthorisationCode><Ds_TransactionType>A</Ds_TransactionType><Ds_SecurePayment>0</Ds_SecurePayment><Ds_Language>1</Ds_Language><Ds_MerchantData></Ds_MerchantData><Ds_Card_Country>724</Ds_Card_Country></OPERACION></RETORNOXML> + POST_SCRUBBED + end + + def empty_string_cvv_pre_scrubbed + <<-PRE_SCRUBBED + entrada=%3CDATOSENTRADA%3E%0A++%3CDS_Version%3E0.1%3C%2FDS_Version%3E%0A++%3CDS_MERCHANT_CURRENCY%3E978%3C%2FDS_MERCHANT_CURRENCY%3E%0A++%3CDS_MERCHANT_AMOUNT%3E100%3C%2FDS_MERCHANT_AMOUNT%3E%0A++%3CDS_MERCHANT_ORDER%3E135214014098%3C%2FDS_MERCHANT_ORDER%3E%0A++%3CDS_MERCHANT_TRANSACTIONTYPE%3EA%3C%2FDS_MERCHANT_TRANSACTIONTYPE%3E%0A++%3CDS_MERCHANT_TERMINAL%3E1%3C%2FDS_MERCHANT_TERMINAL%3E%0A++%3CDS_MERCHANT_MERCHANTCODE%3E91952713%3C%2FDS_MERCHANT_MERCHANTCODE%3E%0A++%3CDS_MERCHANT_MERCHANTSIGNATURE%3E39589b03cdd3c525885cdb3b3761e2fb7a8be9ee%3C%2FDS_MERCHANT_MERCHANTSIGNATURE%3E%0A++%3CDS_MERCHANT_TITULAR%3ELongbob+Longsen%3C%2FDS_MERCHANT_TITULAR%3E%0A++%3CDS_MERCHANT_PAN%3E4548812049400004%3C%2FDS_MERCHANT_PAN%3E%0A++%3CDS_MERCHANT_EXPIRYDATE%3E1309%3C%2FDS_MERCHANT_EXPIRYDATE%3E%0A++%3CDS_MERCHANT_CVV2%3E%3C%2FDS_MERCHANT_CVV2%3E%0A%3C%2FDATOSENTRADA%3E%0A + <?xml version='1.0' encoding="ISO-8859-1" ?><RETORNOXML><CODIGO>0</CODIGO><Ds_Version>0.1</Ds_Version><OPERACION><Ds_Amount>100</Ds_Amount><Ds_Currency>978</Ds_Currency><Ds_Order>135214014098</Ds_Order><Ds_Signature>97FBF7E648015AC8AFCA107CD67A1F600FBE9611</Ds_Signature><Ds_MerchantCode>91952713</Ds_MerchantCode><Ds_Terminal>1</Ds_Terminal><Ds_Response>0000</Ds_Response><Ds_AuthorisationCode>701841</Ds_AuthorisationCode><Ds_TransactionType>A</Ds_TransactionType><Ds_SecurePayment>0</Ds_SecurePayment><Ds_Language>1</Ds_Language><Ds_MerchantData></Ds_MerchantData><Ds_Card_Country>724</Ds_Card_Country></OPERACION></RETORNOXML> + PRE_SCRUBBED + end + + def empty_string_cvv_post_scrubbed + <<-PRE_SCRUBBED + entrada=%3CDATOSENTRADA%3E%0A++%3CDS_Version%3E0.1%3C%2FDS_Version%3E%0A++%3CDS_MERCHANT_CURRENCY%3E978%3C%2FDS_MERCHANT_CURRENCY%3E%0A++%3CDS_MERCHANT_AMOUNT%3E100%3C%2FDS_MERCHANT_AMOUNT%3E%0A++%3CDS_MERCHANT_ORDER%3E135214014098%3C%2FDS_MERCHANT_ORDER%3E%0A++%3CDS_MERCHANT_TRANSACTIONTYPE%3EA%3C%2FDS_MERCHANT_TRANSACTIONTYPE%3E%0A++%3CDS_MERCHANT_TERMINAL%3E1%3C%2FDS_MERCHANT_TERMINAL%3E%0A++%3CDS_MERCHANT_MERCHANTCODE%3E91952713%3C%2FDS_MERCHANT_MERCHANTCODE%3E%0A++%3CDS_MERCHANT_MERCHANTSIGNATURE%3E39589b03cdd3c525885cdb3b3761e2fb7a8be9ee%3C%2FDS_MERCHANT_MERCHANTSIGNATURE%3E%0A++%3CDS_MERCHANT_TITULAR%3ELongbob+Longsen%3C%2FDS_MERCHANT_TITULAR%3E%0A++%3CDS_MERCHANT_PAN%3E[FILTERED]%3C%2FDS_MERCHANT_PAN%3E%0A++%3CDS_MERCHANT_EXPIRYDATE%3E1309%3C%2FDS_MERCHANT_EXPIRYDATE%3E%0A++%3CDS_MERCHANT_CVV2%3E[BLANK]%3C%2FDS_MERCHANT_CVV2%3E%0A%3C%2FDATOSENTRADA%3E%0A + <?xml version='1.0' encoding="ISO-8859-1" ?><RETORNOXML><CODIGO>0</CODIGO><Ds_Version>0.1</Ds_Version><OPERACION><Ds_Amount>100</Ds_Amount><Ds_Currency>978</Ds_Currency><Ds_Order>135214014098</Ds_Order><Ds_Signature>97FBF7E648015AC8AFCA107CD67A1F600FBE9611</Ds_Signature><Ds_MerchantCode>91952713</Ds_MerchantCode><Ds_Terminal>1</Ds_Terminal><Ds_Response>0000</Ds_Response><Ds_AuthorisationCode>701841</Ds_AuthorisationCode><Ds_TransactionType>A</Ds_TransactionType><Ds_SecurePayment>0</Ds_SecurePayment><Ds_Language>1</Ds_Language><Ds_MerchantData></Ds_MerchantData><Ds_Card_Country>724</Ds_Card_Country></OPERACION></RETORNOXML> + PRE_SCRUBBED + end + + def whitespace_string_cvv_pre_scrubbed + <<-PRE_SCRUBBED + entrada=%3CDATOSENTRADA%3E%0A++%3CDS_Version%3E0.1%3C%2FDS_Version%3E%0A++%3CDS_MERCHANT_CURRENCY%3E978%3C%2FDS_MERCHANT_CURRENCY%3E%0A++%3CDS_MERCHANT_AMOUNT%3E100%3C%2FDS_MERCHANT_AMOUNT%3E%0A++%3CDS_MERCHANT_ORDER%3E135214014098%3C%2FDS_MERCHANT_ORDER%3E%0A++%3CDS_MERCHANT_TRANSACTIONTYPE%3EA%3C%2FDS_MERCHANT_TRANSACTIONTYPE%3E%0A++%3CDS_MERCHANT_TERMINAL%3E1%3C%2FDS_MERCHANT_TERMINAL%3E%0A++%3CDS_MERCHANT_MERCHANTCODE%3E91952713%3C%2FDS_MERCHANT_MERCHANTCODE%3E%0A++%3CDS_MERCHANT_MERCHANTSIGNATURE%3E39589b03cdd3c525885cdb3b3761e2fb7a8be9ee%3C%2FDS_MERCHANT_MERCHANTSIGNATURE%3E%0A++%3CDS_MERCHANT_TITULAR%3ELongbob+Longsen%3C%2FDS_MERCHANT_TITULAR%3E%0A++%3CDS_MERCHANT_PAN%3E4548812049400004%3C%2FDS_MERCHANT_PAN%3E%0A++%3CDS_MERCHANT_EXPIRYDATE%3E1309%3C%2FDS_MERCHANT_EXPIRYDATE%3E%0A++%3CDS_MERCHANT_CVV2%3E+++%3C%2FDS_MERCHANT_CVV2%3E%0A%3C%2FDATOSENTRADA%3E%0A + <?xml version='1.0' encoding="ISO-8859-1" ?><RETORNOXML><CODIGO>0</CODIGO><Ds_Version>0.1</Ds_Version><OPERACION><Ds_Amount>100</Ds_Amount><Ds_Currency>978</Ds_Currency><Ds_Order>135214014098</Ds_Order><Ds_Signature>97FBF7E648015AC8AFCA107CD67A1F600FBE9611</Ds_Signature><Ds_MerchantCode>91952713</Ds_MerchantCode><Ds_Terminal>1</Ds_Terminal><Ds_Response>0000</Ds_Response><Ds_AuthorisationCode>701841</Ds_AuthorisationCode><Ds_TransactionType>A</Ds_TransactionType><Ds_SecurePayment>0</Ds_SecurePayment><Ds_Language>1</Ds_Language><Ds_MerchantData></Ds_MerchantData><Ds_Card_Country>724</Ds_Card_Country></OPERACION></RETORNOXML> + PRE_SCRUBBED + end + + def whitespace_string_cvv_post_scrubbed + <<-PRE_SCRUBBED + entrada=%3CDATOSENTRADA%3E%0A++%3CDS_Version%3E0.1%3C%2FDS_Version%3E%0A++%3CDS_MERCHANT_CURRENCY%3E978%3C%2FDS_MERCHANT_CURRENCY%3E%0A++%3CDS_MERCHANT_AMOUNT%3E100%3C%2FDS_MERCHANT_AMOUNT%3E%0A++%3CDS_MERCHANT_ORDER%3E135214014098%3C%2FDS_MERCHANT_ORDER%3E%0A++%3CDS_MERCHANT_TRANSACTIONTYPE%3EA%3C%2FDS_MERCHANT_TRANSACTIONTYPE%3E%0A++%3CDS_MERCHANT_TERMINAL%3E1%3C%2FDS_MERCHANT_TERMINAL%3E%0A++%3CDS_MERCHANT_MERCHANTCODE%3E91952713%3C%2FDS_MERCHANT_MERCHANTCODE%3E%0A++%3CDS_MERCHANT_MERCHANTSIGNATURE%3E39589b03cdd3c525885cdb3b3761e2fb7a8be9ee%3C%2FDS_MERCHANT_MERCHANTSIGNATURE%3E%0A++%3CDS_MERCHANT_TITULAR%3ELongbob+Longsen%3C%2FDS_MERCHANT_TITULAR%3E%0A++%3CDS_MERCHANT_PAN%3E[FILTERED]%3C%2FDS_MERCHANT_PAN%3E%0A++%3CDS_MERCHANT_EXPIRYDATE%3E1309%3C%2FDS_MERCHANT_EXPIRYDATE%3E%0A++%3CDS_MERCHANT_CVV2%3E[BLANK]%3C%2FDS_MERCHANT_CVV2%3E%0A%3C%2FDATOSENTRADA%3E%0A + <?xml version='1.0' encoding="ISO-8859-1" ?><RETORNOXML><CODIGO>0</CODIGO><Ds_Version>0.1</Ds_Version><OPERACION><Ds_Amount>100</Ds_Amount><Ds_Currency>978</Ds_Currency><Ds_Order>135214014098</Ds_Order><Ds_Signature>97FBF7E648015AC8AFCA107CD67A1F600FBE9611</Ds_Signature><Ds_MerchantCode>91952713</Ds_MerchantCode><Ds_Terminal>1</Ds_Terminal><Ds_Response>0000</Ds_Response><Ds_AuthorisationCode>701841</Ds_AuthorisationCode><Ds_TransactionType>A</Ds_TransactionType><Ds_SecurePayment>0</Ds_SecurePayment><Ds_Language>1</Ds_Language><Ds_MerchantData></Ds_MerchantData><Ds_Card_Country>724</Ds_Card_Country></OPERACION></RETORNOXML> + PRE_SCRUBBED + end +end diff --git a/test/unit/gateways/redsys_test.rb b/test/unit/gateways/redsys_test.rb index 9f3a8a352e8..741b5ae9eed 100644 --- a/test/unit/gateways/redsys_test.rb +++ b/test/unit/gateways/redsys_test.rb @@ -1,18 +1,21 @@ require 'test_helper' class RedsysTest < Test::Unit::TestCase + include CommStub def setup - Base.gateway_mode = :test + Base.mode = :test @credentials = { :login => '091952713', - :secret_key => "qwertyasdf0123456789", + :secret_key => 'qwertyasdf0123456789', :terminal => '1', } @gateway = RedsysGateway.new(@credentials) @headers = { 'Content-Type' => 'application/x-www-form-urlencoded' } + + @options = { order_id: '1001' } end def test_purchase_payload @@ -20,23 +23,36 @@ def test_purchase_payload @gateway.purchase(123, credit_card, :order_id => '1001') end + def test_purchase_payload_with_credit_card_token + @gateway.expects(:ssl_post).with(RedsysGateway.test_url, purchase_request_with_credit_card_token, @headers).returns(successful_purchase_response) + @gateway.purchase(123, '77bff3a969d6f97b2ec815448cdcff453971f573', :order_id => '1001') + end + def test_successful_purchase - order_id = '1001' @gateway.expects(:ssl_post).returns(successful_purchase_response) - res = @gateway.purchase(123, credit_card, :order_id => order_id) + res = @gateway.purchase(123, credit_card, @options) assert_success res - assert_equal "Transaction Approved", res.message - assert_equal "1001|123|978", res.authorization - assert_equal order_id, res.params['ds_order'] + assert_equal 'Transaction Approved', res.message + assert_equal '1001|123|978', res.authorization + assert_equal '1001', res.params['ds_order'] + end + + def test_successful_purchase_requesting_credit_card_token + @gateway.expects(:ssl_post).returns(successful_purchase_response_with_credit_card_token) + res = @gateway.purchase(123, credit_card, @options) + assert_success res + assert_equal 'Transaction Approved', res.message + assert_equal '141661632759|100|978', res.authorization + assert_equal '141661632759', res.params['ds_order'] + assert_equal '77bff3a969d6f97b2ec815448cdcff453971f573', res.params['ds_merchant_identifier'] end def test_failed_purchase - order_id = '1002' @gateway.expects(:ssl_post).returns(failed_purchase_response) - res = @gateway.purchase(123, credit_card, :order_id => order_id) + res = @gateway.purchase(123, credit_card, @options) assert_failure res - assert_equal "Refusal with no specific reason", res.message - assert_equal order_id, res.params['ds_order'] + assert_equal 'Refusal with no specific reason', res.message + assert_equal '1002', res.params['ds_order'] end def test_purchase_without_order_id @@ -46,11 +62,10 @@ def test_purchase_without_order_id end def test_error_purchase - order_id = '1001' # duplicate! @gateway.expects(:ssl_post).returns(error_purchase_response) - res = @gateway.purchase(123, credit_card, :order_id => order_id) + res = @gateway.purchase(123, credit_card, @options) assert_failure res - assert_equal "SIS0051 ERROR", res.message + assert_equal 'SIS0051 ERROR', res.message end def test_refund_request @@ -59,21 +74,19 @@ def test_refund_request end def test_successful_refund - order_id = '1001' @gateway.expects(:ssl_post).returns(successful_refund_response) - res = @gateway.refund(123, order_id) + res = @gateway.refund(123, '1001') assert_success res - assert_equal "Refund / Confirmation approved", res.message - assert_equal "1001|123|978", res.authorization - assert_equal order_id, res.params['ds_order'] + assert_equal 'Refund / Confirmation approved', res.message + assert_equal '1001|123|978', res.authorization + assert_equal '1001', res.params['ds_order'] end def test_error_refund - order_id = '1001' # duplicate! @gateway.expects(:ssl_post).returns(error_refund_response) - res = @gateway.refund(123, order_id) + res = @gateway.refund(123, '1001') assert_failure res - assert_equal "SIS0057 ERROR", res.message + assert_equal 'SIS0057 ERROR', res.message end # Remaining methods a pretty much the same, so we just test that @@ -83,13 +96,14 @@ def test_authorize @gateway.expects(:ssl_post).with( anything, all_of( - includes(CGI.escape("<DS_MERCHANT_TRANSACTIONTYPE>1</DS_MERCHANT_TRANSACTIONTYPE>")), - includes(CGI.escape("<DS_MERCHANT_PAN>4242424242424242</DS_MERCHANT_PAN>")), - includes(CGI.escape("<DS_MERCHANT_AMOUNT>123</DS_MERCHANT_AMOUNT>")) + includes(CGI.escape('<DS_MERCHANT_TRANSACTIONTYPE>1</DS_MERCHANT_TRANSACTIONTYPE>')), + includes(CGI.escape('<DS_MERCHANT_PAN>4242424242424242</DS_MERCHANT_PAN>')), + includes(CGI.escape('<DS_MERCHANT_AMOUNT>123</DS_MERCHANT_AMOUNT>')) ), anything - ).returns(successful_purchase_response) - @gateway.authorize(123, credit_card, :order_id => '1001') + ).returns(successful_authorize_response) + response = @gateway.authorize(123, credit_card, @options) + assert_success response end def test_authorize_without_order_id @@ -99,21 +113,31 @@ def test_authorize_without_order_id end def test_bad_order_id_format - assert_raise ArgumentError do - @gateway.authorize(123, credit_card, :order_id => "a") - end + stub_comms(@gateway, :ssl_request) do + @gateway.authorize(123, credit_card, order_id: 'Una#cce-ptable44Format') + end.check_request do |method, endpoint, data, headers| + assert_match(/MERCHANT_ORDER%3E\d\d\d\dUnaccept%3C/, data) + end.respond_with(successful_authorize_response) + end + + def test_order_id_numeric_start_but_too_long + stub_comms(@gateway, :ssl_request) do + @gateway.authorize(123, credit_card, order_id: '1234ThisIs]FineButTooLong') + end.check_request do |method, endpoint, data, headers| + assert_match(/MERCHANT_ORDER%3E1234ThisIsFi%3C/, data) + end.respond_with(successful_authorize_response) end def test_capture @gateway.expects(:ssl_post).with( anything, all_of( - includes(CGI.escape("<DS_MERCHANT_TRANSACTIONTYPE>2</DS_MERCHANT_TRANSACTIONTYPE>")), - includes(CGI.escape("<DS_MERCHANT_ORDER>1001</DS_MERCHANT_ORDER>")), - includes(CGI.escape("<DS_MERCHANT_AMOUNT>123</DS_MERCHANT_AMOUNT>")) + includes(CGI.escape('<DS_MERCHANT_TRANSACTIONTYPE>2</DS_MERCHANT_TRANSACTIONTYPE>')), + includes(CGI.escape('<DS_MERCHANT_ORDER>1001</DS_MERCHANT_ORDER>')), + includes(CGI.escape('<DS_MERCHANT_AMOUNT>123</DS_MERCHANT_AMOUNT>')) ), anything - ).returns(successful_purchase_response) + ).returns(successful_capture_response) @gateway.capture(123, '1001') end @@ -121,25 +145,51 @@ def test_void @gateway.expects(:ssl_post).with( anything, all_of( - includes(CGI.escape("<DS_MERCHANT_TRANSACTIONTYPE>9</DS_MERCHANT_TRANSACTIONTYPE>")), - includes(CGI.escape("<DS_MERCHANT_ORDER>1001</DS_MERCHANT_ORDER>")), - includes(CGI.escape("<DS_MERCHANT_AMOUNT>123</DS_MERCHANT_AMOUNT>")), - includes(CGI.escape("<DS_MERCHANT_CURRENCY>978</DS_MERCHANT_CURRENCY>")) + includes(CGI.escape('<DS_MERCHANT_TRANSACTIONTYPE>9</DS_MERCHANT_TRANSACTIONTYPE>')), + includes(CGI.escape('<DS_MERCHANT_ORDER>1001</DS_MERCHANT_ORDER>')), + includes(CGI.escape('<DS_MERCHANT_AMOUNT>123</DS_MERCHANT_AMOUNT>')), + includes(CGI.escape('<DS_MERCHANT_CURRENCY>978</DS_MERCHANT_CURRENCY>')) ), anything - ).returns(successful_purchase_response) + ).returns(successful_void_response) @gateway.void('1001|123|978') end def test_override_currency @gateway.expects(:ssl_post).with( anything, - includes(CGI.escape("<DS_MERCHANT_CURRENCY>840</DS_MERCHANT_CURRENCY>")), + includes(CGI.escape('<DS_MERCHANT_CURRENCY>840</DS_MERCHANT_CURRENCY>')), anything ).returns(successful_purchase_response) @gateway.authorize(123, credit_card, :order_id => '1001', :currency => 'USD') end + def test_successful_verify + @gateway.expects(:ssl_post).times(2).returns(successful_authorize_response).then.returns(successful_void_response) + response = @gateway.verify(credit_card, @options) + assert_success response + end + + def test_successful_verify_with_failed_void + @gateway.expects(:ssl_post).times(2).returns(successful_authorize_response).then.returns(failed_void_response) + response = @gateway.verify(credit_card, @options) + assert_success response + assert_equal 'Transaction Approved', response.message + end + + def test_unsuccessful_verify + @gateway.expects(:ssl_post).returns(failed_authorize_response) + response = @gateway.verify(credit_card, @options) + assert_failure response + assert_equal 'SIS0093 ERROR', response.message + end + + def test_unknown_currency + assert_raise ArgumentError do + @gateway.purchase(123, credit_card, @options.merge(currency: 'HUH WUT')) + end + end + def test_default_currency assert_equal 'EUR', RedsysGateway.default_currency end @@ -158,7 +208,7 @@ def test_using_test_mode end def test_overriding_options - Base.gateway_mode = :production + Base.mode = :production gw = RedsysGateway.new( :terminal => 1, :login => '1234', @@ -170,7 +220,7 @@ def test_overriding_options end def test_production_mode - Base.gateway_mode = :production + Base.mode = :production gw = RedsysGateway.new( :terminal => 1, :login => '1234', @@ -180,19 +230,47 @@ def test_production_mode assert_equal RedsysGateway.live_url, gw.send(:url) end + def test_transcript_scrubbing + assert_equal post_scrubbed, @gateway.scrub(pre_scrubbed) + end + + def test_failed_transaction_transcript_scrubbing + assert_equal failed_transaction_post_scrubbed, @gateway.scrub(failed_transaction_pre_scrubbed) + end + + def test_nil_cvv_transcript_scrubbing + assert_equal nil_cvv_post_scrubbed, @gateway.scrub(nil_cvv_pre_scrubbed) + end + + def test_empty_string_cvv_transcript_scrubbing + assert_equal empty_string_cvv_post_scrubbed, @gateway.scrub(empty_string_cvv_pre_scrubbed) + end + + def test_whitespace_string_cvv_transcript_scrubbing + assert_equal whitespace_string_cvv_post_scrubbed, @gateway.scrub(whitespace_string_cvv_pre_scrubbed) + end + private # Sample response for two main types of operation, # one with card and another without. def purchase_request - "entrada=%3CDATOSENTRADA%3E%0A++%3CDS_Version%3E0.1%3C%2FDS_Version%3E%0A++%3CDS_MERCHANT_CURRENCY%3E978%3C%2FDS_MERCHANT_CURRENCY%3E%0A++%3CDS_MERCHANT_AMOUNT%3E123%3C%2FDS_MERCHANT_AMOUNT%3E%0A++%3CDS_MERCHANT_ORDER%3E1001%3C%2FDS_MERCHANT_ORDER%3E%0A++%3CDS_MERCHANT_TRANSACTIONTYPE%3EA%3C%2FDS_MERCHANT_TRANSACTIONTYPE%3E%0A++%3CDS_MERCHANT_TERMINAL%3E1%3C%2FDS_MERCHANT_TERMINAL%3E%0A++%3CDS_MERCHANT_MERCHANTCODE%3E091952713%3C%2FDS_MERCHANT_MERCHANTCODE%3E%0A++%3CDS_MERCHANT_MERCHANTSIGNATURE%3Eb98b606a6a588d8c45c239f244160efbbe30b4a8%3C%2FDS_MERCHANT_MERCHANTSIGNATURE%3E%0A++%3CDS_MERCHANT_TITULAR%3ELongbob+Longsen%3C%2FDS_MERCHANT_TITULAR%3E%0A++%3CDS_MERCHANT_PAN%3E4242424242424242%3C%2FDS_MERCHANT_PAN%3E%0A++%3CDS_MERCHANT_EXPIRYDATE%3E#{(Time.now.year + 1).to_s.slice(2,2)}09%3C%2FDS_MERCHANT_EXPIRYDATE%3E%0A++%3CDS_MERCHANT_CVV2%3E123%3C%2FDS_MERCHANT_CVV2%3E%0A%3C%2FDATOSENTRADA%3E%0A" + "entrada=%3CDATOSENTRADA%3E%0A++%3CDS_Version%3E0.1%3C%2FDS_Version%3E%0A++%3CDS_MERCHANT_CURRENCY%3E978%3C%2FDS_MERCHANT_CURRENCY%3E%0A++%3CDS_MERCHANT_AMOUNT%3E123%3C%2FDS_MERCHANT_AMOUNT%3E%0A++%3CDS_MERCHANT_ORDER%3E1001%3C%2FDS_MERCHANT_ORDER%3E%0A++%3CDS_MERCHANT_TRANSACTIONTYPE%3EA%3C%2FDS_MERCHANT_TRANSACTIONTYPE%3E%0A++%3CDS_MERCHANT_PRODUCTDESCRIPTION%2F%3E%0A++%3CDS_MERCHANT_TERMINAL%3E1%3C%2FDS_MERCHANT_TERMINAL%3E%0A++%3CDS_MERCHANT_MERCHANTCODE%3E091952713%3C%2FDS_MERCHANT_MERCHANTCODE%3E%0A++%3CDS_MERCHANT_MERCHANTSIGNATURE%3Eb98b606a6a588d8c45c239f244160efbbe30b4a8%3C%2FDS_MERCHANT_MERCHANTSIGNATURE%3E%0A++%3CDS_MERCHANT_TITULAR%3ELongbob+Longsen%3C%2FDS_MERCHANT_TITULAR%3E%0A++%3CDS_MERCHANT_PAN%3E4242424242424242%3C%2FDS_MERCHANT_PAN%3E%0A++%3CDS_MERCHANT_EXPIRYDATE%3E#{(Time.now.year + 1).to_s.slice(2, 2)}09%3C%2FDS_MERCHANT_EXPIRYDATE%3E%0A++%3CDS_MERCHANT_CVV2%3E123%3C%2FDS_MERCHANT_CVV2%3E%0A%3C%2FDATOSENTRADA%3E%0A" + end + + def purchase_request_with_credit_card_token + 'entrada=%3CDATOSENTRADA%3E%0A++%3CDS_Version%3E0.1%3C%2FDS_Version%3E%0A++%3CDS_MERCHANT_CURRENCY%3E978%3C%2FDS_MERCHANT_CURRENCY%3E%0A++%3CDS_MERCHANT_AMOUNT%3E123%3C%2FDS_MERCHANT_AMOUNT%3E%0A++%3CDS_MERCHANT_ORDER%3E1001%3C%2FDS_MERCHANT_ORDER%3E%0A++%3CDS_MERCHANT_TRANSACTIONTYPE%3EA%3C%2FDS_MERCHANT_TRANSACTIONTYPE%3E%0A++%3CDS_MERCHANT_PRODUCTDESCRIPTION%2F%3E%0A++%3CDS_MERCHANT_TERMINAL%3E1%3C%2FDS_MERCHANT_TERMINAL%3E%0A++%3CDS_MERCHANT_MERCHANTCODE%3E091952713%3C%2FDS_MERCHANT_MERCHANTCODE%3E%0A++%3CDS_MERCHANT_MERCHANTSIGNATURE%3Ecbcc0dee5724cd3fff08bbd4371946a0599c7fb9%3C%2FDS_MERCHANT_MERCHANTSIGNATURE%3E%0A++%3CDS_MERCHANT_IDENTIFIER%3E77bff3a969d6f97b2ec815448cdcff453971f573%3C%2FDS_MERCHANT_IDENTIFIER%3E%0A++%3CDS_MERCHANT_DIRECTPAYMENT%3Etrue%3C%2FDS_MERCHANT_DIRECTPAYMENT%3E%0A%3C%2FDATOSENTRADA%3E%0A' end def successful_purchase_response "<?xml version='1.0' encoding=\"ISO-8859-1\" ?><RETORNOXML><CODIGO>0</CODIGO><Ds_Version>0.1</Ds_Version><OPERACION><Ds_Amount>123</Ds_Amount><Ds_Currency>978</Ds_Currency><Ds_Order>1001</Ds_Order><Ds_Signature>989D357BCC9EF0962A456C51422C4FAF4BF4399F</Ds_Signature><Ds_MerchantCode>91952713</Ds_MerchantCode><Ds_Terminal>1</Ds_Terminal><Ds_Response>0000</Ds_Response><Ds_AuthorisationCode>561350</Ds_AuthorisationCode><Ds_TransactionType>A</Ds_TransactionType><Ds_SecurePayment>0</Ds_SecurePayment><Ds_Language>1</Ds_Language><Ds_MerchantData></Ds_MerchantData><Ds_Card_Country>724</Ds_Card_Country></OPERACION></RETORNOXML>" end + def successful_purchase_response_with_credit_card_token + "<?xml version='1.0' encoding=\"ISO-8859-1\" ?><RETORNOXML><CODIGO>0</CODIGO><Ds_Version>0.1</Ds_Version><OPERACION><Ds_Amount>100</Ds_Amount><Ds_Currency>978</Ds_Currency><Ds_Order>141661632759</Ds_Order><Ds_Signature>C65E11D80534B432042ABAA47DCA54F5AFEC23ED</Ds_Signature><Ds_MerchantCode>327234688</Ds_MerchantCode><Ds_Terminal>2</Ds_Terminal><Ds_Response>0000</Ds_Response><Ds_AuthorisationCode>341129</Ds_AuthorisationCode><Ds_TransactionType>A</Ds_TransactionType><Ds_SecurePayment>0</Ds_SecurePayment><Ds_Language>1</Ds_Language><Ds_Merchant_Identifier>77bff3a969d6f97b2ec815448cdcff453971f573</Ds_Merchant_Identifier><Ds_MerchantData></Ds_MerchantData><Ds_Card_Country>724</Ds_Card_Country></OPERACION></RETORNOXML>" + end + def failed_purchase_response "<?xml version='1.0' encoding=\"ISO-8859-1\" ?><RETORNOXML><CODIGO>0</CODIGO><Ds_Version>0.1</Ds_Version><OPERACION><Ds_Amount>123</Ds_Amount><Ds_Currency>978</Ds_Currency><Ds_Order>1002</Ds_Order><Ds_Signature>80D5D1BE64777946519C4E633EE5498C6187747B</Ds_Signature><Ds_MerchantCode>91952713</Ds_MerchantCode><Ds_Terminal>1</Ds_Terminal><Ds_Response>190</Ds_Response><Ds_AuthorisationCode>561350</Ds_AuthorisationCode><Ds_TransactionType>A</Ds_TransactionType><Ds_SecurePayment>0</Ds_SecurePayment><Ds_Language>1</Ds_Language><Ds_MerchantData></Ds_MerchantData><Ds_Card_Country>724</Ds_Card_Country></OPERACION></RETORNOXML>" end @@ -201,8 +279,16 @@ def error_purchase_response "<?xml version='1.0' encoding=\"ISO-8859-1\" ?><RETORNOXML><CODIGO>SIS0051</CODIGO><RECIBIDO><DATOSENTRADA>\n <DS_Version>0.1</DS_Version>\n <DS_MERCHANT_CURRENCY>978</DS_MERCHANT_CURRENCY>\n <DS_MERCHANT_AMOUNT>123</DS_MERCHANT_AMOUNT>\n <DS_MERCHANT_ORDER>1001</DS_MERCHANT_ORDER>\n <DS_MERCHANT_TRANSACTIONTYPE>A</DS_MERCHANT_TRANSACTIONTYPE>\n <DS_MERCHANT_TERMINAL>1</DS_MERCHANT_TERMINAL>\n <DS_MERCHANT_MERCHANTCODE>91952713</DS_MERCHANT_MERCHANTCODE>\n <DS_MERCHANT_MERCHANTSIGNATURE>b5cdaf0f0672be67e6c77f219b63ecbeed1ce525</DS_MERCHANT_MERCHANTSIGNATURE>\n <DS_MERCHANT_TITULAR>Sam Lown</DS_MERCHANT_TITULAR>\n <DS_MERCHANT_PAN>4792587766554414</DS_MERCHANT_PAN>\n <DS_MERCHANT_EXPIRYDATE>1510</DS_MERCHANT_EXPIRYDATE>\n <DS_MERCHANT_CVV2>737</DS_MERCHANT_CVV2>\n</DATOSENTRADA>\n</RECIBIDO></RETORNOXML>\n" end + def successful_authorize_response + "<?xml version='1.0' encoding=\"ISO-8859-1\" ?><RETORNOXML><CODIGO>0</CODIGO><Ds_Version>0.1</Ds_Version><OPERACION><Ds_Amount>100</Ds_Amount><Ds_Currency>978</Ds_Currency><Ds_Order>141278659571</Ds_Order><Ds_Signature>c0d1d069d7ee1443e356927818b2abe281d077e5</Ds_Signature><Ds_MerchantCode>91952713</Ds_MerchantCode><Ds_Terminal>1</Ds_Terminal><Ds_Response>0000</Ds_Response><Ds_AuthorisationCode>385661</Ds_AuthorisationCode><Ds_TransactionType>1</Ds_TransactionType><Ds_SecurePayment>0</Ds_SecurePayment><Ds_Language>1</Ds_Language><Ds_MerchantData></Ds_MerchantData><Ds_Card_Country>724</Ds_Card_Country></OPERACION></RETORNOXML>" + end + + def failed_authorize_response + "<?xml version='1.0' encoding=\"ISO-8859-1\" ?><RETORNOXML><CODIGO>SIS0093</CODIGO><RECIBIDO><DATOSENTRADA>\n <DS_Version>0.1</DS_Version>\n <DS_MERCHANT_CURRENCY>978</DS_MERCHANT_CURRENCY>\n <DS_MERCHANT_AMOUNT>100</DS_MERCHANT_AMOUNT>\n <DS_MERCHANT_ORDER>141278225678</DS_MERCHANT_ORDER>\n <DS_MERCHANT_TRANSACTIONTYPE>1</DS_MERCHANT_TRANSACTIONTYPE>\n <DS_MERCHANT_TERMINAL>1</DS_MERCHANT_TERMINAL>\n <DS_MERCHANT_MERCHANTCODE>91952713</DS_MERCHANT_MERCHANTCODE>\n <DS_MERCHANT_MERCHANTSIGNATURE>1c34699589507802f800b929ea314dc143b0b8a5</DS_MERCHANT_MERCHANTSIGNATURE>\n <DS_MERCHANT_TITULAR>Longbob Longsen</DS_MERCHANT_TITULAR>\n <DS_MERCHANT_PAN>4242424242424242</DS_MERCHANT_PAN>\n <DS_MERCHANT_EXPIRYDATE>1509</DS_MERCHANT_EXPIRYDATE>\n <DS_MERCHANT_CVV2>123</DS_MERCHANT_CVV2>\n</DATOSENTRADA>\n</RECIBIDO></RETORNOXML>" + end + def refund_request - 'entrada=%3CDATOSENTRADA%3E%0A++%3CDS_Version%3E0.1%3C%2FDS_Version%3E%0A++%3CDS_MERCHANT_CURRENCY%3E978%3C%2FDS_MERCHANT_CURRENCY%3E%0A++%3CDS_MERCHANT_AMOUNT%3E123%3C%2FDS_MERCHANT_AMOUNT%3E%0A++%3CDS_MERCHANT_ORDER%3E1001%3C%2FDS_MERCHANT_ORDER%3E%0A++%3CDS_MERCHANT_TRANSACTIONTYPE%3E3%3C%2FDS_MERCHANT_TRANSACTIONTYPE%3E%0A++%3CDS_MERCHANT_TERMINAL%3E1%3C%2FDS_MERCHANT_TERMINAL%3E%0A++%3CDS_MERCHANT_MERCHANTCODE%3E091952713%3C%2FDS_MERCHANT_MERCHANTCODE%3E%0A++%3CDS_MERCHANT_MERCHANTSIGNATURE%3Eba048e120a510e3ef4382bc65e8f29bf132d8ee7%3C%2FDS_MERCHANT_MERCHANTSIGNATURE%3E%0A%3C%2FDATOSENTRADA%3E%0A' + 'entrada=%3CDATOSENTRADA%3E%0A++%3CDS_Version%3E0.1%3C%2FDS_Version%3E%0A++%3CDS_MERCHANT_CURRENCY%3E978%3C%2FDS_MERCHANT_CURRENCY%3E%0A++%3CDS_MERCHANT_AMOUNT%3E123%3C%2FDS_MERCHANT_AMOUNT%3E%0A++%3CDS_MERCHANT_ORDER%3E1001%3C%2FDS_MERCHANT_ORDER%3E%0A++%3CDS_MERCHANT_TRANSACTIONTYPE%3E3%3C%2FDS_MERCHANT_TRANSACTIONTYPE%3E%0A++%3CDS_MERCHANT_PRODUCTDESCRIPTION%2F%3E%0A++%3CDS_MERCHANT_TERMINAL%3E1%3C%2FDS_MERCHANT_TERMINAL%3E%0A++%3CDS_MERCHANT_MERCHANTCODE%3E091952713%3C%2FDS_MERCHANT_MERCHANTCODE%3E%0A++%3CDS_MERCHANT_MERCHANTSIGNATURE%3Eba048e120a510e3ef4382bc65e8f29bf132d8ee7%3C%2FDS_MERCHANT_MERCHANTSIGNATURE%3E%0A%3C%2FDATOSENTRADA%3E%0A' end def successful_refund_response @@ -213,4 +299,85 @@ def error_refund_response "<?xml version='1.0' encoding=\"ISO-8859-1\" ?><RETORNOXML><CODIGO>SIS0057</CODIGO><RECIBIDO><DATOSENTRADA>\n <DS_Version>0.1</DS_Version>\n <DS_MERCHANT_CURRENCY>978</DS_MERCHANT_CURRENCY>\n <DS_MERCHANT_AMOUNT>123</DS_MERCHANT_AMOUNT>\n <DS_MERCHANT_ORDER>1001</DS_MERCHANT_ORDER>\n <DS_MERCHANT_TRANSACTIONTYPE>3</DS_MERCHANT_TRANSACTIONTYPE>\n <DS_MERCHANT_TERMINAL>1</DS_MERCHANT_TERMINAL>\n <DS_MERCHANT_MERCHANTCODE>91952713</DS_MERCHANT_MERCHANTCODE>\n <DS_MERCHANT_MERCHANTSIGNATURE>9e12f1607147b4611bfdbff80aa143241c27f935</DS_MERCHANT_MERCHANTSIGNATURE>\n</DATOSENTRADA>\n</RECIBIDO></RETORNOXML>\n" end + def successful_void_response + "<?xml version='1.0' encoding=\"ISO-8859-1\" ?><RETORNOXML><CODIGO>0</CODIGO><Ds_Version>0.1</Ds_Version><OPERACION><Ds_Amount>100</Ds_Amount><Ds_Currency>978</Ds_Currency><Ds_Order>141278288802</Ds_Order><Ds_Signature>A29ED7C7E46ED8F4A5F4F05ED7BA0BF83149E062</Ds_Signature><Ds_MerchantCode>91952713</Ds_MerchantCode><Ds_Terminal>1</Ds_Terminal><Ds_Response>0400</Ds_Response><Ds_AuthorisationCode>385387</Ds_AuthorisationCode><Ds_TransactionType>9</Ds_TransactionType><Ds_SecurePayment>0</Ds_SecurePayment><Ds_Language>1</Ds_Language><Ds_MerchantData></Ds_MerchantData><Ds_Card_Country>724</Ds_Card_Country></OPERACION></RETORNOXML>" + end + + def failed_void_response + "<?xml version='1.0' encoding=\"ISO-8859-1\" ?><RETORNOXML><CODIGO>SIS0222</CODIGO><RECIBIDO><DATOSENTRADA>\n <DS_Version>0.1</DS_Version>\n <DS_MERCHANT_CURRENCY>978</DS_MERCHANT_CURRENCY>\n <DS_MERCHANT_AMOUNT>100</DS_MERCHANT_AMOUNT>\n <DS_MERCHANT_ORDER>141278298713</DS_MERCHANT_ORDER>\n <DS_MERCHANT_TRANSACTIONTYPE>9</DS_MERCHANT_TRANSACTIONTYPE>\n <DS_MERCHANT_TERMINAL>1</DS_MERCHANT_TERMINAL>\n <DS_MERCHANT_MERCHANTCODE>91952713</DS_MERCHANT_MERCHANTCODE>\n <DS_MERCHANT_MERCHANTSIGNATURE>ead33f15453316e86dfc51642e400e2467fe71bb</DS_MERCHANT_MERCHANTSIGNATURE>\n</DATOSENTRADA>\n</RECIBIDO></RETORNOXML>" + end + + def successful_capture_response + "<?xml version='1.0' encoding=\"ISO-8859-1\" ?><RETORNOXML><CODIGO>0</CODIGO><Ds_Version>0.1</Ds_Version><OPERACION><Ds_Amount>100</Ds_Amount><Ds_Currency>978</Ds_Currency><Ds_Order>141278546960</Ds_Order><Ds_Signature>D138D4178F28BC71661B8015CFBB2656AFF8F722</Ds_Signature><Ds_MerchantCode>91952713</Ds_MerchantCode><Ds_Terminal>1</Ds_Terminal><Ds_Response>0900</Ds_Response><Ds_AuthorisationCode>385584</Ds_AuthorisationCode><Ds_TransactionType>2</Ds_TransactionType><Ds_SecurePayment>0</Ds_SecurePayment><Ds_Language>1</Ds_Language><Ds_MerchantData></Ds_MerchantData><Ds_Card_Country>724</Ds_Card_Country></OPERACION></RETORNOXML>" + end + + def pre_scrubbed + <<-PRE_SCRUBBED + entrada=%3CDATOSENTRADA%3E%0A++%3CDS_Version%3E0.1%3C%2FDS_Version%3E%0A++%3CDS_MERCHANT_CURRENCY%3E978%3C%2FDS_MERCHANT_CURRENCY%3E%0A++%3CDS_MERCHANT_AMOUNT%3E100%3C%2FDS_MERCHANT_AMOUNT%3E%0A++%3CDS_MERCHANT_ORDER%3E135214014098%3C%2FDS_MERCHANT_ORDER%3E%0A++%3CDS_MERCHANT_TRANSACTIONTYPE%3EA%3C%2FDS_MERCHANT_TRANSACTIONTYPE%3E%0A++%3CDS_MERCHANT_TERMINAL%3E1%3C%2FDS_MERCHANT_TERMINAL%3E%0A++%3CDS_MERCHANT_MERCHANTCODE%3E91952713%3C%2FDS_MERCHANT_MERCHANTCODE%3E%0A++%3CDS_MERCHANT_MERCHANTSIGNATURE%3E39589b03cdd3c525885cdb3b3761e2fb7a8be9ee%3C%2FDS_MERCHANT_MERCHANTSIGNATURE%3E%0A++%3CDS_MERCHANT_TITULAR%3ELongbob+Longsen%3C%2FDS_MERCHANT_TITULAR%3E%0A++%3CDS_MERCHANT_PAN%3E4548812049400004%3C%2FDS_MERCHANT_PAN%3E%0A++%3CDS_MERCHANT_EXPIRYDATE%3E1309%3C%2FDS_MERCHANT_EXPIRYDATE%3E%0A++%3CDS_MERCHANT_CVV2%3E123%3C%2FDS_MERCHANT_CVV2%3E%0A%3C%2FDATOSENTRADA%3E%0A + <?xml version='1.0' encoding="ISO-8859-1" ?><RETORNOXML><CODIGO>0</CODIGO><Ds_Version>0.1</Ds_Version><OPERACION><Ds_Amount>100</Ds_Amount><Ds_Currency>978</Ds_Currency><Ds_Order>135214014098</Ds_Order><Ds_Signature>97FBF7E648015AC8AFCA107CD67A1F600FBE9611</Ds_Signature><Ds_MerchantCode>91952713</Ds_MerchantCode><Ds_Terminal>1</Ds_Terminal><Ds_Response>0000</Ds_Response><Ds_AuthorisationCode>701841</Ds_AuthorisationCode><Ds_TransactionType>A</Ds_TransactionType><Ds_SecurePayment>0</Ds_SecurePayment><Ds_Language>1</Ds_Language><Ds_MerchantData></Ds_MerchantData><Ds_Card_Country>724</Ds_Card_Country></OPERACION></RETORNOXML> + PRE_SCRUBBED + end + + def post_scrubbed + <<-POST_SCRUBBED + entrada=%3CDATOSENTRADA%3E%0A++%3CDS_Version%3E0.1%3C%2FDS_Version%3E%0A++%3CDS_MERCHANT_CURRENCY%3E978%3C%2FDS_MERCHANT_CURRENCY%3E%0A++%3CDS_MERCHANT_AMOUNT%3E100%3C%2FDS_MERCHANT_AMOUNT%3E%0A++%3CDS_MERCHANT_ORDER%3E135214014098%3C%2FDS_MERCHANT_ORDER%3E%0A++%3CDS_MERCHANT_TRANSACTIONTYPE%3EA%3C%2FDS_MERCHANT_TRANSACTIONTYPE%3E%0A++%3CDS_MERCHANT_TERMINAL%3E1%3C%2FDS_MERCHANT_TERMINAL%3E%0A++%3CDS_MERCHANT_MERCHANTCODE%3E91952713%3C%2FDS_MERCHANT_MERCHANTCODE%3E%0A++%3CDS_MERCHANT_MERCHANTSIGNATURE%3E39589b03cdd3c525885cdb3b3761e2fb7a8be9ee%3C%2FDS_MERCHANT_MERCHANTSIGNATURE%3E%0A++%3CDS_MERCHANT_TITULAR%3ELongbob+Longsen%3C%2FDS_MERCHANT_TITULAR%3E%0A++%3CDS_MERCHANT_PAN%3E[FILTERED]%3C%2FDS_MERCHANT_PAN%3E%0A++%3CDS_MERCHANT_EXPIRYDATE%3E1309%3C%2FDS_MERCHANT_EXPIRYDATE%3E%0A++%3CDS_MERCHANT_CVV2%3E[FILTERED]%3C%2FDS_MERCHANT_CVV2%3E%0A%3C%2FDATOSENTRADA%3E%0A + <?xml version='1.0' encoding="ISO-8859-1" ?><RETORNOXML><CODIGO>0</CODIGO><Ds_Version>0.1</Ds_Version><OPERACION><Ds_Amount>100</Ds_Amount><Ds_Currency>978</Ds_Currency><Ds_Order>135214014098</Ds_Order><Ds_Signature>97FBF7E648015AC8AFCA107CD67A1F600FBE9611</Ds_Signature><Ds_MerchantCode>91952713</Ds_MerchantCode><Ds_Terminal>1</Ds_Terminal><Ds_Response>0000</Ds_Response><Ds_AuthorisationCode>701841</Ds_AuthorisationCode><Ds_TransactionType>A</Ds_TransactionType><Ds_SecurePayment>0</Ds_SecurePayment><Ds_Language>1</Ds_Language><Ds_MerchantData></Ds_MerchantData><Ds_Card_Country>724</Ds_Card_Country></OPERACION></RETORNOXML> + POST_SCRUBBED + end + + def failed_transaction_pre_scrubbed + %q( +POST /sis/operaciones HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: sis-t.redsys.es:25443\r\nContent-Length: 969\r\n\r\n"<- "entrada=%3CDATOSENTRADA%3E%0A++%3CDS_Version%3E0.1%3C%2FDS_Version%3E%0A++%3CDS_MERCHANT_CURRENCY%3E978%3C%2FDS_MERCHANT_CURRENCY%3E%0A++%3CDS_MERCHANT_AMOUNT%3E%3C%2FDS_MERCHANT_AMOUNT%3E%0A++%3CDS_MERCHANT_ORDER%3E144009991943%3C%2FDS_MERCHANT_ORDER%3E%0A++%3CDS_MERCHANT_TRANSACTIONTYPE%3EA%3C%2FDS_MERCHANT_TRANSACTIONTYPE%3E%0A++%3CDS_MERCHANT_PRODUCTDESCRIPTION%3ETest+Description%3C%2FDS_MERCHANT_PRODUCTDESCRIPTION%3E%0A++%3CDS_MERCHANT_TERMINAL%3E1%3C%2FDS_MERCHANT_TERMINAL%3E%0A++%3CDS_MERCHANT_MERCHANTCODE%3E91952713%3C%2FDS_MERCHANT_MERCHANTCODE%3E%0A++%3CDS_MERCHANT_MERCHANTSIGNATURE%3E2bf324cba60dcdd9e2c1bc8de2458a6ed168778f%3C%2FDS_MERCHANT_MERCHANTSIGNATURE%3E%0A++%3CDS_MERCHANT_TITULAR%3ELongbob+Longsen%3C%2FDS_MERCHANT_TITULAR%3E%0A++%3CDS_MERCHANT_PAN%3E4548812049400004%3C%2FDS_MERCHANT_PAN%3E%0A++%3CDS_MERCHANT_EXPIRYDATE%3E1609%3C%2FDS_MERCHANT_EXPIRYDATE%3E%0A++%3CDS_MERCHANT_CVV2%3E123%3C%2FDS_MERCHANT_CVV2%3E%0A%3C%2FDATOSENTRADA%3E%0A +<?xml version='1.0' encoding=\"ISO-8859-1\" ?><RETORNOXML><CODIGO>SIS0018</CODIGO><RECIBIDO><DATOSENTRADA>\n <DS_Version>0.1</DS_Version>\n <DS_MERCHANT_CURRENCY>978</DS_MERCHANT_CURRENCY>\n <DS_MERCHANT_AMOUNT></DS_MERCHANT_AMOUNT>\n <DS_MERCHANT_ORDER>144009991943</DS_MERCHANT_ORDER>\n <DS_MERCHANT_TRANSACTIONTYPE>A</DS_MERCHANT_TRANSACTIONTYPE>\n <DS_MERCHANT_PRODUCTDESCRIPTION>Test Description</DS_MERCHANT_PRODUCTDESCRIPTION>\n <DS_MERCHANT_TERMINAL>1</DS_MERCHANT_TERMINAL>\n <DS_MERCHANT_MERCHANTCODE>91952713</DS_MERCHANT_MERCHANTCODE>\n <DS_MERCHANT_MERCHANTSIGNATURE>2bf324cba60dcdd9e2c1bc8de2458a6ed168778f</DS_MERCHANT_MERCHANTSIGNATURE>\n <DS_MERCHANT_TITULAR>Longbob Longsen</DS_MERCHANT_TITULAR>\n <DS_MERCHANT_PAN>4548812049400004</DS_MERCHANT_PAN>\n <DS_MERCHANT_EXPIRYDATE>1609</DS_MERCHANT_EXPIRYDATE>\n <DS_MERCHANT_CVV2>123</DS_MERCHANT_CVV2>\n</DATOSENTRADA>\n</RECIBIDO></RETORNOXML>\n + ) + end + + def failed_transaction_post_scrubbed + %q( +POST /sis/operaciones HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: sis-t.redsys.es:25443\r\nContent-Length: 969\r\n\r\n"<- "entrada=%3CDATOSENTRADA%3E%0A++%3CDS_Version%3E0.1%3C%2FDS_Version%3E%0A++%3CDS_MERCHANT_CURRENCY%3E978%3C%2FDS_MERCHANT_CURRENCY%3E%0A++%3CDS_MERCHANT_AMOUNT%3E%3C%2FDS_MERCHANT_AMOUNT%3E%0A++%3CDS_MERCHANT_ORDER%3E144009991943%3C%2FDS_MERCHANT_ORDER%3E%0A++%3CDS_MERCHANT_TRANSACTIONTYPE%3EA%3C%2FDS_MERCHANT_TRANSACTIONTYPE%3E%0A++%3CDS_MERCHANT_PRODUCTDESCRIPTION%3ETest+Description%3C%2FDS_MERCHANT_PRODUCTDESCRIPTION%3E%0A++%3CDS_MERCHANT_TERMINAL%3E1%3C%2FDS_MERCHANT_TERMINAL%3E%0A++%3CDS_MERCHANT_MERCHANTCODE%3E91952713%3C%2FDS_MERCHANT_MERCHANTCODE%3E%0A++%3CDS_MERCHANT_MERCHANTSIGNATURE%3E2bf324cba60dcdd9e2c1bc8de2458a6ed168778f%3C%2FDS_MERCHANT_MERCHANTSIGNATURE%3E%0A++%3CDS_MERCHANT_TITULAR%3ELongbob+Longsen%3C%2FDS_MERCHANT_TITULAR%3E%0A++%3CDS_MERCHANT_PAN%3E[FILTERED]%3C%2FDS_MERCHANT_PAN%3E%0A++%3CDS_MERCHANT_EXPIRYDATE%3E1609%3C%2FDS_MERCHANT_EXPIRYDATE%3E%0A++%3CDS_MERCHANT_CVV2%3E[FILTERED]%3C%2FDS_MERCHANT_CVV2%3E%0A%3C%2FDATOSENTRADA%3E%0A +<?xml version='1.0' encoding=\"ISO-8859-1\" ?><RETORNOXML><CODIGO>SIS0018</CODIGO><RECIBIDO><DATOSENTRADA>\n <DS_Version>0.1</DS_Version>\n <DS_MERCHANT_CURRENCY>978</DS_MERCHANT_CURRENCY>\n <DS_MERCHANT_AMOUNT></DS_MERCHANT_AMOUNT>\n <DS_MERCHANT_ORDER>144009991943</DS_MERCHANT_ORDER>\n <DS_MERCHANT_TRANSACTIONTYPE>A</DS_MERCHANT_TRANSACTIONTYPE>\n <DS_MERCHANT_PRODUCTDESCRIPTION>Test Description</DS_MERCHANT_PRODUCTDESCRIPTION>\n <DS_MERCHANT_TERMINAL>1</DS_MERCHANT_TERMINAL>\n <DS_MERCHANT_MERCHANTCODE>91952713</DS_MERCHANT_MERCHANTCODE>\n <DS_MERCHANT_MERCHANTSIGNATURE>2bf324cba60dcdd9e2c1bc8de2458a6ed168778f</DS_MERCHANT_MERCHANTSIGNATURE>\n <DS_MERCHANT_TITULAR>Longbob Longsen</DS_MERCHANT_TITULAR>\n <DS_MERCHANT_PAN>[FILTERED]</DS_MERCHANT_PAN>\n <DS_MERCHANT_EXPIRYDATE>1609</DS_MERCHANT_EXPIRYDATE>\n <DS_MERCHANT_CVV2>[FILTERED]</DS_MERCHANT_CVV2>\n</DATOSENTRADA>\n</RECIBIDO></RETORNOXML>\n + ) + end + + def nil_cvv_pre_scrubbed + <<-PRE_SCRUBBED + entrada=%3CDATOSENTRADA%3E%0A++%3CDS_Version%3E0.1%3C%2FDS_Version%3E%0A++%3CDS_MERCHANT_CURRENCY%3E978%3C%2FDS_MERCHANT_CURRENCY%3E%0A++%3CDS_MERCHANT_AMOUNT%3E100%3C%2FDS_MERCHANT_AMOUNT%3E%0A++%3CDS_MERCHANT_ORDER%3E135214014098%3C%2FDS_MERCHANT_ORDER%3E%0A++%3CDS_MERCHANT_TRANSACTIONTYPE%3EA%3C%2FDS_MERCHANT_TRANSACTIONTYPE%3E%0A++%3CDS_MERCHANT_TERMINAL%3E1%3C%2FDS_MERCHANT_TERMINAL%3E%0A++%3CDS_MERCHANT_MERCHANTCODE%3E91952713%3C%2FDS_MERCHANT_MERCHANTCODE%3E%0A++%3CDS_MERCHANT_MERCHANTSIGNATURE%3E39589b03cdd3c525885cdb3b3761e2fb7a8be9ee%3C%2FDS_MERCHANT_MERCHANTSIGNATURE%3E%0A++%3CDS_MERCHANT_TITULAR%3ELongbob+Longsen%3C%2FDS_MERCHANT_TITULAR%3E%0A++%3CDS_MERCHANT_PAN%3E4548812049400004%3C%2FDS_MERCHANT_PAN%3E%0A++%3CDS_MERCHANT_EXPIRYDATE%3E1309%3C%2FDS_MERCHANT_EXPIRYDATE%3E%0A++%3CDS_MERCHANT_CVV2%2F%3E%0A%3C%2FDATOSENTRADA%3E%0A + <?xml version='1.0' encoding="ISO-8859-1" ?><RETORNOXML><CODIGO>0</CODIGO><Ds_Version>0.1</Ds_Version><OPERACION><Ds_Amount>100</Ds_Amount><Ds_Currency>978</Ds_Currency><Ds_Order>135214014098</Ds_Order><Ds_Signature>97FBF7E648015AC8AFCA107CD67A1F600FBE9611</Ds_Signature><Ds_MerchantCode>91952713</Ds_MerchantCode><Ds_Terminal>1</Ds_Terminal><Ds_Response>0000</Ds_Response><Ds_AuthorisationCode>701841</Ds_AuthorisationCode><Ds_TransactionType>A</Ds_TransactionType><Ds_SecurePayment>0</Ds_SecurePayment><Ds_Language>1</Ds_Language><Ds_MerchantData></Ds_MerchantData><Ds_Card_Country>724</Ds_Card_Country></OPERACION></RETORNOXML> + PRE_SCRUBBED + end + + def nil_cvv_post_scrubbed + <<-POST_SCRUBBED + entrada=%3CDATOSENTRADA%3E%0A++%3CDS_Version%3E0.1%3C%2FDS_Version%3E%0A++%3CDS_MERCHANT_CURRENCY%3E978%3C%2FDS_MERCHANT_CURRENCY%3E%0A++%3CDS_MERCHANT_AMOUNT%3E100%3C%2FDS_MERCHANT_AMOUNT%3E%0A++%3CDS_MERCHANT_ORDER%3E135214014098%3C%2FDS_MERCHANT_ORDER%3E%0A++%3CDS_MERCHANT_TRANSACTIONTYPE%3EA%3C%2FDS_MERCHANT_TRANSACTIONTYPE%3E%0A++%3CDS_MERCHANT_TERMINAL%3E1%3C%2FDS_MERCHANT_TERMINAL%3E%0A++%3CDS_MERCHANT_MERCHANTCODE%3E91952713%3C%2FDS_MERCHANT_MERCHANTCODE%3E%0A++%3CDS_MERCHANT_MERCHANTSIGNATURE%3E39589b03cdd3c525885cdb3b3761e2fb7a8be9ee%3C%2FDS_MERCHANT_MERCHANTSIGNATURE%3E%0A++%3CDS_MERCHANT_TITULAR%3ELongbob+Longsen%3C%2FDS_MERCHANT_TITULAR%3E%0A++%3CDS_MERCHANT_PAN%3E[FILTERED]%3C%2FDS_MERCHANT_PAN%3E%0A++%3CDS_MERCHANT_EXPIRYDATE%3E1309%3C%2FDS_MERCHANT_EXPIRYDATE%3E%0A++%3CDS_MERCHANT_CVV2[BLANK]DATOSENTRADA%3E%0A + <?xml version='1.0' encoding="ISO-8859-1" ?><RETORNOXML><CODIGO>0</CODIGO><Ds_Version>0.1</Ds_Version><OPERACION><Ds_Amount>100</Ds_Amount><Ds_Currency>978</Ds_Currency><Ds_Order>135214014098</Ds_Order><Ds_Signature>97FBF7E648015AC8AFCA107CD67A1F600FBE9611</Ds_Signature><Ds_MerchantCode>91952713</Ds_MerchantCode><Ds_Terminal>1</Ds_Terminal><Ds_Response>0000</Ds_Response><Ds_AuthorisationCode>701841</Ds_AuthorisationCode><Ds_TransactionType>A</Ds_TransactionType><Ds_SecurePayment>0</Ds_SecurePayment><Ds_Language>1</Ds_Language><Ds_MerchantData></Ds_MerchantData><Ds_Card_Country>724</Ds_Card_Country></OPERACION></RETORNOXML> + POST_SCRUBBED + end + + def empty_string_cvv_pre_scrubbed + <<-PRE_SCRUBBED + entrada=%3CDATOSENTRADA%3E%0A++%3CDS_Version%3E0.1%3C%2FDS_Version%3E%0A++%3CDS_MERCHANT_CURRENCY%3E978%3C%2FDS_MERCHANT_CURRENCY%3E%0A++%3CDS_MERCHANT_AMOUNT%3E100%3C%2FDS_MERCHANT_AMOUNT%3E%0A++%3CDS_MERCHANT_ORDER%3E135214014098%3C%2FDS_MERCHANT_ORDER%3E%0A++%3CDS_MERCHANT_TRANSACTIONTYPE%3EA%3C%2FDS_MERCHANT_TRANSACTIONTYPE%3E%0A++%3CDS_MERCHANT_TERMINAL%3E1%3C%2FDS_MERCHANT_TERMINAL%3E%0A++%3CDS_MERCHANT_MERCHANTCODE%3E91952713%3C%2FDS_MERCHANT_MERCHANTCODE%3E%0A++%3CDS_MERCHANT_MERCHANTSIGNATURE%3E39589b03cdd3c525885cdb3b3761e2fb7a8be9ee%3C%2FDS_MERCHANT_MERCHANTSIGNATURE%3E%0A++%3CDS_MERCHANT_TITULAR%3ELongbob+Longsen%3C%2FDS_MERCHANT_TITULAR%3E%0A++%3CDS_MERCHANT_PAN%3E4548812049400004%3C%2FDS_MERCHANT_PAN%3E%0A++%3CDS_MERCHANT_EXPIRYDATE%3E1309%3C%2FDS_MERCHANT_EXPIRYDATE%3E%0A++%3CDS_MERCHANT_CVV2%3E%3C%2FDS_MERCHANT_CVV2%3E%0A%3C%2FDATOSENTRADA%3E%0A + <?xml version='1.0' encoding="ISO-8859-1" ?><RETORNOXML><CODIGO>0</CODIGO><Ds_Version>0.1</Ds_Version><OPERACION><Ds_Amount>100</Ds_Amount><Ds_Currency>978</Ds_Currency><Ds_Order>135214014098</Ds_Order><Ds_Signature>97FBF7E648015AC8AFCA107CD67A1F600FBE9611</Ds_Signature><Ds_MerchantCode>91952713</Ds_MerchantCode><Ds_Terminal>1</Ds_Terminal><Ds_Response>0000</Ds_Response><Ds_AuthorisationCode>701841</Ds_AuthorisationCode><Ds_TransactionType>A</Ds_TransactionType><Ds_SecurePayment>0</Ds_SecurePayment><Ds_Language>1</Ds_Language><Ds_MerchantData></Ds_MerchantData><Ds_Card_Country>724</Ds_Card_Country></OPERACION></RETORNOXML> + PRE_SCRUBBED + end + + def empty_string_cvv_post_scrubbed + <<-PRE_SCRUBBED + entrada=%3CDATOSENTRADA%3E%0A++%3CDS_Version%3E0.1%3C%2FDS_Version%3E%0A++%3CDS_MERCHANT_CURRENCY%3E978%3C%2FDS_MERCHANT_CURRENCY%3E%0A++%3CDS_MERCHANT_AMOUNT%3E100%3C%2FDS_MERCHANT_AMOUNT%3E%0A++%3CDS_MERCHANT_ORDER%3E135214014098%3C%2FDS_MERCHANT_ORDER%3E%0A++%3CDS_MERCHANT_TRANSACTIONTYPE%3EA%3C%2FDS_MERCHANT_TRANSACTIONTYPE%3E%0A++%3CDS_MERCHANT_TERMINAL%3E1%3C%2FDS_MERCHANT_TERMINAL%3E%0A++%3CDS_MERCHANT_MERCHANTCODE%3E91952713%3C%2FDS_MERCHANT_MERCHANTCODE%3E%0A++%3CDS_MERCHANT_MERCHANTSIGNATURE%3E39589b03cdd3c525885cdb3b3761e2fb7a8be9ee%3C%2FDS_MERCHANT_MERCHANTSIGNATURE%3E%0A++%3CDS_MERCHANT_TITULAR%3ELongbob+Longsen%3C%2FDS_MERCHANT_TITULAR%3E%0A++%3CDS_MERCHANT_PAN%3E[FILTERED]%3C%2FDS_MERCHANT_PAN%3E%0A++%3CDS_MERCHANT_EXPIRYDATE%3E1309%3C%2FDS_MERCHANT_EXPIRYDATE%3E%0A++%3CDS_MERCHANT_CVV2%3E[BLANK]%3C%2FDS_MERCHANT_CVV2%3E%0A%3C%2FDATOSENTRADA%3E%0A + <?xml version='1.0' encoding="ISO-8859-1" ?><RETORNOXML><CODIGO>0</CODIGO><Ds_Version>0.1</Ds_Version><OPERACION><Ds_Amount>100</Ds_Amount><Ds_Currency>978</Ds_Currency><Ds_Order>135214014098</Ds_Order><Ds_Signature>97FBF7E648015AC8AFCA107CD67A1F600FBE9611</Ds_Signature><Ds_MerchantCode>91952713</Ds_MerchantCode><Ds_Terminal>1</Ds_Terminal><Ds_Response>0000</Ds_Response><Ds_AuthorisationCode>701841</Ds_AuthorisationCode><Ds_TransactionType>A</Ds_TransactionType><Ds_SecurePayment>0</Ds_SecurePayment><Ds_Language>1</Ds_Language><Ds_MerchantData></Ds_MerchantData><Ds_Card_Country>724</Ds_Card_Country></OPERACION></RETORNOXML> + PRE_SCRUBBED + end + + def whitespace_string_cvv_pre_scrubbed + <<-PRE_SCRUBBED + entrada=%3CDATOSENTRADA%3E%0A++%3CDS_Version%3E0.1%3C%2FDS_Version%3E%0A++%3CDS_MERCHANT_CURRENCY%3E978%3C%2FDS_MERCHANT_CURRENCY%3E%0A++%3CDS_MERCHANT_AMOUNT%3E100%3C%2FDS_MERCHANT_AMOUNT%3E%0A++%3CDS_MERCHANT_ORDER%3E135214014098%3C%2FDS_MERCHANT_ORDER%3E%0A++%3CDS_MERCHANT_TRANSACTIONTYPE%3EA%3C%2FDS_MERCHANT_TRANSACTIONTYPE%3E%0A++%3CDS_MERCHANT_TERMINAL%3E1%3C%2FDS_MERCHANT_TERMINAL%3E%0A++%3CDS_MERCHANT_MERCHANTCODE%3E91952713%3C%2FDS_MERCHANT_MERCHANTCODE%3E%0A++%3CDS_MERCHANT_MERCHANTSIGNATURE%3E39589b03cdd3c525885cdb3b3761e2fb7a8be9ee%3C%2FDS_MERCHANT_MERCHANTSIGNATURE%3E%0A++%3CDS_MERCHANT_TITULAR%3ELongbob+Longsen%3C%2FDS_MERCHANT_TITULAR%3E%0A++%3CDS_MERCHANT_PAN%3E4548812049400004%3C%2FDS_MERCHANT_PAN%3E%0A++%3CDS_MERCHANT_EXPIRYDATE%3E1309%3C%2FDS_MERCHANT_EXPIRYDATE%3E%0A++%3CDS_MERCHANT_CVV2%3E+++%3C%2FDS_MERCHANT_CVV2%3E%0A%3C%2FDATOSENTRADA%3E%0A + <?xml version='1.0' encoding="ISO-8859-1" ?><RETORNOXML><CODIGO>0</CODIGO><Ds_Version>0.1</Ds_Version><OPERACION><Ds_Amount>100</Ds_Amount><Ds_Currency>978</Ds_Currency><Ds_Order>135214014098</Ds_Order><Ds_Signature>97FBF7E648015AC8AFCA107CD67A1F600FBE9611</Ds_Signature><Ds_MerchantCode>91952713</Ds_MerchantCode><Ds_Terminal>1</Ds_Terminal><Ds_Response>0000</Ds_Response><Ds_AuthorisationCode>701841</Ds_AuthorisationCode><Ds_TransactionType>A</Ds_TransactionType><Ds_SecurePayment>0</Ds_SecurePayment><Ds_Language>1</Ds_Language><Ds_MerchantData></Ds_MerchantData><Ds_Card_Country>724</Ds_Card_Country></OPERACION></RETORNOXML> + PRE_SCRUBBED + end + + def whitespace_string_cvv_post_scrubbed + <<-PRE_SCRUBBED + entrada=%3CDATOSENTRADA%3E%0A++%3CDS_Version%3E0.1%3C%2FDS_Version%3E%0A++%3CDS_MERCHANT_CURRENCY%3E978%3C%2FDS_MERCHANT_CURRENCY%3E%0A++%3CDS_MERCHANT_AMOUNT%3E100%3C%2FDS_MERCHANT_AMOUNT%3E%0A++%3CDS_MERCHANT_ORDER%3E135214014098%3C%2FDS_MERCHANT_ORDER%3E%0A++%3CDS_MERCHANT_TRANSACTIONTYPE%3EA%3C%2FDS_MERCHANT_TRANSACTIONTYPE%3E%0A++%3CDS_MERCHANT_TERMINAL%3E1%3C%2FDS_MERCHANT_TERMINAL%3E%0A++%3CDS_MERCHANT_MERCHANTCODE%3E91952713%3C%2FDS_MERCHANT_MERCHANTCODE%3E%0A++%3CDS_MERCHANT_MERCHANTSIGNATURE%3E39589b03cdd3c525885cdb3b3761e2fb7a8be9ee%3C%2FDS_MERCHANT_MERCHANTSIGNATURE%3E%0A++%3CDS_MERCHANT_TITULAR%3ELongbob+Longsen%3C%2FDS_MERCHANT_TITULAR%3E%0A++%3CDS_MERCHANT_PAN%3E[FILTERED]%3C%2FDS_MERCHANT_PAN%3E%0A++%3CDS_MERCHANT_EXPIRYDATE%3E1309%3C%2FDS_MERCHANT_EXPIRYDATE%3E%0A++%3CDS_MERCHANT_CVV2%3E[BLANK]%3C%2FDS_MERCHANT_CVV2%3E%0A%3C%2FDATOSENTRADA%3E%0A + <?xml version='1.0' encoding="ISO-8859-1" ?><RETORNOXML><CODIGO>0</CODIGO><Ds_Version>0.1</Ds_Version><OPERACION><Ds_Amount>100</Ds_Amount><Ds_Currency>978</Ds_Currency><Ds_Order>135214014098</Ds_Order><Ds_Signature>97FBF7E648015AC8AFCA107CD67A1F600FBE9611</Ds_Signature><Ds_MerchantCode>91952713</Ds_MerchantCode><Ds_Terminal>1</Ds_Terminal><Ds_Response>0000</Ds_Response><Ds_AuthorisationCode>701841</Ds_AuthorisationCode><Ds_TransactionType>A</Ds_TransactionType><Ds_SecurePayment>0</Ds_SecurePayment><Ds_Language>1</Ds_Language><Ds_MerchantData></Ds_MerchantData><Ds_Card_Country>724</Ds_Card_Country></OPERACION></RETORNOXML> + PRE_SCRUBBED + end end diff --git a/test/unit/gateways/s5_test.rb b/test/unit/gateways/s5_test.rb new file mode 100644 index 00000000000..5a858c1edf9 --- /dev/null +++ b/test/unit/gateways/s5_test.rb @@ -0,0 +1,493 @@ +require 'test_helper' + +class S5Test < Test::Unit::TestCase + include CommStub + + def setup + @gateway = S5Gateway.new( + sender: 'sender', + channel: 'channel', + login: 'login', + password: 'password' + ) + + @credit_card = credit_card + @amount = 100 + + @options = { + order_id: '1', + billing_address: address, + description: 'Store Purchase' + } + end + + def test_successful_purchase + @gateway.expects(:ssl_post).returns(successful_purchase_response) + + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + + assert_equal '8a8294494d0a8ecd014d25a71d1502c7', response.authorization + assert response.test? + end + + def test_successful_purchase_with_recurring_flag + response = stub_comms do + @gateway.purchase(@amount, @credit_card, @options.merge(recurring: true)) + end.check_request do |endpoint, data, headers| + assert_match(/Recurrence.*REPEATED/, data) + end.respond_with(successful_purchase_response) + + assert_success response + end + + def test_failed_purchase + @gateway.expects(:ssl_post).returns(failed_purchase_response) + + response = @gateway.purchase(@amount, @credit_card, @options) + assert_failure response + end + + def test_successful_authorize + @gateway.expects(:ssl_post).returns(successful_authorize_response) + + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + end + + def test_failed_authorize + @gateway.expects(:ssl_post).returns(failed_authorize_response) + + auth = @gateway.authorize(@amount, @credit_card, @options) + assert_failure auth + end + + def test_successful_capture + @gateway.expects(:ssl_post).returns(successful_capture_response) + + assert response = @gateway.capture(@amount, '8a8294494d0a8ecd014d25a71d1502c7') + assert_success response + assert_match %r{Request successfully processed}, response.message + end + + def test_failed_capture + @gateway.expects(:ssl_post).returns(failed_capture_response) + + response = @gateway.capture(nil, '') + assert_failure response + end + + def test_successful_refund + @gateway.expects(:ssl_post).returns(successful_refund_response) + + assert refund = @gateway.refund(@amount, '8a8294494d0a8ecd014d25a71d1502c7') + assert_success refund + end + + def test_failed_refund + @gateway.expects(:ssl_post).returns(failed_refund_response) + + response = @gateway.refund(nil, '') + assert_failure response + end + + def test_successful_void + @gateway.expects(:ssl_post).returns(successful_void_response) + + assert void = @gateway.void('8a8294494d0a8ecd014d25a71d1502c7') + assert_success void + end + + def test_failed_void + @gateway.expects(:ssl_post).returns(failed_void_response) + + response = @gateway.void('') + assert_failure response + end + + def test_successful_verify + @gateway.expects(:ssl_post).times(2).returns(successful_authorize_response, successful_void_response) + + response = @gateway.verify(@credit_card, @options) + assert_success response + end + + def test_successful_verify_with_failed_void + @gateway.expects(:ssl_post).times(2).returns(successful_authorize_response, failed_void_response) + + response = @gateway.verify(@credit_card, @options) + assert_success response + end + + def test_failed_verify + @gateway.expects(:ssl_post).returns(failed_authorize_response) + + response = @gateway.verify(@credit_card, @options) + assert_failure response + end + + def test_successful_store + response = stub_comms do + @gateway.store(@credit_card) + end.respond_with(successful_store_response) + + assert_success response + assert response.test? + end + + def test_failed_store + response = stub_comms do + @gateway.store(@credit_card) + end.respond_with(failed_store_response) + + assert_failure response + assert response.test? + end + + def test_scrub + assert @gateway.supports_scrubbing? + assert_equal @gateway.scrub(pre_scrubbed), post_scrubbed + end + + private + + def pre_scrubbed + %q( + SSL established + <- "load=<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Request version=\"1.0\">\n <Header>\n <Security sender=\"ff80808142b2c03c0142b7a7339603e0\"/>\n </Header>\n <Transaction mode=\"CONNECTOR_TEST\" channel=\"ff80808142b2c03c0142b7a7339803e5\">\n <User login=\"8a82941847c4d0780147cea1d1730dcc\" pwd=\"n3yNMBGK\"/>\n <Payment code=\"CC.DB\">\n <Presentation>\n <Amount>1.00</Amount>\n <Currency>EUR</Currency>\n <Usage>Store Purchase</Usage>\n </Presentation>\n </Payment>\n <Account>\n <Number>4000100011112224</Number>\n <Holder>Longbob Longsen</Holder>\n <Brand>visa</Brand>\n <Expiry year=\"2016\" month=\"9\"/>\n <Verification>123</Verification>\n </Account>\n <Customer>\n <Contact>\n <Email/>\n <Ip/>\n <Phone>(555)555-5555</Phone>\n </Contact>\n <Address>\n <Street>456 My Street Apt 1</Street>\n <Zip>K1C2N6</Zip>\n <City>Ottawa</City>\n <State>ON</State>\n <Country>CA</Country>\n </Address>\n <Name>\n <Given>Longbob</Given>\n <Family>Longsen</Family>\n <Company/>\n </Name>\n </Customer>\n <Recurrence mode=\"INITIAL\"/>\n </Transaction>\n</Request>\n" + ) + end + + def post_scrubbed + %q( + SSL established + <- "load=<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Request version=\"1.0\">\n <Header>\n <Security sender=\"ff80808142b2c03c0142b7a7339603e0\"/>\n </Header>\n <Transaction mode=\"CONNECTOR_TEST\" channel=\"ff80808142b2c03c0142b7a7339803e5\">\n <User login=\"8a82941847c4d0780147cea1d1730dcc\" pwd=[FILTERED]/>\n <Payment code=\"CC.DB\">\n <Presentation>\n <Amount>1.00</Amount>\n <Currency>EUR</Currency>\n <Usage>Store Purchase</Usage>\n </Presentation>\n </Payment>\n <Account>\n <Number>[FILTERED]</Number>\n <Holder>Longbob Longsen</Holder>\n <Brand>visa</Brand>\n <Expiry year=\"2016\" month=\"9\"/>\n <Verification>[FILTERED]</Verification>\n </Account>\n <Customer>\n <Contact>\n <Email/>\n <Ip/>\n <Phone>(555)555-5555</Phone>\n </Contact>\n <Address>\n <Street>456 My Street Apt 1</Street>\n <Zip>K1C2N6</Zip>\n <City>Ottawa</City>\n <State>ON</State>\n <Country>CA</Country>\n </Address>\n <Name>\n <Given>Longbob</Given>\n <Family>Longsen</Family>\n <Company/>\n </Name>\n </Customer>\n <Recurrence mode=\"INITIAL\"/>\n </Transaction>\n</Request>\n" + ) + end + + def successful_purchase_response + <<-RESPONSE + <Response version="1.0"> + <Transaction mode="CONNECTOR_TEST" channel="ff80808142b2c03c0142b7a7339803e5"> + <Identification> + <ShortID>8856.7293.6098</ShortID> + <UniqueID>8a8294494d0a8ecd014d25a71d1502c7</UniqueID> + <ReferenceID /> + </Identification> + <Payment code="CC.DB"> + <Clearing> + <Amount>1.00</Amount> + <Currency>EUR</Currency> + <Descriptor>8856.7293.6098 eCommerce Store Purchase</Descriptor> + <FxRate>1.0</FxRate> + <FxSource>INTERN</FxSource> + <FxDate>2015-05-05 19:56:58</FxDate> + </Clearing> + </Payment> + <Processing code="CC.DB.90.00"> + <Timestamp>2015-05-05 19:56:58</Timestamp> + <Result>ACK</Result> + <Status code="90">NEW</Status> + <Reason code="00">Successful Processing</Reason> + <Return code="000.100.112">Request successfully processed in 'Merchant in Connector Test Mode'</Return> + <Risk score="-200" /> + <SecurityHash>6b5a52b4b838b174f9c7875d2e97466abf30b224</SecurityHash> + </Processing> + </Transaction> + </Response> + RESPONSE + end + + def failed_purchase_response + <<-RESPONSE + <Response version="1.0"> + <Transaction mode="CONNECTOR_TEST" channel="ff80808142b2c03c0142b7a7339803e5"> + <Identification> + <ReferenceID /> + </Identification> + <Payment code="CC.DB" /> + <Processing code="CC.DB.60.95"> + <Timestamp>2015-05-05 20:01:41</Timestamp> + <Result>NOK</Result> + <Status code="60">REJECTED_BANK</Status> + <Reason code="95">Authorization Error</Reason> + <Return code="800.100.151">transaction declined (invalid card)</Return> + <InfoMessage>This error is the result of passing: "return_code=800.100.151" in the memo-field of the request</InfoMessage> + <SecurityHash>957413fda6ffc521c30d46ab880064379c8c2193</SecurityHash> + </Processing> + </Transaction> + </Response> + RESPONSE + end + + def successful_authorize_response + <<-RESPONSE + <Response version="1.0"> + <Transaction mode="CONNECTOR_TEST" channel="ff80808142b2c03c0142b7a7339803e5"> + <Identification> + <ShortID>5828.7774.1730</ShortID> + <UniqueID>8a82944a4d0aa282014d25aea9d16729</UniqueID> + <ReferenceID>8a8294494d0a8ecd014d25aea4850378</ReferenceID> + </Identification> + <Payment code="CC.CP"> + <Clearing> + <Amount>1.00</Amount> + <Currency>EUR</Currency> + <Descriptor>5828.7774.1730 eCommerce Store Purchase</Descriptor> + <FxRate>1.0</FxRate> + <FxSource>INTERN</FxSource> + <FxDate>2015-05-05 20:05:13</FxDate> + </Clearing> + </Payment> + <Processing code="CC.CP.90.00"> + <Timestamp>2015-05-05 20:05:13</Timestamp> + <Result>ACK</Result> + <Status code="90">NEW</Status> + <Reason code="00">Successful Processing</Reason> + <Return code="000.100.112">Request successfully processed in 'Merchant in Connector Test Mode'</Return> + <Risk score="-100" /> + <SecurityHash>1869f6d5506af35726026a63c8c904d083edb9c4</SecurityHash> + </Processing> + </Transaction> + </Response> + RESPONSE + end + + def failed_authorize_response + <<-RESPONSE + <Response version="1.0"> + <Transaction mode="CONNECTOR_TEST" channel="ff80808142b2c03c0142b7a7339803e5"> + <Identification> + <ReferenceID /> + </Identification> + <Payment code="CC.PA" /> + <Processing code="CC.PA.65.78"> + <Timestamp>2015-05-05 20:06:13</Timestamp> + <Result>NOK</Result> + <Status code="65">REJECTED_RISK</Status> + <Reason code="78">External Risk Error</Reason> + <Return code="100.400.080">authorization failure</Return> + <InfoMessage>This error is the result of passing: "return_code=100.400.080" in the memo-field of the request</InfoMessage> + <SecurityHash>08ea8175a66923303c6b4bbb4d70dbbb4253550c</SecurityHash> + </Processing> + </Transaction> + </Response> + RESPONSE + end + + def successful_capture_response + <<-RESPONSE + <Response version="1.0"> + <Transaction mode="CONNECTOR_TEST" channel="ff80808142b2c03c0142b7a7339803e5"> + <Identification> + <ShortID>1039.8888.8226</ShortID> + <UniqueID>8a8294494d0a8ecd014d25b069ec03d8</UniqueID> + <ReferenceID>8a8294494d0a8ecd014d25b063f503c9</ReferenceID> + </Identification> + <Payment code="CC.CP"> + <Clearing> + <Amount>0.99</Amount> + <Currency>EUR</Currency> + <Descriptor>1039.8888.8226 eCommerce Store Purchase</Descriptor> + <FxRate>1.0</FxRate> + <FxSource>INTERN</FxSource> + <FxDate>2015-05-05 20:07:08</FxDate> + </Clearing> + </Payment> + <Processing code="CC.CP.90.00"> + <Timestamp>2015-05-05 20:07:08</Timestamp> + <Result>ACK</Result> + <Status code="90">NEW</Status> + <Reason code="00">Successful Processing</Reason> + <Return code="000.100.112">Request successfully processed in 'Merchant in Connector Test Mode'</Return> + <Risk score="-100" /> + <SecurityHash>bc4f41acddd19bb70a696d8479c8b165fe2e7e19</SecurityHash> + </Processing> + </Transaction> + </Response> + RESPONSE + end + + def failed_capture_response + <<-RESPONSE + <Response version="1.0"> + <Transaction mode="CONNECTOR_TEST" channel="ff80808142b2c03c0142b7a7339803e5"> + <Identification> + <ShortID>1383.4862.6594</ShortID> + <UniqueID>8a8294494d0a8ecd014d25b1060603f7</UniqueID> + <ReferenceID /> + </Identification> + <Payment code="CC.CP" /> + <Processing code="CC.CP.70.35"> + <Timestamp>2015-05-05 20:07:47</Timestamp> + <Result>NOK</Result> + <Status code="70">REJECTED_VALIDATION</Status> + <Reason code="35">Amount Error</Reason> + <Return code="100.550.300">request contains no amount or too low amount</Return> + <SecurityHash>6ec0c692bdf08b8de2012af7811b803d603188d6</SecurityHash> + </Processing> + </Transaction> + </Response> + RESPONSE + end + + def successful_refund_response + <<-RESPONSE + <Response version="1.0"> + <Transaction mode="CONNECTOR_TEST" channel="ff80808142b2c03c0142b7a7339803e5"> + <Identification> + <ShortID>6193.8496.3746</ShortID> + <UniqueID>8a8294494d0a8ecd014d25b20ded043e</UniqueID> + <ReferenceID>8a8294494d0a8ecd014d25b208b1042f</ReferenceID> + </Identification> + <Payment code="CC.RF"> + <Clearing> + <Amount>1.00</Amount> + <Currency>EUR</Currency> + <Descriptor>6193.8496.3746 eCommerce Store Purchase</Descriptor> + <FxRate>1.0</FxRate> + <FxSource>INTERN</FxSource> + <FxDate>2015-05-05 20:08:55</FxDate> + </Clearing> + </Payment> + <Processing code="CC.RF.90.00"> + <Timestamp>2015-05-05 20:08:55</Timestamp> + <Result>ACK</Result> + <Status code="90">NEW</Status> + <Reason code="00">Successful Processing</Reason> + <Return code="000.100.112">Request successfully processed in 'Merchant in Connector Test Mode'</Return> + <SecurityHash>2a42ad4815845fc490788000f47d8133f8c56df5</SecurityHash> + </Processing> + </Transaction> + </Response> + RESPONSE + end + + def failed_refund_response + <<-RESPONSE + <Response version="1.0"> + <Transaction mode="CONNECTOR_TEST" channel="ff80808142b2c03c0142b7a7339803e5"> + <Identification> + <ShortID>8663.4558.3266</ShortID> + <UniqueID>8a82944a4d0aa282014d25b317a6679c</UniqueID> + <ReferenceID /> + </Identification> + <Payment code="CC.RF" /> + <Processing code="CC.RF.70.35"> + <Timestamp>2015-05-05 20:10:03</Timestamp> + <Result>NOK</Result> + <Status code="70">REJECTED_VALIDATION</Status> + <Reason code="35">Amount Error</Reason> + <Return code="100.550.300">request contains no amount or too low amount</Return> + <SecurityHash>2a3b858866371e35bb6ee50e5edc480e251e6320</SecurityHash> + </Processing> + </Transaction> + </Response> + RESPONSE + end + + def successful_void_response + <<-RESPONSE + <Response version="1.0"> + <Transaction mode="CONNECTOR_TEST" channel="ff80808142b2c03c0142b7a7339803e5"> + <Identification> + <ShortID>8083.6352.4770</ShortID> + <UniqueID>8a8294494d0a8ecd014d25b3a9d3048e</UniqueID> + <ReferenceID>8a8294494d0a8ecd014d25b3a364047f</ReferenceID> + </Identification> + <Payment code="CC.RV"> + <Clearing> + <Amount>1.00</Amount> + <Currency>EUR</Currency> + <Descriptor>8083.6352.4770 eCommerce Store Purchase</Descriptor> + <FxRate>1.0</FxRate> + <FxSource>INTERN</FxSource> + <FxDate>2015-05-05 20:10:40</FxDate> + </Clearing> + </Payment> + <Processing code="CC.RV.90.00"> + <Timestamp>2015-05-05 20:10:41</Timestamp> + <Result>ACK</Result> + <Status code="90">NEW</Status> + <Reason code="00">Successful Processing</Reason> + <Return code="000.100.112">Request successfully processed in 'Merchant in Connector Test Mode'</Return> + <Risk score="-100" /> + <SecurityHash>b3bce3318214c86efdf63ad4874db803e9bbaf28</SecurityHash> + </Processing> + </Transaction> + </Response> + RESPONSE + end + + def failed_void_response + <<-RESPONSE + <Response version="1.0"> + <Transaction mode="CONNECTOR_TEST" channel="ff80808142b2c03c0142b7a7339803e5"> + <Identification> + <ShortID>6945.4689.1426</ShortID> + <UniqueID>8a82944a4d0aa282014d25b4576d67cd</UniqueID> + <ReferenceID /> + </Identification> + <Payment code="CC.RV" /> + <Processing code="CC.RV.70.30"> + <Timestamp>2015-05-05 20:11:25</Timestamp> + <Result>NOK</Result> + <Status code="70">REJECTED_VALIDATION</Status> + <Reason code="30">Reference Error</Reason> + <Return code="700.400.530">reversal needs at least one successful transaction of type (CP or DB or RB or PA)</Return> + <Risk score="-100" /> + <SecurityHash>06ee4076e8ae7cad6613c3eaf7d8094fccdd5773</SecurityHash> + </Processing> + </Transaction> + </Response> + RESPONSE + end + + def successful_store_response + <<-RESPONSE + <Response version="1.0"> + <Transaction mode="CONNECTOR_TEST" channel="ff80808142b2c03c0142b7a7339803e5"> + <Identification> + <ShortID>1901.3386.3074</ShortID> + <UniqueID>8a8294494e634488014e6a586d91354e</UniqueID> + </Identification> + <Payment code="CC.RG" /> + <Account /> + <Processing code="CC.RG.90.00"> + <Timestamp>2015-07-07 21:07:36</Timestamp> + <Result>ACK</Result> + <Status code="90">NEW</Status> + <Reason code="00">Successful Processing</Reason> + <Return code="000.100.112">Request successfully processed in 'Merchant in Connector Test Mode'</Return> + <Risk score="-100" /> + <ConfirmationStatus>CONFIRMED</ConfirmationStatus> + <SecurityHash>1ebb9fc2109729dbfd63f7fe9df7996b20f8d66f</SecurityHash> + </Processing> + </Transaction> + </Response> + RESPONSE + end + + def failed_store_response + <<-RESPONSE + <Response version="1.0"> + <Transaction mode="CONNECTOR_TEST" channel="ff80808142b2c03c0142b7a7339803e5"> + <Identification> + <ShortID>1263.6366.5058</ShortID> + <UniqueID>8a82944a4e6357e2014e6a66adea602a</UniqueID> + </Identification> + <Payment code="CC.RG" /> + <Account /> + <Processing code="CC.RG.70.40"> + <Timestamp>2015-07-07 21:23:10</Timestamp> + <Result>NOK</Result> + <Status code="70">REJECTED_VALIDATION</Status> + <Reason code="40">Account Validation</Reason> + <Return code="100.100.101">invalid creditcard, bank account number or bank name</Return> + <SecurityHash>ecc63ca63ef074129c8997c8bb94591223f127a4</SecurityHash> + </Processing> + </Transaction> + </Response> + RESPONSE + end +end diff --git a/test/unit/gateways/safe_charge_test.rb b/test/unit/gateways/safe_charge_test.rb new file mode 100644 index 00000000000..94d310907c6 --- /dev/null +++ b/test/unit/gateways/safe_charge_test.rb @@ -0,0 +1,363 @@ +require 'test_helper' + +class SafeChargeTest < Test::Unit::TestCase + include CommStub + + def setup + @gateway = SafeChargeGateway.new(client_login_id: 'login', client_password: 'password') + @credit_card = credit_card + @three_ds_enrolled_card = credit_card('4012 0010 3749 0014') + @amount = 100 + + @options = { + order_id: '1', + billing_address: address, + description: 'Store Purchase' + } + @merchant_options = @options.merge( + merchant_descriptor: 'Test Descriptor', + merchant_phone_number: '(555)555-5555', + merchant_name: 'Test Merchant' + ) + @three_ds_options = @options.merge(three_d_secure: true) + end + + def test_successful_purchase + @gateway.expects(:ssl_post).returns(successful_purchase_response) + + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + + assert_equal '111951|101508189567|ZQBpAFAASABGAHAAVgBPAFUAMABiADMAewBtAGsAd' \ + 'AAvAFIAQQBrAGoAYwBxACoAXABHAEEAOgA3ACsAMgA4AD0AOABDAG4AbQAzAF' \ + 'UAbQBYAFIAMwA=|%02d|%d|1.00|USD' % [@credit_card.month, @credit_card.year.to_s[-2..-1]], response.authorization + assert response.test? + end + + def test_successful_purchase_with_merchant_options + purchase = stub_comms do + @gateway.purchase(@amount, @credit_card, @merchant_options) + end.check_request do |endpoint, data, headers| + assert_match(/sg_Descriptor/, data) + assert_match(/sg_MerchantPhoneNumber/, data) + assert_match(/sg_MerchantName/, data) + end.respond_with(successful_purchase_response) + + assert_success purchase + assert_equal '111951|101508189567|ZQBpAFAASABGAHAAVgBPAFUAMABiADMAewBtAGsAd' \ + 'AAvAFIAQQBrAGoAYwBxACoAXABHAEEAOgA3ACsAMgA4AD0AOABDAG4AbQAzAF' \ + 'UAbQBYAFIAMwA=|%02d|%d|1.00|USD' % [@credit_card.month, @credit_card.year.to_s[-2..-1]], purchase.authorization + assert purchase.test? + end + + def test_successful_purchase_with_truthy_stored_credential_mode + purchase = stub_comms do + @gateway.purchase(@amount, @credit_card, @options.merge(stored_credential_mode: true)) + end.check_request do |endpoint, data, headers| + assert_match(/sg_StoredCredentialMode=1/, data) + end.respond_with(successful_purchase_response) + + assert_success purchase + assert_equal '111951|101508189567|ZQBpAFAASABGAHAAVgBPAFUAMABiADMAewBtAGsAd' \ + 'AAvAFIAQQBrAGoAYwBxACoAXABHAEEAOgA3ACsAMgA4AD0AOABDAG4AbQAzAF' \ + 'UAbQBYAFIAMwA=|%02d|%d|1.00|USD' % [@credit_card.month, @credit_card.year.to_s[-2..-1]], purchase.authorization + assert purchase.test? + end + + def test_successful_purchase_with_falsey_stored_credential_mode + purchase = stub_comms do + @gateway.purchase(@amount, @credit_card, @options.merge(stored_credential_mode: false)) + end.check_request do |endpoint, data, headers| + assert_match(/sg_StoredCredentialMode=0/, data) + end.respond_with(successful_purchase_response) + + assert_success purchase + assert_equal '111951|101508189567|ZQBpAFAASABGAHAAVgBPAFUAMABiADMAewBtAGsAd' \ + 'AAvAFIAQQBrAGoAYwBxACoAXABHAEEAOgA3ACsAMgA4AD0AOABDAG4AbQAzAF' \ + 'UAbQBYAFIAMwA=|%02d|%d|1.00|USD' % [@credit_card.month, @credit_card.year.to_s[-2..-1]], purchase.authorization + assert purchase.test? + end + + def test_failed_purchase + @gateway.expects(:ssl_post).returns(failed_purchase_response) + + response = @gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_equal '0', response.error_code + end + + def test_successful_authorize + @gateway.expects(:ssl_post).returns(successful_authorize_response) + + response = @gateway.authorize(@amount, @credit_card, @options) + assert_success response + + assert_equal '111534|101508189855|MQBVAG4ASABkAEgAagB3AEsAbgAtACoAWgAzAFwAW' \ + 'wBNAF8ATQBUAD0AegBQAGwAQAAtAD0AXAB5AFkALwBtAFAALABaAHoAOgBFAE' \ + 'wAUAA1AFUAMwA=|%02d|%d|1.00|USD' % [@credit_card.month, @credit_card.year.to_s[-2..-1]], response.authorization + assert response.test? + end + + def test_failed_authorize + @gateway.expects(:ssl_post).returns(failed_authorize_response) + + response = @gateway.authorize(@amount, @credit_card, @options) + assert_failure response + assert_equal '0', response.error_code + end + + def test_successful_capture + @gateway.expects(:ssl_post).returns(successful_capture_response) + + response = @gateway.capture(@amount, 'auth|transaction_id|token|month|year|amount|currency') + assert_success response + + assert_equal '111301|101508190200|RwA1AGQAMgAwAEkAWABKADkAcABjAHYAQQA4AC8AZ' \ + 'AAlAHMAfABoADEALAA8ADQAewB8ADsAewBiADsANQBoACwAeAA/AGQAXQAjAF' \ + 'EAYgBVAHIAMwA=|month|year|1.00|currency', response.authorization + assert response.test? + end + + def test_failed_capture + @gateway.expects(:ssl_post).returns(failed_capture_response) + + response = @gateway.capture(@amount, '', @options) + assert_failure response + assert_equal '1163', response.error_code + end + + def test_successful_refund + @gateway.expects(:ssl_post).returns(successful_refund_response) + + response = @gateway.refund(@amount, 'authorization', @options) + assert_success response + assert_equal 'Success', response.message + end + + def test_failed_refund + @gateway.expects(:ssl_post).returns(failed_refund_response) + + response = @gateway.refund(@amount, '', @options) + assert_failure response + assert_equal '1163', response.error_code + end + + def test_successful_credit + @gateway.expects(:ssl_post).returns(successful_credit_response) + + response = @gateway.credit(@amount, @credit_card, @options) + assert_success response + assert_equal 'Success', response.message + end + + def test_failed_credit + @gateway.expects(:ssl_post).returns(failed_credit_response) + + response = @gateway.credit(@amount, @credit_card, @options) + assert_failure response + assert_equal 'Decline', response.message + end + + def test_successful_void + @gateway.expects(:ssl_post).returns(successful_void_response) + + response = @gateway.void('auth|transaction_id|token|month|year|amount|currency') + assert_success response + + assert_equal '111171|101508208625|ZQBpAFAAZgBuAHEATgBUAHcASAAwADUAcwBHAHQAV' \ + 'QBLAHAAbgB6AGwAJAA1AEMAfAB2AGYASwBrAHEAeQBOAEwAOwBZAGIAewB4AG' \ + 'wAYwBUAE0AMwA=|month|year|0.00|currency', response.authorization + assert response.test? + end + + def test_failed_void + @gateway.expects(:ssl_post).returns(failed_void_response) + + response = @gateway.void('') + assert_failure response + assert_equal 'Invalid Amount', response.message + assert response.test? + end + + def test_successful_verify + @gateway.expects(:ssl_post).times(2).returns(successful_authorize_response, successful_void_response) + + response = @gateway.verify(@credit_card, @options) + assert_success response + + assert_equal '111534|101508189855|MQBVAG4ASABkAEgAagB3AEsAbgAtACoAWgAzAFwAW' \ + 'wBNAF8ATQBUAD0AegBQAGwAQAAtAD0AXAB5AFkALwBtAFAALABaAHoAOgBFAE' \ + 'wAUAA1AFUAMwA=|%02d|%d|1.00|USD' % [@credit_card.month, @credit_card.year.to_s[-2..-1]], response.authorization + assert response.test? + end + + def test_successful_verify_with_failed_void + @gateway.expects(:ssl_post).times(2).returns(successful_authorize_response, failed_void_response) + + response = @gateway.verify(@credit_card, @options) + assert_success response + end + + def test_failed_verify + @gateway.expects(:ssl_post).returns(failed_authorize_response) + + response = @gateway.verify(@credit_card, @options) + assert_failure response + assert_equal '0', response.error_code + end + + def test_scrub + assert @gateway.supports_scrubbing? + assert_equal @gateway.scrub(pre_scrubbed), post_scrubbed + end + + def test_3ds_response + purchase = stub_comms do + @gateway.purchase(@amount, @three_ds_enrolled_card, @three_ds_options) + end.check_request do |endpoint, data, headers| + assert_match(/Sale3D/, data) + assert_match(/sg_APIType/, data) + end.respond_with(successful_3ds_purchase_response) + + assert_success purchase + assert_equal 'MDAwMDAwMDAwMDE1MTAxMDgzMTA=', purchase.params['xid'] + assert_equal 'eJxVUdtuwjAM/ZWK95GYgijIjVTWaUNTGdqQ4DUKFq2gF9J0A75+SVcuixTF59g+sY5xlWqi+ItUo0lgQnUtd+Rl27BXyScYAQce+MB7ApfRJx0FfpOus7IQ0Of9AbIrtK1apbIwAqU6zuYLMQSY8ABZBzEnPY8FfzhjGCH7o7GQOYlIq9J4K6qNd5VD1mZQlU1h9FkEQ47sCrDRB5EaU00ZO5RKHtKyth2ORXYfaNm4qLYqp2wrkjj6ud8XSFbRKYl3F/uGyFwFbqUhMeAwBvC5B6Opz6c+IGt5lLn73hlgR+kAVu6PqMu4xCOB1l1NhTqLydg6ckNIp6osyFZYJ28xsvvAz2/OT2WsRa+bdf2+X6cXtd9oHxZNPks+ojB0DrcFTi2zrkDAJ62cA8icBOuWx7oF2+jf4n8B', purchase.params['pareq'] + assert_equal 'https://pit.3dsecure.net/VbVTestSuiteService/pit1/acsService/paReq?summary=MjRlZGYwY2EtZTk5Zi00NDJjLTljOTAtNWUxZmRhMjEwODg3', purchase.params['acsurl'] + end + + private + + def pre_scrubbed + %q( +opening connection to process.sandbox.safecharge.com:443... +opened +starting SSL for process.sandbox.safecharge.com:443... +SSL established +<- "POST /service.asmx/Process HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: process.sandbox.safecharge.com\r\nContent-Length: 249\r\n\r\n" +<- "sg_TransType=Sale&sg_Currency=USD&sg_Amount=1.00&sg_ClientLoginID=SpreedlyTestTRX&sg_ClientPassword=5Jp5xKmgqY&sg_ResponseFormat=4&sg_Version=4.1.0&sg_NameOnCard=Longbob+Longsen&sg_CardNumber=4000100011112224&sg_ExpMonth=09&sg_ExpYear=18&sg_CVV2=123" +-> "HTTP/1.1 200 OK\r\n" +-> "Cache-Control: private, max-age=0\r\n" +-> "Content-Type: text/xml; charset=utf-8\r\n" +-> "Content-Encoding: gzip\r\n" +-> "Vary: Accept-Encoding\r\n" +-> "Server: Microsoft-IIS/8.5\r\n" +-> "X-AspNet-Version: 4.0.30319\r\n" +-> "X-Powered-By: ASP.NET\r\n" +-> "Date: Wed, 29 Mar 2017 18:28:17 GMT\r\n" +-> "Connection: close\r\n" +-> "Content-Length: 727\r\n" +-> "Set-Cookie: visid_incap_847807=oQqFyASiS0y3sQoZ55M7TsH821gAAAAAQUIPAAAAAAA/rRn9PSjQ7LsSqhb2S1AZ; expires=Thu, 29 Mar 2018 13:12:58 GMT; path=/; Domain=.sandbox.safecharge.com\r\n" +-> "Set-Cookie: incap_ses_225_847807=H1/pC1tNgzhTmiAXOl0fA8H821gAAAAAFE9hBYJtG83f0yrtcxrGsg==; path=/; Domain=.sandbox.safecharge.com\r\n" +-> "X-Iinfo: 9-132035054-132035081 NNNN CT(207 413 0) RT(1490812095742 212) q(0 0 6 -1) r(14 14) U5\r\n" +-> "X-CDN: Incapsula\r\n" +-> "\r\n" +reading 727 bytes... + ) + end + + def post_scrubbed + %q( +opening connection to process.sandbox.safecharge.com:443... +opened +starting SSL for process.sandbox.safecharge.com:443... +SSL established +<- "POST /service.asmx/Process HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: process.sandbox.safecharge.com\r\nContent-Length: 249\r\n\r\n" +<- "sg_TransType=Sale&sg_Currency=USD&sg_Amount=1.00&sg_ClientLoginID=SpreedlyTestTRX&sg_ClientPassword=[FILTERED]&sg_ResponseFormat=4&sg_Version=4.1.0&sg_NameOnCard=Longbob+Longsen&sg_CardNumber=[FILTERED]&sg_ExpMonth=09&sg_ExpYear=18&sg_CVV2=[FILTERED]" +-> "HTTP/1.1 200 OK\r\n" +-> "Cache-Control: private, max-age=0\r\n" +-> "Content-Type: text/xml; charset=utf-8\r\n" +-> "Content-Encoding: gzip\r\n" +-> "Vary: Accept-Encoding\r\n" +-> "Server: Microsoft-IIS/8.5\r\n" +-> "X-AspNet-Version: 4.0.30319\r\n" +-> "X-Powered-By: ASP.NET\r\n" +-> "Date: Wed, 29 Mar 2017 18:28:17 GMT\r\n" +-> "Connection: close\r\n" +-> "Content-Length: 727\r\n" +-> "Set-Cookie: visid_incap_847807=oQqFyASiS0y3sQoZ55M7TsH821gAAAAAQUIPAAAAAAA/rRn9PSjQ7LsSqhb2S1AZ; expires=Thu, 29 Mar 2018 13:12:58 GMT; path=/; Domain=.sandbox.safecharge.com\r\n" +-> "Set-Cookie: incap_ses_225_847807=H1/pC1tNgzhTmiAXOl0fA8H821gAAAAAFE9hBYJtG83f0yrtcxrGsg==; path=/; Domain=.sandbox.safecharge.com\r\n" +-> "X-Iinfo: 9-132035054-132035081 NNNN CT(207 413 0) RT(1490812095742 212) q(0 0 6 -1) r(14 14) U5\r\n" +-> "X-CDN: Incapsula\r\n" +-> "\r\n" +reading 727 bytes... + ) + end + + def successful_purchase_response + %( + <Response><Version>4.1.0</Version><ClientLoginID>SpreedlyTestTRX</ClientLoginID><ClientUniqueID></ClientUniqueID><TransactionID>101508189567</TransactionID><Status>APPROVED</Status><AuthCode>111951</AuthCode><AVSCode></AVSCode><CVV2Reply></CVV2Reply><ReasonCodes><Reason code="0"></Reason></ReasonCodes><ErrCode>0</ErrCode><ExErrCode>0</ExErrCode><Token>ZQBpAFAASABGAHAAVgBPAFUAMABiADMAewBtAGsAdAAvAFIAQQBrAGoAYwBxACoAXABHAEEAOgA3ACsAMgA4AD0AOABDAG4AbQAzAFUAbQBYAFIAMwA=</Token><CustomData></CustomData><AcquirerID>19</AcquirerID><IssuerBankName>University First Federal Credit Union</IssuerBankName><IssuerBankCountry>us</IssuerBankCountry><Reference></Reference><AGVCode></AGVCode><AGVError></AGVError><UniqueCC>SuiMHP60FrDKfyaJs47hqqrR/JU=</UniqueCC><CustomData2></CustomData2><CreditCardInfo><IsPrepaid>0</IsPrepaid><CardType>Credit</CardType><CardProgram></CardProgram><CardProduct></CardProduct></CreditCardInfo><FraudResponse><FinalDecision>Accept</FinalDecision><Recommendations /><Rule /></FraudResponse></Response> + ) + end + + def failed_purchase_response + %( + <Response><Version>4.1.0</Version><ClientLoginID>SpreedlyTestTRX</ClientLoginID><ClientUniqueID></ClientUniqueID><TransactionID>101508189637</TransactionID><Status>DECLINED</Status><AuthCode></AuthCode><AVSCode></AVSCode><CVV2Reply></CVV2Reply><ReasonCodes><Reason code="0">Decline</Reason></ReasonCodes><ErrCode>-1</ErrCode><ExErrCode>0</ExErrCode><Token>bwBVAEYAUgBuAGcAbABSAFYASgB5AEAAMgA/ACsAUQBIAC4AbgB1AHgAdABAAE8ARgBRAGoAbwApACQAWwBKAFwATwAxAEcAMwBZAG4AdwBmACgAMwA=</Token><CustomData></CustomData><AcquirerID>19</AcquirerID><IssuerBankName></IssuerBankName><IssuerBankCountry></IssuerBankCountry><Reference></Reference><AGVCode></AGVCode><AGVError></AGVError><UniqueCC>GyueFkuQqW+UL38d57fuA5/RqfQ=</UniqueCC><CustomData2></CustomData2><CreditCardInfo><IsPrepaid>0</IsPrepaid><CardType></CardType><CardProgram></CardProgram><CardProduct></CardProduct></CreditCardInfo><FraudResponse><FinalDecision>Accept</FinalDecision><Recommendations /><Rule /></FraudResponse></Response> + ) + end + + def successful_authorize_response + %( + <Response><Version>4.1.0</Version><ClientLoginID>SpreedlyTestTRX</ClientLoginID><ClientUniqueID></ClientUniqueID><TransactionID>101508189855</TransactionID><Status>APPROVED</Status><AuthCode>111534</AuthCode><AVSCode></AVSCode><CVV2Reply></CVV2Reply><ReasonCodes><Reason code="0"></Reason></ReasonCodes><ErrCode>0</ErrCode><ExErrCode>0</ExErrCode><Token>MQBVAG4ASABkAEgAagB3AEsAbgAtACoAWgAzAFwAWwBNAF8ATQBUAD0AegBQAGwAQAAtAD0AXAB5AFkALwBtAFAALABaAHoAOgBFAEwAUAA1AFUAMwA=</Token><CustomData></CustomData><AcquirerID>19</AcquirerID><IssuerBankName>University First Federal Credit Union</IssuerBankName><IssuerBankCountry>us</IssuerBankCountry><Reference></Reference><AGVCode></AGVCode><AGVError></AGVError><UniqueCC>SuiMHP60FrDKfyaJs47hqqrR/JU=</UniqueCC><CustomData2></CustomData2><CreditCardInfo><IsPrepaid>0</IsPrepaid><CardType>Credit</CardType><CardProgram></CardProgram><CardProduct></CardProduct></CreditCardInfo><FraudResponse><FinalDecision>Accept</FinalDecision><Recommendations /><Rule /></FraudResponse></Response> + ) + end + + def failed_authorize_response + %( + <Response><Version>4.1.0</Version><ClientLoginID>SpreedlyTestTRX</ClientLoginID><ClientUniqueID></ClientUniqueID><TransactionID>101508190604</TransactionID><Status>DECLINED</Status><AuthCode></AuthCode><AVSCode></AVSCode><CVV2Reply></CVV2Reply><ReasonCodes><Reason code="0">Decline</Reason></ReasonCodes><ErrCode>-1</ErrCode><ExErrCode>0</ExErrCode><Token>MQBLAG4AMgAwADMAOABmAFYANABbAGYAcwA+ACMAVgBXAD0AUQBQAEoANQBrAHQAWABsAFEAeABQAF8ARwA6ACsALgBHADUALwBTAEAARwBIACgAMwA=</Token><CustomData></CustomData><AcquirerID>19</AcquirerID><IssuerBankName></IssuerBankName><IssuerBankCountry></IssuerBankCountry><Reference></Reference><AGVCode></AGVCode><AGVError></AGVError><UniqueCC>GyueFkuQqW+UL38d57fuA5/RqfQ=</UniqueCC><CustomData2></CustomData2><CreditCardInfo><IsPrepaid>0</IsPrepaid><CardType></CardType><CardProgram></CardProgram><CardProduct></CardProduct></CreditCardInfo><FraudResponse><FinalDecision>Accept</FinalDecision><Recommendations /><Rule /></FraudResponse></Response> + ) + end + + def successful_capture_response + %( + <Response><Version>4.1.0</Version><ClientLoginID>SpreedlyTestTRX</ClientLoginID><ClientUniqueID></ClientUniqueID><TransactionID>101508190200</TransactionID><Status>APPROVED</Status><AuthCode>111301</AuthCode><AVSCode></AVSCode><CVV2Reply></CVV2Reply><ReasonCodes><Reason code="0"></Reason></ReasonCodes><ErrCode>0</ErrCode><ExErrCode>0</ExErrCode><Token>RwA1AGQAMgAwAEkAWABKADkAcABjAHYAQQA4AC8AZAAlAHMAfABoADEALAA8ADQAewB8ADsAewBiADsANQBoACwAeAA/AGQAXQAjAFEAYgBVAHIAMwA=</Token><CustomData></CustomData><AcquirerID>19</AcquirerID><IssuerBankName>University First Federal Credit Union</IssuerBankName><IssuerBankCountry>us</IssuerBankCountry><Reference></Reference><AGVCode></AGVCode><AGVError></AGVError><UniqueCC>SuiMHP60FrDKfyaJs47hqqrR/JU=</UniqueCC><CustomData2></CustomData2><CreditCardInfo><IsPrepaid>0</IsPrepaid><CardType>Credit</CardType><CardProgram></CardProgram><CardProduct></CardProduct></CreditCardInfo><FraudResponse /></Response> + ) + end + + def failed_capture_response + %( + <Response><Version>4.1.0</Version><ClientLoginID>SpreedlyTestTRX</ClientLoginID><ClientUniqueID></ClientUniqueID><TransactionID>101508190627</TransactionID><Status>ERROR</Status><AuthCode></AuthCode><AVSCode></AVSCode><CVV2Reply></CVV2Reply><Reason>Transaction must contain a Card/Token/Account</Reason><ErrCode>-1100</ErrCode><ExErrCode>1163</ExErrCode><CustomData></CustomData><AcquirerID>-1</AcquirerID><IssuerBankName></IssuerBankName><IssuerBankCountry></IssuerBankCountry><Reference></Reference><AGVCode></AGVCode><AGVError></AGVError><UniqueCC>2jmj7l5rSw0yVb/vlWAYkK/YBwk=</UniqueCC><CustomData2></CustomData2><CreditCardInfo><IsPrepaid>0</IsPrepaid><CardType></CardType><CardProgram></CardProgram><CardProduct></CardProduct></CreditCardInfo></Response> + ) + end + + def successful_refund_response + %( + <Response><Version>4.1.0</Version><ClientLoginID>SpreedlyTestTRX</ClientLoginID><ClientUniqueID></ClientUniqueID><TransactionID>101508440432</TransactionID><Status>APPROVED</Status><AuthCode>111207</AuthCode><AVSCode></AVSCode><CVV2Reply></CVV2Reply><ReasonCodes><Reason code="0"></Reason></ReasonCodes><ErrCode>0</ErrCode><ExErrCode>0</ExErrCode><Token>MQBVAG4AUgAwAFcAaABxAGoASABdAE4ALABvAGYANAAmAE8AcQA/AEgAawAkAHYASQBKAFMAegBiACoAcQBBAC8AVABlAD4AKwBkAC0AKwA8ACcAMwA=</Token><CustomData></CustomData><AcquirerID>19</AcquirerID><IssuerBankName></IssuerBankName><IssuerBankCountry></IssuerBankCountry><Reference></Reference><AGVCode></AGVCode><AGVError></AGVError><UniqueCC>SuiMHP60FrDKfyaJs47hqqrR/JU=</UniqueCC><CustomData2></CustomData2><CreditCardInfo><IsPrepaid>0</IsPrepaid><CardType></CardType><CardProgram></CardProgram><CardProduct></CardProduct></CreditCardInfo><FraudResponse /></Response> + ) + end + + def failed_refund_response + %( + <Response><Version>4.1.0</Version><ClientLoginID>SpreedlyTestTRX</ClientLoginID><ClientUniqueID></ClientUniqueID><TransactionID>101508208595</TransactionID><Status>ERROR</Status><AuthCode></AuthCode><AVSCode></AVSCode><CVV2Reply></CVV2Reply><Reason>Transaction must contain a Card/Token/Account</Reason><ErrCode>-1100</ErrCode><ExErrCode>1163</ExErrCode><CustomData></CustomData><AcquirerID>-1</AcquirerID><IssuerBankName></IssuerBankName><IssuerBankCountry></IssuerBankCountry><Reference></Reference><AGVCode></AGVCode><AGVError></AGVError><UniqueCC>2jmj7l5rSw0yVb/vlWAYkK/YBwk=</UniqueCC><CustomData2></CustomData2><CreditCardInfo><IsPrepaid>0</IsPrepaid><CardType></CardType><CardProgram></CardProgram><CardProduct></CardProduct></CreditCardInfo></Response> + ) + end + + def successful_credit_response + %( + <Response><Version>4.1.0</Version><ClientLoginID>SpreedlyTestTRX</ClientLoginID><ClientUniqueID></ClientUniqueID><TransactionID>101508440421</TransactionID><Status>APPROVED</Status><AuthCode>111644</AuthCode><AVSCode></AVSCode><CVV2Reply></CVV2Reply><ReasonCodes><Reason code="0"></Reason></ReasonCodes><ErrCode>0</ErrCode><ExErrCode>0</ExErrCode><Token>bwA1ADAAcAAwAHUAVABJAFYAUQAlAGcAfAB8AFQAbwBkAHAAbwAjAG4AaABDAHsAUABdACoAYwBaAEsAMQBHAEUAMQBuAHQAdwBXAFUAVABZACMAMwA=</Token><CustomData></CustomData><AcquirerID>19</AcquirerID><IssuerBankName></IssuerBankName><IssuerBankCountry></IssuerBankCountry><Reference></Reference><AGVCode></AGVCode><AGVError></AGVError><UniqueCC>SuiMHP60FrDKfyaJs47hqqrR/JU=</UniqueCC><CustomData2></CustomData2><CreditCardInfo><IsPrepaid>0</IsPrepaid><CardType></CardType><CardProgram></CardProgram><CardProduct></CardProduct></CreditCardInfo><FraudResponse /></Response> + ) + end + + def failed_credit_response + %( + <Response><Version>4.1.0</Version><ClientLoginID>SpreedlyTestTRX</ClientLoginID><ClientUniqueID></ClientUniqueID><TransactionID>101508440424</TransactionID><Status>DECLINED</Status><AuthCode></AuthCode><AVSCode></AVSCode><CVV2Reply></CVV2Reply><ReasonCodes><Reason code="0">Decline</Reason></ReasonCodes><ErrCode>-1</ErrCode><ExErrCode>0</ExErrCode><Token>RwBVAGQAZgAwAFMAbABwAEwASgBNAFMAXABJAGAAeAAsAHsALAA7ADUAOgBUAEMAZwBNAG4AbABQAC4AQAAvAC0APwBpAEAAWQBoACMAdwBvAGEAMwA=</Token><CustomData></CustomData><AcquirerID>19</AcquirerID><IssuerBankName></IssuerBankName><IssuerBankCountry></IssuerBankCountry><Reference></Reference><AGVCode></AGVCode><AGVError></AGVError><UniqueCC>GyueFkuQqW+UL38d57fuA5/RqfQ=</UniqueCC><CustomData2></CustomData2><CreditCardInfo><IsPrepaid>0</IsPrepaid><CardType></CardType><CardProgram></CardProgram><CardProduct></CardProduct></CreditCardInfo><FraudResponse /></Response> + ) + end + + def successful_void_response + %( + <Response><Version>4.1.0</Version><ClientLoginID>SpreedlyTestTRX</ClientLoginID><ClientUniqueID></ClientUniqueID><TransactionID>101508208625</TransactionID><Status>APPROVED</Status><AuthCode>111171</AuthCode><AVSCode></AVSCode><CVV2Reply></CVV2Reply><ReasonCodes><Reason code="0"></Reason></ReasonCodes><ErrCode>0</ErrCode><ExErrCode>0</ExErrCode><Token>ZQBpAFAAZgBuAHEATgBUAHcASAAwADUAcwBHAHQAVQBLAHAAbgB6AGwAJAA1AEMAfAB2AGYASwBrAHEAeQBOAEwAOwBZAGIAewB4AGwAYwBUAE0AMwA=</Token><CustomData></CustomData><AcquirerID>19</AcquirerID><IssuerBankName>University First Federal Credit Union</IssuerBankName><IssuerBankCountry>us</IssuerBankCountry><Reference></Reference><AGVCode></AGVCode><AGVError></AGVError><UniqueCC>SuiMHP60FrDKfyaJs47hqqrR/JU=</UniqueCC><CustomData2></CustomData2><CreditCardInfo><IsPrepaid>0</IsPrepaid><CardType>Credit</CardType><CardProgram></CardProgram><CardProduct></CardProduct></CreditCardInfo><FraudResponse /></Response> + ) + end + + def failed_void_response + %( + <Response><Version>4.1.0</Version><ClientLoginID>SpreedlyTestTRX</ClientLoginID><ClientUniqueID></ClientUniqueID><TransactionID>101508208633</TransactionID><Status>ERROR</Status><AuthCode></AuthCode><AVSCode></AVSCode><CVV2Reply></CVV2Reply><Reason>Invalid Amount</Reason><ErrCode>-1100</ErrCode><ExErrCode>1201</ExErrCode><CustomData></CustomData><AcquirerID>-1</AcquirerID><IssuerBankName></IssuerBankName><IssuerBankCountry></IssuerBankCountry><Reference></Reference><AGVCode></AGVCode><AGVError></AGVError><UniqueCC>2jmj7l5rSw0yVb/vlWAYkK/YBwk=</UniqueCC><CustomData2></CustomData2><CreditCardInfo><IsPrepaid>0</IsPrepaid><CardType></CardType><CardProgram></CardProgram><CardProduct></CardProduct></CreditCardInfo></Response> + ) + end + + def successful_3ds_purchase_response + %( + <Response><Version>4.1.0</Version><ClientLoginID>SpreedlyManTestTRX</ClientLoginID><ClientUniqueID>98bd80c8c9534088311153ad6a67d108</ClientUniqueID><TransactionID>101510108310</TransactionID><Status>APPROVED</Status><AuthCode></AuthCode><AVSCode></AVSCode><CVV2Reply></CVV2Reply><ReasonCodes><Reason code="0"></Reason></ReasonCodes><ErrCode>0</ErrCode><ExErrCode>0</ExErrCode><Token>ZQBpAFAAMwBTAEcAMQBZAHcASQA4ADoAPQBlACQAZAB3ACMAWwAyAFoAWQBLAFUAPwBTAHYAKQAnAHQAUAA2AHYAYwAoAG0ARgBNAEEAcAAlAGEAMwA=</Token><CustomData></CustomData><ThreeDResponse><Auth3DResponse><Result>Y</Result><PaReq>eJxVUdtuwjAM/ZWK95GYgijIjVTWaUNTGdqQ4DUKFq2gF9J0A75+SVcuixTF59g+sY5xlWqi+ItUo0lgQnUtd+Rl27BXyScYAQce+MB7ApfRJx0FfpOus7IQ0Of9AbIrtK1apbIwAqU6zuYLMQSY8ABZBzEnPY8FfzhjGCH7o7GQOYlIq9J4K6qNd5VD1mZQlU1h9FkEQ47sCrDRB5EaU00ZO5RKHtKyth2ORXYfaNm4qLYqp2wrkjj6ud8XSFbRKYl3F/uGyFwFbqUhMeAwBvC5B6Opz6c+IGt5lLn73hlgR+kAVu6PqMu4xCOB1l1NhTqLydg6ckNIp6osyFZYJ28xsvvAz2/OT2WsRa+bdf2+X6cXtd9oHxZNPks+ojB0DrcFTi2zrkDAJ62cA8icBOuWx7oF2+jf4n8B</PaReq><MerchantID>000000000000715</MerchantID><ACSurl>https://pit.3dsecure.net/VbVTestSuiteService/pit1/acsService/paReq?summary=MjRlZGYwY2EtZTk5Zi00NDJjLTljOTAtNWUxZmRhMjEwODg3</ACSurl><XID>MDAwMDAwMDAwMDE1MTAxMDgzMTA=</XID><ThreeDReason></ThreeDReason></Auth3DResponse></ThreeDResponse><AcquirerID>19</AcquirerID><IssuerBankName>Visa Production Support Client Bid 1</IssuerBankName><IssuerBankCountry>us</IssuerBankCountry><Reference></Reference><AGVCode></AGVCode><AGVError></AGVError><UniqueCC>rDNDlh6XR8R6CVdGQyqDkZzdqE0=</UniqueCC><CustomData2></CustomData2><ThreeDFlow>1</ThreeDFlow><CreditCardInfo><IsPrepaid>0</IsPrepaid><CardType>Debit</CardType><CardProgram></CardProgram><CardProduct></CardProduct></CreditCardInfo><IsPartialApproval>0</IsPartialApproval><AmountInfo><RequestedAmount>1</RequestedAmount><RequestedCurrency>EUR</RequestedCurrency><ProcessedAmount>1</ProcessedAmount><ProcessedCurrency>EUR</ProcessedCurrency></AmountInfo><RRN></RRN><ICC></ICC><CVVReply></CVVReply></Response> + ) + end +end diff --git a/test/unit/gateways/sage_bankcard_test.rb b/test/unit/gateways/sage_bankcard_test.rb deleted file mode 100644 index 4ca1c997578..00000000000 --- a/test/unit/gateways/sage_bankcard_test.rb +++ /dev/null @@ -1,214 +0,0 @@ -require 'test_helper' - -class SageBankcardGatewayTest < Test::Unit::TestCase - include CommStub - - def setup - @gateway = SageBankcardGateway.new( - :login => 'login', - :password => 'password' - ) - - @credit_card = credit_card - @amount = 100 - - @options = { - :order_id => '1', - :billing_address => address, - :description => 'Store Purchase' - } - end - - def test_successful_authorization - @gateway.expects(:ssl_post).returns(successful_authorization_response) - - assert response = @gateway.purchase(@amount, @credit_card, @options) - assert_instance_of Response, response - assert_success response - - assert_equal "APPROVED", response.message - assert_equal "1234567890;bankcard", response.authorization - - assert_equal "A", response.params["success"] - assert_equal "911911", response.params["code"] - assert_equal "APPROVED", response.params["message"] - assert_equal "00", response.params["front_end"] - assert_equal "M", response.params["cvv_result"] - assert_equal "X", response.params["avs_result"] - assert_equal "00", response.params["risk"] - assert_equal "1234567890", response.params["reference"] - assert_equal "1000", response.params["order_number"] - assert_equal "0", response.params["recurring"] - end - - def test_successful_purchase - @gateway.expects(:ssl_post).returns(successful_purchase_response) - - assert response = @gateway.purchase(@amount, @credit_card, @options) - assert_instance_of Response, response - assert_success response - - assert_equal "APPROVED 000001", response.message - assert_equal "B5O89VPdf0;bankcard", response.authorization - - assert_equal "A", response.params["success"] - assert_equal "000001", response.params["code"] - assert_equal "APPROVED 000001", response.params["message"] - assert_equal "10", response.params["front_end"] - assert_equal "M", response.params["cvv_result"] - assert_equal "", response.params["avs_result"] - assert_equal "00", response.params["risk"] - assert_equal "B5O89VPdf0", response.params["reference"] - assert_equal "e81cab9e6144a160da82", response.params["order_number"] - assert_equal "0", response.params["recurring"] - end - - def test_declined_purchase - @gateway.expects(:ssl_post).returns(declined_purchase_response) - - assert response = @gateway.purchase(@amount, @credit_card, @options) - assert_failure response - assert response.test? - assert_equal "DECLINED", response.message - assert_equal "A5O89kkix0;bankcard", response.authorization - - assert_equal "E", response.params["success"] - assert_equal "000002", response.params["code"] - assert_equal "DECLINED", response.params["message"] - assert_equal "10", response.params["front_end"] - assert_equal "N", response.params["cvv_result"] - assert_equal "", response.params["avs_result"] - assert_equal "00", response.params["risk"] - assert_equal "A5O89kkix0", response.params["reference"] - assert_equal "3443d6426188f8256b8f", response.params["order_number"] - assert_equal "0", response.params["recurring"] - end - - def test_successful_capture - @gateway.expects(:ssl_post).returns(successful_capture_response) - - assert response = @gateway.capture(@amount, "A5O89kkix0") - assert_instance_of Response, response - assert_success response - - assert_equal "APPROVED 000001", response.message - assert_equal "B5O8AdFhu0;bankcard", response.authorization - - assert_equal "A", response.params["success"] - assert_equal "000001", response.params["code"] - assert_equal "APPROVED 000001", response.params["message"] - assert_equal "10", response.params["front_end"] - assert_equal "P", response.params["cvv_result"] - assert_equal "", response.params["avs_result"] - assert_equal "00", response.params["risk"] - assert_equal "B5O8AdFhu0", response.params["reference"] - assert_equal "ID5O8AdFhw", response.params["order_number"] - assert_equal "0", response.params["recurring"] - end - - def test_invalid_login - @gateway.expects(:ssl_post).returns(invalid_login_response) - - assert response = @gateway.purchase(@amount, @credit_card, @options) - assert_failure response - assert response.test? - assert_equal "SECURITY VIOLATION", response.message - assert_equal "0000000000;bankcard", response.authorization - - assert_equal "X", response.params["success"] - assert_equal "911911", response.params["code"] - assert_equal "SECURITY VIOLATION", response.params["message"] - assert_equal "00", response.params["front_end"] - assert_equal "P", response.params["cvv_result"] - assert_equal "", response.params["avs_result"] - assert_equal "00", response.params["risk"] - assert_equal "0000000000", response.params["reference"] - assert_equal "", response.params["order_number"] - assert_equal "0", response.params["recurring"] - end - - def test_include_customer_number_for_numeric_values - stub_comms do - @gateway.purchase(@amount, @credit_card, @options.merge({:customer => "123"})) - end.check_request do |method, data| - assert data =~ /T_customer_number=123/ - end.respond_with(successful_authorization_response) - end - - def test_dont_include_customer_number_for_numeric_values - stub_comms do - @gateway.purchase(@amount, @credit_card, @options.merge({:customer => "bob@test.com"})) - end.check_request do |method, data| - assert data !~ /T_customer_number/ - end.respond_with(successful_authorization_response) - end - - def test_avs_result - @gateway.expects(:ssl_post).returns(successful_authorization_response) - - response = @gateway.purchase(@amount, @credit_card, @options) - assert_equal 'X', response.avs_result['code'] - end - - def test_cvv_result - @gateway.expects(:ssl_post).returns(successful_purchase_response) - - response = @gateway.purchase(@amount, @credit_card, @options) - assert_equal 'M', response.cvv_result['code'] - end - - def test_us_address_with_state - post = {} - options = { - :billing_address => { :country => "US", :state => "CA"} - } - @gateway.send(:add_addresses, post, options) - - assert_equal "US", post[:C_country] - assert_equal "CA", post[:C_state] - end - - def test_us_address_without_state - post = {} - options = { - :billing_address => { :country => "US", :state => ""} - } - @gateway.send(:add_addresses, post, options) - - assert_equal "US", post[:C_country] - assert_equal "", post[:C_state] - end - - - def test_international_address_without_state - post = {} - options = { - :billing_address => { :country => "JP", :state => ""} - } - @gateway.send(:add_addresses, post, options) - - assert_equal "JP", post[:C_country] - assert_equal "Outside of United States", post[:C_state] - end - - private - def successful_authorization_response - "\002A911911APPROVED 00MX001234567890\0341000\0340\034\003" - end - - def successful_purchase_response - "\002A000001APPROVED 000001 10M 00B5O89VPdf0\034e81cab9e6144a160da82\0340\034\003" - end - - def successful_capture_response - "\002A000001APPROVED 000001 10P 00B5O8AdFhu0\034ID5O8AdFhw\0340\034\003" - end - - def declined_purchase_response - "\002E000002DECLINED 10N 00A5O89kkix0\0343443d6426188f8256b8f\0340\034\003" - end - - def invalid_login_response - "\002X911911SECURITY VIOLATION 00P 000000000000\034\0340\034\003" - end -end diff --git a/test/unit/gateways/sage_pay_test.rb b/test/unit/gateways/sage_pay_test.rb index 688b40826d9..034d563e448 100644 --- a/test/unit/gateways/sage_pay_test.rb +++ b/test/unit/gateways/sage_pay_test.rb @@ -4,17 +4,16 @@ class SagePayTest < Test::Unit::TestCase include CommStub def setup - @gateway = SagePayGateway.new( - :login => 'X' - ) + @gateway = SagePayGateway.new(login: 'X') @credit_card = credit_card('4242424242424242', :brand => 'visa') + @electron_credit_card = credit_card('4245190000000000', :brand => 'visa') @options = { :billing_address => { :name => 'Tekin Suleyman', :address1 => 'Flat 10 Lapwing Court', :address2 => 'West Didsbury', - :city => "Manchester", + :city => 'Manchester', :county => 'Greater Manchester', :country => 'GB', :zip => 'M20 2PS' @@ -32,8 +31,14 @@ def test_successful_purchase @gateway.expects(:ssl_post).returns(successful_purchase_response) assert response = @gateway.purchase(@amount, @credit_card, @options) - assert_instance_of Response, response - assert_equal "1;B8AE1CF6-9DEF-C876-1BB4-9B382E6CE520;4193753;OHMETD7DFK;purchase", response.authorization + assert_equal '1;B8AE1CF6-9DEF-C876-1BB4-9B382E6CE520;4193753;OHMETD7DFK;purchase', response.authorization + assert_success response + end + + def test_electron_card_type_is_set_correctly + @gateway.expects(:ssl_post).with(anything, regexp_matches(/CardType=UKE/)).returns(successful_purchase_response) + + assert response = @gateway.purchase(@amount, @electron_credit_card, @options) assert_success response end @@ -41,7 +46,6 @@ def test_unsuccessful_purchase @gateway.expects(:ssl_post).returns(unsuccessful_purchase_response) assert response = @gateway.purchase(@amount, @credit_card, @options) - assert_instance_of Response, response assert_failure response end @@ -53,59 +57,39 @@ def test_capture_url assert_equal 'https://test.sagepay.com/gateway/service/release.vsp', @gateway.send(:url_for, :capture) end - def test_electron_cards - # Visa range - assert_no_match SagePayGateway::ELECTRON, '4245180000000000' - - # First electron range - assert_match SagePayGateway::ELECTRON, '4245190000000000' - - # Second range - assert_match SagePayGateway::ELECTRON, '4249620000000000' - assert_match SagePayGateway::ELECTRON, '4249630000000000' + def test_matched_avs_result + @gateway.expects(:ssl_post).returns(unsuccessful_purchase_response) - # Third - assert_match SagePayGateway::ELECTRON, '4508750000000000' + response = @gateway.purchase(@amount, @credit_card, @options) - # Fourth - assert_match SagePayGateway::ELECTRON, '4844060000000000' - assert_match SagePayGateway::ELECTRON, '4844080000000000' + assert_equal 'Y', response.avs_result['postal_match'] + assert_equal 'Y', response.avs_result['street_match'] + end - # Fifth - assert_match SagePayGateway::ELECTRON, '4844110000000000' - assert_match SagePayGateway::ELECTRON, '4844550000000000' + def test_partially_matched_avs_result + @gateway.expects(:ssl_post).returns(successful_purchase_response) - # Sixth - assert_match SagePayGateway::ELECTRON, '4917300000000000' - assert_match SagePayGateway::ELECTRON, '4917590000000000' + response = @gateway.purchase(@amount, @credit_card, @options) - # Seventh - assert_match SagePayGateway::ELECTRON, '4918800000000000' + assert_equal 'Y', response.avs_result['postal_match'] + assert_equal 'N', response.avs_result['street_match'] + end - # Visa - assert_no_match SagePayGateway::ELECTRON, '4918810000000000' + def test_matched_cvv_result + @gateway.expects(:ssl_post).returns(unsuccessful_purchase_response) - # 19 PAN length - assert_match SagePayGateway::ELECTRON, '4249620000000000000' + response = @gateway.purchase(@amount, @credit_card, @options) - # 20 PAN length - assert_no_match SagePayGateway::ELECTRON, '42496200000000000' + assert_equal 'M', response.cvv_result['code'] end - def test_avs_result - @gateway.expects(:ssl_post).returns(successful_purchase_response) - - response = @gateway.purchase(@amount, @credit_card, @options) - assert_equal 'Y', response.avs_result['postal_match'] - assert_equal 'N', response.avs_result['street_match'] - end + def test_not_matched_cvv_result + @gateway.expects(:ssl_post).returns(successful_purchase_response) - def test_cvv_result - @gateway.expects(:ssl_post).returns(successful_purchase_response) + response = @gateway.purchase(@amount, @credit_card, @options) - response = @gateway.purchase(@amount, @credit_card, @options) - assert_equal 'N', response.cvv_result['code'] - end + assert_equal 'N', response.cvv_result['code'] + end def test_dont_send_fractional_amount_for_chinese_yen @amount = 100_00 # 100 YEN @@ -124,24 +108,237 @@ def test_send_fractional_amount_for_british_pounds @gateway.send(:add_amount, {}, @amount, @options) end + def test_paypal_callback_url_is_submitted + stub_comms(@gateway, :ssl_request) do + purchase_with_options(paypal_callback_url: 'callback.com') + end.check_request do |method, endpoint, data, headers| + assert_match(/PayPalCallbackURL=callback\.com/, data) + end.respond_with(successful_purchase_response) + end + + def test_basket_is_submitted + stub_comms(@gateway, :ssl_request) do + purchase_with_options(basket: 'A1.2 Basket section') + end.check_request do |method, endpoint, data, headers| + assert_match(/Basket=A1\.2\+Basket\+section/, data) + end.respond_with(successful_purchase_response) + end + def test_gift_aid_payment_is_submitted - stub_comms(:ssl_request) do - @gateway.purchase(@amount, @credit_card, @options.merge({:gift_aid_payment => 1})) + stub_comms(@gateway, :ssl_request) do + purchase_with_options(gift_aid_payment: 1) end.check_request do |method, endpoint, data, headers| assert_match(/GiftAidPayment=1/, data) end.respond_with(successful_purchase_response) end + def test_apply_avscv2_is_submitted + stub_comms(@gateway, :ssl_request) do + purchase_with_options(apply_avscv2: 1) + end.check_request do |method, endpoint, data, headers| + assert_match(/ApplyAVSCV2=1/, data) + end.respond_with(successful_purchase_response) + end + def test_disable_3d_security_flag_is_submitted - stub_comms(:ssl_request) do - @gateway.purchase(@amount, @credit_card, @options.merge({:apply_3d_secure => 1})) + stub_comms(@gateway, :ssl_request) do + purchase_with_options(apply_3d_secure: 1) end.check_request do |method, endpoint, data, headers| assert_match(/Apply3DSecure=1/, data) end.respond_with(successful_purchase_response) end + def test_account_type_is_submitted + stub_comms(@gateway, :ssl_request) do + purchase_with_options(account_type: 'M') + end.check_request do |method, endpoint, data, headers| + assert_match(/AccountType=M/, data) + end.respond_with(successful_purchase_response) + end + + def test_billing_agreement_is_submitted + stub_comms(@gateway, :ssl_request) do + purchase_with_options(billing_agreement: 1) + end.check_request do |method, endpoint, data, headers| + assert_match(/BillingAgreement=1/, data) + end.respond_with(successful_purchase_response) + end + + def test_store_token_is_submitted + stub_comms(@gateway, :ssl_request) do + purchase_with_options(store: true) + end.check_request do |method, endpoint, data, headers| + assert_match(/CreateToken=1/, data) + end.respond_with(successful_purchase_response) + end + + def test_basket_xml_is_submitted + stub_comms(@gateway, :ssl_request) do + purchase_with_options(basket_xml: 'A1.3 BasketXML section') + end.check_request do |method, endpoint, data, headers| + assert_match(/BasketXML=A1\.3\+BasketXML\+section/, data) + end.respond_with(successful_purchase_response) + end + + def test_customer_xml_is_submitted + stub_comms(@gateway, :ssl_request) do + purchase_with_options(customer_xml: 'A1.4 CustomerXML section') + end.check_request do |method, endpoint, data, headers| + assert_match(/CustomerXML=A1\.4\+CustomerXML\+section/, data) + end.respond_with(successful_purchase_response) + end + + def test_surcharge_xml_is_submitted + stub_comms(@gateway, :ssl_request) do + purchase_with_options(surcharge_xml: 'A1.1 SurchargeXML section') + end.check_request do |method, endpoint, data, headers| + assert_match(/SurchargeXML=A1\.1\+SurchargeXML\+section/, data) + end.respond_with(successful_purchase_response) + end + + def test_vendor_data_is_submitted + stub_comms(@gateway, :ssl_request) do + purchase_with_options(vendor_data: 'any data') + end.check_request do |method, endpoint, data, headers| + assert_match(/VendorData=any\+data/, data) + end.respond_with(successful_purchase_response) + end + + def test_language_is_submitted + stub_comms(@gateway, :ssl_request) do + purchase_with_options(language: 'FR') + end.check_request do |method, endpoint, data, headers| + assert_match(/Language=FR/, data) + end.respond_with(successful_purchase_response) + end + + def test_website_is_submitted + stub_comms(@gateway, :ssl_request) do + purchase_with_options(website: 'transaction-origin.com') + end.check_request do |method, endpoint, data, headers| + assert_match(/Website=transaction-origin\.com/, data) + end.respond_with(successful_purchase_response) + end + + def test_FIxxxx_optional_fields_are_submitted + stub_comms(@gateway, :ssl_request) do + purchase_with_options(recipient_account_number: '1234567890', + recipient_surname: 'Withnail', recipient_postcode: 'AB11AB', + recipient_dob: '19701223') + end.check_request do |method, endpoint, data, headers| + assert_match(/FIRecipientAcctNumber=1234567890/, data) + assert_match(/FIRecipientSurname=Withnail/, data) + assert_match(/FIRecipientPostcode=AB11AB/, data) + assert_match(/FIRecipientDoB=19701223/, data) + end.respond_with(successful_purchase_response) + end + + def test_description_is_truncated + huge_description = 'SagePay transactions fail if the déscription is more than 100 characters. Therefore, we truncate it to 100 characters.' + ' Lots more text ' * 1000 + stub_comms(@gateway, :ssl_request) do + purchase_with_options(description: huge_description) + end.check_request do |method, endpoint, data, headers| + assert_match(/&Description=SagePay\+transactions\+fail\+if\+the\+d%C3%A9scription\+is\+more\+than\+100\+characters.\+Therefore%2C\+we\+trunc&/, data) + end.respond_with(successful_purchase_response) + end + + def test_protocol_version_is_honoured + gateway = SagePayGateway.new(protocol_version: '2.23', login: 'X') + + stub_comms(gateway, :ssl_request) do + gateway.purchase(@amount, @credit_card, @options) + end.check_request do |method, endpoint, data, headers| + assert_match(/VPSProtocol=2.23/, data) + end.respond_with(successful_purchase_response) + end + + def test_referrer_id_is_added_to_post_data_parameters + ActiveMerchant::Billing::SagePayGateway.application_id = '00000000-0000-0000-0000-000000000001' + stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, @credit_card, @options) + end.check_request do |method, endpoint, data, headers| + assert data.include?('ReferrerID=00000000-0000-0000-0000-000000000001') + end.respond_with(successful_purchase_response) + ensure + ActiveMerchant::Billing::SagePayGateway.application_id = nil + end + + def test_successful_store + response = stub_comms(@gateway, :ssl_request) do + @gateway.store(@credit_card) + end.check_request do |method, endpoint, data, headers| + assert_match(/TxType=TOKEN/, data) + end.respond_with(successful_purchase_response) + + assert_equal '1', response.authorization + end + + def test_successful_verify + response = stub_comms do + @gateway.verify(@credit_card, @options) + end.respond_with(successful_authorize_response, successful_void_response) + assert_success response + end + + def test_successful_verify_with_failed_void + response = stub_comms do + @gateway.verify(@credit_card, @options) + end.respond_with(successful_authorize_response, unsuccessful_void_response) + assert_success response + end + + def test_unsuccessful_verify + response = stub_comms do + @gateway.verify(@credit_card, @options) + end.respond_with(unsuccessful_authorize_response, unsuccessful_void_response) + assert_failure response + end + + def test_transcript_scrubbing + assert_equal scrubbed_transcript, @gateway.scrub(transcript) + end + + def test_truncate_accounts_for_url_encoding + assert_nil @gateway.send(:truncate, nil, 3) + assert_equal 'Wow', @gateway.send(:truncate, 'WowAmaze', 3) + assert_equal 'Joikam Lomström', @gateway.send(:truncate, 'Joikam Lomström Rate', 20) + end + + def test_successful_authorization_and_capture_and_refund + auth = stub_comms do + @gateway.authorize(@amount, @credit_card, @options) + end.respond_with(successful_authorize_response) + assert_success auth + + capture = stub_comms do + @gateway.capture(@amount, auth.authorization) + end.respond_with(successful_capture_response) + assert_success capture + + refund = stub_comms do + @gateway.refund(@amount, capture.authorization, + order_id: generate_unique_id, + description: 'Refund txn' + ) + end.respond_with(successful_refund_response) + assert_success refund + end + + def test_repeat_purchase_with_reference_token + stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, '1455548a8d178beecd88fe6a285f50ff;{0D2ACAF0-FA64-6DFF-3869-7ADDDC1E0474};15353766;BS231FNE14;purchase', @options) + end.check_request do |method, endpoint, data, headers| + assert_match(/RelatedVPSTxId=%7B0D2ACAF0-FA64-6DFF-3869-7ADDDC1E0474%/, data) + assert_match(/TxType=REPEAT/, data) + end.respond_with(successful_purchase_response) + end + private + def purchase_with_options(optional) + @gateway.purchase(@amount, @credit_card, @options.merge(optional)) + end + def successful_purchase_response <<-RESP VPSProtocol=2.23 @@ -155,6 +352,7 @@ def successful_purchase_response PostCodeResult=MATCHED CV2Result=NOTMATCHED 3DSecureStatus=NOTCHECKED +Token=1 RESP end @@ -171,4 +369,127 @@ def unsuccessful_purchase_response CV2Result=MATCHED RESP end + + def successful_authorize_response + <<-RESP +VPSProtocol=2.23 +Status=OK +StatusDetail=0000 : The Authorisation was Successful. +VPSTxId=B8AE1CF6-9DEF-C876-1BB4-9B382E6CE520 +SecurityKey=OHMETD7DFK +TxAuthNo=4193753 +AVSCV2=NO DATA MATCHES +AddressResult=NOTMATCHED +PostCodeResult=MATCHED +CV2Result=NOTMATCHED +3DSecureStatus=NOTCHECKED +Token=1 + RESP + end + + def successful_refund_response + <<-RESP +VPSProtocol=3.00 +Status=OK +StatusDetail=0000 : The Authorisation was Successful. +SecurityKey=KUMJBP02HM +TxAuthNo=15282432 +VPSTxId={08C870A9-1E53-3852-BA44-CBC91612CBCA} + RESP + end + + def successful_capture_response + <<-RESP +VPSProtocol=3.00 +Status=OK +StatusDetail=2004 : The Release was Successful. + RESP + end + + def unsuccessful_authorize_response + <<-RESP +VPSProtocol=2.23 +Status=NOTAUTHED +StatusDetail=VSP Direct transaction from VSP Simulator. +VPSTxId=7BBA9078-8489-48CD-BF0D-10B0E6B0EF30 +SecurityKey=DKDYLDYLXV +AVSCV2=ALL MATCH +AddressResult=MATCHED +PostCodeResult=MATCHED +CV2Result=MATCHED + RESP + end + + def successful_void_response + <<-RESP +VPSProtocol=2.23 +Status=OK +StatusDetail=2006 : The Abort was Successful. +VPSTxId=B8AE1CF6-9DEF-C876-1BB4-9B382E6CE520 +SecurityKey=OHMETD7DFK +TxAuthNo=4193753 +AVSCV2=NO DATA MATCHES +AddressResult=NOTMATCHED +PostCodeResult=MATCHED +CV2Result=NOTMATCHED +3DSecureStatus=NOTCHECKED +Token=1 + RESP + end + + def unsuccessful_void_response + <<-RESP +VPSProtocol=2.23 +Status=MALFORMED +StatusDetail=3046 : The VPSTxId field is missing. +VPSTxId=7BBA9078-8489-48CD-BF0D-10B0E6B0EF30 +SecurityKey=DKDYLDYLXV +AVSCV2=ALL MATCH +AddressResult=MATCHED +PostCodeResult=MATCHED +CV2Result=MATCHED + RESP + end + + def transcript + <<-TRANSCRIPT + Amount=1.00&Currency=GBP&VendorTxCode=9094108b21f7b917e68d3e84b49ce9c4&Description=Store+purchase&CardHolder=Tekin+Suleyman&CardNumber=4929000000006&ExpiryDate=0616&CardType=VISA&CV2=123&BillingSurname=Suleyman&BillingFirstnames=Tekin&BillingAddress1=Flat+10+Lapwing+Court&BillingAddress2=West+Didsbury&BillingCity=Manchester&BillingCountry=GB&BillingPostCode=M20+2PS&DeliverySurname=Suleyman&DeliveryFirstnames=Tekin&DeliveryAddress1=120+Grosvenor+St&DeliveryCity=Manchester&DeliveryCountry=GB&DeliveryPostCode=M1+7QW&CustomerEMail=tekin%40tekin.co.uk&ClientIPAddress=86.150.65.37&Vendor=spreedly&TxType=PAYMENT&VPSProtocol=3.00 +I, [2015-07-22T17:16:49.292774 #97998] INFO -- : [ActiveMerchant::Billing::SagePayGateway] --> 200 OK (356 1.8635s) +D, [2015-07-22T17:16:49.292836 #97998] DEBUG -- : VPSProtocol=3.00 +Status=OK +StatusDetail=0000 : The Authorisation was Successful. +VPSTxId={D5B43220-E93C-ED13-6643-D22224BD1CDB} +SecurityKey=7OYK4OHM7Y +TxAuthNo=8769237 +AVSCV2=DATA NOT CHECKED +AddressResult=NOTPROVIDED +PostCodeResult=NOTPROVIDED +CV2Result=NOTPROVIDED +3DSecureStatus=NOTCHECKED +DeclineCode=00 +ExpiryDate=0616 +BankAuthCode=999777 + TRANSCRIPT + end + + def scrubbed_transcript + <<-TRANSCRIPT + Amount=1.00&Currency=GBP&VendorTxCode=9094108b21f7b917e68d3e84b49ce9c4&Description=Store+purchase&CardHolder=Tekin+Suleyman&CardNumber=[FILTERED]&ExpiryDate=0616&CardType=VISA&CV2=[FILTERED]&BillingSurname=Suleyman&BillingFirstnames=Tekin&BillingAddress1=Flat+10+Lapwing+Court&BillingAddress2=West+Didsbury&BillingCity=Manchester&BillingCountry=GB&BillingPostCode=M20+2PS&DeliverySurname=Suleyman&DeliveryFirstnames=Tekin&DeliveryAddress1=120+Grosvenor+St&DeliveryCity=Manchester&DeliveryCountry=GB&DeliveryPostCode=M1+7QW&CustomerEMail=tekin%40tekin.co.uk&ClientIPAddress=86.150.65.37&Vendor=spreedly&TxType=PAYMENT&VPSProtocol=3.00 +I, [2015-07-22T17:16:49.292774 #97998] INFO -- : [ActiveMerchant::Billing::SagePayGateway] --> 200 OK (356 1.8635s) +D, [2015-07-22T17:16:49.292836 #97998] DEBUG -- : VPSProtocol=3.00 +Status=OK +StatusDetail=0000 : The Authorisation was Successful. +VPSTxId={D5B43220-E93C-ED13-6643-D22224BD1CDB} +SecurityKey=7OYK4OHM7Y +TxAuthNo=8769237 +AVSCV2=DATA NOT CHECKED +AddressResult=NOTPROVIDED +PostCodeResult=NOTPROVIDED +CV2Result=NOTPROVIDED +3DSecureStatus=NOTCHECKED +DeclineCode=00 +ExpiryDate=0616 +BankAuthCode=999777 + TRANSCRIPT + end end diff --git a/test/unit/gateways/sage_test.rb b/test/unit/gateways/sage_test.rb new file mode 100644 index 00000000000..b6faa1761c6 --- /dev/null +++ b/test/unit/gateways/sage_test.rb @@ -0,0 +1,540 @@ +require 'test_helper' + +class SageGatewayTest < Test::Unit::TestCase + include CommStub + + def setup + @gateway = SageGateway.new( + :login => 'login', + :password => 'password' + ) + + @credit_card = credit_card + @check = check + @amount = 100 + + @options = { + :order_id => '1', + :billing_address => address, + :description => 'Store Purchase' + } + + @check_options = { + :order_id => generate_unique_id, + :billing_address => address, + :shipping_address => address, + :email => 'longbob@example.com', + :drivers_license_state => 'CA', + :drivers_license_number => '12345689', + :date_of_birth => Date.new(1978, 8, 11), + :ssn => '078051120' + } + end + + def test_successful_authorization + @gateway.expects(:ssl_post).returns(successful_authorization_response) + + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_instance_of Response, response + assert_success response + + assert_equal 'APPROVED', response.message + assert_equal '1234567890;bankcard', response.authorization + + assert_equal 'A', response.params['success'] + assert_equal '911911', response.params['code'] + assert_equal 'APPROVED', response.params['message'] + assert_equal '00', response.params['front_end'] + assert_equal 'M', response.params['cvv_result'] + assert_equal 'X', response.params['avs_result'] + assert_equal '00', response.params['risk'] + assert_equal '1234567890', response.params['reference'] + assert_equal '1000', response.params['order_number'] + assert_equal '0', response.params['recurring'] + end + + def test_successful_purchase + @gateway.expects(:ssl_post).returns(successful_purchase_response) + + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_instance_of Response, response + assert_success response + + assert_equal 'APPROVED 000001', response.message + assert_equal 'B5O89VPdf0;bankcard', response.authorization + + assert_equal 'A', response.params['success'] + assert_equal '000001', response.params['code'] + assert_equal 'APPROVED 000001', response.params['message'] + assert_equal '10', response.params['front_end'] + assert_equal 'M', response.params['cvv_result'] + assert_equal '', response.params['avs_result'] + assert_equal '00', response.params['risk'] + assert_equal 'B5O89VPdf0', response.params['reference'] + assert_equal 'e81cab9e6144a160da82', response.params['order_number'] + assert_equal '0', response.params['recurring'] + end + + def test_declined_purchase + @gateway.expects(:ssl_post).returns(declined_purchase_response) + + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert response.test? + assert_equal 'DECLINED', response.message + assert_equal 'A5O89kkix0;bankcard', response.authorization + + assert_equal 'E', response.params['success'] + assert_equal '000002', response.params['code'] + assert_equal 'DECLINED', response.params['message'] + assert_equal '10', response.params['front_end'] + assert_equal 'N', response.params['cvv_result'] + assert_equal '', response.params['avs_result'] + assert_equal '00', response.params['risk'] + assert_equal 'A5O89kkix0', response.params['reference'] + assert_equal '3443d6426188f8256b8f', response.params['order_number'] + assert_equal '0', response.params['recurring'] + end + + def test_successful_capture + @gateway.expects(:ssl_post).returns(successful_capture_response) + + assert response = @gateway.capture(@amount, 'A5O89kkix0') + assert_instance_of Response, response + assert_success response + + assert_equal 'APPROVED 000001', response.message + assert_equal 'B5O8AdFhu0;bankcard', response.authorization + + assert_equal 'A', response.params['success'] + assert_equal '000001', response.params['code'] + assert_equal 'APPROVED 000001', response.params['message'] + assert_equal '10', response.params['front_end'] + assert_equal 'P', response.params['cvv_result'] + assert_equal '', response.params['avs_result'] + assert_equal '00', response.params['risk'] + assert_equal 'B5O8AdFhu0', response.params['reference'] + assert_equal 'ID5O8AdFhw', response.params['order_number'] + assert_equal '0', response.params['recurring'] + end + + def test_successful_refund + @gateway.expects(:ssl_post).returns(successful_refund_response) + response = @gateway.refund(@amount, 'Authorization') + assert_success response + assert_equal 'G68FCU2c60;bankcard', response.authorization + assert_equal 'APPROVED', response.message + end + + def test_failed_refund + @gateway.expects(:ssl_post).returns(failed_refund_response) + + response = @gateway.refund(@amount, 'Authorization') + assert_failure response + assert_equal 'INVALID T_REFERENCE', response.message + end + + def test_invalid_login + @gateway.expects(:ssl_post).returns(invalid_login_response) + + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert response.test? + assert_equal 'SECURITY VIOLATION', response.message + assert_equal '0000000000;bankcard', response.authorization + + assert_equal 'X', response.params['success'] + assert_equal '911911', response.params['code'] + assert_equal 'SECURITY VIOLATION', response.params['message'] + assert_equal '00', response.params['front_end'] + assert_equal 'P', response.params['cvv_result'] + assert_equal '', response.params['avs_result'] + assert_equal '00', response.params['risk'] + assert_equal '0000000000', response.params['reference'] + assert_equal '', response.params['order_number'] + assert_equal '0', response.params['recurring'] + end + + def test_include_customer_number_for_numeric_values + stub_comms do + @gateway.purchase(@amount, @credit_card, @options.merge({:customer => '123'})) + end.check_request do |method, data| + assert data =~ /T_customer_number=123/ + end.respond_with(successful_authorization_response) + end + + def test_dont_include_customer_number_for_numeric_values + stub_comms do + @gateway.purchase(@amount, @credit_card, @options.merge({:customer => 'bob@test.com'})) + end.check_request do |method, data| + assert data !~ /T_customer_number/ + end.respond_with(successful_authorization_response) + end + + def test_avs_result + @gateway.expects(:ssl_post).returns(successful_authorization_response) + + response = @gateway.purchase(@amount, @credit_card, @options) + assert_equal 'X', response.avs_result['code'] + end + + def test_cvv_result + @gateway.expects(:ssl_post).returns(successful_purchase_response) + + response = @gateway.purchase(@amount, @credit_card, @options) + assert_equal 'M', response.cvv_result['code'] + end + + def test_address_with_state + post = {} + options = { + :billing_address => { :country => 'US', :state => 'CA'} + } + @gateway.send(:add_addresses, post, options) + + assert_equal 'US', post[:C_country] + assert_equal 'CA', post[:C_state] + end + + def test_address_without_state + post = {} + options = { + :billing_address => { :country => 'NZ', :state => ''} + } + @gateway.send(:add_addresses, post, options) + + assert_equal 'NZ', post[:C_country] + assert_equal 'Outside of US', post[:C_state] + end + + def test_successful_check_purchase + @gateway.expects(:ssl_post).returns(successful_check_purchase_response) + + response = @gateway.purchase(@amount, @check, @check_options) + assert_instance_of Response, response + assert_success response + + assert_equal 'ACCEPTED', response.message + assert_equal 'C5O8NUdNt0;virtual_check', response.authorization + + assert_equal 'A', response.params['success'] + assert_equal '', response.params['code'] + assert_equal 'ACCEPTED', response.params['message'] + assert_equal '00', response.params['risk'] + assert_equal 'C5O8NUdNt0', response.params['reference'] + assert_equal '89be635e663b05eca587', response.params['order_number'] + assert_equal '0', response.params['authentication_indicator'] + assert_equal 'NONE', response.params['authentication_disclosure'] + end + + def test_declined_check_purchase + @gateway.expects(:ssl_post).returns(declined_check_purchase_response) + + response = @gateway.purchase(@amount, @check, @check_options) + assert_failure response + assert response.test? + assert_equal 'INVALID C_RTE', response.message + assert_equal 'C5O8NR6Nr0;virtual_check', response.authorization + + assert_equal 'X', response.params['success'] + assert_equal '900016', response.params['code'] + assert_equal 'INVALID C_RTE', response.params['message'] + assert_equal '00', response.params['risk'] + assert_equal 'C5O8NR6Nr0', response.params['reference'] + assert_equal 'd98cf50f7a2430fe04ad', response.params['order_number'] + assert_equal '0', response.params['authentication_indicator'] + assert_equal nil, response.params['authentication_disclosure'] + end + + def test_successful_store + response = stub_comms do + @gateway.store(@credit_card, @options) + end.check_request do |endpoint, data, headers| + assert_match(/<ns1:M_ID>login<\/ns1:M_ID>/, data) + assert_match(/<ns1:M_KEY>password<\/ns1:M_KEY>/, data) + assert_match(/<ns1:CARDNUMBER>#{credit_card.number}<\/ns1:CARDNUMBER>/, data) + assert_match(/<ns1:EXPIRATION_DATE>#{expected_expiration_date}<\/ns1:EXPIRATION_DATE>/, data) + assert_equal headers['SOAPAction'], 'https://www.sagepayments.net/web_services/wsVault/wsVault/INSERT_CREDIT_CARD_DATA' + end.respond_with(successful_store_response) + + assert response + assert_instance_of Response, response + assert_success response + assert_equal '66234d2dfec24efe9fdcd4b751578c11', response.authorization + assert_equal 'Success', response.message + end + + def test_failed_store + response = stub_comms do + @gateway.store(@credit_card, @options) + end.check_request do |endpoint, data, headers| + assert_match(/<ns1:M_ID>login<\/ns1:M_ID>/, data) + assert_match(/<ns1:M_KEY>password<\/ns1:M_KEY>/, data) + assert_match(/<ns1:CARDNUMBER>#{credit_card.number}<\/ns1:CARDNUMBER>/, data) + assert_match(/<ns1:EXPIRATION_DATE>#{expected_expiration_date}<\/ns1:EXPIRATION_DATE>/, data) + assert_equal headers['SOAPAction'], 'https://www.sagepayments.net/web_services/wsVault/wsVault/INSERT_CREDIT_CARD_DATA' + end.respond_with(failed_store_response) + + assert response + assert_instance_of Response, response + assert_failure response + assert_nil response.authorization + assert_equal 'Unable to verify vault service', response.message + end + + def test_successful_unstore + response = stub_comms do + @gateway.unstore('1234', @options) + end.check_request do |endpoint, data, headers| + assert_match(/<ns1:M_ID>login<\/ns1:M_ID>/, data) + assert_match(/<ns1:M_KEY>password<\/ns1:M_KEY>/, data) + assert_match(/<ns1:GUID>1234<\/ns1:GUID>/, data) + assert_equal headers['SOAPAction'], 'https://www.sagepayments.net/web_services/wsVault/wsVault/DELETE_DATA' + end.respond_with(successful_unstore_response) + + assert response + assert_instance_of Response, response + assert_success response + assert_equal 'Succeeded', response.message + end + + def test_failed_unstore + response = stub_comms do + @gateway.unstore('1234', @options) + end.check_request do |endpoint, data, headers| + assert_match(/<ns1:M_ID>login<\/ns1:M_ID>/, data) + assert_match(/<ns1:M_KEY>password<\/ns1:M_KEY>/, data) + assert_match(/<ns1:GUID>1234<\/ns1:GUID>/, data) + assert_equal headers['SOAPAction'], 'https://www.sagepayments.net/web_services/wsVault/wsVault/DELETE_DATA' + end.respond_with(failed_unstore_response) + + assert response + assert_instance_of Response, response + assert_failure response + assert_equal 'Failed', response.message + end + + def test_scrub + assert_equal @gateway.scrub(pre_scrubbed), post_scrubbed + assert_equal @gateway.scrub(pre_scrubbed_echeck), post_scrubbed_echeck + end + + def test_supports_scrubbing? + assert @gateway.supports_scrubbing? + end + + private + + def successful_authorization_response + "\002A911911APPROVED 00MX001234567890\0341000\0340\034\003" + end + + def successful_purchase_response + "\002A000001APPROVED 000001 10M 00B5O89VPdf0\034e81cab9e6144a160da82\0340\034\003" + end + + def successful_capture_response + "\002A000001APPROVED 000001 10P 00B5O8AdFhu0\034ID5O8AdFhw\0340\034\003" + end + + def declined_purchase_response + "\002E000002DECLINED 10N 00A5O89kkix0\0343443d6426188f8256b8f\0340\034\003" + end + + def successful_refund_response + "\x02A APPROVED 10P 00G68FCU2c60\x1C16119318c1edb9a27f70\x1C0\x1C\x03" + end + + def failed_refund_response + "\x02X900022INVALID T_REFERENCE 00P 00G68FCVJd10\x1C72bf690488cf72c81120\x1C0\x1C\x03" + end + + def invalid_login_response + "\002X911911SECURITY VIOLATION 00P 000000000000\034\0340\034\003" + end + + def successful_check_purchase_response + "\002A ACCEPTED 00C5O8NUdNt0\03489be635e663b05eca587\0340\034NONE\034\003" + end + + def declined_check_purchase_response + "\002X900016INVALID C_RTE 00C5O8NR6Nr0\034d98cf50f7a2430fe04ad\0340\034\034\003" + end + + def expected_expiration_date + '%02d%02d' % [@credit_card.month, @credit_card.year.to_s[2..4]] + end + + def successful_store_response + <<-XML +<?xml version="1.0" encoding="utf-8" ?> +<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> + <soap:Body> + <INSERT_CREDIT_CARD_DATAResponse xmlns="https://www.sagepayments.net/web_services/wsVault/wsVault"> + <INSERT_CREDIT_CARD_DATAResult> + <!-- Bunch of xs:schema stuff. Then... --> + <diffgr:diffgram xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" xmlns:diffgr="urn:schemas-microsoft-com:xml-diffgram-v1"> + <NewDataSet xmlns=""> + <Table1 diffgr:id="Table11" msdata:rowOrder="0" diffgr:hasChanges="inserted"> + <SUCCESS>true</SUCCESS> + <GUID>66234d2dfec24efe9fdcd4b751578c11</GUID> + <MESSAGE>SUCCESS</MESSAGE> + </Table1> + </NewDataSet> + </diffgr:diffgram> + </INSERT_CREDIT_CARD_DATAResult> + </INSERT_CREDIT_CARD_DATAResponse> + </soap:Body> +</soap:Envelope> + XML + end + + def failed_store_response + <<-XML +<?xml version="1.0" encoding="utf-8" ?> +<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> + <soap:Body> + <INSERT_CREDIT_CARD_DATAResponse xmlns="https://www.sagepayments.net/web_services/wsVault/wsVault"> + <INSERT_CREDIT_CARD_DATAResult> + <!-- Bunch of xs:schema stuff. Then... --> + <diffgr:diffgram xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" xmlns:diffgr="urn:schemas-microsoft-com:xml-diffgram-v1"> + <NewDataSet xmlns=""> + <Table1 diffgr:id="Table11" msdata:rowOrder="0" diffgr:hasChanges="inserted"> + <SUCCESS>false</SUCCESS> + <GUID /> + <MESSAGE>UNABLE TO VERIFY VAULT SERVICE</MESSAGE> + </Table1> + </NewDataSet> + </diffgr:diffgram> + </INSERT_CREDIT_CARD_DATAResult> + </INSERT_CREDIT_CARD_DATAResponse> + </soap:Body> +</soap:Envelope> + XML + end + + def successful_unstore_response + <<-XML +<?xml version="1.0" encoding="utf-8" ?> +<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> + <soap:Body> + <DELETE_DATAResponse xmlns="https://www.sagepayments.net/web_services/wsVault/wsVault"> + <DELETE_DATAResult>true</DELETE_DATAResult> + </DELETE_DATAResponse> + </soap:Body> +</soap:Envelope> + XML + end + + def failed_unstore_response + <<-XML +<?xml version="1.0" encoding="utf-8" ?> +<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> + <soap:Body> + <DELETE_DATAResponse xmlns="https://www.sagepayments.net/web_services/wsVault/wsVault"> + <DELETE_DATAResult>false</DELETE_DATAResult> + </DELETE_DATAResponse> + </soap:Body> +</soap:Envelope> + XML + end + + def pre_scrubbed + <<-PRE_SCRUBBED +opening connection to www.sagepayments.net:443... +opened +starting SSL for www.sagepayments.net:443... +SSL established +<- "POST /cgi-bin/eftBankcard.dll?transaction HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: www.sagepayments.net\r\nContent-Length: 444\r\n\r\n" +<- "C_name=Longbob+Longsen&C_cardnumber=4111111111111111&C_exp=0917&C_cvv=123&T_amt=1.00&T_ordernum=1741a24e00a5a5f11653&C_address=456+My+Street&C_city=Ottawa&C_state=ON&C_zip=K1C2N6&C_country=CA&C_telephone=%28555%29555-5555&C_fax=%28555%29555-6666&C_email=longbob%40example.com&C_ship_name=Jim+Smith&C_ship_address=456+My+Street&C_ship_city=Ottawa&C_ship_state=ON&C_ship_zip=K1C2N6&C_ship_country=CA&M_id=214282982451&M_key=Z5W2S8J7X8T5&T_code=01" +-> "HTTP/1.1 200 OK\r\n" +-> "Content-Type: text/html\r\n" +-> "Content-Encoding: gzip\r\n" +-> "Vary: Accept-Encoding\r\n" +-> "Server: \r\n" +-> "X-AspNet-Version: \r\n" +-> "X-Powered-By: ASP.NET\r\n" +-> "Date: Thu, 30 Jun 2016 02:58:40 GMT\r\n" +-> "Connection: close\r\n" +-> "Content-Length: 185\r\n" +-> "\r\n" +reading 185 bytes... +-> "\x1F\x8B\b\x00\x00\x00\x00\x00\x04\x00\xED\xBD\a`\x1CI\x96%&/m\xCA{\x7FJ\xF5J\xD7\xE0t\xA1\b\x80`\x13$\xD8\x90@\x10\xEC\xC1\x88\xCD\xE6\x92\xEC\x1DiG#)\xAB*\x81\xCAeVe]f\x16@\xCC\xED\x9D\xBC\xF7\xDE{\xEF\xBD\xF7\xDE{\xEF\xBD\xF7\xBA;\x9DN'\xF7\xDF\xFF?\\fd\x01l\xF6\xCEJ\xDA\xC9\x9E!\x80\xAA\xC8\x1F?~|\x1F?\"~\xAD\xE3\x1D<\xBB\xC7/_\xBE\xFA\xF2'O\x9F\xA6\xF4\a\xFD\x99v\x9F\xDD\x9D/\xE8\xAB\xCF?}\xF3\xF9\x17\xEF\xCE\xBF;\xDF\xF9\x9Dv\x1F\xEC\xEFf{\xFB\xF9\xCENv?\xBB\x7F\xBE\xBB\xFB\xE9\xFD{\xBF\xD3\xCE\xEF\xF4k\xFF?XI\x04rQ\x00\x00\x00" +read 185 bytes +Conn close + PRE_SCRUBBED + end + + def post_scrubbed + <<-POST_SCRUBBED +opening connection to www.sagepayments.net:443... +opened +starting SSL for www.sagepayments.net:443... +SSL established +<- "POST /cgi-bin/eftBankcard.dll?transaction HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: www.sagepayments.net\r\nContent-Length: 444\r\n\r\n" +<- "C_name=Longbob+Longsen&C_cardnumber=[FILTERED]&C_exp=0917&C_cvv=[FILTERED]&T_amt=1.00&T_ordernum=1741a24e00a5a5f11653&C_address=456+My+Street&C_city=Ottawa&C_state=ON&C_zip=K1C2N6&C_country=CA&C_telephone=%28555%29555-5555&C_fax=%28555%29555-6666&C_email=longbob%40example.com&C_ship_name=Jim+Smith&C_ship_address=456+My+Street&C_ship_city=Ottawa&C_ship_state=ON&C_ship_zip=K1C2N6&C_ship_country=CA&M_id=[FILTERED]&M_key=[FILTERED]&T_code=01" +-> "HTTP/1.1 200 OK\r\n" +-> "Content-Type: text/html\r\n" +-> "Content-Encoding: gzip\r\n" +-> "Vary: Accept-Encoding\r\n" +-> "Server: \r\n" +-> "X-AspNet-Version: \r\n" +-> "X-Powered-By: ASP.NET\r\n" +-> "Date: Thu, 30 Jun 2016 02:58:40 GMT\r\n" +-> "Connection: close\r\n" +-> "Content-Length: 185\r\n" +-> "\r\n" +reading 185 bytes... +-> \"\u001F?\b\u0000\u0000\u0000\u0000\u0000\u0004\u0000??\a`\u001CI?%&/m?{\u007FJ?J??t?\b?`\u0013$?@\u0010??????\u001DiG#)?*??eVe]f\u0016@????{???{???;?N'????\\fd\u0001l??J??!???\u001F?~|\u001F?\"~??\u001D<??/_???'O???\a??v??/???}??\u0017??;???v\u001F??f{???Nv??\u007F?????{?????k??XI\u0004rQ\u0000\u0000\u0000\" +read 185 bytes +Conn close + POST_SCRUBBED + end + + def pre_scrubbed_echeck + <<-PRE_SCRUBBED +opening connection to www.sagepayments.net:443... +opened +starting SSL for www.sagepayments.net:443... +SSL established +<- "POST /cgi-bin/eftVirtualCheck.dll?transaction HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: www.sagepayments.net\r\nContent-Length: 562\r\n\r\n" +<- "C_first_name=Jim&C_last_name=Smith&C_rte=244183602&C_acct=15378535&C_check_number=1&C_acct_type=DDA&C_customer_type=WEB&C_originator_id=&T_addenda=&C_ssn=&C_dl_state_code=&C_dl_number=&C_dob=&T_amt=1.00&T_ordernum=0ac6fd1f74a98de94bf9&C_address=456+My+Street&C_city=Ottawa&C_state=ON&C_zip=K1C2N6&C_country=CA&C_telephone=%28555%29555-5555&C_fax=%28555%29555-6666&C_email=longbob%40example.com&C_ship_name=Jim+Smith&C_ship_address=456+My+Street&C_ship_city=Ottawa&C_ship_state=ON&C_ship_zip=K1C2N6&C_ship_country=CA&M_id=562313162894&M_key=J6U9B3G2F6L3&T_code=01" +-> "HTTP/1.1 200 OK\r\n" +-> "Cache-Control: no-cache\r\n" +-> "Pragma: no-cache\r\n" +-> "Transfer-Encoding: chunked\r\n" +-> "Content-Type: text/html; charset=us-ascii\r\n" +-> "Content-Encoding: gzip\r\n" +-> "Expires: -1\r\n" +-> "Vary: Accept-Encoding\r\n" +-> "Server: Microsoft-IIS/7.5\r\n" +-> "X-Powered-By: ASP.NET\r\n" +-> "Date: Thu, 02 Nov 2017 13:26:30 GMT\r\n" +-> "Connection: close\r\n" +-> "\r\n" +-> "ac\r\n" +reading 172 bytes... +-> "\x1F\x8B\b\x00\x00\x00\x00\x00\x04\x00\xED\xBD\a`\x1CI\x96%&/m\xCA{\x7FJ\xF5J\xD7\xE0t\xA1\b\x80`\x13$\xD8\x90@\x10\xEC\xC1\x88\xCD\xE6\x92\xEC\x1DiG#)\xAB*\x81\xCAeVe]f\x16@\xCC\xED\x9D\xBC\xF7\xDE{\xEF\xBD\xF7\xDE{\xEF\xBD\xF7\xBA;\x9DN'\xF7\xDF\xFF?\\fd\x01l\xF6\xCEJ\xDA\xC9\x9E!\x80\xAA\xC8\x1F?~|\x1F?\"~\xAD\xE3\x94\x9F\xE3\x93\x93\xD3\x97oN\x9F\xD2\xAF\xD1gg\xE7\xD9\x93\xBDo?\xFC\x89\xAF\x16\xF7v~\xA7\x9Dl\xFA\xE9\xF9l\xF7\xFC\xC1~\xF6\xF0`\x96?\xDC\x9F\x9C?\xFC\x9Dv~\xA7\x17_\xBE8\xA5\x1F\xBF" +read 172 bytes +reading 2 bytes... +-> "\r\n" +read 2 bytes +-> "b\r\n" +reading 11 bytes... +-> "\xF6\xFF\x03\x90\xEB\x1E T\x00\x00\x00" +read 11 bytes +reading 2 bytes... +-> "\r\n" +read 2 bytes +-> "0\r\n" +-> "\r\n" +Conn close + PRE_SCRUBBED + end + + def post_scrubbed_echeck + <<-POST_SCRUBBED +opening connection to www.sagepayments.net:443...\nopened\nstarting SSL for www.sagepayments.net:443...\nSSL established\n<- \"POST /cgi-bin/eftVirtualCheck.dll?transaction HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: www.sagepayments.net\r\nContent-Length: 562\r\n\r\n\"\n<- \"C_first_name=Jim&C_last_name=Smith&C_rte=[FILTERED]&C_acct=[FILTERED]&C_check_number=1&C_acct_type=DDA&C_customer_type=WEB&C_originator_id=&T_addenda=&C_ssn=[FILTERED]&C_dl_state_code=&C_dl_number=&C_dob=&T_amt=1.00&T_ordernum=0ac6fd1f74a98de94bf9&C_address=456+My+Street&C_city=Ottawa&C_state=ON&C_zip=K1C2N6&C_country=CA&C_telephone=%28555%29555-5555&C_fax=%28555%29555-6666&C_email=longbob%40example.com&C_ship_name=Jim+Smith&C_ship_address=456+My+Street&C_ship_city=Ottawa&C_ship_state=ON&C_ship_zip=K1C2N6&C_ship_country=CA&M_id=[FILTERED]&M_key=[FILTERED]&T_code=01\"\n-> \"HTTP/1.1 200 OK\r\n\"\n-> \"Cache-Control: no-cache\r\n\"\n-> \"Pragma: no-cache\r\n\"\n-> \"Transfer-Encoding: chunked\r\n\"\n-> \"Content-Type: text/html; charset=us-ascii\r\n\"\n-> \"Content-Encoding: gzip\r\n\"\n-> \"Expires: -1\r\n\"\n-> \"Vary: Accept-Encoding\r\n\"\n-> \"Server: Microsoft-IIS/7.5\r\n\"\n-> \"X-Powered-By: ASP.NET\r\n\"\n-> \"Date: Thu, 02 Nov 2017 13:26:30 GMT\r\n\"\n-> \"Connection: close\r\n\"\n-> \"\r\n\"\n-> \"ac\r\n\"\nreading 172 bytes...\n-> \"\u001F?\b\u0000\u0000\u0000\u0000\u0000\u0004\u0000??\a`\u001CI?%&/m?{\u007FJ?J??t?\b?`\u0013$?@\u0010??????\u001DiG#)?*??eVe]f\u0016@????{???{???;?N'????\\fd\u0001l??J??!???\u001F?~|\u001F?\"~????oN???gg???o????\u0016?v~??l???l???~??`???????v~?\u0017_?8?\u001F?\"\nread 172 bytes\nreading 2 bytes...\n-> \"\r\n\"\nread 2 bytes\n-> \"b\r\n\"\nreading 11 bytes...\n-> \"??\u0003??\u001E T\u0000\u0000\u0000\"\nread 11 bytes\nreading 2 bytes...\n-> \"\r\n\"\nread 2 bytes\n-> \"0\r\n\"\n-> \"\r\n\"\nConn close + POST_SCRUBBED + end +end diff --git a/test/unit/gateways/sage_virtual_check_test.rb b/test/unit/gateways/sage_virtual_check_test.rb deleted file mode 100644 index 748a2be7a01..00000000000 --- a/test/unit/gateways/sage_virtual_check_test.rb +++ /dev/null @@ -1,71 +0,0 @@ -require 'test_helper' - -class SageVirtualCheckTest < Test::Unit::TestCase - def setup - @gateway = SageVirtualCheckGateway.new( - :login => 'login', - :password => 'password' - ) - - @check = check - - @options = { - :order_id => generate_unique_id, - :billing_address => address, - :shipping_address => address, - :email => 'longbob@example.com', - :drivers_license_state => 'CA', - :drivers_license_number => '12345689', - :date_of_birth => Date.new(1978, 8, 11), - :ssn => '078051120' - } - end - - def test_successful_purchase - @gateway.expects(:ssl_post).returns(successful_purchase_response) - - assert response = @gateway.purchase(@amount, @check, @options) - assert_instance_of Response, response - assert_success response - - assert_equal "ACCEPTED", response.message - assert_equal "C5O8NUdNt0;virtual_check", response.authorization - - assert_equal "A", response.params["success"] - assert_equal "", response.params["code"] - assert_equal "ACCEPTED", response.params["message"] - assert_equal "00", response.params["risk"] - assert_equal "C5O8NUdNt0", response.params["reference"] - assert_equal "89be635e663b05eca587", response.params["order_number"] - assert_equal "0", response.params["authentication_indicator"] - assert_equal "NONE", response.params["authentication_disclosure"] - end - - def test_declined_purchase - @gateway.expects(:ssl_post).returns(declined_purchase_response) - - assert response = @gateway.purchase(@amount, @check, @options) - assert_failure response - assert response.test? - assert_equal "INVALID C_RTE", response.message - assert_equal "C5O8NR6Nr0;virtual_check", response.authorization - - assert_equal "X", response.params["success"] - assert_equal "900016", response.params["code"] - assert_equal "INVALID C_RTE", response.params["message"] - assert_equal "00", response.params["risk"] - assert_equal "C5O8NR6Nr0", response.params["reference"] - assert_equal "d98cf50f7a2430fe04ad", response.params["order_number"] - assert_equal "0", response.params["authentication_indicator"] - assert_equal nil, response.params["authentication_disclosure"] - end - - private - def successful_purchase_response - "\002A ACCEPTED 00C5O8NUdNt0\03489be635e663b05eca587\0340\034NONE\034\003" - end - - def declined_purchase_response - "\002X900016INVALID C_RTE 00C5O8NR6Nr0\034d98cf50f7a2430fe04ad\0340\034\034\003" - end -end diff --git a/test/unit/gateways/sallie_mae_test.rb b/test/unit/gateways/sallie_mae_test.rb index 5f7eade75be..40194a47164 100644 --- a/test/unit/gateways/sallie_mae_test.rb +++ b/test/unit/gateways/sallie_mae_test.rb @@ -8,46 +8,46 @@ def setup @credit_card = credit_card @amount = 100 - - @options = { + + @options = { :order_id => '1', :billing_address => address, :description => 'Store Purchase' } end - + def test_successful_purchase @gateway.expects(:ssl_post).returns(successful_purchase_response) - + assert response = @gateway.purchase(@amount, @credit_card, @options) assert_success response end def test_unsuccessful_request @gateway.expects(:ssl_post).returns(failed_purchase_response) - + assert response = @gateway.purchase(@amount, @credit_card, @options) assert_failure response end - + def test_non_test_account assert !@gateway.test? end - + def test_test_account - gateway = SallieMaeGateway.new(:login => "TEST0") - assert !@gateway.test? + gateway = SallieMaeGateway.new(:login => 'TEST0') + assert gateway.test? end private - + # Place raw successful response from gateway here def successful_purchase_response - "Status=Accepted" + 'Status=Accepted' end - + # Place raw failed response from gateway here def failed_purchase_response - "Status=Declined" + 'Status=Declined' end end diff --git a/test/unit/gateways/samurai_test.rb b/test/unit/gateways/samurai_test.rb deleted file mode 100644 index ab95c3fab5e..00000000000 --- a/test/unit/gateways/samurai_test.rb +++ /dev/null @@ -1,256 +0,0 @@ -require 'test_helper' - -class SamuraiTest < Test::Unit::TestCase - def setup - @gateway = SamuraiGateway.new( - :login => "MERCHANT KEY", - :password => "MERCHANT_PASSWORD", - :processor_token => "PROCESSOR_TOKEN" - ) - @successful_credit_card = credit_card - @successful_payment_method_token = "successful_token" - @amount = '1.00' - @amount_cents = 100 - @successful_authorization_id = "successful_authorization_id" - end - - - def test_successful_purchase_with_payment_method_token - Samurai::Processor.expects(:purchase). - with(@successful_payment_method_token, @amount, {}). - returns(successful_purchase_response) - - response = @gateway.purchase(@amount_cents, @successful_payment_method_token, {}) - assert_instance_of Response, response - assert_success response - assert_equal "reference_id", response.authorization - end - - def test_successful_authorize_with_payment_method_token - Samurai::Processor.expects(:authorize). - with(@successful_payment_method_token, @amount, {}). - returns(successful_authorize_response) - - response = @gateway.authorize(@amount_cents, @successful_payment_method_token) - assert_instance_of Response, response - assert_success response - assert_equal "reference_id", response.authorization - end - - - def test_successful_purchase_with_credit_card - @gateway.expects(:store). - with(@successful_credit_card, {}). - returns(successful_store_result) - - Samurai::Processor.expects(:purchase). - with(@successful_payment_method_token, @amount, {}). - returns(successful_purchase_response) - - response = @gateway.purchase(@amount_cents, @successful_credit_card) - assert_instance_of Response, response - assert_success response - assert_equal "reference_id", response.authorization - end - - def test_successful_authorize_with_credit_card - @gateway.expects(:store). - with(@successful_credit_card, {}). - returns(successful_store_result) - - Samurai::Processor.expects(:authorize). - with(@successful_payment_method_token, @amount, {}). - returns(successful_authorize_response) - - response = @gateway.authorize(@amount_cents, @successful_credit_card) - assert_instance_of Response, response - assert_success response - assert_equal "reference_id", response.authorization - end - - def test_successful_capture - Samurai::Transaction.expects(:find). - with(@successful_authorization_id). - returns(transaction = successful_authorize_response) - - transaction.expects(:capture). - with(@amount). - returns(successful_capture_response) - - response = @gateway.capture(@amount_cents, @successful_authorization_id) - assert_instance_of Response, response - assert_success response - assert_equal "reference_id", response.authorization - end - - - def test_successful_refund - Samurai::Transaction.expects(:find). - with(@successful_authorization_id). - returns(transaction = successful_authorize_response) - - transaction.expects(:credit). - with(@amount). - returns(successful_credit_response) - - response = @gateway.refund(@amount_cents, @successful_authorization_id) - assert_instance_of Response, response - assert_success response - assert_equal "reference_id", response.authorization - end - - def test_successful_void - Samurai::Transaction.expects(:find). - with(@successful_authorization_id). - returns(transaction = successful_authorize_response) - - transaction.expects(:void).returns(successful_void_response) - - response = @gateway.void(@successful_authorization_id) - assert_instance_of Response, response - assert_success response - assert_equal "reference_id", response.authorization - end - - def test_successful_store - card_to_store = { - :card_number => "4242424242424242", - :expiry_month => "09", - :expiry_year => (Time.now.year + 1).to_s, - :cvv => "123", - :first_name => "Longbob", - :last_name => "Longsen", - :address_1 => nil, - :address_2 => nil, - :city => nil, - :zip => nil, - :sandbox => true - } - Samurai::PaymentMethod.expects(:create). - with(card_to_store). - returns(successful_create_payment_method_response) - response = @gateway.store(@successful_credit_card) - assert_instance_of Response, response - assert_success response - end - - def test_successful_retain - card_to_store = valid_credit_card - payment_method = successful_create_payment_method_response - Samurai::PaymentMethod.expects(:create). - with(card_to_store). - returns(payment_method) - payment_method.expects(:retain) - response = @gateway.store(@successful_credit_card, :retain => true) - assert_instance_of Response, response - assert_success response - end - - def test_no_retain_on_failed_store - card_to_store = valid_credit_card - payment_method = successful_create_payment_method_response - payment_method.is_sensitive_data_valid = false - payment_method.payment_method_token = nil - Samurai::PaymentMethod.expects(:create). - with(card_to_store). - returns(payment_method) - payment_method.expects(:retain).never - response = @gateway.store(@successful_credit_card, :retain => true) - end - - def test_no_retain_options - card_to_store = valid_credit_card - payment_method = successful_create_payment_method_response - Samurai::PaymentMethod.expects(:create). - with(card_to_store). - returns(payment_method). - twice - payment_method.expects(:retain).never - response = @gateway.store(@successful_credit_card, :retain => false) - response = @gateway.store(@successful_credit_card) - end - - def test_passing_optional_processor_options - Samurai::Processor.expects(:purchase). - with(@successful_payment_method_token, @amount, {:billing_reference => 'billing_reference'}). - returns(successful_purchase_response) - - response = @gateway.purchase(@amount_cents, @successful_payment_method_token, {:billing_reference => 'billing_reference', :invalid_option => 'not_included'}) - assert_instance_of Response, response - assert_success response - assert_equal "reference_id", response.authorization - end - - def test_successful_avs_and_cvv - Samurai::Processor.expects(:purchase). - with(@successful_payment_method_token, @amount, {}). - returns(successful_purchase_response) - - response = @gateway.purchase(@amount_cents, @successful_payment_method_token) - assert_instance_of Response, response - assert_success response - assert_equal "Y", response.avs_result["code"] - assert_equal "M", response.cvv_result["code"] - end - - private - - def valid_credit_card - { - :card_number => "4242424242424242", - :expiry_month => "09", - :expiry_year => (Time.now.year + 1).to_s, - :cvv => "123", - :first_name => "Longbob", - :last_name => "Longsen", - :address_1 => nil, - :address_2 => nil, - :city => nil, - :zip => nil, - :sandbox => true - } - end - - def successful_purchase_response - successful_response("Purchase") - end - - def successful_capture_response - successful_response("Capture") - end - - def successful_credit_response - successful_response("Credit") - end - - def successful_authorize_response - successful_response("Authorize") - end - - def successful_void_response - successful_response("Void") - end - - def successful_store_result - Response.new(true, "message", {:payment_method_token => @successful_payment_method_token}) - end - - def successful_create_payment_method_response - Samurai::PaymentMethod.new(:is_sensitive_data_valid => true, :payment_method_token => @successful_payment_method_token) - end - - def successful_response(transaction_type, options = {}) - payment_method = Samurai::PaymentMethod.new(:payment_method_token => "payment_method_token") - processor_response = Samurai::ProcessorResponse.new(:success => true, - :messages => [{:context => 'processor.avs_result_code', :key => 'Y', :subclass => 'info'}, - {:context => 'processor.cvv_result_code', :key => 'M', :subclass => 'info'}]) - Samurai::Transaction.new({ - :reference_id => "reference_id", - :transaction_token => "transaction_token", - :payment_method => payment_method, - :processor_response => processor_response, - :transaction_type => transaction_type - }.merge(options)) - end - -end diff --git a/test/unit/gateways/secure_net_test.rb b/test/unit/gateways/secure_net_test.rb index 4221f602f19..01c9b55c70d 100644 --- a/test/unit/gateways/secure_net_test.rb +++ b/test/unit/gateways/secure_net_test.rb @@ -114,12 +114,13 @@ def test_failed_refund assert_equal 'CREDIT CANNOT BE COMPLETED ON AN UNSETTLED TRANSACTION', response.message end - def test_supported_countries - assert_equal ['US'], SecureNetGateway.supported_countries - end - - def test_supported_card_types - assert_equal [:visa, :master, :american_express, :discover], SecureNetGateway.supported_cardtypes + def test_order_id_is_truncated + order_id = "SecureNet doesn't like order_ids greater than 25 characters." + stub_comms do + @gateway.purchase(@amount, @credit_card, order_id: order_id) + end.check_request do |endpoint, data, headers| + assert_match(/ORDERID>SecureNet doesn't like or</, data) + end.respond_with(successful_purchase_response) end def test_failure_without_response_reason_text @@ -129,7 +130,7 @@ def test_failure_without_response_reason_text end def test_passes_optional_fields - options = { description: "Good Stuff", invoice_description: "Sweet Invoice", invoice_number: "48" } + options = { description: 'Good Stuff', invoice_description: 'Sweet Invoice', invoice_number: '48' } stub_comms do @gateway.purchase(@amount, @credit_card, options) end.check_request do |endpoint, data, headers| @@ -149,6 +150,42 @@ def test_only_passes_optional_fields_if_specified end.respond_with(successful_purchase_response) end + def test_passes_with_no_developer_id + stub_comms do + @gateway.purchase(@amount, @credit_card, {}) + end.check_request do |endpoint, data, headers| + assert_no_match(%r{DEVELOPERID}, data) + end.respond_with(successful_purchase_response) + end + + def test_passes_with_developer_id + stub_comms do + @gateway.purchase(@amount, @credit_card, developer_id: '1234') + end.check_request do |endpoint, data, headers| + assert_match(%r{DEVELOPERID}, data) + end.respond_with(successful_purchase_response) + end + + def test_passes_with_test_mode + stub_comms do + @gateway.purchase(@amount, @credit_card, test_mode: false) + end.check_request do |endpoint, data, headers| + assert_match(%r{<TEST>FALSE</TEST>}, data) + end.respond_with(successful_purchase_response) + end + + def test_passes_without_test_mode + stub_comms do + @gateway.purchase(@amount, @credit_card) + end.check_request do |endpoint, data, headers| + assert_match(%r{<TEST>TRUE</TEST>}, data) + end.respond_with(successful_purchase_response) + end + + def test_scrub + assert @gateway.supports_scrubbing? + assert_equal @gateway.scrub(pre_scrubbed), post_scrubbed + end private @@ -193,4 +230,49 @@ def failed_refund_response '<GATEWAYRESPONSE xmlns="http://gateway.securenet.com/API/Contracts" xmlns:i="http://www.w3.org/2001/XMLSchema-instance"><ASPREPONSE i:nil="true"/><TRANSACTIONRESPONSE><RESPONSE_CODE>3</RESPONSE_CODE><RESPONSE_REASON_CODE>01R3</RESPONSE_REASON_CODE><RESPONSE_REASON_TEXT>CREDIT CANNOT BE COMPLETED ON AN UNSETTLED TRANSACTION</RESPONSE_REASON_TEXT><RESPONSE_SUBCODE/><ADDITIONALAMOUNT>0</ADDITIONALAMOUNT><ADDITIONALDATA1 i:nil="true"/><ADDITIONALDATA2 i:nil="true"/><ADDITIONALDATA3 i:nil="true"/><ADDITIONALDATA4 i:nil="true"/><ADDITIONALDATA5 i:nil="true"/><AUTHCODE/><AUTHORIZEDAMOUNT>0</AUTHORIZEDAMOUNT><AVS_RESULT_CODE/><BANK_ACCOUNTNAME i:nil="true"/><BANK_ACCOUNTTYPE i:nil="true"/><BATCHID i:nil="true"/><CARDHOLDER_FIRSTNAME i:nil="true"/><CARDHOLDER_LASTNAME i:nil="true"/><CARDLEVEL_RESULTS i:nil="true"/><CARDTYPE i:nil="true"/><CARD_CODE_RESPONSE_CODE/><CASHBACK_AMOUNT>0</CASHBACK_AMOUNT><CAVV_RESPONSE_CODE/><CHECKNUM i:nil="true"/><CODE>0500</CODE><CUSTOMERID/><CUSTOMER_BILL><ADDRESS/><CITY/><COMPANY/><COUNTRY/><EMAIL/><EMAILRECEIPT>FALSE</EMAILRECEIPT><FIRSTNAME/><LASTNAME/><PHONE/><STATE/><ZIP/></CUSTOMER_BILL><EXPIRYDATE i:nil="true"/><GRATUITY>0</GRATUITY><INDUSTRYSPECIFICDATA i:nil="true"/><LAST4DIGITS i:nil="true"/><LEVEL2_VALID>FALSE</LEVEL2_VALID><LEVEL3_VALID>FALSE</LEVEL3_VALID><MARKETSPECIFICDATA i:nil="true"/><METHOD>CC</METHOD><NETWORKCODE i:nil="true"/><NETWORKID i:nil="true"/><ORDERID>1285171984419000</ORDERID><PAYMENTID i:nil="true"/><RETREFERENCENUM i:nil="true"/><SECURENETID>1002550</SECURENETID><SETTLEMENTAMOUNT>0</SETTLEMENTAMOUNT><SETTLEMENTDATETIME i:nil="true"/><SYSTEM_TRACENUM i:nil="true"/><TRACKTYPE i:nil="true"/><TRANSACTIONAMOUNT>1.00</TRANSACTIONAMOUNT><TRANSACTIONDATETIME i:nil="true"/><TRANSACTIONID>0</TRANSACTIONID></TRANSACTIONRESPONSE><VAULTACCOUNTRESPONSE i:nil="true"/><VAULTCUSTOMERRESPONSE i:nil="true"/></GATEWAYRESPONSE>' end + def pre_scrubbed + <<-EOS +opening connection to certify.securenet.com:443... +opened +starting SSL for certify.securenet.com:443... +SSL established +<- "POST /API/gateway.svc/webHttp/ProcessTransaction HTTP/1.1\r\nContent-Type: text/xml\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: certify.securenet.com\r\nContent-Length: 1044\r\n\r\n" +<- "<?xml version=\"1.0\" encoding=\"UTF-8\"?><TRANSACTION xmlns=\"http://gateway.securenet.com/API/Contracts\" xmlns:i=\"http://www.w3.org/2001/XMLSchema-instance\"><AMOUNT>1.00</AMOUNT><CARD><CARDCODE>123</CARDCODE><CARDNUMBER>4000100011112224</CARDNUMBER><EXPDATE>0919</EXPDATE></CARD><CODE>0100</CODE><CUSTOMER_BILL><ADDRESS>456 My Street</ADDRESS><CITY>Ottawa</CITY><COMPANY>Widgets Inc</COMPANY><COUNTRY>CA</COUNTRY><FIRSTNAME>Longbob</FIRSTNAME><LASTNAME>Longsen</LASTNAME><PHONE>(555)555-5555</PHONE><STATE>ON</STATE><ZIP>K1C2N6</ZIP></CUSTOMER_BILL><CUSTOMER_SHIP i:nil=\"true\"></CUSTOMER_SHIP><DCI>0</DCI><INSTALLMENT_SEQUENCENUM>1</INSTALLMENT_SEQUENCENUM><MERCHANT_KEY><GROUPID>0</GROUPID><SECUREKEY>BI8gL8HO1dKP</SECUREKEY><SECURENETID>7001218</SECURENETID></MERCHANT_KEY><METHOD>CC</METHOD><NOTE>Store Purchase</NOTE><ORDERID>1519921868962609</ORDERID><OVERRIDE_FROM>0</OVERRIDE_FROM><RETAIL_LANENUM>0</RETAIL_LANENUM><TEST>TRUE</TEST><TOTAL_INSTALLMENTCOUNT>0</TOTAL_INSTALLMENTCOUNT><TRANSACTION_SERVICE>0</TRANSACTION_SERVICE></TRANSACTION>" +-> "HTTP/1.1 200 OK\r\n" +-> "Content-Length: 2547\r\n" +-> "Content-Type: application/xml; charset=utf-8\r\n" +-> "X-Powered-By: ASP.NET\r\n" +-> "Date: Thu, 01 Mar 2018 16:31:01 GMT\r\n" +-> "Connection: close\r\n" +-> "Set-Cookie: TS01e56b0e=010bfb2c76b6671aabf6f176a4e5aefd8e7a6ce7f697d82dfcfd424edede4ae7d4dba7557a4a7a13a539cfc1c5c061e08d5040811a; Path=/\r\n" +-> "\r\n" +reading 2547 bytes... +-> "<GATEWAYRESPONSE xmlns=\"http://gateway.securenet.com/API/Contracts\" xmlns:i=\"http://www.w3.org/2001/XMLSchema-instance\"><ABRESPONSE i:nil=\"true\"/><TRANSACTIONRESPONSE><RESPONSE_CODE>1</RESPONSE_CODE><RESPONSE_REASON_CODE>0000</RESPONSE_REASON_CODE><RESPONSE_REASON_TEXT>Approved</RESPONSE_REASON_TEXT><RESPONSE_SUBCODE/><ADDITIONALAMOUNT>0</ADDITIONALAMOUNT><ADDITIONALDATA1/><ADDITIONALDATA2/><ADDITIONALDATA3/><ADDITIONALDATA4/><ADDITIONALDATA5/><AUTHCODE>JUJQLQ</AUTHCODE><AUTHORIZATIONMODE i:nil=\"true\"/><AUTHORIZEDAMOUNT>1.00</AUTHORIZEDAMOUNT><AVS_RESULT_CODE>Y</AVS_RESULT_CODE><BANK_ACCOUNTNAME/><BANK_ACCOUNTTYPE/><BATCHID>0</BATCHID><CALLID/><CARDENTRYMODE i:nil=\"true\"/><CARDHOLDERVERIFICATION i:nil=\"true\"/><CARDHOLDER_FIRSTNAME>Longbob</CARDHOLDER_FIRSTNAME><CARDHOLDER_LASTNAME>Longsen</CARDHOLDER_LASTNAME><CARDLEVEL_RESULTS/><CARDTYPE>VI</CARDTYPE><CARD_CODE_RESPONSE_CODE>M</CARD_CODE_RESPONSE_CODE><CASHBACK_AMOUNT>0</CASHBACK_AMOUNT><CATINDICATOR>0</CATINDICATOR><CAVV_RESPONSE_CODE/><CHECKNUM i:nil=\"true\"/><CODE>0100</CODE><CUSTOMERID/><CUSTOMER_BILL><ADDRESS>456 My Street</ADDRESS><CITY>Ottawa</CITY><COMPANY>Widgets Inc</COMPANY><COUNTRY>CA</COUNTRY><EMAIL/><EMAILRECEIPT>FALSE</EMAILRECEIPT><FIRSTNAME>Longbob</FIRSTNAME><LASTNAME>Longsen</LASTNAME><PHONE>(555)555-5555</PHONE><STATE>ON</STATE><ZIP>K1C2N6</ZIP></CUSTOMER_BILL><DYNAMICMCC i:nil=\"true\"/><EMVRESPONSE><ISSUERAUTHENTICATIONDATA i:nil=\"true\"/><ISSUERSCRIPTTEMPLATE1 i:nil=\"true\"/><ISSUERSCRIPTTEMPLATE2 i:nil=\"true\"/></EMVRESPONSE><EXPIRYDATE>0919</EXPIRYDATE><GRATUITY>0</GRATUITY><INDUSTRYSPECIFICDATA>P</INDUSTRYSPECIFICDATA><INVOICEDESCRIPTION i:nil=\"true\"/><LAST4DIGITS>2224</LAST4DIGITS><LEVEL2_VALID>FALSE</LEVEL2_VALID><LEVEL3_VALID>FALSE</LEVEL3_VALID><MARKETSPECIFICDATA/><METHOD>CC</METHOD><NETWORKCODE/><NETWORKID/><NOTES>Store Purchase</NOTES><ORDERID>1519921868962609</ORDERID><PAYMENTID/><RETREFERENCENUM/><RISK_CATEGORY i:nil=\"true\"/><RISK_REASON1 i:nil=\"true\"/><RISK_REASON2 i:nil=\"true\"/><RISK_REASON3 i:nil=\"true\"/><RISK_REASON4 i:nil=\"true\"/><RISK_REASON5 i:nil=\"true\"/><SECURENETID>7001218</SECURENETID><SETTLEMENTAMOUNT>1.00</SETTLEMENTAMOUNT><SETTLEMENTDATETIME>03012018113101</SETTLEMENTDATETIME><SOFTDESCRIPTOR/><SYSTEM_TRACENUM/><TRACKTYPE>0</TRACKTYPE><TRANSACTIONAMOUNT>1.00</TRANSACTIONAMOUNT><TRANSACTIONDATETIME>03012018113101</TRANSACTIONDATETIME><TRANSACTIONID>116186071</TRANSACTIONID><USERDEFINED i:nil=\"true\"/></TRANSACTIONRESPONSE><VAULTACCOUNTRESPONSE i:nil=\"true\"/><VAULTCUSTOMERRESPONSE i:nil=\"true\"/></GATEWAYRESPONSE>" +read 2547 bytes +Conn close + EOS + end + + def post_scrubbed + <<-EOS +opening connection to certify.securenet.com:443... +opened +starting SSL for certify.securenet.com:443... +SSL established +<- "POST /API/gateway.svc/webHttp/ProcessTransaction HTTP/1.1\r\nContent-Type: text/xml\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: certify.securenet.com\r\nContent-Length: 1044\r\n\r\n" +<- "<?xml version=\"1.0\" encoding=\"UTF-8\"?><TRANSACTION xmlns=\"http://gateway.securenet.com/API/Contracts\" xmlns:i=\"http://www.w3.org/2001/XMLSchema-instance\"><AMOUNT>1.00</AMOUNT><CARD><CARDCODE>[FILTERED]</CARDCODE><CARDNUMBER>[FILTERED]</CARDNUMBER><EXPDATE>0919</EXPDATE></CARD><CODE>0100</CODE><CUSTOMER_BILL><ADDRESS>456 My Street</ADDRESS><CITY>Ottawa</CITY><COMPANY>Widgets Inc</COMPANY><COUNTRY>CA</COUNTRY><FIRSTNAME>Longbob</FIRSTNAME><LASTNAME>Longsen</LASTNAME><PHONE>(555)555-5555</PHONE><STATE>ON</STATE><ZIP>K1C2N6</ZIP></CUSTOMER_BILL><CUSTOMER_SHIP i:nil=\"true\"></CUSTOMER_SHIP><DCI>0</DCI><INSTALLMENT_SEQUENCENUM>1</INSTALLMENT_SEQUENCENUM><MERCHANT_KEY><GROUPID>0</GROUPID><SECUREKEY>[FILTERED]</SECUREKEY><SECURENETID>7001218</SECURENETID></MERCHANT_KEY><METHOD>CC</METHOD><NOTE>Store Purchase</NOTE><ORDERID>1519921868962609</ORDERID><OVERRIDE_FROM>0</OVERRIDE_FROM><RETAIL_LANENUM>0</RETAIL_LANENUM><TEST>TRUE</TEST><TOTAL_INSTALLMENTCOUNT>0</TOTAL_INSTALLMENTCOUNT><TRANSACTION_SERVICE>0</TRANSACTION_SERVICE></TRANSACTION>" +-> "HTTP/1.1 200 OK\r\n" +-> "Content-Length: 2547\r\n" +-> "Content-Type: application/xml; charset=utf-8\r\n" +-> "X-Powered-By: ASP.NET\r\n" +-> "Date: Thu, 01 Mar 2018 16:31:01 GMT\r\n" +-> "Connection: close\r\n" +-> "Set-Cookie: TS01e56b0e=010bfb2c76b6671aabf6f176a4e5aefd8e7a6ce7f697d82dfcfd424edede4ae7d4dba7557a4a7a13a539cfc1c5c061e08d5040811a; Path=/\r\n" +-> "\r\n" +reading 2547 bytes... +-> "<GATEWAYRESPONSE xmlns=\"http://gateway.securenet.com/API/Contracts\" xmlns:i=\"http://www.w3.org/2001/XMLSchema-instance\"><ABRESPONSE i:nil=\"true\"/><TRANSACTIONRESPONSE><RESPONSE_CODE>1</RESPONSE_CODE><RESPONSE_REASON_CODE>0000</RESPONSE_REASON_CODE><RESPONSE_REASON_TEXT>Approved</RESPONSE_REASON_TEXT><RESPONSE_SUBCODE/><ADDITIONALAMOUNT>0</ADDITIONALAMOUNT><ADDITIONALDATA1/><ADDITIONALDATA2/><ADDITIONALDATA3/><ADDITIONALDATA4/><ADDITIONALDATA5/><AUTHCODE>JUJQLQ</AUTHCODE><AUTHORIZATIONMODE i:nil=\"true\"/><AUTHORIZEDAMOUNT>1.00</AUTHORIZEDAMOUNT><AVS_RESULT_CODE>Y</AVS_RESULT_CODE><BANK_ACCOUNTNAME/><BANK_ACCOUNTTYPE/><BATCHID>0</BATCHID><CALLID/><CARDENTRYMODE i:nil=\"true\"/><CARDHOLDERVERIFICATION i:nil=\"true\"/><CARDHOLDER_FIRSTNAME>Longbob</CARDHOLDER_FIRSTNAME><CARDHOLDER_LASTNAME>Longsen</CARDHOLDER_LASTNAME><CARDLEVEL_RESULTS/><CARDTYPE>VI</CARDTYPE><CARD_CODE_RESPONSE_CODE>M</CARD_CODE_RESPONSE_CODE><CASHBACK_AMOUNT>0</CASHBACK_AMOUNT><CATINDICATOR>0</CATINDICATOR><CAVV_RESPONSE_CODE/><CHECKNUM i:nil=\"true\"/><CODE>0100</CODE><CUSTOMERID/><CUSTOMER_BILL><ADDRESS>456 My Street</ADDRESS><CITY>Ottawa</CITY><COMPANY>Widgets Inc</COMPANY><COUNTRY>CA</COUNTRY><EMAIL/><EMAILRECEIPT>FALSE</EMAILRECEIPT><FIRSTNAME>Longbob</FIRSTNAME><LASTNAME>Longsen</LASTNAME><PHONE>(555)555-5555</PHONE><STATE>ON</STATE><ZIP>K1C2N6</ZIP></CUSTOMER_BILL><DYNAMICMCC i:nil=\"true\"/><EMVRESPONSE><ISSUERAUTHENTICATIONDATA i:nil=\"true\"/><ISSUERSCRIPTTEMPLATE1 i:nil=\"true\"/><ISSUERSCRIPTTEMPLATE2 i:nil=\"true\"/></EMVRESPONSE><EXPIRYDATE>0919</EXPIRYDATE><GRATUITY>0</GRATUITY><INDUSTRYSPECIFICDATA>P</INDUSTRYSPECIFICDATA><INVOICEDESCRIPTION i:nil=\"true\"/><LAST4DIGITS>2224</LAST4DIGITS><LEVEL2_VALID>FALSE</LEVEL2_VALID><LEVEL3_VALID>FALSE</LEVEL3_VALID><MARKETSPECIFICDATA/><METHOD>CC</METHOD><NETWORKCODE/><NETWORKID/><NOTES>Store Purchase</NOTES><ORDERID>1519921868962609</ORDERID><PAYMENTID/><RETREFERENCENUM/><RISK_CATEGORY i:nil=\"true\"/><RISK_REASON1 i:nil=\"true\"/><RISK_REASON2 i:nil=\"true\"/><RISK_REASON3 i:nil=\"true\"/><RISK_REASON4 i:nil=\"true\"/><RISK_REASON5 i:nil=\"true\"/><SECURENETID>7001218</SECURENETID><SETTLEMENTAMOUNT>1.00</SETTLEMENTAMOUNT><SETTLEMENTDATETIME>03012018113101</SETTLEMENTDATETIME><SOFTDESCRIPTOR/><SYSTEM_TRACENUM/><TRACKTYPE>0</TRACKTYPE><TRANSACTIONAMOUNT>1.00</TRANSACTIONAMOUNT><TRANSACTIONDATETIME>03012018113101</TRANSACTIONDATETIME><TRANSACTIONID>116186071</TRANSACTIONID><USERDEFINED i:nil=\"true\"/></TRANSACTIONRESPONSE><VAULTACCOUNTRESPONSE i:nil=\"true\"/><VAULTCUSTOMERRESPONSE i:nil=\"true\"/></GATEWAYRESPONSE>" +read 2547 bytes +Conn close + EOS + end end diff --git a/test/unit/gateways/secure_pay_au_test.rb b/test/unit/gateways/secure_pay_au_test.rb index 54b49c753ba..5d995aab6f8 100644 --- a/test/unit/gateways/secure_pay_au_test.rb +++ b/test/unit/gateways/secure_pay_au_test.rb @@ -1,6 +1,8 @@ require 'test_helper' class SecurePayAuTest < Test::Unit::TestCase + include CommStub + def setup @gateway = SecurePayAuGateway.new( :login => 'login', @@ -25,7 +27,6 @@ def test_supported_card_types assert_equal [:visa, :master, :american_express, :diners_club, :jcb], SecurePayAuGateway.supported_cardtypes end - def test_successful_purchase_with_live_data @gateway.expects(:ssl_post).returns(successful_live_purchase_response) @@ -48,6 +49,20 @@ def test_successful_purchase assert response.test? end + def test_localized_currency + stub_comms do + @gateway.purchase(100, @credit_card, @options.merge(:currency => 'CAD')) + end.check_request do |endpoint, data, headers| + assert_match %r{<amount>100<\/amount>}, data + end.respond_with(successful_purchase_response) + + stub_comms do + @gateway.purchase(100, @credit_card, @options.merge(:currency => 'JPY')) + end.check_request do |endpoint, data, headers| + assert_match %r{<amount>1<\/amount>}, data + end.respond_with(successful_purchase_response) + end + def test_failed_purchase @gateway.expects(:ssl_post).returns(failed_purchase_response) @@ -55,13 +70,13 @@ def test_failed_purchase assert_instance_of Response, response assert_failure response assert response.test? - assert_equal "CARD EXPIRED", response.message + assert_equal 'CARD EXPIRED', response.message end def test_purchase_with_stored_id_calls_commit_periodic @gateway.expects(:commit_periodic) - @gateway.purchase(@amount, "123", @options) + @gateway.purchase(@amount, '123', @options) end def test_purchase_with_creditcard_calls_commit_with_purchase @@ -84,59 +99,59 @@ def test_failed_authorization assert response = @gateway.authorize(@amount, @credit_card, @options) assert_failure response - assert_equal "Insufficient Funds", response.message + assert_equal 'Insufficient Funds', response.message end def test_successful_capture @gateway.expects(:ssl_post).returns(successful_capture_response) - assert response = @gateway.capture(@amount, "crazy*reference*thingy*100", {}) + assert response = @gateway.capture(@amount, 'crazy*reference*thingy*100', {}) assert_success response - assert_equal "Approved", response.message + assert_equal 'Approved', response.message end def test_failed_capture @gateway.expects(:ssl_post).returns(failed_capture_response) - assert response = @gateway.capture(@amount, "crazy*reference*thingy*100") + assert response = @gateway.capture(@amount, 'crazy*reference*thingy*100') assert_failure response - assert_equal "Preauth was done for smaller amount", response.message + assert_equal 'Preauth was done for smaller amount', response.message end def test_successful_refund @gateway.expects(:ssl_post).returns(successful_refund_response) - assert_success @gateway.refund(@amount, "crazy*reference*thingy*100", {}) + assert_success @gateway.refund(@amount, 'crazy*reference*thingy*100', {}) end def test_failed_refund @gateway.expects(:ssl_post).returns(failed_refund_response) - assert response = @gateway.refund(@amount, "crazy*reference*thingy*100") + assert response = @gateway.refund(@amount, 'crazy*reference*thingy*100') assert_failure response - assert_equal "Only $1.00 available for refund", response.message + assert_equal 'Only $1.00 available for refund', response.message end def test_deprecated_credit @gateway.expects(:ssl_post).returns(successful_refund_response) - assert_deprecation_warning(Gateway::CREDIT_DEPRECATION_MESSAGE, @gateway) do - assert_success @gateway.credit(@amount, "crazy*reference*thingy*100", {}) + assert_deprecation_warning(Gateway::CREDIT_DEPRECATION_MESSAGE) do + assert_success @gateway.credit(@amount, 'crazy*reference*thingy*100', {}) end end def test_successful_void @gateway.expects(:ssl_post).returns(successful_void_response) - assert response = @gateway.void("crazy*reference*thingy*100", {}) + assert response = @gateway.void('crazy*reference*thingy*100', {}) assert_success response end def test_failed_void @gateway.expects(:ssl_post).returns(failed_void_response) - assert response = @gateway.void("crazy*reference*thingy*100") + assert response = @gateway.void('crazy*reference*thingy*100') assert_failure response - assert_equal "Transaction was done for different amount", response.message + assert_equal 'Transaction was done for different amount', response.message end def test_failed_login @@ -145,7 +160,7 @@ def test_failed_login assert response = @gateway.purchase(@amount, @credit_card, @options) assert_instance_of Response, response assert_failure response - assert_equal "Invalid merchant ID", response.message + assert_equal 'Invalid merchant ID', response.message end def test_successful_store @@ -153,7 +168,7 @@ def test_successful_store assert response = @gateway.store(@credit_card, {:billing_id => 'test3', :amount => 123}) assert_instance_of Response, response - assert_equal "Successful", response.message + assert_equal 'Successful', response.message assert_equal 'test3', response.params['client_id'] end @@ -162,7 +177,7 @@ def test_successful_unstore assert response = @gateway.unstore('test2') assert_instance_of Response, response - assert_equal "Successful", response.message + assert_equal 'Successful', response.message assert_equal 'test2', response.params['client_id'] end @@ -171,14 +186,22 @@ def test_successful_triggered_payment assert response = @gateway.purchase(@amount, 'test3', @options) assert_instance_of Response, response - assert_equal "Approved", response.message + assert_equal 'Approved', response.message assert_equal 'test3', response.params['client_id'] end + def test_scrub + assert_equal @gateway.scrub(pre_scrub), post_scrub + end + + def test_supports_scrubbing? + assert @gateway.supports_scrubbing? + end + private def successful_store_response - <<-XML.gsub(/^\s{4}/,'') + <<-XML.gsub(/^\s{4}/, '') <?xml version="1.0" encoding="UTF-8"?> <SecurePayMessage> <MessageInfo> @@ -217,7 +240,7 @@ def successful_store_response end def successful_unstore_response - <<-XML.gsub(/^\s{4}/,'') + <<-XML.gsub(/^\s{4}/, '') <?xml version="1.0" encoding="UTF-8"?> <SecurePayMessage> <MessageInfo> @@ -249,7 +272,7 @@ def successful_unstore_response end def successful_triggered_payment_response - <<-XML.gsub(/^\s{4}/,'') + <<-XML.gsub(/^\s{4}/, '') <?xml version="1.0" encoding="UTF-8"?> <SecurePayMessage> <MessageInfo> @@ -295,7 +318,7 @@ def failed_login_response end def successful_purchase_response - <<-XML.gsub(/^\s{4}/,'') + <<-XML.gsub(/^\s{4}/, '') <?xml version="1.0" encoding="UTF-8"?> <SecurePayMessage> <MessageInfo> @@ -342,7 +365,7 @@ def successful_purchase_response end def failed_purchase_response - <<-XML.gsub(/^\s{4}/,'') + <<-XML.gsub(/^\s{4}/, '') <?xml version="1.0" encoding="UTF-8"?> <SecurePayMessage> <MessageInfo> @@ -389,7 +412,7 @@ def failed_purchase_response end def successful_live_purchase_response - <<-XML.gsub(/^\s{4}/,'') + <<-XML.gsub(/^\s{4}/, '') <?xml version="1.0" encoding="UTF-8"?> <SecurePayMessage> <MessageInfo> @@ -466,4 +489,50 @@ def successful_refund_response def failed_refund_response %(<?xml version="1.0" encoding="UTF-8" standalone="no"?><SecurePayMessage><MessageInfo><messageID>6bacab2b7ae1200d8099e0873e25bc</messageID><messageTimestamp>20102807071248484000+600</messageTimestamp><apiVersion>xml-4.2</apiVersion></MessageInfo><RequestType>Payment</RequestType><MerchantInfo><merchantID>CAX0001</merchantID></MerchantInfo><Status><statusCode>000</statusCode><statusDescription>Normal</statusDescription></Status><Payment><TxnList count="1"><Txn ID="1"><txnType>4</txnType><txnSource>23</txnSource><amount>101</amount><currency>AUD</currency><purchaseOrderNo>269061</purchaseOrderNo><approved>No</approved><responseCode>134</responseCode><responseText>Only $1.00 available for refund</responseText><thinlinkResponseCode>300</thinlinkResponseCode><thinlinkResponseText>000</thinlinkResponseText><thinlinkEventStatusCode>999</thinlinkEventStatusCode><thinlinkEventStatusText>Error - Transaction Already Fully Refunded/Only $x.xx Available for Refund</thinlinkEventStatusText><settlementDate/><txnID/><CreditCardInfo><pan>444433...111</pan><expiryDate>09/11</expiryDate><cardType>6</cardType><cardDescription>Visa</cardDescription></CreditCardInfo></Txn></TxnList></Payment></SecurePayMessage>) end + + def pre_scrub + <<-XML + opening connection to api.securepay.com.au:443... + opened + starting SSL for api.securepay.com.au:443... + SSL established + <- "POST /test/payment HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: api.securepay.com.au\r\nContent-Length: 710\r\n\r\n" + <- "<?xml version=\"1.0\" encoding=\"UTF-8\"?><SecurePayMessage><MessageInfo><messageID>0223a57aff3e71b22fc526a0b9f692</messageID><messageTimestamp>20160811005228628453+000</messageTimestamp><timeoutValue>60</timeoutValue><apiVersion>xml-4.2</apiVersion></MessageInfo><MerchantInfo><merchantID>ABC0030</merchantID><password>abc123</password></MerchantInfo><RequestType>Payment</RequestType><Payment><TxnList count=\"1\"><Txn ID=\"1\"><txnType>0</txnType><txnSource>23</txnSource><amount>100</amount><currency>AUD</currency><purchaseOrderNo>2</purchaseOrderNo><CreditCardInfo><cardNumber>4242424242424242</cardNumber><expiryDate>09/15</expiryDate><cvv>123</cvv></CreditCardInfo></Txn></TxnList></Payment></SecurePayMessage>" + -> "HTTP/1.1 200 OK\r\n" + -> "Content-Type: text/xml;charset=ISO-8859-1\r\n" + -> "Content-Length: 1148\r\n" + -> "Date: Tue, 08 Nov 2016 00:52:30 GMT\r\n" + -> "Connection: close\r\n" + -> "Server: Apache\r\n" + -> "Set-Cookie: ltm_api.securepay.com.au=1073719488.36895.0000; path=/\r\n" + -> "\r\n" + reading 1148 bytes... + -> "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?><SecurePayMessage><MessageInfo><messageID>0223a57aff3e71b22fc526a0b9f692</messageID><messageTimestamp>20160811115230823000+660</messageTimestamp><apiVersion>xml-4.2</apiVersion></MessageInfo><RequestType>Payment</RequestType><MerchantInfo><merchantID>ABC0030</merchantID></MerchantInfo><Status><statusCode>000</statusCode><statusDescription>Normal</statusDescription></Status><Payment><TxnList count=\"1\"><Txn ID=\"1\"><txnType>0</txnType><txnSource>23</txnSource><amount>100</amount><currency>AUD</currency><purchaseOrderNo>2</purchaseOrderNo><approved>Yes</approved><responseCode>00</responseCode><responseText>Approved</responseText><thinlinkResponseCode>100</thinlinkResponseCode><thinlinkResponseText>000</thinlinkResponseText><thinlinkEventStatusCode>000</thinlinkEventStatusCode><thinlinkEventStatusText>Normal</thinlinkEventStatusText><settlementDate>20161108</settlementDate><txnID>123822</txnID><CreditCardInfo><pan>424242...242</pan><expiryDate>09/15</expiryDate><cardType>6</cardType><cardDescription>Visa</cardDescription></CreditCardInfo></Txn></TxnList></Payment></SecurePayMessage>" + read 1148 bytes + Conn close + XML + end + + def post_scrub + <<-XML + opening connection to api.securepay.com.au:443... + opened + starting SSL for api.securepay.com.au:443... + SSL established + <- "POST /test/payment HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: api.securepay.com.au\r\nContent-Length: 710\r\n\r\n" + <- "<?xml version=\"1.0\" encoding=\"UTF-8\"?><SecurePayMessage><MessageInfo><messageID>0223a57aff3e71b22fc526a0b9f692</messageID><messageTimestamp>20160811005228628453+000</messageTimestamp><timeoutValue>60</timeoutValue><apiVersion>xml-4.2</apiVersion></MessageInfo><MerchantInfo><merchantID>[FILTERED]</merchantID><password>[FILTERED]</password></MerchantInfo><RequestType>Payment</RequestType><Payment><TxnList count=\"1\"><Txn ID=\"1\"><txnType>0</txnType><txnSource>23</txnSource><amount>100</amount><currency>AUD</currency><purchaseOrderNo>2</purchaseOrderNo><CreditCardInfo><cardNumber>[FILTERED]</cardNumber><expiryDate>09/15</expiryDate><cvv>[FILTERED]</cvv></CreditCardInfo></Txn></TxnList></Payment></SecurePayMessage>" + -> "HTTP/1.1 200 OK\r\n" + -> "Content-Type: text/xml;charset=ISO-8859-1\r\n" + -> "Content-Length: 1148\r\n" + -> "Date: Tue, 08 Nov 2016 00:52:30 GMT\r\n" + -> "Connection: close\r\n" + -> "Server: Apache\r\n" + -> "Set-Cookie: ltm_api.securepay.com.au=1073719488.36895.0000; path=/\r\n" + -> "\r\n" + reading 1148 bytes... + -> "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?><SecurePayMessage><MessageInfo><messageID>0223a57aff3e71b22fc526a0b9f692</messageID><messageTimestamp>20160811115230823000+660</messageTimestamp><apiVersion>xml-4.2</apiVersion></MessageInfo><RequestType>Payment</RequestType><MerchantInfo><merchantID>[FILTERED]</merchantID></MerchantInfo><Status><statusCode>000</statusCode><statusDescription>Normal</statusDescription></Status><Payment><TxnList count=\"1\"><Txn ID=\"1\"><txnType>0</txnType><txnSource>23</txnSource><amount>100</amount><currency>AUD</currency><purchaseOrderNo>2</purchaseOrderNo><approved>Yes</approved><responseCode>00</responseCode><responseText>Approved</responseText><thinlinkResponseCode>100</thinlinkResponseCode><thinlinkResponseText>000</thinlinkResponseText><thinlinkEventStatusCode>000</thinlinkEventStatusCode><thinlinkEventStatusText>Normal</thinlinkEventStatusText><settlementDate>20161108</settlementDate><txnID>123822</txnID><CreditCardInfo><pan>424242...242</pan><expiryDate>09/15</expiryDate><cardType>6</cardType><cardDescription>Visa</cardDescription></CreditCardInfo></Txn></TxnList></Payment></SecurePayMessage>" + read 1148 bytes + Conn close + XML + end end diff --git a/test/unit/gateways/secure_pay_tech_test.rb b/test/unit/gateways/secure_pay_tech_test.rb index 008f447cbc7..87293a7f817 100644 --- a/test/unit/gateways/secure_pay_tech_test.rb +++ b/test/unit/gateways/secure_pay_tech_test.rb @@ -13,31 +13,32 @@ def setup :billing_address => address } end - + def test_successful_purchase @gateway.expects(:ssl_post).returns(successful_purchase_response) - + assert response = @gateway.purchase(@amount, @credit_card, @options) assert_instance_of Response, response assert_success response assert response.test? assert_equal '4--120119220646821', response.authorization end - + def test_unsuccessful_purchase @gateway.expects(:ssl_post).returns(unsuccessful_purchase_response) - + assert response = @gateway.purchase(@amount, @credit_card, @options) assert_instance_of Response, response assert_failure response assert response.test? end - + private + def successful_purchase_response "1,4--120119220646821,000000014511,23284,014511,20080125\r\n" end - + def unsuccessful_purchase_response "4,4--120119180936527,000000014510,23283,014510,20080125\r\n" end diff --git a/test/unit/gateways/secure_pay_test.rb b/test/unit/gateways/secure_pay_test.rb index e5b97147f2c..0fdc3b08493 100644 --- a/test/unit/gateways/secure_pay_test.rb +++ b/test/unit/gateways/secure_pay_test.rb @@ -1,101 +1,86 @@ require 'test_helper' - + class SecurePayTest < Test::Unit::TestCase - def setup @gateway = SecurePayGateway.new( :login => 'X', :password => 'Y' ) - + @credit_card = credit_card - + @options = { :order_id => generate_unique_id, :description => 'Store purchase', :billing_address => address } - + @amount = 100 end - + def test_failed_purchase @gateway.stubs(:ssl_post).returns(failure_response) - + assert response = @gateway.purchase(@amount, @credit_card, @options) assert_failure response assert response.test? assert_equal 'This transaction has been declined', response.message assert_equal '3377475', response.authorization end - + def test_failed_production_purchase @gateway.stubs(:ssl_post).returns(failed_production_response) - + assert response = @gateway.purchase(@amount, @credit_card, @options) assert_failure response assert response.test? assert_equal 'This transaction has been declined', response.message assert_equal '7416654', response.authorization end - + def test_successful_purchase @gateway.stubs(:ssl_post).returns(successful_purchase_response) - + assert response = @gateway.purchase(@amount, @credit_card, @options) assert_success response assert response.test? assert_equal 'This transaction has been approved', response.message assert response.authorization end - - + def test_avs_result @gateway.expects(:ssl_post).returns(successful_purchase_response) - + response = @gateway.purchase(@amount, @credit_card) assert_equal 'X', response.avs_result['code'] end - + def test_cvv_result_not_supported @gateway.expects(:ssl_post).returns(successful_purchase_response) - + response = @gateway.purchase(@amount, @credit_card) assert_nil response.cvv_result['code'] end - - - def test_undefine_unsupported_methods - assert @gateway.respond_to?(:purchase) - - [ :authorize, :capture, :void, :credit ].each do |m| - assert !@gateway.respond_to?(m) - end + + def test_supported_countries + assert_equal %w(US CA GB AU), SecurePayGateway.supported_countries end - - def test_supported_countries_are_inherited - assert_equal AuthorizeNetGateway.supported_countries, SecurePayGateway.supported_countries - end - - def test_supported_card_types_are_inherited - assert_equal AuthorizeNetGateway.supported_cardtypes, SecurePayGateway.supported_cardtypes - end - + private - + def successful_purchase_response '1,,1,This transaction has been approved.,100721,X,3377575,f6af895031c07d88399ed9fdb48c8476,Store+purchase,0.01,,AUTH_CAPTURE,,Cody,Fauser,,100+Example+St.%Ottawa,ON,K2A5P7,Canada,,,,,,,,,,,,,,,,,,,' end - + def failure_response '2,,2,This transaction has been declined.,NOT APPROVED,U,3377475,55adbbaed13aa7e2526846d672fdb594,Store+purchase,1.00,,AUTH_CAPTURE,,Longbob,Longsen,,1234+Test+St.,Ottawa,ON,K1N5P8,Canada,,,,,,,,,,,,,,,,,,,' end - + def failed_capture_response '3,,6,The credit card number is invalid.,,,,,,0.01,,PRIOR_AUTH_CAPTURE,,,,,,,,,,,,,,,,,,,,,,,,,,,,' end - + def failed_production_response '2,,2,This transaction has been declined.,NOT APPROVED,U,7416654,%231014.11,,7.95,,AUTH_CAPTURE,321hunter%40comcast.net,David,Hunter,,260+Windsor+Ave,Haddonfield,NJ,08033,US,856+795+7941,,321hunter%40comcast.net,,,,260+Windsor+Ave,Haddonfield,Haddonfield,NJ,08033,,,,,,,,' end -end \ No newline at end of file +end diff --git a/test/unit/gateways/securion_pay_test.rb b/test/unit/gateways/securion_pay_test.rb new file mode 100644 index 00000000000..06fbe0ff622 --- /dev/null +++ b/test/unit/gateways/securion_pay_test.rb @@ -0,0 +1,786 @@ +require 'test_helper' + +class SecurionPayTest < Test::Unit::TestCase + include CommStub + + def setup + @gateway = SecurionPayGateway.new( + secret_key: 'pr_test_SyMyCpIJosFIAESEsZUd3TgN' + ) + + @credit_card = credit_card + @declined_card = credit_card('4916018475814056') + @new_credit_card = credit_card('4012888888881881') + @amount = 2000 + @refund_amount = 300 + + @options = { + billing_address: address, + description: 'Store Purchase' + } + end + + def test_successful_store + @gateway.expects(:ssl_post).returns(successful_authorize_response) + @gateway.expects(:ssl_post).returns(successful_new_customer_response) + @gateway.expects(:ssl_post).returns(successful_void_response) + + response = @gateway.store(@credit_card, @options) + assert_success response + assert_match %r(^cust_\w+$), response.authorization + assert_equal 'customer', response.params['objectType'] + assert_match %r(^card_\w+$), response.params['cards'][0]['id'] + assert_equal 'card', response.params['cards'][0]['objectType'] + + @gateway.expects(:ssl_post).returns(successful_authorize_response) + @gateway.expects(:ssl_post).returns(successful_void_response) + + @options[:customer_id] = response.authorization + response = @gateway.store(@new_credit_card, @options) + assert_success response + assert_match %r(^card_\w+$), response.params['card']['id'] + assert_equal @options[:customer_id], response.params['card']['customerId'] + + @gateway.expects(:ssl_request).returns(successful_customer_update_response) + + response = @gateway.customer(@options) + assert_success response + assert_equal @options[:customer_id], response.params['id'] + assert_equal '401288', response.params['cards'][0]['first6'] + assert_equal '1881', response.params['cards'][0]['last4'] + assert_equal '424242', response.params['cards'][1]['first6'] + assert_equal '4242', response.params['cards'][1]['last4'] + end + + def test_successful_purchase + @gateway.expects(:ssl_request).returns(successful_purchase_response) + + response = @gateway.purchase(@amount, @credit_card, @options) + assert_instance_of Response, response + assert_success response + + assert_equal 'char_J10t4hOZCHGO2izfJPKLM9W5', response.authorization + assert response.test? + end + + def test_successful_purchase_with_token + response = stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, 'tok_xxx') + end.check_request do |method, endpoint, data, headers| + assert_match(/card=tok_xxx/, data) + refute_match(/card\[number\]/, data) + end.respond_with(successful_purchase_response) + + assert response + assert_instance_of Response, response + assert_success response + end + + def test_invalid_raw_response + @gateway.expects(:ssl_request).returns(invalid_json_response) + + response = @gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_match(/^Invalid response received from the SecurionPay API/, response.message) + end + + def test_client_data_submitted_with_purchase + stub_comms(@gateway, :ssl_request) do + updated_options = @options.merge({ description: 'test charge', ip: '127.127.127.127', user_agent: 'browser XXX', referrer: 'http://www.foobar.com', email: 'foo@bar.com' }) + @gateway.purchase(@amount, @credit_card, updated_options) + end.check_request do |method, endpoint, data, headers| + assert_match(/description=test\+charge/, data) + assert_match(/ip=127\.127\.127\.127/, data) + assert_match(/user_agent=browser\+XXX/, data) + assert_match(/referrer=http\%3A\%2F\%2Fwww\.foobar\.com/, data) + assert_match(/metadata\[email\]=foo\%40bar\.com/, data) + end.respond_with(successful_purchase_response) + end + + def test_client_data_submitted_with_purchase_without_email_or_order + stub_comms(@gateway, :ssl_request) do + updated_options = @options.merge({ description: 'test charge', ip: '127.127.127.127', user_agent: 'browser XXX', referrer: 'http://www.foobar.com' }) + @gateway.purchase(@amount, @credit_card, updated_options) + end.check_request do |method, endpoint, data, headers| + assert_match(/description=test\+charge/, data) + assert_match(/ip=127\.127\.127\.127/, data) + assert_match(/user_agent=browser\+XXX/, data) + assert_match(/referrer=http\%3A\%2F\%2Fwww\.foobar\.com/, data) + refute data.include?('metadata') + end.respond_with(successful_purchase_response) + end + + def test_successful_authorization + @gateway.expects(:ssl_request).returns(successful_authorize_response) + + response = @gateway.authorize(@amount, @credit_card, @options) + assert_instance_of Response, response + assert_success response + + assert_equal 'char_hWKC9C5wkXuiTLsxdHzncea3', response.authorization + assert response.test? + end + + def test_add_address + post = { card: { } } + @gateway.send(:add_address, post, @options) + assert_equal @options[:billing_address][:zip], post[:card][:addressZip] + assert_equal @options[:billing_address][:state], post[:card][:addressState] + assert_equal @options[:billing_address][:address1], post[:card][:addressLine1] + assert_equal @options[:billing_address][:address2], post[:card][:addressLine2] + assert_equal @options[:billing_address][:country], post[:card][:addressCountry] + assert_equal @options[:billing_address][:city], post[:card][:addressCity] + end + + def test_ensure_does_not_respond_to_credit + assert !@gateway.respond_to?(:credit) + end + + def test_address_is_included_with_card_data + stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, @credit_card, @options) + end.check_request do |method, endpoint, data, headers| + assert data =~ /card\[addressLine1\]/ + end.respond_with(successful_purchase_response) + end + + def test_failed_purchase + @gateway.expects(:ssl_request).returns(failed_purchase_response) + + response = @gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_equal Gateway::STANDARD_ERROR_CODE[:invalid_number], response.error_code + assert_nil response.authorization + end + + def test_successful_authorize + @gateway.expects(:ssl_request).returns(successful_authorize_response) + + response = @gateway.authorize(@amount, @credit_card, @options) + assert_instance_of Response, response + assert_success response + + assert_equal 'char_hWKC9C5wkXuiTLsxdHzncea3', response.authorization + assert response.test? + end + + def test_failed_authorize + @gateway.expects(:ssl_request).returns(failed_authorize_response) + + response = @gateway.authorize(@amount, @credit_card, @options) + assert_failure response + assert_equal Gateway::STANDARD_ERROR_CODE[:card_declined], response.error_code + assert_nil response.authorization + assert response.test? + end + + def test_successful_capture + @gateway.expects(:ssl_request).returns(successful_capture_response) + + response = @gateway.capture(@amount, 'char_CqH9rftszMnaMYBrgtVI49LM', @options) + assert_instance_of Response, response + assert_success response + assert response.test? + end + + def test_failed_capture + @gateway.expects(:ssl_request).returns(failed_capture_response) + + response = @gateway.capture(@amount, 'invalid_authorization_token', @options) + assert_failure response + assert_match(/^Requested Charge does not exist/, response.message) + assert_nil response.authorization + assert response.test? + end + + def test_successful_full_refund + @gateway.expects(:ssl_request).returns(successful_full_refund_response) + + response = @gateway.refund(@amount, 'char_DQca5ZjbewP2Oe0lIsNe4EXP', @options) + assert_instance_of Response, response + assert_success response + assert response.params['refunded'] + assert_equal 0, response.params['amount'] + assert_equal 1, response.params['refunds'].size + assert_equal @amount, response.params['refunds'].map { |r| r['amount'] }.sum + assert_equal 'char_DQca5ZjbewP2Oe0lIsNe4EXP', response.authorization + assert response.test? + end + + def test_successful_partially_refund + @gateway.expects(:ssl_request).returns(successful_partially_refund_response) + + response = @gateway.refund(@refund_amount, 'char_oVnJ1j6fZqOvnopBBvlnpEuX', @options) + assert_instance_of Response, response + assert_success response + assert response.params['refunded'] + assert_equal @amount - @refund_amount, response.params['amount'] + assert_equal @refund_amount, response.params['refunds'].map { |r| r['amount'] }.sum + assert_equal 'char_oVnJ1j6fZqOvnopBBvlnpEuX', response.authorization + assert response.test? + end + + def test_failed_refund + @gateway.expects(:ssl_request).returns(failed_refund_response) + + response = @gateway.refund(@refund_amount + 1, 'char_oVnJ1j6fZqOvnopBBvlnpEuX', @options) + assert_failure response + assert_match(/^Wrong Refund data/, response.message) + assert_nil response.authorization + assert response.test? + end + + def test_failed_authorize_refund + @gateway.expects(:ssl_request).returns(failed_authorize_refund_response) + + response = @gateway.refund(@refund_amount, 'invalid_authorization_token', @options) + assert_failure response + assert_match(/^Requested Charge does not exist/, response.message) + assert_nil response.authorization + assert response.test? + end + + def test_successful_void + @gateway.expects(:ssl_request).returns(successful_void_response) + + response = @gateway.void('char_yDS2wtcTFZSWOWGaANjpchVb') + assert_success response + end + + def test_failed_authorization_void + @gateway.expects(:ssl_request).returns(failed_authorization_void_response) + + response = @gateway.void('invalid_authorization_token', @options) + assert_failure response + assert_equal 'Requested Charge does not exist', response.message + assert_nil response.authorization + end + + def test_successful_verify + response = stub_comms(@gateway, :ssl_request) do + @gateway.verify(@credit_card, @options) + end.respond_with(successful_authorize_response, successful_void_response) + assert_success response + end + + def test_successful_verify_with_failed_void + response = stub_comms(@gateway, :ssl_request) do + @gateway.verify(@credit_card, @options) + end.respond_with(successful_authorize_response, failed_void_response) + assert_success response + assert_equal 'Transaction approved', response.message + end + + def test_failed_verify + response = stub_comms(@gateway, :ssl_request) do + @gateway.verify(@declined_card, @options) + end.respond_with(failed_authorize_response, successful_void_response) + assert_failure response + assert_equal 'The card was declined for other reason.', response.message + end + + def test_scrub + assert @gateway.supports_scrubbing? + assert_equal @gateway.scrub(pre_scrubbed), post_scrubbed + end + + def test_unsuccessful_request + @gateway.expects(:ssl_request).returns(failed_purchase_response) + + response = @gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_nil response.authorization + end + + def test_declined_request + @gateway.expects(:ssl_request).returns(failed_authorize_response) + + response = @gateway.purchase(@amount, @credit_card, @options) + assert_failure response + + assert_equal Gateway::STANDARD_ERROR_CODE[:card_declined], response.error_code + assert_equal 'char_mApucpvVbCJgo7x09Je4n9gC', response.params['error']['chargeId'] + end + + private + + def pre_scrubbed + <<-PRE_SCRUBBED + opening connection to api.securionpay.com:443... + opened + starting SSL for api.securionpay.com:443... + SSL established + <- "POST /charges HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAuthorization: Basic cHJfdGVzdF9xWk40VlZJS0N5U2ZDZVhDQm9ITzlEQmU6\r\nUser-Agent: SecurionPay/v1 ActiveMerchantBindings/1.47.0\r\nAccept-Encoding: gzip;q=0,deflate;q=0.6\r\nAccept: */*\r\nConnection: close\r\nHost: api.securionpay.com\r\nContent-Length: 214\r\n\r\n" + <- "amount=2000&currency=usd&card[number]=4242424242424242&card[expMonth]=9&card[expYear]=2016&card[cvc]=123&card[cardholderName]=Longbob+Longsen&description=ActiveMerchant+test+charge&metadata[email]=foo%40example.com" + -> "HTTP/1.1 200 OK\r\n" + -> "Server: cloudflare-nginx\r\n" + -> "Date: Fri, 12 Jun 2015 21:36:39 GMT\r\n" + -> "Content-Type: application/json;charset=UTF-8\r\n" + -> "Transfer-Encoding: chunked\r\n" + -> "Connection: close\r\n" + -> "Set-Cookie: __cfduid=d5da73266c61acce6307176d45e2672b41434144998; expires=Sat, 11-Jun-16 21:36:38 GMT; path=/; domain=.securionpay.com; HttpOnly\r\n" + -> "CF-RAY: 1f58b1414ca00af6-WAW\r\n" + -> "\r\n" + -> "1f4\r\n" + reading 500 bytes... + -> "{\"id\":\"char_TOnen0ZcDMYzECNS4fItK9P4\",\"created\":1434144998,\"objectType\":\"charge\",\"amount\":2000,\"currency\":\"USD\",\"description\":\"ActiveMerchant test charge\",\"card\":{\"id\":\"card_yJ4JNcp6P4sG8UrtZ62VWb5e\",\"created\":1434144998,\"objectType\":\"card\",\"first6\":\"424242\",\"last4\":\"4242\",\"fingerprint\":\"ecAKhFD1dmDAMKD9\",\"expMonth\":\"9\",\"expYear\":\"2016\",\"cardholderName\":\"Longbob Longsen\",\"brand\":\"Visa\",\"type\":\"Credit Card\"},\"captured\":true,\"refunded\":false,\"disputed\":false,\"metadata\":{\"email\":\"foo@example.com\"}}" + read 500 bytes + reading 2 bytes... + -> "\r\n" + read 2 bytes + -> "0\r\n" + -> "\r\n" + Conn close + PRE_SCRUBBED + end + + def post_scrubbed + <<-POST_SCRUBBED + opening connection to api.securionpay.com:443... + opened + starting SSL for api.securionpay.com:443... + SSL established + <- "POST /charges HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAuthorization: Basic [FILTERED]\r\nUser-Agent: SecurionPay/v1 ActiveMerchantBindings/1.47.0\r\nAccept-Encoding: gzip;q=0,deflate;q=0.6\r\nAccept: */*\r\nConnection: close\r\nHost: api.securionpay.com\r\nContent-Length: 214\r\n\r\n" + <- "amount=2000&currency=usd&card[number]=[FILTERED]&card[expMonth]=9&card[expYear]=2016&card[cvc]=[FILTERED]&card[cardholderName]=Longbob+Longsen&description=ActiveMerchant+test+charge&metadata[email]=foo%40example.com" + -> "HTTP/1.1 200 OK\r\n" + -> "Server: cloudflare-nginx\r\n" + -> "Date: Fri, 12 Jun 2015 21:36:39 GMT\r\n" + -> "Content-Type: application/json;charset=UTF-8\r\n" + -> "Transfer-Encoding: chunked\r\n" + -> "Connection: close\r\n" + -> "Set-Cookie: __cfduid=d5da73266c61acce6307176d45e2672b41434144998; expires=Sat, 11-Jun-16 21:36:38 GMT; path=/; domain=.securionpay.com; HttpOnly\r\n" + -> "CF-RAY: 1f58b1414ca00af6-WAW\r\n" + -> "\r\n" + -> "1f4\r\n" + reading 500 bytes... + -> "{\"id\":\"char_TOnen0ZcDMYzECNS4fItK9P4\",\"created\":1434144998,\"objectType\":\"charge\",\"amount\":2000,\"currency\":\"USD\",\"description\":\"ActiveMerchant test charge\",\"card\":{\"id\":\"card_yJ4JNcp6P4sG8UrtZ62VWb5e\",\"created\":1434144998,\"objectType\":\"card\",\"first6\":\"424242\",\"last4\":\"4242\",\"fingerprint\":\"ecAKhFD1dmDAMKD9\",\"expMonth\":\"9\",\"expYear\":\"2016\",\"cardholderName\":\"Longbob Longsen\",\"brand\":\"Visa\",\"type\":\"Credit Card\"},\"captured\":true,\"refunded\":false,\"disputed\":false,\"metadata\":{\"email\":\"foo@example.com\"}}" + read 500 bytes + reading 2 bytes... + -> "\r\n" + read 2 bytes + -> "0\r\n" + -> "\r\n" + Conn close + POST_SCRUBBED + end + + def successful_purchase_response + <<-RESPONSE + { + "id" : "char_J10t4hOZCHGO2izfJPKLM9W5", + "created" : 1426583959, + "objectType" : "charge", + "amount" : 2000, + "currency" : "USD", + "card" : { + "id" : "card_Pd9TxYGGFYKmqJlugewRotP1", + "created" : 1426583959, + "objectType" : "card", + "first6" : "401200", + "last4" : "0007", + "expMonth" : "11", + "expYear" : "2022", + "brand" : "Visa", + "type" : "Credit Card" + }, + "captured" : true, + "refunded" : false, + "disputed" : false + } + RESPONSE + end + + def invalid_json_response + <<-RESPONSE + { + foo : bar + } + RESPONSE + end + + def failed_purchase_response + <<-RESPONSE + { + "error" : { + "type" : "card_error", + "code" : "invalid_number", + "message" : "The card number is not a valid credit card number." + } + } + RESPONSE + end + + def successful_authorize_response + <<-RESPONSE + { + "id" : "char_hWKC9C5wkXuiTLsxdHzncea3", + "created" : 1426624786, + "objectType" : "charge", + "amount" : 2000, + "currency" : "USD", + "card" : { + "id" : "card_vLk7wLtaWAiqsnnYL9xlcf6W", + "created" : 1426624786, + "objectType" : "card", + "first6" : "401200", + "last4" : "0007", + "expMonth" : "11", + "expYear" : "2022", + "brand" : "Visa", + "type" : "Credit Card", + "customerId" : "cust_OWTybrAX3JP4Bbv1xnkpnHEj" + }, + "captured" : false, + "refunded" : false, + "disputed" : false + } + RESPONSE + end + + def failed_authorize_response + <<-RESPONSE + { + "error" : { + "type" : "card_error", + "code" : "card_declined", + "message" : "The card was declined for other reason.", + "chargeId" : "char_mApucpvVbCJgo7x09Je4n9gC" + } + } + RESPONSE + end + + def successful_capture_response + <<-RESPONSE + { + "id" : "char_CqH9rftszMnaMYBrgtVI49LM", + "created" : 1426626399, + "objectType" : "charge", + "amount" : 2000, + "currency" : "USD", + "description" : "ActiveMerchant test charge", + "card" : { + "id" : "card_UpPL1PYsy28Mn8QzWe6gR90x", + "created" : 1426626399, + "objectType" : "card", + "first6" : "424242", + "last4" : "4242", + "expMonth" : "9", + "expYear" : "2016", + "brand" : "Visa", + "type" : "Credit Card" + }, + "captured" : true, + "refunded" : false, + "disputed" : false, + "metadata" : { + "email" : "foo@example.com" + } + } + RESPONSE + end + + def failed_capture_response + <<-RESPONSE + { + "error" : { + "type" : "invalid_request", + "message" : "Requested Charge does not exist" + } + } + RESPONSE + end + + def successful_full_refund_response + <<-RESPONSE + { + "id" : "char_DQca5ZjbewP2Oe0lIsNe4EXP", + "created" : 1426673886, + "objectType" : "charge", + "amount" : 0, + "currency" : "USD", + "description" : "ActiveMerchant test charge", + "card" : { + "id" : "card_d62RqD9jGvcyFSmsGaDEpTic", + "created" : 1426673886, + "objectType" : "card", + "first6" : "424242", + "last4" : "4242", + "expMonth" : "9", + "expYear" : "2016", + "brand" : "Visa", + "type" : "Credit Card" + }, + "captured" : true, + "refunded" : true, + "refunds" : [ { + "created" : 1426675529990, + "amount" : 2000, + "currency" : "USD" + } ], + "disputed" : false, + "metadata" : { + "email" : "foo@example.com" + } + } + RESPONSE + end + + def successful_partially_refund_response + <<-RESPONSE + { + "id" : "char_oVnJ1j6fZqOvnopBBvlnpEuX", + "created" : 1426673886, + "objectType" : "charge", + "amount" : 1700, + "currency" : "USD", + "description" : "ActiveMerchant test charge", + "card" : { + "id" : "card_psNTJq6c32PcZNGuztJ6mJGu", + "created" : 1426673886, + "objectType" : "card", + "first6" : "424242", + "last4" : "4242", + "expMonth" : "9", + "expYear" : "2016", + "brand" : "Visa", + "type" : "Credit Card" + }, + "captured" : true, + "refunded" : true, + "refunds" : [ { + "created" : 1426680168474, + "amount" : 300, + "currency" : "USD" + } ], + "disputed" : false, + "metadata" : { + "email" : "foo@example.com" + } + } + RESPONSE + end + + def failed_refund_response + <<-RESPONSE + { + "error" : { + "type" : "invalid_request", + "message" : "Wrong Refund data" + } + } + RESPONSE + end + + def failed_authorize_refund_response + <<-RESPONSE + { + "error" : { + "type" : "invalid_request", + "message" : "Requested Charge does not exist" + } + } + RESPONSE + end + + def successful_void_response + <<-RESPONSE + { + "id" : "char_yDS2wtcTFZSWOWGaANjpchVb", + "created" : 1426682831, + "objectType" : "charge", + "amount" : 0, + "currency" : "USD", + "description" : "ActiveMerchant test charge", + "card" : { + "id" : "card_0mMgPMnlw2vaiwez5WkDarqv", + "created" : 1426682831, + "objectType" : "card", + "first6" : "424242", + "last4" : "4242", + "expMonth" : "9", + "expYear" : "2016", + "brand" : "Visa", + "type" : "Credit Card", + "customerId" : "cust_OWTybrAX3JP4Bbv1xnkpnHEj" + }, + "captured" : true, + "refunded" : true, + "refunds" : [ { + "created" : 1426683310391, + "amount" : 2000, + "currency" : "USD" + } ], + "disputed" : false, + "metadata" : { + "email" : "foo@example.com" + } + } + RESPONSE + end + + def failed_authorization_void_response + <<-RESPONSE + { + "error" : { + "type" : "invalid_request", + "message" : "Requested Charge does not exist" + } + } + RESPONSE + end + + def failed_void_response + <<-RESPONSE + { + "error" : { + "type" : "card_error", + "code" : "card_declined", + "message" : "The card was declined for other reason.", + "chargeId" : "char_2OLS47AyzcHSy5ssRx8wdBEq" + } + } + RESPONSE + end + + def successful_new_customer_response + <<-RESPONSE + { + "id" : "cust_OWTybrAX3JP4Bbv1xnkpnHEj", + "created" : 1426706773, + "objectType" : "customer", + "email" : "r@r.pl", + "defaultCardId" : "card_e2M5PpMhzGwpdSs6Nopu54Ny", + "cards" : [ { + "id" : "card_e2M5PpMhzGwpdSs6Nopu54Ny", + "created" : 1426706773, + "objectType" : "card", + "first6" : "401288", + "last4" : "1881", + "expMonth" : "11", + "expYear" : "2022", + "cardholderName" : "Tobias Luetke", + "customerId" : "cust_OWTybrAX3JP4Bbv1xnkpnHEj", + "brand" : "Visa", + "type" : "Credit Card" + } ], + "metadata" : { + "chargeId" : "char_hWKC9C5wkXuiTLsxdHzncea3" + } + } + RESPONSE + end + + def successful_new_card_response + <<-RESPONSE + { + "id" : "cust_QwQdf2Y1fjCFKrchTtSmwpUM", + "created" : 1426756430, + "objectType" : "customer", + "email" : "r@r.pl", + "defaultCardId" : "card_Yu7rzoGnhD1YWeo0UmtQVPEL", + "cards" : [ { + "id" : "card_gF90YA1KO56BSjkyQmCGfjO5", + "created" : 1426756429, + "objectType" : "card", + "first6" : "401288", + "last4" : "1881", + "expMonth" : "11", + "expYear" : "2022", + "cardholderName" : "Tobias Luetke", + "customerId" : "cust_QwQdf2Y1fjCFKrchTtSmwpUM", + "brand" : "Visa", + "type" : "Credit Card" + }, { + "id" : "card_Yu7rzoGnhD1YWeo0UmtQVPEL", + "created" : 1426759288, + "objectType" : "card", + "first6" : "401200", + "last4" : "0007", + "expMonth" : "11", + "expYear" : "2022", + "cardholderName" : "Tobias Luetke", + "customerId" : "cust_QwQdf2Y1fjCFKrchTtSmwpUM", + "brand" : "Visa", + "type" : "Credit Card" + } ] + } + RESPONSE + end + + def successful_customer_update_response + <<-RESPONSE + { + "id" : "cust_OWTybrAX3JP4Bbv1xnkpnHEj", + "created" : 1426756430, + "objectType" : "customer", + "email" : "test@email.pl", + "description" : "Test Description", + "defaultCardId" : "card_9k3THkOkmz8OQpJejlaZGizB", + "cards" : [ { + "id" : "card_9k3THkOkmz8OQpJejlaZGizB", + "created" : 1426759508, + "objectType" : "card", + "first6" : "401288", + "last4" : "1881", + "expMonth" : "11", + "expYear" : "2022", + "cardholderName" : "Tobias Luetke", + "customerId" : "cust_OWTybrAX3JP4Bbv1xnkpnHEj", + "brand" : "Visa", + "type" : "Credit Card" + }, { + "id" : "card_gF90YA1KO56BSjkyQmCGfjO5", + "created" : 1426756429, + "objectType" : "card", + "first6" : "424242", + "last4" : "4242", + "expMonth" : "11", + "expYear" : "2022", + "cardholderName" : "Tobias Luetke", + "customerId" : "cust_OWTybrAX3JP4Bbv1xnkpnHEj", + "brand" : "Visa", + "type" : "Credit Card" + } ] + } + RESPONSE + end + + def successful_change_default_card_response + <<-RESPONSE + { + "id" : "cust_QwQdf2Y1fjCFKrchTtSmwpUM", + "created" : 1426756430, + "objectType" : "customer", + "email" : "test@email.pl", + "description" : "Test Description", + "defaultCardId" : "card_gF90YA1KO56BSjkyQmCGfjO5", + "cards" : [ { + "id" : "card_9k3THkOkmz8OQpJejlaZGizB", + "created" : 1426759508, + "objectType" : "card", + "first6" : "401200", + "last4" : "0007", + "expMonth" : "11", + "expYear" : "2022", + "cardholderName" : "Tobias Luetke", + "customerId" : "cust_QwQdf2Y1fjCFKrchTtSmwpUM", + "brand" : "Visa", + "type" : "Credit Card" + }, { + "id" : "card_gF90YA1KO56BSjkyQmCGfjO5", + "created" : 1426756429, + "objectType" : "card", + "first6" : "401288", + "last4" : "1881", + "expMonth" : "11", + "expYear" : "2022", + "cardholderName" : "Tobias Luetke", + "customerId" : "cust_QwQdf2Y1fjCFKrchTtSmwpUM", + "brand" : "Visa", + "type" : "Credit Card" + } ] + } + RESPONSE + end +end diff --git a/test/unit/gateways/skip_jack_test.rb b/test/unit/gateways/skip_jack_test.rb index 463f1b5d09b..6df67c6a94e 100644 --- a/test/unit/gateways/skip_jack_test.rb +++ b/test/unit/gateways/skip_jack_test.rb @@ -3,13 +3,13 @@ class SkipJackTest < Test::Unit::TestCase def setup - Base.gateway_mode = :test + Base.mode = :test @gateway = SkipJackGateway.new(:login => 'X', :password => 'Y') @credit_card = credit_card('4242424242424242') - @billing_address = { + @billing_address = { :address1 => '123 Any St.', :address2 => 'Apt. B', :city => 'Anytown', @@ -20,7 +20,7 @@ def setup :fax => '616-555-2121' } - @shipping_address = { + @shipping_address = { :name => 'Stew Packman', :address1 => 'Company', :address2 => '321 No RD', @@ -29,19 +29,19 @@ def setup :country => 'MX', :phone => '0123231212' } - + @options = { :order_id => 1, :email => 'cody@example.com' } - + @amount = 100 end - def test_authorization_success + def test_authorization_success @gateway.expects(:ssl_post).returns(successful_authorization_response) - assert response = @gateway.authorize(@amount, @credit_card, @options) + response = @gateway.authorize(@amount, @credit_card, @options) assert_instance_of Response, response assert_success response assert_equal '9802853155172.022', response.authorization @@ -50,23 +50,23 @@ def test_authorization_success def test_authorization_failure @gateway.expects(:ssl_post).returns(unsuccessful_authorization_response) - assert response = @gateway.authorize(@amount, @credit_card, @options) + response = @gateway.authorize(@amount, @credit_card, @options) assert_instance_of Response, response assert_failure response end - + def test_purchase_success @gateway.expects(:ssl_post).times(2).returns(successful_authorization_response, successful_capture_response) - assert response = @gateway.purchase(@amount, @credit_card, @options) + response = @gateway.purchase(@amount, @credit_card, @options) assert_success response - assert_equal "9802853155172.022", response.authorization + assert_equal '9802853155172.022', response.authorization end - + def test_purchase_failure @gateway.expects(:ssl_post).returns(unsuccessful_authorization_response) - assert response = @gateway.purchase(@amount, @credit_card, @options) + response = @gateway.purchase(@amount, @credit_card, @options) assert_instance_of Response, response assert_failure response end @@ -74,7 +74,7 @@ def test_purchase_failure def test_refund_success @gateway.expects(:ssl_post).returns(successful_refund_response) - assert response = @gateway.refund(@amount, 123) + response = @gateway.refund(@amount, 123) assert_instance_of Response, response assert_failure response end @@ -82,8 +82,8 @@ def test_refund_success def test_deprecated_credit @gateway.expects(:ssl_post).returns(successful_refund_response) - assert_deprecation_warning(Gateway::CREDIT_DEPRECATION_MESSAGE, @gateway) do - assert response = @gateway.credit(@amount, 123) + assert_deprecation_warning(Gateway::CREDIT_DEPRECATION_MESSAGE) do + response = @gateway.credit(@amount, 123) assert_instance_of Response, response assert_failure response end @@ -91,131 +91,132 @@ def test_deprecated_credit def test_split_line keys = @gateway.send(:split_line, '"AUTHCODE","szSerialNumber","szTransactionAmount","szAuthorizationDeclinedMessage","szAVSResponseCode","szAVSResponseMessage","szOrderNumber","szAuthorizationResponseCode","szIsApproved","szCVV2ResponseCode","szCVV2ResponseMessage","szReturnCode","szTransactionFileName","szCAVVResponseCode"') - + values = @gateway.send(:split_line, '"000067","999888777666","1900","","N","Card authorized, exact address match with 5 digit zipcode.","1","000067","1","","","1","10138083786558.009",""') - + assert_equal keys.size, values.size - + keyvals = keys.zip(values).flatten map = Hash[*keyvals] - + assert_equal '000067', map['AUTHCODE'] end - + def test_turn_authorizeapi_response_into_hash body = <<-EOS "AUTHCODE","szSerialNumber","szTransactionAmount","szAuthorizationDeclinedMessage","szAVSResponseCode","szAVSResponseMessage","szOrderNumber","szAuthorizationResponseCode","szIsApproved","szCVV2ResponseCode","szCVV2ResponseMessage","szReturnCode","szTransactionFileName","szCAVVResponseCode" "000067","999888777666","1900","","N","Card authorized, exact address match with 5 digit zipcode.","1","000067","1","","","1","10138083786558.009","" EOS - + map = @gateway.send(:authorize_response_map, body) - + assert_equal 14, map.keys.size assert_equal '10138083786558.009', map[:szTransactionFileName] end - + def test_avs_result @gateway.expects(:ssl_post).returns(successful_authorization_response) - + response = @gateway.authorize(@amount, @credit_card, @options) assert_equal 'Y', response.avs_result['code'] end - + def test_cvv_result @gateway.expects(:ssl_post).returns(successful_authorization_response) - + response = @gateway.authorize(@amount, @credit_card, @options) assert_equal 'M', response.cvv_result['code'] end - + def test_basic_test_url @gateway.stubs(:test?).returns(true) @gateway.stubs(:advanced?).returns(false) - assert_equal "https://developer.skipjackic.com/scripts/evolvcc.dll?AuthorizeAPI", @gateway.send(:url_for, :authorization) + assert_equal 'https://developer.skipjackic.com/scripts/evolvcc.dll?AuthorizeAPI', @gateway.send(:url_for, :authorization) end - + def test_basic_test_url_non_authorization @gateway.stubs(:test?).returns(true) @gateway.stubs(:advanced?).returns(false) - assert_equal "https://developer.skipjackic.com/scripts/evolvcc.dll?SJAPI_TransactionChangeStatusRequest", @gateway.send(:url_for, :change_status) + assert_equal 'https://developer.skipjackic.com/scripts/evolvcc.dll?SJAPI_TransactionChangeStatusRequest', @gateway.send(:url_for, :change_status) end - + def test_advanced_test_url @gateway.stubs(:test?).returns(true) @gateway.stubs(:advanced?).returns(true) - assert_equal "https://developer.skipjackic.com/evolvcc/evolvcc.aspx?AuthorizeAPI", @gateway.send(:url_for, :authorization) + assert_equal 'https://developer.skipjackic.com/evolvcc/evolvcc.aspx?AuthorizeAPI', @gateway.send(:url_for, :authorization) end - + def test_advanced_test_url_non_authorization @gateway.stubs(:test?).returns(true) @gateway.stubs(:advanced?).returns(true) - assert_equal "https://developer.skipjackic.com/scripts/evolvcc.dll?SJAPI_TransactionChangeStatusRequest", @gateway.send(:url_for, :change_status) + assert_equal 'https://developer.skipjackic.com/scripts/evolvcc.dll?SJAPI_TransactionChangeStatusRequest', @gateway.send(:url_for, :change_status) end - + def test_basic_live_url @gateway.stubs(:test?).returns(false) @gateway.stubs(:advanced?).returns(false) - assert_equal "https://www.skipjackic.com/scripts/evolvcc.dll?AuthorizeAPI", @gateway.send(:url_for, :authorization) + assert_equal 'https://www.skipjackic.com/scripts/evolvcc.dll?AuthorizeAPI', @gateway.send(:url_for, :authorization) end - + def test_basic_live_url_non_authorization @gateway.stubs(:test?).returns(false) @gateway.stubs(:advanced?).returns(false) - assert_equal "https://www.skipjackic.com/scripts/evolvcc.dll?SJAPI_TransactionChangeStatusRequest", @gateway.send(:url_for, :change_status) + assert_equal 'https://www.skipjackic.com/scripts/evolvcc.dll?SJAPI_TransactionChangeStatusRequest', @gateway.send(:url_for, :change_status) end - + def test_advanced_live_url @gateway.stubs(:test?).returns(false) @gateway.stubs(:advanced?).returns(true) - assert_equal "https://www.skipjackic.com/evolvcc/evolvcc.aspx?AuthorizeAPI", @gateway.send(:url_for, :authorization) + assert_equal 'https://www.skipjackic.com/evolvcc/evolvcc.aspx?AuthorizeAPI', @gateway.send(:url_for, :authorization) end - + def test_advanced_live_url_non_authorization @gateway.stubs(:test?).returns(false) @gateway.stubs(:advanced?).returns(true) - assert_equal "https://www.skipjackic.com/scripts/evolvcc.dll?SJAPI_TransactionChangeStatusRequest", @gateway.send(:url_for, :change_status) + assert_equal 'https://www.skipjackic.com/scripts/evolvcc.dll?SJAPI_TransactionChangeStatusRequest', @gateway.send(:url_for, :change_status) end - - def test_paymentech_authorization_success + + def test_paymentech_authorization_success @gateway.expects(:ssl_post).returns(successful_paymentech_authorization_response) - assert response = @gateway.authorize(@amount, @credit_card, @options) + response = @gateway.authorize(@amount, @credit_card, @options) assert_instance_of Response, response assert_success response assert_equal '40000024585892.109', response.authorization end - + def test_paymentech_authorization_failure @gateway.expects(:ssl_post).returns(unsuccessful_paymentech_authorization_response) - assert response = @gateway.authorize(@amount, @credit_card, @options) + response = @gateway.authorize(@amount, @credit_card, @options) assert_instance_of Response, response assert_failure response end - def test_serial_number_is_added_before_developer_serial_number_for_authorization - @gateway.expects(:ssl_post).with('https://developer.skipjackic.com/scripts/evolvcc.dll?AuthorizeAPI', "Year=#{Time.now.year + 1}&TransactionAmount=1.00&ShipToPhone=&SerialNumber=X&SJName=Longbob+Longsen&OrderString=1%7ENone%7E0.00%7E0%7EN%7E%7C%7C&OrderNumber=1&OrderDescription=&Month=9&InvoiceNumber=&Email=cody%40example.com&DeveloperSerialNumber=Y&CustomerCode=&CVV2=123&AccountNumber=4242424242424242").returns(successful_authorization_response) - - assert response = @gateway.authorize(@amount, @credit_card, @options) + expected ="Year=#{Time.now.year + 1}&TransactionAmount=1.00&ShipToPhone=&SerialNumber=X&SJName=Longbob+Longsen&OrderString=1~None~0.00~0~N~%7C%7C&OrderNumber=1&OrderDescription=&Month=9&InvoiceNumber=&Email=cody%40example.com&DeveloperSerialNumber=Y&CustomerCode=&CVV2=123&AccountNumber=4242424242424242" + expected = expected.gsub('~', '%7E') if RUBY_VERSION < '2.5.0' + @gateway.expects(:ssl_post).with('https://developer.skipjackic.com/scripts/evolvcc.dll?AuthorizeAPI', expected).returns(successful_authorization_response) + + @gateway.authorize(@amount, @credit_card, @options) end - + def test_serial_number_is_added_before_developer_serial_number_for_capture @gateway.expects(:ssl_post).returns(successful_authorization_response) - assert response = @gateway.authorize(@amount, @credit_card, @options) - + response = @gateway.authorize(@amount, @credit_card, @options) + @gateway.expects(:ssl_post).with('https://developer.skipjackic.com/scripts/evolvcc.dll?SJAPI_TransactionChangeStatusRequest', "szTransactionId=#{response.authorization}&szSerialNumber=X&szForceSettlement=0&szDeveloperSerialNumber=Y&szDesiredStatus=SETTLE&szAmount=1.00").returns(successful_capture_response) - assert response = @gateway.capture(@amount, response.authorization) + @gateway.capture(@amount, response.authorization) end - + def test_successful_partial_capture @amount = 200 @gateway.expects(:ssl_post).returns(successful_authorization_response) - assert response = @gateway.authorize(@amount, @credit_card, @options) + response = @gateway.authorize(@amount, @credit_card, @options) @gateway.expects(:ssl_post).with('https://developer.skipjackic.com/scripts/evolvcc.dll?SJAPI_TransactionChangeStatusRequest', "szTransactionId=#{response.authorization}&szSerialNumber=X&szForceSettlement=0&szDeveloperSerialNumber=Y&szDesiredStatus=SETTLE&szAmount=1.00").returns(successful_capture_response) - assert response = @gateway.capture(@amount/2, response.authorization) - assert_equal "1.0000", response.params["TransactionAmount"] + response = @gateway.capture(@amount/2, response.authorization) + assert_equal '1.0000', response.params['TransactionAmount'] end def test_dont_send_blank_state @@ -229,49 +230,49 @@ def test_dont_send_blank_state CGI.parse(params)['ShipToState'].first == 'XX' end.returns(successful_authorization_response) - assert response = @gateway.authorize(@amount, @credit_card, @options) + @gateway.authorize(@amount, @credit_card, @options) end private + def successful_authorization_response <<-CSV "AUTHCODE","szSerialNumber","szTransactionAmount","szAuthorizationDeclinedMessage","szAVSResponseCode","szAVSResponseMessage","szOrderNumber","szAuthorizationResponseCode","szIsApproved","szCVV2ResponseCode","szCVV2ResponseMessage","szReturnCode","szTransactionFileName","szCAVVResponseCode" "TAS204","000386891209","100","","Y","Card authorized, exact address match with 5 digit zip code.","107a0fdb21ba42cf04f60274908085ea","TAS204","1","M","Match","1","9802853155172.022","" CSV end - + def successful_capture_response <<-CSV -"000386891209","0","1","","","","","","","","","" +"000386891209","0","1","","","","","","","","","" "000386891209","1.0000","SETTLE","SUCCESSFUL","Valid","618844630c5fad658e95abfd5e1d4e22","9802853156029.022" CSV end - + def successful_refund_response <<-CSV "AUTHCODE","szSerialNumber","szTransactionAmount","szAuthorizationDeclinedMessage","szAVSResponseCode","szAVSResponseMessage","szOrderNumber","szAuthorizationResponseCode","szIsApproved","szCVV2ResponseCode","szCVV2ResponseMessage","szReturnCode","szTransactionFileName","szCAVVResponseCode" "TAS204","000386891209","100","","Y","Card authorized, exact address match with 5 digit zip code.","107a0fdb21ba42cf04f60274908085ea","TAS204","1","M","Match","1","9802853155172.022","" CSV end - + def unsuccessful_authorization_response <<-CSV "AUTHCODE","szSerialNumber","szTransactionAmount","szAuthorizationDeclinedMessage","szAVSResponseCode","szAVSResponseMessage","szOrderNumber","szAuthorizationResponseCode","szIsApproved","szCVV2ResponseCode","szCVV2ResponseMessage","szReturnCode","szTransactionFileName","szCAVVResponseCode"\r\n"EMPTY","000386891209","100","","","","b1eec256d0182f29375e0cbae685092d","","0","","","-35","","" CSV end - + def unsuccessful_paymentech_authorization_response <<-CSV "AUTHCODE","szSerialNumber","szTransactionAmount","szAuthorizationDeclinedMessage","szAVSResponseCode","szAVSResponseMessage","szOrderNumber","szAuthorizationResponseCode","szIsApproved","szCVV2ResponseCode","szCVV2ResponseMessage","szReturnCode","szTransactionFileName","szCAVVResponseCode", -"EMPTY","000000000000","1.00","","","","43985b7953199d1f02c3017f948e9f13","","0","","","-83","","", +"EMPTY","000000000000","1.00","","","","43985b7953199d1f02c3017f948e9f13","","0","","","-83","","", CSV end - + def successful_paymentech_authorization_response <<-CSV "AUTHCODE","szSerialNumber","szTransactionAmount","szAuthorizationDeclinedMessage","szAVSResponseCode","szAVSResponseMessage","szOrderNumber","szAuthorizationResponseCode","szIsApproved","szCVV2ResponseCode","szCVV2ResponseMessage","szReturnCode","szTransactionFileName","szCAVVResponseCode", -"093223","000000000000","1.00","","Y","Card authorized, exact address match with 5 digit zip code.","5ac0f04e737baea5a5370037afe827f6","093223","1","M","Match","1","40000024585892.109","", +"093223","000000000000","1.00","","Y","Card authorized, exact address match with 5 digit zip code.","5ac0f04e737baea5a5370037afe827f6","093223","1","M","Match","1","40000024585892.109","", CSV end end - diff --git a/test/unit/gateways/so_easy_pay_test.rb b/test/unit/gateways/so_easy_pay_test.rb new file mode 100644 index 00000000000..94a7f913a80 --- /dev/null +++ b/test/unit/gateways/so_easy_pay_test.rb @@ -0,0 +1,224 @@ +require 'test_helper' + +class SoEasyPayTest < Test::Unit::TestCase + def setup + @gateway = SoEasyPayGateway.new( + :login => 'login', + :password => 'password' + ) + + @credit_card = credit_card + @amount = 100 + + @options = { + :order_id => '1', + :billing_address => address, + :description => 'Store Purchase' + } + end + + def test_successful_purchase + @gateway.expects(:ssl_post).returns(successful_purchase_response) + + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_instance_of Response, response + assert_success response + + # Replace with authorization number from the successful response + assert_equal '1708978', response.authorization + assert response.test? + end + + def test_unsuccessful_request + @gateway.expects(:ssl_post).returns(failed_purchase_response) + + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_equal '1708979', response.authorization + assert response.test? + end + + def test_successful_authorize + @gateway.expects(:ssl_post).returns(successful_authorize_response) + + assert response = @gateway.authorize(@amount, @credit_card, @options) + assert_instance_of Response, response + assert_success response + + assert_equal('1708980', response.authorization) + assert response.test? + end + + def test_successful_capture + @gateway.expects(:ssl_post).returns(successful_capture_response) + + assert response = @gateway.capture(1111, '1708980') + assert_instance_of Response, response + assert_success response + + assert_equal('1708981', response.authorization) + assert response.test? + end + + def test_successful_refund + @gateway.expects(:ssl_post).returns(successful_credit_response) + assert response = @gateway.refund(@amount, '1708978') + assert_instance_of Response, response + assert_success response + assert_equal 'Transaction successful', response.message + end + + def test_failed_refund + @gateway.expects(:ssl_post).returns(failed_credit_response) + + assert response = @gateway.refund(@amount, '1708978') + assert_instance_of Response, response + assert_failure response + assert_equal 'Card declined', response.message + end + + def test_do_not_depend_on_expiry_date_class + @gateway.stubs(:ssl_post).returns(successful_purchase_response) + @credit_card.expects(:expiry_date).never + + @gateway.purchase(@amount, @credit_card, @options) + end + + def test_use_ducktyping_for_credit_card + @gateway.expects(:ssl_post).returns(successful_purchase_response) + + credit_card = stub(:number => '4242424242424242', :verification_value => '123', :name => 'Hans Tester', :year => 2012, :month => 1) + + assert_nothing_raised do + assert_success @gateway.purchase(@amount, credit_card, @options) + end + end + + private + + # Place raw successful response from gateway here + def successful_purchase_response + %(<?xml version="1.0" encoding="utf-8"?> + <soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:tns="urn:Interface" xmlns:types="urn:Interface/encodedTypes" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> + <soap:Body soap:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"> + <tns:SaleTransactionResponse> + <return href="#id1" /> + </tns:SaleTransactionResponse> + <tns:SaleTransactionResponse id="id1" xsi:type="tns:SaleTransactionResponse"> + <transactionID xsi:type="xsd:string">1708978</transactionID> + <orderID xsi:type="xsd:string">12</orderID> + <status xsi:type="xsd:string">Authorized</status> + <errorcode xsi:type="xsd:string">000</errorcode> + <errormessage xsi:type="xsd:string">Transaction successful</errormessage> + <AVSResult xsi:type="xsd:string">K</AVSResult> + <FSResult xsi:type="xsd:string">NOSCORE</FSResult> + <FSStatus xsi:type="xsd:string">0000</FSStatus> + <cardNumberSuffix xsi:type="xsd:string">**21</cardNumberSuffix> + <cardExpiryDate xsi:type="xsd:string">02/16</cardExpiryDate> + <cardType xsi:type="xsd:string">VISA</cardType> + </tns:SaleTransactionResponse> + </soap:Body> + </soap:Envelope>) + end + + # Place raw failed response from gateway here + def failed_purchase_response + %(<?xml version="1.0" encoding="utf-8"?> + <soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:tns="urn:Interface" xmlns:types="urn:Interface/encodedTypes" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> + <soap:Body soap:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"> + <tns:SaleTransactionResponse> + <return href="#id1" /> + </tns:SaleTransactionResponse> + <tns:SaleTransactionResponse id="id1" xsi:type="tns:SaleTransactionResponse"> + <transactionID xsi:type="xsd:string">1708979</transactionID> + <orderID xsi:type="xsd:string">12</orderID> + <status xsi:type="xsd:string">Not Authorized</status> + <errorcode xsi:type="xsd:string">002</errorcode> + <errormessage xsi:type="xsd:string">Card declined</errormessage> + <AVSResult xsi:type="xsd:string">K</AVSResult> + <FSResult xsi:type="xsd:string">NOSCORE</FSResult> + <FSStatus xsi:type="xsd:string">0000</FSStatus> + <cardNumberSuffix xsi:type="xsd:string">**21</cardNumberSuffix> + <cardExpiryDate xsi:type="xsd:string">02/16</cardExpiryDate> + <cardType xsi:type="xsd:string">VISA</cardType> + </tns:SaleTransactionResponse> + </soap:Body> + </soap:Envelope>) + end + + def successful_authorize_response + %(<?xml version="1.0" encoding="utf-8"?> + <soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:tns="urn:Interface" xmlns:types="urn:Interface/encodedTypes" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> + <soap:Body soap:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"> + <tns:AuthorizeTransactionResponse> + <return href="#id1" /> + </tns:AuthorizeTransactionResponse> + <tns:AuthorizeTransactionResponse id="id1" xsi:type="tns:AuthorizeTransactionResponse"> + <transactionID xsi:type="xsd:string">1708980</transactionID> + <orderID xsi:type="xsd:string">12</orderID> + <status xsi:type="xsd:string">Authorized</status> + <errorcode xsi:type="xsd:string">000</errorcode> + <errormessage xsi:type="xsd:string">Transaction successful</errormessage> + <AVSResult xsi:type="xsd:string">K</AVSResult> + <FSResult xsi:type="xsd:string">NOSCORE</FSResult> + <FSStatus xsi:type="xsd:string">0000</FSStatus> + <cardNumberSuffix xsi:type="xsd:string">**21</cardNumberSuffix> + <cardExpiryDate xsi:type="xsd:string">02/16</cardExpiryDate> + <cardType xsi:type="xsd:string">VISA</cardType> + </tns:AuthorizeTransactionResponse> + </soap:Body> + </soap:Envelope>) + end + + def successful_capture_response + %(<?xml version="1.0" encoding="utf-8"?> + <soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:tns="urn:Interface" xmlns:types="urn:Interface/encodedTypes" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> + <soap:Body soap:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"> + <tns:CaptureTransactionResponse> + <return href="#id1" /> + </tns:CaptureTransactionResponse> + <tns:CaptureTransactionResponse id="id1" xsi:type="tns:CaptureTransactionResponse"> + <transactionID xsi:type="xsd:string">1708981</transactionID> + <status xsi:type="xsd:string">Authorized</status> + <errorcode xsi:type="xsd:string">000</errorcode> + <errormessage xsi:type="xsd:string">Transaction successful</errormessage> + </tns:CaptureTransactionResponse> + </soap:Body> + </soap:Envelope>) + end + + def successful_credit_response + %(<?xml version="1.0" encoding="utf-8"?> + <soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:tns="urn:Interface" xmlns:types="urn:Interface/encodedTypes" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> + <soap:Body soap:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"> + <tns:RefundTransactionResponse> + <return href="#id1" /> + </tns:RefundTransactionResponse> + <tns:RefundTransactionResponse id="id1" xsi:type="tns:RefundTransactionResponse"> + <transactionID xsi:type="xsd:string">1708982</transactionID> + <status xsi:type="xsd:string">Authorized</status> + <errorcode xsi:type="xsd:string">000</errorcode> + <errormessage xsi:type="xsd:string">Transaction successful</errormessage> + </tns:RefundTransactionResponse> + </soap:Body> + </soap:Envelope>) + end + + def failed_credit_response + %(<?xml version="1.0" encoding="utf-8"?> + <soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:tns="urn:Interface" xmlns:types="urn:Interface/encodedTypes" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> + <soap:Body soap:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"> + <tns:RefundTransactionResponse> + <return href="#id1" /> + </tns:RefundTransactionResponse> + <tns:RefundTransactionResponse id="id1" xsi:type="tns:RefundTransactionResponse"> + <transactionID xsi:type="xsd:string">1708983</transactionID> + <status xsi:type="xsd:string">Not Authorized</status> + <errorcode xsi:type="xsd:string">002</errorcode> + <errormessage xsi:type="xsd:string">Card declined</errormessage> + </tns:RefundTransactionResponse> + </soap:Body> + </soap:Envelope>) + end + +end diff --git a/test/unit/gateways/spreedly_core_test.rb b/test/unit/gateways/spreedly_core_test.rb index 597a1f4745d..89e86fb2e28 100644 --- a/test/unit/gateways/spreedly_core_test.rb +++ b/test/unit/gateways/spreedly_core_test.rb @@ -7,7 +7,10 @@ def setup @payment_method_token = 'E3eQGR3E0xiosj7FOJRtIKbF8Ch' @credit_card = credit_card + @check = check @amount = 103 + @existing_transaction = 'LKA3RchoqYO0njAfhHVw60ohjrC' + @not_found_transaction = 'AdyQXaG0SVpSoMPdmFlvd3aA3uz' end def test_successful_purchase_with_payment_method_token @@ -18,10 +21,10 @@ def test_successful_purchase_with_payment_method_token assert_success response assert !response.test? - assert_equal "K1CRcdN0jK32UyrnZGPOXLRjqJl", response.authorization - assert_equal "Succeeded!", response.message - assert_equal "Non-U.S. issuing bank does not support AVS.", response.avs_result["message"] - assert_equal "Failed data validation check", response.cvv_result["message"] + assert_equal 'K1CRcdN0jK32UyrnZGPOXLRjqJl', response.authorization + assert_equal 'Succeeded!', response.message + assert_equal 'Non-U.S. issuing bank does not support AVS.', response.avs_result['message'] + assert_equal 'CVV failed data validation check', response.cvv_result['message'] end def test_failed_purchase_with_payment_method_token @@ -31,27 +34,43 @@ def test_failed_purchase_with_payment_method_token assert_failure response assert !response.test? - assert_equal "Xh0T15CfYQeUqYV9Ixm8YV283Ds", response.authorization - assert_equal "This transaction cannot be processed.", response.message + assert_equal 'Xh0T15CfYQeUqYV9Ixm8YV283Ds', response.authorization + assert_equal 'This transaction cannot be processed.', response.message assert_equal '10762', response.params['response_error_code'] assert_nil response.avs_result['message'] assert_nil response.cvv_result['message'] end def test_successful_purchase_with_credit_card - @gateway.stubs(:raw_ssl_request).returns(successful_store_response, successful_purchase_response) + @gateway.stubs(:raw_ssl_request).returns(successful_purchase_response) response = @gateway.purchase(@amount, @credit_card) assert_success response assert !response.test? - assert_equal "K1CRcdN0jK32UyrnZGPOXLRjqJl", response.authorization - assert_equal "Succeeded!", response.message - assert_equal "Non-U.S. issuing bank does not support AVS.", response.avs_result["message"] - assert_equal "Failed data validation check", response.cvv_result["message"] + assert_equal 'K1CRcdN0jK32UyrnZGPOXLRjqJl', response.authorization + assert_equal 'Succeeded!', response.message + assert_equal 'Non-U.S. issuing bank does not support AVS.', response.avs_result['message'] + assert_equal 'CVV failed data validation check', response.cvv_result['message'] assert_equal 'Purchase', response.params['transaction_type'] assert_equal '5WxC03VQ0LmmkYvIHl7XsPKIpUb', response.params['payment_method_token'] assert_equal '6644', response.params['payment_method_last_four_digits'] + assert_equal 'used', response.params['payment_method_storage_state'] + end + + def test_successful_purchase_with_check + @gateway.stubs(:raw_ssl_request).returns(successful_check_purchase_response) + response = @gateway.purchase(@amount, @check) + + assert_success response + assert !response.test? + + assert_equal 'ZwnfZs3Qy4gRDPWXHopamNuarCJ', response.authorization + assert_equal 'Succeeded!', response.message + assert_equal 'Purchase', response.params['transaction_type'] + assert_equal 'HtCrYfW17wEzWWfrMbwDX4TwPVW', response.params['payment_method_token'] + assert_equal '021*', response.params['payment_method_routing_number'] + assert_equal '*3210', response.params['payment_method_account_number'] end def test_failed_purchase_with_invalid_credit_card @@ -62,18 +81,28 @@ def test_failed_purchase_with_invalid_credit_card end def test_failed_purchase_with_credit_card - @gateway.stubs(:raw_ssl_request).returns(successful_store_response, failed_purchase_response) + @gateway.stubs(:raw_ssl_request).returns(failed_purchase_response) response = @gateway.purchase(@amount, @credit_card) assert_failure response - assert_equal "Xh0T15CfYQeUqYV9Ixm8YV283Ds", response.authorization - assert_equal "This transaction cannot be processed.", response.message + assert_equal 'Xh0T15CfYQeUqYV9Ixm8YV283Ds', response.authorization + assert_equal 'This transaction cannot be processed.', response.message assert_equal '10762', response.params['response_error_code'] assert_nil response.avs_result['message'] assert_nil response.cvv_result['message'] assert_equal '0957', response.params['payment_method_last_four_digits'] end + def test_purchase_without_gateway_token_option + @gateway.expects(:commit).with('gateways/token/purchase.xml', anything) + @gateway.purchase(@amount, @payment_method_token) + end + + def test_purchase_with_gateway_token_option + @gateway.expects(:commit).with('gateways/mynewtoken/purchase.xml', anything) + @gateway.purchase(@amount, @payment_method_token, gateway_token: 'mynewtoken') + end + def test_successful_authorize_with_token_and_capture @gateway.expects(:raw_ssl_request).returns(successful_authorize_response) response = @gateway.authorize(@amount, @payment_method_token) @@ -81,18 +110,18 @@ def test_successful_authorize_with_token_and_capture assert_success response assert !response.test? - assert_equal "NKz5SO6jrsRDc0UyaujwayXJZ1a", response.authorization - assert_equal "Succeeded!", response.message - assert_equal "Non-U.S. issuing bank does not support AVS.", response.avs_result["message"] - assert_equal "Failed data validation check", response.cvv_result["message"] + assert_equal 'NKz5SO6jrsRDc0UyaujwayXJZ1a', response.authorization + assert_equal 'Succeeded!', response.message + assert_equal 'Non-U.S. issuing bank does not support AVS.', response.avs_result['message'] + assert_equal 'CVV failed data validation check', response.cvv_result['message'] @gateway.expects(:raw_ssl_request).returns(successful_capture_response) response = @gateway.capture(@amount, response.authorization) assert_success response assert !response.test? - assert_equal "Bd1ZeztpPyjfXzfUa14BQGfaLmg", response.authorization - assert_equal "Succeeded!", response.message + assert_equal 'Bd1ZeztpPyjfXzfUa14BQGfaLmg', response.authorization + assert_equal 'Succeeded!', response.message assert_nil response.avs_result['message'] assert_nil response.cvv_result['message'] end @@ -101,39 +130,40 @@ def test_failed_authorize_with_token @gateway.expects(:raw_ssl_request).returns(failed_authorize_response) response = @gateway.authorize(@amount, @payment_method_token) assert_failure response - assert_equal "This transaction cannot be processed.", response.message + assert_equal 'This transaction cannot be processed.', response.message assert_equal '10762', response.params['response_error_code'] assert_nil response.avs_result['message'] assert_nil response.cvv_result['message'] end def test_successful_authorize_with_credit_card_and_capture - @gateway.stubs(:raw_ssl_request).returns(successful_store_response, successful_authorize_response) + @gateway.stubs(:raw_ssl_request).returns(successful_authorize_response) response = @gateway.authorize(@amount, @credit_card) assert_success response assert !response.test? - assert_equal "NKz5SO6jrsRDc0UyaujwayXJZ1a", response.authorization - assert_equal "Succeeded!", response.message - assert_equal "Non-U.S. issuing bank does not support AVS.", response.avs_result["message"] - assert_equal "Failed data validation check", response.cvv_result["message"] + assert_equal 'NKz5SO6jrsRDc0UyaujwayXJZ1a', response.authorization + assert_equal 'Succeeded!', response.message + assert_equal 'Non-U.S. issuing bank does not support AVS.', response.avs_result['message'] + assert_equal 'CVV failed data validation check', response.cvv_result['message'] assert_equal 'Authorization', response.params['transaction_type'] assert_equal '5WxC03VQ0LmmkYvIHl7XsPKIpUb', response.params['payment_method_token'] assert_equal '6644', response.params['payment_method_last_four_digits'] + assert_equal 'used', response.params['payment_method_storage_state'] @gateway.expects(:raw_ssl_request).returns(successful_capture_response) response = @gateway.capture(@amount, response.authorization) assert_success response - assert_equal "Bd1ZeztpPyjfXzfUa14BQGfaLmg", response.authorization - assert_equal "Succeeded!", response.message + assert_equal 'Bd1ZeztpPyjfXzfUa14BQGfaLmg', response.authorization + assert_equal 'Succeeded!', response.message end def test_failed_authorize_with_credit_card - @gateway.stubs(:raw_ssl_request).returns(successful_store_response, failed_authorize_response) + @gateway.stubs(:raw_ssl_request).returns(failed_authorize_response) response = @gateway.authorize(@amount, @credit_card) assert_failure response - assert_equal "This transaction cannot be processed.", response.message + assert_equal 'This transaction cannot be processed.', response.message assert_equal '10762', response.params['response_error_code'] end @@ -152,7 +182,7 @@ def test_failed_capture @gateway.expects(:raw_ssl_request).returns(failed_capture_response) response = @gateway.capture(@amount + 20, response.authorization) assert_failure response - assert_equal "Amount specified exceeds allowable limit.", response.message + assert_equal 'Amount specified exceeds allowable limit.', response.message end def test_successful_refund @@ -163,8 +193,8 @@ def test_successful_refund @gateway.expects(:raw_ssl_request).returns(successful_refund_response) response = @gateway.refund(@amount, response.authorization) assert_success response - assert_equal "Succeeded!", response.message - assert_equal "Credit", response.params["transaction_type"] + assert_equal 'Succeeded!', response.message + assert_equal 'Credit', response.params['transaction_type'] end def test_failed_refund @@ -175,7 +205,7 @@ def test_failed_refund @gateway.expects(:raw_ssl_request).returns(failed_refund_response) response = @gateway.refund(@amount + 20, response.authorization) assert_failure response - assert_equal "The partial refund amount must be less than or equal to the original transaction amount", response.message + assert_equal 'The partial refund amount must be less than or equal to the original transaction amount', response.message assert_equal '10009', response.params['response_error_code'] end @@ -187,7 +217,7 @@ def test_successful_void @gateway.expects(:raw_ssl_request).returns(successful_void_response) response = @gateway.void(response.authorization) assert_success response - assert_equal "Succeeded!", response.message + assert_equal 'Succeeded!', response.message end def test_failed_void @@ -198,17 +228,35 @@ def test_failed_void @gateway.expects(:raw_ssl_request).returns(failed_void_response) response = @gateway.void(response.authorization) assert_failure response - assert_equal "Authorization is voided.", response.message + assert_equal 'Authorization is voided.', response.message assert_equal '10600', response.params['response_error_code'] end + def test_successful_verify + @gateway.expects(:raw_ssl_request).returns(successful_verify_response) + response = @gateway.verify(@payment_method_token) + assert_success response + + assert_equal 'Succeeded!', response.message + assert_equal 'Verification', response.params['transaction_type'] + end + + def test_failed_verify + @gateway.expects(:raw_ssl_request).returns(failed_verify_response) + response = @gateway.verify(@payment_method_token) + assert_failure response + + assert_equal 'Unable to process the verify transaction.', response.message + assert_empty response.params['response_error_code'] + end + def test_successful_store @gateway.expects(:raw_ssl_request).returns(successful_store_response) response = @gateway.store(@credit_card) assert_success response - assert_equal "Succeeded!", response.message - assert_equal "Bml92ojQgsTf7bQ7z7WlwQVIdjr", response.authorization - assert_equal "true", response.params["retained"] + assert_equal 'Succeeded!', response.message + assert_equal 'Bml92ojQgsTf7bQ7z7WlwQVIdjr', response.authorization + assert_equal 'true', response.params['retained'] end def test_failed_store @@ -226,11 +274,34 @@ def test_successful_unstore @gateway.expects(:raw_ssl_request).returns(successful_unstore_response) response = @gateway.unstore(response.authorization) assert_success response - assert_equal "Succeeded!", response.message + assert_equal 'Succeeded!', response.message + end + + def test_successful_find + @gateway.expects(:raw_ssl_request).returns(successful_find_response) + response = @gateway.find(@existing_transaction) + assert_success response + + assert_equal 'Succeeded!', response.message + assert_equal 'LKA3RchoqYO0njAfhHVw60ohjrC', response.authorization + end + + def test_failed_find + @gateway.expects(:raw_ssl_request).returns(failed_find_response) + response = @gateway.find(@not_found_transaction) + assert_failure response + + assert_match %r(Unable to find the transaction), response.message + assert_match %r(#{@not_found_transaction}), response.message end + def test_scrubbing + assert @gateway.supports_scrubbing? + assert_equal @gateway.scrub(pre_scrubbed), post_scrubbed + end private + def successful_purchase_response MockResponse.succeeded <<-XML <transaction> @@ -256,7 +327,7 @@ def successful_purchase_response <avs_code>G</avs_code> <avs_message>Non-U.S. issuing bank does not support AVS.</avs_message> <cvv_code>I</cvv_code> - <cvv_message>Failed data validation check</cvv_message> + <cvv_message>CVV failed data validation check</cvv_message> <error_code nil="true"/> <error_detail nil="true"/> <created_at type="datetime">2012-12-06T20:28:14Z</created_at> @@ -283,6 +354,7 @@ def successful_purchase_response <data> <how_many>2</how_many> </data> + <storage_state>used</storage_state> <payment_method_type>credit_card</payment_method_type> <verification_value/> <number>XXXX-XXXX-XXXX-6644</number> @@ -295,6 +367,97 @@ def successful_purchase_response XML end + def successful_check_purchase_response + MockResponse.succeeded <<-XML + <transaction> + <on_test_gateway type="boolean">false</on_test_gateway> + <created_at type="dateTime">2019-01-06T18:24:33Z</created_at> + <updated_at type="dateTime">2019-01-06T18:24:33Z</updated_at> + <succeeded type="boolean">true</succeeded> + <state>succeeded</state> + <token>ZwnfZs3Qy4gRDPWXHopamNuarCJ</token> + <transaction_type>Purchase</transaction_type> + <order_id nil="true"/> + <ip nil="true"/> + <description nil="true"/> + <email nil="true"/> + <merchant_name_descriptor nil="true"/> + <merchant_location_descriptor nil="true"/> + <gateway_specific_fields nil="true"/> + <gateway_specific_response_fields> + </gateway_specific_response_fields> + <gateway_transaction_id>49</gateway_transaction_id> + <gateway_latency_ms type="integer">0</gateway_latency_ms> + <amount type="integer">100</amount> + <currency_code>USD</currency_code> + <retain_on_success type="boolean">false</retain_on_success> + <payment_method_added type="boolean">true</payment_method_added> + <message key="messages.transaction_succeeded">Succeeded!</message> + <gateway_token>3gLeg4726V5P0HK7cq7QzHsL0a6</gateway_token> + <gateway_type>test</gateway_type> + <shipping_address> + <name nil="true"/> + <address1 nil="true"/> + <address2 nil="true"/> + <city nil="true"/> + <state nil="true"/> + <zip nil="true"/> + <country nil="true"/> + <phone_number nil="true"/> + </shipping_address> + <response> + <success type="boolean">true</success> + <message>Successful purchase</message> + <avs_code nil="true"/> + <avs_message nil="true"/> + <cvv_code nil="true"/> + <cvv_message nil="true"/> + <pending type="boolean">false</pending> + <result_unknown type="boolean">false</result_unknown> + <error_code nil="true"/> + <error_detail nil="true"/> + <cancelled type="boolean">false</cancelled> + <fraud_review nil="true"/> + <created_at type="dateTime">2019-01-06T18:24:33Z</created_at> + <updated_at type="dateTime">2019-01-06T18:24:33Z</updated_at> + </response> + <api_urls> + </api_urls> + <payment_method> + <token>HtCrYfW17wEzWWfrMbwDX4TwPVW</token> + <created_at type="dateTime">2019-01-06T18:24:33Z</created_at> + <updated_at type="dateTime">2019-01-06T18:24:33Z</updated_at> + <email nil="true"/> + <data nil="true"/> + <storage_state>cached</storage_state> + <test type="boolean">true</test> + <metadata nil="true"/> + <full_name>Jim Smith</full_name> + <bank_name nil="true"/> + <account_type>checking</account_type> + <account_holder_type>personal</account_holder_type> + <routing_number_display_digits>021</routing_number_display_digits> + <account_number_display_digits>3210</account_number_display_digits> + <first_name>Jim</first_name> + <last_name>Smith</last_name> + <address1 nil="true"/> + <address2 nil="true"/> + <city nil="true"/> + <state nil="true"/> + <zip nil="true"/> + <country nil="true"/> + <phone_number nil="true"/> + <company nil="true"/> + <payment_method_type>bank_account</payment_method_type> + <errors> + </errors> + <routing_number>021*</routing_number> + <account_number>*3210</account_number> + </payment_method> + </transaction> + XML + end + def failed_purchase_response MockResponse.failed <<-XML <transaction> @@ -384,7 +547,7 @@ def successful_authorize_response <avs_code>G</avs_code> <avs_message>Non-U.S. issuing bank does not support AVS.</avs_message> <cvv_code>I</cvv_code> - <cvv_message>Failed data validation check</cvv_message> + <cvv_message>CVV failed data validation check</cvv_message> <error_code nil="true"/> <error_detail nil="true"/> <created_at type="datetime">2012-12-08T04:13:48Z</created_at> @@ -411,6 +574,7 @@ def successful_authorize_response <data> <how_many>2</how_many> </data> + <storage_state>used</storage_state> <payment_method_type>credit_card</payment_method_type> <verification_value/> <number>XXXX-XXXX-XXXX-6644</number> @@ -785,23 +949,319 @@ def successful_unstore_response XML end - class MockResponse - attr_reader :code, :body - def self.succeeded(xml) - MockResponse.new(200, xml) - end + def pre_scrubbed + <<-EOS + opening connection to core.spreedly.com:443... + opened + starting SSL for core.spreedly.com:443... + SSL established + <- "POST /v1/payment_methods.xml HTTP/1.1\r\nContent-Type: text/xml\r\nAuthorization: Basic NFk5YlZrT0NwWWVzUFFPZkRpN1RYUXlVdzUwOlkyaTdBamdVMDNTVWp3WTR4bk9QcXpkc3Y0ZE1iUERDUXpvckFrOEJjb3kwVThFSVZFNGlubkdqdW9NUXY3TU4=\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: core.spreedly.com\r\nContent-Length: 404\r\n\r\n" + <- "<?xml version=\"1.0\"?>\n<payment_method>\n <credit_card>\n <number>5555555555554444</number>\n <verification_value>123</verification_value>\n <first_name>Longbob</first_name>\n <last_name>Longsen</last_name>\n <month>9</month>\n <year>2019</year>\n <email/>\n <address1/>\n <address2/>\n <city/>\n <state/>\n <zip/>\n <country/>\n </credit_card>\n <data></data>\n</payment_method>\n" + -> "HTTP/1.1 201 Created\r\n" + -> "Date: Sat, 10 Mar 2018 22:04:06 GMT\r\n" + -> "Content-Type: application/xml; charset=utf-8\r\n" + -> "Content-Length: 1875\r\n" + -> "Connection: close\r\n" + -> "X-Frame-Options: SAMEORIGIN\r\n" + -> "X-XSS-Protection: 1; mode=block\r\n" + -> "X-Content-Type-Options: nosniff\r\n" + -> "ETag: W/\"c4ef6dfc389a5514d6b6ffd8bac8786c\"\r\n" + -> "Cache-Control: max-age=0, private, must-revalidate\r\n" + -> "X-Request-Id: b227ok4du2hrj7mrtt10.core_dcaa82760687b3ef\r\n" + -> "Server: nginx\r\n" + -> "Strict-Transport-Security: max-age=31536000; includeSubdomains;\r\n" + -> "\r\n" + reading 1875 bytes... + -> "<transaction>\n <token>NRBpydUCWn658GHV8h2kVlUzB0i</token>\n <created_at type=\"dateTime\">2018-03-10T22:04:06Z</created_at>\n <updated_at type=\"dateTime\">2018-03-10T22:04:06Z</updated_at>\n <succeeded type=\"boolean\">true</succeeded>\n <transaction_type>AddPaymentMethod</transaction_type>\n <retained type=\"boolean\">false</retained>\n <state>succeeded</state>\n <message key=\"messages.transaction_succeeded\">Succeeded!</message>\n <payment_method>\n <token>Wd25UIrH1uopTkZZ4UDdb5XmSDd</token>\n <created_at type=\"dateTime\">2018-03-10T22:04:06Z</created_at>\n <updated_at type=\"dateTime\">2018-03-10T22:04:06Z</updated_at>\n <email nil=\"true\"/>\n <data nil=\"true\"/>\n <storage_state>cached</storage_state>\n <test type=\"boolean\">true</test>\n <last_four_digits>4444</last_four_digits>\n <first_six_digits>555555</first_six_digits>\n <card_type>master</card_type>\n <first_name>Longbob</first_name>\n <last_name>Longsen</last_name>\n <month type=\"integer\">9</month>\n <year type=\"integer\">2019</year>\n <address1 nil=\"true\"/>\n <address2 nil=\"true\"/>\n <city nil=\"true\"/>\n <state nil=\"true\"/>\n <zip nil=\"true\"/>\n <country nil=\"true\"/>\n <phone_number nil=\"true\"/>\n <company nil=\"true\"/>\n <full_name>Longbob Longsen</full_name>\n <eligible_for_card_updater type=\"boolean\">true</eligible_for_card_updater>\n <shipping_address1 nil=\"true\"/>\n <shipping_address2 nil=\"true\"/>\n <shipping_city nil=\"true\"/>\n <shipping_state nil=\"true\"/>\n <shipping_zip nil=\"true\"/>\n <shipping_country nil=\"true\"/>\n <shipping_phone_number nil=\"true\"/>\n <payment_method_type>credit_card</payment_method_type>\n <errors>\n </errors>\n <verification_value>XXX</verification_value>\n <number>XXXX-XXXX-XXXX-4444</number>\n <fingerprint>125370bb396dff6fed4f581f85a91a9e5317</fingerprint>\n </payment_method>\n</transaction>\n" + read 1875 bytes + Conn close + EOS + end + + def post_scrubbed + <<-EOS + opening connection to core.spreedly.com:443... + opened + starting SSL for core.spreedly.com:443... + SSL established + <- "POST /v1/payment_methods.xml HTTP/1.1\r\nContent-Type: text/xml\r\nAuthorization: Basic [FILTERED]=\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: core.spreedly.com\r\nContent-Length: 404\r\n\r\n" + <- "<?xml version=\"1.0\"?>\n<payment_method>\n <credit_card>\n <number>[FILTERED]</number>\n <verification_value>[FILTERED]</verification_value>\n <first_name>Longbob</first_name>\n <last_name>Longsen</last_name>\n <month>9</month>\n <year>2019</year>\n <email/>\n <address1/>\n <address2/>\n <city/>\n <state/>\n <zip/>\n <country/>\n </credit_card>\n <data></data>\n</payment_method>\n" + -> "HTTP/1.1 201 Created\r\n" + -> "Date: Sat, 10 Mar 2018 22:04:06 GMT\r\n" + -> "Content-Type: application/xml; charset=utf-8\r\n" + -> "Content-Length: 1875\r\n" + -> "Connection: close\r\n" + -> "X-Frame-Options: SAMEORIGIN\r\n" + -> "X-XSS-Protection: 1; mode=block\r\n" + -> "X-Content-Type-Options: nosniff\r\n" + -> "ETag: W/\"c4ef6dfc389a5514d6b6ffd8bac8786c\"\r\n" + -> "Cache-Control: max-age=0, private, must-revalidate\r\n" + -> "X-Request-Id: b227ok4du2hrj7mrtt10.core_dcaa82760687b3ef\r\n" + -> "Server: nginx\r\n" + -> "Strict-Transport-Security: max-age=31536000; includeSubdomains;\r\n" + -> "\r\n" + reading 1875 bytes... + -> "<transaction>\n <token>NRBpydUCWn658GHV8h2kVlUzB0i</token>\n <created_at type=\"dateTime\">2018-03-10T22:04:06Z</created_at>\n <updated_at type=\"dateTime\">2018-03-10T22:04:06Z</updated_at>\n <succeeded type=\"boolean\">true</succeeded>\n <transaction_type>AddPaymentMethod</transaction_type>\n <retained type=\"boolean\">false</retained>\n <state>succeeded</state>\n <message key=\"messages.transaction_succeeded\">Succeeded!</message>\n <payment_method>\n <token>Wd25UIrH1uopTkZZ4UDdb5XmSDd</token>\n <created_at type=\"dateTime\">2018-03-10T22:04:06Z</created_at>\n <updated_at type=\"dateTime\">2018-03-10T22:04:06Z</updated_at>\n <email nil=\"true\"/>\n <data nil=\"true\"/>\n <storage_state>cached</storage_state>\n <test type=\"boolean\">true</test>\n <last_four_digits>4444</last_four_digits>\n <first_six_digits>555555</first_six_digits>\n <card_type>master</card_type>\n <first_name>Longbob</first_name>\n <last_name>Longsen</last_name>\n <month type=\"integer\">9</month>\n <year type=\"integer\">2019</year>\n <address1 nil=\"true\"/>\n <address2 nil=\"true\"/>\n <city nil=\"true\"/>\n <state nil=\"true\"/>\n <zip nil=\"true\"/>\n <country nil=\"true\"/>\n <phone_number nil=\"true\"/>\n <company nil=\"true\"/>\n <full_name>Longbob Longsen</full_name>\n <eligible_for_card_updater type=\"boolean\">true</eligible_for_card_updater>\n <shipping_address1 nil=\"true\"/>\n <shipping_address2 nil=\"true\"/>\n <shipping_city nil=\"true\"/>\n <shipping_state nil=\"true\"/>\n <shipping_zip nil=\"true\"/>\n <shipping_country nil=\"true\"/>\n <shipping_phone_number nil=\"true\"/>\n <payment_method_type>credit_card</payment_method_type>\n <errors>\n </errors>\n <verification_value>[FILTERED]</verification_value>\n <number>[FILTERED]</number>\n <fingerprint>125370bb396dff6fed4f581f85a91a9e5317</fingerprint>\n </payment_method>\n</transaction>\n" + read 1875 bytes + Conn close + EOS + end - def self.failed(xml) - MockResponse.new(422, xml) - end + def successful_verify_response + MockResponse.succeeded <<-XML + <transaction> + <on_test_gateway type="boolean">true</on_test_gateway> + <created_at type="dateTime">2018-02-24T00:47:56Z</created_at> + <updated_at type="dateTime">2018-02-24T00:47:56Z</updated_at> + <succeeded type="boolean">true</succeeded> + <state>succeeded</state> + <token>891hWyHKmfCggQQ7Q35sGVcEC01</token> + <transaction_type>Verification</transaction_type> + <order_id nil="true"/> + <ip nil="true"/> + <description nil="true"/> + <email nil="true"/> + <merchant_name_descriptor nil="true"/> + <merchant_location_descriptor nil="true"/> + <gateway_specific_fields nil="true"/> + <gateway_specific_response_fields> + </gateway_specific_response_fields> + <gateway_transaction_id>67</gateway_transaction_id> + <gateway_latency_ms type="integer">27</gateway_latency_ms> + <currency_code>USD</currency_code> + <retain_on_success type="boolean">false</retain_on_success> + <payment_method_added type="boolean">false</payment_method_added> + <message key="messages.transaction_succeeded">Succeeded!</message> + <gateway_token>3gLeg4726V5P0HK7cq7QzHsL0a6</gateway_token> + <gateway_type>test</gateway_type> + <shipping_address> + <name>Jim TesterDude</name> + <address1 nil="true"/> + <address2 nil="true"/> + <city nil="true"/> + <state nil="true"/> + <zip nil="true"/> + <country nil="true"/> + <phone_number nil="true"/> + </shipping_address> + <response> + <success type="boolean">true</success> + <message>Successful verify</message> + <avs_code nil="true"/> + <avs_message nil="true"/> + <cvv_code nil="true"/> + <cvv_message nil="true"/> + <pending type="boolean">false</pending> + <result_unknown type="boolean">false</result_unknown> + <error_code></error_code> + <error_detail nil="true"/> + <cancelled type="boolean">false</cancelled> + <fraud_review nil="true"/> + <created_at type="dateTime">2018-02-24T00:47:56Z</created_at> + <updated_at type="dateTime">2018-02-24T00:47:56Z</updated_at> + </response> + <payment_method> + <token>9AjLflWs7SOKuqJLveOZya9bixa</token> + <created_at type="dateTime">2012-12-07T19:08:15Z</created_at> + <updated_at type="dateTime">2018-02-24T00:35:45Z</updated_at> + <email nil="true"/> + <data> + <how_many>2</how_many> + </data> + <storage_state>retained</storage_state> + <test type="boolean">true</test> + <last_four_digits>4444</last_four_digits> + <first_six_digits>555555</first_six_digits> + <card_type>master</card_type> + <first_name>Jim</first_name> + <last_name>TesterDude</last_name> + <month type="integer">9</month> + <year type="integer">2022</year> + <address1 nil="true"/> + <address2 nil="true"/> + <city nil="true"/> + <state nil="true"/> + <zip nil="true"/> + <country nil="true"/> + <phone_number nil="true"/> + <company nil="true"/> + <full_name>Jim TesterDude</full_name> + <eligible_for_card_updater nil="true"/> + <shipping_address1 nil="true"/> + <shipping_address2 nil="true"/> + <shipping_city nil="true"/> + <shipping_state nil="true"/> + <shipping_zip nil="true"/> + <shipping_country nil="true"/> + <shipping_phone_number nil="true"/> + <payment_method_type>credit_card</payment_method_type> + <errors> + </errors> + <verification_value></verification_value> + <number>XXXX-XXXX-XXXX-4444</number> + <fingerprint>125370bb396dff6fed4f581f85a91a9e5317</fingerprint> + </payment_method> + </transaction> + XML + end - def initialize(code, body, headers={}) - @code, @body, @headers = code, body, headers - end + def failed_verify_response + MockResponse.failed <<-XML + <transaction> + <on_test_gateway type="boolean">true</on_test_gateway> + <created_at type="dateTime">2018-02-24T00:53:58Z</created_at> + <updated_at type="dateTime">2018-02-24T00:53:58Z</updated_at> + <succeeded type="boolean">false</succeeded> + <state>gateway_processing_failed</state> + <token>RwmpyTCRmCpji1YtSD5f5fQDpkS</token> + <transaction_type>Verification</transaction_type> + <order_id nil="true"/> + <ip nil="true"/> + <description nil="true"/> + <email nil="true"/> + <merchant_name_descriptor nil="true"/> + <merchant_location_descriptor nil="true"/> + <gateway_specific_fields nil="true"/> + <gateway_specific_response_fields> + </gateway_specific_response_fields> + <gateway_transaction_id nil="true"/> + <gateway_latency_ms type="integer">24</gateway_latency_ms> + <currency_code>USD</currency_code> + <retain_on_success type="boolean">false</retain_on_success> + <payment_method_added type="boolean">false</payment_method_added> + <message>Unable to process the verify transaction.</message> + <gateway_token>3gLeg4726V5P0HK7cq7QzHsL0a6</gateway_token> + <gateway_type>test</gateway_type> + <shipping_address> + <name>Longbob Longsen</name> + <address1 nil="true"/> + <address2 nil="true"/> + <city nil="true"/> + <state nil="true"/> + <zip nil="true"/> + <country nil="true"/> + <phone_number nil="true"/> + </shipping_address> + <response> + <success type="boolean">false</success> + <message>Unable to process the verify transaction.</message> + <avs_code nil="true"/> + <avs_message nil="true"/> + <cvv_code nil="true"/> + <cvv_message nil="true"/> + <pending type="boolean">false</pending> + <result_unknown type="boolean">false</result_unknown> + <error_code></error_code> + <error_detail nil="true"/> + <cancelled type="boolean">false</cancelled> + <fraud_review nil="true"/> + <created_at type="dateTime">2018-02-24T00:53:58Z</created_at> + <updated_at type="dateTime">2018-02-24T00:53:58Z</updated_at> + </response> + <payment_method> + <token>UzUKWHwI7GtZe3gz1UU5FiZ6DxH</token> + <created_at type="dateTime">2018-02-24T00:53:56Z</created_at> + <updated_at type="dateTime">2018-02-24T00:53:56Z</updated_at> + <email nil="true"/> + <data nil="true"/> + <storage_state>cached</storage_state> + <test type="boolean">true</test> + <last_four_digits>1881</last_four_digits> + <first_six_digits>401288</first_six_digits> + <card_type>visa</card_type> + <first_name>Longbob</first_name> + <last_name>Longsen</last_name> + <month type="integer">9</month> + <year type="integer">2019</year> + <address1 nil="true"/> + <address2 nil="true"/> + <city nil="true"/> + <state nil="true"/> + <zip nil="true"/> + <country nil="true"/> + <phone_number nil="true"/> + <company nil="true"/> + <full_name>Longbob Longsen</full_name> + <eligible_for_card_updater nil="true"/> + <shipping_address1 nil="true"/> + <shipping_address2 nil="true"/> + <shipping_city nil="true"/> + <shipping_state nil="true"/> + <shipping_zip nil="true"/> + <shipping_country nil="true"/> + <shipping_phone_number nil="true"/> + <payment_method_type>credit_card</payment_method_type> + <errors> + </errors> + <verification_value>XXX</verification_value> + <number>XXXX-XXXX-XXXX-1881</number> + <fingerprint>db33a42fcf2908a3795bd4ea881de2e0f015</fingerprint> + </payment_method> + </transaction> + XML + end - def [](header) - @headers[header] - end + def successful_find_response + MockResponse.succeeded <<-XML + <transaction> + <token>LKA3RchoqYO0njAfhHVw60ohjrC</token> + <created_at type="dateTime">2012-12-07T19:03:50Z</created_at> + <updated_at type="dateTime">2012-12-07T19:03:50Z</updated_at> + <succeeded type="boolean">true</succeeded> + <transaction_type>AddPaymentMethod</transaction_type> + <retained type="boolean">false</retained> + <state>succeeded</state> + <message key="messages.transaction_succeeded">Succeeded!</message> + <payment_method> + <token>67KlSyyvBAt9VUMJg3lUeWbBaWX</token> + <created_at type="dateTime">2012-12-07T19:03:50Z</created_at> + <updated_at type="dateTime">2017-07-29T23:25:21Z</updated_at> + <email nil="true"/> + <data> + <how_many>2</how_many> + </data> + <storage_state>redacted</storage_state> + <test type="boolean">false</test> + <last_four_digits>4444</last_four_digits> + <first_six_digits nil="true"/> + <card_type>master</card_type> + <first_name>Jim</first_name> + <last_name>TesterDude</last_name> + <month type="integer">9</month> + <year type="integer">2022</year> + <address1 nil="true"/> + <address2 nil="true"/> + <city nil="true"/> + <state nil="true"/> + <zip nil="true"/> + <country nil="true"/> + <phone_number nil="true"/> + <company nil="true"/> + <full_name>Jim TesterDude</full_name> + <eligible_for_card_updater type="boolean">true</eligible_for_card_updater> + <shipping_address1 nil="true"/> + <shipping_address2 nil="true"/> + <shipping_city nil="true"/> + <shipping_state nil="true"/> + <shipping_zip nil="true"/> + <shipping_country nil="true"/> + <shipping_phone_number nil="true"/> + <payment_method_type>credit_card</payment_method_type> + <errors> + </errors> + <verification_value></verification_value> + <number></number> + <fingerprint nil="true"/> + </payment_method> + </transaction> + XML end + def failed_find_response + MockResponse.failed <<-XML + <errors> + <error key="errors.transaction_not_found">Unable to find the transaction AdyQXaG0SVpSoMPdmFlvd3aA3uz.</error> + </errors> + XML + end end diff --git a/test/unit/gateways/stripe_payment_intents_test.rb b/test/unit/gateways/stripe_payment_intents_test.rb new file mode 100644 index 00000000000..6245e7b494a --- /dev/null +++ b/test/unit/gateways/stripe_payment_intents_test.rb @@ -0,0 +1,266 @@ +require 'test_helper' + +class StripePaymentIntentsTest < Test::Unit::TestCase + include CommStub + + def setup + @gateway = StripePaymentIntentsGateway.new(:login => 'login') + + @credit_card = credit_card() + @threeds_2_card = credit_card('4000000000003220') + @visa_token = 'pm_card_visa' + @amount = 2020 + @update_amount = 2050 + + @options = { + currency: 'GBP', + confirmation_method: 'manual', + } + end + + def test_successful_create_and_confirm_intent + @gateway.expects(:ssl_request).times(3).returns(successful_create_3ds2_payment_method, successful_create_3ds2_intent_response, successful_confirm_3ds2_intent_response) + + assert create = @gateway.create_intent(@amount, @threeds_2_card, @options.merge(return_url: 'https://www.example.com', capture_method: 'manual')) + assert_instance_of Response, create + assert_success create + + assert_equal 'pi_1F1wpFAWOtgoysog8nTulYGk', create.authorization + assert_equal 'requires_confirmation', create.params['status'] + assert create.test? + + assert confirm = @gateway.confirm_intent(create.params['id'], nil, return_url: 'https://example.com/return-to-me') + assert_equal 'redirect_to_url', confirm.params.dig('next_action', 'type') + end + + def test_successful_create_and_capture_intent + options = @options.merge(capture_method: 'manual', confirm: true) + @gateway.expects(:ssl_request).twice.returns(successful_create_intent_response, successful_capture_response) + assert create = @gateway.create_intent(@amount, @visa_token, options) + assert_success create + assert_equal 'requires_capture', create.params['status'] + + assert capture = @gateway.capture(@amount, create.params['id'], options) + assert_success capture + assert_equal 'succeeded', capture.params['status'] + assert_equal 'Payment complete.', capture.params.dig('charges', 'data')[0].dig('outcome', 'seller_message') + end + + def test_successful_create_and_update_intent + @gateway.expects(:ssl_request).twice.returns(successful_create_intent_response, successful_update_intent_response) + assert create = @gateway.create_intent(@amount, @visa_token, @options.merge(capture_method: 'manual')) + + assert update = @gateway.update_intent(@update_amount, create.params['id'], nil, @options.merge(capture_method: 'manual')) + assert_equal @update_amount, update.params['amount'] + assert_equal 'requires_confirmation', update.params['status'] + end + + def test_successful_create_and_void_intent + @gateway.expects(:ssl_request).twice.returns(successful_create_intent_response, successful_void_response) + assert create = @gateway.create_intent(@amount, @visa_token, @options.merge(capture_method: 'manual', confirm: true)) + + assert cancel = @gateway.void(create.params['id']) + assert_equal @amount, cancel.params.dig('charges', 'data')[0].dig('amount_refunded') + assert_equal 'canceled', cancel.params['status'] + end + + def test_failed_capture_after_creation + @gateway.expects(:ssl_request).returns(failed_capture_response) + + assert create = @gateway.create_intent(@amount, 'pm_card_chargeDeclined', @options.merge(confirm: true)) + assert_equal 'requires_payment_method', create.params.dig('error', 'payment_intent', 'status') + assert_equal false, create.params.dig('error', 'payment_intent', 'charges', 'data')[0].dig('captured') + end + + def test_failed_void_after_capture + @gateway.expects(:ssl_request).twice.returns(successful_capture_response, failed_cancel_response) + assert create = @gateway.create_intent(@amount, @visa_token, @options.merge(confirm: true)) + assert_equal 'succeeded', create.params['status'] + intent_id = create.params['id'] + + assert cancel = @gateway.void(intent_id, cancellation_reason: 'requested_by_customer') + assert_equal 'You cannot cancel this PaymentIntent because ' \ + 'it has a status of succeeded. Only a PaymentIntent with ' \ + 'one of the following statuses may be canceled: ' \ + 'requires_payment_method, requires_capture, requires_confirmation, requires_action.', cancel.message + end + + private + + def successful_create_intent_response + <<-RESPONSE + {"id":"pi_1F1xauAWOtgoysogIfHO8jGi","object":"payment_intent","amount":2020,"amount_capturable":2020,"amount_received":0,"application":null,"application_fee_amount":null,"canceled_at":null,"cancellation_reason":null,"capture_method":"manual","charges":{"object":"list","data":[{"id":"ch_1F1xavAWOtgoysogxrtSiCu4","object":"charge","amount":2020,"amount_refunded":0,"application":null,"application_fee":null,"application_fee_amount":null,"balance_transaction":null,"billing_details":{"address":{"city":null,"country":null,"line1":null,"line2":null,"postal_code":null,"state":null},"email":null,"name":null,"phone":null},"captured":false,"created":1564501833,"currency":"gbp","customer":"cus_7s22nNueP2Hjj6","description":null,"destination":null,"dispute":null,"failure_code":null,"failure_message":null,"fraud_details":{},"invoice":null,"livemode":false,"metadata":{},"on_behalf_of":null,"order":null,"outcome":{"network_status":"approved_by_network","reason":null,"risk_level":"normal","risk_score":58,"seller_message":"Payment complete.","type":"authorized"},"paid":true,"payment_intent":"pi_1F1xauAWOtgoysogIfHO8jGi","payment_method":"pm_1F1xauAWOtgoysog00COoKIU","payment_method_details":{"card":{"brand":"visa","checks":{"address_line1_check":null,"address_postal_code_check":null,"cvc_check":null},"country":"US","exp_month":7,"exp_year":2020,"fingerprint":"hfaVNMiXc0dYSiC5","funding":"credit","last4":"4242","three_d_secure":null,"wallet":null},"type":"card"},"receipt_email":null,"receipt_number":null,"receipt_url":"https://pay.stripe.com/receipts/acct_160DX6AWOtgoysog/ch_1F1xavAWOtgoysogxrtSiCu4/rcpt_FX1eGdFRi8ssOY8Fqk4X6nEjNeGV5PG","refunded":false,"refunds":{"object":"list","data":[],"has_more":false,"total_count":0,"url":"/v1/charges/ch_1F1xavAWOtgoysogxrtSiCu4/refunds"},"review":null,"shipping":null,"source":null,"source_transfer":null,"statement_descriptor":null,"status":"succeeded","transfer_data":null,"transfer_group":null}],"has_more":false,"total_count":1,"url":"/v1/charges?payment_intent=pi_1F1xauAWOtgoysogIfHO8jGi"},"client_secret":"pi_1F1xauAWOtgoysogIfHO8jGi_secret_ZrXvfydFv0BelaMQJgHxjts5b","confirmation_method":"manual","created":1564501832,"currency":"gbp","customer":"cus_7s22nNueP2Hjj6","description":null,"invoice":null,"last_payment_error":null,"livemode":false,"metadata":{},"next_action":null,"on_behalf_of":null,"payment_method":"pm_1F1xauAWOtgoysog00COoKIU","payment_method_options":{"card":{"request_three_d_secure":"automatic"}},"payment_method_types":["card"],"receipt_email":null,"review":null,"setup_future_usage":null,"shipping":null,"source":null,"statement_descriptor":null,"status":"requires_capture","transfer_data":null,"transfer_group":null} + RESPONSE + end + + def successful_capture_response + <<-RESPONSE + {"id":"pi_1F1xauAWOtgoysogIfHO8jGi","object":"payment_intent","amount":2020,"amount_capturable":0,"amount_received":2020,"application":null,"application_fee_amount":null,"canceled_at":null,"cancellation_reason":null,"capture_method":"manual","charges":{"object":"list","data":[{"id":"ch_1F1xavAWOtgoysogxrtSiCu4","object":"charge","amount":2020,"amount_refunded":0,"application":null,"application_fee":null,"application_fee_amount":null,"balance_transaction":"txn_1F1xawAWOtgoysog27xGBjM6","billing_details":{"address":{"city":null,"country":null,"line1":null,"line2":null,"postal_code":null,"state":null},"email":null,"name":null,"phone":null},"captured":true,"created":1564501833,"currency":"gbp","customer":"cus_7s22nNueP2Hjj6","description":null,"destination":null,"dispute":null,"failure_code":null,"failure_message":null,"fraud_details":{},"invoice":null,"livemode":false,"metadata":{},"on_behalf_of":null,"order":null,"outcome":{"network_status":"approved_by_network","reason":null,"risk_level":"normal","risk_score":58,"seller_message":"Payment complete.","type":"authorized"},"paid":true,"payment_intent":"pi_1F1xauAWOtgoysogIfHO8jGi","payment_method":"pm_1F1xauAWOtgoysog00COoKIU","payment_method_details":{"card":{"brand":"visa","checks":{"address_line1_check":null,"address_postal_code_check":null,"cvc_check":null},"country":"US","exp_month":7,"exp_year":2020,"fingerprint":"hfaVNMiXc0dYSiC5","funding":"credit","last4":"4242","three_d_secure":null,"wallet":null},"type":"card"},"receipt_email":null,"receipt_number":null,"receipt_url":"https://pay.stripe.com/receipts/acct_160DX6AWOtgoysog/ch_1F1xavAWOtgoysogxrtSiCu4/rcpt_FX1eGdFRi8ssOY8Fqk4X6nEjNeGV5PG","refunded":false,"refunds":{"object":"list","data":[],"has_more":false,"total_count":0,"url":"/v1/charges/ch_1F1xavAWOtgoysogxrtSiCu4/refunds"},"review":null,"shipping":null,"source":null,"source_transfer":null,"statement_descriptor":null,"status":"succeeded","transfer_data":null,"transfer_group":null}],"has_more":false,"total_count":1,"url":"/v1/charges?payment_intent=pi_1F1xauAWOtgoysogIfHO8jGi"},"client_secret":"pi_1F1xauAWOtgoysogIfHO8jGi_secret_ZrXvfydFv0BelaMQJgHxjts5b","confirmation_method":"manual","created":1564501832,"currency":"gbp","customer":"cus_7s22nNueP2Hjj6","description":null,"invoice":null,"last_payment_error":null,"livemode":false,"metadata":{},"next_action":null,"on_behalf_of":null,"payment_method":"pm_1F1xauAWOtgoysog00COoKIU","payment_method_options":{"card":{"request_three_d_secure":"automatic"}},"payment_method_types":["card"],"receipt_email":null,"review":null,"setup_future_usage":null,"shipping":null,"source":null,"statement_descriptor":null,"status":"succeeded","transfer_data":null,"transfer_group":null} + RESPONSE + end + + def successful_void_response + <<-RESPONSE + {"id":"pi_1F1yBVAWOtgoysogearamRvl","object":"payment_intent","amount":2020,"amount_capturable":0,"amount_received":0,"application":null,"application_fee_amount":null,"canceled_at":1564504103,"cancellation_reason":"requested_by_customer","capture_method":"manual","charges":{"object":"list","data":[{"id":"ch_1F1yBWAWOtgoysog1MQfDpJH","object":"charge","amount":2020,"amount_refunded":2020,"application":null,"application_fee":null,"application_fee_amount":null,"balance_transaction":null,"billing_details":{"address":{"city":null,"country":null,"line1":null,"line2":null,"postal_code":null,"state":null},"email":null,"name":null,"phone":null},"captured":false,"created":1564504102,"currency":"gbp","customer":"cus_7s22nNueP2Hjj6","description":null,"destination":null,"dispute":null,"failure_code":null,"failure_message":null,"fraud_details":{},"invoice":null,"livemode":false,"metadata":{},"on_behalf_of":null,"order":null,"outcome":{"network_status":"approved_by_network","reason":null,"risk_level":"normal","risk_score":46,"seller_message":"Payment complete.","type":"authorized"},"paid":true,"payment_intent":"pi_1F1yBVAWOtgoysogearamRvl","payment_method":"pm_1F1yBVAWOtgoysogddy4E3hL","payment_method_details":{"card":{"brand":"visa","checks":{"address_line1_check":null,"address_postal_code_check":null,"cvc_check":null},"country":"US","exp_month":7,"exp_year":2020,"fingerprint":"hfaVNMiXc0dYSiC5","funding":"credit","last4":"4242","three_d_secure":null,"wallet":null},"type":"card"},"receipt_email":null,"receipt_number":null,"receipt_url":"https://pay.stripe.com/receipts/acct_160DX6AWOtgoysog/ch_1F1yBWAWOtgoysog1MQfDpJH/rcpt_FX2Go3YHBqAYQPJuKGMeab3nyCU0Kks","refunded":true,"refunds":{"object":"list","data":[{"id":"re_1F1yBXAWOtgoysog0PU371Yz","object":"refund","amount":2020,"balance_transaction":null,"charge":"ch_1F1yBWAWOtgoysog1MQfDpJH","created":1564504103,"currency":"gbp","metadata":{},"reason":"requested_by_customer","receipt_number":null,"source_transfer_reversal":null,"status":"succeeded","transfer_reversal":null}],"has_more":false,"total_count":1,"url":"/v1/charges/ch_1F1yBWAWOtgoysog1MQfDpJH/refunds"},"review":null,"shipping":null,"source":null,"source_transfer":null,"statement_descriptor":null,"status":"succeeded","transfer_data":null,"transfer_group":null}],"has_more":false,"total_count":1,"url":"/v1/charges?payment_intent=pi_1F1yBVAWOtgoysogearamRvl"},"client_secret":"pi_1F1yBVAWOtgoysogearamRvl_secret_oCnlR2t0GPclqACgHt2rst4gM","confirmation_method":"manual","created":1564504101,"currency":"gbp","customer":"cus_7s22nNueP2Hjj6","description":null,"invoice":null,"last_payment_error":null,"livemode":false,"metadata":{},"next_action":null,"on_behalf_of":null,"payment_method":"pm_1F1yBVAWOtgoysogddy4E3hL","payment_method_options":{"card":{"request_three_d_secure":"automatic"}},"payment_method_types":["card"],"receipt_email":null,"review":null,"setup_future_usage":null,"shipping":null,"source":null,"statement_descriptor":null,"status":"canceled","transfer_data":null,"transfer_group":null} + RESPONSE + end + + def successful_update_intent_response + <<-RESPONSE + {"id":"pi_1F1yBbAWOtgoysog52J88BuO","object":"payment_intent","amount":2050,"amount_capturable":0,"amount_received":0,"application":null,"application_fee_amount":null,"canceled_at":null,"cancellation_reason":null,"capture_method":"manual","charges":{"object":"list","data":[],"has_more":false,"total_count":0,"url":"/v1/charges?payment_intent=pi_1F1yBbAWOtgoysog52J88BuO"},"client_secret":"pi_1F1yBbAWOtgoysog52J88BuO_secret_olw5rmbtm7cd72S9JfbKjTJJv","confirmation_method":"manual","created":1564504107,"currency":"gbp","customer":"cus_7s22nNueP2Hjj6","description":null,"invoice":null,"last_payment_error":null,"livemode":false,"metadata":{},"next_action":null,"on_behalf_of":null,"payment_method":"pm_1F1yBbAWOtgoysoguJQsDdYj","payment_method_options":{"card":{"request_three_d_secure":"automatic"}},"payment_method_types":["card"],"receipt_email":null,"review":null,"setup_future_usage":null,"shipping":null,"source":null,"statement_descriptor":null,"status":"requires_confirmation","transfer_data":null,"transfer_group":null} + RESPONSE + end + + def successful_create_3ds2_payment_method + <<-RESPONSE + { + "id": "pm_1F1xK0AWOtgoysogfPuRKN1d", + "object": "payment_method", + "billing_details": { + "address": {"city": null, + "country": null, + "line1": null, + "line2": null, + "postal_code": null, + "state": null}, + "email": null, + "name": null, + "phone": null}, + "card": { + "brand": "visa", + "checks": {"address_line1_check": null, + "address_postal_code_check": null, + "cvc_check": "unchecked"}, + "country": null, + "exp_month": 10, + "exp_year": 2020, + "fingerprint": "l3J0NJaGgv0jAGLV", + "funding": "credit", + "generated_from": null, + "last4": "3220", + "three_d_secure_usage": {"supported": true}, + "wallet": null}, + "created": 1564500784, + "customer": null, + "livemode": false, + "metadata": {}, + "type": "card" + } + RESPONSE + end + + def successful_create_3ds2_intent_response + <<-RESPONSE + { + "id": "pi_1F1wpFAWOtgoysog8nTulYGk", + "object": "payment_intent", + "amount": 2020, + "amount_capturable": 0, + "amount_received": 0, + "application": null, + "application_fee_amount": null, + "canceled_at": null, + "cancellation_reason": null, + "capture_method": "manual", + "charges": { + "object": "list", + "data": [], + "has_more": false, + "total_count": 0, + "url": "/v1/charges?payment_intent=pi_1F1wpFAWOtgoysog8nTulYGk" + }, + "client_secret": "pi_1F1wpFAWOtgoysog8nTulYGk_secret_75qf7rjBDsTTz279LfS1feXUj", + "confirmation_method": "manual", + "created": 1564498877, + "currency": "gbp", + "customer": "cus_7s22nNueP2Hjj6", + "description": null, + "invoice": null, + "last_payment_error": null, + "livemode": false, + "metadata": {}, + "next_action": null, + "on_behalf_of": null, + "payment_method": "pm_1F1wpFAWOtgoysogJ8zQ8K07", + "payment_method_options": { + "card": {"request_three_d_secure": "automatic"} + }, + "payment_method_types": ["card"], + "receipt_email": null, + "review": null, + "setup_future_usage": null, + "shipping": null, + "source": null, + "statement_descriptor": null, + "status": "requires_confirmation", + "transfer_data": null, + "transfer_group": null + } + RESPONSE + end + + def successful_confirm_3ds2_intent_response + <<-RESPONSE + { + "id": "pi_1F1wpFAWOtgoysog8nTulYGk", + "object": "payment_intent", + "amount": 2020, + "amount_capturable": 0, + "amount_received": 0, + "application": null, + "application_fee_amount": null, + "canceled_at": null, + "cancellation_reason": null, + "capture_method": "manual", + "charges": { + "object": "list", + "data": [], + "has_more": false, + "total_count": 0, + "url": "/v1/charges?payment_intent=pi_1F1wpFAWOtgoysog8nTulYGk"}, + "client_secret": "pi_1F1wpFAWOtgoysog8nTulYGk_secret_75qf7rjBDsTTz279LfS1feXUj", + "confirmation_method": "manual", + "created": 1564498877, + "currency": "gbp", + "customer": "cus_7s22nNueP2Hjj6", + "description": null, + "invoice": null, + "last_payment_error": null, + "livemode": false, + "metadata": {}, + "next_action": { + "redirect_to_url": { + "return_url": "https://example.com/return-to-me", + "url": "https://hooks.stripe.com/3d_secure_2_eap/begin_test/src_1F1wpGAWOtgoysog4f00umCp/src_client_secret_FX0qk3uQ04woFWgdJbN3pnHD"}, + "type": "redirect_to_url"}, + "on_behalf_of": null, + "payment_method": "pm_1F1wpFAWOtgoysogJ8zQ8K07", + "payment_method_options": { + "card": {"request_three_d_secure": "automatic"} + }, + "payment_method_types": ["card"], + "receipt_email": null, + "review": null, + "setup_future_usage": null, + "shipping": null, + "source": null, + "statement_descriptor": null, + "status": "requires_action", + "transfer_data": null, + "transfer_group": null + } + RESPONSE + end + + def failed_capture_response + <<-RESPONSE + {"error":{"charge":"ch_1F2MB6AWOtgoysogAIvNV32Z","code":"card_declined","decline_code":"generic_decline","doc_url":"https://stripe.com/docs/error-codes/card-declined","message":"Your card was declined.","payment_intent":{"id":"pi_1F2MB5AWOtgoysogCMt8BaxR","object":"payment_intent","amount":2020,"amount_capturable":0,"amount_received":0,"application":null,"application_fee_amount":null,"canceled_at":null,"cancellation_reason":null,"capture_method":"automatic","charges":{"object":"list","data":[{"id":"ch_1F2MB6AWOtgoysogAIvNV32Z","object":"charge","amount":2020,"amount_refunded":0,"application":null,"application_fee":null,"application_fee_amount":null,"balance_transaction":null,"billing_details":{"address":{"city":null,"country":null,"line1":null,"line2":null,"postal_code":null,"state":null},"email":null,"name":null,"phone":null},"captured":false,"created":1564596332,"currency":"gbp","customer":"cus_7s22nNueP2Hjj6","description":null,"destination":null,"dispute":null,"failure_code":"card_declined","failure_message":"Your card was declined.","fraud_details":{},"invoice":null,"livemode":false,"metadata":{},"on_behalf_of":null,"order":null,"outcome":{"network_status":"declined_by_network","reason":"generic_decline","risk_level":"normal","risk_score":41,"seller_message":"The bank did not return any further details with this decline.","type":"issuer_declined"},"paid":false,"payment_intent":"pi_1F2MB5AWOtgoysogCMt8BaxR","payment_method":"pm_1F2MB5AWOtgoysogq3yXZ98h","payment_method_details":{"card":{"brand":"visa","checks":{"address_line1_check":null,"address_postal_code_check":null,"cvc_check":null},"country":"US","exp_month":7,"exp_year":2020,"fingerprint":"1VUoWMvHnqtngyrD","funding":"credit","last4":"0002","three_d_secure":null,"wallet":null},"type":"card"},"receipt_email":null,"receipt_number":null,"receipt_url":"https://pay.stripe.com/receipts/acct_160DX6AWOtgoysog/ch_1F2MB6AWOtgoysogAIvNV32Z/rcpt_FXR3PjBGluHmHsnLmp0S2KQiHl3yg6W","refunded":false,"refunds":{"object":"list","data":[],"has_more":false,"total_count":0,"url":"/v1/charges/ch_1F2MB6AWOtgoysogAIvNV32Z/refunds"},"review":null,"shipping":null,"source":null,"source_transfer":null,"statement_descriptor":null,"status":"failed","transfer_data":null,"transfer_group":null}],"has_more":false,"total_count":1,"url":"/v1/charges?payment_intent=pi_1F2MB5AWOtgoysogCMt8BaxR"},"client_secret":"pi_1F2MB5AWOtgoysogCMt8BaxR_secret_fOHryjtjBE4gACiHTcREraXSQ","confirmation_method":"manual","created":1564596331,"currency":"gbp","customer":"cus_7s22nNueP2Hjj6","description":null,"invoice":null,"last_payment_error":{"charge":"ch_1F2MB6AWOtgoysogAIvNV32Z","code":"card_declined","decline_code":"generic_decline","doc_url":"https://stripe.com/docs/error-codes/card-declined","message":"Your card was declined.","payment_method":{"id":"pm_1F2MB5AWOtgoysogq3yXZ98h","object":"payment_method","billing_details":{"address":{"city":null,"country":null,"line1":null,"line2":null,"postal_code":null,"state":null},"email":null,"name":null,"phone":null},"card":{"brand":"visa","checks":{"address_line1_check":null,"address_postal_code_check":null,"cvc_check":null},"country":"US","exp_month":7,"exp_year":2020,"fingerprint":"1VUoWMvHnqtngyrD","funding":"credit","generated_from":null,"last4":"0002","three_d_secure_usage":{"supported":true},"wallet":null},"created":1564596331,"customer":null,"livemode":false,"metadata":{},"type":"card"},"type":"card_error"},"livemode":false,"metadata":{},"next_action":null,"on_behalf_of":null,"payment_method":null,"payment_method_options":{"card":{"request_three_d_secure":"automatic"}},"payment_method_types":["card"],"receipt_email":null,"review":null,"setup_future_usage":null,"shipping":null,"source":null,"statement_descriptor":null,"status":"requires_payment_method","transfer_data":null,"transfer_group":null},"payment_method":{"id":"pm_1F2MB5AWOtgoysogq3yXZ98h","object":"payment_method","billing_details":{"address":{"city":null,"country":null,"line1":null,"line2":null,"postal_code":null,"state":null},"email":null,"name":null,"phone":null},"card":{"brand":"visa","checks":{"address_line1_check":null,"address_postal_code_check":null,"cvc_check":null},"country":"US","exp_month":7,"exp_year":2020,"fingerprint":"1VUoWMvHnqtngyrD","funding":"credit","generated_from":null,"last4":"0002","three_d_secure_usage":{"supported":true},"wallet":null},"created":1564596331,"customer":null,"livemode":false,"metadata":{},"type":"card"},"type":"card_error"}} + RESPONSE + end + + def failed_cancel_response + <<-RESPONSE + {"error":{"code":"payment_intent_unexpected_state","doc_url":"https://stripe.com/docs/error-codes/payment-intent-unexpected-state","message":"You cannot cancel this PaymentIntent because it has a status of succeeded. Only a PaymentIntent with one of the following statuses may be canceled: requires_payment_method, requires_capture, requires_confirmation, requires_action.","payment_intent":{"id":"pi_1F2McmAWOtgoysoglFLDRWab","object":"payment_intent","amount":2020,"amount_capturable":0,"amount_received":2020,"application":null,"application_fee_amount":null,"canceled_at":null,"cancellation_reason":null,"capture_method":"automatic","charges":{"object":"list","data":[{"id":"ch_1F2McmAWOtgoysogQgUS1YtH","object":"charge","amount":2020,"amount_refunded":0,"application":null,"application_fee":null,"application_fee_amount":null,"balance_transaction":"txn_1F2McmAWOtgoysog8uxBEJ30","billing_details":{"address":{"city":null,"country":null,"line1":null,"line2":null,"postal_code":null,"state":null},"email":null,"name":null,"phone":null},"captured":true,"created":1564598048,"currency":"gbp","customer":"cus_7s22nNueP2Hjj6","description":null,"destination":null,"dispute":null,"failure_code":null,"failure_message":null,"fraud_details":{},"invoice":null,"livemode":false,"metadata":{},"on_behalf_of":null,"order":null,"outcome":{"network_status":"approved_by_network","reason":null,"risk_level":"normal","risk_score":53,"seller_message":"Payment complete.","type":"authorized"},"paid":true,"payment_intent":"pi_1F2McmAWOtgoysoglFLDRWab","payment_method":"pm_1F2MclAWOtgoysogq80GBBMO","payment_method_details":{"card":{"brand":"visa","checks":{"address_line1_check":null,"address_postal_code_check":null,"cvc_check":null},"country":"US","exp_month":7,"exp_year":2020,"fingerprint":"hfaVNMiXc0dYSiC5","funding":"credit","last4":"4242","three_d_secure":null,"wallet":null},"type":"card"},"receipt_email":null,"receipt_number":null,"receipt_url":"https://pay.stripe.com/receipts/acct_160DX6AWOtgoysog/ch_1F2McmAWOtgoysogQgUS1YtH/rcpt_FXRVzyFnf7aCS1r13N3uym1u8AaboOJ","refunded":false,"refunds":{"object":"list","data":[],"has_more":false,"total_count":0,"url":"/v1/charges/ch_1F2McmAWOtgoysogQgUS1YtH/refunds"},"review":null,"shipping":null,"source":null,"source_transfer":null,"statement_descriptor":null,"status":"succeeded","transfer_data":null,"transfer_group":null}],"has_more":false,"total_count":1,"url":"/v1/charges?payment_intent=pi_1F2McmAWOtgoysoglFLDRWab"},"client_secret":"pi_1F2McmAWOtgoysoglFLDRWab_secret_z4faDF0Cv0JZJ6pxK3bdIodkD","confirmation_method":"manual","created":1564598048,"currency":"gbp","customer":"cus_7s22nNueP2Hjj6","description":null,"invoice":null,"last_payment_error":null,"livemode":false,"metadata":{},"next_action":null,"on_behalf_of":null,"payment_method":"pm_1F2MclAWOtgoysogq80GBBMO","payment_method_options":{"card":{"request_three_d_secure":"automatic"}},"payment_method_types":["card"],"receipt_email":null,"review":null,"setup_future_usage":null,"shipping":null,"source":null,"statement_descriptor":null,"status":"succeeded","transfer_data":null,"transfer_group":null},"type":"invalid_request_error"}} + RESPONSE + end +end diff --git a/test/unit/gateways/stripe_test.rb b/test/unit/gateways/stripe_test.rb index bf579bd4883..0fb2d96ff1b 100644 --- a/test/unit/gateways/stripe_test.rb +++ b/test/unit/gateways/stripe_test.rb @@ -7,426 +7,2309 @@ def setup @gateway = StripeGateway.new(:login => 'login') @credit_card = credit_card() + @threeds_card = credit_card('4000000000003063') + @non_3ds_card = credit_card('378282246310005') @amount = 400 @refund_amount = 200 @options = { :billing_address => address(), + :statement_address => statement_address(), :description => 'Test Purchase' } + + @threeds_options = { + :execute_threed => true, + :callback_url => 'http://www.example.com/callback' + } + + @apple_pay_payment_token = apple_pay_payment_token + @emv_credit_card = credit_card_with_icc_data + @payment_token = StripeGateway::StripePaymentToken.new(token_params) + @token_string = @payment_token.payment_data['id'] + + @check = check({ + bank_name: 'STRIPE TEST BANK', + account_number: '000123456789', + routing_number: '110000000', + }) end - def test_successful_authorization - @gateway.expects(:ssl_request).returns(successful_authorization_response) + def test_successful_new_customer_with_card + @gateway.expects(:ssl_request).returns(successful_new_customer_response) + @gateway.expects(:add_creditcard) - assert response = @gateway.authorize(@amount, @credit_card, @options) + assert response = @gateway.store(@credit_card, @options) assert_instance_of Response, response assert_success response - assert_equal 'ch_test_charge', response.authorization + assert_equal 'cus_3sgheFxeBgTQ3M|card_483etw4er9fg4vF3sQdrt3FG', response.authorization assert response.test? end - def test_successful_capture - @gateway.expects(:ssl_request).returns(successful_capture_response) + def test_successful_new_customer_with_apple_pay_payment_token + @gateway.expects(:ssl_request).returns(successful_new_customer_response) + @gateway.expects(:tokenize_apple_pay_token).returns(Response.new(true, nil, token: successful_apple_pay_token_exchange)) - assert response = @gateway.capture(@amount, "ch_test_charge") + assert response = @gateway.store(@apple_pay_payment_token, @options) + assert_instance_of Response, response assert_success response + + assert_equal 'cus_3sgheFxeBgTQ3M|card_483etw4er9fg4vF3sQdrt3FG', response.authorization assert response.test? end - def test_successful_purchase - @gateway.expects(:ssl_request).returns(successful_purchase_response) + def test_successful_new_customer_with_emv_credit_card + @gateway.expects(:ssl_request).returns(successful_new_customer_response) - assert response = @gateway.purchase(@amount, @credit_card, @options) + assert response = @gateway.store(@emv_credit_card, @options) assert_instance_of Response, response assert_success response - # Replace with authorization number from the successful response - assert_equal 'ch_test_charge', response.authorization + assert_equal 'cus_3sgheFxeBgTQ3M|card_483etw4er9fg4vF3sQdrt3FG', response.authorization assert response.test? end - def test_successful_void - @gateway.expects(:ssl_request).returns(successful_purchase_response(true)) + def test_successful_new_customer_with_bank_account + @gateway.expects(:ssl_request).twice.returns(successful_bank_token_request, successful_new_customer_bank_account_response) - assert response = @gateway.void('ch_test_charge') - assert_instance_of Response, response + response = @gateway.store(@check, @options) assert_success response + assert_equal 'cus_7s6levMt8IqhTR|ba_17cMXgAWOtgoysog7UDWXbn4', response.authorization + end - # Replace with authorization number from the successful response - assert_equal 'ch_test_charge', response.authorization + def test_successful_new_card + @gateway.expects(:ssl_request).returns(successful_new_card_response) + @gateway.expects(:add_creditcard) + + assert response = @gateway.store(@credit_card, :customer => 'cus_3sgheFxeBgTQ3M') + assert_instance_of MultiResponse, response + assert_success response + + assert_equal 'cus_3sgheFxeBgTQ3M|card_483etw4er9fg4vF3sQdrt3FG', response.authorization assert response.test? end - def test_successful_refund - @gateway.expects(:ssl_request).returns(successful_partially_refunded_response) + def test_successful_new_card_via_apple_pay_payment_token + @gateway.expects(:ssl_request).returns(successful_new_card_response) + @gateway.expects(:tokenize_apple_pay_token).returns(Response.new(true, nil, token: successful_apple_pay_token_exchange)) - assert response = @gateway.refund(@refund_amount, 'ch_test_charge') + assert response = @gateway.store(@apple_pay_payment_token, :customer => 'cus_3sgheFxeBgTQ3M') + assert_instance_of MultiResponse, response assert_success response - # Replace with authorization number from the successful response - assert_equal 'ch_test_charge', response.authorization + assert_equal 'cus_3sgheFxeBgTQ3M|card_483etw4er9fg4vF3sQdrt3FG', response.authorization assert response.test? end - def test_unsuccessful_refund - @gateway.expects(:ssl_request).returns(generic_error_response) + def test_successful_new_card_with_emv_credit_card + @gateway.expects(:ssl_request).returns(successful_new_card_response) + @gateway.expects(:add_creditcard) - assert response = @gateway.refund(@refund_amount, 'ch_test_charge') - assert_failure response + assert response = @gateway.store(@emv_credit_card, :customer => 'cus_3sgheFxeBgTQ3M') + assert_instance_of MultiResponse, response + assert_success response + + assert_equal 'cus_3sgheFxeBgTQ3M|card_483etw4er9fg4vF3sQdrt3FG', response.authorization + assert response.test? end - def test_successful_refund_with_refund_fee_amount - s = sequence("request") - @gateway.expects(:ssl_request).returns(successful_partially_refunded_response).in_sequence(s) - @gateway.expects(:ssl_request).returns(successful_application_fee_list_response).in_sequence(s) - @gateway.expects(:ssl_request).returns(successful_refunded_application_fee_response).in_sequence(s) + def test_successful_new_card_with_token_string + @gateway.expects(:ssl_request).returns(successful_new_card_response) + @gateway.expects(:add_creditcard) - assert response = @gateway.refund(@refund_amount, 'ch_test_charge', :refund_fee_amount => 100) + assert response = @gateway.store(@token_string, :customer => 'cus_3sgheFxeBgTQ3M') + assert_instance_of MultiResponse, response assert_success response + + assert_equal 'cus_3sgheFxeBgTQ3M|card_483etw4er9fg4vF3sQdrt3FG', response.authorization + assert response.test? end - def test_refund_with_fee_response_gives_a_charge_authorization - s = sequence("request") - @gateway.expects(:ssl_request).returns(successful_partially_refunded_response).in_sequence(s) - @gateway.expects(:ssl_request).returns(successful_application_fee_list_response).in_sequence(s) - @gateway.expects(:ssl_request).returns(successful_refunded_application_fee_response).in_sequence(s) + def test_successful_new_card_with_payment_token + @gateway.expects(:ssl_request).returns(successful_new_card_response) + @gateway.expects(:add_payment_token) - assert response = @gateway.refund(@refund_amount, 'ch_test_charge', :refund_fee_amount => 100) + assert response = @gateway.store(@payment_token, :customer => 'cus_3sgheFxeBgTQ3M') + assert_instance_of MultiResponse, response assert_success response - assert_equal 'ch_test_charge', response.authorization + + assert_equal 'cus_3sgheFxeBgTQ3M|card_483etw4er9fg4vF3sQdrt3FG', response.authorization + assert response.test? end - def test_unsuccessful_refund_with_refund_fee_amount_when_application_fee_id_not_found - s = sequence("request") - @gateway.expects(:ssl_request).returns(successful_partially_refunded_response).in_sequence(s) - @gateway.expects(:ssl_request).returns(unsuccessful_application_fee_list_response).in_sequence(s) + def test_successful_new_card_and_customer_update + @gateway.expects(:ssl_request).twice.returns(successful_new_card_response, successful_new_customer_response) + @gateway.expects(:add_creditcard) - assert response = @gateway.refund(@refund_amount, 'ch_test_charge', :refund_fee_amount => 100) - assert_failure response - assert_match(/^Application fee id could not be found/, response.message) + assert response = @gateway.store(@credit_card, :customer => 'cus_3sgheFxeBgTQ3M', :email => 'test@test.com') + assert_instance_of MultiResponse, response + assert_success response + + assert_equal 'cus_3sgheFxeBgTQ3M|card_483etw4er9fg4vF3sQdrt3FG', response.authorization + assert_equal 2, response.responses.size + assert_equal 'cus_3sgheFxeBgTQ3M|card_483etw4er9fg4vF3sQdrt3FG', response.responses[0].authorization + assert_equal 'cus_3sgheFxeBgTQ3M', response.responses[1].authorization + assert response.test? end - def test_unsuccessful_refund_with_refund_fee_amount_when_refunding_application_fee - s = sequence("request") - @gateway.expects(:ssl_request).returns(successful_partially_refunded_response).in_sequence(s) - @gateway.expects(:ssl_request).returns(successful_application_fee_list_response).in_sequence(s) - @gateway.expects(:ssl_request).returns(generic_error_response).in_sequence(s) + def test_successful_new_card_and_customer_update_via_apple_pay_payment_token + @gateway.expects(:ssl_request).twice.returns(successful_new_card_response, successful_new_customer_response) + @gateway.expects(:tokenize_apple_pay_token).returns(Response.new(true, nil, token: successful_apple_pay_token_exchange)) - assert response = @gateway.refund(@refund_amount, 'ch_test_charge', :refund_fee_amount => 100) - assert_failure response + assert response = @gateway.store(@apple_pay_payment_token, :customer => 'cus_3sgheFxeBgTQ3M', :email => 'test@test.com') + assert_instance_of MultiResponse, response + assert_success response + + assert_equal 'cus_3sgheFxeBgTQ3M|card_483etw4er9fg4vF3sQdrt3FG', response.authorization + assert_equal 2, response.responses.size + assert_equal 'cus_3sgheFxeBgTQ3M|card_483etw4er9fg4vF3sQdrt3FG', response.responses[0].authorization + assert_equal 'cus_3sgheFxeBgTQ3M', response.responses[1].authorization + assert response.test? end - def test_successful_request_always_uses_live_mode_to_determine_test_request - @gateway.expects(:ssl_request).returns(successful_partially_refunded_response(:livemode => true)) + def test_successful_new_card_and_customer_update_with_emv_credit_card + @gateway.expects(:ssl_request).twice.returns(successful_new_card_response, successful_new_customer_response) - assert response = @gateway.refund(@refund_amount, 'ch_test_charge') + assert response = @gateway.store(@emv_credit_card, :customer => 'cus_3sgheFxeBgTQ3M', :email => 'test@test.com') + assert_instance_of MultiResponse, response assert_success response - assert !response.test? + assert_equal 'cus_3sgheFxeBgTQ3M|card_483etw4er9fg4vF3sQdrt3FG', response.authorization + assert_equal 2, response.responses.size + assert_equal 'cus_3sgheFxeBgTQ3M|card_483etw4er9fg4vF3sQdrt3FG', response.responses[0].authorization + assert_equal 'cus_3sgheFxeBgTQ3M', response.responses[1].authorization + assert response.test? end - def test_unsuccessful_request - @gateway.expects(:ssl_request).returns(failed_purchase_response) + def test_successful_new_card_and_customer_update_with_token_string + @gateway.expects(:ssl_request).twice.returns(successful_new_card_response, successful_new_customer_response) - assert response = @gateway.purchase(@amount, @credit_card, @options) - assert_failure response - # unsuccessful request defaults to live - assert !response.test? + assert response = @gateway.store(@token_string, :customer => 'cus_3sgheFxeBgTQ3M', :email => 'test@test.com') + assert_instance_of MultiResponse, response + assert_success response + + assert_equal 'cus_3sgheFxeBgTQ3M|card_483etw4er9fg4vF3sQdrt3FG', response.authorization + assert_equal 2, response.responses.size + assert_equal 'cus_3sgheFxeBgTQ3M|card_483etw4er9fg4vF3sQdrt3FG', response.responses[0].authorization + assert_equal 'cus_3sgheFxeBgTQ3M', response.responses[1].authorization + assert response.test? end - def test_invalid_raw_response - @gateway.expects(:ssl_request).returns(invalid_json_response) + def test_successful_new_card_and_customer_update_with_payment_token + @gateway.expects(:ssl_request).twice.returns(successful_new_card_response, successful_new_customer_response) - assert response = @gateway.purchase(@amount, @credit_card, @options) - assert_failure response - assert_match(/^Invalid response received from the Stripe API/, response.message) + assert response = @gateway.store(@payment_token, :customer => 'cus_3sgheFxeBgTQ3M', :email => 'test@test.com') + assert_instance_of MultiResponse, response + assert_success response + + assert_equal 'cus_3sgheFxeBgTQ3M|card_483etw4er9fg4vF3sQdrt3FG', response.authorization + assert_equal 2, response.responses.size + assert_equal 'cus_3sgheFxeBgTQ3M|card_483etw4er9fg4vF3sQdrt3FG', response.responses[0].authorization + assert_equal 'cus_3sgheFxeBgTQ3M', response.responses[1].authorization + assert response.test? end - def test_add_customer - post = {} - @gateway.send(:add_customer, post, {:customer => "test_customer"}) - assert_equal "test_customer", post[:customer] + def test_successful_new_default_card + @gateway.expects(:ssl_request).twice.returns(successful_new_card_response, successful_new_customer_response) + @gateway.expects(:add_creditcard) + + assert response = @gateway.store(@credit_card, @options.merge(:customer => 'cus_3sgheFxeBgTQ3M', :set_default => true)) + assert_instance_of MultiResponse, response + assert_success response + + assert_equal 'cus_3sgheFxeBgTQ3M|card_483etw4er9fg4vF3sQdrt3FG', response.authorization + assert_equal 2, response.responses.size + assert_equal 'cus_3sgheFxeBgTQ3M|card_483etw4er9fg4vF3sQdrt3FG', response.responses[0].authorization + assert_equal 'cus_3sgheFxeBgTQ3M', response.responses[1].authorization + assert response.test? end - def test_doesnt_add_customer_if_card - post = { :card => 'foo' } - @gateway.send(:add_customer, post, {:customer => "test_customer"}) - assert !post[:customer] + def test_successful_new_default_card_via_apple_pay_payment_token + @gateway.expects(:ssl_request).twice.returns(successful_new_card_response, successful_new_customer_response) + @gateway.expects(:tokenize_apple_pay_token).returns(Response.new(true, nil, token: successful_apple_pay_token_exchange)) + + assert response = @gateway.store(@apple_pay_payment_token, @options.merge(:customer => 'cus_3sgheFxeBgTQ3M', :set_default => true)) + assert_instance_of MultiResponse, response + assert_success response + + assert_equal 'cus_3sgheFxeBgTQ3M|card_483etw4er9fg4vF3sQdrt3FG', response.authorization + assert_equal 'cus_3sgheFxeBgTQ3M|card_483etw4er9fg4vF3sQdrt3FG', response.responses[0].authorization + assert_equal 2, response.responses.size + assert_equal 'cus_3sgheFxeBgTQ3M', response.responses[1].authorization + assert response.test? end - def test_application_fee_is_submitted_for_purchase - stub_comms(:ssl_request) do - @gateway.purchase(@amount, @credit_card, @options.merge({:application_fee => 144})) - end.check_request do |method, endpoint, data, headers| - assert_match(/application_fee=144/, data) - end.respond_with(successful_purchase_response) + def test_successful_new_default_card_with_emv_credit_card + @gateway.expects(:ssl_request).twice.returns(successful_new_card_response, successful_new_customer_response) + + assert response = @gateway.store(@emv_credit_card, @options.merge(:customer => 'cus_3sgheFxeBgTQ3M', :set_default => true)) + assert_instance_of MultiResponse, response + assert_success response + + assert_equal 'cus_3sgheFxeBgTQ3M|card_483etw4er9fg4vF3sQdrt3FG', response.authorization + assert_equal 2, response.responses.size + assert_equal 'cus_3sgheFxeBgTQ3M|card_483etw4er9fg4vF3sQdrt3FG', response.responses[0].authorization + assert_equal 'cus_3sgheFxeBgTQ3M', response.responses[1].authorization + assert response.test? end - def test_application_fee_is_submitted_for_capture - stub_comms(:ssl_request) do - @gateway.capture(@amount, "ch_test_charge", @options.merge({:application_fee => 144})) + def test_successful_new_default_card_with_token_string + @gateway.expects(:ssl_request).twice.returns(successful_new_card_response, successful_new_customer_response) + @gateway.expects(:add_creditcard) + + assert response = @gateway.store(@token_string, @options.merge(:customer => 'cus_3sgheFxeBgTQ3M', :set_default => true)) + assert_instance_of MultiResponse, response + assert_success response + + assert_equal 'cus_3sgheFxeBgTQ3M|card_483etw4er9fg4vF3sQdrt3FG', response.authorization + assert_equal 2, response.responses.size + assert_equal 'cus_3sgheFxeBgTQ3M|card_483etw4er9fg4vF3sQdrt3FG', response.responses[0].authorization + assert_equal 'cus_3sgheFxeBgTQ3M', response.responses[1].authorization + assert response.test? + end + + def test_successful_new_default_card_with_payment_token + @gateway.expects(:ssl_request).twice.returns(successful_new_card_response, successful_new_customer_response) + @gateway.expects(:add_payment_token) + + assert response = @gateway.store(@payment_token, @options.merge(:customer => 'cus_3sgheFxeBgTQ3M', :set_default => true)) + assert_instance_of MultiResponse, response + assert_success response + + assert_equal 'cus_3sgheFxeBgTQ3M|card_483etw4er9fg4vF3sQdrt3FG', response.authorization + assert_equal 2, response.responses.size + assert_equal 'cus_3sgheFxeBgTQ3M|card_483etw4er9fg4vF3sQdrt3FG', response.responses[0].authorization + assert_equal 'cus_3sgheFxeBgTQ3M', response.responses[1].authorization + assert response.test? + end + + def test_passing_validate_false_on_store + response = stub_comms(@gateway, :ssl_request) do + @gateway.store(@credit_card, validate: false) end.check_request do |method, endpoint, data, headers| - assert_match(/application_fee=144/, data) - end.respond_with(successful_capture_response) + assert_match(/validate=false/, data) + end.respond_with(successful_new_customer_response) + + assert_success response end - def test_client_data_submitted_with_purchase - stub_comms(:ssl_request) do - updated_options = @options.merge({:description => "a test customer",:browser_ip => "127.127.127.127", :user_agent => "some browser", :order_id => "42", :email => "foo@wonderfullyfakedomain.com", :referrer =>"http://www.shopify.com"}) - @gateway.purchase(@amount,@credit_card,updated_options) + def test_empty_values_not_sent + response = stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, @credit_card, referrer: '') end.check_request do |method, endpoint, data, headers| - assert_match(/description=a\+test\+customer/, data) - assert_match(/ip=127\.127\.127\.127/, data) - assert_match(/user_agent=some\+browser/, data) - assert_match(/external_id=42/, data) - assert_match(/referrer=http\%3A\%2F\%2Fwww\.shopify\.com/, data) - assert_match(/payment_user_agent=Stripe\%2Fv1\+ActiveMerchantBindings\%2F\d+\.\d+\.\d+/, data) + refute_match(/referrer/, data) end.respond_with(successful_purchase_response) - end - def test_add_address - post = {:card => {}} - @gateway.send(:add_address, post, @options) - assert_equal @options[:billing_address][:zip], post[:card][:address_zip] - assert_equal @options[:billing_address][:state], post[:card][:address_state] - assert_equal @options[:billing_address][:address1], post[:card][:address_line1] - assert_equal @options[:billing_address][:address2], post[:card][:address_line2] - assert_equal @options[:billing_address][:country], post[:card][:address_country] - assert_equal @options[:billing_address][:city], post[:card][:address_city] + assert_success response end - def test_ensure_does_not_respond_to_credit - assert !@gateway.respond_to?(:credit) + def test_successful_authorization + @gateway.expects(:add_creditcard) + @gateway.expects(:ssl_request).returns(successful_authorization_response) + + assert response = @gateway.authorize(@amount, @credit_card, @options) + assert_instance_of Response, response + assert_success response + + assert_equal 'ch_test_charge', response.authorization + assert response.test? end - def test_gateway_without_credentials - assert_raises ArgumentError do - StripeGateway.new - end + def test_successful_authorization_with_token_string + @gateway.expects(:add_creditcard) + @gateway.expects(:ssl_request).returns(successful_authorization_response) + + assert response = @gateway.authorize(@amount, @token_string, @options) + assert_instance_of Response, response + assert_success response + + assert_equal 'ch_test_charge', response.authorization + assert response.test? end - def test_metadata_header - @gateway.expects(:ssl_request).once.with {|method, url, post, headers| - headers && headers['X-Stripe-Client-User-Metadata'] == {:ip => '1.1.1.1'}.to_json - }.returns(successful_purchase_response) + def test_successful_authorization_with_payment_token + @gateway.expects(:add_payment_token) + @gateway.expects(:ssl_request).returns(successful_authorization_response) - @gateway.purchase(@amount, @credit_card, @options.merge(:ip => '1.1.1.1')) + assert response = @gateway.authorize(@amount, @payment_token, @options) + assert_instance_of Response, response + assert_success response + + assert_equal 'ch_test_charge', response.authorization + assert response.test? end - def test_track_data_and_traditional_should_be_mutually_exclusive - stub_comms(:ssl_request) do - @gateway.purchase(@amount, @credit_card, @options) - end.check_request do |method, endpoint, data, headers| - assert data =~ /card\[name\]/ - assert data !~ /card\[swipe_data\]/ - end.respond_with(successful_purchase_response) + def test_successful_authorization_with_apple_pay_token_exchange + @gateway.expects(:tokenize_apple_pay_token).returns(Response.new(true, nil, token: successful_apple_pay_token_exchange)) + @gateway.expects(:ssl_request).returns(successful_authorization_response) - stub_comms(:ssl_request) do - @credit_card.track_data = '%B378282246310005^LONGSON/LONGBOB^1705101130504392?' - @gateway.purchase(@amount, @credit_card, @options) - end.check_request do |method, endpoint, data, headers| - assert data !~ /card\[name\]/ - assert data =~ /card\[swipe_data\]/ - end.respond_with(successful_purchase_response) + assert response = @gateway.authorize(@amount, @apple_pay_payment_token, @options) + assert_instance_of Response, response + assert_success response + + assert_equal 'ch_test_charge', response.authorization + assert response.test? end - def test_address_is_included_with_card_data - stub_comms(:ssl_request) do - @gateway.purchase(@amount, @credit_card, @options) - end.check_request do |method, endpoint, data, headers| - assert data =~ /card\[address_line1\]/ - end.respond_with(successful_purchase_response) + def test_successful_authorization_with_emv_credit_card + @gateway.expects(:ssl_request).returns(successful_authorization_response_with_icc_data) + + assert response = @gateway.authorize(@amount, @emv_credit_card, @options) + assert_instance_of Response, response + assert_success response + + assert_equal 'ch_test_emv_charge', response.authorization + assert response.emv_authorization, 'Response should include emv_authorization containing the EMV ARPC' end - private + def test_declined_authorization_with_emv_credit_card + @gateway.expects(:ssl_request).returns(declined_authorization_response_with_emv_auth_data) - def successful_authorization_response - <<-RESPONSE -{ - "id": "ch_test_charge", - "object": "charge", - "created": 1309131571, - "livemode": false, - "paid": true, - "amount": 400, - "currency": "usd", - "refunded": false, - "fee": 0, - "fee_details": [], - "card": { - "country": "US", - "exp_month": 9, - "exp_year": #{Time.now.year + 1}, - "last4": "4242", - "object": "card", - "type": "Visa" - }, - "captured": false, - "description": "ActiveMerchant Test Purchase", - "dispute": null, - "uncaptured": true, - "disputed": false -} - RESPONSE + assert response = @gateway.authorize(@amount, @emv_credit_card, @options) + assert_instance_of Response, response + assert_failure response + + assert_equal 'ch_declined_auth', response.authorization + assert response.emv_authorization, 'Response should include emv_auth_data containing the EMV ARC' end - def successful_capture_response - <<-RESPONSE -{ - "id": "ch_test_charge", - "object": "charge", - "created": 1309131571, - "livemode": false, - "paid": true, - "amount": 400, - "currency": "usd", - "refunded": false, - "fee": 0, - "fee_details": [], - "card": { - "country": "US", - "exp_month": 9, - "exp_year": #{Time.now.year + 1}, - "last4": "4242", - "object": "card", - "type": "Visa" - }, - "captured": true, - "description": "ActiveMerchant Test Purchase", - "dispute": null, - "uncaptured": false, - "disputed": false -} - RESPONSE + def test_successful_capture + @gateway.expects(:ssl_request).returns(successful_capture_response) + + assert response = @gateway.capture(@amount, 'ch_test_charge') + assert_success response + assert response.test? end - def successful_purchase_response(refunded=false) - <<-RESPONSE -{ - "amount": 400, - "created": 1309131571, - "currency": "usd", - "description": "Test Purchase", - "id": "ch_test_charge", - "livemode": false, - "object": "charge", - "paid": true, - "refunded": #{refunded}, - "card": { - "country": "US", - "exp_month": 9, - "exp_year": #{Time.now.year + 1}, - "last4": "4242", - "object": "card", - "type": "Visa" - } -} - RESPONSE - end - - def successful_partially_refunded_response(options = {}) - options = {:livemode=>false}.merge!(options) - <<-RESPONSE -{ - "amount": 400, - "amount_refunded": 200, - "created": 1309131571, - "currency": "usd", - "description": "Test Purchase", - "id": "ch_test_charge", - "livemode": #{options[:livemode]}, - "object": "charge", - "paid": true, - "refunded": true, - "card": { - "country": "US", - "exp_month": 9, - "exp_year": #{Time.now.year + 1}, - "last4": "4242", - "object": "card", - "type": "Visa" - } -} - RESPONSE - end - - def successful_refunded_application_fee_response - <<-RESPONSE -{ - "id": "fee_id", - "object": "application_fee", - "created": 1375375417, - "livemode": false, - "amount": 10, - "currency": "usd", - "user": "acct_id", - "user_email": "acct_id", - "application": "ca_application", - "charge": "ch_test_charge", - "refunded": false, - "amount_refunded": 10 -} - RESPONSE - end - - def successful_application_fee_list_response - <<-RESPONSE -{ - "object": "list", - "count": 2, - "url": "/v1/application_fees", - "data": [ - { - "object": "application_fee", - "id": "application_fee_id" - }, - { - "object": "another_fee", - "id": "another_fee_id" - } - ] -} - RESPONSE + def test_successful_capture_with_emv_credit_card_tc + @gateway.expects(:ssl_request).returns(successful_capture_response_with_icc_data) + + assert response = @gateway.capture(@amount, 'ch_test_emv_charge') + assert_success response + assert response.emv_authorization, 'Response should include emv_authorization containing the EMV TC' end - def unsuccessful_application_fee_list_response - <<-RESPONSE -{ - "object": "list", - "count": 0, - "url": "/v1/application_fees", - "data": [] -} - RESPONSE + def test_successful_purchase + @gateway.expects(:add_creditcard) + @gateway.expects(:ssl_request).returns(successful_purchase_response) + + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_instance_of Response, response + assert_success response + + assert_equal 'ch_test_charge', response.authorization + assert response.test? end - # Place raw failed response from gateway here - def failed_purchase_response - <<-RESPONSE - { - "error": { - "code": "incorrect_number", - "param": "number", - "type": "card_error", - "message": "Your card number is incorrect" - } - } - RESPONSE + def test_successful_purchase_with_token_string + @gateway.expects(:add_creditcard) + @gateway.expects(:ssl_request).returns(successful_purchase_response) + + assert response = @gateway.purchase(@amount, @token_string, @options) + assert_success response + + assert_equal 'ch_test_charge', response.authorization + assert response.test? end - # Place raw invalid JSON from gateway here - def invalid_json_response - <<-RESPONSE - { - foo : bar - } - RESPONSE + def test_successful_purchase_with_payment_token + @gateway.expects(:add_payment_token) + @gateway.expects(:ssl_request).returns(successful_purchase_response) + + assert response = @gateway.purchase(@amount, @payment_token, @options) + assert_success response + + assert_equal 'ch_test_charge', response.authorization + assert response.test? end - def generic_error_response - <<-RESPONSE + def test_successful_purchase_with_apple_pay_token_exchange + @gateway.expects(:tokenize_apple_pay_token).returns(Response.new(true, nil, token: successful_apple_pay_token_exchange)) + @gateway.expects(:ssl_request).returns(successful_purchase_response) + + assert response = @gateway.purchase(@amount, @apple_pay_payment_token, @options) + assert_instance_of Response, response + assert_success response + + assert_equal 'ch_test_charge', response.authorization + assert response.test? + end + + def test_successful_purchase_with_level3_data + @gateway.expects(:add_creditcard) + + @options[:merchant_reference] = 123 + @options[:customer_reference] = 456 + @options[:shipping_address_zip] = 98765 + @options[:shipping_from_zip] = 54321 + @options[:shipping_amount] = 40 + @options[:line_items] = [ + { + 'product_code' => 1234, + 'product_description' => 'An item', + 'unit_cost' => 60, + 'quantity' => 7, + 'tax_amount' => 0 + }, + { + 'product_code' => 999, + 'tax_amount' => 888 + } + ] + + response = stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, @credit_card, @options) + end.check_request do |_method, endpoint, data, _headers| + if %r{/charges} =~ endpoint + assert_match('level3[merchant_reference]=123', data) + assert_match('level3[customer_reference]=456', data) + assert_match('level3[shipping_address_zip]=98765', data) + assert_match('level3[shipping_amount]=40', data) + assert_match('level3[shipping_from_zip]=54321', data) + assert_match('level3[line_items][0][product_description]=An+item', data) + assert_match('level3[line_items][1][product_code]=999', data) + end + end.respond_with(successful_purchase_response) + + assert_success response + assert response.test? + end + + def test_amount_localization + @gateway.expects(:ssl_request).returns(successful_purchase_response(true)) + @gateway.expects(:post_data).with do |params| + params[:amount] == '4' + end + + @options[:currency] = 'JPY' + + @gateway.purchase(@amount, @credit_card, @options) + end + + def test_adds_application_to_x_stripe_client_user_agent_header + application = { + name: 'app', + version: '1.0', + url: 'https://example.com' + } + + response = stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, 'cus_xxx|card_xxx', @options.merge({application: application})) + end.check_request do |method, endpoint, data, headers| + assert_match(/\"application\"/, headers['X-Stripe-Client-User-Agent']) + assert_match(/\"name\":\"app\"/, headers['X-Stripe-Client-User-Agent']) + assert_match(/\"version\":\"1.0\"/, headers['X-Stripe-Client-User-Agent']) + assert_match(/\"url\":\"https:\/\/example.com\"/, headers['X-Stripe-Client-User-Agent']) + end.respond_with(successful_purchase_response) + + assert_success response + end + + def test_successful_purchase_with_token_including_customer + response = stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, 'cus_xxx|card_xxx') + end.check_request do |method, endpoint, data, headers| + assert_match(/customer=cus_xxx/, data) + assert_match(/card=card_xxx/, data) + end.respond_with(successful_purchase_response) + + assert_success response + end + + def test_successful_purchase_with_token + response = stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, 'card_xxx') + end.check_request do |method, endpoint, data, headers| + assert_match(/card=card_xxx/, data) + end.respond_with(successful_purchase_response) + + assert_success response + end + + def test_successful_purchase_with_statement_description + stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, @credit_card, statement_description: '5K RACE TICKET') + end.check_request do |method, endpoint, data, headers| + assert_match(/statement_descriptor=5K\+RACE\+TICKET/, data) + end.respond_with(successful_purchase_response) + end + + def test_successful_void + @gateway.expects(:ssl_request).returns(successful_purchase_response(true)) + + assert response = @gateway.void('ch_test_charge') + assert_instance_of Response, response + assert_success response + + assert_equal 'ch_test_charge', response.authorization + assert response.test? + end + + def test_void_contains_charge_expand + @gateway.expects(:ssl_request).with do |_, _, post, _| + post.include?('expand[0]=charge') + end.returns(successful_purchase_response(true)) + + assert response = @gateway.void('ch_test_charge') + assert_success response + end + + def test_void_with_additional_expand_contains_two_expands + @gateway.expects(:ssl_request).with do |_, _, post, _| + parsed = CGI.parse(post) + parsed['expand[0]'] = 'balance_transaction' + parsed['expand[1]'] = 'charge' + end.returns(successful_purchase_response(true)) + + assert response = @gateway.void('ch_test_charge', expand: :balance_transaction) + assert_success response + end + + def test_void_with_expand_charge_only_sends_one_charge_expand + @gateway.expects(:ssl_request).with do |_, _, post, _| + parsed = CGI.parse(post) + parsed['expand[0]'] == ['charge'] + end.returns(successful_purchase_response(true)) + + assert response = @gateway.void('ch_test_charge', expand: ['charge']) + assert_success response + end + + def test_successful_void_with_metadata + @gateway.expects(:ssl_request).with do |_, _, post, _| + post.include?('metadata[first_value]=true') + end.returns(successful_purchase_response(true)) + + assert response = @gateway.void('ch_test_charge', {metadata: {first_value: true}}) + assert_success response + end + + def test_successful_void_with_reason + @gateway.expects(:ssl_request).with do |_, _, post, _| + post.include?('reason=fraudulent') + end.returns(successful_purchase_response(true)) + + assert response = @gateway.void('ch_test_charge', {reason: 'fraudulent'}) + assert_success response + end + + def test_successful_refund + @gateway.expects(:ssl_request).returns(successful_partially_refunded_response) + + assert response = @gateway.refund(@refund_amount, 'ch_test_charge') + assert_success response + + assert_equal 're_test_refund', response.authorization + end + + def test_successful_refund_with_reason + @gateway.expects(:ssl_request).returns(successful_partially_refunded_response) + + assert response = @gateway.refund(@refund_amount, 'ch_test_charge', reason: 'fraudulent') + assert_success response + + assert_equal 're_test_refund', response.authorization + end + + def test_unsuccessful_refund + @gateway.expects(:ssl_request).returns(generic_error_response) + + assert response = @gateway.refund(@refund_amount, 'ch_test_charge') + assert_failure response + end + + def test_successful_refund_with_refund_application_fee + @gateway.expects(:ssl_request).with do |method, url, post, headers| + post.include?('refund_application_fee=true') + end.returns(successful_partially_refunded_response) + + assert response = @gateway.refund(@refund_amount, 'ch_test_charge', :refund_application_fee => true) + assert_success response + end + + def test_refund_contains_charge_expand + @gateway.expects(:ssl_request).with do |_, _, post, _| + post.include?('expand[0]=charge') + end.returns(successful_partially_refunded_response) + + assert response = @gateway.refund(@refund_amount, 'ch_test_charge') + assert_success response + end + + def test_refund_with_additional_expand_contains_two_expands + @gateway.expects(:ssl_request).with do |_, _, post, _| + parsed = CGI.parse(post) + parsed['expand[0]'] = 'balance_transaction' + parsed['expand[1]'] = 'charge' + end.returns(successful_partially_refunded_response) + + assert response = @gateway.refund(@refund_amount, 'ch_test_charge', expand: :balance_transaction) + assert_success response + end + + def test_refund_with_expand_charge_only_sends_one_charge_expand + @gateway.expects(:ssl_request).with do |_, _, post, _| + parsed = CGI.parse(post) + parsed['expand[0]'] == ['charge'] + end.returns(successful_partially_refunded_response) + + assert response = @gateway.refund(@refund_amount, 'ch_test_charge', expand: ['charge']) + assert_success response + end + + def test_successful_refund_with_metadata + @gateway.expects(:ssl_request).with do |method, url, post, headers| + post.include?('metadata[first_value]=true') + end.returns(successful_partially_refunded_response) + + assert response = @gateway.refund(@refund_amount, 'ch_test_charge', {metadata: {first_value: true}}) + assert_success response + end + + def test_successful_refund_with_reverse_transfer + stub_comms(@gateway, :ssl_request) do + @gateway.refund(@amount, 'auth', reverse_transfer: true) + end.check_request do |method, endpoint, data, headers| + assert_match(/reverse_transfer=true/, data) + end.respond_with(successful_partially_refunded_response) + end + + def test_successful_refund_with_refund_fee_amount + s = sequence('request') + @gateway.expects(:ssl_request).returns(successful_partially_refunded_response).in_sequence(s) + @gateway.expects(:ssl_request).returns(successful_fetch_application_fee_response).in_sequence(s) + @gateway.expects(:ssl_request).returns(successful_partially_refunded_application_fee_response).in_sequence(s) + + assert response = @gateway.refund(@refund_amount, 'ch_test_charge', :refund_fee_amount => 100) + assert_success response + end + + def test_refund_with_fee_response_responds_with_the_refund_authorization + s = sequence('request') + @gateway.expects(:ssl_request).returns(successful_partially_refunded_response).in_sequence(s) + @gateway.expects(:ssl_request).returns(successful_fetch_application_fee_response).in_sequence(s) + @gateway.expects(:ssl_request).returns(successful_partially_refunded_application_fee_response).in_sequence(s) + + assert response = @gateway.refund(@refund_amount, 'ch_test_charge', :refund_fee_amount => 100) + assert_success response + assert_equal 're_test_refund', response.authorization + end + + def test_successful_refund_with_failed_fee_refund_fetch + s = sequence('request') + @gateway.expects(:ssl_request).returns(successful_partially_refunded_response).in_sequence(s) + @gateway.expects(:ssl_request).returns(unsuccessful_fetch_application_fee_response).in_sequence(s) + + assert response = @gateway.refund(@refund_amount, 'ch_test_charge', :refund_fee_amount => 100) + assert_success response + end + + def test_successful_refund_with_failed_fee_refund + s = sequence('request') + @gateway.expects(:ssl_request).returns(successful_partially_refunded_response).in_sequence(s) + @gateway.expects(:ssl_request).returns(successful_fetch_application_fee_response).in_sequence(s) + @gateway.expects(:ssl_request).returns(generic_error_response).in_sequence(s) + + assert response = @gateway.refund(@refund_amount, 'ch_test_charge', :refund_fee_amount => 100) + assert_success response + end + + def test_unsuccessful_refund_does_not_refund_fee + s = sequence('request') + @gateway.expects(:ssl_request).returns(generic_error_response).in_sequence(s) + + assert response = @gateway.refund(@refund_amount, 'ch_test_charge', :refund_fee_amount => 100) + assert_failure response + end + + def test_successful_verify + response = stub_comms(@gateway, :ssl_request) do + @gateway.verify(@credit_card, @options) + end.respond_with(successful_authorization_response, successful_void_response) + assert_success response + end + + def test_successful_verify_with_failed_void + response = stub_comms(@gateway, :ssl_request) do + @gateway.verify(@credit_card, @options) + end.respond_with(successful_authorization_response, failed_void_response) + assert_success response + assert_equal 'Transaction approved', response.message + end + + def test_unsuccessful_verify + response = stub_comms(@gateway, :ssl_request) do + @gateway.verify(@credit_card, @options) + end.respond_with(declined_authorization_response, successful_void_response) + assert_failure response + assert_equal 'Your card was declined.', response.message + end + + def test_successful_request_always_uses_live_mode_to_determine_test_request + @gateway.expects(:ssl_request).returns(successful_partially_refunded_response) + + assert response = @gateway.refund(@refund_amount, 'ch_test_charge') + assert_success response + + assert !response.test? + end + + def test_unsuccessful_request + @gateway.expects(:ssl_request).returns(failed_purchase_response) + + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert !response.test? # unsuccessful request defaults to live + assert_nil response.authorization + end + + def test_declined_request + @gateway.expects(:ssl_request).returns(declined_purchase_response) + + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_failure response + + assert_equal Gateway::STANDARD_ERROR_CODE[:card_declined], response.error_code + refute response.test? # unsuccessful request defaults to live + assert_equal 'ch_test_charge', response.authorization + end + + def test_declined_request_advanced_decline_codes + @gateway.expects(:ssl_request).returns(declined_call_issuer_purchase_response) + + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_failure response + + assert_equal Gateway::STANDARD_ERROR_CODE[:call_issuer], response.error_code + refute response.test? # unsuccessful request defaults to live + assert_equal 'ch_test_charge', response.authorization + end + + def test_declined_request_advanced_pickup_card_code + @gateway.expects(:ssl_request).returns(declined_pickup_card_purchase_response) + + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_failure response + + assert_equal Gateway::STANDARD_ERROR_CODE[:pickup_card], response.error_code + refute response.test? # unsuccessful request defaults to live + assert_equal 'ch_test_charge', response.authorization + end + + def test_declined_request_advanced_decline_code_not_in_standard_mapping + @gateway.expects(:ssl_request).returns(declined_generic_decline_purchase_response) + + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_failure response + + assert_equal Gateway::STANDARD_ERROR_CODE[:card_declined], response.error_code + assert !response.test? # unsuccessful request defaults to live + assert_equal 'ch_test_charge', response.authorization + end + + def test_invalid_raw_response + @gateway.expects(:ssl_request).returns(invalid_json_response) + + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_match(/^Invalid response received from the Stripe API/, response.message) + end + + def test_add_creditcard_with_credit_card + post = {} + @gateway.send(:add_creditcard, post, @credit_card, {}) + assert_equal @credit_card.number, post[:card][:number] + assert_equal @credit_card.month, post[:card][:exp_month] + assert_equal @credit_card.year, post[:card][:exp_year] + assert_equal @credit_card.verification_value, post[:card][:cvc] + assert_equal @credit_card.name, post[:card][:name] + end + + def test_add_creditcard_with_track_data + post = {} + @credit_card.stubs(:track_data).returns('Swipe data') + @credit_card.stubs(:read_method).returns('contactless_magstripe') + @gateway.send(:add_creditcard, post, @credit_card, {}) + assert_equal @credit_card.track_data, post[:card][:swipe_data] + assert_equal 'contactless_magstripe_mode', post[:card][:read_method] + assert_nil post[:card][:number] + assert_nil post[:card][:exp_year] + assert_nil post[:card][:exp_month] + assert_nil post[:card][:cvc] + assert_nil post[:card][:name] + end + + def test_add_creditcard_with_fallback_no_chip + post = {} + @credit_card.stubs(:track_data).returns('Swipe data') + @credit_card.stubs(:read_method).returns('fallback_no_chip') + @gateway.send(:add_creditcard, post, @credit_card, {}) + assert_equal @credit_card.track_data, post[:card][:swipe_data] + assert_equal 'no_chip', post[:card][:fallback_reason] + assert_nil post[:card][:number] + assert_nil post[:card][:exp_year] + assert_nil post[:card][:exp_month] + assert_nil post[:card][:cvc] + assert_nil post[:card][:name] + end + + def test_add_creditcard_with_fallback_chip_error + post = {} + @credit_card.stubs(:track_data).returns('Swipe data') + @credit_card.stubs(:read_method).returns('fallback_chip_error') + @gateway.send(:add_creditcard, post, @credit_card, {}) + assert_equal @credit_card.track_data, post[:card][:swipe_data] + assert_equal 'chip_error', post[:card][:fallback_reason] + assert_nil post[:card][:number] + assert_nil post[:card][:exp_year] + assert_nil post[:card][:exp_month] + assert_nil post[:card][:cvc] + assert_nil post[:card][:name] + end + + def test_add_creditcard_with_card_token + post = {} + credit_card_token = 'card_2iD4AezYnNNzkW' + @gateway.send(:add_creditcard, post, credit_card_token, {}) + assert_equal 'card_2iD4AezYnNNzkW', post[:card] + end + + def test_add_creditcard_with_card_token_and_customer + post = {} + credit_card_token = 'cus_3sgheFxeBgTQ3M|card_2iD4AezYnNNzkW' + @gateway.send(:add_creditcard, post, credit_card_token, {}) + assert_equal 'cus_3sgheFxeBgTQ3M', post[:customer] + assert_equal 'card_2iD4AezYnNNzkW', post[:card] + end + + def test_add_creditcard_with_card_token_and_track_data + post = {} + credit_card_token = 'card_2iD4AezYnNNzkW' + @gateway.send(:add_creditcard, post, credit_card_token, :track_data => 'Tracking data') + assert_equal 'Tracking data', post[:card][:swipe_data] + end + + def test_add_creditcard_with_emv_credit_card + post = {} + @gateway.send(:add_creditcard, post, @emv_credit_card, {}) + + assert_equal @emv_credit_card.icc_data, post[:card][:emv_auth_data] + end + + def test_add_creditcard_pads_eci_value + post = {} + credit_card = network_tokenization_credit_card('4242424242424242', + payment_cryptogram: '111111111100cryptogram', + verification_value: nil, + eci: '7' + ) + + @gateway.send(:add_creditcard, post, credit_card, {}) + + assert_equal '07', post[:card][:eci] + end + + def test_application_fee_is_submitted_for_purchase + stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, @credit_card, @options.merge({:application_fee => 144})) + end.check_request do |method, endpoint, data, headers| + assert_match(/application_fee=144/, data) + end.respond_with(successful_purchase_response) + end + + def test_application_fee_is_submitted_for_capture + stub_comms(@gateway, :ssl_request) do + @gateway.capture(@amount, 'ch_test_charge', @options.merge({:application_fee => 144})) + end.check_request do |method, endpoint, data, headers| + assert_match(/application_fee=144/, data) + end.respond_with(successful_capture_response) + end + + def test_exchange_rate_is_submitted_for_purchase + stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, @credit_card, @options.merge({:exchange_rate => 0.96251})) + end.check_request do |method, endpoint, data, headers| + assert_match(/exchange_rate=0.96251/, data) + end.respond_with(successful_purchase_response) + end + + def test_exchange_rate_is_submitted_for_capture + stub_comms(@gateway, :ssl_request) do + @gateway.capture(@amount, 'ch_test_charge', @options.merge({:exchange_rate => 0.96251})) + end.check_request do |method, endpoint, data, headers| + assert_match(/exchange_rate=0.96251/, data) + end.respond_with(successful_capture_response) + end + + def test_destination_is_submitted_for_purchase + stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, @credit_card, @options.merge({:destination => 'subaccountid'})) + end.check_request do |method, endpoint, data, headers| + assert_match(/destination\[account\]=subaccountid/, data) + end.respond_with(successful_purchase_response) + end + + def test_destination_amount_is_submitted_for_purchase + stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, @credit_card, @options.merge({:destination => 'subaccountid', :destination_amount => @amount - 20})) + end.check_request do |method, endpoint, data, headers| + assert_match(/destination\[amount\]=#{@amount - 20}/, data) + end.respond_with(successful_purchase_response) + end + + def test_client_data_submitted_with_purchase + stub_comms(@gateway, :ssl_request) do + updated_options = @options.merge({:description => 'a test customer', :ip => '127.127.127.127', :user_agent => 'some browser', :order_id => '42', :email => 'foo@wonderfullyfakedomain.com', :receipt_email => 'receipt-receiver@wonderfullyfakedomain.com', :referrer =>'http://www.shopify.com'}) + @gateway.purchase(@amount, @credit_card, updated_options) + end.check_request do |method, endpoint, data, headers| + assert_match(/description=a\+test\+customer/, data) + assert_match(/ip=127\.127\.127\.127/, data) + assert_match(/user_agent=some\+browser/, data) + assert_match(/external_id=42/, data) + assert_match(/referrer=http\%3A\%2F\%2Fwww\.shopify\.com/, data) + assert_match(/payment_user_agent=Stripe\%2Fv1\+ActiveMerchantBindings\%2F\d+\.\d+\.\d+/, data) + assert_match(/metadata\[email\]=foo\%40wonderfullyfakedomain\.com/, data) + assert_match(/receipt_email=receipt-receiver\%40wonderfullyfakedomain\.com/, data) + assert_match(/metadata\[order_id\]=42/, data) + end.respond_with(successful_purchase_response) + end + + def test_client_data_submitted_with_purchase_without_email_or_order + stub_comms(@gateway, :ssl_request) do + updated_options = @options.merge({:description => 'a test customer', :ip => '127.127.127.127', :user_agent => 'some browser', :referrer =>'http://www.shopify.com'}) + @gateway.purchase(@amount, @credit_card, updated_options) + end.check_request do |method, endpoint, data, headers| + assert_match(/description=a\+test\+customer/, data) + assert_match(/ip=127\.127\.127\.127/, data) + assert_match(/user_agent=some\+browser/, data) + assert_match(/referrer=http\%3A\%2F\%2Fwww\.shopify\.com/, data) + assert_match(/payment_user_agent=Stripe\%2Fv1\+ActiveMerchantBindings\%2F\d+\.\d+\.\d+/, data) + refute data.include?('metadata') + end.respond_with(successful_purchase_response) + end + + def test_client_data_submitted_with_metadata_in_options + stub_comms(@gateway, :ssl_request) do + updated_options = @options.merge({:metadata => {:this_is_a_random_key_name => 'with a random value', :i_made_up_this_key_too => 'canyoutell'}, :order_id => '42', :email => 'foo@wonderfullyfakedomain.com'}) + @gateway.purchase(@amount, @credit_card, updated_options) + end.check_request do |method, endpoint, data, headers| + assert_match(/metadata\[this_is_a_random_key_name\]=with\+a\+random\+value/, data) + assert_match(/metadata\[i_made_up_this_key_too\]=canyoutell/, data) + assert_match(/metadata\[email\]=foo\%40wonderfullyfakedomain\.com/, data) + assert_match(/metadata\[order_id\]=42/, data) + end.respond_with(successful_purchase_response) + end + + def test_client_data_submitted_with_metadata_in_options_with_emv_credit_card_purchase + stub_comms(@gateway, :ssl_request) do + updated_options = @options.merge({:metadata => {:this_is_a_random_key_name => 'with a random value', :i_made_up_this_key_too => 'canyoutell'}, :order_id => '42', :email => 'foo@wonderfullyfakedomain.com'}) + @gateway.purchase(@amount, @emv_credit_card, updated_options) + end.check_request do |method, endpoint, data, headers| + assert_match(/metadata\[this_is_a_random_key_name\]=with\+a\+random\+value/, data) + assert_match(/metadata\[i_made_up_this_key_too\]=canyoutell/, data) + assert_match(/metadata\[email\]=foo\%40wonderfullyfakedomain\.com/, data) + assert_match(/metadata\[order_id\]=42/, data) + assert_match(/metadata\[card_read_method\]=contact/, data) + end.respond_with(successful_purchase_response) + end + + def test_client_data_submitted_with_metadata_in_options_with_emv_credit_card_authorize + stub_comms(@gateway, :ssl_request) do + updated_options = @options.merge({:metadata => {:this_is_a_random_key_name => 'with a random value', :i_made_up_this_key_too => 'canyoutell'}, :order_id => '42', :email => 'foo@wonderfullyfakedomain.com'}) + @gateway.authorize(@amount, @emv_credit_card, updated_options) + end.check_request do |method, endpoint, data, headers| + assert_match(/metadata\[this_is_a_random_key_name\]=with\+a\+random\+value/, data) + assert_match(/metadata\[i_made_up_this_key_too\]=canyoutell/, data) + assert_match(/metadata\[email\]=foo\%40wonderfullyfakedomain\.com/, data) + assert_match(/metadata\[order_id\]=42/, data) + assert_match(/metadata\[card_read_method\]=contact/, data) + end.respond_with(successful_purchase_response) + end + + def test_quickchip_is_set_on_purchase + stub_comms(@gateway, :ssl_request) do + @emv_credit_card.read_method = 'contact_quickchip' + @gateway.purchase(@amount, @emv_credit_card, @options) + end.check_request do |method, endpoint, data, headers| + assert_match(/card\[processing_method\]=quick_chip/, data) + end.respond_with(successful_purchase_response) + end + + def test_quickchip_is_not_set_on_authorize + stub_comms(@gateway, :ssl_request) do + @emv_credit_card.read_method = 'contact_quickchip' + @gateway.authorize(@amount, @emv_credit_card, @options) + end.check_request do |method, endpoint, data, headers| + refute_match(/card\[processing_method\]=quick_chip/, data) + end.respond_with(successful_purchase_response) + end + + def test_add_address + post = {:card => {}} + @gateway.send(:add_address, post, @options) + assert_equal @options[:billing_address][:zip], post[:card][:address_zip] + assert_equal @options[:billing_address][:state], post[:card][:address_state] + assert_equal @options[:billing_address][:address1], post[:card][:address_line1] + assert_equal @options[:billing_address][:address2], post[:card][:address_line2] + assert_equal @options[:billing_address][:country], post[:card][:address_country] + assert_equal @options[:billing_address][:city], post[:card][:address_city] + end + + def test_add_statement_address + post = {} + + @gateway.send(:add_statement_address, post, @options) + + assert_equal @options[:statement_address][:zip], post[:statement_address][:postal_code] + assert_equal @options[:statement_address][:state], post[:statement_address][:state] + assert_equal @options[:statement_address][:address1], post[:statement_address][:line1] + assert_equal @options[:statement_address][:address2], post[:statement_address][:line2] + assert_equal @options[:statement_address][:country], post[:statement_address][:country] + assert_equal @options[:statement_address][:city], post[:statement_address][:city] + end + + def test_add_statement_address_returns_nil_if_required_fields_missing + post = {} + [:address1, :city, :zip, :state].each do |required_key| + missing_required = @options.tap do |options| + options[:statement_address].delete_if { |k| k == required_key } + end + + @gateway.send(:add_statement_address, post, missing_required) + + assert_equal nil, post[:statement_address] + end + end + + def test_ensure_does_not_respond_to_credit + assert !@gateway.respond_to?(:credit) + end + + def test_gateway_without_credentials + assert_raises ArgumentError do + StripeGateway.new + end + end + + def test_metadata_header + @gateway.expects(:ssl_request).once.with { |method, url, post, headers| + headers && headers['X-Stripe-Client-User-Metadata'] == {:ip => '1.1.1.1'}.to_json + }.returns(successful_purchase_response) + + @gateway.purchase(@amount, @credit_card, @options.merge(:ip => '1.1.1.1')) + end + + def test_optional_version_header + @gateway.expects(:ssl_request).once.with { |method, url, post, headers| + headers && headers['Stripe-Version'] == '2013-10-29' + }.returns(successful_purchase_response) + + @gateway.purchase(@amount, @credit_card, @options.merge(:version => '2013-10-29')) + end + + def test_optional_idempotency_key_header + @gateway.expects(:ssl_request).once.with { |method, url, post, headers| + headers && headers['Idempotency-Key'] == 'test123' + }.returns(successful_purchase_response) + + response = @gateway.purchase(@amount, @credit_card, @options.merge(:idempotency_key => 'test123')) + assert_success response + end + + def test_optional_idempotency_on_void + @gateway.expects(:ssl_request).once.with { |method, url, post, headers| + headers && headers['Idempotency-Key'] == 'test123' + }.returns(successful_purchase_response(true)) + + response = @gateway.void('ch_test_charge', @options.merge(:idempotency_key => 'test123')) + assert_success response + end + + def test_optional_idempotency_on_verify + @gateway.expects(:ssl_request).with do |method, url, post, headers| + headers && headers['Idempotency-Key'] == nil + end.returns(successful_void_response) + + @gateway.expects(:ssl_request).with do |method, url, post, headers| + headers && headers['Idempotency-Key'] == 'test123' + end.returns(successful_authorization_response) + + response = @gateway.verify(@credit_card, @options.merge(:idempotency_key => 'test123')) + assert_success response + end + + def test_initialize_gateway_with_version + @gateway = StripeGateway.new(:login => 'login', :version => '2013-12-03') + @gateway.expects(:ssl_request).once.with { |method, url, post, headers| + headers && headers['Stripe-Version'] == '2013-12-03' + }.returns(successful_purchase_response) + + @gateway.purchase(@amount, @credit_card, @options) + end + + def test_track_data_and_traditional_should_be_mutually_exclusive + stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, @credit_card, @options) + end.check_request do |method, endpoint, data, headers| + assert data =~ /card\[name\]/ + assert data !~ /card\[swipe_data\]/ + end.respond_with(successful_purchase_response) + + stub_comms(@gateway, :ssl_request) do + @credit_card.track_data = '%B378282246310005^LONGSON/LONGBOB^1705101130504392?' + @gateway.purchase(@amount, @credit_card, @options) + end.check_request do |method, endpoint, data, headers| + assert data !~ /card\[name\]/ + assert data =~ /card\[swipe_data\]/ + end.respond_with(successful_purchase_response) + end + + def test_address_is_included_with_card_data + stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, @credit_card, @options) + end.check_request do |method, endpoint, data, headers| + assert data =~ /card\[address_line1\]/ + end.respond_with(successful_purchase_response) + end + + def test_contactless_flag_is_included_with_emv_card_data + stub_comms(@gateway, :ssl_request) do + @emv_credit_card.read_method = 'contactless' + @gateway.purchase(@amount, @emv_credit_card, @options) + end.check_request do |method, endpoint, data, headers| + assert data =~ /card\[read_method\]=contactless/ + end.respond_with(successful_purchase_response) + end + + def test_contactless_magstripe_flag_is_included_with_emv_card_data + stub_comms(@gateway, :ssl_request) do + @emv_credit_card.read_method = 'contactless_magstripe' + @gateway.purchase(@amount, @emv_credit_card, @options) + end.check_request do |method, endpoint, data, headers| + assert data =~ /card\[read_method\]=contactless_magstripe_mode/ + end.respond_with(successful_purchase_response) + end + + def test_contactless_flag_is_not_included_with_emv_card_data_by_default + stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, @emv_credit_card, @options) + end.check_request do |method, endpoint, data, headers| + assert data !~ /card\[read_method\]=contactless/ && data !~ /card\[read_method\]=contactless_magstripe_mode/ + end.respond_with(successful_purchase_response) + end + + def test_encrypted_pin_is_included_with_emv_card_data + stub_comms(@gateway, :ssl_request) do + @emv_credit_card.encrypted_pin_cryptogram = '8b68af72199529b8' + @emv_credit_card.encrypted_pin_ksn = 'ffff0102628d12000001' + @gateway.purchase(@amount, @emv_credit_card, @options) + end.check_request do |method, endpoint, data, headers| + assert data =~ /card\[encrypted_pin\]=8b68af72199529b8/ + assert data =~ /card\[encrypted_pin_key_id\]=ffff0102628d12000001/ + end.respond_with(successful_purchase_response) + end + + def generate_options_should_allow_key + assert_equal({:key => '12345'}, generate_options({:key => '12345'})) + end + + def test_passing_expand_parameters + @gateway.expects(:ssl_request).with do |method, url, post, headers| + post.include?('expand[0]=balance_transaction') + end.returns(successful_authorization_response) + + @options[:expand] = :balance_transaction + + @gateway.authorize(@amount, @credit_card, @options) + end + + def test_passing_expand_parameters_as_array + @gateway.expects(:ssl_request).with do |method, url, post, headers| + post.include?('expand[0]=balance_transaction&expand[1]=customer') + end.returns(successful_authorization_response) + + @options[:expand] = [:balance_transaction, :customer] + + @gateway.authorize(@amount, @credit_card, @options) + end + + def test_recurring_flag_not_set_by_default + @gateway.expects(:ssl_request).with do |method, url, post, headers| + !post.include?('recurring') + end.returns(successful_authorization_response) + + @gateway.authorize(@amount, @credit_card, @options) + end + + def test_passing_recurring_eci_sets_recurring_flag + @gateway.expects(:ssl_request).with do |method, url, post, headers| + post.include?('recurring=true') + end.returns(successful_authorization_response) + + @options[:eci] = 'recurring' + + @gateway.authorize(@amount, @credit_card, @options) + end + + def test_passing_unknown_eci_does_not_set_recurring_flag + @gateway.expects(:ssl_request).with do |method, url, post, headers| + !post.include?('recurring') + end.returns(successful_authorization_response) + + @options[:eci] = 'installment' + + @gateway.authorize(@amount, @credit_card, @options) + end + + def test_passing_recurring_true_option_sets_recurring_flag + @gateway.expects(:ssl_request).with do |method, url, post, headers| + post.include?('recurring=true') + end.returns(successful_authorization_response) + + @options[:recurring] = true + + @gateway.authorize(@amount, @credit_card, @options) + end + + def test_passing_recurring_false_option_does_not_set_recurring_flag + @gateway.expects(:ssl_request).with do |method, url, post, headers| + !post.include?('recurring') + end.returns(successful_authorization_response) + + @options[:recurring] = false + + @gateway.authorize(@amount, @credit_card, @options) + end + + def test_new_attributes_are_included_in_update + stub_comms(@gateway, :ssl_request) do + @gateway.send(:update, 'cus_3sgheFxeBgTQ3M', 'card_483etw4er9fg4vF3sQdrt3FG', { :name => 'John Smith', :exp_year => 2021, :exp_month => 6 }) + end.check_request do |method, endpoint, data, headers| + assert data == 'name=John+Smith&exp_year=2021&exp_month=6' + assert endpoint.include? '/customers/cus_3sgheFxeBgTQ3M/cards/card_483etw4er9fg4vF3sQdrt3FG' + end.respond_with(successful_update_credit_card_response) + end + + def test_scrub + assert_equal @gateway.scrub(pre_scrubbed), post_scrubbed + end + + def test_scrubs_track_data + assert_equal @gateway.scrub(pre_scrubbed_with_track_data), post_scrubbed_with_track_data + end + + def test_scrubs_emv_data + assert_equal @gateway.scrub(pre_scrubbed_with_emv_data), post_scrubbed_with_emv_data + end + + def test_supports_scrubbing? + assert @gateway.supports_scrubbing? + end + + def test_successful_auth_with_network_tokenization_apple_pay + @gateway.expects(:ssl_request).with do |method, endpoint, data, headers| + assert_equal :post, method + assert_match %r'card\[cryptogram\]=111111111100cryptogram&card\[eci\]=05&card\[tokenization_method\]=apple_pay', data + true + end.returns(successful_authorization_response) + + credit_card = network_tokenization_credit_card('4242424242424242', + payment_cryptogram: '111111111100cryptogram', + verification_value: nil, + eci: '05' + ) + + assert response = @gateway.authorize(@amount, credit_card, @options) + assert_instance_of Response, response + assert_success response + + assert_equal 'ch_test_charge', response.authorization + assert response.test? + end + + def test_successful_auth_with_network_tokenization_android_pay + @gateway.expects(:ssl_request).with do |method, endpoint, data, headers| + assert_equal :post, method + assert_match %r'card\[cryptogram\]=111111111100cryptogram&card\[eci\]=05&card\[tokenization_method\]=android_pay', data + true + end.returns(successful_authorization_response) + + credit_card = network_tokenization_credit_card('4242424242424242', + payment_cryptogram: '111111111100cryptogram', + verification_value: nil, + eci: '05', + source: :android_pay + ) + + assert response = @gateway.authorize(@amount, credit_card, @options) + assert_instance_of Response, response + assert_success response + + assert_equal 'ch_test_charge', response.authorization + assert response.test? + end + + def test_successful_purchase_with_network_tokenization_apple_pay + @gateway.expects(:ssl_request).with do |method, endpoint, data, headers| + assert_equal :post, method + assert_match %r'card\[cryptogram\]=111111111100cryptogram&card\[eci\]=05&card\[tokenization_method\]=apple_pay', data + true + end.returns(successful_authorization_response) + + credit_card = network_tokenization_credit_card('4242424242424242', + payment_cryptogram: '111111111100cryptogram', + verification_value: nil, + eci: '05' + ) + + assert response = @gateway.purchase(@amount, credit_card, @options) + assert_instance_of Response, response + assert_success response + + assert_equal 'ch_test_charge', response.authorization + assert response.test? + end + + def test_successful_purchase_with_network_tokenization_android_pay + @gateway.expects(:ssl_request).with do |method, endpoint, data, headers| + assert_equal :post, method + assert_match %r'card\[cryptogram\]=111111111100cryptogram&card\[eci\]=05&card\[tokenization_method\]=android_pay', data + true + end.returns(successful_authorization_response) + + credit_card = network_tokenization_credit_card('4242424242424242', + payment_cryptogram: '111111111100cryptogram', + verification_value: nil, + eci: '05', + source: :android_pay + ) + + assert response = @gateway.purchase(@amount, credit_card, @options) + assert_instance_of Response, response + assert_success response + + assert_equal 'ch_test_charge', response.authorization + assert response.test? + end + + def test_supports_network_tokenization + assert_instance_of TrueClass, @gateway.supports_network_tokenization? + end + + def test_emv_capture_application_fee_ignored + response = stub_comms(@gateway, :ssl_request) do + @gateway.capture(@amount, 'ch_test_charge', application_fee: 100, icc_data: @emv_credit_card.icc_data) + end.check_request do |method, endpoint, data, headers| + assert data !~ /application_fee/, 'request should not include application_fee' + end.respond_with(successful_capture_response_with_icc_data) + + assert_success response + end + + def test_authorization_with_emv_payment_application_fee_included + response = stub_comms(@gateway, :ssl_request) do + @gateway.authorize(@amount, 'ch_test_charge', application_fee: 100, icc_data: @emv_credit_card.icc_data) + end.check_request do |method, endpoint, data, headers| + assert data =~ /application_fee/, 'request should include application_fee' + end.respond_with(successful_capture_response_with_icc_data) + + assert_success response + end + + def test_passing_stripe_account_header + @gateway.expects(:ssl_request).with do |method, url, post, headers| + headers.include?('Stripe-Account') + end.returns(successful_authorization_response) + + @options[:stripe_account] = fixtures(:stripe_destination)[:stripe_user_id] + + @gateway.purchase(@amount, @credit_card, @options) + end + + def test_3ds_source_creation + @gateway.expects(:ssl_request).twice.returns(threeds_first_sources_created_response, threeds_second_sources_created_response) + card_source = @gateway.send(:create_source, @amount, @threeds_card, 'card', @options.merge(@threeds_options)) + assert_success card_source + response = @gateway.send(:create_source, @amount, card_source.params['id'], 'three_d_secure', @options) + assert_equal 'source', response.params['object'] + assert_equal 'pending', response.params['status'] + assert_equal 'three_d_secure', response.params['type'] + assert_equal false, response.params['three_d_secure']['authenticated'] + end + + def test_non3ds_card_source_creation + @gateway.expects(:ssl_request).returns(non_3ds_sources_create_response) + response = @gateway.send(:create_source, @amount, @non_3ds_card, 'card', @options.merge(@threeds_options)) + assert_equal 'source', response.params['object'] + assert_equal 'chargeable', response.params['status'] + assert_equal 'card', response.params['type'] + assert_equal 'not_supported', response.params['card']['three_d_secure'] + end + + def test_webhook_creation + @gateway.expects(:ssl_request).returns(webhook_event_creation_response) + response = @gateway.send(:create_webhook_endpoint, @options.merge(@threeds_options), ['source.chargeable']) + assert_includes response.params['enabled_events'], 'source.chargeable' + assert_equal @options.merge(@threeds_options)[:callback_url], response.params['url'] + end + + def test_webhook_deletion + @gateway.expects(:ssl_request).twice.returns(webhook_event_creation_response, webhook_event_deletion_response) + webhook = @gateway.send(:create_webhook_endpoint, @options.merge(@threeds_options), ['source.chargeable']) + response = @gateway.send(:delete_webhook_endpoint, @options.merge(:webhook_id => webhook.params['id'])) + assert_equal response.params['id'], webhook.params['id'] + assert_equal true, response.params['deleted'] + end + + def test_verify_good_credentials + @gateway.expects(:raw_ssl_request).returns(credentials_are_legit_response) + assert @gateway.verify_credentials + end + + def test_verify_bad_credentials + @gateway.expects(:raw_ssl_request).returns(credentials_are_bogus_response) + assert !@gateway.verify_credentials + end + + def test_stripe_internal_error_fails + @gateway.expects(:add_creditcard) + @gateway.expects(:ssl_request).returns(stripe_internal_error_response) + + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_equal response.message, 'No error details' + assert response.test? + end + + private + + # this mock is only useful with unit tests, as cryptograms generated by an EMV terminal + # are specific to the target acquirer, so remote tests using this mock will fail elsewhere. + def credit_card_with_icc_data + ActiveMerchant::Billing::CreditCard.new(read_method: 'contact', icc_data: '500B56495341204352454449545F201A56495341204143515549524552205445535420434152442030315F24031512315F280208405F2A0208265F300202015F34010182025C008407A0000000031010950502000080009A031408259B02E8009C01009F02060000000734499F03060000000000009F0607A00000000310109F0902008C9F100706010A03A080009F120F4352454449544F20444520564953419F1A0208269F1C0831373030303437309F1E0831373030303437309F2608EB2EC0F472BEA0A49F2701809F3303E0B8C89F34031E03009F3501229F360200C39F37040A27296F9F4104000001319F4502DAC5DFAE5711476173FFFFFF0119D15122011758989389DFAE5A08476173FFFFFF011957114761739001010119D151220117589893895A084761739001010119') + end + + def pre_scrubbed + <<-PRE_SCRUBBED + opening connection to api.stripe.com:443... + opened + starting SSL for api.stripe.com:443... + SSL established + <- "POST /v1/charges HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAuthorization: Basic c2tfdGVzdF9oQkwwTXF6ZGZ6Rnk3OXU0cFloUmVhQlo6\r\nUser-Agent: Stripe/v1 ActiveMerchantBindings/1.45.0\r\nX-Stripe-Client-User-Agent: {\"bindings_version\":\"1.45.0\",\"lang\":\"ruby\",\"lang_version\":\"2.1.3 p242 (2014-09-19)\",\"platform\":\"x86_64-linux\",\"publisher\":\"active_merchant\"}\r\nX-Stripe-Client-User-Metadata: {\"ip\":null}\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nConnection: close\r\nHost: api.stripe.com\r\nContent-Length: 270\r\n\r\n" + <- "amount=100&currency=usd&card[number]=4242424242424242&card[exp_month]=9&card[exp_year]=2015&card[cvc]=123&card[name]=Longbob+Longsen&description=ActiveMerchant+Test+Purchase&payment_user_agent=Stripe%2Fv1+ActiveMerchantBindings%2F1.45.0&metadata[email]=wow%40example.com&card[cryptogram]=sensitive_data&three_d_secure[cryptogram]=123456789abcdefghijklmnop&three_d_secure[apple_pay]=true" + -> "HTTP/1.1 200 OK\r\n" + -> "Server: nginx\r\n" + -> "Date: Tue, 02 Dec 2014 19:44:17 GMT\r\n" + -> "Content-Type: application/json;charset=utf-8\r\n" + -> "Content-Length: 1303\r\n" + -> "Connection: close\r\n" + -> "Access-Control-Allow-Credentials: true\r\n" + -> "Access-Control-Allow-Methods: GET, POST, HEAD, OPTIONS, DELETE\r\n" + -> "Access-Control-Max-Age: 300\r\n" + -> "Cache-Control: no-cache, no-store\r\n" + -> "Request-Id: 89de951c-f880-4c39-93b0-832b3cc6dd32\r\n" + -> "Stripe-Version: 2013-12-03\r\n" + -> "Strict-Transport-Security: max-age=31556926; includeSubDomains\r\n" + -> "\r\n" + reading 1303 bytes... + -> "{\n \"id\": \"ch_155MZJ2gKyKnHxtY1dGqFhSb\",\n \"object\": \"charge\",\n \"created\": 1417549457,\n \"livemode\": false,\n \"paid\": true,\n \"amount\": 100,\n \"currency\": \"usd\",\n \"refunded\": false,\n \"captured\": true,\n \"refunds\": [],\n \"card\": {\n \"id\": \"card_155MZJ2gKyKnHxtYihrJ8z94\",\n \"object\": \"card\",\n \"last4\": \"4242\",\n \"brand\": \"Visa\",\n \"funding\": \"credit\",\n \"exp_month\": 9,\n \"exp_year\": 2015,\n \"fingerprint\": \"944LvWcY01HVTbVc\",\n \"country\": \"US\",\n \"name\": \"Longbob Longsen\",\n \"address_line1\": null,\n \"address_line2\": null,\n \"address_city\": null,\n \"address_state\": null,\n \"address_zip\": null,\n \"address_country\": null,\n \"cvc_check\": \"pass\",\n \"address_line1_check\": null,\n \"address_zip_check\": null,\n \"dynamic_last4\": null,\n \"customer\": null,\n \"type\": \"Visa\"\n },\n \"balance_transaction\": \"txn_155MZJ2gKyKnHxtYxpYDI5OW\",\n \"failure_message\": null,\n \"failure_code\": null,\n \"amount_refunded\": 0,\n \"customer\": null,\n \"invoice\": null,\n \"description\": \"ActiveMerchant Test Purchase\",\n \"dispute\": null,\n \"metadata\": {\n \"email\": \"wow@example.com\"\n },\n \"statement_description\": null,\n \"fraud_details\": {\n \"stripe_report\": \"unavailable\",\n \"user_report\": null\n },\n \"receipt_email\": null,\n \"receipt_number\": null,\n \"shipping\": null\n}\n" + read 1303 bytes + Conn close + PRE_SCRUBBED + end + + def pre_scrubbed_with_track_data + <<-PRE_SCRUBBED + opening connection to api.stripe.com:443... + opened + starting SSL for api.stripe.com:443... + SSL established + <- "POST /v1/charges HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAuthorization: Basic c2tfdGVzdF8zT0Q0VGRLU0lPaERPTDIxNDZKSmNDNzk6\r\nUser-Agent: Stripe/v1 ActiveMerchantBindings/1.54.0\r\nStripe-Version: 2015-04-07\r\nX-Stripe-Client-User-Agent: {\"bindings_version\":\"1.54.0\",\"lang\":\"ruby\",\"lang_version\":\"2.1.1 p76 (2014-02-24)\",\"platform\":\"x86_64-darwin12.0\",\"publisher\":\"active_merchant\"}\r\nX-Stripe-Client-User-Metadata: {\"ip\":null}\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nConnection: close\r\nHost: api.stripe.com\r\nContent-Length: 165\r\n\r\n" + <- "card[swipe_data]=%25B378282246310005%5ELONGSON%2FLONGBOB%5E1705101130504392%3F&amount=100&currency=usd&payment_user_agent=Stripe%2Fv1+ActiveMerchantBindings%2F1.54.0" + -> "HTTP/1.1 200 OK\r\n" + -> "Server: nginx\r\n" + -> "Date: Wed, 21 Oct 2015 17:22:09 GMT\r\n" + -> "Content-Type: application/json\r\n" + -> "Content-Length: 1446\r\n" + -> "Connection: close\r\n" + -> "Access-Control-Allow-Credentials: true\r\n" + -> "Access-Control-Allow-Methods: GET, POST, HEAD, OPTIONS, DELETE\r\n" + -> "Access-Control-Allow-Origin: *\r\n" + -> "Access-Control-Max-Age: 300\r\n" + -> "Cache-Control: no-cache, no-store\r\n" + -> "Request-Id: req_7CpxqdRGPV3xWh\r\n" + -> "Stripe-Version: 2015-04-07\r\n" + -> "Strict-Transport-Security: max-age=31556926; includeSubDomains\r\n" + -> "\r\n" + reading 1446 bytes... + -> "{\n \"id\": \"ch_16yQHtAWOtgoysogh1YOAtDB\",\n \"object\": \"charge\",\n \"amount\": 100,\n \"amount_refunded\": 0,\n \"application_fee\": null,\n \"balance_transaction\": \"txn_16yQHtAWOtgoysogTNhtGJBn\",\n \"captured\": true,\n \"created\": 1445448129,\n \"currency\": \"usd\",\n \"customer\": null,\n \"description\": null,\n \"destination\": null,\n \"dispute\": null,\n \"failure_code\": null,\n \"failure_message\": null,\n \"fraud_details\": {},\n \"invoice\": null,\n \"livemode\": false,\n \"metadata\": {},\n \"paid\": true,\n \"receipt_email\": null,\n \"receipt_number\": null,\n \"refunded\": false,\n \"refunds\": {\n \"object\": \"list\",\n \"data\": [],\n \"has_more\": false,\n \"total_count\": 0,\n \"url\": \"/v1/charges/ch_16yQHtAWOtgoysogh1YOAtDB/refunds\"\n },\n \"shipping\": null,\n \"source\": {\n \"id\": \"card_16yQHtAWOtgoysogdSGVCkXK\",\n \"object\": \"card\",\n \"address_city\": null,\n \"address_country\": null,\n \"address_line1\": null,\n \"address_line1_check\": null,\n \"address_line2\": null,\n \"address_state\": null,\n \"address_zip\": null,\n \"address_zip_check\": null,\n \"brand\": \"American Express\",\n \"country\": \"US\",\n \"customer\": null,\n \"cvc_check\": null,\n \"dynamic_last4\": null,\n \"exp_month\": 5,\n \"exp_year\": 2017,\n \"fingerprint\": \"DjZpoV89lmOMsJLF\",\n \"funding\": \"credit\",\n \"last4\": \"0005\",\n \"metadata\": {},\n \"name\": \"LONGSON/LONGBOB\",\n \"tokenization_method\": null\n },\n \"statement_descriptor\": null,\n \"status\": \"succeeded\"\n}\n" + read 1446 bytes + Conn close + PRE_SCRUBBED + end + + def pre_scrubbed_with_emv_data + <<-PRE_SCRUBBED + opening connection to api.stripe.com:443... + opened + starting SSL for api.stripe.com:443... + SSL established + <- "POST /v1/charges HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAuthorization: Basic c2tfdGVzdF8zT0Q0VGRLU0lPaERPTDIxNDZKSmNDNzk6\r\nUser-Agent: Stripe/v1 ActiveMerchantBindings/1.54.0\r\nStripe-Version: 2015-04-07\r\nX-Stripe-Client-User-Agent: {\"bindings_version\":\"1.54.0\",\"lang\":\"ruby\",\"lang_version\":\"2.1.1 p76 (2014-02-24)\",\"platform\":\"x86_64-darwin12.0\",\"publisher\":\"active_merchant\"}\r\nX-Stripe-Client-User-Metadata: {\"ip\":null}\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nConnection: close\r\nHost: api.stripe.com\r\nContent-Length: 713\r\n\r\n" + <- "card[emv_auth_data]=500B56495341204352454449545F201A56495341204143515549524552205445535420434152442030315F24031512315F280208405F2A0208265F300202015F34010182025C008407A0000000031010950502000080009A031408259B02E8009C01009F02060000000734499F03060000000000009F0607A00000000310109F0902008C9F100706010A03A080009F120F4352454449544F20444520564953419F1A0208269F1C0831373030303437309F1E0831373030303437309F2608EB2EC0F472BEA0A49F2701809F3303E0B8C89F34031E03009F3501229F360200C39F37040A27296F9F4104000001319F4502DAC5DFAE5711476173FFFFFF0119D15122011758989389DFAE5A08476173FFFFFF011957114761739001010119D151220117589893895A084761739001010119&card[emv_approval_data]=garbage&card[encrypted_pin]=8b68af72199529b8&card[encrypted_pin_key_id]=ffff0102628d12000001" + -> "HTTP/1.1 402 Payment Required\r\n" + -> "Server: nginx\r\n" + -> "Date: Wed, 21 Oct 2015 17:39:02 GMT\r\n" + -> "Content-Type: application/json\r\n" + -> "Content-Length: 195\r\n" + -> "Connection: close\r\n" + -> "Access-Control-Allow-Credentials: true\r\n" + -> "Access-Control-Allow-Methods: GET, POST, HEAD, OPTIONS, DELETE\r\n" + -> "Access-Control-Allow-Origin: *\r\n" + -> "Access-Control-Max-Age: 300\r\n" + -> "Cache-Control: no-cache, no-store\r\n" + -> "Request-Id: req_7CqEdcyzeRNGQO\r\n" + -> "Stripe-Version: 2015-04-07\r\n" + -> "\r\n" + reading 195 bytes... + -> "{\n \"error\": {\n \"message\": \"Your card was declined.\",\n \"type\": \"card_error\",\n \"code\": \"card_declined\",\n \"charge\": \"ch_16yQYEAWOtgoysogscsBRQwg\",\n \"emv_auth_data\": \"8A023035\"\n }\n}\n" + read 195 bytes + Conn close + PRE_SCRUBBED + end + + def post_scrubbed_with_emv_data + <<-POST_SCRUBBED + opening connection to api.stripe.com:443... + opened + starting SSL for api.stripe.com:443... + SSL established + <- "POST /v1/charges HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAuthorization: Basic [FILTERED]\r\nUser-Agent: Stripe/v1 ActiveMerchantBindings/1.54.0\r\nStripe-Version: 2015-04-07\r\nX-Stripe-Client-User-Agent: {\"bindings_version\":\"1.54.0\",\"lang\":\"ruby\",\"lang_version\":\"2.1.1 p76 (2014-02-24)\",\"platform\":\"x86_64-darwin12.0\",\"publisher\":\"active_merchant\"}\r\nX-Stripe-Client-User-Metadata: {\"ip\":null}\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nConnection: close\r\nHost: api.stripe.com\r\nContent-Length: 713\r\n\r\n" + <- "card[emv_auth_data]=[FILTERED]&card[emv_approval_data]=[FILTERED]&card[encrypted_pin]=[FILTERED]&card[encrypted_pin_key_id]=[FILTERED]" + -> "HTTP/1.1 402 Payment Required\r\n" + -> "Server: nginx\r\n" + -> "Date: Wed, 21 Oct 2015 17:39:02 GMT\r\n" + -> "Content-Type: application/json\r\n" + -> "Content-Length: 195\r\n" + -> "Connection: close\r\n" + -> "Access-Control-Allow-Credentials: true\r\n" + -> "Access-Control-Allow-Methods: GET, POST, HEAD, OPTIONS, DELETE\r\n" + -> "Access-Control-Allow-Origin: *\r\n" + -> "Access-Control-Max-Age: 300\r\n" + -> "Cache-Control: no-cache, no-store\r\n" + -> "Request-Id: req_7CqEdcyzeRNGQO\r\n" + -> "Stripe-Version: 2015-04-07\r\n" + -> "\r\n" + reading 195 bytes... + -> "{\n \"error\": {\n \"message\": \"Your card was declined.\",\n \"type\": \"card_error\",\n \"code\": \"card_declined\",\n \"charge\": \"ch_16yQYEAWOtgoysogscsBRQwg\",\n \"emv_auth_data\": \"8A023035\"\n }\n}\n" + read 195 bytes + Conn close + POST_SCRUBBED + end + + def post_scrubbed_with_track_data + <<-POST_SCRUBBED + opening connection to api.stripe.com:443... + opened + starting SSL for api.stripe.com:443... + SSL established + <- "POST /v1/charges HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAuthorization: Basic [FILTERED]\r\nUser-Agent: Stripe/v1 ActiveMerchantBindings/1.54.0\r\nStripe-Version: 2015-04-07\r\nX-Stripe-Client-User-Agent: {\"bindings_version\":\"1.54.0\",\"lang\":\"ruby\",\"lang_version\":\"2.1.1 p76 (2014-02-24)\",\"platform\":\"x86_64-darwin12.0\",\"publisher\":\"active_merchant\"}\r\nX-Stripe-Client-User-Metadata: {\"ip\":null}\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nConnection: close\r\nHost: api.stripe.com\r\nContent-Length: 165\r\n\r\n" + <- "card[swipe_data]=[FILTERED]&amount=100&currency=usd&payment_user_agent=Stripe%2Fv1+ActiveMerchantBindings%2F1.54.0" + -> "HTTP/1.1 200 OK\r\n" + -> "Server: nginx\r\n" + -> "Date: Wed, 21 Oct 2015 17:22:09 GMT\r\n" + -> "Content-Type: application/json\r\n" + -> "Content-Length: 1446\r\n" + -> "Connection: close\r\n" + -> "Access-Control-Allow-Credentials: true\r\n" + -> "Access-Control-Allow-Methods: GET, POST, HEAD, OPTIONS, DELETE\r\n" + -> "Access-Control-Allow-Origin: *\r\n" + -> "Access-Control-Max-Age: 300\r\n" + -> "Cache-Control: no-cache, no-store\r\n" + -> "Request-Id: req_7CpxqdRGPV3xWh\r\n" + -> "Stripe-Version: 2015-04-07\r\n" + -> "Strict-Transport-Security: max-age=31556926; includeSubDomains\r\n" + -> "\r\n" + reading 1446 bytes... + -> "{\n \"id\": \"ch_16yQHtAWOtgoysogh1YOAtDB\",\n \"object\": \"charge\",\n \"amount\": 100,\n \"amount_refunded\": 0,\n \"application_fee\": null,\n \"balance_transaction\": \"txn_16yQHtAWOtgoysogTNhtGJBn\",\n \"captured\": true,\n \"created\": 1445448129,\n \"currency\": \"usd\",\n \"customer\": null,\n \"description\": null,\n \"destination\": null,\n \"dispute\": null,\n \"failure_code\": null,\n \"failure_message\": null,\n \"fraud_details\": {},\n \"invoice\": null,\n \"livemode\": false,\n \"metadata\": {},\n \"paid\": true,\n \"receipt_email\": null,\n \"receipt_number\": null,\n \"refunded\": false,\n \"refunds\": {\n \"object\": \"list\",\n \"data\": [],\n \"has_more\": false,\n \"total_count\": 0,\n \"url\": \"/v1/charges/ch_16yQHtAWOtgoysogh1YOAtDB/refunds\"\n },\n \"shipping\": null,\n \"source\": {\n \"id\": \"card_16yQHtAWOtgoysogdSGVCkXK\",\n \"object\": \"card\",\n \"address_city\": null,\n \"address_country\": null,\n \"address_line1\": null,\n \"address_line1_check\": null,\n \"address_line2\": null,\n \"address_state\": null,\n \"address_zip\": null,\n \"address_zip_check\": null,\n \"brand\": \"American Express\",\n \"country\": \"US\",\n \"customer\": null,\n \"cvc_check\": null,\n \"dynamic_last4\": null,\n \"exp_month\": 5,\n \"exp_year\": 2017,\n \"fingerprint\": \"DjZpoV89lmOMsJLF\",\n \"funding\": \"credit\",\n \"last4\": \"0005\",\n \"metadata\": {},\n \"name\": \"LONGSON/LONGBOB\",\n \"tokenization_method\": null\n },\n \"statement_descriptor\": null,\n \"status\": \"succeeded\"\n}\n" + read 1446 bytes + Conn close + POST_SCRUBBED + end + + def post_scrubbed + <<-POST_SCRUBBED + opening connection to api.stripe.com:443... + opened + starting SSL for api.stripe.com:443... + SSL established + <- "POST /v1/charges HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAuthorization: Basic [FILTERED]\r\nUser-Agent: Stripe/v1 ActiveMerchantBindings/1.45.0\r\nX-Stripe-Client-User-Agent: {\"bindings_version\":\"1.45.0\",\"lang\":\"ruby\",\"lang_version\":\"2.1.3 p242 (2014-09-19)\",\"platform\":\"x86_64-linux\",\"publisher\":\"active_merchant\"}\r\nX-Stripe-Client-User-Metadata: {\"ip\":null}\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nConnection: close\r\nHost: api.stripe.com\r\nContent-Length: 270\r\n\r\n" + <- "amount=100&currency=usd&card[number]=[FILTERED]&card[exp_month]=9&card[exp_year]=2015&card[cvc]=[FILTERED]&card[name]=Longbob+Longsen&description=ActiveMerchant+Test+Purchase&payment_user_agent=Stripe%2Fv1+ActiveMerchantBindings%2F1.45.0&metadata[email]=wow%40example.com&card[cryptogram]=[FILTERED]&three_d_secure[cryptogram]=[FILTERED]&three_d_secure[apple_pay]=true" + -> "HTTP/1.1 200 OK\r\n" + -> "Server: nginx\r\n" + -> "Date: Tue, 02 Dec 2014 19:44:17 GMT\r\n" + -> "Content-Type: application/json;charset=utf-8\r\n" + -> "Content-Length: 1303\r\n" + -> "Connection: close\r\n" + -> "Access-Control-Allow-Credentials: true\r\n" + -> "Access-Control-Allow-Methods: GET, POST, HEAD, OPTIONS, DELETE\r\n" + -> "Access-Control-Max-Age: 300\r\n" + -> "Cache-Control: no-cache, no-store\r\n" + -> "Request-Id: 89de951c-f880-4c39-93b0-832b3cc6dd32\r\n" + -> "Stripe-Version: 2013-12-03\r\n" + -> "Strict-Transport-Security: max-age=31556926; includeSubDomains\r\n" + -> "\r\n" + reading 1303 bytes... + -> "{\n \"id\": \"ch_155MZJ2gKyKnHxtY1dGqFhSb\",\n \"object\": \"charge\",\n \"created\": 1417549457,\n \"livemode\": false,\n \"paid\": true,\n \"amount\": 100,\n \"currency\": \"usd\",\n \"refunded\": false,\n \"captured\": true,\n \"refunds\": [],\n \"card\": {\n \"id\": \"card_155MZJ2gKyKnHxtYihrJ8z94\",\n \"object\": \"card\",\n \"last4\": \"4242\",\n \"brand\": \"Visa\",\n \"funding\": \"credit\",\n \"exp_month\": 9,\n \"exp_year\": 2015,\n \"fingerprint\": \"944LvWcY01HVTbVc\",\n \"country\": \"US\",\n \"name\": \"Longbob Longsen\",\n \"address_line1\": null,\n \"address_line2\": null,\n \"address_city\": null,\n \"address_state\": null,\n \"address_zip\": null,\n \"address_country\": null,\n \"cvc_check\": \"pass\",\n \"address_line1_check\": null,\n \"address_zip_check\": null,\n \"dynamic_last4\": null,\n \"customer\": null,\n \"type\": \"Visa\"\n },\n \"balance_transaction\": \"txn_155MZJ2gKyKnHxtYxpYDI5OW\",\n \"failure_message\": null,\n \"failure_code\": null,\n \"amount_refunded\": 0,\n \"customer\": null,\n \"invoice\": null,\n \"description\": \"ActiveMerchant Test Purchase\",\n \"dispute\": null,\n \"metadata\": {\n \"email\": \"wow@example.com\"\n },\n \"statement_description\": null,\n \"fraud_details\": {\n \"stripe_report\": \"unavailable\",\n \"user_report\": null\n },\n \"receipt_email\": null,\n \"receipt_number\": null,\n \"shipping\": null\n}\n" + read 1303 bytes + Conn close + POST_SCRUBBED + end + + def successful_new_customer_response + <<-RESPONSE + { + "object": "customer", + "created": 1383137317, + "id": "cus_3sgheFxeBgTQ3M", + "livemode": false, + "description": null, + "email": null, + "delinquent": false, + "metadata": {}, + "subscription": null, + "discount": null, + "account_balance": 0, + "sources": + { + "object": "list", + "count": 1, + "url": "/v1/customers/cus_3sgheFxeBgTQ3M/cards", + "data": + [ + { + "id": "card_483etw4er9fg4vF3sQdrt3FG", + "object": "card", + "last4": "4242", + "type": "Visa", + "exp_month": 11, + "exp_year": 2020, + "fingerprint": "5dgRQ3dVRGaQWDFb", + "customer": "cus_3sgheFxeBgTQ3M", + "country": "US", + "name": "John Doe", + "address_line1": null, + "address_line2": null, + "address_city": null, + "address_state": null, + "address_zip": null, + "address_country": null, + "cvc_check": null, + "address_line1_check": null, + "address_zip_check": null + } + ] + }, + "default_card": "card_483etw4er9fg4vF3sQdrt3FG" + } + RESPONSE + end + + def successful_new_card_response + <<-RESPONSE + { + "id": "card_483etw4er9fg4vF3sQdrt3FG", + "livemode": false, + "object": "card", + "last4": "4242", + "type": "Visa", + "exp_month": 11, + "exp_year": 2020, + "fingerprint": "5dgRQ3dVRGaQWDFb", + "customer": "cus_3sgheFxeBgTQ3M", + "country": "US", + "name": "John Doe", + "address_line1": null, + "address_line2": null, + "address_city": null, + "address_state": null, + "address_zip": null, + "address_country": null, + "cvc_check": null, + "address_line1_check": null, + "address_zip_check": null + } + RESPONSE + end + + def successful_bank_token_request + <<-RESPONSE + { + "id": "btok_7s6lmOv1DRUpyG", + "object": "token", + "bank_account": { + "id": "ba_17cMXgAWOtgoysog7UDWXbn4", + "object": "bank_account", + "account_holder_type": "individual", + "bank_name": "STRIPE TEST BANK", + "country": "US", + "currency": "usd", + "fingerprint": "uCkXlMFxqys7GosR", + "last4": "6789", + "name": "Jim Smith", + "routing_number": "110000000", + "status": "new" + }, + "client_ip": "24.142.217.2", + "created": 1454966852, + "livemode": false, + "type": "bank_account", + "used": false + } + RESPONSE + end + + def successful_new_customer_bank_account_response + <<-RESPONSE + { + "id": "cus_7s6levMt8IqhTR", + "object": "customer", + "account_balance": 0, + "created": 1454966853, + "currency": null, + "default_source": "ba_17cMXgAWOtgoysog7UDWXbn4", + "delinquent": false, + "description": null, + "discount": null, + "email": null, + "livemode": false, + "metadata": {}, + "shipping": null, + "sources": { + "object": "list", + "data": [ + { + "id": "ba_17cMXgAWOtgoysog7UDWXbn4", + "object": "bank_account", + "account_holder_type": "individual", + "bank_name": "STRIPE TEST BANK", + "country": "US", + "currency": "usd", + "customer": "cus_7s6levMt8IqhTR", + "fingerprint": "uCkXlMFxqys7GosR", + "last4": "6789", + "metadata": {}, + "name": "Jim Smith", + "routing_number": "110000000", + "status": "new" + } + ], + "has_more": false, + "total_count": 1, + "url": "/v1/customers/cus_7s6levMt8IqhTR/sources" + }, + "subscriptions": { + "object": "list", + "data": [], + "has_more": false, + "total_count": 0, + "url": "/v1/customers/cus_7s6levMt8IqhTR/subscriptions" + } + } + RESPONSE + end + + def successful_authorization_response + <<-RESPONSE + { + "id": "ch_test_charge", + "object": "charge", + "created": 1309131571, + "livemode": false, + "paid": true, + "amount": 400, + "currency": "usd", + "refunded": false, + "fee": 0, + "fee_details": [], + "card": { + "country": "US", + "exp_month": 9, + "exp_year": #{Time.now.year + 1}, + "last4": "4242", + "object": "card", + "type": "Visa" + }, + "captured": false, + "description": "ActiveMerchant Test Purchase", + "dispute": null, + "uncaptured": true, + "disputed": false + } + RESPONSE + end + + def successful_authorization_response_with_icc_data + <<-RESPONSE + { + "id": "ch_test_emv_charge", + "object": "charge", + "created": 1429642948, + "livemode": true, + "paid": true, + "status": "succeeded", + "amount": 1000, + "currency": "usd", + "refunded": false, + "source": { + "id": "card_15u6dcHMpVh8I77hUfAVAfsK", + "object": "card", + "last4": "8123", + "brand": "MasterCard", + "funding": "unknown", + "exp_month": 12, + "exp_year": 2025, + "fingerprint": "tdVpM3XDe3H4juSD", + "country": "US", + "name": null, + "address_line1": null, + "address_line2": null, + "address_city": null, + "address_state": null, + "address_zip": null, + "address_country": null, + "cvc_check": null, + "address_line1_check": null, + "address_zip_check": null, + "dynamic_last4": null, + "metadata": {}, + "customer": null, + "emv_auth_data": "8A023835910AF7F7BA77D7ACCFAB0012710F860D8424000008C1EFF627EAE08933" + }, + "captured": false, + "balance_transaction": null, + "failure_message": null, + "failure_code": null, + "amount_refunded": 0, + "customer": null, + "invoice": null, + "description": null, + "dispute": null, + "metadata": {}, + "statement_descriptor": null, + "fraud_details": {}, + "receipt_email": null, + "receipt_number": null, + "authorization_code": "816826", + "shipping": null, + "application_fee": null, + "refunds": { + "object": "list", + "total_count": 0, + "has_more": false, + "url": "/v1/charges/ch_15u6dcHMpVh8I77hdIKNQ1jH/refunds", + "data": [] + } + } + RESPONSE + end + + def successful_capture_response + <<-RESPONSE + { + "id": "ch_test_charge", + "object": "charge", + "created": 1309131571, + "livemode": false, + "paid": true, + "amount": 400, + "currency": "usd", + "refunded": false, + "fee": 0, + "fee_details": [], + "card": { + "country": "US", + "exp_month": 9, + "exp_year": #{Time.now.year + 1}, + "last4": "4242", + "object": "card", + "type": "Visa" + }, + "captured": true, + "description": "ActiveMerchant Test Purchase", + "dispute": null, + "uncaptured": false, + "disputed": false + } + RESPONSE + end + + def successful_capture_response_with_icc_data + <<-RESPONSE + { + "id": "ch_test_emv_charge", + "object": "charge", + "created": 1429643380, + "livemode": true, + "paid": true, + "status": "succeeded", + "amount": 1000, + "currency": "usd", + "refunded": false, + "source": { + "id": "card_15u6kaHMpVh8I77htEt6tgX4", + "object": "card", + "last4": "8123", + "brand": "MasterCard", + "funding": "unknown", + "exp_month": 12, + "exp_year": 2025, + "fingerprint": "tdVpM3XDe3H4juSD", + "country": "US", + "name": null, + "address_line1": null, + "address_line2": null, + "address_city": null, + "address_state": null, + "address_zip": null, + "address_country": null, + "cvc_check": null, + "address_line1_check": null, + "address_zip_check": null, + "dynamic_last4": null, + "metadata": {}, + "customer": null, + "emv_auth_data": "8A023835910AF7F7BA77D7ACCFAB0012710F860D8424000008C1EFF627EAE08933" + }, + "captured": true, + "balance_transaction": "txn_15u6kbHMpVh8I77hA79CanC2", + "failure_message": null, + "failure_code": null, + "amount_refunded": 900, + "customer": null, + "invoice": null, + "description": null, + "dispute": null, + "metadata": {}, + "statement_descriptor": null, + "fraud_details": {}, + "receipt_email": null, + "receipt_number": null, + "authorization_code": "662021", + "shipping": null, + "application_fee": null, + "refunds": { + "object": "list", + "total_count": 1, + "has_more": false, + "url": "/v1/charges/ch_15u6kaHMpVh8I77hrF9XY8bG/refunds", + "data": [ + { + "id": "re_15u6kbHMpVh8I77h2o6RsdQq", + "amount": 900, + "currency": "usd", + "created": 1429643381, + "object": "refund", + "balance_transaction": "txn_15u6kbHMpVh8I77hXqAYL6kZ", + "metadata": {}, + "charge": "ch_15u6kaHMpVh8I77hrF9XY8bG", + "receipt_number": null, + "reason": null + } + ] + } + } + RESPONSE + end + + def successful_purchase_response(refunded=false) + <<-RESPONSE + { + "amount": 400, + "created": 1309131571, + "currency": "usd", + "description": "Test Purchase", + "id": "ch_test_charge", + "livemode": false, + "object": "charge", + "paid": true, + "refunded": #{refunded}, + "card": { + "country": "US", + "exp_month": 9, + "exp_year": #{Time.now.year + 1}, + "last4": "4242", + "object": "card", + "type": "Visa" + } + } + RESPONSE + end + + def successful_partially_refunded_response + <<-RESPONSE + { + "id": "re_test_refund", + "object": "refund", + "amount": 80, + "balance_transaction": "txn_1737ZdAWOtgoysogRvA3jg6b", + "charge": "ch_1737ZcAWOtgoysogGRRsFjN9", + "created": 1446567833, + "currency": "usd", + "metadata": {}, + "reason": null, + "receipt_number": null + } + RESPONSE + end + + def successful_void_response + <<-RESPONSE + { + "id": "re_173VMpAWOtgoysogOSE7Hzss", + "object": "refund", + "amount": 100, + "balance_transaction": "txn_173VMpAWOtgoysog3QNrt0xD", + "charge": "ch_173VMpAWOtgoysogrTPZT1YP", + "created": 1446659295, + "currency": "usd", + "metadata": {}, + "reason": null, + "receipt_number": null + } + RESPONSE + end + + def failed_void_response + <<-RESPONSE + { + "error": { + "type": "invalid_request_error", + "message": "Charge ch_173VPSAWOtgoysoggGxIDRIq has already been refunded." + } + } + RESPONSE + end + + def successful_partially_refunded_application_fee_response + <<-RESPONSE + { + "id": "fr_C8qmJKrZVMTjjF", + "object": "fee_refund", + "amount": 10, + "balance_transaction": "txn_1BkZ4uAWOtgoysognvusG5N5", + "created": 1516027008, + "currency": "usd", + "fee": "fee_1BkZ4rIPBJTitsenGWcxYWCZ", + "metadata": {} + } + RESPONSE + end + + def successful_fetch_application_fee_response + <<-RESPONSE + { + "id": "ch_1Bja3MIPBJTitsenv28Gy6iN", + "object": "charge", + "amount": 100, + "amount_refunded": 100, + "application": "ca_6E9gvTfZGEMknxpoHhC8xoeyMit55FAV", + "application_fee": "fee_1Bja3MIPBJTitsenKqV8Hc6R", + "balance_transaction": "txn_1Bja3OIPBJTitsenJ5amtW58", + "captured": true, + "created": 1515792428, + "currency": "usd", + "customer": null, + "description": "ActiveMerchant Test Purchase", + "destination": null, + "dispute": null, + "failure_code": null, + "failure_message": null, + "fraud_details": {}, + "invoice": null, + "livemode": false + } + RESPONSE + end + + def unsuccessful_fetch_application_fee_response + <<-RESPONSE + { + "error": { + "type": "invalid_request_error", + "message": "No such charge: bad_auth", + "param": "id" + } + } + RESPONSE + end + + def failed_purchase_response + <<-RESPONSE + { + "error": { + "code": "incorrect_number", + "param": "number", + "type": "card_error", + "message": "Your card number is incorrect" + } + } + RESPONSE + end + + def declined_purchase_response + <<-RESPONSE + { + "error": { + "message": "Your card was declined.", + "type": "card_error", + "code": "card_declined", + "charge": "ch_test_charge" + } + } + RESPONSE + end + + def declined_call_issuer_purchase_response + <<-RESPONSE + { + "error": { + "message": "Your card was declined.", + "type": "card_error", + "code": "card_declined", + "decline_code": "call_issuer", + "charge": "ch_test_charge" + } + } + RESPONSE + end + + def declined_pickup_card_purchase_response + <<-RESPONSE + { + "error": { + "message": "Your card was declined.", + "type": "card_error", + "code": "card_declined", + "decline_code": "pickup_card", + "charge": "ch_test_charge" + } + } + RESPONSE + end + + def declined_generic_decline_purchase_response + <<-RESPONSE + { + "error": { + "message": "Your card was declined.", + "type": "card_error", + "code": "card_declined", + "decline_code": "generic_decline", + "charge": "ch_test_charge" + } + } + RESPONSE + end + + def declined_authorization_response + <<-RESPONSE + { + "error": { + "message": "Your card was declined.", + "type": "card_error", + "code": "card_declined", + "charge": "ch_4IKxffGOKVRJ4l" + } + } + RESPONSE + end + + def declined_authorization_response_with_emv_auth_data + <<-RESPONSE + { + "error": { + "message": "Your card was declined.", + "type": "card_error", + "code": "card_declined", + "decline_code": "generic_decline", + "charge": "ch_declined_auth", + "emv_auth_data": "8A023531910ACD16B371D277FDB90000" + } + } + RESPONSE + end + + def successful_update_credit_card_response + <<-RESPONSE + { + "id": "card_483etw4er9fg4vF3sQdrt3FG", + "object": "card", + "last4": "4242", + "type": "Visa", + "exp_month": 6, + "exp_year": 2021, + "fingerprint": "5dgRQ3dVRGaQWDFb", + "customer": "cus_3sgheFxeBgTQ3M", + "country": "US", + "name": "John Smith", + "address_line1": null, + "address_line2": null, + "address_city": null, + "address_state": null, + "address_zip": null, + "address_country": null, + "cvc_check": null, + "address_line1_check": null, + "address_zip_check": null + } + RESPONSE + end + + def successful_apple_pay_token_exchange + <<-RESPONSE + { + "id": "tok_14uq3k2gKyKnHxtYUAZZZlH3", + "livemode": false, + "created": 1415041212, + "used": false, + "object": "token", + "type": "card", + "card": { + "id": "card_483etw4er9fg4vF3sQdrt3FG", + "object": "card", + "last4": "0000", + "brand": "Visa", + "funding": "credit", + "exp_month": 6, + "exp_year": 2019, + "fingerprint": "HOh74kZU387WlUvy", + "country": "US", + "name": null, + "address_line1": null, + "address_line2": null, + "address_city": null, + "address_state": null, + "address_zip": null, + "address_country": null, + "dynamic_last4": "4242", + "customer": null, + "type": "Visa" + } + } + RESPONSE + end + + def invalid_json_response + <<-RESPONSE + { + foo : bar + } + RESPONSE + end + + def generic_error_response + <<-RESPONSE { "error": { "code": "generic", @@ -435,4 +2318,336 @@ def generic_error_response } RESPONSE end + + def credentials_are_legit_response + body = <<-JSON + { + "error": { + "type": "invalid_request_error", + "message": "No such charge: nonexistent", + "param": "id" + } + } + JSON + MockResponse.new(404, body) + end + + def credentials_are_bogus_response + body = <<-JSON + { + "error": { + "type": "invalid_request_error", + "message": "Invalid API Key provided: an_unknown_***_key" + } + } + JSON + MockResponse.new(401, body) + end + + def stripe_internal_error_response + <<-RESPONSE + { + "id": "ch_1CSa8KL6Z03PTOVWo9B3qYCI", + "object": "charge", + "amount": 900, + "amount_refunded": 0, + "application": null, + "application_fee": null, + "balance_transaction": null, + "captured": false, + "created": 1526517494, + "currency": "usd", + "customer": null, + "description": null, + "destination": null, + "dispute": null, + "failure_code": null, + "failure_message": null, + "fraud_details": {}, + "invoice": null, + "livemode": true, + "metadata": { + "product_name": "1 month", + "vendor_id": "12345", + "connect_agent": "Spreedly", + "email": "someone@example.com", + "order_id": "3193747-1568289" + }, + "on_behalf_of": null, + "order": null, + "outcome": { + "network_status": "approved_by_network", + "reason": null, + "seller_message": "Payment complete.", + "type": "authorized" + }, + "paid": false, + "receipt_email": null, + "receipt_number": null, + "refunded": false, + "refunds": { + "object": "list", + "data": [], + "has_more": false, + "total_count": 0, + "url": "/v1/charges/ch_1CSa8KL6Z03PTOVWo9B3qYCI/refunds" + }, + "review": null, + "shipping": null, + "source": { + "id": "card_1a2b3c4d5e6f7g8h9j", + "object": "card", + "address_city": null, + "address_country": null, + "address_line1": null, + "address_line1_check": null, + "address_line2": null, + "address_state": null, + "address_zip": null, + "address_zip_check": null, + "brand": "Visa", + "country": "MX", + "customer": null, + "cvc_check": null, + "dynamic_last4": null, + "exp_month": 11, + "exp_year": 2021, + "fingerprint": "ABCDEFGHIJKLMNO", + "funding": "credit", + "last4": "0123", + "metadata": {}, + "name": null, + "tokenization_method": null + }, + "source_transfer": null, + "statement_descriptor": "MAGIC*TEST*STUFF", + "status": "failed", + "transfer_group": null, + "livemode": false + } + RESPONSE + end + + def token_params + { + 'id' => 'tok_14uq3k2gKyKnHxtYUAZZZlH3', + 'object' => 'token', + 'card' => { + 'id' => 'card_189f8n2eZvKYlo2CgvOd3Vtn', + 'object' => 'card', + 'address_city' => nil, + 'address_country' => nil, + 'address_line1' => nil, + 'address_line1_check' => nil, + 'address_line2' => nil, + 'address_state' => nil, + 'address_zip' => nil, + 'address_zip_check' => nil, + 'brand' => 'Visa', + 'country' => 'US', + 'cvc_check' => nil, + 'dynamic_last4' => nil, + 'exp_month' => 8, + 'exp_year' => 2017, + 'funding' => 'credit', + 'last4' => '4242', + 'metadata' => { + }, + 'name' => nil, + 'tokenization_method' => nil + }, + 'client_ip' => nil, + 'created' => 1462903169, + 'livemode' => false, + 'type' => 'card', + 'used' => false + } + end + + def threeds_first_sources_created_response + <<-RESPONSE + { + "id": "src_1Dj5lqAWOtgoysogqA4CJX9Y", + "object": "source", + "amount": null, + "card": { + "exp_month": 9, + "exp_year": 2019, + "brand": "Visa", + "country": "US", + "cvc_check": "unchecked", + "fingerprint": "53W491Mwz0OMuEJr", + "funding": "credit", + "last4": "3063", + "three_d_secure": "required", + "name": null, + "address_line1_check": null, + "address_zip_check": null, + "tokenization_method": null, + "dynamic_last4": null + }, + "client_secret": "src_client_secret_EBShsJorDXd6WD521kRIQlbP", + "created": 1545228694, + "currency": null, + "flow": "none", + "livemode": false, + "metadata": { + }, + "owner": { + "address": null, + "email": null, + "name": null, + "phone": null, + "verified_address": null, + "verified_email": null, + "verified_name": null, + "verified_phone": null + }, + "statement_descriptor": null, + "status": "chargeable", + "type": "card", + "usage": "reusable" + } + RESPONSE + end + + def threeds_second_sources_created_response + <<-RESPONSE + { + "id": "src_1Dj5lrAWOtgoysog910mc8oS", + "object": "source", + "amount": 100, + "client_secret": "src_client_secret_EBShU4HfxQAw2bVGMxvRECO1", + "created": 1545228695, + "currency": "usd", + "flow": "redirect", + "livemode": false, + "metadata": { + }, + "owner": { + "address": { + "city": null, + "country": null, + "line1": "", + "line2": null, + "postal_code": null, + "state": null + }, + "email": null, + "name": null, + "phone": null, + "verified_address": null, + "verified_email": null, + "verified_name": null, + "verified_phone": null + }, + "redirect": { + "failure_reason": null, + "return_url": "http://www.example.com/callback", + "status": "pending", + "url": "https://hooks.stripe.com/redirect/authenticate/src_1Dj5lrAWOtgoysog910mc8oS?client_secret=src_client_secret_EBShU4HfxQAw2bVGMxvRECO1" + }, + "statement_descriptor": null, + "status": "pending", + "three_d_secure": { + "card": "src_1Dj5lqAWOtgoysogqA4CJX9Y", + "brand": "Visa", + "country": "US", + "cvc_check": "unchecked", + "exp_month": 9, + "exp_year": 2019, + "fingerprint": "53W491Mwz0OMuEJr", + "funding": "credit", + "last4": "3063", + "three_d_secure": "required", + "customer": null, + "authenticated": false, + "name": null, + "address_line1_check": null, + "address_zip_check": null, + "tokenization_method": null, + "dynamic_last4": null + }, + "type": "three_d_secure", + "usage": "single_use" + } + RESPONSE + end + + def non_3ds_sources_create_response + <<-RESPONSE + { + "id": "src_1Dj5yAAWOtgoysogPB6hwOa1", + "object": "source", + "amount": null, + "card": { + "exp_month": 9, + "exp_year": 2019, + "brand": "American Express", + "country": "US", + "cvc_check": "unchecked", + "fingerprint": "DjZpoV89lmOMsJLF", + "funding": "credit", + "last4": "0005", + "three_d_secure": "not_supported", + "name": null, + "address_line1_check": null, + "address_zip_check": null, + "tokenization_method": null, + "dynamic_last4": null + }, + "client_secret": "src_client_secret_EBStgH6cBMsODApAChcj9Kkq", + "created": 1545229458, + "currency": null, + "flow": "none", + "livemode": false, + "metadata": { + }, + "owner": { + "address": null, + "email": null, + "name": null, + "phone": null, + "verified_address": null, + "verified_email": null, + "verified_name": null, + "verified_phone": null + }, + "statement_descriptor": null, + "status": "chargeable", + "type": "card", + "usage": "reusable" + } + RESPONSE + end + + def webhook_event_creation_response + <<-RESPONSE + { + "id": "we_1Dj8GvAWOtgoysogAW1V5FFm", + "object": "webhook_endpoint", + "application": null, + "created": 1545238309, + "enabled_events": [ + "source.chargeable", + "source.failed", + "source.canceled" + ], + "livemode": false, + "secret": "whsec_sJVAv7f1rddt1bNhouoDvxwQbZ8t0Pgn", + "status": "enabled", + "url": "http://www.example.com/callback" + } + RESPONSE + end + + def webhook_event_deletion_response + <<-RESPONSE + { + "id": "we_1Dj8GvAWOtgoysogAW1V5FFm", + "object": "webhook_endpoint", + "deleted": true + } + RESPONSE + end end diff --git a/test/unit/gateways/swipe_checkout_test.rb b/test/unit/gateways/swipe_checkout_test.rb new file mode 100644 index 00000000000..e6c4e0da63a --- /dev/null +++ b/test/unit/gateways/swipe_checkout_test.rb @@ -0,0 +1,154 @@ +require 'test_helper' + +class SwipeCheckoutTest < Test::Unit::TestCase + def setup + @gateway = SwipeCheckoutGateway.new( + login: '0000000000000', + api_key: '0000000000000000000000000000000000000000000000000000000000000000', + region: 'NZ' + ) + + @credit_card = credit_card + @amount = 100 + + @options = { + order_id: '1', + billing_address: address, + description: 'Store Purchase' + } + end + + def test_supported_countries + assert @gateway.supported_countries == ['NZ', 'CA'] + end + + def test_successful_purchase + @gateway.expects(:ssl_post).returns(successful_purchase_response) + + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_instance_of Response, response + assert_success response + assert_equal 'Transaction approved', response.message + assert response.test? + end + + def test_successful_test_purchase + @gateway.expects(:ssl_post).returns(successful_test_purchase_response) + + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_instance_of Response, response + + assert_success response + assert response.test? + end + + def test_unsuccessful_purchase + @gateway.expects(:ssl_post).returns(failed_purchase_response) + + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert response.test? + end + + def test_unsuccessful_test_purchase + @gateway.expects(:ssl_post).returns(failed_test_purchase_response) + + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert response.test? + end + + def test_unsuccessful_request_invalid_card + @gateway.expects(:ssl_post).returns(failed_purchase_response_invalid_card) + + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert response.test? + end + + def test_unsuccessful_request_system_error + @gateway.expects(:ssl_post).returns(failed_purchase_response_system_error) + + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert response.test? + end + + def test_unsuccessful_request_incorrect_amount + @gateway.expects(:ssl_post).returns(failed_purchase_response_incorrect_amount) + + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert response.test? + end + + def test_unsuccessful_request_access_denied + @gateway.expects(:ssl_post).returns(failed_purchase_response_access_denied) + + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert response.test? + end + + def test_unsuccessful_request_not_enough_parameters + @gateway.expects(:ssl_post).returns(failed_purchase_response_not_enough_parameters) + + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert response.test? + end + + def test_unsuccessful_request_invalid_json_in_response + @gateway.expects(:ssl_post).returns(response_with_invalid_json) + + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert response.test? + end + + private + + def successful_purchase_response + '{"response_code": 200, "message": "OK", "data": {"tx_transaction_id": "00000000000000", "result": "accepted"}}' + end + + def successful_test_purchase_response + '{"response_code": 200, "message": "OK", "data": {"tx_transaction_id": "00000000000000", "result": "test-accepted"}}' + end + + def failed_purchase_response + '{"response_code": 200, "message": "OK", "data": {"tx_transaction_id": "00000000000000", "result": "declined"}}' + end + + def failed_test_purchase_response + '{"response_code": 200, "message": "OK", "data": {"tx_transaction_id": "00000000000000", "result": "test-declined"}}' + end + + def failed_purchase_response_invalid_card + build_failed_response 303, 'Invalid card data' + end + + def failed_purchase_response_system_error + build_failed_response 402, 'System error' + end + + def failed_purchase_response_incorrect_amount + build_failed_response 302, 'Incorrect amount' + end + + def failed_purchase_response_access_denied + build_failed_response 400, 'System error' + end + + def failed_purchase_response_not_enough_parameters + build_failed_response 403, 'Not enough parameters' + end + + def response_with_invalid_json + '{"response_code": ' + end + + def build_failed_response(code, message) + "{\"response_code\": #{code}, \"message\": \"#{message}\"}" + end +end diff --git a/test/unit/gateways/telr_test.rb b/test/unit/gateways/telr_test.rb new file mode 100644 index 00000000000..8e3ac5766bd --- /dev/null +++ b/test/unit/gateways/telr_test.rb @@ -0,0 +1,280 @@ +require 'test_helper' + +class TelrTest < Test::Unit::TestCase + include CommStub + + def setup + @gateway = TelrGateway.new( + merchant_id: 'login', + api_key: 'password' + ) + + @credit_card = credit_card + @amount = 100 + end + + def test_successful_purchase + response = stub_comms do + @gateway.purchase(@amount, @credit_card) + end.respond_with(successful_purchase_response) + + assert_success response + + assert_equal '029724176180|100|AED', response.authorization + assert response.test? + end + + def test_failed_purchase + response = stub_comms do + @gateway.purchase(@amount, @credit_card) + end.respond_with(failed_purchase_response) + + assert_failure response + assert_equal 'Not authorised', response.message + end + + def test_successful_authorize_and_capture + response = stub_comms do + @gateway.authorize(@amount, @credit_card) + end.respond_with(successful_authorize_response) + + assert_success response + assert_match(/029894296182/, response.authorization) + + capture = stub_comms do + @gateway.capture(@amount, response.authorization) + end.check_request do |endpoint, data, headers| + assert_match(/029894296182/, data) + end.respond_with(successful_capture_response) + + assert_success capture + end + + def test_failed_authorize + response = stub_comms do + @gateway.authorize(@amount, @credit_card) + end.respond_with(failed_authorize_response) + + assert_failure response + assert_equal 'Not authorised', response.message + end + + def test_failed_capture + response = stub_comms do + @gateway.capture(100, '') + end.respond_with(failed_capture_response) + + assert_failure response + end + + def test_successful_void + response = stub_comms do + @gateway.purchase(@amount, @credit_card) + end.respond_with(successful_authorize_response) + + assert_success response + assert_equal '029894296182|100|AED', response.authorization + + void = stub_comms do + @gateway.void(response.authorization) + end.check_request do |endpoint, data, headers| + assert_match(/029894296182/, data) + end.respond_with(successful_void_response) + + assert_success void + end + + def test_failed_void + response = stub_comms do + @gateway.void('5d53a33d960c46d00f5dc061947d998c') + end.check_request do |endpoint, data, headers| + assert_match(/5d53a33d960c46d00f5dc061947d998c/, data) + end.respond_with(failed_void_response) + + assert_failure response + end + + def test_successful_refund + response = stub_comms do + @gateway.purchase(@amount, @credit_card) + end.respond_with(successful_purchase_response) + + assert_success response + assert_equal '029724176180|100|AED', response.authorization + + refund = stub_comms do + @gateway.refund(@amount, response.authorization) + end.check_request do |endpoint, data, headers| + assert_match(/029724176180/, data) + end.respond_with(successful_refund_response) + + assert_success refund + end + + def test_failed_refund + response = stub_comms do + @gateway.refund(nil, '') + end.respond_with(failed_refund_response) + + assert_failure response + end + + def test_successful_verify + response = stub_comms do + @gateway.verify(@credit_card) + end.respond_with(successful_authorize_response, failed_void_response) + assert_success response + assert_equal 'Succeeded', response.message + end + + def test_failed_verify + response = stub_comms do + @gateway.verify(@credit_card) + end.respond_with(failed_authorize_response, successful_void_response) + assert_failure response + assert_equal 'Not authorised', response.message + end + + def test_successful_reference_purchase + response = stub_comms do + @gateway.purchase(@amount, @credit_card) + end.respond_with(successful_purchase_response) + + assert_success response + assert_match(/029724176180/, response.authorization) + + ref_purchase = stub_comms do + @gateway.purchase(@amount, response.authorization) + end.check_request do |endpoint, data, headers| + assert_match(/029724176180/, data) + end.respond_with(successful_reference_purchase_response) + + assert_success ref_purchase + end + + def test_transcript_scrubbing + assert_equal scrubbed_transcript, @gateway.scrub(transcript) + end + + private + + def successful_purchase_response + %( + <remote><auth><status>A</status><code>904492</code><message>Authorised</message><tranref>029724176180</tranref><cvv>Y</cvv><avs>X</avs><trace>4000/4491/5772cdab</trace></auth></remote> + ) + end + + def failed_purchase_response + %( + <remote><auth><status>D</status><code>31</code><message>Not authorised</message><tranref>020220116187</tranref><cvv>X</cvv><avs>X</avs><trace>4000/10194/577bcfae</trace></auth></remote> + ) + end + + def successful_authorize_response + %( + <remote><auth><status>A</status><code>926899</code><message>Authorised</message><tranref>029894296182</tranref><cvv>Y</cvv><avs>X</avs><trace>4000/26898/577531b2</trace></auth></remote> + ) + end + + def failed_authorize_response + %( + <remote><auth><status>D</status><code>31</code><message>Not authorised</message><tranref>020220646187</tranref><cvv>X</cvv><avs>X</avs><trace>4000/10700/577bd1ae</trace></auth></remote> + ) + end + + def successful_capture_response + %( + <remote><auth><status>A</status><code>926914</code><message>Authorised</message><tranref>017235426182</tranref><cvv>Y</cvv><avs>X</avs><trace>4000/26913/577531b6</trace></auth></remote> + ) + end + + def failed_capture_response + %( + <remote><auth><status>E</status><code>22</code><message>Invalid transaction reference</message><tranref>000000000000</tranref><cvv>X</cvv><avs>X</avs><trace>4000/10724/577bd1cc</trace></auth></remote> + ) + end + + def successful_void_response + %( + <remote><auth><status>A</status><code>923928</code><message>Processed</message><tranref>017319276183</tranref><cvv>Y</cvv><avs>X</avs><trace>4001/23927/5776b2e2</trace></auth></remote> + ) + end + + def failed_void_response + %( + <remote><auth><status>E</status><code>05</code><message>Transaction cost or currency not valid</message><tranref>000000000000</tranref><cvv>X</cvv><avs>X</avs><trace>4000/10757/577bd1fb</trace></auth></remote> + ) + end + + def successful_refund_response + %( + <remote><auth><status>A</status><code>927615</code><message>Accepted</message><tranref>029895196182</tranref><cvv>Y</cvv><avs>X</avs><trace>4000/27614/577533d0</trace></auth></remote> + ) + end + + def failed_refund_response + %( + <remote><auth><status>E</status><code>05</code><message>Transaction cost or currency not valid</message><tranref>000000000000</tranref><cvv>X</cvv><avs>X</avs><trace>4000/10779/577bd219</trace></auth></remote> + ) + end + + def successful_reference_purchase_response + %( + <remote><auth><status>A</status><code>930196</code><message>Authorised</message><tranref>017855576193</tranref><cvv>Y</cvv><avs>X</avs><trace>4000/30195/5783aee5</trace></auth></remote> + ) + end + + def transcript + %q( + opening connection to secure.telr.com:443... + opened + starting SSL for secure.telr.com:443... + SSL established + <- "POST /gateway/remote.xml HTTP/1.1\r\nContent-Type: text/xml\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: secure.telr.com\r\nContent-Length: 864\r\n\r\n" + <- "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<remote>\n <store>16715</store>\n <key>WZV7W#gbMVw^kSBk</key>\n <tran>\n <type>sale</type>\n <amount>1.00</amount>\n <currency>USD</currency>\n <cartid>eb82363979530f39ef983a9edbe99611</cartid>\n <class>moto</class>\n <description>Test transaction</description>\n <test>1</test>\n </tran>\n <card>\n <number>5105105105105100</number>\n <cvv>123</cvv>\n <expiry>\n <month>09</month>\n <year>17</year>\n </expiry>\n </card>\n <billing>\n <name>\n <first>Longbob</first>\n <last>Longsen</last>\n </name>\n <email>email@address.com</email>\n <ip>107.15.245.37</ip>\n <address>\n <country>CA</country>\n <region>ON</region>\n <line1>456 My Street</line1>\n <line2>Apt 1</line2>\n <city>Ottawa</city>\n <zip>K1C2N6</zip>\n </address>\n </billing>\n</remote>\n" + -> "HTTP/1.1 200 OK\r\n" + -> "Date: Tue, 05 Jul 2016 15:39:29 GMT\r\n" + -> "Server: Apache\r\n" + -> "Expires: -1\r\n" + -> "Cache-Control: no-cache\r\n" + -> "CacheControl: no-cache\r\n" + -> "Pragma: no-cache\r\n" + -> "Content-Length: 226\r\n" + -> "Content-Type: text/xml;charset=UTF-8\r\n" + -> "Connection: close\r\n" + -> "\r\n" + reading 226 bytes... + -> "" + -> "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<remote><auth><status>A</status><code>911424</code><message>Authorised</message><tranref>017562546187</tranref><cvv>Y</cvv><avs>X</avs><trace>4000/11423/577bd4ae</trace></auth></remote>\n\n" + read 226 bytes + Conn close + ) + end + + def scrubbed_transcript + %q( + opening connection to secure.telr.com:443... + opened + starting SSL for secure.telr.com:443... + SSL established + <- "POST /gateway/remote.xml HTTP/1.1\r\nContent-Type: text/xml\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: secure.telr.com\r\nContent-Length: 864\r\n\r\n" + <- "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<remote>\n <store>16715</store>\n <key>[FILTERED]</key>\n <tran>\n <type>sale</type>\n <amount>1.00</amount>\n <currency>USD</currency>\n <cartid>eb82363979530f39ef983a9edbe99611</cartid>\n <class>moto</class>\n <description>Test transaction</description>\n <test>1</test>\n </tran>\n <card>\n <number>[FILTERED]</number>\n <cvv>[FILTERED]</cvv>\n <expiry>\n <month>09</month>\n <year>17</year>\n </expiry>\n </card>\n <billing>\n <name>\n <first>Longbob</first>\n <last>Longsen</last>\n </name>\n <email>email@address.com</email>\n <ip>107.15.245.37</ip>\n <address>\n <country>CA</country>\n <region>ON</region>\n <line1>456 My Street</line1>\n <line2>Apt 1</line2>\n <city>Ottawa</city>\n <zip>K1C2N6</zip>\n </address>\n </billing>\n</remote>\n" + -> "HTTP/1.1 200 OK\r\n" + -> "Date: Tue, 05 Jul 2016 15:39:29 GMT\r\n" + -> "Server: Apache\r\n" + -> "Expires: -1\r\n" + -> "Cache-Control: no-cache\r\n" + -> "CacheControl: no-cache\r\n" + -> "Pragma: no-cache\r\n" + -> "Content-Length: 226\r\n" + -> "Content-Type: text/xml;charset=UTF-8\r\n" + -> "Connection: close\r\n" + -> "\r\n" + reading 226 bytes... + -> "" + -> "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<remote><auth><status>A</status><code>911424</code><message>Authorised</message><tranref>017562546187</tranref><cvv>[FILTERED]</cvv><avs>X</avs><trace>4000/11423/577bd4ae</trace></auth></remote>\n\n" + read 226 bytes + Conn close + ) + end +end diff --git a/test/unit/gateways/tns_test.rb b/test/unit/gateways/tns_test.rb new file mode 100644 index 00000000000..f97a0e6e242 --- /dev/null +++ b/test/unit/gateways/tns_test.rb @@ -0,0 +1,256 @@ +require 'test_helper' + +class TnsTest < Test::Unit::TestCase + include CommStub + def setup + @gateway = TnsGateway.new( + userid: 'userid', + password: 'password' + ) + + @credit_card = credit_card + @amount = 100 + + @options = { + order_id: '1', + billing_address: address, + description: 'Store Purchase' + } + end + + def test_successful_purchase + @gateway.expects(:ssl_request).twice.returns(successful_authorize_response).then.returns(successful_capture_response) + + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + + assert_equal '2a79d859-8b23-4dd0-b319-201fe2373c50|ce61e06e-8c92-4a0f-a491-6eb473d883dd', response.authorization + assert response.test? + end + + def test_failed_purchase + response = stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, @credit_card) + end.respond_with(failed_purchase_response) + + assert_failure response + assert_equal 'FAILURE - DECLINED', response.message + assert response.test? + end + + def test_authorize_and_capture + response = stub_comms(@gateway, :ssl_request) do + @gateway.authorize(@amount, @credit_card) + end.respond_with(successful_authorize_response) + + assert_success response + assert_equal '91debbeb-d88f-42e9-a6ce-9b62c99d656b|f3d100a7-18d9-4609-aabc-8a710ad0e210', response.authorization + + capture = stub_comms(@gateway, :ssl_request) do + @gateway.capture(@amount, response.authorization) + end.check_request do |method, endpoint, data, headers| + assert_match(/f3d100a7-18d9-4609-aabc-8a710ad0e210/, data) + end.respond_with(successful_capture_response) + + assert_success capture + end + + def test_refund + response = stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, @credit_card) + end.respond_with(successful_capture_response) + + assert_success response + assert_equal '2a79d859-8b23-4dd0-b319-201fe2373c50|ce61e06e-8c92-4a0f-a491-6eb473d883dd', response.authorization + + refund = stub_comms(@gateway, :ssl_request) do + @gateway.refund(@amount, response.authorization) + end.check_request do |method, endpoint, data, headers| + assert_match(/ce61e06e-8c92-4a0f-a491-6eb473d883dd/, data) + end.respond_with(successful_refund_response) + + assert_success refund + end + + def test_void + response = stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, @credit_card) + end.respond_with(successful_capture_response) + + assert_success response + assert_equal '2a79d859-8b23-4dd0-b319-201fe2373c50|ce61e06e-8c92-4a0f-a491-6eb473d883dd', response.authorization + + void = stub_comms(@gateway, :ssl_request) do + @gateway.void(response.authorization) + end.check_request do |method, endpoint, data, headers| + assert_match(/ce61e06e-8c92-4a0f-a491-6eb473d883dd/, data) + end.respond_with(successful_void_response) + + assert_success void + end + + def test_passing_alpha3_country_code + stub_comms(@gateway, :ssl_request) do + @gateway.authorize(@amount, @credit_card, :billing_address => {country: 'US'}) + end.check_request do |method, endpoint, data, headers| + assert_match(/USA/, data) + end.respond_with(successful_authorize_response) + end + + def test_non_existent_country + stub_comms(@gateway, :ssl_request) do + @gateway.authorize(@amount, @credit_card, :billing_address => {country: 'Blah'}) + end.check_request do |method, endpoint, data, headers| + assert_match(/"country":null/, data) + end.respond_with(successful_authorize_response) + end + + def test_passing_cvv + stub_comms(@gateway, :ssl_request) do + @gateway.authorize(@amount, @credit_card) + end.check_request do |method, endpoint, data, headers| + assert_match(/#{@credit_card.verification_value}/, data) + end.respond_with(successful_authorize_response) + end + + def test_passing_billing_address + stub_comms(@gateway, :ssl_request) do + @gateway.authorize(@amount, @credit_card, :billing_address => address) + end.check_request do |method, endpoint, data, headers| + parsed = JSON.parse(data) + assert_equal('456 My Street', parsed['billing']['address']['street']) + assert_equal('K1C2N6', parsed['billing']['address']['postcodeZip']) + end.respond_with(successful_authorize_response) + end + + def test_passing_shipping_name + stub_comms(@gateway, :ssl_request) do + @gateway.authorize(@amount, @credit_card, :shipping_address => address) + end.check_request do |method, endpoint, data, headers| + parsed = JSON.parse(data) + assert_equal('Jim', parsed['shipping']['firstName']) + assert_equal('Smith', parsed['shipping']['lastName']) + end.respond_with(successful_authorize_response) + end + + def test_successful_verify + response = stub_comms(@gateway, :ssl_request) do + @gateway.verify(@credit_card, @options) + end.respond_with(successful_authorize_response, successful_void_response) + assert_success response + assert_equal '91debbeb-d88f-42e9-a6ce-9b62c99d656b', response.params['order']['id'] + end + + def test_successful_verify_with_failed_void + response = stub_comms(@gateway, :ssl_request) do + @gateway.verify(@credit_card, @options) + end.respond_with(successful_authorize_response, failed_void_response) + assert_success response + assert_equal 'Succeeded', response.message + end + + def test_unsuccessful_verify + response = stub_comms(@gateway, :ssl_request) do + @gateway.verify(@credit_card, @options) + end.respond_with(failed_authorize_response, successful_void_response) + assert_failure response + assert_equal 'FAILURE - DECLINED', response.message + end + + def test_north_america_region_url + @gateway = TnsGateway.new( + userid: 'userid', + password: 'password', + region: 'north_america' + ) + + response = stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, @credit_card, @options) + end.check_request do |method, endpoint, data, headers| + assert_match(/secure.na.tnspayments.com/, endpoint) + end.respond_with(successful_capture_response) + + assert_success response + end + + def test_asia_pacific_region_url + @gateway = TnsGateway.new( + userid: 'userid', + password: 'password', + region: 'asia_pacific' + ) + + response = stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, @credit_card, @options) + end.check_request do |method, endpoint, data, headers| + assert_match(/secure.ap.tnspayments.com/, endpoint) + end.respond_with(successful_capture_response) + + assert_success response + end + + def test_scrub + assert @gateway.supports_scrubbing? + assert_equal @gateway.scrub(pre_scrubbed), post_scrubbed + end + + private + + def pre_scrubbed + %q[ + D, {"provided":{"card":{"expiry":{"year":"17","month":"09"},"number":"5123456789012346","securityCode":"123"}},"type":"CARD"} + <- transaction/1 HTTP/1.1\r\nAuthorization: Basic bWVyY2hhbnQuVEVTVFNQUkVFRExZMDE6M2YzNGZlNTAzMzRmYmU2Y2JlMDRjMjgzNDExYTU4NjA=\r\nContent-Type: + <- {\"order\":{\"amount\":\"1.00\",\"currency\":\"USD\"},\"sourceOfFunds\":{\"provided\":{\"card\":{\"expiry\":{\"year\":\"17\",\"month\":\"09\"},\"number\":\"5123456789012346\",\"securityCode\":\"123\"}},\"type\":\"CARD\"} + ] + end + + def post_scrubbed + %q[ + D, {"provided":{"card":{"expiry":{"year":"17","month":"09"},"number":"[FILTERED]","securityCode":"[FILTERED]"}},"type":"CARD"} + <- transaction/1 HTTP/1.1\r\nAuthorization: Basic [FILTERED]Content-Type: + <- {\"order\":{\"amount\":\"1.00\",\"currency\":\"USD\"},\"sourceOfFunds\":{\"provided\":{\"card\":{\"expiry\":{\"year\":\"17\",\"month\":\"09\"},\"number\":\"[FILTERED]\",\"securityCode\":\"[FILTERED]\"}},\"type\":\"CARD\"} + ] + end + + def successful_authorize_response + %( + {"billing":{"address":{"city":"Ottawa","country":"USA","postcodeZip":"K1C2N6","stateProvince":"ON","street":"456 My Street, Apt 1"},"phone":"(555)555-5555"},"gatewayEntryPoint":"WEB_SERVICES_API","merchant":"TESTSPREEDLY01","order":{"amount":1.00,"creationTime":"2014-10-16T17:23:56.444Z","currency":"USD","id":"91debbeb-d88f-42e9-a6ce-9b62c99d656b","status":"CAPTURED","totalAuthorizedAmount":1.00,"totalCapturedAmount":1.00,"totalRefundedAmount":0.00},"response":{"acquirerCode":"0","acquirerMessage":"Transaction is approved","cardSecurityCode":{"acquirerCode":"N","gatewayCode":"NO_MATCH"},"gatewayCode":"APPROVED","risk":{"gatewayCode":"ACCEPTED","review":{"decision":"NOT_REQUIRED"},"rule":[{"data":"512345","name":"MERCHANT_BIN_RANGE","recommendation":"NO_ACTION","type":"MERCHANT_RULE"},{"data":"N","name":"MERCHANT_CSC","recommendation":"NO_ACTION","type":"MERCHANT_RULE"},{"name":"SUSPECT_CARD_LIST","recommendation":"NO_ACTION","type":"MERCHANT_RULE"},{"name":"TRUSTED_CARD_LIST","recommendation":"NO_ACTION","type":"MERCHANT_RULE"},{"data":"512345","name":"MSO_BIN_RANGE","recommendation":"NO_ACTION","type":"MSO_RULE"},{"data":"N","name":"MSO_CSC","recommendation":"NO_ACTION","type":"MSO_RULE"}]}},"result":"SUCCESS","sourceOfFunds":{"provided":{"card":{"brand":"MASTERCARD","expiry":{"month":"9","year":"15"},"fundingMethod":"CREDIT","number":"512345xxxxxx2346","scheme":"MASTERCARD"}},"type":"CARD"},"timeOfRecord":"2014-10-16T17:23:57.083Z","transaction":{"acquirer":{"batch":1,"id":"PAYMENTECH_TAMPA","merchantId":"1234678"},"amount":1.00,"authorizationCode":"005163","currency":"USD","frequency":"SINGLE","id":"f3d100a7-18d9-4609-aabc-8a710ad0e210","receipt":"428917000180","reference":"1","source":"INTERNET","terminal":"002","type":"CAPTURE"},"version":"22"} + ) + end + + def successful_capture_response + %( + {"billing":{"address":{"city":"Ottawa","country":"USA","postcodeZip":"K1C2N6","stateProvince":"ON","street":"456 My Street, Apt 1"},"phone":"(555)555-5555"},"gatewayEntryPoint":"WEB_SERVICES_API","merchant":"TESTSPREEDLY01","order":{"amount":1.00,"creationTime":"2014-10-16T17:28:32.999Z","currency":"USD","id":"2a79d859-8b23-4dd0-b319-201fe2373c50","status":"CAPTURED","totalAuthorizedAmount":1.00,"totalCapturedAmount":1.00,"totalRefundedAmount":0.00},"response":{"acquirerCode":"0","acquirerMessage":"Transaction is approved","cardSecurityCode":{"acquirerCode":"N","gatewayCode":"NO_MATCH"},"gatewayCode":"APPROVED","risk":{"gatewayCode":"ACCEPTED","review":{"decision":"NOT_REQUIRED"},"rule":[{"data":"512345","name":"MERCHANT_BIN_RANGE","recommendation":"NO_ACTION","type":"MERCHANT_RULE"},{"data":"N","name":"MERCHANT_CSC","recommendation":"NO_ACTION","type":"MERCHANT_RULE"},{"name":"SUSPECT_CARD_LIST","recommendation":"NO_ACTION","type":"MERCHANT_RULE"},{"name":"TRUSTED_CARD_LIST","recommendation":"NO_ACTION","type":"MERCHANT_RULE"},{"data":"512345","name":"MSO_BIN_RANGE","recommendation":"NO_ACTION","type":"MSO_RULE"},{"data":"N","name":"MSO_CSC","recommendation":"NO_ACTION","type":"MSO_RULE"}]}},"result":"SUCCESS","sourceOfFunds":{"provided":{"card":{"brand":"MASTERCARD","expiry":{"month":"9","year":"15"},"fundingMethod":"CREDIT","number":"512345xxxxxx2346","scheme":"MASTERCARD"}},"type":"CARD"},"timeOfRecord":"2014-10-16T17:28:33.685Z","transaction":{"acquirer":{"batch":1,"id":"PAYMENTECH_TAMPA","merchantId":"1234678"},"amount":1.00,"authorizationCode":"005202","currency":"USD","frequency":"SINGLE","id":"ce61e06e-8c92-4a0f-a491-6eb473d883dd","receipt":"428917000182","reference":"1","source":"INTERNET","terminal":"002","type":"CAPTURE"},"version":"22"} + ) + end + + def failed_purchase_response + %( + {"billing":{"address":{"city":"Ottawa","country":"USA","postcodeZip":"K1C2N6","stateProvince":"ON","street":"456 My Street, Apt 1"},"phone":"(555)555-5555"},"gatewayEntryPoint":"WEB_SERVICES_API","merchant":"TESTSPREEDLY01","order":{"amount":1.00,"creationTime":"2014-10-16T18:25:46.095Z","currency":"USD","id":"fb21987d-a646-48f1-aa6a-07028a74a956","status":"FAILED","totalAuthorizedAmount":0.00,"totalCapturedAmount":0.00,"totalRefundedAmount":0.00},"response":{"cardSecurityCode":{"acquirerCode":"N","gatewayCode":"NO_MATCH"},"gatewayCode":"DECLINED","risk":{"gatewayCode":"ACCEPTED","review":{"decision":"NOT_REQUIRED"},"rule":[{"data":"400030","name":"MERCHANT_BIN_RANGE","recommendation":"NO_ACTION","type":"MERCHANT_RULE"},{"name":"SUSPECT_CARD_LIST","recommendation":"NO_ACTION","type":"MERCHANT_RULE"},{"name":"TRUSTED_CARD_LIST","recommendation":"NO_ACTION","type":"MERCHANT_RULE"},{"data":"400030","name":"MSO_BIN_RANGE","recommendation":"NO_ACTION","type":"MSO_RULE"}]}},"result":"FAILURE","sourceOfFunds":{"provided":{"card":{"brand":"VISA","expiry":{"month":"9","year":"15"},"fundingMethod":"CREDIT","number":"400030xxxxxx2220","scheme":"VISA"}},"type":"CARD"},"timeOfRecord":"2014-10-16T18:25:46.095Z","transaction":{"acquirer":{"batch":1,"id":"PAYMENTECH_TAMPA","merchantId":"1234678"},"amount":1.00,"currency":"USD","frequency":"SINGLE","id":"1","receipt":"428918000183","source":"INTERNET","terminal":"002","type":"AUTHORIZATION"},"version":"22"} + ) + end + + def successful_refund_response + %( + {"billing":{"address":{"city":"Ottawa","country":"USA","postcodeZip":"K1C2N6","stateProvince":"ON","street":"456 My Street, Apt 1"},"phone":"(555)555-5555"},"gatewayEntryPoint":"WEB_SERVICES_API","merchant":"TESTSPREEDLY01","order":{"amount":1.00,"creationTime":"2014-10-16T18:49:35.969Z","currency":"USD","id":"98619fc2-3f30-4f3a-9199-84e435dfa498","status":"REFUNDED","totalAuthorizedAmount":1.00,"totalCapturedAmount":1.00,"totalRefundedAmount":1.00},"response":{"acquirerCode":"0","acquirerMessage":"Transaction is approved","cardSecurityCode":{"acquirerCode":"N","gatewayCode":"NO_MATCH"},"gatewayCode":"APPROVED","risk":{"gatewayCode":"ACCEPTED","review":{"decision":"NOT_REQUIRED"},"rule":[{"data":"512345","name":"MERCHANT_BIN_RANGE","recommendation":"NO_ACTION","type":"MERCHANT_RULE"},{"data":"N","name":"MERCHANT_CSC","recommendation":"NO_ACTION","type":"MERCHANT_RULE"},{"name":"SUSPECT_CARD_LIST","recommendation":"NO_ACTION","type":"MERCHANT_RULE"},{"name":"TRUSTED_CARD_LIST","recommendation":"NO_ACTION","type":"MERCHANT_RULE"},{"data":"512345","name":"MSO_BIN_RANGE","recommendation":"NO_ACTION","type":"MSO_RULE"},{"data":"N","name":"MSO_CSC","recommendation":"NO_ACTION","type":"MSO_RULE"}]}},"result":"SUCCESS","sourceOfFunds":{"provided":{"card":{"brand":"MASTERCARD","expiry":{"month":"9","year":"15"},"fundingMethod":"CREDIT","number":"512345xxxxxx2346","scheme":"MASTERCARD"}},"type":"CARD"},"timeOfRecord":"2014-10-16T18:49:37.417Z","transaction":{"acquirer":{"batch":1,"id":"PAYMENTECH_TAMPA","merchantId":"1234678"},"amount":1.00,"currency":"USD","frequency":"SINGLE","id":"9f8a3bc9-9a00-40a7-98ea-8113fa53c018","receipt":"428918000186","reference":"1","source":"INTERNET","terminal":"002","type":"REFUND"},"version":"22"} + ) + end + + def successful_void_response + %( + {"billing":{"address":{"city":"Ottawa","country":"USA","postcodeZip":"K1C2N6","stateProvince":"ON","street":"456 My Street, Apt 1"},"phone":"(555)555-5555"},"gatewayEntryPoint":"WEB_SERVICES_API","merchant":"TESTSPREEDLY01","order":{"amount":1.00,"creationTime":"2014-10-16T18:57:00.277Z","currency":"USD","id":"fb1125bd-b169-48a2-878d-18831639ec08","status":"CANCELLED","totalAuthorizedAmount":0.00,"totalCapturedAmount":0.00,"totalRefundedAmount":0.00},"response":{"acquirerCode":"000","acquirerMessage":"Approved","cardSecurityCode":{"acquirerCode":"N","gatewayCode":"NO_MATCH"},"gatewayCode":"APPROVED","risk":{"gatewayCode":"ACCEPTED","review":{"decision":"NOT_REQUIRED"},"rule":[{"data":"512345","name":"MERCHANT_BIN_RANGE","recommendation":"NO_ACTION","type":"MERCHANT_RULE"},{"data":"N","name":"MERCHANT_CSC","recommendation":"NO_ACTION","type":"MERCHANT_RULE"},{"name":"SUSPECT_CARD_LIST","recommendation":"NO_ACTION","type":"MERCHANT_RULE"},{"name":"TRUSTED_CARD_LIST","recommendation":"NO_ACTION","type":"MERCHANT_RULE"},{"data":"512345","name":"MSO_BIN_RANGE","recommendation":"NO_ACTION","type":"MSO_RULE"},{"data":"N","name":"MSO_CSC","recommendation":"NO_ACTION","type":"MSO_RULE"}]}},"result":"SUCCESS","sourceOfFunds":{"provided":{"card":{"brand":"MASTERCARD","expiry":{"month":"9","year":"15"},"fundingMethod":"CREDIT","number":"512345xxxxxx2346","scheme":"MASTERCARD"}},"type":"CARD"},"timeOfRecord":"2014-10-16T18:57:01.132Z","transaction":{"acquirer":{"batch":1,"id":"PAYMENTECH_TAMPA","merchantId":"1234678"},"amount":1.00,"currency":"USD","frequency":"SINGLE","id":"e648c580-9edf-4baa-b05e-5eeadab3f86e","receipt":"428918000188","source":"INTERNET","terminal":"002","type":"VOID_AUTHORIZATION"},"version":"22"} + ) + end + + def failed_authorize_response + %( + {"billing":{"address":{"city":"Ottawa","country":"USA","postcodeZip":"K1C2N6","stateProvince":"ON","street":"456 My Street, Apt 1"},"phone":"(555)555-5555"},"gatewayEntryPoint":"WEB_SERVICES_API","merchant":"TESTSPREEDLY01","order":{"amount":1.00,"creationTime":"2014-10-17T12:44:15.432Z","currency":"USD","id":"1a0abf5e-3d1f-4c1b-bd0f-94a64aa9304c","status":"FAILED","totalAuthorizedAmount":0.00,"totalCapturedAmount":0.00,"totalRefundedAmount":0.00},"response":{"cardSecurityCode":{"acquirerCode":"N","gatewayCode":"NO_MATCH"},"gatewayCode":"DECLINED","risk":{"gatewayCode":"ACCEPTED","review":{"decision":"NOT_REQUIRED"},"rule":[{"data":"400030","name":"MERCHANT_BIN_RANGE","recommendation":"NO_ACTION","type":"MERCHANT_RULE"},{"name":"SUSPECT_CARD_LIST","recommendation":"NO_ACTION","type":"MERCHANT_RULE"},{"name":"TRUSTED_CARD_LIST","recommendation":"NO_ACTION","type":"MERCHANT_RULE"},{"data":"400030","name":"MSO_BIN_RANGE","recommendation":"NO_ACTION","type":"MSO_RULE"}]}},"result":"FAILURE","sourceOfFunds":{"provided":{"card":{"brand":"VISA","expiry":{"month":"9","year":"15"},"fundingMethod":"CREDIT","number":"400030xxxxxx2220","scheme":"VISA"}},"type":"CARD"},"timeOfRecord":"2014-10-17T12:44:15.432Z","transaction":{"acquirer":{"batch":1,"id":"PAYMENTECH_TAMPA","merchantId":"1234678"},"amount":1.00,"currency":"USD","frequency":"SINGLE","id":"1","receipt":"429012000210","source":"INTERNET","terminal":"002","type":"AUTHORIZATION"},"version":"22"} + ) + end + + def failed_void_response + %( + {\"error\":{\"cause\":\"INVALID_REQUEST\",\"explanation\":\"Value 'VOID' is invalid. There is no transaction to void.\",\"field\":\"apiOperation\",\"validationType\":\"INVALID\"},\"result\":\"ERROR\"} + ) + end +end diff --git a/test/unit/gateways/trans_first_test.rb b/test/unit/gateways/trans_first_test.rb index e3fa53cc046..34f04f9a75c 100644 --- a/test/unit/gateways/trans_first_test.rb +++ b/test/unit/gateways/trans_first_test.rb @@ -9,104 +9,362 @@ def setup ) @credit_card = credit_card('4242424242424242') + @check = check @options = { :billing_address => address } @amount = 100 end - + def test_missing_field_response @gateway.stubs(:ssl_post).returns(missing_field_response) - + response = @gateway.purchase(@amount, @credit_card, @options) - + assert_failure response assert response.test? assert_equal 'Missing parameter: UserId.', response.message end - + def test_successful_purchase @gateway.stubs(:ssl_post).returns(successful_purchase_response) - + response = @gateway.purchase(@amount, @credit_card, @options) - + assert_success response assert response.test? assert_equal 'test transaction', response.message - assert_equal '355', response.authorization + assert_equal '355|creditcard', response.authorization end - + def test_failed_purchase @gateway.stubs(:ssl_post).returns(failed_purchase_response) - + response = @gateway.purchase(@amount, @credit_card, @options) - + assert_failure response assert response.test? - assert_equal '29005716', response.authorization + assert_equal '29005716|creditcard', response.authorization assert_equal 'Invalid cardholder number', response.message end - + + def test_successful_purchase_with_echeck + @gateway.stubs(:ssl_post).returns(successful_purchase_echeck_response) + response = @gateway.purchase(@amount, @check, @options) + + assert_success response + end + + def test_failed_purchase_with_echeck + @gateway.stubs(:ssl_post).returns(failed_purchase_echeck_response) + response = @gateway.purchase(@amount, @check, @options) + + assert_failure response + end + + def test_successful_refund + @gateway.stubs(:ssl_post).returns(successful_refund_response) + + response = @gateway.refund(@amount, 'TransID') + assert_success response + assert_equal '207686608|creditcard', response.authorization + assert_equal @amount, response.params['amount'].to_i*100 + end + + def test_failed_refund + @gateway.stubs(:ssl_post).returns(failed_refund_response) + + response = @gateway.refund(@amount, 'TransID') + assert_failure response + end + + def test_successful_void + @gateway.stubs(:ssl_post).returns(successful_void_response) + + response = @gateway.void('TransID') + assert_success response + end + + def test_failed_void + @gateway.stubs(:ssl_post).returns(failed_void_response) + + response = @gateway.void('TransID') + assert_failure response + end + def test_avs_result @gateway.expects(:ssl_post).returns(successful_purchase_response) - + response = @gateway.purchase(@amount, @credit_card, @options) assert_equal 'X', response.avs_result['code'] end - + def test_cvv_result @gateway.expects(:ssl_post).returns(successful_purchase_response) - + response = @gateway.purchase(@amount, @credit_card, @options) assert_equal 'M', response.cvv_result['code'] end - + + def test_scrub + assert @gateway.supports_scrubbing? + assert_equal @gateway.scrub(pre_scrubbed), post_scrubbed + end + + def test_scrub_echeck + assert @gateway.supports_scrubbing? + assert_equal @gateway.scrub(pre_scrubbed_echeck), post_scrubbed_echeck + end + private + def missing_field_response "Missing parameter: UserId.\r\n" end - + + def pre_scrubbed + %q( + opening connection to ws.cert.transfirst.com:443... + opened + starting SSL for ws.cert.transfirst.com:443... + SSL established + <- "POST /creditcard.asmx/CCSale HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: ws.cert.transfirst.com\r\nContent-Length: 507\r\n\r\n" + <- "Amount=12.01&RefID=80d6ca993942c62f90079990e3b6ae8f&SECCCode=ActiveMerchant+Sale&PONumber=ActiveMerchant+Sale&SaleTaxAmount=0.00&PaymentDesc=&TaxIndicator=0&CompanyName=&CardHolderName=Longbob+Longsen&CardNumber=4485896261017708&Expiration=0916&CVV2=999&Address=456+My+Street&ZipCode=K1C2N6&ECIValue=&UserId=&CAVVData=&TrackData=&POSInd=&EComInd=&MerchZIP=&MerchCustPNum=&MCC=&InstallmentNum=&InstallmentOf=&POSEntryMode=&POSConditionCode=&AuthCharInd=&CardCertData=&MerchantID=45567&RegKey=TNYYKYMFZ59HSN7Q" + -> "HTTP/1.1 200 OK\r\n" + -> "Cache-Control: private, max-age=0\r\n" + -> "Content-Type: text/xml; charset=utf-8\r\n" + -> "Server: Microsoft-IIS/7.5\r\n" + -> "X-AspNet-Version: 2.0.50727\r\n" + -> "X-Powered-By: ASP.NET\r\n" + -> "Date: Wed, 16 Dec 2015 18:46:28 GMT\r\n" + -> "Cteonnt-Length: 679\r\n" + -> "Set-Cookie: NSC_JOb34jhgcucdnowdxp3wxrei43g5edn=ffffffff0918171e45525d5f4f58455e445a4a42378b;expires=Wed, 16-Dec-2015 18:55:08 GMT;path=/;secure;httponly\r\n" + -> "Content-Encoding: gzip\r\n" + -> "Content-Length: 383\r\n" + -> "\r\n" + reading 383 bytes... + -> "\x1F\x8B\b\x00\x00\x00\x00\x00\x00\x03\x8D\x92\xD1k\x830\x10\xC6\xDF\a\xFB\x1F\xC4wMb[\xABb-\xC5\xBE\x14V\x18u\xC8^\xD3x\xB6\x01M\x8A\x89\xB5\xFB\xEF\x97j-\x0E\x06\xDB\xDB\xE5\xBB\xEFw\x17\xEE.^\xDF\xEA\xCA\xBAB\xA3\xB8\x14+\x9B\xB8\xD8\xB6@0YpqZ\xD9\xAD.\x9D\xC0^'\xAF/q\x9Af\xB4\x82-\x1C\xB9>\x80\xBAH\xA1\xC02\xA8P\xD1M\xF1\x95}\xD6\xFA\x12!\xD4u\x9D\xDB\xCD\\\xD9\x9C\x90\x871A\x9F\xFB\xB7\x8C\x9D\xA1\xA6\x0E\x17JS\xC1\xC0~R\xC5\xDF\xD4\xC3\xFC\xC3x\xA1_5\b\xDD\x80\x92m\xC3@\xB9L\xD6\xA8\x83\xA3\x82\xE6\xCA\xCD\e\xD9\xE6\xBB\x96\x15\x7F4T\xA8\xDD6\xC11\x1A\xC3^?@i\xC2\x00\x17>\xA3a8\v\xE7\x1E\xF3\xBD2\xC4x\x19\x86!\x86\xD9\xD1\xA7\x10\x941\x1A|=\xF2.\x95\x86bK5$\x1E&\v\x87x\x0E\xF1?\b\x8E\xE6~\xE4\x05.Y\x06$X,\x1C\x1CD\xD8t\x9B\xB8{:\x03\xAD\xAB\xFF\xE3S{\xCFoj\xD9\n\x9D\x10\xCF\xC5$F\x8F\xD7\x90i\xF59\x95\x05X\xE8\xD1IS\xDD\xAA$\xBD\xCF\xD9\x940\xB5\x06a0\xE7\xD9\xD4\xBB\a\xA5\xE8\t\x92\xA2\xBDT\x9C\x99f\x962\xFB\x8D\xD1\xA8\xF7\xA64\xCF\xBD\xB4Iw\xCF\xD0\xF4\xCE\xFA\r\x8CJ?e\xCA\xB49\xA4]a6\xC4K\x0E\xCD\x98\xCCi\xC5\vz\xCFM\xEB\xA5\x9B<7\xD7\xD4V\xFA\xA9\xC6\xE8\x97KK\xBE\x01\xFA\xDC>T\xA7\x02\x00\x00" + read 383 bytes + Conn close + ) + end + + def post_scrubbed + %q( + opening connection to ws.cert.transfirst.com:443... + opened + starting SSL for ws.cert.transfirst.com:443... + SSL established + <- "POST /creditcard.asmx/CCSale HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: ws.cert.transfirst.com\r\nContent-Length: 507\r\n\r\n" + <- "Amount=12.01&RefID=80d6ca993942c62f90079990e3b6ae8f&SECCCode=ActiveMerchant+Sale&PONumber=ActiveMerchant+Sale&SaleTaxAmount=0.00&PaymentDesc=&TaxIndicator=0&CompanyName=&CardHolderName=Longbob+Longsen&CardNumber=[FILTERED]&Expiration=0916&CVV2=[FILTERED]&Address=456+My+Street&ZipCode=K1C2N6&ECIValue=&UserId=&CAVVData=&TrackData=&POSInd=&EComInd=&MerchZIP=&MerchCustPNum=&MCC=&InstallmentNum=&InstallmentOf=&POSEntryMode=&POSConditionCode=&AuthCharInd=&CardCertData=&MerchantID=45567&RegKey=[FILTERED]" + -> "HTTP/1.1 200 OK\r\n" + -> "Cache-Control: private, max-age=0\r\n" + -> "Content-Type: text/xml; charset=utf-8\r\n" + -> "Server: Microsoft-IIS/7.5\r\n" + -> "X-AspNet-Version: 2.0.50727\r\n" + -> "X-Powered-By: ASP.NET\r\n" + -> "Date: Wed, 16 Dec 2015 18:46:28 GMT\r\n" + -> "Cteonnt-Length: 679\r\n" + -> "Set-Cookie: NSC_JOb34jhgcucdnowdxp3wxrei43g5edn=ffffffff0918171e45525d5f4f58455e445a4a42378b;expires=Wed, 16-Dec-2015 18:55:08 GMT;path=/;secure;httponly\r\n" + -> "Content-Encoding: gzip\r\n" + -> "Content-Length: 383\r\n" + -> "\r\n" + reading 383 bytes... + -> "\x1F\x8B\b\x00\x00\x00\x00\x00\x00\x03\x8D\x92\xD1k\x830\x10\xC6\xDF\a\xFB\x1F\xC4wMb[\xABb-\xC5\xBE\x14V\x18u\xC8^\xD3x\xB6\x01M\x8A\x89\xB5\xFB\xEF\x97j-\x0E\x06\xDB\xDB\xE5\xBB\xEFw\x17\xEE.^\xDF\xEA\xCA\xBAB\xA3\xB8\x14+\x9B\xB8\xD8\xB6@0YpqZ\xD9\xAD.\x9D\xC0^'\xAF/q\x9Af\xB4\x82-\x1C\xB9>\x80\xBAH\xA1\xC02\xA8P\xD1M\xF1\x95}\xD6\xFA\x12!\xD4u\x9D\xDB\xCD\\\xD9\x9C\x90\x871A\x9F\xFB\xB7\x8C\x9D\xA1\xA6\x0E\x17JS\xC1\xC0~R\xC5\xDF\xD4\xC3\xFC\xC3x\xA1_5\b\xDD\x80\x92m\xC3@\xB9L\xD6\xA8\x83\xA3\x82\xE6\xCA\xCD\e\xD9\xE6\xBB\x96\x15\x7F4T\xA8\xDD6\xC11\x1A\xC3^?@i\xC2\x00\x17>\xA3a8\v\xE7\x1E\xF3\xBD2\xC4x\x19\x86!\x86\xD9\xD1\xA7\x10\x941\x1A|=\xF2.\x95\x86bK5$\x1E&\v\x87x\x0E\xF1?\b\x8E\xE6~\xE4\x05.Y\x06$X,\x1C\x1CD\xD8t\x9B\xB8{:\x03\xAD\xAB\xFF\xE3S{\xCFoj\xD9\n\x9D\x10\xCF\xC5$F\x8F\xD7\x90i\xF59\x95\x05X\xE8\xD1IS\xDD\xAA$\xBD\xCF\xD9\x940\xB5\x06a0\xE7\xD9\xD4\xBB\a\xA5\xE8\t\x92\xA2\xBDT\x9C\x99f\x962\xFB\x8D\xD1\xA8\xF7\xA64\xCF\xBD\xB4Iw\xCF\xD0\xF4\xCE\xFA\r\x8CJ?e\xCA\xB49\xA4]a6\xC4K\x0E\xCD\x98\xCCi\xC5\vz\xCFM\xEB\xA5\x9B<7\xD7\xD4V\xFA\xA9\xC6\xE8\x97KK\xBE\x01\xFA\xDC>T\xA7\x02\x00\x00" + read 383 bytes + Conn close + ) + end + + def pre_scrubbed_echeck + %q( + opening connection to ws.cert.transfirst.com:443... + opened + starting SSL for ws.cert.transfirst.com:443... + SSL established + <- "POST /checkverifyws/checkverifyws.asmx/ACHDebit HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: ws.cert.transfirst.com\r\nContent-Length: 558\r\n\r\n" + <- "Amount=12.01&RefID=4b75519794a46dddcc50ed7349560d18&SECCCode=ActiveMerchant+Sale&PONumber=ActiveMerchant+Sale&SaleTaxAmount=0.00&PaymentDesc=&TaxIndicator=0&CompanyName=&TransRoute=244183602&BankAccountNo=15378535&BankAccountType=Checking&CheckType=Personal&Name=Jim+Smith&ProcessDate=121615&Description=&Address=456+My+Street&ZipCode=K1C2N6&ECIValue=&UserId=&CAVVData=&TrackData=&POSInd=&EComInd=&MerchZIP=&MerchCustPNum=&MCC=&InstallmentNum=&InstallmentOf=&POSEntryMode=&POSConditionCode=&AuthCharInd=&CardCertData=&MerchantID=45567&RegKey=TNYYKYMFZ59HSN7Q" + -> "HTTP/1.1 200 OK\r\n" + -> "Cache-Control: private, max-age=0\r\n" + -> "Content-Type: text/xml; charset=utf-8\r\n" + -> "Server: Microsoft-IIS/7.5\r\n" + -> "X-AspNet-Version: 2.0.50727\r\n" + -> "X-Powered-By: ASP.NET\r\n" + -> "Date: Wed, 16 Dec 2015 18:55:56 GMT\r\n" + -> "Cteonnt-Length: 504\r\n" + -> "Set-Cookie: NSC_JOb34jhgcucdnowdxp3wxrei43g5edn=ffffffff0918171e45525d5f4f58455e445a4a42378b;expires=Wed, 16-Dec-2015 19:04:36 GMT;path=/;secure;httponly\r\n" + -> "Content-Encoding: gzip\r\n" + -> "Content-Length: 341\r\n" + -> "\r\n" + reading 341 bytes... + -> "\x1F\x8B\b\x00\x00\x00\x00\x00\x00\x03\x85\x91_o\x820\x14\xC5\xDF\x97\xEC;4\xBCC[\xE4\x9F\x04k\f\xBA\x8ClN\xA3\xC6\xEC\x15\xDB\xAB\x90\x8Dbh\x11\xF7\xED\xC7\x80\x19\xF7\xB4\xB7{\xCF9\xBF\xE6\xDE\xDEhz->\xD1\x05*\x95\x97rbP\x8B\x18\b$/E.O\x13\xA3\xD6G30\xA6\xEC\xF1!\x8A3\xE0\x1F[\x9D\xEAZ\xA1\x16\x91*\xBC\xAA|bdZ\x9FC\x8C\x9B\xA6\xB1\x9A\x91UV'l\x13B\xF1\xFB\xF2u\xCB3R3\x97J\xA7\x92\x83q\xA3\xC4\xFF\xD4\x10\xFE\x13<\xA7_\x05H]\x81*\xEB\x8A\x83\xC5\xCB\x027pPP]r\x0E\n\e\xED\x94\bE\xB3Zgq\x80\xC5\xCF\x8B\xF8\x05%[\xF4\xB6\xDA\xA1\xFDb\x93<%\x8By\x84o~\x97^\x82R\xE9\t\x10\xEE\xDBu\xA94\x88y\xAA\x81\xD9\x84\xBA&\xB5M\xEA\xED\t]7t=\xCB\xA7#\xC7\xA7\xBEI\x82\x90\x90\b\xDF\xA5;z\x03\xC7d\xCE\x9C\x83\xEF\xBAt\xEC\x8F\x9D\xD4\xF1\x84\x10\x9C\xBB\x04\x84?r\xC6\xAEG\x04\r\"\xDC\xE7:\xA4\xFFP6[\xAF7\xAB\xFD\xCFx\x83\xD0\x9B5o\x17S\x8C\xB6\xF2Pv\xFA\xAEJ\xA5j_\xB0\x03\xEA8\x9E\xEFE\xF8W\xE9\xECYQ\xD6R3j[\xA4%\x87\xAE\xBD \xBE;!\xFB\x06\xEF\x9A\xBA\x89\xF8\x01\x00\x00" + read 341 bytes + Conn close + ) + end + + def post_scrubbed_echeck + %q( + opening connection to ws.cert.transfirst.com:443... + opened + starting SSL for ws.cert.transfirst.com:443... + SSL established + <- "POST /checkverifyws/checkverifyws.asmx/ACHDebit HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: ws.cert.transfirst.com\r\nContent-Length: 558\r\n\r\n" + <- "Amount=12.01&RefID=4b75519794a46dddcc50ed7349560d18&SECCCode=ActiveMerchant+Sale&PONumber=ActiveMerchant+Sale&SaleTaxAmount=0.00&PaymentDesc=&TaxIndicator=0&CompanyName=&TransRoute=[FILTERED]&BankAccountNo=[FILTERED]&BankAccountType=Checking&CheckType=Personal&Name=Jim+Smith&ProcessDate=121615&Description=&Address=456+My+Street&ZipCode=K1C2N6&ECIValue=&UserId=&CAVVData=&TrackData=&POSInd=&EComInd=&MerchZIP=&MerchCustPNum=&MCC=&InstallmentNum=&InstallmentOf=&POSEntryMode=&POSConditionCode=&AuthCharInd=&CardCertData=&MerchantID=45567&RegKey=[FILTERED]" + -> "HTTP/1.1 200 OK\r\n" + -> "Cache-Control: private, max-age=0\r\n" + -> "Content-Type: text/xml; charset=utf-8\r\n" + -> "Server: Microsoft-IIS/7.5\r\n" + -> "X-AspNet-Version: 2.0.50727\r\n" + -> "X-Powered-By: ASP.NET\r\n" + -> "Date: Wed, 16 Dec 2015 18:55:56 GMT\r\n" + -> "Cteonnt-Length: 504\r\n" + -> "Set-Cookie: NSC_JOb34jhgcucdnowdxp3wxrei43g5edn=ffffffff0918171e45525d5f4f58455e445a4a42378b;expires=Wed, 16-Dec-2015 19:04:36 GMT;path=/;secure;httponly\r\n" + -> "Content-Encoding: gzip\r\n" + -> "Content-Length: 341\r\n" + -> "\r\n" + reading 341 bytes... + -> "\x1F\x8B\b\x00\x00\x00\x00\x00\x00\x03\x85\x91_o\x820\x14\xC5\xDF\x97\xEC;4\xBCC[\xE4\x9F\x04k\f\xBA\x8ClN\xA3\xC6\xEC\x15\xDB\xAB\x90\x8Dbh\x11\xF7\xED\xC7\x80\x19\xF7\xB4\xB7{\xCF9\xBF\xE6\xDE\xDEhz->\xD1\x05*\x95\x97rbP\x8B\x18\b$/E.O\x13\xA3\xD6G30\xA6\xEC\xF1!\x8A3\xE0\x1F[\x9D\xEAZ\xA1\x16\x91*\xBC\xAA|bdZ\x9FC\x8C\x9B\xA6\xB1\x9A\x91UV'l\x13B\xF1\xFB\xF2u\xCB3R3\x97J\xA7\x92\x83q\xA3\xC4\xFF\xD4\x10\xFE\x13<\xA7_\x05H]\x81*\xEB\x8A\x83\xC5\xCB\x027pPP]r\x0E\n\e\xED\x94\bE\xB3Zgq\x80\xC5\xCF\x8B\xF8\x05%[\xF4\xB6\xDA\xA1\xFDb\x93<%\x8By\x84o~\x97^\x82R\xE9\t\x10\xEE\xDBu\xA94\x88y\xAA\x81\xD9\x84\xBA&\xB5M\xEA\xED\t]7t=\xCB\xA7#\xC7\xA7\xBEI\x82\x90\x90\b\xDF\xA5;z\x03\xC7d\xCE\x9C\x83\xEF\xBAt\xEC\x8F\x9D\xD4\xF1\x84\x10\x9C\xBB\x04\x84?r\xC6\xAEG\x04\r\"\xDC\xE7:\xA4\xFFP6[\xAF7\xAB\xFD\xCFx\x83\xD0\x9B5o\x17S\x8C\xB6\xF2Pv\xFA\xAEJ\xA5j_\xB0\x03\xEA8\x9E\xEFE\xF8W\xE9\xECYQ\xD6R3j[\xA4%\x87\xAE\xBD \xBE;!\xFB\x06\xEF\x9A\xBA\x89\xF8\x01\x00\x00" + read 341 bytes + Conn close + ) + end + def successful_purchase_response <<-XML -<?xml version="1.0" encoding="utf-8"?> -<CCSaleDebitResponse xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.paymentresources.com/webservices/"> - <TransID>355</TransID> - <RefID>c2535abbf0bb38005a14fd575553df65</RefID> - <Amount>1.00</Amount> - <AuthCode>Test00</AuthCode> - <Status>Authorized</Status> - <AVSCode>X</AVSCode> - <Message>test transaction</Message> - <CVV2Code>M</CVV2Code> - <ACI /> - <AuthSource /> - <TransactionIdentifier /> - <ValidationCode /> - <CAVVResultCode /> -</CCSaleDebitResponse> + <?xml version="1.0" encoding="utf-8"?> + <CCSaleDebitResponse xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.paymentresources.com/webservices/"> + <TransID>355</TransID> + <RefID>c2535abbf0bb38005a14fd575553df65</RefID> + <Amount>1.00</Amount> + <AuthCode>Test00</AuthCode> + <Status>Authorized</Status> + <AVSCode>X</AVSCode> + <Message>test transaction</Message> + <CVV2Code>M</CVV2Code> + <ACI /> + <AuthSource /> + <TransactionIdentifier /> + <ValidationCode /> + <CAVVResultCode /> + </CCSaleDebitResponse> XML end - + def failed_purchase_response <<-XML -<?xml version="1.0" encoding="utf-8" ?> -<CCSaleDebitResponse xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.paymentresources.com/webservices/"> - <TransID>29005716</TransID> - <RefID>0610</RefID> - <PostedDate>2005-09-29T15:16:23.7297658-07:00</PostedDate> - <SettledDate>2005-09-29T15:16:23.9641468-07:00</SettledDate> - <Amount>0.02</Amount> - <AuthCode /> - <Status>Declined</Status> - <AVSCode /> - <Message>Invalid cardholder number</Message> - <CVV2Code /> - <ACI /> - <AuthSource /> - <TransactionIdentifier /> - <ValidationCode /> - <CAVVResultCode /> -</CCSaleDebitResponse> + <?xml version="1.0" encoding="utf-8" ?> + <CCSaleDebitResponse xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.paymentresources.com/webservices/"> + <TransID>29005716</TransID> + <RefID>0610</RefID> + <PostedDate>2005-09-29T15:16:23.7297658-07:00</PostedDate> + <SettledDate>2005-09-29T15:16:23.9641468-07:00</SettledDate> + <Amount>0.02</Amount> + <AuthCode /> + <Status>Declined</Status> + <AVSCode /> + <Message>Invalid cardholder number</Message> + <CVV2Code /> + <ACI /> + <AuthSource /> + <TransactionIdentifier /> + <ValidationCode /> + <CAVVResultCode /> + </CCSaleDebitResponse> + XML + end + + def successful_purchase_echeck_response + <<-XML + <?xml version="1.0" encoding="utf-8" ?> + <CheckStatus> + xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema- instance" xmlns="http://www.paymentresources.com/webservices/"> + <Success>1</Success> + <TransID>11996</TransID> + <RefID>PRICreditTest</RefID> + <PostedDate>004-02-04T08:23:02.9467720-08:00</PostedDate> + <AuthCode> CHECK IS NOT VERIFIED </AuthCode> + <Status>APPROVED</Status> + <Message /> + <Amount>1.01</Amount> + </CheckStatus> + XML + end + + def failed_purchase_echeck_response + <<-XML + <?xml version="1.0" encoding="utf-8" ?> + <CheckStatus> + xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema- instance" xmlns="http://www.paymentresources.com/webservices/"> + <Success>0</Success> + <TransID>0</TransID> + <RefID>PRICreditTest</RefID> + <PostedDate>2004-02-04T08:23:02.9467720-08:00</PostedDate> + <AuthCode> CHECK IS NOT VERIFIED </AuthCode> + <Status>DENIED</Status> + <Message> Error Message </Message> + <Amount>1.01</Amount> + </CheckStatus> + XML + end + + def successful_refund_response + <<-XML + <?xml version="1.0" encoding="utf-8" ?> + <BankCardRefundStatus xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://www.paymentresources.com/webservices/"> + <TransID>207686608</TransID> + <CreditID>5681409</CreditID> + <RefID /> + <PostedDate>2010-08-09T15:20:50.9740575-06:00</PostedDate> <SettledDate>0001-01-01T00:00:00</SettledDate> + <Amount>1.0000</Amount> + <Status>Authorized</Status> + </BankCardRefundStatus> + XML + end + + def failed_refund_response + <<-XML + <?xml version="1.0" encoding="utf-8" ?> + <BankCardRefundStatus xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://www.paymentresources.com/webservices/"> + <TransID>0</TransID> + <CreditID>0</CreditID> + <PostedDate>0001-01-01T00:00:00</PostedDate> <SettledDate>0001-01-01T00:00:00</SettledDate> + <Amount>0</Amount> + <Status>Canceled</Status> + <Message>Transaction Is Not Allowed To Void or Refund</Message> + </BankCardRefundStatus> + XML + end + + def successful_void_response + <<-XML + <?xml version="1.0" encoding="utf-8" ?> + <BankCardRefundStatus xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://www.paymentresources.com/webservices/"> + <TransID>0</TransID> + <TransID>207616632</TransID> + <CreditID>0</CreditID> + <RefID>123</RefID> + <PostedDate>2010-08-09T12:25:00</PostedDate> <SettledDate>0001-01-01T00:00:00</SettledDate> + <Amount>1.3100</Amount> + <AuthCode>012921</AuthCode> + <Status>Voided</Status> + <AVSCode>N</AVSCode> + <CVV2Code /> + </BankCardRefundStatus> + XML + end + + def failed_void_response + <<-XML + <?xml version="1.0" encoding="utf-8" ?> + <BankCardRefundStatus xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://www.paymentresources.com/webservices/"> + <TransID>0</TransID> + <CreditID>0</CreditID> + <PostedDate>0001-01-01T00:00:00</PostedDate> <SettledDate>0001-01-01T00:00:00</SettledDate> + <Amount>0</Amount> + <Status>Canceled</Status> + <Message>Transaction Is Not Allowed To Void or Refund</Message> + </BankCardRefundStatus> XML end end diff --git a/test/unit/gateways/trans_first_transaction_express_test.rb b/test/unit/gateways/trans_first_transaction_express_test.rb new file mode 100644 index 00000000000..1bbfdf81f88 --- /dev/null +++ b/test/unit/gateways/trans_first_transaction_express_test.rb @@ -0,0 +1,382 @@ +require 'test_helper' + +class TransFirstTransactionExpressTest < Test::Unit::TestCase + include CommStub + + def setup + @gateway = TransFirstTransactionExpressGateway.new( + gateway_id: 'gateway_id', + reg_key: 'reg_key' + ) + + @credit_card = credit_card + @check = check + @amount = 100 + @declined_amount = 21 + end + + def test_successful_purchase + response = stub_comms do + @gateway.purchase(@amount, @credit_card) + end.respond_with(successful_purchase_response) + + assert_success response + + assert_equal 'purchase|000015212561', response.authorization + assert response.test? + end + + def test_failed_purchase + response = stub_comms do + @gateway.purchase(@declined_amount, @credit_card) + end.respond_with(failed_purchase_response) + + assert_failure response + assert_equal 'Not sufficient funds', response.message + assert_equal '51', response.error_code + assert response.test? + end + + def test_successful_purchase_with_echeck + @gateway.stubs(:ssl_post).returns(successful_purchase_echeck_response) + response = @gateway.purchase(@amount, @check) + + assert_success response + assert_equal 'purchase_echeck|000028705491', response.authorization + end + + def test_failed_purchase_with_echeck + @gateway.stubs(:ssl_post).returns(failed_purchase_echeck_response) + response = @gateway.purchase(@amount, @check) + + assert_failure response + assert_equal 'Error. Bank routing number validation negative (ABA).', response.message + end + + def test_successful_authorize_and_capture + response = stub_comms do + @gateway.authorize(@amount, @credit_card) + end.respond_with(successful_authorize_response) + + assert_success response + assert_equal 'authorize|000015377801', response.authorization + + capture = stub_comms do + @gateway.capture(@amount, response.authorization) + end.check_request do |endpoint, data, headers| + assert_match(/000015377801/, data) + end.respond_with(successful_capture_response) + + assert_success capture + end + + def test_failed_authorize + response = stub_comms do + @gateway.authorize(@amount, @credit_card) + end.respond_with(failed_authorize_response) + + assert_failure response + assert_equal 'Not sufficient funds', response.message + assert_equal '51', response.error_code + assert response.test? + end + + def test_failed_capture + response = stub_comms do + @gateway.capture(100, '') + end.respond_with(failed_capture_response) + + assert_failure response + end + + def test_successful_void + response = stub_comms do + @gateway.purchase(@amount, @credit_card) + end.respond_with(successful_purchase_response) + + assert_success response + assert_equal 'purchase|000015212561', response.authorization + + void = stub_comms do + @gateway.void(response.authorization) + end.check_request do |endpoint, data, headers| + assert_match(/000015212561/, data) + end.respond_with(successful_void_response) + + assert_success void + end + + def test_failed_void + response = stub_comms do + @gateway.void('purchase|5d53a33d960c46d00f5dc061947d998c') + end.check_request do |endpoint, data, headers| + assert_match(/5d53a33d960c46d00f5dc061947d998c/, data) + end.respond_with(failed_void_response) + + assert_failure response + assert_equal '50011', response.error_code + end + + def test_successful_refund + response = stub_comms do + @gateway.purchase(@amount, @credit_card) + end.respond_with(successful_purchase_response) + + assert_success response + assert_equal 'purchase|000015212561', response.authorization + + refund = stub_comms do + @gateway.refund(@amount, response.authorization) + end.check_request do |endpoint, data, headers| + assert_match(/000015212561/, data) + end.respond_with(successful_refund_response) + + assert_success refund + end + + def test_failed_refund + response = stub_comms do + @gateway.refund(nil, '') + end.respond_with(failed_refund_response) + + assert_failure response + assert_equal '50011', response.error_code + end + + def test_successful_refund_with_echeck + response = stub_comms do + @gateway.purchase(@amount, @check) + end.respond_with(successful_purchase_echeck_response) + + assert_success response + assert_equal 'purchase_echeck|000028705491', response.authorization + + refund = stub_comms do + @gateway.refund(@amount, response.authorization) + end.check_request do |endpoint, data, headers| + assert_match(/000028705491/, data) + end.respond_with(successful_refund_echeck_response) + + assert_success refund + end + + def test_failed_refund_with_echeck + response = stub_comms do + @gateway.refund(@amount, 'purchase_echeck|000028706091') + end.respond_with(failed_refund_response) + + assert_failure response + assert_equal '50011', response.error_code + end + + def test_successful_credit + response = stub_comms do + @gateway.credit(@amount, @credit_card) + end.respond_with(successful_credit_response) + + assert_success response + + assert_equal 'credit|000001677461', response.authorization + assert response.test? + end + + def test_failed_credit + response = stub_comms do + @gateway.credit(@amount, @credit_card) + end.respond_with(failed_credit_response) + + assert_failure response + assert_equal 'Validation Error', response.message + assert_equal '51334', response.error_code + assert response.test? + end + + def test_successful_verify + response = stub_comms do + @gateway.verify(@credit_card) + end.respond_with(successful_authorize_response, failed_void_response) + assert_success response + assert_equal 'Succeeded', response.message + end + + def test_failed_verify + response = stub_comms do + @gateway.verify(@credit_card) + end.respond_with(failed_authorize_response, successful_void_response) + assert_failure response + assert_equal 'Not sufficient funds', response.message + end + + def test_successful_store + response = stub_comms do + @gateway.store(@credit_card) + end.respond_with(successful_store_response) + + assert_success response + + assert_equal 'Succeeded', response.message + assert_equal 'store|1453495229881170023', response.authorization + assert response.test? + end + + def test_failed_store + response = stub_comms do + @gateway.store(@credit_card) + end.respond_with(failed_store_response) + + assert_failure response + assert_equal 'Validation Failure', response.message + assert response.test? + end + + def test_empty_response_fails + response = stub_comms do + @gateway.purchase(@amount, @credit_card) + end.respond_with(empty_purchase_response) + + assert_failure response + assert_equal nil, response.message + end + + def test_transcript_scrubbing + assert_equal scrubbed_transcript, @gateway.scrub(transcript) + end + + private + + def successful_purchase_response + %(<?xml version='1.0' encoding='UTF-8'?><S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/"><S:Body><ns2:SendTranResponse xmlns="http://postilion/realtime/portal/soa/xsd/Faults/2009/01" xmlns:ns2="http://postilion/realtime/merchantframework/xsd/v1/"><ns2:rspCode>00</ns2:rspCode><ns2:authRsp><ns2:aci>Y</ns2:aci></ns2:authRsp><ns2:tranData><ns2:swchKey>0A1009331525B2A2DBFAF771E2E62B</ns2:swchKey><ns2:tranNr>000015212561</ns2:tranNr><ns2:dtTm>2016-01-19T10:33:57.000-08:00</ns2:dtTm><ns2:amt>000000000100</ns2:amt><ns2:stan>305156</ns2:stan><ns2:auth>Lexc05</ns2:auth></ns2:tranData><ns2:cardType>0</ns2:cardType><ns2:mapCaid>300979940268000</ns2:mapCaid></ns2:SendTranResponse></S:Body></S:Envelope>) + end + + def failed_purchase_response + %(<?xml version='1.0' encoding='UTF-8'?><S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/"><S:Body><ns2:SendTranResponse xmlns="http://postilion/realtime/portal/soa/xsd/Faults/2009/01" xmlns:ns2="http://postilion/realtime/merchantframework/xsd/v1/"><ns2:rspCode>51</ns2:rspCode><ns2:authRsp><ns2:aci>Y</ns2:aci></ns2:authRsp><ns2:tranData><ns2:swchKey>0A1009331525BA8F333FC15F59AB32</ns2:swchKey><ns2:tranNr>000015220671</ns2:tranNr><ns2:dtTm>2016-01-19T12:52:25.000-08:00</ns2:dtTm><ns2:amt>000000000021</ns2:amt><ns2:stan>305918</ns2:stan><ns2:auth>Lexc05</ns2:auth></ns2:tranData><ns2:cardType>0</ns2:cardType><ns2:mapCaid>300979940268000</ns2:mapCaid></ns2:SendTranResponse></S:Body></S:Envelope>) + end + + def successful_authorize_response + %(<?xml version='1.0' encoding='UTF-8'?><S:Envelope xmlns:S=\"http://schemas.xmlsoap.org/soap/envelope/\"><S:Body><ns2:SendTranResponse xmlns=\"http://postilion/realtime/portal/soa/xsd/Faults/2009/01\" xmlns:ns2=\"http://postilion/realtime/merchantframework/xsd/v1/\"><ns2:rspCode>00</ns2:rspCode><ns2:authRsp><ns2:secRslt>M</ns2:secRslt><ns2:avsRslt>Z</ns2:avsRslt><ns2:aci>Y</ns2:aci></ns2:authRsp><ns2:tranData><ns2:swchKey>0A10093315265DE34CE542A9E44548</ns2:swchKey><ns2:tranNr>000015377801</ns2:tranNr><ns2:dtTm>2016-01-21T12:26:47.000-08:00</ns2:dtTm><ns2:amt>000000000100</ns2:amt><ns2:stan>319955</ns2:stan><ns2:auth>Lexc05</ns2:auth></ns2:tranData><ns2:cardType>0</ns2:cardType><ns2:mapCaid>300979940268000</ns2:mapCaid></ns2:SendTranResponse></S:Body></S:Envelope>) + end + + def failed_authorize_response + %(<?xml version='1.0' encoding='UTF-8'?><S:Envelope xmlns:S=\"http://schemas.xmlsoap.org/soap/envelope/\"><S:Body><ns2:SendTranResponse xmlns=\"http://postilion/realtime/portal/soa/xsd/Faults/2009/01\" xmlns:ns2=\"http://postilion/realtime/merchantframework/xsd/v1/\"><ns2:rspCode>51</ns2:rspCode><ns2:authRsp><ns2:secRslt>M</ns2:secRslt><ns2:avsRslt>Z</ns2:avsRslt><ns2:aci>Y</ns2:aci></ns2:authRsp><ns2:tranData><ns2:swchKey>0A10093315265F2FCEF82DB9231D67</ns2:swchKey><ns2:tranNr>000015378101</ns2:tranNr><ns2:dtTm>2016-01-21T12:49:29.000-08:00</ns2:dtTm><ns2:amt>000000000000</ns2:amt><ns2:stan>319985</ns2:stan><ns2:auth>Lexc05</ns2:auth></ns2:tranData><ns2:cardType>0</ns2:cardType><ns2:mapCaid>300979940268000</ns2:mapCaid></ns2:SendTranResponse></S:Body></S:Envelope>) + end + + def successful_capture_response + %(<?xml version='1.0' encoding='UTF-8'?><S:Envelope xmlns:S=\"http://schemas.xmlsoap.org/soap/envelope/\"><S:Body><ns2:SendTranResponse xmlns=\"http://postilion/realtime/portal/soa/xsd/Faults/2009/01\" xmlns:ns2=\"http://postilion/realtime/merchantframework/xsd/v1/\"><ns2:rspCode>00</ns2:rspCode><ns2:authRsp/><ns2:tranData><ns2:swchKey>0A10093315265DF34907B4587F31D5</ns2:swchKey><ns2:tranNr>000015377821</ns2:tranNr><ns2:dtTm>2016-01-21T12:27:52.000-08:00</ns2:dtTm><ns2:amt>000000000100</ns2:amt><ns2:stan>319958</ns2:stan><ns2:auth>Lexc05</ns2:auth></ns2:tranData><ns2:cardType>0</ns2:cardType><ns2:mapCaid>300979940268000</ns2:mapCaid></ns2:SendTranResponse></S:Body></S:Envelope>) + end + + def failed_capture_response + %(<?xml version='1.0' encoding='UTF-8'?><S:Envelope xmlns:S=\"http://schemas.xmlsoap.org/soap/envelope/\"><S:Body><S:Fault xmlns:ns4=\"http://www.w3.org/2003/05/soap-envelope\"><faultcode>S:Server</faultcode><faultstring>Validation Failure</faultstring><detail><SystemFault:SystemFault xmlns:SystemFault=\"http://postilion/realtime/portal/soa/xsd/Faults/2009/01\" xmlns=\"http://postilion/realtime/portal/soa/xsd/Faults/2009/01\" xmlns:ns2=\"http://postilion/realtime/merchantframework/xsd/v1/\"><name>Validation Fault</name><message>cvc-type.3.1.3: The value '' of element 'v1:tranNr' is not valid.</message><errorCode>50011</errorCode></SystemFault:SystemFault></detail></S:Fault></S:Body></S:Envelope>) + end + + def successful_void_response + %(<?xml version='1.0' encoding='UTF-8'?><S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/"><S:Body><ns2:SendTranResponse xmlns="http://postilion/realtime/portal/soa/xsd/Faults/2009/01" xmlns:ns2="http://postilion/realtime/merchantframework/xsd/v1/"><ns2:rspCode>00</ns2:rspCode><ns2:authRsp/><ns2:tranData><ns2:swchKey>0A1009331525BAC88E077EFB8D7542</ns2:swchKey><ns2:tranNr>000015212561</ns2:tranNr><ns2:dtTm>2016-01-19T12:56:20.000-08:00</ns2:dtTm><ns2:amt>000000000100</ns2:amt><ns2:stan>305938</ns2:stan><ns2:auth>Lexc05</ns2:auth></ns2:tranData><ns2:cardType>0</ns2:cardType><ns2:mapCaid>300979940268000</ns2:mapCaid><ns2:additionalAmount><ns2:accountType>30</ns2:accountType><ns2:amountType>53</ns2:amountType><ns2:currencyCode>840</ns2:currencyCode><ns2:amountSign>D</ns2:amountSign><ns2:amount>000000000100</ns2:amount></ns2:additionalAmount></ns2:SendTranResponse></S:Body></S:Envelope>) + end + + def failed_void_response + %(<?xml version='1.0' encoding='UTF-8'?><S:Envelope xmlns:S=\"http://schemas.xmlsoap.org/soap/envelope/\"><S:Body><S:Fault xmlns:ns4=\"http://www.w3.org/2003/05/soap-envelope\"><faultcode>S:Server</faultcode><faultstring>Validation Failure</faultstring><detail><SystemFault:SystemFault xmlns:SystemFault=\"http://postilion/realtime/portal/soa/xsd/Faults/2009/01\" xmlns=\"http://postilion/realtime/portal/soa/xsd/Faults/2009/01\" xmlns:ns2=\"http://postilion/realtime/merchantframework/xsd/v1/\"><name>Validation Fault</name><message>cvc-type.3.1.3: The value '' of element 'v1:tranNr' is not valid.</message><errorCode>50011</errorCode></SystemFault:SystemFault></detail></S:Fault></S:Body></S:Envelope>) + end + + def successful_refund_response + %(<?xml version='1.0' encoding='UTF-8'?><S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/"><S:Body><SendTranResponse xmlns="http://postilion/realtime/merchantframework/xsd/v1/" xmlns:ns2="http://postilion/realtime/portal/soa/xsd/Faults/2009/01"> <ns2:rspCode>00</ns2:rspCode><ns2:tranData><ns2:swchKey>0A10064112F57B9D997D4D1111888E</ns2:swchKey><ns2:tranNr>000001829611</ns2:tranNr><ns2:dtTm>2011-04-14T22:54:48.000-07:00</ns2:dtTm><ns2:amt>000000000100</ns2:amt></ns2:tranData></SendTranResponse></S:Body></S:Envelope>) + end + + def failed_refund_response + %(<?xml version='1.0' encoding='UTF-8'?><S:Envelope xmlns:S=\"http://schemas.xmlsoap.org/soap/envelope/\"><S:Body><S:Fault xmlns:ns4=\"http://www.w3.org/2003/05/soap-envelope\"><faultcode>S:Server</faultcode><faultstring>Validation Failure</faultstring><detail><SystemFault:SystemFault xmlns:SystemFault=\"http://postilion/realtime/portal/soa/xsd/Faults/2009/01\" xmlns=\"http://postilion/realtime/portal/soa/xsd/Faults/2009/01\" xmlns:ns2=\"http://postilion/realtime/merchantframework/xsd/v1/\"><name>Validation Fault</name><message>cvc-type.3.1.3: The value '' of element 'v1:tranNr' is not valid.</message><errorCode>50011</errorCode></SystemFault:SystemFault></detail></S:Fault></S:Body></S:Envelope>) + end + + def successful_credit_response + %(<?xml version='1.0' encoding='UTF-8'?><S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/"><S:Body><ns2:SendTranResponse xmlns="http://postilion/realtime/portal/soa/xsd/Faults/2009/01" xmlns:ns2="http://postilion/realtime/merchantframework/xsd/v1/"><ns2:rspCode>00</ns2:rspCode><ns2:authRsp/><ns2:tranData><ns2:swchKey>0A6E6B4B135B08437C7C1370A116B7</ns2:swchKey><ns2:tranNr>000001677461</ns2:tranNr><ns2:dtTm>2012-02-24T09:59:09.000-08:00</ns2:dtTm><ns2:amt>000000000100</ns2:amt><ns2:stan>000301</ns2:stan></ns2:tranData><ns2:cardType>0</ns2:cardType><ns2:mapCaid>7310</ns2:mapCaid></ns2:SendTranResponse></S:Body></S:Envelope>) + end + + def failed_credit_response + %(<?xml version='1.0' encoding='UTF-8'?><S:Envelope xmlns:S=\"http://schemas.xmlsoap.org/soap/envelope/\"><S:Body><S:Fault xmlns:ns4=\"http://www.w3.org/2003/05/soap-envelope\"><faultcode>S:Server</faultcode><faultstring>Validation Error</faultstring><detail><SystemFault:SystemFault xmlns:SystemFault=\"http://postilion/realtime/portal/soa/xsd/Faults/2009/01\" xmlns=\"http://postilion/realtime/portal/soa/xsd/Faults/2009/01\" xmlns:ns2=\"http://postilion/realtime/merchantframework/xsd/v1/\"><name>Validation Error</name><message>Validation Error Fault</message><errorCode>51334</errorCode></SystemFault:SystemFault></detail></S:Fault></S:Body></S:Envelope>) + end + + def successful_store_response + %(<?xml version='1.0' encoding='UTF-8'?><S:Envelope xmlns:S=\"http://schemas.xmlsoap.org/soap/envelope/\"><S:Body><ns2:UpdtRecurrProfResponse xmlns=\"http://postilion/realtime/portal/soa/xsd/Faults/2009/01\" xmlns:ns2=\"http://postilion/realtime/merchantframework/xsd/v1/\"><ns2:pmtId>1453495229881170023</ns2:pmtId><ns2:rspCode>00</ns2:rspCode></ns2:UpdtRecurrProfResponse></S:Body></S:Envelope>) + end + + def failed_store_response + %(<?xml version='1.0' encoding='UTF-8'?><S:Envelope xmlns:S=\"http://schemas.xmlsoap.org/soap/envelope/\"><S:Body><S:Fault xmlns:ns4=\"http://www.w3.org/2003/05/soap-envelope\"><faultcode>S:Server</faultcode><faultstring>Validation Failure</faultstring><detail><SystemFault:SystemFault xmlns:SystemFault=\"http://postilion/realtime/portal/soa/xsd/Faults/2009/01\" xmlns=\"http://postilion/realtime/portal/soa/xsd/Faults/2009/01\" xmlns:ns2=\"http://postilion/realtime/merchantframework/xsd/v1/\"><name>Validation Fault</name><message>cvc-type.3.1.3: The value '123' of element 'v1:pan' is not valid.</message><errorCode>50011</errorCode></SystemFault:SystemFault></detail></S:Fault></S:Body></S:Envelope>) + end + + def successful_purchase_echeck_response + %(<?xml version='1.0' encoding='UTF-8'?><S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/"><S:Body><ns2:SendTranResponse xmlns="http://postilion/realtime/portal/soa/xsd/Faults/2009/01" xmlns:ns2="http://postilion/realtime/merchantframework/xsd/v1/"><ns2:rspCode>00</ns2:rspCode><ns2:authRsp><ns2:gwyTranId>43550871</ns2:gwyTranId></ns2:authRsp><ns2:tranData><ns2:swchKey>0A09071615AD2403F804EFDA26EA76</ns2:swchKey><ns2:tranNr>000028705491</ns2:tranNr><ns2:dtTm>2017-03-15T06:55:10-07:00</ns2:dtTm><ns2:amt>000000000100</ns2:amt><ns2:stan>386950</ns2:stan></ns2:tranData><ns2:achResponse><ns2:Message>Transaction processed.</ns2:Message><ns2:Note>PrevPay: nil +0</ns2:Note><ns2:Note>Score: 100/100</ns2:Note></ns2:achResponse></ns2:SendTranResponse></S:Body></S:Envelope>) + end + + def failed_purchase_echeck_response + %(<?xml version='1.0' encoding='UTF-8'?><S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/"><S:Body><ns2:SendTranResponse xmlns="http://postilion/realtime/portal/soa/xsd/Faults/2009/01" xmlns:ns2="http://postilion/realtime/merchantframework/xsd/v1/"><ns2:rspCode>06</ns2:rspCode><ns2:authRsp/><ns2:tranData><ns2:swchKey>0A09071715AD2654A6814EE9ADC0EF</ns2:swchKey><ns2:tranNr>000028705711</ns2:tranNr><ns2:dtTm>2017-03-15T07:35:38-07:00</ns2:dtTm><ns2:amt>000000000100</ns2:amt><ns2:stan>386972</ns2:stan></ns2:tranData><ns2:achResponse><ns2:Message>Bank routing number validation negative (ABA).</ns2:Message></ns2:achResponse></ns2:SendTranResponse></S:Body></S:Envelope>) + end + + def successful_refund_echeck_response + %( <?xml version='1.0' encoding='UTF-8'?><S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/"><S:Body><ns2:SendTranResponse xmlns="http://postilion/realtime/portal/soa/xsd/Faults/2009/01" xmlns:ns2="http://postilion/realtime/merchantframework/xsd/v1/"><ns2:rspCode>00</ns2:rspCode><ns2:authRsp><ns2:gwyTranId>43550889</ns2:gwyTranId></ns2:authRsp><ns2:tranData><ns2:swchKey>0A09071715AD2786821E2F357D7E52</ns2:swchKey><ns2:tranNr>000028706091</ns2:tranNr><ns2:dtTm>2017-03-15T07:56:31-07:00</ns2:dtTm><ns2:amt>000000000100</ns2:amt><ns2:stan>387010</ns2:stan></ns2:tranData><ns2:achResponse><ns2:Message>Transaction Cancelled.</ns2:Message><ns2:Note>PrevPay: nil +0</ns2:Note><ns2:Note>Score: 100/100</ns2:Note><ns2:Note>Cancellation Notes: RefNumber:28706091</ns2:Note></ns2:achResponse></ns2:SendTranResponse></S:Body></S:Envelope>) + end + + def failed_refund_echeck_response + %(<?xml version='1.0' encoding='UTF-8'?><S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/"><S:Body><ns2:SendTranResponse xmlns="http://postilion/realtime/portal/soa/xsd/Faults/2009/01" xmlns:ns2="http://postilion/realtime/merchantframework/xsd/v1/"><ns2:rspCode>12</ns2:rspCode><ns2:extRspCode>B40F</ns2:extRspCode><ns2:authRsp><ns2:gwyTranId>43550889</ns2:gwyTranId></ns2:authRsp><ns2:tranData><ns2:swchKey>0A09071615AD285C3E4E0AE3A42CF3</ns2:swchKey><ns2:tranNr>000028706091</ns2:tranNr><ns2:dtTm>2017-03-15T08:11:06-07:00</ns2:dtTm><ns2:amt>000000000100</ns2:amt></ns2:tranData></ns2:SendTranResponse></S:Body></S:Envelope>) + end + + def empty_purchase_response + %() + end + + def transcript + <<-PRE_SCRUBBED +opening connection to ws.cert.transactionexpress.com:443... +opened +starting SSL for ws.cert.transactionexpress.com:443... +SSL established +<- "POST /portal/merchantframework/MerchantWebServices-v1?wsdl HTTP/1.1\r\nContent-Type: text/xml\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: ws.cert.transactionexpress.com\r\nContent-Length: 1186\r\n\r\n" +<- "<soapenv:Envelope xmlns:soapenv=\"http://schemas.xmlsoap.org/soap/envelope/\"><soapenv:Body><v1:SendTranRequest xmlns:v1=\"http://postilion/realtime/merchantframework/xsd/v1/\"><v1:merc><v1:id>7777778764</v1:id><v1:regKey>M84PKPDMD5BY86HN</v1:regKey><v1:inType>1</v1:inType></v1:merc><v1:tranCode>1</v1:tranCode><v1:card><v1:pan>4485896261017708</v1:pan><v1:xprDt>1709</v1:xprDt></v1:card><v1:contact><v1:fullName>Longbob Longsen</v1:fullName><v1:coName>Acme</v1:coName><v1:title>QA Manager</v1:title><v1:phone><v1:type>4</v1:type><v1:nr>3334445555</v1:nr></v1:phone><v1:addrLn1>450 Main</v1:addrLn1><v1:addrLn2>Suite 100</v1:addrLn2><v1:city>Broomfield</v1:city><v1:state>CO</v1:state><v1:zipCode>85284</v1:zipCode><v1:ctry>US</v1:ctry><v1:email>example@example.com</v1:email><v1:ship><v1:fullName>Longbob Longsen</v1:fullName><v1:addrLn1>450 Main</v1:addrLn1><v1:addrLn2>Suite 100</v1:addrLn2><v1:city>Broomfield</v1:city><v1:state>CO</v1:state><v1:zipCode>85284</v1:zipCode><v1:phone>3334445555</v1:phone></v1:ship></v1:contact><v1:reqAmt>100</v1:reqAmt><v1:authReq><v1:ordNr>7a0f975b6e86aff44364360cbc6d0f00</v1:ordNr></v1:authReq></v1:SendTranRequest></soapenv:Body></soapenv:Envelope>" +-> "HTTP/1.1 200 OK\r\n" +-> "Content-Type: text/xml;charset=utf-8\r\n" +-> "Date: Thu, 21 Jan 2016 20:09:44 GMT\r\n" +-> "Server: WebServer\r\n" +-> "Set-Cookie: NSC_UMT12_DFSU-xt.dfsu.UYQ.dpn=ffffffff0918172545525d5f4f58455e445a4a42378b;expires=Thu, 21-Jan-2016 20:17:43 GMT;path=/;secure;httponly\r\n" +-> "Cache-Control: private\r\n" +-> "Content-Encoding: gzip\r\n" +-> "Transfer-Encoding: chunked\r\n" +-> "\r\n" +-> "1AA \r\n" +reading 426 bytes... +-> "" +read 426 bytes +reading 2 bytes... +-> "\r\n" +read 2 bytes +-> "0\r\n" +-> "\r\n" +Conn close + PRE_SCRUBBED + end + + def scrubbed_transcript + <<-POST_SCRUBBED +opening connection to ws.cert.transactionexpress.com:443... +opened +starting SSL for ws.cert.transactionexpress.com:443... +SSL established +<- "POST /portal/merchantframework/MerchantWebServices-v1?wsdl HTTP/1.1\r\nContent-Type: text/xml\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: ws.cert.transactionexpress.com\r\nContent-Length: 1186\r\n\r\n" +<- "<soapenv:Envelope xmlns:soapenv=\"http://schemas.xmlsoap.org/soap/envelope/\"><soapenv:Body><v1:SendTranRequest xmlns:v1=\"http://postilion/realtime/merchantframework/xsd/v1/\"><v1:merc><v1:id>[FILTERED]</v1:id><v1:regKey>[FILTERED]</v1:regKey><v1:inType>1</v1:inType></v1:merc><v1:tranCode>1</v1:tranCode><v1:card><v1:pan>[FILTERED]</v1:pan><v1:xprDt>1709</v1:xprDt></v1:card><v1:contact><v1:fullName>Longbob Longsen</v1:fullName><v1:coName>Acme</v1:coName><v1:title>QA Manager</v1:title><v1:phone><v1:type>4</v1:type><v1:nr>3334445555</v1:nr></v1:phone><v1:addrLn1>450 Main</v1:addrLn1><v1:addrLn2>Suite 100</v1:addrLn2><v1:city>Broomfield</v1:city><v1:state>CO</v1:state><v1:zipCode>85284</v1:zipCode><v1:ctry>US</v1:ctry><v1:email>example@example.com</v1:email><v1:ship><v1:fullName>Longbob Longsen</v1:fullName><v1:addrLn1>450 Main</v1:addrLn1><v1:addrLn2>Suite 100</v1:addrLn2><v1:city>Broomfield</v1:city><v1:state>CO</v1:state><v1:zipCode>85284</v1:zipCode><v1:phone>3334445555</v1:phone></v1:ship></v1:contact><v1:reqAmt>100</v1:reqAmt><v1:authReq><v1:ordNr>7a0f975b6e86aff44364360cbc6d0f00</v1:ordNr></v1:authReq></v1:SendTranRequest></soapenv:Body></soapenv:Envelope>" +-> "HTTP/1.1 200 OK\r\n" +-> "Content-Type: text/xml;charset=utf-8\r\n" +-> "Date: Thu, 21 Jan 2016 20:09:44 GMT\r\n" +-> "Server: WebServer\r\n" +-> "Set-Cookie: NSC_UMT12_DFSU-xt.dfsu.UYQ.dpn=ffffffff0918172545525d5f4f58455e445a4a42378b;expires=Thu, 21-Jan-2016 20:17:43 GMT;path=/;secure;httponly\r\n" +-> "Cache-Control: private\r\n" +-> "Content-Encoding: gzip\r\n" +-> "Transfer-Encoding: chunked\r\n" +-> "\r\n" +-> "1AA \r\n" +reading 426 bytes... +-> "" +read 426 bytes +reading 2 bytes... +-> "\r\n" +read 2 bytes +-> "0\r\n" +-> "\r\n" +Conn close + POST_SCRUBBED + end +end diff --git a/test/unit/gateways/transact_pro_test.rb b/test/unit/gateways/transact_pro_test.rb new file mode 100644 index 00000000000..6323dc78536 --- /dev/null +++ b/test/unit/gateways/transact_pro_test.rb @@ -0,0 +1,199 @@ +require 'test_helper' + +class TransactProTest < Test::Unit::TestCase + def setup + @gateway = TransactProGateway.new( + guid: 'login', + password: 'password', + terminal: 'terminal' + ) + + @credit_card = credit_card + @amount = 100 + + @options = { + order_id: '1', + billing_address: address, + description: 'Store Purchase' + } + end + + def test_successful_purchase + @gateway.expects(:ssl_post).twice.returns(successful_init_response, successful_purchase_response) + + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + + assert_equal 'a27891dedd57e875df653144c518b8fb646b2351|100', response.authorization + assert_equal 'Success', response.message + assert_equal 'a27891dedd57e875df653144c518b8fb646b2351', response.params['id'] + assert_equal '646391', response.params['approval_code'] + assert response.test? + end + + def test_failed_purchase + @gateway.expects(:ssl_post).twice.returns(successful_init_response, failed_purchase_response) + + response = @gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_equal 'fed54b10b610bb760816aad42721672e8fd19327|100', response.authorization + assert_equal 'Failed', response.message + assert_equal '908', response.params['result_code'] + end + + def test_successful_authorize + @gateway.expects(:ssl_post).twice.returns(successful_init_response, successful_authorize_response) + + response = @gateway.authorize(@amount, @credit_card, @options) + + assert_success response + assert_equal '3d25ab044075924479d3836f549b015481d15d74|100', response.authorization + assert_equal 'HoldOk', response.message + end + + def test_failed_authorize + @gateway.expects(:ssl_post).twice.returns(successful_init_response, failed_authorize_response) + + response = @gateway.purchase(@amount, @credit_card, @options) + + assert_failure response + assert_equal 'c9c789a575ba8556e2c5f56174d859c23ac56e09|100', response.authorization + assert_equal 'Failed', response.message + assert_equal '908', response.params['result_code'] + end + + def test_successful_capture + @gateway.expects(:ssl_post).returns(successful_capture_response) + + capture = @gateway.capture(nil, '3d25ab044075924479d3836f549b015481d15d74|100') + assert_success capture + assert_equal '3d25ab044075924479d3836f549b015481d15d74|100', capture.authorization + assert_equal 'Success', capture.message + end + + def test_partial_capture + @gateway.expects(:ssl_post).never + + assert_raise(ArgumentError) do + @gateway.capture(@amount-1, '3d25ab044075924479d3836f549b015481d15d74|100') + end + end + + def test_failed_capture + @gateway.expects(:ssl_post).returns(failed_capture_response) + + capture = @gateway.capture(nil, '3d25ab044075924479d3836f549b015481d15d74|100') + assert_failure capture + assert_equal '4dd02f79f428470bbd794590834dfbf38b5721ac|100', capture.authorization + assert_equal 'Failed', capture.message + end + + def test_successful_refund + @gateway.expects(:ssl_post).returns(successful_refund_response) + + assert refund = @gateway.refund(@amount, '3d25ab044075924479d3836f549b015481d15d74|100') + assert_success refund + assert_equal 'Refund Success', refund.message + assert_equal '3d25ab044075924479d3836f549b015481d15d74', refund.authorization + end + + def test_partial_refund + @gateway.expects(:ssl_post).returns(successful_refund_response) + + assert refund = @gateway.refund(@amount-1, '3d25ab044075924479d3836f549b015481d15d74|100') + assert_success refund + assert_equal 'Refund Success', refund.message + assert_equal '3d25ab044075924479d3836f549b015481d15d74', refund.authorization + end + + def test_failed_refund + @gateway.expects(:ssl_post).returns(failed_refund_response) + + assert refund = @gateway.refund(@amount+1, '3d25ab044075924479d3836f549b015481d15d74|100') + assert_failure refund + end + + def test_successful_void + @gateway.expects(:ssl_post).returns(successful_void_response) + + assert void = @gateway.void('3d25ab044075924479d3836f549b015481d15d74|100') + assert_success void + assert_equal '3d25ab044075924479d3836f549b015481d15d74', void.authorization + assert_equal 'DMS canceled OK', void.message + end + + def test_failed_void + @gateway.expects(:ssl_post).returns(failed_void_response) + + void = @gateway.void('') + assert_failure void + assert_match %r{fail}i, void.message + end + + def test_successful_verify + @gateway.expects(:ssl_post).times(3).returns(successful_init_response, successful_authorize_response, successful_void_response) + + response = @gateway.verify(@credit_card, @options) + assert_success response + end + + def test_successful_verify_with_failed_void + @gateway.expects(:ssl_post).times(3).returns(successful_init_response, successful_authorize_response, failed_void_response) + + response = @gateway.verify(@credit_card, @options) + assert_success response + end + + def test_failed_verify + @gateway.expects(:ssl_post).times(2).returns(successful_init_response, failed_authorize_response) + + response = @gateway.verify(@credit_card, @options) + assert_failure response + end + + private + + def successful_purchase_response + 'ID:a27891dedd57e875df653144c518b8fb646b2351~Status:Success~MerchantID:1410896668~Terminal:Rietumu - non3D~ResultCode:000~ApprovalCode:646391~CardIssuerCountry:XX' + end + + def failed_purchase_response + 'ID:fed54b10b610bb760816aad42721672e8fd19327~Status:Failed~MerchantID:1410965369~Terminal:Rietumu - non3D~ResultCode:908~ApprovalCode:-3~CardIssuerCountry:XX' + end + + def successful_authorize_response + 'ID:3d25ab044075924479d3836f549b015481d15d74~Status:HoldOk~MerchantID:1410974273~Terminal:Rietumu - non3D~ResultCode:000~ApprovalCode:524282~CardIssuerCountry:XX' + end + + def failed_authorize_response + 'ID:c9c789a575ba8556e2c5f56174d859c23ac56e09~Status:Failed~MerchantID:1410974976~Terminal:Rietumu - non3D~ResultCode:908~ApprovalCode:-3~CardIssuerCountry:XX' + end + + def successful_capture_response + 'ID:3d25ab044075924479d3836f549b015481d15d74~Status:Success~MerchantID:1410974273~Terminal:Rietumu - non3D~ResultCode:000~ApprovalCode:524282~CardIssuerCountry:XX' + end + + def failed_capture_response + 'ID:4dd02f79f428470bbd794590834dfbf38b5721ac~Status:Failed~MerchantID:1325788706~Terminal:TerminalName~ResultCode:000~ApprovalCode:804958' + end + + def successful_refund_response + 'Refund Success' + end + + def failed_refund_response + "1411048562:Multiple refund requests 'b8828586f5ece2874e26e8ac021f410610b6f921' detected, please wait 3 minutes and try again:" + end + + def successful_void_response + 'DMS canceled OK' + end + + def failed_void_response + 'DMS Cancel failed' + end + + def successful_init_response + 'OK:a27891dedd57e875df653144c518b8fb646b2351' + end +end diff --git a/test/unit/gateways/trexle_test.rb b/test/unit/gateways/trexle_test.rb new file mode 100644 index 00000000000..a1ee4295ccc --- /dev/null +++ b/test/unit/gateways/trexle_test.rb @@ -0,0 +1,444 @@ +require 'test_helper' + +class TrexleTest < Test::Unit::TestCase + def setup + @gateway = TrexleGateway.new(api_key: 'THIS_IS_NOT_A_REAL_API_KEY') + + @credit_card = credit_card + @amount = 100 + + @options = { + email: 'john@trexle.com', + billing_address: address, + description: 'Store Purchase', + ip: '127.0.0.1' + } + end + + def test_required_api_key_on_initialization + assert_raises ArgumentError do + TrexleGateway.new + end + end + + def test_default_currency + assert_equal 'USD', TrexleGateway.default_currency + end + + def test_money_format + assert_equal :cents, TrexleGateway.money_format + end + + def test_url + assert_equal 'https://core.trexle.com/api/v1', TrexleGateway.test_url + end + + def test_live_url + assert_equal 'https://core.trexle.com/api/v1', TrexleGateway.live_url + end + + def test_supported_countries + expected_supported_countries = %w(AD AE AT AU BD BE BG BN CA CH CY CZ DE DK EE EG ES FI FR GB + GI GR HK HU ID IE IL IM IN IS IT JO KW LB LI LK LT LU LV MC + MT MU MV MX MY NL NO NZ OM PH PL PT QA RO SA SE SG SI SK SM + TR TT UM US VA VN ZA) + assert_equal expected_supported_countries, TrexleGateway.supported_countries + end + + def test_supported_cardtypes + assert_equal [:visa, :master, :american_express], TrexleGateway.supported_cardtypes + end + + def test_display_name + assert_equal 'Trexle', TrexleGateway.display_name + end + + def test_setup_purchase_parameters + @gateway.expects(:add_amount).with(instance_of(Hash), @amount, @options) + @gateway.expects(:add_customer_data).with(instance_of(Hash), @options) + @gateway.expects(:add_invoice).with(instance_of(Hash), @options) + @gateway.expects(:add_creditcard).with(instance_of(Hash), @credit_card) + @gateway.expects(:add_address).with(instance_of(Hash), @credit_card, @options) + + @gateway.stubs(:ssl_request).returns(successful_purchase_response) + assert_success @gateway.purchase(@amount, @credit_card, @options) + end + + def test_successful_purchase + post_data = {} + headers = {} + @gateway.stubs(:headers).returns(headers) + @gateway.stubs(:post_data).returns(post_data) + @gateway.expects(:ssl_request).with(:post, 'https://core.trexle.com/api/v1/charges', post_data, headers).returns(successful_purchase_response) + + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal 'charge_0cfad7ee5ffe75f58222bff214bfa5cc7ad7c367', response.authorization + assert_equal JSON.parse(successful_purchase_response), response.params + assert response.test? + end + + def test_unsuccessful_request + @gateway.expects(:ssl_request).returns(failed_purchase_response) + + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_equal 'Invalid response.', response.message + end + + def test_unparsable_body_of_successful_response + @gateway.stubs(:raw_ssl_request).returns(MockResponse.succeeded('not-json')) + + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_match(/Invalid JSON response received/, response.message) + end + + def test_successful_store + @gateway.expects(:ssl_request).returns(successful_store_response) + assert response = @gateway.store(@credit_card, @options) + assert_success response + assert_equal 'token_2cb443cf26b6ecdadd8144d1fac8240710aa41f1', response.authorization + assert_equal JSON.parse(successful_store_response), response.params + assert response.test? + end + + def test_unsuccessful_store + @gateway.expects(:ssl_request).returns(failed_store_response) + + assert response = @gateway.store(@credit_card, @options) + assert_failure response + assert_equal 'Invalid response.', response.message + end + + def test_successful_update + token = 'token_940ade441a23d53e04017f53af6c3a1eae9978ae' + @gateway.expects(:ssl_request).with(:put, "https://core.trexle.com/api/v1/customers/#{token}", instance_of(String), instance_of(Hash)).returns(successful_customer_store_response) + assert response = @gateway.update('token_940ade441a23d53e04017f53af6c3a1eae9978ae', @credit_card, @options) + assert_success response + assert_equal 'token_940ade441a23d53e04017f53af6c3a1eae9978ae', response.authorization + assert_equal JSON.parse(successful_customer_store_response), response.params + assert response.test? + end + + def test_successful_refund + token = 'charge_0cfad7ee5ffe75f58222bff214bfa5cc7ad7c367' + @gateway.expects(:ssl_request).with(:post, "https://core.trexle.com/api/v1/charges/#{token}/refunds", {amount: '100'}.to_json, instance_of(Hash)).returns(successful_refund_response) + + assert response = @gateway.refund(100, token) + assert_equal 'refund_7f696a86f9cb136520c51ea90c17f687b8df40b0', response.authorization + assert_success response + assert response.test? + end + + def test_unsuccessful_refund + token = 'charge_0cfad7ee5ffe75f58222bff214bfa5cc7ad7c367' + @gateway.expects(:ssl_request).with(:post, "https://core.trexle.com/api/v1/charges/#{token}/refunds", {amount: '100'}.to_json, instance_of(Hash)).returns(failed_refund_response) + + assert response = @gateway.refund(100, token) + assert_failure response + assert_equal 'Invalid response.', response.message + end + + def test_successful_authorize + post_data = {} + headers = {} + @gateway.stubs(:headers).returns(headers) + @gateway.stubs(:post_data).returns(post_data) + @gateway.expects(:ssl_request).with(:post, 'https://core.trexle.com/api/v1/charges', post_data, headers).returns(successful_purchase_response) + + assert response = @gateway.authorize(@amount, @credit_card, @options) + assert_success response + assert_equal 'charge_0cfad7ee5ffe75f58222bff214bfa5cc7ad7c367', response.authorization + assert_equal JSON.parse(successful_purchase_response), response.params + assert response.test? + end + + def test_successful_capture + post_data = {} + headers = {} + token = 'charge_6e47a330dca67ec7f696e8b650db22fe69bb8499' + @gateway.stubs(:headers).returns(headers) + @gateway.stubs(:post_data).returns(post_data) + @gateway.expects(:ssl_request).with(:put, "https://core.trexle.com/api/v1/charges/#{token}/capture", post_data, headers).returns(successful_capture_response) + + assert response = @gateway.capture(100, token) + assert_success response + assert_equal token, response.authorization + assert response.test? + end + + def test_store_parameters + @gateway.expects(:add_creditcard).with(instance_of(Hash), @credit_card) + @gateway.expects(:add_address).with(instance_of(Hash), @credit_card, @options) + @gateway.expects(:ssl_request).returns(successful_store_response) + assert_success @gateway.store(@credit_card, @options) + end + + def test_update_parameters + @gateway.expects(:add_creditcard).with(instance_of(Hash), @credit_card) + @gateway.expects(:add_address).with(instance_of(Hash), @credit_card, @options) + @gateway.expects(:ssl_request).returns(successful_store_response) + assert_success @gateway.update('token_6b5d89f723d1aeee8ff0c588fd4ccbaae223b9aa', @credit_card, @options) + end + + def test_add_amount + @gateway.expects(:amount).with(100).returns('100') + post = {} + @gateway.send(:add_amount, post, 100, @options) + assert_equal '100', post[:amount] + end + + def test_set_default_currency + @gateway.expects(:currency).with(100).returns('USD') + post = {} + @gateway.send(:add_amount, post, 100, @options) + assert_equal 'USD', post[:currency] + end + + def test_set_currency + @gateway.expects(:currency).never + post = {} + @options[:currency] = 'USD' + @gateway.send(:add_amount, post, 100, @options) + assert_equal 'USD', post[:currency] + end + + def test_set_currency_case + @gateway.expects(:currency).never + post = {} + @options[:currency] = 'usd' + @gateway.send(:add_amount, post, 100, @options) + assert_equal 'USD', post[:currency] + end + + def test_add_customer_data + post = {} + + @gateway.send(:add_customer_data, post, @options) + + assert_equal 'john@trexle.com', post[:email] + assert_equal '127.0.0.1', post[:ip_address] + end + + def test_add_address + post = {} + + @gateway.send(:add_address, post, @credit_card, @options) + + assert_equal @options[:billing_address][:address1], post[:card][:address_line1] + assert_equal @options[:billing_address][:city], post[:card][:address_city] + assert_equal @options[:billing_address][:zip], post[:card][:address_postcode] + assert_equal @options[:billing_address][:state], post[:card][:address_state] + assert_equal @options[:billing_address][:country], post[:card][:address_country] + end + + def test_add_address_with_card_token + post = {} + + @gateway.send(:add_address, post, 'somecreditcardtoken', @options) + + assert_equal false, post.has_key?(:card) + end + + def test_add_invoice + post = {} + @gateway.send(:add_invoice, post, @options) + + assert_equal @options[:description], post[:description] + end + + def test_add_creditcard + post = {} + @gateway.send(:add_creditcard, post, @credit_card) + + assert_equal @credit_card.number, post[:card][:number] + assert_equal @credit_card.month, post[:card][:expiry_month] + assert_equal @credit_card.year, post[:card][:expiry_year] + assert_equal @credit_card.verification_value, post[:card][:cvc] + assert_equal @credit_card.name, post[:card][:name] + end + + def test_add_creditcard_with_card_token + post = {} + @gateway.send(:add_creditcard, post, 'token_f974687e4e866d6cca534e1cd42236817d315b3a') + assert_equal 'token_f974687e4e866d6cca534e1cd42236817d315b3a', post[:card_token] + assert_false post.has_key?(:card) + end + + def test_add_creditcard_with_customer_token + post = {} + @gateway.send(:add_creditcard, post, 'token_2cb443cf26b6ecdadd8144d1fac8240710aa41f1') + assert_equal 'token_2cb443cf26b6ecdadd8144d1fac8240710aa41f1', post[:card_token] + assert_false post.has_key?(:card) + end + + def test_post_data + post = {} + @gateway.send(:add_creditcard, post, @credit_card) + assert_equal post.to_json, @gateway.send(:post_data, post) + end + + def test_headers + expected_headers = { + 'Content-Type' => 'application/json', + 'Authorization' => "Basic #{Base64.strict_encode64('THIS_IS_NOT_A_REAL_API_KEY:').strip}" + } + + @gateway.expects(:ssl_request).with(:post, anything, anything, expected_headers).returns(successful_purchase_response) + assert @gateway.purchase(@amount, @credit_card, {}) + + expected_headers['X-Partner-Key'] = 'MyPartnerKey' + expected_headers['X-Safe-Card'] = '1' + + @gateway.expects(:ssl_request).with(:post, anything, anything, expected_headers).returns(successful_purchase_response) + assert @gateway.purchase(@amount, @credit_card, partner_key: 'MyPartnerKey', safe_card: '1') + end + + def test_transcript_scrubbing + assert_equal scrubbed_transcript, @gateway.scrub(transcript) + end + + private + + def successful_purchase_response + '{ + "response":{ + "token":"charge_0cfad7ee5ffe75f58222bff214bfa5cc7ad7c367", + "success":true, + "captured":true + } + }' + end + + def failed_purchase_response + '{ + "error":"Payment failed", + "detail":"An error occurred while processing your card. Try again in a little bit." + }' + end + + def successful_store_response + '{ + "response":{ + "token":"token_2cb443cf26b6ecdadd8144d1fac8240710aa41f1", + "card":{ + "token":"token_f974687e4e866d6cca534e1cd42236817d315b3a", + "primary":true + } + } + }' + end + + def failed_store_response + '{ + "error":"an error has occured", + "detail":"invalid token" + }' + end + + def successful_customer_store_response + '{ + "response":{ + "token":"token_940ade441a23d53e04017f53af6c3a1eae9978ae", + "card":{ + "token":"token_9a3f559962cbf6828e2cc38a02023565b0294548", + "scheme":"master", + "display_number":"XXXX-XXXX-XXXX-4444", + "expiry_year":2019, + "expiry_month":9, + "cvc":123, + "name":"Longbob Longsen", + "address_line1":"456 My Street", + "address_line2":null, + "address_city":"Ottawa", + "address_state":"ON", + "address_postcode":"K1C2N6", + "address_country":"CA", + "primary":true + } + } + }' + end + + def failed_customer_store_response + '{ + "error":"an error has occured", + "detail":"invalid token" + }' + end + + def successful_refund_response + '{ + "response":{ + "token":"refund_7f696a86f9cb136520c51ea90c17f687b8df40b0", + "success":true, + "amount":100, + "charge":"charge_ee4542e9f4d2c50f7fea55b694423a53991a323a", + "status_message":"Transaction approved" + } + }' + end + + def failed_refund_response + '{ + "error":"Refund failed", + "detail":"invalid token" + }' + end + + def successful_capture_response + '{ + "response":{ + "token":"charge_6e47a330dca67ec7f696e8b650db22fe69bb8499", + "success":true, + "captured":true + } + }' + end + + def transcript + '{ + "amount":"100", + "currency":"USD", + "email":"john@trexle.com", + "ip_address":"66.249.79.118", + "description":"Store Purchase 1437598192", + "card":{ + "number":"5555555555554444", + "expiry_month":9, + "expiry_year":2017, + "cvc":"123", + "name":"Longbob Longsen", + "address_line1":"456 My Street", + "address_city":"Ottawa", + "address_postcode":"K1C2N6", + "address_state":"ON", + "address_country":"CA" + } + }' + end + + def scrubbed_transcript + '{ + "amount":"100", + "currency":"USD", + "email":"john@trexle.com", + "ip_address":"66.249.79.118", + "description":"Store Purchase 1437598192", + "card":{ + "number":"[FILTERED]", + "expiry_month":9, + "expiry_year":2017, + "cvc":"[FILTERED]", + "name":"Longbob Longsen", + "address_line1":"456 My Street", + "address_city":"Ottawa", + "address_postcode":"K1C2N6", + "address_state":"ON", + "address_country":"CA" + } + }' + end + +end diff --git a/test/unit/gateways/trust_commerce_test.rb b/test/unit/gateways/trust_commerce_test.rb index f687e8bf83e..84d14090b66 100644 --- a/test/unit/gateways/trust_commerce_test.rb +++ b/test/unit/gateways/trust_commerce_test.rb @@ -1,16 +1,25 @@ require 'test_helper' class TrustCommerceTest < Test::Unit::TestCase + include CommStub def setup @gateway = TrustCommerceGateway.new( :login => 'TestMerchant', - :password => 'password' + :password => 'password', + :aggregator_id => 'abc123' ) # Force SSL post @gateway.stubs(:tclink?).returns(false) @amount = 100 + @check = check @credit_card = credit_card('4111111111111111') + + @options_with_custom_fields = { + custom_fields: { + 'customfield1' => 'test1' + } + } end def test_successful_purchase @@ -18,7 +27,7 @@ def test_successful_purchase assert response = @gateway.purchase(@amount, @credit_card) assert_instance_of Response, response assert_success response - assert_equal '025-0007423614', response.authorization + assert_equal '025-0007423614|sale', response.authorization end def test_unsuccessful_purchase @@ -27,29 +36,111 @@ def test_unsuccessful_purchase assert_instance_of Response, response assert_failure response end - - def test_amount_style - assert_equal '1034', @gateway.send(:amount, 1034) - - assert_raise(ArgumentError) do - @gateway.send(:amount, '10.34') - end + + def test_succesful_purchase_with_check + ActiveMerchant::Billing::TrustCommerceGateway.application_id = 'abc123' + stub_comms do + @gateway.purchase(@amount, @check) + end.check_request do |endpoint, data, headers| + assert_match(%r{aggregator1}, data) + assert_match(%r{name=Jim\+Smith}, data) + end.respond_with(successful_purchase_response) + end + + def test_succesful_purchase_with_custom_fields + stub_comms do + @gateway.purchase(@amount, @credit_card, @options_with_custom_fields) + end.check_request do |endpoint, data, headers| + assert_match(%r{customfield1=test1}, data) + end.respond_with(successful_purchase_response) + end + + def test_succesful_authorize_with_custom_fields + stub_comms do + @gateway.authorize(@amount, @check, @options_with_custom_fields) + end.check_request do |endpoint, data, headers| + assert_match(%r{customfield1=test1}, data) + end.respond_with(successful_authorize_response) + end + + def test_successful_void_from_purchase + stub_comms do + @gateway.void('1235|sale') + end.check_request do |endpoint, data, headers| + assert_match(%r{action=void}, data) + end.respond_with(successful_void_response) + end + + def test_successful_void_from_authorize + stub_comms do + @gateway.void('1235|preauth') + end.check_request do |endpoint, data, headers| + assert_match(%r{action=reversal}, data) + end.respond_with(successful_void_response) + end + + def test_succesful_capture_with_custom_fields + stub_comms do + @gateway.capture(@amount, 'auth', @options_with_custom_fields) + end.check_request do |endpoint, data, headers| + assert_match(%r{customfield1=test1}, data) + end.respond_with(successful_capture_response) + end + + def test_succesful_refund_with_custom_fields + stub_comms do + @gateway.refund(@amount, 'auth|100', @options_with_custom_fields) + end.check_request do |endpoint, data, headers| + assert_match(%r{customfield1=test1}, data) + end.respond_with(successful_refund_response) + end + + def test_succesful_void_with_custom_fields + stub_comms do + @gateway.void('1235|sale', @options_with_custom_fields) + end.check_request do |endpoint, data, headers| + assert_match(%r{customfield1=test1}, data) + end.respond_with(successful_void_response) + end + + def test_succesful_store_with_custom_fields + stub_comms do + @gateway.store(@credit_card, @options_with_custom_fields) + end.check_request do |endpoint, data, headers| + assert_match(%r{customfield1=test1}, data) + end.respond_with(successful_store_response) + end + + def test_succesful_unstore_with_custom_fields + stub_comms do + @gateway.unstore('test', @options_with_custom_fields) + end.check_request do |endpoint, data, headers| + assert_match(%r{customfield1=test1}, data) + end.respond_with(successful_unstore_response) + end + + def test_amount_style + assert_equal '1034', @gateway.send(:amount, 1034) + + assert_raise(ArgumentError) do + @gateway.send(:amount, '10.34') + end end - + def test_avs_result @gateway.expects(:ssl_post).returns(successful_purchase_response) - + response = @gateway.purchase(@amount, @credit_card) assert_equal 'Y', response.avs_result['code'] end - + def test_cvv_result @gateway.expects(:ssl_post).returns(successful_purchase_response) - + response = @gateway.purchase(@amount, @credit_card) assert_equal 'P', response.cvv_result['code'] end - + def test_supported_countries assert_equal ['US'], TrustCommerceGateway.supported_countries end @@ -57,19 +148,33 @@ def test_supported_countries def test_supported_card_types assert_equal [:visa, :master, :discover, :american_express, :diners_club, :jcb], TrustCommerceGateway.supported_cardtypes end - + def test_test_flag_should_be_set_when_using_test_login_in_production - Base.gateway_mode = :production + Base.mode = :production @gateway.expects(:ssl_post).returns(successful_purchase_response) assert response = @gateway.purchase(@amount, @credit_card) assert_success response assert response.test? ensure - Base.gateway_mode = :test + Base.mode = :test + end + + def test_transcript_scrubbing + assert_equal scrubbed_transcript, @gateway.scrub(transcript) end - + private - + + def successful_authorize_response + <<-RESPONSE +authcode=123456 +transid=026-0193338367, +status=approved +avs=Y +cvv=M + RESPONSE + end + def successful_purchase_response <<-RESPONSE transid=025-0007423614 @@ -78,7 +183,14 @@ def successful_purchase_response cvv=P RESPONSE end - + + def successful_capture_response + <<-RESPONSE +transid=026-0193338993 +status=accepted + RESPONSE + end + def unsuccessful_purchase_response <<-RESPONSE transid=025-0007423827 @@ -87,4 +199,47 @@ def unsuccessful_purchase_response cvv=N RESPONSE end + + def successful_void_response + <<-RESPONSE +transid=025-0007423828 +status=accpeted + RESPONSE + end + + def successful_refund_response + <<-RESPONSE +transid=026-0193345407 +status=accepted + RESPONSE + end + + def successful_store_response + <<-RESPONSE +transid=026-0193346109 +status=approved, +cvv=M, +avs=0 +billingid=Q5T7PT + RESPONSE + end + + def successful_unstore_response + <<-RESPONSE +transid=026-0193346231 +status=rejected + RESPONSE + end + + def transcript + <<-TRANSCRIPT +action=sale&demo=y&password=password&custid=TestMerchant&shipto_zip=90001&shipto_state=CA&shipto_city=Somewhere&shipto_address1=123+Test+St.&avs=n&zip=90001&state=CA&city=Somewhere&address1=123+Test+St.&cvv=1234&exp=0916&cc=4111111111111111&name=Longbob+Longsen&media=cc&ip=10.10.10.10&email=cody%40example.com&ticket=%231000.1&amount=100 + TRANSCRIPT + end + + def scrubbed_transcript + <<-TRANSCRIPT +action=sale&demo=y&password=[FILTERED]&custid=TestMerchant&shipto_zip=90001&shipto_state=CA&shipto_city=Somewhere&shipto_address1=123+Test+St.&avs=n&zip=90001&state=CA&city=Somewhere&address1=123+Test+St.&cvv=[FILTERED]&exp=0916&cc=[FILTERED]&name=Longbob+Longsen&media=cc&ip=10.10.10.10&email=cody%40example.com&ticket=%231000.1&amount=100 + TRANSCRIPT + end end diff --git a/test/unit/gateways/usa_epay_advanced_test.rb b/test/unit/gateways/usa_epay_advanced_test.rb index c498193cd22..7c22b87d841 100644 --- a/test/unit/gateways/usa_epay_advanced_test.rb +++ b/test/unit/gateways/usa_epay_advanced_test.rb @@ -1,4 +1,5 @@ # encoding: utf-8 + require 'test_helper' require 'logger' @@ -26,40 +27,40 @@ def setup :year => 12, :brand => 'visa', :verification_value => '123', - :first_name => "Fred", - :last_name => "Flintstone" + :first_name => 'Fred', + :last_name => 'Flintstone' ) @check = ActiveMerchant::Billing::Check.new( :account_number => '123456789012', :routing_number => '123456789', :account_type => 'checking', - :first_name => "Fred", - :last_name => "Flintstone" + :first_name => 'Fred', + :last_name => 'Flintstone' ) payment_methods = [ { - :name => "My Visa", # optional + :name => 'My Visa', # optional :sort => 2, # optional :method => @credit_card }, { - :name => "My Checking", + :name => 'My Checking', :method => @check } ] payment_method = { - :name => "My new Visa", # optional + :name => 'My new Visa', # optional :method => @credit_card } @customer_options = { :id => 1, # optional: merchant assigned id, usually db id - :notes => "Note about customer", # optional - :data => "Some Data", # optional - :url => "awesomesite.com", # optional + :notes => 'Note about customer', # optional + :data => 'Some Data', # optional + :url => 'awesomesite.com', # optional :payment_methods => payment_methods # optional } @@ -78,7 +79,7 @@ def setup @standard_transaction_options = { :method_id => 0, :command => 'Sale', - :amount => 2000 #20.00 + :amount => 2000 # 20.00 } @get_payment_options = { @@ -151,7 +152,7 @@ def test_successful_void def test_successful_credit @gateway.expects(:ssl_post).returns(successful_credit_response) - assert_deprecation_warning(Gateway::CREDIT_DEPRECATION_MESSAGE, @gateway) do + assert_deprecation_warning(Gateway::CREDIT_DEPRECATION_MESSAGE) do assert response = @gateway.credit(1234, @credit_card, @options) assert_instance_of Response, response assert response.test? @@ -199,6 +200,18 @@ def test_successful_update_customer assert_nil response.authorization end + def test_successful_quick_update_customer + @gateway.expects(:ssl_post).returns(successful_customer_response('quickUpdateCustomer')) + + assert response = @gateway.quick_update_customer({customer_number: @options[:customer_number], update_data: @customer_options}) + assert_instance_of Response, response + assert response.test? + assert_success response + assert_equal 'true', response.params['quick_update_customer_return'] + assert_equal 'true', response.message + assert_nil response.authorization + end + def test_successful_enable_customer @options.merge!(@standard_transaction_options) @gateway.expects(:ssl_post).returns(successful_customer_response('enableCustomer')) @@ -277,7 +290,7 @@ def test_successful_get_customer_payment_methods end def test_successful_update_customer_payment_method - @options.merge!(@payment_options).merge!(:method_id => 1) + @options.merge!(@payment_options)[:method_id] = 1 @gateway.expects(:ssl_post).returns(successful_update_customer_payment_method_response) assert response = @gateway.update_customer_payment_method(@options) @@ -408,7 +421,7 @@ def test_successful_run_check_credit end # TODO get post_auth response - #def test_successful_post_auth + # def test_successful_post_auth # @options.merge!(:authorization_code => 'bogus') # @gateway.expects(:ssl_post).returns(successful_post_auth_response) @@ -420,7 +433,7 @@ def test_successful_run_check_credit # #assert_equal '47568732', response.authorization # puts response.inspect - #end + # end def test_successful_run_quick_sale @options.merge!(@transaction_options) @@ -482,7 +495,7 @@ def test_successful_refund_transaction end # TODO get override_transaction response - #def test_successful_override_transaction + # def test_successful_override_transaction # @gateway.expects(:ssl_post).returns(successful_override_transaction_response) # assert response = @gateway.override_transaction(@options) @@ -491,7 +504,7 @@ def test_successful_refund_transaction # assert response.test? # puts response.inspect - #end + # end # Transaction Status ================================================ @@ -642,7 +655,7 @@ def failed_add_customer_payment_method_response def successful_get_customer_payment_method_response <<-XML -<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="urn:usaepay" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><SOAP-ENV:Body><ns1:getCustomerPaymentMethodResponse><getCustomerPaymentMethodReturn xsi:type="ns1:PaymentMethod"><MethodType xsi:type="xsd:string">cc</MethodType><MethodID xsi:type="xsd:integer">103</MethodID><MethodName xsi:type="xsd:string">My CC</MethodName><SecondarySort xsi:type="xsd:integer">5</SecondarySort><Created xsi:type="xsd:dateTime">2011-06-09T13:48:57+08:00</Created><Modified xsi:type="xsd:dateTime">2011-06-09T13:48:57+08:00</Modified><AvsStreet xsi:type="xsd:string">1234 My Street</AvsStreet><AvsZip xsi:type="xsd:string">K1C2N6</AvsZip><CardExpiration xsi:type="xsd:string">2012-12</CardExpiration><CardNumber xsi:type="xsd:string">XXXXXXXXXXXX2224</CardNumber><CardType xsi:type="xsd:string">V</CardType></getCustomerPaymentMethodReturn></ns1:getCustomerPaymentMethodResponse></SOAP-ENV:Body></SOAP-ENV:Envelope> +<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="urn:usaepay" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><SOAP-ENV:Body><ns1:getCustomerPaymentMethodResponse><getCustomerPaymentMethodReturn xsi:type="ns1:PaymentMethod"><MethodType xsi:type="xsd:string">cc</MethodType><MethodID xsi:type="xsd:integer">103</MethodID><MethodName xsi:type="xsd:string">My CC</MethodName><SecondarySort xsi:type="xsd:integer">5</SecondarySort><Created xsi:type="xsd:dateTime">2011-06-09T13:48:57+08:00</Created><Modified xsi:type="xsd:dateTime">2011-06-09T13:48:57+08:00</Modified><AvsStreet xsi:type="xsd:string">456 My Street</AvsStreet><AvsZip xsi:type="xsd:string">K1C2N6</AvsZip><CardExpiration xsi:type="xsd:string">2012-12</CardExpiration><CardNumber xsi:type="xsd:string">XXXXXXXXXXXX2224</CardNumber><CardType xsi:type="xsd:string">V</CardType></getCustomerPaymentMethodReturn></ns1:getCustomerPaymentMethodResponse></SOAP-ENV:Body></SOAP-ENV:Envelope> XML end @@ -654,13 +667,13 @@ def failed_get_customer_payment_method_response def successful_get_customer_payment_methods_response <<-XML -<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="urn:usaepay" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><SOAP-ENV:Body><ns1:getCustomerPaymentMethodsResponse><getCustomerPaymentMethodsReturn SOAP-ENC:arrayType="ns1:PaymentMethod[2]" xsi:type="ns1:PaymentMethodArray"><item xsi:type="ns1:PaymentMethod"><MethodType xsi:type="xsd:string">cc</MethodType><MethodID xsi:type="xsd:integer">93</MethodID><MethodName xsi:type="xsd:string">My CC</MethodName><SecondarySort xsi:type="xsd:integer">5</SecondarySort><Created xsi:type="xsd:dateTime">2011-06-09T08:10:44+08:00</Created><Modified xsi:type="xsd:dateTime">2011-06-09T08:10:44+08:00</Modified><AvsStreet xsi:type="xsd:string">1234 My Street</AvsStreet><AvsZip xsi:type="xsd:string">K1C2N6</AvsZip><CardExpiration xsi:type="xsd:string">2012-12</CardExpiration><CardNumber xsi:type="xsd:string">XXXXXXXXXXXX2224</CardNumber><CardType xsi:type="xsd:string">V</CardType></item><item xsi:type="ns1:PaymentMethod"><MethodType xsi:type="xsd:string">cc</MethodType><MethodID xsi:type="xsd:integer">94</MethodID><MethodName xsi:type="xsd:string">Other CC</MethodName><SecondarySort xsi:type="xsd:integer">12</SecondarySort><Created xsi:type="xsd:dateTime">2011-06-09T08:10:44+08:00</Created><Modified xsi:type="xsd:dateTime">2011-06-09T08:10:44+08:00</Modified><AvsStreet xsi:type="xsd:string">1234 My Street</AvsStreet><AvsZip xsi:type="xsd:string">K1C2N6</AvsZip><CardExpiration xsi:type="xsd:string">2012-12</CardExpiration><CardNumber xsi:type="xsd:string">XXXXXXXXXXXX2224</CardNumber><CardType xsi:type="xsd:string">V</CardType></item></getCustomerPaymentMethodsReturn></ns1:getCustomerPaymentMethodsResponse></SOAP-ENV:Body></SOAP-ENV:Envelope> +<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="urn:usaepay" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><SOAP-ENV:Body><ns1:getCustomerPaymentMethodsResponse><getCustomerPaymentMethodsReturn SOAP-ENC:arrayType="ns1:PaymentMethod[2]" xsi:type="ns1:PaymentMethodArray"><item xsi:type="ns1:PaymentMethod"><MethodType xsi:type="xsd:string">cc</MethodType><MethodID xsi:type="xsd:integer">93</MethodID><MethodName xsi:type="xsd:string">My CC</MethodName><SecondarySort xsi:type="xsd:integer">5</SecondarySort><Created xsi:type="xsd:dateTime">2011-06-09T08:10:44+08:00</Created><Modified xsi:type="xsd:dateTime">2011-06-09T08:10:44+08:00</Modified><AvsStreet xsi:type="xsd:string">456 My Street</AvsStreet><AvsZip xsi:type="xsd:string">K1C2N6</AvsZip><CardExpiration xsi:type="xsd:string">2012-12</CardExpiration><CardNumber xsi:type="xsd:string">XXXXXXXXXXXX2224</CardNumber><CardType xsi:type="xsd:string">V</CardType></item><item xsi:type="ns1:PaymentMethod"><MethodType xsi:type="xsd:string">cc</MethodType><MethodID xsi:type="xsd:integer">94</MethodID><MethodName xsi:type="xsd:string">Other CC</MethodName><SecondarySort xsi:type="xsd:integer">12</SecondarySort><Created xsi:type="xsd:dateTime">2011-06-09T08:10:44+08:00</Created><Modified xsi:type="xsd:dateTime">2011-06-09T08:10:44+08:00</Modified><AvsStreet xsi:type="xsd:string">456 My Street</AvsStreet><AvsZip xsi:type="xsd:string">K1C2N6</AvsZip><CardExpiration xsi:type="xsd:string">2012-12</CardExpiration><CardNumber xsi:type="xsd:string">XXXXXXXXXXXX2224</CardNumber><CardType xsi:type="xsd:string">V</CardType></item></getCustomerPaymentMethodsReturn></ns1:getCustomerPaymentMethodsResponse></SOAP-ENV:Body></SOAP-ENV:Envelope> XML end def successful_single_get_customer_payment_methods_response <<-XML -<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="urn:usaepay" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><SOAP-ENV:Body><ns1:getCustomerPaymentMethodsResponse><getCustomerPaymentMethodsReturn SOAP-ENC:arrayType="ns1:PaymentMethod[1]" xsi:type="ns1:PaymentMethodArray"><item xsi:type="ns1:PaymentMethod"><MethodType xsi:type="xsd:string">cc</MethodType><MethodID xsi:type="xsd:integer">15</MethodID><MethodName xsi:type="xsd:string">My Visa</MethodName><SecondarySort xsi:type="xsd:integer">2</SecondarySort><Created xsi:type="xsd:dateTime">2011-06-05T19:44:09+08:00</Created><Modified xsi:type="xsd:dateTime">2011-06-05T19:44:09+08:00</Modified><AvsStreet xsi:type="xsd:string">1234 My Street</AvsStreet><AvsZip xsi:type="xsd:string">K1C2N6</AvsZip><CardExpiration xsi:type="xsd:string">2012-09</CardExpiration><CardNumber xsi:type="xsd:string">XXXXXXXXXXXX4242</CardNumber><CardType xsi:type="xsd:string">V</CardType></item></getCustomerPaymentMethodsReturn></ns1:getCustomerPaymentMethodsResponse></SOAP-ENV:Body></SOAP-ENV:Envelope> +<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="urn:usaepay" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><SOAP-ENV:Body><ns1:getCustomerPaymentMethodsResponse><getCustomerPaymentMethodsReturn SOAP-ENC:arrayType="ns1:PaymentMethod[1]" xsi:type="ns1:PaymentMethodArray"><item xsi:type="ns1:PaymentMethod"><MethodType xsi:type="xsd:string">cc</MethodType><MethodID xsi:type="xsd:integer">15</MethodID><MethodName xsi:type="xsd:string">My Visa</MethodName><SecondarySort xsi:type="xsd:integer">2</SecondarySort><Created xsi:type="xsd:dateTime">2011-06-05T19:44:09+08:00</Created><Modified xsi:type="xsd:dateTime">2011-06-05T19:44:09+08:00</Modified><AvsStreet xsi:type="xsd:string">456 My Street</AvsStreet><AvsZip xsi:type="xsd:string">K1C2N6</AvsZip><CardExpiration xsi:type="xsd:string">2012-09</CardExpiration><CardNumber xsi:type="xsd:string">XXXXXXXXXXXX4242</CardNumber><CardType xsi:type="xsd:string">V</CardType></item></getCustomerPaymentMethodsReturn></ns1:getCustomerPaymentMethodsResponse></SOAP-ENV:Body></SOAP-ENV:Envelope> XML end @@ -783,7 +796,7 @@ def successful_get_check_trace_response def successful_get_transaction_response <<-XML <?xml version="1.0" encoding="UTF-8"?> -<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="urn:usaepay" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><SOAP-ENV:Body><ns1:getTransactionResponse><getTransactionReturn xsi:type="ns1:TransactionObject"><AccountHolder xsi:type="xsd:string"></AccountHolder><BillingAddress xsi:type="ns1:Address"><City xsi:type="xsd:string">Ottawa</City><Company xsi:type="xsd:string">Widgets Inc</Company><Country xsi:type="xsd:string">CA</Country><Email xsi:type="xsd:string"></Email><Fax xsi:type="xsd:string"></Fax><FirstName xsi:type="xsd:string">Jim</FirstName><LastName xsi:type="xsd:string">Smith</LastName><Phone xsi:type="xsd:string">(555)555-5555</Phone><State xsi:type="xsd:string">ON</State><Street xsi:type="xsd:string">1234 My Street</Street><Street2 xsi:type="xsd:string">Apt 1</Street2><Zip xsi:type="xsd:string">K1C2N6</Zip></BillingAddress><CheckData xsi:type="ns1:CheckData"><Account xsi:nil="true"/><Routing xsi:nil="true"/></CheckData><CheckTrace xsi:type="ns1:CheckTrace"/><ClientIP xsi:type="xsd:string">127.0.0.1</ClientIP><CreditCardData xsi:type="ns1:CreditCardData"><AvsStreet xsi:type="xsd:string">1234 My Street</AvsStreet><AvsZip xsi:type="xsd:string">K1C2N6</AvsZip><CardCode xsi:type="xsd:string">XXX</CardCode><CardExpiration xsi:type="xsd:string">XXXX</CardExpiration><CardNumber xsi:type="xsd:string">XXXXXXXXXXXX2224</CardNumber><CardPresent xsi:type="xsd:boolean">false</CardPresent><CardType xsi:type="xsd:string">V</CardType><InternalCardAuth xsi:type="xsd:boolean">false</InternalCardAuth><MagStripe xsi:type="xsd:string"></MagStripe><MagSupport xsi:type="xsd:string"></MagSupport><Pares xsi:type="xsd:string"></Pares><TermType xsi:type="xsd:string"></TermType></CreditCardData><CustomerID xsi:type="xsd:string"></CustomerID><CustomFields SOAP-ENC:arrayType="ns1:FieldValue[0]" xsi:type="ns1:FieldValueArray"/><DateTime xsi:type="xsd:string">2011-06-11 19:23:37</DateTime><Details xsi:type="ns1:TransactionDetail"><Amount xsi:type="xsd:double">50</Amount><Clerk xsi:type="xsd:string"></Clerk><Currency xsi:type="xsd:string"></Currency><Description xsi:type="xsd:string"></Description><Comments xsi:type="xsd:string"></Comments><Discount xsi:type="xsd:double">0</Discount><Invoice xsi:type="xsd:string"></Invoice><NonTax xsi:type="xsd:boolean">false</NonTax><OrderID xsi:type="xsd:string"></OrderID><PONum xsi:type="xsd:string"></PONum><Shipping xsi:type="xsd:double">0</Shipping><Subtotal xsi:type="xsd:double">0</Subtotal><Table xsi:type="xsd:string"></Table><Tax xsi:type="xsd:double">0</Tax><Terminal xsi:type="xsd:string"></Terminal><Tip xsi:type="xsd:double">0</Tip></Details><LineItems SOAP-ENC:arrayType="ns1:LineItem[0]" xsi:type="ns1:LineItemArray"/><Response xsi:type="ns1:TransactionResponse"><AcsUrl xsi:nil="true"/><AuthAmount xsi:type="xsd:double">50</AuthAmount><AuthCode xsi:type="xsd:string">050129</AuthCode><AvsResult xsi:type="xsd:string">Address: Match &amp; 5 Digit Zip: Match</AvsResult><AvsResultCode xsi:type="xsd:string">YYY</AvsResultCode><BatchNum xsi:type="xsd:integer">1</BatchNum><BatchRefNum xsi:type="xsd:integer">14004</BatchRefNum><CardCodeResult xsi:type="xsd:string">Match</CardCodeResult><CardCodeResultCode xsi:type="xsd:string">M</CardCodeResultCode><CardLevelResult xsi:nil="true"/><CardLevelResultCode xsi:nil="true"/><ConversionRate xsi:type="xsd:double">0</ConversionRate><ConvertedAmount xsi:type="xsd:double">0</ConvertedAmount><ConvertedAmountCurrency xsi:type="xsd:string"></ConvertedAmountCurrency><CustNum xsi:type="xsd:integer">0</CustNum><Error xsi:type="xsd:string">Approved</Error><ErrorCode xsi:type="xsd:integer">0</ErrorCode><isDuplicate xsi:type="xsd:boolean">false</isDuplicate><Payload xsi:nil="true"/><RefNum xsi:type="xsd:integer">47568950</RefNum><Result xsi:type="xsd:string">Approved</Result><ResultCode xsi:type="xsd:string">A</ResultCode><Status xsi:type="xsd:string">Pending</Status><StatusCode xsi:type="xsd:string">P</StatusCode><VpasResultCode xsi:nil="true"/></Response><ServerIP xsi:type="xsd:string">67.168.21.42</ServerIP><ShippingAddress xsi:type="ns1:Address"><City xsi:type="xsd:string">Ottawa</City><Company xsi:type="xsd:string">Widgets Inc</Company><Country xsi:type="xsd:string">CA</Country><Email xsi:type="xsd:string"></Email><Fax xsi:type="xsd:string"></Fax><FirstName xsi:type="xsd:string">Jim</FirstName><LastName xsi:type="xsd:string">Smith</LastName><Phone xsi:type="xsd:string">(555)555-5555</Phone><State xsi:type="xsd:string">ON</State><Street xsi:type="xsd:string">1234 My Street</Street><Street2 xsi:type="xsd:string">Apt 1</Street2><Zip xsi:type="xsd:string">K1C2N6</Zip></ShippingAddress><Source xsi:type="xsd:string">test</Source><Status xsi:type="xsd:string">Authorized (Pending Settlement)</Status><TransactionType xsi:type="xsd:string">Sale</TransactionType><User xsi:type="xsd:string">auto</User></getTransactionReturn></ns1:getTransactionResponse></SOAP-ENV:Body></SOAP-ENV:Envelope> +<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="urn:usaepay" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><SOAP-ENV:Body><ns1:getTransactionResponse><getTransactionReturn xsi:type="ns1:TransactionObject"><AccountHolder xsi:type="xsd:string"></AccountHolder><BillingAddress xsi:type="ns1:Address"><City xsi:type="xsd:string">Ottawa</City><Company xsi:type="xsd:string">Widgets Inc</Company><Country xsi:type="xsd:string">CA</Country><Email xsi:type="xsd:string"></Email><Fax xsi:type="xsd:string"></Fax><FirstName xsi:type="xsd:string">Jim</FirstName><LastName xsi:type="xsd:string">Smith</LastName><Phone xsi:type="xsd:string">(555)555-5555</Phone><State xsi:type="xsd:string">ON</State><Street xsi:type="xsd:string">456 My Street</Street><Street2 xsi:type="xsd:string">Apt 1</Street2><Zip xsi:type="xsd:string">K1C2N6</Zip></BillingAddress><CheckData xsi:type="ns1:CheckData"><Account xsi:nil="true"/><Routing xsi:nil="true"/></CheckData><CheckTrace xsi:type="ns1:CheckTrace"/><ClientIP xsi:type="xsd:string">127.0.0.1</ClientIP><CreditCardData xsi:type="ns1:CreditCardData"><AvsStreet xsi:type="xsd:string">456 My Street</AvsStreet><AvsZip xsi:type="xsd:string">K1C2N6</AvsZip><CardCode xsi:type="xsd:string">XXX</CardCode><CardExpiration xsi:type="xsd:string">XXXX</CardExpiration><CardNumber xsi:type="xsd:string">XXXXXXXXXXXX2224</CardNumber><CardPresent xsi:type="xsd:boolean">false</CardPresent><CardType xsi:type="xsd:string">V</CardType><InternalCardAuth xsi:type="xsd:boolean">false</InternalCardAuth><MagStripe xsi:type="xsd:string"></MagStripe><MagSupport xsi:type="xsd:string"></MagSupport><Pares xsi:type="xsd:string"></Pares><TermType xsi:type="xsd:string"></TermType></CreditCardData><CustomerID xsi:type="xsd:string"></CustomerID><CustomFields SOAP-ENC:arrayType="ns1:FieldValue[0]" xsi:type="ns1:FieldValueArray"/><DateTime xsi:type="xsd:string">2011-06-11 19:23:37</DateTime><Details xsi:type="ns1:TransactionDetail"><Amount xsi:type="xsd:double">50</Amount><Clerk xsi:type="xsd:string"></Clerk><Currency xsi:type="xsd:string"></Currency><Description xsi:type="xsd:string"></Description><Comments xsi:type="xsd:string"></Comments><Discount xsi:type="xsd:double">0</Discount><Invoice xsi:type="xsd:string"></Invoice><NonTax xsi:type="xsd:boolean">false</NonTax><OrderID xsi:type="xsd:string"></OrderID><PONum xsi:type="xsd:string"></PONum><Shipping xsi:type="xsd:double">0</Shipping><Subtotal xsi:type="xsd:double">0</Subtotal><Table xsi:type="xsd:string"></Table><Tax xsi:type="xsd:double">0</Tax><Terminal xsi:type="xsd:string"></Terminal><Tip xsi:type="xsd:double">0</Tip></Details><LineItems SOAP-ENC:arrayType="ns1:LineItem[0]" xsi:type="ns1:LineItemArray"/><Response xsi:type="ns1:TransactionResponse"><AcsUrl xsi:nil="true"/><AuthAmount xsi:type="xsd:double">50</AuthAmount><AuthCode xsi:type="xsd:string">050129</AuthCode><AvsResult xsi:type="xsd:string">Address: Match &amp; 5 Digit Zip: Match</AvsResult><AvsResultCode xsi:type="xsd:string">YYY</AvsResultCode><BatchNum xsi:type="xsd:integer">1</BatchNum><BatchRefNum xsi:type="xsd:integer">14004</BatchRefNum><CardCodeResult xsi:type="xsd:string">Match</CardCodeResult><CardCodeResultCode xsi:type="xsd:string">M</CardCodeResultCode><CardLevelResult xsi:nil="true"/><CardLevelResultCode xsi:nil="true"/><ConversionRate xsi:type="xsd:double">0</ConversionRate><ConvertedAmount xsi:type="xsd:double">0</ConvertedAmount><ConvertedAmountCurrency xsi:type="xsd:string"></ConvertedAmountCurrency><CustNum xsi:type="xsd:integer">0</CustNum><Error xsi:type="xsd:string">Approved</Error><ErrorCode xsi:type="xsd:integer">0</ErrorCode><isDuplicate xsi:type="xsd:boolean">false</isDuplicate><Payload xsi:nil="true"/><RefNum xsi:type="xsd:integer">47568950</RefNum><Result xsi:type="xsd:string">Approved</Result><ResultCode xsi:type="xsd:string">A</ResultCode><Status xsi:type="xsd:string">Pending</Status><StatusCode xsi:type="xsd:string">P</StatusCode><VpasResultCode xsi:nil="true"/></Response><ServerIP xsi:type="xsd:string">67.168.21.42</ServerIP><ShippingAddress xsi:type="ns1:Address"><City xsi:type="xsd:string">Ottawa</City><Company xsi:type="xsd:string">Widgets Inc</Company><Country xsi:type="xsd:string">CA</Country><Email xsi:type="xsd:string"></Email><Fax xsi:type="xsd:string"></Fax><FirstName xsi:type="xsd:string">Jim</FirstName><LastName xsi:type="xsd:string">Smith</LastName><Phone xsi:type="xsd:string">(555)555-5555</Phone><State xsi:type="xsd:string">ON</State><Street xsi:type="xsd:string">456 My Street</Street><Street2 xsi:type="xsd:string">Apt 1</Street2><Zip xsi:type="xsd:string">K1C2N6</Zip></ShippingAddress><Source xsi:type="xsd:string">test</Source><Status xsi:type="xsd:string">Authorized (Pending Settlement)</Status><TransactionType xsi:type="xsd:string">Sale</TransactionType><User xsi:type="xsd:string">auto</User></getTransactionReturn></ns1:getTransactionResponse></SOAP-ENV:Body></SOAP-ENV:Envelope> XML end diff --git a/test/unit/gateways/usa_epay_transaction_test.rb b/test/unit/gateways/usa_epay_transaction_test.rb index 681682e8ea2..b75959ee308 100644 --- a/test/unit/gateways/usa_epay_transaction_test.rb +++ b/test/unit/gateways/usa_epay_transaction_test.rb @@ -4,45 +4,368 @@ class UsaEpayTransactionTest < Test::Unit::TestCase include CommStub def setup - @gateway = UsaEpayTransactionGateway.new( - :login => 'LOGIN' - ) + @gateway = UsaEpayTransactionGateway.new(:login => 'LOGIN') @credit_card = credit_card('4242424242424242') + @check = check @options = { - :billing_address => address, + :billing_address => address, :shipping_address => address } @amount = 100 end + def test_urls + assert_equal 'https://www.usaepay.com/gate', UsaEpayTransactionGateway.live_url + assert_equal 'https://sandbox.usaepay.com/gate', UsaEpayTransactionGateway.test_url + end + + def test_request_url_live + gateway = UsaEpayTransactionGateway.new(:login => 'LOGIN', :test => false) + gateway.expects(:ssl_post). + with('https://www.usaepay.com/gate', regexp_matches(Regexp.new('^' + Regexp.escape(purchase_request)))). + returns(successful_purchase_response) + gateway.purchase(@amount, @credit_card, @options) + end + + def test_request_url_test + @gateway.expects(:ssl_post). + with('https://sandbox.usaepay.com/gate', regexp_matches(Regexp.new('^' + Regexp.escape(purchase_request)))). + returns(successful_purchase_response) + @gateway.purchase(@amount, @credit_card, @options) + end + def test_successful_request @gateway.expects(:ssl_post).returns(successful_purchase_response) - assert response = @gateway.purchase(@amount, @credit_card, @options) + response = @gateway.purchase(@amount, @credit_card, @options) assert_success response assert_equal '55074409', response.authorization assert response.test? end + def test_successful_request_with_echeck + @gateway.expects(:ssl_post).returns(successful_purchase_response_echeck) + + response = @gateway.purchase(@amount, @check, @options) + assert_success response + assert_equal '133134803', response.authorization + assert response.test? + end + + def test_successful_purchase_with_echeck_and_extra_options + response = stub_comms do + @gateway.purchase(@amount, check(account_type: 'savings'), @options.merge(check_format: 'ARC')) + end.check_request do |endpoint, data, headers| + assert_match(/UMcheckformat=ARC/, data) + assert_match(/UMaccounttype=Savings/, data) + end.respond_with(successful_purchase_response_echeck) + + assert_equal 'Success', response.message + assert_equal '133134803', response.authorization + assert_success response + assert response.test? + end + def test_unsuccessful_request @gateway.expects(:ssl_post).returns(unsuccessful_purchase_response) - assert response = @gateway.purchase(@amount, @credit_card, @options) + response = @gateway.purchase(@amount, @credit_card, @options) assert_failure response assert response.test? + assert Gateway::STANDARD_ERROR_CODE[:card_declined], response.error_code end def test_successful_purchase_passing_extra_info response = stub_comms do - @gateway.purchase(@amount, @credit_card, @options.merge(:order_id => "1337", :description => "socool")) + @gateway.purchase(@amount, @credit_card, @options.merge(:invoice => '1337', :description => 'socool')) end.check_request do |endpoint, data, headers| assert_match(/UMinvoice=1337/, data) assert_match(/UMdescription=socool/, data) + assert_match(/UMtestmode=0/, data) + end.respond_with(successful_purchase_response) + assert_success response + end + + def test_successful_purchase_passing_extra_test_mode + response = stub_comms do + @gateway.purchase(@amount, @credit_card, @options.merge(:test_mode => true)) + end.check_request do |endpoint, data, headers| + assert_match(/UMtestmode=1/, data) end.respond_with(successful_purchase_response) assert_success response end + def test_successful_purchase_email_receipt + response = stub_comms do + @gateway.purchase(@amount, @credit_card, @options.merge(:email => 'bobby@hill.com', :cust_receipt => 'Yes', :cust_receipt_name => 'socool')) + end.check_request do |endpoint, data, headers| + assert_match(/UMcustreceipt=Yes/, data) + assert_match(/UMcustreceiptname=socool/, data) + assert_match(/UMtestmode=0/, data) + end.respond_with(successful_purchase_response) + assert_success response + end + + def test_successful_purchase_split_payment + response = stub_comms do + @gateway.purchase(@amount, @credit_card, @options.merge( + :split_payments => [ + { :key => 'abc123', :amount => 199, :description => 'Second payee' }, + { :key => 'def456', :amount => 911, :description => 'Third payee' }, + ] + )) + end.check_request do |endpoint, data, headers| + assert_match %r{UM02key=abc123}, data + assert_match %r{UM02amount=1.99}, data + assert_match %r{UM02description=Second\+payee}, data + + assert_match %r{UM03key=def456}, data + assert_match %r{UM03amount=9.11}, data + assert_match %r{UM03description=Third\+payee}, data + + assert_match %r{UMonError=Void}, data + end.respond_with(successful_purchase_response) + assert_success response + end + + def test_successful_purchase_split_payment_with_custom_on_error + response = stub_comms do + @gateway.purchase(@amount, @credit_card, @options.merge( + :split_payments => [ + { :key => 'abc123', :amount => 199, :description => 'Second payee' } + ], + :on_error => 'Continue' + )) + end.check_request do |endpoint, data, headers| + assert_match %r{UMonError=Continue}, data + end.respond_with(successful_purchase_response) + assert_success response + end + + def test_successful_purchase_recurring_fields + response = stub_comms do + @gateway.purchase(@amount, @credit_card, @options.merge( + :recurring_fields => { + add_customer: true, + schedule: 'quarterly', + bill_source_key: 'bill source key', + bill_amount: 123, + num_left: 5, + start: '20501212', + recurring_receipt: true + } + )) + end.check_request do |endpoint, data, headers| + assert_match %r{UMaddcustomer=yes}, data + assert_match %r{UMschedule=quarterly}, data + assert_match %r{UMbillsourcekey=bill\+source\+key}, data + assert_match %r{UMbillamount=1.23}, data + assert_match %r{UMnumleft=5}, data + assert_match %r{UMstart=20501212}, data + assert_match %r{UMrecurringreceipt=yes}, data + end.respond_with(successful_purchase_response) + assert_success response + end + + def test_successful_purchase_custom_fields + response = stub_comms do + @gateway.purchase(@amount, @credit_card, @options.merge( + :custom_fields => { + 1 => 'diablo', + 2 => 'mephisto', + 3 => 'baal' + } + )) + end.check_request do |endpoint, data, headers| + assert_match %r{UMcustom1=diablo}, data + assert_match %r{UMcustom2=mephisto}, data + assert_match %r{UMcustom3=baal}, data + end.respond_with(successful_purchase_response) + assert_success response + end + + def test_first_index_guard_on_custom_fields + assert_raise(ArgumentError) do + @gateway.purchase(@amount, @credit_card, @options.merge( + :custom_fields => { + 0 => 'butcher', + 1 => 'diablo', + 2 => 'mephisto', + 3 => 'baal' + } + )) + end + + assert_raise(ArgumentError) do + @gateway.purchase(@amount, @credit_card, @options.merge( + :custom_fields => { + '0' => 'butcher', + '1' => 'diablo', + '2' => 'mephisto', + '3' => 'baal' + } + )) + end + end + + def test_successful_purchase_line_items + response = stub_comms do + @gateway.purchase(@amount, @credit_card, @options.merge( + :line_items => [ + { :sku=> 'abc123', :cost => 119, :quantity => 1 }, + { :sku => 'def456', :cost => 200, :quantity => 2, :name => 'an item' }, + { :cost => 300, :qty => 4 } + ] + )) + end.check_request do |endpoint, data, headers| + assert_match %r{UMline0sku=abc123}, data + assert_match %r{UMline0cost=1.19}, data + assert_match %r{UMline0qty=1}, data + + assert_match %r{UMline1sku=def456}, data + assert_match %r{UMline1cost=2.00}, data + assert_match %r{UMline1qty=2}, data + assert_match %r{UMline1name=an\+item}, data + + assert_match %r{UMline2cost=3.00}, data + assert_match %r{UMline2qty=4}, data + end.respond_with(successful_purchase_response) + assert_success response + end + + def test_successful_authorize_request + @gateway.expects(:ssl_post).returns(successful_authorize_response) + + response = @gateway.authorize(@amount, @credit_card, @options) + assert_success response + assert_equal '65074409', response.authorization + assert response.test? + end + + def test_successful_authorize_passing_extra_info + response = stub_comms do + @gateway.authorize(@amount, @credit_card, @options.merge(:invoice => '1337', order_id: 'w00t', :description => 'socool')) + end.check_request do |endpoint, data, headers| + assert_match(/UMinvoice=1337/, data) + assert_match(/UMorderid=w00t/, data) + assert_match(/UMdescription=socool/, data) + assert_match(/UMtestmode=0/, data) + end.respond_with(successful_authorize_response) + assert_success response + end + + def test_successful_authorize_passing_extra_test_mode + response = stub_comms do + @gateway.authorize(@amount, @credit_card, @options.merge(:test_mode => true)) + end.check_request do |endpoint, data, headers| + assert_match(/UMtestmode=1/, data) + end.respond_with(successful_authorize_response) + assert_success response + end + + def test_successful_capture_request + @gateway.expects(:ssl_post).returns(successful_capture_response) + + response = @gateway.capture(@amount, '65074409', @options) + assert_success response + assert_equal '65074409', response.authorization + assert response.test? + end + + def test_successful_capture_passing_extra_info + response = stub_comms do + @gateway.capture(@amount, '65074409', @options) + end.check_request do |endpoint, data, headers| + assert_match(/UMamount=1.00/, data) + assert_match(/UMtestmode=0/, data) + end.respond_with(successful_capture_response) + assert_success response + end + + def test_successful_capture_passing_extra_test_mode + response = stub_comms do + @gateway.capture(@amount, '65074409', @options.merge(:test_mode => true)) + end.check_request do |endpoint, data, headers| + assert_match(/UMtestmode=1/, data) + end.respond_with(successful_capture_response) + assert_success response + end + + def test_successful_refund_request + @gateway.expects(:ssl_post).returns(successful_refund_response) + + response = @gateway.refund(@amount, '65074409', @options) + assert_success response + assert_equal '63813138', response.authorization + assert response.test? + end + + def test_successful_refund_request_with_echeck + @gateway.expects(:ssl_post).returns(successful_refund_response_echeck) + + response = @gateway.refund(@amount, '65074409', @options) + assert_success response + assert_equal '133134926', response.authorization + assert response.test? + end + + def test_successful_refund_passing_extra_info + response = stub_comms do + @gateway.refund(@amount, '65074409', @options) + end.check_request do |endpoint, data, headers| + assert_match(/UMamount=1.00/, data) + assert_match(/UMtestmode=0/, data) + end.respond_with(successful_refund_response) + assert_success response + end + + def test_successful_refund_passing_extra_test_mode + response = stub_comms do + @gateway.refund(@amount, '65074409', @options.merge(:test_mode => true)) + end.check_request do |endpoint, data, headers| + assert_match(/UMtestmode=1/, data) + end.respond_with(successful_refund_response) + assert_success response + end + + def test_successful_void_request + @gateway.expects(:ssl_post).returns(successful_void_response) + + response = @gateway.void('65074409', @options) + assert_success response + assert_equal '63812270', response.authorization + assert response.test? + end + + def test_successful_void_request_with_echeck + @gateway.expects(:ssl_post).returns(successful_void_response_echeck) + + response = @gateway.void('65074409', @options) + assert_success response + assert_equal '133134971', response.authorization + assert response.test? + end + + def test_successful_void_passing_extra_info + response = stub_comms do + @gateway.void('65074409', @options.merge(:no_release => true)) + end.check_request do |endpoint, data, headers| + assert_match(/UMcommand=cc%3Avoid/, data) + assert_match(/UMtestmode=0/, data) + end.respond_with(successful_void_response) + assert_success response + end + + def test_successful_void_passing_extra_test_mode + response = stub_comms do + @gateway.refund(@amount, '65074409', @options.merge(:test_mode => true)) + end.check_request do |endpoint, data, headers| + assert_match(/UMtestmode=1/, data) + end.respond_with(successful_void_response) + assert_success response + end + def test_address_key_prefix assert_equal 'bill', @gateway.send(:address_key_prefix, :billing) assert_equal 'ship', @gateway.send(:address_key_prefix, :shipping) @@ -75,12 +398,54 @@ def test_add_billing_and_shipping_addresses assert_equal 20, post.keys.size end + def test_add_address_with_empty_billing_and_shipping_names + post = {} + @options[:billing_address].delete(:name) + @options[:shipping_address][:name] = '' + + @gateway.send(:add_address, post, @credit_card, @options) + assert_address(:shipping, post, 'Longbob', 'Longsen') + assert_address(:billing, post, 'Longbob', 'Longsen') + assert_equal 20, post.keys.size + end + + def test_add_address_with_single_billing_and_shipping_names + post = {} + options = { + :billing_address => address(:name => 'Smith'), + :shipping_address => address(:name => 'Longsen') + } + + @gateway.send(:add_address, post, @credit_card, options) + assert_address(:billing, post, '', 'Smith') + assert_address(:shipping, post, '', 'Longsen') + assert_equal 20, post.keys.size + end + + def test_add_test_mode_without_test_mode_option + post = {} + @gateway.send(:add_test_mode, post, {}) + assert_nil post[:testmode] + end + + def test_add_test_mode_with_true_test_mode_option + post = {} + @gateway.send(:add_test_mode, post, :test_mode => true) + assert_equal 1, post[:testmode] + end + + def test_add_test_mode_with_false_test_mode_option + post = {} + @gateway.send(:add_test_mode, post, :test_mode => false) + assert_equal 0, post[:testmode] + end + def test_amount_style - assert_equal '10.34', @gateway.send(:amount, 1034) + assert_equal '10.34', @gateway.send(:amount, 1034) - assert_raise(ArgumentError) do - @gateway.send(:amount, '10.34') - end + assert_raise(ArgumentError) do + @gateway.send(:amount, '10.34') + end end def test_supported_countries @@ -107,28 +472,76 @@ def test_cvv_result assert_equal 'M', response.cvv_result['code'] end + def test_add_track_data_with_creditcard + @credit_card.track_data = 'data' + + @gateway.expects(:ssl_post).with do |_, body| + body.include?('UMmagstripe=data') + end.returns(successful_purchase_response) + + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + end + + def test_add_track_data_with_empty_data + ['', nil].each do |data| + @credit_card.track_data = data + + @gateway.expects(:ssl_post).with do |_, body| + refute body.include? 'UMmagstripe=' + body + end.returns(successful_purchase_response) + + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + end + end + + def test_manual_entry_is_properly_indicated_on_purchase + @credit_card.manual_entry = true + response = stub_comms do + @gateway.purchase(@amount, @credit_card, @options) + end.check_request do |endpoint, data, headers| + assert_match %r{UMcard=4242424242424242}, data + assert_match %r{UMcardpresent=true}, data + end.respond_with(successful_purchase_response) + + assert_success response + end + def test_does_not_raise_error_on_missing_values - @gateway.expects(:ssl_post).returns("status") + @gateway.expects(:ssl_post).returns('status') assert_nothing_raised do response = @gateway.purchase(@amount, @credit_card, @options) assert_failure response end end + def test_scrub + assert @gateway.supports_scrubbing? + assert_equal @gateway.scrub(pre_scrubbed), post_scrubbed + assert_equal @gateway.scrub(pre_scrubbed_track_data), post_scrubbed_track_data + assert_equal @gateway.scrub(pre_scrubbed_echeck), post_scrubbed_echeck + end + private - def assert_address(type, post) + def assert_address(type, post, expected_first_name = nil, expected_last_name = nil) prefix = key_prefix(type) - assert_equal @credit_card.first_name, post[key(prefix, 'fname')] - assert_equal @credit_card.last_name, post[key(prefix, 'lname')] - assert_equal @options[:billing_address][:company], post[key(prefix, 'company')] + first_name, last_name = split_names(@options[:billing_address][:name]) + first_name = expected_first_name if expected_first_name + last_name = expected_last_name if expected_last_name + + assert_equal first_name, post[key(prefix, 'fname')] + assert_equal last_name, post[key(prefix, 'lname')] + assert_equal @options[:billing_address][:company], post[key(prefix, 'company')] assert_equal @options[:billing_address][:address1], post[key(prefix, 'street')] assert_equal @options[:billing_address][:address2], post[key(prefix, 'street2')] - assert_equal @options[:billing_address][:city], post[key(prefix, 'city')] - assert_equal @options[:billing_address][:state], post[key(prefix, 'state')] - assert_equal @options[:billing_address][:zip], post[key(prefix, 'zip')] - assert_equal @options[:billing_address][:country], post[key(prefix, 'country')] - assert_equal @options[:billing_address][:phone], post[key(prefix, 'phone')] + assert_equal @options[:billing_address][:city], post[key(prefix, 'city')] + assert_equal @options[:billing_address][:state], post[key(prefix, 'state')] + assert_equal @options[:billing_address][:zip], post[key(prefix, 'zip')] + assert_equal @options[:billing_address][:country], post[key(prefix, 'country')] + assert_equal @options[:billing_address][:phone], post[key(prefix, 'phone')] end def key_prefix(type) @@ -139,11 +552,194 @@ def key(prefix, key) @gateway.send(:address_key, prefix, key) end + def split_names(full_name) + names = (full_name || '').split + last_name = names.pop + first_name = names.join(' ') + [first_name, last_name] + end + + def purchase_request + "UMamount=1.00&UMinvoice=&UMorderid=&UMdescription=&UMcard=4242424242424242&UMcvv2=123&UMexpir=09#{@credit_card.year.to_s[-2..-1]}&UMname=Longbob+Longsen&UMbillfname=Jim&UMbilllname=Smith&UMbillcompany=Widgets+Inc&UMbillstreet=456+My+Street&UMbillstreet2=Apt+1&UMbillcity=Ottawa&UMbillstate=ON&UMbillzip=K1C2N6&UMbillcountry=CA&UMbillphone=%28555%29555-5555&UMshipfname=Jim&UMshiplname=Smith&UMshipcompany=Widgets+Inc&UMshipstreet=456+My+Street&UMshipstreet2=Apt+1&UMshipcity=Ottawa&UMshipstate=ON&UMshipzip=K1C2N6&UMshipcountry=CA&UMshipphone=%28555%29555-5555&UMstreet=456+My+Street&UMzip=K1C2N6&UMcommand=cc%3Asale&UMkey=LOGIN&UMsoftware=Active+Merchant&UMtestmode=0" + end + def successful_purchase_response - "UMversion=2.9&UMstatus=Approved&UMauthCode=001716&UMrefNum=55074409&UMavsResult=Address%3A%20Match%20%26%205%20Digit%20Zip%3A%20Match&UMavsResultCode=Y&UMcvv2Result=Match&UMcvv2ResultCode=M&UMresult=A&UMvpasResultCode=&UMerror=Approved&UMerrorcode=00000&UMcustnum=&UMbatch=596&UMisDuplicate=N&UMconvertedAmount=&UMconvertedAmountCurrency=840&UMconversionRate=&UMcustReceiptResult=No%20Receipt%20Sent&UMfiller=filled" + 'UMversion=2.9&UMstatus=Approved&UMauthCode=001716&UMrefNum=55074409&UMavsResult=Address%3A%20Match%20%26%205%20Digit%20Zip%3A%20Match&UMavsResultCode=Y&UMcvv2Result=Match&UMcvv2ResultCode=M&UMresult=A&UMvpasResultCode=&UMerror=Approved&UMerrorcode=00000&UMcustnum=&UMbatch=596&UMisDuplicate=N&UMconvertedAmount=&UMconvertedAmountCurrency=840&UMconversionRate=&UMcustReceiptResult=No%20Receipt%20Sent&UMfiller=filled' end def unsuccessful_purchase_response - "UMversion=2.9&UMstatus=Declined&UMauthCode=000000&UMrefNum=55076060&UMavsResult=Address%3A%20Match%20%26%205%20Digit%20Zip%3A%20Match&UMavsResultCode=Y&UMcvv2Result=Not%20Processed&UMcvv2ResultCode=P&UMvpasResultCode=&UMresult=D&UMerror=Card%20Declined&UMerrorcode=10127&UMbatch=596&UMfiller=filled" + 'UMversion=2.9&UMstatus=Declined&UMauthCode=000000&UMrefNum=55076060&UMavsResult=Address%3A%20Match%20%26%205%20Digit%20Zip%3A%20Match&UMavsResultCode=Y&UMcvv2Result=Not%20Processed&UMcvv2ResultCode=P&UMvpasResultCode=&UMresult=D&UMerror=Card%20Declined&UMerrorcode=10127&UMbatch=596&UMfiller=filled' + end + + def successful_authorize_response + 'UMversion=2.9&UMstatus=Approved&UMauthCode=101716&UMrefNum=65074409&UMavsResult=Address%3A%20Match%20%26%205%20Digit%20Zip%3A%20Match&UMavsResultCode=Y&UMcvv2Result=Match&UMcvv2ResultCode=M&UMresult=A&UMvpasResultCode=&UMerror=Approved&UMerrorcode=00000&UMcustnum=&UMbatch=596&UMisDuplicate=N&UMconvertedAmount=&UMconvertedAmountCurrency=840&UMconversionRate=&UMcustReceiptResult=No%20Receipt%20Sent&UMfiller=filled' + end + + def successful_capture_response + 'UMversion=2.9&UMstatus=Approved&UMauthCode=101716&UMrefNum=65074409&UMavsResult=No%20AVS%20response%20%28Typically%20no%20AVS%20data%20sent%20or%20swiped%20transaction%29&UMavsResultCode=&UMcvv2Result=No%20CVV2%2FCVC%20data%20available%20for%20transaction.&UMcvv2ResultCode=&UMresult=A&UMvpasResultCode=&UMerror=Approved&UMerrorcode=00000&UMcustnum=&UMbatch=&UMbatchRefNum=&UMisDuplicate=N&UMconvertedAmount=&UMconvertedAmountCurrency=840&UMconversionRate=&UMcustReceiptResult=No%20Receipt%20Sent&UMprocRefNum=&UMcardLevelResult=&UMauthAmount=&UMfiller=filled' + end + + def successful_refund_response + 'UMversion=2.9&UMstatus=Approved&UMauthCode=101716&UMrefNum=63813138&UMavsResult=Unmapped%20AVS%20response%20%28%20%20%20%29&UMavsResultCode=%20%20%20&UMcvv2Result=No%20CVV2%2FCVC%20data%20available%20for%20transaction.&UMcvv2ResultCode=&UMresult=A&UMvpasResultCode=&UMerror=&UMerrorcode=00000&UMcustnum=&UMbatch=&UMbatchRefNum=&UMisDuplicate=N&UMconvertedAmount=1.00&UMconvertedAmountCurrency=840&UMconversionRate=&UMcustReceiptResult=No%20Receipt%20Sent&UMprocRefNum=&UMcardLevelResult=&UMauthAmount=&UMfiller=filled' + end + + def successful_void_response + 'UMversion=2.9&UMstatus=Approved&UMauthCode=&UMrefNum=63812270&UMavsResult=No%20AVS%20response%20%28Typically%20no%20AVS%20data%20sent%20or%20swiped%20transaction%29&UMavsResultCode=&UMcvv2Result=No%20CVV2%2FCVC%20data%20available%20for%20transaction.&UMcvv2ResultCode=&UMresult=A&UMvpasResultCode=&UMerror=Transaction%20Voided%20Successfully&UMerrorcode=00000&UMcustnum=&UMbatch=&UMbatchRefNum=&UMisDuplicate=N&UMconvertedAmount=&UMconvertedAmountCurrency=840&UMconversionRate=&UMcustReceiptResult=No%20Receipt%20Sent&UMprocRefNum=&UMcardLevelResult=&UMauthAmount=&UMfiller=filled' + end + + def successful_purchase_response_echeck + 'UMversion=2.9&UMstatus=Approved&UMauthCode=TMEC4D&UMrefNum=133134803&UMavsResult=No%20AVS%20response%20%28Typically%20no%20AVS%20data%20sent%20or%20swiped%20transaction%29&UMavsResultCode=&UMcvv2Result=No%20CVV2%2FCVC%20data%20available%20for%20transaction.&UMcvv2ResultCode=&UMresult=A&UMvpasResultCode=&UMerror=&UMerrorcode=00000&UMcustnum=&UMbatch=180316&UMbatchRefNum=&UMisDuplicate=N&UMconvertedAmount=&UMconvertedAmountCurrency=840&UMconversionRate=&UMcustReceiptResult=No%20Receipt%20Sent&UMprocRefNum=18031621233065&UMcardLevelResult=&UMauthAmount=&UMfiller=filled' + end + + def successful_refund_response_echeck + 'UMversion=2.9&UMstatus=Approved&UMauthCode=TM1E74&UMrefNum=133134926&UMavsResult=No%20AVS%20response%20%28Typically%20no%20AVS%20data%20sent%20or%20swiped%20transaction%29&UMavsResultCode=&UMcvv2Result=No%20CVV2%2FCVC%20data%20available%20for%20transaction.&UMcvv2ResultCode=&UMresult=A&UMvpasResultCode=&UMerror=&UMerrorcode=00000&UMcustnum=&UMbatch=&UMbatchRefNum=&UMisDuplicate=N&UMconvertedAmount=&UMconvertedAmountCurrency=840&UMconversionRate=&UMcustReceiptResult=No%20Receipt%20Sent&UMprocRefNum=&UMcardLevelResult=&UMauthAmount=&UMfiller=filled' + end + + def successful_void_response_echeck + 'UMversion=2.9&UMstatus=Approved&UMauthCode=TM80A5&UMrefNum=133134971&UMavsResult=No%20AVS%20response%20%28Typically%20no%20AVS%20data%20sent%20or%20swiped%20transaction%29&UMavsResultCode=&UMcvv2Result=No%20CVV2%2FCVC%20data%20available%20for%20transaction.&UMcvv2ResultCode=&UMresult=A&UMvpasResultCode=&UMerror=&UMerrorcode=00000&UMcustnum=&UMbatch=&UMbatchRefNum=&UMisDuplicate=N&UMconvertedAmount=&UMconvertedAmountCurrency=840&UMconversionRate=&UMcustReceiptResult=No%20Receipt%20Sent&UMprocRefNum=&UMcardLevelResult=&UMauthAmount=&UMfiller=filled' + end + + def pre_scrubbed + <<-EOS +opening connection to sandbox.usaepay.com:443... +opened +starting SSL for sandbox.usaepay.com:443... +SSL established +<- "POST /gate HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: sandbox.usaepay.com\r\nContent-Length: 774\r\n\r\n" +<- "UMamount=1.00&UMinvoice=&UMdescription=&UMcard=4000100011112224&UMcvv2=123&UMexpir=0919&UMname=Longbob+Longsen&UMbillfname=Jim&UMbilllname=Smith&UMbillcompany=Widgets+Inc&UMbillstreet=456+My+Street&UMbillstreet2=Apt+1&UMbillcity=Ottawa&UMbillstate=NC&UMbillzip=27614&UMbillcountry=CA&UMbillphone=%28555%29555-5555&UMshipfname=Jim&UMshiplname=Smith&UMshipcompany=Widgets+Inc&UMshipstreet=456+My+Street&UMshipstreet2=Apt+1&UMshipcity=Ottawa&UMshipstate=ON&UMshipzip=K1C2N6&UMshipcountry=CA&UMshipphone=%28555%29555-5555&UMstreet=456+My+Street&UMzip=27614&UMcommand=cc%3Asale&UMkey=4EoZ5U2Q55j976W7eplC71i6b7kn4pcV&UMsoftware=Active+Merchant&UMtestmode=0&UMhash=s%2F5268F91058BC9F9FA944693D799F324B2497B7247850A51E53226309FB2540F0%2F7b4c4f6a4e775141cc0e4e10c0388d9adeb47fd1%2Fn" +-> "HTTP/1.1 200 OK\r\n" +-> "Server: http\r\n" +-> "Date: Tue, 13 Feb 2018 18:17:20 GMT\r\n" +-> "Content-Type: text/html\r\n" +-> "Content-Length: 485\r\n" +-> "Connection: close\r\n" +-> "P3P: policyref=\"http://www.usaepay.com/w3c/p3p.xml\", CP=\"NON TAIa IVAa IVDa OUR NOR PHY ONL UNI FIN INT DEM\"\r\n" +-> "Strict-Transport-Security: max-age=15768000\r\n" +-> "\r\n" +reading 485 bytes... +-> "UMversion=2.9&UMstatus=Approved&UMauthCode=042366&UMrefNum=132020588&UMavsResult=Address%3A%20Match%20%26%205%20Digit%20Zip%3A%20Match&UMavsResultCode=YYY&UMcvv2Result=Match&UMcvv2ResultCode=M&UMresult=A&UMvpasResultCode=&UMerror=Approved&UMerrorcode=00000&UMcustnum=&UMbatch=120&UMbatchRefNum=848&UMisDuplicate=N&UMconvertedAmount=&UMconvertedAmountCurrency=840&UMconversionRate=&UMcustReceiptResult=No%20Receipt%20Sent&UMprocRefNum=&UMcardLevelResult=A&UMauthAmount=1&UMfiller=filled" +read 485 bytes +Conn close + EOS + end + + def post_scrubbed + <<-EOS +opening connection to sandbox.usaepay.com:443... +opened +starting SSL for sandbox.usaepay.com:443... +SSL established +<- "POST /gate HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: sandbox.usaepay.com\r\nContent-Length: 774\r\n\r\n" +<- "UMamount=1.00&UMinvoice=&UMdescription=&UMcard=[FILTERED]&UMcvv2=[FILTERED]&UMexpir=0919&UMname=Longbob+Longsen&UMbillfname=Jim&UMbilllname=Smith&UMbillcompany=Widgets+Inc&UMbillstreet=456+My+Street&UMbillstreet2=Apt+1&UMbillcity=Ottawa&UMbillstate=NC&UMbillzip=27614&UMbillcountry=CA&UMbillphone=%28555%29555-5555&UMshipfname=Jim&UMshiplname=Smith&UMshipcompany=Widgets+Inc&UMshipstreet=456+My+Street&UMshipstreet2=Apt+1&UMshipcity=Ottawa&UMshipstate=ON&UMshipzip=K1C2N6&UMshipcountry=CA&UMshipphone=%28555%29555-5555&UMstreet=456+My+Street&UMzip=27614&UMcommand=cc%3Asale&UMkey=[FILTERED]&UMsoftware=Active+Merchant&UMtestmode=0&UMhash=s%2F5268F91058BC9F9FA944693D799F324B2497B7247850A51E53226309FB2540F0%2F7b4c4f6a4e775141cc0e4e10c0388d9adeb47fd1%2Fn" +-> "HTTP/1.1 200 OK\r\n" +-> "Server: http\r\n" +-> "Date: Tue, 13 Feb 2018 18:17:20 GMT\r\n" +-> "Content-Type: text/html\r\n" +-> "Content-Length: 485\r\n" +-> "Connection: close\r\n" +-> "P3P: policyref=\"http://www.usaepay.com/w3c/p3p.xml\", CP=\"NON TAIa IVAa IVDa OUR NOR PHY ONL UNI FIN INT DEM\"\r\n" +-> "Strict-Transport-Security: max-age=15768000\r\n" +-> "\r\n" +reading 485 bytes... +-> "UMversion=2.9&UMstatus=Approved&UMauthCode=042366&UMrefNum=132020588&UMavsResult=Address%3A%20Match%20%26%205%20Digit%20Zip%3A%20Match&UMavsResultCode=YYY&UMcvv2Result=Match&UMcvv2ResultCode=M&UMresult=A&UMvpasResultCode=&UMerror=Approved&UMerrorcode=00000&UMcustnum=&UMbatch=120&UMbatchRefNum=848&UMisDuplicate=N&UMconvertedAmount=&UMconvertedAmountCurrency=840&UMconversionRate=&UMcustReceiptResult=No%20Receipt%20Sent&UMprocRefNum=&UMcardLevelResult=A&UMauthAmount=1&UMfiller=filled" +read 485 bytes +Conn close + EOS + end + + def pre_scrubbed_track_data + <<-EOS +opening connection to sandbox.usaepay.com:443... +opened +starting SSL for sandbox.usaepay.com:443... +SSL established +<- "POST /gate HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: sandbox.usaepay.com\r\nContent-Length: 382\r\n\r\n" +<- "UMamount=1.00&UMinvoice=&UMdescription=&UMmagstripe=%25B4000100011112224%5ELONGSEN%2FL.+%5E19091200000000000000%2A%2A123%2A%2A%2A%2A%2A%2A%3F&UMcardpresent=true&UMcommand=cc%3Asale&UMkey=4EoZ5U2Q55j976W7eplC71i6b7kn4pcV&UMsoftware=Active+Merchant&UMtestmode=0&UMhash=s%2FE27734F076643B23131E5432C1E225EFF982A73D350179EFC2F191CA499B59A4%2F13391bd14ab6e61058cc9a1b78f259a4c26aa8e1%2Fn" +-> "HTTP/1.1 200 OK\r\n" +-> "Server: http\r\n" +-> "Date: Tue, 13 Feb 2018 18:13:11 GMT\r\n" +-> "Content-Type: text/html\r\n" +-> "Content-Length: 485\r\n" +-> "Connection: close\r\n" +-> "P3P: policyref=\"http://www.usaepay.com/w3c/p3p.xml\", CP=\"NON TAIa IVAa IVDa OUR NOR PHY ONL UNI FIN INT DEM\"\r\n" +-> "Strict-Transport-Security: max-age=15768000\r\n" +-> "\r\n" +reading 485 bytes... +-> "UMversion=2.9&UMstatus=Approved&UMauthCode=042087&UMrefNum=132020522&UMavsResult=Address%3A%20Match%20%26%205%20Digit%20Zip%3A%20Match&UMavsResultCode=YYY&UMcvv2Result=Match&UMcvv2ResultCode=M&UMresult=A&UMvpasResultCode=&UMerror=Approved&UMerrorcode=00000&UMcustnum=&UMbatch=120&UMbatchRefNum=848&UMisDuplicate=N&UMconvertedAmount=&UMconvertedAmountCurrency=840&UMconversionRate=&UMcustReceiptResult=No%20Receipt%20Sent&UMprocRefNum=&UMcardLevelResult=A&UMauthAmount=1&UMfiller=filled" +read 485 bytes +Conn close + EOS + end + + def post_scrubbed_track_data + <<-EOS +opening connection to sandbox.usaepay.com:443... +opened +starting SSL for sandbox.usaepay.com:443... +SSL established +<- "POST /gate HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: sandbox.usaepay.com\r\nContent-Length: 382\r\n\r\n" +<- "UMamount=1.00&UMinvoice=&UMdescription=&UMmagstripe=[FILTERED]&UMcardpresent=true&UMcommand=cc%3Asale&UMkey=[FILTERED]&UMsoftware=Active+Merchant&UMtestmode=0&UMhash=s%2FE27734F076643B23131E5432C1E225EFF982A73D350179EFC2F191CA499B59A4%2F13391bd14ab6e61058cc9a1b78f259a4c26aa8e1%2Fn" +-> "HTTP/1.1 200 OK\r\n" +-> "Server: http\r\n" +-> "Date: Tue, 13 Feb 2018 18:13:11 GMT\r\n" +-> "Content-Type: text/html\r\n" +-> "Content-Length: 485\r\n" +-> "Connection: close\r\n" +-> "P3P: policyref=\"http://www.usaepay.com/w3c/p3p.xml\", CP=\"NON TAIa IVAa IVDa OUR NOR PHY ONL UNI FIN INT DEM\"\r\n" +-> "Strict-Transport-Security: max-age=15768000\r\n" +-> "\r\n" +reading 485 bytes... +-> "UMversion=2.9&UMstatus=Approved&UMauthCode=042087&UMrefNum=132020522&UMavsResult=Address%3A%20Match%20%26%205%20Digit%20Zip%3A%20Match&UMavsResultCode=YYY&UMcvv2Result=Match&UMcvv2ResultCode=M&UMresult=A&UMvpasResultCode=&UMerror=Approved&UMerrorcode=00000&UMcustnum=&UMbatch=120&UMbatchRefNum=848&UMisDuplicate=N&UMconvertedAmount=&UMconvertedAmountCurrency=840&UMconversionRate=&UMcustReceiptResult=No%20Receipt%20Sent&UMprocRefNum=&UMcardLevelResult=A&UMauthAmount=1&UMfiller=filled" +read 485 bytes +Conn close + EOS + end + + def pre_scrubbed_echeck + <<-EOS +opening connection to sandbox.usaepay.com:443... +opened +starting SSL for sandbox.usaepay.com:443... +SSL established +<- "POST /gate HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: sandbox.usaepay.com\r\nContent-Length: 762\r\n\r\n" +<- "UMamount=1.00&UMinvoice=&UMdescription=&UMaccount=15378535&UMrouting=244183602&UMname=Jim+Smith&UMbillfname=Jim&UMbilllname=Smith&UMbillcompany=Widgets+Inc&UMbillstreet=456+My+Street&UMbillstreet2=Apt+1&UMbillcity=Ottawa&UMbillstate=NC&UMbillzip=27614&UMbillcountry=CA&UMbillphone=%28555%29555-5555&UMshipfname=Jim&UMshiplname=Smith&UMshipcompany=Widgets+Inc&UMshipstreet=456+My+Street&UMshipstreet2=Apt+1&UMshipcity=Ottawa&UMshipstate=ON&UMshipzip=K1C2N6&UMshipcountry=CA&UMshipphone=%28555%29555-5555&UMstreet=456+My+Street&UMzip=27614&UMcommand=check%3Asale&UMkey=4EoZ5U2Q55j976W7eplC71i6b7kn4pcV&UMsoftware=Active+Merchant&UMtestmode=0&UMhash=s%2F7F71E7DCB851901EA1D4E2CA1C60D2A7E8BAB99FA10F6220E821BD8B8331114B%2F85f1a7ab01b725c4eed80a12c78ef65d3fa367e6%2Fn" +-> "HTTP/1.1 200 OK\r\n" +-> "Server: http\r\n" +-> "Date: Fri, 16 Mar 2018 20:54:49 GMT\r\n" +-> "Content-Type: text/html\r\n" +-> "Content-Length: 572\r\n" +-> "Connection: close\r\n" +-> "P3P: policyref=\"http://www.usaepay.com/w3c/p3p.xml\", CP=\"NON TAIa IVAa IVDa OUR NOR PHY ONL UNI FIN INT DEM\"\r\n" +-> "Strict-Transport-Security: max-age=15768000\r\n" +-> "\r\n" +reading 572 bytes... +-> "UMversion=2.9&UMstatus=Approved&UMauthCode=TMEAAF&UMrefNum=133135121&UMavsResult=No%20AVS%20response%20%28Typically%20no%20AVS%20data%20sent%20or%20swiped%20transaction%29&UMavsResultCode=&UMcvv2Result=No%20CVV2%2FCVC%20data%20available%20for%20transaction.&UMcvv2ResultCode=&UMresult=A&UMvpasResultCode=&UMerror=&UMerrorcode=00000&UMcustnum=&UMbatch=180316&UMbatchRefNum=&UMisDuplicate=N&UMconvertedAmount=&UMconvertedAmountCurrency=840&UMconversionRate=&UMcustReceiptResult=No%20Receipt%20Sent&UMprocRefNum=18031621233689&UMcardLevelResult=&UMauthAmount=&UMfiller=filled" +read 572 bytes +Conn close + EOS + end + + def post_scrubbed_echeck + <<-EOS +opening connection to sandbox.usaepay.com:443... +opened +starting SSL for sandbox.usaepay.com:443... +SSL established +<- "POST /gate HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: sandbox.usaepay.com\r\nContent-Length: 762\r\n\r\n" +<- "UMamount=1.00&UMinvoice=&UMdescription=&UMaccount=[FILTERED]&UMrouting=244183602&UMname=Jim+Smith&UMbillfname=Jim&UMbilllname=Smith&UMbillcompany=Widgets+Inc&UMbillstreet=456+My+Street&UMbillstreet2=Apt+1&UMbillcity=Ottawa&UMbillstate=NC&UMbillzip=27614&UMbillcountry=CA&UMbillphone=%28555%29555-5555&UMshipfname=Jim&UMshiplname=Smith&UMshipcompany=Widgets+Inc&UMshipstreet=456+My+Street&UMshipstreet2=Apt+1&UMshipcity=Ottawa&UMshipstate=ON&UMshipzip=K1C2N6&UMshipcountry=CA&UMshipphone=%28555%29555-5555&UMstreet=456+My+Street&UMzip=27614&UMcommand=check%3Asale&UMkey=[FILTERED]&UMsoftware=Active+Merchant&UMtestmode=0&UMhash=s%2F7F71E7DCB851901EA1D4E2CA1C60D2A7E8BAB99FA10F6220E821BD8B8331114B%2F85f1a7ab01b725c4eed80a12c78ef65d3fa367e6%2Fn" +-> "HTTP/1.1 200 OK\r\n" +-> "Server: http\r\n" +-> "Date: Fri, 16 Mar 2018 20:54:49 GMT\r\n" +-> "Content-Type: text/html\r\n" +-> "Content-Length: 572\r\n" +-> "Connection: close\r\n" +-> "P3P: policyref=\"http://www.usaepay.com/w3c/p3p.xml\", CP=\"NON TAIa IVAa IVDa OUR NOR PHY ONL UNI FIN INT DEM\"\r\n" +-> "Strict-Transport-Security: max-age=15768000\r\n" +-> "\r\n" +reading 572 bytes... +-> "UMversion=2.9&UMstatus=Approved&UMauthCode=TMEAAF&UMrefNum=133135121&UMavsResult=No%20AVS%20response%20%28Typically%20no%20AVS%20data%20sent%20or%20swiped%20transaction%29&UMavsResultCode=&UMcvv2Result=No%20CVV2%2FCVC%20data%20available%20for%20transaction.&UMcvv2ResultCode=&UMresult=A&UMvpasResultCode=&UMerror=&UMerrorcode=00000&UMcustnum=&UMbatch=180316&UMbatchRefNum=&UMisDuplicate=N&UMconvertedAmount=&UMconvertedAmountCurrency=840&UMconversionRate=&UMcustReceiptResult=No%20Receipt%20Sent&UMprocRefNum=18031621233689&UMcardLevelResult=&UMauthAmount=&UMfiller=filled" +read 572 bytes +Conn close + EOS end end diff --git a/test/unit/gateways/vanco_test.rb b/test/unit/gateways/vanco_test.rb new file mode 100644 index 00000000000..449cd696448 --- /dev/null +++ b/test/unit/gateways/vanco_test.rb @@ -0,0 +1,188 @@ +require 'test_helper' + +class VancoTest < Test::Unit::TestCase + include CommStub + + def setup + @gateway = VancoGateway.new(user_id: 'login', password: 'password', client_id: 'client_id') + @credit_card = credit_card + @check = check + @amount = 100 + @options = { + order_id: '1', + billing_address: address, + description: 'Store Purchase' + } + end + + def test_successful_purchase + response = stub_comms do + @gateway.purchase(@amount, @credit_card, @options) + end.respond_with(successful_login_response, successful_purchase_response) + + assert_success response + assert_equal '14949117|15756594|16136938', response.authorization + assert_equal 'Success', response.message + assert response.test? + end + + def test_successful_purchase_with_fund_id + response = stub_comms do + @gateway.purchase(@amount, @credit_card, @options.merge(fund_id: 'MyEggcellentFund')) + end.check_request do |endpoint, data, headers| + if data =~ /<RequestType>EFTAdd/ + assert_match(%r(<FundID>MyEggcellentFund<\/FundID>), data) + end + end.respond_with(successful_login_response, successful_purchase_with_fund_id_response) + + assert_success response + assert_equal '14949117|15756594|16137331', response.authorization + assert_equal 'Success', response.message + end + + def test_successful_purchase_with_ip_address + response = stub_comms do + @gateway.purchase(@amount, @credit_card, @options.merge(ip: '192.168.0.1')) + end.check_request do |endpoint, data, headers| + if data =~ /<RequestType>EFTAdd/ + assert_match(%r(<CustomerIPAddress>192), data) + end + end.respond_with(successful_login_response, successful_purchase_response) + assert_success response + end + + def test_failed_purchase + response = stub_comms do + @gateway.purchase(@amount, @credit_card, @options) + end.respond_with(successful_login_response, failed_purchase_response) + + assert_failure response + assert_equal '286', response.params['error_codes'] + end + + def test_failed_purchase_multiple_errors + response = stub_comms do + @gateway.purchase(@amount, @credit_card, @options) + end.respond_with(successful_login_response, failed_purchase_multiple_errors_response) + + assert_failure response + assert_equal 'Client not set up for International Credit Card Processing. Another Error.', response.message + assert_equal '286, 331', response.params['error_codes'] + end + + def test_successful_purchase_echeck + response = stub_comms do + @gateway.purchase(@amount, @check, @options) + end.respond_with(successful_login_response, successful_purchase_echeck_response) + + assert_success response + assert_equal '14949514|15757035|16138421', response.authorization + assert_equal 'Success', response.message + assert response.test? + end + + def test_failed_purchase_echeck + response = stub_comms do + @gateway.purchase(@amount, @check, @options) + end.respond_with(successful_login_response, failed_purchase_echeck_response) + + assert_failure response + assert_equal '178', response.params['error_codes'] + end + + def test_successful_refund + response = stub_comms do + @gateway.refund(@amount, 'authoriziation') + end.respond_with(successful_login_response, successful_refund_response) + + assert_success response + assert_equal 'Success', response.message + end + + def test_failed_refund + response = stub_comms do + @gateway.refund(@amount, 'authorization') + end.respond_with(successful_login_response, failed_refund_response) + + assert_failure response + assert_equal '575', response.params['error_codes'] + end + + def test_scrub + assert @gateway.supports_scrubbing? + assert_equal @gateway.scrub(pre_scrubbed), post_scrubbed + end + + private + + def pre_scrubbed + <<-PRE_SCRUBBED + <- "<?xml version=\"1.0\"?>\n<VancoWS>\n <Auth>\n <RequestType>Login</RequestType>\n <RequestID>5464bab3283f1da7d123d3a5030f99</RequestID>\n <RequestTime>2015-05-01 14:04:52 -0400</RequestTime>\n <Version>2</Version>\n </Auth>\n <Request>\n <RequestVars>\n <UserID>SPREEDWS</UserID>\n <Password>v@nco2oo</Password>\n </RequestVars>\n </Request>\n</VancoWS>\n" + <- "POST /cgi-bin/wstest2.vps HTTP/1.1\r\nContent-Type: text/xml\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: www.vancodev.com\r\nContent-Length: 1194\r\n\r\n" + <- "<?xml version=\"1.0\"?>\n<VancoWS>\n <Auth>\n <RequestType>EFTAddCompleteTransaction</RequestType>\n <RequestID>70b35fa8fa2ac3efb0c672fb3936a4</RequestID>\n <RequestTime>2015-05-01 14:04:52 -0400</RequestTime>\n <SessionID>c6bd084a24bd1898c5465a468282055f3807526c</SessionID>\n <Version>2</Version>\n </Auth>\n <Request>\n <RequestVars>\n <ClientID>SPREEDLY</ClientID>\n <AccountType>CC</AccountType>\n <Amount>100.05</Amount>\n <AccountNumber>4111111111111111</AccountNumber>\n <TransactionTypeCode>WEB</TransactionTypeCode>\n <CustomerName>Longsen, Longbob</CustomerName>\n <CardExpMonth>09</CardExpMonth>\n <CardExpYear>16</CardExpYear>\n <CardCVV2>123</CardCVV2>\n <CardBillingName>Longbob Longsen</CardBillingName>\n <CardBillingAddr1>456 My Street</CardBillingAddr1>\n <CardBillingAddr2>Apt 1</CardBillingAddr2>\n <CardBillingCity>Ottawa</CardBillingCity>\n <CardBillingState>NC</CardBillingState>\n <CardBillingZip>06085</CardBillingZip>\n <CardBillingCountryCode>US</CardBillingCountryCode>\n <StartDate>0000-00-00</StartDate>\n <FrequencyCode>O</FrequencyCode>\n </RequestVars>\n </Request>\n</VancoWS>\n" + PRE_SCRUBBED + end + + def post_scrubbed + <<-POST_SCRUBBED + <- "<?xml version=\"1.0\"?>\n<VancoWS>\n <Auth>\n <RequestType>Login</RequestType>\n <RequestID>5464bab3283f1da7d123d3a5030f99</RequestID>\n <RequestTime>2015-05-01 14:04:52 -0400</RequestTime>\n <Version>2</Version>\n </Auth>\n <Request>\n <RequestVars>\n <UserID>SPREEDWS</UserID>\n <Password>[FILTERED]</Password>\n </RequestVars>\n </Request>\n</VancoWS>\n" + <- "POST /cgi-bin/wstest2.vps HTTP/1.1\r\nContent-Type: text/xml\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: www.vancodev.com\r\nContent-Length: 1194\r\n\r\n" + <- "<?xml version=\"1.0\"?>\n<VancoWS>\n <Auth>\n <RequestType>EFTAddCompleteTransaction</RequestType>\n <RequestID>70b35fa8fa2ac3efb0c672fb3936a4</RequestID>\n <RequestTime>2015-05-01 14:04:52 -0400</RequestTime>\n <SessionID>c6bd084a24bd1898c5465a468282055f3807526c</SessionID>\n <Version>2</Version>\n </Auth>\n <Request>\n <RequestVars>\n <ClientID>SPREEDLY</ClientID>\n <AccountType>CC</AccountType>\n <Amount>100.05</Amount>\n <AccountNumber>[FILTERED]</AccountNumber>\n <TransactionTypeCode>WEB</TransactionTypeCode>\n <CustomerName>Longsen, Longbob</CustomerName>\n <CardExpMonth>09</CardExpMonth>\n <CardExpYear>16</CardExpYear>\n <CardCVV2>[FILTERED]</CardCVV2>\n <CardBillingName>Longbob Longsen</CardBillingName>\n <CardBillingAddr1>456 My Street</CardBillingAddr1>\n <CardBillingAddr2>Apt 1</CardBillingAddr2>\n <CardBillingCity>Ottawa</CardBillingCity>\n <CardBillingState>NC</CardBillingState>\n <CardBillingZip>06085</CardBillingZip>\n <CardBillingCountryCode>US</CardBillingCountryCode>\n <StartDate>0000-00-00</StartDate>\n <FrequencyCode>O</FrequencyCode>\n </RequestVars>\n </Request>\n</VancoWS>\n" + POST_SCRUBBED + end + + def successful_login_response + %( + <?xml version="1.0" encoding="UTF-8" ?><VancoWS><Auth><RequestID>fa031c3a937c1749bcbccc920d88e0</RequestID><RequestTime>2015-05-01 16:08:07 -0400</RequestTime><RequestType>Login</RequestType><Version>2</Version></Auth><Response><SessionID>5d8b104c9d8265db46bdf35ae9685472f4789dc8</SessionID></Response></VancoWS> + ) + end + + def successful_purchase_response + %( + <?xml version="1.0" encoding="UTF-8" ?><VancoWS><Auth><RequestID>ad4cbab9740e909423a02e622689d6</RequestID><RequestTime>2015-05-01 16:08:07 -0400</RequestTime><RequestType>EFTAddCompleteTransaction</RequestType><Signature></Signature><SessionID>5d8b104c9d8265db46bdf35ae9685472f4789dc8</SessionID><Version>2</Version></Auth><Response><StartDate>2015-05-01</StartDate><CustomerRef>14949117</CustomerRef><PaymentMethodRef>15756594</PaymentMethodRef><TransactionRef>16136938</TransactionRef><TransactionFee>3.20</TransactionFee></Response></VancoWS> + ) + end + + def successful_purchase_with_fund_id_response + %( + <?xml version=\"1.0\" encoding=\"UTF-8\" ?><VancoWS><Auth><RequestID>8cf42301416298c9d6d71a39b27a0d</RequestID><RequestTime>2015-05-05 15:45:57 -0400</RequestTime><RequestType>EFTAddCompleteTransaction</RequestType><Signature></Signature><SessionID>b2ec96e366f38a5c1ecd3f5343475526beaba4f9</SessionID><Version>2</Version></Auth><Response><StartDate>2015-05-05</StartDate><CustomerRef>14949117</CustomerRef><PaymentMethodRef>15756594</PaymentMethodRef><TransactionRef>16137331</TransactionRef><TransactionFee>3.20</TransactionFee></Response></VancoWS>\n\n + ) + end + + def successful_purchase_echeck_response + %( + <?xml version=\"1.0\" encoding=\"UTF-8\" ?><VancoWS><Auth><RequestID>186f2495a292b62a32435fcb8cd869</RequestID><RequestTime>2015-05-14 15:52:09 -0400</RequestTime><RequestType>EFTAddCompleteTransaction</RequestType><Signature></Signature><SessionID>38b674bb2301c570a6034c10488cc59ba5b2f9f7</SessionID><Version>2</Version></Auth><Response><StartDate>2015-05-18</StartDate><CustomerRef>14949514</CustomerRef><PaymentMethodRef>15757035</PaymentMethodRef><TransactionRef>16138421</TransactionRef><TransactionFee></TransactionFee></Response></VancoWS>\n\n + ) + end + + def failed_purchase_echeck_response + %( + <?xml version="1.0" encoding="UTF-8" ?><VancoWS><Auth><RequestID>e010cc92dfe411212d7b19c8100ff2</RequestID><RequestTime>2015-05-14 15:56:10 -0400</RequestTime><RequestType>EFTAddCompleteTransaction</RequestType><Signature></Signature><SessionID>160f8eee46ed4683a08cc3009dd19beada6ed225</SessionID><Version>2</Version></Auth><Response><Errors><Error><ErrorCode>178</ErrorCode><ErrorDescription>Invalid Routing Number</ErrorDescription></Error></Errors></Response></VancoWS> + ) + end + + def failed_purchase_response + %( + <?xml version="1.0" encoding="UTF-8" ?><VancoWS><Auth><RequestID>8fde1fc5f27a09eeafffe8761c546c</RequestID><RequestTime>2015-05-01 16:13:34 -0400</RequestTime><RequestType>EFTAddCompleteTransaction</RequestType><Signature></Signature><SessionID>ae3a84a0963b83eeb44db13027e37f172e24d939</SessionID><Version>2</Version></Auth><Response><Errors><Error><ErrorCode>286</ErrorCode><ErrorDescription>Client not set up for International Credit Card Processing</ErrorDescription></Error></Errors></Response></VancoWS> + ) + end + + def failed_purchase_multiple_errors_response + %( + <?xml version="1.0" encoding="UTF-8" ?> <VancoWS> <Auth> <RequestID> 8fde1fc5f27a09eeafffe8761c546c</RequestID> <RequestTime> 2015-05-01 16:13:34 -0400</RequestTime> <RequestType> EFTAddCompleteTransaction</RequestType> <Signature> </Signature> <SessionID> ae3a84a0963b83eeb44db13027e37f172e24d939</SessionID> <Version> 2</Version> </Auth> <Response> <Errors> <Error> <ErrorCode>286</ErrorCode> <ErrorDescription>Client not set up for International Credit Card Processing</ErrorDescription> </Error> <Error> <ErrorCode>331</ErrorCode> <ErrorDescription>Another Error</ErrorDescription> </Error> </Errors> </Response> </VancoWS> + ) + end + + def successful_refund_response + %( + <?xml version="1.0" encoding="UTF-8" ?><VancoWS><Auth><RequestID>bca6411369a25f9fe9329f7f6c3f1d</RequestID><RequestTime>2015-05-01 16:18:33 -0400</RequestTime><RequestType>EFTAddCredit</RequestType><Signature></Signature><SessionID>32bed62d469e6ee4e92a5d2c56a77d1dea149a6e</SessionID><Version>2</Version></Auth><Response><CreditRequestReceived>Yes</CreditRequestReceived></Response></VancoWS> + ) + end + + def failed_refund_response + %( + <?xml version="1.0" encoding="UTF-8" ?><VancoWS><Auth><RequestID>dc9a5e2b620eee5d248e1b33cc1f33</RequestID><RequestTime>2015-05-01 16:19:33 -0400</RequestTime><RequestType>EFTAddCredit</RequestType><Signature></Signature><SessionID>67a731057f821413155033bc23551aef3ba0b204</SessionID><Version>2</Version></Auth><Response><Errors><Error><ErrorCode>575</ErrorCode><ErrorDescription>Amount Cannot Be Greater Than $100.05</ErrorDescription></Error></Errors></Response></VancoWS> + ) + end + +end diff --git a/test/unit/gateways/verifi_test.rb b/test/unit/gateways/verifi_test.rb index 21260db90c3..404556b2d6c 100644 --- a/test/unit/gateways/verifi_test.rb +++ b/test/unit/gateways/verifi_test.rb @@ -8,15 +8,15 @@ def setup :login => 'l', :password => 'p' ) - + @credit_card = credit_card('4111111111111111') - + @options = { :order_id => '37', - :email => "paul@example.com", - :billing_address => address + :email => 'paul@example.com', + :billing_address => address } - + @amount = 100 end @@ -36,81 +36,79 @@ def test_unsuccessful_request assert_failure response assert response.test? end - + def test_credit - @gateway.expects(:ssl_post).with(anything, regexp_matches(/ccnumber=#{@credit_card.number}/), anything).returns("") + @gateway.expects(:ssl_post).with(anything, regexp_matches(/ccnumber=#{@credit_card.number}/), anything).returns('') @gateway.expects(:parse).returns({}) @gateway.credit(@amount, @credit_card, @options) end - + def test_deprecated_credit - @gateway.expects(:ssl_post).with(anything, regexp_matches(/transactionid=transaction_id/), anything).returns("") + @gateway.expects(:ssl_post).with(anything, regexp_matches(/transactionid=transaction_id/), anything).returns('') @gateway.expects(:parse).returns({}) - assert_deprecation_warning(Gateway::CREDIT_DEPRECATION_MESSAGE, @gateway) do - @gateway.credit(@amount, "transaction_id", @options) + assert_deprecation_warning(Gateway::CREDIT_DEPRECATION_MESSAGE) do + @gateway.credit(@amount, 'transaction_id', @options) end end - + def test_refund - @gateway.expects(:ssl_post).with(anything, regexp_matches(/transactionid=transaction_id/), anything).returns("") + @gateway.expects(:ssl_post).with(anything, regexp_matches(/transactionid=transaction_id/), anything).returns('') @gateway.expects(:parse).returns({}) - @gateway.refund(@amount, "transaction_id", @options) + @gateway.refund(@amount, 'transaction_id', @options) end - + def test_amount_style assert_equal '10.34', @gateway.send(:amount, 1034) - + assert_raise(ArgumentError) do - @gateway.send(:amount, '10.34') + @gateway.send(:amount, '10.34') end end - + def test_add_description result = {} @gateway.send(:add_invoice_data, result, :description => 'My Purchase is great') assert_equal 'My Purchase is great', result[:orderdescription] - end def test_purchase_meets_minimum_requirements post = VerifiGateway::VerifiPostData.new - post[:amount] = "1.01" - + post[:amount] = '1.01' + @gateway.send(:add_credit_card, post, @credit_card) - + assert data = @gateway.send(:post_data, :authorization, post) - - minimum_requirements.each do |key| + + minimum_requirements.each do |key| assert_not_nil(data =~ /#{key}=/) end - end - + def test_avs_result @gateway.expects(:ssl_post).returns(successful_purchase_response) - + response = @gateway.purchase(@amount, @credit_card, @options) assert_equal 'N', response.avs_result['code'] end - + def test_cvv_result @gateway.expects(:ssl_post).returns(successful_purchase_response) - + response = @gateway.purchase(@amount, @credit_card, @options) assert_equal 'N', response.cvv_result['code'] end private - + def minimum_requirements %w(type username password ccnumber ccexp amount) end - + def successful_purchase_response - "response=1&responsetext=SUCCESS&authcode=123456&transactionid=546061538&avsresponse=N&cvvresponse=N&orderid=37&type=sale&response_code=100" + 'response=1&responsetext=SUCCESS&authcode=123456&transactionid=546061538&avsresponse=N&cvvresponse=N&orderid=37&type=sale&response_code=100' end def unsuccessful_purchase_response - "response=3&responsetext=Field required: ccnumber REFID:12109909&authcode=&transactionid=0&avsresponse=&cvvresponse=&orderid=37&type=sale&response_code=300" + 'response=3&responsetext=Field required: ccnumber REFID:12109909&authcode=&transactionid=0&avsresponse=&cvvresponse=&orderid=37&type=sale&response_code=300' end end diff --git a/test/unit/gateways/viaklix_test.rb b/test/unit/gateways/viaklix_test.rb index 8625f01ad5d..ef288b6b652 100644 --- a/test/unit/gateways/viaklix_test.rb +++ b/test/unit/gateways/viaklix_test.rb @@ -7,20 +7,20 @@ def setup :login => 'LOGIN', :password => 'PIN' ) - - @credit_card = credit_card + + @credit_card = credit_card @options = { :order_id => '37', - :email => "paul@domain.com", + :email => 'paul@domain.com', :description => 'Test Transaction', :billing_address => address } @amount = 100 end - - def test_purchase_success + + def test_purchase_success @gateway.expects(:ssl_post).returns(successful_purchase_response) - + assert response = @gateway.purchase(@amount, @credit_card, @options) assert_instance_of Response, response assert_success response @@ -29,50 +29,50 @@ def test_purchase_success def test_purchase_error @gateway.expects(:ssl_post).returns(unsuccessful_purchase_response) - + assert response = @gateway.purchase(@amount, @credit_card, @options) assert_instance_of Response, response assert_failure response end - + def test_invalid_login @gateway.expects(:ssl_post).returns(invalid_login_response) - + assert response = @gateway.purchase(@amount, @credit_card, @options) - + assert_equal '7000', response.params['result'] assert_equal 'The viaKLIX ID and/or User ID supplied in the authorization request is invalid.', response.params['result_message'] assert_failure response end - + def test_avs_result @gateway.expects(:ssl_post).returns(successful_purchase_response) - + response = @gateway.purchase(@amount, @credit_card) assert_equal 'Y', response.avs_result['code'] end - + def test_cvv_result @gateway.expects(:ssl_post).returns(successful_purchase_response) - + response = @gateway.purchase(@amount, @credit_card) assert_equal 'M', response.cvv_result['code'] end - + private - + def successful_purchase_response "ssl_result=0\r\nssl_company=;\r\nssl_city=Herndon\r\nssl_avs_zip=90201\r\nssl_address2=\r\nssl_ship_to_last_name=Jacobs\r\nssl_ship_to_city=Herndon\r\nssl_approval_code=05737D\r\nssl_avs_response=Y\r\nssl_salestax=\r\nssl_ship_to_phone=\r\ncustomer_code=jacobsr1@cox.net\r\nship_to_country=US\r\ncountry=US\r\nssl_txn_id=7E2419F7-2354-4766-BF5C-19C75A1F379A\r\nssl_transaction_type=SALE\r\nssl_invoice_number=#1158.1\r\nssl_amount=243.95\r\nssl_card_number=43*******6820\r\nssl_description=\r\nssl_phone=703-404-9270\r\nssl_ship_to_avs_address=\r\nssl_first_name=Cody\r\nssl_avs_address=12213 Jonathons Glen Way\r\nssl_result_message=APPROVED\r\nssl_exp_date=1109\r\nssl_last_name=Fauser\r\nssl_ship_to_first_name=Robert\r\nssl_ship_to_address2=\r\nssl_ship_to_state=VA\r\nssl_ship_to_avs_zip=\r\nssl_cvv2_response=M\r\nssl_state=VA\r\nssl_email=cody@example.com\r\nssl_ship_to_company=\r\n" end - + def unsuccessful_purchase_response "ssl_result=1\r\nssl_result_message=This transaction request has not been approved. You may elect to use another form of payment to complete this transaction or contact customer service for additional options." end - + def invalid_login_response <<-RESPONSE ssl_result=7000\r ssl_result_message=The viaKLIX ID and/or User ID supplied in the authorization request is invalid.\r RESPONSE end -end \ No newline at end of file +end diff --git a/test/unit/gateways/vindicia_test.rb b/test/unit/gateways/vindicia_test.rb deleted file mode 100644 index 7e49c95e524..00000000000 --- a/test/unit/gateways/vindicia_test.rb +++ /dev/null @@ -1,354 +0,0 @@ -require 'test_helper' -require 'vindicia-api' - -class VindiciaTest < Test::Unit::TestCase - def setup - unless Vindicia.config.is_configured? - schema = File.read(File.dirname(__FILE__) + '/../../schema/vindicia/Vindicia.xsd') - response = Net::HTTPResponse.new('1.1', '200', 'OK') - response.expects(:body).once.returns(schema) - Vindicia.expects(:get_vindicia_file).once.returns(response) - end - - @gateway = VindiciaGateway.new( - :login => 'login', - :password => 'password', - :account_id => 1 - ) - - @credit_card = credit_card - @amount = 100 - - @options = { - :order_id => '1', - :billing_address => address, - :line_items => { - :name => 'Test Product', - :sku => 'TEST_PRODUCT', - :price => 5, - :quantity => 1 - } - } - end - - def test_successful_purchase - @gateway.expects(:ssl_post).twice.returns(successful_authorize_response, successful_capture_response) - - assert response = @gateway.purchase(@amount, @credit_card, @options) - assert_instance_of Response, response - assert_success response - - assert response.authorization.include?(@options[:order_id]) - assert response.test? - end - - def test_unsuccessful_authorize_status - @gateway.expects(:ssl_post).once.returns(unsuccessful_authorize_response(:status => "Cancelled")) - - assert response = @gateway.purchase(@amount, @credit_card, @options) - assert_failure response - assert response.test? - end - - def test_unsuccessful_authorize_avs - @gateway.expects(:ssl_post).twice.returns(unsuccessful_authorize_response(:avs => "T"), successful_void_response) - - assert response = @gateway.purchase(@amount, @credit_card, @options) - assert_failure response - assert response.test? - end - - def test_unsuccessful_authorize_cvn - @gateway.expects(:ssl_post).twice.returns(unsuccessful_authorize_response(:cvn => "N"), successful_void_response) - - assert response = @gateway.purchase(@amount, @credit_card, @options) - assert_failure response - assert response.test? - end - - def test_unsuccessful_capture - @gateway.expects(:ssl_post).twice.returns(successful_authorize_response, unsuccessful_capture_response) - - assert response = @gateway.purchase(@amount, @credit_card, @options) - assert_failure response - assert response.test? - end - - def test_unsuccessful_request - @gateway.expects(:ssl_post).returns(failed_purchase_response) - - assert response = @gateway.purchase(@amount, @credit_card, @options) - assert_failure response - assert response.test? - end - - def test_successful_recurring_setup - @gateway.expects(:ssl_post).times(3).returns(successful_authorize_response, - successful_capture_response, - successful_update_response) - - assert response = @gateway.recurring(@amount, @credit_card, @options.merge(:product_sku => "TEST_SKU")) - assert_instance_of Response, response - assert_success response - - assert response.authorization.include?(@options[:order_id]) - assert response.test? - end - - def test_unsuccessful_recurring_setup - @gateway.expects(:ssl_post).times(4).returns(successful_authorize_response, - successful_capture_response, - unsuccessful_update_response, - successful_void_response) - - assert response = @gateway.recurring(@amount, @credit_card, @options.merge(:product_sku => "TEST_SKU")) - assert_failure response - assert response.test? - end - - private - - def successful_authorize_response - <<-END - <?xml version="1.0" encoding="UTF-8"?> - <soap:Envelope - xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" - xmlns:vin="http://soap.vindicia.com/v3_6/Vindicia" - xmlns:xsd="http://www.w3.org/2001/XMLSchema" - soap:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" - xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> - <soap:Body> - <authResponse xmlns="http://soap.vindicia.com/v3_6/Transaction"> - <return xmlns="" xsi:type="vin:Return"> - <returnCode xsi:type="vin:ReturnCode">200</returnCode> - <soapId xsi:type="xsd:string">0f3f650ca1882fc5f15d83c8dd3f69838662491d</soapId> - <returnString xsi:type="xsd:string">OK</returnString> - </return> - - <transaction xmlns="" xsi:type="vin:Transaction"> - <merchantTransactionId xmlns="" xsi:type="xsd:string">#{@options[:order_id]}</merchantTransactionId> - - <statusLog xmlns="" xsi:type="vin:TransactionStatus"> - <status xmlns="" xsi:type="vin:TransactionStatusType">Authorized</status> - <creditCardStatus xmlns="" xsi:type="vin:TransactionStatusCreditCard"> - <authCode xmlns="" xsi:type="xsd:string">100</authCode> - <avsCode xmlns="" xsi:type="xsd:string">X</avsCode> - <cvnCode xmlns="" xsi:type="xsd:string">M</cvnCode> - </creditCardStatus> - </statusLog> - - <statusLog xmlns="" xsi:type="vin:TransactionStatus"> - <status xmlns="" xsi:type="vin:TransactionStatusType">New</status> - </statusLog> - </transaction> - </authResponse> - </soap:Body> - </soap:Envelope> - END - end - - def unsuccessful_authorize_response(options = {}) - <<-END - <?xml version="1.0" encoding="UTF-8"?> - <soap:Envelope - xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" - xmlns:vin="http://soap.vindicia.com/v3_6/Vindicia" - xmlns:xsd="http://www.w3.org/2001/XMLSchema" - soap:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" - xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> - <soap:Body> - <authResponse xmlns="http://soap.vindicia.com/v3_6/Transaction"> - <return xmlns="" xsi:type="vin:Return"> - <returnCode xsi:type="vin:ReturnCode">200</returnCode> - <soapId xsi:type="xsd:string">0f3f650ca1882fc5f15d83c8dd3f69838662491d</soapId> - <returnString xsi:type="xsd:string">OK</returnString> - </return> - - <transaction xmlns="" xsi:type="vin:Transaction"> - <merchantTransactionId xmlns="" xsi:type="xsd:string">#{@options[:order_id]}</merchantTransactionId> - - <statusLog xmlns="" xsi:type="vin:TransactionStatus"> - <status xmlns="" xsi:type="vin:TransactionStatusType">#{options[:status] || "Authorized"}</status> - <creditCardStatus xmlns="" xsi:type="vin:TransactionStatusCreditCard"> - <authCode xmlns="" xsi:type="xsd:string">100</authCode> - <avsCode xmlns="" xsi:type="xsd:string">#{options[:avs] || "X"}</avsCode> - <cvnCode xmlns="" xsi:type="xsd:string">#{options[:cvn] || "M"}</cvnCode> - </creditCardStatus> - </statusLog> - - <statusLog xmlns="" xsi:type="vin:TransactionStatus"> - <status xmlns="" xsi:type="vin:TransactionStatusType">New</status> - </statusLog> - </transaction> - </authResponse> - </soap:Body> - </soap:Envelope> - END - end - - def unsuccessful_capture_response - <<-END - <?xml version="1.0" encoding="UTF-8"?> - <soap:Envelope - xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" - xmlns:vin="http://soap.vindicia.com/v3_6/Vindicia" - xmlns:xsd="http://www.w3.org/2001/XMLSchema" - soap:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" - xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> - <soap:Body> - <captureResponse xmlns="http://soap.vindicia.com/v3_6/Transaction"> - <return xmlns="" xsi:type="vin:Return"> - <returnCode xsi:type="vin:ReturnCode">200</returnCode> - - <soapId xsi:type="xsd:string">f500e87cc8941c90310f31ef1de9684a3171fed6</soapId> - - <returnString xsi:type="xsd:string">Ok</returnString> - </return> - - <qtySuccess xmlns="" xsi:type="xsd:int">0</qtySuccess> - - <qtyFail xmlns="" xsi:type="xsd:int">1</qtyFail> - - <results xmlns="" xsi:type="vin:CaptureResult"> - <returnCode xmlns="" xsi:type="xsd:int">400</returnCode> - <merchantTransactionId xmlns="" xsi:type="xsd:string">#{@options[:order_id]}</merchantTransactionId> - </results> - </captureResponse> - </soap:Body> - </soap:Envelope> - END - end - - def successful_capture_response - <<-END - <?xml version="1.0" encoding="UTF-8"?> - <soap:Envelope - xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" - xmlns:vin="http://soap.vindicia.com/v3_6/Vindicia" - xmlns:xsd="http://www.w3.org/2001/XMLSchema" - soap:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" - xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> - <soap:Body> - <captureResponse xmlns="http://soap.vindicia.com/v3_6/Transaction"> - <return xmlns="" xsi:type="vin:Return"> - <returnCode xsi:type="vin:ReturnCode">200</returnCode> - - <soapId xsi:type="xsd:string">f500e87cc8941c90310f31ef1de9684a3171fed6</soapId> - - <returnString xsi:type="xsd:string">Ok</returnString> - </return> - - <qtySuccess xmlns="" xsi:type="xsd:int">1</qtySuccess> - - <qtyFail xmlns="" xsi:type="xsd:int">0</qtyFail> - - <results xmlns="" xsi:type="vin:CaptureResult"> - <returnCode xmlns="" xsi:type="xsd:int">200</returnCode> - <merchantTransactionId xmlns="" xsi:type="xsd:string">#{@options[:order_id]}</merchantTransactionId> - </results> - </captureResponse> - </soap:Body> - </soap:Envelope> - END - end - # Not exactly the same, but very similar - alias :successful_void_response :successful_capture_response - - def failed_purchase_response - <<-END - <?xml version="1.0" encoding="UTF-8"?> - <soap:Envelope - xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" - xmlns:vin="http://soap.vindicia.com/v3_6/Vindicia" - xmlns:xsd="http://www.w3.org/2001/XMLSchema" - soap:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" - xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> - <soap:Body> - <authResponse xmlns="http://soap.vindicia.com/v3_6/Transaction"> - <return xmlns="" xsi:type="vin:Return"> - <returnCode xsi:type="vin:ReturnCode">400</returnCode> - <soapId xsi:type="xsd:string">e91e2caacf88e5c912eae8dbf354db8af053d20e</soapId> - <returnString xsi:type="xsd:string">OK</returnString> - </return> - - <transaction xmlns="" xsi:type="vin:Transaction"> - <merchantTransactionId xmlns="" xsi:type="xsd:string">R557887665</merchantTransactionId> - </transaction> - </authResponse> - </soap:Body> - </soap:Envelope> - END - end - - # AutoBill responses - def successful_update_response - <<-END - <?xml version="1.0" encoding="UTF-8"?> - <soap:Envelope - xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" - xmlns:vin="http://soap.vindicia.com/v3_6/Vindicia" - xmlns:xsd="http://www.w3.org/2001/XMLSchema" - soap:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" - xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> - <soap:Body> - <updateResponse xmlns="http://soap.vindicia.com/v3_6/AutoBill"> - <return xmlns="" xsi:type="vin:Return"> - <returnCode xsi:type="vin:ReturnCode">200</returnCode> - <soapId xsi:type="xsd:string">398ee6483a4b27dda94064cd67cf27464f6394eb</soapId> - <returnString xsi:type="xsd:string">OK</returnString> - </return> - - <autobill xmlns="" xsi:type="vin:AutoBill"> - <VID xmlns="" xsi:type="xsd:string">5857cab01a01a05939d1db9a8c63e3119254b405</VID> - <merchantAutoBillId xmlns="" xsi:type="xsd:string">A#{@options[:order_id]}</merchantAutoBillId> - <status xmlns="" xsi:type="vin:AutoBillStatus">Active</status> - <startTimestamp xmlns="" xsi:type="xsd:dateTime">2011-05-13T10:32:23-07:00</startTimestamp> - <endTimestamp xmlns="" xsi:type="xsd:dateTime">2011-05-15T10:32:23-07:00</endTimestamp> - </autobill> - - <created xmlns="" xsi:type="xsd:boolean">1</created> - </updateResponse> - </soap:Body> - </soap:Envelope> - END - end - - def unsuccessful_update_response - <<-END - <?xml version="1.0" encoding="UTF-8"?> - <soap:Envelope - xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" - xmlns:vin="http://soap.vindicia.com/v3_6/Vindicia" - xmlns:xsd="http://www.w3.org/2001/XMLSchema" - soap:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" - xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> - <soap:Body> - <updateResponse xmlns="http://soap.vindicia.com/v3_6/AutoBill"> - <return xmlns="" xsi:type="vin:Return"> - <returnCode xsi:type="vin:ReturnCode">200</returnCode> - <soapId xsi:type="xsd:string">398ee6483a4b27dda94064cd67cf27464f6394eb</soapId> - <returnString xsi:type="xsd:string">OK</returnString> - </return> - - <autobill xmlns="" xsi:type="vin:AutoBill"> - <VID xmlns="" xsi:type="xsd:string">5857cab01a01a05939d1db9a8c63e3119254b405</VID> - <merchantAutoBillId xmlns="" xsi:type="xsd:string">A#{@options[:order_id]}</merchantAutoBillId> - <status xmlns="" xsi:type="vin:AutoBillStatus">Cancelled</status> - <startTimestamp xmlns="" xsi:type="xsd:dateTime">2011-05-13T10:32:23-07:00</startTimestamp> - <endTimestamp xmlns="" xsi:type="xsd:dateTime">2011-05-15T10:32:23-07:00</endTimestamp> - </autobill> - - <created xmlns="" xsi:type="xsd:boolean">0</created> - </updateResponse> - </soap:Body> - </soap:Envelope> - END - end -end diff --git a/test/unit/gateways/visanet_peru_test.rb b/test/unit/gateways/visanet_peru_test.rb new file mode 100644 index 00000000000..c58840c78a0 --- /dev/null +++ b/test/unit/gateways/visanet_peru_test.rb @@ -0,0 +1,522 @@ +require 'test_helper' + +class VisanetPeruTest < Test::Unit::TestCase + def setup + @gateway = VisanetPeruGateway.new(fixtures(:visanet_peru)) + + @amount = 100 + @credit_card = credit_card('4500340090000016', verification_value: '377') + @declined_card = credit_card('4111111111111111') + + @options = { + billing_address: address, + order_id: generate_unique_id, + email: 'visanetperutest@mailinator.com' + } + end + + def test_successful_purchase + @gateway.expects(:ssl_request).with(:post, any_parameters).returns(successful_authorize_response) + @gateway.expects(:ssl_request).with(:put, any_parameters).returns(successful_capture_response) + + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal 'OK', response.message + + assert_match %r([0-9]{9}|$), response.authorization + assert_equal 'de9dc65c094fb4f1defddc562731af81', response.params['externalTransactionId'] + assert response.test? + end + + def test_failed_purchase + @gateway.expects(:ssl_request).with(:post, any_parameters).returns(failed_authorize_response_bad_card) + + response = @gateway.purchase(@amount, @declined_card, @options) + assert_failure response + assert_equal 400, response.error_code + assert_equal 'Operacion Denegada.', response.message + end + + def test_successful_authorize + @gateway.expects(:ssl_request).returns(successful_authorize_response) + response = @gateway.authorize(@amount, @credit_card, @options) + assert_success response + assert_equal 'OK', response.message + assert_match %r(^[0-9]{9}|$), response.authorization + assert_equal @options[:order_id], response.params['externalTransactionId'] + assert_equal '1.00', response.params['data']['IMP_AUTORIZADO'] + assert response.test? + end + + def test_failed_authorize + @gateway.expects(:ssl_request).returns(failed_authorize_response_bad_card) + response = @gateway.authorize(@amount, @declined_card, @options) + assert_failure response + assert_equal 400, response.error_code + assert_equal 'Operacion Denegada.', response.message + + @gateway.expects(:ssl_request).returns(failed_authorize_response_bad_email) + @options[:email] = 'cybersource@reject.com' + response = @gateway.authorize(@amount, @credit_card, @options) + assert_failure response + assert_equal 400, response.error_code + assert_equal 'REJECT | Operacion denegada', response.message + end + + def test_successful_capture + @gateway.expects(:ssl_request).with(:post, any_parameters).returns(successful_authorize_response) + @gateway.expects(:ssl_request).with(:put, any_parameters).returns(successful_capture_response) + response = @gateway.authorize(@amount, @credit_card, @options) + capture = @gateway.capture(response.authorization, @options) + assert_success capture + assert_equal 'OK', capture.message + assert_match %r(^[0-9]{9}|$), capture.authorization + assert_equal 'de9dc65c094fb4f1defddc562731af81', capture.params['externalTransactionId'] + assert capture.test? + end + + def test_failed_capture + @gateway.expects(:ssl_request).returns(failed_capture_response) + invalid_purchase_number = '900000044' + response = @gateway.capture(invalid_purchase_number) + assert_failure response + assert_equal '[ "NUMORDEN 900000044 no se encuentra registrado", "No se realizo el deposito" ]', response.message + assert_equal 400, response.error_code + end + + def test_successful_refund + @gateway.expects(:ssl_request).returns(successful_authorize_response) + @gateway.expects(:ssl_request).returns(successful_capture_response) + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + + @gateway.expects(:ssl_request).with(:put, any_parameters).returns(successful_refund_response) + refund = @gateway.refund(@amount, response.authorization) + assert_success refund + assert_equal 'OK', refund.message + end + + def test_failed_refund + @gateway.expects(:ssl_request).with(:put, any_parameters).returns(failed_refund_response) + response = @gateway.refund(@amount, '122333444') + assert_failure response + assert_match(/No se realizo la anulacion del deposito/, response.message) + assert_equal 400, response.error_code + end + + def test_failed_full_refund_when_unsettled + @gateway.expects(:ssl_request).with(:put, any_parameters).returns(failed_refund_response) + @gateway.expects(:ssl_request).with(:post, any_parameters).returns(failed_refund_with_action_code_response) + response = @gateway.refund(@amount, '122333444|444333221', force_full_refund_if_unsettled: true) + assert_failure response + assert_equal("Operacion Denegada. | [ 'NUMORDEN 122333444 no se encuentra registrado', 'No se realizo la anulacion del deposito' ]", response.message) + assert_equal 400, response.error_code + end + + def test_failed_full_refund_when_unsettled_additional_message_concatenation + @gateway.expects(:ssl_request).with(:put, any_parameters).returns(failed_refund_with_message_and_action_code_response) + @gateway.expects(:ssl_request).with(:post, any_parameters).returns(failed_refund_with_message_and_action_code_response_2) + first_msg = 'No se realizo la anulacion del deposito' + first_dsc = 'Operacion Denegada.' + second_msg = 'Mal funcionamiento de la inteligencia artificial' + second_dsc = 'Lo siento Dave, me temo que no puedo hacer eso.' + + response = @gateway.refund(@amount, '122333444|444333221', force_full_refund_if_unsettled: true) + assert_equal("#{second_msg} | #{second_dsc} | #{first_msg} | #{first_dsc}", response.message) + end + + def test_successful_void + @gateway.expects(:ssl_request).returns(successful_authorize_response) + response = @gateway.authorize(@amount, @credit_card, @options) + assert_success response + + @gateway.expects(:ssl_request).returns(successful_void_response) + void = @gateway.void(response.authorization) + assert_success void + assert_equal 'OK', void.message + end + + def test_failed_void + @gateway.expects(:ssl_request).returns(failed_void_response) + response = @gateway.void('122333444') + assert_failure response + assert_match(/No se ha realizado la anulacion del pedido/, response.message) + assert_equal 400, response.error_code + end + + def test_successful_verify + @gateway.expects(:ssl_request).returns(successful_authorize_response) + @gateway.expects(:ssl_request).returns(successful_verify_response) + response = @gateway.verify(@credit_card, @options) + assert_success response + assert_equal 'OK', response.message + assert_equal @options[:order_id], response.params['externalTransactionId'] + end + + def test_failed_verify + @gateway.expects(:ssl_request).returns(failed_verify_response) + response = @gateway.verify(@declined_card, @options) + assert_failure response + assert_equal 400, response.error_code + assert_equal 'Operacion Denegada.', response.message + end + + def test_scrub + assert @gateway.supports_scrubbing? + assert_equal @gateway.scrub(pre_scrubbed), post_scrubbed + end + + private + + def pre_scrubbed + %q( + opening connection to devapi.vnforapps.com:443... + opened + starting SSL for devapi.vnforapps.com:443... + SSL established + <- "POST /api.tokenization/api/v2/merchant/101266802 HTTP/1.1\r\nContent-Type: application/json\r\nAuthorization: Basic QUtJQUpQT1FaN0JBWEpaNUszNUE6VXIrVTBwbjFia2pSaFBHeitHK09MTmpxSWk3T0Jsd2taMmVUSHlTRw==\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: devapi.vnforapps.com\r\nContent-Length: 551\r\n\r\n" + <- "{\"amount\":1.0,\"purchaseNumber\":\"858169315\",\"externalTransactionId\":\"858169315\",\"currencyId\":604,\"firstName\":\"Longbob\",\"lastName\":\"Longsen\",\"cardNumber\":\"4500340090000016\",\"cvv2Code\":\"377\",\"expirationYear\":\"2017\",\"expirationMonth\":\"09\",\"email\":\"visanetperutest@mailinator.com\",\"antifraud\":{\"billTo_street1\":\"456 My Street\",\"billTo_city\":\"Ottawa\",\"billTo_state\":\"ON\",\"billTo_country\":\"CA\",\"billTo_postalCode\":\"K1C2N6\",\"deviceFingerprintId\":\"deadbeef\",\"merchantDefineData\":{\"field3\":\"movil\",\"field91\":\"101266802\",\"field92\":\"Cabify\"}},\"createAlias\":false}" + -> "HTTP/1.1 200 OK\r\n" + -> "Content-Type: application/json\r\n" + -> "Date: Mon, 21 Mar 2016 07:21:09 GMT\r\n" + -> "Server: WildFly/9\r\n" + -> "X-Powered-By: Undertow/1\r\n" + -> "Content-Length: 679\r\n" + -> "Connection: Close\r\n" + -> "\r\n" + reading 679 bytes... + -> "{\"errorCode\":0,\"errorMessage\":\"OK\",\"transactionUUID\":\"3db3c81a-835a-4db6-9e86-eb450c580b2c\",\"externalTransactionId\":\"858169315\",\"transactionDateTime\":1458544860693,\"transactionDuration\":0,\"merchantId\":\"101266802\",\"userTokenId\":null,\"aliasName\":null,\"data\":{\"FECHAYHORA_TX\":\"21/03/2016 02:23\",\"DSC_ECI\":\"Tarjeta no autenticada.\",\"DSC_COD_ACCION\":\"Operacion Autorizada\",\"NOM_EMISOR\":\"FINANCIERA CORDILLER\",\"RESPUESTA\":\"1\",\"ID_UNICO\":\"\",\"NUMORDEN\":\"858169315\",\"CODACCION\":\"000\",\"ETICKET\":\"3106040291071603210220450000\",\"IMP_AUTORIZADO\":\"1.00\",\"DECISIONCS\":\"1\",\"COD_AUTORIZA\":\"160351\",\"CODTIENDA\":\"101266802\",\"PAN\":\"450034******0016\",\"reviewTransaction\":\"false\",\"ORI_TARJETA\":\"N\"}}" + read 679 bytes + Conn close + opening connection to devapi.vnforapps.com:443... + opened + starting SSL for devapi.vnforapps.com:443... + SSL established + <- "PUT /api.tokenization/api/v2/merchant/101266802/deposit/858169315 HTTP/1.1\r\nAuthorization: Basic QUtJQUpQT1FaN0JBWEpaNUszNUE6VXIrVTBwbjFia2pSaFBHeitHK09MTmpxSWk3T0Jsd2taMmVUSHlTRw==\r\nContent-Type: application/json\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: devapi.vnforapps.com\r\nContent-Length: 37\r\n\r\n" + <- "{\"externalTransactionId\":\"858169315\"}" + -> "HTTP/1.1 200 OK\r\n" + -> "Content-Type: application/json\r\n" + -> "Date: Mon, 21 Mar 2016 07:21:36 GMT\r\n" + -> "Server: WildFly/9\r\n" + -> "X-Powered-By: Undertow/1\r\n" + -> "Content-Length: 550\r\n" + -> "Connection: Close\r\n" + -> "\r\n" + reading 550 bytes... + -> "{\"errorCode\":0,\"errorMessage\":\"OK\",\"transactionUUID\":\"6dee4901-813a-40ab-b571-60ee450be2ea\",\"externalTransactionId\":\"858169315\",\"transactionDateTime\":1458544871523,\"transactionDuration\":0,\"merchantId\":\"101266802\",\"userTokenId\":null,\"aliasName\":null,\"data\":{\"FECHAYHORA_TX\":null,\"DSC_ECI\":null,\"DSC_COD_ACCION\":null,\"NOM_EMISOR\":null,\"ESTADO\":\"Depositado\",\"RESPUESTA\":\"1\",\"ID_UNICO\":null,\"NUMORDEN\":null,\"CODACCION\":null,\"ETICKET\":null,\"IMP_AUTORIZADO\":null,\"DECISIONCS\":null,\"COD_AUTORIZA\":null,\"CODTIENDA\":\"101266802\",\"PAN\":null,\"ORI_TARJETA\":null}}" + read 550 bytes + Conn close + ) + end + + def post_scrubbed + %q( + opening connection to devapi.vnforapps.com:443... + opened + starting SSL for devapi.vnforapps.com:443... + SSL established + <- "POST /api.tokenization/api/v2/merchant/101266802 HTTP/1.1\r\nContent-Type: application/json\r\nAuthorization: Basic [FILTERED]==\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: devapi.vnforapps.com\r\nContent-Length: 551\r\n\r\n" + <- "{\"amount\":1.0,\"purchaseNumber\":\"858169315\",\"externalTransactionId\":\"858169315\",\"currencyId\":604,\"firstName\":\"Longbob\",\"lastName\":\"Longsen\",\"cardNumber\":\"[FILTERED]\",\"cvv2Code\":\"[FILTERED]\",\"expirationYear\":\"2017\",\"expirationMonth\":\"09\",\"email\":\"visanetperutest@mailinator.com\",\"antifraud\":{\"billTo_street1\":\"456 My Street\",\"billTo_city\":\"Ottawa\",\"billTo_state\":\"ON\",\"billTo_country\":\"CA\",\"billTo_postalCode\":\"K1C2N6\",\"deviceFingerprintId\":\"deadbeef\",\"merchantDefineData\":{\"field3\":\"movil\",\"field91\":\"101266802\",\"field92\":\"Cabify\"}},\"createAlias\":false}" + -> "HTTP/1.1 200 OK\r\n" + -> "Content-Type: application/json\r\n" + -> "Date: Mon, 21 Mar 2016 07:21:09 GMT\r\n" + -> "Server: WildFly/9\r\n" + -> "X-Powered-By: Undertow/1\r\n" + -> "Content-Length: 679\r\n" + -> "Connection: Close\r\n" + -> "\r\n" + reading 679 bytes... + -> "{\"errorCode\":0,\"errorMessage\":\"OK\",\"transactionUUID\":\"3db3c81a-835a-4db6-9e86-eb450c580b2c\",\"externalTransactionId\":\"858169315\",\"transactionDateTime\":1458544860693,\"transactionDuration\":0,\"merchantId\":\"101266802\",\"userTokenId\":null,\"aliasName\":null,\"data\":{\"FECHAYHORA_TX\":\"21/03/2016 02:23\",\"DSC_ECI\":\"Tarjeta no autenticada.\",\"DSC_COD_ACCION\":\"Operacion Autorizada\",\"NOM_EMISOR\":\"FINANCIERA CORDILLER\",\"RESPUESTA\":\"1\",\"ID_UNICO\":\"\",\"NUMORDEN\":\"858169315\",\"CODACCION\":\"000\",\"ETICKET\":\"3106040291071603210220450000\",\"IMP_AUTORIZADO\":\"1.00\",\"DECISIONCS\":\"1\",\"COD_AUTORIZA\":\"160351\",\"CODTIENDA\":\"101266802\",\"PAN\":\"450034******0016\",\"reviewTransaction\":\"false\",\"ORI_TARJETA\":\"N\"}}" + read 679 bytes + Conn close + opening connection to devapi.vnforapps.com:443... + opened + starting SSL for devapi.vnforapps.com:443... + SSL established + <- "PUT /api.tokenization/api/v2/merchant/101266802/deposit/858169315 HTTP/1.1\r\nAuthorization: Basic [FILTERED]==\r\nContent-Type: application/json\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: devapi.vnforapps.com\r\nContent-Length: 37\r\n\r\n" + <- "{\"externalTransactionId\":\"858169315\"}" + -> "HTTP/1.1 200 OK\r\n" + -> "Content-Type: application/json\r\n" + -> "Date: Mon, 21 Mar 2016 07:21:36 GMT\r\n" + -> "Server: WildFly/9\r\n" + -> "X-Powered-By: Undertow/1\r\n" + -> "Content-Length: 550\r\n" + -> "Connection: Close\r\n" + -> "\r\n" + reading 550 bytes... + -> "{\"errorCode\":0,\"errorMessage\":\"OK\",\"transactionUUID\":\"6dee4901-813a-40ab-b571-60ee450be2ea\",\"externalTransactionId\":\"858169315\",\"transactionDateTime\":1458544871523,\"transactionDuration\":0,\"merchantId\":\"101266802\",\"userTokenId\":null,\"aliasName\":null,\"data\":{\"FECHAYHORA_TX\":null,\"DSC_ECI\":null,\"DSC_COD_ACCION\":null,\"NOM_EMISOR\":null,\"ESTADO\":\"Depositado\",\"RESPUESTA\":\"1\",\"ID_UNICO\":null,\"NUMORDEN\":null,\"CODACCION\":null,\"ETICKET\":null,\"IMP_AUTORIZADO\":null,\"DECISIONCS\":null,\"COD_AUTORIZA\":null,\"CODTIENDA\":\"101266802\",\"PAN\":null,\"ORI_TARJETA\":null}}" + read 550 bytes + Conn close + ) + end + + def successful_authorize_response + <<-RESPONSE + { + "errorCode": 0, + "errorMessage": "OK", + "externalTransactionId": "#{@options[:order_id]}", + "merchantId": "101266802", + "data": { + "IMP_AUTORIZADO": "1.00" + } + } + RESPONSE + end + + def failed_authorize_response_bad_card + <<-RESPONSE + { + "errorCode": 400, + "errorMessage": "[ ]", + "data": { + "DSC_COD_ACCION": "Operacion Denegada." + } + } + RESPONSE + end + + def failed_authorize_response_bad_email + %q( + { + "errorCode": 400, + "errorMessage": "REJECT", + "millis": 513, + "transactionUUID": "e2f0f73d-2f44-4f05-9e13-dad3bf378c57", + "transactionDate": 1519932476254, + "data": { + "FECHAYHORA_TX": "01\/03\/2018 21:26", + "RES_CVV2": null, + "CSIMENSAJE": null, + "ID_UNICO": null, + "ETICKET": null, + "DECISIONCS": "REJECT", + "CSIPORCENTAJEDESCUENTO": null, + "NROCUOTA": null, + "CSIIMPORTECOMERCIO": null, + "CSICODIGOPROGRAMA": null, + "DSC_ECI": null, + "ECI": "00", + "DSC_COD_ACCION": "Operacion denegada", + "NOM_EMISOR": null, + "IMPCUOTAAPROX": null, + "CSITIPOCOBRO": null, + "NUMREFERENCIA": null, + "RESPUESTA": "2", + "NUMORDEN": "376876217", + "CODACCION": "670", + "IMP_AUTORIZADO": "0.00", + "COD_AUTORIZA": null, + "CODTIENDA": "vndp", + "PAN": null, + "reviewTransaction": "false", + "ORI_TARJETA": null + }, + "transactionLog": { + + } + } + ) + end + + def successful_capture_response + '{"errorCode":0,"errorMessage":"OK","transactionUUID":"8517cf68-4820-4224-959b-01c8117385e0","externalTransactionId":"de9dc65c094fb4f1defddc562731af81","transactionDateTime":1519937673906,"transactionDuration":0,"merchantId":"543025501","userTokenId":null,"aliasName":null,"data":{"FECHAYHORA_TX":null,"DSC_ECI":null,"DSC_COD_ACCION":null,"NOM_EMISOR":null,"ESTADO":"Depositado","RESPUESTA":"1","ID_UNICO":null,"NUMORDEN":null,"CODACCION":null,"ETICKET":null,"IMP_AUTORIZADO":null,"DECISIONCS":null,"COD_AUTORIZA":null,"CODTIENDA":"543025501","PAN":null,"ORI_TARJETA":null}}' + end + + def failed_capture_response + %q( + { + "errorCode":400,"errorMessage":"[ \"NUMORDEN 900000044 no se encuentra registrado\", \"No se realizo el deposito\" ]","millis":513,"transactionUUID":"e2f0f73d-2f44-4f05-9e13-dad3bf378c57","transactionDate":1519932476254,"data":{"FECHAYHORA_TX":null,"DSC_ECI":null,"DSC_COD_ACCION":null,"NOM_EMISOR":null,"ESTADO":"","RESPUESTA":"2","ID_UNICO":null,"NUMORDEN":null,"CODACCION":null,"ETICKET":null,"IMP_AUTORIZADO":null,"DECISIONCS":null,"COD_AUTORIZA":null,"CODTIENDA":"543025501","PAN":null,"ORI_TARJETA":null},"transactionLog":{} + } + ) + end + + def successful_verify_response + <<-RESPONSE + { + "errorCode": 0, + "errorMessage": "OK", + "externalTransactionId": "#{@options[:order_id]}", + "merchantId": "101266802", + "userTokenId": null, + "aliasName": null, + "data": { + "FECHAYHORA_TX": null, + "DSC_ECI": null, + "DSC_COD_ACCION": null, + "NOM_EMISOR": null, + "ESTADO": "Anulado", + "RESPUESTA": "1", + "ID_UNICO": null, + "NUMORDEN": null, + "CODACCION": null, + "ETICKET": null, + "IMP_AUTORIZADO": null, + "DECISIONCS": null, + "COD_AUTORIZA": null, + "CODTIENDA": "543025501", + "PAN": null, + "ORI_TARJETA": null + } + } + RESPONSE + end + + def failed_verify_response + <<-RESPONSE + { + "errorCode": 400, + "errorMessage": "[ ]", + "data": { + "DSC_COD_ACCION": "Operacion Denegada." + } + } + RESPONSE + end + + def successful_void_response + <<-RESPONSE + { + "errorCode": 0, + "errorMessage": "OK", + "externalTransactionId": "987654321", + "merchantId": "101266802", + "userTokenId": null, + "aliasName": null, + "data": { + "FECHAYHORA_TX": null, + "DSC_ECI": null, + "DSC_COD_ACCION": null, + "NOM_EMISOR": null, + "ESTADO": "Anulado", + "RESPUESTA": "1", + "ID_UNICO": null, + "NUMORDEN": null, + "CODACCION": null, + "ETICKET": null, + "IMP_AUTORIZADO": null, + "DECISIONCS": null, + "COD_AUTORIZA": null, + "CODTIENDA": "543025501", + "PAN": null, + "ORI_TARJETA": null + } + } + RESPONSE + end + + def failed_void_response + <<-RESPONSE + { + "errorCode": 400, + "errorMessage": "[ 'NUMORDEN no se encuentra registrado.', 'No se ha realizado la anulacion del pedido' ]", + "data": { + "FECHAYHORA_TX": null, + "DSC_ECI": null, + "DSC_COD_ACCION": null, + "NOM_EMISOR": null, + "ESTADO": " ", + "RESPUESTA": "2", + "ID_UNICO": null, + "NUMORDEN": null, + "CODACCION": null, + "ETICKET": null, + "IMP_AUTORIZADO": null, + "DECISIONCS": null, + "COD_AUTORIZA": null, + "CODTIENDA": "543025501", + "PAN": null, + "ORI_TARJETA": null + }, + "transactionLog": { + + } + } + RESPONSE + end + + def successful_refund_response + <<-RESPONSE + { + "errorCode":0, + "errorMessage":"OK", + "transactionUUID":"388e1e2b-46cc-4abc-818a-a7e4691bf67a", + "externalTransactionId":null, + "transactionDateTime":1463682009508, + "transactionDuration":0, + "merchantId":"101266802", + "userTokenId":null, + "aliasName":null, + "data":{"ESTADO":"Autorizado","RESPUESTA":"1"} + } + RESPONSE + end + + def failed_refund_response + <<-RESPONSE + { + "errorCode": 400, + "errorMessage": "[ 'NUMORDEN 122333444 no se encuentra registrado', 'No se realizo la anulacion del deposito' ]", + "data": { + "ESTADO": "", + "RESPUESTA": "2" + }, + "transactionLog": { + + } + } + RESPONSE + end + + def failed_refund_with_action_code_response + <<-RESPONSE + { + "errorCode": 400, + "errorMessage": "[ ]", + "data": { + "ESTADO": "", + "RESPUESTA": "2", + "DSC_COD_ACCION": "Operacion Denegada." + }, + "transactionLog": { + + } + } + RESPONSE + end + + def failed_refund_with_message_and_action_code_response + <<-RESPONSE + { + "errorCode": 400, + "errorMessage": "No se realizo la anulacion del deposito", + "data": { + "ESTADO": "", + "RESPUESTA": "2", + "DSC_COD_ACCION": "Operacion Denegada." + }, + "transactionLog": { + + } + } + RESPONSE + end + + def failed_refund_with_message_and_action_code_response_2 + <<-RESPONSE + { + "errorCode": 400, + "errorMessage": "Mal funcionamiento de la inteligencia artificial", + "data": { + "ESTADO": "", + "RESPUESTA": "2", + "DSC_COD_ACCION": "Lo siento Dave, me temo que no puedo hacer eso." + }, + "transactionLog": { + + } + } + RESPONSE + end + +end diff --git a/test/unit/gateways/webpay_test.rb b/test/unit/gateways/webpay_test.rb index 1e6d9406c9c..25123a5623b 100644 --- a/test/unit/gateways/webpay_test.rb +++ b/test/unit/gateways/webpay_test.rb @@ -1,6 +1,8 @@ require 'test_helper' class WebpayTest < Test::Unit::TestCase + include CommStub + def setup @gateway = WebpayGateway.new(:login => 'login') @@ -14,6 +16,25 @@ def setup } end + def test_successful_authorization + @gateway.expects(:ssl_request).returns(successful_authorization_response) + + assert response = @gateway.authorize(@amount, @credit_card, @options) + assert_instance_of Response, response + assert_success response + + assert_equal 'ch_test_charge', response.authorization + assert response.test? + end + + def test_successful_capture + @gateway.expects(:ssl_request).returns(successful_capture_response) + + assert response = @gateway.capture(@amount, 'ch_test_charge') + assert_success response + assert response.test? + end + def test_successful_purchase @gateway.expects(:ssl_request).returns(successful_purchase_response) @@ -26,19 +47,31 @@ def test_successful_purchase assert response.test? end - def test_appropiate_purchase_amount + def test_appropriate_purchase_amount @gateway.expects(:ssl_request).returns(successful_purchase_response) response = @gateway.purchase(@amount, @credit_card, @options) assert_instance_of Response, response assert_success response - assert_equal @amount / 100, response.params["amount"] + assert_equal @amount / 100, response.params['amount'] end + def test_successful_purchase_with_token + response = stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, 'cus_xxx|card_xxx') + end.check_request do |method, endpoint, data, headers| + assert_match(/customer=cus_xxx/, data) + assert_match(/card=card_xxx/, data) + end.respond_with(successful_purchase_response) + + assert response + assert_instance_of Response, response + assert_success response + end def test_successful_void - @gateway.expects(:ssl_request).returns(successful_purchase_response(true)) + @gateway.expects(:ssl_request).returns(successful_refunded_response) assert response = @gateway.void('ch_test_charge') assert_instance_of Response, response @@ -83,25 +116,25 @@ def test_invalid_raw_response assert response = @gateway.purchase(@amount, @credit_card, @options) assert_failure response - assert_match /^Invalid response received from the WebPay API/, response.message + assert_match %r{^Invalid response received from the WebPay API}, response.message end def test_add_customer post = {} - @gateway.send(:add_customer, post, {:customer => "test_customer"}) - assert_equal "test_customer", post[:customer] + @gateway.send(:add_customer, post, 'card_token', {:customer => 'test_customer'}) + assert_equal 'test_customer', post[:customer] end def test_doesnt_add_customer_if_card - post = { :card => 'foo' } - @gateway.send(:add_customer, post, {:customer => "test_customer"}) + post = {} + @gateway.send(:add_customer, post, @credit_card, {:customer => 'test_customer'}) assert !post[:customer] end def test_add_customer_data post = {} - @gateway.send(:add_customer_data, post, {:description => "a test customer"}) - assert_equal "a test customer", post[:description] + @gateway.send(:add_customer_data, post, {:description => 'a test customer'}) + assert_equal 'a test customer', post[:description] end def test_add_address @@ -125,7 +158,7 @@ def test_gateway_without_credentials end def test_metadata_header - @gateway.expects(:ssl_request).once.with {|method, url, post, headers| + @gateway.expects(:ssl_request).once.with { |method, url, post, headers| headers && headers['X-Webpay-Client-User-Metadata'] == {:ip => '1.1.1.1'}.to_json }.returns(successful_purchase_response) @@ -134,27 +167,176 @@ def test_metadata_header private - # Place raw successful response from gateway here - def successful_purchase_response(refunded=false) + def successful_authorization_response <<-RESPONSE { - "amount": 400, - "created": 1309131571, - "currency": "jpy", - "description": "Test Purchase", "id": "ch_test_charge", + "object": "charge", "livemode": false, + "currency": "jpy", + "description": "ActiveMerchant Test Purchase", + "amount": 40000, + "amount_refunded": 0, + "customer": null, + "recursion": null, + "created": 1309131571, + "paid": false, + "refunded": false, + "failure_message": null, + "card": { + "object": "card", + "exp_year": #{Time.now.year + 1}, + "exp_month": 11, + "fingerprint": "215b5b2fe460809b8bb90bae6eeac0e0e0987bd7", + "name": "LONGBOB LONGSEN", + "country": "JP", + "type": "Visa", + "cvc_check": "pass", + "last4": "4242" + }, + "captured": false, + "expire_time": 1309736371, + "fees": [ + + ] +} + RESPONSE + end + + def successful_capture_response + <<-RESPONSE +{ + "id": "ch_test_charge", "object": "charge", + "livemode": false, + "currency": "jpy", + "description": "ActiveMerchant Test Purchase", + "amount": 40000, + "amount_refunded": 0, + "customer": null, + "recursion": null, + "created": 1309131571, "paid": true, - "refunded": #{refunded}, + "refunded": false, + "failure_message": null, "card": { + "object": "card", + "exp_year": #{Time.now.year + 1}, + "exp_month": 11, + "fingerprint": "215b5b2fe460809b8bb90bae6eeac0e0e0987bd7", + "name": "LONGBOB LONGSEN", "country": "JP", - "exp_month": 9, + "type": "Visa", + "cvc_check": "pass", + "last4": "4242" + }, + "captured": true, + "expire_time": 1309736371, + "fees": [ + { + "object": "fee", + "transaction_type": "payment", + "transaction_fee": 0, + "rate": 3.25, + "amount": 1300, + "created": 1408585142 + } + ] +} + RESPONSE + end + + # Place raw successful response from gateway here + def successful_purchase_response(refunded=false) + <<-RESPONSE +{ + "id": "ch_test_charge", + "object": "charge", + "livemode": false, + "currency": "jpy", + "description": "ActiveMerchant Test Purchase", + "amount": 400, + "amount_refunded": 0, + "customer": null, + "recursion": null, + "created": 1408585273, + "paid": true, + "refunded": false, + "failure_message": null, + "card": { + "object": "card", "exp_year": #{Time.now.year + 1}, - "last4": "4242", + "exp_month": 11, + "fingerprint": "215b5b2fe460809b8bb90bae6eeac0e0e0987bd7", + "name": "LONGBOB LONGSEN", + "country": "JP", + "type": "Visa", + "cvc_check": "pass", + "last4": "4242" + }, + "captured": true, + "expire_time": null, + "fees": [ + { + "object": "fee", + "transaction_type": "payment", + "transaction_fee": 0, + "rate": 3.25, + "amount": 1300, + "created": 1408585273 + } + ] +} + RESPONSE + end + + def successful_refunded_response + <<-RESPONSE +{ + "id": "ch_test_charge", + "object": "charge", + "livemode": false, + "currency": "jpy", + "description": "ActiveMerchant Test Purchase", + "amount": 400, + "amount_refunded": 400, + "customer": null, + "recursion": null, + "created": 1408585273, + "paid": true, + "refunded": true, + "failure_message": null, + "card": { "object": "card", - "type": "Visa" - } + "exp_year": #{Time.now.year + 1}, + "exp_month": 11, + "fingerprint": "215b5b2fe460809b8bb90bae6eeac0e0e0987bd7", + "name": "KEI KUBO", + "country": "JP", + "type": "Visa", + "cvc_check": "pass", + "last4": "4242" + }, + "captured": true, + "expire_time": null, + "fees": [ + { + "object": "fee", + "transaction_type": "payment", + "transaction_fee": 0, + "rate": 3.25, + "amount": 1300, + "created": 1408585273 + }, + { + "object": "fee", + "transaction_type": "refund", + "transaction_fee": 0, + "rate": 3.25, + "amount": -1300, + "created": 1408585461 + } + ] } RESPONSE end @@ -163,24 +345,58 @@ def successful_partially_refunded_response(options = {}) options = {:livemode=>false}.merge!(options) <<-RESPONSE { - "amount": 400, - "amount_refunded": 200, - "created": 1309131571, - "currency": "jpy", - "description": "Test Purchase", "id": "ch_test_charge", - "livemode": #{options[:livemode]}, "object": "charge", + "livemode": #{options[:livemode]}, + "currency": "jpy", + "description": "ActiveMerchant Test Purchase", + "amount": 400, + "amount_refunded": 200, + "customer": null, + "recursion": null, + "created": 1408584994, "paid": true, - "refunded": true, + "refunded": false, + "failure_message": null, "card": { - "country": "JP", - "exp_month": 9, - "exp_year": #{Time.now.year + 1}, - "last4": "4242", "object": "card", - "type": "Visa" - } + "exp_year": #{Time.now.year + 1}, + "exp_month": 11, + "fingerprint": "215b5b2fe460809b8bb90bae6eeac0e0e0987bd7", + "name": "KEI KUBO", + "country": "JP", + "type": "Visa", + "cvc_check": "pass", + "last4": "4242" + }, + "captured": true, + "expire_time": 1409189794, + "fees": [ + { + "object": "fee", + "transaction_type": "payment", + "transaction_fee": 0, + "rate": 3.25, + "amount": 1300, + "created": 1408585142 + }, + { + "object": "fee", + "transaction_type": "refund", + "transaction_fee": 0, + "rate": 3.25, + "amount": -1300, + "created": 1408585699 + }, + { + "object": "fee", + "transaction_type": "payment", + "transaction_fee": 0, + "rate": 3.25, + "amount": 650, + "created": 1408585699 + } + ] } RESPONSE end @@ -188,14 +404,15 @@ def successful_partially_refunded_response(options = {}) # Place raw failed response from gateway here def failed_purchase_response <<-RESPONSE - { - "error": { - "code": "incorrect_number", - "param": "number", - "type": "card_error", - "message": "Your card number is incorrect" - } - } +{ + "error": { + "message": "The card number is invalid. Make sure the number entered matches your credit card.", + "caused_by": "buyer", + "param": "number", + "type": "card_error", + "code": "incorrect_number" + } +} RESPONSE end diff --git a/test/unit/gateways/wepay_test.rb b/test/unit/gateways/wepay_test.rb new file mode 100644 index 00000000000..9f749e27916 --- /dev/null +++ b/test/unit/gateways/wepay_test.rb @@ -0,0 +1,429 @@ +require 'test_helper' + +class WepayTest < Test::Unit::TestCase + include CommStub + + def setup + @gateway = WepayGateway.new( + client_id: 'client_id', + account_id: 'account_id', + access_token: 'access_token' + ) + + @credit_card = credit_card + @amount = 20000 + + @options = { + email: 'test@example.com' + } + end + + def test_successful_purchase + @gateway.expects(:ssl_post).at_most(3).returns(successful_capture_response) + + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + + assert_equal '1181910285|20.00', response.authorization + end + + def test_failed_purchase + @gateway.expects(:ssl_post).at_most(2).returns(failed_capture_response) + + response = @gateway.purchase(@amount, @credit_card, @options) + assert_failure response + end + + def test_successful_purchase_with_token + @gateway.expects(:ssl_post).at_most(2).returns(successful_capture_response) + + response = @gateway.purchase(@amount, '1422891921', @options) + assert_success response + + assert_equal '1181910285|20.00', response.authorization + end + + def test_failed_purchase_with_token + @gateway.expects(:ssl_post).at_most(2).returns(failed_capture_response) + + response = @gateway.purchase(@amount, '1422891921', @options) + assert_failure response + end + + def test_successful_refund + @gateway.expects(:ssl_post).returns(successful_refund_response) + + response = @gateway.refund(@amount, @credit_card, @options) + assert_success response + end + + def test_failed_refund + @gateway.expects(:ssl_post).returns(failed_refund_response) + + response = @gateway.refund(@amount, @credit_card, @options) + assert_failure response + assert_equal 'refund_reason parameter is required', response.message + end + + def test_successful_authorize + @gateway.expects(:ssl_post).at_most(2).returns(successful_authorize_response) + + response = @gateway.authorize(@amount, @credit_card, @options) + assert_success response + end + + def test_failed_authorize + @gateway.expects(:ssl_post).at_most(2).returns(failed_authorize_response) + + response = @gateway.authorize(@amount, @credit_card, @options) + assert_failure response + assert_equal 'Invalid credit card number', response.message + end + + def test_successful_authorize_with_token + @gateway.expects(:ssl_post).at_most(2).returns(successful_authorize_response) + + response = @gateway.authorize(@amount, '1422891921', @options) + assert_success response + end + + def test_failed_authorize_with_token + @gateway.expects(:ssl_post).at_most(2).returns(failed_authorize_response) + + response = @gateway.authorize(@amount, '1422891921', @options) + assert_failure response + assert_equal 'Invalid credit card number', response.message + end + + def test_successful_capture + @gateway.expects(:ssl_post).at_most(2).returns(successful_capture_response) + + response = @gateway.capture(@amount, 'auth|amount', @options) + assert_success response + end + + def test_failed_capture + @gateway.expects(:ssl_post).at_most(3).returns(failed_capture_response) + + response = @gateway.capture(@amount, 'auth|200.00', @options) + assert_failure response + assert_equal "Checkout object must be in state 'Reserved' to capture. Currently it is in state captured", response.message + end + + def test_successful_void + @gateway.expects(:ssl_post).returns(successful_void_response) + + response = @gateway.void('auth|amount', @options) + assert_success response + end + + def test_failed_void + @gateway.expects(:ssl_post).returns(failed_void_response) + + response = @gateway.void('auth|amount', @options) + assert_failure response + assert_equal 'this checkout has already been cancelled', response.message + end + + def test_successful_store_via_create + @gateway.expects(:ssl_post).returns(successful_store_response) + + response = @gateway.store(@credit_card, @options) + assert_success response + assert_equal '3322208138', response.authorization + end + + def test_successful_store_via_transfer + @gateway.expects(:ssl_post).returns(successful_store_response) + + response = @gateway.store(@credit_card, @options.merge(recurring: true)) + assert_success response + assert_equal '3322208138', response.authorization + end + + def test_failed_store + @gateway.expects(:ssl_post).returns(failed_store_response) + + response = @gateway.store(@credit_card, @options) + assert_failure response + assert_equal 'Invalid credit card number', response.message + end + + def test_invalid_json_response + @gateway.expects(:ssl_post).returns(invalid_json_response) + + response = @gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_match(/Invalid JSON response received from WePay/, response.message) + end + + def test_no_version_by_default + stub_comms do + @gateway.purchase(@amount, @credit_card, @options) + end.check_request do |endpoint, data, headers| + assert_no_match(/Api-Version/, headers.to_s) + end.respond_with(successful_authorize_response) + end + + def test_version_override + stub_comms do + @gateway.purchase(@amount, @credit_card, @options.merge(version: '2017-05-31')) + end.check_request do |endpoint, data, headers| + assert_match(/"Api-Version\"=>\"2017-05-31\"/, headers.to_s) + end.respond_with(successful_authorize_response) + end + + def test_scrub + assert @gateway.supports_scrubbing? + assert_equal @gateway.scrub(pre_scrubbed), post_scrubbed + end + + private + + def pre_scrubbed + %q( + opening connection to stage.wepayapi.com:443... + opened + starting SSL for stage.wepayapi.com:443... + SSL established + <- "POST /v2/credit_card/create HTTP/1.1\r\nContent-Type: application/json\r\nUser-Agent: ActiveMerchantBindings/1.65.0\r\nAuthorization: Bearer STAGE_c91882b0bed3584b8aed0f7f515f2f05a1d40924ee6f394ce82d91018cb0f2d3\r\nApi-Version: 2017-02-01\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nConnection: close\r\nHost: stage.wepayapi.com\r\nContent-Length: 272\r\n\r\n" + <- "{\"client_id\":\"44716\",\"user_name\":\"Longbob Longsen\",\"email\":\"test@example.com\",\"cc_number\":\"5496198584584769\",\"cvv\":\"123\",\"expiration_month\":9,\"expiration_year\":2018,\"address\":{\"address1\":\"456 My Street\",\"city\":\"Ottawa\",\"country\":\"CA\",\"region\":\"ON\",\"postal_code\":\"K1C2N6\"}}" + -> "HTTP/1.1 200 OK\r\n" + -> "Server: nginx\r\n" + -> "Content-Type: application/json\r\n" + -> "X-Content-Type-Options: nosniff\r\n" + -> "Strict-Transport-Security: max-age=31536000; preload\r\n" + -> "Transfer-Encoding: chunked\r\n" + -> "Accept-Ranges: bytes\r\n" + -> "Date: Wed, 26 Apr 2017 18:27:33 GMT\r\n" + -> "Via: 1.1 varnish\r\n" + -> "Connection: close\r\n" + -> "X-Served-By: cache-fra1231-FRA\r\n" + -> "X-Cache: MISS\r\n" + -> "X-Cache-Hits: 0\r\n" + -> "X-Timer: S1493231252.436069,VS0,VE1258\r\n" + -> "Vary: Authorization\r\n" + -> "\r\n" + -> "2b\r\n" + reading 43 bytes... + -> "{\"credit_card_id\":2559797807,\"state\":\"new\"}" + read 43 bytes + reading 2 bytes... + -> "\r\n" + read 2 bytes + -> "0\r\n" + -> "\r\n" + Conn close + opening connection to stage.wepayapi.com:443... + opened + starting SSL for stage.wepayapi.com:443... + SSL established + <- "POST /v2/checkout/create HTTP/1.1\r\nContent-Type: application/json\r\nUser-Agent: ActiveMerchantBindings/1.65.0\r\nAuthorization: Bearer STAGE_c91882b0bed3584b8aed0f7f515f2f05a1d40924ee6f394ce82d91018cb0f2d3\r\nApi-Version: 2017-02-01\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nConnection: close\r\nHost: stage.wepayapi.com\r\nContent-Length: 202\r\n\r\n" + <- "{\"payment_method\":{\"type\":\"credit_card\",\"credit_card\":{\"id\":\"2559797807\",\"auto_capture\":false}},\"account_id\":\"2080478981\",\"amount\":\"20.00\",\"short_description\":\"Purchase\",\"type\":\"goods\",\"currency\":\"USD\"}" + -> "HTTP/1.1 200 OK\r\n" + -> "Server: nginx\r\n" + -> "Content-Type: application/json\r\n" + -> "X-Content-Type-Options: nosniff\r\n" + -> "Strict-Transport-Security: max-age=31536000; preload\r\n" + -> "Transfer-Encoding: chunked\r\n" + -> "Accept-Ranges: bytes\r\n" + -> "Date: Wed, 26 Apr 2017 18:27:36 GMT\r\n" + -> "Via: 1.1 varnish\r\n" + -> "Connection: close\r\n" + -> "X-Served-By: cache-fra1247-FRA\r\n" + -> "X-Cache: MISS\r\n" + -> "X-Cache-Hits: 0\r\n" + -> "X-Timer: S1493231255.546126,VS0,VE1713\r\n" + -> "Vary: Authorization\r\n" + -> "\r\n" + -> "324\r\n" + reading 804 bytes... + -> "{\"checkout_id\":1709862829,\"account_id\":2080478981,\"type\":\"goods\",\"short_description\":\"Purchase\",\"currency\":\"USD\",\"amount\":20,\"state\":\"authorized\",\"soft_descriptor\":\"WPY*Spreedly\",\"create_time\":1493231254,\"gross\":20.88,\"reference_id\":null,\"callback_uri\":null,\"long_description\":null,\"delivery_type\":null,\"fee\":{\"app_fee\":0,\"processing_fee\":0.88,\"fee_payer\":\"payer\"},\"chargeback\":{\"amount_charged_back\":0,\"dispute_uri\":null},\"refund\":{\"amount_refunded\":0,\"refund_reason\":null},\"payment_method\":{\"type\":\"credit_card\",\"credit_card\":{\"id\":2559797807,\"data\":{\"emv_receipt\":null,\"signature_url\":null},\"auto_capture\":false}},\"hosted_checkout\":null,\"payer\":{\"email\":\"test@example.com\",\"name\":\"Longbob Longsen\",\"home_address\":null},\"npo_information\":null,\"payment_error\":null,\"in_review\":false,\"auto_release\":true}" + read 804 bytes + reading 2 bytes... + -> "\r\n" + read 2 bytes + -> "0\r\n" + -> "\r\n" + Conn close + opening connection to stage.wepayapi.com:443... + opened + starting SSL for stage.wepayapi.com:443... + SSL established + <- "POST /v2/checkout/capture HTTP/1.1\r\nContent-Type: application/json\r\nUser-Agent: ActiveMerchantBindings/1.65.0\r\nAuthorization: Bearer STAGE_c91882b0bed3584b8aed0f7f515f2f05a1d40924ee6f394ce82d91018cb0f2d3\r\nApi-Version: 2017-02-01\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nConnection: close\r\nHost: stage.wepayapi.com\r\nContent-Length: 28\r\n\r\n" + <- "{\"checkout_id\":\"1709862829\"}" + -> "HTTP/1.1 200 OK\r\n" + -> "Server: nginx\r\n" + -> "Content-Type: application/json\r\n" + -> "X-Content-Type-Options: nosniff\r\n" + -> "Strict-Transport-Security: max-age=31536000; preload\r\n" + -> "Transfer-Encoding: chunked\r\n" + -> "Accept-Ranges: bytes\r\n" + -> "Date: Wed, 26 Apr 2017 18:27:38 GMT\r\n" + -> "Via: 1.1 varnish\r\n" + -> "Connection: close\r\n" + -> "X-Served-By: cache-fra1239-FRA\r\n" + -> "X-Cache: MISS\r\n" + -> "X-Cache-Hits: 0\r\n" + -> "X-Timer: S1493231257.113609,VS0,VE1136\r\n" + -> "Vary: Authorization\r\n" + -> "\r\n" + -> "324\r\n" + reading 804 bytes... + -> "{\"checkout_id\":1709862829,\"account_id\":2080478981,\"type\":\"goods\",\"short_description\":\"Purchase\",\"currency\":\"USD\",\"amount\":20,\"state\":\"authorized\",\"soft_descriptor\":\"WPY*Spreedly\",\"create_time\":1493231254,\"gross\":20.88,\"reference_id\":null,\"callback_uri\":null,\"long_description\":null,\"delivery_type\":null,\"fee\":{\"app_fee\":0,\"processing_fee\":0.88,\"fee_payer\":\"payer\"},\"chargeback\":{\"amount_charged_back\":0,\"dispute_uri\":null},\"refund\":{\"amount_refunded\":0,\"refund_reason\":null},\"payment_method\":{\"type\":\"credit_card\",\"credit_card\":{\"id\":2559797807,\"data\":{\"emv_receipt\":null,\"signature_url\":null},\"auto_capture\":false}},\"hosted_checkout\":null,\"payer\":{\"email\":\"test@example.com\",\"name\":\"Longbob Longsen\",\"home_address\":null},\"npo_information\":null,\"payment_error\":null,\"in_review\":false,\"auto_release\":true}" + read 804 bytes + reading 2 bytes... + -> "\r\n" + read 2 bytes + -> "0\r\n" + -> "\r\n" + Conn close + ) + end + + def post_scrubbed + %q( + opening connection to stage.wepayapi.com:443... + opened + starting SSL for stage.wepayapi.com:443... + SSL established + <- "POST /v2/credit_card/create HTTP/1.1\r\nContent-Type: application/json\r\nUser-Agent: ActiveMerchantBindings/1.65.0\r\nAuthorization: Bearer [FILTERED]\r\nApi-Version: 2017-02-01\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nConnection: close\r\nHost: stage.wepayapi.com\r\nContent-Length: 272\r\n\r\n" + <- "{\"client_id\":\"44716\",\"user_name\":\"Longbob Longsen\",\"email\":\"test@example.com\",\"cc_number\":\"[FILTERED]\",\"cvv\":\"[FILTERED]\",\"expiration_month\":9,\"expiration_year\":2018,\"address\":{\"address1\":\"456 My Street\",\"city\":\"Ottawa\",\"country\":\"CA\",\"region\":\"ON\",\"postal_code\":\"K1C2N6\"}}" + -> "HTTP/1.1 200 OK\r\n" + -> "Server: nginx\r\n" + -> "Content-Type: application/json\r\n" + -> "X-Content-Type-Options: nosniff\r\n" + -> "Strict-Transport-Security: max-age=31536000; preload\r\n" + -> "Transfer-Encoding: chunked\r\n" + -> "Accept-Ranges: bytes\r\n" + -> "Date: Wed, 26 Apr 2017 18:27:33 GMT\r\n" + -> "Via: 1.1 varnish\r\n" + -> "Connection: close\r\n" + -> "X-Served-By: cache-fra1231-FRA\r\n" + -> "X-Cache: MISS\r\n" + -> "X-Cache-Hits: 0\r\n" + -> "X-Timer: S1493231252.436069,VS0,VE1258\r\n" + -> "Vary: Authorization\r\n" + -> "\r\n" + -> "2b\r\n" + reading 43 bytes... + -> "{\"credit_card_id\":2559797807,\"state\":\"new\"}" + read 43 bytes + reading 2 bytes... + -> "\r\n" + read 2 bytes + -> "0\r\n" + -> "\r\n" + Conn close + opening connection to stage.wepayapi.com:443... + opened + starting SSL for stage.wepayapi.com:443... + SSL established + <- "POST /v2/checkout/create HTTP/1.1\r\nContent-Type: application/json\r\nUser-Agent: ActiveMerchantBindings/1.65.0\r\nAuthorization: Bearer [FILTERED]\r\nApi-Version: 2017-02-01\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nConnection: close\r\nHost: stage.wepayapi.com\r\nContent-Length: 202\r\n\r\n" + <- "{\"payment_method\":{\"type\":\"credit_card\",\"credit_card\":{\"id\":\"2559797807\",\"auto_capture\":false}},\"account_id\":\"2080478981\",\"amount\":\"20.00\",\"short_description\":\"Purchase\",\"type\":\"goods\",\"currency\":\"USD\"}" + -> "HTTP/1.1 200 OK\r\n" + -> "Server: nginx\r\n" + -> "Content-Type: application/json\r\n" + -> "X-Content-Type-Options: nosniff\r\n" + -> "Strict-Transport-Security: max-age=31536000; preload\r\n" + -> "Transfer-Encoding: chunked\r\n" + -> "Accept-Ranges: bytes\r\n" + -> "Date: Wed, 26 Apr 2017 18:27:36 GMT\r\n" + -> "Via: 1.1 varnish\r\n" + -> "Connection: close\r\n" + -> "X-Served-By: cache-fra1247-FRA\r\n" + -> "X-Cache: MISS\r\n" + -> "X-Cache-Hits: 0\r\n" + -> "X-Timer: S1493231255.546126,VS0,VE1713\r\n" + -> "Vary: Authorization\r\n" + -> "\r\n" + -> "324\r\n" + reading 804 bytes... + -> "{\"checkout_id\":1709862829,\"account_id\":2080478981,\"type\":\"goods\",\"short_description\":\"Purchase\",\"currency\":\"USD\",\"amount\":20,\"state\":\"authorized\",\"soft_descriptor\":\"WPY*Spreedly\",\"create_time\":1493231254,\"gross\":20.88,\"reference_id\":null,\"callback_uri\":null,\"long_description\":null,\"delivery_type\":null,\"fee\":{\"app_fee\":0,\"processing_fee\":0.88,\"fee_payer\":\"payer\"},\"chargeback\":{\"amount_charged_back\":0,\"dispute_uri\":null},\"refund\":{\"amount_refunded\":0,\"refund_reason\":null},\"payment_method\":{\"type\":\"credit_card\",\"credit_card\":{\"id\":2559797807,\"data\":{\"emv_receipt\":null,\"signature_url\":null},\"auto_capture\":false}},\"hosted_checkout\":null,\"payer\":{\"email\":\"test@example.com\",\"name\":\"Longbob Longsen\",\"home_address\":null},\"npo_information\":null,\"payment_error\":null,\"in_review\":false,\"auto_release\":true}" + read 804 bytes + reading 2 bytes... + -> "\r\n" + read 2 bytes + -> "0\r\n" + -> "\r\n" + Conn close + opening connection to stage.wepayapi.com:443... + opened + starting SSL for stage.wepayapi.com:443... + SSL established + <- "POST /v2/checkout/capture HTTP/1.1\r\nContent-Type: application/json\r\nUser-Agent: ActiveMerchantBindings/1.65.0\r\nAuthorization: Bearer [FILTERED]\r\nApi-Version: 2017-02-01\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nConnection: close\r\nHost: stage.wepayapi.com\r\nContent-Length: 28\r\n\r\n" + <- "{\"checkout_id\":\"1709862829\"}" + -> "HTTP/1.1 200 OK\r\n" + -> "Server: nginx\r\n" + -> "Content-Type: application/json\r\n" + -> "X-Content-Type-Options: nosniff\r\n" + -> "Strict-Transport-Security: max-age=31536000; preload\r\n" + -> "Transfer-Encoding: chunked\r\n" + -> "Accept-Ranges: bytes\r\n" + -> "Date: Wed, 26 Apr 2017 18:27:38 GMT\r\n" + -> "Via: 1.1 varnish\r\n" + -> "Connection: close\r\n" + -> "X-Served-By: cache-fra1239-FRA\r\n" + -> "X-Cache: MISS\r\n" + -> "X-Cache-Hits: 0\r\n" + -> "X-Timer: S1493231257.113609,VS0,VE1136\r\n" + -> "Vary: Authorization\r\n" + -> "\r\n" + -> "324\r\n" + reading 804 bytes... + -> "{\"checkout_id\":1709862829,\"account_id\":2080478981,\"type\":\"goods\",\"short_description\":\"Purchase\",\"currency\":\"USD\",\"amount\":20,\"state\":\"authorized\",\"soft_descriptor\":\"WPY*Spreedly\",\"create_time\":1493231254,\"gross\":20.88,\"reference_id\":null,\"callback_uri\":null,\"long_description\":null,\"delivery_type\":null,\"fee\":{\"app_fee\":0,\"processing_fee\":0.88,\"fee_payer\":\"payer\"},\"chargeback\":{\"amount_charged_back\":0,\"dispute_uri\":null},\"refund\":{\"amount_refunded\":0,\"refund_reason\":null},\"payment_method\":{\"type\":\"credit_card\",\"credit_card\":{\"id\":2559797807,\"data\":{\"emv_receipt\":null,\"signature_url\":null},\"auto_capture\":false}},\"hosted_checkout\":null,\"payer\":{\"email\":\"test@example.com\",\"name\":\"Longbob Longsen\",\"home_address\":null},\"npo_information\":null,\"payment_error\":null,\"in_review\":false,\"auto_release\":true}" + read 804 bytes + reading 2 bytes... + -> "\r\n" + read 2 bytes + -> "0\r\n" + -> "\r\n" + Conn close + ) + end + + def successful_store_response + %({"credit_card_id": 3322208138,"state": "new"}) + end + + def failed_store_response + %({"error": "invalid_request","error_description": "Invalid credit card number","error_code": 1003}) + end + + def successful_refund_response + %({"checkout_id":1852898602,"state":"refunded"}) + end + + def failed_refund_response + %({"error":"invalid_request","error_description":"refund_reason parameter is required","error_code":1004}) + end + + def successful_void_response + %({"checkout_id":225040456,"state":"cancelled"}) + end + + def failed_void_response + %({"error":"invalid_request","error_description":"this checkout has already been cancelled","error_code":4004}) + end + + def successful_authorize_response + %({\"checkout_id\":1181910285,\"account_id\":2080478981,\"type\":\"goods\",\"short_description\":\"Purchase\",\"currency\":\"USD\",\"amount\":20,\"state\":\"authorized\",\"soft_descriptor\":\"WPY*Spreedly\",\"create_time\":1481836590,\"gross\":20.88,\"reference_id\":null,\"callback_uri\":null,\"long_description\":null,\"delivery_type\":null,\"fee\":{\"app_fee\":0,\"processing_fee\":0.88,\"fee_payer\":\"payer\"},\"chargeback\":{\"amount_charged_back\":0,\"dispute_uri\":null},\"refund\":{\"amount_refunded\":0,\"refund_reason\":null},\"payment_method\":{\"type\":\"credit_card\",\"credit_card\":{\"id\":1929540809,\"data\":{\"emv_receipt\":null,\"signature_url\":null},\"auto_capture\":false}},\"hosted_checkout\":null,\"payer\":{\"email\":\"test@example.com\",\"name\":\"Longbob Longsen\",\"home_address\":null},\"npo_information\":null,\"payment_error\":null,\"in_review\":false,\"auto_release\":true}) + end + + def failed_authorize_response + %({\"error\":\"invalid_request\",\"error_description\":\"Invalid credit card number\",\"error_code\":1003}) + end + + def successful_capture_response + %({\"checkout_id\":1181910285,\"account_id\":2080478981,\"type\":\"goods\",\"short_description\":\"Purchase\",\"currency\":\"USD\",\"amount\":20,\"state\":\"authorized\",\"soft_descriptor\":\"WPY*Spreedly\",\"create_time\":1481836590,\"gross\":20.88,\"reference_id\":null,\"callback_uri\":null,\"long_description\":null,\"delivery_type\":null,\"fee\":{\"app_fee\":0,\"processing_fee\":0.88,\"fee_payer\":\"payer\"},\"chargeback\":{\"amount_charged_back\":0,\"dispute_uri\":null},\"refund\":{\"amount_refunded\":0,\"refund_reason\":null},\"payment_method\":{\"type\":\"credit_card\",\"credit_card\":{\"id\":1929540809,\"data\":{\"emv_receipt\":null,\"signature_url\":null},\"auto_capture\":false}},\"hosted_checkout\":null,\"payer\":{\"email\":\"test@example.com\",\"name\":\"Longbob Longsen\",\"home_address\":null},\"npo_information\":null,\"payment_error\":null,\"in_review\":false,\"auto_release\":true}) + end + + def failed_capture_response + %({"error":"invalid_request","error_description":"Checkout object must be in state 'Reserved' to capture. Currently it is in state captured","error_code":4004}) + end + + def invalid_json_response + %({"checkout_id"=1852898602,"state":"captured") + end + +end diff --git a/test/unit/gateways/wirecard_test.rb b/test/unit/gateways/wirecard_test.rb index 6a1a75218ac..f8f3e2d2b90 100644 --- a/test/unit/gateways/wirecard_test.rb +++ b/test/unit/gateways/wirecard_test.rb @@ -1,3 +1,5 @@ +# encoding: UTF-8 + require 'test_helper' class WirecardTest < Test::Unit::TestCase @@ -8,28 +10,37 @@ class WirecardTest < Test::Unit::TestCase TEST_CAPTURE_GUWID = 'C833707121385268439116' def setup - @gateway = WirecardGateway.new(:login => '', :password => '', :signature => '') + @gateway = WirecardGateway.new(login: '', password: '', signature: '') @credit_card = credit_card('4200000000000000') @declined_card = credit_card('4000300011112220') - @unsupported_card = credit_card('4200000000000000', :brand => :maestro) + @unsupported_card = credit_card('4200000000000000', brand: :maestro) + @amex_card = credit_card('370000000000000', brand: 'american_express') @amount = 111 @options = { - :order_id => '1', - :billing_address => address, - :description => 'Wirecard Purchase', - :email => 'soleone@example.com' + order_id: '1', + billing_address: address, + description: 'Wirecard Purchase', + email: 'soleone@example.com' } @address_without_state = { - :name => 'Jim Smith', - :address1 => '1234 My Street', - :company => 'Widgets Inc', - :city => 'Ottawa', - :zip => 'K12 P2A', - :country => 'CA', - :state => nil, + name: 'Jim Smith', + address1: '1234 My Street', + company: 'Widgets Inc', + city: 'Ottawa', + zip: 'K12 P2A', + country: 'CA', + state: nil, + } + + @address_avs = { + address1: '9 Derry Street', + city: 'London', + zip: 'W8 2TE', + country: 'GB', + state: 'London', } end @@ -53,6 +64,26 @@ def test_successful_purchase assert_equal TEST_PURCHASE_GUWID, response.authorization end + def test_successful_reference_purchase + @gateway.expects(:ssl_post).returns(successful_purchase_response) + assert response = @gateway.purchase(@amount, '12345', @options) + assert_instance_of Response, response + + assert_success response + assert response.test? + assert_equal TEST_PURCHASE_GUWID, response.authorization + end + + def test_successful_reference_authorization + @gateway.expects(:ssl_post).returns(successful_authorization_response) + assert response = @gateway.authorize(@amount, '12345', @options) + assert_instance_of Response, response + + assert_success response + assert response.test? + assert_equal TEST_AUTHORIZATION_GUWID, response.authorization + end + def test_wrong_credit_card_authorization @gateway.expects(:ssl_post).returns(wrong_creditcard_authorization_response) assert response = @gateway.authorize(@amount, @declined_card, @options) @@ -61,7 +92,8 @@ def test_wrong_credit_card_authorization assert_failure response assert response.test? assert_equal TEST_AUTHORIZATION_GUWID, response.authorization - assert response.message[/credit card number not allowed in demo mode/i] + assert_match %r{credit card number not allowed in demo mode}i, response.message + assert_equal '24997', response.params['ErrorCode'] end def test_successful_authorization_and_capture @@ -77,6 +109,32 @@ def test_successful_authorization_and_capture assert response.message[/this is a demo/i] end + def test_successful_refund + @gateway.expects(:ssl_post).returns(successful_purchase_response) + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal TEST_PURCHASE_GUWID, response.authorization + + @gateway.expects(:ssl_post).returns(successful_refund_response) + assert response = @gateway.refund(@amount - 30, response.authorization, @options) + assert_success response + assert response.test? + assert_match %r{All good!}, response.message + end + + def test_successful_void + @gateway.expects(:ssl_post).returns(successful_purchase_response) + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal TEST_PURCHASE_GUWID, response.authorization + + @gateway.expects(:ssl_post).returns(successful_void_response) + assert response = @gateway.void(response.authorization, @options) + assert_success response + assert response.test? + assert_match %r{Nice one!}, response.message + end + def test_successful_authorization_and_partial_capture @gateway.expects(:ssl_post).returns(successful_authorization_response) assert response = @gateway.authorize(@amount, @credit_card, @options) @@ -92,15 +150,29 @@ def test_successful_authorization_and_partial_capture def test_unauthorized_capture @gateway.expects(:ssl_post).returns(unauthorized_capture_response) - assert response = @gateway.capture(@amount, "1234567890123456789012", @options) + assert response = @gateway.capture(@amount, '1234567890123456789012', @options) assert_failure response assert_equal TEST_CAPTURE_GUWID, response.authorization - assert response.message["Could not find referenced transaction for GuWID 1234567890123456789012."] + assert response.message['Could not find referenced transaction for GuWID 1234567890123456789012.'] + end + + def test_failed_refund + @gateway.expects(:ssl_post).returns(failed_refund_response) + assert response = @gateway.refund(@amount - 30, 'TheIdentifcation', @options) + assert_failure response + assert_match %r{Not prudent}, response.message + end + + def test_failed_void + @gateway.expects(:ssl_post).returns(failed_void_response) + assert response = @gateway.refund(@amount - 30, 'TheIdentifcation', @options) + assert_failure response + assert_match %r{Not gonna do it}, response.message end def test_no_error_if_no_state_is_provided_in_address - options = @options.merge(:billing_address => @address_without_state) + options = @options.merge(billing_address: @address_without_state) @gateway.expects(:ssl_post).returns(unauthorized_capture_response) assert_nothing_raised do @gateway.authorize(@amount, @credit_card, options) @@ -116,7 +188,7 @@ def test_no_error_if_no_address_provided end def test_description_trucated_to_32_chars_in_authorize - options = {:description => "32chars-------------------------EXTRA"} + options = { description: '32chars-------------------------EXTRA' } stub_comms do @gateway.authorize(@amount, @credit_card, options) @@ -126,7 +198,7 @@ def test_description_trucated_to_32_chars_in_authorize end def test_description_trucated_to_32_chars_in_purchase - options = {:description => "32chars-------------------------EXTRA"} + options = { description: '32chars-------------------------EXTRA' } stub_comms do @gateway.purchase(@amount, @credit_card, options) @@ -135,8 +207,126 @@ def test_description_trucated_to_32_chars_in_purchase end.respond_with(successful_purchase_response) end + def test_description_is_ascii_encoded_since_wirecard_does_not_like_utf_8 + options = { description: '¿Dónde está la estación?' } + + stub_comms do + @gateway.purchase(@amount, @credit_card, options) + end.check_request do |endpoint, data, headers| + assert_match(/<FunctionID>\?D\?nde est\? la estaci\?n\?<\/FunctionID>/, data) + end.respond_with(successful_purchase_response) + end + + def test_failed_avs_response_message + options = @options.merge(billing_address: @address_avs) + @gateway.expects(:ssl_post).returns(failed_avs_response) + response = @gateway.purchase(@amount, @credit_card, options) + assert_match %r{A}, response.avs_result['code'] + end + + def test_failed_amex_avs_response_code + options = @options.merge(billing_address: @address_avs) + @gateway.expects(:ssl_post).returns(failed_avs_response) + response = @gateway.purchase(@amount, @amex_card, options) + assert_match %r{B}, response.avs_result['code'] + end + + def test_commerce_type_option + options = { commerce_type: 'MOTO' } + + stub_comms do + @gateway.purchase(@amount, @credit_card, options) + end.check_request do |endpoint, data, headers| + assert_match(/<CommerceType>MOTO<\/CommerceType>/, data) + end.respond_with(successful_purchase_response) + end + + def test_store_sets_recurring_transaction_type_to_initial + stub_comms do + @gateway.store(@credit_card) + end.check_request do |endpoint, body, headers| + assert_xml_element_text(body, '//RECURRING_TRANSACTION/Type', 'Initial') + end.respond_with(successful_authorization_response) + end + + def test_store_sets_amount_to_100_by_default + stub_comms do + @gateway.store(@credit_card) + end.check_request do |endpoint, body, headers| + assert_xml_element_text(body, '//CC_TRANSACTION/Amount', '100') + end.respond_with(successful_authorization_response) + end + + def test_store_sets_amount_to_amount_from_options + stub_comms do + @gateway.store(@credit_card, :amount => 120) + end.check_request do |endpoint, body, headers| + assert_xml_element_text(body, '//CC_TRANSACTION/Amount', '120') + end.respond_with(successful_authorization_response) + end + + def test_authorization_using_reference_sets_proper_elements + stub_comms do + @gateway.authorize(@amount, '45678', @options) + end.check_request do |endpoint, body, headers| + assert_xml_element_text(body, '//GuWID', '45678') + assert_no_match(/<CREDIT_CARD_DATA>/, body) + end.respond_with(successful_authorization_response) + end + + def test_purchase_using_reference_sets_proper_elements + stub_comms do + @gateway.purchase(@amount, '87654', @options) + end.check_request do |endpoint, body, headers| + assert_xml_element_text(body, '//GuWID', '87654') + assert_no_match(/<CREDIT_CARD_DATA>/, body) + end.respond_with(successful_authorization_response) + end + + def test_authorization_with_recurring_transaction_type_initial + stub_comms do + @gateway.authorize(@amount, @credit_card, @options.merge(:recurring => 'Initial')) + end.check_request do |endpoint, body, headers| + assert_xml_element_text(body, '//RECURRING_TRANSACTION/Type', 'Initial') + end.respond_with(successful_authorization_response) + end + + def test_purchase_using_with_recurring_transaction_type_initial + stub_comms do + @gateway.purchase(@amount, @credit_card, @options.merge(:recurring => 'Initial')) + end.check_request do |endpoint, body, headers| + assert_xml_element_text(body, '//RECURRING_TRANSACTION/Type', 'Initial') + end.respond_with(successful_authorization_response) + end + + def test_system_error_response + @gateway.expects(:ssl_post).returns(system_error_response) + assert response = @gateway.purchase(@amount, @credit_card, @options) + + assert_failure response + end + + def test_system_error_response_without_job + @gateway.expects(:ssl_post).returns(system_error_response_without_job) + assert response = @gateway.purchase(@amount, @credit_card, @options) + + assert_failure response + assert_equal 'Job Refused', response.params['Message'] + assert_equal '10003', response.params['ErrorCode'] + end + + def test_transcript_scrubbing + assert_equal scrubbed_transcript, @gateway.scrub(transcript) + end + private + def assert_xml_element_text(xml, xpath, expected_text) + root = REXML::Document.new(xml).root + actual_text = root ? root.get_text(xpath).to_s : nil + assert_equal expected_text, actual_text, %{Expected to find the text "#{expected_text}" within the XML element with path "#{xpath}", but instead found the text "#{actual_text}" in the following XML:\n#{xml}} + end + # Authorization success def successful_authorization_response <<-XML @@ -178,10 +368,11 @@ def wrong_creditcard_authorization_response XML result_node = '</FunctionResult>' auth = 'AuthorizationCode' - successful_authorization_response.gsub('ACK', 'NOK') \ - .gsub(result_node, result_node + error) \ - .gsub(/<#{auth}>\w+<\/#{auth}>/, "<#{auth}><\/#{auth}>") \ - .gsub(/<Info>.+<\/Info>/, '') + successful_authorization_response. + gsub('ACK', 'NOK'). + gsub(result_node, result_node + error). + gsub(/<#{auth}>\w+<\/#{auth}>/, "<#{auth}><\/#{auth}>"). + gsub(/<Info>.+<\/Info>/, '') end # Capture success @@ -272,6 +463,122 @@ def successful_purchase_response XML end + def successful_refund_response + <<-XML + <?xml version="1.0" encoding="UTF-8"?> + <WIRECARD_BXML xmlns:xsi="http://www.w3.org/1999/XMLSchema-instance" xsi:noNamespaceSchemaLocation="wirecard.xsd"> + <W_RESPONSE> + <W_JOB> + <JobID></JobID> + <FNC_CC_BOOKBACK> + <FunctionID></FunctionID> + <CC_TRANSACTION> + <TransactionID>2a486b3ab747df694d5460c3cb444591</TransactionID> + <PROCESSING_STATUS> + <GuWID>C898842138247065382261</GuWID> + <AuthorizationCode>424492</AuthorizationCode> + <Info>All good!</Info> + <StatusType>INFO</StatusType> + <FunctionResult>ACK</FunctionResult> + <TimeStamp>2013-10-22 21:37:33</TimeStamp> + </PROCESSING_STATUS> + </CC_TRANSACTION> + </FNC_CC_BOOKBACK> + </W_JOB> + </W_RESPONSE> + </WIRECARD_BXML> + XML + end + + def failed_refund_response + <<-XML + <?xml version="1.0" encoding="UTF-8"?> + <WIRECARD_BXML xmlns:xsi="http://www.w3.org/1999/XMLSchema-instance" xsi:noNamespaceSchemaLocation="wirecard.xsd"> + <W_RESPONSE> + <W_JOB> + <JobID></JobID> + <FNC_CC_BOOKBACK> + <FunctionID></FunctionID> + <CC_TRANSACTION> + <TransactionID>98680cbeee81d32e94a2b71397ffdf88</TransactionID> + <PROCESSING_STATUS> + <GuWID>C999187138247102291030</GuWID> + <AuthorizationCode></AuthorizationCode> + <StatusType>INFO</StatusType> + <FunctionResult>NOK</FunctionResult> + <ERROR> + <Type>DATA_ERROR</Type> + <Number>20080</Number> + <Message>Not prudent</Message> + </ERROR> + <TimeStamp>2013-10-22 21:43:42</TimeStamp> + </PROCESSING_STATUS> + </CC_TRANSACTION> + </FNC_CC_BOOKBACK> + </W_JOB> + </W_RESPONSE> + </WIRECARD_BXML> + XML + end + + def successful_void_response + <<-XML + <?xml version="1.0" encoding="UTF-8"?> + <WIRECARD_BXML xmlns:xsi="http://www.w3.org/1999/XMLSchema-instance" xsi:noNamespaceSchemaLocation="wirecard.xsd"> + <W_RESPONSE> + <W_JOB> + <JobID></JobID> + <FNC_CC_REVERSAL> + <FunctionID></FunctionID> + <CC_TRANSACTION> + <TransactionID>5f1a2ab3fb2ed7a6aaa0eea74dc109e2</TransactionID> + <PROCESSING_STATUS> + <GuWID>C907807138247383379288</GuWID> + <AuthorizationCode>802187</AuthorizationCode> + <Info>Nice one!</Info> + <StatusType>INFO</StatusType> + <FunctionResult>ACK</FunctionResult> + <TimeStamp>2013-10-22 22:30:33</TimeStamp> + </PROCESSING_STATUS> + </CC_TRANSACTION> + </FNC_CC_REVERSAL> + </W_JOB> + </W_RESPONSE> + </WIRECARD_BXML> + XML + end + + def failed_void_response + <<-XML + <?xml version="1.0" encoding="UTF-8"?> + <WIRECARD_BXML xmlns:xsi="http://www.w3.org/1999/XMLSchema-instance" xsi:noNamespaceSchemaLocation="wirecard.xsd"> + <W_RESPONSE> + <W_JOB> + <JobID></JobID> + <FNC_CC_REVERSAL> + <FunctionID></FunctionID> + <CC_TRANSACTION> + <TransactionID>c11154e9395cf03c49bd68ec5c7087cc</TransactionID> + <PROCESSING_STATUS> + <GuWID>C941776138247400010330</GuWID> + <AuthorizationCode></AuthorizationCode> + <StatusType>INFO</StatusType> + <FunctionResult>NOK</FunctionResult> + <ERROR> + <Type>DATA_ERROR</Type> + <Number>20080</Number> + <Message>Not gonna do it</Message> + </ERROR> + <TimeStamp>2013-10-22 22:33:20</TimeStamp> + </PROCESSING_STATUS> + </CC_TRANSACTION> + </FNC_CC_REVERSAL> + </W_JOB> + </W_RESPONSE> + </WIRECARD_BXML> + XML + end + # Purchase failure def wrong_creditcard_purchase_response <<-XML @@ -303,4 +610,173 @@ def wrong_creditcard_purchase_response </WIRECARD_BXML> XML end + + # AVS failure + def failed_avs_response + <<-XML + <?xml version="1.0" encoding="UTF-8"?> + <WIRECARD_BXML xmlns:xsi="http://www.w3.org/1999/XMLSchema-instance" xsi:noNamespaceSchemaLocation="wirecard.xsd"> + <W_RESPONSE> + <W_JOB> + <JobID></JobID> + <FNC_CC_PURCHASE> + <FunctionID></FunctionID> + <CC_TRANSACTION> + <TransactionID>E0BCBF30B82D0131000000000000E4CF</TransactionID> + <PROCESSING_STATUS> + <GuWID>C997753139988691610455</GuWID> + <AuthorizationCode>732129</AuthorizationCode> + <Info>THIS IS A DEMO TRANSACTION USING CREDIT CARD NUMBER 420000****0000. NO REAL MONEY WILL BE TRANSFERED.</Info> + <StatusType>INFO</StatusType> + <FunctionResult>PENDING</FunctionResult> + <AVS> + <ResultCode>U</ResultCode> + <Message>AVS Unavailable.</Message> + <AuthorizationEntity>5</AuthorizationEntity> + <AuthorizationEntityMessage>Response provided by issuer processor.</AuthorizationEntityMessage> + <ProviderResultCode>A</ProviderResultCode> + <ProviderResultMessage>Address information is unavailable, or the Issuer does not support AVS. Acquirer has representment rights.</ProviderResultMessage> + </AVS> + <TimeStamp>2014-05-12 11:28:36</TimeStamp> + </PROCESSING_STATUS> + </CC_TRANSACTION> + </FNC_CC_PURCHASE> + </W_JOB> + </W_RESPONSE> + </WIRECARD_BXML> + XML + end + + def system_error_response + <<-XML + <?xml version="1.0" encoding="UTF-8"?> + <WIRECARD_BXML xmlns:xsi="http://www.w3.org/1999/XMLSchema-instance" xsi:noNamespaceSchemaLocation="wirecard.xsd"> + <W_RESPONSE> + <W_JOB> + <JobID></JobID> + <FNC_CC_PURCHASE> + <FunctionID></FunctionID> + <CC_TRANSACTION> + <TransactionID>3A368E50D50B01310000000000009153</TransactionID> + <PROCESSING_STATUS> + <GuWID>C967464140265180577024</GuWID> + <AuthorizationCode></AuthorizationCode> + <Info>THIS IS A DEMO TRANSACTION USING CREDIT CARD NUMBER 420000****0000. NO REAL MONEY WILL BE TRANSFERED.</Info> + <StatusType>INFO</StatusType> + <FunctionResult>NOK</FunctionResult> + <ERROR> + <Type>SYSTEM_ERROR</Type> + <Number>20205</Number> + <Message></Message> + </ERROR> + <TimeStamp>2014-06-13 11:30:05</TimeStamp> + </PROCESSING_STATUS> + </CC_TRANSACTION> + </FNC_CC_PURCHASE> + </W_JOB> + </W_RESPONSE> + </WIRECARD_BXML> + XML + end + + def system_error_response_without_job + <<-XML + <?xml version="1.0" encoding="UTF-8"?> + <WIRECARD_BXML xmlns:xsi="http://www.w3.org/1999/XMLSchema-instance" xsi:noNamespaceSchemaLocation="wirecard.xsd"> + <W_RESPONSE> + <ERROR> + <Type>SYSTEM_ERROR</Type> + <Number>10003</Number> + <Message>Job Refused</Message> + </ERROR> + </W_RESPONSE> + </WIRECARD_BXML> + XML + end + + def transcript + <<-XML + <WIRECARD_BXML> + <W_REQUEST> + <W_JOB> + <JobID></JobID> + <BusinessCaseSignature>00000031629CAFD5</BusinessCaseSignature> + <FNC_CC_PURCHASE> + <FunctionID>Wirecard remote test purchase</FunctionID> + <CC_TRANSACTION> + <TransactionID>1</TransactionID> + <Amount>100</Amount> + <Currency>EUR</Currency> + <CountryCode>CA</CountryCode> + <RECURRING_TRANSACTION> + <Type>Single</Type> + </RECURRING_TRANSACTION> + <CREDIT_CARD_DATA> + <CreditCardNumber>4200000000000000</CreditCardNumber> + <CVC2>123</CVC2> + <ExpirationYear>2016</ExpirationYear> + <ExpirationMonth>09</ExpirationMonth> + <CardHolderName>Longbob Longsen</CardHolderName> + </CREDIT_CARD_DATA> + <CORPTRUSTCENTER_DATA> + <ADDRESS> + <Address1>456 My Street</Address1> + <Address2>Apt 1</Address2> + <City>Ottawa</City> + <ZipCode>K1C2N6</ZipCode> + <State>ON</State> + <Country>CA</Country> + <Email>soleone@example.com</Email> + </ADDRESS> + </CORPTRUSTCENTER_DATA> + </CC_TRANSACTION> + </FNC_CC_PURCHASE> + </W_JOB> + </W_REQUEST> + </WIRECARD_BXML> + XML + end + + def scrubbed_transcript + <<-XML + <WIRECARD_BXML> + <W_REQUEST> + <W_JOB> + <JobID></JobID> + <BusinessCaseSignature>00000031629CAFD5</BusinessCaseSignature> + <FNC_CC_PURCHASE> + <FunctionID>Wirecard remote test purchase</FunctionID> + <CC_TRANSACTION> + <TransactionID>1</TransactionID> + <Amount>100</Amount> + <Currency>EUR</Currency> + <CountryCode>CA</CountryCode> + <RECURRING_TRANSACTION> + <Type>Single</Type> + </RECURRING_TRANSACTION> + <CREDIT_CARD_DATA> + <CreditCardNumber>[FILTERED]</CreditCardNumber> + <CVC2>[FILTERED]</CVC2> + <ExpirationYear>2016</ExpirationYear> + <ExpirationMonth>09</ExpirationMonth> + <CardHolderName>Longbob Longsen</CardHolderName> + </CREDIT_CARD_DATA> + <CORPTRUSTCENTER_DATA> + <ADDRESS> + <Address1>456 My Street</Address1> + <Address2>Apt 1</Address2> + <City>Ottawa</City> + <ZipCode>K1C2N6</ZipCode> + <State>ON</State> + <Country>CA</Country> + <Email>soleone@example.com</Email> + </ADDRESS> + </CORPTRUSTCENTER_DATA> + </CC_TRANSACTION> + </FNC_CC_PURCHASE> + </W_JOB> + </W_REQUEST> + </WIRECARD_BXML> + XML + end end diff --git a/test/unit/gateways/world_net_test.rb b/test/unit/gateways/world_net_test.rb new file mode 100644 index 00000000000..c32728f69b9 --- /dev/null +++ b/test/unit/gateways/world_net_test.rb @@ -0,0 +1,260 @@ +require 'test_helper' + +class WorldNetTest < Test::Unit::TestCase + def setup + @gateway = WorldNetGateway.new(terminal_id: '6001', secret: 'SOMECREDENTIAL') + @credit_card = credit_card + @amount = 100 + + @options = { + order_id: '1', + billing_address: address, + description: 'Store Purchase' + } + @refund_options = { + operator: 'mr.nobody', + reason: 'returned' + } + end + + def test_successful_purchase + @gateway.expects(:ssl_post).returns(successful_purchase_response) + + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + + assert_equal 'GZG6IG6VXI', response.authorization + assert response.test? + end + + def test_failed_purchase + @gateway.expects(:ssl_post).returns(failed_purchase_response) + + response = @gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_equal Gateway::STANDARD_ERROR_CODE[:card_declined], response.error_code + end + + def test_successful_authorize + @gateway.expects(:ssl_post).returns(successful_authorize_response) + + response = @gateway.authorize(@amount, @credit_card, @options) + assert_success response + + assert_equal 'BF4CNN6WXP', response.authorization + assert response.test? + end + + def test_failed_authorize + @gateway.expects(:ssl_post).returns(failed_purchase_response) + + response = @gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_equal Gateway::STANDARD_ERROR_CODE[:card_declined], response.error_code + end + + def test_successful_capture + @gateway.expects(:ssl_post).returns(successful_capture_response) + + response = @gateway.capture(@amount, 'BF4CNN6WXP', @options) + assert_success response + + assert_equal 'BF4CNN6WXP', response.authorization + assert response.test? + end + + def test_failed_capture + @gateway.expects(:ssl_post).returns(failed_capture_response) + + response = @gateway.capture(@amount, 'BF4CNN6WXP', @options) + assert_failure response + assert_equal Gateway::STANDARD_ERROR_CODE[:processing_error], response.error_code + end + + def test_successful_refund + @gateway.expects(:ssl_post).returns(successful_refund_response) + + response = @gateway.refund(@amount, 'GIOH10II2J', @refund_options) + assert_success response + + assert_equal 'GIOH10II2J', response.authorization + assert response.test? + end + + def test_failed_refund + @gateway.expects(:ssl_post).returns(failed_refund_response) + + response = @gateway.refund(@amount, 'GIOH10II2J', @refund_options) + assert_failure response + assert_equal Gateway::STANDARD_ERROR_CODE[:processing_error], response.error_code + end + + def test_failed_void + @gateway.expects(:ssl_post).returns(failed_void_response) + + response = @gateway.void('GIOH10II2J') + assert_failure response + assert_equal Gateway::STANDARD_ERROR_CODE[:processing_error], response.error_code + end + + def test_successful_verify_with_failed_void + @gateway.expects(:ssl_post).times(2).returns(successful_authorize_response).then.returns(failed_void_response) + response = @gateway.verify(credit_card, @options) + assert_success response + assert_equal 'APPROVAL', response.message + end + + def test_failed_verify + @gateway.expects(:ssl_post).returns(failed_authorize_response) + response = @gateway.verify(credit_card, @options) + assert_failure response + assert_equal 'DECLINED', response.message + end + + def test_successful_store + @gateway.expects(:ssl_post).returns(successful_store_response) + response = @gateway.store(credit_card, @options) + assert_success response + end + + def test_unsuccessful_store + @gateway.expects(:ssl_post).returns(failed_store_response) + response = @gateway.store(credit_card, @options) + assert_failure response + end + + def test_successful_unstore + @gateway.expects(:ssl_post).returns(successful_unstore_response) + response = @gateway.unstore('4111111111111111', @options) + assert_success response + end + + def test_unsuccessful_unstore + @gateway.expects(:ssl_post).returns(failed_unstore_response) + response = @gateway.unstore('4111111111111111', @options) + assert_failure response + end + + def test_scrub + assert @gateway.supports_scrubbing? + assert_equal @gateway.scrub(pre_scrubbed), post_scrubbed + end + + private + + def pre_scrubbed + %q( +opening connection to testpayments.worldnettps.com:443... +opened +starting SSL for testpayments.worldnettps.com:443... +SSL established +<- "POST /merchant/xmlpayment HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: testpayments.worldnettps.com\r\nContent-Length: 516\r\n\r\n" +<- "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<PAYMENT>\n <ORDERID>144232907005</ORDERID>\n <TERMINALID>6001</TERMINALID>\n <AMOUNT>1.00</AMOUNT>\n <DATETIME>15-09-2015:14:57:50:054</DATETIME>\n <CARDNUMBER>3779810000000005</CARDNUMBER>\n <CARDTYPE>VISA</CARDTYPE>\n <CARDEXPIRY>0916</CARDEXPIRY>\n <CARDHOLDERNAME>Longbob Longsen</CARDHOLDERNAME>\n <HASH>e1d545745667ff6ab6c7bd9d961d3090</HASH>\n <CURRENCY>EUR</CURRENCY>\n <TERMINALTYPE>2</TERMINALTYPE>\n <TRANSACTIONTYPE>7</TRANSACTIONTYPE>\n <CVV>123</CVV>\n</PAYMENT>\n" +-> "HTTP/1.1 200 OK\r\n" +-> "Date: Tue, 15 Sep 2015 14:57:50 GMT\r\n" +-> "Server: Apache\r\n" +-> "Content-Length: 352\r\n" +-> "Strict-Transport-Security: max-age=15768000\r\n" +-> "Connection: close\r\n" +-> "Content-Type: text/xml;charset=UTF-8\r\n" +-> "\r\n" +reading 352 bytes... +-> "" +-> "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n<PAYMENTRESPONSE><UNIQUEREF>C41TUQLEKZ</UNIQUEREF><RESPONSECODE>A</RESPONSECODE><RESPONSETEXT>APPROVAL</RESPONSETEXT><APPROVALCODE>475318</APPROVALCODE><DATETIME>2015-09-15T15:57:50</DATETIME><AVSRESPONSE>X</AVSRESPONSE><CVVRESPONSE>M</CVVRESPONSE><HASH>7cddcd17853c9d0736397dfadfb12a3e</HASH></PAYMENTRESPONSE>\n" +read 352 bytes +Conn close + ) + end + + def post_scrubbed + %q( +opening connection to testpayments.worldnettps.com:443... +opened +starting SSL for testpayments.worldnettps.com:443... +SSL established +<- "POST /merchant/xmlpayment HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: testpayments.worldnettps.com\r\nContent-Length: 516\r\n\r\n" +<- "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<PAYMENT>\n <ORDERID>144232907005</ORDERID>\n <TERMINALID>6001</TERMINALID>\n <AMOUNT>1.00</AMOUNT>\n <DATETIME>15-09-2015:14:57:50:054</DATETIME>\n <CARDNUMBER>377981...0005</CARDNUMBER>\n <CARDTYPE>VISA</CARDTYPE>\n <CARDEXPIRY>0916</CARDEXPIRY>\n <CARDHOLDERNAME>Longbob Longsen</CARDHOLDERNAME>\n <HASH>e1d545745667ff6ab6c7bd9d961d3090</HASH>\n <CURRENCY>EUR</CURRENCY>\n <TERMINALTYPE>2</TERMINALTYPE>\n <TRANSACTIONTYPE>7</TRANSACTIONTYPE>\n <CVV>...</CVV>\n</PAYMENT>\n" +-> "HTTP/1.1 200 OK\r\n" +-> "Date: Tue, 15 Sep 2015 14:57:50 GMT\r\n" +-> "Server: Apache\r\n" +-> "Content-Length: 352\r\n" +-> "Strict-Transport-Security: max-age=15768000\r\n" +-> "Connection: close\r\n" +-> "Content-Type: text/xml;charset=UTF-8\r\n" +-> "\r\n" +reading 352 bytes... +-> "" +-> "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n<PAYMENTRESPONSE><UNIQUEREF>C41TUQLEKZ</UNIQUEREF><RESPONSECODE>A</RESPONSECODE><RESPONSETEXT>APPROVAL</RESPONSETEXT><APPROVALCODE>475318</APPROVALCODE><DATETIME>2015-09-15T15:57:50</DATETIME><AVSRESPONSE>X</AVSRESPONSE><CVVRESPONSE>M</CVVRESPONSE><HASH>7cddcd17853c9d0736397dfadfb12a3e</HASH></PAYMENTRESPONSE>\n" +read 352 bytes +Conn close + ) + end + + def successful_purchase_response + '<?xml version="1.0" encoding="UTF-8"?> +<PAYMENTRESPONSE><UNIQUEREF>GZG6IG6VXI</UNIQUEREF><RESPONSECODE>A</RESPONSECODE><RESPONSETEXT>APPROVAL</RESPONSETEXT><APPROVALCODE>475318</APPROVALCODE><DATETIME>2015-09-14T21:22:12</DATETIME><AVSRESPONSE>X</AVSRESPONSE><CVVRESPONSE>M</CVVRESPONSE><HASH>f8642d613c56628371a579443ce8d895</HASH></PAYMENTRESPONSE>' + end + + def failed_purchase_response + '<?xml version="1.0" encoding="UTF-8"?> +<PAYMENTRESPONSE><UNIQUEREF>JQU1810S4E</UNIQUEREF><RESPONSECODE>D</RESPONSECODE><RESPONSETEXT>DECLINED</RESPONSETEXT><APPROVALCODE></APPROVALCODE><DATETIME>2015-09-14T21:40:07</DATETIME><AVSRESPONSE></AVSRESPONSE><CVVRESPONSE></CVVRESPONSE><HASH>c0ba33a10a6388b12c8fad79a107f2b5</HASH></PAYMENTRESPONSE>' + end + + def successful_authorize_response + '<?xml version="1.0" encoding="UTF-8"?> +<PREAUTHRESPONSE><UNIQUEREF>BF4CNN6WXP</UNIQUEREF><RESPONSECODE>A</RESPONSECODE><RESPONSETEXT>APPROVAL</RESPONSETEXT><APPROVALCODE>450848</APPROVALCODE><DATETIME>2015-09-14T21:53:10</DATETIME><AVSRESPONSE></AVSRESPONSE><CVVRESPONSE></CVVRESPONSE><HASH>e80c52476af1dd969f3bf89ed02fe16f</HASH></PREAUTHRESPONSE>' + end + + def failed_authorize_response + '<?xml version="1.0" encoding="UTF-8"?> +<PREAUTHRESPONSE><UNIQUEREF>IP0PUDDXG5</UNIQUEREF><RESPONSECODE>D</RESPONSECODE><RESPONSETEXT>DECLINED</RESPONSETEXT><APPROVALCODE></APPROVALCODE><DATETIME>2015-09-15T14:21:37</DATETIME><AVSRESPONSE></AVSRESPONSE><CVVRESPONSE></CVVRESPONSE><HASH>05dfa85163ee8d8afa8711019f64acb3</HASH></PREAUTHRESPONSE>' + end + + def successful_capture_response + '<?xml version="1.0" encoding="UTF-8"?> +<PREAUTHCOMPLETIONRESPONSE><UNIQUEREF>BF4CNN6WXP</UNIQUEREF><RESPONSECODE>A</RESPONSECODE><RESPONSETEXT>APPROVAL</RESPONSETEXT><APPROVALCODE>450848</APPROVALCODE><DATETIME>2015-09-14T21:53:10</DATETIME><AVSRESPONSE></AVSRESPONSE><CVVRESPONSE></CVVRESPONSE><HASH>e80c52476af1dd969f3bf89ed02fe16f</HASH></PREAUTHCOMPLETIONRESPONSE>' + end + + def failed_capture_response + '<?xml version="1.0" encoding="UTF-8"?> +<ERROR><ERRORSTRING>cvc-minLength-valid: Value &apos;&apos; with length = &apos;0&apos; is not facet-valid with respect to minLength &apos;10&apos; for type &apos;UID&apos;.</ERRORSTRING></ERROR>' + end + + def successful_refund_response + '<?xml version="1.0" encoding="UTF-8"?> +<REFUNDRESPONSE><RESPONSECODE>A</RESPONSECODE><RESPONSETEXT>SUCCESS</RESPONSETEXT><UNIQUEREF>GIOH10II2J</UNIQUEREF><DATETIME>15-09-2015:14:44:17:999</DATETIME><HASH>aebd69e9db6e4b0db7ecbae79a2970a0</HASH></REFUNDRESPONSE>' + end + + def failed_refund_response + '<?xml version="1.0" encoding="UTF-8"?> +<ERROR><ERRORSTRING>cvc-minLength-valid: Value &apos;&apos; with length = &apos;0&apos; is not facet-valid with respect to minLength &apos;10&apos; for type &apos;UID&apos;.</ERRORSTRING></ERROR>' + end + + def successful_void_response + end + + def successful_store_response + '<?xml version="1.0" encoding="UTF-8"?> +<SECURECARDREGISTRATIONRESPONSE><MERCHANTREF>146304412401</MERCHANTREF><CARDREFERENCE>2967530956419033</CARDREFERENCE><DATETIME>12-05-2016:10:08:46:269</DATETIME><HASH>b2e497d14014ad9f4770edbf7716435e</HASH></SECURECARDREGISTRATIONRESPONSE>' + end + + def failed_store_response + '<?xml version="1.0" encoding="UTF-8"?> +<ERROR><ERRORCODE>E11</ERRORCODE><ERRORSTRING>INVALID CARDEXPIRY</ERRORSTRING></ERROR>' + end + + def successful_unstore_response + '<?xml version="1.0" encoding="UTF-8"?> +<SECURECARDREMOVALRESPONSE><MERCHANTREF>146304412401</MERCHANTREF><DATETIME>12-05-2016:10:08:48:399</DATETIME><HASH>7f755e185be8066a535699755f709646</HASH></SECURECARDREMOVALRESPONSE>' + end + + def failed_unstore_response + '<?xml version="1.0" encoding="UTF-8"?> +<ERROR><ERRORCODE>E04</ERRORCODE><ERRORSTRING>INVALID REFERENCE DETAILS</ERRORSTRING></ERROR>' + end + + def failed_void_response + '<?xml version="1.0" encoding="UTF-8"?> +<ERROR><ERRORSTRING>cvc-elt.1: Cannot find the declaration of element &apos;VOID&apos;.</ERRORSTRING></ERROR>' + end +end diff --git a/test/unit/gateways/worldpay_online_payments_test.rb b/test/unit/gateways/worldpay_online_payments_test.rb new file mode 100644 index 00000000000..697ac11aba0 --- /dev/null +++ b/test/unit/gateways/worldpay_online_payments_test.rb @@ -0,0 +1,250 @@ +require 'test_helper' + +class WorldpayOnlinePaymentsTest < Test::Unit::TestCase + def setup + @gateway = WorldpayOnlinePaymentsGateway.new(fixtures(:worldpay_online_payments)) + + @amount = 1000 + @credit_card = credit_card('4444333322221111') + @declined_card = credit_card('4242424242424242') + @options = { + order_id: '1', + currency: 'GBP', + billing_address: address, + description: 'Test Purchase' + } + + @token = 'TEST_RU_8fcc4f2f-8c0d-483d-a0a3-eaad7356623e' + @intoken = '_TEST_RU_8fcc4f2f-8c0d-483d-a0a3-eaad7356623e_' + + @orderCode = 'e69b5445-2a46-4f2c-b67d-7e1e95bd00a5' + @inorderCode = '_e69b5445-2a46-4f2c-b67d-7e1e95bd00a5_' + end + + def test_successful_purchase + @gateway.expects(:ssl_request).returns(successful_purchase_response) + @gateway.expects(:ssl_request).returns(successful_token_response) + response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal 'SUCCESS', response.message + end + + def test_failed_purchase + @gateway.expects(:ssl_request).returns(failed_purchase_response) + response = @gateway.purchase(@amount, @declined_card, @options) + assert_failure response + assert_not_equal 'SUCCESS', response.message + end + + def test_successful_authorize_and_capture + @gateway.expects(:ssl_request).returns(successful_authorize_response) + @gateway.expects(:ssl_request).returns(successful_token_response) + authorize = @gateway.authorize(@amount, @credit_card, @options) + assert_success authorize + + @gateway.expects(:ssl_request).returns(successful_capture_response) + assert capture = @gateway.capture(@amount-1, authorize.authorization) + assert_success capture + end + + def test_failed_authorize_and_capture + @gateway.expects(:ssl_request).returns(failed_authorize_response) + authorize = @gateway.authorize(@amount, @declined_card, @options) + assert_failure authorize + + @gateway.expects(:ssl_request).returns(failed_capture_response) + assert capture = @gateway.capture(@amount, authorize.authorization) + assert_failure capture + assert_not_equal 'SUCCESS', capture.message + end + + def test_failed_authorize + @gateway.expects(:ssl_request).returns(failed_authorize_response) + response = @gateway.authorize(@amount, @declined_card, @options) + assert_failure response + end + + def test_partial_capture + @gateway.expects(:ssl_request).returns(successful_authorize_response) + @gateway.expects(:ssl_request).returns(successful_token_response) + authorize = @gateway.authorize(@amount, @credit_card, @options) + assert_success authorize + + @gateway.expects(:ssl_request).returns(successful_capture_response) + assert capture = @gateway.capture(@amount-1, authorize.authorization) + assert_success capture + end + + def test_failed_capture + @gateway.expects(:ssl_request).returns(failed_capture_response) + response = @gateway.capture(nil, '') + assert_failure response + end + + def test_successful_refund + @gateway.expects(:ssl_request).returns(successful_purchase_response) + @gateway.expects(:ssl_request).returns(successful_token_response) + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + + @gateway.expects(:ssl_request).returns(successful_refund_response) + assert refund = @gateway.refund(nil, purchase.authorization) + assert_success refund + assert_match %r{SUCCESS}, refund.message + end + + def test_failed_refund + @gateway.expects(:ssl_request).returns(failed_refund_response) + refund = @gateway.refund(nil, '') + assert_failure refund + assert_not_match %r{SUCCESS}, refund.message + end + + def test_failed_double_refund + @gateway.expects(:ssl_request).returns(successful_purchase_response) + @gateway.expects(:ssl_request).returns(successful_token_response) + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + + @gateway.expects(:ssl_request).returns(failed_refund_response) + assert refund = @gateway.refund(nil, purchase.authorization) + assert_failure refund + + @gateway.expects(:ssl_request).returns(failed_refund_response) + assert refund = @gateway.refund(nil, purchase.authorization) + assert_failure refund + end + + def test_failed_partial_refund + @gateway.expects(:ssl_request).returns(successful_purchase_response) + @gateway.expects(:ssl_request).returns(successful_token_response) + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + + @gateway.expects(:ssl_request).returns(failed_refund_response) + assert refund = @gateway.refund(@amount, purchase.authorization) + assert_failure refund + end + + def test_successful_void + @gateway.expects(:ssl_request).returns(successful_authorize_response) + @gateway.expects(:ssl_request).returns(successful_token_response) + authorize = @gateway.authorize(@amount, @credit_card, @options) + assert_success authorize + + @gateway.expects(:ssl_request).returns(successful_authorize_response) + void = @gateway.void(authorize.authorization) + assert_success void + end + + def test_successful_order_void + @gateway.expects(:ssl_request).returns(successful_purchase_response) + @gateway.expects(:ssl_request).returns(successful_token_response) + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + + @gateway.expects(:ssl_request).returns(successful_void_response) + void = @gateway.void(purchase.authorization) + assert_success void + end + + def test_failed_void + @gateway.expects(:ssl_request).returns(failed_void_response) + void = @gateway.void('InvalidOrderCode') + assert_failure void + end + + def test_failed_double_void + @gateway.expects(:ssl_request).returns(successful_authorize_response) + @gateway.expects(:ssl_request).returns(successful_token_response) + authorize = @gateway.authorize(@amount, @credit_card, @options) + assert_success authorize + + @gateway.expects(:ssl_request).returns(successful_void_response) + void = @gateway.void(authorize.authorization) + assert_success void + + @gateway.expects(:ssl_request).returns(failed_void_response) + void = @gateway.void(authorize.authorization) + assert_failure void + end + + def test_successful_verify + @gateway.expects(:ssl_request).returns(successful_authorize_response) + @gateway.expects(:ssl_request).returns(successful_token_response) + response = @gateway.verify(@credit_card, @options) + assert_success response + end + + def test_failed_verify + @gateway.expects(:ssl_request).returns(failed_authorize_response) + response = @gateway.verify(@declined_card, @options) + assert_failure response + end + + def test_invalid_login + badgateway = WorldpayOnlinePaymentsGateway.new( + client_key: 'T_C_NOT_VALID', + service_key: 'T_S_NOT_VALID' + ) + + badgateway.expects(:ssl_request).returns(failed_login_response) + response = badgateway.purchase(@amount, @credit_card, @options) + assert_failure response + end + + private + + def successful_token_response + %({"token": "TEST_RU_8fcc4f2f-8c0d-483d-a0a3-eaad7356623e","paymentMethod": {"type": "ObfuscatedCard","name": "Longbob Longsen","expiryMonth": 10,"expiryYear": 2016,"cardType": "VISA","maskedCardNumber": "**** **** **** 1111"},"reusable": true}) + end + + def successful_authorize_response + %({"orderCode": "a46502d0-80ba-425b-a6db-2c57e9de91da","token": "TEST_RU_8fcc4f2f-8c0d-483d-a0a3-eaad7356623e","orderDescription": "Test Purchase","amount": 0,"currencyCode": "GBP","authorizeOnly": true,"paymentStatus": "AUTHORIZED","paymentResponse": {"type": "ObfuscatedCard","name": "Longbob Longsen","expiryMonth": 10,"expiryYear": 2016,"cardType": "VISA_CREDIT","maskedCardNumber": "**** **** **** 1111"},"environment": "TEST","authorizedAmount": 1000}) + end + + def failed_authorize_response + %({"httpStatusCode":400,"customCode":"BAD_REQUEST","message":"CVC can't be null/empty","description":"Some of request parameters are invalid, please check your request. For more information please refer to Json schema.","errorHelpUrl":null,"originalRequest":"{'reusable':false,'paymentMethod':{'type':'Card','name':'Example Name','expiryMonth':'**','expiryYear':'****','cardNumber':'**** **** **** 1111','cvc':''},'clientKey':'T_C_845d39f4-f33c-430c-8fca-ad89bf1e5810'}"} ) + end + + def successful_purchase_response + %({"orderCode": "0f2e0901-6de9-4bcc-85ec-832f4f62ca36","token": "TEST_RU_8fcc4f2f-8c0d-483d-a0a3-eaad7356623e","orderDescription": "Test Purchase","amount": 1000,"currencyCode": "GBP","paymentStatus": "SUCCESS","paymentResponse": {"type": "ObfuscatedCard","name": "Longbob Longsen","expiryMonth": 10,"expiryYear": 2016,"cardType": "VISA_CREDIT","maskedCardNumber": "**** **** **** 1111"},"environment": "TEST"}) + end + + def failed_purchase_response + %({"httpStatusCode": 401,"customCode": "UNAUTHORIZED","message": "Unauthorized Access","description": "Request can't be authorized, please validate your request","errorHelpUrl": null,"originalRequest": "{'token':'TEST_RU_8fcc4f2f-8c0d-483d-a0a3-eaad7356623e','orderDescription':'Test Purchase','amount':1000,'currencyCode':'GBP','name':'Longbob Longsen','billingAddress':{'address1':'address1','address2':'address2','address3':'address3','postalCode':'EEEE','city':'City','state':'State','countryCode':'GB'}}"}) + end + + def successful_capture_response + %({"orderCode": "a46502d0-80ba-425b-a6db-2c57e9de91da","token": "TEST_RU_8fcc4f2f-8c0d-483d-a0a3-eaad7356623e","orderDescription": "Test Purchase","amount": 999,"currencyCode": "GBP","authorizeOnly": true,"paymentStatus": "SUCCESS","paymentResponse": {"type": "ObfuscatedCard","name": "Longbob Longsen","expiryMonth": 10,"expiryYear": 2016,"cardType": "VISA_CREDIT","maskedCardNumber": "**** **** **** 1111"},"environment": "TEST","authorizedAmount": 1000}) + end + + def failed_capture_response + %({"httpStatusCode": 404,"customCode": "ORDER_NOT_FOUND","message": "Order with Order Code: 33ce1213-df9f-497e-aee8-b93f48865daa4c not found","description": "Order Code used with request does not exist, please check the orderCode.","errorHelpUrl": null,"originalRequest": "{'captureAmount':1000}"}) + end + + def successful_refund_response + end + + def failed_refund_response + %({"httpStatusCode": 400,"customCode": "BAD_REQUEST","message": "TEST Order: 8d5b48ed-71e8-475a-a648-245b011e5527 with state: REFUNDED cannot be Refunded","description": "Some of request parameters are invalid, please check your request. For more information please refer to Json schema.","errorHelpUrl": null,"originalRequest": "{}"}) + end + + def successful_void_response + end + + def failed_void_response + %({"httpStatusCode": 404,"customCode": "ORDER_NOT_FOUND","message": "Order with Order Code: InvalidOrderCode not found","description": "Order Code used with request does not exist, please check the orderCode.","errorHelpUrl": null,"originalRequest": "{'refundAmount':5}"}) + end + + def successful_verify_response + end + + def failed_verify_response + %({"httpStatusCode": 404,"customCode": "ORDER_NOT_FOUND","message": "Order with Order Code: InvalidOrderCode not found","description": "Order Code used with request does not exist, please check the orderCode.","errorHelpUrl": null,"originalRequest": "{'refundAmount':5}"}) + end + + def failed_login_response + %({"httpStatusCode": 401,"customCode": "UNAUTHORIZED","message": "Unauthorized Access","description": "Request can't be authorized, please validate your request","errorHelpUrl": null,"originalRequest": "{'token':'TEST_RU_ba2497be-8140-4be7-89ec-fa24eb6b01e8','orderDescription':'Test Purchase','amount':1000,'currencyCode':'GBP','name':'Longbob Longsen','billingAddress':{'address1':'address1','address2':'address2','address3':'address3','postalCode':'EEEE','city':'City','state':'State','countryCode':'GB'}}"}) + end +end diff --git a/test/unit/gateways/worldpay_test.rb b/test/unit/gateways/worldpay_test.rb index 1d1e9e5b29a..531ef493450 100644 --- a/test/unit/gateways/worldpay_test.rb +++ b/test/unit/gateways/worldpay_test.rb @@ -4,14 +4,28 @@ class WorldpayTest < Test::Unit::TestCase include CommStub def setup - @gateway = WorldpayGateway.new( - :login => 'testlogin', - :password => 'testpassword' - ) + @gateway = WorldpayGateway.new( + :login => 'testlogin', + :password => 'testpassword' + ) @amount = 100 @credit_card = credit_card('4242424242424242') + @token = '|99411111780163871111|shopper|59424549c291397379f30c5c082dbed8' + @elo_credit_card = credit_card('4514 1600 0000 0008', + :month => 10, + :year => 2020, + :first_name => 'John', + :last_name => 'Smith', + :verification_value => '737', + :brand => 'elo' + ) + @sodexo_voucher = credit_card('6060704495764400', brand: 'sodexo') @options = {:order_id => 1} + @store_options = { + customer: '59424549c291397379f30c5c082dbed8', + email: 'wow@example.com' + } end def test_successful_authorize @@ -24,10 +38,56 @@ def test_successful_authorize assert_equal 'R50704213207145707', response.authorization end + def test_successful_authorize_by_reference + response = stub_comms do + @gateway.authorize(@amount, @options[:order_id].to_s, @options) + end.check_request do |endpoint, data, headers| + assert_match(/payAsOrder/, data) + end.respond_with(successful_authorize_response) + assert_success response + assert_equal 'R50704213207145707', response.authorization + end + + def test_successful_reference_transaction_authorize_with_merchant_code + response = stub_comms do + @gateway.authorize(@amount, @options[:order_id].to_s, @options.merge({ merchant_code: 'testlogin2'})) + end.check_request do |endpoint, data, headers| + assert_match(/testlogin2/, data) + end.respond_with(successful_authorize_response) + assert_success response + assert_equal 'R50704213207145707', response.authorization + end + + def test_authorize_passes_ip_and_session_id + response = stub_comms do + @gateway.authorize(@amount, @credit_card, @options.merge(ip: '127.0.0.1', session_id: '0215ui8ib1')) + end.check_request do |endpoint, data, headers| + assert_match(/<session shopperIPAddress="127.0.0.1" id="0215ui8ib1"\/>/, data) + end.respond_with(successful_authorize_response) + assert_success response + end + + def test_authorize_passes_stored_credential_options + options = @options.merge( + stored_credential_usage: 'USED', + stored_credential_initiated_reason: 'UNSCHEDULED', + stored_credential_transaction_id: '000000000000020005060720116005060' + ) + response = stub_comms do + @gateway.authorize(@amount, @credit_card, options) + end.check_request do |endpoint, data, headers| + assert_match(/<storedCredentials usage\=\"USED\" merchantInitiatedReason\=\"UNSCHEDULED\"\>/, data) + assert_match(/<schemeTransactionIdentifier\>000000000000020005060720116005060\<\/schemeTransactionIdentifier\>/, data) + end.respond_with(successful_authorize_response) + assert_success response + end + def test_failed_authorize response = stub_comms do @gateway.authorize(@amount, @credit_card, @options) end.respond_with(failed_authorize_response) + assert_equal '7', response.error_code + assert_match 'Invalid payment details', response.message assert_failure response end @@ -38,9 +98,16 @@ def test_successful_purchase assert_success response end + def test_successful_purchase_with_elo + response = stub_comms do + @gateway.purchase(@amount, @credit_card, @options.merge(currency: 'BRL')) + end.respond_with(successful_authorize_with_elo_response, successful_capture_with_elo_response) + assert_success response + end + def test_purchase_passes_correct_currency response = stub_comms do - @gateway.purchase(@amount, @credit_card, @options.merge(:currency => 'CAD')) + @gateway.purchase(@amount, @credit_card, @options.merge(currency: 'CAD')) end.check_request do |endpoint, data, headers| assert_match(/CAD/, data) end.respond_with(successful_authorize_response, successful_capture_response) @@ -52,6 +119,8 @@ def test_purchase_authorize_fails @gateway.purchase(@amount, @credit_card, @options) end.respond_with(failed_authorize_response) assert_failure response + assert_equal '7', response.error_code + assert_match 'Invalid payment details', response.message assert_equal 1, response.responses.size end @@ -67,7 +136,7 @@ def test_purchase_does_not_run_inquiry end.respond_with(successful_capture_response) assert_success response - assert_equal %w(authorize capture), response.responses.collect{|e| e.params["action"]} + assert_equal(%w(authorize capture), response.responses.collect { |e| e.params['action'] }) end def test_successful_void @@ -75,8 +144,17 @@ def test_successful_void @gateway.void(@options[:order_id], @options) end.respond_with(successful_void_inquiry_response, successful_void_response) assert_success response - assert_equal "SUCCESS", response.message - assert_equal "924e810350efc21a989e0ac7727ce43b", response.params["cancel_received_order_code"] + assert_equal 'SUCCESS', response.message + assert_equal '924e810350efc21a989e0ac7727ce43b', response.params['cancel_received_order_code'] + end + + def test_successful_void_with_elo + response = stub_comms do + @gateway.void(@options[:order_id], @options) + end.respond_with(successful_void_inquiry_with_elo_response, successful_void_with_elo_response) + assert_success response + assert_equal 'SUCCESS', response.message + assert_equal '3a10f83fb9bb765488d0b3eb153879d7', response.params['cancel_received_order_code'] end def test_void_fails_unless_status_is_authorized @@ -87,6 +165,23 @@ def test_void_fails_unless_status_is_authorized assert_equal "A transaction status of 'AUTHORISED' is required.", response.message end + def test_void_using_order_id_embedded_with_token + response = stub_comms do + authorization = "#{@options[:order_id]}|99411111780163871111|shopper|59424549c291397379f30c5c082dbed8" + @gateway.void(authorization, @options) + end.check_request do |endpoint, data, headers| + if %r(<orderInquiry .*?>) =~ data + assert_tag_with_attributes('orderInquiry', {'orderCode' => @options[:order_id].to_s}, data) + end + if %r(<orderModification .*?>) =~ data + assert_tag_with_attributes('orderModification', {'orderCode' => @options[:order_id].to_s}, data) + end + end.respond_with(successful_void_inquiry_response, successful_void_response) + assert_success response + assert_equal 'SUCCESS', response.message + assert_equal '924e810350efc21a989e0ac7727ce43b', response.params['cancel_received_order_code'] + end + def test_successful_refund_for_captured_payment response = stub_comms do @gateway.refund(@amount, @options[:order_id], @options) @@ -99,7 +194,15 @@ def test_successful_refund_for_settled_payment @gateway.refund(@amount, @options[:order_id], @options) end.respond_with(successful_refund_inquiry_response('SETTLED'), successful_refund_response) assert_success response - assert_equal "05d9f8c622553b1df1fe3a145ce91ccf", response.params['refund_received_order_code'] + assert_equal '05d9f8c622553b1df1fe3a145ce91ccf', response.params['refund_received_order_code'] + end + + def test_successful_refund_for_settled_by_merchant_payment + response = stub_comms do + @gateway.refund(@amount, @options[:order_id], @options) + end.respond_with(successful_refund_inquiry_response('SETTLED_BY_MERCHANT'), successful_refund_response) + assert_success response + assert_equal '05d9f8c622553b1df1fe3a145ce91ccf', response.params['refund_received_order_code'] end def test_refund_fails_unless_status_is_captured @@ -107,7 +210,30 @@ def test_refund_fails_unless_status_is_captured @gateway.refund(@amount, @options[:order_id], @options) end.respond_with(failed_refund_inquiry_response, successful_refund_response) assert_failure response - assert_equal "A transaction status of 'CAPTURED' or 'SETTLED' is required.", response.message + assert_equal "A transaction status of 'CAPTURED' or 'SETTLED' or 'SETTLED_BY_MERCHANT' is required.", response.message + end + + def test_full_refund_for_unsettled_payment_forces_void + response = stub_comms do + @gateway.refund(@amount, @options[:order_id], @options.merge(force_full_refund_if_unsettled: true)) + end.respond_with(failed_refund_inquiry_response, failed_refund_inquiry_response, successful_void_response) + assert_success response + assert 'cancel', response.responses.last.params['action'] + end + + def test_refund_using_order_id_embedded_with_token + response = stub_comms do + authorization = "#{@options[:order_id]}|99411111780163871111|shopper|59424549c291397379f30c5c082dbed8" + @gateway.refund(@amount, authorization, @options) + end.check_request do |endpoint, data, headers| + if %r(<orderInquiry .*?>) =~ data + assert_tag_with_attributes('orderInquiry', {'orderCode' => @options[:order_id].to_s}, data) + end + if %r(<orderModification .*?>) =~ data + assert_tag_with_attributes('orderModification', {'orderCode' => @options[:order_id].to_s}, data) + end + end.respond_with(successful_refund_inquiry_response('CAPTURED'), successful_refund_response) + assert_success response end def test_capture @@ -118,6 +244,39 @@ def test_capture assert_success response end + def test_capture_using_order_id_embedded_with_token + response = stub_comms do + response = @gateway.authorize(@amount, @credit_card, @options) + authorization = "#{response.authorization}|99411111780163871111|shopper|59424549c291397379f30c5c082dbed8" + @gateway.capture(@amount, authorization, @options) + end.check_request do |endpoint, data, headers| + if %r(<orderModification .*?>) =~ data + assert_tag_with_attributes('orderModification', {'orderCode' => response.authorization}, data) + end + end.respond_with(successful_authorize_response, successful_capture_response) + assert_success response + end + + def test_successful_visa_credit + response = stub_comms do + @gateway.credit(@amount, @credit_card, @options) + end.check_request do |endpoint, data, headers| + assert_match(/<paymentDetails action="REFUND">/, data) + end.respond_with(successful_visa_credit_response) + assert_success response + assert_equal '3d4187536044bd39ad6a289c4339c41c', response.authorization + end + + def test_successful_mastercard_credit + response = stub_comms do + @gateway.credit(@amount, @credit_card, @options) + end.check_request do |endpoint, data, headers| + assert_match(/<paymentDetails action="REFUND">/, data) + end.respond_with(successful_mastercard_credit_response) + assert_success response + assert_equal 'f25257d251b81fb1fd9c210973c941ff', response.authorization + end + def test_description stub_comms do @gateway.authorize(@amount, @credit_card, @options) @@ -126,7 +285,7 @@ def test_description end.respond_with(successful_authorize_response) stub_comms do - @gateway.authorize(@amount, @credit_card, @options.merge(:description => "Something cool.")) + @gateway.authorize(@amount, @credit_card, @options.merge(description: 'Something cool.')) end.check_request do |endpoint, data, headers| assert_match %r(<description>Something cool.</description>), data end.respond_with(successful_authorize_response) @@ -140,7 +299,7 @@ def test_order_content end.respond_with(successful_authorize_response) stub_comms do - @gateway.authorize(@amount, @credit_card, @options.merge(:order_content => "Lots 'o' crazy <data> stuff.")) + @gateway.authorize(@amount, @credit_card, @options.merge(order_content: "Lots 'o' crazy <data> stuff.")) end.check_request do |endpoint, data, headers| assert_match %r(<orderContent>\s*<!\[CDATA\[Lots 'o' crazy <data> stuff\.\]\]>\s*</orderContent>), data end.respond_with(successful_authorize_response) @@ -153,7 +312,7 @@ def test_capture_time if data =~ /capture/ t = Time.now assert_tag_with_attributes 'date', - {'dayOfMonth' => t.day.to_s, 'month' => t.month.to_s, 'year' => t.year.to_s}, + {'dayOfMonth' => t.day.to_s, 'month' => t.month.to_s, 'year' => t.year.to_s}, data end end.respond_with(successful_inquiry_response, successful_capture_response) @@ -164,20 +323,51 @@ def test_amount_handling @gateway.authorize(100, @credit_card, @options) end.check_request do |endpoint, data, headers| assert_tag_with_attributes 'amount', - {'value' => '100', 'exponent' => '2', 'currencyCode' => 'GBP'}, + {'value' => '100', 'exponent' => '2', 'currencyCode' => 'GBP'}, + data + end.respond_with(successful_authorize_response) + end + + def test_currency_exponent_handling + stub_comms do + @gateway.authorize(10000, @credit_card, @options.merge(currency: :JPY)) + end.check_request do |endpoint, data, headers| + assert_tag_with_attributes 'amount', + {'value' => '100', 'exponent' => '0', 'currencyCode' => 'JPY'}, + data + end.respond_with(successful_authorize_response) + + stub_comms do + @gateway.authorize(10000, @credit_card, @options.merge(currency: :OMR)) + end.check_request do |endpoint, data, headers| + assert_tag_with_attributes 'amount', + {'value' => '10000', 'exponent' => '3', 'currencyCode' => 'OMR'}, data end.respond_with(successful_authorize_response) end def test_address_handling stub_comms do - @gateway.authorize(100, @credit_card, @options.merge(:billing_address => address)) + @gateway.authorize(100, @credit_card, @options.merge(billing_address: address)) + end.check_request do |endpoint, data, headers| + assert_match %r(<firstName>Jim</firstName>), data + assert_match %r(<lastName>Smith</lastName>), data + assert_match %r(<address1>456 My Street</address1>), data + assert_match %r(<address2>Apt 1</address2>), data + assert_match %r(<postalCode>K1C2N6</postalCode>), data + assert_match %r(<city>Ottawa</city>), data + assert_match %r(<state>ON</state>), data + assert_match %r(<countryCode>CA</countryCode>), data + assert_match %r(<telephoneNumber>\(555\)555-5555</telephoneNumber>), data + end.respond_with(successful_authorize_response) + + stub_comms do + @gateway.authorize(100, @credit_card, @options.merge(billing_address: address.with_indifferent_access)) end.check_request do |endpoint, data, headers| assert_match %r(<firstName>Jim</firstName>), data assert_match %r(<lastName>Smith</lastName>), data - assert_match %r(<street>My Street</street>), data - assert_match %r(<houseNumber>1234</houseNumber>), data - assert_match %r(<houseName>Apt 1</houseName>), data + assert_match %r(<address1>456 My Street</address1>), data + assert_match %r(<address2>Apt 1</address2>), data assert_match %r(<postalCode>K1C2N6</postalCode>), data assert_match %r(<city>Ottawa</city>), data assert_match %r(<state>ON</state>), data @@ -186,13 +376,12 @@ def test_address_handling end.respond_with(successful_authorize_response) stub_comms do - @gateway.authorize(100, @credit_card, @options.merge(:address => address)) + @gateway.authorize(100, @credit_card, @options.merge(address: address)) end.check_request do |endpoint, data, headers| assert_match %r(<firstName>Jim</firstName>), data assert_match %r(<lastName>Smith</lastName>), data - assert_match %r(<street>My Street</street>), data - assert_match %r(<houseNumber>1234</houseNumber>), data - assert_match %r(<houseName>Apt 1</houseName>), data + assert_match %r(<address1>456 My Street</address1>), data + assert_match %r(<address2>Apt 1</address2>), data assert_match %r(<postalCode>K1C2N6</postalCode>), data assert_match %r(<city>Ottawa</city>), data assert_match %r(<state>ON</state>), data @@ -201,53 +390,139 @@ def test_address_handling end.respond_with(successful_authorize_response) stub_comms do - @gateway.authorize(100, @credit_card, @options.merge(:address => {:address1 => "Anystreet", :country => "US"})) + @gateway.authorize(100, @credit_card, @options.merge(billing_address: { phone: '555-3323' })) + end.check_request do |endpoint, data, headers| + assert_no_match %r(firstName), data + assert_no_match %r(lastName), data + assert_no_match %r(address2), data + assert_match %r(<address1>N/A</address1>), data + assert_match %r(<city>N/A</city>), data + assert_match %r(<postalCode>0000</postalCode>), data + assert_match %r(<state>N/A</state>), data + assert_match %r(<countryCode>US</countryCode>), data + assert_match %r(<telephoneNumber>555-3323</telephoneNumber>), data + end.respond_with(successful_authorize_response) + end + + def test_no_address_specified + stub_comms do + @gateway.authorize(100, @credit_card, @options) end.check_request do |endpoint, data, headers| + assert_no_match %r(cardAddress), data + assert_no_match %r(address), data assert_no_match %r(firstName), data assert_no_match %r(lastName), data - assert_no_match %r(houseName), data + assert_no_match %r(address1), data + assert_no_match %r(address2), data + assert_no_match %r(postalCode), data assert_no_match %r(city), data + assert_no_match %r(state), data + assert_no_match %r(countryCode), data assert_no_match %r(telephoneNumber), data - assert_match %r(<street>Anystreet</street>), data + end.respond_with(successful_authorize_response) + end + + def test_address_with_parts_unspecified + address_with_nils = { address1: nil, city: ' ', state: nil, zip: ' ', + country: nil, phone: '555-3323' } + + stub_comms do + @gateway.authorize(100, @credit_card, @options.merge(billing_address: address_with_nils)) + end.check_request do |endpoint, data, headers| + assert_no_match %r(firstName), data + assert_no_match %r(lastName), data + assert_no_match %r(address2), data + assert_match %r(<address1>N/A</address1>), data + assert_match %r(<city>N/A</city>), data assert_match %r(<postalCode>0000</postalCode>), data assert_match %r(<state>N/A</state>), data + assert_match %r(<countryCode>US</countryCode>), data + assert_match %r(<telephoneNumber>555-3323</telephoneNumber>), data + end.respond_with(successful_authorize_response) + end + + def test_email + stub_comms do + @gateway.authorize(100, @credit_card, @options.merge(email: 'eggcellent@example.com')) + end.check_request do |endpoint, data, headers| + assert_match %r(<shopperEmailAddress>eggcellent@example.com</shopperEmailAddress>), data + end.respond_with(successful_authorize_response) + + stub_comms do + @gateway.authorize(100, @credit_card, @options) + end.check_request do |endpoint, data, headers| + assert_no_match %r(shopperEmailAddress), data + end.respond_with(successful_authorize_response) + end + + def test_instalments + stub_comms do + @gateway.purchase(100, @credit_card, @options.merge(instalments: 3)) + end.check_request do |endpoint, data, headers| + unless /<capture>/ =~ data + assert_match %r(<instalments>3</instalments>), data + assert_no_match %r(cpf), data + end + end.respond_with(successful_authorize_response, successful_capture_response) + + stub_comms do + @gateway.purchase(100, @credit_card, @options.merge(instalments: 3, cpf: 12341234)) + end.check_request do |endpoint, data, headers| + unless /<capture>/ =~ data + assert_match %r(<instalments>3</instalments>), data + assert_match %r(<cpf>12341234</cpf>), data + end + end.respond_with(successful_authorize_response, successful_capture_response) + end + + def test_ip + stub_comms do + @gateway.authorize(100, @credit_card, @options.merge(ip: '192.137.11.44')) + end.check_request do |endpoint, data, headers| + assert_match %r(<session shopperIPAddress="192.137.11.44"/>), data + end.respond_with(successful_authorize_response) + + stub_comms do + @gateway.authorize(100, @credit_card, @options) + end.check_request do |endpoint, data, headers| + assert_no_match %r(<session), data end.respond_with(successful_authorize_response) end def test_parsing response = stub_comms do - @gateway.authorize(100, @credit_card, @options.merge(:address => {:address1 => "123 Anystreet", :country => "US"})) + @gateway.authorize(100, @credit_card, @options.merge(address: {address1: '123 Anystreet', country: 'US'})) end.respond_with(successful_authorize_response) assert_equal({ - "action"=>"authorize", - "amount_currency_code"=>"HKD", - "amount_debit_credit_indicator"=>"credit", - "amount_exponent"=>"2", - "amount_value"=>"15000", - "avs_result_code_description"=>"UNKNOWN", - "balance"=>true, - "balance_account_type"=>"IN_PROCESS_AUTHORISED", - "card_number"=>"4111********1111", - "cvc_result_code_description"=>"UNKNOWN", - "last_event"=>"AUTHORISED", - "order_status"=>true, - "order_status_order_code"=>"R50704213207145707", - "payment"=>true, - "payment_method"=>"VISA-SSL", - "payment_service"=>true, - "payment_service_merchant_code"=>"XXXXXXXXXXXXXXX", - "payment_service_version"=>"1.4", - "reply"=>true, - "risk_score_value"=>"1", + 'action'=>'authorize', + 'amount_currency_code'=>'HKD', + 'amount_debit_credit_indicator'=>'credit', + 'amount_exponent'=>'2', + 'amount_value'=>'15000', + 'avs_result_code_description'=>'UNKNOWN', + 'balance'=>true, + 'balance_account_type'=>'IN_PROCESS_AUTHORISED', + 'card_number'=>'4111********1111', + 'cvc_result_code_description'=>'UNKNOWN', + 'last_event'=>'AUTHORISED', + 'order_status'=>true, + 'order_status_order_code'=>'R50704213207145707', + 'payment'=>true, + 'payment_method'=>'VISA-SSL', + 'payment_service'=>true, + 'payment_service_merchant_code'=>'XXXXXXXXXXXXXXX', + 'payment_service_version'=>'1.4', + 'reply'=>true, + 'risk_score_value'=>'1', }, response.params) end def test_auth - response = stub_comms do + stub_comms do @gateway.authorize(100, @credit_card, @options) end.check_request do |endpoint, data, headers| - assert_equal "Basic dGVzdGxvZ2luOnRlc3RwYXNzd29yZA==", headers['Authorization'] + assert_equal 'Basic dGVzdGxvZ2luOnRlc3RwYXNzd29yZA==', headers['Authorization'] end.respond_with(successful_authorize_response) end @@ -260,12 +535,12 @@ def test_request_respects_test_mode_on_gateway_instance :test => true ) - response = stub_comms do + stub_comms do @gateway.purchase(@amount, @credit_card, @options) end.check_request do |endpoint, data, headers| assert_equal WorldpayGateway.test_url, endpoint end.respond_with(successful_authorize_response, successful_capture_response) - + ensure ActiveMerchant::Billing::Base.mode = :test end @@ -281,14 +556,296 @@ def test_refund_amount_contains_debit_credit_indicator assert_success response end + def test_successful_verify + @gateway.expects(:ssl_post).times(2).returns(successful_authorize_response, successful_void_response) + + response = @gateway.verify(@credit_card, @options) + assert_success response + end + + def test_successful_verify_with_elo + @gateway.expects(:ssl_post).times(2).returns(successful_authorize_with_elo_response, successful_void_with_elo_response) + + response = @gateway.verify(@elo_credit_card, @options.merge(currency: 'BRL')) + assert_success response + end + + def test_successful_verify_with_failed_void + @gateway.expects(:ssl_post).times(2).returns(successful_authorize_response, failed_void_response) + + response = @gateway.verify(@credit_card, @options) + assert_success response + end + + def test_failed_verify + @gateway.expects(:ssl_post).returns(failed_authorize_response) + + response = @gateway.verify(@credit_card, @options) + assert_failure response + end + + def test_3ds_name_coersion + @options[:execute_threed] = true + response = stub_comms do + @gateway.purchase(@amount, @credit_card, @options) + end.check_request do |endpoint, data, headers| + if /<submit>/ =~ data + assert_match %r{<cardHolderName>3D</cardHolderName>}, data + end + end.respond_with(successful_authorize_response, successful_capture_response) + assert_success response + end + + def test_transcript_scrubbing + assert_equal scrubbed_transcript, @gateway.scrub(transcript) + end + + def test_3ds_version_1_request + stub_comms do + @gateway.authorize(@amount, @credit_card, @options.merge(three_d_secure_option(version: '1.0.2', xid: 'xid'))) + end.check_request do |endpoint, data, headers| + assert_match %r{<paymentService version="1.4" merchantCode="testlogin">}, data + assert_match %r{<eci>eci</eci>}, data + assert_match %r{<cavv>cavv</cavv>}, data + assert_match %r{<xid>xid</xid>}, data + assert_match %r{<threeDSVersion>1.0.2</threeDSVersion>}, data + end.respond_with(successful_authorize_response) + end + + def test_3ds_version_2_request + stub_comms do + @gateway.authorize(@amount, @credit_card, @options.merge(three_d_secure_option(version: '2.1.0', ds_transaction_id: 'ds_transaction_id'))) + end.check_request do |endpoint, data, headers| + assert_match %r{<paymentService version="1.4" merchantCode="testlogin">}, data + assert_match %r{<eci>eci</eci>}, data + assert_match %r{<cavv>cavv</cavv>}, data + assert_match %r{<dsTransactionId>ds_transaction_id</dsTransactionId>}, data + assert_match %r{<threeDSVersion>2.1.0</threeDSVersion>}, data + end.respond_with(successful_authorize_response) + end + + def test_failed_authorize_with_unknown_card + response = stub_comms do + @gateway.authorize(@amount, @sodexo_voucher, @options) + end.respond_with(failed_with_unknown_card_response) + assert_failure response + assert_equal '5', response.error_code + end + + def test_failed_purchase_with_unknown_card + response = stub_comms do + @gateway.purchase(@amount, @sodexo_voucher, @options) + end.respond_with(failed_with_unknown_card_response) + assert_failure response + assert_equal '5', response.error_code + end + + def test_failed_verify_with_unknown_card + @gateway.expects(:ssl_post).returns(failed_with_unknown_card_response) + + response = @gateway.verify(@sodexo_voucher, @options) + assert_failure response + assert_equal '5', response.error_code + end + + def test_successful_store + response = stub_comms do + @gateway.store(@credit_card, @store_options) + end.check_request do |endpoint, data, headers| + assert_match %r(<paymentTokenCreate>), data + assert_match %r(<createToken/?>), data + assert_match %r(<authenticatedShopperID>59424549c291397379f30c5c082dbed8</authenticatedShopperID>), data + assert_match %r(4242424242424242), data + assert_no_match %r(<order>), data + assert_no_match %r(<paymentDetails>), data + assert_no_match %r(<VISA-SSL>), data + end.respond_with(successful_store_response) + + assert_success response + assert_equal 'SUCCESS', response.message + assert_equal @token, response.authorization + end + + def test_successful_authorize_using_token + response = stub_comms do + @gateway.authorize(@amount, @token, @options) + end.check_request do |endpoint, data, headers| + assert_tag_with_attributes('order', {'orderCode' => @options[:order_id].to_s}, data) + assert_match %r(<authenticatedShopperID>59424549c291397379f30c5c082dbed8</authenticatedShopperID>), data + assert_tag_with_attributes 'TOKEN-SSL', {'tokenScope' => 'shopper'}, data + assert_match %r(<paymentTokenID>99411111780163871111</paymentTokenID>), data + end.respond_with(successful_authorize_response) + + assert_success response + assert_equal 'SUCCESS', response.message + end + + def test_authorize_with_token_includes_shopper_using_minimal_options + stub_comms do + @gateway.authorize(@amount, @token, @options) + end.check_request do |endpoint, data, headers| + assert_match %r(<authenticatedShopperID>59424549c291397379f30c5c082dbed8</authenticatedShopperID>), data + end.respond_with(successful_authorize_response) + end + + def test_successful_purchase_using_token + response = stub_comms do + @gateway.purchase(@amount, @token, @options) + end.check_request do |endpoint, data, headers| + if %r(<order .*?>) =~ data + assert_tag_with_attributes('order', {'orderCode' => @options[:order_id].to_s}, data) + end + end.respond_with(successful_authorize_response, successful_capture_response) + + assert_success response + assert_equal 'SUCCESS', response.message + end + + def test_successful_verify_using_token + response = stub_comms do + @gateway.verify(@token, @options) + end.check_request do |endpoint, data, headers| + if %r(<order .*?>) =~ data + assert_tag_with_attributes('order', {'orderCode' => @options[:order_id].to_s}, data) + end + end.respond_with(successful_authorize_response, successful_void_response) + + assert_success response + assert_equal 'SUCCESS', response.message + end + + def test_successful_credit_using_token + response = stub_comms do + @gateway.credit(@amount, @token, @options) + end.check_request do |endpoint, data, headers| + assert_tag_with_attributes('order', {'orderCode' => @options[:order_id].to_s}, data) + assert_match(/<paymentDetails action="REFUND">/, data) + assert_match %r(<authenticatedShopperID>59424549c291397379f30c5c082dbed8</authenticatedShopperID>), data + assert_tag_with_attributes 'TOKEN-SSL', {'tokenScope' => 'shopper'}, data + assert_match '<paymentTokenID>99411111780163871111</paymentTokenID>', data + end.respond_with(successful_visa_credit_response) + + assert_success response + assert_equal 'SUCCESS', response.message + assert_equal '3d4187536044bd39ad6a289c4339c41c', response.authorization + end + + def test_failed_store + response = stub_comms do + @gateway.store(@credit_card, @store_options.merge(customer: '_invalidId')) + end.respond_with(failed_store_response) + + assert_failure response + assert_equal '2', response.error_code + assert_equal 'authenticatedShopperID cannot start with an underscore', response.message + end + + def test_store_should_raise_when_customer_not_present + assert_raises(ArgumentError) do + @gateway.store(@credit_card) + end + end + + def test_failed_authorize_using_token + response = stub_comms do + @gateway.authorize(@amount, @token, @options) + end.respond_with(failed_authorize_response_2) + + assert_failure response + assert_equal '5', response.error_code + assert_match %r{XML failed validation: Invalid payment details : Card number not recognised:}, response.message + end + + def test_failed_verify_using_token + response = stub_comms do + @gateway.verify(@token, @options) + end.respond_with(failed_authorize_response_2) + + assert_failure response + assert_equal '5', response.error_code + assert_match %r{XML failed validation: Invalid payment details : Card number not recognised:}, response.message + end + + def test_authorize_order_id_not_overridden_by_order_id_of_token + @token = 'wrong_order_id|99411111780163871111|shopper|59424549c291397379f30c5c082dbed8' + response = stub_comms do + @gateway.authorize(@amount, @token, @options) + end.check_request do |endpoint, data, headers| + assert_tag_with_attributes('order', {'orderCode' => @options[:order_id].to_s}, data) + assert_match %r(<authenticatedShopperID>59424549c291397379f30c5c082dbed8</authenticatedShopperID>), data + assert_tag_with_attributes 'TOKEN-SSL', {'tokenScope' => 'shopper'}, data + assert_match %r(<paymentTokenID>99411111780163871111</paymentTokenID>), data + end.respond_with(successful_authorize_response) + + assert_success response + assert_equal 'SUCCESS', response.message + end + + def test_purchase_order_id_not_overridden_by_order_id_of_token + @token = 'wrong_order_id|99411111780163871111|shopper|59424549c291397379f30c5c082dbed8' + response = stub_comms do + @gateway.purchase(@amount, @token, @options) + end.check_request do |endpoint, data, headers| + if %r(<order .*?>) =~ data + assert_tag_with_attributes('order', {'orderCode' => @options[:order_id].to_s}, data) + end + end.respond_with(successful_authorize_response, successful_capture_response) + + assert_success response + assert_equal 'SUCCESS', response.message + end + + def test_verify_order_id_not_overridden_by_order_id_of_token + @token = 'wrong_order_id|99411111780163871111|shopper|59424549c291397379f30c5c082dbed8' + response = stub_comms do + @gateway.verify(@token, @options) + end.check_request do |endpoint, data, headers| + if %r(<order .*?>) =~ data + assert_tag_with_attributes('order', {'orderCode' => @options[:order_id].to_s}, data) + end + end.respond_with(successful_authorize_response, successful_void_response) + + assert_success response + assert_equal 'SUCCESS', response.message + end + + def test_credit_order_id_not_overridden_by_order_if_of_token + @token = 'wrong_order_id|99411111780163871111|shopper|59424549c291397379f30c5c082dbed8' + response = stub_comms do + @gateway.credit(@amount, @token, @options) + end.check_request do |endpoint, data, headers| + assert_tag_with_attributes('order', {'orderCode' => @options[:order_id].to_s}, data) + assert_match(/<paymentDetails action="REFUND">/, data) + assert_match %r(<authenticatedShopperID>59424549c291397379f30c5c082dbed8</authenticatedShopperID>), data + assert_tag_with_attributes 'TOKEN-SSL', {'tokenScope' => 'shopper'}, data + assert_match '<paymentTokenID>99411111780163871111</paymentTokenID>', data + end.respond_with(successful_visa_credit_response) + + assert_success response + assert_equal 'SUCCESS', response.message + assert_equal '3d4187536044bd39ad6a289c4339c41c', response.authorization + end + + private + def assert_tag_with_attributes(tag, attributes, string) - assert(m = %r(<#{tag}([^>]+)/>).match(string)) + assert(m = %r(<#{tag}([^>]+)/?>).match(string)) attributes.each do |attribute, value| assert_match %r(#{attribute}="#{value}"), m[1] end end - private + def three_d_secure_option(version:, xid: nil, ds_transaction_id: nil) + { + three_d_secure: { + eci: 'eci', + cavv: 'cavv', + xid: xid, + ds_transaction_id: ds_transaction_id, + version: version, + } + } + end def successful_authorize_response <<-RESPONSE @@ -333,6 +890,21 @@ def failed_authorize_response RESPONSE end + # main variation is that CDATA is nested inside <error> w/o newlines; also a + # more recent captured response from remote tests where the reply is + # contained the error directly (no <orderStatus>) + def failed_authorize_response_2 + <<-RESPONSE + <?xml version="1.0" encoding="UTF-8"?> + <!DOCTYPE paymentService PUBLIC "-//WorldPay//DTD WorldPay PaymentService v1//EN" "http://dtd.worldpay.com/paymentService_v1.dtd"> + <paymentService version="1.4" merchantCode="SPREEDLY"> + <reply> + <error code="5"><![CDATA[XML failed validation: Invalid payment details : Card number not recognised: 606070******4400]]></error> + </reply> + </paymentService> + RESPONSE + end + def successful_capture_response <<-RESPONSE <?xml version="1.0" encoding="UTF-8"?> @@ -350,6 +922,86 @@ def successful_capture_response RESPONSE end + def successful_authorize_with_elo_response + <<-RESPONSE + <?xml version="1.0" encoding="UTF-8"?> + <!DOCTYPE paymentService PUBLIC "-//WorldPay//DTD WorldPay PaymentService v1//EN" "http://dtd.worldpay.com/paymentService_v1.dtd"> + <paymentService version="1.4" merchantCode="SPREEDLY"> + <reply> + <orderStatus orderCode="9fe31a79de5f6aa3ce1ed7bea7edbf42"> + <payment> + <paymentMethod>ELO-SSL</paymentMethod> + <amount value="100" currencyCode="BRL" exponent="2" debitCreditIndicator="credit" /> + <lastEvent>AUTHORISED</lastEvent> + <CVCResultCode description="C" /> + <AVSResultCode description="H" /> + <balance accountType="IN_PROCESS_AUTHORISED"> + <amount value="100" currencyCode="BRL" exponent="2" debitCreditIndicator="credit" /> + </balance> + <cardNumber>4514********0008</cardNumber> + <riskScore value="21" /> + </payment> + </orderStatus> + </reply> + </paymentService> + RESPONSE + end + + def successful_capture_with_elo_response + <<-RESPONSE + <?xml version="1.0" encoding="UTF-8"?> + <!DOCTYPE paymentService PUBLIC "-//WorldPay//DTD WorldPay PaymentService v1//EN" "http://dtd.worldpay.com/paymentService_v1.dtd"> + <paymentService version="1.4" merchantCode="SPREEDLY"> + <reply> + <ok> + <captureReceived orderCode="9fe31a79de5f6aa3ce1ed7bea7edbf42"> + <amount value="100" currencyCode="BRL" exponent="2" debitCreditIndicator="credit" /> + </captureReceived> + </ok> + </reply> + </paymentService> + RESPONSE + end + + def successful_void_inquiry_with_elo_response + <<-RESPONSE + <?xml version="1.0" encoding="UTF-8"?> + <!DOCTYPE paymentService PUBLIC "-//WorldPay//DTD WorldPay PaymentService v1//EN" "http://dtd.worldpay.com/paymentService_v1.dtd"> + <paymentService version="1.4" merchantCode="SPREEDLY"> + <reply> + <orderStatus orderCode="eda0b101428892fdb32e2fc617a7f5e0"> + <payment> + <paymentMethod>ELO-SSL</paymentMethod> + <amount value="100" currencyCode="BRL" exponent="2" debitCreditIndicator="credit" /> + <lastEvent>AUTHORISED</lastEvent> + <CVCResultCode description="C" /> + <AVSResultCode description="H" /> + <balance accountType="IN_PROCESS_AUTHORISED"> + <amount value="100" currencyCode="BRL" exponent="2" debitCreditIndicator="credit" /> + </balance> + <cardNumber>4514********0008</cardNumber> + <riskScore value="21" /> + </payment> + </orderStatus> + </reply> + </paymentService> + RESPONSE + end + + def successful_void_with_elo_response + <<-RESPONSE + <?xml version="1.0" encoding="UTF-8"?> + <!DOCTYPE paymentService PUBLIC "-//WorldPay//DTD WorldPay PaymentService v1//EN" "http://dtd.worldpay.com/paymentService_v1.dtd"> + <paymentService version="1.4" merchantCode="SPREEDLY"> + <reply> + <ok> + <cancelReceived orderCode="3a10f83fb9bb765488d0b3eb153879d7" /> + </ok> + </reply> + </paymentService> + RESPONSE + end + def successful_inquiry_response <<-RESPONSE <?xml version="1.0" encoding="UTF-8"?> @@ -447,7 +1099,7 @@ def failed_void_inquiry_response RESPONSE end - def successful_refund_inquiry_response(last_event="CAPTURED") + def successful_refund_inquiry_response(last_event='CAPTURED') <<-RESPONSE <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE paymentService PUBLIC "-//Bibit//DTD Bibit PaymentService v1//EN" @@ -458,7 +1110,7 @@ def successful_refund_inquiry_response(last_event="CAPTURED") <payment> <paymentMethod>VISA-SSL</paymentMethod> <amount value="100" currencyCode="GBP" exponent="2" debitCreditIndicator="credit"/> - <lastEvent>#{ last_event }</lastEvent> + <lastEvent>#{last_event}</lastEvent> <CVCResultCode description="UNKNOWN"/> <AVSResultCode description="NOT SUPPLIED BY SHOPPER"/> <balance accountType="IN_PROCESS_AUTHORISED"> @@ -520,6 +1172,62 @@ def failed_refund_inquiry_response RESPONSE end + def failed_void_response + <<-REQUEST + <?xml version="1.0" encoding="UTF-8"?> + <!DOCTYPE paymentService PUBLIC "-//WorldPay//DTD WorldPay PaymentService v1//EN" "http://dtd.worldpay.com/paymentService_v1.dtd"> + <paymentService version="1.4" merchantCode="CHARGEBEEM1"> + <reply> + <orderStatus orderCode="non_existent_authorization"> + <error code="5"> + <![CDATA[Could not find payment for order]]> + </error> + </orderStatus> + </reply> + </paymentService> + REQUEST + end + + def successful_visa_credit_response + <<-RESPONSE + <?xml version="1.0" encoding="UTF-8"?> + <!DOCTYPE paymentService PUBLIC "-//WorldPay//DTD WorldPay PaymentService v1//EN" + "http://dtd.worldpay.com/paymentService_v1.dtd"> + <paymentService version="1.4" merchantCode="SPREEDLYCFT"> + <reply> + <ok> + <refundReceived orderCode="3d4187536044bd39ad6a289c4339c41c"> + <amount value="100" currencyCode="GBP" exponent="2" debitCreditIndicator="credit"/> + </refundReceived> + </ok> + </reply> + </paymentService> + RESPONSE + end + + def successful_mastercard_credit_response + <<~RESPONSE + <?xml version="1.0" encoding="UTF-8"?> + <!DOCTYPE paymentService PUBLIC "-//WorldPay//DTD WorldPay PaymentService v1//EN" + "http://dtd.worldpay.com/paymentService_v1.dtd"> + <paymentService version="1.4" merchantCode="YOUR_MERCHANT_CODE"> + <reply> + <orderStatus orderCode="f25257d251b81fb1fd9c210973c941ff\"> + <payment> + <paymentMethod>ECMC_DEBIT-SSL</paymentMethod> + <amount value="1110" currencyCode="GBP" exponent="2" debitCreditIndicator="credit"/> + <lastEvent>SENT_FOR_REFUND</lastEvent> + <AuthorisationId id="987654"/> + <balance accountType="IN_PROCESS_CAPTURED"> + <amount value="1110" currencyCode="GBP" exponent="2" debitCreditIndicator="debit"/> + </balance> + </payment> + </orderStatus> + </reply> + </paymentService> + RESPONSE + end + def sample_authorization_request <<-REQUEST <?xml version="1.0" encoding="UTF-8"?> @@ -542,7 +1250,7 @@ def sample_authorization_request <address> <firstName>Jim</firstName> <lastName>Smith</lastName> - <street>1234 My Street</street> + <street>456 My Street</street> <houseName>Apt 1</houseName> <postalCode>K1C2N6</postalCode> <city>Ottawa</city> @@ -565,4 +1273,135 @@ def sample_authorization_request </paymentService> REQUEST end + + def transcript + <<-TRANSCRIPT + <paymentService version="1.4" merchantCode="CHARGEBEEM1"> + <submit> + <order orderCode="4efd348dbe6708b9ec9c118322e0954f"> + <description>Purchase</description> + <amount value="100" currencyCode="GBP" exponent="2"/> + <paymentDetails> + <VISA-SSL> + <cardNumber>4111111111111111</cardNumber> + <expiryDate> + <date month="09" year="2016"/> + </expiryDate> + <cardHolderName>Longbob Longsen</cardHolderName> + <cvc>123</cvc> + <cardAddress> + <address> + <address1>N/A</address1> + <postalCode>0000</postalCode> + <city>N/A</city> + <state>N/A</state> + <countryCode>US</countryCode> + </address> + </cardAddress> + </VISA-SSL> + </paymentDetails> + <shopper> + <shopperEmailAddress>wow@example.com</shopperEmailAddress> + </shopper> + </order> + </submit> + </paymentService> + TRANSCRIPT + end + + def scrubbed_transcript + <<-TRANSCRIPT + <paymentService version="1.4" merchantCode="CHARGEBEEM1"> + <submit> + <order orderCode="4efd348dbe6708b9ec9c118322e0954f"> + <description>Purchase</description> + <amount value="100" currencyCode="GBP" exponent="2"/> + <paymentDetails> + <VISA-SSL> + <cardNumber>[FILTERED]</cardNumber> + <expiryDate> + <date month="09" year="2016"/> + </expiryDate> + <cardHolderName>Longbob Longsen</cardHolderName> + <cvc>[FILTERED]</cvc> + <cardAddress> + <address> + <address1>N/A</address1> + <postalCode>0000</postalCode> + <city>N/A</city> + <state>N/A</state> + <countryCode>US</countryCode> + </address> + </cardAddress> + </VISA-SSL> + </paymentDetails> + <shopper> + <shopperEmailAddress>wow@example.com</shopperEmailAddress> + </shopper> + </order> + </submit> + </paymentService> + TRANSCRIPT + end + + def failed_with_unknown_card_response + <<-RESPONSE + <?xml version="1.0" encoding="UTF-8"?> + <!DOCTYPE paymentService PUBLIC "-//WorldPay//DTD WorldPay PaymentService v1//EN" "http://dtd.worldpay.com/paymentService_v1.dtd"> + <paymentService version="1.4" merchantCode="SPREEDLY"> + <reply> + <error code="5"> + <![CDATA[XML failed validation: Invalid payment details : Card number not recognised: 606070******4400]]> + </error> + </reply> + </paymentService> + RESPONSE + end + + def successful_store_response + <<-RESPONSE + <?xml version="1.0"?> + <!DOCTYPE paymentService PUBLIC "-//WorldPay//DTD WorldPay PaymentService v1//EN" "http://dtd.worldpay.com/paymentService_v1.dtd"> + <paymentService version="1.4" merchantCode="SPREEDLY"> + <reply> + <token> + <authenticatedShopperID>59424549c291397379f30c5c082dbed8</authenticatedShopperID> + <tokenDetails tokenEvent="NEW"> + <paymentTokenID>99411111780163871111</paymentTokenID> + <paymentTokenExpiry> + <date dayOfMonth="30" month="05" year="2019" hour="22" minute="54" second="47"/> + </paymentTokenExpiry> + <tokenReason>Created token without payment on 2019-05-23</tokenReason> + </tokenDetails> + <paymentInstrument> + <cardDetails> + <expiryDate> + <date month="09" year="2020"/> + </expiryDate> + <cardHolderName><![CDATA[Longbob Longsen]]></cardHolderName> + <derived> + <cardBrand>VISA</cardBrand> + <cardSubBrand>VISA_CREDIT</cardSubBrand> + <issuerCountryCode>N/A</issuerCountryCode> + <obfuscatedPAN>4111********1111</obfuscatedPAN> + </derived> + </cardDetails> + </paymentInstrument> + </token> + </reply> + </paymentService> + RESPONSE + end + + def failed_store_response + <<-RESPONSE + <?xml version="1.0" encoding="UTF-8"?> + <!DOCTYPE paymentService PUBLIC "-//WorldPay//DTD WorldPay PaymentService v1//EN" "http://dtd.worldpay.com/paymentService_v1.dtd"> + <paymentService version="1.4" merchantCode="SPREEDLY"> + <reply> + <error code="2"><![CDATA[authenticatedShopperID cannot start with an underscore]]></error> + </reply> + </paymentService> + RESPONSE + end end diff --git a/test/unit/gateways/worldpay_us_test.rb b/test/unit/gateways/worldpay_us_test.rb new file mode 100644 index 00000000000..ee576daa591 --- /dev/null +++ b/test/unit/gateways/worldpay_us_test.rb @@ -0,0 +1,529 @@ +require 'test_helper' + +class WorldpayUsTest < Test::Unit::TestCase + include CommStub + + def setup + @gateway = WorldpayUsGateway.new( + acctid: 'acctid', + subid: 'subid', + merchantpin: 'merchantpin' + ) + + @credit_card = credit_card + @check = check + @amount = 100 + + @options = { + order_id: '1', + billing_address: address, + description: 'Store Purchase' + } + end + + def test_successful_purchase + response = stub_comms do + @gateway.purchase(@amount, @credit_card) + end.respond_with(successful_purchase_response) + + assert_success response + + assert_equal '353583515|252889136', response.authorization + assert response.test? + end + + def test_failed_purchase + @gateway.expects(:ssl_post).returns(failed_purchase_response) + + response = @gateway.purchase(@amount, @credit_card, @options) + assert_failure response + end + + def test_successful_echeck_purchase + response = stub_comms do + @gateway.purchase(@amount, @check) + end.respond_with(successful_echeck_purchase_response) + + assert_success response + + assert_equal '421414035|306588394', response.authorization + assert response.test? + end + + def test_failed_echeck_purchase + @gateway.expects(:ssl_post).returns(failed_echeck_purchase_response) + + response = @gateway.purchase(@amount, @check, @options) + assert_failure response + end + + def test_authorize_and_capture + response = stub_comms do + @gateway.authorize(@amount, @credit_card) + end.respond_with(successful_authorize_response) + + assert_success response + assert_equal '354275517|253394390', response.authorization + + capture = stub_comms do + @gateway.capture(@amount, response.authorization) + end.check_request do |endpoint, data, headers| + assert_match(/postonly=354275517/, data) + end.respond_with(successful_capture_response) + + assert_success capture + end + + def test_refund + response = stub_comms do + @gateway.purchase(@amount, @credit_card) + end.respond_with(successful_purchase_response) + + assert_success response + assert_equal '353583515|252889136', response.authorization + + refund = stub_comms do + @gateway.refund(@amount, response.authorization) + end.check_request do |endpoint, data, headers| + assert_match(/historykeyid=353583515/, data) + assert_match(/orderkeyid=252889136/, data) + end.respond_with(successful_refund_response) + + assert_success refund + end + + def test_void + response = stub_comms do + @gateway.purchase(@amount, @credit_card) + end.respond_with(successful_purchase_response) + + assert_success response + assert_equal '353583515|252889136', response.authorization + + refund = stub_comms do + @gateway.void(response.authorization) + end.check_request do |endpoint, data, headers| + assert_match(/historykeyid=353583515/, data) + assert_match(/orderkeyid=252889136/, data) + end.respond_with(successful_refund_response) + + assert_success refund + end + + def test_successful_verify + response = stub_comms do + @gateway.verify(@credit_card) + end.respond_with(successful_authorize_response, successful_void_response) + assert_success response + end + + def test_successful_verify_failed_void + response = stub_comms do + @gateway.verify(@credit_card, @options) + end.respond_with(successful_authorize_response, failed_void_response) + assert_success response + assert_equal 'Succeeded', response.message + end + + def test_unsuccessful_verify + response = stub_comms do + @gateway.verify(@credit_card, @options) + end.respond_with(failed_authorize_response, successful_void_response) + assert_failure response + assert response.message =~ /DECLINED/ + end + + def test_passing_cvv + stub_comms do + @gateway.purchase(@amount, @credit_card) + end.check_request do |endpoint, data, headers| + assert_match(/#{@credit_card.verification_value}/, data) + end.respond_with(successful_purchase_response) + end + + def test_passing_billing_address + stub_comms do + @gateway.purchase(@amount, @credit_card, :billing_address => address) + end.check_request do |endpoint, data, headers| + assert_match(/ci_billaddr1=456\+My\+Street/, data) + assert_match(/ci_billzip=K1C2N6/, data) + end.respond_with(successful_purchase_response) + end + + def test_passing_phone_number + stub_comms do + @gateway.purchase(@amount, @credit_card, :billing_address => address) + end.check_request do |endpoint, data, headers| + assert_match(/ci_phone=%28555%29555-5555/, data) + end.respond_with(successful_purchase_response) + end + + def test_passing_billing_address_without_phone + stub_comms do + @gateway.purchase(@amount, @credit_card, :billing_address => address(:phone => nil)) + end.check_request do |endpoint, data, headers| + assert_no_match(/udf3/, data) + end.respond_with(successful_purchase_response) + end + + def test_empty_response_fails + response = stub_comms do + @gateway.purchase(@amount, @credit_card) + end.respond_with(empty_purchase_response) + + assert_failure response + assert_equal 'Unable to read error message', response.message + end + + def test_backup_url + response = stub_comms(@gateway) do + @gateway.purchase(@amount, @credit_card, use_backup_url: true) + end.check_request do |endpoint, data, headers| + assert_equal WorldpayUsGateway.backup_url, endpoint + end.respond_with(successful_purchase_response) + assert_success response + end + + def test_scrub + assert @gateway.supports_scrubbing? + assert_equal @gateway.scrub(pre_scrubbed), post_scrubbed + assert_equal @gateway.scrub(pre_scrubbed_check), post_scrubbed_check + end + + private + + def successful_purchase_response + %( +<html> + <body> + <plaintext> +Accepted=SALE:016918:891::353583515::: +historyid=353583515 +orderid=252889136 +Accepted=SALE:016918:891::353583515::: +ACCOUNTNUMBER=444666xxxxxx7892 +authcode=016918 +AuthNo=SALE:016918:891::353583515::: +AVS_RESULT= +BALANCE= +BATCHNUMBER= +CVV2_RESULT= +DEBIT_TRACE_NUMBER= +ENTRYMETHOD=KEYED +historyid=353583515 +MERCHANT_DBA_ADDR=11121 Willows Road NE +MERCHANT_DBA_CITY=Redmond +MERCHANT_DBA_NAME=Merchant Partners +MERCHANT_DBA_PHONE=4254979909 +MERCHANT_DBA_STATE=WA +MERCHANTID=542929804946788 +orderid=252889136 +PAYTYPE=Visa +PRODUCT_DESCRIPTION= +Reason= +RECEIPT_FOOTER=Thank You +recurid=0 +refcode=353583515-016918 +result=1 +SEQUENCE_NUMBER=22818217 +Status=Accepted +SYSTEMAUDITTRACENUMBER=891 +TERMINALID=160551 +TRANSGUID=3178ed9f-4d03-4d29-98c0-1b203f52cfe1:374 +transid=22818217 +transresult=APPROVED + ) + end + + def failed_purchase_response + %( +<html> + <body> + <plaintext> +Declined=DECLINED:1101970001:Invalid Expiration Date: +historyid=354275106 +orderid=253393990 +ACCOUNTNUMBER=444666xxxxxx7892 +Declined=DECLINED:1101970001:Invalid Expiration Date: +ENTRYMETHOD=KEYED +historyid=354275106 +orderid=253393990 +PAYTYPE=Visa +rcode=1101970001 +Reason=DECLINED:1101970001:Invalid Expiration Date: +recurid=0 +result=0 +Status=Declined +transid=0 + ) + end + + def successful_authorize_response + %( +<html> + <body> + <plaintext> +Accepted=AUTH:070484:548::354275517::: +historyid=354275517 +orderid=253394390 +Accepted=AUTH:070484:548::354275517::: +ACCOUNTNUMBER=444666xxxxxx7892 +authcode=070484 +AuthNo=AUTH:070484:548::354275517::: +AVS_RESULT= +BALANCE= +BATCHNUMBER= +CVV2_RESULT= +DEBIT_TRACE_NUMBER= +ENTRYMETHOD=KEYED +historyid=354275517 +MERCHANT_DBA_ADDR=11121 Willows Road NE +MERCHANT_DBA_CITY=Redmond +MERCHANT_DBA_NAME=Merchant Partners +MERCHANT_DBA_PHONE=4254979909 +MERCHANT_DBA_STATE=WA +MERCHANTID=542929804946788 +orderid=253394390 +PAYTYPE=Visa +PRODUCT_DESCRIPTION= +Reason= +RECEIPT_FOOTER=Thank You +recurid=0 +refcode=354275517-070484 +result=1 +SEQUENCE_NUMBER=23067552 +Status=Accepted +SYSTEMAUDITTRACENUMBER=548 +TERMINALID=160551 +TRANSGUID=561a665f-12d2-4416-a153-c0def07b13c5:265 +transid=23067552 +transresult=APPROVED + ) + end + + def successful_echeck_purchase_response + %( +<html><body><plaintext> +Accepted=CHECKAUTH:421414035:::421414035::: +historyid=421414035 +orderid=306588394 +Accepted=CHECKAUTH:421414035:::421414035::: +ACCOUNTNUMBER=****8535 +authcode=421414035 +AuthNo=CHECKAUTH:421414035:::421414035::: +ENTRYMETHOD=KEYED +historyid=421414035 +MERCHANTORDERNUMBER=691831d72f862d0fe24c52420f7f6963 +orderid=306588394 +PAYTYPE=Check +recurid=0 +refcode=421414035-421414035 +result=1 +Status=Accepted +transid=0 + ) + end + + def failed_echeck_purchase_response + %( +<html><body><plaintext> +Declined=DECLINED:1102780001:Invalid Bank: +historyid=421428338 +orderid=306594834 +ACCOUNTNUMBER=****8535 +Declined=DECLINED:1102780001:Invalid Bank: +ENTRYMETHOD=KEYED +historyid=421428338 +MERCHANTORDERNUMBER=5e9e7e04267187992c959eb9a55c4017 +orderid=306594834 +PAYTYPE=Check +rcode=1102780001 +Reason=DECLINED:1102780001:Invalid Bank: +recurid=0 +result=0 +Status=Declined +transid=0 + ) + end + + def failed_authorize_response + %( +<html><body><plaintext> +Declined=DECLINED:0500870009:PICK UP CARD: +historyid=354468057 +orderid=253537576 +ACCOUNTNUMBER=400030xxxxxx2220 +Declined=DECLINED:0500870009:PICK UP CARD: +ENTRYMETHOD=KEYED +historyid=354468057 +MERCHANTORDERNUMBER=1 +orderid=253537576 +PAYTYPE=Visa +rcode=0500870009 +Reason=DECLINED:0500870009:PICK UP CARD: +recurid=0 +result=0 +Status=Declined +SYSTEMAUDITTRACENUMBER=652 +TRANSGUID=408e6eae-fc22-4117-bd22-92d51218c27c:546 +transid=23132495 + ) + end + + alias successful_capture_response successful_authorize_response + alias successful_refund_response successful_authorize_response + + def empty_purchase_response + %( + ) + end + + def successful_void_response + %( +<html><body><plaintext> +Accepted=VOID:001849:643::354467495::: +historyid=354467495 +orderid=253537232 +Accepted=VOID:001849:643::354467495::: +ACCOUNTNUMBER=444666xxxxxx7892 +authcode=001849 +AuthNo=VOID:001849:643::354467495::: +AVS_RESULT= +BALANCE= +BATCHNUMBER= +CVV2_RESULT= +DEBIT_TRACE_NUMBER= +ENTRYMETHOD=KEYED +historyid=354467495 +MERCHANT_DBA_ADDR=11121 Willows Road NE +MERCHANT_DBA_CITY=Redmond +MERCHANT_DBA_NAME=Merchant Partners +MERCHANT_DBA_PHONE=4254979909 +MERCHANT_DBA_STATE=WA +MERCHANTID=542929804946788 +MERCHANTORDERNUMBER=1 +orderid=253537232 +PAYTYPE=Visa +PRODUCT_DESCRIPTION= +Reason= +RECEIPT_FOOTER=Thank You +recurid=0 +refcode=354467495-001849 +result=1 +SEQUENCE_NUMBER=23132246 +Status=Accepted +SYSTEMAUDITTRACENUMBER=643 +TERMINALID=160551 +TRANSGUID=15681fd7-b3a8-48b1-90be-9857ab426ca4:265 +transid=23132246 +transresult=APPROVED + ) + end + + def failed_void_response + %( +<html><body><plaintext> +Declined=DECLINED:3101680001:Invalid acct type: +historyid= +orderid= +Declined=DECLINED:3101680001:Invalid acct type: +rcode=3101680001 +Reason=DECLINED:3101680001:Invalid acct type: +result=0 +Status=Declined +transid=0 + ) + end + + def pre_scrubbed + <<-EOS +opening connection to trans.worldpay.us:443... +opened +starting SSL for trans.worldpay.us:443... +SSL established +<- "POST /cgi-bin/process.cgi HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: trans.worldpay.us\r\nContent-Length: 425\r\n\r\n" +<- "acctid=MPNAB&action=ns_quicksale_cc&amount=1.00&ccname=Longbob+Longsen&ccnum=4446661234567892&ci_billaddr1=456+My+Street&ci_billaddr2=Apt+1&ci_billcity=Ottawa&ci_billcountry=CA&ci_billstate=ON&ci_billzip=K1C2N6&ci_companyname=Widgets+Inc&ci_email=&ci_ipaddress=&ci_phone=%28555%29555-5555&currencycode=USD&cvv2=987&expmon=09&expyear=2019&merchantordernumber=67f4f20082e79684f036f25dafe96304&merchantpin=1234567890&subid=SPREE" +-> "HTTP/1.1 200 OK\r\n" +-> "Date: Tue, 13 Feb 2018 19:28:27 GMT\r\n" +-> "Server: Apache\r\n" +-> "X-Frame-Options: SAMEORIGIN\r\n" +-> "Content-Type: text/html;charset=ISO-8859-1\r\n" +-> "Content-Length: 962\r\n" +-> "Connection: close\r\n" +-> "\r\n" +reading 962 bytes... +-> "<html><body><plaintext>\r\nAccepted=SALE:036586:477::919067116:N::N\r\nhistoryid=919067116\r\norderid=722189706\r\nAccepted=SALE:036586:477::919067116:N::N\r\nACCOUNTNUMBER=************7892\r\nACCTID=MPNAB\r\nauthcode=036586\r\nAuthNo=SALE:036586:477::919067116:N::N\r\nAVS_RESULT=N\r\nBATCHNUMBER=\r\nCVV2_RESULT=N\r\nDEBIT_TRACE_NUMBER=\r\nENTRYMETHOD=M\r\nhistoryid=919067116\r\nMERCHANT_DBA_ADDR=11121 Willows Road NE\r\nMERCHANT_DBA_CITY=Redmond\r\nMERCHANT_DBA_NAME=Merchant Partners\r\nMERCHANT_DBA_PHONE=4254979909\r\nMERCHANT_DBA_STATE=WA\r\nMERCHANTID=542929804946788\r\nMERCHANTORDERNUMBER=67f4f20082e79684f036f25dafe96304\r\norderid=722189706\r\nPAYTYPE=Visa\r\nPRODUCT_DESCRIPTION=\r\nReason=\r\nRECEIPT_FOOTER=Thank You\r\nrecurid=0\r\nrefcode=919067116-036586\r\nresult=1\r\nSEQUENCE_NUMBER=370609730\r\nStatus=Accepted\r\nSUBID=SPREE\r\nSYSTEMAUDITTRACENUMBER=477\r\nTERMINALID=160551\r\nTRANSGUID=d5701d57-9147-4ded-b596-6805581f081c:266\r\ntransid=370609730\r\ntransresult=APPROVED\r\nVISATRANSACTIONID=088044000036586\r\n" +read 962 bytes +Conn close + EOS + end + + def post_scrubbed + <<-EOS +opening connection to trans.worldpay.us:443... +opened +starting SSL for trans.worldpay.us:443... +SSL established +<- "POST /cgi-bin/process.cgi HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: trans.worldpay.us\r\nContent-Length: 425\r\n\r\n" +<- "acctid=MPNAB&action=ns_quicksale_cc&amount=1.00&ccname=Longbob+Longsen&ccnum=[FILTERED]&ci_billaddr1=456+My+Street&ci_billaddr2=Apt+1&ci_billcity=Ottawa&ci_billcountry=CA&ci_billstate=ON&ci_billzip=K1C2N6&ci_companyname=Widgets+Inc&ci_email=&ci_ipaddress=&ci_phone=%28555%29555-5555&currencycode=USD&cvv2=[FILTERED]&expmon=09&expyear=2019&merchantordernumber=67f4f20082e79684f036f25dafe96304&merchantpin=[FILTERED]&subid=SPREE" +-> "HTTP/1.1 200 OK\r\n" +-> "Date: Tue, 13 Feb 2018 19:28:27 GMT\r\n" +-> "Server: Apache\r\n" +-> "X-Frame-Options: SAMEORIGIN\r\n" +-> "Content-Type: text/html;charset=ISO-8859-1\r\n" +-> "Content-Length: 962\r\n" +-> "Connection: close\r\n" +-> "\r\n" +reading 962 bytes... +-> "<html><body><plaintext>\r\nAccepted=SALE:036586:477::919067116:N::N\r\nhistoryid=919067116\r\norderid=722189706\r\nAccepted=SALE:036586:477::919067116:N::N\r\nACCOUNTNUMBER=************7892\r\nACCTID=MPNAB\r\nauthcode=036586\r\nAuthNo=SALE:036586:477::919067116:N::N\r\nAVS_RESULT=N\r\nBATCHNUMBER=\r\nCVV2_RESULT=N\r\nDEBIT_TRACE_NUMBER=\r\nENTRYMETHOD=M\r\nhistoryid=919067116\r\nMERCHANT_DBA_ADDR=11121 Willows Road NE\r\nMERCHANT_DBA_CITY=Redmond\r\nMERCHANT_DBA_NAME=Merchant Partners\r\nMERCHANT_DBA_PHONE=4254979909\r\nMERCHANT_DBA_STATE=WA\r\nMERCHANTID=542929804946788\r\nMERCHANTORDERNUMBER=67f4f20082e79684f036f25dafe96304\r\norderid=722189706\r\nPAYTYPE=Visa\r\nPRODUCT_DESCRIPTION=\r\nReason=\r\nRECEIPT_FOOTER=Thank You\r\nrecurid=0\r\nrefcode=919067116-036586\r\nresult=1\r\nSEQUENCE_NUMBER=370609730\r\nStatus=Accepted\r\nSUBID=SPREE\r\nSYSTEMAUDITTRACENUMBER=477\r\nTERMINALID=160551\r\nTRANSGUID=d5701d57-9147-4ded-b596-6805581f081c:266\r\ntransid=370609730\r\ntransresult=APPROVED\r\nVISATRANSACTIONID=088044000036586\r\n" +read 962 bytes +Conn close + EOS + end + + def pre_scrubbed_check + <<-EOS +opening connection to trans.worldpay.us:443... +opened +starting SSL for trans.worldpay.us:443... +SSL established +<- "POST /cgi-bin/process.cgi HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: trans.worldpay.us\r\nContent-Length: 412\r\n\r\n" +<- "acctid=MPNAB&action=ns_quicksale_check&amount=1.00&ci_billaddr1=456+My+Street&ci_billaddr2=Apt+1&ci_billcity=Ottawa&ci_billcountry=CA&ci_billstate=ON&ci_billzip=K1C2N6&ci_companyname=Widgets+Inc&ci_email=&ci_ipaddress=&ci_phone=%28555%29555-5555&ckaba=244183602&ckacct=15378535&ckaccttype=1&ckno=12345654321&currencycode=USD&merchantordernumber=5ec80ff8210dc9d24248ac2777d6b4f3&merchantpin=1234567890&subid=SPREE" +-> "HTTP/1.1 200 OK\r\n" +-> "Date: Tue, 13 Feb 2018 19:29:38 GMT\r\n" +-> "Server: Apache\r\n" +-> "X-Frame-Options: SAMEORIGIN\r\n" +-> "Content-Type: text/html;charset=ISO-8859-1\r\n" +-> "Content-Length: 414\r\n" +-> "Connection: close\r\n" +-> "\r\n" +reading 414 bytes... +-> "<html><body><plaintext>\r\nDeclined=DECLINED:1103180001:Invalid Bank:\r\nhistoryid=919060608\r\norderid=722196666\r\nACCOUNTNUMBER=****8535\r\nDeclined=DECLINED:1103180001:Invalid Bank:\r\nENTRYMETHOD=KEYED\r\nhistoryid=919060608\r\nMERCHANTORDERNUMBER=5ec80ff8210dc9d24248ac2777d6b4f3\r\norderid=722196666\r\nPAYTYPE=Check\r\nrcode=1103180001\r\nReason=DECLINED:1103180001:Invalid Bank:\r\nrecurid=0\r\nresult=0\r\nStatus=Declined\r\ntransid=0\r\n" +read 414 bytes +Conn close + EOS + end + + def post_scrubbed_check + <<-EOS +opening connection to trans.worldpay.us:443... +opened +starting SSL for trans.worldpay.us:443... +SSL established +<- "POST /cgi-bin/process.cgi HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: trans.worldpay.us\r\nContent-Length: 412\r\n\r\n" +<- "acctid=MPNAB&action=ns_quicksale_check&amount=1.00&ci_billaddr1=456+My+Street&ci_billaddr2=Apt+1&ci_billcity=Ottawa&ci_billcountry=CA&ci_billstate=ON&ci_billzip=K1C2N6&ci_companyname=Widgets+Inc&ci_email=&ci_ipaddress=&ci_phone=%28555%29555-5555&ckaba=244183602&ckacct=[FILTERED]&ckaccttype=1&ckno=12345654321&currencycode=USD&merchantordernumber=5ec80ff8210dc9d24248ac2777d6b4f3&merchantpin=[FILTERED]&subid=SPREE" +-> "HTTP/1.1 200 OK\r\n" +-> "Date: Tue, 13 Feb 2018 19:29:38 GMT\r\n" +-> "Server: Apache\r\n" +-> "X-Frame-Options: SAMEORIGIN\r\n" +-> "Content-Type: text/html;charset=ISO-8859-1\r\n" +-> "Content-Length: 414\r\n" +-> "Connection: close\r\n" +-> "\r\n" +reading 414 bytes... +-> "<html><body><plaintext>\r\nDeclined=DECLINED:1103180001:Invalid Bank:\r\nhistoryid=919060608\r\norderid=722196666\r\nACCOUNTNUMBER=****8535\r\nDeclined=DECLINED:1103180001:Invalid Bank:\r\nENTRYMETHOD=KEYED\r\nhistoryid=919060608\r\nMERCHANTORDERNUMBER=5ec80ff8210dc9d24248ac2777d6b4f3\r\norderid=722196666\r\nPAYTYPE=Check\r\nrcode=1103180001\r\nReason=DECLINED:1103180001:Invalid Bank:\r\nrecurid=0\r\nresult=0\r\nStatus=Declined\r\ntransid=0\r\n" +read 414 bytes +Conn close + EOS + end +end diff --git a/test/unit/integrations/a1agregator_module_test.rb b/test/unit/integrations/a1agregator_module_test.rb deleted file mode 100644 index 18b7e71b9de..00000000000 --- a/test/unit/integrations/a1agregator_module_test.rb +++ /dev/null @@ -1,9 +0,0 @@ -require 'test_helper' - -class A1agregatorModuleTest < Test::Unit::TestCase - include ActiveMerchant::Billing::Integrations - - def test_notification_method - assert_instance_of A1agregator::Notification, A1agregator.notification('name=cody') - end -end diff --git a/test/unit/integrations/action_view_helper_test.rb b/test/unit/integrations/action_view_helper_test.rb deleted file mode 100644 index 357209aecb3..00000000000 --- a/test/unit/integrations/action_view_helper_test.rb +++ /dev/null @@ -1,87 +0,0 @@ -require 'test_helper' - -class ActionViewHelperTest < Test::Unit::TestCase - include ActionViewHelperTestHelper - - def test_basic_payment_service - payment_service_for('order-1','test', :service => :bogus){} - assert_match(/^<form.*action="http:\/\/www.bogus.com".*/, @output_buffer) - assert_match(/<input id="account" name="account" type="hidden" value="test" \/>/, @output_buffer) - assert_match(/<input id="order" name="order" type="hidden" value="order-1" \/>/, @output_buffer) - assert_match(/<\/form>/, @output_buffer) - end - - def test_payment_service_no_block_given - assert_raise(ArgumentError){ payment_service_for } - end - - protected - def protect_against_forgery? - false - end -end - -if "".respond_to? :html_safe? - class ActionView::Base - include ActiveMerchant::Billing::Integrations::ActionViewHelper - include ActionView::Helpers::FormHelper - include ActionView::Helpers::FormTagHelper - include ActionView::Helpers::UrlHelper - include ActionView::Helpers::TagHelper - include ActionView::Helpers::CaptureHelper - include ActionView::Helpers::TextHelper - end - - ::MissingSourceFile::REGEXPS << [/^cannot load such file -- (.+)$/i, 1] - class PaymentServiceController < ActionController::Base - - def payment_action - render :inline => "<% payment_service_for('order-1','test', :service => :bogus){} %>" - end - end - - class PaymentServiceControllerTest < ActionController::TestCase - if ActionPack::VERSION::MAJOR > 2 - begin - require 'rails' - rescue NameError, LoadError - puts "You need to install the 'rails' gem to run these tests" - end - - class MerchantApp < Rails::Application; end - PaymentServiceController.send :include, Rails.application.routes.url_helpers - if Rails.version.start_with? '4' - Rails.application.config.secret_key_base = 'dad95720ad4ac592311874defcac8dd586795da07a5c87e51810c5a84012f2f2bf474b352fa76b1a0852cc14cf451b19d82abafa97dfdb1d14298843904c9b9b' - end - end - - def test_html_safety - with_routes do - get :payment_action - - assert_match(/^<form.*action="http:\/\/www.bogus.com".*/, @response.body) - assert_match(/<input id="account" name="account" type="hidden" value="test" \/>/, @response.body) - assert_match(/<input id="order" name="order" type="hidden" value="order-1" \/>/, @response.body) - assert_match(/<\/form>/, @response.body) - end - end - - private - def with_routes - raise "You need to pass a block to me" unless block_given? - - if ActionPack::VERSION::MAJOR > 2 - with_routing do |set| - set.draw { match '/:action', :controller => 'payment_service', :via => [:get, :post] } - yield - end - else - # Falling back to Rails 2.x - with_routing do |set| - set.draw { |map| map.connect ':controller/:action/:id' } - yield - end - end - end - end -end diff --git a/test/unit/integrations/authorize_net_sim_module_test.rb b/test/unit/integrations/authorize_net_sim_module_test.rb deleted file mode 100644 index 333aa5ac581..00000000000 --- a/test/unit/integrations/authorize_net_sim_module_test.rb +++ /dev/null @@ -1,244 +0,0 @@ -require 'test_helper' - -class AuthorizeNetSimModuleTest < Test::Unit::TestCase - include ActionViewHelperTestHelper - include ActiveMerchant::Billing::Integrations - - def test_notification_method - assert_instance_of AuthorizeNetSim::Notification, AuthorizeNetSim.notification('name=cody') - end - - def test_address2 - payment_service_for('44','8wd65QS', :service => :authorize_net_sim, :amount => 157.0){|service| - service.billing_address :address1 => 'address1', :address2 => 'line 2' - } - all= ['<input id="x_address" name="x_address" type="hidden" value="address1 line 2" />'] - check_inclusion all - end - - def test_lots_of_line_items_same_name - payment_service_for('44','8wd65QS', :service => :authorize_net_sim, :amount => 157.0){|service| - 35.times {service.add_line_item :name => 'beauty2 - ayoyo', :quantity => 1, :unit_price => 0} - } - assert @output_buffer =~ / more unshown items after this one/ - # It should display them all in, despite each having the same name. - assert @output_buffer.scan(/beauty2 - ayoyo/).length > 5 - end - - def test_lots_of_line_items_different_names - payment_service_for('44','8wd65QS', :service => :authorize_net_sim, :amount => 157.0){|service| - 35.times {|n| service.add_line_item :name => 'beauty2 - ayoyo' + n.to_s, :quantity => 1, :unit_price => 0} - } - assert @output_buffer =~ / ayoyo3/ - assert @output_buffer =~ / ayoyo4/ - end - - def test_should_round_numbers - payment_service_for('44','8wd65QS', :service => :authorize_net_sim, :amount => "157.003"){} - assert @output_buffer !~ /x_amount.*157.003"/ - payment_service_for('44','8wd65QS', :service => :authorize_net_sim, :amount => "157.005"){} - assert @output_buffer =~ /x_amount.*157.01"/ - end - - def test_all_fields - payment_service_for('44','8wd65QS', :service => :authorize_net_sim, :amount => 157.0){|service| - - service.setup_hash :transaction_key => '8CP6zJ7uD875J6tY', - :order_timestamp => 1206836763 - service.customer_id 8 - service.customer :first_name => 'g', - :last_name => 'g', - :email => 'g@g.com', - :phone => '3' - - service.billing_address :zip => 'g', - :country => 'United States of America', - :address1 => 'g' - - service.ship_to_address :first_name => 'g', - :last_name => 'g', - :city => '', - :address1 => 'g', - :address2 => '', - :state => 'ut', - :country => 'United States of America', - :zip => 'g' - - service.invoice "516428355" - service.notify_url "http://t/authorize_net_sim/payment_received_notification_sub_step" - service.payment_header 'MyFavoritePal' - service.add_line_item :name => 'beauty2 - ayoyo', :quantity => 1, :unit_price => 0.0 - service.test_request 'true' - service.shipping '25.0' - service.add_shipping_as_line_item - } - - all = '<INPUT TYPE=HIDDEN name="x_cust_id" value="8"> - <INPUT TYPE=HIDDEN name="x_ship_to_last_name" value="g"> - <INPUT TYPE=HIDDEN name="x_fp_timestamp" value="1206836763"> - <INPUT TYPE=HIDDEN name="x_ship_to_first_name" value="g"> - <INPUT TYPE=HIDDEN name="x_last_name" value="g"> - <INPUT TYPE=HIDDEN name="x_amount" value="157.0"> - <INPUT TYPE=HIDDEN name="x_ship_to_country" value="United States of America"> - <INPUT TYPE=HIDDEN name="x_ship_to_zip" value="g"> - <INPUT TYPE=HIDDEN name="x_zip" value="g"> - <INPUT TYPE=HIDDEN name="x_country" value="United States of America"> - <INPUT TYPE=HIDDEN name="x_duplicate_window" value="28800"> - <INPUT TYPE=HIDDEN name="x_relay_response" value="TRUE"> - <INPUT TYPE=HIDDEN name="x_ship_to_address" value="g"> - <INPUT TYPE=HIDDEN name="x_first_name" value="g"> - <INPUT TYPE=HIDDEN name="x_version" value="3.1"> - <INPUT TYPE=HIDDEN name="x_invoice_num" value="516428355"> - <INPUT TYPE=HIDDEN name="x_address" value="g"> - <INPUT TYPE=HIDDEN name="x_login" value="8wd65QS"> - <INPUT TYPE=HIDDEN name="x_phone" value="3"> - <INPUT TYPE=HIDDEN name="x_relay_url" value="http://t/authorize_net_sim/payment_received_notification_sub_step"> - <INPUT TYPE=HIDDEN name="x_fp_sequence" value="44"> - <INPUT TYPE=HIDDEN name="x_show_form" value="PAYMENT_FORM"> - <INPUT TYPE=HIDDEN name="x_header_html_payment_form" value="MyFavoritePal"> - <INPUT TYPE=HIDDEN name="x_email" value="g@g.com"> - <INPUT TYPE=HIDDEN name="x_fp_hash" value="31d572da4e9910b36e999d73925eb01c"> - <INPUT TYPE=HIDDEN name="x_line_item" value="Item 1<|>beauty2 - ayoyo<|>beauty2 - ayoyo<|>1<|>0.0<|>N"> - <INPUT TYPE=HIDDEN name="x_test_request" value="true"> - <INPUT TYPE=HIDDEN name="x_freight" value="25.0"/> - <INPUT TYPE=HIDDEN name="x_line_item" value="Shipping<|>Shipping and Handling Cost<|>Shipping and Handling Cost<|>1<|>25.0<|>N">' - - # clean it up a bit for parsing - @output_buffer.gsub!("type=\"hidden\" ", "") - for line in all.split("\n") do - line.strip! - if line =~ /(name=".*".*value=".*")/i - line = $1 - assert @output_buffer.include?(line), 'didnt find' + line + 'in ' + @output_buffer - end - end - end - - def check_inclusion(these_lines) - for line in these_lines do - assert @output_buffer.include?(line), ['unable to find ', line, ' ', 'in \n', @output_buffer].join(' ') - end - end - - def test_custom - payment_service_for('44','8wd65QS', :service => :authorize_net_sim, :amount => 157.0){|service| - service.add_custom_field 'abc', 'def' - } - all = ["<input id=\"abc\" name=\"abc\" type=\"hidden\" value=\"def\" />"] - check_inclusion all - end - - - def test_shipping_and_tax_line_item - payment_service_for('44','8wd65QS', :service => :authorize_net_sim, :amount => 157.0){|service| - service.shipping 44.0 - service.tax 44.0 - service.add_shipping_as_line_item - service.add_tax_as_line_item - } - all = ['<input id="x_line_item" name="x_line_item" type="hidden" value="Tax<|>Total Tax<|>Total Tax<|>1<|>44.0<|>N', - 'input id="x_line_item" name="x_line_item" type="hidden" value="Shipping<|>Shipping and Handling Cost<|>Shipping and Handling Cost<|>1<|>44.0<|>N" />' - ] - check_inclusion all - end - - def test_shipping_large - payment_service_for('44','8wd65QS', :service => :authorize_net_sim, :amount => 157.0){|service| - - service.ship_to_address :first_name => 'first', :last_name => 'last', :company => 'company1', - :city => 'city2', :state => 'TX', :zip => 84601, :country => 'US' - } - expected = "<input id=\"x_ship_to_city\" name=\"x_ship_to_city\" type=\"hidden\" value=\"city2\" />\n<input id=\"x_ship_to_last_name\" name=\"x_ship_to_last_name\" type=\"hidden\" value=\"last\" />\n<input id=\"x_ship_to_first_name\" name=\"x_ship_to_first_name\" type=\"hidden\" value=\"first\" /> - <input id=\"x_ship_to_country\" name=\"x_ship_to_country\" type=\"hidden\" value=\"US\" />\n<input id=\"x_ship_to_zip\" name=\"x_ship_to_zip\" type=\"hidden\" value=\"84601\" />\n<input id=\"x_ship_to_company\" name=\"x_ship_to_company\" type=\"hidden\" value=\"company1\" />\n - <input id=\"x_ship_to_state\" name=\"x_ship_to_state\" type=\"hidden\" value=\"TX\" />\n" - for line in expected.split("\n") do - assert @output_buffer.include?(line.strip), 'expected but not found' + line - end - end - - def test_line_item - payment_service_for('44','8wd65QS', :service => :authorize_net_sim, :amount => 157.0){|service| - service.add_line_item :name => 'name1', :quantity => 1, :unit_price => 1, :tax => 'true' - service.add_line_item :name => 'name2', :quantity => '2', :unit_price => '2' - assert_raise(RuntimeError) do - service.add_line_item :name => 'name3', :quantity => '3', :unit_price => '-3' - end - service.tax 4 - service.shipping 5 - service.add_tax_as_line_item - service.add_shipping_as_line_item - } - all = ["<input id=\"x_line_item\" name=\"x_line_item\" type=\"hidden\" value=\"Item 1<|>name1<|>name1<|>1<|>1.0<|>N\" />"] - check_inclusion all - end - - def test_line_item_weird_prices - payment_service_for('44','8wd65QS', :service => :authorize_net_sim, :amount => 157.0){|service| - service.add_line_item :name => 'name1', :quantity => 1, :unit_price => "1.001", :tax => 'true' - service.add_line_item :name => 'name2', :quantity => '2', :unit_price => '1.006' - } - # should round the prices - assert @output_buffer !~ /1.001/ - assert @output_buffer =~ /1.01/ - end - - def test_ship_to - payment_service_for('44','8wd65QS', :service => :authorize_net_sim, :amount => 157.0){|service| - service.tax 4 - service.ship_to_address :first_name => 'firsty' - } - assert @output_buffer.include? "<input id=\"x_ship_to_first_name\" name=\"x_ship_to_first_name\" type=\"hidden\" value=\"firsty\" />" - end - - def test_normal_fields - payment_service_for('44','8wd65QS', :service => :authorize_net_sim, :amount => 157.0){|service| - - service.setup_hash :transaction_key => '8CP6zJ7uD875J6tY', - :order_timestamp => 1206836763 - service.customer_id 8 - service.customer :first_name => 'Cody', - :last_name => 'Fauser', - :phone => '(555)555-5555', - :email => 'g@g.com' - - service.billing_address :city => 'city1', - :address1 => 'g', - :address2 => '', - :state => 'UT', - :country => 'United States of America', - :zip => '90210' - service.invoice '#1000' - service.shipping '30.00' - service.tax '31.00' - service.test_request 'true' - - } - - expected = "<input id=\"x_cust_id\" name=\"x_cust_id\" type=\"hidden\" value=\"8\" /> - - <input id=\"x_city\" name=\"x_city\" type=\"hidden\" value=\"city1\" /> - <input id=\"x_fp_timestamp\" name=\"x_fp_timestamp\" type=\"hidden\" value=\"1206836763\" /> - <input id=\"x_last_name\" name=\"x_last_name\" type=\"hidden\" value=\"Fauser\" />\n<input id=\"x_amount\" name=\"x_amount\" type=\"hidden\" value=\"157.0\" /> - <input id=\"x_country\" name=\"x_country\" type=\"hidden\" value=\"United States of America\" />\n<input id=\"x_zip\" name=\"x_zip\" type=\"hidden\" value=\"90210\" />\n<input id=\"x_duplicate_window\" name=\"x_duplicate_window\" type=\"hidden\" value=\"28800\" /> - \n<input id=\"x_relay_response\" name=\"x_relay_response\" type=\"hidden\" value=\"TRUE\" />\n<input id=\"x_first_name\" name=\"x_first_name\" type=\"hidden\" value=\"Cody\" />\n<input id=\"x_type\" name=\"x_type\" type=\"hidden\" value=\"AUTH_CAPTURE\" />\n<input id=\"x_version\" name=\"x_version\" type=\"hidden\" value=\"3.1\" />\n<input id=\"x_login\" name=\"x_login\" type=\"hidden\" value=\"8wd65QS\" />\n<input id=\"x_invoice_num\" name=\"x_invoice_num\" type=\"hidden\" value=\"#1000\" />\n<input id=\"x_phone\" name=\"x_phone\" type=\"hidden\" value=\"(555)555-5555\" />\n<input id=\"x_fp_sequence\" name=\"x_fp_sequence\" type=\"hidden\" value=\"44\" />\n<input id=\"x_show_form\" name=\"x_show_form\" type=\"hidden\" value=\"PAYMENT_FORM\" /> - <input id=\"x_state\" name=\"x_state\" type=\"hidden\" value=\"UT\" />\n<input id=\"x_email\" name=\"x_email\" type=\"hidden\" value=\"g@g.com\" />\n<input id=\"x_fp_hash\" name=\"x_fp_hash\" type=\"hidden\" value=\"31d572da4e9910b36e999d73925eb01c\" /> - <input id=\"x_tax\" name=\"x_tax\" type=\"hidden\" value=\"31.00\" /> - <input id=\"x_freight\" name=\"x_freight\" type=\"hidden\" value=\"30.00\" />".split("\n") - - for line in expected - assert @output_buffer.include?(line.strip), 'missing field' + line + ' in' + "\n" - end - - end - - def test_test_mode - ActiveMerchant::Billing::Base.integration_mode = :test - assert_equal 'https://test.authorize.net/gateway/transact.dll', AuthorizeNetSim.service_url - end - - def test_production_mode - ActiveMerchant::Billing::Base.integration_mode = :production - assert_equal 'https://secure.authorize.net/gateway/transact.dll', AuthorizeNetSim.service_url - end - -end diff --git a/test/unit/integrations/bogus_module_test.rb b/test/unit/integrations/bogus_module_test.rb deleted file mode 100644 index a4d4902ce76..00000000000 --- a/test/unit/integrations/bogus_module_test.rb +++ /dev/null @@ -1,24 +0,0 @@ -require 'test_helper' - -class BogusModuleTest < Test::Unit::TestCase - include ActiveMerchant::Billing::Integrations - - def test_notification_method - assert_instance_of Bogus::Notification, Bogus.notification('name=cody', {}) - end - - def test_service_url - new = 'http://www.unbogus.com' - assert_equal 'http://www.bogus.com', Bogus.service_url - Bogus.service_url = new - assert_equal new, Bogus.service_url - end - - def test_return_method - assert_instance_of Bogus::Return, Bogus.return('name=cody', {}) - end - - def teardown - Bogus.service_url = 'http://www.bogus.com' - end -end diff --git a/test/unit/integrations/chronopay_module_test.rb b/test/unit/integrations/chronopay_module_test.rb deleted file mode 100644 index af46fb420df..00000000000 --- a/test/unit/integrations/chronopay_module_test.rb +++ /dev/null @@ -1,13 +0,0 @@ -require 'test_helper' - -class ChronopayModuleTest < Test::Unit::TestCase - include ActiveMerchant::Billing::Integrations - - def test_notification_method - assert_instance_of Chronopay::Notification, Chronopay.notification('name=cody', {}) - end - - def test_return_method - assert_instance_of Chronopay::Return, Chronopay.return('name=cody', {}) - end -end diff --git a/test/unit/integrations/direc_pay_module_test.rb b/test/unit/integrations/direc_pay_module_test.rb deleted file mode 100644 index 20a61ecf8f8..00000000000 --- a/test/unit/integrations/direc_pay_module_test.rb +++ /dev/null @@ -1,18 +0,0 @@ -require 'test_helper' - -class DirecPayModuleTest < Test::Unit::TestCase - include ActiveMerchant::Billing::Integrations - - def test_notification_method - assert_instance_of DirecPay::Notification, DirecPay.notification('name=me', {}) - end - - def test_return - assert_instance_of DirecPay::Return, DirecPay.return("name=me", {}) - end - - def test_status_update_instantiates_status_class - DirecPay::Status.any_instance.expects(:update).with('authorization', 'http://localhost/return') - DirecPay.request_status_update('mid', 'authorization', 'http://localhost/return') - end -end diff --git a/test/unit/integrations/dotpay_module_test.rb b/test/unit/integrations/dotpay_module_test.rb deleted file mode 100644 index 0fcc433ce06..00000000000 --- a/test/unit/integrations/dotpay_module_test.rb +++ /dev/null @@ -1,13 +0,0 @@ -require 'test_helper' - -class DotpayModuleTest < Test::Unit::TestCase - include ActiveMerchant::Billing::Integrations - - def test_notification_method - assert_instance_of Dotpay::Notification, Dotpay.notification('name=cody', :pin => '1234567890') - end - - def test_return - assert_instance_of Dotpay::Return, Dotpay.return("name=me", {}) - end -end diff --git a/test/unit/integrations/dwolla_module_test.rb b/test/unit/integrations/dwolla_module_test.rb deleted file mode 100644 index ce8025b6fd9..00000000000 --- a/test/unit/integrations/dwolla_module_test.rb +++ /dev/null @@ -1,13 +0,0 @@ -require 'test_helper' - -class DwollaModuleTest < Test::Unit::TestCase - include ActiveMerchant::Billing::Integrations - - def test_notification_method - assert_instance_of Dwolla::Notification, Dwolla.notification('{"Amount":0.01,"OrderId":"abc123","Status":"Completed","Error":null,"TransactionId":3165397,"CheckoutId":"ac5b910a-7ec1-4b65-9f68-90449ed030f6","Signature":"7d4c5deaf9178faae7c437fd8693fc0b97b1b22b","TestMode":"false","ClearingDate":"6/8/2013 8:07:41 PM"}', {:credential3 => '62hdv0jBjsBlD+0AmhVn9pQuULSC661AGo2SsksQTpqNUrff7Z'}) - end - - def test_return - assert_instance_of Dwolla::Return, Dwolla.return("signature=7d4c5deaf9178faae7c437fd8693fc0b97b1b22b&orderId=abc123&amount=0.01&checkoutId=ac5b910a-7ec1-4b65-9f68-90449ed030f6&status=Completed&clearingDate=6/8/2013%208:07:41%20PM&transaction=3165397&postback=success", {:credential3 => '62hdv0jBjsBlD+0AmhVn9pQuULSC661AGo2SsksQTpqNUrff7Z'}) - end -end diff --git a/test/unit/integrations/e_payment_plans_module_test.rb b/test/unit/integrations/e_payment_plans_module_test.rb deleted file mode 100644 index 7b66fb9371e..00000000000 --- a/test/unit/integrations/e_payment_plans_module_test.rb +++ /dev/null @@ -1,24 +0,0 @@ -require 'test_helper' - -class EPaymentPlansModuleTest < Test::Unit::TestCase - include ActiveMerchant::Billing::Integrations - - def test_notification_method - assert_instance_of EPaymentPlans::Notification, EPaymentPlans.notification('name=cody') - end - - def test_test_mode - ActiveMerchant::Billing::Base.integration_mode = :test - assert_equal 'https://test.epaymentplans.com/order/purchase', EPaymentPlans.service_url - end - - def test_production_mode - ActiveMerchant::Billing::Base.integration_mode = :production - assert_equal 'https://www.epaymentplans.com/order/purchase', EPaymentPlans.service_url - end - - def test_invalid_mode - ActiveMerchant::Billing::Base.integration_mode = :coolmode - assert_raise(StandardError){ EPaymentPlans.service_url } - end -end diff --git a/test/unit/integrations/easy_pay_module_test.rb b/test/unit/integrations/easy_pay_module_test.rb deleted file mode 100644 index 5e4d1e8af5d..00000000000 --- a/test/unit/integrations/easy_pay_module_test.rb +++ /dev/null @@ -1,18 +0,0 @@ -require 'test_helper' - -class EasyPayModuleTest < Test::Unit::TestCase - include ActiveMerchant::Billing::Integrations - - def test_helper_method - assert_instance_of EasyPay::Helper, EasyPay.helper(123, 'test', :credential2 => 'secret') - end - - def test_notification_method - assert_instance_of EasyPay::Notification, EasyPay.notification('name=cody', :credential2 => '123') - end - - def test_service_url - url = 'https://ssl.easypay.by/weborder/' - assert_equal url, EasyPay.service_url - end -end diff --git a/test/unit/integrations/epay_module_test.rb b/test/unit/integrations/epay_module_test.rb deleted file mode 100644 index 3b68ff94c40..00000000000 --- a/test/unit/integrations/epay_module_test.rb +++ /dev/null @@ -1,9 +0,0 @@ -require 'test_helper' - -class EpayModuleTest < Test::Unit::TestCase - include ActiveMerchant::Billing::Integrations - - def test_notification_method - assert_instance_of Epay::Notification, Epay.notification('name=cody', {}) - end -end diff --git a/test/unit/integrations/first_data_module_test.rb b/test/unit/integrations/first_data_module_test.rb deleted file mode 100644 index 1e50e9978d7..00000000000 --- a/test/unit/integrations/first_data_module_test.rb +++ /dev/null @@ -1,232 +0,0 @@ -require 'test_helper' - -class FirstDataModuleTest < Test::Unit::TestCase - include ActionViewHelperTestHelper - include ActiveMerchant::Billing::Integrations - - def test_notification_method - assert_instance_of FirstData::Notification, FirstData.notification('name=cody') - end - - def test_address2 - payment_service_for('44','8wd65QS', :service => :first_data, :amount => 157.0) do |service| - service.billing_address :address1 => 'address1', :address2 => 'line 2' - end - check_inclusion '<input id="x_address" name="x_address" type="hidden" value="address1 line 2" />' - end - - def test_lots_of_line_items_same_name - payment_service_for('44','8wd65QS', :service => :first_data, :amount => 157.0) do |service| - 35.times{service.add_line_item :name => 'beauty2 - ayoyo', :quantity => 1, :unit_price => 0} - end - assert_match(/ more unshown items after this one/, @output_buffer) - - # It should display them all in, despite each having the same name. - assert(@output_buffer.scan(/beauty2 - ayoyo/).length > 5) - end - - def test_lots_of_line_items_different_names - payment_service_for('44','8wd65QS', :service => :first_data, :amount => 157.0) do |service| - 35.times{|n| service.add_line_item :name => 'beauty2 - ayoyo' + n.to_s, :quantity => 1, :unit_price => 0} - end - assert_match(/ ayoyo3/, @output_buffer) - assert_match(/ ayoyo4/, @output_buffer) - end - - def test_should_round_numbers - payment_service_for('44','8wd65QS', :service => :first_data, :amount => "157.003"){} - assert_no_match(/x_amount.*157.003"/, @output_buffer) - payment_service_for('44','8wd65QS', :service => :first_data, :amount => "157.005"){} - assert_match(/x_amount.*157.01"/, @output_buffer) - end - - def test_all_fields - payment_service_for('44','8wd65QS', :service => :first_data, :amount => 157.0) do |service| - service.setup_hash :transaction_key => '8CP6zJ7uD875J6tY', - :order_timestamp => 1206836763 - service.customer_id 8 - service.customer :first_name => 'g', - :last_name => 'g', - :email => 'g@g.com', - :phone => '3' - - service.billing_address :zip => 'g', - :country => 'United States of America', - :address1 => 'g' - - service.ship_to_address :first_name => 'g', - :last_name => 'g', - :city => '', - :address1 => 'g', - :address2 => '', - :state => 'ut', - :country => 'United States of America', - :zip => 'g' - - service.invoice "516428355" - service.notify_url "http://t/first_data/payment_received_notification_sub_step" - service.payment_header 'MyFavoritePal' - service.add_line_item :name => 'beauty2 - ayoyo', :quantity => 1, :unit_price => 0.0 - service.test_request 'true' - service.shipping '25.0' - service.add_shipping_as_line_item - end - - check_inclusion %(<input id="x_cust_id" name="x_cust_id" type="hidden" value="8" />), - %(<input id="x_ship_to_last_name" name="x_ship_to_last_name" type="hidden" value="g" />), - %(<input id="x_fp_timestamp" name="x_fp_timestamp" type="hidden" value="1206836763" />), - %(<input id="x_ship_to_first_name" name="x_ship_to_first_name" type="hidden" value="g" />), - %(<input id="x_last_name" name="x_last_name" type="hidden" value="g" />), - %(<input id="x_amount" name="x_amount" type="hidden" value="157.0" />), - %(<input id="x_ship_to_country" name="x_ship_to_country" type="hidden" value="United States of America" />), - %(<input id="x_ship_to_zip" name="x_ship_to_zip" type="hidden" value="g" />), - %(<input id="x_zip" name="x_zip" type="hidden" value="g" />), - %(<input id="x_country" name="x_country" type="hidden" value="United States of America" />), - %(<input id="x_duplicate_window" name="x_duplicate_window" type="hidden" value="28800" />), - %(<input id="x_relay_response" name="x_relay_response" type="hidden" value="TRUE" />), - %(<input id="x_ship_to_address" name="x_ship_to_address" type="hidden" value="g" />), - %(<input id="x_first_name" name="x_first_name" type="hidden" value="g" />), - %(<input id="x_version" name="x_version" type="hidden" value="3.1" />), - %(<input id="x_invoice_num" name="x_invoice_num" type="hidden" value="516428355" />), - %(<input id="x_address" name="x_address" type="hidden" value="g" />), - %(<input id="x_login" name="x_login" type="hidden" value="8wd65QS" />), - %(<input id="x_phone" name="x_phone" type="hidden" value="3" />), - %(<input id="x_relay_url" name="x_relay_url" type="hidden" value="http://t/first_data/payment_received_notification_sub_step" />), - %(<input id="x_fp_sequence" name="x_fp_sequence" type="hidden" value="44" />), - %(<input id="x_show_form" name="x_show_form" type="hidden" value="PAYMENT_FORM" />), - %(<input id="x_header_html_payment_form" name="x_header_html_payment_form" type="hidden" value="MyFavoritePal" />), - %(<input id="x_email" name="x_email" type="hidden" value="g@g.com" />), - %(<input id="x_fp_hash" name="x_fp_hash" type="hidden" value="31d572da4e9910b36e999d73925eb01c" />), - %(<input id="x_line_item" name="x_line_item" type="hidden" value="Item 1<|>beauty2 - ayoyo<|>beauty2 - ayoyo<|>1<|>0.0<|>N" />), - %(<input id="x_test_request" name="x_test_request" type="hidden" value="true" />), - %(<input id="x_freight" name="x_freight" type="hidden" value="25.0" />), - %(<input id="x_line_item" name="x_line_item" type="hidden" value="Shipping<|>Shipping and Handling Cost<|>Shipping and Handling Cost<|>1<|>25.0<|>N" />) - end - - def check_inclusion(*lines) - lines.each do |line| - assert @output_buffer.include?(line), "Unable to find #{line} in\n#{@output_buffer}" - end - end - - def test_custom - payment_service_for('44','8wd65QS', :service => :first_data, :amount => 157.0) do |service| - service.add_custom_field 'abc', 'def' - end - check_inclusion %(<input id="abc" name="abc" type="hidden" value="def" />) - end - - def test_shipping_and_tax_line_item - payment_service_for('44','8wd65QS', :service => :first_data, :amount => 157.0) do |service| - service.shipping 44.0 - service.tax 44.0 - service.add_shipping_as_line_item - service.add_tax_as_line_item - end - check_inclusion '<input id="x_line_item" name="x_line_item" type="hidden" value="Tax<|>Total Tax<|>Total Tax<|>1<|>44.0<|>N', - 'input id="x_line_item" name="x_line_item" type="hidden" value="Shipping<|>Shipping and Handling Cost<|>Shipping and Handling Cost<|>1<|>44.0<|>N" />' - end - - def test_shipping_large - payment_service_for('44','8wd65QS', :service => :first_data, :amount => 157.0) do |service| - service.ship_to_address :first_name => 'first', :last_name => 'last', :company => 'company1', - :city => 'city2', :state => 'TX', :zip => 84601, :country => 'US' - end - check_inclusion %(<input id="x_ship_to_city" name="x_ship_to_city" type="hidden" value="city2" />), - %(<input id="x_ship_to_last_name" name="x_ship_to_last_name" type="hidden" value="last" />), - %(<input id="x_ship_to_first_name" name="x_ship_to_first_name" type="hidden" value="first" />), - %(<input id="x_ship_to_country" name="x_ship_to_country" type="hidden" value="US" />), - %(<input id="x_ship_to_zip" name="x_ship_to_zip" type="hidden" value="84601" />), - %(<input id="x_ship_to_company" name="x_ship_to_company" type="hidden" value="company1" />), - %(<input id="x_ship_to_state" name="x_ship_to_state" type="hidden" value="TX" />) - end - - def test_line_item - payment_service_for('44','8wd65QS', :service => :first_data, :amount => 157.0) do |service| - service.add_line_item :name => 'name1', :quantity => 1, :unit_price => 1, :tax => 'true' - service.add_line_item :name => 'name2', :quantity => '2', :unit_price => '2' - assert_raise(RuntimeError) do - service.add_line_item :name => 'name3', :quantity => '3', :unit_price => '-3' - end - service.tax 4 - service.shipping 5 - service.add_tax_as_line_item - service.add_shipping_as_line_item - end - check_inclusion %(<input id="x_line_item" name="x_line_item" type="hidden" value="Item 1<|>name1<|>name1<|>1<|>1.0<|>N" />) - end - - def test_line_item_weird_prices - payment_service_for('44','8wd65QS', :service => :first_data, :amount => 157.0) do |service| - service.add_line_item :name => 'name1', :quantity => 1, :unit_price => "1.001", :tax => 'true' - service.add_line_item :name => 'name2', :quantity => '2', :unit_price => '1.006' - end - # should round the prices - assert_no_match(/1.001/, @output_buffer) - assert_match(/1.01/, @output_buffer) - end - - def test_ship_to - payment_service_for('44','8wd65QS', :service => :first_data, :amount => 157.0) do |service| - service.tax 4 - service.ship_to_address :first_name => 'firsty' - end - check_inclusion %(<input id="x_ship_to_first_name" name="x_ship_to_first_name" type="hidden" value="firsty" />) - end - - def test_normal_fields - payment_service_for('44','8wd65QS', :service => :first_data, :amount => 157.0) do |service| - service.setup_hash :transaction_key => '8CP6zJ7uD875J6tY', - :order_timestamp => 1206836763 - service.customer_id 8 - service.customer :first_name => 'Cody', - :last_name => 'Fauser', - :phone => '(555)555-5555', - :email => 'g@g.com' - - service.billing_address :city => 'city1', - :address1 => 'g', - :address2 => '', - :state => 'UT', - :country => 'United States of America', - :zip => '90210' - service.invoice '#1000' - service.shipping '30.00' - service.tax '31.00' - service.test_request 'true' - end - - check_inclusion %(<input id="x_cust_id" name="x_cust_id" type="hidden" value="8" />), - %(<input id="x_city" name="x_city" type="hidden" value="city1" />), - %(<input id="x_fp_timestamp" name="x_fp_timestamp" type="hidden" value="1206836763" />), - %(<input id="x_last_name" name="x_last_name" type="hidden" value="Fauser" />), - %(<input id="x_amount" name="x_amount" type="hidden" value="157.0" />), - %(<input id="x_country" name="x_country" type="hidden" value="United States of America" />), - %(<input id="x_zip" name="x_zip" type="hidden" value="90210" />), - %(<input id="x_duplicate_window" name="x_duplicate_window" type="hidden" value="28800" />), - %(<input id="x_relay_response" name="x_relay_response" type="hidden" value="TRUE" />), - %(<input id="x_first_name" name="x_first_name" type="hidden" value="Cody" />), - %(<input id="x_type" name="x_type" type="hidden" value="AUTH_CAPTURE" />), - %(<input id="x_version" name="x_version" type="hidden" value="3.1" />), - %(<input id="x_login" name="x_login" type="hidden" value="8wd65QS" />), - %(<input id="x_invoice_num" name="x_invoice_num" type="hidden" value="#1000" />), - %(<input id="x_phone" name="x_phone" type="hidden" value="(555)555-5555" />), - %(<input id="x_fp_sequence" name="x_fp_sequence" type="hidden" value="44" />), - %(<input id="x_show_form" name="x_show_form" type="hidden" value="PAYMENT_FORM" />), - %(<input id="x_state" name="x_state" type="hidden" value="UT" />), - %(<input id="x_email" name="x_email" type="hidden" value="g@g.com" />), - %(<input id="x_fp_hash" name="x_fp_hash" type="hidden" value="31d572da4e9910b36e999d73925eb01c" />), - %(<input id="x_tax" name="x_tax" type="hidden" value="31.00" />), - %(<input id="x_freight" name="x_freight" type="hidden" value="30.00" />) - end - - def test_test_mode - ActiveMerchant::Billing::Base.integration_mode = :test - assert_equal 'https://demo.globalgatewaye4.firstdata.com/payment', FirstData.service_url - end - - def test_production_mode - ActiveMerchant::Billing::Base.integration_mode = :production - assert_equal 'https://checkout.globalgatewaye4.firstdata.com/payment', FirstData.service_url - end -end diff --git a/test/unit/integrations/gestpay_module_test.rb b/test/unit/integrations/gestpay_module_test.rb deleted file mode 100644 index 246b6489445..00000000000 --- a/test/unit/integrations/gestpay_module_test.rb +++ /dev/null @@ -1,14 +0,0 @@ -require 'test_helper' - -class GestpayModuleTest < Test::Unit::TestCase - include ActiveMerchant::Billing::Integrations - - def test_notification_method - Gestpay::Notification.any_instance.expects(:ssl_get).returns('#decryptstring#a=9000000&b=PAY1_UICCODE=242*P1*PAY1_AMOUNT=1234.56*P1*PAY1_TRANSACTIONRESULT=OK*P1*PAY1_BANKTRANSACTIONID=ABCD1234*P1*PAY1_SHOPTRANSACTIONID=1000#/decryptstring#') - assert_instance_of Gestpay::Notification, Gestpay.notification('a=900000&b=F7DEB36478FD84760F9134F23C922697272D57DE6D4518EB9B4D468B769D9A3A8071B6EB160B35CB412FC1820C7CC12D17B3141855B1ED55468613702A2E213DDE9DE5B0209E13C416448AE833525959F05693172D7F0656', {}) - end - - def test_return_method - assert_instance_of Gestpay::Return, Gestpay.return('name=cody', {}) - end -end diff --git a/test/unit/integrations/helpers/a1agregator_helper_test.rb b/test/unit/integrations/helpers/a1agregator_helper_test.rb deleted file mode 100644 index 8e1690a565e..00000000000 --- a/test/unit/integrations/helpers/a1agregator_helper_test.rb +++ /dev/null @@ -1,30 +0,0 @@ -require 'test_helper' - -class A1agregatorHelperTest < Test::Unit::TestCase - include ActiveMerchant::Billing::Integrations - - def setup - @helper = A1agregator::Helper.new 123,'some_key', - :amount => 500, - :transaction_type => 'wmz', - :credential2 => 'Some product', - :credential3 => 'Some comments', - :credential4 => '1' - end - - def test_basic_helper_fields - assert_field 'order_id', '123' - assert_field 'key', 'some_key' - assert_field 'cost', '500' - assert_field 'name', 'Some product' - assert_field 'comment', 'Some comments' - assert_field 'verbose', '1' - end - - def test_customer_fields - @helper.customer :email => 'some_email@mail.com', :phone => '123345123' - assert_field 'email', 'some_email@mail.com' - assert_field 'phone_number', '123345123' - end - -end diff --git a/test/unit/integrations/helpers/authorize_net_sim_helper_test.rb b/test/unit/integrations/helpers/authorize_net_sim_helper_test.rb deleted file mode 100644 index 371ecf0a6a1..00000000000 --- a/test/unit/integrations/helpers/authorize_net_sim_helper_test.rb +++ /dev/null @@ -1,53 +0,0 @@ -require 'test_helper' - -class AuthorizeNetSimHelperTest < Test::Unit::TestCase - include ActiveMerchant::Billing::Integrations - - def setup - # currency is currently ignored... - @helper = AuthorizeNetSim::Helper.new('order-500','cody@example.com', :amount => 500, :currency => 'USD') - end - - def test_basic_helper_fields - assert_field 'x_login', 'cody@example.com' - assert_field 'x_amount', '500.0' - assert_field 'x_fp_sequence', 'order-500' - end - - def test_customer_fields - @helper.customer :first_name => 'Cody', :last_name => 'Fauser', :email => 'cody@example.com' - assert_field 'x_first_name', 'Cody' - assert_field 'x_last_name', 'Fauser' - assert_field 'x_email', 'cody@example.com' - end - - def test_address_mapping - @helper.billing_address :address1 => '1 My Street', - :address2 => '', - :city => 'Leeds', - :state => 'Yorkshire', - :zip => 'LS2 7EE', - :country => 'CA' - assert_field 'x_address', '1 My Street' - assert_field 'x_city', 'Leeds' - assert_field 'x_state', 'Yorkshire' - assert_field 'x_zip', 'LS2 7EE' - end - - def test_unknown_address_mapping - @helper.billing_address :farm => 'CA' - assert_equal 8, @helper.fields.size - end - - def test_unknown_mapping - assert_nothing_raised do - @helper.company_address :address => '500 Dwemthy Fox Road' - end - end - - def test_setting_invalid_address_field - fields = @helper.fields.dup - @helper.billing_address :street => 'My Street' - assert_equal fields, @helper.fields - end -end diff --git a/test/unit/integrations/helpers/bogus_helper_test.rb b/test/unit/integrations/helpers/bogus_helper_test.rb deleted file mode 100644 index 6f39b9d8b2a..00000000000 --- a/test/unit/integrations/helpers/bogus_helper_test.rb +++ /dev/null @@ -1,28 +0,0 @@ -require 'test_helper' - -class BogusHelperTest < Test::Unit::TestCase - include ActiveMerchant::Billing::Integrations - - def setup - @helper = Bogus::Helper.new('order-500','cfauser', :amount => 500, :currency => 'CAD') - end - - def test_basic_helper_fields - assert_field 'order', 'order-500' - assert_field 'account', 'cfauser' - assert_field 'amount', '500' - assert_field 'currency', 'CAD' - end - - def test_customer_fields - @helper.customer :first_name => 'Cody', :last_name => 'Fauser' - assert_field 'first_name', 'Cody' - assert_field 'last_name', 'Fauser' - end - - def test_setting_unknown_field - fields = @helper.fields.dup - @helper.space_shuttle :name => 'Rockety' - assert_equal fields, @helper.fields - end -end diff --git a/test/unit/integrations/helpers/chronopay_helper_test.rb b/test/unit/integrations/helpers/chronopay_helper_test.rb deleted file mode 100644 index b2a279ab788..00000000000 --- a/test/unit/integrations/helpers/chronopay_helper_test.rb +++ /dev/null @@ -1,110 +0,0 @@ -require 'test_helper' - -class ChronopayHelperTest < Test::Unit::TestCase - include ActiveMerchant::Billing::Integrations - - def setup - @helper = Chronopay::Helper.new('order-500','003176-0001-0001', :amount => 500, :currency => 'CAD') - end - - def test_basic_helper_fields - assert_field 'cs1', 'order-500' - assert_field 'product_id', '003176-0001-0001' - assert_field 'product_price', '500' - assert_field 'product_price_currency', 'CAD' - end - - def test_customer_fields - @helper.customer :first_name => 'Cody', :last_name => 'Fauser' - assert_field 'f_name', 'Cody' - assert_field 's_name', 'Fauser' - end - - def test_address_mapping - @helper.billing_address :country => 'CAN', - :address1 => '1 My Street', - :city => 'Ottawa', - :state => 'On', - :zip => '90210' - - assert_field 'country', 'CAN' - assert_field 'street', '1 My Street' - assert_field 'state', 'On' - assert_field 'zip', '90210' - end - - def test_country_code_mapping - @helper.billing_address :country => 'CA' - assert_field 'country', 'CAN' - end - - def test_province_code_mapping_non_us - @helper.billing_address :country => 'DE', :state => 'Berlin' - assert_field 'country', 'DEU' - assert_field 'state', 'XX' - end - - def test_state_code_mapping_us - @helper.billing_address :country => 'US', :state => 'CA' - assert_field 'country', 'USA' - assert_field 'state', 'CA' - end - - def test_unknown_mapping - assert_nothing_raised do - @helper.company_address :address => '500 Dwemthy Fox Road' - end - end - - def test_setting_invalid_address_field - fields = @helper.fields.dup - @helper.billing_address :street => 'My Street' - - # Will still set the state code to 'XX' and language to 'EN' - fields['state'] = 'XX' - fields['language'] = 'EN' - assert_equal fields, @helper.fields - end - - - def test_sets_corresponding_checkout_language_for_country - @helper.billing_address :country => 'DEU' - assert_field 'language', 'DE' - - @helper.billing_address :country => 'RUS' - assert_field 'language', 'RU' - - @helper.billing_address :country => 'Spain' - assert_field 'language', 'ES' - - @helper.billing_address :country => 'Venezuela' - assert_field 'language', 'ES' - - @helper.billing_address :country => 'Portugal' - assert_field 'language', 'PT' - - @helper.billing_address :country => 'China' - assert_field 'language', 'CN1' - - @helper.billing_address :country => 'Latvia' - assert_field 'language', 'LV' - end - - def test_checkout_language_defaults_to_english - @helper.billing_address :country => 'USA' - assert_field 'language', 'EN' - - @helper.billing_address :country => 'Canada' - assert_field 'language', 'EN' - - @helper.billing_address :country => 'Great Britain' - assert_field 'language', 'EN' - - @helper.billing_address :country => 'Italy' - assert_field 'language', 'EN' - - @helper.billing_address :country => 'Japan' - assert_field 'language', 'EN' - end - -end diff --git a/test/unit/integrations/helpers/direc_pay_helper_test.rb b/test/unit/integrations/helpers/direc_pay_helper_test.rb deleted file mode 100644 index c03b2222148..00000000000 --- a/test/unit/integrations/helpers/direc_pay_helper_test.rb +++ /dev/null @@ -1,238 +0,0 @@ -require 'test_helper' - -class DirecPayHelperTest < Test::Unit::TestCase - include ActiveMerchant::Billing::Integrations - - def setup - @helper = DirecPay::Helper.new('#1234', 'account id', :amount => 500, :currency => 'INR') - end - - def test_basic_helper_fields - assert_field 'MID', 'account id' - assert_field 'Merchant Order No', '#1234' - - assert_field 'Amount', '5.00' - assert_field 'Currency', 'INR' - assert_field 'Country', 'IND' - end - - def test_customer_fields - @helper.customer :first_name => 'Cody', :last_name => 'Fauser', :email => 'cody@example.com' - assert_field 'custName', 'Cody Fauser' - assert_field 'custEmailId', 'cody@example.com' - end - - def test_billing_address_mapping - @helper.billing_address :address1 => '1 My Street', - :address2 => 'apartment 8', - :city => 'Leeds', - :state => 'Yorkshire', - :zip => 'LS2 7EE', - :country => 'IN' - - assert_field 'custAddress', '1 My Street apartment 8' - assert_field 'custCity', 'Leeds' - assert_field 'custState', 'Yorkshire' - assert_field 'custPinCode', 'LS2 7EE' - assert_field 'custCountry', 'IN' - end - - def test_address_with_a_single_street_address_field - @helper.billing_address :address1 => "1 My Street" - @helper.shipping_address :address1 => "1 My Street" - assert_field "custAddress", "1 My Street" - assert_field "deliveryAddress", "1 My Street" - end - - def test_address_with_two_street_address_fields - @helper.customer :first_name => "Bob", :last_name => "Biller" - @helper.billing_address :address1 => "1 Bill Street", :address2 => "Bill Address 2" - @helper.shipping_address :first_name => "Stan", :last_name => "Shipper", :address1 => "1 Ship Street", :address2 => "Ship Address 2" - assert_field 'custName', 'Bob Biller' - assert_field "custAddress", "1 Bill Street Bill Address 2" - assert_field 'deliveryName', 'Stan Shipper' - assert_field "deliveryAddress", "1 Ship Street Ship Address 2" - end - - def test_phone_number_for_billing_address - @helper.billing_address :phone => "+91 022 28000000" - assert_field 'custMobileNo', '91 022 28000000' - end - - def test_phone_number_for_shipping_address - @helper.shipping_address :phone => "+91 022 28000000" - assert_field 'deliveryMobileNo', '91 022 28000000' - end - - def test_land_line_phone_number_mapping_for_india - @helper.billing_address :phone2 => "+91 022 28000000", :country => 'IN' - - assert_field 'custPhoneNo1', '91' - assert_field 'custPhoneNo2', '022' - assert_field 'custPhoneNo3', '28000000' - end - - def test_land_line_phone_number_mapping_for_america - @helper.billing_address :phone2 => "6131234567", :country => 'CA' - - assert_field 'custPhoneNo1', '01' - assert_field 'custPhoneNo2', '613' - assert_field 'custPhoneNo3', '1234567' - end - - def test_land_line_phone_number_mapping_for_germany - @helper.billing_address :phone2 => "+49 2628 12345", :country => 'DE' - - assert_field 'custPhoneNo1', '49' - assert_field 'custPhoneNo2', '2628' - assert_field 'custPhoneNo3', '12345' - end - - def test_shipping_address_mapping - @helper.customer :first_name => 'Cody', :last_name => 'Fauser', :email => 'cody@example.com' - @helper.shipping_address :address1 => '1 My Street', - :address2 => 'apartment 8', - :city => 'Leeds', - :state => 'Yorkshire', - :zip => 'LS2 7EE', - :country => 'IN' - - assert_field 'deliveryAddress', '1 My Street apartment 8' - assert_field 'deliveryCity', 'Leeds' - assert_field 'deliveryState', 'Yorkshire' - assert_field 'deliveryPinCode', 'LS2 7EE' - assert_field 'deliveryCountry', 'IN' - assert_field 'deliveryName', 'Cody Fauser' - end - - def test_unknown_mapping - assert_nothing_raised do - @helper.company_address :address => '500 Dwemthy Fox Road' - end - end - - def test_setting_invalid_address_field - fields = @helper.fields.dup - @helper.billing_address :street => 'My Street' - assert_equal fields, @helper.fields - end - - def test_add_request_parameters - fill_in_transaction_details!(@helper) - - transaction_params = ['account id', 'DOM', 'IND', 'INR', '5.00', "#1234", 'NULL', "http://localhost/return", "http://localhost/return", "TOML"] - @helper.expects(:encode_value).with(transaction_params.join('|')).returns("dummy encoded value").twice - - @helper.form_fields - @helper.send(:add_request_parameters) - assert_field 'requestparameter', "dummy encoded value" - end - - def test_parameters_do_not_contain_special_characters - @helper.customer :first_name => "Bob", :last_name => "B & Ob", :email => 'bob@example.com' - @helper.description "50% discount for bob's order" - - @helper.form_fields.each do |name, value| - assert_no_match %r/[~'"&#%]/, value - end - end - - - def test_exported_form_fields - fill_in_transaction_details!(@helper) - - exported_fields = [ - "custAddress", - "custCity", - "custCountry", - "custEmailId", - "custMobileNo", - "custName", - "custPhoneNo1", - "custPhoneNo2", - "custPhoneNo3", - "custPinCode", - "custState", - "deliveryAddress", - "deliveryCity", - "deliveryCountry", - "deliveryMobileNo", - "deliveryName", - "deliveryPhNo1", - "deliveryPhNo2", - "deliveryPhNo3", - "deliveryPinCode", - "deliveryState", - "editAllowed", - "otherNotes", - "requestparameter" - ] - assert_equal exported_fields, @helper.form_fields.keys.sort - end - - def test_encode_value - # outofdate_expected = 'TVRqQXdPVEEwTWpneE1EQXdNREF4ZkVSUFRYeEpUa1I4U1U1U2ZEVTRMakF3ZkRJeWZERjhhSFIwY0RvdkwyeHZZMkZzYUc5emREb3pNREF3TDI5eVpHVnljeTh4TDJWa05USXpNRFk1Tm1Ga05USTFZamxsTXpJeVlUWmhOalJpTlRZek1qSmxMMlJ2Ym1VL2RYUnRYMjV2YjNabGNuSnBaR1U5TVh4b2RIUndPaTh2YUdGeVpHTnZjbVZuWVcxbGNpNXNiMk5oYkdodmMzUTZNekF3TUh4VVQwMU0=' - expected = 'TVRqQXdPVEEwTWpneE1EQXdNREF4ZkVSUFRYeEpUa1I4U1U1U2ZEVTRMakF3ZkRJeWZFNVZURXg4YUhSMGNEb3ZMMnh2WTJGc2FHOXpkRG96TURBd0wyOXlaR1Z5Y3k4eEwyVmtOVEl6TURZNU5tRmtOVEkxWWpsbE16SXlZVFpoTmpSaU5UWXpNakpsTDJSdmJtVS9kWFJ0WDI1dmIzWmxjbkpwWkdVOU1YeG9kSFJ3T2k4dmFHRnlaR052Y21WbllXMWxjaTVzYjJOaGJHaHZjM1E2TXpBd01IeFVUMDFN' - decoded = '200904281000001|DOM|IND|INR|58.00|22|NULL|http://localhost:3000/orders/1/ed5230696ad525b9e322a6a64b56322e/done?utm_nooverride=1|http://hardcoregamer.localhost:3000|TOML' - - encoded = @helper.send(:encode_value, decoded) - assert_equal expected, encoded - end - - def test_decode_value - expected = '200904281000001|DOM|IND|INR|58.00|22|1|http://localhost:3000/orders/1/ed5230696ad525b9e322a6a64b56322e/done?utm_nooverride=1|http://hardcoregamer.localhost:3000|TOML' - encoded = 'TVRqQXdPVEEwTWpneE1EQXdNREF4ZkVSUFRYeEpUa1I4U1U1U2ZEVTRMakF3ZkRJeWZERjhhSFIwY0RvdkwyeHZZMkZzYUc5emREb3pNREF3TDI5eVpHVnljeTh4TDJWa05USXpNRFk1Tm1Ga05USTFZamxsTXpJeVlUWmhOalJpTlRZek1qSmxMMlJ2Ym1VL2RYUnRYMjV2YjNabGNuSnBaR1U5TVh4b2RIUndPaTh2YUdGeVpHTnZjbVZuWVcxbGNpNXNiMk5oYkdodmMzUTZNekF3TUh4VVQwMU0=' - - decoded = @helper.send(:decode_value, encoded) - assert_equal expected, decoded - end - - def test_failure_url - @helper.return_url = "http://localhost/return" - @helper.failure_url = "http://localhost/fail" - - assert_field 'Failure URL', "http://localhost/fail" - end - - def test_failure_url_is_set_to_return_url_if_not_provided - @helper.return_url = "http://localhost/return" - @helper.form_fields - assert_field 'Failure URL', "http://localhost/return" - end - - - def test_status_supports_ssl_get - assert DirecPay::Status.new('dummy-account').respond_to?(:ssl_get) - end - - def test_status_update_in_production_mode - ActiveMerchant::Billing::Base.integration_mode = :production - params = "dummy-authorization|1234|http://localhost/notify" - DirecPay::Status.any_instance.expects(:ssl_get).with("https://www.timesofmoney.com/direcpay/secure/dpPullMerchAtrnDtls.jsp?requestparams=#{CGI.escape(params)}") - - DirecPay::Status.new(1234, :test => false).update("dummy-authorization", "http://localhost/notify") - ActiveMerchant::Billing::Base.integration_mode = :test - end - - def test_status_update_in_test_mode - params = "dummy-authorization|1234|http://localhost/notify" - DirecPay::Status.any_instance.expects(:ssl_get).with("https://test.direcpay.com/direcpay/secure/dpMerchantTransaction.jsp?requestparams=#{CGI.escape(params)}") - - DirecPay::Status.new(1234, :test => true).update("dummy-authorization", "http://localhost/notify") - end - - - - private - - def fill_in_transaction_details!(helper) - helper.customer :first_name => 'Carl', :last_name => 'Carlton', :email => 'carlton@example.com' - helper.description = "blabla" - - indian_address = address(:country => "India", :phone => "9122028000000", :phone2 => '+1 613 123 4567') - helper.shipping_address(indian_address) - helper.billing_address(indian_address) - - helper.return_url = "http://localhost/return" - end -end diff --git a/test/unit/integrations/helpers/directebanking_helper_test.rb b/test/unit/integrations/helpers/directebanking_helper_test.rb deleted file mode 100644 index 6761fe62d1a..00000000000 --- a/test/unit/integrations/helpers/directebanking_helper_test.rb +++ /dev/null @@ -1,52 +0,0 @@ -require 'test_helper' - -class DirectebankingHelperTest < Test::Unit::TestCase - include ActiveMerchant::Billing::Integrations - - def setup - @helper = Directebanking::Helper.new('order-500','UserID-24352435', :credential2 => "ProjectID-1234", - :amount => 500, :currency => 'EUR', :credential3 => "mysecretString") - @helper.return_url "https://localhost:8080/directebanking" - end - - def test_urls - @helper.cancel_return_url "https://localhost:8080/directebanking/cancel" - @helper.notify_url "https://localhost:8080/directebanking/notify" - - assert_field 'user_variable_1', "https://localhost:8080/directebanking" - assert_field 'user_variable_2', "https://localhost:8080/directebanking/cancel" - assert_field 'user_variable_3', "https://localhost:8080/directebanking/notify" - end - - def test_basic_helper_fields - @helper.description "My order #1234" - assert_field 'user_id', 'UserID-24352435' - assert_field 'project_id', 'ProjectID-1234' - assert_field 'amount', '5.00' - assert_field 'user_variable_0', 'order-500' - assert_field 'reason_1', 'My order #1234' - end - - def test_generate_signature_string - assert_equal "UserID-24352435|ProjectID-1234|||||5.00|EUR|||order-500|https://localhost:8080/directebanking|||||mysecretString", - @helper.generate_signature_string - end - - def test_generate_signature - assert !@helper.form_fields['hash'].empty? - assert_equal 'c34113bc04eb28a045fe5c2b1e9e186fe3cde03b', @helper.generate_signature - assert_equal "c34113bc04eb28a045fe5c2b1e9e186fe3cde03b", @helper.form_fields['hash'] - end - - def test_unknown_mapping - assert_nothing_raised do - @helper.company_address :address => '501 Dwemthy Fox Road' - end - end - - def test_setting_invalid_address_field - fields = @helper.fields.dup - @helper.billing_address :street => 'My Street' - assert_equal fields, @helper.fields - end -end diff --git a/test/unit/integrations/helpers/dotpay_helper_test.rb b/test/unit/integrations/helpers/dotpay_helper_test.rb deleted file mode 100644 index 32a8b3202a3..00000000000 --- a/test/unit/integrations/helpers/dotpay_helper_test.rb +++ /dev/null @@ -1,107 +0,0 @@ -require 'test_helper' - -class DotpayHelperTest < Test::Unit::TestCase - include ActiveMerchant::Billing::Integrations - - def setup - @helper = Dotpay::Helper.new('order-500', '42655', :amount => 500) - @url = 'http://someuri.com/return.html' - end - - def test_basic_helper_fields - assert_field 'id', '42655' - assert_field 'lang', 'PL' - assert_field 'description', 'order-500' - assert_field 'currency', 'PLN' - end - - def test_customer_fields - @helper.customer :firstname => 'Przemyslaw', :lastname => 'Ciacka', :email => 'przemek@example.com' - assert_field 'firstname', 'Przemyslaw' - assert_field 'lastname', 'Ciacka' - assert_field 'email', 'przemek@example.com' - end - - def test_address_mapping - @helper.billing_address :street => 'Malborska', - :street_n1 => '130', - :city => 'Cracow', - :postcode => '30-624' - - assert_field 'street', 'Malborska' - assert_field 'street_n1', '130' - assert_field 'city', 'Cracow' - assert_field 'postcode', '30-624' - assert_field 'country', 'POL' - end - - def test_description - @helper.description = 'Order 500/2012' - assert_field 'description', 'Order 500/2012' - end - - def test_channel - assert_field 'channel', '0' - @helper.channel = '2' - assert_field 'channel', '2' - end - - def test_ch_lock - assert_field 'ch_lock', '0' - @helper.ch_lock = '1' - assert_field 'ch_lock', '1' - end - - def test_onlinetransfer - assert_field 'onlinetransfer', '0' - @helper.onlinetransfer = 1 - assert_field 'onlinetransfer', '1' - end - - def test_url - @helper.url = @url - assert_field 'url', @url - end - - def test_urlc - @helper.urlc = @url - assert_field 'urlc', @url - end - - def test_type - assert_field 'type', '2' - @helper.type = '3' - assert_field 'type', '3' - end - - def test_buttontext - @helper.buttontext = 'Return to the shop' - assert_field 'buttontext', 'Return to the shop' - end - - def test_control - @helper.control = 'ThisISSOMEControlP@rameter' - assert_field 'control', 'ThisISSOMEControlP@rameter' - end - - def test_code - @helper.code = 'somecode' - assert_field 'code', 'somecode' - end - - def test_p_info - @helper.p_info = 'Company Name' - assert_field 'p_info', 'Company Name' - end - - def test_p_email - @helper.p_email = 'company@email.com' - assert_field 'p_email', 'company@email.com' - end - - def test_tax - assert_field 'tax', '0' - @helper.tax = '1' - assert_field 'tax', '1' - end -end diff --git a/test/unit/integrations/helpers/dwolla_helper_test.rb b/test/unit/integrations/helpers/dwolla_helper_test.rb deleted file mode 100644 index 6639441690b..00000000000 --- a/test/unit/integrations/helpers/dwolla_helper_test.rb +++ /dev/null @@ -1,41 +0,0 @@ -require 'test_helper' -require 'digest/sha1' - -class DwollaHelperTest < Test::Unit::TestCase - include ActiveMerchant::Billing::Integrations - - def setup - @helper = Dwolla::Helper.new('order-500','812-546-3855', :credential2 => 'mykey', :credential3 => 'mysecret', :amount => 500, :currency => 'USD') - end - - def test_basic_helper_fields - assert_field 'amount', '500' - assert_field 'orderid', 'order-500' - assert_field 'destinationid', '812-546-3855' - assert_field 'key', 'mykey' - assert_field 'timestamp', "1370726016" - - expected_signature = "4eb42d8635547a0bc9f1b23615bf92c89f12f606" - assert_field 'signature', expected_signature - end - - def test_other_fields - @helper.return_url 'http://test.com/ecommerce/redirect.aspx' - @helper.notify_url 'http://test.com/test/callback' - @helper.test_mode true - @helper.description 'Store Purchase Description' - @helper.shipping 0.00 - @helper.tax 0.00 - - assert_field 'key', 'mykey' - assert_field 'destinationid', '812-546-3855' - assert_field 'redirect', 'http://test.com/ecommerce/redirect.aspx' - assert_field 'callback', 'http://test.com/test/callback' - assert_field 'test', 'true' - assert_field 'description', 'Store Purchase Description' - assert_field 'destinationid', '812-546-3855' - assert_field 'shipping', '0.0' - assert_field 'tax', '0.0' - assert_field 'allowFundingSources', 'true' - end -end diff --git a/test/unit/integrations/helpers/e_payment_plans_helper_test.rb b/test/unit/integrations/helpers/e_payment_plans_helper_test.rb deleted file mode 100644 index e8ccb703ed9..00000000000 --- a/test/unit/integrations/helpers/e_payment_plans_helper_test.rb +++ /dev/null @@ -1,56 +0,0 @@ -require 'test_helper' - -class EPaymentPlansHelperTest < Test::Unit::TestCase - include ActiveMerchant::Billing::Integrations - - def setup - @helper = EPaymentPlans::Helper.new('order-500','cody@example.com', :amount => 500, :currency => 'USD') - end - - def test_basic_helper_fields - assert_field 'order[account]', 'cody@example.com' - - assert_field 'order[amount]', '500' - assert_field 'order[num]', 'order-500' - end - - def test_customer_fields - @helper.customer :first_name => 'Cody', :last_name => 'Fauser', :email => 'cody@example.com' - assert_field 'order[first_name]', 'Cody' - assert_field 'order[last_name]', 'Fauser' - assert_field 'order[email]', 'cody@example.com' - end - - def test_address_mapping - @helper.billing_address :address1 => '1 My Street', - :address2 => '', - :company => 'Shopify', - :city => 'Leeds', - :state => 'Yorkshire', - :zip => 'LS2 7EE', - :country => 'CA' - - assert_field 'order[address1]', '1 My Street' - assert_field 'order[city]', 'Leeds' - assert_field 'order[company]', 'Shopify' - assert_field 'order[state]', 'Yorkshire' - assert_field 'order[zip]', 'LS2 7EE' - end - - def test_unknown_address_mapping - @helper.billing_address :farm => 'CA' - assert_equal 3, @helper.fields.size - end - - def test_unknown_mapping - assert_nothing_raised do - @helper.company_address :address => '500 Dwemthy Fox Road' - end - end - - def test_setting_invalid_address_field - fields = @helper.fields.dup - @helper.billing_address :street => 'My Street' - assert_equal fields, @helper.fields - end -end diff --git a/test/unit/integrations/helpers/easy_pay_helper_test.rb b/test/unit/integrations/helpers/easy_pay_helper_test.rb deleted file mode 100644 index 2100b96bd7f..00000000000 --- a/test/unit/integrations/helpers/easy_pay_helper_test.rb +++ /dev/null @@ -1,19 +0,0 @@ -require 'test_helper' - -class EasyPayHelperTest < Test::Unit::TestCase - include ActiveMerchant::Billing::Integrations - - def setup - @helper = EasyPay::Helper.new(123, 'test_account', :amount => 500, :credential2 => 'secret') - end - - def test_basic_helper_fields - assert_field 'EP_MerNo', 'test_account' - assert_field 'EP_Sum', '500' - assert_field 'EP_OrderNo', '123' - end - - def test_request_signature_string - assert_equal 'test_accountsecret123500', @helper.request_signature_string - end -end diff --git a/test/unit/integrations/helpers/epay_helper_test.rb b/test/unit/integrations/helpers/epay_helper_test.rb deleted file mode 100644 index 711b9bddb21..00000000000 --- a/test/unit/integrations/helpers/epay_helper_test.rb +++ /dev/null @@ -1,39 +0,0 @@ -require 'test_helper' - -class EpayHelperTest < Test::Unit::TestCase - include ActiveMerchant::Billing::Integrations - - def setup - @helper = Epay::Helper.new('order-500','99999999', :amount => 500, :currency => 'DKK') - @helper.md5secret "secretmd5" - @helper.return_url 'http://example.com/ok' - @helper.cancel_return_url 'http://example.com/cancel' - @helper.notify_url 'http://example.com/notify' - end - - def test_basic_helper_fields - assert_field 'merchantnumber', '99999999' - assert_field 'amount', '500' - assert_field 'orderid', 'order500' - end - - def test_generate_md5string - assert_equal 'http://example.com/ok500http://example.com/notifyhttp://example.com/cancelDKK099999999order5003secretmd5', @helper.generate_md5string - end - - def test_generate_md5hash - assert_equal '251c2f80d1dcd120a87a2480025714cb', @helper.generate_md5hash - end - - def test_unknown_mapping - assert_nothing_raised do - @helper.company_address :address => '500 Dwemthy Fox Road' - end - end - - def test_setting_invalid_address_field - fields = @helper.fields.dup - @helper.billing_address :street => 'My Street' - assert_equal fields, @helper.fields - end -end diff --git a/test/unit/integrations/helpers/first_data_helper_test.rb b/test/unit/integrations/helpers/first_data_helper_test.rb deleted file mode 100644 index f54815b79a5..00000000000 --- a/test/unit/integrations/helpers/first_data_helper_test.rb +++ /dev/null @@ -1,53 +0,0 @@ -require 'test_helper' - -class FirstDataHelperTest < Test::Unit::TestCase - include ActiveMerchant::Billing::Integrations - - def setup - # currency is currently ignored... - @helper = FirstData::Helper.new('order-500','cody@example.com', :amount => 500, :currency => 'USD') - end - - def test_basic_helper_fields - assert_field 'x_login', 'cody@example.com' - assert_field 'x_amount', '500.0' - assert_field 'x_fp_sequence', 'order-500' - end - - def test_customer_fields - @helper.customer :first_name => 'Cody', :last_name => 'Fauser', :email => 'cody@example.com' - assert_field 'x_first_name', 'Cody' - assert_field 'x_last_name', 'Fauser' - assert_field 'x_email', 'cody@example.com' - end - - def test_address_mapping - @helper.billing_address :address1 => '1 My Street', - :address2 => '', - :city => 'Leeds', - :state => 'Yorkshire', - :zip => 'LS2 7EE', - :country => 'CA' - assert_field 'x_address', '1 My Street' - assert_field 'x_city', 'Leeds' - assert_field 'x_state', 'Yorkshire' - assert_field 'x_zip', 'LS2 7EE' - end - - def test_unknown_address_mapping - @helper.billing_address :farm => 'CA' - assert_equal 8, @helper.fields.size - end - - def test_unknown_mapping - assert_nothing_raised do - @helper.company_address :address => '500 Dwemthy Fox Road' - end - end - - def test_setting_invalid_address_field - fields = @helper.fields.dup - @helper.billing_address :street => 'My Street' - assert_equal fields, @helper.fields - end -end diff --git a/test/unit/integrations/helpers/gestpay_helper_test.rb b/test/unit/integrations/helpers/gestpay_helper_test.rb deleted file mode 100644 index 163d0951b83..00000000000 --- a/test/unit/integrations/helpers/gestpay_helper_test.rb +++ /dev/null @@ -1,100 +0,0 @@ -require 'test_helper' - -class GestpayHelperTest < Test::Unit::TestCase - include ActiveMerchant::Billing::Integrations - - def setup - @helper = Gestpay::Helper.new('order-500','1234567', :amount => '5.00', :currency => 'EUR') - end - - def test_basic_helper_fields - assert_field 'ShopLogin', '1234567' - assert_field 'PAY1_AMOUNT', '5.00' - assert_field 'PAY1_SHOPTRANSACTIONID', 'order-500' - assert_field 'PAY1_UICCODE', '242' - end - - def test_italian_currency - @helper = Gestpay::Helper.new('order-500','1234567', :amount => '5.00', :currency => 'ITL') - assert_field 'PAY1_UICCODE', '18' - end - - def test_invalid_currency - assert_raise(StandardError) do - Gestpay::Helper.new('order-500','1234567', :amount => '5.00', :currency => 'CAD') - end - end - - def test_customer_fields - @helper.customer :first_name => 'Cody', :last_name => 'Fauser', :email => 'cody@example.com' - assert_field 'PAY1_CHNAME', 'Cody Fauser' - assert_field 'PAY1_CHEMAIL', 'cody@example.com' - end - - def test_get_encryption_string - @helper.expects(:ssl_get).returns(encrypted_string_response) - assert_equal encrypted_string, @helper.send(:get_encrypted_string) - end - - def test_get_encryption_string_fails - @helper.expects(:ssl_get).returns('#error#1132-Not accepted call: shop is not in active state#/error#\r\n') - - assert_raise(StandardError) do - @helper.send(:get_encrypted_string) - end - end - - def test_get_encryption_string_returns_empty_response - @helper.expects(:ssl_get).returns('') - - assert_raise(StandardError) do - @helper.send(:get_encrypted_string) - end - end - - def test_form_fields - @helper.expects(:ssl_get).returns(encrypted_string_response) - assert_equal '1234567', @helper.form_fields['a'] - assert_equal encrypted_string, @helper.form_fields['b'] - end - - # Doesn't do any address mapping - def test_address_mapping - assert_nothing_raised do - @helper.billing_address :address1 => '1 My Street', - :address2 => '', - :city => 'Leeds', - :state => 'Yorkshire', - :zip => 'LS2 7EE', - :country => 'CA' - end - end - - def test_unknown_address_mapping - total_fields = @helper.fields.size - @helper.billing_address :farm => 'CA' - assert_equal total_fields, @helper.fields.size - end - - def test_unknown_mapping - assert_nothing_raised do - @helper.company_address :address => '500 Dwemthy Fox Road' - end - end - - def test_setting_invalid_address_field - fields = @helper.fields.dup - @helper.billing_address :street => 'My Street' - assert_equal fields, @helper.fields - end - - private - - def encrypted_string_response - '#cryptstring#F7DEB36478FD84760F9134F23C922697272D57DE6D4518EB9B4D468B769D9A3A8071B6EB160B35CB412FC1820C7CC12D17B3141855B1ED55468613702A2E213DDE9DE5B0209E13C416448AE833525959F05693172D7F0656#/cryptstring#' - end - - def encrypted_string - 'F7DEB36478FD84760F9134F23C922697272D57DE6D4518EB9B4D468B769D9A3A8071B6EB160B35CB412FC1820C7CC12D17B3141855B1ED55468613702A2E213DDE9DE5B0209E13C416448AE833525959F05693172D7F0656' - end -end diff --git a/test/unit/integrations/helpers/hi_trust_helper_test.rb b/test/unit/integrations/helpers/hi_trust_helper_test.rb deleted file mode 100644 index 495d9d1ce29..00000000000 --- a/test/unit/integrations/helpers/hi_trust_helper_test.rb +++ /dev/null @@ -1,16 +0,0 @@ -require 'test_helper' - -class HiTrustHelperTest < Test::Unit::TestCase - include ActiveMerchant::Billing::Integrations - - def setup - @helper = HiTrust::Helper.new('order-500','cody@example.com', :amount => 500, :currency => 'USD') - end - - def test_basic_helper_fields - assert_field 'storeid', 'cody@example.com' - assert_field 'amount', '500' - assert_field 'ordernumber', 'order-500' - assert_field 'currency', 'USD' - end -end diff --git a/test/unit/integrations/helpers/ipay88_helper_test.rb b/test/unit/integrations/helpers/ipay88_helper_test.rb deleted file mode 100644 index 675ba816728..00000000000 --- a/test/unit/integrations/helpers/ipay88_helper_test.rb +++ /dev/null @@ -1,103 +0,0 @@ -require "test_helper" - -class Ipay88HelperTest < Test::Unit::TestCase - include ActiveMerchant::Billing::Integrations - - def setup - @helper = Ipay88::Helper.new("order-500", "ipay88merchcode", :credential2 => "abc", :amount => 500, :currency => "MYR") - end - - def test_basic_helper_fields - assert_field "MerchantCode", "ipay88merchcode" - assert_field "Amount", "5.00" - assert_field "RefNo", "order-500" - assert_field "Currency", "MYR" - end - - def test_customer_fields - @helper.customer :first_name => "John", :last_name => "Doe", :email => "john@example.com", :phone => "+60128888888" - assert_field "UserName", "John Doe" - assert_field "UserEmail", "john@example.com" - assert_field "UserContact", "+60128888888" - end - - def test_product_fields - @helper.description "TiC Store Purchase" - assert_field "ProdDesc", "TiC Store Purchase" - end - - def test_supported_currency - %w[MYR USD CNY].each do |cur| - @helper.currency cur - assert_field "Currency", cur - end - end - - def test_unsupported_currency - assert_raise ArgumentError do - @helper.currency "FOO" - end - end - - def test_remark - @helper.remark "Remarkable remark" - assert_field "Remark", "Remarkable remark" - end - - def test_supported_lang - %w[ISO-8859-1 UTF-8 GB2312 GD18030 BIG5].each do |lang| - @helper.language lang - assert_field "Lang", lang - end - end - - def test_unsupported_lang - assert_raise ArgumentError do - @helper.language "KLINGON" - end - end - - def test_return_url - @helper.return_url "http://www.example.com" - assert_field "ResponseURL", "http://www.example.com" - end - - def test_supported_payment - @helper.payment 6 # 6 => m2u - assert_field "PaymentId", "6" - end - - def test_unsupported_payment - assert_raise ArgumentError do - @helper.payment 999 - end - end - - def test_signature - assert_field "Signature", "vDwWN/XHvYnlReq3f1llHFCxDTY=" - end - - def test_valid_amount - @helper.amount = 100 - assert_field "Amount", "1.00" - end - - def test_invalid_amount_as_string - assert_raise ArgumentError do - @helper.amount = "1.00" - end - end - - def test_invalid_amount_as_negative_integer_in_cents - assert_raise ArgumentError do - @helper.amount = -100 - end - end - - def test_sig_components_amount_doesnt_include_decimal_points - @helper.amount = 0.5 - assert_equal "abcipay88merchcodeorder-50005MYR", @helper.send(:sig_components) - @helper.amount = 12.34 - assert_equal "abcipay88merchcodeorder-5001234MYR", @helper.send(:sig_components) - end -end diff --git a/test/unit/integrations/helpers/liqpay_helper_test.rb b/test/unit/integrations/helpers/liqpay_helper_test.rb deleted file mode 100644 index 092d6420c1d..00000000000 --- a/test/unit/integrations/helpers/liqpay_helper_test.rb +++ /dev/null @@ -1,35 +0,0 @@ -require 'test_helper' - -class LiqpayHelperTest < Test::Unit::TestCase - include ActiveMerchant::Billing::Integrations - - def setup - @options = { :service => :liqpay, - :secret => @key, - :amount => 157.0 } - @form_fields = { :merchant_id => 'i15520', - :secret => '3HSi', - :pay_way => 'card', - :currency => 'RUR', - :order_id => '12', - :description => 'Test payment', - :default_phone => '+71231212123', - :server_url => 'http://t/liqpay/server_url?&', - :result_url => 'http://t/liqpay/result_url?&' } - - @helper = Liqpay::Helper.new(@options, nil) - end - - def test_form_fields - @helper.payment_service_for(44, @username, @form_options) do |service| - @form_fields.each do |key, value| - service.add_field(key, value) - end - end - - fields = @helper.form_fields - - assert fields['operation_xml'].present? - assert fields['signature'].present? - end -end diff --git a/test/unit/integrations/helpers/maksuturva_helper_test.rb b/test/unit/integrations/helpers/maksuturva_helper_test.rb deleted file mode 100644 index 1755754cb27..00000000000 --- a/test/unit/integrations/helpers/maksuturva_helper_test.rb +++ /dev/null @@ -1,98 +0,0 @@ -require "test_helper" - -class MaksuturvaHelperTest < Test::Unit::TestCase - include ActiveMerchant::Billing::Integrations - - def setup - @helper = Maksuturva::Helper.new("2","testikauppias", :amount => "200,00", :currency => "EUR", :credential2 => "11223344556677889900") - end - - def test_basic_helper_fields - assert_field "pmt_id", "2" - - assert_field "pmt_amount", "200,00" - assert_field "pmt_currency", "EUR" - assert_field "pmt_action", "NEW_PAYMENT_EXTENDED" - assert_field "pmt_sellerid", "testikauppias" - end - - def test_customer_fields - @helper.customer :first_name => "Cody", :last_name => "Fauser", :email => "cody@example.com" - assert_field "pmt_buyeremail", "cody@example.com" - end - - def test_address_mapping - @helper.billing_address :address1 => "1 My Street", - :address2 => "", - :city => "Leeds", - :zip => "LS2 7EE", - :country => "CA" - - assert_field "pmt_buyeraddress", "1 My Street" - assert_field "pmt_buyercity", "Leeds" - assert_field "pmt_buyerpostalcode", "LS2 7EE" - assert_field "pmt_buyercountry", "CA" - end - - def test_authcode_generation - @helper.customer :email => 'antti@example.com', :phone => "0401234556" - @helper.billing_address :address1 => '1 My Street', - :address2 => '', - :city => 'Helsinki', - :state => '-', - :zip => '00180', - :country => 'Finland' - @helper.pmt_reference = "134662" - @helper.pmt_duedate = "24.06.2012" - @helper.pmt_reference = "134662" - @helper.pmt_duedate = "24.06.2012" - - @helper.pmt_orderid = "2" - @helper.pmt_buyername = "Antti Akonniemi" - @helper.pmt_deliveryname = "Antti Akonniemi" - @helper.pmt_deliveryaddress = "1 My Street" - @helper.pmt_deliverypostalcode = "00180" - @helper.pmt_deliverycity = "Helsinki" - @helper.pmt_deliverycountry = "FI" - @helper.pmt_rows = 1 - @helper.pmt_row_name1 = "testi" - @helper.pmt_row_desc1 = "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum." - @helper.pmt_row_articlenr1 = "1" - @helper.pmt_row_quantity1 = "1" - @helper.pmt_row_deliverydate1 = "26.6.2012" - @helper.pmt_row_price_gross1 = "200,00" - @helper.pmt_row_vat1= "23,00" - @helper.pmt_row_discountpercentage1 = "0,00" - @helper.pmt_row_type1 = "1" - @helper.pmt_charset = "UTF-8" - @helper.pmt_charsethttp = "UTF-8" - - @helper.return_url "http://localhost/pages/process" - @helper.cancel_return_url "http://example.com" - @helper.pmt_errorreturn "http://example.com" - - @helper.pmt_delayedpayreturn "http://example.com" - @helper.pmt_escrow "N" - @helper.pmt_escrowchangeallowed "N" - @helper.pmt_sellercosts "0,00" - @helper.pmt_keygeneration "001" - assert_equal @helper.generate_md5string, "DD27A6D63F47FEFE7743EFA68D1C397D" - end - - def test_unknown_address_mapping - @helper.billing_address :farm => "CA" - assert_equal 7, @helper.fields.size - end - - def test_unknown_mapping - assert_nothing_raised do - @helper.company_address :address => "500 Dwemthy Fox Road" - end - end - - def test_setting_invalid_address_field - fields = @helper.fields.dup - @helper.billing_address :street => "My Street" - assert_equal fields, @helper.fields - end -end diff --git a/test/unit/integrations/helpers/moneybookers_helper_test.rb b/test/unit/integrations/helpers/moneybookers_helper_test.rb deleted file mode 100644 index e18b0ef037d..00000000000 --- a/test/unit/integrations/helpers/moneybookers_helper_test.rb +++ /dev/null @@ -1,98 +0,0 @@ -require 'test_helper' - -class MoneybookersHelperTest < Test::Unit::TestCase - include ActiveMerchant::Billing::Integrations - - def setup - Moneybookers::Helper.application_id = 'ActiveMerchant' - @helper = Moneybookers::Helper.new('order-500','cody@example.com', :amount => 500, :currency => 'USD') - end - - def test_basic_helper_fields - assert_field 'hide_login', '1' - assert_field 'pay_to_email', 'cody@example.com' - assert_field 'amount', '500' - assert_field 'transaction_id', 'order-500' - end - - def test_customer_fields - @helper.customer :first_name => 'Cody', :last_name => 'Fauser', :email => 'cody@example.com' - assert_field 'firstname', 'Cody' - assert_field 'lastname', 'Fauser' - assert_field 'pay_to_email', 'cody@example.com' - end - - def test_address_mapping - @helper.billing_address :address1 => '1 My Street', - :address2 => '', - :city => 'Leeds', - :state => 'Yorkshire', - :zip => 'LS2 7EE', - :country => 'CA' - - assert_field 'address', '1 My Street' - assert_field 'city', 'Leeds' - assert_field 'state', 'Yorkshire' - assert_field 'postal_code', 'LS2 7EE' - end - - def test_unknown_address_mapping - total_fields = @helper.fields.size - @helper.billing_address :farm => 'CA' - assert_equal total_fields, @helper.fields.size - end - - def test_unknown_mapping - assert_nothing_raised do - @helper.company_address :address => '500 Dwemthy Fox Road' - end - end - - def test_setting_invalid_address_field - fields = @helper.fields.dup - @helper.billing_address :street => 'My Street' - assert_equal fields, @helper.fields - end - - def test_tracking_token - Moneybookers::Helper.application_id = '123' - @helper = Moneybookers::Helper.new('order-500','cody@example.com', :amount => 500, :currency => 'USD') - assert_field 'merchant_fields', 'platform' - assert_field 'platform', '123' - end - - def test_tracking_token_not_added_by_default - assert_nil @helper.fields['merchant_fields'] - assert_nil @helper.fields['platform'] - end - - def test_country - @helper = Moneybookers::Helper.new('order-500','cody@example.com', :amount => 500, :currency => 'USD', :country => 'GBR') - assert_field 'country', 'GBR' - - @helper = Moneybookers::Helper.new('order-500','cody@example.com', :amount => 500, :currency => 'USD', :country => 'GB') - assert_field 'country', 'GBR' - - @helper = Moneybookers::Helper.new('order-500','cody@example.com', :amount => 500, :currency => 'USD', :country => 'United Kingdom') - assert_field 'country', 'GBR' - end - - def test_account_name - @helper = Moneybookers::Helper.new('order-500','cody@example.com', :amount => 500, :currency => 'USD', :account_name => 'My account name') - assert_field 'recipient_description', "My account name" - end - - def test_language - - @helper = Moneybookers::Helper.new('order-500', 'cody@example.com', :amount => 500, :currency => 'USD', :country => 'DK') - assert_field 'language', 'DA' - - # Country with supported language (non-mapped) - @helper = Moneybookers::Helper.new('order-500', 'cody@example.com', :amount => 500, :currency => 'USD', :country => 'PL') - assert_field 'language', 'PL' - - # Country with unsupported language - @helper = Moneybookers::Helper.new('order-500', 'cody@example.com', :amount => 500, :currency => 'USD', :country => 'CA') - assert_field 'language', 'EN' - end -end diff --git a/test/unit/integrations/helpers/nochex_helper_test.rb b/test/unit/integrations/helpers/nochex_helper_test.rb deleted file mode 100644 index 6190756f4c0..00000000000 --- a/test/unit/integrations/helpers/nochex_helper_test.rb +++ /dev/null @@ -1,53 +0,0 @@ -require 'test_helper' - -class NochexHelperTest < Test::Unit::TestCase - include ActiveMerchant::Billing::Integrations - - def setup - @helper = Nochex::Helper.new('order-500','cody@example.com', :amount => 500, :currency => 'GBP') - end - - def test_basic_helper_fields - assert_field 'email', 'cody@example.com' - - # Nochex requires 2 decimal places on the amount - assert_field 'amount', '5.00' - assert_field 'ordernumber', 'order-500' - end - - def test_customer_fields - @helper.customer :first_name => 'Cody', :last_name => 'Fauser', :email => 'cody@example.com' - assert_field 'firstname', 'Cody' - assert_field 'lastname', 'Fauser' - assert_field 'email_address_sender', 'cody@example.com' - end - - def test_address_mapping - @helper.billing_address :address1 => '1 My Street', - :city => 'Leeds', - :state => 'Yorkshire', - :zip => 'LS2 7EE' - - assert_field 'firstline', '1 My Street' - assert_field 'town', 'Leeds' - assert_field 'county', 'Yorkshire' - assert_field 'postcode', 'LS2 7EE' - end - - def test_unknown_address_mapping - @helper.billing_address :country => 'CA' - assert_equal 3, @helper.fields.size - end - - def test_unknown_mapping - assert_nothing_raised do - @helper.company_address :address => '500 Dwemthy Fox Road' - end - end - - def test_setting_invalid_address_field - fields = @helper.fields.dup - @helper.billing_address :street => 'My Street' - assert_equal fields, @helper.fields - end -end diff --git a/test/unit/integrations/helpers/paxum_helper_test.rb b/test/unit/integrations/helpers/paxum_helper_test.rb deleted file mode 100644 index 18bbd65756e..00000000000 --- a/test/unit/integrations/helpers/paxum_helper_test.rb +++ /dev/null @@ -1,25 +0,0 @@ -require 'test_helper' - -class PaxumHelperTest < Test::Unit::TestCase - include ActiveMerchant::Billing::Integrations - - def setup - @helper = Paxum::Helper.new( - 123, 'test_account', - :description => "Order description", - :amount => 500, - :currency => 'USD', - :fail_url => "http://example.com/fail_url", - :success_url => "http://example.com/success_url", - :result_url => "http://example.com/result_url" - ) - end - - def test_basic_helper_fields - assert_field 'business_email', 'test_account' - - assert_field 'amount', '500' - assert_field 'item_id', '123' - assert_field 'currency', 'USD' - end -end diff --git a/test/unit/integrations/helpers/pay_fast_helper_test.rb b/test/unit/integrations/helpers/pay_fast_helper_test.rb deleted file mode 100644 index 70eab62f374..00000000000 --- a/test/unit/integrations/helpers/pay_fast_helper_test.rb +++ /dev/null @@ -1,35 +0,0 @@ -require 'test_helper' - -class PayFastHelperTest < Test::Unit::TestCase - include ActiveMerchant::Billing::Integrations - - def setup - @helper = PayFast::Helper.new(123, '10000100', :amount => 500, :credential2 => '46f0cd694581a', :credential3 => '0a1e2e10-03a7-4928-af8a-fbdfdfe31d43') - end - - def assing_required_fields - @helper.item_name = 'ZOMG' - @helper.notify_url = 'http://test.com/pay_fast/paid' - end - - def test_basic_helper_fields - assing_required_fields - - assert_field 'merchant_id', '10000100' - assert_field 'merchant_key', '46f0cd694581a' - assert_field 'notify_url', 'http://test.com/pay_fast/paid' - assert_field 'amount', '500' - assert_field 'm_payment_id', '123' - assert_field 'item_name', 'ZOMG' - end - - def test_request_signature_string - assing_required_fields - - assert_equal 'merchant_id=10000100&merchant_key=46f0cd694581a&notify_url=http%3A%2F%2Ftest.com%2Fpay_fast%2Fpaid&m_payment_id=123&amount=500&item_name=ZOMG', @helper.request_signature_string - end - - def test_request_generated_signature - assert_equal '45aa67464a46c9cf837257365866b247', @helper.generate_signature(:request) - end -end diff --git a/test/unit/integrations/helpers/payflow_link_helper_test.rb b/test/unit/integrations/helpers/payflow_link_helper_test.rb deleted file mode 100644 index 40f7a9402ed..00000000000 --- a/test/unit/integrations/helpers/payflow_link_helper_test.rb +++ /dev/null @@ -1,223 +0,0 @@ -# encoding: utf-8 -require 'test_helper' - -class PayflowLinkHelperTest < Test::Unit::TestCase - include ActiveMerchant::Billing::Integrations - - def setup - if RUBY_VERSION < '1.9' && $KCODE == "NONE" - @original_kcode = $KCODE - $KCODE = 'u' - end - - @helper = PayflowLink::Helper.new(1121, 'myaccount', :amount => 500, - :currency => 'CAD', :credential3 => 'PayPal', - :credential2 => "password", :test => true, :credential4 => '') - @url = 'http://example.com' - end - - def teardown - $KCODE = @original_kcode if @original_kcode - end - - def test_basic_helper_fields - - @helper.expects(:ssl_post).with { |url, data| - params = parse_params(data) - - assert_equal 'myaccount', params["login[9]"] - assert_equal 'myaccount', params["user[9]"] - assert_equal 'PayPal', params["partner[6]"] - assert_equal '500', params["amt[3]"] - assert_equal 'S', params["trxtype[1]"] - assert_equal '1121', params["user1[4]"] - assert_equal '1121', params["invoice[4]"] - true - }.returns("RESPMSG=APPROVED&SECURETOKEN=aaa&SECURETOKENID=yyy") - - @helper.form_fields - end - - def test_description - @helper.description "my order" - @helper.expects(:ssl_post).with { |url, data| - params = parse_params(data) - - 'my order' == params["description[8]"] - }.returns("RESPMSG=APPROVED&SECURETOKEN=aaa&SECURETOKENID=yyy") - - @helper.form_fields - end - - def test_description_cleaned - @helper.description "#my order#" - @helper.expects(:ssl_post).with { |url, data| - params = parse_params(data) - - 'my order' == params["description[8]"] - }.returns("RESPMSG=APPROVED&SECURETOKEN=aaa&SECURETOKENID=yyy") - - @helper.form_fields - end - - def test_description_transliterate - @helper.description "#my ordér" - - @helper.expects(:ssl_post).with { |url, data| - params = parse_params(data) - if ActiveSupport::Inflector.method(:transliterate).arity == -2 - 'my order' == params["description[8]"] - elsif RUBY_VERSION >= '1.9' - 'my ordr' == params["description[7]"] - else - 'my order' == params["description[8]"] - end - }.returns("RESPMSG=APPROVED&SECURETOKEN=aaa&SECURETOKENID=yyy") - - @helper.form_fields - end - - def test_name - @helper.customer :first_name => "John", :last_name => "Doe" - - @helper.expects(:ssl_post).with { |url, data| - params = parse_params(data) - - assert_equal 'John', params["first_name[4]"] - assert_equal 'Doe', params["last_name[3]"] - true - }.returns("RESPMSG=APPROVED&SECURETOKEN=aaa&SECURETOKENID=yyy") - - @helper.form_fields - end - - def test_billing_information - @helper.billing_address :country => 'CA', - :address1 => '1 My Street', - :address2 => 'APT. 2', - :city => 'Ottawa', - :state => 'On', - :zip => '90210', - :phone => '(555)123-4567' - - @helper.expects(:ssl_post).with { |url, data| - params = parse_params(data) - - assert_equal '1 My Street APT. 2', params["address[18]"] - assert_equal 'Ottawa', params["city[6]"] - assert_equal 'ON', params["state[2]"] - assert_equal '90210', params["zip[5]"] - assert_equal 'CA', params["country[2]"] - assert_equal '(555)123-4567', params["phone[13]"] - true - }.returns("RESPMSG=APPROVED&SECURETOKEN=aaa&SECURETOKENID=yyy") - - @helper.form_fields - end - - def test_state - @helper.billing_address :country => 'US', - :state => 'TX' - @helper.expects(:ssl_post).with { |url, data| - params = parse_params(data) - - assert_equal "US", params["country[2]"] - assert_equal "TX", params["state[2]"] - true - }.returns("RESPMSG=APPROVED&SECURETOKEN=aaa&SECURETOKENID=yyy") - - @helper.form_fields - end - - def test_country_code - @helper.billing_address :country => 'CAN' - @helper.expects(:ssl_post).with { |url, data| - params = parse_params(data) - - assert_equal "CA", params["country[2]"] - true - }.returns("RESPMSG=APPROVED&SECURETOKEN=aaa&SECURETOKENID=yyy") - - @helper.form_fields - end - - def test_setting_invalid_address_field - fields = @helper.fields.dup - fields["state"] = 'N/A' - - @helper.billing_address :street => 'My Street' - assert_equal fields, @helper.fields - end - - def test_uk_shipping_address_with_no_state - @helper.billing_address :country => 'GB', - :state => '' - - @helper.expects(:ssl_post).with { |url, data| - params = parse_params(data) - - assert_equal "N/A", params["state[3]"] - true - }.returns("RESPMSG=APPROVED&SECURETOKEN=aaa&SECURETOKENID=yyy") - - @helper.form_fields - end - - def test_form_fields_when_using_secure_token - @helper.expects(:ssl_post => "RESPMSG=APPROVED&SECURETOKEN=aaa&SECURETOKENID=yyy") - - fields = @helper.form_fields - - assert_equal "aaa", fields["securetoken"] - assert_equal "test", fields["mode"] - assert_equal "yyy", fields["securetokenid"] - end - - def test_form_fields_when_secure_token_failed - @helper.expects(:ssl_post => "RESPMSG=FAILED&SECURETOKEN=aaa&SECURETOKENID=yyy") - - fields = @helper.form_fields - - assert_equal "test", fields["mode"] - assert_nil fields["securetoken"] - assert_nil fields["securetokenid"] - end - - def test_submits_correct_fields_to_generate_secure_token - @helper.expects(:secure_token_id => "aaaa") - @helper.expects(:ssl_post).with { |url, data| - params = parse_params(data) - - assert_equal "password", params["pwd[8]"] - assert_equal "S", params["trxtype[1]"] - assert_equal "myaccount", params["user[9]"] - assert_equal "myaccount", params["vendor[9]"] - assert_equal "aaaa", params["securetokenid[4]"] - assert_equal "Y", params["createsecuretoken[1]"] - true - }.returns("RESPMSG=APPROVED&SECURETOKEN=aaa&SECURETOKENID=yyy") - - @helper.form_fields - end - - def test_transaction_type - helper = PayflowLink::Helper.new(1121, 'myaccount', :amount => 500, - :currency => 'CAD', :credential3 => 'PayPal', - :credential2 => "password", :test => true, :transaction_type => 'A') - helper.expects(:ssl_post).with { |url, data| - params = parse_params(data) - assert_equal 'A', params["trxtype[1]"] - true - }.returns("RESPMSG=APPROVED&SECURETOKEN=aaa&SECURETOKENID=yyy") - helper.form_fields - end - - private - def parse_params(response) - response.split("&").inject({}) do |hash, param| - key, value = param.split("=") - hash[key] = value - hash - end - end -end diff --git a/test/unit/integrations/helpers/paypal_helper_test.rb b/test/unit/integrations/helpers/paypal_helper_test.rb deleted file mode 100644 index f5378879259..00000000000 --- a/test/unit/integrations/helpers/paypal_helper_test.rb +++ /dev/null @@ -1,171 +0,0 @@ -require 'test_helper' - -class PaypalHelperTest < Test::Unit::TestCase - include ActiveMerchant::Billing::Integrations - - def setup - @helper = Paypal::Helper.new(1,'cody@example.com', :amount => 500, :currency => 'CAD') - @url = 'http://example.com' - end - - def test_static_fields - assert_field 'cmd', '_ext-enter' - assert_field 'redirect_cmd', '_xclick' - assert_field 'quantity', '1' - assert_field 'item_name', 'Store purchase' - assert_field 'no_shipping', '1' - assert_field 'no_note', '1' - assert_field 'charset', 'utf-8' - end - - def test_basic_helper_fields - assert_field 'item_number', '1' - assert_field 'custom', '1' - assert_field 'business', 'cody@example.com' - assert_field 'amount', '500' - assert_field 'currency_code', 'CAD' - end - - def test_invoice - @helper.invoice = 'Shopify shirt' - assert_field 'invoice', 'Shopify shirt' - end - - def test_notification_url - @helper.notify_url = @url - assert_field 'notify_url', @url - end - - def test_return_url - @helper.return_url = @url - assert_field 'return', @url - end - - def test_cancel_return_url - @helper.cancel_return_url = @url - assert_field 'cancel_return', @url - end - - def test_customer_fields - @helper.customer :first_name => 'Cody', - :last_name => 'Fauser', - :email => 'cody@example.com' - - assert_field 'first_name', 'Cody' - assert_field 'last_name', 'Fauser' - assert_field 'email', 'cody@example.com' - end - - def test_shipping_address - @helper.shipping_address :country => 'CA', - :address1 => '1 My Street', - :city => 'Ottawa', - :zip => '90210', - :phone => '(555)123-4567' - - assert_field 'country', 'CA' - assert_field 'address1', '1 My Street' - assert_field 'zip', '90210' - assert_field 'night_phone_a', '555' - assert_field 'night_phone_b', '123' - assert_field 'night_phone_c', '4567' - end - - def test_phone_parsing - @helper.shipping_address :country => 'CA', :phone => '111-222-3333' - - assert_field 'night_phone_a', '111' - assert_field 'night_phone_b', '222' - assert_field 'night_phone_c', '3333' - end - - - def test_phone_parsing_for_non_us - @helper.shipping_address :country => 'GB', :phone => '028 38841670' - - assert_field 'night_phone_a', nil - assert_field 'night_phone_b', '02838841670' - assert_field 'night_phone_c', nil - end - - - def test_province - @helper.shipping_address :country => 'CA', - :state => 'On' - - assert_field 'country', 'CA' - assert_field 'state', 'Ontario' - end - - def test_state - @helper.shipping_address :country => 'US', - :state => 'TX' - - assert_field 'country', 'US' - assert_field 'state', 'TX' - end - - def test_shipping - @helper.shipping '7.99' - assert_field 'shipping', '7.99' - end - - def test_tax - @helper.tax '14.99' - assert_field 'tax', '14.99' - end - - def test_country_code - @helper.shipping_address :country => 'CAN' - assert_field 'country', 'CA' - end - - def test_setting_invalid_address_field - fields = @helper.fields.dup - fields["state"] = 'N/A' - - @helper.shipping_address :street => 'My Street' - assert_equal fields, @helper.fields - end - - def test_setting_item_name - @helper.item_name = 'Really Cool Gizmo' - assert_field 'item_name', 'Really Cool Gizmo' - end - - def test_setting_quantity - @helper.quantity = '10' - assert_field 'quantity', '10' - end - - def test_setting_no_shipping - @helper.no_shipping = '0' - assert_field 'no_shipping', '0' - end - - def test_setting_no_note - @helper.no_note = '0' - assert_field 'no_note', '0' - end - - def test_uk_shipping_address_with_no_state - @helper.shipping_address :country => 'GB', - :state => '' - - assert_field 'state', 'N/A' - end - - def test_default_bn - assert_field 'bn', ActiveMerchant::Billing::Integrations::Helper.application_id - end - - def test_override_bn - identifier = 'CodeGenies_ShoppingCart_IC_CA' - - Paypal::Helper.application_id = identifier - - @helper = Paypal::Helper.new(1,'cody@example.com', :amount => 500, :currency => 'CAD') - assert_field 'bn', identifier - end - -end diff --git a/test/unit/integrations/helpers/paypal_payments_advanced_test.rb b/test/unit/integrations/helpers/paypal_payments_advanced_test.rb deleted file mode 100644 index 5c47e1ef792..00000000000 --- a/test/unit/integrations/helpers/paypal_payments_advanced_test.rb +++ /dev/null @@ -1,195 +0,0 @@ -require 'test_helper' - -class PaypalPaymentsAdvancedHelperTest < Test::Unit::TestCase - include ActiveMerchant::Billing::Integrations - - def setup - if RUBY_VERSION < '1.9' && $KCODE == "NONE" - @original_kcode = $KCODE - $KCODE = 'u' - end - - @helper = PaypalPaymentsAdvanced::Helper.new(1121, 'myaccount', :amount => 500, - :currency => 'CAD', :credential2 => "password", - :test => true) - @url = 'http://example.com' - end - - def teardown - $KCODE = @original_kcode if @original_kcode - end - - def test_basic_helper_fields - - @helper.expects(:ssl_post).with { |url, data| - params = parse_params(data) - - assert_equal 'myaccount', params["login[9]"] - assert_equal 'myaccount', params["user[9]"] - assert_equal 'PayPal', params["partner[6]"] - assert_equal '500', params["amt[3]"] - assert_equal 'S', params["trxtype[1]"] - assert_equal '1121', params["user1[4]"] - assert_equal '1121', params["invoice[4]"] - true - }.returns("RESPMSG=APPROVED&SECURETOKEN=aaa&SECURETOKENID=yyy") - - @helper.form_fields - end - - def test_description - @helper.description "my order" - @helper.expects(:ssl_post).with { |url, data| - params = parse_params(data) - - assert_equal 'my order', params["description[8]"] - true - }.returns("RESPMSG=APPROVED&SECURETOKEN=aaa&SECURETOKENID=yyy") - - @helper.form_fields - end - - def test_name - @helper.customer :first_name => "John", :last_name => "Doe" - - @helper.expects(:ssl_post).with { |url, data| - params = parse_params(data) - - assert_equal 'John', params["first_name[4]"] - assert_equal 'Doe', params["last_name[3]"] - true - }.returns("RESPMSG=APPROVED&SECURETOKEN=aaa&SECURETOKENID=yyy") - - @helper.form_fields - end - - def test_billing_information - @helper.billing_address :country => 'CA', - :address1 => '1 My Street', - :address2 => 'APT. 2', - :city => 'Ottawa', - :state => 'On', - :zip => '90210', - :phone => '(555)123-4567' - - @helper.expects(:ssl_post).with { |url, data| - params = parse_params(data) - - assert_equal '1 My Street APT. 2', params["address[18]"] - assert_equal 'Ottawa', params["city[6]"] - assert_equal 'ON', params["state[2]"] - assert_equal '90210', params["zip[5]"] - assert_equal 'CA', params["country[2]"] - assert_equal '(555)123-4567', params["phone[13]"] - true - }.returns("RESPMSG=APPROVED&SECURETOKEN=aaa&SECURETOKENID=yyy") - - @helper.form_fields - end - - def test_state - @helper.billing_address :country => 'US', - :state => 'TX' - @helper.expects(:ssl_post).with { |url, data| - params = parse_params(data) - - assert_equal "US", params["country[2]"] - assert_equal "TX", params["state[2]"] - true - }.returns("RESPMSG=APPROVED&SECURETOKEN=aaa&SECURETOKENID=yyy") - - @helper.form_fields - end - - def test_country_code - @helper.billing_address :country => 'CAN' - @helper.expects(:ssl_post).with { |url, data| - params = parse_params(data) - - assert_equal "CA", params["country[2]"] - true - }.returns("RESPMSG=APPROVED&SECURETOKEN=aaa&SECURETOKENID=yyy") - - @helper.form_fields - end - - def test_setting_invalid_address_field - fields = @helper.fields.dup - fields["state"] = 'N/A' - - @helper.billing_address :street => 'My Street' - assert_equal fields, @helper.fields - end - - def test_uk_shipping_address_with_no_state - @helper.billing_address :country => 'GB', - :state => '' - - @helper.expects(:ssl_post).with { |url, data| - params = parse_params(data) - - assert_equal "N/A", params["state[3]"] - true - }.returns("RESPMSG=APPROVED&SECURETOKEN=aaa&SECURETOKENID=yyy") - - @helper.form_fields - end - - def test_form_fields_when_using_secure_token - @helper.expects(:ssl_post => "RESPMSG=APPROVED&SECURETOKEN=aaa&SECURETOKENID=yyy") - - fields = @helper.form_fields - - assert_equal "aaa", fields["securetoken"] - assert_equal "test", fields["mode"] - assert_equal "yyy", fields["securetokenid"] - end - - def test_form_fields_when_secure_token_failed - @helper.expects(:ssl_post => "RESPMSG=FAILED&SECURETOKEN=aaa&SECURETOKENID=yyy") - - fields = @helper.form_fields - - assert_equal "test", fields["mode"] - assert_nil fields["securetoken"] - assert_nil fields["securetokenid"] - end - - def test_submits_correct_fields_to_generate_secure_token - @helper.expects(:secure_token_id => "aaaa") - @helper.expects(:ssl_post).with { |url, data| - params = parse_params(data) - - assert_equal "password", params["pwd[8]"] - assert_equal "S", params["trxtype[1]"] - assert_equal "myaccount", params["user[9]"] - assert_equal "myaccount", params["vendor[9]"] - assert_equal "aaaa", params["securetokenid[4]"] - assert_equal "Y", params["createsecuretoken[1]"] - true - }.returns("RESPMSG=APPROVED&SECURETOKEN=aaa&SECURETOKENID=yyy") - - @helper.form_fields - end - - def test_transaction_type - helper = PayflowLink::Helper.new(1121, 'myaccount', :amount => 500, - :currency => 'CAD', :credential2 => "password", - :test => true, :transaction_type => 'A') - helper.expects(:ssl_post).with { |url, data| - params = parse_params(data) - assert_equal 'A', params["trxtype[1]"] - true - }.returns("RESPMSG=APPROVED&SECURETOKEN=aaa&SECURETOKENID=yyy") - helper.form_fields - end - - private - def parse_params(response) - response.split("&").inject({}) do |hash, param| - key, value = param.split("=") - hash[key] = value - hash - end - end -end diff --git a/test/unit/integrations/helpers/paysbuy_helper_test.rb b/test/unit/integrations/helpers/paysbuy_helper_test.rb deleted file mode 100644 index 68d68216d1b..00000000000 --- a/test/unit/integrations/helpers/paysbuy_helper_test.rb +++ /dev/null @@ -1,16 +0,0 @@ -require 'test_helper' - -class PaysbuyHelperTest < Test::Unit::TestCase - include ActiveMerchant::Billing::Integrations - - def setup - @helper = Paysbuy::Helper.new('order-500','cody@example.com', :amount => 5) - end - - def test_basic_helper_fields - assert_field 'biz', 'cody@example.com' - - assert_field 'amt', '5' - assert_field 'inv', 'order-500' - end -end diff --git a/test/unit/integrations/helpers/payu_in_helper_test.rb b/test/unit/integrations/helpers/payu_in_helper_test.rb deleted file mode 100644 index 20d7f368319..00000000000 --- a/test/unit/integrations/helpers/payu_in_helper_test.rb +++ /dev/null @@ -1,66 +0,0 @@ -require 'test_helper' - -class PayuInHelperTest < Test::Unit::TestCase - include ActiveMerchant::Billing::Integrations - - def setup - @helper = PayuIn::Helper.new( 'jh34h53kj4h5hj34kh5', 'C0Dr8m', :amount => '10.00', :credential2 => 'Product Info') - end - - def test_basic_helper_fields - assert_equal '10.00', @helper.fields['amount'] - assert_equal 'C0Dr8m', @helper.fields['key'] - assert_equal 'jh34h53kj4h5hj34kh5', @helper.fields['txnid'] - assert_equal 'Product Info', @helper.fields['productinfo'] - end - - def test_customer_fields - @helper.customer :first_name => 'Payu-Admin', :last_name => '', :email => 'test@example.com', :phone => '1234567890' - - assert_equal 'Payu-Admin', @helper.fields['firstname'] - assert_equal 'test@example.com', @helper.fields['email'] - assert_equal '1234567890', @helper.fields['phone'] - end - - def test_billing_address_fields - @helper.billing_address :city => 'New Delhi', :address1 => '666, Wooo', :address2 => 'EEE Street', :state => 'New Delhi', :zip => '110001', :country => 'india' - - assert_equal 'New Delhi', @helper.fields['city'] - assert_equal '666, Wooo', @helper.fields['address1'] - assert_equal 'EEE Street', @helper.fields['address2'] - assert_equal 'New Delhi', @helper.fields['state'] - assert_equal '110001', @helper.fields['zip'] - assert_equal 'india', @helper.fields['country'] - end - - def test_return_url_fields - @helper.return_url 'some_return_url' - - assert_equal 'some_return_url', @helper.fields['surl'] - assert_equal 'some_return_url', @helper.fields['furl'] - end - - def test_user_defined_fields - @helper.user_defined :var1 => 'var_one', :var2 => 'var_two', :var3 => 'var_three', :var4 => 'var_four', :var5 => 'var_five', :var6 => 'var_six', :var7 => 'var_seven', :var8 => 'var_eight', :var9 => 'var_nine', :var10 => 'var_ten' - - assert_equal 'var_one', @helper.fields['udf1'] - assert_equal 'var_two', @helper.fields['udf2'] - assert_equal 'var_three', @helper.fields['udf3'] - assert_equal 'var_four', @helper.fields['udf4'] - assert_equal 'var_five', @helper.fields['udf5'] - assert_equal 'var_six', @helper.fields['udf6'] - assert_equal 'var_seven', @helper.fields['udf7'] - assert_equal 'var_eight', @helper.fields['udf8'] - assert_equal 'var_nine', @helper.fields['udf9'] - assert_equal 'var_ten', @helper.fields['udf10'] - end - - def test_add_checksum_method - options = { :mode => 'CC' } - @helper.customer :first_name => 'Payu-Admin', :email => 'test@example.com' - @helper.user_defined :var1 => 'var_one', :var2 => 'var_two', :var3 => 'var_three', :var4 => 'var_four', :var5 => 'var_five', :var6 => 'var_six', :var7 => 'var_seven', :var8 => 'var_eight', :var9 => 'var_nine', :var10 => 'var_ten' - - assert_equal "032606d7fb5cfe357d9e6b358b4bb8db1d34e9dfa30f039cb7dec75ae6d77f7d1f67a58c123ea0ee358bf040554d5e3048066a369ae63888132e27c14e79ee5a", @helper.form_fields["hash"] - end - -end diff --git a/test/unit/integrations/helpers/platron_helper_test.rb b/test/unit/integrations/helpers/platron_helper_test.rb deleted file mode 100644 index c5fbe6b172c..00000000000 --- a/test/unit/integrations/helpers/platron_helper_test.rb +++ /dev/null @@ -1,25 +0,0 @@ -require 'test_helper' - -class PlatronHelperTest < Test::Unit::TestCase - include ActiveMerchant::Billing::Integrations - - def setup - @helper = Platron::Helper.new( - '123', - 'test_account', - :amount => 200, - :currency => 'USD', - :description => 'payment description', - :secret => 'secret', - :path => 'payment.php' - ) - end - - def test_helper_fields - assert_field 'pg_merchant_id', 'test_account' - assert_field 'pg_amount', '200' - assert_field 'pg_order_id', '123' - assert_field 'pg_currency', 'USD' - assert_field 'pg_description', 'payment description' - end -end diff --git a/test/unit/integrations/helpers/quickpay_helper_test.rb b/test/unit/integrations/helpers/quickpay_helper_test.rb deleted file mode 100644 index 03230b3c035..00000000000 --- a/test/unit/integrations/helpers/quickpay_helper_test.rb +++ /dev/null @@ -1,40 +0,0 @@ -require 'test_helper' - -class QuickpayHelperTest < Test::Unit::TestCase - include ActiveMerchant::Billing::Integrations - - def setup - @helper = Quickpay::Helper.new('order-500','24352435', :amount => 500, :currency => 'USD') - @helper.md5secret "mysecretmd5string" - @helper.return_url 'http://example.com/ok' - @helper.cancel_return_url 'http://example.com/cancel' - @helper.notify_url 'http://example.com/notify' - end - - def test_basic_helper_fields - assert_field 'merchant', '24352435' - assert_field 'amount', '500' - assert_field 'ordernumber', 'order500' - end - - def test_generate_md5string - assert_equal '6authorize24352435daorder500500USDhttp://example.com/okhttp://example.com/cancelhttp://example.com/notify01mysecretmd5string', - @helper.generate_md5string - end - - def test_generate_md5check - assert_equal '12e8e7d95eae56ef8766eafa96aff267', @helper.generate_md5check - end - - def test_unknown_mapping - assert_nothing_raised do - @helper.company_address :address => '500 Dwemthy Fox Road' - end - end - - def test_setting_invalid_address_field - fields = @helper.fields.dup - @helper.billing_address :street => 'My Street' - assert_equal fields, @helper.fields - end -end diff --git a/test/unit/integrations/helpers/rbkmoney_helper_test.rb b/test/unit/integrations/helpers/rbkmoney_helper_test.rb deleted file mode 100644 index 148eb38440e..00000000000 --- a/test/unit/integrations/helpers/rbkmoney_helper_test.rb +++ /dev/null @@ -1,34 +0,0 @@ -require 'test_helper' - -class RbkmoneyHelperTest < Test::Unit::TestCase - include ActiveMerchant::Billing::Integrations - - def setup - @helper = Rbkmoney::Helper.new( - 100500, - 123465, - :amount => 500, - :currency => 'USD', - :credential2 => 'awesome stuff', - :credential3 => 'http://somewhere.com/success', - :credential4 => 'http://somewhere.com/cancel' - ) - end - - def test_basic_helper_fields - assert_field 'orderId', '100500' - assert_field 'eshopId', '123465' - - assert_field 'recipientAmount', '500' - assert_field 'recipientCurrency', 'USD' - assert_field 'successUrl', 'http://somewhere.com/success' - assert_field 'failUrl', 'http://somewhere.com/cancel' - assert_field 'serviceName', 'awesome stuff' - end - - def test_customer_fields - @helper.customer(:email => 'cody@example.com') - assert_field 'user_email', 'cody@example.com' - end - -end diff --git a/test/unit/integrations/helpers/robokassa_helper_test.rb b/test/unit/integrations/helpers/robokassa_helper_test.rb deleted file mode 100644 index 3f15158add2..00000000000 --- a/test/unit/integrations/helpers/robokassa_helper_test.rb +++ /dev/null @@ -1,40 +0,0 @@ -require 'test_helper' - -class RobokassaHelperTest < Test::Unit::TestCase - include ActiveMerchant::Billing::Integrations - - def setup - @helper = Robokassa::Helper.new(123,'test_account', :amount => 500, :currency => 'USD', :secret => 'secret') - end - - def test_basic_helper_fields - assert_field 'MrchLogin', 'test_account' - - assert_field 'OutSum', '500' - assert_field 'InvId', '123' - end - - def test_signature_string - assert_equal 'test_account:500:123:secret', @helper.generate_signature_string - end - - def test_signature_string_with_empty_amount - helper = Robokassa::Helper.new(123,'test_account', :currency => 'USD', :secret => 'secret') - assert_equal 'test_account::123:secret', helper.generate_signature_string - end - - def test_custom_fields - @helper.shpa = '123' - @helper.shpMySuperParam = '456' - - assert_field 'shpa', '123' - assert_field 'shpMySuperParam', '456' - end - - def test_signature_string_with_custom_fields - @helper.shpb = '456' - @helper.shpa = '123' - - assert_equal 'test_account:500:123:secret:shpa=123:shpb=456', @helper.generate_signature_string - end -end diff --git a/test/unit/integrations/helpers/sage_pay_form_helper_test.rb b/test/unit/integrations/helpers/sage_pay_form_helper_test.rb deleted file mode 100644 index dd5b8c134a9..00000000000 --- a/test/unit/integrations/helpers/sage_pay_form_helper_test.rb +++ /dev/null @@ -1,250 +0,0 @@ -# encoding: utf-8 - -require 'test_helper' - -class SagePayFormHelperTest < Test::Unit::TestCase - include ActiveMerchant::Billing::Integrations - - def setup - @key = 'EncryptionKey123' - @helper = SagePayForm::Helper.new('order-500', 'cody@example.com', - :amount => '5.00', - :currency => 'USD', - :credential2 => @key - ) - @helper.credential2 - end - - def test_basic_helper_fields - assert_equal 5, @helper.fields.size - assert_field 'Vendor', 'cody@example.com' - assert_field 'Amount', '5.00' - assert_field 'VendorTxCode', 'order-500' - end - - def test_customer_fields - @helper.customer :first_name => 'Cody', :last_name => 'Fauser', :email => 'cody@example.com' - assert_equal 8, @helper.fields.size - assert_field 'BillingFirstnames', 'Cody' - assert_field 'BillingSurname', 'Fauser' - assert_field 'CustomerEMail', 'cody@example.com' - end - - def test_customer_send_email - @helper.customer :first_name => 'Cody', :last_name => 'Fauser', :email => 'cody@example.com', :send_email_confirmation => true - with_crypt_plaintext do |plain| - assert plain.include?('cody@example.com') - end - end - - def test_customer_default_no_email - @helper.customer :first_name => 'Cody', :last_name => 'Fauser', :email => 'cody@example.com' - with_crypt_plaintext do |plain| - assert !plain.include?('cody@example.com') - end - end - - def test_us_address_mapping - @helper.billing_address( - :address1 => '1 My Street', - :address2 => '', - :city => 'Chicago', - :state => 'IL', - :zip => '60606', - :country => 'US' - ) - - assert_equal 10, @helper.fields.size - assert_field 'BillingAddress1', '1 My Street' - assert_field 'BillingCity', 'Chicago' - assert_field 'BillingState', 'IL' - assert_field 'BillingPostCode', '60606' - assert_field 'BillingCountry', 'US' - - with_crypt_plaintext do |plain| - assert plain.include?('&BillingState=IL') - end - end - - def test_non_us_address_mapping - @helper.billing_address( - :address1 => '1 My Street', - :address2 => '', - :city => 'Leeds', - :state => 'Yorkshire', # ignored - :zip => 'LS23', - :country => 'GB' - ) - - assert_equal 10, @helper.fields.size - assert_field 'BillingAddress1', '1 My Street' - assert_field 'BillingCity', 'Leeds' - assert_field 'BillingPostCode', 'LS23' - assert_field 'BillingCountry', 'GB' - - with_crypt_plaintext do |plain| - assert !plain.include?('&BillingState=') - assert !plain.include?('Yorkshire') - end - end - - def test_shipping_address_falls_back_to_billing_address - @helper.billing_address( - :address1 => '1 My Street', - :address2 => '', - :city => 'Chicago', - :state => 'IL', - :zip => '60606', - :country => 'US' - ) - - @helper.form_fields - assert_equal 19, @helper.fields.size - assert_field 'DeliveryAddress1', '1 My Street' - assert_field 'DeliveryCity', 'Chicago' - assert_field 'DeliveryState', 'IL' - assert_field 'DeliveryPostCode', '60606' - assert_field 'DeliveryCountry', 'US' - - with_crypt_plaintext do |plain| - assert plain.include?('&DeliveryState=IL') - end - end - - def test_description_should_truncate - description = 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut la' - assert_equal 101, description.size - @helper.add_field('Description', description) - - with_crypt_plaintext do |plain| - assert plain.include?('Description=Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut l') - end - - description = 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut' - assert_equal 98, description.size - @helper.add_field('Description', description) - - with_crypt_plaintext do |plain| - assert plain.include?('Description=Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut') - end - end - - def test_set_shipping_address_wont_be_overridden_by_billing_address - @helper.billing_address( - :address1 => '1 My Street', - :address2 => '', - :city => 'Chicago', - :state => 'IL', - :zip => '60606', - :country => 'US' - ) - @helper.shipping_address( - :address1 => '1 Shipping Street', - :address2 => '', - :city => 'Chicago Shipping', - :state => 'NY', - :zip => '123123', - :country => 'US' - ) - - @helper.form_fields - assert_equal 18, @helper.fields.size - assert_field 'DeliveryAddress1', '1 Shipping Street' - assert_field 'DeliveryCity', 'Chicago Shipping' - assert_field 'DeliveryState', 'NY' - assert_field 'DeliveryPostCode', '123123' - assert_field 'DeliveryCountry', 'US' - end - - def test_unknown_address_mapping - @helper.billing_address :farm => 'CA' - assert_equal 5, @helper.fields.size - end - - def test_unknown_mapping - assert_nothing_raised do - @helper.company_address :address => '500 Dwemthy Fox Road' - end - assert_equal 5, @helper.fields.size - end - - def test_setting_invalid_address_field - fields = @helper.fields.dup - @helper.billing_address :street => 'My Street' - assert_equal fields, @helper.fields - end - - def test_basic_form_fields - params = @helper.form_fields - - assert_equal '2.23', params['VPSProtocol'] - assert_equal 'PAYMENT', params['TxType'] - assert_equal 'cody@example.com', params['Vendor'] - assert_not_nil params['Crypt'] - end - - def test_unicode_fields - @helper.customer :first_name => 'Tobias', :last_name => "Lütke", :email => 'cody@example.com' - params = @helper.form_fields - - assert_equal '2.23', params['VPSProtocol'] - assert_equal 'PAYMENT', params['TxType'] - assert_equal 'cody@example.com', params['Vendor'] - assert_not_nil params['Crypt'] - end - - def test_crypt_field_is_base64 - crypt = @helper.form_fields['Crypt'] - assert_match /^[A-Za-z0-9\+\/]+=*$/, crypt - end - - def test_crypt_field_salt - random = 'ExpectSomePartOfThisSalt' - SecureRandom.expects(:base64).returns(random) - - with_crypt_plaintext do |plain| - salt = plain.split('&').first - assert random.start_with?(salt) - end - end - - def test_crypt_field_is_salted_uniq - crypts = (1..5).map { @helper.dup.form_fields['Crypt'] } - assert_equal 5, crypts.uniq.count - end - - def test_not_including_post_code_uses_0000_default - @helper.billing_address( - :address1 => '1 My Street', - :address2 => '', - :city => 'Dublin', - :country => 'IE' - ) - @helper.shipping_address( - :address1 => '1 Shipping Street', - :address2 => '', - :city => 'Dublin Shipping', - :country => 'IE' - ) - - @helper.form_fields - assert_equal 16, @helper.fields.size - - assert_field 'BillingAddress1', '1 My Street' - assert_field 'BillingCity', 'Dublin' - assert_field 'BillingPostCode', '0000' - assert_field 'BillingCountry', 'IE' - - assert_field 'DeliveryAddress1', '1 Shipping Street' - assert_field 'DeliveryCity', 'Dublin Shipping' - assert_field 'DeliveryPostCode', '0000' - assert_field 'DeliveryCountry', 'IE' - end - - private - - def with_crypt_plaintext - crypt = @helper.dup.form_fields['Crypt'] - yield @helper.sage_decrypt(crypt, @key) - end -end diff --git a/test/unit/integrations/helpers/two_checkout_helper_test.rb b/test/unit/integrations/helpers/two_checkout_helper_test.rb deleted file mode 100644 index 63cba4c6b75..00000000000 --- a/test/unit/integrations/helpers/two_checkout_helper_test.rb +++ /dev/null @@ -1,113 +0,0 @@ -require 'test_helper' - -class TwoCheckoutHelperTest < Test::Unit::TestCase - include ActiveMerchant::Billing::Integrations - - def setup - @helper = TwoCheckout::Helper.new('order-500','cody@example.com', :amount => '5.00', :currency => 'USD') - end - - def teardown - ActiveMerchant::Billing::Base.integration_mode = :test - end - - def test_basic_helper_fields - assert_field 'sid', 'cody@example.com' - assert_field 'total', '5.00' - assert_field 'cart_order_id', 'order-500' - end - - def test_customer_fields - @helper.customer :first_name => 'Cody', :last_name => 'Fauser', :email => 'cody@example.com', :phone => '(555)555-5555' - assert_field 'card_holder_name', 'Cody Fauser' - assert_field 'email', 'cody@example.com' - assert_field 'phone', '(555)555-5555' - end - - def test_line_item_fields - @helper.line_item :quantity => 1, :recurrence => '1 Week' - @helper.line_item :description => 'Test Product', :price => '15.0' - - assert_field 'li_1_quantity', '1' - assert_field 'li_1_recurrence', '1 Week' - - assert_field 'li_2_description', 'Test Product' - assert_field 'li_2_price', '15.0' - end - - def test_auto_settle_fields - @helper.auto_settle :prod => "1,1", :name => 'Example Product Name' - @helper.auto_settle :description => 'Example Product Description', :price => '15.0' - - assert_field 'c_prod_1', '1,1' - assert_field 'c_name_1', 'Example Product Name' - - assert_field 'c_description_2', 'Example Product Description' - assert_field 'c_price_2', '15.0' - end - - def test_address_mapping - @helper.billing_address :address1 => '1 My Street', - :address2 => 'Apt. 1', - :city => 'Leeds', - :state => 'Yorkshire', - :zip => 'LS2 7EE', - :country => 'CA' - - assert_field 'street_address', '1 My Street' - assert_field 'street_address2', 'Apt. 1' - assert_field 'city', 'Leeds' - assert_field 'state', 'Yorkshire' - assert_field 'zip', 'LS2 7EE' - assert_field 'country', 'CA' - end - - def test_shipping_address - @helper.shipping_address :address1 => '1 My Street', - :address2 => 'Apt. 1', - :city => 'London', - :state => 'Whales', - :zip => 'LS2 7E1', - :country => 'GB' - - assert_field 'ship_city', 'London' - assert_field 'ship_street_address', '1 My Street' - assert_field 'ship_state', 'Whales' - assert_field 'ship_zip', 'LS2 7E1' - assert_field 'ship_country', 'GB' - end - - def test_unknown_address_mapping - @helper.billing_address :farm => 'CA' - assert_equal 5, @helper.fields.size - end - - def test_unknown_mapping - assert_nothing_raised do - @helper.company_address :address => '500 Dwemthy Fox Road' - end - end - - def test_setting_invalid_address_field - fields = @helper.fields.dup - @helper.billing_address :street => 'My Street' - assert_equal fields, @helper.fields - end - - def test_test_mode - @helper = TwoCheckout::Helper.new('order-500','cody@example.com', :amount => '5.00', :currency => 'USD') - assert_field 'demo', 'Y' - end - - def test_force_test_mode - ActiveMerchant::Billing::Base.integration_mode = :production - @helper = TwoCheckout::Helper.new('order-500','cody@example.com', :amount => '5.00', :currency => 'USD', :test => true) - assert_field 'demo', 'Y' - end - - def test_production_mode - ActiveMerchant::Billing::Base.integration_mode = :production - @helper = TwoCheckout::Helper.new('order-500','cody@example.com', :amount => '5.00', :currency => 'USD') - assert !@helper.fields.has_key?("demo") - end -end diff --git a/test/unit/integrations/helpers/valitor_helper_test.rb b/test/unit/integrations/helpers/valitor_helper_test.rb deleted file mode 100644 index 64519cb9470..00000000000 --- a/test/unit/integrations/helpers/valitor_helper_test.rb +++ /dev/null @@ -1,135 +0,0 @@ -require 'test_helper' - -class ValitorHelperTest < Test::Unit::TestCase - include ActiveMerchant::Billing::Integrations - - def setup - @helper = Valitor::Helper.new( - 'order-500', - 'cody@example.com', - :currency => 'USD', - :credential2 => '123', - :amount => 1000 - ) - end - - def test_basic_helper_fields - assert_field 'VefverslunID', 'cody@example.com' - - assert_field 'Adeinsheimild', '0' - assert_field 'Tilvisunarnumer', 'order-500' - assert_field 'Gjaldmidill', 'USD' - - assert_equal Digest::MD5.hexdigest(['123', '0', '1', '1000', '0', 'cody@example.com', 'order-500', 'USD'].join('')), - @helper.form_fields['RafraenUndirskrift'] - end - - def test_products - @helper.product(1, :description => 'one', :quantity => '2', :amount => 100, :discount => 50) - @helper.product(2, :description => 'two', :amount => 200) - - assert_field 'Vara_1_Lysing', 'one' - assert_field 'Vara_1_Fjoldi', '2' - assert_field 'Vara_1_Verd', '100' - assert_field 'Vara_1_Afslattur', '50' - - assert_field 'Vara_2_Lysing', 'two' - assert_field 'Vara_2_Fjoldi', '1' - assert_field 'Vara_2_Verd', '200' - assert_field 'Vara_2_Afslattur', '0' - - assert_equal Digest::MD5.hexdigest( - ['123', '0', - '2', '100', '50', - '1', '200', '0', - 'cody@example.com', 'order-500', 'USD'].join('')), - @helper.form_fields['RafraenUndirskrift'] - end - - def test_invalid_products - assert_nothing_raised do - @helper.product(1, :description => '1', :amount => 100) - end - - assert_nothing_raised ArgumentError do - @helper.product('2', :description => '2', :amount => 100) - end - - assert_raise ArgumentError do - @helper.product(501, :description => '501', :amount => 100) - end - - assert_raise ArgumentError do - @helper.product(0, :description => '0', :amount => 100) - end - - assert_raise ArgumentError do - @helper.product(3, :amount => 100) - end - - assert_raise ArgumentError do - @helper.product(3, :description => 100) - end - - assert_raise ArgumentError do - @helper.product(3, :amount => 100, :bogus => 'something') - end - end - - def test_authorize_only - @helper.authorize_only - assert_field 'Adeinsheimild', '1' - end - - def test_missing_password - @helper.instance_eval{@security_number = nil} - assert_raise ArgumentError do - @helper.form_fields - end - end - - def test_urls - @helper.return_url = 'http://example.com/return' - assert_field 'SlodTokstAdGjaldfaera', 'http://example.com/return' - - @helper.cancel_return_url = 'http://example.com/cancel' - assert_field 'SlodNotandiHaettirVid', 'http://example.com/cancel' - - @helper.notify_url = 'http://example.com/notify' - assert_field 'SlodTokstAdGjaldfaeraServerSide', 'http://example.com/notify' - - assert_equal Digest::MD5.hexdigest( - ['123', '0', - '1', '1000', '0', - 'cody@example.com', 'order-500', 'http://example.com/return', 'http://example.com/notify', 'USD'].join('')), - @helper.form_fields['RafraenUndirskrift'] - end - - def test_collect_customer_info - assert_field 'KaupandaUpplysingar', '0' - @helper.collect_customer_info - assert_field 'KaupandaUpplysingar', '1' - end - - def test_hide_header - assert_field 'SlokkvaHaus', '0' - @helper.hide_header - assert_field 'SlokkvaHaus', '1' - end - - def test_misc_mappings - assert_field 'SlodTokstAdGjaldfaeraTexti', nil - @helper.success_text = 'text' - assert_field 'SlodTokstAdGjaldfaeraTexti', 'text' - - assert_field 'Lang', nil - @helper.language = 'en' - assert_field 'Lang', 'en' - end - - def test_amount_gets_sent_without_decimals - @helper = Valitor::Helper.new('order-500', 'cody@example.com', :currency => 'ISK', :credential2 => '123', :amount => 115.10) - @helper.form_fields - assert_field "Vara_1_Verd", '115' - end -end diff --git a/test/unit/integrations/helpers/verkkomaksut_helper_test.rb b/test/unit/integrations/helpers/verkkomaksut_helper_test.rb deleted file mode 100644 index 54cd2120c22..00000000000 --- a/test/unit/integrations/helpers/verkkomaksut_helper_test.rb +++ /dev/null @@ -1,80 +0,0 @@ -require 'test_helper' - -class VerkkomaksutHelperTest < Test::Unit::TestCase - include ActiveMerchant::Billing::Integrations - - def setup - @helper = Verkkomaksut::Helper.new('2','13466', :amount => 500, :currency => 'EUR', :credential2 => "6pKF4jkv97zmqBJ3ZL8gUw5DfT2NMQ") - end - - def test_basic_helper_fields - assert_field 'MERCHANT_ID', '13466' - assert_field 'ORDER_NUMBER', '2' - assert_field 'CURRENCY', 'EUR' - end - - def test_customer_fields - @helper.customer :first_name => 'Antti', :last_name => 'Akonniemi', :email => 'antti@example.com', :phone => "0401234556", :tellno => "0401234557", :company => "Kisko Labs" - assert_field 'CONTACT_FIRSTNAME', 'Antti' - assert_field 'CONTACT_LASTNAME', 'Akonniemi' - assert_field 'CONTACT_EMAIL', 'antti@example.com' - assert_field 'CONTACT_CELLNO', '0401234556' - assert_field 'CONTACT_TELLNO', '0401234557' - assert_field 'CONTACT_COMPANY', 'Kisko Labs' - end - - def test_address_mapping - @helper.billing_address :address1 => '1 My Street', - :address2 => '', - :city => 'Helsinki', - :state => '-', - :zip => '00180', - :country => 'Finland' - - assert_field 'CONTACT_ADDR_STREET', '1 My Street' - assert_field 'CONTACT_ADDR_CITY', 'Helsinki' - assert_field 'CONTACT_ADDR_ZIP', '00180' - assert_field 'CONTACT_ADDR_COUNTRY', 'FI' - end - - def test_authcode_generation - @helper.customer :first_name => 'Antti', :last_name => 'Akonniemi', :email => 'antti@example.com', :phone => "0401234556", :tellno => "0401234557", :company => "Kisko Labs" - @helper.billing_address :address1 => '1 My Street', - :address2 => '', - :city => 'Helsinki', - :state => '-', - :zip => '00180', - :country => 'Finland' - @helper.items = 1 - @helper.item_title_0 = "test" - @helper.item_no_0 = "1" - @helper.item_amount_0 = "1" - @helper.item_price_0 = "30.00" - @helper.item_tax_0 = "23.00" - @helper.item_discount_0 = "0" - - - @helper.include_vat '1' - - @helper.return_url "http://example.com" - @helper.cancel_return_url "http://example.com" - assert_equal @helper.generate_md5string, "AC7B763192D40886906E657E2ED26E17" - end - - def test_unknown_address_mapping - @helper.billing_address :farm => 'CA' - assert_equal 4, @helper.fields.size - end - - def test_unknown_mapping - assert_nothing_raised do - @helper.company_address :address => '500 Dwemthy Fox Road' - end - end - - def test_setting_invalid_address_field - fields = @helper.fields.dup - @helper.billing_address :street => 'My Street' - assert_equal fields, @helper.fields - end -end diff --git a/test/unit/integrations/helpers/web_pay_helper_test.rb b/test/unit/integrations/helpers/web_pay_helper_test.rb deleted file mode 100644 index 3a6f6b312f5..00000000000 --- a/test/unit/integrations/helpers/web_pay_helper_test.rb +++ /dev/null @@ -1,59 +0,0 @@ -require 'test_helper' - -class WebPayHelperTest < Test::Unit::TestCase - include ActiveMerchant::Billing::Integrations - - def setup - @helper = WebPay::Helper.new('ORDER-12345678', '11111111', :amount => '21950', :currency => 'BYR', :secret => '12345678901234567890') - end - - def assign_invoice - @helper.add_line_item(:name => 'hammer', :quantity => '1', :price => '500') - end - - def assign_signature_fields - @helper.seed = '1242649174' - @helper.test = '1' - @helper.version = '2' - end - - def test_basic_helper_fields - assert_field 'wsb_storeid', '11111111' - assert_field 'wsb_order_num', 'ORDER-12345678' - assert_field 'wsb_currency_id', 'BYR' - end - - def test_signature_string - assign_signature_fields - - assert_equal '124264917411111111ORDER-123456781BYR2195012345678901234567890', @helper.request_signature_string - end - - def test_generated_signature - assign_signature_fields - - assert_equal '7a0142975bc660d219b793c650346af7ffce2473', @helper.generate_signature(:request) - end - - def test_invoice_form_fields - assign_invoice - - assert_field 'wsb_invoice_item_name[0]', 'hammer' - assert_field 'wsb_invoice_item_quantity[0]', '1' - assert_field 'wsb_invoice_item_price[0]', '500' - end - - def test_total_invoice_price - assign_invoice - - @helper.tax = '5' - @helper.shipping_price = '10' - @helper.discount_price = '10' - - assert_field 'wsb_tax', '5' - assert_field 'wsb_shipping_price', '10' - assert_field 'wsb_discount_price', '10' - - assert_equal 505, @helper.calculate_total - end -end diff --git a/test/unit/integrations/helpers/webmoney_helper_test.rb b/test/unit/integrations/helpers/webmoney_helper_test.rb deleted file mode 100644 index 757568ad024..00000000000 --- a/test/unit/integrations/helpers/webmoney_helper_test.rb +++ /dev/null @@ -1,23 +0,0 @@ -require 'test_helper' - -class WebmoneyHelperTest < Test::Unit::TestCase - include ActiveMerchant::Billing::Integrations - - def setup - @helper = Webmoney::Helper.new( - 123, 'test_account', - :description => "Order description", - :amount => 500, - :fail_url => "http://example.com/fail_url", - :success_url => "http://example.com/success_url", - :result_url => "http://example.com/result_url" - ) - end - - def test_basic_helper_fields - assert_field 'LMI_PAYEE_PURSE', 'test_account' - - assert_field 'LMI_PAYMENT_AMOUNT', '500' - assert_field 'LMI_PAYMENT_NO', '123' - end -end diff --git a/test/unit/integrations/helpers/world_pay_helper_test.rb b/test/unit/integrations/helpers/world_pay_helper_test.rb deleted file mode 100644 index e6491342293..00000000000 --- a/test/unit/integrations/helpers/world_pay_helper_test.rb +++ /dev/null @@ -1,96 +0,0 @@ -require 'test_helper' - -class WorldPayHelperTest < Test::Unit::TestCase - include ActiveMerchant::Billing::Integrations - - def setup - @helper = WorldPay::Helper.new(1,'99999', :amount => '5.00', :currency => 'GBP') - end - - def test_basic_helper_fields - assert_field 'instId', '99999' - assert_field 'amount', '5.00' - assert_field 'cartId', '1' - assert_field 'currency', 'GBP' - end - - def test_customer_fields - @helper.customer :first_name => 'Andrew', - :last_name => 'White', - :phone => '024 7699 9999', - :email => 'andyw@example.com' - - assert_field 'name', 'Andrew White' - assert_field 'tel', '024 7699 9999' - assert_field 'email', 'andyw@example.com' - end - - def test_address_mapping - @helper.billing_address :address1 => '1 Nowhere Close', - :address2 => 'Electric Wharf', - :city => 'Coventry', - :state => 'Warwickshire', - :zip => 'CV1 1AA', - :country => 'GB' - - assert_field 'address', '1 Nowhere Close&#10;Electric Wharf&#10;Coventry&#10;Warwickshire' - assert_field 'postcode', 'CV1 1AA' - assert_field 'country', 'GB' - end - - def test_address_mapping_without_address1_and_state - @helper.billing_address :address1 => 'Teststr. 1', - :city => 'Berlin', - :zip => '10000', - :country => 'DE' - - assert_field 'address', 'Teststr. 1&#10;Berlin' - assert_field 'postcode', '10000' - assert_field 'country', 'DE' - end - - def test_unknown_mapping - assert_nothing_raised do - @helper.company_address :address => '1 Somewhere Else' - end - end - - def test_setting_invalid_address_field - fields = @helper.fields.dup - @helper.billing_address :street => 'Twilight Zone' - assert_equal fields, @helper.fields - end - - def test_encryption - @helper.encrypt 'secret', [:amount, :currency, :account, :order] - - assert_field 'signatureFields', 'amount:currency:instId:cartId' - assert_field 'signature', 'adbfc78d82c9a23cbc075f4dfe05daed' - end - - def test_valid_from_time - @helper.valid_from Time.utc('2007-01-01 00:00:00') - assert_field 'authValidFrom', '1167609600000' - end - - def test_valid_to_time - @helper.valid_to Time.utc('2007-01-01 00:00:00') - assert_field 'authValidTo', '1167609600000' - end - - def test_custom_params - @helper.response_params :custom_1 => 'Custom Value 1' - @helper.callback_params :custom_2 => 'Custom Value 2' - @helper.combined_params :custom_3 => 'Custom Value 3' - - assert_field 'C_custom_1', 'Custom Value 1' - assert_field 'M_custom_2', 'Custom Value 2' - assert_field 'MC_custom_3', 'Custom Value 3' - end - - def test_return_url - @helper.return_url 'some_return_url' - - assert_equal 'some_return_url', @helper.fields['MC_return'] - end -end \ No newline at end of file diff --git a/test/unit/integrations/hi_trust_module_test.rb b/test/unit/integrations/hi_trust_module_test.rb deleted file mode 100644 index 3480d66a11a..00000000000 --- a/test/unit/integrations/hi_trust_module_test.rb +++ /dev/null @@ -1,13 +0,0 @@ -require 'test_helper' - -class HiTrustModuleTest < Test::Unit::TestCase - include ActiveMerchant::Billing::Integrations - - def test_notification_method - assert_instance_of HiTrust::Notification, HiTrust.notification('name=cody', {}) - end - - def test_return_method - assert_instance_of HiTrust::Return, HiTrust.return('name=cody', {}) - end -end diff --git a/test/unit/integrations/ipay88_module_test.rb b/test/unit/integrations/ipay88_module_test.rb deleted file mode 100644 index 3125714060c..00000000000 --- a/test/unit/integrations/ipay88_module_test.rb +++ /dev/null @@ -1,13 +0,0 @@ -require 'test_helper' - -class Ipay88ModuleTest < Test::Unit::TestCase - include ActiveMerchant::Billing::Integrations - - def test_return_method - assert_instance_of Ipay88::Return, Ipay88.return('name=cody') - end - - def test_service_url - assert_equal "https://www.mobile88.com/epayment/entry.asp", Ipay88.service_url - end -end diff --git a/test/unit/integrations/liqpay_module_test.rb b/test/unit/integrations/liqpay_module_test.rb deleted file mode 100644 index ae43f5c2fa0..00000000000 --- a/test/unit/integrations/liqpay_module_test.rb +++ /dev/null @@ -1,19 +0,0 @@ -require 'test_helper' - -class LiqpayModuleTest < Test::Unit::TestCase - include ActiveMerchant::Billing::Integrations - - def test_helper_method - assert_instance_of Liqpay::Helper, Liqpay.helper(123, 'test') - end - - def test_notification_method - assert_instance_of Liqpay::Notification, Liqpay.notification(http_post_data) - end - - private - - def http_post_data - 'signature=mOJdMHeDHlGlBY0NKZiI1wlU1BY%3D&operation_xml=PHJlc3BvbnNlPgo8c2VuZGVyX3Bob25lPis3OTA5NDM0MzMzNTwvc2VuZGVyX3Bob25lPgo8c3Rh%0AdHVzPnN1Y2Nlc3M8L3N0YXR1cz4KPHZlcnNpb24%2BMS4yPC92ZXJzaW9uPgo8b3JkZXJfaWQ%2BMTc8%0AL29yZGVyX2lkPgo8bWVyY2hhbnRfaWQ%2BaTU1MjA0Njg0OTg8L21lcmNoYW50X2lkPgo8cGF5X2Rl%0AdGFpbHM%2BPC9wYXlfZGV0YWlscz4KPGRlc2NyaXB0aW9uPsOQwp7DkMK%2Fw5DCu8OQwrDDkcKCw5DC%0AsCDDkcKHw5DCtcORwoDDkMK1w5DCtyDDkMK4w5DCvcORwoLDkMK1w5HCgMOQwr3DkMK1w5HCgi48%0AL2Rlc2NyaXB0aW9uPgo8Y3VycmVuY3k%2BUlVSPC9jdXJyZW5jeT4KPGFtb3VudD4xLjAwPC9hbW91%0AbnQ%2BCjxwYXlfd2F5PmNhcmQ8L3BheV93YXk%2BCjx0cmFuc2FjdGlvbl9pZD4yMTMxNjE0NTwvdHJh%0AbnNhY3Rpb25faWQ%2BCjxhY3Rpb24%2Bc2VydmVyX3VybDwvYWN0aW9uPgo8Y29kZT48L2NvZGU%2BCjwv%0AcmVzcG9uc2U%2B%0A' - end -end diff --git a/test/unit/integrations/maksuturva_module_test.rb b/test/unit/integrations/maksuturva_module_test.rb deleted file mode 100644 index 41f1e932f92..00000000000 --- a/test/unit/integrations/maksuturva_module_test.rb +++ /dev/null @@ -1,9 +0,0 @@ -require 'test_helper' - -class MaksuturvaModuleTest < Test::Unit::TestCase - include ActiveMerchant::Billing::Integrations - - def test_notification_method - assert_instance_of Maksuturva::Notification, Maksuturva.notification("pmt_action" => "NEW_PAYMENT_EXTENDED", "pmt_version" => "0004", "pmt_id" => "2", "pmt_reference" => "134662", "pmt_amount" => "200,00", "pmt_currency" => "EUR", "pmt_sellercosts" => "0,00", "pmt_paymentmethod" => "FI01", "pmt_escrow" => "N", "pmt_hash" => "BDF4F41FA194612017CBE13CF7670971") - end -end diff --git a/test/unit/integrations/moneybookers_module_test.rb b/test/unit/integrations/moneybookers_module_test.rb deleted file mode 100644 index 48ea0d3296e..00000000000 --- a/test/unit/integrations/moneybookers_module_test.rb +++ /dev/null @@ -1,14 +0,0 @@ -require 'test_helper' - -class MoneybookersModuleTest < Test::Unit::TestCase - include ActiveMerchant::Billing::Integrations - - def test_notification_method - assert_instance_of Moneybookers::Notification, Moneybookers.notification('name=cody', :credential2 => 'secret') - end - - def test_service_url - url = 'https://www.moneybookers.com/app/payment.pl' - assert_equal url, Moneybookers.service_url - end -end diff --git a/test/unit/integrations/nochex_module_test.rb b/test/unit/integrations/nochex_module_test.rb deleted file mode 100644 index 02f72cf49f2..00000000000 --- a/test/unit/integrations/nochex_module_test.rb +++ /dev/null @@ -1,13 +0,0 @@ -require 'test_helper' - -class NochexModuleTest < Test::Unit::TestCase - include ActiveMerchant::Billing::Integrations - - def test_notification_method - assert_instance_of Nochex::Notification, Nochex.notification('name=cody', {}) - end - - def test_return_method - assert_instance_of Nochex::Return, Nochex.return('name=cody', {}) - end -end diff --git a/test/unit/integrations/notifications/a1agregator_notification_test.rb b/test/unit/integrations/notifications/a1agregator_notification_test.rb deleted file mode 100644 index 8d7cc43008c..00000000000 --- a/test/unit/integrations/notifications/a1agregator_notification_test.rb +++ /dev/null @@ -1,46 +0,0 @@ -require 'test_helper' - -class A1agregatorNotificationTest < Test::Unit::TestCase - include ActiveMerchant::Billing::Integrations - - def setup - @a1agregator = A1agregator::Notification.new(http_raw_data, :secret => 'some_secret') - end - - def test_accessors - assert @a1agregator.complete? - assert_equal "132", @a1agregator.transaction_id - assert_equal "ProductName", @a1agregator.title - assert_equal "Comment", @a1agregator.comment - assert_equal "234", @a1agregator.partner_id - assert_equal "345", @a1agregator.service_id - assert_equal "456", @a1agregator.item_id - assert_equal "wm", @a1agregator.type - assert_equal "99", @a1agregator.partner_income - assert_equal "100", @a1agregator.system_income - assert_equal "e7e634f0a068b29b5457a189396d2c78", @a1agregator.security_key - - assert_equal "100", @a1agregator.gross - assert_false @a1agregator.test? - end - - def test_compositions - assert_equal Money.new(10000, 'RUB'), @a1agregator.amount - end - - def test_acknowledgement - assert @a1agregator.acknowledge - end - - def test_respond_to_acknowledge - assert @a1agregator.respond_to?(:acknowledge) - end - -private - - def http_raw_data - "tid=132&name=ProductName&comment=Comment&partner_id=234&service_id=345\ -&order_id=456&type=wm&partner_income=99&system_income=100\ -&check=e7e634f0a068b29b5457a189396d2c78" - end -end diff --git a/test/unit/integrations/notifications/authorize_net_sim_notification_test.rb b/test/unit/integrations/notifications/authorize_net_sim_notification_test.rb deleted file mode 100644 index 2aa61698dae..00000000000 --- a/test/unit/integrations/notifications/authorize_net_sim_notification_test.rb +++ /dev/null @@ -1,61 +0,0 @@ -require 'test_helper' - -class AuthorizeNetSimNotificationTest < Test::Unit::TestCase - include ActiveMerchant::Billing::Integrations - - def setup - @authorize_net_sim = AuthorizeNetSim::Notification.new(http_raw_data) - end - - def test_accessors_when_not_set - @authorize_net_sim = AuthorizeNetSim::Notification.new("") - assert !@authorize_net_sim.complete? - assert_equal [], @authorize_net_sim.billing_address.values.compact - assert_equal [], @authorize_net_sim.ship_to_address.values.compact - assert_equal({}, @authorize_net_sim.all_custom_values_passed_in_and_now_passed_back_to_us) - assert !@authorize_net_sim.avs_code_matches? - assert !@authorize_net_sim.cavv_matches? - - assert !@authorize_net_sim.test? # default is false - # should fail...assert !@authorize_net_sim.acknowledge('', '') - [:customer_id, :auth_code, :po_num, - :tax, :transaction_type, :method, :method_available, :invoice_num, - :duty, :freight, :shipping, :description, :response_code_as_ruby_symbol, :response_reason_text, :response_reason_code, - :response_subcode, :tax_exempt, :avs_code, :cvv2_resp_code, :cavv_response, - :item_id, :transaction_id, :payer_email, :security_key, :gross].each{|m| - assert_equal nil, @authorize_net_sim.send(m) - } - end - - def test_compositions - assert_equal Money.new(12100, 'USD'), @authorize_net_sim.amount - end - - def test_accessors_when_set - {:gross => "121.00", :auth_code => "000000", :payer_email => "test@test.com", :item_id => "441543269", :complete? => true, - :duty => "0.0000", :customer_id => "10", :avs_code => "P", :cvv2_resp_code_matches? => false, :cvv2_resp_code => "", :tax_exempt => "FALSE", - :billing_address => {:country=>"United States of America", :fax=>"", :email=>"test@test.com", :address=>"test", :first_name=>"test", :company=>"", :city=>"test", :state=>"UT", :zip=>"84601", :last_name=>"test"}, - :ship_to_address => {:country=>"United States of America", :address=>"test", :first_name=>"test", :city=>"test", :zip=>"84601", :last_name=>"test"}, - :test? => true,:response_reason_code => "1", :status => true, :security_key => "9B934370EE2378E844B0A6A6C6FC42E4", :response_code_as_ruby_symbol => :approved, - :cavv_matches? => true, :po_num => "", :all_custom_values_passed_in_and_now_passed_back_to_us => {"commit"=>"Pay securely with Authorize.net"}, - :receiver_email => nil, :invoice_num => "441543269"}.each{|m, v| - assert_equal(v, @authorize_net_sim.send(m)) - } - end - - def test_acknowledgement - assert !@authorize_net_sim.acknowledge('abc', 'def') - assert @authorize_net_sim.acknowledge('', '8wd65QSj') - end - - def test_respond_to_acknowledge - assert @authorize_net_sim.respond_to?(:acknowledge) - end - - private - - def http_raw_data - "x_response_code=1&x_response_subcode=1&x_response_reason_code=1&x_response_reason_text=%28TESTMODE%29+This+transaction+has+been+approved%2E&x_auth_code=000000&x_avs_code=P&x_trans_id=0&x_invoice_num=441543269&x_description=&x_amount=121%2E00&x_method=CC&x_type=auth%5Fcapture&x_cust_id=10&x_first_name=test&x_last_name=test&x_company=&x_address=test&x_city=test&x_state=UT&x_zip=84601&x_country=United+States+of+America&x_phone=8013776152&x_fax=&x_email=test%40test%2Ecom&x_ship_to_first_name=test&x_ship_to_last_name=test&x_ship_to_company=&x_ship_to_address=test&x_ship_to_city=test&x_ship_to_state=UT&x_ship_to_zip=84601&x_ship_to_country=United+States+of+America&x_tax=0%2E0000&x_duty=0%2E0000&x_freight=25%2E0000&x_tax_exempt=FALSE&x_po_num=&x_MD5_Hash=9B934370EE2378E844B0A6A6C6FC42E4&x_cvv2_resp_code=&x_cavv_response=&x_test_request=true&commit=Pay+securely+with+Authorize%2Enet&x_method_available=true" - end - -end diff --git a/test/unit/integrations/notifications/chronopay_notification_test.rb b/test/unit/integrations/notifications/chronopay_notification_test.rb deleted file mode 100644 index 90140689780..00000000000 --- a/test/unit/integrations/notifications/chronopay_notification_test.rb +++ /dev/null @@ -1,66 +0,0 @@ -require 'test_helper' - -class ChronopayNotificationTest < Test::Unit::TestCase - include ActiveMerchant::Billing::Integrations - - def setup - @notification = Chronopay::Notification.new(http_raw_data) - end - - def test_notification - assert_instance_of Chronopay::Notification, @notification - end - - def test_accessors - assert @notification.complete? - assert_equal "Completed", @notification.status - assert_equal "003176-000000005", @notification.customer_id - assert_equal "003176-0001", @notification.site_id - assert_equal "003176-0001-0001", @notification.product_id - assert_equal "CODY FAUSER", @notification.name - assert_equal 'XX', @notification.state - assert_equal 'Ottawa', @notification.city - assert_equal '138 Clarence St.', @notification.street - assert_equal 'CAD', @notification.currency - assert_equal 'first', @notification.item_id - assert_equal 'second', @notification.custom2 - assert_equal 'third', @notification.custom3 - - # If the date and time are nil then it is a test notification - assert @notification.test? - end - - # docs - def test_parse_received_at - # mm/dd/yyyy format - raw_received = "date=03%2f30%2f2006&time=12%3a30%3a10" - @notification = Chronopay::Notification.new(raw_received) - assert_equal CGI.unescape("03%2f30%2f2006"), @notification.params['date'] - assert_equal CGI.unescape("12%3a30%3a10"), @notification.params['time'] - assert_equal Time.local(2006, 3, 30, 12, 30, 10), @notification.received_at - end - - def test_compositions - assert_equal Money.new(50000, 'CAD'), @notification.amount - end - - def test_payment_successful_status - notification = Chronopay::Notification.new('transaction_type=onetime') - assert_equal 'Completed', notification.status - end - - def test_payment_failure_status - notification = Chronopay::Notification.new('transaction_type=decline') - assert_equal 'Failed', notification.status - end - - def test_acknowledge - assert @notification.acknowledge - end - - private - def http_raw_data - "transaction_type=onetime&customer_id=003176-000000005&site_id=003176-0001&product_id=003176-0001-0001&date=&time=&transaction_id=&email=codyfauser%40gmail.com&country=CAN&name=CODY+FAUSER&city=Ottawa&street=138+Clarence+St.&phone=&state=XX&zip=K1N+5P8&language=EN&cs1=first&cs2=second&cs3=third&username=&password=&total=500.00&currency=CAD" - end -end - diff --git a/test/unit/integrations/notifications/direc_pay_notification_test.rb b/test/unit/integrations/notifications/direc_pay_notification_test.rb deleted file mode 100644 index af77cb090e9..00000000000 --- a/test/unit/integrations/notifications/direc_pay_notification_test.rb +++ /dev/null @@ -1,69 +0,0 @@ -require 'test_helper' - -class DirecPayNotificationTest < Test::Unit::TestCase - include ActiveMerchant::Billing::Integrations - - def setup - @direc_pay = DirecPay::Notification.new(http_raw_data_success) - end - - def test_success - assert @direc_pay.complete? - assert_equal "Completed", @direc_pay.status - assert_equal "1001010000026481", @direc_pay.transaction_id - assert_equal "1001", @direc_pay.item_id - assert_equal "1.00", @direc_pay.gross - assert_equal "INR", @direc_pay.currency - assert_equal "IND", @direc_pay.country - end - - def test_failure - @direc_pay = DirecPay::Notification.new(http_raw_data_failure) - assert !@direc_pay.complete? - assert_equal "Failed", @direc_pay.status - assert_equal "1001010000026516", @direc_pay.transaction_id - assert_equal "1001", @direc_pay.item_id - assert_equal "1.00", @direc_pay.gross - assert_equal "INR", @direc_pay.currency - assert_equal "IND", @direc_pay.country - assert @direc_pay.acknowledge - end - - def test_error - @direc_pay = DirecPay::Notification.new(http_raw_data_error) - assert !@direc_pay.complete? - assert_equal "Error", @direc_pay.status - assert_equal "1001010000026516", @direc_pay.transaction_id - end - - def test_compositions - assert_equal Money.new(100, 'INR'), @direc_pay.amount - end - - def test_acknowledgement - assert @direc_pay.acknowledge - end - - def test_respond_to_acknowledge - assert @direc_pay.respond_to?(:acknowledge) - end - - - private - - def http_raw_data_success - "responseparams=1001010000026481|SUCCESS|IND|INR|1|1001|1.00|" - end - - def http_raw_data_failure - "responseparams=1001010000026516|FAIL|IND|INR|1|1001|1.00|" - end - - def http_raw_data_pending - "responseparams=1001010000026516|PENDING|IND|INR|1|1001|1|" - end - - def http_raw_data_error - "responseparams=1001010000026516|ERROR|Record not found for this transaction reference number" - end -end diff --git a/test/unit/integrations/notifications/directebanking_notification_test.rb b/test/unit/integrations/notifications/directebanking_notification_test.rb deleted file mode 100644 index 83dc214534e..00000000000 --- a/test/unit/integrations/notifications/directebanking_notification_test.rb +++ /dev/null @@ -1,73 +0,0 @@ -require 'test_helper' - -class DirectebankingNotificationTest < Test::Unit::TestCase - include ActiveMerchant::Billing::Integrations - - def setup - @deb = Directebanking.notification(http_raw_data, :credential4 => "3qx-:03DDfmUVh}b16#Y") - end - - def test_accessors - assert @deb.complete? - assert_equal 'Completed', @deb.status - assert_equal "19488-100576-4D6A7F5F-7FC4", @deb.transaction_id - assert_equal "123456789", @deb.item_id - assert_equal "1.00", @deb.gross - assert_equal "EUR", @deb.currency - assert_equal Time.parse("2011-02-27 17:45:23"), @deb.received_at - assert @deb.test? - end - - def test_compositions - assert_equal Money.new(100, 'EUR'), @deb.amount - end - - def test_acknowledgement - assert @deb.acknowledge - end - - def test_acknowledgement_with_wrong_password - @deb = Directebanking::Notification.new(http_raw_data, :credential4 => "XXXX") - # needs to fail cause the password is wrong - assert !@deb.acknowledge - end - - def test_credential4_required - assert_raises ArgumentError do - Directebanking::Notification.new(http_raw_data, {}) - end - assert_nothing_raised do - Directebanking::Notification.new(http_raw_data, :credential4 => 'secret') - end - end - - def test_directebanking_attributes - assert_equal "19488", @deb.user_id - assert_equal "Project", @deb.reason_1 - assert_equal "Test", @deb.reason_2 - assert_equal "", @deb.user_variable_4 - assert_equal "", @deb.user_variable_5 - end - - def test_generate_signature_string - assert_equal "19488-100576-4D6A7F5F-7FC4|19488|100576|Musterman, Petra|2345XXXXXX|00XXX|Testbank|PNAGXXXXXXX|AT680000XXXXXXXXXXXX|AT|BIZZONS eMarketing GmbH|0000XXXXXX|19XXX|Bankhaus KXXXXX|KREXXXXX|AT031952XXXXXXXXXXXX|AT|0|1.00|EUR|Project|Test|1|123456789|https://localhost:8080/directebanking/return|||||2011-02-27 17:45:23|3qx-:03DDfmUVh}b16#Y", - @deb.generate_signature_string - end - - def test_generate_md5check - assert_equal "9c39be1c7bfdb563467819f41d650fb4d2acad64", @deb.generate_signature - end - - private - def http_raw_data - "transaction=19488-100576-4D6A7F5F-7FC4&user_id=19488&project_id=100576&sender_holder=Musterman%2C+Petra"+ - "&sender_account_number=2345XXXXXX&sender_bank_code=00XXX&sender_bank_name=Testbank&sender_bank_bic=PNAGXXXXXXX"+ - "&sender_iban=AT680000XXXXXXXXXXXX&sender_country_id=AT&recipient_holder=BIZZONS+eMarketing+GmbH"+ - "&recipient_account_number=0000XXXXXX&recipient_bank_code=19XXX&recipient_bank_name=Bankhaus+KXXXXX"+ - "&recipient_bank_bic=KREXXXXX&recipient_iban=AT031952XXXXXXXXXXXX&recipient_country_id=AT"+ - "&international_transaction=0&amount=1.00&currency_id=EUR&reason_1=Project&reason_2=Test&security_criteria=1"+ - "&user_variable_0=123456789&user_variable_1=https%3A%2F%2Flocalhost%3A8080%2Fdirectebanking%2Freturn&user_variable_2="+ - "&user_variable_3=&user_variable_4=&user_variable_5=&email_sender=&email_recipient="+ - "&created=2011-02-27+17%3A45%3A23&hash=9c39be1c7bfdb563467819f41d650fb4d2acad64" - end -end diff --git a/test/unit/integrations/notifications/dotpay_notification_test.rb b/test/unit/integrations/notifications/dotpay_notification_test.rb deleted file mode 100644 index da8d4fb3833..00000000000 --- a/test/unit/integrations/notifications/dotpay_notification_test.rb +++ /dev/null @@ -1,91 +0,0 @@ -require 'test_helper' - -class DotpayNotificationTest < Test::Unit::TestCase - include ActiveMerchant::Billing::Integrations - - def setup - @dotpay = Dotpay::Notification.new(http_raw_data, :pin => 'my3secret1pinCod') - @dotpay_error = Dotpay::Notification.new(http_raw_data_error, :pin => 'my3secret1pinCod') - @dotpay_test = Dotpay::Notification.new(http_raw_test_data, :pin => 'my3secret1pinCod') - end - - def test_accessors - assert @dotpay.complete? - assert_equal "OK", @dotpay.status - assert_equal "2", @dotpay.t_status - assert_equal "42655trans", @dotpay.t_id - assert_equal "150.00", @dotpay.gross - assert_equal "1234567890", @dotpay.control - assert_equal "150.00 PLN", @dotpay.orginal_amount - assert_equal "example@email.com", @dotpay.email - assert_equal "Description", @dotpay.description - assert_equal "PLN", @dotpay.currency - assert_equal "8f63de71b987e61cb8fa98bcb88100a2", @dotpay.md5 - end - - def test_accessors_error - assert !@dotpay_error.complete? - assert_equal "3", @dotpay_error.t_status - assert_equal "1e0a636401a3a0604890a957b0d014f7", @dotpay_error.md5 - end - - def test_compositions - assert_equal Money.new(15000, 'PLN'), @dotpay.amount - end - - # Replace with real successful acknowledgement code - def test_acknowledgement - assert @dotpay.acknowledge - end - - def test_pin_setter - @dotpay.pin = 'wrongpin' - assert !@dotpay.acknowledge - end - - def test_respond_to_acknowledge - assert @dotpay.respond_to?(:acknowledge) - end - - def test_acknowledgement_with_wrong_pin - @dotpay = Dotpay::Notification.new(http_raw_data, :pin => "XXXX") - assert !@dotpay.acknowledge - end - - def test_generate_signature_string - assert_equal "my3secret1pinCod:42655:1234567890:42655trans:150.00:example@email.com:::::2", - @dotpay.generate_signature_string - end - - def test_generate_md5check - assert_equal "8f63de71b987e61cb8fa98bcb88100a2", @dotpay.generate_signature - end - - def test_generate_md5check_when_error - assert_equal "1e0a636401a3a0604890a957b0d014f7", @dotpay_error.generate_signature - end - - def test_test_notification - assert !@dotpay.test? - assert @dotpay_test.test? - end - - private - def http_raw_data - "status=OK&id=42655&t_id=42655trans&transaction_id=42655trans&control=1234567890&amount=150.00" + - "&orginal_amount=150.00 PLN&email=example@email.com&description=Description&t_status=2&t_date=" + - "&version=1.4&channel=&code=&service=&md5=8f63de71b987e61cb8fa98bcb88100a2" - end - - def http_raw_data_error - "status=OK&id=42655&t_id=42655trans&transaction_id=42655trans&control=1234567890&amount=150.00" + - "&orginal_amount=150.00 PLN&email=example@email.com&description=Description&t_status=3&t_date=" + - "&version=1.4&channel=&code=&service=&md5=1e0a636401a3a0604890a957b0d014f7" - end - - def http_raw_test_data - "status=OK&id=42655&t_id=42655-TST11&transaction_id=42655-TST11&control=1234567890&amount=150.00" + - "&orginal_amount=150.00 PLN&email=example@email.com&description=Description&t_status=2&t_date=" + - "&version=1.4&channel=&code=&service=&md5=455acc67fa94b1d6e5c0f7ebd7b032c9" - end -end diff --git a/test/unit/integrations/notifications/dwolla_notification_test.rb b/test/unit/integrations/notifications/dwolla_notification_test.rb deleted file mode 100644 index f8d1c9226f9..00000000000 --- a/test/unit/integrations/notifications/dwolla_notification_test.rb +++ /dev/null @@ -1,56 +0,0 @@ -require 'test_helper' - -class DwollaNotificationTest < Test::Unit::TestCase - include ActiveMerchant::Billing::Integrations - - def setup - @error_dwolla = Dwolla::Notification.new(http_raw_error_data, {:credential3 => '62hdv0jBjsBlD+0AmhVn9pQuULSC661AGo2SsksQTpqNUrff7Z'}) - @success = Dwolla::Notification.new(http_raw_success_data, {:credential3 => '62hdv0jBjsBlD+0AmhVn9pQuULSC661AGo2SsksQTpqNUrff7Z'}) - end - - def test_success_accessors - assert @success.complete? - assert_equal "abc123", @success.item_id - assert_equal "Completed", @success.status - assert_equal 0.01, @success.gross - assert_equal "USD", @success.currency - assert_false @success.test? - end - - def test_error_accessors - assert_false @error_dwolla.complete? - assert_equal "abc123", @error_dwolla.item_id - assert_equal "Failed", @error_dwolla.status - assert_equal 300.00, @error_dwolla.gross - assert_equal "USD", @error_dwolla.currency - assert_equal "Insufficient funds exist to complete the transaction.", @error_dwolla.error - assert_false @error_dwolla.test? - end - - def test_compositions - assert_equal Money.new(1, 'USD'), @success.amount - end - - # Replace with real successful acknowledgement code - def test_acknowledgement - assert_equal true, @success.acknowledge - end - - def test_respond_to_acknowledge - assert @success.respond_to?(:acknowledge) - end - - def test_raw_should_be_set - assert @success.raw.present? - end - - private - - def http_raw_error_data - %*{"Amount":300.00,"OrderId":"abc123","Status":"Failed","Error":"Insufficient funds exist to complete the transaction.","TransactionId":null,"CheckoutId":"a6129f18-2932-4c4f-ac36-4363aa2bd19b","Signature":"641ac3fb80566eb33c5f6bf3db282a8c9f912a71","TestMode":"false","ClearingDate":""}* - end - - def http_raw_success_data - %*{"Amount":0.01,"OrderId":"abc123","Status":"Completed","Error":null,"TransactionId":3165397,"CheckoutId":"ac5b910a-7ec1-4b65-9f68-90449ed030f6","Signature":"7d4c5deaf9178faae7c437fd8693fc0b97b1b22b","TestMode":"false","ClearingDate":"6/8/2013 8:07:41 PM"}* - end -end diff --git a/test/unit/integrations/notifications/e_payment_plans_notification_test.rb b/test/unit/integrations/notifications/e_payment_plans_notification_test.rb deleted file mode 100644 index 394d65de051..00000000000 --- a/test/unit/integrations/notifications/e_payment_plans_notification_test.rb +++ /dev/null @@ -1,34 +0,0 @@ -require 'test_helper' - -class EPaymentPlansNotificationTest < Test::Unit::TestCase - include ActiveMerchant::Billing::Integrations - - def setup - @e_payment_plan = EPaymentPlans::Notification.new(http_raw_data) - end - - def test_accessors - assert @e_payment_plan.complete? - assert_equal "Completed", @e_payment_plan.status - assert_equal "12345", @e_payment_plan.item_id - assert_equal "35.52", @e_payment_plan.gross - assert_equal "USD", @e_payment_plan.currency - assert_equal Time.utc(2011,05,24,23,55,52), @e_payment_plan.received_at - assert_equal "789123456", @e_payment_plan.transaction_id - assert_equal "xxxxxxxxxx", @e_payment_plan.security_key - assert @e_payment_plan.test? - end - - def test_acknowledgement - EPaymentPlans::Notification.any_instance.stubs(:ssl_post).returns('AUTHORISED') - assert @e_payment_plan.acknowledge - - EPaymentPlans::Notification.any_instance.stubs(:ssl_post).returns('DECLINED') - assert !@e_payment_plan.acknowledge - end - - private - def http_raw_data - "received_at=2011-05-24 23:55:52 UTC&status=completed&item_id=12345&transaction_id=789123456&security_key=xxxxxxxxxx&currency=USD&gross=35.52&test=test" - end -end diff --git a/test/unit/integrations/notifications/easy_pay_notification_test.rb b/test/unit/integrations/notifications/easy_pay_notification_test.rb deleted file mode 100644 index 0b033462438..00000000000 --- a/test/unit/integrations/notifications/easy_pay_notification_test.rb +++ /dev/null @@ -1,51 +0,0 @@ -require 'test_helper' - -class EasyPayNotificationTest < Test::Unit::TestCase - include ActiveMerchant::Billing::Integrations - - def setup - @easypay = EasyPay::Notification.new(http_raw_data, :credential2 => 'dh48djklhgl5893j') - end - - def test_accessors - assert @easypay.complete? - assert_equal "100.00", @easypay.gross - assert_equal "1000", @easypay.item_id - end - - def test_compositions - assert_equal BigDecimal.new("100"), @easypay.amount - end - - def test_credential2_required - assert_raises ArgumentError do - EasyPay::Notification.new(http_raw_data, {}) - end - - assert_nothing_raised do - EasyPay::Notification.new(http_raw_data, :credential2 => 'secret') - end - end - - def test_respond_to_acknowledge - assert @easypay.respond_to?(:acknowledge) - end - - def test_acknowledgement - assert @easypay.acknowledge - end - - def test_wrong_signature - @easypay = EasyPay::Notification.new(http_raw_data_with_wrong_signature, :credential2 => 'dh48djklhgl5893j') - assert !@easypay.acknowledge - end - - private - def http_raw_data - "order_mer_code=1000&sum=100.00&mer_no=ok6666&card=00539900&purch_date=2006-09-11 22:45:21&notify_signature=633f711926e02eeb22fb0025c2308e75" - end - - def http_raw_data_with_wrong_signature - "order_mer_code=1000&sum=100.00&mer_no=ok6666&card=00539900&purch_date=2006-09-11 22:45:21&notify_signature=wrong" - end -end diff --git a/test/unit/integrations/notifications/epay_notification_test.rb b/test/unit/integrations/notifications/epay_notification_test.rb deleted file mode 100644 index f253635df12..00000000000 --- a/test/unit/integrations/notifications/epay_notification_test.rb +++ /dev/null @@ -1,51 +0,0 @@ -require 'test_helper' - -class EpayNotificationTest < Test::Unit::TestCase - include ActiveMerchant::Billing::Integrations - - def setup - @epay = Epay::Notification.new(http_raw_data, :credential3 => "secretmd5") - end - - def test_accessors - assert @epay.complete? - assert_equal "9572252", @epay.transaction_id - assert_equal "189139", @epay.item_id - assert_equal "3987.50", @epay.gross - assert_equal "DKK", @epay.currency - assert_equal Time.parse("2012-04-03 14:42:00"), @epay.received_at - assert !@epay.test? - end - - def test_compositions - assert_equal Money.new(398750, 'DKK'), @epay.amount - end - - def test_acknowledgement - assert @epay.acknowledge - end - - def test_failed_acknnowledgement - @epay = Epay::Notification.new(http_raw_data, :credential3 => "badmd5string") - assert !@epay.acknowledge - end - - def test_generate_md5string - assert_equal "1957225218913939875020820120403144203453903XXXXXX9862secretmd5", - @epay.generate_md5string - end - - def test_generate_md5hash - assert_equal "6f81086c474f03af80ef894e48f81f99", @epay.generate_md5hash - end - - def test_respond_to_acknowledge - assert @epay.respond_to?(:acknowledge) - end - - private - def http_raw_data - "language=1&txnid=9572252&orderid=189139&amount=398750&currency=208&date=20120403&time=1442&txnfee=0&paymenttype=3&cardno=453903XXXXXX9862&hash=6f81086c474f03af80ef894e48f81f99" - end - -end diff --git a/test/unit/integrations/notifications/first_data_notification_test.rb b/test/unit/integrations/notifications/first_data_notification_test.rb deleted file mode 100644 index 9d9fc2e8c5e..00000000000 --- a/test/unit/integrations/notifications/first_data_notification_test.rb +++ /dev/null @@ -1,77 +0,0 @@ -require 'test_helper' - -class FirstDataNotificationTest < Test::Unit::TestCase - include ActiveMerchant::Billing::Integrations - - def setup - @first_data = FirstData::Notification.new(http_raw_data) - end - - def test_accessors_when_not_set - @first_data = FirstData::Notification.new("") - assert !@first_data.complete? - assert_equal [], @first_data.billing_address.values.compact - assert_equal [], @first_data.ship_to_address.values.compact - assert_equal({}, @first_data.all_custom_values_passed_in_and_now_passed_back_to_us) - assert !@first_data.avs_code_matches? - assert !@first_data.cavv_matches? - - assert !@first_data.test? # default is false - [ - :customer_id, :auth_code, :po_num, - :tax, :transaction_type, :method, :method_available, :invoice_num, - :duty, :freight, :shipping, :description, :response_code_as_ruby_symbol, - :response_reason_text, :response_reason_code, - :response_subcode, :tax_exempt, :avs_code, :cvv2_resp_code, :cavv_response, - :item_id, :transaction_id, :payer_email, :security_key, :gross - ].each{|m| assert_equal nil, @first_data.send(m)} - end - - def test_compositions - assert_equal Money.new(12100, 'USD'), @first_data.amount - end - - def test_accessors_when_set - { - :gross => "121.00", :auth_code => "000000", - :payer_email => "test@test.com", :item_id => "441543269", - :complete? => true, :duty => "0.0000", :customer_id => "10", - :avs_code => "P", :cvv2_resp_code_matches? => false, - :cvv2_resp_code => "", :tax_exempt => "FALSE", - :billing_address => { - :country => "United States of America", :fax => "", - :email => "test@test.com", :address => "test", :first_name => "test", - :company => "", :city => "test", :state => "UT", :zip => "84601", - :last_name => "test" - }, - :ship_to_address => { - :country => "United States of America", :address => "test", - :first_name => "test", :city => "test", :zip => "84601", - :last_name => "test" - }, - :test? => true,:response_reason_code => "1", :status => true, - :security_key => "9B934370EE2378E844B0A6A6C6FC42E4", - :response_code_as_ruby_symbol => :approved, :cavv_matches? => true, - :po_num => "", - :all_custom_values_passed_in_and_now_passed_back_to_us => { - "commit" => "Pay securely with First Data" - }, - :receiver_email => nil, :invoice_num => "441543269" - }.each{|m, v| assert_equal(v, @first_data.send(m))} - end - - def test_acknowledgement - assert !@first_data.acknowledge('abc', 'def') - assert @first_data.acknowledge('', '8wd65QSj') - end - - def test_respond_to_acknowledge - assert @first_data.respond_to?(:acknowledge) - end - - private - - def http_raw_data - "x_response_code=1&x_response_subcode=1&x_response_reason_code=1&x_response_reason_text=%28TESTMODE%29+This+transaction+has+been+approved%2E&x_auth_code=000000&x_avs_code=P&x_trans_id=0&x_invoice_num=441543269&x_description=&x_amount=121%2E00&x_method=CC&x_type=auth%5Fcapture&x_cust_id=10&x_first_name=test&x_last_name=test&x_company=&x_address=test&x_city=test&x_state=UT&x_zip=84601&x_country=United+States+of+America&x_phone=8013776152&x_fax=&x_email=test%40test%2Ecom&x_ship_to_first_name=test&x_ship_to_last_name=test&x_ship_to_company=&x_ship_to_address=test&x_ship_to_city=test&x_ship_to_state=UT&x_ship_to_zip=84601&x_ship_to_country=United+States+of+America&x_tax=0%2E0000&x_duty=0%2E0000&x_freight=25%2E0000&x_tax_exempt=FALSE&x_po_num=&x_MD5_Hash=9B934370EE2378E844B0A6A6C6FC42E4&x_cvv2_resp_code=&x_cavv_response=&x_test_request=true&commit=Pay+securely+with+First+Data&x_method_available=true" - end -end diff --git a/test/unit/integrations/notifications/gestpay_notification_test.rb b/test/unit/integrations/notifications/gestpay_notification_test.rb deleted file mode 100644 index 133ab13a258..00000000000 --- a/test/unit/integrations/notifications/gestpay_notification_test.rb +++ /dev/null @@ -1,60 +0,0 @@ -require 'test_helper' - -class GestpayNotificationTest < Test::Unit::TestCase - include ActiveMerchant::Billing::Integrations - - def test_successful_notification - Gestpay::Notification.any_instance.expects(:ssl_get).returns('#decryptstring#PAY1_UICCODE=242*P1*PAY1_AMOUNT=1234.56*P1*PAY1_TRANSACTIONRESULT=OK*P1*PAY1_BANKTRANSACTIONID=ABCD1234*P1*PAY1_SHOPTRANSACTIONID=1000#/decryptstring#') - notification = Gestpay::Notification.new(raw_query_string) - assert notification.complete? - assert !notification.test? - assert_equal "Completed", notification.status - assert_equal "ABCD1234", notification.transaction_id - assert_equal "1000", notification.item_id - assert_equal "1234.56", notification.gross - assert_equal "EUR", notification.currency - assert_equal Money.new(123456, 'EUR'), notification.amount - end - - def test_failed_notification - Gestpay::Notification.any_instance.expects(:ssl_get).returns('#decryptstring#PAY1_UICCODE=242*P1*PAY1_AMOUNT=1234.56*P1*PAY1_TRANSACTIONRESULT=KO*P1*PAY1_BANKTRANSACTIONID=ABCD1234*P1*PAY1_SHOPTRANSACTIONID=1000#/decryptstring#') - notification = Gestpay::Notification.new(raw_query_string) - assert !notification.complete? - assert !notification.test? - assert_equal "Failed", notification.status - end - - def test_empty_notification - Gestpay::Notification.any_instance.stubs(:ssl_get).returns('') - notification = Gestpay::Notification.new('') - assert !notification.complete? - assert !notification.test? - assert_equal "Failed", notification.status - end - - def test_nil_notification - Gestpay::Notification.any_instance.stubs(:ssl_get).returns('') - notification = Gestpay::Notification.new(nil) - assert !notification.complete? - assert !notification.test? - assert_equal "Failed", notification.status - end - - def test_abandoned_order - Gestpay::Notification.any_instance.expects(:ssl_get).returns(unencrypted_string) - notification = Gestpay::Notification.new(raw_query_string) - assert !notification.complete? - assert !notification.test? - assert_equal "Failed", notification.status - assert_equal '1000', notification.item_id - end - - private - def raw_query_string - "a=900000&b=F7DEB36478FD84760F9134F23C922697272D57DE6D4518EB9B4D468B769D9A3A8071B6EB160B35CB412FC1820C7CC12D17B3141855B1ED55468613702A2E213DDE9DE5B0209E13C416448AE833525959F05693172D7F0656" - end - - def unencrypted_string - "#decryptstring#PAY1_TRANSACTIONRESULT=KO*P1*PAY1_SHOPTRANSACTIONID=1000*P1*PAY1_BANKTRANSACTIONID=*P1*PAY1_UICCODE=242*P1*PAY1_AMOUNT=50.00*P1*PAY1_AUTHORIZATIONCODE=*P1*PAY1_ERRORCODE=1143*P1*PAY1_ERRORDESCRIPTION=Transazione abbandonata dal compratore*P1*PAY1_CHEMAIL=*P1*PAY1_CHNAME=#/decryptstring#\r\n" - end -end diff --git a/test/unit/integrations/notifications/hi_trust_notification_test.rb b/test/unit/integrations/notifications/hi_trust_notification_test.rb deleted file mode 100644 index 8b26c16b02d..00000000000 --- a/test/unit/integrations/notifications/hi_trust_notification_test.rb +++ /dev/null @@ -1,59 +0,0 @@ -require 'test_helper' - -class HiTrustNotificationTest < Test::Unit::TestCase - include ActiveMerchant::Billing::Integrations - - def setup - @notification = HiTrust::Notification.new(successful_response) - end - - def teardown - ActiveMerchant::Billing::Base.integration_mode = :test - end - - def test_accessors - assert @notification.complete? - assert_equal "Completed", @notification.status - assert_equal "012345678901", @notification.transaction_id - assert_equal "1000", @notification.item_id - assert_equal "101010", @notification.account - assert_equal "5.00", @notification.gross - assert_equal "USD", @notification.currency - assert_equal Time.parse("2007-12-01.12.35.40.123456"), @notification.received_at - assert @notification.test? - end - - def test_compositions - assert_equal Money.new(500, 'USD'), @notification.amount - end - - def test_send_acknowledgement - assert @notification.acknowledge - end - - def test_respond_to_acknowledge - assert @notification.respond_to?(:acknowledge) - end - - def test_valid_sender_in_testmode_always_true - assert @notification.test? - assert @notification.valid_sender?('127.0.0.1') - assert @notification.valid_sender?(nil) - end - - def test_valid_sender - ActiveMerchant::Billing::Base.integration_mode = :production - assert @notification.valid_sender?('203.75.242.8') - end - - def test_invalid_sender - ActiveMerchant::Billing::Base.integration_mode = :production - assert_false @notification.valid_sender?('127.0.0.1') - assert_false @notification.valid_sender?(nil) - end - - private - def successful_response - 'retcode=00&ordernumber=1000&orderstatus=02&authCode=123456&eci=VISA3D&authRRN=012345678901&storeid=101010&approveamount=500&currency=USD&orderdate=2007-12-01.12.35.40.123456' - end -end diff --git a/test/unit/integrations/notifications/liqpay_notification_test.rb b/test/unit/integrations/notifications/liqpay_notification_test.rb deleted file mode 100644 index 3bcd9243504..00000000000 --- a/test/unit/integrations/notifications/liqpay_notification_test.rb +++ /dev/null @@ -1,47 +0,0 @@ -require 'test_helper' - -class LiqpayNotificationTest < Test::Unit::TestCase - include ActiveMerchant::Billing::Integrations - - def setup - @options = { :secret => '3HSiVfb06EVAbK39MWvlFdVJPyEKlnxhbJ' } - end - - def test_successful_transaction_notification - notification = Liqpay::Notification.new(successful_response, @options) - - assert notification.acknowledge - assert notification.complete? - assert notification.transaction_id.present? - assert_match notification.gross, '1.00' - assert_match notification.currency, 'RUR' - assert_match notification.item_id, '17' - end - - def test_failed_transaction_notification - notification = Liqpay::Notification.new(failed_response, @options) - - assert notification.acknowledge - assert !notification.complete? - assert notification.transaction_id.present? - assert_match notification.gross, '2410.00' - assert_match notification.currency, 'RUR' - assert_match notification.item_id, '19' - end - - def test_exception_without_http_params - assert_raise ArgumentError do - Liqpay::Notification.new('') - end - end - - private - - def successful_response - 'signature=mOJdMHeDHlGlBY0NKZiI1wlU1BY%3D&operation_xml=PHJlc3BvbnNlPgo8c2VuZGVyX3Bob25lPis3OTA5NDM0MzMzNTwvc2VuZGVyX3Bob25lPgo8c3Rh%0AdHVzPnN1Y2Nlc3M8L3N0YXR1cz4KPHZlcnNpb24%2BMS4yPC92ZXJzaW9uPgo8b3JkZXJfaWQ%2BMTc8%0AL29yZGVyX2lkPgo8bWVyY2hhbnRfaWQ%2BaTU1MjA0Njg0OTg8L21lcmNoYW50X2lkPgo8cGF5X2Rl%0AdGFpbHM%2BPC9wYXlfZGV0YWlscz4KPGRlc2NyaXB0aW9uPsOQwp7DkMK%2Fw5DCu8OQwrDDkcKCw5DC%0AsCDDkcKHw5DCtcORwoDDkMK1w5DCtyDDkMK4w5DCvcORwoLDkMK1w5HCgMOQwr3DkMK1w5HCgi48%0AL2Rlc2NyaXB0aW9uPgo8Y3VycmVuY3k%2BUlVSPC9jdXJyZW5jeT4KPGFtb3VudD4xLjAwPC9hbW91%0AbnQ%2BCjxwYXlfd2F5PmNhcmQ8L3BheV93YXk%2BCjx0cmFuc2FjdGlvbl9pZD4yMTMxNjE0NTwvdHJh%0AbnNhY3Rpb25faWQ%2BCjxhY3Rpb24%2Bc2VydmVyX3VybDwvYWN0aW9uPgo8Y29kZT48L2NvZGU%2BCjwv%0AcmVzcG9uc2U%2B%0A' - end - - def failed_response - 'operation_xml=PHJlc3BvbnNlPgogIDxhY3Rpb24%2BcmVzdWx0X3VybDwvYWN0aW9uPgogIDxhbW91bnQ%2BMjQxMC4w%0D%0AMDwvYW1vdW50PgogIDxjdXJyZW5jeT5SVVI8L2N1cnJlbmN5PgogIDxkZXNjcmlwdGlvbj7DkMKe%0D%0Aw5DCv8OQwrvDkMKww5HCgsOQwrAgw5HCh8OQwrXDkcKAw5DCtcOQwrcgw5DCuMOQwr3DkcKCw5DC%0D%0AtcORwoDDkMK9w5DCtcORwoIuPC9kZXNjcmlwdGlvbj4KICA8bWVyY2hhbnRfaWQ%2BaTU1MjA0Njg0%0D%0AOTg8L21lcmNoYW50X2lkPgogIDxvcmRlcl9pZD4xOTwvb3JkZXJfaWQ%2BCiAgPHBheV93YXk%2BY2Fy%0D%0AZDwvcGF5X3dheT4KICA8c2VuZGVyX3Bob25lPis3OTA5NDM0MzMzNTwvc2VuZGVyX3Bob25lPgog%0D%0AIDxzdGF0dXM%2BZmFpbHVyZTwvc3RhdHVzPgogIDx0cmFuc2FjdGlvbl9pZD4yMTMxOTU4MTwvdHJh%0D%0AbnNhY3Rpb25faWQ%2BCiAgPHZlcnNpb24%2BMS4yPC92ZXJzaW9uPgo8L3Jlc3BvbnNlPgo%3D%0D%0A&signature=b6a8U5Dg%2Fhl%2BNtYOIyjIwJH0Fi8%3D' - end -end diff --git a/test/unit/integrations/notifications/maksuturva_notification_test.rb b/test/unit/integrations/notifications/maksuturva_notification_test.rb deleted file mode 100644 index ad13b415b99..00000000000 --- a/test/unit/integrations/notifications/maksuturva_notification_test.rb +++ /dev/null @@ -1,36 +0,0 @@ -require 'test_helper' - -class MaksuturvaNotificationTest < Test::Unit::TestCase - include ActiveMerchant::Billing::Integrations - - def setup - @Maksuturva = Maksuturva::Notification.new(http_raw_data) - end - - def test_accessors - assert @Maksuturva.complete? - assert_equal "PAID", @Maksuturva.status - assert_equal "2", @Maksuturva.transaction_id - assert_equal "200,00", @Maksuturva.gross - assert_equal "EUR", @Maksuturva.currency - end - - def test_acknowledgement - assert @Maksuturva.acknowledge("11223344556677889900") - end - - def test_faulty_acknowledgement - @Maksuturva = Maksuturva::Notification.new({"pmt_action"=>"NEW_PAYMENT_EXTENDED", "pmt_version"=>"0004", "pmt_id"=>"2", "pmt_reference"=>"134663", "pmt_amount"=>"200,00", "pmt_currency"=>"EUR", "pmt_sellercosts"=>"0,00", "pmt_paymentmethod"=>"FI01", "pmt_escrow"=>"N", "pmt_hash"=>"BDF4F41FA194612017CBE13CF7670971"}) - assert_equal false, @Maksuturva.acknowledge("11223344556677889900") - end - - def test_respond_to_acknowledge - assert @Maksuturva.respond_to?(:acknowledge) - end - - private - - def http_raw_data - {"pmt_action"=>"NEW_PAYMENT_EXTENDED", "pmt_version"=>"0004", "pmt_id"=>"2", "pmt_reference"=>"134662", "pmt_amount"=>"200,00", "pmt_currency"=>"EUR", "pmt_sellercosts"=>"0,00", "pmt_paymentmethod"=>"FI01", "pmt_escrow"=>"N", "pmt_hash"=>"BDF4F41FA194612017CBE13CF7670971"} - end -end diff --git a/test/unit/integrations/notifications/moneybookers_notification_test.rb b/test/unit/integrations/notifications/moneybookers_notification_test.rb deleted file mode 100644 index db52f973556..00000000000 --- a/test/unit/integrations/notifications/moneybookers_notification_test.rb +++ /dev/null @@ -1,75 +0,0 @@ -require 'test_helper' - -class MoneybookersNotificationTest < Test::Unit::TestCase - include ActiveMerchant::Billing::Integrations - - def setup - @moneybookers = Moneybookers::Notification.new(http_raw_data, :credential2 => 'secret') - end - - def test_accessors - assert @moneybookers.complete? - assert_equal 'Completed', @moneybookers.status - assert_equal "200234", @moneybookers.transaction_id - assert_equal "1005", @moneybookers.item_id - assert_equal '39.60', @moneybookers.gross - assert_equal 'EUR', @moneybookers.currency - assert_equal "25.46", @moneybookers.merchant_amount - assert_equal "GBP", @moneybookers.merchant_currency - assert_equal nil, @moneybookers.received_at - end - - def test_compositions - assert_equal Money.new(3960, 'EUR'), @moneybookers.amount - end - - def test_respond_to_acknowledge - assert @moneybookers.respond_to?(:acknowledge) - end - - def test_credential2_required - assert_raises ArgumentError do - Moneybookers::Notification.new(http_raw_data, {}) - end - assert_nothing_raised do - Moneybookers::Notification.new(http_raw_data, :credential2 => 'secret') - end - end - - def test_status_failed - notification = Moneybookers::Notification.new(http_raw_data.sub(/status=2/, 'status=-2'), :credential2 => 'secret') - assert_equal 'Failed', notification.status - end - - def test_status_pending - notification = Moneybookers::Notification.new(http_raw_data.sub(/status=2/, 'status=0'), :credential2 => 'secret') - assert_equal 'Pending', notification.status - end - - def test_status_cancelled - notification = Moneybookers::Notification.new(http_raw_data.sub(/status=2/, 'status=-1'), :credential2 => 'secret') - assert_equal 'Cancelled', notification.status - end - - def test_status_reversed - notification = Moneybookers::Notification.new(http_raw_data.sub(/status=2/, 'status=-3'), :credential2 => 'secret') - assert_equal 'Reversed', notification.status - end - - def test_status_error - notification = Moneybookers::Notification.new(http_raw_data.sub(/status=2/, 'status='), :credential2 => 'secret') - assert_equal 'Error', notification.status - end - - def test_acknowledge - data = {"md5sig"=>"CDEA3910DDD4090DF89034CA82C46D34", "transaction_id"=>"54910248", "amount"=>"1.03", "id"=>"968", "pay_to_email"=>"dennis@shopify.com", "mb_currency"=>"EUR", "currency"=>"USD", "mb_transaction_id"=>"403117232", "mb_amount"=>"0.735741", "merchant_id"=>"21235995", "status"=>"2", "pay_from_email"=>"dennis@shopify.com", "payment_type"=>"MBD"} - data = data.collect{|key, value| "#{key}=#{value}"}.join('&') - notification = Moneybookers::Notification.new(data, :credential2 => 't3stt3st') - assert notification.acknowledge - end - - private - def http_raw_data - "pay_to_email=merchant@merchant.com&pay_from_email=payer@moneybookers.com&merchant_id=100005&mb_transaction_id=200234&transaction_id=1005&mb_amount=25.46&mb_currency=GBP&status=2&md5sig=327638C253A4637199CEBA6642371F20&amount=39.60&currency=EUR" - end -end diff --git a/test/unit/integrations/notifications/nochex_notification_test.rb b/test/unit/integrations/notifications/nochex_notification_test.rb deleted file mode 100644 index f00552001c8..00000000000 --- a/test/unit/integrations/notifications/nochex_notification_test.rb +++ /dev/null @@ -1,51 +0,0 @@ -require 'test_helper' - -class NochexNotificationTest < Test::Unit::TestCase - include ActiveMerchant::Billing::Integrations - - def setup - @nochex = Nochex::Notification.new(http_raw_data) - end - - def test_accessors - assert @nochex.complete? - assert_equal "Completed", @nochex.status - assert_equal "91191", @nochex.transaction_id - assert_equal "11", @nochex.item_id - assert_equal "31.66", @nochex.gross - assert_equal "GBP", @nochex.currency - assert_equal Time.utc(2006, 9, 27, 22, 30, 53), @nochex.received_at - assert @nochex.test? - end - - def test_compositions - assert_equal Money.new(3166, 'GBP'), @nochex.amount - end - - def test_successful_acknowledgement - Nochex::Notification.any_instance.expects(:ssl_post).returns('AUTHORISED') - - assert @nochex.acknowledge - end - - def test_failed_acknowledgement - Nochex::Notification.any_instance.expects(:ssl_post).returns('DECLINED') - - assert !@nochex.acknowledge - end - - def test_respond_to_acknowledge - assert @nochex.respond_to?(:acknowledge) - end - - def test_nil_notification - Nochex::Notification.any_instance.expects(:ssl_post).returns('DECLINED') - notification = Nochex::Notification.new(nil) - assert !notification.acknowledge - end - - private - def http_raw_data - "transaction_date=27/09/2006 22:30:53&transaction_id=91191&order_id=11&from_email=test2@nochex.com&to_email=test1@nochex.com&amount=31.6600&security_key=L254524366479818252491366&status=test&custom=" - end -end diff --git a/test/unit/integrations/notifications/notification_test.rb b/test/unit/integrations/notifications/notification_test.rb deleted file mode 100644 index 0e9d4a759ed..00000000000 --- a/test/unit/integrations/notifications/notification_test.rb +++ /dev/null @@ -1,54 +0,0 @@ -require 'test_helper' - -class NotificationTest < Test::Unit::TestCase - include ActiveMerchant::Billing::Integrations - - def setup - @notification = Notification.new(http_raw_data) - end - - def test_raw - assert_equal http_raw_data, @notification.raw - end - - def test_parse - assert_equal "500.00", @notification.params['mc_gross'] - assert_equal "confirmed", @notification.params['address_status'] - assert_equal "EVMXCLDZJV77Q", @notification.params['payer_id'] - assert_equal "Completed", @notification.params['payment_status'] - assert_equal CGI.unescape("15%3A23%3A54+Apr+15%2C+2005+PDT"), @notification.params['payment_date'] - end - - def test_accessors - assert_raise(NotImplementedError){ @notification.status } - assert_raise(NotImplementedError){ @notification.gross } - assert_raise(NotImplementedError){ @notification.gross_cents } - end - - def test_notification_data_with_period - notification = Notification.new(http_raw_data_with_period) - assert_equal 'clicked', notification.params['checkout.x'] - end - - def test_valid_sender_always_true_in_testmode - assert_equal ActiveMerchant::Billing::Base.integration_mode, :test - assert @notification.valid_sender?(nil) - assert @notification.valid_sender?('localhost') - end - - def test_valid_sender_always_true_when_no_ips - ActiveMerchant::Billing::Base.integration_mode = :production - assert @notification.valid_sender?(nil) - assert @notification.valid_sender?('localhost') - ActiveMerchant::Billing::Base.integration_mode = :test - end - - private - def http_raw_data - "mc_gross=500.00&address_status=confirmed&payer_id=EVMXCLDZJV77Q&tax=0.00&address_street=164+Waverley+Street&payment_date=15%3A23%3A54+Apr+15%2C+2005+PDT&payment_status=Completed&address_zip=K2P0V6&first_name=Tobias&mc_fee=15.05&address_country_code=CA&address_name=Tobias+Luetke&notify_version=1.7&custom=&payer_status=unverified&business=tobi%40leetsoft.com&address_country=Canada&address_city=Ottawa&quantity=1&payer_email=tobi%40snowdevil.ca&verify_sign=AEt48rmhLYtkZ9VzOGAtwL7rTGxUAoLNsuf7UewmX7UGvcyC3wfUmzJP&txn_id=6G996328CK404320L&payment_type=instant&last_name=Luetke&address_state=Ontario&receiver_email=tobi%40leetsoft.com&payment_fee=&receiver_id=UQ8PDYXJZQD9Y&txn_type=web_accept&item_name=Store+Purchase&mc_currency=CAD&item_number=&test_ipn=1&payment_gross=&shipping=0.00" - end - - def http_raw_data_with_period - "mc_gross=500.00&address_status=confirmed&payer_id=EVMXCLDZJV77Q&tax=0.00&address_street=164+Waverley+Street&payment_date=15%3A23%3A54+Apr+15%2C+2005+PDT&payment_status=Completed&address_zip=K2P0V6&first_name=Tobias&mc_fee=15.05&address_country_code=CA&address_name=Tobias+Luetke&notify_version=1.7&custom=&payer_status=unverified&business=tobi%40leetsoft.com&address_country=Canada&address_city=Ottawa&quantity=1&payer_email=tobi%40snowdevil.ca&verify_sign=AEt48rmhLYtkZ9VzOGAtwL7rTGxUAoLNsuf7UewmX7UGvcyC3wfUmzJP&txn_id=6G996328CK404320L&payment_type=instant&last_name=Luetke&address_state=Ontario&receiver_email=tobi%40leetsoft.com&payment_fee=&receiver_id=UQ8PDYXJZQD9Y&txn_type=web_accept&item_name=Store+Purchase&mc_currency=CAD&item_number=&test_ipn=1&payment_gross=&shipping=0.00&checkout.x=clicked" - end -end diff --git a/test/unit/integrations/notifications/paxum_notification_test.rb b/test/unit/integrations/notifications/paxum_notification_test.rb deleted file mode 100644 index 217bc0a722b..00000000000 --- a/test/unit/integrations/notifications/paxum_notification_test.rb +++ /dev/null @@ -1,32 +0,0 @@ -require 'test_helper' - -class PaxumNotificationTest < Test::Unit::TestCase - include ActiveMerchant::Billing::Integrations - - def setup - @paxum = Paxum::Notification.new(http_raw_data, :secret => 'secret') - end - - def test_acknowledgement - assert @paxum.acknowledge - end - - def test_respond_to_acknowledge - assert @paxum.respond_to?(:acknowledge) - end - - def test_wrong_signature - @paxum = Robokassa::Notification.new(http_raw_data_with_wrong_signature, :secret => 'secret') - assert !@paxum.acknowledge - end - - private - - def http_raw_data - "buyer_username=user@paxum.com&test=0&buyer_name=Alexander Smirnov&buyer_contact_phone=7123123123&buyer_email=user@paxum.com&buyer_id=23315&buyer_status=verified&resend=false&transaction_id=6599376&transaction_description=Received money from user@paxum.com&transaction_item_id=123&transaction_item_name=Request to paxum #123&transaction_amount=1&transaction_status=done&transaction_exchange_rate=1.00&transaction_currency=USD&transaction_date=2012-07-19&transaction_type=14&transaction_quantity=1&key=7933f7bf5a6a5deeebbc3dcca7b70fe4" - end - - def http_raw_data_with_wrong_signature - "buyer_username=user@paxum.com&test=0&buyer_name=Alexander Smirnov&buyer_contact_phone=7123123123&buyer_email=user@paxum.com&buyer_id=23315&buyer_status=verified&resend=false&transaction_id=6599376&transaction_description=Received money from user@paxum.com&transaction_item_id=123&transaction_item_name=Request to paxum #123&transaction_amount=1&transaction_status=done&transaction_exchange_rate=1.00&transaction_currency=USD&transaction_date=2012-07-19&transaction_type=14&key=7933f7bf5a6a5deeebbc3dcca7b70fe4" - end -end diff --git a/test/unit/integrations/notifications/pay_fast_notification_test.rb b/test/unit/integrations/notifications/pay_fast_notification_test.rb deleted file mode 100644 index b179c3f145c..00000000000 --- a/test/unit/integrations/notifications/pay_fast_notification_test.rb +++ /dev/null @@ -1,65 +0,0 @@ -require 'test_helper' - -class PayFastNotificationTest < Test::Unit::TestCase - include ActiveMerchant::Billing::Integrations - - def setup - @pay_fast = PayFast::Notification.new(http_raw_data) - end - - def test_accessors - assert @pay_fast.complete? - assert_equal "46591", @pay_fast.transaction_id - assert_equal "Completed", @pay_fast.status - assert_equal "Name", @pay_fast.item_name - assert_equal "123.00", @pay_fast.gross - assert_equal "-2.80", @pay_fast.fee - assert_equal "120.20", @pay_fast.amount - assert_equal "10000100", @pay_fast.merchant_id - end - - def test_acknowledgement - PayFast::Notification.any_instance.stubs(:ssl_post).returns('VALID') - assert @pay_fast.acknowledge - - PayFast::Notification.any_instance.stubs(:ssl_post).returns('INVALID') - assert !@pay_fast.acknowledge - end - - def test_send_acknowledgement - PayFast::Notification.any_instance.expects(:ssl_post).with( - PayFast.validate_service_url, - @pay_fast.notify_signature_string, - { 'Content-Type' => 'application/x-www-form-urlencoded', - 'Content-Length' => "#{@pay_fast.notify_signature_string.size}" } - ).returns('VALID') - - assert @pay_fast.acknowledge - end - - def test_payment_successful_status - notification = PayFast::Notification.new('payment_status=COMPLETE') - assert_equal 'Completed', notification.status - end - - #does payfast ever return pending? - def test_payment_pending_status - notification = PayFast::Notification.new('payment_status=PENDING') - assert_equal 'Failed', notification.status - end - - def test_payment_failure_status - notification = PayFast::Notification.new('payment_status=FAILED') - assert_equal 'Failed', notification.status - end - - def test_respond_to_acknowledge - assert @pay_fast.respond_to?(:acknowledge) - end - - private - - def http_raw_data - "m_payment_id=&pf_payment_id=46591&payment_status=COMPLETE&item_name=Name&item_description=&amount_gross=123.00&amount_fee=-2.80&amount_net=120.20&custom_str1=&custom_str2=&custom_str3=&custom_str4=&custom_str5=&custom_int1=&custom_int2=&custom_int3=&custom_int4=&custom_int5=&name_first=Test&name_last=User+01&email_address=sbtu01%40payfast.co.za&merchant_id=10000100&signature=bae21e96d4dc7bf36bd1a6bb1a103f5f" - end -end diff --git a/test/unit/integrations/notifications/payflow_link_notification_test.rb b/test/unit/integrations/notifications/payflow_link_notification_test.rb deleted file mode 100644 index 463bd577025..00000000000 --- a/test/unit/integrations/notifications/payflow_link_notification_test.rb +++ /dev/null @@ -1,61 +0,0 @@ -require 'test_helper' - -class PayflowLinkNotificationTest < Test::Unit::TestCase - include ActiveMerchant::Billing::Integrations - - def setup - @payflow = ActiveMerchant::Billing::Integrations::PayflowLink.notification(http_raw_data) - end - - def test_accessors - assert @payflow.complete? - assert_equal "Completed", @payflow.status - assert_equal "V24A0C03E977", @payflow.transaction_id - assert_equal "S", @payflow.type - assert_equal "21.30", @payflow.gross - assert_equal "20" , @payflow.item_id - assert_equal "1111" , @payflow.account - assert_equal "2011-08-03T10:05:41+00:00", @payflow.received_at.to_s - assert @payflow.test? - end - - def test_payment_successful_status - notification = PayflowLink::Notification.new('RESULT=0') - assert_equal 'Completed', notification.status - end - - def test_missing_transittime - notification = PayflowLink::Notification.new('') - assert_nil notification.received_at - end - - def test_invalid_transittime - notification = PayflowLink::Notification.new('TRANSTIME=magic') - assert_nil notification.received_at - end - - def test_payment_failure_status - notification = PayflowLink::Notification.new('RESULT=7') - assert_equal 'Failed', notification.status - end - - def test_respond_to_acknowledge - assert @payflow.respond_to?(:acknowledge) - end - - def test_item_id_mapping - notification = PayflowLink::Notification.new('USER1=1') - assert_equal '1', notification.item_id - end - - def test_invoice_mapping - notification = PayflowLink::Notification.new('INVNUM=1') - assert_equal '1', notification.invoice - end - - private - - def http_raw_data - "AVSZIP=Y&STATE=ON&TYPE=S&USER4=&ZIPTOSHIP=&ACCT=1111&EMAIL=&EMAILTOSHIP=&ADDRESSTOSHIP=&METHOD=CC&TRANSTIME=2011-08-03+10%3A05%3A41&USER8=&USER5=&IAVS=N&STATETOSHIP=&USER3=&PHONETOSHIP=&USER7=&TAX=&CARDTYPE=1&AVSDATA=YYY&CITYTOSHIP=&USER6=&PROCAVS=Y&INVNUM=&CITY=Ottawa&USER1=20&DESCRIPTION=Shop+One+store+purchase.+Order+1008&HOSTCODE=A&RESULT=0&USER10=&USER2=true&FAX=&PONUM=&LASTNAME=Doe&PNREF=V24A0C03E977&PHONE=6132623672&AMT=21.30&NAMETOSHIP=&ZIP=K1p4l2&AUTHCODE=026PNI&EXPDATE=0522&RESPMSG=Approved&COUNTRY=&CUSTID=&ORIGMETHOD=&FIRSTNAME=John&USER9=&FAXTOSHIP=&AVSADDR=Y&NAME=John+Doe+&COUNTRYTOSHIP=&ADDRESS=43+Somewhere+Street+" - end -end diff --git a/test/unit/integrations/notifications/paypal_notification_test.rb b/test/unit/integrations/notifications/paypal_notification_test.rb deleted file mode 100644 index c3861aef451..00000000000 --- a/test/unit/integrations/notifications/paypal_notification_test.rb +++ /dev/null @@ -1,131 +0,0 @@ -require 'test_helper' - -class PaypalNotificationTest < Test::Unit::TestCase - include ActiveMerchant::Billing::Integrations - - def setup - @paypal = Paypal::Notification.new(http_raw_data) - @mass_pay_paypal = Paypal::Notification.new(mass_pay_http_raw_data) - end - - def test_accessors - assert @paypal.complete? - assert !@paypal.masspay? - assert_equal "Completed", @paypal.status - assert_equal "6G996328CK404320L", @paypal.transaction_id - assert_equal "web_accept", @paypal.type - assert_equal "500.00", @paypal.gross - assert_equal "15.05", @paypal.fee - assert_equal "CAD", @paypal.currency - assert_equal 'tobi@leetsoft.com' , @paypal.account - assert @paypal.test? - end - - def test_mass_pay_accessors - assert @mass_pay_paypal.complete? - assert @mass_pay_paypal.masspay? - assert_equal "Completed", @mass_pay_paypal.status - assert_equal "masspay", @mass_pay_paypal.type - assert_equal nil, @mass_pay_paypal.transaction_id - assert_equal nil, @mass_pay_paypal.gross - assert_equal nil, @mass_pay_paypal.fee - assert_equal nil, @mass_pay_paypal.currency - assert_equal nil , @mass_pay_paypal.account - assert_equal 3 , @mass_pay_paypal.items.size - assert_equal "7XW35917TG8293137", @mass_pay_paypal.items[0].transaction_id - assert_equal "79512417EL9296629", @mass_pay_paypal.items[1].transaction_id - assert_equal "75X24749Y32677910", @mass_pay_paypal.items[2].transaction_id - assert_equal "10.00", @mass_pay_paypal.items[0].gross - assert_equal "24.50", @mass_pay_paypal.items[1].gross - assert_equal "20.00", @mass_pay_paypal.items[2].gross - assert_equal "0.20", @mass_pay_paypal.items[0].fee - assert_equal "0.49", @mass_pay_paypal.items[1].fee - assert_equal "0.40", @mass_pay_paypal.items[2].fee - assert_equal "GBP", @mass_pay_paypal.items[0].currency - assert_equal "GBP", @mass_pay_paypal.items[1].currency - assert_equal "GBP", @mass_pay_paypal.items[2].currency - assert_equal "123", @mass_pay_paypal.items[0].item_id - assert_equal "456", @mass_pay_paypal.items[1].item_id - assert_equal "789", @mass_pay_paypal.items[2].item_id - assert_equal "buyer_1348066306_per@example.com", @mass_pay_paypal.items[0].account - assert_equal "buyer_1351170859_per@example.com", @mass_pay_paypal.items[1].account - assert_equal "buyer_1351170993_per@example.com", @mass_pay_paypal.items[2].account - assert_equal "Completed", @mass_pay_paypal.items[0].status - assert_equal "Completed", @mass_pay_paypal.items[1].status - assert_equal "Completed", @mass_pay_paypal.items[2].status - assert @mass_pay_paypal.test? - end - - def test_compositions - assert_equal Money.new(50000, 'CAD'), @paypal.amount - end - - def test_acknowledgement - Paypal::Notification.any_instance.stubs(:ssl_post).returns('VERIFIED') - assert @paypal.acknowledge - - Paypal::Notification.any_instance.stubs(:ssl_post).returns('INVALID') - assert !@paypal.acknowledge - end - - def test_send_acknowledgement - Paypal::Notification.any_instance.expects(:ssl_post).with( - "#{Paypal.service_url}?cmd=_notify-validate", - http_raw_data, - { 'Content-Length' => "#{http_raw_data.size}", 'User-Agent' => "Active Merchant -- http://activemerchant.org" } - ).returns('VERIFIED') - - assert @paypal.acknowledge - end - - def test_payment_successful_status - notification = Paypal::Notification.new('payment_status=Completed') - assert_equal 'Completed', notification.status - end - - def test_payment_pending_status - notification = Paypal::Notification.new('payment_status=Pending') - assert_equal 'Pending', notification.status - end - - def test_payment_failure_status - notification = Paypal::Notification.new('payment_status=Failed') - assert_equal 'Failed', notification.status - end - - def test_respond_to_acknowledge - assert @paypal.respond_to?(:acknowledge) - end - - def test_item_id_mapping - notification = Paypal::Notification.new('item_number=1') - assert_equal '1', notification.item_id - end - - def test_custom_mapped_to_item_id - notification = Paypal::Notification.new('custom=1') - assert_equal '1', notification.item_id - end - - def test_nil_notification - Paypal::Notification.any_instance.stubs(:ssl_post).returns('INVALID') - assert !@paypal.acknowledge - end - - def test_received_at_time_parsing - assert_match %r{15/04/2005 08:23:54 (UTC|GMT)}, @paypal.received_at.strftime("%d/%m/%Y %H:%M:%S %Z") - - paypal = Paypal::Notification.new("payment_date=13%3A38%3A14+Jan+22%2C+2013+PST") - assert_match %r{22/01/2013 05:38:14 (UTC|GMT)}, paypal.received_at.strftime("%d/%m/%Y %H:%M:%S %Z") - end - - private - - def http_raw_data - "mc_gross=500.00&address_status=confirmed&payer_id=EVMXCLDZJV77Q&tax=0.00&address_street=164+Waverley+Street&payment_date=15%3A23%3A54+Apr+15%2C+2005+PDT&payment_status=Completed&address_zip=K2P0V6&first_name=Tobias&mc_fee=15.05&address_country_code=CA&address_name=Tobias+Luetke&notify_version=1.7&custom=&payer_status=unverified&business=tobi%40leetsoft.com&address_country=Canada&address_city=Ottawa&quantity=1&payer_email=tobi%40snowdevil.ca&verify_sign=AEt48rmhLYtkZ9VzOGAtwL7rTGxUAoLNsuf7UewmX7UGvcyC3wfUmzJP&txn_id=6G996328CK404320L&payment_type=instant&last_name=Luetke&address_state=Ontario&receiver_email=tobi%40leetsoft.com&payment_fee=&receiver_id=UQ8PDYXJZQD9Y&txn_type=web_accept&item_name=Store+Purchase&mc_currency=CAD&item_number=&test_ipn=1&payment_gross=&shipping=0.00" - end - - def mass_pay_http_raw_data - "payer_id=LPV4F4HZHCE&payment_date=06%3A25%3A37+Oct+25%2C+2012+PDT&payment_gross_1=&payment_gross_2=&payment_gross_3=&payment_status=Completed&receiver_email_1=buyer_1348066306_per%40example.com&receiver_email_2=buyer_1351170859_per%40example.com&charset=windows-1252&receiver_email_3=buyer_1351170993_per%40example.com&mc_currency_1=GBP&masspay_txn_id_1=7XW35917TG8293137&mc_currency_2=GBP&masspay_txn_id_2=79512417EL9296629&mc_currency_3=GBP&masspay_txn_id_3=75X24749Y32677910&first_name=Test&unique_id_1=123&notify_version=3.7&unique_id_2=456&unique_id_3=789&payer_status=verified&verify_sign=AwtKW.5QSiJCrI10IE.2gmVei1MEAwsHLftLIB9pXgu82MLXoCS1yeE-&payer_email=massuk_1351170591_biz%40example.com&payer_business_name=Tests%27s+Test+Store&last_name=Test&status_1=Completed&status_2=Completed&status_3=Completed&txn_type=masspay&mc_gross_1=10.00&mc_gross_2=24.50&mc_gross_3=20.00&payment_fee_1=&residence_country=GB&test_ipn=1&payment_fee_2=&payment_fee_3=&mc_fee_1=0.20&mc_fee_2=0.49&mc_fee_3=0.40&ipn_track_id=89f7ff244947f" - end -end diff --git a/test/unit/integrations/notifications/paysbuy_notification_test.rb b/test/unit/integrations/notifications/paysbuy_notification_test.rb deleted file mode 100644 index 562fd0c501b..00000000000 --- a/test/unit/integrations/notifications/paysbuy_notification_test.rb +++ /dev/null @@ -1,21 +0,0 @@ -require 'test_helper' - -class PaysbuyNotificationTest < Test::Unit::TestCase - include ActiveMerchant::Billing::Integrations - - def setup - @paysbuy = Paysbuy::Notification.new(http_raw_data) - end - - def test_accessors - assert @paysbuy.complete? - assert_equal "Completed", @paysbuy.status - assert_equal "100013", @paysbuy.item_id - end - - private - - def http_raw_data - "result=00100013" - end -end diff --git a/test/unit/integrations/notifications/payu_in_notification_test.rb b/test/unit/integrations/notifications/payu_in_notification_test.rb deleted file mode 100644 index 7ca50e350e1..00000000000 --- a/test/unit/integrations/notifications/payu_in_notification_test.rb +++ /dev/null @@ -1,49 +0,0 @@ -require 'test_helper' - -class PayuInNotificationTest < Test::Unit::TestCase - include ActiveMerchant::Billing::Integrations - - def setup - @payu = PayuIn::Notification.new(http_raw_data, :credential1 => 'C0Dr8m', :credential2 => '3sf0jURk') - end - - def test_accessors - assert @payu.complete? - assert_equal "Completed", @payu.status - assert_equal "403993715508030204", @payu.transaction_id - assert_equal "success", @payu.transaction_status - assert_equal "10.00", @payu.gross - assert_equal "Product Info", @payu.product_info - assert_equal "INR", @payu.currency - assert_equal true, @payu.invoice_ok?('4ba4afe87f7e73468f2a') - assert_equal true, @payu.amount_ok?(BigDecimal.new('10.00'),BigDecimal.new('0.00')) - assert_equal "CC", @payu.type - assert_equal "4ba4afe87f7e73468f2a", @payu.invoice - assert_equal "C0Dr8m", @payu.account - assert_equal "0.00", @payu.discount - assert_equal "test@example.com", @payu.customer_email - assert_equal "1234567890", @payu.customer_phone - assert_equal "Payu-Admin", @payu.customer_first_name - assert_equal "", @payu.customer_last_name - assert_equal "ef0c1b509a42b802a4938c25dc9bb9efe40b75a7dfb8bde1a6f126fa1f86cee264c5e5a17e87db85150d6d8912eafda838416e669712f1989dcb9cbdb8c24219", @payu.checksum - assert_equal "E000", @payu.message - assert_equal true, @payu.checksum_ok? - end - - def test_compositions - assert_equal '10.00', @payu.gross - end - - def test_acknowledgement - assert @payu.acknowledge - end - - def test_respond_to_acknowledge - assert @payu.respond_to?(:acknowledge) - end - - private - def http_raw_data - "mihpayid=403993715508030204&mode=CC&status=success&unmappedstatus=captured&key=C0Dr8m&txnid=4ba4afe87f7e73468f2a&amount=10.00&discount=0.00&addedon=2013-05-10 18 32 30&productinfo=Product Info&firstname=Payu-Admin&lastname=&address1=&address2=&city=&state=&country=&zipcode=&email=test@example.com&phone=1234567890&udf1=&udf2=&udf3=&udf4=&udf5=&udf6=&udf7=&udf8=&udf9=&udf10=&hash=ef0c1b509a42b802a4938c25dc9bb9efe40b75a7dfb8bde1a6f126fa1f86cee264c5e5a17e87db85150d6d8912eafda838416e669712f1989dcb9cbdb8c24219&field1=313069903923&field2=999999&field3=59117331831301&field4=-1&field5=&field6=&field7=&field8=&PG_TYPE=HDFC&bank_ref_num=59117331831301&bankcode=CC&error=E000&cardnum=512345XXXXXX2346&cardhash=766f0227cc4b4c5f773a04cb31d8d1c5be071dd8d08fe365ecf5e2e5c947546d" - end -end diff --git a/test/unit/integrations/notifications/platron_notification_test.rb b/test/unit/integrations/notifications/platron_notification_test.rb deleted file mode 100644 index aa64fa70099..00000000000 --- a/test/unit/integrations/notifications/platron_notification_test.rb +++ /dev/null @@ -1,68 +0,0 @@ -require 'test_helper' - -class PlatronNotificationTest < Test::Unit::TestCase - include ActiveMerchant::Billing::Integrations - - def setup - @correct_notification = Platron::Notification.new(http_raw_data_with_correct_signature, :secret => 'secret', :path => 'result') - @notification_with_wrong_signature= Platron::Notification.new(http_raw_data_with_wrong_signature, :secret => 'secret',:path => 'result') - end - - def test_accessors - assert_equal '111', @correct_notification.order_id - assert_equal '1023', @correct_notification.platron_payment_id - assert_equal 'USD', @correct_notification.currency - assert_equal '2013-01-02 10:14:20', @correct_notification.payment_date - assert_equal '1', @correct_notification.complete? - assert_equal 'Visa', @correct_notification.payment_system - assert_equal 'VI', @correct_notification.card_brand - assert_equal '990',@correct_notification.amount - end - - def test_acknowledgement - assert_equal @correct_notification.acknowledge, true - assert_equal @notification_with_wrong_signature.acknowledge, false - end - - def test_respond_to_acknowledge - assert @correct_notification.respond_to?(:acknowledge) - end - - def test_success_response - xml_response = @correct_notification.success_response('result', 'secret') - - assert_nothing_raised do - hash = Hash.from_xml(xml_response) - assert_equal hash['response']['pg_status'],'ok' - sign = Digest::MD5.hexdigest( - [ - 'result', - {:pg_status => 'ok',:pg_salt => hash['response']['pg_salt']}.with_indifferent_access.sort.map{|ar|ar[1]}, - 'secret' - ].join(';') - ) - assert_equal hash['response']['pg_sig'], sign - end - end - - private - - def test_response_params - { - :pg_result=>'1',:pg_order_id=>'111',:pg_payment_id=>'1023',:pg_amount=>'990',:pg_ps_currency=>'USD', - :pg_payment_system=>'Visa',:pg_payment_date=>'2013-01-02 10:14:20',:pg_card_brand=>'VI',:pg_overpayment=>'0', - :pg_salt=>'vfw87rb2vwevhj' - } - end - - def http_raw_data_with_correct_signature - pg_sig= Digest::MD5.hexdigest(['result',test_response_params.with_indifferent_access.sort.map{|ar|ar[1]},'secret'].join(';')) - test_response_params.merge({:pg_sig=>pg_sig}).to_param - end - - def http_raw_data_with_wrong_signature - pg_sig= 'wrong signature' - test_response_params.merge({:pg_sig=>pg_sig}).to_param - end - -end diff --git a/test/unit/integrations/notifications/pxpay_notification_test.rb b/test/unit/integrations/notifications/pxpay_notification_test.rb deleted file mode 100644 index 821e99f5241..00000000000 --- a/test/unit/integrations/notifications/pxpay_notification_test.rb +++ /dev/null @@ -1,57 +0,0 @@ -require 'test_helper' - -class PxPayNotificationTest < Test::Unit::TestCase - include ActiveMerchant::Billing::Integrations - - def setup - end - - def test_successful_transaction_notification - Pxpay::Notification.any_instance.expects(:ssl_post).returns(successful_xml_response) - notification = Pxpay::Notification.new(http_post_data, :credential1 => 'user', :credential2 => 'key') - - assert notification.acknowledge - assert_match "Completed", notification.status - assert_match "157.00", notification.gross - assert_match "00000008096f3c7a", notification.transaction_id - assert_match "1", notification.item_id - end - - def test_failed_transaction_notification - Pxpay::Notification.any_instance.expects(:ssl_post).returns(failed_xml_response) - notification = Pxpay::Notification.new(http_post_data, :credential1 => 'user', :credential2 => 'key') - - assert notification.acknowledge - assert_match "Failed", notification.status - assert_match "100", notification.item_id - end - - def test_exception_without_credentials - assert_raise ArgumentError do - notification = Pxpay::Notification.new(http_post_data) - end - end - - def test_exception_without_http_params - assert_raise ArgumentError do - notification = Pxpay::Notification.new("", :credential1 => 'user', :credential2 => 'pass') - end - end - - private - - def http_post_data - "result=v5OKdyEkGAkbwMhFgpYUH7ImWB0CAu1r_skOZkoZrcrgWbrb9RR-Vp1DVXygfuuHutktZ-I_KMWD2lyL9bX8-8CNEE_g2BRRRM1Ay4JQUhnsKd5WP8Y6QTxLo6njihiaMduzaWlEjxBzrzqSUF4GSMPIzdOtPFwaXlOutqsEBBOLFvvjE_YM88RJittIiS_QBpQDIMXLvrT0-qEMMtddnNUfq7u6nb9qoWCTbAygIY0YjzPL01f0M8tKc_x3hjVF19k2x7KD5yoSmy4PfN-RhWsqfMO69q83pyhu3whChC5mBbJHXC6Mhpjcyw__MD7V8meLkB5ulgRRtOTCznoRsruVOKeHP7m6Cd1KWrgas-ErIIE8mxLtdf5ZAz0J9asaGfm_GfJ8QHiCHqBNFivBp5z5qJgO4EGZvU7uWTRQM6kVFiBqm7ZBG1w9FxSqIkIYOrGepneA7aEALeF1kdwq0I2A==&userid=PxPayUser" - end - - def successful_xml_response - '<Response valid="1"><Success>1</Success><TxnType>Purchase</TxnType><CurrencyInput>USD</CurrencyInput><MerchantReference></MerchantReference><TxnData1></TxnData1><TxnData2></TxnData2><TxnData3></TxnData3><AuthCode>035411</AuthCode><CardName>Visa</CardName><CardHolderName>FIRSTNAME LASTNAME</CardHolderName><CardNumber>411111........11</CardNumber><DateExpiry>1220</DateExpiry><ClientInfo>67.210.173.114</ClientInfo><TxnId>1</TxnId><EmailAddress>g@g.com</EmailAddress><DpsTxnRef>00000008096f3c7a</DpsTxnRef><BillingId></BillingId><DpsBillingId></DpsBillingId><AmountSettlement>157.00</AmountSettlement><CurrencySettlement>USD</CurrencySettlement><DateSettlement>20120731</DateSettlement><TxnMac>2BC29AF2</TxnMac><ResponseText>APPROVED</ResponseText><CardNumber2></CardNumber2><IssuerCountryId>0</IssuerCountryId></Response>' - end - - def failed_xml_response - '<Response valid="1"><Success>0</Success><TxnType>Purchase</TxnType><CurrencyInput>USD</CurrencyInput><MerchantReference></MerchantReference><TxnData1></TxnData1><TxnData2></TxnData2><TxnData3></TxnData3><AuthCode></AuthCode><CardName>Visa</CardName><CardHolderName>FIRSTNAME LASTNAME</CardHolderName><CardNumber>411111........12</CardNumber><DateExpiry>1210</DateExpiry><ClientInfo>67.210.173.114</ClientInfo><TxnId>100</TxnId><EmailAddress>g@g.com</EmailAddress><DpsTxnRef>00000008096fa1b2</DpsTxnRef><BillingId></BillingId><DpsBillingId></DpsBillingId><AmountSettlement>157.00</AmountSettlement><CurrencySettlement>USD</CurrencySettlement><DateSettlement>20120731</DateSettlement><TxnMac></TxnMac><ResponseText>DECLINED</ResponseText><CardNumber2></CardNumber2><IssuerCountryId>0</IssuerCountryId></Response>' - end - - def invalid_xml_response - end -end diff --git a/test/unit/integrations/notifications/quickpay_notification_test.rb b/test/unit/integrations/notifications/quickpay_notification_test.rb deleted file mode 100644 index 4d2bfd57051..00000000000 --- a/test/unit/integrations/notifications/quickpay_notification_test.rb +++ /dev/null @@ -1,145 +0,0 @@ -require 'test_helper' - -class QuickpayNotificationTest < Test::Unit::TestCase - include ActiveMerchant::Billing::Integrations - - def setup - @quickpay = Quickpay::Notification.new(http_raw_data, :credential2 => "test") - end - - def test_accessors - assert @quickpay.complete? - assert_equal "000", @quickpay.status - assert_equal "4262", @quickpay.transaction_id - assert_equal "1353061158", @quickpay.item_id - assert_equal "1.23", @quickpay.gross - assert_equal "DKK", @quickpay.currency - assert_equal Time.parse("2012-11-16 10:19:36+00:00"), @quickpay.received_at - end - - def test_compositions - assert_equal Money.new(123, 'DKK'), @quickpay.amount - end - - def test_acknowledgement - assert @quickpay.acknowledge - end - - def test_failed_acknnowledgement - @quickpay = Quickpay::Notification.new(http_raw_data, :credential2 => "badmd5string") - assert !@quickpay.acknowledge - end - - def test_quickpay_attributes - assert_equal "1", @quickpay.state - assert_equal "authorize", @quickpay.msgtype - end - - def test_generate_md5string - assert_equal "authorize1353061158123DKK2012-11-16T10:19:36+00:001000OK000OKMerchant #1merchant1@pil.dk4262dankortXXXXXXXXXXXX999910test", - @quickpay.generate_md5string - end - - def test_generate_md5check - assert_equal "7caa0df7d17085206af135ed70d22cc9", @quickpay.generate_md5check - end - - def test_respond_to_acknowledge - assert @quickpay.respond_to?(:acknowledge) - end - - private - def http_raw_data - <<-END_POST -------------------------------8a827a0e6829 -Content-Disposition: form-data; name="msgtype" - -authorize -------------------------------8a827a0e6829 -Content-Disposition: form-data; name="ordernumber" - -1353061158 -------------------------------8a827a0e6829 -Content-Disposition: form-data; name="amount" - -123 -------------------------------8a827a0e6829 -Content-Disposition: form-data; name="currency" - -DKK -------------------------------8a827a0e6829 -Content-Disposition: form-data; name="time" - -2012-11-16T10:19:36+00:00 -------------------------------8a827a0e6829 -Content-Disposition: form-data; name="state" - -1 -------------------------------8a827a0e6829 -Content-Disposition: form-data; name="qpstat" - -000 -------------------------------8a827a0e6829 -Content-Disposition: form-data; name="qpstatmsg" - -OK -------------------------------8a827a0e6829 -Content-Disposition: form-data; name="chstat" - -000 -------------------------------8a827a0e6829 -Content-Disposition: form-data; name="chstatmsg" - -OK -------------------------------8a827a0e6829 -Content-Disposition: form-data; name="merchant" - -Merchant #1 -------------------------------8a827a0e6829 -Content-Disposition: form-data; name="merchantemail" - -merchant1@pil.dk -------------------------------8a827a0e6829 -Content-Disposition: form-data; name="transaction" - -4262 -------------------------------8a827a0e6829 -Content-Disposition: form-data; name="cardtype" - -dankort -------------------------------8a827a0e6829 -Content-Disposition: form-data; name="cardnumber" - -XXXXXXXXXXXX9999 -------------------------------8a827a0e6829 -Content-Disposition: form-data; name="cardhash" - - -------------------------------8a827a0e6829 -Content-Disposition: form-data; name="splitpayment" - -1 -------------------------------8a827a0e6829 -Content-Disposition: form-data; name="fraudprobability" - - -------------------------------8a827a0e6829 -Content-Disposition: form-data; name="fraudremarks" - - -------------------------------8a827a0e6829 -Content-Disposition: form-data; name="fraudreport" - - -------------------------------8a827a0e6829 -Content-Disposition: form-data; name="fee" - -0 -------------------------------8a827a0e6829 -Content-Disposition: form-data; name="md5check" - -7caa0df7d17085206af135ed70d22cc9 -------------------------------8a827a0e6829-- -END_POST - end -end diff --git a/test/unit/integrations/notifications/rbkmoney_notification_test.rb b/test/unit/integrations/notifications/rbkmoney_notification_test.rb deleted file mode 100644 index 0ab355b9869..00000000000 --- a/test/unit/integrations/notifications/rbkmoney_notification_test.rb +++ /dev/null @@ -1,54 +0,0 @@ -require 'test_helper' - -class RbkmoneyNotificationTest < Test::Unit::TestCase - include ActiveMerchant::Billing::Integrations - - def setup - @rbkmoney = Rbkmoney::Notification.new(https_raw_data, :secret => 'myKey') - end - - def test_accessors - assert @rbkmoney.complete? - assert_equal 'completed', @rbkmoney.status - assert_equal '100500', @rbkmoney.transaction_id - assert_equal '1234', @rbkmoney.item_id - assert_equal '12.30', @rbkmoney.gross - assert_equal 'RUR', @rbkmoney.currency - assert_equal '2007-10-28 14:22:35', @rbkmoney.received_at - assert_false @rbkmoney.test? - end - - def test_https_acknowledgement - assert @rbkmoney.acknowledge - end - - def test_respond_to_acknowledge - assert @rbkmoney.respond_to?(:acknowledge) - end - - def test_user_fields - expected = { - 'userField_1' => 'user field 1' - } - assert_equal expected, @rbkmoney.user_fields - end - - private - - def https_raw_data - "eshopId=12&\ -paymentId=100500&\ -orderId=1234&\ -eshopAccount=RU123456789&\ -serviceName=Kniga&\ -recipientAmount=12.30&\ -recipientCurrency=RUR&\ -paymentStatus=5&\ -userName=Petrov%20Alexander&\ -userEmail=admin@rbkmoney.ru&\ -paymentData=2007-10-28%2014:22:35&\ -secretKey=myKey&\ -hash=8f4693792fe46de17a2c4d93b84910a6&\ -userField_1=user%20field%201" - end -end diff --git a/test/unit/integrations/notifications/robokassa_notification_test.rb b/test/unit/integrations/notifications/robokassa_notification_test.rb deleted file mode 100644 index c1676fe8ff0..00000000000 --- a/test/unit/integrations/notifications/robokassa_notification_test.rb +++ /dev/null @@ -1,42 +0,0 @@ -require 'test_helper' - -class RobokassaNotificationTest < Test::Unit::TestCase - include ActiveMerchant::Billing::Integrations - - def setup - @robokassa = Robokassa::Notification.new(http_raw_data, :secret => 'secret') - end - - def test_accessors - assert @robokassa.complete? - assert_equal "500", @robokassa.gross - assert_equal "123", @robokassa.item_id - end - - def test_compositions - assert_equal Money.new(50000, 'USD'), @robokassa.amount - end - - # Replace with real successful acknowledgement code - def test_acknowledgement - assert @robokassa.acknowledge - end - - def test_respond_to_acknowledge - assert @robokassa.respond_to?(:acknowledge) - end - - def test_wrong_signature - @robokassa = Robokassa::Notification.new(http_raw_data_with_wrong_signature, :secret => 'secret') - assert !@robokassa.acknowledge - end - - private - def http_raw_data - "InvId=123&OutSum=500&SignatureValue=4a827a06c6e54595c2bd8f67fb7a0091&shpMySuperParam=456&shpa=123" - end - - def http_raw_data_with_wrong_signature - "InvId=123&OutSum=500&SignatureValue=wrong&shpMySuperParam=456&shpa=123" - end -end diff --git a/test/unit/integrations/notifications/sage_pay_form_notification_test.rb b/test/unit/integrations/notifications/sage_pay_form_notification_test.rb deleted file mode 100644 index 74472f7c79c..00000000000 --- a/test/unit/integrations/notifications/sage_pay_form_notification_test.rb +++ /dev/null @@ -1,133 +0,0 @@ -require 'test_helper' - -class SagePayFormNotificationTest < Test::Unit::TestCase - include ActiveMerchant::Billing::Integrations - - def setup - @options = {:credential2 => 'EncryptionKey123'} - end - - def test_successful_purchase - n = SagePayForm::Notification.new(successful_purchase, @options) - assert n.complete? - assert_false n.cancelled? - - assert_equal 'Completed', n.status - assert_equal 'OK', n.status_code - assert_equal 'Successfully Authorised Transaction', n.message - assert_equal '28', n.item_id - assert_equal '{2D370B0B-692D-4D07-B616-91B86CCDF85A}', n.transaction_id - assert_equal '7349', n.auth_id - assert_equal '31.47', n.gross - assert_equal 'ALL MATCH', n.avs_cv2_result - assert_equal 'MATCHED', n.address_result - assert_equal 'MATCHED', n.post_code_result - assert_equal 'MATCHED', n.cv2_result - assert_equal 'OK', n.buyer_auth_result - assert_equal 'MNG8ZAJDJRUKW90GGYZNTH', n.buyer_auth_result_code - assert_equal 'VISA', n.credit_card_type - assert_equal '8356', n.credit_card_last_4_digits - - assert_false n.gift_aid? - assert_false n.payer_verified? - assert_false n.test? - - assert_nil n.address_status - assert_nil n.currency - end - - def test_failed_purchase - n = SagePayForm::Notification.new(failed_purchase, @options) - assert_false n.complete? - assert_false n.cancelled? - - assert_equal 'Failed', n.status - assert_equal 'NOTAUTHED', n.status_code - assert_equal 'NOTAUTHED message generated by Simulator', n.message - assert_equal '28', n.item_id - assert_equal '{2D370B0B-692D-4D07-B616-91B86CCDF85A}', n.transaction_id - assert_equal '31.47', n.gross - assert_equal 'ALL MATCH', n.avs_cv2_result - assert_equal 'MATCHED', n.address_result - assert_equal 'MATCHED', n.post_code_result - assert_equal 'MATCHED', n.cv2_result - assert_equal 'OK', n.buyer_auth_result - assert_equal 'MNVJYYXXHMCNH0BOTBT97Z', n.buyer_auth_result_code - assert_equal 'VISA', n.credit_card_type - assert_equal '4353', n.credit_card_last_4_digits - - assert_false n.gift_aid? - assert_false n.payer_verified? - assert_false n.test? - - assert_nil n.auth_id - assert_nil n.address_status - assert_nil n.currency - end - - def test_cancelled_purchase - n = SagePayForm::Notification.new(cancelled_purchase, @options) - assert_false n.complete? - assert n.cancelled? - - assert_equal 'Failed', n.status - assert_equal 'ABORT', n.status_code - assert_equal 'ABORT message generated by Simulator', n.message - assert_equal '5', n.item_id - assert_equal '{90A42BA2-1281-4CA9-8E84-E43C0E7FD85F}', n.transaction_id - assert_equal '148.99', n.gross - assert_equal 'ALL MATCH', n.avs_cv2_result - assert_equal 'MATCHED', n.address_result - assert_equal 'MATCHED', n.post_code_result - assert_equal 'MATCHED', n.cv2_result - assert_equal 'OK', n.buyer_auth_result - assert_equal 'MNJ8W58FNX1Q5OAV4TZKW2', n.buyer_auth_result_code - assert_equal 'VISA', n.credit_card_type - assert_equal '6425', n.credit_card_last_4_digits - - assert_false n.gift_aid? - assert_false n.payer_verified? - assert_false n.test? - - assert_nil n.auth_id - assert_nil n.address_status - assert_nil n.currency - end - - def test_compositions - n = SagePayForm::Notification.new(successful_purchase, @options) - assert_equal Money.new(3147, nil), n.amount - end - - def test_bogus_crypt - assert_raises SagePayForm::Notification::InvalidCryptData do - SagePayForm::Notification.new('crypt=SomeInvalidCryptField', @options) - end - end - - def test_missing_crypt - assert_raises SagePayForm::Notification::MissingCryptData do - SagePayForm::Notification.new('other=stuff', @options) - end - end - - def test_missing_key - assert_raises SagePayForm::Notification::MissingCryptKey do - SagePayForm::Notification.new(successful_purchase, {}) - end - end - - private - - def successful_purchase - 'utm_nooverride=1&crypt=FhoCBgwDSSYkSBgRGEVHQAELFxMQHEk6Gg0oAApCVEYpAhpSOAUAAQAcIhYcVRJnNw8NARgTAAAAAG0zHF9WXDc6GzEWFBFUXVZtMyliZksMCl4JSzRHXl8seydUBwsBAUNXNklHWStZX31IQABwC3MtIDY/SEEoEkgfHThERlsLAV5FSkRNTy4DJBAXRQ8AdEBXRV8xIjosOHlYOH1+EwgvNzExVjUNCxwuFgpjV0AwAhdPNDEgKicrD0MpXkFHBgEHFysVBxwDGnYoOGVxewAqRTEvQiYMHBsnEUR8c2cGJiY2XzcdDxsvIgFEARQAAT0GEQwCETobDz8QCgx9eGMtIiQvTTknKFYRJDN1eGEQJTRLSTczMDUgHy1fclNBIToaAhxNIiA8L20pGEJGBwEHBBsNA0lRXFt9' - end - - def failed_purchase - 'utm_nooverride=1&crypt=FhoCBgwDSScgOgowLXl3d2M9FxMNBQctChoqDBUMfHwRLzYmMTUwSQILOBYYVlcTIgsNFwsRAAwLTikcWWJbXjACAgYWAlI/CgAvCgtlSnAqCgZPS0hSPz89Hx0wVQ9IdypQRUkyRCtCWHJXPRwGd3VZTjBPQUJEVl8JXU9ycXcDVlYzBFY1BAAbJRFEAgMdcVlFMy8jNz9dUwopNRF/chEtK1Q4FBAbCh04NxxCR18xUy4zLTM8LCtIGwoKRXFcIQsxFwoFGB1SIwoxOnl3d2MtNUArFQccAxp2KDhlcXsAKkU1EBYAKAYKdlVfAnZgIA0WABwjAAgbGzhYNnoUcAQ4NU80PiIjNjcTPTF8cX0NXiE9LTIgUFg0bSYYQ1ZnPB4GTy85JyhJIioWDQV2WiIHFwFEREdcXA==' - end - - def cancelled_purchase - 'utm_nooverride=1&crypt=FhoCBgwDSSgtIRkxX2JGUjEbEDYcBBUAA1MKJzZjZhMoCxABGBcRSQgLJQALUEZWIU4BC1kjHQQaAioRFkMUZSAABx0LJAwqAAouWEwXZGMWOhs7HU0PUF8vf1c7cAAedFxbQ1RENyhWQ3MgQQUfdnFdIEI8RzItV1sNGF9wX1wwABdPSERMR1ZXbSQvYnFld1MiPjVQOSg7LQNDOFVWQSAdECAcAwEFG1MGJC1yenYBSDMdCgQ3BgsLGQAKRF5HeCMiJjo4MS1JLR1XK1RBRikaXj84JDchKiptIhBXRnIsCl5CX0MwOgoNPhccYkZSMRsQTzY7UiouOB1YNH94CxJbWzQ3KEU4WiEKM01laHgSXEUxGAIQPRYeLlgveGFyYyICAQ1EMAAIBz8WRAcGAXA=' - end -end diff --git a/test/unit/integrations/notifications/two_checkout_notification_test.rb b/test/unit/integrations/notifications/two_checkout_notification_test.rb deleted file mode 100644 index 8323e2a4843..00000000000 --- a/test/unit/integrations/notifications/two_checkout_notification_test.rb +++ /dev/null @@ -1,48 +0,0 @@ -require 'test_helper' - -class TwoCheckoutNotificationTest < Test::Unit::TestCase - include ActiveMerchant::Billing::Integrations - - def setup - @test_notification = TwoCheckout::Notification.new(test_http_raw_data) - @live_notification = TwoCheckout::Notification.new(live_http_raw_data) - end - - def test_accessors - assert @test_notification.complete? - assert @test_notification.test? - assert_equal "Completed", @test_notification.status - - assert_equal "3644445821", @test_notification.transaction_id - assert_equal "10", @test_notification.item_id - assert_equal "31.66", @test_notification.gross - assert_equal "USD", @test_notification.currency - assert_equal 'cody@example.com', @test_notification.payer_email - end - - def test_live_accessors - assert @live_notification.complete? - assert !@live_notification.test? - end - - def test_compositions - assert_equal Money.new(3166, 'USD'), @test_notification.amount - end - - def test_acknowledgement - test_notification = TwoCheckout::Notification.new(test_http_raw_data, :credential2 => 'tango') - live_notification = TwoCheckout::Notification.new(live_http_raw_data, :credential2 => 'tango') - assert !test_notification.acknowledge - assert live_notification.acknowledge - end - - - private - def test_http_raw_data - "sid=1232919&fixed=Y&key=B5446FF1061F5522C29CCCA0F95EA375&state=ON&email=cody%40example.com&city=Ottawa&street_address=1+-+8+Clarence+St%2C+Apartment+5&product_id=&cart_order_id=10&tcoid=8032992fe053170efb7e58de35b07d39&country=Canada&order_number=3644445821&merchant_order_id=10&option-=&cart_id=10&Product_description=&lang=&demo=Y&pay_method=CC&quantity=1&total=31.66&phone=(555)555-5555&return_url=&credit_card_processed=Y&zip=K1M+3G7&merchant_product_id=10&card_holder_name=Cody+Fauser" - end - - def live_http_raw_data - "sid=1232919&fixed=Y&key=0ee5cd112a9d34952167399c6b55d14f&state=ON&email=cody%40example.com&city=Ottawa&street_address=1+-+8+Clarence+St%2C+Apartment+5&product_id=&cart_order_id=10&tcoid=8032992fe053170efb7e58de35b07d39&country=Canada&order_number=3644445821&merchant_order_id=10&option-=&cart_id=10&Product_description=&lang=&pay_method=CC&quantity=1&total=31.66&phone=(555)555-5555&return_url=&credit_card_processed=Y&zip=K1M+3G7&merchant_product_id=10&card_holder_name=Cody+Fauser" - end -end diff --git a/test/unit/integrations/notifications/valitor_notification_test.rb b/test/unit/integrations/notifications/valitor_notification_test.rb deleted file mode 100644 index a49502e4108..00000000000 --- a/test/unit/integrations/notifications/valitor_notification_test.rb +++ /dev/null @@ -1,57 +0,0 @@ -# encoding: utf-8 -require 'test_helper' - -class ValitorNotificationTest < Test::Unit::TestCase - include ActiveMerchant::Billing::Integrations - - def setup - @notification = Valitor::Notification.new(http_raw_query) - end - - def test_accessors - assert @notification.complete? - assert @notification.acknowledge - assert @notification.success? - assert_equal "Completed", @notification.status - assert_equal "2b969de3-6928-4fa7-a0d6-6dec63fec5c3", @notification.transaction_id - assert_equal "order684afbb93730db2492a8fa2f3fedbcb9", @notification.order - assert_equal Time.parse("2011-01-21").utc, @notification.received_at - - assert_equal "VISA", @notification.card_type - assert_equal "9999", @notification.card_last_four - assert_equal "123450", @notification.authorization_number - assert_equal "F\303\206RSLUNR: 0026237", @notification.transaction_number - assert_equal "NAME", @notification.customer_name - assert_equal "123 ADDRESS", @notification.customer_address - assert_equal "CITY", @notification.customer_city - assert_equal "98765", @notification.customer_zip - assert_equal "COUNTRY", @notification.customer_country - assert_equal "EMAIL@EXAMPLE.COM", @notification.customer_email - assert_equal "COMMENTS", @notification.customer_comment - assert_equal "100.00", @notification.gross - assert_nil @notification.currency - - assert !@notification.test? - end - - def test_acknowledge - valid = Valitor.notification(http_raw_query, :credential2 => 'password') - assert valid.acknowledge - assert valid.success? - assert valid.complete? - - invalid = Valitor.notification(http_raw_query, :credential2 => 'bogus') - assert !invalid.acknowledge - assert !invalid.success? - assert !invalid.complete? - end - - def test_test_mode - assert Valitor::Notification.new(http_raw_query, :test => true).test? - assert !Valitor::Notification.new(http_raw_query).test? - end - - def http_raw_query - "Kortategund=VISA&KortnumerSidustu=9999&Dagsetning=21.01.2011&Heimildarnumer=123450&Faerslunumer=FÆRSLUNR: 0026237&VefverslunSalaID=2b969de3-6928-4fa7-a0d6-6dec63fec5c3&Tilvisunarnumer=order684afbb93730db2492a8fa2f3fedbcb9&RafraenUndirskriftSvar=03d859813eff711d6c8667b0caf5f5a5&Upphaed=100&Nafn=NAME&Heimilisfang=123 ADDRESS&Postnumer=98765&Stadur=CITY&Land=COUNTRY&Tolvupostfang=EMAIL@EXAMPLE.COM&Athugasemdir=COMMENTS&LeyfirEndurtoku=" - end -end diff --git a/test/unit/integrations/notifications/verkkomaksut_notification_test.rb b/test/unit/integrations/notifications/verkkomaksut_notification_test.rb deleted file mode 100644 index 4abb7aa4d0b..00000000000 --- a/test/unit/integrations/notifications/verkkomaksut_notification_test.rb +++ /dev/null @@ -1,32 +0,0 @@ -require 'test_helper' - -class VerkkomaksutNotificationTest < Test::Unit::TestCase - include ActiveMerchant::Billing::Integrations - - def setup - @verkkomaksut = Verkkomaksut::Notification.new(http_params) - end - - def test_accessors - assert @verkkomaksut.complete? - assert_equal "PAID", @verkkomaksut.status - assert_equal "2", @verkkomaksut.order_id - assert_equal "1336058061", @verkkomaksut.received_at - assert_equal "4", @verkkomaksut.method - assert_equal "6B40F9B939D03EFE7573D61708FA4126", @verkkomaksut.security_key - end - - def test_acknowledgement - assert @verkkomaksut.acknowledge("6pKF4jkv97zmqBJ3ZL8gUw5DfT2NMQ") - end - - def test_faulty_acknowledgement - @verkkomaksut = Verkkomaksut::Notification.new({"ORDER_NUMBER"=>"2", "TIMESTAMP"=>"1336058061", "PAID"=>"3DF5BB7E26", "METHOD"=>"4", "RETURN_AUTHCODE"=>"6asd0F9B939D03EFE7573D61708FA4126"}) - assert_equal false, @verkkomaksut.acknowledge("6pKF4jkv97zmqBJ3ZL8gUw5DfT2NMQ") - end - - private - def http_params - {"ORDER_NUMBER"=>"2", "TIMESTAMP"=>"1336058061", "PAID"=>"3DF5BB7E26", "METHOD"=>"4", "RETURN_AUTHCODE"=>"6B40F9B939D03EFE7573D61708FA4126"} - end -end diff --git a/test/unit/integrations/notifications/web_pay_notification_test.rb b/test/unit/integrations/notifications/web_pay_notification_test.rb deleted file mode 100644 index 2aef0d13b63..00000000000 --- a/test/unit/integrations/notifications/web_pay_notification_test.rb +++ /dev/null @@ -1,42 +0,0 @@ -require 'test_helper' - -class WebPayNotificationTest < Test::Unit::TestCase - include ActiveMerchant::Billing::Integrations - - def setup - @web_pay = WebPay::Notification.new(http_raw_data, :secret => 'secret') - end - - def test_accessors - assert @web_pay.complete? - assert_equal "500", @web_pay.gross - assert_equal "123", @web_pay.item_id - end - - def test_compositions - assert_equal BigDecimal.new("500"), @web_pay.amount - end - - def test_respond_to_acknowledge - assert @web_pay.respond_to?(:acknowledge) - end - - def test_acknowledgement - assert @web_pay.acknowledge - end - - def test_wrong_signature - @web_pay = WebPay::Notification.new(http_raw_data_with_wrong_signature, :secret => 'secret') - assert !@web_pay.acknowledge - end - - private - - def http_raw_data - "batch_timestamp=123&currency_id=BYR&amount=500&payment_method=test&order_id=666&site_order_id=123&transaction_id=666&payment_type=type&rrn=123&wsb_signature=9b1d56e24e5cd0a0a443276073248510" - end - - def http_raw_data_with_wrong_signature - "batch_timestamp=123&currency_id=BYR&amount=500&payment_method=test&order_id=666&site_order_id=123&transaction_id=666&payment_type=type&rrn=123&wsb_signature=wrong" - end -end diff --git a/test/unit/integrations/notifications/webmoney_notification_test.rb b/test/unit/integrations/notifications/webmoney_notification_test.rb deleted file mode 100644 index a43c010153e..00000000000 --- a/test/unit/integrations/notifications/webmoney_notification_test.rb +++ /dev/null @@ -1,38 +0,0 @@ -require 'test_helper' - -class WebmoneyNotificationTest < Test::Unit::TestCase - include ActiveMerchant::Billing::Integrations - - def setup - @webmoney = Webmoney::Notification.new(http_raw_data, :secret => 'qert1234qee') - end - - def test_accessors - assert_equal "1.00", @webmoney.gross - assert_equal "123", @webmoney.item_id - assert_equal BigDecimal.new("1"), @webmoney.amount - end - - def test_acknowledgement - assert @webmoney.acknowledge - end - - def test_respond_to_acknowledge - assert @webmoney.respond_to?(:acknowledge) - end - - def test_wrong_signature - @webmoney = Webmoney::Notification.new(http_raw_data_with_wrong_signature, :secret => 'qert1234qee') - assert !@webmoney.acknowledge - end - - private - - def http_raw_data - "LMI_MODE=1&LMI_PAYMENT_AMOUNT=1.00&LMI_PAYEE_PURSE=Z133417776395&LMI_PAYMENT_NO=123&LMI_PAYER_WM=273350110703&LMI_PAYER_PURSE=Z133417776395&LMI_SYS_INVS_NO=8&LMI_SYS_TRANS_NO=708&LMI_SYS_TRANS_DATE=20120823+15%3A54%3A01&LMI_HASH=F5E7A18237B73D4A7E620CCFC065D8FC&LMI_PAYMENT_DESC=Request+to+webmoney+%23123&LMI_LANG=ru-RU&LMI_DBLCHK=SMS" - end - - def http_raw_data_with_wrong_signature - "LMI_MODE=1&LMI_PAYMENT_AMOUNT=1.00&LMI_PAYEE_PURSE=Z133417776395&LMI_PAYMENT_NO=123&LMI_PAYER_WM=273350110703&LMI_PAYER_PURSE=Z133417776395&LMI_SYS_INVS_NO=8&LMI_SYS_TRANS_NO=708&LMI_SYS_TRANS_DATE=20120823+15%3A54%3A01&LMI_HASH=QWEE123237B73D4A7E620CCFC065D8FC&LMI_PAYMENT_DESC=Request+to+webmoney+%23123&LMI_LANG=ru-RU&LMI_DBLCHK=SMS" - end -end diff --git a/test/unit/integrations/notifications/world_pay_notification_test.rb b/test/unit/integrations/notifications/world_pay_notification_test.rb deleted file mode 100644 index c56fbda53e2..00000000000 --- a/test/unit/integrations/notifications/world_pay_notification_test.rb +++ /dev/null @@ -1,75 +0,0 @@ -require 'test_helper' - -class WorldPayNotificationTest < Test::Unit::TestCase - include ActiveMerchant::Billing::Integrations - - def setup - @world_pay = WorldPay::Notification.new(http_raw_data) - end - - def test_accessors - assert @world_pay.complete? - assert_equal "Completed", @world_pay.status - assert_equal "1234123412341234", @world_pay.transaction_id - assert_equal "1", @world_pay.item_id - assert_equal "5.00", @world_pay.gross - assert_equal "GBP", @world_pay.currency - assert_equal Time.utc('2007-01-01 00:00:00').utc, @world_pay.received_at - assert @world_pay.test? - end - - def test_compositions - assert_equal Money.new(500, 'GBP'), @world_pay.amount - end - - def test_extra_accessors - assert_equal "Andrew White", @world_pay.name - assert_equal "1 Nowhere Close", @world_pay.address - assert_equal "CV1 1AA", @world_pay.postcode - assert_equal "GB", @world_pay.country - assert_equal "024 7699 9999", @world_pay.phone_number - assert_equal "024 7699 9999", @world_pay.fax_number - assert_equal "andyw@example.com", @world_pay.email_address - assert_equal "Mastercard", @world_pay.card_type - end - - def test_respond_to_acknowledge - assert @world_pay.respond_to?(:acknowledge) - end - - def test_payment_successful_status - notification = WorldPay::Notification.new('transStatus=Y') - assert_equal 'Completed', notification.status - end - - def test_payment_cancelled_status - notification = WorldPay::Notification.new('transStatus=C') - assert_equal 'Cancelled', notification.status - end - - def test_callback_password - assert_equal 'password', @world_pay.security_key - end - - def test_fraud_prevention_checks - assert_equal :matched, @world_pay.cvv_status - assert_equal :matched, @world_pay.postcode_status - assert_equal :matched, @world_pay.address_status - assert_equal :matched, @world_pay.country_status - end - - def test_custom_parameters - notification = WorldPay::Notification.new("M_custom_1=Custom Value 1&MC_custom_2=Custom Value 2&CM_custom_3=Custom Value 3") - assert_equal 'Custom Value 1', notification.custom_params[:custom_1] - assert_equal 'Custom Value 2', notification.custom_params[:custom_2] - assert_equal 'Custom Value 3', notification.custom_params[:custom_3] - end - - - private - - def http_raw_data - "transId=1234123412341234&transStatus=Y&currency=GBP&transTime=1167609600000&testMode=100&authAmount=5.00&cartId=1&authCurrency=GBP&callbackPW=password&countryMatch=Y&AVS=2222&cardType=Mastercard&name=Andrew White&address=1 Nowhere Close&postcode=CV1 1AA&country=GB&tel=024 7699 9999&fax=024 7699 9999&email=andyw@example.com" - end - -end \ No newline at end of file diff --git a/test/unit/integrations/paxum_module_test.rb b/test/unit/integrations/paxum_module_test.rb deleted file mode 100644 index e084e4bff26..00000000000 --- a/test/unit/integrations/paxum_module_test.rb +++ /dev/null @@ -1,28 +0,0 @@ -require 'test_helper' - -class PaxumModuleTest < Test::Unit::TestCase - include ActiveMerchant::Billing::Integrations - - def test_helper_method - assert_instance_of Paxum::Helper, Paxum.helper(123, 'test') - end - - def test_notification_method - assert_instance_of Paxum::Notification, Paxum.notification('name=cody') - end - - def test_test_mode - ActiveMerchant::Billing::Base.integration_mode = :test - assert_equal 'https://paxum.com/payment/phrame.php?action=displayProcessPaymentLogin', Paxum.service_url - end - - def test_production_mode - ActiveMerchant::Billing::Base.integration_mode = :production - assert_equal 'https://paxum.com/payment/phrame.php?action=displayProcessPaymentLogin', Paxum.service_url - end - - def test_invalid_mode - ActiveMerchant::Billing::Base.integration_mode = :bro - assert_raise(StandardError){Paxum.service_url} - end -end diff --git a/test/unit/integrations/pay_fast_module_test.rb b/test/unit/integrations/pay_fast_module_test.rb deleted file mode 100644 index ae5f3f2659b..00000000000 --- a/test/unit/integrations/pay_fast_module_test.rb +++ /dev/null @@ -1,39 +0,0 @@ -require 'test_helper' - -class PayFastModuleTest < Test::Unit::TestCase - include ActiveMerchant::Billing::Integrations - - def test_helper_method - assert_instance_of PayFast::Helper, PayFast.helper(123, 'test') - end - - def test_notification_method - assert_instance_of PayFast::Notification, PayFast.notification('name=cody') - end - - def test_test_process_mode - ActiveMerchant::Billing::Base.integration_mode = :test - assert_equal 'https://sandbox.payfast.co.za/eng/process', PayFast.service_url - end - - def test_test_validate_mode - ActiveMerchant::Billing::Base.integration_mode = :test - assert_equal 'https://sandbox.payfast.co.za/eng/query/validate', PayFast.validate_service_url - end - - def test_production_process_mode - ActiveMerchant::Billing::Base.integration_mode = :production - assert_equal 'https://www.payfast.co.za/eng/process', PayFast.service_url - end - - def test_production_validate_mode - ActiveMerchant::Billing::Base.integration_mode = :production - assert_equal 'https://www.payfast.co.za/eng/query/validate', PayFast.validate_service_url - end - - def test_invalid_mode - ActiveMerchant::Billing::Base.integration_mode = :winterfell - assert_raise(StandardError) { PayFast.service_url } - assert_raise(StandardError) { PayFast.validate_service_url } - end -end diff --git a/test/unit/integrations/paypal_module_test.rb b/test/unit/integrations/paypal_module_test.rb deleted file mode 100644 index aecccf2d56a..00000000000 --- a/test/unit/integrations/paypal_module_test.rb +++ /dev/null @@ -1,28 +0,0 @@ -require 'test_helper' - -class PaypalModuleTest < Test::Unit::TestCase - include ActiveMerchant::Billing::Integrations - - def test_notification_method - assert_instance_of Paypal::Notification, Paypal.notification('name=cody', {}) - end - - def test_test_mode - ActiveMerchant::Billing::Base.integration_mode = :test - assert_equal 'https://www.sandbox.paypal.com/cgi-bin/webscr', Paypal.service_url - end - - def test_production_mode - ActiveMerchant::Billing::Base.integration_mode = :production - assert_equal 'https://www.paypal.com/cgi-bin/webscr', Paypal.service_url - end - - def test_invalid_mode - ActiveMerchant::Billing::Base.integration_mode = :zoomin - assert_raise(StandardError){ Paypal.service_url } - end - - def test_return_method - assert_instance_of Paypal::Return, Paypal.return('name=cody', {}) - end -end diff --git a/test/unit/integrations/paysbuy_module_test.rb b/test/unit/integrations/paysbuy_module_test.rb deleted file mode 100644 index e107f4b566e..00000000000 --- a/test/unit/integrations/paysbuy_module_test.rb +++ /dev/null @@ -1,9 +0,0 @@ -require 'test_helper' - -class PaysbuyModuleTest < Test::Unit::TestCase - include ActiveMerchant::Billing::Integrations - - def test_notification_method - assert_instance_of Paysbuy::Notification, Paysbuy.notification('name=cody') - end -end diff --git a/test/unit/integrations/payu_in_module_test.rb b/test/unit/integrations/payu_in_module_test.rb deleted file mode 100644 index f563f4a425b..00000000000 --- a/test/unit/integrations/payu_in_module_test.rb +++ /dev/null @@ -1,32 +0,0 @@ -require 'test_helper' - -class PayuInModuleTest < Test::Unit::TestCase - include ActiveMerchant::Billing::Integrations - - def setup - ActiveMerchant::Billing::Base.integration_mode = :test - @merchant_id = 'C0Dr8m' - @secret_key = '3sf0jURk' - end - - def test_service_url_method - ActiveMerchant::Billing::Base.integration_mode = :test - assert_equal "https://test.payu.in/_payment.php", PayuIn.service_url - - ActiveMerchant::Billing::Base.integration_mode = :production - assert_equal "https://secure.payu.in/_payment.php", PayuIn.service_url - end - - def test_return_method - assert_instance_of PayuIn::Return, PayuIn.return('name=foo', {}) - end - - def test_notification_method - assert_instance_of PayuIn::Notification, PayuIn.notification('name=foo', {}) - end - - def test_checksum_method - payu_load = "4ba4afe87f7e73468f2a|10.00|Product Info|Payu-Admin|test@example.com||||||||||" - assert_equal "cd324f64891b07d95492a2fd80ae469092e302faa3d3df5ba1b829936fd7497b6e89c3e48fd70e2a131cdd4f17d14bc20f292e9408650c085bc3bedb32f44266", PayuIn.checksum(@merchant_id, @secret_key, payu_load) - end -end diff --git a/test/unit/integrations/platron_module_test.rb b/test/unit/integrations/platron_module_test.rb deleted file mode 100644 index 858bc8aef4c..00000000000 --- a/test/unit/integrations/platron_module_test.rb +++ /dev/null @@ -1,25 +0,0 @@ -require 'test_helper' - -class PlatronModuleTest < Test::Unit::TestCase - include ActiveMerchant::Billing::Integrations - - def test_notification_method - assert_instance_of Platron::Notification, Platron.notification('name=cody') - end - - def test_signature_string - signature_string = Platron.generate_signature_string( - { - :amount => 200, - :currency => 'USD', - :description => 'payment description', - :salt => 'salt', - :merchant_id => 'test_account', - :order_id => '123' - }, - 'payment.php', - 'secret' - ) - assert_equal "payment.php;200;USD;payment description;test_account;123;salt;secret", signature_string - end -end diff --git a/test/unit/integrations/pxpay_module_test.rb b/test/unit/integrations/pxpay_module_test.rb deleted file mode 100644 index 2c237eb6ace..00000000000 --- a/test/unit/integrations/pxpay_module_test.rb +++ /dev/null @@ -1,119 +0,0 @@ -require 'test_helper' - -class PxpayModuleTest < Test::Unit::TestCase - include CommStub - include ActionViewHelperTestHelper - include ActiveMerchant::Billing::Integrations - - def setup - super - @options = fixtures(:pxpay) - @username = @options[:login] - @key = @options[:password] - end - - def test_notification_method - Pxpay::Notification.any_instance.stubs(:decrypt_transaction_result) - - assert_instance_of Pxpay::Notification, Pxpay.notification('name=cody&result=token', :credential1 => '', :credential2 => '') - end - - def test_should_round_numbers - Pxpay::Helper.any_instance.stubs(:form_fields).returns({}) - - request = "" - payment_service_for('44',@username, :service => :pxpay, :amount => "157.003"){ |service| request = service.send :generate_request} - assert request !~ /AmountInput>157.003</ - - payment_service_for('44',@username, :service => :pxpay, :amount => "157.005"){ |service| request = service.send :generate_request} - assert request =~ /AmountInput>157.01</ - end - - def test_amount_has_cent_precision - Pxpay::Helper.any_instance.stubs(:form_fields).returns({}) - - request = "" - payment_service_for('44',@username, :service => :pxpay, :amount => "157"){ |service| request = service.send :generate_request} - assert request =~ /AmountInput>157.00</ - end - - def test_all_fields - Pxpay::Helper.any_instance.stubs(:form_fields).returns({}) - - request = "" - payment_service_for('44',@username, :service => :pxpay, :amount => 157.0){|service| - - service.customer_id 8 - service.customer :first_name => 'g', - :last_name => 'g', - :email => 'g@g.com', - :phone => '3' - - service.return_url "http://example.com/pxpay/return_url" - - service.credential2 @key - - request = service.send :generate_request - } - - assert_match /<TxnId>44</, request - assert_match /<PxPayUserId>#{@username}</, request - assert_match /<PxPayKey>#{@key}</, request - assert_match /<TxnType>Purchase</, request - assert_match /<AmountInput>157.00</, request - assert_match /<EnableAddBillCard>0</, request - assert_match /<EmailAddress>g@g.com</, request - assert_match /<UrlSuccess>http:\/\/example.com\/pxpay\/return_url</, request - assert_match /<UrlFail>http:\/\/example.com\/pxpay\/return_url</, request - end - - def test_xml_escaping_fields - Pxpay::Helper.any_instance.stubs(:form_fields).returns({}) - - request = "" - - payment_service_for('44',@username, :service => :pxpay, :amount => 157.0){|service| - - service.customer_id 8 - service.customer :first_name => 'g<', - :last_name => 'g&', - :email => '<g g> g@g.com', - :phone => '3' - - service.billing_address :zip => 'g', - :country => 'United States of <', - :address1 => 'g' - - service.ship_to_address :first_name => 'g>', - :last_name => 'g>', - :city => '><&', - :address1 => 'g&', - :address2 => '>', - :state => '>ut', - :country => '>United States of America', - :zip => '>g' - - service.credential2 @key - - service.return_url "http://t/pxpay/return_url?&" - service.cancel_return_url "http://t/pxpay/cancel_url?&" - request = service.generate_request - } - - assert_nothing_raised do - doc = Nokogiri::XML(request) { |config| config.options = Nokogiri::XML::ParseOptions::STRICT } - end - end - - def test_created_form_is_valid - Pxpay::Helper.any_instance.stubs(:ssl_post).returns('<Request valid="1"><URI>https://sec.paymentexpress.com/pxpay/pxpay.aspx?userid=PXPAY_USER&amp;request=REQUEST_TOKEN</URI></Request>') - - payment_service_for('44',@username, :service => :pxpay, :amount => 157.0){|service| - service.credential2 @key - service.return_url "http://store.shopify.com/done" - service.cancel_return_url "http://store.shopify.com/cancel" - } - - assert_match /method=\"GET\"/i, @output_buffer - end -end diff --git a/test/unit/integrations/quickpay_module_test.rb b/test/unit/integrations/quickpay_module_test.rb deleted file mode 100644 index 9c4019d7409..00000000000 --- a/test/unit/integrations/quickpay_module_test.rb +++ /dev/null @@ -1,9 +0,0 @@ -require 'test_helper' - -class QuickpayModuleTest < Test::Unit::TestCase - include ActiveMerchant::Billing::Integrations - - def test_notification_method - assert_instance_of Quickpay::Notification, Quickpay.notification('name=cody', {}) - end -end diff --git a/test/unit/integrations/rbkmoney_module_test.rb b/test/unit/integrations/rbkmoney_module_test.rb deleted file mode 100644 index 69a804ab15e..00000000000 --- a/test/unit/integrations/rbkmoney_module_test.rb +++ /dev/null @@ -1,9 +0,0 @@ -require 'test_helper' - -class RbkmoneyModuleTest < Test::Unit::TestCase - include ActiveMerchant::Billing::Integrations - - def test_notification_method - assert_instance_of Rbkmoney::Notification, Rbkmoney.notification('name=cody') - end -end diff --git a/test/unit/integrations/returns/chronopay_return_test.rb b/test/unit/integrations/returns/chronopay_return_test.rb deleted file mode 100644 index c0cb528137e..00000000000 --- a/test/unit/integrations/returns/chronopay_return_test.rb +++ /dev/null @@ -1,11 +0,0 @@ -require 'test_helper' - -class ChronopayReturnTest < Test::Unit::TestCase - include ActiveMerchant::Billing::Integrations - - def test_return - r = Chronopay::Return.new('') - assert r.success? - end -end - diff --git a/test/unit/integrations/returns/direc_pay_return_test.rb b/test/unit/integrations/returns/direc_pay_return_test.rb deleted file mode 100644 index 313b5cda7e3..00000000000 --- a/test/unit/integrations/returns/direc_pay_return_test.rb +++ /dev/null @@ -1,54 +0,0 @@ -require 'test_helper' - -class DirecPayReturnTest < Test::Unit::TestCase - include ActiveMerchant::Billing::Integrations - - def test_success - direc_pay = DirecPay::Return.new(http_raw_data_success) - assert direc_pay.success? - assert_equal 'Completed', direc_pay.message - end - - def test_failure_is_successful - direc_pay = DirecPay::Return.new(http_raw_data_failure) - assert direc_pay.success? - assert_equal 'Pending', direc_pay.message - end - - def test_treat_initial_failures_as_pending - direc_pay = DirecPay::Return.new(http_raw_data_failure) - assert_equal 'Pending', direc_pay.notification.status - end - - def test_return_has_notification - direc_pay = DirecPay::Return.new(http_raw_data_success) - notification = direc_pay.notification - - assert_equal '1001010000026481', direc_pay.notification.transaction_id - assert notification.complete? - assert_equal 'Completed', notification.status - assert_equal '1001', notification.item_id - assert_equal '1.00', notification.gross - assert_equal 100, notification.gross_cents - assert_equal Money.new(100, 'INR'), notification.amount - assert_equal 'INR', notification.currency - assert_equal 'IND', notification.country - assert_equal 'NULL', notification.other_details - end - - def test_treat_failed_return_as_complete - direc_pay = DirecPay::Return.new(http_raw_data_failure) - assert direc_pay.notification.complete? - end - - private - - def http_raw_data_success - "responseparams=1001010000026481|SUCCESS|IND|INR|NULL|1001|1.00|" - end - - def http_raw_data_failure - "responseparams=1001010000026516|FAIL|IND|INR|NULL|1001|1.00|" - end - -end diff --git a/test/unit/integrations/returns/directebanking_return_test.rb b/test/unit/integrations/returns/directebanking_return_test.rb deleted file mode 100644 index 758bb21fd7c..00000000000 --- a/test/unit/integrations/returns/directebanking_return_test.rb +++ /dev/null @@ -1,10 +0,0 @@ -require 'test_helper' - -class DirectebankingReturnTest < Test::Unit::TestCase - include ActiveMerchant::Billing::Integrations - - def test_return_is_always_succesful - r = Directebanking::Return.new("") - assert r.success? - end -end \ No newline at end of file diff --git a/test/unit/integrations/returns/dotpay_return_test.rb b/test/unit/integrations/returns/dotpay_return_test.rb deleted file mode 100644 index 0b99fdd859a..00000000000 --- a/test/unit/integrations/returns/dotpay_return_test.rb +++ /dev/null @@ -1,10 +0,0 @@ -require 'test_helper' - -class DotpayReturnTest < Test::Unit::TestCase - include ActiveMerchant::Billing::Integrations - - def test_return_is_always_succesful - r = Dotpay::Return.new("") - assert r.success? - end -end \ No newline at end of file diff --git a/test/unit/integrations/returns/dwolla_return_test.rb b/test/unit/integrations/returns/dwolla_return_test.rb deleted file mode 100644 index da2f6730fe7..00000000000 --- a/test/unit/integrations/returns/dwolla_return_test.rb +++ /dev/null @@ -1,54 +0,0 @@ -require 'test_helper' - -class DwollaReturnTest < Test::Unit::TestCase - include ActiveMerchant::Billing::Integrations - - def setup - @checkout_failed = Dwolla::Return.new(http_raw_data_failed_checkout, {:credential3 => '62hdv0jBjsBlD+0AmhVn9pQuULSC661AGo2SsksQTpqNUrff7Z'}) - @callback_failed = Dwolla::Return.new(http_raw_data_failed_callback, {:credential3 => '62hdv0jBjsBlD+0AmhVn9pQuULSC661AGo2SsksQTpqNUrff7Z'}) - @success = Dwolla::Return.new(http_raw_data_success, {:credential3 => '62hdv0jBjsBlD+0AmhVn9pQuULSC661AGo2SsksQTpqNUrff7Z'}) - end - - def test_success_return - assert @success.success? - end - - def test_success_accessors - assert_equal "ac5b910a-7ec1-4b65-9f68-90449ed030f6", @success.checkout_id - assert_equal "3165397", @success.transaction - assert_false @success.test? - end - - def test_failed_callback_return - assert_false @callback_failed.success? - end - - def test_failed_callback_accessors - assert_equal "ac5b910a-7ec1-4b65-9f68-90449ed030f6", @callback_failed.checkout_id - assert_equal "3165397", @callback_failed.transaction - assert @callback_failed.test? - end - - def test_checkout_failed_return - assert_false @callback_failed.success? - end - - def test_checkout_failed_accessors - assert_equal "failure", @checkout_failed.error - assert_equal "User Cancelled", @checkout_failed.error_description - end - - private - - def http_raw_data_success - "signature=7d4c5deaf9178faae7c437fd8693fc0b97b1b22b&orderId=abc123&amount=0.01&checkoutId=ac5b910a-7ec1-4b65-9f68-90449ed030f6&status=Completed&clearingDate=6/8/2013%208:07:41%20PM&transaction=3165397&postback=success" - end - - def http_raw_data_failed_callback - "signature=7d4c5deaf9178faae7c437fd8693fc0b97b1b22b&orderId=abc123&amount=0.01&checkoutId=ac5b910a-7ec1-4b65-9f68-90449ed030f6&status=Completed&clearingDate=6/8/2013%208:07:41%20PM&transaction=3165397&postback=failure&test=true" - end - - def http_raw_data_failed_checkout - "error=failure&error_description=User+Cancelled" - end -end \ No newline at end of file diff --git a/test/unit/integrations/returns/gestpay_return_test.rb b/test/unit/integrations/returns/gestpay_return_test.rb deleted file mode 100644 index 91e6ed3038f..00000000000 --- a/test/unit/integrations/returns/gestpay_return_test.rb +++ /dev/null @@ -1,10 +0,0 @@ -require 'test_helper' - -class GestpayReturnTest < Test::Unit::TestCase - include ActiveMerchant::Billing::Integrations - - def test_return - r = Gestpay::Return.new('') - assert r.success? - end -end diff --git a/test/unit/integrations/returns/hi_trust_return_test.rb b/test/unit/integrations/returns/hi_trust_return_test.rb deleted file mode 100644 index 2ed319ffae5..00000000000 --- a/test/unit/integrations/returns/hi_trust_return_test.rb +++ /dev/null @@ -1,29 +0,0 @@ -require 'test_helper' - -class HiTrustReturnTest < Test::Unit::TestCase - include ActiveMerchant::Billing::Integrations - - def test_successful_return - r = HiTrust::Return.new('order_id=&mscssid=&retcode=00&ordernumber=1138742&type=Auth') - assert r.success? - assert_equal HiTrust::Return::SUCCESS, r.params['retcode'] - assert_equal HiTrust::Return::CODES[HiTrust::Return::SUCCESS], r.message - end - - def test_failed_return - r = HiTrust::Return.new('retcode=-100') - assert_false r.success? - assert_equal HiTrust::Return::CODES['-100'], r.message - end - - def test_unknown_return - r = HiTrust::Return.new('retcode=unknown') - assert_false r.success? - assert_nil r.message - end - - def test_message_returns_the_correct_string_for_positive_retcode - r = HiTrust::Return.new('order_id=&mscssid=&retcode=12&ordernumber=1138742&type=Auth') - assert_equal HiTrust::Return::CODES["positive"], r.message - end -end diff --git a/test/unit/integrations/returns/ipay88_return_test.rb b/test/unit/integrations/returns/ipay88_return_test.rb deleted file mode 100644 index efae6dae385..00000000000 --- a/test/unit/integrations/returns/ipay88_return_test.rb +++ /dev/null @@ -1,106 +0,0 @@ -require "test_helper" - -class Ipay88ReturnTest < Test::Unit::TestCase - include ActiveMerchant::Billing::Integrations - - def setup - @ipay88 = build_return(http_raw_data) - end - - def test_accessors - assert_equal "ipay88merchcode", @ipay88.account - assert_equal 6, @ipay88.payment - assert_equal "order-500", @ipay88.order - assert_equal "5.00", @ipay88.amount - assert_equal "MYR", @ipay88.currency - assert_equal "Remarkable", @ipay88.remark - assert_equal "12345", @ipay88.transaction - assert_equal "auth123", @ipay88.auth_code - assert_equal "1", @ipay88.status - assert_equal "Invalid merchant", @ipay88.error - assert_equal "bPlMszCBwxlfGX9ZkgmSfT+OeLQ=", @ipay88.signature - end - - def test_secure_request - assert @ipay88.secure? - end - - def test_insecure_request - assert !build_return(http_raw_data(:invalid_sig)).secure? - end - - def test_successful_return - params = parameterize(payload) - Ipay88::Return.any_instance.expects(:ssl_post).with(Ipay88.service_url, params, - { "Content-Length" => params.size.to_s, "User-Agent" => "Active Merchant -- http://activemerchant.org" } - ).returns("00") - - assert @ipay88.success? - end - - def test_unsuccessful_return_due_to_signature - ipay = build_return(http_raw_data(:invalid_sig)) - assert !ipay.success? - end - - def test_unsuccessful_return_due_to_requery - params = parameterize(payload) - Ipay88::Return.any_instance.expects(:ssl_post).with(Ipay88.service_url, params, - { "Content-Length" => params.size.to_s, "User-Agent" => "Active Merchant -- http://activemerchant.org" } - ).returns("Invalid parameters") - assert !@ipay88.success? - end - - def test_unsuccessful_return_due_to_payment_failed - params = parameterize(payload) - Ipay88::Return.any_instance.expects(:ssl_post).with(Ipay88.service_url, params, - { "Content-Length" => params.size.to_s, "User-Agent" => "Active Merchant -- http://activemerchant.org" } - ).returns("00") - ipay = build_return(http_raw_data(:payment_failed)) - assert !ipay.success? - end - - def test_unsuccessful_return_due_to_missing_amount - ipay = build_return(http_raw_data(:missing_amount)) - assert !ipay.success? - end - - private - def http_raw_data(mode=:success) - base = { "MerchantCode" => "ipay88merchcode", - "PaymentId" => 6, - "RefNo" => "order-500", - "Amount" => "5.00", - "Currency" => "MYR", - "Remark" => "Remarkable", - "TransId" => "12345", - "AuthCode" => "auth123", - "Status" => 1, - "ErrDesc" => "Invalid merchant" } - - case mode - when :success - parameterize(base.merge("Signature" => "bPlMszCBwxlfGX9ZkgmSfT+OeLQ=")) - when :invalid_sig - parameterize(base.merge("Signature" => "hacked")) - when :payment_failed - parameterize(base.merge("Status" => 0, "Signature" => "p8nXYcl/wytpNMzsf31O/iu/2EU=")) - when :missing_amount - parameterize(base.except("Amount")) - else - "" - end - end - - def payload - { "MerchantCode" => "ipay88merchcode", "RefNo" => "order-500", "Amount" => "5.00" } - end - - def parameterize(params) - params.reject{|k, v| v.blank?}.keys.sort.collect { |key| "#{key}=#{CGI.escape(params[key].to_s)}" }.join("&") - end - - def build_return(data) - Ipay88::Return.new(data, :credential2 => "apple") - end -end diff --git a/test/unit/integrations/returns/liqpay_return_test.rb b/test/unit/integrations/returns/liqpay_return_test.rb deleted file mode 100644 index 028cfaabe1f..00000000000 --- a/test/unit/integrations/returns/liqpay_return_test.rb +++ /dev/null @@ -1,16 +0,0 @@ -require 'test_helper' - -class LiqpayReturnTest < Test::Unit::TestCase - include ActiveMerchant::Billing::Integrations - - def test_return - r = Liqpay::Return.new(http_post_data) - assert r.success? - end - - private - - def http_post_data - 'signature=mOJdMHeDHlGlBY0NKZiI1wlU1BY%3D&operation_xml=PHJlc3BvbnNlPgo8c2VuZGVyX3Bob25lPis3OTA5NDM0MzMzNTwvc2VuZGVyX3Bob25lPgo8c3Rh%0AdHVzPnN1Y2Nlc3M8L3N0YXR1cz4KPHZlcnNpb24%2BMS4yPC92ZXJzaW9uPgo8b3JkZXJfaWQ%2BMTc8%0AL29yZGVyX2lkPgo8bWVyY2hhbnRfaWQ%2BaTU1MjA0Njg0OTg8L21lcmNoYW50X2lkPgo8cGF5X2Rl%0AdGFpbHM%2BPC9wYXlfZGV0YWlscz4KPGRlc2NyaXB0aW9uPsOQwp7DkMK%2Fw5DCu8OQwrDDkcKCw5DC%0AsCDDkcKHw5DCtcORwoDDkMK1w5DCtyDDkMK4w5DCvcORwoLDkMK1w5HCgMOQwr3DkMK1w5HCgi48%0AL2Rlc2NyaXB0aW9uPgo8Y3VycmVuY3k%2BUlVSPC9jdXJyZW5jeT4KPGFtb3VudD4xLjAwPC9hbW91%0AbnQ%2BCjxwYXlfd2F5PmNhcmQ8L3BheV93YXk%2BCjx0cmFuc2FjdGlvbl9pZD4yMTMxNjE0NTwvdHJh%0AbnNhY3Rpb25faWQ%2BCjxhY3Rpb24%2Bc2VydmVyX3VybDwvYWN0aW9uPgo8Y29kZT48L2NvZGU%2BCjwv%0AcmVzcG9uc2U%2B%0A' - end -end diff --git a/test/unit/integrations/returns/nochex_return_test.rb b/test/unit/integrations/returns/nochex_return_test.rb deleted file mode 100644 index eae0308928d..00000000000 --- a/test/unit/integrations/returns/nochex_return_test.rb +++ /dev/null @@ -1,10 +0,0 @@ -require 'test_helper' - -class NochexReturnTest < Test::Unit::TestCase - include ActiveMerchant::Billing::Integrations - - def test_return - r = Nochex::Return.new('') - assert r.success? - end -end \ No newline at end of file diff --git a/test/unit/integrations/returns/paypal_return_test.rb b/test/unit/integrations/returns/paypal_return_test.rb deleted file mode 100644 index 4615039dc7a..00000000000 --- a/test/unit/integrations/returns/paypal_return_test.rb +++ /dev/null @@ -1,10 +0,0 @@ -require 'test_helper' - -class PaypalReturnTest < Test::Unit::TestCase - include ActiveMerchant::Billing::Integrations - - def test_return - r = Paypal::Return.new('') - assert r.success? - end -end \ No newline at end of file diff --git a/test/unit/integrations/returns/payu_in_return_test.rb b/test/unit/integrations/returns/payu_in_return_test.rb deleted file mode 100644 index add0f355cfe..00000000000 --- a/test/unit/integrations/returns/payu_in_return_test.rb +++ /dev/null @@ -1,66 +0,0 @@ -require 'test_helper' - -class PayuInReturnTest < Test::Unit::TestCase - include ActiveMerchant::Billing::Integrations - - def setup - @payu = PayuIn::Return.new(http_raw_data_success, :credential1 => 'C0Dr8m', :credential2 => '3sf0jURk') - end - - def setup_failed_return - @payu = PayuIn::Return.new(http_raw_data_failure, :credential1 => 'C0Dr8m', :credential2 => '3sf0jURk') - end - - def test_success - assert @payu.success? - assert_equal 'Completed', @payu.status('4ba4afe87f7e73468f2a','10.00') - end - - def test_failure_is_successful - setup_failed_return - assert_equal 'Failed', @payu.status('8ae1034d1abf47fde1cf', '10.00') - end - - def test_treat_initial_failures_as_pending - setup_failed_return - assert_equal 'Failed', @payu.notification.status - end - - def test_return_has_notification - notification = @payu.notification - - assert notification.complete? - assert_equal 'Completed', notification.status - assert notification.invoice_ok?('4ba4afe87f7e73468f2a') - assert notification.amount_ok?(BigDecimal.new('10.00'),BigDecimal.new('0.00')) - assert_equal "success", notification.transaction_status - assert_equal '403993715508030204', @payu.notification.transaction_id - assert_equal 'CC', @payu.notification.type - assert_equal 'INR', notification.currency - assert_equal '4ba4afe87f7e73468f2a', notification.invoice - assert_equal 'C0Dr8m', notification.account - assert_equal '10.00', notification.gross - assert_equal '0.00', notification.discount - assert_equal nil, notification.offer_description - assert_equal 'Product Info', notification.product_info - assert_equal 'test@example.com', notification.customer_email - assert_equal '1234567890', notification.customer_phone - assert_equal 'Payu-Admin', notification.customer_first_name - assert_equal '', notification.customer_last_name - assert_equal ["", "", "", "", "", "", "", "", "", ""], notification.user_defined - assert_equal 'ef0c1b509a42b802a4938c25dc9bb9efe40b75a7dfb8bde1a6f126fa1f86cee264c5e5a17e87db85150d6d8912eafda838416e669712f1989dcb9cbdb8c24219', notification.checksum - assert_equal 'E000', notification.message - assert notification.checksum_ok? - end - - private - - def http_raw_data_success - "mihpayid=403993715508030204&mode=CC&status=success&unmappedstatus=captured&key=C0Dr8m&txnid=4ba4afe87f7e73468f2a&amount=10.00&discount=0.00&addedon=2013-05-10 18 32 30&productinfo=Product Info&firstname=Payu-Admin&lastname=&address1=&address2=&city=&state=&country=&zipcode=&email=test@example.com&phone=1234567890&udf1=&udf2=&udf3=&udf4=&udf5=&udf6=&udf7=&udf8=&udf9=&udf10=&hash=ef0c1b509a42b802a4938c25dc9bb9efe40b75a7dfb8bde1a6f126fa1f86cee264c5e5a17e87db85150d6d8912eafda838416e669712f1989dcb9cbdb8c24219&field1=313069903923&field2=999999&field3=59117331831301&field4=-1&field5=&field6=&field7=&field8=&PG_TYPE=HDFC&bank_ref_num=59117331831301&bankcode=CC&error=E000&cardnum=512345XXXXXX2346&cardhash=766f0227cc4b4c5f773a04cb31d8d1c5be071dd8d08fe365ecf5e2e5c947546d" - end - - def http_raw_data_failure - "mihpayid=403993715508030204&mode=CC&status=failure&unmappedstatus=failed&key=C0Dr8m&txnid=8ae1034d1abf47fde1cf&amount=10.00&discount=0.00&addedon=2013-05-13 11:09:20&productinfo=Product Info&firstname=Payu-Admin&lastname=&address1=&address2=&city=&state=&country=&zipcode=&email=test@example.com&phone=1234567890&udf1=&udf2=&udf3=&udf4=&udf5=&udf6=&udf7=&udf8=&udf9=&udf10=&hash=65774f82abe64cec54be31107529b2a3eef8f6a3f97a8cb81e9769f4394b890b0e7171f8988c4df3684e7f9f337035d0fe09a844da4b76e68dd643e8ac5e5c63&field1=&field2=&field3=&field4=&field5=!ERROR!-GV00103-Invalid BrandError Code: GV00103&field6=&field7=&field8=failed in enrollment&PG_TYPE=HDFC&bank_ref_num=&bankcode=CC&error=E201&cardnum=411111XXXXXX1111&cardhash=49c73d6c44f27f7ac71b439de842f91e27fcbc3b9ce9dfbcbf1ce9a8fe790c17" - end - -end diff --git a/test/unit/integrations/returns/return_test.rb b/test/unit/integrations/returns/return_test.rb deleted file mode 100644 index 969bee49058..00000000000 --- a/test/unit/integrations/returns/return_test.rb +++ /dev/null @@ -1,11 +0,0 @@ -require 'test_helper' - -class ReturnTest < Test::Unit::TestCase - include ActiveMerchant::Billing::Integrations - - - def test_return - r = Return.new('') - assert r.success? - end -end \ No newline at end of file diff --git a/test/unit/integrations/returns/sage_pay_form_return_test.rb b/test/unit/integrations/returns/sage_pay_form_return_test.rb deleted file mode 100644 index 38e92d1cb9c..00000000000 --- a/test/unit/integrations/returns/sage_pay_form_return_test.rb +++ /dev/null @@ -1,57 +0,0 @@ -require 'test_helper' - -class SagePayFormReturnTest < Test::Unit::TestCase - include ActiveMerchant::Billing::Integrations - - def setup - @options = {:credential2 => 'EncryptionKey123'} - end - - def test_successful_purchase - r = SagePayForm::Return.new(successful_purchase, @options) - assert r.success? - assert_equal 'Successfully Authorised Transaction', r.message - end - - def test_failed_purchase - r = SagePayForm::Return.new(failed_purchase, @options) - assert !r.success? - assert_equal 'NOTAUTHED message generated by Simulator', r.message - end - - def test_bogus_crypt - r = SagePayForm::Return.new('crypt=SomeInvalidCryptField', @options) - assert !r.success? - assert_equal 'Invalid data received from SagePay', r.message - end - - def test_missing_crypt - r = SagePayForm::Return.new('other=stuff', @options) - assert !r.success? - assert_equal 'No data received from SagePay', r.message - end - - def test_missing_key - r = SagePayForm::Return.new(successful_purchase, {}) - assert !r.success? - assert_equal 'No merchant decryption key supplied', r.message - end - - def test_notification - r = SagePayForm::Return.new(successful_purchase, @options) - - assert r.notification - assert_kind_of SagePayForm::Notification, r.notification - assert r.notification.complete? - assert_equal 'Successfully Authorised Transaction', r.notification.message - end - - private - def successful_purchase - 'utm_nooverride=1&crypt=FhoCBgwDSSYkSBgRGEVHQAELFxMQHEk6Gg0oAApCVEYpAhpSOAUAAQAcIhYcVRJnNw8NARgTAAAAAG0zHF9WXDc6GzEWFBFUXVZtMyliZksMCl4JSzRHXl8seydUBwsBAUNXNklHWStZX31IQABwC3MtIDY/SEEoEkgfHThERlsLAV5FSkRNTy4DJBAXRQ8AdEBXRV8xIjosOHlYOH1+EwgvNzExVjUNCxwuFgpjV0AwAhdPNDEgKicrD0MpXkFHBgEHFysVBxwDGnYoOGVxewAqRTEvQiYMHBsnEUR8c2cGJiY2XzcdDxsvIgFEARQAAT0GEQwCETobDz8QCgx9eGMtIiQvTTknKFYRJDN1eGEQJTRLSTczMDUgHy1fclNBIToaAhxNIiA8L20pGEJGBwEHBBsNA0lRXFt9' - end - - def failed_purchase - 'utm_nooverride=1&crypt=FhoCBgwDSScgOgowLXl3d2M9FxMNBQctChoqDBUMfHwRLzYmMTUwSQILOBYYVlcTIgsNFwsRAAwLTikcWWJbXjACAgYWAlI/CgAvCgtlSnAqCgZPS0hSPz89Hx0wVQ9IdypQRUkyRCtCWHJXPRwGd3VZTjBPQUJEVl8JXU9ycXcDVlYzBFY1BAAbJRFEAgMdcVlFMy8jNz9dUwopNRF/chEtK1Q4FBAbCh04NxxCR18xUy4zLTM8LCtIGwoKRXFcIQsxFwoFGB1SIwoxOnl3d2MtNUArFQccAxp2KDhlcXsAKkU1EBYAKAYKdlVfAnZgIA0WABwjAAgbGzhYNnoUcAQ4NU80PiIjNjcTPTF8cX0NXiE9LTIgUFg0bSYYQ1ZnPB4GTy85JyhJIioWDQV2WiIHFwFEREdcXA==' - end -end diff --git a/test/unit/integrations/returns/two_checkout_return_test.rb b/test/unit/integrations/returns/two_checkout_return_test.rb deleted file mode 100644 index 6d928918aa6..00000000000 --- a/test/unit/integrations/returns/two_checkout_return_test.rb +++ /dev/null @@ -1,24 +0,0 @@ -require 'test_helper' - -class TwoCheckoutReturnTest < Test::Unit::TestCase - include ActiveMerchant::Billing::Integrations - - def test_successful_purchase - r = TwoCheckout::Return.new(successful_purchase) - assert r.success? - end - - def test_pending_purchase - r = TwoCheckout::Return.new(failed_purchase) - assert !r.success? - end - - private - def successful_purchase - 'sid=1232919&fixed=Y&key=C17C887BDCCD0499264FAE9F578CCA66&state=ON&email=codyfauser%40gmail.com&street_address=138+Clarence+St.&city=Ottawa&cart_order_id=9&order_number=3860340141&merchant_order_id=%231009&country=CAN&ip_country=&cart_id=9&lang=en&demo=Y&pay_method=CC&total=118.30&phone=%28613%29555-5555+&credit_card_processed=Y&zip=K1N5P8&street_address2=Apartment+1&card_holder_name=Cody++Fauser' - end - - def failed_purchase - 'sid=1232919&fixed=Y&key=C17C887BDCCD0499264FAE9F578CCA66&state=ON&email=codyfauser%40gmail.com&street_address=138+Clarence+St.&city=Ottawa&cart_order_id=9&order_number=3860340141&merchant_order_id=%231009&country=CAN&ip_country=&cart_id=9&lang=en&demo=Y&pay_method=CC&total=118.30&phone=%28613%29555-5555+&credit_card_processed=K&zip=K1N5P8&street_address2=Apartment+1&card_holder_name=Cody++Fauser' - end -end \ No newline at end of file diff --git a/test/unit/integrations/returns/valitor_return_test.rb b/test/unit/integrations/returns/valitor_return_test.rb deleted file mode 100644 index c35d081324b..00000000000 --- a/test/unit/integrations/returns/valitor_return_test.rb +++ /dev/null @@ -1,56 +0,0 @@ -# encoding: utf-8 -require 'test_helper' - -class ValitorReturnTest < Test::Unit::TestCase - include ActiveMerchant::Billing::Integrations - - def setup - @ret = Valitor::Return.new(http_raw_query) - end - - def test_accessors - assert @ret.complete? - assert @ret.acknowledge - assert @ret.success? - assert_equal "Completed", @ret.status - assert_equal "2b969de3-6928-4fa7-a0d6-6dec63fec5c3", @ret.transaction_id - assert_equal "order684afbb93730db2492a8fa2f3fedbcb9", @ret.order - assert_equal Time.parse("2011-01-21").utc, @ret.received_at - - assert_equal "VISA", @ret.card_type - assert_equal "9999", @ret.card_last_four - assert_equal "123450", @ret.authorization_number - assert_equal "F\303\206RSLUNR: 0026237", @ret.transaction_number - assert_equal "NAME", @ret.customer_name - assert_equal "123 ADDRESS", @ret.customer_address - assert_equal "CITY", @ret.customer_city - assert_equal "98765", @ret.customer_zip - assert_equal "COUNTRY", @ret.customer_country - assert_equal "EMAIL@EXAMPLE.COM", @ret.customer_email - assert_equal "COMMENTS", @ret.customer_comment - assert_equal "100.00", @ret.gross - - assert !@ret.test? - end - - def test_acknowledge - valid = Valitor::Return.new(http_raw_query, :credential2 => 'password') - assert valid.acknowledge - assert valid.success? - assert valid.complete? - - invalid = Valitor::Return.new(http_raw_query, :credential2 => 'bogus') - assert !invalid.acknowledge - assert !invalid.success? - assert !invalid.complete? - end - - def test_test_mode - assert Valitor::Return.new(http_raw_query, :test => true).test? - assert !Valitor::Return.new(http_raw_query).test? - end - - def http_raw_query - "Kortategund=VISA&KortnumerSidustu=9999&Dagsetning=21.01.2011&Heimildarnumer=123450&Faerslunumer=FÆRSLUNR: 0026237&VefverslunSalaID=2b969de3-6928-4fa7-a0d6-6dec63fec5c3&Tilvisunarnumer=order684afbb93730db2492a8fa2f3fedbcb9&RafraenUndirskriftSvar=03d859813eff711d6c8667b0caf5f5a5&Upphaed=100&Nafn=NAME&Heimilisfang=123 ADDRESS&Postnumer=98765&Stadur=CITY&Land=COUNTRY&Tolvupostfang=EMAIL@EXAMPLE.COM&Athugasemdir=COMMENTS&LeyfirEndurtoku=" - end -end \ No newline at end of file diff --git a/test/unit/integrations/robokassa_module_test.rb b/test/unit/integrations/robokassa_module_test.rb deleted file mode 100644 index 1a069aba208..00000000000 --- a/test/unit/integrations/robokassa_module_test.rb +++ /dev/null @@ -1,32 +0,0 @@ -require 'test_helper' - -class RobokassaModuleTest < Test::Unit::TestCase - include ActiveMerchant::Billing::Integrations - - def test_helper_method - assert_instance_of Robokassa::Helper, Robokassa.helper(123, 'test') - end - - def test_notification_method - assert_instance_of Robokassa::Notification, Robokassa.notification('name=cody') - end - - def test_return_method - assert_instance_of Robokassa::Return, Robokassa.return('name=cody') - end - - def test_test_mode - ActiveMerchant::Billing::Base.integration_mode = :test - assert_equal 'http://test.robokassa.ru/Index.aspx', Robokassa.service_url - end - - def test_production_mode - ActiveMerchant::Billing::Base.integration_mode = :production - assert_equal 'https://merchant.roboxchange.com/Index.aspx', Robokassa.service_url - end - - def test_invalid_mode - ActiveMerchant::Billing::Base.integration_mode = :bro - assert_raise(StandardError){ Robokassa.service_url } - end -end diff --git a/test/unit/integrations/sage_pay_form_module_test.rb b/test/unit/integrations/sage_pay_form_module_test.rb deleted file mode 100644 index c0c2b39f1bc..00000000000 --- a/test/unit/integrations/sage_pay_form_module_test.rb +++ /dev/null @@ -1,30 +0,0 @@ -require 'test_helper' - -class SagePayFormModuleTest < Test::Unit::TestCase - include ActiveMerchant::Billing::Integrations - - def test_return_method - assert_instance_of SagePayForm::Return, SagePayForm.return('name=cody', {}) - end - - def test_production_mode - ActiveMerchant::Billing::Base.integration_mode = :production - assert_equal 'https://live.sagepay.com/gateway/service/vspform-register.vsp', SagePayForm.service_url - end - - def test_test_mode - ActiveMerchant::Billing::Base.integration_mode = :test - assert_equal 'https://test.sagepay.com/gateway/service/vspform-register.vsp', SagePayForm.service_url - end - - def test_simulate_mode - ActiveMerchant::Billing::Base.integration_mode = :simulate - assert_equal 'https://test.sagepay.com/Simulator/VSPFormGateway.asp', SagePayForm.service_url - end - - def test_invalid_mode - ActiveMerchant::Billing::Base.integration_mode = :zoomin - assert_raise(StandardError){ SagePayForm.service_url } - end - -end diff --git a/test/unit/integrations/two_checkout_module_test.rb b/test/unit/integrations/two_checkout_module_test.rb deleted file mode 100644 index 7eb248a0ac6..00000000000 --- a/test/unit/integrations/two_checkout_module_test.rb +++ /dev/null @@ -1,32 +0,0 @@ -require 'test_helper' - -class TwoCheckoutModuleTest < Test::Unit::TestCase - include ActiveMerchant::Billing::Integrations - - def test_default_service_url - assert_equal 'https://www.2checkout.com/checkout/spurchase', TwoCheckout.service_url - end - - def test_legacy_service_url_writer - TwoCheckout.service_url = 'https://www.2checkout.com/checkout/purchase' - assert_equal :multi_page, TwoCheckout.payment_routine - end - - def test_single_page_payment_routine_service_url - TwoCheckout.payment_routine = :single_page - assert_equal 'https://www.2checkout.com/checkout/spurchase', TwoCheckout.service_url - end - - def test_multi_page_payment_routine_service_url - TwoCheckout.payment_routine = :multi_page - assert_equal 'https://www.2checkout.com/checkout/purchase', TwoCheckout.service_url - end - - def test_notification_method - assert_instance_of TwoCheckout::Notification, TwoCheckout.notification('name=cody', {}) - end - - def test_return_method - assert_instance_of TwoCheckout::Return, TwoCheckout.return('name=cody', {}) - end -end diff --git a/test/unit/integrations/valitor_module_test.rb b/test/unit/integrations/valitor_module_test.rb deleted file mode 100644 index 373676cae03..00000000000 --- a/test/unit/integrations/valitor_module_test.rb +++ /dev/null @@ -1,39 +0,0 @@ -require 'test_helper' - -class ValitorModuleTest < Test::Unit::TestCase - include ActiveMerchant::Billing::Integrations - - def test_notification_method - notification = Valitor.notification('Land=USA', :credential2 => 'password') - assert_instance_of Valitor::Notification, notification - assert_equal 'password', notification.instance_eval{@options}[:credential2] - assert_equal 'USA', notification.customer_country - assert notification.test? - - ActiveMerchant::Billing::Base.integration_mode = :production - assert !Valitor.notification('Land=USA', :credential2 => 'password').test? - ensure - ActiveMerchant::Billing::Base.integration_mode = :test - end - - def test_return_method - ret = Valitor.return('Land=USA', :credential2 => 'password') - assert_instance_of Valitor::Return, ret - assert_equal 'password', ret.instance_eval{@options}[:credential2] - assert_equal 'USA', ret.customer_country - assert ret.test? - - ActiveMerchant::Billing::Base.integration_mode = :production - assert !Valitor.return('Land=USA', :credential2 => 'password').test? - ensure - ActiveMerchant::Billing::Base.integration_mode = :test - end - - def test_service_url - assert_equal "https://testvefverslun.valitor.is/1_1/", Valitor.service_url - ActiveMerchant::Billing::Base.integration_mode = :production - assert_equal "https://vefverslun.valitor.is/1_1/", Valitor.service_url - ensure - ActiveMerchant::Billing::Base.integration_mode = :test - end -end diff --git a/test/unit/integrations/verkkomaksut_module_test.rb b/test/unit/integrations/verkkomaksut_module_test.rb deleted file mode 100644 index 51d8d844452..00000000000 --- a/test/unit/integrations/verkkomaksut_module_test.rb +++ /dev/null @@ -1,9 +0,0 @@ -require 'test_helper' - -class VerkkomaksutModuleTest < Test::Unit::TestCase - include ActiveMerchant::Billing::Integrations - - def test_notification_method - assert_instance_of Verkkomaksut::Notification, Verkkomaksut.notification({"ORDER_NUMBER"=>"2", "TIMESTAMP"=>"1336058061", "PAID"=>"3DF5BB7E26", "METHOD"=>"4", "RETURN_AUTHCODE"=>"6B40F9B939D03EFE7573D61708FA4126"}) - end -end diff --git a/test/unit/integrations/web_pay_module_test.rb b/test/unit/integrations/web_pay_module_test.rb deleted file mode 100644 index d46b537e8c7..00000000000 --- a/test/unit/integrations/web_pay_module_test.rb +++ /dev/null @@ -1,28 +0,0 @@ -require 'test_helper' - -class WebPayModuleTest < Test::Unit::TestCase - include ActiveMerchant::Billing::Integrations - - def test_helper_method - assert_instance_of WebPay::Helper, WebPay.helper(123, 'test') - end - - def test_notification_method - assert_instance_of WebPay::Notification, WebPay.notification('name=cody') - end - - def test_test_mode - ActiveMerchant::Billing::Base.integration_mode = :test - assert_equal 'https://secure.sandbox.webpay.by:8843', WebPay.service_url - end - - def test_production_mode - ActiveMerchant::Billing::Base.integration_mode = :production - assert_equal 'https://secure.webpay.by', WebPay.service_url - end - - def test_invalid_mode - ActiveMerchant::Billing::Base.integration_mode = :winterfell - assert_raise(StandardError){WebPay.service_url} - end -end diff --git a/test/unit/integrations/webmoney_module_test.rb b/test/unit/integrations/webmoney_module_test.rb deleted file mode 100644 index 59357bfb4c9..00000000000 --- a/test/unit/integrations/webmoney_module_test.rb +++ /dev/null @@ -1,28 +0,0 @@ -require 'test_helper' - -class WebmoneyModuleTest < Test::Unit::TestCase - include ActiveMerchant::Billing::Integrations - - def test_helper_method - assert_instance_of Webmoney::Helper, Webmoney.helper(123, 'test') - end - - def test_notification_method - assert_instance_of Webmoney::Notification, Webmoney.notification('name=cody') - end - - def test_test_mode - ActiveMerchant::Billing::Base.integration_mode = :test - assert_equal "https://merchant.webmoney.ru/lmi/payment.asp", Webmoney.service_url - end - - def test_production_mode - ActiveMerchant::Billing::Base.integration_mode = :production - assert_equal "https://merchant.webmoney.ru/lmi/payment.asp", Webmoney.service_url - end - - def test_invalid_mode - ActiveMerchant::Billing::Base.integration_mode = :bro - assert_raise(StandardError){Webmoney.service_url} - end -end diff --git a/test/unit/integrations/world_pay_module_test.rb b/test/unit/integrations/world_pay_module_test.rb deleted file mode 100644 index 1476b712b0d..00000000000 --- a/test/unit/integrations/world_pay_module_test.rb +++ /dev/null @@ -1,22 +0,0 @@ -require 'test_helper' - -class WorldPayModuleTest < Test::Unit::TestCase - include ActiveMerchant::Billing::Integrations - - def setup - ActiveMerchant::Billing::Base.integration_mode = :test - end - - def test_service_url_in_test_mode - assert_equal 'https://secure-test.worldpay.com/wcc/purchase', WorldPay.service_url - end - - def test_service_url_in_production_mode - ActiveMerchant::Billing::Base.integration_mode = :production - assert_equal 'https://secure.worldpay.com/wcc/purchase', WorldPay.service_url - end - - def test_notification_method - assert_instance_of WorldPay::Notification, WorldPay.notification('name=Andrew White', {}) - end -end diff --git a/test/unit/multi_response_test.rb b/test/unit/multi_response_test.rb index 18a3b34dd88..a60062d0003 100644 --- a/test/unit/multi_response_test.rb +++ b/test/unit/multi_response_test.rb @@ -9,21 +9,21 @@ def test_initial_state end def test_processes_sub_requests - r1 = Response.new(true, "1", {}) - r2 = Response.new(true, "2", {}) + r1 = Response.new(true, '1', {}) + r2 = Response.new(true, '2', {}) m = MultiResponse.run do |r| - r.process{r1} - r.process{r2} + r.process { r1 } + r.process { r2 } end assert_equal [r1, r2], m.responses end def test_run_convenience_method - r1 = Response.new(true, "1", {}) - r2 = Response.new(true, "2", {}) + r1 = Response.new(true, '1', {}) + r2 = Response.new(true, '2', {}) m = MultiResponse.run do |r| - r.process{r1} - r.process{r2} + r.process { r1 } + r.process { r2 } end assert_equal [r1, r2], m.responses end @@ -33,127 +33,143 @@ def test_proxies_last_request r1 = Response.new( true, - "1", - {"one" => 1}, + '1', + {'one' => 1}, :test => true, - :authorization => "auth1", - :avs_result => {:code => "AVS1"}, - :cvv_result => "CVV1", + :authorization => 'auth1', + :avs_result => {:code => 'AVS1'}, + :cvv_result => 'CVV1', + :error_code => :card_declined, :fraud_review => true ) - m.process{r1} - assert_equal({"one" => 1}, m.params) - assert_equal "1", m.message + m.process { r1 } + assert_equal({'one' => 1}, m.params) + assert_equal '1', m.message assert m.test - assert_equal "auth1", m.authorization - assert_equal "AVS1", m.avs_result["code"] - assert_equal "CVV1", m.cvv_result["code"] + assert_equal 'auth1', m.authorization + assert_equal 'AVS1', m.avs_result['code'] + assert_equal 'CVV1', m.cvv_result['code'] + assert_equal :card_declined, m.error_code assert m.test? assert m.fraud_review? r2 = Response.new( true, - "2", - {"two" => 2}, + '2', + {'two' => 2}, :test => false, - :authorization => "auth2", - :avs_result => {:code => "AVS2"}, - :cvv_result => "CVV2", + :authorization => 'auth2', + :avs_result => {:code => 'AVS2'}, + :cvv_result => 'CVV2', :fraud_review => false ) - m.process{r2} - assert_equal({"two" => 2}, m.params) - assert_equal "2", m.message + m.process { r2 } + assert_equal({'two' => 2}, m.params) + assert_equal '2', m.message assert !m.test - assert_equal "auth2", m.authorization - assert_equal "AVS2", m.avs_result["code"] - assert_equal "CVV2", m.cvv_result["code"] + assert_equal 'auth2', m.authorization + assert_equal 'AVS2', m.avs_result['code'] + assert_equal 'CVV2', m.cvv_result['code'] assert !m.test? assert !m.fraud_review? end def test_proxies_first_request_if_marked - m = MultiResponse.new - m.primary_response = :first + m = MultiResponse.new(:use_first_response) r1 = Response.new( true, - "1", - {"one" => 1}, + '1', + {'one' => 1}, :test => true, - :authorization => "auth1", - :avs_result => {:code => "AVS1"}, - :cvv_result => "CVV1", + :authorization => 'auth1', + :avs_result => {:code => 'AVS1'}, + :cvv_result => 'CVV1', :fraud_review => true ) - m.process{r1} - assert_equal({"one" => 1}, m.params) - assert_equal "1", m.message + m.process { r1 } + assert_equal({'one' => 1}, m.params) + assert_equal '1', m.message assert m.test - assert_equal "auth1", m.authorization - assert_equal "AVS1", m.avs_result["code"] - assert_equal "CVV1", m.cvv_result["code"] + assert_equal 'auth1', m.authorization + assert_equal 'AVS1', m.avs_result['code'] + assert_equal 'CVV1', m.cvv_result['code'] assert m.test? assert m.fraud_review? r2 = Response.new( true, - "2", - {"two" => 2}, + '2', + {'two' => 2}, :test => false, - :authorization => "auth2", - :avs_result => {:code => "AVS2"}, - :cvv_result => "CVV2", + :authorization => 'auth2', + :avs_result => {:code => 'AVS2'}, + :cvv_result => 'CVV2', :fraud_review => false ) - m.process{r2} - assert_equal({"one" => 1}, m.params) - assert_equal "1", m.message + m.process { r2 } + assert_equal({'one' => 1}, m.params) + assert_equal '1', m.message assert m.test - assert_equal "auth1", m.authorization - assert_equal "AVS1", m.avs_result["code"] - assert_equal "CVV1", m.cvv_result["code"] + assert_equal 'auth1', m.authorization + assert_equal 'AVS1', m.avs_result['code'] + assert_equal 'CVV1', m.cvv_result['code'] assert m.test? assert m.fraud_review? end def test_primary_response_always_returns_the_last_response_on_failure - m = MultiResponse.new - m.primary_response = :first - - r1 = Response.new(true, "1", {}, {}) - r2 = Response.new(false, "2", {}, {}) - r3 = Response.new(false, "3", {}, {}) - m.process{r1} - m.process{r2} - m.process{r3} + m = MultiResponse.new(:use_first_response) + + r1 = Response.new(true, '1', {}, {}) + r2 = Response.new(false, '2', {}, {}) + r3 = Response.new(false, '3', {}, {}) + m.process { r1 } + m.process { r2 } + m.process { r3 } assert_equal r2, m.primary_response assert_equal '2', m.message end def test_stops_processing_upon_failure - r1 = Response.new(false, "1", {}) - r2 = Response.new(true, "2", {}) + r1 = Response.new(false, '1', {}) + r2 = Response.new(true, '2', {}) m = MultiResponse.run do |r| - r.process{r1} - r.process{r2} + r.process { r1 } + r.process { r2 } end assert !m.success? assert_equal [r1], m.responses end def test_merges_sub_multi_responses - r1 = Response.new(true, "1", {}) - r2 = Response.new(true, "2", {}) - r3 = Response.new(true, "3", {}) + r1 = Response.new(true, '1', {}) + r2 = Response.new(true, '2', {}) + r3 = Response.new(true, '3', {}) m1 = MultiResponse.run do |r| - r.process{r1} - r.process{r2} + r.process { r1 } + r.process { r2 } end m = MultiResponse.run do |r| - r.process{m1} - r.process{r3} + r.process { m1 } + r.process { r3 } end assert_equal [r1, r2, r3], m.responses end + + def test_handles_ignores_optional_request_result + m = MultiResponse.new + + r1 = Response.new(true, '1') + m.process { r1 } + assert_equal '1', m.message + assert_equal [r1], m.responses + + r2 = Response.new(false, '2') + m.process(:ignore_result) { r2 } + assert_equal '1', m.message + assert_equal [r1, r2], m.responses + + assert m.success? + end end diff --git a/test/unit/network_connection_retries_test.rb b/test/unit/network_connection_retries_test.rb new file mode 100644 index 00000000000..49ce4b3d8e6 --- /dev/null +++ b/test/unit/network_connection_retries_test.rb @@ -0,0 +1,189 @@ +require 'test_helper' + +class NetworkConnectionRetriesTest < Test::Unit::TestCase + class MyNewError < StandardError + end + + include ActiveMerchant::NetworkConnectionRetries + + def setup + @logger = stubs(:logger) + @requester = stubs(:requester) + @ok = stub(:code => 200, :message => 'OK', :body => 'success') + end + + def test_eoferror_raises_correctly + raised = assert_raises(ActiveMerchant::ConnectionError) do + retry_exceptions do + raise EOFError + end + end + + assert_equal 'The remote server dropped the connection', raised.message + end + + def test_econnreset_raises_correctly + raised = assert_raises(ActiveMerchant::ConnectionError) do + retry_exceptions do + raise Errno::ECONNRESET + end + end + assert_equal 'The remote server reset the connection', raised.message + end + + def test_timeout_errors_raise_correctly + exceptions = [Timeout::Error, Errno::ETIMEDOUT, Net::ReadTimeout, Net::OpenTimeout] + + exceptions.each do |exception| + raised = assert_raises(ActiveMerchant::ConnectionError) do + retry_exceptions do + raise exception + end + end + assert_equal 'The connection to the remote server timed out', raised.message + end + end + + def test_socket_error_raises_correctly + raised = assert_raises(ActiveMerchant::ConnectionError) do + retry_exceptions do + raise SocketError + end + end + assert_equal 'The connection to the remote server could not be established', raised.message + end + + def test_ssl_errors_raise_correctly + exceptions = [OpenSSL::SSL::SSLError, OpenSSL::SSL::SSLErrorWaitWritable, + OpenSSL::SSL::SSLErrorWaitReadable] + + exceptions.each do |exception| + raised = assert_raises(ActiveMerchant::ConnectionError) do + retry_exceptions do + raise exception + end + end + assert_equal 'The SSL connection to the remote server could not be established', raised.message + end + end + + def test_invalid_response_error + assert_raises(ActiveMerchant::InvalidResponseError) do + retry_exceptions do + raise Zlib::BufError + end + end + end + + def test_unrecoverable_exception_logged_if_logger_provided + @logger.expects(:info).once + assert_raises(ActiveMerchant::ConnectionError) do + retry_exceptions :logger => @logger do + raise EOFError + end + end + end + + def test_failure_then_success_with_recoverable_exception + @requester.expects(:post).times(2).raises(Errno::ECONNREFUSED).then.returns(@ok) + + retry_exceptions do + @requester.post + end + end + + def test_failure_limit_reached + @requester.expects(:post).times(ActiveMerchant::NetworkConnectionRetries::DEFAULT_RETRIES).raises(Errno::ECONNREFUSED) + + assert_raises(ActiveMerchant::ConnectionError) do + retry_exceptions do + @requester.post + end + end + end + + def test_failure_limit_reached_logs_final_error + @logger.expects(:info).times(3) + @requester.expects(:post).times(ActiveMerchant::NetworkConnectionRetries::DEFAULT_RETRIES).raises(Errno::ECONNREFUSED) + + assert_raises(ActiveMerchant::ConnectionError) do + retry_exceptions(:logger => @logger) do + @requester.post + end + end + end + + def test_failure_then_success_with_retry_safe_enabled + @requester.expects(:post).times(2).raises(EOFError).then.returns(@ok) + + retry_exceptions :retry_safe => true do + @requester.post + end + end + + def test_failure_then_success_logs_success + @logger.expects(:info).with(regexp_matches(/dropped/)) + @logger.expects(:info).with(regexp_matches(/success/)) + @requester.expects(:post).times(2).raises(EOFError).then.returns(@ok) + + retry_exceptions(:logger => @logger, :retry_safe => true) do + @requester.post + end + end + + def test_mixture_of_failures_with_retry_safe_enabled + @requester. + expects(:post). + times(3). + raises(Errno::ECONNRESET). + raises(Errno::ECONNREFUSED). + raises(EOFError) + + assert_raises(ActiveMerchant::ConnectionError) do + retry_exceptions :retry_safe => true do + @requester.post + end + end + end + + def test_failure_with_ssl_certificate + @requester.expects(:post).raises(OpenSSL::X509::CertificateError) + + assert_raises(ActiveMerchant::ClientCertificateError) do + retry_exceptions do + @requester.post + end + end + end + + def test_failure_with_ssl_certificate_logs_error_if_logger_specified + @logger.expects(:error).once + @requester.expects(:post).raises(OpenSSL::X509::CertificateError) + + assert_raises(ActiveMerchant::ClientCertificateError) do + retry_exceptions :logger => @logger do + @requester.post + end + end + end + + def test_failure_with_additional_exceptions_specified + @requester.expects(:post).raises(MyNewError) + + assert_raises(ActiveMerchant::ConnectionError) do + retry_exceptions :connection_exceptions => {MyNewError => 'my message'} do + @requester.post + end + end + end + + def test_failure_without_additional_exceptions_specified + @requester.expects(:post).raises(MyNewError) + + assert_raises(MyNewError) do + retry_exceptions do + @requester.post + end + end + end +end diff --git a/test/unit/network_tokenization_credit_card_test.rb b/test/unit/network_tokenization_credit_card_test.rb new file mode 100644 index 00000000000..a5ac80e822d --- /dev/null +++ b/test/unit/network_tokenization_credit_card_test.rb @@ -0,0 +1,48 @@ +require 'test_helper' + +class NetworkTokenizationCreditCardTest < Test::Unit::TestCase + + def setup + @tokenized_card = ActiveMerchant::Billing::NetworkTokenizationCreditCard.new({ + number: '4242424242424242', :brand => 'visa', + month: default_expiration_date.month, year: default_expiration_date.year, + payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=', eci: '05' + }) + @tokenized_apple_pay_card = ActiveMerchant::Billing::NetworkTokenizationCreditCard.new({ + source: :apple_pay + }) + @tokenized_android_pay_card = ActiveMerchant::Billing::NetworkTokenizationCreditCard.new({ + source: :android_pay + }) + @tokenized_google_pay_card = ActiveMerchant::Billing::NetworkTokenizationCreditCard.new({ + source: :google_pay + }) + @tokenized_bogus_pay_card = ActiveMerchant::Billing::NetworkTokenizationCreditCard.new({ + source: :bogus_pay + }) + end + + def test_type + assert_equal 'network_tokenization', @tokenized_card.type + end + + def test_credit_card? + assert @tokenized_card.credit_card? + assert @tokenized_apple_pay_card.credit_card? + assert @tokenized_android_pay_card.credit_card? + assert @tokenized_google_pay_card.credit_card? + assert @tokenized_bogus_pay_card.credit_card? + end + + def test_optional_validations + assert_valid @tokenized_card, 'Network tokenization card should not require name or verification value' + end + + def test_source + assert_equal @tokenized_card.source, :apple_pay + assert_equal @tokenized_apple_pay_card.source, :apple_pay + assert_equal @tokenized_android_pay_card.source, :android_pay + assert_equal @tokenized_google_pay_card.source, :google_pay + assert_equal @tokenized_bogus_pay_card.source, :apple_pay + end +end diff --git a/test/unit/post_data_test.rb b/test/unit/post_data_test.rb new file mode 100644 index 00000000000..dd22426f862 --- /dev/null +++ b/test/unit/post_data_test.rb @@ -0,0 +1,50 @@ +require 'test_helper' + +class MyPost < ActiveMerchant::PostData + self.required_fields = [ :ccnumber, :ccexp, :firstname, :lastname, :username, :password, :order_id, :key, :time ] +end + +class PostDataTest < Test::Unit::TestCase + def teardown + ActiveMerchant::PostData.required_fields = [] + end + + def test_element_assignment + name = 'Cody Fauser' + post = ActiveMerchant::PostData.new + + post[:name] = name + assert_equal name, post[:name] + end + + def test_ignore_blank_fields + post = ActiveMerchant::PostData.new + assert_equal 0, post.keys.size + + post[:name] = '' + assert_equal 0, post.keys.size + + post[:name] = nil + assert_equal 0, post.keys.size + end + + def test_dont_ignore_required_blank_fields + ActiveMerchant::PostData.required_fields = [ :name ] + post = ActiveMerchant::PostData.new + + assert_equal 0, post.keys.size + + post[:name] = '' + assert_equal 1, post.keys.size + assert_equal '', post[:name] + + post[:name] = nil + assert_equal 1, post.keys.size + assert_nil post[:name] + end + + def test_subclass + post = MyPost.new + assert_equal [ :ccnumber, :ccexp, :firstname, :lastname, :username, :password, :order_id, :key, :time ], post.required_fields + end +end diff --git a/test/unit/posts_data_test.rb b/test/unit/posts_data_test.rb index 405abd30433..960992cd0d4 100644 --- a/test/unit/posts_data_test.rb +++ b/test/unit/posts_data_test.rb @@ -8,25 +8,25 @@ def setup @ok = stub(:body => '', :code => '200', :message => 'OK') @error = stub(:code => 500, :message => 'Internal Server Error', :body => 'failure') end - + def teardown SimpleTestGateway.retry_safe = false end - + def test_single_successful_post ActiveMerchant::Connection.any_instance.expects(:request).returns(@ok) - + assert_nothing_raised do - @gateway.ssl_post(@url, '') + @gateway.ssl_post(@url, '') end end - + def test_multiple_successful_posts ActiveMerchant::Connection.any_instance.expects(:request).times(2).returns(@ok, @ok) - + assert_nothing_raised do @gateway.ssl_post(@url, '') - @gateway.ssl_post(@url, '') + @gateway.ssl_post(@url, '') end end @@ -42,6 +42,14 @@ def test_successful_raw_request assert_equal @ok, @gateway.raw_ssl_request(:post, @url, '') end + def test_raw_ssl_request_does_not_mutate_headers_argument + ActiveMerchant::Connection.any_instance.expects(:request).returns(@ok) + + headers = { 'Content-Type' => 'text/xml' }.freeze + @gateway.raw_ssl_request(:post, @url, '', headers) + assert_equal({ 'Content-Type' => 'text/xml' }, headers) + end + def test_setting_ssl_strict_outside_class_definition assert_equal SimpleTestGateway.ssl_strict, SubclassGateway.ssl_strict SimpleTestGateway.ssl_strict = !SimpleTestGateway.ssl_strict @@ -59,4 +67,16 @@ def test_setting_timeouts @gateway.ssl_post(@url, '') end end + + def test_setting_proxy_settings + @gateway.class.proxy_address = 'http://proxy.com' + @gateway.class.proxy_port = 1234 + ActiveMerchant::Connection.any_instance.expects(:request).returns(@ok) + ActiveMerchant::Connection.any_instance.expects(:proxy_address=).with('http://proxy.com') + ActiveMerchant::Connection.any_instance.expects(:proxy_port=).with(1234) + + assert_nothing_raised do + @gateway.ssl_post(@url, '') + end + end end diff --git a/test/unit/rails_compatibility_test.rb b/test/unit/rails_compatibility_test.rb new file mode 100644 index 00000000000..addcb3ec701 --- /dev/null +++ b/test/unit/rails_compatibility_test.rb @@ -0,0 +1,14 @@ +require 'test_helper' + +class RailsCompatibilityTest < Test::Unit::TestCase + def test_should_be_able_to_access_errors_indifferently + cc = credit_card('4779139500118580', :first_name => '') + + silence_deprecation_warnings do + assert !cc.valid? + assert cc.errors.on(:first_name) + assert cc.errors.on('first_name') + assert_equal 'cannot be empty', cc.errors.on(:first_name) + end + end +end